From 3cd1b669651f77946053193abcf7db0efd773179 Mon Sep 17 00:00:00 2001 From: Colin McMillen Date: Tue, 24 Sep 2019 14:57:24 -0400 Subject: [PATCH] add main.js & main.html --- main.html | 33 +++++++ main.js | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 main.html create mode 100644 main.js diff --git a/main.html b/main.html new file mode 100644 index 0000000..e124205 --- /dev/null +++ b/main.html @@ -0,0 +1,33 @@ + + + + + snej + + + + + +
+ + + + + + + + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..d982b64 --- /dev/null +++ b/main.js @@ -0,0 +1,254 @@ +class Input { + constructor() { + this.left = false; + this.right = false; + this.up = false; + this.down = false; + this.a = false; + this.b = false; + this.x = false; + this.y = false; + this.l = false; + this.r = false; + this.select = false; + this.start = false; + + window.addEventListener('gamepadconnected', this.gamepadConnected); + window.addEventListener('gamepaddisconnected', this.gamepadDisconnected); + } + + update() { + // TODO: have a config screen instead of hard-coding the 8Bitdo SNES30 pad. + // TODO: handle connects / disconnects more correctly. + const gamepad = navigator.getGamepads()[0]; + if (gamepad == null || !gamepad.connected || gamepad.axes.length < 2 || + gamepad.buttons.length < 12) { + return; + } + + this.left = gamepad.axes[0] < 0; + this.right = gamepad.axes[0] > 0; + this.up = gamepad.axes[1] < 0; + this.down = gamepad.axes[1] > 0; + this.a = gamepad.buttons[0].pressed; + this.b = gamepad.buttons[1].pressed; + this.x = gamepad.buttons[3].pressed; + this.y = gamepad.buttons[4].pressed; + this.l = gamepad.buttons[6].pressed; + this.r = gamepad.buttons[7].pressed; + this.select = gamepad.buttons[10].pressed; + this.start = gamepad.buttons[11].pressed; + debug(this.toString()); + } + + gamepadConnected(e) { + debug('gamepad connected! :)'); + console.log('gamepad connected @ index %d: %d buttons, %d axes\n[%s]', + e.gamepad.index, e.gamepad.buttons.length, e.gamepad.axes.length, + e.gamepad.id); + } + + gamepadDisconnected(e) { + debug('gamepad disconnected :('); + console.log('gamepad disconnected @ index %d:\n[%s]', e.gamepad.index, + e.gamepad.id); + } + + toString() { + let result = ''; + + if (this.up) { + result += '^'; + } else if (this.down) { + result += 'v'; + } else { + result += '-'; + } + + if (this.left) { + result += '<'; + } else if (this.right) { + result += '>'; + } else { + result += '-'; + } + + result += ' '; + + if (this.a) { + result += 'A'; + } + if (this.b) { + result += 'B'; + } + if (this.x) { + result += 'X'; + } + if (this.y) { + result += 'Y'; + } + if (this.l) { + result += 'L'; + } + if (this.r) { + result += 'R'; + } + if (this.select) { + result += 's'; + } + if (this.start) { + result += 'S'; + } + return result; + } +} + +const input = new Input(); + +class Graphics { + constructor(canvas) { + this.canvas_ = canvas; + this.ctx_ = canvas.getContext('2d'); + } + + get width() { + return this.canvas_.width; + } + + get height() { + return this.canvas_.height; + } + + fill(color) { + this.ctx_.fillStyle = color; + this.ctx_.beginPath(); + this.ctx_.rect(0, 0, this.canvas_.width, this.canvas_.height); + this.ctx_.fill(); + this.ctx_.closePath(); + } + + circle(x, y, radius, color) { + this.ctx_.fillStyle = color; + this.ctx_.beginPath(); + this.ctx_.arc(x, y, radius, 0, 2 * Math.PI) + this.ctx_.fill(); + this.ctx_.closePath(); + } + + // TODO: replace with custom sprite-based text rendering. + text(string, x, y, size, color) { + this.ctx_.imageSmoothingEnabled = false; + this.ctx_.fillStyle = color; + this.ctx_.font = '' + size + 'px monospace'; + this.ctx_.fillText(string, x, y); + } +} + +class FpsCounter { + constructor() { + this.fps = 0; + this.frameTimes_ = new Array(60); + this.idx_ = 0; + } + + update(timestampMs) { + if (this.frameTimes_[this.idx_]) { + const timeElapsed = (timestampMs - this.frameTimes_[this.idx_]) / 1000; + this.fps = this.frameTimes_.length / timeElapsed; + } + this.frameTimes_[this.idx_] = timestampMs; + this.idx_++; + if (this.idx_ == this.frameTimes_.length) { + this.idx_ = 0; + } + } + + draw(gfx) { + gfx.text('FPS: ' + Math.round(this.fps), 8, 16, 16, 'yellow'); + } +} + +class World { + constructor() { + this.state_ = null; + this.fpsCounter_ = new FpsCounter(); + this.input_ = new Input(); + this.gamepadRenderer_ = new GamepadRenderer(); + } + + update(timestampMs) { + this.fpsCounter_.update(timestampMs); + this.input_.update(); + } + + draw(gfx) { + this.gamepadRenderer_.draw(gfx, this.input_); + this.fpsCounter_.draw(gfx); + } +} + +class GamepadRenderer { + draw(gfx, input) { + const centerX = gfx.width / 2; + const centerY = gfx.height / 2; + + gfx.fill('black'); + + // Select & Start + gfx.circle(centerX + 12, centerY, 8, input.start ? 'cyan' : 'grey'); + gfx.circle(centerX - 12, centerY, 8, input.select ? 'cyan' : 'grey'); + + // Y X B A + gfx.circle(centerX + 48, centerY, 8, input.y ? 'cyan' : 'grey'); + gfx.circle(centerX + 64, centerY - 16, 8, input.x ? 'cyan' : 'grey'); + gfx.circle(centerX + 64, centerY + 16, 8, input.b ? 'cyan' : 'grey'); + gfx.circle(centerX + 80, centerY, 8, input.a ? 'cyan' : 'grey'); + + // dpad + gfx.circle(centerX - 48, centerY, 8, input.right ? 'cyan' : 'grey'); + gfx.circle(centerX - 64, centerY - 16, 8, input.up ? 'cyan' : 'grey'); + gfx.circle(centerX - 64, centerY + 16, 8, input.down ? 'cyan' : 'grey'); + gfx.circle(centerX - 80, centerY, 8, input.left ? 'cyan' : 'grey'); + + // L & R + gfx.circle(centerX + 30, centerY - 32, 8, input.r ? 'cyan' : 'grey'); + gfx.circle(centerX - 30, centerY - 32, 8, input.l ? 'cyan' : 'grey'); + } +} + +function debug(message) { + const debugDiv = document.getElementById('debug'); + debugDiv.innerText = message; +} + +function loop(world, gfx) { + return timestampMs => { + world.update(timestampMs); + world.draw(gfx); + window.requestAnimationFrame(loop(world, gfx)); + }; +} + +function setCanvasScale(scale) { + const snesWidth = 256; + const snesHeight = 224; + const canvas = document.getElementById('canvas'); + canvas.style.width = '' + snesWidth * scale + 'px'; + canvas.style.height = '' + snesHeight * scale + 'px'; +} + +function init() { + const world = new World(); + const gfx = new Graphics(document.getElementById('canvas')); + + document.getElementById('1x').onclick = () => setCanvasScale(1); + document.getElementById('2x').onclick = () => setCanvasScale(2); + document.getElementById('3x').onclick = () => setCanvasScale(3); + document.getElementById('4x').onclick = () => setCanvasScale(4); + document.getElementById('5x').onclick = () => setCanvasScale(5); + setCanvasScale(4); + window.requestAnimationFrame(loop(world, gfx)); + debug('initialized!'); +} + +init();