//nim2.c --- NIM GAME, modified by tsaiwn@csie.nctu.edu.tw //prototype by tsaiwn@csie.nctu.edu.tw // 你該有注意到在前一版本中, 多處用 int 的 0 與 1 表示 false 與 true; // 既然可用 bool, false, true, 第0版中只用在 bool gameOver( )很可惜! // ..這版本就把該用 bool, false, true 的都儘量用! // C++ 本來就認識 bool, false, true; C則可用 typedef enum...bool; // 其實更好的是 typedef {false, true} bool; (稍後解說) //... 還有, 這版本也做了適度的防呆, 但還有改進的空間 /********** * ! 請注意本程式 pile 堆的編號 0 不用, 從第一堆用起! *******************************************************///////// /*** === === === 關於 false, true, bool (Java 中稱 boolean) 在 BATNUM Game 範例中, 我們用 #define bool int 使得 compiler 看到 bool 就換成 int, 並且利用 C/C++ 是把 false 看作 0, 把 true 看作 1 的特性, 在程式中我們充份利用 bool 使得程式更有可讀性! ======> 其實這些在 C++ / Java /C# 都已經這樣 (注意 bool, boolean) ** 其實, 在 C 語言中, 除了 #define bool int 還可以用 typedef int bool; 使得 compiler 認得 bool 注意, #define 是 C 的前處理器的句子, 不是 C 的句子, 尾巴沒有分號; 但是 typedef 是 C 的句子, 所以尾巴有分號 typedef int bool; 且要注意 typedef 的寫法跟#define不同: #define 巨集名稱 巨集定義 *** 更好的方法是這範例中用的: typedef {false, true } bool; 因為這樣以後, 不但 compiler 認識 bool, 且 false 就是 0, true是 1, 也就是說這樣一來, C 用起來就跟 C++ 完全一樣! 既然 C++ 本來就認識 bool, 因此我們用 #ifndef __cplusplus 使得 C 的 Compiler 要做 typedef {false, true } bool; 但 C++ 的 Compiler ( 認識 __cplusplus ) 則不做這 typedef! 這版中還沒充份利用 bool, false, true =========================== **********************************/ /// 若要用 Turbo C++ 的分割劃面可以參考: /// ftp://ftp.csie.nctu.edu.tw/pub/CSIE/course/cs2/textwin.cpp #include #include #include #ifndef __cplusplus //typedef int bool; // 與 #define bool int 同效果, 但要加分號; typedef enum {false=0, true } bool; // 更好 :-) 跟 C++ 同樣! #endif #define MAX_PILE 7 #define MAX_STONE 31 #define MIN_STONE 5 /// =================> use Global variables 比較方便但要小心處理! int nPile; /* number of Pile */ int nStone[MAX_PILE + 1]; // 第 0 堆不要用, 用 1 到 7 !! // 注意不用第 0 堆是因這樣跟人的習慣比較像 :-) int pTake, nTake; // take from which pile, how many stones bool lastToWin; // 1 == 拿到最後一個贏, 0 == 拿到最後一個贏輸 bool userFirst; // user want to Go first bool gameBegin; // used in printStatus( ) char msgWin[ ][33] = { "拿到最後一顆輸Lose!", "拿到最後一顆贏Win!" }; bool gameOver( ); // indicate somebody took the last stone void userTurn(void), computerTurn(void); void prepareGame(void); // 把game 用到的整體(global;共用)變數準備好. void playGame(void); void printStatus(void); // 印出各堆石頭狀況. void hello(void) { printf("NIM 是從many多石頭堆中輪流拿, \r\n"); // 有些compiler下不能寫 "許" ? compile時會出問題! 故寫 many 多 :-) printf(" ..NIM 每次只能從一堆拿, 至少拿一個, 至多把該堆拿光,\r\n"); printf(" .. 可規定拿到最後一顆贏, 或拿到最後一顆輸!\r\n\n"); } bool askYN( ){ bool ans; // 1 == true == Yes, 0 == false == No static char buf[333]; fgets(buf, sizeof(buf), stdin); if(buf[0] == 'N' || buf[0] =='n') return false; if(buf[0] == 0 ) return false; // '\0' == 0 return true; // We like the user to play :-) So default is YES } // askYN( /// ======== ====== ====== MAIN PROGRAM ====== ====== ======== /// int main( ) { bool playAgain; // Local variable is enough hello( ); srand( time(0) &0xfffff ); // true random, see "man 3 time" playAgain = true; // force to play :-) while(playAgain) { prepareGame( ); playGame( ); printf("Want to play again (Y, N)? "); playAgain = askYN( ); } printf("\r\nThank you for your play!\nCome to play again!\n"); return 0; } // main /////////////////////////////////////////////////////////// void prepareGame(void) { int i; lastToWin = userFirst = (38==38); // true if(rand( ) % 100 >= 50) lastToWin = false; // 50%, 就是 %2 == 1 nPile = 3 + rand( ) % (MAX_PILE-3+1); // 3..7 for(i=1; i <= nPile; ++i) { /// 注意由第一堆算起 nStone[i] = MIN_STONE + rand( ) % (MAX_STONE -MIN_STONE +1); } } // prepareGame void printStatus( ){ int i; printf("There are 共有 %d pile堆石頭:\r\n", nPile); printf(" P_%d: %d", 1, nStone[1]); // Note that 沒第 0 堆 for(i=2; i <= nPile; ++i) { // then pile-2, pile-3, ... printf(", P_%d: %d", i, nStone[i]); } printf("\r\n"); if(gameBegin) { // 只有剛開始才印這句 printf("!!! 注意 %s! %s!!\r\n", msgWin[lastToWin], msgWin[lastToWin]); gameBegin = false; //So that won't print again } } // printStatus bool gameOver( ) { // Note that Pile# is 1..nPile int i, totalStone=0; for(i=1; i <= nPile; ++i) totalStone += nStone[i]; return (totalStone <= 0); // no more stone == gameOver } // gameOver void computerTurn( ){ pTake = 1; // 從第一堆看看; 注意我們一開始就規劃不用第 0 堆 while(nStone[pTake]==0) ++pTake; // 目前先找還有石頭的 nTake = 1; // 至少要拿一個 nTake = 1 + rand( ) % nStone[pTake]; // 隨便拿 // // 要確保資料正確, 不可拿超過, 並想辦法能贏就要贏 // how to win? Hint: utilize XOR operation // see wikipedia.org printf(" --> I take %d from Pile %d\r\n", nTake, pTake); nStone[pTake] -= nTake; printStatus( ); } // computerTurn void userTurn( ) { static char buf[88]; again: // 若不用 goto, 要如何做? 可改用 while 搭配 flag printf("Take from which Pile? "); fgets(buf, sizeof(buf), stdin); pTake = (int)atol(buf); // 防呆處理: 要確保輸入資料正確, 不可讓 user 亂輸入.. // ... 若該堆 0 個也不要讓他拿喔! if(pTake > nPile ) { // 這版先 check pile# 太大的 // print 沒那麼多堆喔 ! goto again; // goes back } again22: // 要求輸入 # of stone in that pile printf(" from Pile# %d, take how many stones (1..%d)? ", pTake, nStone[pTake]); fgets(buf, sizeof(buf), stdin); nTake = (int)atol(buf); if(nTake < 0) goto again; //負數表示後悔剛輸入的 pile# // 防呆處理: 要確保輸入資料正確, 不可拿超過 if(nTake==0 || nTake > nStone[pTake]) { // print 你白痴啊? 不要亂輸入 ! goto again22; // pTake OK, 但 nTake 要重新輸入 } //if, 思考: 若不用 這 兩層 goto, 要如何用兩層 Loop 取代??? // 取得 pTake 堆 與 nTake, 阿就讓他拿掉啊 nStone[pTake] -= nTake; printStatus( ); } // userTurn void playGame(void) { int userWin; // Local variable is enough gameBegin = 1; // 每次game開始要強迫印出拿最後一個會贏還是會輸 printStatus( ); // 印出目前各堆石頭, 若第一次印要印上列說的 printf(" ..Want to go first (Y, N)? "); userFirst = askYN( ); userWin = false; if(userFirst) userTurn( ); if(gameOver( ) && lastToWin) userWin = true; while( !gameOver( ) ) { // take turn until no more stone computerTurn( ); if(gameOver( )) { if(lastToWin) userWin = false; else userWin = true; break; } userTurn( ); if(gameOver( )) { if(lastToWin) userWin = true; else userWin = false; break; } } // while if(userWin) { printf(" Congratulations -- You Win!\r\n"); }else{ printf(" SOOORRRY Sorry! -- Ha Ha Ha! -- I Won!\r\n"); } } // playGame /************ -- Proof techniques #1: Proof by Induction. Q: 試用歸納法證明:飯永遠吃不飽. 1. 吃一粒米顯然不會飽 2. 假設吃了 n 粒米沒有飽 再吃一粒米顯然也不會飽, 就是說吃 n+1 粒米也不會飽 由此可推得米飯永遠吃不飽 ! QED. (QED translates from the Latin as "So what?") ((((((((((((((((((((((((((())))))))))))))))))))))))))))) ********************************************************/