前言
贪吃蛇,一款极其经典的游戏。也是众多程序员入门的小程序。
但是贪吃蛇中却蕴含着大学问。如何让蛇自己动起来,能自己吃食物,并且还不能死亡以使蛇长度尽可能地长。这是一个很普通也很深奥的问题。网上有很多大牛设计出了自己的算法,如下图:
总结起来,主要有以下几种:
- BFS(广度优先搜索)算法
- A STAR算法
- 哈密顿回路
算法设计
由于本人能力有限,只能通过曼哈顿距离,最简单的贪婪算法来进行设计。其主要思想是:
- 计算四个不同方向(上下左右)的曼哈顿,选择距离最短的方向
- 判断选择该方向会不会死亡,否则选择距离第二短的方向,以此类推。
虽然这个算法很不理智,蛇很容易死掉,但是这个算法应该是最容易理解的了。代码实现如下:
char whereGoNext() {
char movable[4]= {'D','A','S','W'};
int min=9999,minDir;
for(int i=0; i<4; i++) {
snakeX[0]+=direx[i];
snakeY[0]+=direy[i];
if(!gameover()) {
int cost=fabs(snakeX[0]-foody)+fabs(snakeY[0]-foodx);
if(min>cost) {
min=cost;
minDir=i;
}
}
snakeX[0]-=direx[i];
snakeY[0]-=direy[i];
}
if(min<9999)
return movable[minDir];
else return 0;
}
最主要的部分说完了,还剩下一些细节怎么处理呢?比如如何实现蛇的移动,如何投放食物…..
蛇移动的实现:
- 用两个数组存放蛇的位置坐标:snakeX[] 和 snakeY[],每次移动一个单位,改变数组的值。注意要分吃到食物和没吃到食物两种情况来讨论。
如何投放食物
- 用一二维数组存放可以投放食物的位置的坐标。
- 遍历整个地图,将空位置记录在这个二维数组上
- 用随机数决定选择哪个坐标投放食物。
如何打印蛇
- 为了避免调用system(“cls”)产生闪烁效果而导致用户不友好,在此我们选择用VT100控制码 来设置光标的位置,从而避免了每次都刷新屏幕。
全部的代码看下方,实验环境是Red Hat Enterprise Linux 6,makefile 文件我也放在了代码后面,实测编译通过(就是死得比较快)….
/**
* mysnake.c
* version 1.0
* Red Hat Enterprise Linux 6
* Jzin Ou 2017/12/18
*/
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#include<unistd.h>
#define SNAKE_MAX_LENGTH 150
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define SNAKE_FOOD '$'
#define WALL_CELL '*'
#define MAP_LENGTH 12
#define DELAY_TIME 100000 //delay 100ms
/**
* move snake
* @param dir int movement direction
*/
void snakeMove(int dir);
/**
* output cells of the grid
*/
void show(void);
/**
* output the snake
*/
void output(void);
/**
* put a food randomized on a blank cell
* @return int 1 for put successfully, 0 for fail
*/
int putFood(void);
/**
* judge whether gameover
* @return int 0 for not over,1 for game over
*/
int gameover(void);
/**
* decide the smart snake's direction
* @return snake's movement direction
*/
char whereGoNext(void);
char map[MAP_LENGTH][MAP_LENGTH+1]= {//initial status
"************",
"*XXXXH *",
"* * *",
"* * *",
"* ** *",
"* * *",
"* *",
"* *",
"* *",
"* *",
"* *",
"************"
};
const int direx[4]= {0,0,1,-1}; //four movement direction:
const int direy[4]= {1,-1,0,0}; //(0,1)turn down (0,-1)turn up (1,0)turn right (-1,0)turn left
int snakeX[SNAKE_MAX_LENGTH]= {5,4,3,2,1}; //snake's coordinate
int snakeY[SNAKE_MAX_LENGTH]= {1,1,1,1,1}; //(snakeX[0],snakeY[0]) means head (snakeX[length-1],snakeY[length-1]) means tail
int snakeLength=5;
int foodx,foody,eatFood=1;
int main(void) {
char s;
srand(time(NULL));
printf("\033[2J");//clear the screen
printf("\033[1;1H");
show();
do {
s=whereGoNext();
if(s==0)break;
switch(s) {
case 'D':
snakeMove(0);
break;
case 'A':
snakeMove(1);
break;
case 'S':
snakeMove(2);
break;
case 'W':
snakeMove(3);
break;
}
output();
usleep(DELAY_TIME);
} while(!gameover());
printf("\033[13;1HGame over!!!\n");
return 0;
}
void snakeMove(int dir) {
//first clear the tail
printf("\033[%d;%dH%c\n",snakeY[snakeLength-1]+1,snakeX[snakeLength-1]+1,BLANK_CELL);
map[snakeY[snakeLength-1]][snakeX[snakeLength-1]]=BLANK_CELL;
if(snakeX[0]+direx[dir]==foody&&snakeY[0]+direy[dir]==foodx) {//eat food
eatFood=1;
snakeLength++;//increase length
}
for(int i=snakeLength-1; i>0; i--) {//body move
snakeX[i]=snakeX[i-1];
snakeY[i]=snakeY[i-1];
}
snakeX[0]+=direx[dir];//head move
snakeY[0]+=direy[dir];
}
void show() {
for(int i=0; i<MAP_LENGTH; i++) {//output map
for(int j=0; j<MAP_LENGTH; j++) {
printf("%c",map[i][j]);
}
printf("\n");
}
}
void output(void) {
printf("\033[%d;%dH%c\n",snakeY[0]+1,snakeX[0]+1,SNAKE_HEAD);/print snake/
printf("\033[%d;%dH%c\n",snakeY[1]+1,snakeX[1]+1,SNAKE_BODY);
printf("\033[%d;%dH%c\n",snakeY[snakeLength-1]+1,snakeX[snakeLength-1]+1,SNAKE_BODY);
//must empty buffers: printf+'\n' or fflush(stdout);
map[snakeY[0]][snakeX[0]]=SNAKE_HEAD;
map[snakeY[snakeLength-1]][snakeX[snakeLength-1]]=SNAKE_BODY;
if(eatFood) {
eatFood=0;
if(putFood()) { //put food
printf("\033[%d;%dH%c\n",foodx+1,foody+1,SNAKE_FOOD);
}
} else {
printf("\033[%d;%dH%c\n",foodx+1,foody+1,SNAKE_FOOD);
}
printf("\033[13;1H\n");
}
int gameover(void) {
if(snakeLength>=(MAP_LENGTH-2)*(MAP_LENGTH-2))return 1;//the map is full
if(map[snakeY[0]][snakeX[0]]==WALL_CELL)return 1;//not touch wall
for(int j=1; j<snakeLength; j++) {//doesn't eat itself
if(snakeX[0]==snakeX[j]&&snakeY[0]==snakeY[j])return 1;
}
return 0;
}
int putFood(void) {
int foodMap[MAP_LENGTH*MAP_LENGTH][2],len,ran;
len=0;
for(int i=1; i<MAP_LENGTH-1; i++) {//put empty place into foodMap
for(int j=1; j<MAP_LENGTH-1; j++) {
if(map[i][j]==BLANK_CELL) {
foodMap[len][0]=i;
foodMap[len][1]=j;
len++;
}
}
}
if(len>1)ran=rand()%len;//place food randomly
else if(len==0)return 0;
else ran=0;
foodx=foodMap[ran][0];
foody=foodMap[ran][1];
return 1;
}
char whereGoNext() {
char movable[4]= {'D','A','S','W'};
int min=9999,minDir;
for(int i=0; i<4; i++) {
snakeX[0]+=direx[i];
snakeY[0]+=direy[i];
if(!gameover()) {
int cost=fabs(snakeX[0]-foody)+fabs(snakeY[0]-foodx);
if(min>cost) {
min=cost;
minDir=i;
}
}
snakeX[0]-=direx[i];
snakeY[0]-=direy[i];
}
if(min<9999)
return movable[minDir];
else return 0;
}
#makefile文件
.SUFFIXES: .c .o
CC=gcc
SRCS1=mysnake.c
OBJS1=$(SRCS1:.c=.o)
EXEC1=mysnake
all: $(OBJS1)
$(CC) -std=c99 -o $(EXEC1) $(OBJS1)
@echo '-------------ok--------------'
.c.o:
$(CC) -std=c99 -Wall -g -o $@ -c $<
clean:
rm -f $(OBJS1) $(EXEC1)