一条贪吃蛇的自我修养——从手动皮皮蛇到智能皮皮蛇

不知道大家当初选择学习C语言是出于什么目的,我的目的比较简单粗暴,就是为了以后能自己设计游戏。然!鹅!学了之后才发现一个简单的贪吃蛇也能让我累死累活,不过经历了我的仔细琢磨研究,总算是完成了贪吃蛇的代码,以及后续的升级版——智能蛇(伪)(说伪是因为我用的算法做出来的蛇比较傻)。
闲话不多说,让我们先来看看一个最基础的皮皮蛇是如何构建的吧。
首先我们规定蛇的活动范围为10x10,蛇头为“H”,蛇身为“X”,食物为“$”,阻碍物为“*”。
我们的第一个目标是做一条能接收指令自己动的蛇;
让我们先书写它的伪代码

 输出字符矩阵
    WHILE not 游戏结束 DO
        ch=等待输入
        CASE ch DO
        ‘A’:左前进一步,break 
        ‘D’:右前进一步,break    
        ‘W’:上前进一步,break    
        ‘S’:下前进一步,break    
        END CASE
        输出字符矩阵
    END WHILE
    输出 Game Over!!! 

然后我们按照伪代码先写好总控代码:

int main()
{
    char choice;
    printmap();
    while (running) {
        scanf("%c", &choice);
        move(choice);
        printmap();
    }
    return 0;
}

这里运用了自顶向下的思路,写完总体我们再分别书写各个函数,条理就会显得比较清晰,最后的成品如下:

#include <stdio.h>
#include<stdlib.h>
#include<time.h>
char map[12][13] = {
    "************",
    "*XXXXH     *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "************"
};
int bodyx[5] = { 1,1,1,1 };
int bodyy[5] = { 4,3,2,1 };
int headx = 1, heady = 5;
int running = 1, len = 5;
void printmap() {
    system("cls");
    for (int i = 0; i < 12; i++)
        printf("%s\n", map[i]);
}
void gameover() {
    printf("Game over!\n");
    running = 0;
}
void move(char ctrl) {
    int i = 0;
    int prex = headx, prey = heady;
    switch (ctrl) {
    case 'A':case 'a':
        heady--;
        break;
    case 'W':case 'w':
        headx--;
        break;
    case 'D':case 'd':
        heady++;
        break;
    case 'S':case 's':
        headx++;
        break;
    default:
        return;
    }
    if (map[headx][heady] != ' ')
        gameover();
    else {
        map[headx][heady] = 'H';
        map[prex][prey] = map[bodyx[i]][bodyy[i]];
        for (i; i < len - 2; i++) {
            map[bodyx[i]][bodyy[i]] = map[bodyx[i + 1]][bodyy[i + 1]];
        }
        map[bodyx[i]][bodyy[i]] = ' ';
        for (i = len - 3; i >= 0; i--) {
            bodyx[i + 1] = bodyx[i];
            bodyy[i + 1] = bodyy[i];
        }
        bodyx[0] = prex;
        bodyy[0] = prey;
    }
}
int main()
{
    char choice;
    printmap();
    while (running) {
        scanf("%c", &choice);
        move(choice);
        printmap();
    }
    return 0;
}

完成了会动的蛇,我们当然不能忘了贪吃蛇的精髓,吃了食物身体会变长。
有了第一条代码做基础,如果你能理解第一条代码中move函数的意图,那么完成这一步将会变得十分轻松。
我们只需增加一个food函数,这要用到随机数,因为食物的put是在随机位置的。
那么,我们就有:

void food() {
    foodx = rand() % 10 + 1;
    foody = rand() % 10 + 1;
    while (map[foodx][foody] != ' ') {
        foodx = rand() % 10 + 1;
        foody = rand() % 10 + 1;
    }
    map[foodx][foody] = '$';
}

然后我们还要考虑吃了食物的蛇身体会变长,我们对move函数稍加改进:

void move(char ctrl) {
    int i = 0;
    int prex = headx, prey = heady;
    switch (ctrl) {
    case 'A':case 'a':
        heady--;
        break;
    case 'W':case 'w':
        headx--;
        break;
    case 'D':case 'd':
        heady++;
        break;
    case 'S':case 's':
        headx++;
        break;
    default:
        return;
    }
    if (map[headx][heady] != ' '&&map[headx][heady] != '$')
        gameover();
    else if (map[headx][heady] == ' ') {
        map[headx][heady] = 'H';
        map[prex][prey] = map[bodyx[i]][bodyy[i]];
        for (i; i < len - 2; i++) {
            map[bodyx[i]][bodyy[i]] = map[bodyx[i + 1]][bodyy[i + 1]];
        }
        map[bodyx[i]][bodyy[i]] = ' ';
        for (i = len - 3; i >= 0; i--) {
            bodyx[i + 1] = bodyx[i];
            bodyy[i + 1] = bodyy[i];
        }
        bodyx[0] = prex;
        bodyy[0] = prey;
    }
    else {
        map[headx][heady] = 'H';
        map[prex][prey] = map[bodyx[i]][bodyy[i]];
        for (i; i < len - 2; i++) {
            map[bodyx[i]][bodyy[i]] = map[bodyx[i + 1]][bodyy[i + 1]];
        }
        map[bodyx[i]][bodyy[i]] = 'X';
        len++;
        for (i = len - 3; i >= 0; i--) {
            bodyx[i + 1] = bodyx[i];
            bodyy[i + 1] = bodyy[i];
        }
        bodyx[0] = prex;
        bodyy[0] = prey;
        food();
    }
}

完成这些,再把food函数放到main里的正确位置,一个会吃的蛇就大功告成了。

然后我们进入第三步,升华这只皮皮蛇的蛇生!
我们还是先来写个伪代码,关于让他自动寻路的算法的伪代码:

     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]
    }

我们依然只需要写一个函数,再将它插入到main函数中正确的位置即可
以下是我写的wheregonext函数:

char wheregonext(int hx, int hy, int fx, int fy) {
    int p = 0, min = 100;
    char movable[4] = { 'a','w','d','s' };
    int distance[4] = { 0 };
    distance[0] = abs(fx - hx) + abs(fy - (hy - 1));
    if (distance[0] <= min && (map[hx][hy - 1] == ' ' || map[hx][hy - 1] == '$')) {
        min = distance[0];
        p = 0;
    }
    else
        min = min;
    distance[1] = abs(fx - (hx - 1)) + abs(fy - hy);
    if (distance[1] <= min && (map[hx - 1][hy] == ' ' || map[hx - 1][hy] == '$')) {
        min = distance[1];
        p = 1;
    }
    else
        min = min;
    distance[2] = abs(fx - hx) + abs(fy - (hy + 1));
    if (distance[2] <= min && (map[hx][hy + 1] == ' ' || map[hx][hy + 1] == '$')) {
        min = distance[2];
        p = 2;
    }
    else
        min = min;
    distance[3] = abs(fx - (hx + 1)) + abs(fy - hy);
    if (distance[3] <= min && (map[hx + 1][hy] == ' ' || map[hx + 1][hy] == '$')) {
        min = distance[3];
        p = 3;
    }
    else
        min = min;
    return movable[p];
}

此时的主函数稍微修改一下:

#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include<math.h>
#include<windows.h>
int main()
{
    char choice;
    srand(time(NULL));
    food();
    printmap();
    while (running) {
        Sleep(40);
        choice = wheregonext(headx, heady, foodx, foody);
        move(choice);
        printmap();
    }
    return 0;
}

这是在Windows下书写的智能蛇,当我们改用Linux环境时,就要进行一些小改动。
比如头文件windows.h在linux中不存在,我们可以使用unist.h,还要注意的是,两个头文件中的sleep函数用法不一样;再比如清屏,Windows中用system(“cls”)即可,而Linux中可以使用VT100终端中的printf(“\033[2J”)实现清屏。关于VT100终端标准,有兴趣的可以自行浏览http://www.cnblogs.com/zengjfgit/p/4373564.html
在Linux中,完成智能贪吃蛇,我们还要实现kbhit函数,这对于我们初学者来说难度太大,所以这里给好了代码,我们只需将自己在Windows下的智能蛇代码融入即可(maybe):

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <termios.h>
#include <unistd.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 代码放在这里

int main()
{
        //设置终端进入非缓冲状态
        int tty_set_flag;
        tty_set_flag = tty_set();

        //将你的 snake 代码放在这里
        printf("pressed `q` to quit!\n");
        while(1) {

                if( kbhit() ) {
                        const int key = getchar();
                        printf("%c pressed\n", key);
                        if(key == 'q')
                                break;
                } else {
                       ;// fprintf(stderr, "<no key detected>\n");
                }
        }

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

最后,一个能在Linux系统下自行活动的蛇就完成了,当然,我们这里用的算法是比较傻的,能拿多少分完全随缘,就像我的一次游戏体验:
这里写图片描述
(我觉得这水平我还是可以碾压的)
更高级的算法还是留给有兴趣的的人去挑战吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值