package rps const val TURNS = 100 fun main() { println("Outcome from Player 1’s perspective: ${playGame(TURNS)}") } /** Determine the [Outcome] of [first] vs [second] from the perspective of [first]. */ fun determineOutcome(first: Move, second: Move): Outcome = when(second) { in first.strongAgainst() -> Outcome.WIN first -> Outcome.DRAW else -> Outcome.LOSS } // Note: This could be slightly more efficiently and directly on the Sequence // (but a lot less elegantly) with an imperative loop and three mutable integers. fun List.calculateGameSummary() = GameSummary( wins = count { it == Outcome.WIN }, draws = count { it == Outcome.DRAW }, losses = count { it == Outcome.LOSS }, ) fun playGame(turns: Int): GameSummary = generateSequence { randomMove() to randomMove() } .take(turns) .map { (p1, p2) -> determineOutcome(p1, p2) } .toList() .calculateGameSummary() fun randomMove(): Move = Move.values().random() /* * Classes and named tuples below this point. * (All in the same file for easier reviewability in a web UI on Github/Gitea). */ /** * A possible move in a game of rock-paper-scissors. * * [strongAgainst] is a function for lazy evaluation because otherwise we can’t access SCISSORS before it’s been defined. */ enum class Move(val strongAgainst: () -> Set) { ROCK({ setOf(SCISSORS, LIZARD) }), PAPER({ setOf(ROCK, SPOCK) }), SCISSORS({ setOf(PAPER, LIZARD) }), LIZARD({ setOf(SPOCK, PAPER) }), SPOCK({ setOf(SCISSORS, ROCK) }), } // purposely not calling this Result to avoid confusion with kotlin.Result and similar enum class Outcome { WIN, DRAW, LOSS } // Named Triple for readability data class GameSummary(val wins: Int, val draws: Int, val losses: Int)