基於 pygame 設計貪吃蛇游戲

来源:https://www.cnblogs.com/liquancai/archive/2020/07/08/13269428.html
-Advertisement-
Play Games

轉載註明鏈接:https://www.cnblogs.com/liquancai/p/13269428.html 基於 pygame 設計貪吃蛇游戲 貪吃蛇游戲通過玩家控制蛇移動,不斷吃到食物增長,直到碰到蛇身或邊界游戲結束。其運行效果如下所示: 游戲開始時,先導入可能需要用到的包。 import ...


轉載註明鏈接:https://www.cnblogs.com/liquancai/p/13269428.html

基於 pygame 設計貪吃蛇游戲

貪吃蛇游戲通過玩家控制蛇移動,不斷吃到食物增長,直到碰到蛇身或邊界游戲結束。其運行效果如下所示:

游戲開始時,先導入可能需要用到的包。

import time
import random
import pygame
from pygame.locals import *

輸入下麵兩行來啟用並初始化 pygame,這樣 pygame 在改程式中就可以使用了。

pygame.init()
fps_clock = pygame.time.Clock()

第 1 行告訴 pygame 初始化,第 2 行創建一個名為 fps_clock 的變數,改變數用來控制刷新游戲界面(即 游戲迴圈執行)的速度。然後用下麵的兩行代碼新建一個 pygame 顯示層(游戲元素畫布)。第 3~6 行分別定義了游戲結束畫面顯示的 “ Game Over ”,及其字體、大小、位置等。

screen = pygame.display.set_mode(SCREEN_RECT.size)	# SCREEN_RECT 是游戲界面的 rect。
pygame.display.set_caption("貪吃蛇")
gameover_font = pygame.font.SysFont('arial', 100)
gameover_text = gameover_font.render("Game Over", True, grey)
gameover_rect = gameover_text.get_rect()
gameover_rect.center = SCREEN_RECT.center

接下來定義一些顏色,雖然這一步不是必須的,但它會減少程式中的代碼量。下麵代碼定義了程式中用到的顏色。

red = pygame.Color(255, 0, 0)
black = pygame.Color(0, 0, 0)
white = pygame.Color(255, 255, 255)
grey = pygame.Color(150, 150, 150)

下麵的幾行代碼初始化了程式中用到的一些變數,這是很重要的一步,因為如果游戲開始時,這些變數為空,python 將無法正常運行。

snake_position = [100, 100]			# 蛇頭位置
# 蛇身序列座標,OFFSET 可以看作蛇運動速度,
snake_segments = [[100, 100], [100-OFFSET, 100], [100-2*OFFSET, 100]]	
target = [300, 300]			# 食物位置
flag = 0			# 是否吃到食物,1為是,0為否
direction = 'right'			# 運動方向,初始設置為右
temp_direction = direction 		# 待改變運動方向

由上可以看出,用列表來表示蛇頭、蛇身序列和食物的座標。

至此程式的開頭部分已經完成,接下來進入主要部分。該程式運行在一個無限迴圈(一個永不退出的 while 迴圈)中,直到蛇撞到了牆或者自己才會結束游戲。

首先用 while True 開始主迴圈。沒有其他的比較條件,python 會檢測 True 是否為真。如果 True 一直為真,迴圈會一直進行,直到滿足游戲結束條件後退出迴圈。

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            exit()
    keys_pressed = pygame.key.get_pressed()
    if keys_pressed[K_RIGHT]:
        temp_direction = 'right'
    if keys_pressed[K_LEFT]:
        temp_direction = 'left'
    if keys_pressed[K_UP]:
        temp_direction = 'up'
    if keys_pressed[K_DOWN]:
        temp_direction = 'down'

for 迴圈用來檢測 pygame 退出事件。if event.type == QUIT 告訴 python 如果 pygame 發出了 QUIT 信息(當用戶按下游戲界面右上角的 × 按鈕時),通知 pygame 和 python 程式結束並退出。

keys_pressed = pygame.key.get_pressed() 用來檢測用戶鍵盤按鍵,通過判斷 keys_pressed 中相應按鍵值(1或0),來改變 temp_direction 的值,從而控制蛇的運動方向。

程式開始時,蛇會按照 temp_direction 預設的值向右移動,直到用戶按下鍵盤的方向鍵改變其方向。

在程式開始的初始化部分有一個 direction 變數,這個變數協同 temp_direction 檢測用戶發出的命令是否有效。蛇是不能立即向後運動的(如果發生該情況,蛇會死亡,同時游戲結束)。為了防止這樣的情況發生,將用戶發出的請求(保存在 temp_direction 里)和目前的方向(保存在 direction 里)進行比較,如果方向相反,忽略該命令,蛇會繼續按原方向運動。如下代碼所示:

if ((temp_direction == 'right' and direction != 'left')
   		or (temp_direcion == 'left' and direction != 'right')
   		or (temp_direction == 'up' and direction != 'down')
   		or (temp_direction == 'down' and direction != 'up')):
	direction = temp_direction

這樣就保證了用戶輸入的合法性,蛇(在屏幕上顯示為一系列塊)就能夠按照用戶的輸入移動。每次轉彎時,蛇會向該方向移動一小節。每個小節像素值為 OFFSET(用戶可以自己設定)。用戶按下按鍵即告訴 pygame 在任何方向移動一小節。代碼如下:

if direction == 'right':
    snake_position[0] += OFFSET
if direction == 'left':
    snake_position[0] -= OFFSET
if direction == 'up':
    snake_position[1] -= OFFSET
if direction == 'down':
    snake_position[1] += OFFSET

snake_position 為蛇頭的新位置,程式開始處的另一個列表變數 snake_segments 卻不是這樣。該列表存儲蛇身體的位置(頭部後邊),隨著蛇吃掉食物導致長度增加,列表會增加長度同時提高游戲難度。隨著游戲的進行,避免蛇頭撞到身體的難度變大。如果蛇頭撞到身體,蛇會死亡,同時游戲結束。此時用下麵的代碼使蛇的身體增長:

snake_segments.insert(0, list(snake_position))

這裡用 insert() 方法向 snake_segments 列表(存有蛇身的位置)中添加新的項目。每當 python 運行到這一行,它就會將蛇的身體增長一節,同時這一節放在蛇的頭部,在玩家看來蛇在增長。當然,用戶只希望蛇吃到食物時才增長,否則蛇會一直變長。輸入下麵的幾行代碼:

if snake_position[0] == target[0] and snake_position[1] == target[1]:
    flag = 1
else:
    snake_segments.pop()

第 1 條 if 語句檢測蛇頭部的 x 和 y 座標是否等於食物的座標。如果相等,該食物就會被蛇吃掉,同時 flag 變數置為 1。else 語句告訴 python 如果食物沒有被吃掉,將 snake_segments 列表中最早的項目 pop 出來。

pop 語句簡單、易用,它刪除列表中末尾的項目(並返回該項目),從而使列表縮短一項。在 snake_segments 列表裡,它使 python 刪掉距離頭部最遠的一部分。在玩家看來,蛇整體在移動而不會增長。實際上,它在一端增長小節,在另一端刪除小節。由於有 else 語句,pop 語句只有在蛇沒有吃到食物使執行。如果蛇吃到了食物,列表中的最後一項不會被刪掉,所以會增加一小節。

現在,蛇就可以通過吃食物來讓自己變長了。但是如果游戲中只有一個食物難免會有些無聊,所以若蛇吃了一個食物,用下麵的代碼增加一個新的食物到游戲界面中:

if flag:
    x = random.randint(1, SCREEN_RECT.width // OFFSET)
    y = random.randint(1, SCREEN_RECT.height // OFFSET)
    target = [int(x*OFFSET), int(y*OFFSET)]
    flag = 0

這部分代碼通過判斷變數 flag 是否為 1(不為 0)來判斷食物是否被蛇吃掉了,如果被吃掉,使用程式開始引入的 random 模塊獲取一個隨機的位置。然後將這個位置和蛇的每個小節的長度(像素寬和高均為OFFSET)相乘來確定它在游戲界面中的位置。隨機地放置食物是很重要的,防止用戶預先知道下一個食物出現的位置。最後將 flag 變數置為 0,以保證每個時刻界面上只有一個食物。

現在有了讓蛇移動和生長的代碼,包括食物被吃和新建的操作(在游戲中稱為食物重生),但是還沒有在界面上畫東西。輸入一下代碼,可以將蛇和食物顯示在游戲界面上。

screen.fill(black)
for each in snake_segments:
    pygame.draw.rect(screen, white, Rect(each[0], each[1], OFFSET, OFFSET))
pygame.draw.rect(screen, red, Rect(target[0], target[1], OFFSET, OFFSET))
pygame.display.update()

這些代碼讓 pygame 填充背景色為黑色,蛇的頭部和身體為白色,食物為紅色。最後一行的 pygame.display.update() 讓 pygame 更新界面(如果沒有這條語句,用戶將看不到任何東西。每次在界面上畫玩對象時,記得使用 pygame.display.update() 讓用戶看到更新的界面)。

現在還沒有涉及蛇死亡的代碼。如果游戲中的角色永遠也死不了,玩家很快會感到無聊,所以用下麵的代碼設置一些讓蛇死亡的場景:

if (snake_position[0] < 0 or snake_position[0] > SCREEN_RECT.width-OFFSET 
		or snake_position[1] < 0 or snake_position[1] > SCREEN_RECT.height-OFFSET):
    screen.blit(gameover_text, gameover_rect)
    pygame.display.update()
    time.sleep(2)
    pygame.quit()
    exit()

if 語句檢查蛇是否走出了游戲視窗四周的邊界,如果走出邊界,則游戲結束,列印游戲結束信息,並且在結束界面暫停 2s ,再退出游戲,關閉界面。

如果蛇頭撞到了自己身體的任何部分,也會讓蛇死亡,游戲結束。所以游戲結束的代碼還有下麵一種情況:

for each in snake_segments[1:]:
    if snake_position[0] == each[0] and snake_position[1] == each[1]:
        screen.blit(gameover_text, gameover_rect)
        pygame.display.update()
        time.sleep(2)
        pygame.quit()
        exit()

這裡的 for 語句遍歷蛇的每一小節的位置(從列表的第二項開始到最後一項),同時和當前蛇頭的位置比較。這裡用 snake_segments[1:] 來保證從列表的第 2 項開始遍歷。列表的第 1 項為頭部位置,如果從第 1 項開始比較,那麼游戲一開始就死亡了。

最後,只需要設置 fps_clock 變數的值即可控制游戲迴圈的速度。

fps_clock.tick(20)

貪吃蛇游戲的完整源代碼如下:

import time
import random
import pygame
from pygame.locals import *



pygame.init()
SCREEN_RECT = pygame.Rect(0, 0, 1000, 750)
OFFSET = 10
fps_clock = pygame.time.Clock()
screen = pygame.display.set_mode(SCREEN_RECT.size)
pygame.display.set_caption("貪吃蛇")
gameover_font = pygame.font.SysFont("arial", 100)
gameover_text = gameover_font.render("Game Over", True, (150, 150, 150))
gameover_rect = gameover_text.get_rect()
gameover_rect.center = SCREEN_RECT.center
snake_position = [100, 100]
snake_segments = [[100, 100], [100 - OFFSET, 100], [100 - 2 * OFFSET, 100]]
target_position = [300, 300]
flag = 0
direction = 'right'
temp_direction = direction
while True:
    fps_clock.tick(20)
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            exit()
    keys_pressed = pygame.key.get_pressed()
    if keys_pressed[K_RIGHT]:
        temp_direction = 'right'
    if keys_pressed[K_LEFT]:
        temp_direction = 'left'
    if keys_pressed[K_UP]:
        temp_direction = 'up'
    if keys_pressed[K_DOWN]:
        temp_direction = 'down'
    if (((temp_direction == 'right') and (direction != 'left'))
            or ((temp_direction == 'left') and (direction != 'right'))
            or ((temp_direction == 'up') and (direction != 'down'))
            or (temp_direction == 'down') and (direction != 'up')):
        direction = temp_direction
    if direction == 'right':
        snake_position[0] += OFFSET
    if direction == 'left':
        snake_position[0] -= OFFSET
    if direction == 'up':
        snake_position[1] -= OFFSET
    if direction == 'down':
        snake_position[1] += OFFSET
    snake_segments.insert(0, list(snake_position))
    if (snake_position[0] == target_position[0]) and (snake_position[1] == target_position[1]):
        flag = 1
    else:
        snake_segments.pop()
    if flag:
        x = random.randrange(1, SCREEN_RECT.width // OFFSET)
        y = random.randint(1, SCREEN_RECT.height // OFFSET)
        target_position = [int(x * OFFSET), int(y * OFFSET)]
        flag = 0
    screen.fill((0, 0, 0))
    for each in snake_segments:
        pygame.draw.rect(screen, (255, 255, 255), Rect(each[0], each[1], OFFSET, OFFSET))
    pygame.draw.rect(screen, (255, 0, 0), Rect(target_position[0], target_position[1], OFFSET, OFFSET))
    pygame.display.update()
    if ((snake_position[0] < 0)
            or (snake_position[0] > SCREEN_RECT.width - OFFSET)
            or (snake_position[1] < 0)
            or (snake_position[1] > SCREEN_RECT.height - OFFSET)):
        screen.blit(gameover_text, gameover_rect)
        pygame.display.update()
        time.sleep(2)
        pygame.quit()
        exit()
    for each in snake_segments[1:]:
        if (snake_segments[0] == each[0]) and (snake_segments[1] == each[1]):
            screen.blit(gameover_text, gameover_rect)
            pygame.display.update()
            time.sleep(2)
            pygame.quit()
            exit()
            

游戲運行結束時畫面如下:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 問題起源 使用SpringCloud構建項目時,使用Swagger生成相應的介面文檔是推薦的選項,Swagger能夠提供頁面訪問,直接在網頁上調試後端系統的介面, 非常方便。最近卻遇到了一個有點困惑的問題,演示介面示例如下(原有功能介面帶有業務實現邏輯,這裡簡化了介面): /** * @descri ...
  • 多線程與非同步 非同步是目的,而多線程是實現這個目的的方法。 1 Java J.U.C線程調度 JDK 1.5新增的java.util.concurrent包,增加了併發編程的很多類。 Executor 定義了方法execute(),用來執行一個任務 public interface Executor ...
  • 1.http:(1)當⽤戶在地址輸⼊了⽹址 發送⽹絡請求的過程是什麼 (2)http的請求⽅式 get請求 (1)⽐較便捷 缺點:不安全:明⽂ 參數的⻓度有限制 post請求 (1)⽐較安全 (2)數據整體沒有限制 (3)上傳⽂件 put(不完全的) delete(刪除 ⼀ 些信息) head(請求 ...
  • 一、UDP編程 1.DatagramPacket特性以及構造方法 UDP是一個面向無連接的協議,因此,在通信時發送端和接收端不用建立連接。 Datagram類就相當於一個集裝箱用於封裝UDP通信中發送或者接收的數據。 構造方法: (1)DatagramPacket(byte[] buf,int le ...
  • 自定義攔截器 /** * UserSecurityInterceptor * Created with IntelliJ IDEA. * Author: yangyongkang * Date: 2018/8/22 * Time: 14:20 */ @Component public class U ...
  • 我是跟著《深入淺出 Spring Boot 2.x》這本書學習的,在“初識Spring MVC”章節中,搭建項目,然後訪問jsp頁面時報錯:Path with "WEB-INF" or "META-INF": [WEB-INF/jsp/details.jsp 1、因為沒有書本中說的 WEB-INF  ...
  • from docx import Document w=Document() w.add_section() w.add_section() w.paragraphs[0].add_run('第一個段落') w.paragraphs[1].add_run('第二個段落') w.save(r'D:\w ...
  • 一、類及對象 1. 類的組成成分 屬性(成員變數,Field) 方法(成員方法,函數,Method) 2. 屬性 成員變數 vs 局部變數 相同點: 遵循變數聲明的格式: 數據類型 變數名 = 初始化值 都有作用域 不同點: 聲明的位置的不同 :成員變數:聲明在類里,方法外, 局部變數:聲明在方法內 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...