Skip to content

Commit

Permalink
Merge pull request #1 from jaymoid/pairing_with_anthony
Browse files Browse the repository at this point in the history
Pairing with anthony
  • Loading branch information
jaymoid authored Apr 26, 2019
2 parents 7d7f666 + 425b4f1 commit 45aa6bc
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.idea/
gradle/
out/
build
28 changes: 17 additions & 11 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,27 @@ repositories {
mavenCentral()
}

dependencies {
implementation('org.jetbrains.kotlin:kotlin-stdlib-jdk8')

testRuntime('org.junit.jupiter:junit-jupiter-engine:5.3.2')
testCompile('org.junit.jupiter:junit-jupiter-api:5.3.2')
testCompile('org.junit.jupiter:junit-jupiter-params:5.3.2')
testCompile("org.assertj:assertj-core:3.11.1")
testImplementation('io.mockk:mockk:1.9')
}

test {
useJUnitPlatform()
useJUnitPlatform {
includeEngines "jqwik"
}
testLogging {
events "passed", "skipped", "failed"
}
include '**/*Properties.class'
include '**/*Test.class'
include '**/*Tests.class'
}

dependencies {
implementation('org.jetbrains.kotlin:kotlin-stdlib-jdk8')

testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.0')
testCompile('org.junit.jupiter:junit-jupiter-api:5.4.0')
testCompile('org.junit.jupiter:junit-jupiter-params:5.4.0')
testCompile('org.assertj:assertj-core:3.11.1')
testCompile('net.jqwik:jqwik:1.1.0')
testImplementation('io.mockk:mockk:1.9')
}

compileKotlin {
Expand Down
59 changes: 37 additions & 22 deletions src/main/kotlin/uk/co/pittendreigh/blackjack/BlackJackGame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,50 @@ enum class GameFinish {
DrawGame
}

data class CardsState(val playerHand: List<Card>, val dealerHand: List<Card>, val deck: List<Card>)
data class CardsState(
val playerHand: List<Card> = emptyList(),
val dealerHand: List<Card> = emptyList(),
val deck: List<Card> = emptyList()
)

private sealed class BlackJackResult
private object BlackJack : BlackJackResult()
private object Bust : BlackJackResult()
private data class EqualTo21OrLower(val possibleScores: Set<Int>) : BlackJackResult()

class BlackJackGame(
val cardProviderFunction: () -> List<Card>,
val cardShufflerFunction: (List<Card>) -> List<Card>
val createDeckOfCards: () -> List<Card>,
val shufffleCards: List<Card>.() -> List<Card>
) {

fun deal(): BlackJackGameState =
cardShufflerFunction(cardProviderFunction()).let { deck ->
getNewGameState(
CardsState(
playerHand = listOf(deck.get(0), deck.get(2)),
dealerHand = listOf(deck.get(1), deck.get(3)),
deck = deck.drop(4)
)
)
getNewGameState(
createDeckOfCards()
.shufffleCards()
.dealFirstHand()
)

private fun List<Card>.dealFirstHand(): CardsState =
fold(CardsState()) { cardState, nextCard ->
with(cardState) {
when {
playerHand.size == 2 && dealerHand.size == 2 -> copy(deck = cardState.deck + nextCard)
playerHand.size == dealerHand.size -> copy(playerHand = cardState.playerHand + nextCard)
else -> copy(dealerHand = cardState.dealerHand + nextCard)
}
}
}

fun twist(gameState: PlayerHas21OrLower): BlackJackGameState =
getNewGameState(
CardsState(
playerHand = gameState.state.playerHand + gameState.state.deck.get(0),
dealerHand = gameState.state.dealerHand,
deck = gameState.state.deck.drop(1)
gameState.state.deck.firstOrNull()?.let { newCard ->
getNewGameState(
CardsState(
playerHand = gameState.state.playerHand + newCard,
dealerHand = gameState.state.dealerHand,
deck = gameState.state.deck - newCard
)
)
)
} ?: gameState

private fun getNewGameState(cardState: CardsState): BlackJackGameState {
val playerHandValue = calculateHandValue(cardState.playerHand)
Expand Down Expand Up @@ -84,11 +97,13 @@ class BlackJackGame(
}

private fun dealerTakesDeckCard(state: CardsState): CardsState =
CardsState(
playerHand = state.playerHand,
dealerHand = state.dealerHand + state.deck.first(),
deck = state.deck.drop(1)
)
state.deck.firstOrNull()?.let { newCard ->
CardsState(
playerHand = state.playerHand,
dealerHand = state.dealerHand + newCard,
deck = state.deck - newCard
)
} ?: state

private fun calculateHandValue(hand: List<Card>): BlackJackResult {
val scores = calculatePossibleValues(hand)
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/uk/co/pittendreigh/blackjack/Cards.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ data class Card(val rank: Rank, val suit: Suit) {
override fun toString() = "[$rank of $suit]"
}

fun shuffleCards(unShuffled: Iterable<Card>): List<Card> = unShuffled.shuffled()
fun Iterable<Card>.shuffleCards(): List<Card> = shuffled()

infix fun Rank.of(suit: Suit): Card = Card(this, suit)
enum class Suit {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/uk/co/pittendreigh/blackjack/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package uk.co.pittendreigh.blackjack

fun main() =
TerminalBlackJackInterface(
BlackJackGame(::createDeckOfCards, ::shuffleCards),
BlackJackGame(::createDeckOfCards, List<Card>::shuffleCards),
System.out,
System.`in`
).startGame()
Expand Down
99 changes: 73 additions & 26 deletions src/test/kotlin/uk/co/pittendreigh/blackjack/BlackJackGameTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import org.junit.jupiter.api.DynamicTest.dynamicTest
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestFactory
import org.junit.jupiter.api.assertAll
import uk.co.pittendreigh.blackjack.GameFinish.*
import uk.co.pittendreigh.blackjack.Rank.*
import uk.co.pittendreigh.blackjack.Suit.*

private val cardProviderFunction: () -> List<Card> = mockk()
private val cardShufflerFunction: (List<Card>) -> List<Card> = { it }
private val cardShufflerExtFunction: Iterable<Card>.() -> List<Card> = { this.toList() }

private val game = BlackJackGame(cardProviderFunction, cardShufflerFunction)
private val game = BlackJackGame(cardProviderFunction, cardShufflerExtFunction)

class `BlackJackGame - Deal functionality` {

Expand All @@ -40,32 +41,34 @@ class `BlackJackGame - Deal functionality` {

@Test
fun `then the cards from the provider are shuffled`() {
val cardShufflerFunction: (List<Card>) -> List<Card> = { cards -> cards.reversed() }
val cardShufflerFunction: List<Card>.() -> List<Card> = { reversed() }

val shuffledGame = BlackJackGame(cardProviderFunction, cardShufflerFunction)
val gameState = shuffledGame.deal()

assertEquals(listOf(ACE of HEARTS), gameState.state.deck)
assertEquals(listOf(JACK of DIAMONDS, ACE of CLUBS), gameState.state.playerHand)
assertEquals(listOf(TWO of CLUBS, TWO of HEARTS), gameState.state.dealerHand)
assertAll(
{ assertEquals(listOf(ACE of HEARTS), gameState.state.deck) },
{ assertEquals(listOf(JACK of DIAMONDS, ACE of CLUBS), gameState.state.playerHand) },
{ assertEquals(listOf(TWO of CLUBS, TWO of HEARTS), gameState.state.dealerHand) }
)
}

@Test
fun `then the player has the first card, and the third card`() {
val gameState = game.deal()
assertTrue(gameState.state.playerHand == listOf(ACE of HEARTS, ACE of CLUBS))
assertThat(gameState.state.playerHand).containsOnly(ACE of HEARTS, ACE of CLUBS)
}

@Test
fun `then the dealer has the second and fourth card`() {
val gameState = game.deal()
assertTrue(gameState.state.dealerHand == listOf(TWO of HEARTS, TWO of CLUBS))
assertThat(gameState.state.dealerHand).containsOnly(TWO of HEARTS, TWO of CLUBS)
}

@Test
fun `then the dealer and player cards are no longer in the deck`() {
val gameState = game.deal()
assertTrue(gameState.state.deck == listOf(JACK of DIAMONDS))
assertThat(gameState.state.deck).containsOnly(JACK of DIAMONDS)
}
}

Expand All @@ -90,8 +93,10 @@ class `BlackJackGame - Deal functionality` {
every { cardProviderFunction() } returns createPreparedDeckOf(playerHand = hand)
val gameState = game.deal()

assertTrue(gameState is PlayerHas21OrLower)
assertEquals(expectedScores, (gameState as PlayerHas21OrLower).possibleScores)
assertAll(
{ assertTrue(gameState is PlayerHas21OrLower) },
{ assertEquals(expectedScores, (gameState as PlayerHas21OrLower).possibleScores) }
)
}
}
}
Expand All @@ -105,7 +110,6 @@ class `BlackJackGame - Deal functionality` {
.flatMap { (playerCard, dealerCard) -> listOf(playerCard, dealerCard) }
.plus(remainingDeckCards)


@Nested
inner class `given the player has blackjack hand but dealer does not, when starting deal is performed` {

Expand All @@ -127,8 +131,10 @@ class `BlackJackGame - Deal functionality` {
)
val gameState = game.deal()

assertTrue(gameState is GameOver)
assertTrue((gameState as GameOver).result == PlayerIsBlackJack)
assertAll(
{ assertTrue(gameState is GameOver) },
{ assertTrue((gameState as GameOver).result == PlayerIsBlackJack) }
)
}
}
}
Expand Down Expand Up @@ -163,7 +169,6 @@ class `BlackJackGame - Deal functionality` {

val gameState = game.deal()


assertEquals(PlayerAndDealerBlackJack, (gameState as GameOver).result)
}
}
Expand Down Expand Up @@ -258,10 +263,14 @@ class `BlackJackGame - Twist functionality` {

val stateAfterTwist = game.twist(priorGameState)

assertTrue(stateAfterTwist is PlayerHas21OrLower)
assertEquals(
setOf(expectedHandValue),
(stateAfterTwist as PlayerHas21OrLower).possibleScores
assertAll(
{ assertTrue(stateAfterTwist is PlayerHas21OrLower) },
{
assertEquals(
setOf(expectedHandValue),
(stateAfterTwist as PlayerHas21OrLower).possibleScores
)
}
)
}
}
Expand Down Expand Up @@ -366,8 +375,10 @@ class `BlackJackGame - Stick functionality` {

val stateAfterStick = game.stick(priorGameState)

assertEquals(DealerIsBust, stateAfterStick.result)
assertThat(stateAfterStick.state.dealerHand).contains(TEN of DIAMONDS)
assertAll(
{ assertEquals(DealerIsBust, stateAfterStick.result) },
{ assertThat(stateAfterStick.state.dealerHand).contains(TEN of DIAMONDS) }
)
}
}

Expand All @@ -386,9 +397,10 @@ class `BlackJackGame - Stick functionality` {
)

val stateAfterStick = game.stick(priorGameState)

assertEquals(DealerWins, stateAfterStick.result)
assertThat(stateAfterStick.state.dealerHand).contains(FOUR of DIAMONDS)
assertAll(
{ assertEquals(DealerWins, stateAfterStick.result) },
{ assertThat(stateAfterStick.state.dealerHand).contains(FOUR of DIAMONDS) }
)
}
}

Expand All @@ -408,8 +420,43 @@ class `BlackJackGame - Stick functionality` {

val stateAfterStick = game.stick(priorGameState)

assertEquals(DrawGame, stateAfterStick.result)
assertThat(stateAfterStick.state.dealerHand).contains(FOUR of DIAMONDS)
assertAll(
{ assertEquals(DrawGame, stateAfterStick.result) },
{ assertThat(stateAfterStick.state.dealerHand).contains(FOUR of DIAMONDS) }
)
}
}

@Test
internal fun spikeFold() {
val startList = (1..5).toList()

val foldl = startList.fold("0", { acc: String, i: Int -> "($acc+$i)" })
println("left associative fold: $foldl")

val foldr = startList.foldRight("0", { i: Int, acc: String -> "($i+$acc)" })
println("right associative fold: $foldr")


val bigList = (1..10_000).toList()
var executedL = 0
val bigFoldl = bigList.fold("0", { _: String, i: Int ->
executedL++
i.toString()
})
println("left associative big fold: $bigFoldl, function was executed: $executedL")

var executedR = 0
val bigFoldR = bigList.foldRight("0", { i: Int, _: String ->
executedR++
i.toString()
})
println("right associative big fold: $bigFoldR, function was executed: $executedR")

}

@Test
internal fun spike() {
emptyList<String>().drop(1)
}
}
3 changes: 2 additions & 1 deletion src/test/kotlin/uk/co/pittendreigh/blackjack/CardsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class CardDeckTest {
inner class `given unshuffled cards, when they are shuffled` {

private val unshuffledCards = createDeckOfCards()
private val shuffledCards = shuffleCards(unshuffledCards)
private val shuffledCards = unshuffledCards.shuffleCards()

@Test
fun `then the list of shuffled cards contain all of the provided unshuffled cards`() {
Expand All @@ -49,3 +49,4 @@ class CardDeckTest {
}
}
}

0 comments on commit 45aa6bc

Please sign in to comment.