Jam version
authorcassowarii <cassowarii@users.noreply.github.com>
Sun, 1 Oct 2023 22:18:51 +0000 (15:18 -0700)
committercassowarii <cassowarii@users.noreply.github.com>
Sun, 1 Oct 2023 22:18:51 +0000 (15:18 -0700)
88 files changed:
.gitignore [new file with mode: 0644]
clicktostart.png [new file with mode: 0644]
cover.png [new file with mode: 0644]
covre.xcf [new file with mode: 0644]
game.js [new file with mode: 0644]
harefont.ttf [new file with mode: 0644]
harefont.zip [new file with mode: 0644]
img/backgrass.png [new file with mode: 0644]
img/buttons.aseprite [new file with mode: 0644]
img/buttons.png [new file with mode: 0644]
img/clicktocontinue.aseprite [new file with mode: 0644]
img/clicktocontinue.png [new file with mode: 0644]
img/end.aseprite [new file with mode: 0644]
img/end.png [new file with mode: 0644]
img/frontgrass.png [new file with mode: 0644]
img/grass.aseprite [new file with mode: 0644]
img/grass.png [new file with mode: 0644]
img/ground_tile.aseprite [new file with mode: 0644]
img/ground_tile.png [new file with mode: 0644]
img/level/1.aseprite [new file with mode: 0644]
img/level/1.png [new file with mode: 0644]
img/level/10.aseprite [new file with mode: 0644]
img/level/10.png [new file with mode: 0644]
img/level/2.aseprite [new file with mode: 0644]
img/level/2.png [new file with mode: 0644]
img/level/3.aseprite [new file with mode: 0644]
img/level/3.png [new file with mode: 0644]
img/level/4.aseprite [new file with mode: 0644]
img/level/4.png [new file with mode: 0644]
img/level/5.aseprite [new file with mode: 0644]
img/level/5.png [new file with mode: 0644]
img/level/6.aseprite [new file with mode: 0644]
img/level/6.png [new file with mode: 0644]
img/level/7.aseprite [new file with mode: 0644]
img/level/7.png [new file with mode: 0644]
img/level/8.aseprite [new file with mode: 0644]
img/level/8.png [new file with mode: 0644]
img/level/9.aseprite [new file with mode: 0644]
img/level/9.png [new file with mode: 0644]
img/patchy_tile.aseprite [new file with mode: 0644]
img/patchy_tile.png [new file with mode: 0644]
img/rabbits.aseprite [new file with mode: 0644]
img/rabbitsbrown.aseprite [new file with mode: 0644]
img/rabbitsbrown.png [new file with mode: 0644]
img/rabbitswhite.aseprite [new file with mode: 0644]
img/rabbitswhite.gif [new file with mode: 0644]
img/rabbitswhite.png [new file with mode: 0644]
img/selector.aseprite [new file with mode: 0644]
img/selector.gif [new file with mode: 0644]
img/selector.png [new file with mode: 0644]
img/sign_tile.aseprite [new file with mode: 0644]
img/sign_tile.png [new file with mode: 0644]
img/snow_tile.aseprite [new file with mode: 0644]
img/snow_tile.png [new file with mode: 0644]
img/title.aseprite [new file with mode: 0644]
img/title.png [new file with mode: 0644]
img/water_tile.aseprite [new file with mode: 0644]
img/water_tile.png [new file with mode: 0644]
img/welldone.aseprite [new file with mode: 0644]
img/welldone.png [new file with mode: 0644]
index.html [new file with mode: 0644]
levels.js [new file with mode: 0644]
levels/1.tmx [new file with mode: 0644]
levels/10.tmx [new file with mode: 0644]
levels/2.tmx [new file with mode: 0644]
levels/3.tmx [new file with mode: 0644]
levels/4.tmx [new file with mode: 0644]
levels/5.tmx [new file with mode: 0644]
levels/6.tmx [new file with mode: 0644]
levels/7.tmx [new file with mode: 0644]
levels/8.tmx [new file with mode: 0644]
levels/9.tmx [new file with mode: 0644]
levels/end.tmx [new file with mode: 0644]
levels/mapcompile.py [new file with mode: 0755]
levels/title.tmx [new file with mode: 0644]
levels/tmptile-sheet.png [new file with mode: 0644]
levels/tmptile.png [new file with mode: 0644]
loading.png [new file with mode: 0644]
music/haregamesong.mp3 [new file with mode: 0644]
pause.png [new file with mode: 0644]
sfx/alert.mp3 [new file with mode: 0644]
sfx/cronch.mp3 [new file with mode: 0644]
sfx/haregameyouwon.mp3 [new file with mode: 0644]
sfx/jump.mp3 [new file with mode: 0644]
sfx/nervous.mp3 [new file with mode: 0644]
sfx/push.mp3 [new file with mode: 0644]
sfx/whew.mp3 [new file with mode: 0644]
zucchinibread.js [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..1fbeb22
--- /dev/null
@@ -0,0 +1,2 @@
+*.swp
+packaging.zip
diff --git a/clicktostart.png b/clicktostart.png
new file mode 100644 (file)
index 0000000..213139e
Binary files /dev/null and b/clicktostart.png differ
diff --git a/cover.png b/cover.png
new file mode 100644 (file)
index 0000000..1b58a2a
Binary files /dev/null and b/cover.png differ
diff --git a/covre.xcf b/covre.xcf
new file mode 100644 (file)
index 0000000..c29c590
Binary files /dev/null and b/covre.xcf differ
diff --git a/game.js b/game.js
new file mode 100644 (file)
index 0000000..f1e5592
--- /dev/null
+++ b/game.js
@@ -0,0 +1,1102 @@
+"use strict";
+
+let game;
+
+let game_started = false;
+
+let level_number = 1;
+let intitle = true;
+let inend = false;
+
+let map;
+
+/* state:
+ * STAND: waiting for input
+ * DRAG: dragging hare path
+ * HOPDIR: thinking about where to hop
+ * HOP: hop animation
+ * WIN: won level
+ */
+let State = { STAND: 0, DRAG: 1, HOPDIR: 2, HOP: 3, WIN: 4 };
+
+let save_data = 1;
+const SAVE_KEY = "casso.haregame.save";
+
+zb.ready(function() {
+    game = zb.create_game({
+        canvas: 'canvas',
+        canvas_w: 648,
+        canvas_h: 720,
+        draw_scale: 3,
+        tile_size: 24,
+        level_w: 9,
+        level_h: 9,
+        background_color: 'black',
+        draw_func: do_draw,
+        update_func: do_update,
+        run_in_background: true,
+        //save_key: SAVE_KEY,
+        state: State.STAND,
+        events: {
+            keydown: handle_keydown,
+            keyup: handle_keyup,
+            mouseup: handle_mouseup,
+            mousedown: handle_mousedown,
+            mousemove: handle_mousemove,
+            mouseleave: handle_mouseleave,
+            gamestart: handle_gamestart,
+        },
+    });
+
+    game.buttons = {
+        undo: {
+            id: 0,
+            state: 0,
+            x: game.screen_w - 40,
+            y: -20,
+            w: 16,
+            h: 16,
+            callback: undo,
+        },
+        reset: {
+            id: 1,
+            state: 0,
+            x: game.screen_w - 20,
+            y: -20,
+            w: 16,
+            h: 16,
+            callback: reset,
+        },
+    };
+
+    game.register_sfx({
+        jump: {
+            path: 'sfx/jump.mp3',
+            volume: 0.4,
+        },
+        push: {
+            path: 'sfx/push.mp3',
+            volume: 0.1,
+        },
+        whew: {
+            path: 'sfx/whew.mp3',
+            volume: 1,
+        },
+        cronch: {
+            path: 'sfx/cronch.mp3',
+            volume: 1,
+        },
+        alert_sound: {
+            path: 'sfx/alert.mp3',
+            volume: 0.8,
+        },
+        youwon: {
+            path: 'sfx/haregameyouwon.mp3',
+            volume: 0.2,
+        },
+    });
+
+    game.register_images({
+        selector: 'img/selector.png',
+        hare_white: 'img/rabbitswhite.png',
+        hare_brown: 'img/rabbitsbrown.png',
+        ground_tile: 'img/ground_tile.png',
+        water_tile: 'img/water_tile.png',
+        snow_tile: 'img/snow_tile.png',
+        patchy_tile: 'img/patchy_tile.png',
+        sign_tile: 'img/sign_tile.png',
+        frontgrass: 'img/frontgrass.png',
+        backgrass: 'img/backgrass.png',
+        welldone: 'img/welldone.png',
+        clicktocontinue: 'img/clicktocontinue.png',
+        title: 'img/title.png',
+        end: 'img/end.png',
+        buttons: 'img/buttons.png',
+        level: {
+            1: 'img/level/1.png',
+            2: 'img/level/2.png',
+            3: 'img/level/3.png',
+            4: 'img/level/4.png',
+            5: 'img/level/5.png',
+            6: 'img/level/6.png',
+            7: 'img/level/7.png',
+            8: 'img/level/8.png',
+            9: 'img/level/9.png',
+            10: 'img/level/10.png',
+        }
+    });
+
+    game.register_music({
+        song: {
+            path: 'music/haregamesong',
+            volume: 0.2,
+        },
+        nervous: {
+            path: 'sfx/nervous',
+            volume: 0.6,
+        },
+    });
+
+    game.resources_ready();
+});
+
+/* ---- constants ---- */
+
+let TOP_BAR_SIZE = 24; /* height of top bar */
+
+let CURSOR_CHANGE_SPEED = 300;
+let WATER_CHANGE_SPEED = 1000;
+
+let HARE_LAST_HOP_FRAME = 5; /* index of last frame before hop ends */
+let HARE_OFFSET = 3; /* hare vertical offset from tile */
+let HARE_STAND_FRAME_LENGTH = 50; /* frames when standing to anxious state */
+let HARE_QUIVER_FRAME_LENGTH = 100; /* frame length for nervous animation */
+let HARE_SIT_FRAME_LENGTH = 100; /* frames when sitting from anxious state */
+
+let GRASS_OFFSET = 2; /* how much draw grass up from grid ?? */
+
+let Tile = {
+    WATER: 0,
+    GROUND: 1,
+    SNOW: 2,
+    PATCHY: 3,
+    SIGN: 4,
+}
+
+let HareType = {
+    WHITE: 0,
+    BROWN: 1,
+}
+
+let Dir = {
+    DOWN: 0,
+    LEFT: 1,
+    RIGHT: 2,
+    UP: 3,
+}
+
+/* ---- global state vars ---- */
+
+let mouse_x = null, mouse_y = null;
+
+let selector = {
+    x: null,
+    y: null,
+    frame: 0,
+    timer: 0,
+};
+
+let path = [];
+let path_extra = [];
+
+let hares = [];
+let grass = [];
+let backgrass = [];
+
+let hop_target = null;
+let hop_frame_length = 100;
+
+let push_fraction = 0; /* for calculation of how far we've pushed our friend */
+
+let water = {
+    frame: 0,
+    timer: 0,
+}
+
+let win_title_offset;
+let ctc_alpha;
+
+let playing_anxious_sound = false;
+
+/* ---- random convenience funcs ---- */
+
+function delete_save() {
+    try {
+        // delete save
+    } catch (e) {
+        console.error("oops, can't save! though that uh... doesn't matter here");
+    }
+}
+
+function save() {
+    try {
+        // save
+    } catch (e) {
+        console.error("oops, can't save!", e);
+    }
+}
+
+function handle_gamestart(game) {
+    console.log("Game start!");
+
+    game.music.song.play();
+
+    // initialize game
+    load_level_data(levels.title);
+}
+
+let undo_stack = [];
+
+function create_undo_point() {
+    let undo_point = {};
+
+    undo_point.hares = zb.copy_flat_objlist(hares);
+    undo_point.grass = zb.copy_flat_objlist(grass);
+
+    undo_stack.push(undo_point);
+}
+
+function undo() {
+    // perform undo
+    if (undo_stack.length) {
+        let state = undo_stack.pop();
+
+        hares = state.hares;
+        grass = state.grass;
+        backgrass = [];
+        for (let g of state.grass) {
+            backgrass.push({
+                is_backgrass: true,
+                x: g.x,
+                y: g.y,
+                eaten: g.eaten,
+            });
+        }
+
+        game.state = State.STAND;
+        path = [];
+        path_extra = [];
+        update_selector(mouse_x, mouse_y);
+    }
+}
+
+function reset() {
+    game.start_transition(zb.transition.FADE, 500, function() {
+        load_level();
+        game.state = State.STAND;
+    });
+}
+
+function advance_level() {
+    if (level_number === 10) {
+        win_everything();
+    } else {
+        game.long_transition(zb.transition.FADE, 350, function() {
+            undo_stack = [];
+            level_number ++;
+            load_level();
+            game.state = State.STAND;
+        });
+    }
+}
+
+function win_everything() {
+    game.long_transition(zb.transition.FADE, 1000, function() {
+        inend = true;
+        load_level_data(levels.end);
+    });
+}
+
+function load_level() {
+    if (level_number > Object.keys(levels).length) {
+        win_everything();
+    } else {
+        console.log(levels);
+        console.log(level_number);
+        console.log(levels[level_number]);
+        load_level_data(levels[level_number]);
+    }
+}
+
+function load_level_data(lvl) {
+    game.state = State.STAND;
+
+    map = lvl.map;
+    console.log("map:", map);
+
+    path = [];
+    path_extra = [];
+
+    hares = [];
+    for (let h of lvl.hares) {
+        create_hare(h.x, h.y, h.color === 'brown' ? HareType.BROWN : HareType.WHITE);
+    }
+
+    grass = [];
+    backgrass = [];
+    for (let g of lvl.grass) {
+        create_grass(g.x, g.y);
+    }
+}
+
+function create_hare(x, y, type) {
+    hares.push({
+        is_hare: true,
+        x: x,
+        y: y,
+        type: type,
+        dir: Dir.DOWN,
+        frame: 0,
+        timer: 0,
+    });
+}
+
+function create_grass(x, y) {
+    grass.push({
+        is_grass: true,
+        x: x,
+        y: y,
+        eaten: false,
+    });
+
+    backgrass.push({
+        is_backgrass: true,
+        x: x,
+        y: y,
+        eaten: false,
+    });
+}
+
+function hare_matches_tile(hare, tile) {
+    return tile === Tile.PATCHY
+        || hare_strictly_matches_tile(hare, tile);
+}
+
+function hare_strictly_matches_tile(hare, tile) {
+    return hare.type === HareType.BROWN && tile === Tile.GROUND
+        || hare.type === HareType.WHITE && tile === Tile.SNOW;
+}
+
+/* ---- buttons ---- */
+
+
+/* ---- thing finding funcs ---- */
+
+function tile_at(x, y) {
+    if (x < 0 || x >= game.level_w || y < 0 || y >= game.level_h) {
+        return Tile.WATER;
+    }
+    return map[y * game.level_w + x];
+}
+
+function hare_at(x, y) {
+    let list = hares.filter(h => h.x === x && h.y === y);
+    if (list.length) {
+        return list[0];
+    } else {
+        return null;
+    }
+}
+
+function grass_at(x, y) {
+    let frontlist = grass.filter(g => g.x === x && g.y === y);
+    let backlist = backgrass.filter(g => g.x === x && g.y === y);
+    if (frontlist.length && backlist.length) {
+        return { front: frontlist[0], back: backlist[0] };
+    } else {
+        return null;
+    }
+}
+
+function can_push_to(x, y) {
+    return tile_at(x, y) !== Tile.WATER;
+}
+
+/* ---- update ---- */
+
+/* MAIN UPDATE FUNCTION */
+function do_update(delta) {
+    selector.timer += delta;
+    while (selector.timer > CURSOR_CHANGE_SPEED) {
+        selector.timer -= CURSOR_CHANGE_SPEED;
+        selector.frame ++;
+        selector.frame = zb.mod(selector.frame, 2);
+    }
+
+    water.timer += delta;
+    while (water.timer > WATER_CHANGE_SPEED) {
+        water.timer -= WATER_CHANGE_SPEED;
+        water.frame ++;
+        water.frame = zb.mod(water.frame, 2);
+    }
+
+    update_hare_anxiety(delta);
+
+    update_hare_movement(delta);
+
+    if (game.state === State.WIN) {
+        if (win_title_offset > 1) {
+            win_title_offset /= 1.08;
+            ctc_alpha = 0;
+        } else {
+            win_title_offset = 0;
+            if (ctc_alpha < 1) {
+                ctc_alpha += delta / 500;
+            } else {
+                ctc_alpha = 1;
+            }
+        }
+    }
+}
+
+function update_hare_movement(delta) {
+    if (game.state === State.HOPDIR) {
+        push_fraction = 0;
+        for (let h of hares) {
+            if (h.being_pushed) {
+                h.x = h.target_x;
+                h.y = h.target_y;
+                delete h.target_x;
+                delete h.target_y;
+            }
+            h.being_pushed = false;
+        }
+
+        if (path.length > 0) {
+            let next = path.shift();
+            let dx, dy;
+            if (next.x === hop_target.x - 1) {
+                hop_target.dir = Dir.LEFT;
+                dx = -1; dy = 0;
+            } else if (next.x === hop_target.x + 1) {
+                hop_target.dir = Dir.RIGHT;
+                dx = 1; dy = 0;
+            } else if (next.y === hop_target.y - 1) {
+                hop_target.dir = Dir.UP;
+                dx = 0; dy = -1;
+            } else {
+                hop_target.dir = Dir.DOWN;
+                dx = 0; dy = 1;
+            }
+            game.state = State.HOP;
+            game.sfx.jump.play();
+            hop_target.frame = 1;
+            hop_target.timer = 0;
+            hop_target.target_x = next.x;
+            hop_target.target_y = next.y;
+            let h = hare_at(next.x, next.y);
+            if (h) {
+                if (can_push_to(h.x + dx, h.y + dy)) {
+                    h.target_x = h.x + dx;
+                    h.target_y = h.y + dy;
+                    h.being_pushed = true;
+                    game.sfx.push.play();
+                } else {
+                    cancel_move();
+                }
+            }
+        } else {
+            cancel_move();
+        }
+    } else if (game.state === State.HOP) {
+        hop_target.timer += delta;
+        while (hop_target.timer > hop_frame_length) {
+            hop_target.timer -= hop_frame_length;
+            hop_target.frame ++;
+            push_fraction = (hop_target.frame - hop_target.timer / hop_frame_length) / (HARE_LAST_HOP_FRAME + 1);
+            if (hop_target.frame > HARE_LAST_HOP_FRAME) {
+                game.state = State.HOPDIR;
+                hop_target.frame = 1;
+                hop_target.timer = 0;
+                hop_target.x = hop_target.target_x;
+                hop_target.y = hop_target.target_y;
+                if (!hare_matches_tile(hop_target, tile_at(hop_target.x, hop_target.y))) {
+                    hop_target.anxiety = true;
+                    path = [];
+                    path_extra = [];
+                } else {
+                    /* Eat any grass on the tile */
+                    eat_grass(hop_target.x, hop_target.y);
+                }
+                break;
+            }
+        }
+    }
+}
+
+function eat_grass(x, y) {
+    let g = grass_at(x, y);
+    if (g) {
+        if (!g.front.eaten) {
+            game.sfx.cronch.play();
+        }
+        g.front.eaten = true;
+        g.back.eaten = true;
+    }
+    check_victory();
+}
+
+function cancel_move() {
+    selector.x = null;
+    selector.y = null;
+    path = [];
+    path_extra = [];
+    update_selector(mouse_x, mouse_y);
+    game.state = State.STAND;
+    hop_target.frame = 0;
+    hop_target.timer = 0;
+}
+
+function update_hare_anxiety(delta) {
+    /* Hare Anxiety */
+    let any_anxiety = false;
+    for (let h of hares) {
+        if (hare_matches_tile(h, tile_at(h.x, h.y))) {
+            h.anxiety = false;
+        } else {
+            h.anxiety = true;
+            any_anxiety = true;
+        }
+
+        if (h.anxiety && h.frame < 7) {
+            h.frame = 7;
+            game.sfx.alert_sound.play();
+        } else if (h.anxiety && h.frame < 10) {
+            h.timer += delta;
+            while (h.timer > HARE_STAND_FRAME_LENGTH && h.frame < 10) {
+                h.timer -= HARE_STAND_FRAME_LENGTH
+                h.frame ++;
+            }
+            if (h.frame === 10) {
+                h.timer = 0;
+            }
+        } else if (h.anxiety) {
+            /* alternate between frames 10 and 11 */
+            h.timer += delta;
+            while (h.timer > HARE_QUIVER_FRAME_LENGTH) {
+                h.timer -= HARE_QUIVER_FRAME_LENGTH;
+                if (h.frame === 10) {
+                    h.frame = 11;
+                } else {
+                    h.frame = 10;
+                }
+            }
+        } else if (!h.anxiety && h.frame === 10 || h.frame === 11) {
+            h.frame = 12;
+            h.timer = 0;
+            game.sfx.whew.play();
+        } else if (!h.anxiety && h.frame > 7) {
+            h.timer += delta;
+            while (h.timer > HARE_SIT_FRAME_LENGTH) {
+                h.timer -= HARE_SIT_FRAME_LENGTH
+                h.frame ++;
+                if (h.frame > 14) {
+                    h.frame = 0;
+                    h.timer = 0;
+                    eat_grass(h.x, h.y);
+                }
+            }
+        }
+    }
+
+    if (any_anxiety && !playing_anxious_sound) {
+        game.music.nervous.play();
+        playing_anxious_sound = true;
+    } else if (!any_anxiety && playing_anxious_sound) {
+        game.music.nervous.pause();
+        playing_anxious_sound = false;
+    }
+}
+
+function check_victory() {
+    for (let g of grass) {
+        if (!g.eaten) {
+            return false;
+        }
+    }
+
+    for (let h of hares) {
+        if (!hare_strictly_matches_tile(h, tile_at(h.x, h.y))) {
+            return false;
+        }
+    }
+
+    win();
+}
+
+function win() {
+    for (let h of hares) {
+        h.frame = 0;
+    }
+
+    console.log("You won!");
+    win_title_offset = game.screen_h;
+    game.sfx.youwon.play();
+    game.state = State.WIN;
+}
+
+/* ---- draw ---- */
+
+/* DRAW */
+function do_draw(ctx) {
+    ctx.save();
+    ctx.translate(0, TOP_BAR_SIZE);
+
+    draw_map(ctx);
+
+    draw_objects(ctx);
+
+    draw_selector(ctx);
+
+    draw_ui(ctx);
+
+    ctx.restore();
+}
+
+function rpgmaker_tile_format_thing(x, y, tile_type) {
+    let u = tile_at(x, y - 1);
+    let d = tile_at(x, y + 1);
+    let l = tile_at(x - 1, y);
+    let r = tile_at(x + 1, y);
+    let ul = tile_at(x - 1, y - 1);
+    let ur = tile_at(x + 1, y - 1);
+    let dl = tile_at(x - 1, y + 1);
+    let dr = tile_at(x + 1, y + 1);
+    let ulx, uly, urx, ury, dlx, dly, drx, dry;
+
+    /* Draw top left */
+    if (u === tile_type) {
+        if (l === tile_type) {
+            if (ul === tile_type) {
+                /* Open water */
+                ulx = 2; uly = 4;
+            } else {
+                /* Upper left corner */
+                ulx = 2; uly = 0;
+            }
+        } else {
+            /* Left border */
+            ulx = 0; uly = 4;
+        }
+    } else {
+        if (l === tile_type) {
+            /* Top border */
+            ulx = 2; uly = 2;
+        } else {
+            /* Very corner */
+            ulx = 0; uly = 2;
+        }
+    }
+
+    /* Draw top right */
+    if (u === tile_type) {
+        if (r === tile_type) {
+            if (ur === tile_type) {
+                /* Open water */
+                urx = 1; ury = 4;
+            } else {
+                /* Upper right corner */
+                urx = 3; ury = 0;
+            }
+        } else {
+            /* Right border */
+            urx = 3; ury = 4;
+        }
+    } else {
+        if (r === tile_type) {
+            /* Top border */
+            urx = 1; ury = 2;
+        } else {
+            /* Very corner */
+            urx = 3; ury = 2;
+        }
+    }
+
+    /* Draw bottom left */
+    if (d === tile_type) {
+        if (l === tile_type) {
+            if (dl === tile_type) {
+                /* Open water */
+                dlx = 2; dly = 3;
+            } else {
+                /* Bottom left corner */
+                dlx = 2; dly = 1;
+            }
+        } else {
+            /* Left border */
+            dlx = 0; dly = 3;
+        }
+    } else {
+        if (l === tile_type) {
+            /* Bottom border */
+            dlx = 2; dly = 5;
+        } else {
+            /* Very corner */
+            dlx = 0; dly = 5;
+        }
+    }
+
+    /* Draw bottom right */
+    if (d === tile_type) {
+        if (r === tile_type) {
+            if (dr === tile_type) {
+                /* Open water */
+                drx = 1; dry = 3;
+            } else {
+                /* Bottom right corner */
+                drx = 3; dry = 1;
+            }
+        } else {
+            /* Right border */
+            drx = 3; dry = 3;
+        }
+    } else {
+        if (r === tile_type) {
+            /* Bottom border */
+            drx = 1; dry = 5;
+        } else {
+            /* Very corner */
+            drx = 3; dry = 5;
+        }
+    }
+
+    return { ulx: ulx, uly: uly, urx: urx, ury: ury, dlx: dlx, dly: dly, drx: drx, dry: dry };
+}
+
+function draw_map(ctx) {
+    for (let y = 0; y < game.level_h; y++) {
+        for (let x = 0; x < game.level_w; x++) {
+            if ((x + y) % 2 == 1) {
+                ctx.fillStyle = 'cornflowerblue';
+                ctx.fillRect(x * game.tile_size, y * game.tile_size, game.tile_size, game.tile_size);
+            }
+        }
+    }
+
+    let ts = game.tile_size;
+    for (let y = 0; y < game.level_h; y++) {
+        for (let x = 0; x < game.level_w; x++) {
+            if (tile_at(x, y) === Tile.GROUND) {
+                zb.sprite_draw(ctx, game.img.ground_tile, ts, ts, 0, (x + y) % 2, x * ts, y * ts);
+            } else if (tile_at(x, y) === Tile.WATER) {
+                let { ulx, uly, urx, ury, dlx, dly, drx, dry } = rpgmaker_tile_format_thing(x, y, Tile.WATER);
+                /* Correct for frames with water animation. I really need to put tile/sprite animations
+                 * into the actual engine, this stuff always turns into spaghetti code */
+                uly += 6 * water.frame;
+                ury += 6 * water.frame;
+                dly += 6 * water.frame;
+                dry += 6 * water.frame;
+                zb.sprite_draw(ctx, game.img.water_tile, ts / 2, ts / 2, ulx, uly, x * ts, y * ts);
+                zb.sprite_draw(ctx, game.img.water_tile, ts / 2, ts / 2, urx, ury, x * ts + ts / 2, y * ts);
+                zb.sprite_draw(ctx, game.img.water_tile, ts / 2, ts / 2, dlx, dly, x * ts, y * ts + ts / 2);
+                zb.sprite_draw(ctx, game.img.water_tile, ts / 2, ts / 2, drx, dry, x * ts + ts / 2, y * ts + ts / 2);
+            } else if (tile_at(x, y) === Tile.SNOW) {
+                let { ulx, uly, urx, ury, dlx, dly, drx, dry } = rpgmaker_tile_format_thing(x, y, Tile.SNOW);
+                zb.sprite_draw(ctx, game.img.snow_tile, ts / 2, ts / 2, ulx, uly, x * ts, y * ts);
+                zb.sprite_draw(ctx, game.img.snow_tile, ts / 2, ts / 2, urx, ury, x * ts + ts / 2, y * ts);
+                zb.sprite_draw(ctx, game.img.snow_tile, ts / 2, ts / 2, dlx, dly, x * ts, y * ts + ts / 2);
+                zb.sprite_draw(ctx, game.img.snow_tile, ts / 2, ts / 2, drx, dry, x * ts + ts / 2, y * ts + ts / 2);
+            } else if (tile_at(x, y) === Tile.PATCHY) {
+                let { ulx, uly, urx, ury, dlx, dly, drx, dry } = rpgmaker_tile_format_thing(x, y, Tile.PATCHY);
+                zb.sprite_draw(ctx, game.img.patchy_tile, ts / 2, ts / 2, ulx, uly, x * ts, y * ts);
+                zb.sprite_draw(ctx, game.img.patchy_tile, ts / 2, ts / 2, urx, ury, x * ts + ts / 2, y * ts);
+                zb.sprite_draw(ctx, game.img.patchy_tile, ts / 2, ts / 2, dlx, dly, x * ts, y * ts + ts / 2);
+                zb.sprite_draw(ctx, game.img.patchy_tile, ts / 2, ts / 2, drx, dry, x * ts + ts / 2, y * ts + ts / 2);
+            } else if (tile_at(x, y) === Tile.SIGN) {
+                let { ulx, uly, urx, ury, dlx, dly, drx, dry } = rpgmaker_tile_format_thing(x, y, Tile.SIGN);
+                zb.sprite_draw(ctx, game.img.sign_tile, ts / 2, ts / 2, ulx, uly, x * ts, y * ts);
+                zb.sprite_draw(ctx, game.img.sign_tile, ts / 2, ts / 2, urx, ury, x * ts + ts / 2, y * ts);
+                zb.sprite_draw(ctx, game.img.sign_tile, ts / 2, ts / 2, dlx, dly, x * ts, y * ts + ts / 2);
+                zb.sprite_draw(ctx, game.img.sign_tile, ts / 2, ts / 2, drx, dry, x * ts + ts / 2, y * ts + ts / 2);
+            }
+        }
+    }
+
+    if (game.img.level[level_number] && !intitle && !inend) {
+        ctx.save();
+        ctx.translate(0, -TOP_BAR_SIZE);
+        zb.screen_draw(ctx, game.img.level[level_number]);
+        ctx.restore();
+    }
+}
+
+function draw_objects(ctx) {
+    /* dumb way to do this */
+    let objs = [ ...backgrass, ...hares, ...grass ];
+    objs.sort((a, b) => {
+        let ay = a.y;
+        if (a.target_y && a.target_y > a.y) {
+            ay = a.target_y;
+        }
+        let by = b.y;
+        if (b.target_y && b.target_y > b.y) {
+            by = b.target_y;
+        }
+        return ay - by;
+    });
+
+    for (let o of objs) {
+        if (o.is_hare) {
+            draw_hare(ctx, o);
+        } else if (o.is_backgrass) {
+            draw_backgrass(ctx, o);
+        } else if (o.is_grass) {
+            draw_grass(ctx, o);
+        }
+    }
+}
+
+function draw_hare(ctx, h) {
+    let img;
+    if (h.type === HareType.WHITE) {
+        img = game.img.hare_white;
+    } else if (h.type === HareType.BROWN) {
+        img = game.img.hare_brown;
+    }
+
+    let ts = game.tile_size;
+    let draw_x = h.x * ts - ts;
+    let draw_y = h.y * ts - ts - HARE_OFFSET;
+    if (h.being_pushed) {
+        let pf = push_fraction;
+        /* Slide them along in accordance with push_fraction if they're being pushed */
+        draw_x = (h.x * (1 - pf) + h.target_x * pf) * ts - ts;
+        draw_y = (h.y * (1 - pf) + h.target_y * pf) * ts - ts - HARE_OFFSET;
+    }
+
+    zb.sprite_draw(ctx, img, 72, 72, h.dir, h.frame, draw_x, draw_y);
+}
+
+function draw_backgrass(ctx, g) {
+    let ts = game.tile_size;
+    zb.sprite_draw(ctx, game.img.backgrass, 72, 72, 0, g.eaten ? 1 : 0, g.x * ts - ts, g.y * ts - ts - GRASS_OFFSET);
+}
+
+function draw_grass(ctx, g) {
+    let ts = game.tile_size;
+    zb.sprite_draw(ctx, game.img.frontgrass, 72, 72, 0, g.eaten ? 1 : 0, g.x * ts - ts, g.y * ts - ts - GRASS_OFFSET);
+}
+
+function draw_selector(ctx) {
+    let ts = game.tile_size;
+
+    if (game.state === State.STAND || game.state === State.DRAG) {
+        if (selector.x !== null && selector.y !== null) {
+            zb.sprite_draw(ctx, game.img.selector, 72, 72, 0, selector.frame, selector.x * ts - ts, selector.y * ts - ts);
+        }
+    }
+
+    if (game.state === State.DRAG) {
+        for (let component of path) {
+            zb.sprite_draw(ctx, game.img.selector, 72, 72, 0, 2, component.x * ts - ts, component.y * ts - ts);
+        }
+
+        for (let component of path_extra) {
+            zb.sprite_draw(ctx, game.img.selector, 72, 72, 0, 3 + component.dir, component.x * ts - ts, component.y * ts - ts);
+        }
+    }
+}
+
+function draw_ui(ctx) {
+    if (game.state === State.WIN) {
+        ctx.save();
+        ctx.translate(0, Math.round(-win_title_offset));
+        zb.screen_draw(ctx, game.img.welldone);
+        if (win_title_offset === 0) {
+            ctx.save();
+            ctx.globalAlpha = ctc_alpha;
+            zb.screen_draw(ctx, game.img.clicktocontinue);
+            ctx.restore();
+        }
+        ctx.restore();
+    }
+
+    if (intitle) {
+        ctx.save();
+        ctx.translate(0, -TOP_BAR_SIZE);
+        zb.screen_draw(ctx, game.img.title);
+        ctx.restore();
+    } else if (inend) {
+        ctx.save();
+        ctx.translate(0, -TOP_BAR_SIZE);
+        zb.screen_draw(ctx, game.img.end);
+        ctx.restore();
+    } else {
+        /* Reset button */
+        zb.sprite_draw(ctx, game.img.buttons, 16, 16, 1, game.buttons.reset.state, game.screen_w - 20, -20);
+        /* Undo button */
+        zb.sprite_draw(ctx, game.img.buttons, 16, 16, 0, game.buttons.undo.state, game.screen_w - 40, -20);
+    }
+}
+
+
+/* ---- event handlers ---- */
+
+function handle_keydown(game, e) {
+    // key down
+}
+
+let x_pressed = false;
+function handle_keyup(game, e) {
+    // key up
+    switch (e.key) {
+        case 'm':
+            game.toggle_mute();
+            e.preventDefault();
+            break;
+        case 'r':
+            reset();
+            e.preventDefault();
+            break;
+        case 'z':
+            undo();
+            e.preventDefault();
+            break;
+        case 'x':
+            x_pressed = true;
+            break;
+        case 'w':
+            if (x_pressed) {
+                delete_save();
+                e.preventDefault();
+            }
+            break;
+    }
+
+    if (e.keyCode !== 88) {
+        /* non-X key */
+        x_pressed = false;
+    }
+}
+
+function handle_mousedown(game) {
+    if (intitle) {
+        game.long_transition(zb.transition.FADE, 1000, function() {
+            intitle = false;
+            load_level();
+        });
+    }
+
+    if (inend) {
+        game.long_transition(zb.transition.FADE, 1000, function() {
+            inend = false;
+            intitle = true;
+            level_number = 1;
+            load_level_data(levels.title);
+        });
+    }
+
+    for (let b of Object.values(game.buttons)) {
+        if (b.state === 1) {
+            b.state = 2;
+            return;
+        }
+    }
+
+    if (game.state === State.STAND) {
+        if (selector.x !== null && selector.y !== null) {
+            let hare = hare_at(selector.x, selector.y);
+            if (hare && hare_matches_tile(hare, tile_at(hare.x, hare.y))) {
+                game.state = State.DRAG;
+                hop_target = hare;
+            }
+        }
+    }
+
+    if (game.state === State.WIN && win_title_offset === 0) {
+        advance_level();
+    }
+}
+
+function handle_mouseup(game) {
+    for (let b of Object.values(game.buttons)) {
+        if (b.state === 2) {
+            b.callback();
+            b.state = 1;
+            return;
+        }
+    }
+
+    if (game.state === State.DRAG) {
+        if (path.length > 0) {
+            create_undo_point();
+            game.state = State.HOPDIR;
+            hop_frame_length = Math.max(5, 100 - Math.log(path.length) / Math.log(1.3) * 7);
+        } else {
+            game.state = State.STAND;
+        }
+    }
+}
+
+function update_selector(x, y) {
+    let tile_x = Math.floor(x / game.tile_size);
+    let tile_y = Math.floor(y / game.tile_size);
+
+    selector.x = null;
+    selector.y = null;
+
+    if (intitle || inend) return;
+
+    if (0 <= tile_x && tile_x < game.level_w && 0 <= tile_y && tile_y < game.level_h) {
+        selector.x = tile_x;
+        selector.y = tile_y;
+    }
+}
+
+function handle_mousemove(game, e, x, canvy) {
+    let y = canvy - TOP_BAR_SIZE;
+
+    mouse_x = x;
+    mouse_y = y;
+
+    for (let b of Object.values(game.buttons)) {
+        if (mouse_x >= b.x && mouse_x < b.x + b.w && mouse_y >= b.y && mouse_y < b.y + b.h) {
+            b.state = 1;
+        } else {
+            b.state = 0;
+        }
+    }
+
+    if (game.state === State.STAND) {
+        update_selector(x, y);
+    } else if (game.state === State.DRAG) {
+        let tile_x = Math.floor(x / game.tile_size);
+        let tile_y = Math.floor(y / game.tile_size);
+
+        let tile_list = [ selector, ...path ];
+        let last = tile_list[tile_list.length - 1];
+
+        let revert = { x: null, y: null };
+        if (tile_list.length > 1) {
+            revert = tile_list[tile_list.length - 2];
+        }
+
+        if (tile_x === revert.x && tile_y === revert.y) {
+            path.pop();
+            path_extra.pop();
+        } else if (tile_x !== last.x || tile_y !== last.y) {
+            if (tile_x === last.x - 1 && tile_y === last.y
+                    || tile_x === last.x + 1 && tile_y === last.y
+                    || tile_x === last.x && tile_y === last.y + 1
+                    || tile_x === last.x && tile_y === last.y - 1) {
+                if (0 <= tile_x && tile_x < game.level_w && 0 <= tile_y && tile_y < game.level_h) {
+                    if (tile_at(tile_x, tile_y) !== Tile.WATER) {
+                        if (hare_matches_tile(hop_target, tile_at(last.x, last.y))) {
+                            path.push({ x: tile_x, y: tile_y });
+                            if (last.x - 1 === tile_x) {
+                                path_extra.push({ x: last.x, y: last.y, dir: Dir.LEFT });
+                            } else if (last.x + 1 === tile_x) {
+                                path_extra.push({ x: last.x, y: last.y, dir: Dir.RIGHT });
+                            } else if (last.y - 1 === tile_y) {
+                                path_extra.push({ x: last.x, y: last.y, dir: Dir.UP });
+                            } else if (last.y + 1 === tile_y) {
+                                path_extra.push({ x: last.x, y: last.y, dir: Dir.DOWN });
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+function handle_mouseleave(game, e) {
+    if (game.state === State.STAND) {
+        selector.x = null;
+        selector.y = null;
+    }
+
+    for (let b of Object.values(game.buttons)) {
+        b.state = 0;
+    }
+}
diff --git a/harefont.ttf b/harefont.ttf
new file mode 100644 (file)
index 0000000..40af6aa
Binary files /dev/null and b/harefont.ttf differ
diff --git a/harefont.zip b/harefont.zip
new file mode 100644 (file)
index 0000000..86cd2b0
Binary files /dev/null and b/harefont.zip differ
diff --git a/img/backgrass.png b/img/backgrass.png
new file mode 100644 (file)
index 0000000..a9b25d7
Binary files /dev/null and b/img/backgrass.png differ
diff --git a/img/buttons.aseprite b/img/buttons.aseprite
new file mode 100644 (file)
index 0000000..f4a4101
Binary files /dev/null and b/img/buttons.aseprite differ
diff --git a/img/buttons.png b/img/buttons.png
new file mode 100644 (file)
index 0000000..d693863
Binary files /dev/null and b/img/buttons.png differ
diff --git a/img/clicktocontinue.aseprite b/img/clicktocontinue.aseprite
new file mode 100644 (file)
index 0000000..462bb65
Binary files /dev/null and b/img/clicktocontinue.aseprite differ
diff --git a/img/clicktocontinue.png b/img/clicktocontinue.png
new file mode 100644 (file)
index 0000000..87a3089
Binary files /dev/null and b/img/clicktocontinue.png differ
diff --git a/img/end.aseprite b/img/end.aseprite
new file mode 100644 (file)
index 0000000..540db19
Binary files /dev/null and b/img/end.aseprite differ
diff --git a/img/end.png b/img/end.png
new file mode 100644 (file)
index 0000000..b4a37eb
Binary files /dev/null and b/img/end.png differ
diff --git a/img/frontgrass.png b/img/frontgrass.png
new file mode 100644 (file)
index 0000000..0c9cc7c
Binary files /dev/null and b/img/frontgrass.png differ
diff --git a/img/grass.aseprite b/img/grass.aseprite
new file mode 100644 (file)
index 0000000..363caf2
Binary files /dev/null and b/img/grass.aseprite differ
diff --git a/img/grass.png b/img/grass.png
new file mode 100644 (file)
index 0000000..7ca5d8d
Binary files /dev/null and b/img/grass.png differ
diff --git a/img/ground_tile.aseprite b/img/ground_tile.aseprite
new file mode 100644 (file)
index 0000000..ff0381e
Binary files /dev/null and b/img/ground_tile.aseprite differ
diff --git a/img/ground_tile.png b/img/ground_tile.png
new file mode 100644 (file)
index 0000000..d5ae362
Binary files /dev/null and b/img/ground_tile.png differ
diff --git a/img/level/1.aseprite b/img/level/1.aseprite
new file mode 100644 (file)
index 0000000..03c6aea
Binary files /dev/null and b/img/level/1.aseprite differ
diff --git a/img/level/1.png b/img/level/1.png
new file mode 100644 (file)
index 0000000..29ee1ca
Binary files /dev/null and b/img/level/1.png differ
diff --git a/img/level/10.aseprite b/img/level/10.aseprite
new file mode 100644 (file)
index 0000000..4839725
Binary files /dev/null and b/img/level/10.aseprite differ
diff --git a/img/level/10.png b/img/level/10.png
new file mode 100644 (file)
index 0000000..47fa034
Binary files /dev/null and b/img/level/10.png differ
diff --git a/img/level/2.aseprite b/img/level/2.aseprite
new file mode 100644 (file)
index 0000000..dd2e7dc
Binary files /dev/null and b/img/level/2.aseprite differ
diff --git a/img/level/2.png b/img/level/2.png
new file mode 100644 (file)
index 0000000..058dd77
Binary files /dev/null and b/img/level/2.png differ
diff --git a/img/level/3.aseprite b/img/level/3.aseprite
new file mode 100644 (file)
index 0000000..1bbcf7c
Binary files /dev/null and b/img/level/3.aseprite differ
diff --git a/img/level/3.png b/img/level/3.png
new file mode 100644 (file)
index 0000000..b1a0f44
Binary files /dev/null and b/img/level/3.png differ
diff --git a/img/level/4.aseprite b/img/level/4.aseprite
new file mode 100644 (file)
index 0000000..ceeec22
Binary files /dev/null and b/img/level/4.aseprite differ
diff --git a/img/level/4.png b/img/level/4.png
new file mode 100644 (file)
index 0000000..462a931
Binary files /dev/null and b/img/level/4.png differ
diff --git a/img/level/5.aseprite b/img/level/5.aseprite
new file mode 100644 (file)
index 0000000..7b0fd43
Binary files /dev/null and b/img/level/5.aseprite differ
diff --git a/img/level/5.png b/img/level/5.png
new file mode 100644 (file)
index 0000000..b213a26
Binary files /dev/null and b/img/level/5.png differ
diff --git a/img/level/6.aseprite b/img/level/6.aseprite
new file mode 100644 (file)
index 0000000..c6c13f3
Binary files /dev/null and b/img/level/6.aseprite differ
diff --git a/img/level/6.png b/img/level/6.png
new file mode 100644 (file)
index 0000000..20326ff
Binary files /dev/null and b/img/level/6.png differ
diff --git a/img/level/7.aseprite b/img/level/7.aseprite
new file mode 100644 (file)
index 0000000..e292f14
Binary files /dev/null and b/img/level/7.aseprite differ
diff --git a/img/level/7.png b/img/level/7.png
new file mode 100644 (file)
index 0000000..aad01c0
Binary files /dev/null and b/img/level/7.png differ
diff --git a/img/level/8.aseprite b/img/level/8.aseprite
new file mode 100644 (file)
index 0000000..10e82d1
Binary files /dev/null and b/img/level/8.aseprite differ
diff --git a/img/level/8.png b/img/level/8.png
new file mode 100644 (file)
index 0000000..c1ee361
Binary files /dev/null and b/img/level/8.png differ
diff --git a/img/level/9.aseprite b/img/level/9.aseprite
new file mode 100644 (file)
index 0000000..bdcfeac
Binary files /dev/null and b/img/level/9.aseprite differ
diff --git a/img/level/9.png b/img/level/9.png
new file mode 100644 (file)
index 0000000..840d45d
Binary files /dev/null and b/img/level/9.png differ
diff --git a/img/patchy_tile.aseprite b/img/patchy_tile.aseprite
new file mode 100644 (file)
index 0000000..3589987
Binary files /dev/null and b/img/patchy_tile.aseprite differ
diff --git a/img/patchy_tile.png b/img/patchy_tile.png
new file mode 100644 (file)
index 0000000..750ad9a
Binary files /dev/null and b/img/patchy_tile.png differ
diff --git a/img/rabbits.aseprite b/img/rabbits.aseprite
new file mode 100644 (file)
index 0000000..8402962
Binary files /dev/null and b/img/rabbits.aseprite differ
diff --git a/img/rabbitsbrown.aseprite b/img/rabbitsbrown.aseprite
new file mode 100644 (file)
index 0000000..5e84d63
Binary files /dev/null and b/img/rabbitsbrown.aseprite differ
diff --git a/img/rabbitsbrown.png b/img/rabbitsbrown.png
new file mode 100644 (file)
index 0000000..3dc6b98
Binary files /dev/null and b/img/rabbitsbrown.png differ
diff --git a/img/rabbitswhite.aseprite b/img/rabbitswhite.aseprite
new file mode 100644 (file)
index 0000000..158cc9c
Binary files /dev/null and b/img/rabbitswhite.aseprite differ
diff --git a/img/rabbitswhite.gif b/img/rabbitswhite.gif
new file mode 100644 (file)
index 0000000..604072f
Binary files /dev/null and b/img/rabbitswhite.gif differ
diff --git a/img/rabbitswhite.png b/img/rabbitswhite.png
new file mode 100644 (file)
index 0000000..07563c7
Binary files /dev/null and b/img/rabbitswhite.png differ
diff --git a/img/selector.aseprite b/img/selector.aseprite
new file mode 100644 (file)
index 0000000..0932670
Binary files /dev/null and b/img/selector.aseprite differ
diff --git a/img/selector.gif b/img/selector.gif
new file mode 100644 (file)
index 0000000..ebd2319
Binary files /dev/null and b/img/selector.gif differ
diff --git a/img/selector.png b/img/selector.png
new file mode 100644 (file)
index 0000000..3972db1
Binary files /dev/null and b/img/selector.png differ
diff --git a/img/sign_tile.aseprite b/img/sign_tile.aseprite
new file mode 100644 (file)
index 0000000..05273d8
Binary files /dev/null and b/img/sign_tile.aseprite differ
diff --git a/img/sign_tile.png b/img/sign_tile.png
new file mode 100644 (file)
index 0000000..d60a3f1
Binary files /dev/null and b/img/sign_tile.png differ
diff --git a/img/snow_tile.aseprite b/img/snow_tile.aseprite
new file mode 100644 (file)
index 0000000..45dd13e
Binary files /dev/null and b/img/snow_tile.aseprite differ
diff --git a/img/snow_tile.png b/img/snow_tile.png
new file mode 100644 (file)
index 0000000..d6ad850
Binary files /dev/null and b/img/snow_tile.png differ
diff --git a/img/title.aseprite b/img/title.aseprite
new file mode 100644 (file)
index 0000000..d9e8262
Binary files /dev/null and b/img/title.aseprite differ
diff --git a/img/title.png b/img/title.png
new file mode 100644 (file)
index 0000000..f87377f
Binary files /dev/null and b/img/title.png differ
diff --git a/img/water_tile.aseprite b/img/water_tile.aseprite
new file mode 100644 (file)
index 0000000..8671f35
Binary files /dev/null and b/img/water_tile.aseprite differ
diff --git a/img/water_tile.png b/img/water_tile.png
new file mode 100644 (file)
index 0000000..7cd6876
Binary files /dev/null and b/img/water_tile.png differ
diff --git a/img/welldone.aseprite b/img/welldone.aseprite
new file mode 100644 (file)
index 0000000..ba5d26f
Binary files /dev/null and b/img/welldone.aseprite differ
diff --git a/img/welldone.png b/img/welldone.png
new file mode 100644 (file)
index 0000000..49062b4
Binary files /dev/null and b/img/welldone.png differ
diff --git a/index.html b/index.html
new file mode 100644 (file)
index 0000000..1ea7b81
--- /dev/null
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+<head><title>
+    ld54
+</title>
+<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, width=100%, target-densitydpi=device-dpi, user-scalable=no" />
+<style>
+* {
+    border: 0; margin: 0;
+}
+</style>
+</head>
+<body style="background: black">
+    <canvas id="canvas" width="648" height="720" style="max-width:100%" tabindex=1> </canvas>
+</body>
+<script src="levels.js"></script>
+<script src="zucchinibread.js"></script>
+<script src="game.js"></script>
+</html>
diff --git a/levels.js b/levels.js
new file mode 100644 (file)
index 0000000..62c4ef8
--- /dev/null
+++ b/levels.js
@@ -0,0 +1,14 @@
+var levels={
+        10: { map: [0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,2,0,0,0,0,2,2,0,1,0,0,0,0,0,0,3,0,1,0,0,0,0,0,2,1,3,2,2,0,0,0,0,0,3,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:4, y:1, color: "brown" }, { x:6, y:1, color: "white" }, ],grass: [{ x:2, y:2 }, { x:6, y:4 }, { x:2, y:7 }, ], },
+        1: { map: [0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,0,1,0,0,2,2,0,0,0,1,1,0,0,2,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:2, y:5, color: "white" }, { x:6, y:7, color: "brown" }, ],grass: [{ x:5, y:5 }, { x:7, y:5 }, { x:1, y:7 }, { x:3, y:7 }, ], },
+        2: { map: [0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,1,1,0,2,2,0,0,0,0,1,1,1,1,1,2,2,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:4, y:6, color: "white" }, { x:1, y:7, color: "brown" }, ],grass: [{ x:1, y:6 }, { x:7, y:7 }, ], },
+        3: { map: [0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,1,2,2,2,1,0,0,0,0,1,2,2,2,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:2, y:2, color: "brown" }, { x:3, y:3, color: "white" }, ],grass: [{ x:4, y:5 }, ], },
+        4: { map: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,1,1,1,0,0,0,0,2,2,2,2,1,0,0,0,0,1,2,2,2,2,0,0,0,0,2,1,1,1,2,0,0,0,0,2,1,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:3, y:3, color: "white" }, { x:6, y:3, color: "brown" }, ],grass: [{ x:2, y:6 }, { x:3, y:6 }, { x:4, y:6 }, { x:5, y:6 }, { x:6, y:6 }, ], },
+        5: { map: [0,0,0,0,0,0,0,0,0,0,0,2,2,0,1,1,0,0,0,0,2,3,1,2,2,0,0,0,0,1,1,1,2,2,0,0,0,0,1,1,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:2, y:1, color: "white" }, { x:4, y:3, color: "brown" }, ],grass: [{ x:6, y:1 }, { x:2, y:2 }, { x:2, y:3 }, { x:6, y:3 }, { x:2, y:4 }, { x:3, y:4 }, { x:5, y:4 }, { x:6, y:4 }, ], },
+        6: { map: [0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,2,2,2,3,3,0,0,0,0,2,1,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,2,1,0,0,0,1,1,2,2,3,1,1,0,0,0,0,2,2,0,2,0,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:2, y:3, color: "white" }, { x:3, y:3, color: "brown" }, ],grass: [{ x:3, y:1 }, { x:1, y:6 }, { x:3, y:7 }, ], },
+        7: { map: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,2,2,1,1,0,0,0,0,0,0,2,1,0,0,0,0,0,0,0,2,2,1,1,0,0,0,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:4, y:2, color: "white" }, { x:5, y:2, color: "brown" }, ],grass: [{ x:2, y:3 }, { x:6, y:5 }, { x:2, y:6 }, ], },
+        8: { map: [0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,3,0,0,0,0,0,2,2,2,3,0,0,0,0,0,1,2,0,1,0,0,0,2,3,1,3,0,1,0,0,0,0,0,2,0,0,1,0,0,0,0,0,1,2,1,3,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:3, y:3, color: "brown" }, { x:3, y:5, color: "white" }, ],grass: [{ x:1, y:4 }, { x:4, y:6 }, { x:5, y:7 }, ], },
+        9: { map: [0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,0,0,2,2,3,1,1,0,0,0,2,1,1,0,1,1,0,0,0,2,1,2,0,1,0,0,0,0,0,1,0,0,1,2,0,0,1,1,2,3,1,1,2,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,],hares: [{ x:4, y:3, color: "brown" }, { x:4, y:4, color: "white" }, ],grass: [{ x:1, y:6 }, { x:5, y:6 }, { x:7, y:7 }, ], },
+        end: { map: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],hares: [],grass: [], },
+        title: { map: [0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],hares: [],grass: [], },
+}
diff --git a/levels/1.tmx b/levels/1.tmx
new file mode 100644 (file)
index 0000000..53642db
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="-1" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <tileset firstgid="13" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,1,1,1,1,1,1,1,1,
+1,3,5,3,3,10,1,9,1,
+1,3,3,1,1,1,2,2,1,
+1,10,1,9,2,2,4,2,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/10.tmx b/levels/10.tmx
new file mode 100644 (file)
index 0000000..43b647d
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="-1" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="11" columns="11">
+  <image source="tmptile.png" width="264" height="24"/>
+ </tileset>
+ <tileset firstgid="12" name="tmptile" tilewidth="24" tileheight="24" tilecount="11" columns="11">
+  <image source="tmptile.png" width="264" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,4,6,5,1,1,
+1,1,10,3,1,2,1,1,1,
+1,1,1,6,1,2,1,1,1,
+1,1,3,2,6,3,10,1,1,
+1,1,1,6,3,1,1,1,1,
+1,1,1,3,1,1,1,1,1,
+1,1,9,2,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/2.tmx b/levels/2.tmx
new file mode 100644 (file)
index 0000000..b082b33
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="-1" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,1,1,1,1,1,1,1,1,
+1,9,2,1,5,3,1,1,1,
+1,4,2,2,2,2,3,10,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/3.tmx b/levels/3.tmx
new file mode 100644 (file)
index 0000000..82bf2bd
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="0" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,1,2,2,2,1,1,1,
+1,1,4,3,3,3,2,1,1,
+1,1,2,5,3,3,2,1,1,
+1,1,1,1,2,2,1,1,1,
+1,1,1,1,9,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,2,1,1,1,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/4.tmx b/levels/4.tmx
new file mode 100644 (file)
index 0000000..03b05b2
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="0" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,3,3,2,2,2,1,1,
+1,1,3,5,3,3,4,1,1,
+1,1,2,3,3,3,3,1,1,
+1,1,3,2,2,2,3,1,1,
+1,1,10,9,9,9,10,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/5.tmx b/levels/5.tmx
new file mode 100644 (file)
index 0000000..4c237b6
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="-1" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,5,3,1,2,9,1,1,
+1,1,10,6,2,3,3,1,1,
+1,1,9,2,4,3,10,1,1,
+1,1,9,9,1,10,10,1,1,
+1,1,1,1,1,1,1,1,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/6.tmx b/levels/6.tmx
new file mode 100644 (file)
index 0000000..650c2a7
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="-1" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="11" columns="11">
+  <image source="tmptile.png" width="264" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,1,10,3,3,1,1,1,
+1,1,3,3,3,6,6,1,1,
+1,1,5,4,1,2,2,1,1,
+1,1,1,1,1,1,2,1,1,
+1,1,1,1,1,3,2,1,1,
+1,9,2,3,3,6,2,2,1,
+1,1,1,10,3,1,3,1,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/7.tmx b/levels/7.tmx
new file mode 100644 (file)
index 0000000..94d4071
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="-1" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="11" columns="11">
+  <image source="tmptile.png" width="264" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,5,4,1,1,1,
+1,1,10,3,2,2,1,1,1,
+1,1,1,3,2,1,1,1,1,
+1,1,1,3,3,2,9,1,1,
+1,1,9,2,3,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/8.tmx b/levels/8.tmx
new file mode 100644 (file)
index 0000000..950a35b
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="0" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,6,1,6,1,1,
+1,1,1,3,3,3,6,1,1,
+1,1,1,4,3,1,2,1,1,
+1,10,6,2,6,1,2,1,1,
+1,1,1,5,1,1,2,1,1,
+1,1,1,2,10,2,6,1,1,
+1,1,1,1,1,9,1,1,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/9.tmx b/levels/9.tmx
new file mode 100644 (file)
index 0000000..01aafd1
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="0" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,6,1,2,1,1,
+1,1,1,3,3,6,2,2,1,
+1,1,3,2,4,1,2,2,1,
+1,1,3,2,5,1,2,1,1,
+1,1,2,2,1,1,2,3,1,
+1,2,3,6,1,9,2,3,1,
+1,9,1,1,1,3,3,10,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/end.tmx b/levels/end.tmx
new file mode 100644 (file)
index 0000000..1eedab8
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="-1" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/mapcompile.py b/levels/mapcompile.py
new file mode 100755 (executable)
index 0000000..8efd7e8
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/python3
+
+import ulvl
+import sys
+
+if len(sys.argv) < 2:
+    print("usage:", sys.argv[0], "<infiles>")
+    sys.exit(1)
+
+screenwidth, screenheight = 9, 9
+
+tilemapping = { 3: 1, 4: 2, 5: 3, 6: 3, 7: 3, 8: 1, 9: 2, 10: 3, 11: 4 }
+
+objmapping = { }
+
+print("var levels={")
+for filename in sys.argv[1:]:
+    m = ulvl.TMX.load(filename)
+
+    w = m.meta['width']
+    h = m.meta['height']
+
+    print('\t', filename.replace('.tmx', '').replace('levels/', ''), end=': { ')
+
+    hares = [ ]
+    grass = [ ]
+
+    print('map: [', end='')
+    for y in range(h):
+        for x in range(w):
+            thing = m.layers[0].tiles[y * w + x] - 1
+            if thing == 3 or thing == 6:
+                hares.append({ 'x': x, 'y': y, 'color': 'brown' })
+            elif thing == 4 or thing == 7:
+                hares.append({ 'x': x, 'y': y, 'color': 'white' })
+            elif thing == 8 or thing == 9 or thing == 10:
+                grass.append({ 'x': x, 'y': y })
+
+            print("" + str(tilemapping.get(thing, thing)) + ",", end='')
+    print('],', end='');
+
+    print('hares: [', end='')
+    for h in hares:
+        print('{ x:' + str(h['x']) + ', y:' + str(h['y']) + ', color: "' + h['color'] + '" }, ', end='')
+    print('],', end='')
+
+    print('grass: [', end='')
+    for g in grass:
+        print('{ x:' + str(g['x']) + ', y:' + str(g['y']) + ' }, ', end='')
+    print('],', end='')
+
+    print(' },')
+
+print("}")
diff --git a/levels/title.tmx b/levels/title.tmx
new file mode 100644 (file)
index 0000000..165c35c
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="orthogonal" renderorder="right-down" compressionlevel="0" width="9" height="9" tilewidth="24" tileheight="24" infinite="0" nextlayerid="2" nextobjectid="1">
+ <tileset firstgid="1" name="tmptile" tilewidth="24" tileheight="24" tilecount="12" columns="12">
+  <image source="tmptile.png" width="288" height="24"/>
+ </tileset>
+ <layer id="1" name="Tile Layer 1" width="9" height="9">
+  <data encoding="csv">
+1,1,1,1,1,1,1,1,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,12,12,12,12,12,12,12,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>
diff --git a/levels/tmptile-sheet.png b/levels/tmptile-sheet.png
new file mode 100644 (file)
index 0000000..f3ab3dc
Binary files /dev/null and b/levels/tmptile-sheet.png differ
diff --git a/levels/tmptile.png b/levels/tmptile.png
new file mode 100644 (file)
index 0000000..00b1d95
Binary files /dev/null and b/levels/tmptile.png differ
diff --git a/loading.png b/loading.png
new file mode 100644 (file)
index 0000000..d278451
Binary files /dev/null and b/loading.png differ
diff --git a/music/haregamesong.mp3 b/music/haregamesong.mp3
new file mode 100644 (file)
index 0000000..7f3443a
Binary files /dev/null and b/music/haregamesong.mp3 differ
diff --git a/pause.png b/pause.png
new file mode 100644 (file)
index 0000000..2530de4
Binary files /dev/null and b/pause.png differ
diff --git a/sfx/alert.mp3 b/sfx/alert.mp3
new file mode 100644 (file)
index 0000000..3e17161
Binary files /dev/null and b/sfx/alert.mp3 differ
diff --git a/sfx/cronch.mp3 b/sfx/cronch.mp3
new file mode 100644 (file)
index 0000000..7922908
Binary files /dev/null and b/sfx/cronch.mp3 differ
diff --git a/sfx/haregameyouwon.mp3 b/sfx/haregameyouwon.mp3
new file mode 100644 (file)
index 0000000..79be1b3
Binary files /dev/null and b/sfx/haregameyouwon.mp3 differ
diff --git a/sfx/jump.mp3 b/sfx/jump.mp3
new file mode 100644 (file)
index 0000000..2253673
Binary files /dev/null and b/sfx/jump.mp3 differ
diff --git a/sfx/nervous.mp3 b/sfx/nervous.mp3
new file mode 100644 (file)
index 0000000..2d02b84
Binary files /dev/null and b/sfx/nervous.mp3 differ
diff --git a/sfx/push.mp3 b/sfx/push.mp3
new file mode 100644 (file)
index 0000000..ea96639
Binary files /dev/null and b/sfx/push.mp3 differ
diff --git a/sfx/whew.mp3 b/sfx/whew.mp3
new file mode 100644 (file)
index 0000000..f3c5d22
Binary files /dev/null and b/sfx/whew.mp3 differ
diff --git a/zucchinibread.js b/zucchinibread.js
new file mode 100644 (file)
index 0000000..4a48d7e
--- /dev/null
@@ -0,0 +1,1150 @@
+"use strict";
+
+/* Please forgive my strictly procedural/functional style here.
+ * Thanks for reading though! */
+
+let zb = (function() {
+    /* ---- Util ---- */
+
+    function mod(x, n) {
+         return ((x%n)+n)%n;
+    }
+
+    function copy_list(list) {
+        let newlist = [];
+        for (let x of list) {
+            newlist.push(x);
+        }
+        return newlist;
+    }
+
+    function copy_flat_objlist(list) {
+        let newlist = [];
+        for (let x of list) {
+            newlist.push({ ...x });
+        }
+        return newlist;
+    }
+
+    /* ---- Resource loading / loading screen ---- */
+
+    let _audiocheck = document.createElement('audio');
+
+    let _SFX_ARRAY_SIZE = 10;
+
+    /* Returns a callback that should be called when a resource finishes loading. */
+    function _register_resource(name, game, callback) {
+        game._total_things_to_load ++;
+        console.log("Loading", name + ". Things to load:", game._total_things_to_load);
+        return function() {
+            if (!game.ready_to_go) {
+                game._things_loaded ++;
+                console.log("Loaded", name + ". Things loaded:", game._things_loaded, "/", game._total_things_to_load);
+                _check_if_loaded(game);
+                if (callback) {
+                    callback();
+                }
+            }
+        }
+    }
+
+    /* Check if audio failed to load bc file doesn't exist and print error message. */
+    /* TODO add the same thing for an image */
+    function _sound_resource_error(name, game, audio) {
+        return function(e) {
+            if (audio === undefined) {
+                console.error("Error loading resource, skipping:", name);
+                game._things_loaded ++;
+                _check_if_loaded(game);
+            }
+        }
+    }
+
+    /* Check if the game has loaded everything and if so show the 'click to start' image */
+    function _check_if_loaded(game) {
+        if (game.ready_to_go) return;
+
+        if (game._things_loaded >= game._total_things_to_load) {
+            console.log("Ready");
+            game.ready_to_go = true;
+            game._on_ready();
+        }
+    }
+
+    function _register_sfx(sfxdata, game) {
+        for (let key in sfxdata) {
+            let sfx_array = new Array(_SFX_ARRAY_SIZE);
+            for (let i = 0; i < _SFX_ARRAY_SIZE; i++) {
+                sfx_array[i] = new Audio(sfxdata[key].path);
+                sfx_array[i].addEventListener('canplaythrough',
+                    _register_resource(sfxdata[key].path + '#' + (i+1), game), false);
+                sfx_array[i].addEventListener('error',
+                    _sound_resource_error(sfxdata[key].path + '#' + (i+1), game), false, sfx_array[i]);
+                if (sfxdata[key].hasOwnProperty('volume')) {
+                    sfx_array[i].volume = sfxdata[key].volume;
+                }
+            }
+            game.sfx[key] = {
+                _array: sfx_array,
+                _index: 0,
+                play: function() {
+                    if (!game.muted) {
+                        this._array[this._index].currentTime = 0;
+                        this._array[this._index].play();
+                        this._index ++;
+                        this._index = mod(this._index, _SFX_ARRAY_SIZE);
+                    }
+                }
+            }
+        }
+    }
+
+    function _register_music(musicdata, game) {
+        for (let key in musicdata) {
+            let musicpath;
+            if (musicdata[key].path.match(/\.[a-z]{3}$/)) {
+                musicpath = musicdata[key].path;
+            } else if (_audiocheck.canPlayType('audio/mpeg')) {
+                musicpath = musicdata[key].path + '.mp3';
+            } else if (_audiocheck.canPlayType('audio/ogg')) {
+                musicpath = musicdata[key].path + '.ogg';
+            } else {
+                console.log("No supported music type known :(");
+                return;
+            }
+            let music = new Audio(musicpath);
+            if (musicdata[key].hasOwnProperty('volume')) {
+                music.volume = musicdata[key].volume;
+            }
+
+            if (!musicdata[key].hasOwnProperty('loop') || musicdata[key].loop) {
+                /* We loop by default unless 'loop: false' is specified. */
+                music.loop = true;
+            }
+            music.addEventListener('canplaythrough', _register_resource(musicpath, game), false);
+            music.addEventListener('error', _sound_resource_error(musicpath, game, music), false);
+
+            /* Handle music changes when muted, so when we unmute,
+             * the correct music will be playing. */
+            music._og_play = music.play;
+            music.play = function() {
+                if (game.muted) {
+                    music.was_playing = true;
+                } else {
+                    music._og_play();
+                }
+            }
+
+            music._og_pause = music.pause;
+            music.pause = function() {
+                if (game.muted) {
+                    music.was_playing = false;
+                } else {
+                    music._og_pause();
+                }
+            }
+
+            game.music[key] = music;
+        }
+    }
+
+    function _register_images(imgdata, game) {
+        function _recursive_load_images(pathmap) {
+            let result = {};
+            for (let key in pathmap) {
+                if (typeof pathmap[key] === 'object') {
+                    result[key] = _recursive_load_images(pathmap[key]);
+                } else {
+                    result[key] = new Image();
+                    result[key].onload = _register_resource(pathmap[key], game);
+                    result[key].src = pathmap[key];
+                }
+            }
+            return result;
+        }
+
+        let loaded_imgs = _recursive_load_images(imgdata);
+        for (let k in loaded_imgs) {
+            game.img[k] = loaded_imgs[k];
+        }
+    }
+
+    function create_game(params) {
+        let game_props = {...params};
+
+        game_props.frame_rate = params.frame_rate || 60;
+
+        game_props.canvas_w = params.canvas_w || 640;
+        game_props.canvas_h = params.canvas_h || 480;
+        game_props.draw_scale = params.draw_scale || 4;
+        game_props.top_border = params.top_border || 0;
+        game_props.bottom_border = params.bottom_border || 8;
+        game_props.left_border = params.left_border || 0;
+        game_props.right_border = params.right_border || 0;
+
+        game_props.tile_size = params.tile_size || 16;
+        game_props.level_w = params.level_w || 10;
+        game_props.level_h = params.level_h || 7;
+
+        game_props.screen_w = game_props.canvas_w / game_props.draw_scale;
+        game_props.screen_h = game_props.canvas_h / game_props.draw_scale;
+
+        game_props.background_color = params.background_color || '#000000';
+
+        let canvas = document.getElementById(game_props.canvas);
+
+        let global_ctx = canvas.getContext('2d');
+        global_ctx.imageSmoothingEnabled = false;
+        global_ctx.webkitImageSmoothingEnabled = false;
+        global_ctx.mozImageSmoothingEnabled = false;
+
+        let mask_canvas = document.createElement('canvas');
+        mask_canvas.width = canvas.width;
+        mask_canvas.height = canvas.height;
+        let mask_ctx = mask_canvas.getContext('2d');
+        mask_ctx.imageSmoothingEnabled = false;
+        mask_ctx.webkitImageSmoothingEnabled = false;
+        mask_ctx.mozImageSmoothingEnabled = false;
+
+        let copy_canvas = document.createElement('canvas');
+        copy_canvas.width = canvas.width;
+        copy_canvas.height = canvas.height;
+        let copy_ctx = copy_canvas.getContext('2d');
+        copy_ctx.imageSmoothingEnabled = false;
+        copy_ctx.webkitImageSmoothingEnabled = false;
+        copy_ctx.mozImageSmoothingEnabled = false;
+
+        let draw_canvas = document.createElement('canvas');
+        draw_canvas.width = canvas.width;
+        draw_canvas.height = canvas.height;
+        let draw_ctx = draw_canvas.getContext('2d');
+        draw_ctx.imageSmoothingEnabled = false;
+        draw_ctx.webkitImageSmoothingEnabled = false;
+        draw_ctx.mozImageSmoothingEnabled = false;
+
+        let game = {
+            /* General properties */
+            ...game_props,
+
+            canvas: canvas,
+
+            /* Loading */
+            ready_to_go: false,
+            _total_things_to_load: 1,
+            _things_loaded: 0,
+            resources_ready: function() {
+                this._things_loaded ++;
+                console.log("Finished enumerating resources to load. Things loaded:",
+                    this._things_loaded, "/", this._total_things_to_load);
+                _check_if_loaded(this);
+            },
+            _on_ready: function() {
+                this.ready_to_go = true;
+                if (game.img._clicktostart && game.img._clicktostart.complete) {
+                    game.ctx.global.save();
+                    game.ctx.global.scale(game.draw_scale, game.draw_scale);
+                    game.ctx.global.drawImage(game.img._clicktostart, 0, 0);
+                    game.ctx.global.restore();
+                }
+            },
+            playing: false,
+            play: function() {
+                this.playing = true;
+                if (this.events.gamestart) {
+                    this.events.gamestart(this);
+                }
+                _loop(this);
+            },
+            _norun: false,
+
+            /* Audio */
+            sfx: {},
+            music: {},
+            muted: false,
+            mute: function() {
+                _mute(this);
+            },
+            unmute: function() {
+                _unmute(this);
+            },
+            toggle_mute: function() {
+                if (this.muted) {
+                    _unmute(this);
+                } else {
+                    _mute(this);
+                }
+            },
+            register_sfx: function(sfxdata) {
+                _register_sfx(sfxdata, this);
+            },
+            register_music: function(musicdata) {
+                _register_music(musicdata, this);
+            },
+
+            /* Drawing */
+            ctx: {
+                global: global_ctx, /* context for the actual real canvas */
+                mask: mask_ctx,     /* context for drawing the transition mask, gets scaled up */
+                copy: copy_ctx,     /* context for copying the old screen on transition */
+                draw: draw_ctx,     /* context for drawing the real level */
+            },
+            img: {},
+            register_images: function(imgdata) {
+                _register_images(imgdata, this);
+            },
+
+            /* Save */
+            save: function(key, data) {
+                _save(game, key, data);
+            },
+            load: function(key) {
+                return _load(game, key);
+            },
+
+            /* Transition system */
+            transition: _transition,
+            start_transition: function(type, length, callback, on_done) {
+                _start_transition(this, type, length, callback, on_done);
+            },
+            long_transition: function(type, length, callback, on_done) {
+                _long_transition(this, type, length, callback, on_done);
+            }
+        };
+
+        window.requestAnimFrame = (function() {
+            return window.requestAnimationFrame      ||
+                window.webkitRequestAnimationFrame   ||
+                window.mozRequestAnimationFrame      ||
+                window.oRequestAnimationFrame        ||
+                window.msRequestAnimationFrame       ||
+                function(callback, element) {
+                    window.setTimeout(callback, 1000/game.frame_rate);
+                };
+        })();
+
+        /* Register event listeners */
+        for (let ev in params.events) {
+            /* Register any other events I guess */
+            canvas['on' + ev] = function(e) {
+                if (game.playing && !game._norun) {
+                    params.events[ev](game, e);
+                }
+            }
+        }
+
+        /* Override with special events */
+        canvas.onmousedown = function(e) {
+            if (!game._norun) {
+                _handle_mousedown(game, e);
+            }
+        }
+
+        canvas.onmousemove = function(e) {
+            if (!game._norun) {
+                _handle_mousemove(game, e);
+            }
+        }
+
+        canvas.onmouseup = function(e) {
+            if (!game._norun) {
+                _handle_mouseup(game, e);
+            }
+        }
+
+        canvas.onkeydown = function(e) {
+            if (!game._norun && game.events.keydown) {
+                game.events.keydown(game, e);
+                e.preventDefault();
+            }
+        }
+
+        canvas.onkeyup = function(e) {
+            if (!game._norun && game.events.keyup) {
+                game.events.keyup(game, e);
+                e.preventDefault();
+            }
+        }
+
+        canvas.onblur = function(e) {
+            if (game.playing && !game.run_in_background) {
+                game._norun = true;
+                _stop_music(game);
+            }
+        }
+
+        canvas.onfocus = function(e) {
+            if (game.playing && !game.run_in_background) {
+                game._norun = false;
+                if (!game.muted) {
+                    _start_music(game);
+                }
+                _loop(game);
+            }
+        }
+
+        /* Set loading stuff */
+        let loading_img = new Image();
+        loading_img.onload = _register_resource('loading.png', game, function() {
+            if (!game.ready_to_go) {
+                game.ctx.global.save();
+                game.ctx.global.scale(game.draw_scale, game.draw_scale);
+                game.ctx.global.drawImage(loading_img, 0, 0);
+                game.ctx.global.restore();
+            }
+        });
+        loading_img.src = 'loading.png';
+
+        if (!game.run_in_background) {
+            let pause_img = new Image();
+            pause_img.onload = _register_resource('pause.png', game);
+            pause_img.src = 'pause.png';
+            game.img._pause = pause_img;
+        }
+
+        if (game.hasOwnProperty('save_key')) {
+            let saveerror_img = new Image();
+            saveerror_img.onload = _register_resource('saveerror.png', game);
+            saveerror_img.src = 'saveerror.png';
+            game.img._saveerror = saveerror_img;
+        }
+
+        let clicktostart_img = new Image();
+        clicktostart_img.onload = _register_resource('clicktostart.png', game, function() {
+            if (game.ready_to_go) {
+                game.ctx.global.save();
+                game.ctx.global.scale(game.draw_scale, game.draw_scale);
+                game.ctx.global.drawImage(game.img._clicktostart, 0, 0);
+                game.ctx.global.restore();
+            }
+        });
+        clicktostart_img.src = 'clicktostart.png';
+        game.img._clicktostart = clicktostart_img;
+
+        return game;
+    }
+
+    /* ---- Audio ---- */
+
+    function _stop_music(game) {
+        if (game.muted) return;
+
+        for (let m in game.music) {
+            if (!game.music[m].paused) {
+                game.music[m].was_playing = true;
+                game.music[m].pause();
+            } else {
+                game.music[m].was_playing = false;
+            }
+        }
+    }
+
+    function _start_music(game, unmuting) {
+        if (game.muted && !unmuting) return;
+
+        for (let m in game.music) {
+            if (game.music[m].was_playing) {
+                game.music[m].play();
+            }
+        }
+    }
+
+    function _mute(game) {
+        _stop_music(game);
+        game.muted = true;
+    }
+
+    function _unmute(game) {
+        game.muted = false;
+        _start_music(game);
+    }
+
+    /* ---- Game update stuff ---- */
+
+    let _last_frame_time;
+    let _timedelta = 0;
+    function _loop(game, timestamp) {
+        if (timestamp == undefined) {
+            timestamp = 0;
+            _last_frame_time = timestamp;
+        }
+        _timedelta += timestamp - _last_frame_time;
+        _last_frame_time = timestamp;
+
+        while (_timedelta >= 1000 / game.frame_rate) {
+            _update(game, 1000 / game.frame_rate);
+            _timedelta -= 1000 / game.frame_rate;
+        }
+        _draw(game);
+
+        if (!game._norun) {
+            requestAnimFrame(function(timestamp) {
+                _loop(game, timestamp);
+            });
+        }
+    }
+
+    function _update(game, delta) {
+        game.update_func(delta);
+
+        if (game.transition.is_transitioning) {
+            game.transition.timer += delta;
+            if (game.transition.timer > game.transition.end_time) {
+                _finish_transition(game);
+            }
+        }
+    }
+
+    /* ---- Mouse ---- */
+
+    function _handle_mousedown(game, e) {
+        if (!game.playing) return;
+
+        const rect = game.canvas.getBoundingClientRect();
+        let x = Math.round((e.clientX - rect.left) / game.draw_scale) - 1;
+        let y = Math.round((e.clientY - rect.top) / game.draw_scale) - 1;
+        if (e.button === 0 && game.events.mousedown) {
+            game.events.mousedown(game, e, x, y);
+        }
+    }
+
+    function _handle_mouseup(game, e) {
+        if (!game.playing && game.ready_to_go) {
+            /* Click to start */
+            if (game.hasOwnProperty('save_key') && !game._show_save_error) {
+                /* Test if save/load is working */
+                try {
+                    console.log("Test saving");
+                    localStorage.setItem(game.save_key + ".test", "savetest");
+                    let savetest = localStorage.getItem(game.save_key + ".test");
+                    if (savetest !== "savetest") {
+                        throw new Error("oops");
+                    }
+                } catch (e) {
+                    /* If error saving, show save error message first */
+                    /* We set _show_save_error so that the second time the user clicks,
+                     * the game will actually start */
+                    console.log("- SAVE FAILED -");
+                    game._show_save_error = true;
+                    game.ctx.global.save();
+                    game.ctx.global.scale(game.draw_scale, game.draw_scale);
+                    game.ctx.global.drawImage(game.img._saveerror, 0, 0);
+                    game.ctx.global.restore();
+                    return;
+                }
+            }
+            game.play();
+            return;
+        }
+
+        const rect = game.canvas.getBoundingClientRect();
+        let x = Math.round((e.clientX - rect.left) / game.draw_scale) - 1;
+        let y = Math.round((e.clientY - rect.top) / game.draw_scale) - 1;
+        if (e.button === 0 && game.events.mouseup) {
+            game.events.mouseup(game, e, x, y);
+        }
+    }
+
+    function _handle_mousemove(game, e) {
+        const rect = game.canvas.getBoundingClientRect();
+        let x = Math.round((e.clientX - rect.left) / game.draw_scale) - 1;
+        let y = Math.round((e.clientY - rect.top) / game.draw_scale) - 1;
+        if (game.events.mousemove) {
+            game.events.mousemove(game, e, x, y);
+        }
+    }
+
+    /* ---- Drawing stuff ---- */
+
+    function _draw(game) {
+        let ctx = game.ctx.draw;
+
+        ctx.save();
+
+        ctx.fillStyle = game.background_color;
+
+        ctx.beginPath();
+        ctx.rect(0, 0, game.screen_w, game.screen_h);
+        ctx.fill();
+
+        game.draw_func(game.ctx.draw);
+
+        if (game.transition.mid_long) {
+            ctx.fillStyle = game.transition.color;
+            ctx.fillRect(-1, -1, game.canvas_w + 5, game.canvas_h + 5);
+        }
+
+        ctx.restore();
+
+        game.ctx.global.fillStyle = 'rgb(0, 0, 0)';
+        game.ctx.global.beginPath();
+        game.ctx.global.rect(0, 0, game.screen_w * game.draw_scale, game.screen_h * game.draw_scale);
+        game.ctx.global.fill();
+
+        game.ctx.global.save();
+
+        game.ctx.global.scale(game.draw_scale, game.draw_scale);
+
+        game.ctx.global.drawImage(ctx.canvas, 0, 0);
+
+        if (game.transition.is_transitioning) {
+            _draw_transition(game);
+        }
+
+        if (game._norun) {
+            game.ctx.global.drawImage(game.img._pause, 0, 0);
+        }
+
+        game.ctx.global.restore();
+    }
+
+    /* Draw a particular section of an image/spritesheet,
+     * without having to do as much math or type the destination size */
+    function sprite_draw(ctx, img, section_w, section_h, section_x, section_y, dest_x, dest_y) {
+        ctx.drawImage(img,
+            section_w * section_x, section_h * section_y, section_w, section_h,
+            dest_x, dest_y, section_w, section_h)
+    }
+
+    /* Draw an image over the whole screen lol */
+    function screen_draw(ctx, img) {
+        ctx.drawImage(img, 0, 0);
+    }
+
+    /* ---- Save ---- */
+
+    function _save(game, key, data) {
+        let save_data;
+
+        if (!game.hasOwnProperty('save_key')) {
+            throw new Error("In order to enable saving/loading, please specify a save_key property when creating the game object");
+        }
+
+        try {
+            save_data = localStorage.getItem(game.save_key) || "{}";
+        } catch (e) {
+            console.error("Saving is disabled; cannot save " + key, e);
+            return;
+        }
+
+        try {
+            save_data = JSON.parse(save_data);
+        } catch (e) {
+            console.error("Cannot parse stored save data (when trying to save " + key + ")", e);
+            return;
+        }
+
+        save_data[key] = JSON.stringify(data);
+
+        try {
+            save_data = JSON.stringify(save_data);
+        } catch (e) {
+            console.error("Failed to stringify save data (while trying to save " + key + ")", e);
+            return;
+        }
+
+        try {
+            localStorage.setItem(game.save_key, save_data);
+        } catch (e) {
+            console.error("Failed to save to localStorage (while saving key " + key + ")", e);
+            return;
+        }
+    }
+
+    function _load(game, key) {
+        let save_data;
+
+        if (!game.hasOwnProperty('save_key')) {
+            throw new Error("In order to enable saving/loading, please specify a save_key property when creating the game object");
+        }
+
+        try {
+            save_data = localStorage.getItem(game.save_key);
+        } catch (e) {
+            console.error("Loading is disabled; cannot load " + key, e);
+            return;
+        }
+
+        try {
+            save_data = JSON.parse(save_data);
+        } catch (e) {
+            console.error("Cannot parse stored save data (when trying to load " + key + ")", e);
+            return;
+        }
+
+        if (!save_data) {
+            return null;
+        }
+
+        return save_data[key];
+    }
+
+    /* ---- Transition stuff ---- */
+
+    let TransitionType = { DOTS: 1, SLIDE_DOWN: 2, SLIDE_UP: 3, FADE: 4, CIRCLE: 5 };
+
+    let _transition = {
+        is_transitioning: false,
+        timer: 0,
+        color: '#000000',
+        w: 20,
+        h: 14,
+        dir_invert_v: false,
+        dir_invert_h: false,
+        invert_shape: true,
+        mid_long: false,
+        done_func: null,
+        type: TransitionType.DOTS,
+        nodraw: false,
+        end_time: 100,
+    }
+
+    function _long_transition(game, type, length, callback) {
+        if (game.transition.is_transitioning) return;
+
+        _draw(game);
+
+        game.transition.invert_shape = false;
+        _internal_start_transition(game, type, length, function() {
+            game.transition.mid_long = true;
+        }, function() {
+            game.transition.invert_shape = true;
+            game.transition.is_transitioning = true;
+            let tdiv = game.transition.dir_invert_v;
+            let tdih = game.transition.dir_invert_h;
+            _internal_start_transition(game, type, length, function() {
+                game.transition.mid_long = false;
+                callback();
+                game.transition.dir_invert_v = tdiv;
+                game.transition.dir_invert_h = tdih;
+            });
+        });
+    }
+
+    function _start_transition(game, type, length, callback, on_done) {
+        if (game.transition.is_transitioning) return;
+        if (!game.transition.nodraw) _draw(game);
+
+        _internal_start_transition(game, type, length, callback, on_done);
+    }
+
+    function _internal_start_transition(game, type, length, callback, on_done) {
+        if (on_done) {
+            game.transition.done_func = on_done;
+        }
+
+        game.transition.type = type;
+        game.transition.end_time = length;
+
+        game.ctx.copy.drawImage(game.ctx.draw.canvas, 0, 0);
+
+        game.transition.dir_invert_v = Math.random() < 0.5;
+        game.transition.dir_invert_h = Math.random() < 0.5;
+
+        callback();
+
+        game.transition.is_transitioning = true;
+        game.transition.timer = 0;
+    }
+
+    function _finish_transition(game) {
+        game.transition.is_transitioning = false;
+        game.transition.timer = 0;
+
+        if (game.transition.done_func) {
+            setTimeout(function() {
+                game.transition.done_func();
+                game.transition.done_func = null;
+            }, 400);
+        }
+    }
+
+    function _draw_transition(game) {
+        game.ctx.global.save();
+
+        if (game.transition.type == TransitionType.DOTS) {
+            game.ctx.mask.clearRect(0, 0, game.screen_w, game.screen_h);
+            _draw_transition_dot_mask(game, game.ctx.mask);
+
+            // Redraw to reduce antialiasing effects
+            for (let i = 0; i < 5; i++) {
+                game.ctx.mask.drawImage(game.ctx.mask.canvas, 0, 0);
+            }
+
+            game.ctx.mask.globalCompositeOperation = 'source-in';
+            game.ctx.mask.drawImage(game.ctx.copy.canvas, 0, 0);
+            game.ctx.mask.globalCompositeOperation = 'source-over';
+
+            game.ctx.global.drawImage(game.ctx.mask.canvas, 0, 0);
+        } else if (game.transition.type == TransitionType.SLIDE_DOWN) {
+            let offset = game.transition.timer / game.transition.end_time * game.screen_h;
+
+            game.ctx.global.drawImage(game.ctx.copy.canvas, 0, -offset);
+            game.ctx.global.drawImage(ctx.canvas, 0, game.screen_h - offset);
+        } else if (game.transition.type == TransitionType.SLIDE_UP) {
+            let offset = game.transition.timer / game.transition.end_time * game.screen_h;
+
+            game.ctx.global.drawImage(game.ctx.copy.canvas, 0, offset);
+            game.ctx.global.drawImage(ctx.canvas, 0, - game.screen_h + offset);
+        } else if (game.transition.type == TransitionType.FADE) {
+            let alpha = 0;
+            alpha = 1 - game.transition.timer / game.transition.end_time;
+
+            game.ctx.mask.clearRect(0, 0, game.screen_w, game.screen_h);
+            game.ctx.mask.fillStyle = 'rgba(255,255,255,' + alpha + ')';
+            game.ctx.mask.fillRect(0, 0, game.screen_w, game.screen_h);
+            game.ctx.mask.globalCompositeOperation = 'source-in';
+            game.ctx.mask.drawImage(game.ctx.copy.canvas, 0, 0);
+            game.ctx.mask.globalCompositeOperation = 'source-over';
+
+            game.ctx.global.drawImage(game.ctx.mask.canvas, 0, 0);
+        } else if (game.transition.type == TransitionType.CIRCLE) {
+            game.ctx.mask.clearRect(0, 0, game.screen_w, game.screen_h);
+
+            let frac = game.transition.timer / game.transition.end_time;
+            if (!game.transition.invert_shape) {
+                frac = 1 - frac;
+            }
+            frac = Math.pow(frac, 1.5);
+
+            let cx = character.x + 0.5;
+            let cy = character.y + 0.5;
+            let lh = game.level_h + 1;
+            let distances_to_corners = [
+                Math.sqrt(Math.pow(cx * game.tile_size, 2) + Math.pow(cy * game.tile_size, 2)),
+                Math.sqrt(Math.pow((game.level_w - cx) * game.tile_size, 2) + Math.pow(cy * game.tile_size, 2)),
+                Math.sqrt(Math.pow(cx * game.tile_size, 2) + Math.pow((lh - cy) * game.tile_size, 2)),
+                Math.sqrt(Math.pow((game.level_w - cx) * game.tile_size, 2) + Math.pow((lh - cy) * game.tile_size, 2)),
+            ];
+            let max_radius = Math.max(...distances_to_corners);
+            let radius = frac * max_radius;
+
+            game.ctx.mask.globalCompositeOperation = 'source-over';
+            game.ctx.mask.drawImage(game.ctx.copy.canvas, 0, 0);
+
+            game.ctx.mask.globalCompositeOperation = 'destination-out';
+            game.ctx.mask.fillStyle = 'rgba(255,255,255)';
+            game.ctx.mask.beginPath();
+            if (!game.transition.invert_shape) {
+                game.ctx.mask.rect(-5, -5, game.screen_w + 5, game.screen_h + 5);
+            }
+            game.ctx.mask.arc(character.x * game.tile_size + game.tile_size / 2,
+                character.y * game.tile_size + game.tile_size / 2,
+                radius, 0, 2 * Math.PI,
+                !game.transition.invert_shape);
+            game.ctx.mask.fill();
+
+            game.ctx.mask.globalCompositeOperation = 'source-over';
+
+            game.ctx.global.drawImage(game.ctx.mask.canvas, 0, 0);
+        }
+
+        game.ctx.global.restore();
+    }
+
+    function _draw_transition_dot_mask(game, ctx) {
+        ctx.fillStyle = '#0000ff';
+        let cell_width = game.screen_w / game.transition.w;
+        let cell_height = game.screen_h / game.transition.h;
+        let max_radius = 0.75 * Math.max(cell_width, cell_height);
+
+        let transition_dot_length = game.transition.end_time * 3 / 8;
+
+        for (let x = -1; x < game.transition.w + 1; x++) {
+            for (let y = -1; y < game.transition.h + 1; y++) {
+                let radius;
+
+                let circle_start_time = (x + y) / (game.transition.w + game.transition.h)
+                        * (game.transition.end_time - transition_dot_length);
+                if (game.transition.timer - circle_start_time < 0) {
+                    if (game.transition.invert_shape) {
+                        radius = 0;
+                    } else {
+                        radius = max_radius;
+                    }
+                } else if (game.transition.timer - circle_start_time < transition_dot_length) {
+                    if (game.transition.invert_shape) {
+                        radius = (game.transition.timer - circle_start_time) / transition_dot_length * max_radius;
+                    } else {
+                        radius = (1 - (game.transition.timer - circle_start_time) / transition_dot_length) * max_radius;
+                    }
+                } else {
+                    if (game.transition.invert_shape) {
+                        radius = max_radius;
+                    } else {
+                        radius = 0;
+                    }
+                }
+
+                let draw_x = x;
+                let draw_y = y;
+                if (game.transition.dir_invert_v) draw_x = game.transition.w - 1 - x;
+                if (game.transition.dir_invert_h) draw_y = game.transition.h - 1 - y;
+
+                if (radius >= max_radius * 0.8) {
+                    if (!game.transition.invert_shape) {
+                        ctx.fillRect(draw_x * cell_width, draw_y * cell_width, cell_width + 1, cell_width + 1);
+                    }
+                } else if (radius > 0) {
+                    ctx.save();
+                    ctx.beginPath();
+                    if (game.transition.invert_shape) {
+                        ctx.rect(draw_x * cell_width, draw_y * cell_width, cell_width + 3, cell_width + 3);
+                    }
+                    ctx.moveTo(draw_x * cell_width + cell_width / 2, draw_y * cell_width + cell_width / 2);
+                    ctx.arc(draw_x * cell_width + cell_width / 2,
+                             draw_y * cell_width + cell_width / 2,
+                             radius, 0, 2 * Math.PI, game.transition.invert_shape);
+                    ctx.clip();
+                    ctx.fillRect(draw_x * cell_width, draw_y * cell_width, cell_width, cell_width);
+                    ctx.restore();
+                } else {
+                    if (game.transition.invert_shape) {
+                        ctx.fillRect(draw_x * cell_width, draw_y * cell_width, cell_width + 3, cell_width + 3);
+                    }
+                }
+            }
+        }
+    }
+
+    /* ---- former contents of ready.js ---- */
+
+    // Due to Timo Huovinen
+    // https://stackoverflow.com/questions/799981/document-ready-equivalent-without-jquery
+    var ready = (function(){
+
+        var readyList,
+            DOMContentLoaded,
+            class2type = {};
+            class2type["[object Boolean]"] = "boolean";
+            class2type["[object Number]"] = "number";
+            class2type["[object String]"] = "string";
+            class2type["[object Function]"] = "function";
+            class2type["[object Array]"] = "array";
+            class2type["[object Date]"] = "date";
+            class2type["[object RegExp]"] = "regexp";
+            class2type["[object Object]"] = "object";
+
+        var ReadyObj = {
+            // Is the DOM ready to be used? Set to true once it occurs.
+            isReady: false,
+            // A counter to track how many items to wait for before
+            // the ready event fires. See #6781
+            readyWait: 1,
+            // Hold (or release) the ready event
+            holdReady: function( hold ) {
+                if ( hold ) {
+                    ReadyObj.readyWait++;
+                } else {
+                    ReadyObj.ready( true );
+                }
+            },
+            // Handle when the DOM is ready
+            ready: function( wait ) {
+                // Either a released hold or an DOMready/load event and not yet ready
+                if ( (wait === true && !--ReadyObj.readyWait) || (wait !== true && !ReadyObj.isReady) ) {
+                    // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+                    if ( !document.body ) {
+                        return setTimeout( ReadyObj.ready, 1 );
+                    }
+
+                    // Remember that the DOM is ready
+                    ReadyObj.isReady = true;
+                    // If a normal DOM Ready event fired, decrement, and wait if need be
+                    if ( wait !== true && --ReadyObj.readyWait > 0 ) {
+                        return;
+                    }
+                    // If there are functions bound, to execute
+                    readyList.resolveWith( document, [ ReadyObj ] );
+
+                    // Trigger any bound ready events
+                    //if ( ReadyObj.fn.trigger ) {
+                    //    ReadyObj( document ).trigger( "ready" ).unbind( "ready" );
+                    //}
+                }
+            },
+            bindReady: function() {
+                if ( readyList ) {
+                    return;
+                }
+                readyList = ReadyObj._Deferred();
+
+                // Catch cases where $(document).ready() is called after the
+                // browser event has already occurred.
+                if ( document.readyState === "complete" ) {
+                    // Handle it asynchronously to allow scripts the opportunity to delay ready
+                    return setTimeout( ReadyObj.ready, 1 );
+                }
+
+                // Mozilla, Opera and webkit nightlies currently support this event
+                if ( document.addEventListener ) {
+                    // Use the handy event callback
+                    document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+                    // A fallback to window.onload, that will always work
+                    window.addEventListener( "load", ReadyObj.ready, false );
+
+                // If IE event model is used
+                } else if ( document.attachEvent ) {
+                    // ensure firing before onload,
+                    // maybe late but safe also for iframes
+                    document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+                    // A fallback to window.onload, that will always work
+                    window.attachEvent( "onload", ReadyObj.ready );
+
+                    // If IE and not a frame
+                    // continually check to see if the document is ready
+                    var toplevel = false;
+
+                    try {
+                        toplevel = window.frameElement == null;
+                    } catch(e) {}
+
+                    if ( document.documentElement.doScroll && toplevel ) {
+                        doScrollCheck();
+                    }
+                }
+            },
+            _Deferred: function() {
+                var // callbacks list
+                    callbacks = [],
+                    // stored [ context , args ]
+                    fired,
+                    // to avoid firing when already doing so
+                    firing,
+                    // flag to know if the deferred has been cancelled
+                    cancelled,
+                    // the deferred itself
+                    deferred  = {
+
+                        // done( f1, f2, ...)
+                        done: function() {
+                            if ( !cancelled ) {
+                                var args = arguments,
+                                    i,
+                                    length,
+                                    elem,
+                                    type,
+                                    _fired;
+                                if ( fired ) {
+                                    _fired = fired;
+                                    fired = 0;
+                                }
+                                for ( i = 0, length = args.length; i < length; i++ ) {
+                                    elem = args[ i ];
+                                    type = ReadyObj.type( elem );
+                                    if ( type === "array" ) {
+                                        deferred.done.apply( deferred, elem );
+                                    } else if ( type === "function" ) {
+                                        callbacks.push( elem );
+                                    }
+                                }
+                                if ( _fired ) {
+                                    deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
+                                }
+                            }
+                            return this;
+                        },
+
+                        // resolve with given context and args
+                        resolveWith: function( context, args ) {
+                            if ( !cancelled && !fired && !firing ) {
+                                // make sure args are available (#8421)
+                                args = args || [];
+                                firing = 1;
+                                try {
+                                    while( callbacks[ 0 ] ) {
+                                        callbacks.shift().apply( context, args );//shifts a callback, and applies it to document
+                                    }
+                                }
+                                finally {
+                                    fired = [ context, args ];
+                                    firing = 0;
+                                }
+                            }
+                            return this;
+                        },
+
+                        // resolve with this as context and given arguments
+                        resolve: function() {
+                            deferred.resolveWith( this, arguments );
+                            return this;
+                        },
+
+                        // Has this deferred been resolved?
+                        isResolved: function() {
+                            return !!( firing || fired );
+                        },
+
+                        // Cancel
+                        cancel: function() {
+                            cancelled = 1;
+                            callbacks = [];
+                            return this;
+                        }
+                    };
+
+                return deferred;
+            },
+            type: function( obj ) {
+                return obj == null ?
+                    String( obj ) :
+                    class2type[ Object.prototype.toString.call(obj) ] || "object";
+            }
+        }
+        // The DOM ready check for Internet Explorer
+        function doScrollCheck() {
+            if ( ReadyObj.isReady ) {
+                return;
+            }
+
+            try {
+                // If IE is used, use the trick by Diego Perini
+                // http://javascript.nwbox.com/IEContentLoaded/
+                document.documentElement.doScroll("left");
+            } catch(e) {
+                setTimeout( doScrollCheck, 1 );
+                return;
+            }
+
+            // and execute any waiting functions
+            ReadyObj.ready();
+        }
+        // Cleanup functions for the document ready method
+        if ( document.addEventListener ) {
+            DOMContentLoaded = function() {
+                document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+                ReadyObj.ready();
+            };
+
+        } else if ( document.attachEvent ) {
+            DOMContentLoaded = function() {
+                // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+                if ( document.readyState === "complete" ) {
+                    document.detachEvent( "onreadystatechange", DOMContentLoaded );
+                    ReadyObj.ready();
+                }
+            };
+        }
+        function ready( fn ) {
+            // Attach the listeners
+            ReadyObj.bindReady();
+
+            var type = ReadyObj.type( fn );
+
+            // Add the callback
+            readyList.done( fn );//readyList is result of _Deferred()
+        }
+        return ready;
+    })();
+
+    return {
+        create_game: create_game,
+        ready: ready,
+        mod: mod,
+        copy_list: copy_list,
+        copy_flat_objlist: copy_flat_objlist,
+        sprite_draw: sprite_draw,
+        screen_draw: screen_draw,
+        transition: TransitionType,
+    }
+})();