在貪食蛇遊戲中,有兩個非常重要的物件,分別是蛇和食物,蛇吃到食物會變長,撞到自己身體或牆壁則會死亡,食物被吃掉則會消失,並在隨機點重生。可以用C++中的Class來實現。
蛇 遊戲中的主角,也是玩家唯一可以操控的人物(動物?),基本資料有蛇頭的位置、長度,建議用 private 包起來,避免不小心從外部動到。行為有出生、移動、生長、死亡等等,可以寫成函數的形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Snake {private : int x; int y; int player; bool eat = false ; public : void gotoxy (int xpos, int ypos) { COORD scrn; HANDLE hOuput = GetStdHandle (STD_OUTPUT_HANDLE); scrn.X = xpos; scrn.Y = ypos; SetConsoleCursorPosition (hOuput,scrn); } void Init () { } void move () { } void grow () { } void die () { } };
架構大概長這樣,至於身體的部分,先從移動方式下手,觀察後發現,其實可以想成把蛇的尾端去掉,移到蛇頭前(準備要移動的位置)。因為 deque (雙向佇列) 的特性可以輕鬆的從前後端放入和移除資料,所以用其作為儲存身體的資料結構就是一個不錯的選擇。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Body { private : int x; int y; private : int xpos () { return x; } int ypos () { return y; } void setxy (int a, int b) { x = a; y = b; } }; deque<Body> body;
這段直接加在 Snake 類別的 private 即可。
接著來寫出生、移動、生長、死亡等等的程式碼吧
出生 其實就是初始化,代表蛇在遊戲一開始的狀態,把 deque 清空,然後放蛇頭進去就好了。
1 2 3 4 5 6 7 void Init () { player = 4 ; Body head; head.setxy (1 , 1 ); body.clear (); body.push_front (head); }
移動 上一篇文章有講到只有一節身體的移動方式,現在要移動一整條蛇,也不難,就按照上面的策略把蛇的尾端去掉,接在頭的前面(要移動到的地點生一節出來),這邊其實要判斷一下是否有吃到食物,若有,就省略去掉尾端的動作,因為前面還是會生長,所以看起來就變長了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void move (char key) { Body head = body.front (); if (!eat){ Body tail = body.back (); gotoxy (tail.xpos () + Left_Edge, tail.ypos ()); cout << " " ; body.pop_back (); } if (key == 'a' ){ head.setxy (head.xpos ()-1 , head.ypos ()); } else if (key == 'd' ){ head.setxy (head.xpos ()+1 , head.ypos ()); } else if (key == 'w' ){ head.setxy (head.xpos (), head.ypos ()-1 ); } else if (key == 's' ){ head.setxy (head.xpos (), head.ypos ()+1 ); } gotoxy (head.xpos () + Left_Edge, head.ypos ()); cout << "S" ; body.push_front (head); eat = false ; }
生長 這邊好像沒什麼東西欸,幾乎都寫在移動了,就是更新成有吃到食物的狀態,然後再進入移動環節。有吃到食物才會呼叫這個函式。
1 2 3 void grow () { eat = true ; }
死亡 你已經死了!!什麼!!
用來判斷死了沒,看有沒有碰到自己身體或牆壁
1 2 3 4 5 6 7 8 9 10 11 bool die () { Body head = body.front (); if (player <= 0 ) return true ; if (head.xpos () < 1 || head.xpos () > Map_Width-1 || head.ypos () < 1 || head.ypos () > Map_Height-1 ) return true ; for (int i = 1 ; i < body.size (); ++i){ if (head.x == body[i].xpos () && head.y == body[i].ypos ()){ return true ; } } return false ; }
其他 從外部取得指定的身體部分
1 2 3 Body GetBody (int i) { return body[i]; }
從外部取得蛇的長度
1 2 3 int length () { return body.size (); }
基本的操作大概就這樣,至於 Player 這個變數是幹嘛用的,還有 Left_Edge 是什麼東西,之後應該都會提到。
食物 吃的東西,用來使蛇生長的,基本資料有位置、是否存在,行為有出生和被吃(?)等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Food {private : bool flag; int x; int y; public : void gotoxy (int xpos, int ypos) { COORD scrn; HANDLE hOuput = GetStdHandle (STD_OUTPUT_HANDLE); scrn.X = xpos; scrn.Y = ypos; SetConsoleCursorPosition (hOuput,scrn); } void InitFood () { } void CreateFood () { } void EatFood () { } }food;
架構大概長這樣,兩行之後,程式碼時間。
出生 初始化,將 flag 設為 true,代表食物不存在
1 2 3 void InitFood () { flag = true ; }
接著若食物不存在,就開啟創造模式,隨機生成食物,且不能生長在有蛇的地方,不過可能會有運氣不好的時候,一直生在有蛇的地方,那就將食物視為不存在,下次繼續創造看看,總會有成功的一天。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void CreateFood () { if (flag){ x = rand () % (Map_Width-2 ) + 1 ; y = rand () % (Map_Height-2 ) + 1 ; for (int i = 0 ; i < snake.length (); ++i){ if (x == snake.GetBody (i).xpos () && y == snake.GetBody (i).ypos ()){ x = rand () % (Map_Width-2 ) + 1 ; y = rand () % (Map_Height-2 ) + 1 ; } } bool CreateError = false ; for (int i = 0 ; i < snake.length (); ++i){ if (x == snake.GetBody (i).xpos () && y == snake.GetBody (i).ypos ()){ CreateError = true ; break ; } } if (CreateError) flag = true ; else { gotoxy (x + Left_Edge, y); cout << "f" ; flag = false ; } } }
死亡 如果食物位置等於蛇頭位置,蛇生長,食物設定為不存在。
1 2 3 4 5 6 void EatFood () { if (x == snake.GetBody (i).xpos () && y == snake.GetBody (i).ypos ()){ snake.grow (); flag = true ; } }
其他 從外部取得x, y
1 2 3 4 5 6 int xpos () { return x; } int ypos () { return y; }
此遊戲最重要的兩個物件差不多就完成了,當然,這只是最基礎的功能而已,之後會再加上一些有趣的東西,甚至是新的物件,讓遊戲豐富起來。