Created
December 14, 2022 21:46
-
-
Save zoldar/945f7bdc0ed0837600a995c4f0d2428b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import tables, strutils, sequtils, math, sugar, strformat | |
type | |
StopAction = enum Continue, Put, Discard | |
Point = tuple[x: int, y: int] | |
Cave = object | |
map: Table[Point, char] | |
bounds: tuple[min: Point, max: Point] | |
proc `-`(p1, p2: Point): Point = (p1.x - p2.x, p1.y - p2.y) | |
proc `+`(p1, p2: Point): Point = (p1.x + p2.x, p1.y + p2.y) | |
proc normalize(p: Point): Point = (sgn(p.x), sgn(p.y)) | |
proc bounds(map: Table[Point, char]): (Point, Point) = | |
let (xs, ys) = map.keys.toSeq.unzip | |
((min(xs), min(ys)), (max(xs), max(ys))) | |
proc outsideCave(cave: Cave, p: Point): StopAction = | |
let (min, max) = cave.bounds | |
if p.x < min.x or p.x > max.x or p.y > max.y: | |
Discard | |
else: | |
Continue | |
proc belowTheFloor(cave: Cave, p: Point): StopAction = | |
if p.y == cave.bounds.max.y + 1: | |
Put | |
else: | |
Continue | |
proc loadCave(input: seq[string]): Cave = | |
for line in input: | |
let path = line.split(" -> ") | |
.mapIt(it.split(",").mapIt(parseInt(it))) | |
.mapIt((x: it[0], y: it[1])) | |
for idx in 0..<path.len-1: | |
var current = path[idx] | |
let stop = path[idx+1] | |
let vector = (stop - current).normalize | |
while current != stop: | |
result.map[current] = '#' | |
current = current + vector | |
result.map[stop] = '#' | |
result.map[(500, 0)] = '+' | |
result.bounds = bounds(result.map) | |
proc drawCave*(cave: Cave): void = | |
let (minPoint, maxPoint) = bounds(cave.map) | |
for y in minPoint.y..maxPoint.y: | |
echo (minPoint.x..maxPoint.x).mapIt(cave.map.getOrDefault((it, y), '.')).join | |
proc moveDown(p: Point, cave: Cave): Point = | |
for move in [(0, 1), (-1, 1), (1, 1)]: | |
let next = p + move | |
if not cave.map.hasKey(next): | |
return next | |
return p | |
proc dropSand(cave: var Cave, path: var seq[Point], stopFn: (c: Cave, p: Point) -> StopAction): bool = | |
var current = | |
if path.len > 1: | |
discard path.pop | |
path[^1] | |
else: | |
(500, 0) | |
var next = moveDown(current, cave) | |
if current == next: | |
cave.map[next] = 'o' | |
return false | |
while true: | |
if current == next: | |
discard path.pop | |
cave.map[next] = 'o' | |
return true | |
case stopFn(cave, next): | |
of Discard: | |
return false | |
of Put: | |
cave.map[next] = 'o' | |
return true | |
of Continue: | |
path.add(next) | |
current = next | |
next = moveDown(current, cave) | |
proc dropAllSand(cave: Cave, stopFn: (c: Cave, p: Point) -> StopAction): Cave = | |
var currentCave = cave | |
var path = @[(500, 0)] | |
while true: | |
if not dropSand(currentCave, path, stopFn): | |
break | |
currentCave | |
let cave = loadCave("day-14/input.txt".lines.toSeq) | |
let filledCave = dropAllSand(cave, outsideCave) | |
# drawCave(filledCave) | |
let atRestCount = filledCave.map.values.toSeq.filterIt(it == 'o').len | |
echo fmt"Number of units of sand at rest after cave is filled: {atRestCount}" | |
let caveWithFloor = dropAllSand(cave, belowTheFloor) | |
# drawCave(caveWithFloor) | |
let atRestCountWithFloor = caveWithFloor.map.values.toSeq.filterIt(it == 'o').len | |
echo fmt"Number of units of sand at rest after cave is filled from floor: {atRestCountWithFloor}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment