James Williams

Advent Of Code 2022 - Day 2 - Rock, Paper, Scissors

Day 2 was the first real problem this month. The scenario was to simulate scoring based on the win conditions of a Rock, Paper, Scissors game.

Your score for each round was based on the sum of the outcome (win = 6pts, lose = 0pts, or draw = 3pts) and the hand shape you chose (Rock = 1pt, Paper = 2pts, Scissors = 3pts).

Part I

My first attempt at Part I seized on the fact that the indicators for Rock, Paper, Scissors were assigned to A, B, and C for the opponent and X, Y, and Z for the player.

I "scaled" the X,Y,Z to A, B, C by subtracting the difference between X and Z. I then did a simple comparison and accounted for the off by one error I'd introduced. It worked fine for the sample cases...


What I noticed when trying the real data was that the test cases never test Rock vs Scissors. That's the one case where a simple greater than comparison fails. Anticipating that Part II would take some sort of turn and need a more robust solution, I went verbose and made an enum class for the hand shapes storing their possible ids and point values.

enum class Shape(val idOne: Char, val idTwo: Char, val value:Int) {
    ROCK('A', 'X', 1),
    PAPER('B', 'Y', 2),
    SCISSORS('C', 'Z', 3)

private fun findShape(i: Char): Shape {
    return Shape.values().first { it.idOne == i || it.idTwo == i }

Once the shapes are determined, they are passed to determineWinner where I listed out the five possible end states as a bunch of if-else statements.

private fun determineWinner(opponent: Shape, player:Shape): Int {
    val draw = 3
    val won = 6
    return if (opponent == player) {
        draw + player.value
    } else if (opponent == Shape.ROCK && player == Shape.PAPER) {
        won + player.value
    } else if (opponent == Shape.PAPER && player == Shape.SCISSORS) {
        won + player.value
    } else if (opponent == Shape.SCISSORS && player == Shape.ROCK) {
        won + player.value
    } else player.value

val lines = mutableListOf()  // for part two

override fun part01(): Any? {
    var sum = 0
    while(scan.hasNextLine()) {
        val line = scan.nextLine().toCharArray()
        val opponent = findShape(line[0])
        val player = findShape(line[2])
        sum += determineWinner(opponent, player)
    return sum

Part II

The Part II twist was that instead of the second character indicated what you played, it now indicated the outcome and you had to determine what to play to reach that outcome. The scoring algorithm from the first round stayed the same.

I created an enum class called Outcome to allow me to lookup the states. Totally not required but it made the code a bit more readable and pluggable into the Part I code. determinePlay took the opponent's play and the desired outcome to provide the right move. Like with the determineWinner function, it was a small list and easily enumeratable.

enum class Outcome(val id:Char) {LOSE('X'),DRAW('Y'), WIN('Z') }
private fun findOutcome(i: Char):Outcome { return Outcome.values().first { it.id == i }}

private fun determinePlay(opponent: Shape, outcome: Outcome): Shape {
    when (outcome) {
        Outcome.DRAW -> return opponent
        Outcome.WIN -> {
            return when(opponent) {
                Shape.ROCK -> Shape.PAPER
                Shape.PAPER -> Shape.SCISSORS
                Shape.SCISSORS -> Shape.ROCK
        Outcome.LOSE -> {
            return when(opponent) {
                Shape.ROCK -> Shape.SCISSORS
                Shape.PAPER -> Shape.ROCK
                Shape.SCISSORS -> Shape.PAPER

override fun part02(): Any? {
    var sum = 0
    lines.forEach {
        val opponent = findShape(it[0])
        val outcome = findOutcome(it[2])
        val player = determinePlay(opponent, outcome)
        sum += determineWinner(opponent, player)
    return sum

From there, I passed the opponent move and the derived player move into determineWinner from Part I to get the score.

On to Day 3...