aoc-2020/d11/main.go

464 lines
7.3 KiB
Go

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()
}