///////////////////////////////
//  File:        ByteMe.java // 
//  Programmer:  Marc Douet  //
//  Date:        06/28/02    //
///////////////////////////////

import java.util.*;
import java.io.*;



//////////////////////////////////////////////////////////////////////////
//  Class Name:  ByteMe                  				//
//									//
//  Description: Main driver class of the Binary Black Jack Simulator.  //
//		 It is responsible for reading in input, playing the 	//
//               game of binary black jack, and printing the results.	//
//////////////////////////////////////////////////////////////////////////
public class ByteMe {

	////////////////////
	//  Data Members  //
	////////////////////
	private Dealer 		cardDealer;	    // Dealer of the card game.
	private Players		cardPlayers;	    // Players of the card game.
	private String 		tag;		    // String to read in the START/END tag.
	private String 		numPlayersString;   // String to read in the number of players.
	private String 		dealerHand;	    // String to hold the dealer's hand.
	private String 		playerHands;	    // String to hold all of the player's hands.
	private String		byteString;	    // String for all cards in the byte deck.
	private String		nibbleString;	    // String for all cards in the nibble deck.
	private String 		line;		    // String to hold a line from input.
	private StringTokenizer tokenBuffer;	    // Tokenizer to grab each data element.
	private StringTokenizer byteDeck;	    // Tokenizer to grab each byte in the deck.
	private StringTokenizer nibbleDeck;	    // Tokenizer to grab each nibble in the deck.
	private BufferedReader 	in;		    // Buffered Reader to read in input.
	private int 		numPlayers;	    // The number of players in this game.
	
	private final int 	WIN_SCORE = 510;    // The score needed to win the card game.
	private final int 	BYTE_LIMIT = 382;   // The highest score to take a byte hit on.
	private final int	NIBBLE_LIMIT = 500; // The highest score to take a nibble hit.
	private final int	BYTE = 1;	    // Flag for taking a byte-hit.
	private final int	NIBBLE = 0;	    // Flag for taking a nibble-hit.

	////////////////////////
	//  Member Functions  //
	////////////////////////
	//////////////////////////////////////////////////////////////////
	// Function:  Constructor					//
	// 								//
	// Synopsis:  public ByteMe(void)				//
	//								//
	// Description: Initializes all data members of the Binary 	//
	//              Black Jack Simulator.				//
	//								//
	// Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	public ByteMe()
	{
		// Initialize all data members.
		tag = null;			
		numPlayersString = null;		
		dealerHand = null;		
		playerHands = null;		
		byteString = null;
		nibbleString = null;
		line = null;			
		tokenBuffer = null;	
		byteDeck = null;
		nibbleDeck = null;
		numPlayers = 0;			
        	in = new BufferedReader(new InputStreamReader(System.in));
	}

	
	//////////////////////////////////////////////////////////////////
	// Function:  start						//
	// 								//
	// Synopsis:  public void start(void)				//
	// 								//
	// Description: Starts the simulation by reading the data set,	//
	//              dealing the cards to the dealer and players, 	//
	//		playing the card game, and printing the outcome.//
	//								//
	// Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	public void start()
	{
		// Read in the first line of input.
		try {
                	line = in.readLine();
			tokenBuffer = new StringTokenizer(line);
			tag = tokenBuffer.nextToken();
            	} catch(IOException ioError) {
			System.out.println(
				"Error occurred while reading in the first line of input.");
                	ioError.printStackTrace();
			System.exit(1);
            	}

		// While we have found a START tag...
		while((tag != null) && (tag.equals("START"))) {
			// Get the number of players from the token buffer.
                	numPlayersString = tokenBuffer.nextToken();
            		
			// If there are too few, or too many players, print an error and exit 1.
			numPlayers = Integer.parseInt(numPlayersString);
			if(numPlayers < 1 || numPlayers > 10) {
				System.out.println("Error: " + numPlayers 
					+ " is an invalid number of players.");
				System.exit(1);
			}

			// Read in the dealer's hand.	
			try {
                		dealerHand = in.readLine();
            		} catch(IOException ioError) {
				System.out.println(
					"Error occurred while reading in the dealer's hand.");
                		ioError.printStackTrace();
				System.exit(1);
            		}

			// Create a Dealer by populating his hand with the cards drawn.
			cardDealer = new Dealer(dealerHand);

			// Read in all of the players' hands.	
			try {
                		playerHands = in.readLine();
            		} catch(IOException ioError) {
				System.out.println(
					"Error occurred while reading in the players' hands.");
                		ioError.printStackTrace();
				System.exit(1);
            		}

			// Create the Players by populating all of the players' hands with 
			// the cards drawn.
			cardPlayers = new Players(playerHands, numPlayers);

			// Read in the byte deck (all byte cards availiable for hitting).
			try {
				byteString = in.readLine();
			} catch(IOException ioError) {
				System.out.println(
					"Error occurred while reading in the byte deck.");
                		ioError.printStackTrace();
				System.exit(1);
            		}

			// Read in the nibble deck (all nibble cards availiable for hitting).
			try {
				nibbleString = in.readLine();
			} catch(IOException ioError) {
				System.out.println(
					"Error occurred while reading in the nibble deck.");
                		ioError.printStackTrace();
				System.exit(1);
            		}

			// Read in the END Tag.
			try {
				tag = null;
				line = null;
                		line = in.readLine();
				tokenBuffer = new StringTokenizer(line);
				tag = tokenBuffer.nextToken();
            		} catch(IOException ioError) {
				System.out.println(
					"Error occurred while reading in the End Tag.");
                		ioError.printStackTrace();
				System.exit(1);
            		}

			// IF the END Tag was not found, print an error and exit 1.
			if(!tag.equals("END")) {
				System.out.println(
					"Error: No END tag was found after the data set.");
				System.exit(1);
			}

			// Play the card game and print out the results.
			playGame();

			// Try to read the next START Tag/Player Count pair.
			try {
				line = null;
				tag = null;
                		line = in.readLine();
            		} catch(IOException ioError) {
				System.out.println(
			   "Error occurred while reading in the start of the next data set.");
                		ioError.printStackTrace();
				System.exit(1);
            		}
			
			// If we were able to read another data line, tokenize and get the tag.
			try {
				if(line.startsWith("START")) {
					tokenBuffer = new StringTokenizer(line);
					tag = tokenBuffer.nextToken();
				}
			} catch(NullPointerException ptrError) {}

			// Re-initialize all strings used to read in input.
			numPlayersString = null;
			dealerHand = null;
			playerHands = null; 
			byteString = null;
			nibbleString = null;
		}
	}


	//////////////////////////////////////////////////////////////////
	// Function:  playGame						//
	// 								//
	// Synopsis:  private void playGame(void)			//
	//								//
	// Desciption: Plays the card game, printing out the outcome.	//
	//								//
	// Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	private void playGame()
	{
		String gameResult = null;	// String to hold the final print message.

		// Initialize the tokenizers for the byte and nibble decks.
		byteDeck = new StringTokenizer(byteString);
		nibbleDeck = new StringTokenizer(nibbleString);

		// IF the byte deck doesn't have 4 cards, throw an error.
		if(byteDeck.countTokens() != 4) {
			System.out.println("Error: There are not 4 byte cards in the deck.");
			System.exit(1);
		}

		// IF the nibble deck doesn't have 4 cards, throw an error.
		if(nibbleDeck.countTokens() != 4) {
			System.out.println("Error: There are not 4 nibble cards in the deck.");
			System.exit(1);
		}

		// Print out the game label.
		System.out.println("HAND " + numPlayers);

		// WHILE the dealer has not won, the dealer is not out of hits,
		// the dealer's score is less than the best player's score, the
		// dealer's score is less than or equal to the nibble-hit limit, 
		// and none of the other players have won...
		while((cardDealer.getScore() < WIN_SCORE) 
			&& (cardDealer.getRemainingHits() > 0)
			&& (cardDealer.getScore() < cardPlayers.getBestScore())
			&& (cardDealer.getScore() <= NIBBLE_LIMIT)
			&& (cardPlayers.getBestScore() <= WIN_SCORE)) {
			// IF the dealer's score is less than the byte-hit limit, take a 
			// byte hit!
			if(cardDealer.getScore() < BYTE_LIMIT) {
				// Grab the next byte card, and get its size in bits.
				String	byteCard = new String(byteDeck.nextToken());
				int 	numBits = byteCard.length();

				// Insure that the card is a byte.
				if(numBits != 8) {
					System.out.println(
			   "Error: Found a byte card that consists of " + numBits + " bits.");
					System.exit(1);
				}

				// Add the byte card to the dealer's hand.
				cardDealer.addCardToHand(byteCard.toCharArray(), BYTE);
				
				// Print a message saying that we are taking a byte hit.
				System.out.println("Byte me!");
			}				

			// ELSE take a hit from the nibble deck!
			else {
				// Grab the next nibble card, and get its size in bits.
				String	nibbleCard = new String(nibbleDeck.nextToken());
				int 	numBits = nibbleCard.length();

				// Insure that the card is a nibble
				if(numBits != 4) {
					System.out.println(
			 "Error: Found a nibble card that consists of " + numBits + " bits.");
					System.exit(1);
				}
				
				// Add the nibble card to the dealer's hand.
				cardDealer.addCardToHand(nibbleCard.toCharArray(), NIBBLE);

				// Print a message saying that we are taking a nibble hit.
				System.out.println("Nibble me!");
			}
		}

		// IF the dealer's score is greater than the win score, set the result 
		// to 'Bust'.
		if(cardDealer.getScore() > WIN_SCORE) 
			gameResult = "Bust!";

		// IF the dealer's score is less than the best player's score, set the result
		// to 'Lose'.
		else if(cardDealer.getScore() < cardPlayers.getBestScore())
			gameResult = "Lose!";

		// ELSE the dealer has won the game, so set the result to 'Win'.
		else  gameResult = "Win!";

		// Print the final result.
		System.out.println(gameResult);
	}


	//////////////////////////////////////////////////////////////////
	//  Function:  main						//
	//								//
	//  Synopsis:  public static void main(String args[])		//
	//	       args  [IN]  Not used.				//
	//								//
	//  Description: The main driver of the Binary Black Jack Sim.	//
	//	         Creates an instance of the simulator, and then //
	//		 starts it up.					//
	//								//
	//  Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	public static void main(String args[])
	{
		// Create an instance of the Binary Black Jack Simulator.
		ByteMe binaryBlackJack = new ByteMe();	

		// Start the Binary Black Jack Simulator.
		binaryBlackJack.start();

		// We are done, so exit without errors.
		System.exit(0);
	}
}



//////////////////////////////////////////////////////////////////////////
//  Class Name:  Dealer                  				//
//									//
//  Description: Object to represent the state of the dealer.		//
//////////////////////////////////////////////////////////////////////////
class Dealer {

	////////////////////
	//  Data Members  //
	////////////////////
	private BinaryConverter	cardConverter; // Converts the each of the dealer's cards.
	private int[][] 	dealerHand;    // The value of each of the dealer's cards.
	private int 		numCards;      // The number of cards the dealer has.
	private int 		remainingHits; // The number of times the dealer can hit.
	private int 		totalPoints;   // The total integer value of the dealer's hand.

	private final int 	BYTE = 1;      // Flag for when a byte hit is being performed.
	private final int 	NIBBLE = 0;    // Flag for when a nibble hit is being performed.

	////////////////////////
	//  Member Functions  //
	////////////////////////
	//////////////////////////////////////////////////////////////////
	// Function:  Constructor					//
	//								//
	// Synopsis:  public Dealer(String cardList)			//
	//	      cardList  [IN]  List of 2 cards drawn.		//
	//								//
	// Description: Create an instance of the Dealer object by      //
	//              adding cards to the dealer's hand, determine	//
	//		the value of his hand, and initialize the number//
	//              of remaining hits.				//
	//								//
	// Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	public Dealer(String cardList)
	{
		// Initialize all data members.
		dealerHand = new int[6][8];  	       // Dealer can have up to 6 byte cards.
		cardConverter = new BinaryConverter(); // The converter used convert each card.
		totalPoints = 0;		       // The dealer's score starts at 0.
		remainingHits = 4;		       // The dealer is allowed 4 hits.
		numCards = 0;			       // The dealer starts out with 0 cards.

		// Tokenize the list of dealer cards.
		StringTokenizer dealerCards = new StringTokenizer(cardList);

		// IF the dealer wasn't dealt 2 cards, throw an error.
		if(dealerCards.countTokens() != 2) {
			System.out.println("Error: The dealer was not dealt 2 cards.");
			System.exit(1);
		}

		// Get the cards to be dealt to the dealer.
                String cardA = new String(dealerCards.nextToken());
		String cardB = new String(dealerCards.nextToken());

		// Ensure that the cards are bytes.
		if(cardA.length() != 8 || cardB.length() != 8) {
			System.out.println(
				"Error: The cards dealt to the dealer are the wrong size.");
			System.exit(1);
		}
		
		// Add two cards the has dealer drawn to his hand and update the point total.
		// Note: BYTE is passed as the hit type, since all cards will be bytes.
		addCardToHand(cardA.toCharArray(), BYTE);
		addCardToHand(cardB.toCharArray(), BYTE);
	}


	//////////////////////////////////////////////////////////////////
	// Function:  addCardToHand					//
	//								//
	// Synopsis:  public void addCardToHand(			//
	//	      	char[] 	cardDrawn				//
	//	       ,int 	hitType)				//
	//								//
	//	      cardDrawn  [IN]  Binary value of the card to add	//
	//	                       to the dealer's hand.		//
	//	      hitType	 [IN]  1 if doing a byte hit and 0 if   //
	//			       doing a nibble hit.		//
	//								//
	// Description: Add the card to the dealer's hand, and update   //
	//              the dealer's point total.			//
	//								//
	// Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	public void addCardToHand(char[] cardDrawn, int hitType)
	{	
		int bitCount;			// Number of bits in the card.

		// IF the dealer has no more hits remaining, throw an error.
		if(remainingHits == 0 || numCards >= 6) {
			System.out.println("Error: Cannot hit, no remaining hits.");
			System.exit(1);
		}

		// Initalize the card bit count depending on the type of hit (byte or nibble).
		if(hitType == BYTE)
			bitCount = 8;
		else  	bitCount = 4;

		// Add the card drawn to the dealer's hand, converting the value to an integer.
		for(int cardIndex = 0; cardIndex < bitCount; cardIndex++) {
			dealerHand[numCards][cardIndex] = (cardDrawn[cardIndex] - 48);
		}

		// Increment the number of cards the dealer has in his hand.
		numCards++;

		// IF we are hitting, then decrement the number of hits remaining.
		if(numCards > 2) 
			remainingHits--;

		// Use the card converter to update the dealer's point total.
		totalPoints += cardConverter.getCardValue(dealerHand[numCards-1], bitCount);
	}


	//////////////////////////////////////////////////////////////////
	// Function:  getRemainingHits					//
	//								//
	// Synopsis:  public int getRemainingHits(void)			//
	//	      							//
	// Description: Get the number of hits the dealer has left.     //
	//								//
	// Return Value: Returns the number of hits the dealer has left.//
	//////////////////////////////////////////////////////////////////
	public int getRemainingHits()
	{	
		return remainingHits;
	}


	//////////////////////////////////////////////////////////////////
	// Function:  getScore						//
	//								//
	// Synopsis:  public int getScore(void)				//
	//	      							//
	// Description: Get the dealer's total score.			//
	//								//
	// Return Value: Return the dealer's total score.		//
	//////////////////////////////////////////////////////////////////
	public int getScore()
	{	
		return totalPoints;
	}
}



//////////////////////////////////////////////////////////////////////////
//  Class Name:  Players                  				//
//									//
//  Description: Object to represent the state of all players.		//
//////////////////////////////////////////////////////////////////////////
class Players {

	////////////////////
	//  Data Members  //
	////////////////////
	private BinaryConverter	cardConverter;	 // Converts of each of the player's cards.
	private int[][] 	playerHands;	 // List of cards each player has showing.
	private int[] 		hiddenCard;	 // Value of each player's hidden hand.
	private int 		playerCount;	 // Number of players in the game.
	private int 		bestPlayerScore; // The total value of the best player's hand.

	////////////////////////
	//  Member Functions  //
	////////////////////////
	//////////////////////////////////////////////////////////////////
	// Function:  Constructor					//
	//								//
	// Synopsis:  public Players(String cardList, int numPlayers)   //
	//	      cardList   [IN]  List of all cards dealt to all 	//
	//                             players.				//
	//	      numPlayers [IN]  The number of players playing in //
	//                             the game.			//
	//	      							//
	// Description: Create an instance of the Players object by     //
	//		setting the number of players in the game,	//
	//              adding all dealt cards to the list of player    //
	//		cards, determine the best score out of all of   //
	//              the players, and set the value of the hidden    //
	//              card (same for all players).  			//
	//								//
	// Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	public Players(String cardList, int numPlayers)
	{	
		// Initialize all member variables.
		playerHands = new int[10][8];	       // Can have up to 10 players.
		hiddenCard = new int[8];	       // The hidden card is a byte card.
		cardConverter = new BinaryConverter(); // The converter used to convert cards.
		bestPlayerScore = 0;		       // The best starting score.
		playerCount = numPlayers;	       // Number of players in this game.

		// Tokenize the list of player cards.
		StringTokenizer playerCards = new StringTokenizer(cardList);

		// IF there isn't a card for every player, throw an error.
		if(playerCards.countTokens() != numPlayers) {
			System.out.println(
				"Error: There must be one card dealt to every player.");
			System.exit(1);
		}
		
		// Deal the cards out to all players, determining the best score along the way.
		for(int player = 0; player < playerCount; player++) {
			// Grad the card to be dealt to the player.
			String card = new String(playerCards.nextToken());

			// Ensure that the card is a byte.
			if(card.length() != 8) {
				System.out.println(
				 "Error: The cards dealt to the players are the wrong size.");
				System.exit(1);
			}

			// Add the card to the player's hand.
			addCardToHand(card.toCharArray(), player);
		}

		// Set the value of the hidden card (a byte consisting of all 1's).
		for(int cardIndex = 0; cardIndex < 8; cardIndex++)
			hiddenCard[cardIndex] = 1;
	}


	//////////////////////////////////////////////////////////////////
	// Function:  addCardToHand					//
	//								//
	// Synopsis:  private void addCardToHand(			//
	//	      	 char[] cardDealt				//
	//	       	,int	playerId)				//
	//	      cardDealt  [IN]  Binary value of the card to add	//
	//	                       to the player's hand.		//
	//	      playerId   [IN]  ID of the player we are giving a //
	//			       card to.				//
	//								//
	// Description: Add the card to the player's hand, update that	//
	//              player's score, and determine whether that score//
	//		is the best seen so far, and update the best    //
	//		score total accordingly.			//
	//								//
	// Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	private void addCardToHand(char[] cardDealt, int playerId)
	{
		// IF there are more players than expected, throw an exception.
		if((playerId+1) > playerCount) {
			System.out.println("Error, this is an invalid player ID.");
			System.exit(1);
		}

		// Add the card to the player's hand, converting it to an integer.
		for(int cardIndex = 0; cardIndex < 8; cardIndex++) {
			playerHands[playerId][cardIndex] = (cardDealt[cardIndex] - 48);
			
		// Get that player's score, passing 8 since this is a byte card.
		int playerScore = cardConverter.getCardValue(playerHands[playerId], 8);
				
		// IF this is the best score so far, update the best score total.
		if(playerScore > bestPlayerScore)
			bestPlayerScore = playerScore;
		}
	}


	//////////////////////////////////////////////////////////////////
	// Function:  getBestScore					//
	//								//
	// Synopsis:  public int getBestScore(void)			//
	//	      							//
	// Description: Get the highest score amoung all of the players,//
	//              which is the score the dealer has to beat.	//
	//								//
	// Return Value: Returns the value of the showing card of the   //
	//  		 best player + the value of the hidden card.	//
	//////////////////////////////////////////////////////////////////
	public int getBestScore()
	{	
		return bestPlayerScore + cardConverter.getCardValue(hiddenCard, 8);
	}
}



//////////////////////////////////////////////////////////////////////////
//  Class Name:  BinaryConverter                			//
//									//
//  Description: Object to perform binary to integer conversions on     //
//		 integer strings which represent binary numbers.  This	//
//	         is not really an object in the sense that it doesn't   //
//		 have any state variables, but was created as a seperate//
//		 class to avoid having to define binary to integer      //
//		 conversion functions in both the Dealer and Players    //
//		 classes.						//
//////////////////////////////////////////////////////////////////////////
class BinaryConverter {
	
	////////////////////////
	//  Member Functions  //
	////////////////////////
	//////////////////////////////////////////////////////////////////
	// Function:  Constructor					//
	//								//
	// Synopsis:  public BinaryConverter(void)			//
	//	      							//
	// Description: Creates an instance of the BinaryConverter 	//
	//		object.  Nothing to do here since there are no  //
	//              member variables to initialize.  		//
	//								//
	// Return Value: None.						//
	//////////////////////////////////////////////////////////////////
	public BinaryConverter()
	{ }


	//////////////////////////////////////////////////////////////////
	// Function:  binaryToInt					//
	//								//
	// Synopsis:  private int binaryToInt(				//
	//		int[] 	binaryNum				//
	//             ,int	numBits)				//
	//								//
	//	      binaryNum  [IN]  This is the integer string that  //
	//	      		       represents the binary number to  //
	//			       be converted.			//
	//	      numBits	 [IN]  The number of bits in the binary //
	//			       string.				//
	//	      							//
	// Description: Convert the binary number to its integer equiv. //
	// 		by stepping through the integer array and using //
	//		the position of each integer element in the 	//
	//		array to calculate the integer equivalent.  The //
	//		following formula is applied to each element in //
	//		the integer array:				//
	//			1)  IF the integer is a 0, do nothing.	//
	//			2)  IF the integer is 1, use:		//
	//			    value += (2^((numBits-1)-position))	//
	//								//
	// Return Value: Return the integer equivalent of the binary 	//
	//		 number.					//
	//////////////////////////////////////////////////////////////////
	private int binaryToInt(int[] binaryNum, int numBits)
	{	
		int intValue = 0;	// Integer equivalent of the binary number.

		// Cumulatively calculate the integer value using each element in the array.
		for(int index = 0; index < numBits; index++) {
			// While we're here, let's check the integrity of the data...
			if((binaryNum[index] != 0) && (binaryNum[index] != 1)) {
				System.out.println(
				    "Error: The card data must only consist of 0's and 1's.");
				System.exit(1);
			}

			// If we've found a 1, accumulate the total for that position.
			if(binaryNum[index] == 1)
				intValue += (int)Math.pow(2.0, (double)((numBits-1)-index));
		}

		// Return the integer equivalent of the binary number.
		return intValue;
	}


	//////////////////////////////////////////////////////////////////
	// Function:  getCardValue					//
	//								//
	// Synopsis:  public int getCardValue(int[] card, int numBits)	//
	//								//
	//	      card    [IN]  This is the integer string that 	//
	//	      		    represents the binary value of the  //
	//			    card to be converted.		//
	//	      numBits [IN]  The number of bits in the binary    //
	//			    to be converted.			//
	//	      							//
	// Description: Call binaryToInt() to get the integer value of  //
	//		the binary representation of the card and return//
	//		that value to the caller.			//
	//								//
	// Return Value: Return the integer equivalent of the binary 	//
	//		 representation of the card.			//
	//////////////////////////////////////////////////////////////////
	public int getCardValue(int[] card, int numBits)
	{	
		return binaryToInt(card, numBits);
	}
}