Algorithms & data structures
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
bubble_sort
Real sliding swaps; array + compare + swap.
// Bubble Sort — real sliding swaps. `array` gives fixed slot boxes and value
// cells; `compare(a, i, j)` flashes the values now in slots i and j, and
// `swap(a, i, j)` slides them past each other into the swapped slots. `swap`
// carries the array's occupancy forward, so a whole chain of swaps composes
// correctly (no `say`). We sort [3, 1, 2] -> [1, 2, 3].
//
// manic examples/bubble_sort.manic
title("Bubble Sort");
canvas("16:9");
text(head, (cx, 130), "compare neighbours, swap if out of order");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (cx, 560), ""); color(cap, dim); size(cap, 26);
array(a, "3 1 2", (cx, 360), 100, 100);
show(head, 0.5);
say(cap, "an unsorted array");
wait(0.6);
section("Pass 1");
say(cap, "compare slots 0 and 1: 3 > 1, swap");
compare(a, 0, 1);
swap(a, 0, 1);
say(cap, "compare slots 1 and 2: 3 > 2, swap");
compare(a, 1, 2);
swap(a, 1, 2);
section("Pass 2");
say(cap, "compare slots 0 and 1: 1 < 2, ok");
compare(a, 0, 1, lime);
say(cap, "compare slots 1 and 2: 2 < 3, ok");
compare(a, 1, 2, lime);
section("Sorted");
say(cap, "1 2 3 -- done");
recolor(a.cells, lime, 0.4);
par { pulse(a.c0); pulse(a.c1); pulse(a.c2); }
wait(1.4);
two_pointer
lo/hi index carets scanning inward on a sorted array.
// Two Pointers — the `pointer`/`pointat` primitive. `pointer(id, arr, slot, label)`
// drops a caret under a slot; `pointat(id, arr, slot)` slides it to another slot
// (its label follows). Pointers track slot *positions*, so they stay put as
// values move. Here `lo`/`hi` scan inward on a sorted array.
//
// manic examples/two_pointer.manic
title("Two Pointers");
canvas("16:9");
text(head, (cx, 120), "two pointers scan toward the middle");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (cx, 560), ""); color(cap, dim); size(cap, 26);
array(a, "1 3 5 7 9 11", (cx, 340), 92, 92);
pointer(lo, a, 0, "lo");
pointer(hi, a, 5, "hi");
show(head, 0.5);
say(cap, "start at both ends");
wait(0.5);
compare(a, 0, 5);
say(cap, "step both inward");
par { pointat(lo, a, 1); pointat(hi, a, 4); }
compare(a, 1, 4);
par { pointat(lo, a, 2); pointat(hi, a, 3); }
compare(a, 2, 3);
say(cap, "the pointers have met");
wait(1.2);
stack_queue
LIFO stack + FIFO queue, with action-point carets.
// Stack & Queue — dynamic structures, with carets marking WHERE each op acts.
// `push`/`pop` (stack, LIFO, grows up) and `enqueue`/`dequeue` (queue, FIFO,
// grows right) are mutating verbs: they add a cell and animate it in, tracking
// occupancy so a chain of ops composes. A `caret` marks the action point and is
// `move`d in step with each op so it rides the changing top / back.
//
// manic examples/stack_queue.manic
title("Stack & Queue");
canvas("16:9");
let sx = 300; let sy = 500; // stack anchor (bottom cell centre)
let qx = 780; let qy = 300; // queue anchor (front cell centre)
let cw = 84; let ch = 64; // cell size
let stx = sx + 62; // stack "top" caret sits right of the column
text(head, (cx, 80), "LIFO stack, FIFO queue");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (cx, 660), ""); color(cap, dim); size(cap, 24);
text(sl, (sx, sy + 66), "stack: push / pop"); color(sl, lime); size(sl, 22);
text(ql, (qx + cw, qy - 118), "queue: enqueue / dequeue"); color(ql, cyan); size(ql, 22);
stack(st, (sx, sy), cw, ch);
queue(qu, (qx, qy), cw, ch);
// action-point markers
caret(top, (stx, sy), "top", left); // rides the top of the stack
caret(front, (qx, qy - 52), "front", down); // fixed: dequeue leaves here
caret(back, (qx, qy + 52), "back", up); // rides the back of the queue
show(head, 0.5);
section("Stack");
say(cap, "push 5, 3, 8 — each lands on top, the top caret rises");
push(st, "5");
par { push(st, "3"); move(top, (stx, sy - ch)); }
par { push(st, "8"); move(top, (stx, sy - 2*ch)); }
say(cap, "pop — the top value (8) leaves, the caret drops back");
par { pop(st); move(top, (stx, sy - ch)); }
wait(0.5);
section("Queue");
say(cap, "enqueue A, B, C — they join at the back caret");
enqueue(qu, "A");
par { enqueue(qu, "B"); move(back, (qx + cw, qy + 52)); }
par { enqueue(qu, "C"); move(back, (qx + 2*cw, qy + 52)); }
say(cap, "dequeue — the front leaves, the rest advance, back shifts in");
par { dequeue(qu); move(back, (qx + cw, qy + 52)); }
par { dequeue(qu); move(back, (qx, qy + 52)); }
say(cap, "stack: in and out at the top. queue: in at back, out at front.");
wait(1.2);
linked_list
Singly / doubly / circular — classic node anatomy + pointer re-threading.
// Linked List — classic anatomy, three variations. A node is a framed box split
// into compartments: singly `[ data | .next ]`, doubly `[ .prev | data | next. ]`,
// where a pointer field carries a dot its arrow starts from. `head` marks the
// entry node; the tail's `next` ends at `NULL` (singly/doubly) or curves back to
// the head (circular). `insert`/`remove` re-thread the pointers — no shifting.
//
// manic examples/linked_list.manic
title("Linked List");
canvas("16:9");
text(head, (cx, 56), "node = data + pointer field(s); head in, NULL or loop at the tail");
display(head); color(head, cyan); size(head, 24); hidden(head);
text(cap, (cx, 700), ""); color(cap, dim); size(cap, 24);
text(t1, (150, 160), "singly"); color(t1, lime); size(t1, 22);
text(t2, (150, 350), "doubly"); color(t2, lime); size(t2, 22);
text(t3, (150, 545), "circular"); color(t3, lime); size(t3, 22);
list(sa, "3 8 5", (cx, 160), singly, 64, 50);
list(da, "3 8 5", (cx, 350), doubly, 64, 50);
list(ca, "3 8 5", (cx, 545), circular, 64, 50);
show(head, 0.5);
say(cap, "three classic variations of the same idea");
wait(0.9);
section("Insert");
say(cap, "insert 7 after node 1 in the doubly list — pointers re-thread, no shift");
insert(da, 1, "7");
wait(0.6);
section("Remove");
say(cap, "remove the head of the singly list — the list re-points past it");
remove(sa, 0);
say(cap, "data + pointers: the whole family from one primitive");
wait(1.4);
hashmap
Hash a key to a bucket; collisions chain on (separate chaining).
// Hash Map — separate chaining. `hashmap(id, n, (cx,cy))` draws n numbered
// buckets in a column; `put(id, key, val)` hashes the key to a bucket and chains
// the `key:val` entry on (collisions extend the chain); `get(id, key)` hashes,
// then scans that bucket's chain — each entry flashes until the key matches
// (lime) or the chain ends (bucket flashes magenta = miss).
//
// (Hash = sum of the key's bytes mod n. "cat" and "act" are anagrams, so they
// collide — same bucket, chained.)
//
// manic examples/hashmap.manic
title("Hash Map");
canvas("16:9");
text(head, (cx, 60), "separate chaining: hash the key, chain on collision");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (cx, 690), ""); color(cap, dim); size(cap, 24);
hashmap(ht, 5, (360, 360), 128, 46);
show(head, 0.5);
say(cap, "put cat, dog, ok — each hashes to its bucket");
put(ht, "cat", "7");
put(ht, "dog", "3");
put(ht, "ok", "1");
wait(0.5);
section("Collision");
say(cap, "put act — same bytes as cat, so it collides and chains on");
put(ht, "act", "9");
wait(0.6);
section("Lookup");
say(cap, "get act — scan the chain in bucket 2 until the key matches");
get(ht, "act");
say(cap, "get xyz — hashes to a bucket, scans, falls off the end: miss");
get(ht, "xyz");
wait(1.2);