貪吃蛇之智能蛇的實現

歷經很長時間,在下終於把貪吃蛇人工控制的版本寫好,雖然略顯粗糙,但也爲自己初步寫好一個簡單遊戲高興不已。近日,又嘗試了智能蛇的實現。
智能蛇與人工蛇很相似,不同之處在於一個不需要手動輸入字符,另一個則需要手動控制方向。因此也就導致了兩者關於移動(move)的算法不盡相同。

先來看智能蛇在判定移動方向時的僞代碼:
Hx,Hy: 頭的位置
Fx,Fy:食物的位置
function whereGoNext(Hx,Hy,Fx,Fy) {
用數組movable[4]={“a”,”d”,”w”,”s”} 記錄可走的方向
用數組distance[4]={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]
}
更細化的來看,智能蛇與人工蛇的區別也只在於判定方向的不同;即,智能蛇靠分辨距離自動判定方向,而人工蛇靠人腦分析手動輸入方向。在如何移動這一點,兩者算法是相同的。
因爲不需要手動輸入字符控制移動,總控代碼也需要做相應改變:
輸出字符矩陣
WHILE not 遊戲結束 DO
wait(time)
ch=whereGoNext(Hx,Hy,Fx,Fy)
CASE ch DO
‘A’:左前進一步,break
‘D’:右前進一步,break
‘W’:上前進一步,break
‘S’:下前進一步,break
END CASE
輸出字符矩陣
END WHILE
輸出 Game Over!!!

在初期,我按照上述的僞代碼寫出了1.0版本,卻發現一瞬間蛇就已經走過了,因此,我決定用sleep函數,以便更好地觀察蛇的走向。
以下是最初版的wheregonext函數代碼:

// 在全局變量中用數組movable[4]={“a”,”d”,”w”,”s”} 記錄可走的方向,用數組distance[4]={0,0,0,0} 記錄離食物的距離
char wheregonext(int hx, int hy, int fx, int fy) {// Hx,Hy: 頭的位置
    // Fx,Fy:食物的位置
    int p = 0, min = 9999;
    int i;
    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食物位置
    for (i = 0; i < 4; i++){
        if (distance[i] <= min) {
            min = distance[i];
            p = i;
        }
    }// 選擇distance中存最小距離的下標p,注意最小距離不能是9999
    min = 9999;
    return movable[p];  // 返回 movable[p]
}

這樣,一個至少會吃東西的智能蛇就做出來了。可是,智能蛇的表現好像有些不盡人意。
在測試中,這隻蛇總會出現回頭的現象,即撞向自己的身子,這種自殺式表演顯然不是我們想要的。

思前想後,我對函數作出了些許優化,當確保不會碰到身子或者邊界時再移動:
將原函數改爲

// 在全局變量中用數組movable[4]={“a”,”d”,”w”,”s”} 記錄可走的方向,用數組distance[4]={0,0,0,0} 記錄離食物的距離
char wheregonext(int hx, int hy, int fx, int fy) {// Hx,Hy: 頭的位置
    // Fx,Fy:食物的位置
    int p = 0, min = 9999;
    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 = 0;
        }
    if (distance[1] <= min && (map[hy][hx + 1] == ' ' || map[hy][hx + 1] == '$')) {
            min = distance[1];
            p = 1;
        }
    if (distance[2] <= min && (map[hy - 1][hx] == ' ' || map[hy - 1][hx] == '$')) {
            min = distance[2];
            p = 2;
        }
    if (distance[3] <= min && (map[hy + 1][hx] == ' ' || map[hy + 1][hx] == '$')) {
            min = distance[3];
            p = 3;
        }
    // 選擇distance中存最小距離的下標p,最小距離不能是9999
    min = 9999;
    return movable[p];  // 返回 movable[p]
}

這樣,就避免了自殺的尷尬。
至此,智能蛇的設計就初步完成了,順便加上sleep函數和清空函數,一隻莽撞的智能蛇就出爐了!
代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include<time.h>
#include<math.h>
#include<Windows.h>
#define SNAKE_MAX_LENGTH 50
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define SNAKE_FOOD '$'
#define WALL_CELL '*'


char map[12][13] = {
    "************",
    "*XXXXH     *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "************" };
//全局變量:初始狀態表
char movable[4] = { 'a', 'd', 'w', 's' };
int distance[4] = { 0 };
int food = 0;//定義開始時食物數量爲0
int x = 0, y = 0;//定義食物的座標(x,y)
int snakex[SNAKE_MAX_LENGTH] = { 5, 4, 3, 2, 1 };//蛇身的橫座標
int snakey[SNAKE_MAX_LENGTH] = { 1, 1, 1, 1, 1 };//蛇身的縱座標
int lastX = 1, lastY = 1;//表示蛇尾部的座標
int length = 5;//定義初始長度,在該變量完成蛇長的變化
char wheregonext(int x, int y, int z, int m);
void move(int x, int y);//蛇移動函數
void output(void);//蛇移動效果打印函數
void clear(void);//蛇移動前的蛇圖像清空
void printmap(void);//打印
int gameover(void);//判定遊戲結束(碰到身子或邊界)
void snakefood(void);//用來生成食物
void eatfood(void);//蛇吃食物後身體增長
int main() {
    int i;
    snakefood();
    printmap();
    //打印初始狀態圖
    char c;
    int a = 0, b = 0;
    while (1)
    {
        snakefood();
        c = wheregonext(snakex[0], snakey[0], x, y);
        Sleep(30);
        switch (c){//進行移動
        case 'a'://左移時
            a = 1;
            b = 0;
            snakefood();
            move(a, b);
            if (gameover())//判定遊戲是否結束
            {
                printf("\nGAME OVER!!!\n");
                Sleep(1000);
                return 0;
            }
            else
            {
                system("cls");
                printmap();
            }
            break;
        case 'd'://右移
            b = 1;
            a = 0;
            snakefood();
            move(a, b);
            if (gameover())//判定遊戲是否結束
            {
                printf("\nGAME OVER!!!\n");
                Sleep(1000);
                return 0;
            }
            else
            {
                system("cls");
                printmap();
            }
            break;
        case 's'://上移
            a = -1;
            b = 0;
            snakefood();
            move(a, b);

            if (gameover())//判定遊戲是否結束
            {
                printf("\nGAME OVER!!!\n");
                Sleep(1000);
                return 0;
            }
            else
            {
                system("cls");
                printmap();
            }
            break;
        case 'w'://下移
            b = -1;
            a = 0;
            snakefood();
            move(a, b);
            if (gameover())//判定遊戲是否結束
            {
                printf("\nGAME OVER!!!\n");
                Sleep(1000);
                return 0;
            }
            else
            {
                system("cls");
                printmap();
            }

            break;
        }

    }//判定貪吃蛇的移動(通過move函數實現)並通過output函數實現map上的變化


}
// 在全局變量中用數組movable[4]={“a”,”d”,”w”,”s”} 記錄可走的方向,用數組distance[4]={0,0,0,0} 記錄離食物的距離
char wheregonext(int hx, int hy, int fx, int fy) {// Hx,Hy: 頭的位置
    // Fx,Fy:食物的位置
    int p = 0, min = 9999;
    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 = 0;
        }
    if (distance[1] <= min && (map[hy][hx + 1] == ' ' || map[hy][hx + 1] == '$')) {
            min = distance[1];
            p = 1;
        }
    if (distance[2] <= min && (map[hy - 1][hx] == ' ' || map[hy - 1][hx] == '$')) {
            min = distance[2];
            p = 2;
        }
    if (distance[3] <= min && (map[hy + 1][hx] == ' ' || map[hy + 1][hx] == '$')) {
            min = distance[3];
            p = 3;
        }
    // 選擇distance中存最小距離的下標p,最小距離不能是9999
    min = 9999;
    return movable[p];  // 返回 movable[p]
}
void move(int a, int b){
    int i;
    if (a == 1 && b == 0)
    {
        clear();//清空map
        lastX = snakex[length - 1];
        lastY = snakey[length - 1];//記錄當前蛇尾座標
        for (i = length - 1; i >= 1; i--)
        {
            snakex[i] = snakex[i - 1];
            snakey[i] = snakey[i - 1];//移動
        }
        snakex[0]--;//蛇頭移動
        eatfood();//判斷是否吃了食物
        output();//移動完成
    }
    if (a == 0 && b == 1)
    {
        clear();
        lastX = snakex[length - 1];
        lastY = snakey[length - 1];
        for (i = length - 1; i >= 1; i--)
        {
            snakex[i] = snakex[i - 1];
            snakey[i] = snakey[i - 1];
        }
        snakex[0]++;
        eatfood();
        output();
    }
    if (a == -1 && b == 0)
    {
        clear();
        lastX = snakex[length - 1];
        lastY = snakey[length - 1];
        for (i = length - 1; i >= 1; i--)
        {
            snakex[i] = snakex[i - 1];
            snakey[i] = snakey[i - 1];
        }
        snakey[0]++;
        eatfood();
        output();
    }
    if (a == 0 && b == -1)
    {
        clear();
        lastX = snakex[length - 1];
        lastY = snakey[length - 1];
        for (i = length - 1; i >= 1; i--)
        {
            snakex[i] = snakex[i - 1];
            snakey[i] = snakey[i - 1];
        }
        snakey[0]--;
        eatfood();
        output();
    }

}

void clear(void){
    int i;
    for (i = 0; i < length; i++)
        map[snakey[i]][snakex[i]] = BLANK_CELL;//將蛇原來位置清空
}
void output(void){
    int i;
    map[snakey[0]][snakex[0]] = SNAKE_HEAD;
    for (i = 1; i < length; i++)
        map[snakey[i]][snakex[i]] = SNAKE_BODY;//蛇移動後的位置

}
void printmap(void){
    printf("\n\n\n\n\n\n\n\n\n\n\n\n");
    int i;
    for (i = 0; i < 12; i++)
    {
        printf("%s\n", map[i]);
    }
    //蛇可以完成移動,並打印
}
int gameover(void){
    int i;
    int fail = 0;
    for (i = 1; i<length; i++)//用來判斷是否頭碰身子
    {
        if (snakex[0] == snakex[i] && snakey[0] == snakey[i])
            fail = 1;
    }
    if (snakex[0]>10 || snakey[0] > 10 || snakex[0] < 1 || snakey[0] < 1 || fail == 1)//前四個條件爲是否觸碰邊界,後一個條件判斷是否頭碰身子
        return 1;
    else
        return 0;
}
void snakefood(void){
    srand(time(NULL));

    if (food == 0)
    {
        x = rand() % 10 + 1;
        y = rand() % 10 + 1;
        if (map[y][x] == ' ')//確保在空白位置出現食物
        {
            map[y][x] = SNAKE_FOOD;//在地圖可到達位置上隨機生成食物
            food++;//確保每次只出現一個食物
        }

    }
}
void eatfood(void){
    if (snakey[0] == y&&snakex[0] == x)//判斷蛇是否吃到食物
    {
        length++;//蛇長增加一個
        food = 0;
        snakex[length - 1] = lastX;
        snakey[length - 1] = lastY;
    }
}

在windows下的成果圖:(在linux下,則需要調用其它函數庫而不是windows庫,相應函數也要做出調整)
這裏寫圖片描述

可是,很多bug依舊沒有解決。
如gif圖所示,蛇會不由自主地走進死衚衕,在算法的處理上顯然還有很多不足之處。如老師所說,貪吃蛇將會陪伴我們大學四年,我也會在以後不斷優化算法的設計。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章