307 lines
6.7 KiB
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
|
|
}
|