Pong es uno de los proyectos más útiles para aprender programación de juegos. Su mecánica es sencilla: dos paletas controlan una pelota que rebota y cada jugador intenta evitar que salga por su lado. Detrás de esa idea aparecen conceptos importantes como coordenadas, eventos de teclado, bucle de juego, colisiones, velocidad, puntuación y separación del código en funciones.
Para seguir esta guía conviene revisar el tutorial de Turtle en Python, el proyecto de Snake con Turtle, los fundamentos de funciones, la explicación de bucles y la guía de programación orientada a objetos.
Las referencias externas principales son la documentación oficial de turtle y la documentación oficial del módulo time, que utilizaremos para controlar la velocidad del bucle.
Crear la ventana
import turtle
pantalla = turtle.Screen()
pantalla.title("Pong con Python")
pantalla.bgcolor("black")
pantalla.setup(width=900, height=600)
pantalla.tracer(0)
tracer(0) desactiva el refresco automático. Después actualizaremos la ventana una vez por iteración mediante pantalla.update(). Esto evita parpadeos y proporciona un movimiento más uniforme.
Crear una función para las paletas
def crear_paleta(x, y):
paleta = turtle.Turtle()
paleta.speed(0)
paleta.shape("square")
paleta.color("white")
paleta.shapesize(stretch_wid=5, stretch_len=1)
paleta.penup()
paleta.goto(x, y)
return paleta
paleta_izquierda = crear_paleta(-400, 0)
paleta_derecha = crear_paleta(400, 0)
La forma cuadrada se estira verticalmente. penup() impide que el objeto dibuje líneas al moverse.
Crear la pelota
pelota = turtle.Turtle()
pelota.speed(0)
pelota.shape("circle")
pelota.color("white")
pelota.penup()
pelota.goto(0, 0)
pelota.dx = 4
pelota.dy = 4
dx y dy representan el desplazamiento horizontal y vertical por fotograma. Turtle permite añadir atributos a la instancia, aunque en un proyecto mayor sería más claro crear una clase.
Mover las paletas
LIMITE_Y = 240
PASO_PALETA = 30
def subir_izquierda():
nuevo_y = min(
LIMITE_Y,
paleta_izquierda.ycor() + PASO_PALETA,
)
paleta_izquierda.sety(nuevo_y)
def bajar_izquierda():
nuevo_y = max(
-LIMITE_Y,
paleta_izquierda.ycor() - PASO_PALETA,
)
paleta_izquierda.sety(nuevo_y)
Los límites evitan que la paleta salga de la ventana. Crea funciones equivalentes para la derecha:
def subir_derecha():
paleta_derecha.sety(
min(LIMITE_Y, paleta_derecha.ycor() + PASO_PALETA)
)
def bajar_derecha():
paleta_derecha.sety(
max(-LIMITE_Y, paleta_derecha.ycor() - PASO_PALETA)
)
Configurar el teclado
pantalla.listen()
pantalla.onkeypress(subir_izquierda, "w")
pantalla.onkeypress(bajar_izquierda, "s")
pantalla.onkeypress(subir_derecha, "Up")
pantalla.onkeypress(bajar_derecha, "Down")
En onkeypress se pasa la función sin paréntesis. Si escribes subir_izquierda(), la función se ejecutará al registrar el evento y no cuando se pulse la tecla.
Crear el marcador
puntos_izquierda = 0
puntos_derecha = 0
marcador = turtle.Turtle()
marcador.speed(0)
marcador.color("white")
marcador.penup()
marcador.hideturtle()
marcador.goto(0, 250)
def actualizar_marcador():
marcador.clear()
marcador.write(
f"{puntos_izquierda} | {puntos_derecha}",
align="center",
font=("Arial", 24, "normal"),
)
actualizar_marcador()
Debemos declarar como globales los puntos dentro de las funciones que los modifican, o agrupar el estado en una clase.
Crear el bucle principal
import time
while True:
pantalla.update()
time.sleep(1 / 120)
pelota.setx(pelota.xcor() + pelota.dx)
pelota.sety(pelota.ycor() + pelota.dy)
La pausa limita aproximadamente la frecuencia del ciclo. No garantiza una tasa exacta, pero evita que el juego consuma toda la CPU. En motores más avanzados se utiliza el tiempo transcurrido entre fotogramas.
Colisiones con los bordes superior e inferior
if pelota.ycor() > 285:
pelota.sety(285)
pelota.dy *= -1
if pelota.ycor() < -285:
pelota.sety(-285)
pelota.dy *= -1
Reposicionar la pelota dentro del límite evita que siga fuera después de invertir la dirección.
Detectar puntos
def reiniciar_pelota(direccion):
pelota.goto(0, 0)
pelota.dx = abs(pelota.dx) * direccion
pelota.dy = 4
Dentro del bucle:
if pelota.xcor() > 440:
puntos_izquierda += 1
actualizar_marcador()
reiniciar_pelota(-1)
if pelota.xcor() < -440:
puntos_derecha += 1
actualizar_marcador()
reiniciar_pelota(1)
Si este código está en el nivel principal, las variables pueden modificarse directamente. Si está dentro de una función, utiliza una clase o declara el alcance adecuadamente.
Colisión con las paletas
def choca_con_paleta(paleta, lado):
cerca_en_x = abs(pelota.xcor() - paleta.xcor()) < 25
cerca_en_y = abs(pelota.ycor() - paleta.ycor()) < 60
if lado == "izquierda":
va_hacia_paleta = pelota.dx < 0
else:
va_hacia_paleta = pelota.dx > 0
return cerca_en_x and cerca_en_y and va_hacia_paleta
En el bucle:
if choca_con_paleta(paleta_izquierda, "izquierda"):
pelota.setx(-375)
pelota.dx *= -1
if choca_con_paleta(paleta_derecha, "derecha"):
pelota.setx(375)
pelota.dx *= -1
Comprobar la dirección evita múltiples rebotes mientras la pelota sigue superpuesta a la paleta.
Cambiar el ángulo según el impacto
Para que el juego sea menos predecible, ajusta la velocidad vertical según la distancia entre la pelota y el centro de la paleta:
def rebotar_en_paleta(paleta, nuevo_x):
diferencia = pelota.ycor() - paleta.ycor()
pelota.setx(nuevo_x)
pelota.dx *= -1
pelota.dy = diferencia * 0.12
Un impacto cerca de los extremos genera un ángulo mayor. Limita dy para evitar trayectorias casi verticales:
pelota.dy = max(-7, min(7, pelota.dy))
Aumentar gradualmente la dificultad
def acelerar_pelota():
limite = 9
pelota.dx = max(-limite, min(limite, pelota.dx * 1.04))
Llama a esta función después de cada rebote. Mantén un límite para que el juego siga siendo jugable.
Añadir una línea central
linea = turtle.Turtle()
linea.color("gray")
linea.hideturtle()
linea.penup()
linea.goto(0, -300)
linea.setheading(90)
for _ in range(15):
linea.pendown()
linea.forward(20)
linea.penup()
linea.forward(20)
Pausar el juego
pausado = False
def alternar_pausa():
global pausado
pausado = not pausado
pantalla.onkeypress(alternar_pausa, "space")
En el bucle, mueve la pelota únicamente cuando pausado sea falso. En un diseño orientado a objetos, el estado pertenecería a una instancia del juego.
Terminar con una puntuación máxima
PUNTOS_PARA_GANAR = 10
if puntos_izquierda >= PUNTOS_PARA_GANAR:
mostrar_ganador("Jugador izquierdo")
break
if puntos_derecha >= PUNTOS_PARA_GANAR:
mostrar_ganador("Jugador derecho")
break
Después del bucle puedes usar pantalla.mainloop() para mantener visible el resultado, o presentar una opción de reinicio.
Separar el proyecto en clases
Cuando el código crece, crea clases Paleta, Pelota, Marcador y Juego. Así cada objeto administra su estado y comportamiento. Por ejemplo:
class Pelota(turtle.Turtle):
def __init__(self):
super().__init__()
self.shape("circle")
self.color("white")
self.penup()
self.dx = 4
self.dy = 4
def mover(self):
self.goto(self.xcor() + self.dx, self.ycor() + self.dy)
Probar la lógica sin abrir la ventana
Las colisiones pueden extraerse a funciones puras:
def hay_colision(px, py, paleta_x, paleta_y):
return abs(px - paleta_x) < 25 and abs(py - paleta_y) < 60
Después crea tests con coordenadas dentro y fuera de los límites. Esto permite verificar la lógica sin depender de eventos gráficos.
Errores frecuentes
Los fallos habituales son olvidar pantalla.update(), registrar callbacks con paréntesis, no limitar las paletas, invertir la dirección varias veces en una misma colisión y usar una pausa demasiado larga. También es común mezclar todo el estado en variables globales, lo que dificulta reiniciar o probar el juego.
Mejoras posibles
Puedes añadir sonidos, menú inicial, selección de dificultad, inteligencia artificial para una paleta, efectos visuales, modo a un jugador y almacenamiento de récords. Mantén la física sencilla y limita la velocidad. Antes de añadir funciones, separa el código en componentes claros.
Conclusión
Crear Pong con Turtle enseña eventos, coordenadas, colisiones, bucles y estado de juego. Empieza con una ventana, dos paletas y una pelota, añade límites y marcador y después mejora los rebotes. Una vez que funcione, reorganiza el proyecto en clases y extrae la lógica para poder probarla sin la interfaz.






