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...
A Y
B X
C Z
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()
lines.add(line)
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...