código·java·oracle
Java y JVM

El juego de la serpiente en Java: un clásico para aprender

Un tablero, una serpiente que crece y un bucle de juego a 10 fotogramas: el proyecto perfecto para entender hilos, colas y el paso del tiempo en Java.

publicado noviembre de 2011 revisado agosto de 2026 661 palabras
JAVAPunto.java
// Punto.java -- una celda del tablero
public record Punto(int x, int y) {
    public Punto mover(Dir d) {
        return switch (d) {
            case ARRIBA    -> new Punto(x, y - 1);
            case ABAJO     -> new Punto(x, y + 1);
            case IZQUIERDA -> new Punto(x - 1, y);
            case DERECHA   -> new Punto(x + 1, y);
        };
    }
}

El juego de la serpiente es el «hola mundo» de los videojuegos: reglas de una línea: comes, creces; chocas, mueres: y todo el meollo técnico en superficie. En 2011 este sitio publicó una versión beta con AWT y applets; los applets murieron, pero el esqueleto didáctico sigue siendo el proyecto perfecto para entender cómo una programa maneja el tiempo, el estado y la entrada a la vez. Esta es la reconstrucción moderna, compilable con cualquier JDK 17.

Viejo monitor CRT con una retícula verde difusa y un serpenteo de píxeles brillantes, teclado mecánico de los 2000 delante
El taller de 2011: CRT, retícula verde y una serpiente de píxeles.

El corazón: el bucle de juego

Todos los juegos de tablero comparten la misma estructura: actualizar estado, repintar, esperar un tick, repetir. En Java, un hilo con Thread.sleep sigue siendo la forma más clara de verlo:

JAVABucleJuego.java
private static final int TICK_MS = 90;   // ~11 fotogramas/seg

public void run() {
    while (enMarcha) {
        long inicio = System.nanoTime();
        actualizar();
        repintar();
        long empleado = (System.nanoTime() - inicio) / 1_000_000;
        try {
            long pausa = TICK_MS - empleado;
            if (pausa > 0) Thread.sleep(pausa);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            enMarcha = false;
        }
    }
}

El detalle elegante es medir cuánto ha costado actualizar y restarlo de la espera: el tick real queda clavado a 90 ms aunque el repintado tarde 15. Y el detalle de adulto es restaurar el flag de interrupción en el catch: un InterruptedException tragado en silencio es la vía rápida a hilos zombis que ni viven ni mueren.

Estado: la serpiente es una cola

Estado: la serpiente es una cola : El juego de la serpiente en Java: un clásico para aprender

Una serpiente es exactamente una estructura de datos: una Deque donde la cabeza entra por un extremo y la cola sale por el otro: salvo cuando comes, que la cola se queda. Con el record Punto de arriba, la actualización completa son seis líneas:

JAVASerpiente.java
private final Deque<Punto> serpiente = new ArrayDeque<>();
private Punto comida;

void actualizar() {
    Punto nuevaCabeza = serpiente.peekFirst().mover(direccion);

    if (nuevaCabeza.x() < 0 || nuevaCabeza.x() >= ANCHO
        || nuevaCabeza.y() < 0 || nuevaCabeza.y() >= ALTO
        || serpiente.contains(nuevaCabeza)) {
        enMarcha = false;                 // pared o auto-collision
        return;
    }
    serpiente.addFirst(nuevaCabeza);
    if (nuevaCabeza.equals(comida)) {
        puntuacion++;
        colocarComida();                  // crece: no quitamos cola
    } else {
        serpiente.removeLast();
    }
}

contains sobre una cola es O(n), y aquí está bien: cien celdas no justifican un HashSet. Saber cuándo la optimización es innecesaria es parte del aprendizaje.

Entrada: el teclado y la dirección prohibida

JAVATeclado.java
addKeyListener(new KeyAdapter() {
    @Override public void keyPressed(KeyEvent e) {
        Dir nueva = switch (e.getKeyCode()) {
            case KeyEvent.VK_UP    -> Dir.ARRIBA;
            case KeyEvent.VK_DOWN  -> Dir.ABAJO;
            case KeyEvent.VK_LEFT  -> Dir.IZQUIERDA;
            case KeyEvent.VK_RIGHT -> Dir.DERECHA;
            default -> direccion;
        };
        // prohibido darse la vuelta sobre uno mismo
        if (nueva.opuesta() != direccion) direccion = nueva;
    }
});

El guardián de la dirección opuesta no es un capricho: sin él, un doble pulsación rápida en pleno tick haría a la serpiente entrar en su propio cuello y morir por un error de interfaz, no del jugador. Toda la game-feel vive en detalles así.

Dibujar: Canvas y nada más

Dibujar: Canvas y nada más : El juego de la serpiente en Java: un clásico para aprender
JAVATablero.java
@Override protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(new Color(0x15171c));
    g.fillRect(0, 0, getWidth(), getHeight());

    g.setColor(new Color(0xf89820));           // serpiente
    for (Punto p : serpiente)
        g.fillRect(p.x() * CELDA, p.y() * CELDA, CELDA - 1, CELDA - 1);

    g.setColor(new Color(0xc74634));           // comida
    Punto c = comida;
    g.fillOval(c.x() * CELDA, c.y() * CELDA, CELDA - 1, CELDA - 1);
}

Sobre un JPanel con paintComponent, el dibujado entero no llega a diez líneas. El -1 en el tamaño de la celda deja un hilo de separación entre segmentos: cuatro píxeles que convierten un blob en una serpiente reconocible.

Lo que se aprende de verdad

  • Inmutabilidad: el record Punto y el switch que devuelve nuevos puntos en vez de mutar: la serpiente nunca edita su cuerpo, lo reemplaza.
  • Hilos con educación: sleep interrumpible, flag de parada, interrupción restaurada.
  • Estado y vista separados: actualizar no sabe dibujar, paintComponent no decide; la misma lección que separa un juego de juguete de una arquitectura decente.

De aquí saltan dos caminos naturales: profundizar en el lenguaje con la reflexión y sus alternativas, o el histórico puente Java-JavaScript, que era el otro gran truco de la época: y sigue siendo una buena historia.

El juego de la serpiente nació en la época de los applets, cuando el código Java convivía con JavaScript dentro del navegador. Ese puente entre lenguajes ha cambiado mucho desde entonces: primero con JSObject y LiveConnect, después con Rhino y Nashorn, y hoy con el modelo poliglota de GraalVM. Para entender cómo se comunican ambos mundos en la actualidad, conviene revisar la evolución de esas técnicas en de LiveConnect a GraalJS, donde se detallan los mecanismos que sustituyeron a los applets.