-
Notifications
You must be signed in to change notification settings - Fork 0
/
Day10.worksheet.sc
156 lines (125 loc) · 4.72 KB
/
Day10.worksheet.sc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import aoc.*
val input = io.Source.fromResource("2023/day-10.txt").getLines().toVector
val area = Area(input)
val start = area.pointsIterator.find(input(_) == 'S').get
// NOTE: alternatively tuples/sets of directions
enum Tile:
case TopBot, TopLeft, TopRight, BotLeft, BotRight, LeftRight, Empty
def topOpen: Boolean = this match
case TopBot | TopLeft | TopRight => true
case _ => false
def botOpen: Boolean = this match
case TopBot | BotLeft | BotRight => true
case _ => false
def leftOpen: Boolean = this match
case TopLeft | BotLeft | LeftRight => true
case _ => false
def rightOpen: Boolean = this match
case TopRight | BotRight | LeftRight => true
case _ => false
def show: Char = this match
case TopBot => '║'
case TopLeft => '╝'
case TopRight => '╚'
case BotLeft => '╗'
case BotRight => '╔'
case LeftRight => '═'
case Empty => '.'
object Tile:
def fromChar(c: Char) = c match
case '.' => Empty
case '7' => BotLeft
case 'J' => TopLeft
case 'L' => TopRight
case 'F' => BotRight
case '-' => LeftRight
case '|' => TopBot
case 'S' => startTileType
case _ => ???
import Tile.*
val startTileType: Tile =
val n = Tile.fromChar(input(start.n))
val s = Tile.fromChar(input(start.s))
val e = Tile.fromChar(input(start.e))
val w = Tile.fromChar(input(start.w))
if n.botOpen && s.topOpen then TopBot
else if n.botOpen && w.rightOpen then TopLeft
else if n.botOpen && e.leftOpen then TopRight
else if s.topOpen && w.rightOpen then BotLeft
else if s.topOpen && e.leftOpen then BotRight
else if w.rightOpen && e.leftOpen then LeftRight
else ???
val startDir: Dir =
if startTileType.topOpen then Dir.N
else if startTileType.botOpen then Dir.S
else if startTileType.rightOpen then Dir.E
else Dir.W
val grid: Map[Point, Tile] = Map.from[Point, Tile]:
area.pointsIterator.map(p => p -> Tile.fromChar(input(p)))
def adjacent(p: Point): Set[Point] =
val n = Option.when(p.n.inBounds(area) && grid(p).topOpen && grid(p.n).botOpen)(p.n)
val s = Option.when(p.s.inBounds(area) && grid(p).botOpen && grid(p.s).topOpen)(p.s)
val e = Option.when(p.e.inBounds(area) && grid(p).rightOpen && grid(p.e).leftOpen)(p.e)
val w = Option.when(p.w.inBounds(area) && grid(p).leftOpen && grid(p.w).rightOpen)(p.w)
List(n, s, e, w).flatten.toSet
val loopSteps: List[Set[Point]] =
val steps = Iterator.unfold(Set.empty[Point] -> Set(start)):
case (visited, current) =>
Option.when(current.nonEmpty):
val next = current.flatMap(adjacent).diff(visited)
(current, current -> next)
steps.toList
val ans1 = loopSteps.length - 1
val boundaryPoints = loopSteps.reduce(_ union _)
def nextDir(pos: Point, dir: Dir) =
val destTile = grid(pos.move(dir))
if destTile.topOpen && dir != Dir.S then Dir.N
else if destTile.botOpen && dir != Dir.N then Dir.S
else if destTile.leftOpen && dir != Dir.E then Dir.W
else if destTile.rightOpen && dir != Dir.W then Dir.E
else throw new Exception(s"no next dir for $pos $dir")
def walk = Iterator.unfold[(List[Point], List[Point]), (Point, Dir)]((start, Dir.N)):
case (pos, dir) =>
val nextPos = pos.move(dir)
val left = pos.move(dir.turnLeft)
val right = pos.move(dir.turnRight)
Option.when(nextPos != start):
val leftSpace =
List(left, left.move(dir))
.filter(_.inBounds(area))
.filterNot(boundaryPoints)
val rightSpace =
List(right, right.move(dir))
.filter(_.inBounds(area))
.filterNot(boundaryPoints)
val newState = (nextPos, nextDir(pos, dir))
(leftSpace, rightSpace) -> newState
val (left, right) = walk.toList.unzip
val leftSeeds = left.flatten.toSet
val rightSeeds = right.flatten.toSet
assert(leftSeeds.intersect(rightSeeds).isEmpty)
def flood(seeds: Set[Point]): Set[Point] =
val points = Iterator.unfold(Set.empty[Point] -> seeds):
case (visited, current) =>
Option.when(current.nonEmpty):
val next = current.flatMap(area.adjacent).diff(boundaryPoints).diff(visited)
(current, current -> next)
points.reduce(_ union _)
val leftFlood = flood(leftSeeds)
val rightFlood = flood(rightSeeds)
assert(area.size == rightFlood.size + leftFlood.size + boundaryPoints.size)
val (inside, outside) =
if leftFlood.contains(Point.origin) then (rightFlood, leftFlood)
else (leftFlood, rightFlood)
assert(inside.intersect(outside).isEmpty)
val unicodeDarkShade = '\u2593'
val unicodeLightShade = '\u2591'
val unicodeBlock = '\u2588'
println:
area.draw: p =>
if p == start then unicodeBlock
else if inside(p) then unicodeDarkShade
else if outside(p) then unicodeLightShade
else if boundaryPoints.contains(p) then grid(p).show
else '?'
val ans2 = inside.size