智能蛇
在上一篇博客当中,已经粗略完成一个小小的“贪吃蛇”的程序框架,接下来我们来看一下如何实现更高级的玩法–不需要控制,让蛇自己去吃!
实验环境的准备
其实在上一篇博客中的使用颜色选择时已经在Ubuntu系统下使用了VT-100控制码。
这个实验我选择在Ubuntu下的Code::Blocks完成
非阻塞地检测键盘输入的方法
在之前的游戏中,为了输入命令后不需要回车,并且使输入的命令不显示
在Windows下使用的是conio.h中的getch()函数
在Ubuntu下使用的是
system("stty -icanon");
system("stty -echo");
input=getchar();
system("stty icanon");
system("stty echo");
而今天我们在Ubuntu下使用更高级的kbhit()函数来解决这个问题
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <termios.h>
#include <unistd.h>
#define SNAKE_MAX_LENGTH 20
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define SNAKE_FOOD '$'
#define WALL_CELL '*'
#define BLOCKS '#'
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 snakeLength=5;
int tempX=0;
int tempY=0;
int color=0;
int score=0;
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;
}
void color_choose(void)
{
printf("Please choose color (Input 1~5):\n");
printf("\033[30m(1)Black \033[31m(2)Red \033[32m(3)Green \033[33m(4)Yellow \033[34m(5)Blue\033[0m\n");
scanf("%d",&color);
system("clear");
}
void set_blocks(void)
{
int number=0;
int x = 0;
int y = 0;
int turn=0;
srand((unsigned)time(NULL));
printf("How many blocks do you want to set:(range from 0 to 5)\n");
scanf("%d",&number);
while(turn<number)
{
x=rand()%10+1;
y=rand()%10+1;
if(map[x][y]==BLANK_CELL)
{
map[x][y]=BLOCKS;
turn++;
}
}
}
void snakeMove(int dx,int dy)
{
int turn=0;
tempX=snakeX[snakeLength-1];
tempY=snakeY[snakeLength-1];
int tempX1=0;
int tempY1=0;
if(map[snakeY[snakeLength-1]+dy][snakeX[snakeLength-1]+dx]==BLOCKS)//当蛇头面前是blocks时无法前进
{
return;
}
snakeY[snakeLength-1]+=dy;
snakeX[snakeLength-1]+=dx;
for(turn=snakeLength-2;turn>=0;turn--)
{
tempX1=snakeX[turn];
tempY1=snakeY[turn];
snakeX[turn]=tempX;
snakeY[turn]=tempY;
tempX=tempX1;
tempY=tempY1;
}
}
void put_money(void)
{
int x = 0;
int y = 0;
srand((unsigned)time(NULL));
while(map[x][y]!=BLANK_CELL)
{
x=rand()%10+1;
y=rand()%10+1;
}
map[x][y]=SNAKE_FOOD;
}
void eat_food(void)
{
int turn = 0;
if(map[snakeY[snakeLength-1]][snakeX[snakeLength-1]]==SNAKE_FOOD)
{
snakeLength++;
score+=100;
if(snakeLength>20)
{
return;
}
for(turn=snakeLength-1;turn>0;turn--)
{
snakeX[turn]=snakeX[turn-1];
snakeY[turn]=snakeY[turn-1];
}
snakeX[turn]=tempX;
snakeY[turn]=tempY;
put_money();
}
}
void output(void)
{
int turnx=0;
int turny=0;
for(turnx=1;turnx<11;turnx++)
{
for(turny=1;turny<11;turny++)
{
if(map[turnx][turny]!=SNAKE_FOOD&&map[turnx][turny]!=BLOCKS)
{
map[turnx][turny]=BLANK_CELL;
}
}
}
for(turnx=0,turny=0;turnx<snakeLength;turnx++,turny++)
{
if(map[snakeY[turny]][snakeX[turnx]]!=SNAKE_FOOD)
{
map[snakeY[turny]][snakeX[turnx]]=SNAKE_BODY;
}
}
map[snakeY[snakeLength-1]][snakeX[snakeLength-1]]=SNAKE_HEAD;
system("clear");
for(turnx=0;turnx<12;turnx++)
{
for(turny=0;turny<12;turny++)
{
printf("\033[?25l \033[%dm %c",29+color,map[turnx][turny]);
}
printf("\n");
}
printf("Score:%d\n",score);
}
int gameover(void)
{
int turn = 0;
if(map[snakeY[snakeLength-1]][snakeX[snakeLength-1]]==WALL_CELL)
{
printf("Game Over!!!");
return 0;
}
if(snakeLength>SNAKE_MAX_LENGTH)
{
printf("Game Over!!!");
return 0;
}
for(turn=snakeLength-2;turn>=0;turn--)
{
if(snakeX[turn]==snakeX[snakeLength-1]&&snakeY[turn]==snakeY[snakeLength-1])
{
printf("Game Over!!!");
return 0;
}
}
return 1;
}
//将你的 snake 代码放在这里
int main()
{
char input;
//设置终端进入非缓冲状态
int tty_set_flag;
tty_set_flag = tty_set();
color_choose();
set_blocks();
put_money();
output();
//将你的 snake 代码放在这里
printf("pressed `q` to quit!\n");
while(1) {
if( kbhit() )
{
input = getchar();
switch(input)
{
case'A':snakeMove(-1,0);break;
case'D':snakeMove(1,0);break;
case'W':snakeMove(0,-1);break;
case'S':snakeMove(0,1);break;
case'q':return 0;
default:break;
}
}
else
{
continue;
}
eat_food();
if(gameover()!=0)
{
output();
continue;
}
else
{
break;
}
}
//恢复终端设置
if(tty_set_flag == 0)
tty_reset();
return 0;
}
智能蛇
好了,接下来就开始正式开始我们的智能蛇的编写
首先根据
决定蛇行走的方向函数的伪代码
// Hx,Hy: 头的位置
// Fx,Fy:食物的位置
function whereGoNext(Hx,Hy,Fx,Fy) {
// 用数组movable[3]={“a”,”d”,”w”,”s”} 记录可走的方向
// 用数组distance[3]={0,0,0,0} 记录离食物的距离
// 分别计算蛇头周边四个位置到食物的距离。H头的位置,F食物位置
// 例如:假设输入”a” 则distance[0] = |Fx – (Hx-1)| + |Fy – Hy|
// 如果 Hx-1,Hy 位置不是Blank,则 distance[0] = 9999
// 选择distance中存最小距离的下标p,注意最小距离不能是9999
// 返回 movable[p]
这是我写的Autorun函数
void Autorun(void)
{
int add=0;
int turn=0;
int nowx=snakeX[snakeLength-1];
int nowy=snakeY[snakeLength-1];
if(map[nowy-1][nowx]==WALL_CELL||map[nowy-1][nowx]==BLOCKS||map[nowy-1][nowx]==SNAKE_BODY)
{
distance[0]=9999;
}
else
{
distance[0]=abs(nowy-1-Moneyx)+abs(nowx-Moneyy);
}
if(map[nowy][nowx-1]==WALL_CELL||map[nowy][nowx-1]==BLOCKS||map[nowy][nowx-1]==SNAKE_BODY)
{
distance[1]=9999;
}
else
{
distance[1]=abs(nowy-Moneyx)+abs(nowx-Moneyy-1);
}
if(map[nowy+1][nowx]==WALL_CELL||map[nowy+1][nowx]==BLOCKS||map[nowy+1][nowx]==SNAKE_BODY)
{
distance[2]=9999;
}
else
{
distance[2]=abs(nowy-Moneyx+1)+abs(nowx-Moneyy);
}
if(map[nowy][nowx+1]==WALL_CELL||map[nowy][nowx+1]==BLOCKS||map[nowy][nowx+1]==SNAKE_BODY)
{
distance[3]=9999;
}
else
{
distance[3]=abs(nowy-Moneyx)+abs(nowx-Moneyy+1);
}
for(turn=0;turn<3;turn++)
{
if(distance[turn]>distance[turn+1])
{
add=0;
}
else
{
distance[turn+1]=distance[turn];
add++;
}
}
if(distance[3]==9999)
{
return;
}
switch((3-add))
{
case 0:snakeMove(0,-1);break;
case 1:snakeMove(-1,0);break;
case 2:snakeMove(0,1);break;
case 3:snakeMove(1,0);break;
default:break;
}
}
但是由于算法太过简陋、粗暴,经常自己走向死局,所以以后还会慢慢改进。
这是本次“智能蛇”的完整代码
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <termios.h>
#include <unistd.h>
#define SNAKE_MAX_LENGTH 100
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define SNAKE_FOOD '$'
#define WALL_CELL '*'
#define BLOCKS '#'
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 snakeLength=5;
int tempX=0;
int tempY=0;
int color=0;
int score=0;
int Moneyx=0;
int Moneyy=0;
int distance[4]={0};
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;
}
void color_choose(void)
{
printf("Please choose color (Input 1~5):\n");
printf("\033[30m(1)Black \033[31m(2)Red \033[32m(3)Green \033[33m(4)Yellow \033[34m(5)Blue\033[0m\n");
scanf("%d",&color);
system("clear");
}
void set_blocks(void)
{
int number=0;
int x = 0;
int y = 0;
int turn=0;
srand((unsigned)time(NULL));
printf("How many blocks do you want to set:(range from 0 to 15)\n");
scanf("%d",&number);
while(turn<number)
{
x=rand()%10+1;
y=rand()%10+1;
if(map[x][y]==BLANK_CELL)
{
map[x][y]=BLOCKS;
turn++;
}
}
}
void snakeMove(int dx,int dy)
{
int turn=0;
tempX=snakeX[snakeLength-1];
tempY=snakeY[snakeLength-1];
int tempX1=0;
int tempY1=0;
if(map[snakeY[snakeLength-1]+dy][snakeX[snakeLength-1]+dx]==BLOCKS)//当蛇头面前是blocks时无法前进
{
return;
}
snakeY[snakeLength-1]+=dy;
snakeX[snakeLength-1]+=dx;
for(turn=snakeLength-2;turn>=0;turn--)
{
tempX1=snakeX[turn];
tempY1=snakeY[turn];
snakeX[turn]=tempX;
snakeY[turn]=tempY;
tempX=tempX1;
tempY=tempY1;
}
}
void put_money(void)
{
Moneyx = 0;
Moneyy = 0;
srand((unsigned)time(NULL));
while(map[Moneyx][Moneyy]!=BLANK_CELL)
{
Moneyx=rand()%10+1;
Moneyy=rand()%10+1;
}
map[Moneyx][Moneyy]=SNAKE_FOOD;
}
void eat_food(void)
{
int turn = 0;
if(map[snakeY[snakeLength-1]][snakeX[snakeLength-1]]==SNAKE_FOOD)
{
snakeLength++;
score+=100;
if(snakeLength>20)
{
return;
}
for(turn=snakeLength-1;turn>0;turn--)
{
snakeX[turn]=snakeX[turn-1];
snakeY[turn]=snakeY[turn-1];
}
snakeX[turn]=tempX;
snakeY[turn]=tempY;
put_money();
}
}
void output(void)
{
int turnx=0;
int turny=0;
for(turnx=1;turnx<11;turnx++)
{
for(turny=1;turny<11;turny++)
{
if(map[turnx][turny]!=SNAKE_FOOD&&map[turnx][turny]!=BLOCKS)
{
map[turnx][turny]=BLANK_CELL;
}
}
}
for(turnx=0,turny=0;turnx<snakeLength;turnx++,turny++)
{
if(map[snakeY[turny]][snakeX[turnx]]!=SNAKE_FOOD)
{
map[snakeY[turny]][snakeX[turnx]]=SNAKE_BODY;
}
}
map[snakeY[snakeLength-1]][snakeX[snakeLength-1]]=SNAKE_HEAD;
system("clear");
for(turnx=0;turnx<12;turnx++)
{
for(turny=0;turny<12;turny++)
{
printf("\033[?25l \033[%dm %c",29+color,map[turnx][turny]);
}
printf("\n");
}
printf("Score:%d\n",score);
}
void Autorun(void)
{
int add=0;
int turn=0;
int nowx=snakeX[snakeLength-1];
int nowy=snakeY[snakeLength-1];
if(map[nowy-1][nowx]==WALL_CELL||map[nowy-1][nowx]==BLOCKS||map[nowy-1][nowx]==SNAKE_BODY)
{
distance[0]=9999;
}
else
{
distance[0]=abs(nowy-1-Moneyx)+abs(nowx-Moneyy);
}
if(map[nowy][nowx-1]==WALL_CELL||map[nowy][nowx-1]==BLOCKS||map[nowy][nowx-1]==SNAKE_BODY)
{
distance[1]=9999;
}
else
{
distance[1]=abs(nowy-Moneyx)+abs(nowx-Moneyy-1);
}
if(map[nowy+1][nowx]==WALL_CELL||map[nowy+1][nowx]==BLOCKS||map[nowy+1][nowx]==SNAKE_BODY)
{
distance[2]=9999;
}
else
{
distance[2]=abs(nowy-Moneyx+1)+abs(nowx-Moneyy);
}
if(map[nowy][nowx+1]==WALL_CELL||map[nowy][nowx+1]==BLOCKS||map[nowy][nowx+1]==SNAKE_BODY)
{
distance[3]=9999;
}
else
{
distance[3]=abs(nowy-Moneyx)+abs(nowx-Moneyy+1);
}
for(turn=0;turn<3;turn++)
{
if(distance[turn]>distance[turn+1])
{
add=0;
}
else
{
distance[turn+1]=distance[turn];
add++;
}
}
if(distance[3]==9999)
{
return;
}
switch((3-add))
{
case 0:snakeMove(0,-1);break;
case 1:snakeMove(-1,0);break;
case 2:snakeMove(0,1);break;
case 3:snakeMove(1,0);break;
default:break;
}
}
int gameover(void)
{
int turn = 0;
if(map[snakeY[snakeLength-1]][snakeX[snakeLength-1]]==WALL_CELL)
{
printf("Game Over!!!");
return 0;
}
if(distance[3]==9999)
{
printf("Game Over!!!");
return 0;
}
for(turn=snakeLength-2;turn>=0;turn--)
{
if(snakeX[turn]==snakeX[snakeLength-1]&&snakeY[turn]==snakeY[snakeLength-1])
{
printf("Game Over!!!");
return 0;
}
}
return 1;
}
//将你的 snake 代码放在这里
int main()
{
char input;
unsigned int timE=1000000;
//设置终端进入非缓冲状态
int tty_set_flag;
tty_set_flag = tty_set();
color_choose();
set_blocks();
put_money();
output();
//将你的 snake 代码放在这里
printf("pressed `Q` to quit!\n");
printf("pressed `F` to accelerate!\n");
printf("pressed `L` to decelerate!\n");
while(1) {
if( kbhit() )
{
input = getchar();
switch(input)
{
case'A':snakeMove(-1,0);break;
case'D':snakeMove(1,0);break;
case'W':snakeMove(0,-1);break;
case'S':snakeMove(0,1);break;
case'F':timE-=200000;break;
case'L':timE+=200000;break;
case'Q':return 0;
default:break;
}
}
else
{
Autorun();
usleep(timE);
}
eat_food();
if(gameover()!=0)
{
output();
continue;
}
else
{
break;
}
}
//恢复终端设置
if(tty_set_flag == 0)
tty_reset();
return 0;
}
End
虽然编写贪吃蛇和智能蛇花费了不少的时间,但是那种专心于当中的感觉真的很好,对程序设计、对软导课的兴趣也在不断增加!!!!