gigatron/rom/Contrib/lb3361/runjs/html/gamepad.js
2025-01-28 19:17:01 +03:00

156 lines
4.7 KiB
JavaScript

export const BUTTON_A = 0x80;
export const BUTTON_B = 0x40;
export const BUTTON_SELECT = 0x20;
export const BUTTON_START = 0x10;
export const BUTTON_UP = 0x08;
export const BUTTON_DOWN = 0x04;
export const BUTTON_LEFT = 0x02;
export const BUTTON_RIGHT = 0x01;
/** map from controller button name to inReg bit */
const buttonMap = {
a: BUTTON_A,
b: BUTTON_B,
select: BUTTON_SELECT,
start: BUTTON_START,
up: BUTTON_UP,
down: BUTTON_DOWN,
left: BUTTON_LEFT,
right: BUTTON_RIGHT,
};
const axisThreshold = 0.5;
/** map from standard gamepad button index to inReg bit */
const gamepadButtonMap = {
0: buttonMap.a,
1: buttonMap.b,
6: buttonMap.b,
7: buttonMap.a,
8: buttonMap.select,
9: buttonMap.start,
12: buttonMap.up,
13: buttonMap.down,
14: buttonMap.left,
15: buttonMap.right,
};
/** map from standard gamepad axis index to negative and positive inReg bits */
const gamepadAxisMap = {
0: [buttonMap.left, buttonMap.right],
1: [buttonMap.up, buttonMap.down],
};
/** Gamepad device */
export class Gamepad {
/** Create a Gamepad
* @param {Gigatron} cpu - The cpu to control
* @param {Object.<string,string[]>} keys - Map from controller button
* name to list of keys
*/
constructor(cpu, keys) {
this.cpu = cpu;
this.enabled = false;
this.pressed = 0;
this.keyMap = {};
/* build map from keyboard key to controller button name */
for (let button of Object.keys(keys)) {
for (let key of keys[button]) {
this.keyMap[key] = buttonMap[button];
}
}
/* build map of ASCII codes that the Gigatron understands as well */
this.asciiMap = {
'Tab': 9,
'Enter': 10,
'Escape': 27,
'Esc': 27,
'Delete': 127,
'Backspace': 127,
};
for (let ascii=32; ascii<127; ascii++) {
this.asciiMap[String.fromCharCode(ascii)] = ascii;
}
for (let fnKey=1; fnKey<=12; fnKey++) {
this.asciiMap['F' + fnKey] = 0xc0 + fnKey;
}
}
/** start handling key events */
start() {
$(document)
.on('keydown', (event) => {
let bit = this.keyMap[event.key];
if (bit) {
this.pressed |= bit;
event.preventDefault();
} else {
let ascii = this.asciiMap[event.key];
if (ascii) {
if (event.ctrlKey) {
// Control codes (e.g. Ctrl-C for ETX or BREAK)
if (ascii == 63 /*'?'*/) ascii = 127;
else if (ascii == 32 /*' '*/) ascii = 0;
else ascii &= 31;
}
this.pressed = ascii ^ 0xff; /// will be inverted again in tick()
event.preventDefault();
}
}
})
.on('keyup', (event) => {
let bit = this.keyMap[event.key];
if (bit) {
this.pressed &= ~bit;
} else {
this.pressed = 0;
}
event.preventDefault();
});
this.enabled = true;
}
/** stop handling key events */
stop() {
$(document).off('keydown keyup');
this.enabled = false;
}
/** check gamepads */
tick() {
let pressed = this.pressed;
if (navigator.getGamepads) {
let gamepads = navigator.getGamepads();
for (let gamepad of gamepads) {
if (gamepad) {
// check the axes
for (let axisIndex of Object.keys(gamepadAxisMap)) {
let axis = gamepad.axes[axisIndex];
let bits = gamepadAxisMap[axisIndex];
if (axis < -axisThreshold) {
pressed |= bits[0];
} else if (axis > axisThreshold) {
pressed |= bits[1];
}
}
// check the buttons
for (let buttonIndex of Object.keys(gamepadButtonMap)) {
let bit = gamepadButtonMap[buttonIndex];
if (gamepad.buttons[buttonIndex].pressed) {
pressed |= bit;
}
}
}
}
}
if (this.enabled) {
this.cpu.inReg = pressed ^ 0xff; // active low
}
}
}