【Python项目】乒乓球小游戏

零、序章

这是一个使用Python写的打乒乓游戏。

可以练习一些面向对象的知识。

实现的功能有:

  • 球的发球、碰撞检测及反弹、出界检测
  • 玩家球拍的上下移动、电脑球拍的自动移动
  • 区分玩家、电脑的计分板及刷新

可以改进的地方有:

  • 暂停功能
  • 碰撞检测可以优化,现在是给球加个个碰撞后的加速(闪现)以免卡bug
  • 球和球拍的碰撞可以,现在只能接住球并反弹出去,而不能将球拍的移动或碰撞位置与球的反弹联动起来实现击球效果
  • 电脑球拍的逻辑优化,现在移动过于鬼畜

一、游戏主程序

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)

五、效果展示

在这里插入图片描述

  • 5
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sprite.Nym

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值