Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Generative & recursive

Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).

fractal_tree

One recursive def, drawn to depth 12.

// Fractal Tree — a recursive `def` macro draws a branching tree. Each branch
// splits into two shorter branches at a fixed angle; `if depth > 0` is the base
// case that stops the recursion. Branches are keyed by a binary-heap index
// (k -> 2k, 2k+1) so every segment gets a unique id, hued and thinned by depth.
//
// Showcases the Phase-2 language layer: `def`, recursion, `if`, comparisons.
//
//   manic examples/fractal_tree.manic
//   manic examples/fractal_tree.manic --record out --fps 60

title("Fractal Tree");
canvas(1280, 720);

text(head, (640, 92), "one recursive rule, drawn to depth 9");
display(head);  color(head, cyan);  size(head, 26);  hidden(head);

// draw a branch, then recurse into two children (unless we've bottomed out)
def branch(k, x, y, ang, len, depth) {
  // stop at the base depth OR once a branch is too short to see — so even a
  // large `depth` self-limits (the tree is bounded by branch length)
  if depth > 0 && len > 2 {
    let x2 = x + len * cos(ang);
    let y2 = y - len * sin(ang);          // screen y grows downward
    line(seg{k}, (x, y), (x2, y2));
    stroke(seg{k}, 1 + depth * 0.8);
    hue(seg{k}, 120 + depth * 15);        // trunk bluish -> tips green
    untraced(seg{k});  tag(seg{k}, tree);
    branch(2*k,     x2, y2, ang + 0.42, len * 0.72, depth - 1);
    branch(2*k + 1, x2, y2, ang - 0.42, len * 0.72, depth - 1);
  }
}

// grow from the bottom centre, pointing up (angle pi/2)
branch(1, 640, 700, 1.5708, 150, 20);

// --- script ---
show(head, 0.5);
draw(tree, 1.8);
wait(1.6);

hue_wave

An animated hue wave across a grid.

// Hue Wave — a ring of dots, each with its own starting hue, all advancing
// their hue at the same rate so the rainbow *rotates* around the ring. Shows
// off `hue` as an animatable track: `to(id, hue, degrees)` cycles colour over
// time (unlike `recolor`, it travels around the colour wheel, not through grey).
//
//   manic examples/hue_wave.manic
//   manic examples/hue_wave.manic --record out --fps 60

title("Hue Wave");
canvas(1280, 720);

text(head, (640, 110), "an animated hue track — colour that cycles");
display(head);  color(head, cyan);  size(head, 26);  hidden(head);

let n = 36;   let cx = 640;   let cy = 400;   let r = 210;

// a ring of dots, rainbow-coloured by angle
for i in 0..n {
  let a = tau * i / n;
  dot(d{i}, (cx + r*cos(a), cy + r*sin(a)), 18);
  hue(d{i}, 360 * i / n);
  glow(d{i}, 1.4);
  tag(d{i}, ring);
}

// --- script ---
show(head, 0.5);

// spin the whole rainbow: every dot advances its hue by 720 deg (two full
// cycles) over 6s, in parallel — the pattern rotates around the ring
par {
  for i in 0..n {
    to(d{i}, hue, 360*i/n + 720, 6.0, linear);
  }
}

hill_run

A little scene animated with the language layer.

// Uphill / Downhill — a rate x time = distance word problem.
// "Up a hill at 4 mph, back down the same path at 6 mph, round trip = 1 hour.
//  Total distance?"  Answer: one-way d = 2.4 mi, round trip = 4.8 mi.
//
// The distance is SOLVED in-language: d = 1 / (1/4 + 1/6) = 2.4, total = 2d.
// The runner climbs slowly, descends faster (3s vs 2s ~ the real 0.6h : 0.4h),
// then the equation is derived and the answer counts up on a live readout.
//
//   manic examples/hill_run.manic
//   manic examples/hill_run.manic --record out --fps 60

title("Uphill / Downhill");
canvas("16:9");

// --- the numbers, computed the same way you'd reason it out ---
let up   = 4;                      // mph, uphill
let down = 6;                      // mph, downhill
let d     = 1 / (1/up + 1/down);   // one-way distance = 2.4 mi  (from d/4 + d/6 = 1)
let total = 2 * d;                 // round trip        = 4.8 mi

text(head, (cx, 84), "up at 4 mph, down at 6 mph -- round trip takes 1 hour");
display(head);  color(head, cyan);  size(head, 24);  hidden(head);
text(cap, (cx, 668), "");  color(cap, dim);  size(cap, 23);

// --- the hill (a single path, run up then down) ---
line(ground, (150, 560), (700, 560));   color(ground, dim);   stroke(ground, 2);   untraced(ground);
line(path,   (200, 560), (620, 210));    color(path, cyan);    stroke(path, 4);      untraced(path);
text(flag, (628, 196), "top");           color(flag, dim);     size(flag, 18);       hidden(flag);
dot(runner, (200, 560), 16);             color(runner, lime);  glow(runner, 1.7);    hidden(runner);

text(uplbl,   (300, 470), "4 mph");      color(uplbl, cyan);      size(uplbl, 24);   hidden(uplbl);
text(downlbl, (520, 320), "6 mph");      color(downlbl, magenta); size(downlbl, 24); hidden(downlbl);

// --- the derivation, on the right ---
text(e1, (960, 230), "time = distance / rate");   color(e1, dim);  size(e1, 22);  hidden(e1);
text(e2, (960, 300), "d/4 + d/6 = 1");            display(e2);  color(e2, fg);      size(e2, 30);  hidden(e2);
text(e3, (960, 360), "5d/12 = 1   ->   d = 2.4");  display(e3);  color(e3, cyan);    size(e3, 26);  hidden(e3);
counter(ans, (960, 450), 0, 1, "round trip = 2d = ", " mi");  display(ans);  color(ans, lime);  size(ans, 30);  hidden(ans);

// --- script ---
show(head, 0.5);
say(cap, "an athlete runs up a hill, then back down the same path");
par { draw(ground, 0.5);  draw(path, 0.7); }
par { show(flag, 0.3);  show(runner, 0.3); }
wait(0.3);

section("Up the hill");
say(cap, "uphill at 4 mph -- the slow leg");
show(uplbl, 0.3);
move(runner, (620, 210), 3.0, linear);

section("Back down");
say(cap, "downhill at 6 mph -- faster, so less time");
show(downlbl, 0.3);
move(runner, (200, 560), 2.0, linear);
wait(0.3);

section("Set up the equation");
say(cap, "let d = the one-way distance; time = distance / rate");
show(e1, 0.4);
show(e2, 0.4);
say(cap, "combine the fractions: 5d/12 = 1, so d = 2.4 miles");
show(e3, 0.5);
flash(e3, lime);

section("Total distance");
say(cap, "the round trip is 2d");
show(ans, 0.3);
to(ans, value, total, 1.4);
pulse(ans);
wait(1.6);

equal_cuts

A circle halved again and again (pizza cuts).

// Equal Cuts — a circle sliced into equal pieces, repeatedly doubled:
// 2 → 4 → 8 equal wedges. Each "cut" is a diameter traced across the circle
// at an equal angle. (manic has no sector primitive yet, so cuts are lines.)
//
//   manic examples/equal_cuts.manic
//   manic examples/equal_cuts.manic --record out --fps 60

title("Equal Cuts");
canvas(1280, 720);

// the circle to divide, centred at (640, 400) with radius 240
circle(pie, (640, 400), 240);  stroke(pie, 3);

// four diameters through the centre at 0, 45, 90, 135 degrees.
// revealed in stages, they cut the circle into 2, then 4, then 8 equal pieces.
line(c0, (400, 400), (880, 400));  color(c0, magenta);  stroke(c0, 3);  untraced(c0);   //   0
line(c1, (640, 160), (640, 640));  color(c1, magenta);  stroke(c1, 3);  untraced(c1);   //  90
line(c2, (470, 230), (810, 570));  color(c2, lime);     stroke(c2, 3);  untraced(c2);   // 135
line(c3, (810, 230), (470, 570));  color(c3, lime);     stroke(c3, 3);  untraced(c3);   //  45

text(cap, (640, 690), "");    color(cap, dim);   size(cap, 22);
text(count, (1040, 170), ""); color(count, cyan); size(count, 34);  bold(count);

// --- cut in half ---
say(cap, "cut the circle in half");
draw(c0, 0.6);
say(count, "2 pieces");
wait(0.5);

// --- cut again: four equal pieces ---
say(cap, "cut again at a right angle — four equal pieces");
draw(c1, 0.6);
say(count, "4 pieces");
wait(0.5);

// --- and again: eight equal pieces ---
say(cap, "and again on both diagonals — eight equal pieces");
par {
  draw(c2, 0.6);
  draw(c3, 0.6);
}
say(count, "8 pieces");
pulse(pie);
wait(1.2);

archimedes_pi

Bounding pi with inscribed / circumscribed polygons.

// Approximating pi — Archimedes' method (c. 250 BC): inscribe a regular polygon
// in a circle and its perimeter closes in on the circumference. For an n-gon in
// a circle of radius R the perimeter is 2R * n*sin(pi/n), so pi ~ n*sin(pi/n),
// which -> pi as n grows. We sweep n = 6, 24, 96 (Archimedes' own 96-gon) and
// zoom in to see the last polygon nearly kiss the circle.
//
// Uses: a `for` loop per polygon, computed estimates, a live counter, and the
// camera (cam + zoom).
//
//   manic examples/archimedes_pi.manic
//   manic examples/archimedes_pi.manic --record out --fps 60

title("Approximating pi");
canvas("16:9");

let ox = 440;   let oy = 400;   let R = 240;   // circle centre + radius

// the estimates, computed in-language
let e6  = 6  * sin(pi/6);       // 3.000
let e24 = 24 * sin(pi/24);      // 3.133
let e96 = 96 * sin(pi/96);      // 3.141

text(head, (640, 78), "Archimedes: straight lines closing in on a circle");
display(head);  color(head, cyan);  size(head, 25);  hidden(head);
text(cap, (640, 675), "");  color(cap, dim);  size(cap, 22);

// the true circle (the target)
circle(circ, (ox, oy), R);  outlined(circ);  outline(circ, dim);  stroke(circ, 2);  untraced(circ);

// live pi readout
counter(est, (990, 330), 0, 3, "pi ~ ", "");  display(est);  color(est, lime);  size(est, 40);  hidden(est);
text(truth, (990, 395), "true pi = 3.14159...");  color(truth, dim);  size(truth, 20);  hidden(truth);

// --- hexagon: n = 6 (magenta) ---
let n = 6;
for i in 0..n {
  let a0 = tau*i/n;   let a1 = tau*(i+1)/n;
  line(h{i}, (ox + R*cos(a0), oy + R*sin(a0)), (ox + R*cos(a1), oy + R*sin(a1)));
  color(h{i}, magenta);  stroke(h{i}, 3);  untraced(h{i});  tag(h{i}, p6);
}
// --- 24-gon (cyan) ---
let n = 24;
for i in 0..n {
  let a0 = tau*i/n;   let a1 = tau*(i+1)/n;
  line(g{i}, (ox + R*cos(a0), oy + R*sin(a0)), (ox + R*cos(a1), oy + R*sin(a1)));
  color(g{i}, cyan);  stroke(g{i}, 3);  untraced(g{i});  tag(g{i}, p24);
}
// --- 96-gon (lime), Archimedes' own ---
let n = 96;
for i in 0..n {
  let a0 = tau*i/n;   let a1 = tau*(i+1)/n;
  line(k{i}, (ox + R*cos(a0), oy + R*sin(a0)), (ox + R*cos(a1), oy + R*sin(a1)));
  color(k{i}, lime);  stroke(k{i}, 2);  untraced(k{i});  tag(k{i}, p96);
}

// --- script ---
show(head, 0.5);
say(cap, "how close can straight lines get to a curve?");
draw(circ, 1.0);
par { show(est, 0.3);  show(truth, 0.3); }
wait(0.4);

section("6 sides");
say(cap, "start with a hexagon inside the circle");
draw(p6, 0.8);
to(est, value, e6, 1.0);
wait(0.7);
fade(p6, 0.4);

section("24 sides");
say(cap, "more sides hug the circle more tightly");
draw(p24, 1.0);
to(est, value, e24, 1.0);
wait(0.7);
fade(p24, 0.4);

section("96 sides");
say(cap, "Archimedes went to 96 sides -- around 250 BC");
draw(p96, 1.2);
to(est, value, e96, 1.0);
pulse(est);
wait(0.7);

section("Almost a circle");
say(cap, "zoom in: the polygon edge and the arc nearly touch");
par { cam((ox, oy - R), 1.5, smooth);  zoom(5, 1.5, smooth); }
wait(1.4);
par { cam((cx, cy), 1.0, smooth);  zoom(1, 1.0, smooth); }
wait(0.8);