/*
Any Input: "press any key" gates, tick repeat, and "everything is up" release edges.
@description Press-any-key gates, tick repeat, and all-keys-up release edges for keyboard and gamepad, with a log.
@ogScale integer
Part of the BLIT386 demo series.
Prerequisites:
- Keyboard Input: https://demos.blit386.dev/keyboard-input
- Gamepad Input: https://demos.blit386.dev/gamepad-input
Live version: https://demos.blit386.dev/any-input
Guides:
- Input: https://blit386.dev/docs/guides/input#keyboard
- Input: https://blit386.dev/docs/guides/input#frame-timing-semantics
---
WHAT YOU WILL SEE
-----------------
A wide 600x315 screen with a title strip and four equal panels in a row under it:
- "Gate: isAnyKeyPressed" runs a tiny three-screen game (Title, Play, Game over) and
counts presses that arrived while another key was already held.
- "Repeat while held" has a slider for the repeat rate in ticks, a "Press/rep" counter,
and a pip that lights while any key is held.
- "Release: A vs all" compares the A key's own release edge with the whole keyboard's.
- "Pads (press a button)" has one row per gamepad slot, P1 to P4, with press and
"all up" counts.
The four panels share one width and one height. Under them, an event log writes each event
down with the engine tick it happened on, newest at the bottom, so quick edges are easy to
read after the fact; before the first event it shows a dim "Press a key or a gamepad
button" line. The engine overlay is switched off for this demo.
WHAT YOU WILL LEARN
-------------------
Sometimes a game does not care WHICH key you pressed, only that you pressed one: "Press
any key to start" on a title screen is the classic case. BLIT386 has six helpers for that,
three for the keyboard and three for gamepads:
- BT.isAnyKeyDown() / BT.isAnyButtonDown(player): true while at least one is held.
- BT.isAnyKeyPressed(repeatRate) / BT.isAnyButtonPressed(player, repeatRate): true on the
tick something goes down, even when other keys are already held. With a repeat rate it
also fires every N ticks while you keep holding.
- BT.isAnyKeyReleased() / BT.isAnyButtonReleased(player): true only on the tick when
EVERYTHING is up. Letting go of one key while another is still held does not count.
Also in use: BT.isKeyDown(code) and BT.isKeyReleased(code) for one specific key, BT.ticks
for time stamps, ui.slider() for the repeat rate, and isOverlayEnabled: false plus
isCapturingKeyboardScroll: true in configure().
WHY ARE THE GAMEPAD HELPERS READ IN RENDER()?
---------------------------------------------
Keyboard edges clear once per fixed update tick, so they are read in update(). Gamepad
edges are the odd one out: the engine rolls them over once per DRAWN frame instead. On a
display faster than the engine's tick rate some frames run no update at all, and on a
slower one a frame can run two, so reading gamepad edges from update() would miss or
double-count presses. render() runs exactly once per drawn frame, so this demo reads the
gamepad helpers there and each edge is seen exactly once.
TRY THIS
--------
- Press any key on the title screen. Tap keys to play until the game is over after five
hits, then press one more to go back to the title.
- Hold one key, then press a second one. The gate still fires - the "Overlap" counter
counts those presses.
- Hold a key and watch the "Press/rep" counter climb. Drag the slider to change how many
ticks pass between repeats (1 to 30; the engine ticks 60 times a second by default).
- Hold A and S together, then let go of A: "A released" goes up, "All up" does not. Let go
of S and "All up" finally fires.
- Plug in a gamepad (press a button to wake it) and watch its player slot light up. Each
of the four slots is tracked on its own.
*/
import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT, class Vector2iInteger 2D vector for pixel-perfect positioning.
Used for points, sizes, directions, and camera offsets throughout the engine.
The API includes both allocation-free `*To()` / `*InPlace()` variants and
convenience methods that return new vectors.Vector2i } from 'blit386';
import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
// The three screens of the tiny "game" behind the press-any-key gate.
const const STAGE_TITLE: "Title"STAGE_TITLE = 'Title';
const const STAGE_PLAY: "Play"STAGE_PLAY = 'Play';
const const STAGE_GAME_OVER: "Game over"STAGE_GAME_OVER = 'Game over';
// How many key presses it takes to finish the play stage.
const const HITS_TO_FINISH: 5HITS_TO_FINISH = 5;
// Repeat slider limits and starting value, in engine ticks (60 per second by default).
const const REPEAT_MIN: 1REPEAT_MIN = 1;
const const REPEAT_MAX: 30REPEAT_MAX = 30;
const const REPEAT_DEFAULT: 10REPEAT_DEFAULT = 10;
// The one specific key we compare against "any key". KeyboardEvent.code strings name the
// physical key, so this is the A key on any keyboard layout.
const const WATCHED_KEY: "KeyA"WATCHED_KEY = 'KeyA';
// A gamepad slot is a number from 0 to 3, so four players fit.
const const PLAYER_COUNT: 4PLAYER_COUNT = 4;
// How many lines the event log keeps. Older lines scroll away. The four panels above
// never change height, so the log can take all the room below them.
const const LOG_MAX: 16LOG_MAX = 16;
// The screen is 600x315 display pixels. That is exactly half of the 1200x630 social card,
// so the card shows it at a clean 2x scale with no blurry resampling.
const const DISPLAY_W: 600DISPLAY_W = 600;
const const DISPLAY_H: 315DISPLAY_H = 315;
// Fixed layout, in display pixels, on a 4-pixel grid: four equal panels in a row under
// the 22-pixel title strip, the log under them, 4 pixels from the screen sides and 4
// between panels (4 + 4 x 145 + 3 x 4 + 4 = 600).
const const EDGE: 4EDGE = 4;
const const GAP: 4GAP = 4;
const const PANEL_WIDTH: 145PANEL_WIDTH = 145;
const const GATE_PANEL_X: 4GATE_PANEL_X = const EDGE: 4EDGE;
const const REPEAT_PANEL_X: numberREPEAT_PANEL_X = const GATE_PANEL_X: 4GATE_PANEL_X + const PANEL_WIDTH: 145PANEL_WIDTH + const GAP: 4GAP;
const const RELEASE_PANEL_X: numberRELEASE_PANEL_X = const REPEAT_PANEL_X: numberREPEAT_PANEL_X + const PANEL_WIDTH: 145PANEL_WIDTH + const GAP: 4GAP;
const const PADS_PANEL_X: numberPADS_PANEL_X = const RELEASE_PANEL_X: numberRELEASE_PANEL_X + const PANEL_WIDTH: 145PANEL_WIDTH + const GAP: 4GAP;
const const LOG_PANEL_WIDTH: numberLOG_PANEL_WIDTH = const DISPLAY_W: 600DISPLAY_W - 2 * const EDGE: 4EDGE;
// A kit panel with a title adds 22 pixels above its rows and 11 below. Every panel in
// the top row reserves the same 46 pixels of rows - the Repeat panel's slider (a 10-pixel
// label row and a 16-pixel bar row), a 10-pixel counter row, and a 10-pixel pip row - so
// the four end on the same line. The slider fills the panel's content width (the panel
// minus 9 pixels of padding on each side).
const const TOP_ROWS_H: 46TOP_ROWS_H = 46;
const const TOP_PANEL_H: numberTOP_PANEL_H = 22 + const TOP_ROWS_H: 46TOP_ROWS_H + 11;
const const LOG_PANEL_H: numberLOG_PANEL_H = 22 + const LOG_MAX: 16LOG_MAX * 10 + 11;
const const SLIDER_WIDTH: numberSLIDER_WIDTH = const PANEL_WIDTH: 145PANEL_WIDTH - 18;
// The top row and the log, 4 pixels apart, are centered in the space under the title
// strip.
const const BLOCK_H: numberBLOCK_H = const TOP_PANEL_H: numberTOP_PANEL_H + const GAP: 4GAP + const LOG_PANEL_H: numberLOG_PANEL_H;
const const PANEL_TOP_Y: anyPANEL_TOP_Y = 22 + Math.floor((const DISPLAY_H: 315DISPLAY_H - 22 - const BLOCK_H: numberBLOCK_H) / 2);
const const LOG_PANEL_Y: anyLOG_PANEL_Y = const PANEL_TOP_Y: anyPANEL_TOP_Y + const TOP_PANEL_H: numberTOP_PANEL_H + const GAP: 4GAP;
// Shows the six BT.isAny* helpers side by side with their per-key cousins.
class class DemoDemo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Palette slots of the shared UI theme colors, filled by applyTheme() in init().
/** @type {ReturnType<typeof applyTheme> | null} */
Demo.theme: anytheme = null;
// Which screen of the little gate game we are on.
Demo.stage: stringstage = const STAGE_TITLE: "Title"STAGE_TITLE;
// Key presses so far in the play stage.
Demo.playHits: numberplayHits = 0;
// Presses that arrived while another key was already held (the "second key" case).
Demo.overlapPresses: numberoverlapPresses = 0;
// Was any key held at the end of the previous tick? Used to spot an overlapping
// press.
Demo.wasAnyKeyHeld: booleanwasAnyKeyHeld = false;
// How many ticks pass between repeats of isAnyKeyPressed(repeatRate). The slider
// edits it.
Demo.repeatRate: numberrepeatRate = const REPEAT_DEFAULT: 10REPEAT_DEFAULT;
// Counters for the repeat panel: first presses, and the extra repeat fires while
// holding.
Demo.firstPresses: numberfirstPresses = 0;
Demo.repeatFires: numberrepeatFires = 0;
// Counters for the release panel: the A key alone versus the whole keyboard.
Demo.watchedKeyReleases: numberwatchedKeyReleases = 0;
Demo.allUpReleases: numberallUpReleases = 0;
// Per-player gamepad counters, one entry per slot.
Demo.padPresses: anypadPresses = new Array(const PLAYER_COUNT: 4PLAYER_COUNT).fill(0);
Demo.padReleases: anypadReleases = new Array(const PLAYER_COUNT: 4PLAYER_COUNT).fill(0);
// The event log lines, newest last.
/** @type {string[]} */
Demo.log: {}log = [];
// Screen size, no engine overlay, and the keyboard scroll guard.
Demo.configure(): {
displaySize: Vector2i;
isOverlayEnabled: boolean;
isCapturingKeyboardScroll: boolean;
}
configure() {
return {
displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const DISPLAY_W: 600DISPLAY_W, const DISPLAY_H: 315DISPLAY_H),
// Turn the engine's debug overlay off, along with its small corner toggle
// mark. This demo has no use for it.
isOverlayEnabled: booleanisOverlayEnabled: false,
// Space and the arrow keys scroll the host page by default. Every key is fair
// game in this demo, so opt in to keep the page still while you press them.
isCapturingKeyboardScroll: booleanisCapturingKeyboardScroll: true,
};
}
// Installs the shared UI theme.
async Demo.init(): unknowninit() {
this.Demo.palette: Palette | nullpalette = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteCreate: (size?: number) => PaletteCreates a standalone palette instance.paletteCreate(256);
// The shared UI colors go in before the palette is handed to the engine.
this.Demo.theme: anytheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteSet: (palette: Palette) => voidStores the active engine palette.
Use this to swap the **entire palette** (e.g. switch between a day and night
theme). After this call the renderer uploads the new palette uniform on the
next frame.
**Palette-value swap (change what a slot looks like):** mutate the live
{@link
BT.palette
}
in place with `palette.set(slot, newColor)`. The renderer
uploads dirty slots on the next frame; no `paletteSet()` or
{@link
BT.spritesRefresh
}
needed.
**Palette-layout swap (same colors, different slot positions):** build a new
palette with the same colors at new indices, call `paletteSet()`, then call
{@link
BT.spritesRefresh
}
so every sprite sheet re-maps its original RGBA
pixels against the new slot layout.paletteSet(this.Demo.palette: Palettepalette);
return true;
}
// Reads the keyboard helpers once per tick. Keyboard edges (pressed / released) only
// last one tick, so they must be read here and remembered, never read from render().
Demo.update(): voidupdate() {
// The UI kit's once-per-tick housekeeping. It also notices touch contacts, which
// ui.hasTouch() in render() relies on to show the "Needs a keyboard" label.
import uiui.tick();
this.Demo.updateKeyboard(): voidupdateKeyboard();
}
// Reads the gamepad helpers, clears the frame, and declares the panels.
Demo.render(): voidrender() {
// Gamepad edges are the odd one out: the engine clears them once per DRAWN frame,
// not once per tick. On a fast screen some frames run no tick at all, and on a
// slow one a frame can run two, so reading them in update() would miss or
// double-count presses. render() runs exactly once per drawn frame, so each edge
// is seen exactly once here.
this.Demo.readGamepads(): voidreadGamepads();
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.clear: (paletteIndex: number) => voidSets the frame clear color using a palette index.
The renderer uses this color when clearing the full display at the start
of the next frame.clear(this.Demo.theme: anytheme.bg);
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR);
import uiui.panel('Any Input - keys and gamepad buttons');
import uiui.end();
this.Demo.renderGatePanel(): voidrenderGatePanel();
this.Demo.renderRepeatPanel(): voidrenderRepeatPanel();
this.Demo.renderReleasePanel(): voidrenderReleasePanel();
this.Demo.renderGamepadPanel(): voidrenderGamepadPanel();
this.Demo.renderLog(): voidrenderLog();
}
// Handles every keyboard helper for this tick.
Demo.updateKeyboard(): voidupdateKeyboard() {
// Edge only: true on the tick a key goes down, even if another key is already
// held. (Think of a doorbell: it rings when someone presses the button, whatever
// else is going on.)
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyKeyPressed: (repeatRate?: number) => booleanChecks whether any keyboard key was pressed on the current fixed-update tick.
Fires when a key goes down, even if other keys are already held. With `repeatRate > 0` it
also fires every `repeatRate` ticks while keys stay held, counted from the earliest-pressed
key that is still down. Call from `update()`, not `render()`, like
{@link
isKeyPressed
}
.isAnyKeyPressed()) {
this.Demo.firstPresses: numberfirstPresses += 1;
this.Demo.addLog(text: any): voidaddLog('any key pressed');
// If a key was already held when this one arrived, it is the "second key"
// case.
if (this.Demo.wasAnyKeyHeld: booleanwasAnyKeyHeld) {
this.Demo.overlapPresses: numberoverlapPresses += 1;
}
this.Demo.advanceGate(): voidadvanceGate();
} else if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyKeyPressed: (repeatRate?: number) => booleanChecks whether any keyboard key was pressed on the current fixed-update tick.
Fires when a key goes down, even if other keys are already held. With `repeatRate > 0` it
also fires every `repeatRate` ticks while keys stay held, counted from the earliest-pressed
key that is still down. Call from `update()`, not `render()`, like
{@link
isKeyPressed
}
.isAnyKeyPressed(this.Demo.repeatRate: numberrepeatRate)) {
// Same helper with a repeat rate. The plain call above was false, so this one
// is true only because a key has been held for a multiple of repeatRate
// ticks.
this.Demo.repeatFires: numberrepeatFires += 1;
this.Demo.addLog(text: any): voidaddLog('repeat fire');
}
// The A key on its own. This fires the moment A comes up, even if S is still
// held.
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyReleased: (key: string) => booleanChecks whether a keyboard key was released on the current frame.
Call from `update()`, not `render()`: the release edge clears once per fixed-update
tick, which always runs before that frame's `render()`, so a release read from
`render()` can be intermittently missed under rapid input.isKeyReleased(const WATCHED_KEY: "KeyA"WATCHED_KEY)) {
this.Demo.watchedKeyReleases: numberwatchedKeyReleases += 1;
this.Demo.addLog(text: any): voidaddLog('A released');
}
// The whole keyboard. This fires only on the tick when nothing at all is held.
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyKeyReleased: () => booleanChecks whether every keyboard key has just been released.
Matches RetroBlit: `true` only on the tick where no key is held any more and at least one
was held before. Releasing one key while another stays down does not count; use
{@link
isKeyReleased
}
for per-key edges. Call from `update()`, not `render()`.isAnyKeyReleased()) {
this.Demo.allUpReleases: numberallUpReleases += 1;
this.Demo.addLog(text: any): voidaddLog('ALL keys up');
}
// Remember held state for the next tick's overlap check.
this.Demo.wasAnyKeyHeld: booleanwasAnyKeyHeld = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyKeyDown: () => booleanChecks whether at least one keyboard key is currently held.isAnyKeyDown();
}
// Handles the gamepad helpers for each of the four player slots. Called once per
// drawn frame from render(), see the note there.
Demo.readGamepads(): voidreadGamepads() {
for (let let player: numberplayer = 0; let player: numberplayer < const PLAYER_COUNT: 4PLAYER_COUNT; let player: numberplayer++) {
// Any button on this player's pad went down this frame.
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyButtonPressed: (player?: number, repeatRate?: number) => booleanChecks whether any gamepad button was pressed since the last rendered frame.
Same press and repeat semantics as
{@link
isAnyKeyPressed
}
, for one player's gamepad. Call from `update()`.
Like
{@link
isPressed
}
, the gamepad edge rolls over once per rendered frame, not once per fixed update: a
frame that runs no update can miss it, and a frame that runs several updates reports it to each.isAnyButtonPressed(let player: numberplayer)) {
this.Demo.padPresses: anypadPresses[let player: numberplayer] += 1;
this.Demo.addLog(text: any): voidaddLog(`P${let player: numberplayer + 1} pad pressed`);
}
// Like the keyboard version, this is the "everything is up" moment for that
// player.
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyButtonReleased: (player?: number) => booleanChecks whether every gamepad button has just been released for a player.
Same "everything is up" meaning as
{@link
isAnyKeyReleased
}
: `true` only on the frame where no button
is held any more and at least one was held before. A gamepad disconnecting while buttons were held
also counts. Call from `update()`. Like
{@link
isReleased
}
, the edge rolls over once per rendered frame,
not once per fixed update.isAnyButtonReleased(let player: numberplayer)) {
this.Demo.padReleases: anypadReleases[let player: numberplayer] += 1;
this.Demo.addLog(text: any): voidaddLog(`P${let player: numberplayer + 1} pad ALL up`);
}
}
}
// Moves the little title / play / game over loop one step on a key press.
Demo.advanceGate(): voidadvanceGate() {
if (this.Demo.stage: stringstage === const STAGE_TITLE: "Title"STAGE_TITLE) {
this.Demo.stage: stringstage = const STAGE_PLAY: "Play"STAGE_PLAY;
} else if (this.Demo.stage: stringstage === const STAGE_PLAY: "Play"STAGE_PLAY) {
this.Demo.playHits: numberplayHits += 1;
if (this.Demo.playHits: numberplayHits >= const HITS_TO_FINISH: 5HITS_TO_FINISH) {
this.Demo.stage: stringstage = const STAGE_GAME_OVER: "Game over"STAGE_GAME_OVER;
}
} else {
// Back to the title: clear the hit count so the next round starts from zero.
this.Demo.stage: stringstage = const STAGE_TITLE: "Title"STAGE_TITLE;
this.Demo.playHits: numberplayHits = 0;
}
}
// Adds a line to the log, stamped with the engine tick, dropping the oldest when
// full.
Demo.addLog(text: any): voidaddLog(text: anytext) {
this.Demo.log: {}log.push(`t${const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.ticks: numberCurrent fixed-update tick counter.
Increments once per engine update. Reset via
{@link
BT.ticksReset
}
.ticks} ${text: anytext}`);
if (this.Demo.log: {}log.length > const LOG_MAX: 16LOG_MAX) {
this.Demo.log: {}log.shift();
}
}
// The "press any key" gate and its overlap counter.
Demo.renderGatePanel(): voidrenderGatePanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const GATE_PANEL_X: 4GATE_PANEL_X, y: anyy: const PANEL_TOP_Y: anyPANEL_TOP_Y, width: numberwidth: const PANEL_WIDTH: 145PANEL_WIDTH });
import uiui.panel('Gate: isAnyKeyPressed');
import uiui.kv('Stage', this.Demo.stage: stringstage);
import uiui.kv('Hits', `${this.Demo.playHits: numberplayHits}/${const HITS_TO_FINISH: 5HITS_TO_FINISH}`);
import uiui.kv('Overlap', this.Demo.overlapPresses: numberoverlapPresses);
// The last row depends on where we are: a touch warning, or a hint for the stage.
if (import uiui.hasTouch()) {
// Touch screens have no keyboard, so say so instead of staying silent.
import uiui.label('Needs a keyboard', { color: stringcolor: 'warm' });
} else if (this.Demo.stage: stringstage === const STAGE_TITLE: "Title"STAGE_TITLE) {
import uiui.label('Press any key', { color: stringcolor: 'accent' });
} else if (this.Demo.stage: stringstage === const STAGE_PLAY: "Play"STAGE_PLAY) {
import uiui.label('Hold one, tap another', { color: stringcolor: 'dim' });
} else {
import uiui.label('Any key: title', { color: stringcolor: 'dim' });
}
// Pad the four 10-pixel rows to the top row's shared height.
import uiui.spacer(const TOP_ROWS_H: 46TOP_ROWS_H - 4 * 10);
import uiui.end();
}
// Press and repeat counters, with a slider for the repeat rate.
Demo.renderRepeatPanel(): voidrenderRepeatPanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const REPEAT_PANEL_X: numberREPEAT_PANEL_X, y: anyy: const PANEL_TOP_Y: anyPANEL_TOP_Y, width: numberwidth: const PANEL_WIDTH: 145PANEL_WIDTH });
import uiui.panel('Repeat while held');
// The slider hands back its new value every frame. Rounding keeps it a whole
// number of ticks, and decimals: 0 prints it that way too ("10", not "10.00").
this.Demo.repeatRate: numberrepeatRate = Math.round(
import uiui.slider('Ticks', this.Demo.repeatRate: numberrepeatRate, {
min: numbermin: const REPEAT_MIN: 1REPEAT_MIN,
max: numbermax: const REPEAT_MAX: 30REPEAT_MAX,
width: numberwidth: const SLIDER_WIDTH: numberSLIDER_WIDTH,
decimals: numberdecimals: 0,
}),
);
// Plain presses (edge only) next to the extra fires that only the repeat rate
// adds.
import uiui.kv('Press/rep', `${this.Demo.firstPresses: numberfirstPresses}/${this.Demo.repeatFires: numberrepeatFires}`);
// Held state is safe to read in render(). Only the edges must stay in update().
import uiui.pip('Any key held', const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyKeyDown: () => booleanChecks whether at least one keyboard key is currently held.isAnyKeyDown());
import uiui.end();
}
// One key's release versus the whole keyboard's release.
Demo.renderReleasePanel(): voidrenderReleasePanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const RELEASE_PANEL_X: numberRELEASE_PANEL_X, y: anyy: const PANEL_TOP_Y: anyPANEL_TOP_Y, width: numberwidth: const PANEL_WIDTH: 145PANEL_WIDTH });
import uiui.panel('Release: A vs all');
import uiui.pip('A held (isKeyDown)', const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyDown: (key: string) => booleanChecks whether a keyboard key is currently held.
Uses `KeyboardEvent.code` (for example `"KeyW"`, `"Space"`, `"ArrowUp"`).isKeyDown(const WATCHED_KEY: "KeyA"WATCHED_KEY));
import uiui.kv('A released', this.Demo.watchedKeyReleases: numberwatchedKeyReleases);
import uiui.kv('All up', this.Demo.allUpReleases: numberallUpReleases);
import uiui.label('Hold A+S, free A', { color: stringcolor: 'dim' });
// Pad the four 10-pixel rows to the top row's shared height.
import uiui.spacer(const TOP_ROWS_H: 46TOP_ROWS_H - 4 * 10);
import uiui.end();
}
// One row per gamepad slot, so "per player" is easy to see.
Demo.renderGamepadPanel(): voidrenderGamepadPanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const PADS_PANEL_X: numberPADS_PANEL_X, y: anyy: const PANEL_TOP_Y: anyPANEL_TOP_Y, width: numberwidth: const PANEL_WIDTH: 145PANEL_WIDTH });
import uiui.panel('Pads (press a button)');
for (let let player: numberplayer = 0; let player: numberplayer < const PLAYER_COUNT: 4PLAYER_COUNT; let player: numberplayer++) {
// The dot is lit while that player holds any button. The numbers are the
// press and "all up" counts for that slot.
const const text: stringtext = `P${let player: numberplayer + 1} ${this.Demo.padPresses: anypadPresses[let player: numberplayer]} press ${this.Demo.padReleases: anypadReleases[let player: numberplayer]} up`;
import uiui.pip(const text: stringtext, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyButtonDown: (player?: number) => booleanChecks whether at least one gamepad button is held for a player.
Covers the `BTN_UP` to `BTN_SELECT` buttons of a connected gamepad. Sticks, triggers
(analog or `BTN_L2` / `BTN_R2`), the keyboard, and pointers are not included; use
{@link
isAnyKeyDown
}
for the keyboard.isAnyButtonDown(let player: numberplayer));
}
// Pad the four 10-pixel rows to the top row's shared height.
import uiui.spacer(const TOP_ROWS_H: 46TOP_ROWS_H - const PLAYER_COUNT: 4PLAYER_COUNT * 10);
import uiui.end();
}
// The event log under the top row. It always keeps room for LOG_MAX lines, so its
// height never changes as events arrive.
Demo.renderLog(): voidrenderLog() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const EDGE: 4EDGE, y: anyy: const LOG_PANEL_Y: anyLOG_PANEL_Y, width: numberwidth: const LOG_PANEL_WIDTH: numberLOG_PANEL_WIDTH });
import uiui.panel('Event log (tick, event)');
// Before the first event, one dim line says what to do instead of a column of
// placeholders.
if (this.Demo.log: {}log.length === 0) {
import uiui.label('Press a key or a gamepad button', { color: stringcolor: 'dim' });
import uiui.spacer((const LOG_MAX: 16LOG_MAX - 1) * 10);
} else {
for (const const line: anyline of this.Demo.log: {}log) {
import uiui.label(const line: anyline);
}
// Empty space for the lines still to come.
import uiui.spacer((const LOG_MAX: 16LOG_MAX - this.Demo.log: {}log.length) * 10);
}
import uiui.end();
}
}
function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap(class DemoDemo);