添加贪吃蛇游戏及其控制功能

pull/109/head
Maziang 3 months ago
parent cd39d74ace
commit db65657f15

@ -0,0 +1,437 @@
# snake_game.py
"""
贪吃蛇小游戏模块
用户用WASD或方向键控制贪吃蛇移动
"""
import sys
import random
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QLabel, QMessageBox
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QRect
from PyQt5.QtGui import QPainter, QColor, QFont, QBrush, QPen
class SnakeGame(QWidget):
"""贪吃蛇游戏画布"""
# 游戏常量
GRID_SIZE = 20 # 网格大小
GRID_WIDTH = 30 # 水平网格数
GRID_HEIGHT = 20 # 垂直网格数
GAME_SPEED = 150 # 游戏速度(毫秒)
MIN_SPEED = 50 # 最小速度(毫秒)
MAX_SPEED = 300 # 最大速度(毫秒)
SPEED_STEP = 10 # 速度调节步长(毫秒)
# 信号
score_changed = pyqtSignal(int)
game_over = pyqtSignal(int)
speed_changed = pyqtSignal(int) # 速度改变信号
# 方向常量
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)
def __init__(self):
super().__init__()
self.init_game()
self.init_ui()
def init_ui(self):
"""初始化UI"""
self.setFixedSize(
self.GRID_WIDTH * self.GRID_SIZE,
self.GRID_HEIGHT * self.GRID_SIZE
)
self.setStyleSheet("background-color: #1a1a1a;")
self.setFocus() # 获取焦点以接收键盘输入
def init_game(self):
"""初始化游戏状态"""
# 蛇的初始位置(从中间开始)
self.snake = [
(self.GRID_WIDTH // 2, self.GRID_HEIGHT // 2),
(self.GRID_WIDTH // 2 - 1, self.GRID_HEIGHT // 2),
(self.GRID_WIDTH // 2 - 2, self.GRID_HEIGHT // 2),
]
# 方向
self.direction = self.RIGHT
self.next_direction = self.RIGHT
# 食物位置
self.food = self.generate_food()
# 分数
self.score = 0
# 游戏速度
self.current_speed = self.GAME_SPEED
# 游戏状态
self.is_running = False
self.is_game_over = False
# 游戏定时器
self.game_timer = QTimer()
self.game_timer.timeout.connect(self.update_game)
def generate_food(self):
"""生成食物位置"""
while True:
x = random.randint(0, self.GRID_WIDTH - 1)
y = random.randint(0, self.GRID_HEIGHT - 1)
if (x, y) not in self.snake:
return (x, y)
def start_game(self):
"""开始游戏"""
if not self.is_running:
self.is_running = True
self.is_game_over = False
self.game_timer.start(self.current_speed)
self.setFocus()
def pause_game(self):
"""暂停游戏"""
if self.is_running:
self.is_running = False
self.game_timer.stop()
def resume_game(self):
"""恢复游戏"""
if not self.is_running and not self.is_game_over:
self.is_running = True
self.game_timer.start(self.current_speed)
def restart_game(self):
"""重新开始游戏"""
self.game_timer.stop()
self.init_game()
self.score_changed.emit(0)
self.speed_changed.emit(self.current_speed)
self.update()
# 重新启动游戏
self.start_game()
def increase_speed(self):
"""增加游戏速度"""
if self.current_speed > self.MIN_SPEED:
self.current_speed = max(self.current_speed - self.SPEED_STEP, self.MIN_SPEED)
self.speed_changed.emit(self.current_speed)
if self.is_running:
self.game_timer.setInterval(self.current_speed)
def decrease_speed(self):
"""降低游戏速度"""
if self.current_speed < self.MAX_SPEED:
self.current_speed = min(self.current_speed + self.SPEED_STEP, self.MAX_SPEED)
self.speed_changed.emit(self.current_speed)
if self.is_running:
self.game_timer.setInterval(self.current_speed)
def update_game(self):
"""更新游戏状态"""
if not self.is_running:
return
# 更新方向
self.direction = self.next_direction
# 计算新的头部位置
head_x, head_y = self.snake[0]
dx, dy = self.direction
new_head = (head_x + dx, head_y + dy)
# 检查碰撞
if self.check_collision(new_head):
self.is_running = False
self.is_game_over = True
self.game_timer.stop()
self.game_over.emit(self.score)
self.update()
return
# 添加新的头部
self.snake.insert(0, new_head)
# 检查是否吃到食物
if new_head == self.food:
self.score += 10
self.score_changed.emit(self.score)
self.food = self.generate_food()
else:
# 移除尾部
self.snake.pop()
self.update()
def check_collision(self, position):
"""检查碰撞"""
x, y = position
# 检查边界碰撞
if x < 0 or x >= self.GRID_WIDTH or y < 0 or y >= self.GRID_HEIGHT:
return True
# 检查自身碰撞
if position in self.snake:
return True
return False
def keyPressEvent(self, event):
"""处理键盘输入"""
if event.isAutoRepeat():
return
key = event.key()
# 使用WASD或方向键控制方向
if key in (Qt.Key_W, Qt.Key_Up):
# 上键:向上
if self.direction != self.DOWN:
self.next_direction = self.UP
elif key in (Qt.Key_S, Qt.Key_Down):
# 下键:向下
if self.direction != self.UP:
self.next_direction = self.DOWN
elif key in (Qt.Key_A, Qt.Key_Left):
# 左键:向左(不用于调速)
if self.direction != self.RIGHT:
self.next_direction = self.LEFT
elif key in (Qt.Key_D, Qt.Key_Right):
# 右键:向右(不用于调速)
if self.direction != self.LEFT:
self.next_direction = self.RIGHT
elif key == Qt.Key_Space:
# 空格键暂停/恢复
if self.is_running:
self.pause_game()
elif not self.is_game_over:
self.resume_game()
elif key == Qt.Key_R:
# R键重新开始
if self.is_game_over:
self.restart_game()
elif key == Qt.Key_Plus or key == Qt.Key_Equal:
# + 或 = 键加速
self.increase_speed()
elif key == Qt.Key_Minus:
# - 键减速
self.decrease_speed()
event.accept()
def paintEvent(self, event):
"""绘制游戏"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 绘制网格背景
self.draw_grid(painter)
# 绘制食物
self.draw_food(painter)
# 绘制蛇
self.draw_snake(painter)
# 如果游戏结束,显示游戏结束提示
if self.is_game_over:
self.draw_game_over(painter)
def draw_grid(self, painter):
"""绘制网格"""
painter.setPen(QPen(QColor(50, 50, 50), 1))
# 绘制竖线
for x in range(self.GRID_WIDTH + 1):
painter.drawLine(
x * self.GRID_SIZE, 0,
x * self.GRID_SIZE, self.GRID_HEIGHT * self.GRID_SIZE
)
# 绘制横线
for y in range(self.GRID_HEIGHT + 1):
painter.drawLine(
0, y * self.GRID_SIZE,
self.GRID_WIDTH * self.GRID_SIZE, y * self.GRID_SIZE
)
def draw_snake(self, painter):
"""绘制蛇"""
# 绘制蛇身
for i, (x, y) in enumerate(self.snake):
if i == 0:
# 蛇头 - 更亮的绿色
painter.fillRect(
x * self.GRID_SIZE + 1,
y * self.GRID_SIZE + 1,
self.GRID_SIZE - 2,
self.GRID_SIZE - 2,
QColor(0, 255, 0)
)
# 绘制眼睛
painter.fillRect(
x * self.GRID_SIZE + 4,
y * self.GRID_SIZE + 4,
3, 3,
QColor(255, 0, 0)
)
else:
# 蛇身 - 稍暗的绿色
painter.fillRect(
x * self.GRID_SIZE + 1,
y * self.GRID_SIZE + 1,
self.GRID_SIZE - 2,
self.GRID_SIZE - 2,
QColor(0, 200, 0)
)
def draw_food(self, painter):
"""绘制食物"""
x, y = self.food
painter.fillRect(
x * self.GRID_SIZE + 3,
y * self.GRID_SIZE + 3,
self.GRID_SIZE - 6,
self.GRID_SIZE - 6,
QColor(255, 0, 0)
)
def draw_game_over(self, painter):
"""绘制游戏结束界面"""
# 半透明黑色背景
painter.fillRect(self.rect(), QColor(0, 0, 0, 200))
# 绘制文本
painter.setPen(QColor(255, 255, 255))
font = QFont("Arial", 30, QFont.Bold)
painter.setFont(font)
text = "游戏结束"
fm = painter.fontMetrics()
text_width = fm.width(text)
text_height = fm.height()
x = (self.width() - text_width) // 2
y = (self.height() - text_height) // 2 - 20
painter.drawText(x, y, text)
# 绘制分数
font.setPointSize(20)
painter.setFont(font)
score_text = f"最终分数: {self.score}"
fm = painter.fontMetrics()
score_width = fm.width(score_text)
score_x = (self.width() - score_width) // 2
score_y = y + 50
painter.drawText(score_x, score_y, score_text)
# 绘制提示
font.setPointSize(12)
painter.setFont(font)
hint_text = "按R键重新开始"
fm = painter.fontMetrics()
hint_width = fm.width(hint_text)
hint_x = (self.width() - hint_width) // 2
hint_y = score_y + 40
painter.drawText(hint_x, hint_y, hint_text)
class SnakeGameWindow(QMainWindow):
"""贪吃蛇游戏窗口"""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("贪吃蛇游戏")
self.setGeometry(200, 200, 700, 550)
# 创建中央控件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建布局
layout = QVBoxLayout(central_widget)
layout.setContentsMargins(10, 10, 10, 10)
# 创建游戏画布
self.game_widget = SnakeGame()
layout.addWidget(self.game_widget)
# 创建控制面板
control_layout = QVBoxLayout()
# 分数标签
self.score_label = QLabel("分数: 0")
self.score_label.setStyleSheet("font-size: 16px; font-weight: bold;")
control_layout.addWidget(self.score_label)
# 速度标签
self.speed_label = QLabel("速度: 正常")
self.speed_label.setStyleSheet("font-size: 14px; color: #0066cc;")
control_layout.addWidget(self.speed_label)
# 提示标签
self.hint_label = QLabel(
"控制方法: W/↑ 上 S/↓ 下 A/← 左 D/→ 右 | 空格暂停 | +/- 调速 | R重新开始"
)
self.hint_label.setStyleSheet("font-size: 12px; color: gray;")
control_layout.addWidget(self.hint_label)
layout.addLayout(control_layout)
# 连接信号
self.game_widget.score_changed.connect(self.update_score)
self.game_widget.game_over.connect(self.on_game_over)
self.game_widget.speed_changed.connect(self.update_speed)
# 设置窗口样式
self.setStyleSheet("""
QMainWindow {
background-color: #f0f0f0;
}
QLabel {
color: #333;
}
""")
# 启动游戏
self.game_widget.start_game()
def update_score(self, score):
"""更新分数显示"""
self.score_label.setText(f"分数: {score}")
def update_speed(self, speed_ms):
"""更新速度显示"""
# 将毫秒转换为速度等级
if speed_ms <= 50:
speed_level = "极快"
elif speed_ms <= 100:
speed_level = "很快"
elif speed_ms <= 150:
speed_level = "正常"
elif speed_ms <= 200:
speed_level = "稍慢"
else:
speed_level = "很慢"
self.speed_label.setText(f"速度: {speed_level} ({speed_ms}ms)")
def on_game_over(self, final_score):
"""游戏结束处理"""
pass # 游戏结束信息已在游戏画布中显示
def keyPressEvent(self, event):
"""处理键盘输入"""
if event.key() == Qt.Key_R:
self.game_widget.restart_game()
else:
super().keyPressEvent(event)

@ -813,6 +813,17 @@ class WordStyleMainWindow(QMainWindow):
# 开发工具菜单
developer_menu = menubar.addMenu('开发工具(Q)')
# 应用选项菜单
app_menu = menubar.addMenu('应用选项(O)')
# 小游戏子菜单
games_menu = app_menu.addMenu('小游戏')
# 贪吃蛇游戏
snake_game_action = QAction('贪吃蛇', self)
snake_game_action.triggered.connect(self.launch_snake_game)
games_menu.addAction(snake_game_action)
# 帮助菜单
help_menu = menubar.addMenu('帮助(H)')
@ -2710,6 +2721,21 @@ class WordStyleMainWindow(QMainWindow):
# 显示替换结果
QMessageBox.information(self, "替换", f"已完成 {count} 处替换。")
def launch_snake_game(self):
"""启动贪吃蛇游戏"""
try:
from ui.snake_game import SnakeGameWindow
# 创建游戏窗口
self.snake_game_window = SnakeGameWindow(self)
self.snake_game_window.show()
except Exception as e:
QMessageBox.critical(
self,
"错误",
f"无法启动贪吃蛇游戏:{str(e)}"
)
def show_about(self):
"""显示关于对话框"""
# 创建自定义对话框

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# test_snake_game.py
"""测试贪吃蛇游戏"""
import sys
import os
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, project_root)
from PyQt5.QtWidgets import QApplication
from src.ui.snake_game import SnakeGameWindow
def main():
app = QApplication(sys.argv)
game_window = SnakeGameWindow()
game_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""
测试贪吃蛇游戏的速度调节功能
"""
import sys
sys.path.insert(0, '/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design')
from src.ui.snake_game import SnakeGame, SnakeGameWindow
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent
def test_snake_game():
"""测试贪吃蛇游戏"""
app = QApplication(sys.argv)
# 创建游戏窗口
window = SnakeGameWindow()
window.show()
# 获取游戏实例
game = window.game_widget
# 测试初始速度
print(f"初始速度: {game.current_speed}ms")
assert game.current_speed == game.GAME_SPEED, "初始速度应该是150ms"
# 测试增加速度(按上键)
print("\n测试增加速度...")
initial_speed = game.current_speed
game.increase_speed()
print(f"按上键后速度: {game.current_speed}ms (从 {initial_speed}ms)")
assert game.current_speed < initial_speed, "速度应该增加(毫秒数减少)"
# 测试降低速度(按下键)
print("\n测试降低速度...")
current_speed = game.current_speed
game.decrease_speed()
print(f"按下键后速度: {game.current_speed}ms (从 {current_speed}ms)")
assert game.current_speed > current_speed, "速度应该降低(毫秒数增加)"
# 测试速度限制
print("\n测试速度限制...")
# 测试最小速度限制
game.current_speed = game.MIN_SPEED
game.increase_speed()
print(f"最小速度限制测试: {game.current_speed}ms (应该 >= {game.MIN_SPEED}ms)")
assert game.current_speed >= game.MIN_SPEED, "速度不应该低于最小值"
# 测试最大速度限制
game.current_speed = game.MAX_SPEED
game.decrease_speed()
print(f"最大速度限制测试: {game.current_speed}ms (应该 <= {game.MAX_SPEED}ms)")
assert game.current_speed <= game.MAX_SPEED, "速度不应该超过最大值"
print("\n✓ 所有测试通过!")
print(f"速度范围: {game.MIN_SPEED}ms - {game.MAX_SPEED}ms")
print(f"速度步长: {game.SPEED_STEP}ms")
window.close()
if __name__ == '__main__':
try:
test_snake_game()
except Exception as e:
print(f"✗ 测试失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
Loading…
Cancel
Save