package main import ( "bufio" "fmt" "log" "os" ) func processLines(path string, f func(string) (stop bool, err error)) error { file, err := os.Open(path) if err != nil { return err } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { stop, err := f(scanner.Text()) if stop || err != nil { return err } } if err := scanner.Err(); err != nil { return err } return nil } var ( emptySeatCode = 'L' occupiedSeatCode = '#' floorCode = '.' printMaps = false // Variables for controlling part 1 and part 2. maxOccupied = 4 lookVisible = false ) type point struct { x, y int } type square struct { seat bool occupied bool loc point left *square right *square up *square down *square tock func() error } func (s square) isAvailable() bool { return s.seat && !s.occupied } func (s square) hasPerson() bool { return s.seat && s.occupied } func (s *square) sit() error { if !s.seat { return fmt.Errorf("cannot sit on the floor") } if s.occupied { return fmt.Errorf("someone is already sitting here") } s.occupied = true return nil } func (s *square) leave() error { if !s.seat { return fmt.Errorf("cannot leave from the floor") } if !s.occupied { return fmt.Errorf("nobody is here to leave") } s.occupied = false return nil } func (s *square) setLeft(l *square) { if l.right != nil { log.Fatalf( "cannot set square above %v to %v because %v already has %v below it", s.loc, l.loc, l.loc, l.right.loc, ) } s.left = l l.right = s } func (s *square) setUp(u *square) { if u.down != nil { log.Fatalf( "cannot set square above %v to %v because %v already has %v below it", s.loc, u.loc, u.loc, u.down.loc, ) } s.up = u u.down = s } func (s *square) leftUp() *square { if s.left != nil { if s.left.up != nil { return s.left.up } } return nil } func (s *square) upRight() *square { if s.up != nil { if s.up.right != nil { return s.up.right } } return nil } func (s *square) rightDown() *square { if s.right != nil { if s.right.down != nil { return s.right.down } } return nil } func (s *square) downLeft() *square { if s.down != nil { if s.down.left != nil { return s.down.left } } return nil } func (s *square) lookLeft() *square { n := s.left if n != nil && !n.seat { return n.lookLeft() } return n } func (s *square) lookRight() *square { n := s.right if n != nil && !n.seat { return n.lookRight() } return n } func (s *square) lookUp() *square { n := s.up if n != nil && !n.seat { return n.lookUp() } return n } func (s *square) lookDown() *square { n := s.down if n != nil && !n.seat { return n.lookDown() } return n } func (s *square) lookLeftUp() *square { n := s.leftUp() if n != nil && !n.seat { return n.lookLeftUp() } return n } func (s *square) lookUpRight() *square { n := s.upRight() if n != nil && !n.seat { return n.lookUpRight() } return n } func (s *square) lookRightDown() *square { n := s.rightDown() if n != nil && !n.seat { return n.lookRightDown() } return n } func (s *square) lookDownLeft() *square { n := s.downLeft() if n != nil && !n.seat { return n.lookDownLeft() } return n } func appendNonNil(s []*square, e *square) []*square { if e != nil { return append(s, e) } return s } func (s *square) visibleSeats() []*square { result := []*square{} result = appendNonNil(result, s.lookLeft()) result = appendNonNil(result, s.lookRight()) result = appendNonNil(result, s.lookUp()) result = appendNonNil(result, s.lookDown()) result = appendNonNil(result, s.lookLeftUp()) result = appendNonNil(result, s.lookUpRight()) result = appendNonNil(result, s.lookRightDown()) result = appendNonNil(result, s.lookDownLeft()) return result } func (s *square) adjacentSeats() []*square { result := []*square{} result = appendNonNil(result, s.left) result = appendNonNil(result, s.right) result = appendNonNil(result, s.up) result = appendNonNil(result, s.down) result = appendNonNil(result, s.leftUp()) result = appendNonNil(result, s.upRight()) result = appendNonNil(result, s.rightDown()) result = appendNonNil(result, s.downLeft()) return result } func (s *square) tick() bool { // Noop tock function s.tock = func() error { return nil } if !s.seat { return false } var seats []*square if lookVisible { seats = s.visibleSeats() } else { seats = s.adjacentSeats() } if s.isAvailable() { // Check adjacent seats for _, adj := range seats { if adj.hasPerson() { // If any adjacent seat is occupied, do nothing return false } } // Nothing is occupied? Sit! s.tock = s.sit return true } if s.hasPerson() { numOccupied := 0 for _, adj := range seats { if adj.hasPerson() { numOccupied++ } } if numOccupied >= maxOccupied { s.tock = s.leave return true } } return false } func (s square) String() string { // return fmt.Sprintf("%v", s.loc) if !s.seat { return "." } if s.occupied { return "#" } return "L" } func readMap() *[][]*square { worldMap := [][]*square{} rowIndex := 0 if err := processLines("input.txt", func(line string) (bool, error) { // fmt.Printf("New line! %d\n", rowIndex) row := []*square{} var leftSeat *square for i, c := range line { var s square switch c { case emptySeatCode: s = square{loc: point{i, rowIndex}, seat: true} case occupiedSeatCode: s = square{loc: point{i, rowIndex}, seat: true, occupied: true} case floorCode: s = square{loc: point{i, rowIndex}, seat: false} default: return false, fmt.Errorf("unknown square value %x", c) } // fmt.Printf("%v", s.loc) if leftSeat != nil { s.setLeft(leftSeat) } if rowIndex > 0 { up := worldMap[rowIndex-1][i] // fmt.Printf("\nTry set above %d,%d to %d,%d ", i, rowIndex, up.loc.x, up.loc.y) s.setUp(up) } leftSeat = &s row = append(row, &s) } // fmt.Println(row) worldMap = append(worldMap, row) // fmt.Printf("\n") rowIndex++ return false, nil }); err != nil { log.Fatal("could not parse map", err) } return &worldMap } func printMap(worldMap *[][]*square) { for _, row := range *worldMap { for _, s := range row { fmt.Print(s.String()) } fmt.Print("\n") } } func tickMap(worldMap *[][]*square) bool { worldChanged := false for _, row := range *worldMap { for _, s := range row { worldChanged = s.tick() || worldChanged } } return worldChanged } func tockMap(worldMap *[][]*square) error { for _, row := range *worldMap { for _, s := range row { err := s.tock() if err != nil { return err } } } return nil } func countOccupied(worldMap *[][]*square) int { occupied := 0 for _, row := range *worldMap { for _, s := range row { if s.hasPerson() { occupied++ } } } return occupied } func run() { worldMap := readMap() if printMaps { fmt.Println("Time 0") printMap(worldMap) } var err error t := 1 changed := true for changed { changed = tickMap(worldMap) err = tockMap(worldMap) if err != nil { log.Fatal(err) } if printMaps { fmt.Printf("\nTime %d (%t)\n", t, changed) printMap(worldMap) } t++ } fmt.Printf("\nTotal %d occupied seats after %d iterations\n", countOccupied(worldMap), t) } func main() { // Part 1 run() // Part 2 maxOccupied = 5 lookVisible = true run() }