C语言——贪吃蛇游戏

用c语言在window环境的控制台中模拟写出贪吃蛇小游戏。

一.要实现的基本的功能

如下:

++贪吃蛇的地图绘制++

++贪吃蛇吃食物的功能(方向控制蛇的动作)++

++贪吃蛇撞墙死亡判断++

贪吃蛇撞自己死亡判断

++计算得分++

++贪吃蛇的速度++

++暂停游戏++

二 贪吃蛇的铺垫准备:

1.控制台的窗口大小控制

system("mode con cols=250 lines=80"); cols (列) lines(行);

2.控制台屏幕上的坐标COORD

一般来说,控制台的输出一般是从左上角开始的,但是贪吃蛇的食物应该设置成随机出现,那我们需要先了解一下控制台屏幕上的坐标COORD:

COORD是windowsAPI中的定义的一个结构体,表示一个字符在控制台屏幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。

cpp 复制代码
COORD类型的声明:
typedef struct _COORD{
    SHORT X;
    SHORT Y;
  }COORD,_PCOORD;

给坐标赋值:

cpp 复制代码
COORD pos={x,y};

3.控制台光标隐藏

在一般的情况下,程序结束后,控制台上总有一个光标在闪烁

我们需要把它给隐藏掉。

cpp 复制代码
//光标影藏掉
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标的操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

这段代码的功能是为了把光标给隐藏掉。

4.获取按键GetAsynKeyState

这个函数是获取按键的情况。(用与获取贪吃蛇的方向控制)

GetAsynKeyState的函数原型如下:

cpp 复制代码
SHORT GetAsynKeyState(
        int vKey
  );

将键盘上的每个键的虚拟键值传递给函数,函数通过返回值来分辨按键状态。

GetAsynKeyState的返回值是short类型,在上一次调用GetAsynKeyState函数后,如果返回的16位short数据中,最高位是1,说明按键的状态按下,如果是0,说明按键的状态是抬起;如果最低位置为1则说明该按键被安国,否则为0。

为了代码的简易性,这里把该函数给定义成宏来使用更简便:

define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

这里的意思是说,当键盘是按过状态的时候,返回1,没按下过的状态返回值为0

这里用一个例子来简单说明一下:

如图:当我按下键盘1的时候,第1个if判断返回值为0,所以为假,跳转到第二个if条件判断

第2个if里的判断为真,,所以打印了1.。。。

当我再按下键盘2的时候,第1个和第2个if条件判断都为假,跳转到第三个if条件判断才为真,打印2.。。。

当按下除了0;1;2;3这四个键之外的所有键,均为判断为假,不输出任何信息;

5.设置程序适应本地环境

setlocale(LC_ALL, ""); //点击跳转至setlocale函数讲解。

6.食物刷新的随机坐标

srand((unsigned int)time(NULL)); //点击跳转至srand函数讲解。

三.贪吃蛇游戏的理解

1.贪吃蛇在c语言中可以理解为一条链表,通过不断的移动链表的方向与位置,把某个坐标食物"吃"掉,成为链表的一份子,在链表中的结构如下:

cpp 复制代码
//贪吃蛇结点的描述
typedef struct SnakeNode
{
	//坐标
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

2.游戏中我们还需要时刻通过键盘的按键来控制蛇的走向,所以这个时候,还需要一个变量状态来控制着蛇的方向,所以要枚举一个方向DIRECTION

cpp 复制代码
enum DIRECTION
{
	UP = 1,    //上
	DOWN,      //下
	LEFT,      //左
	RIGHT      //右
};

3.在这个过程中,我们需要绘制一个范围地图,比如:墙,或者障碍体,蛇每一次移动,都需要判断链表的头结点(贪吃蛇的头部)是否越界,或者触碰到障碍体与蛇头触碰到自己的身体而死掉,还要枚举一个GAME_STATUS来记录游戏输掉与结束的原因。

cpp 复制代码
enum GAME_STATUS
{
	OK,//正常运行
	END_NORMAL,//按ESC退出
	KILL_BY_WALL,//触碰到墙体
	KILL_BY_SELF //触碰到身体
};

4.如果想功能丰富些,还可以自定义添加得分机制,和蛇的移动速度自定义调节之类,添加闯关功能,添加障碍体等等。

通过上面分析,我们可以得知贪吃蛇大致的结构:

cpp 复制代码
//贪吃蛇的结构
typedef struct Snake
{
	pSnakeNode _pSnake;//指向贪吃蛇头结点的指针
	pSnakeNode _pFood;//指向食物结点的指针
	int _Score;//贪吃蛇累计的总分
	int _FoodWeight;//一个食物的分数
	int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢
	enum DIRECTION _Dir;//描述蛇的方向
	enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;

四.贪吃蛇的代码:

接下来就是关于游戏的代码写法了。

cpp 复制代码
void test()
{
	int ch = 0;
	do
	{
		Snake snake = { 0 };//创建了贪吃蛇

		//1. 游戏开始 - 初始化游戏
		GameStart(&snake);

		//2. 游戏运行 - 游戏的正常运行过程
		GameRun(&snake);

		//3. 游戏结束 - 游戏善后(释放资源)
		GameEnd(&snake);

		SetPos(20, 18);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		getchar();// 清理掉\n
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);
}

int main()
{
	//设置程序适应本地环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();	
	return 0;
}

先来一个大概的游戏入口test函数

test()函数里包含了贪吃蛇的游戏内容

总体分为三部分:

//1. 游戏开始 - 初始化游戏

GameStart(&snake);

//2. 游戏运行 - 游戏的正常运行过程

GameRun(&snake);

//3. 游戏结束 - 游戏善后(释放资源)

GameEnd(&snake);

这三个函数里面各分别还需要实现一些小功能的函数

这里先实现一下 **GameStart(&snake)**这个函数的各个功能并讲解:

1. GameStart(&snake) :

cpp 复制代码
void GameStart(pSnake ps)
{
	//控制台窗口的设置
	system("mode con cols=250 lines=80");  //控制台的窗口大小
	system("title 梁丶贪吃蛇");             //控制台上面的名字命名

	//光标影藏掉
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	//影藏光标操作
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false; //隐藏控制台光标
	SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

	//打印游戏玩法界面
	WelComeToGame();
	//创建地图
	CreateMap();
	//初始化贪食蛇
	InitSnake(ps);
	//创建食物
	CreateFood(ps);
}

1.1 //打印游戏玩法界面------WelComeToGame();

cpp 复制代码
void WelComeToGame()
{
	//定位光标
	SetPos(115, 35);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(115, 36);
	system("pause");//pause是暂停
	system("cls");
	SetPos(100, 35);
	printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");
	SetPos(115, 36);
	system("pause");
	system("cls");
}

这里的函数功能实现的效果如下:

SetPos()为自己自定义的函数,他的功能是指定输出的位置坐标,

cpp 复制代码
//设置光标的坐标		1
void SetPos(short x, short y)	
{
	COORD pos = { x, y };
	HANDLE hOutput = NULL;
	//获取标准输出的句柄(用来标识不同设备的数值)
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置标准输出上光标的位置为pos
	SetConsoleCursorPosition(hOutput, pos);
}

简单来说就是在指定的位置输出信息。

1.2 //打印墙体------void CreateMap();

如图红色标记的就是所谓的墙体,这里的墙体显示是使用多个 "口" 符号来简单设置的。

要在控制台上打印该"墙体"需要用到之前的setpos()函数,

简单的理解就是使用for循环在每个坐标的位置打印一个"口";

cpp 复制代码
//打印地图
void CreateMap()
{                        
	//上
	SetPos(0, 0);
	int i = 0;
	for (i = 0; i <= 200; i += 2)  //这里的墙体大小随意改,不一定要200,合适就好
	{
		wprintf(L"%lc", WALL); //注:#define WALL L'□'
	}                          
	//下
	SetPos(0, 70);
	for (i = 0; i <= 200; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i < 70; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i < 70; i++)
	{
		SetPos(200, i);
		wprintf(L"%lc", WALL);
	}
}

1.3 //初始化贪食蛇------InitSnake(ps);

设置完地图,现在要打造一下蛇的身体了

cpp 复制代码
//初始化贪吃蛇
void InitSnake(pSnake ps)
{
	pSnakeNode cur = NULL;   //游戏开始的时候,我们默认一下蛇的身体有五个节点
	for (int i = 0; i < 5; i++)    //在此循环创建五个节点,并相连成链表
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc err...");
			return;
		}
		cur->x = POS_X + i * 2;      //pos_x和pos_y为贪吃蛇头的节点位置,
		cur->y = POS_Y;              //在头文件里会define一下
		cur->next = NULL;            

		//头插法
		if (ps->_pSnake == NULL)     //把节点相连
		{
			ps->_pSnake = cur;
		}
		else
		{
			cur->next = ps->_pSnake;
			ps->_pSnake = cur;
		}
	}

	//打印蛇身
	cur = ps->_pSnake;              //这里为打印蛇的身体,就如图中的实心白色原点
	while (cur)                     //遍历链表,把有坐标的节点都在控制台上打印出来
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%1c", BODY);      //注:#define BODY L'●'
		cur = cur->next;
	}

	ps->_Status = OK;        //游戏的状态:正常、退出、撞墙、吃到自己
	ps->_Score = 0;          //贪吃蛇累计的总分
	ps->_pFood = NULL;       //指向食物结点的指针
	ps->_SleepTime = 200;    //每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢
	ps->_FoodWeight = 10;    //一个食物的分数
	ps->_Dir = RIGHT;        //描述蛇的方向
}

这里是把游戏的初期给初始化

如图中的位置,贪吃蛇的位置不一定是固定的,也可自己更改调试。

1.4 //创建食物------CreateFood(ps);

把贪吃蛇初始化后,接下来就要把食物给创建出来了。

cpp 复制代码
 void CreateFood(pSnake ps)
{
	int x = 0;            //创建两个坐标变量x与y;
	int y = 0;
tmp:
	do
	{
		x = rand() % 195 + 4;    //因为我的创建的墙体宽度是200,所以为了保证食物坐标在墙体内
		y = rand() % 66 + 2;     //所以需要%195,y坐标同理(要注意墙体的位置,不要与墙体重叠)
	} while (x % 2 != 0);        //这里的判断结束条件是因为单个墙体"口"的宽度是2,注意~
	//不能与蛇身重复
	pSnakeNode cur = ps->_pSnake;    //创建一个新节点赋予蛇的头结点
	while (cur)                      //要遍历一下该节点的坐标与蛇的身体坐标有没有重叠
	{                                //重叠的话就用goto语句重新生成一个食物节点坐标
		if (cur->x == x && cur->y == y)    
		{
			goto tmp;
		}
		cur = cur->next;
	}
              //当没有重叠的时候,创建一个新节点,赋予x和y的坐标值建立新节点并打印食物标志☆
	pSnakeNode set = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (set == NULL)
	{
		perror("creatrfood():err...");
		return;
	}
	set->x = x;
	set->y = y;
	ps->_pFood = set;
	//打印食物
	SetPos(x, y);
	wprintf(L"%lc", FOOD);        打印食物 "☆"
}

2.GameRun(&snake);

这里就需要运用到GetAsynKeyState(文章铺垫有讲解)

cpp 复制代码
void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelpInfo();        //这里是为了贴心点,打印一下玩法信息讲解。
	do
	{
		SetPos(205, 10);      //这些坐标位置是需要自己去选取合适的位置
		printf("得分:%05d", ps->_Score);
		SetPos(205, 12);
		printf("每个食物的分数:%2d", ps->_FoodWeight);

		if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN)
		{
			ps->_Dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
		{
			ps->_Dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
		{
			ps->_Dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
		{
			ps->_Dir = RIGHT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_Status = END_NORMAL;
			break;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			Pause();
		}
		else if (KEY_PRESS(VK_F3))//加速
		{
			if (ps->_SleepTime >= 80)
			{
				ps->_SleepTime -= 30;
				ps->_FoodWeight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))//减速
		{
			if (ps->_SleepTime < 320)
			{
				ps->_SleepTime += 30;
				ps->_FoodWeight -= 2;
			}
		}

		Sleep(ps->_SleepTime);    //系统库函数sleep(),休眠毫秒数
		SnakeMove(ps);        
	} while (ps->_Status == OK);
      //循环条件为ps->status,当这里的状态为ok的时候,游戏正常继续运行,
      //当为其他状态的时候,说明游戏已经结束(输了),结束循环break。选择继续或下一把!

}

2.1 //打印帮助信息------PrintHelpInfo();

文本打印的位置可以靠setpos(x,y)函数来更改合适的位置。

cpp 复制代码
//打印帮助信息
void PrintHelpInfo()
{
	SetPos(205, 35);
	printf("1.不能撞墙,不能咬到自己");
	SetPos(205, 37);
	printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");
	SetPos(205, 39);
	printf("3.F3加速,F4减速");
	SetPos(205, 41);
	printf("4.ESC-退出, 空格-暂停游戏");

	SetPos(205, 43);
	printf("凉凉~");
}

2.2 //解除暂停状态------void Pause()

cpp 复制代码
void Pause()
{
	while (1)
	{
		Sleep(100);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

这里使用一个死循环,当再次按下空格键的时候,便结束循环。

2.2 //控制蛇的方向并判断食物------void SnakeMove(pSnake ps)

cpp 复制代码
void SnakeMove(pSnake ps)
{
	pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (cur == NULL)
	{
		perror("snakemove( ):err...");
		return;
	}
	cur->next = NULL;    //创建一个新节点,用于存储蛇的下一个方向移动的信息。
	switch (ps->_Dir)    //根据蛇的方向信息判断下一个节点蛇的位置从而更改蛇节点的坐标x和y
	{
	case UP:
		cur->x = ps->_pSnake->x;
		cur->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		cur->x = ps->_pSnake->x;
		cur->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		cur->x = ps->_pSnake->x - 2;
		cur->y = ps->_pSnake->y;
		break;
	case RIGHT:
		cur->x = ps->_pSnake->x + 2;
		cur->y = ps->_pSnake->y;
		break;
	}

	//判断蛇头到达的坐标处是否是食物
	if (NextIsFood(ps, cur))
	{
		//吃掉食物
		EatFood(ps, cur);
		//CreateFood(ps);
	}
	else
	{
		//不吃食物
		NoFood(ps, cur);
	}
	//蛇是否撞墙
	KillByWall(ps);

	//蛇是否自杀
	KillBySelf(ps);
}
2.2.1 //判断蛇头到达的坐标处是否是食物------NextIsFood(ps, cur)
cpp 复制代码
int NextIsFood(pSnake ps, pSnakeNode pnext)
{
	if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

用食物节点与新节点cur比较。

这里分两种情况:

1,当坐标的x与y相等时,这时候就说明贪吃蛇"吃到"食物为真

返回 1 .并执行下一步EatFood( )

吃到食物------EatFood()
cpp 复制代码
//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext)
{
	pnext->next = ps->_pSnake;
	ps->_pSnake = pnext;
	//打印蛇
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%1c", BODY);
		cur = cur->next;
	}
	//free掉食物节点;
	free(ps->_pFood);
	ps->_Score += ps->_FoodWeight;
	//CreateFood(ps);
	CreateFood(ps);
}
  1. 反之没有"吃到"食物为假,并返回 0 不执行EatFood(),代表这个位置没吃到食物,继续往下走。
没有吃到食物------void NoFood ( )
cpp 复制代码
//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext)
{
	pnext->next = ps->_pSnake;
	ps->_pSnake = pnext;
	//打印蛇身
	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%1c", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	//pSnakeNode fp = cur->next;
	//cur->next = NULL;
	free(cur->next);
	cur->next = NULL;
}

判断完是否吃到食物后,就需要判断贪吃蛇本体是否撞墙,或者撞到自己的身体部分了

判断蛇是否撞墙------KillByWall(ps);
cpp 复制代码
//蛇是否撞墙
void KillByWall(pSnake ps)
{
	if (ps->_pSnake->x == 0 ||
		ps->_pSnake->x == 200 ||
		ps->_pSnake->y == 0 ||
		ps->_pSnake->y == 69)
		ps->_Status = KILL_BY_WALL; //当if为真的时候,把status状态更改为kill_by------wall
                                    //打印游戏结束后的判断条件
}
蛇是否自杀------void KillBySelf(pSnake ps)
cpp 复制代码
//蛇是否自杀
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y)
		{
			ps->_Status = KILL_BY_SELF;
		}
		cur = cur->next;    //遍历节点,当与身体相撞时更改status状态;
	}
}

3.GameEnd(pSnake ps)

当编程走到这说明游戏已经结束。

现在要打印一下输赢状态与获取分数等数据信息并释放一下蛇的节点内存

cpp 复制代码
void GameEnd(pSnake ps)
{
	SetPos(20, 12);
	switch (ps->_Status)
	{
	case END_NORMAL:
		printf("您主动退出游戏\n");
		break;
	case KILL_BY_SELF:
		printf("自杀了,游戏结束\n");
		break;
	case KILL_BY_WALL:
		printf("撞墙了,游戏结束\n");
		break;
	}
	//释放蛇身的结点
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
	ps->_pSnake = NULL;

}

贪吃蛇小游戏就结束了!

游戏比较简单,还可以添加许多玩法。

增加障碍物

循环地图

添加生命次数

等。。。。。。。

以下为全代码:

cpp 复制代码
//#pragma once

#include <locale.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>


#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

#define POS_X 24
#define POS_Y 5

#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

enum DIRECTION
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

enum GAME_STATUS
{
	OK,//正常运行
	END_NORMAL,//按ESC退出
	KILL_BY_WALL,
	KILL_BY_SELF
};


//贪吃蛇结点的描述
typedef struct SnakeNode
{
	//坐标
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode, * pSnakeNode;


//
//贪吃蛇的结构
//
typedef struct Snake
{
	pSnakeNode _pSnake;//指向贪吃蛇头结点的指针
	pSnakeNode _pFood;//指向食物结点的指针
	int _Score;//贪吃蛇累计的总分
	int _FoodWeight;//一个食物的分数
	int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢
	enum DIRECTION _Dir;//描述蛇的方向
	enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;


//游戏开始 - 完成游戏的初始化动作		1
void GameStart(pSnake ps);

//定位坐标
void SetPos(short x, short y);

//游戏开始的欢迎界面					1
void WelComeToGame();

//打印地图                               1
void CreateMap();

//初始化贪吃蛇							 1
void InitSnake(pSnake ps);

//创建食物								 1
void CreateFood(pSnake ps);

//游戏的正常运行						1
void GameRun(pSnake ps);

//打印帮助信息							1
void PrintHelpInfo();


//游戏暂定和恢复						1
void Pause();

//蛇的移动								1
void SnakeMove(pSnake ps);

//判断蛇头到达的坐标处是否是食物		1
int NextIsFood(pSnake ps, pSnakeNode pnext);


//吃掉食物							1
void EatFood(pSnake ps, pSnakeNode pnext);

//不吃食物						1
void NoFood(pSnake ps, pSnakeNode pnext);

//蛇是否撞墙
void KillByWall(pSnake ps);

//蛇是否自杀
void KillBySelf(pSnake ps);

//游戏结束后的善后处理
void GameEnd(pSnake ps);

#define _CRT_SECURE_NO_WARNINGS 1

#include "sneke.h"

//设置光标的坐标		1
void SetPos(short x, short y)
{
	COORD pos = { x, y };
	HANDLE hOutput = NULL;
	//获取标准输出的句柄(用来标识不同设备的数值)
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置标准输出上光标的位置为pos
	SetConsoleCursorPosition(hOutput, pos);
}

void WelComeToGame()
{
	//定位光标
	SetPos(115, 35);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(115, 36);
	system("pause");//pause是暂停
	system("cls");
	SetPos(100, 35);
	printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");
	SetPos(115, 36);
	system("pause");
	system("cls");
}


//打印地图
void CreateMap()
{
	//上
	SetPos(0, 0);
	int i = 0;
	for (i = 0; i <= 200; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 70);
	for (i = 0; i <= 200; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i < 70; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i < 70; i++)
	{
		SetPos(200, i);
		wprintf(L"%lc", WALL);
	}
}


//初始化贪吃蛇
void InitSnake(pSnake ps)
{
	//pSnake cur = (pSnake)malloc(sizeof(Snake));
	pSnakeNode cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc err...");
			return;
		}
		cur->x = POS_X + i * 2;
		cur->y = POS_Y;
		cur->next = NULL;

		//头插法
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = cur;
		}
		else
		{
			cur->next = ps->_pSnake;
			ps->_pSnake = cur;
		}
	}

	//打印蛇身
	cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%1c", BODY);
		cur = cur->next;
	}
	ps->_Status = OK;
	ps->_Score = 0;
	ps->_pFood = NULL;
	ps->_SleepTime = 200;
	ps->_FoodWeight = 10;
	ps->_Dir = RIGHT;
}

void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
tmp:
	do
	{
		x = rand() % 195 + 4;
		y = rand() % 66 + 2;
	} while (x % 2 != 0);
	//不能与蛇身重复
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto tmp;
		}
		cur = cur->next;
	}

	pSnakeNode set = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (set == NULL)
	{
		perror("creatrfood():err...");
		return;
	}
	set->x = x;
	set->y = y;
	ps->_pFood = set;
	//打印食物
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
}

void GameStart(pSnake ps)
{
	//控制台窗口的设置
	system("mode con cols=250 lines=80");
	system("title 梁丶贪吃蛇");

	//光标影藏掉
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	//影藏光标操作
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false; //隐藏控制台光标
	SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

	//打印欢迎界面
	WelComeToGame();
	//创建地图
	CreateMap();
	//初始化贪食蛇
	InitSnake(ps);
	//创建食物
	CreateFood(ps);
}



void Pause()
{
	while (1)
	{
		Sleep(100);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

int NextIsFood(pSnake ps, pSnakeNode pnext)
{
	if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}


//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext)
{
	pnext->next = ps->_pSnake;
	ps->_pSnake = pnext;
	//打印蛇
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%1c", BODY);
		cur = cur->next;
	}
	//free掉食物节点;
	free(ps->_pFood);
	ps->_Score += ps->_FoodWeight;
	//CreateFood(ps);
	CreateFood(ps);
}


//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext)
{
	pnext->next = ps->_pSnake;
	ps->_pSnake = pnext;
	//打印蛇身
	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%1c", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	//pSnakeNode fp = cur->next;
	//cur->next = NULL;
	free(cur->next);
	cur->next = NULL;
}


//蛇是否撞墙
void KillByWall(pSnake ps)
{
	if (ps->_pSnake->x == 0 ||
		ps->_pSnake->x == 200 ||
		ps->_pSnake->y == 0 ||
		ps->_pSnake->y == 69)
		ps->_Status = KILL_BY_WALL;
}

//蛇是否自杀
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y)
		{
			ps->_Status = KILL_BY_SELF;
		}
		cur = cur->next;
	}
}



void SnakeMove(pSnake ps)
{
	pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (cur == NULL)
	{
		perror("snakemove( ):err...");
		return;
	}
	cur->next = NULL;
	switch (ps->_Dir)
	{
	case UP:
		cur->x = ps->_pSnake->x;
		cur->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		cur->x = ps->_pSnake->x;
		cur->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		cur->x = ps->_pSnake->x - 2;
		cur->y = ps->_pSnake->y;
		break;
	case RIGHT:
		cur->x = ps->_pSnake->x + 2;
		cur->y = ps->_pSnake->y;
		break;
	}

	//判断蛇头到达的坐标处是否是食物
	if (NextIsFood(ps, cur))
	{
		//吃掉食物
		EatFood(ps, cur);
		//CreateFood(ps);
	}
	else
	{
		//不吃食物
		NoFood(ps, cur);
	}
	//蛇是否撞墙
	KillByWall(ps);

	//蛇是否自杀
	KillBySelf(ps);
}


void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelpInfo();
	do
	{
		SetPos(205, 10);
		printf("得分:%05d", ps->_Score);
		SetPos(205, 12);
		printf("每个食物的分数:%2d", ps->_FoodWeight);

		if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN)
		{
			ps->_Dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
		{
			ps->_Dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
		{
			ps->_Dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
		{
			ps->_Dir = RIGHT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_Status = END_NORMAL;
			break;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			Pause();
		}
		else if (KEY_PRESS(VK_F3))//加速
		{
			if (ps->_SleepTime >= 80)
			{
				ps->_SleepTime -= 30;
				ps->_FoodWeight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))//减速
		{
			if (ps->_SleepTime < 320)
			{
				ps->_SleepTime += 30;
				ps->_FoodWeight -= 2;
			}
		}

		Sleep(ps->_SleepTime);
		SnakeMove(ps);
	} while (ps->_Status == OK);

}
//打印帮助信息
void PrintHelpInfo()
{
	SetPos(205, 35);
	printf("1.不能撞墙,不能咬到自己");
	SetPos(205, 37);
	printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");
	SetPos(205, 39);
	printf("3.F3加速,F4减速");
	SetPos(205, 41);
	printf("4.ESC-退出, 空格-暂停游戏");

	SetPos(205, 43);
	printf("凉凉~");
}
void GameEnd(pSnake ps)
{
	SetPos(20, 12);
	switch (ps->_Status)
	{
	case END_NORMAL:
		printf("您主动退出游戏\n");
		break;
	case KILL_BY_SELF:
		printf("自杀了,游戏结束\n");
		break;
	case KILL_BY_WALL:
		printf("撞墙了,游戏结束\n");
		break;
	}
	//释放蛇身的结点
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
	ps->_pSnake = NULL;

}

#define _CRT_SECURE_NO_WARNINGS 1

#include "sneke.h"


void test()
{
	int ch = 0;
	do
	{
		Snake snake = { 0 };//创建了贪吃蛇

		//1. 游戏开始 - 初始化游戏
		GameStart(&snake);

		//2. 游戏运行 - 游戏的正常运行过程
		GameRun(&snake);

		//3. 游戏结束 - 游戏善后(释放资源)
		GameEnd(&snake);

		SetPos(20, 18);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		getchar();// 清理掉\n
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);
}

int main()
{
	//设置程序适应本地环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();
	return 0;
}
相关推荐
资源分享助手13 小时前
我!勇者?The Warrior免安装中文版下载与玩法体验
游戏
云起SAAS15 小时前
抖音小游戏源码 - 消消乐 | 含激励广告+成就系统 | 开箱即用商业级消除游戏模板
android·游戏·广告联盟·看激励广告联盟流量主·抖音小游戏源码 - 消消乐
津津有味道16 小时前
一键写入启动游戏NDEF复合记录NFC标签vb6源码
游戏·标签·nfc·ndef·复合记录
游乐码16 小时前
Unity基础(四)向量相关
游戏·unity·游戏引擎
阿阳微客18 小时前
网易Buff游戏搬砖,长期可做!
笔记·学习·游戏
Kurisu57518 小时前
探灵直播2026最新官方正版免费下载 一键转存 永久更新 (看到速转存 资源随时走丢)
游戏·游戏引擎·游戏程序·动画·关卡设计
STDD19 小时前
Abiotic Factor多人生存建筑游戏《非生物因素》 专用服务器搭建教程
服务器·数据库·游戏
开开心心就好20 小时前
带OCR识别的电子发票打印工具
运维·javascript·科技·游戏·青少年编程·ocr·powerpoint
经济元宇宙1 天前
HOPE星火燎原不是希望工程,也不是游戏项目:项目名称与定位澄清
游戏
2601_950316061 天前
XBOX360 KINECT体感游戏合集109个
游戏