当前位置: 首页 > 工具软件 > snake-win > 使用案例 >

[Small Game]Gluttonous Snake-V1.0

姜育
2023-12-01

        今天写了人生第一条蛇——贪吃蛇的V1.0版本。

        来分享下吧。

  

        下面先贴代码。

#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>
#include<conio.h>
/*=========================================================================================================*/
#define LEN 30
#define WID 25
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
/*=========================================================================================================*/
int snake_direction = LEFT;
int snake_length = 3;

bool alive = true;
bool eatup = false;
/*=========================================================================================================*/
//光标相关
	void Set_Cursor_To(int x, int y);
//蛇相关
	void direction_snake();
	void flag_snake();
	void draw_snake();
	void eraser_snake();
//食物相关
	void product_food();
//环境相关
	void draw_boundary();
//控制相关
	void Game_Over();
	void initialize();
/*=========================================================================================================*/
//光标相关 
	HANDLE handle_console = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置光标位置
	void Set_Cursor_To(int x, int y)
	{
		COORD position = { x, y };
		SetConsoleCursorPosition(handle_console, position);
	}

//蛇相关
	int snake[LEN][WID] = { 0 };											//全地图标识数组
	//检测移动方向
	void direction_snake()
	{
		if (kbhit())
		{
			int ch = getch();
			switch (getch())
			{
				case UP:	snake_direction = UP;	break; 
				case DOWN:	snake_direction = DOWN;	break;
				case LEFT:	snake_direction = LEFT;	break;
				case RIGHT:	snake_direction = RIGHT;break;
				default: break;
			}
		}
		eraser_snake();
		flag_snake();
		draw_snake();
	}
	//标记蛇
	void flag_snake()
	{
		int Snake_ix, Snake_iy;
		
		for (int x = 0; x < LEN; x++)
		for (int y = 0; y < WID; y++)
		{
			if (snake[x][y] == 1)//记录蛇头Snake_ix/iy
			{
				switch (snake_direction)
				{
					case UP:Snake_ix = x; Snake_iy = y - 1; break;
					case DOWN:Snake_ix = x; Snake_iy = y + 1; break;
					case LEFT:Snake_ix = x-1; Snake_iy = y; break;
					case RIGHT:Snake_ix = x+1; Snake_iy = y; break;
					default: break;

				}
				if (Snake_ix == 0 || Snake_iy == 0 || Snake_ix == LEN || Snake_iy == WID)//检查移动后头坐标是否满足规则
				{
					alive = false;
				}
				if (snake[Snake_ix][Snake_iy] > 0)//检查移动后头位置是否满足规则
				{
					alive = false;
				}
				if (snake[Snake_ix][Snake_iy] == -1)
				{
					snake_length++;
					eatup = true;
				}
			}
			if (snake[x][y] == snake_length)//必要时清除蛇尾
			{
				snake[x][y] = 0;
			}
			if (snake[x][y] > 0)//对蛇身进行编号
			{
				snake[x][y] += 1;
			}
		}
		snake[Snake_ix][Snake_iy] = 1;
	}
	//画蛇
	void draw_snake()
	{
		for (int i = 0; i < LEN;i++)
		for (int j = 0; j < WID;j++)
		{
			if (snake[i][j]>0)
			{
				Set_Cursor_To(2 * i, j);
				printf("■");
			}
		}
	}
	//除尾
	void eraser_snake()
	{
		for (int i = 0; i < LEN;i++)
		for (int j = 0; j < WID; j++)
		{
			if (snake[i][j] == snake_length)
			{
				Set_Cursor_To(2 * i, j);
				printf(" ");
			}
		}
	}
//食物相关
	//生产食物
	void product_food()
	{
		int food_ix, food_iy;

		food_ix = rand() % LEN;
		food_iy = rand() % WID;
		while (snake[food_ix][food_iy] != 0)
		{
			food_ix = rand() % LEN;
			food_iy = rand() % WID;
		}
		snake[food_ix][food_iy] = -1;
		Set_Cursor_To(2 * food_ix, food_iy);
		printf("★");
		
		eatup = false;
	}
//环境相关
	//边界
	void draw_boundary()
	{
		for (int i = 0; i < LEN; i++)//上下边界
		{
			Set_Cursor_To(2 * i, 0);
			printf("--");
			Set_Cursor_To(2 * i, WID - 1);
			printf("--");
			snake[i][0] = -2;
			snake[i][WID - 1] = -2;
		}
		for (int i = 0; i < WID; i++)
		{
			Set_Cursor_To(0, i);
			printf("|");
			Set_Cursor_To(2*LEN - 1, i);
			printf("|");
			snake[0][i] = -2;
			snake[LEN-1][i] = -2;
		}
	}
//控制相关
	//判定游戏结束
	void Game_Over()
	{
		Set_Cursor_To(25, 12);
		printf("~~傻屌,你死了~~");
		Set_Cursor_To(21, 13);
		printf("Press any key to replay.");
		system("pause > nul");
	}
	//初始化
	void initialize()
	{
		for (int x = 0; x < LEN; x++)
		for (int y = 0; y < WID; y++)
		{
			snake[x][y] = 0;
		}
		system("title Bit_Percy_贪吃蛇V1.0");
		system("mode con:cols=62 lines= 27");
		draw_boundary();
		for (int i = 0; i < snake_length; i++)                  //初始化蛇
		{
			snake[i + 12][12] = i + 1;
		}
		draw_snake();
		product_food();
		alive = true;
	}
/*=========================================================================================================*/
int main()
{
	while (1)
	{
		system("cls");
		initialize();
		while (1)
		{
			if (alive == false)
			{
				Game_Over();
				break;
			}
			else
			{
				if (eatup == true)
				{
					product_food();
				}
				direction_snake();
				Sleep(100);
			}
		}
	}
	return 0;
}
/*=========================================================================================================*/



接下来分块说了~

首先呢,写这玩意最好先列个提纲。。要不然会死的= =。。

上面程序的注释当提纲已经很清楚了。下面来依次解释下吧。


第一部分:预编译部分

#define LEN 30
#define WID 25
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77

LEN表示场地 列数/2。

WID表示场地 行数。

UP是键盘“上”的键值。

DOWN是键盘“下”的键值。

LEFT是键盘“左”的键值。

RIGHT是键盘“右”的键值。


第二部分:声明部分

int snake_direction = LEFT;
int snake_length = 3;

bool alive = true;
bool eatup = false;
/*=========================================================================================================*/
//光标相关
void Set_Cursor_To(int x, int y);
//蛇相关
void direction_snake();
void flag_snake();
void draw_snake();
void eraser_snake();
//食物相关
void product_food();
//环境相关
void draw_boundary();
//控制相关
void Game_Over();
void initialize();

初始蛇的前进方向snake_direction设置为LEFT。

初始蛇的长度设置为3。

初始生命设置为true。

初始食物是否吃完设置为false。


之后是几个函数的声明。


第三部分:首先介绍光标相关的函数:

//光标相关 
HANDLE handle_console = GetStdHandle(STD_OUTPUT_HANDLE);
//设置光标位置
void Set_Cursor_To(int x, int y)
{
	COORD position = { x, y };
	SetConsoleCursorPosition(handle_console, position);
}

首先介绍一下windows编程中句柄的概念:

        一个句柄是指使用的一个唯一的整数值,即一个四字节长的数值,来标识应用程序中的不同对象和同类对象中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是一个指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不用在I/O文件中,它是毫无用处的。 句柄是windows用来标志应用程序中建立的或是使用的唯一整数,windows使用了大量的句柄来标志很多对象。(摘自百度百科)


        总之,句柄虽然不是一个指针,但我们可以在用途方面初步地理解为一个指针,就相当于win下资源的身份证,无论窗口,画板,画笔,都有一个唯一的句柄来访问。比如你开了2个qq窗口,系统只用句柄来区别他们。程序也可以用相关api通过句柄访问。之所以说它不是一个指针,是因为指针是指向内存的,而句柄本质上是一个无符号数字。 


好了,我们现在知道什么是句柄了。这里HANDLE声明的就是一个句柄handle_console。

这个句柄用来表示什么东西呢?我们使用GetStdHandle(STD_OUTPUT_HANDLE)来获得标准输出设备(一般是显示器这里就是控制台了)的句柄。

COORD是一个windows API中定义的一个结构体。

就相当于:

typedef struct _COORD { // coord. 
    SHORT X;      // horizontal coordinate 
    SHORT Y;      // vertical coordinate 
} COORD; 
        我们用它来表示 坐标。
        SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD dwCursorPosition);这句用来移动光标位置到指定坐标。


通过光标相关的函数,我们可以把光标发送到地图的任意一个角落,来执行输出操作。


第四部分:蛇相关

int snake[LEN][WID] = { 0 };											//全地图标识数组
         首先定义全地图标识数组。

4.1:检测移动方向

void direction_snake()
{
	if (kbhit())
	{
		int ch = getch();
		switch (getch())
		{
		case UP:	snake_direction = UP;	break;
		case DOWN:	snake_direction = DOWN;	break;
		case LEFT:	snake_direction = LEFT;	break; 
		case RIGHT:	snake_direction = RIGHT; break; 
		default: break;
		}
	}
	eraser_snake();
	flag_snake();
	draw_snake();
}

        kbhit()函数的功能为:  检查当前是否有键盘输入,若有则返回一个非0值,否则返回0。

        我们让ch来接受这个传入的字符。如果是上下左右时,将蛇的前进方向设置为UP、DOWN、LEFT、RIGHT。

        之后来执行的3个函数是为了让蛇动起来。

        这三个函数在接下来会说。

        4.2:标记蛇

void flag_snake()
{
	int Snake_ix, Snake_iy;

	for (int x = 0; x < LEN; x++)
	for (int y = 0; y < WID; y++)
	{
		if (snake[x][y] == 1)//记录蛇头Snake_ix/iy
		{
			switch (snake_direction)
			{
			case UP:Snake_ix = x; Snake_iy = y - 1; break;
			case DOWN:Snake_ix = x; Snake_iy = y + 1; break;
			case LEFT:Snake_ix = x - 1; Snake_iy = y; break;
			case RIGHT:Snake_ix = x + 1; Snake_iy = y; break;
			default: break;

			}
			if (Snake_ix == 0 || Snake_iy == 0 || Snake_ix == LEN || Snake_iy == WID)//检查移动后头坐标是否满足规则
			{
				alive = false;
			}
			if (snake[Snake_ix][Snake_iy] > 0)//检查移动后头位置是否满足规则
			{
				alive = false;
			}
			if (snake[Snake_ix][Snake_iy] == -1)
			{
				snake_length++;
				eatup = true;
			}
		}
		if (snake[x][y] == snake_length)//必要时清除蛇尾
		{
			snake[x][y] = 0;
		}
		if (snake[x][y] > 0)//对蛇身进行编号
		{
			snake[x][y] += 1;
		}
	}
	snake[Snake_ix][Snake_iy] = 1;
}

       Snake_ix,Snake_iy用来标记蛇移动后头的坐标。

       然后根据方向对蛇头进行坐标上的移动。

       在检验移动后的坐标位置是否不可到达(到达边界活着与蛇身相撞)。如果是则死亡。

       还需要检验是否迟到了食物,如果吃到,蛇应该变长。 

       必要情况(没有吃到食物时)下还应当清除蛇尾。

       之后再对整条蛇重新编号。

       这就是整个标记蛇的过程。


      4.3:画蛇

void draw_snake()
{
	for (int i = 0; i < LEN; i++)
	for (int j = 0; j < WID; j++)
	{
		if (snake[i][j]>0)
		{
			Set_Cursor_To(2 * i, j);
			printf("■");
		}
	}
}

         接下来就是画蛇了。

         由于标记时所有有蛇的坐标点全部都是正值,有食物的坐标点讲置为-1,边界将置为-2,所以画蛇只需要将所有值大于0的坐标点涂黑了~


         4.4:除尾

void eraser_snake()
{
	for (int i = 0; i < LEN; i++)
	for (int j = 0; j < WID; j++)
	{
		if (snake[i][j] == snake_length)
		{
			Set_Cursor_To(2 * i, j);
			printf(" ");
		}
	}
}

        在必要的时候(也就是没吃到食物而进行移动的时候),我们需要除去蛇的尾巴(也就是最后一个方框框)。

        由于计划每次都重新画蛇,所以我们每次都除去尾巴即可。

        除去尾巴的操作很简单啦。。不说了


        第五部分:食物相关

         

//生产食物
void product_food()
{
	int food_ix, food_iy;

	food_ix = rand() % LEN;
	food_iy = rand() % WID;
	while (snake[food_ix][food_iy] != 0)
	{
		food_ix = rand() % LEN;
		food_iy = rand() % WID;
	}
	snake[food_ix][food_iy] = -1;
	Set_Cursor_To(2 * food_ix, food_iy);
	printf("★");

	eatup = false;
}

        我们的食物用星星来表示。

        rand()函数用来产生一个随机数。我们要让食物在地图内的可用点上,也就是说必须是地图 上的,而且是标记为0的点 才可能有食物。

        为了满足这个条件,我们使用如上方法。

 

        第六部分:环境相关

//边界
void draw_boundary()
{
	for (int i = 0; i < LEN; i++)//上下边界
	{
		Set_Cursor_To(2 * i, 0);
		printf("--");
		Set_Cursor_To(2 * i, WID - 1);
		printf("--");
		snake[i][0] = -2;
		snake[i][WID - 1] = -2;
	}
	for (int i = 0; i < WID; i++)
	{
		Set_Cursor_To(0, i);
		printf("|");
		Set_Cursor_To(2 * LEN - 1, i);
		printf("|");
		snake[0][i] = -2;
		snake[LEN - 1][i] = -2;
	}
}

只是用来画个边界啦。很简单的。


       第七部分:控制相关

//判定游戏结束
void Game_Over()
{
	Set_Cursor_To(25, 12);
	printf("~~傻屌,你死了~~");
	Set_Cursor_To(21, 13);
	printf("Press any key to replay.");
	system("pause > nul");
}

system("pause > nul");和system("pause");效果一样,区别只在于前者不显示“Press any key to continue. ”。


//初始化
void initialize()
{
	for (int x = 0; x < LEN; x++)
	for (int y = 0; y < WID; y++)
	{
		snake[x][y] = 0;
	}
	system("title Bit_Percy_贪吃蛇V1.0");
	system("mode con:cols=62 lines= 27");
	draw_boundary();
	for (int i = 0; i < snake_length; i++)                  //初始化蛇
	{
		snake[i + 12][12] = i + 1;
	}
	draw_snake();
	product_food();
	alive = true;
}

         初始化地图的过程。

        system("title xxxxxxxx");用于替换控制台的名称。

        system("mode con:cols = 62 lines = 27");用于控制控制台的大小。

 

        第八部分:主函数

int main()
{
	while (1)
	{
		system("cls");
		initialize();
		while (1)
		{
			if (alive == false)
			{
				Game_Over();
				break;
			}
			else
			{
				if (eatup == true)
				{
					product_food();
				}
				direction_snake();
				Sleep(100);
			}
		}
	}
	return 0;
}

sleep(100);是让程序休眠0.1s 。修改这里的值可以调节蛇的速度。

system("cls");是清屏命令。

 类似资料: