From: cassowarii Date: Sun, 6 Oct 2024 22:16:43 +0000 (-0700) Subject: Initial commit X-Git-Url: https://www.git.cassowary.me/gitweb.cgi?a=commitdiff_plain;h=c511898eb2c3bf0213864c36986942b0362cfaf9;p=ld56.git Initial commit --- c511898eb2c3bf0213864c36986942b0362cfaf9 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0f331c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.swp +*.zip +packaging/* diff --git a/ase/buttons..aseprite b/ase/buttons..aseprite new file mode 100644 index 0000000..b7eb2ac Binary files /dev/null and b/ase/buttons..aseprite differ diff --git a/ase/clicktocontinue.aseprite b/ase/clicktocontinue.aseprite new file mode 100644 index 0000000..e555de8 Binary files /dev/null and b/ase/clicktocontinue.aseprite differ diff --git a/ase/endscreen.aseprite b/ase/endscreen.aseprite new file mode 100644 index 0000000..076d3c8 Binary files /dev/null and b/ase/endscreen.aseprite differ diff --git a/ase/guys.aseprite b/ase/guys.aseprite new file mode 100644 index 0000000..7c31021 Binary files /dev/null and b/ase/guys.aseprite differ diff --git a/ase/guysalt.aseprite b/ase/guysalt.aseprite new file mode 100644 index 0000000..dfb49c3 Binary files /dev/null and b/ase/guysalt.aseprite differ diff --git a/ase/levelnums.aseprite b/ase/levelnums.aseprite new file mode 100644 index 0000000..3f97822 Binary files /dev/null and b/ase/levelnums.aseprite differ diff --git a/ase/markers.aseprite b/ase/markers.aseprite new file mode 100644 index 0000000..08c4627 Binary files /dev/null and b/ase/markers.aseprite differ diff --git a/ase/selector.aseprite b/ase/selector.aseprite new file mode 100644 index 0000000..ab18404 Binary files /dev/null and b/ase/selector.aseprite differ diff --git a/ase/shine.aseprite b/ase/shine.aseprite new file mode 100644 index 0000000..c74d883 Binary files /dev/null and b/ase/shine.aseprite differ diff --git a/ase/sparkle.aseprite b/ase/sparkle.aseprite new file mode 100644 index 0000000..3b27bc5 Binary files /dev/null and b/ase/sparkle.aseprite differ diff --git a/ase/tiles.aseprite b/ase/tiles.aseprite new file mode 100644 index 0000000..e6c4f7d Binary files /dev/null and b/ase/tiles.aseprite differ diff --git a/ase/titlescreen.aseprite b/ase/titlescreen.aseprite new file mode 100644 index 0000000..ab444bb Binary files /dev/null and b/ase/titlescreen.aseprite differ diff --git a/bugfont.ttf b/bugfont.ttf new file mode 100644 index 0000000..4bf2655 Binary files /dev/null and b/bugfont.ttf differ diff --git a/clicktostart.png b/clicktostart.png new file mode 100644 index 0000000..e9705e7 Binary files /dev/null and b/clicktostart.png differ diff --git a/cover.png b/cover.png new file mode 100644 index 0000000..64ff5f0 Binary files /dev/null and b/cover.png differ diff --git a/cover2.png b/cover2.png new file mode 100644 index 0000000..ed1be85 Binary files /dev/null and b/cover2.png differ diff --git a/game.js b/game.js new file mode 100644 index 0000000..c43d87e --- /dev/null +++ b/game.js @@ -0,0 +1,1061 @@ +"use strict"; + +let game; + +let game_started = false; + +let just_started = true; + +let level_number = 1; + +let map; + +let intitle; +let wonitall; + +/* --------- definitions ---------- */ + +/* state: + * STAND: doing some kind of selection, or waiting + * MOVE: creature is moving + * WIN: level complete + */ +let State = { STAND: 0, MOVE: 1, WIN: 2 }; + +let can_continue = false; + +let save_data = 1; +const SAVE_KEY = "casso.lizardwizard.save" + +zb.ready(function() { + game = zb.create_game({ + canvas: 'canvas', + canvas_w: 480, + canvas_h: 720, + draw_scale: 3, + tile_size: 20, + level_w: 8, + level_h: 12, + background_color: '#41522E', + draw_func: do_draw, + update_func: do_update, + run_in_background: true, + save_key: SAVE_KEY, + state: State.STAND, + events: { + keyup: handle_keyup, + mouseup: handle_mouseup, + mousedown: handle_mousedown, + mousemove: handle_mousemove, + gamestart: handle_gamestart, + }, + buttons: [ + { + id: 0, + callback: undo, + x: 28, + y: 0, + }, + { + id: 1, + callback: reset, + x: 44, + y: 0, + }, + { + id: 2, + callback: mute_button, + x: 116, + y: 0, + }, + { + id: 4, + callback: colorblind_button, + x: 100, + y: 0, + }, + ], + }); + + game.register_images({ + creatures: 'img/creatures.png', + tiles: 'img/tiles.png', + selector: 'img/selector.png', + markers: 'img/markers.png', + shine: 'img/shine.png', + sparkle: 'img/sparkle.png', + levelnums: 'img/levelnums.png', + clicktocontinue: 'img/clicktocontinue.png', + buttons: 'img/buttons.png', + titlescreen: 'img/titlescreen.png', + endscreen: 'img/endscreen.png', + leveltext: { + 1: 'img/level/1.png', + 2: 'img/level/2.png', + 4: 'img/level/4.png', + 7: 'img/level/7.png', + 9: 'img/level/9.png', + } + }); + + game.register_sfx({ + win: { + path: 'sfx/win.wav', + volume: 0.95, + }, + hop: { + path: 'sfx/win.wav', + volume: 0.95, + }, + }); + + game.register_music({ + bgm: { + path: 'music/bugjazz', + volume: 0.9, + }, + feet: { + path: 'sfx/feet.wav', + volume: 1.0, + }, + slide: { + path: 'sfx/slide.wav', + volume: 1.0, + }, + }); + + game.resources_ready(); + + game.transition.color = game.background_color; + + for (let b of game.buttons) { + b.state = 0; + } +}); + +let ID = { + ladybug: 0, + frog: 1, + spider: 2, + snailV: 3, + snailH: 4, + stagbeetleV: 5, + stagbeetleH: 6, +}; + +let mapID = { + blank: 0, + normal: 1, + blue: 2, + red: 3, + access: 4, +}; + +let creatureColorID = { + red: 0, + blue: 1, +} + +let move_speeds = { + [ID.ladybug]: 6, + [ID.frog]: 3, + [ID.spider]: 4, + [ID.snailV]: 3, + [ID.snailH]: 3, + [ID.stagbeetleV]: 5, + [ID.stagbeetleH]: 5, +}; + +let can_push = { + [ID.stagbeetleV]: 1, + [ID.stagbeetleH]: 1, +}; + +function mute_button() { + game.toggle_mute(); + if (game.muted) { + game.buttons[2].id = 3; + } else { + game.buttons[2].id = 2; + } +} + +function colorblind_button() { + draw_markers = !draw_markers; + if (draw_markers) { + game.buttons[3].id = 5; + } else { + game.buttons[3].id = 4; + } +} + +/* ------ timers & static timer values --------- */ + +let access_flash_timer = 0; + +let clicktocontinue_opacity = 0; +const CTC_FADEIN_SPEED = 0.8; + +let shine_progress = 0; +const SHINE_IN_LENGTH = 0.2; +const SHINE_OUT_LENGTH = 0.5; +const SHINE_SPEED = 2.8; +const MAX_SHINE_OPACITY = 0.4; + +const SPARKLE_GAP = 1.8; +const SPARKLE_FRAME_LENGTH = 100; + +/* ------- game global state -------- */ + +let creatures = []; + +let selected_creature = null; +let moving_creatures = []; + +let accessible_tiles = new Set(); + +let pathfinding = {}; +let path = []; + +let selector_info = null; + +let draw_markers = false; + +let sparkles = []; + +/* ------- game behavior functions -------- */ + +let find_accessible_tiles = { + [ID.ladybug]: function(x, y) { + return [ [x - 1, y], [x + 1, y], [x, y - 1], [x, y + 1] ]; + }, + + [ID.frog]: function(x, y) { + return [ [x + 1, y + 2], [x + 1, y - 2], [x - 1, y + 2], [x - 1, y - 2], [x + 2, y + 1], [x + 2, y - 1], [x - 2, y + 1], [x - 2, y - 1] ]; + }, + + [ID.spider]: function(x, y) { + return [ [x + 1, y + 1], [x + 1, y - 1], [x - 1, y + 1], [x - 1, y - 1] ]; + }, + + [ID.snailV]: function(x, y) { + return [ [x, y + 1], [x, y - 1] ]; + }, + + [ID.snailH]: function(x, y) { + return [ [x + 1, y], [x - 1, y] ]; + }, + + [ID.stagbeetleV]: function(x, y) { + return [ [x, y + 1], [x, y - 1] ]; + }, + + [ID.stagbeetleH]: function(x, y) { + return [ [x + 1, y], [x - 1, y] ]; + }, +}; + +function snail_walk(ctx, x, y, tx, ty, mf, creature) { + if (y < ty) { creature.vflip = true } + else { creature.vflip = false } + if (x < tx) { creature.hflip = true } + else { creature.hflip = false } + + if (mf >= 0.25 && mf < 0.75) return 0; + return 1; +} + +function beetle_walk(ctx, x, y, tx, ty, mf, creature) { + if (y < ty) { creature.vflip = true } + else { creature.vflip = false } + if (x < tx) { creature.hflip = true } + else { creature.hflip = false } + + if (mf < 0.25 || mf >= 0.5 && mf < 0.75) return 0; + return 1; +} + +/* These functions take a canvas context, as well as the creature's + * x, y, target_x, target_y, and move_fraction as parameters, + * and then maybe do something to the drawing canvas before the + * creature gets drawn. (rotate, offset, flip, etc), and then + * return the frame that the creature should draw. */ +let display_when_in_motion = { + [ID.ladybug]: function (ctx, x, y, tx, ty, mf, creature) { + if (ty < y) { creature.rotate = 0 } + else if (tx > x) { creature.rotate = 90 } + else if (ty > y) { creature.rotate = 180 } + else if (tx < x) { creature.rotate = 270 } + + if (mf >= 0.25 && mf < 0.75) return 0; + return 1; + }, + + [ID.frog]: function (ctx, x, y, tx, ty, mf, creature) { + if (tx > x) { creature.hflip = true } + else { creature.hflip = false } + + let y_offset = (4 * Math.pow(mf, 2) - 4 * mf) * 30; + ctx.translate(0, y_offset); + + if (mf < 0.8) return 1; + return 0; + }, + + [ID.spider]: function (ctx, x, y, tx, ty, mf, creature) { + if (mf < 0.125 || mf >= 0.375 && mf < 0.5 || mf >= 0.875) return 1; + return 0; + }, + + [ID.snailH]: snail_walk, + + [ID.snailV]: snail_walk, + + [ID.stagbeetleH]: beetle_walk, + + [ID.stagbeetleV]: beetle_walk, +}; + +function sofc(coords) { + return "" + coords[0] + "," + coords[1]; +} + +function cofs(string) { + return string.split(",").map(a => parseInt(a, 10)); +} + +function compute_accessible_tiles_for_creature(creature) { + pathfinding = {}; + accessible_tiles.clear(); + + if (creature.type === ID.stagbeetleH) { + let look_x = creature.x; + let creature_count = 0; + for (let look_x = creature.x + 1; ; look_x ++) { + if (creatures_at(look_x + creature_count, creature.y).length > 0) creature_count ++; + if (creatures_at(look_x + creature_count, creature.y).length > 0) creature_count ++; + if (creature_count > 1) break; + if (tile_at(look_x + creature_count, creature.y) === 0) break; + accessible_tiles.add(sofc([look_x, creature.y])); + pathfinding[sofc([look_x, creature.y])] = sofc([look_x - 1, creature.y]); + } + + creature_count = 0; + for (let look_x = creature.x - 1; ; look_x --) { + if (creatures_at(look_x - creature_count, creature.y).length > 0) creature_count ++; + if (creatures_at(look_x - creature_count, creature.y).length > 0) creature_count ++; + if (creature_count > 1) break; + if (tile_at(look_x - creature_count, creature.y) === 0) break; + accessible_tiles.add(sofc([look_x, creature.y])); + pathfinding[sofc([look_x, creature.y])] = sofc([look_x + 1, creature.y]); + } + } else if (creature.type === ID.stagbeetleV) { + let look_y = creature.y; + let creature_count = 0; + for (let look_y = creature.y + 1; ; look_y ++) { + if (creatures_at(creature.x, look_y + creature_count).length > 0) creature_count ++; + if (creatures_at(creature.x, look_y + creature_count).length > 0) creature_count ++; + if (creature_count > 1) break; + if (tile_at(creature.x, look_y + creature_count) === 0) break; + accessible_tiles.add(sofc([creature.x, look_y])); + pathfinding[sofc([creature.x, look_y])] = sofc([creature.x, look_y - 1]); + } + + creature_count = 0; + for (let look_y = creature.y - 1; ; look_y --) { + if (creatures_at(creature.x, look_y - creature_count).length > 0) creature_count ++; + if (creatures_at(creature.x, look_y - creature_count).length > 0) creature_count ++; + if (creature_count > 1) break; + if (tile_at(creature.x, look_y - creature_count) === 0) break; + accessible_tiles.add(sofc([creature.x, look_y])); + pathfinding[sofc([creature.x, look_y])] = sofc([creature.x, look_y + 1]); + } + } else { + accessible_tiles.add(sofc([ creature.x, creature.y ])); + + /* repeatedly call the 'find accessible tiles' function on all the tiles already in the set, + * until we don't add any new ones - this is pretty inefficient, but the levels are tiny, + * so who cares! */ + let last_size; + do { + last_size = accessible_tiles.size; + + for (let sAlready of accessible_tiles.values()) { + /* sorry, this is kind of insane, we convert them to a string so that we can tell if + * a coordinate is already in the set... but then we convert the string back into + * coordinates in order ot pass them to find_accessible_tiles... spaghetti code */ + let found = find_accessible_tiles[creature.type](...cofs(sAlready)); + for (let cFound of found) { + /* tile only accessible if no creature already there and if the tile is not 0 + * (and also out of bounds check) */ + if (creatures_at(...cFound).length !== 0 && !can_push[creature.type]) continue; + if (tile_at(...cFound) === 0) continue; + if (cFound[0] < 0 || cFound[0] >= game.level_w || cFound[1] < 0 || cFound[1] >= game.level_h) continue; + + if (!accessible_tiles.has(sofc(cFound))) { + /* Mark how we found the path to this tile so that we can follow it later + * if we want to! */ + pathfinding[sofc(cFound)] = sAlready; + accessible_tiles.add(sofc(cFound)); + } + } + } + } while (last_size != accessible_tiles.size); + + accessible_tiles.delete(sofc([ creature.x, creature.y ])); + } +} + +function do_single_move(c, tx, ty, first) { + if (c === selected_creature) { + if (c.type === ID.frog) { + game.sfx.hop.play(); + } else if (first) { + if (c.type === ID.snailH || c.type === ID.snailV) { + game.music.slide.play(); + } else { + game.music.feet.play(); + } + } + } + + c.target_x = path[0][0]; + c.target_y = path[0][1]; + c.move_fraction = 0; + + if (c === selected_creature && can_push[c.type]) { + /* If there's a creature in the way on the tile we're moving to, then + * push it */ + let creatures = creatures_at(c.target_x, c.target_y); + if (creatures.length > 0) { + console.log("pushing"); + creatures[0].target_x = creatures[0].x + c.target_x - c.x; + creatures[0].target_y = creatures[0].y + c.target_y - c.y; + if (!moving_creatures.includes(creatures[0])) { + moving_creatures.push(creatures[0]); + } + } + } +} + +/* ------- generic functions -------- */ + +function delete_save() { + try { + game.save('level_num', 1); + } catch (e) { + console.error("oops, can't save! though that uh... doesn't matter here"); + } +} + +function save() { + try { + console.log("Saving"); + let save_data = level_number; + if (game.state === State.WIN) { + save_data = level_number + 1; + } + game.save('level_num', save_data); + } catch (e) { + console.error("oops, can't save!", e); + } +} + +function handle_gamestart(game) { + console.log("Game start!"); + + intitle = true; + wonitall = false; + + let save_data = parseInt(game.load('level_num') || "1"); + console.log("save data: ", save_data); + level_number = save_data; + if (level_number > Math.max(...Object.keys(levels).map(a => parseInt(a, 10) || 0))) { + level_number = 1; + } + load_level_data(levels.title); +} + +function creatures_at(x, y) { + return creatures.filter(o => o.x === x && o.y === y); +} + +function tile_at(x, y) { + let result = map[y * game.level_w + x]; + if (result > 4) return 0; + return result; +} + +function cosmetic_tile_at(x, y) { + return map[y * game.level_w + x]; +} + +let undo_stack = []; + +function create_undo_point() { + console.log("cup"); + undo_stack.push(zb.copy_flat_objlist(creatures)); +} + +function undo() { + console.log("undo") + if (undo_stack.length > 0) { + creatures = undo_stack.pop(); + selected_creature = null; + game.state = State.STAND; + accessible_tiles.clear(); + selector_info = null; + + for (let c of creatures) { + c.move_fraction = 0; + c.target_x = c.x; + c.target_y = c.y; + } + } +} + +function reset() { + game.start_transition(zb.transition.FADE, 300, function() { + load_level(); + }); +} + +function advance_level() { + if (!can_continue) return; + + console.log("W:", Math.max(...Object.keys(levels).map(a => parseInt(a, 10) || 0))); + + if (level_number + 1 > Math.max(...Object.keys(levels).map(a => parseInt(a, 10) || 0))) { + win_everything(); + return; + } + + game.long_transition(zb.transition.FADE, 375, function() { + level_number ++; + load_level(); + can_continue = false; + game.state = State.STAND; + }); +} + +function skip_to(num) { + level_number = num; + load_level(); +} + +function win_everything() { + game.long_transition(zb.transition.FADE, 1000, function() { + wonitall = true; + load_level_data(levels.end); + delete_save(); + }); +} + +function load_level() { + load_level_data(levels[level_number]); +} + +function load_level_data(lvl) { + map = lvl.map; + creatures = zb.copy_flat_objlist(lvl.creatures); + + sparkles = []; + + for (let c of creatures) { + if (c.type === ID.snailH || c.type === ID.stagbeetleH || c.type === ID.frog) { + if (c.x < game.level_w / 2) { + c.hflip = true; + } + } + + if (c.type === ID.snailV || c.type === ID.stagbeetleV) { + if (c.y < game.level_h / 2) { + c.vflip = true; + } + } + + c.move_fraction = 0; + + c.sparkle_timer = Math.random() * SPARKLE_GAP; + } + + selected_creature = null; + game.state = State.STAND; + accessible_tiles.clear(); + selector_info = null; +} + +function check_victory() { + /* Check that every red square has a red creature on it, and every + * blue square has a blue creature on it */ + for (let y = 0; y < game.level_h; y++) { + for (let x = 0; x < game.level_w; x++) { + if (tile_at(x, y) === mapID.blue) { + let creatures = creatures_at(x, y); + if (creatures.length === 0 || creatures[0].color === creatureColorID.red) { + return false; + } + } + + if (tile_at(x, y) === mapID.red) { + let creatures = creatures_at(x, y); + if (creatures.length === 0 || creatures[0].color === creatureColorID.blue) { + return false; + } + } + } + } + + win(); +} + +function win() { + console.log("You won!"); + selector_info = null; + selected_creature = null; + accessible_tiles.clear(); + game.state = State.WIN; + clicktocontinue_opacity = 0; + + game.sfx.win.play(); + + let min_position = game.level_w + game.level_h; + for (let y = 0; y < game.level_h; y++) { + for (let x = 0; x < game.level_w; x++) { + if (tile_at(x, y) !== 0 && x + y < min_position) { + min_position = x + y; + } + } + } + shine_progress = min_position / (game.level_w + game.level_h); + + save(); + window.setTimeout(function() { + can_continue = true; + }, 350); +} + +/* ---------- update functions ------------ */ + +/* MAIN UPDATE FUNCTION */ +function do_update(delta) { + if (game.state === State.STAND) { + if (selected_creature) { + access_flash_timer += delta; + } + } else if (game.state === State.MOVE) { + for (let c of moving_creatures) { + c.move_fraction += move_speeds[selected_creature.type] * delta / 1000; + } + + if (selected_creature.move_fraction >= 1) { + for (let c of moving_creatures) { + c.x = c.target_x; + c.y = c.target_y; + c.move_fraction = 0; + } + + /* if selected creature completes move, continue path */ + path.shift(); + if (path.length === 0) { + game.music.feet.pause(); + game.music.slide.pause(); + game.state = State.STAND; + selected_creature = null; + moving_creatures = []; + check_victory(); + } else { + do_single_move(selected_creature, path[0][0], path[0][1], false); + } + } + } else if (game.state === State.WIN) { + if (clicktocontinue_opacity < 1) { + clicktocontinue_opacity += CTC_FADEIN_SPEED * delta / 1000; + if (clicktocontinue_opacity > 1) { + clicktocontinue_opacity = 1; + } + } + + shine_progress += delta / 1000 / SHINE_SPEED; + } + + if (game.state !== State.MOVE) { + for (let c of creatures) { + if (tile_at(c.x, c.y) === mapID.red && c.color === creatureColorID.red + || tile_at(c.x, c.y) === mapID.blue && c.color === creatureColorID.blue) { + c.sparkling = true; + c.sparkle_timer += delta / 1000; + let sparkle_threshold = SPARKLE_GAP * (Math.random() * 0.8 + 0.4); + if (game.state === State.WIN) { + sparkle_threshold /= 1.8; + } + if (c.sparkle_timer > sparkle_threshold) { + c.sparkle_timer -= sparkle_threshold; + + let angle = Math.random() * 2 * Math.PI; + let magnitude = Math.random() * game.tile_size * 0.2 + game.tile_size * 0.3; + sparkles.push({ + x: c.x * game.tile_size + game.tile_size / 2 + Math.cos(angle) * magnitude, + y: c.y * game.tile_size + game.tile_size / 2 + Math.sin(angle) * magnitude, + timer: 0, + frame: 0, + }); + } + } else { + c.sparkling = false; + } + } + } + + for (let s of sparkles) { + s.timer += delta; + while (s.timer > SPARKLE_FRAME_LENGTH) { + s.timer -= SPARKLE_FRAME_LENGTH; + s.frame ++; + } + if (s.frame > 4) { + s.deleteme = true; + } + } + sparkles = sparkles.filter(s => !s.deleteme); +} + +/* ---------- draw functions ----------- */ + +/* DRAW */ +function do_draw(ctx) { + draw_map(ctx); + + draw_creatures(ctx); + + draw_sparkles(ctx); + + if (intitle) { + zb.screen_draw(ctx, game.img.titlescreen); + } else if (wonitall) { + zb.screen_draw(ctx, game.img.endscreen); + } else { + draw_buttons(ctx); + } + + if (game.state === State.WIN) { + ctx.save(); + ctx.globalAlpha = clicktocontinue_opacity; + zb.screen_draw(ctx, game.img.clicktocontinue); + ctx.restore(); + + draw_shine(ctx); + } +} + +function draw_map(ctx) { + if (!intitle && !wonitall) { + zb.sprite_draw(ctx, game.img.levelnums, 160, 8, 0, level_number - 1, 0, 4); + } + + for (let y = 0; y < game.level_h; y++) { + for (let x = 0; x < game.level_w; x++) { + zb.sprite_draw(ctx, game.img.tiles, game.tile_size, game.tile_size, cosmetic_tile_at(x, y), (x + y) % 2, x * game.tile_size, y * game.tile_size); + + if (selected_creature && accessible_tiles.has(sofc([x, y]))) { + ctx.save(); + ctx.globalAlpha = Math.sin(access_flash_timer / 1000 * 2 * Math.PI) / 3 + 0.66; + zb.sprite_draw(ctx, game.img.tiles, game.tile_size, game.tile_size, mapID.access, (x + y) % 2, x * game.tile_size, y * game.tile_size); + ctx.restore(); + } + } + } + + if (!intitle && !wonitall) { + if (selector_info) { + ctx.drawImage(game.img.selector, selector_info.x * game.tile_size - 1, selector_info.y * game.tile_size - 1); + } + + if (selected_creature && game.state === State.STAND) { + ctx.drawImage(game.img.selector, selected_creature.x * game.tile_size - 1, selected_creature.y * game.tile_size - 1); + } + + if (draw_markers) { + for (let y = 0; y < game.level_h; y++) { + for (let x = 0; x < game.level_w; x++) { + if (tile_at(x, y) == 2) { + zb.sprite_draw(ctx, game.img.markers, game.tile_size, game.tile_size, 0, 1, x * game.tile_size, y * game.tile_size); + } else if (tile_at(x, y) == 3) { + zb.sprite_draw(ctx, game.img.markers, game.tile_size, game.tile_size, 0, 0, x * game.tile_size, y * game.tile_size); + } + } + } + } + + if (game.img.leveltext.hasOwnProperty(level_number)) { + zb.screen_draw(ctx, game.img.leveltext[level_number]); + } + } +} + +function draw_creatures(ctx) { + for (let c of creatures) { + draw_creature(ctx, c); + } + + for (let c of moving_creatures) { + /* Draw moving creatures on top of other ones */ + draw_creature(ctx, c); + } +} + +function draw_sparkles(ctx) { + for (let s of sparkles) { + zb.sprite_draw(ctx, game.img.sparkle, 7, 7, 0, s.frame, s.x - 3, s.y - 3); + } +} + +function draw_creature(ctx, c) { + ctx.save(); + + let frame = 0; + let display_x = c.x * game.tile_size; + let display_y = c.y * game.tile_size; + if (moving_creatures.includes(c) && game.state === State.MOVE) { + display_x = c.x * game.tile_size * (1 - c.move_fraction) + c.target_x * game.tile_size * c.move_fraction; + display_y = c.y * game.tile_size * (1 - c.move_fraction) + c.target_y * game.tile_size * c.move_fraction; + } + + ctx.translate(display_x, display_y); + + if (!moving_creatures.includes(c) && draw_markers) { + zb.sprite_draw(ctx, game.img.markers, game.tile_size, game.tile_size, 1, c.color, 0, 0); + } + + if (c.hflip) { + ctx.translate(game.tile_size, 0); + ctx.scale(-1, 1); + } + + if (c.vflip) { + ctx.translate(0, game.tile_size); + ctx.scale(1, -1); + } + + if (c.rotate) { + ctx.translate(game.tile_size / 2, game.tile_size / 2); + ctx.rotate(c.rotate * Math.PI / 180); + ctx.translate(-game.tile_size / 2, -game.tile_size / 2); + } + + if (c === selected_creature && game.state === State.MOVE) { + frame = display_when_in_motion[c.type](ctx, c.x, c.y, c.target_x, c.target_y, c.move_fraction, c); + } + + zb.sprite_draw(ctx, game.img.creatures, game.tile_size + 2, game.tile_size + 2, c.type, frame * 2 + c.color, -1, -1); + + ctx.restore(); +} + +function draw_buttons(ctx) { + for (let b of game.buttons) { + zb.sprite_draw(ctx, game.img.buttons, 16, 16, b.id, b.state, b.x, b.y); + } +} + +function draw_shine(ctx) { + if (game.state === State.WIN) { + for (let y = 0; y < game.level_h; y++) { + for (let x = 0; x < game.level_w; x++) { + if (tile_at(x, y) == 0) continue; + + ctx.save(); + ctx.globalCompositeOperation = 'screen'; + let shine_threshold = (x + y) / (game.level_w + game.level_h); + if (shine_progress > shine_threshold) { + if (shine_progress - shine_threshold < SHINE_IN_LENGTH) { + ctx.globalAlpha = Math.pow((shine_progress - shine_threshold) / SHINE_IN_LENGTH, 2/3) * MAX_SHINE_OPACITY; + } else if (shine_progress - shine_threshold - SHINE_IN_LENGTH < SHINE_OUT_LENGTH) { + ctx.globalAlpha = Math.pow(1 - (shine_progress - shine_threshold - SHINE_IN_LENGTH) / SHINE_OUT_LENGTH, 2/3) * MAX_SHINE_OPACITY; + } else { + ctx.restore(); + continue; + } + + ctx.drawImage(game.img.shine, x * game.tile_size, y * game.tile_size); + } + ctx.restore(); + } + } + } +} + +/* ---------- event handlers ------------ */ + +function handle_mousedown(game, e, x, y) { + if (intitle) return; + if (wonitall) return; + + for (let b of game.buttons) { + b.state = 0; + if (0 <= x - b.x && x - b.x < 16 && 0 <= y - b.y && y - b.y < 16) { + b.state = 2; + } + } +} + +function handle_mouseup(game, e, x, y) { + if (intitle) { + game.long_transition(zb.transition.FADE, 500, function() { + intitle = false; + if (just_started) game.music.bgm.play(); + just_started = false; + load_level(); + }); + return; + } + + if (wonitall) { + game.long_transition(zb.transition.FADE, 500, function() { + intitle = true; + wonitall = false; + level_number = 1; + load_level_data(levels.title); + }); + return; + } + + for (let b of game.buttons) { + b.state = 0; + if (0 <= x - b.x && x - b.x < 16 && 0 <= y - b.y && y - b.y < 16) { + b.callback(); + b.state = 1; + } + } + + if (game.state === State.WIN) { + advance_level(); + return; + } + + if (game.state !== State.STAND) return; + + let tile_x = Math.floor(x / game.tile_size); + let tile_y = Math.floor(y / game.tile_size); + if (accessible_tiles.has(sofc([tile_x, tile_y]))) { + create_undo_point(); + + /* move the creach */ + console.log("Computing creature path"); + path = []; + + /* pathfinding x, y */ + let pfx = tile_x; + let pfy = tile_y; + do { + path.unshift([ pfx, pfy ]); + [pfx, pfy] = cofs(pathfinding[sofc([pfx, pfy])]); + } while (pfx !== selected_creature.x || pfy !== selected_creature.y); + + console.log(path); + + game.state = State.MOVE; + + moving_creatures = [ selected_creature ]; + + accessible_tiles.clear(); + do_single_move(selected_creature, path[0][0], path[0][1], true); + } else { + if (!selector_info) { + accessible_tiles.clear(); + selected_creature = null; + return; + } + + let creatures = creatures_at(selector_info.x, selector_info.y); + + if (creatures.length === 0) { + accessible_tiles.clear(); + selected_creature = null; + } + + if (creatures[0] === selected_creature) { + accessible_tiles.clear(); + selected_creature = null; + return; + } + + selected_creature = creatures[0]; + + /* Compute creature's possible movements */ + compute_accessible_tiles_for_creature(selected_creature); + + if (accessible_tiles.size === 0) { + selected_creature = null; + } else { + access_flash_timer = 0; + } + } +} + +function handle_mousemove(game, e, x, y) { + if (intitle) return; + + for (let b of game.buttons) { + b.state = 0; + if (0 <= x - b.x && x - b.x < 16 && 0 <= y - b.y && y - b.y < 16) { + b.state = 1; + } + } + + if (game.state === State.WIN) return; + + let tile_x = Math.floor(x / game.tile_size); + let tile_y = Math.floor(y / game.tile_size); + + selector_info = null; + + let creatures = creatures_at(tile_x, tile_y); + if (creatures.length > 0 && !moving_creatures.includes(creatures[0])) { + selector_info = { + x: tile_x, + y: tile_y, + } + } else if (accessible_tiles.has(sofc([ tile_x, tile_y ]))) { + selector_info = { + x: tile_x, + y: tile_y, + } + } +} + +let x_pressed = false; +function handle_keyup(game, e) { + if (wonitall) return; + if (game.transition.is_transitioning) return; + + // 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; + } +} diff --git a/img/buttons.png b/img/buttons.png new file mode 100644 index 0000000..c12f8e4 Binary files /dev/null and b/img/buttons.png differ diff --git a/img/clicktocontinue.png b/img/clicktocontinue.png new file mode 100644 index 0000000..0ad6a5f Binary files /dev/null and b/img/clicktocontinue.png differ diff --git a/img/creatures.png b/img/creatures.png new file mode 100644 index 0000000..40cf324 Binary files /dev/null and b/img/creatures.png differ diff --git a/img/endscreen.png b/img/endscreen.png new file mode 100644 index 0000000..1f58b69 Binary files /dev/null and b/img/endscreen.png differ diff --git a/img/level/1.aseprite b/img/level/1.aseprite new file mode 100644 index 0000000..b250761 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 index 0000000..f654e0d Binary files /dev/null and b/img/level/1.png differ diff --git a/img/level/2.aseprite b/img/level/2.aseprite new file mode 100644 index 0000000..b618786 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 index 0000000..d05b1cf Binary files /dev/null and b/img/level/2.png differ diff --git a/img/level/4.aseprite b/img/level/4.aseprite new file mode 100644 index 0000000..e75ad07 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 index 0000000..40c2e2d Binary files /dev/null and b/img/level/4.png differ diff --git a/img/level/7.aseprite b/img/level/7.aseprite new file mode 100644 index 0000000..58f958c 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 index 0000000..84336c9 Binary files /dev/null and b/img/level/7.png differ diff --git a/img/level/9.aseprite b/img/level/9.aseprite new file mode 100644 index 0000000..fe8ecde 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 index 0000000..a0d28cb Binary files /dev/null and b/img/level/9.png differ diff --git a/img/levelnums.png b/img/levelnums.png new file mode 100644 index 0000000..e7f78f4 Binary files /dev/null and b/img/levelnums.png differ diff --git a/img/markers.png b/img/markers.png new file mode 100644 index 0000000..204cd15 Binary files /dev/null and b/img/markers.png differ diff --git a/img/saveerror.png b/img/saveerror.png new file mode 100644 index 0000000..7c3a3bb Binary files /dev/null and b/img/saveerror.png differ diff --git a/img/selector.png b/img/selector.png new file mode 100644 index 0000000..bb6849b Binary files /dev/null and b/img/selector.png differ diff --git a/img/shine.png b/img/shine.png new file mode 100644 index 0000000..c2d9c65 Binary files /dev/null and b/img/shine.png differ diff --git a/img/sparkle.png b/img/sparkle.png new file mode 100644 index 0000000..468f746 Binary files /dev/null and b/img/sparkle.png differ diff --git a/img/tiles.png b/img/tiles.png new file mode 100644 index 0000000..899443a Binary files /dev/null and b/img/tiles.png differ diff --git a/img/titlescreen.png b/img/titlescreen.png new file mode 100644 index 0000000..24e8f36 Binary files /dev/null and b/img/titlescreen.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..a2ddd5e --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + minibeast game + + + + + + + + + + + diff --git a/levels.js b/levels.js new file mode 100644 index 0000000..1e1d886 --- /dev/null +++ b/levels.js @@ -0,0 +1,20 @@ +var levels={ + 10: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,6,3,0,0,0,0,6,2,1,8,0,0,0,6,2,1,3,0,0,0,0,2,1,3,8,0,0,0,6,1,3,8,0,0,0,0,2,8,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 6 ,y: 3 ,type: 1 ,color: 1},{x: 4 ,y: 4 ,type: 0 ,color: 0},{x: 3 ,y: 5 ,type: 5 ,color: 0},{x: 5 ,y: 5 ,type: 0 ,color: 1},{x: 2 ,y: 6 ,type: 0 ,color: 0},{x: 4 ,y: 6 ,type: 6 ,color: 1},{x: 3 ,y: 7 ,type: 0 ,color: 1},{x: 1 ,y: 8 ,type: 2 ,color: 0},], }, + 11: { map: [8,0,0,0,0,0,0,5,0,0,11,11,0,0,0,0,0,0,3,2,0,0,0,0,0,0,5,3,7,0,0,0,0,0,0,1,1,7,0,0,0,0,0,1,1,8,0,0,0,0,9,2,3,7,0,0,0,0,0,1,1,8,0,0,0,0,0,5,1,7,0,0,0,0,0,0,2,8,0,0,0,0,0,0,12,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 2 ,type: 0 ,color: 1},{x: 3 ,y: 2 ,type: 0 ,color: 0},{x: 3 ,y: 3 ,type: 2 ,color: 1},{x: 3 ,y: 6 ,type: 3 ,color: 0},{x: 4 ,y: 6 ,type: 4 ,color: 1},{x: 4 ,y: 9 ,type: 5 ,color: 0},], }, + 12: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,3,7,0,0,0,0,0,3,1,7,0,0,0,0,0,1,1,7,0,0,0,0,0,1,1,7,7,7,7,0,0,1,1,1,1,1,2,0,0,1,1,1,1,2,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 3 ,type: 3 ,color: 1},{x: 1 ,y: 4 ,type: 4 ,color: 1},{x: 6 ,y: 7 ,type: 6 ,color: 0},{x: 5 ,y: 8 ,type: 5 ,color: 0},], }, + 13: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,11,11,0,0,0,0,0,6,1,1,0,0,0,0,0,3,1,2,0,6,7,0,0,2,1,10,9,2,3,0,0,1,8,0,6,1,3,0,0,1,7,6,1,0,1,0,0,3,1,1,1,1,2,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 1 ,y: 4 ,type: 3 ,color: 1},{x: 3 ,y: 4 ,type: 4 ,color: 0},{x: 1 ,y: 5 ,type: 3 ,color: 0},{x: 5 ,y: 5 ,type: 2 ,color: 0},{x: 6 ,y: 5 ,type: 0 ,color: 1},{x: 6 ,y: 6 ,type: 1 ,color: 1},{x: 1 ,y: 8 ,type: 4 ,color: 1},{x: 6 ,y: 8 ,type: 3 ,color: 0},], }, + 14: { map: [8,0,0,0,0,0,0,5,0,0,11,11,11,11,0,0,0,0,2,1,1,3,0,0,0,0,0,0,3,0,0,0,0,0,1,3,1,2,0,0,0,0,5,2,1,0,0,0,0,0,0,1,0,2,0,0,0,0,0,1,0,0,0,0,0,0,6,1,7,0,0,0,0,0,2,1,3,3,0,0,0,0,12,12,12,12,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 2 ,type: 0 ,color: 0},{x: 5 ,y: 2 ,type: 4 ,color: 1},{x: 4 ,y: 3 ,type: 3 ,color: 1},{x: 3 ,y: 4 ,type: 1 ,color: 1},{x: 5 ,y: 4 ,type: 6 ,color: 0},{x: 3 ,y: 5 ,type: 0 ,color: 0},{x: 5 ,y: 6 ,type: 2 ,color: 0},{x: 2 ,y: 9 ,type: 4 ,color: 0},{x: 4 ,y: 9 ,type: 0 ,color: 1},{x: 5 ,y: 9 ,type: 0 ,color: 1},], }, + 15: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,7,0,0,0,0,3,0,2,2,10,0,0,0,0,1,2,0,0,0,0,9,2,0,1,3,0,0,0,0,5,3,1,8,0,0,0,0,0,5,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 4 ,type: 2 ,color: 1},{x: 4 ,y: 4 ,type: 5 ,color: 0},{x: 5 ,y: 4 ,type: 1 ,color: 0},{x: 4 ,y: 5 ,type: 0 ,color: 0},{x: 2 ,y: 6 ,type: 2 ,color: 0},{x: 5 ,y: 6 ,type: 4 ,color: 1},{x: 3 ,y: 7 ,type: 0 ,color: 1},{x: 4 ,y: 8 ,type: 2 ,color: 1},], }, + 16: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,9,1,0,11,0,0,0,0,6,1,0,3,0,0,0,0,1,8,0,2,0,0,0,6,1,0,0,0,0,0,6,1,10,0,0,0,0,0,1,1,3,0,0,0,0,0,5,3,2,7,0,0,0,0,0,2,12,1,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 6 ,y: 3 ,type: 1 ,color: 1},{x: 6 ,y: 4 ,type: 1 ,color: 0},{x: 3 ,y: 7 ,type: 0 ,color: 1},{x: 2 ,y: 8 ,type: 5 ,color: 1},{x: 3 ,y: 8 ,type: 0 ,color: 0},{x: 2 ,y: 9 ,type: 0 ,color: 0},], }, + 1: { map: [8,0,0,0,0,0,0,5,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,11,11,11,11,0,0,0,0,2,1,1,3,0,0,0,0,2,1,1,3,0,0,0,0,12,12,12,12,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,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 5 ,type: 0 ,color: 0},{x: 5 ,y: 5 ,type: 0 ,color: 1},{x: 2 ,y: 6 ,type: 0 ,color: 0},{x: 5 ,y: 6 ,type: 0 ,color: 1},], }, + 2: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,6,2,0,0,0,0,0,0,2,1,0,0,0,0,0,0,5,1,7,0,0,0,0,0,0,1,1,11,1,7,0,0,0,5,1,1,1,3,0,0,0,0,0,5,3,8,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 3 ,type: 0 ,color: 0},{x: 1 ,y: 4 ,type: 2 ,color: 0},{x: 6 ,y: 7 ,type: 0 ,color: 1},{x: 5 ,y: 8 ,type: 2 ,color: 1},], }, + 3: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,6,3,0,0,0,0,0,6,1,1,0,0,0,0,6,1,12,3,0,0,0,0,1,0,0,1,0,0,0,0,5,1,11,2,0,0,0,0,0,5,1,1,0,0,0,0,0,0,5,2,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 5 ,y: 3 ,type: 2 ,color: 1},{x: 5 ,y: 5 ,type: 0 ,color: 1},{x: 5 ,y: 7 ,type: 0 ,color: 0},{x: 5 ,y: 9 ,type: 2 ,color: 0},], }, + 4: { map: [8,0,0,0,0,0,0,5,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,9,2,1,7,0,0,0,0,0,1,3,2,7,0,0,0,0,5,2,3,1,0,0,0,0,0,5,1,3,10,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,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 4 ,type: 0 ,color: 0},{x: 3 ,y: 5 ,type: 3 ,color: 1},{x: 4 ,y: 5 ,type: 4 ,color: 0},{x: 3 ,y: 6 ,type: 4 ,color: 0},{x: 4 ,y: 6 ,type: 3 ,color: 1},{x: 5 ,y: 7 ,type: 2 ,color: 1},], }, + 5: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,7,7,7,0,0,6,2,1,3,1,7,0,0,6,3,2,1,3,7,0,0,5,1,3,2,2,8,0,0,5,1,2,1,3,8,0,0,5,5,5,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 4 ,type: 4 ,color: 0},{x: 4 ,y: 4 ,type: 3 ,color: 1},{x: 2 ,y: 5 ,type: 3 ,color: 1},{x: 3 ,y: 5 ,type: 4 ,color: 0},{x: 5 ,y: 5 ,type: 4 ,color: 1},{x: 3 ,y: 6 ,type: 3 ,color: 1},{x: 4 ,y: 6 ,type: 4 ,color: 0},{x: 5 ,y: 6 ,type: 3 ,color: 0},{x: 3 ,y: 7 ,type: 4 ,color: 0},{x: 5 ,y: 7 ,type: 3 ,color: 1},], }, + 6: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,2,10,0,0,0,6,2,1,3,7,0,0,0,1,2,0,1,1,7,0,0,5,3,3,3,2,8,0,0,5,5,1,8,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,7,0,0,0,0,0,0,6,],creatures: [{x: 4 ,y: 3 ,type: 2 ,color: 0},{x: 2 ,y: 4 ,type: 4 ,color: 0},{x: 4 ,y: 4 ,type: 2 ,color: 1},{x: 2 ,y: 5 ,type: 0 ,color: 0},{x: 2 ,y: 6 ,type: 3 ,color: 1},{x: 3 ,y: 6 ,type: 0 ,color: 1},{x: 4 ,y: 6 ,type: 3 ,color: 1},{x: 5 ,y: 6 ,type: 4 ,color: 0},], }, + 7: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,3,0,0,0,0,0,0,6,1,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,1,1,0,0,0,0,0,0,3,8,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 3 ,type: 0 ,color: 1},{x: 1 ,y: 5 ,type: 2 ,color: 0},{x: 2 ,y: 6 ,type: 3 ,color: 0},{x: 1 ,y: 8 ,type: 5 ,color: 1},], }, + 8: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,2,7,0,0,0,0,0,2,1,1,1,7,0,0,0,5,1,1,1,1,1,0,0,0,1,0,1,1,10,0,0,0,12,0,1,1,3,0,0,0,0,0,5,3,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 2 ,y: 3 ,type: 2 ,color: 0},{x: 1 ,y: 4 ,type: 2 ,color: 0},{x: 6 ,y: 7 ,type: 6 ,color: 1},{x: 5 ,y: 8 ,type: 5 ,color: 1},], }, + 9: { map: [8,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,11,0,2,7,0,0,0,6,3,0,1,2,10,0,0,3,1,1,1,1,0,0,0,0,0,0,5,1,2,0,0,0,0,0,0,1,8,0,0,0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,6,],creatures: [{x: 4 ,y: 2 ,type: 0 ,color: 0},{x: 2 ,y: 3 ,type: 0 ,color: 1},{x: 5 ,y: 3 ,type: 1 ,color: 0},{x: 1 ,y: 4 ,type: 0 ,color: 1},{x: 6 ,y: 5 ,type: 4 ,color: 0},{x: 5 ,y: 8 ,type: 5 ,color: 1},], }, + end: { map: [8,8,8,8,5,5,5,5,8,0,0,0,0,0,0,5,8,0,0,0,0,0,0,5,8,0,0,0,0,0,0,5,8,0,0,0,0,0,0,5,8,0,0,6,7,0,0,5,7,0,0,5,8,0,0,6,7,0,0,0,0,0,0,6,7,0,0,0,0,0,0,6,7,0,0,0,0,0,0,6,7,0,0,0,0,0,0,6,7,7,7,7,6,6,6,6,],creatures: [], }, + title: { map: [8,8,8,8,5,5,5,5,8,0,0,0,0,0,0,5,8,0,0,0,0,0,0,5,8,0,0,0,0,0,0,5,8,6,6,6,7,7,7,5,8,6,2,3,2,3,7,5,7,5,3,2,3,2,8,6,7,5,5,5,8,8,8,6,7,0,0,0,0,0,0,6,7,0,0,0,0,0,0,6,7,0,0,0,0,0,0,6,7,7,7,7,6,6,6,6,],creatures: [{x: 2 ,y: 5 ,type: 1 ,color: 0},{x: 3 ,y: 5 ,type: 0 ,color: 1},{x: 4 ,y: 5 ,type: 0 ,color: 0},{x: 5 ,y: 5 ,type: 1 ,color: 1},{x: 2 ,y: 6 ,type: 2 ,color: 1},{x: 3 ,y: 6 ,type: 4 ,color: 0},{x: 4 ,y: 6 ,type: 4 ,color: 1},{x: 5 ,y: 6 ,type: 2 ,color: 0},], }, +} diff --git a/levels/1.tmx b/levels/1.tmx new file mode 100644 index 0000000..089e35e --- /dev/null +++ b/levels/1.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +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,23,23,23,23,1,1, +1,1,3,2,2,4,1,1, +1,1,3,2,2,4,1,1, +1,1,24,24,24,24,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, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/10.tmx b/levels/10.tmx new file mode 100644 index 0000000..a182257 --- /dev/null +++ b/levels/10.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,23,1, +1,1,1,1,1,18,6,1, +1,1,1,18,3,2,20,1, +1,1,18,13,2,4,1,1, +1,1,3,2,16,20,1,1, +1,18,2,4,20,1,1,1, +1,7,20,1,1,1,1,1, +1,24,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/11.tmx b/levels/11.tmx new file mode 100644 index 0000000..5c66744 --- /dev/null +++ b/levels/11.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,23,23,1,1,1,1, +1,1,4,3,1,1,1,1, +1,1,17,8,19,1,1,1, +1,1,1,2,2,19,1,1, +1,1,1,2,2,20,1,1, +1,1,21,9,12,19,1,1, +1,1,1,2,2,20,1,1, +1,1,1,17,2,19,1,1, +1,1,1,1,13,20,1,1, +1,1,1,1,24,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/12.tmx b/levels/12.tmx new file mode 100644 index 0000000..bab7126 --- /dev/null +++ b/levels/12.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +1,18,10,19,1,1,1,1, +1,12,2,19,1,1,1,1, +1,2,2,19,1,1,1,1, +1,2,2,19,19,19,19,1, +1,2,2,2,2,2,15,1, +1,2,2,2,2,13,20,1, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/13.tmx b/levels/13.tmx new file mode 100644 index 0000000..af0c710 --- /dev/null +++ b/levels/13.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,23,23,1,1,1,1, +1,18,2,2,1,1,1,1, +1,10,2,11,1,18,19,1, +1,9,2,22,21,7,4,1, +1,2,20,1,18,2,6,1, +1,2,19,18,2,1,2,1, +1,12,2,2,2,2,9,1, +1,1,1,1,1,24,24,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/14.tmx b/levels/14.tmx new file mode 100644 index 0000000..1aa02ec --- /dev/null +++ b/levels/14.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,23,23,23,23,1,1, +1,1,3,2,2,12,1,1, +1,1,1,1,10,1,1,1, +1,1,2,6,2,15,1,1, +1,1,17,3,2,1,1,1, +1,1,1,2,1,7,1,1, +1,1,1,2,1,1,1,1, +1,1,18,2,19,1,1,1, +1,1,11,2,4,4,1,1, +1,1,24,24,24,24,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/15.tmx b/levels/15.tmx new file mode 100644 index 0000000..dfc53d3 --- /dev/null +++ b/levels/15.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +1,1,1,1,18,19,1,1, +1,1,8,1,13,5,22,1, +1,1,1,2,3,1,1,1, +1,21,7,1,2,12,1,1, +1,1,17,4,2,20,1,1, +1,1,1,17,8,2,1,1, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/16.tmx b/levels/16.tmx new file mode 100644 index 0000000..f4162a2 --- /dev/null +++ b/levels/16.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,1,21,2,1,23,1, +1,1,1,18,2,1,6,1, +1,1,1,2,20,1,5,1, +1,1,18,2,1,1,1,1, +1,18,2,22,1,1,1,1, +1,2,2,4,1,1,1,1, +1,17,14,3,19,1,1,1, +1,1,3,24,2,1,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/2.tmx b/levels/2.tmx new file mode 100644 index 0000000..3546c63 --- /dev/null +++ b/levels/2.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,23,1,1,1,1,1, +1,18,3,1,1,1,1,1, +1,7,2,1,1,1,1,1, +1,17,2,19,1,1,1,1, +1,1,2,2,23,2,19,1, +1,1,17,2,2,2,4,1, +1,1,1,1,17,8,20,1, +1,1,1,1,1,24,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/3.tmx b/levels/3.tmx new file mode 100644 index 0000000..11ab9bf --- /dev/null +++ b/levels/3.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,1,1,1,18,1,1, +1,1,1,1,18,8,1,1, +1,1,1,18,2,2,1,1, +1,1,18,2,24,4,1,1, +1,1,2,1,1,2,1,1, +1,1,17,2,23,3,1,1, +1,1,1,17,2,2,1,1, +1,1,1,1,17,7,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/4.tmx b/levels/4.tmx new file mode 100644 index 0000000..851659a --- /dev/null +++ b/levels/4.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +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,21,3,2,19,1,1,1, +1,1,2,10,11,19,1,1, +1,1,17,11,10,2,1,1, +1,1,1,17,2,8,22,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, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/5.tmx b/levels/5.tmx new file mode 100644 index 0000000..ed83d8a --- /dev/null +++ b/levels/5.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +1,18,18,18,19,19,19,1, +1,18,11,2,10,2,19,1, +1,18,10,11,2,12,19,1, +1,17,2,10,11,9,20,1, +1,17,2,11,2,10,20,1, +1,17,17,17,20,20,20,1, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/6.tmx b/levels/6.tmx new file mode 100644 index 0000000..dff2965 --- /dev/null +++ b/levels/6.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +1,1,1,18,7,22,1,1, +1,18,11,2,8,19,1,1, +1,2,3,1,2,2,19,1, +1,17,10,4,10,11,20,1, +1,17,17,2,20,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, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/7.tmx b/levels/7.tmx new file mode 100644 index 0000000..9f85fa5 --- /dev/null +++ b/levels/7.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,23,1,1,1,1,1, +1,1,4,1,1,1,1,1, +1,18,2,1,1,1,1,1, +1,7,2,1,1,1,1,1, +1,2,9,1,1,1,1,1, +1,2,2,1,1,1,1,1, +1,14,20,1,1,1,1,1, +1,24,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/8.tmx b/levels/8.tmx new file mode 100644 index 0000000..60a3039 --- /dev/null +++ b/levels/8.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +1,18,7,19,1,1,1,1, +1,7,2,2,2,19,1,1, +1,17,2,2,2,2,2,1, +1,1,2,1,2,2,22,1, +1,1,24,1,2,2,16,1, +1,1,1,1,17,14,20,1, +1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/9.tmx b/levels/9.tmx new file mode 100644 index 0000000..382d243 --- /dev/null +++ b/levels/9.tmx @@ -0,0 +1,20 @@ + + + + + +20,1,1,1,1,1,1,17, +1,1,1,1,1,1,1,1, +1,1,23,1,3,19,1,1, +1,18,4,1,2,5,22,1, +1,4,2,2,2,2,1,1, +1,1,1,1,17,2,11,1, +1,1,1,1,1,2,20,1, +1,1,1,1,1,2,1,1, +1,1,1,1,1,14,1,1, +1,1,1,1,1,24,1,1, +1,1,1,1,1,1,1,1, +19,1,1,1,1,1,1,18 + + + diff --git a/levels/end.tmx b/levels/end.tmx new file mode 100644 index 0000000..4bec45d --- /dev/null +++ b/levels/end.tmx @@ -0,0 +1,20 @@ + + + + + +20,20,20,20,17,17,17,17, +20,1,1,1,1,1,1,17, +20,1,1,1,1,1,1,17, +20,1,1,1,1,1,1,17, +20,1,1,1,1,1,1,17, +20,1,1,18,19,1,1,17, +19,1,1,17,20,1,1,18, +19,1,1,1,1,1,1,18, +19,1,1,1,1,1,1,18, +19,1,1,1,1,1,1,18, +19,1,1,1,1,1,1,18, +19,19,19,19,18,18,18,18 + + + diff --git a/levels/mapcompile.py b/levels/mapcompile.py new file mode 100755 index 0000000..a290a3f --- /dev/null +++ b/levels/mapcompile.py @@ -0,0 +1,52 @@ +#!/usr/bin/python3 + +import ulvl +import sys + +if len(sys.argv) < 2: + print("usage:", sys.argv[0], "") + sys.exit(1) + +screenwidth, screenheight = 8, 8 + +tilemapping = { 16: 5, 17: 6, 18: 7, 19: 8, 20: 9, 21: 10, 22: 11, 23: 12 } + +FIRST_CREATURE_INDEX = 2 +LAST_CREATURE_INDEX = 15 + +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=': { ') + + creatures = [ ] + + print('map: [', end='') + for y in range(h): + for x in range(w): + thing = m.layers[0].tiles[y * w + x] - 1 + + if thing >= FIRST_CREATURE_INDEX and thing <= LAST_CREATURE_INDEX: + if thing % 2: # odd indices = blue on red tile, even = red on blue + print("3,", end='') + else: + print("2,", end='') + creatures.append({ 'type': (thing - FIRST_CREATURE_INDEX) // 2, 'color': thing % 2, 'x': x, 'y': y }) + else: + print("" + str(tilemapping.get(thing, thing)) + ",", end='') + + print('],', end=''); + print('creatures: [', end='') + + for c in creatures: + print("{x:", c['x'], ",y:", c['y'], ",type:", c['type'], ",color:", c['color'], end='},') + + print('],', end='') + + print(' },') + +print("}") diff --git a/levels/title.tmx b/levels/title.tmx new file mode 100644 index 0000000..f48ccd5 --- /dev/null +++ b/levels/title.tmx @@ -0,0 +1,20 @@ + + + + + +20,20,20,20,17,17,17,17, +20,1,1,1,1,1,1,17, +20,1,1,1,1,1,1,17, +20,1,1,1,1,1,1,17, +20,18,18,18,19,19,19,17, +20,18,5,4,3,6,19,17, +19,17,8,11,12,7,20,18, +19,17,17,17,20,20,20,18, +19,1,1,1,1,1,1,18, +19,1,1,1,1,1,1,18, +19,1,1,1,1,1,1,18, +19,19,19,19,18,18,18,18 + + + diff --git a/levels/tmptile-sheet.png b/levels/tmptile-sheet.png new file mode 100644 index 0000000..ea441c3 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 index 0000000..040c141 Binary files /dev/null and b/levels/tmptile.png differ diff --git a/levels/tmptile.tsx b/levels/tmptile.tsx new file mode 100644 index 0000000..cf8d15a --- /dev/null +++ b/levels/tmptile.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..b4ff844 --- /dev/null +++ b/license.txt @@ -0,0 +1,163 @@ +FontStruct Non-Commercial Software End User License Agreement 1.0 + +This software end user license agreement (hereinafter “Agreement”) is made +between you or, if you represent a legal entity, that legal entity (hereinafter +“You”) and “broomweed”, the copyright holder and designer (hereinafter +“Font Designer”) of the font software named “bugfont” (hereinafter +“Font Software”). + +1. BINDING AGREEMENT +This Agreement is a binding agreement between You and the Font Designer and +shall set forth the terms and conditions under which You can use the Font +Software. For the purposes of this Agreement, Font Software shall mean the Font +Software, and any parts or copies of it. + +2. GRANT OF NON-EXCLUSIVE AND LICENCE +The Font Designer hereby grants You a non-exclusive, non-transferable licence to +use the Font Software provided that You comply with all terms and conditions of +this Agreement. + +2.1 PERSONAL USE +You may use the Fonts Software only for Your own Personal Purposes and for Your +Work. For the purposes of this Agreement, “Personal Purposes” shall mean any +private use of the Font Software by you. “Work” shall mean any work or +result created by You or on Your behalf with the Font Software. For the +avoidance of doubt, the use for Personal Purposes does not allow any use of the +Font Software and/or any Work for commercial purposes (see Section 2.2) and/or +as a web-font (see Section 2.6). + +2.2 NO COMMERCIAL USE +Neither the Font Software, nor any of its individual components or any Work, may +be used by You for any form of profit-making or otherwise commercial projects, +without express prior written permission from the Font Designer. + +2.3 NO SALE OR DISTRIBUTION +You may not sell, rent, license, sublicense, distribute, redistribute, give-away +or make available (in any other way) the Font Software alone or as part of any +collection, product or service to any third party. + +2.4 NO MODIFICATION +You may not modify, adapt, rename, translate, reverse engineer, decompile, +disassemble, alter, or attempt to discover the source code of the Font Software. + +2.5 EMBEDDING +You may embed the Font Software in documents, applications or devices either as +a rasterized representation of the Font Software (e.g., a GIF or JPEG) or as a +subset of the Font Software as long as the document, application or device is +distributed in a secure format that permits only the viewing and printing but +not the editing of the text. + +2.6 NO USE AS A WEBFONT +You may not make the Font Software accessible on a web server in order to enable +a web browser to render the content of Websites using the respective Font +Software. + +2.7 BACKUP +You may make backup copies of the Font Software for archival purposes only, +provided that You retain exclusive custody and control over such copies. Any +backup copy of the Font Software must contain the same copyright, trademark, and +other proprietary information as the original. + +2.8 PRINT SHOPS AND SERVICE BUREAUS +You may provide a copy of the Font Software used in Your Work to a print shop, +service bureau, etc. for printing or otherwise outputting Your Work. Afterwards, +the print shop, service bureau, etc. must destroy and/or delete all copies of +the Font Software. + +3. ATTRIBUTION +If You make Your Work publicly accessible in any form, You must keep intact all +copyright notices for the Font Software and display, reasonable to the medium or +means You are utilizing for Your Work, an attribution notice (hereinafter “The +Attribution Notice”) comprising of: + +the name of the Font Designer or pseudonym, if applicable (“broomweed”); +the title of the Font Software (“bugfont”); and +to the extent reasonably practicable, the URI +(https://fontstruct.com/fontstructions/show/2549558) from where the Font was +originally downloaded. + +The Attribution Notice may be implemented in any reasonable manner. For the +avoidance of doubt, You may only use the credit required by this Section for the +purpose of attribution in the manner set out above and, by exercising Your +rights under this License, You may not implicitly or explicitly assert or imply +any connection with, sponsorship or endorsement by the Font Designer of You or +Your Work. + +4. YOUR REPRESENTATIONS AND INDEMNIFICATIONS +You represent, warrant, and covenant that You shall use the Font Software only +in accordance with the terms of this Agreement and keep the Font Designer fully +indemnified against all actions, claims, costs, proceedings and damages +whatsoever incurred by any breach of any of the aforesaid representations, +warranties of this Agreement. + +5. OWNERSHIP AND RIGHTS TO THE FONT SOFTWARE +The Font Designer reserves all rights not expressly granted to You in this +Agreement (hereinafter “Reserved Rights”). The Font Software is protected by +copyright and other intellectual property laws and treaties. You acknowledge +that the Font Designer shall be the sole owner of the Font Software, of the +copyright and all Reserved Rights of whatever kind and nature in the Font +Software. + +6. WARRANTY +The Font Software is made available “as is” WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT.THE FONT DESIGNER DOES NOT AND +CANNOT GUARANTEE THE PERFORMANCE OR RESULTS YOU MAY OBTAIN BY USING THE FONT +SOFTWARE. +THE FONT DESIGNER SHALL IN NO EVENT BE LIABLE TO YOU FOR ANY CONSEQUENTIAL, +INCIDENTAL OR SPECIAL DAMAGES, INCLUDING ANY LOST PROFITS OR LOST SAVINGS, OR +FOR ANY CLAIM BY ANY THIRD PARTY, ARISING OUT OF THE USE OR INABILITY TO USE THE +FONT SOFTWARE. + +7. TERMINATION / CONDITION PRECEDENT +You may use the Font Software only as set forth in this Agreement. Any use of +the Font Software that is not expressly allowed in this Agreement will +automatically terminate Your rights to use the Font Software under this +Agreement. If Your right to use the Font Software is terminated You shall +immediately stop using the Font Software, call back or change any Work that is +used in the public, and delete/destroy any copies of the Font Software. The +termination of the Agreement shall not affect the right of the Font Designer to +claim damages or other rights. + +8. WAIVER +A waiver by either Party of any term or condition of this Agreement shall not be +deemed or construed to be a waiver of such term or condition for the future or +any subsequent breach thereof. All remedies, rights, undertakings, obligations +and agreements contained in this Agreement shall be cumulative and none of them +shall be in limitation of any other remedy, right, undertaking, obligation or +agreement of either Party. + +9. GOVERNING LAW, PLACE OF JURISDICTION +This Agreement shall be construed and shall take effect in accordance with the +laws of the Federal Republic of Germany excluding the United Nations Convention +on Contracts for the International Sales of Goods (CISG). To the extent +permitted by law, the Courts of Berlin, Germany shall have exclusive +jurisdiction to resolve any dispute which may arise. + +10. ENTIRE AGREEMENT +This Agreement contains the entire agreement between the Parties. No variation +of any of the terms or conditions of this Agreement may be made unless such +variation is agreed in writing and signed by You and the Font Designer. + +11. SEVERABILITY +If any provision of this Agreement is adjudged by a court to be invalid or +unenforceable, such provision shall in no way affect any other provision of this +Agreement, the application of such provision in any other circumstance or the +validity or enforceability of this Agreement. The Parties will negotiate in +good faith about a provision that will replace the invalid or unenforceable +provision. + +FontStruct Notice: FontStruct no Party to this Agreement + +Please note that FontStruct (www.fontstruct.com) is not a party to this +Agreement between You and the Font Designer. + +FontStruct makes no warranty of any kind in connection with the Font Software, +its use or the results that You may obtain by using the Font Software. + +FontStruct shall in no event be liable to You or any third party for any damages +whatsoever arising out of or relating to this Agreement or the use of the Font +Software, including but not limited to incidental, special damages, any lost +profits or lost savings or any claim made by a third party. + diff --git a/loading.png b/loading.png new file mode 100644 index 0000000..d4dc0de Binary files /dev/null and b/loading.png differ diff --git a/music/bugjazz.mp3 b/music/bugjazz.mp3 new file mode 100755 index 0000000..3525ca9 Binary files /dev/null and b/music/bugjazz.mp3 differ diff --git a/pause.png b/pause.png new file mode 100644 index 0000000..2530de4 Binary files /dev/null and b/pause.png differ diff --git a/saveerror.png b/saveerror.png new file mode 100644 index 0000000..21fbd14 Binary files /dev/null and b/saveerror.png differ diff --git a/sfx/2024_10_06_14_09_14.mp3 b/sfx/2024_10_06_14_09_14.mp3 new file mode 100644 index 0000000..31c97d2 Binary files /dev/null and b/sfx/2024_10_06_14_09_14.mp3 differ diff --git a/sfx/2024_10_06_14_10_59.mp3 b/sfx/2024_10_06_14_10_59.mp3 new file mode 100644 index 0000000..91fa83a Binary files /dev/null and b/sfx/2024_10_06_14_10_59.mp3 differ diff --git a/sfx/2024_10_06_14_13_55.mp3 b/sfx/2024_10_06_14_13_55.mp3 new file mode 100644 index 0000000..687c52d Binary files /dev/null and b/sfx/2024_10_06_14_13_55.mp3 differ diff --git a/sfx/feet.wav b/sfx/feet.wav new file mode 100644 index 0000000..5aa79be Binary files /dev/null and b/sfx/feet.wav differ diff --git a/sfx/hop.wav b/sfx/hop.wav new file mode 100644 index 0000000..b1b8d44 Binary files /dev/null and b/sfx/hop.wav differ diff --git a/sfx/slide.wav b/sfx/slide.wav new file mode 100644 index 0000000..bb5a0d6 Binary files /dev/null and b/sfx/slide.wav differ diff --git a/sfx/win.wav b/sfx/win.wav new file mode 100644 index 0000000..1ed9cdf Binary files /dev/null and b/sfx/win.wav differ diff --git a/zucchinibread.js b/zucchinibread.js new file mode 100644 index 0000000..f7b8ed3 --- /dev/null +++ b/zucchinibread.js @@ -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(game.ctx.draw.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, + } +})();