C++实现贪吃蛇

写在卸载之前

一个蒟蒻无力的挣扎

正式开始

首先 为了保证代码的大部分你可以看懂 你需要具备类与对象的前置知识

可以看看这里

使用C++实现贪吃蛇 尽管网上已经存在很多版本 但是

要么看不懂 要么连运行都运行不了

所幸 还是找到了一篇可以看懂的博客

本代码大多数都是从这篇博客借鉴抄袭而来

由于AI智能实在是不知道怎么实现 毕竟本蒟蒻也不是人工智能专业的

所以只提供具备手动玩法的

做出来大致是这样

捕获.PNG

tmp1.gif

代码会分为很多部分 我们开始分布式口胡

1.初始化游戏设置

由于贪吃蛇的特殊性 我们需要一些特殊的操作

关于sprintf(https://www.cnblogs.com/ZY-Dream/p/9986093.html)

关于隐藏光标(https://blog.csdn.net/Lemon_CHA/article/details/108362689)

void gotoxy(short x, short y) 
{//这是让你的输入输出光标自动跳转到exe指定坐标的函数
//同时要注意的是 这里的(x,y)对应的二维坐标系的坐标 x为横坐标 y为纵坐标
//也就是说x是行 y是列
  COORD coord={x, y}; 
  //COORD是Windows API中定义的一种结构体类型,表示控制台屏幕上的坐标。
  //上面语句是定义了COORD类型的变量coord,并以形参x和y进行初始化。
  SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
  //GetStdHandle(STD_OUTPUT_HANDLE); 获取控制台输出句柄
  //然后用SetConsoleCursorPosition设置控制台(cmd)光标位置
}
class GameSetting
{//初始化游戏设置 
  public:
    static const int window_height=40;
    static const int window_width=80;
  public:
    static void GameInit()
    {
	//设置游戏窗口大小 
	char buffer[32];
	sprintf(buffer,"mode con cols=%d lines=%d",window_width,window_height);
	system(buffer);
			
	//这一部分是要隐藏光标 
	HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursor;
	GetConsoleCursorInfo(handle,&cursor);//获取光标控制台的信息 
	cursor.bVisible=false;//设为隐藏
	SetConsoleCursorInfo(handle,&cursor);
	//初始化随机种子 
	srand((unsigned int)time((int)(998244353==19260817)));
			
     }	
    static void GameStart()
    {
	system("cls");//游戏其实是暴力的一帧清然后一帧画 
	for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
	putchar('\n');
	for(int i=2;i<GameSetting::window_height;++i)
	{
	  for(int j=1;j<=GameSetting::window_width;++j)
	  if(j==1||j==GameSetting::window_width) putchar('#');
          else putchar(' '); 
	  putchar('\n'); 
	} 
	  for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
	  gotoxy(35,15);printf("贪吃蛇游戏");
	  gotoxy(35,20);printf("方向键控制");
	  gotoxy(33,25);printf("按回车键开始游戏"); 
     }
};

2.打印相关的游戏信息

我们需要时刻显示当前的游戏等级,游戏分数,以及相应的操作还有制作作者

class PrintInfo
{//打印信息类 打印相关游戏信息 
  public:
    static void DrawMap()//绘制地图
    {
      system("cls");//游戏其实是暴力的一帧清然后一帧画 
      for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
      putchar('\n');
      for(int i=2;i<GameSetting::window_height;++i)
      {
        for(int j=1;j<=GameSetting::window_width;++j)
        if(i==13&&j>=GameSetting::window_width-28) putchar('#');
        else if(j==1||j==GameSetting::window_width||j==GameSetting::window_width-28) putchar('#');
        else putchar(' '); 
    		
        putchar('\n'); 
        } 
        for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
    } 
    static void DrawScore(int score,int allhave)
    {
      gotoxy(GameSetting::window_width-22,4);
      printf("当前游戏等级:%02d\n",allhave);
      gotoxy(GameSetting::window_width-22,6);
      printf("当前玩家分数为:%03d\n",score);
    }
    static void GameOver(int score)
    {
      system("cls");//游戏其实是暴力的一帧清然后一帧画 
      for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
      putchar('\n');
      for(int i=2;i<GameSetting::window_height;++i)
      {
        for(int j=1;j<=GameSetting::window_width;++j)
        if(j==1||j==GameSetting::window_width) putchar('#');
        else putchar(' '); 
        putchar('\n'); 
       }
       for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
       gotoxy(35,19);printf("游戏结束了!");
       gotoxy(31,21);printf("你取得的分数是:%03d",score); 
    } 
    static void DrawGameInfo()//画游戏操作 
    {
      gotoxy(GameSetting::window_width-22,17);
      printf("游戏操作说明:\n"); 
      gotoxy(GameSetting::window_width-22,24);
      printf("方向键控制\n");
      gotoxy(GameSetting::window_width-22,30);
      printf("作者:破晓晨光");
    } 
};

3.生成食物

贪吃蛇出了版面之外 就是蛇和食物两大部分 所以我们先考虑生成食物

struct Node
{
  int xx,yy;
};//使用结构体来存储二维坐标
class Food
{
  private:
    Node food;
  public:
  void GetFoodAt(const vector<Node>& snake)	
  {//传入蛇的坐标之后 我们生成食物  
    food.xx=rand()%(GameSetting::window_width-30)+1;
    food.yy=rand()%(GameSetting::window_height-2)+1;
    for(int i=0;i<(int)snake.size();++i)
    {//原则上不允许食物出现在蛇身上 
      if(food.xx==snake[i].xx&&food.xx==snake[i].yy)
      {
        food.xx=rand()%(GameSetting::window_width-30)+1;
        food.yy=rand()%(GameSetting::window_height-2)+1;	
        i=0;//回去重新检验 防止食物出在蛇身上	
      }
    }
  }
  Food(){}//初始构造函数
  Food(const vector<Node>& snake) 
  {//重载构造函数
    food.xx=rand()%(GameSetting::window_width-30)+1;
    food.yy=rand()%(GameSetting::window_height-2)+1;
    for(int i=0;i<(int)snake.size();++i)
    {//原则上不允许食物出现在蛇身上 
      if(food.xx==snake[i].xx&&food.xx==snake[i].yy)
      {
      food.xx=rand()%(GameSetting::window_width-30)+1;
      food.yy=rand()%(GameSetting::window_height-2)+1;	
      i=0;	
      }
    }
  }
  void DrawFood()
  {//在我们的exe上显示出食物
    gotoxy(food.xx,food.yy);
    putchar('$');
  }
  Node GetFoodWhere(){return (Node){food.xx,food.yy};}//获取食物坐标
 }; 

4.生成蛇

接下来就是生成蛇了

非阻塞函数 _kbhit()(https://blog.csdn.net/zhao_fu_lu/article/details/24976331)

方向键读入 (https://blog.csdn.net/weixin_50012998/article/details/108899429)

#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4 
class SNAKE
{
  private:
    int dirct;
    bool alive;
  public:
    vector<Node> snake;
  public:
  SNAKE()
  {//初始构造函数
    dirct=UP;//方向向上
    alive=true;//活的
    Node snake_head; 
    snake_head.xx=(GameSetting::window_width/2)-13;//蛇为与正中间
    snake_head.yy=(GameSetting::window_height/2);
    snake.push_back(snake_head);
    snake_head.yy++;
    snake.push_back(snake_head);
    snake_head.yy++;
    snake.push_back(snake_head);
  } 
  void listen_keyboard()
  {//监听键盘 
    char ch;
    if(_kbhit())
    {//非阻塞函数 如果键盘存在输入的话 返回值为一个非零值 否则为零
      ch=_getch(); 
      ch=_getch();//这是用于读入方向键指令
      if((ch==72)&&this->dirct!=DOWN) this->dirct=UP;
      else if((ch==80)&&this->dirct!=UP) this->dirct=DOWN;
      else if((ch==75)&&this->dirct!=RIGHT) this->dirct=LEFT;
      else if((ch==77)&&this->dirct!=LEFT) this->dirct=RIGHT;
    }
  }

  void move_snake()
  {
    listen_keyboard();
    Node nowhead=snake[0];
    switch(dirct)
    {//通过方向来移动蛇
      case UP:
      nowhead.yy--;
      break;
      case DOWN:
      nowhead.yy++;
      break;
      case LEFT:
      nowhead.xx--;
      break;
      case RIGHT:
      nowhead.xx++;
      break;			
    }
    snake.insert(snake.begin(),nowhead);
//这里我们手动模拟就会发现 贪吃蛇的每一次移动只会涉及到头部和尾部的变化
  }
  bool is_eat_food(Food& nowfood)
  {//判断是否吃到了食物
    Node nowhead=snake[0];
    Node nowfoodat=nowfood.GetFoodWhere();
    if(nowhead.xx==nowfoodat.xx&&nowhead.yy==nowfoodat.yy)
    {//如果吃到了食物 长度会增加 吃到的食物会直接成为头 按照上面的写法不会涉及到删除尾部
      nowfood.GetFoodAt(snake);//我们需要生成下一个食物
      return true;
    }
    else 
    {
      snake.erase(snake.end()-1);//保证移动合法性 删除尾部
      return false;
    }
  }	
  bool snake_is_alive()
  {//判断蛇还是否存活
    Node nowhead=snake[0];
    if(nowhead.xx<=1||nowhead.xx>=GameSetting::window_width-28||nowhead.yy<=1||nowhead.yy>=GameSetting::window_height)
    {//撞墙了
      alive=false;return alive;
    }
    for(int i=1;i<(int)snake.size();++i)
    if(nowhead.xx==snake[i].xx&&nowhead.yy==snake[i].yy)
    {//咬到自己了
      alive=false;
      return alive;
    }
    alive=true;
    return alive;
  }
  void draw_snake()
  {
    for(int i=0;i<(int)snake.size();++i)
    {//把蛇在屏幕上显示出来
      gotoxy(snake[i].xx,snake[i].yy);
      if(i==0) putchar('%');
      else putchar('*');
    }
  }
  void ClearSnake()
  {//由于我们已经发现蛇的移动规律是规律是每一次只会涉及到头部和尾部的变化 所以我们删除蛇的话 只删除掉尾部
    gotoxy(snake[(int)snake.size()-1].xx,snake[(int)snake.size()-1].yy);
    putchar(' ');
  }
  int GetSnakeSize(){return (int)snake.size();}//获得蛇的长短
};

5.主函数

在完成了各个部分之后 我们在主函数中将其拼接在一起

int Speed=300,Score,tot,cnt=1; 
int main()
{
  GameSetting newset;
  PrintInfo print_info;
  SNAKE newsnake;
  newset.GameInit();//初始化界面
  newset.GameStart();//载入开始界面
  getchar();
  gotoxy(GameSetting::window_width/2-9,GameSetting::window_height/2+4);
  print_info.DrawMap();//画地图 
  print_info.DrawGameInfo();//画操作 
  Food food(newsnake.snake);//画食物 
  while(true)
  {
    print_info.DrawScore(Score,cnt);//打印成绩 
    food.DrawFood();//打印食物 
    newsnake.ClearSnake();//清理蛇 
    if(newsnake.is_eat_food(food))
    {//我们吃到了 
      Score+=cnt;++tot;
      if(tot==5) tot=0,++cnt,Speed=(Speed*4)/5;
    }//这里是我自己写的一个计分和提升难度的机制 觉得不适应可以自定义
    newsnake.move_snake();//移动蛇 
    newsnake.draw_snake();//画蛇 
    if(!newsnake.snake_is_alive()) 
    {//如果蛇已经死了 
      print_info.GameOver(Score);
      break;
    }

    Sleep(Speed); 
  }
  return 0;
} 

完整代码CODE:

#include<bits/stdc++.h>
#include<windows.h>
#include<conio.h>
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4 
using namespace std;
int Speed=300,Score,tot,cnt=1; 
struct Node
{
  int xx,yy;
};
void gotoxy(short x, short y) 
{
  COORD coord={x, y}; 
  //COORD是Windows API中定义的一种结构体类型,表示控制台屏幕上的坐标。
  //上面语句是定义了COORD类型的变量coord,并以形参x和y进行初始化。
  SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
  //GetStdHandle(STD_OUTPUT_HANDLE); 获取控制台输出句柄
  //然后用SetConsoleCursorPosition设置控制台(cmd)光标位置
}
class GameSetting
{//初始化游戏设置 
  public:
    static const int window_height=40;
    static const int window_width=80;
  public:
    static void GameInit()
    {
      //设置游戏窗口大小 
      char buffer[32];
      sprintf(buffer,"mode con cols=%d lines=%d",window_width,window_height);
      system(buffer);

      //这一部分是要隐藏光标 
      HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
      CONSOLE_CURSOR_INFO cursor;
      GetConsoleCursorInfo(handle,&cursor);//获取光标控制台的信息 
      cursor.bVisible=false;//设为隐藏
      SetConsoleCursorInfo(handle,&cursor);
      //初始化随机种子 
      srand((unsigned int)time((int)(998244353==19260817)));

    }	
    static void GameStart()
    {
      system("cls");//游戏其实是暴力的一帧清然后一帧画 
      for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
      putchar('\n');
      for(int i=2;i<GameSetting::window_height;++i)
      {
        for(int j=1;j<=GameSetting::window_width;++j)
        if(j==1||j==GameSetting::window_width) putchar('#');
        else putchar(' '); 
        putchar('\n'); 
      } 
      for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
      gotoxy(35,15);printf("贪吃蛇游戏");
      gotoxy(35,20);printf("方向键控制");
      gotoxy(33,25);printf("按回车键开始游戏"); 
    }
};
class PrintInfo
{//打印信息类 打印相关游戏信息 
  public:
    static void DrawMap()//绘制地图
    {
      system("cls");//游戏其实是暴力的一帧清然后一帧画 
      for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
      putchar('\n');
      for(int i=2;i<GameSetting::window_height;++i)
      {
        for(int j=1;j<=GameSetting::window_width;++j)
        if(i==13&&j>=GameSetting::window_width-28) putchar('#');
        else if(j==1||j==GameSetting::window_width||j==GameSetting::window_width-28) putchar('#');
        else putchar(' '); 

        putchar('\n'); 
      } 
      for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
    } 
    static void DrawScore(int score,int allhave)
    {
      gotoxy(GameSetting::window_width-22,4);
      printf("当前游戏等级:%02d\n",allhave);
      gotoxy(GameSetting::window_width-22,6);
      printf("当前玩家分数为:%03d\n",score);
    }
    static void GameOver(int score)
    {
      system("cls");//游戏其实是暴力的一帧清然后一帧画 
      for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
      putchar('\n');
      for(int i=2;i<GameSetting::window_height;++i)
      {
        for(int j=1;j<=GameSetting::window_width;++j)
        if(j==1||j==GameSetting::window_width) putchar('#');
        else putchar(' '); 
        putchar('\n'); 
      }
      for(int i=1;i<=GameSetting::window_width;++i) putchar('#');
      gotoxy(35,19);printf("游戏结束了!");
      gotoxy(31,21);printf("你取得的分数是:%03d",score); 
    } 
    static void DrawGameInfo()//画游戏操作 
    {
      gotoxy(GameSetting::window_width-22,17);
      printf("游戏操作说明:\n"); 
      gotoxy(GameSetting::window_width-22,24);
      printf("方向键控制\n");
      gotoxy(GameSetting::window_width-22,30);
      printf("作者:破晓晨光");
    } 
}; 
//食物生成 
class Food
{
  private:
    Node food;
  public:
    void GetFoodAt(const vector<Node>& snake)	
    {//传入蛇的坐标之后 我们生成食物  
      food.xx=rand()%(GameSetting::window_width-30)+1;
      food.yy=rand()%(GameSetting::window_height-2)+1;
      for(int i=0;i<(int)snake.size();++i)
      {//原则上不允许食物出现在蛇身上 
        if(food.xx==snake[i].xx&&food.xx==snake[i].yy)
        {
          food.xx=rand()%(GameSetting::window_width-30)+1;
          food.yy=rand()%(GameSetting::window_height-2)+1;	
          i=0;	
        }
      }
    }
    Food(){}
    Food(const vector<Node>& snake) 
    {
      food.xx=rand()%(GameSetting::window_width-30)+1;
      food.yy=rand()%(GameSetting::window_height-2)+1;
      for(int i=0;i<(int)snake.size();++i)
      {//原则上不允许食物出现在蛇身上 
        if(food.xx==snake[i].xx&&food.xx==snake[i].yy)
        {
          food.xx=rand()%(GameSetting::window_width-30)+1;
          food.yy=rand()%(GameSetting::window_height-2)+1;	
          i=0;	
        }
      }
    }
    void DrawFood()
    {
      gotoxy(food.xx,food.yy);
      putchar('$');
    }
    Node GetFoodWhere()
    {return (Node){food.xx,food.yy};}
}; 
class SNAKE
{
  private:
    int dirct;
    bool alive;
  public:
    vector<Node> snake;
  public:
    SNAKE()
    {
      dirct=UP;
      alive=true;
      Node snake_head; 
      snake_head.xx=(GameSetting::window_width/2)-13;
      snake_head.yy=(GameSetting::window_height/2);
      snake.push_back(snake_head);
      snake_head.yy++;
      snake.push_back(snake_head);
      snake_head.yy++;
      snake.push_back(snake_head);
    } 
    void listen_keyboard()
    {//监听键盘 
      char ch;
      if(_kbhit())
      {
        ch=_getch(); 
        ch=_getch();
        if((ch==72)&&this->dirct!=DOWN) this->dirct=UP;
        else if((ch==80)&&this->dirct!=UP) this->dirct=DOWN;
        else if((ch==75)&&this->dirct!=RIGHT) this->dirct=LEFT;
        else if((ch==77)&&this->dirct!=LEFT) this->dirct=RIGHT;
      }
    }

    void move_snake()
    {
      listen_keyboard();
      Node nowhead=snake[0];
      switch(dirct)
      {
        case UP:
        nowhead.yy--;
        break;
        case DOWN:
        nowhead.yy++;
        break;
        case LEFT:
        nowhead.xx--;
        break;
        case RIGHT:
        nowhead.xx++;
        break;			
      }
      snake.insert(snake.begin(),nowhead);
    }
    bool is_eat_food(Food& nowfood)
    {
      Node nowhead=snake[0];
      Node nowfoodat=nowfood.GetFoodWhere();
      if(nowhead.xx==nowfoodat.xx&&nowhead.yy==nowfoodat.yy)
      {
        nowfood.GetFoodAt(snake);
        return true;
      }
      else 
      {
        snake.erase(snake.end()-1);
        return false;
      }
    }	
    bool snake_is_alive()
    {
      Node nowhead=snake[0];
      if(nowhead.xx<=1||nowhead.xx>=GameSetting::window_width-28||nowhead.yy<=1||nowhead.yy>=GameSetting::window_height)
      {
        alive=false;return alive;
      }
      for(int i=1;i<(int)snake.size();++i)
      if(nowhead.xx==snake[i].xx&&nowhead.yy==snake[i].yy)
      {
        alive=false;
        return alive;
      }
      alive=true;
      return alive;
    }
    void draw_snake()
    {
     for(int i=0;i<(int)snake.size();++i)
     {
      gotoxy(snake[i].xx,snake[i].yy);
      if(i==0) putchar('%');
      else putchar('*');
     }
    }
    void ClearSnake()
    {
      gotoxy(snake[(int)snake.size()-1].xx,snake[(int)snake.size()-1].yy);
      putchar(' ');
    }
    int GetSnakeSize(){return (int)snake.size();}
};
int main()
{
  GameSetting newset;
  PrintInfo print_info;
  SNAKE newsnake;
  newset.GameInit();
  newset.GameStart(); 
  getchar();
  gotoxy(GameSetting::window_width/2-9,GameSetting::window_height/2+4);
  print_info.DrawMap();//画地图 
  print_info.DrawGameInfo();//画操作 
  Food food(newsnake.snake);//画食物 
  while(true)
  {
    print_info.DrawScore(Score,cnt);//打印成绩 
    food.DrawFood();//打印食物 
    newsnake.ClearSnake();//清理蛇 
    if(newsnake.is_eat_food(food))
    {//我们吃到了 
      Score+=cnt;++tot;
      if(tot==5) tot=0,++cnt,Speed=(Speed*4)/5;
    } 
    newsnake.move_snake();//移动蛇 
    newsnake.draw_snake();//画蛇 
    if(!newsnake.snake_is_alive()) 
    {//如果蛇已经死了 
      print_info.GameOver(Score);
      break;
    }

    Sleep(Speed); 
  }
  return 0;
} 
原文地址:https://www.cnblogs.com/tcswuzb/p/14512554.html