C64-Well
About
For some reason, you thought it really was a good idea to take your robot exosuit into that deep dark hole. It couldn't possibly be infinite could it? And if there's as much energy down there as your scanner says there is, you could be set for life.
Never mind that your exosuit's battery can't hold a charge (it leaks like a sieve), and oh—there's horrible energy sucking spikes that are suddenly arriving from below.
Yeah, this was a great idea.
Controls & Gameplay
Navigate using the joystick in port #2 (browser version is configured to use the arrow keys). Land on platforms for a brief respite from gravity. But don't stay still too long; your battery has to expend energy to keep you alive. You can jump (press UP), but make sure it's worth it—that costs a lot of energy.
You have 200 units of energy. You need to find hearts to recharge. Avoid the spikes—they drain your energy precipitously.
You earn money for each screen you clear based on the amount of energy you have available. How much can you earn?
battery discharge-0.16horizontal movement-0.1jump-12spike-50heart+10#asciibasic10liner entry
This game was created for the #asciibasic10liner challenge. As such, this is probably some of the worst code I've ever written! Nevertheless, I'll attempt an explanation of my logic.
Init (0-2)
This is the initialization section to define the variables that we're using throughout the game.
- w$ is the wall character.
- l$ (ledge) is a combination of these wall characters.
- f (forty) is just short for the constant 40 (used for screen width)
- ec is the character code for the heart
- sp is the character code for the spikes
- h is the character code for the wall
- p is the memory position for the player (in screen memory). We don't use X and Y positioning -- we just have a single address to save some space.
More initialization.
- g$ is additional graphics. g$(0) is the heart (energy). g$(1) is a series of red spikes. g$(2) is a single red spike.
- j is the poke address to read the joystick.
- l is the chance a ledge (and corresponding graphics -- heart or spikes) will appear
- a is the valid position range for the ledges.
More initialization!
- u is the distance one can jump up -- currently 40*3 or 120.
- c$ is short for "clear screen" string.
- We dimension d (delta) for 256 entries. Joystick Left (123) and Right(119) get -1 and 1 respectively. 126 is equivalent to Joystick UP. We set to zero initially; jumping only works from a ledge.
- z$ is used for moving the cursor left.
- Call the routine at line 7 (draw new screen).
Gameplay Loop (3-6)
The gameplay loop takes keyboard input from the user and updates the player's position. It determines if the player has touched a heart, a spike, or a wall. It also updates the player's energy use and displays their energy and score on the screen.
3 x=d(pE(j)):w=pE(p+x):n=p-(w<>h)*x:b=pE(n+f):n=n-(b<>h)*f:d(126)=(b=h)*u- x (player delta) is looked up from the d (delta) array, which is based on the current joystick reading.
- We read the character at the player's intended location into w (wall).
- n (new position) is calculated from p (player position). We use the fact that BASIC returns -1 for a comparision that is true to only change the position if the player player delta was set, and if there's no wall in the way.
- b (bottom) is used to detect below the player. Instead of looking to the new position horizontally, it looks down (+f, or +40). If there's no wall underneath, the player moves down a line (40 characters)
- e (energy) is used to store the player's remaining energy. It counts UP (relying on BASIC's default of 0 for uninitialized variables).
- The player gains 10 units if they touch a heart (ec), and loses 50 units if they touch a spike. The studious reader will catch that this isn't perfect; it's possible to read that the player has touched a heart or a spike when it's not obvious they've done so on screen.
- q (points) is used to store the player's score. It increases only whenever the player advances a screen. It's used loosely as money in the display. Here we convert it to an integer so BASIC isn't showing the user any decimal portion.
- Finally, if the new player position (np) is different from the previous player position (pp), poke a space at the previous position to clear the player's avatar.
- The new position is assigned to the player's position, and the player's avatar is poked into screen memory. 54272+p is used to poke the player character's color. Because p is an index to screen memory from 0 (not 1024 like you might expect), the 54272 constant here is lower than you'd expect when referring to color memory.
- Next, we print the player's score (money). We do it a line below the top (the top line will be their energy.)
- The player's position is checked, and if it's near the bottom of the screen, we call the screen redraw routine to render the next part of the level
- Keeping the player alive costs energy. We're always subtracting a fractional portion. In practice this means that the longer the player goes without accumulating hearts, the less money they will make as they advance.
- The player's energy is also impacted based on their movement. Jumping is significantly more costly than horizontal movement.
- The player's energy is printed in the top left of the screen.
- If the player hasn't exhausted their energy, we repeat the whole process (go back to line 3).
- But if they are out of energy, we clear the keyboard buffer, clear the screen, and print their score before ending.
- Note the use of "on goto" here; by abusing how a true conditional is evaluated to -1 and a false one evaluates to zero, we can create an else/then like structure. If e>205, the game is over because the value is "true(-1) + 1" will be zero. On Goto is 1-based, meaning that the branch is never taken. If e>205 is not true, however, we get "false(0)+1", and that does match to the first line in the list, and we take the branch.
Draw new screen (7-9)
This routine draws the next portion of the level, including ledges, hearts, and spikes. It also accumulates the user's score.
7 pRc$:fOz=1to22:pR:ifrN(0)<ltHc=rN(0)*a+9:pRsPc)l$;:fOg=1toc:pRz$;:neX- Clear the screen.
- We go from the top of the screen to the bottom, randomly printing ledges.
- If we print a ledge, we also backtrack to the beginning of the line by printing a lot of left characters (z$)
- Set the background color to black. I wanted this in the init section, but there wasn't room. This does mean there can be slight screen flicker as the layer draws.
- Accumulate a score for the user, for each line on the screen. The more energy the user has, the better the score will be.
- There's also a chance that we'll display one of our three graphics -- this can be a heart, a row of spikes, or a single spike. It may obliterate some of the ledges we just printed.
- Finish the loop, go to the top left of the screen, and print the walls of the well.
- We move the player back to the top of the screen by subtracting 960 (24 lines of 40 characters)
- ... and we return to the caller.
Why the C64?
The C64 was my first personal computer as a kid, and I learned the basics of programming on it. I still have it, and it still works, although it could use some restoration.
Thanks
- Downwell, which game me the inspiration for level layout.
- To https://commocore.itch.io/mathsticks-10 for making me aware of https://lvllvl.com/c64/ to supply a browser-playable version of the game.
- VICE's `petcat` utility for making it easy to easily automate the build & test process on my Mac!
Note
If you want to convert the BASIC source code to a PRG file, you need to use VICE's petcat utility. You can find it in the VICE bundle after installation.
petcat -w2 -o c64well.prg -- c64well.bas