#ifndef _WALL_H_
#define _WALL_H_
#include<iostream>
using namespace std;
class WALL
{
public:
enum {LONG = 40,WIND = 40}; //设置围墙大小
void initWall(); //初始化墙
void drawWall(); //打印墙壁
void setWall(int a, int b, char c); //对外提供接口通过坐标,和符号来设置蛇的状态
char getWALL(int a, int b); //获取蛇的某个坐标下的的状态
private:
char wall[LONG][WIND]; //内部维护一个二维数组
};
#endif
#include"wall.h"
void WALL::initWall()
{
for (int i = 0; i < LONG; ++i)
{
for (int j = 0; j < WIND; ++j)
{
if (i == 0 || j == 0 || i == LONG - 1 || j == WIND - 1)
wall[i][j] = '*';
else
wall[i][j] = ' '; //多打印一个空格,维持围墙的长和宽基本相等的正方形
}
}
}
void WALL::drawWall() //打印墙壁
{
for (int i = 0; i < LONG; ++i)
{
for (int j = 0; j < WIND; ++j)
{
cout << wall[i][j] << ' ';
}
cout << endl;
}
}
void WALL::setWall(int a,int b,char c) //对外提供接口通过坐标,和符号来设置蛇的状态
{
wall[a][b] = c;
}
char WALL::getWALL(int a,int b) //获取二维数组中的某个位置
{
return wall[a][b];
}
#pragma once
#include<iostream>
#include"wall.h"
using namespace std;
class food
{
public:
food(WALL& wall); //初始化
void setfood(); //设置食物
int foodX; //食物坐标
int foodY;
WALL& wall; //维护一个引用
};
#include"food.h"
#include<windows.h>
HANDLE hOut2 = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量,并且这个只能在每个头文件中单独定义句柄和函数,否则无效
void gotoxy2(HANDLE hOut2, int x, int y)//其中x,y是与正常理解相反的,注意区分
{
COORD pos;
pos.X = x; //横坐标
pos.Y = y; //纵坐标
SetConsoleCursorPosition(hOut2, pos);
}//光标定位函数
void food::setfood() //设置食物
{
while (1)
{
foodX = rand() % (WALL::LONG - 2) + 1; //两个式子来获取随机刷新食物的坐标,坐标只会处于围墙内部,并且不和围墙重合
foodY = rand() % (WALL::WIND - 2) + 1;
if (wall.getWALL(foodX, foodY) == ' ') //只有当所处的位置是‘ ’才设置食物,如果是蛇身的话就重新设置
{
wall.setWall(foodX, foodY, '#');
gotoxy2(hOut2, foodY * 2, foodX); //下面两个位置是获取光标位置并且将光标所在位置的内容输出为#
cout << "#";
break;
}
}
}
food::food(WALL& temp) :wall(temp) //指定引用对象,必须使用初始化列表,引用类,和引用对象类必须使用初始化列表
{
}
#pragma once
#include<iostream>
#include"wall.h"
#include"food.h"
using namespace std;
class snake
{
public:
enum { UP = 'w', DOWN = 's', LEFT = 'a', RIGHT = 'd' }; //为了让代码看起来更整齐,通过枚举
snake(WALL& temp, food& temp2); //初始化蛇,需要刷新围墙和食物所以蛇必须获得食物和蛇的对象达到类内互通的目的
struct point
{
int x;
int y;
point* next;
}; //结点
point* head ;//定义头结点
void initSnake();//初始化蛇
void destroySnake();//销毁所有结点
void addSnake(int x,int y);//添加结点
void delpoint();//在蛇的移动过程中删除尾结点
bool move(char key); //通过键入的key的值操作蛇移动
int getSleepTime(); //设置刷屏时间通过分数可以让刷屏时间变慢,达到加速的后果
int countList(); //读取蛇的长度通过长度可可以设置分数
int getscore(); //算分数的函数
WALL& wall; //包含头文件#include"wall.h",设置一个引用,但是并不说明它是谁的引用,只知道是WALL类型的引用
food& food1; //包含头文件#include"food.h"设置一个引用,但是并不说明它是谁的引用,只知道是food类型的引用
bool isrool; //定义一个判断的值,非0即1用来在后面的代码中做判断
};
#include"snake.h"
#include"food.h"
#include<windows.h>
HANDLE hOut1 = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量,并且这个只能在每个头文件中单独定义句柄和函数,否则无效
void gotoxy1(HANDLE hOut1, int x, int y)//其中x,y是与正常理解相反的,注意区分
{ COORD pos;
pos.X = x; //横坐标
pos.Y = y; //纵坐标
SetConsoleCursorPosition(hOut1, pos);
}//光标定位函数
snake::snake(WALL& temp, food& temp2) :wall(temp), food1(temp2)
{
/*wall = *temp;
food1 = *temp2;*/
head = NULL;
isrool = false;
}
void snake::initSnake()
{
destroySnake(); //这一步是多想为的是有的用户想要连续的玩耍,比如 一把玩完后,在不退出程序的情况下再玩一次就需要把生一次的蛇身先处理掉
addSnake(5,3); //下面三行的目的是固定蛇的位置,也就是增加结点
addSnake(5,4);
addSnake(5,5); //蛇头
}
void snake::destroySnake() //删除结点
{
point* cur = head; //从头结点开始
while (head != NULL)
{
cur = head->next;
delete head;
head = cur;
}
}
void snake::addSnake(int x, int y) //添加结点
{
point* newpoint = new point; //这一个就是初始化蛇的时候,蛇还不存在也就是说头节点指向空(我们想要的是处理完毕之后头结点指向蛇头,头结点的下一个结点指向蛇头的后一个身段)
newpoint->x = x; //首先是使用我们默认给出的三个位置初始化蛇的身体
newpoint->y = y;
newpoint->next = NULL;
if (head != NULL) //对于第二个节点话head已经不是空了,先将之前的蛇头变成蛇身=,之后再执行身后的代码就是蛇头往前移动
{
wall.setWall(head->x, head->y, '=');
gotoxy1(hOut1, head->y * 2, head->x); //光标定位
cout << "=";
}
newpoint->next = head; //对头结点进行赋值操作,因为我们定义的head指向的是NULL
head = newpoint;
wall.setWall(head->x, head->y, '@'); //对第一个创建出来的结点赋予蛇头
gotoxy1(hOut1, head->y * 2, head->x);
cout << "@";
}
void snake::delpoint() //删除结点,移动过程中需要删除结点
{
if (head == NULL || head->next == NULL ) //如果只有蛇头,和蛇的一个=身体的话,那么就不进行任何操作
return;
point* pcur = head->next; //用两个指针来锁定蛇尾
point* cur = head;
while (pcur->next != NULL)
{
cur = cur->next;
pcur = pcur->next;
}
wall.setWall(pcur->x, pcur->y, ' '); //锁定蛇尾将蛇尾变成空格也就是空
gotoxy1(hOut1, pcur->y * 2, pcur->x); //光标定位
cout << " ";
delete pcur; //将之前构造蛇身的时候申请的结点删除的一干二净
pcur = NULL;
cur->next = NULL; //让蛇尾之前的结点的next为空
}
bool snake::move(char key) //通过键入使蛇运动
{
int x = head->x; //首先获取蛇头坐标
int y = head->y;
switch (key)
{
case UP:
x--;
break;
case LEFT:
y--;
break;
case DOWN:
x++;
break;
case RIGHT:
y++;
break;
default:
break;
}
//判断如果下一步碰到的是尾巴(末梢)不应该死亡,因为蛇头蛇尾是一起移动的
point* pcur = head->next;
point* cur = head;
while (pcur->next != NULL)
{
cur = cur->next;
pcur = pcur->next;
}
if (pcur->x == x && pcur->y == y)
{
isrool = true;
}
else
{
//判断用户移动是否成功
if (wall.getWALL(x, y) == '*' || wall.getWALL(x, y) == '=') //如果移动不是蛇尾的话那么就判断是不是墙壁和身体的其他部位
{
addSnake(x, y); //先增减一个结点让蛇头代替墙的*
delpoint(); //删除一个尾结点,
system("cls"); //并清一次屏
wall.drawWall(); //再打印一下最终的墙和玩家得分情况
cout << "得分:" << getscore() << endl;
cout << "GAME OVER!" << endl;
return false;
}
}
//判断用户是否吃到食物
if (wall.getWALL(x, y) == '#') //如果移动之后的位置有食物则重新重新刷新食物
{
addSnake(x, y); //此时只进行增加结点而不删除,目的就是为了使蛇的长度增加
//重新设置食物
food1.setfood();
}
else
{
addSnake(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 if (size > 10 && size <= 15)
{
sleeptime = 100;
}
else if(size > 15 && size < 20)
{
sleeptime = 50;
}
else
{
sleeptime = 30;
}
return sleeptime;
}
int snake::countList() //获取蛇的长度
{
int count = 0;
point* cur = head;
while (cur != NULL)
{
cur = cur->next;
count++;
}
return count;
}
int snake::getscore() ///计算分数
{
int size = countList();
int score = (size - 3) * 100;
return score;
}
#include<iostream>
using namespace std;
#include<time.h>
#include"wall.h"
#include"snake.h"
#include"food.h"
#include<conio.h>
#include<windows.h>
//关于光标定位
//我们的代码是通过睡眠使代码一帧一帧的刷新,达到动态的效果,但是围墙或者其他位置没有改变的话就不必要刷新,所以我们可以通过光标定位,来锁定极个别的刷新位置,来进行刷新打印就可以了。
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量,并且这个只能在每个头文件中单独定义句柄和函数,否则无效
void gotoxy(HANDLE hOut, int x, int y)//其中x,y是与正常理解相反的,注意区分。
{
COORD pos;
pos.X = x; //横坐标
pos.Y = y; //纵坐标
SetConsoleCursorPosition(hOut, pos);
}//光标定位函数
//关于光标定位的注意事项,每个文件中必须声明一次,并且hOut,gotoxy,在每个文件中的名字都不可以重复,而且要添加头文件#include<windows.h>
//游戏入口
int main()
{
bool isdead = false; //设置一个判断值,判断蛇是否还活着,默认未死亡
char poor = NULL; //用poor来记录前一次按下的是哪一个按键
srand((unsigned int)time(NULL));
WALL p;
p.initWall(); //画墙
p.drawWall(); //打印
food v(p);
v.setfood();
snake s(p,v);
s.initSnake();
gotoxy(hOut, 0, WALL::LONG);
cout << "得分:" << s.getscore() << endl;
while (!false)
{
char key = _getch(); //从键盘读取一个字符
if (poor ==NULL && key == s.LEFT) //我们刚开始蛇头是朝右的,你要是刚上来就按左的话我们默认是蛇不动
{
continue;
}
do
{
if (key == s.LEFT || key == s.UP || key == s.RIGHT || key == s.DOWN)
{
if ((key == s.LEFT &&poor == s.RIGHT) || ///抵消按键冲突,就是如果你在往后正在走突然按下往前,他就会以为你撞到身子然后就死掉了
(key == s.UP &&poor == s.DOWN) ||
(key == s.RIGHT &&poor == s.LEFT) ||
(key == s.DOWN &&poor == s.UP))
{
key = poor; //入股发生按键冲突就依然按照之前的输入前进
}
poor = key; //用poor记录输入的值
if (s.move(key) == true)
{
//system("cls");
//p.drawWall();
gotoxy(hOut, 0,WALL::LONG);
cout << "得分:" << s.getscore() << endl;
Sleep(s.getSleepTime());
}
else
{
isdead = true; //已经死亡
break;
}
}
else
{
key = poor; ///如果键入字母之后,后续并没有再键入字母则依然使用上次的字母
}
} while (!_kbhit()); //判断键盘是否有输入,有输入蛇才会被激活(但是只有按wasd才会动其他字符无效是因为再循环内部还有判断)
}
return 0;
}