TwentyOne.java

Download TwentyOne.java

  1: /** TwentyOne.java - A program that allows the user to play the card game 21
  2:  *  against the computer "dealer".
  3:  *
  4:  * Written 3/2006 by Wayne Pollock, Tampa Florida USA.
  5:  */
  6: 
  7: import java.util.*;
  8: import javax.swing.JOptionPane;
  9: 
 10: /** Only the dealer's first card is shown during play.  The value of a hand
 11:  *  is determined by adding the ranks of its cards, with
 12:  *  picture cards worth 10, and an Ace worth either 1 or 11.  As long as
 13:  *  the value of the player's hand is less than 21 they can take a "hit",
 14:  *  that is add a card from the "shoe" to their hand.  The higher score wins,
 15:  *  with a tie going to the dealer.  In this simple version of the game,
 16:  *  the dealer will always hit on 16 and "stand pat" on 17.
 17:  */
 18: class TwentyOne
 19: {
 20:    private static final String title = "The Card Game 21";
 21:    private static final int STAND_PAT_POINT = 17;
 22: 
 23:    private static Shoe shoe = new Shoe();
 24: 
 25:    public static void main ( String [] args )
 26:    {
 27:       int rc = 0;  // The return code from the showConfirmDialog
 28: 
 29:       do
 30:       {  playGame();
 31:          rc = JOptionPane.showConfirmDialog( null, "Play Again?",
 32:             title, JOptionPane.YES_NO_OPTION );
 33:       } while ( rc == JOptionPane.YES_OPTION );
 34:    }
 35: 
 36:    private static void playGame ()
 37:    {
 38:       int rc = 0;  // The return code from the showConfirmDialog
 39:       Hand dealersHand = new Hand( shoe ),
 40:            playersHand = new Hand( shoe );
 41: 
 42:       StringBuilder initMsg = new StringBuilder( "Dealer shows : " );
 43:       initMsg.append( dealersHand.firstCard() );
 44:       initMsg.append( "\n\nPlayer's Hand: " );
 45: 
 46:       while ( playersHand.value() < 21 )
 47:       {
 48:          StringBuilder msg = new StringBuilder( initMsg );
 49:          msg.append( playersHand.toString() );
 50:          msg.append( "\n\nDo you want another card?" );
 51: 
 52:          rc = JOptionPane.showConfirmDialog( null, msg, title,
 53:             JOptionPane.YES_NO_CANCEL_OPTION );
 54: 
 55:          if ( rc != JOptionPane.YES_OPTION )
 56:             break;
 57:          playersHand.hit();
 58:       }
 59: 
 60:       if ( rc == JOptionPane.CANCEL_OPTION )
 61:          return;
 62: 
 63:       // Determine who won, and display appropriate message:
 64:       String msg = null;
 65: 
 66:       if ( playersHand.value() > 21 )
 67:       {
 68:          msg = "Sorry, you lost!\n\n";
 69:       } else
 70:       {
 71:          while ( dealersHand.value() < STAND_PAT_POINT )
 72:             dealersHand.hit();
 73: 
 74:          if ( dealersHand.value() > 21 ||
 75:               playersHand.value() > dealersHand.value() )
 76:             msg = "Congratulations, you won!\n\n";
 77:          else
 78:             msg = "Sorry, you lost!\n\n";
 79:       }
 80: 
 81:       msg += "Dealers Hand: " + dealersHand.toString() + "\n\n" +
 82:              "Players Hand: " + playersHand.toString();
 83: 
 84:       JOptionPane.showMessageDialog( null, msg, title,
 85:          JOptionPane.PLAIN_MESSAGE );
 86:    }
 87: }
 88: 
 89: /** Class Shoe represents a set of decks of standard (52) playing cards.
 90:  *  In a real casino, they shuffle and deal from a "shoe" containing
 91:  *  4 or more decks.  No matter how many decks are in the show, when only
 92:  *  a few are left, to prevent couting, the entire shoe of cards is
 93:  *  re-shuffled.  In this program I used 5% unused as the time to re-shuffle.
 94:  */
 95: 
 96: class Shoe
 97: {
 98:    private static final int NUM_DECKS = 4;
 99:    private static final int RESHUFFLE_POINT = (int) (0.05 * (NUM_DECKS * 52) );
100: 
101:    private ArrayList<Card> deck;
102: 
103:    public Shoe ()
104:    {   shuffleCards();
105:    }
106: 
107:    public ArrayList<Card> drawCards ( int numCardsToDeal )
108:    {
109:       ArrayList<Card> cards = new ArrayList<Card>( numCardsToDeal );
110: 
111:       for ( int i = 0; i < numCardsToDeal; ++i )
112:       {
113:          if ( deck.size() <= RESHUFFLE_POINT )
114:             shuffleCards();
115:          cards.add( deck.remove(0) );
116:       }
117:       return cards;
118:    }
119: 
120:    private void shuffleCards ()
121:    {
122:       deck = new ArrayList<Card>( NUM_DECKS * 52 );
123: 
124:       for ( int numDecks = 0; numDecks < NUM_DECKS; ++numDecks )
125:          for ( int suit = 1; suit <= 4; ++suit )
126:             for ( int rank = 1; rank <= 13; ++rank )
127:                deck.add( new Card( suit, rank ) );
128: 
129:       Collections.shuffle( deck );
130:    }
131: }
132: 
133: /** Class Hand represents the "hand" of cards held by some player.
134:  *  In the came 21, each hand contains two cards initially.
135:  */
136: 
137: class Hand
138: {
139:    private ArrayList<Card> cards = new ArrayList<Card>();
140:    private Shoe shoe;
141: 
142:    public Hand ( Shoe shoe )
143:    {
144:       this.shoe = shoe;
145: 
146:       // Deal two cards to initialize the hand:
147:       cards.addAll( shoe.drawCards( 2 ) );
148:    }
149: 
150:    /** Calculates the value of the hand.  Picture cards are worth 10 and an
151:     *  Ace is worth 1 or 11.
152:     */
153:    public int value ()
154:    {
155:       int total = 0;
156:       boolean hasAce = false;
157:       for ( Card card : cards )
158:       {
159:          int val = card.value();
160:          if  ( val == 1 )  hasAce = true;
161:          total += val;
162:       }
163: 
164:       while ( total < (21 - 10) && hasAce )
165:          total += 10;
166:       return total;
167:    }
168: 
169:    /** Add one card to the hand from the shoe.
170:     */
171:    public void hit () {  cards.addAll( shoe.drawCards(1) ); }
172: 
173:    /** During the play only the first card of the dealer's hand should be shown.
174:     */
175:    public Card firstCard() { return cards.get( 0 );  }
176: 
177:    public String toString ()
178:    {
179:       StringBuilder sb = new StringBuilder();
180:       for ( Card card : cards )
181:       {
182:          sb.append( card );
183:          sb.append( "   " );
184:       }
185:       return sb.toString();
186:    }
187: }
188: 
189: /** Represents one card from a standard "bridge" pack of (52) cards.
190:  */
191: class Card
192: {
193:    final int suit;  // A Java 5 "enum" would be a good choice instead of int!
194:    final int rank;
195: 
196:    Card ( int suit, int rank )
197:    {
198:       if ( suit < 1 || suit > 4 )
199:          throw new IllegalArgumentException(
200:             "suit must be between 1 (clubs) and 4 (spades)." );
201: 
202:       if ( rank < 1 || rank > 13 )
203:          throw new IllegalArgumentException(
204:             "rank must be between 1 (ace) and 13 (king)" );
205: 
206:       this.suit = suit;
207:       this.rank = rank;
208:    }
209: 
210:    /** Calculates the value of the card.
211:     *  @return the rank of the card, with Ace = 1, picture cards = 10.
212:     */
213:    public int value ()
214:    {
215:       int val = rank;
216:       if ( rank > 10 )
217:          val = 10;
218:       return val;
219:    }
220: 
221:    /** Uses ASCII to show the rank (A,2,3,4,5,6,7,8,9,10,J,Q,K) and suit.
222:     *  Note that if using console output it would be better to use letters
223:     *  for the suits (C, D, H, and S) rather than symbols, as they wouldn't
224:     *  show up properly on all platforms.
225:     */
226:    public String toString()
227:    {
228:       StringBuilder sb = new StringBuilder( 2 );
229:       switch ( rank )
230:       {
231:          case  1: sb.append( 'A' );  break;
232:          case 11: sb.append( 'J' );  break;
233:          case 12: sb.append( 'Q' );  break;
234:          case 13: sb.append( 'K' );  break;
235:          default: sb.append( rank ); break;
236:       }
237: 
238:       switch ( suit )
239:       {
240:          case 1: sb.append( '\u2663' );  break;  // Club
241:          case 2: sb.append( '\u2666' );  break;  // Diamond
242:          case 3: sb.append( '\u2665' );  break;  // Heart
243:          case 4: sb.append( '\u2660' );  break;  // Spade
244:       }
245:       return sb.toString();
246:    }
247: }