简介
利用pygame实现,可直接运行。
网上大部分都是面向过程直接设计,利用这次做作业的契机正好写一个面向对象的小demo。
逻辑清晰,更利于新手程序员的理解。
运行效果
源码
import random
import time
import pygame
from pygame.locals import *
from collections import deque
class Config:
"""全局参数集合"""
rate = 0.6 # 界面屏幕比
assert rate <= 1
pygame.init() # pygame 模块的初始化
screen_info = pygame.display.Info() # 获得显示器屏幕信息
block_x = int(20) # 最小模块的宽度
block_y = int(20) # 最小模块的高度
min_x = int(0) # 游戏场地的范围
min_y = int(2)
max_x = int(screen_info.current_w * rate / block_x)
max_y = int(screen_info.current_h * rate / block_y)
screen_width = int(max_x * block_x) # 界面的大小
screen_height = int(max_y * block_y)
Pause = False # 暂停状态标志位
GameOver = False # 游戏结束标志位
@staticmethod
def get_location(l: tuple):
"""通过坐标映射到界面实际位置"""
return l[0] * Config.block_x, l[1] * Config.block_y
@staticmethod
def add(location: tuple, x: int, y: int):
"""位置坐标的改变"""
return location[0] + x, location[1] + y
class Color:
"""各种颜色的RGB值"""
Light = (100, 100, 100)
Dark = (200, 200, 200)
Black = (0, 0, 0)
Back_Ground = (40, 40, 60)
bYellow = (255, 215, 0)
bGreen = (127, 255, 170)
Red = (255, 0, 0)
cGreen = (85, 107, 47)
bRed = (200, 30, 30)
cRed = (255, 240, 245)
Maroon = (128, 0, 0)
DarkCyan = (0, 139, 139)
DodgerBlue = (30, 144, 255)
BlueViolet = (138, 43, 226)
Purple = (128, 0, 128)
HotPink = (255, 105, 180)
class Towards:
"""朝向坐标枚举"""
North = (0, -1)
South = (0, 1)
West = (-1, 0)
East = (1, 0)
class Snake:
"""蛇的主题信息类,包括蛇体坐标队列和蛇头朝向"""
init_place = (int(Config.max_x / 2), int(Config.max_y / 2)) # 初始化位置
def __init__(self):
self.towards = Towards.East # 初始化朝向
self.body = deque() # 因为要频繁添加蛇头和删除蛇尾,故使用双向队列
self.body.append(self.init_place)
self.body.append(Config.add(self.init_place, -1, 0))
self.body.append(Config.add(self.init_place, -2, 0))
self.body.append(Config.add(self.init_place, -3, 0))
def move(self, _food: tuple):
"""蛇体运动一次,会返回蛇体碰撞和获得食物信息"""
new_head = Config.add(self.body[0], *self.towards)
_hit = self.check_hit(new_head)
get_food = _food[0] == new_head[0] and _food[1] == new_head[1]
self.body.appendleft(new_head)
if not get_food:
self.body.pop()
return _hit, get_food
def turn_toward(self, t: tuple):
"""蛇头进行转向"""
if t[0] != self.towards[0] and t[1] != self.towards[1]:
self.towards = t
def check_hit(self, new_head):
"""碰撞检查"""
return new_head in self.body \
or new_head[0] < Config.min_x or new_head[0] >= Config.max_x \
or new_head[1] < Config.min_y or new_head[1] >= Config.max_y
def draw_snake(self, _screen):
"""绘制蛇体与蛇头"""
for s in self.body:
pygame.draw.rect(_screen, Color.bRed, (*Config.get_location(s), Config.block_x, Config.block_y))
pygame.draw.rect(_screen, Color.bYellow, (*Config.get_location(self.body[0]), Config.block_x, Config.block_y))
for s in self.body:
pygame.draw.rect(_screen, Color.Maroon, (*Config.get_location(s), Config.block_x, Config.block_y), 2)
class Food:
"""食物坐标信息"""
# 食物颜色列表
food_color = [Color.Light, Color.DarkCyan, Color.DodgerBlue, Color.HotPink, Color.Purple, Color.Dark]
def __init__(self, snake):
self.pos = (0, 0)
self.create_food(snake)
def create_food(self, snake: deque):
"""随机创建食物坐标,冲突会重新绘制"""
x = random.randint(Config.min_x, Config.max_x - 1)
y = random.randint(Config.min_y, Config.max_y - 1)
while (x, y) in snake:
x = random.randint(Config.min_x, Config.max_x - 1)
y = random.randint(Config.min_y, Config.max_y - 1)
self.pos = (x, y)
def paint_food(self, _screen):
"""绘制食物,每次会随机变色"""
color = Food.food_color[random.randint(0, len(Food.food_color) - 1)]
pygame.draw.rect(_screen, color, (*Config.get_location(self.pos), Config.block_x, Config.block_y))
def draw_window():
"""窗口界面的初始化"""
obj = pygame.display.set_mode((Config.screen_width, Config.screen_height))
pygame.display.set_caption("Greedy Snake")
return obj
def draw_background(_screen):
"""背景的绘制"""
screen.fill(Color.bGreen)
for x in range(Config.min_x * Config.block_x, Config.screen_width, Config.block_x):
pygame.draw.line(_screen, Color.cGreen, (x, Config.min_y * Config.block_y),
(x, Config.screen_height), 1)
for y in range(Config.min_y * Config.block_y, Config.screen_height, Config.block_y):
pygame.draw.line(_screen, Color.cGreen, (Config.min_x * Config.block_y, y),
(Config.screen_width, y), 1)
def draw_score_and_speed(_screen, _score, _speed):
"""分数与速度的绘制"""
text = f" score: {_score} speed: {11 - _speed} press Space to stop or restart"
font = pygame.font.SysFont('SimHei', 30)
_screen.blit(font.render(text, True, Color.Black), (0, 0))
def init(_screen):
"""初始化界面"""
text = "Press Space to start"
size = int(Config.screen_width / 20)
font = pygame.font.SysFont('SimHei', size)
tx, ty = font.size(text)
screen.fill(Color.bGreen)
_screen.blit(font.render(text, True, Color.Black),
((Config.screen_width - tx) // 2, (Config.screen_height - ty) // 2))
pygame.display.update()
Config.GameOver = False
Config.Pause = True
wait_until_no_pause()
def GameOver(_screen):
"""游戏结束界面"""
text1 = "Game Over!"
text2 = "Press Q to quit or space restart"
size = int(Config.screen_width / 10)
font1 = pygame.font.SysFont(None, size)
font2 = pygame.font.SysFont(None, size // 2)
tx1, ty1 = font1.size(text1)
tx2, ty2 = font2.size(text2)
screen.fill(Color.bRed)
_screen.blit(font1.render(text1, True, Color.Black),
((Config.screen_width - tx1) // 2, (Config.screen_height - ty1) // 2))
_screen.blit(font2.render(text2, True, Color.Black),
((Config.screen_width - tx2) // 2, (Config.screen_height - ty2) // 3 * 2))
pygame.display.update()
while True: # 等待按键响应
event = pygame.event.wait()
if event.type == pygame.QUIT:
shutdown()
elif event.type == KEYDOWN and event.key == K_SPACE:
return False
elif event.type == KEYDOWN and event.key == K_q:
return True
def shutdown():
"""结束程序"""
pygame.quit()
exit(1)
def change_speed(_score: int):
"""速度改变函数"""
ret = 10 - int(_score / 10)
if ret < 4:
ret = 4
return ret
def wait_until_no_pause():
"""暂停等待函数"""
if not Config.Pause:
return
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
shutdown()
elif event.type == KEYDOWN and event.key == K_SPACE:
Config.Pause = False
return
def paint_speed_control(last_time, _speed):
"""判断是否到达刷新时间"""
target_time = last_time + 0.02 * _speed
if target_time <= time.time():
return True
return False
def event_loop(snake):
"""按键事件监视"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
shutdown()
elif event.type == KEYDOWN:
if event.key == K_SPACE:
Config.Pause = True
elif event.key in (K_UP, K_w):
snake.turn_toward(Towards.North)
elif event.key in (K_DOWN, K_s):
snake.turn_toward(Towards.South)
elif event.key in (K_LEFT, K_a):
snake.turn_toward(Towards.West)
elif event.key in (K_RIGHT, K_d):
snake.turn_toward(Towards.East)
def run(_screen):
"""逻辑运行函数"""
snake = Snake()
food = Food(snake.body)
score = 0
speed = 10
last_flush_time = time.time()
draw_background(screen)
pygame.display.update()
while not Config.GameOver:
wait_until_no_pause()
while not Config.Pause:
if paint_speed_control(last_flush_time, speed): # 到达刷新时间后重新绘制屏幕
last_flush_time = time.time()
draw_background(_screen)
snake.draw_snake(_screen)
food.paint_food(_screen)
draw_score_and_speed(_screen, score, speed)
pygame.display.update()
Config.GameOver, food_flush = snake.move(food.pos)
if Config.GameOver:
return
if food_flush:
food.create_food(snake.body)
score += 1
speed = change_speed(score)
event_loop(snake) # 为了即时的控制
if __name__ == '__main__':
screen = draw_window()
while True:
init(screen)
run(screen)
if GameOver(screen): # 判断是否要重新开始游戏
break
shutdown()