Advent-of-Code-2023/day16/floorWillBeLava.go

307 lines
6.7 KiB
Go

package day16
import (
"fmt"
"log"
"math"
"os"
"strings"
"sync"
)
func Run() int {
fmt.Println("hello from day 16")
log.Println("starting")
filename := "day16/example"
field := ReadField(filename)
startPoints := StartPoints(&field)
var startPointsWaitGroup sync.WaitGroup
startPointsWaitGroup.Add(len(startPoints))
results := make(chan int)
go func() {
startPointsWaitGroup.Wait()
close(results)
}()
for _, start := range startPoints {
go func(start MovementPoint) {
cleanField := ReadField(filename)
cleanField.StartTraversal(start)
thisResult := cleanField.CountEnergized()
results <- thisResult
startPointsWaitGroup.Done()
}(start)
}
max := math.MinInt
for energized := range results {
if energized > max {
max = energized
log.Println("found new max: ", max)
}
}
// fmt.Println(field.String())
// field.StartTraversal()
// fmt.Println(field.ShowEnergyzed())
return max
}
func StartPoints(f *Field) []MovementPoint {
result := make([]MovementPoint, 0)
for rowNum, row := range f.cells {
result = append(result,
MovementPoint{Row: rowNum, Col: 0, Direction: Rightward},
MovementPoint{Row: rowNum, Col: len(row) - 1, Direction: Leftward})
}
for colNum, _ := range f.cells[0] {
result = append(result,
MovementPoint{Row: 0, Col: colNum, Direction: Downward},
MovementPoint{Row: len(f.cells) - 1, Col: colNum, Direction: Upward})
}
return result
}
// have shared field
// running traversal recursive function per ray
// exit if going out of field or visiting cell that already had light in this direction
// (i.e encountering loop)
type CellType rune
const (
Empty CellType = '.'
SplitterNS = '|'
SplitterEW = '-'
MirrorBackslash = '\\'
MirrorSlash = '/'
)
type Direction int
const (
Upward Direction = iota
Downward
Leftward
Rightward
)
type Cell struct {
CellType CellType
KnownBeams map[Direction]any
}
type Field struct {
cells [][]*Cell
}
func (f *Field) isValid(mp MovementPoint) bool {
if mp.Row < 0 || mp.Col < 0 {
return false
}
if mp.Row >= len(f.cells) || len(f.cells) == 0 || mp.Col >= len(f.cells[0]) {
return false
}
return true
}
func ReadField(filename string) Field {
result := Field{}
bytes, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Sprint("cannot read file: ", filename))
}
text := string(bytes)
text = strings.TrimSpace(text)
for _, line := range strings.Split(text, "\n") {
rowCells := make([]*Cell, 0)
for _, symb := range line {
rowCells = append(rowCells, &Cell{
CellType: CellType(symb),
KnownBeams: make(map[Direction]any),
})
}
result.cells = append(result.cells, rowCells)
}
return result
}
func (f *Field) String() string {
result := "\n"
for _, row := range f.cells {
for _, cell := range row {
result += string(cell.CellType)
}
result += "\n"
}
return result
}
func (f *Field) ShowEnergyzed() string {
result := "\n"
for _, row := range f.cells {
for _, cell := range row {
if len(cell.KnownBeams) > 0 {
result += "#"
} else {
result += string(cell.CellType)
}
}
result += "\n"
}
return result
}
type MovementPoint struct {
Row, Col int
Direction Direction
}
func (f *Field) StartTraversal(startPoint MovementPoint) {
reportedVisits := make(chan MovementPoint)
var wg sync.WaitGroup
go f.RecordVisits(reportedVisits)
wg.Add(1)
go f.TraverseFrom(startPoint, reportedVisits, &wg)
wg.Wait()
close(reportedVisits)
}
func (f *Field) CountEnergized() (result int) {
for _, row := range f.cells {
for _, cell := range row {
if len(cell.KnownBeams) > 0 {
result += 1
}
}
}
return
}
func (f *Field) RecordVisits(reportedPoints <-chan MovementPoint) {
for point := range reportedPoints {
cell := f.cells[point.Row][point.Col]
log.Printf("recording visit %+v to %+v at row %d col %d\n", point, cell, point.Row, point.Col)
cell.KnownBeams[point.Direction] = struct{}{}
}
}
// starting at point, mark as visited
// move (concurrently if required) into next points
// ends - when out of the field, or if encountering a cycle
func (f *Field) TraverseFrom(current MovementPoint, reportVisits chan<- MovementPoint, wg *sync.WaitGroup) {
log.Printf("> starting traverse through %+v", current)
if !f.isValid(current) {
log.Println("invalid current ", current, " should be impossible")
wg.Done()
return
}
cell := f.cells[current.Row][current.Col]
_, knownDirection := cell.KnownBeams[current.Direction]
if knownDirection {
log.Printf("found cycle at %+v in %+v", current, cell)
wg.Done()
return
}
reportVisits <- current
nextPoints := NextPoints(f, current)
log.Printf("for current %+v next are: %+v\n", current, nextPoints)
switch len(nextPoints) {
case 0:
wg.Done()
return
case 1:
f.TraverseFrom(nextPoints[0], reportVisits, wg)
return
case 2:
wg.Add(1)
go f.TraverseFrom(nextPoints[0], reportVisits, wg)
f.TraverseFrom(nextPoints[1], reportVisits, wg)
return
}
}
func NextPoints(f *Field, current MovementPoint) []MovementPoint {
cell := f.cells[current.Row][current.Col]
nextDirections := cell.CellType.NextDirections(current.Direction)
nextCells := make([]MovementPoint, 0)
for _, direction := range nextDirections {
nextMovementPoint := current.ApplyDirection(direction)
if f.isValid(nextMovementPoint) {
nextCells = append(nextCells, nextMovementPoint)
}
}
return nextCells
}
// value receiver, can safely modify incoming mp
// doesn't know about Field dimentions
func (mp MovementPoint) ApplyDirection(d Direction) MovementPoint {
switch d {
case Upward:
mp.Row -= 1
case Downward:
mp.Row += 1
case Leftward:
mp.Col -= 1
case Rightward:
mp.Col += 1
}
mp.Direction = d
return mp
}
func (ct CellType) NextDirections(currentDirection Direction) (nextDirections []Direction) {
switch ct {
case Empty:
nextDirections = []Direction{currentDirection}
case SplitterNS:
if currentDirection == Rightward || currentDirection == Leftward {
nextDirections = []Direction{Upward, Downward}
} else {
nextDirections = []Direction{currentDirection}
}
case SplitterEW:
if currentDirection == Downward || currentDirection == Upward {
nextDirections = []Direction{Leftward, Rightward}
} else {
nextDirections = []Direction{currentDirection}
}
case MirrorBackslash:
// mirror symbol is \
directionMappings := map[Direction]Direction{
Leftward: Upward,
Rightward: Downward,
Upward: Leftward,
Downward: Rightward,
}
nextDirections = []Direction{directionMappings[currentDirection]}
case MirrorSlash:
// mirrow symbol is /
directionMappings := map[Direction]Direction{
Leftward: Downward,
Rightward: Upward,
Upward: Rightward,
Downward: Leftward,
}
nextDirections = []Direction{directionMappings[currentDirection]}
}
return
}