mirror of
https://github.com/RussellChamp/advent-of-code-2021.git
synced 2026-10-27 21:52:32 +00:00
day 11
This commit is contained in:
1 parent
41c5e86e7c
commit
44923a1ed4
3 files changed
+236
No files matched your search
@@ -0,0 +1,4 @@
|
||||
# TODO
|
||||
* Write more unit tests
|
||||
* Try out clipperhouse/gen library
|
||||
* Try out urfave/cli library
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/* Day 11: Dumbo Octopus */
|
||||
package main
|
||||
|
||||
import (
|
||||
"AoC2021/utils/arrays"
|
||||
"AoC2021/utils/log"
|
||||
"AoC2021/utils/timer"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const WIDTH = 10
|
||||
const HEIGHT = 10
|
||||
const ALREADY_FLASHED = -1
|
||||
const STEPS = 100
|
||||
|
||||
type Grid = [][]int
|
||||
|
||||
func check(e error) {
|
||||
if e != nil {
|
||||
log.Println(log.DEBUG, "Oh snap!")
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.Println(log.NORMAL, "--- Day 11: Dumbo Octopus ---")
|
||||
timer.Start()
|
||||
part1()
|
||||
timer.Tick()
|
||||
log.Println(log.NORMAL)
|
||||
|
||||
part2()
|
||||
timer.Tick()
|
||||
log.Println(log.NORMAL)
|
||||
}
|
||||
|
||||
func part1() {
|
||||
log.Println(log.NORMAL, "* Part 1 *")
|
||||
log.Println(log.NORMAL, " Goal: Given the starting energy levels of the dumbo octopuses in your cavern, simulate 100 steps.")
|
||||
log.Println(log.NORMAL, " Answer: How many total flashes are there after 100 steps?")
|
||||
|
||||
grid := loadGrid()
|
||||
flashCount := 0
|
||||
|
||||
for step := 0; step < STEPS; step++ {
|
||||
newFlashes := stepSimulation(&grid)
|
||||
log.Printf(log.DEBUG, "Step %d: Saw %d new flashes\n", step, newFlashes)
|
||||
flashCount += newFlashes
|
||||
}
|
||||
|
||||
log.Printf(log.NORMAL, "After %d steps, the total number of flashes was %d\n", STEPS, flashCount)
|
||||
log.Printf(log.DEBUG, " and the grid looked like: \n")
|
||||
printGrid(grid)
|
||||
}
|
||||
|
||||
func part2() {
|
||||
log.Println(log.NORMAL, "* Part 2 *")
|
||||
log.Println(log.NORMAL, " Goal: If you can calculate the exact moments when the octopuses will all flash simultaneously, you should be able to navigate through the cavern.")
|
||||
log.Println(log.NORMAL, " Answer: What is the first step during which all octopuses flash?")
|
||||
|
||||
grid := loadGrid()
|
||||
step := 0
|
||||
newFlashes := 0
|
||||
|
||||
for ; newFlashes != HEIGHT*WIDTH; step++ {
|
||||
newFlashes = stepSimulation(&grid)
|
||||
log.Printf(log.DEBUG, "Step %d: Saw %d new flashes\n", step, newFlashes)
|
||||
}
|
||||
|
||||
log.Printf(log.NORMAL, "After %d steps, we saw all %d nodes flash\n", step, newFlashes)
|
||||
log.Printf(log.DEBUG, " and the grid looked like: \n")
|
||||
printGrid(grid)
|
||||
}
|
||||
|
||||
func loadGrid() Grid {
|
||||
input, err := os.Open("./input.txt")
|
||||
check(err)
|
||||
defer input.Close()
|
||||
|
||||
scanner := bufio.NewScanner(input)
|
||||
lines := 0
|
||||
|
||||
var grid Grid
|
||||
|
||||
for scanner.Scan() {
|
||||
if len(grid) > HEIGHT {
|
||||
panic(fmt.Sprintf("Error: Tried to read more than %d lines from input", HEIGHT))
|
||||
}
|
||||
line := arrays.MapStrToInt(strings.Split(scanner.Text(), ""), myAtoi)
|
||||
|
||||
if len(line) != WIDTH {
|
||||
panic(fmt.Sprintf("Error: Read line with invalid width: %d", len(line)))
|
||||
}
|
||||
grid = append(grid, line)
|
||||
lines++
|
||||
}
|
||||
if len(grid) != HEIGHT {
|
||||
panic(fmt.Sprintf("Error: Input was invalid length. Expected %d row but got %d", HEIGHT, len(grid)))
|
||||
}
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
func myAtoi(s string) int {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error: Attempted to parse invalid string to Atoi: '%s'", s))
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func printGrid(grid Grid) {
|
||||
for _, row := range grid {
|
||||
log.Println(log.DEBUG, row)
|
||||
}
|
||||
}
|
||||
|
||||
// update the grid after a step and return the number of flashes that happened
|
||||
func stepSimulation(grid *Grid) int {
|
||||
flashCount := 0
|
||||
|
||||
// increase ALL energy levels by 1
|
||||
incrementEnergy(grid)
|
||||
|
||||
for {
|
||||
// while nodes are still flashing, keep going
|
||||
flashes := flashFullNodesSinglePass(grid)
|
||||
if flashes == 0 {
|
||||
break
|
||||
}
|
||||
flashCount += flashes
|
||||
}
|
||||
|
||||
// reset all nodes marked as -1 to 0
|
||||
resetTiredEnergy(grid)
|
||||
|
||||
return flashCount
|
||||
}
|
||||
|
||||
func incrementEnergy(grid *Grid) {
|
||||
for rIdx := 0; rIdx < HEIGHT; rIdx++ {
|
||||
for cIdx := 0; cIdx < WIDTH; cIdx++ {
|
||||
(*grid)[rIdx][cIdx] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flashes nodes that are at full energy, updates their neighbors, and returns the total number of nodes flashed
|
||||
// does a SINGLE pass through the grid
|
||||
func flashFullNodesSinglePass(grid *Grid) int {
|
||||
flashCount := 0
|
||||
|
||||
for rIdx := 0; rIdx < HEIGHT; rIdx++ {
|
||||
for cIdx := 0; cIdx < WIDTH; cIdx++ {
|
||||
// an update can spiral from a single node
|
||||
// check how many flashed form this single node
|
||||
flashCount += checkNode(grid, rIdx, cIdx, 0)
|
||||
}
|
||||
}
|
||||
return flashCount
|
||||
}
|
||||
|
||||
// check if a node should flash and then update it's neighbors
|
||||
// if we're checking a node as a result of it's neighbor flashing, we also increment the energy
|
||||
func checkNode(grid *Grid, row, col, bonusEnergy int) int {
|
||||
if !isNodeValid(grid, row, col) {
|
||||
return 0
|
||||
}
|
||||
if bonusEnergy > 0 {
|
||||
(*grid)[row][col] += bonusEnergy
|
||||
}
|
||||
if (*grid)[row][col] < 10 {
|
||||
return 0
|
||||
}
|
||||
|
||||
flashes := 1
|
||||
|
||||
// set the node to -1 to represent that it has flashed this step
|
||||
(*grid)[row][col] = ALREADY_FLASHED
|
||||
|
||||
// increment all eight valid neighbors by 1 and check if they flash
|
||||
/* UP */
|
||||
flashes += checkNode(grid, row-1, col, 1)
|
||||
/* DOWN */ flashes += checkNode(grid, row+1, col, 1)
|
||||
/* LEFT */ flashes += checkNode(grid, row, col-1, 1)
|
||||
/* RIGHT */ flashes += checkNode(grid, row, col+1, 1)
|
||||
/* UP-LEFT */ flashes += checkNode(grid, row-1, col-1, 1)
|
||||
/* UP-RIGHT */ flashes += checkNode(grid, row-1, col+1, 1)
|
||||
/* DOWN-LEFT */ flashes += checkNode(grid, row+1, col-1, 1)
|
||||
/* DOWN-RIGHT */ flashes += checkNode(grid, row+1, col+1, 1)
|
||||
|
||||
return flashes
|
||||
}
|
||||
|
||||
// check if a node is valid
|
||||
func isNodeValid(grid *Grid, row, col int) bool {
|
||||
if row < 0 || col < 0 || row >= HEIGHT || col >= WIDTH {
|
||||
return false
|
||||
}
|
||||
if (*grid)[row][col] == ALREADY_FLASHED {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// nodes that have flashed this step are set to -1 so that they aren't accidentally incremented anymore
|
||||
// reset that value back to 0 at the end of the step
|
||||
func resetTiredEnergy(grid *Grid) {
|
||||
for rIdx := 0; rIdx < HEIGHT; rIdx++ {
|
||||
for cIdx := 0; cIdx < WIDTH; cIdx++ {
|
||||
if (*grid)[rIdx][cIdx] == ALREADY_FLASHED {
|
||||
(*grid)[rIdx][cIdx] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AoC2021/utils/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDay11(t *testing.T) {
|
||||
assert.IsTrue(t, true, "cool")
|
||||
assert.EqualsStr(t, "foo", "bar", "baz")
|
||||
}
|
||||
Reference in new issue
Block a user