C/C++实现贪吃蛇游戏

前言

小时候都玩过贪吃蛇这个经典的小游戏,在我们的普通手机里似乎都是必备的。它伴随着我们的童年,经历了好多好多时光。它带给我们了许多的乐趣。“贪吃蛇”就是C语言中非常基础的部分,重点需要的知识就是结构体,循环,函数等知识,好在没有让人生畏的指针。

一、游戏效果展示

在这里插入图片描述

二、游戏说明

游戏界面当中没有打印相关的按键说明,这里先逐一列出,贪吃蛇游戏按键说明:

1.按方向键上下左右,可以实现蛇移动方向的改变。
2.短时间长按方向键上下左右其中之一,可实现蛇向该方向的短时间加速移动。
3.按空格键可实现暂停,暂停后按任意键继续游戏。
4.按Esc键可直接退出游戏。
5.按R键可重新开始游戏。

除此之外,本游戏还拥有计分系统,可保存玩家的历史最高记录。

三、游戏框架构建

在这里插入图片描述

1、游戏界面的大小

首先是创建窗口和给窗口画个背景色,要用到头文件graphics。

graphics的安装:下个Easyx,打开可见exe可执行文件,运行后可以根据自己的VC版本选择适合自己的图形库。

需要用的函数是initgraph和setbkcolor,这里我们设定一个640 * 480的窗口,颜色是蓝色,别忘了要清空一下绘图设备,运行一下,就成了。

initgraph(640,480);
setbkcolor(RGB(14,218,243));//背景色是蓝色
cleardevice();//清空绘图设备

不过运行一下,会出现闪退的情况,这是正常的,因为程序已经结束了。要想显示,可以在main中加一句while(1){}或getchar()

2、创建蛇

在这里插入图片描述

2.1定义蛇的结构体

需要定义蛇的节点,长度,方向,坐标,其中坐标可以用POINT来直接定义,坐标里的数值可以用宏定义来处理。

POINT coor[SNAKE_NUM];

看一下POINT的定义:

typedef struct tagPOINT
{
    LONG  x;
    LONG  y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;

用typedef定义的结构体可以直接用,这样就方便书写,不过为了混淆,最好大写。

2.2、初始化蛇

分两步,一是完成01的过程,二是完成1N的过程,也就是先初始化蛇头,再初始化蛇身。也就是要初始化节数、长度、方向、坐标,刚开始可能没有思路,那就先画一个蛇头:

struct Snake
{
       int len; //记录蛇身长度
       int x; //蛇头横坐标
       int y; //蛇头纵坐标
}snake;

蛇身其实就是三个蛇头,每个蛇头之间10间隔,因此整个蛇就是循环3个蛇头的过程:
蛇身结构体当中存储着该段蛇身的位置坐标

struct Body
{
       int x; //蛇身横坐标
       int y; //蛇身纵坐标
}body[ROW*COL]; //开辟足以存储蛇身的结构体数组

2.3、画蛇

也是和上面一样的,刚开始没思路可以先画个蛇头,然后用循环画蛇身,这里用到的函数就是solidcircle,里面要输入x坐标,y坐标和半径。

for(int i=0;i<snake.size;i++)
{
	solidcircle(snake.coor[i].x,snake.coor[i].y,5);
}

觉得颜色不好看,可以用这个函数改:setfillcolor(WHITE)

注意:运行一下,可以看到有些闪屏,那就在绘制函数中用BeginBatchDraw()和EndBatchDraw(),一头一尾。

3、标记游戏区

同时我们需要一个二维数组来存储游戏区各个位置是什么(该位置为空、墙、食物、蛇头以及蛇身)。

nt face[ROW][COL]; //存储游戏区各个位置是什么,通过存储不同的数字便可达到目的

为了增加代码的可读性,最好运用宏来定义空、墙、食物、蛇头以及蛇身。

#define KONG 0 //标记空(什么也没有)
#define WALL 1 //标记墙
#define FOOD 2 //标记食物
#define HEAD 3 //标记蛇头
#define BODY 4 //标记蛇身

当然,为了代码的可读性,我们最好也将需要用到的按键的键值用宏进行定义

#define UP 72 //方向键:上
#define DOWN 80 //方向键:下
#define LEFT 75 //方向键:左
#define RIGHT 77 //方向键:右
#define SPACE 32 //暂停
#define ESC 27 //退出

4、菜单栏的设置

void menu() {
    system("title 贪吃蛇");//设置窗口标题
    system("mode con cols=84 lines=23"); //设置cmd窗口的大小
    color(14);//设置文字为淡黄色
    printf("*****************************************************************\n");
    printf("******************欢迎来到贪吃蛇的游戏里!!!*******************\n");
    printf("*****************************************************************\n");
    printf("*********按方向键上下左右,可以实现蛇移动方向的改变**************\n");
    printf("*****************************************************************\n");
    printf("**********按空格键可实现暂停,暂停后按任意键继续游戏*************\n");
    printf("*****************************************************************\n");
    printf("*********************按Esc键可直接退出游戏***********************\n");
    printf("*********************按R键可重新开始游戏*************************\n");
    printf("*****************************************************************\n");
    system("pause");//暂停程序,按任意键继续
}

5、让蛇动起来

在这里插入图片描述

5.1、蛇的移动

先看蛇头再看蛇身,蛇头很好动,就一直向前移动。

snake.coor[0].x++;

而蛇身就是后一个走到前一个位置上,顺序从后往前,从前往后的话那第二个数据谁给它?

这里是易错点,正确思路是2的数据由1给,1的由0给,0是自己++。若是反过来,0自己++,1的数据由0给,然后覆盖了,那这时候2的数据谁给?所以从后往前赋。

for(int i = snake.size-1 ;i > 0 ;i--)
	{
		snake.coor[i] = snake.coor[i-1];
	}

这里会出现闪屏,卡屏的情况,只要在绘制函数中用BeginBatchDraw()和EndBatchDraw(),一头一尾。//这就是双缓存绘图的函数
但是这样做之后,发现全都汇聚到蛇头了,因为没有方向判定。

5.2、表示方向

这就要用到枚举数据类型enum,常规思路是用宏定义,但是上下左右四个宏定义,太多了。于是精简一下,改用enum这样的枚举数据类型。

enum DIR //表示蛇的方向
{
	UP,
	DOWN,
	LEFT,
	RIGHT,
};

然后我们初始化一下,就定义从右边开始。因为窗口的坐标系是从左上开始的,我们定义的蛇也是从左上开始的,所以定义方向是右边。

snake.dir = RIGHT;

下面我们要判定方向,先看蛇头的方向,这就要用switch来判定了(这里移动方式是有问题的,但是便于观测,先看蛇头的移动,后面会改动):

switch(snake.dir)
{
        case UP:
		snake.coor[0].y--;

	case DOWN:
		snake.coor[0].y++;

	case LEFT:
		snake.coor[0].x--;

	case RIGHT:
		snake.coor[0].x++;
}

5.3、控制方向

因为我们需要用键盘来控制这条蛇,所以我们也要用switch语句来控制。其中我们要用conio这个头文件下的_getch()函数,因为这个函数会从控制台读取一个字符,但不显示在屏幕上。

switch(_getch()) 
	 {
	 case 'w':
	 case 'W':
	 case '72':
		snake.dir=UP;

	 case 's':
	 case 'S':
	 case '80':
		snake.dir=DOWN;

	 case 'a':
	 case 'A':
	 case '75':
		snake.dir=LEFT;

	 case 'd':
	 case 'D':
	 case '77':
		snake.dir=RIGHT;
	 }

我们运行一下,发现只有我们按一下键盘它才动一下。这个跟_getch()有关,我们要想它自动执行这个循环,那就要没有输入的时候就可以自动执行。这里就可以用到_kbhit()函数,它也是conio.h头文件的,功能是检查控制台是否有输入,有就返回真值。

所以只要把这个switch放到if(_kbhit())循环中就好。

5.4调整

其实上面存在些问题,比如头和身体合并了,移动的话不能向左边移动,或者整节蛇在移动,有点像俄罗斯方块,还有就是超出边界怎么办的处理(穿墙)。

首先调整身体的移动,之前头的代码是这样的:

snake.coor[0].y--;

这就导致后面的会全部聚在头里,因为1单位移动变化小,看起来像是聚在一起的。所以我们优化一下:

snake.coor[0].y-=snake.speed;

这就是根据蛇身的长度来移动的,这么做让头的坐标变化要跟速度保持一致性,头加(减)10,身体也这样才满足。

接下来调整穿墙的问题,也就是从边界出,就能从边界进。就是坐标、半径和边界的关系:

if(snake.coor[0].y+5<=0)//这里以从上面穿墙为例
{
	snake.coor[0].y = 480; //480:窗口的宽
}

解决好这个问题,就能解决穿墙和向左移的问题了,但是我们发现它居然还可以调头,这个我们也得解决一下。

最后是调头的问题:

就是键盘输入的方向中,不能出现调头的情况,往上就不能往下。

if(snake.dir != DOWN)
	snake.dir=UP;

6、创建食物

随机在游戏区生成食物,需要对生成后的坐标进行判断,只有该位置为空才能在此生成食物,否则需要重新生成坐标。食物坐标确定后,需要对游戏区该位置的状态进行标记。


void createFood()
{
	if (snake.x[0] == food.x && snake.y[0] == food.y)//蛇头碰到食物
	{
		//蛇头碰到食物即为要吃掉这个食物了,因此需要再次生成一个食物
		while (1)
		{
			int flag = 1;
			srand((unsigned int)time(NULL));
			food.x = rand() % (MAPWIDTH - 4) + 2;
			food.y = rand() % (MAPHEIGHT - 2) + 1;
 
			//随机生成的食物不能在蛇的身体上
			for (int i = 0; i < snake.len; i++)
			{
				if (snake.x[i] == food.x && snake.y[i] == food.y)
				{
					flag = 0;
					break;
				}
			}
			//随机生成的食物不能横坐标为奇数,也不能在蛇身,否则重新生成
			if (flag && food.x % 2 == 0)
				break;
		}
 
		//绘制食物
		gotoxy(food.x, food.y);
		printf("★");
 
		snake.len++;//吃到食物,蛇身长度加1
		sorce += 10;//每个食物得10分
		snake.speed -= 5;//随着吃的食物越来越多,速度会越来越快
		changeFlag = 1;//很重要,因为吃到了食物,就不用再擦除蛇尾的那一节,以此来造成蛇身体增长的效果
	}
	return;
}

7、打印蛇和覆盖蛇

打印蛇和覆盖蛇这里直接使用一个函数来实现,若传入参数flag为1,则打印蛇;若传入参数为0,则用空格覆盖蛇。
打印蛇:

1.先根据结构体变量snake获取蛇头的坐标,到相应位置打印蛇头。
2.然后根据结构体数组body依次获取蛇身的坐标,到相应位置进行打印即可

覆盖蛇:

1.用空格覆盖最后一段蛇身即可。
但需要注意在覆盖前判断覆盖的位置是否为(0,0)位置,因为当得分后蛇身长度增加,需要覆盖当前的蛇(进而打印长度增加后的蛇),而此时新加蛇身还未进行赋值(编译器一般默认初始化为0),我们根据最后一段蛇身获取到的坐标便是(0,0),则会用空格对(0,0)位置的墙进行覆盖,需要看完后面的移动蛇函数的实现后再进行理解。(也可以先将该判断去掉,观察蛇吃到食物后(0,0)位置墙的变化再进行分析)

//打印蛇与覆盖蛇
void DrawSnake(int flag)
{
	if (flag == 1) //打印蛇
	{
		color(10); //颜色设置为绿色
		CursorJump(2 * snake.x, snake.y);
		printf("■"); //打印蛇头
		for (int i = 0; i < snake.len; i++)
		{
			CursorJump(2 * body[i].x, body[i].y);
			printf("□"); //打印蛇身
		}
	}
	else //覆盖蛇
	{
		if (body[snake.len - 1].x != 0) //防止len++后将(0, 0)位置的墙覆盖
		{
			//将蛇尾覆盖为空格即可
			CursorJump(2 * body[snake.len - 1].x, body[snake.len - 1].y);
			printf("  ");
		}
	}
}

8、移动蛇

移动蛇函数的作用就是先覆盖当前所显示的蛇,然后再打印移动后的蛇。
参数说明:

x:蛇移动后的横坐标相对于当前蛇的横坐标的变化。
y:蛇移动后的纵坐标相对于当前蛇的纵坐标的变化。

蛇移动后,各种信息需要变化:

最后一段蛇身在游戏区当中需要被重新标记为空。蛇头位置在游戏区当中需要被重新标记为蛇身。存储蛇身坐标信息的结构体数组body当中,需要将第i段蛇身的坐标信息更新为第i-1段蛇身的坐标信息,而第0段,即第一段蛇身的坐标信息需要更新为当前蛇头的坐标信息。蛇头的坐标信息需要根据传入的参数x和y,进行重新计算。(以上过程请想象蛇移动的情景)

//移动蛇
void MoveSnake(int x, int y){
    DrawSnake(0); //先覆盖当前所显示的蛇
    face[body[snake.len - 1].y][body[snake.len - 1].x] = KONG; //蛇移动后蛇尾重新标记为空
    face[snake.y][snake.x] = BODY; //蛇移动后蛇头的位置变为蛇身
    //蛇移动后各个蛇身位置坐标需要更新
    for (int i = snake.len - 1; i > 0; i--)
    {
       body[i].x = body[i - 1].x;
       body[i].y = body[i - 1].y;
    }
    //蛇移动后蛇头位置信息变为第0个蛇身的位置信息
    body[0].x = snake.x;
    body[0].y = snake.y;
    //蛇头的位置更改
    snake.x = snake.x + x;
    snake.y = snake.y + y;
    DrawSnake(1); //打印移动后的蛇
}

9、蛇的运动

void run(char map[ROW_MAX][LINE_MAX], int snake[ROW_MAX][LINE_MAX]){
/*
上   -32 0xffffffe0 72 H
下   -32 0xffffffe0 80 P
左   -32 0xffffffe0 75 K
右   -32 0xffffffe0 77 M
*/
    char sh, ch;
    while(1){

        if(JudgeWall()){
        /**********判断键盘是否敲击***********/
            if (kbhit()){

                ch = getch();
                if (ch == -32){

                    sh = getch();
                    switch (sh){

                        case 'H': direct = 'w'; break;
                        case 'P': direct = 's'; break;
                        case 'K': direct = 'a'; break;
                        case 'M': direct = 'd'; break;                  
                    }
                }
                else{                   
                    switch (ch){

                    case 'w':case 'W': direct = 'w'; break;
                    case 's':case 'S': direct = 's'; break;
                    case 'a':case 'A': direct = 'a'; break;
                    case 'd':case 'D': direct = 'd'; break;

                    }
                }               
            }
        /************************************/

        /**************蛇的运动******************/
            switch (direct){

                case 'w':
                    if(snake[Head_x-1][Head_y] != 0)
                        return;

                    snake[Head_x-1][Head_y] = ++Head_v;
                    Head_x--;
                    if(EatFood(map))
                        MoveTail(snake);
                    else
                        CreateFood(map, snake);
                    break;
                case 'a':
                    if(snake[Head_x][Head_y-1] != 0)
                        return;

                    snake[Head_x][Head_y-1] = ++Head_v;
                    Head_y--;
                    if(EatFood(map))
                        MoveTail(snake);
                    else
                        CreateFood(map, snake);
                    break;
                case 's':
                    if(snake[Head_x+1][Head_y] != 0)
                        return;

                    snake[Head_x+1][Head_y] = ++Head_v;
                    Head_x++;
                    if(EatFood(map))
                        MoveTail(snake);
                    else
                        CreateFood(map, snake);
                    break;                  
                case 'd': 
                    if(snake[Head_x][Head_y+1] != 0)
                        return;

                    snake[Head_x][Head_y+1] = ++Head_v;
                    Head_y++;
                    if(EatFood(map))
                        MoveTail(snake);
                    else
                        CreateFood(map, snake);
                    break;          
            }
            system("cls");
            TraverseMap(map, snake);
        /****************************************/
        }
        else
            return;
    }
}

分两部分解释
解释: 键盘是否被敲击, 运用到了 kbhit() 这个函数, 如果被敲击, 则返回1, 没有则返回0.
以及还需要注意一点的是, 上下左右是组合键, 需要用到文中代码的格式,

      ch = getch();
                if (ch == -32){

                    sh = getch();
                    switch (sh){

                        case 'H': direct = 'w'; break;
                        case 'P': direct = 's'; break;
                        case 'K': direct = 'a'; break;
                        case 'M': direct = 'd'; break;                  
                    }
                }

上下左右的第一个字节是0xe0即-32; 如果是的话, 则在使用一个getch(); 判断具体什么键.
以及增加了也可以使用wasd控制方向. 如果不是-32的话, 就进入这块的代码;
并将direct 赋值 为那一方向的值;

解释:蛇的运动: 通过direct的值判断 蛇的走向;
if(snake[Head_x-1][Head_y] != 0) return;
这代码是判断是否咬到自己的身体(以及下三个方向同理).
更新即将要到 snake[Head_x-1][Head_y] 位置的值; 并更蛇脑袋的新坐标 Head_x–;
接下来就是, 看有没有吃到食物, 没有则移动尾部, 吃到则不移动位置, 并在创建一个新的食物坐标;
最后就是清屏打印移动一个位置的蛇;

10、判断得分与结束

判断得分:
若蛇头即将到达的位置是食物,则得分。得分后需要将蛇身加长,并且更新当前得分,除此之外,还需要重新生成食物。

判断结束:
若蛇头即将到达的位置是墙或者蛇身,则游戏结束。游戏结束后比较本局得分和历史最高得分,给出相应的提示语句,并且询问玩家是否再来一局,可自由发挥。

//判断得分与结束
void JudgeFunc(int x, int y)
{
	//若蛇头即将到达的位置是食物,则得分
	if (face[snake.y + y][snake.x + x] == FOOD)
	{
		snake.len++; //蛇身加长
		grade += 10; //更新当前得分
		color(7); //颜色设置为白色
		CursorJump(0, ROW);
		printf("当前得分:%d", grade); //重新打印当前得分
		RandFood(); //重新随机生成食物
	}
	//若蛇头即将到达的位置是墙或者蛇身,则游戏结束
	else if (face[snake.y + y][snake.x + x] == WALL || face[snake.y + y][snake.x + x] == BODY)
	{
		Sleep(1000); //留给玩家反应时间
		system("cls"); //清空屏幕
		color(7); //颜色设置为白色
		CursorJump(2 * (COL / 3), ROW / 2 - 3);
		if (grade > max)
		{
			printf("恭喜你打破最高记录,最高记录更新为%d", grade);
			WriteGrade();
		}
		else if (grade == max)
		{
			printf("与最高记录持平,加油再创佳绩", grade);
		}
		else
		{
			printf("请继续加油,当前与最高记录相差%d", max - grade);
		}
		CursorJump(2 * (COL / 3), ROW / 2);
		printf("GAME OVER");
		while (1) //询问玩家是否再来一局
		{
			char ch;
			CursorJump(2 * (COL / 3), ROW / 2 + 3);
			printf("再来一局?(y/n):");
			scanf("%c", &ch);
			if (ch == 'y' || ch == 'Y')
			{
				system("cls");
				main();
			}
			else if (ch == 'n' || ch == 'N')
			{
				CursorJump(2 * (COL / 3), ROW / 2 + 5);
				exit(0);
			}
			else
			{
				CursorJump(2 * (COL / 3), ROW / 2 + 5);
				printf("选择错误,请再次选择");
			}
		}
	}
}

注意: 若本局得分大于历史最高得分,需要更新最高分到文件。
首先使用fopen函数打开“贪吃蛇最高得分记录.txt”,然后将本局游戏的分数grade写入文件当中即可(覆盖式)。

//更新最高分到文件
void WriteGrade()
{
    FILE* pf = fopen("贪吃蛇最高得分记录.txt", "w"); //以只写的方式打开文件
    if (pf == NULL) //打开文件失败
    {
       printf("保存最高得分记录失败\n");
       exit(0);
    }
    fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中
    fclose(pf); //关闭文件
    pf = NULL; //文件指针及时置空
}

四、源代码

# include <stdio.h>
# include <string.h>
# include <windows.h>
# include <stdlib.h>
# include <conio.h>
# include <time.h>
# include <math.h>

# define de_lenth 5//蛇初始长度
# define ROW_MAX 20//地图行
# define LINE_MAX 30//地图列

int ROW = ROW_MAX;
int LINE = LINE_MAX;
int Head_x = ROW_MAX/2, Head_y = LINE_MAX/2;//蛇的初始坐标
int Head_v = 5;//判断是否为头
int Tail_x = 0, Tail_y = 0;//蛇尾坐标
int count = 0;//计数
char direct = 'a';//蛇移动的方向
int times = 1;

void SetUp(void);
void CreateMap(char map[ROW_MAX][LINE_MAX]);//创建地图
void CreateSnake(int snkae[ROW_MAX][LINE_MAX]);//构造蛇的初始位置
void CreateFood(char map[ROW_MAX][LINE_MAX], int snake[ROW_MAX][LINE_MAX]);//产生随机食物
void TraverseMap(char map[ROW_MAX][LINE_MAX], int snake[ROW_MAX][LINE_MAX]);//输出地图以及蛇的全部
int JudgeWall(void);//判断蛇是否撞墙
void MoveTail(int snake[ROW_MAX][LINE_MAX]);//蛇尾部的移动
int EatFood(char map[ROW_MAX][LINE_MAX]);//蛇吃到食物的操作
void run(char map[ROW_MAX][LINE_MAX], int snake[ROW_MAX][LINE_MAX]);//蛇的移动
void gotoxy(int x, int y);//将光标定位到某一位置, 用于显示结果, 以及暂停是使用
void Result(void);//显示结果
void dely(void);

int main()
{
    char map[ROW_MAX][LINE_MAX];
    int snake[ROW_MAX][LINE_MAX];
    char ch;
    srand(time(NULL));
/************设置*********************/   
    system("cls");
    printf("\tPress T/t to set up\n");
    printf("Press any other key to start!\n");
    ch = getch();

    if(ch == 'T' || ch == 't')
        SetUp();
/*************************************/

/**************初始画面**************/
    memset(map, 0, sizeof(map));
    memset(snake, 0, sizeof(snake));
    system("cls");
    CreateMap(map); 
//  gotoxy(1, 1);
    CreateSnake(snake);
    CreateFood(map, snake);
    TraverseMap(map, snake);
/**************初始画面**************/

/**********运动*****************/
    run(map, snake);
/*******************************/

/*************结果***************/
    Result();
/********************************/
    return 0;
}

void SetUp(void){

    char ch;
    int row;
    int line;
    int speed;
    system("cls");
    printf("\tSET UP\n");
    printf("1. speed\n");
    printf("2. area\n");
    printf("0. return\n");

    ch = getch();
    while(1){
        switch (ch){

        case '1':
            printf("The less the figure the faster speed level(1~8)\n");
            printf("Speed level: ");
            scanf("%d", &speed);
            times = speed>8? 8: speed<1? 1: speed;
            printf("The speed leve: %ld", times);
            break;
        case '2':
            printf("Update length and width(the max size is 80 x 80 and input 0 default)\n");
            printf("length: ");
            scanf("%d", &row);
            printf("width: ");
            scanf("%d", &line);

            if(line != 0 && row != 0){

                ROW = row+2;
                LINE = line+2;
                Head_x = ROW/2;
                Head_y = LINE/2;
            }
            printf("After updating length: %d\twidth: %d\n", row, line);

            break;

            default:
                return;
        }
        for(long i = 0; i < 400000000; ++i);
        //fflush(stdin);
        system("cls");
        printf("\tSET UP\n");
        printf("1. speed\n");
        printf("2. area\n");
        printf("0. return\n");

        ch = getch();
    }

}

void CreateMap(char map[ROW_MAX][LINE_MAX]){
//将行为0和ROW_MAX的位置赋值为'#', 以及列为0和LINE_MAX赋值为'#', 表示墙;
    int i, j;


    for(i = 0; i < ROW; ++i)
        map[i][0] = '#';
    for(j = 1; j < LINE; ++j)
        map[0][j] = '#';
    for(i = ROW-1, j = 1; j < LINE; ++j)
        map[i][j] = '#';
    for(j = LINE-1, i = 1; i < ROW; ++i)
        map[i][j] = '#';
}

void CreateSnake(int snake[ROW_MAX][LINE_MAX]){

    int i, j;
    int value = Head_v;
    snake[Head_x][Head_y] = value;

    for(i = Head_x, j = Head_y+1; j < Head_y + de_lenth; ++j)
        snake[i][j] = --value;
    Tail_x = i;
    Tail_y = --j;
}

void CreateFood(char map[ROW_MAX][LINE_MAX], int snake[ROW_MAX][LINE_MAX]){

    int food_x = 0;
    int food_y = 0;

    while (map[food_x][food_y] == '#' || snake[food_x][food_y] != 0){

        food_x = rand()%(ROW-3) + 1;
        food_y = rand()%(LINE-3) + 1;
    }

    map[food_x][food_y] = 'O';
}

void TraverseMap(char map[ROW_MAX][LINE_MAX], int snake[ROW_MAX][LINE_MAX]){

    int i, j;

    for (i = 0; i < ROW; ++i){
        for (j = 0; j < LINE; ++j){
            if(snake[i][j] == 0)
                printf("%c", map[i][j]);
            else{
                if(snake[i][j] == Head_v)
                    putchar('Q');
                else
                    putchar('a');               
            }
        }
        putchar('\n');
    }
}

int JudgeWall(void){

    if((Head_x == 0 || Head_x == ROW-1) || (Head_y == 0 || Head_y == LINE-1))
        return 0;
    else 
        return 1;
}



void MoveTail(int snake[ROW_MAX][LINE_MAX]){

    if (snake[Tail_x][Tail_y]+1 == snake[Tail_x-1][Tail_y]){

        snake[Tail_x][Tail_y] = 0;
        Tail_x--;
    }
    else if (snake[Tail_x][Tail_y]+1 == snake[Tail_x+1][Tail_y]){

        snake[Tail_x][Tail_y] = 0;
        Tail_x++;
    }
    else if (snake[Tail_x][Tail_y]+1 == snake[Tail_x][Tail_y-1]){

        snake[Tail_x][Tail_y] = 0;
        Tail_y--;
    }
    else{

        snake[Tail_x][Tail_y] = 0;
        Tail_y++;
    }
}

int EatFood(char map[ROW_MAX][LINE_MAX]){

    if(map[Head_x][Head_y] == 'O'){

        map[Head_x][Head_y] = 0;
        count++;
        return 0;
    }
    else
        return 1;
}

void run(char map[ROW_MAX][LINE_MAX], int snake[ROW_MAX][LINE_MAX]){
/*
上   -32 0xffffffe0 72 H
下   -32 0xffffffe0 80 P
左   -32 0xffffffe0 75 K
右   -32 0xffffffe0 77 M
*/
    char sh, ch;
    while(1){

        if(JudgeWall()){
        /**********判断键盘是否敲击***********/
            if (kbhit()){

                ch = getch();
                if (ch == -32){

                    sh = getch();
                    if(direct == 'w'){

                        if(sh == 'P')
                            continue;
                    }
                    else if (direct == 's'){

                        if(sh == 'H')
                            continue;
                    }
                    else if (direct == 'a'){

                        if(sh == 'M')
                            continue;
                    }
                    else{

                        if(sh == 'K')
                            continue;
                    }

                    switch (sh){

                        case 'H': direct = 'w'; break;
                        case 'P': direct = 's'; break;
                        case 'K': direct = 'a'; break;
                        case 'M': direct = 'd'; break;              
                    }
                }
                else{

                    if(direct == 'w'){

                        if(ch == 's' || ch == 'S')
                            continue;
                    }
                    else if (direct == 's'){

                        if(ch == 'w' || ch == 'W')
                            continue;
                    }
                    else if (direct == 'a'){

                        if(ch == 'd' || ch == 'D')
                            continue;
                    }
                    else if (direct == 'd'){

                        if(ch == 'a' || ch == 'A')
                            continue;
                    }

                    switch (ch){

                    case 'w':case 'W': direct = 'w'; break;
                    case 's':case 'S': direct = 's'; break;
                    case 'a':case 'A': direct = 'a'; break;
                    case 'd':case 'D': direct = 'd'; break;
                    case 27:
                        int i, j;
                        for(i = ROW/2-1; i <= ROW/2+1; ++i){
                                gotoxy(0, i);
                                for(j = 0; j <= LINE; ++j)
                                    putchar(' ');
                        }

                        gotoxy(LINE/2-8<0?0:LINE/2-8, ROW/2);
                        system("pause");

                        break;

                    }
                }               
            }
        /************************************/

        /**************蛇的运动******************/
            switch (direct){

                case 'w':
                    if(snake[Head_x-1][Head_y] != 0)
                        return;

                    snake[Head_x-1][Head_y] = ++Head_v;
                    Head_x--;
                    if(EatFood(map))
                        MoveTail(snake);
                    else
                        CreateFood(map, snake);
                    break;
                case 'a':
                    if(snake[Head_x][Head_y-1] != 0)
                        return;

                    snake[Head_x][Head_y-1] = ++Head_v;
                    Head_y--;
                    if(EatFood(map))
                        MoveTail(snake);
                    else
                        CreateFood(map, snake);
                    break;
                case 's':
                    if(snake[Head_x+1][Head_y] != 0)
                        return;

                    snake[Head_x+1][Head_y] = ++Head_v;
                    Head_x++;
                    if(EatFood(map))
                        MoveTail(snake);
                    else
                        CreateFood(map, snake);
                    break;                  
                case 'd': 
                    if(snake[Head_x][Head_y+1] != 0)
                        return;

                    snake[Head_x][Head_y+1] = ++Head_v;
                    Head_y++;
                    if(EatFood(map))
                        MoveTail(snake);
                    else
                        CreateFood(map, snake);
                    break;          
            }
            system("cls");
            TraverseMap(map, snake);
        /****************************************/
        }
        else
            return;
        dely();
    }
}



void gotoxy(int x, int y)
{
    CONSOLE_SCREEN_BUFFER_INFO    csbiInfo;                            
    HANDLE    hConsoleOut;
    hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleScreenBufferInfo(hConsoleOut,&csbiInfo);
    csbiInfo.dwCursorPosition.X = x;                                    
    csbiInfo.dwCursorPosition.Y = y;                                    
    SetConsoleCursorPosition(hConsoleOut,csbiInfo.dwCursorPosition); 
}

void Result(void){
    int i, j;
    for(i = ROW/2-2; i <= ROW/2+1; ++i){
        gotoxy(0, i);
        for(j = 0; j <= LINE; ++j)
            putchar(' ');
    }
    gotoxy(LINE/2-4<0?0:LINE/2-4, ROW/2-1);
    printf("The end!\n");
    gotoxy(LINE/2-8<0?0:LINE/2-8, ROW/2);
    printf("Your score is %d\n", count);
    gotoxy(0,ROW);
    printf("Press any key to quit\n");
    printf("Thank you!\n");
    getch();
}

void dely (void){

    for(long i = 0; i <= pow(16, times); ++i)
        ;
}

总结

以上就是我对贪吃蛇小游戏的理解,以及通过这个程序的编程实现,对于图形化操作界面、数组的深入、游戏逻辑的判断等等都有了更深入的了解,对于编程的提高有很大的帮助,看似复杂的程序, 实则思路理解了, 也变的简单化了。如果有需要改进的地方,还请各位大佬指点一下!

  • 58
    点赞
  • 369
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
C语言是一种高效、底层的编程语言,虽然相对于其他语言来说,使用C语言开发3D游戏网盘可能会更复杂,但也是完全可行的。 首先,为了实现3D效果,我们需要使用类似OpenGL或者DirectX这样的图形API,它们提供了强大的图形渲染功能。C语言可以通过调用这些API来实现图形渲染,并处理用户输入、碰撞检测等功能。我们可以使用C语言的指针操作和底层算法来控制运行效率,并且可以利用C语言的内存管理功能,避免内存泄漏和资源浪费问题。 其次,对于网络功能,C语言提供了底层的套接字(Socket)库,我们可以使用该库来实现网络通信。通过套接字,我们可以在服务器和客户端之间传递数据,实现用户在网盘中上传、下载和分享文件的功能。C语言的底层特性使我们可以自定义网络通信协议和数据包格式,提高传输效率。 在开发过程中,我们可以使用C语言的模块化编程思想,将不同的功能模块拆分,并设计适当的接口,以提高代码的复用性和可维护性。同时,C语言还可以通过使用宏、预处理指令等特性实现代码的优化,提高性能。 然而,由于C语言的底层特性,相比其他高级语言,它需要更多的编码工作、对硬件的了解以及对内存管理的谨慎使用。此外,对于3D游戏网盘这样的大型项目,团队合作和代码管理也非常重要。 总的来说,尽管使用C语言开发3D游戏网盘相对复杂,但在充分利用C语言的特性、借助合适的库以及良好的团队协作下,完全可以实现一个高效、功能强大的3D游戏网盘。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Starry灬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值