貪吃蛇C語言原始碼與演算法分析
阿新 • • 發佈:2019-01-31
經典的貪吃蛇遊戲演算法,無疑是一個較大的挑戰,綜合性較高,像我這種剛入門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();
}
返回頂部
有更簡單的演算法歡迎評論指正!