相信許多前行在編程學習之路的同學們都跟我一樣有自己寫遊戲的小夢想。在之前的探索和學習中,我們已經接觸過了用諸如Construct2這樣的“黑箱”開發平臺來創造自己的遊戲,但不是自己打出來的代碼始終覺得缺少一點感覺。經過半個多學期的編程技術和編程思想的學習,運用較爲基本的C語言知識和自頂向下的思想方法,我們已經有能力自己完成一個經典的字符遊戲:貪喫蛇。
上一次博客中我們討論了字符遊戲貪喫蛇初階的設計和程序實現,在這次的進階實踐中,我們將要討論貪喫蛇的完整實現和制杖智能蛇的算法設計實現。(實驗環境:Ubuntu)
完整的貪喫蛇
此處只討論關鍵處理,篇幅原因不貼上源代碼。源代碼可見GitHub中我的源碼。
初階的不足與進階完整化
在初階的貪喫蛇實現中,我們的蛇只會“一令一動”,即蛇的移動完全由玩家控制WSAD來實現,沒有鍵盤輸入時蛇是靜止不動的。這與我們熟悉的貪喫蛇遊戲不同,傳統的貪喫蛇遊戲中,除了接收到鍵盤指令轉向外蛇是會一直前進的,下面我們就要讓我們的代碼實現這一功能。
如何在沒有鍵盤輸入時一直運行我們原先令貪喫蛇移動的那部分程序,而在有鍵盤指令時更新指令繼續移動呢?kbhit函數很好的滿足了我們的需求。kbhit函數功能是檢測當前是否有鍵盤輸入,若無則返回0,若有則返回一個非零參數,使用時需包含conio.h頭文件。因此我們可以在main函數中執行貪喫蛇的運行過程中利用這個函數,來替換先前的每次執行都要等待鍵盤輸入的條件,從而實現蛇的持續運行。具體代碼塊如下:
int main(void)
{
char kbi = 'D', lastmove = 'D';
srand(time(NULL));
creatfood();
printer();
while (flag)
{
if (_kbhit()) { kbi = _getch(); continue; }
else
switch (kbi)
{
case 'A':
if (lastmove == 'D') break;
else move(-1, 0);
lastmove = kbi;
break;
case 'S':
if (lastmove == 'W') break;
else move(0, -1);
lastmove = kbi;
break;
case 'D':
if (lastmove == 'A') break;
else move(1, 0);
lastmove = kbi;
break;
case 'W':
if (lastmove == 'S') break;
else move(0, 1);
lastmove = kbi;
break;
}
system("cls");
printer();
Sleep(200);
}
system("cls");
printf("************\n* *\n* *\n* *\n* *\n* GAME *\n* OVER *\n* *\n* *\n* *\n* *\n************\n");
return 0;
}
這樣,我們的蛇就可以不依賴於鍵盤控制而一直沿着原來的方向前進。
大家可能還注意到了一個Sleep(int)函數,這是windows.h庫中的一個控制程序運行快慢的函數。因爲在我們的計算機上,整段移動程序的耗時很短,蛇在我們開始遊戲還來不及控制方向的時候就已經完全高速衝出了屏幕(好吧因爲撞到牆就GameOver了還是沒機會衝出屏幕的),所以我們需要用Sleep函數定時掛起,控制一下程序運行的速度,可以這樣簡單地理解,在適當的地方加上Sleep(200)實現了控制蛇移動的板塊程序每0.2秒運行一次,即當沒有輸入時,蛇每約0.2秒前進一個單位。
下爲遊戲運行實錄(爲方便錄製,程序在Windows Visual Studio中運行):
智能蛇設計
此處只講關鍵算法模塊,源代碼見GitHub我的源碼。
真正貪喫的蛇怎麼能自己不會去找食物呢?爲了實現蛇的“智能覓食”,老師給我們推薦了一個算法,通過僞代碼,我們可以很直觀地瞭解到這種算法的執行原理和過程:
參考“貪婪算法”,whereGoNext函數實現:
char whereGoNext()
{
int distance[4] = { 0,0,0,0 };
char direction[4] = { 'W','S','A','D' };
if ((map[locationx[length - 1] - 1][locationy[length - 1]] == 0) || (map[locationx[length - 1] - 1][locationy[length - 1]] == 3))
distance[0] = abs(foodx - locationx[length - 1] + 1) + abs(foody - locationy[length - 1]);
else distance[0] = 9999;
if ((map[locationx[length - 1] + 1][locationy[length - 1]] == 0) || (map[locationx[length - 1] + 1][locationy[length - 1]] == 3))
distance[1] = abs(foodx - locationx[length - 1] - 1) + abs(foody - locationy[length - 1]);
else distance[1] = 9999;
if ((map[locationx[length - 1]][locationy[length - 1] - 1] == 0) || (map[locationx[length - 1]][locationy[length - 1] - 1] == 3))
distance[2] = abs(foodx - locationx[length - 1]) + abs(foody - locationy[length - 1] + 1);
else distance[2] = 9999;
if ((map[locationx[length - 1]][locationy[length - 1] + 1] == 0) || (map[locationx[length - 1]][locationy[length - 1] + 1] == 3))
distance[3] = abs(foodx - locationx[length - 1]) + abs(foody - locationy[length - 1] - 1);
else distance[3] = 9999;
int min = 0;
for (int i = 1; i < 4; i++)
if (distance[min] > distance[i]) min = i;
return direction[min];
}
實現了這個子函數來決策前進方向,再在主函數中稍加改動,加大難度在地圖上添加障礙物,就可以把智能蛇拉出來溜溜啦~
來看看這蛇有多智能(爲方便錄製,程序在Windows Visual Studio中運行):
PS:此處筆者刪除了一些閃動幀,真實運行時屏幕刷新有些許閃動。
可見智能蛇的“貪婪算法”還是有些許缺陷,就是缺少路徑預判,蛇很容易走進自己走出的死路。因爲有障礙物的影響極端情況下,蛇還會進入死循環一直原地繞圈而永遠喫不到食物。算法當然還可以加以改進,此次不再深入,以後我們還可以繼續探索。下提供其他的一些算法思路:
1.佛系覓食“傻”而萬無一失法:按照可循環路徑遍歷整個地圖。
2.BFS算法,初級算法設計和實現可參考這裏。
3.更多思考參考這裏。
……
以上便是筆者貪喫蛇進階學習的學習記錄。
謝謝閱讀!