Python游戲編程:一步步用Python打造經典貪吃蛇小游戲

来源:https://www.cnblogs.com/obullxl/p/18212076/NTopic2024052301
-Advertisement-
Play Games

貪吃蛇作為一款極其經典且廣受歡迎的小游戲,是早期 Windows 電腦和功能手機(特別是諾基亞手機)流行度極高的小游戲,是當時功能手機時代最具代表性的游戲之一。游戲的基本規則和目標十分簡單,但卻極具吸引力,讓人欲罷不能。本博文我們用 Python 編寫屬於自己的貪吃蛇游戲,一起來體驗一下編程的樂趣與... ...


貪吃蛇作為一款極其經典且廣受歡迎的小游戲,是早期 Windows 電腦和功能手機(特別是諾基亞手機)流行度極高的小游戲,是當時功能手機時代最具代表性的游戲之一。游戲的基本規則和目標十分簡單,但卻極具吸引力,讓人欲罷不能。本博文我們用 Python 編寫屬於自己的貪吃蛇游戲,一起來體驗一下編程的樂趣與成就……

本文 Python 貪吃蛇游戲源代碼:https://gitee.com/obullxl/PythonCS/tree/master/CS-CY2405

上篇Python消消樂小游戲和源代碼:https://mp.weixin.qq.com/s/QExc_gT-AKlDHWEN1Ycnlw

貪吃蛇游戲分析

控制蛇的移動:通過上下左右鍵,控制一條蛇在游戲區域中移動,最初蛇很短,通常由 1 個方塊組成。

吃到食物增長:游戲區域中會隨機出現食物(例如一個方塊),當蛇頭觸碰到食物時,代表蛇吃到了食物,蛇身體會增長一節,同時得 1 分。

避免越界或碰撞:游戲中需要避免蛇頭撞到游戲區域的邊界,或者蛇頭碰到自己的身體。

策略性移動:隨著游戲的進行蛇身增長,需要巧妙地操控蛇的路徑,既要吃到食物,又要避免越界碰撞,這變得越來越具挑戰性和趣味性。

游戲分數和結束:游戲過程中,需要記錄當前得分(即:蛇吃到食物的數量),游戲結束,展示總得分和重新開始游戲或者退出。

游戲進行中界面

準備:安裝 pygame 工具包

貪吃蛇游戲依賴pygame這個強大的 Python 游戲工具包:

pip install pygame

代碼:設置基礎參數

基礎參數包括屏幕大小、中文字體、背景顏色、字體顏色、蛇的顏色、食物的顏色、蛇的大小和蛇游動速度等。中文simsun.ttf字體文件在源代碼目錄,請一起下載到本地:

import random
import pygame

# 初始化
pygame.init()

# 設置視窗和初始化
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
SCREEN = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

# 設置視窗標題
pygame.display.set_caption('老牛同學:貪吃蛇')

# 游戲屏幕字體
SCORE_FONT = pygame.font.Font('./fonts/simsun.ttf', 25)
RESULT_FONT = pygame.font.Font('./fonts/simsun.ttf', 25)

# 顏色設置
BLACK = (0, 0, 0)  # 黑色(屏幕的顏色)
WHITE = (255, 255, 255)  # 白色(分數的顏色)
GREEN = (0, 255, 0)  # 綠色(蛇的顏色)
RED = (255, 0, 0)  # 紅色(食物的顏色,游戲結束字體的顏色)

# 蛇區塊大小(正方形)和游動速度
SNAKE_BLOCK = 10
SNAKE_SPEED = 8

代碼:繪製游戲進行中的得分

游戲進行中,得分預設在視窗左上角展示:

def draw_score(score):
    """繪製當前分數"""
    score_text = SCORE_FONT.render("總分數:" + str(score), True, WHITE)
    #SCREEN.blit(score_text, [20, 20])  # 左上角

也可以設置為頂部居中展示,如下代碼:

def draw_score(score):
    """繪製當前分數"""
    score_text = SCORE_FONT.render("總分數:" + str(score), True, WHITE)
    score_rect = score_text.get_rect(center=(SCREEN_WIDTH // 2, 20))
    SCREEN.blit(score_text, score_rect)

代碼:繪製游戲進行中蛇的身體

游戲進行中,蛇的身體其實就是一些方塊的位置,蛇的數據結構為一個 list 列表,列表的元素是 x 和 y 坐標 list 列表,即[[x1,y1],[x2,y2],[x3,y3]...]數據存儲形式,蛇尾是第 1 個元素,蛇頭是在最後 1 個元素。

def draw_snake(snake_list):
    """繪製蛇的身體,由於都是方塊,所以繪製過程無需區分蛇頭和蛇身等"""
    for x in snake_list:
        pygame.draw.rect(SCREEN, GREEN, [x[0], x[1], SNAKE_BLOCK, SNAKE_BLOCK])

問題:蛇尾在第 1 個元素,而蛇頭在最後 1 個元素,為什麼要這麼設計?

答案:從後面代碼可以看出,蛇在游動的過程中,蛇新的坐標是用的append到列表,然後刪除列表的第 1 個元素。當然完全可以通過insert的方式反過來設計。

代碼:計算食物的隨機坐標

我們使用random函數隨機計算食物的坐標,同時需要註意,避免食物越界:

def food_position():
    """隨機計算食物坐標"""
    x_food = round(random.randrange(0, SCREEN_WIDTH - SNAKE_BLOCK, SNAKE_BLOCK))
    y_food = round(random.randrange(0, SCREEN_HEIGHT - SNAKE_BLOCK, SNAKE_BLOCK))
    return x_food, y_food

代碼:繪製游戲結束分數和提示

我們在屏幕正中央分別展示 3 行提示文本:游戲結束、總得分和繼續游戲提示。文本正中央展示和總得分類似:

def draw_result(snake_length):
    """繪製游戲結果"""
    # 在屏幕中央顯示文本
    game_over_text = RESULT_FONT.render('游戲結束', True, RED)
    game_over_rect = game_over_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50))
    SCREEN.blit(game_over_text, game_over_rect)

    # 顯示最終得分文本
    final_score_text = RESULT_FONT.render(f'總得分: {snake_length - 1}', True, RED)
    final_score_rect = final_score_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2))
    SCREEN.blit(final_score_text, final_score_rect)

    # 顯示重新開始游戲的提示文本
    restart_text = RESULT_FONT.render('按`Q`退出游戲,按`C`重新開始游戲', True, RED)
    restart_rect = restart_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 50))
    SCREEN.blit(restart_text, restart_rect)

代碼:游戲主迴圈設置

主迴圈游戲把以上功能函數縫合起來,同時包括控制代碼:

  1. game_close參數控制單局游戲是否結束,game_over參數控制整個游戲是否結束
  2. 上下左右按鍵:上下按鍵控制蛇Y軸(上減少、下增加),左右按鍵控制蛇X軸(左減少、右增加)
  3. 蛇觸牆檢測:蛇頭坐標x1,y1是否在整個屏幕之內
  4. 蛇觸蛇身檢測:蛇頭坐標是否在蛇身體的任意坐標相同
  5. 蛇吃到食物檢測:蛇頭坐標是否和食物坐標相同,如果相同則重新設置食物位置,同時蛇身長加 1 個方塊
def game_loop():
    """游戲主迴圈函數"""
    game_over = False  # 退出游戲
    game_close = False  # 單次游戲結束

    # 初始化蛇的坐標和坐標增量
    x1 = SCREEN_WIDTH / 2
    y1 = SCREEN_HEIGHT / 2
    x1_change = 0
    y1_change = 0

    # 蛇的身體列表,初始長度為1
    snake_list = []
    snake_length = 1

    # 隨機生成食物的位置
    x_food, y_food = food_position()

    while not game_over:
        # 如果游戲結束但未選擇退出或重玩,則進入此迴圈
        while game_close:
            # 清空屏幕,準備下一輪繪製
            SCREEN.fill(BLACK)
            draw_result(snake_length)
            pygame.display.update()  # 刷新屏幕

            # 等待按鍵
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:
                        game_over = True  # 退出游戲
                        game_close = False
                    if event.key == pygame.K_c:
                        game_loop()  # 重新開始游戲

        # 處理鍵盤事件,改變蛇的移動方向
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_over = True  # 退出游戲
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    # 左:X坐標減少1個區塊,Y坐標不變
                    x1_change = -1
                    y1_change = 0
                elif event.key == pygame.K_RIGHT:
                    # 右:X坐標增加1個區塊,Y坐標不變
                    x1_change = 1
                    y1_change = 0
                elif event.key == pygame.K_UP:
                    # 上:X坐標不變,Y坐標減少1個區塊
                    x1_change = 0
                    y1_change = -1
                elif event.key == pygame.K_DOWN:
                    # 下:X坐標不變,Y坐標增加1個區塊
                    x1_change = 0
                    y1_change = 1

        # 退出游戲
        if game_over:
            break

        # 檢測蛇是否觸牆
        if x1 >= SCREEN_WIDTH or x1 < 0 or y1 >= SCREEN_HEIGHT or y1 < 0:
            game_close = True

        # 更新蛇的位置
        x1 += x1_change * SNAKE_BLOCK
        y1 += y1_change * SNAKE_BLOCK

        # 清空屏幕,準備下一輪繪製
        SCREEN.fill(BLACK)

        # 畫食物
        pygame.draw.rect(SCREEN, RED, [x_food, y_food, SNAKE_BLOCK, SNAKE_BLOCK])

        # 新的蛇頭位置,同時刪除最後蛇尾區塊,以保持蛇的總長度不變
        snake_head = [x1, y1]
        snake_list.append(snake_head)

        if len(snake_list) > snake_length:
            del snake_list[0]  # 刪除蛇尾

        # 檢查蛇頭是否碰到蛇的身體
        for x in snake_list[:-1]:
            if x == snake_head:
                game_close = True

        # 繪製蛇
        draw_snake(snake_list)

        # 繪製得分
        draw_score(snake_length - 1)

        # 刷新屏幕
        pygame.display.update()

        # 檢查蛇頭是否碰到食物,若碰到則增加長度並重新生成食物
        if x1 == x_food and y1 == y_food:
            x_food, y_food = food_position()
            snake_length += 1

        # 控制游戲幀率
        clock = pygame.time.Clock()
        clock.tick(SNAKE_SPEED)

    # 游戲結束時清理pygame環境
    pygame.quit()
    quit()

最後:啟動游戲主迴圈

啟動游戲,進入主迴圈,通過上下左右按鍵控制蛇身游動:

# 開始游戲
if __name__ == '__main__':
    game_loop()

最後,我們游戲界面大概像這樣:

游戲進行中界面

單局游戲結束的界面:

游戲結束界面

禪定: 我們可以繼續進一步優化這個程式:

  1. 每次吃到食物、單局游戲結束增加音效
  2. 增加難度:長按上下左右按鍵,加速蛇游動的速度
  3. 記住每輪游戲分數,進行分數排名(本地記錄,或者聯網)

關註本公眾號,我們一起探尋編程的樂趣!


我的本博客原地址:https://ntopic.cn/p/2024052301


微信公眾號:老牛同學

本文作者:奔跑的蝸牛,轉載請註明原文鏈接:https://ntopic.cn


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

-Advertisement-
Play Games
更多相關文章
  • 背景 根據orangepi zero2用戶手冊說明,linux5.13內核不能使用 modprobe fbtft_device 驅動spi lcd 查看linux內核源碼提交記錄,發現在v5.4-rc3中刪除了fbtft_device.c文件 commit如下 staging/fbtft: Remo ...
  • 一、卸載mariadb的rpm包 1、首先,你需要找出已安裝的MariaDB包的具體名稱。可以使用以下命令列出所有已安裝的MariaDB包: rpm -qa | grep mariadb 2、刪除命令(安裝mysql不一定需要卸載)yum -y remove +【上圖的文件名】或者rpm -e -- ...
  • 前言 應用中的信息傳遞是為了實現各種功能和交互。信息傳遞可以幫助用戶和應用之間進行有效的溝通和交流。通過信息傳遞,應用可以向用戶傳遞重要的消息、通知和提示,以提供及時的反饋和指導。同時,用戶也可以通過信息傳遞嚮應用發送指令、請求和反饋,以實現個性化的需求和操作。 信息傳遞還可以幫助應用之間實現數 ...
  • Symbol 引用 iconfont icon圖標庫 Symbol 引用 這是一種全新的使用方式,應該說這才是未來的主流,也是平臺目前推薦的用法。相關介紹可以參考這篇文章 這種用法其實是做了一個 SVG 的集合,與另外兩種相比具有如下特點: 支持多色圖標了,不再受單色限制。 通過一些技巧,支持像字體 ...
  • XML中的字元串數據類型表示字元序列,包括換行、回車和製表符。處理器不修改值。`normalizedString`去除這些特殊字元,`token`則進一步移除前導和尾隨空格及多餘空格。字元串類型可使用枚舉、長度等限制。`date`和`dateTime`數據類型表示日期和時間,`duration`表示... ...
  • 一、是什麼 HMR全稱 Hot Module Replacement,可以理解為模塊熱替換,指在應用程式運行過程中,替換、添加、刪除模塊,而無需重新刷新整個應用 例如,我們在應用運行過程中修改了某個模塊,通過自動刷新會導致整個應用的整體刷新,那頁面中的狀態信息都會丟失 如果使用的是 HMR,就可以實 ...
  • theme: nico 寫在前面 主頁有更多其他篇章的方法,歡迎訪問查看。 本篇我們介紹radash中對象相關方法的使用和源碼解析。 assign:遞歸合併兩個對象 使用說明 功能說明:類似於 JavaScript 的 Object.assign 方法,用於將 override 對象的屬性和值複製到 ...
  • title: Vue 3 組件基礎與模板語法詳解 date: 2024/5/24 16:31:13 updated: 2024/5/24 16:31:13 categories: 前端開發 tags: Vue3特性 CompositionAPI Teleport Suspense Vue3安裝 組件 ...
一周排行
    -Advertisement-
    Play Games
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式會偶發性的卡死一段時間,然後又好了,讓我幫忙看下怎麼回事?窗體類的程式解決起來相對來說比較簡單,讓朋友用procdump自動抓一個卡死時的dump,拿到dump之後,上 windbg 說話。 二:WinDbg 分析 1. 主線程在做什麼 要想 ...
  • 功能說明 使用ListView時,希望可以在單元格顯示圖片或其他控制項,發現原生的ListView不支持,於是通過拓展,實現ListView可以顯示任意控制項的功能,效果如下: 實現方法 本來想著在單元格裡面實現控制項的自繪的,但是沒找到辦法,最後是通過在單元格的錶面顯示對應控制項的,浮於錶面達到目的。 實 ...
  • 由於.NET Framework 4.0 是比較古老的版本,只有New Relic 7.0以下的版本才會支持.NET Framework 4.0的引用程式。 Technical support for .NET Framework 4.0 or lower 你可以參考這個官方Install New ...
  • 前言 隨著 DEV24.1.3 的發佈,XAF Blazor 中的屬性編輯器(PropertyEditor)也進行了很大的改動,在使用體驗上也更接近 WinForm 了,由於進行了大量的封裝,理解上沒有 WinForm 直觀,所以本文通過對屬性編輯器的原理進行解析,並對比新舊版本中的變化,使大家能夠 ...
  • OPC基金會提供了OPC UA .NET標準庫以及示常式序,但官方文檔過於簡單,光看官方文檔和示常式序很難弄懂OPC UA .NET標準庫怎麼用,花了不少時間摸索才略微弄懂如何使用,以下記錄如何從一個控制台程式開發一個OPC UA伺服器。 安裝Nuget包 安裝OPCFoundation.NetSt ...
  • 今天在技術群里,石頭哥向大家提了個問題:"如何在一個以System身份運行的.NET程式(Windows Services)中,以其它活動的用戶身份啟動可互動式進程(桌面應用程式、控制台程式、等帶有UI和互動式體驗的程式)"? 我以前有過類似的需求,是在GitLab流水線中運行帶有UI的自動化測試程 ...
  • .Net 中提供了一系列的管理對象集合的類型,數組、可變列表、字典等。從類型安全上集合分為兩類,泛型集合 和 非泛型集合,傳統的非泛型集合存儲為Object,需要類型轉。而泛型集合提供了更好的性能、編譯時類型安全,推薦使用。 ...
  • 在以前我做程式的時候,一般在登錄視窗裡面顯示程式名稱,登錄視窗一般設置一張背景圖片,由於程式的名稱一般都是確定的,所以也不存在太大的問題,不過如果客戶定製不同的系統的時候,需要使用Photoshop修改下圖層的文字,再生成圖片,然後替換一下也可以了。不過本著減少客戶使用繁瑣性,也可以使用空白名稱的通... ...
  • 一:背景 1. 講故事 在dump分析的過程中經常會看到很多線程卡在Monitor.Wait方法上,曾經也有不少人問我為什麼用 !syncblk 看不到 Monitor.Wait 上的鎖信息,剛好昨天有時間我就來研究一下。 二:Monitor.Wait 底層怎麼玩的 1. 案例演示 為了方便講述,先 ...
  • 目錄前言學習參考過程總結: 前言 做個自由仔。 學習參考 ChatGpt; https://www.cnblogs.com/zhili/p/DesignPatternSummery.html(大佬的,看了好多次) 過程 原由: 一開始只是想查查鏈式調用原理,以為是要繼承什麼介面,實現什麼方法才可以實 ...