From 3df652419a96291dd774115a4eda7a484486db0a Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Sat, 30 Aug 2025 01:55:52 +0300 Subject: [PATCH] Better code readability --- Games/Pomppu Paavo/Pomppu Paavo.bas | 481 ++++++++++++++++------------ 1 file changed, 271 insertions(+), 210 deletions(-) diff --git a/Games/Pomppu Paavo/Pomppu Paavo.bas b/Games/Pomppu Paavo/Pomppu Paavo.bas index 75dc932..d83ae6d 100755 --- a/Games/Pomppu Paavo/Pomppu Paavo.bas +++ b/Games/Pomppu Paavo/Pomppu Paavo.bas @@ -1,4 +1,3 @@ -DECLARE SUB RenderSpriteFromFile (x%, y%, spriteID%, animationFrame%) ' Pomppu Paavo ' ' This program is free software: released under Creative Commons Zero (CC0) license @@ -10,15 +9,16 @@ DECLARE SUB RenderSpriteFromFile (x%, y%, spriteID%, animationFrame%) ' 1998, Initial version ' 2025, Improved program readability - DECLARE SUB DisplayGameStatistics () -DECLARE SUB LoadCurrentLevel (y%) +DECLARE SUB LoadCurrentLevel (levelNumber%) +DECLARE SUB RenderSpriteFromFile (xPosition%, yPosition%, spriteID%, animationFrame%) DEFINT A-Z DIM SHARED AsciiLevelData(1 TO 20) AS STRING * 31 -DIM SHARED Companion1PositionY%, Companion1PositionX%, Companion2PositionY%, Companion2PositionX% +DIM SHARED Companion1VerticalPosition%, Companion1HorizontalPosition%, Companion2VerticalPosition%, Companion2HorizontalPosition% DIM SHARED LivesRemaining%, CoinsCollected% -DIM SHARED DoorEntryX%, DoorEntryY%, DoorExitX%, DoorExitY% +DIM SHARED DoorEntryX%, DoorEntryY% +DIM SHARED DoorExitX%, DoorExitY% DIM SHARED CurrentLevelNumber% DIM SHARED TerrainGrid(0 TO 34, -10 TO 20) AS STRING * 1 DIM SHARED ObjectGrid(0 TO 34, -10 TO 20) AS STRING * 1 @@ -49,7 +49,7 @@ DIM SHARED PlayerRunLeft2Sprite(51) DIM SHARED PlayerJumpingSprite(51) DIM SHARED DoorSprite(120) -' Define keyboard control sequences +' Define keyboard control sequences using special arrow key codes leftArrowKey$ = CHR$(0) + "K" rightArrowKey$ = CHR$(0) + "M" upArrowKey$ = CHR$(0) + "H" @@ -61,7 +61,7 @@ SCREEN 1 CurrentLevelNumber% = 1 ' Capture sprite images from screen drawing operations. -' This technique uses GET command to save drawn graphics into arrays. +' This technique uses GET command to save drawn graphics directly into arrays. GET (1, 1)-(20, 20), EmptySpaceSprite RenderSpriteFromFile 0, 0, 1, 1 GET (1, 1)-(20, 20), SolidTerrainSprite @@ -119,308 +119,369 @@ RenderSpriteFromFile 0, 0, 16, 1 GET (1, 1)-(20, 20), PlayerJumpingSprite CLS - +' Capture the introductory screen image RenderSpriteFromFile -1, -1, 10, 4 GET (1, 1)-(318, 124), IntroScreen key$ = INPUT$(1) LoadCurrentLevel 1 -a = 50 -b = 50 -Companion1HorizontalVelocity = 1 -Companion2HorizontalVelocity = 1 +PlayerXPosition% = 50 +PlayerYPosition% = 50 +Companion1HorizontalSpeed% = 1 +Companion2HorizontalSpeed% = 1 MainGameLoop: keyboardInput$ = INKEY$ -IF b > 0 THEN GET (a, b)-(a + 20, b + 20), PlayerSpriteBuffer -IF b > 0 THEN IF PlayerAnimationState = 1 THEN PUT (a, b), PlayerRunRight1Sprite, OR -IF b > 0 THEN IF PlayerAnimationState = 2 THEN PUT (a, b), PlayerRunRight2Sprite, OR -IF b > 0 THEN IF PlayerAnimationState = 10 THEN PUT (a, b), PlayerRunLeft1Sprite, OR -IF b > 0 THEN IF PlayerAnimationState = 20 THEN PUT (a, b), PlayerRunLeft2Sprite, OR -IF b > 0 THEN IF PlayerAnimationState = 3 THEN PUT (a, b), PlayerJumpingSprite, OR - -' Draw first companion hedgehog -GET (Companion1PositionX%, Companion1PositionY%)-(Companion1PositionX% + 10, Companion1PositionY% + 10), HedgehogSprite1 -PUT (Companion1PositionX%, Companion1PositionY%), HedgehogSprite, OR - -' Draw second companion hedgehog -GET (Companion2PositionX%, Companion2PositionY%)-(Companion2PositionX% + 10, Companion2PositionY% + 10), HedgehogSprite2 -PUT (Companion2PositionX%, Companion2PositionY%), HedgehogSprite, OR - -' Create short delay using sound command (workaround since QBasic lacks built-in sub-second delay) -' SOUND 0,0.8 creates an inaudible tone that takes approximately 8 milliseconds to process +IF PlayerYPosition% > 0 THEN GET (PlayerXPosition%, PlayerYPosition%)-(PlayerXPosition% + 20, PlayerYPosition% + 20), PlayerSpriteBuffer + +' Display appropriate player animation frame based on current movement state +IF PlayerYPosition% > 0 THEN IF PlayerAnimationState% = 1 THEN PUT (PlayerXPosition%, PlayerYPosition%), PlayerRunRight1Sprite, OR +IF PlayerYPosition% > 0 THEN IF PlayerAnimationState% = 2 THEN PUT (PlayerXPosition%, PlayerYPosition%), PlayerRunRight2Sprite, OR +IF PlayerYPosition% > 0 THEN IF PlayerAnimationState% = 10 THEN PUT (PlayerXPosition%, PlayerYPosition%), PlayerRunLeft1Sprite, OR +IF PlayerYPosition% > 0 THEN IF PlayerAnimationState% = 20 THEN PUT (PlayerXPosition%, PlayerYPosition%), PlayerRunLeft2Sprite, OR +IF PlayerYPosition% > 0 THEN IF PlayerAnimationState% = 3 THEN PUT (PlayerXPosition%, PlayerYPosition%), PlayerJumpingSprite, OR + +' Draw first companion hedgehog to screen +GET (Companion1HorizontalPosition%, Companion1VerticalPosition%)-(Companion1HorizontalPosition% + 10, Companion1VerticalPosition% + 10), HedgehogSprite1 +PUT (Companion1HorizontalPosition%, Companion1VerticalPosition%), HedgehogSprite, OR + +' Draw second companion hedgehog to screen +GET (Companion2HorizontalPosition%, Companion2VerticalPosition%)-(Companion2HorizontalPosition% + 10, Companion2VerticalPosition% + 10), HedgehogSprite2 +PUT (Companion2HorizontalPosition%, Companion2VerticalPosition%), HedgehogSprite, OR + +' Create short delay using sound command (workaround for lack of built-in sub-second delay in QBasic) +' SOUND 0,0.8 produces an inaudible tone that takes approximately 0.8 milliseconds to process SOUND 0, .8 -' Check collisions with terrain (z = solid block) -' Right side collision checks -IF TerrainGrid((a + 38) \ 20, (b + 37) \ 20) = "z" THEN eda = -1: r = 9: lke = 0 -IF TerrainGrid((a + 38) \ 20, (b + 22) \ 20) = "z" THEN eda = -1: r = 9: lke = 0 +' Check collisions with solid terrain ("z" character in grid) +' Right side collision detection - checks two points along right edge of player hitbox +' Player hitbox is slightly smaller than sprite (38 instead of full 40 width) +IF TerrainGrid((PlayerXPosition% + 38) \ 20, (PlayerYPosition% + 37) \ 20) = "z" THEN PlayerHorizontalSpeed% = -1: HorizontalAnimationCounter% = 9: GroundContactTime% = 0 +IF TerrainGrid((PlayerXPosition% + 38) \ 20, (PlayerYPosition% + 22) \ 20) = "z" THEN PlayerHorizontalSpeed% = -1: HorizontalAnimationCounter% = 9: GroundContactTime% = 0 -' Left side collision checks -IF TerrainGrid((a + 21) \ 20, (b + 22) \ 20) = "z" THEN eda = 1: r = 9: lke = 0 -IF TerrainGrid((a + 21) \ 20, (b + 37) \ 20) = "z" THEN eda = 1: r = 9: lke = 0 +' Left side collision detection - checks two points along left edge of player hitbox +IF TerrainGrid((PlayerXPosition% + 21) \ 20, (PlayerYPosition% + 22) \ 20) = "z" THEN PlayerHorizontalSpeed% = 1: HorizontalAnimationCounter% = 9: GroundContactTime% = 0 +IF TerrainGrid((PlayerXPosition% + 21) \ 20, (PlayerYPosition% + 37) \ 20) = "z" THEN PlayerHorizontalSpeed% = 1: HorizontalAnimationCounter% = 9: GroundContactTime% = 0 -' Top collision checks -IF TerrainGrid((a + 22) \ 20, (b + 21) \ 20) = "z" THEN all = 0: cd = 10: lke = 0 -IF TerrainGrid((a + 37) \ 20, (b + 21) \ 20) = "z" THEN all = 0: cd = 10: lke = 0 +' Top collision detection - checks if player hits ceiling ("z" block above) +IF TerrainGrid((PlayerXPosition% + 22) \ 20, (PlayerYPosition% + 21) \ 20) = "z" THEN PlayerVerticalSpeed% = 0: CeilingContactCooldown% = 10: GroundContactTime% = 0 +IF TerrainGrid((PlayerXPosition% + 37) \ 20, (PlayerYPosition% + 21) \ 20) = "z" THEN PlayerVerticalSpeed% = 0: CeilingContactCooldown% = 10: GroundContactTime% = 0 -' Bottom collision checks (landing on ground) -IF TerrainGrid((a + 22) \ 20, (b + 38) \ 20) = "z" THEN all = -1: r1 = 2: lk = 1: lke = 0 -IF TerrainGrid((a + 37) \ 20, (b + 38) \ 20) = "z" THEN all = -1: r1 = 2: lk = 1: lke = 0 +' Bottom collision detection (landing on ground) - checks if player lands on solid terrain +' When landing, sets vertical speed to negative (bounce effect), resets jump ability +IF TerrainGrid((PlayerXPosition% + 22) \ 20, (PlayerYPosition% + 38) \ 20) = "z" THEN PlayerVerticalSpeed% = -1: VerticalAnimationCounter% = 2: JumpReadinessCounter% = 1: GroundContactTime% = 0 +IF TerrainGrid((PlayerXPosition% + 37) \ 20, (PlayerYPosition% + 38) \ 20) = "z" THEN PlayerVerticalSpeed% = -1: VerticalAnimationCounter% = 2: JumpReadinessCounter% = 1: GroundContactTime% = 0 -qwer = qwer + 1: IF ObjectGrid((a + 30) \ 20, (b + 30) \ 20) = "q" AND qwer > 20 THEN qwer = 1: LivesRemaining% = LivesRemaining% - 1: eda = -10: all = -5: DisplayGameStatistics -IF qwer > 100 THEN qwer = 50 +HazardExposureTimer% = HazardExposureTimer% + 1: IF ObjectGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 30) \ 20) = "q" AND HazardExposureTimer% > 20 THEN HazardExposureTimer% = 1: LivesRemaining% = LivesRemaining% - 1: _ +PlayerHorizontalSpeed% = -10: PlayerVerticalSpeed% = -5: DisplayGameStatistics +IF HazardExposureTimer% > 100 THEN HazardExposureTimer% = 50 -' Coin collection logic -IF TerrainGrid((a + 30) \ 20, (b + 30) \ 20) = "1" THEN CoinsCollected% = CoinsCollected% + 1: kustuta1 = 1: DisplayGameStatistics: TerrainGrid((a + 30) \ 20, (b + 30) \ 20) = "" +' Coin collection logic - when player overlaps coin position ("1" in grid) +IF TerrainGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 30) \ 20) = "1" THEN CoinsCollected% = CoinsCollected% + 1: CoinCollectedFlag% = 1: DisplayGameStatistics: TerrainGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 30) \ 20) = "" -' Trampoline effect -IF TerrainGrid((a + 30) \ 20, (b + 30) \ 20) = "v" THEN all = -8: PlayerAnimationState = 3 +' Trampoline effect - when player lands on trampoline ("v" marker), gives strong upward boost +IF TerrainGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 30) \ 20) = "v" THEN PlayerVerticalSpeed% = -8: PlayerAnimationState% = 3 -' Conveyor belt movement triggers -IF ObjectGrid((a + 30) \ 20, (b + 38) \ 20) = ">" THEN eda = 3 -IF ObjectGrid((a + 30) \ 20, (b + 38) \ 20) = "<" THEN eda = -3 +' Conveyor belt movement - ">" and "<" markers in object grid push player horizontally +IF ObjectGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 38) \ 20) = ">" THEN PlayerHorizontalSpeed% = 3 +IF ObjectGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 38) \ 20) = "<" THEN PlayerHorizontalSpeed% = -3 -lke = lke + 1 -r = r + 1: IF r > 10 THEN r = 0: IF eda > 0 THEN eda = eda - 1: ELSE IF eda < 0 THEN eda = eda + 1 -r1 = r1 + 1: IF r1 > 3 THEN r1 = 0: all = all + 1 -lk = lk + 1: cd = cd - 1 +GroundContactTime% = GroundContactTime% + 1 +HorizontalAnimationCounter% = HorizontalAnimationCounter% + 1: IF HorizontalAnimationCounter% > 10 THEN HorizontalAnimationCounter% = 0: IF PlayerHorizontalSpeed% > 0 THEN PlayerHorizontalSpeed% = PlayerHorizontalSpeed% - 1 ELSE IF _ +PlayerHorizontalSpeed% < 0 THEN PlayerHorizontalSpeed% = PlayerHorizontalSpeed% + 1 +VerticalAnimationCounter% = VerticalAnimationCounter% + 1: IF VerticalAnimationCounter% > 3 THEN VerticalAnimationCounter% = 0: PlayerVerticalSpeed% = PlayerVerticalSpeed% + 1 +JumpReadinessCounter% = JumpReadinessCounter% + 1: CeilingContactCooldown% = CeilingContactCooldown% - 1 -' Breakable block interaction -IF ObjectGrid((a + 30) \ 20, (b + 21) \ 20) = "o" THEN TerrainGrid((a + 30) \ 20, (b + 21) \ 20) = "": ObjectGrid((a + 30) \ 20, (b + 21) \ 20) = "": kustuta = 1 +' Breakable block interaction - hitting block from below ("o" character) destroys it +IF ObjectGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 21) \ 20) = "o" THEN TerrainGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 21) \ 20) = "": ObjectGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 21) \ 20) = "": _ +TopBreakableBlockClearedFlag% = 1 -IF ObjectGrid((a + 30) \ 20, (b + 38) \ 20) = "a" THEN TerrainGrid((a + 30) \ 20, (b + 38) \ 20) = "": ObjectGrid((a + 30) \ 20, (b + 38) \ 20) = "": kustuta2 = 1 +' Bottom breakable block interaction - similar to above but from top side +IF ObjectGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 38) \ 20) = "a" THEN TerrainGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 38) \ 20) = "": ObjectGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 38) \ 20) = "": _ +BottomBreakableBlockClearedFlag% = 1 ' First companion hedgehog terrain collision checks -IF TerrainGrid((Companion1PositionX% + 25) \ 20, (Companion1PositionY% + 25) \ 20) = "z" THEN Companion1VerticalVelocity = -1 -IF TerrainGrid((Companion1PositionX% + 30) \ 20, (Companion1PositionY% + 10) \ 20) = "z" THEN Companion1HorizontalVelocity = -1 -IF TerrainGrid((Companion1PositionX% + 20) \ 20, (Companion1PositionY% + 10) \ 20) = "z" THEN Companion1HorizontalVelocity = 1 +IF TerrainGrid((Companion1HorizontalPosition% + 25) \ 20, (Companion1VerticalPosition% + 25) \ 20) = "z" THEN Companion1VerticalSpeed% = -1 +IF TerrainGrid((Companion1HorizontalPosition% + 30) \ 20, (Companion1VerticalPosition% + 10) \ 20) = "z" THEN Companion1HorizontalSpeed% = -1 +IF TerrainGrid((Companion1HorizontalPosition% + 20) \ 20, (Companion1VerticalPosition% + 10) \ 20) = "z" THEN Companion1HorizontalSpeed% = 1 ' Second companion hedgehog terrain collision checks -IF TerrainGrid((Companion2PositionX% + 25) \ 20, (Companion2PositionY% + 25) \ 20) = "z" THEN Companion2VerticalVelocity = -1 -IF TerrainGrid((Companion2PositionX% + 30) \ 20, (Companion2PositionY% + 10) \ 20) = "z" THEN Companion2HorizontalVelocity = -1 -IF TerrainGrid((Companion2PositionX% + 20) \ 20, (Companion2PositionY% + 10) \ 20) = "z" THEN Companion2HorizontalVelocity = 1 - -' Screen boundary checks for companions -IF Companion1PositionX% > 300 THEN Companion1HorizontalVelocity = -1 -IF Companion1PositionX% < 3 THEN Companion1HorizontalVelocity = 1 -IF Companion2PositionX% > 300 THEN Companion2HorizontalVelocity = -1 -IF Companion2PositionX% < 3 THEN Companion2HorizontalVelocity = 1 - -' Restore previous companion positions -PUT (Companion2PositionX%, Companion2PositionY%), HedgehogSprite2, PSET -PUT (Companion1PositionX%, Companion1PositionY%), HedgehogSprite1, PSET - -' Restore previous player position -IF b > 0 THEN PUT (a, b), PlayerSpriteBuffer, PSET - -' Handle sprite clearing after coin collection -IF kustuta = 1 THEN kustuta = 0: PUT (((a + 10) \ 20) * 20, (b \ 20) * 20), EmptySpaceSprite, PSET -IF kustuta1 = 1 THEN kustuta1 = 0: PUT (((a + 10) \ 20) * 20, ((b + 10) \ 20) * 20), EmptySpaceSprite, PSET -IF kustuta2 = 1 THEN kustuta2 = 0: PUT (((a + 10) \ 20) * 20, ((b + 28) \ 20) * 20), EmptySpaceSprite, PSET - -IF makk = 1 THEN makk = 0: a = a - 20 - -ObjectGrid((Companion2PositionX% + 25) \ 20, (Companion2PositionY% + 8) \ 20) = "": ObjectGrid((Companion1PositionX% + 25) \ 20, (Companion1PositionY% + 8) \ 20) = "" - -' Update companion positions -Companion1PositionX% = Companion1PositionX% + Companion1HorizontalVelocity -Companion1PositionY% = Companion1PositionY% + Companion1VerticalVelocity -Companion2PositionX% = Companion2PositionX% + Companion2HorizontalVelocity -Companion2PositionY% = Companion2PositionY% + Companion2VerticalVelocity - -' Mark new companion positions in object grid -ObjectGrid((Companion2PositionX% + 25) \ 20, (Companion2PositionY% + 8) \ 20) = "q": ObjectGrid((Companion1PositionX% + 25) \ 20, (Companion1PositionY% + 8) \ 20) = "q" - -' Apply gravity to companions (max downward velocity = 2) -Companion1VerticalVelocity = Companion1VerticalVelocity + 1: IF Companion1VerticalVelocity > 2 THEN Companion1VerticalVelocity = 2 -Companion2VerticalVelocity = Companion2VerticalVelocity + 1: IF Companion2VerticalVelocity > 2 THEN Companion2VerticalVelocity = 2 - -' Update player position based on velocity -a = a + eda -b = b + all - -' Level transition when reaching right edge -IF a > 297 THEN a = 2: CurrentLevelNumber% = CurrentLevelNumber% + 1: LoadCurrentLevel CurrentLevelNumber%: Companion1HorizontalVelocity = 1: Companion2HorizontalVelocity = 1 - -' Level transition when reaching left edge -IF a < 1 THEN a = 296: IF CurrentLevelNumber% = 1 THEN LoadCurrentLevel CurrentLevelNumber%: ELSE CurrentLevelNumber% = CurrentLevelNumber% - 1: LoadCurrentLevel CurrentLevelNumber%: IF GraphicsDisplayMode = 2 THEN a = 594 - -' Player falls off bottom of screen -IF b > 179 THEN LivesRemaining% = LivesRemaining% - 1: DisplayGameStatistics: CurrentLevelNumber% = CurrentLevelNumber% - 1: LoadCurrentLevel CurrentLevelNumber%: b = 100: a = 2: DisplayGameStatistics - -' Teleportation triggers ("u" and "U" markers in level data) -IF TerrainGrid((a + 30) \ 20, (b + 30) \ 20) = "u" THEN a = DoorExitX% + 10: b = DoorExitY%: eda = 0 -IF TerrainGrid((a + 30) \ 20, (b + 30) \ 20) = "U" THEN a = DoorEntryX% + 10: b = DoorEntryY%: eda = 0 - -IF keyboardInput$ = rightArrowKey$ THEN eda = eda + 1: IF eda > 3 THEN eda = 3: r = 0 ELSE IF lke > 10 THEN eda = 5 -IF keyboardInput$ = rightArrowKey$ THEN IF sipa = 1 THEN PlayerAnimationState = 1: ELSE PlayerAnimationState = 2 -IF keyboardInput$ = leftArrowKey$ THEN IF sipa = 1 THEN PlayerAnimationState = 10: ELSE PlayerAnimationState = 20 -IF keyboardInput$ = leftArrowKey$ THEN eda = eda - 1: IF eda < -3 THEN eda = -3: r = 0 ELSE IF lke > 10 THEN eda = -5 -IF keyboardInput$ = upArrowKey$ AND lk < 10 THEN all = all - 5: lk = 20: r1 = 0: PlayerAnimationState = 3 -IF keyboardInput$ = downArrowKey$ THEN all = all + 1 -IF keyboardInput$ = "/" THEN a = 2: b = 50: CurrentLevelNumber% = CurrentLevelNumber% + 1: LoadCurrentLevel CurrentLevelNumber%: Companion1HorizontalVelocity = 1: Companion2HorizontalVelocity = 1 -IF keyboardInput$ = "+" THEN a = 2: b = 50: CurrentLevelNumber% = CurrentLevelNumber% + 5: LoadCurrentLevel CurrentLevelNumber%: Companion1HorizontalVelocity = 1: Companion2HorizontalVelocity = 1 +IF TerrainGrid((Companion2HorizontalPosition% + 25) \ 20, (Companion2VerticalPosition% + 25) \ 20) = "z" THEN Companion2VerticalSpeed% = -1 +IF TerrainGrid((Companion2HorizontalPosition% + 30) \ 20, (Companion2VerticalPosition% + 10) \ 20) = "z" THEN Companion2HorizontalSpeed% = -1 +IF TerrainGrid((Companion2HorizontalPosition% + 20) \ 20, (Companion2VerticalPosition% + 10) \ 20) = "z" THEN Companion2HorizontalSpeed% = 1 + +' Screen boundary checks for companions to keep them within play area +IF Companion1HorizontalPosition% > 300 THEN Companion1HorizontalSpeed% = -1 +IF Companion1HorizontalPosition% < 3 THEN Companion1HorizontalSpeed% = 1 +IF Companion2HorizontalPosition% > 300 THEN Companion2HorizontalSpeed% = -1 +IF Companion2HorizontalPosition% < 3 THEN Companion2HorizontalSpeed% = 1 + +' Restore previous companion positions before moving them (erases old sprite) +PUT (Companion2HorizontalPosition%, Companion2VerticalPosition%), HedgehogSprite2, PSET +PUT (Companion1HorizontalPosition%, Companion1VerticalPosition%), HedgehogSprite1, PSET + +' Restore previous player position (erases old sprite) +IF PlayerYPosition% > 0 THEN PUT (PlayerXPosition%, PlayerYPosition%), PlayerSpriteBuffer, PSET + +' Handle sprite clearing after collecting coins or breaking blocks +IF TopBreakableBlockClearedFlag% = 1 THEN TopBreakableBlockClearedFlag% = 0: PUT (((PlayerXPosition% + 10) \ 20) * 20, (PlayerYPosition% \ 20) * 20), EmptySpaceSprite, PSET +IF CoinCollectedFlag% = 1 THEN CoinCollectedFlag% = 0: PUT (((PlayerXPosition% + 10) \ 20) * 20, ((PlayerYPosition% + 10) \ 20) * 20), EmptySpaceSprite, PSET +IF BottomBreakableBlockClearedFlag% = 1 THEN BottomBreakableBlockClearedFlag% = 0: PUT (((PlayerXPosition% + 10) \ 20) * 20, ((PlayerYPosition% + 28) \ 20) * 20), EmptySpaceSprite, PSET + +IF PullBackwardFlag% = 1 THEN PullBackwardFlag% = 0: PlayerXPosition% = PlayerXPosition% - 20 + +ObjectGrid((Companion2HorizontalPosition% + 25) \ 20, (Companion2VerticalPosition% + 8) \ 20) = "": ObjectGrid((Companion1HorizontalPosition% + 25) \ 20, (Companion1VerticalPosition% + 8) \ 20) = "" + +' Update companion positions based on their current velocities +Companion1HorizontalPosition% = Companion1HorizontalPosition% + Companion1HorizontalSpeed% +Companion1VerticalPosition% = Companion1VerticalPosition% + Companion1VerticalSpeed% +Companion2HorizontalPosition% = Companion2HorizontalPosition% + Companion2HorizontalSpeed% +Companion2VerticalPosition% = Companion2VerticalPosition% + Companion2VerticalSpeed% + +' Mark new companion positions in object grid for collision detection +ObjectGrid((Companion2HorizontalPosition% + 25) \ 20, (Companion2VerticalPosition% + 8) \ 20) = "q": ObjectGrid((Companion1HorizontalPosition% + 25) \ 20, (Companion1VerticalPosition% + 8) \ 20) = "q" + +' Apply gravity to companions (max downward speed capped at 2 pixels/frame) +Companion1VerticalSpeed% = Companion1VerticalSpeed% + 1: IF Companion1VerticalSpeed% > 2 THEN Companion1VerticalSpeed% = 2 +Companion2VerticalSpeed% = Companion2VerticalSpeed% + 1: IF Companion2VerticalSpeed% > 2 THEN Companion2VerticalSpeed% = 2 + +' Update player position based on calculated velocities +PlayerXPosition% = PlayerXPosition% + PlayerHorizontalSpeed% +PlayerYPosition% = PlayerYPosition% + PlayerVerticalSpeed% + +' Level transition when reaching right edge of screen +IF PlayerXPosition% > 297 THEN PlayerXPosition% = 2: CurrentLevelNumber% = CurrentLevelNumber% + 1: LoadCurrentLevel CurrentLevelNumber%: Companion1HorizontalSpeed% = 1: Companion2HorizontalSpeed% = 1 + +' Level transition when reaching left edge of screen +IF PlayerXPosition% < 1 THEN PlayerXPosition% = 296: IF CurrentLevelNumber% = 1 THEN LoadCurrentLevel CurrentLevelNumber% ELSE CurrentLevelNumber% = CurrentLevelNumber% - 1: LoadCurrentLevel CurrentLevelNumber%: IF GraphicsDisplayMode% = 2 THEN _ +PlayerXPosition% = 594 + +' Player falls off bottom of screen - lose life and restart level +IF PlayerYPosition% > 179 THEN LivesRemaining% = LivesRemaining% - 1: DisplayGameStatistics: CurrentLevelNumber% = CurrentLevelNumber% - 1: LoadCurrentLevel CurrentLevelNumber%: PlayerYPosition% = 100: PlayerXPosition% = 2: DisplayGameStatistics + +' Teleportation triggers ("u" and "U" markers in level data create door pairs) +IF TerrainGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 30) \ 20) = "u" THEN PlayerXPosition% = DoorExitX% + 10: PlayerYPosition% = DoorExitY%: PlayerHorizontalSpeed% = 0 +IF TerrainGrid((PlayerXPosition% + 30) \ 20, (PlayerYPosition% + 30) \ 20) = "U" THEN PlayerXPosition% = DoorEntryX% + 10: PlayerYPosition% = DoorEntryY%: PlayerHorizontalSpeed% = 0 + +' Handle keyboard input for movement controls +IF keyboardInput$ = rightArrowKey$ THEN PlayerHorizontalSpeed% = PlayerHorizontalSpeed% + 1: IF PlayerHorizontalSpeed% > 3 THEN PlayerHorizontalSpeed% = 3: HorizontalAnimationCounter% = 0 ELSE IF GroundContactTime% > 10 THEN PlayerHorizontalSpeed% = 5 +IF keyboardInput$ = rightArrowKey$ THEN IF RunningAnimationStage% = 1 THEN PlayerAnimationState% = 1 ELSE PlayerAnimationState% = 2 +IF keyboardInput$ = leftArrowKey$ THEN IF RunningAnimationStage% = 1 THEN PlayerAnimationState% = 10 ELSE PlayerAnimationState% = 20 +IF keyboardInput$ = leftArrowKey$ THEN PlayerHorizontalSpeed% = PlayerHorizontalSpeed% - 1: IF PlayerHorizontalSpeed% < -3 THEN PlayerHorizontalSpeed% = -3: HorizontalAnimationCounter% = 0 ELSE IF GroundContactTime% > 10 THEN PlayerHorizontalSpeed% = -5 +IF keyboardInput$ = upArrowKey$ AND JumpReadinessCounter% < 10 THEN PlayerVerticalSpeed% = PlayerVerticalSpeed% - 5: JumpReadinessCounter% = 20: VerticalAnimationCounter% = 0: PlayerAnimationState% = 3 +IF keyboardInput$ = downArrowKey$ THEN PlayerVerticalSpeed% = PlayerVerticalSpeed% + 1 +IF keyboardInput$ = "/" THEN PlayerXPosition% = 2: PlayerYPosition% = 50: CurrentLevelNumber% = CurrentLevelNumber% + 1: LoadCurrentLevel CurrentLevelNumber%: Companion1HorizontalSpeed% = 1: Companion2HorizontalSpeed% = 1 +IF keyboardInput$ = "+" THEN PlayerXPosition% = 2: PlayerYPosition% = 50: CurrentLevelNumber% = CurrentLevelNumber% + 5: LoadCurrentLevel CurrentLevelNumber%: Companion1HorizontalSpeed% = 1: Companion2HorizontalSpeed% = 1 IF keyboardInput$ = "q" THEN END -sipa = sipa + 1 -IF sipa = 3 THEN sipa = 1 + +' Cycle through running animation frames (alternates between two states) +RunningAnimationStage% = RunningAnimationStage% + 1 +IF RunningAnimationStage% = 3 THEN RunningAnimationStage% = 1 GOTO MainGameLoop SUB DisplayGameStatistics ' -' Updates and displays the game's status information (coins, lives) -' Handles game over condition when lives reach zero. +' Updates and displays the game's status information on screen (coins collected, remaining lives) +' Handles game over condition when player runs out of lives. ' -' This subroutine is called whenever: -' - A coin is collected -' - The player loses a life (from hazards or falling) -' - Periodically during gameplay for live updates +' This subroutine is triggered whenever: +' - A coin is collected (incrementing coins counter) +' - The player loses a life due to hazards or falling off-screen +' - Periodically during gameplay for live status updates LOCATE 1, 1 IF GraphicsDisplayMode% = 2 THEN GOTO SkipTextDisplay -' Clear previous stats display to prevent text overlap +' Clear previous statistics display to prevent overlapping text PRINT " " -' Award extra life every 10 coins collected -IF CoinsCollected% > 9 THEN CoinsCollected% = 0: LivesRemaining% = LivesRemaining% + 1 +' Award extra life every 10 coins collected as bonus +IF CoinsCollected% > 9 THEN + CoinsCollected% = 0 + LivesRemaining% = LivesRemaining% + 1 +END IF -' Display current game statistics +' Display current game statistics in top-left corner of screen LOCATE 1, 1 -PRINT "o "; CoinsCollected%; " Lives "; LivesRemaining% +PRINT "Coins: "; CoinsCollected%; " Lives: "; LivesRemaining% SkipTextDisplay: -' Check if player has run out of lives +' Check if player has completely run out of lives (game over condition) IF LivesRemaining% < 0 THEN END END SUB -SUB LoadCurrentLevel (y) +SUB LoadCurrentLevel (levelNumber%) 125 -Companion1PositionX% = 0 -Companion1PositionY% = 0 -aiia1 = 0 -Companion2PositionY% = 0 -FOR a1 = 1 TO 32 - FOR b1 = 1 TO 20 - TerrainGrid(a1, b1) = "" - ObjectGrid(a1, b1) = "" - NEXT b1 -NEXT a1 - -FOR a = 1 TO 20 - AsciiLevelData(a) = "" -NEXT a +Companion1HorizontalPosition% = 0 +Companion1VerticalPosition% = 0 +Companion2HorizontalPosition = 0 +Companion2VerticalPosition% = 0 + +' Clear terrain and object grids before loading new level data +FOR xIndex% = 1 TO 32 + FOR yIndex% = 1 TO 20 + TerrainGrid(xIndex%, yIndex%) = "" + ObjectGrid(xIndex%, yIndex%) = "" + NEXT yIndex% +NEXT xIndex% + +' Initialize ASCII level data array +FOR lineCounter% = 1 TO 20 + AsciiLevelData(lineCounter%) = "" +NEXT lineCounter% CLS LOCATE 3, 10 -IF y >= 1 AND y <= 18 THEN - fileName$ = "lvl/" + LTRIM$(STR$(y)) + ".lvl" +' Determine which level file to load based on requested level number +IF levelNumber% >= 1 AND levelNumber% <= 18 THEN + fileName$ = "lvl/" + LTRIM$(STR$(levelNumber%)) + ".lvl" OPEN fileName$ FOR INPUT AS #1 - INPUT #1, increment - i% = 1 + INPUT #1, LevelNumberAdjustment% + rowIndex% = 1 WHILE NOT EOF(1) - LINE INPUT #1, AsciiLevelData(i%) - i% = i% + 1 + LINE INPUT #1, AsciiLevelData(rowIndex%) + rowIndex% = rowIndex% + 1 WEND CLOSE #1 - CurrentLevelNumber% = CurrentLevelNumber% + increment + CurrentLevelNumber% = CurrentLevelNumber% + LevelNumberAdjustment% ELSE - SELECT CASE y + ' Special handling for non-standard levels (intro screen, test levels) + SELECT CASE levelNumber% CASE 19 CLS RenderSpriteFromFile 1, 1, 10, 3 LOCATE 20, 1 PRINT "end" - ' Wait for 50 frames before accepting input - FOR frameCount% = 1 TO 50 + ' Wait for 50 frames before continuing (creates brief pause) + FOR frameCounter% = 1 TO 50 inputBuffer$ = INKEY$ - NEXT frameCount% + NEXT frameCounter% inputBuffer$ = INPUT$(1) CLS SCREEN 2 END CASE 100 + ' Create empty level with 10 blank lines FOR lineIndex% = 1 TO 10 AsciiLevelData(lineIndex%) = " " NEXT lineIndex% CASE 101 + ' Create larger empty level with 19 blank lines FOR lineIndex% = 1 TO 19 AsciiLevelData(lineIndex%) = " " NEXT lineIndex% GraphicsDisplayMode% = 2 END SELECT END IF -FOR e = 0 TO 9 -FOR D = 0 TO 15 -q$ = RIGHT$(LEFT$(AsciiLevelData(e + 1), D + 1), 1) -IF q$ = "m" THEN PUT (D * 20, e * 20), SolidTerrainSprite, PSET: TerrainGrid(D + 1, e + 1) = "z" -IF q$ = "o" THEN PUT (D * 20, e * 20), BoxSprite, PSET: TerrainGrid(D + 1, e + 1) = "z": ObjectGrid(D + 1, e + 1) = "o" -IF q$ = "." THEN PUT (D * 20, e * 20), StarSprite, PSET -IF q$ = "-" THEN PUT (D * 20, e * 20), CloudSprite, PSET -IF q$ = "x" THEN Companion1PositionX% = D * 20: Companion1PositionY% = (e + 1) * 20 -IF q$ = "y" THEN Companion2PositionX% = D * 20: Companion2PositionY% = (e + 1) * 20 -IF q$ = "1" THEN PUT (D * 20, e * 20), CoinSprite, PSET: TerrainGrid(D + 1, e + 1) = "1" -IF q$ = "p" THEN PUT (D * 20, (e * 20) + 10), BushSprite, PSET -IF q$ = "h" THEN PUT (D * 20, e * 20), WindowSprite, PSET -IF q$ = "v" THEN PUT (D * 20, (e * 20) + 10), TrampolineSprite, PSET: TerrainGrid(D + 1, e + 1) = "v" -IF q$ = "t" THEN PUT (D * 20, e * 20), TreeSprite, PSET -IF q$ = ">" THEN PUT (D * 20, e * 20), RightArrowSprite, PSET: TerrainGrid(D + 1, e + 1) = "z": ObjectGrid(D + 1, e + 1) = ">" -IF q$ = "<" THEN PUT (D * 20, e * 20), LeftArrowSprite, PSET: TerrainGrid(D + 1, e + 1) = "z": ObjectGrid(D + 1, e + 1) = "<" -IF q$ = "a" THEN PUT (D * 20, e * 20), HoleSprite, PSET: TerrainGrid(D + 1, e + 1) = "z": ObjectGrid(D + 1, e + 1) = "a" -IF q$ = "u" THEN PUT (D * 20, e * 20), DoorSprite, PSET: TerrainGrid(D + 1, e + 1) = "u": DoorEntryX% = D * 20: DoorEntryY% = e * 20 -IF q$ = "U" THEN PUT (D * 20, e * 20), DoorSprite, PSET: TerrainGrid(D + 1, e + 1) = "U": DoorExitX% = D * 20: DoorExitY% = e * 20 -NEXT D -NEXT e + +' Parse ASCII level representation into game world +FOR rowIndex% = 0 TO 9 + FOR columnIndex% = 0 TO 15 + ' Extract single character from level data at current position + currentCharacter$ = RIGHT$(LEFT$(AsciiLevelData(rowIndex% + 1), columnIndex% + 1), 1) + + ' Interpret character codes to build terrain and place objects: + ' "m" = solid block (terrain) + IF currentCharacter$ = "m" THEN PUT (columnIndex% * 20, rowIndex% * 20), SolidTerrainSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "z" + + ' "o" = breakable box (both terrain and object properties) + IF currentCharacter$ = "o" THEN PUT (columnIndex% * 20, rowIndex% * 20), BoxSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "z": ObjectGrid(columnIndex% + 1, rowIndex% + 1) = "o" + + ' "." = star decoration (cosmetic only) + IF currentCharacter$ = "." THEN PUT (columnIndex% * 20, rowIndex% * 20), StarSprite, PSET + + ' "-" = cloud decoration (cosmetic only) + IF currentCharacter$ = "-" THEN PUT (columnIndex% * 20, rowIndex% * 20), CloudSprite, PSET + + ' "x" = starting position for first companion hedgehog + IF currentCharacter$ = "x" THEN Companion1HorizontalPosition% = columnIndex% * 20: Companion1VerticalPosition% = (rowIndex% + 1) * 20 + + ' "y" = starting position for second companion hedgehog + IF currentCharacter$ = "y" THEN Companion2HorizontalPosition% = columnIndex% * 20: Companion2VerticalPosition% = (rowIndex% + 1) * 20 + + ' "1" = collectible coin + IF currentCharacter$ = "1" THEN PUT (columnIndex% * 20, rowIndex% * 20), CoinSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "1" + + ' "p" = decorative bush (rendered slightly lower) + IF currentCharacter$ = "p" THEN PUT (columnIndex% * 20, (rowIndex% * 20) + 10), BushSprite, PSET + + ' "h" = window decoration + IF currentCharacter$ = "h" THEN PUT (columnIndex% * 20, rowIndex% * 20), WindowSprite, PSET + + ' "v" = trampoline that boosts player upward when landed on + IF currentCharacter$ = "v" THEN PUT (columnIndex% * 20, (rowIndex% * 20) + 10), TrampolineSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "v" + + ' "t" = tree decoration + IF currentCharacter$ = "t" THEN PUT (columnIndex% * 20, rowIndex% * 20), TreeSprite, PSET + + ' ">" = right-moving conveyor belt (overrides normal movement) + IF currentCharacter$ = ">" THEN PUT (columnIndex% * 20, rowIndex% * 20), RightArrowSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "z": ObjectGrid(columnIndex% + 1, rowIndex% + 1) = ">" + + ' "<" = left-moving conveyor belt (overrides normal movement) + IF currentCharacter$ = "<" THEN PUT (columnIndex% * 20, rowIndex% * 20), LeftArrowSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "z": ObjectGrid(columnIndex% + 1, rowIndex% + 1) = "<" + + ' "a" = bottom breakable block (similar to "o" but different behavior) + IF currentCharacter$ = "a" THEN PUT (columnIndex% * 20, rowIndex% * 20), HoleSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "z": ObjectGrid(columnIndex% + 1, rowIndex% + 1) = "a" + + ' "u" = entry door for teleportation system + IF currentCharacter$ = "u" THEN PUT (columnIndex% * 20, rowIndex% * 20), DoorSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "u": DoorEntryX% = columnIndex% * 20: DoorEntryY% = rowIndex% * 20 + + ' "U" = exit door for teleportation system (pairs with "u") + IF currentCharacter$ = "U" THEN PUT (columnIndex% * 20, rowIndex% * 20), DoorSprite, PSET: TerrainGrid(columnIndex% + 1, rowIndex% + 1) = "U": DoorExitX% = columnIndex% * 20: DoorExitY% = rowIndex% * 20 + NEXT columnIndex% +NEXT rowIndex% END SUB SUB RenderSpriteFromFile (x%, y%, spriteID%, animationFrame%) -' -' Renders a sprite on screen by loading pixel data from external file +' Renders a sprite on screen by loading pixel data directly from external file ' File format explanation: -' - First line contains height (number of rows) -' - Subsequent lines contain strings of digit characters: -' '0' = transparent/background -' '1'-'3' = color indexes (mapped to current palette) -' Example file for 3x2 sprite: +' - First line contains height (number of pixel rows in sprite) +' - Subsequent lines contain strings of digit characters where: +' '0' = transparent/background (no drawing) +' '1'-'3' = color values (mapped to current palette) +' Example file for simple 3x2 sprite: ' 2 ' 123 ' 010 ' ' Parameters: -' x%, y% = Top-left drawing position -' spriteID% = Which sprite file to load (img/.i01) -' animationFrame% = Special handling: -' 1 = normal rendering -' 50 = horizontally flipped -' Other values = scaled rendering (value = scale factor) +' xPosition%, yPosition% = Top-left drawing coordinates on screen +' spriteID% = Which sprite file to load (references img/.i01) +' animationFrame% = Special rendering mode selector: +' 1 = normal rendering +' 50 = horizontally flipped version +' Other values = scaled rendering (value indicates scale factor) DIM rowText AS STRING fileName$ = "img/" + LTRIM$(STR$(spriteID%)) + ".i01" OPEN fileName$ FOR INPUT AS #1 -INPUT #1, height% -DIM spritePixelRows(1 TO 100) AS STRING -FOR rowIndex% = 1 TO height% +INPUT #1, spriteHeight% +DIM spritePixelRows(1 TO 100) AS STRING +FOR rowIndex% = 1 TO spriteHeight% LINE INPUT #1, spritePixelRows(rowIndex%) NEXT rowIndex% CLOSE #1 -' Handle special rendering modes based on animationFrame parameter +' Handle special rendering modes based on animation frame parameter IF animationFrame% = 50 THEN GOTO DrawFlippedSprite +' Normal rendering - draw pixels from top to bottom, left to right FOR rowIndex% = 1 TO 100 IF spritePixelRows(rowIndex%) = "" THEN GOTO FinishDrawing - FOR columnIndex% = 1 TO LEN(spritePixelRows(rowIndex%)) - ' Convert character digit to numeric color value (0-3) - pixelColor% = ASC(RIGHT$(LEFT$(spritePixelRows(rowIndex%), columnIndex%), 1)) - 48 - ' Calculate actual screen position considering scaleFactor - PSET ((x% + columnIndex%), (y% + rowIndex%)), pixelColor% - NEXT columnIndex% + FOR pixelColumn% = 1 TO LEN(spritePixelRows(rowIndex%)) + ' Convert character digit to numeric color value + ' ASCII '0'=48, so subtracting 48 gives 0-9 numeric value + pixelColor% = ASC(RIGHT$(LEFT$(spritePixelRows(rowIndex%), pixelColumn%), 1)) - 48 + + ' Only draw non-zero pixels (0 is treated as transparent) + IF pixelColor% > 0 THEN PSET ((xPosition% + pixelColumn%), (yPosition% + rowIndex%)), pixelColor% + NEXT pixelColumn% NEXT rowIndex% GOTO FinishDrawing DrawFlippedSprite: -' Horizontally flipped rendering +' Horizontally flipped rendering - mirror image for left-facing sprites FOR rowIndex% = 1 TO 100 IF spritePixelRows(rowIndex%) = "" THEN GOTO FinishDrawing FOR columnIndex% = 1 TO LEN(spritePixelRows(rowIndex%)) -- 2.20.1