2022 Day 2

Posted on Dec 2, 2022

Part 1

Problem

Each line in the file represents a round of Rock Paper Scissors, with the two hands separated by whitespace. Calculate the total score based on the result of each round and the response by your player.

General solution

Create a nested data structure which is a list of rounds and populate this from the file, calculating the score for each round at the same time. Sum all the scores to find the total.

Solution: Go

To make the code readable, we create an enum for the three hand types: Rock, Paper, Scissors. The values of these do not matter as we will only ever compare them for equality. Using iota for the first element means that we do not have to manually assign values.

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	const (
		Rock = iota
		Paper
		Scissors
	)

	type Round struct {
		elfHand     int
		playerHand  int
		playerScore int
	}

	rounds := []Round{}

	scanner := bufio.NewScanner(os.Stdin)

	// Read in the data and convert into structure
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		hands := strings.Fields(line)
		elfHandString := hands[0]
		playerHandString := hands[1]

		round := Round{}

		if elfHandString == "A" {
			round.elfHand = Rock
		} else if elfHandString == "B" {
			round.elfHand = Paper
		} else if elfHandString == "C" {
			round.elfHand = Scissors
		}

		if playerHandString == "X" {
			round.playerHand = Rock
		} else if playerHandString == "Y" {
			round.playerHand = Paper
		} else if playerHandString == "Z" {
			round.playerHand = Scissors
		}

		round.playerScore = 0

		if round.playerHand == Rock {
			round.playerScore += 1
		} else if round.playerHand == Paper {
			round.playerScore += 2
		} else if round.playerHand == Scissors {
			round.playerScore += 3
		}

		if round.elfHand == round.playerHand {
			// Draw
			round.playerScore += 3
		} else if round.elfHand == Rock && round.playerHand == Paper {
			// Win: Paper wraps Rock
			round.playerScore += 6
		} else if round.elfHand == Scissors && round.playerHand == Rock {
			// Win: Rock breaks Scissors
			round.playerScore += 6
		} else if round.elfHand == Paper && round.playerHand == Scissors {
			// Win: Scissors cut Paper
			round.playerScore += 6
		}

		rounds = append(rounds, round)
	}

	// Calculate total score
	totalScore := 0

	for r := range rounds {
		totalScore += rounds[r].playerScore
	}

	fmt.Println(totalScore)
}

Part 2

Problem

Work out which hand to play to achieve the desired result (win, lose, or draw) rather than working out the result from the hand played.

General solution

The only change required is how we calculate the player hand - the rest of the code can remain unchanged.

Solution: Go

The main change from part 1 is that we create an array of highest calories (array is fine as the size is fixed) and initialise this with zeroes. We then check the calorie count of each group and replace the lowest of the highest calories if the group calorie count is greater, always sorting the highest calorie array to ensure it is in ascending order (with a small array this is unlikely to have performance issues).

This solution could be shortened by increasing the player score when the result is processed, rather than checking the two hands afterwards.

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	const (
		Rock = iota
		Paper
		Scissors
	)

	type Round struct {
		elfHand     int
		playerHand  int
		playerScore int
	}

	rounds := []Round{}

	scanner := bufio.NewScanner(os.Stdin)

	// Read in the data and convert into structure
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		hands := strings.Fields(line)
		elfHandString := hands[0]
		resultString := hands[1]

		round := Round{}

		if elfHandString == "A" {
			round.elfHand = Rock
		} else if elfHandString == "B" {
			round.elfHand = Paper
		} else if elfHandString == "C" {
			round.elfHand = Scissors
		}

		if resultString == "X" {
			// Need to lose
			if round.elfHand == Rock {
				round.playerHand = Scissors
			} else if round.elfHand == Scissors {
				round.playerHand = Paper
			} else if round.elfHand == Paper {
				round.playerHand = Rock
			}
		} else if resultString == "Y" {
			// Need to draw
			round.playerHand = round.elfHand
		} else if resultString == "Z" {
			// Need to win
			if round.elfHand == Rock {
				round.playerHand = Paper
			} else if round.elfHand == Scissors {
				round.playerHand = Rock
			} else if round.elfHand == Paper {
				round.playerHand = Scissors
			}
		}

		round.playerScore = 0

		if round.playerHand == Rock {
			round.playerScore += 1
		} else if round.playerHand == Paper {
			round.playerScore += 2
		} else if round.playerHand == Scissors {
			round.playerScore += 3
		}

		if round.elfHand == round.playerHand {
			// Draw
			round.playerScore += 3
		} else if round.elfHand == Rock && round.playerHand == Paper {
			// Win: Paper wraps Rock
			round.playerScore += 6
		} else if round.elfHand == Scissors && round.playerHand == Rock {
			// Win: Rock breaks Scissors
			round.playerScore += 6
		} else if round.elfHand == Paper && round.playerHand == Scissors {
			// Win: Scissors cut Paper
			round.playerScore += 6
		}

		rounds = append(rounds, round)
	}

	// Calculate total score
	totalScore := 0

	for r := range rounds {
		totalScore += rounds[r].playerScore
	}

	fmt.Println(totalScore)
}

Post-solution thoughts

Could the solution be improved to be generic, e.g. create an instance of a struct called Hand for each of the objects (Rock, Paper and Scissors) and have one of the members within the struct define which object this beats (and possibly is beaten by - this could be calculated but it is probably easier for lookups to have it in the struct). Effectively this ends up being like a double-linked list which circles back on itself.

If the solution was made generic, could this be extended to implement Rock Paper Scissors Lizard Spock?