How to fit a real game into ten lines of FastBasic on the Atari 8-bit, for the BASIC 10Liner contest: what the language lets you get away with, which tricks actually save characters, how to keep a ten-liner fast enough to never drop a frame, and a small toolchain that measures all of it so you don't have to guess.
Everything here was learned the hard way on real PUR-120 entries, then generalized. Every
character count in these pages is checked against the real compiler by
tests/tricks.py, every behavior claim is run in an emulator, and every
timing comes from bench/. If a number here is wrong, a test should say so.
Ten lines. 120 characters each (or 256, if you're feeling EXTREME). No machine code. Go.
If you read nothing else:
- Never abbreviate by hand. Write a readable, commented source and let a minimizer do the
spelling. Use fbp with
-O: on two full PUR-120 listings it beat FastBasic's own-lsby 31 and 21 characters, with a byte-identical XEX. - Learn the precedence.
&,!andEXORbind tighter than*and/, which bind tighter than+and-.I+U&BisI+(U&B). Most parentheses you'd type in another BASIC are dead weight here. (It cuts both ways:X+1&7isX+1, not a wrap.) - Comparisons are numbers. True is 1, false is 0, so
F=A>Breplaces a whole IF/ELSE/ENDIF (15 characters) andK=K+(B>9)*7replaces an IF. - One-letter names, given to the variables you use most. There are 27 of them: A to Z and
_. - Functions take a bare argument.
PEEK 712,STICK 0,RAND 10. - Strings are your data segment. Any byte but
"and $9B fits in a literal, and&"..."is the address of a literal with no variable at all. - The OS already did the work. Patch its display list instead of building one; the runtime already turned the sound off.
- Speed is a budget, not a feeling. A frame is 131 units of 2 scanlines (NTSC). Measure what statements cost, give heavy jobs their own frames, and count lost frames with a tool, not your eyes.
- Packing is its own problem. A statement never splits across lines, so the last 10 to 40 characters of each line are where listings go to die.
- Try ideas as variants, not edits, and keep the ones that measure better.
| 1. The contest | Categories, what counts as a character, what to submit |
| 2. FastBasic for sizecoders | The language facts every trick leans on: abbreviations, precedence, names, strings, blocks |
| 3. Size tricks | The catalogue: before, after, characters saved, and why it works |
| 4. Atari tricks | Display lists, players and missiles, DLIs, sound and input on a budget |
| 5. Speed | The frame budget, what each statement costs, scheduling, counting lost frames |
| 6. Packing ten lines | Line breaks, reordering, and why the end of a line matters most |
| 7. Workflow | Readable source, variants, measuring, testing: the loop that keeps it honest |
| 8. Submission | The ZIP, the proof image, the disk image |
| Abbreviations | Every keyword's shortest spelling, generated from the grammar |
Python 3 scripts in tools/, documented in tools/README.md:
| Tool | What it does |
|---|---|
fbsize.py |
Measure, minimize, pack into 10 lines (with safe reordering), verify |
variants.py |
Try size ideas as named, combinable edits and see what each saves |
fbbench.py |
Time statements in the emulator, in units of 2 scanlines |
fbframes.py |
Count lost frames in your game while a script plays it |
proof.py |
Render a listing as an ATASCII image for the contest's proof |
abbrevs.py |
Regenerate the abbreviation table from FastBasic's grammar |
export FASTBASIC=/path/to/fastbasic # the cross-compiler
export FBP=/path/to/fbp # optional, but it's free characters
python3 tools/fbsize.py min game.bas -o GAME.min # minimize + check the XEX is identical
python3 tools/fbsize.py pack GAME.min -o GAME.BAS # break into 10 lines of 120
python3 tools/fbsize.py measure GAME.BAS # what's left on every line
python3 tools/proof.py GAME.BAS -o proof.png # the listing as the Atari sees itThe size tools need only Python and FastBasic. The emulator tools (fbbench, fbframes,
the behavior half of the tests) need Linux with atari800, Xvfb, xdotool and ImageMagick.
Shoutouts to @dmsc for FastBasic, which makes all of this possible (and fast), and to Gunnar and the NOMAM crew for running the BASIC 10Liner contest year after year. Timings were measured in atari800 with its built-in AltirraOS, thanks to Avery Lee's Altirra.
The tools, tests and example programs are MIT. The writing (this README, docs/
and the benchmark results) is CC BY 4.0: share it, adapt it, just give credit.
Found a trick that isn't here, or a number that's wrong? Open an issue or a PR, ideally with a
trick(...) line for tests/tricks.py that proves it.