在貪食蛇遊戲中,有兩個非常重要的物件,分別是蛇和食物,蛇吃到食物會變長,撞到自己身體或牆壁則會死亡,食物被吃掉則會消失,並在隨機點重生。可以用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; } 
此遊戲最重要的兩個物件差不多就完成了,當然,這只是最基礎的功能而已,之後會再加上一些有趣的東西,甚至是新的物件,讓遊戲豐富起來。