← all entries  /  js1k.com/2425  /  2016 · eleMental · 632 bytes

Recursive Tree

by LuisQuin — a random tree grown through recursive branching on HTML5 Canvas

Submission
click to regenerate
Submission source  632 bytes
for(_='"rgb(roundon()functi{l,m/2,extn,p,A,Style=;b.stroke";c.[TAB]Math.random"+(64*+b.height>>0)+",
line,b.width);d;g(c.75*,60,-PI12,12)( d{c.fill"black[TAB]ft="bold 16px Arial[TAB]tAlign="center
[TAB]fillT("Click  image to regenerate tree"50)} g(b,c,e,h,k,d,f){var ,r=2*PI/4beginPathmoveTo(c,e);
l=c+h*cos(k);m=e+h*sin(k)Cap=""Width=fTo()2>=d?0, 1280)":6450, 25)";if(A=d-1)for(c=(2*)+1,f*=.7,
e=0;e
Expanded

The submission uses a string-substitution compressor: a dictionary of common substrings is prepended to a template, each entry separated by the control character it replaces. The loop finds the first control character, promotes the text before it into a replacement, and repeats until the template is clean JavaScript. What follows is the decompressed result — 784 characters, still compact but now readable.

click to regenerate
Expanded source  784 bytes
(function(){
  function d(){
    c.fillStyle="black";
    c.font="bold 16px Arial";
    c.textAlign="center";
    c.fillText("Click on image to regenerate tree",b.width/2,50)
  }

  function g(b,c,e,h,k,d,f){
    var n,p,A,l,m,r=2*Math.PI/4;
    b.beginPath();
    b.moveTo(c,e);
    l=c+h*Math.cos(k);
    m=e+h*Math.sin(k);
    b.lineCap="round";
    b.lineWidth=f;
    b.lineTo(l,m);
    b.strokeStyle=2>=d
      ?"rgb(0, "+(64*Math.random()+128>>0)+", 0)"
      :"rgb("+(64*Math.random()+64>>0)+", 50, 25)";
    b.stroke();
    if(A=d-1)
      for(c=Math.round(2*Math.random())+1,f*=.7,e=0;e<c;e++)
        p=k+Math.random()*r-.5*r,
        n=h*(.7+.3*Math.random()),
        setTimeout(function(){g(b,l,m,n,p,A,f)},30)
  }

  var b=a,c=b.getContext("2d");
  d();
  g(c,b.width/2,.75*b.height,60,-Math.PI/2,12,12);
  b.onclick=function(){c.clearRect(0,0,b.width,b.height);d();g(c,b.width/2,.75*b.height,60,-Math.PI/2,12,12)}
})();
Commented

The tree grows by recursion via setTimeout. Each call to g() draws one branch segment, then schedules 1 or 2 child branches at random angles. Recursion stops when depth d reaches zero. Branches at depth ≤2 are drawn green; deeper ones use a warm reddish-brown, mimicking bark. The lineWidth shrinks by 30% at each level, so trunk segments are thick and twigs are hair-thin.

// js1k shim provides: a=canvas, b=document.body, c=2d context, d=document
// The submission wraps everything in an IIFE to avoid polluting global scope.

(function(){

  // ── Prompt text ─────────────────────────────────────────────────────────────
  // Draws "Click on image to regenerate tree" near the top of the canvas.
  // Called once on load and again after each click-clear.
  function d(){
    c.fillStyle = "black";
    c.font      = "bold 16px Arial";
    c.textAlign = "center";
    c.fillText("Click on image to regenerate tree", b.width/2, 50);
  }

  // ── Branch drawing / recursion ───────────────────────────────────────────────
  // Parameters:
  //   b  – canvas element (used for .onclick and dimensions)
  //   c  – 2d context
  //   e  – start Y (note: variable shadowing – 'e' re-used from outer scope)
  //   h  – segment length in pixels
  //   k  – angle in radians (starts at -PI/2 = straight up)
  //   d  – remaining depth (12 on first call; recursion stops at 0)
  //   f  – line width (12 on first call; shrinks by ×0.7 each level)
  function g(b, c, e, h, k, d, f){
    var n, p, A, l, m;
    var r = 2 * Math.PI / 4;  // = PI/2 – the maximum random deviation per branch

    // Draw the segment from (c,e) to (l,m)
    b.beginPath();
    b.moveTo(c, e);
    l = c + h * Math.cos(k);   // tip X
    m = e + h * Math.sin(k);   // tip Y

    b.lineCap   = "round";     // rounded ends look organic
    b.lineWidth = f;
    b.lineTo(l, m);

    // Color: near-leaf branches (depth ≤ 2) → green; deeper → reddish-brown bark
    b.strokeStyle = (2 >= d)
      ? "rgb(0, "  + (64 * Math.random() + 128 >> 0) + ", 0)"    // green, 128–191
      : "rgb("     + (64 * Math.random() + 64  >> 0) + ", 50, 25)"; // brown, 64–127
    b.stroke();

    // Recurse: if depth remains (A = d-1 is truthy while d > 1)
    if (A = d - 1) {
      // Pick how many child branches: 1 or 2 (Math.round(2*random())+1 → 1, 2, or 3 but
      // weighted to 1–2). Each child is delayed 30ms so the tree "grows" visibly.
      for (
        c = Math.round(2 * Math.random()) + 1,  // reuse 'c' as branch count (1–3)
        f *= .7,                                  // thin the line by 30%
        e = 0;
        e < c;
        e++
      ) {
        p = k + Math.random() * r - .5 * r;        // new angle: parent ± up to PI/4
        n = h * (.7 + .3 * Math.random());         // new length: 70–100% of parent's
        setTimeout(function(){
          g(b, l, m, n, p, A, f);                 // branch from tip (l,m)
        }, 30);
      }
    }
  }

  // ── Bootstrap ────────────────────────────────────────────────────────────────
  // js1k shim already put the canvas in 'a'; grab context and kick off the tree.
  var b = a,
      c = b.getContext("2d");

  d();  // draw prompt text
  g(c, b.width/2, .75 * b.height, 60, -Math.PI/2, 12, 12);
  // ^ starts at horizontal centre, 75% down, length 60px, pointing straight up, depth 12, width 12

  b.onclick = function(){
    c.clearRect(0, 0, b.width, b.height);  // wipe canvas
    d();                                       // redraw prompt
    g(c, b.width/2, .75 * b.height, 60, -Math.PI/2, 12, 12);
  };

})();