BIGOcean 发表于 2020-7-6 15:19

【C++】编写控制台贪吃蛇游戏

# 介绍
**
背景介绍 :大一上半年用C语言配合easyx写了个图形贪吃蛇作为大作业。下半年学了C++决定用面对对象写一个贪吃蛇作为大作业。(主要是觉得其他的一些xxx管理系统、等等等各种系统写下来无用代码量太大,而且不敢说99%、至少80%的同学都是写管理系统。没意思)
**
**
本体介绍 :贪吃蛇游戏,利用deque双端队列配合循环实现有开场动画的贪吃蛇游戏,玩家通过方向键控制蛇的上下左右方向移动,蛇吃到随机产生的食物得分、蛇身长度加1,每四个星星会触发奖励食物,吃到它加分更多。玩家可以自主选择游戏难度。自吃或碰壁蛇即死亡,游戏结束。
**

# 设计思想

**    整个游戏其实就是一个无穷的循环,直到退出游戏时退出循环。我们暂且将这个循环称为一级循环,这个循环包含游戏开始动画,游戏难度选择,游戏过程这三个子模块,其中游戏过程这个模块亦是一个循环,我们暂且将其称为二级循环。它们之间的关系大致如下图:
**
![关系图](https://gitee.com/bigoceana/cdn/raw/master/img/52pojie/greedysnake/1.png)

# 流程图

![流程图](https://gitee.com/bigoceana/cdn/raw/master/img/52pojie/greedysnake/2.png)

# 模块分析
**   首先是游戏开场动画:开始动画的实现首先需要建立一个概念“将控制台看做原点在左上角的坐标系”,将其窗口看作是无数个(根据我们自己定义)正方形小格子的点所构成的且这些点的坐标看做O(x,y)。如下图:**

![看做坐标系](https://gitee.com/bigoceana/cdn/raw/master/img/52pojie/greedysnake/3.png)

**
   我设计的开场动画是由一条方块蛇和“HQT66”从左向右移动而成的。如何实现它呢?我将这个过程认为可以分为三个部分:①第一部分为蛇从左边开始出现到整个身体完全出现;②第二部分为蛇身整体从左移动到接触右边界的过程;③第三部分为蛇从接触右边界到完全消失的过程。
**
   **其次是难度选择模块:这个模块想要实现很简单,将选中的文字打上背景色即可达到“选中”的效果,通过改变蛇的移动速度(sleep函数值的大小)来实现难度的更改。**
       **
   因为整个游戏其实最根本的需求就是对“点”的各种操作,我建立了七个类,其中两个类“tools”和“point”都是有关对点的操作。其中point担当大任、tools类是用于设置控制台光标的位置(调整光标位置以改善游戏体验)、设置输出文字的颜色。**
       **
   Controller类是游戏的主干类,负责游戏的各个阶段:更新分数、游戏难度调整等等。Food类顾名思义就是控制整个游戏的食物随机出现坐标,同理,map类负责绘制边界(地图)。Snake类就是游戏第二循环的主体了:控制蛇的移动和判断是否吃到食物、是否触发额外奖励等等。
**

# 各个模块关系

![模块关系——类图](https://gitee.com/bigoceana/cdn/raw/master/img/52pojie/greedysnake/4.png)

# 主要模块实现分析
## controller模块的实现

**
   首先声明一个controller类,使整个游戏进入一个循环状态。进入开始界面,new一个开场动画,然后进入一个while循环里,首先绘制选择界面,然后开始选择游戏难度,选择之后进入疯狂的蛇游戏的第二级循环也就是在这时游戏界面进入初始化状态,开始游戏。利用while循环不断地判断蛇是否死亡,同时在这个while里面要通过一个if语句设置一个键盘监听来判断是否玩家按下了esc暂停键。如果暂停就绘制暂停界面,在PlayGame函数中利用switch语句返回的tmp值到Game函数中的if语句里,从而判断玩家接下来是要继续游戏,还是重新游戏,或是结束游戏。还是在playgame函数里可以继续判断蛇是否吃到食物,以及是否有特殊食物生成。以及分数系统(分数根据游戏难度以及参数tmp确定)。通过rewritescore函数可以对分数进行重绘,为保持分数尾部对齐,将最大分数设置为6位,计算当前分数位数,将剩余位数用空格补全,再输出分数。通过这段语句实现{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 ;}最后的controller类里的函数是游戏结束时的菜单绘制,这包含了一个用while循环封装起来的一个选择页面,以判断玩家是否重新开始游戏、退出游戏。
**

## startinterface开场动画模块的实现

**
   开场动画是由一条方块蛇和“HQT66”从左向右移动而成的。如何实现它呢?我将这个过程认为可以分为三个部分:①第一部分为蛇从左边开始出现到整个身体完全出现;②第二部分为蛇身整体从左移动到接触右边界的过程;③第三部分为蛇从接触右边界到完全消失的过程。
我先来说一下第一部分,第一部分的实现首先是建立一个deque双端队列,用于存储点的对象,这些点就是组成蛇身的元素,然后再用一个for循环将容器中的点依次打印出来,每打印一个点停顿一会,这样就达到了移动的效果。全部打印完后就到了第二部分,这部分蛇的每次前进都是通过计算将要移动到的下一个点的坐标,然后将这个点打印出来,与此同时将蛇尾,亦即deque中的首端点去掉,并擦除屏幕上该点颜色。第三部分就直接依次从蛇尾擦除即可。同理,文字“HQT66”的移动也基本类似,稍微改动即可,因为无需对首尾进行操作,而是要对所有点进行移动,因此容器选用vector。这一部分还是很好实现的。 
**

## food模块

**
   老规矩,食物也就是地图随机生成的一个点(正方块面积里的一颗内接五角星),利用rand函数随机获得坐标,我们只需要把食物随机的坐标限制在2-29(也就是我们设置的地图大小内,不包含地图边界)内,如果随机的坐标和蛇身重合,那么就重新获取。另外我增加了一个小特性就是每吃到五个食物就会刷新出一个特殊食物(分数加的更多,但是需要在限定时间内吃到,否则就会消失,它与普通食物并存)。绘制特殊食物是通过DrawBigFood函数实现的,利用一定长度的线段不停地被for循环语句所缩短,达到限时特殊食物的目的。
**

# 源码
```cpp
//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(11);//设置开始动画颜色
    StartInterface *start = new StartInterface();//动态分配一个StartInterface类start
    start->Action();//开始动画
    delete start;//释放内存空间

    /*设置关标位置,并输出提示语,等待任意键输入结束*/
    SetCursorPosition(13, 26);
    std::cout << "亲这边建议您按任意键开始呢... " ;
    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://Enter回车键
            flag = true;
            break;
      default://无效按键
            break;
      }
      if (flag) break;//输入Enter回车键确认,退出检查输入循环

      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 << " ┃               Good Game(小辣鸡) !!!    ┃" ;
    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>//kbhit
#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函数返回值为两个,需注意,因为kbhit是非阻塞函数,无论是否有键按下都会返回一个非零值。所以和getch这个阻塞函数配合使用
    {
      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(50) {//调来调去还是觉得50ms间隔让人觉得自然
      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(-27, 14));//H
      textsnake.emplace_back(Point(-27, 15));
      textsnake.emplace_back(Point(-27, 16));
      textsnake.emplace_back(Point(-27, 17));
      textsnake.emplace_back(Point(-27, 18));
      textsnake.emplace_back(Point(-26, 16));
      textsnake.emplace_back(Point(-25, 16));
      textsnake.emplace_back(Point(-24, 14));
      textsnake.emplace_back(Point(-24, 15));
      textsnake.emplace_back(Point(-24, 16));
      textsnake.emplace_back(Point(-24, 17));
      textsnake.emplace_back(Point(-24, 18));





      textsnake.emplace_back(Point(-22, 14));//Q
      textsnake.emplace_back(Point(-22, 15));
      textsnake.emplace_back(Point(-22, 16));
      textsnake.emplace_back(Point(-22, 17));
      textsnake.emplace_back(Point(-21, 14));
      textsnake.emplace_back(Point(-21, 17));
      textsnake.emplace_back(Point(-20, 14));
      textsnake.emplace_back(Point(-20, 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(-18, 18));

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

      textsnake.emplace_back(Point(-7, 14));//6
      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, 14));
      textsnake.emplace_back(Point(-6, 16));
      textsnake.emplace_back(Point(-6, 18));
      textsnake.emplace_back(Point(-5, 14));
      textsnake.emplace_back(Point(-5, 16));
      textsnake.emplace_back(Point(-5, 17));
      textsnake.emplace_back(Point(-5, 18));

      textsnake.emplace_back(Point(-2, 14));//6
      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, 17));
      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 疯狂的蛇---made by:计科二班hqt");//设置窗口标题
    char cmd;
    sprintf_s(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 );
}
```

# 尾声
**
这次编写没有用到EasyX,可以直接编译食用。**

**有问题可以评论我看到一定会回答滴。**

**以后有用到源码的小伙伴不要白piao啊
**

BIGOcean 发表于 2020-7-6 21:30

C语言版本的https://www.52pojie.cn/thread-1110040-1-1.html

BIGOcean 发表于 2020-7-6 16:35

qyh1206 发表于 2020-7-6 16:34
想问一下,

这个库文件在系统里面有吗

在源码里

BIGOcean 发表于 2020-7-6 15:23

减轻吾爱服务器压力,图片就放在码云cdn啦{:301_975:}

yjczawyl 发表于 2020-7-6 15:24

一位手拿苦瓜的吃瓜群众路过一下……

BIGOcean 发表于 2020-7-6 15:25

流程图和类图是用【draw.io】制作的。

kaka0000 发表于 2020-7-6 15:31

向大佬学习

Loser_Lus 发表于 2020-7-6 15:47

最近写游戏已经写吐了

sup817ch 发表于 2020-7-6 15:50

很棒 学习了

wwwroot 发表于 2020-7-6 15:59

谢谢 学习了

qyh1206 发表于 2020-7-6 16:34

想问一下,
#include "controller.h"

这个库文件在系统里面有吗
页: [1] 2 3
查看完整版本: 【C++】编写控制台贪吃蛇游戏