464 lines
7.3 KiB
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()
|
|
}
|