Skip to content

Instantly share code, notes, and snippets.

@zoldar
Created December 14, 2022 15:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zoldar/3e9048d82a42eda7cac8ab1a8fa4c89e to your computer and use it in GitHub Desktop.
Save zoldar/3e9048d82a42eda7cac8ab1a8fa4c89e to your computer and use it in GitHub Desktop.
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: var Cave, stopFn: (c: Cave, p: Point) -> StopAction): bool =
var current = (500, 0)
var next = moveDown(current, cave)
if current == next:
cave.map[next] = 'o'
return false
while true:
if current == next:
cave.map[next] = 'o'
return true
case stopFn(cave, next):
of Discard:
return false
of Put:
cave.map[next] = 'o'
return true
of Continue:
discard
current = next
next = moveDown(current, cave)
proc dropAllSand(cave: Cave, stopFn: (c: Cave, p: Point) -> StopAction): Cave =
var currentCave = cave
while true:
if not dropSand(currentCave, 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