打造C语言版贪吃蛇(下)

接上篇打造C语言版贪吃蛇(中)

贪吃蛇的游戏主体已经打造完毕,下面就开始最后的工作了。
这篇我们的目标是给这条蛇加个简单的AI,实现文章开篇的智能寻路效果。

贪吃蛇AI的最终目的是吃满全图,吃满全图的过程中能越快越好。
这篇文章介绍的贪吃蛇AI可以实现简单的最短寻路。

最简单的寻路算法

既然我想快点吃到食物,那么算法中就不可避免得需要判断蛇头到食物的距离,最直接的想法就是用两者的横纵坐标分别相减的绝对值之和来作为评价函数来控制蛇头下一步的走位。若下一步使得两者横纵坐标分别相减的绝对值之和减少,那么很大概率会使得两者的真正要走的路程减少。

实现如下

可以考虑使用数组标记地图信息,墙角,障碍物以及蛇身标记为1表示不能走,使用另一个二维数组记录方向,表示下一步蛇头的坐标的变化。
地图初始化:

void initMap(char map[][MAP_HIGHT + 2]) {
    for (int i = 0; i < MAP_LENGTH + 2; ++ i) {
        for (int j = 0; j < MAP_HIGHT + 2; ++ j) {
            if (i == 0 || j == 0 || i == MAP_LENGTH + 1 || j == MAP_HIGHT + 1) {
                map[i][j] = 1;
            }
        }
    }
    for (int i = 2; i < snake.length; ++ i) {
        map[snake.x[i]][snake.y[i]] = 1;
    }
}

方向数组:
const int direction[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

const int direction[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

简单寻路:

void search(char map[][MAP_HIGHT + 2]) {
    int nextX;
    int nextY;
    for (int i = 0; i < 4; ++ i) {
        nextX = snake.x[0] + direction[i][0];
        nextY = snake.y[0] + direction[i][1];
        if (map[nextX][nextY] == 1) continue;
        if ( (abs(nextX - food.x) + abs(nextY - food.y))
            < (abs(snake.x[0] - food.x) + abs(snake.y[0] - food.y)) ) {
                snake.x[0] = nextX;
                snake.y[0] = nextY;
                return;
        }
    }
}

可以看到这样子做会出现很多bug,所以需要后期修正:

void snakeSmartMove() {
    char map[MAP_LENGTH + 2][MAP_HIGHT + 2] = {0};
    initMap(map);
    search(map);
    if (snake.x[0] == snake.x[1] && snake.y[0] == snake.y[1]) {
        for (int i = 0; i < 4; ++ i) {
            snake.x[0] = snake.x[0] + direction[i][0];
            snake.y[0] = snake.y[0] + direction[i][1];
            if (map[snake.x[0]][snake.y[0]] == 0) return;
            snake.x[0] = snake.x[0] - direction[i][0];
            snake.y[0] = snake.y[0] - direction[i][1];
        }
    }
}

当蛇头找不到路时,按顺序选择可选的道路。

后期预告

春节前会把第二篇和第三篇给补充完整,届时会给出更加智能的算法。
这篇以未完善的完整贪吃蛇源码(无注释)结尾。

/**********************************************************
*snake.c
*a snake game
*
*Created by penhison
*Copyright 2017 penhison@gmail.com
*All right reserved
*
**********************************************************/

/**********************************************************
*changelog:
*
*2017/12/19 initial release
*2017/12/20 make map partial refresh
*2017/12/21 add player rank
*2017/12/23 add cheat mode
*
**********************************************************/


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <windows.h>

#define MAP_LENGTH 40
#define MAP_HIGHT 30
#define BLANK_CELL ' '
#define WALL_CELL '*'
#define INIT_SNAKE_LENGTH 3
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define SNAKE_FOOD '$'
#define UP (input == 'W' || input == 'w' || input == 72)
#define DOWN (input == 'S' || input == 's' || input == 80)
#define LEFT (input == 'A' || input == 'a' || input == 75)
#define RIGHT (input == 'D' || input == 'd' || input == 77)
#define PAUSE (input == ' ')
#define CHEAT (input == 'C' || input == 'c')


void initSnake();
void initFood();
void printMap();
void printMode();
void initGame();

void printSnake();
void printFood();
void printLength();

void productFood();
void eatFood();

int inputValid(char input);
void snakeControlMove(char input);
void snakeAutoMove();
void snakeMove();
int snakeDie();

void startGame();
void selectMode();
void playGame();
void gamePause();
void gameOver();
void restart();

void locateCursor(int x, int y);
void switchCursorVisible();

void inputName();
void getTime();
void initRank();
void ranking(FILE * fp, struct Player player[]);
void printRank(FILE * fp, struct Player player[]);
void rank();

void switchCheatMode();
void snakeSmartMove();

struct Snake {
    int x[MAP_LENGTH * MAP_HIGHT + 1];
    int y[MAP_LENGTH * MAP_HIGHT + 1];
    int length;
    int speed;
};

struct Food {
    int x;
    int y;
    int exist;
};

struct Player {
    char name[20];
    char date[20];
    int score;
};


struct Snake snake;
struct Food food;
struct Player currentPlayer;
char mode[20];
int cheat;


int main() {
    startGame();
    while(1) {
        selectMode();
        playGame();
        gameOver();
        restart();
    }
    return 0;
}


void startGame() {
    printf("贪吃蛇游戏\n\n");
    printf("方向键或WASD控制\n");
    printf("长按加速\n");
    printf("按空格键暂停\n\n");
    printf("按任意键开始游戏\n");
    getch();
    inputName();
}

void selectMode() {
    system("cls");
    printf("请选择游戏模式:\n");
    printf("1. easy\n");
    printf("2. normal\n");
    printf("3. hard\n");
    printf("4. very hard\n");
    printf("5. crazy\n");
    printf("6. go die\n");
    char input = getch();
    switch (input) {
        case '1':
            snake.speed = 600;
            strcpy(mode, "easy");
            break;
        case '2':
            snake.speed = 300;
            strcpy(mode, "normal");
            break;
        case '3':
            snake.speed = 100;
            strcpy(mode, "hard");
            break;
        case '4':
            snake.speed = 50;
            strcpy(mode, "very hard");
            break;
        case '5':
            snake.speed = 20;
            strcpy(mode, "crazy");
            break;
        case '6':
            snake.speed = 10;
            strcpy(mode, "go die");
            break;
        default:
            snake.speed = 300;
            strcpy(mode, "normal");
            break;
    }
    return;
}

void playGame() {
    initGame();
    while (!snakeDie()) {
        printSnake();
        printFood();
        printLength();
        locateCursor(0, 1);
        putchar(WALL_CELL);
        locateCursor(0, MAP_HIGHT + 3);
        snakeMove();
        eatFood();
        productFood();
    }
}

void gamePause() {
    system("pause");
    locateCursor(0, MAP_HIGHT + 3);
    printf("                   ");
}

void gameOver() {
    system("cls"); 
    printf("\n\n\n\n Game over\n\n\n\n");
    printf("你的蛇长:%d\n", snake.length);
    printf("按空格键重玩\n\n");
    rank();
}

void restart() {
    char ch = 0;
    while(ch != ' ') {
        ch = getch();
    }
}


void initGame() {
    cheat = 0;
    system("cls");
    initSnake();
    initFood();
    printMap();
}

void initSnake() {
    snake.length = INIT_SNAKE_LENGTH;
    for (int i = 0; i <= snake.length; ++ i) {
        snake.x[i] = snake.length + 1 - i;
        snake.y[i] = 5;
    }
}

void initFood() {
    food.exist = 0;
    productFood();
}

void printMap() {
    printf("length:%d", snake.length);
    printMode();
    char map[MAP_HIGHT + 2][MAP_LENGTH + 2];
    for (int i = 0; i < MAP_HIGHT + 2; ++ i) {
        for (int j = 0; j < MAP_LENGTH + 2; ++ j) {
            if (i == 0 || i == MAP_HIGHT + 1 || j == 0 || j == MAP_LENGTH + 1)
                map[i][j] = WALL_CELL;
            else map[i][j] = BLANK_CELL;
            putchar(map[i][j]);
        }
        putchar('\n');
    }
}


void printMode() {
    int modeLength;
    modeLength = strlen(mode);
    for (int i = 0; i < 34 - modeLength; ++ i) {
        putchar(' ');
    }
    printf("%s\n", mode);
}

void printSnake() {
    locateCursor(snake.x[snake.length], snake.y[snake.length] + 1);
    putchar(BLANK_CELL);
    locateCursor(snake.x[0], snake.y[0] + 1);
    putchar(SNAKE_HEAD);
    locateCursor(snake.x[1], snake.y[1] + 1);
    putchar(SNAKE_BODY);
}

void printFood() {
    locateCursor(food.x, food.y + 1);
    putchar(SNAKE_FOOD);
}

void printLength() {
    locateCursor(7, 0);
    printf("%d", snake.length);
}


void productFood() {
    srand( (unsigned)time(NULL) );
    while (!food.exist) {
        food.x = rand() % MAP_LENGTH + 1;
        food.y = rand() % MAP_HIGHT + 1;
        food.exist = 1;
        for (int i = 0; i < snake.length; ++ i) {
            if (snake.x[i] == food.x && snake.y[i] == food.y) {
                food.exist = 0;
                break;
            }
        }
    }
}

void eatFood() {
    if (snake.x[0] == food.x && snake.y[0] == food.y) {
        food.exist = 0;
        snake.length += 1;
    }
}


int inputValid(char input) {
    if UP
        if (snake.y[0] > snake.y[2]) return 0;
    if DOWN
        if (snake.y[0] < snake.y[2]) return 0;
    if LEFT
        if (snake.x[0] > snake.x[2]) return 0;
    if RIGHT
        if (snake.x[0] < snake.x[2]) return 0;
    return 1;
}

void snakeControlMove(char input) {
    if UP snake.y[0] -= 1;
    if DOWN snake.y[0] += 1;
    if LEFT snake.x[0] -= 1;
    if RIGHT snake.x[0] += 1;
    return;
}

void snakeAutoMove() {
    if (cheat == 1) {
        snakeSmartMove();
        return;
    }
    if (snake.x[0] > snake.x[2]) snake.x[0] += 1;
    if (snake.x[0] < snake.x[2]) snake.x[0] -= 1;
    if (snake.y[0] > snake.y[2]) snake.y[0] += 1;
    if (snake.y[0] < snake.y[2]) snake.y[0] -= 1;
}

void snakeMove() {
    for (int i = snake.length; i > 0; -- i) {
        snake.x[i] = snake.x[i - 1];
        snake.y[i] = snake.y[i - 1];
    }
    char input = 0;
    int start = clock();
    while (clock() - start < snake.speed) {
        if (!kbhit()) continue;
        input = getch();
        if (!UP && !DOWN && !LEFT && !RIGHT && !PAUSE && !CHEAT)
            continue;
        if PAUSE {
            gamePause();
            continue;
        }
        if CHEAT {
            switchCheatMode();
            continue;
        }
        if (!inputValid(input)) {
            continue;
        }
        snakeControlMove(input);
        return;
    }
    snakeAutoMove();
}

int snakeDie() {
    if (snake.x[0] == 0 || snake.x[0] == MAP_LENGTH + 1
        || snake.y[0] == 0 || snake.y[0] == MAP_HIGHT + 1)
        return 1;
    for(int i = 1; i < snake.length; ++ i) {
        if( (snake.x[0] == snake.x[i]) && (snake.y[0] == snake.y[i]) )
            return 1;
    }
    return 0;
}


void locateCursor(int x, int y) {
    COORD cor;
    cor.X = x;
    cor.Y = y;
    HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hout, cor);
}

void switchCursorVisible() {
    HANDLE hout;
    hout = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cci;
    GetConsoleCursorInfo(hout, &cci);
    cci.bVisible = 0;
    SetConsoleCursorInfo(hout, &cci);
}


void rank() {
    currentPlayer.score = snake.length;
    getTime();
    struct Player player[5];
    FILE * fp;
    fp = fopen(mode, "r");
    if (fp == NULL) {
        fclose(fp);
        initRank();
        fp = fopen(mode, "r");
    }
    ranking(fp, player);
    printRank(fp, player);
    fclose(fp);
}

void inputName() {
    system("cls");
    printf("Please input you name\n");
    scanf("%s", currentPlayer.name);
    switchCursorVisible();
}

void getTime() {
    struct tm * date;
    time_t currentTime;
    time(&currentTime);
    date = localtime(&currentTime);
    strftime(currentPlayer.date, 20, "%Y/%m/%d %H:%M:%S", date);
}

void initRank() {
    FILE * fp;
    fp = fopen(mode, "w");
    for (int i = 0; i < 5; ++ i) {
        fprintf(fp, "%s\t%d\t", "anonymous", 0);
        fprintf(fp, "%s %s\n", "2017/12/22", "00:00:00");
    }
    fclose(fp);
}

void ranking(FILE * fp, struct Player player[]) {
    for (int i = 0; i < 5; ++ i) {
        fscanf(fp, "%s\t%d\t", player[i].name, &player[i].score);
        fgets(player[i].date, 20, fp);
    }
    for (int i = 0; i < 5; ++ i) {
        if (currentPlayer.score >= player[i].score) {
            for (int j = 4; j > i; -- j) {
                strcpy(player[j].name, player[j - 1].name);
                strcpy(player[j].date, player[j - 1].date);
                player[j].score = player[j - 1].score;
            }
            strcpy(player[i].name, currentPlayer.name);
            strcpy(player[i].date, currentPlayer.date);
            player[i].score = currentPlayer.score;
            break;
        }
    }
}

void printRank(FILE * fp, struct Player player[]) {
    printf("\nrank:\n\n");
    printf("%-10s %s\n\n", "player", "score");
    for (int i = 0; i < 5; ++ i) {
        printf("%-10s %5d  %s\n", player[i].name, player[i].score,
                player[i].date);
    }
    fclose(fp);
    fp = fopen(mode, "w");
    for (int i = 0; i < 5; ++ i) {
        fprintf(fp, "%s\t%d\t%s\n", player[i].name, player[i].score,
                player[i].date);
    }
}


void switchCheatMode() {
    cheat = !cheat;
    if (cheat) {
        locateCursor(16, 0);
        printf("cheat mode");
    }
    else {
        locateCursor(16, 0);
        printf("          ");
    }
    return;
}

const int direction[4][2] = {{1, 0}, {0, 1}, {0, -1}, {-1, 0}};
#define f(x) (x->g + x->h)
#define abs(number) number > 0 ? number : -number

struct node {
    char x;
    char y;
    char g;
    char h;
    char isBlock;
    char inOpenTable;
    char inCloseTable;
    struct node * parent;
};

struct Result {
    int x;
    int y;
    int found;
};

struct node mapNode[MAP_LENGTH + 2][MAP_HIGHT + 2];
struct node * openTable[1200];
struct node * closeTable[1200];
int openTableCount;
int closeTableCount;

void initMapNode() {
    for (int i = 0; i < MAP_LENGTH + 2; ++ i) {
        for (int j = 0; j < MAP_HIGHT + 2; ++ j) {
            mapNode[i][j].x = i;
            mapNode[i][j].y = j;
            mapNode[i][j].g = 0;
            mapNode[i][j].h = 0;
            mapNode[i][j].inOpenTable = 0;
            mapNode[i][j].inCloseTable = 0;
            mapNode[i][j].parent = NULL;
            if (i == 0 || i == MAP_LENGTH + 1 || j == 0 || j == MAP_HIGHT + 1)
                mapNode[i][j].isBlock = 1;
            else mapNode[i][j].isBlock = 0;
        }
    }
    for (int i = 1; i < snake.length; ++ i) {
        mapNode[snake.x[i]][snake.y[i]].isBlock = 1;
    }
}

void swap(int x1, int x2) {
    struct  node * temp = openTable[x1];
    openTable[x1] = openTable[x2];
    openTable[x2] = temp;
}

void heapSort(int nodeIndex) {
    int current = nodeIndex;
    int parent = (current - 1) / 2;
    int child = current * 2 + 1;
    while (child < openTableCount) {
        if ( (child + 1 < openTableCount) && (f(openTable[child + 1]) < f(openTable[child])) )
            child++;
        if (f(openTable[current]) <= f(openTable[child])) break;
        else {
            swap(current, child);
            current = child;
            child = current * 2 + 1;
        }
    }
    if (current != nodeIndex) return;
    while (current != 0) {
        if (f(openTable[current]) >= f(openTable[parent])) break;
        else {
            swap(current, parent);
            current = parent;
            parent = (current - 1) / 2;
        }
    }
}

void insertToOpenTable(int x, int y, struct node * currentNode, struct node * endNode) {
    if (!mapNode[x][y].isBlock && !mapNode[x][y].inCloseTable) {
        if (mapNode[x][y].inOpenTable) {
            if (mapNode[x][y].g > currentNode->g + 1) {
                mapNode[x][y].g = currentNode->g + 1;
                mapNode[x][y].parent = currentNode;
                int i;
                for (i = 0; i < openTableCount; ++ i) {
                    if (openTable[i]->x == x && openTable[i]->y == y) break;
                }
                heapSort(i);
            }
        }
        else {
            mapNode[x][y].g = currentNode->g + 1;
            mapNode[x][y].h = abs(endNode->x - x) + abs(endNode->y - y);
            mapNode[x][y].parent = currentNode;
            mapNode[x][y].inOpenTable = 1;
            openTable[openTableCount++] = &(mapNode[x][y]);
            heapSort(openTableCount - 1);
        }
    }
}

void getNeighbors(struct node * currentNode, struct node * endNode) {
    for (int i = 0; i < 4; ++ i) {
        int tempx = currentNode->x + direction[i][0];
        int tempy = currentNode->y + direction[i][1];
        insertToOpenTable(tempx, tempy, currentNode, endNode);
    }
}

struct Result search(int startX, int startY, int goalX, int goalY) {
    struct Result result;
    initMapNode();
    struct node * startNode;
    struct node * endNode;
    struct node * currentNode;
    openTableCount = 0;
    closeTableCount = 0;
    startNode = &(mapNode[startX][startY]);
    endNode = &(mapNode[goalX][goalY]);
    endNode->isBlock = 0;
    openTable[openTableCount++] = startNode;
    startNode->h = abs(startNode->x - endNode->x) + abs(startNode->y - endNode->y);
    startNode->inOpenTable = 1;
    while(1) {
        currentNode = openTable[0];
        openTable[0] = openTable[--openTableCount];
        heapSort(0);
        closeTable[closeTableCount++] = currentNode;
        currentNode->inCloseTable = 1;
        if (currentNode->x == endNode->x && currentNode->y == endNode->y) {
            result.x = currentNode->parent->x;
            result.y = currentNode->parent->y;
            result.found = 1;
            break;
        }
        getNeighbors(currentNode, endNode);
        if (openTableCount == 0) {
            result.found = 0;
            break;
        }
    }
    return result;
}

void wander() {
    int nextX;
    int nextY;
    for (int i= 0; i < 4; ++ i) {
        nextX = snake.x[0] + direction[i][0];
        nextY = snake.y[0] + direction[i][1];
        initMapNode();
        if (mapNode[nextX][nextY].isBlock) continue;
        if (search(snake.x[snake.length - 1], snake.y[snake.length - 1], nextX, nextY).found) {
            snake.x[0] = nextX;
            snake.y[0] = nextY;
            return;
        }
    }
}

void snakeSmartMove() {
    struct Result findSnakeHead;
    struct Result findSnakeTail;
    findSnakeHead = search(food.x, food.y, snake.x[0], snake.y[0]);
    if (findSnakeHead.found == 1) {
        findSnakeTail = search(snake.x[snake.length - 1], snake.y[snake.length - 1], findSnakeHead.x, findSnakeHead.y);
        if (findSnakeTail.found == 1) {
            snake.x[0] = findSnakeHead.x;
            snake.y[0] = findSnakeHead.y;
            return;
        }
    }
    wander();
    return;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值