程序員如何用Python10分鐘繪製貪吃蛇小遊戲?

貪吃蛇是一款經典的益智遊戲,有PC和手機等多種版本,既簡單又耐玩。玩家通過上下左右鍵控制蛇的方向,尋找食物,每吃到一次食物,就能得到一定的積分,而且蛇的身體會越來越長。隨著蛇的身體變長,遊戲的難度就會變大。當蛇碰到四周的牆壁,或者碰到自己的身體的某一個部位的時候,遊戲就結束了。

我們來看一下用Python編寫這款遊戲的主要思路。帶上娃一起編程既好玩又增長知識。

程序員如何用Python10分鐘繪製貪吃蛇小遊戲?

地圖

我們將整個遊戲界面看成是由許多個小方塊組成的,每個方塊代表一個單位。這樣一來,遊戲界面就由若干個小方塊形成一個地圖,地圖上的每個位置都可以表示為小方塊的整數倍,如圖1所示。

程序員如何用Python10分鐘繪製貪吃蛇小遊戲?

圖1

貪吃蛇的長度也用這個小方塊來表示,每次吃到食物,蛇身的長度就會增加一個單位。

程序界面

這是一款完整的遊戲,所以我們一共為其設計了3個界面,除了遊戲界面以外,還包括遊戲開始界面和遊戲結束界面。

自定義函數

我們要創建的函數包括:main(主程序)、startGame(遊戲開始)、runGame(運行遊戲)、drawFood(繪製食物)、drawSnake(繪製貪吃蛇)、drawScore(繪製成績)、moveSnake(移動貪吃蛇)、isEattingFood(是否吃到食物)、isAlive(判斷貪吃蛇是否死掉了)、gameOver(遊戲結束)和terminate(終止遊戲)。

事件

我們要用到的事件是鍵盤事件。鍵盤事件是玩家操控貪吃蛇移動的時候發生的事件。我們會在後面介紹的startGame()函數和gameOver()函數中監聽鍵盤事件,並且根據事件類型,來做相應的處理。

聲音

我們會在遊戲開始後,調用Sound對象的play()方法,播放的背景音樂。

程序員如何用Python10分鐘繪製貪吃蛇小遊戲?

點擊購書 每買100減50元

導入模塊


首先,將在程序中用到的模塊都導入。我們要使用Pygame的函數,因此需要導入Pygame模塊。除此之外,我們還會用到sys模塊和random模塊。sys模塊負責程序與Python解釋器的交互,用於操控Python運行時的環境,程序要使用sys模塊的exit()函數來退出遊戲。random模塊用於生成隨機數。導入這3個模塊的語句如下所示:

初始化設置


為了進行遊戲啟動和運行前的準備工作,程序需要做一些初始化設置工作,包括定義程序要用到的顏色、方向變量,確定遊戲窗口和地圖的大小,定義遊戲需要用到的一些其他變量等。先來看看這部分初始化代碼。定義顏色變量

遊戲中要用到的顏色主要包括如下幾種。

定義方向變量

為了能夠讓玩家能夠操控貪吃蛇的方向,我們在程序中定義了4個方向變量,分別和玩家操控貪吃蛇移動的上下左右相對應。

定義窗口大小

我們要為貪吃蛇遊戲定義一個窗口,讓貪吃蛇在這個窗口中移動。我們通過兩個變量來定義窗口的寬和高,這是一個寬800像素、高600像素的矩形窗口。

定義地圖大小

我們使用變量mapWidth 和mapHeight 來表示地圖的寬和高。需要注意的是,地圖的寬和高都是基礎單位cellSize的整數倍。

其他變量

程序還會用到如下兩個變量:

基礎函數

程序最終要以通過函數來定義所要執行的功能,並且通過函數調用來完成和實現這些功能。前面介紹了貪吃蛇這款遊戲需要定義的函數,這些函數是遊戲程序的核心代碼,接下來,我們依次來看看這些函數是如何實現的。

main()函數

main()函數是程序執行的入口。先來看一下main()函數的詳細代碼。

首先,初始化Pygame,調用pygame.init()函數進行模塊初始化。然後調用了pygame.display.set_mode()函數,創建了一個寬800像素、高600像素的顯示窗口,返回了用於該窗口的pygame.Surface 對象並將其存儲在名為screen的變量中。接下來,調用pygame.display.set_caption()函數來設置窗口的標題,這裡給遊戲窗口起名為“貪吃蛇”。調用screen.fill()函數,將窗口用白色填充。然後創建了一個時鐘(Clock)對象,將其賦值給snakeSpeedClock變量,用它來控制幀速率。

然後,調用startGame這個自定義函數,這是負責啟動遊戲的函數,給它傳遞的參數是變量screen,後面的小節會詳細介紹這個函數。

接下來,為了保持Pygame事件循環一直運行,我們使用while循環,並且循環條件就是布爾值True,這表示循環會一直進行,而退出這個循環的唯一方式是程序終止。在本書前面的示例中,我們通常都會使用一個變量作為循環條件,當程序退出時,修改這個變量來結束循環。這兩種做法的效果是相同的,相比之下,這裡的用法要更加簡單一些。

在循環體中,我們先初始化混合器,然後將一段背景音樂snake.wav加載到一個Sound對象中,並且將其存儲到變量music中。接下來調用play()函數播放音樂,參數−1表示會一直循環播放。通過上述代碼,我們就為遊戲添加了背景音樂。然後調用runGame函數,傳遞給它的參數是變量screen和snakeSpeedClock,這個函數負責遊戲運行,後面的小節還會詳細介紹這個函數。當這個函數執行完後,就會調用music.stop()函數來停止背景音樂播放。然後,調用gameOver函數結束遊戲,傳遞給它的參數是變量screen。

startGame()函數

這個函數負責控制我們的程序啟動,它接收的參數是窗口的pygame.Surface對象。我們來看一下該函數的代碼。

首先,調用image()函數在Pygame窗口中加載“gameStart.png”圖片,並創建一個名為gameStart的Surface對象。然後,調用blit()函數,將像素從一個Surface複製到另一個Surface之上。就是把gameStart對象複製到Screen這個Surface上。通過blit()將gameStart複製到指定位置(70, 30)。然後,調用pygame.font.SysFont函數來創建Font對象,並將其賦值給名為font的變量,這個對象允許我們以40點的SimHei字體繪製到Pygame的Surface上。接下來,在所創建的font對象上使用render()命令,把字符串“按任意鍵開始遊戲”繪製到Surface上。然後,調用blit()函數,將像素從一個Surface複製到另一個Surface之上。screen.blit(tip, (240, 550))負責把tip對象複製到screen這個Surface上的指定位置。調用pygame.display.update()函數,把繪製到Surface對象上的所有內容都顯示到窗口上。接下來,為了保持Pygame事件循環一直運行,我們使用while循環。在事件循環中,判斷事件類型如果是QUIT(關閉窗口),就調用terminate()函數終止程序,我們稍後會介紹terminate()函數。否則,如果事件類型是KEYDOWN,那麼事件對象將有一個key屬性來識別按下的是哪個鍵。如果key屬性等於K_ESCAPE,表示用戶按下的是Esc鍵,意味著玩家希望結束程序,那麼程序的處理方式和點擊關閉窗口一樣,調用terminate()函數終止程序。否則,表示用戶按下的是其他鍵,退出這個函數,表示遊戲開始運行。當這個函數執行後,會出現如圖1所示的遊戲界面,這個時候,玩家可以按Esc鍵關閉程序,如果按其他的任意鍵則會開始玩遊戲。runGame()函數這個函數控制著遊戲程序運行,它接受的參數是窗口的pygame.Surface對象和Pygame的時鐘對象。runGame()函數的代碼如下所示:

首先使用random.randint()函數在3到mapWidth−8之間選取一個隨機整數賦值給變量startX,在3到mapHeight−8之間選取一個隨機整數賦值給變量startY,這兩個變量分別表示貪吃蛇初始的x座標和y座標。選取隨機數的目的是讓貪吃蛇出現的位置不是固定的,這樣就增加了遊戲的不確定性。然後,用嵌套字典的一個列表來表示貪吃蛇。每個字典表示地圖上的一個座標,{'x': startX, 'y': startY}表示蛇頭的位置,{'x': startX - 1, 'y': startY}和{'x': startX - 2, 'y': startY}表示蛇的身體。這是一條水平放置的蛇,蛇頭靠右,有兩節蛇身。將direction設置為RIGHT,表示方向向右。RIGHT是我們前面定義過的方向變量。然後使用random.randint()函數在0到mapWidth−1之間選取一個隨機整數作為字典中x鍵的值,在0到mapHeight−1之間選取一個隨機整數作為字典中y鍵的值,將字典賦值給變量food。用這個變量表示食物的座標位置。接下來,為了保持Pygame事件循環一直運行,我們使用while循環。在事件循環中,判斷事件類型,如果是QUIT,那麼調用terminate()函數終止程序。否則,如果事件類型是KEYDOWN,那麼事件對象將有一個key屬性來識別按下的是哪個鍵。如果key屬性等於K_LEFT,表示用戶按下的是向左方向鍵,並且direction != RIGHT,那麼將變量direction設置為LEFT,也就是將方向設置為向左。這裡,direction != RIGHT的含義是蛇頭不向右,因為蛇頭向右的話,是沒有辦法再將方向設置為向左的(因為要避免貪吃蛇直接掉頭導致頭部和身體相碰撞的情況),只有蛇頭向上、向下或向左的時候,我們才可以將蛇頭方向設置為向左。如果key屬性等於K_RIGHT,並且direction != LEFT,那麼將變量direction設置為RIGHT;如果key屬性等於K_UP,並且direction != DOWN,那麼將變量direction設置為UP;如果key屬性等於K_DOWN,並且direction !=UP,那麼將變量direction設置為DOWN。如果key屬性等於K_ESCAPE,表示用戶按下的是Esc 鍵,意味著玩家希望結束程序,那麼處理方式和玩家點擊關閉窗口是一樣的,調用terminate()函數終止程序。然後,調用moveSnake()函數移動貪吃蛇,該函數的參數是變量direction和snakeCoords,稍後我們還會詳細介紹moveSnake()函數。接下來調用isEattingFood()函數,判斷貪吃蛇是否吃到食物,該函數參數是變量snakeCoords和 food,稍後我們還會詳細介紹isEattingFood()函數。然後調用isAlive()函數,判斷貪吃蛇是否死亡,該函數的參數是變量snakeCoords,並且將它的返回結果賦值給變量ret,稍後會詳細介紹isAlive()函數。判斷變量ret是否是True,如果不是,跳出while循環,表示貪吃蛇已經死了,遊戲結束。調用image.load()函數加載遊戲背景圖片,並創建一個名為gameRun的Surface對象。調用blit()函數,把gameRun對象複製到screen這個Surface上,指定位置是左上角。然後調用drawFood()函數繪製食物,該函數的參數是變量screen和food,稍後我們還會介紹drawFood()函數。然後調用drawSnake()函數繪製貪吃蛇,該函數的參數是變量screen和snakeCoords,稍後我們還會詳細介紹drawFood()函數。接下來調用drawScore()函數繪製分數,該函數的參數是變量screen和len(snakeCoords) – 3的結果。len(snakeCoords)表示貪吃蛇的長度。減去3,是因為貪吃蛇最初有一個蛇頭和兩節身體,也就是snakeCoords初始有3個元素,減去3就是新增的身體部分,也就是相應的得分。調用pygame.display.update()函數,把繪製到Surface對象上的所有內容,都顯示到窗口上。調用時鐘對象的tick()方法,表示遊戲運行的幀速率是snakeSpeed FPS,即每秒snakeSpeed次。當runGame()函數執行後,會出現如圖3所示的遊戲界面。

程序員如何用Python10分鐘繪製貪吃蛇小遊戲?

圖3

下面分別介紹一下runGame()函數中用到的其他的自定義函數。drawFood()函數drawFood()函數用來繪製食物,它接受的參數是窗口的pygame.Surface對象和表示座標的字典對象。drawFood()函數的代碼如下所示。

將字典food的鍵“x”對應的值乘以變量cellSize的結果賦值給變量x,將字典food的鍵“y”對應的值乘以變量cellSize的結果賦值給變量y。因為food的座標是相對於地圖上的座標,而不是真正窗口的座標,只有在乘以cellSize後才能夠得到窗口上對應的像素位置。然後,調用pygame.draw.rect()函數繪製用黃色填充的一個小方塊。drawSnake()函數drawSnake()函數用來繪製貪吃蛇,它接受的參數是窗口的pygame.Surface對象和表示貪吃蛇的列表。drawSnake()函數的代碼如下所示。

用一個for循環來遍歷snakeCoords列表中所有的元素,把每個元素賦值給變量coord。在每個循環體中,將字典coord的鍵“x”對應的值乘以變量cellSize,再把結果賦值給變量x,將字典coord的鍵“y”對應的值乘以變量cellSize,再把結果賦值給變量y。變量x和y對應的是窗口上的像素位置。然後調用pygame.draw.rect()函數繪製一個用深綠色填充的小方塊,再次調用pygame.draw.rect()函數在深綠色方塊中繪製一個淺綠色的小方塊。一個大方塊和一個小方塊,一起構成了蛇的一節身體。drawScore()函數drawScore()函數用來繪製分數,它接受的參數是窗口的pygame.Surface對象和表示分數的變量。drawScore()函數的代碼如下所示。

調用pygame.font.SysFont()函數,把字符串"得分:"以及變量score的值繪製到界面上,以抗鋸齒方式繪製,文本顏色為白色,將生成的這個Font對象賦值給變量scoreSurf。然後獲取scoreSurf的矩形對象並將其賦值給變量scoreRect。指定scoreRect的左上角的座標為(windowsWidth - 200, 50)。然後,調用blit()函數,把scoreSurf對象複製到screen這個Surface上。moveSnake()函數moveSnake()函數用來移動貪吃蛇,它接受的參數是表示方向的變量和表示貪吃蛇的列表。moveSnake()函數會根據方向,來增加一個蛇頭的元素到列表中。moveSnake()函數的代碼如下所示。

如果變量direction等於UP,表示貪吃蛇的方向是向上,那麼創建一個新的蛇頭元素,“x”鍵的值是原來蛇頭的“x”鍵的值不變,“y”鍵的值是原來蛇頭的“y”鍵的值減去1個單位。

否則,如果變量direction等於DOWN,表示貪吃蛇的方向是向下,那麼創建一個新的蛇頭元素,“x”鍵的值是原來蛇頭的“x”鍵的值不變,“y”鍵的值是原來蛇頭的“y”鍵的值加上1個單位。

否則,如果變量direction等於LEFT,表示貪吃蛇的方向是向左,那麼創建一個新的蛇頭元素,“x”鍵的值是原來蛇頭的“x”鍵的值減去1個單位,“y”鍵的值是原來蛇頭的“y”鍵的值不變。

否則,如果變量direction等於RIGHT,表示貪吃蛇的方向是向右,那麼創建一個新的蛇頭元素,“x”鍵的值是原來蛇頭的“x”鍵的值加上1個單位,“y”鍵的值是原來蛇頭的“y”鍵的值不變。

然後,把這個新創建的字典元素插入到貪吃蛇列表的第一個位置。

isEattingFood()函數

isEattingFood()函數用來判斷貪吃蛇是否吃到了食物,它接受的參數是表示貪吃蛇的列表和表示食物位置的變量。isEattingFood()函數的代碼如下所示。

首先判斷列表snakeCoords的第一個元素的“x”鍵和“y”鍵的值是否等於變量food的“x”鍵和“y”鍵的值。變量HEAD等於0。

如果相等,表示蛇頭碰到了食物。那麼重新設置變量food的“x”鍵和“y”鍵的值。請注意,對於列表或字典,在函數內修改參數的內容,會影響到函數之外的對象。

如果不相等,刪除snakeCoords列表中最後一個元素。在介紹moveSnake()函數的時候提到過,移動貪吃蛇,其實就是增加一個新的元素。例如,最初是3個元素,向右移動一步,就變成了4個元素。如果這個時候沒有吃到食物,那麼為了保證元素數量不變,就要刪除最後一個元素,這樣才能確保snakeCoords列表中的元素數量沒有變化,仍然是3個元素。

isAlive()函數

isAive()函數用來判斷貪吃蛇是否死亡,它接受的參數是表示貪吃蛇的列表。isAive()函數的代碼如下所示。

首先,將變量tag設置為True。然後,判斷在地圖上的蛇頭的x座標是否等於−1,或者蛇頭的x座標是否等於mapWidth,或者蛇頭的y座標是否等於−1,或者蛇頭的y座標是否等於mapHeight,只要滿足其中的任何一個條件,就表示蛇頭碰到了牆壁,那麼就將變量tag設置為False。然後用一個for循環,來遍歷snakeCoords列表中的第2個元素以及之後的元素,把每個元素賦值給變量snake_body,表示蛇的身體。在循環體內,判斷字典snake_body的鍵“x”和“y”對應的值是否等於snakeCoords列表第一個元素,也就是蛇頭的鍵“x”和“y”對應的值,如果相等,表示蛇頭碰到了蛇的身體,那麼就將變量tag設置為False。最後,該函數返回了變量tag。如果tag是True,表示蛇還活著;如果tag是False,表示蛇死掉了。gameOver()函數gameOver()函數控制整個程序的結束,它接受的參數是窗口的pygame.Surface對象。gameOver()函數的代碼如下所示。

首先調用screen.fill()函數,用白色填充窗口。

然後調用image()函數在Pygame窗口中加載“gameover.png”圖片,並創建一個名為gameOver的Surface對象。然後我們調用blit()函數,把gameOver對象複製到screen這個Surface上。通過blit()將gameOver複製到指定左上角位置(0, 0)。

然後調用pygame.font.SysFont函數,把字符串"按Q或者ESC退出遊戲,按其他鍵重新開始遊戲"繪製到界面上。然後我們調用blit()函數把tip對象複製到screen這個Surface上。

調用pygame .display.update()函數,把繪製到Surface對象上的所有內容,都顯示到窗口上。

接下來,使用while循環監聽鍵盤事件。在事件循環中,判斷事件類型,如果是QUIT,那麼調用terminate函數終止程序。否則,如果事件類型是KEYDOWN,那麼事件對象將有一個key屬性來識別按下的是哪個鍵。如果key屬性等於K_ESCAPE或K_q,表示用戶按下的是Esc或Q鍵,意味著玩家希望結束程序,那麼處理方式和點擊關閉窗口是一樣的,調用terminate()函數終止程序。否則,表示用戶按下的是其他鍵,結束這個函數,重新開始遊戲。

當這個函數執行後,會出現如圖4所示的遊戲界面,這個時候,可以按Q鍵和Esc鍵結束程序,也可以按任意鍵重新開始一局遊戲。

程序員如何用Python10分鐘繪製貪吃蛇小遊戲?

圖4

terminate()函數

terminate()函數終止程序。我們來看一下該函數的代碼。

調用pygame.quit()函數,它是和init()相對應的一個函數。在退出程序之前,需要調用它。然後才會退出Pygame。調用sys.exit()函數,退出主程序退。調用入口函數最後,我們只要調用入口函數main(),程序就可以開始運行了。

到這裡,我們的貪吃蛇遊戲就完成了。這是一個真正意義上的完整遊戲,有開始界面和結束界面,有遊戲背景,還有背景音樂。嘗試著玩一玩,然後再回過頭來看看遊戲的程序代碼,這樣會更有助於對代碼的理解。

《Python少兒趣味編程》

李強 李若瑜 著

程序員如何用Python10分鐘繪製貪吃蛇小遊戲?

● 少兒編程暢銷圖書作者精心編寫 。

●全綵印刷, 提供代碼和素材下載,方便親子互動和少兒自學 。

Python簡單易學,功能強大,是少兒學習編程的首選語言。本書是少兒學習Python編程的趣味指南,全書共17章,按照由簡到難、逐步深入的方式組織各章內容。

本書精心選取內容,注重難易適度和趣味性,語言通俗易懂,代碼示例豐富。在多章的末尾,還給出了一些練習題並給出瞭解答。本書適合想要學習Python編程基礎的少兒(尤其是10歲以上的孩子)及想要教孩子學習編程的家長閱讀,也適合少兒編程培訓班的老師用作少兒編程培訓的教材。

- END -

分享時刻

你對本書的看法?

截止11月14日,留言+轉發朋友圈

抽取2名讀者


分享到:


相關文章: