贪吃蛇@双蛇夺食

贪吃蛇实现

还记得小时候那种按键的游戏机,经常被我拿到学校偷偷玩,上面有个贪吃蛇的游戏。除了坦克大战,和俄罗斯方块就是只剩他了。通过吃食物来增加蛇长。简陋的界面却也承载我小时候的快乐。在接触到计算机语言后,一直希望我可以做出一个贪吃蛇。现在,这个愿望终于实现了。

一、运行环境

使用编译器为turc2.0。这个是DOS系统下的编译器,只支持键盘操作。距离现在已经很早了。(至于我为什么使用他呢?哦!我亲爱的朋友,只有上帝知道!)
值得注意的是:
1.在tc中,int 只有两字节。
2.tc不会清除上一次的运行结果,所以这里需要使用清屏函数clrscr();此函数包含在头文件<stdlib.h>中。

二、实现前需要解决的问题

1.获取用户按键

这里介绍bioskey()函数。
bioskey()函数使用
引用一下别人的详细介绍,我咋这里简单介绍一下具体用法:
键盘的每一个键,都有一个具体的键值与之对应。获取键,其实就是获取键按下时的键值。
bioskey(0):等待用户从键盘输入,将用户输入的键值放入缓冲区,然后将值赋值给某个变量,这个键值从缓冲区中弹出。
bioskey(1):如果没有键按下,函数返回NULL,当有键按下时,将用户输入的键值放入缓冲区,然后将值赋值给某个变量,这个键值仍存在在缓冲区。当下次从缓冲区取值时,仍是这个值,无法获得后面的键值。这是一个很大的麻烦。所以当使用bioskey(1)时,若有键按下时,需要接受这个键值,使他从缓冲区中清除。

	if(bioskey(1)){
		ch = bioskey(1);//得到按键值
		getch();//清楚缓冲区中按下的键值
	}

注意:getch();包含在头文件<conio.h>中。

2.输出蛇

蛇不可能从(0,0)点开始出发,这是不符合日常的认知的。我们需要将光标定位到某一点,从这一点进行输出。
当然我的蛇肯定没有现在贪吃蛇那么花里胡哨,只是很简单的图标。
那现在来介绍gotoxy(int x, int y)函数,此函数的作用就是将光标定位到(x,y)处,然后进行输出。(在tc中可直接调用,需添加头文件<conio.h>)

3.蛇的移动

在蛇的移动中,其实只发生了三件事,头的移动和变化,尾的删除。
在这里插入图片描述
从上图可以清楚的看到,删除尾,将原来的头变为身体,根据方向,将原来的头的后一个坐标变为头。

4.食物的生成

整个屏幕使用地图来进行操作。只在地图中值为0的地方随机生成食物。避免食物生成到墙上和蛇的身体上。

static int map[MAP] = {0};

这里定义食物的结构体。将食物定义为全局变量,方便对食物的管理。

typedef struct FOOD{
	int x;
	int y;
	int exist;//判断屏幕中是否存在食物
}FOOD;

三、代码实现

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

#define UP_KEY      18432
#define DOWM_KEY    20480
#define LEFT_KEY    19200
#define RIGHT_KEY   19712
#define ESC         283
#define	W_KEY       4471
#define	S_KEY       8051
#define A_KEY       7777
#define D_KEY       8292

#define MAP         1840
#define MAX_BODY    200

#define TRUE        1
#define FALSE       0

typedef unsigned char boolean;

typedef struct COORDINATE{
	int x;
	int y;
}COORDINATE;

typedef struct FOOD{
	int x;
	int y;
	int exist;
}FOOD;

typedef struct VARIATION {
	int x_variation;//方向改变时,x的改变量
	int y_variation;//方向改变时,y的改变量
}VARIATION;

typedef struct SNAKE{
	int direction;//此时蛇头方向
	int lastdirection;//上一次蛇头方向
	int head;//指向蛇的头
	int tail;//指向蛇的尾
	int length;//蛇的限制长度
	int real_length;//蛇真正的长度
	char headform;//蛇头形状
	long time;//限制时间
	COORDINATE body[MAX_BODY];//记录蛇身的数组
}SNAKE;

static int map[MAP] = {0};
static FOOD food = {0};

void initSnake();
boolean Gomeover(SNAKE *snake);
void score(SNAKE *snakeone, SNAKE *snakeother);
void snakefood();
void eatfood(SNAKE *snake);
void Direction(SNAKE *snakeone, SNAKE *snakeother);
int judesnake_key(int key);
void judesnake_dir(SNAKE *snakeone, SNAKE *snakeother, int i, int newkey);
void movesnake(SNAKE *snake);
void changesnake(SNAKE *snakeone, SNAKE *snakeother);
int judgesign(SNAKE *snake);
void deltail(int x, int y);
void draw(int x, int y);
void speed(SNAKE *snake);
void GameStare();
void snakescore(SNAKE *snakeone, SNAKE *snakeother);

//游戏结束时输出两条蛇的得分。
void snakescore(SNAKE *snakeone, SNAKE *snakeother) {

	clrscr();
	gotoxy(35,10);
	printf("A : %d  B : %d",snakeone->real_length,snakeother->real_length);
	getch();
}

//游戏开始函数
void GameStare() {
	//定义第一条蛇的初始信息
	SNAKE snakeone = {0, RIGHT_KEY, RIGHT_KEY, 0, 0, 3, 1, '>', 50000, 
					{41,10}};
	//定义第二条蛇的初始信息
	SNAKE snakeother = {0, A_KEY, A_KEY, 0, 0, 3, 1, '<', 50000, 
					{39,10}};

	clrscr();
	initSnake();
	//只要有一条蛇死亡,游戏结束
	while(!(Gomeover(&snakeone) || Gomeover(&snakeother))){
		score(&snakeone,&snakeother);
		snakefood();
		eatfood(&snakeone);
		eatfood(&snakeother);
		Direction(&snakeone, &snakeother);
		movesnake(&snakeone);
		movesnake(&snakeother);
		speed(&snakeone);
	}

	snakescore(&snakeone, &snakeother);
}

//限制蛇移动速度,拓展:以后可根据蛇身的增长来增加移动速度
void speed(SNAKE *snake){
	long i;
	for(i = 0; i < snake->time; i++){
	}
}

//将坐标移至指定点,画出蛇的节点
void draw(int x, int y) {
	gotoxy(x, y);
	printf("%c",4);
}

//将坐标移至指定点,用' '来覆盖尾节点
void deltail(int x, int y) {
	gotoxy(x, y);
	printf(" ");
}

//判断蛇头此时方向,返回相应的值。
int judgesign(SNAKE *snake){
	if(UP_KEY == snake->direction || W_KEY == snake->direction){
		return 0;
	}
	if(DOWM_KEY == snake->direction || S_KEY == snake->direction){
		return 1;
	}
	if(LEFT_KEY == snake->direction || A_KEY == snake->direction){
		return 2;
	}
	if(RIGHT_KEY == snake->direction || D_KEY == snake->direction){
		return 3;
	}
}

//移动蛇(画蛇)
void movesnake(SNAKE *snake) {
	int i;
	
	//根据蛇头变化定义改变量
	VARIATION change[4] = { {0, -1}, {0, 1}, {-1, 0}, {1, 0} };
	i = judgesign(snake);
	
	//判断限制长度和真实长度之间的关系:当真实长度大于等于限制长度时,进行删除尾结点的操作,否则增加真实长度。
	if(snake->length < snake->real_length) {
		deltail(snake->body[snake->tail].x, snake->body[snake->tail].y);
		map[(snake->body[snake->tail].x)*80 + (snake->body[snake->tail].y)] = 0;//将地图中的删除的尾节点重新置为0
		snake->tail = (snake->tail + 1) % MAX_BODY;//运用循环数组,取下一个尾
	} else {
		snake->real_length++;
		map[(snake->body[snake->tail].x)*80 + (snake->body[snake->tail].y)] = 1;
	}
	
	//将原来的蛇头变成蛇身
	draw(snake->body[snake->head].x, snake->body[snake->head].y);

	//取得下一个蛇头的坐标和指向蛇头的指针
	snake->body[(snake->head + 1) % MAX_BODY].x = snake->body[snake->head].x + change[i].x_variation;
	snake->body[(snake->head + 1) % MAX_BODY].y = snake->body[snake->head].y + change[i].y_variation;
	snake->head = (snake->head + 1) % MAX_BODY;

	gotoxy(snake->body[snake->head].x, snake->body[snake->head].y);
	printf("%c",snake->headform);
	map[(snake->body[snake->head].x)*80 + (snake->body[snake->head].y)] = 1;//在地图中记录这个位置不为空
}

//判断是哪个用户输入的键值
int judesnake_key(int key) {
	if(key == UP_KEY || key == DOWM_KEY || key == LEFT_KEY || key == RIGHT_KEY) {
		return 1;
	} else if(key == W_KEY || key == S_KEY || key == A_KEY || key == D_KEY) {
		return 2;
	} else {
		return 0;
	}
}

//判断此时的方向和将键值赋值给相应的蛇。拓展:在一个蛇进行移动时,只需要删除函数关于第二条蛇的相关代码即可。
void judesnake_dir(SNAKE *snakeone, SNAKE *snakeother, int i, int newkey) {
	if(i == 1){
		if(UP_KEY == newkey && DOWM_KEY != snakeone->lastdirection) {
			snakeone->direction = newkey;
			snakeone->headform = '^';
		} else if(DOWM_KEY == newkey && UP_KEY != snakeone->lastdirection) {
			snakeone->direction = newkey;
			snakeone->headform = 'v';
		} else if(LEFT_KEY == newkey && RIGHT_KEY != snakeone->lastdirection) {
			snakeone->direction = newkey;
			snakeone->headform = '<';
		} else if(RIGHT_KEY == newkey && LEFT_KEY != snakeone->lastdirection) {
			snakeone->direction = newkey;
			snakeone->headform = '>';
		}
	} else if(i == 2) {
		if(W_KEY == newkey && S_KEY != snakeother->lastdirection) {
			snakeother->direction = newkey;
			snakeother->headform = '^';
		} else if(S_KEY == newkey && W_KEY != snakeother->lastdirection) {
			snakeother->direction = newkey;
			snakeother->headform = 'v';
		} else if(A_KEY == newkey && D_KEY != snakeother->lastdirection) {
			snakeother->direction = newkey;
			snakeother->headform = '<';
		} else if(D_KEY == newkey && A_KEY != snakeother->lastdirection) {
			snakeother->direction = newkey;
			snakeother->headform = '>';
		}
	} else {
		if(ESC == newkey) {
			snakeone->direction = newkey;
			snakeother->direction = newkey;
		} else{
			//若是其他无关键值,保持原来方向。
			snakeone->direction = snakeone->lastdirection;
			snakeother->direction = snakeother->lastdirection;
		}
	}
	//记录上一次方向
	snakeone->lastdirection = snakeone->direction;
	snakeother->lastdirection = snakeother->direction;
}

//改变蛇方向
void changesnake(SNAKE *snakeone, SNAKE *snakeother) {
	int i;
	int newkey;
	
	//无按键时,不进入该语句
	if(bioskey(1)) {
		newkey = bioskey(1);
		getch();
		i = judesnake_key(newkey);
		judesnake_dir(snakeone, snakeother,i, newkey);
	}
}

//集成调用函数,调用两次,因为要满足两条蛇同时移动
void Direction(SNAKE *snakeone, SNAKE *snakeother) {
	changesnake(snakeone, snakeother);
	changesnake(snakeone, snakeother);
}

//蛇吃食物
void eatfood(SNAKE *snake){
	if(food.x == snake->body[snake->head].x && food.y == snake->body[snake->head].y){
		snake->length++;
		food.exist = 0;
	}
}

//生成食物具体代码
void snakefood() {
	int i;
	int count = 0;
	int tmp;
	int t;
	int z;
	int tmpmap[MAP] = {0};//临时地图,记录此时地图为空的坐标
	
	if(food.exist == 0) {
		srand((unsigned int)time(NULL));//播散随机种子
		for(i = 0; i < MAP; i++) {
			if(map[i] == 0 ) {
				tmpmap[count++] = i;//将空地图的下标赋值给临时地图
			}
		}
		
		//洗牌算法,从临时地图的最后一个有效空间开始,随机取临时地图前面的下标,与临时地图最后一个有效空间进行交换,有效空间值count--,重复之前的过程,直到count = 1
		for(i = count - 1 ; i > 1; i--) {
			z = rand() % i;//获得随机数
			tmp = tmpmap[z];
			tmpmap[z] = tmpmap[i];
			tmpmap[i] = tmp;
		}
		t = tmpmap[rand() % count];//从临时地图中取得随机的空地图下标
		//地图值一维化二维
		food.x = t % 80;
		food.y = t / 80 + 1;
		gotoxy(food.x, food.y);
		printf("%c",3);
		food.exist = 1;
	}
}

void score(SNAKE *snakeone,SNAKE *snakeother) {
	gotoxy(20,24);
	printf("onescore  %d  otherscore %d",snakeone->real_length,snakeother->real_length);
}

//判断游戏结束
boolean Gomeover(SNAKE *snake) {
	if(ESC == snake->direction || (snake->body[snake->head]).x < 2 || (snake->body[snake->head]).x > 79 
		|| (snake->body[snake->head]).y < 2 || (snake->body[snake->head]).y > 22) {
		return TRUE;
	}
	return FALSE;
}

// 初始化游戏
void initSnake() {
	int x;
	int y;
	clrscr();

	gotoxy(30,10);
	printf("Esc : Geme over\n");
	gotoxy(27,13);
	printf("Press any key to continue\n");
	x = bioskey(0);
	clrscr();

	//打印墙体
	for(x = 0; x < 80; x++){
		printf("#");
		map[x] = 2;
	}

	for(y = 2;  y <= 22; y++){
		for(x = 0; x < 80; x++) {
			if(x == 0){
				printf("#");
				map[y*80 + x] = 2;
			} else if(x == 79) {
				printf("#");
				map[y*80 + x] = 2;
			} else {
				printf(" ");
			}
		}
	}

	for(x = 0; x < 80; x++){
		printf("#");
		map[22*80 + x] = 2;
	}
	printf("\n");

}

int main( )
{
	GameStare();
	getch();
	return 0;
}

四、总结

这是我做的第一个可以玩的游戏,尽管内容十分粗糙,很多功能也没有添加,但是基本框架已经搭建完成,以后只需要添加功能即可。
这次编写贪吃蛇,感觉到从0到1的难度,只要踏出了这一步,后面起码不是睁眼瞎了。在编写的过程中,很多想法用代码来实现确实有很大的难度,需要一次次的更改代码,一次次的纠错,这真的是一个很痛苦的过程。这也是需要学习的能力。
总之,加油吧!

20200729于机房

猜你喜欢

转载自blog.csdn.net/weixin_45483328/article/details/107657211