This page is part of the Blackjack Lab.
BlackjackUI handles interaction with the user. The game mechanics are handled by Blackjack. See Object oriented UI.
Start files:
Solutions are at the bottom of this page.
Testing BlackjackUI
Incremental testing
The only public methods in BlackjackUI are playHand and playHandsUntilQuit. Write a test class with a main method. Make an object of type BlackjackUI. Run playHand. As you complete the private methods, add appropriate calls to playHand.
This process is necessary because most of the methods can’t be tested in isolation. For example, playPlayersHand can only be tested after a valid bet has been placed and the cards dealt.
Using the real Shoe or TestShoe is up to you. If you use the TestShoe, you must specify the cards just like the other tests. The real Shoe will deal random cards.
See the Hints section below for incremental tests of getValidBet and playPlayersHand.
Manually testing the finished class
BlackjackUIManualTester provides code to manually test the completed BlackjacUI class (excluding the playHandsUntilQuit method) with various scenarios.
The existing test code requires manually entering input and manually checking the results. Comments in the test code indicate how each hand is supposed to be played and the expected result. See BlackjackUIManualTester expected interaction.
As with the other tests, the code depends on TestShoe to use predetermined cards.
Automated testing of the finished class
I am aware of an issue with the BlackjackUIAutomatedTester on Windows. I’m working to understand the issue and to develop a more robust test.
BlackjackUIAutomatedTester redirects the standard input (System.in) and the standard output (System.out) to allow predetermined input and check against expected output.
BlackjackUIAutomatedTester tests the same hands as BlackjackUIManualTester; however, it does so one at a time.
Passing the automated test requires precision. The output must exactly match the expected output, including spacing and line breaks. The input must be accepted exactly as specified, including line breaks.
The code depends on TestShoe.
Note: The expected output is currently hard coded for US dollars.
Provided code
Constant and instance variables
The constant STARTING_MONEY is self explanatory. It is used when constructing a Backjack object.
The Blackjack class is responsible for the game mechanics. BlackjackUI stores a reference to a Blackjack object.
BlackjackUI stores a reference to a Scanner to accept input from the user.
The NumberFormat object is used to produce formatted output of currency. When run on a computer configured to use US dollars, the format includes a dollar sign ($) followed by the amount rounded to 2 decimal places.
stringToNumber method
stringToNumber accepts input as a String and attempts to convert it to a double. If the conversion succeeds, the method returns the double value. If the conversion fails, the method returns -1.
This method allows easy handling of non-numeric invalid input.
Code to be written
BlackjackUI constructor
bj and fromKeyboard should be initialized normally.
NumberFormat objects are not constructed using the new keyword. The static method getCurrencyInstance is used to obtain a NumberFormat object. See the NumberFormat Java API. Also see Floating point roundoff error.
getValidBet method
getValidBet accepts and returns a valid bet. It does not attempt to actually place the bet. See Input validation as String.
playPlayersHand method
The player can hit (take another card) until the Blackjack class says they can’t or they choose to stand.
The dealer’s first card and the player’s entire hand should be displayed before each prompt to hit. If the player cannot hit initially (ex: the dealer has Blackjack) nothing is displayed.
displayResult method
displayResult displays the player’s and dealer’s complete hands and the game result ("push", "blackjack", "win", or "loss").
playHand method
playHand runs a combination of Blackjack and BlackjackUI methods to conduct the game.
playHandsUntilQuit method
playHandsUntilQuit plays at least 1 hand. After each hand, the user is asked if they want to play again. If the user wants to play again, the process repeats.
Hints
Incremental testing
Create a main method in a different class. Make an object of type BlackjackUI. Run playHand.
public static void main(String[] args)
{
BlackjackUI bjui = new BlackjackUI();
bjui.playHand();
}
As mentioned above, pay attention whether Blackjack is using Shoe or TestShoe. Using TestShoe requires specifying the cards. Using Shoe will result in random cards.
Testing getValidBet
To test getValidBet, temporarily implement playHand as:
public void playHand()
{
System.out.println("Bet: " + getValidBet());
}
Invalid bets should be rejected with an error message and a new bet should be requested. This should happen as many times as necessary. A valid bet should be accepted and the amount printed.
Example interaction:
Bet: $-5
Invalid bet
Bet: $hamster
Invalid bet
Bet: $10
Bet: 10.0
Testing playPlayersHand
To test playPlayersHand, temporarily implement playHand as:
public void playHand()
{
bj.placeBetAndDealCards(getValidBet());
playPlayersHand();
System.out.println(bj.getPlayersHand());
}
Run the test until you are able to hit at least once.
Note that if the player and/or the dealer have Blackjack, this test will not work well. Simply run it again.
Example interaction:
Bet: $10
Dealer: 4D
Player: 4S 7H (11)
Hit? (y/n): y
Dealer: 4D
Player: 4S 7H 8C (19)
Hit? (y/n): n
4S 7H 8C (19)
playPlayersHand method
Depending on the player’s and dealer’s hands, the player may be able to hit 0, 1, or more times. This method requires a loop that runs if it is possible for the player to hit.
Each time the loop runs, the player is asked if they want to hit. If the player wants to hit, the Blackjack method hit is run.
There are 2 conditions that prevent the player from being able to hit:
- The
BlackjackmethodcanHitreturnsfalse. This can happen before the first hit or after any hit. - The player chooses to stand. This can only happen after the player is asked to hit.
The determination of whether the player can potentially hit must be made before the first run of the loop and after each run.
playHand method
A Blackjack hand is played as follows:
- A valid bet is placed and the initial hands are dealt.
- The player plays their hand.
- The dealer plays their hand.
- The result is displayed.
- Bets are resolved adn the game is reset.
- The player’s money is displayed.
This requires calling a combination of methods from Blackjack and BlackjackUI. playPlayersHand is a BlackjackUI method because it requires interaction with the user. playDealersHand is a Blackjack method because it does not require user interaction.
playHand should not call playHandsUntilQuit.
playHandsUntilQuit method
playHandsUntilQuit is responsible for allowing the user to play again. This is easily accomplished with a simple while loop based on the player’s response to "Play again (y/n): ".
playHandsUntilQuit calls playHand once for each hand the player wants to play. It is reasonable to assume that the player wants to play at least 1 hand (and does not need to be asked before the first hand if they want to play).
Solution code
The tested requires that Blackjack use TestShoe.
BlackjackUI constructor
public BlackjackUI()
{
bj = new Blackjack(STARTING_MONEY);
fromKeyboard = new Scanner(System.in);
nf = NumberFormat.getCurrencyInstance();
}
getValidBet method
private double getValidBet()
{
System.out.println();
System.out.print("Bet: $");
String input = fromKeyboard.nextLine();
double bet = stringToNumber(input);
while(bet <= 0)
{
System.out.println("Invalid bet");
System.out.print("Bet: $");
input = fromKeyboard.nextLine();
bet = stringToNumber(input);
}
return bet;
}
playPlayersHand method
private void playPlayersHand()
{
boolean canHitAgain = bj.canHit();
while(canHitAgain)
{
System.out.println();
System.out.println("Dealer: " + bj.getDealersHand().getCards().get(0));
System.out.println("Player: " + bj.getPlayersHand());
System.out.print("Hit? (y/n): ");
boolean hit = fromKeyboard.nextLine().toLowerCase().startsWith("y");
if(hit)
bj.hit();
canHitAgain = bj.canHit() && hit;
}
}
displayResult method
private void displayResult()
{
System.out.println();
System.out.println("Dealer: " + bj.getDealersHand());
System.out.println("Player: " + bj.getPlayersHand());
System.out.print("Result: ");
int result = bj.getResult();
if(result == Blackjack.PUSH_RESULT)
System.out.println("push");
else if(result == Blackjack.WIN_BJ_RESULT)
System.out.println("blackjack");
else if(result == Blackjack.WIN_RESULT)
System.out.println("win");
else
System.out.println("loss");
}
playHand method
public void playHand()
{
bj.placeBetAndDealCards(getValidBet());
playPlayersHand();
bj.playDealersHand();
displayResult();
bj.resolveBetsAndReset();
System.out.println("\nMoney: " + nf.format(bj.getPlayersMoney()));
}
playHandsUntilQuit method
public void playHandsUntilQuit()
{
System.out.println("Money: " + nf.format(bj.getPlayersMoney()));
boolean playAgain = true;
while(playAgain)
{
playHand();
System.out.print("Play again (y/n): ");
playAgain = fromKeyboard.nextLine().toLowerCase().startsWith("y");
}
}