Sneekie

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 mechanismThe 2026 replacement
POKE / PEEK into video memory at &HB000/&HB800poke() / 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 keyasync + a Promise keyboard buffer (keyOrTimeout/waitKey)
SOUND freq, ticksa 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.