游戏中的实现元素
游戏中元素分为:墙壁,蛇,事物以及蛇的可行区域和右侧的版本号和游戏玩法提示
墙壁
*
号表示,代表一个区域范围,也就是蛇的可移动区域,蛇如果碰到墙壁视为死亡,
蛇
分为蛇头,蛇身,蛇头用@
符号表示,蛇身用=
等号表示,当蛇吃到食物时候,蛇身+1,一意为着长度变长,贪吃蛇可以通过不断地吃食物来增加自己的身体
食物
#
井号表示,蛇碰到食物会将食物吃掉
可移动区域
空格 表示,代表蛇可以移动的区域
提示信息
右侧展示,可以显示当前贪吃蛇版本号,制作人员,游戏玩法等提示信息
游戏规则
当运行游戏时候,画面静止不动,可以默认让蛇头超右,游戏中设置w s a d 4个按键分别代表,上,下,左,右,也是用户比较常用的方向按键,当用户输入w或者s或者d时候激活游戏,注意输入a不可以激活,因为蛇不可以180°转弯,因此蛇的移动方向只可以一直向前或者920°旋转
当蛇吃掉食物时候,此时蛇会增加一个身段,另外食物需要重新随机的设置到屏幕上
游戏结束方式有两种:
- 蛇碰到墙壁为死亡
- 蛇碰到蛇身子,把自己吃掉也视为死亡
游戏移动
当激活游戏后,也分为两种,一种是死亡,这个是当 蛇头碰到蛇身或者是碰到墙壁两种死亡,这个我们暂时先不考虑,第二种是正常移动,那么我们先分析下正常移动
在正常移动的时候,也分为两种状态
第一种:蛇没吃到食物
这个时候,蛇只是单纯的移动,没吃到食物的时候,蛇会更新蛇头的位置,并且将之前蛇尾巴的位置为空格,也就是表示向前移动
第二种:蛇吃到食物
当吃到食物时,当前的蛇头的位置应该为之前食物的位置,那么蛇尾由于吃到了食物,就还是在原有位置,然后食物再重新分配到一个其他的位置,这个位置不能是蛇,也不能是墙。
墙模块
我们维护游戏种墙模块的开发,首先经过分析,我们可以得出再墙模块中,我们需要维护一个二维数组,对整个游戏中得元素进行设置,所以我们可以声明一个二维数组,char gameArray[][]
,具体的行数和列数可以定义出一个枚举,比如本游戏中设置的是26行,26列,enum{ROW=26,COL=26};
那么墙模块开发阶段,需要提供得主要接口是 初始化墙initwall
,以及打印墙,也就是将二维数组中得内容打印到控制台中,draw
方法,当然对外还要提供出一个可以修改二维数组元素的方法以及根据索引获取二维数组元素的方法:getWall,setWall
wall.h文件
#include<iostream>
using namespace std;class Wall
{
public:enum{ROW = 26, //行数COL = 26//列数};//初始化墙壁void initWall();//画出墙壁void drawWall();//根据索引来设置 二维数组里的内容//设置蛇的部分的时候和设置食物要用void setWall(int x, int y, char c);//根据索引来获取当前位置的符号char getWall(int x, int y);
private:char gameArray[ROW][COL];
};#endif
wall.cpp文件
#include<iostream>
#include"wall.h"
using namespace std;void Wall::initWall() //初始化墙壁,用二维数组
{for (int i = 0; i < ROW; i++){for (int j = 0; j < COL; j++){//放墙壁的地方if (i == 0 || j == 0 || i == ROW - 1 || j == COL - 1){gameArray[i][j] = '*';}else{gameArray[i][j] = ' ';}}}
}void Wall::drawWall()
{for (int i = 0; i < ROW; ++i){for (int j = 0; j < COL; ++j){//画的时候多加一个空格,看起来好看一些cout << gameArray[i][j] << " ";}if (i == 4){cout << "版本:1.0";}if (i == 5){cout << "制作人:刘晓昱";}if (i == 6){cout << "a:向左";}if (i == 7){cout << "d:向右";}if (i == 8){cout << "w:向上";}if (i == 9){cout << "s:向下";}cout << endl;}
}void Wall::setWall(int x, int y, char c)
{gameArray[x][y] = c;
}char Wall::getWall(int x, int y)
{return gameArray[x][y];
}
蛇模块
snake.h
#pragma once
#include<iostream>
#include"wall.h"
using namespace std;
#include"food.h"
class Snake
{
public:Snake(Wall &tempWall,Food&food); enum {UP = 'w',DOWN = 's',LEFT = 'a',RIGHT = 'd'};struct Point{//数据域int x;int y;//指针域Point *next;};//初始化结点void InitSnake();//销毁结点void destroyPoint();//添加结点void addPoint(int x, int y);//移动时删除结点void delPoint();//移动操作//返回值代表是否成功bool move(char key);//设定难度//获取刷屏时间int getSleepTime();//获取蛇的身段int countList();//获取分数int getScore();Point * pHead;Wall &wall;Food &food;bool isRool;//循环的标识
};
snake.cpp
#include"snake.h"
#include"wall.h"
#include<Windows.h>void gotoxy1(HANDLE hOut1, int x, int y)
{COORD pos;pos.X = x; //横坐标pos.Y = y; //纵坐标SetConsoleCursorPosition(hOut1, pos);
}
HANDLE hOut1 = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量Snake::Snake(Wall &tempWall,Food& tempFood) : wall(tempWall), food(tempFood)
{pHead = NULL;isRool = false;
}
void Snake::destroyPoint()
{Point * pCur = pHead;while (pHead!=NULL){pCur = pHead->next;delete pHead;pHead = pCur;}
}void Snake::addPoint(int x, int y)
{//创建新结点Point * newpoint = new Point;newpoint->x = x;newpoint->y = y;newpoint->next = NULL;//如果原来头不为空,改为身子if (pHead != NULL){wall.setWall(pHead->x, pHead->y, '=');gotoxy1(hOut1, pHead->y * 2, pHead->x);cout << "=";}newpoint->next = pHead;pHead = newpoint;//更新头部wall.setWall(pHead->x, pHead->y, '@');gotoxy1(hOut1, pHead->y * 2, pHead->x);cout << "@";}void Snake::InitSnake()
{destroyPoint();addPoint(5, 3);addPoint(5, 4);addPoint(5, 5);
}//移动时删除结点
void Snake::delPoint()
{//两个以上结点 才去做删除操作if (pHead == NULL || pHead->next == NULL){return;}//当前结点Point *pCur = pHead->next;//上一个结点Point *pPre = pHead;while (pCur->next!=NULL){pPre = pPre->next;pCur = pCur->next;}//删除尾结点wall.setWall(pCur->x, pCur->y, ' ');gotoxy1(hOut1, pCur->y * 2, pCur->x);cout << " ";delete pCur;pCur = NULL;pPre->next = NULL;
}bool Snake::move(char key)
{int x = pHead->x;int y = pHead->y;switch (key){case UP:x--;break;case DOWN:x++;break;case LEFT:y--;break;case RIGHT:y++;break;default:break;}//判断 如果下一步碰到的是尾巴,不应该死亡Point *pCur = pHead->next;//上一个结点Point *pPre = pHead;while (pCur->next != NULL){pPre = pPre->next;pCur = pCur->next;}if (pCur->x == x&&pCur->y == y){//碰到尾巴的循环isRool = true;}else{//判断用户要到达的位置是否成功if (wall.getWall(x, y) == '*' || wall.getWall(x, y) == '='){addPoint(x, y);delPoint();system("cls");wall.drawWall();cout << "得分:" << getScore() << "分" << endl;cout << "GAME OVER" << endl;return false;}}//移动成功 分两种//吃到食物,未吃到食物if (wall.getWall(x, y) == '#'){addPoint(x, y);//重新设置食物food.setFood();}else{addPoint(x, y);delPoint();if (isRool == true){wall.setWall(x, y, '@');gotoxy1(hOut1, y * 2, x);cout << "@";}}return true;
}int Snake::getSleepTime()
{int sleepTime=0;int size = countList();if (size < 5){sleepTime = 300;}else if (size >= 5 && size <= 10){sleepTime = 200;}else{sleepTime = 100;}return sleepTime;
}int Snake::countList()
{int size = 0;Point * curPoint = pHead;while (curPoint!=NULL){size++;curPoint = curPoint->next;}return size;
}int Snake::getScore()
{int size = countList();int score = (size-3) * 100;return score;
}
食物模块
food.h
#pragma once#include<iostream>
using namespace std;
#include"wall.h"
class Food
{
public:Food(Wall & tempwall);void setFood();int FoodX;int FoodY;Wall &wall;
};
food.cpp
#include"food.h"
#include<Windows.h>void gotoxy2(HANDLE hOut2, int x, int y)
{COORD pos;pos.X = x; //横坐标pos.Y = y; //纵坐标SetConsoleCursorPosition(hOut2, pos);
}
HANDLE hOut2 = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量Food::Food(Wall &tempwall) :wall(tempwall)
{}void Food::setFood()
{while (true){FoodX = rand() % (Wall::ROW - 2) + 1;FoodY = rand() % (Wall::COL - 2) + 1;//如果随机的位置是蛇头或蛇身,就重新生成随机数if (wall.getWall(FoodX, FoodY) == ' '){wall.setWall(FoodX, FoodY, '#');gotoxy2(hOut2, FoodY * 2, FoodX);cout << '#';break;}}}
game.cpp
#include<iostream>using namespace std;
#include"wall.h"
#include"snake.h"
#include"food.h"
#include<ctime>
#include<conio.h>
#include<Windows.h>//解决光标问题
void gotoxy(HANDLE hOut, int x, int y)
{COORD pos;pos.X = x; //横坐标pos.Y = y; //纵坐标SetConsoleCursorPosition(hOut, pos);
}
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量int main()
{//添加随机种子srand((unsigned int)time(NULL));//是否死亡的标识bool isDead = false;char preKey = NULL;Wall wall;wall.initWall();wall.drawWall();Food food(wall);food.setFood();Snake snake(wall,food);snake.InitSnake();gotoxy(hOut, 0, Wall::ROW);cout << "得分:" << snake.getScore() << "分" << endl;//gotoxy(hOut, 10, 5);//y*2 x //接受用户的输入while (!isDead){char key = _getch();//判断如果是第一次按了,左键,才不能激活游戏//判断上一次移动方向if (preKey == NULL&&key == snake.LEFT){continue;}do{if (key == snake.UP || key == snake.DOWN || key == snake.LEFT || key == snake.RIGHT){//判断本次按键是否与上次冲突if ((key == snake.LEFT&&preKey == snake.RIGHT )||(key == snake.RIGHT&&preKey == snake.LEFT )||(key == snake.UP&&preKey == snake.DOWN) ||(key == snake.DOWN&&preKey == snake.UP) ){key = preKey;}else{preKey = key;//不是冲突按键,可以更新按键}if (snake.move(key) == true){//移动成功//system("cls");//wall.drawWall();gotoxy(hOut, 0, Wall::ROW);cout << "得分:" << snake.getScore() << "分" << endl;Sleep(snake.getSleepTime());}else{isDead = true;break;}}else{key = preKey;//强制将错误按键变为上一次移动的方向}} while (!_kbhit());//当没有键盘输入的时候返回0}system("pause");return 0;
}
总结
墙模块
- 二维数组维护游戏内容
- 初始化二维数组
- 活出墙壁
- 提供对外接口
蛇模块
- 初始化蛇
- 销毁所有结点
- 添加新结点
食物模块
- 食物位置
- 提供对外接口,可以设置食物
- 随机出两个可以放置的位置,设置#
删除结点和移动蛇的封装
- 删除结点,通过两个临时结点,删除尾结点
- 移动 判断用户输入内容,然后进行移动操作
接受用户输入
- 接受一个字符,让蛇移动
- 用户输入按键后,进行自动转换
解决bug
- 按键冲突
- 180°不可以转
- 死亡撞墙,夺走一步
- 循环追尾,不要进入死亡的判断
辅助玩法
- 难度设定,根据蛇身段 产生不同的难度
- 分数的设定
优化游戏
- 用光标定位
项目效果