/home/wpollock1/public_html/Java/TwentyOne.java
/** TwentyOne.java - A program that allows the user to play the card game 21
* against the computer "dealer".
*
* Written 3/2006 by Wayne Pollock, Tampa Florida USA.
*/
import java.util.*;
import javax.swing.JOptionPane;
/** Only the dealer's first card is shown during play. The value of a hand
* is determined by adding the ranks of its cards, with
* picture cards worth 10, and an Ace worth either 1 or 11. As long as
* the value of the player's hand is less than 21 they can take a "hit",
* that is add a card from the "shoe" to their hand. The higher score wins,
* with a tie going to the dealer. In this simple version of the game,
* the dealer will always hit on 16 and "stand pat" on 17.
*/
class TwentyOne
{
private static final String title = "The Card Game 21";
private static final int STAND_PAT_POINT = 17;
private static Shoe shoe = new Shoe();
public static void main ( String [] args )
{
int rc = 0; // The return code from the showConfirmDialog
do
{ playGame();
rc = JOptionPane.showConfirmDialog( null, "Play Again?",
title, JOptionPane.YES_NO_OPTION );
} while ( rc == JOptionPane.YES_OPTION );
}
private static void playGame ()
{
int rc = 0; // The return code from the showConfirmDialog
Hand dealersHand = new Hand( shoe ),
playersHand = new Hand( shoe );
StringBuilder initMsg = new StringBuilder( "Dealer shows : " );
initMsg.append( dealersHand.firstCard() );
initMsg.append( "\n\nPlayer's Hand: " );
while ( playersHand.value() < 21 )
{
StringBuilder msg = new StringBuilder( initMsg );
msg.append( playersHand.toString() );
msg.append( "\n\nDo you want another card?" );
rc = JOptionPane.showConfirmDialog( null, msg, title,
JOptionPane.YES_NO_CANCEL_OPTION );
if ( rc != JOptionPane.YES_OPTION )
break;
playersHand.hit();
}
if ( rc == JOptionPane.CANCEL_OPTION )
return;
// Determine who won, and display appropriate message:
String msg = null;
if ( playersHand.value() > 21 )
{
msg = "Sorry, you lost!\n\n";
} else
{
while ( dealersHand.value() < STAND_PAT_POINT )
dealersHand.hit();
if ( dealersHand.value() > 21 ||
playersHand.value() > dealersHand.value() )
msg = "Congratulations, you won!\n\n";
else
msg = "Sorry, you lost!\n\n";
}
msg += "Dealers Hand: " + dealersHand.toString() + "\n\n" +
"Players Hand: " + playersHand.toString();
JOptionPane.showMessageDialog( null, msg, title,
JOptionPane.PLAIN_MESSAGE );
}
}
/** Class Shoe represents a set of decks of standard (52) playing cards.
* In a real casino, they shuffle and deal from a "shoe" containing
* 4 or more decks. No matter how many decks are in the show, when only
* a few are left, to prevent couting, the entire shoe of cards is
* re-shuffled. In this program I used 5% unused as the time to re-shuffle.
*/
class Shoe
{
private static final int NUM_DECKS = 4;
private static final int RESHUFFLE_POINT = (int) (0.05 * (NUM_DECKS * 52) );
private ArrayList<Card> deck;
public Shoe ()
{ shuffleCards();
}
public ArrayList<Card> drawCards ( int numCardsToDeal )
{
ArrayList<Card> cards = new ArrayList<Card>( numCardsToDeal );
for ( int i = 0; i < numCardsToDeal; ++i )
{
if ( deck.size() <= RESHUFFLE_POINT )
shuffleCards();
cards.add( deck.remove(0) );
}
return cards;
}
private void shuffleCards ()
{
deck = new ArrayList<Card>( NUM_DECKS * 52 );
for ( int numDecks = 0; numDecks < NUM_DECKS; ++numDecks )
for ( int suit = 1; suit <= 4; ++suit )
for ( int rank = 1; rank <= 13; ++rank )
deck.add( new Card( suit, rank ) );
Collections.shuffle( deck );
}
}
/** Class Hand represents the "hand" of cards held by some player.
* In the came 21, each hand contains two cards initially.
*/
class Hand
{
private ArrayList<Card> cards = new ArrayList<Card>();
private Shoe shoe;
public Hand ( Shoe shoe )
{
this.shoe = shoe;
// Deal two cards to initialize the hand:
cards.addAll( shoe.drawCards( 2 ) );
}
/** Calculates the value of the hand. Picture cards are worth 10 and an
* Ace is worth 1 or 11.
*/
public int value ()
{
int total = 0;
boolean hasAce = false;
for ( Card card : cards )
{
int val = card.value();
if ( val == 1 ) hasAce = true;
total += val;
}
while ( total < (21 - 10) && hasAce )
total += 10;
return total;
}
/** Add one card to the hand from the shoe.
*/
public void hit () { cards.addAll( shoe.drawCards(1) ); }
/** During the play only the first card of the dealer's hand should be shown.
*/
public Card firstCard() { return cards.get( 0 ); }
public String toString ()
{
StringBuilder sb = new StringBuilder();
for ( Card card : cards )
{
sb.append( card );
sb.append( " " );
}
return sb.toString();
}
}
/** Represents one card from a standard "bridge" pack of (52) cards.
*/
class Card
{
final int suit; // A Java 5 "enum" would be a good choice instead of int!
final int rank;
Card ( int suit, int rank )
{
if ( suit < 1 || suit > 4 )
throw new IllegalArgumentException(
"suit must be between 1 (clubs) and 4 (spades)." );
if ( rank < 1 || rank > 13 )
throw new IllegalArgumentException(
"rank must be between 1 (ace) and 13 (king)" );
this.suit = suit;
this.rank = rank;
}
/** Calculates the value of the card.
* @return the rank of the card, with Ace = 1, picture cards = 10.
*/
public int value ()
{
int val = rank;
if ( rank > 10 )
val = 10;
return val;
}
/** Uses ASCII to show the rank (A,2,3,4,5,6,7,8,9,10,J,Q,K) and suit.
* Note that if using console output it would be better to use letters
* for the suits (C, D, H, and S) rather than symbols, as they wouldn't
* show up properly on all platforms.
*/
public String toString()
{
StringBuilder sb = new StringBuilder( 2 );
switch ( rank )
{
case 1: sb.append( 'A' ); break;
case 11: sb.append( 'J' ); break;
case 12: sb.append( 'Q' ); break;
case 13: sb.append( 'K' ); break;
default: sb.append( rank ); break;
}
switch ( suit )
{
case 1: sb.append( '\u2663' ); break; // Club
case 2: sb.append( '\u2666' ); break; // Diamond
case 3: sb.append( '\u2665' ); break; // Heart
case 4: sb.append( '\u2660' ); break; // Spade
}
return sb.toString();
}
}