Wait For It

Advent of Code 2023 [Day 6]

06-12-2023

The ferry quickly brings you across Island Island. After asking around, you discover that there is indeed normally a large pile of sand somewhere near here, but you don’t see anything besides lots of water and the small island where the ferry has docked.

As you try to figure out what to do next, you notice a poster on a wall near the ferry dock. “Boat races! Open to the public! Grand prize is an all-expenses-paid trip to Desert Island!” That must be where the sand comes from! Best of all, the boat races are starting in just a few minutes.

You manage to sign up as a competitor in the boat races just in time. The organizer explains that it’s not really a traditional race - instead, you will get a fixed amount of time during which your boat has to travel as far as it can, and you win if your boat goes the farthest.

As part of signing up, you get a sheet of paper (your puzzle input) that lists the time allowed for each race and also the best distance ever recorded in that race. To guarantee you win the grand prize, you need to make sure you go farther in each race than the current record holder.

The organizer brings you over to the area where the boat races are held. The boats are much smaller than you expected - they’re actually toy boats, each with a big button on top. Holding down the button charges the boat, and releasing the button allows the boat to move. Boats move faster if their button was held longer, but time spent holding the button counts against the total race time. You can only hold the button at the start of the race, and boats don’t move until the button is released.

For example:

Time:      7  15   30
Distance:  9  40  200

This document describes three races:

Your toy boat has a starting speed of zero millimeters per millisecond. For each whole millisecond you spend at the beginning of the race holding down the button, the boat’s speed increases by one millimeter per millisecond.

So, because the first race lasts 7 milliseconds, you only have a few options:

Since the current record for this race is 9 millimeters, there are actually 4 different ways you could win: you could hold the button for 2, 3, 4, or 5 milliseconds at the start of the race.

In the second race, you could hold the button for at least 4 milliseconds and at most 11 milliseconds and beat the record, a total of 8 different ways to win.

In the third race, you could hold the button for at least 11 milliseconds and no more than 19 milliseconds and still beat the record, a total of 9 ways you could win.

To see how much margin of error you have, determine the number of ways you can beat the record in each race; in this example, if you multiply these values together, you get 288 (4 * 8 * 9).

Determine the number of ways you could beat the record in each race. What do you get if you multiply these numbers together?

type Boat struct {
	Starting uint
	Increase uint
}

func (b *Boat) GetDistance(delay uint, remaining uint) uint {
	return b.Starting + b.Increase*delay*remaining
}

type Race struct {
	Boat
	Time     uint
	Distance uint
}

func (r *Race) GetWinningTimes() (counter uint) {
	for i := uint(0); i < r.Time; i++ {
		if r.GetDistance(i, r.Time-i) > r.Distance {
			counter++
		}
	}
	return
}

func TestRace_GetWinningTimes(t *testing.T) {
	tests := []struct {
		name string
		Race wait.Race
		want uint
	}{
		{
			name: "Test 1",
			Race: wait.Race{
				Boat: wait.Boat{
					Starting: 0,
					Increase: 1,
				},
				Time:     7,
				Distance: 9,
			},
			want: 4,
		},
		{
			name: "Test 2",
			Race: wait.Race{
				Boat: wait.Boat{
					Starting: 0,
					Increase: 1,
				},
				Time:     15,
				Distance: 40,
			},
			want: 8,
		},
		{
			name: "Test 3",
			Race: wait.Race{
				Boat: wait.Boat{
					Starting: 0,
					Increase: 1,
				},
				Time:     30,
				Distance: 200,
			},
			want: 9,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.Race.GetWinningTimes(); got != tt.want {
				t.Errorf("Race.GetWinningMultiplier() = %v, want %v", got, tt.want)
			}
		})
	}
}

As the race is about to start, you realize the piece of paper with race times and record distances you got earlier actually just has very bad kerning. There’s really only one race - ignore the spaces between the numbers on each line.

So, the example from before:

Time:      7  15   30
Distance:  9  40  200

…now instead means this:

Time:      71530
Distance:  940200

Now, you have to figure out how many ways there are to win this single race. In this example, the race lasts for 71530 milliseconds and the record distance you need to beat is 940200 millimeters. You could hold the button anywhere from 14 to 71516 milliseconds and beat the record, a total of 71503 ways!

How many ways can you beat the record in this one much longer race?

func main() {
	file, err := os.Open("input.txt")
	if err != nil {
		log.Println(err.Error())
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)

	var (
		time      []uint      = []uint{}
		distance  []uint      = []uint{}
		races     []wait.Race = []wait.Race{}
		pointer               = &time
		giantRace wait.Race   = wait.Race{
			Boat: wait.Boat{
				Starting: 0,
				Increase: 1,
			},
		}
		times     []rune = []rune{}
		distances []rune = []rune{}
	)

	for scanner.Scan() {
		for _, value := range strings.Split(strings.Split(scanner.Text(), ":")[1], " ") {
			if number, err := strconv.Atoi(value); err == nil {
				*pointer = append(*pointer, uint(number))
			}
		}
		pointer = &distance
	}

	if err := scanner.Err(); err != nil {
		log.Println(err.Error())
	}

	if len(time) != len(distance) {
		log.Fatalln("len(time) != len(distance)")
	}

	for i := 0; i < len(time); i++ {
		races = append(races, wait.Race{
			Boat: wait.Boat{
				Starting: 0,
				Increase: 1,
			},
			Time:     time[i],
			Distance: distance[i],
		})

		times = append(times, []rune(strconv.Itoa(int(time[i])))...)
		distances = append(distances, []rune(strconv.Itoa(int(distance[i])))...)
	}

	mul1 := uint(1)
	for _, race := range races {
		mul1 *= race.GetWinningTimes()
	}

	timeValue, _ := strconv.Atoi(string(times))
	giantRace.Time = uint(timeValue)
	distanceValue, _ := strconv.Atoi(string(distances))
	giantRace.Distance = uint(distanceValue)
	tim2 := giantRace.GetWinningTimes()

	log.Printf("sum: %d, %d\n", mul1, tim2)
}

If you’re new to Advent of Code, it’s an annual event that takes place throughout December, featuring a series of programming puzzles that get progressively more challenging as Christmas approaches.

Click to show the input
Time:        34     90     89     86
Distance:   204   1713   1210   1780