From db65657f15bb60679263bc78fe1b975c025e95c2 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Thu, 20 Nov 2025 21:56:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=B4=AA=E5=90=83=E8=9B=87?= =?UTF-8?q?=E6=B8=B8=E6=88=8F=E5=8F=8A=E5=85=B6=E6=8E=A7=E5=88=B6=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/snake_game.py | 437 ++++++++++++++++++++++++++++++++++++++++ src/word_main_window.py | 26 +++ test_snake_game.py | 22 ++ test_speed_control.py | 71 +++++++ 4 files changed, 556 insertions(+) create mode 100644 src/ui/snake_game.py create mode 100644 test_snake_game.py create mode 100644 test_speed_control.py diff --git a/src/ui/snake_game.py b/src/ui/snake_game.py new file mode 100644 index 0000000..6c8dd5f --- /dev/null +++ b/src/ui/snake_game.py @@ -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) diff --git a/src/word_main_window.py b/src/word_main_window.py index 7744c81..fab2880 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -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): """显示关于对话框""" # 创建自定义对话框 diff --git a/test_snake_game.py b/test_snake_game.py new file mode 100644 index 0000000..fc416aa --- /dev/null +++ b/test_snake_game.py @@ -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() diff --git a/test_speed_control.py b/test_speed_control.py new file mode 100644 index 0000000..9f08bae --- /dev/null +++ b/test_speed_control.py @@ -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)