四、成果作品
1. 貪吃蛇程式簡介
從加入電研社,學了程式設計之後,所寫得程式無非就是去解題目,講難聽一點,都不是為自己寫的。人都有虛榮心,我也不例外;當被認識的朋友知悉我有寫程式
的興趣之後,絕大多數人都會問說:「你會不會寫遊戲?」或是「你會不會寫外掛?」,寫外掛這種程式我確信以我現在這樣的程度我是寫不出來的,所以,我就一
直保持著好好寫一個遊戲的想法。在偶然的機會下,常逛 奇摩知識+ 的我,看到有人為發問kbhit() (註一)函式和getch()
(註二)函式的人解答,使我認識這兩個函式,於是,綜合以上的動機,和一個網友寫的「蛇王」參考之下,我便動手寫了我的貪吃蛇遊戲,一款為自己寫的貪吃蛇
遊戲。
在這樣的條件下寫出來的貪吃蛇遊戲,因為使用效果不佳的函式,造成畫面閃爍,整體給人的感覺就是──「粗糙」,在社團學長的幫助之下,將整個遊戲環境優
化,將這隻程式從見不得人的處境中解救出來。現在,我將開始依遊戲的進程,站在程式碼的觀點上介紹我最後修改的版本。
(註一)
kbhit()函式的用途是檢測目前是否有按下鍵盤上的按鍵,配合流程控制和迴圈使用可以在有按下按鈕才作輸入動作的功用,也就是平常程式會作它的事,當
按下按鈕時,它才會停下來,最普通應用是碼表。使用時需引入windows.h,這個函式庫只有windows支援。
(註二)
getch()含是的用途是讀取在輸入串流內所按按鈕的ASCII值,以及一些特殊按鈕的鍵碼。使用時需引入windows.h,這個函式庫只有
windows支援。
1-1.遊戲開場
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
文字基本上是以gotoxy()
(註三)函式來輸出的,這個上下左右進行選擇的點子是來自於手機的設定選單,在程式碼部份我採用非常有彈性的寫法,利用預先定義的
MAX_MAIN_MENU變數,設定選單中選項的數量,並將各個選項的資料(顯示文字、輸出位置)利用main_menu陣列儲存,在執行程式時,上下
移動選項時需要一個暫時的變數來儲存目前的游標位置,並且依此讀取預先存的位置,以變更輸出位置,我利用%(求餘數)來取代列舉法的寫作方式(註四),並
大大降低增加新選項的複雜度;同樣的,我在選擇左右時也採用同樣的寫法,避免太多重複的程式碼出現。
(註三)gotoxy()算是一個網路上常用的一個通用函式,但是想要在Dev-C++使用仍需要自訂,這個函式我是直接上網抓別人寫的,這個函式的功能
是將執行中的命令提示字元視窗的游標放到隨意的位置上,目前我只知道使用方法。這個自訂函式內的敘述需要引入windows.h,這個函式庫只有
windows支援。
(註四)所謂列舉法,就是為每個選項放一個if,當在該選項按下上下左右鍵時,改變其在視窗上顯示的東西,使用列舉法會造成程式碼大量重複,因為不同的地
方只在於位置、內容、以及所有可以挑選的選單細項。
while(ca=getch())
{
if(ca==-32)
{
ca=getch();
if(ca==72 || ca==80)//上下
{
font(main_menu[ia+1].sx-1,main_menu[ia+1].sy,-1);
cout<<' '<<main_menu[ia+1].sa;
font(leftsite,main_menu[ia+1].sy,-1);
cout<<" ";
font(rightsite,main_menu[ia+1].sy,-1);
cout<<" ";
if(ca==72)ia+=MAX_MAIN_MENU-3;
else
ia+=MAX_MAIN_MENU-1;
ia%=MAX_MAIN_MENU-2;
font(main_menu[ia+1].sx-1,main_menu[ia+1].sy,1);
cout<<' '<<main_menu[ia+1].sa;
font(leftsite,main_menu[ia+1].sy,2);
cout<<"←";
font(rightsite,main_menu[ia+1].sy,-1);
style="font-family: Courier New;">
cout<<"→";
style="font-family: Courier New;">
font(79,24,-1);//把游標藏起來
style="font-family: Courier New;">
font(-1,-1,0);//清空顏色設定
style="font-family: Courier New;">
}
else if(ca==75 || ca==77)//左右
style="font-family: Courier New;">
{
if(ca==75)setting[ia]+=max[ia]-1;
style="font-family: Courier New;">
else
setting[ia]+=max[ia]+1;
style="font-family: Courier New;">
setting[ia]%=max[ia];
style="font-family: Courier New;">
font(sett_x_y[ia][0],sett_x_y[ia][1],-1);
style="font-family: Courier New;">
cout<<setting_text[ia][ setting[ia] ];
style="font-family: Courier New;">
}
}
style="font-family: Courier New;">
else if(ca==13)
{
style="font-family: Courier New;">
font(-1,-1,0);
gotoxy(leftsite,main_menu[ia+1].sy);
style="font-family: Courier New;">
cout<<" ";
style="font-family: Courier New;">
gotoxy(rightsite,main_menu[ia+1].sy);
style="font-family: Courier New;">
cout<<" ";
style="font-family: Courier New;">
return;
}
style="font-family: Courier New;">
font(79,24,-1);//把游標藏起來
style="font-family: Courier New;">
}
style="font-family: Courier New;">
lang="EN-US">
1-2.遊戲畫面初始
接下來就到了設定使用者設定的時候了,也就是設定遊戲模式、遊戲速度、遊戲地圖,其中,遊戲模式裡的食物模式(註五)是我自己想出來
的,遊戲地圖則是讀取障礙物地圖。設定完成之後就開始倒數,倒數完就開始生成遊戲盤面,盤面我是用兩個大陣列進行儲存,一個儲存文字版的,另一個是以整數
儲存蛇身的先後順序,兩個陣列配合,以show_color_board()函式顯示彩色盤面,在show_color_board()內是以自訂函式
font()來設定顏色及位置,在font()裡我有設定配色的代號,在程式中一律使用代號,以便以後更改配色。底下我還有輸出一個記分板,接著,就可以
開始遊戲了。
(註五)食物模式是我偶然之下想出來的,剛開始,我是想說一次一個食物會導致遊戲時間變很久,於是,我就想說乾脆把一開始的盤面全部都放食物好了,可是在
真正寫出程式以後,發現這樣根本不能玩,因為每一口吃到的都是食物,導致尾巴被固定,到最後死路一條,於是,我便改成亂數食物數量,而且還設定食物最多只
會有一半的盤面,這樣遊戲時間就會縮短許多。
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
1-3.進行遊戲
這裡利用迴圈、流程控制、kbhit()函式和getch(),作成了在使用者按下按鈕時,程式才會作出適當反應,其他時候,蛇身會繼續向前直走,每一次
迴圈都用Sleep()函式進行暫留,暫留的時間長短由遊戲速度決定,蛇移動時是先檢查,如果可以移動的話把原本的頭部換成身體的符號、輸出一個新頭部、
然後決定尾部是否去掉,如果不用去掉代表吃到食物,就該城增加分數,如果沒吃到食物,就把最後一個身體符號去掉。
border="1" cellpadding="2" cellspacing="2">
while(1)
{
if(kbhit())//接收所按的按鍵
{
ca=getch();
if(ca==13)
{
while(kbhit())getch();
switch(pause_status())
{
case 1:
return 0;
case 2:
admin=!admin;
break;
case 3:
int choose1;
do
{
//...pause...(省略)
}while(change_setting(choose1));
break;
}
clrscr();
show_color_board(board,va);
show_score_board(3);
}
if(ca==-32)//因為方向鍵的鍵碼是兩個字元,以ASCII -32開始
{
ca=getch();
if(ca==72
&& direction!=2)direction=8;//上
else if(ca==80
&& direction!=8)direction=2;//下
else if(ca==75
&& direction!=6)direction=4;//左
else if(ca==77
&& direction!=4)direction=6;//右
}
}
if(!food_count)
{
//...produce food... (省略)
}
int vc=move_head(&head,direction,board,va);
step_count++;
if(vc==0)break;
else if(vc==1)
{
food_count--;
score+=setting[1]+step_count/100;
show_score_board(2);
}
else if(vc==2)move_tail(&tail,board,va);
Sleep(velocity);
}
2. 猜數字遊戲
這個程式是我剛學程式沒多久寫的,那時候因為題目還沒有寫多少,很多寫法不是寫得很精練。這算是我第一次用時間函式和亂數函式寫的程式。程式碼第5行和第
6行定義的SIZE和iTIMES分別是數字數和可猜的次數,可以在編譯時變更。第19行的time_t是時間的專屬變數型態。第29行我原本是用int
型態讀,可是很多試玩的同學都會不小心打到其他東西,所以才改用字串讀整行。第56行是使用cmd的pause指令。第62行我採用自訂函式aa還取代輸
入和判斷的功能。第76行的CLK_TCK變數是在ctime函式庫裡預先定義下來的常數,代表一秒所執行的滴答數。接下來看到aa函式的定義,第95和
104行一樣是怕使用者打到非數字的文字,所以採字串讀整行的作法,用字串讀取是後來加進去的東西,為了遷就於舊有的程式碼,所以才又轉成數字陣列,有點
走冤枉路的感覺就是了。從剛開始寫程式時,我就非常強烈的要求排版,所以這個算是剛學沒多久寫的程式,我排版也排的一絲不苟,這個程式的缺點就是走冤枉路
和宣告太多全域變數。以下是實際執行時的圖案。
border="1" cellpadding="2" cellspacing="2">
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
3. 簡易檔案比較程式
這個程式原本是為我雙胞胎弟弟寫的,他原本是只有學Visual Basic
6.0,為了可以參加程式設計比賽,我便叫他去練習ACM,但是ACM沒有提供Visual Basic 6.0的Judge,
而且這種視窗化的語言一般來說很難利用程式自行檢查,所以,我就用亂數跑出一些測資,然後叫他用檔案I/O來輸出他的答案,然後我就可以用兩個輸出檔來進
行比較,這樣的話就可以知道程式有沒有Accept。
後來,在我弟改學C++之後,這個程式也暫時變成無用武之地,後來,在我二年級在社團當幹部,開始帶學弟之後,就開始又重新用到這個程式,因為ACM如果
程式有錯的話,不會告訴你錯在哪,於是,當學弟解題目時得到Wrong
Answer而來求助於我時,我就會利用這程式來找出兩個人程式執行結果的差異處。
因為主要判斷的檔案是解題目的輸出檔,所以我的程式是用逐行比較的方式寫的,有不同則會輸出不同的行數,兩個檔案的行數不同時也會顯示。這個程式的執行畫
面如下圖
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
4. 質數輸出程式
這個程式是為了解題目準備的,在遇到題目需要一些質數,且所需數量不多時,直接列舉會比執行時再跑一遍快多了,程式執行完會先輸出質數數量,然後質數會直
接輸出在記事本裡,為了能直接貼進程式碼裡,質數中間均是以逗號分隔。求質數的方法是先開根號,然後以不大於開根號的質數去除,這樣就可以用很快的速度確
認其是不是質數。
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
5.讀鍵盤鍵碼的程式(程式碼如下)
border="1" cellpadding="2" cellspacing="2">
#include<conio.h>
using namespace std;
int main()
{
char ca;
while(ca=getch())cout<<ca<<" =
"<<(int)ca<<endl;
system("pause");
}
這個程式是我在測試getch()函式時所寫的程式,為了以後使用方便,我便順手把它做成鍵碼表。
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
圖的鍵盤鍵碼表是用我的程式所讀到的,其中綠色字和紫色字都是讀到雙字元,而且雙字元內的第一個字元都是 -32。
6.隨意更改命令提示字元游標位置(程式碼如下)
border="1" cellpadding="2" cellspacing="2">
#include<windows.h>
using namespace std;
HANDLE hOuput;
COORD scrn;
void gotoxy(int xpos, int ypos)
{
scrn.X = xpos; scrn.Y = ypos;
SetConsoleCursorPosition(hOuput,scrn);
}
int main()
{
hOuput = GetStdHandle(STD_OUTPUT_HANDLE);
int a,b;
while(cin>>a>>b)gotoxy(a,b);
system("pause");
}
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
這個程式是我用來測試gotoxy()函式所寫的程式,程式裡迴圈每讀取兩個數字之後,就馬上執行gotoxy()函式還進行游標換位,圖為實際執行之結
果。
7.打愛的鼓勵的程式(程式碼如下)
border="1" cellpadding="2" cellspacing="2">
#include<windows.h>
#define SPEED 60
using namespace std;
int main()
{
int ia[11]={6,6,3,3,6,2,2,2,6,3,0};//6 3 2
for(int i=0;i<10;i++)ia[i]*=SPEED;
Sleep(1000);
for(int i=0;i<11;i++)
{
cout<<"\a";
Sleep(ia[i]);
}
system("pause");
}
這個程式是在社團課時學弟問我的問題,那一次是教跳脫字元,然後那個學弟在試著輸出"\a"之後,就在嘗試使用跳脫字元這個來打節
拍,他原本是用好幾千萬次的迴圈來造成延遲的效果,然後整個程式碼弄得很複雜,然後就跑來問我要怎麼改。我就將延遲用的迴圈全部改成windows.h函
式庫裡面的Sleep()函式,Sleep()函式裡面可以放入一個整數,代表延遲的毫秒數。在反覆嘗試之後,終於把最適合的間隔調了出來,我和那個學弟
寫出這個程式之後都覺得這個程式蠻有趣的,但是在調間隔時間時很麻煩就是了。
8.kbhit()函式應用-碼表(程式碼如下)
border="1" cellpadding="2" cellspacing="2">
#include<windows.h>
#include<conio.h>
using namespace std;
int main()
{
long long la=0;
bool ba=true;
while(ba)
{
Sleep(100);
cout.precision(1);
cout.setf(ios::fixed,ios::floatfield);
cout<<(la++)/10.0<<endl;
if(kbhit() && cin.get())
{
cout<<"1)繼續 2)歸零 3)結束\n -> ";
int ia;
cin>>ia;cin.get();
switch(ia)
{
case 2:
la=0; break;
case 3:
ba=false; break;
}
}
}
system("pause");
}
kbhit()函式的功用是檢測是否有按下鍵盤上的按鍵,和迴圈、選擇結構配合之後,便可以很容易的寫出碼表程式,平常時間程式會一直增加時間的計數器,
等到你按下任意鍵之後,程式就會停下來問你要作啥事。計時的功能我是用Sleep()函式進行延遲的,每次延遲0.1秒。
9. SetColor()測試程式
件者 href="http://picasaweb.google.com/taichunmin/oDJBsH?feat=embedwebsite">成
果作品
在原本C++的輸出視窗(也就是命令提示字元)是黑底白字的,但是,只要適當地使用這個SetColor()函式,把文字想改得顏色傳給第一個參數,背景
色傳給第二個參數,就可以把輸出的東西變得美美的。我的貪吃蛇就有特地花時間弄這個東西,要不然黑白遊戲就跟黑白電影一樣會越來越少人想玩。
border="1" cellpadding="2" cellspacing="2">
#include<windows.h>//需要使用到此標頭檔
using namespace std;
void SetColor(int f=0,int b=15)
{
//f 和 b 參數傳遞形式 i*8+j
//其中i為亮暗的差別,j為顏色
// 黑 藍 綠 青 紅 紫 黃 白
unsigned short ForeColor=f+16*b;
HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE); //本例以輸出為例
SetConsoleTextAttribute(hCon,ForeColor);
}
int main()
{
system("title 顏色控制碼");
SetColor();//復原
for(int i=0;i<2000;i++)printf(" ");
system("cls");
for(int i=0;i<16;i++)
{
SetColor(i,15);
cout<<"△▲☆★◆◇
SetColor("<<i<<",0)"<<'\t';
SetColor(0,i);
cout<<"△▲☆★◆◇
SetColor(0,"<<i<<")"<<endl;
}
SetColor();//復原
system("pause");
}