fucked UI

This commit is contained in:
rattatwinko
2025-06-04 20:08:38 +02:00
parent 31d90004a0
commit a7365e0f27
5 changed files with 272 additions and 73 deletions

10
.idea/.gitignore generated vendored Normal file
View 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
View 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
View 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
View 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>

View File

@@ -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<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() {
@@ -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<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;
// 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<Point> visited = new HashSet<>();
Queue<Point> queue = new LinkedList<>();
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()
.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<Point> 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);
}
}