/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();
   }
}