works
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
17
pom.xml
Normal file
17
pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.hamiltoniansnakeai</groupId>
|
||||
<artifactId>HamiltonianSnakeAI</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>24</maven.compiler.source>
|
||||
<maven.compiler.target>24</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
496
src/main/java/org/hamiltoniansnakeai/HamiltonianSnakeAI.java
Normal file
496
src/main/java/org/hamiltoniansnakeai/HamiltonianSnakeAI.java
Normal file
@@ -0,0 +1,496 @@
|
||||
package org.hamiltoniansnakeai;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||
private javax.swing.Timer timer;
|
||||
private int gridSize = 20;
|
||||
private int cellSize = 20;
|
||||
private int delay = 100;
|
||||
|
||||
private Snake snake;
|
||||
private Point food;
|
||||
private int[][] hamiltonianPath;
|
||||
private boolean gameRunning = false;
|
||||
private boolean showPath = false;
|
||||
|
||||
// UI Components
|
||||
private JSlider speedSlider;
|
||||
private JSlider gridSlider;
|
||||
private JButton startButton;
|
||||
private JButton pauseButton;
|
||||
private JCheckBox showPathBox;
|
||||
private JLabel scoreLabel;
|
||||
private JLabel statusLabel;
|
||||
|
||||
public HamiltonianSnakeAI() {
|
||||
initGame();
|
||||
setupUI();
|
||||
}
|
||||
|
||||
private void initGame() {
|
||||
snake = new Snake(gridSize);
|
||||
generateHamiltonianCycle();
|
||||
spawnFood();
|
||||
timer = new javax.swing.Timer(delay, this);
|
||||
}
|
||||
|
||||
private void setupUI() {
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(Color.BLACK);
|
||||
|
||||
// Game panel
|
||||
JPanel gamePanel = new JPanel() {
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
draw(g);
|
||||
}
|
||||
};
|
||||
gamePanel.setBackground(Color.BLACK);
|
||||
gamePanel.setPreferredSize(new Dimension(gridSize * cellSize, gridSize * cellSize));
|
||||
|
||||
// Control panel
|
||||
JPanel controlPanel = new JPanel(new GridLayout(6, 2, 5, 5));
|
||||
controlPanel.setBackground(Color.DARK_GRAY);
|
||||
controlPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
// Speed control
|
||||
controlPanel.add(new JLabel("Speed:", SwingConstants.CENTER));
|
||||
speedSlider = new JSlider(1, 20, 10);
|
||||
speedSlider.addChangeListener(e -> {
|
||||
delay = 210 - speedSlider.getValue() * 10;
|
||||
if (timer != null) timer.setDelay(delay);
|
||||
});
|
||||
controlPanel.add(speedSlider);
|
||||
|
||||
// Grid size control
|
||||
controlPanel.add(new JLabel("Grid Size:", SwingConstants.CENTER));
|
||||
gridSlider = new JSlider(10, 50, 20); // Reduced max grid size for better performance
|
||||
gridSlider.addChangeListener(e -> {
|
||||
if (!gameRunning) {
|
||||
gridSize = gridSlider.getValue();
|
||||
cellSize = Math.max(5, Math.min(20, 800 / gridSize)); // Minimum cell size of 5
|
||||
gamePanel.setPreferredSize(new Dimension(gridSize * cellSize, gridSize * cellSize));
|
||||
resetGame();
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
controlPanel.add(gridSlider);
|
||||
|
||||
// Control buttons
|
||||
startButton = new JButton("Start");
|
||||
startButton.addActionListener(e -> startGame());
|
||||
controlPanel.add(startButton);
|
||||
|
||||
pauseButton = new JButton("Pause");
|
||||
pauseButton.addActionListener(e -> pauseGame());
|
||||
pauseButton.setEnabled(false);
|
||||
controlPanel.add(pauseButton);
|
||||
|
||||
// Show path checkbox
|
||||
showPathBox = new JCheckBox("Show Path");
|
||||
showPathBox.addActionListener(e -> {
|
||||
showPath = showPathBox.isSelected();
|
||||
repaint();
|
||||
});
|
||||
controlPanel.add(showPathBox);
|
||||
|
||||
JButton resetButton = new JButton("Reset");
|
||||
resetButton.addActionListener(e -> resetGame());
|
||||
controlPanel.add(resetButton);
|
||||
|
||||
// Status labels
|
||||
scoreLabel = new JLabel("Score: 0", SwingConstants.CENTER);
|
||||
scoreLabel.setForeground(Color.WHITE);
|
||||
controlPanel.add(scoreLabel);
|
||||
|
||||
statusLabel = new JLabel("Ready", SwingConstants.CENTER);
|
||||
statusLabel.setForeground(Color.GREEN);
|
||||
controlPanel.add(statusLabel);
|
||||
|
||||
add(gamePanel, BorderLayout.CENTER);
|
||||
add(controlPanel, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
private void generateHamiltonianCycle() {
|
||||
hamiltonianPath = new int[gridSize][gridSize];
|
||||
|
||||
// Generate a more efficient Hamiltonian cycle
|
||||
if (gridSize % 2 == 0) {
|
||||
generateEvenGridCycle();
|
||||
} else {
|
||||
generateOddGridCycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void generateEvenGridCycle() {
|
||||
int pathIndex = 0;
|
||||
for (int row = 0; row < gridSize; row++) {
|
||||
if (row % 2 == 0) {
|
||||
// Left to right
|
||||
for (int col = 0; col < gridSize; col++) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
}
|
||||
} else {
|
||||
// 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
|
||||
int pathIndex = 0;
|
||||
int row = 0, col = 0;
|
||||
boolean movingRight = true;
|
||||
|
||||
while (row < gridSize) {
|
||||
hamiltonianPath[row][col] = pathIndex++;
|
||||
|
||||
if (movingRight) {
|
||||
if (col < gridSize - 1) {
|
||||
col++;
|
||||
} else {
|
||||
row++;
|
||||
movingRight = false;
|
||||
}
|
||||
} else {
|
||||
if (col > 0) {
|
||||
col--;
|
||||
} else {
|
||||
row++;
|
||||
movingRight = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect the last cell back to the first
|
||||
hamiltonianPath[gridSize-1][0] = pathIndex;
|
||||
}
|
||||
|
||||
private void spawnFood() {
|
||||
Random rand = new Random();
|
||||
do {
|
||||
food = new Point(rand.nextInt(gridSize), rand.nextInt(gridSize));
|
||||
} while (snake.contains(food));
|
||||
}
|
||||
|
||||
private void startGame() {
|
||||
gameRunning = true;
|
||||
timer.start();
|
||||
startButton.setEnabled(false);
|
||||
pauseButton.setEnabled(true);
|
||||
gridSlider.setEnabled(false);
|
||||
statusLabel.setText("Running");
|
||||
statusLabel.setForeground(Color.GREEN);
|
||||
}
|
||||
|
||||
private void pauseGame() {
|
||||
gameRunning = false;
|
||||
timer.stop();
|
||||
startButton.setEnabled(true);
|
||||
pauseButton.setEnabled(false);
|
||||
statusLabel.setText("Paused");
|
||||
statusLabel.setForeground(Color.YELLOW);
|
||||
}
|
||||
|
||||
private void resetGame() {
|
||||
timer.stop();
|
||||
gameRunning = false;
|
||||
snake = new Snake(gridSize);
|
||||
generateHamiltonianCycle();
|
||||
spawnFood();
|
||||
startButton.setEnabled(true);
|
||||
pauseButton.setEnabled(false);
|
||||
gridSlider.setEnabled(true);
|
||||
scoreLabel.setText("Score: 0");
|
||||
statusLabel.setText("Ready");
|
||||
statusLabel.setForeground(Color.GREEN);
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (gameRunning) {
|
||||
move();
|
||||
repaint();
|
||||
|
||||
if (snake.getLength() >= gridSize * gridSize) {
|
||||
gameRunning = false;
|
||||
timer.stop();
|
||||
statusLabel.setText("YOU WIN!");
|
||||
statusLabel.setForeground(Color.yellow);
|
||||
startButton.setEnabled(true);
|
||||
pauseButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void move() {
|
||||
Point head = snake.getHead();
|
||||
Point nextMove = getNextMove(head);
|
||||
|
||||
// Check for collision with body before moving
|
||||
if (snake.contains(nextMove) && !nextMove.equals(snake.getTail())) {
|
||||
gameRunning = false;
|
||||
timer.stop();
|
||||
statusLabel.setText("GAME OVER - Collision!");
|
||||
statusLabel.setForeground(Color.RED);
|
||||
startButton.setEnabled(true);
|
||||
pauseButton.setEnabled(false);
|
||||
gridSlider.setEnabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
snake.move(nextMove);
|
||||
|
||||
// Check if food eaten
|
||||
if (snake.getHead().equals(food)) {
|
||||
snake.grow();
|
||||
spawnFood();
|
||||
scoreLabel.setText("Score: " + (snake.getLength() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
private Point getNextMove(Point head) {
|
||||
List<Point> possibleMoves = getSafeMoves(head);
|
||||
|
||||
if (possibleMoves.isEmpty()) {
|
||||
return head; // Stay in place (will trigger collision detection)
|
||||
}
|
||||
|
||||
// If we can reach food safely with a shortcut, take it
|
||||
Point bestMove = findBestShortcut(head, possibleMoves);
|
||||
if (bestMove != null) {
|
||||
return bestMove;
|
||||
}
|
||||
|
||||
// Otherwise follow the Hamiltonian cycle
|
||||
return followHamiltonianCycle(head, possibleMoves);
|
||||
}
|
||||
|
||||
private Point findBestShortcut(Point head, List<Point> possibleMoves) {
|
||||
// Only consider shortcuts when snake is not too long
|
||||
if (snake.getLength() > gridSize * gridSize * 0.75) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Point bestMove = null;
|
||||
int minDistance = Integer.MAX_VALUE;
|
||||
|
||||
for (Point move : possibleMoves) {
|
||||
int distance = estimateDistance(move, food);
|
||||
if (distance < minDistance && isPathSafe(move, food)) {
|
||||
minDistance = distance;
|
||||
bestMove = move;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMove;
|
||||
}
|
||||
|
||||
private boolean isPathSafe(Point from, Point to) {
|
||||
// Simple flood fill to check if path exists
|
||||
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)) {
|
||||
visited.add(neighbor);
|
||||
queue.add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Point followHamiltonianCycle(Point head, List<Point> possibleMoves) {
|
||||
int currentIndex = hamiltonianPath[head.y][head.x];
|
||||
int nextIndex = (currentIndex + 1) % (gridSize * gridSize);
|
||||
|
||||
// Find the next point in the cycle that's a valid move
|
||||
for (Point move : possibleMoves) {
|
||||
if (hamiltonianPath[move.y][move.x] == nextIndex) {
|
||||
return move;
|
||||
}
|
||||
}
|
||||
|
||||
// If exact next in cycle isn't possible, find the closest
|
||||
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));
|
||||
}
|
||||
|
||||
private int estimateDistance(Point a, Point b) {
|
||||
// Manhattan distance
|
||||
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
||||
}
|
||||
|
||||
private List<Point> getSafeMoves(Point head) {
|
||||
List<Point> safeMoves = new ArrayList<>();
|
||||
int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // down, right, up, left
|
||||
|
||||
for (int[] dir : directions) {
|
||||
int newX = head.x + dir[0];
|
||||
int newY = head.y + dir[1];
|
||||
|
||||
// Check bounds
|
||||
if (newX >= 0 && newX < gridSize && newY >= 0 && newY < gridSize) {
|
||||
Point newPos = new Point(newX, newY);
|
||||
|
||||
// Check if this position is safe
|
||||
if (!snake.contains(newPos) || newPos.equals(snake.getTail())) {
|
||||
safeMoves.add(newPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return safeMoves;
|
||||
}
|
||||
|
||||
private List<Point> getNeighbors(Point p) {
|
||||
List<Point> neighbors = new ArrayList<>();
|
||||
int[][] dirs = {{0,1}, {1,0}, {0,-1}, {-1,0}};
|
||||
|
||||
for (int[] dir : dirs) {
|
||||
int nx = p.x + dir[0];
|
||||
int ny = p.y + dir[1];
|
||||
if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize) {
|
||||
neighbors.add(new Point(nx, ny));
|
||||
}
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
private void draw(Graphics g) {
|
||||
// Clear background
|
||||
g.setColor(Color.BLACK);
|
||||
g.fillRect(0, 0, getWidth(), getHeight());
|
||||
|
||||
// Draw grid
|
||||
g.setColor(Color.DARK_GRAY);
|
||||
for (int i = 0; i <= gridSize; i++) {
|
||||
g.drawLine(i * cellSize, 0, i * cellSize, gridSize * cellSize);
|
||||
g.drawLine(0, i * cellSize, gridSize * cellSize, i * cellSize);
|
||||
}
|
||||
|
||||
// Draw Hamiltonian path if enabled
|
||||
if (showPath) {
|
||||
g.setColor(new Color(50, 50, 100, 100));
|
||||
for (int y = 0; y < gridSize; y++) {
|
||||
for (int x = 0; x < gridSize; x++) {
|
||||
int pathNum = hamiltonianPath[y][x];
|
||||
g.fillRect(x * cellSize + 1, y * cellSize + 1, cellSize - 2, cellSize - 2);
|
||||
|
||||
if (cellSize > 10) {
|
||||
g.setColor(Color.CYAN);
|
||||
g.setFont(new Font("Arial", Font.PLAIN, Math.max(8, cellSize / 3)));
|
||||
String text = String.valueOf(pathNum);
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
int textX = x * cellSize + (cellSize - fm.stringWidth(text)) / 2;
|
||||
int textY = y * cellSize + (cellSize + fm.getAscent()) / 2;
|
||||
g.drawString(text, textX, textY);
|
||||
g.setColor(new Color(50, 50, 100, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw food
|
||||
g.setColor(Color.RED);
|
||||
g.fillOval(food.x * cellSize + 2, food.y * cellSize + 2, cellSize - 4, cellSize - 4);
|
||||
|
||||
// Draw snake
|
||||
List<Point> body = snake.getBody();
|
||||
for (int i = 0; i < body.size(); i++) {
|
||||
Point segment = body.get(i);
|
||||
if (i == 0) {
|
||||
g.setColor(Color.GREEN); // Head
|
||||
} else {
|
||||
g.setColor(new Color(0, 255 - i * 2, 0)); // Body gradient
|
||||
}
|
||||
g.fillRect(segment.x * cellSize + 1, segment.y * cellSize + 1, cellSize - 2, cellSize - 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JFrame frame = new JFrame("Hamiltonian Snake AI");
|
||||
HamiltonianSnakeAI game = new HamiltonianSnakeAI();
|
||||
|
||||
frame.add(game);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setResizable(true);
|
||||
frame.pack();
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setVisible(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Snake {
|
||||
private LinkedList<Point> body;
|
||||
private int gridSize;
|
||||
|
||||
public Snake(int gridSize) {
|
||||
this.gridSize = gridSize;
|
||||
this.body = new LinkedList<>();
|
||||
// Start at top-left corner
|
||||
this.body.add(new Point(0, 0));
|
||||
}
|
||||
|
||||
public void move(Point newHead) {
|
||||
body.addFirst(new Point(newHead.x, newHead.y));
|
||||
body.removeLast();
|
||||
}
|
||||
|
||||
public void grow() {
|
||||
Point tail = body.getLast();
|
||||
body.addLast(new Point(tail.x, tail.y));
|
||||
}
|
||||
|
||||
public Point getHead() {
|
||||
return body.getFirst();
|
||||
}
|
||||
|
||||
public Point getTail() {
|
||||
return body.getLast();
|
||||
}
|
||||
|
||||
public List<Point> getBody() {
|
||||
return new ArrayList<>(body);
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return body.size();
|
||||
}
|
||||
|
||||
public boolean contains(Point point) {
|
||||
return body.contains(point);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user