貪食蛇-移動

  1. 1. 鍵盤讀取
  2. 2. 移動
  3. 3. 連續移動
  4. 4. 隱藏光標

如果要用鍵盤來控制移動方向(w:上 s:下 a:左 d:右),就要讓電腦知道當下按的鍵,並馬上做出反應,再根據按鍵來決定移動方向。

這裡可以用 _kbhit() 以及 _getch() 這兩個函式來實現此目標。

_kbhit() 是用來檢查當前是否有鍵盤輸入,若否,則回傳 false(無論有無按鍵都會立刻回傳)。
_getch() 可以從鍵盤讀入一個字元,回傳字元的 ASCII 值,若沒輸入就會一直卡在那邊。

使用 _kbhit(), _getch() 需要 #include <conio.h>

鍵盤讀取

首先,我們先嘗試讓電腦可以不斷印出當下的按鍵為何,用 _kbhit() 來判斷是否有按鍵,若有,則用 _getch() 接收此按鍵,並將其輸出,外面用一個無窮迴圈包著使其可以重複判斷讀取。

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <bits/stdc++.h>
#include <conio.h> // _kbhit() 和 _getch() 需要用到
using namespace std;

int main(){
char Key;
while(true){
if(_kbhit()){
Key = _getch();
cout << Key << "\n";
}
}
return 0;
}

結果

移動

接著,就可以開始想辦法讓蛇在螢幕上顯示出來,並根據按鍵來移動。

下面是我最初的做法,用 x, y 來儲存蛇當前的位置,若有輸入,判斷按鍵為何,更新 x, y 值,最後用迴圈跑,在對應的 x, y 位置輸出蛇的代號,否則輸出空白,並且適時的換行,在下一次輸入時使用 system(“CLS”) 來達成清屏效果。

C++
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
28
#include <bits/stdc++.h>
#include <conio.h>
using namespace std;

int main(){
char Key;
int x = 10, y = 10;
while(true){
if(_kbhit()){
system("CLS"); //清屏
Key = _getch();
if(Key == 'd') ++x;
else if(Key == 'a') --x;
else if(Key == 'w') --y;
else if(Key == 's') ++y;
for(int i = 0; i < 20; ++i){
for(int j = 0; j < 20; ++j){
if(y == i && x == j){
cout << "S";
}
else cout << " ";
}
cout << "\n";
}
}
}
return 0;
}

這當然不是一個好方法,實在太沒效率了,於是我後來查了一下發現,有 gotoxy() 這個強大的函式,可以用來控制游標位置,舉例來說,如果蛇的位置在 (10, 16),就可以這樣寫

1
2
gotoxy(10, 16); //將游標位置移動到指定位置
cout << "S";

就這?
沒錯,多麼輕鬆愉快,完全不用使用迴圈,也不用清屏,只要將移動前的位置輸出空白就好了

1
2
3
4
gotoxy(10, 16); //從 (10, 16) 移動到 (10, 17)
cout << " ";
gotoxy(10, 17);
cout << "S";

gotoxy() 不是內建的,使用前要自己定義

1
2
3
4
5
6
7
void gotoxy(int xpos, int ypos)
{
COORD scrn;
HANDLE hOuput = GetStdHandle(STD_OUTPUT_HANDLE);
scrn.X = xpos; scrn.Y = ypos;
SetConsoleCursorPosition(hOuput,scrn);
}

使用 gotoxy() 需要 #include <windows.h>

改良之後的程式碼

C++
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
28
29
30
31
32
33
#include <bits/stdc++.h>
#include <conio.h> // _kbhit() 和 _getch() 需要用到
#include <windows.h> //gotoxy() 需要用到
using namespace std;

void gotoxy(int xpos, int ypos)
{
COORD scrn;
HANDLE hOuput = GetStdHandle(STD_OUTPUT_HANDLE);
scrn.X = xpos; scrn.Y = ypos;
SetConsoleCursorPosition(hOuput,scrn);
}

int main(){
char Key;
int x = 10, y = 10;
while(true){
if(_kbhit()){
gotoxy(x, y);
cout << " ";

Key = _getch();
if(Key == 'd') ++x;
else if(Key == 'a') --x;
else if(Key == 'w') --y;
else if(Key == 's') ++y;

gotoxy(x, y);
cout << "S";
}
}
return 0;
}

結果

連續移動

目前只有按按鍵才會移動,這樣你滿足了嗎?

NO, I am curious and interested!!

我們要讓蛇可以自動依照最後一個按鍵方向移動,只要將 _getch() 以外的東西移到 _kbhit()判斷條件外,如此一來就算沒有鍵盤輸入也會按照前一次更新的 Key 值做移動。

但是由於程式運行速度太快,蛇會以10毫秒16步來移動,這顯然不是我們要的,因此,我們需要使用 Sleep() 函式,傳入的值代表程式運行到這裡會在此卡多久(毫秒),製造移動的時間差。

使用 Sleep() 需要 #include <windows.h>

C++
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
28
29
30
31
32
33
34
#include <bits/stdc++.h>
#include <conio.h>
#include <windows.h>
using namespace std;

void gotoxy(int xpos, int ypos)
{
COORD scrn;
HANDLE hOuput = GetStdHandle(STD_OUTPUT_HANDLE);
scrn.X = xpos; scrn.Y = ypos;
SetConsoleCursorPosition(hOuput,scrn);
}

int main(){
char Key = 'd';
int x = 10, y = 10;
while(true){
if(_kbhit()){
Key = _getch();
}
gotoxy(x, y);
cout << " ";

if(Key == 'd') ++x;
else if(Key == 'a') --x;
else if(Key == 'w') --y;
else if(Key == 's') ++y;

gotoxy(x, y);
cout << "S";
Sleep(200);
}
return 0;
}

結果

基本上長差不多,只是鍵盤不用一直按

隱藏光標

一直閃看了很不舒服,現在我有 HideCursor() 可以隱藏光標,使用前要自己定義

1
2
3
4
void HideCursor(){
CONSOLE_CURSOR_INFO cursor_info = {1, 0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

在 main() 一開始呼叫即可

1
2
3
4
5
6
7
8
9
10
11
12
13
以上省略

void HideCursor(){
CONSOLE_CURSOR_INFO cursor_info = {1, 0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

int main(){
HideCursor();
...
...
return 0;
}

結果

看起來多麼舒適~

移動的部分目前就先告一段落ㄌ