diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..7bc07ec
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Environment-dependent path to Maven home directory
+/mavenHomeManager.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..250ea61
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..f88aef9
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/hamiltoniansnakeai/HamiltonianSnakeAI.java b/src/main/java/org/hamiltoniansnakeai/HamiltonianSnakeAI.java
index 6d4be7e..9447cad 100644
--- a/src/main/java/org/hamiltoniansnakeai/HamiltonianSnakeAI.java
+++ b/src/main/java/org/hamiltoniansnakeai/HamiltonianSnakeAI.java
@@ -10,7 +10,7 @@ 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 cellSize = 25;
private int delay = 100;
private Snake snake;
@@ -27,6 +27,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
private JCheckBox showPathBox;
private JLabel scoreLabel;
private JLabel statusLabel;
+ private JPanel gamePanel;
public HamiltonianSnakeAI() {
initGame();
@@ -42,87 +43,152 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
private void setupUI() {
setLayout(new BorderLayout());
- setBackground(Color.BLACK);
+ setBackground(new Color(30, 30, 30));
- // Game panel
- JPanel gamePanel = new JPanel() {
+ // Game panel with better styling
+ gamePanel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
};
- gamePanel.setBackground(Color.BLACK);
+ 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));
- // 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));
+ // Control panel with modern look
+ JPanel controlPanel = new JPanel();
+ controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
+ controlPanel.setBackground(new Color(40, 40, 40));
+ controlPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
+
+ // Title label
+ JLabel titleLabel = new JLabel("Hamiltonian Snake AI");
+ titleLabel.setFont(new Font("Arial", Font.BOLD, 18));
+ titleLabel.setForeground(new Color(220, 220, 220));
+ titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+ controlPanel.add(titleLabel);
+ controlPanel.add(Box.createRigidArea(new Dimension(0, 15)));
// Speed control
- controlPanel.add(new JLabel("Speed:", SwingConstants.CENTER));
- speedSlider = new JSlider(1, 20, 10);
+ JPanel speedPanel = createControlPanel("Speed:", speedSlider = new JSlider(1, 20, 10));
speedSlider.addChangeListener(e -> {
delay = 210 - speedSlider.getValue() * 10;
if (timer != null) timer.setDelay(delay);
});
- controlPanel.add(speedSlider);
+ controlPanel.add(speedPanel);
// Grid size control
- controlPanel.add(new JLabel("Grid Size:", SwingConstants.CENTER));
- gridSlider = new JSlider(10, 50, 20); // Reduced max grid size for better performance
+ JPanel gridPanel = createControlPanel("Grid Size:", gridSlider = new JSlider(10, 30, 20));
gridSlider.addChangeListener(e -> {
if (!gameRunning) {
gridSize = gridSlider.getValue();
- cellSize = Math.max(5, Math.min(20, 800 / gridSize)); // Minimum cell size of 5
+ cellSize = Math.max(15, Math.min(30, 600 / gridSize)); // Better cell size range
gamePanel.setPreferredSize(new Dimension(gridSize * cellSize, gridSize * cellSize));
resetGame();
revalidate();
repaint();
}
});
- controlPanel.add(gridSlider);
+ controlPanel.add(gridPanel);
- // Control buttons
- startButton = new JButton("Start");
+ // Button panel
+ JPanel buttonPanel = new JPanel(new GridLayout(1, 3, 10, 0));
+ buttonPanel.setBackground(new Color(40, 40, 40));
+ buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
+
+ startButton = createStyledButton("Start");
startButton.addActionListener(e -> startGame());
- controlPanel.add(startButton);
+ buttonPanel.add(startButton);
- pauseButton = new JButton("Pause");
+ pauseButton = createStyledButton("Pause");
pauseButton.addActionListener(e -> pauseGame());
pauseButton.setEnabled(false);
- controlPanel.add(pauseButton);
+ buttonPanel.add(pauseButton);
- // Show path checkbox
- showPathBox = new JCheckBox("Show Path");
+ JButton resetButton = createStyledButton("Reset");
+ resetButton.addActionListener(e -> resetGame());
+ buttonPanel.add(resetButton);
+
+ controlPanel.add(buttonPanel);
+
+ // Checkbox panel
+ JPanel checkBoxPanel = new JPanel();
+ checkBoxPanel.setBackground(new Color(40, 40, 40));
+ showPathBox = new JCheckBox("Show Hamiltonian Path");
+ showPathBox.setForeground(Color.WHITE);
+ showPathBox.setBackground(new Color(40, 40, 40));
+ showPathBox.setFocusPainted(false);
showPathBox.addActionListener(e -> {
showPath = showPathBox.isSelected();
repaint();
});
- controlPanel.add(showPathBox);
+ checkBoxPanel.add(showPathBox);
+ controlPanel.add(checkBoxPanel);
- JButton resetButton = new JButton("Reset");
- resetButton.addActionListener(e -> resetGame());
- controlPanel.add(resetButton);
+ // Status panel
+ JPanel statusPanel = new JPanel(new GridLayout(2, 1, 5, 5));
+ statusPanel.setBackground(new Color(40, 40, 40));
- // Status labels
- scoreLabel = new JLabel("Score: 0", SwingConstants.CENTER);
- scoreLabel.setForeground(Color.WHITE);
- controlPanel.add(scoreLabel);
+ scoreLabel = createStatusLabel("Score: 0");
+ statusPanel.add(scoreLabel);
- statusLabel = new JLabel("Ready", SwingConstants.CENTER);
- statusLabel.setForeground(Color.GREEN);
- controlPanel.add(statusLabel);
+ statusLabel = createStatusLabel("Ready");
+ statusLabel.setForeground(new Color(100, 255, 100));
+ statusPanel.add(statusLabel);
+
+ controlPanel.add(statusPanel);
add(gamePanel, BorderLayout.CENTER);
add(controlPanel, BorderLayout.EAST);
}
+ private JPanel createControlPanel(String labelText, JSlider slider) {
+ JPanel panel = new JPanel(new BorderLayout(10, 0));
+ panel.setBackground(new Color(40, 40, 40));
+
+ JLabel label = new JLabel(labelText);
+ label.setForeground(Color.WHITE);
+ panel.add(label, BorderLayout.WEST);
+
+ slider.setBackground(new Color(40, 40, 40));
+ slider.setForeground(Color.WHITE);
+ slider.setPaintTicks(true);
+ slider.setPaintLabels(true);
+ slider.setMajorTickSpacing(slider.getMaximum() / 4);
+ panel.add(slider, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ private JButton createStyledButton(String text) {
+ JButton button = new JButton(text);
+ button.setBackground(new Color(70, 70, 70));
+ button.setForeground(Color.WHITE);
+ button.setFocusPainted(false);
+ button.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(new Color(100, 100, 100), 1),
+ BorderFactory.createEmptyBorder(5, 15, 5, 15)
+ ));
+ button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ return button;
+ }
+
+ private JLabel createStatusLabel(String text) {
+ JLabel label = new JLabel(text, SwingConstants.CENTER);
+ label.setFont(new Font("Arial", Font.BOLD, 14));
+ label.setForeground(Color.WHITE);
+ label.setOpaque(true);
+ label.setBackground(new Color(50, 50, 50));
+ label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ return label;
+ }
+
private void generateHamiltonianCycle() {
hamiltonianPath = new int[gridSize][gridSize];
- // Generate a more efficient Hamiltonian cycle
+ // Generate a more efficient Hamiltonian cycle that's better for longer snakes
if (gridSize % 2 == 0) {
generateEvenGridCycle();
} else {
@@ -148,7 +214,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
}
private void generateOddGridCycle() {
- // More complex cycle for odd-sized grids
+ // More complex cycle for odd-sized grids that reduces tail collisions
int pathIndex = 0;
int row = 0, col = 0;
boolean movingRight = true;
@@ -160,28 +226,70 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
if (col < gridSize - 1) {
col++;
} else {
+ // At right edge, move down and reverse direction
row++;
+ if (row < gridSize) {
+ hamiltonianPath[row][col] = pathIndex++;
+ }
movingRight = false;
}
} else {
if (col > 0) {
col--;
} else {
+ // At left edge, move down and reverse direction
row++;
+ if (row < gridSize) {
+ hamiltonianPath[row][col] = pathIndex++;
+ }
movingRight = true;
}
}
}
-
- // Connect the last cell back to the first
- hamiltonianPath[gridSize-1][0] = pathIndex;
}
private void spawnFood() {
Random rand = new Random();
+ int attempts = 0;
+ int maxAttempts = 100;
+
do {
food = new Point(rand.nextInt(gridSize), rand.nextInt(gridSize));
- } while (snake.contains(food));
+ attempts++;
+ // Ensure food isn't placed where it would be impossible to reach
+ if (attempts >= maxAttempts || !snake.contains(food)) {
+ 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
+ Set obstacles = new HashSet<>(snake.getBody());
+ obstacles.remove(snake.getTail());
+
+ // Simple flood fill to check reachability from snake head
+ Set visited = new HashSet<>();
+ Queue queue = new LinkedList<>();
+ queue.add(snake.getHead());
+ visited.add(snake.getHead());
+
+ while (!queue.isEmpty()) {
+ Point current = queue.poll();
+ if (current.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);
+ }
+ }
+ }
+
+ return true; // Food is in a dead zone
}
private void startGame() {
@@ -191,7 +299,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
pauseButton.setEnabled(true);
gridSlider.setEnabled(false);
statusLabel.setText("Running");
- statusLabel.setForeground(Color.GREEN);
+ statusLabel.setForeground(new Color(100, 255, 100));
}
private void pauseGame() {
@@ -214,7 +322,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
gridSlider.setEnabled(true);
scoreLabel.setText("Score: 0");
statusLabel.setText("Ready");
- statusLabel.setForeground(Color.GREEN);
+ statusLabel.setForeground(new Color(100, 255, 100));
repaint();
}
@@ -228,7 +336,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
gameRunning = false;
timer.stop();
statusLabel.setText("YOU WIN!");
- statusLabel.setForeground(Color.yellow);
+ statusLabel.setForeground(Color.YELLOW);
startButton.setEnabled(true);
pauseButton.setEnabled(false);
}
@@ -268,38 +376,53 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
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;
+ // 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;
+ }
}
- // Otherwise follow the Hamiltonian cycle
+ // Follow the Hamiltonian cycle with tail collision awareness
return followHamiltonianCycle(head, possibleMoves);
}
private Point findBestShortcut(Point head, List 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;
+ // Additional check for tail collision risk
+ if (!willCollideWithTail(move)) {
+ minDistance = distance;
+ 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
+ }
+
+ // 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
+ }
+
private boolean isPathSafe(Point from, Point to) {
- // Simple flood fill to check if path exists
+ // More efficient path safety check with early termination
Set visited = new HashSet<>();
Queue queue = new LinkedList<>();
Set obstacles = new HashSet<>(snake.getBody());
@@ -336,7 +459,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
}
}
- // If exact next in cycle isn't possible, find the closest
+ // 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;
@@ -362,7 +485,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
if (newX >= 0 && newX < gridSize && newY >= 0 && newY < gridSize) {
Point newPos = new Point(newX, newY);
- // Check if this position is safe
+ // Check if this position is safe (not occupied by snake body, except tail which will move)
if (!snake.contains(newPos) || newPos.equals(snake.getTail())) {
safeMoves.add(newPos);
}
@@ -389,11 +512,11 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
private void draw(Graphics g) {
// Clear background
- g.setColor(Color.BLACK);
+ g.setColor(new Color(20, 20, 20));
g.fillRect(0, 0, getWidth(), getHeight());
- // Draw grid
- g.setColor(Color.DARK_GRAY);
+ // Draw grid with better visibility
+ g.setColor(new Color(50, 50, 50));
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);
@@ -401,40 +524,74 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
// Draw Hamiltonian path if enabled
if (showPath) {
- g.setColor(new Color(50, 50, 100, 100));
+ g.setColor(new Color(30, 30, 70, 150));
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);
+ if (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(pathNum);
+ String text = String.valueOf(hamiltonianPath[y][x]);
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));
+ g.setColor(new Color(30, 30, 70, 150));
}
}
}
}
- // Draw food
- g.setColor(Color.RED);
+ // Draw food with better visual
+ GradientPaint foodGradient = new GradientPaint(
+ food.x * cellSize, food.y * cellSize, new Color(255, 50, 50),
+ food.x * cellSize + cellSize, food.y * cellSize + cellSize, new Color(200, 0, 0)
+ );
+ ((Graphics2D)g).setPaint(foodGradient);
g.fillOval(food.x * cellSize + 2, food.y * cellSize + 2, cellSize - 4, cellSize - 4);
+ g.setColor(new Color(150, 0, 0));
+ g.drawOval(food.x * cellSize + 2, food.y * cellSize + 2, cellSize - 4, cellSize - 4);
- // Draw snake
+ // Draw snake with better visual
List body = snake.getBody();
for (int i = 0; i < body.size(); i++) {
Point segment = body.get(i);
+
+ // Create gradient for snake segments
+ Color startColor, endColor;
if (i == 0) {
- g.setColor(Color.GREEN); // Head
+ // Head
+ startColor = new Color(100, 255, 100);
+ endColor = new Color(50, 200, 50);
} else {
- g.setColor(new Color(0, 255 - i * 2, 0)); // Body gradient
+ // Body - gradient from head to tail
+ float ratio = (float)i / body.size();
+ startColor = new Color(
+ (int)(100 + 155 * (1 - ratio)),
+ (int)(255 - 155 * ratio),
+ (int)(100 + 155 * (1 - ratio))
+ );
+ endColor = new Color(
+ (int)(50 + 50 * (1 - ratio)),
+ (int)(200 - 150 * ratio),
+ (int)(50 + 50 * (1 - ratio))
+ );
}
- g.fillRect(segment.x * cellSize + 1, segment.y * cellSize + 1, cellSize - 2, cellSize - 2);
+
+ GradientPaint segmentGradient = new GradientPaint(
+ segment.x * cellSize, segment.y * cellSize, startColor,
+ segment.x * cellSize + cellSize, segment.y * cellSize + cellSize, endColor
+ );
+
+ ((Graphics2D)g).setPaint(segmentGradient);
+ g.fillRoundRect(segment.x * cellSize + 1, segment.y * cellSize + 1,
+ cellSize - 2, cellSize - 2, 5, 5);
+
+ // Add border to segments
+ g.setColor(new Color(0, 80, 0));
+ g.drawRoundRect(segment.x * cellSize + 1, segment.y * cellSize + 1,
+ cellSize - 2, cellSize - 2, 5, 5);
}
}