Python贪吃蛇双人大战-升级版

Python贪吃蛇双人大战-升级版

在玩了几天贪吃蛇游戏之后,(代码可参考我之前写的 Python贪吃蛇双人大战),家里小朋友提出了新的需求(用户反馈)如下:

  1. 豆子不能出现在豆子上。
  2. 豆子不能出现在蛇身上。
  3. 要显示得分,方便看出来谁的蛇比较长。
  4. 如果蛇头撞到另一条蛇,自己挂掉,变成豆子
  5. 蛇变成的豆子被吃了之后不重新生成
  6. 燃烧自己身体可以加速,每次燃烧身体减少一格,速度提升至两倍

趁着是周末,赶紧实现一下新需求,以满足小朋友的期望。

豆子不出现在重复的位置

为了实现豆子出现位置不与已有的豆子重复,也不与蛇所在位置重复,修改了豆类的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

至此,就可以和我家小朋友再次一起愉快的玩耍了
Python贪吃蛇双人大战-升级版_第1张图片

以上都是我改版需求实现的过程,希望能给大家提供一些帮助和思路,若是自己整理不出来完整的代码(按照我以上的思路应该是可以从之前的版本改出来的),可以到此处下载。

你可能感兴趣的:(Python)