这是一个使用Python写的打乒乓游戏。
可以练习一些面向对象的知识。
实现的功能有:
可以改进的地方有:
import time
from turtle import Turtle, Screen
from ball import Ball
from bat import Bat
from scoreboard import ScoreBoard
# 设置难度
MODE = 'normal'
# 实例化屏幕对象、设置屏幕大小、背景色、标题、关闭实时刷新
screen = Screen()
screen.setup(width=1200, height=600)
screen.bgcolor('black')
screen.title('Ping-Pong')
screen.tracer(False)
def cal_limits(screen):
"""
根据屏幕计算边界的函数
:param screen: 屏幕
:return: 上下左右四个边界
"""
u_limit = screen.window_height() // 2 - 30
d_limit = -screen.window_height() // 2 + 30
l_limit = -screen.window_width() // 2 + 30
r_limit = screen.window_width() // 2 - 30
return u_limit, d_limit, l_limit, r_limit
def draw_mid_line(limits):
"""
这是一个绘制中线的函数
:param limits: 边界
:return: 无
"""
line = Turtle()
line.ht()
line.pensize(10)
line.pencolor('white')
line.penup()
line.goto(0, limits[1])
line.setheading(90)
while line.pos()[1] <= limits[0]:
line.pendown()
line.forward(10)
line.penup()
line.forward(30)
def game_init():
"""
这是一个重开一局的函数
:return: 无
"""
# 调用发球函数
ball.serve()
# 初始化球拍位置
player_bat.b_goto(limits[2], 0)
computer_bat.b_goto(limits[3], 0)
# 停顿1秒
time.sleep(1)
def quit_game():
"""
控制退出游戏的函数
:return: 无
"""
screen.bye()
# 计算边界
limits = cal_limits(screen)
# 画出中线
draw_mid_line(limits)
# 实例化球对象、球拍对象、分数版对象
ball = Ball()
player_bat = Bat(limits[2], limits)
computer_bat = Bat(limits[3], limits)
player_score = ScoreBoard((-100, limits[0] - 50))
computer_score = ScoreBoard((60, limits[0] - 50))
# 监听屏幕
screen.listen()
screen.onkey(key='q', fun=quit_game)
screen.onkey(key='Up', fun=player_bat.up)
screen.onkey(key='Down', fun=player_bat.down)
while True:
# 每局游戏开始刷新分数
player_score.refresh_score()
computer_score.refresh_score()
while True:
# 每隔0.015刷新一次屏幕
time.sleep(0.015)
# 电脑球拍移动
computer_bat.computer_move(ball_pos=ball.pos(), mode=MODE)
# 撞击检测
hit_result = ball.check_hit(limits, player_bat, computer_bat)
# 如果右出界给玩家加分,左出界给电脑加分
if hit_result == 1:
player_score.score += 1
game_init()
break
elif hit_result == 2:
computer_score.score += 1
game_init()
break
# 球向前移动
ball.forward(10)
# 屏幕刷新
screen.update()
import random
from turtle import Turtle
# 球类继承自Turtle类,提笔、形状是球、白色
class Ball(Turtle):
def __init__(self):
super().__init__()
self.penup()
self.shape('circle')
self.color('white')
# 调用发球函数
self.serve()
def serve(self):
"""
发球函数
:return: 无
"""
self.goto(0, 0)
# 发球角度随机
heading = random.choice(
[random.randint(330, 350), random.randint(10, 30), random.randint(150, 170), random.randint(190, 210)]
)
self.setheading(heading)
def check_hit_wall(self, limits):
"""
检测撞上下边界的函数
:param limits: 边界
:return: 无
"""
if self.pos()[1] > limits[0] or self.pos()[1] < limits[1]:
self.vertical_hit()
def check_hit_bat(self, p_bat, c_bat):
"""
检测撞板子或出左右边界的函数
:param p_bat: 玩家控制的球板对象
:param c_bat: 电脑控制的球板对象
:return: 撞到板子了就触发水平碰撞函数并返回0,什么都没碰到也返回0,左出界返回2,右出界返回1
"""
ball_x = self.pos()[0]
ball_y = self.pos()[1]
# 检测逻辑:如果x坐标很靠近,且球的y坐标被包括在了球拍的上下边界之中,则判定为碰撞
if ball_x - p_bat.pos()[0] < 20 and p_bat.lower_limit - 20 < ball_y < p_bat.upper_limit:
self.horizontal_hit()
elif c_bat.pos()[0] - ball_x < 20 and c_bat.lower_limit - 20 < ball_y < c_bat.upper_limit and ball_y:
self.horizontal_hit()
else:
# 这里在检测是否从右、左出界
if ball_x > c_bat.pos()[0]:
return 1
elif ball_x < p_bat.pos()[0]:
return 2
return 0
def check_hit(self, limits, p_bat, c_bat):
"""
这是一个综合检测是否出界+撞击的函数
:param limits: 边界
:param p_bat: 玩家控制的球板对象
:param c_bat: 电脑控制的球板对象
:return: 和上一个函数返回值相同
"""
self.check_hit_wall(limits)
return self.check_hit_bat(p_bat, c_bat)
def horizontal_hit(self):
"""
水平撞击函数,在镜像反弹的基础上加入随机变量,且撞击会给球一个小加速(这是为了防止卡bug)
:return: 无
"""
self.setheading(540 - self.heading() + random.randint(-10, 10))
# 小加速
self.forward(30)
def vertical_hit(self):
"""
垂直撞击函数,没有添加随机变量,完全弹性碰撞
:return: 无
"""
self.setheading(360 - self.heading())
import random
from turtle import Turtle
# 球拍移动距离常数、球拍长度常数
STEP = 30
BAT_SIZE = 6
# 球拍类,继承自Turtle类
class Bat(Turtle):
# 方形,扯成长条状、提笔、白色、加入上下边界两个属性、也把屏幕边界作为属性记下来,这是为了后面方便移动函数
def __init__(self, x, game_edge):
super().__init__()
self.shape('square')
self.shapesize(stretch_wid=BAT_SIZE, stretch_len=1)
self.penup()
self.color('white')
self.upper_limit = 0
self.lower_limit = 0
# 这个b_goto是goto的改进版
self.b_goto(x, 0)
self.limits = game_edge
def up(self):
"""
这是一个控制向上移动的函数,如果已经达到上边界就不允许移动
:return: 无
"""
if self.upper_limit <= self.limits[0] - 30:
self.setheading(90)
self.forward(STEP)
self.setheading(0)
self.upper_limit += STEP
self.lower_limit += STEP
def down(self):
"""
同上
:return: 无
"""
if self.lower_limit >= self.limits[1] + 30:
self.setheading(270)
self.forward(STEP)
self.setheading(0)
self.upper_limit -= STEP
self.lower_limit -= STEP
def computer_move(self, ball_pos, mode):
"""
这是一个控制电脑球拍移动的函数,移动逻辑是简单地跟随球的y坐标
:param ball_pos: 球坐标
:param mode: 难度
:return: 无
"""
# 根据难度设置电脑球拍的发呆概率
if mode == 'easy':
ignore = 0.9
elif mode == 'normal':
ignore = 0.85
elif mode == 'hard':
ignore = 0.8
else:
ignore = 0.75
# 如果随机数大于发呆率,则执行移动,否则发呆
if random.random() > ignore:
if ball_pos[1] - self.pos()[1] > 10:
self.up()
elif ball_pos[1] - self.pos()[1] < 10:
self.down()
def b_goto(self, x, y):
"""
goto的改进版,主要是为了同步刷新上下边界属性
:param x: x坐标
:param y: y坐标
:return: 无
"""
self.goto(x, y)
# 同步刷新上下边界属性
self.upper_limit = self.pos()[1] + BAT_SIZE / 2 * 20
self.lower_limit = self.pos()[1] - BAT_SIZE / 2 * 20
from turtle import Turtle
# 字体常量
FONT = 'TimeNewRoman', 60, 'normal'
# 计分板类,继承自Turtle类
class ScoreBoard(Turtle):
# 计分板要隐藏起来、提笔、初始score属性为0分、到指定位置
def __init__(self, pos):
super().__init__()
self.ht()
self.penup()
self.score = 0
self.goto(pos)
def refresh_score(self):
"""
这是一个刷新分数的函数
:return: 无
"""
# 清空之前的分并写上新分
self.clear()
self.pendown()
self.pencolor('white')
self.write(self.score, font=FONT)