Created
December 14, 2022 13:17
-
-
Save zoldar/0c2e02d82f6dceb26adde1fe62a3c12e to your computer and use it in GitHub Desktop.
This file contains 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 < min.y 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 next in [p + (0, 1), p + (-1, 1), p + (1, 1)]: | |
if not cave.map.hasKey(next): | |
return next | |
return p | |
proc dropSand(cave: Cave, stopFn: (c: Cave, p: Point) -> StopAction): Cave = | |
result = cave | |
var current = (500, 0) | |
var next = moveDown(current, cave) | |
while true: | |
if current == next: | |
result.map[next] = 'o' | |
break | |
case stopFn(cave, next): | |
of Discard: | |
return cave | |
of Put: | |
result.map[next] = 'o' | |
break | |
of Continue: | |
discard | |
current = next | |
next = moveDown(current, cave) | |
proc dropAllSand(cave: Cave, stopFn: (c: Cave, p: Point) -> StopAction): Cave = | |
var currentCave = cave | |
var nextCave = dropSand(currentCave, stopFn) | |
while nextCave != currentCave: | |
currentCave = nextCave | |
nextCave = dropSand(currentCave, stopFn) | |
nextCave | |
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