還記得在我們小時候玩過的貪吃蛇嗎?正在學C語言的我,現在雖然無法完全還原那些年玩過的貪吃蛇經典小遊戲,但卻可以利用字符設計出低配版貪吃蛇啦!在下用了很長時間,終於完成了自己製作的第一款C語言遊戲,在這裏和大家分享!
字符·貪吃蛇遊戲設計及算法
在設計之前,我們首先要回憶貪吃蛇的基本玩法和細節:
1.蛇只能進行上下左右四個方向的移動
2.蛇可以吃到食物,吃到食物後會在蛇尾增加一節長度
3.蛇頭碰到牆壁或者自己的身體時遊戲結束
這便是最基本的貪吃蛇,此外還有很多拓展出的功能,這裏不再一一贅述。
在此基礎上,我製作了人工控制移動的貪吃蛇。
最開始,我們需要了解貪吃蛇的基本元素,如蛇頭蛇尾,食物,邊界等。
#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] = { "************",
"*HXXXX *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"************" };
人工蛇,顧名思義,就是自己控制蛇的走位,不斷吃食物壯大自己。
因此,上述三個基本特徵分別可以通過下面的方法實現:
1.輸入一個字符,判斷移動方向,以WASD爲例:W向上,A向左,S向下,D向右
2.在地圖上食物以“
3.用IF條件判斷蛇頭H座標與牆壁或者與任意一處蛇身X座標是否相同,若是,遊戲結束
總體框架用僞代碼表示出來就是
print map[12][13]
while(not 遊戲結束)
生成食物
輸入字符c
SWIHCH(c)
CASE a:蛇頭向左走 break;
CASE d:蛇頭向右走 break;
CASE w:蛇頭向上走 break;
CASE s:蛇頭向下走 break;
END switch
END WHILE
print Game Over!!!
之後,利用函數不斷的完善。
其中,關於移動的函數是最重要的,算法如下:
length爲蛇長;
i爲循環值,初始化爲0;
snakex爲蛇身的x座標,snakey爲蛇身的y座標(snakex[0] snakey[0]分別爲蛇頭的x座標,y座標);
當輸入字符時,判斷蛇頭走向,以決定snakex[0]和snakey[0]的值變化,然後:
while(i <=length-1)DO
{snakex[i]=snakex[i-1]
snakey[i]=snakey[i-1]
}
便完成了移動。
之後,還有蛇吃食物的蛇長變化,
爲使其在蛇尾部分長度+1,設計如下算法:
length++;
lastx=snakex[length-1]
lasty=snakey[length-1]
MOVE;
IF(snake eat food)THEN
snakex[length-1]=lastx;
snakey[length-1]=lasty;
這樣就完成了蛇長的變化。
在總框架的基礎上,完成各部分的函數,這樣,貪吃蛇之人工蛇版本便完成了:
#include <stdio.h>
#include <stdlib.h>
#include<time.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 *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"************" };
//全局變量:初始狀態表
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;//定義初始長度,在該變量完成蛇長的變化
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;
printmap();
//打印初始狀態圖
char c;
int a = 0, b = 0;
while ((scanf_s("%c", &c)) != EOF)//輸入字母W,A,S,D
{
switch (c){//進行移動
case 'a'://左移時
a = 1;
b = 0;
snakefood();
move(a, b);
if (gameover())//判定遊戲是否結束
{
printf("\nGAME OVER!!!\n");
break;
}
else
printmap();
break;
case 'd'://右移
b = 1;
a = 0;
snakefood();
move(a, b);
if (gameover())//判定遊戲是否結束
{
printf("\nGAME OVER!!!\n");
break;
}
else
printmap();
break;
case 's'://上移
a = -1;
b = 0;
snakefood();
move(a, b);
if (gameover())//判定遊戲是否結束
{
printf("\nGAME OVER!!!\n");
break;
}
else
printmap();
break;
case 'w'://下移
b = -1;
a = 0;
snakefood();
move(a, b);
if (gameover())//判定遊戲是否結束
{
printf("\nGAME OVER!!!\n");
break;
}
else
printmap();
break;
}
}//判定貪吃蛇的移動(通過move函數實現)並通過output函數實現map上的變化
return 0;
}
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){
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[x][y] == ' ')//確保在空白位置出現食物
{
map[x][y] = SNAKE_FOOD;//在地圖可到達位置上隨機生成食物
food++;//確保每次只出現一個食物
}
}
}
void eatfood(void){
if (snakey[0] == x&&snakex[0] == y)//判斷蛇是否吃到食物
{
length++;
food = 0;//食物已經被吃掉,清空
snakex[length - 1] = lastX;
snakey[length - 1] = lastY;//蛇長增加一個
}
}
遊戲成果如下:
在後期,可以通過引用windows函數庫來優化界面,例如每次循環後清空整個界面,使其更加直觀等等,大家可以嘗試。
我們可以看到,先寫出總控代碼是多麼重要,它的清晰程度決定了你設計算法以及對應函數需要的時間和思考量。