From GW-BASIC to JavaScript, side by side
The 1988 source on the left, the 2026 port on the right — and how the new one is put together.
The web version isn't a rewrite — it's a faithful port. The game logic is translated
statement-for-statement from SNEEKIE.BAS, keeping the original variable names and even the BASIC
line numbers (as comments). What actually changed is the thin layer underneath: the handful of things GW-BASIC
gave you for free — screen memory, keyboard, sound, GOTO — had to be rebuilt for the browser.
This page pairs representative sections and explains that layer; for every BASIC line, use
Explained.
The bridge: the screen is the data structure
GW-BASIC POKEd characters straight into the PC's 80×25 text-screen memory and PEEKed
it back to find out what was where. There was no separate "snake" or "wall" object — the picture was
the state. The port keeps that model exactly, as a 4000-byte Uint8Array (vram): two bytes
per cell, character then attribute, addressed by
offset = (row − 1) × 160 + (col − 1) × 2
Because the model is identical, the game code barely changes — only the words POKE and
PEEK become the functions poke() and peek(). Everything else on this page
follows from rebuilding the rest of GW-BASIC's runtime around that one shared array.
What had to be rebuilt
| The 1988 mechanism | The 2026 replacement |
|---|---|
POKE / PEEK into video memory at &HB000/&HB800 | poke() / peek() on a 4000-byte Uint8Array |
LOCATE r,c : PRINT CHR$(n) | locate(r,c); pc(n) — same cursor, same character codes |
| the screen refreshes itself (it is video memory) | a dirty-cell set + requestAnimationFrame redraw, glyphs blitted from an embedded IBM CP437 font |
INKEY$ / INPUT$ block until a key | async + a Promise keyboard buffer (keyOrTimeout/waitKey) |
SOUND freq, ticks | a Web Audio square-wave oscillator on the same 1/18.2 s clock |
ON LEVEL GOSUB … | the CFG[] and ENEMY[] dispatch arrays |
GOTO 510 / RETURN 510 (die) | throw DEATH, caught around the move loop |
variables (DEFINT A-Y, Dutch names) | the same names kept verbatim (T, BTEL, HART, KLAVER…) |
The one deep change: blocking becomes async
GW-BASIC ran top-to-bottom and simply stopped at INKEY$/INPUT$ until you pressed
a key. A browser tab can never block like that — so the waiting points became awaits, which forced
playLevels() and program() to be async. A small Promise-based keyboard buffer
stands in for the BIOS key buffer, and because GOTO doesn't exist, the "jump out and die" lines turn into
a thrown DEATH sentinel caught around the loop. Those two tricks — await for input, throw to die
— are what let the rest of the code stay a literal translation.