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

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

