文章目录
- 效果图:
- 项目目录结构
- 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 = gameself.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_PAUSEclass 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_NAMEpygame.display.set_caption(self.game_name)self.screen_size = kwargs.get("screen_size") or SCREEN_SIZEself.screen_width, self.screen_height = self.screen_sizeself.display_mode = kwargs.get("display_mode") or DISPLAY_MODEself.images = {}self.sounds = {}self.musics = {}self.icon = kwargs.get("icon") or Noneself.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_SPEEDself.font_size = kwargs.get("font_size") or FONT_SIZEself.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 = 0self.background_color = kwargs.get("background") or BLACKself.set_background()self.key_bindings = {} # 按键与函数绑定字典self.add_key_binding(KEY_PAUSE, self.pause)self.game_actions = {} # 游戏数据更新动作self.draw_actions = [self.draw_background] # 画面更新动作列表self.running = Trueself.draw = pygame.drawdef 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.runningif 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 allaction(**kwargs) if kwargs else action() if action else Nonedef 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 Noneself.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 = xyrect = pygame.Rect(x * size, y * size, size, size)self.screen.fill(color1, rect)if color2:self.screen.fill(color2, rect.inflate(-4, -4))@staticmethoddef quit():"""退出游戏"""pygame.quit()sys.exit(0)@staticmethoddef load_music(filename):pygame.mixer.music.load(filename)@staticmethoddef play_music():pygame.mixer.music.play(-1)@staticmethoddef pause_music():pygame.mixer.music.pause()@staticmethoddef resume_music():pygame.mixer.music.unpause()@staticmethoddef 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 = rectself.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 pygamefrom snake import constantclass Snake:"""贪吃蛇"""def __init__(self, game):self.game = gameself.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 = speedself.game.add_game_action("snake.move", self.move, 1000 // speed)def get_speed(self):"""@summary: 获取当前蛇的移动速度:return:"""return self._speed@propertydef speed(self):return self._speed@speed.setterdef speed(self, speed):self._speed = speedself.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_DEADbody_color = constant.SNAKE_COLOR_BODY if self.is_alive else constant.SNAKE_COLOR_BODY_DEADhead_color = constant.SNAKE_COLOR_HEAD if self.is_alive else constant.SNAKE_COLOR_HEAD_DEADfor 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) orself.direction in (constant.UP, constant.DOWN) and direction in (constant.LEFT, constant.RIGHT)):self.new_direction = directiondef 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 += 1else:self.body.pop()# 蛇头变成脖子self.body = [self.head] + self.body# 蛇头移动到新位置self.head = meetingdef restart_pawn(self):"""重生"""self.head = (constant.SNAKE_X, constant.SNAKE_Y)self.body = [(-1, -1)] * constant.SNAKE_BODY_LENGTHself.direction = constant.SNAKE_DIRECTIONself.new_direction = constant.SNAKE_DIRECTIONself.speed = constant.SNAKE_SPEEDself.is_alive = Truedef 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