//nim0.c --- NIM GAME , prototype by tsaiwn@csie.nctu.edu.tw /********** * 這個習題 NIM 遊戲對戰, 請用這程式修改, 原有 statements 儘量留著! * 電腦要儘可能贏, 就是說能贏就要贏! * 本練習題規定: * 產生的 NIM 石頭的堆數限制在 3..7, 每堆石頭數限在 5..31, 亂數決定 * 誰先拿則 問 user 決定; 請寫成讓人與電腦交談對玩! * ! 請注意本程式 pile 堆的編號 0 不用, 從第一堆用起! *******************************************************/////////// /// 若要用 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; // 這招 更好 :-) #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 int lastToWin; // 1 == 拿到最後一個贏, 0 == 拿到最後一個贏輸 int userFirst; // user want to Go first int gameBegin; // used in printStatus( ) char msgWin[ ][33] = { "拿到最後一顆輸Loss!", "拿到最後一顆贏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多石頭堆中輪流拿, 每次只能從一堆拿, 至少拿一個,\n"); // 有些compiler下不能寫 "許" ? compile時會出問題! 故寫 many 多 :-) printf("..NIM 每次只能從一堆拿, 至少拿一個,\n"); printf("..至多把該堆拿光, 可規定拿到最後一顆贏, 或拿到最後一顆輸!\n\n"); } int askYN( ){ int ans; // 1 == yes, 0 == no static char buf[333]; fgets(buf, sizeof(buf), stdin); if(buf[0] == 'N' || buf[0] =='n') return 0; if(buf[0] == '0' ) return 0; return 1; // We like the user to play :-) So default is YES } int main( ) { int playAgain; // Local variable is enough hello( ); srand( time(0) &0xfffff ); // true random, see "man 3 time" playAgain = 1; // force to play :-) while(playAgain) { prepareGame( ); playGame( ); printf("Want to play again (Y, N)? "); playAgain = askYN( ); } printf("\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 = 0; // 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堆石頭:\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("\n"); if(gameBegin) { // 只有剛開始才印這句 printf("!!! 注意 %s! %s!!\n", msgWin[lastToWin], msgWin[lastToWin]); gameBegin = 0; //print only when game begins,do NOT 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 } void computerTurn( ){ pTake = 1; // 從第一堆看看 while(nStone[pTake]==0) ++pTake; // 目前先找還有石頭的 nTake = 1; // 至少要拿一個 nTake = 1 + rand( ) % nStone[pTake]; // 隨便拿 // // 要確保資料正確, 不可拿超過, 並想辦法能贏就要贏 // how to win? // printf(" --> I take %d from Pile %d\n", nTake, pTake); nStone[pTake] -= nTake; printStatus( ); } // computerTurn void userTurn( ) { static char buf[88]; again: printf("Take from which Pile? "); fgets(buf, sizeof(buf), stdin); pTake = (int)atol(buf); // 防呆處理: 要確保輸入資料正確, 不可讓 user 亂輸入.. // ... 若該堆 0 個也不要讓他拿喔! 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 拿 :-) // 防呆處理: 要確保輸入資料正確, 不可拿超過 ///*** how ? 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 = 0; if(userFirst) userTurn( ); if(gameOver( ) && lastToWin) userWin = 1; while( !gameOver( ) ) { computerTurn( ); if(gameOver( )) { if(lastToWin) userWin = 0; else userWin = 1; break; } userTurn( ); if(gameOver( )) { if(lastToWin) userWin = 1; else userWin = 0; break; } } // while if(userWin) { printf(" Congratulations -- You Win!\n"); }else{ printf(" SOOORRRY! -- Ha Ha Ha! -- I Win!\n"); } } // playGame /************ -- Proof techniques #1: Proof by Induction. Q: 試用歸納法證明:飯永遠吃不飽. 1. 吃一粒米顯然不會飽 2. 假設吃了 n 粒米沒有飽 再吃一粒米顯然也不會飽, 就是說吃 n+1 粒米也不會飽 由此可推得米飯永遠吃不飽 ! QED. (QED translates from the Latin as "So what?") ((((((((((((((((((((((((((())))))))))))))))))))))))))))) ********************************************************/