SOURCE / PINNED RELEASE
Made of little things.
Chase'n Those Narly Waves
- Release
- 0c7dde9d4921…
- Author-recorded commit
- 88e58516be17…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ny7l44q0au5xkv3mjgt6zks5f0e2ydefslq4nhh2ve2xypgc2mqqat8qx2/wss%3A%2F%2Fgit.napplet.soy%2F/n-836af5a08b9
Archive hash verified: e1acfdbe67c54dd4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Keyboard and touch input for the game.
// W A S D are "held" keys (steer and paddle). Arrow keys are single presses
// that get collected and turned into surf tricks by the game.
export type Arrow = 'up' | 'down' | 'left' | 'right';
export type MoveKey = 'w' | 'a' | 's' | 'd';
const MOVE_KEYS: Record<string, MoveKey> = {
KeyW: 'w',
KeyA: 'a',
KeyS: 's',
KeyD: 'd',
};
const ARROW_KEYS: Record<string, Arrow> = {
ArrowUp: 'up',
ArrowDown: 'down',
ArrowLeft: 'left',
ArrowRight: 'right',
};
export class Input {
readonly held = new Set<MoveKey>();
private arrows: Arrow[] = [];
/** Keys that were just pressed down (not held from before). */
private tapped = new Set<MoveKey>();
press(key: MoveKey): void {
if (!this.held.has(key)) this.tapped.add(key);
this.held.add(key);
}
/** Was this key tapped since last frame? (Holding it down only counts once.) */
takeTap(key: MoveKey): boolean {
const tapped = this.tapped.has(key);
this.tapped.delete(key);
return tapped;
}
release(key: MoveKey): void {
this.held.delete(key);
}
pressArrow(arrow: Arrow): void {
this.arrows.push(arrow);
}
/** Hand the arrow presses since last frame to the game, then forget them. */
takeArrows(): Arrow[] {
const taken = this.arrows;
this.arrows = [];
return taken;
}
/** Let go of everything (used when the game loses focus or pauses). */
clear(): void {
this.held.clear();
this.tapped.clear();
this.arrows = [];
}
/** -1 = steer left, 1 = steer right, 0 = straight. */
get steer(): number {
return (this.held.has('d') ? 1 : 0) - (this.held.has('a') ? 1 : 0);
}
get paddling(): boolean {
return this.held.has('w');
}
get braking(): boolean {
return this.held.has('s');
}
}
/**
* Listen for game keys while the napplet has keyboard focus.
* `isPlaying` stops game keys from doing anything on menus.
*/
export function attachKeyboard(input: Input, isPlaying: () => boolean): void {
window.addEventListener('keydown', (event) => {
const move = MOVE_KEYS[event.code];
const arrow = ARROW_KEYS[event.code];
if (!move && !arrow) return;
if (!isPlaying()) return;
event.preventDefault(); // stop arrows from scrolling the page
if (move) input.press(move);
if (arrow && !event.repeat) input.pressArrow(arrow);
});
window.addEventListener('keyup', (event) => {
const move = MOVE_KEYS[event.code];
if (move) input.release(move);
});
window.addEventListener('blur', () => input.clear());
}
/** On-screen buttons for phones and tablets. */
export function attachTouchButtons(root: HTMLElement, input: Input): void {
for (const button of root.querySelectorAll<HTMLButtonElement>('[data-hold]')) {
const key = button.dataset.hold as MoveKey;
const up = (): void => input.release(key);
button.addEventListener('pointerdown', (event) => {
event.preventDefault();
button.setPointerCapture(event.pointerId);
input.press(key);
});
button.addEventListener('pointerup', up);
button.addEventListener('pointercancel', up);
button.addEventListener('lostpointercapture', up);
}
for (const button of root.querySelectorAll<HTMLButtonElement>('[data-arrow]')) {
const arrow = button.dataset.arrow as Arrow;
button.addEventListener('pointerdown', (event) => {
event.preventDefault();
input.pressArrow(arrow);
});
}
}
