文章目录
- 效果图:
- 项目目录结构
- main.py
- game/apple.py
- game/base.py
- game/snake.py
- constant.py
效果图:
项目目录结构
main.py
from snake.game.apple import Apple # 导入苹果类
from snake.game.base import * # 导入游戏基类
from snake.game.snake import Snake # 导入蛇类
class SnakeGame(GameBase):
"""贪吃蛇游戏"""
def __init__(self):
"""初始化游戏"""
super(SnakeGame, self).__init__(
game_name=GAME_NAME, icon=ICON, # 调用基类的初始化方法
screen_size=SCREEN_SIZE,
display_mode=DISPLAY_MODE,
loop_speed=LOOP_SPEED,
font_size=FONT_SIZE,
background=WHITE,
font_name=None
)
# 绘制背景
self.prepare_background()
# 创建游戏对象
self.apple_count = 0 # 苹果计数器
self.high_score = 0 # 记录最高分
self.snake = Snake(self) # 创建蛇对象
self.apple = Apple(self) # 创建苹果对象
# 绑定按键事件
self.add_key_binding(KEY_UP, self.snake.turn, direction=UP) # 绑定上方向键
self.add_key_binding(KEY_DOWN, self.snake.turn, direction=DOWN) # 绑定下方向键
self.add_key_binding(KEY_LEFT, self.snake.turn, direction=LEFT) # 绑定左方向键
self.add_key_binding(KEY_RIGHT, self.snake.turn, direction=RIGHT) # 绑定右方向键
self.add_key_binding(KEY_RESTART, self.restart) # 绑定R键(重启游戏)
self.add_key_binding(KEY_PAUSE, self.pause) # 绑定R键(重启游戏)
self.add_key_binding(KEY_EXIT, self.quit) # 绑定退出键
# 添加绘图函数
self.add_draw_action(self.draw_score) # 添加绘制分数的函数
def prepare_background(self):
"""准备背景"""
self.background.fill(BACKGROUND_COLOR) # 用背景颜色填充背景
for _ in range(CELL_SIZE, SCREEN_WIDTH, CELL_SIZE): # 绘制垂直网格线
self.draw.line(self.background, GRID_COLOR, (_, 0), (_, SCREEN_HEIGHT))
for _ in range(CELL_SIZE, SCREEN_HEIGHT, CELL_SIZE): # 绘制水平网格线
self.draw.line(self.background, GRID_COLOR, (0, _), (SCREEN_WIDTH, _))
def restart(self):
"""重启游戏"""
if not self.snake.is_alive: # 如果蛇已经死亡
self.apple_count = 0 # 重置苹果计数器
self.apple.drop() # 重新放置苹果
self.snake.restart_pawn() # 重生蛇
self.running = True # 继续游戏循环
def draw_score(self):
"""绘制分数"""
text = f"Apple: {self.apple_count}" # 准备要绘制的文本
self.high_score = max(self.high_score, self.apple_count) # 更新最高分
self.draw_text(text, (0, 0), (255, 255, 33)) # 绘制文本
if not self.snake.is_alive: # 如果蛇已经死亡
self.draw_text(" 游戏结束 ", (SCREEN_WIDTH / 2 - 54, SCREEN_HEIGHT / 2 - 10), # 绘制游戏结束文本
(255, 33, 33), WHITE)
self.draw_text(" 按R键重启 ", (SCREEN_WIDTH / 2 - 85, SCREEN_HEIGHT / 2 + 20), # 绘制重启提示文本
GREY, DARK_GREY)
self.draw_text(f"当前最高分: {self.high_score}", (SCREEN_WIDTH / 2 - 114, SCREEN_HEIGHT / 2 + 50), # 绘制最高分文本
(255, 33, 33), WHITE) # 展示最高分
if not self.running and self.snake.is_alive: # 如果游戏暂停且蛇还活着
self.draw_text("游戏暂停 ", (SCREEN_WIDTH / 2 - 55, SCREEN_HEIGHT / 2 - 10), # 绘制游戏暂停文本
LIGHT_GREY, DARK_GREY)
if __name__ == '__main__':
SnakeGame().run() # 运行游戏
game/apple.py
from random import randint
from snake.constant import *
class Apple:
"""
苹果类:表示游戏中的苹果,蛇吃到苹果会增长身体长度。
"""
def __init__(self, game):
"""
初始化苹果对象。
:param game: 游戏对象。
"""
self.game = game
self.x = self.y = 0 # 苹果的初始位置
self.game.add_draw_action(self.draw) # 将 draw 方法添加到游戏的绘制动作列表中
self.drop() # 生成一个新的苹果
def drop(self):
"""
生成一个新的苹果,确保苹果不在蛇的身体上。
"""
snake = self.game.snake.body + [self.game.snake.head] # 获取蛇的身体和头部的所有位置
while True:
(x, y) = randint(0, COLUMNS - 1), randint(0, ROWS - 1) # 随机生成一个位置
if (x, y) not in snake: # 如果这个位置不在蛇的身体上
self.x, self.y = x, y # 将苹果的位置设置为这个位置
break # 退出循环
def draw(self):
"""
绘制苹果。
"""
self.game.draw_cell(
(self.x, self.y), # 苹果的位置
CELL_SIZE, # 每个单元格的大小
APPLE_COLOR_SKIN, # 苹果的外框颜色
APPLE_COLOR_BODY # 苹果的主体颜色
)
game/base.py
import os
import sys
import time
from snake.constant import *
# 使窗口居中
os.environ["SDL_VIDEO_CENTERED"] = "1"
# MyGame 默认值
GAME_NAME = "贪吃蛇 By stormsha"
SCREEN_SIZE = 640, 480
DISPLAY_MODE = pygame.HWSURFACE | pygame.DOUBLEBUF
LOOP_SPEED = 60
# noinspection SpellCheckingInspection
FONT_NAME = "resources/MONACO.ttf"
FONT_SIZE = 16
KEY_PAUSE = pygame.K_PAUSE
class GameBase(object):
"""pygame模板类"""
def __init__(self, **kwargs):
"""初始化
可选参数:
game_name 游戏名称
icon 图标文件名
screen_size 画面大小
display_mode 显示模式
loop_speed 主循环速度
font_name 字体文件名
font_size 字体大小
"""
pygame.init()
pygame.mixer.init()
self.game_name = kwargs.get("game_name") or GAME_NAME
pygame.display.set_caption(self.game_name)
self.screen_size = kwargs.get("screen_size") or SCREEN_SIZE
self.screen_width, self.screen_height = self.screen_size
self.display_mode = kwargs.get("display_mode") or DISPLAY_MODE
self.images = {}
self.sounds = {}
self.musics = {}
self.icon = kwargs.get("icon") or None
self.icon and pygame.display.set_icon(pygame.image.load(self.icon))
self.screen = pygame.display.set_mode(self.screen_size,
self.display_mode)
self.loop_speed = kwargs.get("loop_speed") or LOOP_SPEED
self.font_size = kwargs.get("font_size") or FONT_SIZE
self.background = None
# noinspection SpellCheckingInspection
''' 支持中文的字体
新细明体:PMingLiU
细明体:MingLiU
标楷体:DFKai - SB
黑体:SimHei
宋体:SimSun
新宋体:NSimSun
仿宋:FangSong
楷体:KaiTi
仿宋_GB2312:FangSong_GB2312
楷体_GB2312:KaiTi_GB2312
微软正黑体:Microsoft JhengHei
微软雅黑体:Microsoft YaHei
'''
self.font_name = kwargs.get("font_name") or pygame.font.match_font('SimHei') # 获取系统字体
self.font = pygame.font.Font(self.font_name, self.font_size)
self.clock = pygame.time.Clock()
self.now = 0
self.background_color = kwargs.get("background") or BLACK
self.set_background()
self.key_bindings = {} # 按键与函数绑定字典
self.add_key_binding(KEY_PAUSE, self.pause)
self.game_actions = {} # 游戏数据更新动作
self.draw_actions = [self.draw_background] # 画面更新动作列表
self.running = True
self.draw = pygame.draw
def run(self):
"""主循环"""
while True:
self.now = pygame.time.get_ticks()
self.process_events()
if self.running:
self.update_game_data()
self.update_display()
self.clock.tick(self.loop_speed)
def pause(self):
"""暂停游戏"""
self.running = not self.running
if self.running:
for action in self.game_actions.values():
if action["next_time"]:
action["next_time"] = self.now + action["interval"]
def process_events(self):
"""事件处理"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.quit()
elif event.type == pygame.KEYDOWN:
action, kwargs = self.key_bindings.get(event.key, (None, None))
# noinspection all
action(**kwargs) if kwargs else action() if action else None
def update_game_data(self):
"""更新游戏数据"""
for action in self.game_actions.values():
if not action["next_time"]:
action["run"]()
elif self.now >= action["next_time"]:
action["next_time"] += action["interval"]
action["run"]()
def update_display(self):
"""更新画面显示"""
for action in self.draw_actions:
action()
pygame.display.flip()
def draw_background(self):
"""绘制背景"""
self.screen.blit(self.background, (0, 0))
def add_key_binding(self, key, action, **kwargs):
"""增加按键绑定"""
self.key_bindings[key] = action, kwargs
# TODO: 更新动作若有次序要求,则用字典保存不合适
def add_game_action(self, name, action, interval=0):
"""添加游戏数据更新动作"""
next_time = self.now + interval if interval else None
self.game_actions[name] = dict(
run=action,
interval=interval,
next_time=next_time)
def add_draw_action(self, action):
"""添加画面更新动作"""
self.draw_actions.append(action)
def draw_text(self, text, loc, color, bgcolor=None):
if bgcolor:
surface = self.font.render(text, True, color, bgcolor)
else:
surface = self.font.render(text, True, color)
self.screen.blit(surface, loc)
def draw_cell(self, xy, size, color1, color2=None):
x, y = xy
rect = pygame.Rect(x * size, y * size, size, size)
self.screen.fill(color1, rect)
if color2:
self.screen.fill(color2, rect.inflate(-4, -4))
@staticmethod
def quit():
"""退出游戏"""
pygame.quit()
sys.exit(0)
@staticmethod
def load_music(filename):
pygame.mixer.music.load(filename)
@staticmethod
def play_music():
pygame.mixer.music.play(-1)
@staticmethod
def pause_music():
pygame.mixer.music.pause()
@staticmethod
def resume_music():
pygame.mixer.music.unpause()
@staticmethod
def stop_music():
pygame.mixer.music.stop()
def save_screenshots(self):
filename = time.strftime('screenshots/%Y%m%d%H%M%S.png')
pygame.image.save(self.screen, filename)
def load_images(self, filename, sub_img=None):
sub_img = sub_img or {}
image = pygame.image.load(filename).convert_alpha() # 文件打开失败
for name, rect in sub_img.items():
x, y, w, h = rect
self.images[name] = image.subsurface(pygame.Rect((x, y), (w, h)))
def set_background(self, background=None):
if isinstance(background, str):
self.background = pygame.image.load(background)
else:
self.background = pygame.Surface(self.screen_size)
self.background_color = background \
if isinstance(background, tuple) else (0, 0, 0)
self.background.fill(self.background_color)
def load_sounds(self, **sounds):
"""
@summary: 加载音乐
:param sounds:
:return:
"""
for name, filename in sounds.items():
self.sounds[name] = pygame.mixer.Sound(filename)
def play_sound(self, name):
self.sounds[name].play()
if __name__ == '__main__':
GameBase().run()
game/snake.py
import pygame
from snake import constant
class Snake:
"""贪吃蛇"""
def __init__(self, game):
self.game = game
self.sound_hit = pygame.mixer.Sound("resources/hit.wav")
self.sound_eat = pygame.mixer.Sound("resources/eat.wav")
self.game.add_draw_action(self.draw)
# 初始化数据
self.head = (constant.SNAKE_X, constant.SNAKE_Y) # 蛇头当前位置
self.body = [(-1, -1)] * constant.SNAKE_BODY_LENGTH # 蛇身长度
self.direction = constant.SNAKE_DIRECTION # 当前方向
self.new_direction = constant.SNAKE_DIRECTION # 移动方向
self.speed = constant.SNAKE_SPEED # 移动速度
self.is_alive = True # 是否存活
def set_speed(self, speed):
"""
@summary: 设置蛇的移动速度
:param speed: 移动速度
:return:
"""
self._speed = speed
self.game.add_game_action("snake.move", self.move, 1000 // speed)
def get_speed(self):
"""
@summary: 获取当前蛇的移动速度
:return:
"""
return self._speed
@property
def speed(self):
return self._speed
@speed.setter
def speed(self, speed):
self._speed = speed
self.game.add_game_action("snake.move", self.move, 1000 // speed)
def draw(self):
"""
@summary: 绘制小蛇
:return:
"""
skin_color = constant.SNAKE_COLOR_SKIN if self.is_alive else constant.SNAKE_COLOR_SKIN_DEAD
body_color = constant.SNAKE_COLOR_BODY if self.is_alive else constant.SNAKE_COLOR_BODY_DEAD
head_color = constant.SNAKE_COLOR_HEAD if self.is_alive else constant.SNAKE_COLOR_HEAD_DEAD
for cell in self.body:
self.game.draw_cell(cell, constant.CELL_SIZE, skin_color, body_color)
self.game.draw_cell(self.head, constant.CELL_SIZE, skin_color, head_color)
def turn(self, direction):
"""
@summary: 改变小蛇方向
:param direction:
:return:
"""
if (self.direction in (constant.LEFT, constant.RIGHT) and direction in (constant.UP, constant.DOWN) or
self.direction in (constant.UP, constant.DOWN) and direction in (constant.LEFT, constant.RIGHT)):
self.new_direction = direction
def move(self):
"""
@summary: 移动小蛇
:return:
"""
if not self.is_alive:
return
# 设定方向
self.direction = self.new_direction
# 检测前方
x, y = meeting = (
self.head[0] + self.direction[0],
self.head[1] + self.direction[1]
)
# 死亡判断
if meeting in self.body or x not in range(constant.COLUMNS) or y not in range(constant.ROWS):
self.die()
return
# 判断是否吃了苹果
if meeting == (self.game.apple.x, self.game.apple.y):
self.sound_eat.play()
self.game.apple.drop()
self.game.apple_count += 1
else:
self.body.pop()
# 蛇头变成脖子
self.body = [self.head] + self.body
# 蛇头移动到新位置
self.head = meeting
def restart_pawn(self):
"""重生"""
self.head = (constant.SNAKE_X, constant.SNAKE_Y)
self.body = [(-1, -1)] * constant.SNAKE_BODY_LENGTH
self.direction = constant.SNAKE_DIRECTION
self.new_direction = constant.SNAKE_DIRECTION
self.speed = constant.SNAKE_SPEED
self.is_alive = True
def die(self):
self.sound_hit.play()
self.is_alive = False
constant.py
import pygame
# 颜色设定
BLACK = 0, 0, 0
WHITE = 255, 255, 255
DARK_GREY = 33, 33, 33
GREY = 127, 127, 127
LIGHT_GREY = 192, 192, 192
BACKGROUND_COLOR = BLACK
GRID_COLOR = DARK_GREY
APPLE_COLOR_SKIN = 255, 127, 127
APPLE_COLOR_BODY = 255, 66, 66
SNAKE_COLOR_SKIN = 33, 255, 33
SNAKE_COLOR_BODY = 33, 192, 33
SNAKE_COLOR_HEAD = 192, 192, 33
SNAKE_COLOR_SKIN_DEAD = LIGHT_GREY
SNAKE_COLOR_BODY_DEAD = GREY
SNAKE_COLOR_HEAD_DEAD = DARK_GREY
# 一般设定
GAME_NAME = "SnakeGame"
SCREEN_SIZE = SCREEN_WIDTH, SCREEN_HEIGHT = 640, 480
CELL_SIZE = 20
COLUMNS, ROWS = SCREEN_WIDTH // CELL_SIZE, SCREEN_HEIGHT // CELL_SIZE
DISPLAY_MODE = pygame.HWSURFACE | pygame.DOUBLEBUF
LOOP_SPEED = 60
# noinspection SpellCheckingInspection
FONT_NAME = "resources/MONACO.TTF"
FONT_SIZE = 16
# noinspection SpellCheckingInspection
ICON = "resources/snake.png"
UP, DOWN, LEFT, RIGHT = (0, -1), (0, 1), (-1, 0), (1, 0)
# 按键设定
KEY_EXIT = pygame.K_ESCAPE
KEY_UP = pygame.K_UP
KEY_DOWN = pygame.K_DOWN
KEY_LEFT = pygame.K_LEFT
KEY_RIGHT = pygame.K_RIGHT
KEY_RESTART = pygame.K_r
K_PAUSE = pygame.K_PAUSE
# 蛇的默认值
SNAKE_X = 0
SNAKE_Y = 0
SNAKE_BODY_LENGTH = 5
SNAKE_DIRECTION = RIGHT
SNAKE_SPEED = 10
源码地址:https://gitcode.com/stormsha1/games/overview