Bart Kessels
Bart Kessels
Passionate open source software engineer who loves to go backpacking

Advent of Code 2022, Day two - Part Two

Advent of Code 2022, Day two - Part Two
This image is generated using Dall-E
  • Prompt: Generate an image of two elves playing rock paper scissors in minimalistic flat style
  • Preface

    Now that we have the following data structure from our previous post, we can start by implementing the business logic to calculate the outcome of each match. Before we can do that, we need to setup three conversion tables. One for converting each strategy, one for each expected outcome and another one to convert each match outcome to points.

    1
    2
    3
    4
    5
    
    [
      { "A", "Y" }, // Match 1
      { "B", "X" }, // Match 2
      { "C", "Z" }  // Match 3
    ]
    

    Conversion table for strategies

    A for Rock, B for Paper, and C for Scissors […] X for Rock, Y for Paper, and Z for Scissors

    Rock Paper Scissors
    A B C

    Conversion table for expected match outcomes

    X means […] lose, Y means […] draw, and Z means […] win

    Lost Draw Won
    X Y Z

    Conversion table for the match outcome

    score for the outcome of the round […] 0 if you lost, 3 if the round was a draw, and 6 if you won

    0 points 3 points 6 points
    Rock - Paper Rock - Rock Rock - Scissors
    Paper - Scissors Paper - Paper Paper - Rock
    Scissors - Rock Scissors - Scissors Scissors - Paper

    Design

    So now that we know what our opponents strategy is going to be, and what the expected outcome is going to be we need to think about how we can get our move based on the expected outcome.

    So we need to calculate which move we need to make to get our desired outcome. So if the opponent chooses Rock, our move is going to be Paper. And based on that outcome we can then play the match to get the points as described in the previous table and the move points we’ve been given:

    the score for the shape you selected […] 1 for Rock, 2 for Paper, and 3 for Scissors

    So with the given sample data, and the conversion tables we get the following diagram.

    flowchart LR
    
      subgraph matchone["Match one"]
        direction LR
    
        subgraph m1Strategy["Calculate strategy"]
            direction LR
    
          m1opponent["opponent move: <b>Rock</b>"]
          m1Outcome["Expected outcome <b>Draw</b>"]
          m1GetMove["GetMove()"]
          m1OwnMove["Own move: <b>Rock</b>"]
    
          m1opponent --> m1GetMove
          m1Outcome --> m1GetMove
          m1GetMove --> m1OwnMove
        end
    
        m1PlayMatch["PlayMatch()"]
        m1Points["3 points"]
        m1TotalPoints["Total <b>4 points</b>"]
    
        m1opponent --> m1PlayMatch
        m1OwnMove --> m1PlayMatch
        m1PlayMatch --> m1Points
        m1Points -- "Add 1 strategy point" --> m1TotalPoints
      end
    
      subgraph matchtwo["Match two"]
        direction LR
    
        subgraph m2Strategy["Calculate strategy"]
            direction LR
    
          m2opponent["opponent move: <b>Paper</b>"]
          m2Outcome["Expected outcome <b>Lost</b>"]
          m2GetMove["GetMove()"]
          m2OwnMove["Own move: <b>Rock</b>"]
    
          m2opponent --> m2GetMove
          m2Outcome --> m2GetMove
          m2GetMove --> m2OwnMove
        end
    
        m2PlayMatch["PlayMatch()"]
        m2Points["0 points"]
        m2TotalPoints["Total <b>1 points</b>"]
    
        m2opponent --> m2PlayMatch
        m2OwnMove --> m2PlayMatch
        m2PlayMatch --> m2Points
        m2Points -- "Add 1 strategy point" --> m2TotalPoints
      end
    
      subgraph matchthree["Match three"]
        direction LR
    
        subgraph m3Strategy["Calculate strategy"]
            direction LR
    
          m3opponent["opponent move: <b>Scissors</b>"]
          m3Outcome["Expected outcome <b>Won</b>"]
          m3GetMove["GetMove()"]
          m3OwnMove["Own move: <b>Rock</b>"]
    
          m3opponent --> m3GetMove
          m3Outcome --> m3GetMove
          m3GetMove --> m3OwnMove
        end
    
        m3PlayMatch["PlayMatch()"]
        m3Points["6 points"]
        m3TotalPoints["Total <b>7 points</b>"]
    
        m3opponent --> m3PlayMatch
        m3OwnMove --> m3PlayMatch
        m3PlayMatch --> m3Points
        m3Points -- "Add 1 strategy point" --> m3TotalPoints
      end
    
      sum["Sum()"]
      total["Total: <b>12 points</b>"]
    
      m1TotalPoints --> sum
      m2TotalPoints --> sum
      m3TotalPoints --> sum
      sum --> total
    

    Once all the matches have been played, and the points calculated we can add all outcomes togheter for our end result.

    Implementation

    Business logic

    Now we know what we want our code to do, let’s start implementing it in our PartOne class.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
    class PartTwo(
        private val sanitizer: Sanitizer
    ) {
        fun getResult(): Int {
            val data = sanitizer.getItems()
            val points = data?.map {
                val strategyPoints = when(getMove(it.first, it.second)) {
                    "A" -> 1    // Rock
                    "B" -> 2    // Paper
                    else -> 3   // Scissors
                }
                val roundOutcome = when(it.second) {
                    "X" -> 0    // Lost
                    "Y" -> 3    // Draw
                    else -> 6   // Won
                }
    
                strategyPoints + roundOutcome
            }
    
            val totalRoundsOutcome = points?.sum()
    
            return totalRoundsOutcome ?: -1;
        }
    
        /**
         * Get the expected move based on the expected outcome and the opponents move
         *
         * @param opponentMove the move the opponent is going to make
         * @param expectedOutcome the outcome that is expected
         * @return our move
         */
        private fun getMove(opponentMove: String, expectedOutcome: String): String =
            when(expectedOutcome) {
                "X" -> when(opponentMove) { // Lost
                    "A" -> "C"  // Opponent: Rock, own: Scissors
                    "B" -> "A"  // Opponent: Paper, own: Rock
                    else -> "B" // Opponent: Scissors, own: Paper
                } // Won
                "Y" -> opponentMove // Draw
                else -> when(opponentMove) { // Won
                    "A" -> "B"  // Opponent: Rock, own: Paper
                    "B" -> "C"  // Opponent: Paper, own: Scissors
                    else -> "A" // Opponent: Scissors, own: Rock
                }
            }
    }
    

    Test case

    Because we know that we have a list of all round outcomes, we know that we can sum each item in the list to get the total score. As you can see in our previous diagram, the total score of the sample input will be 12.

    So we can write a test case that validates our test input to the outcome of 12. Right now we can update the PartTwoTest class with the following contents.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    class PartTwoTest {
        @Test
        fun testGetResult() {
            // Arrange
            val resource = {}::class.java.getResource("/input.txt")
            val sanitizer = Sanitizer(resource)
            val sut = PartTwo(sanitizer)
            val expectedNumberOfPoints = 12
    
            // Act
            val result = sut.getResult()
    
            // Assert
            assertEquals(expectedNumberOfPoints, result)
        }
    }
    

    Categories

    Related articles

    Advent of Code 2022, Day two - Sanitizer

    Turn the input into a list of rock-paper-scissor games.

    Advent of Code 2022, Day two - Part One

    Get the outcome of each rock-paper-scissors match.