不間斷 Python 挑戰 Day 26 - 專題:貪食蛇(下)

更新於 發佈於 閱讀時間約 15 分鐘

接續前一篇文章,說明貪食蛇遊戲架構的後半部份,來完成整個遊戲的程式設計。

遊戲架構(續)

控制方向

要透過鍵盤來控制貪食蛇移動的方向,需要使用到Turtle模組的按鍵事件方法,也就是透過鍵盤按鍵的按下或釋放來觸發某一段程式執行。在這裡,我們使用鍵盤的上、下、左、右鍵做為觸發事件的按鍵,當這些按鍵按下後,觸發某一段程式,其語法如下:

turtle.listen()
turtle.onkeypress(fun, key)
  • fun – 按鍵按下後執行的函數
  • key – 觸發事件的按鍵

當向上的按鍵按下後,如果當下貪食蛇的方向為向左或向右,在下一步即轉為向上移動,也就是說,如果當下貪食蛇的方向為向上或向下,該事件無效,因為蛇身已經同向,或是不能直接反轉,其它方向的事件以此類推。

首先我們在Snake類別的初始化函數內新增一個移動方向的串列。

# snake.py
self.move_angle_list = []

當上、下、左、右鍵按下後,立即在move_angle_list中新增一個元素,代表下一個要轉向的角度。

# snake.py
def move_up(self):
  self.move_angle_list.append(90)

def move_down(self):
  self.move_angle_list.append(270)

def turn_right(self):
  self.move_angle_list.append(0)

def turn_left(self):
  self.move_angle_list.append(180)

在move()方法中,新增一段程式檢查move_angle_list是否為空串列,若有元素存在,即執行轉向的動作,在下一步改變移動的方向。

# snake.py
def move(self):
  # 檢查是否要轉彎
  while self.move_angle_list:
    next_move_angle = self.move_angle_list.pop(0)
    if next_move_angle != self.head.heading() and \
      next_move_angle != (self.head.heading() + 180) % 360:
      self.head.setheading(next_move_angle)
      break;
    else:
      continue;

# 往前移動
  for body_seg in range(len(self.snake_body) - 1, 0, -1):
    new_x = self.snake_body[body_seg - 1].xcor()
    new_y = self.snake_body[body_seg - 1].ycor()
    self.snake_body[body_seg].goto(new_x, new_y)
  self.head.forward(MOVE_DISTANCE)

在主程式中,則利用listen()及onkeypress()方法,接受上、下、左、右鍵按下的事件。

# 主程式
# 按鍵設定
snake_screen.listen()
snake_screen.onkeypress(snake.move_up, "Up")
snake_screen.onkeypress(snake.move_down, "Down")
snake_screen.onkeypress(snake.turn_left, "Left")
snake_screen.onkeypress(snake.turn_right, "Right")

如此一來便可用鍵盤來控制貪食蛇移動的方向,下一步就可以開始讓貪食蛇吃食物並延長蛇身。

raw-image

檢查是否吃到食物

在遊戲中吃食物必定是用蛇頭去吃,我們可以在每一步去檢查蛇頭和食物的距離,當距離足夠小即表示有吃到食物,這是和蛇相關的行為,因此在Snake類別中新增一個方法來執行這個任務。

# snake.py
def is_collision_with_food(self, food):
  if self.head.distance(food) < 5:
    return True
  return False

在吃食物的同時也會增加身體的長度,同樣在Snake類別中新增一個方法,複製貪食蛇末端的元素到snake_body串列的最後方,當蛇身下一次移動時就會完成延長蛇身的動作。

# snake.py
def extend_snake(self):
  self.add_snake_body(self.snake_body[-1].position())

最後,在主程式的while迴圈中,每一步移動之後即檢查是否吃到食物,若有,則呼叫food.random_food()隨機產生下一個食物,並執行snake.extend_snake()延長蛇身。

# 主程式
# 是否吃到食物
if snake.is_collision_with_food(food):
  food.random_food()
  snake.extend_snake()

執行結果:

raw-image

建立記分板

上一篇文章畫出圍牆的時候,我們有在畫布的上方預留比較大的空間,做為顯示記分板文字的空間,在貪食蛇吃到食物的同時更新分數,例如一次加一分。做為一個相對獨立的功能,我們在snake資料夾內另開一個scoreboard.py檔案,建立Scoreboard類別,繼承自Turtle模組。在初始化函數中移動至適當的位置,設定初始分數為0,並用write()方法寫到畫布上。

# scoreboard.py
from turtle import Turtle

SCORE_COLOR = "white"
SCORE_POSITION = (0, 265)

class Scoreboard(Turtle):

  def __init__(self):
    super().__init__()
    self.score = 0
    self.hideturtle()
    self.penup()
    self.color(SCORE_COLOR)
    self.speed("fastest")
    self.goto(SCORE_POSITION)
    self.write(f"score: {self.score}", False, align="center", font=("Arial", 20, "normal"))

  def get_score(self):
    self.score += 1
    self.clear()
    self.write(f"score: {self.score}", False, align="center", font=("Arial", 20, "normal"))

主程式中建立Scoreboard類別的物件,並在while迴圈中當偵測到蛇頭吃到食物時呼叫scoreboard.get_score()方法更新分數。

# 主程式
# 建立遊戲相關物件
scoreboard = Scoreboard()

# 遊戲主程式
is_game_on = True
while is_game_on:
  # 遊戲畫面更新
  snake_screen.update()
  time.sleep(time_delay)
  snake.move()

  # 是否吃到食物
  if snake.is_collision_with_food(food):
    food.random_food()
    snake.extend_snake()
    scoreboard.get_score()

執行結果:

raw-image

檢查是否撞到牆

程式來到了最後的步驟,要決定遊戲何時該結束,可以歸納出兩個條件:

  1. 蛇頭撞到牆。
  2. 蛇頭撞到身體。

先處理撞到牆的部份,判斷撞到牆的條件是蛇頭超出了圍牆的範圍,前篇文章已知圍牆內緣的座標為:左上(-270, 250)、右上(270, 250)、左下(-270, -270)、右下(270, -270),我們便以此為條件在Snake類別新增is_collision_with_wall()方法來判斷。

# snake.py
def is_collision_with_wall(self, width, height):
  if self.head.xcor() > (width / 2 - 30) or \
    self.head.xcor() < -(width / 2 - 30) or \
    self.head.ycor() > (height / 2 - 50) or \
    self.head.ycor() < -(height / 2 - 30):
    return True
  return False

主程式的while迴圈中,每一次更新位置後呼叫snake.is_collision_with_wall()判斷是否撞牆。

# 主程式
# 是否撞牆
if snake.is_collision_with_wall(width=SCREEN_WIDTH, height=SCREEN_HEIGHT):
  is_game_on = False

執行結果:

raw-image

檢查是否撞到蛇身

要判斷蛇頭是否撞到蛇身,我們將蛇頭和蛇身每一節的距離一一比對,當距離小於20,也就是每前進一次的距離,就表示撞到自己了,當然設定更小也是沒有問題的。

# snake.py
def is_collision_with_body(self):
  for body_seg in self.snake_body[1:]:
    if self.head.distance(body_seg) < 5:
      return True
  return False

在主程式的while迴圈中,同樣在每一次更新位置後呼叫snake.is_collision_with_body(),來判斷是否相撞。

# 主程式
# 是否撞身體
if snake.is_collision_with_body():
  is_game_on = False

執行結果:

raw-image

結束遊戲

為了讓遊戲結束時有明確的標示,讓遊戲看起來更完整,我們讓前面兩個條件成立時進一步在畫布上顯示出"Game Over"字樣以提示遊戲結束,因為性質和記分板類似,我們也在Scoreboard類別中新增此功能。

# scoreboard.py
def game_over(self):
  self.color(GAMEOVER_COLOR)
  self.goto(GAMEOVER_POSITION)
  self.write("Game Over", False, align="center", font=("Arial", 40, "normal"))

最後,在判斷是否相撞的條件內呼叫scoreboard.game_over()方法,整個遊戲便大功告成。

# 主程式
# 遊戲主程式
is_game_on = True
while is_game_on:
  # 遊戲畫面更新
  snake_screen.update()
  time.sleep(time_delay)
  snake.move()

  # 是否吃到食物
  if snake.is_collision_with_food(food):
    food.random_food()
    snake.extend_snake()
    scoreboard.get_score()

  # 是否撞牆
  if snake.is_collision_with_wall(width=SCREEN_WIDTH, height=SCREEN_HEIGHT):
    is_game_on = False
    scoreboard.game_over()

  # 是否撞身體
  if snake.is_collision_with_body():
    is_game_on = False
    scoreboard.game_over()

"Game Over"顯示畫面:

raw-image

程式範例

主程式:https://github.com/wjweng/marathon_python/blob/master/Day1_to_25/marathon_python_day25.py

Snake類別:
https://github.com/wjweng/marathon_python/blob/master/Day1_to_25/snake/snake.py

Food類別:
https://github.com/wjweng/marathon_python/blob/master/Day1_to_25/snake/food.py

Wall類別:
https://github.com/wjweng/marathon_python/blob/master/Day1_to_25/snake/wall.py

Scoreboard類別:
https://github.com/wjweng/marathon_python/blob/master/Day1_to_25/snake/scoreboard.py

留言
avatar-img
留言分享你的想法!
avatar-img
Wei-Jie Weng的沙龍
48會員
36內容數
Wei-Jie Weng的沙龍的其他內容
2022/07/13
對於程式的初學者而言,理解程式的流程、迴圈的進行、或是變數的變化會需要一定程度將程式在腦中進行運算的能力,要一段時間熟悉與適應,尤其是當程式執行的結果不如預期時,往往是計算的過程和自己所想像的不同,這時又更難靠自己的能力找出錯誤。因此,這邊要介紹的這個工具可以將程式執行的過程逐行將變數的變化視覺化地
Thumbnail
2022/07/13
對於程式的初學者而言,理解程式的流程、迴圈的進行、或是變數的變化會需要一定程度將程式在腦中進行運算的能力,要一段時間熟悉與適應,尤其是當程式執行的結果不如預期時,往往是計算的過程和自己所想像的不同,這時又更難靠自己的能力找出錯誤。因此,這邊要介紹的這個工具可以將程式執行的過程逐行將變數的變化視覺化地
Thumbnail
2022/07/13
在上一節介紹了 JSON 資料的基本架構後,我們將改寫並擴充密碼產生器程式,讓它能夠藉由 JSON 的資料結構完成帳密搜尋的功能。
Thumbnail
2022/07/13
在上一節介紹了 JSON 資料的基本架構後,我們將改寫並擴充密碼產生器程式,讓它能夠藉由 JSON 的資料結構完成帳密搜尋的功能。
Thumbnail
2022/06/23
JSON的全名叫JavaScript Object Notation,是由Douglas Crockford所設計的一種資料格式,最初應用在JavaScript程式語言中,做為一種資料交換的格式,而後被廣泛運用在Web開發與NoSQL資料庫,現今已成為一種重要的資料格式。
Thumbnail
2022/06/23
JSON的全名叫JavaScript Object Notation,是由Douglas Crockford所設計的一種資料格式,最初應用在JavaScript程式語言中,做為一種資料交換的格式,而後被廣泛運用在Web開發與NoSQL資料庫,現今已成為一種重要的資料格式。
Thumbnail
看更多
你可能也想看
Thumbnail
TOMICA第一波推出吉伊卡哇聯名小車車的時候馬上就被搶購一空,一直很扼腕當時沒有趕緊入手。前陣子閒來無事逛蝦皮,突然發現幾家商場都又開始重新上架,價格也都回到正常水準,估計是官方又再補了一批貨,想都沒想就立刻下單! 同文也跟大家分享近期蝦皮購物紀錄、好用推薦、蝦皮分潤計畫的聯盟行銷!
Thumbnail
TOMICA第一波推出吉伊卡哇聯名小車車的時候馬上就被搶購一空,一直很扼腕當時沒有趕緊入手。前陣子閒來無事逛蝦皮,突然發現幾家商場都又開始重新上架,價格也都回到正常水準,估計是官方又再補了一批貨,想都沒想就立刻下單! 同文也跟大家分享近期蝦皮購物紀錄、好用推薦、蝦皮分潤計畫的聯盟行銷!
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
【亡靈區,冥路旁的蟲洞區域,蟲洞小徑】 赤足、或爪,輕輕巧巧地踏上蟲洞小徑。然而蟲洞的引力不容小覷,這點「輕盈」仍需強力的時空法術支持,而也就是這一點法術的迴響,傳到了陸安琪的耳裡,也在白熙安的魔力感應中迴盪。  第一位,穿著紅色衣服的,是一隻小巧的老鼠。
Thumbnail
【亡靈區,冥路旁的蟲洞區域,蟲洞小徑】 赤足、或爪,輕輕巧巧地踏上蟲洞小徑。然而蟲洞的引力不容小覷,這點「輕盈」仍需強力的時空法術支持,而也就是這一點法術的迴響,傳到了陸安琪的耳裡,也在白熙安的魔力感應中迴盪。  第一位,穿著紅色衣服的,是一隻小巧的老鼠。
Thumbnail
固定行為模式 許多動物經常會被特定條件觸發特定行為。 例如有一種肉食螢火蟲a會捕食另一種螢火蟲b。 a會在b的繁殖季發出b種雌蟲的光,然後b就會自動飛過去送頭。 另一個例子則是火雞,火雞馬麻會對一種幼火雞特殊的嘰嘰叫產生反應並做出育兒行為。 富有實驗精神的科學家就用火雞天敵——臭鼬的形象做了個會發出
Thumbnail
固定行為模式 許多動物經常會被特定條件觸發特定行為。 例如有一種肉食螢火蟲a會捕食另一種螢火蟲b。 a會在b的繁殖季發出b種雌蟲的光,然後b就會自動飛過去送頭。 另一個例子則是火雞,火雞馬麻會對一種幼火雞特殊的嘰嘰叫產生反應並做出育兒行為。 富有實驗精神的科學家就用火雞天敵——臭鼬的形象做了個會發出
Thumbnail
# 在食蟲植物社團看到版友分享國立自然科學博物館內有食蟲植物進駐販賣的消息,然後就跑了一趟。 # 在攤販前從左邊走到右邊, 再從右邊走回左邊。 一下順時針繞繞, 一下逆時針繞繞。 (繞到店員都跑來關心了XD) # 每一盆都好喜歡~~
Thumbnail
# 在食蟲植物社團看到版友分享國立自然科學博物館內有食蟲植物進駐販賣的消息,然後就跑了一趟。 # 在攤販前從左邊走到右邊, 再從右邊走回左邊。 一下順時針繞繞, 一下逆時針繞繞。 (繞到店員都跑來關心了XD) # 每一盆都好喜歡~~
Thumbnail
一條蛇蜷曲在路上,是草花蛇。 我們馬上路邊停車、下車。 遠遠看過去,小蛇還在緩緩試圖往路邊移動,讓我心存一絲希望。 走近一看,小蛇眼神渙散,只剩下最後幾個呼吸,最後一點力氣,隱隱約約傳來死亡的味道,我知道我們能做的很有限。
Thumbnail
一條蛇蜷曲在路上,是草花蛇。 我們馬上路邊停車、下車。 遠遠看過去,小蛇還在緩緩試圖往路邊移動,讓我心存一絲希望。 走近一看,小蛇眼神渙散,只剩下最後幾個呼吸,最後一點力氣,隱隱約約傳來死亡的味道,我知道我們能做的很有限。
Thumbnail
平常時候帶小孩去公園戶外活動 最怕被蚊子叮咬了 今天就來看關於蚊子的繪本 這隻蚊子已經餓好幾天了 他正在想該吃什麼呢? 他遇到一隻蜘蛛 蜘蛛是可以吃掉蚊子的! 他也餓著肚子,想吃蚊子 等等蜘蛛的後面有一隻鳥 看著蜘蛛,這隻鳥想著等等就可以用餐了🍽 偷轉頭一看後面是一條蛇 結果貪食怪來了!
Thumbnail
平常時候帶小孩去公園戶外活動 最怕被蚊子叮咬了 今天就來看關於蚊子的繪本 這隻蚊子已經餓好幾天了 他正在想該吃什麼呢? 他遇到一隻蜘蛛 蜘蛛是可以吃掉蚊子的! 他也餓著肚子,想吃蚊子 等等蜘蛛的後面有一隻鳥 看著蜘蛛,這隻鳥想著等等就可以用餐了🍽 偷轉頭一看後面是一條蛇 結果貪食怪來了!
Thumbnail
接續前一篇文章,說明貪食蛇遊戲架構的後半部份,來完成整個遊戲的程式設計。
Thumbnail
接續前一篇文章,說明貪食蛇遊戲架構的後半部份,來完成整個遊戲的程式設計。
Thumbnail
#動物溝通個案 #寵物溝通 #爬蟲類 #磨合 [剛來新家的緊張孩子] D小姐透過購買爬蟲類 很期待已久的孩子 透過貨運送到家之後 很兇、很威嚇、感覺很想咬 在我得知之後邀請D小姐是否願意讓我跟她溝通之後同意 拿到照片連線 感受到她很害怕很緊張 他給我看他在陰暗狹小的箱子裡面搖搖晃晃而且緊繃想吐的感受
Thumbnail
#動物溝通個案 #寵物溝通 #爬蟲類 #磨合 [剛來新家的緊張孩子] D小姐透過購買爬蟲類 很期待已久的孩子 透過貨運送到家之後 很兇、很威嚇、感覺很想咬 在我得知之後邀請D小姐是否願意讓我跟她溝通之後同意 拿到照片連線 感受到她很害怕很緊張 他給我看他在陰暗狹小的箱子裡面搖搖晃晃而且緊繃想吐的感受
Thumbnail
哈!天黑了,我可以出來遛躂,找吃的了。就在我身旁,啪!一聲,嚇我一跳,趕快躲進櫃子下面。等了一陣,沒動靜,先探出頭來,沒人,跑跑跑,
Thumbnail
哈!天黑了,我可以出來遛躂,找吃的了。就在我身旁,啪!一聲,嚇我一跳,趕快躲進櫃子下面。等了一陣,沒動靜,先探出頭來,沒人,跑跑跑,
Thumbnail
即使是單細胞生物,也有複雜多變的攝食模式。
Thumbnail
即使是單細胞生物,也有複雜多變的攝食模式。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News