Capture the dragon roaming your kingdom! Click to move your knight. Click the minimap to pan the view.
for(_='40210K0,JJ48I(eHH,tG:d>F-3Z](Y=>X(tW14V;p--;)U0)),0&&]=92n[e]+1+c%2f,o,6J61,e(0|],)}-a.offsete%30*4+50c[c.fcYfor(=efy][	W,4*e/3+20(X{JJ64I","#Math.sin(RH).map(c)t]p in c)c[p[0]+p[6]+[p[V]]p;e=[,"#111910c96382352036fff"t=337,l=342,f=508,o=28,i=imEJr=s=n=[m=Date.now(d=l,f)p=16U(	t<8?t:0|t/8**p&7])l+p%4,f+p/41,1,h=G)XdJy=G)snY6,6,-6,6,8*(2-f8*(-o)dJrrY,RX[eZJeZ-+29,e+3Je+3+-29SX>t,SW)g=(d,p=90	ld2	149J1JVJVUh(p,(d=5*(p%30-17)*(pZ0/3e3)+m%p)+3)>6?K715155V9F4?0xa6d5b25b2dF1?5F0?0x5db6db6d:6p0<dd<=4?1/0:e;h(l,K63421h312659677682daYa,JJIIsS][7sRYtJSWr]H;e!=t;)y(s,134316032<e=t)(ry(r,55124988316iR(l).someHXe*s*m)>0>l]l)l==W=s)?(l=y2474498575568):setTimeout(g));a.onclick=(G.xLeft,c.yTop)t<480?Hi==r,>0<1/0r)W/8+f-50/4+30*(c/8+o-2/4)):c<V0f=tZJo=cZlg(b.bgColor[3g();';G=/[-U-ZF-K]/.exec(_);)with(_.split(G))_=join(shift());eval(_)Zm9yKF89JzQwMjEwSzAsSko0OEkoZUhILHRHOmQ+Ri0zWl0oWT0+WCh0VzE0VjtwLS07KVUwKR8pLB4wHh0mJhxdPRs5MhpuWxkZZV0YGCsxK2MlMhdmLG8sNko2FjEsZRUoMHwUXSwTKX0SLWEub2Zmc2V0EWUlMzAqNCs1MBBjW2MuDw9mY1kOZm9yKAw9ZQsPZnldC1sJVywINCoUZS8zHysyMAccKAZYewUOSko2NEkdBCIsIiMDTWF0aC5zaW4oAlJIKS5tYXAoCGMpBRl0XQEMcCBpbiBjKWNbcFswXStwWzZdK1twW1ZdXRtwO2U9WywiIzExMQM5MTADYzk2AzM4MgMzNTIDMDM2A2ZmZiITdD0zMzcsbD0zNDIsZj01MDgsbz0yOCxpPQ9pbUUbSnI9cz1uPVsTbT1EYXRlLm5vdygeZD0IbCxmKQUMcD0xNlUoCXQ8OD90OjB8dC84KipwJjddKRwObCtwJTQsZisUcC80HjEsMRIsaD1HKVhkCBBKBx55PUcpBQ9zblk2LDYsLTYsNiw4KigQMi1mHjgqKActbykeZAhKHQ9yclkSLFILWFtlWkplWhUtFSsyOSxlKzNKZSszFSsVLTI5E1MLWAE+FwYZdBsXLFNXKRIeZz0oZCxwPTkwHwUMCQ9sZBsyEwQJMRMONDlKMUpWSlYfVWgocCwoZD01KgIocCUzMC0xNykqKHBaMB8vM2UzKSsCbSVwKSszKT42P0s3MTUxNTVWOUY0PzB4YTZkGjViMjViMmRGMT81RjA/MHgaNRpkYjZkYjZkOjYeGXAbMDxkHGQ8PTQ/MS8wOmU7aChsLEs2GjM0MhoxHmgIMzEyNho1OTY3NzY4Mh4PZGFZYSwWSkpJSR0Pc1NdC1s3Ew9zUlkWHRl0G0pTVx4Zcl0GSAUMO2UhPXQ7KXkocwssMTM0MzE2MDMyHgE8GAZlPXQSEikoch55KHIsNTUxMjQ5ODgzMTYdaQZSKGwpLnNvbWVIWAJlKnMqbSk+MBwYPhlsXQZsCykebD09Vz1zKT8obD0EeQgyNDc0NDk4NTc1NTY4Hyk6c2V0VGltZW91dChnKSkSO2Eub25jbGljaz0oRwsueBFMZWZ0LGMLLnkRVG9wKQV0PDQ4MD9IBWkLPT1yLBg+MBwYPDEvMAZyCxIpFFcvOCtmLTUwHy80KzMwKhQoYy84K28tMh8vNCkpOmM8VjAGZj10WkpvPWNaHWwcZygSHmIuYmdDb2xvcgtbMxNnKCk7JztHPS9bAS0fVS1aRi1LXS8uZXhlYyhfKTspd2l0aChfLnNwbGl0KEcpKV89am9pbihzaGlmdCgpKTtldmFsKF8p// The source code is available at https://github.com/stasm/homm1k.
var palette = [
    ,       // transparent
    "#111", // 1 black
    "#910", // 2 red
    "#c96", // 3 beige
    "#382", // 4 light green
    "#352", // 5 dark green
    "#036", // 6 blue
    "#fff", // 7 white
],
// The position of the player and the critter expressed as an index into the
// 30x30 world array.
player = 337,
critter = 342,
// The offset of the currently visible area of the minimap, in canvas pixels
// from the origin. This makes it easy to update it when scrolling the view, to
// draw the white border on the minimap, and to draw the zoomed in viewport.
offset_x = 508,
offset_y = 28,
// moving: Whether the player is currently moving in response to a double click.
// imageSmoothingEnabled: The visible minimap fragment is drawn into the main
// viewport scaled up via drawImage(). Preserve the sharpness of pixels.
moving = c.imageSmoothingEnabled = false,
// The world array index of the most recently clicked tile.
target = -1,
// The world array index of the next tile on the path from the player to the
// current target. It's updated every time the path is traced. Because the
// tracing starts at the target, the last tile traced is the closest to the
// player.
next = -1,
// The world array stores distances of each tile to the player, as well as a
// non-numeric value for non-passable terrain, and +Infinity for unreachable
// tiles.
world = [],
// The seed for the terrain generation.
seed = Date.now(),
// DRAWING
// A generic draw function capable of drawing 4x4 sprites or solid 4x4 blocks of
// color. Sprites encode 48 bits of data: 7 colors or transparency (3 bits) for
// each of the 16 pixels. When written as octal numbers, the pixel pattern is
// easy to spot; each digits corresponds to one pixel. Pixels are encoded in the
// reversed order, i.e. the first digit of the 16-digit long octal number
// (the factor of 8**15) represents the last pixel of the sprite in the
// top-to-bottom, left-to-right reading order. Hint: transparent pixels on the
// last row of the sprite will result in zeros at the front of the encoded
// sprite. Free bytes!
draw = (sprite, x, y) => {
    for (p=16; p--;) {
        // Single-digit sprites are solid 4x4 blocks of the same color. Regular
        // sprites are decoded into pixels by dividing the whole sprite by a
        // factor of 8 corresponding to the position of the currently drawn
        // pixel, followed by an AND with 0b111 to get just the value of that
        // pixel. The value is an index into the array of colors.
        if (c.fillStyle = palette[sprite < 8
                ? sprite : 0|sprite / 8 ** p & 7]) {
            c.fillRect(x + (p % 4), y + (0|p / 4), 1, 1);
        }
    }
},
// Draw a sprite on the minimap.
minimap = (cell, sprite) =>
    draw(sprite, cell % 30 * 4 + 500, (0|cell / 30) * 4 + 20),
// Draw a sprite in the main viewport. This only happens for the path indicators
// and the victory checkmark. The canvas needs to be scaled up and rotated. This
// makes the X look like an x :)
viewport = (cell, sprite) => {
    // Approximate scale(8, 8) and rotate(Math.PI / 4).
    c.setTransform(
            6, 6, -6, 6,
            (cell % 30 * 4 + 502 - offset_x) * 8,
            ((0|cell / 30) * 4 + 20 - offset_y) * 8);
    draw(sprite, 0, 0);
    c.resetTransform();
},
// PATH-FINDING
// For a given tile of the world array, return the 8 tiles neighboring with it
// on the map. Note: this makes the map wrap around horizontally.
neighbors = cell => [
    cell - 30, // N
    cell - 31, // NW
    cell - 1, // W
    cell + 29, // SW
    cell + 30, // S
    cell + 31, // SE
    cell + 1, // E
    cell - 29, // NE
],
// For a given tile, inspect its neighbors and increment their distance scores
// if they haven't been inspected yet. Non-passable terrain is represented as a
// non-numeric value which also fails the > check. If the computed distance
// score of the neighbor is lower than the previous one, assign it and
// recursively call distance on the neighbor's neighbors.
distance = cell => neighbors(cell).map((n, i) => {
    if (world[n] > world[cell] + 1 + i % 2) {
        world[n] = world[cell] + 1 + i % 2;
        distance(n);
    }
}),
// Trace the path connecting the player and the target. The tracing starts at
// the target and follows the descending gradient of distance scores stored in
// the world array. For each tile on the path, its neighbors are considered and
// the neighbor with a lower distance score is chosen as the next one. This
// function also updates the `next` global used in the game loop to move the
// player. The while loop makes sure that `next` is never set to the current
// position of the player. The last time `next` is updated it holds the index of
// the tile which is the closest to the player and on the path to the target.
trace = cell => {
    while (cell != player) {
        viewport(next = cell, 00000001000300000); // The dot
        neighbors(cell).map((n, i) => {
            if (world[n] < world[cell]) {
                cell = n;
            }
        });
    }
},
// Plan the player's movement in response to a click.
plan = cell => {
    // If this is the second click on the same tile, start moving towards it.
    moving = cell == target;
    // If the tile is reachable set it as the current target.
    if (world[cell] > 0 && world[cell] < Infinity) {
        target = cell;
    }
},
// GAME LOOP
tick = (v, cell = 900) => {
    // Draw the red background.
    // Also set the line width for the minimap visible area
    c.fillStyle = palette[c.lineWidth = 2];
    c.fillRect(0, 0, 640, 480);
    // Draw the minimap's black border.
    c.fillStyle = palette[1];
    c.fillRect(490, 10, 140, 140);
    // Draw the minimap.
    while (cell--) {
        // Generate the terrain adding a bit of high-frequency noise.
        v = 5 * Math.sin((cell % 30 - 17) * (cell - 300) / 3e3)
                + Math.sin(seed % cell) + 3;
        minimap(cell,
                v > 6 ? 01111111131115315: // rock
                v > 4 ? 05155444554455455: // tree
                v > 1 ? 5: // grass
                v > 0 ? 04445445555555555: // bush
                6 // water
        );
        // palette here is used as a non-numerical value (also when coerced)
        // which doesn't compare as less than nor greater than a number when
        // compared in distance(). It represents non-passable terrain.
        world[cell] = 0 < v && v <= 4 ? Infinity : palette;
    }
    minimap(critter, 01111111002102211); // black dragon
    minimap(player, 00707016107307762); // knight
    // Draw the main viewport by copying and scaling the minimap up.
    c.drawImage(a, offset_x, offset_y, 60, 60, 0, 0, 480, 480);
    // Draw the white border around the visible area on the minimap.
    c.strokeStyle = palette[7];
    c.strokeRect(offset_x, offset_y, 60, 60);
    // Populate the world array with distances of each tile to the player.
    world[player] = 0;
    distance(player);
    // Handle movement if the player if they haven't reached the target yet. The
    // world[target] check is similar to player != target, but it also avoids
    // drawing the path for the null target at the beginning of the game.
    if (world[target]) {
        // Trace the path from the target to the player.
        trace(target);
        // Draw the X mark at the target.
        viewport(target, 00010013103330030); // The X
        if (moving) {
            // Move the critter one tile away from the player, if possible.
            // The critter moves first to give it a chance to escape when the
            // player is on an adjacent tile. If the critter has been just
            // caught, critter is undefined and neighbors(critter) returns an
            // array of NaNs which is good enough for making this no-op.
            neighbors(critter).some(n =>
                // Filter the neighboring tiles by mixing in the seed (a
                // pseudo-random component) and the player's next position to
                // land somewhere far on the x axis and testing if sin() is
                // above zero. This has a similar effect as a random sort.
                Math.sin(n * next * seed) > 0
                // We use the > check rather than >= to force the critter to run
                // away from the player. E.g. if the player is at NW, the
                // critter will choose between S, SE, and E.
                && world[n] > world[critter]
                // If the tile is a good candidate for the critter's movement,
                // update the critter's position and return true to end the
                // some() iteration.
                && (critter = n)
            );
            // Move the player one tile along the traced path and check the
            // victory condition.
            if (critter == (player = next)) {
                // Clear the screen and draw the checkmark. critter is set to
                // undefined and acts as a flag disabling the onclick handler.
                critter = c.fillRect(0, 0, 640, 480);
                viewport(player, 00550054405400040); // The checkmark
            } else {
                // Schedule the next tick only if the critter is roaming free.
                setTimeout(tick);
            }
        }
    }
};
a.onclick = (e,
        x = e.x - a.offsetLeft,
        y = e.y - a.offsetTop) => {
    if (x < 480) {
        // Handle viewport clicks. Transform x, y into an index into the world
        // array taking the scaled offset into account.
        plan(
            0|(x / 8 + offset_x - 500) / 4
            + 30 * (0|(y / 8 + offset_y - 20) / 4));
    } else if (y < 140) {
        // Handle minimap clicks. Adjust the offset of the visible minimap
        // fragment.
        offset_x = x - 30;
        offset_y = y - 30;
    }
    // If the critter hasn't been caught yet, up