GreedSnake_v2.0.py
思路概述: 本次实例用面向对象思想写一个贪吃蛇小游戏.这个游戏总共分为三个大类,蛇类、石头和食物类、按键类. 蛇类又分为蛇头和蛇身两个类继承蛇类,蛇类定义类字段速度字段,控制蛇整体速度,对象属性位置,方向,以及方向属性,拐点列表,蛇头增加蛇头颜色和半径
本次项目难点在于蛇身需要跟随蛇头到指定的拐点再向指定方向转弯,所以拐点列表中的拐点字典存储了该点的位置,以及应该转的方向,因为存在拐点遍历,每一次走到相应拐点都会存在转弯,致使蛇身,脱离蛇头,最后考虑在拐点字典上加了'count'键用来记录改拐点走过的次数,每走过一个身体就+1,如果经过的次数等于蛇身长度,说明整条蛇已经走过这个拐点,于是把这个拐点字典从列表中删除,还可以节约主机的内存
另外就是蛇头吃食物,食物对象是存储在列表中,如果被吃掉时直接删除会导致遍历出问题,所以我使用的办法是先将已经被吃掉的食物半径置为0,然后完成这次遍历,在下次刷新屏幕时再次遍历食物列表,将所有半径为0的食物对象删除,这样就不会影响到比较位置,其余部分逻辑都很简单,不做阐述
import pygame
from random import randint as ri
from math import sqrt as sq
screenWid = 1200 # 窗口大小
screenHet = 800
speed = 1
def randpos(): # 封装产生随机点方法
return (ri(14, screenWid), ri(14, screenHet))
def getDistance(pos1, pos2): # 封装计算两点距离算法
x1, y1 = pos1
x2, y2 = pos2
return sq((x1 - x2)**2 + (y1 - y2)**2)
def showInfo(sc, score, level): # 展示得分和速度等级
font = pygame.font.SysFont('Times', 30)
sInfo = 'Score:' + str(score) + ' Level:' + str(level)
info = font.render(sInfo, True, (0, 0, 0))
sc.blit(info, (800, 2))
class FoodNRock(object): # 食物和石头类(蛇头碰到石头会结束游戏)
def __init__(self, sc, pos):
self.pos = pos
self.xSpd = ri(1, 4)
self.ySpd = ri(1, 4)
radius = ri(1, 4) # 有四种种类的食物
if radius == 1:
self.radius = 8
self.color = (206, 145, 120)
pygame.draw.circle(sc, (206, 145, 120), self.pos, self.radius)
elif radius == 2:
self.radius = 10
self.color = (240, 162, 27)
pygame.draw.circle(sc, (240, 162, 27), self.pos, self.radius)
elif radius == 3:
self.radius = 12
self.color = (197, 34, 192)
pygame.draw.circle(sc, (197, 34, 192), self.pos, self.radius)
else:
self.radius = 14
self.color = (86, 156, 214)
pygame.draw.circle(sc, (86, 156, 214), self.pos, self.radius)
def drawFood(self, sc):
pygame.draw.circle(sc, self.color, self.pos, self.radius)
def foodMove(self): # 食物移动方法
fX, fY = self.pos
if (fX - self.radius) < 0:
fX = self.radius
self.xSpd *= -1
if (fX + self.radius) > screenWid:
fX = screenWid - self.radius
self.xSpd *= -1
if (fY - self.radius) < 0:
fY = self.radius
self.ySpd *= -1
if (fY + self.radius) > screenHet:
fY = screenHet - self.radius
self.ySpd *= -1
fX += self.xSpd
fY += self.ySpd
self.pos = fX, fY
class Snake(object): # 蛇类
speed = 1 # 总体速度字段
def __init__(self, screen):
self.screen = screen
self.pos = (150, 400)
self.xSpd = 1
self.ySpd = 0
self.direction = 'right'
self.turns = []
def move(self): # 蛇的运动方法
pX, pY = self.pos
pX += Snake.speed * self.xSpd
pY += Snake.speed * self.ySpd
self.pos = (pX, pY)
def gameOver(sc): # 游戏结束方法
sc.fill((100, 200, 200))
font = pygame.font.SysFont('Times', 100, bold=1)
title = font.render('Game Over', True, (255, 0, 0))
sc.blit(title, (360, 320))
pygame.display.update()
pygame.time.delay(3000)
exit()
class Shead(Snake): # 蛇头类
def __init__(self, screen):
super().__init__(screen)
self.pos = (150, 400)
self.headCol = (63, 140, 162)
self.radius = 14
def drawShead(self, sc):
pygame.draw.circle(sc, self.headCol, self.pos, self.radius)
def eat(self, food): # 如果两点距离小于半径之和则可以吃掉该食物
if (self.radius + food.radius) > getDistance(self.pos, food.pos):
return True
return False
class Sbody(Snake): # 蛇身类
def __init__(self, screen, lastbody=None, pos=(124, 400)):
super().__init__(screen)
self.bodyCol = (124, 220, 254)
self.radius = 12
self.direction = 'right'
if lastbody: # 判断蛇身最后一个对象的位置和方向,吃掉食物之后新增加的身体在蛇身最后面
if lastbody.direction == 'up':
self.direction = 'up'
pos = (lastbody.pos[0], lastbody.pos[1] + 2 * self.radius)
elif lastbody.direction == 'down':
self.direction = 'down'
pos = (lastbody.pos[0], lastbody.pos[1] - 2 * self.radius)
elif lastbody.direction == 'left':
self.direction = 'left'
pos = (lastbody.pos[0] + 2 * self.radius, lastbody.pos[1])
elif lastbody.direction == 'right':
self.direction = 'right'
pos = (lastbody.pos[0] - 2 * self.radius, lastbody.pos[1])
self.pos = pos
def bodyMove(self): # 身体移动方法
if self.direction == 'up':
self.xSpd = 0
self.ySpd = -1
bX, bY = self.pos
bX += self.xSpd * Snake.speed
bY += self.ySpd * Snake.speed
self.pos = (bX, bY)
elif self.direction == 'down':
self.xSpd = 0
self.ySpd = 1
bX, bY = self.pos
bX += self.xSpd * Snake.speed
bY += self.ySpd * Snake.speed
self.pos = (bX, bY)
elif self.direction == 'left':
self.xSpd = -1
self.ySpd = 0
bX, bY = self.pos
bX += self.xSpd * Snake.speed
bY += self.ySpd * Snake.speed
self.pos = (bX, bY)
elif self.direction == 'right':
self.xSpd = 1
self.ySpd = 0
bX, bY = self.pos
bX += self.xSpd * Snake.speed
bY += self.ySpd * Snake.speed
self.pos = (bX, bY)
def drawbody(self, sc, head=0):
pygame.draw.circle(sc, self.bodyCol, self.pos, self.radius)
@staticmethod
def bodyReSort(bodys, lastbody): # 刷新蛇身方法 未启用
for i in range(len(bodys)):
if i == 0:
if lastbody.direction == 'up':
bodys[i].pos = (
lastbody.pos[0],
lastbody.pos[1] + bodys[i].radius + lastbody.radius)
elif lastbody.direction == 'down':
bodys[i].pos = (
lastbody.pos[0],
lastbody.pos[1] - bodys[i].radius - lastbody.radius)
elif lastbody.direction == 'left':
bodys[i].pos = (
lastbody.pos[0] + bodys[i].radius + lastbody.radius,
lastbody.pos[1])
elif lastbody.direction == 'right':
bodys[i].pos = (
lastbody.pos[0] - bodys[i].radius - lastbody.radius,
lastbody.pos[1])
else:
if lastbody.direction == 'up':
bodys[i].pos = (lastbody.pos[0],
lastbody.pos[1] + 2 * lastbody.radius)
elif lastbody.direction == 'down':
bodys[i].pos = (lastbody.pos[0],
lastbody.pos[1] - 2 * lastbody.radius)
elif lastbody.direction == 'left':
bodys[i].pos = (lastbody.pos[0] + 2 * lastbody.radius,
lastbody.pos[1])
elif lastbody.direction == 'right':
bodys[i].pos = (lastbody.pos[0] - 2 * lastbody.radius,
lastbody.pos[1])
lastbody = bodys[i]
class Key(object): # 按键类
def __init__(self, keyVle, isBan=False):
self.keyVle = keyVle
self.isBan = isBan
def kUPress(self, head, snake, kU, kD, kL, kR): # 按下按键之后关闭朝前和朝后的按键事件触发
head.xSpd = 0
head.ySpd = -1
head.direction = 'up'
snake.turns.append({'pos': head.pos, 'direction': 'up', 'count': 0})
kD.isBan = True
kU.isBan = True
kL.isBan = False
kR.isBan = False
def kDPress(self, head, snake, kU, kD, kL, kR):
head.xSpd = 0
head.ySpd = 1
head.direction = 'down'
snake.turns.append({'pos': head.pos, 'direction': 'down', 'count': 0})
kD.isBan = True
kU.isBan = True
kL.isBan = False
kR.isBan = False
def kLPress(self, head, snake, kU, kD, kL, kR):
head.xSpd = -1
head.ySpd = 0
head.direction = 'left'
snake.turns.append({'pos': head.pos, 'direction': 'left', 'count': 0})
kD.isBan = False
kU.isBan = False
kL.isBan = True
kR.isBan = True
def kRPress(self, head, snake, kU, kD, kL, kR):
head.xSpd = 1
head.ySpd = 0
head.direction = 'right'
snake.turns.append({'pos': head.pos, 'direction': 'right', 'count': 0})
kD.isBan = False
kU.isBan = False
kL.isBan = True
kR.isBan = True
def main(): # 主方法
score = 0
level = 0
pygame.init()
screen = pygame.display.set_caption('GreedsHead')
screen = pygame.display.set_mode((screenWid, screenHet))
screen.fill((255, 255, 255))
kU = Key(273, False)
kD = Key(274, False)
kR = Key(275, False)
kL = Key(276, True)
snake = Snake(screen)
head = Shead(screen)
bodys = [Sbody(screen)]
head.drawShead(screen)
bodys[0].drawbody(screen)
foods = [FoodNRock(screen, randpos())]
pygame.display.flip()
while True:
if score // 25 > level: # 最大等级为5级,每25分升一级,前四级加速
if Snake.speed <= 4:
level += 1
Snake.speed += 1
else:
level = 5
for f in foods: # 删除半径为0的食物
if f.radius == 0:
foods.remove(f)
if len(foods) < 2: # 保持屏幕中有2个食物存在
foods.append(FoodNRock(screen, randpos()))
for evt in pygame.event.get():
if evt.type == pygame.QUIT: # 退出事件
exit()
if evt.type == pygame.KEYDOWN: # 按键事件
if evt.key == kU.keyVle:
if not kU.isBan:
kU.kUPress(head, snake, kU, kD, kL, kR)
break
if evt.key == kD.keyVle:
if not kD.isBan:
kD.kDPress(head, snake, kU, kD, kL, kR)
break
if evt.key == kL.keyVle:
if not kL.isBan:
kL.kLPress(head, snake, kU, kD, kL, kR)
break
if evt.key == kR.keyVle:
if not kR.isBan:
kR.kRPress(head, snake, kU, kD, kL, kR)
break
screen.fill((255, 255, 255))
showInfo(screen, score, level) # 展示得分等信息
for body in bodys:
for bTurn in snake.turns: # 如果哪个身体遇到了拐点就拐弯
if getDistance(body.pos, bTurn['pos']) < Snake.speed: # 调整因为速度增加无法正好到达拐点的情况
body.pos = bTurn['pos'] # 强制更新身体的位置
body.direction = bTurn['direction']
bTurn['count'] += 1 # 计数+1
if bTurn['count'] == len(bodys): # 如果所有身体的点都经过过一次这个拐点,则删除这个拐点
snake.turns.remove(bTurn)
body.bodyMove()
body.drawbody(screen)
lastBody = body # 保留最后一个身体对象
for food in foods:
if level == 5: # 如果等级为5 ,则食物开始移动
food.foodMove()
food.drawFood(screen)
if head.eat(food): # 判断食物是否被吃
score += food.radius // 3 # 食物半径的1/3作为得分
food.radius = 0 # 被吃掉的食物半径为0
bodys.append(Sbody(screen, lastBody)) # 增加一个身体对象
head.move()
head.drawShead(screen)
if head.pos[0] + head.radius > screenWid or head.pos[0] - head.radius < 0 or head.pos[1] - head.radius < 0 or head.pos[1] + head.radius > screenHet: # 判断舌头是否触碰边界,触碰则结束游戏
Snake.gameOver(screen)
pygame.display.update()
if __name__ == '__main__':
main()