【C语言】贪吃蛇小游戏代码详解

贪吃蛇的规则大家应该都知道,简单来说就是吃到食物则蛇身增长,蛇可以在游戏区域内随意移动。游戏结束条件就是蛇撞到边界或者撞到自己色蛇身。
在这里插入图片描述

首先,实现一个函数,这个函数的作用是将光标移动到我们所期望的位置

/*
 *  控制光标所在的位置
 */
void gotoxy(int x, int y)
{
    
    
	COORD coord;
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

构建结构体对象

设计这个游戏,最重要的两个对象就是蛇和食物,因此用两个结构体来代表这两个对象。食物只关注其坐标位置,蛇的话,成员包括坐标数组,长度和分数。

// 定义游戏区域的边界宽度
#define   WIDTH        100
#define   HIGH         20
// 定义蛇的最大长度
#define   MAX_LENGHT   100
// 定义游戏初始速度
#define   SPEED        500

// 定义游戏中的两个对象:食物和蛇
struct
{
    
    
	int x;
	int y;
}food;

struct
{
    
    
	int len;
	int x_buf[MAX_LENGHT];
	int y_buf[MAX_LENGHT];
	int score;
}snake;

// 要用到的全局变量
int food_flag = 0;
int key = 72;

初始化游戏边界和小蛇

这个没什么难度,就是循环打印,直接放代码。

/* 
 *  绘制游戏区域边界
 */
void DrawMap(void)
{
    
    
	int x, y;
	for (x = 0; x < WIDTH + 4; x += 2)
	{
    
    
		y = 0;
		gotoxy(x, y);
		printf("■");
		y = HIGH + 2;
		gotoxy(x, y);
		printf("■");
	}

	for (y = 1; y < HIGH + 2; y++)
	{
    
    
		x = 0;
		gotoxy(x, y);
		printf("■");
		x = WIDTH + 2;
		gotoxy(x, y);
		printf("■");
	}

	// 将光标移出游戏区域
	gotoxy(0, HIGH + 5);
}

注意最后一行使用gotoxy函数移动函数很重要,不然光标会在游戏区域内持续闪烁,在退出游戏时,甚至会出现显示问题。(为了避免遗漏,我基本在每个涉及到光标移动的函数最后都加了这句话)

/*
 *  初始化小蛇
 */
void CreateSnake(void)
{
    
    
	int orgin_x, orgin_y;
	orgin_x = WIDTH / 2 + 2;
	orgin_y = HIGH / 2 + 1;

	snake.len = 3;
	snake.x_buf[0] = orgin_x;
	snake.y_buf[0] = orgin_y;
	snake.x_buf[1] = orgin_x;
	snake.y_buf[1] = ++orgin_y;
	snake.x_buf[2] = orgin_x;
	snake.y_buf[2] = ++orgin_y;

	snake.score = -1;

	int i;
	for (i = 0; i < snake.len; i++)
	{
    
    
		gotoxy(snake.x_buf[i], snake.y_buf[i]);
		printf("■");
	}
	gotoxy(0, HIGH + 5);
}

生成食物

我使用一个标志位food_flag来代表区域内是否存在食物(初始时刻,以及蛇吃到食物时,food_flag为0)。当food_flag为0时就执行生成食物的操作。由于要保证游戏的随机性,用随机数来生成食物坐标。

/*
 *  当游戏区域内不存在食物时,随机创造一个食物
 */
void CreateFood(void)
{
    
    
	if (food_flag == 0)
	{
    
    
		int flag = 0,i;
		do
		{
    
    
			srand((unsigned int)time(NULL));
			food.x = (rand() % (WIDTH/2))*2 + 2;
			food.y = rand() % HIGH + 1;

			// 判断生成的食物是否和蛇身重合
			for (i = 0; i < snake.len; i++)
			{
    
    
				if (snake.x_buf[i] == food.x && snake.y_buf[i] == food.y)
				{
    
    
					flag = 1;
					break;
				}
			}

		} while (flag);

		gotoxy(food.x, food.y);
		printf("★");

		// 吃到食物,则分数加1
		snake.score++;

		food_flag = 1;
	}
	gotoxy(0, HIGH + 5);
}

实现的方法都大同小异,但是我强调的点是随机生成坐标的实现。由于字符"■"是占两位,所以横坐标必须为2的倍数。其他的帖子里一般是用以下方法

food.x = rand() % WIDTH + 2;
food.y = rand() % HIGH + 1;

然后通过while循环来判断x是否适合要求,不符合就重新生成。但这种做法会出现一种问题,游戏进行过程中会出现卡顿的效果。主要原因就是奇偶数出现概率各占50%,因此有大概率出现多次的循环操作。
我的方法如下,这样的好处是可以保证生成的横坐标一定是偶数。避免了大量的循环操作。

food.x = (rand() % (WIDTH/2))*2 + 2;
food.y = rand() % HIGH + 1;

移动小蛇

首先要对snake对象中坐标缓存区内的坐标进行后移,以便装入新的坐标。在此要分两种情况,一是小蛇没吃到食物,则抹去最后一节,在头部加入新坐标;二是小蛇吃到食物,则不删去最后一节。

void SnakeMove(int x, int y)
{
    
    
	// 判断是否吃到食物,吃到长度加1
	if (!food_flag)
		snake.len++;
	// 没吃到则抹去最后一节
	else
	{
    
    
		gotoxy(snake.x_buf[snake.len - 1], snake.y_buf[snake.len - 1]);
		printf("  ");
	}
	int i;
	for (i = snake.len - 1; i > 0; i--)
	{
    
    
		snake.x_buf[i] = snake.x_buf[i - 1];
		snake.y_buf[i] = snake.y_buf[i - 1];
	}
	snake.x_buf[0] = x;
	snake.y_buf[0] = y;
	gotoxy(snake.x_buf[0], snake.y_buf[0]);
	printf("■");
	gotoxy(0, HIGH + 5);
}

随后根据用户按键,改变小蛇前进方向。这块部分代码也是我移植过来的,有详细的注释,就不在这赘述了。

void move()
{
    
    
	int pre_key = key, x, y;

	if (_kbhit())//如果用户按下了键盘中的某个键
	{
    
    
		fflush(stdin);//清空缓冲区的字符

		//getch()读取方向键的时候,会返回两次,第一次调用返回0或者224,第二次调用返回的才是实际值
		key = _getch();//第一次调用返回的不是实际值
		key = _getch();//第二次调用返回实际值
	}

	// 小蛇移动方向不能和上一次的方向相反
	if (pre_key == 72 && key == 80)
		key = 72;
	if (pre_key == 80 && key == 72)
		key = 80;
	if (pre_key == 75 && key == 77)
		key = 75;
	if (pre_key == 77 && key == 75)
		key = 77;

	switch (key)
	{
    
    
	case 75:
		x = snake.x_buf[0] - 2;//往左
		y = snake.y_buf[0];
		break;
	case 77:
		x = snake.x_buf[0] + 2;//往右
		y = snake.y_buf[0];
		break;
	case 72:
		x = snake.x_buf[0];
		y = snake.y_buf[0] - 1;//往上
		break;
	case 80:
		x = snake.x_buf[0];
		y = snake.y_buf[0] + 1;//往下
		break;
	}

	if (x == food.x&&y == food.y)
		food_flag = 0;
	SnakeMove(x, y);
}

检查游戏进程

主要是包括两个方面,(1)失败:小蛇撞墙或者撞到自己的身体;(2)成功:小蛇长度达到最大长度。

void check(void)
{
    
    
	int i;

	// 失败条件
	if (snake.x_buf[0] == 0 | snake.x_buf[0] == WIDTH + 4 | snake.y_buf[0] == 0 | snake.y_buf[0] == HIGH + 2)
	{
    
    
		printf("Game Over!\n");
		exit(0);
	}
	for (i = 1; i < snake.len; i++)
	{
    
    
		if (snake.x_buf[0] == snake.x_buf[i] && snake.y_buf[0] == snake.y_buf[i])
		{
    
    
			printf("Game Over!\n");
			exit(0);
		}
	}

	// 胜利条件
	if (snake.len == MAX_LENGHT)
	{
    
    
		printf("Your are win!\n");
		exit(0);
	}

	// 打印得分
	gotoxy(0, HIGH + 6);
	printf("Your score: %d", snake.score);
}

主函数

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>
#include <conio.h>

int main(void)
{
    
    
	DrawMap();
	CreateSnake();
	while (1)
	{
    
    
		CreateFood();
		move();
		// 用于控制游戏的速度
		Sleep(SPEED - 2 * snake.len);
		check();
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_43354913/article/details/113760763