一直很喜歡玩這個小游戲,簡單的游戲中包含運氣與思考與策略,喜歡這種簡約又不失內涵的游戲風格。於是萌生了用C語言實現一下的想法。
具體代碼是模仿這個:https://www.cnblogs.com/judgeyoung/p/3760515.html
博主分析的都很到位,很多算法技巧都值得借鑒,C語言實現2048的主要思想已經在那個博客中詳細的分析了,但是我覺得在博主的代碼中還是有很多很好的思想是值得我借鑒學習的。
比如這個生成隨機數,順便規定隨機數的概率:
/* 生成隨機數 函數定義 */ void add_rand_num() { srand(time(0)); int n = rand() % get_null_count();/* 確定在何處空位置生成隨機數 */ for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (board[i][j] == 0 && n-- == 0) /* 定位待生成的位置 */ { board[i][j] = (rand() % 3 ? 2 : 4);/* 確定生成何值,設定生成2的概率是4的概率的兩倍 */ return; } } } }
首先是 srand() 函數,他是一個隨機數發生器的初始化函數。
原型為:void srand(unsigned seed)
用法是:程序員需要為這個函數提供一個隨機數的種子:srand(隨機數),如果使用相同的種子,那么后面的rand()函數就會每次運行都是生成一樣的隨機數,即偽隨機數。
如:srand(1),直接用1來初始化種子,后面都是一樣的隨機數 。
為了生成真正的隨機數,我們一般采用系統時間來作為隨機數初始化函數的種子。使用time()函數來獲取系統時間:
它的返回值為從 00:00:00 GMT, January 1, 1970 到現在所持續的秒數,然后將time_t型數據轉化為(unsigned)型再傳給srand函數,即: srand((unsigned) time(&t));
還有一個經常用法,不需要定義 time_t 型 t 變量,即: srand((unsigned) time(NULL)); 直接傳入一個空指針,因為你的程序中往往並不需要經過參數獲得的 t 數據。
第二句是:
int n = rand() % get_null_count();//在空余格中生成一個隨機位置
利用隨機數對剩余空格數目進行取余運算,得到小於剩余格數的隨機數。
最后一句是:
if (board[i][j] == 0 && n-- == 0)//隨機位置處為0時填入一個隨機數,但是如果隨機位置處不為0呢? { board[i][j] = (rand() % 3 ? 2 : 4);//在隨機生成的空白格處填上一個2或者4,利用三項表達式對3取余,得到1/3,2/3的概率。 return;//結束函數 }
就是在格子中沒有數字時並且在剛剛生成的隨機數的位置處,填入一個數字2或者4,並且為了降低難度,固定生成2是生成4的概率的2倍。
利用三目運算符和對3取余的特點,產生概率分布。
rand()隨機數對3取余只有可能是0/1/2,而在三目運算符中,當第一個數不為0時,運算符的值就取中間那個數的值,否則取最后一個數。所以取2的可能性為2/3,取4的可能性為1/3,這樣就產生了不同的概率。但是這種方法只能產生 n:1 的概率分布,如果要產生 4:5, 8:17 的概率時,這種方法就不在適用了。
上面這些代碼還是有一些漏洞的,因為游戲剛開始是需要有兩個數的,一個數必為2,另一個數就是上面生成的那個數,但是如果第二個數恰好生成的隨機位置處是第一個數,那么根據這兒代碼,就什么也沒有執行,最終導致出現剛開始界面就一個數字的情況。
解決辦法就是在剛開始生成的兩個數的程序中不使用 get_null_count(),而是獲取第一個數字2的准確位置,然后生成第二個數字時,在排除第一個數字的地方生成數字即可。
程序的主體就是數字的上下左右移動,go_left()和其他三個函數,他們的思想都是相似的,就拿go_left()函數來分析一下:
按照原作者的思想,移動的時候一共有三種情況。
如果相鄰的兩個數一樣,就合並,數字相加。
如果相鄰的數字不一樣,簡單的說,又分兩種情況:數字需要移動和不需要移動。
具體代碼為:
/*左移函數*/ void go_left(void) { /*i遍歷行下標*/ for (int i = 0; i < 4; i++) { /*j為列下標,k為待比較項列下標,循環進入時k < j*/ for (int j = 1,k = 0; j < 4; j++) { /*找出k后面第一個不為0的項*/ if (board[i][j] > 0) { /*情況1*/ if (board[i][j] == board[i][k]) //兩個數相同就合並 { scoer += board[i][k++] <<= 1; board[i][j] = 0; if_need_add_num = 1; //合並之后需要生成隨機數和刷新界面 } /*情況2*/ else if (board[i][k] == 0) //k項為空,則把j格移到k格 { board[i][k] = board[i][j]; board[i][j] = 0; if_need_add_num = 1; } /*情況3*/ else //k項不為空,也不等於j項,此時兩個都不需要動,只是下標需要變換 board[i][++k] = board[i][j]; //把j項移到k項的緊挨着的右邊 if (j != k) //移動過之后不相等說明之前他們不是緊挨着的 { board[i][j] = 0; if_need_add_num = 1; //此時移動雖然沒有消去一個數,但是也要添加一個隨機數出來 } } } } }
項目完整代碼在原博客中已經給出。
運行效果: