贪吃蛇小程序·C/C++

Hello,大家好,这里是“大千小熊”,一个又会MMD,又会C++的正派角色。Bilibili同步更新,期待你的关注。

程序效果:

开始游戏的欢迎界面:

在这里插入图片描述

字符画(1)

开发商的Logo:

在这里插入图片描述

字符画(2)

游戏的主界面:

在这里插入图片描述

主界面

注意事项:您需要使用“旧版本的控制台”才能真确的进行游戏。

玩法说明:贪吃蛇会随着长度越来越难,蛇的速度会不断的加速。

程序耗时:从0到完整的代码总共耗时为7个小时。

思路分析:

贪吃蛇是一个很经典的游戏,其中算法不难,新手也能轻松做出。

其中这个程序涉及两个问题:(1)Windows控制台编程。(2)C++程序设计。

对于(1)您可以参阅Windows Doc文档。(2)相信您已经有了一定的基础,有基础就够了。

什么是程序的句柄?

在Windows编程中,是Windows用来标识被应用程序所建立或使用的对象的唯一整数,Windows使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。

进程和线程的概念是什么?

您可以理解为线程是进程的子概念,进程是资源调度的最小单位,而线程是CPU调度的最小单位。

贪吃蛇怎么实现扭来扭曲的效果?

实现的方法有许多,在这里我使用了路劲缓存,将贪吃蛇每一次走过的路线都保存一次,然后在根据长度,还原路劲,把路径输出来。

怎么检测键盘上的按钮被按下来了?

在这里,使用了kbhit()和getch(),来检测按钮是不是被按下。同时,在检测方面您可以设置成为多线程,这样的好处是,可以精确捕捉玩家的按键时刻,手感很好。甚至您还可以改写程序,在另一个线程判断用户按下一个按钮是不是一直按下,以此来判断要不要暂时加速贪吃蛇。

程序开始的字符画是怎么实现的?

在这里,您可以使用freopen来重定向输入输出,将字符画保存在二进制文件中,然后使用getline函数按行读取和输出。当您想要再次重定向到不同的文件,您可以使用cin.clear()来再一次地freopen。

控制台的字符宽度和长度怎么处理?

在控制台中,字符的宽度是长度的二分之一,也就是说,字符比较高和窄,然而比如您现在想填充一个宽字符,比如,您在(0,0)位置插入了一个“■”,很可能将(0,1)的位置也占用了。(注意:(x,y)坐标轴中x向下,y向右。这是我自己在程序中规定的,您可以看gotoPos()为什么x和y是反过来的,因为默认的坐标轴是x向右,y向下。)所以在这里我们规定,我们只使用形如( ,2)( ,4)这样y是偶数的坐标一次来对应。

还有更简单的写法吗?

当然有,如果您感兴趣,您可以百度搜索“最简单的贪吃蛇”,有人用短短的几行代码就实现了全部的功能,非常厉害。

程序的改进空间是什么?

您可以,再次重定向一个新的文本,在里面保存一个最大的分数(蛇的长度),然后每次挑战如果刷新了记录,就把结果保存在这个txt文本中。

您还可以如上面所说的,记录按键按下的时间,如果玩家一直在按同一个方向的按钮,可以让蛇暂时加速,当玩家松开按键,或改变按键,蛇的加速取消。

您还可以设置一个暂停游戏的按钮,或者重新再开始游戏的机制。

怎么控制画布中的光标?

您可以参阅WIndows控制台的文档,里面有详细的函数可以控制光标的位置。

程序的源代码(含详细的注释):

字符画,游戏开始界面(Fre_Welcome.txt):

Loading...
Snack
Program by Bear Qian (MoonPolishLove)



               .o                                           **       ...
              .=@@@                       =@@              .@@       @@@^.
             .@@O=@@@          ..*****.   =@^              .@@       =o@^.
            =@@^O^ O@@@*       =@@@@@@^...@@@@@@@@@@^      .@@  .*@@@@@@@@@@@O^
         =@@@@ .@@^  =@@@@^    =@^  @@^.=@@ooooooooo.   =@@@@@@@@o@O       .@O^
       \@@[\@@@@@@@@@@@/.[@@^  =@^  @@^=@@`             =@^.@@=@^*@O]`     .@O^
       .[  .[[[[[[[O@@@`       =@^  @@/\OO@@@@@@@@^     =@^.@@=@^...@^
          ..******=@@^***.     =@^  @@^.     .=@@@.     =@^.@@=@^. .@^    =@^
          O@@@@@@@@@@@@@@^     =@^  @@^.    .@@@        =@@@@@@@^* .@^ =@@@@
          O@^.  ]`     =@^     =@\]]@@^.   ,/@/`        =@^*@@=@^* .@@@@/[`.
          O@^. .@@^    =@^     =@^**@@^.  =@@           =O^.@@=@@O .@^
          O@^.,/@/`]/]],[`     =@^  \@^.,]@`      ,@]      .@@]/@@`.@^     .]..
            ,]@@[..[[@@@].     ,[`      =@@       =@/  ]/@@@@@[[@@^.@^     ,@O^
        .@@@@@*        =@@^             .*@@@@@@@@@O   *=           @@@@@@@@@
         .

字符画,程序Logo(Fre_Logo.txt):

Loading...
Snack
Program by Bear Qian (MoonPolishLove)




                   @@@                        o@@@@^              =@@^              @@@*    *@@@    ^
                   @@@             =@@@@@@@@@@@@O^                =@@^            =@@^  @@@ *@@^ @@@@@
                   @@@             *=@OO*  O@@                    =@@^           O@@oO@@@@@@^@@@@@^
                   @@@                     O@@                    =@@^           O@@@@Oo^ O@@@@^    @@O
          =@@@@@@@@@@@@@@@@@@@@            O@@              *=@@^ =@@^  @@O       @@@@@@@@@^*@@@@@@@@@
          =OOOOOOOO@@@OOOOOOOOO            O@@              =@@^  =@@^  =@@^      @@*    =@^*@@^
                  *@@@*          =@@@@@@@@@@@@@@@@@@@@@^    @@@   =@@^   =@@^     @@@@@@@@@^*@@^ *=@@@
                 *=@@@@*         *OOoooooooO@@oooooooO^*   *@@o   =@@^    @@@     @@*    =@^*@@@@@@O
                 =@@^=@@^                  O@@             @@@    =@@^     @@@    @@@@@@@@@^*@@@    =@o
                *@@*  @@@                  O@@            =@@^    =@@^     =@@    @@*    =@^*@@@oooO@@@
               =@@^    @@@^                O@@            =@^     =@@^     **     @@*  o@@@^ *@@@@@@@^
             =@@@*      =@@@               O@@                    =@@^            @@*  *O^  *@@^  =@^*
          =@@@@^          @@@@^            O@@                    =@@^           O@@   @@@  *@@^  *@@@
          =@@^*            =@@@            @@@                  =@@@@*          =@@^   o@@   @@@   *@@o
                                           *o^

代码的主要实现部分:

虽然看起来很长,但实际内容没有什么复杂的东西。

//文档参阅:https://docs.microsoft.com/zh-cn/windows/console/window-and-screen-buffer-size
//地图的边框(2,0)(2,46)(25,0)(25,46)//第一个是行,我改写的,默认第一个是列
//注意,由于控制台的特性,纵列的宽度是横向的二分之一,所以绘制图像的时候需要从(,0)(,2)这样绘制
//不要从(,1)来绘制图像。
//黑色 = 0      蓝色 = 1      绿色 = 2      湖蓝色 = 3
//红色 = 4      紫色 = 5      黄色 = 6      白色 = 7
//灰色 = 8      淡蓝色=9      淡绿色=A      白色=C
//淡紫色=D      淡黄色=E      亮白色=F       system("color 背景颜色文字颜色")
#include <cmath>#include<conio.h>#include<cstdio>#include<ctime>#include<iomanip>//格式化输出程序
#include<iostream>#include<thread>//多线程的程序
#include<vector>#include<windows.h>using namespace std;

HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); //全局句柄
CONSOLE_SCREEN_BUFFER_INFO bInfo;              // 窗口缓冲区信息
void InitCMD();                                //控制台初始化
void gotoPos(SHORT x, SHORT y);                //设置光标的位置
void InitMap();                                //画边框和信息
void PaintSnack();                             //画出Sncak的样子
void MoveSncak(); //移动Snack的头部,并且在队列里面将🐍的位置存储下来
void ShowScore(); //为了设置双重缓冲区,将要改变的东西存放在这个地方
void SetDirection();
void Sweet();                  //检查糖果设置
void SnackCheck(int x, int y); //测试(x,y)这个位置是什么情况
typedef pair<int, int>P;
int Speed = 700;
int Map[53][53] = {
    0}; // 0可以行走 1不可以走的地方 2是糖果的位置 依旧按照纵坐标为偶数来

class Snack {
public:
  Snack(int Len, int _x, int _y) : length(Len), x(_x), y(_y) {
    q.push_back(P(x, y));
    Map[x][y] = 1;
  }
  int GetLength() { return length; }
  int x;
  int y;
  vector<pair<int, int>> q;
  int length = 0;
  int Direction = 0; // 0 上 1 下 2 左 3 右
private:
};

Snack Actor(1, 12, 22);
bool SweetIn = false,
     SweetShow = false; //判断糖果是不是在地图之中,是不是闪烁出现过了

int sx = 2, sy = 0; //全局糖果的坐标。
int main() {
  srand(time(0));
  InitCMD();
  InitMap();
  thread first(SetDirection); //使用多线程
  first.detach();             //变为守护线程
  while (1) {
    Sleep(10);
    Sweet();
    ShowScore();
    PaintSnack();
    gotoPos(0, 0);
    // for (int i = 2; i <= 25; i++) {
    //   for (int j = 0; j <= 46; j++) {
    //     if (j % 2 == 0)
    //       cout << setw(2) << Map[i][j];
    //   }
    //   cout << endl;
    // }
    MoveSncak();
  }
  return 0;
}
void SnackCheck(int x, int y) {
  if (Map[x][y] == 2) {
    Actor.length += 1;
    Map[x][y] = 0;
    SweetShow = false;
    SweetIn = false;
    sx = 2;
    sy = 0;
  }
  if (Map[x][y] == 1) {
    gotoPos(12, 10);
    SetConsoleTextAttribute(hOut, 0xBC);
    cout << "GAME OVER 蛇蛇的长度是" << Actor.length;
    Sleep(5000);
    exit(0);
  }
}
void Sweet() {
  if (SweetIn) {
    Map[sx][sy] = 2;//虽然存在糖果,但是我为了保险依旧重新设置一遍。
    if (SweetShow == false) {
      gotoPos(sx, sy);
      SetConsoleTextAttribute(hOut, 0xFC);
      cout << "■";
      SweetShow = true;
    } else {
      gotoPos(sx, sy);
      SetConsoleTextAttribute(hOut, 0xFC);
      cout << " ";
      SweetShow = false;
    }
  }
  if (!SweetIn) {
    while (Map[sx][sy] == 1) {
      sx = rand() % 21 + 3;
      sy = rand() % 45 + 1;
      if (sy % 2 != 0)
        sy++;
    }
    Map[sx][sy] = 2;
    gotoPos(sx, sy);
    SetConsoleTextAttribute(hOut, 0xFC);
    cout << "■";
    SweetIn = true;
    SweetShow = true;
  }
}
void PaintSnack() {
  vector<P>::iterator ite;
  int pl = 1;
  for (ite = Actor.q.end() - 1; pl <= Actor.length; ite--) {
    gotoPos((*ite).first, (*ite).second);
    pl++;
    SetConsoleTextAttribute(hOut, 0xF2);
    cout << "■";
    Map[(*ite).first][(*ite).second] = 1;
  }
  if (Actor.q.size() > Actor.length) {
    ite = Actor.q.end() - 1 - Actor.length;
    gotoPos((*ite).first, (*ite).second);
    SetConsoleTextAttribute(hOut, 0xFF);
    cout << " ";
    Map[(*ite).first][(*ite).second] = 0;
  }
}

void MoveSncak() {
  if (Speed > 100)
    Speed = 700 - pow(1.7, Actor.length);
  if (Speed < 70)
    Speed = 70;
  Sleep(Speed);
  switch (Actor.Direction) {
  case 0:
    SnackCheck(Actor.x - 1, Actor.y); //行走之前先判断能不能走
    Actor.q.push_back(P(Actor.x - 1, Actor.y));
    Actor.x -= 1; //控制头部的坐标
    break;
  case 1: //下
    SnackCheck(Actor.x + 1, Actor.y);
    Actor.q.push_back(P(Actor.x + 1, Actor.y));
    Actor.x += 1; //控制头部的坐标
    break;
  case 2: //左
    SnackCheck(Actor.x, Actor.y - 2);
    Actor.q.push_back(P(Actor.x, Actor.y - 2));
    Actor.y -= 2; //控制头部的坐标
    break;
  case 3: //右
    SnackCheck(Actor.x, Actor.y + 2);
    Actor.q.push_back(P(Actor.x, Actor.y + 2));
    Actor.y += 2; //控制头部的坐标
    break;
  case 4:
    break;
  }

  if (Actor.q.size() > 400) {
    //为了将过长的路径缓存去除,根据地图不可能长度超过400。
    Actor.q.erase(Actor.q.begin());
  }
}

void SetDirection() {
  while (1) {
    Sleep(10);
    if (kbhit()) {
      switch (getch()) {
      case 'w':
      case 'W':
      case 72:
        Actor.Direction = 0;
        break;

      case 'a':
      case 'A':
      case 75:
        Actor.Direction = 2;
        break;

      case 'd':
      case 'D':
      case 77:
        Actor.Direction = 3;
        break;

      case 's':
      case 'S':
      case 80:
        Actor.Direction = 1;
        break;
      }
    }
  }
}

void InitCMD() {
  system("color 0E");
  SetConsoleTitleA("贪吃蛇_By 大千小熊");
  COORD dSiz = {202, 100};
  SetConsoleScreenBufferSize(hOut, dSiz);      //设置窗口缓冲区大小
  CONSOLE_CURSOR_INFO _guan_biao = {1, FALSE}; //设置光标大小,隐藏光标
  SetConsoleCursorInfo(hOut, &_guan_biao);

  SMALL_RECT rc = {0, 0, 90, 90}; //设置窗口位置
  SetConsoleWindowInfo(hOut, true, &rc);

  system("mode con cols=170 lines=40"); //调整窗口大小
  cin.clear();
  freopen("Fre_Welcome.txt", "r", stdin);
  string Tchar;
  while (getline(cin, Tchar)) {
    cout << Tchar << endl;
  }
  cin.clear();
  Sleep(2000);
  system("cls");
  freopen("Fre_Logo.txt", "r", stdin);
  while (getline(cin, Tchar)) {
    cout << Tchar << endl;
  }
  Sleep(2000);
  // system("color F0");
  SetConsoleTextAttribute(hOut, 0xF0);
  system("cls");
}

void gotoPos(SHORT y, SHORT x) { //现在gotoPos为(行,列)
  COORD pos = {x, y};
  SetConsoleCursorPosition(hOut, pos);
}

void InitMap() {
  system("mode con cols=130 lines=28"); //调整窗口大小
  gotoPos(0, 0);
  cout << "贪吃蛇小程序\nPrograme By 大千小熊\n";
  //上边框
  for (int i = 0; i < 23; i++) {
    cout << "●";
    Map[2][2 * i] = 1;
  }
  //左边框
  gotoPos(2, 0);
  for (int i = 0; i < 23; i++) {
    cout << "●" << endl;
    Map[2 + i][0] = 1;
  }
  //右边框
  for (int i = 0; i < 23; i++) {
    gotoPos(2 + i, 46);
    cout << "●";
    Map[2 + i][46] = 1;
  }
  //下边框
  gotoPos(25, 0);
  for (int i = 0; i < 24; i++) {
    cout << "●";
    Map[25][2 * i] = 1;
  }

  //游戏的提示信息
  gotoPos(4, 53);
  cout << "■游戏说明:";
  gotoPos(5, 53);
  cout << "通过上下左右按键或者WSAD键盘以来控制移动";
  gotoPos(6, 53);
  cout << "需要您注意的是:本程序需要使用旧版本的控制台才能正常运行";
  gotoPos(7, 53);
  cout << "具体的方法是:在程序标题栏右键,勾选旧版本控制台选项";
}

void ShowScore() {
  gotoPos(9, 53);
  SetConsoleTextAttribute(hOut, 0xF0);
  cout << "■当前得分(贪吃蛇的长度):";
  gotoPos(10, 53);
  SetConsoleTextAttribute(hOut, 0xF0);
  cout << setw(4) << Actor.GetLength();

  gotoPos(12, 53);
  SetConsoleTextAttribute(hOut, 0xF0);
  cout << "■当前速度(游戏难度):";
  gotoPos(13, 53);
  SetConsoleTextAttribute(hOut, 0xF0);
  cout << setw(4) << 1000 - Speed;

  gotoPos(15, 53);
  SetConsoleTextAttribute(hOut, 0xF0);
  cout << "■游戏的难度跟蛇的长度为2次函数的关系";
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值