Home Advent of Code 2022, Day 2 - Part Two
Post
Cancel

Advent of Code 2022, Day 2 - Part Two

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

RockPaperScissors
ABC

Conversion table for expected match outcomes

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

LostDrawWon
XYZ

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 points3 points6 points
Rock - PaperRock - RockRock - Scissors
Paper - ScissorsPaper - PaperPaper - Rock
Scissors - RockScissors - ScissorsScissors - 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 = PartTwoTest::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)
    }
}
This post is licensed under CC BY 4.0 by the author.