2022-12-02 14:08:51 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import dataclasses
|
|
|
|
import enum
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
def main(filename: str, expected_part_1: int = None, expected_part_2: int = None):
|
|
|
|
print(f"\n+ Running on {filename}")
|
|
|
|
with open(filename) as f:
|
|
|
|
data = f.read().strip().split("\n")
|
|
|
|
|
2022-12-02 14:18:43 +01:00
|
|
|
parsed_data = parse_data_part_1(data)
|
|
|
|
solution_part_1 = solve_part_1(parsed_data)
|
2022-12-02 14:08:51 +01:00
|
|
|
|
|
|
|
print(f"1. Found {solution_part_1}")
|
|
|
|
if expected_part_1:
|
|
|
|
assert expected_part_1 == solution_part_1
|
|
|
|
|
2022-12-02 14:18:43 +01:00
|
|
|
parsed_data = parse_data_part_2(data)
|
|
|
|
solution_part_2 = solve_part_2(parsed_data)
|
2022-12-02 14:08:51 +01:00
|
|
|
print(f"2. Found {solution_part_2}")
|
|
|
|
if expected_part_2:
|
|
|
|
assert expected_part_2 == solution_part_2
|
|
|
|
|
|
|
|
|
|
|
|
class Shape(enum.IntEnum):
|
|
|
|
ROCK = 1
|
|
|
|
PAPER = 2
|
|
|
|
SCISSORS = 3
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_input(cls, letter: str) -> Shape:
|
|
|
|
return {
|
|
|
|
"A": cls.ROCK,
|
|
|
|
"B": cls.PAPER,
|
|
|
|
"C": cls.SCISSORS,
|
|
|
|
"X": cls.ROCK,
|
|
|
|
"Y": cls.PAPER,
|
|
|
|
"Z": cls.SCISSORS,
|
|
|
|
}[letter]
|
|
|
|
|
2022-12-02 14:18:43 +01:00
|
|
|
def winner_for_opponent(self) -> Shape:
|
|
|
|
return {
|
|
|
|
self.SCISSORS: self.ROCK,
|
|
|
|
self.ROCK: self.PAPER,
|
|
|
|
self.PAPER: self.SCISSORS,
|
|
|
|
}[self]
|
|
|
|
|
|
|
|
def loser_for_opponent(self) -> Shape:
|
|
|
|
return {
|
|
|
|
self.ROCK: self.SCISSORS,
|
|
|
|
self.PAPER: self.ROCK,
|
|
|
|
self.SCISSORS: self.PAPER,
|
|
|
|
}[self]
|
|
|
|
|
2022-12-02 14:08:51 +01:00
|
|
|
|
|
|
|
@dataclasses.dataclass
|
|
|
|
class Round:
|
|
|
|
opponent: Shape
|
2022-12-02 14:18:43 +01:00
|
|
|
me: Shape | None = None
|
2022-12-02 14:08:51 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def value(self) -> int:
|
|
|
|
return self.me.value + self.score
|
|
|
|
|
|
|
|
@property
|
|
|
|
def score(self) -> int:
|
|
|
|
if self.victory:
|
|
|
|
return 6
|
|
|
|
elif self.draw:
|
|
|
|
return 3
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
@property
|
|
|
|
def victory(self) -> bool:
|
|
|
|
return (self.me.value - self.opponent.value % 3) == 1
|
|
|
|
|
|
|
|
@property
|
|
|
|
def draw(self) -> bool:
|
|
|
|
return self.opponent == self.me
|
|
|
|
|
2022-12-02 14:18:43 +01:00
|
|
|
def complete(self, instruction: str):
|
|
|
|
if instruction == "X":
|
|
|
|
self.me = self.opponent.loser_for_opponent()
|
|
|
|
elif instruction == "Y":
|
|
|
|
self.me = self.opponent
|
|
|
|
else:
|
|
|
|
self.me = self.opponent.winner_for_opponent()
|
|
|
|
|
2022-12-02 14:08:51 +01:00
|
|
|
|
|
|
|
DataType = list[Round]
|
|
|
|
|
|
|
|
|
2022-12-02 14:18:43 +01:00
|
|
|
def parse_data_part_1(data: list[str]) -> DataType:
|
2022-12-02 14:08:51 +01:00
|
|
|
rounds = []
|
|
|
|
for round in data: # noqa: A001
|
|
|
|
opponent, me = round.split(" ")
|
|
|
|
rounds.append(Round(Shape.from_input(opponent), Shape.from_input(me)))
|
|
|
|
return rounds
|
|
|
|
|
|
|
|
|
2022-12-02 14:18:43 +01:00
|
|
|
def parse_data_part_2(data: list[str]) -> DataType:
|
|
|
|
rounds = []
|
|
|
|
for round in data: # noqa: A001
|
|
|
|
opponent, instruction = round.split(" ")
|
|
|
|
r = Round(Shape.from_input(opponent))
|
|
|
|
r.complete(instruction)
|
|
|
|
rounds.append(r)
|
|
|
|
return rounds
|
|
|
|
|
|
|
|
|
2022-12-02 14:08:51 +01:00
|
|
|
def solve_part_1(data: DataType) -> int:
|
|
|
|
return sum([round.value for round in data]) # noqa: A001
|
|
|
|
|
|
|
|
|
|
|
|
def solve_part_2(data: DataType) -> int:
|
2022-12-02 14:18:43 +01:00
|
|
|
return sum([round.value for round in data]) # noqa: A001
|
2022-12-02 14:08:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"opponent,me",
|
|
|
|
[
|
|
|
|
(Shape.ROCK, Shape.PAPER),
|
|
|
|
(Shape.PAPER, Shape.SCISSORS),
|
|
|
|
(Shape.SCISSORS, Shape.ROCK),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_round_victory(opponent, me):
|
|
|
|
round = Round(opponent, me) # noqa: A001
|
|
|
|
assert round.victory
|
|
|
|
assert not round.draw
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"opponent,me",
|
|
|
|
[
|
|
|
|
(Shape.ROCK, Shape.ROCK),
|
|
|
|
(Shape.PAPER, Shape.PAPER),
|
|
|
|
(Shape.SCISSORS, Shape.SCISSORS),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_round_draw(opponent, me):
|
|
|
|
round = Round(opponent, me) # noqa: A001
|
|
|
|
assert not round.victory
|
|
|
|
assert round.draw
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"opponent,me",
|
|
|
|
[
|
|
|
|
(Shape.PAPER, Shape.ROCK),
|
|
|
|
(Shape.SCISSORS, Shape.PAPER),
|
|
|
|
(Shape.ROCK, Shape.SCISSORS),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_round_defeat(opponent, me):
|
|
|
|
round = Round(opponent, me) # noqa: A001
|
|
|
|
assert not round.victory
|
|
|
|
assert not round.draw
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2022-12-02 14:18:43 +01:00
|
|
|
main("inputs/day02-test1", expected_part_1=15, expected_part_2=12)
|
|
|
|
main("inputs/day02", expected_part_1=12156, expected_part_2=10835)
|