ZXFLASHSpectrum BASIC → AVM1 bytecode → Ruffle
PROGRAM
0 OK
click the screen to give it the keyboard · Esc back to code
MANUALeverything compiles to Flash 8 bytecode and runs inside Ruffle

What this is

You type a ZX Spectrum BASIC program. The page compiles it, in JavaScript, straight to an uncompressed .swf: the Spectrum ROM font as sprite glyphs, a bank of square-wave DefineSounds for BEEP, and your program as AVM1 (ActionScript 1) bytecode with a small runtime. Ruffle, the open-source Flash player, runs it. Nothing is interpreted in the browser; the BASIC really is running inside the Flash movie. Every edit rebuilds the movie and reloads it.

Lines and statements

Lines may start with a number, as on the Spectrum. Lines without a number are numbered automatically (previous + 10). Several statements go on one line separated by :. Keywords are case-insensitive. LET is optional.

Statements

PRINT itemsitems separated by ; (no gap), , (next 16-column zone), ' (newline). Modifiers: AT row,col; TAB n; INK n; PAPER n; BRIGHT n;
INPUT "prompt"; a$reads from the lower screen; Enter finishes, Backspace edits. Several variables allowed.
LET a = 1, LET a$ = "x"string variables end in $
IF c THEN ... ELSE ...ELSE is an extension; IF x THEN 100 jumps
FOR i = a TO b STEP s / NEXT i
REPEAT / UNTIL c, WHILE c / WENDextensions
GO TO n, GO SUB n, RETURNGOTO/GOSUB also accepted; computed targets work (GO TO 100+k*10)
CLS, INK n, PAPER n, BORDER n, BRIGHT ncolours 0 black 1 blue 2 red 3 magenta 4 green 5 cyan 6 yellow 7 white; 8–15 are the bright set; extension: any #RRGGBB
PLOT x,y, DRAW dx,dy, CIRCLE x,y,r256×176 pixel grid, origin bottom-left, colour items allowed first (PLOT INK 2; x,y)
RECT x,y,w,hextension: filled rectangle in the current INK (w, h in pixels)
PAUSE nn frames at 50 per second; PAUSE 0 waits for a key; any key ends a pause early
KEEP "name", nextension: remembers the number n under that name between visits (a Flash SharedObject, stored by the browser); the function KEPT "name" gives it back, or 0 if nothing was kept. Names are shared by every program, so make them specific, like "popparty.best"
HALTextension: wait for the next frame (a 50th of a second), like the Z80 instruction. A game loop ending in HALT runs 50 times a second; one ending in PAUSE 1 skips a frame and runs 25 times
BEEP seconds, pitchpitch in semitones from middle C, −24 to 36 (Spectrum square wave)
LOADER, LOADER "name"extension: stops the program and goes back to the loader menu, or opens the example with that id ("monster-maze", "tvp", "jump"...; the same ids as the # in the page address). It follows a #name link on this page, so a downloaded .swf just stops there
TONE pitch [, seconds]extension: a soft sine chime with a quick decay, −12 to 24; does not pause unless seconds is given
DIM a(10), DIM b$(3,4)arrays, elements 1..n
DATA 1,2,"x", READ a, b$, RESTORE [n]as on the Spectrum
WRAP 1 / WRAP 0extension: with WRAP 1, PRINT moves a word that would not fit onto the next line instead of splitting it (handy for text adventures)
SPRITE "name", "row", "row"...defines a bitmap sprite baked into the movie: . transparent, # black, + grey, W white, 0-7 colours, a-h bright colours; repeating the name appends rows
SPRITE n$, x, y [, xscale [, yscale [, rotation [, alpha]]]]places a copy (top-left at x,y in screen pixels, y downwards; scale in percent, rotation in degrees, alpha in percent); SPRITE CLEAR removes them all, CLS too
EMOJI "🙂", "name" [, size]the browser rasterises the emoji at compile time (default 48 px) and bakes it into the movie as a true-colour alpha bitmap; place it with SPRITE "name", x, y, ..., centred on x,y so rotation spins about the middle. Plain text with no emoji in it, such as EMOJI "+10", "plus10", comes out as chunky outlined cartoon lettering, shrunk to fit: handy for titles and score pop-ups
FX "method", args...escape hatch to Flash: calls that MovieClip drawing-API method on the graphics layer, e.g. FX "lineStyle", 3, #ff00ff, 60, FX "moveTo", 10, 10, FX "curveTo", 128, 0, 246, 180, FX "beginFill", #0000ff, 40, FX "endFill", FX "chequer" (a ZX81 chequerboard fill). Coordinates are Flash's: pixels, origin top-left.
FXP "property", valuesets a property of the graphics layer: FXP "_alpha", 50, FXP "_rotation", 30, FXP "_xscale", 200
STOP, REMFLASH, INVERSE, OVER, POKE, RANDOMIZE, CLEAR are accepted and ignored

Functions

Spectrum style, no brackets needed: INT x, ABS, SGN, SQR, SIN, COS, TAN, ATN, EXP, LN, RND, PI, LEN a$, STR$ n, VAL a$, CHR$ n, CODE a$, INKEY$ (cursor keys give CHR$ 8 left, 9 right, 10 down, 11 up). String slicing: a$(2 TO 4), a$(3). Extensions: MOUSEX, MOUSEY (Spectrum pixel coordinates), MOUSEB (1 while pressed; brief clicks and taps are reported for one executing BASIC frame), TICKS (frames since start), KEY "p" (1 while that key is held; several keys can be held at once, unlike INKEY$; KEY CHR$ 9 is cursor right).

Characters 128–143 are the block graphics; you can also paste ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█ into strings. © and £ work too.

Loading real tapes

OPEN FILE takes a .bas listing or a Spectrum .tap tape image. BASIC blocks are detokenised into the editor; CODE blocks are listed in a REM at the top. Tape, printer and stream commands (LOAD, SAVE, LPRINT...) are accepted and skipped, PEEK/IN/ATTR/POINT return dummies, and USR stops the program with a report, because machine code cannot run here.

Playing the .swf elsewhere

DOWNLOAD .SWF saves the current movie. It is a plain Flash 8 file using only Flash 6–8 era APIs, so it should play in Adobe's original player, but it has only been tested in Ruffle. Adobe ended Flash Player at the end of 2020 and removed the downloads; the last standalone projector (32.0.0.465) survives in archives such as BlueMaxima's Flashpoint. The practical options today are the Ruffle desktop app or any page that embeds Ruffle.

Where the extension points are

Everything the BASIC can do is a compiler case in js/basic.js that emits a call into the AVM1 runtime in js/runtime.js. RECT is twelve lines across the two. Three ways to go further, from cheap to deep:

  1. FX / FXP (above): call any MovieClip drawing-API method or set any clip property from BASIC, no compiler change needed.
  2. New statements: add a keyword, a parser case and a runtime function. Sprites (SPRITE n, x, y backed by attachMovie), gradients, alpha, rotation, bitmap fills, sound bank entries, a second graphics layer.
  3. New assets: the SWF writer in js/swf.js can add tags (DefineBitsLossless for images, DefineFont for text fields, longer DefineSounds) that BASIC statements then reference by export name.

REM lines are deliberately inert; directives would be better as real keywords so the editor and the error messages know about them.

How the running works

The whole program becomes one AVM1 function that is re-entered every frame (a program over about 40 KB of bytecode is split across several functions automatically; a function may hold 64 KB). Backward jumps spend a per-frame budget, so a 10 GO TO 10 loop never hangs the player: it just yields and carries on next frame. PAUSE, BEEP and INPUT yield properly. RETURN and computed GO TO re-enter through that resume chain, so they cost a few hundred operations each: keep GO SUBs out of per-vertex inner loops. Errors appear Spectrum-style in the lower two rows of the screen.