/*
Pixel Art: tiny pictures from number grids and from math patterns.
@description Draw tiny pictures from number grids and from math patterns, one pixel at a time, the way old games did.
Part of the BLIT386 demo series.
Prerequisites:
- Basics: https://demos.blit386.dev/basics
- Primitives: https://demos.blit386.dev/primitives
- Colors: https://demos.blit386.dev/colors
Live version: https://demos.blit386.dev/pixel-art
Guides:
- Palette: https://blit386.dev/docs/guides/palette#draw-using-absolute-palette-indices
- Overlay: https://blit386.dev/docs/guides/overlay#colors-and-hud-palette-slots
Reference:
- Rendering: https://blit386.dev/docs/api/rendering#primitives
- Core Types: https://blit386.dev/docs/api/core-types#color32
---
WHAT YOU WILL SEE
-----------------
A deep gray-blue screen with two small pieces of artwork on the left, each under an amber
caption drawn by the shared UI kit:
- "Heart 8x8": a heart built from an 8 by 8 grid of numbers, drawn at 4 screen pixels per
grid cell, so it is 32 pixels wide. Its body is dark red with two bright red highlight
pixels in the second row.
- "Checkerboard": an 8 by 8 board of 10-pixel squares. The two square colors slide back
and forth - one between red and yellow, the other between cyan and blue - so the board
never stops changing.
The engine overlay is open from the start with its palette grid at 16 swatches per row, so
the demo's 32-slot palette shows as two rows and you can watch slots 10 and 11 change.
WHAT YOU WILL LEARN
-------------------
- A 2D array (a grid) of small numbers that stand for colors, like paint-by-number on
graph paper.
- Nested loops: an outer loop for each row and an inner loop for each column, like
reading a book line by line and word by word.
- How a grid's row and column turn into x and y positions on the screen.
- Why many BT.drawPixel() calls in a small block make one "big" chunky pixel.
- A pattern drawn only with loops and math (no picture array), with colors that move over
time thanks to Color32.lerp() and palette.set() in update().
- A small palette (32 slots) with the shared UI theme moved to slots 20-31 by passing a
start slot to applyTheme().
TRY THIS
--------
- Watch slots 10 and 11 in the overlay's palette grid: they are the checkerboard colors
and they move with the squares below.
- Press the backquote key (`) to hide the overlay and see the artwork alone.
*/
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 Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32, class Rect2iInteger rectangle for pixel-perfect bounds and regions.
Used throughout the engine for sprite regions, display-space bounds, and
zero-allocation geometry helpers. Both convenience getters and allocation-free
`*To()` helpers are provided so callers can choose between readability and
hot-path efficiency.Rect2i, 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 } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
// Every color used for drawing gets a numbered slot in the palette, like a numbered paint
// jar. Slot 0 is always transparent, so our own colors start at 1.
const const C_WHITE: 1C_WHITE = 1; // pure white, kept in the palette but not drawn with
const const C_BG: 2C_BG = 2; // deep gray-blue: fills the screen background
const const C_TAG: 3C_TAG = 3; // pale blue-white: overlay bar and timing-chart accents
// Heart colors, picked by the codes in HEART_PALETTE_MAP below.
const const C_HEART_OUTLINE: 5C_HEART_OUTLINE = 5; // dark red: the heart's body (grid code 1)
const const C_HEART_FILL: 6C_HEART_FILL = 6; // bright red: the two highlight pixels (grid code 2)
// Checker colors. update() repaints these every tick so the board keeps moving.
const const C_CHECKER_A: 10C_CHECKER_A = 10; // dynamic: slides between red and yellow
const const C_CHECKER_B: 11C_CHECKER_B = 11; // dynamic: slides between cyan and blue
// HEART_GRID is an 8 by 8 table of small whole numbers. Think of graph paper: each cell
// is one tiny part of the picture. 0 means "leave empty" (we skip drawing there so the
// background shows through); 1 and 2 pick palette slots from HEART_PALETTE_MAP below.
const const HEART_GRID: {}HEART_GRID = [
[0, 1, 1, 0, 0, 1, 1, 0],
[1, 2, 1, 1, 1, 2, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0],
];
// HEART_PALETTE_MAP lines up with the numbers in the grid: paletteMap[1] is the palette
// slot for code 1, paletteMap[2] for code 2. Index 0 is null because 0 means "no paint".
// These are palette slot numbers, not Color32 objects; the palette knows the real colors.
const const HEART_PALETTE_MAP: {}HEART_PALETTE_MAP = [null, const C_HEART_OUTLINE: 5C_HEART_OUTLINE, const C_HEART_FILL: 6C_HEART_FILL];
// Looks up the palette slot for a paint code. The grid only holds small numbers we wrote
// ourselves, not user input, but this is still the one place that checks them: 0 (empty)
// and anything outside the map both come back as null, so callers need just one check.
function function indexFromPaletteMap(paletteMap: any, code: any): anyindexFromPaletteMap(paletteMap: anypaletteMap, code: anycode) {
if (code: anycode < 1 || code: anycode >= paletteMap: anypaletteMap.length) {
return null;
}
return paletteMap: anypaletteMap[code: anycode];
}
// Teaches pixel grids, nested loops, mapping grid cells to the screen, and a tiny
// procedural pattern.
class class DemoDemo {
// A clock that counts up in seconds. It only changes inside update(), so the
// animation stays steady even when render() runs at an odd rate.
Demo.animTime: numberanimTime = 0;
// The palette holds every color this demo uses.
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Where the shared UI theme colors landed in the palette, filled by applyTheme() in
// init(). The UI kit draws the section captions with these slots.
Demo.theme: nulltheme = null;
// Optional engine settings. We keep the default 320x240 screen, open the overlay from
// the start, and show its palette grid at 16 swatches per row so our 32-slot palette
// appears as two neat rows.
Demo.configure(): {
isOverlayPaletteEnabled: boolean;
overlayPaletteColumns: number;
isOverlayVisibleAtStart: boolean;
overlayStyle: {
barPaletteIndex: number;
textPaletteIndex: number;
gapPaletteIndex: number;
};
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
warningPaletteIndex: number;
errorPaletteIndex: number;
tagPaletteIndex: number;
};
}
configure() {
return {
isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true,
overlayPaletteColumns: numberoverlayPaletteColumns: 16,
isOverlayVisibleAtStart: booleanisOverlayVisibleAtStart: true,
// Overlay bar: pale blue-white with dark text.
overlayStyle: {
barPaletteIndex: number;
textPaletteIndex: number;
gapPaletteIndex: number;
}
overlayStyle: {
barPaletteIndex: numberbarPaletteIndex: const C_TAG: 3C_TAG,
textPaletteIndex: numbertextPaletteIndex: const C_BG: 2C_BG,
gapPaletteIndex: numbergapPaletteIndex: const C_BG: 2C_BG,
},
// Timing chart colors. The chart itself is not enabled in this demo, so these
// only matter if you add isOverlayTimingChartEnabled: true above.
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
warningPaletteIndex: number;
errorPaletteIndex: number;
tagPaletteIndex: number;
}
overlayTimingChartStyle: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_BG: 2C_BG,
renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_TAG: 3C_TAG,
warningPaletteIndex: numberwarningPaletteIndex: const C_TAG: 3C_TAG,
errorPaletteIndex: numbererrorPaletteIndex: 4, // slot 4 has no named constant and is never set
tagPaletteIndex: numbertagPaletteIndex: const C_BG: 2C_BG,
},
};
}
// Sets up the palette. Returns true when ready.
async Demo.init(): unknowninit() {
// Lay out the colors first, like putting paint on an artist's tray before
// starting a painting. Every color gets a number, all chosen before drawing
// begins. 32 slots are enough: the artwork uses 1 through 11 and the shared UI
// theme takes 12 more (slots 20-31, installed below).
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(32);
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_WHITE: 1C_WHITE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 255, 255)); // pure white
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BG: 2C_BG, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(28, 32, 48)); // deep gray-blue background
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TAG: 3C_TAG, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(200, 210, 230)); // pale blue-white
// Heart colors.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_HEART_OUTLINE: 5C_HEART_OUTLINE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(110, 10, 30)); // dark red body
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_HEART_FILL: 6C_HEART_FILL, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(230, 55, 75)); // bright red highlights
// Starting values for the checker colors. update() overwrites them on the very
// first tick.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHECKER_A: 10C_CHECKER_A, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 0, 0)); // starts red
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHECKER_B: 11C_CHECKER_B, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(0, 0, 255)); // starts blue
// Install the shared UI theme the captions draw with. It writes 12 colors
// starting at the slot we pass - 20 through 31 here, which stays clear of the
// artwork colors (1-11) and fills the top of our 32-slot palette exactly. Must
// happen before BT.paletteSet().
this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette, 20);
// Tell the engine to use this palette for all drawing.
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;
}
// Runs at a fixed rate (60 times per second by default). Advances animTime and
// repaints the checker colors in the palette. See the Basics demo for why update()
// and render() are separate steps: https://demos.blit386.dev/basics
Demo.update(): voidupdate() {
// Add one tick's worth of time. At the default 60 ticks per second that is about
// 1/60 of a second.
this.Demo.animTime: numberanimTime += 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.deltaSeconds: numberFixed-step seconds per update tick.
Equivalent to `1 / BT.targetFPS` when `BT.targetFPS` is finite and positive.
Falls back to `1 / 60` when target FPS is non-finite or non-positive.deltaSeconds;
// The checker squares use "lerp" (linear interpolation: a smooth blend between
// two colors). wave swings from 0 to 1 and back using Math.sin(): sin returns
// -1..1, so adding 1 and halving moves it to 0..1. At wave 0, color A is red and
// color B is cyan; at wave 1, A is yellow and B is blue. B uses 1 - wave, so the
// two colors slide in opposite directions and always contrast.
const const wave: numberwave = (Math.sin(this.Demo.animTime: numberanimTime * 2) + 1) * 0.5;
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHECKER_A: 10C_CHECKER_A, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.red: Color32Pure red color (255, 0, 0, 255).
Cached frozen singleton - do not modify.red.Color32.lerp(other: Color32, t: number): Color32Linearly interpolates between this color and another.
Useful for color transitions and gradients.lerp(class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.yellow: Color32Yellow color (255, 255, 0, 255).
Cached frozen singleton - do not modify.yellow, const wave: numberwave));
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHECKER_B: 11C_CHECKER_B, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.blue: Color32Pure blue color (0, 0, 255, 255).
Cached frozen singleton - do not modify.blue.Color32.lerp(other: Color32, t: number): Color32Linearly interpolates between this color and another.
Useful for color transitions and gradients.lerp(class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.cyan: Color32Cyan color (0, 255, 255, 255).
Cached frozen singleton - do not modify.cyan, 1 - const wave: numberwave));
}
// Draws the whole frame: the captions, the number-grid heart, and the checkerboard.
// FPS and tick stats live in the engine overlay, not on the canvas.
Demo.render(): voidrender() {
// Clear to the deep gray-blue background so the artwork pops.
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(const C_BG: 2C_BG);
// Heart on top.
this.Demo.renderHeartSection(): voidrenderHeartSection();
// Checkerboard below, with colors that shift with animTime.
this.Demo.renderCheckerPatternSection(): voidrenderCheckerPatternSection();
}
// Turns a 2D number grid into chunky pixels on screen. `grid` holds rows of paint
// codes (grid[row][col], like graph paper), `paletteMap` says which palette slot each
// code means (null to skip), `originX` and `originY` are where column 0 and row 0
// land on screen, and `scale` is how many screen pixels wide and tall each grid cell
// becomes.
//
// Nested loops: the outer `for` walks row = 0, 1, 2... (which strip of the grid), the
// inner `for` walks col = 0, 1, 2... inside that row (left to right). Finish one
// whole row before moving down, like reading a page.
//
// BT.drawPixel() paints exactly one screen pixel. To make each grid cell bigger we
// use two more small loops (dx and dy) that stamp a scale-by-scale block of pixels.
// Another valid way is BT.drawRectFill() with width and height equal to scale: same
// math, one call per cell.
Demo.drawGridWithScaledPixels(grid: any, paletteMap: any, originX: any, originY: any, scale: any): voiddrawGridWithScaledPixels(grid: anygrid, paletteMap: anypaletteMap, originX: anyoriginX, originY: anyoriginY, scale: anyscale) {
// Outer loop: which row of the design (the top row is row 0).
for (let let row: numberrow = 0; let row: numberrow < grid: anygrid.length; let row: numberrow++) {
// One row of the picture as a normal JavaScript array.
const const rowCodes: anyrowCodes = grid: anygrid[let row: numberrow];
// Inner loop: move across that row from left to right.
for (let let col: numbercol = 0; let col: numbercol < const rowCodes: anyrowCodes.length; let col: numbercol++) {
// Read this cell's paint code, like finding a square on graph paper.
const const code: anycode = const rowCodes: anyrowCodes[let col: numbercol];
// Ask the helper which palette slot this code means. It answers
// null for code 0 ("no ink here") and for any code outside the
// map, so this one check is all the guarding we need: skip, and
// the background stays visible.
const const paletteIndex: anypaletteIndex = function indexFromPaletteMap(paletteMap: any, code: any): anyindexFromPaletteMap(paletteMap: anypaletteMap, const code: anycode);
if (const paletteIndex: anypaletteIndex === null) {
continue;
}
// Map grid (col, row) to the top-left corner of this cell on screen. The
// column moves x (sideways), the row moves y (down the screen).
const const baseX: anybaseX = originX: anyoriginX + let col: numbercol * scale: anyscale;
const const baseY: anybaseY = originY: anyoriginY + let row: numberrow * scale: anyscale;
// Two tiny loops fill a scale-by-scale square, one drawPixel at a time.
for (let let dy: numberdy = 0; let dy: numberdy < scale: anyscale; let dy: numberdy++) {
for (let let dx: numberdx = 0; let dx: numberdx < scale: anyscale; let dx: numberdx++) {
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.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const baseX: anybaseX + let dx: numberdx, const baseY: anybaseY + let dy: numberdy), const paletteIndex: anypaletteIndex);
}
}
}
}
}
// Captions and draws the 8x8 heart.
Demo.renderHeartSection(): voidrenderHeartSection() {
// The caption comes from ui.caption() in the shared UI kit, the same widget every
// demo in the series uses, so all captions look identical everywhere.
import uiui.caption(12, 52, 'Heart 8x8 (number grid, no external bitmap used)');
// scale 4 makes the 8-cell-wide picture 32 screen pixels wide.
const const scale: 4scale = 4;
const const originX: 12originX = 12;
const const originY: 62originY = 62;
this.Demo.drawGridWithScaledPixels(grid: any, paletteMap: any, originX: any, originY: any, scale: any): voiddrawGridWithScaledPixels(const HEART_GRID: {}HEART_GRID, const HEART_PALETTE_MAP: {}HEART_PALETTE_MAP, const originX: 12originX, const originY: 62originY, const scale: 4scale);
}
// Draws a checkerboard using only math inside nested loops - no picture array. The
// colors slide around with animTime (advanced in update()) so you can see the clock
// moving.
Demo.renderCheckerPatternSection(): voidrenderCheckerPatternSection() {
import uiui.caption(12, 102, 'Checkerboard (loops + math, no grid array)');
// How many squares along each side.
const const cells: 8cells = 8;
// Pixel size of one square on the 320x240 screen.
const const cellSize: 10cellSize = 10;
// Top-left corner of the whole board.
const const startX: 12startX = 12;
const const startY: 112startY = 112;
// Outer loop picks the row of squares, inner loop the column: the same nested
// idea as the heart.
for (let let row: numberrow = 0; let row: numberrow < const cells: 8cells; let row: numberrow++) {
for (let let col: numbercol = 0; let col: numbercol < const cells: 8cells; let col: numbercol++) {
// Checker rule: neighbors must look different, like a chessboard.
// % is "remainder after division": (row + col) % 2 is 0 for even
// sums and 1 for odd sums. Each step right or down changes
// row + col by one, which flips the remainder, so no two touching
// squares share a color. update() already refreshed C_CHECKER_A
// and C_CHECKER_B this tick.
const const fill: 10 | 11fill = (let row: numberrow + let col: numbercol) % 2 === 0 ? const C_CHECKER_A: 10C_CHECKER_A : const C_CHECKER_B: 11C_CHECKER_B;
// Rect2i(x, y, width, height) describes a solid rectangle in pixels.
const const x: numberx = const startX: 12startX + let col: numbercol * const cellSize: 10cellSize;
const const y: numbery = const startY: 112startY + let row: numberrow * const cellSize: 10cellSize;
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.drawRectFill: (rect: Rect2i, paletteIndex: number) => voidDraws a filled rectangle.drawRectFill(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(const x: numberx, const y: numbery, const cellSize: 10cellSize, const cellSize: 10cellSize), const fill: 10 | 11fill);
}
}
}
}
// Hand the Demo class to BLIT386 to start the demo loop.
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);