使用Python Turtle 创建Breakout游戏
在这篇文章中,我们将建立一个著名游戏的克隆: ‘Breakout’。 我们将使用Python的一个内置库建立这个游戏: Turtle.
要求
- 一个像PyCharm这样的IDE。
- Python的基础知识
- Python-Turtle的基本知识。
- 愿意学习
项目结构
我们希望保持编程的简单性。最好的方法是将问题分解成更简单的任务,并逐一实现。我们使用面向对象的编程来实现这一目标。这将使我们的组件相互独立,从而使它们的工作变得容易。
逐步实现
第1步:创建一个黑色背景颜色的窗口:
这里我们简单地导入turtle,并从它的Screen类中创建一个对象来控制程序窗口的发生。我们给它以大小、颜色和标题,以获得一个空白的黑色屏幕。
main.py
import turtle as tr
screen = tr.Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Breakout')
tr.mainloop()
第二步:创建桨:
首先要做的是导入海龟库。决定桨在每次移动时的距离。我们创建桨的类,继承海龟类,然后赋予我们的桨所需的属性,其中包括它的形状、大小和颜色。我们不希望它作画,因此我们使用penup方法。然后我们让它走到屏幕的最低处和中间。我们的窗口宽度为600。由于每个乌龟屏幕是一个XY平面,原点在中心,所以最低点是-300。默认情况下,乌龟对象的尺寸是20 X 20。因为我们只拉伸了它的长度,宽度没有受到影响。我们把乌龟放在最低点以上20个像素处,这样它就能保持可见,否则它的一半就会被隐藏起来。我们还需要它移动,因此我们定义了两个方法来使它向左和向右移动。由于默认情况下乌龟是朝向右边的,所以它基本上会向前和向后移动。
paddle.py
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)
现在,导入划桨的类,并从中创建一个对象。我们使用屏幕对象的listen()方法,使我们的程序对按下的键作出反应。然后,使用onkey()方法,我们定义它需要听哪个键,也就是左和右的方向键。为此,我们分别将我们的乌龟向后中心和向前移动。这里我们也使用了屏幕对象的两个方法,tracer()和update()。我们在程序的窗口中控制动画。通过向tracer传递0,我们关闭了它,通过使用update,我们打开了它。因为有了这些方法,我们可以直接看到屏幕底部的桨叶。如果我们不使用这些方法,我们就会看到在窗口中央创建一个桨,然后移动到底部。
main.py
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()
第三步:创建我们的球
与桨类似,我们继承了Turtle类并创建了一个球,它只是一个具有圆形的乌龟。我们也不希望它作画,所以我们再次使用penup。我们定义的第一个方法是让它移动。由于我们的球将在XY平面上移动,我们让它的x和y坐标同时增加相同的量。这个移动方法将在我们的main.py中被循环调用,因为我们看到一个一直在移动的球。然后我们创建一个方法,当它与墙壁、砖头或球拍碰撞时,使其改变方向,从而将其弹开。有时只需要改变侧面的方向,有时需要改变垂直方向,有时则两者都需要。因此我们有两个if语句。要使方向改变,只需使移动距离为负数,为所需的坐标。最后,我们有一个复位方法,它使我们的球回到它开始的地方。我们希望当球与底部的墙相撞时,这意味着用户没有击中它,应该失去一条生命。
ball.py
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
我们在里面创建一个无限的while循环,我们做的第一件事就是更新我们的屏幕,延迟我们的程序并使球移动。计算机的速度比我们人类所能理解的要快得多,因此我们的程序在每次迭代时都会延迟一点。在每次迭代中,我们也让球移动一次。我们还检查我们的球的当前坐标是什么。我们已经知道了窗口的尺寸,以及在原点位于窗口中心的情况下,边缘的X和Y点将是什么。因此,我们检查我们的球的坐标是否已经超过了边缘,基于此,我们让它弹起。类似地,我们将有方法来检测与球拍的碰撞和与砖块的碰撞。
main.py
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()
第四步:让桨能够弹开球。
桨的长度是200,乌龟的距离法测量两只乌龟中心的距离。因此,桨的左边和右边都有100。同样地,球(20 X 20)在其中心点周围有10。因此,当它们的中心点之间的距离为100+10<=110时,球就接触到了划子。同时,我们也要检查球的y坐标是否已经达到了桨的上端y坐标。当这两个条件都满足时,球就会被球拍击中,并且应该会弹起来。在原来的游戏中,我们可以注意到,当球打到球拍的极端边缘时,它的水平方向也会改变。我们检查球拍是否在窗口的左边或右边,在这里面,我们检查球是否在球拍的左边或右边,然后如果都在左边或右边,就使它改变水平方向。
main.py
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 belo 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()
第五步:设置砖块
我们创建两个类。一个是单独的砖块,一个是出现在窗口上的那些砖块的集合。一个单独的砖块是一个简单的乌龟,它被横向拉长,使其成为矩形。它也有一个 “数量 “属性,即它的重量,也就是在它消失之前需要被球击中的次数。这个数字是随机赋予砖头的,我们不希望我们的游戏太难,因此我们把1作为最指定的重量。为了检查球与砖头的碰撞情况,我们需要砖头四条边的坐标。X坐标代表左右,Y坐标代表上下。砖块的列表只是一个砖块的数组。我们知道,砖块的起始x坐标和结束坐标保持不变,对于每一层或每一列砖块,唯一变化的是y轴。因此,一个单一的车道创建方法被调用到另一个方法里面,该方法传递其车道的y坐标。
bricks.py
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, 650, 62):
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)
在我们的main.py中导入砖块,使它们对用户可见。现在是时候定义检查球是否与砖块碰撞的函数了。我们使用距离法检查球和砖头是否在接触范围内。在检查这个条件时要记住球和砖头的尺寸,因为接触距离是每个乌龟尺寸的一半之和。有时,需要进行撞击和试验来得出一个适当的数字。一旦我们检查了球和砖头是否接触,我们就检查它的坐标是否在砖头的左边、右边、上面或下面,然后让它相应地弹起。如果它与砖头侧面相撞,球的水平方向会改变,其他垂直方向也会改变。当碰撞被确认后,我们减少该砖头的数量,当变成等于零时,相应的砖头首先被移出可见窗口范围,然后从列表中删除。
main.py
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 belo 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()
第六步:额外的用户界面、分数和暂停游戏
乌龟只需要写,因此我们把它隐藏起来。我们定义了一个最大的生命数,在decrease_lives方法中,每当用户没有击中球时,该方法将在main.py中被调用,该方法将减少1的值。每次更新分数时,都需要清除之前的文本。我们还维护一个高分,它依赖于从一个文本文件中读取的数据。如果该文本文件不存在,我们就创建一个,并向其写入一个数字,即0。这就是一个高分。否则,如果它存在,我们看它是否有一些数据。如果它没有,那么分数又是空的。如果没有遇到错误,分数就被简单地读取和显示。
scoreboard.py
# 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))
我们只需要在屏幕上写一些东西,所以我们隐藏了乌龟,没有给它任何形状。我们确实给它一个从颜色列表中随机选择的颜色。我们用一个方法来写我们需要写的东西,这个方法在对象第一次被创建时被调用。当游戏暂停时,我们简单地改变颜色并延迟程序,这样它就不会太快,而且用户在再次点击空格键时,在游戏恢复之前会有一些时间。每次,我们在写东西或改变颜色时,都需要清除之前的文本,这里我们也继承了Turtle库中的Turtle类。
ui.py
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):
self.clear()
self.write("Game is Over", align='center', font=FONT)
我们听从附加键,听从空格键来暂停游戏。按这个键只是调用一个函数,这个函数会反转一个布尔值,如果这个布尔值为真,就用来在无限的while循环中运行一切。如果这个布尔值是假的,我们只是不断改变我们的用户界面的颜色。由于球只有在循环内调用移动方法时才会移动,现在是在布尔值的检查下,整个游戏就会暂停,因为球没有移动。每当球接触到底部墙壁时,我们也会减少生命值。同样地,如果球碰到砖头,我们需要增加分数。我们还需要在生命耗尽时显示游戏结束,并结束while循环。
main.py
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()
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 belo 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()
else:
ui.paused_status()
tr.mainloop()
最终代码
main.py
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()
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 belo 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()
else:
ui.paused_status()
tr.mainloop()
ball.py
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
bricks.py
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, 650, 62):
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)
paddle.py
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)
scoreboard.py
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
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):
self.clear()
self.write("Game is Over", align='center', font=FONT)
Output: