//nim3.c --- NIM GAME, by tsaiwn@csie.nctu.edu.tw //prototype by tsaiwn@csie.nctu.edu.tw //****** // ..這版本比 nim2.c 最主要就是 userTurn( ) 內的 goto 改用 while // C++ 本來就認識 bool, false, true; C則可用 typedef enum...bool; /********** 請仔細研讀前一版 num2.c 中的說明 與 Lab-11 的說明 *******************************************************/////////// /// 若要用 Turbo C++ 的分割劃面可以參考: /// ftp://ftp.csie.nctu.edu.tw/pub/CSIE/course/cs2/textwin.cpp #include #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 quit; // indicate that user want to Stop playing 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 } void clrERR( ) { errno = 0; // clear errno in }//clrErr 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 } void computerTurn( ){ pTake = 1; // 從第一堆看看; 注意我們一開始就規劃不用第 0 堆 while(nStone[pTake]==0) ++pTake; // 目前先找還有石頭的 nTake = 1; // 至少要拿一個 nTake = 1 + rand( ) % nStone[pTake]; // 隨便拿 // // 要確保資料正確, 不可拿超過, 並想辦法能贏就要贏 // how to win? // printf(" --> I take %d from Pile %d\r\n", nTake, pTake); nStone[pTake] -= nTake; printStatus( ); } // computerTurn void userTurn( ) { bool goodData; // Note that "quit" is a Global variable static char buf[88]; //again: while( true ) { // not got good data yet quit = false; // assume continue to play ! printf("Take from which Pile? "); fgets(buf, sizeof(buf), stdin); pTake = (int)atol(buf); // 防呆處理: 要確保輸入資料正確, 不可讓 user 亂輸入.. // ... 若該堆 0 個也不要讓他拿喔! if(feof(stdin)) { clrERR( ); pTake = -1; } // 讀到 CTRL_Z if(pTake < 0) { // 輸入負數看要如何處理, 可當作錯, 也可表示不玩了 :-) quit = true; // want to quit from the game break; } if(pTake ==0 ) { printf(" Sorry, there is no pile#0\r\n"); continue; // goto again; } if(pTake > nPile ) { printf(" ? Which pile? "); // print 沒那麼多堆喔 ! continue; // goes back to while( ) (again:) } //again22: while( true ) { // Loop forever printf(" from Pile# %d, take how many stones (1..%d)? ", pTake, nStone[pTake]); fgets(buf, sizeof(buf), stdin); nTake = (int)atol(buf); // 防呆處理: 要確保輸入資料正確, 不可拿超過 ///*** how ? if(feof(stdin)){clrERR( ); nTake = -1;} // 輸入 CTRL_Z if(nTake < 0) { // 如何允許他後悔改從別堆 Pile 拿 ? goodData = false; // set FLAG for outside while Loop break; // leave this while Loop } // if if(nTake == 0 || nTake > nStone[pTake]) { printf(" 耍白痴?? 不要亂打啦!\r\n"); continue; // goto again22; } goodData = true; // data is good :-) break; // } // while if( !goodData ) continue; // goto again; break; // OK, 取得 pTake 堆 與 nTake } // while !dataOK if(quit) { fprintf(stderr, " You chicken!!! \r\n"); return; // user 不玩了 } nStone[pTake] -= nTake; printStatus( ); } // userTurn void playGame(void) { int userWin; // Local variable is enough gameBegin = true;// 每次game開始, 要印出拿最後一個會贏還是會輸 quit = false; // indicate continue to play printStatus( ); // 印出目前各堆石頭, 若第一次印要印上列說的 printf(" ..Want to go first (Y, N)? "); userFirst = askYN( ); userWin = false; // Game starts .. if(userFirst) userTurn( ); if(gameOver( ) && lastToWin) userWin = true; while(!quit && !gameOver( )){ // take turn till no more stone computerTurn( ); if(gameOver( )) { if(lastToWin) userWin = false; else userWin = true; break; } userTurn( ); if(gameOver( )) { userWin = false; if(lastToWin) userWin = true; // user took the last 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?") ((((((((((((((((((((((((((())))))))))))))))))))))))))))) ********************************************************/