UNPKG

peekpoke

Version:

Tiny minimal retro fantasy console having only two commands: peek and poke.

1,551 lines (1,455 loc) 33.6 kB
(function (exports) { 'use strict'; let codeState; let pressingCode = {}; let pressedCode = {}; let releasedCode = {}; let codeStateEntries; function init$4() { codeState = fromEntities(codes.map((c) => [ c, { isPressed: false, isJustPressed: false, isJustReleased: false, }, ])); codeStateEntries = entries(codeState); document.addEventListener("keydown", (e) => { pressingCode[e.code] = pressedCode[e.code] = true; if (e.code === "AltLeft" || e.code === "AltRight" || e.code === "ArrowRight" || e.code === "ArrowDown" || e.code === "ArrowLeft" || e.code === "ArrowUp") { e.preventDefault(); } }); document.addEventListener("keyup", (e) => { pressingCode[e.code] = false; releasedCode[e.code] = true; }); } function update$2() { codeStateEntries.forEach(([c, s]) => { s.isJustPressed = !s.isPressed && pressedCode[c]; s.isJustReleased = s.isPressed && releasedCode[c]; s.isPressed = !!pressingCode[c]; }); pressedCode = {}; releasedCode = {}; } function fromEntities(v) { return [...v].reduce((obj, [key, value]) => { obj[key] = value; return obj; }, {}); } function entries(obj) { return Object.keys(obj).map((p) => [p, obj[p]]); } const codes = [ "ArrowRight", "KeyD", "ArrowDown", "KeyS", "ArrowLeft", "KeyA", "ArrowUp", "KeyW", "KeyX", "Slash", "Space", "KeyZ", "Period", "KeyM", ]; let buttons; let pressingButtons; let pressedButtons; let releasedButtons; let positions; let screen; let pixelSize; function init$3(_screen, _pixelSize, buttonPositions) { buttons = buttonPositions.map((b) => ({ x: b.x, y: b.y, size: b.size, touchSize: b.size * 2, isPressed: false, isJustPressed: false, isJustReleased: false, isShowingPressed: false, })); pressingButtons = []; pressedButtons = []; releasedButtons = []; for (let i = 0; i < buttons.length; i++) { pressingButtons.push(false); pressedButtons.push(false); releasedButtons.push(false); } positions = {}; screen = _screen; pixelSize = { x: _pixelSize.x, y: _pixelSize.y }; document.addEventListener("mousedown", (e) => { onDown(e.pageX, e.pageY, "_mouse"); }); document.addEventListener("touchstart", (e) => { const touches = e.changedTouches; for (let i = 0; i < touches.length; i++) { const t = touches[i]; onDown(t.pageX, t.pageY, t.identifier); } }); document.addEventListener("mousemove", (e) => { onMove(e.pageX, e.pageY, "_mouse"); }); document.addEventListener("touchmove", (e) => { e.preventDefault(); const touches = e.changedTouches; for (let i = 0; i < touches.length; i++) { const t = touches[i]; onMove(t.pageX, t.pageY, t.identifier); } }, { passive: false }); document.addEventListener("mouseup", (e) => { onUp("_mouse"); }); document.addEventListener("touchend", (e) => { e.preventDefault(); const touches = e.changedTouches; for (let i = 0; i < touches.length; i++) { const t = touches[i]; onUp(t.identifier); } }, { passive: false }); } function update$1() { buttons.forEach((b, i) => { b.isJustPressed = pressedButtons[i]; b.isJustReleased = releasedButtons[i]; b.isPressed = pressingButtons[i]; pressedButtons[i] = releasedButtons[i] = false; }); } function onDown(x, y, id) { if (!positions.hasOwnProperty(id)) { const pressingButtonIds = []; for (let i = 0; i < buttons.length; i++) { pressingButtonIds.push(false); } positions[id] = { pressingButtonIds, isPressed: true }; } else { positions[id].isPressed = true; } updateButtonState(x, y, "down", positions[id].pressingButtonIds); } function onMove(x, y, id) { if (!positions.hasOwnProperty(id) || !positions[id].isPressed) { return; } positions[id]; updateButtonState(x, y, "move", positions[id].pressingButtonIds); } function onUp(id) { if (!positions.hasOwnProperty(id) || !positions[id].isPressed) { return; } const p = positions[id]; for (let i = 0; i < buttons.length; i++) { if (p.pressingButtonIds[i]) { pressingButtons[i] = false; releasedButtons[i] = true; p.pressingButtonIds[i] = false; } } p.isPressed = false; } function updateButtonState(x, y, type, pressingButtonIds) { const pp = calcPointerPos(x, y); buttons.forEach((b, i) => { if (Math.abs(pp.x - b.x + 0.5) < b.touchSize / 2 && Math.abs(pp.y - b.y + 0.5) < b.touchSize / 2) { if (type === "down" || (type === "move" && !pressingButtonIds[i])) { pressingButtons[i] = pressedButtons[i] = true; pressingButtonIds[i] = true; } } else if (type === "move" && pressingButtonIds[i]) { pressingButtons[i] = false; releasedButtons[i] = true; pressingButtonIds[i] = false; } }); } let pointerPos = { x: 0, y: 0 }; function calcPointerPos(x, y) { if (screen == null) { return; } pointerPos.x = Math.round(((x - screen.offsetLeft) / screen.clientWidth + 0.5) * pixelSize.x); pointerPos.y = Math.round(((y - screen.offsetTop) / screen.clientHeight + 0.5) * pixelSize.y); return pointerPos; } let audioContext; let gain; let buzzerBuffers; let beepNode; let currentFrequency; function init$2() { // @ts-ignore audioContext = new (window.AudioContext || window.webkitAudioContext)(); window.addEventListener("mousedown", resumeAudio); window.addEventListener("touchstart", resumeAudio); window.addEventListener("keydown", resumeAudio); gain = audioContext.createGain(); gain.gain.value = 0.05; gain.connect(audioContext.destination); buzzerBuffers = {}; currentFrequency = 0; document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") { beepOff(); } }); } function beepOn(frequency) { if (frequency === currentFrequency) { return; } if (currentFrequency > 0) { beepNode.stop(); } let buffer; if (buzzerBuffers[frequency] == null) { buzzerBuffers[frequency] = createBuzzerBufferData(frequency); } buffer = buzzerBuffers[frequency]; beepNode = new AudioBufferSourceNode(audioContext, { buffer, loop: true, }); beepNode.start(); beepNode.stop(getAudioTime() + 3); beepNode.connect(gain); currentFrequency = frequency; } function beepOff() { if (currentFrequency === 0) { return; } beepNode.stop(); currentFrequency = 0; } function getAudioTime() { return audioContext.currentTime; } function resumeAudio() { audioContext.resume(); } function createBuzzerBufferData(frequency) { const buffer = new AudioBuffer({ numberOfChannels: 1, length: audioContext.sampleRate, sampleRate: audioContext.sampleRate, }); const buzzerBufferData = buffer.getChannelData(0); for (let i = 0; i < audioContext.sampleRate; i++) { const vl = 0.5 + (Math.floor(i / (audioContext.sampleRate / 5000)) % 2 === 0 ? 0.2 : -0.2); let v = Math.floor(i / (audioContext.sampleRate / frequency)) % 2 === 0 ? vl : -vl; buzzerBufferData[i] = v; } return buffer; } const size$1 = { x: 32, y: 30 }; const pixels = []; const textPixels = []; const prevPixels = []; let canvasContext$1; let colorStyles$1; let screenCanvasX; let screenCanvasY; function init$1(_canvasContext, _colorStyles, _screenCanvasX, _screenCanvasY) { canvasContext$1 = _canvasContext; colorStyles$1 = _colorStyles; screenCanvasX = _screenCanvasX; screenCanvasY = _screenCanvasY; for (let x = 0; x < size$1.x; x++) { const l = []; const tl = []; const pl = []; for (let y = 0; y < size$1.y; y++) { l.push(0); tl.push(0); pl.push(0); } pixels.push(l); textPixels.push(tl); prevPixels.push(pl); } } function draw() { for (let x = 0; x < size$1.x; x++) { for (let y = 0; y < size$1.y; y++) { const p = textPixels[x][y] === 0 ? pixels[x][y] : textPixels[x][y]; if (prevPixels[x][y] !== p) { canvasContext$1.fillStyle = colorStyles$1[p % COLOR_COUNT]; canvasContext$1.fillRect(x + screenCanvasX, y + screenCanvasY, 1, 1); prevPixels[x][y] = p; } } } } const textPatternStrings = [ // ! ` l l l l `, ` l l l l `, ` l l lll l l lll l l `, ` ll ll lll ll ll `, ` l l l l l l l `, ` ll ll lll l lll `, ` l l `, ` l l l l l `, ` l l l l l `, ` l lll l lll l `, ` l l lll l l `, ` l l `, ` lll `, ` l `, ` l l l l l `, // 0 ` lll l l l l l l lll `, ` l l l l 1 `, ` lll l lll l lll `, ` lll l lll l lll `, ` l l l l lll l l `, ` lll l lll l lll `, ` l l lll l l lll `, ` lll l l l l `, ` lll l l lll l l lll `, ` lll l l lll l l `, // : ` l l `, ` l l l `, ` l l l l l `, ` lll lll `, ` l l l l l `, ` lll l ll l `, ` lll l l l ll `, // A ` lll l l lll l l l l `, ` ll l l lll l l ll `, ` lll l l l lll `, ` ll l l l l l l ll `, ` lll l lll l lll `, ` lll l lll l l `, ` lll l l l l l ll `, ` l l l l lll l l l l `, ` l l l l l `, ` l l l l ll `, ` l l l l ll l l l l `, ` l l l l lll `, ` l l lll l l l l l l `, ` l l lll lll lll l l `, ` lll l l l l l l lll `, ` lll l l lll l l `, ` lll l l l l lll lll `, ` ll l l ll l l l l `, ` lll l lll l lll `, ` lll l l l l `, ` l l l l l l l l lll `, ` l l l l l l l l l `, ` l l l l lll lll l l `, ` l l l l l l l l l `, ` l l l l lll l l `, ` lll l l l lll `, ` ll l l l ll `, ` l l l l l `, ` ll l l l ll `, ` l l l `, ` lll `, ` l l `, // a ` ll l l ll `, ` l lll l l lll `, ` lll l lll `, ` l lll l l lll `, ` lll l ll `, ` ll l lll l `, ` lll lll l ll `, ` l l lll l l `, ` l l l `, ` l l ll `, ` l l l ll l l `, ` l l l l `, ` lll lll l l `, ` ll l l l l `, ` lll l l lll `, ` lll lll l `, ` lll lll l `, ` lll l l `, ` ll lll ll `, ` lll l l `, ` l l l l lll `, ` l l l l l `, ` l l lll l l `, ` l l l l l `, ` l l l l `, ` lll l lll `, //{ ` ll l l l ll `, ` l l l l l `, ` ll l l l ll `, ` l lll l `, ]; const iconPatternStrings = [ ` l l l l l l `, ]; const size = { x: 8, y: 5 }; const letterSize = { x: 3, y: 5 }; const grid = []; let pattern; const prevGrid = []; function init(defaultColor) { pattern = setPattern(textPatternStrings); for (let x = 0; x < size.x; x++) { const l = []; const pl = []; for (let y = 0; y < size.y; y++) { l.push({ code: 0, color: defaultColor, background: 0 }); pl.push({ code: 0, color: defaultColor, background: 0 }); } grid.push(l); prevGrid.push(pl); } } function setPattern(patternStrings) { const pattern = []; patternStrings.forEach((tp) => { const p = []; const ls = tp.split("\n"); for (let y = 1; y <= letterSize.y; y++) { const l = ls[y]; const lp = []; for (let x = 0; x < letterSize.x; x++) { lp.push(!(x >= l.length) && l.charAt(x) !== " "); } p.push(lp); } pattern.push(p); }); return pattern; } function update() { for (let x = 0; x < size.x; x++) { for (let y = 0; y < size.y; y++) { const g = grid[x][y]; const pg = prevGrid[x][y]; if (pg.code !== g.code || pg.color !== g.color || pg.background !== g.background) { drawPattern(x, y, g); pg.code = g.code; pg.color = g.color; pg.background = g.background; } } } } function drawPattern(x, y, c) { const ox = 1 + x * (letterSize.x + 1); const oy = 1 + y * (letterSize.y + 1); for (let x = -1; x < letterSize.x; x++) { for (let y = -1; y < letterSize.y; y++) { textPixels[ox + x][oy + y] = c.background; } } if (c.code >= 33 && c.code <= 126) { const p = pattern[c.code - 33]; for (let x = 0; x < letterSize.x; x++) { for (let y = 0; y < letterSize.y; y++) { if (p[y][x]) { textPixels[ox + x][oy + y] = c.color; } } } } } const VIDEO_WIDTH = size$1.x; const VIDEO_HEIGHT = size$1.y; const TEXT_WIDTH = size.x; const TEXT_HEIGHT = size.y; const COLOR_BLACK = 0; const COLOR_BLUE = 1; const COLOR_RED = 2; const COLOR_PURPLE = 3; const COLOR_GREEN = 4; const COLOR_CYAN = 5; const COLOR_YELLOW = 6; const COLOR_WHITE = 7; const COLOR_COUNT$1 = 8; const KEY_RIGHT = 0; const KEY_DOWN = 1; const KEY_LEFT = 2; const KEY_UP = 3; const KEY_X = 4; const KEY_Z = 5; const KEY_MUTE = 6; const KEY_COUNT = 7; const KEY_STATE_IS_PRESSED = 1; const KEY_STATE_IS_JUST_PRESSED = 2; const KEY_STATE_IS_JUST_RELEASED = 4; const BUZZER_COUNT = 1; const MUTE_COUNT = 1; const ADDRESS_VIDEO = 0; const ADDRESS_TEXT = VIDEO_WIDTH * VIDEO_HEIGHT; const ADDRESS_TEXT_COLOR = ADDRESS_TEXT + TEXT_WIDTH * TEXT_HEIGHT; const ADDRESS_TEXT_BACKGROUND = ADDRESS_TEXT_COLOR + TEXT_WIDTH * TEXT_HEIGHT; const ADDRESS_KEY = ADDRESS_TEXT_BACKGROUND + TEXT_WIDTH * TEXT_HEIGHT; const ADDRESS_BUZZER = ADDRESS_KEY + KEY_COUNT; const ADDRESS_MUTE = ADDRESS_BUZZER + BUZZER_COUNT; const ADDRESS_COUNT = ADDRESS_MUTE + MUTE_COUNT; /** * Retrieve the value at the specified address in memory. * * @param {number} address - The address to peek at. * @returns {number} - The value at the specified address. * @throws {string} - If the address is invalid. */ function peek(address) { // Check if the address is within the valid range if (address < 0 || address >= ADDRESS_COUNT || !Number.isInteger(address)) { throw new ErrorWithStackTrace(`Invalid address: peek(${address})`); } // Return the value at the specified address return memory[address]; } /** * Write a value to a memory address. * * @param address - The memory address to write to. * @param value - The value to write. * @throws {string} - If the address or value is invalid. */ function poke(address, value) { // Check if the address is out of bounds if (address < 0 || address >= ADDRESS_COUNT || !Number.isInteger(address)) { throw new ErrorWithStackTrace(`Invalid address: poke(${address}, ${value})`); } if (value < 0 || value >= 256 || !Number.isInteger(value)) { throw new ErrorWithStackTrace(`Invalid value: poke(${address}, ${value})`); } // Write the value to the memory address memory[address] = value; } const win = window; win.enableSplashScreen = false; /** * Initialize PEEKPOKE with the given options. * @param options - The options for configuring the peekpoke functions. */ function initPeekpoke(options) { win.setup = options.setup; win.loop = options.loop; if (options.enableSplashScreen != null) { win.enableSplashScreen = options.enableSplashScreen; } } window.addEventListener("load", onLoad); const memory = []; let iconPattern; let splashScreenTicks = -1; function onLoad() { for (let i = 0; i < ADDRESS_COUNT; i++) { memory.push(0); } for (let i = ADDRESS_TEXT_COLOR; i < ADDRESS_TEXT_COLOR + TEXT_WIDTH * TEXT_HEIGHT; i++) { memory[i] = COLOR_WHITE; } init$4(); init$2(); initColors(); init(COLOR_WHITE); iconPattern = setPattern(iconPatternStrings); initCanvas(); if (window.enableSplashScreen) { splashScreenTicks = 0; } else { setup(); } // Capturing the canvas with gif-capture-canvas /*(window as any).gcc.setOptions({ scale: 3, durationSec: 9, capturingFps: 60, isSmoothingEnabled: false, });*/ requestAnimationFrame(updateFrame); } const targetFps = 68; const deltaTime = 1000 / targetFps; let nextFrameTime = 0; function updateFrame() { const requestId = requestAnimationFrame(updateFrame); const now = window.performance.now(); if (now < nextFrameTime - targetFps / 12) { return; } nextFrameTime += deltaTime; if (nextFrameTime < now || nextFrameTime > now + deltaTime * 2) { nextFrameTime = now + deltaTime; } try { update$2(); update$1(); updateKeyboardMemory(); drawButtons(); if (splashScreenTicks >= 0) { loopSplashScreen(); } else { loop(); } updateVideo(); updateText(); update(); draw(); updateBuzzer(); // Capturing the canvas with gif-capture-canvas //(window as any).gcc.capture(canvas); } catch (e) { cancelAnimationFrame(requestId); console.error(e); } } const title = "PEEKPOKE"; const splashScreenBuzzerSequence = [ [100, 80], [0, 85], [200, 88], [0, 93], [0, 9999], ]; let splashScreenBuzzerSequenceIndex = 0; function loopSplashScreen() { if (splashScreenTicks === splashScreenBuzzerSequence[splashScreenBuzzerSequenceIndex][1]) { memory[ADDRESS_BUZZER] = splashScreenBuzzerSequence[splashScreenBuzzerSequenceIndex][0]; splashScreenBuzzerSequenceIndex++; } if (splashScreenTicks < 80) { const sequence = Math.floor(splashScreenTicks / 40); for (let i = 0; i < 3; i++) { const tx = Math.floor(Math.random() * TEXT_WIDTH); const ty = Math.floor(Math.random() * TEXT_HEIGHT); let v; if (sequence === 0) { const n = Math.floor(Math.random() * 16); v = n < 10 ? "0".charCodeAt(0) + n : "A".charCodeAt(0) + (n - 10); } else { v = ty === 2 ? title.charCodeAt(tx) : 0; } pokeSplashScreenVideo(tx, ty, v, v); } } if (splashScreenTicks === 80) { for (let tx = 0; tx < TEXT_WIDTH; tx++) { for (let ty = 0; ty < TEXT_HEIGHT; ty++) { const v = ty === 2 ? title.charCodeAt(tx) : 0; pokeSplashScreenVideo(tx, ty, v, 0); } } } if (splashScreenTicks === 160 || peek(ADDRESS_KEY + KEY_X) & KEY_STATE_IS_JUST_PRESSED) { splashScreenTicks = -1; for (let i = ADDRESS_VIDEO; i < ADDRESS_TEXT + TEXT_WIDTH * TEXT_HEIGHT; i++) { memory[i] = 0; } memory[ADDRESS_BUZZER] = 0; setup(); return; } splashScreenTicks++; } function pokeSplashScreenVideo(x, y, tv, vv) { memory[ADDRESS_TEXT + x + y * TEXT_WIDTH] = tv; for (let sx = 0; sx < VIDEO_WIDTH / TEXT_WIDTH; sx++) { const vx = x + sx * TEXT_WIDTH; for (let sy = 0; sy < VIDEO_HEIGHT / TEXT_HEIGHT; sy++) { const vy = y + sy * TEXT_HEIGHT; memory[ADDRESS_VIDEO + vx + vy * VIDEO_WIDTH] = vv; } } } const keyCodes = [ ["ArrowRight", "KeyD"], ["ArrowDown", "KeyS"], ["ArrowLeft", "KeyA"], ["ArrowUp", "KeyW"], ["KeyX", "Slash", "Space"], ["KeyZ", "Period"], ["KeyM"], ]; function updateKeyboardMemory() { for (let i = 0; i < KEY_COUNT; i++) { let k = 0; keyCodes[i].forEach((c) => { if (codeState[c].isPressed) { k |= KEY_STATE_IS_PRESSED; } if (codeState[c].isJustPressed) { k |= KEY_STATE_IS_JUST_PRESSED; } if (codeState[c].isJustReleased) { k |= KEY_STATE_IS_JUST_RELEASED; } }); if (buttons[i].isPressed) { k |= KEY_STATE_IS_PRESSED; } if (buttons[i].isJustPressed) { k |= KEY_STATE_IS_JUST_PRESSED; } if (buttons[i].isJustReleased) { k |= KEY_STATE_IS_JUST_RELEASED; } memory[ADDRESS_KEY + i] = k; buttons[i].isShowingPressed = (k & KEY_STATE_IS_PRESSED) > 0; } if ((memory[ADDRESS_KEY + KEY_MUTE] & KEY_STATE_IS_JUST_PRESSED) > 0) { memory[ADDRESS_MUTE] = memory[ADDRESS_MUTE] === 0 ? 1 : 0; drawBuzzerIcon(); } } function updateVideo() { let x = 0; let y = 0; for (let i = ADDRESS_VIDEO; i < ADDRESS_VIDEO + VIDEO_WIDTH * VIDEO_HEIGHT; i++) { pixels[x][y] = memory[i]; x++; if (x >= VIDEO_WIDTH) { x = 0; y++; } } } function updateText() { let x = 0; let y = 0; for (let i = 0; i < TEXT_WIDTH * TEXT_HEIGHT; i++) { const tg = grid[x][y]; tg.code = memory[ADDRESS_TEXT + i]; tg.color = memory[ADDRESS_TEXT_COLOR + i]; tg.background = memory[ADDRESS_TEXT_BACKGROUND + i]; x++; if (x >= TEXT_WIDTH) { x = 0; y++; } } } function updateBuzzer() { if (memory[ADDRESS_BUZZER] > 0 && memory[ADDRESS_MUTE] === 0) { beepOn(memory[ADDRESS_BUZZER] * 10); } else { beepOff(); } } const canvasWidth = 48; const canvasHeight = 80; let canvas; let canvasContext; function initCanvas() { const _bodyBackground = "#111"; const bodyCss = ` -webkit-touch-callout: none; -webkit-tap-highlight-color: ${_bodyBackground}; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background: ${_bodyBackground}; color: #888; `; const canvasCss = ` position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); background: ${colorStyles[COLOR_CYAN]}; `; const crispCss = ` image-rendering: -moz-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: -o-crisp-edges; image-rendering: pixelated; `; document.body.style.cssText = bodyCss; canvas = document.createElement("canvas"); canvas.width = canvasWidth; canvas.height = canvasHeight; canvasContext = canvas.getContext("2d"); canvasContext.imageSmoothingEnabled = false; canvas.style.cssText = canvasCss + crispCss; canvasContext.fillStyle = colorStyles[COLOR_CYAN]; canvasContext.fillRect(0, 0, canvasWidth, canvasHeight); const screenCanvasX = Math.floor((canvasWidth - VIDEO_WIDTH) / 2); const screenCanvasY = Math.floor((canvasHeight / 2 - VIDEO_HEIGHT) / 2); const videoBezelX = Math.floor((canvasWidth - VIDEO_WIDTH) / 4); const videoBezelY = Math.floor((canvasHeight / 2 - VIDEO_HEIGHT) / 5); canvasContext.fillStyle = colorStyles[COLOR_YELLOW]; canvasContext.fillRect(screenCanvasX - videoBezelX, screenCanvasY - videoBezelY, VIDEO_WIDTH + videoBezelX * 2, VIDEO_HEIGHT + videoBezelY * 2); canvasContext.fillStyle = colorStyles[COLOR_BLACK]; canvasContext.fillRect(screenCanvasX, screenCanvasY, VIDEO_WIDTH, VIDEO_HEIGHT); init$1(canvasContext, colorStyles, screenCanvasX, screenCanvasY); initButtons(); canvasContext.fillStyle = colorStyles[COLOR_BLUE]; drawText(pattern["X".charCodeAt(0) - 33], Math.floor(canvasWidth * 0.87), Math.floor(canvasHeight * 0.8)); drawText(pattern["Z".charCodeAt(0) - 33], Math.floor(canvasWidth * 0.65), Math.floor(canvasHeight * 0.9)); drawBuzzerIcon(); const setSize = () => { const cs = 0.95; const wr = innerWidth / innerHeight; const cr = canvasWidth / canvasHeight; const flgWh = wr < cr; const cw = flgWh ? cs * innerWidth : cs * innerHeight * cr; const ch = !flgWh ? cs * innerHeight : (cs * innerWidth) / cr; canvas.style.width = `${cw}px`; canvas.style.height = `${ch}px`; }; window.addEventListener("resize", setSize); setSize(); document.body.appendChild(canvas); } function initButtons() { const size = Math.floor(canvasWidth * 0.15); const arrowButtonX = Math.floor(canvasWidth * 0.3); const arrowButtonY = Math.floor(canvasHeight * 0.7); const buttonPositions = [ { x: arrowButtonX + Math.floor(size * 1.2), y: arrowButtonY, size, }, { x: arrowButtonX, y: arrowButtonY + Math.floor(size * 1.2), size, }, { x: arrowButtonX - Math.floor(size * 1.2), y: arrowButtonY, size, }, { x: arrowButtonX, y: arrowButtonY - Math.floor(size * 1.2), size, }, { x: Math.floor(canvasWidth * 0.9), y: Math.floor(canvasHeight * 0.75), size, }, { x: Math.floor(canvasWidth * 0.7), y: Math.floor(canvasHeight * 0.85), size, }, { x: Math.floor(canvasWidth * 0.75), y: Math.floor(canvasHeight * 0.55), size: Math.floor(size * 0.66), }, ]; init$3(canvas, { x: canvasWidth, y: canvasHeight }, buttonPositions); drawButtons(); } function drawButtons() { buttons.forEach((b) => { canvasContext.fillStyle = colorStyles[b.isShowingPressed ? COLOR_BLUE : COLOR_BLACK]; const x = Math.floor(b.x - b.size / 2); const y = Math.floor(b.y - b.size / 2); canvasContext.fillRect(x, y, b.size, b.size); if (!b.isShowingPressed) { canvasContext.fillStyle = colorStyles[COLOR_WHITE]; canvasContext.fillRect(x + 1, y + 1, 2, 1); canvasContext.fillRect(x + 1, y + 2, 1, 1); } }); } function drawText(pattern, ox, oy) { for (let x = 0; x < letterSize.x; x++) { for (let y = 0; y < letterSize.y; y++) { if (pattern[y][x]) { canvasContext.fillRect(ox + x, oy + y, 1, 1); } } } } function drawBuzzerIcon() { canvasContext.fillStyle = colorStyles[memory[ADDRESS_MUTE] === 0 ? COLOR_RED : COLOR_BLACK]; drawText(iconPattern[0], Math.floor(canvasWidth * 0.85), Math.floor(canvasHeight * 0.53)); } let colorStyles; function initColors() { const rgbNumbers = [ 0x222222, 0x3f51b5, 0xe91e63, 0x9c27b0, 0x4caf50, 0x03a9f4, 0xffc107, 0xeeeeee, ]; colorStyles = []; for (let i = COLOR_BLACK; i <= COLOR_WHITE; i++) { const n = rgbNumbers[i]; const r = (n & 0xff0000) >> 16; const g = (n & 0xff00) >> 8; const b = n & 0xff; colorStyles.push(`rgb(${r},${g},${b})`); } } class ErrorWithStackTrace extends Error { constructor(message) { super(message); this.name = "Error"; const er = Error; if (er.captureStackTrace) { er.captureStackTrace(this, this.constructor); } } } exports.ADDRESS_BUZZER = ADDRESS_BUZZER; exports.ADDRESS_COUNT = ADDRESS_COUNT; exports.ADDRESS_KEY = ADDRESS_KEY; exports.ADDRESS_MUTE = ADDRESS_MUTE; exports.ADDRESS_TEXT = ADDRESS_TEXT; exports.ADDRESS_TEXT_BACKGROUND = ADDRESS_TEXT_BACKGROUND; exports.ADDRESS_TEXT_COLOR = ADDRESS_TEXT_COLOR; exports.ADDRESS_VIDEO = ADDRESS_VIDEO; exports.BUZZER_COUNT = BUZZER_COUNT; exports.COLOR_BLACK = COLOR_BLACK; exports.COLOR_BLUE = COLOR_BLUE; exports.COLOR_COUNT = COLOR_COUNT$1; exports.COLOR_CYAN = COLOR_CYAN; exports.COLOR_GREEN = COLOR_GREEN; exports.COLOR_PURPLE = COLOR_PURPLE; exports.COLOR_RED = COLOR_RED; exports.COLOR_WHITE = COLOR_WHITE; exports.COLOR_YELLOW = COLOR_YELLOW; exports.KEY_COUNT = KEY_COUNT; exports.KEY_DOWN = KEY_DOWN; exports.KEY_LEFT = KEY_LEFT; exports.KEY_MUTE = KEY_MUTE; exports.KEY_RIGHT = KEY_RIGHT; exports.KEY_STATE_IS_JUST_PRESSED = KEY_STATE_IS_JUST_PRESSED; exports.KEY_STATE_IS_JUST_RELEASED = KEY_STATE_IS_JUST_RELEASED; exports.KEY_STATE_IS_PRESSED = KEY_STATE_IS_PRESSED; exports.KEY_UP = KEY_UP; exports.KEY_X = KEY_X; exports.KEY_Z = KEY_Z; exports.MUTE_COUNT = MUTE_COUNT; exports.TEXT_HEIGHT = TEXT_HEIGHT; exports.TEXT_WIDTH = TEXT_WIDTH; exports.VIDEO_HEIGHT = VIDEO_HEIGHT; exports.VIDEO_WIDTH = VIDEO_WIDTH; exports.initPeekpoke = initPeekpoke; exports.peek = peek; exports.poke = poke; })(window || {});