利用Random類的對象的鏈表-隨機的順序存儲一副52張的紙牌。用含有兩個字符的字符串表明紙牌,例如「1C」表示梅花A,」JD」表示方片J等。從棧中輸出4手牌,每手牌有13張紙牌。java
完成一個變形版的紙牌21點遊戲。該遊戲來源於21點遊戲,實現人機對戰。算法
遊戲說明以下:數組
(1)該遊戲須要兩副牌,沒有Joker,共104張。每張「紙牌」應具備花色與數字兩個屬性。app
(2)遊戲在機器與人類玩家之間進行。遊戲一開始應先洗牌(將104張牌打亂)。dom
(3)機器永遠是莊家,因此永遠先給機器發牌,機器的牌不可見,只能看到機器要了幾張牌。機器中止要牌後,再給人類玩家發牌。ide
(4)遊戲勝利與失敗的條件與普通21相同;除此之外,一方在當前牌沒有爆掉的前提下,若是下一張牌使得手中有兩張徹底同樣的牌(同數字、同花色)則馬上勝利。函數
(5)遊戲結束時機器的牌要所有顯示,並提示誰勝利了。學習
程序設計要求以下:測試
(1)程序中應至少有Card類和CardGame類。優化
(2)Card類須要重寫Object類的equals(Object o)函數,用於比較兩張牌是否徹底同樣;重寫toString函數,用於輸出牌時直接顯示牌的花色與數字。
(3)CardGame類應具備shuffle(洗牌)、deal(發牌)、win(勝利判別)等函數。
(4)選擇適當的java集合類來實現「發牌牌堆」和「手牌」(不容許都使用數組)。
· 學習點之一:用enum枚舉
enum通常用來枚舉一組相同類型的常量。如性別、日期、月份、顏色等。對這些屬性用常量的好處是顯而易見的,不只能夠保證單例,且要做比較的時候能夠用」== 」來替換」equals」,是一種好的習慣(如本例中第45行)。
用法:如:
性別:
[java] view plain copy
本例中的花色:
[java] view plain copy
須要注意的是,枚舉對象裏面的值都必須是惟一的,特別的,咱們能夠還經過enum類型名直接引用該常量,好比本例中的121,124,127等行經過類型名直接引用。
除此以外,咱們還能夠往enum中添加新方法,這裏就不加介紹了。
· 學習點之二:重寫equals函數進行比較
equals(Object o)函數本來是Object類下面的函數,我查了一下JAVA的API,截圖以下:
簡而言之,若是JAVA中默認的equals方法跟實際不符的話,就須要重寫equals方法。咱們這裏要對牌是否相同做比較,所以須要重寫該方法(第40~50行):
[java] view plain copy
另外咱們注意到API裏面寫着:
「注意:當此方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。」這也就是咱們下面要學習的第三點。
· 學習點之三:重寫hashcode()
問:也許上面的話有些晦澀難懂啊,咱們爲何要重寫hashcode()方法呢?
答: object對象中的 public boolean equals(Object obj),對於任何非空引用值 x 和 y,當且僅當 x 和 y 引用同一個對象時,此方法才返回 true;
注意:當此方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。以下:
(1)當obj1.equals(obj2)爲true時,obj1.hashCode() == obj2.hashCode()必須爲true
(2)當obj1.hashCode() ==obj2.hashCode()爲false時,obj1.equals(obj2)必須爲false
若是不重寫equals,那麼比較的將是對象的引用是否指向同一塊內存地址,重寫以後目的是爲了比較兩個對象的value值是否相等。特別指出利用equals比較八大包裝對象(如int,float等)和String類(由於該類已重寫了equals和hashcode方法)對象時,默認比較的是值,在比較其它自定義對象時都是比較的引用地址。
hashcode是用於散列數據的快速存取,如利用HashSet/HashMap/Hashtable類來存儲數據時,都是根據存儲對象的hashcode值來進行判斷是否相同的。
總而言之,這樣若是咱們對一個對象重寫了equals,意 思是隻要對象的成員變量值都相等那麼equals就等於true,但不重寫hashcode,那麼咱們再new一個新的對象,當原對象.equals(新 對象)等於true時,二者的hashcode倒是不同的,由此將產生了理解的不一致,如在存儲散列集合時(如Set類),將會存儲了兩個值同樣的對 象,致使混淆,所以,就也須要重寫hashcode()。
更通俗的說:
Object中的hashcode()方法在咱們建立對象的時候爲每一個對象計算一個散列碼,這個散列碼是惟一的,因此若是2個對象的散列碼相同,那他們必定是同一個對象。
本身定義的類也能夠重寫hashCode()方法,按照本身定義的算法計算散列碼的生成。
Object中的equals()方法,比較的是2個對象的引用地址,包括他們各自的散列碼,若是不一樣,就認爲是不一樣的對象。
String類中重寫了equals方法,比較的是他們字符串的內容。
在咱們這裏本身定義的算法是(第53~58行):
[java] view plain copy
· 學習點之四:重寫toString()函數
由於它是Object裏面已經有了的方法,而全部類都是繼承Object,因此「全部對象都有這個方法」。
它一般只是爲了方便輸出,好比System.out.println(xx),括號裏面的「xx」若是不是String類型的話,就自動調用xx的toString()方法。然而對於默認的toString()方法每每不能知足需求,須要重寫覆蓋這個方法。
比較易懂,就不具體說明了,見代碼60~86行。
·學習點之五:動態數組ArrayList
動態數組,便可以將 ArrayList想象成一種「會自動擴增容量的Array」。它的優勢是能夠動態地插入和刪除元素,但犧牲效率。
有關使用ArrayList的例子,參考ArrayList用法,裏面介紹的很詳細。在咱們這題中,因爲電腦和玩家的手牌都會由於抽牌而增長,所以將兩者均設爲ArrarList(第114,115行),方便動態插入。
· 學習點之六:加強for循環
見代碼138~140行,不少人沒看懂for(Card element:cards) 這段,其實這是JDK5.0的新特性,做用是遍歷數組元素。其語法以下:
· 學習點之七:善於使用隨機數
直接調用Math.random()能夠產生一個[0,1)之間的隨機數,注意區間是前閉後開的。本題當中由於共有104張牌,那麼咱們直接用(int)Math.random()*104便可以產生[0,104)之間的隨機數,對應一下數組的下標前閉後開恰好知足。
· 學習點之八:利用Scanner進行輸入
咱們都知 道,JAVA裏輸入輸出函數十分的麻煩,尤爲是輸入。可喜的是,從SDK1.5開始,新增了Scanner類,簡化了輸入函數。如本題的221 行:Scanner in=new Scanner(System.in) 首先建立了一個in對象,而後in對象能夠調用下列方法,讀取用戶在命令行輸入的各類數據類型: nextDouble(), nextFloat, nextInt(),nextLine(),nextLong()等,上述方法執行時都會形成堵塞,等待用戶在命令行輸入數據回車確認,例如本題中的第 279行,在系統問詢是否繼續要牌(y/n)後,用in.nextLine()來接受玩家輸入的值。
說了這麼多,咱們來運行下程序:
e….我什麼都沒幹 輸了- -;
再來一次。。。
平局。。。行,就這樣了,不要欺負電腦了。
咱們再回到題目和代碼:
· 完成一個變形版的紙牌21點遊戲。該遊戲來源於21點遊戲,實現人機對戰。
遊戲說明以下:
(1)該遊戲須要兩副牌,沒有Joker,共104張。每張「紙牌」應具備花色與數字兩個屬性。--直接對應代碼24~37,60~102,117~134行
(2)遊戲在機器與人類玩家之間進行。遊戲一開始應先洗牌(將104張牌打亂)。--直接對應代碼144~166行
(3)機器永遠是莊家,因此永遠先給機器發牌,機器的牌不可見,只能看到機器要了幾張牌。機器中止要牌後,再給人類玩家發牌。--直接對應代碼224~280行
(4)遊戲勝利與失敗的條件與普通21相同;除此之外,一方在當前牌沒有爆掉的前提下,若是下一張牌使得手中有兩張徹底同樣的牌(同數字、同花色)則馬上勝利。--直接對應代碼181~205,208~217,311~323行
(5)遊戲結束時機器的牌要所有顯示,並提示誰勝利了。--直接對應代碼172~179,283~297行
程序設計要求以下:
(1)程序中應至少有Card類和CardGame類。
(2)Card類須要重寫Object類的equals(Object o)函數,用於比較兩張牌是否徹底同樣;重寫toString函數,用於輸出牌時直接顯示牌的花色與數字。
(3)CardGame類應具備shuffle(洗牌)、deal(發牌)、win(勝利判別)等函數。
(4)選擇適當的java集合類來實現「發牌牌堆」和「手牌」(不容許都使用數組)。
21點遊戲是一個古老的撲克遊戲,遊戲的規則是:各個參與者設法使本身的牌達到總分21而不超過這個數值。撲克牌的分值取它們的面值,A充當1或者11分,J,Q和K人頭牌都是10分。莊家VS1~7個玩家。在開局時,包括莊家在內的全部參與者都有兩張牌。玩家能夠看到他們的全部牌以及總分,而莊家有一張牌暫時是隱藏的。接下來,只要願意,各個玩家都有機會依次再拿一張牌。若是是玩家的總分超過了21(稱爲引爆),那麼這個玩家就輸了。在全部玩家都拿了額外的牌後,莊家將顯示隱藏的牌。只要莊家的總分等於或小於16,那麼他就必須再拿牌。若是莊家引爆了,那麼尚未引爆的全部玩家都將獲勝,引爆的玩家打成平局。不然,將餘下的各玩家的總分與莊家的總分作比較,若是玩家的總分大於莊家的總分,則玩家獲勝。若是兩者的總分相同,則玩家與莊家打成平局中。
//Card.Java
package card; public class Card{ private String color; private String number; public Card(String color, String number) { this.color = color; this.number = number; } public String getColor() { return color; } public String getNumber() { return number; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Card)) return false; Card other = (Card) obj; if (color == null) { if (other.color != null) return false; } else if (!color.equals(other.color)) return false; if (number == null) { if (other.number != null) return false; } else if (!number.equals(other.number)) return false; return true; } }
//Cards.java
package card; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Cards { private List<Card> list = new ArrayList<Card>(); //建立一副撲克牌 public Cards(){ System.out.println("-----------------建立撲克牌------------------"); String[] color = {"黑桃", "紅桃", "梅花", "方片"}; String[] number = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J","Q","K", "A"}; for(int i=0;i<color.length;i++) for(int j=0;j<number.length;j++){ list.add(new Card(color[i], number[j])); } System.out.println("----------------撲克牌建立成功!---------------"); } //獲取一副撲克牌 public List<Card> getList() { return list; } //洗牌(打亂) public void shufCards(){ System.out.println("----------------開始洗牌------------------------"); Collections.shuffle(list); System.out.println("----------------洗牌結束------------------------"); } //展現一副撲克牌 public void showCards(){ System.out.print("當前的撲克牌爲:"); System.out.print("[ "); for(int i=0;i<list.size();i++){ System.out.print(list.get(i).getColor() + list.get(i).getNumber()+ " "); } System.out.println(" ]"); } }
//Player.java
package card; import java.util.ArrayList; import java.util.List; public class Player { private int id; private String name; private List<Card> handCards = new ArrayList<Card>(); public Player(int id, String name){ this.id = id; this.name = name; } public List<Card> getHandCards() { return handCards; } public void setHandCards(Card card) { handCards.add(card); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
//CardComparator.java(自定義排序規則)
package card; import java.util.Comparator; public class CardComparator implements Comparator<Card> { @Override public int compare(Card c1, Card c2) { // 構建花色和牌值數組,經過比對,計算獲得某張牌的價值(大小) String[] color = {"方片", "梅花", "紅桃", "黑桃"}; String[] number = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J","Q","K", "A"}; //因爲比較規則是先比較牌值,若是相等再比較花色(黑紅梅方),因此將牌值賦予較高的權值 int valueOfC1 = 0; int valueOfC2 = 0; for(int i=0;i<number.length;i++){ if(c1.getNumber().equals(number[i])) valueOfC1 += i*10; if(c2.getNumber().equals(number[i])) valueOfC2 += i*10; } for(int i=0;i<color.length;i++){ if(c1.getColor().equals(color[i])) valueOfC1 += i; if(c2.getColor().equals(color[i])) valueOfC2 += i; } if( valueOfC1 > valueOfC2 ) return -1; if( valueOfC1 < valueOfC2 ) return 1; return 0; } }
//PlayDemo.java
package card; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Scanner; public class PlayDemo { //建立玩家 //要對玩家ID的異常處理,要求用戶只能輸入整數ID,不然須要從新輸入 public Player setPlayer(){ int id=0; String name=""; Scanner console = new Scanner(System.in); boolean ready = true; do{ try{ System.out.println("輸入ID:"); id = console.nextInt(); ready = true; }catch(Exception e){ System.out.println("請輸入整數類型的ID!"); ready = false; console.nextLine(); } }while(ready==false); System.out.println("輸入姓名:"); name = console.next(); return new Player(id, name); } public static void main(String[] args) { //測試簡易撲克牌程序 PlayDemo game = new PlayDemo(); //(1)建立一副牌 Cards cards = new Cards(); //(2)展現新的撲克牌 cards.showCards(); //(3)洗牌 cards.shufCards(); //(4)建立玩家 System.out.println("--------------建立兩個(or多個)玩家就能夠開始遊戲啦!-------------"); List<Player> p = new ArrayList<Player>(); for(int i=0;i<2;i++) { System.out.println("請輸入第"+(i+1)+"位玩家的ID和姓名:"); p.add(game.setPlayer()); } for(int i=0;i<p.size();i++) { System.out.println("歡迎玩家:"+p.get(i).getName()); } //(5)撲克牌比大小遊戲開始啦~ int count = 0; System.out.println("------------------開始發牌---------------------"); //設定每人分別拿兩張(or多張) for(int i=0; i<2;i++){ //玩家輪流拿牌 for(int j=0; j< p.size(); j++){ System.out.println(">玩家"+p.get(j).getName()+"拿牌"); p.get(j).setHandCards(cards.getList().get(count)); count++; } } System.out.println("------------------發牌結束!--------------------"); System.out.println("------------------開始遊戲 ---------------------"); for(int i=0;i<p.size();i++){ System.out.print("玩家"+p.get(i).getName()+"的手牌爲:[ "); for(int j=0;j<p.get(i).getHandCards().size();j++){ Card cur = p.get(i).getHandCards().get(j); System.out.print(cur.getColor()+cur.getNumber()+" "); } System.out.println(" ]"); } //排序獲得每一個玩家最大的手牌(排序規則自定義) for(int i=0;i<p.size();i++){ Collections.sort(p.get(i).getHandCards(), new CardComparator()); } List<Card> maxCard = new ArrayList<Card>(); for(int i=0;i<p.size();i++){ Card maxCur = p.get(i).getHandCards().get(0); System.out.println("玩家"+p.get(i).getName()+"最大的手牌爲:"+ maxCur.getColor()+maxCur.getNumber()); maxCard.add(maxCur); } //獲得最後的勝者 List<Card> temp = new ArrayList<Card>(); temp.addAll(maxCard); Collections.sort(temp, new CardComparator()); for(int i=0;i<p.size();i++){ if(maxCard.get(i).equals(temp.get(0))) System.out.println("恭喜玩家:"+p.get(i).getName()+"獲勝!"); } } }
還有一個地方須要優化,即不能輸入重複的ID和姓名,能夠重寫Player的equals方法,將用戶的輸入與已有的輸入相比較,不知足要求則須要從新輸入,