package rps import kotlin.random.Random const val TURNS = 100 val PLAYER_TWO_CHOICE = Move.ROCK 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 { first.strongAgainst() == second -> Outcome.WIN first == second -> 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() } .take(turns) .map { determineOutcome(it, PLAYER_TWO_CHOICE) } .toList() .calculateGameSummary() fun randomMove(): Move = Move.values().let { allMoves -> allMoves[Random.nextInt(allMoves.size)] } /* * 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. * If the game needs to be expanded (e.g. the somewhat common “rock, paper, scissors, salamander, spock”), * this argument could be a List instead. */ enum class Move(val strongAgainst: () -> Move) { ROCK({ SCISSORS }), PAPER({ ROCK }), SCISSORS({ PAPER }) } // 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)