diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7929358 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +# EditorConfig is awesome: https://editorconfig.org + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true diff --git a/.env b/.env new file mode 100644 index 0000000..8ec22da --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +NODE_ENV=development +SESSION_KEY=THIS_IS_MY_TEMP_KEY \ No newline at end of file diff --git a/.gitignore b/.gitignore index d8d6855..5e6f416 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,4 @@ TODO.md # old directories kept locally stash/ -trash/ \ No newline at end of file +trash/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c689dfb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[json5]": { + "editor.insertSpaces": true, + "editor.tabSize": 4 + } +} diff --git a/package-lock.json b/package-lock.json index d8a62fc..989d54d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "iew-site", - "version": "0.14.0", + "version": "0.14.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "iew-site", - "version": "0.14.0", + "version": "0.14.2", "license": "MIT", "devDependencies": { "web-weevr": "git+ssh://git@git.itsericwoodward.com:eric/web-weevr.git" diff --git a/package.json b/package.json index fa4aa77..94fd632 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iew-site", - "version": "0.14.1", + "version": "0.14.2", "description": "", "main": "index.js", "scripts": {}, diff --git a/site.config.json5 b/site.config.json5 index 9f9dc37..981ed4c 100644 --- a/site.config.json5 +++ b/site.config.json5 @@ -1,60 +1,59 @@ { - site: { - title: "It's Eric Woodward (dotcom)", - author: { - name: "Eric Woodward", - email: "hey@itsericwoodward.com", // not used - photo: "/images/eric-8bit.gif", - site: "https://itsericwoodward.com", - geo: { - position: "35.4, -80.5", - placename: "Concord", - region: "US-NC", - }, - }, + site: { + title: "It's Eric Woodward (dotcom)", + author: { + name: "Eric Woodward", + email: "hey@itsericwoodward.com", // not used + photo: "/images/eric-8bit.gif", + site: "https://itsericwoodward.com", + geo: { + position: "35.4, -80.5", + placename: "Concord", + region: "US-NC", + }, + }, - base_uri: "", - // csp: "default-src 'self' data: https://v8.js-dos.com 'unsafe-inline'; img-src 'self' https://*; media-src 'self' https://* data:; script-src 'self' https://v8.js-dos.com 'wasm-eval' 'unsafe-eval' 'unsafe-inline'; style-src 'self' https://v8.js-dos.com 'unsafe-inline'; worker-src 'self' blob:;", - csp: "default-src 'self' data: ; img-src 'self' https://*; media-src 'self' https://* data:;", - robots: "index,follow", - language: "en-us", - copyright: "Copyright 2014-2025 Eric Woodward, licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.", - basePath: "", - uri: "https://www.itsericwoodward.com", - comment_insert: "\n\ - ___________________________.\n\ - |;;| |;;||\n\ - |[]|---------------------|[]||\n\ - |;;| |;;||\n\ - |;;| |;;||\n\ - |;;| ItsEricWoodward.com |;;||\n\ - |;;| |;;||\n\ - |;;| |;;||\n\ - |;;| |;;||\n\ - |;;|_____________________|;;||\n\ - |;;;;;;;;;;;;;;;;;;;;;;;;;;;||\n\ - |;;;;;;_______________ ;;;;;||\n\ - |;;;;;| ___ |;;;;;||\n\ - |;;;;;| |;;;| |;;;;;||\n\ - |;;;;;| |;;;| |;;;;;||\n\ - |;;;;;| |;;;| |;;;;;||\n\ - |;;;;;| |;;;| |;;;;;||\n\ - |;;;;;| |___| |;;;;;||\n\ - \\_____|_______________|_____||\n\ - ~~~~~^^^^^^^^^^^^^^^^^~~~~~~\n\ - ", - }, - build: { - journalsPerPage: 5, - srcPath: "src", - outputPath: "out", - publishPath: "public", - }, - serve: { - authTypeUI: "basic", - handleStatic: true, - port: 4997, - shortCodeLink: "/q/", - static404: "./public/errors/404.html", - }, + base_uri: "", + csp: "default-src 'self' data: ; img-src 'self' https://*; media-src 'self' https://* data:; worker-src 'self' blob:;", + robots: "index,follow", + language: "en-us", + copyright: "Copyright 2014-2026 Eric Woodward, licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.", + basePath: "", + uri: "https://www.itsericwoodward.com", + comment_insert: "\n\ + ___________________________.\n\ + |;;| |;;||\n\ + |[]|---------------------|[]||\n\ + |;;| |;;||\n\ + |;;| |;;||\n\ + |;;| ItsEricWoodward.com |;;||\n\ + |;;| |;;||\n\ + |;;| |;;||\n\ + |;;| |;;||\n\ + |;;|_____________________|;;||\n\ + |;;;;;;;;;;;;;;;;;;;;;;;;;;;||\n\ + |;;;;;;_______________ ;;;;;||\n\ + |;;;;;| ___ |;;;;;||\n\ + |;;;;;| |;;;| |;;;;;||\n\ + |;;;;;| |;;;| |;;;;;||\n\ + |;;;;;| |;;;| |;;;;;||\n\ + |;;;;;| |;;;| |;;;;;||\n\ + |;;;;;| |___| |;;;;;||\n\ + \\_____|_______________|_____||\n\ + ~~~~~^^^^^^^^^^^^^^^^^~~~~~~\n\ + ", + }, + build: { + journalsPerPage: 5, + srcPath: "src", + outputPath: "out", + publishPath: "public", + }, + serve: { + authTypeUI: "basic", + handleStatic: true, + port: 4997, + shortCodeLink: "/q/", + static404: "./public/errors/404.html", + }, } diff --git a/src/assets/_root/webtoys/bbe/scripts/bbe.js b/src/assets/_root/webtoys/bbe/scripts/bbe.js index 31867a7..e765418 100644 --- a/src/assets/_root/webtoys/bbe/scripts/bbe.js +++ b/src/assets/_root/webtoys/bbe/scripts/bbe.js @@ -1,536 +1,545 @@ +// @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0 +/**************************************************************************** + * Byte Beasties Escape! + * + * Copyright 2023-2026 Eric Woodward + * Source released under CC0 Public Domain License v1.0 + * https://www.planarvagabond.com/licenses/cc0/ + * http://creativecommons.org/publicdomain/zero/1.0/ + ****************************************************************************/ import { createLightning, getMousePos } from "./lightning.js"; const startGame = (canvas) => { - const ctx = canvas.getContext("2d"), - BEASTIE_IMAGES = [ - "blinker_shadow_red.png", - "creeper_shadow_red.png", - "grabber_shadow_red.png", - "scanner_shadow_red.png", - "bobbler_shadow_yellow.png", - "gawker_shadow_yellow.png", - "reacher_shadow_yellow.png", - "shuffler_shadow_yellow.png", - "puffer_shadow_green.png", - "seer_shadow_green.png", - "spooker_shadow_green.png", - "thinker_shadow_green.png", - "chomper_shadow_blue.png", - "diver_shadow_blue.png", - "snapper_shadow_blue.png", - "walker_shadow_blue.png", - ], - GATE = { - HEIGHT: 20, - MARGIN: 25, - MAX_SPACE: 140, - MIN_SPACE: 60, - START: canvas.height - 200, - WIDTH: 65, - }, - game = { - interval: 1000 / 90, // ms/f = 1000 ms/s / 90 f/s - winScore: 15000, - winScores: { - easy: 15000, - medium: 30000, - hard: 50000, - endless: 0, - }, - blockerFrequency: 0.3, - blockerFrequencies: { - easy: 0.3, - medium: 0.4, - hard: 0.5, - endless: 0.3, - }, - hasBlockers: true, - isOver: false, - isPaused: false, - showRestartModal: false, - maxLead: 400, - maxSpeed: -8, + const ctx = canvas.getContext("2d"), + BEASTIE_IMAGES = [ + "blinker_shadow_red.png", + "creeper_shadow_red.png", + "grabber_shadow_red.png", + "scanner_shadow_red.png", + "bobbler_shadow_yellow.png", + "gawker_shadow_yellow.png", + "reacher_shadow_yellow.png", + "shuffler_shadow_yellow.png", + "puffer_shadow_green.png", + "seer_shadow_green.png", + "spooker_shadow_green.png", + "thinker_shadow_green.png", + "chomper_shadow_blue.png", + "diver_shadow_blue.png", + "snapper_shadow_blue.png", + "walker_shadow_blue.png", + ], + GATE = { + HEIGHT: 20, + MARGIN: 25, + MAX_SPACE: 140, + MIN_SPACE: 60, + START: canvas.height - 200, + WIDTH: 65, + }, + game = { + interval: 1000 / 90, // ms/f = 1000 ms/s / 90 f/s + winScore: 15000, + winScores: { + easy: 15000, + medium: 30000, + hard: 50000, + endless: 0, + }, + blockerFrequency: 0.3, + blockerFrequencies: { + easy: 0.3, + medium: 0.4, + hard: 0.5, + endless: 0.3, + }, + hasBlockers: true, + isOver: false, + isPaused: false, + showRestartModal: false, + maxLead: 400, + maxSpeed: -8, - speed: -1, - speedBoost: { - duration: 1, - multiplier: 2, - }, - }, - drag = 0.3, - beastie = { - width: 48, - height: 48, - x: canvas.width / 2 - 20, - y: GATE.START + 100, + speed: -1, + speedBoost: { + duration: 1, + multiplier: 2, + }, + }, + drag = 0.3, + beastie = { + width: 48, + height: 48, + x: canvas.width / 2 - 20, + y: GATE.START + 100, - // velocity - dx: 0, - dy: 0, + // velocity + dx: 0, + dy: 0, - // for tracking speed boosts - boostEnd: 0, + // for tracking speed boosts + boostEnd: 0, - score: 0, - image: null, - }, - player = { - dx: 0, - dy: 0, - }, - enemy = { - width: canvas.width, - height: canvas.height, - x: 0, - y: canvas.height - 50, - delaySeconds: 5, - speed: -2, - }, - random = (min, max) => Math.random() * (max - min) + min, - images = BEASTIE_IMAGES.reduce((acc, name) => { - const tempImg = new Image(); - tempImg.src = `/webtoys/bbe/beasties/${name}`; - acc[name] = tempImg; - return acc; - }, {}), - img = new Image(), - hasCollision = (gate) => - // Collision check: AAB (Axis-Aligned Bounding Box) - beastie.x < gate.x + GATE.WIDTH && - beastie.x > gate.x - beastie.width && - beastie.y < gate.y + GATE.HEIGHT && - beastie.y > gate.y - beastie.height, - settingsBeastie = document.getElementById("settingsBeastie"); + score: 0, + image: null, + }, + player = { + dx: 0, + dy: 0, + }, + enemy = { + width: canvas.width, + height: canvas.height, + x: 0, + y: canvas.height - 50, + delaySeconds: 5, + speed: -2, + }, + random = (min, max) => Math.random() * (max - min) + min, + images = BEASTIE_IMAGES.reduce((acc, name) => { + const tempImg = new Image(); + tempImg.src = `/webtoys/bbe/beasties/${name}`; + acc[name] = tempImg; + return acc; + }, {}), + img = new Image(), + hasCollision = (gate) => + // Collision check: AAB (Axis-Aligned Bounding Box) + beastie.x < gate.x + GATE.WIDTH && + beastie.x > gate.x - beastie.width && + beastie.y < gate.y + GATE.HEIGHT && + beastie.y > gate.y - beastie.height, + settingsBeastie = document.getElementById("settingsBeastie"); - let // vertical space between gates - minPlatformSpace = GATE.MIN_SPACE, - maxPlatformSpace = GATE.MAX_SPACE, - gates = [ - { - x: canvas.width / 2 - GATE.WIDTH / 2, - y: GATE.START, - touched: false, - blocker: false, - }, - ], - y = GATE.START, - keydown = false, - runtime = 0, - lastTimestamp; + let // vertical space between gates + minPlatformSpace = GATE.MIN_SPACE, + maxPlatformSpace = GATE.MAX_SPACE, + gates = [ + { + x: canvas.width / 2 - GATE.WIDTH / 2, + y: GATE.START, + touched: false, + blocker: false, + }, + ], + y = GATE.START, + keydown = false, + runtime = 0, + lastTimestamp; - img.onload = () => { - ctx.drawImage(img, beastie.x, beastie.y); - }; - // img.src = "/webtoys/bbe/beasties/blinker_shadow_red.png"; + img.onload = () => { + ctx.drawImage(img, beastie.x, beastie.y); + }; + // img.src = "/webtoys/bbe/beasties/blinker_shadow_red.png"; - Object.keys(images).forEach((key, i) => { - const input = document.createElement("input"), - label = document.createElement("label"), - bImage = images[key]; - label.className = "beastieImageInputWrapper"; - input.name = "beastie"; - input.type = "radio"; - // TODO: Add Cookie support - input.checked = i === 0; - input.value = key; - label.appendChild(input); - bImage.className = "beastieImage"; - label.appendChild(bImage); - settingsBeastie.appendChild(label); - }); + Object.keys(images).forEach((key, i) => { + const input = document.createElement("input"), + label = document.createElement("label"), + bImage = images[key]; + label.className = "beastieImageInputWrapper"; + input.name = "beastie"; + input.type = "radio"; + // TODO: Add Cookie support + input.checked = i === 0; + input.value = key; + label.appendChild(input); + bImage.className = "beastieImage"; + label.appendChild(bImage); + settingsBeastie.appendChild(label); + }); - const renderLightning = () => { - var color = "hsla(60, 100%, 50%, .7)"; + const renderLightning = () => { + var color = "hsla(60, 100%, 50%, .7)"; - ctx.save(); - ctx.globalCompositeOperation = "lighter"; + ctx.save(); + ctx.globalCompositeOperation = "lighter"; - ctx.strokeStyle = color; - ctx.shadowColor = color; + ctx.strokeStyle = color; + ctx.shadowColor = color; - ctx.fillStyle = color; - ctx.fillRect(enemy.x, enemy.y, enemy.width, canvas.height); - ctx.fillStyle = "hsla(0, 0%, 10%, 0.2)"; - ctx.shadowBlur = 0; - ctx.globalCompositeOperation = "source-over"; - ctx.fillRect(enemy.x, enemy.y, enemy.width, canvas.height); - ctx.globalCompositeOperation = "lighter"; - ctx.shadowBlur = 15; + ctx.fillStyle = color; + ctx.fillRect(enemy.x, enemy.y, enemy.width, canvas.height); + ctx.fillStyle = "hsla(0, 0%, 10%, 0.2)"; + ctx.shadowBlur = 0; + ctx.globalCompositeOperation = "source-over"; + ctx.fillRect(enemy.x, enemy.y, enemy.width, canvas.height); + ctx.globalCompositeOperation = "lighter"; + ctx.shadowBlur = 15; - const lightning = createLightning(canvas, enemy); - ctx.beginPath(); - for (let i = 0; i < lightning.length; i++) { - ctx.lineTo(lightning[i].x, Math.max(lightning[i].y, enemy.y)); - } - ctx.stroke(); - // requestAnimationFrame(renderLightning); - ctx.restore(); - }; + const lightning = createLightning(canvas, enemy); + ctx.beginPath(); + for (let i = 0; i < lightning.length; i++) { + ctx.lineTo(lightning[i].x, Math.max(lightning[i].y, enemy.y)); + } + ctx.stroke(); + // requestAnimationFrame(renderLightning); + ctx.restore(); + }; - const restartGame = () => { - const whichBeastie = document.querySelector( - 'input[name="beastie"]:checked' - ).value, - settingsMode = document.getElementById("settingsMode"); + const restartGame = () => { + const whichBeastie = document.querySelector( + 'input[name="beastie"]:checked' + ).value, + settingsMode = document.getElementById("settingsMode"); - if (settingsMode) { - const { value } = settingsMode; - game.winScore = game.winScores[value]; - game.blockerFrequency = game.blockerFrequencies[value]; - } + if (settingsMode) { + const { value } = settingsMode; + game.winScore = game.winScores[value]; + game.blockerFrequency = game.blockerFrequencies[value]; + } - img.src = `/webtoys/bbe/beasties/${whichBeastie}`; + img.src = `/webtoys/bbe/beasties/${whichBeastie}`; - game.isPaused = false; - game.isOver = false; - game.showRestartModal = false; - y = GATE.START; - minPlatformSpace = GATE.MIN_SPACE; - maxPlatformSpace = GATE.MAX_SPACE; - // all the gates - starts in the bottom middle - gates = [ - { - x: canvas.width / 2 - GATE.WIDTH / 2, - y: GATE.START, - touched: false, - blocker: false, - }, - ]; - beastie.dx = 0; - beastie.dy = 0; - beastie.boostEnd = 0; - beastie.score = 0; - beastie.x = canvas.width / 2 - 20; - beastie.y = canvas.height - 50; + game.isPaused = false; + game.isOver = false; + game.showRestartModal = false; + y = GATE.START; + minPlatformSpace = GATE.MIN_SPACE; + maxPlatformSpace = GATE.MAX_SPACE; + // all the gates - starts in the bottom middle + gates = [ + { + x: canvas.width / 2 - GATE.WIDTH / 2, + y: GATE.START, + touched: false, + blocker: false, + }, + ]; + beastie.dx = 0; + beastie.dy = 0; + beastie.boostEnd = 0; + beastie.score = 0; + beastie.x = canvas.width / 2 - 20; + beastie.y = canvas.height - 50; - player.dx = 0; - player.dy = 0; + player.dx = 0; + player.dy = 0; - enemy.y = canvas.height - 50; + enemy.y = canvas.height - 50; - runtime = 0; + runtime = 0; - while (y > 0) { - // the next gate can be placed above the previous one with a space - // somewhere between the min and max space - y -= GATE.HEIGHT + random(minPlatformSpace, maxPlatformSpace); + while (y > 0) { + // the next gate can be placed above the previous one with a space + // somewhere between the min and max space + y -= GATE.HEIGHT + random(minPlatformSpace, maxPlatformSpace); - gates.push({ - x: random( - GATE.MARGIN, - canvas.width - GATE.MARGIN - GATE.WIDTH - ), - y, - touched: false, - }); - } - }, - // game loop - loop = (timestamp) => { - requestAnimationFrame(loop); + gates.push({ + x: random( + GATE.MARGIN, + canvas.width - GATE.MARGIN - GATE.WIDTH + ), + y, + touched: false, + }); + } + }, + // game loop + loop = (timestamp) => { + requestAnimationFrame(loop); - // limit FPS - if (timestamp - lastTimestamp <= game.interval) return; + // limit FPS + if (timestamp - lastTimestamp <= game.interval) return; - if (!(game.isOver || game.isPaused)) - runtime += timestamp - lastTimestamp; - lastTimestamp = timestamp; + if (!(game.isOver || game.isPaused)) + runtime += timestamp - lastTimestamp; + lastTimestamp = timestamp; - if (game.isOver) { - ctx.save(); - ctx.fillStyle = "yellow"; - ctx.fillRect( - canvas.width / 4, - canvas.height / 4, - enemy.width / 2, - canvas.height / 2 - ); - ctx.fillStyle = "black"; - ctx.font = "32px sans"; - ctx.fillText( - beastie.y <= 0 ? "You Win!" : "Game Over", - canvas.width / 3, - canvas.height / 3, - canvas.width / 3 - ); - ctx.fillText( - `Score: + if (game.isOver) { + ctx.save(); + ctx.fillStyle = "yellow"; + ctx.fillRect( + canvas.width / 4, + canvas.height / 4, + enemy.width / 2, + canvas.height / 2 + ); + ctx.fillStyle = "black"; + ctx.font = "32px sans"; + ctx.fillText( + beastie.y <= 0 ? "You Win!" : "Game Over", + canvas.width / 3, + canvas.height / 3, + canvas.width / 3 + ); + ctx.fillText( + `Score: ${beastie.score}`, - canvas.width / 3, - (canvas.height / 3) * 2, - canvas.width / 3 - ); - ctx.restore(); - return; - } + canvas.width / 3, + (canvas.height / 3) * 2, + canvas.width / 3 + ); + ctx.restore(); + return; + } - if (game.showRestartModal) { - ctx.save(); - ctx.fillStyle = "yellow"; - ctx.fillRect( - canvas.width / 4, - canvas.height / 4, - enemy.width / 2, - canvas.height / 2 - ); - ctx.fillStyle = "black"; - ctx.font = "48px sans"; - ctx.fillText( - `Restart? (Y / N)`, - canvas.width / 3, - canvas.height / 3, - canvas.width / 3 - ); - ctx.restore(); - return; - } + if (game.showRestartModal) { + ctx.save(); + ctx.fillStyle = "yellow"; + ctx.fillRect( + canvas.width / 4, + canvas.height / 4, + enemy.width / 2, + canvas.height / 2 + ); + ctx.fillStyle = "black"; + ctx.font = "48px sans"; + ctx.fillText( + `Restart? (Y / N)`, + canvas.width / 3, + canvas.height / 3, + canvas.width / 3 + ); + ctx.restore(); + return; + } - if (game.isPaused) { - ctx.save(); - ctx.fillStyle = "yellow"; - ctx.fillRect( - canvas.width / 4, - canvas.height / 3, - canvas.width / 2, - canvas.height / 3 - ); - ctx.fillStyle = "black"; - ctx.font = "40px sans"; - ctx.fillText(`Paused`, canvas.width / 3 - 5, canvas.height / 2); - ctx.restore(); - return; - } - ctx.clearRect(0, 0, canvas.width, canvas.height); + if (game.isPaused) { + ctx.save(); + ctx.fillStyle = "yellow"; + ctx.fillRect( + canvas.width / 4, + canvas.height / 3, + canvas.width / 2, + canvas.height / 3 + ); + ctx.fillStyle = "black"; + ctx.font = "40px sans"; + ctx.fillText(`Paused`, canvas.width / 3 - 5, canvas.height / 2); + ctx.restore(); + return; + } + ctx.clearRect(0, 0, canvas.width, canvas.height); - // if beastie reaches the middle of the screen, move the gates down - // instead of beastie up to make it look like beastie is going up - if ( - beastie.y < canvas.height - canvas.height / 3 && - beastie.dy < 0 && - (beastie.score <= game.winScore || !game.winScore) - ) { - gates = gates.map(({ y, ...otherProps }) => ({ - ...otherProps, - y: y - beastie.dy, - })); + // if beastie reaches the middle of the screen, move the gates down + // instead of beastie up to make it look like beastie is going up + if ( + beastie.y < canvas.height - canvas.height / 3 && + beastie.dy < 0 && + (beastie.score <= game.winScore || !game.winScore) + ) { + gates = gates.map(({ y, ...otherProps }) => ({ + ...otherProps, + y: y - beastie.dy, + })); - // add more gates to the top of the screen as beastie moves up - while (gates[gates.length - 1].y > 0) { - gates.push({ - x: random(25, canvas.width - 25 - GATE.WIDTH), - y: - gates[gates.length - 1].y - - (GATE.HEIGHT + - random(minPlatformSpace, maxPlatformSpace)), - touched: false, - blocker: - game.hasBlockers && - gates.filter(({ blocker }) => blocker).length / - gates.length < - game.blockerFrequency && - Math.random() <= game.blockerFrequency - ? true - : false, - }); + // add more gates to the top of the screen as beastie moves up + while (gates[gates.length - 1].y > 0) { + gates.push({ + x: random(25, canvas.width - 25 - GATE.WIDTH), + y: + gates[gates.length - 1].y - + (GATE.HEIGHT + + random(minPlatformSpace, maxPlatformSpace)), + touched: false, + blocker: + game.hasBlockers && + gates.filter(({ blocker }) => blocker).length / + gates.length < + game.blockerFrequency && + Math.random() <= game.blockerFrequency + ? true + : false, + }); - // add a bit to the min/max gate space as the player goes up - minPlatformSpace += 0.5; - maxPlatformSpace += 0.5; + // add a bit to the min/max gate space as the player goes up + minPlatformSpace += 0.5; + maxPlatformSpace += 0.5; - // cap max space - maxPlatformSpace = Math.min( - maxPlatformSpace, - canvas.height / 2 - ); - } - // move enemy backwards - enemy.y -= beastie.dy; - } else { - if (beastie.y <= 0) beastie.dy = -10; - beastie.y += beastie.dy; - } + // cap max space + maxPlatformSpace = Math.min( + maxPlatformSpace, + canvas.height / 2 + ); + } + // move enemy backwards + enemy.y -= beastie.dy; + } else { + if (beastie.y <= 0) beastie.dy = -10; + beastie.y += beastie.dy; + } - // apply drag when key not pressed - if (!keydown) { - if (player.dx < 0) { - beastie.dx += drag; + // apply drag when key not pressed + if (!keydown) { + if (player.dx < 0) { + beastie.dx += drag; - // don't let dx go above 0 - if (beastie.dx > 0) { - beastie.dx = 0; - player.dx = 0; - } - } else if (player.dx > 0) { - beastie.dx -= drag; + // don't let dx go above 0 + if (beastie.dx > 0) { + beastie.dx = 0; + player.dx = 0; + } + } else if (player.dx > 0) { + beastie.dx -= drag; - if (beastie.dx < 0) { - beastie.dx = 0; - player.dx = 0; - } - } + if (beastie.dx < 0) { + beastie.dx = 0; + player.dx = 0; + } + } - if ( - player.dy === 0 || - (player.dy < 0 && Date.now() > beastie.boostEnd) - ) { - beastie.dy += drag; + if ( + player.dy === 0 || + (player.dy < 0 && Date.now() > beastie.boostEnd) + ) { + beastie.dy += drag; - // don't let dx go above 0 - if (beastie.dy > game.speed) { - beastie.dy = game.speed; - player.dy = game.speed; - } - } else if (player.dy > 0) { - beastie.dy -= drag; + // don't let dx go above 0 + if (beastie.dy > game.speed) { + beastie.dy = game.speed; + player.dy = game.speed; + } + } else if (player.dy > 0) { + beastie.dy -= drag; - if (beastie.dy < 0) { - beastie.dy = 0; - player.dy = 0; - } - } - } + if (beastie.dy < 0) { + beastie.dy = 0; + player.dy = 0; + } + } + } - beastie.x += beastie.dx; + beastie.x += beastie.dx; - // make beastie wrap the screen - if (beastie.x + beastie.width < 0) { - beastie.x = canvas.width; - } else if (beastie.x > canvas.width) { - beastie.x = -beastie.width; - } + // make beastie wrap the screen + if (beastie.x + beastie.width < 0) { + beastie.x = canvas.width; + } else if (beastie.x > canvas.width) { + beastie.x = -beastie.width; + } - ctx.fillStyle = "green"; - gates = gates.map((gate) => { - // draw gates - ctx.save(); - if (gate.blocker) ctx.fillStyle = "red"; - else ctx.fillStyle = gate.touched ? "lime" : "green"; - ctx.fillRect(gate.x, gate.y, GATE.WIDTH, GATE.HEIGHT); - if (gate.blocker) { - ctx.save(); - ctx.lineWidth = 4; - ctx.strokeStyle = "black"; - ctx.beginPath(); - ctx.moveTo(gate.x, gate.y); - ctx.lineTo(gate.x + GATE.WIDTH, gate.y + GATE.HEIGHT); - ctx.moveTo(gate.x + GATE.WIDTH, gate.y); - ctx.lineTo(gate.x, gate.y + GATE.HEIGHT); - ctx.stroke(); - ctx.restore(); - } + ctx.fillStyle = "green"; + gates = gates.map((gate) => { + // draw gates + ctx.save(); + if (gate.blocker) ctx.fillStyle = "red"; + else ctx.fillStyle = gate.touched ? "lime" : "green"; + ctx.fillRect(gate.x, gate.y, GATE.WIDTH, GATE.HEIGHT); + if (gate.blocker) { + ctx.save(); + ctx.lineWidth = 4; + ctx.strokeStyle = "black"; + ctx.beginPath(); + ctx.moveTo(gate.x, gate.y); + ctx.lineTo(gate.x + GATE.WIDTH, gate.y + GATE.HEIGHT); + ctx.moveTo(gate.x + GATE.WIDTH, gate.y); + ctx.lineTo(gate.x, gate.y + GATE.HEIGHT); + ctx.stroke(); + ctx.restore(); + } - // make beastie stop if it collides with a blocking gate - if (gate.blocker && hasCollision(gate)) { - beastie.dy = game.speed * 0.5; - gate.touched = true; - } + // make beastie stop if it collides with a blocking gate + if (gate.blocker && hasCollision(gate)) { + beastie.dy = game.speed * 0.5; + gate.touched = true; + } - // make beastie jump if it collides with a gate from above - if ( - // beastie is falling - // beastie.dy > 0 && - // beastie was previous above the gate - // prevDoodleY + doodle.height <= gate.y && - // doodle collides with gate + // make beastie jump if it collides with a gate from above + if ( + // beastie is falling + // beastie.dy > 0 && + // beastie was previous above the gate + // prevDoodleY + doodle.height <= gate.y && + // doodle collides with gate - !gate.blocker && - // !gate.touched && - hasCollision(gate) - ) { - // reset beastie position so it's on top of the gate - // beastie.y = gate.y - beastie.height; - if (!gate.touched) { - beastie.dy = Math.max( - game.maxSpeed, - game.speedBoost.multiplier * - Math.min(beastie.dy, game.speed) - ); - beastie.score += 100; - } - beastie.boostEnd = - Date.now() + game.speedBoost.duration * 1000; - gate.touched = true; - } + !gate.blocker && + // !gate.touched && + hasCollision(gate) + ) { + // reset beastie position so it's on top of the gate + // beastie.y = gate.y - beastie.height; + if (!gate.touched) { + beastie.dy = Math.max( + game.maxSpeed, + game.speedBoost.multiplier * + Math.min(beastie.dy, game.speed) + ); + beastie.score += 100; + } + beastie.boostEnd = + Date.now() + game.speedBoost.duration * 1000; + gate.touched = true; + } - return gate; - }); + return gate; + }); - // check on enemy - if (!runtime) runtime = 0; + // check on enemy + if (!runtime) runtime = 0; - if (runtime > enemy.delaySeconds * 1000) - enemy.y = enemy.y + enemy.speed; - if (enemy.y < 1) { - enemy.y = 0; - game.isOver = true; - } else if (enemy.y > beastie.y + game.maxLead) - enemy.y = beastie.y + game.maxLead; + if (runtime > enemy.delaySeconds * 1000) + enemy.y = enemy.y + enemy.speed; + if (enemy.y < 1) { + enemy.y = 0; + game.isOver = true; + } else if (enemy.y > beastie.y + game.maxLead) + enemy.y = beastie.y + game.maxLead; - // draw beastie - ctx.drawImage( - img, - beastie.x, - beastie.y, - beastie.width, - beastie.height - ); + // draw beastie + ctx.drawImage( + img, + beastie.x, + beastie.y, + beastie.width, + beastie.height + ); - // +100 points for each offscreen blocker that wasn't touched - beastie.score += - 100 * - gates.filter( - (gate) => - gate.y > canvas.height && gate.blocker && !gate.touched - ).length; - // remove any offscreen gates - gates = gates.filter((gate) => gate.y <= canvas.height); + // +100 points for each offscreen blocker that wasn't touched + beastie.score += + 100 * + gates.filter( + (gate) => + gate.y > canvas.height && gate.blocker && !gate.touched + ).length; + // remove any offscreen gates + gates = gates.filter((gate) => gate.y <= canvas.height); - beastie.score += Math.floor(Math.abs(beastie.dy)); - ctx.fillStyle = "white"; - ctx.font = "12px sans"; - ctx.fillText(`Score: ${beastie.score}`, 10, 20); + beastie.score += Math.floor(Math.abs(beastie.dy)); + ctx.fillStyle = "white"; + ctx.font = "12px sans"; + ctx.fillText(`Score: ${beastie.score}`, 10, 20); - renderLightning(); - }; + renderLightning(); + }; - // listen to keyboard events to move beastie - document.addEventListener("keydown", (e) => { - if (keydown) return; + // listen to keyboard events to move beastie + document.addEventListener("keydown", (e) => { + if (keydown) return; - if (e.code === "ArrowLeft") { - keydown = true; - player.dx = -1; - beastie.dx = -3; - } else if (e.code === "ArrowRight") { - keydown = true; - player.dx = 1; - beastie.dx = 3; - } else if (e.code === "KeyP") { - keydown = true; - game.isPaused = !game.isPaused; - } else if (e.code === "KeyR") { - keydown = true; - if (game.isOver) { - restartGame(); - return; - } - game.isPaused = true; - game.showRestartModal = true; - } else if (e.code === "KeyS") { - keydown = true; - game.isPaused = true; - document.getElementById("settings").classList.add("showSettings"); - } else if (e.code === "KeyY") { - keydown = true; - if (game.showRestartModal) restartGame(); - } else if (e.code === "KeyN") { - keydown = true; - if (game.showRestartModal) { - game.showRestartModal = false; - game.isPaused = false; - } + if (e.code === "ArrowLeft") { + keydown = true; + player.dx = -1; + beastie.dx = -3; + } else if (e.code === "ArrowRight") { + keydown = true; + player.dx = 1; + beastie.dx = 3; + } else if (e.code === "KeyP") { + keydown = true; + game.isPaused = !game.isPaused; + } else if (e.code === "KeyR") { + keydown = true; + if (game.isOver) { + restartGame(); + return; + } + game.isPaused = true; + game.showRestartModal = true; + } else if (e.code === "KeyS") { + keydown = true; + game.isPaused = true; + document.getElementById("settings").classList.add("showSettings"); + } else if (e.code === "KeyY") { + keydown = true; + if (game.showRestartModal) restartGame(); + } else if (e.code === "KeyN") { + keydown = true; + if (game.showRestartModal) { + game.showRestartModal = false; + game.isPaused = false; + } - /* + /* } else if (e.code === "ArrowDown") { keydown = true; player.dy = 1; @@ -540,117 +549,118 @@ const startGame = (canvas) => { player.dy = -1; beastie.dy = -3; */ - } - }); + } + }); - document.addEventListener("keyup", () => { - keydown = false; - }); + document.addEventListener("keyup", () => { + keydown = false; + }); - canvas.addEventListener( - "mousedown", - (e) => { - keydown = true; - const result = getMousePos(canvas, e); - if (result.x < canvas.width / 2) { - player.dx = -1; - beastie.dx = -3; - } else { - player.dx = 1; - beastie.dx = 3; - } - }, - false - ); + canvas.addEventListener( + "mousedown", + (e) => { + keydown = true; + const result = getMousePos(canvas, e); + if (result.x < canvas.width / 2) { + player.dx = -1; + beastie.dx = -3; + } else { + player.dx = 1; + beastie.dx = 3; + } + }, + false + ); - canvas.addEventListener( - "mousemove", - (e) => { - if (!keydown) return; - const result = getMousePos(canvas, e); - if (result.x < beastie.x) { - player.dx = -1; - beastie.dx = -3; - } else { - player.dx = 1; - beastie.dx = 3; - } - }, - false - ); + canvas.addEventListener( + "mousemove", + (e) => { + if (!keydown) return; + const result = getMousePos(canvas, e); + if (result.x < beastie.x) { + player.dx = -1; + beastie.dx = -3; + } else { + player.dx = 1; + beastie.dx = 3; + } + }, + false + ); - canvas.addEventListener( - "mouseup", - () => { - keydown = false; - }, - false - ); + canvas.addEventListener( + "mouseup", + () => { + keydown = false; + }, + false + ); - canvas.addEventListener( - "touchstart", - (e) => { - mousePos = getTouchPos(canvas, e); - const touch = e.touches[0], - mouseEvent = new MouseEvent("mousedown", { - clientX: touch.clientX, - clientY: touch.clientY, - }); - canvas.dispatchEvent(mouseEvent); - }, - false - ); - canvas.addEventListener( - "touchend", - () => { - var mouseEvent = new MouseEvent("mouseup", {}); - canvas.dispatchEvent(mouseEvent); - }, - false - ); - canvas.addEventListener( - "touchmove", - (e) => { - const touch = e.touches[0]; - keydown = true; - if (touch.clientX < beastie.x) { - player.dx = -1; - beastie.dx = -3; - } else { - player.dx = 1; - beastie.dx = 3; - } - canvas.dispatchEvent(mouseEvent); - }, - false - ); - document.getElementById("cancelSettings").addEventListener("click", (e) => { - // close modal - document.getElementById("settings").classList.remove("showSettings"); - // leave game paused - }); - document.getElementById("applySettings").addEventListener("click", (e) => { - // close modal - document.getElementById("settings").classList.remove("showSettings"); - // restart game - restartGame(); - }); - document - .getElementById("toggleSettings") - ?.addEventListener("click", (e) => { - // pause game - game.isPaused = true; + canvas.addEventListener( + "touchstart", + (e) => { + mousePos = getTouchPos(canvas, e); + const touch = e.touches[0], + mouseEvent = new MouseEvent("mousedown", { + clientX: touch.clientX, + clientY: touch.clientY, + }); + canvas.dispatchEvent(mouseEvent); + }, + false + ); + canvas.addEventListener( + "touchend", + () => { + var mouseEvent = new MouseEvent("mouseup", {}); + canvas.dispatchEvent(mouseEvent); + }, + false + ); + canvas.addEventListener( + "touchmove", + (e) => { + const touch = e.touches[0]; + keydown = true; + if (touch.clientX < beastie.x) { + player.dx = -1; + beastie.dx = -3; + } else { + player.dx = 1; + beastie.dx = 3; + } + canvas.dispatchEvent(mouseEvent); + }, + false + ); + document.getElementById("cancelSettings").addEventListener("click", (e) => { + // close modal + document.getElementById("settings").classList.remove("showSettings"); + // leave game paused + }); + document.getElementById("applySettings").addEventListener("click", (e) => { + // close modal + document.getElementById("settings").classList.remove("showSettings"); + // restart game + restartGame(); + }); + document + .getElementById("toggleSettings") + ?.addEventListener("click", (e) => { + // pause game + game.isPaused = true; - // show settings - document - .getElementById("settings") - .classList.toggle("showSettings"); - }); + // show settings + document + .getElementById("settings") + .classList.toggle("showSettings"); + }); - // start the game - restartGame(); + // start the game + restartGame(); - requestAnimationFrame(loop); + requestAnimationFrame(loop); }; export { startGame }; +// @license-end diff --git a/src/assets/_root/webtoys/bbe/scripts/lightning.js b/src/assets/_root/webtoys/bbe/scripts/lightning.js index 60093a0..864eb73 100644 --- a/src/assets/_root/webtoys/bbe/scripts/lightning.js +++ b/src/assets/_root/webtoys/bbe/scripts/lightning.js @@ -1,45 +1,55 @@ +// @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0 +/**************************************************************************** + * Byte Beasties Escape! + * + * Copyright 2023-2026 Eric Woodward + * Source released under CC0 Public Domain License v1.0 + * https://www.planarvagabond.com/licenses/cc0/ + * http://creativecommons.org/publicdomain/zero/1.0/ + ****************************************************************************/ export const createLightning = (canvas, enemy) => { - const { y: size } = enemy; - var center = { x: canvas.width / 2, y: canvas.height }; - var minSegmentHeight = 5; - var roughness = 2; - var maxDifference = size / 5; + const { y: size } = enemy; + var center = { x: canvas.width / 2, y: canvas.height }; + var minSegmentHeight = 5; + var roughness = 2; + var maxDifference = size / 5; - let lightning = []; - let segmentHeight = size / 3; - lightning.push({ - x: center.x, - y: center.y + 200, - }); - lightning.push({ - x: Math.random() * (canvas.width - 100) + 50, - y: Math.abs((Math.random() - 0.9) * 100), - }); - let currDiff = maxDifference; - while (segmentHeight > minSegmentHeight) { - const newSegments = []; - for (var i = 0; i < lightning.length - 1; i++) { - const start = lightning[i], - end = lightning[i + 1], - midX = (start.x + end.x) / 2, - newX = midX + (Math.random() * 2 - 1) * currDiff; - newSegments.push(start, { x: newX, y: (start.y + end.y) / 2 }); - } + let lightning = []; + let segmentHeight = size / 3; + lightning.push({ + x: center.x, + y: center.y + 200, + }); + lightning.push({ + x: Math.random() * (canvas.width - 100) + 50, + y: Math.abs((Math.random() - 0.9) * 100), + }); + let currDiff = maxDifference; + while (segmentHeight > minSegmentHeight) { + const newSegments = []; + for (var i = 0; i < lightning.length - 1; i++) { + const start = lightning[i], + end = lightning[i + 1], + midX = (start.x + end.x) / 2, + newX = midX + (Math.random() * 2 - 1) * currDiff; + newSegments.push(start, { x: newX, y: (start.y + end.y) / 2 }); + } - newSegments.push(lightning.pop()); - lightning = newSegments; + newSegments.push(lightning.pop()); + lightning = newSegments; - currDiff /= roughness; - segmentHeight /= 2; - } - return lightning; + currDiff /= roughness; + segmentHeight /= 2; + } + return lightning; }; // Get the position of the mouse relative to the canvas export const getMousePos = (canvasDom, mouseEvent) => { - var rect = canvasDom.getBoundingClientRect(); - return { - x: mouseEvent.clientX - rect.left, - y: mouseEvent.clientY - rect.top, - }; + var rect = canvasDom.getBoundingClientRect(); + return { + x: mouseEvent.clientX - rect.left, + y: mouseEvent.clientY - rect.top, + }; }; +// @license-end diff --git a/src/assets/_root/webtoys/bbe/scripts/main.js b/src/assets/_root/webtoys/bbe/scripts/main.js index b4d5469..422679f 100644 --- a/src/assets/_root/webtoys/bbe/scripts/main.js +++ b/src/assets/_root/webtoys/bbe/scripts/main.js @@ -1,8 +1,8 @@ // @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0 /**************************************************************************** - * Planar Vagabond's Guide to the Multiverse (planarvagabond.com) + * Byte Beasties Escape! * - * Copyright 2023-2024 Eric Woodward + * Copyright 2023-2026 Eric Woodward * Source released under CC0 Public Domain License v1.0 * https://www.planarvagabond.com/licenses/cc0/ * http://creativecommons.org/publicdomain/zero/1.0/ @@ -11,22 +11,22 @@ import { startGame } from "./bbe.js"; export default (() => { - // we load this library as a module to guarantee baseline ES6 functionality + // we load this library as a module to guarantee baseline ES6 functionality - // Indicate JS is loaded - document.documentElement.className = - document.documentElement.className.replace("no-js", "js"); + // Indicate JS is loaded + document.documentElement.className = + document.documentElement.className.replace("no-js", "js"); - setTimeout(() => { - const canvas = document.createElement("canvas"), - contentDiv = document.getElementById("game"); + setTimeout(() => { + const canvas = document.createElement("canvas"), + contentDiv = document.getElementById("game"); - canvas.setAttribute("width", "375"); - canvas.setAttribute("height", "667"); - canvas.setAttribute("id", "game"); - contentDiv.appendChild(canvas); + canvas.setAttribute("width", "375"); + canvas.setAttribute("height", "667"); + canvas.setAttribute("id", "game"); + contentDiv.appendChild(canvas); - startGame(canvas); - }, 1); + startGame(canvas); + }, 1); })(); // @license-end diff --git a/src/assets/_root/webtoys/bps/index.html b/src/assets/_root/webtoys/bps/index.html index 3b3051f..e525f8c 100644 --- a/src/assets/_root/webtoys/bps/index.html +++ b/src/assets/_root/webtoys/bps/index.html @@ -1,150 +1,168 @@ - -
- - - -- Download ZIP - Download TAR -
++ Download ZIP + Download TAR +
-- This is the BPS (Bill Paxton Soundboard), my entry into the UI Developers Guild Coding Challenge for - February 2024 at the company I work for. -
++ This is the BPS (Bill Paxton Soundboard), my entry into the UI + Developers Guild Coding Challenge for February 2024 at the company I + work for. +
-- If you want to know a bit more about how (and why) it was made, be sure to check - out the blog post I wrote about it when it was released - on 2024-02-10. -
++ If you want to know a bit more about how (and why) it was made, be + sure to check out + the blog post I wrote about it + when it was released on 2024-02-10. +
-- This particular challenge had 3 simple rules (copied verbatim below): -
++ This particular challenge had 3 simple rules (copied verbatim + below): +
-
- The BPS satisfies all 3 of the rules: it's an HTML5 / CSS / JS application that creates
- a series of virtual audio tags, and then loads a WAV or MP3 into each one before
- inserting them into the document. It then renders a series of clickable image-buttons
- (each one being an img tag, surrounded by a figure tag, and
- augmented by the text of a figcaption tag) and attaches a play()
- function to the click handlers for those figures. Plus, I wrote it all in under 4 hours:
-
+ The BPS satisfies all 3 of the rules: it's an HTML5 / CSS / JS
+ application that creates a series of virtual
+ audio tags, and then loads a WAV or MP3 into each one
+ before inserting them into the document. It then renders a series of
+ clickable image-buttons (each one being an img tag,
+ surrounded by a figure tag, and augmented by the text
+ of a figcaption tag) and attaches a
+ play() function to the click handlers for those
+ figures. Plus, I wrote it all in under 4 hours:
+
player.js and
- main.js modules;
- unzip bps.ziptar -xvzf bps.tar.gzplayer.js and
+ main.js modules;
+ cd bps
- npx http-server
- unzip bps.ziptar -xvzf bps.tar.gzcd bpsnpx http-server- The images and sound clips used for the Bill Paxton Soundboard are copyright their - respective owners, and used under the - fair use provision - of the Constitution of the United - States of America. -
+- All other content on this page (including scripts and text content) is released under a - Creative Commons CC0 1.0 Universal - license. -
+Share and enjoy!
++ The images and sound clips used for the Bill Paxton Soundboard are + copyright their respective owners, and used under the + fair use provision + of the + Constitution of the United States of America. +
- ++ All other content on this page (including scripts and text content) + is released under a + Creative Commons CC0 1.0 Universal + license. +
- - \ No newline at end of file +Share and enjoy!
+ + + +