1. 程式人生 > >貪吃蛇C語言原始碼與演算法分析

貪吃蛇C語言原始碼與演算法分析

經典的貪吃蛇遊戲演算法,無疑是一個較大的挑戰,綜合性較高,像我這種剛入門C語言的也差不多花了整整一週時間才差不多理解透徹,內部包含了較多的函式,陣列,二維陣列,迴圈等思想。
Github專案地址:https://github.com/knightyun/gluttonousSnake

接下來以C語言為例,針對此演算法擷取程式碼片段進行詳細分析,原始碼位於文章底部

演算法分析

概述

首先分析一下,貪吃蛇最基本和重要的動作,一段在螢幕上移動和轉向的軀幹,但是C語言沒有移動字元的函式,只能不斷向螢幕列印輸出和清屏實現移動,軀幹位置在螢幕上的變化可以用二維座標系實現,用一個二維陣列儲存螢幕所有可見內容的x,y座標,並賦予幾種初始值,然後用函式打印出各種值對應的字元,再用迴圈和座標值自增自減實現移動。使用輸入函式判斷方向,隨機函式生成食物,根據頭部座標判斷撞牆或吃到自己而結束遊戲。

標頭檔案

Windows環境中需要包含的標頭檔案:

#include<stdio.h>     
#include<stdlib.h> 
#include<Windows.h>       /*需要使用system("cls")清屏函式*/
#include<conio.h>         /*非標準庫函式,VS中自帶,需要使用_getch()函式獲取輸入*/
#include<time.h>          /*使用隨機數函式需要使用time()函式*/
預定義

遊戲內可自定義設定介面寬度與高度,需要給二維陣列定義一個最大值:

#define MAXX 10000                /* 定義遊戲最大介面寬度座標值 */
#define MAXY 10000                /* 定義最大高度 */
申明變數

需要用到的變數和功能如下:

int speed = 10;                    /* 設定蛇移動速度 */
int mapArr[MAXX][MAXY];            /* 儲存介面座標值的二維陣列 */
int inputX = 50, inputY = 20;      /* 預設遊戲介面寬度和高度 */
int randX = -1, randY = -1;        /* 生成的隨機食物的座標值 */
int foodFlag = 0; /* 判斷是否更新食物 */ int sx = 1, sy = 1; /* 設定蛇身座標值 */ int l = 0; /* 蛇身長度 */ int *body[MAXX * MAXY]; /* 儲存蛇身的指標陣列 */ char input = '6'; /* 預設移動方向 */ int overFlag = 1; /* 判斷遊戲結束,撞牆或吃到自己 */ int moveX = 1, moveY = 1; /* 遊戲開始的動畫效果座標值 */ int moveFlag = 0; /* 開始動畫的迴圈判斷 */

這裡使用了指標陣列控制蛇身,方便賦值
陣列中的引數雖然不能是變數,但是可以是巨集定義

函式

接下來就是重要環節了,分析實現遊戲效果的各個函式。

  • 第一步,定義初始化函式 InitMap() 將螢幕上每個點通過二維陣列賦予座標值 x、y,確定遊戲介面的大小,我們將四周的牆賦值為 1,中間空白賦值為 0,蛇身賦值為 2,隨機出現的食物賦值為 3
  • 第二步,定義 PrintMap() 函式給每個 座標值列印對應的字元,我們將牆用字元 + 表示,空白用空字元 " " 表示,蛇身用星號 * 表示,食物用字元 @ 表示。
  • 第三步,定義函式 StartMsg() 顯示螢幕資訊,提示控制的按鍵。
  • 第四步,定義函式 GetSet() 使玩家可以自定義遊戲介面寬和高和移動速度,使用 scanf() 函式獲取並改變預設介面尺寸。
  • 第五步,定義函式 SetRandNum() 在介面中隨機出現食物,並且不與蛇身和牆重疊,使用 srand(time(0)) 初始隨機函式,然後用一個迴圈不斷用隨機函式 rand() 生成隨機座標值,直到所生成位置是空白為止。
  • 第六步,最核心的演算法,較為複雜,定義函式 SetSnakeNum() 設定蛇身的座標值,並通過通過輸入判斷前進方向,以設定的速度時間間隔不斷自增或自減座標值實現移動,裡面用到了 _kbhit() 函式,作用是:有使用者輸入時,返回值為,無輸入時值為。還有 _getch() 函式與 getchar() 的區別:_getch() 輸入值後不用輸入回車就能獲取輸入值。感覺最不好理解的就是蛇身的轉彎演算法,我的方法是:蛇身每一節在每一次迴圈不斷繼承前一節的值,然後蛇頭位置不斷獲得新座標值,這樣就能實現身體的轉向,這裡就可以用到之前定義的指標陣列 *body[] 來實現。演算法中還需要注意的一點是,蛇身朝某個方向移動時,只能控制另外兩個方向,例如向右移動時不能控制向左移動。然後隨後的函式就好說了。
  • 第七步,定義函式 EatFood() 實現遇到食物座標值時,增加一節蛇身長度值 l
  • 第八步,定義函式 StartGame() 來綜合之前的函式並開始遊戲,需要通過 overFlag 判斷遊戲結束。
  • 第九步,定義函式 SetMoveNum() 實現遊戲開始時的動畫效果,對於遊戲存在意義不大,僅供研究訓練思維和演算法。
  • 第十步,定義函式 JudgeEnd() 判斷之前的函式 SetMuveNum() 的結束時刻,然後不斷迴圈動畫效果。
  • 第十一步,定義函式 StartView() 開始遊戲,按任意鍵遊戲正式開始。

下面是函式申明:

void InitMap();                   /* initialize the background coordinate system */
void PrintMap();                  /* print every point in the arr mapArr to the screen */
void StartMsg();                  /* start message */
void GetSet();                    /* judge whether to edit the game setting */
void SetRandNum();                /* set a random 'food' point in the screen */
void SetSnakeNum();               /* the most complex and important algorithm of this game */
void EatFood();                   /* judge when to eat food and elongate the body */
void StartGame();                 /* start the game */
void SetMoveNum();                /* algorithm of the start animation, some complex */
void JudgeEnd();                  /* judge the end of the animation and loop again*/
void StartView();                 /* start the start animation view */

原始碼

#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>                /* use the function 'system("cls")' to clear screen */
#include<conio.h>                 /* use the function '_getch()' to get input */
#include<time.h>                  /* the random function need the 'time()' function */
#include "public-fun.h"

#define MAXX 10000                /* define the max width of game space */
#define MAXY 10000                /* define the max height */

void InitMap();                   /* initialize the background coordinate system */
void PrintMap();                  /* print every point in the arr mapArr to the screen */
void StartMsg();                  /* start message */
void GetSet();                    /* judge whether to edit the game setting */
void SetRandNum();                /* set a random 'food' point in the screen */
void SetSnakeNum();               /* the most complex and important algorithm of this game */
void EatFood();                   /* judge when to eat food and elongate the body */
void StartGame();                 /* start the game */
void SetMoveNum();                /* algorithm of the start animation, some complex */
void JudgeEnd();                  /* judge the end of the animation and loop again*/
void StartView();                 /* start the start animation view */

int speed = 10;                   /* default snake move speed */
int mapArr[MAXX][MAXY];            /* arr to store the point in the screen */
int inputX = 50, inputY = 20;      /* default width and height */
int randX = -1, randY = -1;        /* set a random food point in the background */
int foodFlag = 0;                  /* judge when to change random food point*/
int sx = 1, sy = 1;             /* body x, y point */
int l = 0;                      /* body lenth */
int *body[MAXX * MAXY];             /* body array pointer */
char input = '6';                   /* default direction */
int overFlag = 1;                 /* judge when the game over, hit wall or eat self*/
int moveX = 1, moveY = 1;         /* start move effect */
int moveFlag = 0;                /* restart the loop effection */

void GetSet() 
{
    printf("\n");
    printf("請輸入遊戲空間的寬度:\n(Please enter the width of game space:)\n");
    scanf_s("%d", &inputX); 
    printf("\n");
    printf("請輸入遊戲空間的高度:\n(Please enter the height of game space:)\n");
    scanf_s("%d", &inputY);
    printf("\n");
    printf("請輸入遊戲速度(1 到 n,1 最慢):\nPlease enter the speed of snake moving:(1 is slowest)\n");
    scanf_s("%d", &speed);
}

void InitMap()
{
    int x, y;
    for (y = 0; y < inputY; y++)
    {
        for (x = 0; x < inputX; x++)

        {
            if ((x == 0) || (x == inputX - 1) || (y == 0) || (y == inputY - 1))
            {
                mapArr[x][y] = 1;
            }
            else
            {
                mapArr[x][y] = 0;
            }
        }
    }
}

void PrintMap()
{
    int x, y;
    for (y = 0; y < inputY; y++)
    {
        for (x = 0; x < inputX; x++)
        {
            switch (mapArr[x][y])
            {
            case 0:
                printf(" ");
                break;
            case 1:
                printf("+");
                break;
            case 2:
                printf("*");
                break;
            case 3:
                printf("@");
            }
        }
        printf("\n");
    }
}

void StartMsg()   
{
    printf
    ("'2(top)', '8(down)', '4(left)', 6(right)' 或 \n'w(top)', 'a(left)', 's(down)', 'd(right)'\n控制方向(control the direction)\n");
}

void SetRandNum() 
{
    srand(time(0));
    while ((mapArr[randX + 1][randY + 1] != 0) && (foodFlag == 0))
    {
        randX = rand() % (inputX - 2), randY = rand() % (inputY - 2);
    }
    mapArr[randX + 1][randY + 1] = 3;   /* set foot number 3 */
    foodFlag = 1;
}

void SetSnakeNum()
{
    if (_kbhit())         /* if there is an input, get it; if not, go on    */
    {
        int a = _getch();       
        switch (input)
        {
        case '2':
        case 'w':
            if (a == '4' || a == '6' || a == 'a' || a == 'd' || a == '2' || a == 'w')
                input = a;
            break;
        case '8':
        case 's':
            if (a == '4' || a == '6' || a == 'a' || a == 'd' || a == '8' || a == 's')
                input = a;
            break;
        case '4':
        case 'a':
            if (a == '2' || a == '8' || a == 'w' || a == 's' || a == '4' || a == 'a')
                input = a;
            break;
        case '6':
        case 'd':
            if (a == '2' || a == '8' || a == 'w' || a == 's' || a == '6' || a == 'd')
                input = a;
            break;
        }
    }
    switch (input)              /* judge the direction by value of input */
    {
    case '2':                   /* up */
    case 'w':
        sy--;
        break;
    case '8':                   /* down */
    case 's':
        sy++;
        break;
    case '4':                   /* left */
    case 'a':
        sx--;
        break;
    case '6':                   /* right */
    case 'd':
        sx++;
        break;
    }
    int i;
    for (i = l; i != 0; i--)           /*  every point's address of body move back one point */
    {
        body[i] = body[i - 1];
        *body[i] = 2;                 /* change value by pointer */
    }
    body[0] = &mapArr[sx][sy];
    if ((*body[0] == 1) || (*body[0] == 2))    /* judge when the snake hit the wall or eat itself */
    {
        overFlag = 0;
    }
    *body[0] = 2;              /* assign the head of snake by pointer */
}

void EatFood() 
{
    if (*body[0] == 3)
    {
        l++;
        foodFlag = 0;
    }
}

void StartGame()
{
    sx = 1;
    sy = 1;
    l = 0;
    input = '6';
    int j;
    for (j = 0; j < l; j++)             /* assign the snake body initial address value*/
    {
        body[j] = &mapArr[sx - j][sy];   
    }
    while (overFlag)    /* loop until the game over */
    {
        InitMap();
        SetSnakeNum();
        SetRandNum();
        EatFood();
        PrintMap();
        StartMsg();
        Sleep(1000/speed);
        system("cls");
    }
}

void SetMoveNum()
{
    /* x move 1 -- (X - 2 ); y move 1 -- (Y - 2) */
    /* move x from left to right */
    if ((moveY == 1 + moveFlag) && (moveX < inputX - 2 - moveFlag))
    {
        mapArr[moveX][moveY] = 2;
        moveX++;
    }
    /* move y from top to buttom */
    else if ((moveX == inputX - 2 - moveFlag) && (moveY < inputY - 2 - moveFlag))
    {
        mapArr[moveX][moveY] = 2;
        moveY++;
    }
    /* move x from right to left */
    else if ((moveY == inputY - 2 - moveFlag) && (moveX > 1 + moveFlag))
    {
        mapArr[moveX][moveY] = 2;
        moveX--;
    }
    /* move y from buttom to top */
    else if ((moveX == 1 + moveFlag) && (moveY > 1 + moveFlag))
    {
        mapArr[moveX][moveY] = 2;
        moveY--;
        if (moveY == 2 + moveFlag)     /* judge when to jump to a deeper layer */
        {
            moveFlag++;
        }
    }
}

void JudgeEnd() 
{
    int i, j;
    int tmp = 1;
    for (j = 0; j < inputY; j++)
    {
        for (i = 0; i < inputX; i++)
        {
            if (mapArr[i][j] == 0)
                goto out;
        }
    }
    moveX = 1, moveY = 1;
    InitMap();
    moveFlag = 0;
out:;
}

void StartView()
{
    moveX = 1, moveY = 1, moveFlag = 0;
    int startFlag = 1;
    InitMap();
    while (startFlag)
    {
        SetMoveNum();
        PrintMap();
        printf("按任意鍵開始遊戲:\n(Press any key to start game: )\n");
        Sleep(10);
        system("cls");
        JudgeEnd();
        if (_kbhit())
        {
            int c = _getch();
            if ((c != '2') && (c != 'w') && (c != '8') && (c != 's') && (c != '4') && (c != 'a') && (c != '6') && (c != 'd'))
                startFlag = 0;
        }
    }
}

int main()             /* main function */
{
    while (1)
    {
        printf("是否修改設定(修改輸入“y”,否則按任意鍵):\nEdit the game setting or not ? (Press 'y' to edit, or press another key to go on:)\n");
        if (_getch() == 'y')
        {
            GetSet();          
        }
        StartView();  /* an animation before game start */
        StartGame();
        printf("Game Over !!!\n遊戲結束,按任意鍵繼續:\n(Press any key to restart: )\n");
        _getch();
        overFlag = 1;   /* restart the game by the flag */
        system("cls");
    }

    //print();
} 

返回頂部

有更簡單的演算法歡迎評論指正!

技術文章推送
手機、電腦實用軟體分享