Files
advent-of-code-go/2023/day16/main.go
T
2024-08-01 11:06:05 -04:00

237 lines
4.5 KiB
Go

package main
import (
_ "embed"
"flag"
"fmt"
"strings"
"github.com/alexchao26/advent-of-code-go/util"
)
//go:embed input.txt
var input string
func init() {
// do this in init (not main) so test file has same input
input = strings.TrimRight(input, "\n")
if len(input) == 0 {
panic("empty input.txt file")
}
}
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
} else {
ans := part2(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
}
func part1(input string) int {
grid := parseInput(input)
return calcEnergizedTiles(grid, beamHead{
velocity: right,
row: 0,
col: 0,
})
}
func part2(input string) int {
grid := parseInput(input)
best := 0
for r := range len(grid) {
// starting from left of grid, headed right
startBeam := beamHead{
velocity: right,
row: r,
col: 0,
}
// max is a go 1.21 addition: https://pkg.go.dev/builtin#max
best = max(best, calcEnergizedTiles(grid, startBeam))
// starting from right of grid, headed left
startBeam = beamHead{
velocity: left,
row: r,
col: len(grid[0]) - 1,
}
best = max(best, calcEnergizedTiles(grid, startBeam))
}
for c := range len(grid[0]) {
// starting from top of grid, headed down
startBeam := beamHead{
velocity: down,
row: 0,
col: c,
}
best = max(best, calcEnergizedTiles(grid, startBeam))
// starting from bottom of grid, headed up
startBeam = beamHead{
velocity: up,
row: len(grid) - 1,
col: c,
}
best = max(best, calcEnergizedTiles(grid, startBeam))
}
return best
}
func calcEnergizedTiles(grid [][]string, beam beamHead) int {
// need to track multiple beams because they can split and generate multiple
beams := []beamHead{beam}
// [4]bool represents being hit from left, right, up and down respectively
// need to track direction so that we can terminate beams that are cyclical
hitGrid := [][][4]bool{}
for range grid {
hitGrid = append(hitGrid, make([][4]bool, len(grid[0])))
}
for len(beams) > 0 {
b := beams[0]
beams = beams[1:]
skip := false
for vel, hitGridIndex := range velToHitGridIndex {
if b.velocity == vel && hitGrid[b.row][b.col][hitGridIndex] {
skip = true
}
}
if skip {
continue
}
// record direction hit in hit grid
hitGrid[b.row][b.col][velToHitGridIndex[b.velocity]] = true
cell := grid[b.row][b.col]
nextVelocities, ok := mirrorToNextVelocities[cell][b.velocity]
if !ok {
panic("no nextVelocities found for cell type: " + cell)
}
for _, nextVelocity := range nextVelocities {
nextRow := b.row + nextVelocity[0]
nextCol := b.col + nextVelocity[1]
if nextRow < 0 || nextRow >= len(grid) ||
nextCol < 0 || nextCol >= len(grid[0]) {
continue
}
beams = append(beams, beamHead{
velocity: nextVelocity,
row: nextRow,
col: nextCol,
})
}
}
energizedTiles := 0
for r := range len(hitGrid) {
for c := range len(hitGrid[0]) {
for _, dir := range hitGrid[r][c] {
if dir {
energizedTiles++
break
}
}
}
}
return energizedTiles
}
var left = [2]int{0, -1}
var right = [2]int{0, 1}
var up = [2]int{-1, 0}
var down = [2]int{1, 0}
var velToHitGridIndex = map[[2]int]int{
left: 0,
right: 1,
up: 2,
down: 3,
}
var mirrorToNextVelocities = map[string]map[[2]int][][2]int{
".": {
left: {left},
right: {right},
up: {up},
down: {down},
},
"/": {
left: {down},
down: {left},
up: {right},
right: {up},
},
"\\": {
left: {up},
up: {left},
down: {right},
right: {down},
},
"|": {
left: {up, down},
right: {up, down},
up: {up},
down: {down},
},
"-": {
left: {left},
right: {right},
up: {left, right},
down: {left, right},
},
}
type beamHead struct {
velocity [2]int
row, col int
}
func (b beamHead) String() string {
return fmt.Sprintf("vel: %v, coords: %d, %d", b.velocity, b.row, b.col)
}
// for debugging
func condenseHitGrid(grid [][][4]bool) [][]int {
ans := [][]int{}
for range grid {
ans = append(ans, make([]int, len(grid[0])))
}
for r, row := range grid {
for c, sli := range row {
for _, val := range sli {
if val {
ans[r][c]++
}
}
}
}
return ans
}
func parseInput(input string) (ans [][]string) {
for _, line := range strings.Split(input, "\n") {
ans = append(ans, strings.Split(line, ""))
}
return ans
}