Commodore blog

In the Commodore Plus/4 Hungary Facebook group, I briefly mentioned that I've started working on a port of The Pit for the Plus/4.

As an introduction, I'll quote what I wrote there:

What are the sounds for, which I mentioned in the earlier post? For one of my big favorites, which was my first encounter with video games, if we don't count the Videoton TV Tennis.

In the mid-eighties, at the officers' club of the military housing estate (!!!), there was, for some reason, a coin-operated arcade machine running The Pit. We tossed countless Kossuth five-forint coins into it until we realized that the key to the "room," which had to be requested separately if we wanted to play, also opened the machine's cash box.

Of course, it was mostly interesting until I had my own computer at home, since the constantly repeating, though accelerating gameplay was far less exciting than waiting for the current game to load in front of the tape recorder.

It left a deep impression on me anyway, and I remembered it again when I recently discovered that Doug Turner, the creator of Icicle Works (and other mining masterpieces), released the new version about five years ago: The Pit.


Which, by the way, is a completely different game, with completely different technical knowledge and standards, yet there is a direct parallel. Doug's inspiration, his own Icicle Works, is a freely interpreted Boulder Dash clone with a Christmas-winter theme. The inspiration for Boulder Dash, according to its creator Peter Liepa, was a BASIC program by Chris Gray that hasn't survived to the present day. However, Chris's program wasn't an original idea either—it was conceived in an arcade while playing a machine. That machine was The Pit.

See the analogy? We see the underground adventure in cross-section, like in an ant farm, rocks fall on our heads, etc., etc...

What a beautiful circle, isn't it?

So, The Pit inspired me to create the Plus/4 port of The Pit. It's not a task that really tests a person and technology, but given the amount of time I have for such things, and how much I need to get back into coding, it's a perfect task for me.

I've also attached three videos in case someone isn't familiar with the game. The first is the original Arcade version, the second is the C64 port, and the third is the future Plus/4 port. The latter is just a taste, as the continuation is still very clumsy.

 

 

 

 

However, it clearly shows that adding digital sounds would be a strong exaggeration (unless we digitize a square wave, but what's the point, right?), and we don't need too many sound effects either, so I'm still racking my brains over how to solve this (in the short clip, TEDZakker is playing).

Another reason I'm looking for an efficient solution: I want to keep it within 16K. The original C64 version was cartridge-based, the cracked version also runs in the cartridge's place, it fits easily in 16K with plenty of free space.

Oh, and what will the title be? Obviously The Pit, since that's the original, but I'll come up with something to distinguish it. Like The Pit Arcade, or similar.


Although I have relatively little time to devote to this (as is usually the case as an adult), the project is progressing nicely and will probably be completed in the foreseeable future.

The reason it became a blog post is that due to its relative simplicity, quite a few specific problems to be solved have arisen, even if I caused some of them myself.

First of all, the original C64 program (at least the one that came to me) was cracked from a cartridge, and it fits in 16K with plenty of space left. So one goal became that the Plus/4 version should also fit in 16K, and even run on the C16 (the two are not the same!).

Secondly: the original has a bunch of sprites (elements that run independently of the background, hardware-based, like the player, enemies, etc...), these can only be done in software on the Plus/4, which is very resource-intensive. However, the characteristic of the game is that most of these are small (1x1 character), and they relatively rarely meet the background or each other, so sprite masking (the "combing" of sprite and background) is almost completely unnecessary.

Therefore, it was decided from the very beginning that the sprites would work as follows:

1. Player: 1x1 character figure, which theoretically could appear on a 2x2 character sprite mask. How can we simplify this?
- Pixel movement remains, but it can only stop within character boundaries. This makes the game a bit easier, as in the original it was particularly tricky to hit the pixel boundaries.
- If the player moves in sand, they always dig out a character-sized area in front of them - because of this, they always move in an empty, already dug-out place. Therefore, they don't meet the background, no masking is needed.
- When picking up treasure, again no masking is needed - since they pick it up, leaving only an empty space here too.
- If they collide with an enemy, no masking is needed, because DEATH! DEATH! DEATH!, which in the original game has a weird, tangled animation. This can be produced from fixed phases, and within character boundaries.
- They don't need to mask the bullet, since they shoot it in front of themselves, and it moves away from them.
- If a rock falls on them, the situation is the same as with the enemy, no extra action needed.
- Additionally, our player doesn't move diagonally, only vertically OR horizontally, not both at the same time.

Because of the above, the player's movement and its display are significantly simplified. Practically, they need to move in front of empty space, they could even appear character-wise, but then the movement would be very choppy. For the sake of pixel movement, the following solution was born:
- If the player is within character boundaries, we simply put them as a character, into which we copy the current animation phase.
- When moving sideways, they get an extra character to the LEFT no, to the RIGHT of the current position, and we shift these two sideways by 1-7 pixels, the 8th happens one character further.
- The same happens when moving up-down, but the second character appears one row below the current position.
- We don't overwrite the background with this, because before movement we check what is found in the four possible directions, and only allow continuation if it's empty space. If there is sand as an obstacle in one direction, but the player would move, we dig once - after that, it will be empty space, and they can proceed.
- Since the playing field is surrounded by impassable obstacles except for the entrance, because of the above, checking the minimum and maximum traversable position is unnecessary, as the obstacles will stop the player before they go beyond the traversable area. The entrance (and the exit next to the tank) are surrounded by $00 characters, which are just as empty as the traversable $20 characters, but block the player's progress.

A separate bonus is the layout of the playing field, which is why the right side of the screen is not traversable by the player. A little explanation about this:

The Commodore Plus/4 screen is 40 characters in 25 rows, or 320x200 in pixels. The vertical position (0-199, or hexadecimally $00-$C7) can be described in one byte, but the horizontal cannot, its value (if the entire screen is traversable) could be $0000-$013F, which can only be stored in two bytes. Even if the sprite position can't be $013F / $C7 anyway, since then only one pixel would be visible in the bottom left corner.

Well, in the case of The Pit, there is no such problem, using the original map, the player (and thus the enemies as well), including their size, can go up to the $D0 / $B8 position. This can be stored in one byte. This difference doesn't seem significant, but we save memory with the shorter program, and processor time by not having to calculate with an unnecessary +1 byte.

2. Enemies: Almost everything that applies to the player applies to them. Plus a bonus, since they very rarely meet each other, we don't need to deal with this deeply either. When colliding with each other, they will simply overwrite each other, which won't be noticeable in the heat of the game, so it won't be disturbing.

3. Bullets: There are two types of bullets, the player's and the tank's. The latter will be simple character display, doesn't require more. The player's won't be more complicated either, since it moves in front of an empty background, it gets destroyed when hitting an obstacle (= simply disappears), and when hitting an enemy, it disappears together with it.

4. Bombs in the lower cave: Their display is similar to the bullet, and they only move in one direction, so they are even simpler to handle.

5. Monster in the acid pool, tank, UFO: They don't really have a role in the game. All three can be displayed with simple character stories, moving in a minimal area. Only the tank gets a half-phase shifted character animation due to the relatively long straight path it takes.

To fit into 16K, I didn't have to make too great efforts, since the game itself is not too complicated. 

  • Instead of the full available character set, I only use half, which is more than enough for screen display.

 

The Pit character set
The Pit character set in its current state. This is not final, the animation phases will still be removed from it.

(For the graphics, I basically used the C-64 version as a base, but I took some details from the original arcade version)

  • The sounds aren't too complicated in the original arcade version either, so they can be produced quite easily and economically. In this, after some searching, Epy helped me. His monophonic effect player is about 256 bytes, plus the effect definitions, which will require a similar amount of space, so the sound only takes about half a kilobyte of memory. By the way, I used Epy's player almost unchanged, I only modified it so that a given effect can be looped (repeated). This way, the sound of the tank and the UFO could be described with relatively little, repeating data.

 

UFO sound
UFO sound, 45 bytes

 

  • The game screen, i.e., the color and character data, take up a total of 2000 bytes of memory. These contain relatively many repeating data, so in theory, the size could be reduced with simple byte compression. However, the unpacking routine needed for this would take up extra space in memory, so the reduction wouldn't be so significant. For now, there is plenty of space left, if it runs out, I'll return to this.
  • The title screen, however, which would theoretically also require 2000 bytes of space, is not stored. Since it has relatively little content, it is output as text content using the factory KERNAL routines. So it only takes $015C, i.e., 348 bytes.
  • The memory areas containing the various states, addresses, timers as variables have largely been moved partly to the zero page ($00-$FF), and partly to the area below the screen memory (below $0800). These don't contribute much, but since they have to be reloaded with data every time the game starts, it would be pointless to wear out the "proper" memory with them, especially since there would be little point in storing them in the finished program file.
  • Keyboard and joystick querying is done from a table. A 37-byte program performs the query, and the table size is 4-5 bytes per key, based on which we know which button's state to query, and if pressed, what to do with it. This still means growth with few keys, but if the game can be controlled from both joystick and keyboard, plus there is pause, exit and similar, we can already gain a few bytes.
  • As much as possible, I use the factory KERNAL routines. Screen clearing, keyboard matrix querying, writing to screen - these individually don't always mean a serious advantage, but with systematic use, you can save quite well.
  • It follows from this that I don't switch off the ROMs, not even temporarily. I don't need the RAM underneath them, and thus I save the STA $FF3F / STA $FF3E three-byte instructions as well. As a consequence, I don't redirect the interrupt at address $FFFE/FF either, but at $0314/15, saving the stacking and reading back of registers.
  • Although we store the player's position in pixels, we often need the character position and the pixel position within the character as well. After movement, the latter are calculated and stored once per screen cycle by a separate routine. So I rather take up another four bytes on the zero page, but I don't have to calculate these over and over again with ANDs and bit shifts during runtime.
  • When writing moving elements to the screen, I load the element's position into a zero-page byte pair, and if it's already there, I use this same byte pair in the same cycle for environment checking as well (e.g., is there an obstacle in front of the player, or has the enemy caught up with the player). This saves having to recalculate screen positions over and over. Combined with the previous idea, a lot of resources can be saved.
  • There are countless small solutions for smaller byte savings - such as if I need to write a data somewhere, and for that I loaded it into a register, then I try to use it for something else as well. A typical example is that if I turn off the screen, then set the border, background to black, then fill it with content, then a single LDY #$00 is enough, because I can write it in turn into $FF06, $FF19, $FF15, and with the same I can start the Y-indexed cycle with which I fill the content. This solution typically works well afterwards, since it's easier to notice such things in the (almost) finished program - but thinking ahead, most of it can already be done in the early phases.
  • ...and well, one of the best ways to save space is that instead of the built-in Monitor, I work in a cross-platform assembler. So I don't have to pack everything to well-memorable, zero-ending addresses like in the classic times :)

Most of the above, of course, only saves minimal space, but to understand why these crumbs are also important, let's scroll back to the above text, seemingly written in green for no reason - its size is about 4500 characters, i.e., about 4.4kB.

This little green text part, four times in a row, no longer fits into 16K!

One more small thing at the end: during development, I often use kind of mock-debug methods, which can then be easily-quickly thrown out of the final version. At such times, the screen is passive, and on essentially unimportant areas of the program's runtime, information that aids debugging or even the development process itself can be excellently displayed. Such things are also visible in this screenshot:

The Pit Arcade Commodore Plus/4
"Debug mode" under The Pit

Clearly visible is, on one hand, the classic "raster time measurement," the frame color set at the beginning of the interrupt and reset at the end, and on the other hand, at the bottom of the screen in a row, the player's X and Y pixel coordinates, the X and Y pixel coordinates within the character, the X and Y character coordinates, the digging counter (0-7, and accordingly @-G), and the current movement direction (horizontal, vertical, or - in this case - none).

In the High Score list, instead of names, at the top are the obstacles surrounding the player in the four directions, below it the current movement direction.

Besides this, here in debug mode, the above-mentioned empty but different from the free area $00, i.e., @ character, is not empty, it contains a single pixel, so it can be distinguished from the truly empty, traversable $20 (space) character. That's why it can be seen, for example, that next to the UFO, the player can only exit downwards.

Besides this, in debug mode, the movement of enemies, the falling of rocks, the tank shooting, etc., can be turned off (and here it is turned off). This can help a lot when you need to trace back some error whose source is still completely obscure.

This whole debug mode can be commented out with a few ; before saving the final version, and can be skipped when saving. In our case - precisely because of the 16K goal, it is located above $4000 anyway, so it doesn't disturb in the available memory either.

 

Well, that's all for now, next time - I hope - I'll report with the finished The Pit.