在玩了几天贪吃蛇游戏之后,(代码可参考我之前写的 Python贪吃蛇双人大战),家里小朋友提出了新的需求(用户反馈)如下:
趁着是周末,赶紧实现一下新需求,以满足小朋友的期望。
为了实现豆子出现位置不与已有的豆子重复,也不与蛇所在位置重复,修改了豆类的generate函数如下:
def generate(self, snake1, snake2):
while self.curNum < self.totalNum:
x = random.randrange(0,SCREEN_WIDTH/GRID_SIZE)
y = random.randrange(0,SCREEN_HEIGHT/GRID_SIZE)
newBeanPos = [int(x*GRID_SIZE),int(y*GRID_SIZE)]
#检查豆子位置是否重复
isBeanRepeated = False
for bean in self.beans: #豆子不能与豆子位置重复
if bean.pos == newBeanPos:
isBeanRepeated = True
break
for pos in snake1.segments: #豆子不能与蛇位置重复
if pos == newBeanPos:
isBeanRepeated = True
break
for pos in snake2.segments: #豆子不能与蛇位置重复
if pos == newBeanPos:
isBeanRepeated = True
break
#新生成的豆子在不重复的地方
if not isBeanRepeated:
self.beans.append(Bean(self.color, newBeanPos))
self.curNum = self.curNum + 1
因为要判断蛇位置,所以把蛇作为参数传进去了,增加了两个参数。函数中每次生成豆子的位置之后进行判断,不重复再增加。
当然,因为改了函数接口,所以主循环中调用的地方也要增加相对应的参数,相关代码如下:
# 初始化豆子
yellowBeans = Beans(YELLOW, BEAN_NUM)
yellowBeans.generate(snake1, snake2)
# 如果豆子被吃掉,则重新生成豆子,否则蛇长度减少一格
if yellowBeans.beEaten(snake1.headPos):
yellowBeans.generate(snake1, snake2)
else:
snake1.pop()
if yellowBeans.beEaten(snake2.headPos):
yellowBeans.generate(snake1, snake2)
else:
snake2.pop()
显示得分这个功能比较简单,就用蛇增加的长度表示得分即可,而蛇的长度就是蛇类中segments的长度。于是创建一个函数getLen直接返回segments的长度。
def getLen(self):
return len(self.segments)
在主循环中创建分数显示层,设置其字体大小如下:
# 分数显示层字体大小
scoreFont = pygame.font.Font(None, int(32*GRID_SIZE/20))
并在刷新时增加分数层的刷新:
# 绘制刷新
playSurface.fill(BLACK) # 绘制pygame显示层
yellowBeans.show(playSurface)
snake1.show(playSurface)
snake2.show(playSurface)
# 显示分数
scoreSurf = scoreFont.render('P1 Score:{} vs P2 Score:{}'.format(snake1.getLen()-3, snake2.getLen()-3), True, WHITE)
scoreRect = scoreSurf.get_rect()
scoreRect.midtop = (SCREEN_WIDTH/2, 0)
playSurface.blit(scoreSurf, scoreRect)
pygame.display.flip() # 刷新pygame显示层
当然,为了显示分数,需要最上面一行不产生豆子,所以修改豆类generate函数中的y值如下:
y = random.randrange(1,SCREEN_HEIGHT/GRID_SIZE)
这个功能相对而言就复杂一些了,同时考虑到蛇变成的豆子被吃了之后不重新生成,于是重新定义了一个蛇豆类如下:
class SnakeBeans:
def __init__(self, snake):
self.color = NORMAL_BEAN_COLOR
self.beans = []
for pos in snake.segments:
self.beans.append(Bean(NORMAL_BEAN_COLOR, pos))
def beEaten(self, snakePos):
for bean in self.beans:
if bean.beEaten(snakePos):
self.beans.remove(bean)
return True
return False
def show(self, playSurface):
for bean in self.beans:
pygame.draw.rect(playSurface,self.color,Rect(bean.pos[0],bean.pos[1],GRID_SIZE,GRID_SIZE))
因为蛇豆不需要重生,所以比较简单,能被吃就可以了。
为了实现蛇撞挂掉,修改了蛇类的 moveAndAdd 函数,增加了检查是否撞到另一条蛇代码,传入另一条蛇作为参数,返回是否撞到。
def moveAndAdd(self, snake=[]):
# 根据方向移动蛇头的坐标
if self.direction == 1:
self.headPos[0] += GRID_SIZE
if self.direction == -1:
self.headPos[0] -= GRID_SIZE
if self.direction == -2:
self.headPos[1] -= GRID_SIZE
if self.direction == 2:
self.headPos[1] += GRID_SIZE
#检查蛇头是否撞到另一条蛇身
if snake == []:
self.segments.insert(0,list(self.headPos)) # 在蛇头插入一格
return False
isSnakeHit = False
for pos in snake.segments:
if self.headPos == pos:
isSnakeHit = True
break
if not isSnakeHit:
self.segments.insert(0,list(self.headPos)) # 在蛇头插入一格
return isSnakeHit
于是在主循环中就可以判断是否撞到,撞到则爆豆子,自己再重生:
if snake1.moveAndAdd(snake2): #撞到了另一条蛇身
snake1Beans = SnakeBeans(snake1)
snake1.respawn()
continue
然后这时候发现重生出问题了,屏幕上东西太多,随机位置重生容易冲突,所以决定单独提取一个函数出来判断是否有空位置,如下:
def isEmptyInArea(area, normalBeanss=[], snakeBeanss=[], snakes=[]):
''' area是位置的列表
'''
for pos in area:
if normalBeanss != []:
for nbs in normalBeanss:
for nb in nbs.beans:
if nb.pos == pos:
return False
if snakeBeanss != []:
for sbs in snakeBeanss:
if sbs != []:
for sb in sbs.beans:
if sb.pos == pos:
return False
if snakes != []:
for snake in snakes:
for snakePos in snake.segments:
if snakePos == pos:
return False
return True
输入参数area是位置的列表,normalBeanss是普通豆子的列表,snakeBeanss是蛇豆的列表,snakes是蛇的列表。这个函数思路很简单,就是拿area中的一个点一个点去判断是否跟其他几个列表中的位置冲突。
有了这个函数之后,一些代码就可以重写了,于是把蛇类的respawn函数改写如下,提取了一个generate函数出来,在初始化函数中也能用。
def __init__(self, color, headColor, ctrlKeys):
self.color = color
self.headColor = headColor
self.ctrlKeys = ctrlKeys #按[上,下,左,右]的顺序
self.segments = []
self.generate()
def generate(self):
x = random.randrange(SNAKE_INIT_POS_MARGIN, SCREEN_WIDTH/GRID_SIZE-SNAKE_INIT_POS_MARGIN)
y = random.randrange(SNAKE_INIT_POS_MARGIN, SCREEN_HEIGHT/GRID_SIZE-SNAKE_INIT_POS_MARGIN)
self.direction = random.choice([-2,2,-1,1]) #([-2,2,-1,1]) # 方向[-2,2,-1,1]分别表示[上,下,左,右]
self.headPos = [int(x*GRID_SIZE), int(y*GRID_SIZE)]
self.segments.clear()
self.segments.insert(0, list(self.headPos))
for i in range(SNAKE_INIT_LEN-1):
self.moveAndAdd()
def respawn(self, normalBeanss=[], snakeBeanss=[], snakes=[]):
self.generate()
while not isEmptyInArea(self.segments, normalBeanss, snakeBeanss, snakes):
self.generate()
最后来实现燃烧自己身体来加速的功能。给蛇类增加两个成员变量:
self.vel = SNAKE_VEL_SLOW
self.velCount = 0
修改之前的changeDirection函数为handleKey,增加了加速键的处理,速度分两档SNAKE_VEL_FAST和SNAKE_VEL_SLOW,并设置了加速的时间SNAKE_ACC_TIME:
def handleKey(self, pressKey):
directions = [-2,2,-1,1] #([-2,2,-1,1]) # 方向[-2,2,-1,1]分别表示[上,下,左,右]
for direct, key in zip(directions, self.ctrlKeys[:4]):
if key == pressKey and direct + self.direction != 0:
self.direction = direct
if pressKey == self.ctrlKeys[4] and self.vel != SNAKE_VEL_FAST: #按下了加速键
if self.getLen() > SNAKE_INIT_LEN:
self.pop() #燃烧自己
self.vel = SNAKE_VEL_FAST #来加速
self.velCount = SNAKE_ACC_TIME
初始化时需要修改,首先需要增加加速按键:
ctrlKeys1 = [ord('w'),ord('s'),ord('a'),ord('d'),ord('g')]
ctrlKeys2 = [K_UP,K_DOWN,K_LEFT,K_RIGHT,ord('m')]
然后增加辅助变量:
snake1VelCount = 1
snake2VelCount = 1
主循环中涉及到蛇运动处理的地方都加上判断前提,如:
if snake1VelCount > 1:
if snake1.moveAndAdd(snake2): #撞到了另一条蛇身
snake1Beans = SnakeBeans(snake1)
snake1.respawn([normBeans],[snake1Beans,snake2Beans],[snake2])
continue
最后增加辅助变量的处理,以实现加速功能:
if snake1VelCount > 1:
if snake1.vel == SNAKE_VEL_FAST:
snake1VelCount = 2
else:
snake1VelCount = 1
else:
snake1VelCount = snake1VelCount + 1
以上都是我改版需求实现的过程,希望能给大家提供一些帮助和思路,若是自己整理不出来完整的代码(按照我以上的思路应该是可以从之前的版本改出来的),可以到此处下载。