《贪吃蛇》毫无疑问是一款非常经典的游戏(虽然现在的贪吃蛇完全变了模样,游戏规则十分简单,但现在我尝试用C语言做出字符贪吃蛇游戏,才发现非常困难。以下便是我用C语言做贪吃蛇并不断将其改进的过程。
这便是我第一次写出的完整的贪吃蛇程序了,还没有开始完善,自然有很多不足,例如程序需要有输入才能继续运行(阻塞输入),输入一次运行一次。但至少我还是做出来了,心里还是很激动的(毕竟写代码五分钟,debug两小时),检查错误不知道花了多长时间。
#include
#include
#include
char map[12][13] = {
"************",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"************",
};
char blank = ' ';
char head = 'H';
char body = 'X';
char wall = '*';
char food = '$';
char M,N;
int headX = 1, headY = 1;
int headcopyX, headcopyY;
int bodyX[100] = {0}, bodyY[100] = {0};
int bodyLen = 0;
int foodX = 0, foodY = 0;
int willBeLonger = 0;
void printMap() { //打印地图
system("cls");
for (int i = 0; i < 12; ++i) {
printf("%s\n", map[i]);
}
}
void spawnFood() { //随机生成食物
// Random food position
foodX = rand() % 10 + 1;
foodY = rand() % 10 + 1;
while (map[foodX][foodY] != blank) {
foodX = rand() % 10 + 1;
foodY = rand() % 10 + 1;
}
map[foodX][foodY] = food;
}
void isGameOver(void) {
printf("\nGAME OVER!\n");
}
void HeadMove (char control) { //头部移动
headcopyX = headX;
headcopyY = headY;
switch (control) {
case 'w':
headX--;
N = 1;
break;
case 's':
headX++;
N = 1;
break;
case 'a':
headY--;
N = 1;
break;
case 'd':
headY++;
N = 1;
break;
default:
N = 0;
}
map[headcopyX][headcopyY] = blank;
M = map[headX][headY];
map[headX][headY] = head;
}
void beLonger () { //身体变长
bodyLen++;
bodyX[bodyLen - 1] = headcopyX;
bodyY[bodyLen - 1] = headcopyY;
map[bodyX[bodyLen - 1]][bodyY[bodyLen - 1]] = body;
}
void bodymove (void) { //身体移动
if (bodyLen > 0) {
map[bodyX[0]][bodyY[0]] = blank;
int i;
for(i = 0; i < bodyLen - 1; i++) {
bodyX[i] = bodyX[i + 1];
bodyY[i] = bodyY[i + 1];
map[bodyX[i]][bodyY[i]] = body;
}
bodyX[i] = headcopyX;
bodyY[i] = headcopyY;
map[bodyX[i]][bodyY[i]] = body;
}
}
int main() {
char control;
srand(time(NULL));
map[headX][headY] = head;
spawnFood();
printMap();
while(1) {
scanf("%c",&control);
HeadMove(control); //首先头部移动
if(N == 0) continue; //如果输入的字符不是'W','A','S','D'则跳过
if (M == food) { //如果吃到食物则身体加长,否则身体一动
beLonger();
spawnFood();
} else {
bodymove();
}
printMap();
if ( M == wall || M == body) { //判断头部是否撞墙或撞到身体
isGameOver();
break;
}
}
}
随后我将程序进行了一下改进,上网百度了很久终于得到了Windows下命令行非阻塞输入的办法,这下就不用按一步敲一次回车键了,没有输入的时候蛇依然会按照原来的方向移动。但新的问题也产生了:依靠系统命令“cls”完成的地图刷新会造成严重的闪屏,于是我又开始了百度之旅。。。。
while(1) {
Sleep(100);
if(!_kbhit()) {
control = control;
mainFuction(control);
} else {
control = _getch();
mainFuction(control);
}
if ( M == wall || M == body) { //判断头部是否撞墙或撞到身体
isGameOver();
break;
}
}
我把主要的移动和吃食物的操作都移入到mainFuction
函数里,利用_kbhit()
和_getch()
函数实现非阻塞输入。
这次完善更多了一点,刷新方式改变了,刷新速度得到了控制,因行进方向改变造成的延迟也消除了。总之闪屏是不会了,虽然有光标不断在闪但也无伤大雅。然而下面对于智能蛇的尝试更是令人抓狂。
void printMap() {
system("cls"); //清屏
for (int i = 0; i < 12; ++i) {
printf("%s\n", map[i]);
}
}
之前的刷新是前者,而后者则解决了闪屏的问题
void printMap() {
hStdout = GetStdHandle(STD_OUTPUT_HANDLE); //读取当前光标位置
cursorPos.X = 0;
for (int i = 0; i < 12; ++i) {
cursorPos.Y = i;
SetConsoleCursorPosition(hStdout, cursorPos); //移动光标
printf("%s\n", map[i]);
}
}
为什么说未完善呢,是因为大致思路、规划都已经完成了,就卡在写代码这一步。如果按照下面的伪代码写出智能蛇,一开始倒是没什么毛病,但仔细观察就会发现,这条蛇毫不注意就会钻进死胡同,撞到自己身体,游戏结束。
// 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]
}
所以又经过漫长的查阅之后,我发现把地图按照哈密顿回路的思路,竟然能100%成功,当然这里的时间并不是最短的,确实最稳的。然而我只做出了第一步。。。
char autoway(void) {
char control;
if(headY % 2 == 0) {
if(headX == 2) {
control = 'd';
if(headY == 10) control = 'w';
} else control = 'w';
} else {
if(headX == 10) {
control = 'd';
} else control = 's';
}
if(headX == 1) {
if(headY == 1) control = 's';
else control = 'a';
}
return control;
}
第一步也只是规划了最大的回路,保证包括到所有的点。智能蛇的路还很长。。。希望我能做出下图的贪吃蛇。