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