You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
13 KiB
438 lines
13 KiB
# 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)
|