興大物件導向程式設計 Assignment Ch07 參考詳解
前言與使用說明
- 請自行消化吸收一遍,能力允許的話對程式做一些修改,不要照抄。
- 我寫的程式碼不是最佳實踐。會盡可能會用課本給的程式碼「修改」,而不是重寫。
- 由於程式碼稍微長一點,我會把對程式碼的修改分步驟貼上,修改的部分用
//程式註解解釋。
題目參考
本篇文章為中興大學物件導向程式設計課程的 Assignment Ch07 的詳解。
題目的部分可參考本站另一篇文章(點擊前往)。
不過本篇文章已把題目 + 答案整合在一起了。
題目 7.18 (Game of Craps)
Write an application that runs 1,000,000 games of craps (Fig. 6.8) and answers the following questions:
-
a) How many games are won on the first roll, second roll, …,twentieth roll and after the twentieth roll?
-
b) How many games are lost on the first roll, second roll, …,twentieth roll and after the twentieth roll?
-
c) What are the chances of winning at craps? [Note: You should discover that craps is one of the fairest casino games. What do you suppose this means?]
-
d) What is the average length of a game of craps?
-
e) Do the chances of winning improve with the length of the game?
Fig.6.8
![]()
![]()
![]()
![]()
![]()
![]()
![]()
7.18 參考解答
Craps 遊戲介紹
題目中題到了 Craps,相信品學兼優的各位可能並不了解這個遊戲。
Craps 又稱花旗骰,是賭場中常會出現的一種賭博遊戲。玩家需要擲兩個骰子(點數為 1 到 6),遊戲的勝負由點數和決定:
- 若第一次兩骰的點數和為 7 或 11:玩家勝。
- 若第一次兩骰的點數和為 2、3 或 12:玩家輸。
- 若第一次兩骰的點數和為其它點數,則記錄這個點數和,然後繼續擲骰。
- 若在之後的擲骰中,玩家擲到點數和等於第一次的點數和:玩家勝。
- 若玩家此時擲到點數和為 7,玩家輸。
- 若玩家擲到其它點數,則直接繼續擲。
關於 Craps 這個遊戲的規則與各種程式語言的實作方法,可參考這個網頁(超連結)。
獲取 7.18 程式碼
知道基本的遊戲規則後,既然題目已經給我們參考程式碼,那就直接拿來用。
首先我們把 Fig.6.8 中的程式碼找出來。在學校電腦 C 槽的 OOP_class 資料夾中有,我這裡直接放程式碼:
// Craps class simulates the dice game craps.import java.security.SecureRandom;
public class Craps { // create secure random number generator for use in method rollDice private static final SecureRandom randomNumbers = new SecureRandom();
// enum type with constants that represent the game status private enum Status { CONTINUE, WON, LOST };
// constants that represent common rolls of the dice private static final int SNAKE_EYES = 2; private static final int TREY = 3; private static final int SEVEN = 7; private static final int YO_LEVEN = 11; private static final int BOX_CARS = 12;
// plays one game of craps public static void main(String[] args) { int myPoint = 0; // point if no win or loss on first roll Status gameStatus; // can contain CONTINUE, WON or LOST
int sumOfDice = rollDice(); // first roll of the dice
// determine game status and point based on first roll switch (sumOfDice) { case SEVEN: // win with 7 on first roll case YO_LEVEN: // win with 11 on first roll gameStatus = Status.WON; break; case SNAKE_EYES: // lose with 2 on first roll case TREY: // lose with 3 on first roll case BOX_CARS: // lose with 12 on first roll gameStatus = Status.LOST; break; default: // did not win or lose, so remember point gameStatus = Status.CONTINUE; // game is not over myPoint = sumOfDice; // remember the point System.out.printf("Point is %d%n", myPoint); break; }
// while game is not complete while (gameStatus == Status.CONTINUE) { // not WON or LOST sumOfDice = rollDice(); // roll dice again
// determine game status if (sumOfDice == myPoint) { // win by making point gameStatus = Status.WON; } else { if (sumOfDice == SEVEN) { // lose by rolling 7 before point gameStatus = Status.LOST; } } }
// display won or lost message if (gameStatus == Status.WON) { System.out.println("Player wins"); } else { System.out.println("Player loses"); } }
// roll dice, calculate sum and display results public static int rollDice() { // pick random die values int die1 = 1 + randomNumbers.nextInt(6); // first die roll int die2 = 1 + randomNumbers.nextInt(6); // second die roll
int sum = die1 + die2; // sum of die values
// display results of this roll System.out.printf("Player rolled %d + %d = %d%n", die1, die2, sum);
return sum; }}
/************************************************************************** * (C) Copyright 1992-2018 by Deitel & Associates, Inc. and * * Pearson Education, Inc. All Rights Reserved. * * * * DISCLAIMER: The authors and publisher of this book have used their * * best efforts in preparing the book. These efforts include the * * development, research, and testing of the theories and programs * * to determine their effectiveness. The authors and publisher make * * no warranty of any kind, expressed or implied, with regard to these * * programs or to the documentation contained in these books. The authors * * and publisher shall not be liable in any event for incidental or * * consequential damages in connection with, or arising out of, the * * furnishing, performance, or use of these programs. * *************************************************************************/把這個程式拿去編譯執行得到的結果如下:

7.18 題幹
Write an application that runs 1,000,000 games of craps (Fig. 6.8) and answers the following questions
注意看剛剛程式碼的執行結果,會發現雖然玩家投了許多次骰子,但最終只會有一個玩家贏/輸的結果,也就是只算是一局遊戲。
根據題幹說明,我們需要讓程式執行 1,000,000 次的 Craps 遊戲,然後進行統計以回答選項的問題。所以我們把原本放在 main 方法裡的大部分邏輯都搬到外面來,寫成一個新的方法叫作 playOnceGame,然後在 main 方法裡用迴圈乎叫:
import java.security.SecureRandom;
public class EX_7_18 { private static final SecureRandom randomNumbers = new SecureRandom();
private enum Status { CONTINUE, WON, LOST };
private static final int SNAKE_EYES = 2; private static final int TREY = 3; private static final int SEVEN = 7; private static final int YO_LEVEN = 11; private static final int BOX_CARS = 12;
// main 方法 public static void main(String[] args) { for (int i = 0; i < 3; i++) { // 測試玩三局 Craps 遊戲 playOnceGame(); } }
// 把遊戲過程包成 playOnceGame 方法 public static void playOnceGame() { int myPoint = 0; Status gameStatus; int sumOfDice = rollDice(); switch (sumOfDice) { case SEVEN: case YO_LEVEN: gameStatus = Status.WON; break; case SNAKE_EYES: case TREY: case BOX_CARS: gameStatus = Status.LOST; break; default: gameStatus = Status.CONTINUE; myPoint = sumOfDice; System.out.printf("Point is %d%n", myPoint); break; } while (gameStatus == Status.CONTINUE) { sumOfDice = rollDice(); if (sumOfDice == myPoint) { gameStatus = Status.WON; } else { if (sumOfDice == SEVEN) { gameStatus = Status.LOST; } } } if (gameStatus == Status.WON) { System.out.println("Player wins"); } else { System.out.println("Player loses"); } }
public static int rollDice() { int die1 = 1 + randomNumbers.nextInt(6); int die2 = 1 + randomNumbers.nextInt(6); int sum = die1 + die2; System.out.printf("Player rolled %d + %d = %d%n", die1, die2, sum); return sum; }}7.18 a 選項
a. How many games are won on the first roll, second roll, …,twentieth roll and after the twentieth roll?
這個選項需要我們統計在這 1,000,000 局遊戲裡面:
- 多少局遊戲玩家會在第一次擲骰子時獲勝?
- 多少局遊戲玩家會在第二次擲骰子時獲勝?
- …以此類推
- 多少局遊戲玩家會在第二十次擲骰子時獲勝?
- 多少局遊戲玩家會在第二十次以後(不含)擲骰子才獲勝?
為了進行統計,首先我們要把程式中印出 "Player rolled 2 + 2 = 4" 之類的程式刪除,改為讓 playOnceGame 方法回傳勝負結果及是在第幾輪獲勝(否則畫面會太雜亂)。然後在 main 方法中統計,並在 1,000,000 局遊戲都結束後印出:
playOnceGame回傳- 回傳
-1代表玩家輸,不進行統計 - 回傳不為
-1的數字x代表玩家在第x輪獲勝,進行統計
- 回傳
import java.security.SecureRandom;
public class EX_7_18 { private static final SecureRandom randomNumbers = new SecureRandom();
private enum Status { CONTINUE, WON, LOST };
private static final int SNAKE_EYES = 2; private static final int TREY = 3; private static final int SEVEN = 7; private static final int YO_LEVEN = 11; private static final int BOX_CARS = 12;
public static void main(String[] args) { int[] winTimes = new int[21]; int winAt = 0; for (int i = 0; i < 1000000; i++) { // 執行 1000000 遍 winAt = playOnceGame(); // winAt 記錄 playOnceGame 回傳的第幾次投骰結束遊戲 if (1 <= winAt && winAt <= 20) { winTimes[winAt - 1]++; } else if (winAt > 20) { winTimes[20]++; // winTimes[20] 儲存經過 20 次投骰後才結束遊戲的局數 } } for (int i = 0; i < 20; i++) { System.out.printf("在第 %d 次投骰玩家會獲勝的局數有:%d 局\n", i + 1, winTimes[i]); } System.out.printf("在第 20 次以後(不含)投骰玩家會獲勝的局數有:%d 局\n", winTimes[20]); }
public static int playOnceGame() { int myPoint = 0; Status gameStatus; int totalPlayTimes = 1; // 統計骰幾次才結束遊戲 int sumOfDice = rollDice(); switch (sumOfDice) { case SEVEN: case YO_LEVEN: gameStatus = Status.WON; break; case SNAKE_EYES: case TREY: case BOX_CARS: gameStatus = Status.LOST; break; default: gameStatus = Status.CONTINUE; myPoint = sumOfDice; break; } while (gameStatus == Status.CONTINUE) { sumOfDice = rollDice(); totalPlayTimes++; if (sumOfDice == myPoint) { gameStatus = Status.WON; } else { if (sumOfDice == SEVEN) { gameStatus = Status.LOST; } } } if (gameStatus == Status.WON) { return totalPlayTimes; } else { return -1; } }
public static int rollDice() { int die1 = 1 + randomNumbers.nextInt(6); int die2 = 1 + randomNumbers.nextInt(6); int sum = die1 + die2; return sum; }}執行結果:

7.18 b 選項
How many games are lost on the first roll, second roll, …,twentieth roll and after the twentieth roll?
這個選項跟 a 選項問的內容差異不大,只是把要統計的對象從玩家贏變成玩家輸:
import java.security.SecureRandom;
public class EX_7_18 { private static final SecureRandom randomNumbers = new SecureRandom();
private enum Status { CONTINUE, WON, LOST };
private static final int SNAKE_EYES = 2; private static final int TREY = 3; private static final int SEVEN = 7; private static final int YO_LEVEN = 11; private static final int BOX_CARS = 12;
public static void main(String[] args) { int[] loseTimes = new int[21]; int loseAt = 0; for (int i = 0; i < 1000000; i++) { loseAt = playOnceGame(); // loseAt 記錄 playOnceGame 回傳的第幾次投骰結束遊戲 if (1 <= loseAt && loseAt <= 20) { loseTimes[loseAt - 1]++; } else if (loseAt > 20) { loseTimes[20]++; // loseTimes[20] 儲存經過 20 次投骰後才結束遊戲的局數 } } for (int i = 0; i < 20; i++) { System.out.printf("在第 %d 次投骰玩家會輸的局數有:%d 局\n", i + 1, loseTimes[i]); // 把輸出的文字改為統計輸的 } System.out.printf("在第 20 次以後(不含)投骰玩家會輸的局數有:%d 局\n", loseTimes[20]); // 把輸出的文字改為統計輸的 }
public static int playOnceGame() { int myPoint = 0; Status gameStatus; int totalPlayTimes = 1; // 統計骰幾次才結束遊戲 int sumOfDice = rollDice(); switch (sumOfDice) { case SEVEN: case YO_LEVEN: gameStatus = Status.WON; break; case SNAKE_EYES: case TREY: case BOX_CARS: gameStatus = Status.LOST; break; default: gameStatus = Status.CONTINUE; myPoint = sumOfDice; break; } while (gameStatus == Status.CONTINUE) { sumOfDice = rollDice(); totalPlayTimes++; if (sumOfDice == myPoint) { gameStatus = Status.WON; } else { if (sumOfDice == SEVEN) { gameStatus = Status.LOST; } } } if (gameStatus == Status.WON) { return -1; // 改成回傳 -1 代表玩家贏,不統計 } else { return totalPlayTimes; // 若玩家輸,回傳在第幾次投骰結束遊戲 } }
public static int rollDice() { int die1 = 1 + randomNumbers.nextInt(6); int die2 = 1 + randomNumbers.nextInt(6); int sum = die1 + die2; return sum; }}執行結果:

7.18 c 選項
What are the chances of winning at craps? [Note: You should discover that craps is one of the fairest casino games. What do you suppose this means?]
這題要求兩個問題:
- Craps 遊戲玩家的勝率為多少?
- Craps 屬於相對較公平的遊戲,你認為這代表什麼?
因為我們知道勝率的計算方法:
所以我們可以修改一下程式,這次不輸出輸贏的統計結果,而是加總後算出勝率。
import java.security.SecureRandom;
public class EX_7_18 { private static final SecureRandom randomNumbers = new SecureRandom();
private enum Status { CONTINUE, WON, LOST };
private static final int SNAKE_EYES = 2; private static final int TREY = 3; private static final int SEVEN = 7; private static final int YO_LEVEN = 11; private static final int BOX_CARS = 12;
public static void main(String[] args) { double totalPlayTimes = 1000000; double winTimes = 0; for (int i = 0; i < totalPlayTimes; i++) { if (playOnceGame()) { // 當回傳為 true 代表贏了 winTimes++; } } System.out.printf("勝率 = %f", winTimes / totalPlayTimes); }
public static boolean playOnceGame() { // 改為回傳布林值,true 代表贏、false 代表輸 int myPoint = 0; Status gameStatus; int sumOfDice = rollDice(); switch (sumOfDice) { case SEVEN: case YO_LEVEN: gameStatus = Status.WON; break; case SNAKE_EYES: case TREY: case BOX_CARS: gameStatus = Status.LOST; break; default: gameStatus = Status.CONTINUE; myPoint = sumOfDice; break; } while (gameStatus == Status.CONTINUE) { sumOfDice = rollDice(); if (sumOfDice == myPoint) { gameStatus = Status.WON; } else { if (sumOfDice == SEVEN) { gameStatus = Status.LOST; } } } if (gameStatus == Status.WON) { return true; // 贏就回傳 true } else { return false; // 輸就回傳 false } }
public static int rollDice() { int die1 = 1 + randomNumbers.nextInt(6); int die2 = 1 + randomNumbers.nextInt(6); int sum = die1 + die2; return sum; }}執行結果只有一行:
勝率 = 0.492374從勝率 = 0.492374 可以看出來,作為一個賭博遊戲來說,已經是相當接近 0.5,也就是相對很公平了。
那麼這代表什麼呢?
我認為既然這個遊戲已經是相對很公平的賭場遊戲,勝率卻又還是不到 0.5,這代表其它的賭場遊戲對玩家來說期望值都相當不理想。
我們在高中學習數學的時候都知道許多遊戲都可以計算出期望值、也知道賭場的遊戲都經過精心設計,期望值一定是負的。但當真正進入賭場、進入投資領域時很多人卻忘記這一點,白白把自己的錢送出去。
還是那句話:「十賭九輸」,沒經過精心計算就妄想靠運氣賺錢是很不實際的行為。另外如果你對期望值和資金管理、交易之間的關係有興趣,可以參考我的這篇交易聖經閱讀心得。
7.18 d 選項
What is the average length of a game of craps?
這個選項要請我們計算出 Craps 遊戲的平均長度(也就是平均要幾次投骰幾次遊戲會結束)。
那麼我們應該可以把每局遊戲投了幾次骰子全部加起來,再除以總局數得到平均,也就是:
寫成 Java 程式:
import java.security.SecureRandom;
public class EX_7_18 { private static final SecureRandom randomNumbers = new SecureRandom();
private enum Status { CONTINUE, WON, LOST };
private static final int SNAKE_EYES = 2; private static final int TREY = 3; private static final int SEVEN = 7; private static final int YO_LEVEN = 11; private static final int BOX_CARS = 12;
public static void main(String[] args) { double totalPlayTimes = 1000000; double totalThrowDiceTimes = 0; for (int i = 0; i < totalPlayTimes; i++) { totalThrowDiceTimes += playOnceGame(); } System.out.printf("Craps 遊戲平均長度 = %f 次投骰。", totalThrowDiceTimes / totalPlayTimes); }
public static int playOnceGame() { // 改為回傳投了幾次骰子才結束(不論玩家是輸還是贏) int playTimes = 1; // 記錄投骰總次數 int myPoint = 0; Status gameStatus; int sumOfDice = rollDice(); switch (sumOfDice) { case SEVEN: case YO_LEVEN: gameStatus = Status.WON; break; case SNAKE_EYES: case TREY: case BOX_CARS: gameStatus = Status.LOST; break; default: gameStatus = Status.CONTINUE; myPoint = sumOfDice; break; } while (gameStatus == Status.CONTINUE) { playTimes++; // 還要繼續,總投骰次數++ sumOfDice = rollDice(); if (sumOfDice == myPoint) { gameStatus = Status.WON; } else { if (sumOfDice == SEVEN) { gameStatus = Status.LOST; } } } return playTimes; // 不論贏或輸都回傳 playTimes }
public static int rollDice() { int die1 = 1 + randomNumbers.nextInt(6); int die2 = 1 + randomNumbers.nextInt(6); int sum = die1 + die2; return sum; }}執行結果只有一行:
Craps 遊戲平均長度 = 3.377009 次投骰。7.18 e 選項
Do the chances of winning improve with the length of the game?
用這個式子我們可以把每一局玩家的勝率計算出來,然後把計算的結果印出來觀察即可。
這裡用 a 選項的程式做修改:
import java.security.SecureRandom;
public class EX_7_18 { private static final SecureRandom randomNumbers = new SecureRandom();
private enum Status { CONTINUE, WON, LOST };
private static final int SNAKE_EYES = 2; private static final int TREY = 3; private static final int SEVEN = 7; private static final int YO_LEVEN = 11; private static final int BOX_CARS = 12;
public static void main(String[] args) { int[] winTimes = new int[21]; int[] loseTimes = new int[21]; int endAt = 0; // 遊戲結束在第 endAt 局 for (int i = 0; i < 1000000; i++) { endAt = playOnceGame(); if (endAt >= 0) { // 玩家贏 if (endAt > 20) { winTimes[20]++; } else { winTimes[endAt - 1]++; } } else { // 玩家輸 endAt = -endAt; // 把回傳值翻正 if (endAt > 20) { loseTimes[20]++; } else { loseTimes[endAt - 1]++; } } } for (int i = 0; i < 20; i++) { System.out.printf("玩家在第 %d 次投骰獲勝的機率 = %f\n", i + 1, (double) winTimes[i] / ((double) winTimes[i] + loseTimes[i])); } }
// 回傳值若為正 i,代表在第 i 局玩家贏 // 回傳值若為負 i,代表在第 i 局玩家輸 public static int playOnceGame() { int myPoint = 0; Status gameStatus; int totalPlayTimes = 1; // 統計骰幾次才結束遊戲 int sumOfDice = rollDice(); switch (sumOfDice) { case SEVEN: case YO_LEVEN: gameStatus = Status.WON; break; case SNAKE_EYES: case TREY: case BOX_CARS: gameStatus = Status.LOST; break; default: gameStatus = Status.CONTINUE; myPoint = sumOfDice; break; } while (gameStatus == Status.CONTINUE) { sumOfDice = rollDice(); totalPlayTimes++; if (sumOfDice == myPoint) { gameStatus = Status.WON; } else { if (sumOfDice == SEVEN) { gameStatus = Status.LOST; } } } if (gameStatus == Status.WON) { return totalPlayTimes; } else { return -totalPlayTimes; } }
public static int rollDice() { int die1 = 1 + randomNumbers.nextInt(6); int die2 = 1 + randomNumbers.nextInt(6); int sum = die1 + die2; return sum; }}執行的結果:

題目 7.30 (Card Shuffling and Dealing)
Modify Fig. 7.11 to deal a five-card poker hand. Then modify class DeckOfCards of Fig. 7.10 to include methods that determine whether a hand contains:
- a) a pair
- b) two pairs
- c) three of a kind (e.g., three jacks)
- d) four of a kind (e.g., four aces)
- e) a flush (i.e., all five cards of the same suit)
- f) a straight (i.e., five cards of consecutive face values)
- g) a full house (i.e., two cards of one face value and three cards of another face value)
[Hint: Add methods getFace and getSuit to class Card of Fig. 7.9.]
Fig.7.11
![]()
Fig.7.10
![]()
![]()
![]()
Fig.7.9
7.30 參考解答
撲克牌
face為數字(A, 2, 3, 4 ...)
suit則為花色(Hearts, Diamonds, Clubs, Spades)
關於撲克牌的牌型資訊可以參考維基百科。
獲取 7.30 程式碼
這題我們會需要用到 Fig.7.11, Fig.7.10, 和 Fig.7.9 這三張圖中的程式碼:
// Card shuffling and dealing.
public class DeckOfCardsTest { // execute application public static void main(String[] args) { DeckOfCards myDeckOfCards = new DeckOfCards(); myDeckOfCards.shuffle(); // place Cards in random order
// print all 52 Cards in the order in which they are dealt for (int i = 1; i <= 52; i++) { // deal and display a Card System.out.printf("%-19s", myDeckOfCards.dealCard());
if (i % 4 == 0) { // output a newline after every fourth card System.out.println(); } } }}
/************************************************************************** * (C) Copyright 1992-2018 by Deitel & Associates, Inc. and * * Pearson Education, Inc. All Rights Reserved. * * * * DISCLAIMER: The authors and publisher of this book have used their * * best efforts in preparing the book. These efforts include the * * development, research, and testing of the theories and programs * * to determine their effectiveness. The authors and publisher make * * no warranty of any kind, expressed or implied, with regard to these * * programs or to the documentation contained in these books. The authors * * and publisher shall not be liable in any event for incidental or * * consequential damages in connection with, or arising out of, the * * furnishing, performance, or use of these programs. * *************************************************************************/// DeckOfCards class represents a deck of playing cards.import java.security.SecureRandom;
public class DeckOfCards { // random number generator private static final SecureRandom randomNumbers = new SecureRandom(); private static final int NUMBER_OF_CARDS = 52; // constant # of Cards
private Card[] deck = new Card[NUMBER_OF_CARDS]; // Card references private int currentCard = 0; // index of next Card to be dealt (0-51)
// constructor fills deck of Cards public DeckOfCards() { String[] faces = {"Ace", "Deuce", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King"}; String[] suits = {"Hearts", "Diamonds", "Clubs", "Spades"};
// populate deck with Card objects for (int count = 0; count < deck.length; count++) { deck[count] = new Card(faces[count % 13], suits[count / 13]); } }
// shuffle deck of Cards with one-pass algorithm public void shuffle() { // next call to method dealCard should start at deck[0] again currentCard = 0;
// for each Card, pick another random Card (0-51) and swap them for (int first = 0; first < deck.length; first++) { // select a random number between 0 and 51 int second = randomNumbers.nextInt(NUMBER_OF_CARDS);
// swap current Card with randomly selected Card Card temp = deck[first]; deck[first] = deck[second]; deck[second] = temp; } }
// deal one Card public Card dealCard() { // determine whether Cards remain to be dealt if (currentCard < deck.length) { return deck[currentCard++]; // return current Card in array } else { return null; // return null to indicate that all Cards were dealt } }}
/************************************************************************** * (C) Copyright 1992-2018 by Deitel & Associates, Inc. and * * Pearson Education, Inc. All Rights Reserved. * * * * DISCLAIMER: The authors and publisher of this book have used their * * best efforts in preparing the book. These efforts include the * * development, research, and testing of the theories and programs * * to determine their effectiveness. The authors and publisher make * * no warranty of any kind, expressed or implied, with regard to these * * programs or to the documentation contained in these books. The authors * * and publisher shall not be liable in any event for incidental or * * consequential damages in connection with, or arising out of, the * * furnishing, performance, or use of these programs. * *************************************************************************/// Card class represents a playing card.
public class Card { private final String face; // face of card ("Ace", "Deuce", ...) private final String suit; // suit of card ("Hearts", "Diamonds", ...)
// two-argument constructor initializes card's face and suit public Card(String cardFace, String cardSuit) { this.face = cardFace; // initialize face of card this.suit = cardSuit; // initialize suit of card }
// return String representation of Card public String toString() { return face + " of " + suit; }}
/************************************************************************** * (C) Copyright 1992-2018 by Deitel & Associates, Inc. and * * Pearson Education, Inc. All Rights Reserved. * * * * DISCLAIMER: The authors and publisher of this book have used their * * best efforts in preparing the book. These efforts include the * * development, research, and testing of the theories and programs * * to determine their effectiveness. The authors and publisher make * * no warranty of any kind, expressed or implied, with regard to these * * programs or to the documentation contained in these books. The authors * * and publisher shall not be liable in any event for incidental or * * consequential damages in connection with, or arising out of, the * * furnishing, performance, or use of these programs. * *************************************************************************/把這三個 .java 檔放到同一個專案裡面後,執行的結果如下:

7.30 題幹
Modify Fig.7.11 to deal a five-card poker hand. Then modify class DeckOfCards of Fig.7.10 to include methods that determine whether a hand contains: … 略
[Hint: Add methods getFace and getSuit to class Card of Fig. 7.9.]
題幹告訴我們可以用它給的程式碼做修改,並且還提示我們要在 Card 新增 getFace 和 getSuit 兩個 method。
所以我們首先可以修改 Card 類備用:
public class Card {
// face 和 suit 是 private 變數 private final String face; private final String suit;
public Card(String cardFace, String cardSuit) { this.face = cardFace; this.suit = cardSuit; }
public String toString() { return face + " of " + suit; }
// 新增 getFace 和 getSuit public String getFace() { return this.face; }
public String getSuit() { return this.suit; }}我們在此處假定程式只需要判斷每一種牌型是或否存在(也就是說,同時具備 pair 與 two pair 屬性的話,兩者皆為 true)。
7.30 a 選項
a) a pair
我們實作一個 isPair 方法來檢查牌組是否為 pair。
註:
pair的定義是具有兩張以上同樣face的牌。
此處只貼上實作isPair方法的部分。
public boolean isPair(Card... cards) { for (int i = 0; i < cards.length - 1; i++) { for (int j = i + 1; j < cards.length; j++) { if (cards[i].getFace().equals(cards[j].getFace())) return true; } } return false;}針對幾行作解釋:
Card... cards(你沒看錯,...不是省略的意思,真的是點點點)
這種語法可以讓 Java 的方法的參數更加靈活,使用這個語法作為參數允許方法:
- 接收一個類型為
Card[]的 Array - 接收數個(拆開來傳遞)
Card的物件(也就是單張撲克牌卡片) - 不傳入任何
Card。
如果傳入的資料是數個拆開來傳遞的 Card 物件,Java 會把它們組成一個 Array,名稱是 Card... cards 後方的 cards。
equals:比較兩陣列中的元素是否完全相等。關於equals的用法請點我(超連結)
7.30 b 選項
b) two pairs
我們實作一個 isTwoPairs 方法來檢查牌組是否為 Two Pairs。
註:
Two Pairs的定義是具有兩組不同face的pair。
此處只貼上實作isTwoPairs方法的部分。
public boolean isTwoPairs(Card... cards) { Card firstPair = null; Card secondPair = null;
for (int i = 0; i < cards.length - 1; i++) { for (int j = i + 1; j < cards.length; j++) { if (cards[i].getFace().equals(cards[j].getFace())) { firstPair = cards[i]; break; } } }
for (int i = 0; i < cards.length - 1; i++) { for (int j = i + 1; j < cards.length; j++) { if (cards[i].getFace().equals(cards[j].getFace()) && !(cards[i].getFace().equals(firstPair.getFace()))) secondPair = cards[i]; } }
return firstPair != null && secondPair != null;}b 選項我們使用 firstPair 跟 secondPair 儲存找到的第一組 pair 跟第二組 pair。
在尋找第二組 pair 的時候,我們有指定 !(cards[i].getFace().equals(firstPair.getFace())),所以我們找到的兩個 pair 不可能是同樣的 face。
當 firstPair 和 secondPair 都不是 null 的時候,代表我們有找到兩組不同 face 的 pair,就會回傳 true。
7.30 c 選項
c) three of a kind (e.g., three jacks)
我們實作一個 isThreeOfAKind 方法來檢查牌組是否為 three of a kind。
註:
three of a kind的定義是有三個一樣的face。
此處只貼上實作isThreeOfAKind方法的部分。
public boolean isThreeOfAKind(Card... cards) { for (int i = 0; i < cards.length - 1; i++) { int faceCount = 1;
for (int j = i + 1; j < cards.length; j++) { if (cards[i].getFace().equals(cards[j].getFace())) faceCount++; }
if (faceCount >= 3) return true; }
return false;}這段程式碼在 Reference 的程式碼中判斷條件是 if (faceCount == 3)。但我認為即使是四張一樣 face 的牌也算是符合定義,故改為 if (faceCount >= 3)。
7.30 d 選項
d) four of a kind (e.g., four aces)
我們實作一個 isFourOfAKind 方法來檢查牌組是否為 four of a kind。
註:
four of a kind的定義是有四個一樣的face。 此處只貼上實作isFourOfAKind方法的部分。
public boolean isFourOfAKind(Card... cards) { for (int i = 0; i < cards.length - 1; i++) { int faceCount = 1;
for (int j = i + 1; j < cards.length; j++) { if (cards[i].getFace().equals(cards[j].getFace())) faceCount++; }
if (faceCount == 4) return true; }
return false;}7.30 e 選項
e) a flush (i.e., all five cards of the same suit)
我們實作一個 isFlush 方法來檢查牌組是否為 flush。
註:
flush的定義是五張牌的suit皆相同。
此處只貼上實作isFlush方法的部分。
public boolean isFlush(Card... cards) { Card firstCard = cards[0];
int suitCount = 1;
for (int i = 1; i < cards.length; i++) { if (cards[i].getSuit().equals(firstCard.getSuit())) suitCount++; }
return suitCount == 5;}7.30 f 選項
f) a straight (i.e., five cards of consecutive face values)
我們實作一個 isStraight 方法來檢查牌組是否為 Straight。
註:
Straight的定義是牌組中的face是連號,而且Ace只能在頭、尾。 此處貼上isStraight、isStraightForPart、sortCards、valueOf方法。
public boolean isStraight(Card... cards) { Card[] sortedCards = new Card[cards.length]; System.arraycopy(cards, 0, sortedCards, 0, cards.length);
sortCards(sortedCards); int currentValue;
if (sortedCards[0].getFace() == "Ace") { // situation1: Ace, Deuce, Three, Four, Five // situation2: Ten, Jack, Queen, King, Ace (unsorted) → Ace, Ten, Jack, Queen, // King (sorted) // 在 situation2 中,可以把 Ace 的 currentValue 當作 8 (跟 Nine 的 currentValue 相同)來判斷。 boolean situation1 = isStraightForPart(currentValue = 0, sortedCards); boolean situation2 = isStraightForPart(currentValue = 8, sortedCards); return (situation1 || situation2); } else { currentValue = valueOf(sortedCards[0].getFace()); return isStraightForPart(currentValue, sortedCards); }}
private boolean isStraightForPart(int currentValue, Card... cards) { for (int i = 1; i < cards.length; i++) { if (valueOf(cards[i].getFace()) == currentValue + 1) currentValue++; else return false; } return true;}
private void sortCards(Card... cards) { for (int i = 0; i < cards.length - 1; i++) { for (int j = i + 1; j < cards.length; j++) { if (valueOf(cards[i].getFace()) > valueOf(cards[j].getFace())) { Card card = cards[i]; cards[i] = cards[j]; cards[j] = card; } } }}
private int valueOf(String face) { String[] faces = { "Ace", "Deuce", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King" };
for (int i = 0; i < faces.length; i++) { if (faces[i].equals(face)) return i; }
return -1;}System.arraycopy(cards, 0, sortedCards, 0, cards.length);arraycopy的用法:System.arraycopy(來源陣列, 複製的起始 index, 目標陣列, 貼上的起始 index, 複製幾個元素);
valueOf- 取得牌面
face的「大小」(定義Ace是0,Deuce是1, …)
- 取得牌面
sortCards- 依照
valueOf取得的「大小」進行 Bubble Sort。
- 依照
isStraightForPart- 由於在
Straight的判定中,Ace有可能出現在頭也有可能出現在尾,所以遇到有Ace的情況時需要分開處理。 - 將判斷有沒有「連號」的 for 迴圈部分獨立成
isStraightForPart,並在isStraight中分成不同情況呼叫,最後回傳。
- 由於在
7.30 g 選項
g) a full house (i.e., two cards of one face value and three cards of another face value)
我們實作一個 isFullHouse 方法來檢查牌組是否為 Full House。
註:
Full House的定義是兩個同樣的face配上三個一樣的另一種face。 此處只貼上實作isFullHouse方法的部分。
public boolean isFullHouse(Card... cards) { int firstValueCount = 1; int firstValue = valueOf(cards[0].getFace());
for (int i = 1; i < cards.length; i++) { if (valueOf(cards[i].getFace()) == firstValue) firstValueCount++; }
if (firstValueCount == 2 || firstValueCount == 3) { int secondValueCount = 1; int secondValue = 0; int initialIndex = 0;
for (int i = 0; i < cards.length; i++) { if (valueOf(cards[i].getFace()) != firstValue) { secondValue = valueOf(cards[i].getFace()); initialIndex = i; break; } }
for (int i = initialIndex + 1; i < cards.length; i++) { if (valueOf(cards[i].getFace()) == secondValue) secondValueCount++; }
if (secondValueCount == (5 - firstValueCount)) return true; } return false;}firstValue儲存第一組相同的face(兩張以上相同) 。firstValueCount記錄第一組相同的face共有幾張牌。- 若
firstValueCount是兩張或三張(才有可能是Full House),就進行判斷。secondValueCount需剛好是(5 -firstValueCount),才符合Full House的定義(2 配 3)。
7.30 執行結果截圖

題目 7.31 (Card Shuffling and Dealing)
Use the methods developed in Exercise 7.30 to write an application that deals two five-card poker hands, evaluates each hand and determines which is better.
7.31 參考解答
這題要我們從一副牌洗出兩手(各五張),並比較兩者 which is better。
謎之語:better… 是要比到多細…
我打算設計一個評分系統,每副牌都可以透過計算出來的分數直接比較輸贏。
(按照牌型大小比較,同牌型者直接判定平手)
一般德州撲克牌型大小依序為:
Four of a kind>Full House>Flush>Straight>Three of a kind>Two pairs>A pair>No pair。
寫一個 compareHand 方法在 DeckOfCards 類裡面:
public int compareHands(Card[] hand1, Card[] hand2) { int handPower1 = 0; int handPower2 = 0;
if (isFourOfAKind(hand1)) handPower1 = 10; else if (isFullHouse(hand1)) handPower1 = 9; else if (isFlush(hand1)) handPower1 = 8; else if (isStraight(hand1)) handPower1 = 7; else if (isThreeOfAKind(hand1)) handPower1 = 6; else if (isTwoPairs(hand1)) handPower1 = 5; else if (isPair(hand1)) handPower1 = 4;
if (isFourOfAKind(hand2)) handPower2 = 10; else if (isFullHouse(hand2)) handPower2 = 9; else if (isFlush(hand2)) handPower2 = 8; else if (isStraight(hand2)) handPower2 = 7; else if (isThreeOfAKind(hand2)) handPower2 = 6; else if (isTwoPairs(hand2)) handPower2 = 5; else if (isPair(hand2)) handPower2 = 4;
if (handPower1 > handPower2) return 1; else if (handPower2 > handPower1) return -1; else return 0;}接著在 DeckOfCardsTest 中取兩手牌,進行比較並輸出。我們順便寫一個 displayHand 方法,方便輸出:
package EX_7_31;
public class DeckOfCardsTest {
public static void main(String[] args) { DeckOfCards myDeckOfCards = new DeckOfCards(); myDeckOfCards.shuffle();
Card[] hand1 = new Card[5]; Card[] hand2 = new Card[5];
for (int i = 0; i < 5; i++) { hand1[i] = myDeckOfCards.dealCard(); hand2[i] = myDeckOfCards.dealCard(); }
System.out.print("Hand 1: "); displayHand(hand1); System.out.print("Hand 2: "); displayHand(hand2);
System.out.println();
if (myDeckOfCards.compareHands(hand1, hand2) == 1) System.out.println("Hand 1 won!"); else if (myDeckOfCards.compareHands(hand1, hand2) == -1) System.out.println("Hand 2 won!"); else System.out.println("Draw");
}
public static void displayHand(Card[] hand) { for (Card card : hand) System.out.printf("%s - ", card);
System.out.println(); }}7.31 執行結果截圖

Reference
7.30 和 7.31 的部分程式碼參考了這個 Github Repo。有些我認為其程式碼有誤的地方有做修改。
部分內容可能已過時



