import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.Math;

/**
 * Applet to show BlackJack odds
 * Creation date: (02/05/2002 10:16:43 AM)
 * Modified date: (04/15/2002 02:45:00 PM)
 * @author: Andrew Freed, arf132@psu.edu
 */

/*
Development of this applet was sponsored by the Penn State Fund for 
Excellence in Learning and Teaching (FELT), project "Java-based 
Teaching of Mathematics in Information Sciences and Technology",
supervised by Frank Ritter and David Mudgett.
*/

public class BlackjackApplet extends Applet {
	private Label	lblInfo		= new Label("Odds of busting: ");
	private Label 	lblUser		= new Label("User hand:");
	private Button 	btnDeal 		= new Button("Deal");
	private Button	btnHit		= new Button("Hit!");
	private Button	btnStand		= new Button("Stand");
	private Label 	lblUserHand		= new Label("<Your hand>");
	private Label	lblSum		= new Label("Sum: ");

	private int deck[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	private int numDecks = 1; //Change this to simulate more decks
	private int cardsDealt = 0; //To figure out when it is time to reshuffle
	private int numHighAces = 0; //To help counting functions determine hand value/bust odds

//Deal two random cards to user's hand
private void deal()
{
	//Re-shuffle if half of deck is used
	if( cardsDealt > (numDecks * 52 / 2) )
		initDeck();

	String newHand = "";
	String newCard;

	lblUserHand.setText("");

	for( int i=1; i<=2; i++)
	{
		newCard = getRandomCard();
		newHand = addNewCard(newHand, newCard);
	}

	btnHit.setEnabled(true);
	btnStand.setEnabled(true);
	btnDeal.setEnabled(false);
	btnHit.requestFocus();

	lblUserHand.setText(newHand);
	calculateBustOdds(newHand);

}

//Add one card to the user's hand
private void hit()
{
	String myHand = lblUserHand.getText();
	String newCard = getRandomCard();
	myHand = addNewCard(myHand, newCard);

	lblUserHand.setText(myHand);
	calculateBustOdds(myHand);

}

//End a user's turn, calculate card sum.  Prepare for next deal.
private void stand()
{
	String myHand = lblUserHand.getText();
	int sum = sumCards(myHand);
	if( sum <= 21 )
		lblInfo.setText("Safe play... but would you have won with " + sum + "?");
	else
		lblInfo.setText("Tough luck, but such is gambling...");

	btnHit.setEnabled(false);
	btnStand.setEnabled(false);
	btnDeal.setEnabled(true);
	btnDeal.requestFocus();
}

//Counts enough Aces as ones to stay below 21
private int sumCards(String myCards)
{
	int result = highSum(myCards);

	while( result > 21 && numHighAces > 0 )
	{
		result -= 10;
		numHighAces--;
	}

	return result;
}

//Counts all Aces as ones -- this helps find probability for busting
// Example: 9 A sums to 20, but has zero probability of busting
private int lowSumCards(String myCards)
{
	int result = highSum(myCards);
	
	while( numHighAces > 0 )
	{
		result -= 10;
		numHighAces--;
	}

	return result;
}

//This should never be called directly.  Either call sumCards or lowSumCards.
//Both of those functions handle the "lowering" of the Ace cards.
//For a hand like A A A this function will return 33. lowSumCards would return 3 and
// sumCards would return 13 on the same hand
private int highSum(String myCards)
{
	int result = 0;
	numHighAces = 0;

	//Read all values from user hand
	for( int idx = 0; idx < myCards.length(); idx++ )
	{
		String myCard = myCards.substring(idx, idx+1);

		//Hand will contain spaces, skip these
		if( myCard.equals(" ") )
		{
			continue;
		}

		else if( myCard.equals("J") || myCard.equals("Q") || 
			   myCard.equals("K") )
			result += 10;

		//If you read in a "1", it is part of a "10".  Thus skip the next character
		else if( myCard.equals("1") )
		{
			result += 10;
			idx++;
		}

		else if( myCard.equals("A") )
		{
			result += 11;
			numHighAces++;
		}
		
		//If it is a 2-9, we can just add this
		else
			result += Integer.parseInt(myCard);
	}

	return result;
}

private void calculateBustOdds(String myHand)
{
	//For the busting odds, we want the lowest possible sum, with each A=1
	int sum = lowSumCards(myHand);
	if (sum > 21)
	{
		lblInfo.setText("You bust!");
		lblSum.setText("Sum: " + sum);
		btnHit.setEnabled(false);
		btnStand.setEnabled(false);
		btnDeal.setEnabled(true);
		btnDeal.requestFocus();
		return;
	}
	else if (sum == 21)
	{
		lblInfo.setText("21! You win!");
		lblSum.setText("Sum: 21");
		btnHit.setEnabled(false);
		btnStand.setEnabled(false);
		btnDeal.setEnabled(true);
		btnDeal.requestFocus();
		return;
	}
	else if (sum < 12)
	{
		// It is possible that we have a 10-value card and an ace.
		// The low sum is then 11, but the high sum is 21, Blackjack!
		sum = sumCards(myHand);
		
		//Need to check if we have Blackjack
		if( sum == 21 )
		{
			lblInfo.setText("21! You win!");
			lblSum.setText("Sum: 21");
			btnHit.setEnabled(false);
			btnStand.setEnabled(false);
			btnDeal.setEnabled(true);
			btnDeal.requestFocus();
			return;
		}

		//Standard case, we still cannot bust because our low value is 11 or less
		//But we still want to display the sum counting as many high-aces as possible
		lblSum.setText("Sum: " + sum);
		lblInfo.setText("You cannot bust on your next card!");
		btnHit.requestFocus();
		return;
	}
	else // between 12 and 20, most common case
	{
		lblSum.setText("Sum: " + sum);
		btnHit.requestFocus();
	}

	// Margin is between 1 and 9
	int margin = 21 - sum;
	int totalCards = 0;    // remaining cards
	int availCards = 0;    // safe cards

	//You can always safely get an Ace
	totalCards += deck[0];
	availCards += deck[0];

	//You can never safely add a 10, J, Q, K since margin always < 10
	totalCards += deck[9];
	totalCards += deck[10];
	totalCards += deck[11];
	totalCards += deck[12];

	//Rememeber that deck[1] = "2", deck[2] = "3", etc by the array storage of the deck
	for( int idx = 1; idx <= 8; idx++ )
	{
		if( idx + 1 <= margin )
			availCards += deck[idx];

		//Always add to total cards
		totalCards += deck[idx];
	}

	double odds = ( (double) availCards ) / ( (double) totalCards);
	String output = "There are " + availCards + " out of the " + 
				totalCards + " remaining cards with which you will not bust. ";
	output += ("P = " + doubleToString(odds));

	lblInfo.setText(output);

	return;
}
	
//Simple function to add one new card to a hand
private String addNewCard(String myHand, String myCard)
{
	String result = myHand;
	if( myCard == "0" )
		result += (" 10 ");
	else
		result += (" " + myCard + " ");

	return result;
}

private String getRandomCard()
{
	String result = "";

	// myRand is between 0 and 12, inclusive
	int myRand = -1;
	boolean newCard = false;

	while(!newCard)
	{
		myRand = (int) ( 13 * java.lang.Math.random() );
		
		//With the given random value, make sure there is a card of that type available!
		if( deck[myRand] >= 0 )
			newCard = true;
	}

	deck[myRand]--;
	cardsDealt++;
	int cardNum = myRand;

	switch( cardNum )
	{
		case 0:	result = "A"; break;
		case 9:	result = "0"; break; // deck[9] is count of "10" cards
		case 10:	result = "J"; break;
		case 11:	result = "Q"; break;
		case 12:	result = "K"; break;

		//[1] = "2", [2] = "3", etc.
		default:	result = Integer.toString(++cardNum);
	}

	return result;
}

//This function takes a floating point value and returns a string with two decimal digits
private String doubleToString( double myNum )
{
	String result = "";
	String buf = "" + myNum + "";

	//Seems that this should always be the case, given myNum is float.
	int pos = buf.indexOf(".");
	if( pos != -1)
	{
		if( buf.length() - pos > 3 )
			result = buf.substring(0, buf.indexOf(".") + 3);
		else
			result = buf;
	}
	else
	{
		result = buf;
	}

	return result;
}

//Shuffles the deck.  Notice that this is actually just resetting the deck array,
//so that all the card values are available again. Cards are instead selected in 
//random order by the getRandomCard() function.
private void initDeck()
{
	lblInfo.setText("Shuffling...");
	for( int i=0; i < 13; i++ )
	{
		deck[i] = 4 * numDecks;
	}
	
	btnHit.setEnabled(false);
	btnStand.setEnabled(false);
	btnDeal.setEnabled(true); // should already be enabled...
	lblInfo.setText("Shuffling complete.  Please hit 'Deal' to get a new hand");

	btnDeal.requestFocus();
}

/**
 * Initializes the applet.
 */
public void init() {
	try {
		super.init();
		setName("BlackjackApplet");
		setLayout(null);
		setSize(500, 350);

		lblInfo.setBounds(10,70,480,30);
		lblUser.setBounds(10, 10, 70, 30);
		btnDeal.setBounds(300, 160, 50, 30);
		btnHit.setBounds(300, 190, 50, 30);
		btnStand.setBounds(300, 220, 50, 30);
		lblUserHand.setBounds(80, 10, 200, 30);
		lblSum.setBounds(10,30,100,30);
		
		add(lblInfo);
		add(lblUser);
		add(btnDeal);
		add(btnHit);
		add(btnStand);
		add(lblUserHand);
		add(lblSum);


			btnDeal.setActionCommand("btnDeal");
			btnDeal.addActionListener(new java.awt.event.ActionListener(){
				public void actionPerformed(java.awt.event.ActionEvent e)
				{
					deal();
				}
			});
			btnHit.setActionCommand("btnHit");
			btnHit.addActionListener(new java.awt.event.ActionListener(){
				public void actionPerformed(java.awt.event.ActionEvent e)
				{
					hit();
				}
			});
			btnStand.setActionCommand("btnStand");
			btnStand.addActionListener(new java.awt.event.ActionListener(){
				public void actionPerformed(java.awt.event.ActionEvent e)
				{
					stand();
				}
			});

		initDeck();

	} catch (java.lang.Throwable Exc) {
		handleException(Exc);
	}
}
    
/**
 * Called whenever the part throws an exception.
 * @param exception java.lang.Throwable
 */
private void handleException(java.lang.Throwable exception) {
	//Currently exceptions are not handled since the applet does not have
	//stdout or stderr available to it.
}

//Main function allows you to run this applet as an application
/**
 * main entrypoint - starts the part when it is run as an application
 * @param args java.lang.String[]
 */
public static void main(java.lang.String[] args) {
	try {
		Frame frame = new java.awt.Frame();
		BlackjackApplet aBlackjackApplet;
		Class iiCls = Class.forName("BlackjackApplet");
		ClassLoader iiClsLoader = iiCls.getClassLoader();
		aBlackjackApplet = (BlackjackApplet)java.beans.Beans.instantiate(iiClsLoader,"BlackjackApplet");
		frame.add("Center", aBlackjackApplet);
		frame.setSize(aBlackjackApplet.getSize());
		frame.addWindowListener(new java.awt.event.WindowAdapter() {
			public void windowClosing(java.awt.event.WindowEvent e) {
				System.exit(0);
			};
		});
		frame.show();
		java.awt.Insets insets = frame.getInsets();
		frame.setSize(frame.getWidth() + insets.left + insets.right, frame.getHeight() + insets.top + insets.bottom);
		frame.setVisible(true);
	} catch (Throwable exception) {
		System.err.println("Exception occurred in main() of java.applet.Applet");
		exception.printStackTrace(System.out);
	}
}

}
