经典游戏开发:C++实现贪吃蛇游戏

贪吃蛇设计思路:

由于是第一次尝试小游戏,主要是了解游戏开发流程、设计思想,借助前人的成果进行修改和尝试,修改调通运行起来,对于自己来说也是进了很大一步呀!

进入主题,贪吃蛇设计思路:

明确功能:

“使用键盘的上下左右键来控制蛇的方向;随机产生食物,蛇吃到食物后,身体变长一节;设定边界,蛇撞到墙或者吃到自身的时候,游戏结束。”

1.一张地图,四周有墙体。 
2.一条蛇,由蛇身和蛇头组成。 
3.食物,蛇吃到食物后身体变长,并且重新生成一个食物。 
3.蛇需要移动,这是难实现的一个部分。 
4.蛇撞到墙或者自己的身体就会死亡。 
5.需要能用键盘控制蛇的运动方向。 

1.整个地图用一个二维字符数组实现。 
2.用一个二维数组snake [x] [3]来描述,snake[i][0] 的值用来描述头和身体,snake[i][0] =1表示头,snake[i][0] =0表示身体,snake[i][1] 和snake[i][2] 分别描述所在的x坐标和y坐标,i表示第i节身体。 
3.通过产生随机数作为食物的坐标,这个坐标不能超过地图的边界也不能和蛇的坐标一样。 
4.蛇的移动,将蛇头移动到下一个坐标的时候,后面的节依次重复前一个节的移动。只需要控制蛇头的方向,设定一个变量dir=1,表示向上,1,2,3,4分别表示上下左右。如果蛇头向上走,x不变,y-1。 
5.蛇头撞墙或者自己的身体,通过判断蛇头坐标是否和墙的坐标重合或者和蛇的某一节重合,当碰撞发生后,停止刷新屏幕。 
6.键盘输入方向键后,重置dir的值。
 

增加游戏的娱乐性,例如等级选择功能,通过输入数字来选择等级,等级越高,贪吃蛇移动速度越快,而且得分越高。

游戏的功能性,在游戏结束后输出得分情况,并提示是否继续游戏,而不是直接退出游戏,而是通过选择的方式决定继续游戏或者退出游戏,暂停功等。

游戏开发心得:

1.这是第一次接触游戏开发相关的知识,感觉自己能力不足,需要学习的东西还很多。

2.现在也有一些开源算法库之类的资源,极大程度方便了初学者,要学会查找资源。因为自己平时用c多一些,所以此次游戏开发还是查找的c相关的实现,但是很遗憾时间的原因,还没有百分之百达到任务要求。

3.在学习小组了解到了pygame等实现方式,也在查找学习,配置环境争取后续可以使用python+pygame实现一个更有意思好看好玩的游戏。

4.这次感触最深的就是:要学会分析问题、拆分问题;把复杂的问题分解简化为一个一个小问题,再组合逻辑流程就可以解决一个大问题。从来没有想到自己也能通过代码得到一个小游戏呢!每个人的能力、起点都不相同,但是只要努力,就可以成为最好的自己!

回头看自己竟然坚持了一周打卡学习任务、也完成了大多的任务,虽然不是最棒最好的,希望自己能将好习惯延续,加油!

C++实现①

实现了基本功能,等价等级选择及暂停
SnakePosition.h

#ifndef _SNAKEPOSITION_H_
#define _SNAKEPOSITION_H_
#pragma warning(disable:4996)
#pragma once
using namespace std;
#define  N 22
 
class SnakePosition
{
public:
	SnakePosition();
	~SnakePosition();
public:

	int x, y;      //x表示行,y表示列
	
	void initialize(int &);//坐标初始化

};
#endif

SnakePosition.cpp

#include "SnakePosition.h"


SnakePosition::SnakePosition()
{
}


SnakePosition::~SnakePosition()
{
}
void SnakePosition::initialize(int &j)
{
	x = 1;

	y = j;
}

SnakeMap.h

#ifndef _SNAKEMAP_H_
#define _SNAKEMAP_H_

#pragma warning(disable:4996)
#pragma once

#include <windows.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <cstring>
#include <cstdio>
#include <iostream>
#include "SnakePosition.h"
//贪吃蛇的棋盘图

using namespace std;


 

class SnakeMap
{
public:
	int x1;
	int y1; // 随机出米
	int gameover;

	int x, y;

	long start;
	SnakeMap(int h = 4, int t = 1, int l = 4, char d = 77, int s = 0);
	~SnakeMap();
	SnakePosition position[(N - 2)*(N - 2) + 1]; //定义贪吃蛇坐标类数组,有(N-2)*(N-2)个坐标
	private:

		char s[N][N];//定义贪吃蛇棋盘,包括墙壁。

		int grade, length;

		int gamespeed; //前进时间间隔

		char direction; // 初始情况下,向右运动

		int head, tail;

		int score;

		bool gameauto;

	public:
	

		void initialize();   //初始化函数

		void show_game();

		int updata_game();

		void setpoint();

		void getgrade();

		void display();



};

#endif

SnakeMap.cpp

#include "SnakeMap.h"

SnakeMap::SnakeMap(int h, int t , int l , char d , int s ) :length(l), direction(d), head(h), tail(t), score(s)
{
}


SnakeMap::~SnakeMap()
{
}
//定义初始化函数,将贪吃蛇的棋盘图进行初始化

void SnakeMap::initialize()
{
	int i, j;

	for (i = 1; i <= 3; i++)

		s[1][i] = '*';

	s[1][4] = '#';

	for (i = 1; i <= N - 2; i++)

	for (j = 1; j <= N - 2; j++)

		s[i][j] = ' '; // 初始化贪吃蛇棋盘中间空白部分

	for (i = 0; i <= N - 1; i++)

		s[0][i] = s[N - 1][i] = '-'; //初始化贪吃蛇棋盘上下墙壁

	for (i = 1; i <= N - 2; i++)

		s[i][0] = s[i][N - 1] = '|'; //初始化贪吃蛇棋盘左右墙壁
}
//============================================
//输出贪吃蛇棋盘信息

void SnakeMap::show_game()

{

	system("cls"); // 清屏

	int i, j;

	cout << endl;

	for (i = 0; i < N; i++)
	{

		cout << '\t';

		for (j = 0; j < N; j++)

			cout << s[i][j] << ' '; // 输出贪吃蛇棋盘

		if (i == 2) cout << "\t等级:" << grade;

		if (i == 6) cout << "\t速度:" << gamespeed;

		if (i == 10) cout << "\t得分:" << score << "分";

		if (i == 14) cout << "\t暂停:按一下空格键";

		if (i == 18) cout << "\t继续:按两下空格键";

		cout << endl;
	}
}

//输入选择等级
void SnakeMap::getgrade()
{
	cin >> grade;

	while (grade > 7 || grade < 1)
	{
		cout << "请输入数字1-7选择等级,输入其他数字无效" << endl;

		cin >> grade;
	}
	switch (grade)
	{
	case 1: gamespeed = 1000; gameauto = 0; break;

	case 2: gamespeed = 800; gameauto = 0; break;

	case 3: gamespeed = 600; gameauto = 0; break;

	case 4: gamespeed = 400; gameauto = 0; break;

	case 5: gamespeed = 200; gameauto = 0; break;

	case 6: gamespeed = 100; gameauto = 0; break;

	case 7: grade = 1; gamespeed = 1000; gameauto = 1; break;

	}

}

//输出等级,得分情况以及称号

void SnakeMap::display()
{

	cout << "\n\t\t\t\t等级:" << grade;

	cout << "\n\n\n\t\t\t\t速度:" << gamespeed;

	cout << "\n\n\n\t\t\t\t得分:" << score << "分";

}

//随机产生米
void SnakeMap::setpoint()
{
	srand(time(0));

	do
	{

		x1 = rand() % (N - 2) + 1;

		y1 = rand() % (N - 2) + 1;

	} while (s[x1][y1] != ' ');

	s[x1][y1] = '*';
}

char key;

int SnakeMap::updata_game()
{
	gameover = 1;

	key = direction;

	start = clock();

	while ((gameover = (clock() - start <= gamespeed)) && !kbhit());

	//如果有键按下或时间超过自动前进时间间隔则终止循环



	if (gameover)
	{

		getch();

		key = getch();
	}

	if (key == ' ')

	{
		while (getch() != ' '){};//这里实现的是按空格键暂停,按空格键继续的功能,但不知为何原因,需要按两下空格才能继续。这是个bug。
	}

	else

		direction = key;

	switch (direction)
	{
	case 72: x = position[head].x - 1; y = position[head].y; break; // 向上

	case 80: x = position[head].x + 1; y = position[head].y; break; // 向下

	case 75: x = position[head].x; y = position[head].y - 1; break; // 向左

	case 77: x = position[head].x; y = position[head].y + 1; // 向右

	}

	if (!(direction == 72 || direction == 80 || direction == 75 || direction == 77))   // 按键非方向键

		gameover = 0;

	else if (x == 0 || x == N - 1 || y == 0 || y == N - 1)   // 碰到墙壁

		gameover = 0;

	else if (s[x][y] != ' '&&!(x == x1&&y == y1))    // 蛇头碰到蛇身

		gameover = 0;

	else if (x == x1 && y == y1)

	{ // 吃米,长度加1

		length++;

		if (length >= 8 && gameauto)

		{

			length -= 8;

			grade++;

			if (gamespeed >= 200)

				gamespeed -= 200; // 改变贪吃蛇前进速度

			else

				gamespeed = 100;

		}

		s[x][y] = '#';  //更新蛇头

		s[position[head].x][position[head].y] = '*'; //吃米后将原先蛇头变为蛇身

		head = (head + 1) % ((N - 2)*(N - 2));   //取蛇头坐标

		position[head].x = x;

		position[head].y = y;

		show_game();

		gameover = 1;

		score += grade * 20;  //加分

		setpoint();   //产生米

	}

	else
	{ // 不吃米

		s[position[tail].x][position[tail].y] = ' ';//将蛇尾置空

		tail = (tail + 1) % ((N - 2) * (N - 2));//更新蛇尾坐标

		s[position[head].x][position[head].y] = '*';  //将蛇头更为蛇身

		head = (head + 1) % ((N - 2) * (N - 2));

		position[head].x = x;

		position[head].y = y;

		s[position[head].x][position[head].y] = '#'; //更新蛇头

		gameover = 1;

	}
	return gameover;

}

main.h

// Snake.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"


#include <windows.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <cstring>
#include <cstdio>
#include <iostream>


#include "SnakeMap.h"
#include "SnakePosition.h"

using namespace std;




//====================================
//主函数部分
//====================================
int main()
{
	char ctn = 'y';

	int nodead;

	cout << "\n\n\n\n\n\t\t\t 欢迎进入贪吃蛇游戏!" << endl;//欢迎界面;

	cout << "\n\n\n\t\t\t 按任意键马上开始。。。" << endl;//准备开始;;

	getch();

	while (ctn == 'y')
	{
		system("cls"); // 清屏

		SnakeMap snake;

		snake.initialize();

		cout << "\n\n请输入数字选择游戏等级:" << endl;

		cout << "\n\n\n\t\t\t1.等级一:速度 1000 \n\n\t\t\t2.等级二:速度 800 \n\n\t\t\t3.等级三:速度 600 ";

		cout << "\n\n\t\t\t4.等级四:速度 400 \n\n\t\t\t5.等级五:速度 200 \n\n\t\t\t6.等级六:速度 100 \n\n\t\t\t7.自动升级模式" << endl;

		snake.getgrade();//获取等级

		for (int i = 1; i <= 4; i++)
		{
			snake.position[i].initialize(i);//初始化坐标
		}

		snake.setpoint();  // 产生第一个米

		do
		{
			snake.show_game();

			nodead = snake.updata_game();

		} while (nodead);

		system("cls"); //清屏



		cout << "\n\n\n\t\t\t\tGameover!\n\n" << endl;

		snake.display();//输出等级/得分情况

		cout << "\n\n\n\t\t    是否选择继续游戏?输入 y 继续,n 退出" << endl;

		cin >> ctn;

	}

	return 0;
}

==================================================================

c++实现②:

更加完善的功能和界面显示,增加游戏记录功能等

snake.h

#ifndef SNAKE
#define SNAKE

#include<windows.h>
#include<vector>
#include<string>

//标记界面和菜单项的ID
enum ID_ { ID_1 = 1, ID_2, ID_3, ID_4, ID_5, ID_6, ID_7 };

//标记初级,中级,高级三种游戏等级
enum Rank_ { first = 150, middle = 100, high = 50 };


//贪吃蛇结构  
struct Snake_data{
	bool state;                    //标记蛇状态
	UINT Snake_size;                //标记蛇长度
	std::vector<COORD> Snake_coord; //蛇的当前坐标
	COORD food_coord;               //食物坐标
	UINT rank;                     //标记等级
	UINT mark;                      //分数
	UINT key;                       //记录按键方向
	ID_ ID_interface;               //标记当前停留界面ID
	short ID_option;               //标记被选中菜单的ID
	bool Flag;                      //标记刷新
};

//贪吃蛇类
class Snake_class{
public:
	Snake_class(){}
	Snake_class(Snake_data *data) : pSnake(data){}
	void ShowMenu();            //显示菜单
	void StartGame();          //开始游戏
	void ShowPause();           //显示暂停界面
	void SaveRecord();          //保存记录
	void ShowRank();            //显示等级
	void ShowRecord();          //显示存档记录列表
	bool Read(size_t);        //读取记录
	void ShowArmory();          //显示英雄榜
	void Die();                 //死亡界面
	size_t Create_file();       //存储文件
private:
	void JudgeDie();            //判断死亡
	bool Jesus();               //上榜判断
	void ShowSnake(COORD&);   //显示蛇
	void ShowPointer(COORD, std::string); //显示指针
	void Show_not();            //显示无数据
	Snake_data *pSnake;
};



#endif;

snake.cpp

#include"SnakeNew.h"
#include<iostream>
#include<fstream>
#include<ctime>
#include<cstdlib>

//获取缓冲区句柄
static const HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
static CONSOLE_SCREEN_BUFFER_INFO info;
//保存蛇的记录数据文件
static const char *Snake_Record[] = { "d://SnakeRecord//Snake1.txt",
"d://SnakeRecord//Snake2.txt",
"d://SnakeRecord//Snake3.txt",
"d://SnakeRecord//Snake4.txt",
"d://SnakeRecord//Snake5.txt" };

static const char *S_Armory[] = { "d://SnakeRecord//Armory1.txt",
"d://SnakeRecord//Armory2.txt",
"d://SnakeRecord//Armory3.txt" };
//显示主菜单(完成已测试)
void Snake_class::ShowMenu(){
	//获取缓冲区相关信息
	GetConsoleScreenBufferInfo(handle, &info);
	char *str[] = { "开 始 游 戏",
		"难 度 等 级",
		"读 取 存 档",
		"英 雄 榜",
		"退 出 游 戏" };
	//输出菜单选项
	short y = 3;
	COORD cd = { info.srWindow.Right / 2 - 5, y };
	for (size_t n = 0; n < sizeof(str) / sizeof(*str); ++n){
		SetConsoleCursorPosition(handle, cd);
		std::cout << *(str + n);
		cd.Y += 2;
	}
	//判断指针指向哪个菜单选项
	cd.X -= 2;
	cd.Y = y;
	switch (pSnake->ID_option){
	case ID_1:
		break;
	case ID_2:
		cd.Y = y + 2; break;
	case ID_3:
		cd.Y = y + 4; break;
	case ID_4:
		cd.Y = y + 6; break;
	case ID_5:
		cd.Y = y + 8; break;
	}
	ShowPointer(cd, pSnake->Flag ? std::string("  ") : std::string("><"));
}

//开始游戏(完成待测试)
void Snake_class::StartGame(){
	COORD cd;
	//判断是否已有食物
	if (!pSnake->food_coord.X){
		srand((unsigned int)time(NULL));
		while (1){
			//限制食物出现的坐标不与围墙相同
			++(cd.X = rand() % 78);
			++(cd.Y = rand() % 18);
			//判断食物坐标是否与蛇身吻合
			std::vector<COORD>::iterator ite;
			for (ite = pSnake->Snake_coord.begin(); ite != pSnake->Snake_coord.end(); ++ite){
				if (ite->X == cd.X && ite->Y == cd.Y)
					break;
			}
			if (ite == pSnake->Snake_coord.end()){
				pSnake->food_coord.X = cd.X;
				pSnake->food_coord.Y = cd.Y;
				break;
			}
		}
	}
	SetConsoleCursorPosition(handle, pSnake->food_coord);
	std::cout << "*";
	//判定按键方向
	cd.X = pSnake->Snake_coord.begin()->X;
	cd.Y = pSnake->Snake_coord.begin()->Y;
	switch (pSnake->key){
	case VK_UP:
		--cd.Y; break;
	case VK_DOWN:
		++cd.Y; break;
	case VK_LEFT:
		--cd.X; break;
	case VK_RIGHT:
		++cd.X; break;
	}
	ShowSnake(cd);
	JudgeDie();
}

//显示暂停界面(完成已测试)
void Snake_class::ShowPause(){
	COORD cd = { info.srWindow.Right / 2 - 10, info.srWindow.Bottom / 5 };
	SetConsoleCursorPosition(handle, cd);
	std::cout << "游 戏 暂 停 中 ......";
	char *str[] = { "继 续 游 戏",
		"保 存 游 戏",
		"退 出 游 戏" };
	//输出菜单选项
	short X = info.srWindow.Right / 3;
	cd.X = X / 2 - 5;
	cd.Y = info.srWindow.Bottom / 3 * 2 - 4;
	for (size_t i = 0; i < sizeof(str) / sizeof(*str); ++i){
		SetConsoleCursorPosition(handle, cd);
		std::cout << *(str + i);
		cd.X += X;
	}

	//判断指针应指向的菜单选项
	switch (pSnake->ID_option){
	case ID_1:
		cd.X = X / 2 - 7; break;
	case ID_2:
		cd.X = X / 2 + X - 7; break;
	case ID_3:
		cd.X = X / 2 + 2 * X - 7; break;
	}
	ShowPointer(cd, pSnake->Flag ? std::string("  ") : std::string("><"));
}

//保存记录(完成未测试)
void Snake_class::SaveRecord(){
	std::ofstream outopen;
	outopen.open(*(Snake_Record + Create_file()));
	if (!outopen){
		std::cerr << "\n打开文件失败!\n";
		exit(0);
	}
	//保存记录到文件中,前面加"0"是为了后面检测是否文件为空使用
	outopen << "0 " << pSnake->Snake_size << " ";
	for (std::vector<COORD>::iterator ite = pSnake->Snake_coord.begin();
		ite != pSnake->Snake_coord.end(); ++ite)
		outopen << ite->X << " " << ite->Y << " ";
	outopen << pSnake->rank << " " << pSnake->mark << " " << pSnake->key;
	outopen.close();
	//输出保存成功
	COORD cd = { info.srWindow.Right / 2 - 4, info.srWindow.Bottom / 3 * 2 - 1 };
	SetConsoleCursorPosition(handle, cd);
	std::cout << "保存成功!\a";
}

//显示等级(已测试)
void Snake_class::ShowRank(){
	COORD cd = { info.srWindow.Right / 2 - 6, info.srWindow.Bottom / 3 + 2 };
	char *str[] = { "初       级",
		"中       级",
		"高       级" };
	for (size_t i = 0; i < sizeof(str) / sizeof(*str); ++i){
		SetConsoleCursorPosition(handle, cd);
		std::cout << *(str + i);
		cd.Y += 2;
	}
	//判断指针所停留的选项
	cd.X -= 2;
	cd.Y = info.srWindow.Bottom / 3 + 2;
	switch (pSnake->ID_option){
	case ID_1:
		break;
	case ID_2:
		cd.Y += 2; break;
	case ID_3:
		cd.Y += 4; break;
	}
	ShowPointer(cd, pSnake->Flag ? std::string("  ") : std::string("><"));
}

//显示存档记录(已测试)
void Snake_class::ShowRecord(){
	COORD cd = { info.srWindow.Right / 2 - 12, 8 };
	//输出记录
	std::ifstream inopen;
	SetConsoleCursorPosition(handle, cd);
	for (size_t i = 0; i < sizeof(Snake_Record) / sizeof(*Snake_Record); ++i){
		inopen.open(*(Snake_Record + i));
		if (!inopen || (inopen.get() == EOF && i == 0)){
			Show_not();
			pSnake->ID_option = ID_7;//第7个选项标记,在按回车时检测下
			return;
		}
		if (inopen.get() != EOF){
			UINT _rank, _mark;
			inopen >> _mark;
			++(_mark *= 2);
			while (_mark--)
				inopen >> _rank;
			inopen >> _mark;
			switch (_rank){
			case first:
				std::cout << "初级"; break;
			case middle:
				std::cout << "中级"; break;
			case high:
				std::cout << "高级"; break;
			}
			std::cout << "\t\t\t  " << _mark;
		}
		else
			std::cout << " ---\t\t\t  ---";

		cd.Y += 2;
		SetConsoleCursorPosition(handle, cd);
		inopen.close();
		inopen.clear();//重置流状态
	}
	std::cout << "\t   返 回 菜 单";
	cd.X = info.srWindow.Right / 2 - 4;
	cd.Y = 4;
	SetConsoleCursorPosition(handle, cd);
	std::cout << "存 档 记 录";
	cd.X -= 10;
	cd.Y += 2;
	SetConsoleCursorPosition(handle, cd);
	std::cout << "游戏等级\t\t当前分数";
	//输出指针
	cd.X = info.srWindow.Right / 2 - 14;
	cd.Y = 8;
	switch (pSnake->ID_option){
	case ID_1:
		break;
	case ID_2:
		cd.Y += 2; break;
	case ID_3:
		cd.Y += 4; break;
	case ID_4:
		cd.Y += 6; break;
	case ID_5:
		cd.Y += 8; break;
	case ID_6:
		cd.Y += 10; break;
	}
	ShowPointer(cd, pSnake->Flag ? std::string("  ") : std::string("><"));
}

//读取记录
bool Snake_class::Read(size_t i){
	std::ifstream inopen(*(Snake_Record + i));
	if (inopen.get() == EOF){
		std::cout << "\a";
		inopen.close();
		return false;
	}
	inopen >> pSnake->Snake_size;
	COORD cd;
	pSnake->Snake_coord.clear();
	for (int n = 1; n <= pSnake->Snake_size; ++n){
		inopen >> cd.X >> cd.Y;
		pSnake->Snake_coord.push_back(cd);
	}
	inopen >> pSnake->rank >> pSnake->mark >> pSnake->key;
	inopen.close();
	inopen.clear();
	return true;
}

//显示英雄榜(未测试)
void Snake_class::ShowArmory(){
	short nt = 0;
	COORD cd = { info.srWindow.Right / 3, info.srWindow.Bottom / 3 };
	std::ifstream inopen;
	for (size_t i = 0; i < sizeof(S_Armory) / sizeof(*S_Armory); ++i){
		UINT _rank = 0, _mark = 0;
		inopen.open(*(S_Armory + i));
		if (!inopen){
			++nt;
			continue;
		}
		inopen >> _rank >> _mark;
		switch (_rank){
		case first:
			SetConsoleCursorPosition(handle, cd);
			std::cout << "小牛 :初级\t\t  " << _mark;
			break;
		case middle:
			cd.Y += 2;
			SetConsoleCursorPosition(handle, cd);
			std::cout << "中牛 :中级\t\t  " << _mark;
			break;
		case high:
			cd.Y += 2;
			SetConsoleCursorPosition(handle, cd);
			std::cout << "大牛 :高级\t\t  " << _mark;
			break;
		}
		inopen.close();
		inopen.clear();
	}
	if (nt == 3){
		Show_not();
		return;
	}
	cd.X = info.srWindow.Right / 2 - 3;
	cd.Y = 4;
	SetConsoleCursorPosition(handle, cd);
	std::cout << "英 雄 榜";
	cd.X -= 10;
	cd.Y += 2;
	SetConsoleCursorPosition(handle, cd);
	std::cout << "\t等 级\t\t分 数";
	cd.Y = info.srWindow.Bottom - 7;
	SetConsoleCursorPosition(handle, cd);
	std::cout << "按回车返回主菜单........";
}

//死亡界面(未测试)
void Snake_class::Die(){
	COORD cd = { info.srWindow.Right / 2 - 10, info.srWindow.Bottom / 5 };
	SetConsoleCursorPosition(handle, cd);
	std::cout << "    您 已 out 了 !";
	char *str[] = { "返 回 菜 单",
		"退 出 游 戏" };
	//输出菜单选项
	short X = info.srWindow.Right / 2;
	cd.X = X / 2 - 5;
	cd.Y = info.srWindow.Bottom / 3 * 2 - 4;
	for (size_t i = 0; i < sizeof(str) / sizeof(*str); ++i){
		SetConsoleCursorPosition(handle, cd);
		std::cout << *(str + i);
		cd.X += X;
	}

	//判断指针应指向的菜单选项
	switch (pSnake->ID_option){
	case ID_1:
		cd.X = X / 2 - 7; break;
	case ID_2:
		cd.X = X / 2 + X - 7; break;
	}
	ShowPointer(cd, pSnake->Flag ? std::string("  ") : std::string("><"));
	if (Jesus()){
		cd.X = X / 2;
		cd.Y = info.srWindow.Bottom / 3;
		SetConsoleCursorPosition(handle, cd);
		std::cout << "哟...这分? ╮(╯▽╰)╭ 也能上榜。。。!";
		cd.Y += 2;
		SetConsoleCursorPosition(handle, cd);
		std::cout << "上榜等级:";
		switch (pSnake->rank){
		case first:
			std::cout << "初级"; break;
		case middle:
			std::cout << "中级"; break;
		case high:
			std::cout << "高级"; break;
		}
		std::cout << "\t上榜分数:" << pSnake->mark;
	}
}

//存储记录文件(完成未测试)
size_t Snake_class::Create_file(){
	std::ifstream inopen;
	size_t fn = 0, fc = 0, iend = sizeof(Snake_Record) / sizeof(*Snake_Record);
	//判断文件是否存在或文件已被存满
	for (size_t i = 0; i < iend; ++i){
		inopen.open(*(Snake_Record + i));
		if (!inopen) ++fn;
		else if (inopen.get() == EOF){
			inopen.close();
			return i;
		}
		else { ++fc; inopen.close(); }
	}
	if (fn == iend || fc == iend){
		std::ofstream outopen;
		//创建文本
		for (size_t i = 0; i < iend; ++i){
			outopen.open(*(Snake_Record + i));
			outopen.close();
		}
	}
	//返回打开成功的文件索引
	return 0;
}

//判断死亡(未测试)
void Snake_class::JudgeDie(){
	std::vector<COORD>::iterator hbeg = pSnake->Snake_coord.begin(),
		beg = hbeg + 1;
	while (beg != pSnake->Snake_coord.end()){
		if (beg->X == hbeg->X && beg->Y == hbeg->Y){
			pSnake->state = FALSE;
			return;
		}
		++beg;
	}
	COORD cd;
	if (hbeg->X <= 0 || hbeg->Y <= 0 ||
		hbeg->X >= info.srWindow.Right || hbeg->Y >= info.srWindow.Bottom - 5){
		if (pSnake->Snake_size < 40){
			pSnake->state = FALSE;
			return;
		}
		//如果达到了40级可以穿墙
		switch (pSnake->key){
		case VK_UP:
			cd.X = pSnake->Snake_coord.begin()->X;
			cd.Y = info.srWindow.Bottom - 6;
			break;
		case VK_DOWN:
			cd.X = pSnake->Snake_coord.begin()->X;
			cd.Y = 1;
			break;
		case VK_LEFT:
			cd.X = info.srWindow.Right - 1;
			cd.Y = pSnake->Snake_coord.begin()->Y;
			break;
		case VK_RIGHT:
			cd.X = 1;
			cd.Y = pSnake->Snake_coord.begin()->Y;
			break;
		}
		ShowSnake(cd);
	}
}

//上榜判断(未测试)
bool Snake_class::Jesus(){
	std::ifstream inopen;
	size_t i;
	//判断相应等级打开相应文件
	switch (pSnake->rank){
	case first:
		i = 0; break;
	case middle:
		i = 1; break;
	case high:
		i = 2; break;
	}
	inopen.open(*(S_Armory + i));
	if (inopen){
		UINT _mark;
		inopen >> _mark >> _mark;
		if (_mark >= pSnake->mark){
			inopen.close();
			return FALSE;
		}
	}
	std::ofstream outopen(*(S_Armory + i));//创建文件并输入
	if (!outopen){
		std::cerr << "打开英雄榜文件出错" << std::endl;
		exit(0);
	}
	outopen << pSnake->rank << " " << pSnake->mark;
	outopen.close();
	return TRUE;
}
//显示蛇(未测试)
void Snake_class::ShowSnake(COORD& cd){
	if (cd.X == pSnake->food_coord.X && cd.Y == pSnake->food_coord.Y){
		//在最后添加一个坐标
		pSnake->Snake_coord.push_back(*(pSnake->Snake_coord.rbegin()));
		pSnake->food_coord.X = pSnake->food_coord.Y = 0;//标志食物已被吃掉
		++pSnake->mark;
		++pSnake->Snake_size;
	}
	COORD cod;
	cod.X = (pSnake->Snake_coord.rbegin())->X;
	cod.Y = (pSnake->Snake_coord.rbegin())->Y;
	std::vector<COORD>::reverse_iterator rbeg = pSnake->Snake_coord.rbegin();
	while (rbeg != pSnake->Snake_coord.rend() - 1){
		rbeg->X = (rbeg + 1)->X;
		rbeg->Y = (rbeg + 1)->Y;
		++rbeg;
	}
	//显示蛇
	pSnake->Snake_coord.begin()->X = cd.X;
	pSnake->Snake_coord.begin()->Y = cd.Y;
	for (std::vector<COORD>::iterator beg = pSnake->Snake_coord.begin();
		beg != pSnake->Snake_coord.end(); ++beg){
		SetConsoleCursorPosition(handle, *beg);
		std::cout << "*";
	}
	SetConsoleCursorPosition(handle, cod);
	std::cout << " ";
}

//显示指针(完成)
inline void Snake_class::ShowPointer(COORD cd, std::string str){
	SetConsoleCursorPosition(handle, cd);
	std::cout << str[0];
	pSnake->ID_interface != ID_6 ? cd.X += 14 : cd.X = info.srWindow.Right / 3 * 2 + 3;
	SetConsoleCursorPosition(handle, cd);
	std::cout << str[1];
}

//显示无数据(完成)
inline void Snake_class::Show_not(){
	COORD cd = { info.srWindow.Right / 2 - 4, info.srWindow.Bottom / 2 };
	SetConsoleCursorPosition(handle, cd);
	std::cout << "无 数 据 !";
	cd.X -= 6;
	cd.Y += 2;
	SetConsoleCursorPosition(handle, cd);
	std::cout << "请按回车返回主菜单......";
}

main.h

// main.cpp : 定义控制台应用程序的入口点。
//

#include<iostream>
#include<direct.h>
#include"SnakeNew.h"

#define _max(ID) --snake.ID_option < ID_1?ID : snake.ID_option
#define _min(ID) ++snake.ID_option > ID?ID_1 : snake.ID_option

//钩子过程
LRESULT CALLBACK _HookProc(int, WPARAM, LPARAM);

void Wall();       //画墙函数
void Frame();      //画框函数
void initialize_();//初始化蛇结构数据

Snake_data snake;
Snake_class csnake(&snake);
static HANDLE handle;  //控制台句柄
//控制台信息结构
static CONSOLE_SCREEN_BUFFER_INFO info;

int main(){
	using namespace std;
	//获取输出缓冲区句柄
	handle = GetStdHandle(STD_OUTPUT_HANDLE);
	//获取相关信息(主要是缓冲区大小)
	GetConsoleScreenBufferInfo(handle, &info);
	initialize_();
	_mkdir("d://SnakeRecord"); //创建目录

	CONSOLE_CURSOR_INFO cursor;           //光标结构 
	cursor.dwSize = 10;
	cursor.bVisible = 0;                  //0为隐藏光标
	SetConsoleCursorInfo(handle, &cursor);//设置隐藏光标函数 

	//csnake.ShowMenu();
	HHOOK hook;
	MSG msg;
	//相应的钩子有相应的过程函数MSDN中可看,安装键盘钩子 
	hook = SetWindowsHookEx(WH_KEYBOARD_LL, _HookProc, GetModuleHandle(NULL), 0);
	while (1){
		//判断蛇是否还活着
		Wall();
		if (!snake.state && snake.ID_interface == ID_2){
			//csnake.Die();
			snake.ID_interface = ID_4;
			Frame();
		}
		if (snake.ID_interface == ID_2){
			csnake.StartGame();
			Sleep(snake.rank);
		}
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){//循环接收消息
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	UnhookWindowsHookEx(hook);    //卸载钩子 
	return 0;
}

//定义钩子过程(未完成)
LRESULT CALLBACK _HookProc(int message, WPARAM wParam, LPARAM lParam){
	//判断是否弹起按键消息
	if (wParam == WM_KEYUP){
		bool state = true;
		switch (((LPKBDLLHOOKSTRUCT)lParam)->vkCode){//lParam:指向一个结构 
			//按下上键
		case VK_UP:
			switch (snake.ID_interface){
			case ID_1:
				snake.Flag = true;
				csnake.ShowMenu();
				snake.Flag = false;
				snake.ID_option = _max(ID_5);
				csnake.ShowMenu();
				break;
			case ID_2:
				if (snake.key != VK_DOWN && snake.key != VK_UP){
					snake.key = VK_UP;
				}
				break;
			case ID_5:
				snake.Flag = true;
				csnake.ShowRank();
				snake.ID_option = _max(ID_3);
				snake.Flag = false;
				csnake.ShowRank();
				break;
			case ID_6:
				snake.Flag = true;
				csnake.ShowRecord();
				snake.ID_option = _max(ID_6);
				snake.Flag = false;
				csnake.ShowRecord();
				break;
			}
			break;
			//按下下键
		case VK_DOWN:
			switch (snake.ID_interface){
			case ID_1:
				snake.Flag = true;
				csnake.ShowMenu();
				snake.Flag = false;
				snake.ID_option = _min(ID_5);
				csnake.ShowMenu();
				break;
			case ID_2:
				if (snake.key != VK_UP && snake.key != VK_DOWN){
					snake.key = VK_DOWN;
				}
				break;
			case ID_5:
				snake.Flag = true;
				csnake.ShowRank();
				snake.ID_option = _min(ID_3);
				snake.Flag = false;
				csnake.ShowRank();
				break;
			case ID_6:
				snake.Flag = true;
				csnake.ShowRecord();
				snake.ID_option = _min(ID_6);
				snake.Flag = false;
				csnake.ShowRecord();
				break;
			}
			break;
			//按下左键
		case VK_LEFT:
			switch (snake.ID_interface){
			case ID_2:
				if (snake.key != VK_RIGHT && snake.key != VK_LEFT){
					snake.key = VK_LEFT;
				}
				break;
			case ID_3:
				snake.Flag = true;
				csnake.ShowPause();
				snake.ID_option = _max(ID_3);
				snake.Flag = false;
				csnake.ShowPause();
				break;
			case ID_4:
				snake.Flag = true;
				csnake.Die();
				snake.ID_option = _max(ID_2);
				snake.Flag = false;
				csnake.Die();
				break;
			}
			break;
			//按下右键
		case VK_RIGHT:
			switch (snake.ID_interface){
			case ID_2:
				if (snake.key != VK_LEFT && snake.key != VK_RIGHT){
					snake.key = VK_RIGHT;
				}
				break;
			case ID_3:
				snake.Flag = true;
				csnake.ShowPause();
				snake.ID_option = _min(ID_3);
				snake.Flag = false;
				csnake.ShowPause();
				break;
			case ID_4:
				snake.Flag = true;
				csnake.Die();
				snake.ID_option = _min(ID_2);
				snake.Flag = false;
				csnake.Die();
				break;
			}
			break;
			//按下空格和回车键
		case VK_SPACE:
		case VK_RETURN:
			system("cls");
			switch (snake.ID_interface){
			case ID_1://主菜单界面
				switch (snake.ID_option){
				case ID_1:
					snake.ID_interface = ID_2;//开始游戏界面ID
					csnake.StartGame();
					break;
				case ID_2:
					snake.ID_interface = ID_5;//等级界面
					snake.ID_option = ID_1;
					Frame();
					//csnake.ShowRank();
					break;
				case ID_3:
					snake.ID_interface = ID_6;//读取记录界面
					snake.ID_option = ID_1;
					csnake.ShowRecord();
					break;
				case ID_4:
					snake.ID_interface = ID_7;//英雄榜界面
					csnake.ShowArmory();
					break;
				case ID_5:
					state = false;
					break;
				}
				break;

			case ID_2://游戏界面
				snake.ID_interface = ID_3;
				snake.ID_option = ID_1;
				Frame();
				//csnake.ShowPause();
				break;

			case ID_3://暂停界面
				switch (snake.ID_option){
				case ID_1:
					snake.ID_interface = ID_2;
					csnake.StartGame();
					break;
				case ID_2:
					csnake.SaveRecord();
					break;
				case ID_3:
					state = false;
					break;
				}
				break;

			case ID_4://死亡界面
				switch (snake.ID_option){
				case ID_1:
					snake.ID_interface = ID_1;
					snake.ID_option = ID_1;
					initialize_();
					csnake.ShowMenu();
					break;
				case ID_2:
					state = false;
					break;
				}
				break;

			case ID_5://等级界面
				switch (snake.ID_option){
				case ID_1:
					snake.rank = first;
					break;
				case ID_2:
					snake.rank = middle;
					break;
				case ID_3:
					snake.rank = high;
					break;
				}
				snake.ID_interface = ID_1;
				snake.ID_option = ID_1;
				csnake.ShowMenu();
				break;

			case ID_6://读取界面
				size_t id;
				switch (snake.ID_option){
				case ID_1:
					id = 0; break;
				case ID_2:
					id = 1; break;
				case ID_3:
					id = 2; break;
				case ID_4:
					id = 3; break;
				case ID_5:
					id = 4; break;
				}
				//进行读取文件
				if (snake.ID_option != ID_6&&snake.ID_option != ID_7){
					initialize_();
					if (csnake.Read(id)){
						snake.ID_interface = ID_2;
						csnake.StartGame();
					}
					else snake.ID_interface = ID_6;
				}
				else {
					snake.ID_interface = ID_1;
					snake.ID_option = ID_1;
					csnake.ShowMenu();
				}
				break;

			case ID_7://英雄榜界面
				snake.ID_interface = ID_1;
				snake.ID_option = ID_1;
				csnake.ShowMenu();
				break;
			}
			if (!state){
				COORD cd = { info.srWindow.Right / 4, info.srWindow.Bottom / 5 * 4 };
				SetConsoleCursorPosition(handle, cd);
				exit(0);
			}
			snake.ID_option = ID_1;
			break;
		}
	}
	//消息传给下个钩子
	return CallNextHookEx(NULL, message, wParam, lParam);
}

//画墙函数和调用相应界面(已测试完成)
void Wall(){
	short i;
	COORD coord = { 0, 0 };  //坐标结构
	//绘墙
	SetConsoleCursorPosition(handle, coord);
	for (i = 0; i <= info.srWindow.Right; ++i)
		std::cout << "#";

	coord.X = info.srWindow.Right;
	coord.Y = 1;
	for (i = 1; i < info.srWindow.Bottom; ++i){
		SetConsoleCursorPosition(handle, coord);
		std::cout << "#";
		++coord.Y;
	}

	coord.X = 0;
	coord.Y = info.srWindow.Bottom;
	for (i = 1; i <= info.srWindow.Right; ++i){
		SetConsoleCursorPosition(handle, coord);
		std::cout << "#";
		++coord.X;
	}

	coord.X = 0;
	coord.Y = 1;
	for (i = 1; i < info.srWindow.Bottom; ++i){
		SetConsoleCursorPosition(handle, coord);
		std::cout << "#";
		++coord.Y;
	}
	//判断所在界面显示相关界面的菜单选项及其它
	int j = info.srWindow.Right / 4;
	switch (snake.ID_interface){
	case ID_1:
		csnake.ShowMenu();
		coord.X = j;
		coord.Y = info.srWindow.Bottom - 6;
		SetConsoleCursorPosition(handle, coord);
		std::cout << "o(-\"-)o  贪  #^_^#  吃  →_→  蛇  \\(^o^)/";
		break;//显示菜单选项

	case ID_2:
	case ID_3:
	case ID_4:
		//绘输出信息框
		coord.X = 1;
		coord.Y = info.srWindow.Bottom - 5;
		SetConsoleCursorPosition(handle, coord);
		for (i = 1; i < info.srWindow.Right; std::cout << "#", ++i);
		for (coord.X = j; coord.X <= info.srWindow.Right - j; coord.X += j){
			coord.Y = info.srWindow.Bottom - 5;
			for (i = coord.Y; i < info.srWindow.Bottom; ++i){
				SetConsoleCursorPosition(handle, coord);
				std::cout << "#";
				++coord.Y;
			}
		}
		//输出每个框的信息
		coord.X = 2;
		coord.Y -= 4;
		SetConsoleCursorPosition(handle, coord);
		std::cout << "游戏难度:";
		coord.Y += 2;
		SetConsoleCursorPosition(handle, coord);
		std::cout << "\t   ";
		switch (snake.rank){
		case first:
			std::cout << "初 级"; break;
		case middle:
			std::cout << "中 级"; break;
		case high:
			std::cout << "高 级"; break;
		}
		//当前分数
		coord.X += j;
		coord.Y -= 2;
		SetConsoleCursorPosition(handle, coord);
		std::cout << "  当 前 分 数";
		coord.X += j / 2 - 3;
		coord.Y += 2;
		SetConsoleCursorPosition(handle, coord);
		std::cout << snake.mark;
		//操作说明
		coord.X = j * 2 + 1;
		coord.Y = info.srWindow.Bottom - 4;
		SetConsoleCursorPosition(handle, coord);
		std::cout << " 操作说明: ";
		coord.Y += 2;
		SetConsoleCursorPosition(handle, coord);
		std::cout << "  ↑ ↓ ←  →";
		++coord.Y;
		SetConsoleCursorPosition(handle, coord);
		std::cout << " 空格: 暂停游戏";
		//制作人
		coord.X += j;
		SetConsoleCursorPosition(handle, coord);
		std::cout << "\t";
		break;

	case ID_5:
		csnake.ShowRank(); break;//显示游戏等级

	case ID_6:
		csnake.ShowRecord(); break;//显示存档记录

	case ID_7:
		csnake.ShowArmory(); break;//显示英雄榜
	}

}

//画框函数(完成)
void Frame(){
	COORD coord = { 0, info.srWindow.Bottom / 3 };
	SetConsoleCursorPosition(handle, coord);
	for (short i = 0; i <= info.srWindow.Right; std::cout << "-", ++i);

	coord.Y = info.srWindow.Bottom / 3 * 2;
	SetConsoleCursorPosition(handle, coord);
	for (short i = 0; i <= info.srWindow.Right; std::cout << "-", ++i);

	switch (snake.ID_interface){
	case ID_3:
		csnake.ShowPause(); break;//显示暂停菜单
	case ID_4:
		csnake.Die(); break;//显示死亡界面菜单
	case ID_5:
		csnake.ShowRank(); break;//显示等级选项
	}
}

//初始化蛇数据(完成)
void initialize_(){
	snake.state = true; //蛇状态
	snake.Snake_size = 5; //蛇初始化长度
	//初始化蛇位置
	COORD cd;
	cd.Y = 3;
	snake.Snake_coord.clear();
	for (size_t i = 10; i > 5; --i){
		cd.X = i;
		snake.Snake_coord.push_back(cd);
	}
	snake.food_coord.X = 0;
	snake.food_coord.Y = 0;
	snake.rank = first;   //默认等级
	snake.mark = 0; //分数
	snake.key = VK_RIGHT;
	snake.ID_interface = ID_1;//界面标记
	snake.ID_option = ID_1;   //选项标记
	snake.Flag = false;
}

参考:

https://blog.csdn.net/shawn_hou/article/details/27734217

http://www.cnblogs.com/sosoft/p/3508072.html

https://blog.csdn.net/mactavish141/article/details/78878280

猜你喜欢

转载自blog.csdn.net/qq_42189368/article/details/85011906