貪食蛇-遊戲物件

  1. 1.
    1. 1.1. 出生
    2. 1.2. 移動
    3. 1.3. 生長
    4. 1.4. 死亡
    5. 1.5. 其他
  2. 2. 食物
    1. 2.1. 出生
    2. 2.2. 死亡
    3. 2.3. 其他

在貪食蛇遊戲中,有兩個非常重要的物件,分別是蛇和食物,蛇吃到食物會變長,撞到自己身體或牆壁則會死亡,食物被吃掉則會消失,並在隨機點重生。可以用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(){ //從外部取得x
return x;
}
int ypos(){ //從外部取得y
return y;
}
void setxy(int a, int b){ //從外部設定x, y
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()); //游標移到尾端,Left_Edge是左邊界的位置,我的蛇是在畫面中間移動的,而不是從最左邊開始,之後應該會提到
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; //Map_Width是地圖寬度
y = rand() % (Map_Height-2) + 1; //Map_Height是地圖長度
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;
}

此遊戲最重要的兩個物件差不多就完成了,當然,這只是最基礎的功能而已,之後會再加上一些有趣的東西,甚至是新的物件,讓遊戲豐富起來。