字符游戏-智能蛇

字符游戏-智能蛇

一、VT 100 终端标准

这里按照老师的课件要求,体验一下VT 100 输入输出功能以及清屏操作,代码直接复制课件中代码,这里就不再放一次了,直接给出运行效果:

 gcc sin-demo.c -osin.out -lm
 ./sin.out
  • 运行后不断输出波浪形字符
    在这里插入图片描述

二、kbhit()

我们还是按照课件要求,体验一检测tty输入的程序,将我们之前的snake的代码放入对应位置,运行后查看结果:

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>


static struct termios ori_attr, cur_attr;

static __inline 
int tty_reset(void)
{
        if (tcsetattr(STDIN_FILENO, TCSANOW, &ori_attr) != 0)
                return -1;

        return 0;
}


static __inline
int tty_set(void)
{
        
        if ( tcgetattr(STDIN_FILENO, &ori_attr) )
                return -1;
        
        memcpy(&cur_attr, &ori_attr, sizeof(cur_attr) );
        cur_attr.c_lflag &= ~ICANON;
//        cur_attr.c_lflag |= ECHO;
        cur_attr.c_lflag &= ~ECHO;
        cur_attr.c_cc[VMIN] = 1;
        cur_attr.c_cc[VTIME] = 0;

        if (tcsetattr(STDIN_FILENO, TCSANOW, &cur_attr) != 0)
                return -1;

        return 0;
}

static __inline
int kbhit(void) 
{
                   
        fd_set rfds;
        struct timeval tv;
        int retval;

        /* Watch stdin (fd 0) to see when it has input. */
        FD_ZERO(&rfds);
        FD_SET(0, &rfds);
        /* Wait up to five seconds. */
        tv.tv_sec  = 0;
        tv.tv_usec = 0;

        retval = select(1, &rfds, NULL, NULL, &tv);
        /* Don't rely on the value of tv now! */

        if (retval == -1) {
                perror("select()");
                return 0;
        } else if (retval)
                return 1;
        /* FD_ISSET(0, &rfds) will be true. */
        else
                return 0;
        return 0;
}

//将你的 snake 代码放在这里
#define SNAKE_MAX_LENGTH 20
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define WALL_CELL '*'
#define SNAKE_FOOD '$'

//  -1表示向上(dy)或者向左(dy)  +1表示向下(dy)或者向右(dx)
void snakeMove(int, int);
//随机投放食物 
void put_money(void);
//输出二维数组图形 
void output(void);
//游戏结束 
void gameover(void);
//吃食物
int eat_money(int dx, int dy);

char map[12][12] =
{ "***********",
"*XXXXH    *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"***********" };
int snakeX[SNAKE_MAX_LENGTH] = { 1,2,3,4,5 };
int snakeY[SNAKE_MAX_LENGTH] = { 1,1,1,1,1 };
int moneyX, moneyY;
int snakeLength = 5;
int continuegame = 1;

int main()
{	
	put_money();                               //  首先投放食物 
	output();
        //设置终端进入非缓冲状态
        int tty_set_flag;
        tty_set_flag = tty_set();
	int key;
        while(continuegame == 1) {

		if( kbhit() ) {
                        key = getchar();
                        if(key == 'w' || key == 'W'){
				snakeMove(0, -1);
			}
			if(key == 's' || key == 'S'){
				snakeMove(0, 1);
			}
			if(key == 'a' || key == 'A'){
				snakeMove(-1, 0);
			}
			if(key == 'd' || key == 'D'){
				snakeMove(1, 0);
			}
		system("clear");//对于 VT100 终端, printf("\033[2J"); 也可以实现清屏。
		output();
		gameover();
                } 
        }

        //恢复终端设置
        if(tty_set_flag == 0) 
                tty_reset();
        return 0;
}

void snakeMove(int dx, int dy) {
	int i;
	//判断是否吃到食物
	if (eat_money(dx, dy));
	else {
		map[snakeY[0]][snakeX[0]] = BLANK_CELL;
		//  如果不是,那么以前的最后一节变成空白
		map[snakeY[snakeLength - 1]][snakeX[snakeLength - 1]] = SNAKE_BODY;
		//  以前的头变成身子
		for (i = 0; i < snakeLength - 1; i++) {
			//  以前所有的坐标都向前移动
			snakeX[i] = snakeX[i + 1];
			snakeY[i] = snakeY[i + 1];
		}
		snakeX[snakeLength - 1] += dx;
		snakeY[snakeLength - 1] += dy;
		map[snakeY[snakeLength - 1]][snakeX[snakeLength - 1]] = SNAKE_HEAD;
		//  新的头
	}
}

void put_money(void) {
	int i;
	srand(time(NULL));
	moneyX = rand() % 9 + 1;
	srand(time(NULL));
	moneyY = rand() % 9 + 1;
	for (i = 0; i < snakeLength; i++) {
		while (moneyX == snakeX[i] && moneyY == snakeY[i]) {
			srand(time(NULL));
			moneyX = rand() % 9 + 1;
			srand(time(NULL));
			moneyY = rand() % 9 + 1;
			i = -1;
		}
	}
	map[moneyY][moneyX] = '$';
}

void output(void) {
	int i, j; 
	for (i = 0; i < 12; ++i) {
		for (j = 0; j < 12; ++j) {
			printf("%c", map[i][j]);
		}
		putchar('\n');
	}
}

void gameover(void) {
	if (snakeX[snakeLength - 1] == 0 || snakeX[snakeLength - 1] == 10
		|| snakeY[snakeLength - 1] == 0 || snakeY[snakeLength - 1] == 10) {
		continuegame = 0;
		printf("撞到墙了!") ; 
	}
	int i;
	for (i = 0; i < snakeLength - 1; i++) {
		//  以前所有的坐标都向前移动
		if (snakeX[i] == snakeX[snakeLength - 1] &&
			snakeY[i] == snakeY[snakeLength - 1]) {
			printf("咬到自己了!") ; 
			continuegame = 0;
			}
	}
	if (SNAKE_MAX_LENGTH == snakeLength) {
		continuegame = 0;
		printf("congratulations!\twin!\n");
	}
}

int eat_money(int dx, int dy) {
	if (snakeX[snakeLength - 1] + dx == moneyX && snakeY[snakeLength - 1] + dy == moneyY) {
		snakeLength++;
		snakeX[snakeLength - 1] = snakeX[snakeLength - 2] + dx;
		snakeY[snakeLength - 1] = snakeY[snakeLength - 2] + dy;
		map[snakeY[0]][snakeX[0]] = SNAKE_BODY;
		map[snakeY[snakeLength - 2]][snakeX[snakeLength - 2]] = SNAKE_BODY;
		map[snakeY[snakeLength - 1]][snakeX[snakeLength - 1]] = SNAKE_HEAD;
		put_money();
		return 1;
	}
	else return 0;
}
  • 之后我们编译运行,并查看结果:
gcc kihib.c -oihib.out -lm
./kihib.out
  • 可以观察看我们的程序变成了检测键盘输入,而不会在终端上出现我们输入的wasd等操作了,体验到了tty输入的优势
    在这里插入图片描述

三、实现智能蛇

所谓的智能蛇,也就是我们要用程序来为我们的snake选择下一步行走的道路,而取代我们的键盘输入,所以整体逻辑和我们原本的snack是相同的,我们要做的是增加一个选择函数,来代替我们的键盘输入来为snake选择路线

  • 我们首先增加对于路径选择函数的定义:
//自动寻找
char wheregonext(int hx, int hy, int fx, int fy);
  • 之后我们要在检测键盘输入的位置,将字符读取更换为使用我们的函数
	while (continuegame == 1) {                   //  每次循环都要判断是否游戏已经结束
		ch = wheregonext(snakeX[snakeLength - 1], snakeY[snakeLength - 1], moneyX, moneyY);
		printf("\033[2J");
		switch (ch) {
  • 最后是我们的函数部分:
    • 整体思路按照老师给出的伪代码来实现:
    • 伪代码如下
      在这里插入图片描述

实现代码如下:

//用数组distance[4]={0,0,0,0} 记录离食物的距离
char wheregonext(int hx, int hy, int fx, int fy) {// Hx,Hy: 头的位置
	// Fx,Fy:食物的位置
	char p = 0;
	int min = 9999;
	int distance[4];
	distance[0] = abs(fx - (hx - 1)) + abs(fy - hy);
	distance[1] = abs(fx - (hx + 1)) + abs(fy - hy);
	distance[2] = abs(fx - hx) + abs(fy - (hy - 1));
	distance[3] = abs(fx - hx) + abs(fy - (hy + 1));
	// 分别计算蛇头周边四个位置到食物的距离。H头的位置,F食物位置
	if (distance[0] <= min && (map[hy][hx - 1] == ' ' || map[hy][hx - 1] == '$')) {
		min = distance[0];
		p = 'a';
	}
	if (distance[1] <= min && (map[hy][hx + 1] == ' ' || map[hy][hx + 1] == '$')) {
		min = distance[1];
		p = 'd';
	}
	if (distance[2] <= min && (map[hy - 1][hx] == ' ' || map[hy - 1][hx] == '$')) {
		min = distance[2];
		p = 'w';
	}
	if (distance[3] <= min && (map[hy + 1][hx] == ' ' || map[hy + 1][hx] == '$')) {
		min = distance[3];
		p = 's';
	}
	// 选择distance中存最小距离的下标p,最小距离不能是9999
	return p;

}

运行结果:

在这里插入图片描述

  • 完整代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>


#define SNAKE_MAX_LENGTH 20
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define WALL_CELL '*'
#define SNAKE_FOOD '$'

//  -1表示向上(dy)或者向左(dy)  +1表示向下(dy)或者向右(dx)
void snakeMove(int, int);
//随机投放食物 
void put_money(void);
//输出二维数组图形 
void output(void);
//游戏结束 
void gameover(void);
//吃食物
int eat_money(int dx, int dy);
//自动寻找
char wheregonext(int hx, int hy, int fx, int fy);

char map[12][12] =
{ "***********",
"*XXXXH    *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"*         *",
"***********" };
int snakeX[SNAKE_MAX_LENGTH] = { 1,2,3,4,5 };
int snakeY[SNAKE_MAX_LENGTH] = { 1,1,1,1,1 };
int moneyX, moneyY;
int snakeLength = 5;
int continuegame = 1;


int main() {
	char ch = 0;
	put_money();                               //  首先投放食物 
	output();
	while (continuegame == 1) {                   //  每次循环都要判断是否游戏已经结束
		ch = wheregonext(snakeX[snakeLength - 1], snakeY[snakeLength - 1], moneyX, moneyY);
		printf("\033[2J");
		switch (ch) {
		case 'A':
		case 'a':
			snakeMove(-1, 0);		//  向左移动 
			break;
		case 'D':
		case 'd':
			snakeMove(1, 0);		//  向右移动 
			break;
		case 'W':
		case 'w':
			snakeMove(0, -1);		//  向上移动 
			break;
		case 'S':
		case 's':
			snakeMove(0, 1);		//  向下移动 
			break;
		default: break;
		}
		output();
		gameover();
		sleep(1);
	}
	printf("Game Over");
}

void snakeMove(int dx, int dy) {
	int i;
	//判断是否吃到食物
	if (eat_money(dx, dy));
	else {
		map[snakeY[0]][snakeX[0]] = BLANK_CELL;
		//  如果不是,那么以前的最后一节变成空白
		map[snakeY[snakeLength - 1]][snakeX[snakeLength - 1]] = SNAKE_BODY;
		//  以前的头变成身子
		for (i = 0; i < snakeLength - 1; i++) {
			//  以前所有的坐标都向前移动
			snakeX[i] = snakeX[i + 1];
			snakeY[i] = snakeY[i + 1];
		}
		snakeX[snakeLength - 1] += dx;
		snakeY[snakeLength - 1] += dy;
		map[snakeY[snakeLength - 1]][snakeX[snakeLength - 1]] = SNAKE_HEAD;
		//  新的头
	}
}

void put_money(void) {
	int i;
	srand(time(NULL));
	moneyX = rand() % 9 + 1;
	//  钱的横纵坐标随机产生
	srand(time(NULL));
	moneyY = rand() % 9 + 1;
	//  如果钱砸身子上了,就换
	for (i = 0; i < snakeLength; i++) {
		while (moneyX == snakeX[i] && moneyY == snakeY[i]) {
			srand(time(NULL));
			moneyX = rand() % 9 + 1;
			srand(time(NULL));
			moneyY = rand() % 9 + 1;
			i = -1;
		}
	}
	map[moneyY][moneyX] = '$';
}

void output(void) {
	int i, j;
	for (i = 0; i < 12; ++i) {
		for (j = 0; j < 12; ++j) {
			printf("%c", map[i][j]);
		}
		putchar('\n');
	}
}

void gameover(void) {
	if (snakeX[snakeLength - 1] == 0 || snakeX[snakeLength - 1] == 10
		|| snakeY[snakeLength - 1] == 0 || snakeY[snakeLength - 1] == 10) {
		continuegame = 0;
		printf("撞到墙了!");
	}
	int i;
	for (i = 0; i < snakeLength - 1; i++) {
		//  以前所有的坐标都向前移动
		if (snakeX[i] == snakeX[snakeLength - 1] &&
			snakeY[i] == snakeY[snakeLength - 1]) {
			printf("咬到自己了!");
			continuegame = 0;
		}
	}
	if (SNAKE_MAX_LENGTH == snakeLength) {
		continuegame = 0;
		printf("congratulations!\twin!\n");
	}
}

int eat_money(int dx, int dy) {
	if (snakeX[snakeLength - 1] + dx == moneyX && snakeY[snakeLength - 1] + dy == moneyY) {
		//  判断是否吃到了money,即两者坐标是否完全相等
		snakeLength++;
		//  如果是,长度加 1
		snakeX[snakeLength - 1] = snakeX[snakeLength - 2] + dx;
		//  头坐标向前移动,表示头向前走了一步
		snakeY[snakeLength - 1] = snakeY[snakeLength - 2] + dy;
		map[snakeY[0]][snakeX[0]] = SNAKE_BODY;
		//  因为新生了一节,所以以前的最后一节依然是SNAKE_BODY
		map[snakeY[snakeLength - 2]][snakeX[snakeLength - 2]] = SNAKE_BODY;
		//  以前的头变成了身子
		map[snakeY[snakeLength - 1]][snakeX[snakeLength - 1]] = SNAKE_HEAD;
		//  现在的头
		put_money();
		//  再生一个'$'
		return 1;
	}
	else return 0;
}
//用数组distance[4]={0,0,0,0} 记录离食物的距离
char wheregonext(int hx, int hy, int fx, int fy) {// Hx,Hy: 头的位置
	// Fx,Fy:食物的位置
	char p = 0;
	int min = 9999;
	int distance[4];
	distance[0] = abs(fx - (hx - 1)) + abs(fy - hy);
	distance[1] = abs(fx - (hx + 1)) + abs(fy - hy);
	distance[2] = abs(fx - hx) + abs(fy - (hy - 1));
	distance[3] = abs(fx - hx) + abs(fy - (hy + 1));
	// 分别计算蛇头周边四个位置到食物的距离。H头的位置,F食物位置
	if (distance[0] <= min && (map[hy][hx - 1] == ' ' || map[hy][hx - 1] == '$')) {
		min = distance[0];
		p = 'a';
	}
	if (distance[1] <= min && (map[hy][hx + 1] == ' ' || map[hy][hx + 1] == '$')) {
		min = distance[1];
		p = 'd';
	}
	if (distance[2] <= min && (map[hy - 1][hx] == ' ' || map[hy - 1][hx] == '$')) {
		min = distance[2];
		p = 'w';
	}
	if (distance[3] <= min && (map[hy + 1][hx] == ' ' || map[hy + 1][hx] == '$')) {
		min = distance[3];
		p = 's';
	}
	// 选择distance中存最小距离的下标p,最小距离不能是9999
	return p;

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值