//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 <stdio.h>
#include <stdlib.h>
#include <time.h>

#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?")
((((((((((((((((((((((((((()))))))))))))))))))))))))))))
********************************************************/
