refactor
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
package fr.gasser.autosnake;
|
package fr.gasser.autosnake;
|
||||||
|
|
||||||
import fr.gasser.autosnake.core.AStarSearch;
|
|
||||||
import fr.gasser.autosnake.model.Direction;
|
import fr.gasser.autosnake.model.Direction;
|
||||||
import fr.gasser.autosnake.model.Snake;
|
import fr.gasser.autosnake.model.Snake;
|
||||||
|
import fr.gasser.autosnake.path.AStarSearch;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package fr.gasser.autosnake;
|
package fr.gasser.autosnake;
|
||||||
|
|
||||||
import fr.gasser.autosnake.core.AStarSearch;
|
|
||||||
import fr.gasser.autosnake.model.Snake;
|
import fr.gasser.autosnake.model.Snake;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
public class GamePanel extends JPanel {
|
public class GamePanel extends JPanel {
|
||||||
|
|
||||||
@@ -61,9 +59,6 @@ public class GamePanel extends JPanel {
|
|||||||
final Rectangle head = model.getHead();
|
final Rectangle head = model.getHead();
|
||||||
if (head != null) g2d.fill(head);
|
if (head != null) g2d.fill(head);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Avoid choppy movement when not moving mouse cursor
|
// Avoid choppy movement when not moving mouse cursor
|
||||||
// https://stackoverflow.com/a/26388175
|
// https://stackoverflow.com/a/26388175
|
||||||
Toolkit.getDefaultToolkit().sync();
|
Toolkit.getDefaultToolkit().sync();
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package fr.gasser.autosnake.model;
|
package fr.gasser.autosnake.model;
|
||||||
|
|
||||||
import fr.gasser.autosnake.core.AStarSearch;
|
import fr.gasser.autosnake.path.AStarSearch;
|
||||||
import fr.gasser.autosnake.core.Cell;
|
import fr.gasser.autosnake.path.Cell;
|
||||||
import fr.gasser.autosnake.core.Grid;
|
import fr.gasser.autosnake.path.Grid;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ public class Snake {
|
|||||||
this.cellSize = cellSize;
|
this.cellSize = cellSize;
|
||||||
this.head = new V2d(5 * cellSize, 5 * cellSize);
|
this.head = new V2d(5 * cellSize, 5 * cellSize);
|
||||||
this.length = cellSize;
|
this.length = cellSize;
|
||||||
this.trail = new ArrayList<>(); // TODO trim trail
|
this.trail = new ArrayList<>();
|
||||||
this.bounds = new V2d(screen.width, screen.height);
|
this.bounds = new V2d(screen.width, screen.height);
|
||||||
this.apple = this.randomApple();
|
this.apple = this.randomApple();
|
||||||
}
|
}
|
||||||
@@ -44,8 +43,7 @@ public class Snake {
|
|||||||
this.speed += 10;
|
this.speed += 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
trimTrail(); // trim excess trail
|
trimTrail(); // trim excess trail.
|
||||||
|
|
||||||
|
|
||||||
// check for collisions with snake body
|
// check for collisions with snake body
|
||||||
var collisionPoint = checkBodyCollisions();
|
var collisionPoint = checkBodyCollisions();
|
||||||
@@ -57,53 +55,26 @@ public class Snake {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Point checkBodyCollisions() {
|
private V2d checkBodyCollisions() {
|
||||||
Point headCell = cellOf(head);
|
boolean leftHead = false;
|
||||||
boolean leftHeadCell = false;
|
for (V2d segment : sampleTrail()) {
|
||||||
for (int i = trail.size() - 1; i >= 0; --i) {
|
double dist = segment.wrap(bounds).wrappedDist(head, bounds);
|
||||||
var segment = trail.get(i);
|
|
||||||
Point bodyCell = cellOf(segment);
|
// Do not collide the head with itself
|
||||||
// Ignore the contiguous trail portion created while the head
|
if (!leftHead) {
|
||||||
// was moving through its current cell.
|
if (dist < cellSize) {
|
||||||
if (!leftHeadCell) {
|
|
||||||
if (bodyCell.equals(headCell)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
leftHeadCell = true;
|
leftHead = true;
|
||||||
}
|
}
|
||||||
if (headCell.equals(bodyCell)) {
|
|
||||||
return headCell;
|
if (dist < cellSize) {
|
||||||
|
return segment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HashSet<Point> getBodyCells() {
|
|
||||||
Point headCell = cellOf(head);
|
|
||||||
HashSet<Point> cells = new HashSet<>();
|
|
||||||
boolean leftHeadCell = false;
|
|
||||||
for (int i = trail.size() - 1; i >= 0; --i) {
|
|
||||||
Point bodyCell = cellOf(trail.get(i));
|
|
||||||
|
|
||||||
// Ignore all historical samples belonging to the head's
|
|
||||||
// current cell. These are the neck/current-cell samples.
|
|
||||||
if (!leftHeadCell) {
|
|
||||||
if (bodyCell.equals(headCell)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
leftHeadCell = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
cells.add(bodyCell);
|
|
||||||
}
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point cellOf(V2d pos) {
|
|
||||||
var wrapped = pos.wrap(bounds);
|
|
||||||
return new Point((int) (wrapped.x / cellSize), (int) (wrapped.y / cellSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void trimTrail() {
|
private void trimTrail() {
|
||||||
int trimPoint;
|
int trimPoint;
|
||||||
V2d current = head;
|
V2d current = head;
|
||||||
@@ -132,19 +103,48 @@ public class Snake {
|
|||||||
var wrappedHead = this.head.wrap(this.bounds);
|
var wrappedHead = this.head.wrap(this.bounds);
|
||||||
var start = new Point((int) (wrappedHead.x / cellSize), (int) (wrappedHead.y / cellSize));
|
var start = new Point((int) (wrappedHead.x / cellSize), (int) (wrappedHead.y / cellSize));
|
||||||
var goal = new Point((int) (this.apple.x / cellSize), (int) (this.apple.y / cellSize));
|
var goal = new Point((int) (this.apple.x / cellSize), (int) (this.apple.y / cellSize));
|
||||||
|
var gridSize = new Dimension((int) (bounds.x / cellSize), (int) (bounds.y / cellSize));
|
||||||
var grid = new Grid() {
|
var grid = new Grid() {
|
||||||
@Override
|
@Override
|
||||||
public Dimension getSize() {
|
public Dimension getSize() {
|
||||||
return new Dimension((int) (bounds.x / cellSize), (int) (bounds.y / cellSize));
|
return gridSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cell get(Point loc) {
|
public Cell get(Point loc) {
|
||||||
V2d pos = new V2d(loc.x * cellSize, loc.y * cellSize);
|
V2d pos = new V2d(loc.x * cellSize, loc.y * cellSize);
|
||||||
if (pos.wrappedDist(wrappedHead, bounds) < cellSize) return () -> true;
|
/*if (pos.wrappedDist(wrappedHead, bounds) < cellSize) return () -> true;
|
||||||
var bodyCells = getBodyCells(); // TODO inline trail sampling in here to remove cellOf
|
var bodyCells = getBodyCells(); // TODO inline trail sampling in here to remove cellOf
|
||||||
if (bodyCells.contains(loc)) return () -> true; // body is an obstacle
|
if (bodyCells.contains(loc)) return () -> true; // body is an obstacle
|
||||||
return () -> false;
|
return () -> false;*/
|
||||||
|
return () -> checkCollisions(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCollisions(V2d pos) {
|
||||||
|
var trail = sampleTrail();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet<Point> getBodyCells() {
|
||||||
|
Point headCell = cellOf(head);
|
||||||
|
HashSet<Point> cells = new HashSet<>();
|
||||||
|
boolean leftHeadCell = false;
|
||||||
|
for (int i = trail.size() - 1; i >= 0; --i) {
|
||||||
|
Point bodyCell = cellOf(trail.get(i));
|
||||||
|
|
||||||
|
// Ignore all historical samples belonging to the head's
|
||||||
|
// current cell. These are the neck/current-cell samples.
|
||||||
|
if (!leftHeadCell) {
|
||||||
|
if (bodyCell.equals(headCell)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
leftHeadCell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
cells.add(bodyCell);
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return new AStarSearch(grid, start, goal, maxIterations);
|
return new AStarSearch(grid, start, goal, maxIterations);
|
||||||
@@ -155,11 +155,15 @@ public class Snake {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Stream<Rectangle> getTrail() {
|
public Stream<Rectangle> getTrail() {
|
||||||
var visibleTrail = new ArrayList<Rectangle>();
|
return this.trail.stream().map(v -> getRect(v.x, v.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<V2d> sampleTrail() {
|
||||||
|
// Sample points from the trail until the accumulated distance is >= this.length.
|
||||||
|
var visibleTrail = new ArrayList<V2d>();
|
||||||
var remaining = this.length;
|
var remaining = this.length;
|
||||||
var current = this.head;
|
var current = this.head;
|
||||||
|
|
||||||
// Sample points from the trail every cellSize until the accumulated distance is >= this.length.
|
|
||||||
for (var i = this.trail.size() - 1; i >= 0 && remaining > 0; --i) {
|
for (var i = this.trail.size() - 1; i >= 0 && remaining > 0; --i) {
|
||||||
var previous = this.trail.get(i);
|
var previous = this.trail.get(i);
|
||||||
var dx = previous.x - current.x;
|
var dx = previous.x - current.x;
|
||||||
@@ -168,20 +172,19 @@ public class Snake {
|
|||||||
if (d > remaining) {
|
if (d > remaining) {
|
||||||
// The tail ends somewhere between current and previous.
|
// The tail ends somewhere between current and previous.
|
||||||
var ratio = remaining / d;
|
var ratio = remaining / d;
|
||||||
|
|
||||||
var tailX = current.x + dx * ratio;
|
var tailX = current.x + dx * ratio;
|
||||||
var tailY = current.y + dy * ratio;
|
var tailY = current.y + dy * ratio;
|
||||||
|
visibleTrail.add(new V2d(tailX, tailY));
|
||||||
visibleTrail.add(getRect(tailX, tailY));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
visibleTrail.add(getRect(previous.x, previous.y));
|
|
||||||
|
visibleTrail.add(previous);
|
||||||
|
|
||||||
remaining -= d;
|
remaining -= d;
|
||||||
current = previous;
|
current = previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return visibleTrail;
|
||||||
return visibleTrail.stream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle getHead() {
|
public Rectangle getHead() {
|
||||||
@@ -196,7 +199,12 @@ public class Snake {
|
|||||||
return new V2d(column * cellSize, row * cellSize);
|
return new V2d(column * cellSize, row * cellSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Rectangle getRect(double x, double y) {
|
private Point cellOf(V2d pos) {
|
||||||
|
var wrapped = pos.wrap(bounds);
|
||||||
|
return new Point((int) (wrapped.x / cellSize), (int) (wrapped.y / cellSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rectangle getRect(double x, double y) {
|
||||||
var wrapped = new V2d(x, y).wrap(bounds);
|
var wrapped = new V2d(x, y).wrap(bounds);
|
||||||
return new Rectangle((int) wrapped.x, (int) wrapped.y, cellSize, cellSize);
|
return new Rectangle((int) wrapped.x, (int) wrapped.y, cellSize, cellSize);
|
||||||
}
|
}
|
||||||
@@ -205,36 +213,4 @@ private Rectangle getRect(double x, double y) {
|
|||||||
// mod that works correctly for negative numbers
|
// mod that works correctly for negative numbers
|
||||||
return ((a % b) + b) % b;
|
return ((a % b) + b) % b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class V2d {
|
|
||||||
private final double x;
|
|
||||||
private final double y;
|
|
||||||
|
|
||||||
public V2d(double x, double y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("x=%s y=%s", x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double wrappedDist(V2d other, V2d bounds) {
|
|
||||||
var dx = Math.abs(this.x - other.x);
|
|
||||||
var dy = Math.abs(this.y - other.y);
|
|
||||||
|
|
||||||
dx = Math.min(dx, bounds.x - dx);
|
|
||||||
dy = Math.min(dy, bounds.y - dy);
|
|
||||||
|
|
||||||
return Math.hypot(dx, dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private V2d wrap(V2d bounds) {
|
|
||||||
// convert world space coordinates to wrapped coordinates
|
|
||||||
var newX = trueMod(x, bounds.x);
|
|
||||||
var newY = trueMod(y, bounds.y);
|
|
||||||
return new V2d(newX, newY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package fr.gasser.autosnake.model;
|
||||||
|
|
||||||
|
class V2d {
|
||||||
|
final double x;
|
||||||
|
final double y;
|
||||||
|
|
||||||
|
V2d(double x, double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("x=%s y=%s", x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
double wrappedDist(V2d other, V2d bounds) {
|
||||||
|
var dx = Math.abs(this.x - other.x);
|
||||||
|
var dy = Math.abs(this.y - other.y);
|
||||||
|
|
||||||
|
dx = Math.min(dx, bounds.x - dx);
|
||||||
|
dy = Math.min(dy, bounds.y - dy);
|
||||||
|
|
||||||
|
return Math.hypot(dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
V2d wrap(V2d bounds) {
|
||||||
|
// convert world space coordinates to wrapped coordinates
|
||||||
|
var newX = trueMod(x, bounds.x);
|
||||||
|
var newY = trueMod(y, bounds.y);
|
||||||
|
return new V2d(newX, newY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double trueMod(double a, double b) {
|
||||||
|
// mod that works correctly for negative numbers
|
||||||
|
return ((a % b) + b) % b;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
package fr.gasser.autosnake.core;
|
package fr.gasser.autosnake.path;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class AStarSearch {
|
public class AStarSearch {
|
||||||
private final Grid grid;
|
private final Grid grid;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package fr.gasser.autosnake.core;
|
package fr.gasser.autosnake.path;
|
||||||
|
|
||||||
public interface Cell {
|
public interface Cell {
|
||||||
boolean isObstacle();
|
boolean isObstacle();
|
||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
package fr.gasser.autosnake.core;
|
package fr.gasser.autosnake.path;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
public enum Direction {
|
public enum Direction {
|
||||||
UP(0, -1),
|
UP(0, -1),
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package fr.gasser.autosnake.core;
|
package fr.gasser.autosnake.path;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
Reference in New Issue
Block a user