This commit is contained in:
2026-08-19 10:50:21 +02:00
parent 695449442b
commit 5e21331cde
8 changed files with 110 additions and 102 deletions
+1 -1
View File
@@ -1,8 +1,8 @@
package fr.gasser.autosnake;
import fr.gasser.autosnake.core.AStarSearch;
import fr.gasser.autosnake.model.Direction;
import fr.gasser.autosnake.model.Snake;
import fr.gasser.autosnake.path.AStarSearch;
import javax.swing.*;
import java.awt.*;
-5
View File
@@ -1,12 +1,10 @@
package fr.gasser.autosnake;
import fr.gasser.autosnake.core.AStarSearch;
import fr.gasser.autosnake.model.Snake;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Queue;
public class GamePanel extends JPanel {
@@ -61,9 +59,6 @@ public class GamePanel extends JPanel {
final Rectangle head = model.getHead();
if (head != null) g2d.fill(head);
// Avoid choppy movement when not moving mouse cursor
// https://stackoverflow.com/a/26388175
Toolkit.getDefaultToolkit().sync();
+66 -90
View File
@@ -1,12 +1,11 @@
package fr.gasser.autosnake.model;
import fr.gasser.autosnake.core.AStarSearch;
import fr.gasser.autosnake.core.Cell;
import fr.gasser.autosnake.core.Grid;
import fr.gasser.autosnake.path.AStarSearch;
import fr.gasser.autosnake.path.Cell;
import fr.gasser.autosnake.path.Grid;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.stream.Stream;
@@ -23,7 +22,7 @@ public class Snake {
this.cellSize = cellSize;
this.head = new V2d(5 * cellSize, 5 * cellSize);
this.length = cellSize;
this.trail = new ArrayList<>(); // TODO trim trail
this.trail = new ArrayList<>();
this.bounds = new V2d(screen.width, screen.height);
this.apple = this.randomApple();
}
@@ -44,8 +43,7 @@ public class Snake {
this.speed += 10;
}
trimTrail(); // trim excess trail
trimTrail(); // trim excess trail.
// check for collisions with snake body
var collisionPoint = checkBodyCollisions();
@@ -57,53 +55,26 @@ public class Snake {
return false;
}
private Point checkBodyCollisions() {
Point headCell = cellOf(head);
boolean leftHeadCell = false;
for (int i = trail.size() - 1; i >= 0; --i) {
var segment = trail.get(i);
Point bodyCell = cellOf(segment);
// Ignore the contiguous trail portion created while the head
// was moving through its current cell.
if (!leftHeadCell) {
if (bodyCell.equals(headCell)) {
private V2d checkBodyCollisions() {
boolean leftHead = false;
for (V2d segment : sampleTrail()) {
double dist = segment.wrap(bounds).wrappedDist(head, bounds);
// Do not collide the head with itself
if (!leftHead) {
if (dist < cellSize) {
continue;
}
leftHeadCell = true;
leftHead = true;
}
if (headCell.equals(bodyCell)) {
return headCell;
if (dist < cellSize) {
return segment;
}
}
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() {
int trimPoint;
V2d current = head;
@@ -132,19 +103,48 @@ public class Snake {
var wrappedHead = this.head.wrap(this.bounds);
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 gridSize = new Dimension((int) (bounds.x / cellSize), (int) (bounds.y / cellSize));
var grid = new Grid() {
@Override
public Dimension getSize() {
return new Dimension((int) (bounds.x / cellSize), (int) (bounds.y / cellSize));
return gridSize;
}
@Override
public Cell get(Point loc) {
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
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);
@@ -155,11 +155,15 @@ public class Snake {
}
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 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) {
var previous = this.trail.get(i);
var dx = previous.x - current.x;
@@ -168,20 +172,19 @@ public class Snake {
if (d > remaining) {
// The tail ends somewhere between current and previous.
var ratio = remaining / d;
var tailX = current.x + dx * ratio;
var tailY = current.y + dy * ratio;
visibleTrail.add(getRect(tailX, tailY));
visibleTrail.add(new V2d(tailX, tailY));
break;
}
visibleTrail.add(getRect(previous.x, previous.y));
visibleTrail.add(previous);
remaining -= d;
current = previous;
}
return visibleTrail.stream();
return visibleTrail;
}
public Rectangle getHead() {
@@ -196,7 +199,12 @@ public class Snake {
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);
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
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);
}
}
}
+38
View File
@@ -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;
}
}
@@ -1,9 +1,9 @@
package fr.gasser.autosnake.core;
package fr.gasser.autosnake.path;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.Queue;
import java.util.*;
public class AStarSearch {
private final Grid grid;
@@ -1,4 +1,4 @@
package fr.gasser.autosnake.core;
package fr.gasser.autosnake.path;
public interface Cell {
boolean isObstacle();
@@ -1,6 +1,5 @@
package fr.gasser.autosnake.core;
package fr.gasser.autosnake.path;
import java.awt.*;
import java.util.Random;
public enum Direction {
UP(0, -1),
@@ -1,4 +1,4 @@
package fr.gasser.autosnake.core;
package fr.gasser.autosnake.path;
import java.awt.*;