c++实现贪吃蛇

游戏中的实现元素

游戏中元素分为:墙壁,蛇,事物以及蛇的可行区域和右侧的版本号和游戏玩法提示
墙壁

*号表示,代表一个区域范围,也就是蛇的可移动区域,蛇如果碰到墙壁视为死亡,

分为蛇头,蛇身,蛇头用@符号表示,蛇身用=等号表示,当蛇吃到食物时候,蛇身+1,一意为着长度变长,贪吃蛇可以通过不断地吃食物来增加自己的身体
食物
#井号表示,蛇碰到食物会将食物吃掉
可移动区域
空格 表示,代表蛇可以移动的区域
提示信息
右侧展示,可以显示当前贪吃蛇版本号,制作人员,游戏玩法等提示信息

游戏规则

当运行游戏时候,画面静止不动,可以默认让蛇头超右,游戏中设置w s a d 4个按键分别代表,上,下,左,右,也是用户比较常用的方向按键,当用户输入w或者s或者d时候激活游戏,注意输入a不可以激活,因为蛇不可以180°转弯,因此蛇的移动方向只可以一直向前或者920°旋转
当蛇吃掉食物时候,此时蛇会增加一个身段,另外食物需要重新随机的设置到屏幕上
游戏结束方式有两种:

  1. 蛇碰到墙壁为死亡
  2. 蛇碰到蛇身子,把自己吃掉也视为死亡

游戏移动

当激活游戏后,也分为两种,一种是死亡,这个是当 蛇头碰到蛇身或者是碰到墙壁两种死亡,这个我们暂时先不考虑,第二种是正常移动,那么我们先分析下正常移动
在正常移动的时候,也分为两种状态

第一种:蛇没吃到食物

这个时候,蛇只是单纯的移动,没吃到食物的时候,蛇会更新蛇头的位置,并且将之前蛇尾巴的位置为空格,也就是表示向前移动

第二种:蛇吃到食物

当吃到食物时,当前的蛇头的位置应该为之前食物的位置,那么蛇尾由于吃到了食物,就还是在原有位置,然后食物再重新分配到一个其他的位置,这个位置不能是蛇,也不能是墙。

墙模块

我们维护游戏种墙模块的开发,首先经过分析,我们可以得出再墙模块中,我们需要维护一个二维数组,对整个游戏中得元素进行设置,所以我们可以声明一个二维数组,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;
}

总结

墙模块

  1. 二维数组维护游戏内容
  2. 初始化二维数组
  3. 活出墙壁
  4. 提供对外接口

蛇模块

  1. 初始化蛇
  2. 销毁所有结点
  3. 添加新结点

食物模块

  1. 食物位置
  2. 提供对外接口,可以设置食物
  3. 随机出两个可以放置的位置,设置#

删除结点和移动蛇的封装

  1. 删除结点,通过两个临时结点,删除尾结点
  2. 移动 判断用户输入内容,然后进行移动操作

接受用户输入

  1. 接受一个字符,让蛇移动
  2. 用户输入按键后,进行自动转换

解决bug

  1. 按键冲突
  2. 180°不可以转
  3. 死亡撞墙,夺走一步
  4. 循环追尾,不要进入死亡的判断

辅助玩法

  1. 难度设定,根据蛇身段 产生不同的难度
  2. 分数的设定

优化游戏

  1. 用光标定位

项目效果

在这里插入图片描述
在这里插入图片描述

  • 72
    点赞
  • 419
    收藏
    觉得还不错? 一键收藏
  • 24
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值