JS1k

#3, Oregon Trail



Source description for demo by Lauri Paimen. See also the description and submitted source for this submission.

/* 
 * Boid simulator
 * Lauri Paimen, 2011, http://lauri.paimen.info
 * Entry to JS1K #3, http://js1k.com/2011-dysentery/
 * 
 * Boid simulator simulates flocking behavior of bird-like objects.
 * Add boid with space, obstacle with enter, control behavior with B-Q keys
 * and use your mouse to place obstacles and steer the boids.
 * For more information, check http://en.wikipedia.org/wiki/Boids
 *
 * Controls for increase/decrease values affecting behavior:
 * B/C Grass height
 * D/E Maximum velocity of boid
 * F/G Minimum distance between boids
 * H/I Flock "gravity", how tightly boids flock (bigger value less)
 * J/K Mouse "gravity", how much mouse pointer attracts boids (bigger less)
 * L/M Obstacle size
 * N/O Perching time
 * P/Q Boid line of sight
 *
 * Simulated behaviors:
 * - Boids try to fly towards other boids (inside line-of-sight)
 * - Boids try to keep a small distance between each other
 * - Boids try to go to same direction as other boids (inside line-of-sight)
 * - Boids try to catch the mouse
 * - Boids try to avoid hitting the obstacles
 * - Boids have maximum speed
 * - Boids perch in the grass for some time
 *
 * To shrink the file (boids_shim.js):
 * 1. Download and extract YUI compressor.
 * 2. Run:
 * java -jar yuicompressor-2.4.2.jar boids_shim.js \
 *   | sed -e 's/0\./\./g' \
 *         -e 's/;$//g' \
 *         -e 's/){with/)with/' \
 *         -e 's/}}}}/}}}/' \
 *         -e 's/00000/E5/g' \
 *         -e 's/000/E3/g' \
 *         -e 's/00/E2/g' \
 *         -e 's/a){/a)/' \
 *         -e 's/\]}/\];/' \
 *         -e 's/)){/))/' \
 *         -e 's/9)}/9);/'
 *
 */

// Tested to run on opera 11.10, ff 3.6.16 (bit slow there) and
// chrome 10.0.648.133. Couldn't test on safari (has no mac).

// a: Canvas draw surface (from shim)
// m: Last mouse X coord
// n: Last mouse Y coord
// Set canvas context with shortened names, proudly copied from earlier JS1Ks
// One can squeese 1 byte out by replacing
// a[$[0] + ($[6] || '')] with a[$[0] += $[6] || '']
for($ in a) a[$[m=n=0] += $[6] || ''] = a[$];

// i: item array (contains boids and obstacles)
i = [];

// c: canvas element (from shim)
// h: height and width of draw area. The size doesn't really matter here.
//    I picked 800x800. Sorry if its too tall for your screen.
h = c.width = c.height = 800;


// b: settings array:
// #  inc dec meaning
// 0: B   C   Grass height
// 1: D   E   Maximum velocity
// 2: F   G   Minimum distance 
// 3: H   I   Flock center "gravity"
// 4: J   K   Goal (mouse) "gravity"
// 5: L   M   Obstacle size
// 6: N   O   Perching time
// 7: P   Q   Boid line of sight
//   0    1  2  3    4    5  6   7
b = [700, 5, 9, 1E5, 3E3, 9, 99, 60];

// This is the only function in boids simulator. It handles:
// - Mouse events
// - Key events
// - Algorithms
// - Drawing
setInterval(onkeyup = onmousemove =
function(E) {
    // E: event

    // If we have E, it's mousemove/keyup event
    E ? (
        // Z: preconstructed item representing boid or obstacle
        // Z.x: x coordinate
        // Z.y: y coordinate
        // Z.X: x velocity
        // Z.Y: y velocity
        // Z.w: perching time left (0 = not perching)
        // Z.W: type; 0 = boid, 1 = obstacle
        Z = { // If the event was mouse event (has pageX), update
              // last mouse position.
              // Also, set the item to be at last mouse position.
              x: m = E.pageX || m,
              y: n = E.pageY || n,
              X: 0,
              Y: 0,
              w: 0,
              W: 0 },
        // Handle key event
        // A: keycode
        // Firefox requires (e.which|0) to make result a number (!NaN)
        // Enter(13) -> Add obstacle. Also, A is set to keyCode - 13
        A = (E.which|0) - 13 || ++Z.W & i.push(Z),
        // space(32 = 19 + 13) -> Add boid
        A - 19 || i.push(Z),
        // Indexing gives: B(67) and C(68) => 0, D(69) and E(70) => 1, etc
        // Multiplier gets either 1.1 to increase or 0.9 to decrease
        // the setting value.
        // Save 2 bytes with simple addition/deletion: += A % 2 - .5;
        b[A + A % 2 - 53 >> 1] *= .9 + A % 2 / 5
        // Key and mouse is now handled. To prevent drawing, execute this
        // statement. Executing it fails always, equaling to
        // returning from handler function.
        // Initialize d here, you can assume it to be Math.abs about anywhere
        // d: Math.abs
        )(d = Math.abs)
    // No E => algorithms and drawing
    // Draw background (set color to slowly fade to black)
    // f: fillstyle
      : a[f = "fillStyle"] = "rgba(0,0,0,.1)";

    // I: loop helper
    a.fc(I = 0, 0, h, h); // fillrect

    // Print stats (it takes quite a lot of bytes)
    // I: loop index
    // Loop increment: round and print stat, increment I
    for (a[f] = "tan"; I < 8;a.fx(b[I++]|0, 99, A))
        // Stat name
        // A: stat y coord
        a.fx("B|C Ground0D|E Speed0F|G Distance0H|I Flocking0J|K Mouse attr0L|M Obst size0N|O Perch time0P|Q Sight len".split(0)[I], 5, A=9*I+9)

    // Draw grass (drawing at "for" -loop initialization)
    a[f] = "#270";
    
    // Calculate, move and draw boids
    // I: preset for loop
    // A: Temporary helper, needed until with()
    // Finish drawing of grass at loop initialization
    for (a.fc(I = 0, b[0], h, h); A = i[I++];) // No {}'s
        // I: DON'T USE AFTER THIS, POINTS TO NEXT -NOT CURRENT- BOID!
        // with() imports properties of boid (Z)
        with (A) {
        
            // Calculate boid movement
            // B: Amount of boids affecting (inside line of sight)
            // K: inner loop index
            // M: center X
            // N: center Y
            // O: dist X
            // P: dist Y
            // Q: avg velocity X
            // R: avg velocity Y
            // S: other boid
            // Initialize obstacle draw value at increment
            for (B = K = M = N = O = P = Q = R = 0; S = i[K++];
                a[f] = "#888") {
                // K: DON'T USE AFTER THIS, POINTS TO NEXT -NOT CURRENT- BOID!

                // A: distance between boids
                // Count line-of-sight items
                // Pythagorean length calculation takes too many bytes,
                // do simplier estimate with box. No-one will notice this.
                (A = d(x - S.x) + d(y - S.y)) < b[7] && (
                    B++,
                    M += S.x,
                    N += S.y,
                    Q += S.X,
                    R += S.Y
                );
                // Collision avoidance
                // Introduces "feature", real would be (S.W?3*b[5]:b[2])
                // I forgot what the "feature" is, sorry. Something useful,
                // I guess.
                A < S.W * 3 * b[5] + b[2] && (
                    O -= S.x - x,
                    P -= S.y - y
                )
            }
            // Calculate velocity, X coord
            X +=
                // Flock center
                (M / B - x) / b[3]
                // Collision avoidance with line-in-sight items
                + O / 9
                // Average velocity of line-in-sight flock
                + (Q / B - X) / 15
                // Mouse gravity
                + (m - x) / b[4];

            y > b[0] | w ? (
                // Starting perch/rest, or already doing it
                // Stop the item and place it onto ground
                y = b[X = 0],
                // -5 starting velocity, also makes boid "stand" in the ground
                Y = -5,
                // Reduce or set perch time
                w = w ? w - 1 : b[6] | 0 // |0 = floor, w must be integer
            ) : // Calculate Y velocity (similar to X above)
                Y +=
                    (N / B - y) / b[3]
                    + P / 9
                    + (R / B - Y) / 15
                    + (n - y) / b[4];

            // speed limiter
            // C: Shortcut for max speed setting
            C = b[1];
            X /= d(X) > C ? d(X) / C : 1;
            Y /= d(Y) > C ? d(Y) / C : 1;

            // Move and draw item
            // fillStyle is #888 set for obstacle already
            // strokeStyle will be red for boid
            with(a) {
                ba(); // beginPath
                strokeStyle = "red"; // Color of stroke (boid)

                W ? // Obstacle: arc, fill
                    a(x, y, b[5], 0, 7, 0) & f()
                    // Boid: move, lineTo (+ boid move), stroke
                  : m(x, y) & l(x += X, y += Y) & s();

            } // End of with(a)

        } // End of with(A)

    // end of outer for loop (no {}'s)

}, // End of master function
50); // setInterval interval