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)
|
||||
@ -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…
Reference in new issue