貪食蛇-遊戲物件

  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;
}

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