Implement play() and scoreHand()

This commit is contained in:
Dave Burke
2022-02-09 21:19:04 -06:00
parent 0760f22494
commit 2b2f9327f7
4 changed files with 253 additions and 26 deletions

View File

@@ -1,9 +1,6 @@
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.UncheckedIOException;
import java.io.Writer; import java.io.Writer;
import java.util.Collections; import java.util.Collections;

View File

@@ -2,8 +2,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.io.Reader;
import java.io.Writer;
public class Game { public class Game {
@@ -74,8 +72,10 @@ public class Game {
printInitialDeal(players, dealer); printInitialDeal(players, dealer);
// TODO if dealer has an ACE, prompt "ANY INSURANCE" and deal with insurance
for(Player player : players){ for(Player player : players){
play(player, deck); play(player);
} }
// only play the dealer if at least one player has not busted or gotten a natural blackjack (21 in the first two cards) // only play the dealer if at least one player has not busted or gotten a natural blackjack (21 in the first two cards)
@@ -122,23 +122,72 @@ public class Game {
* @param player * @param player
* @param deck * @param deck
*/ */
private void play(Player player, Deck deck) { protected void play(Player player) {
// TODO implement play(player, deck) String action = userIo.prompt("PLAYER " + player.getPlayerNumber() + " ");
// If the player hits, deal another card. If the player stays, return. If the player busts, return. while(true){
// delegate to evaluateHand(hand) to determine whether the player busted. if(action.equalsIgnoreCase("H")){ // HIT
// Use promptBoolean and promptInt as examples to start with for prompting actions Card c = deck.deal();
// initially prompt with "PLAYER [x] ?" where x is the player number and accept H, S, D, or / player.dealCard(c);
// after hitting, prompt "RECEIVED A [c] HIT? " where c is the card received and only accept H or S if(scoreHand(player.getHand()) > 21){
// handle splitting and doubling down, or feel free to skip implementing userIo.println("...BUSTED");
// split/double down for now, but leave a todo if that is unfinished return;
// after the first pass. }
action = userIo.prompt("RECEIVED A " + c.toString() + " HIT");
} else if(action.equalsIgnoreCase("S")){ // STAY
return;
} else if(player.getHand().size() == 2 && action.equalsIgnoreCase("D")) { // DOUBLE DOWN
player.setCurrentBet(player.getCurrentBet() * 2);
player.dealCard(deck.deal());
return;
} else if(player.getHand().size() == 2 && action.equalsIgnoreCase("/")) { // SPLIT
if(player.getHand().get(0).equals(player.getHand().get(1))){
// TODO split = split into two hands that play separately. only allowed for pairs
// TODO implement player.split that takes one card from 'hand' and adds it to a new 'splitHand' field.
// TODO determine if the original code allowed re-splitting, splitting on aces, or doubling down on a split and if it requires cards
} else {
userIo.println("SPLITTING NOT ALLOWED");
action = userIo.prompt("PLAYER " + player.getPlayerNumber() + " ");
}
} else {
if(player.getHand().size() > 2) {
action = userIo.prompt("TYPE H, OR S, PLEASE");
} else {
action = userIo.prompt("TYPE H,S,D, OR /, PLEASE");
}
}
}
} }
private int evaluateHand(LinkedList<Card> hand){ /**
// TODO implement evaluateHand * Calculates the value of a hand.
// 'int' is maybe the wrong return type. We need to indicate a bust and somehow communicate the ambiguity of aces. *
// OR maybe we stick with 'int' and use -1 for a bust and otherwise determine the value of aces that gives the highest non-bust score. * @param hand the hand to evaluate
// but note that we also need a distinction between a natural Blackjack (21 in only 2 cards) and a 21 with more than 2 cards (the natural blackjack wins) * @return The numeric value of a hand.
*/
protected int scoreHand(LinkedList<Card> hand){
int nAces = (int) hand.stream().filter(c -> c.getValue() == 1).count();
int value = hand.stream()
.mapToInt(Card::getValue)
.filter(v -> v != 1) // start without aces
.map(v -> v > 10 ? 10 : v) // all face cards are worth 10. The 'expr ? a : b' syntax is called the 'ternary operator'
.sum();
value += nAces; // start by treating all aces as 1
if(nAces > 0 && value <= 11) {
value += 10; // We can use one of the aces to an 11
// You can never use more than one ace as 11, since that would be 22 and a bust.
}
return value;
}
/**
* Compares two hands accounting for natural blackjacks
*
* @param handA hand to compare
* @param handB other hand to compare
* @return a negative integer, zero, or a positive integer as handA is less than, equal to, or greater than handB.
*/
private int compareHands(LinkedList<Card> handA, LinkedList<Card> handB) {
return 0; return 0;
} }

View File

@@ -58,6 +58,11 @@ public class UserIo {
} }
} }
public String prompt(String prompt) {
print(prompt + "? ");
return readLine();
}
/** /**
* Prompts the user for a "Yes" or "No" answer. * Prompts the user for a "Yes" or "No" answer.
* @param prompt The prompt to display to the user on STDOUT. * @param prompt The prompt to display to the user on STDOUT.

View File

@@ -1,15 +1,16 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.EOFException; import java.io.EOFException;
import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.LinkedList;
public class GameTest { public class GameTest {
@@ -17,14 +18,32 @@ public class GameTest {
private StringWriter out; private StringWriter out;
private Game game; private Game game;
private void givenInput(String input) { private void givenStubGame() {
Reader in = new StringReader("\u2404"); // U+2404 is "End of Transmission" sent by CTRL+D (or CTRL+Z on Windows) in = new StringReader("");
StringWriter out = new StringWriter(); out = new StringWriter();
UserIo userIo = new UserIo(in, out); UserIo userIo = new UserIo(in, out);
Deck deck = new Deck((cards) -> cards); Deck deck = new Deck((cards) -> cards);
game = new Game(deck, userIo); game = new Game(deck, userIo);
} }
private void givenInput(String input) {
in = new StringReader(input);
out = new StringWriter();
UserIo userIo = new UserIo(in, out);
Deck deck = new Deck((cards) -> cards);
game = new Game(deck, userIo);
}
private void givenInput(String input, Card... customCards) {
in = new StringReader(input);
out = new StringWriter();
UserIo userIo = new UserIo(in, out);
LinkedList<Card> cardList = new LinkedList<>();
cardList.addAll(Arrays.asList(customCards));
Deck deck = new Deck((cards) -> cardList);
game = new Game(deck, userIo);
}
@Test @Test
public void shouldQuitOnCtrlD() { public void shouldQuitOnCtrlD() {
// Given // Given
@@ -37,4 +56,161 @@ public class GameTest {
assertTrue(e.getCause() instanceof EOFException); assertTrue(e.getCause() instanceof EOFException);
assertEquals("!END OF INPUT", e.getMessage()); assertEquals("!END OF INPUT", e.getMessage());
} }
@Test
@DisplayName("play() should end on STAY")
public void playEndOnStay(){
// Given
Player player = new Player(1);
player.dealCard(new Card(3, Card.Suit.CLUBS));
player.dealCard(new Card(2, Card.Suit.SPADES));
givenInput("S\n"); // "I also like to live dangerously."
// When
game.play(player);
// Then
assertEquals("PLAYER 1 ? ", out.toString());
}
@Test
@DisplayName("play() should allow HIT until BUST")
public void playHitUntilBust() {
// Given
Player player = new Player(1);
player.dealCard(new Card(10, Card.Suit.HEARTS));
player.dealCard(new Card(9, Card.Suit.SPADES));
givenInput("H\nH\nH\n",
new Card(1, Card.Suit.SPADES), // 20
new Card(1, Card.Suit.HEARTS), // 21
new Card(1, Card.Suit.CLUBS)); // 22 - D'oh!
// When
game.play(player);
// Then
assertTrue(out.toString().contains("BUSTED"));
}
@Test
@DisplayName("Should allow double down on initial turn")
public void playDoubleDown(){
// Given
Player player = new Player(1);
player.setCurrentBet(100);
player.dealCard(new Card(10, Card.Suit.HEARTS));
player.dealCard(new Card(4, Card.Suit.SPADES));
givenInput("D\n", new Card(7, Card.Suit.SPADES));
// When
game.play(player);
// Then
assertTrue(player.getCurrentBet() == 200);
assertTrue(player.getHand().size() == 3);
}
@Test
@DisplayName("Should NOT allow double down after initial deal")
public void playDoubleDownLate(){
// Given
Player player = new Player(1);
player.setCurrentBet(100);
player.dealCard(new Card(10, Card.Suit.HEARTS));
player.dealCard(new Card(2, Card.Suit.SPADES));
givenInput("H\nD\nS\n", new Card(7, Card.Suit.SPADES));
// When
game.play(player);
// Then
assertTrue(out.toString().contains("TYPE H, OR S, PLEASE"));
}
@Test
@DisplayName("scoreHand should sum non-ace values normally")
public void scoreHandNormally() {
// Given
givenStubGame();
LinkedList<Card> hand = new LinkedList<>();
hand.add(new Card(4, Card.Suit.SPADES));
hand.add(new Card(6, Card.Suit.SPADES));
hand.add(new Card(10, Card.Suit.SPADES));
// When
int result = game.scoreHand(hand);
// Then
assertEquals(20, result);
}
@Test
@DisplayName("scoreHand should treat face cards as 10")
public void scoreHandFaceCards() {
// Given
givenStubGame();
LinkedList<Card> hand = new LinkedList<>();
hand.add(new Card(11, Card.Suit.SPADES));
hand.add(new Card(12, Card.Suit.SPADES));
hand.add(new Card(13, Card.Suit.SPADES));
// When
int result = game.scoreHand(hand);
// Then
assertEquals(30, result);
}
@Test
@DisplayName("scoreHand should score aces as 11 when possible")
public void scoreHandSoftAce() {
// Given
givenStubGame();
LinkedList<Card> hand = new LinkedList<>();
hand.add(new Card(10, Card.Suit.SPADES));
hand.add(new Card(1, Card.Suit.SPADES));
// When
int result = game.scoreHand(hand);
// Then
assertEquals(21, result);
}
@Test
@DisplayName("scoreHand should score aces as 1 when using 11 would bust")
public void scoreHandHardAce() {
// Given
givenStubGame();
LinkedList<Card> hand = new LinkedList<>();
hand.add(new Card(10, Card.Suit.SPADES));
hand.add(new Card(9, Card.Suit.SPADES));
hand.add(new Card(1, Card.Suit.SPADES));
// When
int result = game.scoreHand(hand);
// Then
assertEquals(20, result);
}
@Test
@DisplayName("scoreHand should score 3 aces as 13")
public void scoreHandMultipleAces() {
// Given
givenStubGame();
LinkedList<Card> hand = new LinkedList<>();
hand.add(new Card(1, Card.Suit.SPADES));
hand.add(new Card(1, Card.Suit.CLUBS));
hand.add(new Card(1, Card.Suit.HEARTS));
// When
int result = game.scoreHand(hand);
// Then
assertEquals(13, result);
}
} }