Python貪喫蛇雙人大戰-升級版
在玩了幾天貪喫蛇遊戲之後,(代碼可參考我之前寫的 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
至此,就可以和我家小朋友再次一起愉快的玩耍了 😃
以上都是我改版需求實現的過程,希望能給大家提供一些幫助和思路,若是自己整理不出來完整的代碼(按照我以上的思路應該是可以從之前的版本改出來的),可以到此處下載。