diff --git a/day16/example b/day16/example new file mode 100644 index 0000000..d6805ce --- /dev/null +++ b/day16/example @@ -0,0 +1,10 @@ +.|...\.... +|.-.\..... +.....|-... +........|. +.......... +.........\ +..../.\\.. +.-.-/..|.. +.|....-|.\ +..//.|.... diff --git a/day16/floorWillBeLava.go b/day16/floorWillBeLava.go new file mode 100644 index 0000000..c21ac35 --- /dev/null +++ b/day16/floorWillBeLava.go @@ -0,0 +1,258 @@ +package day16 + +import ( + "fmt" + "log" + "os" + "strings" + "sync" +) + +func Run() int { + fmt.Println("hello from day 16") + log.Println("starting") + field := ReadField("day16/example") + fmt.Println(field.String()) + field.StartTraversal() + + fmt.Println(field.ShowEnergyzed()) + + return field.CountEnergized() +} + +// 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() { + reportedVisits := make(chan MovementPoint) + var wg sync.WaitGroup + + go f.RecordVisits(reportedVisits) + startPoint := MovementPoint { + Row: 0, Col: 0, Direction: Rightward, + } + 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 +} diff --git a/main.go b/main.go index ca58cb8..28e6220 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,12 @@ package main import ( "log" - "sunshine.industries/aoc2023/day15" + "sunshine.industries/aoc2023/day16" ) func main() { log.Print("> starting run:") - result := day15.Run() - log.Printf("day15 result: %d\n****\n", result) + result := day16.Run() + log.Printf("\n\nday16 result: %d\n****\n", result) }