【C/C++】10分鍾教你用C++寫一個貪吃蛇附帶AI功能(附源代碼詳解和下載)


C++編寫貪吃蛇小游戲快速入門

剛學完C++。一時興起,就花幾天時間手動做了個貪吃蛇,后來覺得不過癮,於是又加入了AI功能。希望大家Enjoy It.

效果圖示

AI模式演示

imageimage
imageimage

整體規划+原理

imageimage

大體上可以分為圖上所示的幾個類。不過……怎么看都有點強行面向對象的味道在里面。。[哭笑][哭笑][哭笑]。不管了……代碼寫得可能有點凌亂,下面我會為大家一一講解。

整個程序設計的原理就是:主函數死循環,不斷刷新打印貪吃蛇和食物。這樣每循環一次,就類似電影里面的一幀,最終顯示的效果就是蛇會動起來。

01 初始化工作-游戲設置

游戲設置和相關初始化放在了一個類里面,並進行了靜態聲明。主要設置了游戲窗口的長和款。並在GameInit()函數里面設置了窗口大小,隱藏光標,初始化隨機數種子等。代碼如下:

 1//游戲設置相關模塊,把函數都放到一個類里面了。函數定義為static靜態成員,不生成實體也可以直接調用
2class GameSetting
3{

4public:
5    //游戲窗口的長寬
6    static const int window_height = 40;
7    static const int window_width = 80;
8public:
9    static void GameInit()
10    
{
11        //設置游戲窗口大小
12        char buffer[32];
13        sprintf_s(buffer, "mode con cols=%d lines=%d",window_width, window_height);
14        system(buffer);
15
16        //隱藏光標
17        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
18        CONSOLE_CURSOR_INFO CursorInfo;
19        GetConsoleCursorInfo(handle, &CursorInfo);//獲取控制台光標信息
20        CursorInfo.bVisible = false//隱藏控制台光標
21        SetConsoleCursorInfo(handle, &CursorInfo);//設置控制台光標狀態
22        //初始化隨機數種子
23        srand((unsigned int)time(0));
24    }
25};

用到了幾個相關的Windows API,本文不做過多介紹,大家百度即可。

02 打印信息類

該類主要是用來打印一些游戲相關信息的。該類大體如下:

imageimage

下面挑幾個重點的來講:

2.1 畫地圖邊界

這個函數主要是根據上面所給的游戲窗口長寬來打印地圖邊界的。其中還划分了幾個區域,主要用來放不同的信息的。

 1//畫地圖邊界
2static void DrawMap()
3
{
4    system("cls");
5    int i, j;
6    for (i = 0; i < GameSetting::window_width; i++)
7        cout << "#";
8    cout << endl;
9    for (i = 0; i < GameSetting::window_height-2; i++)
10    {
11        for (j = 0; j < GameSetting::window_width; j++)
12        {
13            if (i == 13 && j >= GameSetting::window_width - 29)
14            {
15                cout << "#";
16                continue;
17            }
18
19            if (j == 0 || j == GameSetting::window_width - 29 || j == GameSetting::window_width-1)
20            {
21                cout << "#";
22            }
23            else
24                cout << " ";
25
26        }
27        cout << endl;
28    }
29    for (i = 0; i < GameSetting::window_width; i++)
30        cout << "#";
31
32}

划分區域如下圖,#就是邊框了:

imageimage

2.2 畫出分數和模式

該函數主要是在右上角畫出成績和游戲模式的,在繪制之前會進行刷新處理。先清除,再重新打印。用到了一個gotoxy()函數。這個函數主要是移動光標到(x, y)坐標處的。關於(x, y)的位置,根據實際情況調整即可。

 1//畫分數
2static void DrawScore(int score)
3
{
4    gotoxy(GameSetting::window_width - 22+146);
5    cout << "  ";
6    gotoxy(GameSetting::window_width - 22+144);
7    cout << "  ";
8
9    gotoxy(GameSetting::window_width - 226);
10    cout << "當前玩家分數: " << score << endl;
11    gotoxy(GameSetting::window_width - 224);
12    cout << "當前游戲速度: " << 10 - speed / 25 << endl;
13
14}

03 食物類

食物類定義了食物的坐標,隨機生成規則,和畫出食物等一系列操作。其中食物坐標我們用了一個結構體:

1typedef struct
2{

3    int x;
4    int y;
5}COORDINATE;

該結構體兩個成員,分別保存坐標的(x, y)。蛇身的坐標也會用到這個結構體。
有關食物類的大體如下:

imageimage

下面我們還是挑幾個重點來講。

3.1 隨機生成食物

隨機生成食物,原則上不允許食物出現在蛇身的位置上,如果有。我們重新生成。注意地圖的范圍,就是區域左邊一塊。實際情況根據自身的地圖范圍來調整食物坐標的范圍,注意不要越界。用rand()函數獲得隨機坐標。代碼如下:

 1void RandomXY(vector<COORDINATE> & coord)
2
{
3    m_coordinate.x = rand() % (GameSetting::window_width - 30) + 1;
4    m_coordinate.y = rand() % (GameSetting::window_height - 2) + 1;
5    unsigned int i;
6    //原則上不允許食物出現在蛇的位置上,如果有,重新生成
7    for (i = 0; i < coord.size(); i++)
8    {
9        //食物出現在蛇身的位置上。重新生成
10        if (coord[i].x == m_coordinate.x && coord[i].y == m_coordinate.y)
11        {
12            m_coordinate.x = rand() % (GameSetting::window_width - 30) + 1;
13            m_coordinate.y = rand() % (GameSetting::window_height - 2) + 1;
14            i = 0;
15        }
16    }
17}

然后,在構造函數里面傳入蛇身的坐標。即可生成食物。

3.2 畫出食物

畫出食物比較簡單了,gotoxy到隨機生成的坐標之后,cout就行。我們在這還設置了一個食物顏色為紅色。代碼如下:

1void DrawFood()
2
{
3    setColor(120);
4    gotoxy(m_coordinate.x, m_coordinate.y);
5    cout << "@";
6    setColor(70);
7}

04 貪吃蛇類

定義貪吃蛇的移動,打印,吃食物等等。這節課我們暫時不討論AI功能,先把手動操作的貪吃蛇做了跑起來,下節課再做AI功能的介紹。該類大體如下:

imageimage

4.1 成員變量

成員變量m_direction記錄每次移動的方向。m_is_alive記錄貪吃蛇是否還活着。m_coordinate則是貪吃蛇身體坐標的記錄。貪吃蛇是一節一節的,整條蛇必然是由許多節組成的。因此用了一個vector來存儲蛇身,每節類型是COORDINATE結構體的。

4.2 默認構造函數

默認構造函數Snake()里面主要是做了初始貪吃蛇的生成,以及移動方向的定義等。初始的蛇為3節。在中間位置,向上移動。代碼如下:

 1        //移動方向向上
2    m_direction = 1;
3    m_is_alive = true;
4    COORDINATE snake_head;
5    //蛇頭生成位置
6    snake_head.x = GameSetting::window_width / 2 - 15;
7    snake_head.y = GameSetting::window_height / 2;
8
9    this->m_coordinate.push_back(snake_head);
10    snake_head.y++;
11    this->m_coordinate.push_back(snake_head);
12    snake_head.y++;
13    this->m_coordinate.push_back(snake_head); //初始蛇身長度三節

4.3 監聽鍵盤

監聽鍵盤用了C里面的一個庫函數。_kbhit()非阻塞函數,可以不斷監聽鍵盤的情況從而不產生阻塞。有鍵盤按下的時候,就獲取按下的鍵盤是哪個。然后做出相應的變化,其實是方向的調整。需要注意的是,當我們的蛇往上走的時候,按下方向的鍵,我們是不做處理的。其它方向一樣。還有一個調整游戲速度的,speed是休眠時間,speed越小,速度越快。反之速度越慢。

 1    //監聽鍵盤
2void listen_key_borad()
3
{
4    char ch;
5
6    if (_kbhit())                    //kbhit 非阻塞函數 
7    {
8        ch = _getch();    //使用 getch 函數獲取鍵盤輸入 
9        switch (ch)
10        {
11        case 'w':
12        case 'W':
13            if (this->m_direction == DOWN)
14                break;
15            this->m_direction = UP;
16            break;
17        case 's':
18        case 'S':
19            if (this->m_direction == UP)
20                break;
21            this->m_direction = DOWN;
22            break;
23        case 'a':
24        case 'A':
25            if (this->m_direction == RIGHT)
26                break;
27            this->m_direction = LEFT;
28            break;
29        case 'd':
30        case 'D':
31            if (this->m_direction == LEFT)
32                break;
33            this->m_direction = RIGHT;
34            break;
35        case '+':
36            if (speed >= 25)
37            {
38                speed -= 25;
39            }
40            break;
41        case '-':
42            if (speed < 250)
43            {
44                speed += 25;
45            }
46            break;
47        }
48    }
49}

4.4 移動貪吃蛇

移動貪吃蛇,我們用了一個方向變量,在監聽鍵盤的時候獲取移動的方向,然后在根據方向移動貪吃蛇的蛇頭。這里的移動我們是這樣處理的,首先,貪吃蛇每移動一次,需要改變的只有蛇頭和蛇尾兩節。我們只需要把新的蛇頭插進去,最后再畫出來就可以了。至於蛇尾,如果我們不刪除蛇尾的話,蛇會不斷變長的。因此我們的做法是:吃到食物的時候插入蛇頭而不刪除蛇尾,沒有吃到食物的時候插入蛇頭同時刪除蛇尾。這樣就完美搞定了。

 1    //移動貪吃蛇
2void move_snake()
3
{
4    //監聽鍵盤
5    listen_key_borad();
6    //蛇頭
7    COORDINATE head = m_coordinate[0];
8    //direction方向:1 上  2 下  3 左  4 右
9    switch (this->m_direction)
10    {
11    case UP:
12        head.y--;
13        break;
14    case DOWN:
15        head.y++;
16        break;
17    case LEFT:
18        head.x--;
19        break;
20    case RIGHT:
21        head.x++;
22        break;
23    }
24    //插入移動后新的蛇頭。是否刪除蛇尾,在后續吃到食物判斷那里做
25    m_coordinate.insert(m_coordinate.begin(), head);
26}

4.5 是否吃到食物

判斷是否吃到食物,就是看看蛇頭的坐標等不等於食物的坐標。如果等於,就重新生成食物,不刪除蛇尾,蛇變長一節。不等於,就刪除蛇尾,蛇長不變。

 1bool is_eat_food(Food & f)
2
{
3    //獲取食物坐標
4    COORDINATE food_coordinate = f.GetFoodCoordinate();
5    //吃到食物,食物重新生成,不刪除蛇尾
6    if (m_coordinate[HEAD].x == food_coordinate.x && m_coordinate[HEAD].y == food_coordinate.y)
7    {
8        f.RandomXY(m_coordinate);
9        return true;
10    }
11    else
12    {
13        //沒有吃到食物,刪除蛇尾
14        m_coordinate.erase(m_coordinate.end() - 1);
15        return false;
16    }
17}

4.6判斷蛇是否還存活

判斷蛇是否GG,主要是看是否超出邊界,是否碰到自己身體其他部分。

 1//判斷貪吃蛇死了沒
2bool snake_is_alive()
3
{
4    if (m_coordinate[HEAD].x <= 0 ||
5        m_coordinate[HEAD].x >= GameSetting::window_width - 29 ||
6        m_coordinate[HEAD].y <= 0 ||
7        m_coordinate[HEAD].y >= GameSetting::window_height - 1)
8    {
9        //超出邊界
10        m_is_alive = false;
11        return m_is_alive;
12    }
13    //和自己碰到一起
14    for (unsigned int i = 1; i < m_coordinate.size(); i++)
15    {
16        if (m_coordinate[i].x == m_coordinate[HEAD].x && m_coordinate[i].y == m_coordinate[HEAD].y)
17        {
18            m_is_alive = false;
19            return m_is_alive;
20        }
21    }
22    m_is_alive = true;
23
24    return m_is_alive;
25}

4.7 畫出貪吃蛇

畫出貪吃蛇比較簡單,gotoxy到身體的每一節,然后cout就行。在此之前設置了顏色為淺綠色。

 1//畫出貪吃蛇
2void draw_snake()
3
{
4    //設置顏色為淺綠色
5    setColor(100);
6    for (unsigned int i = 0; i < this->m_coordinate.size(); i++)
7    {
8        gotoxy(m_coordinate[i].x, m_coordinate[i].y);
9        cout << "*";
10    }
11    //恢復原來的顏色
12    setColor(70);
13}

4.8 清除屏幕上的貪吃蛇

我們是死循環不斷刷新打印貪吃蛇的,因此每移動一次,必然會在屏幕上留下上一次貪吃蛇的痕跡。因此我們每次在畫蛇之前,不是添足,而是清理一下上次遺留的蛇身。我們知道,蛇每次移動,變的只有蛇頭和蛇尾,因此該函數我們只需要清理蛇尾就行。gotoxy到蛇尾的坐標,cout<<" ";就行。

1gotoxy(m_coordinate[this->m_coordinate.size()-1].x, m_coordinate[this->m_coordinate.size() - 1].y);
2cout << " ";

05 主函數,組裝我們的游戲

我們的游戲在主函數里面進行組裝。然后開始運行。
首先我們做游戲相關的初始化和模式選擇。

 1GameSetting setting;
2PrintInfo print_info;
3Snake  snake;
4//初始化游戲
5setting.GameInit();
6//游戲模式選擇
7print_info.DrawChoiceInfo();
8
9char ch = _getch();
10switch (ch)
11{
12case '1':
13    snake.set_model(true);
14    speed = 50;
15    break;
16case '2':
17    snake.set_model(false);
18    break;
19default:
20    gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 + 3);
21    cout << "輸入錯誤,Bye!" << endl;
22    cin.get();
23    cin.get();
24    return 0;
25}
26gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 + 3);
27system("pause");

然后就是畫地圖邊框,打印游戲相關信息和說明。生成食物了。

1//畫地圖
2print_info.DrawMap();
3print_info.DrawGameInfo(snake.GetModel());
4//生成食物
5Food food(snake.m_coordinate);

最后就是游戲死循環,在死循環里面,我們需要不斷移動蛇,畫蛇,判斷蛇的狀態,判斷食物的狀態,是否吃到食物等等。具體代碼:

 1//游戲死循環
2while (true)
3{
4    //打印成績
5    print_info.DrawScore(snake.GetSnakeSize());
6    //畫出食物
7    food.DrawFood();
8    //清理蛇尾,每次畫蛇前必做
9    snake.ClearSnake();
10    //判斷是否吃到食物
11    snake.is_eat_food(food);
12    //根據用戶模式選擇不同的調度方式
13    if (snake.GetModel() == true)
14    {
15        snake.move_snake();
16    }
17    else
18    {
19        snake.AI_find_path(food);
20        snake.AI_move_snake();
21    }
22    //畫蛇
23    snake.draw_snake();
24    //判斷蛇是否還活着
25    if (!snake.snake_is_alive())
26    {
27        print_info.GameOver(snake.GetSnakeSize());
28        break;
29    }
30    //控制游戲速度
31    Sleep(speed);
32}

最終,我們的代碼就可以Run起來了。具體效果放在開頭了。界面算不上好看,但是整個程序向大家展示了最基本最核心的功能和代碼,大家可以在這個基礎上開發自己喜歡的各種美麗的界面哦。

06 AI部分和完善

代碼是畫了幾天間間斷斷寫出來的,水平不算很高,代碼也寫得亂七八糟的。不過代碼會在后期不斷優化,盡量做到精簡優美。至於AI功能,等下一篇博文寫吧。

代碼獲取

欲獲取代碼,請關注我們的微信公眾號【程序猿聲】,在后台回復:aisnake。即可下載。

微信公眾號微信公眾號

推薦文章:10分鍾教你用Python做個打飛機小游戲超詳細教程

推薦文章:10分鍾教你用python下載和拼接微信好友頭像圖片

推薦文章:10分鍾教你用python一行代碼搞點大新聞

推薦文章:10分鍾教你用python打造貪吃蛇超詳細教程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM