Legend:
w - work day
w - weekend
wwwwwww
Legend:
w - work day
w - weekend
wwwwwww
there's nothing open about OpenAI
Least cluttered Windows Desktop:

Duck me in the ark tonight
Goated movie. They don't make them like that anymore.
Very fiddly solution with lots of debugging required.
Code
type
Vec2 = tuple[x,y: int]
Box = array[2, Vec2]
Dir = enum
U = "^"
R = ">"
D = "v"
L = "<"
proc convertPart2(grid: seq[string]): seq[string] =
for y in 0..grid.high:
result.add ""
for x in 0..grid[0].high:
result[^1] &= (
if grid[y][x] == 'O': "[]"
elif grid[y][x] == '#': "##"
else: "..")
proc shiftLeft(grid: var seq[string], col: int, range: HSlice[int,int]) =
for i in range.a ..< range.b:
grid[col][i] = grid[col][i+1]
grid[col][range.b] = '.'
proc shiftRight(grid: var seq[string], col: int, range: HSlice[int,int]) =
for i in countDown(range.b, range.a+1):
grid[col][i] = grid[col][i-1]
grid[col][range.a] = '.'
proc box(pos: Vec2, grid: seq[string]): array[2, Vec2] =
if grid[pos.y][pos.x] == '[':
[pos, (pos.x+1, pos.y)]
else:
[(pos.x-1, pos.y), pos]
proc step(grid: var seq[string], bot: var Vec2, dir: Dir) =
var (x, y) = bot
case dir
of U:
while (dec y; grid[y][x] != '#' and grid[y][x] != '.'): discard
if grid[y][x] == '#': return
if grid[bot.y-1][bot.x] == 'O': swap(grid[bot.y-1][bot.x], grid[y][x])
dec bot.y
of R:
while (inc x; grid[y][x] != '#' and grid[y][x] != '.'): discard
if grid[y][x] == '#': return
if grid[bot.y][bot.x+1] == 'O': swap(grid[bot.y][bot.x+1], grid[y][x])
inc bot.x
of L:
while (dec x; grid[y][x] != '#' and grid[y][x] != '.'): discard
if grid[y][x] == '#': return
if grid[bot.y][bot.x-1] == 'O': swap(grid[bot.y][bot.x-1], grid[y][x])
dec bot.x
of D:
while (inc y; grid[y][x] != '#' and grid[y][x] != '.'): discard
if grid[y][x] == '#': return
if grid[bot.y+1][bot.x] == 'O': swap(grid[bot.y+1][bot.x], grid[y][x])
inc bot.y
proc canMoveVert(box: Box, grid: seq[string], boxes: var HashSet[Box], dy: int): bool =
boxes.incl box
var left, right = false
let (lbox, rbox) = (box[0], box[1])
let lbigBox = box((lbox.x, lbox.y+dy), grid)
let rbigBox = box((rbox.x, lbox.y+dy), grid)
if grid[lbox.y+dy][lbox.x] == '#' or
grid[rbox.y+dy][rbox.x] == '#': return false
elif grid[lbox.y+dy][lbox.x] == '.': left = true
else:
left = canMoveVert(box((lbox.x,lbox.y+dy), grid), grid, boxes, dy)
if grid[rbox.y+dy][rbox.x] == '.': right = true
elif lbigBox == rbigBox: right = left
else:
right = canMoveVert(box((rbox.x, rbox.y+dy), grid), grid, boxes, dy)
left and right
proc moveBoxes(grid: var seq[string], boxes: var HashSet[Box], d: Vec2) =
for box in boxes:
grid[box[0].y][box[0].x] = '.'
grid[box[1].y][box[1].x] = '.'
for box in boxes:
grid[box[0].y+d.y][box[0].x+d.x] = '['
grid[box[1].y+d.y][box[1].x+d.x] = ']'
boxes.clear()
proc step2(grid: var seq[string], bot: var Vec2, dir: Dir) =
case dir
of U:
if grid[bot.y-1][bot.x] == '#': return
if grid[bot.y-1][bot.x] == '.': dec bot.y
else:
var boxes: HashSet[Box]
if canMoveVert(box((x:bot.x, y:bot.y-1), grid), grid, boxes, -1):
grid.moveBoxes(boxes, (0, -1))
dec bot.y
of R:
var (x, y) = bot
while (inc x; grid[y][x] != '#' and grid[y][x] != '.'): discard
if grid[y][x] == '#': return
if grid[bot.y][bot.x+1] == '[': grid.shiftRight(bot.y, bot.x+1..x)
inc bot.x
of L:
var (x, y) = bot
while (dec x; grid[y][x] != '#' and grid[y][x] != '.'): discard
if grid[y][x] == '#': return
if grid[bot.y][bot.x-1] == ']': grid.shiftLeft(bot.y, x..bot.x-1)
dec bot.x
of D:
if grid[bot.y+1][bot.x] == '#': return
if grid[bot.y+1][bot.x] == '.': inc bot.y
else:
var boxes: HashSet[Box]
if canMoveVert(box((x:bot.x, y:bot.y+1), grid), grid, boxes, 1):
grid.moveBoxes(boxes, (0, 1))
inc bot.y
proc solve(input: string): AOCSolution[int, int] =
let chunks = input.split("\n\n")
var grid = chunks[0].splitLines()
let movements = chunks[1].splitLines().join().join()
var robot: Vec2
for y in 0..grid.high:
for x in 0..grid[0].high:
if grid[y][x] == '@':
grid[y][x] = '.'
robot = (x,y)
block p1:
var grid = grid
var robot = robot
for m in movements:
let dir = parseEnum[Dir]($m)
step(grid, robot, dir)
for y in 0..grid.high:
for x in 0..grid[0].high:
if grid[y][x] == 'O':
result.part1 += 100 * y + x
block p2:
var grid = grid.convertPart2()
var robot = (robot.x*2, robot.y)
for m in movements:
let dir = parseEnum[Dir]($m)
step2(grid, robot, dir)
#grid.inspect(robot)
for y in 0..grid.high:
for x in 0..grid[0].high:
if grid[y][x] == '[':
result.part2 += 100 * y + x
Part 1: there's no need to simulate each step, final position for each robot is
(position + velocity * iterations) modulo grid
Part 2: I solved it interactively: Maybe I just got lucky, but my input has certain pattern: after 99th iteration every 101st iteration looking very different from other. I printed first couple hundred iterations, noticed a pattern and started looking only at "interesting" grids. It took 7371 iterations (I only had to check 72 manually) to reach an easter egg.
type
Vec2 = tuple[x,y: int]
Robot = object
pos, vel: Vec2
var
GridRows = 101
GridCols = 103
proc examine(robots: seq[Robot]) =
for y in 0..<GridCols:
for x in 0..<GridRows:
let c = robots.countIt(it.pos == (x, y))
stdout.write if c == 0: '.' else: char('0'.ord + c)
stdout.write '\n'
stdout.flushFile()
proc solve(input: string): AOCSolution[int, int] =
var robots: seq[Robot]
for line in input.splitLines():
let parts = line.split({' ',',','='})
robots.add Robot(pos: (parts[1].parseInt,parts[2].parseInt),
vel: (parts[4].parseInt,parts[5].parseInt))
block p1:
var quads: array[4, int]
for robot in robots:
let
newX = (robot.pos.x + robot.vel.x * 100).euclmod GridRows
newY = (robot.pos.y + robot.vel.y * 100).euclmod GridCols
relRow = cmp(newX, GridRows div 2)
relCol = cmp(newY, GridCols div 2)
if relRow == 0 or relCol == 0: continue
inc quads[int(relCol>0)*2 + int(relRow>0)]
result.part1 = quads.foldl(a*b)
block p2:
if GridRows != 101: break p2
var interesting = 99
var interval = 101
var i = 0
while true:
for robot in robots.mitems:
robot.pos.x = (robot.pos.x + robot.vel.x).euclmod GridRows
robot.pos.y = (robot.pos.y + robot.vel.y).euclmod GridCols
inc i
if i == interesting:
robots.examine()
echo "Iteration #", i, "; Do you see an xmas tree?[N/y]"
if stdin.readLine().normalize() == "y":
result.part2 = i
break
interesting += interval
I'm embarrasingly bad with math. Couldn't have solved this one without looking up the solution. =C
type Vec2 = tuple[x,y: int64]
const
PriceA = 3
PriceB = 1
ErrorDelta = 10_000_000_000_000
proc isInteger(n: float): bool = n.round().almostEqual(n)
proc `+`(a: Vec2, b: int): Vec2 = (a.x + b, a.y + b)
proc solveEquation(a, b, prize: Vec2): int =
let res_a = (prize.x*b.y - prize.y*b.x) / (a.x*b.y - a.y*b.x)
let res_b = (a.x*prize.y - a.y*prize.x) / (a.x*b.y - a.y*b.x)
if res_a.isInteger and res_b.isInteger:
res_a.int * PriceA + res_b.int * PriceB
else: 0
proc solve(input: string): AOCSolution[int, int] =
let chunks = input.split("\n\n")
for chunk in chunks:
let lines = chunk.splitLines()
let partsA = lines[0].split({' ', ',', '+'})
let partsB = lines[1].split({' ', ',', '+'})
let partsC = lines[2].split({' ', ',', '='})
let a = (parseBiggestInt(partsA[3]), parseBiggestInt(partsA[6]))
let b = (parseBiggestInt(partsB[3]), parseBiggestInt(partsB[6]))
let c = (parseBiggestInt(partsC[2]), parseBiggestInt(partsC[5]))
result.part1 += solveEquation(a,b,c)
result.part2 += solveEquation(a,b,c+ErrorDelta)
Runtime: ~~7ms~~ 3.18 ms
Part 1: I use flood fill to count all grouped plants and keep track of each border I see.
Part 2: I use an algorithm ~~similar to "merge overlapping ranges"~~ to count spans of borders (border orientation matters) in each row and column, for each group. Resulting code (hidden under spoiler) is a little messy and not very DRY (it's completely soaked).
Edit: refactored solution, removed some very stupid code.
proc groupSpans()
proc groupSpans(borders: seq[(Vec2, Dir)]): int =
## returns number of continuous groups of cells with same Direction
## and on the same row or column
var borders = borders
var horiz = borders.filterIt(it[1] in {U, D})
while horiz.len > 0:
var sameYandDir = @[horiz.pop()]
var curY = sameYandDir[^1][0].y
var curDir = sameYandDir[^1][1]
for i in countDown(horiz.high, 0):
if horiz[i][0].y == curY and horiz[i][1] == curDir:
sameYandDir.add horiz[i]
horiz.del i
sameYandDir.sort((a,b)=>cmp(a[0].x, b[0].x), Descending)
var cnt = 1
for i, (p,d) in sameYandDir.toOpenArray(1, sameYandDir.high):
if sameYandDir[i][0].x - p.x != 1: inc cnt
result += cnt
var vert = borders.filterIt(it[1] in {L, R})
while vert.len > 0:
var sameXandDir = @[vert.pop()]
var curX = sameXandDir[^1][0].x
var curDir = sameXandDir[^1][1]
for i in countDown(vert.high, 0):
if vert[i][0].x == curX and vert[i][1] == curDir:
sameXandDir.add vert[i]
vert.del i
sameXandDir.sort((a,b)=>cmp(a[0].y, b[0].y), Descending)
var cnt = 1
for i, (p,d) in sameXandDir.toOpenArray(1, sameXandDir.high):
if sameXandDir[i][0].y - p.y != 1: inc cnt
result += cnt
type
Dir = enum L,R,U,D
Vec2 = tuple[x,y: int]
GroupData = object
plantCount: int
borders: seq[(Vec2, Dir)]
const Adjacent: array[4, Vec2] = [(-1,0),(1,0),(0,-1),(0,1)]
proc solve(input: string): AOCSolution[int, int] =
let grid = input.splitLines()
var visited = newSeqWith(grid.len, newSeq[bool](grid[0].len))
var groups: seq[GroupData]
proc floodFill(pos: Vec2, plant: char, groupId: int) =
visited[pos.y][pos.x] = true
inc groups[groupId].plantCount
for di, d in Adjacent:
let pd: Vec2 = (pos.x+d.x, pos.y+d.y)
if pd.x < 0 or pd.y < 0 or pd.x > grid[0].high or pd.y > grid.high or
grid[pd.y][pd.x] != plant:
groups[groupId].borders.add (pd, Dir(di))
continue
if visited[pd.y][pd.x]: continue
floodFill(pd, plant, groupId)
for y in 0..grid.high:
for x in 0..grid[0].high:
if visited[y][x]: continue
groups.add GroupData()
floodFill((x,y), grid[y][x], groups.high)
for gid, group in groups:
result.part1 += group.plantCount * group.borders.len
result.part2 += group.plantCount * group.borders.groupSpans()
Runtime: 30-40 ms
I'm not very experienced with recursion and memoization, so this took me quite a while.
Edit: slightly better version
template splitNum(numStr: string): seq[int] =
@[parseInt(numStr[0..<numStr.len div 2]), parseInt(numStr[numStr.len div 2..^1])]
template applyRule(stone: int): seq[int] =
if stone == 0: @[1]
else:
let numStr = $stone
if numStr.len mod 2 == 0: splitNum(numStr)
else: @[stone * 2024]
proc memRule(st: int): seq[int] =
var memo {.global.}: Table[int, seq[int]]
if st in memo: return memo[st]
result = st.applyRule
memo[st] = result
proc countAfter(stone: int, targetBlinks: int): int =
var memo {.global.}: Table[(int, int), int]
if (stone,targetBlinks) in memo: return memo[(stone,targetBlinks)]
if targetBlinks == 0: return 1
for st in memRule(stone):
result += st.countAfter(targetBlinks - 1)
memo[(stone,targetBlinks)] = result
proc solve(input: string): AOCSolution[int, int] =
for stone in input.split.map(parseInt):
result.part1 += stone.countAfter(25)
result.part2 += stone.countAfter(75)
As many others today, I've solved part 2 first and then fixed a 'bug' to solve part 1. =)
type Vec2 = tuple[x,y:int]
const Adjacent = [(x:1,y:0),(-1,0),(0,1),(0,-1)]
proc path(start: Vec2, grid: seq[string]): tuple[ends, trails: int] =
var queue = @[@[start]]
var endNodes: HashSet[Vec2]
while queue.len > 0:
let path = queue.pop()
let head = path[^1]
let c = grid[head.y][head.x]
if c == '9':
inc result.trails
endNodes.incl head
continue
for d in Adjacent:
let nd = (x:head.x + d.x, y:head.y + d.y)
if nd.x < 0 or nd.y < 0 or nd.x > grid[0].high or nd.y > grid.high:
continue
if grid[nd.y][nd.x].ord - c.ord != 1: continue
queue.add path & nd
result.ends = endNodes.len
proc solve(input: string): AOCSolution[int, int] =
let grid = input.splitLines()
var trailstarts: seq[Vec2]
for y, line in grid:
for x, c in line:
if c == '0':
trailstarts.add (x,y)
for start in trailstarts:
let (ends, trails) = start.path(grid)
result.part1 += ends
result.part2 += trails
https://youtu.be/DrYRpPxBEaU