貪吃蛇—C—基於easyx圖形庫(下):從畫圖程式到貪吃蛇【自帶穿牆術】

来源:http://www.cnblogs.com/kirito-c/archive/2016/06/18/5596160.html
-Advertisement-
Play Games

上節我們用方向控制函數寫了個小畫圖程式,它雖然簡單好玩,但我們不應該止步於此。革命尚未成功,同志還需努力。 開始擼代碼之前,我們先理清一下思路。和前面畫圖程式不同,貪吃蛇可以有很多節,可以用一個足夠大的結構體數組來儲存它。 還需要一個食物坐標。定義如下: 之前的畫圖程式是四個方向都可以走,可蛇是不能 ...


上節我們用方向控制函數寫了個小畫圖程式,它雖然簡單好玩,但我們不應該止步於此。革命尚未成功,同志還需努力。

 

 

開始擼代碼之前,我們先理清一下思路。和前面畫圖程式不同,貪吃蛇可以有很多節,可以用一個足夠大的結構體數組來儲存它。 還需要一個食物坐標。定義如下:

typedef struct Position  //坐標結構
{
    int x;
    int y;
}Pos;

Pos array;                         //移動方向向量
Pos snake[300000] = {};  //蛇的結構體數組,誰能夠無聊到吃299999個食物~_~
long len=1; //蛇的長度
Pos egg; //食物坐標

 

之前的畫圖程式是四個方向都可以走,可蛇是不能倒著走的,所以方向控制函數要改成這樣:

void command()                              //獲取鍵盤命令
{
    if (_kbhit())       //如果有鍵盤消息
        switch (_getch())      /*這裡不能用getchar()*/
        {
        case 'a':
            if (array.x != 1 || array.y != 0) {//如果命令不是倒著走,就修正方向向量,否則不做改變,下同。
                array.x = -1;
                array.y = 0;
            }
            break;
        case 'd':
            if (array.x != -1 || array.y != 0) {
                array.x = 1;
                array.y = 0;
            }
            break;
        case 'w':
            if (array.x != 0 || array.y != 1) {
                array.x = 0;
                array.y = -1;
            }
            break;
        case 's':
            if (array.x != 0 || array.y != -1) {
                array.x = 0;
                array.y = 1;
            }
            break;
        }
} 

 

蛇可能不止一節,所以移動函數需要做出改變。仔細一想就知道,走了一步之後,除了頭結點外,每個節點的下一個坐標為它前一個結點之前的坐標,而頭節點的坐標等於它本身坐標加上移動向量(這裡是 方向向量*10)

還有個問題是蛇走過的痕跡需要擦除,每走一步,它留下的痕跡應該是走這一步之前蛇的最末一個結點的坐標,我們需要擦除掉它。

結果如下:

void move()    //修改各節點坐標以達到移動的目的
{
    setcolor(BLACK);        //覆蓋尾部走過的痕跡
    rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5);

    for (int i = len-1; i >0; i--)    //除了頭結點外,每個節點的下一個坐標為它前一個結點當前的坐標
    {
        snake[i].x = snake[i - 1].x;
        snake[i].y = snake[i - 1].y;
    }
    snake[0].x += array.x*10;             //頭節點的坐標等於它本身坐標加上移動向量(這裡是 方向向量*10)
    snake[0].y += array.y*10;
}

 

另外,我們的蛇是有穿牆術的~~~它的實現方法非常簡單:

void break_wall()
{
    if (snake[0].x >= 640)             //如果越界,從另一邊出來
        snake[0].x = 0;
    else if (snake[0].x <= 0)
        snake[0].x = 640;
    else if (snake[0].y >= 480)
        snake[0].y = 0;
    else if (snake[0].y <= 0)
        snake[0].y = 480;
}

 

 

接下來是食物相關函數,這個算是重點。

1. 食物生成

我們希望食物每次出現的位置都是隨機的, 可以這樣實現。

1         srand((unsigned)time(NULL));
2         egg.x = rand() % 80 * 5 + 100;    //頭節點位置隨機化
3         egg.y = rand() % 50 * 5 + 100;

 而且食物不能與蛇重合,最好也不要離蛇太近。綜合起來就是這樣:(srand在初始化中會被調用,所以這裡略去了)

void creat_egg()
{
    while (true)
    {
        int ok = 0;   //這是個標記,用於判斷函數是否進入了某一分支
        egg.x = rand() % 80 * 5 + 100;    //頭節點位置隨機化
        egg.y = rand() % 50 * 5 + 100;
        for (int i = 0; i < len; i++)     //判斷是否離蛇太近
        {
            if (snake[i].x == 0 && snake[i].y == 0)
                continue;
            if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10)
                ok = -1;   //如果,進入此分支,改變標記
                break;
        }
        if (ok == 0)    //如果不重合了,跳出函數
            return;
    }
}

2. 吃到食物

如果吃到食物,那麼需要消除被吃掉的食物,生成新食物,蛇也要增長一節。

我覺得這裡最麻煩的就是蛇變長的實現:是在蛇頭添加一節,還是在蛇尾?添加在蛇頭(尾)的上下左右哪一邊?

想來想去,只有在蛇頭位置,我們可以根據當前方向向量,在移動方向上新添一節。這對應的代碼如下:

        //add snake node
        len += 1;
        for (int i = len - 1; i >0; i--)    //所有數據後移一個單位,騰出snake[0]給新添的一節
        {
            snake[i].x = snake[i - 1].x;
            snake[i].y = snake[i - 1].y;
        }
        snake[0].x += array.x * 10;             //這就是新添的這一節的位置
        snake[0].y += array.y * 10;

吃到食物的完整代碼如下:

void eat_egg()
{
    if (fabs(snake[0].x - egg.x)<=5 && fabs(snake[0].y - egg.y)<=5)    //判斷是否吃到食物,因為食物位置有點小偏差,只好使用範圍判定~~
    {
        setcolor(BLACK);          //hide old egg
        circle(egg.x, egg.y, 5);
creat_egg(); //create new egg
//add snake node len += 1; for (int i = len - 1; i >0; i--) { snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } snake[0].x += array.x * 10; //每次移動10pix snake[0].y += array.y * 10; } }

 

 

游戲結束判定

最後,我們還差一個死亡判定,因為自帶穿牆術,所以實際的死亡判定只有一個,就是咬到自己,代碼如下:

void eat_self()
{
    if (len == 1)             //只有一節當然吃不到自己~~
        return;
    for (int i = 1; i < len; i++)
        if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5)            //如果咬到自己(為了不出bug,使用了範圍判定)
        {
            outtextxy(250, 200, "GAME OVER!");  //你的蛇死了~
            Sleep(3000);      //3s時間讓你看看你的死相~~
            closegraph();
            exit(0);     //退出
        }
}

當然,你也可以直接丟掉這個函數,然後開心地狂咬自己—_—||

最後:畫圖函數

畫出食物和蛇,其實蛇沒必要全部畫出來,只要畫蛇頭就可以了,但這之中有些小問題,誰有興趣可以自己玩玩,我是懶得動了~

void draw()     //畫出蛇和食物
{
    setcolor(BLUE);
    for (int i = 0; i < len; i++)
    {
        rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5);
    }
    setcolor(RED);        //畫蛋(怎麼感覺怪怪的~)
    circle(egg.x, egg.y, 5);
    Sleep(100);
}

到這裡,游戲大功告成~~  什麼?你說運行不起來?那是因為少了初始化函數,和游戲迴圈啦~~這幾個都比較簡單,就直接放下麵了:

void init()              //初始化
{
    initgraph(640, 480);                    //初始化圖形界面
    srand((unsigned)time(NULL));            //初始化隨機函數
    snake[0].x = rand() % 80 * 5 + 100;    //頭節點位置隨機化
    snake[0].y = rand() % 50 * 5 + 100;
    array.x = pow(-1,rand());        //初始化方向向量,左或者右
    array.y = 0;
    creat_egg();
}

int main()
{
    init();
    while (true)
    {
        command();      //獲取鍵盤消息
        move();         //修改頭節點坐標-蛇的移動
        eat_egg();
        draw();         //作圖
        eat_self();
    }

    return 0;
}

好了,這是真的大功告成了。給你們看看死亡方式之自盡:

完整代碼如下:

  1 #include<graphics.h>
  2 #include<conio.h>
  3 #include<time.h>
  4 #include<math.h>
  5 
  6 typedef struct Position  //坐標結構
  7 {
  8     int x;
  9     int y;
 10 }Pos;
 11 
 12 Pos snake[300000] = {};
 13 Pos array;
 14 Pos egg;
 15 long len=1;
 16 
 17 void creat_egg()
 18 {
 19     while (true)
 20     {
 21         int ok = 0;
 22         srand((unsigned)time(NULL));            //初始化隨機函數
 23         egg.x = rand() % 80 * 5 + 100;    //頭節點位置隨機化
 24         egg.y = rand() % 50 * 5 + 100;
 25         for (int i = 0; i < len; i++)
 26         {
 27             if (snake[i].x == 0 && snake[i].y == 0)
 28                 continue;
 29             if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10)
 30                 ok = -1;
 31                 break;
 32         }
 33         if (ok == 0)
 34             return;
 35     }
 36 }
 37 
 38 void init()              //初始化
 39 {
 40     initgraph(640, 480);                    //初始化圖形界面
 41     srand((unsigned)time(NULL));            //初始化隨機函數
 42     snake[0].x = rand() % 80 * 5 + 100;    //頭節點位置隨機化
 43     snake[0].y = rand() % 50 * 5 + 100;
 44     array.x = pow(-1,rand());        //初始化方向向量
 45     array.y = 0;
 46     creat_egg();
 47 }
 48 
 49 void command()                              //獲取鍵盤命令
 50 {
 51     if (_kbhit())       //如果有鍵盤消息
 52         switch (_getch()/*這裡不能用getchar()*/)
 53         {
 54         case 'a':
 55             if (array.x != 1 || array.y != 0) {//如果不是反方向
 56                 array.x = -1;
 57                 array.y = 0;
 58             }
 59             break;
 60         case 'd':
 61             if (array.x != -1 || array.y != 0) {
 62                 array.x = 1;
 63                 array.y = 0;
 64             }
 65             break;
 66         case 'w':
 67             if (array.x != 0 || array.y != 1) {
 68                 array.x = 0;
 69                 array.y = -1;
 70             }
 71             break;
 72         case 's':
 73             if (array.x != 0 || array.y != -1) {
 74                 array.x = 0;
 75                 array.y = 1;
 76             }
 77             break;
 78         }
 79 } 
 80 
 81 void move()    //修改各節點坐標以達到移動的目的
 82 {
 83     setcolor(BLACK);        //覆蓋尾部走過的痕跡
 84     rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5);
 85 
 86     for (int i = len-1; i >0; i--)
 87     {
 88         snake[i].x = snake[i - 1].x;
 89         snake[i].y = snake[i - 1].y;
 90     }
 91     snake[0].x += array.x*10;             //每次移動10pix
 92     snake[0].y += array.y*10;
 93 
 94     if (snake[0].x >= 640)             //如果越界,從另一邊出來
 95         snake[0].x = 0;
 96     else if (snake[0].x <= 0)
 97         snake[0].x = 640;
 98     else if (snake[0].y >= 480)
 99         snake[0].y = 0;
100     else if (snake[0].y <= 0)
101         snake[0].y = 480;
102 }
103 
104 void eat_egg()
105 {
106     if (fabs(snake[0].x - egg.x)<=5 && fabs(snake[0].y - egg.y)<=5)
107     {
108         setcolor(BLACK);          //shade old egg
109         circle(egg.x, egg.y, 5);
110         creat_egg();
111         //add snake node
112         len += 1;
113         for (int i = len - 1; i >0; i--)
114         {
115             snake[i].x = snake[i - 1].x;
116             snake[i].y = snake[i - 1].y;
117         }
118         snake[0].x += array.x * 10;             //每次移動10pix
119         snake[0].y += array.y * 10;
120     }
121 }
122 
123 void draw()     //畫出蛇和食物
124 {
125     setcolor(BLUE);
126     for (int i = 0; i < len; i++)
127     {
128         rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5);
129     }
130     setcolor(RED);
131     circle(egg.x, egg.y, 5);
132     Sleep(100);
133 }
134 
135 void eat_self()
136 {
137     if (len == 1)
138         return;
139     for (int i = 1; i < len; i++)
140         if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5)
141         {
142             Sleep(1000);
143             outtextxy(250, 200, "GAME OVER!");
144             Sleep(3000);
145             closegraph();
146             exit(0);
147         }
148 }
149 
150 int main()
151 {
152     init();
153     while (true)
154     {
155         command();      //獲取鍵盤消息
156         move();         //修改頭節點坐標-蛇的移動
157         eat_egg();
158         draw();         //作圖
159         eat_self();
160     }
161 
162     return 0;
163 }
snakey

可能還有若幹bug留存,歡迎大家指正~~

甲鐵城鎮~

 


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

-Advertisement-
Play Games
更多相關文章
  • “伯爵說”序列如下:1, 11, 21, 1211, 111221, ...1 讀作 "one 1" 或者 11。11 讀作 "two 1s" 或者21。21 讀作 "one 2, one 1" 或者 1211。 格式:多組輸入,讀到文件結束。每組輸入給定一個整數n,輸出第n個序列。(1<=n<=3 ...
  • 14.5 SQL 總結 到目前為止,我們在Python示常式序中使用了SQL,並且涉及了許多SQL基礎。在這一小節中,我們特別審視SQL語言,並對其語法進行回顧。 雖然有很多不同的資料庫供應商,但因SQL語言是標準化的,所以我們可以在不同的資料庫系統中方便地移植。 一個關係資料庫是由表、行和列構成的 ...
  • Java核心技術 捲1 基礎知識(第9版)Java核心技術 捲II 高級特性(第9版) 資料庫系統概念(原書第6版)Oracle 從入門到精通MySQL資料庫應用從入門到精通 Java併發編程實戰(第16屆Jolt大獎提名圖書,Java併發編程必讀佳作) Java網路編程(第3版)——O’Reill ...
  • 自己動手實踐了一次,發生中間出了一下問題,現整理出來,供參考。 新建一個java web項目 下載spring的jar包 "http://repo.spring.io/libs release local/org/springframework/spring/" 版本取決於你,我選擇的是4.0.4的 ...
  • 3.1 路由 漂亮的URL絕對是一個嚴肅的web應用程式必須做到的,這種方式使index.php?article_id=57這類的醜陋URL被隱藏,由更受歡迎的像 /read/intro-to-symfony 來替代。 3.1.1 路由配置 配置文件為app/Config/routes.php 以上 ...
  • 轉載請註明出處http://www.cnblogs.com/Wxtrkbc/p/5590004.html 本來最初的想法是實現一個ftp伺服器,用來實現用戶的登陸註冊和文件的斷點上傳下載等,結果做著做著就連CRT也順帶著跟著完成了,然後就變成了這樣一個'不倫不類'的工具。用到的知識有hashlib加 ...
  • 1.問題描述 找不到包 sun.misc.BASE64Encoder 2. 解決方案 只需要在project build path中先移除JRE System Library,再添加庫JRE System Library,重新編譯後就一切正常了。 ...
  • 1、javaScript的概念:是一種表述語言,也是一種基於對象(Object)和事件驅動(EventDriven)的,安全性好的腳本語言,運行在客戶端,從而減輕伺服器端的負擔,總結如下: 1.javaScript主要用來向HTML頁面找那個添加交互行為。 2.javaScript是一種腳本語言,語 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...