準備工作
首先上一張圖,因為這里只是在用C語言驗證算法,所以沒有對界面做很好的優化,丑是理所應當的。
了解了游戲的工作原理,實際上可以將游戲描述為四個帶有方向的同一操作:
1、將所有數字向一個方向移動至中間沒有空位
2、將相鄰的兩個相同的數字加和然后放在更靠近移動方向前部的一個位置上
另外需要判斷一下玩家當前輸入的內容是否可以執行,如果不可以執行等待用戶下一條記錄。
同時需要對游戲的進程進行控制,如果可以繼續游戲,那么運行玩家繼續輸入下一條指令,而如果不可以進行,那么提示無法繼續游戲的提示。
首先的問題就是光標鍵的輸入。光標鍵屬于功能鍵,使用常規的scanf當然是無法進行讀取的,而使用更加接近硬件的getch()
進行以字節為單位的標準輸入。當使用getch()
函數進行標準輸入時,如果用戶輸入了一個功能鍵,例如光標鍵、Home、PgUp、PgDn、End之類的鍵,getch()
將能夠讀取得到兩個字符。當遇到功能鍵輸入的時候,可以編寫一個檢測程序以獲取對應按鍵的數據:
#include<stdio.h>int main(){ while(1){ printf("%d/n",getch()); }}
隨后運行這個數據提取程序,程序將按照一個字節一行,以整型的格式輸出getch得到的數據。這里我查詢到2048需要用到的四個按鍵↑↓←→對應的兩個字節為:
按鍵 | 第一字節 | 第二字節 |
↑ | 224 | 72 |
↓ | 224 | 80 |
← | 224 | 75 |
→ | 224 | 77 |
然后就是游戲的主要的代碼
#include<stdio.h> //標準輸入輸出#include<stdlib.h> //基本工具函數#define bool int //C里邊沒有布爾類型,就自己造#define true 1 //bool的兩種值#define false 0int MAP[4][4]= {0}; //地圖,默認0認為是空位typedef enum { //定義一個方向類型的枚舉變量 UNKNOW, UP, DOWN, LEFT, RIGHT} Direction; void printMap(); //繪制圖形Direction getNextDirection(); //從鍵盤讀入下一個用戶操作bool canMove(Direction direction); //判斷是否可以進行指定方向的操作void doAction(); //游戲事件void move(Direction direction); //移動數字void putNew(); //放入一個新的數字int main() { //主函數 Direction nextStep; //下一步 int i,j; srand(time(0)); putNew(); //游戲開始默認放兩個數字 putNew(); printMap(); //打印格子 while(1) { if(!canMove(UP)&&!canMove(LEFT)&&!canMove(DOWN)&&!canMove(RIGHT)) break; //任意方向都不能移動,那么終止游戲 nextStep=getNextDirection(); //獲取下一個用戶操作 if(nextStep==UNKNOW) continue; //如果不知道用戶按了個什么鍵或者用戶胡亂按的,那么進入新的循環 if(!canMove(nextStep)) continue; //如果下一步不可繼續操作,進入新的循環 system("cls"); //對于Windows來說,執行命令行命令cls清屏 doAction(nextStep); //執行操作 putNew(); //放新的數字 printMap(); //打印格子 } printf("You Died!"); //提示游戲結束 while(1); //等待游戲結束} void printMap() { int i,j; printf("*-------*-------*-------*-------*/n"); for(i=0; i<4; i++) { printf("|"); for(j=0; j<4; j++) { MAP[i][j]?printf("%d",MAP[i][j]):printf(" "); printf("/t|"); if(j>2) printf("/n"); } printf("*-------*-------*-------*-------*/n"); }}void doAction(Direction direction){ int i,j,k; /** * 為了方便處理問題,將每個方向的運動操作簡化為三步 * 1.將數字歸并到一個方向 * 2.處理相同數字可消,并將消掉的數據定為0 * 3.再次將數字歸并到一個方向 */ //1.移動數字,取消數字之間的空位 move(direction); //2.按照方向處理相同數字 switch(direction){ case UP: //按列枚舉 for(i=0;i<4;i++){ //對于每一行的每一個元素 for(j=0;j<3;j++){ //如果元素非零,并且當前和下一個相同,當前的翻倍,下一個置零 if(MAP[j][i]&&MAP[j][i]==MAP[j+1][i]){ MAP[j][i]+=MAP[j+1][i]; MAP[j+1][i]=0; } } } break; case LEFT://同上 for(i=0;i<4;i++) for(j=0;j<3;j++) if(MAP[i][j]&&MAP[i][j]==MAP[i][j+1]){ MAP[i][j]+=MAP[i][j+1]; MAP[i][j+1]=0; } break; case DOWN://同上 for(i=0;i<4;i++) for(j=3;j>0;j--) if(MAP[j][i]&&MAP[j][i]==MAP[j-1][i]){ MAP[j][i]+=MAP[j-1][i]; MAP[j-1][i]=0; } break; case RIGHT://同上 for(i=0;i<4;i++) for(j=3;j>0;j--) if(MAP[i][j]&&MAP[i][j]==MAP[i][j-1]){ MAP[i][j]+=MAP[i][j-1]; MAP[i][j-1]=0; } break; } //3.移動數字,取消因為上一步置零過程中新產生的空位 move(direction);}void move(Direction direction) { //移動數字 int i,j,k; switch(direction) { case UP: //按列枚舉 for(i=0;i<4;i++) //對于每一行的每一個元素 for(j=0;j<4;j++) //如果非零,那么應當取消當前位置,后邊元素向前移動 if(!MAP[j][i]){ for(k=j;k<3;k++){ MAP[k][i]=MAP[k+1][i]; } //新產生的空位置零 MAP[k][i]=0; } break; case LEFT://同上 for(i=0;i<4;i++) for(j=0;j<4;j++) if(!MAP[i][j]){ for(k=j;k<3;k++){ MAP[i][k]=MAP[i][k+1]; } MAP[i][k]=0; } break; case DOWN://同上 for(i=0;i<4;i++) for(j=3;j>=0;j--) if(!MAP[j][i]){ for(k=j;k>0;k--){ MAP[k][i]=MAP[k-1][i]; } MAP[k][i]=0; } break; case RIGHT://同上 for(i=0;i<4;i++) for(j=3;j>=0;j--) if(!MAP[i][j]){ for(k=j;k>0;k--){ MAP[i][k]=MAP[i][k-1]; } MAP[i][k]=0; } break; }}bool canMove(Direction direction) { //判斷是否可以進行指定方向的操作 int i,j; switch(direction) { case UP: //依次檢查每一列 for(i=0;i<4;i++){ //首先排除在遠端的一串空位,直接將j指向第一個非零元素 for(j=3;j>=0;j--) if(MAP[j][i]) break; //j>0代表這一列并非全部為0 if(j>0) //依次檢查每一個剩余元素,遇見空位直接返回true for(;j>=0;j--) if(!MAP[j][i]) return true; //依次檢查相鄰的元素是否存在相同的非零數字 for(j=3;j>0;j--) if(MAP[j][i]&&MAP[j][i]==MAP[j-1][i]) return true; } break; case DOWN://同上 for(i=0;i<4;i++){ for(j=0;j<4;j++) if(MAP[j][i]) break; if(j<4) for(;j<4;j++) if(!MAP[j][i]) return true; for(j=0;j<3;j++) if(MAP[j][i]&&MAP[j][i]==MAP[j+1][i]) return true; } break; case LEFT://同上 for(i=0; i<4; i++){ for(j=3;j>=0;j--) if(MAP[i][j]) break; if(j>=0) for(;j>=0;j--) if(!MAP[i][j]) return true; for(j=0;j<3;j++) if(MAP[i][j]&&MAP[i][j]==MAP[i][j+1]) return true; } break; case RIGHT://同上 for(i=0; i<4; i++){ for(j=0;j<4;j++) if(MAP[i][j]) break; if(j<4) for(;j<4;j++) if(!MAP[i][j]) return true; for(j=0;j<3;j++){ if(MAP[i][j]&&MAP[i][j]==MAP[i][j+1]) return true; } } break; } //當允許條件都被檢查過后,返回不可執行的結果 return false;}Direction getNextDirection() { //第一個字節必須是224,否則判定輸入的不是功能鍵 if(getch()!=224) return UNKNOW; //根據第二字節對應出來用戶的操作 switch(getch()) { case 72: return UP; case 80: return DOWN; case 75: return LEFT; case 77: return RIGHT; default: return UNKNOW; }}void putNew(){ //為了方便操作,臨時存儲一下所有空閑格子的指針,這樣可以用一個線性的內存隨機訪問實現對所有空位中任一空位的隨機訪問. int* boxes[16]={NULL}; //用來臨時保存目標格子的地址 int* target; //統計一共有多少個有效空格 int count=0; int i,j; //統計空位,發現空位即保存地址并累加計數器 for(i=0;i<4;i++) for(j=0;j<4;j++) if(!MAP[i][j]){ boxes[count]=&MAP[i][j]; count++; } if(count){ //如果有空位,那么對這一位進行隨機賦值操作,對于每一位可能性是相同的 target=boxes[rand()%count]; //50%可能出現2 50% 可能出現4 *target=rand()%2?2:4; }}
總結
以上就是這篇文章的全部內容了,小編認為像俄羅斯方塊、2048這些稍微偏算法的小游戲是程序員必寫的幾個小程序。希望這篇文章對大家的學習或者工作能有所幫助,如果有疑問大家可以留言交流。
新聞熱點
疑難解答
圖片精選