//CowBull.java -- 可搭配 NetBull.java, 也可自己 Run ! /*** modified from CowBull.c -- 電腦猜人想的 Bull & Cow game * 請參看 http://en.wikipedia.org/wiki/Bulls_and_cows * Also try here http://pyva.net/cgi-bin/bk * http://www.funmin.com/online-games/bulls-and-cows/ * http://bullscows.com/ (注意它給的不是我們說的 幾 A 幾 B 喔 ) * 本程式可以獨立編譯與執行, 也可以搭配 NetBull.java 成為網路版 * 因為這程式用 reflection 的功能使得編譯時不需有 Brother.class * @CopyLeft by tsaiwn@csie.nctu.edu.tw ***********=====================================******************/ import java.io.*; import java.util.*; // StringTokenizer in this package import java.lang.reflect.*; // reflection, 以便不直接用到 Brother.class //reflect 是指 RUN time 可查知物件型別資訊甚至生出物件... class CowBull { BufferedReader cin=null; //我們要用 BufferedReader 的 readLine( ) PrintStream cout=System.out, cerr=System.err; Object sys=null; // who is running me, if use with NetBull.java String name = " "; // 學號姓名, if with NetBull.java String record = " "; // 紀錄猜的過程備查 public static final int BOTH_AB = 3000; // 若同時回答兩答案 ?A ?B /// int possible[ ] = new int[5040]; // global variable int npsible; // global variable 記著有幾個可能答案 // public static void main(String xx[ ]) { new CowBull( ); } // main for single player CowBull( ) { // 單機版的 constructor, 寫個 main 然後 new 自己就可 prepareIO( ); // 把鍵盤和螢幕 stream 準備好, 稱 cin, cout play( ); } void prepareIO( ) { cin = new BufferedReader( new InputStreamReader( System.in ) ); cout = new PrintStream(System.out); //PrintStream 才有print( ) } // 以下的 constructor 是讓 NetBull.java 搭配使用, him 是 Brother 類別 CowBull(BufferedReader cin, PrintStream cout, Object him) { this.cin = cin; // 因與參數同名, 須用 this.cin 才是這 class 的 cin this.cout = cout; this.sys = him; // 指回去正在 run 我的 Brother, 用它的name, log( ) this.name = getMyName( ); // try from Brother class if any play( ); } // C 程式中原來的 main, 大部分都沒改 int play( ) { int n, tmp, nGuess, nBull, nCow; int yn, myGuess; //long now= time(0); //srand( now &0xffff); /* randomize, see "man srand" */ record= " ** " + name + ": "; boolean quit = false; // 不離開, 要繼續玩 :-) while( ! quit ) { //again: npsible = prepareAnswers( ); // 放在 possible[5040] nBull = nCow = nGuess = 0; while(nBull != 4) { // 由所有可能答案中隨便挑一個當 myGuess, 並印出說猜該數 // n = rand( ) % npsible; /* 0.. npsible -1 */ tmp=possible[0]; possible[0]=possible[n]; possible[n]=tmp; myGuess = possible[0]; /* 永遠猜第 0 個比較好寫 */ cout.printf( "I guess your number" + " is %04d, ... ", myGuess); ++nGuess; // 猜的次數 /// /// 由 user 取得幾A放 nBull, 幾 B 放 nCow cout.print("\r\n \t\t 幾 A 幾 B? "); //getAnswer(&nBull, &nCow); //nBull 就是幾 A nBull = getAB( ); // 問幾A幾B, 但user 可能只回答幾 A // 若 nBull 很大, 表示含有 nCow 值: nBull*10+nCow + BOTH_AB if(nBull >= BOTH_AB) { // he keyin 2 answers nBull = nBull - BOTH_AB; // BOTH_AB 是前面寫的常數 nCow = nBull %10; nBull = nBull / 10; // 注意 整數除以整數是整數 ! }else if(nBull != 4) { // 只回答 幾 A, 且不是 4 A // 再問一次, 取得幾 B ? cout.print("\r \t\t " + nBull + " A 以及 幾 B? "); nCow = getInt( ) % 10; // 只取個位數 } putRecord(myGuess, nBull, nCow); // proof if(nBull == 4){ if(nGuess<5)cout.printf( "才 %d 次", nGuess); cout.printf("喔 Yeah 我猜到了!\n"); if(nGuess>6)cout.printf("這麼衰..猜了 %d 次!\n", nGuess); log(3, " -->got "+myGuess+" in "+nGuess+" times " + " with " + name); break; } cout.printf("%04d : %d A %d B; ", myGuess, nBull, nCow); // /* 還沒猜對, 把不可能的答案刪除並修正 npsible */ npsible = reduceAnswers(myGuess, nBull, nCow); if(npsible <= 0){ cout.printf( "You cheating 欺騙我!\n"); log(5, " -->??? "+myGuess+ " in " + nGuess + " times " + " cheating by " + name); log(5, " ??? " + record); // proof of his cheating cout.printf("\r You see 看: " + record); break; } } // while 還沒猜到 4 A; 注意幾A(公牛)指數字對且位置也對! cout.print("\r\n\t 要不要繼續玩下一攤(yes, no)? "); record= " " + name + ": "; // reset record yn = askPlayAgain( ); // 1 表示 yes, 0 為 no if(yn == 0) quit = true; //goto again; }//while(!quit cout.println( "Thank you and Bye! " + new Date( ) ); return 0; } // play( //function to check if the user want to play again int askPlayAgain(){ /* return 1 if he/she said YES */ String tmp="no"; //static char tmp[99]; try { tmp = cin.readLine( ); // readLine(tmp); if(tmp==null) tmp="no"; // 防呆, EOF (CTRL_Z)會 null tmp = edit(tmp); }catch(Exception e) { } if(tmp.length( ) == 0) return 1; // 只按 ENTER if(tmp.charAt(0) == 'Y' || tmp.charAt(0) == 'y') return 1; if(tmp.charAt(0) == 'N' || tmp.charAt(0) == 'n') return 0; cout.printf( "Please type N or Y: "); return askPlayAgain(); /* recursively call myself */ } /// function prepareAnswers(); int prepareAnswers(){ //想辦法 (用四層 for loop ) 把所有可能答案 放在 possible[5040] //共有 5040 個, 最小 0123, 最大 9876 //(因每位不能重複) //int * x = possible; /* 之後 x[0] 就是 possible[0] */ // 以下是原來給大家的例子 int p=0, i,k,m,n; for(i=0; i<=9; i++){ for(k=0; k<=9; k++){ if(k==i) continue; /* 第二位k與第一位數i相同的都不要 */ for(m=0; m<=9; m++){ if(m==k || m==i) continue; for(n=0; n<=9; n++){ if(n==m || n==k || n==i) continue; possible[p++] = i*1000+k*100+m*10+n; } }//for m }//for k }//for i return p; } /// function reduceAnswers() int reduceAnswers(int myGuess, int nBull, int nCow) { // 刪除不可能之答案, 並將可能之答案放在 array 前端 // // 以下只做刪除不可能之答案 // /// 順便把可能答案往 array 前端移 : 留給大家想 .. int na=0, nb=0, i; int n= 0; /* possible[0] 是第一個可以蓋掉的 */ /* 所以由 possible[1] 開始看看有哪些是有可能的就留下來 */ for(i=1; i< npsible; i++){ //叫用以前的 checkAnswer(要稍微修正) 比較 myGuess 和 possible[i] //以便知道myGuess對於 possible[i]是幾A幾B // //若幾A幾B剛好與 nBull 和 nCow 完全相同表示為可能答案 // //否則都要消去 // int kkk = checkAnswer(myGuess, possible[i], na, nb, i); na = kkk/10; nb = kkk%10; if(na == nBull && nb==nCow){ possible[n] = possible[i]; /*可能的答案移到 array 較前面*/ ++n; /* n 指向下一個可蓋掉的元素 possible[n] */ } }//for i npsible = n; return n; } /****** 要稍微修正checkAnswer, 因以前是比兩個 array, 現在是比兩個整數, 要先用 %10 的方法拆成四位數 ! // *************************************************************/ int checkAnswer(int xx, int yy, int na, int nb, int item){ int x[ ]= new int[4], y[ ] = new int[4], i, j; na = nb =0; /* 要先把整數拆解成四位個位數 */ x[0] = xx % 10; y[0] = yy % 10; x[1] = (xx %100) /10; y[1] = (yy %100) /10; x[2] = (xx /100) %10; y[2] = (yy /100) %10; x[3] = xx /1000; y[3] = yy /1000; for(i=0; i<=3; i++){ if(x[i] == y[i]) ++(na); for(j=0; j<=3; j++) if(i!=j && x[i] == y[j]) ++(nb); }//for i return na*10 + nb; } int getInt( ) { String s = "000"; try { s = cin.readLine( ); s = edit(s); if(s.length( ) == 0) s = "0"; }catch(Exception e) { } int ans =0; //if(s.length( ) == 0) return 0; try { ans = Integer.parseInt(s); }catch(Exception e) { ans = 0; } return ans; } int getAB( ) { // 問 player 幾 A 幾 B? 若回答 二者, + BOTH_AB 做區別 int na=0, nb=0; String s = "0"; try { s = cin.readLine( ); s=edit(s); // 處理 Backspace if(s.length( ) == 0 ) s= "0"; // 只有按 ENTER 當作 0 }catch(Exception e) { s="0"; } StringTokenizer stk = new StringTokenizer(s, " ,\t"); if( stk.hasMoreTokens( ) ) { //StringTokenizer in java.util.* try { na = Integer.parseInt( stk.nextToken( ) ) % 10; // < 10 }catch(Exception e) { na = 0; } // 若亂則打當作 0 } if( stk.hasMoreTokens( ) ) { // nb 母牛數 try { nb = Integer.parseInt( stk.nextToken( ) ); }catch(Exception e) { nb = 0; } na = na*10 + (nb%10) + BOTH_AB; // indicate 2 answers } // return na; // 若 >= BOTH_AB 表示有兩個答案 ?A ? B }//getAB( /// ///VVVVVVVVVVVV /// 模仿 C 語言中的 Library function rand( ) int rand( ) { return (int) (Math.random( ) * 32767 ); } /// /// 因為打入 Backspace 其實留在輸入字串中, 須自己處理 String edit(String s) { // 去掉 s 中的 Backspace 以及前後空白 String t = s.trim( ); // 去掉頭尾空白 char u[ ] = t.toCharArray( ); // 轉為 char array [ ] int p = 0; int last = u.length; for(int i=0; i< last; ++i) { // 127 為 DEL key 一般不處理 //System.out.print(" :" + (int)u[i]); if((int)u[i] == 127){ continue; } // DEL key if((int)u[i] == 8) { if(p>=0) --p; if(p>=0) --p;} //Backspace else u[p] = u[i]; // 把有用的字 copy 到左邊尾巴 ++p; } while(p