algo. changed to be better
This commit is contained in:
@@ -18,6 +18,8 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
private int[][] hamiltonianPath;
|
||||
private boolean gameRunning = false;
|
||||
private boolean showPath = false;
|
||||
private boolean showNumbers = false;
|
||||
private int score = 0;
|
||||
|
||||
// UI Components
|
||||
private JSlider speedSlider;
|
||||
@@ -25,11 +27,14 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
private JButton startButton;
|
||||
private JButton pauseButton;
|
||||
private JCheckBox showPathBox;
|
||||
private JCheckBox showNumbersBox;
|
||||
private JLabel scoreLabel;
|
||||
private JLabel statusLabel;
|
||||
private JPanel gamePanel;
|
||||
private JFrame parentFrame;
|
||||
|
||||
public HamiltonianSnakeAI() {
|
||||
public HamiltonianSnakeAI(JFrame parentFrame) {
|
||||
this.parentFrame = parentFrame;
|
||||
initGame();
|
||||
setupUI();
|
||||
}
|
||||
@@ -55,7 +60,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
};
|
||||
gamePanel.setBackground(new Color(20, 20, 20));
|
||||
gamePanel.setBorder(BorderFactory.createLineBorder(new Color(60, 60, 60), 2));
|
||||
gamePanel.setPreferredSize(new Dimension(gridSize * cellSize, gridSize * cellSize));
|
||||
updateGamePanelSize();
|
||||
|
||||
// Control panel with modern look
|
||||
JPanel controlPanel = new JPanel();
|
||||
@@ -80,15 +85,13 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
controlPanel.add(speedPanel);
|
||||
|
||||
// Grid size control
|
||||
JPanel gridPanel = createControlPanel("Grid Size:", gridSlider = new JSlider(10, 30, 20));
|
||||
JPanel gridPanel = createControlPanel("Grid Size:", gridSlider = new JSlider(10, 40, 20));
|
||||
gridSlider.addChangeListener(e -> {
|
||||
if (!gameRunning) {
|
||||
gridSize = gridSlider.getValue();
|
||||
cellSize = Math.max(15, Math.min(30, 600 / gridSize)); // Better cell size range
|
||||
gamePanel.setPreferredSize(new Dimension(gridSize * cellSize, gridSize * cellSize));
|
||||
updateGamePanelSize();
|
||||
resetGame();
|
||||
revalidate();
|
||||
repaint();
|
||||
parentFrame.pack();
|
||||
}
|
||||
});
|
||||
controlPanel.add(gridPanel);
|
||||
@@ -114,8 +117,9 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
controlPanel.add(buttonPanel);
|
||||
|
||||
// Checkbox panel
|
||||
JPanel checkBoxPanel = new JPanel();
|
||||
JPanel checkBoxPanel = new JPanel(new GridLayout(2, 1, 5, 5));
|
||||
checkBoxPanel.setBackground(new Color(40, 40, 40));
|
||||
|
||||
showPathBox = new JCheckBox("Show Hamiltonian Path");
|
||||
showPathBox.setForeground(Color.WHITE);
|
||||
showPathBox.setBackground(new Color(40, 40, 40));
|
||||
@@ -125,6 +129,17 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
repaint();
|
||||
});
|
||||
checkBoxPanel.add(showPathBox);
|
||||
|
||||
showNumbersBox = new JCheckBox("Show Path Numbers");
|
||||
showNumbersBox.setForeground(Color.WHITE);
|
||||
showNumbersBox.setBackground(new Color(40, 40, 40));
|
||||
showNumbersBox.setFocusPainted(false);
|
||||
showNumbersBox.addActionListener(e -> {
|
||||
showNumbers = showNumbersBox.isSelected();
|
||||
repaint();
|
||||
});
|
||||
checkBoxPanel.add(showNumbersBox);
|
||||
|
||||
controlPanel.add(checkBoxPanel);
|
||||
|
||||
// Status panel
|
||||
@@ -144,6 +159,11 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
add(controlPanel, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
private void updateGamePanelSize() {
|
||||
cellSize = Math.max(10, Math.min(30, 600 / gridSize));
|
||||
gamePanel.setPreferredSize(new Dimension(gridSize * cellSize, gridSize * cellSize));
|
||||
}
|
||||
|
||||
private JPanel createControlPanel(String labelText, JSlider slider) {
|
||||
JPanel panel = new JPanel(new BorderLayout(10, 0));
|
||||
panel.setBackground(new Color(40, 40, 40));
|
||||
@@ -188,15 +208,15 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
private void generateHamiltonianCycle() {
|
||||
hamiltonianPath = new int[gridSize][gridSize];
|
||||
|
||||
// Generate a more efficient Hamiltonian cycle that's better for longer snakes
|
||||
// Generate an optimized Hamiltonian cycle that minimizes the chance of self-collision
|
||||
if (gridSize % 2 == 0) {
|
||||
generateEvenGridCycle();
|
||||
generateOptimizedEvenGridCycle();
|
||||
} else {
|
||||
generateOddGridCycle();
|
||||
generateOptimizedOddGridCycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void generateEvenGridCycle() {
|
||||
private void generateOptimizedEvenGridCycle() {
|
||||
int pathIndex = 0;
|
||||
for (int row = 0; row < gridSize; row++) {
|
||||
if (row % 2 == 0) {
|
||||
@@ -205,87 +225,163 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
}
|
||||
} else {
|
||||
// Right to left
|
||||
for (int col = gridSize - 1; col >= 0; col--) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
// Right to left with a small modification to reduce tail collisions
|
||||
if (row == gridSize - 1) {
|
||||
// Last row - special pattern to connect back to start
|
||||
hamiltonianPath[row][gridSize - 1] = pathIndex++;
|
||||
for (int col = gridSize - 2; col >= 1; col--) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
}
|
||||
hamiltonianPath[row][0] = pathIndex++;
|
||||
} else {
|
||||
// Standard right to left
|
||||
for (int col = gridSize - 1; col >= 0; col--) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateOddGridCycle() {
|
||||
// More complex cycle for odd-sized grids that reduces tail collisions
|
||||
private void generateOptimizedOddGridCycle() {
|
||||
// More sophisticated cycle for odd grids that reduces dead ends
|
||||
int pathIndex = 0;
|
||||
int row = 0, col = 0;
|
||||
boolean movingRight = true;
|
||||
boolean zigzag = true;
|
||||
|
||||
while (row < gridSize) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
|
||||
if (movingRight) {
|
||||
if (col < gridSize - 1) {
|
||||
col++;
|
||||
} else {
|
||||
// At right edge, move down and reverse direction
|
||||
row++;
|
||||
if (row < gridSize) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
if (zigzag) {
|
||||
if (movingRight) {
|
||||
if (col < gridSize - 1) {
|
||||
col++;
|
||||
} else {
|
||||
row++;
|
||||
if (row < gridSize) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
}
|
||||
movingRight = false;
|
||||
}
|
||||
} else {
|
||||
if (col > 0) {
|
||||
col--;
|
||||
} else {
|
||||
row++;
|
||||
if (row < gridSize) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
}
|
||||
movingRight = true;
|
||||
}
|
||||
movingRight = false;
|
||||
}
|
||||
} else {
|
||||
if (col > 0) {
|
||||
col--;
|
||||
} else {
|
||||
// At left edge, move down and reverse direction
|
||||
row++;
|
||||
if (row < gridSize) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
// Alternate pattern for some rows to improve path efficiency
|
||||
if (row % 2 == 0) {
|
||||
if (col < gridSize - 1) {
|
||||
col++;
|
||||
} else {
|
||||
row++;
|
||||
if (row < gridSize) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (col > 0) {
|
||||
col--;
|
||||
} else {
|
||||
row++;
|
||||
if (row < gridSize) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
}
|
||||
}
|
||||
movingRight = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch patterns periodically
|
||||
if (row > 0 && row % 3 == 0) {
|
||||
zigzag = !zigzag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnFood() {
|
||||
Random rand = new Random();
|
||||
int attempts = 0;
|
||||
int maxAttempts = 100;
|
||||
int maxAttempts = gridSize * gridSize * 2; // More attempts for larger grids
|
||||
|
||||
do {
|
||||
food = new Point(rand.nextInt(gridSize), rand.nextInt(gridSize));
|
||||
attempts++;
|
||||
// Ensure food isn't placed where it would be impossible to reach
|
||||
if (attempts >= maxAttempts || !snake.contains(food)) {
|
||||
if (attempts >= maxAttempts) {
|
||||
// Emergency fallback - find any empty spot
|
||||
for (int y = 0; y < gridSize; y++) {
|
||||
for (int x = 0; x < gridSize; x++) {
|
||||
Point p = new Point(x, y);
|
||||
if (!snake.contains(p)) {
|
||||
food = p;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we get here, the board is full (game should be won)
|
||||
break;
|
||||
}
|
||||
} while (snake.contains(food) || isFoodInDeadZone(food));
|
||||
}
|
||||
|
||||
private boolean isFoodInDeadZone(Point foodPos) {
|
||||
// Check if food is placed in a position that would make it unreachable
|
||||
// due to the snake's body blocking all paths
|
||||
// Advanced reachability check using BFS with early termination
|
||||
Set<Point> obstacles = new HashSet<>(snake.getBody());
|
||||
obstacles.remove(snake.getTail());
|
||||
|
||||
// Simple flood fill to check reachability from snake head
|
||||
Set<Point> visited = new HashSet<>();
|
||||
Queue<Point> queue = new LinkedList<>();
|
||||
queue.add(snake.getHead());
|
||||
visited.add(snake.getHead());
|
||||
// Early exit if food is adjacent to head (always reachable)
|
||||
if (estimateDistance(snake.getHead(), foodPos) == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Point current = queue.poll();
|
||||
if (current.equals(foodPos)) {
|
||||
// Use A* algorithm for more efficient pathfinding
|
||||
PriorityQueue<PathNode> openSet = new PriorityQueue<>();
|
||||
Set<Point> closedSet = new HashSet<>();
|
||||
|
||||
openSet.add(new PathNode(snake.getHead(), 0, estimateDistance(snake.getHead(), foodPos)));
|
||||
|
||||
while (!openSet.isEmpty()) {
|
||||
PathNode current = openSet.poll();
|
||||
|
||||
if (current.position.equals(foodPos)) {
|
||||
return false; // Food is reachable
|
||||
}
|
||||
|
||||
for (Point neighbor : getNeighbors(current)) {
|
||||
if (!visited.contains(neighbor) && !obstacles.contains(neighbor)) {
|
||||
visited.add(neighbor);
|
||||
queue.add(neighbor);
|
||||
closedSet.add(current.position);
|
||||
|
||||
for (Point neighbor : getNeighbors(current.position)) {
|
||||
if (closedSet.contains(neighbor) || obstacles.contains(neighbor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int tentativeG = current.g + 1;
|
||||
boolean inOpenSet = false;
|
||||
|
||||
for (PathNode node : openSet) {
|
||||
if (node.position.equals(neighbor)) {
|
||||
inOpenSet = true;
|
||||
if (tentativeG < node.g) {
|
||||
node.g = tentativeG;
|
||||
node.f = node.g + node.h;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inOpenSet) {
|
||||
openSet.add(new PathNode(neighbor, tentativeG, estimateDistance(neighbor, foodPos)));
|
||||
}
|
||||
}
|
||||
|
||||
// Early termination if we've searched enough
|
||||
if (closedSet.size() > gridSize * gridSize / 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +411,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
timer.stop();
|
||||
gameRunning = false;
|
||||
snake = new Snake(gridSize);
|
||||
score = 0;
|
||||
generateHamiltonianCycle();
|
||||
spawnFood();
|
||||
startButton.setEnabled(true);
|
||||
@@ -364,78 +461,278 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
// Check if food eaten
|
||||
if (snake.getHead().equals(food)) {
|
||||
snake.grow();
|
||||
score += (int)(10 * Math.sqrt(gridSize)); // Score based on grid size
|
||||
spawnFood();
|
||||
scoreLabel.setText("Score: " + (snake.getLength() - 1));
|
||||
scoreLabel.setText("Score: " + score);
|
||||
}
|
||||
}
|
||||
|
||||
private Point getNextMove(Point head) {
|
||||
List<Point> possibleMoves = getSafeMoves(head);
|
||||
|
||||
if (possibleMoves.isEmpty()) {
|
||||
return head; // Stay in place (will trigger collision detection)
|
||||
return head; // No safe moves available (will trigger game over)
|
||||
}
|
||||
|
||||
// For longer snakes, be more cautious about shortcuts
|
||||
if (snake.getLength() < gridSize * gridSize * 0.6) {
|
||||
// Try to find a safe shortcut to food
|
||||
Point bestMove = findBestShortcut(head, possibleMoves);
|
||||
if (bestMove != null) {
|
||||
return bestMove;
|
||||
// 1. First priority: Don't die next turn
|
||||
possibleMoves = filterImmediatelyDeadlyMoves(possibleMoves);
|
||||
if (possibleMoves.isEmpty()) return head;
|
||||
|
||||
// 2. Second priority: Don't create future traps
|
||||
possibleMoves = filterFutureTraps(possibleMoves, 3); // Look 3 moves ahead
|
||||
if (possibleMoves.isEmpty()) return head;
|
||||
|
||||
// 3. If food is reachable and safe, go for it
|
||||
Point foodPathMove = findSafePathToFood(head, possibleMoves);
|
||||
if (foodPathMove != null) {
|
||||
return foodPathMove;
|
||||
}
|
||||
|
||||
// 4. Follow Hamiltonian cycle with safety checks
|
||||
return followSuperSafeHamiltonianCycle(head, possibleMoves);
|
||||
}
|
||||
|
||||
private List<Point> filterImmediatelyDeadlyMoves(List<Point> moves) {
|
||||
List<Point> safeMoves = new ArrayList<>();
|
||||
for (Point move : moves) {
|
||||
if (!willCollideImmediately(move)) {
|
||||
safeMoves.add(move);
|
||||
}
|
||||
}
|
||||
return safeMoves;
|
||||
}
|
||||
|
||||
private boolean willCollideImmediately(Point move) {
|
||||
// Check if this move would collide with body (except tail)
|
||||
return snake.contains(move) && !move.equals(snake.getTail());
|
||||
}
|
||||
|
||||
private List<Point> filterFutureTraps(List<Point> moves, int lookAhead) {
|
||||
if (snake.getLength() < gridSize * 0.6) return moves; // Not long enough to worry
|
||||
|
||||
List<Point> safeMoves = new ArrayList<>();
|
||||
for (Point move : moves) {
|
||||
if (!wouldCreateTrap(move, lookAhead)) {
|
||||
safeMoves.add(move);
|
||||
}
|
||||
}
|
||||
return safeMoves;
|
||||
}
|
||||
|
||||
private boolean wouldCreateTrap(Point move, int depth) {
|
||||
// Simulate future moves to detect potential traps
|
||||
List<Point> simulatedBody = new ArrayList<>(snake.getBody());
|
||||
simulatedBody.add(0, move);
|
||||
simulatedBody.remove(simulatedBody.size() - 1);
|
||||
|
||||
if (depth == 0) return false;
|
||||
|
||||
// Check if this move reduces future options too much
|
||||
int safeExits = countSafeExits(move, new HashSet<>(simulatedBody));
|
||||
if (safeExits == 0) return true;
|
||||
|
||||
// Recursively check future moves
|
||||
if (depth > 1) {
|
||||
List<Point> nextMoves = getNeighbors(move);
|
||||
nextMoves.removeAll(simulatedBody);
|
||||
|
||||
for (Point nextMove : nextMoves) {
|
||||
if (wouldCreateTrap(nextMove, depth - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Follow the Hamiltonian cycle with tail collision awareness
|
||||
return followHamiltonianCycle(head, possibleMoves);
|
||||
return false;
|
||||
}
|
||||
|
||||
private Point findBestShortcut(Point head, List<Point> possibleMoves) {
|
||||
private int countSafeExits(Point position, Set<Point> obstacles) {
|
||||
int count = 0;
|
||||
for (Point neighbor : getNeighbors(position)) {
|
||||
if (!obstacles.contains(neighbor)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private Point findSafePathToFood(Point head, List<Point> possibleMoves) {
|
||||
if (estimateDistance(head, food) > gridSize * 0.7) return null;
|
||||
|
||||
// Find the safest path to food using flood fill
|
||||
Map<Point, Integer> distanceMap = new HashMap<>();
|
||||
Queue<Point> queue = new LinkedList<>();
|
||||
Set<Point> obstacles = new HashSet<>(snake.getBody());
|
||||
obstacles.remove(snake.getTail());
|
||||
|
||||
queue.add(food);
|
||||
distanceMap.put(food, 0);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Point current = queue.poll();
|
||||
|
||||
for (Point neighbor : getNeighbors(current)) {
|
||||
if (!distanceMap.containsKey(neighbor) && !obstacles.contains(neighbor)) {
|
||||
distanceMap.put(neighbor, distanceMap.get(current) + 1);
|
||||
queue.add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the move with shortest path to food
|
||||
Point bestMove = null;
|
||||
int minDistance = Integer.MAX_VALUE;
|
||||
|
||||
for (Point move : possibleMoves) {
|
||||
int distance = estimateDistance(move, food);
|
||||
if (distance < minDistance && isPathSafe(move, food)) {
|
||||
// Additional check for tail collision risk
|
||||
if (!willCollideWithTail(move)) {
|
||||
minDistance = distance;
|
||||
bestMove = move;
|
||||
}
|
||||
Integer dist = distanceMap.get(move);
|
||||
if (dist != null && dist < minDistance) {
|
||||
minDistance = dist;
|
||||
bestMove = move;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMove;
|
||||
}
|
||||
|
||||
private boolean willCollideWithTail(Point move) {
|
||||
// Predict if this move might lead to tail collision in the near future
|
||||
if (snake.getLength() < gridSize * 0.75) {
|
||||
return false; // Not long enough to worry about tail collisions
|
||||
private Point followSuperSafeHamiltonianCycle(Point head, List<Point> possibleMoves) {
|
||||
int currentIndex = hamiltonianPath[head.y][head.x];
|
||||
int nextIndex = (currentIndex + 1) % (gridSize * gridSize);
|
||||
|
||||
// Find the safest move along the cycle
|
||||
Point bestMove = null;
|
||||
int bestSafetyScore = -1;
|
||||
|
||||
for (Point move : possibleMoves) {
|
||||
int moveIndex = hamiltonianPath[move.y][move.x];
|
||||
int cycleProgress = (moveIndex - currentIndex + gridSize * gridSize) % (gridSize * gridSize);
|
||||
|
||||
// Calculate safety score (higher is better)
|
||||
int safetyScore = (gridSize * gridSize - cycleProgress) + countSafeExits(move, new HashSet<>(snake.getBody()));
|
||||
|
||||
if (safetyScore > bestSafetyScore ||
|
||||
(safetyScore == bestSafetyScore && cycleProgress < (gridSize * gridSize) / 2)) {
|
||||
bestSafetyScore = safetyScore;
|
||||
bestMove = move;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this move brings us closer to the tail's future position
|
||||
Point tail = snake.getTail();
|
||||
int currentDistToTail = estimateDistance(snake.getHead(), tail);
|
||||
int newDistToTail = estimateDistance(move, tail);
|
||||
|
||||
return newDistToTail < currentDistToTail - 2; // Approaching tail too quickly
|
||||
return bestMove != null ? bestMove : possibleMoves.get(0);
|
||||
}
|
||||
|
||||
private boolean isPathSafe(Point from, Point to) {
|
||||
// More efficient path safety check with early termination
|
||||
private Point findOptimalPathToFood(Point head, List<Point> possibleMoves) {
|
||||
// Use A* algorithm to find the best path to food
|
||||
Map<Point, Point> cameFrom = new HashMap<>();
|
||||
Map<Point, Integer> gScore = new HashMap<>();
|
||||
Map<Point, Integer> fScore = new HashMap<>();
|
||||
PriorityQueue<Point> openSet = new PriorityQueue<>(
|
||||
Comparator.comparingInt(p -> fScore.getOrDefault(p, Integer.MAX_VALUE))
|
||||
);
|
||||
|
||||
Set<Point> obstacles = new HashSet<>(snake.getBody());
|
||||
obstacles.remove(snake.getTail());
|
||||
|
||||
for (Point move : possibleMoves) {
|
||||
gScore.put(move, 1);
|
||||
fScore.put(move, estimateDistance(move, food));
|
||||
cameFrom.put(move, head);
|
||||
openSet.add(move);
|
||||
}
|
||||
|
||||
while (!openSet.isEmpty()) {
|
||||
Point current = openSet.poll();
|
||||
|
||||
if (current.equals(food)) {
|
||||
// Reconstruct path
|
||||
Point bestMove = null;
|
||||
while (cameFrom.get(current) != head) {
|
||||
current = cameFrom.get(current);
|
||||
}
|
||||
bestMove = current;
|
||||
|
||||
// Additional safety check
|
||||
if (!willCauseImmediateDanger(bestMove)) {
|
||||
return bestMove;
|
||||
}
|
||||
}
|
||||
|
||||
for (Point neighbor : getNeighbors(current)) {
|
||||
if (obstacles.contains(neighbor) && !neighbor.equals(snake.getTail())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int tentativeG = gScore.getOrDefault(current, Integer.MAX_VALUE) + 1;
|
||||
|
||||
if (tentativeG < gScore.getOrDefault(neighbor, Integer.MAX_VALUE)) {
|
||||
cameFrom.put(neighbor, current);
|
||||
gScore.put(neighbor, tentativeG);
|
||||
fScore.put(neighbor, tentativeG + estimateDistance(neighbor, food));
|
||||
if (!openSet.contains(neighbor)) {
|
||||
openSet.add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Point avoidFutureCollisions(Point head, List<Point> possibleMoves) {
|
||||
// Predict future snake positions and avoid moves that could lead to traps
|
||||
if (snake.getLength() < gridSize * 0.6) {
|
||||
return null; // Not long enough to worry about
|
||||
}
|
||||
|
||||
Point bestMove = null;
|
||||
double bestScore = Double.NEGATIVE_INFINITY;
|
||||
|
||||
for (Point move : possibleMoves) {
|
||||
// Simulate the move
|
||||
List<Point> simulatedBody = new ArrayList<>(snake.getBody());
|
||||
simulatedBody.add(0, move);
|
||||
simulatedBody.remove(simulatedBody.size() - 1);
|
||||
|
||||
// Calculate safety score
|
||||
double score = calculateMoveSafety(move, simulatedBody);
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMove = move;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMove;
|
||||
}
|
||||
|
||||
private double calculateMoveSafety(Point move, List<Point> simulatedBody) {
|
||||
// Calculate how "safe" this move is based on several factors
|
||||
double score = 0.0;
|
||||
|
||||
// 1. Distance to food (closer is better)
|
||||
score -= 0.5 * estimateDistance(move, food);
|
||||
|
||||
// 2. Number of reachable cells after this move
|
||||
score += 0.3 * countReachableCells(move, new HashSet<>(simulatedBody));
|
||||
|
||||
// 3. Distance to tail (farther is better)
|
||||
score += 0.2 * estimateDistance(move, snake.getTail());
|
||||
|
||||
// 4. Progress along Hamiltonian cycle
|
||||
int currentIndex = hamiltonianPath[snake.getHead().y][snake.getHead().x];
|
||||
int nextIndex = hamiltonianPath[move.y][move.x];
|
||||
int desiredNextIndex = (currentIndex + 1) % (gridSize * gridSize);
|
||||
score += 0.1 * (1.0 - (double)Math.abs(nextIndex - desiredNextIndex) / (gridSize * gridSize));
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private int countReachableCells(Point from, Set<Point> obstacles) {
|
||||
// BFS to count reachable cells
|
||||
Set<Point> visited = new HashSet<>();
|
||||
Queue<Point> queue = new LinkedList<>();
|
||||
Set<Point> obstacles = new HashSet<>(snake.getBody());
|
||||
obstacles.remove(snake.getTail()); // Tail will move
|
||||
|
||||
queue.add(from);
|
||||
visited.add(from);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Point current = queue.poll();
|
||||
if (current.equals(to)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Point neighbor : getNeighbors(current)) {
|
||||
if (!visited.contains(neighbor) && !obstacles.contains(neighbor)) {
|
||||
@@ -445,31 +742,47 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
}
|
||||
}
|
||||
|
||||
return visited.size();
|
||||
}
|
||||
|
||||
private boolean willCauseImmediateDanger(Point move) {
|
||||
// Check if this move would put us in a position where we have no good next moves
|
||||
List<Point> nextPossibleMoves = getNeighbors(move);
|
||||
nextPossibleMoves.removeAll(snake.getBody());
|
||||
|
||||
if (nextPossibleMoves.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Point followHamiltonianCycle(Point head, List<Point> possibleMoves) {
|
||||
private Point followOptimizedHamiltonianCycle(Point head, List<Point> possibleMoves) {
|
||||
int currentIndex = hamiltonianPath[head.y][head.x];
|
||||
int nextIndex = (currentIndex + 1) % (gridSize * gridSize);
|
||||
int prevIndex = (currentIndex - 1 + gridSize * gridSize) % (gridSize * gridSize);
|
||||
|
||||
// Find the next point in the cycle that's a valid move
|
||||
Point bestMove = null;
|
||||
int bestDistance = Integer.MAX_VALUE;
|
||||
|
||||
for (Point move : possibleMoves) {
|
||||
if (hamiltonianPath[move.y][move.x] == nextIndex) {
|
||||
return move;
|
||||
int moveIndex = hamiltonianPath[move.y][move.x];
|
||||
int distance = (moveIndex - currentIndex + gridSize * gridSize) % (gridSize * gridSize);
|
||||
|
||||
// Prefer moves that are closest to the next in cycle, but also consider safety
|
||||
if (distance < bestDistance ||
|
||||
(distance == bestDistance && !willCauseImmediateDanger(move))) {
|
||||
bestDistance = distance;
|
||||
bestMove = move;
|
||||
}
|
||||
}
|
||||
|
||||
// If exact next in cycle isn't possible, find the closest safe move
|
||||
return possibleMoves.stream()
|
||||
.min(Comparator.comparingInt(p -> {
|
||||
int diff = hamiltonianPath[p.y][p.x] - currentIndex;
|
||||
return diff > 0 ? diff : diff + gridSize * gridSize;
|
||||
}))
|
||||
.orElse(possibleMoves.get(0));
|
||||
return bestMove != null ? bestMove : possibleMoves.get(0);
|
||||
}
|
||||
|
||||
private int estimateDistance(Point a, Point b) {
|
||||
// Manhattan distance
|
||||
// Manhattan distance with small tie-breaker
|
||||
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
||||
}
|
||||
|
||||
@@ -529,7 +842,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
for (int x = 0; x < gridSize; x++) {
|
||||
g.fillRect(x * cellSize + 1, y * cellSize + 1, cellSize - 2, cellSize - 2);
|
||||
|
||||
if (cellSize > 15) {
|
||||
if (showNumbers && cellSize > 15) {
|
||||
g.setColor(new Color(100, 200, 255));
|
||||
g.setFont(new Font("Arial", Font.PLAIN, Math.max(8, cellSize / 3)));
|
||||
String text = String.valueOf(hamiltonianPath[y][x]);
|
||||
@@ -598,7 +911,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JFrame frame = new JFrame("Hamiltonian Snake AI");
|
||||
HamiltonianSnakeAI game = new HamiltonianSnakeAI();
|
||||
HamiltonianSnakeAI game = new HamiltonianSnakeAI(frame);
|
||||
|
||||
frame.add(game);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
@@ -608,6 +921,25 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
frame.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
private static class PathNode implements Comparable<PathNode> {
|
||||
Point position;
|
||||
int g; // Cost from start
|
||||
int h; // Heuristic to target
|
||||
int f; // Total cost (g + h)
|
||||
|
||||
public PathNode(Point position, int g, int h) {
|
||||
this.position = position;
|
||||
this.g = g;
|
||||
this.h = h;
|
||||
this.f = g + h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PathNode other) {
|
||||
return Integer.compare(this.f, other.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Snake {
|
||||
|
||||
Reference in New Issue
Block a user