Advent Of Code - Day 11

Today was an easier one again. It’s been a while since I last did some recursion anyway. At least, that’s how I solved this. I’m pretty sure my solution isn’t the most efficient one, but at least it’s working and it’s not horrible.

Day 11 - Part 1

We have some number tracking to do again. This time, instead of fishes, we find ourself an bioluminescent octopus! This time, the rules are neatly outlined to us in a straight forward list. It sort of is like a Conway’s Game of Life scenario in which the current point affects its neighbours.

Remember last time we did directions? Well guess what’s coming to come in handy again. Our directions, but with diagonal directions.

var (
	directions = []point{
		{x: 0, y: -1},
		{x: -1, y: -1},
		{x: -1, y: 0},
		{x: 1, y: 1},
		{x: 0, y: 1},
		{x: -1, y: 1},
		{x: 1, y: 0},
		{x: 1, y: -1},
	}
)

What the plan here? I’ve drawn it our on paper. It’s pretty easy actually. We have to track if an octopus already flashed or not. For that, we’ll use a struct which will hold something like flashed bool value. We’ll have to think about how to reset that after a step is done though.

Next step is, go through the list and if they didn’t flash in that round ( aka !flashed ) then we increase its energy. If the energy is above 9 ( aka. == 10 ), we flash. What is flash? It’s a recursive function. When dealing with recursive functions you have to determine two things.

  • What is my exit criteria?
  • What is my base case? ( aka. the recurring logic )

What is our exit? When there are no more neighbours. What is our base case? Find all neighbours for a given point which did not flash yet and increase their energy. If they are energy reaches 10, flash. That’s where our recurring call will happen. If a neighbour reaches energy level 10 we’ll call flash with that neighbour’s coordinate.

Alright, that isn’t so bad. Did we forget something? Yes… The reset. Unfortunately, this also means that those which did flash, we’ll have to manually go and reset after everything has been done. If we reset them mid logic, then they might flash again, or might get their energy levels increased. Which is not what we want. Ah well.. I’ll figure something out later maybe.

Okay, let’s see some code. Our initial thing is to loop through the steps and increase the energy level.

	for i := 0; i < steps; i++ {
		for y := 0; y < len(grid); y++ {
			for x := 0; x < len(grid[y]); x++ {
				if !grid[y][x].flashed {
					grid[y][x].energy++
					if grid[y][x].energy == 10 {
						flash(point{x: x, y: y}, i, grid)
					}
				}
			}
		}
		// reset flashed
        // unfortunate extra loop...
		for y := 0; y < len(grid); y++ {
			for x := 0; x < len(grid[y]); x++ {
				if grid[y][x].flashed {
					grid[y][x].flashed = false
				}
			}
		}
	}

Now, flash…

func flash(currentPoint point, currentStep int, grid [10][10]*octopus) {
	// flash the current octopus
	flashCount++
	grid[currentPoint.y][currentPoint.x].energy = 0
	grid[currentPoint.y][currentPoint.x].flashed = true
	// select neighbors and increase their energy
	for _, d := range directions {
		np := point{x: currentPoint.x + d.x, y: currentPoint.y + d.y}
		if np.x >= 0 && np.x < len(grid[currentPoint.y]) && np.y >= 0 && np.y < len(grid) {
			if !grid[np.y][np.x].flashed {
				grid[np.y][np.x].energy++
				if grid[np.y][np.x].energy == 10 {
					flash(np, currentStep, grid)
				}
			}
		}
	}
}

So, what’s going on in here? Once an octopus flashes, we set its energy to 0 and we set flashed to true. This is important, because otherwise it would flash again in the same step. Which is not allowed. Then, we check its neighbors and increase their energy. And if that reaches 10, we flash… And that’s it! We do some counting to get how many times we flashed and done!

Day 10 - Part 2

Part 2 is just let it run and see when every one of these is 0. Pretty simple, I didn’t have to change much, just remove the counter for the for loop and add in a check in our reset loop which was already there anyways.

		allZeros := true
		for y := 0; y < len(grid); y++ {
			for x := 0; x < len(grid[y]); x++ {
				if grid[y][x].flashed {
					grid[y][x].flashed = false
				}
				if grid[y][x].energy != 0 {
					allZeros = false
				}
			}
		}
		if allZeros {
			// +1 because steps begin from 0.
			fmt.Println("sync achieved at: ", i+1)
			display(grid)
			break
		}

And with that, part 2 is done!

Conclusion

Today we had a little fun with recursion. Which is nice, because I didn’t had to write recursion in a while.

The repository for all my solutions for AOC 2021 can be found here.

Thank you for reading!

Gergely.