From 5e21331cdeb0d1de46a5a61ed94964a7c8622254 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Wed, 19 Aug 2026 10:50:21 +0200 Subject: [PATCH] refactor --- src/fr/gasser/autosnake/Game.java | 2 +- src/fr/gasser/autosnake/GamePanel.java | 5 - src/fr/gasser/autosnake/model/Snake.java | 156 ++++++++---------- src/fr/gasser/autosnake/model/V2d.java | 38 +++++ .../autosnake/{core => path}/AStarSearch.java | 4 +- .../gasser/autosnake/{core => path}/Cell.java | 2 +- .../autosnake/{core => path}/Direction.java | 3 +- .../gasser/autosnake/{core => path}/Grid.java | 2 +- 8 files changed, 110 insertions(+), 102 deletions(-) create mode 100644 src/fr/gasser/autosnake/model/V2d.java rename src/fr/gasser/autosnake/{core => path}/AStarSearch.java (99%) rename src/fr/gasser/autosnake/{core => path}/Cell.java (60%) rename src/fr/gasser/autosnake/{core => path}/Direction.java (78%) rename src/fr/gasser/autosnake/{core => path}/Grid.java (74%) diff --git a/src/fr/gasser/autosnake/Game.java b/src/fr/gasser/autosnake/Game.java index 06ab6fc..af0ffdd 100644 --- a/src/fr/gasser/autosnake/Game.java +++ b/src/fr/gasser/autosnake/Game.java @@ -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.*; diff --git a/src/fr/gasser/autosnake/GamePanel.java b/src/fr/gasser/autosnake/GamePanel.java index 27a9476..8ef4fd1 100644 --- a/src/fr/gasser/autosnake/GamePanel.java +++ b/src/fr/gasser/autosnake/GamePanel.java @@ -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(); diff --git a/src/fr/gasser/autosnake/model/Snake.java b/src/fr/gasser/autosnake/model/Snake.java index 347830a..ab879df 100644 --- a/src/fr/gasser/autosnake/model/Snake.java +++ b/src/fr/gasser/autosnake/model/Snake.java @@ -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 getBodyCells() { - Point headCell = cellOf(head); - HashSet 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 getBodyCells() { + Point headCell = cellOf(head); + HashSet 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 getTrail() { - var visibleTrail = new ArrayList(); + return this.trail.stream().map(v -> getRect(v.x, v.y)); + } + + private ArrayList sampleTrail() { + // Sample points from the trail until the accumulated distance is >= this.length. + var visibleTrail = new ArrayList(); 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); - } - } } diff --git a/src/fr/gasser/autosnake/model/V2d.java b/src/fr/gasser/autosnake/model/V2d.java new file mode 100644 index 0000000..9210e11 --- /dev/null +++ b/src/fr/gasser/autosnake/model/V2d.java @@ -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; + } +} diff --git a/src/fr/gasser/autosnake/core/AStarSearch.java b/src/fr/gasser/autosnake/path/AStarSearch.java similarity index 99% rename from src/fr/gasser/autosnake/core/AStarSearch.java rename to src/fr/gasser/autosnake/path/AStarSearch.java index f9a75ff..9552770 100644 --- a/src/fr/gasser/autosnake/core/AStarSearch.java +++ b/src/fr/gasser/autosnake/path/AStarSearch.java @@ -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; diff --git a/src/fr/gasser/autosnake/core/Cell.java b/src/fr/gasser/autosnake/path/Cell.java similarity index 60% rename from src/fr/gasser/autosnake/core/Cell.java rename to src/fr/gasser/autosnake/path/Cell.java index 618eba6..3bb895e 100644 --- a/src/fr/gasser/autosnake/core/Cell.java +++ b/src/fr/gasser/autosnake/path/Cell.java @@ -1,4 +1,4 @@ -package fr.gasser.autosnake.core; +package fr.gasser.autosnake.path; public interface Cell { boolean isObstacle(); diff --git a/src/fr/gasser/autosnake/core/Direction.java b/src/fr/gasser/autosnake/path/Direction.java similarity index 78% rename from src/fr/gasser/autosnake/core/Direction.java rename to src/fr/gasser/autosnake/path/Direction.java index 94051a9..4da4f40 100644 --- a/src/fr/gasser/autosnake/core/Direction.java +++ b/src/fr/gasser/autosnake/path/Direction.java @@ -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), diff --git a/src/fr/gasser/autosnake/core/Grid.java b/src/fr/gasser/autosnake/path/Grid.java similarity index 74% rename from src/fr/gasser/autosnake/core/Grid.java rename to src/fr/gasser/autosnake/path/Grid.java index fbdd9fb..c9e5a16 100644 --- a/src/fr/gasser/autosnake/core/Grid.java +++ b/src/fr/gasser/autosnake/path/Grid.java @@ -1,4 +1,4 @@ -package fr.gasser.autosnake.core; +package fr.gasser.autosnake.path; import java.awt.*;