20242221 实验四《Python程序设计》实验报告
课程:《Python程序设计》
班级: 2422
姓名: 周侯妤
学号:20242221
实验教师:王志强
实验日期:2025年5月14日
必修/选修: 公选课
1.实验内容
本实验设计了一个反应测试游戏,玩家需要根据屏幕上出现的节拍(beats)在正确的时间按下相应的方向键(上、下、左、右)。游戏的目标是尽可能多地击中节拍,以获得高分和连击(combo),然后对过往成绩进行排名。
2. 实验过程及结果
(1)初始代码设计
这段代码是一个简单版的节奏反应测试游戏。它包含了游戏的基本逻辑和功能,包括初始化和屏幕设置,颜色定义,游戏参数及游戏的基本逻辑,非常的简洁。
import pygame
import sys
import random
import time
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("节奏反应测试")
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
clock = pygame.time.Clock()
FPS = 60
font = pygame.font.SysFont('Arial', 30)
class RhythmGame:
def __init__(self):
self.beats = []
self.target_pos = WIDTH // 2
self.score = 0
self.combo = 0
self.max_combo = 0
self.last_beat_time = 0
self.beat_interval = 1.0
self.beat_speed = 200
self.generate_beats()
def generate_beats(self):
"""生成一系列节拍"""
self.beats = []
current_time = 0
for _ in range(20):
current_time += self.beat_interval
start_x = random.choice([0, WIDTH])
self.beats.append({
'time': current_time,
'x': start_x,
'width': 30,
'height': 30,
'hit': False,
'accuracy': 0
})
def update(self, current_time):
"""更新游戏状态"""
for beat in self.beats:
if not beat['hit']:
if beat['x'] == 0: # 从左侧进入
beat['x'] += self.beat_speed / FPS
else: # 从右侧进入
beat['x'] -= self.beat_speed / FPS
if abs(beat['x'] - self.target_pos) < 15:
beat['hit'] = True
beat['accuracy'] = -2
def handle_key_press(self, current_time):
"""处理按键事件"""
for beat in self.beats:
if not beat['hit']:
distance = abs(beat['x'] - self.target_pos)
if distance < 50:
beat['hit'] = True
accuracy = 100 - (distance / 50 * 100)
beat['accuracy'] = max(0, accuracy)
self.score += accuracy
self.combo += 1
self.max_combo = max(self.max_combo, self.combo)
return
self.combo = 0
def draw(self, screen):
"""绘制游戏元素"""
screen.fill(BLACK)
pygame.draw.rect(screen, WHITE, (self.target_pos - 25, HEIGHT // 2 - 50, 50, 100), 2)
for beat in self.beats:
if not beat['hit']:
color = YELLOW if abs(beat['x'] - self.target_pos) < 50 else RED
pygame.draw.rect(screen, color, (beat['x'] - beat['width']//2, HEIGHT // 2 - beat['height']//2, beat['width'], beat['height']))
score_text = font.render(f"分数: {int(self.score)}", True, WHITE)
combo_text = font.render(f"连击: {self.combo}", True, WHITE)
max_combo_text = font.render(f"最大连击: {self.max_combo}", True, WHITE)
screen.blit(score_text, (20, 20))
screen.blit(combo_text, (20, 60))
screen.blit(max_combo_text, (20, 100))
def main():
game = RhythmGame()
running = True
start_time = time.time()
while running:
current_time = time.time() - start_time
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
game.handle_key_press(current_time)
game.update(current_time)
game.draw(screen)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
初始代码运行图片:

(2)代码的持续优化
在原有的程序基础上,在多个方面进行了改进和增强,提供了更复杂的节拍生成逻辑、更丰富的按键处理、击打成功后粒子效果、游戏结束和重启机制、高分记录功能、倒计时和背景颜色渐变、更详细的统计信息、更复杂的节拍移动逻辑、更丰富的视觉以及更复杂的用户界面。
其中使用了多种 Python 语法和 Pygame 相关的函数,包括变量赋值、条件语句、循环语句、字典操作、函数定义、类的定义和实例化、异常处理、列表推导式、Pygame 初始化、窗口创建、事件处理、文本和图形绘制、屏幕更新、帧率控制、随机数生成、数学计算、文件操作等。根据AI提供的修改意见,查阅相关资料,增加相关代码段,完善优化原有代码。
优化意见:

相关资料学习:
《python Pygame库介绍和使用,基本游戏开发》https://blog.csdn.net/weixin_45568391/article/details/111562741
《Python 字典》https://www.runoob.com/python/python-dictionary.html
《【python基础】类与类的实例化对象、类方法与类的属性、类实例化的方法与属性、初始化函数、类的继承与重写、@property、私有属性与方法》https://blog.csdn.net/sodaloveer/article/details/134056037
《python 控制帧率》https://blog.51cto.com/u_16175524/9341283#:~:text=%E5%9C%A8Python%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%20time%20%E6%A8%A1%E5%9D%97%E6%9D%A5%E6%8E%A7%E5%88%B6%E5%B8%A7%E7%8E%87%E3%80%82%20time,%E6%A8%A1%E5%9D%97%E6%8F%90%E4%BE%9B%E4%BA%86%E4%B8%80%E4%BA%9B%E5%87%BD%E6%95%B0%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%B8%AE%E5%8A%A9%E6%88%91%E4%BB%AC%E8%AE%A1%E7%AE%97%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E7%9A%84%E6%97%B6%E9%97%B4%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%B8%A7%E7%8E%87%E6%8E%A7%E5%88%B6%E3%80%82%20%E9%A6%96%E5%85%88%EF%BC%8C%E6%88%91%E4%BB%AC%E9%9C%80%E8%A6%81%E7%A1%AE%E5%AE%9A%E6%AF%8F%E4%B8%80%E5%B8%A7%E5%BA%94%E8%AF%A5%E6%8C%81%E7%BB%AD%E7%9A%84%E6%97%B6%E9%97%B4%EF%BC%8C%E5%8D%B3%E6%AF%8F%E4%B8%80%E5%B8%A7%E7%9A%84%E6%97%B6%E9%97%B4%E9%97%B4%E9%9A%94%E3%80%82%20%E4%BE%8B%E5%A6%82%EF%BC%8C%E5%A6%82%E6%9E%9C%E6%88%91%E4%BB%AC%E5%B8%8C%E6%9C%9B%E6%AF%8F%E7%A7%92%E9%92%9F%E7%BB%98%E5%88%B630%E5%B8%A7%EF%BC%8C%E9%82%A3%E4%B9%88%E6%AF%8F%E4%B8%80%E5%B8%A7%E7%9A%84%E6%97%B6%E9%97%B4%E9%97%B4%E9%9A%94%E5%B0%B1%E5%BA%94%E8%AF%A5%E6%98%AF1%2F30%E7%A7%92%E3%80%82%20%E6%8E%A5%E4%B8%8B%E6%9D%A5%EF%BC%8C%E6%88%91%E4%BB%AC%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA%E5%BE%AA%E7%8E%AF%E6%9D%A5%E6%8E%A7%E5%88%B6%E6%AF%8F%E4%B8%80%E5%B8%A7%E7%9A%84%E6%B8%B2%E6%9F%93%E3%80%82
《python如何绘制图形文字》https://docs.pingcode.com/baike/891593
《python 如何创建窗口》https://docs.pingcode.com/baike/722016
优化代码如下:
import pygame
import sys
import random
import time
import math
import json
from pygame import gfxdraw
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Enhanced Rhythm Reaction Test")
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
GRAY = (100, 100, 100)
PURPLE = (128, 0, 128)
CYAN = (0, 255, 255)
clock = pygame.time.Clock()
FPS = 60
font_large = pygame.font.SysFont('Arial', 48)
font_medium = pygame.font.SysFont('Arial', 36)
font_small = pygame.font.SysFont('Arial', 24)
HIGH_SCORE_FILE = "rhythm_high_scores.json"
class Particle:
def __init__(self, x, y, color):
self.x = x
self.y = y
self.color = color
self.size = random.randint(2, 5)
self.life = random.randint(20, 40)
self.angle = random.uniform(0, math.pi * 2)
self.speed = random.uniform(1, 3)
self.alpha = 255
def update(self):
self.x += math.cos(self.angle) * self.speed
self.y += math.sin(self.angle) * self.speed
self.life -= 1
self.alpha = int(255 * (self.life / 40))
def draw(self, surface):
if self.life > 0:
color_with_alpha = (*self.color[:3], self.alpha)
pygame.gfxdraw.filled_circle(
surface,
int(self.x),
int(self.y),
self.size,
color_with_alpha
)
class RhythmGame:
def __init__(self):
self.high_scores = self.load_high_scores()
self.reset_game()
def load_high_scores(self):
try:
with open(HIGH_SCORE_FILE, 'r') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return []
def save_high_scores(self):
with open(HIGH_SCORE_FILE, 'w') as f:
json.dump(self.high_scores, f)
def reset_game(self):
"""Reset game state"""
self.beats = []
self.target_pos = {
'left': (50, HEIGHT // 2),
'right': (WIDTH - 50, HEIGHT // 2),
'up': (WIDTH // 2, 50),
'down': (WIDTH // 2, HEIGHT - 50)
}
self.target_width = 50
self.score = 0
self.combo = 0
self.max_combo = 0
self.beat_count = 0
self.total_beats = 20
self.base_speed = 200
self.speed_increase = 15
self.current_speed = self.base_speed
self.game_over = False
self.start_time = time.time()
self.particles = []
self.judgement_text = ""
self.judgement_time = 0
self.judgement_color = WHITE
self.background_color = BLACK
self.title_alpha = 255
self.game_started = False
self.countdown = 3
self.last_countdown_time = time.time()
self.countdown_text = ""
self.countdown_scale = 1.0
def generate_next_beat(self):
"""Generate next beat from center moving outward"""
if self.beat_count >= self.total_beats:
return
self.beat_count += 1
direction = random.choice(['left', 'right', 'up', 'down'])
start_x = WIDTH // 2
start_y = HEIGHT // 2
width, height = 40, 40
self.beats.append({
'x': start_x,
'y': start_y,
'width': width,
'height': height,
'direction': direction,
'speed': self.current_speed,
'hit': False,
'missed': False,
'accuracy': 0,
'hit_time': 0,
'color': random.choice([RED, GREEN, BLUE, YELLOW, PURPLE, CYAN])
})
self.current_speed += self.speed_increase
def update(self, current_time):
"""Update game state"""
for particle in self.particles[:]:
particle.update()
if particle.life <= 0:
self.particles.remove(particle)
if self.game_over:
return
if not self.game_started:
if time.time() - self.last_countdown_time >= 1.0:
self.countdown -= 1
self.last_countdown_time = time.time()
self.countdown_scale = 1.5
if self.countdown > 0:
self.countdown_text = str(self.countdown)
elif self.countdown == 0:
self.countdown_text = "GO!"
else:
self.game_started = True
self.generate_next_beat()
self.countdown_scale = max(1.0, self.countdown_scale * 0.9)
return
for beat in self.beats:
if not beat['hit'] and not beat['missed']:
if beat['direction'] == 'right':
beat['x'] += beat['speed'] / FPS
if beat['x'] >= self.target_pos['right'][0]:
beat['missed'] = True
self.combo = 0
self.show_judgement("Miss", RED)
elif beat['direction'] == 'left':
beat['x'] -= beat['speed'] / FPS
if beat['x'] <= self.target_pos['left'][0]:
beat['missed'] = True
self.combo = 0
self.show_judgement("Miss", RED)
elif beat['direction'] == 'down':
beat['y'] += beat['speed'] / FPS
if beat['y'] >= self.target_pos['down'][1]:
beat['missed'] = True
self.combo = 0
self.show_judgement("Miss", RED)
elif beat['direction'] == 'up':
beat['y'] -= beat['speed'] / FPS
if beat['y'] <= self.target_pos['up'][1]:
beat['missed'] = True
self.combo = 0
self.show_judgement("Miss", RED)
if len([b for b in self.beats if not b['hit'] and not b['missed']]) == 0 and self.beat_count < self.total_beats:
self.generate_next_beat()
if self.beat_count >= self.total_beats and all(b['hit'] or b['missed'] for b in self.beats):
self.game_over = True
if len(self.high_scores) < 5 or self.score > min(self.high_scores, key=lambda x: x['score'])['score']:
self.high_scores.append({
'score': int(self.score),
'time': time.strftime("%Y-%m-%d %H:%M:%S")
})
self.high_scores.sort(key=lambda x: x['score'], reverse=True)
self.high_scores = self.high_scores[:5]
self.save_high_scores()
def show_judgement(self, text, color):
"""Show judgement text (Perfect, Good, Miss, etc.)"""
self.judgement_text = text
self.judgement_color = color
self.judgement_time = time.time()
def handle_key_press(self, current_time, key):
"""Handle key press based on direction"""
if self.game_over or not self.game_started:
return
key_directions = {
pygame.K_LEFT: 'left',
pygame.K_RIGHT: 'right',
pygame.K_UP: 'up',
pygame.K_DOWN: 'down'
}
if key not in key_directions:
return
direction = key_directions[key]
for beat in self.beats:
if not beat['hit'] and not beat['missed'] and beat['direction'] == direction:
target_x, target_y = self.target_pos[direction]
distance = math.sqrt((beat['x'] - target_x) ** 2 + (beat['y'] - target_y) ** 2)
if distance < self.target_width:
beat['hit'] = True
beat['hit_time'] = current_time
accuracy = 100 - (distance / self.target_width * 100)
beat['accuracy'] = max(0, accuracy)
self.score += accuracy
self.combo += 1
self.max_combo = max(self.max_combo, self.combo)
for _ in range(20):
self.particles.append(Particle(beat['x'], beat['y'], beat['color']))
if accuracy > 90:
self.show_judgement("Perfect!", YELLOW)
elif accuracy > 70:
self.show_judgement("Good!", GREEN)
else:
self.show_judgement("OK", BLUE)
if len([b for b in self.beats if not b['hit'] and not b['missed']]) == 0 and self.beat_count < self.total_beats:
self.generate_next_beat()
return
self.combo = 0
self.show_judgement("Miss", RED)
def draw(self, screen):
"""Draw game elements"""
screen.fill(BLACK)
if not self.game_started and not self.game_over:
if self.countdown_text:
countdown = font_large.render(self.countdown_text, True, WHITE)
scaled_countdown = pygame.transform.scale(
countdown,
(int(countdown.get_width() * self.countdown_scale),
int(countdown.get_height() * self.countdown_scale))
)
screen.blit(scaled_countdown,
(WIDTH // 2 - scaled_countdown.get_width() // 2,
HEIGHT // 2 - scaled_countdown.get_height() // 2))
title = font_large.render("Enhanced Rhythm Reaction Test", True, WHITE)
subtitle = font_medium.render("Hit the beats in rhythm!", True, GRAY)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, HEIGHT // 4 - title.get_height() // 2))
screen.blit(subtitle, (WIDTH // 2 - subtitle.get_width() // 2, HEIGHT // 4 + title.get_height()))
return
for direction, (x, y) in self.target_pos.items():
pygame.draw.circle(screen, WHITE, (x, y), self.target_width // 2, 2)
if direction == 'left':
pygame.draw.polygon(screen, WHITE, [(x - 15, y), (x + 15, y - 15), (x + 15, y + 15)])
elif direction == 'right':
pygame.draw.polygon(screen, WHITE, [(x + 15, y), (x - 15, y - 15), (x - 15, y + 15)])
elif direction == 'up':
pygame.draw.polygon(screen, WHITE, [(x, y - 15), (x - 15, y + 15), (x + 15, y + 15)])
elif direction == 'down':
pygame.draw.polygon(screen, WHITE, [(x, y + 15), (x - 15, y - 15), (x + 15, y - 15)])
for particle in self.particles:
particle.draw(screen)
for beat in self.beats:
if not beat['hit'] and not beat['missed']:
target_x, target_y = self.target_pos[beat['direction']]
distance = math.sqrt((beat['x'] - target_x) ** 2 + (beat['y'] - target_y) ** 2)
color = beat['color']
pygame.draw.rect(screen, color,
(beat['x'] - beat['width'] // 2,
beat['y'] - beat['height'] // 2,
beat['width'],
beat['height']))
score_text = font_small.render(f"Score: {int(self.score)}", True, WHITE)
combo_text = font_small.render(f"Combo: {self.combo}", True, WHITE)
max_combo_text = font_small.render(f"Max Combo: {self.max_combo}", True, WHITE)
beats_text = font_small.render(f"Beats: {self.beat_count}/{self.total_beats}", True, WHITE)
screen.blit(score_text, (20, 20))
screen.blit(combo_text, (20, 60))
screen.blit(max_combo_text, (20, 100))
screen.blit(beats_text, (20, 140))
speed_text = font_small.render(f"Speed: {int(self.current_speed)}", True, WHITE)
screen.blit(speed_text, (WIDTH - 150, 20))
if time.time() - self.judgement_time < 0.5:
judgement = font_medium.render(self.judgement_text, True, self.judgement_color)
screen.blit(judgement, (WIDTH // 2 - judgement.get_width() // 2, HEIGHT // 2 - 50))
keys_text = font_small.render("Use Arrow Keys to Hit Beats", True, GRAY)
screen.blit(keys_text, (WIDTH // 2 - keys_text.get_width() // 2, HEIGHT - 30))
if self.game_over:
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
screen.blit(overlay, (0, 0))
hit_beats = [b for b in self.beats if b['hit']]
hit_rate = len(hit_beats) / self.total_beats * 100
avg_accuracy = sum(b['accuracy'] for b in hit_beats) / len(hit_beats) if hit_beats else 0
title = font_large.render("Game Over!", True, WHITE)
final_score = font_medium.render(f"Final Score: {int(self.score)}", True, YELLOW)
hit_rate_text = font_small.render(f"Hit Rate: {hit_rate:.1f}%", True, WHITE)
avg_acc_text = font_small.render(f"Avg Accuracy: {avg_accuracy:.1f}%", True, WHITE)
max_combo_final = font_small.render(f"Max Combo: {self.max_combo}", True, WHITE)
restart_text = font_small.render("Press R to restart, ESC to quit", True, GRAY)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, HEIGHT // 2 - 150))
screen.blit(final_score, (WIDTH // 2 - final_score.get_width() // 2, HEIGHT // 2 - 80))
screen.blit(hit_rate_text, (WIDTH // 2 - hit_rate_text.get_width() // 2, HEIGHT // 2 - 40))
screen.blit(avg_acc_text, (WIDTH // 2 - avg_acc_text.get_width() // 2, HEIGHT // 2))
screen.blit(max_combo_final, (WIDTH // 2 - max_combo_final.get_width() // 2, HEIGHT // 2 + 40))
high_score_title = font_small.render("High Scores:", True, YELLOW)
screen.blit(high_score_title, (WIDTH // 2 - high_score_title.get_width() // 2, HEIGHT // 2 + 80))
for i, score in enumerate(self.high_scores[:5]):
score_text = font_small.render(
f"{i + 1}. {score['score']} - {score['time']}",
True,
WHITE
)
screen.blit(score_text, (WIDTH // 2 - score_text.get_width() // 2, HEIGHT // 2 + 120 + i * 30))
screen.blit(restart_text, (WIDTH // 2 - restart_text.get_width() // 2, HEIGHT - 60))
def main():
game = RhythmGame()
running = True
while running:
current_time = time.time() - game.start_time
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key in [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN]:
game.handle_key_press(current_time, event.key)
elif event.key == pygame.K_r and game.game_over:
game.reset_game()
elif event.key == pygame.K_ESCAPE:
running = False
game.update(current_time)
game.draw(screen)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
运行结果:



3.实验过程中遇到的问题和解决过程
问题1:游戏中图形页面创建复杂。
问题1解决方案:下载pygame,一个用于开发游戏和多媒体应用程序的 Python 库。它提供了丰富的功能,用于处理图形、音频、事件处理和游戏循环等。大大减小了代码复杂度。
问题2:不同尺寸数据的传递导致程序出现错误。
问题2解决方案:在调用下一个函数之前进行取整操作。
问题3:游戏精度过高导致可操作性不强。
问题3解决办法:调整节拍大小,目标区域,或者速度大小。
4.其他
游戏的核心是玩家的体验。如果游戏难度设计不合理,或者玩家在游戏过程中感到困惑、挫败或无聊,那么游戏的趣味性和教育意义都会大打折扣,。从用户角度出发逐步改进,才能使游戏更有温度。从一个相对基础的创意构建起基础代码,在其基础上,不断生发新的想法,再上网搜索如何进行代码实践,不断在原有代码中增加代码段获得新的功能,进行完善和优化。
通过本学期的Python程序设计课程学习,我掌握了Python编程的基础知识和应用技能。课程从最基础的变量、数据类型讲起,逐步深入到函数、文件操作等等。在实践环节中,我熟练运用了基本语法、判定语句、循环语句、逻辑运算及创建服务端和客户端等。这些实践不仅巩固了理论知识,也提升了解决实际问题的能力。特别是在调试程序的过程中,我学会了如何分析错误信息,逐步排查问题。我认识到Python语言的简洁性和强大功能,这为我后续的专业学习打下了基础。虽然课程即将结束,但我将继续深入学习Python在其他方面的更多应用。
5.参考资料
《Python程序设计与数据结构教程(第二版)》
《Python程序设计与数据结构教程(第二版)》学习指导
《python Pygame库介绍和使用,基本游戏开发》
《Python 字典》
《【python基础】类与类的实例化对象、类方法与类的属性、类实例化的方法与属性、初始化函数、类的继承与重写、@property、私有属性与方法》
《python 控制帧率》
《python如何绘制图形文字》
《python 如何创建窗口》等
浙公网安备 33010602011771号