From: cassowarii <2374677+cassowarii@users.noreply.github.com> Date: Sat, 13 Jun 2026 22:13:57 +0000 (-0700) Subject: zucchinibread.js 2.0... X-Git-Url: https://www.git.cassowary.me/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=ld59.git zucchinibread.js 2.0... --- diff --git a/cover.png b/cover.png deleted file mode 100644 index 5026f84..0000000 Binary files a/cover.png and /dev/null differ diff --git a/game.js b/game.js index d22e86e..e9e9957 100644 --- a/game.js +++ b/game.js @@ -1,8 +1,6 @@ "use strict"; -let game; - -/* --------- definitions ---------- */ +/* --------- setup ---------- */ /* state: * TITLE: title screen @@ -15,23 +13,16 @@ let game; */ let State = { TITLE: -1, STAND: 0, DRAG: 1, DESTROY: 2, REPLACE: 3, LIGHTNING: 4, GAMEOVER: 5 }; -let can_continue = false; - -let save_data = 1; const SAVE_KEY = "casso.signalconnectinggame.save" -zb.ready(function() { - game = zb.create_game({ +zb.ready(() => { + zb.create_game({ canvas: 'canvas', - canvas_w: 486, - canvas_h: 756, draw_scale: 3, tile_size: 16, level_w: 8, level_h: 8, background_color: '#f4ecce', - run_in_background: true, - load_with_progress_bar: true, save_key: SAVE_KEY, state: State.STAND, events: { @@ -41,13 +32,6 @@ zb.ready(function() { mousedown: handle_mousedown, }, modes: { - menu: { - draw: do_draw_menu, - update: do_update_menu, - }, - youwon: { - draw: do_draw_youwon, - }, game: { draw: do_draw, update: do_update, @@ -55,9 +39,8 @@ zb.ready(function() { }, mode: 'game', buttons: {}, - }); - - game.register_images({ + }) + .register_images({ connector: 'img/connector.png', gameover: 'img/gameover.png', lightning: 'img/lightning.png', @@ -72,9 +55,8 @@ zb.ready(function() { timebar_inside: 'img/timebar_inside.png', timebar_outside: 'img/timebar_outside.png', title: 'img/title.png', - }); - - game.register_sfx({ + }) + .register_sfx({ ok: { path: 'sfx/ok.wav', volume: 0.7, @@ -96,102 +78,92 @@ zb.ready(function() { path: 'sfx/gameover.mp3', volume: 0.3, }, - }); - - game.register_music({ + }) + .register_music({ bgm: { - path: 'music/suitgamesong', + path: 'music/suitgamesong.mp3', volume: 0.5, }, - }); - - game.resources_ready(); - - board_offset_x = game.screen_w / 2 - TILE_W * BOARD_W / 2; - board_offset_y = game.screen_h / 2 - TILE_H * BOARD_H / 2; - - high_score = game.load('high_score') || 0; - - game.buttons.menu = zb.buttons.create({ - img: game.img.menubuttons, - button_w: 64, - button_h: 16, - buttons: [ ], - }); - - game.buttons.lightning = zb.buttons.create({ - img: game.img.lightningbutton, - button_w: 36, - button_h: 18, - buttons: [ - { - /* Lightning button */ - x: game.screen_w / 2 - 16, - y: board_offset_y + BOARD_H * TILE_H + 12, - id: 0, - disabled: true, - callback: function() { - if (game.state === State.STAND) { - game.state = State.LIGHTNING; - game.buttons.lightning.buttons[0].disabled = true; - } + }) + .resources_ready() + .setup(() => { + board_offset_x = game.screen_w / 2 - TILE_W * BOARD_W / 2; + board_offset_y = game.screen_h / 2 - TILE_H * BOARD_H / 2; + + high_score = game.load('high_score') || 0; + + game.buttons.lightning = zb.buttons.create({ + img: game.img.lightningbutton, + button_w: 36, + button_h: 18, + buttons: [ + { + /* Lightning button */ + x: game.screen_w / 2 - 16, + y: board_offset_y + BOARD_H * TILE_H + 12, + id: 0, + disabled: true, + callback: function() { + if (game.state === State.STAND) { + game.state = State.LIGHTNING; + game.buttons.lightning.buttons[0].disabled = true; + } + }, }, - }, - ], - }); + ], + }); - game.buttons.play = zb.buttons.create({ - img: game.img.playbutton, - button_w: 36, - button_h: 18, - buttons: [ - { - /* Play button */ - x: game.screen_w / 2 - 16, - y: board_offset_y + BOARD_H * TILE_H + 12, - id: 0, - callback: function() { - if (game.state === State.TITLE) { - initialize(); - score = 0; - displayed_score = 0; - timer_running = false; - lightnings = 0; - } + game.buttons.play = zb.buttons.create({ + img: game.img.playbutton, + button_w: 36, + button_h: 18, + buttons: [ + { + /* Play button */ + x: game.screen_w / 2 - 16, + y: board_offset_y + BOARD_H * TILE_H + 12, + id: 0, + callback: function() { + if (game.state === State.TITLE) { + initialize(); + score = 0; + displayed_score = 0; + timer_running = false; + lightnings = 0; + } + }, }, - }, - ], - }); + ], + }); - game.buttons.replay = zb.buttons.create({ - img: game.img.replaybutton, - button_w: 36, - button_h: 18, - buttons: [ - { - /* Replay button */ - x: game.screen_w / 2 - 16, - y: board_offset_y + BOARD_H * TILE_H + 12, - id: 0, - callback: function() { - if (game.state === State.GAMEOVER) { - initialize(); - score = 0; - displayed_score = 0; - timer_running = false; - lightnings = 0; - n_colors = 2; - game.music.bgm.currentTime = 0; - } + game.buttons.replay = zb.buttons.create({ + img: game.img.replaybutton, + button_w: 36, + button_h: 18, + buttons: [ + { + /* Replay button */ + x: game.screen_w / 2 - 16, + y: board_offset_y + BOARD_H * TILE_H + 12, + id: 0, + callback: function() { + if (game.state === State.GAMEOVER) { + initialize(); + score = 0; + displayed_score = 0; + timer_running = false; + lightnings = 0; + n_colors = 2; + game.music.bgm.currentTime = 0; + } + }, }, - }, - ], - }); - - game.transition.color = game.background_color; + ], + }); - initialize(); + game.transition.color = game.background_color; + }); }); /* ------ relevant constant types --------- */ @@ -552,172 +524,156 @@ function do_update_menu(delta) { /* ---------- draw functions ----------- */ /* DRAW */ -function do_draw(ctx) { - ctx.save(); - - if (game.state === State.TITLE || show_title) { - zb.image_draw(ctx, game.img.title, board_offset_x + TILE_W * BOARD_W / 2 - game.img.title.width / 2, board_offset_y + TILE_H * BOARD_H / 2 - game.img.title.height / 2); - - zb.buttons.draw(ctx, game.buttons.play); - } - - draw_score(ctx); +function do_draw() { + draw.scoped(() => { + if (game.state === State.TITLE || show_title) { + draw.image(game.img.title, + board_offset_x + TILE_W * BOARD_W / 2 - game.img.title.width / 2, + board_offset_y + TILE_H * BOARD_H / 2 - game.img.title.height / 2); + + draw.buttons(game.buttons.play); + } - draw_tiles(ctx); + draw_score(); - draw_lightning(ctx); + draw_tiles(); - draw_selector(ctx); + draw_lightning(); - if (game.state === State.LIGHTNING) { - zb.sprite_draw(ctx, game.img.lightning, 9, 9, 0, 0, mouse_x - 4, mouse_y - 4); - } + draw_selector(); - if (game.state === State.GAMEOVER) { - zb.image_draw(ctx, game.img.gameover, board_offset_x + BOARD_W * TILE_W / 2 - game.img.gameover.width / 2, board_offset_y + BOARD_H * TILE_H / 2 - game.img.gameover.height / 2); + if (game.state === State.LIGHTNING) { + draw.sprite(game.img.lightning, 9, 9, 0, 0, mouse_x - 4, mouse_y - 4); + } - zb.buttons.draw(ctx, game.buttons.replay); - } + if (game.state === State.GAMEOVER) { + draw.image(game.img.gameover, + board_offset_x + BOARD_W * TILE_W / 2 - game.img.gameover.width / 2, + board_offset_y + BOARD_H * TILE_H / 2 - game.img.gameover.height / 2); - ctx.restore(); + draw.buttons(game.buttons.replay); + } + }); } -function draw_score_num(ctx, x, y, num) { +function draw_score_num(x, y, num) { let render_count = num; let col = 1; let lb = game.buttons.lightning.buttons[0]; while (col <= 8 || render_count > 0) { - zb.sprite_draw(ctx, game.img.scorenumbers, 8, 8, render_count % 10, 0, x + (8 - col) * 8, y); + draw.sprite(game.img.scorenumbers, 8, 8, render_count % 10, 0, x + (8 - col) * 8, y); render_count = Math.floor(render_count / 10); col ++; } } -function draw_score(ctx) { +function draw_score() { let timebar_x = board_offset_x + BOARD_W * TILE_W / 2 - game.img.timebar_outside.width / 2 + 16; let timebar_y = board_offset_y - 18 - game.img.timebar_outside.height / 2; if (game.state === State.GAMEOVER || game.state === State.TITLE) { - draw_score_num(ctx, timebar_x, timebar_y + 2, high_score); + draw_score_num(timebar_x, timebar_y + 2, high_score); - zb.sprite_draw(ctx, game.img.text, 32, 12, 0, 0, timebar_x - game.img.text.width, timebar_y + 2); // "best" + draw.sprite(game.img.text, 32, 12, 0, 0, timebar_x - game.img.text.width, timebar_y + 2); // "best" } else { - zb.image_draw(ctx, game.img.timebar_outside, timebar_x, timebar_y); + draw.image(game.img.timebar_outside, timebar_x, timebar_y); let timebar_width = Math.round((timer / MAX_TIMER) * game.img.timebar_inside.width); - zb.sprite_draw(ctx, game.img.timebar_inside, timebar_width, game.img.timebar_inside.height, 0, 0, timebar_x, timebar_y); + draw.sprite(game.img.timebar_inside, timebar_width, game.img.timebar_inside.height, 0, 0, timebar_x, timebar_y); - zb.sprite_draw(ctx, game.img.text, 32, 12, 0, 2, timebar_x - game.img.text.width, timebar_y + 2); // "time" + draw.sprite(game.img.text, 32, 12, 0, 2, timebar_x - game.img.text.width, timebar_y + 2); // "time" } - zb.sprite_draw(ctx, game.img.text, 32, 12, 0, 1, timebar_x - game.img.text.width, timebar_y - 10); // "score" + draw.sprite(game.img.text, 32, 12, 0, 1, timebar_x - game.img.text.width, timebar_y - 10); // "score" - draw_score_num(ctx, timebar_x, timebar_y - 10, displayed_score); + draw_score_num(timebar_x, timebar_y - 10, displayed_score); } -function draw_tiles(ctx) { - ctx.save(); +function draw_tiles() { + draw.scoped(() => { + draw.translate(board_offset_x, board_offset_y); - ctx.translate(board_offset_x, board_offset_y); - - for (let t of level) { - if (t == null) continue; - - zb.sprite_draw(ctx, game.img.tiles, TILE_W, TILE_H, t.suit, t.color, t.x, t.y); - } + for (let t of level) { + if (t == null) continue; - ctx.restore(); + draw.sprite(game.img.tiles, TILE_W, TILE_H, t.suit, t.color, t.x, t.y); + } + }); } -function draw_selector(ctx) { - ctx.save(); - - ctx.translate(board_offset_x, board_offset_y); +function draw_selector() { + draw.scoped(() => { + draw.translate(board_offset_x, board_offset_y); - if ((game.state === State.STAND || game.state === State.LIGHTNING) && selector.x !== -1 && selector.y !== -1) { - zb.sprite_draw(ctx, game.img.selector, 20, 20, 0, 0, selector.x * TILE_W - 2, selector.y * TILE_H - 2); - } else if (game.state === State.DRAG) { - let last_p = null; - for (let p of path) { - if (last_p === null) { - zb.sprite_draw(ctx, game.img.connector, 20, 20, 0, 0, p[0] * TILE_W - 2, p[1] * TILE_H - 2); - } else { - zb.sprite_draw(ctx, game.img.connector, 20, 20, 0, 0, p[0] * TILE_W - 2, p[1] * TILE_H - 2); - } - if (last_p) { - if (last_p[0] == p[0]) { - for (let y = Math.min(last_p[1], p[1]) + 1; y <= Math.max(last_p[1], p[1]) - 1; y++) { - zb.sprite_draw(ctx, game.img.connector, 20, 20, 2, 0, p[0] * TILE_W - 2, y * TILE_H - 2); - } - } else if (last_p[1] == p[1]) { - for (let x = Math.min(last_p[0], p[0]) + 1; x <= Math.max(last_p[0], p[0]) - 1; x++) { - zb.sprite_draw(ctx, game.img.connector, 20, 20, 1, 0, x * TILE_W - 2, p[1] * TILE_H - 2); + if ((game.state === State.STAND || game.state === State.LIGHTNING) && selector.x !== -1 && selector.y !== -1) { + draw.sprite(game.img.selector, 20, 20, 0, 0, selector.x * TILE_W - 2, selector.y * TILE_H - 2); + } else if (game.state === State.DRAG) { + let last_p = null; + for (let p of path) { + if (last_p === null) { + draw.sprite(game.img.connector, 20, 20, 0, 0, p[0] * TILE_W - 2, p[1] * TILE_H - 2); + } else { + draw.sprite(game.img.connector, 20, 20, 0, 0, p[0] * TILE_W - 2, p[1] * TILE_H - 2); + } + if (last_p) { + if (last_p[0] == p[0]) { + for (let y = Math.min(last_p[1], p[1]) + 1; y <= Math.max(last_p[1], p[1]) - 1; y++) { + draw.sprite(game.img.connector, 20, 20, 2, 0, p[0] * TILE_W - 2, y * TILE_H - 2); + } + } else if (last_p[1] == p[1]) { + for (let x = Math.min(last_p[0], p[0]) + 1; x <= Math.max(last_p[0], p[0]) - 1; x++) { + draw.sprite(game.img.connector, 20, 20, 1, 0, x * TILE_W - 2, p[1] * TILE_H - 2); + } } } + last_p = p; } - last_p = p; - } - last_p = null; - for (let p of path) { - if (last_p === null) { - zb.sprite_draw(ctx, game.img.connector, 20, 20, 0, 1, p[0] * TILE_W - 2, p[1] * TILE_H - 2); - } else { - zb.sprite_draw(ctx, game.img.connector, 20, 20, 0, 1, p[0] * TILE_W - 2, p[1] * TILE_H - 2); - } - if (last_p) { - if (last_p[0] == p[0]) { - for (let y = Math.min(last_p[1], p[1]) + 1; y <= Math.max(last_p[1], p[1]) - 1; y++) { - zb.sprite_draw(ctx, game.img.connector, 20, 20, 2, 1, p[0] * TILE_W - 2, y * TILE_H - 2); - } - } else if (last_p[1] == p[1]) { - for (let x = Math.min(last_p[0], p[0]) + 1; x <= Math.max(last_p[0], p[0]) - 1; x++) { - zb.sprite_draw(ctx, game.img.connector, 20, 20, 1, 1, x * TILE_W - 2, p[1] * TILE_H - 2); + last_p = null; + for (let p of path) { + if (last_p === null) { + draw.sprite(game.img.connector, 20, 20, 0, 1, p[0] * TILE_W - 2, p[1] * TILE_H - 2); + } else { + draw.sprite(game.img.connector, 20, 20, 0, 1, p[0] * TILE_W - 2, p[1] * TILE_H - 2); + } + if (last_p) { + if (last_p[0] == p[0]) { + for (let y = Math.min(last_p[1], p[1]) + 1; y <= Math.max(last_p[1], p[1]) - 1; y++) { + draw.sprite(game.img.connector, 20, 20, 2, 1, p[0] * TILE_W - 2, y * TILE_H - 2); + } + } else if (last_p[1] == p[1]) { + for (let x = Math.min(last_p[0], p[0]) + 1; x <= Math.max(last_p[0], p[0]) - 1; x++) { + draw.sprite(game.img.connector, 20, 20, 1, 1, x * TILE_W - 2, p[1] * TILE_H - 2); + } } } + last_p = p; } - last_p = p; } - } - - ctx.restore(); + }); } -function draw_lightning(ctx) { +function draw_lightning() { if (game.state !== State.GAMEOVER && game.state !== State.TITLE) { - zb.buttons.draw(ctx, game.buttons.lightning); + draw.buttons(game.buttons.lightning); let render_count = lightnings; let col = 1; let lb = game.buttons.lightning.buttons[0]; while (render_count > 0 || col == 1) { - zb.sprite_draw(ctx, game.img.lightningnumbers, 5, 8, render_count % 10, 0, lb.x + game.buttons.lightning.button_w - 7 - 5 * col, lb.y + 4); + draw.sprite(game.img.lightningnumbers, 5, 8, render_count % 10, 0, lb.x + game.buttons.lightning.button_w - 7 - 5 * col, lb.y + 4); render_count = Math.floor(render_count / 10); col ++; } } for (let lp of lightning_particles) { - zb.sprite_draw(ctx, game.img.lightning, 9, 9, 0, 0, lp.x - 4, lp.y - 4); + draw.sprite(game.img.lightning, 9, 9, 0, 0, lp.x - 4, lp.y - 4); } } -function do_draw_menu(ctx) { - ctx.save(); - ctx.translate(0, Math.sin(title_timer / TITLE_BOUNCE_LENGTH * 2 * Math.PI) * TITLE_BOUNCE_DISTANCE); - zb.screen_draw(ctx, game.img.title); - ctx.restore(); - - zb.screen_draw(ctx, game.img.titlegraphic); - zb.buttons.draw(ctx, game.buttons.menu); -} - -function do_draw_youwon(ctx) { - zb.screen_draw(ctx, game.img.youwin); -} - /* ---------- event handlers ------------ */ -function handle_gamestart(game) { +function handle_gamestart() { console.log("Game start!"); game.state = State.TITLE; @@ -734,7 +690,7 @@ function toggle_mute_button() { game.toggle_mute(); } -function handle_mousedown(game, e, x, y) { +function handle_mousedown(e, x, y) { if (game.state === State.TITLE) { if (zb.buttons.click(game.buttons.play, x, y)) return; } else if (game.state === State.GAMEOVER) { @@ -790,7 +746,7 @@ function handle_mousedown(game, e, x, y) { } } -function handle_mousemove(game, e, x, y) { +function handle_mousemove(e, x, y) { mouse_x = x; mouse_y = y; @@ -878,16 +834,11 @@ function handle_mousemove(game, e, x, y) { let clicked_once_lightning = false; -function handle_mouseup(game, e, x, y) { +function handle_mouseup(e, x, y) { if (game.state === State.TITLE) { zb.buttons.update(game.buttons.play, x, y); } else if (game.state === State.GAMEOVER) { zb.buttons.update(game.buttons.replay, x, y); - } else if (game.mode === 'youwon') { - game.long_transition(zb.transition.type.fade, 1000, function() { - game.mode = 'menu'; - continue_level = 1; - }); } else { zb.buttons.update(game.buttons.lightning, x, y); diff --git a/pause.png b/pause.png deleted file mode 100644 index 7479e89..0000000 Binary files a/pause.png and /dev/null differ diff --git a/zucchinibread.js b/zucchinibread.js index f46ddc6..6c399cf 100644 --- a/zucchinibread.js +++ b/zucchinibread.js @@ -1,7 +1,7 @@ "use strict"; -/* Please forgive my strictly procedural/functional style here. - * Thanks for reading though! */ +/* There are programming patterns in here that no one should ever have discovered. + * Proceed at your own risk. */ let zb = (function() { /* ---- Util ---- */ @@ -47,6 +47,53 @@ let zb = (function() { } function rand_int(a, b) { + let min = 0; + let max = 0; + if (b === undefined) { + max = a; + } else { + min = a; + max = b; + } + return Math.floor(Math.random() * (max - min) + min); + } + + /* ---- Interface to game code ---- */ + + function implicit_bind(context, func) { + /* cursed and evil javascript magic */ + /* lets us temporarily bind globals like dynamic scope, + * so we can pass in properties like 'game' to our functions + * without needing to call them directly */ + /* this lets us namespace stuff under 'game' or 'draw' + * but also not have those namespaces actually accessible + * from the global scope. but they will be accessible + * in all child function calls! aah! */ + let original_props = {} + for (let key of Object.keys(context)) { + original_props[key] = globalThis[key]; + globalThis[key] = context[key]; + } + + let result = func(); + + for (let key of Object.keys(context)) { + globalThis[key] = original_props[key]; + } + + return result; + } + + function call_game_func(game, func, args) { + return implicit_bind({ game: game }, () => { + func.apply(game, args); + }); + } + + function call_draw_func(game, draw, func, args) { + return implicit_bind({ game: game, draw: draw }, () => { + func.apply(game, args); + }); } /* ---- Resource loading / loading screen ---- */ @@ -55,6 +102,30 @@ let zb = (function() { let _SFX_ARRAY_SIZE = 10; + /* Update progress bar if the progress bar is activated. */ + function _update_progress_bar(game) { + if (game.load_with_progress_bar) { + if (game._progressbar_img_loaded) { + game.ctx.global.save(); + game.ctx.global.scale(game.draw_scale, game.draw_scale); + if (!game.img._progressbar.failed_to_load) { + game.ctx.global.drawImage( + game.img._progressbar, + 0, + 0, + Math.round(game._things_loaded / game._total_things_to_load * game.img._progressbar.width), + game.img._progressbar.height, + Math.round(game.canvas_w / 2 / game.draw_scale - game.img._progressbar.width / 2), + Math.round(game.canvas_h / 2 / game.draw_scale - game.img._progressbar.height / 2), + Math.round(game._things_loaded / game._total_things_to_load * game.img._progressbar.width), + game.img._progressbar.height, + ); + } + game.ctx.global.restore(); + } + } + } + /* Returns a callback that should be called when a resource finishes loading. */ function _register_resource(name, game, callback) { game._total_things_to_load ++; @@ -63,24 +134,7 @@ let zb = (function() { if (!game.ready_to_go) { game._things_loaded ++; console.log("Loaded", name + ". Things loaded:", game._things_loaded, "/", game._total_things_to_load); - if (game.load_with_progress_bar) { - if (game._progressbar_img_loaded) { - game.ctx.global.save(); - game.ctx.global.scale(game.draw_scale, game.draw_scale); - game.ctx.global.drawImage( - game.img._progressbar, - 0, - 0, - Math.round(game._things_loaded / game._total_things_to_load * game.img._progressbar.width), - game.img._progressbar.height, - Math.round(game.canvas_w / 2 / game.draw_scale - game.img._progressbar.width / 2), - Math.round(game.canvas_h / 2 / game.draw_scale - game.img._progressbar.height / 2), - Math.round(game._things_loaded / game._total_things_to_load * game.img._progressbar.width), - game.img._progressbar.height, - ); - game.ctx.global.restore(); - } - } + _update_progress_bar(game); _check_if_loaded(game); if (callback) { callback(); @@ -89,16 +143,29 @@ let zb = (function() { } } - /* 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); - } + function _set_resource_load_handlers(game, resource, success_event, name, callback) { + /* Check if resource successfully loaded and update loading screen. */ + resource.addEventListener(success_event, _register_resource(name, game, callback), false); + + /* Check if resource failed to load bc file doesn't exist and print error message. */ + resource.addEventListener('error', function(e) { + resource.failed_to_load = true; + game._things_loaded ++; + console.error("Error loading resource, skipping:", name); + _update_progress_bar(game); + _check_if_loaded(game); + }, false); + } + + /* Call any queued setup functions in game context (after waiting for things to load, maybe) + * and clear the setup function queue */ + function _call_setup_funcs(game) { + for (let setupfunc of game._setup_funcs) { + call_game_func(game, setupfunc, []); } + + /* clear queue */ + game._setup_funcs = []; } /* Check if the game has loaded everything and if so show the 'click to start' image */ @@ -108,6 +175,8 @@ let zb = (function() { if (game._things_loaded >= game._total_things_to_load) { console.log("Ready"); game.ready_to_go = true; + game._waiting_for_resources = false; + _call_setup_funcs(game); game._on_ready(); } } @@ -117,11 +186,9 @@ let zb = (function() { let sfx_size = sfxdata[key].copies || _SFX_ARRAY_SIZE; let sfx_array = new Array(sfx_size); for (let i = 0; i < sfx_size; i++) { + let resource_name = sfxdata[key].path + '#' + (i+1); 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]); + _set_resource_load_handlers(game, sfx_array[i], 'canplaythrough', resource_name); if (sfxdata[key].hasOwnProperty('volume')) { sfx_array[i].volume = sfxdata[key].volume; } @@ -130,6 +197,8 @@ let zb = (function() { _array: sfx_array, _index: 0, play: function() { + if (this.failed_to_load) return; + if (!game.muted) { this._array[this._index].currentTime = 0; this._array[this._index].play(); @@ -139,19 +208,21 @@ let zb = (function() { } } } + + return game; } function _register_music(musicdata, game) { for (let key in musicdata) { let musicpath; - if (musicdata[key].path.match(/\.[a-z]{3}$/)) { + if (musicdata[key].path.match(/\.[a-z0-9]+$/i)) { 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 :("); + console.error("Browser knows how to play neither mp3 nor ogg :("); return; } let music = new Audio(musicpath); @@ -163,13 +234,15 @@ let zb = (function() { /* 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); + + _set_resource_load_handlers(game, music, 'canplaythrough', musicpath); /* Handle music changes when muted, so when we unmute, * the correct music will be playing. */ music._og_play = music.play; music.play = function() { + if (music.failed_to_load) return; + if (game.muted) { music.was_playing = true; } else { @@ -179,6 +252,8 @@ let zb = (function() { music._og_pause = music.pause; music.pause = function() { + if (music.failed_to_load) return; + if (game.muted) { music.was_playing = false; } else { @@ -188,6 +263,8 @@ let zb = (function() { game.music[key] = music; } + + return game; } function _register_images(imgdata, game) { @@ -198,7 +275,7 @@ let zb = (function() { result[key] = _recursive_load_images(pathmap[key]); } else { result[key] = new Image(); - result[key].onload = _register_resource(pathmap[key], game); + _set_resource_load_handlers(game, result[key], 'load', pathmap[key]); result[key].src = pathmap[key]; } } @@ -209,6 +286,8 @@ let zb = (function() { for (let k in loaded_imgs) { game.img[k] = loaded_imgs[k]; } + + return game; } function _create_canvas_context(canvas) { @@ -224,26 +303,75 @@ let zb = (function() { return new_ctx; } - function create_game(params) { - let game_props = {...params}; + function _generate_draw_context(canvas_ctx) { + /* this generates the magic 'draw' objects that get passed in + * implicitly to the draw event functions. we can still get + * at the original canvas context by `draw.context`, but this + * way, what is getting drawn onto is implicitly passed with + * `implicit_bind`, so we don't have to remember to pass `ctx` + * to every drawing function (which i always forget) but we + * can still draw to the correct canvas for stuff like + * custom transitions */ + return { + context: canvas_ctx, + + image: ((function(ctx) { + return function(img, x, y) { + image_draw(ctx, img, x, y); + }; + })(canvas_ctx)), - game_props.frame_rate = params.frame_rate || 60; + screen: ((function(ctx) { + return function(img) { + screen_draw(ctx); + }; + })(canvas_ctx)), - 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; + sprite: ((function(ctx) { + return function(img, section_w, section_h, section_x, section_y, dest_x, dest_y) { + sprite_draw(ctx, img, section_w, section_h, section_x, section_y, dest_x, dest_y); + }; + })(canvas_ctx)), - game_props.tile_size = params.tile_size || 16; - game_props.level_w = params.level_w || 10; - game_props.level_h = params.level_h || 7; + buttons: ((function(ctx) { + return function(button_data) { + button_draw(ctx, button_data); + }; + })(canvas_ctx)), - game_props.screen_w = game_props.canvas_w / game_props.draw_scale; - game_props.screen_h = game_props.canvas_h / game_props.draw_scale; + scoped: ((function(ctx) { + return function(scopefunc) { + ctx.save(); + scopefunc(); + ctx.restore(); + }; + })(canvas_ctx)), + translate: ((function(ctx) { + return function(x, y) { + ctx.translate(Math.round(x), Math.round(y)); + }; + })(canvas_ctx)), + + rotate: ((function(ctx) { + return function(radians) { + ctx.rotate(radians); + }; + })(canvas_ctx)), + + rotate_deg: ((function(ctx) { + return function(degrees) { + ctx.rotate(degrees * Math.PI / 180); + }; + })(canvas_ctx)), + }; + } + + function create_game(params) { + let game_props = {...params}; + + game_props.frame_rate = params.frame_rate || 60; + game_props.draw_scale = params.draw_scale || 4; game_props.background_color = params.background_color || '#000000'; let canvas = document.getElementById(game_props.canvas); @@ -252,6 +380,7 @@ let zb = (function() { global_ctx.imageSmoothingEnabled = false; global_ctx.webkitImageSmoothingEnabled = false; global_ctx.mozImageSmoothingEnabled = false; + let global_draw = _generate_draw_context(global_ctx); let mask_canvas = document.createElement('canvas'); mask_canvas.width = canvas.width; @@ -260,6 +389,7 @@ let zb = (function() { mask_ctx.imageSmoothingEnabled = false; mask_ctx.webkitImageSmoothingEnabled = false; mask_ctx.mozImageSmoothingEnabled = false; + let mask_draw = _generate_draw_context(mask_ctx); let copy_canvas = document.createElement('canvas'); copy_canvas.width = canvas.width; @@ -268,6 +398,7 @@ let zb = (function() { copy_ctx.imageSmoothingEnabled = false; copy_ctx.webkitImageSmoothingEnabled = false; copy_ctx.mozImageSmoothingEnabled = false; + let copy_draw = _generate_draw_context(copy_ctx); let draw_canvas = document.createElement('canvas'); draw_canvas.width = canvas.width; @@ -276,8 +407,13 @@ let zb = (function() { draw_ctx.imageSmoothingEnabled = false; draw_ctx.webkitImageSmoothingEnabled = false; draw_ctx.mozImageSmoothingEnabled = false; + let draw_draw = _generate_draw_context(draw_ctx); let game = { + /* Defaults that can be overridden */ + run_in_background: true, + load_with_progress_bar: true, + /* General properties */ ...game_props, @@ -287,30 +423,61 @@ let zb = (function() { ready_to_go: false, _total_things_to_load: 1, _things_loaded: 0, + _waiting_for_resources: false, + _setup_funcs: [], resources_ready: function() { this._things_loaded ++; console.log("Finished enumerating resources to load. Things loaded:", this._things_loaded, "/", this._total_things_to_load); + this._waiting_for_resources = true; _check_if_loaded(this); + return 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(); + if (this.img._clicktostart && this.img._clicktostart.complete) { + this.ctx.global.save(); + this.ctx.global.scale(this.draw_scale, this.draw_scale); + if (!this.img._clicktostart.failed_to_load) { + this.ctx.global.drawImage(this.img._clicktostart, 0, 0); + } + this.ctx.global.restore(); } }, playing: false, play: function() { this.playing = true; if (this.events.gamestart) { - this.events.gamestart(this); + call_game_func(this, this.events.gamestart, []); } _loop(this); }, _norun: false, + setup: function(setupfunc) { + if (this._waiting_for_resources) { + /* If we said resources_ready() before calling setup, then + * the setup function will get queued until everything is + * loaded. This allows us to let the setup function depend + * on properties of the things that are loaded (eg image + * sizes, etc) */ + /* TODO: It would be kind of neat to queue the other resource + * loading functions as well if _waiting_for_resources is + * set so that resources_ready() really always waits for + * stuff to load before proceeding. But that might mess + * with the loading screen, and I don't know why this would + * be needed (maybe if somehow one image got loaded depending + * on a property of another image?) so low priority. + * Right now setup() is probably the only thing that depends + * on waiting for loads to complete. */ + this._setup_funcs.push(setupfunc); + } else { + /* If we aren't waiting for resources (ie either everything + * already loaded, or we called setup() before calling + * resources_ready()), just call the setupfunc directly */ + call_game_func(this, setupfunc, []); + } + + return this; + }, /* Audio */ sfx: {}, @@ -330,10 +497,10 @@ let zb = (function() { } }, register_sfx: function(sfxdata) { - _register_sfx(sfxdata, this); + return _register_sfx(sfxdata, this); }, register_music: function(musicdata) { - _register_music(musicdata, this); + return _register_music(musicdata, this); }, /* Drawing */ @@ -343,13 +510,19 @@ let zb = (function() { copy: copy_ctx, /* context for copying the old screen on transition */ draw: draw_ctx, /* context for drawing the real level */ }, + draw_context: { + global: global_draw, + mask: mask_draw, + copy: copy_draw, + draw: draw_draw, + }, create_canvas_context: function() { /* Function to return a fresh screen-sized canvas context, if we need it. */ return _create_canvas_context(this.canvas); }, img: {}, register_images: function(imgdata) { - _register_images(imgdata, this); + return _register_images(imgdata, this); }, /* Save */ @@ -389,12 +562,24 @@ let zb = (function() { }; })(); + /* Calculate screen size */ + if (!game.canvas_w) { + game.canvas_w = game.canvas.width; + } + + if (!game.canvas_h) { + game.canvas_h = game.canvas.height; + } + + game.screen_w = game.canvas_w / game.draw_scale; + game.screen_h = game.canvas_h / game.draw_scale; + /* 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); + call_game_func(game, params.events[ev], [e]); } } } @@ -444,14 +629,14 @@ let zb = (function() { canvas.onkeydown = function(e) { if (!game._norun && game.events.keydown) { - game.events.keydown(game, e); + call_game_func(game, game.events.keydown, [e]); e.preventDefault(); } } canvas.onkeyup = function(e) { if (!game._norun && game.events.keyup) { - game.events.keyup(game, e); + call_game_func(game, game.events.keyup, [e]); e.preventDefault(); } } @@ -475,15 +660,18 @@ let zb = (function() { /* Set loading stuff */ let loading_img = new Image(); - loading_img.onload = _register_resource('loading.png', game, function() { + _set_resource_load_handlers(game, loading_img, 'load', 'loading.png', 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); + if (!loading_img.failed_to_load) { + game.ctx.global.drawImage(loading_img, 0, 0); + } game.ctx.global.restore(); } }); loading_img.src = 'loading.png'; + game.img._loading = loading_img; if (game.load_with_progress_bar) { game._progressbar_img_loaded = false; @@ -491,14 +679,16 @@ let zb = (function() { game._progressbar_height = 0; let progressbar_img = new Image(); - progressbar_img.onload = _register_resource('progressbar.png', game, function() { + _set_resource_load_handlers(game, progressbar_img, 'load', 'progressbar.png', function() { game._progressbar_img_loaded = true; game._progressbar_width = progressbar_img.width; game._progressbar_height = progressbar_img.height; 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); + if (!loading_img.failed_to_load) { + game.ctx.global.drawImage(loading_img, 0, 0); + } game.ctx.global.restore(); } }); @@ -507,25 +697,21 @@ let zb = (function() { } 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; + _register_images({ _pause: 'pause.png' }, game); } 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; + _register_images({ _saveerror: 'saveerror.png' }, game); } let clicktostart_img = new Image(); - clicktostart_img.onload = _register_resource('clicktostart.png', game, function() { + _set_resource_load_handlers(game, clicktostart_img, 'load', 'clicktostart.png', 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); + if (!game.img._clicktostart.failed_to_load) { + game.ctx.global.drawImage(game.img._clicktostart, 0, 0); + } game.ctx.global.restore(); } }); @@ -578,15 +764,22 @@ let zb = (function() { if (timestamp == undefined) { timestamp = 0; _last_frame_time = timestamp; - } else if (_last_frame_time == 0) { - _last_frame_time = timestamp; - } else { - _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; + } + _timedelta += timestamp - _last_frame_time; + _last_frame_time = timestamp; + + let times_updated = 0; + while (_timedelta >= 1000 / game.frame_rate) { + _update(game, 1000 / game.frame_rate); + _timedelta -= 1000 / game.frame_rate; + + /* Draw at least once every 4 frames */ + /* (also sometimes we get a really big delta e.g. if + * run_in_background: false, and we don't want to honor that) */ + times_updated ++; + if (times_updated >= 4) { + _timedelta = 0; + break; } } @@ -601,11 +794,11 @@ let zb = (function() { function _update(game, delta) { if (game.update_func) { - game.update_func(delta); + call_game_func(game, game.update_func, [delta]); } if (game.modes && game.modes.hasOwnProperty(game.mode) && game.modes[game.mode].update) { - game.modes[game.mode].update(delta); + call_game_func(game, game.modes[game.mode].update, [delta]); } if (game.transition.is_transitioning) { @@ -670,7 +863,7 @@ let zb = (function() { game._clicked_button.state = 0; _draw(game); if (game._clicked_button.callback) { - game._clicked_button.callback(); + call_game_func(game, game._clicked_button.callback, []); } game._clicked_button = null; return; @@ -680,19 +873,21 @@ let zb = (function() { /* ---- Mouse ---- */ function _handle_mousedown(game, e) { - if (!game.playing) return; - game.touchmode = false; + if (!game.playing) return; + const rect = game.canvas.getBoundingClientRect(); let x = Math.round((e.clientX - rect.left) / rect.width * game.screen_w) - 1; let y = Math.round((e.clientY - rect.top) / rect.height * game.screen_h) - 1; if (e.button === 0 && game.events.mousedown) { - game.events.mousedown(game, e, x, y); + call_game_func(game, game.events.mousedown, [e, x, y]); } } function _handle_mouseup(game, e) { + game.touchmode = false; + if (!game.playing && game.ready_to_go) { /* Click to start */ if (game.hasOwnProperty('save_key') && !game._show_save_error) { @@ -712,7 +907,9 @@ let zb = (function() { 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); + if (!game.img._saveerror.failed_to_load) { + game.ctx.global.drawImage(game.img._saveerror, 0, 0); + } game.ctx.global.restore(); return; } @@ -721,8 +918,6 @@ let zb = (function() { return; } - game.touchmode = false; - const rect = game.canvas.getBoundingClientRect(); let x = Math.round((e.clientX - rect.left) / rect.width * game.screen_w) - 1; let y = Math.round((e.clientY - rect.top) / rect.height * game.screen_h) - 1; @@ -730,18 +925,18 @@ let zb = (function() { _handle_clicked_button(game); if (e.button === 0 && game.events.mouseup) { - game.events.mouseup(game, e, x, y); + call_game_func(game, game.events.mouseup, [e, x, y]); } } function _handle_mousemove(game, e) { game.touchmode = false; - const rect = game.canvas.getBoundingClientRect(); - let x = Math.round((e.clientX - rect.left) / rect.width * game.screen_w) - 1; - let y = Math.round((e.clientY - rect.top) / rect.height * game.screen_h) - 1; - if (game.events.mousemove) { - game.events.mousemove(game, e, x, y); + if (game.playing && game.events.mousemove) { + const rect = game.canvas.getBoundingClientRect(); + let x = Math.round((e.clientX - rect.left) / rect.width * game.screen_w) - 1; + let y = Math.round((e.clientY - rect.top) / rect.height * game.screen_h) - 1; + call_game_func(game, game.events.mousemove, [e, x, y]); } } @@ -750,10 +945,10 @@ let zb = (function() { let _last_touch_event; function _handle_touchstart(game, e) { - if (!game.playing) return; - game.touchmode = true; + if (!game.playing) return; + if (e.touches) e = e.touches[0]; _last_touch_event = e; @@ -762,24 +957,24 @@ let zb = (function() { let x = Math.round((e.clientX - rect.left) / rect.width * game.screen_w) - 1; let y = Math.round((e.clientY - rect.top) / rect.height * game.screen_h) - 1; if (game.events.touchstart) { - game.events.touchstart(game, e, x, y); + call_game_func(game, game.events.touchstart, [e, x, y]); } else if (game.events.mousedown) { if (game.events.mousemove) { - game.events.mousemove(game, e, x, y); + call_game_func(game, game.events.mousemove, [e, x, y]); } - game.events.mousedown(game, e, x, y); + call_game_func(game, game.events.mousedown, [e, x, y]); } } function _handle_touchend(game, e) { + game.touchmode = true; + if (!game.playing && game.ready_to_go) { /* Click to start */ game.play(); return; } - game.touchmode = true; - const rect = game.canvas.getBoundingClientRect(); let x = Math.round((_last_touch_event.clientX - rect.left) / rect.width * game.screen_w) - 1; let y = Math.round((_last_touch_event.clientY - rect.top) / rect.height * game.screen_h) - 1; @@ -797,10 +992,10 @@ let zb = (function() { } function _handle_touchmove(game, e) { - if (!game.playing) return; - game.touchmode = true; + if (!game.playing) return; + if (e.touches) e = e.touches[0]; _last_touch_event = e; @@ -809,9 +1004,9 @@ let zb = (function() { let x = Math.round((e.clientX - rect.left) / rect.width * game.screen_w) - 1; let y = Math.round((e.clientY - rect.top) / rect.height * game.screen_h) - 1; if (game.events.touchmove) { - game.events.touchmove(game, e, x, y); + call_game_func(game, game.events.touchmove, [e, x, y]); } else if (game.events.mousemove) { - game.events.mousemove(game, e, x, y); + call_game_func(game, game.events.mousemove, [e, x, y]); } } @@ -820,16 +1015,17 @@ let zb = (function() { function _draw(game) { game.ctx.draw.save(); + game.ctx.draw.save(); game.ctx.draw.fillStyle = game.background_color; - game.ctx.draw.fillRect(0, 0, game.screen_w, game.screen_h); + game.ctx.draw.restore(); if (game.draw_func) { - game.draw_func(game.ctx.draw); + call_draw_func(game, game.draw_context.draw, game.draw_func, []); } if (game.modes && game.modes.hasOwnProperty(game.mode) && game.modes[game.mode].draw) { - game.modes[game.mode].draw(game.ctx.draw); + call_draw_func(game, game.draw_context.draw, game.modes[game.mode].draw, []); } if (game.transition.mid_long) { @@ -840,16 +1036,15 @@ let zb = (function() { game.ctx.draw.restore(); if (game.transition.is_transitioning) { - _draw_transition(game, game.ctx.mask); + call_draw_func(game, game.draw_context.mask, _draw_transition, []); } else { game.ctx.mask.drawImage(game.ctx.draw.canvas, 0, 0); } if (game.modes && game.modes.hasOwnProperty(game.mode) && game.modes[game.mode].draw_after_transition) { - game.modes[game.mode].draw_after_transition(game.ctx.mask); + call_draw_func(game, game.draw_context.mask, game.modes[game.mode].draw_after_transition, []); } - game.ctx.global.fillStyle = 'rgb(0, 0, 0)'; game.ctx.global.fillRect(0, 0, game.screen_w * game.draw_scale, game.screen_h * game.draw_scale); @@ -859,7 +1054,7 @@ let zb = (function() { game.ctx.global.drawImage(game.ctx.mask.canvas, 0, 0); - if (game._norun) { + if (game._norun && !game.img._pause.failed_to_load) { game.ctx.global.drawImage(game.img._pause, 0, 0); } @@ -869,19 +1064,25 @@ let zb = (function() { /* 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, - Math.round(section_w * section_x), Math.round(section_h * section_y), section_w, section_h, - Math.round(dest_x), Math.round(dest_y), section_w, section_h) + if (!img.failed_to_load) { + ctx.drawImage(img, + section_w * section_x, section_h * section_y, section_w, section_h, + Math.round(dest_x), Math.round(dest_y), section_w, section_h) + } } /* Draw an image over the whole screen lol */ function screen_draw(ctx, img) { - ctx.drawImage(img, 0, 0); + if (!img.failed_to_load) { + ctx.drawImage(img, 0, 0); + } } /* Draw an image at a specified position */ function image_draw(ctx, img, dest_x, dest_y) { - ctx.drawImage(img, Math.round(dest_x), Math.round(dest_y)); + if (!img.failed_to_load) { + ctx.drawImage(img, Math.round(dest_x), Math.round(dest_y)); + } } /* Draw a set of buttons. */ @@ -995,7 +1196,7 @@ let zb = (function() { let tdih = game.transition.dir_invert_h; _internal_start_transition(game, type, length, function() { game.transition.mid_long = false; - callback(); + call_game_func(game, callback, []); game.transition.dir_invert_v = tdiv; game.transition.dir_invert_h = tdih; }); @@ -1020,7 +1221,7 @@ let zb = (function() { game.ctx.copy.drawImage(game.ctx.draw.canvas, 0, 0); - callback(); + call_game_func(game, callback, []); game.transition.is_transitioning = true; } @@ -1031,283 +1232,67 @@ let zb = (function() { if (game.transition.done_func) { setTimeout(function() { - game.transition.done_func(); + call_game_func(game, game.transition.done_func, []); game.transition.done_func = null; }, 400); } } - function _draw_transition(game, ctx) { + function _draw_transition() { let frac = game.transition.timer / game.transition.end_time; - ctx.save(); - ctx.clearRect(0, 0, game.screen_w, game.screen_h); - game.transition.type(game, ctx, game.ctx.copy, game.ctx.draw, frac, game.transition.invert_shape); - ctx.restore(); + draw.scoped(() => { + draw.context.clearRect(0, 0, game.screen_w, game.screen_h); + game.transition.type(game.ctx.copy.canvas, game.ctx.draw.canvas, frac, game.transition.invert_shape); + }); } - function _draw_slide_down_transition(game, ctx, oldc, newc, frac, reverse) { + function _draw_slide_down_transition(from, to, frac, reverse) { let offset = frac * game.screen_h; - ctx.drawImage(oldc.canvas, 0, -offset); - ctx.drawImage(newc.canvas, 0, game.screen_h - offset); + draw.image(from, 0, -offset); + draw.image(to, 0, game.screen_h - offset); } - function _draw_slide_up_transition(game, ctx, oldc, newc, frac, reverse) { + function _draw_slide_up_transition(from, to, frac, reverse) { let offset = frac * game.screen_h; - ctx.drawImage(oldc.canvas, 0, offset); - ctx.drawImage(newc.canvas, 0, - game.screen_h + offset); + draw.image(from, 0, offset); + draw.image(to, 0, - game.screen_h + offset); } - function _draw_fade_transition(game, ctx, oldc, newc, frac, reverse) { - ctx.globalAlpha = 1; - ctx.drawImage(oldc.canvas, 0, 0); - ctx.globalAlpha = frac; - ctx.drawImage(newc.canvas, 0, 0); + function _draw_fade_transition(from, to, frac, reverse) { + draw.context.globalAlpha = 1; + draw.image(from, 0, 0); + draw.context.globalAlpha = frac; + draw.image(to, 0, 0); } - /* ---- 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() + /* document.ready */ + /* Apparently, we have had DOMContentLoaded widely available since 2015. + * I've been carrying around this ancient document.ready invocation in these games since 2017, + * when using scary new js features might break things, but now it's been almost ten years + * and it's not like the rest of the game code would work in IE8 or whatever. So here's the + * simple version of ready. */ + function ready(ready_func) { + if (document.readyState === "loading") { + ready_func(); + } else { + document.addEventListener("DOMContentLoaded", ready_func); } - return ready; - })(); + } /* ---- direction objects ---- */ const _dir_up = { x: 0, y: -1, index: 0, rotate: 0, horizontal: false, vertical: true }; - const _dir_right = { x: 1, y: 0, index: 1, rotate: Math.PI/2, horizontal: true, vertical: false }; + const _dir_right = { x: 1, y: 0, index: 1, rotate: Math.PI / 2, horizontal: true, vertical: false }; const _dir_down = { x: 0, y: 1, index: 2, rotate: Math.PI, horizontal: false, vertical: true }; - const _dir_left = { x: -1, y: 0, index: 3, rotate: 3*Math.PI/2, horizontal: true, vertical: false }; + const _dir_left = { x: -1, y: 0, index: 3, rotate: 3 * Math.PI / 2, horizontal: true, vertical: false }; _dir_up.opposite = _dir_down; _dir_down.opposite = _dir_up; _dir_right.opposite = _dir_left; _dir_left.opposite = _dir_right; + /* ---- export zb ---- */ return { create_game: create_game, @@ -1318,9 +1303,12 @@ let zb = (function() { copy_list: copy_list, copy_flat_objlist: copy_flat_objlist, - sprite_draw: sprite_draw, - screen_draw: screen_draw, - image_draw: image_draw, + draw: { + sprite: sprite_draw, + screen: screen_draw, + image: image_draw, + buttons: button_draw, + }, buttons: { create: create_buttons, @@ -1345,5 +1333,5 @@ let zb = (function() { slide_down: _draw_slide_down_transition, }, }, - } + }; })();