import java.awt.*; import java.util.LinkedList; import java.util.Random; import javax.swing.*; /** * Clase que representa un juego de la serpiente en ASCII-Art utilizando Java Swing. */ public class SnakeAsciiArt extends JPanel { // Tamaño del área de juego en caracteres private static final int WIDTH = 40; private static final int HEIGHT = 20; // Caracteres utilizados en la representación del juego private static final char APPLE_CHAR = '@'; private static final char EMPTY_CHAR = ' '; private LinkedList<Segment> snake; // Lista de segmentos que representan el cuerpo de la serpiente private Point apple; // Posición de la manzana private char[][] grid; // Matriz de caracteres que representa el área de juego private Timer timer; // Temporizador para actualizar el juego periódicamente private String direction; // Dirección actual de la serpiente private boolean gameOver; // Estado del juego /** * Clase que representa un segmento de la serpiente. */ private static class Segment { Point position; String direction; Segment(Point position, String direction) { this.position = position; this.direction = direction; } } /** * Constructor que inicializa el juego. */ public SnakeAsciiArt() { snake = new LinkedList<>(); snake.add(new Segment(new Point(WIDTH / 2, HEIGHT / 2), "RIGHT")); // Posiciona la serpiente en el centro direction = "RIGHT"; // Dirección inicial gameOver = false; // El juego comienza en estado activo spawnApple(); // Genera la primera manzana grid = new char[HEIGHT][WIDTH]; // Inicializa la matriz de juego // Configura el temporizador para actualizar el juego cada 200ms timer = new Timer(200, e -> updateGame()); timer.start(); } /** * Genera una nueva manzana en una posición aleatoria que no coincida con la serpiente. */ private void spawnApple() { Random rand = new Random(); int x, y; do { x = rand.nextInt(WIDTH); y = rand.nextInt(HEIGHT); } while (isSnakeBody(x, y)); // Asegura que la manzana no aparezca dentro de la serpiente apple = new Point(x, y); } /** * Verifica si una coordenada pertenece al cuerpo de la serpiente. */ private boolean isSnakeBody(int x, int y) { for (Segment s : snake) { if (s.position.x == x && s.position.y == y) return true; } return false; } /** * Actualiza el estado del juego en cada ciclo del temporizador. */ private void updateGame() { if (gameOver) return; // No actualizar si el juego ha terminado Segment head = snake.getFirst(); // Obtiene la cabeza de la serpiente Point newHeadPosition = new Point(head.position); // Movimiento automático hacia la manzana if (apple.x > head.position.x) { newHeadPosition.x++; direction = "RIGHT"; } else if (apple.x < head.position.x) { newHeadPosition.x--; direction = "LEFT"; } else if (apple.y > head.position.y) { newHeadPosition.y++; direction = "DOWN"; } else if (apple.y < head.position.y) { newHeadPosition.y--; direction = "UP"; } // Verifica colisiones con los bordes o con el cuerpo de la serpiente if (newHeadPosition.x < 0 || newHeadPosition.x >= WIDTH || newHeadPosition.y < 0 || newHeadPosition.y >= HEIGHT || isSnakeBody(newHeadPosition.x, newHeadPosition.y)) { System.out.println("Game Over: La serpiente salió del área de juego o se chocó a sí misma."); gameOver = true; // Cambia el estado del juego a terminado timer.stop(); // Detiene el juego al colisionar repaint(); // Redibuja el juego para mostrar el mensaje de "GAME OVER" return; } // Verifica si la serpiente ha comido la manzana if (newHeadPosition.equals(apple)) { snake.addFirst(new Segment(newHeadPosition, direction)); // La serpiente crece spawnApple(); // Se genera una nueva manzana } else { snake.addFirst(new Segment(newHeadPosition, direction)); snake.removeLast(); // Elimina la última parte de la serpiente para simular el movimiento } repaint(); // Redibuja el juego } /** * Llena la matriz de juego con los elementos actuales (serpiente y manzana). */ private void renderGrid() { for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { grid[y][x] = EMPTY_CHAR; } } for (Segment s : snake) { char segmentChar; switch (s.direction) { case "UP": segmentChar = '|'; break; case "DOWN": segmentChar = '|'; break; case "LEFT": segmentChar = '-'; break; case "RIGHT": segmentChar = '-'; break; default: segmentChar = '='; } grid[s.position.y][s.position.x] = segmentChar; } Segment head = snake.getFirst(); switch (head.direction) { case "UP": grid[head.position.y][head.position.x] = '^'; break; case "DOWN": grid[head.position.y][head.position.x] = 'v'; break; case "LEFT": grid[head.position.y][head.position.x] = '<'; break; case "RIGHT": grid[head.position.y][head.position.x] = '>'; break; } grid[apple.y][apple.x] = APPLE_CHAR; } /** * Dibuja el contenido de la matriz en el panel. */ @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.setFont(new Font("Monospaced", Font.PLAIN, 14)); if (gameOver) { // Dibujar el mensaje de "GAME OVER" estilizado en ASCII String[] gameOverMessage = { " _ ____ _____ _ ____ _ ____ ___ _ ", "(_) ___|| ____| / \\ / ___| / \\ | __ ) / _ \\| |", "| \\___ \\| _| / _ \\| | / _ \\ | _ \\| | | | |", "| |___) | |___ / ___ \\ |___ / ___ \\| |_) | |_| |_|", "|_|____/|_____| /_/ \\_\\____/_/ \\_\\____/ \\___/(_)" }; int messageX = (getWidth() - g.getFontMetrics().stringWidth(gameOverMessage[0])) / 2; int messageY = getHeight() / 2 - (gameOverMessage.length * 15) / 2; for (int i = 0; i < gameOverMessage.length; i++) { g.drawString(gameOverMessage[i], messageX, messageY + (i * 15)); } } else { renderGrid(); // Dibujar el título "SNAKE" estilizado en ASCII String[] title = { " ____ _____ ____ ____ ___ _____ _ _ _____ _____ ", "/ ___|| ____| _ \\| _ \\_ _| ____| \\ | |_ _| ____|", "\\___ \\| _| | |_) | |_) | || _| | \\| | | | | _| ", " ___) | |___| _ <| __/| || |___| |\\ | | | | |___ ", "|____/|_____|_| \\_\\_| |___|_____|_| \\_| |_| |_____|" }; int titleX = (getWidth() - g.getFontMetrics().stringWidth(title[0])) / 2; int titleY = 30; // Ajusta la posición vertical según sea necesario for (int i = 0; i < title.length; i++) { g.drawString(title[i], titleX, titleY + (i * 15)); } // Dibujar el contorno del área de juego int gridX = 10; int gridY = (titleY + title.length * 15) + 10; int cellSize = 15; g.drawRect(gridX - 1, gridY - 1, WIDTH * cellSize + 1, HEIGHT * cellSize + 1); for (int y = 0; y < HEIGHT; y++) { StringBuilder line = new StringBuilder(); for (int x = 0; x < WIDTH; x++) { line.append(grid[y][x]); } g.drawString(line.toString(), gridX, gridY + (y * cellSize)); } } } /** * Método principal que inicializa la ventana y ejecuta el juego. */ public static void main(String[] args) { JFrame frame = new JFrame("ASCII Snake Animation"); SnakeAsciiArt panel = new SnakeAsciiArt(); frame.add(panel); frame.setSize(620, 450); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }