基於 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
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...