Canonical Advent of Code Walkthroughs
This page walks through 6 representative Advent of Code challenges solved end-to-end in Tinsel, illustrating how Tinsel's primitives replace dozens of lines of boilerplate in general-purpose languages. At the bottom of the page is a complete index of all runnable scripts in examples/.
Walkthrough 1: Text & Pattern Ingestion
(AoC 2023 Day 1: Trebuchet?! & AoC 2024 Day 3: Mull It Over)
Problem Summary
- Trebuchet: Extract the first and last digit from each line of text, combine them into a two-digit number
first * 10 + last, and sum across all lines. - Mull It Over: Scan corrupted memory text for valid
mul(X,Y)instructions while honoringdo()(enable) anddon't()(disable) gates.
Tinsel Solution
// Part A: AoC 2023 Day 1 (Character arithmetic + negative indexing + fused sum)
let trebuchet_input = "1abc2\npqr3stu8vwx\na1b2c3d4e5f\ntreb7uchet"
let calibration_sum = (for line in trebuchet_input.lines() {
let digits = for ch in line.chars() {
if ch in '0'..='9' {
yield ch - '0'
}
}
if digits.len() > 0 {
yield digits[0] * 10 + digits[-1]
}
}).sum()
assert(calibration_sum == 142)
// Part B: AoC 2024 Day 3 (Split on do()/don't() + unanchored .scan())
let memory = "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
let part1 = memory.scan("mul({x},{y})").map(m -> m.x * m.y).sum()
let part2 = (for chunk in memory.split("do()") {
let enabled_prefix = chunk.split("don't()")[0]
yield enabled_prefix.scan("mul({x},{y})").map(m -> m.x * m.y).sum()
}).sum()
assert(part1 == 161 && part2 == 48)
Why Tinsel Shines: Instead of compiling regular expressions (regexp.MustCompile) and maintaining stateful scanner loops, digits[-1] indexes the tail directly and .scan("mul({x},{y})") extracts and parses integer fields in one call.
Walkthrough 2: 2D Grid Neighborhoods & Wavefronts
(AoC 2025 Day 4: Printing Department & Day 7: Laboratories)
Problem Summary
- Printing Department: On a 2D grid of
@paper rolls, count how many@cells have fewer than 4 adjacent@neighbors in their 8-way Moore neighborhood. - Laboratories: A tachyon beam enters at
'S'and moves downward row by row. When a beam hits a splitter'^', it splits into left (x - 1) and right (x + 1) beams and increments the split counter.
Tinsel Solution
// 1. AoC 2025 Day 4: 8-way boundary-aware grid neighborhood in 6 lines
let warehouse = "..@@.@@@@.\n@@@.@.@.@@\n@@@@@.@.@@\n@.@@@@..@.\n@@.@@@@.@@\n.@@@@@@@.@\n.@.@.@.@@@\n@.@@@.@@@@\n.@@@@@@@@.\n@.@.@@@.@.".grid()
let accessible_count = warehouse.find_all('@').filter(pos -> {
warehouse.adj8(pos).filter(nb -> warehouse[nb] == '@').len() < 4
}).len()
assert(accessible_count == 13)
// 2. AoC 2025 Day 7: Beam-splitting wavefront with .unique()
let lab = ".......S.......\n...............\n.......^.......\n...............\n......^.^......\n...............\n.....^.^.^.....".grid()
let start = lab.find('S')
let mut beams = [start.x]
let mut total_splits = 0
for y in (start.y + 1)..lab.height {
let next_beams = []
for x in beams {
if x in 0..lab.width {
if lab[vec2(x, y)] == '^' {
total_splits += 1
if x - 1 >= 0 { next_beams.push(x - 1) }
if x + 1 < lab.width { next_beams.push(x + 1) }
} else {
next_beams.push(x)
}
}
}
beams = next_beams.unique()
}
assert(total_splits == 6)
Walkthrough 3: Interval Set Algebra at Scale
(AoC 2023 Day 5: Seed Fertilizer & AoC 2025 Day 5: Cafeteria)
Problem Summary
- Seed Fertilizer: Thread initial seed intervals through 7 sequential piecewise translation layers (
[dest_start, src_start, length]) and find the minimum final location coordinate. - Cafeteria: Normalize overlapping inclusive fresh-ingredient ranges (
lo-hi) into a disjointRangeSetand count how many candidate IDs fall inside any fresh interval.
Tinsel Solution
from "std/intervals" import range_set
// 1. AoC 2023 Day 5: Multi-layer piecewise interval trisection & remap
let seeds = range_set([79..93, 55..68])
let layers = [
[[50, 98, 2], [52, 50, 48]],
[[0, 15, 37], [37, 52, 2], [39, 0, 15]],
[[49, 53, 8], [0, 11, 42], [42, 0, 7], [57, 7, 4]],
[[88, 18, 7], [18, 25, 70]],
[[45, 77, 23], [81, 45, 19], [68, 64, 13]],
[[0, 69, 1], [1, 0, 69]],
[[60, 56, 37], [56, 93, 4]]
]
let final_locations = layers.fold(seeds, (rs, layer) -> rs.remap(layer))
assert(final_locations.min() == 46)
// 2. AoC 2025 Day 5: Sweep-line normalized interval membership
let cafeteria_input = "3-5\n10-14\n16-20\n12-18\n\n1\n5\n8\n11\n17\n32"
let [ranges_sec, ids_sec] = cafeteria_input.sections()
let fresh = range_set(for line in ranges_sec.lines() {
let "{lo}-{hi}" = line
yield lo..=hi
})
let fresh_count = ids_sec.ints().filter(id -> id in fresh).len()
assert(fresh_count == 3)
Walkthrough 4: Weighted State-Space Search
(AoC 2024 Day 16: Reindeer Maze)
Problem Summary
Navigate a reindeer from 'S' (facing East) to 'E' in a 2D maze where stepping forward costs 1 point and rotating 90^\circ left or right costs 1000 points.
Tinsel Solution
import "std/grid" as grid
from "std/search" import dijkstra
let maze = "###############\n#.......#....E#\n#.#.###.#.###.#\n#.....#.#...#.#\n#.###.#####.#.#\n#.#.#.......#.#\n#.#.#####.###.#\n#...........#.#\n###.#.#####.#.#\n#...#.....#.#.#\n#.#.#.###.#.#.#\n#.....#...#.#.#\n#.###.#.#.#.#.#\n#S..#.....#...#\n###############".grid()
let start_pos = maze.find('S')
let end_pos = maze.find('E')
let result = dijkstra({
start: (start_pos, 0), // (position, heading 0 = East)
goal: ((pos, _)) -> pos == end_pos,
step: ((pos, dir)) -> {
let moves = [
[(pos, grid.turn_right(dir)), 1000],
[(pos, grid.turn_left(dir)), 1000]
]
let next = grid.forward(pos, dir)
if maze[next] != '#' {
moves.push([(next, dir), 1])
}
moves
}
})
assert(result.found && result.cost == 7036)
Walkthrough 5: 3D Union-Find & Bitmask BFS
(AoC 2025 Day 8: Playground & Day 10: Factory)
Problem Summary
- Playground: Given 3D junction box coordinates, sort all pairwise edges by squared Euclidean distance, union the 10 closest pairs using Disjoint-Set Union (
union_find), and multiply the sizes of the 3 largest connected circuits. - Factory: Given target indicator lights
[.##.]and button wiring masks(3) (1,3) (2), find the minimum button presses to reach the target bitmask from0viabfs.
Tinsel Solution
from "std/search" import union_find, bfs
// 1. AoC 2025 Day 8: 3D distance sorting + Union-Find
let boxes_raw = "162,817,812\n57,618,57\n906,360,560\n592,479,940\n352,342,300\n466,668,158\n542,29,236\n431,825,988\n739,650,466\n52,470,668\n216,146,977\n819,987,18\n117,168,530\n805,96,715\n346,949,466\n970,615,88\n941,993,340\n862,61,35\n984,92,344\n425,690,689"
let pts = boxes_raw.lines().map(l -> {
let [x, y, z] = l.ints()
vec3(x, y, z)
})
let pairs = []
for i in 0..pts.len() {
for j in (i + 1)..pts.len() {
let d = pts[i] - pts[j]
pairs.push([d.x * d.x + d.y * d.y + d.z * d.z, i, j])
}
}
let sorted_pairs = pairs.sort_by(p -> p[0])
let uf = union_find(0..pts.len())
for [_, u, v] in sorted_pairs[..10] {
uf.union(u, v)
}
let groups = uf.groups()
let top3_prod = groups.keys().map(r -> groups[r].len()).sort_desc()[..3].product()
assert(top3_prod == 40)
// 2. AoC 2025 Day 10: Bitmask shortest path with bfs
let machine = "[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}"
let "[{diagram}] {rest}" = machine
let mut target_mask = 0
for [idx, ch] in diagram.chars().enumerate() {
if ch == '#' { target_mask |= (1 << idx) }
}
let button_masks = for m in rest.scan("({wiring})") {
let mut bmask = 0
for bit in to_string(m.wiring).ints() {
bmask |= (1 << bit)
}
yield bmask
}
let min_presses = bfs({
start: 0,
goal: target_mask,
step: state -> button_masks.map(b -> state ^ b)
}).cost
assert(min_presses == 2)
Walkthrough 6: Memoized DAG Paths
(AoC 2025 Day 11: Reactor)
Problem Summary
Given a directed acyclic graph of reactor conduits (node: target1 target2 ...), count the total number of distinct paths from "you" to "out".
Tinsel Solution
from "std/memo" import memoize
let reactor_input = "aaa: you hhh\nyou: bbb ccc\nbbb: ddd eee\nccc: ddd eee fff\nddd: ggg\neee: out\nfff: out\nggg: out\nhhh: ccc fff iii\niii: out"
let graph = {}
for line in reactor_input.lines() {
let "{node}: {targets}" = line
graph[node] = targets.words()
}
let mut count_paths = nil
count_paths = memoize(node -> {
if node == "out" { return 1 }
if node !in graph { return 0 }
graph[node].map(next -> count_paths(next)).sum()
})
assert(count_paths("you") == 5)
Index of Runnable Repository Examples (examples/)
Every script in examples/ can be executed directly via ./bin/tinsel run <path> (or in batch mode via ./bin/tinsel examples/aoc2025/):
| Script Path | Algorithmic Focus |
|---|---|
examples/language_tour.tin |
Interactive tour of structs, options, pattern matching, lazy iterators, and intervals |
examples/dungeon_explorer.tin |
Procedural dungeon generator (Kruskal's MST + std/grid) and multi-objective bfs auto-explorer |
examples/ascii_raymarcher.tin |
3D Signed Distance Field (SDF) ASCII raymarcher with smooth CSG (smin), Phong lighting, and soft shadows |
examples/symbolic_math.tin |
Symbolic calculus differentiator, nested match algebraic simplifier, and exact Newton-Raphson solver |
examples/cellular_automata.tin |
Sparse Game of Life spaceship/oscillator classifier and std/orbit Wireworld logic circuit simulator |
examples/stdlib_example.tin |
Overview of std/math, std/grid, std/search, and std/orbit |
examples/multi_file_example.tin |
Multi-file module imports (examples/modules/grid_utils.tin & aoc_utils.tin) |
examples/solve_with_file.tin |
Reading external input files via args[0] and read_file |
examples/aoc2015_day4.tin |
MD5 hash mining (md5 intrinsic) |
examples/aoc2015_day7.tin |
16-bit bitwise logic gate circuit emulation with memoization |
examples/aoc2020_day13.tin |
Chinese Remainder Theorem (math.crt) for bus schedules |
examples/aoc2020_day17.tin |
3D & 4D Conway Cubes cellular automata (std/vec adj_all) |
examples/aoc2023_day1.tin |
Digit extraction and comprehension fusion |
examples/aoc2023_day5.tin |
Multi-layer interval trisection and RangeSet.remap() |
examples/aoc2023_day18.tin |
Polygon area via math.shoelace and math.picks_theorem |
examples/aoc2024_day3.tin |
Unanchored corrupted text scanning (.scan("mul({x},{y})")) |
examples/aoc2024_day16.tin |
Weighted Dijkstra maze search over (pos, dir) states |
examples/aoc2025/day01.tin .. day12.tin |
Complete 12-day suite for Advent of Code 2025 (Part One) |