package rps import kotlin.random.Random fun main() { println(playGame(100)) } /** * 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) /** 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 would be slightly more efficient (but a lot less elegant) 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, Move.ROCK) } .toList() .calculateGameSummary() fun randomMove(): Move = Move.values().let { allMoves -> allMoves[Random.nextInt(allMoves.size)] }