一、实现的功能
1、介绍程序内容
2、选择控制方式(WASD或者方向键盘)
3、地图选择
4、速度选择
二、实现思路
????????既然刚学完C++,我们就要使用面向对象的方法来编程实现。创建唯一也是核心的类CentralWork,在其中实现所有功能。
? ? ? ? 贪吃蛇实现主要有四个步骤:食物的随机产生、蛇的移动显示、蛇移动方向的控制、蛇吃到食物与牺牲的判定。现在阐述实现思路。
1、食物的随机产生:
? ? ? ? 食物只存在一个,一个被吃后才会产生下一个随机食物。所以食物是否存在需要一个bool类型的变量OneOrZero来判断。每次蛇吃掉食物,更改变量值即可。
????????对于食物的随机出现,用学到的time(NULL)以及srand()、rand()函数随机产生食物的位置坐标即可。当然,食物不能产生在障碍或者边界或者蛇身上,此处只需要加一判断即可。如果产生的随机食物坐标上不为‘? ’(空),则重新随机产生即可。
2、蛇的移动显示
? ? ? ? 蛇的移动,为了让其更加平滑,不采用每次都顺序重新向下打印地图的形式。想要总在同一张地图上操作,即是每次刷新控制台输出,然后在同样的位置输出全张更新后的地图(包括蛇以及食物信息)。经过百度,system(“cls”);可以实现此功能,也就是说,每次蛇到达时间移动一次或者控制方向移动一次,我们?system(“cls”);一次然后输出更新后的地图即可达到想要的平滑效果。显示解决了,那么怎样控制蛇的移动方向呢?
3、蛇的移动方向控制
? ? ? ? 控制移动方向,刚开始都应该考虑到了摁方向键或者WASD后普通的输入(例如cin等)不会立马结束进入下一个环节,必须摁下输出结束符才可以结束方向键输入。经过查阅,getch()函数可以实现摁下键后无需再摁下输入结束符,可以立刻运行后续程序。这也是我们所想要的结果。
? ? ? ? WASD和方向键盘对应的函数实现也会不一样,WASD编码时可以直接用字符表示,但是方向键则不行。且摁下方向键会有两个ascll码值输入,第一是标志四个方向都是224,后面对应的才是区分方向的值。所以我们对于WASD控制每次只需要一次getch()即可,但是对于方向键盘我们需要两次getch()。另外,如果蛇头正在向上走,我们此时摁下S或者↓肯定不可以让它向下走。对此,我们判断此时的运动方向字符是否是与原相反的字符,是相反字符的话不会结束游戏,仍向原方向移动。
? ? ? ? 还有一个难点,如何控制蛇的移动?
4、蛇的移动实现
? ? ? ? 我们在类中创建一个二维数组,此二维数组的每一个变量都会有一个字符变量、一个指向自身类型的指针变量。字符变量可以理解,即是我们时刻要用到的地图,二维数组也就有了横纵坐标,更加便于我们确定蛇的位置。为什么要用到指针呢?我们用指针来“连接”蛇,通过指针的指向来确定蛇身的位置,也便于蛇的移动准确显示。这样,蛇尾无非就是指针为nullprt,这样我们也很容易找到蛇尾。那蛇身蛇尾能确定了,那么蛇头怎样确定呢?可以用头指针确定蛇头,但是在这里我们为了移动并且时刻确定蛇头位置,我们在类中定义了? ? HeadLevelPosition;HeadVerticalPosition;? 两个表示横纵坐标的变量,每次移动时只需要根据移动方向++或者- -这两个变量即可实现蛇的移动,至于蛇头的确定,这两个坐标时刻记录着蛇头位置,也就不要了额外的指针确定蛇头位置。但是蛇头移动走了,我们需要移动蛇身,这里我们采用到指针,这里的指针很类似链表,我们利用一个外界的指针不断更新蛇内的指针值来改变蛇的位置,也就实现了蛇的移动。
????????这是通过摁键控制移动,那么如何通过时间间隔实现蛇的移动呢?经过查阅,clock()函数返回自程序打开之后到clock()函数被调用的CPU时间ms数,所以我们用该语句来实现判断时间间隔是否到达以及是否摁键控制方向提前结束移动倒计时
((Judge = (clock() - StartTime <= MoveTime*SpeedNumber)) && !_kbhit())
其中StartTime我们提前定义一下=clock()即可,MoveTime*SpeedNumber即是我们想要每次移动的时间间隔。对于kbhit()函数,则是检测是否有键按下的函数,返回值会根据有无键按下而变化。
到这里核心的思想也就结束了,我还加了自己简单做的三张地图,一张随机产生障碍物的地图。对于速度也有8个档次,当然我也加了一个额外的随蛇长度边长而速度变化的功能。为了娱乐性稍微有那么一点点,加上了得分功能,实时展示,当然美吃到一次食物得分会随当前的速度而变化。这些小功能都是非常容易实现的,我也不多阐述了,源码公开如下希望大家和我一起学习
(作者大一)
? ? ? ? 对了,如果选择使用方向盘控制,上面也提到了要两次调用getch()函数,那么我们不摁方向键的话会怎么样呢?其实我们如果摁下其他的摁键如字母ASDF啥的,它只会调用完第一个getch()函数,第二个会等待你输入,这其实是一个bug,但是也能实现暂停的功能,也就没做修改。
三、代码实现
? ? ? ??
#include<iostream>
#include<time.h>
#include<conio.h>
#include<Windows.h> //windows.h可不要
#define MoveTime 100 //一百毫秒为基本移动时间单位
#define N 22 //地图的长宽
using namespace std;
//全局变量
auto StartTime = clock(); //定义起始时间
UINT Judge = 0; //用来判断是否吃到食物
bool OneorZero = false; //用来判断食物数量为1或0
UINT SpeedNumber = 0; //选择的速度等级
UINT MapNumber = 0; //选择的地图序号
UINT Error; //方向键盘吸收224 _getch()
UINT UseMode = 1; //选择方向键盘控制或者WASD控制
//链表记录蛇的位置以及其移动坐标位置
struct Position {
UCHAR CH;
Position* pointer;
};
//需一个链表头指针
Position* Hpointer;
//核心功能实现类
class CentralWork {
public:
//初始化定义蛇头以及初始地图
CentralWork() {
for (int i = 0; i != N; ++i)
{
for (int j = 0; j != N + 1; ++j)
{
Coordinate[i][j].pointer = nullptr;
if (i == 0 || i == N - 1)
{
Coordinate[i][j].CH = '=';
}
else if (j == 0 || j == N)
Coordinate[i][j].CH = '|';
else Coordinate[i][j].CH = ' ';
}
}
Coordinate[1][1].CH = '-';
Coordinate[1][2].CH = '@';
Coordinate[1][2].pointer = &Coordinate[1][1];
Coordinate[1][1].pointer = nullptr;
Hpointer = &Coordinate[1][2];
if (UseMode == 1)
{
this->MoveDirection = 77;
this->NowMoveDirection = 77;
}
else
{
this->MoveDirection = 'd';
this->NowMoveDirection = 'd';
}
this->FoodX = 0;
this->FoodY = 0;
this->HeadLevelPosition = 1;
this->HeadVerticalPosition = 2;
this->Score = 0;
this->AteFoodNumber = 0;
this->MapX = 0;
this->MapY = 0;
}
//静态成员函数,介绍使用方法
void static MenuAndMe() {
cout << "\n\n\t\t\t\t\t\t简介:\n\n";
cout << "\t\t\t\t\t8个难度,分别为0.1~0.8秒移动一次\n\n";
cout << "\t\t\t\t\t创作4张简单地图,每张地图障碍物位置不相同\n\n";
cout << "\t\t\t\t\t下面选择操作方式、难度以及地图\n\n";
cout << "\t\t\t\t\t请按任意键继续...\n\n";
cout << "\t\t\t\t\t\t\t\t\t作者:yj小迷弟\n\n\t\t\t\t\t";
Error = _getch();
cin.clear();
system("cls");
cout << "\n\n\t\t\t\t\t\t操作方式选择:\n\n";
cout << "\t\t\t\t\t0、WASD操作(游戏过程中不可暂停)\n\n\t\t\t\t\t1、↑↓←→操作(游戏过程中可摁非方向盘键暂停)\n\n";
cout << "\t\t\t\t\t\t\t\t\t作者:yj小迷弟\n\n\t\t\t\t\t";
cin >> UseMode;
cin.clear();
system("cls");
cout << "\n\n\t\t\t\t\t\t难度选择:\n\n";
cout << "\t\t\t\t\t0、根据长度自动增长速度\n\n\t\t\t\t\t1、100ms移动一次\n\n\t\t\t\t\t2、200ms移动一次\n\n\t\t\t\t\t3、300ms移动一次\n\n\t\t\t\t\t4、400ms移动一次\n\n\t\t\t\t\t5、500ms移动一次\n\n\t\t\t\t\t6、600ms移动一次\n\n\t\t\t\t\t7、700ms移动一次\n\n\t\t\t\t\t8、800ms移动一次\n\n";
cout << "\t\t\t\t\t\t\t\t\t作者:yj小迷弟\n\n\t\t\t\t\t";
cin >> SpeedNumber;
cin.clear();
while (SpeedNumber > 8)
{
cin.clear();
cout << "输入数据有误,请重新输入!\n";
cin >> SpeedNumber;
cin.clear();
}
system("cls");
cout << "\n\n\t\t\t\t\t\t 地图选择";
cout << "\n\n\t\t\t\t\t0、空地图\n\n\t\t\t\t\t1、A地图\n\n\t\t\t\t\t2、B地图\n\n\t\t\t\t\t3、C地图\n\n\t\t\t\t\t4、D地图(50个随机障碍)\n\n";
cout << "\t\t\t\t\t\t\t\t作者:yj小迷弟\n\n\t\t\t\t\t";
cin >> MapNumber;
while (MapNumber > 4)
{
cin.clear();
cout << "输入数据有误,请重新输入!\n";
cin >> MapNumber;
cin.clear();
}
system("cls");
}
//根据地图标号改变地图障碍
void FormMap() {
if (MapNumber == 1)
{
for (int i = 1; i != N/2; ++i)
{
if (!(i == 11 || i == 10 || i == 9))
{
Coordinate[11][i].CH = '#';
Coordinate[11][N-i].CH = '#';
Coordinate[N-i][11].CH = '#';
Coordinate[i][11].CH = '#';
}
}
}
if (MapNumber == 2)
{
for (int i = 1; i <= 7; ++i)
{
Coordinate[13][i].CH = '#';
Coordinate[10][i].CH = '#';
Coordinate[13][N-i].CH = '#';
Coordinate[10][N-i].CH = '#';
Coordinate[N-i][8].CH = '#';
Coordinate[N-i][14].CH = '#';
Coordinate[i][8].CH = '#';
Coordinate[i][14].CH = '#';
}
}
if (MapNumber == 3)
{
for (int i = 1; i <= 6; ++i)
{
Coordinate[14][i].CH = '#';
Coordinate[8][i].CH = '#';
Coordinate[14][N - i].CH = '#';
Coordinate[8][N - i].CH = '#';
Coordinate[N - i][8].CH = '#';
Coordinate[N - i][14].CH = '#';
Coordinate[i][8].CH = '#';
Coordinate[i][14].CH = '#';
}
for (int i = 8; i <= 14; ++i)
{
Coordinate[i][i].CH = '#';
Coordinate[i][N-i].CH = '#';
}
Coordinate[11][11].CH = ' ';
}
if (MapNumber == 4)
{
srand(time(NULL));
for (int i = 0; i != 50; ++i)
{
MapX = rand() % N;
MapY = rand() % N;
if (Coordinate[MapX][MapY].CH == ' '&&(MapX !=1 || MapY != 3))
Coordinate[MapX][MapY].CH = '=';
else --i;
}
}
}
//输出地图
void OutPutFrameWork()
{
cout << "\t\t\t\t\t\t\t当前的长度:" << AteFoodNumber << "\n\t\t\t\t\t\t\t当前得分:" << Score << endl;
for (int i = 0; i != N; ++i)
{
cout << "\t\t\t\t\t";
for (int j = 0; j != N + 1; ++j)
{
cout << Coordinate[i][j].CH << " ";
}
cout << endl;
}
}
//判断蛇是否牺牲
UCHAR JudgeSnakeOver() {
if (Coordinate[HeadLevelPosition][HeadVerticalPosition].CH == '|'
|| Coordinate[HeadLevelPosition][HeadVerticalPosition].CH == '='
|| (Coordinate[HeadLevelPosition][HeadVerticalPosition].CH == '-')
|| (Coordinate[HeadLevelPosition][HeadVerticalPosition].CH == '#'))
return 0;
else if (Coordinate[HeadLevelPosition][HeadVerticalPosition].CH == '*')
return 2;
else return 1;
}
//改变蛇身的位置
bool SnakeChange(UINT& Judge) {
if (Judge == 1) {
Coordinate[HeadLevelPosition][HeadVerticalPosition].CH = '@';
Coordinate[HeadLevelPosition][HeadVerticalPosition].pointer = Hpointer;
Hpointer->CH = '-';
Hpointer = Coordinate[HeadLevelPosition][HeadVerticalPosition].pointer;
while (Hpointer->pointer->pointer && Hpointer->CH=='-')
{
Hpointer = Hpointer->pointer;
}
Hpointer->pointer->CH = ' ';
Hpointer->pointer = nullptr;
Hpointer = &Coordinate[HeadLevelPosition][HeadVerticalPosition];
return true;
}
else if (Judge == 2)
{
++AteFoodNumber;
if (SpeedNumber == 0)
Score += AteFoodNumber / 5 + 1;
else Score += 9 - SpeedNumber;
Coordinate[HeadLevelPosition][HeadVerticalPosition].CH = '@';
Coordinate[HeadLevelPosition][HeadVerticalPosition].pointer = Hpointer;
Hpointer->CH = '-';
Hpointer = &Coordinate[HeadLevelPosition][HeadVerticalPosition];
return false;
}
}
//根据摁键改变舌头位置
void SnakeMove() {
if(SpeedNumber)
while ((Judge = (clock() - StartTime <= MoveTime*SpeedNumber)) && !_kbhit());
else if(SpeedNumber == 0 && AteFoodNumber <= 35) while ((Judge = (clock() - StartTime <= MoveTime * (8 - AteFoodNumber/5))) && !_kbhit());
else if(SpeedNumber == 0 && AteFoodNumber > 35) while ((Judge = (clock() - StartTime <= MoveTime )) && !_kbhit());
if ( UseMode == 1)
{
if (Judge)
{
Error=_getch();
NowMoveDirection = _getch();
}
switch (NowMoveDirection)
{
//反方向摁键时无效果,原方向向前移动一格
case 72: if (MoveDirection != 80) this->MoveDirection = NowMoveDirection; break; //↑
case 75: if (MoveDirection != 77) this->MoveDirection = NowMoveDirection; break; //←
case 80: if (MoveDirection != 72) this->MoveDirection = NowMoveDirection; break; //↓
case 77: if (MoveDirection != 75) this->MoveDirection = NowMoveDirection; break; //→
default: NowMoveDirection = MoveDirection; break;
}
switch (MoveDirection)
{
case 75: HeadVerticalPosition--; break;
case 77: HeadVerticalPosition++; break;
case 72: HeadLevelPosition--; break;
case 80: HeadLevelPosition++; break;
default: break;
}
}
else
{
if (Judge)
{
//_getch();
NowMoveDirection = _getch();
}
switch (NowMoveDirection)
{
//反方向摁键时无效果,原方向向前移动一格
case 'W':case 'w': if (MoveDirection != 'S' && MoveDirection != 's') this->MoveDirection = NowMoveDirection; break; //↑
case 'A':case 'a': if (MoveDirection != 'D' && MoveDirection != 'd') this->MoveDirection = NowMoveDirection; break; //←
case 'S':case 's': if (MoveDirection != 'W' && MoveDirection != 'w') this->MoveDirection = NowMoveDirection; break; //↓
case 'D':case 'd': if (MoveDirection != 'A' && MoveDirection != 'a') this->MoveDirection = NowMoveDirection; break; //→
default: NowMoveDirection = MoveDirection; break;
}
switch (MoveDirection)
{
case 'A':case 'a': HeadVerticalPosition--; break; //←
case 'D':case 'd': HeadVerticalPosition++; break; //→
case 'W':case 'w': HeadLevelPosition--; break; //↑
case 'S':case 's': HeadLevelPosition++; break; //↓
default: break;
}
}
}
//食物产生函数
void FoodEmerge(bool OneorZero) {
if (!OneorZero)
{
srand(time(NULL));
while (Coordinate[FoodX][FoodY].CH != ' ')
{
FoodX = rand() % N;
FoodY = rand() % N;
}
Coordinate[FoodX][FoodY].CH = '*';
}
}
//牺牲后输出游戏结果
void Defate() {
cout << "\t\t\t\t\t\t\tGAMEOVER!\n " << endl;
cout << "\t\t\t\t\t\t\t你的最终得分:" << Score << endl;;
}
private:
Position Coordinate[N][N + 1];
UINT FoodX;
UINT FoodY;
UCHAR MoveDirection;
UCHAR NowMoveDirection;
UINT HeadLevelPosition;
UINT HeadVerticalPosition;
UINT AteFoodNumber;
UINT Score;
UINT MapX;
UINT MapY;
};
int main() {
CentralWork::MenuAndMe();
CentralWork Begin;
Begin.FormMap();
while (1)
{
StartTime = clock();
Begin.FoodEmerge(OneorZero);
Begin.SnakeMove();
Judge = Begin.JudgeSnakeOver();
OneorZero = Begin.SnakeChange(Judge);
system("cls");
Begin.OutPutFrameWork();
if (Judge == 0)
break;
}
Begin.Defate();
return 0;
}
?
?
|