Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
testImplementation(kotlin("test"))
testImplementation("io.kotest:kotest-assertions-core:5.9.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.1.0")
testImplementation("io.mockk:mockk:1.13.12")
}

tasks.test {
Expand Down
55 changes: 38 additions & 17 deletions src/main/kotlin/main.kt
Original file line number Diff line number Diff line change
@@ -1,30 +1,51 @@
fun main() {
val game = Game()
game()
}
fun main() = RepeatGames().start()

class Game(
private val rnd: (List<String>) -> String = { it.random() }
private val rnd: (List<String>) -> String = { it.random() },
private val input: UserInput = UserInput(),
private val output: UserOutput = UserOutput()
) {
operator fun invoke() {
val choices = listOf("rock", "paper", "scissors")
val compChoice = rnd(choices)
println("Enter choice:")
val userIn = readln()
if (userIn !in choices) {
println("Invalid input: $userIn")
output.display("Enter choice:")
val userIn = input.line()
if(userIn !in choices) {
output.display("Invalid input: $userIn")
return
}
println("Computer choice: $compChoice")
if (compChoice == userIn) {
println("draw")

output.display("Computer choice: $compChoice")
if(compChoice == userIn) {
output.display("draw")
return
}
when ("$compChoice $userIn") {
"rock paper" -> println("win")
"paper scissors" -> println("win")
"scissors rock" -> println("win")
else -> println("lose")
when("$compChoice $userIn") {
"rock paper" -> output.display("win")
"paper scissors" -> output.display("win")
"scissors rock" -> output.display("win")
else -> output.display("lose")
}
}
}

class RepeatGames(
private val output: UserOutput = UserOutput(),
private val input: UserInput = UserInput(),
private val generator: () -> Game = { Game() }
) {
fun start() {
do {
generator()()
output.display("Play another game (Enter 'n' or 'no' to exit)?")
} while(input.line().orEmpty().lowercase() !in arrayOf("n", "no"))
}
}

class UserInput {
fun line() : String? = readlnOrNull()
}

class UserOutput {
fun display(line: String) = println(line)
}
107 changes: 67 additions & 40 deletions src/test/kotlin/MainKtTest.kt
Original file line number Diff line number Diff line change
@@ -1,100 +1,127 @@
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldEndWith
import org.junit.jupiter.api.AfterEach
import io.mockk.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.PrintStream

class MainKtTest {

private val oIn = System.`in`
private val oOut = System.out

private lateinit var console: ByteArrayOutputStream
private lateinit var input: UserInput
private lateinit var output: UserOutput

@BeforeEach
fun setUp() {
console = ByteArrayOutputStream()
System.setOut(PrintStream(console))
}
input = mockk()
every { input.line() } returns ""

@AfterEach
fun tearDown() {
System.setIn(oIn)
System.setOut(oOut)
output = mockk()
every { output.display(any()) } just runs
}

@Test
fun `when user enters unrecognised input, then exit with invalid input warning`() {
val user = "Neither a rock nor paper nor scissors"
user.enterAsUserInput()
fun `when user enters unrecognised input, then exit with invalid input warning`() = with("Invalid input") {
enterUserInput()
runGame()
console.asString() shouldEndWith "Invalid input: $user"

verifyOutput {
it shouldEndWith "Invalid input: $this"
}
}


@Test
fun `when computer player randomly selects rock, then print computer player choice`() {
"rock".enterAsUserInput()
"rock".enterUserInput()
val computer = "scissors"
runGameWithComputerChoice(computer)
console.asString() shouldContain "Computer choice: $computer"
verifyOutput {
it shouldContain "Computer choice: $computer"
}
}

@Test
fun `when computer selects rock, and user selects paper, then user wins`() {
"paper".enterAsUserInput()
"paper".enterUserInput()
runGameWithComputerChoice("rock")
console.asString() shouldEndWith "win"
assertUserWin()
}

@Test
fun `when computer selects paper, and user selects scissors, then user wins`() {
"scissors".enterAsUserInput()
"scissors".enterUserInput()
runGameWithComputerChoice("paper")
console.asString() shouldEndWith "win"
assertUserWin()
}

@Test
fun `when computer selects scissors, and user selects rock, then user wins`() {
"rock".enterAsUserInput()
"rock".enterUserInput()
runGameWithComputerChoice("scissors")
console.asString() shouldEndWith "win"
assertUserWin()
}

@Test
fun `when computer selects rock, and user selects scissors, then user loses`() {
"scissors".enterAsUserInput()
"scissors".enterUserInput()
runGame { "rock" }
console.asString() shouldEndWith "lose"
assertUserLoss()
}

@Test
fun `when computer selects paper, and user selects rock, then user loses`() {
"rock".enterAsUserInput()
runGameWithComputerChoice("paper")
console.asString() shouldEndWith "lose"
"rock".enterUserInput()
runGameWithComputerChoice( "paper")
assertUserLoss()
}

@Test
fun `when computer selects scissors, and user selects paper, then user loses`() {
"paper".enterAsUserInput()
"paper".enterUserInput()
runGameWithComputerChoice("scissors")
console.asString() shouldEndWith "lose"
assertUserLoss()
}

@Test
fun `when computer selects scissors, and user selects scissors, then user draws`() = with("scissors") {
enterUserInput()
runGameWithComputerChoice(this)
assertUserDraw()
}

@Test
fun `when computer selects rock, and user selects rock, then user draws`() = with("rock") {
enterUserInput()
runGameWithComputerChoice(this)
assertUserDraw()
}


@Test
fun `when computer selects paper, and user selects paper, then user draws`() = with("paper") {
enterUserInput()
runGameWithComputerChoice(this)
assertUserDraw()
}

private fun runGameWithComputerChoice(choice: String) =
runGame { choice }

private fun runGame(rnd: (List<String>) -> String = { it.random() }) {
Game(rnd)()
Game(rnd, input, output)()
}

private fun String.enterAsUserInput() =
System.setIn(ByteArrayInputStream(toByteArray()))
private fun String.enterUserInput() =
every { input.line() } returns this

private fun verifyOutput(block: (String) -> Unit) {
verify { output.display(withArg { block(it) }) }
}

private fun assertUserWin() =
verifyOutput { it shouldEndWith "win" }

private fun assertUserLoss() =
verifyOutput { it shouldEndWith "lose" }

private fun ByteArrayOutputStream.asString() =
toString().trim()
private fun assertUserDraw() =
verifyOutput { it shouldEndWith "draw" }
}
83 changes: 83 additions & 0 deletions src/test/kotlin/RepeatGamesTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import io.kotest.matchers.ints.shouldBeExactly
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldEndWith
import io.mockk.*
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.io.ByteArrayInputStream

class RepeatGamesTest {

private lateinit var input: UserInput
private lateinit var output: UserOutput
private lateinit var game: Game
private lateinit var repeat: RepeatGames

@BeforeEach
fun setUp() {
input = mockk()
every { input.line() } returns ""

output = mockk()
every { output.display(any()) } just runs

game = mockk<Game>()
every { game() } just runs

repeat = RepeatGames(output, input) { game }
}

@Test
fun `given game finished, when user enters n on keyboard, then run one game`() {
"n".enterUserInput()
repeat.start()
verify(exactly = 1) { game() }
}

@Test
fun `given game finished, when user enters no on keyboard, then run one game`() {
"no".enterUserInput()
repeat.start()
verify(exactly = 1) { game() }
}

@Test
fun `given game finished, when user requests another game, and then enters n on keyboard, then run two games `() {
listOf("y", "n").enterUserInput()
repeat.start()
verify(exactly = 2) { game() }
}

@Test
fun `given game finished, when user requests another game with no input, and then enters n on keyboard, then run two games `() {
listOf("", "n").enterUserInput()
repeat.start()
verify(exactly = 2) { game() }
}

@Test
fun `when game finished, then show next game prompt`() {
"n".enterUserInput()
repeat.start()
verify { output.display(withArg {
it shouldBe "Play another game (Enter 'n' or 'no' to exit)?"
}) }
}

@Test
fun `given two games, when games finished, then show next game prompt appears twice`() {
listOf("yes", "no").enterUserInput()
repeat.start()
verify(exactly = 2) { output.display(withArg {
it shouldBe "Play another game (Enter 'n' or 'no' to exit)?"
}) }
}

private fun String.enterUserInput() =
listOf(this).enterUserInput()
private fun List<String>.enterUserInput() =
every { input.line() } returnsMany this

}