Van GW-BASIC naar JavaScript, naast elkaar
De webversie is geen herschrijving — het is een trouwe port. De spellogica is statement voor statement vertaald uit SNEEKIE.BAS, met de originele variabelenamen en zelfs BASIC-regelnummers als commentaar. Wat echt veranderde is de dunne laag eronder: wat GW-BASIC gratis gaf — schermgeheugen, toetsenbord, geluid, GOTO — moest voor de browser opnieuw worden gebouwd. Deze pagina koppelt representatieve stukken en legt die laag uit; gebruik Uitleg voor elke BASIC-regel.
De brug: het scherm is de datastructuur
GW-BASIC POKEte tekens rechtstreeks in het 80×25 tekstschermgeheugen van de PC en PEEKte ze terug om te zien wat waar stond. Er was geen apart "slang"- of "muur"-object — het beeld was de toestand. De port houdt dat model exact aan, als een 4000-byte Uint8Array (vram): twee bytes per cel, teken en attribuut, geadresseerd met
offset = (rij − 1) × 160 + (kolom − 1) × 2
Omdat het model identiek is, verandert de gamecode nauwelijks — alleen de woorden POKE en PEEK worden de functies poke() en peek(). De rest van deze pagina volgt uit het opnieuw bouwen van GW-BASIC's runtime rond die ene gedeelde array.
Wat opnieuw gebouwd moest worden
| Het mechanisme uit 1988 | De vervanging uit 2026 |
|---|---|
POKE / PEEK in videogeheugen op &HB000/&HB800 |
poke() / peek() op een 4000-byte Uint8Array |
LOCATE r,c : PRINT CHR$(n) |
locate(r,c); pc(n) — dezelfde cursor, dezelfde tekencodes |
| het scherm ververst zichzelf (het is videogeheugen) | een dirty-cell set + requestAnimationFrame-redraw, met glyphs uit een ingebed IBM CP437-font |
INKEY$ / INPUT$ blokkeren tot er een toets is |
async + een Promise-toetsenbuffer (keyOrTimeout/waitKey) |
SOUND freq, ticks |
een Web Audio-blokgolfoscillator op dezelfde 1/18.2 s-klok |
ON LEVEL GOSUB … |
de dispatch-arrays CFG[] en ENEMY[] |
GOTO 510 / RETURN 510 (sterven) |
throw DEATH, gevangen rond de move-loop |
variabelen (DEFINT A-Y, Nederlandse namen) |
de zelfde namen letterlijk behouden (T, BTEL, HART, KLAVER…) |
De ene diepe wijziging: blokkeren wordt async
GW-BASIC liep van boven naar beneden en stopte gewoon bij INKEY$/INPUT$ tot je een toets indrukte. Een browsertab mag nooit zo blokkeren — daarom werden de wachtpunten awaits, waardoor playLevels() en program() async moesten worden. Een kleine Promise-toetsenbuffer vervangt de BIOS-buffer, en omdat GOTO niet bestaat, wordt de "spring eruit en sterf"-logica een geworpen DEATH-signaal dat rond de loop wordt opgevangen. Die twee trucs — await voor invoer, throw om te sterven — laten de rest een letterlijke vertaling blijven.
Het scherm is het datamodel
PEEK/POKE op videogeheugen en kiest zelfs mono (&HB000) of kleur (&HB800). De port heeft maar een in-memory raster nodig: vram is een 4000-byte array en poke()/peek() zijn kleine helpers.Het vaste kader tekenen
LOCATE r,c : PRINT CHR$(n) wordt locate(r,c); pc(n); STRING$(78,…) wordt pcn(196,78); SPC(78) wordt sp(78). De woorden zijn vertaald, maar de box-drawing-codes blijven gelijk.De levelloop & de startslang
FOR LEVEL=1 TO 32 is een for-loop; de slang start op dezelfde offsets (2000 staart, 1840 kop); ON LEVEL GOSUB wordt een indexed call in de CFG-tabel.Een toets lezen: blokkerende INKEY$ wordt async
INKEY$-wachtlus kan een browser niet blokkeren, dus hij wordt een Promise: keyOrTimeout(ms) resolved bij een toets of bij timeout. De gameloop await daarop.Toets sorteren, en ESC
LEN(A$) onderscheidt een tweekarakter-pijltje van een enkele toets; JavaScript gebruikt A$.length. ESC wordt een geworpen doodsignaal in plaats van GOTO 510.De kop omzetten naar een schermstap
A is het doelvak; peek(A) vertelt wat daar staat.Wat staat er in het volgende vak?
IF D<>n THEN GOTO-ladder wordt een if / else if-keten op dezelfde tekencodes: leeg, klaver, hart, steen, smiley, pijl.Geblokkeerd door muur of staart
blocked = true. Beide houden de oude richting, trekken bonus af en slaan de zet over.
De lichaamsbocht tekenen
E en oude richting F, en voegen daarna een nieuwe █ kop toe.Glans & levelgevaar
ON LEVEL GOSUB die pijlen/poorten animeert worden een for-loop en een indexed call in ENEMY.Dood: GOTO/RETURN 510 wordt throw
GOTO 510 of RETURN 510 vanuit een GOSUB. JavaScript gebruikt throw DEATH, gevangen rond de loop: dezelfde sprong naar buiten, maar met exceptions.Twee dispatch-tabellen (ON LEVEL GOSUB)
ON LEVEL GOSUB is een jumptable. De port maakt dat letterlijk: CFG[] bevat levelrecepten en ENEMY[] bevat de animatie per tick.Een item willekeurig neerzetten
RND wordt Math.random(). De even-offsettruc en de "is het leeg?"-test blijven hetzelfde.Scoren
OP bij de score op en werk de highscore bij. PRINT USING "######" wordt de helper pu(6, …).Geluid: SOUND f,d wordt Web Audio
SOUND freq, ticks wordt een korte blokgolf op de Web Audio-klok, met dezelfde frequenties en dezelfde 1/18.2 s-ticks.Invoer: INKEY$-scancodes worden keydown
INKEY$ terug als twee bytes. De browser-handler maakt precies zulke strings ('\0H' = omhoog), zodat de loop hetzelfde ziet als in 1988.