C语言项目@我的贪吃蛇


还记得有一种手机叫做诺基亚,然后上面有一个小游戏,叫做贪吃蛇。简单吗?复杂吗?只知道那个一扭一扭的丑陋的蛇还挺有意思的。最无语的就是积分系统了,排行榜完全是抓住人的求胜心。我和我表弟相互挑战,简陋却也带来无限快乐。

哈哈,巴拉巴拉一堆,终于可以讲一讲自己是怎么用c语言实现贪吃蛇。

一、选择框架

每一个好的想法都得需要一个合适的平台。同样平台也决定了你会有怎样的上下限。
例如,python的pygame就很强,实现的贪吃蛇就会更美观点。当然c语言的话。我使用的是turbo C(意思是涡轮增压后的C)。似不似很强力?可惜我用的是2.0版本(1989年),不支持鼠标操作。都0202年了,不能用鼠标玩电脑,也是个脑力活。

注意点:
1.turbo C 中int是两字节。
2.个人写的.h文件需要放出D:\DISK_C\TC20\INCLUDE 文件夹里。
3.由于运行过快,需要使用system(“pause”); 语句或者getchar(); 语句来接收下一个用户命令。此时才能看到自己的输出。但是还得提前使用clrscr();语句用来清空屏幕。需要头文件stdlib.h。
任务要求: 在turbo c 上实现hello world 和 简单排序。

二、输出与输入

1.输出

大家觉得制作游戏第一步干嘛?是确定核心玩法?还是剧情感人?这些都很重要,但都绕不开显示(即输出)。关了门,不给别人打开一扇窗户,永远不会有人走进你的世界。

贪吃蛇,贪吃蛇,起码得显示是个蛇吧。来看看我的snake!

在这里插入图片描述
呸呸呸,我的蛇怎么可能这么花里胡哨(美丽动人)。你看我的蛇,又长又灵活,还带着指导员。
在这里插入图片描述
很简陋,但确实是个人自己实现的。我希望初学者一样可以通过自己实现这个游戏。因为复制再多的代码也不能得到别人的经历。所以我更倾向于在本篇文章中讲思路,讲自己的经验。代码不难,学习不易。

这里使用的是gotoxy(int ,int)函数。在这个显示器是25 * 80的小小屏幕上,(从一开始)。使用这个函数就能将光标指哪打哪。但是得注意第一参数是列坐标,也就是xy坐标中的y坐标。到了位置之后,就可以让那里显示东西了。例如printf()函数输出“ * ”号。代表蛇身。如此如此,我们就实现了输出这一难关。

2.输入

不讨论输入输出谁更关键。实现才是最重要的。
我喜欢输入,因为我感受到自己的想法的实现。
这里我们就得介绍bioskey()函数。简单来说,这个函数就是实现用户控制。当用户输入上下左右是,屏幕会显示蛇向相应方向走动。这是我认为的交互式输入输出。
具体关于bioskey(),可以看我的另一篇文章。
链接:bioskey()函数使用

要点
1.输出得掌握好gotoxy()函数,记住第一个实参是列坐标,第二个是行指标。
任务: 在屏幕指定16行,24列处输出&号。
2.输入得掌握好bioskey()函数。
任务: 掌握bioskey()的参数0、1和2的不同作用。

三、蛇移动实现

什么是动画?还记将不同画像快速翻阅带来的视觉动态吗?眼睛有时候传递的信息,是错误的。

1.一个点的移动

1)单方向移动

顾名思义:就是一个“ * ”号在一行屏幕移动。
这个不难。主要由两个步骤:
1)显示移动后的星号。
2)将原先显示的星号删除。
关键代码就是:行坐标的增加或者减少。

2)多方向移动

想要星号向上,向下,想左,向右移动,这是就需要键盘输入控制了。
我们知道,
在这里插入图片描述
一般的想法是当我们得到输入时,通过得到的值再将星号的行、列坐标改变。但是通过图片我们发现规律。可以将行、列变化值写成一个数据类型是结构体的数组。如下:

typedef struct DEL_POSITION {
	int deltaRow;//行改变值
	int deltaCol;//列改变值
	char snakeHand;//蛇头形状
}DEL_POSITION;//方向

使用时:

	DEL_POSITION delPos[4] = {
		{-1, 0, '^'},//up--0
		{1, 0, 'v'},//down--1
		{0, 1, '>'},//right--2
		{0, -1,'<'},//left--3
	};

使用bioskey()函数;

#define   UP          		0x4800
#define   DOWN        		0x5000
#define   LEFT        		0x4b00
#define   RIGHT       		0x4d00
#define   ESC         		0x011B

在这里插入图片描述这里只要将bioskey函数使用好,准确得到键盘输入,配合坐标的变化和移动的实现就能实现多方向移动。

2.定长蛇的移动

这时候一定要明白,有关蛇头蛇身体的关键性数据。例如蛇长,蛇移动方向等等。
我是这样定义的:(有一些数据暂时可用不到,可以先跳过)

typedef struct SNAKE_BODY {
	u8 col;//蛇身体列坐标
	u8 row;//蛇身体行坐标
}SNAKE_BODY;

typedef struct SNAKE {
	boolean finished;//结束判断
	boolean died;//死亡判断
	int head;//指向蛇头的指针
    int tail;//指向蛇尾的指针
    SNAKE_BODY body[MAX_COUNT];//蛇身数量
    u8 directe;//蛇行进方向
    double time;//控制蛇速度
    u8 realSnakeLen;//真正的蛇长
    u8 curSnakeLen;//当前的蛇长
    FOOD food[1];//生成的食物
}SNAKE;//蛇的控制元素

首先说一说到底用什么来表示定长蛇。一般有两种想法。
1)地图法: 将整个屏幕看成一个有行列坐标的二维数组。没有蛇身存在的点都是0,有蛇身存在的是1,在扫描数组,就为1的地方显示星号,那么就可以将这条贪吃蛇 “画出来
2)蛇身数据法: 一般采用将蛇身的行列坐标用数组保存下来。每次“画蛇”是根据蛇身数组将蛇画出来。
明白了蛇身的表示,那么要模拟出蛇的移动。重点来了
蛇是如何移动的?
蛇的移动从蛇身整体数据来说,只做了三个步骤。
第一:蛇头移动一格,得到新的蛇头数据。画出蛇头的方向感。
第二:旧的蛇头数据处画出星号。
第三:将旧的蛇尾销毁。
如图:在这里插入图片描述
所以如果每次只关注:旧蛇头、新蛇头和旧蛇尾位置,就可以将蛇的移动展现出来,而且不用管其他蛇身数据。
同时,问题来了:采用普通数组的话,需要删除和复制蛇身数据,很是麻烦。而且蛇的轨迹都被记录下来的话,数组长度得很长很长。
所以对于定长蛇,采用的是循环数组。具体知识暂无。
基本步骤如下:
1)在数组空间中,当蛇移动后,蛇头指针、蛇尾指针自动加一,蛇长不变。
2)当头尾指针遇到最大空间值时,又从下标为0的空间开始。
实现:head = (head + 1) % MAX_COUNT;
蛇的显示是二维化的,但是蛇的数据是一维的。我称为蛇轨迹一维化
图片说明:上面的一维数组,head是蛇头指针,tail为蛇尾指针。空的是轨迹但也是垃圾数据。
在这里插入图片描述这样做的好处很多:
1)数组空间循环使用,不用申请太大空间。
2)当定长蛇增长的,只要不超过申请空间,都可以显示下来。例如我申请200空间,那么玩家需要将蛇长到200.
3)使用头尾指针可以配合循环数组特点。又可以更具头尾指针得到当前蛇的数据。

3.增长蛇的移动

先将食物的产生放一放,怎么使定长蛇增长?
其实也不难。
在这里插入图片描述
就相当于定长蛇的移动后,旧蛇尾不要删除。注意:此时蛇尾指针依然指向旧蛇尾处
所以到此,蛇的移动就实现了。等等,似乎还有一个知识点:
蛇刚产生时,假设蛇长是3,我们从蛇头开始,一个点。如何实现一个点(盘起来的蛇)变成一个舒展开的蛇?
这是就用上了: u8是unsigned char类型
u8 realSnakeLen;//真正的蛇长
u8 curSnakeLen;//当前的蛇长
效果是:
在输出前加一个判断,如果当前的蛇长小于真正的蛇长,那么就不删除蛇尾。
如果当前的蛇长大于等于真正蛇长,删除蛇尾。
这时候,如果蛇吃了食物,那么真正蛇长就会加一。从而不会通过判断,不删除蛇尾。

4.移动速度

double time;//控制蛇速度
由于程序运行是神快的,所以我们在游戏循环中加入一个限制时间,

	while (!snake.finished && !snake.died) { //不死也不结束
		delayTime ++;//延迟时间
		snake.time = LIMITETIME;//限制的时间

		if (delayTime >= snake.time) {
		//只有当延迟时间到达限制时间时,小蛇才移动
		...}

同时,我们通过修改限制时间,就可以修改小蛇的速度。而且可以根据用户输入来修改小蛇速度。这个不难。

四、食物的生成

1.随机生成

C语言一般都会知道,随机数的生成。但是由于随机种子的问题,其实这样就形成了伪随机。而且,食物被吃后,那么该食物需要被清理。所以最初想法是:

		snake->food[0].row = rand() % 25 + 1;//1--25范围
		snake->food[0].col = rand() % 80 + 1;//1--80范围
		draw(snake->food[0].row, snake->food[0].col);//生成
		snake->food[0].eat = FALSE;//标记:未吃

但是,怎么说吧,如果可以实现随机的话,为啥用伪随机?

2.洗牌算法

洗牌算法简介的话,暂无;
使用洗牌算法就是产生一个随机数池子。每一个数出现在这个随机数池子的概率都相等。
同时,将80*25的屏幕的行列坐标值,看成一个数值,即从0–1999;那么每一数值,可以转化为一个行列坐标。
如:1 => row = 1;col = 2;
25 => row = 2; col = 1;
所以通过洗牌算法生成得随机数,是随机的。再将之转变为随机食物坐标。那么生成得食物就是随机的。

t为随机数
		snake->food[0].row = t / 80 + 1;//转化的行坐标 
		snake->food[0].col = t % 80 + 1;//转化的列坐标 
		draw(snake->food[0].row, snake->food[0].col);//生成
		snake->food[0].eat = FALSE;//标记:未吃

目前视频仅支持三种,上传不了o(╥﹏╥)o。
不过我确实没实现太多功能。不够美观。

五、总结

1.项目总结:

这不是一个完善的贪吃蛇游戏,界面,游戏体验都不好。简陋。但却是个人第一个自制小游戏。()

2.收获总结

  • 1)想法多一点,行动才会更快速。
    过程中有不少想到了就去做,然后发现,这一步完成,下一步呢?
    2)退一步,海阔天空。
    刚开始,很是珍惜自己代码。但是实在编不下去了,比如到多个点一起移动。重新删除代码。从头开始时,思考的余地更大了。
    3)见识要广,长。
    不会没关系,不是最佳没影响。但是不能不继续学习。

笔者水平有限,目前只能描述以上问题,如果有其他情况,可以留言,有错误,请指教,有继续优化的,请分享,谢谢!
完整代码如下:
snake.h

#ifndef  _STICKER_SNAKE_H
#define  _STICKER_SNAKE_H

typedef unsigned char   boolean;
typedef boolean  u8;

typedef struct DEL_POSITION {
	int deltaRow;/*行改变值*/
	int deltaCol;/*列改变值*/
	char snakeHand;/*蛇头形状*/
}DEL_POSITION;/*方向*/

#define MAX_COUNT  100
#define NUM_COUNT  2000 /*25 * 80*/

#define   TRUE    1
#define   FALSE   0
#define   LIMITETIME     3000
#define   TIME           1000

#define   UP          		0x4800
#define   DOWN        		0x5000
#define   LEFT        		0x4b00
#define   RIGHT       		0x4d00
#define   ESC         		0x011B

#define   UPSPEED	  		0x4f31
#define   LOWSPEED    		0x5032
#define   RETURNSPEED		0x5133

#define   YES               0x1579
#define   NO                0x316E

typedef struct SNAKE_BODY {
	u8 col;/*蛇身体列坐标*/
	u8 row;/*蛇身体行坐标*/
}SNAKE_BODY;

typedef struct FOOD {
	u8 col;
	u8 row;
	u8 eat;
}FOOD;

typedef struct SNAKE {
	boolean finished;/*结束判断*/
	boolean died;/*死亡判断*/
	int head;/*指向蛇头的指针*/
    int tail;/*指向蛇尾的指针*/
    SNAKE_BODY body[MAX_COUNT];/*蛇身数量*/
    u8 directe;/*蛇行进方向*/
    double time;/*控制蛇速度*/
    u8 realSnakeLen;/*真正的蛇长*/
    u8 curSnakeLen;/*当前的蛇长*/
    FOOD food[1];/*生成的食物*/
}SNAKE;
#endif

snake.c

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

#include "SNAKE.H"

void draw(u8 row, u8 col);
void getEvent(u8 oldEvent, SNAKE *snake);
void startGame();
void move(SNAKE *snake);
void isEnd(SNAKE *snake);
void dealEnd(SNAKE *snake);
void initFood(SNAKE *snake);
void eatFood(SNAKE *snake);
int creatRandNum();
/*洗牌出随机数*/
int creatRandNum() {
	int numPool[NUM_COUNT];
	int t;
	int temp;
	int count = NUM_COUNT;
	static boolean isCreatFoodPool = FALSE;
	static int food = 0;
	/*洗牌算法*/
	if (isCreatFoodPool == FALSE) {
		for (temp = 0; temp < count; temp++) {
			numPool[temp] = temp;
		}	
		 for (; count > 1; count--) {
			srand(time(NULL));
			t = rand() % count;
			temp = numPool[t];
			numPool[t] = numPool[count - 1];
			numPool[count - 1] = temp;
		}
		isCreatFoodPool = TRUE;	
	}
	return numPool[food++];
}
/*食物是否被吃?*/
void eatFood(SNAKE *snake) {
	if (snake->body[snake->head].row == snake->food[0].row 
		&& snake->body[snake->head].col == snake->food[0].col) {
		snake->realSnakeLen++;
		snake->food[0].eat = TRUE;
	}else {
		snake->food[0].eat = FALSE;
	}
}
/*生成食物*/
void initFood(SNAKE *snake) {
	int t;
	int i;
	if (TRUE == snake->food[0].eat) {
		t = creatRandNum();
		for (i = snake->tail;(i % MAX_COUNT) != snake->head; i++) {
			if ((snake->body[snake->tail].row - 1) * 80 + snake->body[snake->tail].col - 1 == t 
				|| (snake->body[snake->head].row - 1) * 80 + snake->body[snake->head].col - 1 == t) {
				t = creatRandNum();/*如果食物产生在蛇身上,重新产生*/
			}
		}	
		snake->food[0].row = t / 80 + 1;
		snake->food[0].col = t % 80 + 1;
		draw(snake->food[0].row, snake->food[0].col);
		snake->food[0].eat = FALSE;
	}
}
/*解决死亡情况*/
void dealEnd(SNAKE *snake) {
	if (TRUE == snake->died) {
		clrscr();
		gotoxy(32, 12);
		printf("you are died!");
	}
	if (TRUE == snake->finished) {
		clrscr();
		gotoxy(32, 12);
		printf("goodbye!");
	}
}
/*解决结束情况*/
void isEnd(SNAKE *snake) {
	int i;
	if (snake->body[snake->head].row < 1 || snake->body[snake->head].row > 25 
		|| snake->body[snake->head].col < 1 || snake->body[snake->head].col > 80) {
		snake->died = TRUE;
	}
	/*erdogicSnake(snake);*/
	for (i = snake->tail ; (i % MAX_COUNT) != snake->head; i++) {
		if (snake->body[snake->head].row == snake->body[i].row 
		&& snake->body[snake->head].col == snake->body[i].col) {
			snake->died = TRUE;
		}
	}
}
/*移动*/
void move(SNAKE *snake) {
	DEL_POSITION delPos[4] = {
		{-1, 0, '^'},/*up*/
		{1, 0, 'v'},/*down*/
		{0, 1, '>'},/*right*/
		{0, -1,'<'},/*left*/
	};

	if (snake->curSnakeLen >= snake->realSnakeLen) {
		gotoxy(snake->body[snake->tail].col, snake->body[snake->tail].row);
		printf(" ");
		snake->tail = (snake->tail + 1) % MAX_COUNT;
	}else {
		snake->curSnakeLen++;
	}
	draw(snake->body[snake->head].row, snake->body[snake->head].col);/*旧蛇头变星号*/

	snake->body[((snake->head + 1) % MAX_COUNT)].row = snake->body[snake->head].row + delPos[snake->directe].deltaRow;/*生成新蛇头行坐标*/
	snake->body[((snake->head + 1) % MAX_COUNT)].col = snake->body[snake->head].col + delPos[snake->directe].deltaCol;/*生成新蛇头列坐标*/

	snake->head = (snake->head + 1) % MAX_COUNT;

	gotoxy(snake->body[snake->head].col, snake->body[snake->head].row);
	printf("%c", delPos[snake->directe].snakeHand);/*画出新蛇头*/

}
/*开始游戏*/
void startGame() {
	SNAKE snake = {
		FALSE, FALSE, 0, 0, { {10,10} }, 2, LIMITETIME, 3, 1, {0, 0, TRUE},
	};
	u8 oldEvent;
	double delayTime = 0;

	draw(snake.body[snake.head].row, snake.body[snake.head].col);
	initFood(&snake);

	while (!snake.finished && !snake.died) {
		delayTime ++;
		snake.time = LIMITETIME;

		if (delayTime >= snake.time) {
			if (bioskey(1) != 0) {
				oldEvent = snake.directe;
				getEvent(oldEvent, &snake);/*处理键盘输入*/
			}
			eatFood(&snake);
			if (TRUE == snake.food[0].eat) {
				initFood(&snake);/*处理是否吃食物*/
			}
			move(&snake);	
			delayTime = 0;
		}
		isEnd(&snake);
	}
	dealEnd(&snake);
}
/*键盘输入*/
void getEvent(u8 oldEvent, SNAKE *snake) {
	double newEvent;/*新的控制*/

	newEvent = bioskey(0);/*接受键盘输入*/
	if (UP == newEvent && oldEvent != 1) {
		snake->directe = 0;/*向上*/
	}else if (DOWN == newEvent && oldEvent != 0) {
		snake->directe = 1;/*向下*/
	}else if (RIGHT == newEvent && oldEvent != 3) {
		snake->directe = 2;/*向右*/
	}else if (LEFT == newEvent && oldEvent != 2) {
		snake->directe = 3;/*向左*/
	}else if (ESC == newEvent) {
		snake->finished = TRUE;/*退出*/
	}else if (UPSPEED == newEvent) {
		snake->time -= TIME;/*加速*/
	}else if (LOWSPEED == newEvent) {
		snake->time += TIME;/*减速*/
	}else if (RETURNSPEED == newEvent) {
		snake->time = LIMITETIME;/*恢复正常速度*/
	}else {
	 	snake->directe = oldEvent;/*没有键盘输入,按照原来情况进行*/
	}
}
/*显示*/
void draw(u8 row, u8 col) {
	gotoxy(col ,row);
	printf("*");	
}

int main() {
	clrscr();
	startGame();

	getchar();
	return 0;
}

2020年02.26 家

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值