Better code readability
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Wed, 25 Jun 2025 14:46:27 +0000 (17:46 +0300)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Wed, 25 Jun 2025 14:46:27 +0000 (17:46 +0300)
3D GFX/Maze explorer/mazeexplorer.bas
3D GFX/Ray casting engine/raycast.bas

index 3b7353d..2810ade 100755 (executable)
@@ -1,4 +1,3 @@
-DECLARE SUB verifyTsrIsLoaded ()\r
 ' Evolving 3D Maze explorer.\r
 '\r
 ' By Svjatoslav Agejenko.\r
@@ -7,7 +6,7 @@ DECLARE SUB verifyTsrIsLoaded ()
 \r
 ' Changelog:\r
 ' 2003.12, Initial version\r
-' 2024, Improved program readability using AI\r
+' 2024 - 2025, Improved program readability\r
 '\r
 ' Navigation:\r
 '   WASD - move around\r
@@ -17,210 +16,216 @@ DECLARE SUB verifyTsrIsLoaded ()
 '\r
 ' Press 'q' to quit the program\r
 \r
+DECLARE SUB verifyTsrIsLoaded ()\r
 DECLARE SUB startText ()\r
-DECLARE SUB control ()\r
+DECLARE SUB handleInput ()\r
 DECLARE SUB putByte (addr!, dat!)\r
 DECLARE SUB putWord (addr!, dat!)\r
 DECLARE FUNCTION getWord! (addr!)\r
 DECLARE FUNCTION getByte! (addr!)\r
-DECLARE SUB start ()\r
-DECLARE SUB animate ()\r
+DECLARE SUB initializeProgram ()\r
+DECLARE SUB render3DScene ()\r
 \r
-DIM SHARED px(1 TO 500)\r
-DIM SHARED py(1 TO 500)\r
-DIM SHARED pz(1 TO 500)\r
-DIM SHARED rpx(1 TO 500)\r
-DIM SHARED rpy(1 TO 500)\r
-DIM SHARED rpe(1 TO 500)\r
+DIM SHARED pointX(1 TO 500)\r
+DIM SHARED pointY(1 TO 500)\r
+DIM SHARED pointZ(1 TO 500)\r
+DIM SHARED renderedPointX(1 TO 500)\r
+DIM SHARED renderedPointY(1 TO 500)\r
+DIM SHARED renderedPointEnabled(1 TO 500)\r
 \r
-DIM SHARED l1(1 TO 500)\r
-DIM SHARED l2(1 TO 500)\r
-DIM SHARED lc(1 TO 500)\r
+DIM SHARED lineStart(1 TO 500)\r
+DIM SHARED lineEnd(1 TO 500)\r
+DIM SHARED lineColor(1 TO 500)\r
 \r
-DIM SHARED nl, np\r
+DIM SHARED lineCount, pointCount\r
 \r
-DIM SHARED an1, an2, an3\r
+DIM SHARED angleY, angleX, angleZ\r
 \r
-DIM SHARED tim\r
+DIM SHARED frameTime\r
 \r
-DIM SHARED extSEG, extADDR\r
+DIM SHARED extensionSegment, extensionAddress\r
 \r
-DIM SHARED myx, myy, myz\r
-DIM SHARED myxs, myys, myzs\r
-DIM SHARED buttonL, buttonR\r
-DIM SHARED maxmove\r
+DIM SHARED cameraX, cameraY, cameraZ\r
+DIM SHARED cameraXSpeed, cameraYSpeed, cameraZSpeed\r
+DIM SHARED leftMouseButton, rightMouseButton\r
+DIM SHARED maxMove\r
 \r
-nl = 0\r
-np = 0\r
+lineCount = 0\r
+pointCount = 0\r
 \r
-start\r
+initializeProgram\r
 \r
 ' Initialize position variables\r
-cx = 0\r
-cy = 0\r
-cz = 0\r
+currentX = 0\r
+currentY = 0\r
+currentZ = 0\r
 \r
-np = 1\r
-px(1) = 0\r
-py(1) = 0\r
-pz(1) = 0\r
+pointCount = 1\r
+pointX(pointCount) = currentX\r
+pointY(pointCount) = currentY\r
+pointZ(pointCount) = currentZ\r
 \r
 1\r
 \r
-IF np < 500 THEN\r
+IF pointCount < 500 THEN\r
 \r
   ' Increase the number of points and add a new point at the current position\r
-  np = np + 1\r
-  px(np) = cx\r
-  py(np) = cy\r
-  pz(np) = cz\r
+  pointCount = pointCount + 1\r
+  pointX(pointCount) = currentX\r
+  pointY(pointCount) = currentY\r
+  pointZ(pointCount) = currentZ\r
 \r
   ' Increase the number of lines and define a line between the new point and the previous one\r
-  nl = nl + 1\r
-  l1(nl) = np\r
-  l2(nl) = np - 1\r
-  lc(nl) = INT(RND * 15) + 1\r
+  lineCount = lineCount + 1\r
+  lineStart(lineCount) = pointCount\r
+  lineEnd(lineCount) = pointCount - 1\r
+  lineColor(lineCount) = INT(RND * 15) + 1\r
 \r
\r
-  SELECT CASE INT(RND * 3)  ' Randomly choose orientation for the next move\r
+  ' Randomly choose orientation for the next move\r
+  SELECT CASE INT(RND * 3)\r
   CASE 0\r
-    cx = RND * 500 - 250\r
+    currentX = RND * 500 - 250\r
   CASE 1\r
-    cy = RND * 100 - 50\r
+    currentY = RND * 100 - 50\r
   CASE 2\r
-    cz = RND * 500 - 250\r
+    currentZ = RND * 500 - 250\r
   END SELECT\r
 END IF\r
 \r
-\r
-control\r
-animate\r
+handleInput\r
+render3DScene\r
 \r
 ' Copy the current screen to page 1 and clear the screen\r
 PCOPY 0, 1\r
 CLS\r
 GOTO 1\r
 \r
-SUB animate\r
+SUB render3DScene\r
 \r
-s1 = SIN(an1)\r
-s2 = SIN(an2)\r
-s3 = SIN(an3)\r
+' Calculate sine and cosine for Y-axis rotation\r
+sinY = SIN(angleY)\r
+cosY = COS(angleY)\r
 \r
-c1 = COS(an1)\r
-c2 = COS(an2)\r
-c3 = COS(an3)\r
+' Calculate sine and cosine for X-axis rotation\r
+sinX = SIN(angleX)\r
+cosX = COS(angleX)\r
+\r
+' Calculate sine and cosine for Z-axis rotation (not used in current code)\r
+sinZ = SIN(angleZ)\r
+cosZ = COS(angleZ)\r
 \r
 ' Project 3D points to 2D screen coordinates\r
-FOR a = 1 TO np\r
-  x = px(a) - myx\r
-  y = py(a) - myy\r
-  z = pz(a) - myz\r
-\r
-  ' Rotate around the Y axis\r
-  x1 = x * c1 + z * s1\r
-  z1 = z * c1 - x * s1\r
-\r
-  ' Rotate around the X axis\r
-  y1 = y * c2 + z1 * s2\r
-  z2 = z1 * c2 - y * s2\r
-\r
-  ' Check if the point is in front of the camera\r
-  IF z2 > 3 THEN\r
-    rpe(a) = 1\r
-    rpx(a) = x1 / z2 * 130 + 160\r
-    rpy(a) = y1 / z2 * 130 + 100\r
+FOR a = 1 TO pointCount\r
+  deltaX = pointX(a) - cameraX\r
+  deltaY = pointY(a) - cameraY\r
+  deltaZ = pointZ(a) - cameraZ\r
+\r
+  ' Rotate around Y axis\r
+  rotatedX = deltaX * cosY + deltaZ * sinY\r
+  rotatedZ = deltaZ * cosY - deltaX * sinY\r
+\r
+  ' Rotate around X axis\r
+  rotatedY = deltaY * cosX + rotatedZ * sinX\r
+  finalZ = rotatedZ * cosX - deltaY * sinX\r
+\r
+  ' Check if the point is in front of the camera (finalZ > 3)\r
+  IF finalZ > 3 THEN\r
+    renderedPointEnabled(a) = 1\r
+    renderedPointX(a) = rotatedX / finalZ * 130 + 160\r
+    renderedPointY(a) = rotatedY / finalZ * 130 + 100\r
   ELSE\r
-    rpe(a) = 0\r
+    renderedPointEnabled(a) = 0\r
   END IF\r
 NEXT a\r
 \r
 ' Draw lines between visible points\r
-FOR a = 1 TO nl\r
+FOR a = 1 TO lineCount\r
 \r
-  p1 = l1(a)\r
-  p2 = l2(a)\r
-  IF (rpe(p1) = 1) AND (rpe(p2) = 1) THEN LINE (rpx(p1), rpy(p1))-(rpx(p2), rpy(p2)), lc(a)\r
+  p1 = lineStart(a)\r
+  p2 = lineEnd(a)\r
+  IF (renderedPointEnabled(p1) = 1) AND (renderedPointEnabled(p2) = 1) THEN\r
+    LINE (renderedPointX(p1), renderedPointY(p1))-(renderedPointX(p2), renderedPointY(p2)), lineColor(a)\r
+  END IF\r
 \r
 NEXT a\r
 \r
 END SUB\r
 \r
-SUB control\r
+SUB handleInput\r
 \r
 ' Read mouse data\r
 IF getByte(8) <> 0 THEN\r
   putByte 8, 0\r
-  ' read mouse translation along x axis\r
-  xp = getWord(2)\r
+  ' Read mouse translation along x axis\r
+  mouseXDelta = getWord(2)\r
   putWord 2, 0\r
-  ' read mouse translation along y axis\r
-  yp = getWord(4)\r
+  ' Read mouse translation along y axis\r
+  mouseYDelta = getWord(4)\r
   putWord 4, 0\r
-  ' read pressed mouse buttons\r
+  ' Read pressed mouse buttons\r
   button = getWord(6)\r
   putWord 6, 0\r
 \r
-  ' detect if left, right or both mouse buttons were pressed\r
-  buttonL = 0\r
-  buttonR = 0\r
-  IF button = 1 THEN buttonL = 1\r
-  IF button = 2 THEN buttonR = 1\r
-  IF button = 3 THEN buttonL = 1: buttonR = 1\r
+  ' Detect if left, right or both mouse buttons were pressed\r
+  leftMouseButton = 0\r
+  rightMouseButton = 0\r
+  IF button = 1 THEN leftMouseButton = 1\r
+  IF button = 2 THEN rightMouseButton = 1\r
+  IF button = 3 THEN leftMouseButton = 1: rightMouseButton = 1\r
 \r
-  IF buttonR = 1 THEN\r
-    IF buttonL = 1 THEN\r
+  IF rightMouseButton = 1 THEN\r
+    IF leftMouseButton = 1 THEN\r
       ' If mouse left and right buttons are pressed at the same time,\r
       ' use mouse translation to move avatar around X and Z axis.\r
-      myxs = myxs + SIN(an1) * yp / 4\r
-      myzs = myzs - COS(an1) * yp / 4\r
+      cameraXSpeed = cameraXSpeed + SIN(angleY) * mouseYDelta / 4\r
+      cameraZSpeed = cameraZSpeed - COS(angleY) * mouseYDelta / 4\r
       GOTO 3\r
     END IF\r
     ' If only right button is pressed, move around Y axis\r
-    myys = myys + yp / 4\r
+    cameraYSpeed = cameraYSpeed + mouseYDelta / 4\r
 3\r
-    yp = 0\r
+    mouseYDelta = 0\r
   END IF\r
 \r
 END IF\r
 \r
-' Limit mouse movement to maxmove\r
-IF xp < -maxmove THEN xp = -maxmove\r
-IF xp > maxmove THEN xp = maxmove\r
-an1 = an1 - xp / 150\r
+' Limit mouse movement to maxMove\r
+IF mouseXDelta < -maxMove THEN mouseXDelta = -maxMove\r
+IF mouseXDelta > maxMove THEN mouseXDelta = maxMove\r
+angleY = angleY - mouseXDelta / 150\r
 \r
-IF yp < -maxmove THEN yp = -maxmove\r
-IF yp > maxmove THEN yp = maxmove\r
-an2 = an2 - yp / 150\r
+IF mouseYDelta < -maxMove THEN mouseYDelta = -maxMove\r
+IF mouseYDelta > maxMove THEN mouseYDelta = maxMove\r
+angleX = angleX - mouseYDelta / 150\r
 \r
 ' Read keyboard input and update player position\r
-a$ = INKEY$\r
+keyInput$ = INKEY$\r
 \r
-IF a$ = "a" THEN myxs = myxs - COS(an1): myzs = myzs - SIN(an1)\r
-IF a$ = "d" THEN myxs = myxs + COS(an1): myzs = myzs + SIN(an1)\r
-IF a$ = "w" THEN myxs = myxs - SIN(an1): myzs = myzs + COS(an1)\r
-IF a$ = "s" THEN myxs = myxs + SIN(an1): myzs = myzs - COS(an1)\r
-IF a$ = "q" THEN SYSTEM\r
+IF keyInput$ = "a" THEN cameraXSpeed = cameraXSpeed - COS(angleY): cameraZSpeed = cameraZSpeed - SIN(angleY)\r
+IF keyInput$ = "d" THEN cameraXSpeed = cameraXSpeed + COS(angleY): cameraZSpeed = cameraZSpeed + SIN(angleY)\r
+IF keyInput$ = "w" THEN cameraXSpeed = cameraXSpeed - SIN(angleY): cameraZSpeed = cameraZSpeed + COS(angleY)\r
+IF keyInput$ = "s" THEN cameraXSpeed = cameraXSpeed + SIN(angleY): cameraZSpeed = cameraZSpeed - COS(angleY)\r
+IF keyInput$ = "q" THEN SYSTEM\r
 \r
 ' Apply friction to player movement\r
-myxs = myxs / 1.1\r
-myys = myys / 1.1\r
-myzs = myzs / 1.1\r
+cameraXSpeed = cameraXSpeed / 1.1\r
+cameraYSpeed = cameraYSpeed / 1.1\r
+cameraZSpeed = cameraZSpeed / 1.1\r
 \r
 ' Update player position\r
-myx = myx + myxs\r
-myz = myz + myzs\r
-myy = myy + myys\r
+cameraX = cameraX + cameraXSpeed\r
+cameraZ = cameraZ + cameraZSpeed\r
+cameraY = cameraY + cameraYSpeed\r
 \r
 END SUB\r
 \r
 FUNCTION getByte (addr)\r
-getByte = PEEK(extADDR + addr)\r
+getByte = PEEK(extensionAddress + addr)\r
 END FUNCTION\r
 \r
 FUNCTION getWord (addr)\r
-a = PEEK(extADDR + addr)\r
-b = PEEK(extADDR + addr + 1)\r
+a = PEEK(extensionAddress + addr)\r
+b = PEEK(extensionAddress + addr + 1)\r
 \r
 ' Convert bytes to a hex string and then to an integer\r
 c$ = HEX$(a)\r
@@ -232,42 +237,8 @@ c = VAL("&H" + HEX$(b) + c$)
 getWord = c\r
 END FUNCTION\r
 \r
-SUB mousedemo\r
-\r
-cx = 150\r
-cy = 100\r
-maxmove = 50\r
-100\r
-frm = frm + 1\r
-\r
-LOCATE 1, 1\r
-PRINT cx, cy\r
-PRINT frm\r
-\r
-CIRCLE (cx, cy), 10, 0\r
-xp = getWord(2)\r
-putWord 2, 0\r
-yp = getWord(4)\r
-putWord 4, 0\r
-\r
-IF xp < -maxmove THEN xp = -maxmove\r
-IF xp > maxmove THEN xp = maxmove\r
-cx = cx + xp\r
-\r
-IF yp < -maxmove THEN yp = -maxmove\r
-IF yp > maxmove THEN yp = maxmove\r
-cy = cy + yp\r
-\r
-CIRCLE (cx, cy), 10, 10\r
-\r
-SOUND 0, .05\r
-GOTO 100\r
-\r
-END SUB\r
-\r
 SUB putByte (addr, dat)\r
-\r
-POKE (extADDR + addr), dat\r
+POKE (extensionAddress + addr), dat\r
 END SUB\r
 \r
 SUB putWord (addr, dat)\r
@@ -280,36 +251,38 @@ IF LEN(b$) < 4 THEN b$ = "0" + b$: GOTO 2
 n1 = VAL("&H" + LEFT$(b$, 2))\r
 n2 = VAL("&H" + RIGHT$(b$, 2))\r
 \r
-POKE (extADDR + addr), n2\r
-POKE (extADDR + addr + 1), n1\r
+POKE (extensionAddress + addr), n2\r
+POKE (extensionAddress + addr + 1), n1\r
 \r
 END SUB\r
 \r
-SUB start\r
+SUB initializeProgram\r
 verifyTsrIsLoaded\r
 \r
 SCREEN 7, , , 1\r
 \r
-maxmove = 50\r
+' Set camera speed limit\r
+maxMove = 50\r
 \r
 END SUB\r
 \r
 SUB verifyTsrIsLoaded\r
 \r
-DEF SEG = 0     ' read first from interrupt table\r
+DEF SEG = 0     ' Read first from interrupt table\r
 \r
-extSEG = PEEK(&H79 * 4 + 3) * 256\r
-extSEG = extSEG + PEEK(&H79 * 4 + 2)\r
+extensionSegment = PEEK(&H79 * 4 + 3) * 256\r
+extensionSegment = extensionSegment + PEEK(&H79 * 4 + 2)\r
 \r
-PRINT "Segment is: " + HEX$(extSEG)\r
+PRINT "Segment is: " + HEX$(extensionSegment)\r
 \r
-extADDR = PEEK(&H79 * 4 + 1) * 256\r
-extADDR = extADDR + PEEK(&H79 * 4 + 0)\r
+extensionAddress = PEEK(&H79 * 4 + 1) * 256\r
+extensionAddress = extensionAddress + PEEK(&H79 * 4 + 0)\r
 \r
-PRINT "relative address is:"; extADDR\r
+PRINT "relative address is:"; extensionAddress\r
 \r
-DEF SEG = extSEG\r
+DEF SEG = extensionSegment\r
 \r
+' Verify TSR signature\r
 IF getWord(0) <> 1983 THEN\r
   PRINT "FATAL ERROR:  you must load"\r
   PRINT "QBasic extension TSR first!"\r
@@ -317,4 +290,3 @@ IF getWord(0) <> 1983 THEN
 END IF\r
 \r
 END SUB\r
-\r
index 7a36760..46be305 100755 (executable)
@@ -1,3 +1,12 @@
+DECLARE SUB InitializeProgram ()\r
+DECLARE SUB GenerateLandscape ()\r
+DECLARE SUB DisplayTopDownLandscape ()\r
+DECLARE SUB DisplayFrame ()\r
+DECLARE FUNCTION getcol! (r!, g!, b!)\r
+DECLARE SUB FillSquareArea (x1%, y1%, x2%, y2%, c%, h%)\r
+DECLARE SUB CreateTower (towerX%, towerY%)\r
+DECLARE SUB SetupPalette ()\r
+DECLARE SUB DrawLineFromPlayer (x%, y%, xl%)\r
 ' Realtime 3D rendering with ray casting engine.\r
 \r
 ' By Svjatoslav Agejenko.\r
@@ -6,7 +15,7 @@
 \r
 ' Changelog:\r
 ' 2003.03, Initial version\r
-' 2024.10, Improved program readability using AI\r
+' 2024 - 2025, Improved program readability\r
 \r
 ' 3D engine automatically adjusts quality to keep constant framerate at 10 fps.\r
 ' When framerate is lower than 10 fps, quality will be decreased to speed up rendering.\r
 ' Space      - jump up (fly)\r
 ' ESC        - exit program\r
 \r
-DECLARE FUNCTION getcol! (r!, g!, b!)\r
 DEFINT A-Y\r
-DECLARE SUB traceline (x%, y%, xl%)\r
-DECLARE SUB tower (x%, y%)\r
-DECLARE SUB square (x1%, y1%, x2%, y2%, c%, h%)\r
 \r
 DIM SHARED landh(0 TO 180, 0 TO 180)\r
 DIM SHARED landc(0 TO 180, 0 TO 180)\r
@@ -152,13 +157,29 @@ myx = zmyx
 DisplayFrame\r
 GOTO 1\r
 \r
+SUB CreateTower (towerX%, towerY%)\r
+\r
+' Draw a tower at the specified position\r
+FOR a = 10 TO 0 STEP -1\r
+    ' Fill each level of the tower with color\r
+    FillSquareArea towerX% - a, towerY% - a, towerX% + a, towerY% + a, getcol(100, 0, a * 20), 20 - a\r
+NEXT a\r
+\r
+' Draw the top of the tower\r
+FillSquareArea towerX% - 11, towerY% - 11, towerX% - 9, towerY% - 9, getcol(255, 0, 0), 20\r
+FillSquareArea towerX% + 9, towerY% - 11, towerX% + 11, towerY% - 9, getcol(0, 255, 0), 20\r
+FillSquareArea towerX% - 11, towerY% + 9, towerX% - 9, towerY% + 11, getcol(0, 0, 255), 20\r
+FillSquareArea towerX% + 9, towerY% + 9, towerX% + 11, towerY% + 11, getcol(255, 255, 0), 20\r
+\r
+END SUB\r
+\r
 SUB DisplayFrame\r
 \r
 l = 0\r
 zst = -.0031 * ste\r
 FOR z = .5 TO -.5 STEP zst\r
     ' Trace a line from the player's perspective\r
-    traceline SIN(zmyan + z) * dist + myx, COS(zmyan + z) * dist + myy, l\r
+    DrawLineFromPlayer SIN(zmyan + z) * dist + myx, COS(zmyan + z) * dist + myy, l\r
     l = l + ste\r
 NEXT z\r
 \r
@@ -195,36 +216,84 @@ PRINT "Press any key to continue..."
 \r
 END SUB\r
 \r
-DEFSNG A-Y\r
-FUNCTION getcol (r, g, b)\r
-IF r < 0 THEN\r
-    ' Ensure the color value is within bounds\r
-    r = 0\r
-END IF\r
-IF g < 0 THEN\r
-    g = 0\r
-END IF\r
-IF b < 0 THEN\r
-    b = 0\r
+SUB DrawLineFromPlayer (x%, y%, xl%)\r
+\r
+' Trace a line from the player's perspective\r
+IF x < 0 THEN\r
+    ' Calculate the distance to the next point\r
+    zpr = myx / (myx - x)\r
+    x = 0\r
+    y = myy - ((myy - y) * zpr)\r
 END IF\r
-IF r > 255 THEN\r
-    r = 255\r
+\r
+IF y < 0 THEN\r
+    ' Calculate the distance to the next point\r
+    zpr = myy / (myy - y)\r
+    y = 0\r
+    x = myx - ((myx - x) * zpr)\r
 END IF\r
-IF g > 255 THEN\r
-    g = 255\r
+\r
+IF x > 180 THEN\r
+    ' Calculate the distance to the next point\r
+    zpr = (180 - myx) / (x - myx)\r
+    x = 180\r
+    y = myy - ((myy - y) * zpr)\r
 END IF\r
-IF b > 255 THEN\r
-    b = 255\r
+\r
+IF y > 180 THEN\r
+    ' Calculate the distance to the next point\r
+    zpr = (180 - myy) / (y - myy)\r
+    y = 180\r
+    x = myx - ((myx - x) * zpr)\r
 END IF\r
-' Calculate the color index\r
-getcol = INT(r / 43) * 36 + INT(g / 43) * 6 + INT(b / 43)\r
-END FUNCTION\r
 \r
-DEFINT A-Y\r
+' Calculate the distance to the next point\r
+lp% = SQR(ABS(myx - x) ^ 2 + ABS(myy - y) ^ 2)\r
+\r
+' Save the current player position and orientation\r
+imyx% = myx\r
+imyy% = myy\r
+imyz% = myz\r
+xp% = x - imyx%\r
+yp% = y - imyy%\r
+istem% = stem\r
+imyan2% = myan2\r
+\r
+' Draw the line\r
+yo% = 200\r
+FOR a% = 1 TO lp%\r
+    cx% = xp% * a% / lp% + imyx%\r
+    cy% = yp% * a% / lp% + imyy%\r
+    yn% = imyan2% - ((landh(cx%, cy%) - imyz%) / a%) * 300\r
+\r
+    ' Draw the line segment\r
+    IF yn% < yo% THEN\r
+        LINE (xl%, yn%)-(xl% + istem%, yo% - 1), landc(cx%, cy%), BF\r
+        yo% = yn%\r
+    END IF\r
+NEXT a%\r
+\r
+' Draw the final line segment\r
+LINE (xl%, yo% - 1)-(xl% + istem%, 0), 0, BF\r
+\r
+END SUB\r
+\r
+SUB FillSquareArea (x1%, y1%, x2%, y2%, c%, h%)\r
+\r
+' Fill a square area with the specified color and height\r
+FOR y = y1 TO y2\r
+    FOR x = x1 TO x2\r
+        landh(x, y) = h\r
+        landc(x, y) = c\r
+    NEXT x\r
+NEXT y\r
+\r
+END SUB\r
+\r
 SUB GenerateLandscape\r
 \r
 ' Create a square landscape\r
-square 0, 0, 180, 180, 15, 0\r
+FillSquareArea 0, 0, 180, 180, 15, 0\r
 \r
 ' Generate the landscape height and color\r
 FOR y = 0 TO 180\r
@@ -259,9 +328,9 @@ FOR y = 10 TO 90
 NEXT y\r
 \r
 ' Add towers to the landscape\r
-tower 20, 20\r
-tower 60, 20\r
-tower 40, 60\r
+CreateTower 20, 20\r
+CreateTower 60, 20\r
+CreateTower 40, 60\r
 \r
 ' Generate a path\r
 FOR y = 100 TO 150\r
@@ -287,34 +356,32 @@ NEXT za
 \r
 END SUB\r
 \r
-SUB SetupPalette\r
-' Initialize the color palette\r
-c = 0\r
-FOR r = 0 TO 5\r
-    FOR g = 0 TO 5\r
-        FOR b = 0 TO 5\r
-            OUT &H3C8, c\r
-            c = c + 1\r
-            OUT &H3C9, r * 12\r
-            OUT &H3C9, g * 12\r
-            OUT &H3C9, b * 12\r
-        NEXT b\r
-    NEXT g\r
-NEXT r\r
-END SUB\r
-\r
-SUB square (x1%, y1%, x2%, y2%, c%, h%)\r
-\r
-' Fill a square area with the specified color and height\r
-FOR y = y1 TO y2\r
-    FOR x = x1 TO x2\r
-        landh(x, y) = h\r
-        landc(x, y) = c\r
-    NEXT x\r
-NEXT y\r
-\r
-END SUB\r
+DEFSNG A-Y\r
+FUNCTION getcol (r, g, b)\r
+IF r < 0 THEN\r
+    ' Ensure the color value is within bounds\r
+    r = 0\r
+END IF\r
+IF g < 0 THEN\r
+    g = 0\r
+END IF\r
+IF b < 0 THEN\r
+    b = 0\r
+END IF\r
+IF r > 255 THEN\r
+    r = 255\r
+END IF\r
+IF g > 255 THEN\r
+    g = 255\r
+END IF\r
+IF b > 255 THEN\r
+    b = 255\r
+END IF\r
+' Calculate the color index\r
+getcol = INT(r / 43) * 36 + INT(g / 43) * 6 + INT(b / 43)\r
+END FUNCTION\r
 \r
+DEFINT A-Y\r
 SUB InitializeProgram\r
 ' Set the graphics mode\r
 SCREEN 13\r
@@ -336,80 +403,19 @@ zmyz = 20
 \r
 END SUB\r
 \r
-SUB tower (x%, y%)\r
-\r
-' Draw a tower at the specified position\r
-FOR a = 10 TO 0 STEP -1\r
-    ' Fill each level of the tower with color\r
-    square x% - a, y% - a, x% + a, y% + a, getcol(100, 0, a * 20), 20 - a\r
-NEXT a\r
-\r
-' Draw the top of the tower\r
-square x% - 11, y% - 11, x% - 9, y% - 9, getcol(255, 0, 0), 20\r
-square x% + 9, y% - 11, x% + 11, y% - 9, getcol(0, 255, 0), 20\r
-square x% - 11, y% + 9, x% - 9, y% + 11, getcol(0, 0, 255), 20\r
-square x% + 9, y% + 9, x% + 11, y% + 11, getcol(255, 255, 0), 20\r
-\r
+SUB SetupPalette\r
+' Initialize the color palette\r
+c = 0\r
+FOR r = 0 TO 5\r
+    FOR g = 0 TO 5\r
+        FOR b = 0 TO 5\r
+            OUT &H3C8, c\r
+            c = c + 1\r
+            OUT &H3C9, r * 12\r
+            OUT &H3C9, g * 12\r
+            OUT &H3C9, b * 12\r
+        NEXT b\r
+    NEXT g\r
+NEXT r\r
 END SUB\r
 \r
-SUB traceline (x%, y%, xl%)\r
-\r
-' Trace a line from the player's perspective\r
-IF x < 0 THEN\r
-    ' Calculate the distance to the next point\r
-    zpr = myx / (myx - x)\r
-    x = 0\r
-    y = myy - ((myy - y) * zpr)\r
-END IF\r
-\r
-IF y < 0 THEN\r
-    ' Calculate the distance to the next point\r
-    zpr = myy / (myy - y)\r
-    y = 0\r
-    x = myx - ((myx - x) * zpr)\r
-END IF\r
-\r
-IF x > 180 THEN\r
-    ' Calculate the distance to the next point\r
-    zpr = (180 - myx) / (x - myx)\r
-    x = 180\r
-    y = myy - ((myy - y) * zpr)\r
-END IF\r
-\r
-IF y > 180 THEN\r
-    ' Calculate the distance to the next point\r
-    zpr = (180 - myy) / (y - myy)\r
-    y = 180\r
-    x = myx - ((myx - x) * zpr)\r
-END IF\r
-\r
-' Calculate the distance to the next point\r
-lp% = SQR(ABS(myx - x) ^ 2 + ABS(myy - y) ^ 2)\r
-\r
-' Save the current player position and orientation\r
-imyx% = myx\r
-imyy% = myy\r
-imyz% = myz\r
-xp% = x - imyx%\r
-yp% = y - imyy%\r
-istem% = stem\r
-imyan2% = myan2\r
-\r
-' Draw the line\r
-yo% = 200\r
-FOR a% = 1 TO lp%\r
-    cx% = xp% * a% / lp% + imyx%\r
-    cy% = yp% * a% / lp% + imyy%\r
-    yn% = imyan2% - ((landh(cx%, cy%) - imyz%) / a%) * 300\r
-\r
-    ' Draw the line segment\r
-    IF yn% < yo% THEN\r
-        LINE (xl%, yn%)-(xl% + istem%, yo% - 1), landc(cx%, cy%), BF\r
-        yo% = yn%\r
-    END IF\r
-NEXT a%\r
-\r
-' Draw the final line segment\r
-LINE (xl%, yo% - 1)-(xl% + istem%, 0), 0, BF\r
-\r
-END SUB\r