diff --git a/10_Blackjack/java/src/Game.java b/10_Blackjack/java/src/Game.java index 7a14e2c2..009b0335 100644 --- a/10_Blackjack/java/src/Game.java +++ b/10_Blackjack/java/src/Game.java @@ -94,12 +94,14 @@ public class Game { play(player); } - // TODO only play the dealer if at least one player has not busted or gotten a natural blackjack (21 in the first two cards) - // otherwise, just print the dealer's concealed card - dealerHand = playDealer(dealerHand, deck); + if(shouldPlayDealer(players)){ + playDealer(dealer); + } else { + userIo.println("DEALER HAD " + dealer.getHand().get(1).toProseString() + " CONCEALED."); + } } - evaluateRound(players, dealer);//TODO: User dealerHand once playHeader implemented + evaluateRound(players, dealer); } } @@ -165,7 +167,7 @@ public class Game { private void play(Player player, int handNumber) { String action; if(player.isSplit()){ - action = userIo.prompt("HAND #" + handNumber); + action = userIo.prompt("HAND " + handNumber); } else { action = userIo.prompt("PLAYER " + player.getPlayerNumber() + " "); } @@ -200,11 +202,13 @@ public class Game { Card card = deck.deal(); player.dealCard(card, 1); userIo.println("FIRST HAND RECEIVES " + card.toProseString()); - play(player, 1); card = deck.deal(); player.dealCard(card, 2); - userIo.println("SECOND HAND RECEIVES " + card.toProseString()); - play(player, 2); + userIo.println("SECOND HAND RECEIVES " + card.toProseString()); + if(player.getHand().get(0).getValue() > 1){ //Can't play after splitting aces + play(player, 1); + play(player, 2); + } return; // Don't fall out of the while loop and print another total } else { userIo.println("SPLITTING NOT ALLOWED"); @@ -218,9 +222,38 @@ public class Game { } } } - userIo.println("TOTAL IS " + ScoringUtils.scoreHand(player.getHand(handNumber))); + int total = ScoringUtils.scoreHand(player.getHand(handNumber)); + if(total == 21) { + userIo.println("BLACKJACK"); + } else { + userIo.println("TOTAL IS " + total); + } } + /** + * Check the Dealer's hand should be played out. If every player has either busted or won with natural Blackjack, + * the Dealer doesn't need to play. + * + * @param players + * @return boolean whether the dealer should play + */ + + protected boolean shouldPlayDealer(List players){ + for(Player player : players){ + int score = ScoringUtils.scoreHand(player.getHand()); + if(score < 21 || (score == 21 && player.getHand().size() > 2)){ + return true; + } + if(player.isSplit()){ + int splitScore = ScoringUtils.scoreHand(player.getHand(2)); + if(splitScore < 21 || (splitScore == 21 && player.getHand(2).size() > 2)){ + return true; + } + } + } + return false; + } + /** * Play the dealer's hand. The dealer draws until they have >=17 or busts. Prints each draw as in the following example: * @@ -232,9 +265,25 @@ public class Game { * @param dealerHand * @return */ - private LinkedList playDealer(LinkedList dealerHand, Deck deck) { - // TODO implement playDealer - return null; + protected void playDealer(Player dealer) { + int score = ScoringUtils.scoreHand(dealer.getHand()); + userIo.println("DEALER HAS " + dealer.getHand().get(1).toProseString() + " CONCEALED FOR A TOTAL OF " + score); + + if(score < 17){ + userIo.print("DRAWS "); + } + while(score < 17) { + Card dealtCard = deck.deal(); + dealer.dealCard(dealtCard); + score = ScoringUtils.scoreHand(dealer.getHand()); + userIo.print(dealtCard.toString() + " "); + } + + if(score > 21) { + userIo.println("...BUSTED\n"); + } else { + userIo.println("---TOTAL IS " + score + "\n"); + } } /** @@ -280,18 +329,18 @@ public class Game { userIo.print("PLAYER " + player.getPlayerNumber()); if(totalBet < 0) { - userIo.print(" LOSES "); + userIo.print(" LOSES " + String.format("%6s", formatter.format(Math.abs(totalBet)))); } else if(totalBet > 0) { - userIo.print(" WINS "); + userIo.print(" WINS " + String.format("%6s", formatter.format(totalBet))); } else { - userIo.print(" PUSHES"); + userIo.print(" PUSHES "); } player.recordRound(totalBet); dealer.recordRound(totalBet*-1); - userIo.println(String.format("%6s", formatter.format(Math.abs(totalBet))) + " TOTAL= " + formatter.format(player.getTotal())); + userIo.println(" TOTAL= " + formatter.format(player.getTotal())); player.resetHand(); } - userIo.println("DEALER'S TOTAL= " + formatter.format(dealer.getTotal())); + userIo.println("DEALER'S TOTAL= " + formatter.format(dealer.getTotal()) + "\n"); } /** diff --git a/10_Blackjack/java/src/ScoringUtils.java b/10_Blackjack/java/src/ScoringUtils.java index df149e27..b7401102 100644 --- a/10_Blackjack/java/src/ScoringUtils.java +++ b/10_Blackjack/java/src/ScoringUtils.java @@ -1,7 +1,7 @@ import java.util.List; public final class ScoringUtils { - + /** * Calculates the value of a hand. When the hand contains aces, it will * count one of them as 11 if that does not result in a bust. @@ -9,15 +9,16 @@ public final class ScoringUtils { * @param hand the hand to evaluate * @return The numeric value of a hand. A value over 21 indicates a bust. */ - public static final int scoreHand(List hand){ + public static final int scoreHand(List 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(); + .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) { + 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. } @@ -25,27 +26,35 @@ public final class ScoringUtils { } /** - * Compares two hands accounting for natural blackjacks using the + * Compares two hands accounting for natural blackjacks and busting using the * java.lang.Comparable convention of returning positive or negative integers * * @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. + * @return a negative integer, zero, or a positive integer as handA is less + * than, equal to, or greater than handB. */ public static final int compareHands(List handA, List handB) { int scoreA = scoreHand(handA); int scoreB = scoreHand(handB); - - if(scoreA == 21 && scoreB == 21){ - if(handA.size() == 2 && handB.size() != 2){ - return 1; //Hand A wins with a natural blackjack - } else if (handA.size() != 2 && handB.size() == 2) { - return -1; //Hand B wins with a natural blackjack + if (scoreA == 21 && scoreB == 21) { + if (handA.size() == 2 && handB.size() != 2) { + return 1; // Hand A wins with a natural blackjack + } else if (handA.size() != 2 && handB.size() == 2) { + return -1; // Hand B wins with a natural blackjack } else { - return 0; //Tie + return 0; // Tie + } + } else if (scoreA > 21 || scoreB > 21) { + if (scoreA > 21 && scoreB > 21) { + return 0; // Tie, both bust + } else if (scoreB > 21) { + return 1; // A wins, B busted + } else { + return -1; // B wins, A busted } } else { - return Integer.compare(scoreA, scoreB); + return Integer.compare(scoreA, scoreB); } } diff --git a/10_Blackjack/java/test/GameTest.java b/10_Blackjack/java/test/GameTest.java index 5eb3553c..ec56874e 100644 --- a/10_Blackjack/java/test/GameTest.java +++ b/10_Blackjack/java/test/GameTest.java @@ -370,8 +370,8 @@ public class GameTest { // Given Player player = new Player(1); player.setCurrentBet(100); - player.dealCard(new Card(1, Card.Suit.HEARTS)); - player.dealCard(new Card(1, Card.Suit.SPADES)); + player.dealCard(new Card(2, Card.Suit.HEARTS)); + player.dealCard(new Card(2, Card.Suit.SPADES)); playerSays("/"); playerGets(13, Card.Suit.CLUBS); // First hand @@ -394,8 +394,8 @@ public class GameTest { // Given Player player = new Player(1); player.setCurrentBet(100); - player.dealCard(new Card(1, Card.Suit.HEARTS)); - player.dealCard(new Card(1, Card.Suit.SPADES)); + player.dealCard(new Card(10, Card.Suit.HEARTS)); + player.dealCard(new Card(10, Card.Suit.SPADES)); playerSays("/"); playerGets(13, Card.Suit.CLUBS); // First hand @@ -425,7 +425,7 @@ public class GameTest { player.setCurrentBet(50); player.dealCard(new Card(1, Card.Suit.HEARTS)); player.dealCard(new Card(1, Card.Suit.SPADES)); - + playerSays("/"); playerGets(13, Card.Suit.CLUBS); // First hand playerSays("S"); @@ -439,7 +439,7 @@ public class GameTest { // Then assertAll( - () -> assertTrue(out.toString().contains("PLAYER 1 WINS 100 TOTAL= 300")), + () -> assertTrue(out.toString().contains("PLAYER 1 WINS 100 TOTAL= 300")), () -> assertTrue(out.toString().contains("DEALER'S TOTAL= -100")) ); } @@ -489,8 +489,134 @@ public class GameTest { // Then assertAll( - () -> assertTrue(out.toString().contains("PLAYER 1 PUSHES 0 TOTAL= 0")), + () -> assertTrue(out.toString().contains("PLAYER 1 PUSHES TOTAL= 0")), () -> assertTrue(out.toString().contains("DEALER'S TOTAL= 0")) ); } + + @Test + @DisplayName("shouldPlayDealer() return false when players bust") + public void shouldPlayDealerBust(){ + // Given + Player player = new Player(1); + player.dealCard(new Card(10, Card.Suit.SPADES)); + player.dealCard(new Card(10, Card.Suit.SPADES)); + player.split(); + player.dealCard(new Card(5, Card.Suit.SPADES)); + player.dealCard(new Card(8, Card.Suit.SPADES));//First hand Busted + + player.dealCard(new Card(5, Card.Suit.SPADES),2); + player.dealCard(new Card(8, Card.Suit.SPADES),2);//Second hand Busted + + Player playerTwo = new Player(2); + playerTwo.dealCard(new Card(7, Card.Suit.HEARTS)); + playerTwo.dealCard(new Card(8, Card.Suit.HEARTS)); + playerTwo.dealCard(new Card(9, Card.Suit.HEARTS)); + initGame(); + + // When + boolean result = game.shouldPlayDealer(Arrays.asList(player,playerTwo)); + + // Then + assertFalse(result); + } + + @Test + @DisplayName("shouldPlayDealer() return false when players bust") + public void ShouldPlayer(){ + // Given + Player player = new Player(1); + player.dealCard(new Card(10, Card.Suit.SPADES)); + player.dealCard(new Card(10, Card.Suit.SPADES)); + player.split(); + player.dealCard(new Card(5, Card.Suit.SPADES)); + player.dealCard(new Card(8, Card.Suit.SPADES));//First hand Busted + + player.dealCard(new Card(5, Card.Suit.SPADES),2); + player.dealCard(new Card(8, Card.Suit.SPADES),2);//Second hand Busted + + Player playerTwo = new Player(2); + playerTwo.dealCard(new Card(7, Card.Suit.HEARTS)); + playerTwo.dealCard(new Card(8, Card.Suit.HEARTS)); + playerTwo.dealCard(new Card(9, Card.Suit.HEARTS)); + initGame(); + + // When + boolean result = game.shouldPlayDealer(Arrays.asList(player,playerTwo)); + + // Then + assertFalse(result); + } + + @Test + @DisplayName("shouldPlayDealer() return true when player has non-natural blackjack") + public void shouldPlayDealerNonNaturalBlackjack(){ + // Given + Player player = new Player(1); + player.dealCard(new Card(5, Card.Suit.SPADES)); + player.dealCard(new Card(6, Card.Suit.DIAMONDS)); + player.dealCard(new Card(10, Card.Suit.SPADES)); + + initGame(); + + // When + boolean result = game.shouldPlayDealer(Arrays.asList(player)); + + // Then + assertTrue(result); + } + + @Test + @DisplayName("shouldPlayDealer() return true when player doesn't have blackjack") + public void shouldPlayDealerNonBlackjack(){ + // Given + Player player = new Player(1); + player.dealCard(new Card(10, Card.Suit.SPADES)); + player.dealCard(new Card(6, Card.Suit.DIAMONDS)); + initGame(); + + // When + boolean result = game.shouldPlayDealer(Arrays.asList(player)); + + // Then + assertTrue(result); + } + + + @Test + @DisplayName("playDealer() should DRAW on less than 17 intial deal") + public void playDealerLessThanSeventeen(){ + // Given + Player dealer = new Player(0); + dealer.dealCard(new Card(10, Card.Suit.SPADES)); + dealer.dealCard(new Card(6, Card.Suit.DIAMONDS)); + playerGets(11, Card.Suit.DIAMONDS); + initGame(); + + // When + game.playDealer(dealer); + + // Then + assertTrue(out.toString().contains("DRAWS")); + assertTrue(out.toString().contains("BUSTED")); + } + + @Test + @DisplayName("playDealer() should stay on more than 17 intial deal") + public void playDealerMoreThanSeventeen(){ + // Given + Player dealer = new Player(0); + dealer.dealCard(new Card(10, Card.Suit.SPADES)); + dealer.dealCard(new Card(8, Card.Suit.DIAMONDS)); + initGame(); + + // When + game.playDealer(dealer); + + // Then + assertFalse(out.toString().contains("DRAWS")); + assertFalse(out.toString().contains("BUSTED")); + assertTrue(out.toString().contains("---TOTAL IS")); + } + } diff --git a/10_Blackjack/java/test/ScoringUtilsTest.java b/10_Blackjack/java/test/ScoringUtilsTest.java index a6c3877f..01a1738c 100644 --- a/10_Blackjack/java/test/ScoringUtilsTest.java +++ b/10_Blackjack/java/test/ScoringUtilsTest.java @@ -140,4 +140,54 @@ public class ScoringUtilsTest { assertEquals(0, result); } + @Test + @DisplayName("compareHands should return 0, hand A and B tie when both bust") + public void compareHandsTieBust() { + LinkedList handA = new LinkedList<>(); + handA.add(new Card(10, Card.Suit.DIAMONDS)); + handA.add(new Card(10, Card.Suit.HEARTS)); + handA.add(new Card(3, Card.Suit.HEARTS)); + + LinkedList handB = new LinkedList<>(); + handB.add(new Card(10, Card.Suit.SPADES)); + handB.add(new Card(11, Card.Suit.SPADES)); + handB.add(new Card(4, Card.Suit.SPADES)); + + int result = ScoringUtils.compareHands(handA,handB); + + assertEquals(0, result); + } + @Test + @DisplayName("compareHands should return -1, meaning B beat A, A busted") + public void compareHandsABusted() { + LinkedList handA = new LinkedList<>(); + handA.add(new Card(10, Card.Suit.DIAMONDS)); + handA.add(new Card(10, Card.Suit.HEARTS)); + handA.add(new Card(3, Card.Suit.HEARTS)); + + LinkedList handB = new LinkedList<>(); + handB.add(new Card(10, Card.Suit.SPADES)); + handB.add(new Card(10, Card.Suit.SPADES)); + + int result = ScoringUtils.compareHands(handA,handB); + + assertEquals(-1, result); + } + + @Test + @DisplayName("compareHands should return 1, meaning A beat B, B busted") + public void compareHandsBBusted() { + LinkedList handA = new LinkedList<>(); + handA.add(new Card(10, Card.Suit.DIAMONDS)); + handA.add(new Card(3, Card.Suit.HEARTS)); + + LinkedList handB = new LinkedList<>(); + handB.add(new Card(10, Card.Suit.SPADES)); + handB.add(new Card(10, Card.Suit.SPADES)); + handB.add(new Card(5, Card.Suit.SPADES)); + + int result = ScoringUtils.compareHands(handA,handB); + + assertEquals(1, result); + } }