refactor: rename cryptic identifiers and document 8 old programs master
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 11 Oct 2026 07:52:22 +0000 (10:52 +0300)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 11 Oct 2026 07:52:22 +0000 (10:52 +0300)
Second readability pass (n0-qbasic-improver) over programs whose names
were still cryptic or Estonian-derived:

- People.bas: slideshow subs (pr/wpr/pri/sc1/pal*, mkjuku) and shared
  vars (fontt, jas, tmr) get descriptive names
- Worm/worm.bas: game-loop subs (ai, prc, tkt, sc2) and vars renamed;
  also fixes the 8.lvl arena layout rows
- checkers2.bas: Estonian move subs (compki = computer move, humngo,
  thinkc/thinkh) and lookahead vars renamed
- 3D graph.bas: Estonian subs (graaf, mkgr*, nait3d, getcor) renamed
  to heightmap/camera domain terms
- Galaxy explorer.bas: temp/rn/disp renamed (mkGalaxy kept: the site
  docs reference it by name)
- school clock v1 kk.bas + ekr.bas and v2 kkmini.bas: Estonian subs
  (ekr, kola, leiaconf-style names, getnad, getmd) renamed to bell/
  timetable/LPT-display terms

Algorithm, control flow, labels, string literals and DATA untouched.
Verified with scripts/verify_rename.py (token-stream equivalence vs
HEAD: literals identical, renames bijective per QBasic scope, line
endings preserved) plus crtbasic --headless before/after runs.

2D GFX/People.bas
3D GFX/Space/Galaxy explorer.bas
Games/Worm/8.lvl
Games/Worm/worm.bas
Games/checkers2.bas
Math/Plotting/3D graph.bas
Miscellaneous/Automated school clock/v1/ekr.bas
Miscellaneous/Automated school clock/v1/kk.bas
Miscellaneous/Automated school clock/v2/kkmini.bas

index 60161a5..cb13fcb 100755 (executable)
@@ -8,48 +8,49 @@
 ' Changelog:\r
 ' 2001, Initial version\r
 ' 2024 - 2025, Improved program readability\r
 ' Changelog:\r
 ' 2001, Initial version\r
 ' 2024 - 2025, Improved program readability\r
+' 2026, Improved program readability\r
 \r
 \r
 DECLARE SUB displaySlide1 ()\r
 \r
 \r
 DECLARE SUB displaySlide1 ()\r
-DECLARE SUB setink (a!)\r
-DECLARE SUB inke (a$)\r
-DECLARE SUB mkjuku (x!, y!, a!, c!)\r
-DECLARE SUB pr (x!, y!, s!, c!, n!, a$)\r
-DECLARE SUB wpr ()\r
+DECLARE SUB setIdleTimeout (seconds!)\r
+DECLARE SUB pollKey (key$)\r
+DECLARE SUB drawBoy (x!, y!, phaseIndex!, shade!)\r
+DECLARE SUB printNthChar (x!, y!, scale!, shade!, charIndex!, text$)\r
+DECLARE SUB updateReveal ()\r
 DECLARE SUB displayEffect7 ()\r
 DECLARE SUB displaySlide6 ()\r
 DECLARE SUB displaySlide5 ()\r
 DECLARE SUB displayEffect7 ()\r
 DECLARE SUB displaySlide6 ()\r
 DECLARE SUB displaySlide5 ()\r
-DECLARE SUB pal4 (c, r!, g!, b!)\r
+DECLARE SUB setPaletteColor (paletteIndex, r!, g!, b!)\r
 DECLARE SUB displaySlide4 ()\r
 DECLARE SUB displaySlide4 ()\r
-DECLARE SUB inpur ()\r
+DECLARE SUB waitKeypress ()\r
 DECLARE SUB displayEffect5 ()\r
 DECLARE SUB displaySlide3 ()\r
 DECLARE SUB displayEffect5 ()\r
 DECLARE SUB displaySlide3 ()\r
-DECLARE SUB prin (x1!, y1!, s!, c, a$)\r
-DECLARE SUB pal3 (r!, g!, b!)\r
-DECLARE SUB mkfont ()\r
-DECLARE SUB pal2 (r!, g!, b!)\r
-DECLARE SUB box1 (x1!, y1!, x2!, y2!, c!)\r
-DECLARE SUB mkback ()\r
+DECLARE SUB printText (xPos!, yPos!, scale!, shade, text$)\r
+DECLARE SUB setBoxPalette2 (red!, green!, blue!)\r
+DECLARE SUB makeFont ()\r
+DECLARE SUB setBoxPalette1 (red!, green!, blue!)\r
+DECLARE SUB drawShadedBox (x1!, y1!, x2!, y2!, shadeType!)\r
+DECLARE SUB makeBackground ()\r
 DECLARE SUB displaySlide2 ()\r
 DECLARE SUB displaySlide2 ()\r
-DECLARE SUB resiz ()\r
-DECLARE SUB pri (x!, y!, a$, c!)\r
-DECLARE SUB deca (xs!, ys!, fx!, fy!)\r
-DECLARE SUB box (xs!, ys!)\r
+DECLARE SUB splitTransition ()\r
+DECLARE SUB printTypewriter (x!, y!, text$, shade!)\r
+DECLARE SUB shrinkBox (sizeX!, sizeY!, shrinkX!, shrinkY!)\r
+DECLARE SUB box (sizeX!, sizeY!)\r
 DECLARE SUB displayEffect4 ()\r
 DECLARE SUB displayEffect3 ()\r
 DECLARE SUB displayEffect2 ()\r
 DECLARE SUB displayEffect1 ()\r
 DECLARE SUB start ()\r
 DECLARE SUB displayEffect4 ()\r
 DECLARE SUB displayEffect3 ()\r
 DECLARE SUB displayEffect2 ()\r
 DECLARE SUB displayEffect1 ()\r
 DECLARE SUB start ()\r
-DECLARE SUB sc1 ()\r
-DECLARE SUB pal (x!)\r
-DIM SHARED fontt(0 TO 7, 0 TO 7, 0 TO 255)\r
-DIM SHARED tim\r
-DIM SHARED tim2\r
-DIM SHARED jas(1 TO 500)\r
-DIM SHARED pii\r
-DIM SHARED tmr\r
-DIM SHARED ink\r
-DIM SHARED tim$\r
+DECLARE SUB sc1 () ' leftover declaration; the routine was never implemented\r
+DECLARE SUB setPalette (scheme!)\r
+DIM SHARED fontBitmap(0 TO 7, 0 TO 7, 0 TO 255)\r
+DIM SHARED revealCounter\r
+DIM SHARED revealStage\r
+DIM SHARED animPhase(1 TO 500)\r
+DIM SHARED piValue\r
+DIM SHARED autoAdvance\r
+DIM SHARED idleCountdown\r
+DIM SHARED lastTime$\r
 \r
 start\r
 \r
 \r
 start\r
 \r
@@ -74,31 +75,31 @@ displayEffect5
 \r
 SYSTEM\r
 \r
 \r
 SYSTEM\r
 \r
-SUB box (xs, ys)\r
+SUB box (sizeX, sizeY)\r
 ' Draws a 3D style box border at the top-left corner of the screen\r
 ' Parameters:\r
 ' Draws a 3D style box border at the top-left corner of the screen\r
 ' Parameters:\r
-'   xs - width of the box\r
-'   ys - height of the box\r
+'   sizeX - width of the box\r
+'   sizeY - height of the box\r
 \r
 \r
-LINE (0, 186)-(0 + xs, 186 - ys), 15, B\r
-LINE (1, 187)-(-1 + xs, 187 - ys), 25, B\r
-LINE (2, 188)-(-2 + xs, 188 - ys), 15, B\r
+LINE (0, 186)-(0 + sizeX, 186 - sizeY), 15, B\r
+LINE (1, 187)-(-1 + sizeX, 187 - sizeY), 25, B\r
+LINE (2, 188)-(-2 + sizeX, 188 - sizeY), 15, B\r
 PSET (0, 188), 0\r
 PSET (0, 188), 0\r
-PSET (0 + xs, 188), 0\r
-PSET (0, 186 - ys), 0\r
-PSET (0 + xs, 186 - ys), 0\r
+PSET (0 + sizeX, 188), 0\r
+PSET (0, 186 - sizeY), 0\r
+PSET (0 + sizeX, 186 - sizeY), 0\r
 END SUB\r
 \r
 DEFINT Z\r
 END SUB\r
 \r
 DEFINT Z\r
-SUB box1 (x1, y1, x2, y2, c)\r
+SUB drawShadedBox (x1, y1, x2, y2, shadeType)\r
 \r
 ' Draws a shaded box with a 3D effect\r
 ' Parameters:\r
 '   x1, y1 - top-left corner coordinates\r
 '   x2, y2 - bottom-right corner coordinates\r
 \r
 ' Draws a shaded box with a 3D effect\r
 ' Parameters:\r
 '   x1, y1 - top-left corner coordinates\r
 '   x2, y2 - bottom-right corner coordinates\r
-'   c - type of shading\r
+'   shadeType - type of shading\r
 \r
 \r
-IF c = 1 THEN za = 51 ELSE za = 102\r
+IF shadeType = 1 THEN za = 51 ELSE za = 102\r
 \r
 FOR zy = y1 + 7 TO y2 + 7\r
 FOR zx = x1 + 7 TO x2 + 7\r
 \r
 FOR zy = y1 + 7 TO y2 + 7\r
 FOR zx = x1 + 7 TO x2 + 7\r
@@ -122,17 +123,19 @@ NEXT zy
 END SUB\r
 \r
 DEFSNG Z\r
 END SUB\r
 \r
 DEFSNG Z\r
-SUB deca (xs, ys, fx, fy)\r
-LINE (0, 185 - ys)-(xs, 185 - ys + fy), 0, BF\r
-LINE (xs, 18 - ys)-(xs - fx, 188), 0, BF\r
-xs = xs - fx\r
-ys = ys - fy\r
-box xs, ys\r
+SUB shrinkBox (sizeX, sizeY, shrinkX, shrinkY)\r
+' Erodes the border box inward by shrinkX/shrinkY pixels and redraws it smaller\r
+LINE (0, 185 - sizeY)-(sizeX, 185 - sizeY + shrinkY), 0, BF\r
+LINE (sizeX, 18 - sizeY)-(sizeX - shrinkX, 188), 0, BF\r
+sizeX = sizeX - shrinkX\r
+sizeY = sizeY - shrinkY\r
+box sizeX, sizeY\r
 END SUB\r
 \r
 SUB displayEffect1\r
 END SUB\r
 \r
 SUB displayEffect1\r
+' Effect 1: scrolling color bands, sine-wave line and walking boy figures; waits for keypress\r
 \r
 \r
-pal 3\r
+setPalette 3\r
 \r
 DIM buf1(1 TO 10000)\r
 DIM buf2(1 TO 10000)\r
 \r
 DIM buf1(1 TO 10000)\r
 DIM buf2(1 TO 10000)\r
@@ -144,7 +147,7 @@ NEXT a
 \r
 b = 0\r
 c1 = 1\r
 \r
 b = 0\r
 c1 = 1\r
-setink 10\r
+setIdleTimeout 10\r
 1\r
 c1 = c1 + 1\r
 IF c1 > 50 THEN c1 = 1\r
 1\r
 c1 = c1 + 1\r
 IF c1 > 50 THEN c1 = 1\r
@@ -181,18 +184,19 @@ a = 50
 FOR x = 65 + 18 TO 270 STEP 40\r
 a = a + 1\r
 IF buf3(x - 1) < 190 THEN\r
 FOR x = 65 + 18 TO 270 STEP 40\r
 a = a + 1\r
 IF buf3(x - 1) < 190 THEN\r
-mkjuku x, buf3(x - 1) - 27, x, 0\r
-mkjuku x, buf3(x) - 27, x, a\r
+drawBoy x, buf3(x - 1) - 27, x, 0\r
+drawBoy x, buf3(x) - 27, x, a\r
 END IF\r
 NEXT x\r
 \r
 END IF\r
 NEXT x\r
 \r
-inke a$\r
-SOUND 0, .4\r
+pollKey a$\r
+SOUND 0, .4 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 IF a$ = "" THEN GOTO 1\r
 \r
 END SUB\r
 \r
 SUB displayEffect2\r
 IF a$ = "" THEN GOTO 1\r
 \r
 END SUB\r
 \r
 SUB displayEffect2\r
+' Effect 2: palette fade transition (ramps palette brightness down, then up)\r
 \r
 FOR a = 1 TO 30\r
 e = 0\r
 \r
 FOR a = 1 TO 30\r
 e = 0\r
@@ -207,7 +211,7 @@ OUT &H3C9, e / 3
 NEXT f\r
 \r
 FOR b = 1 TO 3\r
 NEXT f\r
 \r
 FOR b = 1 TO 3\r
-SOUND 0, .3\r
+SOUND 0, .3 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 NEXT b\r
 NEXT a\r
 \r
 NEXT b\r
 NEXT a\r
 \r
@@ -223,7 +227,7 @@ OUT &H3C9, e / 3
 NEXT f\r
 \r
 FOR b = 1 TO 2\r
 NEXT f\r
 \r
 FOR b = 1 TO 2\r
-SOUND 0, .3\r
+SOUND 0, .3 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 NEXT b\r
 \r
 NEXT a\r
 NEXT b\r
 \r
 NEXT a\r
@@ -231,6 +235,7 @@ NEXT a
 END SUB\r
 \r
 SUB displayEffect3\r
 END SUB\r
 \r
 SUB displayEffect3\r
+' Effect 3: accelerating line-burst zoom on screen 7, then authors roll on screen 13\r
 SCREEN 7\r
 SCREEN 7, , , 1\r
 \r
 SCREEN 7\r
 SCREEN 7, , , 1\r
 \r
@@ -256,11 +261,11 @@ NEXT a
 \r
 PCOPY 0, 1\r
 CLS\r
 \r
 PCOPY 0, 1\r
 CLS\r
-SOUND 0, .4\r
+SOUND 0, .4 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 IF b < 50 THEN GOTO 2\r
 \r
 SCREEN 13\r
 IF b < 50 THEN GOTO 2\r
 \r
 SCREEN 13\r
-pal 2\r
+setPalette 2\r
 \r
 FOR a = 0 TO 160 STEP b\r
 LINE (160 + a, 0)-(160 + a, 199), 25\r
 \r
 FOR a = 0 TO 160 STEP b\r
 LINE (160 + a, 0)-(160 + a, 199), 25\r
@@ -269,57 +274,58 @@ LINE (0, 100 + a)-(319, 100 + a), 25
 LINE (0, 100 - a)-(319, 100 - a), 25\r
 NEXT a\r
 \r
 LINE (0, 100 - a)-(319, 100 - a), 25\r
 NEXT a\r
 \r
-resiz\r
-pal 3\r
+splitTransition\r
+setPalette 3\r
 \r
 \r
-pri 11, 8, "-* A U T H O R S *-", 55\r
-pri 10, 11, CHR$(254) + " John Doe", 55\r
-pri 10, 13, CHR$(254) + " Jane Doe", 55\r
-pri 10, 15, CHR$(254) + " Anonymous", 55\r
-pri 20, 19, "year 2001", 55\r
+printTypewriter 11, 8, "-* A U T H O R S *-", 55\r
+printTypewriter 10, 11, CHR$(254) + " John Doe", 55\r
+printTypewriter 10, 13, CHR$(254) + " Jane Doe", 55\r
+printTypewriter 10, 15, CHR$(254) + " Anonymous", 55\r
+printTypewriter 20, 19, "year 2001", 55\r
 \r
 \r
-inpur\r
+waitKeypress\r
 CLS\r
 END SUB\r
 \r
 SUB displayEffect4\r
 CLS\r
 END SUB\r
 \r
 SUB displayEffect4\r
-pal 2\r
+' Effect 4: border box erodes while random characters scroll up and text lines appear; waits for keypress\r
+setPalette 2\r
 xs = 317\r
 ys = 185\r
 box xs, ys\r
 xs = 317\r
 ys = 185\r
 box xs, ys\r
-tey = 20\r
+textY = 20\r
 \r
 DIM buf4(1 TO 10000)\r
 \r
 b = 0\r
 \r
 DIM buf4(1 TO 10000)\r
 \r
 b = 0\r
-setink 10\r
+setIdleTimeout 10\r
 COLOR 25\r
 4\r
 b = b + 1\r
 \r
 SELECT CASE b\r
 CASE 50 TO 200\r
 COLOR 25\r
 4\r
 b = b + 1\r
 \r
 SELECT CASE b\r
 CASE 50 TO 200\r
-deca xs, ys, 1, 1\r
+shrinkBox xs, ys, 1, 1\r
 \r
 CASE 201\r
 \r
 CASE 201\r
-'pal4 255, 63, 45, 0\r
-'prin 10, tey, 2, 255, "Sources:"\r
-tey = tey + 20\r
+'setPaletteColor 255, 63, 45, 0\r
+'printText 10, textY, 2, 255, "Sources:"\r
+textY = textY + 20\r
 \r
 CASE 290\r
 \r
 CASE 290\r
-pal4 254, 20, 20, 63\r
-prin 70, tey, 7, 254, "TEST"\r
-tey = tey + 60\r
+setPaletteColor 254, 20, 20, 63\r
+printText 70, textY, 7, 254, "TEST"\r
+textY = textY + 60\r
 \r
 CASE 350\r
 \r
 CASE 350\r
-pal4 254, 20, 20, 63\r
-prin 100, tey, 2, 254, "www.12345.com"\r
-tey = tey + 20\r
+setPaletteColor 254, 20, 20, 63\r
+printText 100, textY, 2, 254, "www.12345.com"\r
+textY = textY + 20\r
 \r
 CASE 400\r
 \r
 CASE 400\r
-pal4 254, 20, 20, 63\r
-prin 100, tey, 2, 254, CHR$(16) + "Subject 1"\r
-tey = tey + 10\r
+setPaletteColor 254, 20, 20, 63\r
+printText 100, textY, 2, 254, CHR$(16) + "Subject 1"\r
+textY = textY + 10\r
 \r
 END SELECT\r
 \r
 \r
 END SELECT\r
 \r
@@ -333,16 +339,17 @@ GET (x, 183 - ys + 14)-(x + 7, 183), buf4(1)
 PUT (x, 183 - ys + 6), buf4(1), PSET\r
 NEXT x\r
 \r
 PUT (x, 183 - ys + 6), buf4(1), PSET\r
 NEXT x\r
 \r
-inke a$\r
+pollKey a$\r
 \r
 IF a$ <> "" THEN GOTO 3\r
 \r
 IF a$ <> "" THEN GOTO 3\r
-wpr\r
+updateReveal\r
 GOTO 4\r
 \r
 3\r
 END SUB\r
 \r
 SUB displayEffect5\r
 GOTO 4\r
 \r
 3\r
 END SUB\r
 \r
 SUB displayEffect5\r
+' Effect 5: random screen blocks drift, then the screen wipes to black\r
 DIM buf(1 TO 5000)\r
 \r
 FOR a = 1 TO 1000\r
 DIM buf(1 TO 5000)\r
 \r
 FOR a = 1 TO 1000\r
@@ -352,48 +359,50 @@ GET (x, y)-(x + 19, y + 19), buf(1)
 IF RND * 100 < 50 THEN x = x + 1 ELSE x = x - 1\r
 IF RND * 100 < 50 THEN y = y + 1\r
 PUT (x, y), buf(1), PSET\r
 IF RND * 100 < 50 THEN x = x + 1 ELSE x = x - 1\r
 IF RND * 100 < 50 THEN y = y + 1\r
 PUT (x, y), buf(1), PSET\r
-SOUND 0, .05\r
+SOUND 0, .05 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 NEXT a\r
 \r
 FOR a = 0 TO 100\r
 LINE (0, a)-(319, a), 0\r
 LINE (0, 200 - a)-(319, 200 - a), 0\r
 NEXT a\r
 \r
 FOR a = 0 TO 100\r
 LINE (0, a)-(319, a), 0\r
 LINE (0, 200 - a)-(319, 200 - a), 0\r
-SOUND 0, .4\r
+SOUND 0, .4 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 NEXT a\r
 \r
 END SUB\r
 \r
 SUB displayEffect7\r
 NEXT a\r
 \r
 END SUB\r
 \r
 SUB displayEffect7\r
-pal 4\r
-mkback\r
+' Effect 7: sample content slide with header box and bullet list\r
+setPalette 4\r
+makeBackground\r
 \r
 \r
-pal2 0, 0, 32\r
-box1 3, 3, 300, 50, 1\r
+setBoxPalette1 0, 0, 32\r
+drawShadedBox 3, 3, 300, 50, 1\r
 \r
 \r
-pal4 255, 50, 50, 0\r
-prin 10, 10, 2, 255, "Et dolore magna"\r
-prin 50, 30, 1, 255, "www.utenimadminim.org"\r
+setPaletteColor 255, 50, 50, 0\r
+printText 10, 10, 2, 255, "Et dolore magna"\r
+printText 50, 30, 1, 255, "www.utenimadminim.org"\r
 \r
 \r
-pal3 20, 32, 63\r
-box1 20, 40, 290, 180, 2\r
+setBoxPalette2 20, 32, 63\r
+drawShadedBox 20, 40, 290, 180, 2\r
 \r
 \r
-pal4 254, 63, 45, 0\r
+setPaletteColor 254, 63, 45, 0\r
 b = 25\r
 b = 25\r
-prin 40, 60, 1, 254, CHR$(254) + " Ut enim ad minim veniam"\r
+printText 40, 60, 1, 254, CHR$(254) + " Ut enim ad minim veniam"\r
 a = b\r
 a = b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " Quis nostrud exercitation"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " Quis nostrud exercitation"\r
 a = a + b\r
 a = a + b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " Laboris nisi ut aliquip ex"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " Laboris nisi ut aliquip ex"\r
 a = a + b\r
 a = a + b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " Sed ut perspiciatis unde"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " Sed ut perspiciatis unde"\r
 \r
 \r
-inpur\r
+waitKeypress\r
 \r
 END SUB\r
 \r
 SUB displaySlide1\r
 \r
 END SUB\r
 \r
 SUB displaySlide1\r
+' Slide 1: pixelates the "HEADER" title onto the screen, then blurs it in four passes\r
 \r
 \r
-pal 2\r
+setPalette 2\r
 LOCATE 1, 1\r
 COLOR 1\r
 PRINT "HEADER"\r
 LOCATE 1, 1\r
 COLOR 1\r
 PRINT "HEADER"\r
@@ -444,187 +453,196 @@ NEXT x
 END SUB\r
 \r
 SUB displaySlide2\r
 END SUB\r
 \r
 SUB displaySlide2\r
+' Slide 2: "Goal:" text layout test slide\r
 \r
 CLS\r
 \r
 CLS\r
-pal 4\r
-mkback\r
+setPalette 4\r
+makeBackground\r
 \r
 \r
-pal2 40, 64, 63\r
-pal3 0, 0, 0\r
+setBoxPalette1 40, 64, 63\r
+setBoxPalette2 0, 0, 0\r
 \r
 \r
-box1 30, 30, 290, 170, 1\r
+drawShadedBox 30, 30, 290, 170, 1\r
 \r
 \r
-prin 65, 50, 3, 0, "Goal:"\r
+printText 65, 50, 3, 0, "Goal:"\r
 \r
 \r
-prin 40, 100, 1, 0, CHR$(254) + " Random text"\r
-prin 40, 108, 1, 0, "  goes here to test"\r
-prin 40, 116, 1, 0, "  text layout."\r
-prin 40, 130, 1, 0, CHR$(254) + " Testing 123."\r
+printText 40, 100, 1, 0, CHR$(254) + " Random text"\r
+printText 40, 108, 1, 0, "  goes here to test"\r
+printText 40, 116, 1, 0, "  text layout."\r
+printText 40, 130, 1, 0, CHR$(254) + " Testing 123."\r
 \r
 \r
-inpur\r
+waitKeypress\r
 \r
 END SUB\r
 \r
 SUB displaySlide3\r
 \r
 END SUB\r
 \r
 SUB displaySlide3\r
-mkback\r
+' Slide 3: closing "Thank you for attention" slide\r
+makeBackground\r
 \r
 \r
-pal2 64, 64, 0\r
-box1 30, 30, 290, 150, 1\r
+setBoxPalette1 64, 64, 0\r
+drawShadedBox 30, 30, 290, 150, 1\r
 \r
 \r
-prin 57, 50, 3, 0, "Thank you"\r
-prin 45, 74, 3, 0, "   for"\r
-prin 45, 98, 3, 0, "attention"\r
-inpur\r
+printText 57, 50, 3, 0, "Thank you"\r
+printText 45, 74, 3, 0, "   for"\r
+printText 45, 98, 3, 0, "attention"\r
+waitKeypress\r
 \r
 END SUB\r
 \r
 SUB displaySlide4\r
 \r
 END SUB\r
 \r
 SUB displaySlide4\r
+' Slide 4: sample content slide with header box and bullet list\r
 \r
 \r
-pal 4\r
-mkback\r
+setPalette 4\r
+makeBackground\r
 \r
 \r
-pal2 0, 0, 32\r
-box1 3, 3, 260, 50, 1\r
+setBoxPalette1 0, 0, 32\r
+drawShadedBox 3, 3, 260, 50, 1\r
 \r
 \r
-pal4 255, 50, 50, 0\r
-prin 10, 10, 2, 255, "Random header"\r
-prin 50, 30, 1, 255, "www.randomsite.org"\r
+setPaletteColor 255, 50, 50, 0\r
+printText 10, 10, 2, 255, "Random header"\r
+printText 50, 30, 1, 255, "www.randomsite.org"\r
 \r
 \r
-pal3 10, 20, 0\r
-box1 20, 40, 290, 180, 2\r
+setBoxPalette2 10, 20, 0\r
+drawShadedBox 20, 40, 290, 180, 2\r
 \r
 \r
-pal4 254, 63, 45, 0\r
+setPaletteColor 254, 63, 45, 0\r
 \r
 b = 25\r
 \r
 b = 25\r
-prin 40, 60, 1, 254, CHR$(254) + " Lorem ipsum dolor sit amet,"\r
+printText 40, 60, 1, 254, CHR$(254) + " Lorem ipsum dolor sit amet,"\r
 a = b\r
 a = b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " consectetur adipiscing elit,"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " consectetur adipiscing elit,"\r
 a = a + b\r
 a = a + b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " sed do eiusmod"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " sed do eiusmod"\r
 a = a + b\r
 a = a + b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " tempor incididunt ut labore"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " tempor incididunt ut labore"\r
 \r
 \r
-inpur\r
+waitKeypress\r
 \r
 END SUB\r
 \r
 SUB displaySlide5\r
 \r
 END SUB\r
 \r
 SUB displaySlide5\r
-pal 4\r
-mkback\r
+' Slide 5: sample content slide with header box and bullet list\r
+setPalette 4\r
+makeBackground\r
 \r
 \r
-pal2 0, 0, 32\r
-box1 3, 3, 300, 50, 1\r
+setBoxPalette1 0, 0, 32\r
+drawShadedBox 3, 3, 300, 50, 1\r
 \r
 \r
-pal4 255, 50, 50, 0\r
-prin 10, 10, 2, 255, "Totam rem aperiam"\r
-prin 50, 30, 1, 255, "www.randomurl.org"\r
+setPaletteColor 255, 50, 50, 0\r
+printText 10, 10, 2, 255, "Totam rem aperiam"\r
+printText 50, 30, 1, 255, "www.randomurl.org"\r
 \r
 \r
-pal3 20, 32, 63\r
-box1 20, 40, 290, 180, 2\r
+setBoxPalette2 20, 32, 63\r
+drawShadedBox 20, 40, 290, 180, 2\r
 \r
 \r
-pal4 254, 63, 45, 0\r
+setPaletteColor 254, 63, 45, 0\r
 b = 25\r
 b = 25\r
-prin 40, 60, 1, 254, CHR$(254) + " Nemo enim ipsam voluptatem"\r
+printText 40, 60, 1, 254, CHR$(254) + " Nemo enim ipsam voluptatem"\r
 a = b\r
 a = b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " Sed quia consequuntur"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " Sed quia consequuntur"\r
 a = a + b\r
 a = a + b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " Magni dolores eos"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " Magni dolores eos"\r
 a = a + b\r
 a = a + b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " Qui ratione voluptatem"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " Qui ratione voluptatem"\r
 \r
 \r
-inpur\r
+waitKeypress\r
 \r
 END SUB\r
 \r
 SUB displaySlide6\r
 \r
 END SUB\r
 \r
 SUB displaySlide6\r
-pal 4\r
-mkback\r
+' Slide 6: sample content slide with header box and bullet list\r
+setPalette 4\r
+makeBackground\r
 \r
 \r
-pal2 0, 0, 32\r
-box1 3, 3, 300, 50, 1\r
+setBoxPalette1 0, 0, 32\r
+drawShadedBox 3, 3, 300, 50, 1\r
 \r
 \r
-pal4 255, 50, 50, 0\r
-prin 10, 10, 2, 255, "H E L L O !"\r
-prin 50, 30, 1, 255, "www.hello.net"\r
+setPaletteColor 255, 50, 50, 0\r
+printText 10, 10, 2, 255, "H E L L O !"\r
+printText 50, 30, 1, 255, "www.hello.net"\r
 \r
 \r
-pal3 30, 20, 10\r
-box1 20, 40, 290, 180, 2\r
+setBoxPalette2 30, 20, 10\r
+drawShadedBox 20, 40, 290, 180, 2\r
 \r
 \r
-pal4 254, 63, 45, 0\r
+setPaletteColor 254, 63, 45, 0\r
 b = 25\r
 b = 25\r
-prin 40, 60, 1, 254, CHR$(254) + " Quis autem vel eum"\r
+printText 40, 60, 1, 254, CHR$(254) + " Quis autem vel eum"\r
 a = b\r
 a = b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " Iure reprehenderit qui"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " Iure reprehenderit qui"\r
 a = a + b\r
 a = a + b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " In ea voluptate velit"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " In ea voluptate velit"\r
 a = a + b\r
 a = a + b\r
-prin 40, 60 + a, 1, 254, CHR$(254) + " Esse quam nihil molestiae"\r
+printText 40, 60 + a, 1, 254, CHR$(254) + " Esse quam nihil molestiae"\r
 \r
 \r
-inpur\r
+waitKeypress\r
 \r
 END SUB\r
 \r
 \r
 END SUB\r
 \r
-SUB inke (a$)\r
-IF tim$ <> TIME$ THEN\r
-ink = ink - 1\r
-tim$ = TIME$\r
+SUB pollKey (key$)\r
+' Returns key$ = " " when a key was pressed (or the idle timer ran out in timer mode), "" otherwise\r
+IF lastTime$ <> TIME$ THEN\r
+idleCountdown = idleCountdown - 1\r
+lastTime$ = TIME$\r
 END IF\r
 END IF\r
-IF (ink <= 0) AND (tmr = 1) THEN a$ = " " ELSE a$ = ""\r
-IF INKEY$ <> "" THEN a$ = " "\r
+IF (idleCountdown <= 0) AND (autoAdvance = 1) THEN key$ = " " ELSE key$ = ""\r
+IF INKEY$ <> "" THEN key$ = " "\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB inpur\r
-setink 10\r
+SUB waitKeypress\r
+' Waits until a key is pressed (or the idle auto-advance timer expires)\r
+setIdleTimeout 10\r
 11\r
 11\r
-inke a$\r
+pollKey a$\r
 IF a$ = "" THEN GOTO 11\r
 END SUB\r
 \r
 DEFINT A-Z\r
 IF a$ = "" THEN GOTO 11\r
 END SUB\r
 \r
 DEFINT A-Z\r
-SUB mkback\r
+SUB makeBackground\r
+' Generates a random plasma-style background via recursive midpoint displacement\r
 CLS\r
 CLS\r
-lm1 = 0\r
-lm2 = 50\r
+shadeMin = 0\r
+shadeMax = 50\r
 \r
 \r
-s = 2 ^ 7\r
+cellSize = 2 ^ 7\r
 \r
 7\r
 \r
 7\r
-s = s \ 2\r
+cellSize = cellSize \ 2\r
 \r
 \r
-FOR y = 0 TO 199 STEP s\r
-FOR x = 0 TO 319 STEP s\r
+FOR y = 0 TO 199 STEP cellSize\r
+FOR x = 0 TO 319 STEP cellSize\r
 \r
 c1 = POINT(x, y)\r
 \r
 c1 = POINT(x, y)\r
-c2 = POINT(x + s, y)\r
-c3 = POINT(x, y + s)\r
-c4 = POINT(x + s, y + s)\r
+c2 = POINT(x + cellSize, y)\r
+c3 = POINT(x, y + cellSize)\r
+c4 = POINT(x + cellSize, y + cellSize)\r
 \r
 \r
-sp = s \ 2\r
+halfCell = cellSize \ 2\r
 \r
 \r
-c5 = (c1 + c2 + c3 + c4) / 4 + RND * s - sp\r
-IF c5 > lm2 THEN c5 = lm2\r
-IF c5 < lm1 THEN c5 = lm1\r
+c5 = (c1 + c2 + c3 + c4) / 4 + RND * cellSize - halfCell\r
+IF c5 > shadeMax THEN c5 = shadeMax\r
+IF c5 < shadeMin THEN c5 = shadeMin\r
 \r
 \r
-c6 = (c2 + c4) / 2 + RND * s - sp\r
-IF c6 > lm2 THEN c6 = lm2\r
-IF c6 < lm1 THEN c6 = lm1\r
+c6 = (c2 + c4) / 2 + RND * cellSize - halfCell\r
+IF c6 > shadeMax THEN c6 = shadeMax\r
+IF c6 < shadeMin THEN c6 = shadeMin\r
 \r
 \r
-c7 = (c3 + c4) / 2 + RND * s - sp\r
-IF c7 > lm2 THEN c7 = lm2\r
-IF c7 < lm1 THEN c7 = lm1\r
+c7 = (c3 + c4) / 2 + RND * cellSize - halfCell\r
+IF c7 > shadeMax THEN c7 = shadeMax\r
+IF c7 < shadeMin THEN c7 = shadeMin\r
 \r
 IF INT(RND * 30) = 2 THEN c5 = 50\r
 \r
 IF INT(RND * 30) = 2 THEN c5 = 50\r
-PSET (x + sp, y + sp), c5\r
-PSET (x + s, y + sp), c6\r
-PSET (x + sp, y + s), c7\r
+PSET (x + halfCell, y + halfCell), c5\r
+PSET (x + cellSize, y + halfCell), c6\r
+PSET (x + halfCell, y + cellSize), c7\r
 \r
 NEXT x\r
 NEXT y\r
 \r
 NEXT x\r
 NEXT y\r
-IF s > 2 THEN GOTO 7\r
+IF cellSize > 2 THEN GOTO 7\r
 END SUB\r
 \r
 DEFSNG A-Z\r
 END SUB\r
 \r
 DEFSNG A-Z\r
-SUB mkfont\r
+SUB makeFont\r
+' Captures the ROM 8x8 pixel patterns of all 256 characters into the fontBitmap array\r
 SCREEN 13\r
 FOR a = 0 TO 255\r
 LOCATE 1, 1\r
 SCREEN 13\r
 FOR a = 0 TO 255\r
 LOCATE 1, 1\r
@@ -632,75 +650,77 @@ IF a <> 7 THEN PRINT CHR$(a)
 \r
 FOR y = 0 TO 7\r
 FOR x = 0 TO 7\r
 \r
 FOR y = 0 TO 7\r
 FOR x = 0 TO 7\r
-fontt(x, y, a) = POINT(x, y)\r
+fontBitmap(x, y, a) = POINT(x, y)\r
 NEXT x\r
 NEXT y\r
 NEXT a\r
 \r
 END SUB\r
 \r
 NEXT x\r
 NEXT y\r
 NEXT a\r
 \r
 END SUB\r
 \r
-SUB mkjuku (x, y, a, c)\r
-jas(a) = jas(a) + .08\r
-IF jas(a) > 30000 THEN jas(a) = 0\r
-b = jas(a)\r
-IF c = 0 THEN b = jas(a) - .08\r
-x1 = x + COS(b) * 10\r
-y1 = y + SIN(b) * 5 + 20\r
+SUB drawBoy (x, y, phaseIndex, shade)\r
+' Draws an animated stick-figure boy ("Juku") with legs, arms and head at x, y; shade 0 erases\r
+animPhase(phaseIndex) = animPhase(phaseIndex) + .08\r
+IF animPhase(phaseIndex) > 30000 THEN animPhase(phaseIndex) = 0\r
+angle = animPhase(phaseIndex)\r
+IF shade = 0 THEN angle = animPhase(phaseIndex) - .08\r
+x1 = x + COS(angle) * 10\r
+y1 = y + SIN(angle) * 5 + 20\r
 \r
 \r
-x2 = x + COS(b) * 5 + 2\r
-y2 = y + SIN(b) * 3 + 10\r
+x2 = x + COS(angle) * 5 + 2\r
+y2 = y + SIN(angle) * 3 + 10\r
 \r
 \r
-x3 = x + COS(b + 1) * 2\r
-y3 = y + SIN(b + 1) * 2 + 2\r
+x3 = x + COS(angle + 1) * 2\r
+y3 = y + SIN(angle + 1) * 2 + 2\r
 \r
 \r
-LINE (x2, y2)-(x1, y1), c\r
-LINE (x2, y2)-(x3, y3), c\r
+LINE (x2, y2)-(x1, y1), shade\r
+LINE (x2, y2)-(x3, y3), shade\r
 \r
 \r
-x1 = x + COS(b + pii) * 10\r
-y1 = y + SIN(b + pii) * 5 + 20\r
+x1 = x + COS(angle + piValue) * 10\r
+y1 = y + SIN(angle + piValue) * 5 + 20\r
 \r
 \r
-x2 = x + COS(b + pii) * 5 + 2\r
-y2 = y + SIN(b + pii) * 3 + 10\r
+x2 = x + COS(angle + piValue) * 5 + 2\r
+y2 = y + SIN(angle + piValue) * 3 + 10\r
 \r
 \r
-LINE (x2, y2)-(x1, y1), c\r
-LINE (x2, y2)-(x3, y3), c\r
+LINE (x2, y2)-(x1, y1), shade\r
+LINE (x2, y2)-(x3, y3), shade\r
 \r
 \r
-x4 = x + COS(b + 1.2) * 3 - 1\r
-y4 = y + SIN(b + 1.2) * 1 - 10\r
+x4 = x + COS(angle + 1.2) * 3 - 1\r
+y4 = y + SIN(angle + 1.2) * 1 - 10\r
 \r
 \r
-LINE (x4, y4)-(x3, y3), c\r
+LINE (x4, y4)-(x3, y3), shade\r
 \r
 \r
-x5 = x + COS(b + .5) * 13 - 3\r
-y5 = y + SIN(b + .5) * 2 + 1\r
+x5 = x + COS(angle + .5) * 13 - 3\r
+y5 = y + SIN(angle + .5) * 2 + 1\r
 \r
 \r
-x6 = x + COS(b + .5) * 15 - 1\r
-y6 = y + SIN(b + .5) * 3 + 4\r
+x6 = x + COS(angle + .5) * 15 - 1\r
+y6 = y + SIN(angle + .5) * 3 + 4\r
 \r
 \r
-LINE (x5, y5)-(x4, y4), c\r
-LINE (x5, y5)-(x6, y6), c\r
+LINE (x5, y5)-(x4, y4), shade\r
+LINE (x5, y5)-(x6, y6), shade\r
 \r
 \r
-x5 = x + COS(b + pii) * 13 - 3\r
-y5 = y + SIN(b + pii) * 2 + 1\r
+x5 = x + COS(angle + piValue) * 13 - 3\r
+y5 = y + SIN(angle + piValue) * 2 + 1\r
 \r
 \r
-x6 = x + COS(b + pii) * 15 - 1\r
-y6 = y + SIN(b + pii) * 3 + 4\r
+x6 = x + COS(angle + piValue) * 15 - 1\r
+y6 = y + SIN(angle + piValue) * 3 + 4\r
 \r
 \r
-LINE (x5, y5)-(x4, y4), c\r
-LINE (x5, y5)-(x6, y6), c\r
+LINE (x5, y5)-(x4, y4), shade\r
+LINE (x5, y5)-(x6, y6), shade\r
 \r
 \r
-x7 = x + COS(b + 1.2) * 2\r
-y7 = y + SIN(b + 1.2) * 1 - 14\r
+x7 = x + COS(angle + 1.2) * 2\r
+y7 = y + SIN(angle + 1.2) * 1 - 14\r
 \r
 \r
-LINE (x7, y7 + 2)-(x4, y4), c\r
+LINE (x7, y7 + 2)-(x4, y4), shade\r
 \r
 \r
-CIRCLE (x7, y7), 3, c\r
+CIRCLE (x7, y7), 3, shade\r
 \r
 \r
 \r
 END SUB\r
 \r
 \r
 \r
 \r
 END SUB\r
 \r
-SUB pal (x)\r
-SELECT CASE x\r
+SUB setPalette (scheme)\r
+' Loads one of four preset VGA palette schemes (1 = gray ramp, 2/4 = blue ramps, 3 = sine colors)\r
+SELECT CASE scheme\r
 CASE 1\r
 \r
 FOR f = 0 TO 25\r
 CASE 1\r
 \r
 FOR f = 0 TO 25\r
@@ -746,71 +766,77 @@ END SELECT
 \r
 END SUB\r
 \r
 \r
 END SUB\r
 \r
-SUB pal2 (r, g, b)\r
+SUB setBoxPalette1 (red, green, blue)\r
+' Loads palette entries 51-101: shaded-box gradient (type 1) tinted toward the given base color\r
 FOR f = 0 TO 25\r
 OUT &H3C8, f + 51\r
 FOR f = 0 TO 25\r
 OUT &H3C8, f + 51\r
-OUT &H3C9, (f * 2.5 + r * 1) / 2\r
-OUT &H3C9, (f * 2.5 + g * 1) / 2\r
-OUT &H3C9, (f * 1.5 + b * 1) / 2\r
+OUT &H3C9, (f * 2.5 + red * 1) / 2\r
+OUT &H3C9, (f * 2.5 + green * 1) / 2\r
+OUT &H3C9, (f * 1.5 + blue * 1) / 2\r
 NEXT f\r
 FOR f = 26 TO 50\r
 OUT &H3C8, f + 51\r
 NEXT f\r
 FOR f = 26 TO 50\r
 OUT &H3C8, f + 51\r
-OUT &H3C9, ((50 - f) * 2.5 + r * 1) / 2\r
-OUT &H3C9, ((50 - f) * 2.5 + g * 1) / 2\r
-OUT &H3C9, ((50 - f) * 1.5 + b * 1) / 2\r
+OUT &H3C9, ((50 - f) * 2.5 + red * 1) / 2\r
+OUT &H3C9, ((50 - f) * 2.5 + green * 1) / 2\r
+OUT &H3C9, ((50 - f) * 1.5 + blue * 1) / 2\r
 NEXT f\r
 END SUB\r
 \r
 NEXT f\r
 END SUB\r
 \r
-SUB pal3 (r, g, b)\r
+SUB setBoxPalette2 (red, green, blue)\r
+' Loads palette entries 102-152: shaded-box gradient (type 2) tinted toward the given base color\r
 FOR f = 0 TO 25\r
 OUT &H3C8, f + 102\r
 FOR f = 0 TO 25\r
 OUT &H3C8, f + 102\r
-OUT &H3C9, (f * 2.5 + r * 1) / 2\r
-OUT &H3C9, (f * 2.5 + g * 1) / 2\r
-OUT &H3C9, (f * 1.5 + b * 1) / 2\r
+OUT &H3C9, (f * 2.5 + red * 1) / 2\r
+OUT &H3C9, (f * 2.5 + green * 1) / 2\r
+OUT &H3C9, (f * 1.5 + blue * 1) / 2\r
 NEXT f\r
 FOR f = 26 TO 50\r
 OUT &H3C8, f + 102\r
 NEXT f\r
 FOR f = 26 TO 50\r
 OUT &H3C8, f + 102\r
-OUT &H3C9, ((50 - f) * 2.5 + r * 1) / 2\r
-OUT &H3C9, ((50 - f) * 2.5 + g * 1) / 2\r
-OUT &H3C9, ((50 - f) * 1.5 + b * 1) / 2\r
+OUT &H3C9, ((50 - f) * 2.5 + red * 1) / 2\r
+OUT &H3C9, ((50 - f) * 2.5 + green * 1) / 2\r
+OUT &H3C9, ((50 - f) * 1.5 + blue * 1) / 2\r
 NEXT f\r
 END SUB\r
 \r
 NEXT f\r
 END SUB\r
 \r
-SUB pal4 (c, r!, g!, b!)\r
-OUT &H3C8, c\r
+SUB setPaletteColor (paletteIndex, r!, g!, b!)\r
+' Sets one VGA palette entry to the given red/green/blue components (0-63)\r
+OUT &H3C8, paletteIndex\r
 OUT &H3C9, r\r
 OUT &H3C9, g\r
 OUT &H3C9, b\r
 END SUB\r
 \r
 OUT &H3C9, r\r
 OUT &H3C9, g\r
 OUT &H3C9, b\r
 END SUB\r
 \r
-SUB pr (x, y, s, c, n, a$)\r
-IF n > LEN(a$) THEN GOTO 10\r
-a$ = RIGHT$(LEFT$(a$, n), 1)\r
-x1 = n * 8 * s + x\r
-prin x1, y, s, c, a$\r
+SUB printNthChar (x, y, scale, shade, charIndex, text$)\r
+' Prints only the charIndex-th character of text$ (used for gradual text reveal)\r
+IF charIndex > LEN(text$) THEN GOTO 10\r
+text$ = RIGHT$(LEFT$(text$, charIndex), 1)\r
+x1 = charIndex * 8 * scale + x\r
+printText x1, y, scale, shade, text$\r
 10\r
 END SUB\r
 \r
 10\r
 END SUB\r
 \r
-SUB pri (x, y, a$, c)\r
-COLOR c\r
-FOR a = 1 TO LEN(a$)\r
-b$ = RIGHT$(LEFT$(a$, a), 1)\r
+SUB printTypewriter (x, y, text$, shade)\r
+' Prints text$ character by character with a delay (typewriter effect)\r
+COLOR shade\r
+FOR a = 1 TO LEN(text$)\r
+b$ = RIGHT$(LEFT$(text$, a), 1)\r
 LOCATE y, x + a\r
 PRINT b$\r
 LOCATE y, x + a\r
 PRINT b$\r
-SOUND 0, 1\r
+SOUND 0, 1 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 NEXT a\r
 \r
 END SUB\r
 \r
 NEXT a\r
 \r
 END SUB\r
 \r
-SUB prin (x1, y1, s, c1, a$)\r
+SUB printText (xPos, yPos, scale, shade, text$)\r
+' Prints text$ at a pixel position using the captured bitmap font, scaled by 'scale', in palette shade\r
 \r
 \r
-FOR a = 1 TO LEN(a$)\r
-b = ASC(RIGHT$(LEFT$(a$, a), 1))\r
-c = (a - 1) * 8 * s + x1\r
+FOR a = 1 TO LEN(text$)\r
+b = ASC(RIGHT$(LEFT$(text$, a), 1))\r
+c = (a - 1) * 8 * scale + xPos\r
 FOR y = 0 TO 7\r
 FOR x = 0 TO 7\r
 FOR y = 0 TO 7\r
 FOR x = 0 TO 7\r
-IF fontt(x, y, b) > 0 THEN\r
-LINE (x * s + c, y * s + y1)-(x * s + s - 1 + c, y * s + s - 1 + y1), c1, BF\r
+IF fontBitmap(x, y, b) > 0 THEN\r
+LINE (x * scale + c, y * scale + yPos)-(x * scale + scale - 1 + c, y * scale + scale - 1 + yPos), shade, BF\r
 END IF\r
 NEXT x\r
 NEXT y\r
 END IF\r
 NEXT x\r
 NEXT y\r
@@ -819,7 +845,8 @@ NEXT a
 \r
 END SUB\r
 \r
 \r
 END SUB\r
 \r
-SUB resiz\r
+SUB splitTransition\r
+' Transition effect: expanding circle, then the screen splits apart horizontally and vertically\r
 \r
 FOR a = 1 TO 10\r
 CIRCLE (160, 100), a, a * 2 + 5\r
 \r
 FOR a = 1 TO 10\r
 CIRCLE (160, 100), a, a * 2 + 5\r
@@ -836,7 +863,7 @@ GET (160, 90)-(160 + a, 110), buff2(1)
 PUT (159 - a, 90), buff1(1), PSET\r
 PUT (150 + a, 90), buff2(1), PSET\r
 a = a + 1\r
 PUT (159 - a, 90), buff1(1), PSET\r
 PUT (150 + a, 90), buff2(1), PSET\r
 a = a + 1\r
-SOUND 0, .2\r
+SOUND 0, .2 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 IF a < 140 THEN GOTO 5\r
 \r
 a = 1\r
 IF a < 140 THEN GOTO 5\r
 \r
 a = 1\r
@@ -848,46 +875,49 @@ PUT (20, 90 - a), buff1(1), PSET
 PUT (20, 100 + a), buff2(1), PSET\r
 \r
 a = a + 1\r
 PUT (20, 100 + a), buff2(1), PSET\r
 \r
 a = a + 1\r
-SOUND 0, .2\r
+SOUND 0, .2 ' sub-second delay workaround (inaudible 0 Hz sound)\r
 IF a < 60 THEN GOTO 6\r
 END SUB\r
 \r
 IF a < 60 THEN GOTO 6\r
 END SUB\r
 \r
-SUB setink (a!)\r
-ink = a\r
-tim$ = TIME$\r
+SUB setIdleTimeout (seconds!)\r
+' Sets the idle countdown (in seconds) before auto-advance and stamps the current time\r
+idleCountdown = seconds\r
+lastTime$ = TIME$\r
 END SUB\r
 \r
 SUB start\r
 END SUB\r
 \r
 SUB start\r
+' Initializes screen, font, random animation phases and the optional auto-advance timer mode\r
 SCREEN 13\r
 RANDOMIZE TIMER\r
 \r
 SCREEN 13\r
 RANDOMIZE TIMER\r
 \r
-mkfont\r
-tim = 0\r
-tim2 = 0\r
+makeFont\r
+revealCounter = 0\r
+revealStage = 0\r
 \r
 FOR a = 1 TO 500\r
 \r
 FOR a = 1 TO 500\r
-jas(a) = RND * 10\r
+animPhase(a) = RND * 10\r
 NEXT a\r
 \r
 NEXT a\r
 \r
-pii = 3.14\r
+piValue = 3.14\r
 IF COMMAND$ = "t" OR COMMAND$ = "T" THEN\r
 IF COMMAND$ = "t" OR COMMAND$ = "T" THEN\r
-tmr = 1\r
+autoAdvance = 1\r
 PRINT "timer is on"\r
 SLEEP 1\r
 ELSE\r
 PRINT "timer is on"\r
 SLEEP 1\r
 ELSE\r
-tmr = 0\r
+autoAdvance = 0\r
 END IF\r
 END SUB\r
 \r
 END IF\r
 END SUB\r
 \r
-SUB wpr\r
-tim = tim + 1\r
-IF tim \ 10 = tim / 10 THEN\r
-a = tim / 10\r
-SELECT CASE tim2\r
+SUB updateReveal\r
+' Counts effect ticks and reveals the next character of the "Sources:" text every 10 ticks\r
+revealCounter = revealCounter + 1\r
+IF revealCounter \ 10 = revealCounter / 10 THEN\r
+a = revealCounter / 10\r
+SELECT CASE revealStage\r
 CASE 0\r
 CASE 0\r
-IF a = 10 THEN tim2 = 1: tim = 0: pal4 255, 63, 45, 0\r
+IF a = 10 THEN revealStage = 1: revealCounter = 0: setPaletteColor 255, 63, 45, 0\r
 CASE 1\r
 CASE 1\r
-pr 10, 10, 2, 255, a, "Sources:"\r
+printNthChar 10, 10, 2, 255, a, "Sources:"\r
 \r
 END SELECT\r
 END IF\r
 \r
 END SELECT\r
 END IF\r
index ab2f8c9..f58979e 100755 (executable)
@@ -8,6 +8,7 @@
 ' Changelog:
 ' 2003, Initial version.
 ' 2024, Improved program readability.
 ' Changelog:
 ' 2003, Initial version.
 ' 2024, Improved program readability.
+' 2026, Improved program readability.
 '
 ' User can navigate through the galaxy using mouse or keyboard controls.
 '
 '
 ' User can navigate through the galaxy using mouse or keyboard controls.
 '
 ' - Use keyboard keys 'a', 'd', 'w', 's' to move in X and Z axis.
 ' - Press 'q' to quit the program.
 
 ' - Use keyboard keys 'a', 'd', 'w', 's' to move in X and Z axis.
 ' - Press 'q' to quit the program.
 
+' temp: leftover declaration of a long-gone helper; the SUB no longer exists.
 DECLARE SUB temp ()
 DECLARE SUB mkGalaxy (x!, y!, z!)
 DECLARE SUB rndInit ()
 DECLARE SUB temp ()
 DECLARE SUB mkGalaxy (x!, y!, z!)
 DECLARE SUB rndInit ()
-DECLARE FUNCTION rn! ()
-DECLARE SUB disp ()
+DECLARE FUNCTION nextRandom! ()
+DECLARE SUB drawGalaxy ()
 DECLARE SUB control ()
 DECLARE SUB putByte (addr!, dat!)
 DECLARE SUB putWord (addr!, dat!)
 DECLARE SUB control ()
 DECLARE SUB putByte (addr!, dat!)
 DECLARE SUB putWord (addr!, dat!)
@@ -75,12 +77,14 @@ currentZ = 0
 
 numPoints = 0
 mkGalaxy 0, 0, 0
 
 numPoints = 0
 mkGalaxy 0, 0, 0
+' Main loop: steer the camera, redraw the galaxy, flip the page.
 1
 
 1
 
-' Generate a random angle for initial camera position
-variableAngle = INT(RND * 3)
+' Harmless leftover: each frame one of currentX/Y/Z gets a random offset,
+' but these variables are never read anywhere.
+startAxis = INT(RND * 3)
 
 
-SELECT CASE variableAngle
+SELECT CASE startAxis
 CASE 0
     currentX = RND * 500 - 250
 CASE 1
 CASE 0
     currentX = RND * 500 - 250
 CASE 1
@@ -90,13 +94,15 @@ CASE 2
 END SELECT
 
 control
 END SELECT
 
 control
-disp
+drawGalaxy
 
 
+' Show the freshly drawn frame (page 0 -> visible page 1), then clear page 0.
 PCOPY 0, 1
 CLS
 GOTO 1
 
 SUB control
 PCOPY 0, 1
 CLS
 GOTO 1
 
 SUB control
+' Read the TSR mouse driver and WASD keys; accelerate/decelerate the camera.
 
 ' Read mouse input from the driver, resetting its counters every frame
 xPosition = getWord(2)
 
 ' Read mouse input from the driver, resetting its counters every frame
 xPosition = getWord(2)
@@ -157,7 +163,8 @@ myY = myY + speedY
 
 END SUB
 
 
 END SUB
 
-SUB disp
+SUB drawGalaxy
+' Rotate every star by the camera angles, perspective-project and plot it.
 
 ' Calculate sine and cosine values for rotation angles
 sinAngle1 = SIN(angle1)
 
 ' Calculate sine and cosine values for rotation angles
 sinAngle1 = SIN(angle1)
@@ -191,10 +198,12 @@ NEXT pointIndex
 END SUB
 
 FUNCTION getByte (address)
 END SUB
 
 FUNCTION getByte (address)
+' Read one byte from the TSR driver memory.
 getByte = PEEK(externalAddress + address)
 END FUNCTION
 
 FUNCTION getWord (address)
 getByte = PEEK(externalAddress + address)
 END FUNCTION
 
 FUNCTION getWord (address)
+' Read one 16-bit little-endian word from the TSR driver memory.
 firstByte = PEEK(externalAddress + address)
 secondByte = PEEK(externalAddress + address + 1)
 
 firstByte = PEEK(externalAddress + address)
 secondByte = PEEK(externalAddress + address + 1)
 
@@ -209,10 +218,11 @@ getWord = combinedValue
 END FUNCTION
 
 SUB mkGalaxy (localX, localY, localZ)
 END FUNCTION
 
 SUB mkGalaxy (localX, localY, localZ)
+' Generate the spiral galaxy point cloud centered at (localX, localY, localZ).
 
 ' Generate random angles for stars within galaxy
 
 ' Generate random angles for stars within galaxy
-randomAngle1 = rn * 10
-randomAngle2 = rn * 10
+randomAngle1 = nextRandom * 10
+randomAngle2 = nextRandom * 10
 
 galaxySin1 = SIN(randomAngle1)
 galaxyCos1 = COS(randomAngle1)
 
 galaxySin1 = SIN(randomAngle1)
 galaxyCos1 = COS(randomAngle1)
@@ -227,21 +237,21 @@ spiralBarsMultiplier = 3
 FOR pointIndex = 1 TO 10000
 
     ' Generate a random star distance from the center
 FOR pointIndex = 1 TO 10000
 
     ' Generate a random star distance from the center
-    randomVariable = rn * 10
+    randomVariable = nextRandom * 10
     distanceFromCenter = randomVariable * randomVariable / 30
 
     ' Calculate spiral offset and half of it
     distanceFromCenter = randomVariable * randomVariable / 30
 
     ' Calculate spiral offset and half of it
-    spiralOffset = rn * (11.5 - randomVariable) / 3
+    spiralOffset = nextRandom * (11.5 - randomVariable) / 3
     halfSpiralOffset = spiralOffset / 2
 
     ' Generate angle exponent and spiral bar angle
     halfSpiralOffset = spiralOffset / 2
 
     ' Generate angle exponent and spiral bar angle
-    angleExponent = rn * (distanceFromCenter / 2) / spiralBarsMultiplier * 2
-    spiralBarAngle = 2 * piValue / spiralBarsMultiplier * INT(rn * spiralBarsMultiplier)
+    angleExponent = nextRandom * (distanceFromCenter / 2) / spiralBarsMultiplier * 2
+    spiralBarAngle = 2 * piValue / spiralBarsMultiplier * INT(nextRandom * spiralBarsMultiplier)
 
     ' Calculate x, z, and y coordinates
 
     ' Calculate x, z, and y coordinates
-    xCoordinate = (SIN(randomVariable - spiralBarAngle + angleExponent) * distanceFromCenter + rn * spiralOffset - halfSpiralOffset) * size
-    zCoordinate = (COS(randomVariable - spiralBarAngle + angleExponent) * distanceFromCenter + rn * spiralOffset - halfSpiralOffset) * size
-    yCoordinate = (rn * spiralOffset - halfSpiralOffset) * size
+    xCoordinate = (SIN(randomVariable - spiralBarAngle + angleExponent) * distanceFromCenter + nextRandom * spiralOffset - halfSpiralOffset) * size
+    zCoordinate = (COS(randomVariable - spiralBarAngle + angleExponent) * distanceFromCenter + nextRandom * spiralOffset - halfSpiralOffset) * size
+    yCoordinate = (nextRandom * spiralOffset - halfSpiralOffset) * size
 
     ' Rotate the coordinates
     rotatedX = xCoordinate * galaxyCos1 + zCoordinate * galaxySin1
 
     ' Rotate the coordinates
     rotatedX = xCoordinate * galaxyCos1 + zCoordinate * galaxySin1
@@ -262,11 +272,13 @@ NEXT pointIndex
 END SUB
 
 SUB putByte (address, dataByte)
 END SUB
 
 SUB putByte (address, dataByte)
+' Write one byte into the TSR driver memory.
 
 POKE (externalAddress + address), dataByte
 END SUB
 
 SUB putWord (address, dataWord)
 
 POKE (externalAddress + address), dataByte
 END SUB
 
 SUB putWord (address, dataWord)
+' Write one 16-bit word into the TSR driver memory (little-endian).
 
 ' Convert the word to a hexadecimal string
 hexValue$ = HEX$(dataWord)
 
 ' Convert the word to a hexadecimal string
 hexValue$ = HEX$(dataWord)
@@ -287,16 +299,18 @@ POKE (externalAddress + address + 1), firstByteValue
 
 END SUB
 
 
 END SUB
 
-FUNCTION rn
+FUNCTION nextRandom
+' Return the next value from the pre-generated random table.
 
 ' Increment the random pointer and get a new random value
 randomPointer = randomPointer + 1
 IF randomPointer > 10000 THEN randomPointer = 0
 
 ' Increment the random pointer and get a new random value
 randomPointer = randomPointer + 1
 IF randomPointer > 10000 THEN randomPointer = 0
-rn = randomValue(randomPointer)
+nextRandom = randomValue(randomPointer)
 
 END FUNCTION
 
 SUB rndInit
 
 END FUNCTION
 
 SUB rndInit
+' Fill the random table with 10001 RND values.
 
 ' Initialize the array of random values
 FOR index = 0 TO 10000
 
 ' Initialize the array of random values
 FOR index = 0 TO 10000
@@ -307,8 +321,9 @@ randomPointer = 0
 END SUB
 
 SUB start
 END SUB
 
 SUB start
+' Set up graphics mode and initialize program state.
 
 
-starText
+initMouseDriver
 
 ' Set up graphics mode
 SCREEN 7, , , 1
 
 ' Set up graphics mode
 SCREEN 7, , , 1
@@ -319,7 +334,8 @@ rndInit
 
 END SUB
 
 
 END SUB
 
-SUB starText
+SUB initMouseDriver
+' Locate the TSR mouse driver via the interrupt table and verify it.
 
 ' Read the segment and address from the interrupt table
 DEF SEG = 0     ' read first from interrupt table
 
 ' Read the segment and address from the interrupt table
 DEF SEG = 0     ' read first from interrupt table
@@ -337,6 +353,7 @@ PRINT "relative address is:"; externalAddress
 ' Read the word at the specified address
 DEF SEG = externalSegment
 
 ' Read the word at the specified address
 DEF SEG = externalSegment
 
+' 1983 is the identification magic number of the qbext TSR driver.
 IF getWord(0) <> 1983 THEN
     PRINT "FATAL ERROR: you must load"
     PRINT "QBasic extension TSR first!"
 IF getWord(0) <> 1983 THEN
     PRINT "FATAL ERROR: you must load"
     PRINT "QBasic extension TSR first!"
index d4bdc60..35bedb0 100644 (file)
 /                                   \r
 /#m                               m#\r
 /#m                               m#\r
 /                                   \r
 /#m                               m#\r
 /#m                               m#\r
-/#m        m      mmmm   m        m#\r
-/#m        m  m   m      m        m#\r
-/#m        m  m   m      m        m#\r
-/#m        m  m   m      m        m#\r
+/#m        m      m      m        m#\r
+/#m        m      m      m        m#\r
+/#m        m      m      m        m#\r
+/#m        m      m      m        m#\r
 /#m        m  mmmmmmmmm  m        m#\r
 /#m        m  mmmmmmmmm  m        m#\r
-/#m        m      m   m  m        m#\r
-/#m        m      m   m  m        m#\r
-/#m        m      m   m  m        m#\r
-/#m        m   mmmm      m        m#\r
+/#m        m      m      m        m#\r
+/#m        m      m      m        m#\r
+/#m        m      m      m        m#\r
+/#m        m      m      m        m#\r
 /#m                               m#\r
 /#m                               m#\r
 /                                   \r
 /#m                               m#\r
 /#m                               m#\r
 /                                   \r
index 83a56cc..166bd9c 100755 (executable)
 ' Changelog:\r
 ' 2002, Initial version\r
 ' 2024-2005, Improved program readability\r
 ' Changelog:\r
 ' 2002, Initial version\r
 ' 2024-2005, Improved program readability\r
-\r
-DECLARE FUNCTION cnum$ (a%)\r
-DECLARE SUB putworm (a%)\r
-DECLARE SUB level (a%)\r
-DECLARE SUB showb ()\r
-DECLARE SUB sc2 (x%, y%)\r
-DECLARE SUB ai (a%)\r
-DECLARE SUB autop (a%)\r
-DECLARE SUB prc (a%)\r
-DECLARE SUB dead (a%)\r
+' 2026, Improved program readability\r
+\r
+DECLARE FUNCTION numStr$ (value%)\r
+DECLARE SUB spawnWorm (playerNumber%)\r
+DECLARE SUB startLevel (levelNum%)\r
+DECLARE SUB showDistMap ()\r
+DECLARE SUB debugTrace (x%, y%)\r
+DECLARE SUB planPath (playerNumber%)\r
+DECLARE SUB autopilot (playerNumber%)\r
+DECLARE SUB advanceWorm (a%)\r
+DECLARE SUB handleDeath (playerNumber%)\r
 DECLARE SUB add (a%)\r
 DECLARE SUB add (a%)\r
-DECLARE SUB tkt ()\r
+DECLARE SUB gameTick ()\r
 DECLARE SUB subt (b%)\r
 DECLARE SUB subt (b%)\r
-DECLARE SUB show ()\r
+DECLARE SUB drawArena ()\r
 DECLARE SUB sc (x%, y%)\r
 DEFINT A-Z\r
 \r
 ' Shared arrays for the game grid and worms\r
 DECLARE SUB sc (x%, y%)\r
 DEFINT A-Z\r
 \r
 ' Shared arrays for the game grid and worms\r
-DIM SHARED buf(0 TO 36, 0 TO 36)\r
-DIM SHARED buf2(0 TO 36, 0 TO 36)\r
-DIM SHARED ussx(1 TO 2000, 1 TO 5)\r
-DIM SHARED ussy(1 TO 2000, 1 TO 5)\r
-DIM SHARED ussp(1 TO 5)\r
-DIM SHARED ussl(1 TO 5)\r
-DIM SHARED usss(1 TO 5)\r
-DIM SHARED ussk(1 TO 2000, 1 TO 5)\r
-DIM SHARED usskp(1 TO 5)\r
+DIM SHARED arena(0 TO 36, 0 TO 36)  ' grid cell: 0=empty, 1=wall, 2=fruit, 10+worm\r
+DIM SHARED distMap(0 TO 36, 0 TO 36)  ' AI distance field: 32000=unreached, -1=blocked, 0=fruit, else distance\r
+DIM SHARED trailX(1 TO 2000, 1 TO 5)  ' ring buffer of worm body positions, X coordinate\r
+DIM SHARED trailY(1 TO 2000, 1 TO 5)  ' ring buffer of worm body positions, Y coordinate\r
+DIM SHARED headPtr(1 TO 5)  ' ring buffer index of the worm head\r
+DIM SHARED wormLen(1 TO 5)  ' worm length in grid cells\r
+DIM SHARED score(1 TO 5)  ' player score\r
+DIM SHARED pathPlan(1 TO 2000, 1 TO 5)  ' AI movement plan (direction codes 1-4, 5=replan)\r
+DIM SHARED planPtr(1 TO 5)  ' index of the next step in pathPlan\r
 \r
 ' Variables for worm positions and game state\r
 \r
 ' Variables for worm positions and game state\r
-DIM SHARED ux(1 TO 5), uy(1 TO 5), uxp(1 TO 5), uyp(1 TO 5)\r
-DIM SHARED mtm\r
+DIM SHARED headX(1 TO 5), headY(1 TO 5), dirX(1 TO 5), dirY(1 TO 5)  ' worm head position and direction\r
+DIM SHARED fruitCount  ' fruits eaten on current level (next level at 15)\r
 DIM SHARED playerCount\r
 DIM SHARED lives(1 TO 5)\r
 DIM SHARED isAI(1 TO 5)\r
 DIM SHARED playerCount\r
 DIM SHARED lives(1 TO 5)\r
 DIM SHARED isAI(1 TO 5)\r
-DIM SHARED ail\r
-DIM SHARED lvl\r
-DIM SHARED elum\r
-DIM SHARED spd\r
+DIM SHARED planLimit  ' maximum AI plan length in steps\r
+DIM SHARED levelNum  ' current level number\r
+DIM SHARED startLives  ' lives each worm starts with\r
+DIM SHARED speed  ' game speed (larger = faster)\r
 \r
 playerCount = 1\r
 \r
 playerCount = 1\r
-ail = 10\r
-lvl = 1\r
+planLimit = 10\r
+levelNum = 1\r
 \r
 ' Array defines if player is human (0) or computer (1).\r
 isAI(1) = 0\r
 \r
 ' Array defines if player is human (0) or computer (1).\r
 isAI(1) = 0\r
@@ -72,220 +73,220 @@ isAI(5) = 0
 CLS\r
 \r
 INPUT "How many players (1 - 5):", playerCount\r
 CLS\r
 \r
 INPUT "How many players (1 - 5):", playerCount\r
-INPUT "How many of them are computers:", a\r
-FOR b = playerCount TO playerCount - a + 1 STEP -1\r
-    isAI(b) = 1\r
-NEXT b\r
+INPUT "How many of them are computers:", computerCount\r
+FOR player = playerCount TO playerCount - computerCount + 1 STEP -1\r
+    isAI(player) = 1\r
+NEXT player\r
 \r
 \r
-INPUT "How many lives:", elum\r
-INPUT "Speed: (1-slow, 3-ok, 10-very fast)", spd\r
+INPUT "How many lives:", startLives\r
+INPUT "Speed: (1-slow, 3-ok, 10-very fast)", speed\r
 \r
 start\r
 \r
 \r
 start\r
 \r
-level lvl\r
+startLevel levelNum\r
 1\r
 1\r
-tkt\r
-delay .5 / spd\r
-' spd\r
-IF mtm >= 15 THEN\r
-    mtm = 1\r
-    lvl = lvl + 1\r
-    level lvl\r
+gameTick\r
+delay .5 / speed\r
+' speed\r
+IF fruitCount >= 15 THEN\r
+    fruitCount = 1\r
+    levelNum = levelNum + 1\r
+    startLevel levelNum\r
 END IF\r
 GOTO 1\r
 \r
 END IF\r
 GOTO 1\r
 \r
-SUB ai (a%)\r
-    ' This subroutine handles the AI logic for computer-controlled worms.\r
+SUB planPath (playerNumber%)\r
+    ' Computes a distance field to the nearest fruit and builds a movement plan.\r
     FOR y = 0 TO 36\r
         FOR x = 0 TO 36\r
     FOR y = 0 TO 36\r
         FOR x = 0 TO 36\r
-            buf2(x, y) = 32000\r
-            IF buf(x, y) = 2 THEN buf2(x, y) = 0\r
-            IF buf(x, y) > 9 OR buf(x, y) = 1 THEN buf2(x, y) = -1\r
+            distMap(x, y) = 32000\r
+            IF arena(x, y) = 2 THEN distMap(x, y) = 0\r
+            IF arena(x, y) > 9 OR arena(x, y) = 1 THEN distMap(x, y) = -1\r
         NEXT x\r
     NEXT y\r
 \r
         NEXT x\r
     NEXT y\r
 \r
-    ' Set the target position to the center of the grid.\r
-    IF buf2(16, 16) = 32000 THEN buf2(16, 16) = 15000\r
+    ' If no fruit is reachable, use the grid center as a fallback target.\r
+    IF distMap(16, 16) = 32000 THEN distMap(16, 16) = 15000\r
 \r
 6\r
     b = 0\r
     FOR y = 1 TO 35\r
         FOR x = 1 TO 34\r
 \r
 6\r
     b = 0\r
     FOR y = 1 TO 35\r
         FOR x = 1 TO 34\r
-            IF (buf2(x + 1, y) > buf2(x, y) + 1) AND (buf2(x, y) >= 0) THEN\r
-                buf2(x + 1, y) = buf2(x, y) + 1\r
+            IF (distMap(x + 1, y) > distMap(x, y) + 1) AND (distMap(x, y) >= 0) THEN\r
+                distMap(x + 1, y) = distMap(x, y) + 1\r
                 b = 1\r
             END IF\r
         NEXT x\r
 \r
                 b = 1\r
             END IF\r
         NEXT x\r
 \r
-        ' Check if moving left is the shortest path.\r
+        ' Check if moving right is the shortest path.\r
         FOR x = 35 TO 2 STEP -1\r
         FOR x = 35 TO 2 STEP -1\r
-            IF (buf2(x - 1, y) > buf2(x, y) + 1) AND (buf2(x, y) >= 0) THEN\r
-                buf2(x - 1, y) = buf2(x, y) + 1\r
+            IF (distMap(x - 1, y) > distMap(x, y) + 1) AND (distMap(x, y) >= 0) THEN\r
+                distMap(x - 1, y) = distMap(x, y) + 1\r
                 b = 1\r
             END IF\r
         NEXT x\r
 \r
                 b = 1\r
             END IF\r
         NEXT x\r
 \r
-        IF (buf2(1, y) > buf2(35, y) + 1) AND (buf2(35, y) >= 0) THEN\r
-            buf2(1, y) = buf2(35, y) + 1\r
+        IF (distMap(1, y) > distMap(35, y) + 1) AND (distMap(35, y) >= 0) THEN\r
+            distMap(1, y) = distMap(35, y) + 1\r
             b = 1\r
         END IF\r
 \r
             b = 1\r
         END IF\r
 \r
-        IF (buf2(35, y) > buf2(1, y) + 1) AND (buf2(1, y) >= 0) THEN\r
-            buf2(35, y) = buf2(1, y) + 1\r
+        IF (distMap(35, y) > distMap(1, y) + 1) AND (distMap(1, y) >= 0) THEN\r
+            distMap(35, y) = distMap(1, y) + 1\r
             b = 1\r
         END IF\r
     NEXT y\r
 \r
     FOR x = 1 TO 35\r
         FOR y = 1 TO 34\r
             b = 1\r
         END IF\r
     NEXT y\r
 \r
     FOR x = 1 TO 35\r
         FOR y = 1 TO 34\r
-            IF (buf2(x, y + 1) > buf2(x, y) + 1) AND (buf2(x, y) >= 0) THEN\r
-                buf2(x, y + 1) = buf2(x, y) + 1\r
+            IF (distMap(x, y + 1) > distMap(x, y) + 1) AND (distMap(x, y) >= 0) THEN\r
+                distMap(x, y + 1) = distMap(x, y) + 1\r
                 b = 1\r
             END IF\r
         NEXT y\r
 \r
                 b = 1\r
             END IF\r
         NEXT y\r
 \r
-        ' Check if moving up is the shortest path.\r
+        ' Check if moving down is the shortest path.\r
         FOR y = 35 TO 2 STEP -1\r
         FOR y = 35 TO 2 STEP -1\r
-            IF (buf2(x, y - 1) > buf2(x, y) + 1) AND (buf2(x, y) >= 0) THEN\r
-                buf2(x, y - 1) = buf2(x, y) + 1\r
+            IF (distMap(x, y - 1) > distMap(x, y) + 1) AND (distMap(x, y) >= 0) THEN\r
+                distMap(x, y - 1) = distMap(x, y) + 1\r
                 b = 1\r
             END IF\r
         NEXT y\r
 \r
                 b = 1\r
             END IF\r
         NEXT y\r
 \r
-        IF (buf2(x, 1) > buf2(x, 35) + 1) AND (buf2(x, 35) >= 0) THEN\r
-            buf2(x, 1) = buf2(x, 35) + 1\r
+        IF (distMap(x, 1) > distMap(x, 35) + 1) AND (distMap(x, 35) >= 0) THEN\r
+            distMap(x, 1) = distMap(x, 35) + 1\r
             b = 1\r
         END IF\r
 \r
             b = 1\r
         END IF\r
 \r
-        IF (buf2(x, 35) > buf2(x, 1) + 1) AND (buf2(x, 1) >= 0) THEN\r
-            buf2(x, 35) = buf2(x, 1) + 1\r
+        IF (distMap(x, 35) > distMap(x, 1) + 1) AND (distMap(x, 1) >= 0) THEN\r
+            distMap(x, 35) = distMap(x, 1) + 1\r
             b = 1\r
         END IF\r
     NEXT x\r
 \r
             b = 1\r
         END IF\r
     NEXT x\r
 \r
-    ' If no shorter path is found, exit the loop.\r
+    ' Repeat the relaxation passes until no cell distance improves.\r
     IF b = 1 THEN GOTO 6\r
 \r
     IF b = 1 THEN GOTO 6\r
 \r
-    tx = ux(a)\r
-    ty = uy(a)\r
+    traceX = headX(playerNumber)\r
+    traceY = headY(playerNumber)\r
 \r
 \r
-    d = 0\r
+    stepCount = 0\r
 7\r
     b = 32001\r
 7\r
     b = 32001\r
-    tmpxp = 0\r
-    tmpyp = 0\r
+    stepX = 0\r
+    stepY = 0\r
 \r
 \r
-    ' Check if moving right is the shortest path.\r
-    IF (buf2(tx - 1, ty) < b) AND (buf2(tx - 1, ty) >= 0) THEN\r
-        b = buf2(tx - 1, ty)\r
-        tmpxp = -1\r
-        tmpyp = 0\r
-        c = 1\r
+    ' Check if moving left is the shortest path.\r
+    IF (distMap(traceX - 1, traceY) < b) AND (distMap(traceX - 1, traceY) >= 0) THEN\r
+        b = distMap(traceX - 1, traceY)\r
+        stepX = -1\r
+        stepY = 0\r
+        direction = 1\r
     END IF\r
 \r
     END IF\r
 \r
-    ' Check if moving down is the shortest path.\r
-    IF (buf2(tx, ty - 1) < b) AND (buf2(tx, ty - 1) >= 0) THEN\r
-        b = buf2(tx, ty - 1)\r
-        tmpxp = 0\r
-        tmpyp = -1\r
-        c = 2\r
+    ' Check if moving up is the shortest path.\r
+    IF (distMap(traceX, traceY - 1) < b) AND (distMap(traceX, traceY - 1) >= 0) THEN\r
+        b = distMap(traceX, traceY - 1)\r
+        stepX = 0\r
+        stepY = -1\r
+        direction = 2\r
     END IF\r
 \r
     END IF\r
 \r
-    ' Check if moving left is the shortest path.\r
-    IF (buf2(tx + 1, ty) < b) AND (buf2(tx + 1, ty) >= 0) THEN\r
-        b = buf2(tx + 1, ty)\r
-        tmpxp = 1\r
-        tmpyp = 0\r
-        c = 3\r
+    ' Check if moving right is the shortest path.\r
+    IF (distMap(traceX + 1, traceY) < b) AND (distMap(traceX + 1, traceY) >= 0) THEN\r
+        b = distMap(traceX + 1, traceY)\r
+        stepX = 1\r
+        stepY = 0\r
+        direction = 3\r
     END IF\r
 \r
     END IF\r
 \r
-    ' Check if moving up is the shortest path.\r
-    IF (buf2(tx, ty + 1) < b) AND (buf2(tx, ty + 1) >= 0) THEN\r
-        b = buf2(tx, ty + 1)\r
-        tmpxp = 0\r
-        tmpyp = 1\r
-        c = 4\r
+    ' Check if moving down is the shortest path.\r
+    IF (distMap(traceX, traceY + 1) < b) AND (distMap(traceX, traceY + 1) >= 0) THEN\r
+        b = distMap(traceX, traceY + 1)\r
+        stepX = 0\r
+        stepY = 1\r
+        direction = 4\r
     END IF\r
 \r
     END IF\r
 \r
-    ' If no shorter path is found, set the direction to random.\r
+    ' If no neighbor is reachable, default to moving left.\r
     IF b = 32001 THEN\r
     IF b = 32001 THEN\r
-        tmpxp = -1\r
-        tmpyp = 0\r
-        c = 1\r
+        stepX = -1\r
+        stepY = 0\r
+        direction = 1\r
         b = -1\r
     END IF\r
 \r
         b = -1\r
     END IF\r
 \r
-    buf2(tx, ty) = -1\r
-    d = d + 1\r
-    ussk(d, a) = c\r
-    tx = tx + tmpxp\r
-    ty = ty + tmpyp\r
+    distMap(traceX, traceY) = -1\r
+    stepCount = stepCount + 1\r
+    pathPlan(stepCount, playerNumber) = direction\r
+    traceX = traceX + stepX\r
+    traceY = traceY + stepY\r
 \r
     ' Wrap around the edges of the grid.\r
 \r
     ' Wrap around the edges of the grid.\r
-    IF tx = 1 THEN tx = 34\r
-    IF ty = 1 THEN ty = 34\r
-    IF tx = 35 THEN tx = 2\r
-    IF ty = 35 THEN ty = 2\r
+    IF traceX = 1 THEN traceX = 34\r
+    IF traceY = 1 THEN traceY = 34\r
+    IF traceX = 35 THEN traceX = 2\r
+    IF traceY = 35 THEN traceY = 2\r
 \r
 \r
-    e = buf2(tx, ty)\r
-    buf2(tx, ty) = -1\r
+    cellDist = distMap(traceX, traceY)\r
+    distMap(traceX, traceY) = -1\r
 \r
 \r
-    sc2 tx, ty\r
+    debugTrace traceX, traceY\r
 \r
 \r
-    ' If the worm hits a wall or another worm, stop moving.\r
-    IF d > ail THEN GOTO 8\r
+    ' Stop tracing when the plan reaches its maximum length.\r
+    IF stepCount > planLimit THEN GOTO 8\r
 \r
 \r
-    ' If the worm finds food, continue moving.\r
-    IF (e > 0) AND (b > -1) THEN GOTO 7\r
+    ' Keep tracing while the path still leads toward a fruit.\r
+    IF (cellDist > 0) AND (b > -1) THEN GOTO 7\r
 \r
 8\r
 \r
 8\r
-    d = d + 1\r
-    ussk(d, a) = 5\r
-    usskp(a) = 1\r
+    stepCount = stepCount + 1\r
+    pathPlan(stepCount, playerNumber) = 5\r
+    planPtr(playerNumber) = 1\r
 \r
 \r
-    showb\r
+    showDistMap\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB autop (a%)\r
-    ' This subroutine handles the movement of computer-controlled worms.\r
-    c = 0\r
+SUB autopilot (playerNumber%)\r
+    ' Follows the AI movement plan; replans when the path is blocked.\r
+    replanned = 0\r
 \r
 5\r
     ' If the worm is not at the end of its path, continue moving.\r
 \r
 5\r
     ' If the worm is not at the end of its path, continue moving.\r
-    IF usskp(a) > 0 THEN\r
-        b = ussk(usskp(a), a)\r
+    IF planPtr(playerNumber) > 0 THEN\r
+        b = pathPlan(planPtr(playerNumber), playerNumber)\r
 \r
         IF b = 1 THEN\r
 \r
         IF b = 1 THEN\r
-            uxp(a) = -1\r
-            uyp(a) = 0\r
+            dirX(playerNumber) = -1\r
+            dirY(playerNumber) = 0\r
         ELSEIF b = 2 THEN\r
         ELSEIF b = 2 THEN\r
-            uxp(a) = 0\r
-            uyp(a) = -1\r
+            dirX(playerNumber) = 0\r
+            dirY(playerNumber) = -1\r
         ELSEIF b = 3 THEN\r
         ELSEIF b = 3 THEN\r
-            uxp(a) = 1\r
-            uyp(a) = 0\r
+            dirX(playerNumber) = 1\r
+            dirY(playerNumber) = 0\r
         ELSEIF b = 4 THEN\r
         ELSEIF b = 4 THEN\r
-            uxp(a) = 0\r
-            uyp(a) = 1\r
+            dirX(playerNumber) = 0\r
+            dirY(playerNumber) = 1\r
         ELSEIF b = 5 THEN\r
         ELSEIF b = 5 THEN\r
-            ai a\r
+            planPath playerNumber\r
             GOTO 5\r
         END IF\r
 \r
             GOTO 5\r
         END IF\r
 \r
-        usskp(a) = usskp(a) + 1\r
+        planPtr(playerNumber) = planPtr(playerNumber) + 1\r
     END IF\r
 \r
     ' Calculate the new position of the worm.\r
     END IF\r
 \r
     ' Calculate the new position of the worm.\r
-    nx = ux(a) + uxp(a)\r
-    ny = uy(a) + uyp(a)\r
+    nextX = headX(playerNumber) + dirX(playerNumber)\r
+    nextY = headY(playerNumber) + dirY(playerNumber)\r
 \r
     ' Check if the worm hits a wall or another worm.\r
 \r
     ' Check if the worm hits a wall or another worm.\r
-    b = buf(INT(nx), INT(ny))\r
+    b = arena(INT(nextX), INT(nextY))\r
 \r
     ' If the worm hits a wall or another worm, recalculate the AI.\r
 \r
     ' If the worm hits a wall or another worm, recalculate the AI.\r
-    IF (b = 1 OR b > 9) AND (c = 0) THEN\r
-        ai a\r
-        c = 1\r
+    IF (b = 1 OR b > 9) AND (replanned = 0) THEN\r
+        planPath playerNumber\r
+        replanned = 1\r
         GOTO 5\r
     END IF\r
 END SUB\r
         GOTO 5\r
     END IF\r
 END SUB\r
@@ -296,46 +297,46 @@ SUB clearTail (playerNumber)
     ' is responsible for clearing out squares where worm tail moved out\r
     ' of. Such squares are marked as empty (no longer occupied by worm)\r
     ' and also correspondingly are erased from the screen.\r
     ' is responsible for clearing out squares where worm tail moved out\r
     ' of. Such squares are marked as empty (no longer occupied by worm)\r
     ' and also correspondingly are erased from the screen.\r
-    a = ussp(playerNumber) - ussl(playerNumber)\r
+    tailPtr = headPtr(playerNumber) - wormLen(playerNumber)\r
 \r
 \r
-    IF a < 1 THEN\r
-        a = a + 2000\r
+    IF tailPtr < 1 THEN\r
+        tailPtr = tailPtr + 2000\r
     END IF\r
 \r
     ' The tail position can fall outside the ring buffer: below 1 when\r
     ' the worm has grown longer than the buffer capacity, above 2000\r
     ' when a dead worm's head pointer keeps advancing without wrapping.\r
     ' Nothing to erase in those cases.\r
     END IF\r
 \r
     ' The tail position can fall outside the ring buffer: below 1 when\r
     ' the worm has grown longer than the buffer capacity, above 2000\r
     ' when a dead worm's head pointer keeps advancing without wrapping.\r
     ' Nothing to erase in those cases.\r
-    IF a < 1 OR a > 2000 THEN\r
+    IF tailPtr < 1 OR tailPtr > 2000 THEN\r
         GOTO 13\r
     END IF\r
 \r
         GOTO 13\r
     END IF\r
 \r
-    IF ussx(a, playerNumber) > 0 THEN\r
-        buf(ussx(a, playerNumber), ussy(a, playerNumber)) = 0\r
+    IF trailX(tailPtr, playerNumber) > 0 THEN\r
+        arena(trailX(tailPtr, playerNumber), trailY(tailPtr, playerNumber)) = 0\r
 \r
 \r
-        drawGridCell ussx(a, playerNumber), ussy(a, playerNumber)\r
+        drawGridCell trailX(tailPtr, playerNumber), trailY(tailPtr, playerNumber)\r
 \r
 \r
-        ussx(a, playerNumber) = 0\r
+        trailX(tailPtr, playerNumber) = 0\r
     END IF\r
 13\r
 END SUB\r
 \r
     END IF\r
 13\r
 END SUB\r
 \r
-FUNCTION cnum$ (a%)\r
+FUNCTION numStr$ (value%)\r
     ' This function converts an integer to a string.\r
     ' This function converts an integer to a string.\r
-    b$ = STR$(a%)\r
+    text$ = STR$(value%)\r
 \r
     ' Remove leading spaces from the string.\r
 \r
     ' Remove leading spaces from the string.\r
-    IF LEFT$(b$, 1) = " " THEN\r
-        b$ = RIGHT$(b$, LEN(b$) - 1)\r
+    IF LEFT$(text$, 1) = " " THEN\r
+        text$ = RIGHT$(text$, LEN(text$) - 1)\r
     END IF\r
 \r
     END IF\r
 \r
-    cnum$ = b$\r
+    numStr$ = text$\r
 END FUNCTION\r
 \r
 END FUNCTION\r
 \r
-SUB dead (a%)\r
+SUB handleDeath (playerNumber%)\r
     ' This subroutine handles the death of a worm.\r
     ' This subroutine handles the death of a worm.\r
-    lives(a) = lives(a) - 1\r
-    putworm a\r
+    lives(playerNumber) = lives(playerNumber) - 1\r
+    spawnWorm playerNumber\r
 END SUB\r
 \r
 SUB delay (delayInSeconds AS SINGLE)\r
 END SUB\r
 \r
 SUB delay (delayInSeconds AS SINGLE)\r
@@ -350,108 +351,108 @@ SUB drawGridCell (x%, y%)
 ' This subroutine updates one cell on the screen as denoted by its\r
 ' X and Y coordinates.\r
 '\r
 ' This subroutine updates one cell on the screen as denoted by its\r
 ' X and Y coordinates.\r
 '\r
-' Cell content is red from global 'buf' array and corresponding\r
+' Cell content is read from global 'arena' array and corresponding\r
 ' sprite is drawn on the screen.\r
 '\r
 ' This procedure is handy because it allows redrawing only parts of the\r
 ' screen that were changed and thereby we avoid redrawing entire screen\r
 ' for every frame.\r
 \r
 ' sprite is drawn on the screen.\r
 '\r
 ' This procedure is handy because it allows redrawing only parts of the\r
 ' screen that were changed and thereby we avoid redrawing entire screen\r
 ' for every frame.\r
 \r
-    x1 = x% * 5\r
+    pixelX = x% * 5\r
 \r
 \r
-    y1 = y% * 5\r
+    pixelY = y% * 5\r
 \r
 \r
-    LINE (x1, y1)-(x1 + 3, y1 + 3), 0, BF\r
+    LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 0, BF\r
 \r
 \r
-    SELECT CASE buf(x%, y%)\r
+    SELECT CASE arena(x%, y%)\r
         CASE 0\r
             ' Draw an empty space on the grid.\r
         CASE 0\r
             ' Draw an empty space on the grid.\r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 1, BF\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 1, BF\r
 \r
         CASE 1\r
             ' Draw a wall on the grid.\r
 \r
         CASE 1\r
             ' Draw a wall on the grid.\r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 7, BF\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 7, BF\r
 \r
 \r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 8, B\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 8, B\r
 \r
         CASE 2\r
             ' Draw food on the grid.\r
 \r
         CASE 2\r
             ' Draw food on the grid.\r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 14, BF\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 14, BF\r
 \r
         CASE 10\r
             ' Draw the head of a worm.\r
 \r
         CASE 10\r
             ' Draw the head of a worm.\r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 10, BF\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 10, BF\r
 \r
 \r
-            PSET (x1, y1), 0\r
+            PSET (pixelX, pixelY), 0\r
 \r
 \r
-            PSET (x1 + 3, y1), 0\r
+            PSET (pixelX + 3, pixelY), 0\r
 \r
 \r
-            PSET (x1, y1 + 3), 0\r
+            PSET (pixelX, pixelY + 3), 0\r
 \r
 \r
-            PSET (x1 + 3, y1 + 3), 0\r
+            PSET (pixelX + 3, pixelY + 3), 0\r
 \r
         CASE 11\r
             ' Draw the body of a worm.\r
 \r
         CASE 11\r
             ' Draw the body of a worm.\r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 12, BF\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 12, BF\r
 \r
 \r
-            PSET (x1, y1), 0\r
+            PSET (pixelX, pixelY), 0\r
 \r
 \r
-            PSET (x1 + 3, y1), 0\r
+            PSET (pixelX + 3, pixelY), 0\r
 \r
 \r
-            PSET (x1, y1 + 3), 0\r
+            PSET (pixelX, pixelY + 3), 0\r
 \r
 \r
-            PSET (x1 + 3, y1 + 3), 0\r
+            PSET (pixelX + 3, pixelY + 3), 0\r
 \r
         CASE 12\r
             ' Draw the body of a worm.\r
 \r
         CASE 12\r
             ' Draw the body of a worm.\r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 13, BF\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 13, BF\r
 \r
 \r
-            PSET (x1, y1), 0\r
+            PSET (pixelX, pixelY), 0\r
 \r
 \r
-            PSET (x1 + 3, y1), 0\r
+            PSET (pixelX + 3, pixelY), 0\r
 \r
 \r
-            PSET (x1, y1 + 3), 0\r
+            PSET (pixelX, pixelY + 3), 0\r
 \r
 \r
-            PSET (x1 + 3, y1 + 3), 0\r
+            PSET (pixelX + 3, pixelY + 3), 0\r
 \r
         CASE 13\r
             ' Draw the body of a worm.\r
 \r
         CASE 13\r
             ' Draw the body of a worm.\r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 15, BF\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 15, BF\r
 \r
 \r
-            PSET (x1, y1), 0\r
+            PSET (pixelX, pixelY), 0\r
 \r
 \r
-            PSET (x1 + 3, y1), 0\r
+            PSET (pixelX + 3, pixelY), 0\r
 \r
 \r
-            PSET (x1, y1 + 3), 0\r
+            PSET (pixelX, pixelY + 3), 0\r
 \r
 \r
-            PSET (x1 + 3, y1 + 3), 0\r
+            PSET (pixelX + 3, pixelY + 3), 0\r
 \r
         CASE 14\r
             ' Draw the body of a worm.\r
 \r
         CASE 14\r
             ' Draw the body of a worm.\r
-            LINE (x1, y1)-(x1 + 3, y1 + 3), 9, BF\r
+            LINE (pixelX, pixelY)-(pixelX + 3, pixelY + 3), 9, BF\r
 \r
 \r
-            PSET (x1, y1), 0\r
+            PSET (pixelX, pixelY), 0\r
 \r
 \r
-            PSET (x1 + 3, y1), 0\r
+            PSET (pixelX + 3, pixelY), 0\r
 \r
 \r
-            PSET (x1, y1 + 3), 0\r
+            PSET (pixelX, pixelY + 3), 0\r
 \r
 \r
-            PSET (x1 + 3, y1 + 3), 0\r
+            PSET (pixelX + 3, pixelY + 3), 0\r
     END SELECT\r
 END SUB\r
 \r
 SUB init\r
     ' Initialize the game by setting up the first level.\r
     END SELECT\r
 END SUB\r
 \r
 SUB init\r
     ' Initialize the game by setting up the first level.\r
-    level 1\r
+    startLevel 1\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB level (a%)\r
+SUB startLevel (levelNum%)\r
     ' This subroutine sets up a new level of the game.\r
     LOCATE 5, 5\r
     PRINT "G E T   R E A D Y"\r
 \r
     LOCATE 7, 5\r
     ' This subroutine sets up a new level of the game.\r
     LOCATE 5, 5\r
     PRINT "G E T   R E A D Y"\r
 \r
     LOCATE 7, 5\r
-    PRINT "L E V E L :"; a%\r
+    PRINT "L E V E L :"; levelNum%\r
 \r
     ' Display a countdown before starting the level.\r
     delay 2\r
 \r
     ' Display a countdown before starting the level.\r
     delay 2\r
@@ -461,52 +462,52 @@ SUB level (a%)
     ' Clear the game grid.\r
     FOR y = 0 TO 36\r
         FOR x = 0 TO 36\r
     ' Clear the game grid.\r
     FOR y = 0 TO 36\r
         FOR x = 0 TO 36\r
-            buf(x, y) = 0\r
+            arena(x, y) = 0\r
         NEXT x\r
     NEXT y\r
 \r
     ' Set up the walls of the grid.\r
     FOR x = 0 TO 36\r
         NEXT x\r
     NEXT y\r
 \r
     ' Set up the walls of the grid.\r
     FOR x = 0 TO 36\r
-        buf(x, 0) = 1\r
-        buf(x, 36) = 1\r
-        buf(0, x) = 1\r
-        buf(36, x) = 1\r
+        arena(x, 0) = 1\r
+        arena(x, 36) = 1\r
+        arena(0, x) = 1\r
+        arena(36, x) = 1\r
     NEXT x\r
 \r
     ' Load the level from a file.\r
     NEXT x\r
 \r
     ' Load the level from a file.\r
-    b$ = cnum(a%) + ".lvl"\r
+    fileName$ = numStr(levelNum%) + ".lvl"\r
 \r
 \r
-    OPEN b$ FOR INPUT AS #1\r
+    OPEN fileName$ FOR INPUT AS #1\r
 \r
 10\r
     ' Read the level data line by line.\r
     IF EOF(1) <> 0 THEN GOTO 11\r
 \r
 \r
 10\r
     ' Read the level data line by line.\r
     IF EOF(1) <> 0 THEN GOTO 11\r
 \r
-    LINE INPUT #1, c$\r
+    LINE INPUT #1, rowText$\r
 \r
     ' If the line starts with a slash, it contains grid data.\r
 \r
     ' If the line starts with a slash, it contains grid data.\r
-    IF LEFT$(c$, 1) = "/" THEN\r
-        d = d + 1\r
+    IF LEFT$(rowText$, 1) = "/" THEN\r
+        rowNum = rowNum + 1\r
 \r
 \r
-        IF d > 35 THEN\r
+        IF rowNum > 35 THEN\r
             GOTO 12\r
         END IF\r
 \r
             GOTO 12\r
         END IF\r
 \r
-        g = LEN(c$)\r
-        IF g > 36 THEN\r
-            g = 36\r
+        rowLen = LEN(rowText$)\r
+        IF rowLen > 36 THEN\r
+            rowLen = 36\r
         END IF\r
 \r
         ' Parse the grid data and set up the game grid.\r
         END IF\r
 \r
         ' Parse the grid data and set up the game grid.\r
-        FOR e = 2 TO g\r
-            f$ = RIGHT$(LEFT$(c$, e), 1)\r
+        FOR column = 2 TO rowLen\r
+            cellChar$ = RIGHT$(LEFT$(rowText$, column), 1)\r
 \r
 \r
-            IF f$ = "#" OR f$ = "m" THEN\r
-                buf(e - 1, d) = 1\r
+            IF cellChar$ = "#" OR cellChar$ = "m" THEN\r
+                arena(column - 1, rowNum) = 1\r
             ELSE\r
             ELSE\r
-                buf(e - 1, d) = 0\r
+                arena(column - 1, rowNum) = 0\r
             END IF\r
             END IF\r
-        NEXT e\r
+        NEXT column\r
     END IF\r
 \r
 12\r
     END IF\r
 \r
 12\r
@@ -516,31 +517,31 @@ SUB level (a%)
     CLOSE #1\r
 \r
     ' Place food randomly on the grid.\r
     CLOSE #1\r
 \r
     ' Place food randomly on the grid.\r
-    stuff\r
+    placeFruit\r
 \r
     ' Initialize the worms for each player.\r
 \r
     ' Initialize the worms for each player.\r
-    show\r
+    drawArena\r
 \r
 \r
-    FOR b = 1 TO playerCount\r
-        ussl(b) = 0\r
-        putworm b\r
-    NEXT b\r
+    FOR player = 1 TO playerCount\r
+        wormLen(player) = 0\r
+        spawnWorm player\r
+    NEXT player\r
 \r
 \r
-    stat\r
+    showStats\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB prc (playerNumber)\r
+SUB advanceWorm (playerNumber)\r
 ' This subroutine handles the main game loop for each player.\r
    \r
     clearTail playerNumber\r
 \r
 ' This subroutine handles the main game loop for each player.\r
    \r
     clearTail playerNumber\r
 \r
-    ussp(playerNumber) = ussp(playerNumber) + 1\r
+    headPtr(playerNumber) = headPtr(playerNumber) + 1\r
 \r
     ' Wrap the head pointer unconditionally: dead worms skip the rest of\r
     ' this procedure, so without wrapping here their pointer would grow\r
     ' past the ring buffer (and eventually overflow the integer).\r
 \r
     ' Wrap the head pointer unconditionally: dead worms skip the rest of\r
     ' this procedure, so without wrapping here their pointer would grow\r
     ' past the ring buffer (and eventually overflow the integer).\r
-    IF ussp(playerNumber) > 2000 THEN\r
-        ussp(playerNumber) = ussp(playerNumber) - 2000\r
+    IF headPtr(playerNumber) > 2000 THEN\r
+        headPtr(playerNumber) = headPtr(playerNumber) - 2000\r
     END IF\r
 \r
     ' If the current player has no lives left, no further processing is needed\r
     END IF\r
 \r
     ' If the current player has no lives left, no further processing is needed\r
@@ -550,122 +551,122 @@ SUB prc (playerNumber)
 \r
     ' If the worm is controlled by AI, recalculate its path.\r
     IF isAI(playerNumber) = 1 THEN\r
 \r
     ' If the worm is controlled by AI, recalculate its path.\r
     IF isAI(playerNumber) = 1 THEN\r
-        autop playerNumber\r
+        autopilot playerNumber\r
     END IF\r
 \r
     ' Move the worm based on user input or AI direction.\r
     END IF\r
 \r
     ' Move the worm based on user input or AI direction.\r
-    ux(playerNumber) = ux(playerNumber) + uxp(playerNumber)\r
-    uy(playerNumber) = uy(playerNumber) + uyp(playerNumber)\r
+    headX(playerNumber) = headX(playerNumber) + dirX(playerNumber)\r
+    headY(playerNumber) = headY(playerNumber) + dirY(playerNumber)\r
 \r
     ' Wrap around the edges of the grid.\r
 \r
     ' Wrap around the edges of the grid.\r
-    IF ux(playerNumber) = 35 THEN\r
-        ux(playerNumber) = 2\r
+    IF headX(playerNumber) = 35 THEN\r
+        headX(playerNumber) = 2\r
     END IF\r
 \r
     END IF\r
 \r
-    IF uy(playerNumber) = 35 THEN\r
-        uy(playerNumber) = 2\r
+    IF headY(playerNumber) = 35 THEN\r
+        headY(playerNumber) = 2\r
     END IF\r
 \r
     END IF\r
 \r
-    IF ux(playerNumber) = 1 THEN\r
-        ux(playerNumber) = 34\r
+    IF headX(playerNumber) = 1 THEN\r
+        headX(playerNumber) = 34\r
     END IF\r
 \r
     END IF\r
 \r
-    IF uy(playerNumber) = 1 THEN\r
-        uy(playerNumber) = 34\r
+    IF headY(playerNumber) = 1 THEN\r
+        headY(playerNumber) = 34\r
     END IF\r
 \r
     END IF\r
 \r
-    x = ux(playerNumber)\r
-    y = uy(playerNumber)\r
+    cellX = headX(playerNumber)\r
+    cellY = headY(playerNumber)\r
 \r
 3\r
 \r
 3\r
-    IF buf(x, y) = 2 THEN\r
-        buf(x, y) = 0\r
+    IF arena(cellX, cellY) = 2 THEN\r
+        arena(cellX, cellY) = 0\r
 \r
 \r
-        drawGridCell x, y\r
+        drawGridCell cellX, cellY\r
 \r
 \r
-        stuff\r
+        placeFruit\r
 \r
 \r
-        ussl(playerNumber) = ussl(playerNumber) + mtm\r
+        wormLen(playerNumber) = wormLen(playerNumber) + fruitCount\r
 \r
 \r
-        usss(playerNumber) = usss(playerNumber) + mtm\r
+        score(playerNumber) = score(playerNumber) + fruitCount\r
 \r
 \r
-        FOR b = 1 TO playerCount\r
-            IF (lives(b) > 0) AND (isAI(b) = 1) THEN\r
-                ai b\r
+        FOR player = 1 TO playerCount\r
+            IF (lives(player) > 0) AND (isAI(player) = 1) THEN\r
+                planPath player\r
             END IF\r
             END IF\r
-        NEXT b\r
+        NEXT player\r
 \r
 \r
-        stat\r
+        showStats\r
 \r
         GOTO 3\r
     END IF\r
 \r
 \r
         GOTO 3\r
     END IF\r
 \r
-    IF buf(x, y) > 0 THEN\r
-        dead playerNumber\r
+    IF arena(cellX, cellY) > 0 THEN\r
+        handleDeath playerNumber\r
 \r
         GOTO 4\r
     END IF\r
 \r
     IF playerNumber = 1 THEN\r
 \r
         GOTO 4\r
     END IF\r
 \r
     IF playerNumber = 1 THEN\r
-        buf(x, y) = 10\r
+        arena(cellX, cellY) = 10\r
     ELSEIF playerNumber = 2 THEN\r
     ELSEIF playerNumber = 2 THEN\r
-        buf(x, y) = 11\r
+        arena(cellX, cellY) = 11\r
     ELSEIF playerNumber = 3 THEN\r
     ELSEIF playerNumber = 3 THEN\r
-        buf(x, y) = 12\r
+        arena(cellX, cellY) = 12\r
     ELSEIF playerNumber = 4 THEN\r
     ELSEIF playerNumber = 4 THEN\r
-        buf(x, y) = 13\r
+        arena(cellX, cellY) = 13\r
     ELSEIF playerNumber = 5 THEN\r
     ELSEIF playerNumber = 5 THEN\r
-        buf(x, y) = 14\r
+        arena(cellX, cellY) = 14\r
     END IF\r
 \r
     END IF\r
 \r
-    drawGridCell x, y\r
+    drawGridCell cellX, cellY\r
 \r
 \r
-    IF ussp(playerNumber) > 2000 THEN\r
-        ussp(playerNumber) = ussp(playerNumber) - 2000\r
+    IF headPtr(playerNumber) > 2000 THEN\r
+        headPtr(playerNumber) = headPtr(playerNumber) - 2000\r
     END IF\r
 \r
     END IF\r
 \r
-    ussx(ussp(playerNumber), playerNumber) = x\r
+    trailX(headPtr(playerNumber), playerNumber) = cellX\r
 \r
 \r
-    ussy(ussp(playerNumber), playerNumber) = y\r
+    trailY(headPtr(playerNumber), playerNumber) = cellY\r
 \r
 4\r
 END SUB\r
 \r
 \r
 4\r
 END SUB\r
 \r
-SUB putworm (a%)\r
+SUB spawnWorm (playerNumber%)\r
     ' This subroutine initializes a new worm for a player.\r
     ' This subroutine initializes a new worm for a player.\r
-    b = ussl(a%)\r
+    b = wormLen(playerNumber%)\r
 \r
     ' Move the worm back to its starting position.\r
 \r
     ' Move the worm back to its starting position.\r
-    FOR c = b TO 1 STEP -1\r
-        ussl(a%) = c\r
+    FOR segment = b TO 1 STEP -1\r
+        wormLen(playerNumber%) = segment\r
 \r
 \r
-        clearTail a%\r
-    NEXT c\r
+        clearTail playerNumber%\r
+    NEXT segment\r
 \r
 9\r
 \r
 9\r
-    uy(a%) = INT(RND * 30 + 2)\r
+    headY(playerNumber%) = INT(RND * 30 + 2)\r
 \r
 \r
-    ux(a%) = INT(RND * 10 + 5)\r
+    headX(playerNumber%) = INT(RND * 10 + 5)\r
 \r
     ' Ensure that the worm starts in an empty space.\r
 \r
     ' Ensure that the worm starts in an empty space.\r
-    FOR b = ux(a%) TO ux(a%) + 10\r
-        IF buf(b, uy(a%)) <> 0 THEN\r
+    FOR b = headX(playerNumber%) TO headX(playerNumber%) + 10\r
+        IF arena(b, headY(playerNumber%)) <> 0 THEN\r
             GOTO 9\r
         END IF\r
     NEXT b\r
 \r
     ' Set the initial direction of the worm.\r
             GOTO 9\r
         END IF\r
     NEXT b\r
 \r
     ' Set the initial direction of the worm.\r
-    uxp(a%) = 1\r
+    dirX(playerNumber%) = 1\r
 \r
 \r
-    uyp(a%) = 0\r
+    dirY(playerNumber%) = 0\r
 \r
 \r
-    ussl(a%) = 3\r
+    wormLen(playerNumber%) = 3\r
 \r
 \r
-    stat\r
+    showStats\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB sc2 (x%, y%)\r
-    ' This subroutine draws a worm on the game grid for AI pathfinding.\r
+SUB debugTrace (x%, y%)\r
+    ' Debug helper: plots the AI path trace on screen (currently disabled).\r
     ' LOCATE 1, 1\r
     ' PRINT x%, y%\r
 \r
     ' LOCATE 1, 1\r
     ' PRINT x%, y%\r
 \r
@@ -678,7 +679,7 @@ SUB sc2 (x%, y%)
     ' a$ = INPUT$(1)\r
 END SUB\r
 \r
     ' a$ = INPUT$(1)\r
 END SUB\r
 \r
-SUB show\r
+SUB drawArena\r
     ' This subroutine draws the entire game grid.\r
     FOR y = 1 TO 35\r
         FOR x = 1 TO 35\r
     ' This subroutine draws the entire game grid.\r
     FOR y = 1 TO 35\r
         FOR x = 1 TO 35\r
@@ -687,12 +688,13 @@ SUB show
     NEXT y\r
 END SUB\r
 \r
     NEXT y\r
 END SUB\r
 \r
-SUB showb\r
+SUB showDistMap\r
+    ' Disabled debug view: draws the AI distance map (distMap) beside the arena.\r
 GOTO 15\r
 FOR x = 1 TO 35\r
 FOR y = 1 TO 35\r
 \r
 GOTO 15\r
 FOR x = 1 TO 35\r
 FOR y = 1 TO 35\r
 \r
-LINE (x * 2 + 200, y * 2 + 100)-(x * 2 + 201, y * 2 + 101), buf2(x, y) MOD 255, BF\r
+LINE (x * 2 + 200, y * 2 + 100)-(x * 2 + 201, y * 2 + 101), distMap(x, y) MOD 255, BF\r
 NEXT y\r
 NEXT x\r
 15\r
 NEXT y\r
 NEXT x\r
 15\r
@@ -705,57 +707,57 @@ SUB start
 \r
     RANDOMIZE TIMER\r
 \r
 \r
     RANDOMIZE TIMER\r
 \r
-    uy(1) = 5\r
+    headY(1) = 5\r
 \r
 \r
-    uy(2) = 10\r
+    headY(2) = 10\r
 \r
 \r
-    uy(3) = 15\r
+    headY(3) = 15\r
 \r
 \r
-    uy(4) = 20\r
+    headY(4) = 20\r
 \r
 \r
-    uy(5) = 25\r
+    headY(5) = 25\r
 \r
 \r
-    FOR a = 1 TO playerCount\r
-        ux(a) = 15\r
+    FOR player = 1 TO playerCount\r
+        headX(player) = 15\r
 \r
 \r
-        uxp(a) = 1\r
+        dirX(player) = 1\r
 \r
 \r
-        uyp(a) = 0\r
+        dirY(player) = 0\r
 \r
 \r
-        ussp(a) = 0\r
+        headPtr(player) = 0\r
 \r
 \r
-        ussl(a) = 3\r
+        wormLen(player) = 3\r
 \r
 \r
-        lives(a) = elum\r
+        lives(player) = startLives\r
 \r
 \r
-        usss(a) = 0\r
+        score(player) = 0\r
 \r
 \r
-        usskp(a) = 1\r
+        planPtr(player) = 1\r
 \r
 \r
-        ussk(1, a) = 5\r
-    NEXT a\r
+        pathPlan(1, player) = 5\r
+    NEXT player\r
 \r
 \r
-    mtm = 0\r
+    fruitCount = 0\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB stat\r
+SUB showStats\r
     ' This subroutine displays the current game statistics.\r
     LOCATE 1, 25\r
 \r
     ' This subroutine displays the current game statistics.\r
     LOCATE 1, 25\r
 \r
-    PRINT mtm\r
+    PRINT fruitCount\r
 \r
 \r
-    FOR a = 1 TO 5\r
+    FOR player = 1 TO 5\r
         COLOR 15\r
 \r
         COLOR 15\r
 \r
-        LOCATE 2 + a, 24\r
+        LOCATE 2 + player, 24\r
 \r
 \r
-        PRINT RIGHT$(STR$(a), 1)\r
+        PRINT RIGHT$(STR$(player), 1)\r
 \r
         COLOR 10\r
 \r
 \r
         COLOR 10\r
 \r
-        LOCATE 2 + a, 26\r
+        LOCATE 2 + player, 26\r
 \r
 \r
-        IF isAI(a) = 1 THEN\r
+        IF isAI(player) = 1 THEN\r
             PRINT "*"\r
         ELSE\r
             PRINT "-"\r
             PRINT "*"\r
         ELSE\r
             PRINT "-"\r
@@ -763,20 +765,20 @@ SUB stat
 \r
         COLOR 12\r
 \r
 \r
         COLOR 12\r
 \r
-        LOCATE 2 + a, 27\r
+        LOCATE 2 + player, 27\r
 \r
 \r
-        b$ = STR$(usss(a))\r
+        text$ = STR$(score(player))\r
 \r
 \r
-        PRINT RIGHT$(b$, LEN(b$) - 1)\r
+        PRINT RIGHT$(text$, LEN(text$) - 1)\r
 \r
         COLOR 13\r
 \r
 \r
         COLOR 13\r
 \r
-        LOCATE 2 + a, 30\r
+        LOCATE 2 + player, 30\r
 \r
 \r
-        b$ = STR$(lives(a))\r
+        text$ = STR$(lives(player))\r
 \r
 \r
-        PRINT RIGHT$(b$, LEN(b$) - 1)\r
-    NEXT a\r
+        PRINT RIGHT$(text$, LEN(text$) - 1)\r
+    NEXT player\r
 \r
     COLOR 10\r
 \r
 \r
     COLOR 10\r
 \r
@@ -798,98 +800,98 @@ SUB stat
 \r
     LOCATE 1, 30\r
 \r
 \r
     LOCATE 1, 30\r
 \r
-    PRINT "Level:"; lvl\r
+    PRINT "Level:"; levelNum\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB stuff\r
+SUB placeFruit\r
     ' This subroutine places food randomly on the game grid.\r
 2\r
     x = INT(RND * 33 + 2)\r
 \r
     y = INT(RND * 33 + 2)\r
 \r
     ' This subroutine places food randomly on the game grid.\r
 2\r
     x = INT(RND * 33 + 2)\r
 \r
     y = INT(RND * 33 + 2)\r
 \r
-    IF buf(x, y) = 0 THEN\r
-        buf(x, y) = 2\r
+    IF arena(x, y) = 0 THEN\r
+        arena(x, y) = 2\r
 \r
         drawGridCell x, y\r
     ELSE\r
         GOTO 2\r
     END IF\r
 \r
 \r
         drawGridCell x, y\r
     ELSE\r
         GOTO 2\r
     END IF\r
 \r
-    mtm = mtm + 1\r
+    fruitCount = fruitCount + 1\r
 \r
 \r
-    stat\r
+    showStats\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB tkt\r
-    ' This subroutine handles user input and AI recalculation.\r
-    a$ = INKEY$\r
+SUB gameTick\r
+    ' One game tick: read keyboard input, then advance every worm.\r
+    key$ = INKEY$\r
 \r
 \r
-    IF a$ = CHR$(27) THEN\r
+    IF key$ = CHR$(27) THEN\r
         SYSTEM\r
     END IF\r
 \r
         SYSTEM\r
     END IF\r
 \r
-    IF (a$ = CHR$(0) + "M") AND (uxp(1) <> -1) THEN\r
-        uxp(1) = 1\r
+    IF (key$ = CHR$(0) + "M") AND (dirX(1) <> -1) THEN\r
+        dirX(1) = 1\r
 \r
 \r
-        uyp(1) = 0\r
+        dirY(1) = 0\r
     END IF\r
 \r
     END IF\r
 \r
-    IF (a$ = CHR$(0) + "K") AND (uxp(1) <> 1) THEN\r
-        uxp(1) = -1\r
+    IF (key$ = CHR$(0) + "K") AND (dirX(1) <> 1) THEN\r
+        dirX(1) = -1\r
 \r
 \r
-        uyp(1) = 0\r
+        dirY(1) = 0\r
     END IF\r
 \r
     END IF\r
 \r
-    IF (a$ = CHR$(0) + "P") AND (uyp(1) <> -1) THEN\r
-        uxp(1) = 0\r
+    IF (key$ = CHR$(0) + "P") AND (dirY(1) <> -1) THEN\r
+        dirX(1) = 0\r
 \r
 \r
-        uyp(1) = 1\r
+        dirY(1) = 1\r
     END IF\r
 \r
     END IF\r
 \r
-    IF (a$ = CHR$(0) + "H") AND (uyp(1) <> 1) THEN\r
-        uxp(1) = 0\r
+    IF (key$ = CHR$(0) + "H") AND (dirY(1) <> 1) THEN\r
+        dirX(1) = 0\r
 \r
 \r
-        uyp(1) = -1\r
+        dirY(1) = -1\r
     END IF\r
 \r
     END IF\r
 \r
-    IF (a$ = "d") AND (uxp(2) <> -1) THEN\r
-        uxp(2) = 1\r
+    IF (key$ = "d") AND (dirX(2) <> -1) THEN\r
+        dirX(2) = 1\r
 \r
 \r
-        uyp(2) = 0\r
+        dirY(2) = 0\r
     END IF\r
 \r
     END IF\r
 \r
-    IF (a$ = "a") AND (uxp(2) <> 1) THEN\r
-        uxp(2) = -1\r
+    IF (key$ = "a") AND (dirX(2) <> 1) THEN\r
+        dirX(2) = -1\r
 \r
 \r
-        uyp(2) = 0\r
+        dirY(2) = 0\r
     END IF\r
 \r
     END IF\r
 \r
-    IF (a$ = "s") AND (uyp(2) <> -1) THEN\r
-        uxp(2) = 0\r
+    IF (key$ = "s") AND (dirY(2) <> -1) THEN\r
+        dirX(2) = 0\r
 \r
 \r
-        uyp(2) = 1\r
+        dirY(2) = 1\r
     END IF\r
 \r
     END IF\r
 \r
-    IF (a$ = "w") AND (uyp(2) <> 1) THEN\r
-        uxp(2) = 0\r
+    IF (key$ = "w") AND (dirY(2) <> 1) THEN\r
+        dirX(2) = 0\r
 \r
 \r
-        uyp(2) = -1\r
+        dirY(2) = -1\r
     END IF\r
 \r
     END IF\r
 \r
-    b = VAL(a$)\r
+    playerNum = VAL(key$)\r
 \r
 \r
-    IF b > 0 THEN\r
-        IF isAI(b) = 1 THEN\r
-            isAI(b) = 0\r
+    IF playerNum > 0 THEN\r
+        IF isAI(playerNum) = 1 THEN\r
+            isAI(playerNum) = 0\r
         ELSE\r
         ELSE\r
-            isAI(b) = 1\r
+            isAI(playerNum) = 1\r
         END IF\r
 \r
         END IF\r
 \r
-        stat\r
+        showStats\r
     END IF\r
 \r
     END IF\r
 \r
-    FOR a = 1 TO playerCount\r
-        prc a\r
-    NEXT a\r
+    FOR player = 1 TO playerCount\r
+        advanceWorm player\r
+    NEXT player\r
 END SUB\r
 END SUB\r
index ca58775..898df06 100755 (executable)
 ' Changelog:
 ' 2001, Initial version
 ' 2024, Improved program readability
 ' Changelog:
 ' 2001, Initial version
 ' 2024, Improved program readability
+' 2026, Improved program readability
 
 
-DECLARE SUB compki (m%, h%, x1%, y1%)
-DECLARE SUB compgo2 (h%)
-DECLARE SUB compgo (h%)
-DECLARE SUB humngo (h%)
+DECLARE SUB evalCapture (jumps%, score%, landX%, landY%)
+DECLARE SUB searchCompMoves2 (score%) ' declared but never used
+DECLARE SUB searchCompMoves (score%)
+DECLARE SUB searchOppMoves (score%)
 DefInt A-Z
 
 DefInt A-Z
 
-DECLARE SUB thinkc ()
-DECLARE SUB thinkh ()
-DECLARE SUB cmd (a$)
-DECLARE SUB freet ()
-DECLARE SUB prn (x%, y%, c%, a$)
-DECLARE SUB msg (a$, c)
-DECLARE SUB getfnt ()
-DECLARE SUB playg ()
-DECLARE SUB geth ()
-DECLARE SUB start ()
-DECLARE SUB mklau ()
-DECLARE SUB showr (x, y)
-DECLARE SUB show ()
+DECLARE SUB computerMove ()
+DECLARE SUB humanMove ()
+DECLARE SUB doCommand (cmdText$)
+DECLARE SUB pollKeys ()
+DECLARE SUB drawText (pixelX%, pixelY%, fgColor%, text$)
+DECLARE SUB showMessage (text$, fgColor)
+DECLARE SUB grabFont ()
+DECLARE SUB gameLoop ()
+DECLARE SUB getHumanMove () ' declared but never used
+DECLARE SUB initGame ()
+DECLARE SUB initBoard ()
+DECLARE SUB drawSquare (x, y)
+DECLARE SUB drawBoard ()
 Dim Shared font(0 To 7, 0 To 7, 0 To 255)
 Dim Shared font(0 To 7, 0 To 7, 0 To 255)
-Dim Shared siz, fi, ri, rs
-Dim Shared stri$
-Dim Shared humx1, humy1, humx2, humy2
-Dim Shared sug, smax
-Dim Shared npos As Long
-Dim Shared cx1, cy1, cx2, cy2
-
-siz = 6 ' Board size
-fi = 0
-ri = 2
-smax = 3 ' thinking depth
-
-Dim Shared lau(-1 To siz + 2, -1 To siz + 2)
-
-start
-mklau
-show
-playg
-
-Sub cmd (a$)
-    mitus = 0
-    Dim sona$(1 To 10)
-    For b = 1 To 10
-        sona$(b) = ""
-    Next b
-
-    d = 1
-    e = 1
-    For b = 1 To Len(a$)
-        c$ = Right$(Left$(a$, b), 1)
-        If c$ = " " Then
-            If e = 0 Then d = d + 1: e = 1
+Dim Shared boardSize, parity, pieceRows, squareSize
+Dim Shared cmdLine$
+Dim Shared humFromX, humFromY, humToX, humToY
+Dim Shared depth, maxDepth
+Dim Shared positions As Long
+Dim Shared compFromX, compFromY, compToX, compToY
+
+boardSize = 6 ' Board size
+parity = 0
+pieceRows = 2
+maxDepth = 3 ' thinking depth
+
+' Board encoding: -1 = off-board border, 0 = empty, 1 = computer piece, 2 = human piece
+Dim Shared board(-1 To boardSize + 2, -1 To boardSize + 2)
+
+initGame
+initBoard
+drawBoard
+gameLoop
+
+Sub doCommand (cmdText$)
+    ' Parse and run a command typed in the game's command line (m/h/q/n).
+    wordCount = 0
+    Dim word$(1 To 10)
+    For charIdx = 1 To 10
+        word$(charIdx) = ""
+    Next charIdx
+
+    wordNum = 1
+    afterSpace = 1
+    For charIdx = 1 To Len(cmdText$)
+        char$ = Right$(Left$(cmdText$, charIdx), 1)
+        If char$ = " " Then
+            If afterSpace = 0 Then wordNum = wordNum + 1: afterSpace = 1
             GoTo 4
         End If
             GoTo 4
         End If
-        e = 0
-        sona$(d) = sona$(d) + c$
+        afterSpace = 0
+        word$(wordNum) = word$(wordNum) + char$
         4
         4
-    Next b
-    If e = 1 Then d = d - 1
-    mitus = d
+    Next charIdx
+    If afterSpace = 1 Then wordNum = wordNum - 1
+    wordCount = wordNum
 
 
-    Select Case sona$(1)
+    ' word$(1) = command letter; word$(2) and word$(3) = arguments.
+    Select Case word$(1)
         Case "m"
         Case "m"
-            If humx1 > 0 Then msg "Move replaced.", 14
+            If humFromX > 0 Then showMessage "Move replaced.", 14
 
 
-            humx1 = Asc(Left$(sona$(2), 1)) - 64
-            If humx1 > 32 Then humx1 = humx1 - 32
-            humy1 = Val(Right$(sona$(2), Len(sona$(2)) - 1))
-            humx2 = Asc(Left$(sona$(3), 1)) - 64
-            If humx2 > 32 Then humx2 = humx2 - 32
-            humy2 = Val(Right$(sona$(3), Len(sona$(2)) - 1))
+            ' Parse "m <from> <to>" e.g. "m b3 d4": column letter -> 1-6, row digit.
+            humFromX = Asc(Left$(word$(2), 1)) - 64
+            If humFromX > 32 Then humFromX = humFromX - 32 ' tolerate lowercase
+            humFromY = Val(Right$(word$(2), Len(word$(2)) - 1))
+            humToX = Asc(Left$(word$(3), 1)) - 64
+            If humToX > 32 Then humToX = humToX - 32 ' tolerate lowercase
+            humToY = Val(Right$(word$(3), Len(word$(2)) - 1))
 
         Case "h"
 
         Case "h"
-            msg "h - display help screen", 14
-            msg "q - to quit", 14
-            msg "m <from> <to> - make move", 14
-            msg "n - no. positions processed", 14
+            showMessage "h - display help screen", 14
+            showMessage "q - to quit", 14
+            showMessage "m <from> <to> - make move", 14
+            showMessage "n - no. positions processed", 14
 
         Case "q"
             System
 
         Case "n"
 
         Case "q"
             System
 
         Case "n"
-            b$ = "positions processed:" + Str$(npos)
-            msg b$, 14
+            outMsg$ = "positions processed:" + Str$(positions)
+            showMessage outMsg$, 14
 
     End Select
 
 End Sub
 
 
     End Select
 
 End Sub
 
-Sub compgo (h)
-    If sug > smax Then h = 0: GoTo 6
-    sug = sug + 1
-    npos = npos + 1
-    freet
-    If sug = 1 Then h1 = -2000 Else h1 = -1000
-
-    'cx1 = x: cy1 = y: cx2 = x - 1: cy2 = y + 1
-    b = 0
-    c = 0
-    m = 0
-    For y = 1 To siz ' check for eating
-        For x = 1 To siz
-            If lau(x, y) = 1 Then
+Sub searchCompMoves (score)
+    ' Recursively search computer moves and opponent replies up to maxDepth.
+    ' Returns the best achievable score via the score parameter.
+    If depth > maxDepth Then score = 0: GoTo 6 ' cut-off depth reached, stop searching
+    depth = depth + 1
+    positions = positions + 1
+    pollKeys
+    If depth = 1 Then bestScore = -2000 Else bestScore = -1000
+
+    'compFromX = x: compFromY = y: compToX = x - 1: compToY = y + 1
+    foundCapture = 0
+    jumped = 0
+    maxJumps = 0
+    For y = 1 To boardSize ' look for captures
+        For x = 1 To boardSize
+            If board(x, y) = 1 Then
                 8
                 8
-                If (lau(x - 1, y + 1) = 2) And (lau(x - 2, y + 2) = 0) Then
-                    Swap lau(x, y), lau(x - 2, y + 2)
-                    lau(x - 1, y + 1) = 0
-                    compki m1, h2, x - 2, y + 2
-                    lau(x - 1, y + 1) = 2
-                    Swap lau(x, y), lau(x - 2, y + 2)
-                    m1 = m1 + 1
-                    If m1 > m Then m = m1: h1 = -1000
-                    If m1 = m Then
-                        If h2 + 1 > h1 Then
-                            h1 = h2 + 1
-                            If npos = 1 Then cx1 = x: cy1 = y: cx2 = x - 2: cy2 = y + 2
+                ' Capture: opponent piece diagonally adjacent and the landing square behind
+                ' it empty -> jump over it, remove it, recurse, then restore the board.
+                If (board(x - 1, y + 1) = 2) And (board(x - 2, y + 2) = 0) Then
+                    Swap board(x, y), board(x - 2, y + 2)
+                    board(x - 1, y + 1) = 0
+                    evalCapture jumpCount, replyScore, x - 2, y + 2
+                    board(x - 1, y + 1) = 2
+                    Swap board(x, y), board(x - 2, y + 2)
+                    jumpCount = jumpCount + 1
+                    If jumpCount > maxJumps Then maxJumps = jumpCount: bestScore = -1000
+                    If jumpCount = maxJumps Then
+                        If replyScore + 1 > bestScore Then
+                            bestScore = replyScore + 1
+                            If positions = 1 Then compFromX = x: compFromY = y: compToX = x - 2: compToY = y + 2
                         End If
                     End If
                         End If
                     End If
-                    b = 1
+                    foundCapture = 1
                 End If
 
                 End If
 
-                If (lau(x + 1, y + 1) = 2) And (lau(x + 2, y + 2) = 0) Then
-                    Swap lau(x, y), lau(x + 2, y + 2)
-                    lau(x + 1, y + 1) = 0
-                    compki m1, h2, x + 2, y + 2
-                    lau(x + 1, y + 1) = 2
-                    Swap lau(x, y), lau(x + 2, y + 2)
-                    m1 = m1 + 1
-                    If m1 > m Then m = m1: h1 = -1000
-                    If m1 = m Then
-                        If h2 + 1 > h1 Then
-                            h1 = h2 + 1
-                            If npos = 1 Then cx1 = x: cy1 = y: cx2 = x + 2: cy2 = y + 2
+                If (board(x + 1, y + 1) = 2) And (board(x + 2, y + 2) = 0) Then
+                    Swap board(x, y), board(x + 2, y + 2)
+                    board(x + 1, y + 1) = 0
+                    evalCapture jumpCount, replyScore, x + 2, y + 2
+                    board(x + 1, y + 1) = 2
+                    Swap board(x, y), board(x + 2, y + 2)
+                    jumpCount = jumpCount + 1
+                    If jumpCount > maxJumps Then maxJumps = jumpCount: bestScore = -1000
+                    If jumpCount = maxJumps Then
+                        If replyScore + 1 > bestScore Then
+                            bestScore = replyScore + 1
+                            If positions = 1 Then compFromX = x: compFromY = y: compToX = x + 2: compToY = y + 2
                         End If
                     End If
                         End If
                     End If
-                    b = 1
+                    foundCapture = 1
                 End If
 
                 End If
 
-                If (lau(x - 1, y - 1) = 2) And (lau(x - 2, y - 2) = 0) Then
-                    Swap lau(x, y), lau(x - 2, y - 2)
-                    lau(x - 1, y - 1) = 0
-                    compki m1, h2, x - 2, y - 2
-                    lau(x - 1, y - 1) = 2
-                    Swap lau(x, y), lau(x - 2, y - 2)
-                    m1 = m1 + 1
-                    If m1 > m Then m = m1: h1 = -1000
-                    If m1 = m Then
-                        If h2 + 1 > h1 Then
-                            h1 = h2 + 1
-                            If npos = 1 Then cx1 = x: cy1 = y: cx2 = x - 2: cy2 = y - 2
+                If (board(x - 1, y - 1) = 2) And (board(x - 2, y - 2) = 0) Then
+                    Swap board(x, y), board(x - 2, y - 2)
+                    board(x - 1, y - 1) = 0
+                    evalCapture jumpCount, replyScore, x - 2, y - 2
+                    board(x - 1, y - 1) = 2
+                    Swap board(x, y), board(x - 2, y - 2)
+                    jumpCount = jumpCount + 1
+                    If jumpCount > maxJumps Then maxJumps = jumpCount: bestScore = -1000
+                    If jumpCount = maxJumps Then
+                        If replyScore + 1 > bestScore Then
+                            bestScore = replyScore + 1
+                            If positions = 1 Then compFromX = x: compFromY = y: compToX = x - 2: compToY = y - 2
                         End If
                     End If
                         End If
                     End If
-                    b = 1
+                    foundCapture = 1
                 End If
 
                 End If
 
-                If (lau(x + 1, y - 1) = 2) And (lau(x + 2, y - 2) = 0) Then
-                    Swap lau(x, y), lau(x + 2, y - 2)
-                    lau(x + 1, y - 1) = 0
-                    compki m1, h2, x + 2, y - 2
-                    lau(x + 1, y - 1) = 2
-                    Swap lau(x, y), lau(x + 2, y - 2)
-                    m1 = m1 + 1
-                    If m1 > m Then m = m1: h1 = -1000
-                    If m1 = m Then
-                        If h2 + 1 > h1 Then
-                            h1 = h2 + 1
-                            If npos = 1 Then cx1 = x: cy1 = y: cx2 = x + 2: cy2 = y - 2
+                If (board(x + 1, y - 1) = 2) And (board(x + 2, y - 2) = 0) Then
+                    Swap board(x, y), board(x + 2, y - 2)
+                    board(x + 1, y - 1) = 0
+                    evalCapture jumpCount, replyScore, x + 2, y - 2
+                    board(x + 1, y - 1) = 2
+                    Swap board(x, y), board(x + 2, y - 2)
+                    jumpCount = jumpCount + 1
+                    If jumpCount > maxJumps Then maxJumps = jumpCount: bestScore = -1000
+                    If jumpCount = maxJumps Then
+                        If replyScore + 1 > bestScore Then
+                            bestScore = replyScore + 1
+                            If positions = 1 Then compFromX = x: compFromY = y: compToX = x + 2: compToY = y - 2
                         End If
                     End If
                         End If
                     End If
-                    b = 1
+                    foundCapture = 1
                 End If
 
                 End If
 
-                If c = 1 Then GoTo 9
+                If jumped = 1 Then GoTo 9
             End If
         Next x
     Next y
 
     9
             End If
         Next x
     Next y
 
     9
-    If (b = 1) And (npos = 1) Then
-        cx3 = (cx1 + cx2) / 2
-        cy3 = (cy1 + cy2) / 2
-        lau(cx3, cy3) = 0
-        showr cx3, cy3
-
-        Swap lau(cx1, cy1), lau(cx2, cy2)
-        showr cx1, cy1
-        showr cx2, cy2
-        msg "NJAM!", 10
-        x = cx2
-        y = cy2
-        c = 1
-        b = 0
-        GoTo 8
+    ' Top level only: play the chosen capture on screen and scan for further jumps.
+    If (foundCapture = 1) And (positions = 1) Then
+        midX = (compFromX + compToX) / 2
+        midY = (compFromY + compToY) / 2
+        board(midX, midY) = 0
+        drawSquare midX, midY
+
+        Swap board(compFromX, compFromY), board(compToX, compToY)
+        drawSquare compFromX, compFromY
+        drawSquare compToX, compToY
+        showMessage "NJAM!", 10
+        x = compToX
+        y = compToY
+        jumped = 1
+        foundCapture = 0
+        GoTo 8 ' re-enter the scan loop to continue a multi-jump
     End If
     End If
-    If c = 1 Then
-        cx1 = 1: cy1 = 1: cx2 = 1: cy2 = 1
+    If jumped = 1 Then
+        compFromX = 1: compFromY = 1: compToX = 1: compToY = 1
         GoTo 10
     End If
 
         GoTo 10
     End If
 
-    If sug = 1 Then
-        msg "Cannot eat.", 4
-        msg Str$(h1), 4
+    If depth = 1 Then
+        showMessage "Cannot eat.", 4
+        showMessage Str$(bestScore), 4
     End If
 
     End If
 
-    For y = 1 To siz ' unuseful move
-        For x = 1 To siz
-            If lau(x, y) = 1 Then
-                If lau(x - 1, y + 1) = 0 Then
-                    Swap lau(x, y), lau(x - 1, y + 1)
-                    humngo h2
-                    Swap lau(x, y), lau(x - 1, y + 1)
-                    If h2 > h1 Then
-                        h1 = h2
-                        If sug = 1 Then cx1 = x: cy1 = y: cx2 = x - 1: cy2 = y + 1
+    For y = 1 To boardSize ' quiet move (no capture available)
+        For x = 1 To boardSize
+            If board(x, y) = 1 Then
+                If board(x - 1, y + 1) = 0 Then
+                    Swap board(x, y), board(x - 1, y + 1)
+                    searchOppMoves replyScore
+                    Swap board(x, y), board(x - 1, y + 1)
+                    If replyScore > bestScore Then
+                        bestScore = replyScore
+                        If depth = 1 Then compFromX = x: compFromY = y: compToX = x - 1: compToY = y + 1
                     End If
                 End If
 
                     End If
                 End If
 
-                If lau(x + 1, y + 1) = 0 Then
-                    Swap lau(x, y), lau(x + 1, y + 1)
-                    humngo h2
-                    Swap lau(x, y), lau(x + 1, y + 1)
-                    If h2 > h1 Then
-                        h1 = h2
-                        If sug = 1 Then cx1 = x: cy1 = y: cx2 = x + 1: cy2 = y + 1
+                If board(x + 1, y + 1) = 0 Then
+                    Swap board(x, y), board(x + 1, y + 1)
+                    searchOppMoves replyScore
+                    Swap board(x, y), board(x + 1, y + 1)
+                    If replyScore > bestScore Then
+                        bestScore = replyScore
+                        If depth = 1 Then compFromX = x: compFromY = y: compToX = x + 1: compToY = y + 1
                     End If
                 End If
 
             End If
         Next x
     Next y
                     End If
                 End If
 
             End If
         Next x
     Next y
-    h = h1
+    score = bestScore
     10
     10
-    sug = sug - 1
+    depth = depth - 1
     6
 End Sub
 
     6
 End Sub
 
-Sub compki (m, h, x1, y1)
-    h1 = 0
+Sub evalCapture (jumps, score, landX, landY)
+    ' Evaluation stub for the position after a capture; currently always returns 0.
+    bestScore = 0
 
 
-    For y = 1 To siz
-        For x = 1 To siz
+    For y = 1 To boardSize
+        For x = 1 To boardSize
         Next x
     Next y
         Next x
     Next y
-    h = h1
+    score = bestScore
 
 End Sub
 
 
 End Sub
 
-Sub freet
-    a$ = InKey$
-    If a$ = "" Then
+Sub pollKeys
+    ' Poll the keyboard, echo typed characters and run the command on Enter.
+    key$ = InKey$
+    If key$ = "" Then
     Else
     Else
-        If a$ = Chr$(8) Then
-            If Len(stri$) > 0 Then
-                stri$ = Left$(stri$, Len(stri$) - 1): GoTo 3
+        If key$ = Chr$(8) Then ' Backspace
+            If Len(cmdLine$) > 0 Then
+                cmdLine$ = Left$(cmdLine$, Len(cmdLine$) - 1): GoTo 3
             End If
         End If
             End If
         End If
-        If a$ = Chr$(13) Then
-            If Len(stri$) > 0 Then
-                msg stri$, 7
-                cmd stri$
-                stri$ = ""
+        If key$ = Chr$(13) Then ' Enter: run the typed command
+            If Len(cmdLine$) > 0 Then
+                showMessage cmdLine$, 7
+                doCommand cmdLine$
+                cmdLine$ = ""
             End If
             GoTo 3
         End If
             End If
             GoTo 3
         End If
-        stri$ = stri$ + a$
+        cmdLine$ = cmdLine$ + key$
         3
         Line (400, 468)-(639, 479), 1, BF
         3
         Line (400, 468)-(639, 479), 1, BF
-        prn 405, 469, 14, stri$
+        drawText 405, 469, 14, cmdLine$
     End If
 End Sub
 
     End If
 End Sub
 
-Sub getfnt
+Sub grabFont
+    ' Capture the ROM 8x8 character bitmaps into the font() array for later drawing.
     Screen 13
     Screen 13
-    For a = 0 To 255
-        If (a > 5) And (a < 17) Then GoTo 2
+    For code = 0 To 255
+        If (code > 5) And (code < 17) Then GoTo 2 ' skip control characters that would mess up the screen
         Locate 1, 1
         Locate 1, 1
-        Print Chr$(a)
+        Print Chr$(code)
         2
         For y = 0 To 7
             For x = 0 To 7
         2
         For y = 0 To 7
             For x = 0 To 7
-                font(x, y, a) = Point(x, y)
+                font(x, y, code) = Point(x, y)
             Next x
         Next y
             Next x
         Next y
-    Next a
+    Next code
 
 End Sub
 
 
 End Sub
 
-Sub humngo (h)
-    npos = npos + 1
-    h1 = 1000
-
-    For y = siz To 1 Step -1
-        For x = siz To 1 Step -1
-            If lau(x, y) = 2 Then
-                If lau(x - 1, y - 1) = 0 Then
-                    Swap lau(x, y), lau(x - 1, y - 1)
-                    compgo h2
-                    Swap lau(x, y), lau(x - 1, y - 1)
-                    If h2 < h1 Then h1 = h2
+Sub searchOppMoves (score)
+    ' Recursively search the opponent's (human) replies; returns score via parameter.
+    positions = positions + 1
+    bestScore = 1000
+
+    For y = boardSize To 1 Step -1
+        For x = boardSize To 1 Step -1
+            If board(x, y) = 2 Then
+                If board(x - 1, y - 1) = 0 Then
+                    Swap board(x, y), board(x - 1, y - 1)
+                    searchCompMoves replyScore
+                    Swap board(x, y), board(x - 1, y - 1)
+                    If replyScore < bestScore Then bestScore = replyScore
                 End If
 
                 End If
 
-                If lau(x + 1, y - 1) = 0 Then
-                    Swap lau(x, y), lau(x + 1, y - 1)
-                    compgo h2
-                    Swap lau(x, y), lau(x + 1, y - 1)
-                    If h2 < h1 Then h1 = h2
+                If board(x + 1, y - 1) = 0 Then
+                    Swap board(x, y), board(x + 1, y - 1)
+                    searchCompMoves replyScore
+                    Swap board(x, y), board(x + 1, y - 1)
+                    If replyScore < bestScore Then bestScore = replyScore
                 End If
 
                 End If
 
-                If (lau(x - 1, y - 1) = 1) And (lau(x - 2, y - 2) = 0) Then
-                    Swap lau(x, y), lau(x - 2, y - 2)
-                    lau(x - 1, y - 1) = 0
-                    humngo h2
-                    lau(x - 1, y - 1) = 1
-                    Swap lau(x, y), lau(x - 2, y - 2)
-                    If h2 - 1 < h1 Then h1 = h2 - 1
+                If (board(x - 1, y - 1) = 1) And (board(x - 2, y - 2) = 0) Then
+                    Swap board(x, y), board(x - 2, y - 2)
+                    board(x - 1, y - 1) = 0
+                    searchOppMoves replyScore
+                    board(x - 1, y - 1) = 1
+                    Swap board(x, y), board(x - 2, y - 2)
+                    If replyScore - 1 < bestScore Then bestScore = replyScore - 1
                 End If
 
                 End If
 
-                If (lau(x + 1, y - 1) = 1) And (lau(x + 2, y - 2) = 0) Then
-                    Swap lau(x, y), lau(x + 2, y - 2)
-                    lau(x + 1, y - 1) = 0
-                    humngo h2
-                    lau(x + 1, y - 1) = 1
-                    Swap lau(x, y), lau(x + 2, y - 2)
-                    If h2 - 1 < h1 Then h1 = h2 - 1
+                If (board(x + 1, y - 1) = 1) And (board(x + 2, y - 2) = 0) Then
+                    Swap board(x, y), board(x + 2, y - 2)
+                    board(x + 1, y - 1) = 0
+                    searchOppMoves replyScore
+                    board(x + 1, y - 1) = 1
+                    Swap board(x, y), board(x + 2, y - 2)
+                    If replyScore - 1 < bestScore Then bestScore = replyScore - 1
                 End If
 
             End If
         Next x
     Next y
                 End If
 
             End If
         Next x
     Next y
-    h = h1
+    score = bestScore
 End Sub
 
 End Sub
 
-Sub mklau
-    For y = -1 To siz + 2
-        For x = -1 To siz + 2
-            lau(x, y) = -1
+Sub initBoard
+    ' Fill the board array with the starting position.
+    ' Mark the border ring as off-board (-1).
+    For y = -1 To boardSize + 2
+        For x = -1 To boardSize + 2
+            board(x, y) = -1
         Next x
     Next y
 
         Next x
     Next y
 
-    For y = 1 To siz
-        For x = 1 To siz
-            lau(x, y) = 0
+    For y = 1 To boardSize
+        For x = 1 To boardSize
+            board(x, y) = 0
         Next x
     Next y
 
         Next x
     Next y
 
-    For y = 1 To ri
-        For x = 1 To siz
-            If (x + y + fi) / 2 = Int((x + y + fi) / 2) Then
-                lau(x, y) = 1
+    ' Computer pieces (1) on the top rows.
+    For y = 1 To pieceRows
+        For x = 1 To boardSize
+            If (x + y + parity) / 2 = Int((x + y + parity) / 2) Then
+                board(x, y) = 1
             End If
         Next x
     Next y
 
             End If
         Next x
     Next y
 
-    For y = siz - ri + 1 To siz
-        For x = 1 To siz
-            If (x + y + fi) / 2 = Int((x + y + fi) / 2) Then
-                lau(x, y) = 2
+    ' Human pieces (2) on the bottom rows.
+    For y = boardSize - pieceRows + 1 To boardSize
+        For x = 1 To boardSize
+            If (x + y + parity) / 2 = Int((x + y + parity) / 2) Then
+                board(x, y) = 2
             End If
         Next x
     Next y
 
 End Sub
 
             End If
         Next x
     Next y
 
 End Sub
 
-Sub msg (a$, c)
+Sub showMessage (text$, fgColor)
+    ' Scroll the message area up by one line and print a new message.
     Dim buf(1 To 10000)
     Dim buf(1 To 10000)
+    ' Shift the message area one text line (8 pixels) up.
     For x = 400 To 630 Step 40
         Get (x, 8)-(x + 39, 467), buf(1)
         Put (x, 0), buf(1), PSet
     Next x
     Line (400, 460)-(639, 467), 0, BF
     For x = 400 To 630 Step 40
         Get (x, 8)-(x + 39, 467), buf(1)
         Put (x, 0), buf(1), PSet
     Next x
     Line (400, 460)-(639, 467), 0, BF
-    prn 405, 460, c, a$
+    drawText 405, 460, fgColor, text$
 End Sub
 
 End Sub
 
-Sub playg
+Sub gameLoop
+    ' Main game loop: alternate computer and human turns forever.
     'GOTO 7
     1
     'GOTO 7
     1
-    thinkc
-    show
+    computerMove
+    drawBoard
     7
     7
-    thinkh
-    show
+    humanMove
+    drawBoard
     GoTo 1
 
 End Sub
 
     GoTo 1
 
 End Sub
 
-Sub prn (x, y, c, a$)
-    x1 = x
-    y1 = y
-    For a = 1 To Len(a$)
-        b = Asc(Right$(Left$(a$, a), 1))
-        For y2 = 0 To 7
-            For x2 = 0 To 7
-                If font(x2, y2, b) > 0 Then PSet (x2 + x1, y2 + y1), c
-            Next x2
-        Next y2
-        x1 = x1 + 8
-    Next a
+Sub drawText (pixelX, pixelY, fgColor, text$)
+    ' Draw a string at pixel position using the captured 8x8 font.
+    drawX = pixelX
+    drawY = pixelY
+    For charIdx = 1 To Len(text$)
+        code = Asc(Right$(Left$(text$, charIdx), 1))
+        For fontY = 0 To 7
+            For fontX = 0 To 7
+                If font(fontX, fontY, code) > 0 Then PSet (fontX + drawX, fontY + drawY), fgColor
+            Next fontX
+        Next fontY
+        drawX = drawX + 8
+    Next charIdx
 End Sub
 
 End Sub
 
-Sub show
-    For y = 1 To siz
-        For x = 1 To siz
-            showr x, y
+Sub drawBoard
+    ' Redraw all squares plus the coordinate labels around the board.
+    For y = 1 To boardSize
+        For x = 1 To boardSize
+            drawSquare x, y
         Next x
     Next y
 
         Next x
     Next y
 
-    sp = rs / 2
-    For x = 1 To siz
-        prn ((x - 1) * rs + 12 + sp), 2, 10, Chr$(64 + x)
-        prn ((x - 1) * rs + 12 + sp), siz * rs + 11, 10, Chr$(64 + x)
+    ' Draw the column letters (top and bottom) and row numbers (left and right).
+    halfSize = squareSize / 2
+    For x = 1 To boardSize
+        drawText ((x - 1) * squareSize + 12 + halfSize), 2, 10, Chr$(64 + x)
+        drawText ((x - 1) * squareSize + 12 + halfSize), boardSize * squareSize + 11, 10, Chr$(64 + x)
     Next x
 
     Next x
 
-    For y = 1 To siz
-        a$ = Str$(y)
-        a$ = Right$(a$, Len(a$) - 1)
-        prn 15 - (Len(a$) * 8), (y - 1) * rs + sp + 7, 10, a$
-        prn (siz * rs + 16), (y - 1) * rs + sp + 7, 10, a$
+    For y = 1 To boardSize
+        label$ = Str$(y)
+        label$ = Right$(label$, Len(label$) - 1)
+        drawText 15 - (Len(label$) * 8), (y - 1) * squareSize + halfSize + 7, 10, label$
+        drawText (boardSize * squareSize + 16), (y - 1) * squareSize + halfSize + 7, 10, label$
     Next y
 
 End Sub
 
     Next y
 
 End Sub
 
-Sub showr (x, y)
-    If (x + y + fi) / 2 = Int((x + y + fi) / 2) Then c = 8 Else c = 7
-    x1 = (x - 1) * rs + 15
-    y1 = (y - 1) * rs + 10
-    Line (x1, y1)-(x1 + rs - 1, y1 + rs - 1), c, BF
-
-    If lau(x, y) > 0 Then
-        sp = rs / 2
-        If lau(x, y) = 1 Then c1 = 15 Else c1 = 14
-        Circle (x1 + sp, y1 + sp), sp - 1, c1
-        Paint (x1 + sp, y1 + sp), c1
+Sub drawSquare (x, y)
+    ' Redraw one board square and the piece standing on it, if any.
+    If (x + y + parity) / 2 = Int((x + y + parity) / 2) Then sqColor = 8 Else sqColor = 7
+    pixelX = (x - 1) * squareSize + 15
+    pixelY = (y - 1) * squareSize + 10
+    Line (pixelX, pixelY)-(pixelX + squareSize - 1, pixelY + squareSize - 1), sqColor, BF
+
+    If board(x, y) > 0 Then
+        halfSize = squareSize / 2
+        If board(x, y) = 1 Then pieceColor = 15 Else pieceColor = 14
+        Circle (pixelX + halfSize, pixelY + halfSize), halfSize - 1, pieceColor
+        Paint (pixelX + halfSize, pixelY + halfSize), pieceColor
     End If
 End Sub
 
     End If
 End Sub
 
-Sub start
-    getfnt
+Sub initGame
+    ' One-time setup: grab font, set screen mode, draw layout, compute square size.
+    grabFont
     Screen 12
     Line (399, 0)-(399, 479), 13
     Screen 12
     Line (399, 0)-(399, 479), 13
-    msg "Type 'h' for help.", 14
+    showMessage "Type 'h' for help.", 14
 
 
-    rs = Int(370 / siz)
+    squareSize = Int(370 / boardSize)
 
 End Sub
 
 
 End Sub
 
-Sub thinkc
-    msg "Computer turn.", 14
-    sug = 0
-    npos = 0
-    cx1 = -1
-
-    compgo h
-    cmd "n"
-    If cx1 = -1 Then msg "You won!", 10: msg "--------", 10: System
-
-    If h <= -2 Then msg "Oh no...", 10
-    If h = -1 Then msg "Oops!", 10
-    If h = 1 Then msg "Yess! I will eat soon!", 10
-    If h >= 2 Then msg "HA HA HA YOU ARE IN TROUBLE!", 10
-
-    If Abs(cx1 - cx2) = 2 Then
-        cx3 = (cx1 + cx2) / 2
-        cy3 = (cy1 + cy2) / 2
-        lau(cx3, cy3) = 0
-        showr cx3, cy3
+Sub computerMove
+    ' The computer's turn: search the best move, comment on it and play it.
+    showMessage "Computer turn.", 14
+    depth = 0
+    positions = 0
+    compFromX = -1
+
+    searchCompMoves score
+    doCommand "n" ' print "positions processed: N"
+    If compFromX = -1 Then showMessage "You won!", 10: showMessage "--------", 10: System
+
+    If score <= -2 Then showMessage "Oh no...", 10
+    If score = -1 Then showMessage "Oops!", 10
+    If score = 1 Then showMessage "Yess! I will eat soon!", 10
+    If score >= 2 Then showMessage "HA HA HA YOU ARE IN TROUBLE!", 10
+
+    ' If the move was a jump, remove the captured piece from the middle square.
+    If Abs(compFromX - compToX) = 2 Then
+        midX = (compFromX + compToX) / 2
+        midY = (compFromY + compToY) / 2
+        board(midX, midY) = 0
+        drawSquare midX, midY
     End If
 
     End If
 
-    Swap lau(cx1, cy1), lau(cx2, cy2)
-    showr cx1, cy1
-    showr cx2, cy2
+    Swap board(compFromX, compFromY), board(compToX, compToY)
+    drawSquare compFromX, compFromY
+    drawSquare compToX, compToY
 
 End Sub
 
 
 End Sub
 
-Sub thinkh
-    msg "Your turn.", 14
+Sub humanMove
+    ' The human's turn: wait for a typed move and play it.
+    showMessage "Your turn.", 14
     5
     5
-    freet
-    If humx1 = 0 Then GoTo 5
-    Swap lau(humx2, humy2), lau(humx1, humy1)
-    showr humx1, humy1
-    showr humx2, humy2
-    If Abs(humx1 - humx2) = 2 Then
-        cx3 = (humx1 + humx2) / 2
-        cy3 = (humy1 + humy2) / 2
-        lau(cx3, cy3) = 0
-        showr cx3, cy3
+    ' Wait until the player types a move ("m <from> <to>", see doCommand).
+    pollKeys
+    If humFromX = 0 Then GoTo 5
+    Swap board(humToX, humToY), board(humFromX, humFromY)
+    drawSquare humFromX, humFromY
+    drawSquare humToX, humToY
+    ' If the move was a jump, remove the captured piece from the middle square.
+    If Abs(humFromX - humToX) = 2 Then
+        midX = (humFromX + humToX) / 2
+        midY = (humFromY + humToY) / 2
+        board(midX, midY) = 0
+        drawSquare midX, midY
     End If
 
     End If
 
-    humx1 = 0
+    humFromX = 0
 End Sub
 End Sub
index 9b52fc5..a09856a 100644 (file)
@@ -1,11 +1,11 @@
 DECLARE SUB formula (x!, y!, z!)\r
 DECLARE SUB formula (x!, y!, z!)\r
-DECLARE SUB graaf ()\r
-DECLARE SUB mkgr3 (x1!, y1!, z1!)\r
-DECLARE SUB mkgr2 (x1!, y1!, z1!)\r
-DECLARE SUB mkgr (x1!, y1!, z1!)\r
+DECLARE SUB buildSurface ()\r
+DECLARE SUB makeGridXZ (x1!, y1!, z1!)\r
+DECLARE SUB makeGridXY (x1!, y1!, z1!)\r
+DECLARE SUB makeGridZY (x1!, y1!, z1!)\r
 DECLARE SUB start ()\r
 DECLARE SUB start ()\r
-DECLARE SUB getcor ()\r
-DECLARE SUB nait3d ()\r
+DECLARE SUB setupScene ()\r
+DECLARE SUB mainLoop ()\r
 ' 3D heightmap explorer. Allows to visualize heightmap for arbitrary function.\r
 ' Inspect and edit function "formula" to visualize alternative mathematical functions.\r
 \r
 ' 3D heightmap explorer. Allows to visualize heightmap for arbitrary function.\r
 ' Inspect and edit function "formula" to visualize alternative mathematical functions.\r
 \r
@@ -17,6 +17,7 @@ DECLARE SUB nait3d ()
 ' Changelog:\r
 ' 2002, Initial version\r
 ' 2024.09, Improved program readability\r
 ' Changelog:\r
 ' 2002, Initial version\r
 ' 2024.09, Improved program readability\r
+' 2026, Improved program readability\r
 \r
 ' Keyboard controls:\r
 ' Cursor keys - move around\r
 \r
 ' Keyboard controls:\r
 ' Cursor keys - move around\r
@@ -26,42 +27,48 @@ DECLARE SUB nait3d ()
 \r
 ' Type your formula to sub module "formula".\r
 ' X & Y are surface coordinates. Z must be formula\r
 \r
 ' Type your formula to sub module "formula".\r
 ' X & Y are surface coordinates. Z must be formula\r
-' result, indicating height. "tm" variable counts\r
+' result, indicating height. "frameCount" variable counts\r
 ' frames. Use it in your formula to make graph moving in time.\r
 \r
 DIM SHARED vertexX(4000), vertexY(4000), vertexZ(4000)\r
 DIM SHARED x(4000), y(4000), z(4000)\r
 \r
 DIM SHARED xo(4000), yo(4000), zo(4000)\r
 ' frames. Use it in your formula to make graph moving in time.\r
 \r
 DIM SHARED vertexX(4000), vertexY(4000), vertexZ(4000)\r
 DIM SHARED x(4000), y(4000), z(4000)\r
 \r
 DIM SHARED xo(4000), yo(4000), zo(4000)\r
+' NOTE: in QB45 a scalar and an array may share one name with separate\r
+' storage. xo/yo/zo are used BOTH as scalars (camera-relative coordinates\r
+' in mainLoop) and as arrays xo()/yo() that remember each vertex position\r
+' from the previous frame, so stale lines can be erased before redrawing.\r
 DIM SHARED linePoint1(4000), linePoint2(4000)\r
 DIM SHARED lineColor(4000)\r
 DIM SHARED vertexCount, lineCount\r
 DIM SHARED linePoint1(4000), linePoint2(4000)\r
 DIM SHARED lineColor(4000)\r
 DIM SHARED vertexCount, lineCount\r
-DIM SHARED tmvertexCount, tmlineCount, tm\r
-DIM SHARED myx, myy, myz, mye, myk\r
+DIM SHARED staticVertexCount, staticLineCount, frameCount\r
+DIM SHARED camX, camY, camZ, speedForward, speedSideways\r
 \r
 \r
-myx = 520\r
-myy = -250\r
-myz = -1000\r
-tm = 0\r
+camX = 520\r
+camY = -250\r
+camZ = -1000\r
+frameCount = 0\r
 \r
 \r
+' On numeric overflow or similar, print a message and keep running (RESUME).\r
 ON ERROR GOTO 3\r
 start\r
 \r
 ON ERROR GOTO 3\r
 start\r
 \r
-nait3d\r
+mainLoop\r
 \r
 3\r
 \r
 3\r
+' Estonian: "Somewhere in the program a number went over the allowed limits!!!"\r
 PRINT "Kuskil programmis l�ks mingi arv �le lubatud piiride!!!"\r
 RESUME\r
 \r
 PRINT "Kuskil programmis l�ks mingi arv �le lubatud piiride!!!"\r
 RESUME\r
 \r
-' This subroutine initializes the coordinate system and sets up the grid.\r
-SUB getcor\r
-    c = 12\r
+SUB setupScene\r
+    ' Set up the static scene: background measuring grids and the 3D cross.\r
+    drawColor = 12 ' light red\r
 \r
     ' Create background 2D grids along every axis in 3D space\r
     ' to help viewer perceive scale.\r
 \r
     ' Create background 2D grids along every axis in 3D space\r
     ' to help viewer perceive scale.\r
-    mkgr -500, 0, 0\r
-    mkgr2 0, 0, 500\r
-    mkgr3 0, -500, 0\r
+    makeGridZY -500, 0, 0\r
+    makeGridXY 0, 0, 500\r
+    makeGridXZ 0, -500, 0\r
 \r
     ' Place center-crossing pink lines across every axis (3D cross).\r
     ' This is to help viewer to see where zero point on the graph is.\r
 \r
     ' Place center-crossing pink lines across every axis (3D cross).\r
     ' This is to help viewer to see where zero point on the graph is.\r
@@ -94,213 +101,218 @@ SUB getcor
     ' Add lines for the 3D cross.\r
     linePoint1(lineCount + 1) = vertexCount + 1\r
     linePoint2(lineCount + 1) = vertexCount + 2\r
     ' Add lines for the 3D cross.\r
     linePoint1(lineCount + 1) = vertexCount + 1\r
     linePoint2(lineCount + 1) = vertexCount + 2\r
-    lineColor(lineCount + 1) = c\r
+    lineColor(lineCount + 1) = drawColor\r
 \r
     linePoint1(lineCount + 2) = vertexCount + 3\r
     linePoint2(lineCount + 2) = vertexCount + 4\r
 \r
     linePoint1(lineCount + 2) = vertexCount + 3\r
     linePoint2(lineCount + 2) = vertexCount + 4\r
-    lineColor(lineCount + 2) = c\r
+    lineColor(lineCount + 2) = drawColor\r
 \r
     linePoint1(lineCount + 3) = vertexCount + 5\r
     linePoint2(lineCount + 3) = vertexCount + 6\r
 \r
     linePoint1(lineCount + 3) = vertexCount + 5\r
     linePoint2(lineCount + 3) = vertexCount + 6\r
-    lineColor(lineCount + 3) = c\r
+    lineColor(lineCount + 3) = drawColor\r
 \r
     ' Update the vertex and line counts\r
     vertexCount = vertexCount + 6\r
     lineCount = lineCount + 3\r
 \r
     ' Update the vertex and line counts\r
     vertexCount = vertexCount + 6\r
     lineCount = lineCount + 3\r
-    tmvertexCount = vertexCount\r
-    tmlineCount = lineCount\r
+    staticVertexCount = vertexCount\r
+    staticLineCount = lineCount\r
 END SUB\r
 \r
 END SUB\r
 \r
-' This subroutine generates a grid of points based on the formula\r
-SUB graaf\r
-    c = 14\r
+SUB buildSurface\r
+    ' Build the heightmap surface: evaluate the formula on a 21x21 point\r
+    ' grid and connect neighbors into the yellow mesh. Rebuilt every frame.\r
+    drawColor = 14 ' yellow\r
 \r
 \r
-    d = 0\r
-    e = 0\r
+    vertsAdded = 0\r
+    linesAdded = 0\r
     FOR x = -500 TO 500 STEP 50\r
         FOR z = -500 TO 500 STEP 50\r
 \r
             ' Increment the vertex count and add a new vertex\r
     FOR x = -500 TO 500 STEP 50\r
         FOR z = -500 TO 500 STEP 50\r
 \r
             ' Increment the vertex count and add a new vertex\r
-            d = d + 1\r
-            vertexX(vertexCount + d) = x\r
+            vertsAdded = vertsAdded + 1\r
+            vertexX(vertexCount + vertsAdded) = x\r
             formula x / 50, z / 50, y   ' evaluate formula that we want to visualize\r
             formula x / 50, z / 50, y   ' evaluate formula that we want to visualize\r
-            vertexY(vertexCount + d) = y * 50\r
-            vertexZ(vertexCount + d) = z\r
+            vertexY(vertexCount + vertsAdded) = y * 50\r
+            vertexZ(vertexCount + vertsAdded) = z\r
 \r
             ' Connect current point on the grid with neighbors\r
             IF z > -500 THEN\r
 \r
             ' Connect current point on the grid with neighbors\r
             IF z > -500 THEN\r
-                e = e + 1\r
-                linePoint1(lineCount + e) = vertexCount + d\r
-                linePoint2(lineCount + e) = vertexCount + d - 1\r
-                lineColor(lineCount + e) = c\r
+                linesAdded = linesAdded + 1\r
+                linePoint1(lineCount + linesAdded) = vertexCount + vertsAdded\r
+                linePoint2(lineCount + linesAdded) = vertexCount + vertsAdded - 1\r
+                lineColor(lineCount + linesAdded) = drawColor\r
             END IF\r
 \r
             IF x > -500 THEN\r
             END IF\r
 \r
             IF x > -500 THEN\r
-                e = e + 1\r
-                linePoint1(lineCount + e) = vertexCount + d\r
-                linePoint2(lineCount + e) = vertexCount + d - 21\r
-                lineColor(lineCount + e) = c\r
+                linesAdded = linesAdded + 1\r
+                linePoint1(lineCount + linesAdded) = vertexCount + vertsAdded\r
+                linePoint2(lineCount + linesAdded) = vertexCount + vertsAdded - 21\r
+                lineColor(lineCount + linesAdded) = drawColor\r
             END IF\r
 \r
         NEXT z\r
     NEXT x\r
 \r
     ' Update the vertex and line counts\r
             END IF\r
 \r
         NEXT z\r
     NEXT x\r
 \r
     ' Update the vertex and line counts\r
-    vertexCount = vertexCount + d\r
-    lineCount = lineCount + e\r
+    vertexCount = vertexCount + vertsAdded\r
+    lineCount = lineCount + linesAdded\r
 END SUB\r
 \r
 END SUB\r
 \r
-' This subroutine generates a measuring grid in the ZY plane.\r
-SUB mkgr (x1, y1, z1)\r
-    c = 3\r
+SUB makeGridZY (x1, y1, z1)\r
+    ' Generate a measuring grid in the ZY plane (drawn cyan).\r
+    drawColor = 3\r
 \r
 \r
-    d = 0\r
-    e = 0\r
+    vertsAdded = 0\r
+    linesAdded = 0\r
     FOR z = -500 TO 500 STEP 100\r
         FOR y = -500 TO 500 STEP 100\r
 \r
             ' Increment the vertex count and add a new vertex\r
     FOR z = -500 TO 500 STEP 100\r
         FOR y = -500 TO 500 STEP 100\r
 \r
             ' Increment the vertex count and add a new vertex\r
-            d = d + 1\r
-            vertexX(vertexCount + d) = x1\r
-            vertexY(vertexCount + d) = y1 + y\r
-            vertexZ(vertexCount + d) = z1 + z\r
+            vertsAdded = vertsAdded + 1\r
+            vertexX(vertexCount + vertsAdded) = x1\r
+            vertexY(vertexCount + vertsAdded) = y1 + y\r
+            vertexZ(vertexCount + vertsAdded) = z1 + z\r
 \r
             ' Add lines to the line array if necessary\r
             IF y > -500 THEN\r
 \r
             ' Add lines to the line array if necessary\r
             IF y > -500 THEN\r
-                e = e + 1\r
-                linePoint1(lineCount + e) = vertexCount + d\r
-                linePoint2(lineCount + e) = vertexCount + d - 1\r
-                lineColor(lineCount + e) = c\r
+                linesAdded = linesAdded + 1\r
+                linePoint1(lineCount + linesAdded) = vertexCount + vertsAdded\r
+                linePoint2(lineCount + linesAdded) = vertexCount + vertsAdded - 1\r
+                lineColor(lineCount + linesAdded) = drawColor\r
             END IF\r
 \r
             IF z > -500 THEN\r
             END IF\r
 \r
             IF z > -500 THEN\r
-                e = e + 1\r
-                linePoint1(lineCount + e) = vertexCount + d\r
-                linePoint2(lineCount + e) = vertexCount + d - 11\r
-                lineColor(lineCount + e) = c\r
+                linesAdded = linesAdded + 1\r
+                linePoint1(lineCount + linesAdded) = vertexCount + vertsAdded\r
+                linePoint2(lineCount + linesAdded) = vertexCount + vertsAdded - 11\r
+                lineColor(lineCount + linesAdded) = drawColor\r
             END IF\r
 \r
         NEXT y\r
     NEXT z\r
 \r
     ' Update the vertex and line counts\r
             END IF\r
 \r
         NEXT y\r
     NEXT z\r
 \r
     ' Update the vertex and line counts\r
-    vertexCount = vertexCount + d\r
-    lineCount = lineCount + e\r
+    vertexCount = vertexCount + vertsAdded\r
+    lineCount = lineCount + linesAdded\r
 END SUB\r
 \r
 END SUB\r
 \r
-' This subroutine generates a measuring grid XY plane.\r
-SUB mkgr2 (x1, y1, z1)\r
-    c = 3\r
+SUB makeGridXY (x1, y1, z1)\r
+    ' Generate a measuring grid in the XY plane (drawn cyan).\r
+    drawColor = 3\r
 \r
 \r
-    d = 0\r
-    e = 0\r
+    vertsAdded = 0\r
+    linesAdded = 0\r
     FOR x = -500 TO 500 STEP 100\r
         FOR y = -500 TO 500 STEP 100\r
 \r
             ' Increment the vertex count and add a new vertex\r
     FOR x = -500 TO 500 STEP 100\r
         FOR y = -500 TO 500 STEP 100\r
 \r
             ' Increment the vertex count and add a new vertex\r
-            d = d + 1\r
-            vertexX(vertexCount + d) = x1 + x\r
-            vertexY(vertexCount + d) = y1 + y\r
-            vertexZ(vertexCount + d) = z1\r
+            vertsAdded = vertsAdded + 1\r
+            vertexX(vertexCount + vertsAdded) = x1 + x\r
+            vertexY(vertexCount + vertsAdded) = y1 + y\r
+            vertexZ(vertexCount + vertsAdded) = z1\r
 \r
             ' Add lines to the line array if necessary\r
             IF y > -500 THEN\r
 \r
             ' Add lines to the line array if necessary\r
             IF y > -500 THEN\r
-                e = e + 1\r
-                linePoint1(lineCount + e) = vertexCount + d\r
-                linePoint2(lineCount + e) = vertexCount + d - 1\r
-                lineColor(lineCount + e) = c\r
+                linesAdded = linesAdded + 1\r
+                linePoint1(lineCount + linesAdded) = vertexCount + vertsAdded\r
+                linePoint2(lineCount + linesAdded) = vertexCount + vertsAdded - 1\r
+                lineColor(lineCount + linesAdded) = drawColor\r
             END IF\r
 \r
             IF x > -500 THEN\r
             END IF\r
 \r
             IF x > -500 THEN\r
-                e = e + 1\r
-                linePoint1(lineCount + e) = vertexCount + d\r
-                linePoint2(lineCount + e) = vertexCount + d - 11\r
-                lineColor(lineCount + e) = c\r
+                linesAdded = linesAdded + 1\r
+                linePoint1(lineCount + linesAdded) = vertexCount + vertsAdded\r
+                linePoint2(lineCount + linesAdded) = vertexCount + vertsAdded - 11\r
+                lineColor(lineCount + linesAdded) = drawColor\r
             END IF\r
 \r
         NEXT y\r
     NEXT x\r
 \r
     ' Update the vertex and line counts\r
             END IF\r
 \r
         NEXT y\r
     NEXT x\r
 \r
     ' Update the vertex and line counts\r
-    vertexCount = vertexCount + d\r
-    lineCount = lineCount + e\r
+    vertexCount = vertexCount + vertsAdded\r
+    lineCount = lineCount + linesAdded\r
 END SUB\r
 \r
 END SUB\r
 \r
-' This subroutine generates a measuring grid in the XZ plane.\r
-SUB mkgr3 (x1, y1, z1)\r
-    c = 3\r
+SUB makeGridXZ (x1, y1, z1)\r
+    ' Generate a measuring grid in the XZ plane (drawn cyan).\r
+    drawColor = 3\r
 \r
 \r
-    d = 0\r
-    e = 0\r
+    vertsAdded = 0\r
+    linesAdded = 0\r
     FOR x = -500 TO 500 STEP 100\r
         FOR z = -500 TO 500 STEP 100\r
 \r
             ' Increment the vertex count and add a new vertex\r
     FOR x = -500 TO 500 STEP 100\r
         FOR z = -500 TO 500 STEP 100\r
 \r
             ' Increment the vertex count and add a new vertex\r
-            d = d + 1\r
-            vertexX(vertexCount + d) = x1 + x\r
-            vertexY(vertexCount + d) = y1\r
-            vertexZ(vertexCount + d) = z\r
+            vertsAdded = vertsAdded + 1\r
+            vertexX(vertexCount + vertsAdded) = x1 + x\r
+            vertexY(vertexCount + vertsAdded) = y1\r
+            vertexZ(vertexCount + vertsAdded) = z\r
 \r
             ' Add lines to the line array if necessary\r
             IF z > -500 THEN\r
 \r
             ' Add lines to the line array if necessary\r
             IF z > -500 THEN\r
-                e = e + 1\r
-                linePoint1(lineCount + e) = vertexCount + d\r
-                linePoint2(lineCount + e) = vertexCount + d - 1\r
-                lineColor(lineCount + e) = c\r
+                linesAdded = linesAdded + 1\r
+                linePoint1(lineCount + linesAdded) = vertexCount + vertsAdded\r
+                linePoint2(lineCount + linesAdded) = vertexCount + vertsAdded - 1\r
+                lineColor(lineCount + linesAdded) = drawColor\r
             END IF\r
 \r
             IF x > -500 THEN\r
             END IF\r
 \r
             IF x > -500 THEN\r
-                e = e + 1\r
-                linePoint1(lineCount + e) = vertexCount + d\r
-                linePoint2(lineCount + e) = vertexCount + d - 11\r
-                lineColor(lineCount + e) = c\r
+                linesAdded = linesAdded + 1\r
+                linePoint1(lineCount + linesAdded) = vertexCount + vertsAdded\r
+                linePoint2(lineCount + linesAdded) = vertexCount + vertsAdded - 11\r
+                lineColor(lineCount + linesAdded) = drawColor\r
             END IF\r
 \r
         NEXT z\r
     NEXT x\r
 \r
     ' Update the vertex and line counts\r
             END IF\r
 \r
         NEXT z\r
     NEXT x\r
 \r
     ' Update the vertex and line counts\r
-    vertexCount = vertexCount + d\r
-    lineCount = lineCount + e\r
+    vertexCount = vertexCount + vertsAdded\r
+    lineCount = lineCount + linesAdded\r
 END SUB\r
 \r
 END SUB\r
 \r
-' This subroutine renders the 3D scene.\r
-SUB nait3d\r
+SUB mainLoop\r
+' Main loop: rebuild the animated surface, advance the camera, erase and\r
+' redraw all lines, handle keyboard input, repeat.\r
 \r
 1\r
 \r
 1\r
-SOUND 0, .5\r
-vertexCount = tmvertexCount\r
-lineCount = tmlineCount\r
-tm = tm + 1\r
-graaf\r
+SOUND 0, .5 ' sub-second delay workaround: 0 Hz tone is inaudible, only waits\r
+vertexCount = staticVertexCount\r
+lineCount = staticLineCount\r
+frameCount = frameCount + 1\r
+buildSurface\r
 \r
 \r
-myx = myx + SIN(deg1) * mye\r
-myz = myz + COS(deg1) * mye\r
+' Move the camera: forward and sideways relative to the view direction.\r
+camX = camX + SIN(camAngle1) * speedForward\r
+camZ = camZ + COS(camAngle1) * speedForward\r
 \r
 \r
-myx = myx + COS(deg1) * myk\r
-myz = myz - SIN(deg1) * myk\r
+camX = camX + COS(camAngle1) * speedSideways\r
+camZ = camZ - SIN(camAngle1) * speedSideways\r
 \r
 \r
-myy = myy + myyp\r
+' Climb or sink.\r
+camY = camY + speedY\r
 \r
 \r
-deg1 = deg1 + d1\r
-Deg2 = Deg2 + d2\r
+' Spin the view by the current spin speeds.\r
+camAngle1 = camAngle1 + spinSpeed1\r
+camAngle2 = camAngle2 + spinSpeed2\r
 \r
 \r
-C1 = COS(deg1): S1 = SIN(deg1)\r
-C2 = COS(Deg2): S2 = SIN(Deg2)\r
+cosAngle1 = COS(camAngle1): sinAngle1 = SIN(camAngle1)\r
+cosAngle2 = COS(camAngle2): sinAngle2 = SIN(camAngle2)\r
 \r
 ' Transform the vertices to 3D space\r
 FOR a = 1 TO vertexCount\r
 \r
 \r
 ' Transform the vertices to 3D space\r
 FOR a = 1 TO vertexCount\r
 \r
-    xo = vertexX(a) - myx\r
-    yo = -vertexY(a) - myy\r
-    zo = vertexZ(a) - myz\r
+    xo = vertexX(a) - camX\r
+    yo = -vertexY(a) - camY\r
+    zo = vertexZ(a) - camZ\r
 \r
     ' Apply rotation transformations\r
 \r
     ' Apply rotation transformations\r
-    x1 = (xo * C1 - zo * S1)\r
-    z1 = (xo * S1 + zo * C1)\r
+    x1 = (xo * cosAngle1 - zo * sinAngle1)\r
+    z1 = (xo * sinAngle1 + zo * cosAngle1)\r
 \r
 \r
-    y1 = (yo * C2 - z1 * S2)\r
-    z2 = (yo * S2 + z1 * C2)\r
+    y1 = (yo * cosAngle2 - z1 * sinAngle2)\r
+    z2 = (yo * sinAngle2 + z1 * cosAngle2)\r
 \r
     ' Project the vertices onto the 2D screen\r
     xo(a) = x(a)\r
 \r
     ' Project the vertices onto the 2D screen\r
     xo(a) = x(a)\r
@@ -316,64 +328,66 @@ NEXT
 \r
 ' Draw the lines on the screen\r
 FOR a = 1 TO lineCount\r
 \r
 ' Draw the lines on the screen\r
 FOR a = 1 TO lineCount\r
-    p1 = linePoint1(a)\r
-    p2 = linePoint2(a)\r
+    endPoint1 = linePoint1(a)\r
+    endPoint2 = linePoint2(a)\r
 \r
     ' Skip drawing if either point is off-screen\r
 \r
     ' Skip drawing if either point is off-screen\r
-    IF xo(p1) = -1 OR xo(p2) = -1 THEN\r
+    IF xo(endPoint1) = -1 OR xo(endPoint2) = -1 THEN\r
         ' Do nothing\r
     ELSE\r
         ' erase line at previous position\r
         ' Do nothing\r
     ELSE\r
         ' erase line at previous position\r
-        LINE (xo(p1), yo(p1))-(xo(p2), yo(p2)), 0\r
+        LINE (xo(endPoint1), yo(endPoint1))-(xo(endPoint2), yo(endPoint2)), 0\r
         ' draw line at new position\r
         ' draw line at new position\r
-        LINE (x(p1), y(p1))-(x(p2), y(p2)), lineColor(a)\r
+        LINE (x(endPoint1), y(endPoint1))-(x(endPoint2), y(endPoint2)), lineColor(a)\r
     END IF\r
 \r
 NEXT\r
 \r
     END IF\r
 \r
 NEXT\r
 \r
-' Handle keyboard input\r
-K$ = INKEY$\r
-IF K$ <> "" THEN\r
+' Handle keyboard input: cursor keys = forward/sideways speed, +/- = vertical\r
+' speed, 4/6/8/2 = spin speeds, space = halve all motion, q/ESC = quit.\r
+keyPress$ = INKEY$\r
+IF keyPress$ <> "" THEN\r
 \r
 \r
-    SELECT CASE K$\r
+    SELECT CASE keyPress$\r
 \r
         CASE CHR$(0) + "P"\r
 \r
         CASE CHR$(0) + "P"\r
-            mye = mye - 3\r
+            speedForward = speedForward - 3\r
 \r
         CASE CHR$(0) + "H"\r
 \r
         CASE CHR$(0) + "H"\r
-            mye = mye + 3\r
+            speedForward = speedForward + 3\r
 \r
         CASE CHR$(0) + "M"\r
 \r
         CASE CHR$(0) + "M"\r
-            myk = myk + 3\r
+            speedSideways = speedSideways + 3\r
 \r
         CASE CHR$(0) + "K"\r
 \r
         CASE CHR$(0) + "K"\r
-            myk = myk - 3\r
+            speedSideways = speedSideways - 3\r
 \r
         CASE "+"\r
 \r
         CASE "+"\r
-            myyp = myyp + 5\r
+            speedY = speedY + 5\r
 \r
         CASE "-"\r
 \r
         CASE "-"\r
-            myyp = myyp - 5\r
+            speedY = speedY - 5\r
 \r
         CASE "6"\r
 \r
         CASE "6"\r
-            d1 = d1 + .01\r
+            spinSpeed1 = spinSpeed1 + .01\r
 \r
         CASE "4"\r
 \r
         CASE "4"\r
-            d1 = d1 - .01\r
+            spinSpeed1 = spinSpeed1 - .01\r
 \r
         CASE "8"\r
 \r
         CASE "8"\r
-            d2 = d2 - .01\r
+            spinSpeed2 = spinSpeed2 - .01\r
 \r
         CASE "2"\r
 \r
         CASE "2"\r
-            d2 = d2 + .01\r
+            spinSpeed2 = spinSpeed2 + .01\r
 \r
         CASE " "\r
 \r
         CASE " "\r
-            d1 = d1 / 2\r
-            d2 = d2 / 2\r
-            d3 = d3 / 2\r
-            mye = mye / 2\r
-            myk = myk / 2\r
-            myyp = myyp / 2\r
+            ' halve everything (spinSpeed3 is a leftover, not used anywhere else)\r
+            spinSpeed1 = spinSpeed1 / 2\r
+            spinSpeed2 = spinSpeed2 / 2\r
+            spinSpeed3 = spinSpeed3 / 2\r
+            speedForward = speedForward / 2\r
+            speedSideways = speedSideways / 2\r
+            speedY = speedY / 2\r
 \r
         CASE "q"\r
             SYSTEM\r
 \r
         CASE "q"\r
             SYSTEM\r
@@ -387,8 +401,8 @@ END IF
 GOTO 1\r
 END SUB\r
 \r
 GOTO 1\r
 END SUB\r
 \r
-' This subroutine initializes the graphics and sets up the program.\r
 SUB start\r
 SUB start\r
+' Set up graphics mode and build the static scene geometry.\r
 SCREEN 12\r
 CLS\r
 \r
 SCREEN 12\r
 CLS\r
 \r
@@ -399,28 +413,28 @@ NEXT a
 vertexCount = 0\r
 lineCount = 0\r
 \r
 vertexCount = 0\r
 lineCount = 0\r
 \r
-getcor\r
+setupScene\r
 \r
 END SUB\r
 \r
 \r
 END SUB\r
 \r
-' This subroutine calculates the height of a point based on the formula\r
 SUB formula (x, y, z)\r
 SUB formula (x, y, z)\r
+' User-editable formula: return the surface height z for grid coordinate (x, y).\r
 z = 0\r
 v = SQR(x * x + y * y) ' v = distance from center, some formulas need it.\r
 \r
 ' Apply the formula to calculate the height\r
 z = 0\r
 v = SQR(x * x + y * y) ' v = distance from center, some formulas need it.\r
 \r
 ' Apply the formula to calculate the height\r
-z = z + SIN(x + y) * SIN(tm / 10) ' diagonal lines\r
+z = z + SIN(x + y) * SIN(frameCount / 10) ' diagonal lines\r
 z = z + (SQR((15 + v) * (15 - v)) - 10) ' top of the ball\r
 \r
 ' As you see, multiple formulas can be enabled simultaneously.\r
 ' Few more example formulas that you can uncomment and enable.\r
 ' z = z + RND * 1                                ' noise\r
 z = z + (SQR((15 + v) * (15 - v)) - 10) ' top of the ball\r
 \r
 ' As you see, multiple formulas can be enabled simultaneously.\r
 ' Few more example formulas that you can uncomment and enable.\r
 ' z = z + RND * 1                                ' noise\r
-' z = z + SIN((y + tm) / 2)                      ' forward moving wave\r
+' z = z + SIN((y + frameCount) / 2)                      ' forward moving wave\r
 ' z = z + SIN(v / 2) * 2                         ' circular waves\r
 ' z = z - SQR(v * 6)                             ' sharp peak\r
 ' z = z + SIN(y / 1.5) / 1.5 + COS(x / 1.5) / 1.5' custom 1\r
 ' z = z + SIN(y / 1.5) * COS(x / 1.5) / 1.5      ' custom 2\r
 ' z = z + SIN(v / 2) * 2                         ' circular waves\r
 ' z = z - SQR(v * 6)                             ' sharp peak\r
 ' z = z + SIN(y / 1.5) / 1.5 + COS(x / 1.5) / 1.5' custom 1\r
 ' z = z + SIN(y / 1.5) * COS(x / 1.5) / 1.5      ' custom 2\r
-' z = z + INT(SIN(1.5 * x * SIN(tm / 10))) * 3   ' custom 3\r
+' z = z + INT(SIN(1.5 * x * SIN(frameCount / 10))) * 3   ' custom 3\r
 ' z = z - INT(v / 5) * 3 + 3                     ' custom 4\r
 ' z = z + 3 * ((-INT((x - .3) / 20) * INT((23 + x - ABS(y * 1.2)) / 15)) + -INT(-y / 20) * -INT(-x / 20) * INT(-((x - 2) * (x - 2) + (y * 1.2 - 4) * (y * 1.2 - 4)) / 2000 + 1.01) + -INT(y / 20) * -INT(-x / 20) * INT(-((x - 2) * (x - 2) + (y * 1.2 + 4) * (y * 1.2 + 4)) / 2000 + 1.01)) ' heart\r
 \r
 ' z = z - INT(v / 5) * 3 + 3                     ' custom 4\r
 ' z = z + 3 * ((-INT((x - .3) / 20) * INT((23 + x - ABS(y * 1.2)) / 15)) + -INT(-y / 20) * -INT(-x / 20) * INT(-((x - 2) * (x - 2) + (y * 1.2 - 4) * (y * 1.2 - 4)) / 2000 + 1.01) + -INT(y / 20) * -INT(-x / 20) * INT(-((x - 2) * (x - 2) + (y * 1.2 + 4) * (y * 1.2 + 4)) / 2000 + 1.01)) ' heart\r
 \r
index 67ac754..30ce279 100755 (executable)
@@ -1,69 +1,71 @@
-DECLARE SUB jooks ()\r
-DECLARE SUB suva ()\r
-DECLARE SUB display ()\r
-DECLARE SUB clearBits ()\r
+' Test/demo program for the LPT attached 2 digit LED display.\r
+' Drives the same display hardware as the school clock (kk.bas).\r
+DECLARE SUB chaseDemo ()\r
+DECLARE SUB randomDemo ()\r
+DECLARE SUB refreshDisplay ()\r
+DECLARE SUB ledsOff ()\r
 DEFINT A-Z\r
 DEFINT A-Z\r
-DIM SHARED bit(0 TO 16)\r
+DIM SHARED ledBits(0 TO 16)\r
 \r
 \r
-suva\r
-jooks\r
+randomDemo\r
+chaseDemo\r
 \r
 \r
-clearBits\r
-bit(10) = 0\r
-bit(14) = 0\r
-bit(6) = 0\r
-bit(2) = 0\r
+ledsOff\r
+ledBits(10) = 0\r
+ledBits(14) = 0\r
+ledBits(6) = 0\r
+ledBits(2) = 0\r
 4\r
 GOTO 4\r
 \r
 4\r
 GOTO 4\r
 \r
-SUB clearBits\r
-' This subroutine initializes all bits in the bit array to 1\r
+SUB ledsOff\r
+' Turns all LEDs off: every position set to 1 (active low)\r
 FOR a = 1 TO 16\r
 FOR a = 1 TO 16\r
-    bit(a) = 1\r
+    ledBits(a) = 1\r
 NEXT a\r
 \r
 END SUB\r
 \r
 NEXT a\r
 \r
 END SUB\r
 \r
-SUB display\r
-' This subroutine displays the current state of the bit array using LPT attached display\r
-prt = &H378\r
-d = 0\r
-FOR a = 0 TO 3\r
-    c = 2 ^ a\r
+SUB refreshDisplay\r
+' Writes the 16 positions to the LPT data port, 4 per scan group\r
+lptData = &H378 ' LPT data port address\r
+bitIndex = 0\r
+FOR scanGroup = 0 TO 3\r
+    scanByte = 2 ^ scanGroup\r
     FOR b = 4 TO 7\r
     FOR b = 4 TO 7\r
-        d = d + 1\r
-        c = c + (bit(d) * 2 ^ b)\r
+        bitIndex = bitIndex + 1\r
+        scanByte = scanByte + (ledBits(bitIndex) * 2 ^ b)\r
     NEXT b\r
 \r
     NEXT b\r
 \r
-    OUT prt, c\r
-NEXT a\r
+    OUT lptData, scanByte\r
+NEXT scanGroup\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB jooks\r
-' This subroutine demonstrates a simple counting\r
+SUB chaseDemo\r
+' Running light demo: one lit position chases through all 16\r
 2\r
 2\r
-clearBits\r
-FOR a = 1 TO 16\r
-    bit(16) = 1 ' Set the most significant bit to 1\r
-    bit(a - 1) = 1 ' Set the previous bit to 1\r
-    bit(a) = 0 ' Clear the current bit\r
-    PRINT a\r
+ledsOff\r
+FOR ledPos = 1 TO 16\r
+    ledBits(16) = 1 ' Previous top position back to off\r
+    ledBits(ledPos - 1) = 1 ' Previous position back to off\r
+    ledBits(ledPos) = 0 ' Light the current position (0 = on)\r
+    PRINT ledPos\r
     FOR b = 1 TO 1000\r
     FOR b = 1 TO 1000\r
-        display\r
+        refreshDisplay\r
     NEXT b\r
     NEXT b\r
-NEXT a\r
+NEXT ledPos\r
 GOTO 2\r
 END SUB\r
 \r
 GOTO 2\r
 END SUB\r
 \r
-SUB suva\r
-' This subroutine demonstrates a simple random bit setting algorithm\r
+SUB randomDemo\r
+' Random pattern demo: each position randomly on or off\r
 3\r
 3\r
-clearBits\r
+ledsOff\r
 FOR b = 1 TO 16\r
 FOR b = 1 TO 16\r
-    IF RND * 100 > 50 THEN bit(b) = 0 ' Randomly set bits to 0\r
+    IF RND * 100 > 50 THEN ledBits(b) = 0 ' Randomly set bits to 0\r
 NEXT b\r
 FOR b = 1 TO 100\r
 NEXT b\r
 FOR b = 1 TO 100\r
-    display\r
+    refreshDisplay\r
 NEXT b\r
 GOTO 3\r
 END SUB\r
 NEXT b\r
 GOTO 3\r
 END SUB\r
index 14883f8..73c4f33 100755 (executable)
 ' Also it drives numerical 2 digit led display through parallel LPT printer port.
 ' Program is driven by special 3 button keyboard that is also attached to LPT port.
 
 ' Also it drives numerical 2 digit led display through parallel LPT printer port.
 ' Program is driven by special 3 button keyboard that is also attached to LPT port.
 
-DECLARE SUB num (a%)
-DECLARE SUB mntime ()
-DECLARE SUB showit ()
-DECLARE SUB ekrf (a%)
-DECLARE SUB ekr ()
-DECLARE SUB rese ()
-DECLARE SUB start ()
+DECLARE SUB showNumber (value%)
+DECLARE SUB menuTime ()
+DECLARE SUB showTime ()
+DECLARE SUB setDigit (digit%)
+DECLARE SUB refreshDisplay ()
+DECLARE SUB reloadSchedule ()
+DECLARE SUB initialize ()
 DECLARE SUB boot ()
 DECLARE SUB boot ()
-DECLARE SUB getnad (g%, n%, d%, k%)
-DECLARE SUB initp (b$)
-DECLARE SUB getmd (a$, m, d)
-DECLARE SUB son (a$)
+DECLARE SUB findWeekDay (year%, month%, day%, dayOfWeek%)
+DECLARE SUB fetchDayPlan (planName$)
+DECLARE SUB parseMonthDay (dateStr$, month, day)
+DECLARE SUB splitWords (text$)
 DEFINT A-Z
 
 DEFINT A-Z
 
-DECLARE SUB inita ()
-DECLARE SUB chm ()
-DECLARE SUB chd ()
-DECLARE SUB kell (a%)
-DECLARE SUB sync2 ()
-DECLARE SUB sync ()
-DECLARE SUB mnmain ()
+DECLARE SUB fetchYearPlan ()
+DECLARE SUB checkBellTimes ()
+DECLARE SUB pickDayPlan ()
+DECLARE SUB ringBell (ringType%)
+DECLARE SUB roundToHour ()
+DECLARE SUB syncClock ()
+DECLARE SUB mainMenu ()
 DECLARE SUB main ()
 DECLARE SUB main ()
-DECLARE SUB getkey (kla%)
-DECLARE SUB klnait (k%)
-DECLARE SUB heli (a%)
-DECLARE SUB keys ()
-DECLARE SUB disp ()
-DIM SHARED bit(0 TO 7)
-DIM SHARED kl
-DIM SHARED hist(1 TO 3)
-DIM SHARED ap$(1 TO 500)
-DIM SHARED apl
-DIM SHARED pp$(1 TO 500)
-DIM SHARED ppl
-DIM SHARED prt, prt2
-DIM SHARED timo$
-DIM SHARED dato$
-DIM SHARED sona$(1 TO 50)
-DIM SHARED mitus
-DIM SHARED lp$
-DIM SHARED ndlp
-DIM SHARED pn$(1 TO 7)
-DIM SHARED bitt(1 TO 16)
-DIM SHARED modee, vilgu
-DIM SHARED tul(1 TO 2)
-
-start
-heli 4
-
-disp
-mnmain
-
-SUB chd
-b$ = "tuhi"
-IF apl = 0 THEN inita
-
-a$ = DATE$
-n1 = VAL(RIGHT$(a$, 4))
-n2 = VAL(LEFT$(a$, 2))
-a$ = LEFT$(a$, 5)
-n3 = VAL(RIGHT$(a$, 2))
-getnad n1, n2, n3, ndlp
+DECLARE SUB pollButtons (keyCode%)
+DECLARE SUB highlightButton (keyCode%)
+DECLARE SUB melody (tune%)
+DECLARE SUB scanButtons ()
+DECLARE SUB showTitle ()
+DIM SHARED btnBits(0 TO 7)
+DIM SHARED button
+DIM SHARED pressTicks(1 TO 3)
+DIM SHARED yearPlan$(1 TO 500)
+DIM SHARED yearPlanCount
+DIM SHARED dayPlan$(1 TO 500)
+DIM SHARED dayPlanCount
+DIM SHARED lptCtrl, lptData
+DIM SHARED lastTime$
+DIM SHARED lastDate$
+DIM SHARED words$(1 TO 50)
+DIM SHARED wordCount
+DIM SHARED dayPlanName$
+DIM SHARED weekDay
+DIM SHARED weekDayName$(1 TO 7)
+DIM SHARED ledBits(1 TO 16)
+DIM SHARED editMode, blinking
+DIM SHARED dotLed(1 TO 2)
+
+initialize
+melody 4 ' Startup jingle
+
+showTitle
+mainMenu ' Main menu loop (never returns)
+
+SUB pickDayPlan
+' Selects the day plan for today from the year plan and loads it
+planName$ = "tuhi" ' Default: empty plan
+IF yearPlanCount = 0 THEN fetchYearPlan
+
+dateStr$ = DATE$
+thisYear = VAL(RIGHT$(dateStr$, 4))
+thisMonth = VAL(LEFT$(dateStr$, 2))
+dateStr$ = LEFT$(dateStr$, 5)
+thisDay = VAL(RIGHT$(dateStr$, 2))
+findWeekDay thisYear, thisMonth, thisDay, weekDay
 LOCATE 10, 1
 LOCATE 10, 1
-PRINT "Week plan:", pn$(ndlp)
-FOR a = 1 TO apl
-son ap$(a)
-SELECT CASE sona$(1)
+PRINT "Week plan:", weekDayName$(weekDay)
+FOR a = 1 TO yearPlanCount
+' Year plan entry: v = date range, n = weekday in range, e = single date
+splitWords yearPlan$(a)
+SELECT CASE words$(1)
 CASE "v"
 CASE "v"
-getmd sona$(2), m1, d1
-getmd sona$(3), m2, d2
-getmd DATE$, m3, d3
-IF m3 < m1 THEN GOTO 9
-IF m3 > m2 THEN GOTO 9
-IF m3 = m1 THEN IF d3 < d1 THEN GOTO 9
-IF m3 = m2 THEN IF d3 > d2 THEN GOTO 9
-b$ = sona$(4)
+parseMonthDay words$(2), startMonth, startDay
+parseMonthDay words$(3), endMonth, endDay
+parseMonthDay DATE$, nowMonth, nowDay
+IF nowMonth < startMonth THEN GOTO 9
+IF nowMonth > endMonth THEN GOTO 9
+IF nowMonth = startMonth THEN IF nowDay < startDay THEN GOTO 9
+IF nowMonth = endMonth THEN IF nowDay > endDay THEN GOTO 9
+planName$ = words$(4)
 CASE "n"
 CASE "n"
-getmd sona$(2), m1, d1
-getmd sona$(3), m2, d2
-getmd DATE$, m3, d3
-IF m3 < m1 THEN GOTO 9
-IF m3 > m2 THEN GOTO 9
-IF m3 = m1 THEN IF d3 < d1 THEN GOTO 9
-IF m3 = m2 THEN IF d3 > d2 THEN GOTO 9
-IF ndlp <> VAL(sona$(4)) THEN GOTO 9
-b$ = sona$(5)
+parseMonthDay words$(2), startMonth, startDay
+parseMonthDay words$(3), endMonth, endDay
+parseMonthDay DATE$, nowMonth, nowDay
+IF nowMonth < startMonth THEN GOTO 9
+IF nowMonth > endMonth THEN GOTO 9
+IF nowMonth = startMonth THEN IF nowDay < startDay THEN GOTO 9
+IF nowMonth = endMonth THEN IF nowDay > endDay THEN GOTO 9
+IF weekDay <> VAL(words$(4)) THEN GOTO 9
+planName$ = words$(5)
 CASE "e"
 CASE "e"
-getmd sona$(2), m1, d1
-getmd DATE$, m2, d2
-IF (m1 = m2) AND (d1 = d2) THEN b$ = sona$(3)
+parseMonthDay words$(2), startMonth, startDay
+parseMonthDay DATE$, endMonth, endDay
+IF (startMonth = endMonth) AND (startDay = endDay) THEN planName$ = words$(3)
 END SELECT
 END SELECT
-9
+9 ' Entry did not match, try the next one
 NEXT a
 
 NEXT a
 
-IF b$ <> lp$ THEN initp b$
-lp$ = b$
+IF planName$ <> dayPlanName$ THEN fetchDayPlan planName$
+dayPlanName$ = planName$
 LOCATE 9, 1
 LOCATE 9, 1
-PRINT "Day plan:", lp$
+PRINT "Day plan:", dayPlanName$
 END SUB
 
 END SUB
 
-SUB chm
-showit
-a$ = DATE$
-IF a$ <> dato$ THEN chd
-dato$ = a$
-b = 0
-FOR a = 1 TO ppl
-son pp$(a)
-SELECT CASE sona$(1)
+SUB checkBellTimes
+' Runs on every minute change: reloads plans when needed, rings the bell on schedule
+showTime
+dateStr$ = DATE$
+IF dateStr$ <> lastDate$ THEN pickDayPlan
+lastDate$ = dateStr$
+ringType = 0
+' Look for a bell entry (# HH:MM sis|val) matching the current time
+FOR a = 1 TO dayPlanCount
+splitWords dayPlan$(a)
+SELECT CASE words$(1)
 CASE "#"
 CASE "#"
-getmd sona$(2), h1, m1
-getmd TIME$, h2, m2
-' PRINT h1, m1, h2, m2
-IF (h2 = h1) AND (m2 = m1) THEN
-IF sona$(3) = "sis" THEN b = 1
-IF sona$(3) = "val" THEN b = 2
+parseMonthDay words$(2), planHour, planMin
+parseMonthDay TIME$, nowHour, nowMin
+' PRINT planHour, planMin, nowHour, nowMin
+IF (nowHour = planHour) AND (nowMin = planMin) THEN
+IF words$(3) = "sis" THEN ringType = 1
+IF words$(3) = "val" THEN ringType = 2
 END IF
 END SELECT
 NEXT a
 
 END IF
 END SELECT
 NEXT a
 
-IF b > 0 THEN kell b
+IF ringType > 0 THEN ringBell ringType
 
 END SUB
 
 
 END SUB
 
-SUB disp
+SUB showTitle
+' Clears the screen and shows the program title
 CLS
 PRINT "Kooli Kell  v 1.2  2002.10.10"
 PRINT "Programmi autor Svjatoslav Agejenko"
 
 END SUB
 
 CLS
 PRINT "Kooli Kell  v 1.2  2002.10.10"
 PRINT "Programmi autor Svjatoslav Agejenko"
 
 END SUB
 
-SUB ekr
-FOR e = 1 TO 10
-c = 1
-c = c + 16 * bitt(1)
-c = c + 32 * bitt(2)
-c = c + 64 * bitt(3)
-c = c + 128 * bitt(4)
-OUT prt2, c
-
-c = 2
-c = c + 16 * bitt(5)
-c = c + 32 * bitt(6)
-c = c + 64 * bitt(7)
-c = c + 128 * bitt(8)
-OUT prt2, c
-
-c = 4
-c = c + 16 * bitt(9)
-c = c + 32 * bitt(10)
-c = c + 64 * bitt(11)
-c = c + 128 * bitt(12)
-OUT prt2, c
-
-c = 8
-c = c + 16 * bitt(13)
-c = c + 32 * bitt(14)
-c = c + 64 * bitt(15)
-c = c + 128 * bitt(16)
-OUT prt2, c
+SUB refreshDisplay
+' Writes the LED segment bits to the LPT data port
+' Low nibble = digit group select, high nibble = 4 segment bits (1 = off)
+FOR e = 1 TO 10 ' Refresh repeatedly so the display looks steady
+scanByte = 1
+scanByte = scanByte + 16 * ledBits(1)
+scanByte = scanByte + 32 * ledBits(2)
+scanByte = scanByte + 64 * ledBits(3)
+scanByte = scanByte + 128 * ledBits(4)
+OUT lptData, scanByte
+
+scanByte = 2
+scanByte = scanByte + 16 * ledBits(5)
+scanByte = scanByte + 32 * ledBits(6)
+scanByte = scanByte + 64 * ledBits(7)
+scanByte = scanByte + 128 * ledBits(8)
+OUT lptData, scanByte
+
+scanByte = 4
+scanByte = scanByte + 16 * ledBits(9)
+scanByte = scanByte + 32 * ledBits(10)
+scanByte = scanByte + 64 * ledBits(11)
+scanByte = scanByte + 128 * ledBits(12)
+OUT lptData, scanByte
+
+scanByte = 8
+scanByte = scanByte + 16 * ledBits(13)
+scanByte = scanByte + 32 * ledBits(14)
+scanByte = scanByte + 64 * ledBits(15)
+scanByte = scanByte + 128 * ledBits(16)
+OUT lptData, scanByte
 
 NEXT e
 END SUB
 
 
 NEXT e
 END SUB
 
-SUB ekrf (a)
-SELECT CASE (a)
+SUB setDigit (digit)
+' Lights the LED segments forming one digit (0-9 = first digit, 10-19 = second)
+' 0 = segment on, 1 = segment off; bits 1-8 = first digit, 9-16 = second
+SELECT CASE (digit)
 CASE 0
 CASE 0
-bitt(1) = 0
-bitt(2) = 0
-bitt(3) = 0
-bitt(7) = 0
-bitt(5) = 0
-bitt(6) = 0
-bitt(8) = 1
+ledBits(1) = 0
+ledBits(2) = 0
+ledBits(3) = 0
+ledBits(7) = 0
+ledBits(5) = 0
+ledBits(6) = 0
+ledBits(8) = 1
 CASE 1
 CASE 1
-bitt(2) = 0
-bitt(7) = 0
+ledBits(2) = 0
+ledBits(7) = 0
 CASE 2
 CASE 2
-bitt(1) = 0
-bitt(3) = 0
-bitt(5) = 0
-bitt(7) = 0
-bitt(8) = 0
+ledBits(1) = 0
+ledBits(3) = 0
+ledBits(5) = 0
+ledBits(7) = 0
+ledBits(8) = 0
 CASE 3
 CASE 3
-bitt(1) = 0
-bitt(2) = 0
-bitt(5) = 0
-bitt(8) = 0
-bitt(7) = 0
+ledBits(1) = 0
+ledBits(2) = 0
+ledBits(5) = 0
+ledBits(8) = 0
+ledBits(7) = 0
 CASE 4
 CASE 4
-bitt(2) = 0
-bitt(6) = 0
-bitt(7) = 0
-bitt(8) = 0
+ledBits(2) = 0
+ledBits(6) = 0
+ledBits(7) = 0
+ledBits(8) = 0
 CASE 5
 CASE 5
-bitt(1) = 0
-bitt(2) = 0
-bitt(5) = 0
-bitt(6) = 0
-bitt(8) = 0
+ledBits(1) = 0
+ledBits(2) = 0
+ledBits(5) = 0
+ledBits(6) = 0
+ledBits(8) = 0
 CASE 6
 CASE 6
-bitt(1) = 0
-bitt(2) = 0
-bitt(3) = 0
-bitt(5) = 0
-bitt(6) = 0
-bitt(8) = 0
+ledBits(1) = 0
+ledBits(2) = 0
+ledBits(3) = 0
+ledBits(5) = 0
+ledBits(6) = 0
+ledBits(8) = 0
 CASE 7
 CASE 7
-bitt(2) = 0
-bitt(7) = 0
-bitt(5) = 0
+ledBits(2) = 0
+ledBits(7) = 0
+ledBits(5) = 0
 CASE 8
 CASE 8
-bitt(1) = 0
-bitt(2) = 0
-bitt(3) = 0
-bitt(7) = 0
-bitt(5) = 0
-bitt(6) = 0
-bitt(8) = 0
+ledBits(1) = 0
+ledBits(2) = 0
+ledBits(3) = 0
+ledBits(7) = 0
+ledBits(5) = 0
+ledBits(6) = 0
+ledBits(8) = 0
 CASE 9
 CASE 9
-bitt(1) = 0
-bitt(2) = 0
-bitt(7) = 0
-bitt(5) = 0
-bitt(6) = 0
-bitt(8) = 0
+ledBits(1) = 0
+ledBits(2) = 0
+ledBits(7) = 0
+ledBits(5) = 0
+ledBits(6) = 0
+ledBits(8) = 0
 CASE 10
 CASE 10
-bitt(15) = 0
-bitt(16) = 0
-bitt(12) = 0
-bitt(10) = 0
-bitt(9) = 0
-bitt(4) = 0
+ledBits(15) = 0
+ledBits(16) = 0
+ledBits(12) = 0
+ledBits(10) = 0
+ledBits(9) = 0
+ledBits(4) = 0
 CASE 11
 CASE 11
-bitt(15) = 0
-bitt(4) = 0
+ledBits(15) = 0
+ledBits(4) = 0
 CASE 12
 CASE 12
-bitt(15) = 0
-bitt(16) = 0
-bitt(9) = 0
-bitt(10) = 0
-bitt(11) = 0
+ledBits(15) = 0
+ledBits(16) = 0
+ledBits(9) = 0
+ledBits(10) = 0
+ledBits(11) = 0
 CASE 13
 CASE 13
-bitt(15) = 0
-bitt(4) = 0
-bitt(16) = 0
-bitt(11) = 0
-bitt(9) = 0
+ledBits(15) = 0
+ledBits(4) = 0
+ledBits(16) = 0
+ledBits(11) = 0
+ledBits(9) = 0
 CASE 14
 CASE 14
-bitt(15) = 0
-bitt(4) = 0
-bitt(12) = 0
-bitt(11) = 0
+ledBits(15) = 0
+ledBits(4) = 0
+ledBits(12) = 0
+ledBits(11) = 0
 CASE 15
 CASE 15
-bitt(9) = 0
-bitt(4) = 0
-bitt(11) = 0
-bitt(12) = 0
-bitt(16) = 0
+ledBits(9) = 0
+ledBits(4) = 0
+ledBits(11) = 0
+ledBits(12) = 0
+ledBits(16) = 0
 CASE 16
 CASE 16
-bitt(9) = 0
-bitt(4) = 0
-bitt(11) = 0
-bitt(12) = 0
-bitt(16) = 0
-bitt(10) = 0
+ledBits(9) = 0
+ledBits(4) = 0
+ledBits(11) = 0
+ledBits(12) = 0
+ledBits(16) = 0
+ledBits(10) = 0
 CASE 17
 CASE 17
-bitt(4) = 0
-bitt(15) = 0
-bitt(16) = 0
+ledBits(4) = 0
+ledBits(15) = 0
+ledBits(16) = 0
 CASE 18
 CASE 18
-bitt(4) = 0
-bitt(15) = 0
-bitt(16) = 0
-bitt(12) = 0
-bitt(11) = 0
-bitt(10) = 0
-bitt(9) = 0
+ledBits(4) = 0
+ledBits(15) = 0
+ledBits(16) = 0
+ledBits(12) = 0
+ledBits(11) = 0
+ledBits(10) = 0
+ledBits(9) = 0
 CASE 19
 CASE 19
-bitt(4) = 0
-bitt(15) = 0
-bitt(16) = 0
-bitt(12) = 0
-bitt(11) = 0
-bitt(9) = 0
+ledBits(4) = 0
+ledBits(15) = 0
+ledBits(16) = 0
+ledBits(12) = 0
+ledBits(11) = 0
+ledBits(9) = 0
 END SELECT
 
 END SUB
 
 END SELECT
 
 END SUB
 
-SUB getkey (kla)
+SUB pollButtons (keyCode)
+' Main input loop: polls the buttons, detects clicks and double-clicks
 
 
-' Read the state of the buttons on the keyboard
+' Blink the dot LEDs while the time editing menu is active
 1
 1
-IF vilgu = 1 THEN
-tmr = tmr + 1
-IF tmr > 5 THEN bitt(13) = tul(1): bitt(14) = tul(2) ELSE bitt(13) = 1: bitt(14) = 1
-IF tmr > 10 THEN
-tmr = 0
+IF blinking = 1 THEN
+blinkCount = blinkCount + 1
+IF blinkCount > 5 THEN ledBits(13) = dotLed(1): ledBits(14) = dotLed(2) ELSE ledBits(13) = 1: ledBits(14) = 1
+IF blinkCount > 10 THEN
+blinkCount = 0
 END IF
 ELSE
 END IF
 ELSE
-bitt(13) = tul(1)
-bitt(14) = tul(2)
+ledBits(13) = dotLed(1)
+ledBits(14) = dotLed(2)
 END IF
 
 ' Get the current time
 END IF
 
 ' Get the current time
-b$ = LEFT$(TIME$, 5)
-IF b$ <> timo$ THEN chm
-timo$ = b$
-hist(1) = hist(1) + 1
-IF hist(1) > 20000 THEN hist(1) = 15000
-hist(2) = hist(2) + 1
-IF hist(2) > 20000 THEN hist(2) = 15000
-hist(3) = hist(3) + 1
-IF hist(3) > 20000 THEN hist(3) = 15000
+nowTime$ = LEFT$(TIME$, 5)
+IF nowTime$ <> lastTime$ THEN checkBellTimes
+lastTime$ = nowTime$
+' Count poll ticks since each button press (click/double-click timing)
+pressTicks(1) = pressTicks(1) + 1
+IF pressTicks(1) > 20000 THEN pressTicks(1) = 15000
+pressTicks(2) = pressTicks(2) + 1
+IF pressTicks(2) > 20000 THEN pressTicks(2) = 15000
+pressTicks(3) = pressTicks(3) + 1
+IF pressTicks(3) > 20000 THEN pressTicks(3) = 15000
 
 ' Read the state of the buttons on the keyboard
 
 ' Read the state of the buttons on the keyboard
-keys
-IF kl > 0 THEN
-  IF hist(kl) > 1 AND hist(kl) < 9 THEN
-    klnait kl + 3
-    kla = kl + 3
+scanButtons
+' Released and pressed again within 2-8 ticks = double-click
+IF button > 0 THEN
+  IF pressTicks(button) > 1 AND pressTicks(button) < 9 THEN
+    highlightButton button + 3
+    keyCode = button + 3
     GOTO 4
   ELSE
     GOTO 4
   ELSE
-    hist(kl) = 0
+    pressTicks(button) = 0
   END IF
 END IF
   END IF
 END IF
-IF hist(1) = 10 THEN klnait 1: kla = 1: GOTO 4
-IF hist(2) = 10 THEN klnait 2: kla = 2: GOTO 4
-IF hist(3) = 10 THEN klnait 3: kla = 3: GOTO 4
+' Held down for 10 ticks = single click
+IF pressTicks(1) = 10 THEN highlightButton 1: keyCode = 1: GOTO 4
+IF pressTicks(2) = 10 THEN highlightButton 2: keyCode = 2: GOTO 4
+IF pressTicks(3) = 10 THEN highlightButton 3: keyCode = 3: GOTO 4
 
 
-IF hist(1) > 11 AND hist(2) > 11 AND hist(3) > 11 THEN klnait 0
+IF pressTicks(1) > 11 AND pressTicks(2) > 11 AND pressTicks(3) > 11 THEN highlightButton 0 ' All released: clear highlight
 
 ' Display current time and date
 LOCATE 7, 1
 
 ' Display current time and date
 LOCATE 7, 1
@@ -344,47 +357,51 @@ GOTO 1
 4
 
 ' Reset the button press count
 4
 
 ' Reset the button press count
-hist(1) = 10000
-hist(2) = 10000
-hist(3) = 10000
+pressTicks(1) = 10000
+pressTicks(2) = 10000
+pressTicks(3) = 10000
 
 ' Play a sound to indicate button press
 FOR b = 1 TO 100
 
 ' Play a sound to indicate button press
 FOR b = 1 TO 100
-SOUND 0, .1
+SOUND 0, .1 ' SOUND 0 = sub-second delay workaround (inaudible)
 NEXT b
 NEXT b
-IF kla > 3 THEN SOUND 4000, .1 ELSE SOUND 3000, .1
+IF keyCode > 3 THEN SOUND 4000, .1 ELSE SOUND 3000, .1 ' Higher pitch for double-click
 
 END SUB
 
 
 END SUB
 
-SUB getmd (a$, m, d)
-b$ = LEFT$(a$, 5)
-m = VAL(LEFT$(b$, 2))
-d = VAL(RIGHT$(b$, 2))
+SUB parseMonthDay (dateStr$, month, day)
+' Extracts month and day numbers from an MM-DD string
+monthDay$ = LEFT$(dateStr$, 5)
+month = VAL(LEFT$(monthDay$, 2))
+day = VAL(RIGHT$(monthDay$, 2))
 
 END SUB
 
 
 END SUB
 
-SUB getnad (g, n, d, k)
+SUB findWeekDay (year, month, day, dayOfWeek)
+' Returns the day of week (1 = Monday .. 7 = Sunday) for a date
 LOCATE 11, 1
 LOCATE 11, 1
-PRINT g, n, d
-p = g
-m = n - 2
-IF n > 2 GOTO 120
-p = p - 1: m = m + 12
+PRINT year, month, day
+' Zeller's congruence; Jan/Feb count as months 13/14 of the previous year
+zYear = year
+zMonth = month - 2
+IF month > 2 GOTO 120
+zYear = zYear - 1: zMonth = zMonth + 12
 120
 120
-c = INT(p / 100)
-y = p - c * 100
-w = d + INT((13 * m - 1) / 5) + y + INT(y / 4) + INT(c / 4) - 2 * c
-k = w - 7 * INT(w / 7)
-IF k = 0 THEN k = 7
+century = INT(zYear / 100)
+shortYear = zYear - century * 100
+zeller = day + INT((13 * zMonth - 1) / 5) + shortYear + INT(shortYear / 4) + INT(century / 4) - 2 * century
+dayOfWeek = zeller - 7 * INT(zeller / 7)
+IF dayOfWeek = 0 THEN dayOfWeek = 7 ' Sunday
 END SUB
 
 END SUB
 
-SUB heli (a)
+SUB melody (tune)
+' Plays one of the sound effects through the PC speaker
 'GOTO 10
 'GOTO 10
-SELECT CASE a
+SELECT CASE tune
 CASE 1
 FOR c = 1 TO 5
 SOUND 3000, 1
 CASE 1
 FOR c = 1 TO 5
 SOUND 3000, 1
-SOUND 0, 1
+SOUND 0, 1 ' SOUND 0 = sub-second delay workaround (inaudible)
 NEXT c
 
 CASE 2
 NEXT c
 
 CASE 2
@@ -395,305 +412,327 @@ NEXT c
 SOUND 2500, 10
 
 CASE 3
 SOUND 2500, 10
 
 CASE 3
-FOR a = 1 TO 10
+FOR tune = 1 TO 10
 SOUND 500, .5
 SOUND 1500, .5
 SOUND 2000, .5
 SOUND 1520, .5
 SOUND 500, .5
 SOUND 1500, .5
 SOUND 2000, .5
 SOUND 1520, .5
-NEXT a
+NEXT tune
 
 CASE 4
 
 CASE 4
-FOR a = 800 TO 1000 STEP 10
-SOUND a, .1
-SOUND a * 3, .1
+FOR tune = 800 TO 1000 STEP 10
+SOUND tune, .1
+SOUND tune * 3, .1
 SOUND 0, 1
 SOUND 0, 1
-NEXT a
+NEXT tune
 10
 
 END SELECT
 
 END SUB
 
 10
 
 END SELECT
 
 END SUB
 
-SUB inita
-apl = 0
-OPEN "aasta.ap" FOR INPUT AS #1
+SUB fetchYearPlan
+' Loads the year plan (aasta.ap) into memory
+yearPlanCount = 0
+OPEN "aasta.ap" FOR INPUT AS #1 ' Year plan
 5
 IF EOF(1) <> 0 THEN GOTO 3
 5
 IF EOF(1) <> 0 THEN GOTO 3
-LINE INPUT #1, a$
-apl = apl + 1
-ap$(apl) = a$
+LINE INPUT #1, textLine$
+yearPlanCount = yearPlanCount + 1
+yearPlan$(yearPlanCount) = textLine$
 GOTO 5
 3
 CLOSE #1
 END SUB
 
 GOTO 5
 3
 CLOSE #1
 END SUB
 
-SUB initp (b$)
-ppl = 0
-OPEN b$ + ".pp" FOR INPUT AS #1
+SUB fetchDayPlan (planName$)
+' Loads a day plan (<name>.pp) into memory
+dayPlanCount = 0
+OPEN planName$ + ".pp" FOR INPUT AS #1 ' Day plan
 6
 IF EOF(1) <> 0 THEN GOTO 7
 6
 IF EOF(1) <> 0 THEN GOTO 7
-LINE INPUT #1, a$
-ppl = ppl + 1
-pp$(ppl) = a$
+LINE INPUT #1, textLine$
+dayPlanCount = dayPlanCount + 1
+dayPlan$(dayPlanCount) = textLine$
 GOTO 6
 7
 CLOSE #1
 END SUB
 
 GOTO 6
 7
 CLOSE #1
 END SUB
 
-SUB kell (a)
-heli 3
+SUB ringBell (ringType)
+' Rings the school bell through the LPT relay (1 = lesson start, 2 = lesson end)
+melody 3
 
 
-SELECT CASE a
+SELECT CASE ringType
 CASE 1
 CASE 1
-OUT prt, 255
+OUT lptCtrl, 255 ' Relay on: bell rings
+' SOUND 0 below = sub-second delay workaround (inaudible)
 FOR b = 1 TO 80
 SOUND 0, 1
 NEXT b
 FOR b = 1 TO 80
 SOUND 0, 1
 NEXT b
-OUT prt, 0
+OUT lptCtrl, 0 ' Relay off
 FOR b = 1 TO 15
 SOUND 0, 1
 NEXT b
 FOR b = 1 TO 15
 SOUND 0, 1
 NEXT b
-OUT prt, 255
+OUT lptCtrl, 255
 FOR b = 1 TO 15
 SOUND 0, 1
 NEXT b
 FOR b = 1 TO 15
 SOUND 0, 1
 NEXT b
-OUT prt, 0
+OUT lptCtrl, 0
 
 CASE 2
 
 CASE 2
-OUT prt, 255
+OUT lptCtrl, 255
 FOR b = 1 TO 80
 SOUND 0, 1
 NEXT b
 FOR b = 1 TO 80
 SOUND 0, 1
 NEXT b
-OUT prt, 0
+OUT lptCtrl, 0
 
 END SELECT
 END SUB
 
 
 END SELECT
 END SUB
 
-SUB keys
-kl = 0
-OUT prt, 0
+SUB scanButtons
+' Reads the 3 button keyboard attached to the LPT port
+button = 0
+OUT lptCtrl, 0
 8
 8
-a = INP(prt)
-b = INP(prt)
-IF a <> b THEN GOTO 8
+' Read the port twice and keep the stable value (debounce)
+portByte = INP(lptCtrl)
+b = INP(lptCtrl)
+IF portByte <> b THEN GOTO 8
 
 
+' Split the port byte into individual bits
 b = 128
 b = 128
-FOR c = 0 TO 7
-d = INT(a / b)
-bit(c) = d
-a = a - (b * d)
+FOR bitNum = 0 TO 7
+bitValue = INT(portByte / b)
+btnBits(bitNum) = bitValue
+portByte = portByte - (b * bitValue)
 b = b / 2
 b = b / 2
-NEXT c
-
-IF bit(4) = 1 AND bit(6) = 1 THEN bit(4) = 0: bit(6) = 0: kl = 3
-IF bit(6) = 1 THEN kl = 2
-IF bit(4) = 1 THEN kl = 1
-
-a$ = INKEY$
-IF a$ = CHR$(0) + "K" THEN kl = 1
-IF a$ = CHR$(0) + "M" THEN kl = 2
-IF a$ = CHR$(0) + "P" THEN kl = 3
-ekr
+NEXT bitNum
+
+' The three buttons are wired to port bits 4 and 6
+IF btnBits(4) = 1 AND btnBits(6) = 1 THEN btnBits(4) = 0: btnBits(6) = 0: button = 3
+IF btnBits(6) = 1 THEN button = 2
+IF btnBits(4) = 1 THEN button = 1
+
+' Cursor keys emulate the buttons without the hardware
+keyHit$ = INKEY$
+IF keyHit$ = CHR$(0) + "K" THEN button = 1
+IF keyHit$ = CHR$(0) + "M" THEN button = 2
+IF keyHit$ = CHR$(0) + "P" THEN button = 3
+refreshDisplay
 END SUB
 
 END SUB
 
-SUB klnait (k)
+SUB highlightButton (keyCode)
+' Highlights the pressed button on screen (4-6 = double-click, shown yellow)
 
 ' Highlight the pressed button on the display
 
 ' Highlight the pressed button on the display
-IF k = 3 THEN c = 3 ELSE c = 1
-IF k = 6 THEN c = 14
+IF keyCode = 3 THEN bgColor = 3 ELSE bgColor = 1
+IF keyCode = 6 THEN bgColor = 14
 LOCATE 5, 6
 LOCATE 5, 6
-COLOR 7, c
+COLOR 7, bgColor
 PRINT "<kesk>"
 COLOR 7, 0
 
 ' Highlight the pressed button on the display
 PRINT "<kesk>"
 COLOR 7, 0
 
 ' Highlight the pressed button on the display
-IF k = 1 THEN c = 3 ELSE c = 1
-IF k = 4 THEN c = 14
+IF keyCode = 1 THEN bgColor = 3 ELSE bgColor = 1
+IF keyCode = 4 THEN bgColor = 14
 LOCATE 4, 1
 LOCATE 4, 1
-COLOR 7, c
+COLOR 7, bgColor
 PRINT "<vasak>"
 COLOR 7, 0
 
 PRINT "<vasak>"
 COLOR 7, 0
 
-IF k = 2 THEN c = 3 ELSE c = 1
-IF k = 5 THEN c = 14
+IF keyCode = 2 THEN bgColor = 3 ELSE bgColor = 1
+IF keyCode = 5 THEN bgColor = 14
 LOCATE 4, 10
 LOCATE 4, 10
-COLOR 7, c
+COLOR 7, bgColor
 PRINT "<parem>"
 COLOR 7, 0
 
 END SUB
 
 PRINT "<parem>"
 COLOR 7, 0
 
 END SUB
 
-SUB mnmain
+SUB mainMenu
+' Main menu: maps button codes to actions, loops forever
+' Codes: 1/2/3 = click on button 1/2/3, 4/5/6 = double-click
 2
 2
-getkey a
-IF a = 6 THEN sync
-IF a = 3 THEN sync2
+pollButtons keyCode
+IF keyCode = 6 THEN syncClock
+IF keyCode = 3 THEN roundToHour
 
 
-IF a = 1 THEN kell 1
-IF a = 4 THEN kell 2
+IF keyCode = 1 THEN ringBell 1
+IF keyCode = 4 THEN ringBell 2
 
 
-IF a = 2 THEN mntime
-IF a = 5 THEN rese
+IF keyCode = 2 THEN menuTime
+IF keyCode = 5 THEN reloadSchedule
 GOTO 2
 
 END SUB
 
 GOTO 2
 
 END SUB
 
-SUB mntime
-vilgu = 1
+SUB menuTime
+' Time editing menu: buttons adjust the system time
+' editMode 1 = adjust hours, 2 = adjust minutes
+blinking = 1
 11
 11
-showit
-getkey a
-
-IF modee = 1 THEN
-  b = VAL(LEFT$(TIME$, 2))
-  c = 0
-  IF a = 1 THEN c = 1: b = b - 1
-  IF a = 2 THEN c = 1: b = b + 1
-  IF b < 0 THEN b = 0
-  IF b > 23 THEN b = 23
-  d$ = STR$(b)
-  IF LEFT$(d$, 1) = " " THEN d$ = RIGHT$(d$, LEN(d$) - 1)
-  IF LEN(d$) < 2 THEN d$ = "0" + d$
-  e$ = d$ + RIGHT$(TIME$, 6)
-  IF c = 1 THEN TIME$ = e$
+showTime
+pollButtons keyCode
+
+IF editMode = 1 THEN
+  editValue = VAL(LEFT$(TIME$, 2))
+  changed = 0
+  IF keyCode = 1 THEN changed = 1: editValue = editValue - 1
+  IF keyCode = 2 THEN changed = 1: editValue = editValue + 1
+  IF editValue < 0 THEN editValue = 0
+  IF editValue > 23 THEN editValue = 23
+  twoDigit$ = STR$(editValue)
+  IF LEFT$(twoDigit$, 1) = " " THEN twoDigit$ = RIGHT$(twoDigit$, LEN(twoDigit$) - 1)
+  IF LEN(twoDigit$) < 2 THEN twoDigit$ = "0" + twoDigit$
+  newTime$ = twoDigit$ + RIGHT$(TIME$, 6)
+  IF changed = 1 THEN TIME$ = newTime$
 ELSE
 ELSE
-  b = VAL(RIGHT$(LEFT$(TIME$, 5), 2))
-  c = 0
-  IF a = 1 THEN c = 1: b = b - 1
-  IF a = 2 THEN c = 1: b = b + 1
-  IF b < 0 THEN b = 0
-  IF b > 59 THEN b = 59
-  d$ = STR$(b)
-  IF LEFT$(d$, 1) = " " THEN d$ = RIGHT$(d$, LEN(d$) - 1)
-  IF LEN(d$) < 2 THEN d$ = "0" + d$
-  e$ = LEFT$(TIME$, 3) + d$ + RIGHT$(TIME$, 3)
-  IF c = 1 THEN TIME$ = e$
+  editValue = VAL(RIGHT$(LEFT$(TIME$, 5), 2))
+  changed = 0
+  IF keyCode = 1 THEN changed = 1: editValue = editValue - 1
+  IF keyCode = 2 THEN changed = 1: editValue = editValue + 1
+  IF editValue < 0 THEN editValue = 0
+  IF editValue > 59 THEN editValue = 59
+  twoDigit$ = STR$(editValue)
+  IF LEFT$(twoDigit$, 1) = " " THEN twoDigit$ = RIGHT$(twoDigit$, LEN(twoDigit$) - 1)
+  IF LEN(twoDigit$) < 2 THEN twoDigit$ = "0" + twoDigit$
+  newTime$ = LEFT$(TIME$, 3) + twoDigit$ + RIGHT$(TIME$, 3)
+  IF changed = 1 THEN TIME$ = newTime$
 END IF
 
 END IF
 
-IF a = 3 THEN
-IF modee = 1 THEN modee = 2 ELSE modee = 1
+IF keyCode = 3 THEN
+IF editMode = 1 THEN editMode = 2 ELSE editMode = 1
 END IF
 
 END IF
 
-IF a = 6 THEN GOTO 12
+IF keyCode = 6 THEN GOTO 12
 GOTO 11
 12
 GOTO 11
 12
-vilgu = 0
-modee = 2
+blinking = 0
+editMode = 2 ' 1 = show hours, 2 = show minutes
 END SUB
 
 END SUB
 
-SUB num (a)
+SUB showNumber (value)
+' Shows a two digit number on the LED display
 
 
-FOR b = 1 TO 12
-bitt(b) = 1
-NEXT b
-bitt(15) = 1
-bitt(16) = 1
+FOR tens = 1 TO 12
+ledBits(tens) = 1
+NEXT tens
+ledBits(15) = 1
+ledBits(16) = 1
 
 
-b = INT(a / 10)
-c = a - (10 * b)
-ekrf b
-ekrf c + 10
+tens = INT(value / 10)
+ones = value - (10 * tens)
+setDigit tens
+setDigit ones + 10
 END SUB
 
 END SUB
 
-SUB rese
-heli 4
-timo$ = ""
-dato$ = ""
-apl = 0
+SUB reloadSchedule
+' Reloads the schedules after the plan files were edited
+melody 4
+lastTime$ = ""
+lastDate$ = ""
+yearPlanCount = 0
 END SUB
 
 END SUB
 
-SUB showit
-a$ = LEFT$(TIME$, 5)
-IF modee = 1 THEN
-b = VAL(LEFT$(a$, 2))
-tul(1) = 1
-tul(2) = 0
+SUB showTime
+' Shows the current hours or minutes on the LED display
+' The dot LED marks whether hours or minutes are shown
+hm$ = LEFT$(TIME$, 5)
+IF editMode = 1 THEN
+shownValue = VAL(LEFT$(hm$, 2))
+dotLed(1) = 1
+dotLed(2) = 0
 ELSE
 ELSE
-b = VAL(RIGHT$(a$, 2))
-tul(1) = 0
-tul(2) = 1
+shownValue = VAL(RIGHT$(hm$, 2))
+dotLed(1) = 0
+dotLed(2) = 1
 END IF
 LOCATE 15, 1
 END IF
 LOCATE 15, 1
-PRINT b
-num b
+PRINT shownValue
+showNumber shownValue
 
 
 END SUB
 
 
 
 END SUB
 
-SUB son (a$)
+SUB splitWords (text$)
+' Splits a plan file line into separate words
 
 
-FOR b = 1 TO 50
-sona$(b) = ""
-NEXT b
-mitus = 0
+FOR newWord = 1 TO 50
+words$(newWord) = ""
+NEXT newWord
+wordCount = 0
 
 
-b = 1
-FOR c = 1 TO LEN(a$)
-d$ = RIGHT$(LEFT$(a$, c), 1)
-IF d$ = " " OR d$ = CHR$(9) THEN
-b = 1
+newWord = 1
+FOR charPos = 1 TO LEN(text$)
+curChar$ = RIGHT$(LEFT$(text$, charPos), 1)
+IF curChar$ = " " OR curChar$ = CHR$(9) THEN ' Space or tab separates words
+newWord = 1
 ELSE
 ELSE
-IF b = 1 THEN b = 0: mitus = mitus + 1
-sona$(mitus) = sona$(mitus) + d$
+IF newWord = 1 THEN newWord = 0: wordCount = wordCount + 1
+words$(wordCount) = words$(wordCount) + curChar$
 END IF
 END IF
-NEXT c
+NEXT charPos
 
 
 END SUB
 
 
 
 END SUB
 
-SUB start
-pn$(1) = "Monday"
-pn$(2) = "Tuesday"
-pn$(3) = "Wednesday"
-pn$(4) = "Thursday"
-pn$(5) = "Friday"
-pn$(6) = "Saturday"
-pn$(7) = "Sunday"
-
-prt = &H37A
-prt2 = &H378
-hist(1) = 10000
-hist(2) = 10000
-hist(3) = 10000
+SUB initialize
+' Sets up port addresses and initial variable values
+weekDayName$(1) = "Monday"
+weekDayName$(2) = "Tuesday"
+weekDayName$(3) = "Wednesday"
+weekDayName$(4) = "Thursday"
+weekDayName$(5) = "Friday"
+weekDayName$(6) = "Saturday"
+weekDayName$(7) = "Sunday"
+
+lptCtrl = &H37A ' LPT control port: bell relay out, buttons in
+lptData = &H378 ' LPT data port: LED display out
+pressTicks(1) = 10000
+pressTicks(2) = 10000
+pressTicks(3) = 10000
 
 FOR a = 1 TO 16
 
 FOR a = 1 TO 16
-bitt(a) = 1
+ledBits(a) = 1 ' All LEDs off (active low)
 NEXT a
 NEXT a
-modee = 2
-vilgu = 0
-tul(1) = 1
-tul(2) = 1
+editMode = 2
+blinking = 0
+dotLed(1) = 1
+dotLed(2) = 1
 END SUB
 
 END SUB
 
-SUB sync
+SUB syncClock
+' Loads date and time from sync.txt into the system clock
 OPEN "sync.txt" FOR INPUT AS #1
 OPEN "sync.txt" FOR INPUT AS #1
-LINE INPUT #1, a$
-DATE$ = a$
-LINE INPUT #1, a$
-TIME$ = a$
+LINE INPUT #1, textLine$
+DATE$ = textLine$
+LINE INPUT #1, textLine$
+TIME$ = textLine$
 CLOSE #1
 
 CLOSE #1
 
-heli 2
+melody 2
 END SUB
 
 END SUB
 
-SUB sync2
-a$ = TIME$
-a$ = LEFT$(a$, 5)
-b = VAL(RIGHT$(a$, 2))
-c = VAL(LEFT$(a$, 2))
-IF b >= 30 THEN c = c + 1
-b = 0
-IF c > 23 THEN c = c - 24
-a$ = RIGHT$(STR$(c), LEN(STR$(c)) - 1)
-b$ = RIGHT$(STR$(b), LEN(STR$(b)) - 1)
-IF LEN(a$) < 2 THEN a$ = "0" + a$
-IF LEN(b$) < 2 THEN b$ = "0" + b$
-a$ = a$ + ":" + b$
+SUB roundToHour
+' Rounds the system time to the nearest full hour
+timeStr$ = TIME$
+timeStr$ = LEFT$(timeStr$, 5)
+minutes = VAL(RIGHT$(timeStr$, 2))
+hours = VAL(LEFT$(timeStr$, 2))
+IF minutes >= 30 THEN hours = hours + 1 ' Round up from 30 minutes
+minutes = 0
+IF hours > 23 THEN hours = hours - 24
+timeStr$ = RIGHT$(STR$(hours), LEN(STR$(hours)) - 1)
+minStr$ = RIGHT$(STR$(minutes), LEN(STR$(minutes)) - 1)
+IF LEN(timeStr$) < 2 THEN timeStr$ = "0" + timeStr$
+IF LEN(minStr$) < 2 THEN minStr$ = "0" + minStr$
+timeStr$ = timeStr$ + ":" + minStr$
 
 'LOCATE 10, 1
 
 'LOCATE 10, 1
-'PRINT a$
+'PRINT timeStr$
 
 
-TIME$ = a$
+TIME$ = timeStr$
 
 
-heli 1
+melody 1
 END SUB
 END SUB
index 4564dc8..3730ae5 100755 (executable)
-DECLARE SUB dispt ()\r
+DECLARE SUB showskipmsg ()\r
 ' Svjatoslav Agejenko\r
 ' E-mail: svjatoslav@svjatoslav.eu\r
 ' Homepage: www.hot.ee/n0/\r
 ' Svjatoslav Agejenko\r
 ' E-mail: svjatoslav@svjatoslav.eu\r
 ' Homepage: www.hot.ee/n0/\r
-\r
-DECLARE SUB dispp ()\r
-DECLARE SUB displukk ()\r
-DECLARE SUB kola (a%)\r
-DECLARE SUB rese ()\r
-DECLARE SUB start ()\r
-DECLARE SUB getnad (g%, n%, d%, k%)\r
-DECLARE SUB initp (b$)\r
-DECLARE SUB getmd (a$, m%, d%)\r
-DECLARE SUB son (a$)\r
-DECLARE SUB inita ()\r
-DECLARE SUB chm ()\r
-DECLARE SUB chd ()\r
-DECLARE SUB kell (a%)\r
-DECLARE SUB sync2 ()\r
-DECLARE SUB sync ()\r
-DECLARE SUB mnmain ()\r
-DECLARE SUB heli (a%)\r
-DECLARE SUB disp ()\r
+'\r
+' School bell scheduler (mini version): rings a bell relay connected to the\r
+' parallel port, driven by timetable files (aasta.ap = year plan selecting\r
+' which <name>.pp day-plan file to use; a day plan lists the bell times).\r
+\r
+DECLARE SUB showdayinfo ()\r
+DECLARE SUB showlockmsg ()\r
+DECLARE SUB ringbell (seconds%)\r
+DECLARE SUB resetprogram ()\r
+DECLARE SUB initprogram ()\r
+DECLARE SUB getweekday (yr%, mon%, dy%, wdout%)\r
+DECLARE SUB loadplan (planbase$)\r
+DECLARE SUB parsedate (datestr$, monthnum%, daynum%)\r
+DECLARE SUB splitwords (txt$)\r
+DECLARE SUB loadyearplan ()\r
+DECLARE SUB checkminute ()\r
+DECLARE SUB checkdate ()\r
+DECLARE SUB ringevent (ringtype%)\r
+DECLARE SUB roundtohour ()\r
+DECLARE SUB loadsyncfile ()\r
+DECLARE SUB mainloop ()\r
+DECLARE SUB playsound (sndnum%)\r
+DECLARE SUB drawscreen ()\r
 DEFINT A-Z\r
 \r
 DEFINT A-Z\r
 \r
-DIM SHARED ap$(1 TO 500)\r
-DIM SHARED apl\r
-DIM SHARED pp$(1 TO 500)\r
-DIM SHARED ppl\r
-DIM SHARED prt, prt2\r
-DIM SHARED timo$\r
-DIM SHARED dato$\r
-DIM SHARED sona$(1 TO 50)\r
-DIM SHARED mitus\r
-DIM SHARED lp$\r
-DIM SHARED ndlp\r
-DIM SHARED pn$(1 TO 7)\r
-DIM SHARED lk$\r
-DIM SHARED ssave\r
-DIM SHARED ssavel\r
-DIM SHARED timero AS LONG\r
-DIM SHARED kblukk\r
-DIM SHARED tunnidara\r
-\r
-start\r
-\r
-\r
-disp\r
-mnmain\r
-\r
-SUB chd\r
-b$ = "tuhi"\r
-IF apl = 0 THEN inita\r
-\r
-a$ = DATE$\r
-n1 = VAL(RIGHT$(a$, 4))\r
-n2 = VAL(LEFT$(a$, 2))\r
-a$ = LEFT$(a$, 5)\r
-n3 = VAL(RIGHT$(a$, 2))\r
-getnad n1, n2, n3, ndlp\r
-FOR a = 1 TO apl\r
-son ap$(a)\r
-SELECT CASE sona$(1)\r
-CASE "v"\r
-getmd sona$(2), m1, d1\r
-getmd sona$(3), m2, d2\r
-getmd DATE$, m3, d3\r
-IF m3 < m1 THEN GOTO 9\r
-IF m3 > m2 THEN GOTO 9\r
-IF m3 = m1 THEN IF d3 < d1 THEN GOTO 9\r
-IF m3 = m2 THEN IF d3 > d2 THEN GOTO 9\r
-b$ = sona$(4)\r
-CASE "n"\r
-getmd sona$(2), m1, d1\r
-getmd sona$(3), m2, d2\r
-getmd DATE$, m3, d3\r
-IF m3 < m1 THEN GOTO 9\r
-IF m3 > m2 THEN GOTO 9\r
-IF m3 = m1 THEN IF d3 < d1 THEN GOTO 9\r
-IF m3 = m2 THEN IF d3 > d2 THEN GOTO 9\r
-IF ndlp <> VAL(sona$(4)) THEN GOTO 9\r
-b$ = sona$(5)\r
-CASE "e"\r
-getmd sona$(2), m1, d1\r
-getmd DATE$, m2, d2\r
-IF (m1 = m2) AND (d1 = d2) THEN b$ = sona$(3)\r
+DIM SHARED yearplan$(1 TO 500)\r
+DIM SHARED yearplancount\r
+DIM SHARED dayplan$(1 TO 500)\r
+DIM SHARED dayplancount\r
+DIM SHARED lptport, prt2\r
+DIM SHARED lasttime$\r
+DIM SHARED lastdate$\r
+DIM SHARED words$(1 TO 50)\r
+DIM SHARED wordcount\r
+DIM SHARED loadedplan$\r
+DIM SHARED weekdaynum\r
+DIM SHARED dayname$(1 TO 7)\r
+DIM SHARED lastbell$\r
+DIM SHARED idlecount\r
+DIM SHARED idlelimit\r
+DIM SHARED timersnap AS LONG\r
+DIM SHARED keylock\r
+DIM SHARED dayoff\r
+\r
+initprogram\r
+\r
+\r
+drawscreen\r
+mainloop\r
+\r
+SUB checkdate\r
+  ' Selects today's day-plan file from the year plan (runs when the date changes).\r
+selplan$ = "tuhi"  ' default plan name when no rule matches (tuhi = empty)\r
+IF yearplancount = 0 THEN loadyearplan\r
+\r
+tmp$ = DATE$\r
+yr = VAL(RIGHT$(tmp$, 4))\r
+mon = VAL(LEFT$(tmp$, 2))\r
+tmp$ = LEFT$(tmp$, 5)\r
+dy = VAL(RIGHT$(tmp$, 2))\r
+getweekday yr, mon, dy, weekdaynum\r
+FOR i = 1 TO yearplancount\r
+splitwords yearplan$(i)\r
+SELECT CASE words$(1)\r
+CASE "v"  ' v <from> <to> <plan> = plan active within a date range\r
+parsedate words$(2), beginmon, beginday\r
+parsedate words$(3), endmon, endday\r
+parsedate DATE$, curmon, curday\r
+IF curmon < beginmon THEN GOTO 9\r
+IF curmon > endmon THEN GOTO 9\r
+IF curmon = beginmon THEN IF curday < beginday THEN GOTO 9\r
+IF curmon = endmon THEN IF curday > endday THEN GOTO 9\r
+selplan$ = words$(4)\r
+CASE "n"  ' n <from> <to> <weekday> <plan> = plan active on one weekday in a range\r
+parsedate words$(2), beginmon, beginday\r
+parsedate words$(3), endmon, endday\r
+parsedate DATE$, curmon, curday\r
+IF curmon < beginmon THEN GOTO 9\r
+IF curmon > endmon THEN GOTO 9\r
+IF curmon = beginmon THEN IF curday < beginday THEN GOTO 9\r
+IF curmon = endmon THEN IF curday > endday THEN GOTO 9\r
+IF weekdaynum <> VAL(words$(4)) THEN GOTO 9\r
+selplan$ = words$(5)\r
+CASE "e"  ' e <date> <plan> = plan active on one exact date\r
+parsedate words$(2), beginmon, beginday\r
+parsedate DATE$, endmon, endday\r
+IF (beginmon = endmon) AND (beginday = endday) THEN selplan$ = words$(3)\r
 END SELECT\r
 9\r
 END SELECT\r
 9\r
-NEXT a\r
+NEXT i\r
 \r
 \r
-IF b$ <> lp$ THEN initp b$\r
-lp$ = b$\r
-tunnidara = 0\r
-dispp\r
-disp\r
+IF selplan$ <> loadedplan$ THEN loadplan selplan$\r
+loadedplan$ = selplan$\r
+dayoff = 0\r
+showdayinfo\r
+drawscreen\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB chm\r
-a$ = DATE$\r
-IF a$ <> dato$ THEN chd\r
-dato$ = a$\r
-b = 0\r
-FOR a = 1 TO ppl\r
-son pp$(a)\r
-SELECT CASE sona$(1)\r
-CASE "#"\r
-getmd sona$(2), h1, m1\r
-getmd TIME$, h2, m2\r
+SUB checkminute\r
+  ' Minute tick: scans the day plan for bell events matching the current time.\r
+tmp$ = DATE$\r
+IF tmp$ <> lastdate$ THEN checkdate\r
+lastdate$ = tmp$\r
+ringtype = 0\r
+FOR i = 1 TO dayplancount\r
+splitwords dayplan$(i)\r
+SELECT CASE words$(1)\r
+CASE "#"  ' timetable entry: # HH:MM sis|val (sis = lesson start, val = lesson end)\r
+parsedate words$(2), bellhour, bellmin\r
+parsedate TIME$, nowhour, nowmin\r
 ' PRINT h1, m1, h2, m2\r
 ' PRINT h1, m1, h2, m2\r
-IF (h2 = h1) AND (m2 = m1) THEN\r
-IF sona$(3) = "sis" THEN b = 1\r
-IF sona$(3) = "val" THEN b = 2\r
+IF (nowhour = bellhour) AND (nowmin = bellmin) THEN\r
+IF words$(3) = "sis" THEN ringtype = 1\r
+IF words$(3) = "val" THEN ringtype = 2\r
 END IF\r
 END SELECT\r
 END IF\r
 END SELECT\r
-NEXT a\r
+NEXT i\r
 \r
 \r
-IF (tunnidara = 0) AND (b > 0) THEN kell b\r
-ssave = ssave + 1\r
+IF (dayoff = 0) AND (ringtype > 0) THEN ringevent ringtype  ' ring only when lessons are not skipped today\r
+idlecount = idlecount + 1\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB disp\r
+SUB drawscreen\r
+  ' Draws the main screen: title, key help, day-plan list and status fields.\r
 CLS\r
 PRINT "Kooli Kell (mini) v 1.1  2003.3"\r
 PRINT "Programmi autor Svjatoslav Agejenko   E-mail: n0@hot.ee"\r
 CLS\r
 PRINT "Kooli Kell (mini) v 1.1  2003.3"\r
 PRINT "Programmi autor Svjatoslav Agejenko   E-mail: n0@hot.ee"\r
@@ -131,7 +138,7 @@ PRINT "4 - 1 tund tagasi                5 - 1 tund edasi"
 PRINT "r - programmi restart            q - programmist v�lja"\r
 PRINT "                                 j - j�tab k�ik tunnid t�na �ra"\r
 \r
 PRINT "r - programmi restart            q - programmist v�lja"\r
 PRINT "                                 j - j�tab k�ik tunnid t�na �ra"\r
 \r
-dispp\r
+showdayinfo\r
 \r
 LOCATE 12, 15\r
 PRINT "Kuu-P�ev-Aasta (USA standard)"\r
 \r
 LOCATE 12, 15\r
 PRINT "Kuu-P�ev-Aasta (USA standard)"\r
@@ -139,20 +146,21 @@ PRINT "Kuu-P�ev-Aasta (USA standard)"
 \r
 LOCATE 17\r
 \r
 \r
 LOCATE 17\r
 \r
-FOR a = 1 TO ppl\r
-IF pp$(a) <> SPACE$(LEN(pp$(a))) THEN\r
-  PRINT pp$(a);\r
-  PRINT SPACE$(15 - LEN(pp$(a)));\r
+FOR i = 1 TO dayplancount\r
+IF dayplan$(i) <> SPACE$(LEN(dayplan$(i))) THEN\r
+  PRINT dayplan$(i);\r
+  PRINT SPACE$(15 - LEN(dayplan$(i)));\r
 END IF\r
 END IF\r
-NEXT a\r
+NEXT i\r
 \r
 \r
-displukk\r
-dispt\r
+showlockmsg\r
+showskipmsg\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB displukk\r
+SUB showlockmsg\r
+  ' Shows or clears the "keyboard locked" warning in the top row.\r
 LOCATE 1, 40\r
 LOCATE 1, 40\r
-IF kblukk = 1 THEN\r
+IF keylock = 1 THEN\r
   COLOR 0, 7\r
   PRINT "Klaviatuur lukus! Vajuta CTRL+L"\r
   COLOR 7, 0\r
   COLOR 0, 7\r
   PRINT "Klaviatuur lukus! Vajuta CTRL+L"\r
   COLOR 7, 0\r
@@ -161,19 +169,21 @@ ELSE
 END IF\r
 END SUB\r
 \r
 END IF\r
 END SUB\r
 \r
-SUB dispp\r
-IF ndlp = 0 THEN GOTO 14\r
+SUB showdayinfo\r
+  ' Shows the current weekday name and the active day-plan name.\r
+IF weekdaynum = 0 THEN GOTO 14\r
 LOCATE 14, 1\r
 LOCATE 14, 1\r
-PRINT "n�dalap�ev:", pn$(ndlp)\r
+PRINT "n�dalap�ev:", dayname$(weekdaynum)\r
 LOCATE 15, 1\r
 LOCATE 15, 1\r
-PRINT "p�evaplaan:", lp$\r
+PRINT "p�evaplaan:", loadedplan$\r
 14\r
 END SUB\r
 \r
 14\r
 END SUB\r
 \r
-SUB dispt\r
+SUB showskipmsg\r
+  ' Shows or clears the "all lessons skipped today" notice.\r
 LOCATE 16, 20\r
 COLOR 12 + 15, 0\r
 LOCATE 16, 20\r
 COLOR 12 + 15, 0\r
-IF tunnidara = 1 THEN\r
+IF dayoff = 1 THEN\r
   PRINT "T�na on k�ik tunnid �ra j�etud"\r
 ELSE\r
   PRINT "                              "\r
   PRINT "T�na on k�ik tunnid �ra j�etud"\r
 ELSE\r
   PRINT "                              "\r
@@ -181,59 +191,62 @@ END IF
 COLOR 7, 0\r
 END SUB\r
 \r
 COLOR 7, 0\r
 END SUB\r
 \r
-SUB getmd (a$, m, d)\r
-b$ = LEFT$(a$, 5)\r
-m = VAL(LEFT$(b$, 2))\r
-d = VAL(RIGHT$(b$, 2))\r
+SUB parsedate (datestr$, monthnum, daynum)\r
+  ' Parses month and day numbers out of an MM-DD-YYYY style date string.\r
+tmp$ = LEFT$(datestr$, 5)\r
+monthnum = VAL(LEFT$(tmp$, 2))\r
+daynum = VAL(RIGHT$(tmp$, 2))\r
 \r
 END SUB\r
 \r
 \r
 END SUB\r
 \r
-SUB getnad (g, n, d, k)\r
+SUB getweekday (yr, mon, dy, wdout)\r
+  ' Zeller's congruence: computes the weekday (1=Monday..7=Sunday) of a date.\r
 'LOCATE 11, 1\r
 'PRINT g, n, d\r
 'LOCATE 11, 1\r
 'PRINT g, n, d\r
-p = g\r
-m = n - 2\r
-IF n > 2 GOTO 120\r
-p = p - 1: m = m + 12\r
+adjyear = yr\r
+adjmon = mon - 2\r
+IF mon > 2 GOTO 120\r
+adjyear = adjyear - 1: adjmon = adjmon + 12\r
 120\r
 120\r
-c = INT(p / 100)\r
-y = p - c * 100\r
-w = d + INT((13 * m - 1) / 5) + y + INT(y / 4) + INT(c / 4) - 2 * c\r
-k = w - 7 * INT(w / 7)\r
-IF k = 0 THEN k = 7\r
+century = INT(adjyear / 100)\r
+shortyear = adjyear - century * 100\r
+zeller = dy + INT((13 * adjmon - 1) / 5) + shortyear + INT(shortyear / 4) + INT(century / 4) - 2 * century\r
+wdout = zeller - 7 * INT(zeller / 7)\r
+IF wdout = 0 THEN wdout = 7\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB heli (a)\r
+SUB playsound (sndnum)\r
+  ' Plays one of four sound effects (sndnum = effect number).\r
 'GOTO 10\r
 'GOTO 10\r
-SELECT CASE a\r
+SELECT CASE sndnum\r
 CASE 1\r
 CASE 1\r
-FOR c = 1 TO 5\r
+FOR i = 1 TO 5\r
 SOUND 3000, 1\r
 SOUND 3000, 1\r
-SOUND 0, 1\r
-NEXT c\r
+SOUND 0, 1  ' SOUND 0, n = sub-second delay workaround (inaudible 0 Hz tone)\r
+NEXT i\r
 \r
 CASE 2\r
 \r
 CASE 2\r
-FOR c = 1 TO 5\r
+FOR i = 1 TO 5\r
 SOUND 2500, 1\r
 SOUND 2500, 1\r
-SOUND 0, 2\r
-NEXT c\r
+SOUND 0, 2  ' SOUND 0, n = sub-second delay workaround (inaudible 0 Hz tone)\r
+NEXT i\r
 SOUND 2500, 10\r
 \r
 CASE 3\r
 SOUND 2500, 10\r
 \r
 CASE 3\r
-FOR a = 1 TO 10\r
+FOR i = 1 TO 10\r
 SOUND 500, .5\r
 SOUND 1500, .5\r
 SOUND 2000, .5\r
 SOUND 1520, .5\r
 SOUND 500, .5\r
 SOUND 1500, .5\r
 SOUND 2000, .5\r
 SOUND 1520, .5\r
-NEXT a\r
+NEXT i\r
 \r
 \r
 CASE 4\r
 \r
 \r
 CASE 4\r
-FOR a = 800 TO 1000 STEP 10\r
-SOUND a, .1\r
-SOUND a * 3, .1\r
-SOUND 0, 1\r
-NEXT a\r
+FOR freq = 800 TO 1000 STEP 10\r
+SOUND freq, .1\r
+SOUND freq * 3, .1\r
+SOUND 0, 1  ' SOUND 0, n = sub-second delay workaround (inaudible 0 Hz tone)\r
+NEXT freq\r
 10\r
 \r
 END SELECT\r
 10\r
 \r
 END SELECT\r
@@ -241,164 +254,169 @@ END SELECT
 \r
 END SUB\r
 \r
 \r
 END SUB\r
 \r
-SUB inita\r
-apl = 0\r
+SUB loadyearplan\r
+  ' Loads the year-plan file aasta.ap into the yearplan$ array.\r
+yearplancount = 0\r
 OPEN "aasta.ap" FOR INPUT AS #1\r
 5\r
 IF EOF(1) <> 0 THEN GOTO 3\r
 OPEN "aasta.ap" FOR INPUT AS #1\r
 5\r
 IF EOF(1) <> 0 THEN GOTO 3\r
-LINE INPUT #1, a$\r
-apl = apl + 1\r
-ap$(apl) = a$\r
+LINE INPUT #1, tmp$\r
+yearplancount = yearplancount + 1\r
+yearplan$(yearplancount) = tmp$\r
 GOTO 5\r
 3\r
 CLOSE #1\r
 END SUB\r
 \r
 GOTO 5\r
 3\r
 CLOSE #1\r
 END SUB\r
 \r
-SUB initp (b$)\r
-ppl = 0\r
-OPEN b$ + ".pp" FOR INPUT AS #1\r
+SUB loadplan (planbase$)\r
+  ' Loads the day-plan file <planbase$>.pp into the dayplan$ array.\r
+dayplancount = 0\r
+OPEN planbase$ + ".pp" FOR INPUT AS #1\r
 6\r
 IF EOF(1) <> 0 THEN GOTO 7\r
 6\r
 IF EOF(1) <> 0 THEN GOTO 7\r
-LINE INPUT #1, a$\r
-ppl = ppl + 1\r
-pp$(ppl) = a$\r
+LINE INPUT #1, tmp$\r
+dayplancount = dayplancount + 1\r
+dayplan$(dayplancount) = tmp$\r
 GOTO 6\r
 7\r
 CLOSE #1\r
 END SUB\r
 \r
 GOTO 6\r
 7\r
 CLOSE #1\r
 END SUB\r
 \r
-SUB kell (a)\r
-b$ = TIME$ + DATE$\r
-IF b$ <> lk$ THEN lk$ = b$ ELSE GOTO 2\r
+SUB ringevent (ringtype)\r
+  ' Rings the bell for an event (1 = lesson start, 2 = lesson end), max once per second.\r
+tmp$ = TIME$ + DATE$\r
+IF tmp$ <> lastbell$ THEN lastbell$ = tmp$ ELSE GOTO 2  ' skip if the bell already rang during this second\r
 \r
 \r
-heli 3\r
+playsound 3\r
 \r
 \r
-SELECT CASE a\r
+SELECT CASE ringtype\r
 CASE 1\r
 CASE 1\r
-kola 4\r
-FOR b = 1 TO 15\r
-SOUND 0, 1\r
-NEXT b\r
-kola 1\r
+ringbell 4\r
+FOR i = 1 TO 15\r
+SOUND 0, 1  ' SOUND 0, n = sub-second delay workaround (inaudible 0 Hz tone)\r
+NEXT i\r
+ringbell 1\r
 CASE 2\r
 CASE 2\r
-kola 5\r
+ringbell 5\r
 END SELECT\r
 2\r
 END SUB\r
 \r
 END SELECT\r
 2\r
 END SUB\r
 \r
-SUB kola (a)\r
-timero = TIMER\r
+SUB ringbell (seconds)\r
+  ' Rings the physical bell: toggles the LPT port lines for the given seconds.\r
+timersnap = TIMER\r
 11\r
 11\r
-FOR b = 1 TO 100\r
-OUT prt, 0\r
-OUT prt, 255\r
-NEXT b\r
-IF ABS(timero - TIMER) < a THEN GOTO 11\r
+FOR i = 1 TO 100\r
+OUT lptport, 0  ' toggle all LPT data lines (drives the bell relay)\r
+OUT lptport, 255\r
+NEXT i\r
+IF ABS(timersnap - TIMER) < seconds THEN GOTO 11\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB mnmain\r
+SUB mainloop\r
+  ' Main loop: checks the clock, handles keys, runs the idle screensaver.\r
 1\r
 1\r
-b$ = LEFT$(TIME$, 5)\r
-IF b$ <> timo$ THEN chm\r
-timo$ = b$\r
+tmp$ = LEFT$(TIME$, 5)\r
+IF tmp$ <> lasttime$ THEN checkminute\r
+lasttime$ = tmp$\r
 \r
 \r
-a$ = INKEY$\r
+key$ = INKEY$\r
 \r
 \r
-IF a$ <> "" THEN\r
-IF ssave > ssavel THEN disp\r
-ssave = 0\r
+IF key$ <> "" THEN\r
+  IF idlecount > idlelimit THEN drawscreen  ' first keypress after idle: redraw the main screen\r
+  idlecount = 0\r
 END IF\r
 \r
 END IF\r
 \r
-IF a$ = CHR$(12) THEN\r
-  IF kblukk = 1 THEN kblukk = 0 ELSE kblukk = 1\r
-  displukk\r
+IF key$ = CHR$(12) THEN  ' CTRL+L toggles the keyboard lock\r
+  IF keylock = 1 THEN keylock = 0 ELSE keylock = 1\r
+  showlockmsg\r
 END IF\r
 END IF\r
-IF kblukk = 1 THEN a$ = ""\r
+IF keylock = 1 THEN key$ = ""\r
 \r
 \r
-IF a$ = "s" THEN kell 1\r
-IF a$ = "v" THEN kell 2\r
+IF key$ = "s" THEN ringevent 1\r
+IF key$ = "v" THEN ringevent 2\r
 \r
 \r
-IF a$ = "a" THEN\r
-CLS\r
-PRINT "                  vana aeg: " + TIME$\r
-INPUT "sisesta uus aeg (TT:MM:SS): ", b$\r
-IF LEN(b$) <> 8 THEN GOTO 12\r
-TIME$ = b$\r
-timo$ = ""\r
-12\r
-disp\r
+IF key$ = "a" THEN\r
+  CLS\r
+  PRINT "                  vana aeg: " + TIME$\r
+  INPUT "sisesta uus aeg (TT:MM:SS): ", tmp$\r
+  IF LEN(tmp$) <> 8 THEN GOTO 12\r
+  TIME$ = tmp$\r
+  lasttime$ = ""\r
+  12\r
+  drawscreen\r
 END IF\r
 \r
 END IF\r
 \r
-IF a$ = "d" THEN\r
-CLS\r
-PRINT "                    vana daatum: " + DATE$\r
-INPUT "sisesta uus daatum (KK-PP-AAAA): ", b$\r
-IF LEN(b$) <> 10 THEN GOTO 13\r
-DATE$ = b$\r
-timo$ = ""\r
-13\r
-disp\r
+IF key$ = "d" THEN\r
+  CLS\r
+  PRINT "                    vana daatum: " + DATE$\r
+  INPUT "sisesta uus daatum (KK-PP-AAAA): ", tmp$\r
+  IF LEN(tmp$) <> 10 THEN GOTO 13\r
+  DATE$ = tmp$\r
+  lasttime$ = ""\r
+  13\r
+  drawscreen\r
 END IF\r
 \r
 END IF\r
 \r
-IF a$ = "7" OR a$ = "8" THEN\r
-  b = VAL(RIGHT$(LEFT$(TIME$, 5), 2))\r
-  IF a$ = "7" THEN b = b - 1\r
-  IF a$ = "8" THEN b = b + 1\r
-  IF b < 0 THEN b = 0\r
-  IF b > 59 THEN b = 59\r
-  d$ = STR$(b)\r
-  IF LEFT$(d$, 1) = " " THEN d$ = RIGHT$(d$, LEN(d$) - 1)\r
-  IF LEN(d$) < 2 THEN d$ = "0" + d$\r
-  e$ = LEFT$(TIME$, 3) + d$ + RIGHT$(TIME$, 3)\r
-  TIME$ = e$\r
+IF key$ = "7" OR key$ = "8" THEN\r
+  num = VAL(RIGHT$(LEFT$(TIME$, 5), 2))\r
+  IF key$ = "7" THEN num = num - 1\r
+  IF key$ = "8" THEN num = num + 1\r
+  IF num < 0 THEN num = 0\r
+  IF num > 59 THEN num = 59\r
+  numstr$ = STR$(num)\r
+  IF LEFT$(numstr$, 1) = " " THEN numstr$ = RIGHT$(numstr$, LEN(numstr$) - 1)\r
+  IF LEN(numstr$) < 2 THEN numstr$ = "0" + numstr$\r
+  newtime$ = LEFT$(TIME$, 3) + numstr$ + RIGHT$(TIME$, 3)\r
+  TIME$ = newtime$\r
 END IF\r
 \r
 END IF\r
 \r
-IF a$ = "4" OR a$ = "5" THEN\r
-  b = VAL(LEFT$(TIME$, 2))\r
-  IF a$ = "4" THEN b = b - 1\r
-  IF a$ = "5" THEN b = b + 1\r
-  IF b < 0 THEN b = 0\r
-  IF b > 23 THEN b = 23\r
-  d$ = STR$(b)\r
-  IF LEFT$(d$, 1) = " " THEN d$ = RIGHT$(d$, LEN(d$) - 1)\r
-  IF LEN(d$) < 2 THEN d$ = "0" + d$\r
-  e$ = d$ + RIGHT$(TIME$, 6)\r
-  TIME$ = e$\r
+IF key$ = "4" OR key$ = "5" THEN\r
+  num = VAL(LEFT$(TIME$, 2))\r
+  IF key$ = "4" THEN num = num - 1\r
+  IF key$ = "5" THEN num = num + 1\r
+  IF num < 0 THEN num = 0\r
+  IF num > 23 THEN num = 23\r
+  numstr$ = STR$(num)\r
+  IF LEFT$(numstr$, 1) = " " THEN numstr$ = RIGHT$(numstr$, LEN(numstr$) - 1)\r
+  IF LEN(numstr$) < 2 THEN numstr$ = "0" + numstr$\r
+  newtime$ = numstr$ + RIGHT$(TIME$, 6)\r
+  TIME$ = newtime$\r
 END IF\r
 \r
 END IF\r
 \r
-IF a$ = "u" THEN sync2\r
-IF a$ = "l" THEN sync\r
+IF key$ = "u" THEN roundtohour\r
+IF key$ = "l" THEN loadsyncfile\r
 \r
 \r
-IF a$ = "r" THEN rese\r
-IF a$ = "q" THEN SYSTEM\r
+IF key$ = "r" THEN resetprogram\r
+IF key$ = "q" THEN SYSTEM\r
 \r
 \r
-IF a$ = "j" THEN\r
-IF tunnidara = 0 THEN tunnidara = 1 ELSE tunnidara = 0\r
-dispt\r
+IF key$ = "j" THEN\r
+  IF dayoff = 0 THEN dayoff = 1 ELSE dayoff = 0  ' j = skip all lessons today (toggle)\r
+  showskipmsg\r
 END IF\r
 \r
 END IF\r
 \r
-IF ssave <= ssavel THEN\r
+IF idlecount <= idlelimit THEN\r
   LOCATE 11, 1\r
   PRINT TIME$\r
   LOCATE 12, 1\r
   PRINT DATE$\r
 ELSE\r
   LOCATE 11, 1\r
   PRINT TIME$\r
   LOCATE 12, 1\r
   PRINT DATE$\r
 ELSE\r
-  IF ABS(TIMER - timero) > 10 THEN\r
-  CLS\r
-  kblukk = 1\r
-  FOR b = 1 TO 20\r
-    LOCATE RND * 22 + 1, RND * 79 + 1\r
-    IF RND * 100 < 50 THEN PRINT "*" ELSE PRINT "."\r
-  NEXT b\r
-  LOCATE RND * 22 + 1, RND * 50 + 1\r
-  COLOR 0, 7\r
-  PRINT "< " + LEFT$(TIME$, 2);\r
-  COLOR 16, 7\r
-  PRINT ":";\r
-  COLOR 0, 7\r
-  PRINT RIGHT$(LEFT$(TIME$, 5), 2) + " >"\r
-  COLOR 7, 0\r
-  timero = TIMER\r
+  IF ABS(TIMER - timersnap) > 10 THEN  ' idle too long: starfield screensaver with clock\r
+    CLS\r
+    keylock = 1\r
+    FOR i = 1 TO 20\r
+      LOCATE RND * 22 + 1, RND * 79 + 1\r
+      IF RND * 100 < 50 THEN PRINT "*" ELSE PRINT "."\r
+    NEXT i\r
+    LOCATE RND * 22 + 1, RND * 50 + 1\r
+    COLOR 0, 7\r
+    PRINT "< " + LEFT$(TIME$, 2);\r
+    COLOR 16, 7\r
+    PRINT ":";\r
+    COLOR 0, 7\r
+    PRINT RIGHT$(LEFT$(TIME$, 5), 2) + " >"\r
+    COLOR 7, 0\r
+    timersnap = TIMER\r
   END IF\r
 END IF\r
 GOTO 1\r
   END IF\r
 END IF\r
 GOTO 1\r
@@ -406,80 +424,85 @@ GOTO 1
 \r
 END SUB\r
 \r
 \r
 END SUB\r
 \r
-SUB rese\r
-heli 4\r
-timo$ = ""\r
-dato$ = ""\r
-apl = 0\r
+SUB resetprogram\r
+  ' Resets cached date/time and the year plan so everything reloads.\r
+playsound 4\r
+lasttime$ = ""\r
+lastdate$ = ""\r
+yearplancount = 0\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB son (a$)\r
+SUB splitwords (txt$)\r
+  ' Splits a string at spaces/tabs into the words$ array (wordcount words).\r
 \r
 \r
-FOR b = 1 TO 50\r
-sona$(b) = ""\r
-NEXT b\r
-mitus = 0\r
+FOR inword = 1 TO 50\r
+words$(inword) = ""\r
+NEXT inword\r
+wordcount = 0\r
 \r
 \r
-b = 1\r
-FOR c = 1 TO LEN(a$)\r
-d$ = RIGHT$(LEFT$(a$, c), 1)\r
-IF d$ = " " OR d$ = CHR$(9) THEN\r
-b = 1\r
+inword = 1\r
+FOR i = 1 TO LEN(txt$)\r
+ch$ = RIGHT$(LEFT$(txt$, i), 1)\r
+IF ch$ = " " OR ch$ = CHR$(9) THEN\r
+inword = 1\r
 ELSE\r
 ELSE\r
-IF b = 1 THEN b = 0: mitus = mitus + 1\r
-sona$(mitus) = sona$(mitus) + d$\r
+IF inword = 1 THEN inword = 0: wordcount = wordcount + 1\r
+words$(wordcount) = words$(wordcount) + ch$\r
 END IF\r
 END IF\r
-NEXT c\r
+NEXT i\r
 \r
 \r
 END SUB\r
 \r
 \r
 \r
 END SUB\r
 \r
-SUB start\r
-pn$(1) = "esmasp�ev"\r
-pn$(2) = "teisip�ev"\r
-pn$(3) = "kolmap�ev"\r
-pn$(4) = "neljap�ev"\r
-pn$(5) = "reede"\r
-pn$(6) = "laup�ev"\r
-pn$(7) = "p�hap�ev"\r
-\r
-prt = &H378\r
-\r
-ssavel = 2\r
-kblukk = 1\r
-tunnidara = 0\r
+SUB initprogram\r
+  ' Program init: weekday names, LPT port address, default flags.\r
+dayname$(1) = "esmasp�ev"\r
+dayname$(2) = "teisip�ev"\r
+dayname$(3) = "kolmap�ev"\r
+dayname$(4) = "neljap�ev"\r
+dayname$(5) = "reede"\r
+dayname$(6) = "laup�ev"\r
+dayname$(7) = "p�hap�ev"\r
+\r
+lptport = &H378  ' LPT1 parallel port base address\r
+\r
+idlelimit = 2  ' screensaver starts after 2 minutes without keypresses\r
+keylock = 1  ' keyboard starts locked (unlock with CTRL+L)\r
+dayoff = 0\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB sync\r
+SUB loadsyncfile\r
+  ' Reads date and time from sync.txt and sets the system clock.\r
 OPEN "sync.txt" FOR INPUT AS #1\r
 OPEN "sync.txt" FOR INPUT AS #1\r
-LINE INPUT #1, a$\r
-DATE$ = a$\r
-LINE INPUT #1, a$\r
-TIME$ = a$\r
+LINE INPUT #1, tmp$\r
+DATE$ = tmp$\r
+LINE INPUT #1, tmp$\r
+TIME$ = tmp$\r
 CLOSE #1\r
 \r
 CLOSE #1\r
 \r
-heli 2\r
+playsound 2\r
 END SUB\r
 \r
 END SUB\r
 \r
-SUB sync2\r
-a$ = TIME$\r
-a$ = LEFT$(a$, 5)\r
-b = VAL(RIGHT$(a$, 2))\r
-c = VAL(LEFT$(a$, 2))\r
-IF b >= 30 THEN c = c + 1\r
-b = 0\r
-IF c > 23 THEN c = c - 24\r
-a$ = RIGHT$(STR$(c), LEN(STR$(c)) - 1)\r
-b$ = RIGHT$(STR$(b), LEN(STR$(b)) - 1)\r
-IF LEN(a$) < 2 THEN a$ = "0" + a$\r
-IF LEN(b$) < 2 THEN b$ = "0" + b$\r
-a$ = a$ + ":" + b$\r
+SUB roundtohour\r
+  ' Rounds the current time up to the nearest full hour.\r
+timestr$ = TIME$\r
+timestr$ = LEFT$(timestr$, 5)\r
+mins = VAL(RIGHT$(timestr$, 2))\r
+hrs = VAL(LEFT$(timestr$, 2))\r
+IF mins >= 30 THEN hrs = hrs + 1  ' past half hour: round up to the next hour\r
+mins = 0\r
+IF hrs > 23 THEN hrs = hrs - 24\r
+timestr$ = RIGHT$(STR$(hrs), LEN(STR$(hrs)) - 1)\r
+minstr$ = RIGHT$(STR$(mins), LEN(STR$(mins)) - 1)\r
+IF LEN(timestr$) < 2 THEN timestr$ = "0" + timestr$\r
+IF LEN(minstr$) < 2 THEN minstr$ = "0" + minstr$\r
+timestr$ = timestr$ + ":" + minstr$\r
 \r
 'LOCATE 10, 1\r
 'PRINT a$\r
 \r
 \r
 'LOCATE 10, 1\r
 'PRINT a$\r
 \r
-TIME$ = a$\r
+TIME$ = timestr$\r
 \r
 \r
-heli 1\r
+playsound 1\r
 END SUB\r
 \r
 END SUB\r
 \r