Crear juego de ruptura usando Python

En este artículo construiremos un clon de un juego famoso, ‘Breakout’. Construiremos este juego utilizando una de las bibliotecas integradas de Python, Turtle.

Requisitos

  • Un IDE como PyCharm.
  • Conceptos básicos de Python
  • Conocimientos básicos de Python-Tortuga.
  • Voluntad de aprender.

Estructura del proyecto

Queremos mantener la programación simple. La mejor manera de hacerlo es dividir el problema en tareas más simples y lograrlas una por una. Esto lo logramos usando Programación Orientada a Objetos. Esto mantendrá nuestros componentes independientes entre sí, lo que facilita el trabajo con ellos.

 

Implementación paso a paso

Paso 1: crea una ventana con un color de fondo negro:

Aquí simplemente importamos Turtle y creamos un objeto de su clase Pantalla para controlar lo que sucede con la ventana del programa. Le damos tamaño, color y título para obtener una pantalla negra en blanco. 

principal.py

Python3

import turtle as tr
 
screen = tr.Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Breakout')
 
tr.mainloop()

Paso 2: Creación de la paleta:

Lo primero que debe hacer es importar la biblioteca de turtle. Decide la distancia que la paleta se moverá en cada movimiento. Creamos la clase para la paleta, heredamos la clase Turtle y luego le damos a nuestra paleta las propiedades requeridas, que incluyen su forma, tamaño y color. No queremos que dibuje, por lo que usamos el método penup. Luego lo hacemos ir a la parte más baja de la pantalla y en el medio. Nuestra ventana tiene 600 de ancho. Dado que cada pantalla de Turtle es un plano XY, con origen en el centro, el punto más bajo es -300. De forma predeterminada, las dimensiones de un objeto de Turtle son 20 X 20. Dado que solo estiramos su longitud, el ancho no se ve afectado. Colocamos nuestra Turtle 20 píxeles por encima del punto más bajo para que quede visible, de lo contrario, la mitad quedará oculta. También necesitamos que se mueva, por lo que definimos dos métodos para que se mueva hacia la izquierda y hacia la derecha.

padel.py

Python3

from turtle import Turtle
 
MOVE_DIST = 70
 
class Paddle(Turtle):
    def __init__(self):
        super().__init__()
        self.color('steel blue')
        self.shape('square')
        self.penup()
        self.shapesize(stretch_wid=1, stretch_len=10)
        self.goto(x=0, y=-280)
 
    def move_left(self):
        self.backward(MOVE_DIST)
 
    def move_right(self):
        self.forward(MOVE_DIST)

 

Ahora, importe la clase para la paleta y cree un objeto a partir de ella. Usamos el método listen() del objeto de pantalla que hace que nuestro programa reaccione a las pulsaciones de teclas. Luego, usando el método onkey(), definimos qué teclas necesita escuchar, que son las teclas de flecha izquierda y derecha. Para lo cual movemos nuestra Turtle hacia atrás y hacia adelante respectivamente. Aquí también estamos usando dos métodos de objeto de pantalla, tracer() y update(). Controlamos las animaciones dentro de la ventana de nuestro programa. Al pasar cero al rastreador lo apagamos, al usar actualizar lo encendemos. Debido a estos métodos, vemos directamente la paleta en la parte inferior de la pantalla. Si no los usamos, vemos una paleta creada en el centro de la ventana, que luego se mueve hacia la parte inferior.

principal.py

Python3

import turtle as tr
from paddle import Paddle
 
screen = tr.Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Breakout')
screen.tracer(0)
 
paddle = Paddle()
 
screen.listen()
screen.onkey(key='Left', fun=paddle.move_left)
screen.onkey(key='Right', fun=paddle.move_right)
 
screen.update()
 
tr.mainloop()

Paso 3: Creando nuestra Bola:

Similar a las paletas, heredamos la clase Turtle y creamos una pelota que es solo una Turtle con forma circular. Tampoco queremos que dibuje, así que nuevamente usamos penup. El primer método que definimos es hacer que se mueva. Dado que nuestra bola se moverá en el plano XY, hacemos aumentar sus coordenadas x e y en la misma cantidad al mismo tiempo. Este método de movimiento se llamará dentro de nuestro main.py dentro de un bucle por el cual vemos una bola que siempre se está moviendo. Luego creamos un método para hacer que cambie su dirección cuando choca con una pared, un ladrillo o una paleta, que rebota. A veces, solo es necesario cambiar las direcciones laterales, a veces verticales y, a veces, ambas. Por lo tanto, tenemos dos sentencias if. Para cambiar la dirección, simplemente haga que la distancia de movimiento sea negativa, para la coordenada requerida. Por fin, tenemos el método de reinicio, lo que hace que nuestra bola vuelva a donde empezó. Lo queremos cuando la bola choca con la pared inferior, lo que significa que el usuario no la golpeó y debería perder una vida.

bola.py

Python3

from turtle import Turtle
 
MOVE_DIST = 10
 
class Ball(Turtle):
    def __init__(self):
        super().__init__()
        self.shape('circle')
        self.color('white')
        self.penup()
        self.x_move_dist = MOVE_DIST
        self.y_move_dist = MOVE_DIST
        self.reset()
 
    def move(self):
        # move only 10 steps ahead, both vertically
        # and horizontally.
        new_y = self.ycor() + self.y_move_dist
        new_x = self.xcor() + self.x_move_dist
        self.goto(x=new_x, y=new_y)
 
    def bounce(self, x_bounce, y_bounce):
        if x_bounce:
            # reverse the horizontal direction
            self.x_move_dist *= -1
 
        if y_bounce:
            # reverse the vertical direction
            self.y_move_dist *= -1
 
    def reset(self):
        # ball should got to an initial position,
        # always moving up.
        self.goto(x=0, y=-240)
        self.y_move_dist = 10

 

Creamos un ciclo while infinito dentro del cual lo primero que hacemos es actualizar nuestra pantalla, retrasar nuestro programa y hacer que la bola se mueva. La velocidad de la computadora es mucho más rápida de lo que los humanos podemos comprender, por lo que retrasamos un poco nuestro programa para cada iteración. En cada iteración, también estamos haciendo que nuestra bola se mueva una vez. También comprobamos cuáles son las coordenadas actuales de nuestra bola. Ya conocemos las dimensiones de la ventana, y cuáles serán los puntos x e y de las aristas dado que el origen está en el centro de la ventana. Así comprobamos si las coordenadas de nuestra bola han superado o no los bordes, en función de lo cual la hacemos rebotar. De igual forma, tendremos métodos para detectar colisiones con paletas y colisiones con ladrillos.

principal.py

Python3

import turtle as tr
from paddle import Paddle
from ball import Ball
import time
 
screen = tr.Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Breakout')
screen.tracer(0)
 
paddle = Paddle()
ball = Ball()
 
playing_game = True
 
 
screen.listen()
screen.onkey(key='Left', fun=paddle.move_left)
screen.onkey(key='Right', fun=paddle.move_right)
 
def check_collision_with_walls():
 
    global ball
 
    # detect collision with left and right walls:
    if ball.xcor() < -580 or ball.xcor() > 570:
        ball.bounce(x_bounce=True, y_bounce=False)
        return
 
    # detect collision with upper wall
    if ball.ycor() > 270:
        ball.bounce(x_bounce=False, y_bounce=True)
        return
 
    # detect collision with bottom wall
    # In this case, user failed to hit the ball
    # thus he loses. The game resets.
    if ball.ycor() < -280:
        ball.reset()
        return
 
def check_collision_with_paddle():
    pass
 
def check_collision_with_bricks():
    pass
 
while playing_game:
    screen.update()
    time.sleep(0.01)
    ball.move()
 
    check_collision_with_walls()
 
 
tr.mainloop()

Paso 4: Haz que la pala pueda rebotar en la pelota.

La longitud de la paleta es 200, y el método de la distancia de la Turtle mide la distancia entre los centros de dos turtle. Así el pádel tiene 100 a izquierda y derecha. De manera similar, la pelota (20 X 20) tiene 10 alrededor de su punto central. Esta pelota toca la paleta cuando la distancia entre sus centros es 100+10 <= 110. Al mismo tiempo, también verificamos si la coordenada y de la pelota ha alcanzado la coordenada y del extremo superior de la paleta. Cuando se cumplen estas dos condiciones, la paleta golpea la pelota y debe rebotar. En el juego original, podemos notar que la pelota también cambia su dirección horizontal cuando golpea el borde extremo de la paleta. Verificamos si la paleta está a la izquierda o derecha de la ventana, dentro de esta, verificamos si la pelota está a la izquierda o derecha de la paleta,

principal.py

Python3

import turtle as tr
from paddle import Paddle
from ball import Ball
import time
 
screen = tr.Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Breakout')
screen.tracer(0)
 
paddle = Paddle()
ball = Ball()
 
playing_game = True
 
 
screen.listen()
screen.onkey(key='Left', fun=paddle.move_left)
screen.onkey(key='Right', fun=paddle.move_right)
 
def check_collision_with_walls():
 
    global ball
 
    # detect collision with left and right walls:
    if ball.xcor() < -580 or ball.xcor() > 570:
        ball.bounce(x_bounce=True, y_bounce=False)
        return
 
    # detect collision with upper wall
    if ball.ycor() > 270:
        ball.bounce(x_bounce=False, y_bounce=True)
        return
 
    # detect collision with bottom wall
    # In this case, user failed to hit the
    # ball thus he loses. The game resets.
    if ball.ycor() < -280:
        ball.reset()
        return
 
def check_collision_with_paddle():
 
    global ball, paddle
    # record x-axis coordinates of ball and paddle
    paddle_x = paddle.xcor()
    ball_x = ball.xcor()
 
    # check if ball's distance(from its middle)
    # from paddle(from its middle) is less than
    # width of paddle and ball is below a certain
    # coordinate to detect their collision
    if ball.distance(paddle) < 110 and ball.ycor() < -250:
 
        # If Paddle is on Right of Screen
        if paddle_x > 0:
            if ball_x > paddle_x:
                # If ball hits paddles left side it
                # should go back to left
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
        # If Paddle is left of Screen
        elif paddle_x < 0:
            if ball_x < paddle_x:
                # If ball hits paddles left side it
                # should go back to left
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
        # Else Paddle is in the Middle horizontally
        else:
            if ball_x > paddle_x:
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            elif ball_x < paddle_x:
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
def check_collision_with_bricks():
    pass
 
while playing_game:
    screen.update()
    time.sleep(0.01)
    ball.move()
 
    check_collision_with_walls()
 
    check_collision_with_paddle()
 
tr.mainloop()

Paso 5: Configuración de los ladrillos

Creamos dos clases. Uno para un ladrillo individual y otro para una colección de esos ladrillos que aparecen en la ventana. Un ladrillo individual es una Turtle simple, que se estira hacia los lados para que sea rectangular. También tiene una propiedad de ‘cantidad’, que es su peso, es decir, el número de veces que necesita ser golpeado por la pelota antes de que desaparezca. Este número se le da aleatoriamente a un ladrillo, no queremos que nuestro juego sea demasiado difícil, por lo que hacemos 1 como el peso más asignado. Para verificar cuándo la pelota choca con el ladrillo, necesitamos las coordenadas de los cuatro bordes del ladrillo. coordenadas x para izquierda y derecha y coordenadas y para arriba y abajo. Una lista de ladrillos es simplemente una array de ladrillos. Sabemos que la coordenada x inicial y la coordenada final del ladrillo siguen siendo las mismas, para cada capa o carril de ladrillo, lo único que cambia es el eje y. Por lo tanto, se llama a un método de creación de un solo carril dentro de otro método que pasa sus coordenadas y para los carriles.

ladrillos.py

Python3

from turtle import Turtle
import random
 
COLOR_LIST = ['light blue', 'royal blue',
              'light steel blue', 'steel blue',
              'light cyan', 'light sky blue',
              'violet', 'salmon', 'tomato',
              'sandy brown', 'purple', 'deep pink',
              'medium sea green', 'khaki']
 
weights = [1, 2, 1, 1, 3, 2, 1, 4, 1, 3,
           1, 1, 1, 4, 1, 3, 2, 2, 1, 2,
           1, 2, 1, 2, 1]
 
 
class Brick(Turtle):
    def __init__(self, x_cor, y_cor):
        super().__init__()
        self.penup()
        self.shape('square')
        self.shapesize(stretch_wid=1.5, stretch_len=3)
        self.color(random.choice(COLOR_LIST))
        self.goto(x=x_cor, y=y_cor)
 
        self.quantity = random.choice(weights)
 
        # Defining borders of the brick
        self.left_wall = self.xcor() - 30
        self.right_wall = self.xcor() + 30
        self.upper_wall = self.ycor() + 15
        self.bottom_wall = self.ycor() - 15
 
 
class Bricks:
    def __init__(self):
        self.y_start = 0
        self.y_end = 240
        self.bricks = []
        self.create_all_lanes()
 
    def create_lane(self, y_cor):
        for i in range(-570, 570, 63):
            brick = Brick(i, y_cor)
            self.bricks.append(brick)
 
    def create_all_lanes(self):
        for i in range(self.y_start, self.y_end, 32):
            self.create_lane(i)

 

Importar los ladrillos en nuestro main.py los hace visibles para el usuario. Ahora es el momento de definir la función que verifica si la pelota choca con un ladrillo o no. Verificamos la condición si la bola y el ladrillo están dentro del rango de contacto usando el método de distancia. Las dimensiones de la bola y el ladrillo se tienen en cuenta al verificar esta condición, ya que la distancia de contacto es la suma de la mitad de la dimensión de cada Turtle. A veces es necesario hacer un golpe y un juicio para llegar a un número apropiado. Una vez que hemos verificado si la bola y el ladrillo se han tocado o no, verificamos si sus coordenadas están a la izquierda, derecha, arriba o abajo del ladrillo, y luego lo hacemos rebotar en consecuencia. Si choca con un ladrillo de lado, la dirección horizontal de la bola cambia, otra dirección vertical. Cuando se confirma una colisión, reducimos la cantidad de ese ladrillo,

principal.py

Python3

import turtle as tr
from paddle import Paddle
from ball import Ball
from bricks import Bricks()
import time
 
screen = tr.Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Breakout')
screen.tracer(0)
 
paddle = Paddle()
bricks = Bricks()
ball = Ball()
 
playing_game = True
 
 
screen.listen()
screen.onkey(key='Left', fun=paddle.move_left)
screen.onkey(key='Right', fun=paddle.move_right)
 
def check_collision_with_walls():
 
    global ball, score, playing_game, ui
 
    # detect collision with left and right walls:
    if ball.xcor() < -580 or ball.xcor() > 570:
        ball.bounce(x_bounce=True, y_bounce=False)
        return
 
    # detect collision with upper wall
    if ball.ycor() > 270:
        ball.bounce(x_bounce=False, y_bounce=True)
        return
 
    # detect collision with bottom wall
    # In this case, user failed to hit
    # the ball thus he loses. The game resets.
    if ball.ycor() < -280:
        ball.reset()
        return
 
def check_collision_with_paddle():
 
    global ball, paddle
    # record x-axis coordinates of ball and paddle
    paddle_x = paddle.xcor()
    ball_x = ball.xcor()
 
    # check if ball's distance(from its middle)
    # from paddle(from its middle) is less than
    # width of paddle and ball is below a certain
    # coordinate to detect their collision
    if ball.distance(paddle) < 110 and ball.ycor() < -250:
 
        # If Paddle is on Right of Screen
        if paddle_x > 0:
            if ball_x > paddle_x:
                # If ball hits paddles left side it
                # should go back to left
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
        # If Paddle is left of Screen
        elif paddle_x < 0:
            if ball_x < paddle_x:
                # If ball hits paddles left side it
                # should go back to left
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
        # Else Paddle is in the Middle horizontally
        else:
            if ball_x > paddle_x:
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            elif ball_x < paddle_x:
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
def check_collision_with_bricks():
    global ball, bricks
 
    for brick in bricks.bricks:
        if ball.distance(brick) < 40:
            brick.quantity -= 1
            if brick.quantity == 0:
                brick.clear()
                brick.goto(3000, 3000)
                bricks.bricks.remove(brick)
 
            # detect collision from left
            if ball.xcor() < brick.left_wall:
                ball.bounce(x_bounce=True, y_bounce=False)
 
            # detect collision from right
            elif ball.xcor() > brick.right_wall:
                ball.bounce(x_bounce=True, y_bounce=False)
 
            # detect collision from bottom
            elif ball.ycor() < brick.bottom_wall:
                ball.bounce(x_bounce=False, y_bounce=True)
 
            # detect collision from top
            elif ball.ycor() > brick.upper_wall:
                ball.bounce(x_bounce=False, y_bounce=True)
 
while playing_game:
    screen.update()
    time.sleep(0.01)
    ball.move()
 
    check_collision_with_walls()
 
    check_collision_with_paddle()
 
    check_collision_with_bricks()
 
tr.mainloop()

Paso 6: la interfaz de usuario adicional, la puntuación, la pausa del juego y la detección de la victoria del usuario

La Turtle solo necesita escribir, así la escondemos. Definimos un número máximo de vidas que se reducirá en un valor de 1 en el método de disminución_vidas, que se llamará dentro de main.py, siempre que el usuario falle golpeando la pelota. Cada vez que se actualiza la puntuación, se debe borrar el texto anterior. También mantenemos una puntuación alta, que se basa en la lectura de datos de un archivo de texto. Si el archivo de texto no existe, creamos uno con y le escribimos un solo dígito, cero. Que es una puntuación alta. En caso contrario, si existe vemos si tiene algún dato o no. Si no es así, de nuevo la puntuación es aerodinámica. Si no se encontraron errores, la puntuación simplemente se lee y se muestra.

marcador.py

Python3

# code
print("GFG")
from turtle import Turtle
 
try:
    score = int(open('highestScore.txt', 'r').read())
except FileNotFoundError:
    score = open('highestScore.txt', 'w').write(str(0))
except ValueError:
    score = 0
FONT = ('arial', 18, 'normal')
 
 
class Scoreboard(Turtle):
    def __init__(self, lives):
        super().__init__()
        self.color('white')
        self.penup()
        self.hideturtle()
        self.highScore = score
        self.goto(x=-580, y=260)
        self.lives = lives
        self.score = 0
        self.update_score()
 
    def update_score(self):
        self.clear()
        self.write(f"Score: {self.score} | Highest Score: {self.highScore} \
        | Lives: {self.lives}", align='left', font=FONT)
 
    def increase_score(self):
        self.score += 1
        if self.score > self.highScore:
            self.highScore += 1
        self.update_score()
 
    def decrease_lives(self):
        self.lives -= 1
        self.update_score()
 
    def reset(self):
        self.clear()
        self.score = 0
        self.update_score()
        open('highestScore.txt', 'w').write(str(self.highScore))

 

Solo necesitamos escribir algo en la pantalla, por lo que ocultamos la Turtle sin darle forma. Le damos un color elegido al azar de una lista de colores. Escribimos lo que necesitamos escribir, usando un método. que se llama cuando el objeto se crea por primera vez. Cuando el juego está en pausa, simplemente cambiamos el color y retrasamos el programa para que no sea demasiado rápido y el usuario tenga algo de tiempo antes de que el juego se reanude cuando vuelva a presionar la barra espaciadora. Cada vez que escribimos algo o cambiamos de color, el texto anterior debe borrarse. Aquí también heredamos la clase Turtle de la biblioteca de turtle.

ui.py

Python3

import time
from turtle import Turtle
import random
 
FONT = ("Courier", 52, "normal")
FONT2 = ("Courier", 32, "normal")
ALIGNMENT = 'center'
COLOR = "white"
COLOR_LIST = ['light blue', 'royal blue',
              'light steel blue', 'steel blue',
              'light cyan', 'light sky blue',
              'violet', 'salmon', 'tomato',
              'sandy brown', 'purple', 'deep pink',
              'medium sea green', 'khaki']
 
 
class UI(Turtle):
    def __init__(self):
        super().__init__()
        self.hideturtle()
        self.penup()
        self.color(random.choice(COLOR_LIST))
        self.header()
 
    def header(self):
        self.clear()
        self.goto(x=0, y=-150)
        self.write('Breakout', align=ALIGNMENT, font=FONT)
        self.goto(x=0, y=-180)
        self.write('Press Space to PAUSE or RESUME the Game',
                   align=ALIGNMENT, font=('Calibri', 14, 'normal'))
 
    def change_color(self):
        self.clear()
        self.color(random.choice(COLOR_LIST))
        self.header()
 
    def paused_status(self):
        self.clear()
        self.change_color()
        time.sleep(0.5)
 
    def game_over(self, win):
        self.clear()
        if win == True:
            self.write('You Cleared the Game', align='center', font=FONT)
        else:
            self.write("Game is Over", align='center', font=FONT)

Escuchamos la tecla adicional, la barra espaciadora para pausar el juego. Presionar la tecla simplemente llama a una función que invierte un valor booleano que se usa para ejecutar todo dentro del ciclo while infinito si es verdadero. Si este valor booleano es falso, seguimos cambiando el color de nuestra interfaz de usuario. Dado que la pelota se mueve solo cuando se llama al método de movimiento dentro del bucle, que ahora está bajo la verificación booleana, todo el juego se detiene porque la pelota no se mueve. También disminuimos la vida cada vez que la bola toca la pared inferior. Del mismo modo, necesitamos aumentar la puntuación si la pelota golpea un ladrillo. También necesitamos mostrar el final del juego cada vez que nos quedemos sin vidas y finalizar el ciclo while.

principal.py

Python3

import turtle as tr
from paddle import Paddle
from ball import Ball
from scoreboard import Scoreboard
from ui import UI
from bricks import Bricks
import time
 
 
screen = tr.Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Breakout')
screen.tracer(0)
 
ui = UI()
ui.header()
 
score = Scoreboard(lives=5)
paddle = Paddle()
bricks = Bricks()
 
 
ball = Ball()
 
game_paused = False
playing_game = True
 
 
def pause_game():
    global game_paused
    if game_paused:
        game_paused = False
    else:
        game_paused = True
 
 
screen.listen()
screen.onkey(key='Left', fun=paddle.move_left)
screen.onkey(key='Right', fun=paddle.move_right)
screen.onkey(key='space', fun=pause_game)
 
 
def check_collision_with_walls():
 
    global ball, score, playing_game, ui
 
    # detect collision with left and right walls:
    if ball.xcor() < -580 or ball.xcor() > 570:
        ball.bounce(x_bounce=True, y_bounce=False)
        return
 
    # detect collision with upper wall
    if ball.ycor() > 270:
        ball.bounce(x_bounce=False, y_bounce=True)
        return
 
    # detect collision with bottom wall
    # In this case, user failed to hit the
    # ball thus he loses. The game resets.
    if ball.ycor() < -280:
        ball.reset()
        score.decrease_lives()
        if score.lives == 0:
            score.reset()
            playing_game = False
            ui.game_over(win=False)
            return
        ui.change_color()
        return
 
 
def check_collision_with_paddle():
 
    global ball, paddle
    # record x-axis coordinates of ball and paddle
    paddle_x = paddle.xcor()
    ball_x = ball.xcor()
 
    # check if ball's distance(from its middle)
    # from paddle(from its middle) is less than
    # width of paddle and ball is below a certain
    # coordinate to detect their collision
    if ball.distance(paddle) < 110 and ball.ycor() < -250:
 
        # If Paddle is on Right of Screen
        if paddle_x > 0:
            if ball_x > paddle_x:
                # If ball hits paddles left side
                # it should go back to left
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
        # If Paddle is left of Screen
        elif paddle_x < 0:
            if ball_x < paddle_x:
                # If ball hits paddles left side it
                # should go back to left
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
        # Else Paddle is in the Middle horizontally
        else:
            if ball_x > paddle_x:
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            elif ball_x < paddle_x:
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
 
def check_collision_with_bricks():
    global ball, score, bricks
 
    for brick in bricks.bricks:
        if ball.distance(brick) < 40:
            score.increase_score()
            brick.quantity -= 1
            if brick.quantity == 0:
                brick.clear()
                brick.goto(3000, 3000)
                bricks.bricks.remove(brick)
 
            # detect collision from left
            if ball.xcor() < brick.left_wall:
                ball.bounce(x_bounce=True, y_bounce=False)
 
            # detect collision from right
            elif ball.xcor() > brick.right_wall:
                ball.bounce(x_bounce=True, y_bounce=False)
 
            # detect collision from bottom
            elif ball.ycor() < brick.bottom_wall:
                ball.bounce(x_bounce=False, y_bounce=True)
 
            # detect collision from top
            elif ball.ycor() > brick.upper_wall:
                ball.bounce(x_bounce=False, y_bounce=True)
 
 
while playing_game:
 
    if not game_paused:
 
        # UPDATE SCREEN WITH ALL THE MOTION
        # THAT HAS HAPPENED
        screen.update()
        time.sleep(0.01)
        ball.move()
 
        # DETECTING COLLISION WITH WALLS
        check_collision_with_walls()
 
        # DETECTING COLLISION WITH THE PADDLE
        check_collision_with_paddle()
 
        # DETECTING COLLISION WITH A BRICK
        check_collision_with_bricks()
         
        # DETECTING USER'S VICTORY
        if len(bricks.bricks) == 0:
            ui.game_over(win=True)
            break
 
    else:
        ui.paused_status()
 
 
tr.mainloop()

El Código Final

principal.py

Python3

import turtle as tr
from paddle import Paddle
from ball import Ball
from scoreboard import Scoreboard
from ui import UI
from bricks import Bricks
import time
 
 
screen = tr.Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Breakout')
screen.tracer(0)
 
ui = UI()
ui.header()
 
score = Scoreboard(lives=5)
paddle = Paddle()
bricks = Bricks()
 
 
ball = Ball()
 
game_paused = False
playing_game = True
 
 
def pause_game():
    global game_paused
    if game_paused:
        game_paused = False
    else:
        game_paused = True
 
 
screen.listen()
screen.onkey(key='Left', fun=paddle.move_left)
screen.onkey(key='Right', fun=paddle.move_right)
screen.onkey(key='space', fun=pause_game)
 
 
def check_collision_with_walls():
 
    global ball, score, playing_game, ui
 
    # detect collision with left and right walls:
    if ball.xcor() < -580 or ball.xcor() > 570:
        ball.bounce(x_bounce=True, y_bounce=False)
        return
 
    # detect collision with upper wall
    if ball.ycor() > 270:
        ball.bounce(x_bounce=False, y_bounce=True)
        return
 
    # detect collision with bottom wall
    # In this case, user failed to hit the ball
    # thus he loses. The game resets.
    if ball.ycor() < -280:
        ball.reset()
        score.decrease_lives()
        if score.lives == 0:
            score.reset()
            playing_game = False
            ui.game_over(win=False)
            return
        ui.change_color()
        return
 
 
def check_collision_with_paddle():
 
    global ball, paddle
    # record x-axis coordinates of ball and paddle
    paddle_x = paddle.xcor()
    ball_x = ball.xcor()
 
    # check if ball's distance(from its middle)
    # from paddle(from its middle) is less than
    # width of paddle and ball is below a certain
    #coordinate to detect their collision
    if ball.distance(paddle) < 110 and ball.ycor() < -250:
 
        # If Paddle is on Right of Screen
        if paddle_x > 0:
            if ball_x > paddle_x:
                # If ball hits paddles left side it
                # should go back to left
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
        # If Paddle is left of Screen
        elif paddle_x < 0:
            if ball_x < paddle_x:
                # If ball hits paddles left side it
                # should go back to left
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
        # Else Paddle is in the Middle horizontally
        else:
            if ball_x > paddle_x:
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            elif ball_x < paddle_x:
                ball.bounce(x_bounce=True, y_bounce=True)
                return
            else:
                ball.bounce(x_bounce=False, y_bounce=True)
                return
 
 
def check_collision_with_bricks():
    global ball, score, bricks
 
    for brick in bricks.bricks:
        if ball.distance(brick) < 40:
            score.increase_score()
            brick.quantity -= 1
            if brick.quantity == 0:
                brick.clear()
                brick.goto(3000, 3000)
                bricks.bricks.remove(brick)
 
            # detect collision from left
            if ball.xcor() < brick.left_wall:
                ball.bounce(x_bounce=True, y_bounce=False)
 
            # detect collision from right
            elif ball.xcor() > brick.right_wall:
                ball.bounce(x_bounce=True, y_bounce=False)
 
            # detect collision from bottom
            elif ball.ycor() < brick.bottom_wall:
                ball.bounce(x_bounce=False, y_bounce=True)
 
            # detect collision from top
            elif ball.ycor() > brick.upper_wall:
                ball.bounce(x_bounce=False, y_bounce=True)
 
 
while playing_game:
 
    if not game_paused:
 
        # UPDATE SCREEN WITH ALL THE MOTION THAT HAS HAPPENED
        screen.update()
        time.sleep(0.01)
        ball.move()
 
        # DETECTING COLLISION WITH WALLS
        check_collision_with_walls()
 
        # DETECTING COLLISION WITH THE PADDLE
        check_collision_with_paddle()
 
        # DETECTING COLLISION WITH A BRICK
        check_collision_with_bricks()
         
        # DETECTING USER'S VICTORY
        if len(bricks.bricks) == 0:
            ui.game_over(win=True)
            break
 
    else:
        ui.paused_status()
 
 
tr.mainloop()

bola.py

Python3

from turtle import Turtle
 
MOVE_DIST = 10
 
 
class Ball(Turtle):
    def __init__(self):
        super().__init__()
        self.shape('circle')
        self.color('white')
        self.penup()
        self.x_move_dist = MOVE_DIST
        self.y_move_dist = MOVE_DIST
        self.reset()
 
    def move(self):
        new_y = self.ycor() + self.y_move_dist
        new_x = self.xcor() + self.x_move_dist
        self.goto(x=new_x, y=new_y)
 
    def bounce(self, x_bounce, y_bounce):
        if x_bounce:
            self.x_move_dist *= -1
 
        if y_bounce:
            self.y_move_dist *= -1
 
    def reset(self):
        self.goto(x=0, y=-240)
        self.y_move_dist = 10

ladrillos.py

Python3

from turtle import Turtle
import random
 
COLOR_LIST = ['light blue', 'royal blue',
              'light steel blue', 'steel blue',
              'light cyan', 'light sky blue',
              'violet', 'salmon', 'tomato',
              'sandy brown', 'purple', 'deep pink',
              'medium sea green', 'khaki']
 
weights = [1, 2, 1, 1, 3, 2, 1, 4, 1,
           3, 1, 1, 1, 4, 1, 3, 2, 2,
           1, 2, 1, 2, 1, 2, 1]
 
 
class Brick(Turtle):
    def __init__(self, x_cor, y_cor):
        super().__init__()
        self.penup()
        self.shape('square')
        self.shapesize(stretch_wid=1.5, stretch_len=3)
        self.color(random.choice(COLOR_LIST))
        self.goto(x=x_cor, y=y_cor)
 
        self.quantity = random.choice(weights)
 
        # Defining borders of the brick
        self.left_wall = self.xcor() - 30
        self.right_wall = self.xcor() + 30
        self.upper_wall = self.ycor() + 15
        self.bottom_wall = self.ycor() - 15
 
 
class Bricks:
    def __init__(self):
        self.y_start = 0
        self.y_end = 240
        self.bricks = []
        self.create_all_lanes()
 
    def create_lane(self, y_cor):
        for i in range(-570, 570, 63):
            brick = Brick(i, y_cor)
            self.bricks.append(brick)
 
    def create_all_lanes(self):
        for i in range(self.y_start, self.y_end, 32):
            self.create_lane(i)

padel.py

Python3

from turtle import Turtle
 
 
MOVE_DIST = 70
 
 
class Paddle(Turtle):
    def __init__(self):
        super().__init__()
        self.color('steel blue')
        self.shape('square')
        self.penup()
        self.shapesize(stretch_wid=1, stretch_len=10)
        self.goto(x=0, y=-280)
 
    def move_left(self):
        self.backward(MOVE_DIST)
 
    def move_right(self):
        self.forward(MOVE_DIST)

marcador.py

Python3

from turtle import Turtle
 
try:
    score = int(open('highestScore.txt', 'r').read())
except FileNotFoundError:
    score = open('highestScore.txt', 'w').write(str(0))
except ValueError:
    score = 0
FONT = ('arial', 18, 'normal')
 
 
class Scoreboard(Turtle):
    def __init__(self, lives):
        super().__init__()
        self.color('white')
        self.penup()
        self.hideturtle()
        self.highScore = score
        self.goto(x=-580, y=260)
        self.lives = lives
        self.score = 0
        self.update_score()
 
    def update_score(self):
        self.clear()
        self.write(f"Score: {self.score} | Highest Score: \
        {self.highScore} | Lives: {self.lives}", align='left',
                   font=FONT)
 
    def increase_score(self):
        self.score += 1
        if self.score > self.highScore:
            self.highScore += 1
        self.update_score()
 
    def decrease_lives(self):
        self.lives -= 1
        self.update_score()
 
    def reset(self):
        self.clear()
        self.score = 0
        self.update_score()
        open('highestScore.txt', 'w').write(str(self.highScore))

ui.py

Python3

import time
from turtle import Turtle
import random
 
FONT = ("Courier", 52, "normal")
FONT2 = ("Courier", 32, "normal")
ALIGNMENT = 'center'
COLOR = "white"
COLOR_LIST = ['light blue', 'royal blue',
              'light steel blue', 'steel blue',
              'light cyan', 'light sky blue',
              'violet', 'salmon', 'tomato',
              'sandy brown', 'purple', 'deep pink',
              'medium sea green', 'khaki']
 
 
class UI(Turtle):
    def __init__(self):
        super().__init__()
        self.hideturtle()
        self.penup()
        self.color(random.choice(COLOR_LIST))
        self.header()
 
    def header(self):
        self.clear()
        self.goto(x=0, y=-150)
        self.write('Breakout', align=ALIGNMENT, font=FONT)
        self.goto(x=0, y=-180)
        self.write('Press Space to PAUSE or RESUME the Game',
                   align=ALIGNMENT, font=('Calibri', 14, 'normal'))
 
    def change_color(self):
        self.clear()
        self.color(random.choice(COLOR_LIST))
        self.header()
 
    def paused_status(self):
        self.clear()
        self.change_color()
        time.sleep(0.5)
 
    def game_over(self, win):
        self.clear()
        if win == True:
            self.write('You Cleared the Game', align='center', font=FONT)
        else:
            self.write("Game is Over", align='center', font=FONT)

Producción:

 

Publicación traducida automáticamente

Artículo escrito por ishanrastogi26 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *