es uno de los primeros y más emblemáticos juegos de la historia del entretenimiento digital. En su forma clásica, el juego simula un partido de tenis de mesa con dos palas que se mueven verticalmente por la pantalla para golpear una pelota que rebota. Cada uno de los jugadores controla una paleta y tiene que rebotar la pelota al otro jugador, o de lo contrario le dan un punto al jugador contrario.
La historia del juego es algo interesante. El juego Pong fue creado y escrito por Allan Alcom como prueba cuando fue reclutado por Atari. Luego, este juego se convirtió en un gran éxito, vendiendo un montón de máquinas en los pubs y bares de todo el mundo, y sucedió que las máquinas se ahogaban con la gran cantidad de dinero que la gente invertía, por lo que eventualmente los dueños de los bares y pubs tuvieron que llamar a Atari para que arreglaran sus máquinas.
En este tutorial, utilizaremos el enfoque de programación orientada a objetos de Python para codificar el juego Pong. Este es un tutorial de programación de Python de nivel intermedio que requiere tener un conocimiento preliminar de los fundamentos de Python: lista,…
Entendiendo el proyecto
Hay varias formas de codificar este juego. Podemos usar el método sencillo y hacer cada tarea paso a paso con las repeticiones necesarias, o podemos usar el enfoque de Programación Orientada a Objetos de Python para evitar la repetición y tener un código limpio y organizado. ¡Optaremos por la segunda opción ya que esto haría que el programa del juego sea más sistemático y menos complicado!
Usaremos el módulo Turtle de Python para el desarrollo visual del juego. El módulo tortuga es una funcionalidad incorporada que permite visualizar el código de una manera sencilla. Básicamente consiste en una tortuga que va dibujando formas y líneas a medida que se mueve por la pantalla según las instrucciones del codificador. Es una herramienta poderosa para crear juegos de nivel principiante y obtener comentarios instantáneos a través de una pantalla visual.
Las siguientes son las tareas clave que abordaremos de manera ordenada:
Creación de la pantalla de juego: esta es la pantalla en la que se mostrará el juego Pong. Creación de la clase Paddle & Paddle: este es el código que creará una paleta en la pantalla y configurará sus movimientos, que convertiremos en una clase como plano para crear 2 paletas, una en el lado izquierdo y otra en el lado derecho. Creación de la clase de pelota y los objetos: continuando con el enfoque OOP, crearemos una clase de pelota genérica y luego crearemos la pelota que se moverá por la pantalla, también definiremos sus métodos relevantes. Detección de colisión de la pelota con la pared superior/inferior: esta es la pieza de código que detectará la colisión con las paredes superior e inferior, y si ocurre una colisión, hará que la pelota rebote a través del eje y. Detección de colisión con la paleta: esta es la pieza de código que detectará si la pelota choca con la paleta. En caso afirmativo, hará que la pelota rebote; de lo contrario, si las palas no tocan la pelota, le darán un puntaje al jugador contrario y reiniciarán el juego con la pelota en el centro. Creación de la clase y el objeto del marcador: este es el fragmento de código que incluye la creación de la clase Marcador en un archivo Python separado y la creación de su objeto en el archivo principal del juego.
Creando la pantalla del juego
La primera tarea es crear la pantalla del juego. Esta pantalla tendrá forma rectangular, como en el juego real. Primero importaremos el módulo de tortuga en nuestro código y usaremos su clase Screen para crear el objeto de pantalla y lo personalizaremos para que tenga un ancho de 800 px y una altura de 600 px usando el método setup() de la clase Screen. Estableceremos el color de fondo en negro usando el método bgcolor() y nombraremos la pantalla como "Juego Pong" usando el método title(). A continuación se muestra el código donde hemos creado el objeto de pantalla:
de tortuga importar Tortuga, Pantalla #Configurando el juego Pantalla pantalla = Pantalla() pantalla.setup(ancho=800, altura=600) pantalla.bgcolor("negro") pantalla.título("Juego Pong") pantalla.exitonclick()
Observe que hemos escrito la última línea donde hemos utilizado el método exitonclick() de la pantalla para garantizar que la pantalla permanecerá allí hasta que hagamos clic en ella.
Si encuentra alguna confusión en los métodos anteriores, no dude en consultar la documentación oficial del Módulo Turtle desde aquí.
El siguiente es el resultado mientras ejecutamos el programa:
Creando la clase Paddle & Paddle
La siguiente tarea es crear una paleta, que es un objeto de forma rectangular a ambos lados de la pantalla de juego. Crearemos esta paleta usando la función shape() del módulo tortuga, la personalizaremos para que sea de color blanco usando el método color(), y usaremos el método shapesize() para personalizarla para que tenga un ancho de 20px y una altura de 100px. Observe que hemos pasado 5 y 1 como argumentos del método shapesize(). Esto se debe a que el tamaño de forma() no está en píxeles, sino en referencia a una base de 20 píxeles. Entonces, para obtener una longitud de 100 px, pasaremos 5 (ya que 20 px x 5 = 100 px). Además lo posicionaremos de manera que al inicio del juego quede en el medio del lado derecho, es decir, una coordenada y de 0 y una coordenada x de 350 (recordad que nuestra pantalla tiene 800px de ancho). Usaremos el método penup() para eliminar el rastro de la tortuga y hacer que se mueva a la ubicación deseada usando el método goto().
#Creando la paleta Paddle = Turtle() paddle.shape("square") paddle.color("white") paddle.shapesize(5,1) paddle.penup() paddle.goto(350,0)
El siguiente es el resultado del código anterior. Podemos ver una paleta creada en la pantalla de juego en el lado derecho, sin ningún rastro de tortuga.
Ejecutar el código anterior creará la paleta. Sin embargo, podemos ver que primero se crea la paleta y luego se dirige a su ubicación. Para desactivar la animación, agregaremos el método tracer() de la clase de pantalla en nuestro código. Esto también requerirá que actualicemos la pantalla manualmente:
#Conservar el código original screen.tracer(0) screen.update() screen.exitonclick()
Llamar al método tracer() y pasarle un valor de 0 desactivará la animación.
Una vez hemos creado la pala y actualizado la pantalla desactivando las animaciones, lo siguiente es configurar los movimientos de la pala. Para hacer esto, usaremos oyentes de pantalla. El método de clase listening() de la pantalla nos permite escuchar los eventos del teclado, y el método onkey() nos permite llamar a una función definida cada vez que se presiona una tecla en particular. Así definiremos las funciones go_up y go_down que harán que la paleta se mueva hacia arriba y hacia abajo a lo largo del eje y.
def go_up(): new_y = paddle.ycor() + 40 paddle.goto(paddle.xcor(), new_y) def go_down(): new_y = paddle.ycor() – 40 paddle.goto(paddle.xcor(),new_y)
Como puede verse, hemos definido la función de movimiento hacia arriba y hacia abajo de la paleta haciéndola moverse 40px verticalmente desde su posición original. A continuación, usaremos la capacidad de escucha de pantalla para permitir que se invoquen estas funciones al presionar las teclas del teclado.
screen.listen() screen.onkey(paddle.go_up, "Arriba") screen.onkey(paddle.go_down, "Abajo")
Ahora que hemos creado la paleta y configurado el mecanismo de su movimiento, cambiemos nuestro código al enfoque de programación orientada a objetos. Esto se debe a que necesitaremos 2 paletas para el juego, y tener un plano genérico que cree paletas al instante facilitará nuestra tarea. Refactorizaremos nuestro código para crear otra paleta fácilmente. Moveremos todo el código relacionado con la paleta a otro archivo y crearemos la clase padlle en él.
Dado que las paletas que estamos creando son en esencia objetos de tortuga, haremos que esta clase de paleta herede de la clase Tortuga. Así que crearemos un nuevo archivo Python en nuestro IDE de PyCharm y nuevamente importaremos la clase Turtle del módulo Turtle en este archivo Python separado. A continuación, usaremos la sintaxis de creación de clases y def __inti__() para definir la clase Paddle. Como las paletas izquierda y derecha tendrán diferentes posiciones en la pantalla del juego, agregaremos las coordenadas xey como atributos de la clase.
Ahora usaremos el concepto de herencia en programación orientada a objetos y haremos de la clase Turtle la superclase, y la clase paddle heredará sus atributos y métodos. A continuación, simplemente reemplazaremos la palabra "paleta" en nuestro código anterior donde creamos la paleta con la palabra clave "auto".
de tortuga import Turtle, clase de pantalla Paddle(Turtle): def __init__(self,x,y): # Creando los objetos de paleta super().__init__() self.shape("square") self.color("white") self.shapesize(5, 1) self.penup() self.x = x self.y = y self.goto(x,y) # Configurar el movimiento de la paleta def go_up(self): new_y = self.ycor() + 40 self.goto(self.xcor(), new_y) def go_down(self): new_y = self.ycor() – 40 self.goto(self.xcor(),new_y)
Como se puede ver arriba, también hemos definido los dos métodos de la clase Paddle. Uno es el movimiento ascendente y el segundo es el movimiento descendente que ya hemos definido anteriormente. Una vez definida la clase Paddle, crearemos los objetos de remo y configuraremos los movimientos de subida y bajada de ambas palas:
from paddle import Paddle # Creando objetos de paleta left_paddle = Paddle(-350, 0) right_paddle = Paddle(350, 0) # Configurando el movimiento de las paletas screen.listen() screen.onkey(right_paddle.go_up, "Arriba") screen.onkey(right_paddle.go_down, "Abajo") screen.onkey(left_paddle.go_up, "w") pantalla.onkey(left_paddle.go_down, "s")
Ejecutando el juego
Para ejecutar el juego y actualizarlo usando el método update() de la pantalla, definiremos un bucle while que continuará ejecutándose hasta que se detenga externamente, o cuando la condición del bucle cambie a False.
#El juego está activado: game_is_on = Verdadero mientras que game_is_on: screen.update()
Ahora, cuando ejecutes el archivo principal, verás la pantalla del juego y las paletas creadas, y la capacidad de las paletas para moverse.
Crear la clase de pelota y los objetos
Ahora, continuando con nuestro enfoque de programación orientada a objetos para codificar este juego, crearemos la clase Ball como modelo genérico y crearemos el objeto ball a partir de ella en nuestro archivo principal de Python. Crearemos la pelota como un objeto tortuga, haciendo que la clase Ball herede de la superclase Turtle. Usaremos los métodos color() y shape() de la clase tortuga para inicializar una bola de color blanco en forma circular. Como antes, usaremos el método penup() de tortuga para ocultar el rastro de la tortuga.
de tortuga importar clase Tortuga Bola(Tortuga): def __init__(self): super().__init__() self.color("blanco") self.shape("círculo") self.penup()
Ahora que los atributos de nuestra pelota están definidos, también crearemos los métodos de movimiento de la pelota tan pronto como comience el juego. El juego comenzará con la pelota en el centro de la pantalla del juego y, cuando la pantalla se actualice, se moverá primero en la dirección correcta. En nuestro bucle while principal llamaremos a este método para que la pelota continúe moviéndose mientras el juego esté activo, es decir, sus coordenadas xey cambiarán en cada actualización de la pantalla del juego.
La forma de hacer que la pelota se mueva es cambiando sus coordenadas x e y en un número determinado, digamos 10 por el momento. Definiremos el método move() de la pelota y codificaremos el escenario anterior:
clase Ball(Tortuga): #Conservar el código anterior def move(self): new_x = self.xcor() + 10 new_y = self.ycor() + 10 self.goto(new_x, new_y)
Agregaremos este método del objeto bola que se llamará dentro del ciclo while del juego:
#El juego está activado: game_is_on = True mientras que game_is_on: screen.update() ball.move()
Al ejecutar el código, vemos que la bola desaparece rápidamente y lo que nos quedan son solo las 2 paletas.
Podemos reanudar la animación comentando las líneas screen.tracer() y volviendo a ejecutar el código. Ahora veremos cómo se crean y mueven las 2 paletas y la pelota.
Otra forma de visualizar esto es usar el módulo de tiempo y generar un retraso en el ciclo principal del juego. Esto se puede hacer de la siguiente manera (sin comentar la función tracer()):
tiempo de importación #Conservar el código original #El juego está activado: game_is_on = True mientras que game_is_on: time.sleep(0.1) screen.update() ball.move()
Ahora puedes ver que la pelota se mueve a un ritmo más lento y podemos atraparla con una paleta.
Detección de colisión de bola con pared superior/inferior
Ahora que nuestra pelota está creada y en funcionamiento, necesitamos diseñar un mecanismo para hacer que la pelota rebote cuando golpee las paredes superior e inferior, en cuanto a las paredes izquierda y derecha, la pelota debe ser atrapada por las paletas izquierda y derecha. Si no se atrapa la pelota, significaría que el otro jugador anota un punto.
Entonces, considerando que nuestra bola se mueve desde el centro de la pantalla hasta la esquina superior derecha y llega a la esquina, necesita rebotar ahora. En palabras sencillas, rebotar sería simplemente un cambio de dirección en el eje y, ya que la pelota seguiría avanzando en el eje x. Ahora definiremos un nuevo método de la clase Ball llamado rebote() y lo llamaremos en el bucle principal del juego cuando la pelota alcance el límite:
de importación de tortuga Clase tortuga Bola(Tortuga): def __init__(self): super().__init__() self.color("blanco") self.shape("círculo") self.penup() self.x_move = 10 self.y_move = 10 def move(self): new_x = self.xcor() + self.x_move new_y = self.ycor() + self.y_move self.goto(new_x, new_y) def rebote(self): self.y_move *= -1
Observe que en lo anterior, hemos definido 2 nuevos atributos de la clase Ball, x_move y y_move, y los hemos igualado a 10. Luego, en el método move(), hemos reemplazado la cifra de 10 con estos atributos. Como puede verse, esto resulta útil para nuestro método rebote(). Ahora, cada vez que la pelota rebote, se moverá en la dirección opuesta a su posición y anterior. Esto simplemente significa que si la pelota sube y choca con la pared, el movimiento y cambiará de +10 a -10 y la pelota se moverá hacia abajo, ya que el número negativo significaría que la pelota se mueve hacia abajo. En consecuencia, una colisión con la pared inferior cambiaría este movimiento y de -10 a +10, y la pelota se moverá hacia arriba.
Ahora, agreguemos esta condición en el bucle while principal:
while game_is_on: #Conservar el código original #Detectar colisión con las paredes superior e inferior si ball.ycor() > 275 o ball.ycor() < -275: ball.bounce_y()
En el código anterior, agregamos la condición de la colisión con las paredes a detectar y luego el método de rebote() que se llamará. Puedes usar cualquier valor para los límites, pero tras varios intentos, ¡el valor de 275 es suficiente!
Detección de colisión con paleta
Ahora que sabemos cómo hacer que la pelota rebote desde las paredes superior e inferior, el siguiente paso es detectar una colisión con la paleta y hacer que la pelota rebote desde la paleta. Emplearemos un método similar al anterior, excepto que ahora estamos hablando del eje x.
La forma normal de detectar una colisión entre la pelota y la pared es utilizar el método de la distancia. Si la distancia entre los dos es menor que cierta cantidad, podemos concluir que los 2 se han tocado/chocado. Sin embargo, sepa que la función distancia() funciona calculando la distancia entre los centros de los dos objetos tortuga. En nuestro caso, una es una pelota de 20x20px y la otra es una paleta rectangular de 20×200. La distancia entre ellos variaría a lo largo del remo. Si la pelota golpea la paleta en su borde, el método distancia() no lograría concluir que ambos han hecho contacto.
Podemos agregar otra condición que verificaría si la pelota ha pasado de cierto punto en el eje x, hacia la derecha (en el caso de la paleta derecha), y está dentro de una distancia de 50px de la paleta, entonces la pelota debe haber hecho contacto. Agregaremos esta condición al bucle while principal. Una vez detectada la colisión, haremos que la pelota rebote, pero esta vez en la dirección x. Redefinamos nuestras funciones de rebote para que tengamos ambas funciones de rebote, una para el eje x cuando choca con el addle y la otra en el eje y cuando choca con la pared:
def rebote_y(self): self.y_move *= -1 def rebote_x(self): self.x_move *= -1 while game_is_on: … # Detectar colisión de la pelota con la paleta derecha if ball.distance(right_paddle) < 50 and ball.xcor() > 320: ball.bounce_x() # Detectar colisión de la pelota con la paleta izquierda elif ball.distance(left_paddle) < 50 y bola.xcor() < -320: bola.bounce_x()
Tenga en cuenta que hemos agregado un valor de 320 después de algunos golpes, pruebas y visualizaciones de la pelota chocando con la paleta.
Si una de las palas no alcanza la pelota, el otro jugador obtiene un punto y el juego se reinicia con la pelota en el centro. Para comprobar si la pala no alcanza la pelota, podemos visualizarlo considerando que la pelota va más allá de un cierto punto en el eje horizontal. Sabemos que el ancho de la pantalla es 800 y la paleta está en 350 a lo largo del eje x, por lo que la paleta en realidad va de 340 a 360 ya que tiene un ancho de 20 px, por lo que si la pelota va más allá de los 360 ejes x, significa que las paletas no alcanzaron la pelota. Esto significaría que restableceremos la pelota a la posición inicial en el valor central (0,0). Definiremos un método reset_position() de la bola que se llamará cuando se cumpla la condición anterior. Además, también agregaremos una función que invertirá la dirección de la pelota, de modo que en lugar de ir hacia la derecha, irá hacia la izquierda.
Clase Bola(Tortuga): … def reset_position(self): self.goto(0, 0) self.bounce_x()
El método rebote_x() hará que la pelota invierta la dirección como lo hacía cuando rebotaba en una paleta. Poner estas condiciones en el bucle while principal del juego:
while game_is_on: … # Detectar la paleta derecha sin la pelota si ball.xcor() > 380: ball.reset_position() # Detectar la paleta izquierda sin la pelota si ball.xcor() < -380: ball.reset_position()
Ejecutar el código anterior nos mostrará qué sucede cuando una paleta no alcanza la pelota; la pelota invertiría su dirección y pasaría a la otra paleta. Ahora sólo queda crear un marcador para almacenar y mostrar la puntuación de cada jugador.
Creando el marcador
Para mostrar y actualizar la puntuación de cada jugador, definiremos una clase de marcador en un nuevo archivo de Python. Crearemos la clase Scoreboard heredando de la clase tortuga y definiremos los atributos que ayudarán al objeto tortuga a escribir. Primero inicializaremos los dos atributos, l_score y r_score y los estableceremos en 0 al comienzo del juego. Definiremos dos métodos, l_point y r_point, que se llamarán cada vez que un jugador pierda el balón y aumentarán los puntos del otro usuario. También definiremos un método llamado update_scoreboard() y lo llamaremos cuando un jugador obtenga un punto adicional. Este método, cuando se llama, simplemente actualizará el marcador.
A continuación se muestra la creación de la clase de marcador:
de importación de tortuga Clase de tortuga Marcador(Tortuga): def __init__(self): super().__init__() self.color("blanco") self.penup() self.hideturtle() self.l_score = 0 self.r_score = 0 self.update_scoreboard() def update_scoreboard(self): self.clear() self.goto(-100, 200) self.write(self.l_score, align="center", font=("Arial", 40, "normal")) self.goto(100, 200) self.write(self.r_score, align="center", font=("Arial", 40, "normal")) def l_point(self): self.l_score += 1 self.update_scoreboard() def r_point(self): self.r_score += 1 self.update_scoreboard()
El método update_scoreboard() crea una tortuga que escribe la puntuación de ambos jugadores en la pantalla principal. Observe que aquí hemos utilizado la función write() del módulo Turtle.
A continuación importaremos y crearemos un objeto de marcador en el archivo principal, y usaremos este objeto para acceder a sus métodos, cumpliendo las dos condiciones: cada vez que la pala de un jugador falla la pelota, el otro jugador obtendría un punto.
desde el marcador importar el marcador #Iniciando el objeto del marcador marcador = Marcador() mientras que game_is_on: … # Detectar la paleta derecha sin la pelota si ball.xcor() > 380: ball.reset_position() scoreboard.l_point() # Detectar la paleta izquierda sin la pelota si ball.xcor() < -380: ball.reset_position() scoreboard.r_point()
Aquí es donde el diseño y la codificación del juego llegan a su fin. Al ejecutar el archivo principal de Python se generará la pantalla del juego y sus componentes, con la pelota moviéndose cuando comienza el juego. ¡Ahora sólo necesitas encontrar un jugador con quien jugar este juego!
También puedes cambiar la velocidad del juego mediante algunos cambios en el código (¡eso debes descubrirlo tú!)
Conclusión
En este artículo, hemos desarrollado el clásico juego Pong con la ayuda del módulo Python Turtle. Hemos utilizado el concepto de Programación Orientada a Objetos para crear clases, inicializar atributos y métodos, y a partir de estas clases crear objetos en el archivo principal del juego. Este es un proyecto Python de nivel intermedio, y si se topó con alguna parte del código, asegúrese de consultar la documentación oficial de Python o revisar sus conceptos básicos, particularmente OOP en este caso.