Skip to content
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
libs/
out/
10 changes: 10 additions & 0 deletions src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import com.hmf.Computer
import com.hmf.Game
import com.hmf.StdOutOutputter

fun main(args: Array<String>) {
val opponent = Computer()
val outputter = StdOutOutputter()
val game = Game<Computer,StdOutOutputter>(opponent, outputter)
game.play()
}
7 changes: 7 additions & 0 deletions src/main/kotlin/com/hmf/Computer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.hmf

class Computer :IOpponent {
override fun play():Element{
return Element.values().random();
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/hmf/Element.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.hmf

enum class Element(val idx: Int){
ROCK(0), PAPER(1), SCISSORS(2)
}
59 changes: 59 additions & 0 deletions src/main/kotlin/com/hmf/Game.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.hmf

import java.util.Scanner

class Game<Opponent: IOpponent, Outputter: IOutputter> {

private val scanner = Scanner(System.`in`)
private val opponent: Opponent
private val outputter: Outputter

constructor(opponent:Opponent,outputter:Outputter){
this.opponent = opponent
this.outputter = outputter
}

private var played:Int = 0;
private var won:Int = 0;
private var drawn:Int = 0;
private val rules = GameRules()

fun playRound(playerHand: Element) {
val opponentHand = opponent.play()
val outcome = rules.getOutcome(player = playerHand, opponent=opponentHand)
outputter.printOutcome(roundOutcome=outcome, player = playerHand, opponent = opponentHand)
when(outcome){
Outcome.PLAYER_WON -> won+=1
Outcome.DRAW -> drawn+=1
Outcome.PLAYER_LOST -> Unit
}
played+=1
}


fun play(){
var stopPlaying = false
while (!stopPlaying) {
print("Choose your input:\n'0' for ROCK\n'1' for PAPER\n'2' for SCISSORS\n'x' to stop\n -> ")
val userInput = when (scanner.next().single()) {
'0' -> Element.ROCK
'1' -> Element.PAPER
'2' -> Element.SCISSORS
'x' -> {
outputter.printStats(getStats())
break
}
else -> {
println("Invalid input, fat fingers? Try again!")
continue
}
}
playRound(userInput)
}
}

fun getStats(): GameResult {
return GameResult(roundsPlayed=played,roundsWon=won,roundsDrawn=drawn)
}

}
3 changes: 3 additions & 0 deletions src/main/kotlin/com/hmf/GameResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.hmf

data class GameResult(val roundsPlayed:Int, val roundsWon: Int, val roundsDrawn: Int) { }
16 changes: 16 additions & 0 deletions src/main/kotlin/com/hmf/GameRules.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.hmf

class GameRules {

private val rules: Array<Array<Outcome>> = arrayOf(
arrayOf(Outcome.DRAW, Outcome.PLAYER_LOST, Outcome.PLAYER_WON),
arrayOf(Outcome.PLAYER_WON, Outcome.DRAW, Outcome.PLAYER_LOST),
arrayOf(Outcome.PLAYER_LOST, Outcome.PLAYER_WON, Outcome.DRAW)
)

fun getOutcome(player: Element, opponent: Element): Outcome {
require(player.idx in 0..2)
require(opponent.idx in 0..2)
return rules[player.idx][opponent.idx]
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/hmf/IOpponent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.hmf

interface IOpponent {
fun play(): Element
}
6 changes: 6 additions & 0 deletions src/main/kotlin/com/hmf/IOutputter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.hmf

interface IOutputter {
fun printOutcome(roundOutcome: Outcome, player: Element, opponent: Element)
fun printStats(stats:GameResult)
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/hmf/Outcome.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.hmf

enum class Outcome(val points: Int) {
PLAYER_LOST(-1), DRAW(0), PLAYER_WON(1)
}
21 changes: 21 additions & 0 deletions src/main/kotlin/com/hmf/StdOutOutputter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.hmf

class StdOutOutputter : IOutputter {
override fun printOutcome(roundOutcome: Outcome, player: Element, opponent: Element) {
when(roundOutcome){
Outcome.PLAYER_LOST ->
println("You lost! ${player.toString()} looses to ${opponent.toString()}")
Outcome.PLAYER_WON ->
println("You won! ${player.toString()} beats ${opponent.toString()}")
Outcome.DRAW ->
println("It is a draw! Computer played ${opponent.toString()} as well.")
}
}

override fun printStats(stats:GameResult) {

println("Rounds played: ${stats.roundsPlayed}")
println("Rounds won: ${stats.roundsWon}")
println("Rounds drawn: ${stats.roundsDrawn}")
}
}
20 changes: 20 additions & 0 deletions src/test/kotlin/com/hmf/GameRulesTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.hmf

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class GameRulesTests {

@Test
fun TestSingleRound(){
val round = GameRules()
val outcome0 = round.getOutcome(player=Element.ROCK, opponent=Element.ROCK)
assertEquals(outcome0, Outcome.DRAW)

val outcome1 = round.getOutcome(player=Element.ROCK, opponent=Element.PAPER)
assertEquals(outcome1, Outcome.PLAYER_LOST)

val outcome2 = round.getOutcome(player=Element.ROCK, opponent=Element.SCISSORS)
assertEquals(outcome2, Outcome.PLAYER_WON)
}
}
55 changes: 55 additions & 0 deletions src/test/kotlin/com/hmf/GameTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.hmf

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class StubOpponent(val element: Element) : IOpponent {
override fun play(): Element {
return element;
}
}
class StubOutputter : IOutputter {
override fun printOutcome(roundOutcome: Outcome, player: Element, opponent: Element) {}
override fun printStats(stats: GameResult) {}
}

class GameTests {

val alwaysRockOpponent = StubOpponent(Element.ROCK)
val outputter = StubOutputter()

@Test
fun TestStatsForGameNotPlayed(){
val game = Game<StubOpponent, StubOutputter>(alwaysRockOpponent,outputter)
val result = game.getStats()
assertEquals(result.roundsPlayed, 0)
assertEquals(result.roundsWon, 0)
assertEquals(result.roundsDrawn, 0)
}

@Test
fun TestStatsForMultiplePlayedGameRounds(){
run {
val game = Game<StubOpponent, StubOutputter>(alwaysRockOpponent,outputter)
game.playRound(playerHand=Element.SCISSORS)
game.playRound(playerHand=Element.ROCK)
game.playRound(playerHand=Element.PAPER)
val result = game.getStats()
assertEquals(result.roundsPlayed, 3)
assertEquals(result.roundsWon, 1)
assertEquals(result.roundsDrawn, 1)
}
run {
val game = Game<StubOpponent, StubOutputter>(alwaysRockOpponent,outputter)
game.playRound(playerHand=Element.PAPER)
game.playRound(playerHand=Element.PAPER)
game.playRound(playerHand=Element.PAPER)
val result = game.getStats()
assertEquals(result.roundsPlayed, 3)
assertEquals(result.roundsWon, 3)
assertEquals(result.roundsDrawn, 0)
}
}


}