c++經典專案控制檯貪吃蛇小遊戲詳細教程

silence1772發表於2017-02-12

貪吃蛇GreedySnake

本文將講解如何使用c++物件導向方法編寫控制檯版貪吃蛇小遊戲,專案github地址:silence1772/GreedySnake
遊戲下載:GreedySnake
本人屬初學者,水平所限,難免有所錯誤及不妥之處,勞請指出或發表意見,本人定當及時加以改正。

  • 本文所有程式碼在code::blocks使用c++11標準編譯通過,未測試在其它環境下使用情況

遊戲截圖

開始介面
遊戲結束
開始動畫:
開始動畫
遊戲過程:
遊戲過程

遊戲架構設計

該遊戲的玩法簡單,玩家通過鍵盤方向鍵控制蛇上下左右移動,吃到食物得分並增長,碰到牆或者自己的身體則死亡,遊戲結束。
整個遊戲其實就是一個無窮的迴圈,直到退出遊戲時退出迴圈。我們暫且將這個迴圈稱為一級迴圈,這個迴圈包含遊戲開始動畫,遊戲難度選擇,遊戲過程這三個子模組,其中游戲過程這個模組亦是一個迴圈,我們暫且將其稱為二級迴圈。它們之間的關係大致如下圖:
這裡寫圖片描述
現在我們根據上圖進行細化,對各個模組的實現進行簡單描述。

1.遊戲開始動畫

開始動畫的實現主要依靠對點的操作來實現,這裡我們先建立一個概念,就是將控制檯介面看成一個原點在左上角的座標系,一個點(x,y)表示座標系中的一個格子,如下圖所示:
這裡寫圖片描述
我們的開始動畫是由一條蛇和一行文字從左到右移動而成,這裡我們先單獨討論一下蛇,要達到移動的效果,我採取的策略是將整個過程分為三部分:
第一部分為蛇從左邊開始出現到整個身體完全出現
第二部分為蛇身整體從左移動到接觸右邊界的過程
第三部分為蛇從接觸右邊界到完全消失的過程
這裡寫圖片描述

我們先來看一下第一部分,這一部分的實現首先是建立一個deque雙端佇列,用於儲存點的物件,這些點就是組成蛇身的元素,然後再用一個for迴圈將容器中的點依次列印出來,每列印一個點停頓一會,這樣就達到了移動的效果。全部列印完後就到了第二部分,這部分蛇的每次前進都是通過計算將要移動到的下一個點的座標,然後將這個點列印出來,與此同時將蛇尾,亦即queue中的首端點去掉,並擦除螢幕上該點顏色。第三部分就直接依次從蛇尾擦除即可。
同理,文字snake的移動也基本類似,稍微改動即可,因為無需對首尾進行操作,而是要對所以點進行移動,因此容器選用vector。
具體請參看startinterface.h以及startinterface.cpp

2.選擇難度

其實這個模組很簡單,我就簡單介紹一下,先將難度選擇的文字資訊列印在螢幕上,然後通過控制鍵盤方向鍵選擇,Enter鍵確認,為了突出選中項,需要給選中項打上背景色,然後每一次上下移動時,先將當前的背景色去掉,然後給下一個選中項打上背景色,按下回車後通過改變蛇移動的速度實現改變難度。其中讀取鍵盤輸入是通過getch()函式完成的。
這裡寫圖片描述

3.遊戲過程

這個模組就是整個遊戲最主要的部分了,首先它先繪製出地圖以及側邊欄,同時初始化蛇和食物,然後通過一個無窮迴圈監聽鍵盤,以此來控制蛇移動,同時又進行各種判斷,來判斷是否死亡、吃到食物或暫停。需要提一下,這裡使用kbhit()函式來監聽鍵盤,它用來判斷在一段固定的時間內是否有鍵盤輸入,要知道,這個函式的返回值有兩個,第一個是是否有輸入的返回值,第二個才是鍵盤輸入的內容,也就是說要經過兩次的讀取緩衝區才能讀到真正的鍵盤輸入。
這裡寫圖片描述

遊戲程式碼實現

從這裡開始我們就可以真正動手來實現遊戲了,在動手之前,我建議先下載遊戲來玩幾局,弄清整個遊戲的邏輯,這樣更能有一個清晰的思路。
接著你可以將以下的程式碼或者github上的程式碼按下面幾張圖的流程新增進工程裡,當然如果你使用其他IDE的話就按照它的方式來弄,然後進行編譯試一下。

首先新建工程
這裡寫圖片描述

然後將檔案一個一個新增進工程裡

這裡寫圖片描述

最後所有檔案新增完就是這樣了

這裡寫圖片描述

記得要把編譯器改成c++11標準

這裡寫圖片描述

完成了以上幾步後就可以點編譯按鈕進行編譯,同時執行一下,看看效果。然後閱讀原始碼或者修改一下,看看編譯後有什麼不同。
這裡程式碼.h檔案是類的定義,.cpp檔案是類的實現。整個程式共有七個類,分別為Tools,Point,StartInterface,Snake,Map,Food。
因為整個遊戲需要對於點的大量操作,所以建立Tools和Point兩個類,Tools工具類主要是用於設定游標的位置以及輸出文字的顏色,Point類設定點的物件,因為其他類都是建立在這兩個類的基礎上的,所以閱讀程式碼時要先看這兩個。然後才開始從main.cpp開始看,一行一行,看到出現新的類就轉到該類的宣告與定義檔案去看,這樣閱讀起來比較清晰,這裡簡要說明一下各個類的功能,Controller類就是控制整個遊戲過程的,包括遊戲的各個階段,比如更新分數,遊戲難度選擇等;Food類實現食物的隨機出現;Map類負責繪製地圖,我由於時間關係(主要是懶)沒有加入地圖,只有邊界,但原理和邊界是一模一樣的,同樣是將點繪製出來,然後每一次都判斷蛇是否撞到地圖即可;Snake類控制蛇的移動和吃到食物等。
各個類之間的關係大致如下:

這裡寫圖片描述

以下是實現的程式碼

main.cpp

#include "controller.h"

int main()//程式入口
{
    Controller c;//宣告一個Controller類
    c.Game();//整個遊戲迴圈
    return 0;
}

controller.h

#ifndef CONTROLLER_H
#define CONTROLLER_H


class Controller
{
public:
    Controller() : speed(200), key(1), score(0) {}
    void Start();
    void Select();
    void DrawGame();
    int PlayGame();
    void UpdateScore(const int&);
    void RewriteScore();
    int Menu();
    void Game();
    int GameOver();
private:
    int speed;
    int key;
    int score;
};
#endif // CONTROLLER_H

controller.cpp

#include <iostream>
#include <time.h>
#include <conio.h>
#include <windows.h>
#include "controller.h"
#include "tools.h"
#include "startinterface.h"
#include "map.h"
#include "snake.h"
#include "food.h"

void Controller::Start()//開始介面
{
    SetWindowSize(41, 32);//設定視窗大小
    SetColor(2);//設定開始動畫顏色
    StartInterface *start = new StartInterface();//動態分配一個StartInterface類start
    start->Action();//開始動畫
    delete start;//釋放記憶體空間

    /*設定關標位置,並輸出提示語,等待任意鍵輸入結束*/
    SetCursorPosition(13, 26);
    std::cout << "Press any key to start... " ;
    SetCursorPosition(13, 27);
    system("pause");
}

void Controller::Select()//選擇介面
{
    /*初始化介面選項*/
    SetColor(3);
    SetCursorPosition(13, 26);
    std::cout << "                          " ;
    SetCursorPosition(13, 27);
    std::cout << "                          " ;
    SetCursorPosition(6, 21);
    std::cout << "請選擇遊戲難度:" ;
    SetCursorPosition(6, 22);
    std::cout << "(上下鍵選擇,回車確認)" ;
    SetCursorPosition(27, 22);
    SetBackColor();//第一個選項設定背景色以表示當前選中
    std::cout << "簡單模式" ;
    SetCursorPosition(27, 24);
    SetColor(3);
    std::cout << "普通模式" ;
    SetCursorPosition(27, 26);
    std::cout << "困難模式" ;
    SetCursorPosition(27, 28);
    std::cout << "煉獄模式" ;
    SetCursorPosition(0, 31);
    score = 0;

    /*上下方向鍵選擇模組*/
    int ch;//記錄鍵入值
    key = 1;//記錄選中項,初始選擇第一個
    bool flag = false;//記錄是否鍵入Enter鍵標記,初始置為否
    while ((ch = getch()))
    {
        switch (ch)//檢測輸入鍵
        {
        case 72://UP上方向鍵
            if (key > 1)//當此時選中項為第一項時,UP上方向鍵無效
            {
                switch (key)
                {
                case 2:
                    SetCursorPosition(27, 22);//給待選中項設定背景色
                    SetBackColor();
                    std::cout << "簡單模式" ;

                    SetCursorPosition(27, 24);//將已選中項取消我背景色
                    SetColor(3);
                    std::cout << "普通模式" ;

                    --key;
                    break;
                case 3:
                    SetCursorPosition(27, 24);
                    SetBackColor();
                    std::cout << "普通模式" ;

                    SetCursorPosition(27, 26);
                    SetColor(3);
                    std::cout << "困難模式" ;

                    --key;
                    break;
                case 4:
                    SetCursorPosition(27, 26);
                    SetBackColor();
                    std::cout << "困難模式" ;

                    SetCursorPosition(27, 28);
                    SetColor(3);
                    std::cout << "煉獄模式" ;

                    --key;
                    break;
                }
            }
            break;

        case 80://DOWN下方向鍵
            if (key < 4)
            {
                switch (key)
                {
                case 1:
                    SetCursorPosition(27, 24);
                    SetBackColor();
                    std::cout << "普通模式" ;
                    SetCursorPosition(27, 22);
                    SetColor(3);
                    std::cout << "簡單模式" ;

                    ++key;
                    break;
                case 2:
                    SetCursorPosition(27, 26);
                    SetBackColor();
                    std::cout << "困難模式" ;
                    SetCursorPosition(27, 24);
                    SetColor(3);
                    std::cout << "普通模式" ;

                    ++key;
                    break;
                case 3:
                    SetCursorPosition(27, 28);
                    SetBackColor();
                    std::cout << "煉獄模式" ;
                    SetCursorPosition(27, 26);
                    SetColor(3);
                    std::cout << "困難模式" ;

                    ++key;
                    break;
                }
            }
            break;

        case 13://EnterEnter鍵
            flag = true;
            break;
        default://無效按鍵
            break;
        }
        if (flag) break;//輸入EnterEnter鍵確認,退出檢查輸入迴圈

        SetCursorPosition(0, 31);//將游標置於左下角,避免關標閃爍影響遊戲體驗
    }

    switch (key)//根據所選選項設定蛇的移動速度,speed值越小,速度越快
    {
    case 1:
        speed = 135;
        break;
    case 2:
        speed = 100;
        break;
    case 3:
        speed = 60;
        break;
    case 4:
        speed = 30;
        break;
    default:
        break;
    }
}

void Controller::DrawGame()//繪製遊戲介面
{
    system("cls");//清屏

    /*繪製地圖*/
    SetColor(3);
    Map *init_map = new Map();
    init_map->PrintInitmap();
    delete init_map;

    /*繪製側邊欄*/
    SetColor(3);
    SetCursorPosition(33, 1);
    std::cout << "Greedy Snake" ;
    SetCursorPosition(34, 2);
    std::cout << "貪吃蛇" ;
    SetCursorPosition(31, 4);
    std::cout << "難度:" ;
    SetCursorPosition(36, 5);
    switch (key)
    {
    case 1:
        std::cout << "簡單模式" ;
        break;
    case 2:
        std::cout << "普通模式" ;
        break;
    case 3:
        std::cout << "困難模式" ;
        break;
    case 4:
        std::cout << "煉獄模式" ;
        break;
    default:
        break;
    }
    SetCursorPosition(31, 7);
    std::cout << "得分:" ;
    SetCursorPosition(37, 8);
    std::cout << "     0" ;
    SetCursorPosition(33, 13);
    std::cout << " 方向鍵移動" ;
    SetCursorPosition(33, 15);
    std::cout << " ESC鍵暫停" ;
}

int Controller::PlayGame()//遊戲二級迴圈
{
    /*初始化蛇和食物*/
    Snake *csnake = new Snake();
    Food *cfood = new Food();
    SetColor(6);
    csnake->InitSnake();
    srand((unsigned)time(NULL));//設定隨機數種子,如果沒有 食物的出現位置將會固定
    cfood->DrawFood(*csnake);

    /*遊戲迴圈*/
    while (csnake->OverEdge() && csnake->HitItself()) //判斷是否撞牆或撞到自身,即是否還有生命
    {
        /*調出選擇選單*/
        if (!csnake->ChangeDirection()) //按Esc鍵時
        {
            int tmp = Menu();//繪製選單,並得到返回值
            switch (tmp)
            {
            case 1://繼續遊戲
                break;

            case 2://重新開始
                delete csnake;
                delete cfood;
                return 1;//將1作為PlayGame函式的返回值返回到Game函式中,表示重新開始

            case 3://退出遊戲
                delete csnake;
                delete cfood;
                return 2;//將2作為PlayGame函式的返回值返回到Game函式中,表示退出遊戲

            default:
                break;
            }
        }

        if (csnake->GetFood(*cfood)) //吃到食物
        {
            csnake->Move();//蛇增長
            UpdateScore(1);//更新分數,1為分數權重
            RewriteScore();//重新繪製分數
            cfood->DrawFood(*csnake);//繪製新食物
        }
        else
        {
            csnake->NormalMove();//蛇正常移動
        }

        if (csnake->GetBigFood(*cfood)) //吃到限時食物
        {
            csnake->Move();
            UpdateScore(cfood->GetProgressBar()/5);//分數根據限時食物進度條確定
            RewriteScore();
        }

        if (cfood->GetBigFlag()) //如果此時有限時食物,閃爍它
        {
            cfood->FlashBigFood();
        }

        Sleep(speed);//製造蛇的移動效果
    }

    /*蛇死亡*/
    delete csnake;//釋放分配的記憶體空間
    delete cfood;
    int tmp = GameOver();//繪製遊戲結束介面,並返回所選項
    switch (tmp)
    {
    case 1:
        return 1;//重新開始
    case 2:
        return 2;//退出遊戲
    default:
        return 2;
    }
}

void Controller::UpdateScore(const int& tmp)//更新分數
{
    score += key * 10 * tmp;//所得分數根據遊戲難度及傳人的引數tmp確定
}

void Controller::RewriteScore()//重繪分數
{
    /*為保持分數尾部對齊,將最大分數設定為6位,計算當前分數位數,將剩餘位數用空格補全,再輸出分數*/
    SetCursorPosition(37, 8);
    SetColor(11);
    int bit = 0;
    int tmp = score;
    while (tmp != 0)
    {
        ++bit;
        tmp /= 10;
    }
    for (int i = 0; i < (6 - bit); ++i)
    {
        std::cout << " " ;
    }
    std::cout << score ;
}

int Controller::Menu()//選擇選單
{
    /*繪製選單*/
    SetColor(11);
    SetCursorPosition(32, 19);
    std::cout << "選單:" ;
    Sleep(100);
    SetCursorPosition(34, 21);
    SetBackColor();
    std::cout << "繼續遊戲" ;
    Sleep(100);
    SetCursorPosition(34, 23);
    SetColor(11);
    std::cout << "重新開始" ;
    Sleep(100);
    SetCursorPosition(34, 25);
    std::cout << "退出遊戲" ;
    SetCursorPosition(0, 31);

    /*選擇部分*/
    int ch;
    int tmp_key = 1;
    bool flag = false;
    while ((ch = getch()))
    {
        switch (ch)
        {
        case 72://UP
            if (tmp_key > 1)
            {
                switch (tmp_key)
                {
                case 2:
                    SetCursorPosition(34, 21);
                    SetBackColor();
                    std::cout << "繼續遊戲" ;
                    SetCursorPosition(34, 23);
                    SetColor(11);
                    std::cout << "重新開始" ;

                    --tmp_key;
                    break;
                case 3:
                    SetCursorPosition(34, 23);
                    SetBackColor();
                    std::cout << "重新開始" ;
                    SetCursorPosition(34, 25);
                    SetColor(11);
                    std::cout << "退出遊戲" ;

                    --tmp_key;
                    break;
                }
            }
            break;

        case 80://DOWN
            if (tmp_key < 3)
            {
                switch (tmp_key)
                {
                case 1:
                    SetCursorPosition(34, 23);
                    SetBackColor();
                    std::cout << "重新開始" ;
                    SetCursorPosition(34, 21);
                    SetColor(11);
                    std::cout << "繼續遊戲" ;

                    ++tmp_key;
                    break;
                case 2:
                    SetCursorPosition(34, 25);
                    SetBackColor();
                    std::cout << "退出遊戲" ;
                    SetCursorPosition(34, 23);
                    SetColor(11);
                    std::cout << "重新開始" ;

                    ++tmp_key;
                    break;
                }
            }
            break;

        case 13://Enter
            flag = true;
            break;

        default:
            break;
        }

        if (flag)
        {
            break;
        }
        SetCursorPosition(0, 31);
    }

    if (tmp_key == 1) //選擇繼續遊戲,則將選單擦除
    {
        SetCursorPosition(32, 19);
        std::cout << "      " ;
        SetCursorPosition(34, 21);
        std::cout << "        ";
        SetCursorPosition(34, 23);
        std::cout << "        ";
        SetCursorPosition(34, 25);
        std::cout << "        ";
    }
    return tmp_key;
}

void Controller::Game()//遊戲一級迴圈
{
    Start();//開始介面
    while (true)//遊戲可視為一個死迴圈,直到退出遊戲時迴圈結束
    {
        Select();//選擇介面
        DrawGame();//繪製遊戲介面
        int tmp = PlayGame();//開啟遊戲迴圈,當重新開始或退出遊戲時,結束迴圈並返回值給tmp
        if (tmp == 1) //返回值為1時重新開始遊戲
        {
            system("cls");
            continue;
        }
        else if (tmp == 2) //返回值為2時退出遊戲
        {
            break;
        }
        else
        {
            break;
        }
    }
}

int Controller::GameOver()//遊戲結束介面
{
    /*繪製遊戲結束介面*/
    Sleep(500);
    SetColor(11);
    SetCursorPosition(10, 8);
    std::cout << "━━━━━━━━━━━━━━━━━━━━━━" ;
    Sleep(30);
    SetCursorPosition(9, 9);
    std::cout << " ┃               Game Over !!!              ┃" ;
    Sleep(30);
    SetCursorPosition(9, 10);
    std::cout << " ┃                                          ┃" ;
    Sleep(30);
    SetCursorPosition(9, 11);
    std::cout << " ┃              很遺憾!你掛了              ┃" ;
    Sleep(30);
    SetCursorPosition(9, 12);
    std::cout << " ┃                                          ┃" ;
    Sleep(30);
    SetCursorPosition(9, 13);
    std::cout << " ┃             你的分數為:                 ┃" ;
    SetCursorPosition(24, 13);
    std::cout << score ;
    Sleep(30);
    SetCursorPosition(9, 14);
    std::cout << " ┃                                          ┃" ;
    Sleep(30);
    SetCursorPosition(9, 15);
    std::cout << " ┃   是否再來一局?                         ┃" ;
    Sleep(30);
    SetCursorPosition(9, 16);
    std::cout << " ┃                                          ┃" ;
    Sleep(30);
    SetCursorPosition(9, 17);
    std::cout << " ┃                                          ┃" ;
    Sleep(30);
    SetCursorPosition(9, 18);
    std::cout << " ┃    嗯,好的        不了,還是學習有意思  ┃" ;
    Sleep(30);
    SetCursorPosition(9, 19);
    std::cout << " ┃                                          ┃" ;
    Sleep(30);
    SetCursorPosition(9, 20);
    std::cout << " ┃                                          ┃" ;
    Sleep(30);
    SetCursorPosition(10, 21);
    std::cout << "━━━━━━━━━━━━━━━━━━━━━━" ;

    Sleep(100);
    SetCursorPosition(12, 18);
    SetBackColor();
    std::cout << "嗯,好的" ;
    SetCursorPosition(0, 31);

    /*選擇部分*/
    int ch;
    int tmp_key = 1;
    bool flag = false;
    while ((ch = getch()))
    {
        switch (ch)
        {
        case 75://LEFT
            if (tmp_key > 1)
            {
                SetCursorPosition(12, 18);
                SetBackColor();
                std::cout << "嗯,好的" ;
                SetCursorPosition(20, 18);
                SetColor(11);
                std::cout << "不了,還是學習有意思" ;
                --tmp_key;
            }
            break;

        case 77://RIGHT
            if (tmp_key < 2)
            {
                SetCursorPosition(20, 18);
                SetBackColor();
                std::cout << "不了,還是學習有意思" ;
                SetCursorPosition(12, 18);
                SetColor(11);
                std::cout << "嗯,好的" ;
                ++tmp_key;
            }
            break;

        case 13://Enter
            flag = true;
            break;

        default:
            break;
        }

        SetCursorPosition(0, 31);
        if (flag) {
            break;
        }
    }

    SetColor(11);
    switch (tmp_key)
    {
    case 1:
        return 1;//重新開始
    case 2:
        return 2;//退出遊戲
    default:
        return 1;
    }
}

food.h

#ifndef FOOD_H
#define FOOD_H

#include "snake.h"
class Snake;
class Food
{
public:
    Food() : cnt(0), flash_flag(false), big_flag(false), x(0), y(0), big_x(0), big_y(0), progress_bar(0) {}
    void DrawFood(Snake&);
    void DrawBigFood(Snake&);
    int GetCnt();
    void FlashBigFood();
    bool GetBigFlag();
    int GetProgressBar();
private:
    int cnt;
    bool flash_flag;//閃爍標記
    bool big_flag;//是否有限時食物標記
    int x, y;
    int big_x, big_y;
    int progress_bar;//限時食物進度條
    friend class Snake;
};
#endif // FOOD_H

food.cpp

#include "food.h"
#include "tools.h"
#include <cstdlib>
#include <iostream>


void Food::DrawFood(Snake& csnake)//繪製食物
{
    /*利用rand函式獲得座標,並將其範圍限制在2-29內,即在地圖內,如果獲得的座標與蛇身重疊,則重新獲取。
      同時每5顆食物就出現一顆限時食物*/
    while (true)
    {
        int tmp_x = rand() % 30;
        int tmp_y = rand() % 30;
        if(tmp_x < 2) tmp_x += 2;
        if(tmp_y < 2) tmp_y += 2;
        bool flag = false;
        for (auto& point : csnake.snake)
        {
            if ((point.GetX() == tmp_x && point.GetY() == tmp_y) || (tmp_x == big_x && tmp_y == big_y)) {
                flag = true;
                break;
            }
        }
        if (flag)
            continue;
        x = tmp_x;
        y = tmp_y;
        SetCursorPosition(x, y);
        SetColor(13);
        std::cout << "★" ;
        ++cnt;
        cnt %= 5;
        if (cnt == 0)
        {
            DrawBigFood(csnake);
        }
        break;
    }
}

void Food::DrawBigFood(Snake& csnake)//繪製限時食物
{
    SetCursorPosition(5, 0);
    SetColor(11);
    std::cout << "------------------------------------------" ;//進度條
    progress_bar = 42;
    while (true)
    {
        int tmp_x = rand() % 30;
        int tmp_y = rand() % 30;
        if(tmp_x < 2) tmp_x += 2;
        if(tmp_y < 2) tmp_y += 2;
        bool flag = false;
        for (auto& point : csnake.snake)
        {
            if ((point.GetX() == tmp_x && point.GetY() == tmp_y) || (tmp_x == x && tmp_y == y))
            {
                flag = true;
                break;
            }
        }
        if (flag)
            continue;

        big_x = tmp_x;
        big_y = tmp_y;
        SetCursorPosition(big_x, big_y);
        SetColor(18);
        std::cout << "■" ;
        big_flag = true;
        flash_flag = true;
        break;
    }
}

int Food::GetCnt()
{
    return cnt;
}

void Food::FlashBigFood()//閃爍限時食物
{
    SetCursorPosition(big_x, big_y);
    SetColor(18);
    if (flash_flag)
    {
        std::cout << "  " ;
        flash_flag = false;
    }
    else
    {
        std::cout << "■" ;
        flash_flag = true;
    }

    SetCursorPosition(26, 0);
    SetColor(11);
    for (int i = 42; i >= progress_bar; --i)//進度條縮短
        std::cout << "\b \b" ;
    --progress_bar;
    if (progress_bar == 0) {
        SetCursorPosition(big_x, big_y);
        std::cout << "  " ;
        big_flag = false;
        big_x = 0;
        big_y = 0;
    }
}

bool Food::GetBigFlag()
{
    return big_flag;
}

int Food::GetProgressBar()
{
    return progress_bar;
}

map.h

#ifndef MAP_H
#define MAP_H

#include <vector>
#include "point.h"

class Map
{
public:
    Map()//預設建構函式,將正方形各點壓入initmap
    {
        initmap.emplace_back(Point(1, 1));
        initmap.emplace_back(Point(2, 1));
        initmap.emplace_back(Point(3, 1));
        initmap.emplace_back(Point(4, 1));
        initmap.emplace_back(Point(5, 1));
        initmap.emplace_back(Point(6, 1));
        initmap.emplace_back(Point(7, 1));
        initmap.emplace_back(Point(8, 1));
        initmap.emplace_back(Point(9, 1));
        initmap.emplace_back(Point(10, 1));
        initmap.emplace_back(Point(11, 1));
        initmap.emplace_back(Point(12, 1));
        initmap.emplace_back(Point(13, 1));
        initmap.emplace_back(Point(14, 1));
        initmap.emplace_back(Point(15, 1));
        initmap.emplace_back(Point(16, 1));
        initmap.emplace_back(Point(17, 1));
        initmap.emplace_back(Point(18, 1));
        initmap.emplace_back(Point(19, 1));
        initmap.emplace_back(Point(20, 1));
        initmap.emplace_back(Point(21, 1));
        initmap.emplace_back(Point(22, 1));
        initmap.emplace_back(Point(23, 1));
        initmap.emplace_back(Point(24, 1));
        initmap.emplace_back(Point(25, 1));
        initmap.emplace_back(Point(26, 1));
        initmap.emplace_back(Point(27, 1));
        initmap.emplace_back(Point(28, 1));
        initmap.emplace_back(Point(29, 1));
        initmap.emplace_back(Point(30, 1));
        initmap.emplace_back(Point(1, 2));
        initmap.emplace_back(Point(30, 2));
        initmap.emplace_back(Point(1, 3));
        initmap.emplace_back(Point(30, 3));
        initmap.emplace_back(Point(1, 4));
        initmap.emplace_back(Point(30, 4));
        initmap.emplace_back(Point(1, 5));
        initmap.emplace_back(Point(30, 5));
        initmap.emplace_back(Point(1, 6));
        initmap.emplace_back(Point(30, 6));
        initmap.emplace_back(Point(1, 7));
        initmap.emplace_back(Point(30, 7));
        initmap.emplace_back(Point(1, 8));
        initmap.emplace_back(Point(30, 8));
        initmap.emplace_back(Point(1, 9));
        initmap.emplace_back(Point(30, 9));
        initmap.emplace_back(Point(1, 10));
        initmap.emplace_back(Point(30, 10));
        initmap.emplace_back(Point(1, 11));
        initmap.emplace_back(Point(30, 11));
        initmap.emplace_back(Point(1, 12));
        initmap.emplace_back(Point(30, 12));
        initmap.emplace_back(Point(1, 13));
        initmap.emplace_back(Point(30, 13));
        initmap.emplace_back(Point(1, 14));
        initmap.emplace_back(Point(30, 14));
        initmap.emplace_back(Point(1, 15));
        initmap.emplace_back(Point(30, 15));
        initmap.emplace_back(Point(1, 16));
        initmap.emplace_back(Point(30, 16));
        initmap.emplace_back(Point(1, 17));
        initmap.emplace_back(Point(30, 17));
        initmap.emplace_back(Point(1, 18));
        initmap.emplace_back(Point(30, 18));
        initmap.emplace_back(Point(1, 19));
        initmap.emplace_back(Point(30, 19));
        initmap.emplace_back(Point(1, 20));
        initmap.emplace_back(Point(30, 20));
        initmap.emplace_back(Point(1, 21));
        initmap.emplace_back(Point(30, 21));
        initmap.emplace_back(Point(1, 22));
        initmap.emplace_back(Point(30, 22));
        initmap.emplace_back(Point(1, 23));
        initmap.emplace_back(Point(30, 23));
        initmap.emplace_back(Point(1, 24));
        initmap.emplace_back(Point(30, 24));
        initmap.emplace_back(Point(1, 25));
        initmap.emplace_back(Point(30, 25));
        initmap.emplace_back(Point(1, 26));
        initmap.emplace_back(Point(30, 26));
        initmap.emplace_back(Point(1, 27));
        initmap.emplace_back(Point(30, 27));
        initmap.emplace_back(Point(1, 28));
        initmap.emplace_back(Point(30, 28));
        initmap.emplace_back(Point(1, 29));
        initmap.emplace_back(Point(30, 29));
        initmap.emplace_back(Point(1, 30));
        initmap.emplace_back(Point(2, 30));
        initmap.emplace_back(Point(3, 30));
        initmap.emplace_back(Point(4, 30));
        initmap.emplace_back(Point(5, 30));
        initmap.emplace_back(Point(6, 30));
        initmap.emplace_back(Point(7, 30));
        initmap.emplace_back(Point(8, 30));
        initmap.emplace_back(Point(9, 30));
        initmap.emplace_back(Point(10, 30));
        initmap.emplace_back(Point(11, 30));
        initmap.emplace_back(Point(12, 30));
        initmap.emplace_back(Point(13, 30));
        initmap.emplace_back(Point(14, 30));
        initmap.emplace_back(Point(15, 30));
        initmap.emplace_back(Point(16, 30));
        initmap.emplace_back(Point(17, 30));
        initmap.emplace_back(Point(18, 30));
        initmap.emplace_back(Point(19, 30));
        initmap.emplace_back(Point(20, 30));
        initmap.emplace_back(Point(21, 30));
        initmap.emplace_back(Point(22, 30));
        initmap.emplace_back(Point(23, 30));
        initmap.emplace_back(Point(24, 30));
        initmap.emplace_back(Point(25, 30));
        initmap.emplace_back(Point(26, 30));
        initmap.emplace_back(Point(27, 30));
        initmap.emplace_back(Point(28, 30));
        initmap.emplace_back(Point(29, 30));
        initmap.emplace_back(Point(30, 30));
    }
    void PrintInitmap();//繪製初始地圖
private:
    std::vector<Point> initmap;//儲存初始地圖
    /*Map類可自定義多種地圖,只需將表示地圖的各個點儲存在相應的map中,並在Snake類中增加相應判斷撞牆函式即可
    std::vector<Point> map1;
    std::vector<Point> map2;
    */
};
#endif // MAP_H

map.cpp

#include "map.h"
#include <windows.h>

void Map::PrintInitmap()//繪製初始地圖
{
    for (auto& point : initmap)
    {
        point.Print();
        Sleep(10);//呼叫Sleep函式可營造動畫效果
    }
}

point.h

#ifndef POINT_H
#define POINT_H

class Point
{
public:
    Point(){}
    Point(const int x, const int y) : x(x), y(y) {}
    void Print();
    void PrintCircular();
    void Clear();
    void ChangePosition(const int x, const int y);
    bool operator== (const Point& point) { return (point.x == this->x) && (point.y == this->y); }
    int GetX(){ return this->x; }
    int GetY(){ return this->y; }
private:
    int x, y;
};
#endif // POINT_H

point.cpp

#include "point.h"
#include "tools.h"
#include <iostream>

void Point::Print()//輸出方塊
{
    SetCursorPosition(x, y);
    std::cout << "■" ;
}

void Point::PrintCircular()//輸出圓形
{
    SetCursorPosition(x, y);
    std::cout << "●" ;
}

void Point::Clear()//清除輸出
{
    SetCursorPosition(x, y);
    std::cout << "  " ;
}

void Point::ChangePosition(const int x, const int y)//改變座標
{
    this->x = x;
    this->y = y;
}

snake.h

#ifndef SNAKE_H
#define SNAKE_H

#include <deque>
#include "point.h"
#include "food.h"

class Food;
class Snake
{
public:
    enum Direction {UP, DOWN, LEFT, RIGHT};

    Snake() {
        snake.emplace_back(14, 8);
        snake.emplace_back(15, 8);
        snake.emplace_back(16, 8);
        direction = Direction::DOWN;
    }
    void InitSnake();
    void Move();
    void NormalMove();
    bool OverEdge();
    bool HitItself();
    bool ChangeDirection();
    bool GetFood(const Food&);
    bool GetBigFood(Food&);
private:
    std::deque<Point> snake;
    Direction direction;
    friend class Food;//將Food類置為友元,以便訪問其私有元素
};
#endif // SNAKE_H

snake.cpp

#include "snake.h"
#include <conio.h>
#include "tools.h"
#include <iostream>

void Snake::InitSnake()//初始化蛇
{
    for (auto& point : snake)
    {
        point.PrintCircular();
    }
}

void Snake::Move()//蛇增長
{
    switch (direction)
    {
    case Direction::UP:
        snake.emplace_back(Point(snake.back().GetX(), snake.back().GetY() - 1 ));
        break;
    case Direction::DOWN:
        snake.emplace_back(Point(snake.back().GetX(), snake.back().GetY() + 1 ));
        break;
    case Direction::LEFT:
        snake.emplace_back(Point(snake.back().GetX() - 1, snake.back().GetY() ));
        break;
    case Direction::RIGHT:
        snake.emplace_back(Point(snake.back().GetX() + 1, snake.back().GetY() ));
        break;
    default:
        break;
    }
    SetColor(14);
    snake.back().PrintCircular();
}

void Snake::NormalMove()//蛇正常移動,頭增長,尾縮短
{
    Move();
    snake.front().Clear();
    snake.pop_front();
}

bool Snake::OverEdge()//超出邊界
{
    return snake.back().GetX() < 30 &&
           snake.back().GetY() < 30 &&
           snake.back().GetX() > 1  &&
           snake.back().GetY() > 1;
}

bool Snake::HitItself()//撞到自身
{
    std::deque<Point>::size_type cnt = 1;
    Point *head = new Point(snake.back().GetX(), snake.back().GetY());//獲得頭部座標
    for (auto& point : snake) //如果整條蛇中與蛇頭不相同的座標不等於蛇長,則意味著蛇頭碰撞到自身
    {
        if ( !(point == *head) )
            ++cnt;
        else
            break;
    }
    delete head;
    if(cnt == snake.size())
        return true;
    else
        return false;
}

bool Snake::ChangeDirection()//改變方向
{
    char ch;
    if (kbhit())//kbhit函式返回值為兩個,需注意
    {
        ch = getch();
        switch (ch)
        {
        case -32:
            ch = getch();
            switch (ch)
            {
            case 72:
                if (direction != Direction::DOWN)//如果方向與當前運動方向相反,無效
                    direction = Direction::UP;
                break;
            case 80:
                if (direction != Direction::UP)
                    direction = Direction::DOWN;
                break;
            case 75:
                if (direction != Direction::RIGHT)
                    direction = Direction::LEFT;
                break;
            case 77:
                if (direction != Direction::LEFT)
                    direction = Direction::RIGHT;
                break;
            default:
                break;
            }
            return true;

        case 27://ESC
            return false;

        default:
            return true;

        }
    }
    return true;
}

bool Snake::GetFood(const Food& cfood)
{
    if (snake.back().GetX() == cfood.x && snake.back().GetY() == cfood.y)
        return true;
    else
        return false;
}

bool Snake::GetBigFood(Food& cfood)
{
    if (snake.back().GetX() == cfood.big_x && snake.back().GetY() == cfood.big_y)
    {
        cfood.big_flag = false;
        cfood.big_x = 0;
        cfood.big_y = 0;
        SetCursorPosition(1, 0);
        std::cout << "                                                            " ;
        return true;
    }
    else
        return false;
}

startinterface.h

#ifndef STRATINTERFACE_H
#define STARTINTERFACE_H

#include <deque>
#include <vector>
#include "point.h"

class StartInterface
{
public:
    StartInterface() : speed(35) {
        startsnake.emplace_back(Point(0,14));//Éß
        startsnake.emplace_back(Point(1,14));
        startsnake.emplace_back(Point(2,15));
        startsnake.emplace_back(Point(3,16));
        startsnake.emplace_back(Point(4,17));
        startsnake.emplace_back(Point(5,18));
        startsnake.emplace_back(Point(6,17));
        startsnake.emplace_back(Point(7,16));
        startsnake.emplace_back(Point(8,15));
        startsnake.emplace_back(Point(9,14));

        textsnake.emplace_back(Point(-26, 14));//S
        textsnake.emplace_back(Point(-25, 14));
        textsnake.emplace_back(Point(-27, 15));
        textsnake.emplace_back(Point(-26, 16));
        textsnake.emplace_back(Point(-25, 17));
        textsnake.emplace_back(Point(-27, 18));
        textsnake.emplace_back(Point(-26, 18));

        textsnake.emplace_back(Point(-23, 14));//N
        textsnake.emplace_back(Point(-23, 15));
        textsnake.emplace_back(Point(-23, 16));
        textsnake.emplace_back(Point(-23, 17));
        textsnake.emplace_back(Point(-23, 18));
        textsnake.emplace_back(Point(-22, 15));
        textsnake.emplace_back(Point(-21, 16));
        textsnake.emplace_back(Point(-20, 17));
        textsnake.emplace_back(Point(-19, 14));
        textsnake.emplace_back(Point(-19, 15));
        textsnake.emplace_back(Point(-19, 16));
        textsnake.emplace_back(Point(-19, 17));
        textsnake.emplace_back(Point(-19, 18));

        textsnake.emplace_back(Point(-17, 18));//A
        textsnake.emplace_back(Point(-16, 17));
        textsnake.emplace_back(Point(-15, 16));
        textsnake.emplace_back(Point(-14, 15));
        textsnake.emplace_back(Point(-14, 16));
        textsnake.emplace_back(Point(-13, 14));
        textsnake.emplace_back(Point(-13, 16));
        textsnake.emplace_back(Point(-12, 15));
        textsnake.emplace_back(Point(-12, 16));
        textsnake.emplace_back(Point(-11, 16));
        textsnake.emplace_back(Point(-10, 17));
        textsnake.emplace_back(Point(-9, 18));

        textsnake.emplace_back(Point(-7, 14));//K
        textsnake.emplace_back(Point(-7, 15));
        textsnake.emplace_back(Point(-7, 16));
        textsnake.emplace_back(Point(-7, 17));
        textsnake.emplace_back(Point(-7, 18));
        textsnake.emplace_back(Point(-6, 16));
        textsnake.emplace_back(Point(-5, 15));
        textsnake.emplace_back(Point(-5, 17));
        textsnake.emplace_back(Point(-4, 14));
        textsnake.emplace_back(Point(-4, 18));

        textsnake.emplace_back(Point(-2, 14));//E
        textsnake.emplace_back(Point(-2, 15));
        textsnake.emplace_back(Point(-2, 16));
        textsnake.emplace_back(Point(-2, 17));
        textsnake.emplace_back(Point(-2, 18));
        textsnake.emplace_back(Point(-1, 14));
        textsnake.emplace_back(Point(-1, 16));
        textsnake.emplace_back(Point(-1, 18));
        textsnake.emplace_back(Point(0, 14));
        textsnake.emplace_back(Point(0, 16));
        textsnake.emplace_back(Point(0, 18));
    }
    void PrintFirst();
    void PrintSecond();
    void PrintThird();
    void PrintText();
    void ClearText();
    void Action();
private:
    std::deque<Point> startsnake;//開始動畫中的蛇
    std::vector<Point> textsnake;//開始動畫中的文字
    int speed;//動畫的速度
};
#endif // STRATINTERFACE_H

startinterface.cpp

#include "startinterface.h"
#include <windows.h>


void StartInterface::PrintFirst()//蛇從左邊出現到完全出現的過程
{
    for (auto& point : startsnake)
    {
        point.Print();
        Sleep(speed);
    }
}

void StartInterface::PrintSecond()//蛇從左向右移動的過程
{
    for (int i = 10; i != 40; ++i) //蛇頭需要從10移動到40
    {
        /*計算蛇頭的下一個位置,並將其壓入startsnake中,繪製出來,將蛇尾去掉*/
        int j = ( ((i-2)%8) < 4 )?( 15 + (i-2)%8 ) : ( 21 - (i-2)%8 );
        startsnake.emplace_back( Point(i, j) );
        startsnake.back().Print();
        startsnake.front().Clear();
        startsnake.pop_front();
        Sleep(speed);
    }
}

void StartInterface::PrintThird()//蛇從接觸右邊到消失的過程
{
    while ( !startsnake.empty() || textsnake.back().GetX() < 33 ) //當蛇還沒消失或文字沒移動到指定位置
    {
        if ( !startsnake.empty() ) //如果蛇還沒消失,繼續移動
        {
            startsnake.front().Clear();
            startsnake.pop_front();
        }
        ClearText();//清除已有文字
        PrintText();//繪製更新位置後的文字
        Sleep(speed);
    }
}

void StartInterface::PrintText()
{
    for (auto& point : textsnake)
    {
        if (point.GetX() >= 0)
            point.Print();
    }
}

void StartInterface::ClearText()
{
    for (auto& point : textsnake) //清除的同時將文字整體向右移動一格
    {
        if (point.GetX() >= 0)
            point.Clear();
        point.ChangePosition(point.GetX() + 1, point.GetY());
    }
}

void StartInterface::Action()
{
    PrintFirst();
    PrintSecond();
    PrintThird();
}

tools.h

#ifndef TOOLS_H
#define TOOLS_H


void SetWindowSize(int cols, int lines);
void SetCursorPosition(const int x, const int y);
void SetColor(int colorID);
void SetBackColor();

#endif // TOOLS_H

tools.cpp

#include "tools.h"
#include <windows.h>
#include <stdio.h>

void SetWindowSize(int cols, int lines)//設定視窗大小
{
    system("title 貪吃蛇");//設定視窗標題
    char cmd[30];
    sprintf(cmd, "mode con cols=%d lines=%d", cols * 2, lines);//一個圖形■佔兩個字元,故寬度乘以2
    system(cmd);//system(mode con cols=88 lines=88)設定視窗寬度和高度
}

void SetCursorPosition(const int x, const int y)//設定游標位置
{
    COORD position;
    position.X = x * 2;
    position.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}

void SetColor(int colorID)//設定文字顏色
{
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colorID);
}

void SetBackColor()//設定文字背景色
{
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                            FOREGROUND_BLUE |
                            BACKGROUND_BLUE |
                            BACKGROUND_GREEN |
                            BACKGROUND_RED );
}

相關文章