mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 23:26:40 -08:00
506 lines
15 KiB
Java
506 lines
15 KiB
Java
import org.junit.jupiter.api.Test;
|
|
|
|
import org.junit.jupiter.api.AfterEach;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.DisplayName;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertAll;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
import java.io.EOFException;
|
|
import java.io.StringReader;
|
|
import java.io.StringWriter;
|
|
import java.io.UncheckedIOException;
|
|
import java.util.Arrays;
|
|
import java.util.LinkedList;
|
|
|
|
public class GameTest {
|
|
|
|
private StringReader in;
|
|
private StringWriter out;
|
|
private Game game;
|
|
|
|
private StringBuilder playerActions;
|
|
private LinkedList<Card> cards;
|
|
|
|
@BeforeEach
|
|
public void resetIo() {
|
|
in = null;
|
|
out = null;
|
|
game = null;
|
|
playerActions = new StringBuilder();
|
|
cards = new LinkedList<>();
|
|
}
|
|
|
|
private void playerGets(int value, Card.Suit suit) {
|
|
cards.add(new Card(value, suit));
|
|
}
|
|
|
|
private void playerSays(String action) {
|
|
playerActions.append(action).append(System.lineSeparator());
|
|
}
|
|
|
|
private void initGame() {
|
|
System.out.printf("Running game with input: %s\tand cards: %s\n",playerActions.toString(), cards);
|
|
in = new StringReader(playerActions.toString());
|
|
out = new StringWriter();
|
|
UserIo userIo = new UserIo(in, out);
|
|
Deck deck = new Deck((c) -> cards);
|
|
game = new Game(deck, userIo);
|
|
}
|
|
|
|
@AfterEach
|
|
private void printOutput() {
|
|
System.out.println(out.toString());
|
|
}
|
|
|
|
@Test
|
|
public void shouldQuitOnCtrlD() {
|
|
// Given
|
|
playerSays("\u2404"); // U+2404 is "End of Transmission" sent by CTRL+D (or CTRL+Z on Windows)
|
|
initGame();
|
|
|
|
// When
|
|
Exception e = assertThrows(UncheckedIOException.class, game::run);
|
|
|
|
// Then
|
|
assertTrue(e.getCause() instanceof EOFException);
|
|
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));
|
|
playerSays("S"); // "I also like to live dangerously."
|
|
initGame();
|
|
|
|
// 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(10, Card.Suit.SPADES));
|
|
|
|
playerSays("H");
|
|
playerGets(1, Card.Suit.SPADES); // 20
|
|
playerSays("H");
|
|
playerGets(1, Card.Suit.HEARTS); // 21
|
|
playerSays("H");
|
|
playerGets(1, Card.Suit.CLUBS); // 22 - D'oh!
|
|
initGame();
|
|
|
|
// When
|
|
game.play(player);
|
|
|
|
// Then
|
|
assertTrue(out.toString().contains("BUSTED"));
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("Should allow double down on initial turn")
|
|
public void playDoubleDown(){
|
|
System.out.println("Here");
|
|
// 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));
|
|
|
|
playerSays("D");
|
|
playerGets(7, Card.Suit.SPADES);
|
|
initGame();
|
|
|
|
// 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));
|
|
|
|
playerSays("H");
|
|
playerGets(7, Card.Suit.SPADES);
|
|
playerSays("D");
|
|
playerSays("S");
|
|
initGame();
|
|
|
|
// 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
|
|
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));
|
|
|
|
initGame();
|
|
|
|
// When
|
|
int result = game.scoreHand(hand);
|
|
|
|
// Then
|
|
assertEquals(20, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("scoreHand should treat face cards as 10")
|
|
public void scoreHandFaceCards() {
|
|
// Given
|
|
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));
|
|
|
|
initGame();
|
|
|
|
// When
|
|
int result = game.scoreHand(hand);
|
|
|
|
// Then
|
|
assertEquals(30, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("scoreHand should score aces as 11 when possible")
|
|
public void scoreHandSoftAce() {
|
|
// Given
|
|
LinkedList<Card> hand = new LinkedList<>();
|
|
hand.add(new Card(10, Card.Suit.SPADES));
|
|
hand.add(new Card(1, Card.Suit.SPADES));
|
|
|
|
initGame();
|
|
|
|
// 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
|
|
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));
|
|
|
|
initGame();
|
|
|
|
// When
|
|
int result = game.scoreHand(hand);
|
|
|
|
// Then
|
|
assertEquals(20, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("scoreHand should score 3 aces as 13")
|
|
public void scoreHandMultipleAces() {
|
|
// Given
|
|
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));
|
|
|
|
initGame();
|
|
|
|
// When
|
|
int result = game.scoreHand(hand);
|
|
|
|
// Then
|
|
assertEquals(13, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("compareHands should return 1 meaning A beat B, 20 to 12")
|
|
public void compareHandsAWins() {
|
|
LinkedList<Card> handA = new LinkedList<>();
|
|
handA.add(new Card(10, Card.Suit.SPADES));
|
|
handA.add(new Card(10, Card.Suit.CLUBS));
|
|
|
|
LinkedList<Card> handB = new LinkedList<>();
|
|
handB.add(new Card(1, Card.Suit.SPADES));
|
|
handB.add(new Card(1, Card.Suit.CLUBS));
|
|
|
|
initGame();
|
|
|
|
int result = game.compareHands(handA,handB);
|
|
|
|
assertEquals(1, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("compareHands should return -1 meaning B beat A, 18 to 4")
|
|
public void compareHandsBwins() {
|
|
LinkedList<Card> handA = new LinkedList<>();
|
|
handA.add(new Card(2, Card.Suit.SPADES));
|
|
handA.add(new Card(2, Card.Suit.CLUBS));
|
|
|
|
LinkedList<Card> handB = new LinkedList<>();
|
|
handB.add(new Card(5, Card.Suit.SPADES));
|
|
handB.add(new Card(6, Card.Suit.HEARTS));
|
|
handB.add(new Card(7, Card.Suit.CLUBS));
|
|
|
|
initGame();
|
|
|
|
int result = game.compareHands(handA,handB);
|
|
|
|
assertEquals(-1, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("compareHands should return 1 meaning A beat B, natural Blackjack to Blackjack")
|
|
public void compareHandsAWinsWithNaturalBlackJack() {
|
|
//Hand A wins with natural BlackJack, B with Blackjack
|
|
LinkedList<Card> handA = new LinkedList<>();
|
|
handA.add(new Card(10, Card.Suit.SPADES));
|
|
handA.add(new Card(1, Card.Suit.CLUBS));
|
|
|
|
LinkedList<Card> handB = new LinkedList<>();
|
|
handB.add(new Card(6, Card.Suit.SPADES));
|
|
handB.add(new Card(7, Card.Suit.HEARTS));
|
|
handB.add(new Card(8, Card.Suit.CLUBS));
|
|
|
|
initGame();
|
|
|
|
int result = game.compareHands(handA,handB);
|
|
|
|
assertEquals(1, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("compareHands should return -1 meaning B beat A, natural Blackjack to Blackjack")
|
|
public void compareHandsBWinsWithNaturalBlackJack() {
|
|
LinkedList<Card> handA = new LinkedList<>();
|
|
handA.add(new Card(6, Card.Suit.SPADES));
|
|
handA.add(new Card(7, Card.Suit.HEARTS));
|
|
handA.add(new Card(8, Card.Suit.CLUBS));
|
|
|
|
LinkedList<Card> handB = new LinkedList<>();
|
|
handB.add(new Card(10, Card.Suit.SPADES));
|
|
handB.add(new Card(1, Card.Suit.CLUBS));
|
|
|
|
initGame();
|
|
|
|
int result = game.compareHands(handA,handB);
|
|
|
|
assertEquals(-1, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("compareHands should return 0, hand A and B tied with a Blackjack")
|
|
public void compareHandsTieBothBlackJack() {
|
|
LinkedList<Card> handA = new LinkedList<>();
|
|
handA.add(new Card(11, Card.Suit.SPADES));
|
|
handA.add(new Card(10, Card.Suit.CLUBS));
|
|
|
|
LinkedList<Card> handB = new LinkedList<>();
|
|
handB.add(new Card(10, Card.Suit.SPADES));
|
|
handB.add(new Card(11, Card.Suit.CLUBS));
|
|
|
|
initGame();
|
|
|
|
int result = game.compareHands(handA,handB);
|
|
|
|
assertEquals(0, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("compareHands should return 0, hand A and B tie without a Blackjack")
|
|
public void compareHandsTieNoBlackJack() {
|
|
LinkedList<Card> handA = new LinkedList<>();
|
|
handA.add(new Card(10, Card.Suit.DIAMONDS));
|
|
handA.add(new Card(10, Card.Suit.HEARTS));
|
|
|
|
LinkedList<Card> handB = new LinkedList<>();
|
|
handB.add(new Card(10, Card.Suit.SPADES));
|
|
handB.add(new Card(10, Card.Suit.CLUBS));
|
|
|
|
initGame();
|
|
|
|
int result = game.compareHands(handA,handB);
|
|
|
|
assertEquals(0, result);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("play() should end on STAY after split")
|
|
public void playSplitEndOnStay(){
|
|
// Given
|
|
Player player = new Player(1);
|
|
player.dealCard(new Card(1, Card.Suit.CLUBS));
|
|
player.dealCard(new Card(1, Card.Suit.SPADES));
|
|
|
|
playerSays("/");
|
|
playerGets(2, Card.Suit.SPADES); // First hand
|
|
playerSays("S");
|
|
playerGets(2, Card.Suit.SPADES); // Second hand
|
|
playerSays("S");
|
|
initGame();
|
|
|
|
// When
|
|
game.play(player);
|
|
|
|
// Then
|
|
assertTrue(out.toString().contains("FIRST HAND RECEIVES"));
|
|
assertTrue(out.toString().contains("SECOND HAND RECEIVES"));
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("play() should allow HIT until BUST after split")
|
|
public void playSplitHitUntilBust() {
|
|
// Given
|
|
Player player = new Player(1);
|
|
player.dealCard(new Card(10, Card.Suit.HEARTS));
|
|
player.dealCard(new Card(10, Card.Suit.SPADES));
|
|
|
|
playerSays("/");
|
|
playerGets(12, Card.Suit.SPADES); // First hand has 20
|
|
playerSays("H");
|
|
playerGets(12, Card.Suit.HEARTS); // First hand busted
|
|
playerGets(10, Card.Suit.HEARTS); // Second hand gets a 10
|
|
playerSays("S");
|
|
initGame();
|
|
|
|
// When
|
|
game.play(player);
|
|
|
|
// Then
|
|
assertTrue(out.toString().contains("BUSTED"));
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("play() should allow HIT on split hand until BUST")
|
|
public void playSplitHitUntilBustHand2() {
|
|
// Given
|
|
Player player = new Player(1);
|
|
player.dealCard(new Card(10, Card.Suit.HEARTS));
|
|
player.dealCard(new Card(10, Card.Suit.SPADES));
|
|
|
|
playerSays("/");
|
|
playerGets(1, Card.Suit.CLUBS); // First hand is 21
|
|
playerSays("S");
|
|
playerGets(12, Card.Suit.SPADES); // Second hand is 20
|
|
playerSays("H");
|
|
playerGets(12, Card.Suit.HEARTS); // Busted
|
|
playerSays("H");
|
|
initGame();
|
|
|
|
// When
|
|
game.play(player);
|
|
|
|
// Then
|
|
assertTrue(out.toString().contains("BUSTED"));
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("play() should allow double down on split hands")
|
|
public void playSplitDoubleDown(){
|
|
// Given
|
|
Player player = new Player(1);
|
|
player.setCurrentBet(100);
|
|
player.dealCard(new Card(9, Card.Suit.HEARTS));
|
|
player.dealCard(new Card(9, Card.Suit.SPADES));
|
|
|
|
playerSays("/");
|
|
playerGets(5, Card.Suit.DIAMONDS); // First hand is 14
|
|
playerSays("D");
|
|
playerGets(6, Card.Suit.HEARTS); // First hand is 20
|
|
playerGets(7, Card.Suit.CLUBS); // Second hand is 16
|
|
playerSays("D");
|
|
playerGets(4, Card.Suit.CLUBS); // Second hand is 20
|
|
initGame();
|
|
|
|
// When
|
|
game.play(player);
|
|
|
|
// Then
|
|
assertAll(
|
|
() -> assertEquals(200, player.getCurrentBet(), "Current bet should be doubled"),
|
|
() -> assertEquals(200, player.getSplitBet(), "Split bet should be doubled"),
|
|
() -> assertEquals(3, player.getHand(1).size(), "First hand should have exactly three cards"),
|
|
() -> assertEquals(3, player.getHand(2).size(), "Second hand should have exactly three cards")
|
|
);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("play() should NOT allow re-splitting first split hand")
|
|
public void playSplitTwice(){
|
|
// 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));
|
|
|
|
playerSays("/");
|
|
playerGets(13, Card.Suit.CLUBS); // First hand
|
|
playerSays("/"); // Not allowed
|
|
playerSays("S");
|
|
playerGets(13, Card.Suit.SPADES); // Second hand
|
|
playerSays("S");
|
|
initGame();
|
|
|
|
// When
|
|
game.play(player);
|
|
|
|
// Then
|
|
assertTrue(out.toString().contains("TYPE H, S OR D, PLEASE"));
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("play() should NOT allow re-splitting second split hand")
|
|
public void playSplitTwiceHand2(){
|
|
// 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));
|
|
|
|
playerSays("/");
|
|
playerGets(13, Card.Suit.CLUBS); // First hand
|
|
playerSays("S");
|
|
playerGets(13, Card.Suit.SPADES); // Second hand
|
|
playerSays("/"); // Not allowed
|
|
playerSays("S");
|
|
initGame();
|
|
|
|
// When
|
|
game.play(player);
|
|
|
|
// Then
|
|
assertTrue(out.toString().contains("TYPE H, S OR D, PLEASE"));
|
|
}
|
|
}
|