En este proyecto vamos a crear un juego Snake animado en Python usando Pygame. A diferencia de la versión por turnos que podemos hacer en pseudocódigo, acá la serpiente se moverá continuamente y responderá al teclado en tiempo real.
El objetivo del tutorial es construir un juego completo y, al mismo tiempo, entender conceptos fundamentales del desarrollo de videojuegos: bucle principal, FPS, eventos, estados, colisiones, dibujo en pantalla y actualización continua.
¿Qué vamos a construir?
El juego tendrá:
- movimiento continuo de la serpiente;
- controles con flechas o WASD;
- comida en posiciones aleatorias;
- crecimiento al comer;
- puntaje;
- colisión contra paredes;
- colisión contra el propio cuerpo;
- aumento progresivo de velocidad;
- pantalla de Game Over;
- reinicio de partida sin cerrar el programa.
1. Instalar Pygame
Pygame es una biblioteca para crear juegos 2D en Python. Se instala desde la terminal o consola con:
pip install pygame
Si en tu sistema hay varias versiones de Python, también podés probar:
python -m pip install pygame
o:
py -m pip install pygame
2. Crear la ventana
Primero importamos las bibliotecas y configuramos el tamaño de la ventana.
import pygame import random pygame.init() ANCHO = 600 ALTO = 600 TAMANIO = 20 pantalla = pygame.display.set_mode((ANCHO, ALTO)) pygame.display.set_caption("Snake - Club Programador")
El tamaño de cada bloque de la serpiente será de 20 píxeles. Como la ventana mide 600 x 600, tendremos una cuadrícula lógica de 30 x 30 posiciones.
3. El concepto más importante: el game loop
Un videojuego se ejecuta dentro de un ciclo que se repite constantemente. En cada vuelta:
- leemos eventos del teclado;
- actualizamos la posición de los objetos;
- comprobamos colisiones;
- dibujamos nuevamente la pantalla.
Ese ciclo se conoce como game loop o bucle principal del juego.
while jugando:
procesar_eventos()
actualizar_juego()
dibujar()
pygame.display.flip()
4. Representar la serpiente
Vamos a guardar la serpiente como una lista de coordenadas. La primera posición será siempre la cabeza.
serpiente = [
(300, 300),
(280, 300),
(260, 300)
]
La cabeza está en serpiente[0]. Cada tupla representa una coordenada (x, y).
5. Código completo
import pygame import random pygame.init() ANCHO = 600 ALTO = 600 TAMANIO = 20 FPS_INICIAL = 8 FPS_MAXIMO = 18 pantalla = pygame.display.set_mode((ANCHO, ALTO)) pygame.display.set_caption("Snake - Club Programador") reloj = pygame.time.Clock() fuente = pygame.font.SysFont("Arial", 26) fuente_grande = pygame.font.SysFont("Arial", 48) def nueva_comida(serpiente): while True: x = random.randrange(0, ANCHO, TAMANIO) y = random.randrange(0, ALTO, TAMANIO) posicion = (x, y) if posicion not in serpiente: return posicion def crear_partida(): serpiente = [ (300, 300), (280, 300), (260, 300) ] direccion = (TAMANIO, 0) comida = nueva_comida(serpiente) return serpiente, direccion, comida def direccion_opuesta(actual, nueva): return ( actual[0] + nueva[0] == 0 and actual[1] + nueva[1] == 0 ) def cambiar_direccion(evento, direccion): nueva = direccion if evento.key in (pygame.K_UP, pygame.K_w): nueva = (0, -TAMANIO) elif evento.key in (pygame.K_DOWN, pygame.K_s): nueva = (0, TAMANIO) elif evento.key in (pygame.K_LEFT, pygame.K_a): nueva = (-TAMANIO, 0) elif evento.key in (pygame.K_RIGHT, pygame.K_d): nueva = (TAMANIO, 0) if direccion_opuesta(direccion, nueva): return direccion return nueva def mover_serpiente(serpiente, direccion): cabeza_x, cabeza_y = serpiente[0] nueva_cabeza = ( cabeza_x + direccion[0], cabeza_y + direccion[1] ) serpiente.insert(0, nueva_cabeza) return nueva_cabeza def hay_colision(serpiente): cabeza_x, cabeza_y = serpiente[0] choca_pared = ( cabeza_x < 0 or cabeza_x >= ANCHO or cabeza_y < 0 or cabeza_y >= ALTO ) choca_cuerpo = serpiente[0] in serpiente[1:] return choca_pared or choca_cuerpo def calcular_velocidad(puntos): velocidad = FPS_INICIAL + puntos // 3 return min(velocidad, FPS_MAXIMO) def dibujar_juego(serpiente, comida, puntos): pantalla.fill((15, 23, 42)) pygame.draw.rect( pantalla, (239, 68, 68), (*comida, TAMANIO, TAMANIO) ) for indice, parte in enumerate(serpiente): if indice == 0: color = (34, 197, 94) else: color = (22, 163, 74) pygame.draw.rect( pantalla, color, (*parte, TAMANIO, TAMANIO) ) texto = fuente.render( f"Puntaje: {puntos}", True, (255, 255, 255) ) pantalla.blit(texto, (10, 10)) def dibujar_game_over(puntos): pantalla.fill((15, 23, 42)) titulo = fuente_grande.render( "GAME OVER", True, (255, 255, 255) ) resultado = fuente.render( f"Puntaje: {puntos}", True, (255, 255, 255) ) ayuda = fuente.render( "R: reiniciar | ESC: salir", True, (200, 200, 200) ) pantalla.blit( titulo, titulo.get_rect(center=(ANCHO // 2, ALTO // 2 - 70)) ) pantalla.blit( resultado, resultado.get_rect(center=(ANCHO // 2, ALTO // 2)) ) pantalla.blit( ayuda, ayuda.get_rect(center=(ANCHO // 2, ALTO // 2 + 60)) ) serpiente, direccion, comida = crear_partida() puntos = 0 jugando = True game_over = False while jugando: velocidad = calcular_velocidad(puntos) reloj.tick(velocidad) for evento in pygame.event.get(): if evento.type == pygame.QUIT: jugando = False elif evento.type == pygame.KEYDOWN: if evento.key == pygame.K_ESCAPE: jugando = False elif game_over and evento.key == pygame.K_r: serpiente, direccion, comida = crear_partida() puntos = 0 game_over = False elif not game_over: direccion = cambiar_direccion(evento, direccion) if not game_over: nueva_cabeza = mover_serpiente(serpiente, direccion) if nueva_cabeza == comida: puntos += 1 comida = nueva_comida(serpiente) else: serpiente.pop() if hay_colision(serpiente): game_over = True if game_over: dibujar_game_over(puntos) else: dibujar_juego(serpiente, comida, puntos) pygame.display.flip() pygame.quit()
6. Leer el teclado
Pygame genera eventos cada vez que ocurre algo: cerrar la ventana, mover el mouse o presionar una tecla.
Para Snake nos interesa especialmente:
if evento.type == pygame.KEYDOWN: if evento.key == pygame.K_UP: direccion = (0, -TAMANIO)
También agregamos WASD para que el jugador pueda elegir cualquiera de los dos controles.
7. Evitar el giro de 180 grados
Si la serpiente se mueve hacia la derecha, no debería poder pasar directamente a la izquierda. Eso provocaría una colisión instantánea con su propio cuerpo.
La función direccion_opuesta() comprueba que la nueva dirección no sea exactamente la contraria:
return ( actual[0] + nueva[0] == 0 and actual[1] + nueva[1] == 0 )
8. ¿Cómo se mueve la serpiente?
La nueva cabeza se calcula sumando la dirección actual:
nueva_cabeza = (
cabeza_x + direccion[0],
cabeza_y + direccion[1]
)
Después insertamos esa nueva posición al comienzo de la lista:
serpiente.insert(0, nueva_cabeza)
Si no comió, eliminamos la última parte:
serpiente.pop()
El resultado visual es que todo el cuerpo avanza una posición.
9. ¿Cómo crece?
Cuando la nueva cabeza coincide con la comida, simplemente no eliminamos la cola.
if nueva_cabeza == comida: puntos += 1 comida = nueva_comida(serpiente) else: serpiente.pop()
Como agregamos una nueva cabeza pero no quitamos el último segmento, la longitud aumenta automáticamente.
10. Generar comida sin colocarla sobre la serpiente
random.randrange() permite generar posiciones alineadas con nuestra cuadrícula de 20 píxeles.
x = random.randrange(0, ANCHO, TAMANIO) y = random.randrange(0, ALTO, TAMANIO)
Después comprobamos que esa coordenada no esté ocupada:
if posicion not in serpiente: return posicion
11. Colisiones
Contra las paredes
La serpiente pierde cuando la cabeza sale de los límites de la ventana:
cabeza_x < 0 or cabeza_x >= ANCHO or cabeza_y < 0 or cabeza_y >= ALTO
Contra el propio cuerpo
La cabeza no puede encontrarse dentro del resto de la serpiente:
serpiente[0] in serpiente[1:]
La expresión serpiente[1:] crea una vista lógica de todos los segmentos excepto la cabeza.
12. FPS y velocidad
Clock.tick() controla cuántas veces por segundo se ejecuta aproximadamente el ciclo.
reloj.tick(velocidad)
En nuestro juego la velocidad aumenta cada tres puntos:
velocidad = FPS_INICIAL + puntos // 3
Además usamos min() para evitar que el juego se vuelva excesivamente rápido.
13. Dibujar la pantalla
En cada frame borramos el contenido anterior y volvemos a dibujar comida, serpiente y puntaje.
pantalla.fill((15, 23, 42)) pygame.draw.rect( pantalla, (239, 68, 68), (*comida, TAMANIO, TAMANIO) )
Al finalizar el frame llamamos:
pygame.display.flip()
Eso hace visible todo lo que acabamos de dibujar.
14. Game Over y reinicio
Cuando ocurre una colisión ponemos:
game_over = True
El ciclo general sigue funcionando, pero dejamos de mover la serpiente y mostramos una pantalla de Game Over.
Al pulsar R llamamos nuevamente a crear_partida(), reiniciando posiciones, comida, dirección y puntaje sin cerrar la ventana.
Ideas para seguir mejorando Snake
- agregar sonidos;
- usar sprites en lugar de rectángulos;
- guardar el récord máximo;
- agregar obstáculos;
- crear niveles;
- tener distintos tipos de comida;
- crear comida temporal que otorgue puntos extra;
- permitir atravesar un extremo y aparecer por el opuesto;
- agregar un menú inicial;
- agregar pausa con la tecla P.
Desafío
Agregá un récord máximo. Mientras el programa esté abierto, cada vez que termine una partida compará el puntaje actual contra el récord.
Después intentá guardarlo en un archivo para que el récord siga existiendo incluso después de cerrar el juego.
¿Qué aprendimos?
Este proyecto reúne varios conceptos importantes de Python, pero también introduce ideas propias del desarrollo de videojuegos:
- game loop;
- eventos;
- frames por segundo;
- coordenadas;
- renderizado;
- colisiones;
- estado de juego.
Una vez que entendés esta estructura, podés reutilizarla para crear muchos otros juegos 2D sencillos.
¿Te sirvió este proyecto? ☕
Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:
Seguí aprendiendo Python
Encontrá todos los contenidos de Python en nuestra guía principal:
Aprender Python: guía completa paso a paso →
Descubre más desde Club Programador
Suscríbete y recibe las últimas entradas en tu correo electrónico.