2018年10月11日 星期四

[C語言 練習 1]使用Dev C++設計 一款 貪食蛇吃金幣小遊戲

前言:
       這個是想來溫習一下好久以前學的C語言(不過file、 指標、結構 這些比較後面的章節都不太熟悉,以前學的其他東西自以為還行但實際上在寫時忘掉了很多,藉由寫個小程式來練習,程式方面的設計也只是做一步想一步)。P.S.本人極度缺乏各種基層知識...可能有不少地方會說錯。

     原本是打算用1111陣列來當圍牆,空白陣列當作可移動空間。來做一個貪食蛇小遊戲,不過後來發現我不懂該怎麼控制程式的刷新printf時間 (人物走路速度),所以我就改變成以按鍵操控動作,按一下動一下的吃金幣小遊戲,不然看不清人物在哪...看不清畫面的原因據說是system("cls")洗頻洗得不夠快。p.s.我是用system("cls")來洗掉屏幕的。
____________________________________________________________________________



我的小遊戲是設計一小塊地方,皆以函示來區分動作.
P.S. 我的地圖char wall[][]字元陣列是 全域變數,一開始寫時指標不熟,先用全域變數替代。

第一步、設計地圖與人物圖示
         我是打算利用字元陣列來顯示,1代表圍牆,空白代表可走路段.
         我先設定一個9*9字元陣列再利用for迴圈與if設定邊界與可走路段.
void p_on(int *lv){
 int i,j,ran;
 extern char wall[9][9];
 for(i=0;i<=8;i++)    /*遊戲內值初始化 */ 
 { 
  for(j=0;j<=8;j++)
  {
   if(*lv==0&&i==4&&j==4)
    wall[i][j]=12;  /*人物初始位置設定*/ 
   else if (i==0||i==8||j==0||j==8) 
    wall[i][j]='1';  /*設定圍牆*/
   else if(wall[i][j]!=12)
    wall[i][j]=' ';
  }
 } 
}
_____________________________________________________________________________
第二步、設定一個副程式印出地圖
void printmap(void){ 
 int i,j;
 coordinate(0,0);   //這邊定座標 由於畫面皆相同 打算直接蓋掉 
 printf("吃金幣小遊戲\n");
 for(i=0;i<6;i++){ 
  coordinate(30,5+i);
  printf("%5s:%7s\n",key_set[i].key_name,key_set[i].key_prev);
 };
 for(i=0;i<=8;i++)    
 { 
  coordinate(5,5+i);
  for(j=0;j<=8;j++)
   printf("%c",wall[i][j]);
  printf("\n");
 }
}
__________________________________________________________________________
第三步、抓取按建值 getche() 與設定初始按鍵

在網路查到讀按鍵值而不用按enter的有getch( ),getche( )
getche( )會顯示 按鍵值

這個地方卡住我很久,我的讀值一直讀不好,後來才知道 方向鍵 和一些特殊案件會送出兩個數值
要getch() 兩次,或 聽說有個_ungetch() 的功能只是不安全......送出兩個值似乎和 鍵盤掃描碼有關
但是我詳細的不懂,只知道 方向鍵的上按一次 用getch() 兩次會是 224 72


找到的資訊是提到 224(方向鍵),0(F1~F12),32,27  (其餘兩種忘了)

下面的全域變數宣告 是  當初還不習慣用指標,先用全域變數這樣比較簡單
下面那些全域變數內的陣列  是設定初始按鍵值 與 動作名稱 和按鍵名稱,結構體是用來放這些資料...

只是我不把初始值直接寫在結構體內,是因為
1.寫在結構體會太多{},我很容易漏掉..}or{ 2.我要拿來當初始值用的
另外按鍵初始值 設定三個陣列是 動作名稱,按鍵讀取值,按鍵顯示名稱
struct link{
 char key_name[5];
 int key_load;
 char key_prev[7];
}; 
/* 全域變數*/
struct link key_set[6];
char wall[9][9];
/*按鍵   初始值*/ 
//以下按鍵初始值 設定三個陣列是 動作名稱,按鍵讀取值,按鍵顯示名稱 
//例如: 上 119 w 。"上"是動作名字,"119"是按下鍵盤後getch的讀值,
//"w"是按下鍵盤後那個鈕的名稱
char name[6][5]={{"上"},{"下"},{"左"},{"右"},{"重新"},{"離開"}};  /*動作名稱*/
int load[6]={72,80,75,77,32,27};     /*按鍵讀取值*/
char prev[6][7]={{24},{25},{27},{26},{"空白鍵"},{"ESC"}};  /*按鍵顯示名稱*/
 /*按鍵  初始值*/ 
struct link key_set[6];   
/* 全域變數*/
void board_set(void){
 int i=0;
 for(i=0;i<6;i++)
 {
   strcpy(key_set[i].key_name,name[i]); 
   strcpy(key_set[i].key_prev,prev[i]);
   key_set[i].key_load=load[i];
 }
}
_____________________________________________________________________________
第四步、控制按鍵的修改
這個部分遇到的困難是 getch()讀取的值我設定為數值,但數值轉字元我不曉得,其實就是下面將大寫轉為小寫那部分的概念,只是那時懵懵懂懂念頭沒通達.
另一部分是  字串 為眾多  字元 組合,並且最後面多一個'\0'
void get_proc(void){
 char ans,transl[1][7];    /*中文字2個字元配上最後一個字元'\0'*/
 int i,j,temp,check=0,count;
 while(check<2){
  system("cls");
  printf("吃金幣小遊戲 按建設定\n");
  for(i=0;i<6;i++)
   printf("動作<%5s>對應按鍵<%7s>\n",key_set[i].key_name,key_set[i].key_prev);
  printf("是否重新設定按鍵?\n");
  printf("是(Y/y)/否(N/n)/還原(O/o)\n");  
  /*鍵盤讀取值的大小寫轉換*/ 
  ans=getch();
  if(ans>='A'&&ans<='Z')
   ans='a'+(ans-'A');
  if(ans=='y'){    /*如果是按 y 會有甚麼樣的行動*/ 
   check=1; /*check 是 只要不是按到 N/n 就會不斷的跑修改程式*/
   for(i=0;i<6;i++){
    printf("動作<%5s>原始對應按鍵為<%7s>,預修改為 <請輸入所想設定按鍵>",
    key_set[i].key_name,key_set[i].key_prev);
    if((temp=getch())==224||temp==0)
//temp==32||temp==27  32和27也是某些按鍵的值,
     temp=getch();    //不過我只用方向鍵和F1-F12 而已
    key_set[i].key_load=temp;
    count=0;   
//count 是用來對照 有沒有預設名稱的按鍵 數值,
//都沒有就以字元來計算.

//這邊是為了 按鍵按下去之後讀取得是數值,而數值 無法和 按鍵名稱連結,
//如同 空白鍵 按下後我們讀到的數值會是32
//然而 為了使我們能看到這個按鍵的名稱,我在初始值那邊尋找數值且對照相應的名字,
//讀到 32 顯示值會是 空白鍵
    for(j=0;j<6;j++) {
     if(key_set[i].key_load==load[j])
      strcpy(key_set[i].key_prev,prev[j]);
     else
      count+=1;
    }    
    if(count==6){
     transl[0][0]=temp-65+'A';
     transl[0][1]='\0';
     strcpy(key_set[i].key_prev,transl[0]);
    } 
    printf("%s\n",key_set[i].key_prev);
   }
   printf("已設定完,請按壓任意鍵離開");
   getch();   
   system("cls");
  }
  else if(ans=='n'){ //如果按 n,則讓check=2,會使迴圈跳出 
   check=2;
   system("cls");
  }
  else if(ans=='o'){ //按o代表還原按鍵設定 
   check=1;
   board_set();
  }
  else  //按其他任何鍵 繼續迴圈 
   check=0;
 }
}
_____________________________________________________________________________
第五步、移動鍵 的使用 與 地圖上的顯示
利用 副程式 與 指標 和XOR位元運算子 來使人物進行移動的動作
以前不知道在哪看到的,XOR 可以讓轉換不用再定義一個中間值.    
1010^0110=1100   1100^1010=0110   0110^1100=1010
/*人物行為與按鍵的關係*/ 
void control(int *temp,int *i,int *j,int *goldcount){
 //按動作鍵 上  之後的動作
 if(*temp==key_set[0].key_load&&wall[*i-1][*j]!='1'){                                         
 //換位程式,人物字元位置會變成wall[i-1][j]   
  wall[*i][*j]=wall[*i][*j]^wall[*i-1][*j];                
  wall[*i-1][*j]=wall[*i][*j]^wall[*i-1][*j];
  wall[*i][*j]=wall[*i][*j]^wall[*i-1][*j];
 //wall[i][j]原本字元0會變成空白
  wall[*i][*j]=' ';
 //(x,y)座標值的變化,這邊往上只動了y方向數值變化,因此只移動了i=i-1
  *i=*i-1;
 }
其餘三個方向也是差不多的設置方法
_____________________________________________________________________________
第六步、設定遊戲開始
我還是把他設定為一個副程式來運作,不過其實這個應該直接放主程式就好.
void gamestart(void){
 int i,jx=4,iy=4,tempo=0,count=0,back=0;
 printf("吃金幣小遊戲\n");
 for(i=0;i<6;i++)
  printf("%5s:%7s\n",
  key_set[i].key_name,key_set[i].key_prev);
 printf("按下<重新>即開始,<離開>即結束");
 while(back<1&&count<3){
  tempo=getch();
  if(tempo==0||tempo==224)
   tempo=getch();
  /*預防有人設定離開在 有斷碼的按鍵*/ 
  if(tempo==key_set[5].key_load) 
    back=back+1;
  //此清除畫面為 清除"當前跳離遊戲,請按<重新>開始遊戲" 
  else if(tempo==key_set[4].key_load){
   system("cls");   
   /*遊戲開始的各項數值初始值*/ 
   jx=iy=4;
   p_on();
   while(tempo!=key_set[5].key_load){
    control(&tempo,&jx,&iy);
    //system("cls");
    nextmap_v(&jx,&iy); 
    printmap();
    printf("\n目前座標[%d, %d]\n",jx,iy);
    tempo=getch();
    if(tempo==0||tempo==224)
     tempo=getch();
   }
   printf("當前跳離遊戲,請按<重新>開始遊戲");
  }
  else{
   //system("cls");
   printmap();
   printf("\n座標[%d, %d]\n",jx,iy);
   printf("按下<重新>即開始,<離開>即結束,亂按3次即離開,目前已按%d次",count+1);
   count=count+1;
  }
 }
}
_____________________________________________________________________________
到這邊人物可以行走之後,才開始修改各種副程式增加
1.其他圍牆(1)與金幣(0) 

2.吃完金幣後的第二~n層

3.吃多少金幣會對應到相應得等級
(不過上面我的副程式已經是在最後近乎完成版本時才想到寫個網頁,來介紹...
不然其實本來會有很多printf之類的東西來驗證 有沒有寫錯)
_____________________________________________________________________________
最後程式 長這樣,主程式拿來副程式,不過因為很多東西都分意區塊一區塊,所以有很多東西都在整合好後重複到.
#include <stdio.h>
#include <stdlib.h>   
#include <conio.h>           /* 函式  getch() */
#include <string.h>          /* 函式  strcpy() */
/*副程式區域*/
void p_on(int*);        /*圍牆參數設定 與亂數圍牆生成 */
void printmap();      /*列印出整個地圖*/
void board_set(void);     /*按鍵初始設定與初始功能設置*/
void get_proc(void);     /*控制按鍵的修改*/
void control(int*,int*,int*,int*);  /*人物行為與按鍵的關係*/
void gamestart(void);     /*遊戲開始*/

/*檢查金幣是否吃完進入下一層與顯示等級用與人物圖案*/
void nextmap_v(int*,int*,int*,int*,int*);

/*副程式區域*/
struct link{
 char key_name[5];
 int key_load;
 char key_prev[7];
};
struct link key_set[6];
char wall[9][9];
/*按鍵   初始值*/
//以下按鍵初始值 設定三個陣列是 動作名稱,按鍵讀取值,按鍵顯示名稱
//例如: 上 119 w 上是動作名字,119是按下鍵盤後getch的讀值,
//w是按下的鍵的名稱

//動作名稱
char name[6][5]={{"上"},{"下"},{"左"},{"右"},{"重新"},{"離開"}}; 

//按鍵讀取值
int load[6]={72,80,75,77,32,27};  

//按鍵顯示名稱
char prev[6][7]={{24},{25},{27},{26},{"空白鍵"},{"ESC"}}; 

//按鍵  初始值
int main() {
      board_set();
      get_proc();
      gamestart();
      system("pause");
      system("cls");
      return 0;
}
_____________________________________________________________________________
                                遊戲畫面(我不知道怎麼控制視窗大小所以沒弄)
C程式碼與小遊戲 連結:
https://drive.google.com/drive/folders/16PYiYvS-OqXuO4-FNq2s94PonqsLh6nR?usp=sharing

剛進小遊戲時跑出的按鍵設定

準備開始遊戲

開始吃金幣

_____________________________________________________________________________

不過整個寫完後才發現  有一些缺點
1.因為各個部分都是做一步想一步,2-3天內才做完,昨天與今天的想法會有衝突(有時會茅塞頓開),所以有很多地方重複定義到.

2.檢查 地圖上是否還有金幣 其實可以在生成地圖時使用記數把金幣數量定明,可以不用再寫一個迴圈檢查.

3.在亂數產生地圖,可能可以在產生地圖檢查是否有無法導通的路段,不過我光是額外增加的檢查機制就有點寫不出來...寫出來感覺同個點最多會檢查到3次,要是檢查1次的會有造成死路的窘境...不知道該怎麼解決。

4.畫面刷新有查到system("cls")以外的做法,只是涉及到一些沒用過的寫法,還沒參透.

5.不知道該怎麼控制小遊戲的畫面大小.

沒有留言:

張貼留言

[C語言 練習 3]串鍊連結 創建-刪除-插入-檢視

如果程式沒有練習,有些小細節很容易錯過... 就像一開始我的串列連結  完全沒注意到要在 外面設定一個head 結構link data儲存 資料 *next 指標next指向 下個結構 struct link{ int data; struct link *nex...