Learning Python with Advent of Code Walkthroughs

Dazbo's Advent of Code solutions, written in Python

Rock Paper Scissors

Advent of Code 2022 - Day 2

Day 2: Calorie Counting

Useful Links

Concepts and Packages Demonstrated

Dict

Problem Intro

We’re told we’re going to play Rock, Paper, Scissors with the elves. But we’ve been given a piece of paper that tells us what our opponent will do, and what we should do!

Our input data contains many lines, with each line having two columns.

The input data looks something like this:

A Y
B X
C Z

For each hand we play, our score is the sum of the following:

Our total score is the sum of the scores for all the hands we play.

Part 1

Treat the second column as what we must play. X=Rock, Y=Paper, Z=Scissors

What is our final score?

To be honest, the code is so trivial it hardly needs explaining. Must of how it works is documented in the code itself.

Here it is…

from pathlib import Path
import time

HAND = {
    "ROCK": "A",
    "PAPER": "B",
    "SCISSORS": "C"
}

SCORES = {
    HAND["ROCK"]: 1,
    HAND["PAPER"]: 2,
    HAND["SCISSORS"]: 3,
    "LOSE": 0,
    "DRAW": 3,
    "WIN": 6
}

RULES = { 
    # (Their hand, my hand)
    (HAND["ROCK"], HAND["SCISSORS"]): SCORES["LOSE"],
    (HAND["ROCK"], HAND["ROCK"]): SCORES["DRAW"],
    (HAND["ROCK"], HAND["PAPER"]): SCORES["WIN"],
    (HAND["PAPER"], HAND["ROCK"]): SCORES["LOSE"],
    (HAND["PAPER"], HAND["PAPER"]): SCORES["DRAW"],
    (HAND["PAPER"], HAND["SCISSORS"]): SCORES["WIN"],
    (HAND["SCISSORS"], HAND["PAPER"]): SCORES["LOSE"],
    (HAND["SCISSORS"], HAND["SCISSORS"]): SCORES["DRAW"],
    (HAND["SCISSORS"], HAND["ROCK"]): SCORES["WIN"]
}

SCRIPT_DIR = Path(__file__).parent
INPUT_FILE = Path(SCRIPT_DIR, "input/input.txt")

def main():
    with open(INPUT_FILE, mode="rt") as f:
        data = f.read().splitlines()
    
    # Part 1
    rounds = play_rounds(data)
    scores = play_part_1(rounds)
    print(f"Part 1: Your score = {sum(scores)}")
    
def play_part_1(rounds: list):
    """ Here we treat X, Y, Z as whether we play Rock, Paper, Scissors, respectively. 
    Calculate our score for each hand based on what hand we play, and whether we win, lose or draw. """
    scores = []
    zyz_to_rps = {"X": HAND["ROCK"], "Y": HAND["PAPER"], "Z": HAND["SCISSORS"]}
    for this_round in rounds:
        their_hand, my_hand = this_round
        my_hand = zyz_to_rps[my_hand]
        
        this_score = 0
        this_score += SCORES[my_hand]
        this_score += RULES[(their_hand, my_hand)]    
        scores.append(this_score)
    
    return scores             

def play_rounds(data: list[str]) -> list:
    """ Returns a list. Each element in the list is a pair as a list, e.g. ["A", "X"] """
    rounds = []
    for this_round in data:
        rounds.append(this_round.split())
    
    return rounds

if __name__ == "__main__":
    t1 = time.perf_counter()
    main()
    t2 = time.perf_counter()
    print(f"Execution time: {t2 - t1:0.4f} seconds")

Part 2

Now, we must treat the second column as the outcome required. X=Lose, Y=Draw, Z=Win

What is our final score?

Okay, so we need to extend our rules…

REQUIRED = {
    "LOSE": "X",
    "DRAW": "Y",
    "WIN": "Z"
}

RULES = { 
    # (Their hand, my hand): score
    (HAND["ROCK"], HAND["SCISSORS"]): SCORES["LOSE"],
    (HAND["ROCK"], HAND["ROCK"]): SCORES["DRAW"],
    (HAND["ROCK"], HAND["PAPER"]): SCORES["WIN"],
    (HAND["PAPER"], HAND["ROCK"]): SCORES["LOSE"],
    (HAND["PAPER"], HAND["PAPER"]): SCORES["DRAW"],
    (HAND["PAPER"], HAND["SCISSORS"]): SCORES["WIN"],
    (HAND["SCISSORS"], HAND["PAPER"]): SCORES["LOSE"],
    (HAND["SCISSORS"], HAND["SCISSORS"]): SCORES["DRAW"],
    (HAND["SCISSORS"], HAND["ROCK"]): SCORES["WIN"],
    
    # (Their hand, my goal): score-for-my-hand
    (HAND["ROCK"], REQUIRED["LOSE"]): SCORES[HAND["SCISSORS"]],
    (HAND["ROCK"], REQUIRED["DRAW"]): SCORES[HAND["ROCK"]],
    (HAND["ROCK"], REQUIRED["WIN"]): SCORES[HAND["PAPER"]],
    (HAND["PAPER"], REQUIRED["LOSE"]): SCORES[HAND["ROCK"]],
    (HAND["PAPER"], REQUIRED["DRAW"]): SCORES[HAND["PAPER"]],
    (HAND["PAPER"], REQUIRED["WIN"]): SCORES[HAND["SCISSORS"]],
    (HAND["SCISSORS"], REQUIRED["LOSE"]): SCORES[HAND["PAPER"]],
    (HAND["SCISSORS"], REQUIRED["DRAW"]): SCORES[HAND["SCISSORS"]],
    (HAND["SCISSORS"], REQUIRED["WIN"]): SCORES[HAND["ROCK"]] 
}

And then we just add a new function to apply the new rules:

def play_part_2(rounds: list):
    """ Here we treat X, Y, Z as whether we play Lose, Draw, or Win, respectively. 
    Determine what hand we should play to achieve that outcome.
    Then calculate our score for each hand based on what hand we chose, and whether we win, lose or draw. """
    scores = []
    xyz_to_ldw = {"X": SCORES["LOSE"], "Y": SCORES["DRAW"], "Z": SCORES["WIN"]}
    for this_round in rounds:
        their_hand, my_goal = this_round
        this_score = 0
        this_score += xyz_to_ldw[my_goal]
        this_score += RULES[(their_hand, my_goal)]        
        scores.append(this_score)
        
    return scores  

And then add this to our main method:

    scores = play_part_2(rounds)
    print(f"Part 2: Your score = {sum(scores)}")

Results

The output looks like this:

Part 1: Your score = 13221
Part 2: Your score = 13131
Execution time: 0.0017 seconds