C语言实现贪吃蛇小游戏

在开始阶段我们还是来借鉴网页版的贪吃蛇游戏贪吃蛇,看看其中有哪些功能,想想用C语言该如何实现,当这个游戏还没完成的时候,我就在思考,该如何用键盘控制,如果不按键盘该怎么办?别着急,往下看。

游戏思路

首先游戏有一个边界,我们可以用一个函数来打印边界,蛇就在边界之中移动,有一个函数实现蛇的移动,一个函数用来控制蛇的方向。每移动一次就要实时打印。还有一块食物,如果被蛇吃了,蛇会增加身体长度,食物会重新随机刷新在边界之中,蛇碰到边界或者吃到自己的身体就会死亡。

边界

定义两个宏,L是边界的长,W是边界的宽

#define L 60
#define W 28
void show_map()
{
	for (int i = 0; i <= W; i++)
	{
		for (int j = 0; j <= L; j++)
		{
            //如果在边界的四个角上,那么打印+号
			if ((i == 0 && j == 0) || (i == 0 && j == L)||
				(i == W && j == 0) || (i == W && j == L))
			{
				printf("+");
			}
            //如果在两个长边上,打印-
			else if (i == 0 || i == W )
			{
				printf("-");
			}
            //如果在两个短边上,打印|
			else if (j == 0 || j == L)
			{
				printf("|");
			}
            //如果不是上面三种情况的话,就打印空格
			else
			{
				printf(" ");
			}
		}
        //每打印一行要换行
		printf("\n");
	}
}

 这是打印情况:

 

 

蛇的结构体

typedef struct _body //坐标x,y的结构体
{
	int x;
	int y;
}BODY;
typedef struct _snake
{
	int size; //蛇的长度
	BODY arr[L * W]; //蛇的所有坐标的数组,大小为L*W
	BODY food; //食物位置

	//蛇的移动方向//
	int dx; 
	int dy;
	
	COORD coord; //等等再说
	BODY tail; //等等再说

	//分数
	int score;
}SNAKE;

如果有人不知道typedef的用法可以去C语言中文了解一下。简单来说就是为结构体的类型定义了新名称,比如将struct _body定义了新名称BODY。

我们可以将蛇的每一个部分都看成是两个坐标(可以用数组实现,但是比较麻烦),比如头的坐标,蛇身某一节的坐标,这样只要知道蛇的大小然后在相应的坐标位置打印就好了。

当我们控制蛇移动时,蛇该往哪移动?我们可以定义int dx,int dy,比如蛇往右移动时,因为在控制台中往下是x正轴,往右是y正轴,所以dx=0,dy=1。我们遍历蛇的每节,每一节的x都+=dx,y都+=dy就可以实现蛇的移动。

初始化蛇的参数

void unit_snake(SNAKE* snake)
{
	snake->size = 2; //初始是2节
	//蛇头,在地图的中心位置
	snake->arr[0].x = W / 2;
	snake->arr[0].y = L / 2;

	//蛇尾
	snake->arr[1].x = W / 2;
	snake->arr[1].y = L / 2 - 1 ;

	//初始化食物
	unit_food(&(snake->food));
	
	//移动,默认往右移动
	snake->dx = 0;
	snake->dy = 1;
	//分数初始化为0
	snake->score = 0;
	
	//尾巴等等有说
	snake->tail = snake->arr[1];
}

void unit_food(BODY* food)
{
	food->x = rand() % (W-1)+1;
	food->y = rand() % (L - 1) + 1;
    //因为地图的坐标范围是:1<=x<=27 0<=y<=59,不能让食物生成在边界上
}

隐藏控制台光标

因为每次打印的时候,会有光标一直在闪烁,但是贪吃蛇游戏里不需要这个,只需要在主函数第一行(打印前)用上这个函数就行了。

void hide_cur()
{
	//隐藏控制台光标
	CONSOLE_CURSOR_INFO  cci;
	cci.dwSize = sizeof(cci);
	cci.bVisible = FALSE;
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);
}

打印蛇和食物 

如果蛇、食物、边界在一起的话,必须要将他们都放在同一个数组。这样非常慢,因为每次都需要将所有的信息打印一遍。

但是windows.h中定义了一个结构体它可以控制光标的位置,也就是说,我们只需要先打印一个边界,然后在边界中(也就是空格填满的区域)打印我们的蛇和食物就好了。

这是定义中的结构体,注意有一点其中的X坐标是向右为x轴正方向的,Y坐标是向下为y轴正方向,和我们之前的x,y坐标不同。

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD;

不需要我们来定义,我们只需要创建它的变量然后使用就可以了,我们在蛇的结构体中有创建它。这是具体使用:

coor.X = 20;
coor.Y = 10;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
//这代表着最开始光标在控制台的左上角,然后将光标向右移动20格,再向下移动10格,在这个位置开始打印

接下来实现打印我们的蛇和食物

void show_snake_food(SNAKE* snake)
{
	//设置光标位置,打印蛇
	for (int i = 0; i < snake->size; i++)
	{
        //这就是区别,为什么X赋值y,因为两个的参考坐标系不同
		snake->coord.X = snake->arr[i].y;
		snake->coord.Y = snake->arr[i].x;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
		if (i == 0)
		{
			printf("@"); //蛇头的字符
		}
		else
		{
			printf("&"); //蛇身的字符
		}
	}

	//设置光标位置,打印食物*
	snake->coord.X = snake->food.y;
	snake->coord.Y = snake->food.x;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
	printf("*");
	//尾巴变为空格,每移动一次-等等讲
	snake->coord.X = snake->tail.y;
	snake->coord.Y = snake->tail.x;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
	printf(" ");

}

这是打印效果,因为其中有代码会将尾巴变成空格,所以没打印尾巴。而这个变为空格的代码是为了,蛇每一次移动,都要将原来已经打印了尾巴的位置,变为空白。

 如果没有将尾巴去除的代码那么就会变成下面这样:

 这显然不是我们想要的。

蛇的移动

这里我们就看看蛇是怎么移动的

void move_snake(SNAKE* snake)
{
	snake->tail.x = snake->arr[snake->size - 1].x;
	snake->tail.y = snake->arr[snake->size - 1].y;
    //移动时,记录尾巴的坐标,等等打印的时候要将这个位置打印空格

	for (int i = snake->size - 1; i > 0; i--) //身体的坐标移动
	{
		snake->arr[i] = snake->arr[i - 1];//从后往前赋值,这样就不会覆盖数据
	}
	
	//蛇头的坐标移动,蛇头单独赋值
	snake->arr[0].x += snake->dx;
	snake->arr[0].y += snake->dy;
}

蛇的控制

这就是贪吃蛇最核心的部分了,如果我们去控制蛇,用什么接收?用getchar()?当然不行,因为每按一次要回车一下并且它会使得程序停止。

有一个_khbit()函数,它可以检查当控制台打开时是否有按键按下,如果有就返回一个非0的值,如果没有就返回0,就算你没有按下任何按键,它也会在执行完返回一个0,它不会使程序停下来。

_khbit()和_getch()的头文件都是 <conio.h>。

    char  key;
	while (_kbhit()) //如果按下了按键,_getch()会获取输入的字符
	{
		key = _getch();//并且不会在控制台中显示这个字符,返回值是输入的字符
	}
void control_snake(SNAKE* snake)
{
	char  key=0;
	while (_kbhit()) 
	{
		key = _getch();
	}
	switch (key)
	{
    //向左
	case 'A':
	case 'a':
		snake->dx = 0;
		snake->dy = -1;
		break;
    //向右
	case 'D':
	case 'd':
		snake->dx = 0;
		snake->dy = 1;
		break;
    //向上
	case 'W':
	case 'w':
		snake->dx = -1;
		snake->dy = 0;
		break;
    //向下
	case 'S':
	case 's':
		snake->dx = 1;
		snake->dy = 0;
		break;
	}
    //大小写都可以移动
}

蛇吃到食物

void eat_food(SNAKE* snake)
{
	if (snake->arr[0].x == snake->food.x &&
		snake->arr[0].y == snake->food.y) //当头的坐标和食物的坐标相同的时候
	{
		unit_food(&(snake->food)); //将食物再次生成
		snake->size++; //蛇的长度+1
		snake->score += 10; //得分加10
	}
}

在这里我们并不需要对增加的蛇尾研究,因为吃到食物size会自增1,在蛇移动时是从后向前赋值的,所以这增加的蛇尾会得到之前蛇尾的坐标也就是值。

蛇吃到自己以及游戏结束

int eat_body(SNAKE* snake)
{
	for (int i = 1; i < snake->size; i++)
	{
		if (snake->arr[0].x == snake->arr[i].x &&
			snake->arr[0].y == snake->arr[i].y) //遍历蛇身比较蛇头
		{
			game_over(snake);
			return 1;
		}
	}
	return 0;
}

void game_over(SNAKE* snake)
{
	snake->coord.X = 21;
	snake->coord.Y = 32;
    //在一个合适的位置打印
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
	printf("得分:%d ,", snake->score);
	printf("游戏结束 !\n");
}

游戏函数

void game(SNAKE* snake)
{
	while (snake->arr[0].x > 0 && snake->arr[0].x < W
		&& snake->arr[0].y>0 && snake->arr[0].y < L) //是否超出边界
	{
		show_snake_food(snake);
		control_snake(snake);
		eat_food(snake);
		if (eat_body(snake))
			goto end;
		Sleep(70); //睡眠0.07s
		move_snake(snake);
	}
	game_over(snake);
end:
	;
}

主函数和函数实现和头文件

#define _CRT_SECURE_NO_WARNINGS 1


#include "test.h"




int main()
{

	srand((time_t)time(NULL)); //初始化随机数种子
	hide_cur(); //隐藏光标


	SNAKE* snake =(SNAKE*) malloc(sizeof(SNAKE)); //创建蛇结构体
	unit_snake(snake); //初始化蛇和食物
	show_map();  //打印边界
	game(snake);
	
	Sleep(3000);
	free(snake);
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1

#include "test.h"




void show_map()
{
	for (int i = 0; i <= W; i++)
	{
		for (int j = 0; j <= L; j++)
		{
			if ((i == 0 && j == 0) || (i == 0 && j == L)||
				(i == W && j == 0) || (i == W && j == L))
			{
				printf("+");
			}
			else if (i == 0 || i == W )
			{
				printf("-");
			}
			else if (j == 0 || j == L)
			{
				printf("|");
			}
			else
			{
				printf(" ");
			}
		}
		printf("\n");
	}
}
void unit_snake(SNAKE* snake)
{
	snake->size = 2;
	//蛇头
	snake->arr[0].x = W / 2;
	snake->arr[0].y = L / 2;

	//蛇尾
	snake->arr[1].x = W / 2;
	snake->arr[1].y = L / 2 - 1 ;

	//食物
	unit_food(&(snake->food));
	
	//移动
	snake->dx = 0;
	snake->dy = 1;
	//分数
	snake->score = 0;
	
	//尾巴
	snake->tail = snake->arr[1];
}
void unit_food(BODY* food)
{
	food->x = rand() % (W-1)+1;
	food->y = rand() % (L - 1) + 1;
}
void hide_cur()
{
	//隐藏控制台光标
	CONSOLE_CURSOR_INFO  cci;
	cci.dwSize = sizeof(cci);
	cci.bVisible = FALSE;
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);
}
void show_snake_food(SNAKE* snake)
{
	//设置光标位置,打印蛇
	for (int i = 0; i < snake->size; i++)
	{
		snake->coord.X = snake->arr[i].y;
		snake->coord.Y = snake->arr[i].x;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
		if (i == 0)
		{
			printf("@");
		}
		else
		{
			printf("&");
		}
	}

	//设置光标位置,打印食物
	snake->coord.X = snake->food.y;
	snake->coord.Y = snake->food.x;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
	printf("*");
	//尾巴变为空格,没移动一次
	snake->coord.X = snake->tail.y;
	snake->coord.Y = snake->tail.x;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
	printf(" ");

}


void game(SNAKE* snake)
{
	while (snake->arr[0].x > 0 && snake->arr[0].x < W
		&& snake->arr[0].y>0 && snake->arr[0].y < L)
	{
		show_snake_food(snake);
		control_snake(snake);
		eat_food(snake);
		if (eat_body(snake))
			goto end;
		Sleep(70);
		move_snake(snake);
	}
	game_over(snake);
end:
	;
}
void move_snake(SNAKE* snake)
{
	snake->tail.x = snake->arr[snake->size - 1].x;
	snake->tail.y = snake->arr[snake->size - 1].y;

	for (int i = snake->size - 1; i > 0; i--) //尾巴的坐标移动
	{
		snake->arr[i] = snake->arr[i - 1];
	}
	
	//蛇头的坐标移动
	snake->arr[0].x += snake->dx;
	snake->arr[0].y += snake->dy;
}
void control_snake(SNAKE* snake)
{
	char  key=0;
	while (_kbhit()) 
	{
		key = _getch();
	}
	switch (key)
	{
	case 'A':
	case 'a':
		snake->dx = 0;
		snake->dy = -1;
		break;
	case 'D':
	case 'd':
		snake->dx = 0;
		snake->dy = 1;
		break;
	case 'W':
	case 'w':
		snake->dx = -1;
		snake->dy = 0;
		break;
	case 'S':
	case 's':
		snake->dx = 1;
		snake->dy = 0;
		break;
	}

}
void eat_food(SNAKE* snake)
{
	if (snake->arr[0].x == snake->food.x &&
		snake->arr[0].y == snake->food.y)
	{
		unit_food(&(snake->food));
		snake->size++;
		snake->score += 10;
	}
}
int eat_body(SNAKE* snake)
{
	for (int i = 1; i < snake->size; i++)
	{
		if (snake->arr[0].x == snake->arr[i].x &&
			snake->arr[0].y == snake->arr[i].y)
		{
			game_over(snake);
			return 1;
		}
	}
	return 0;
}
void game_over(SNAKE* snake)
{
	snake->coord.X = 21;
	snake->coord.Y = 32;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
	printf("得分:%d ,", snake->score);
	printf("游戏结束 !\n");
}
#pragma once

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

#include <conio.h> //检测键盘输入

#define L 60
#define W 28

typedef struct _body
{
	int x;
	int y;
}BODY;
typedef struct _snake
{
	int size; //蛇长度
	BODY arr[L * W];
	BODY food; //食物位置
	//蛇的移动方向//
	int dx; 
	int dy;
	
	COORD coord;
	BODY tail; //蛇尾巴

	//分数
	int score;
}SNAKE;


void show_map(); //打印边界

void unit_snake(SNAKE* snake); //初始化蛇

void unit_food(BODY *boay); //初始化食物位置

void hide_cur();//隐藏控制台光标

void show_snake_food(SNAKE* snake); //打印蛇和食物


void game(SNAKE* snake); //开始游戏

void move_snake(SNAKE* snake); //移动蛇

void control_snake(SNAKE* snake);


void eat_food(SNAKE* snake);

int eat_body(SNAKE* snake);

void game_over(SNAKE* snake);

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值