贪吃蛇实现(c实现)

前言:

贪吃蛇是一个久负盛名的游戏,与俄罗斯方块和扫雷游戏相同都是非常经典的游戏

一.游戏相关知识

1.COORD

(1)说明:

控制台屏幕上的坐标,有两个参数x,y,分别对应着横纵坐标,(0,0)对应控制台屏幕左上角顶格位置

(2)声明:

typedef struct _COORD {
  SHORT X;
  SHORT Y;
} COORD, *PCOORD;

2.GetStdHandle

(1)说明:

它用于从一个特定的标准设备中获得一个句柄,用来操作该设备(具体咋理解呢?就相当于你炒菜的肯定是要用锅的嘛,那既然要用锅,那就一定要用把手呗,通过该把手来操控这口锅,然后进行炒菜。这里的把手就相当与这里的句柄,必须你获得了该句柄才能对操作台屏幕上的某些信息进行操作)

(2)声明:

HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

(3)参数:

STD_INPUT_HANDLE((DWORD)-10)标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台。
STD_OUTPUT_HANDLE((DWORD)-11)标准输出设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$
STD_ERROR_HANDLE((DWORD)-12)标准错误设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$

由于我们是要向屏幕输出信息,所以这里我们对该函数的传参选择第二个参数 !!

3.CONSOLE_CURSOR_INFO

(1)说明:

是一个结构体,用来存储控制台屏幕上的光标的占比大小和可见性

(2)声明:

typedef struct _CONSOLE_CURSOR_INFO {
  DWORD dwSize;
  BOOL  bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

4. GetConsoleCursorInfo

(1)说明:

用来获取有关控制台屏幕上的光标的占比和可见性的相关信息

(2)声明:

BOOL WINAPI GetConsoleCursorInfo(
  _In_  HANDLE               hConsoleOutput,
  _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

 (3)参数:

第一个就是刚才的句柄 ,第二个是光标

5.SetConsoleCursorInfo

(1)说明:

对修改过后的光标信息进行设置

(2)声明:

BOOL WINAPI SetConsoleCursorInfo(
  _In_       HANDLE              hConsoleOutput,
  _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

6.SetConsoleCursorPosition 

(1)说明:

设置光标的位置(初始打印位置)

(2)声明:

BOOL WINAPI SetConsoleCursorPosition(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwCursorPosition
);

7. GetAsyncKeyState

(1)说明:

获取键盘上按键的状态,调用函数时键是向上还是向下,以及上次调用 GetAsyncKeyState 后是否按下了该键。

(2)声明:

SHORT GetAsyncKeyState(
  [in] int vKey
);

 (3)参数:

这里的参数是虚拟键代码,传参可以传常数(VK_ESCAPE),也可以传value(0x1B)。在该游戏会用到的虚拟键代码有:

VK_ESCAPE0x1BESC 键
VK_SPACE0x20空格键
VK_LEFT0x25LEFT ARROW 键
VK_UP0x26UP ARROW 键
VK_RIGHT0x27RIGHT ARROW 键
VK_DOWN0x28DOWN ARROW 键
VK_F10x70F1 键
VK_F20x71F2 键

(4)使用方式:

检测按键状态,是看它返回值的最低位,最低位为1表示按下,为0表示没按下

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

 

8.setlocale

(1)说明:头文件为:<locale.h>

于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项
(2)声明:

char* setlocale (int category, const char* locale);

(3)参数:

第一个参数:该参数为类项,如果为LC_ALL,就会影响所有类项,如果只想影响某一个的话就需要单独设置(所有类项说明

第二个参数:

只有两种取值:""(本地模式),"C"(正常模式),在正常模式下,一个小数点就是一个点,但我们的Chinese的模式下一个点是占两个字符位置,从而在本地模式下进行宽字符打印。

(4)宽字符打印:
用wprintf进行打印,在打印是必须加上L,用%lc进行宽字符打印。

wprintf(L"%lc", WALL);

二.游戏设计与分析:

2.1 游戏主逻辑

在程序开始时就需要设置为本地模式,然后进入游戏

该游戏分为三个阶段:游戏开始(GameStart),游戏运行(GameRun),游戏结束(GameEnd)

2.2 游戏开始

主要进行一些页面的打印以及蛇身和食物的设计

void GameStart(pSnake ps)
{
	//1.先设置窗口的 标题 大小
	system("title 贪吃蛇");

	system("mode con cols=100 lines=30");
	
    //2.设置光标的隐藏
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(houtput, &cursor_info);
	cursor_info.bVisible = false;
	SetConsoleCursorInfo(houtput, &cursor_info);
	
	//3.打印欢印界面和游戏规则
	welcomGame();
	
    //4.绘制地图(障碍物)
	createMap();
	
    //5.创建蛇 ==> 初始化蛇身
	SnakeInit(ps);
	
	//6.创建食物
	createFood(ps);

}

2.2.1 欢迎界面打印

(1)展示

(2)实现:

首先我们在启动游戏的时候会打印一个游戏初始界面。上面有着欢迎来到贪吃蛇游戏 。

关于其如何实现:

1.这个启动界面的名称我们需要进行修改成你想要的名字--贪吃蛇。这时需要用到cmd命令--title来实现,在c语言中用system来实现。

system("title 贪吃蛇");

2.启动界面的大小也需要调整,用到的也是cmd命令--mode

system(mode con cols=100 lines=30);

3.在控制台屏幕上的默认打印位置在最开始,然而这里我们想将打印初始位置设置在屏幕中央,此时就需要对光标位置(打印初始位置)为进行设置,这里用到win32API函数中的SetConsoleCursorPosition ,这里我们可以将设置坐标给封装成一个函数,在我们下次使用时就可以直接传参就好了!!

void SetPos(short x, short y)
{
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	COORD pos = { x ,y };
	SetConsoleCursorPosition(houtput, pos);
}

当标题、屏幕大小和光标位置都设置完成我们就可以进行欢迎页面的打印了!!

4.清除当面页面的信息以便打印游戏规则的界面 ==> 用到cmd命令 cls

system("cls");

 2.2.2 游戏规则界面:

实现原理跟上面欢迎界面类似,不再赘述

两个页面打印的代码实现:
 

//打印欢印界面和游戏规则
void welcomGame()
{
	//界面1
	//设置光标
	SetPos(38, 12);
	printf("欢印来到贪吃蛇小游戏");
	SetPos(38, 20);
	system("pause");
	//清空
	system("cls");

	//界面2
	SetPos(32, 12);
	printf("用↑.↓.→.← 来表示蛇移动的方向\n");
	SetPos(32, 13);
	printf("F1为加速,F2为减速,加速吃到食物可以得到更高的分\n");
	SetPos(32, 20);
	system("pause");
	system("cls");


}

 2.2.3 地图打印

由如下图我们可以看见一些小方块,这些就是我们自定义的地图,贪吃蛇只能在这里面行动。

(1)说明:

我们所要实现的地图大小为27行(y),58列(x),需要进行上下左右的小方块的打印:

 上:0~58打印小方块,由于是宽字符打印,故只需要打印29个小方块

下:同上一样

左:1~25打印小方块,共打印25个

右:同左一样

(2)代码实现: 

//这里使用define定义
#define WALL L'□' 


//小方块打印方式
wprintf(L"%lc", WALL);

void createMap()
{
	int i = 0;
	//上
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i < 26; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i < 26; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	
}

2.2.4 初始化蛇身

我们想要实现一条长度为5的蛇,这里我们可以考虑用链表,想要一个长度为5的蛇,那不就是一个链表中有5个被串起来的节点,我们将每一个节点打印在屏幕上,然后就成了一条蛇。

创建一条蛇:

1.因为是要打印在屏幕上,所有我们需要知道坐标,所有蛇身的每一个节点(结构体)中存储的有x(横坐标),y(纵坐标),next(指向下一个蛇身的节点)

//定义一个节点,将很多个节点给串起来就成了一条蛇
typedef struct SnakeNode
{
	//坐标
	int x;
	int y;                                          
	//找到贪吃蛇的下一个节点
	struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

2.关于蛇,它也是需要进行维护的,所以我们也需创建一个蛇(结构体),里面存储有:指向蛇的头节点的指针,指向食物节点的指针,每一个食物的分数,总分数,蛇的运行速度,蛇运动的方向以及蛇的状态

蛇运动的方向和状态都是一个枚举类型。

关于蛇运行的速度,这里采用休眠时间快满来设定蛇的运行速度,休眠时间越短蛇运动越快,越长运动越慢。

//蛇行动的方向
typedef enum SnakeDir
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
}SnakeDir;

//蛇运动的状态
typedef enum SnakeState
{
	OK,           //正常
	KILL_BY_WALL, //撞到墙
	KILL_BY_SELF, //撞到自己
	NORMOL_END    //正常结束游戏        
}SnakeState;

//定义一条蛇,以便维护蛇的一些相关信息
typedef struct Snake 
{
	pSnakeNode phSnake; //指向蛇的头部的指针
	pSnakeNode pFood;   //指向食物节点的指针
	SnakeDir Dir;       //蛇运动的方向
	SnakeState Ste;     //蛇运动的状态,有快有慢
	int sleepTime;      //判断蛇运动快慢的标志,就是用休眠时间来让它进行快或者慢的运动
	int foodScore;      //食物的分数
	int allScore;       //总分
}Snake, * pSnake;

初始化蛇:

蛇的初始位置是从(24,3)开始到(32,3)结束,(32,3)是蛇的头节点

蛇的移动速度是200ms

游戏状态是OK

总分为0

每个食物的分数为10

蛇的初始运动方向为RIGHT

打印蛇:

遍历蛇的每一个节点,然后进行宽字符打印

代码实现:

#define BODY L'●'
void SnakeInit(pSnake ps)
{
	//创建5个节点,并将上述5个节点组成一条蛇
	pSnakeNode cur = NULL;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("SnakeInit :: malloc\n");
			return;
		}
		//设置坐标
		cur->x = X + i * 2;
		cur->y = Y;
		cur->next = NULL;
		if (ps->phSnake == NULL)
		{
			ps->phSnake = cur;
		}
		else
		{
			//头插法
			cur->next = ps->phSnake;
			ps->phSnake = cur;
		}
	}
	

	//遍历这5个节点,打印蛇的身体
	cur = ps->phSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//初始化蛇里面的各种数据
	ps->allScore = 0;
	ps->foodScore = 10;
	ps->sleepTime = 200;
	ps->Ste = OK;
	ps->Dir = RIGHT;

}

效果演示:

2.2.5 创建一个食物

食物的创建:

1. 随机生成坐标(不能超出地图范围)

2.不能生成在墙上

3.不能生成在蛇的身上

4.食物的长度也必须为2个字符大小

代码实现:

void createFood(pSnake ps)
{
	//注意食物必须也占两行,并且不能够创建到墙中或者蛇自己身上
	pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (food == NULL)
	{
		perror("createFood::malloc");
		return;
	}
	
	int x = 0;
	int y = 0;
a:
	do
	{
		x = rand() % 53 + 2; // x:2~54
		y = rand() % 25 + 1; // y:1~25
		
	} while (x % 2 != 0);

	pSnakeNode cur = ps->phSnake;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto a;
		}
		cur = cur->next;
	}
	
	food->x = x;
	food->y = y;
	food->next = NULL;
	ps->pFood = food;
    //SetPos是自己封装的一个设置光标的函数
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	
}

2.3 游戏运行(GameRun)

我们在游戏运行界面右边有一个帮助的显示信息,来提示玩家。

根据游戏状态来进行游戏,如果游戏状态为OK就继续游戏,否则游戏结束。

如果游戏继续就检测运动方向和运动速度,就是检测按键状态(当前方向必须与按键方向相同,否则就直接会碰到自己,会退出游戏)

在GameRun中我们运用一个do while循环,游戏至少先执行一次,然后进行循环判断状态是否为OK,如果不为OK就会跳出循环,从而结束游戏,进行游戏的善后工作。

void GameRun(pSnake ps)
{
	helpPrint();
	do
	{
		SetPos(68, 8);
		printf("得分:%d\n", ps->allScore);
		SetPos(68, 9);
		printf("每个食物的分数:%2d\n", ps->foodScore);
		//现在要让贪吃蛇动起来,此时需要确定它的方向(确定按键状态)
		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_SPACE))
		{
			pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->Ste = NORMOL_END;

		}
		else if (KEY_PRESS(VK_F1))
		{
			//加速,最多加速四次
			if (ps->sleepTime > 80)
			{
				ps->sleepTime -= 30;
				ps->foodScore += 2;
			}

		}
		else if (KEY_PRESS(VK_F2))
		{
			//减速,也最多减速四次
			if (ps->foodScore > 2)
			{
				ps->sleepTime += 30;
				ps->foodScore -= 2;
			}
		}
		//休眠,然后下一次准备再次执行该程序,只到不满足的条件出现就跳出该循环
		Sleep(ps->sleepTime);
		
		//贪吃蛇走动一步
		snakeMove(ps);

	} while (ps->Ste == OK);
	
}

1.helpPrint

//帮助打印显示信息
void helpPrint()
{
	
	SetPos(68, 12);
	printf("不能穿墙,不能碰到自己\n");
	SetPos(68, 13);
	printf("用↑.↓.→.← 来表示蛇移动的方向\n");
	SetPos(68, 14);
	printf("F1表示加速,F2表示减速\n");
	SetPos(68, 15);
	printf("ESC:退出游戏  SPACE:暂停\n");
	SetPos(68, 20);
	printf("比特就业课授权\n");
}

2.KEY_PRESS

我们可以简单的的定义一个宏来检测某个按键是否按下

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

3.pause

当我们按下空格的时候需要进行休眠 ==> 暂停,再次按下空格,跳出循环继续行动!!

void pause()
{
	while (1)
	{
		Sleep(500);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

4.snakeMove

每一次在进行循环的时候都要让蛇往指定方向走一步,并且需要判断下一个节点是不是食物,是不是墙壁,是不是蛇自己尾部的节点。如果为食物就应该吃掉食物,如果不是正常往指定方向行走一步;如果是墙壁,那么蛇被杀死,游戏结束;如果是蛇自己的节点,那么蛇也会被杀死,游戏结束;

代码实现:

void snakeMove(pSnake ps)
{
	//有四种走动方式:上  下  左  右
	//创建下一个节点 
	pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));    
	if (pnext == NULL)
	{
		perror("snakeMove :: malloc");
		return;
	}

	if (ps->Dir == UP)
	{
		pnext->x = ps->phSnake->x;
		pnext->y = ps->phSnake->y - 1;
	}
	else if (ps->Dir == DOWN)
	{
		pnext->x = ps->phSnake->x;
		pnext->y = ps->phSnake->y + 1;
	}
	else if (ps->Dir == RIGHT)
	{
		pnext->x = ps->phSnake->x + 2;
		pnext->y = ps->phSnake->y;
	}
	else if (ps->Dir == LEFT)
	{
		pnext->x = ps->phSnake->x - 2;
		pnext->y = ps->phSnake->y;
	}
	if (isNextFood(pnext, ps))
	{
		eatFood(pnext, ps);
	}
	else
	{
		noFood(pnext, ps);
	}

	if (KillByWall(ps))
	{
		ps->Ste = KILL_BY_WALL;
	}
	
	if (KillBySelf(ps))
	{
		ps->Ste = KILL_BY_SELF;
	}

}
(1)isNextFood
int isNextFood(pSnakeNode pn,pSnake ps)
{
	return (pn->x == ps->pFood->x) && (pn->y == ps->pFood->y);
}
(2)eatFood
void eatFood(pSnakeNode pn, pSnake ps)
{
	pn->next = ps->phSnake;
	ps->phSnake = pn;
	ps->allScore += ps->foodScore;
	pSnakeNode cur = ps->phSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	free(ps->pFood);
	ps->pFood = NULL;
	createFood(ps);
}
(3)noFood
void noFood(pSnakeNode pn, pSnake ps)
{
	pn->next = ps->phSnake;
	ps->phSnake = pn;

	
	pSnakeNode cur = ps->phSnake;
	
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;
}
(4)KillByWall
int KillByWall(pSnake ps)
{
	if ((ps->phSnake->x == 0) || (ps->phSnake->x == 56) || 
		(ps->phSnake->y == 0) || (ps->phSnake->y == 26))
	{
		return 1;
	}
	return 0;
}
(5)KillBySelf
int KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->phSnake->next;
	while (cur)
	{
		
		if ((ps->phSnake->x == cur->x) && (ps->phSnake->y == cur->y))
		{
			return 1;
		}
		cur = cur->next;
	}
	return 0;
}

2.4 游戏结束(GameEnd)

在游戏结束后,我们需要对整个游戏过程中所创建的所有节点进行释放。还可以加上一个功能,在游戏结束以后,输入一个y/Y(yes)来继续进行游戏,如果输入n/N(no),不再进行游戏,此时就退出游戏。

void GameEnd(pSnake ps)
{

	pSnakeNode cur = ps->phSnake;
	while (cur)
	{
		pSnakeNode pn = cur->next;
		free(cur);
		cur = pn;
	}
	
}

2.5 主函数

#include"Snake.h"
void test()
{
	srand((unsigned int)time(NULL));
	int ch = 0;
	do
	{
		Snake snake = { 0 };
		//初始化游戏
		GameStart(&snake);

		//进行游戏
		GameRun(&snake);

		结束游戏
		GameEnd(&snake);
		SetPos(14, 12);
		printf("游戏结束!还要再来一把吗(Y/N):");
		ch = getchar();
        //判断是否存在读YYYYYY/YYYYyyy的情况
		while(getchar() != '\n');
	} while (ch == 'y' || ch == 'Y');
	
}

int main()
{
	
	//设置适配环境,只有在本地模式(Chinese)下才支持宽字符的打印
	setlocale(LC_ALL, "");
	
	test();
	return 0;
}

三. 贪吃蛇实现代码

1.Snake.h:

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <stdbool.h>
#include <locale.h>
#include <time.h>
//定义一个节点,将很多个节点给串起来就成了一条蛇
typedef struct SnakeNode
{
	//坐标
	int x;
	int y;                                          
	//找到贪吃蛇的下一个节点
	struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

//typedef struct SnakeNode* pSnakeNode

//蛇行动的方向
typedef enum SnakeDir
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
}SnakeDir;

//蛇运动的状态
typedef enum SnakeState
{
	OK,           //正常
	KILL_BY_WALL, //撞到墙
	KILL_BY_SELF, //撞到自己
	NORMOL_END            
}SnakeState;

//定义一条蛇,以便维护蛇的一些相关信息
typedef struct Snake 
{
	pSnakeNode phSnake; //指向蛇的头部的指针
	pSnakeNode pFood;   //指向食物节点的指针
	pSnakeNode phFood;  //现有很多食物,指向很多食物的头结点
	SnakeDir Dir;       //蛇运动的方向
	SnakeState Ste;     //蛇运动的状态,有快有慢
	int sleepTime;      //判断蛇运动快慢的标志,就是用休眠时间来让它进行快或者慢的运动
	int foodScore;      //食物的分数
	int allScore;       //总分
}Snake, * pSnake;



//初始化游戏
void GameStart(pSnake ps);

//进行游戏
void GameRun(pSnake ps);

//结束游戏
void GameEnd(pSnake ps);

//定位光标设置
void SetPos(short x, short y);

//欢迎界面的打印
void welcomGame();

//地图的创建
void createMap();

//蛇的初始化
void SnakeInit(pSnake ps);

//创建一个食物
void createFood(pSnake ps);

//贪吃蛇走动
void snakeMove(pSnake ps);

//判断下一个节点是否为食物
int isNextFood(pSnakeNode pn, pSnake ps);

//下一个节点为食物,吃掉食物
void eatFood(pSnakeNode pn, pSnake ps);

//下一个节点不为食物
void noFood(pSnakeNode pn, pSnake ps);

//被墙撞shu
int KillByWall(pSnake ps);

//被自己杀死
int KillBySelf(pSnake ps);

//创建多个食物
void createMoreFood(ps);

//是否为多个食物中的一个
int isMoreNextFood(pSnakeNode pn, pSnake ps);

2.Snack.c:

#define _CRT_SECURE_NO_WARNINGS 1
#include "Snake.h"

#define BODY L'●'
#define FOOD L'★'
#define WALL L'□'
#define X 24
#define Y 3
#define FOODSIZE 3
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) & 1) ? 1 : 0

void SetPos(short x, short y)
{
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	COORD pos = { x ,y };
	SetConsoleCursorPosition(houtput, pos);
}

//打印欢印界面和游戏规则
void welcomGame()
{
	//界面1
	//设置光标
	SetPos(38, 12);
	printf("欢印来到贪吃蛇小游戏");
	SetPos(38, 20);
	system("pause");
	//清空
	system("cls");

	//界面2
	SetPos(32, 12);
	printf("用↑.↓.→.← 来表示蛇移动的方向\n");
	SetPos(32, 13);
	printf("F1为加速,F2为减速,加速吃到食物可以得到更高的分\n");
	SetPos(32, 20);
	system("pause");
	system("cls");


}

void createMap()
{
	int i = 0;
	//上
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i < 26; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i < 26; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	
}

void SnakeInit(pSnake ps)
{
	//创建5个节点,并将上述5个节点组成一条蛇
	pSnakeNode cur = NULL;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("SnakeInit :: malloc\n");
			return;
		}
		//设置坐标
		cur->x = X + i * 2;
		cur->y = Y;
		cur->next = NULL;
		if (ps->phSnake == NULL)
		{
			ps->phSnake = cur;
		}
		else
		{
			//头插法
			cur->next = ps->phSnake;
			ps->phSnake = cur;
		}
	}
	

	//遍历这5个节点,打印蛇的身体
	cur = ps->phSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//初始化蛇里面的各种数据
	ps->allScore = 0;
	ps->foodScore = 10;
	ps->sleepTime = 200;
	ps->Ste = OK;
	ps->Dir = RIGHT;

}

void createFood(pSnake ps)
{
	//注意食物必须也占两行,并且不能够创建到墙中或者蛇自己身上
	pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (food == NULL)
	{
		perror("createFood::malloc");
		return;
	}
	
	int x = 0;
	int y = 0;
a:
	do
	{
		x = rand() % 53 + 2; // x:2~54
		y = rand() % 25 + 1; // y:1~25
		
	} while (x % 2 != 0);

	pSnakeNode cur = ps->phSnake;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto a;
		}
		cur = cur->next;
	}
	
	food->x = x;
	food->y = y;
	food->next = NULL;
	ps->pFood = food;
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	
}


void GameStart(pSnake ps)
{
	//1.先设置窗口的 标题 大小
	system("title 贪吃蛇");

	system("mode con cols=100 lines=30");
	//2.设置光标的隐藏
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(houtput, &cursor_info);
	cursor_info.bVisible = false;
	SetConsoleCursorInfo(houtput, &cursor_info);
	
	//3.打印欢印界面和游戏规则
	welcomGame();
	//4.绘制地图(障碍物)
	createMap();
	//6.创建蛇 ==> 初始化蛇身
	SnakeInit(ps);
	
	//5.创建食物
	createFood(ps);

	//5.创建多个食物
	//createMoreFood(ps);
}

//帮组打印额外的信息
void helpPrint()
{
	
	SetPos(68, 12);
	printf("不能穿墙,不能碰到自己\n");
	SetPos(68, 13);
	printf("用↑.↓.→.← 来表示蛇移动的方向\n");
	SetPos(68, 14);
	printf("F1表示加速,F2表示减速\n");
	SetPos(68, 15);
	printf("ESC:退出游戏  SPACE:暂停\n");
	SetPos(68, 20);
	printf("比特就业课授权\n");
}


void pause()
{
	while (1)
	{
		Sleep(500);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

int isNextFood(pSnakeNode pn,pSnake ps)
{
	return (pn->x == ps->pFood->x) && (pn->y == ps->pFood->y);
}

void eatFood(pSnakeNode pn, pSnake ps)
{
	pn->next = ps->phSnake;
	ps->phSnake = pn;
	ps->allScore += ps->foodScore;
	pSnakeNode cur = ps->phSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	free(ps->pFood);
	ps->pFood = NULL;
	createFood(ps);
}

void noFood(pSnakeNode pn, pSnake ps)
{
	pn->next = ps->phSnake;
	ps->phSnake = pn;

	
	pSnakeNode cur = ps->phSnake;
	
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;
}

int KillByWall(pSnake ps)
{
	if ((ps->phSnake->x == 0) || (ps->phSnake->x == 56) || 
		(ps->phSnake->y == 0) || (ps->phSnake->y == 26))
	{
		return 1;
	}
	return 0;
}

int KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->phSnake->next;
	while (cur)
	{
		
		if ((ps->phSnake->x == cur->x) && (ps->phSnake->y == cur->y))
		{
			return 1;
		}
		cur = cur->next;
	}
	return 0;
}


void snakeMove(pSnake ps)
{
	//有四种走动方式:上  下  左  右
	//创建下一个节点 
	pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));    
	if (pnext == NULL)
	{
		perror("snakeMove :: malloc");
		return;
	}

	if (ps->Dir == UP)
	{
		pnext->x = ps->phSnake->x;
		pnext->y = ps->phSnake->y - 1;
	}
	else if (ps->Dir == DOWN)
	{
		pnext->x = ps->phSnake->x;
		pnext->y = ps->phSnake->y + 1;
	}
	else if (ps->Dir == RIGHT)
	{
		pnext->x = ps->phSnake->x + 2;
		pnext->y = ps->phSnake->y;
	}
	else if (ps->Dir == LEFT)
	{
		pnext->x = ps->phSnake->x - 2;
		pnext->y = ps->phSnake->y;
	}
	if (isNextFood(pnext, ps))
	{
		eatFood(pnext, ps);
	}
	else
	{
		noFood(pnext, ps);
	}

	if (KillByWall(ps))
	{
		ps->Ste = KILL_BY_WALL;
	}
	
	if (KillBySelf(ps))
	{
		ps->Ste = KILL_BY_SELF;
	}

}




void GameRun(pSnake ps)
{
	helpPrint();
	do
	{
		SetPos(68, 8);
		printf("得分:%d\n", ps->allScore);
		SetPos(68, 9);
		printf("每个食物的分数:%2d\n", ps->foodScore);
		//现在要让贪吃蛇动起来,此时需要确定它的方向(确定按键状态)
		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_SPACE))
		{
			pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->Ste = NORMOL_END;

		}
		else if (KEY_PRESS(VK_F1))
		{
			//加速,最多加速四次
			if (ps->sleepTime > 80)
			{
				ps->sleepTime -= 30;
				ps->foodScore += 2;
			}

		}
		else if (KEY_PRESS(VK_F2))
		{
			//减速,也最多减速四次
			if (ps->foodScore > 2)
			{
				ps->sleepTime += 30;
				ps->foodScore -= 2;
			}
		}
		//休眠,然后下一次准备再次执行该程序,只到不满足的条件出现就跳出该循环
		Sleep(ps->sleepTime);
		
		//贪吃蛇走动一步
		snakeMove(ps);

	} while (ps->Ste == OK);
	
}


void GameEnd(pSnake ps)
{

	pSnakeNode cur = ps->phSnake;
	while (cur)
	{
		pSnakeNode pn = cur->next;
		free(cur);
		cur = pn;
	}
	
}

3.test.c:

#include"Snake.h"

void test()
{
	srand((unsigned int)time(NULL));
	int ch = 0;
	do
	{
		Snake snake = { 0 };
		//初始化游戏
		GameStart(&snake);

		//进行游戏
		GameRun(&snake);

		结束游戏
		GameEnd(&snake);
		SetPos(14, 12);
		printf("游戏结束!还要再来一把吗(Y/N):");
		ch = getchar();
		while(getchar() != '\n');
	} while (ch == 'y' || ch == 'Y');
	
}

int main()
{
	
	//设置适配环境,只有在本地模式(Chinese)下才支持宽字符的打印
	setlocale(LC_ALL, "");
	test();
	return 0;
}

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
#define N 200<br>#include <graphics.h><br>#include <stdlib.h><br>#include <dos.h><br>#define LEFT 0x4b00<br>#define RIGHT 0x4d00<br>#define DOWN 0x5000<br>#define UP 0x4800<br>#define ESC 0x011b<br>int i,key;<br>int score=0;/*得分*/<br>int gamespeed=50000;/*游戏速度自己调整*/<br>struct Food<br>{<br> int x;/*食物的横坐标*/<br> int y;/*食物的纵坐标*/<br> int yes;/*判断是否要出现食物的变量*/<br>}food;/*食物的结构体*/<br>struct Snake<br>{<br> int x[N];<br> int y[N];<br> int node;/*蛇的节数*/<br> int direction;/*蛇移动方向*/<br> int life;/* 蛇的生命,0活着,1死亡*/<br>}snake;<br>void Init(void);/*图形驱动*/<br>void Close(void);/*图形结束*/<br>void DrawK(void);/*开始画面*/<br>void GameOver(void);/*结束游戏*/<br>void GamePlay(void);/*玩游戏具体过程*/<br>void PrScore(void);/*输出成绩*/<br>/*主函数*/<br>void main(void)<br>{<br> Init();/*图形驱动*/<br> DrawK();/*开始画面*/<br> GamePlay();/*玩游戏具体过程*/<br> Close();/*图形结束*/<br>}<br>/*图形驱动*/<br>void Init(void)<br>{<br> int gd=DETECT,gm;<br> initgraph(&gd,&gm,"c:\\tc");<br> cleardevice();<br>}<br>/*开始画面,左上角坐标为(50,40),右下角坐标为(610,460)的围墙*/<br>void DrawK(void)<br>{<br>/*setbkcolor(LIGHTGREEN);*/<br> setcolor(11);<br> setlinestyle(SOLID_LINE,0,THICK_WIDTH);/*设置线型*/<br> for(i=50;i<=600;i+=10)/*画围墙*/<br> {<br> rectangle(i,40,i+10,49); /*上边*/<br> rectangle(i,451,i+10,460);/*下边*/<br> }<br> for(i=40;i<=450;i+=10)<br> {<br> rectangle(50,i,59,i+10); /*左边*/<br> rectangle(601,i,610,i+10);/*右边*/<br> }<br>}<br>/*玩游戏具体过程*/<br>void GamePlay(void)<br>{<br> randomize();/*随机数发生器*/<br> food.yes=1;/*1表示需要出现新食物,0表示已经存在食物*/<br> snake.life=0;/*活着*/<br> snake.direction=1;/*方向往右*/<br> snake.x[0]=100;snake.y[0]=100;/*蛇头*/<br> snake.x[1]=110;snake.y[1]=100;<br> snake.node=2;/*节数*/<br> PrScore();/*输出得分*/<br> while(1)/*可以重复玩游戏,压ESC键结束*/<br> {<br> while(!kbhit())/*在没有按键的情况下,蛇自己移动身体*/<br> {<br> if(food.yes==1)/*需要出现新食物*/<br> {<br> food.x=rand()%400+60;<br> food.y=rand()%350+60;<br> while(food.x%10!=0)/*食物随机出现后必须让食物能够在整格内,这样才可以让蛇吃到*/<br> food.x++;<br> while(food.y%10!=0)<br> food.y++;<br> food.yes=0;/*画面上有食物了*/<br> }<br> if(food.yes==0)/*画面上有食物了就要显示*/<br> {<br> setcolor(GREEN);<br> rectangle(food.x,food.y,food.x+10,food.y-10);<br> }<br> for(i=snake.node-1;i>0;i--)/*蛇的每个环节往前移动,也就是贪吃蛇的关键算法*/<br> {<br> snake.x[i]=snake.x[i-1];<br> snake.y[i]=snake.y[i-1];<br> }<br> /*1,2,3,4表示右,左,上,下四个方向,通过这个判断来移动蛇头*/<br> switch(snake.direction)<br> {<br> case 1:snake.x[0]+=10;break;<br> case 2: snake.x[0]-=10;break;<br> case 3: snake.y[0]-=10;break;<br> case 4: snake.y[0]+=10;break;<br> }<br> for(i=3;i<snake.node;i++)/*从蛇的第四节开始判断是否撞到自己了,因为蛇头为两节,第三节不可能拐过来*/<br> {<br> if(snake.x[i]==snake.x[0]&&snake.y[i]==snake.y[0])<br> {<br> GameOver();/*显示失败*/<br> snake.life=1;<br> break;<br> }<br> }<br> if(snake.x[0]<55||snake.x[0]>595||snake.y[0]<55||<br> snake.y[0]>455)/*蛇是否撞到墙壁*/<br> {<br> GameOver();/*本次游戏结束*/<br> snake.life=1; /*蛇死*/<br> }<br> if(snake.life==1)/*以上两种判断以后,如果蛇死就跳出内循环,重新开始*/<br> break;<br> if(snake.x[0]==food.x&&snake.y[0]==food.y)/*吃到食物以后*/<br> {<br> setcolor(0);/*把画面上的食物东西去掉*/<br> rectangle(food.x,food.y,food.x+10,food.y-10);<br> snake.x[snake.node]=-20;snake.y[snake.node]=-20;<br> /*新的一节先放在看不见的位置,下次循环就取前一节的位置*/<br> snake.node++;/*蛇的身体长一节*/<br> food.yes=1;/*画面上需要出现新的食物*/<br> score+=10;<br> PrScore();/*输出新得分*/<br> }<br> setcolor(4);/*画出蛇*/<br> for(i=0;i<snake.node;i++)<br> rectangle(snake.x[i],snake.y[i],snake.x[i]+10,<br> snake.y[i]-10);<br> delay(gamespeed);<br> setcolor(0);/*用黑色去除蛇的的最后一节*/<br> rectangle(snake.x[snake.node-1],snake.y[snake.node-1],<br> snake.x[snake.node-1]+10,snake.y[snake.node-1]-10);<br> } /*endwhile(!kbhit)*/<br> if(snake.life==1)/*如果蛇死就跳出循环*/<br> break;<br> key=bioskey(0);/*接收按键*/<br> if(key==ESC)/*按ESC键退出*/<br> break;<br> else<br> if(key==UP&&snake.direction!=4)<br>/*判断是否往相反的方向移动*/<br> snake.direction=3;<br> else<br> if(key==RIGHT&&snake.direction!=2)<br> snake.direction=1;<br> else<br> if(key==LEFT&&snake.direction!=1)<br> snake.direction=2;<br> else<br> if(key==DOWN&&snake.direction!=3)<br> snake.direction=4;<br> }/*endwhile(1)*/<br>}<br>/*游戏结束*/<br>void GameOver(void)<br>{<br> cleardevice(); <br> PrScore();<br> setcolor(RED);<br> settextstyle(0,0,4);<br> outtextxy(200,200,"GAME OVER");<br> getch();<br>}<br>/*输出成绩*/<br>void PrScore(void)<br>{ <br> char str[10];<br> setfillstyle(SOLID_FILL,YELLOW);<br> bar(50,15,220,35);<br> setcolor(6);<br> settextstyle(0,0,2);<br> sprintf(str,"score:%d",score);<br> outtextxy(55,20,str);<br>}<br>/*图形结束*/<br>void Close(void)<br>{ <br> getch();<br> closegraph();<br>}<br>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值