fucked UI
This commit is contained in:
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -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
|
||||||
12
.idea/material_theme_project_new.xml
generated
Normal file
12
.idea/material_theme_project_new.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MaterialThemeProjectNewConfig">
|
||||||
|
<option name="metadata">
|
||||||
|
<MTProjectMetadataState>
|
||||||
|
<option name="migrated" value="true" />
|
||||||
|
<option name="pristineConfig" value="false" />
|
||||||
|
<option name="userId" value="3730c89c:19672acdc5f:-7ff9" />
|
||||||
|
</MTProjectMetadataState>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_24" default="true" project-jdk-name="openjdk-24" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -10,7 +10,7 @@ import java.util.List;
|
|||||||
public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
||||||
private javax.swing.Timer timer;
|
private javax.swing.Timer timer;
|
||||||
private int gridSize = 20;
|
private int gridSize = 20;
|
||||||
private int cellSize = 20;
|
private int cellSize = 25;
|
||||||
private int delay = 100;
|
private int delay = 100;
|
||||||
|
|
||||||
private Snake snake;
|
private Snake snake;
|
||||||
@@ -27,6 +27,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
private JCheckBox showPathBox;
|
private JCheckBox showPathBox;
|
||||||
private JLabel scoreLabel;
|
private JLabel scoreLabel;
|
||||||
private JLabel statusLabel;
|
private JLabel statusLabel;
|
||||||
|
private JPanel gamePanel;
|
||||||
|
|
||||||
public HamiltonianSnakeAI() {
|
public HamiltonianSnakeAI() {
|
||||||
initGame();
|
initGame();
|
||||||
@@ -42,87 +43,152 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
|
|
||||||
private void setupUI() {
|
private void setupUI() {
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
setBackground(Color.BLACK);
|
setBackground(new Color(30, 30, 30));
|
||||||
|
|
||||||
// Game panel
|
// Game panel with better styling
|
||||||
JPanel gamePanel = new JPanel() {
|
gamePanel = new JPanel() {
|
||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
super.paintComponent(g);
|
super.paintComponent(g);
|
||||||
draw(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));
|
gamePanel.setPreferredSize(new Dimension(gridSize * cellSize, gridSize * cellSize));
|
||||||
|
|
||||||
// Control panel
|
// Control panel with modern look
|
||||||
JPanel controlPanel = new JPanel(new GridLayout(6, 2, 5, 5));
|
JPanel controlPanel = new JPanel();
|
||||||
controlPanel.setBackground(Color.DARK_GRAY);
|
controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
|
||||||
controlPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
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
|
// Speed control
|
||||||
controlPanel.add(new JLabel("Speed:", SwingConstants.CENTER));
|
JPanel speedPanel = createControlPanel("Speed:", speedSlider = new JSlider(1, 20, 10));
|
||||||
speedSlider = new JSlider(1, 20, 10);
|
|
||||||
speedSlider.addChangeListener(e -> {
|
speedSlider.addChangeListener(e -> {
|
||||||
delay = 210 - speedSlider.getValue() * 10;
|
delay = 210 - speedSlider.getValue() * 10;
|
||||||
if (timer != null) timer.setDelay(delay);
|
if (timer != null) timer.setDelay(delay);
|
||||||
});
|
});
|
||||||
controlPanel.add(speedSlider);
|
controlPanel.add(speedPanel);
|
||||||
|
|
||||||
// Grid size control
|
// Grid size control
|
||||||
controlPanel.add(new JLabel("Grid Size:", SwingConstants.CENTER));
|
JPanel gridPanel = createControlPanel("Grid Size:", gridSlider = new JSlider(10, 30, 20));
|
||||||
gridSlider = new JSlider(10, 50, 20); // Reduced max grid size for better performance
|
|
||||||
gridSlider.addChangeListener(e -> {
|
gridSlider.addChangeListener(e -> {
|
||||||
if (!gameRunning) {
|
if (!gameRunning) {
|
||||||
gridSize = gridSlider.getValue();
|
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));
|
gamePanel.setPreferredSize(new Dimension(gridSize * cellSize, gridSize * cellSize));
|
||||||
resetGame();
|
resetGame();
|
||||||
revalidate();
|
revalidate();
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
controlPanel.add(gridSlider);
|
controlPanel.add(gridPanel);
|
||||||
|
|
||||||
// Control buttons
|
// Button panel
|
||||||
startButton = new JButton("Start");
|
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());
|
startButton.addActionListener(e -> startGame());
|
||||||
controlPanel.add(startButton);
|
buttonPanel.add(startButton);
|
||||||
|
|
||||||
pauseButton = new JButton("Pause");
|
pauseButton = createStyledButton("Pause");
|
||||||
pauseButton.addActionListener(e -> pauseGame());
|
pauseButton.addActionListener(e -> pauseGame());
|
||||||
pauseButton.setEnabled(false);
|
pauseButton.setEnabled(false);
|
||||||
controlPanel.add(pauseButton);
|
buttonPanel.add(pauseButton);
|
||||||
|
|
||||||
// Show path checkbox
|
JButton resetButton = createStyledButton("Reset");
|
||||||
showPathBox = new JCheckBox("Show Path");
|
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 -> {
|
showPathBox.addActionListener(e -> {
|
||||||
showPath = showPathBox.isSelected();
|
showPath = showPathBox.isSelected();
|
||||||
repaint();
|
repaint();
|
||||||
});
|
});
|
||||||
controlPanel.add(showPathBox);
|
checkBoxPanel.add(showPathBox);
|
||||||
|
controlPanel.add(checkBoxPanel);
|
||||||
|
|
||||||
JButton resetButton = new JButton("Reset");
|
// Status panel
|
||||||
resetButton.addActionListener(e -> resetGame());
|
JPanel statusPanel = new JPanel(new GridLayout(2, 1, 5, 5));
|
||||||
controlPanel.add(resetButton);
|
statusPanel.setBackground(new Color(40, 40, 40));
|
||||||
|
|
||||||
// Status labels
|
scoreLabel = createStatusLabel("Score: 0");
|
||||||
scoreLabel = new JLabel("Score: 0", SwingConstants.CENTER);
|
statusPanel.add(scoreLabel);
|
||||||
scoreLabel.setForeground(Color.WHITE);
|
|
||||||
controlPanel.add(scoreLabel);
|
|
||||||
|
|
||||||
statusLabel = new JLabel("Ready", SwingConstants.CENTER);
|
statusLabel = createStatusLabel("Ready");
|
||||||
statusLabel.setForeground(Color.GREEN);
|
statusLabel.setForeground(new Color(100, 255, 100));
|
||||||
controlPanel.add(statusLabel);
|
statusPanel.add(statusLabel);
|
||||||
|
|
||||||
|
controlPanel.add(statusPanel);
|
||||||
|
|
||||||
add(gamePanel, BorderLayout.CENTER);
|
add(gamePanel, BorderLayout.CENTER);
|
||||||
add(controlPanel, BorderLayout.EAST);
|
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() {
|
private void generateHamiltonianCycle() {
|
||||||
hamiltonianPath = new int[gridSize][gridSize];
|
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) {
|
if (gridSize % 2 == 0) {
|
||||||
generateEvenGridCycle();
|
generateEvenGridCycle();
|
||||||
} else {
|
} else {
|
||||||
@@ -148,7 +214,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void generateOddGridCycle() {
|
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 pathIndex = 0;
|
||||||
int row = 0, col = 0;
|
int row = 0, col = 0;
|
||||||
boolean movingRight = true;
|
boolean movingRight = true;
|
||||||
@@ -160,28 +226,70 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
if (col < gridSize - 1) {
|
if (col < gridSize - 1) {
|
||||||
col++;
|
col++;
|
||||||
} else {
|
} else {
|
||||||
|
// At right edge, move down and reverse direction
|
||||||
row++;
|
row++;
|
||||||
|
if (row < gridSize) {
|
||||||
|
hamiltonianPath[row][col] = pathIndex++;
|
||||||
|
}
|
||||||
movingRight = false;
|
movingRight = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (col > 0) {
|
if (col > 0) {
|
||||||
col--;
|
col--;
|
||||||
} else {
|
} else {
|
||||||
|
// At left edge, move down and reverse direction
|
||||||
row++;
|
row++;
|
||||||
|
if (row < gridSize) {
|
||||||
|
hamiltonianPath[row][col] = pathIndex++;
|
||||||
|
}
|
||||||
movingRight = true;
|
movingRight = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect the last cell back to the first
|
|
||||||
hamiltonianPath[gridSize-1][0] = pathIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnFood() {
|
private void spawnFood() {
|
||||||
Random rand = new Random();
|
Random rand = new Random();
|
||||||
|
int attempts = 0;
|
||||||
|
int maxAttempts = 100;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
food = new Point(rand.nextInt(gridSize), rand.nextInt(gridSize));
|
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<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());
|
||||||
|
|
||||||
|
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() {
|
private void startGame() {
|
||||||
@@ -191,7 +299,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
pauseButton.setEnabled(true);
|
pauseButton.setEnabled(true);
|
||||||
gridSlider.setEnabled(false);
|
gridSlider.setEnabled(false);
|
||||||
statusLabel.setText("Running");
|
statusLabel.setText("Running");
|
||||||
statusLabel.setForeground(Color.GREEN);
|
statusLabel.setForeground(new Color(100, 255, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pauseGame() {
|
private void pauseGame() {
|
||||||
@@ -214,7 +322,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
gridSlider.setEnabled(true);
|
gridSlider.setEnabled(true);
|
||||||
scoreLabel.setText("Score: 0");
|
scoreLabel.setText("Score: 0");
|
||||||
statusLabel.setText("Ready");
|
statusLabel.setText("Ready");
|
||||||
statusLabel.setForeground(Color.GREEN);
|
statusLabel.setForeground(new Color(100, 255, 100));
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +336,7 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
gameRunning = false;
|
gameRunning = false;
|
||||||
timer.stop();
|
timer.stop();
|
||||||
statusLabel.setText("YOU WIN!");
|
statusLabel.setText("YOU WIN!");
|
||||||
statusLabel.setForeground(Color.yellow);
|
statusLabel.setForeground(Color.YELLOW);
|
||||||
startButton.setEnabled(true);
|
startButton.setEnabled(true);
|
||||||
pauseButton.setEnabled(false);
|
pauseButton.setEnabled(false);
|
||||||
}
|
}
|
||||||
@@ -268,38 +376,53 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
return head; // Stay in place (will trigger collision detection)
|
return head; // Stay in place (will trigger collision detection)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we can reach food safely with a shortcut, take it
|
// For longer snakes, be more cautious about shortcuts
|
||||||
Point bestMove = findBestShortcut(head, possibleMoves);
|
if (snake.getLength() < gridSize * gridSize * 0.6) {
|
||||||
if (bestMove != null) {
|
// Try to find a safe shortcut to food
|
||||||
return bestMove;
|
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);
|
return followHamiltonianCycle(head, possibleMoves);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Point findBestShortcut(Point head, List<Point> 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;
|
Point bestMove = null;
|
||||||
int minDistance = Integer.MAX_VALUE;
|
int minDistance = Integer.MAX_VALUE;
|
||||||
|
|
||||||
for (Point move : possibleMoves) {
|
for (Point move : possibleMoves) {
|
||||||
int distance = estimateDistance(move, food);
|
int distance = estimateDistance(move, food);
|
||||||
if (distance < minDistance && isPathSafe(move, food)) {
|
if (distance < minDistance && isPathSafe(move, food)) {
|
||||||
minDistance = distance;
|
// Additional check for tail collision risk
|
||||||
bestMove = move;
|
if (!willCollideWithTail(move)) {
|
||||||
|
minDistance = distance;
|
||||||
|
bestMove = move;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestMove;
|
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) {
|
private boolean isPathSafe(Point from, Point to) {
|
||||||
// Simple flood fill to check if path exists
|
// More efficient path safety check with early termination
|
||||||
Set<Point> visited = new HashSet<>();
|
Set<Point> visited = new HashSet<>();
|
||||||
Queue<Point> queue = new LinkedList<>();
|
Queue<Point> queue = new LinkedList<>();
|
||||||
Set<Point> obstacles = new HashSet<>(snake.getBody());
|
Set<Point> 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()
|
return possibleMoves.stream()
|
||||||
.min(Comparator.comparingInt(p -> {
|
.min(Comparator.comparingInt(p -> {
|
||||||
int diff = hamiltonianPath[p.y][p.x] - currentIndex;
|
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) {
|
if (newX >= 0 && newX < gridSize && newY >= 0 && newY < gridSize) {
|
||||||
Point newPos = new Point(newX, newY);
|
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())) {
|
if (!snake.contains(newPos) || newPos.equals(snake.getTail())) {
|
||||||
safeMoves.add(newPos);
|
safeMoves.add(newPos);
|
||||||
}
|
}
|
||||||
@@ -389,11 +512,11 @@ public class HamiltonianSnakeAI extends JPanel implements ActionListener {
|
|||||||
|
|
||||||
private void draw(Graphics g) {
|
private void draw(Graphics g) {
|
||||||
// Clear background
|
// Clear background
|
||||||
g.setColor(Color.BLACK);
|
g.setColor(new Color(20, 20, 20));
|
||||||
g.fillRect(0, 0, getWidth(), getHeight());
|
g.fillRect(0, 0, getWidth(), getHeight());
|
||||||
|
|
||||||
// Draw grid
|
// Draw grid with better visibility
|
||||||
g.setColor(Color.DARK_GRAY);
|
g.setColor(new Color(50, 50, 50));
|
||||||
for (int i = 0; i <= gridSize; i++) {
|
for (int i = 0; i <= gridSize; i++) {
|
||||||
g.drawLine(i * cellSize, 0, i * cellSize, gridSize * cellSize);
|
g.drawLine(i * cellSize, 0, i * cellSize, gridSize * cellSize);
|
||||||
g.drawLine(0, i * cellSize, gridSize * cellSize, i * 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
|
// Draw Hamiltonian path if enabled
|
||||||
if (showPath) {
|
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 y = 0; y < gridSize; y++) {
|
||||||
for (int x = 0; x < gridSize; x++) {
|
for (int x = 0; x < gridSize; x++) {
|
||||||
int pathNum = hamiltonianPath[y][x];
|
|
||||||
g.fillRect(x * cellSize + 1, y * cellSize + 1, cellSize - 2, cellSize - 2);
|
g.fillRect(x * cellSize + 1, y * cellSize + 1, cellSize - 2, cellSize - 2);
|
||||||
|
|
||||||
if (cellSize > 10) {
|
if (cellSize > 15) {
|
||||||
g.setColor(Color.CYAN);
|
g.setColor(new Color(100, 200, 255));
|
||||||
g.setFont(new Font("Arial", Font.PLAIN, Math.max(8, cellSize / 3)));
|
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();
|
FontMetrics fm = g.getFontMetrics();
|
||||||
int textX = x * cellSize + (cellSize - fm.stringWidth(text)) / 2;
|
int textX = x * cellSize + (cellSize - fm.stringWidth(text)) / 2;
|
||||||
int textY = y * cellSize + (cellSize + fm.getAscent()) / 2;
|
int textY = y * cellSize + (cellSize + fm.getAscent()) / 2;
|
||||||
g.drawString(text, textX, textY);
|
g.drawString(text, textX, textY);
|
||||||
g.setColor(new Color(50, 50, 100, 100));
|
g.setColor(new Color(30, 30, 70, 150));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw food
|
// Draw food with better visual
|
||||||
g.setColor(Color.RED);
|
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.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<Point> body = snake.getBody();
|
List<Point> body = snake.getBody();
|
||||||
for (int i = 0; i < body.size(); i++) {
|
for (int i = 0; i < body.size(); i++) {
|
||||||
Point segment = body.get(i);
|
Point segment = body.get(i);
|
||||||
|
|
||||||
|
// Create gradient for snake segments
|
||||||
|
Color startColor, endColor;
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
g.setColor(Color.GREEN); // Head
|
// Head
|
||||||
|
startColor = new Color(100, 255, 100);
|
||||||
|
endColor = new Color(50, 200, 50);
|
||||||
} else {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user