Space Invaders - State of play

Posted by Peter Ibbotson on December 28, 2020 · 16 mins read
Categories: Software , Invaders
Tags: Invaders

Space Invaders - State of play

I’ve put the source code up on github the private repo I was using is now public and I’m using the issue tracker as a todo list. A lot of the issues have been spotted while comparing the game screen with Mame. So here are some thoughts that I’ve noticed while writing the clone.

Demo mode, a bit of background

Space invaders plays a demonstration game which is quite handy for cross comparison purposes. It has a list of player actions that move the base across the screen, the list is rather small (11 commands) and plainly not long enough to play a decent game. When initially looking at the source code I couldn’t see how this worked but once I started implementing the game I realised that the main timing element for the demo mode is the players bullet.

Each time a bullet is fired the players base starts moving (or stays still) in a new direction, the act of firing the bullet moves the pointer along the list of commands making the base move left or right. The demo mode doesn’t reset this pointer between demonstration games so each game can be different. The demonstration game ends once the player explodes. Effectively the whole demo mode is timed by the players bullet.

The main code needs to run

One consequence of the structure I’ve chosen where on each timer tick the whole screen is redrawn one line at time, is that all of the code is run on the interrupt service routine (ISR). In the original code (0x81f for play mode, 0xB71 for demo mode) there is a loop which does the player firing, detection of the aliens hitting the screen edges, scoring, sounds and adjusting the fire rate with the other actions that move objects being run in the ISR.

Under windows tight loops should be avoided and for our final STM8 target we’re probably just going to sit in a loop waiting for the next interrupt. In our ISR routine we call a game loop function which does one pass through the code loop at various points in our ISR once the time critical piece of drawing the current screen is done to simulate being in that tight loop.

Handling sprites

Note this is wrong see issue 26 on github but it helps to write this down

In the per line renderer we scan down a list of sprites and if the sprite is visible we give it a chance to put what ever bits are are necessary into the line data before we put it on screen.

foreach (var sprite in Sprites)
    if (sprite.Visible)
        sprite.Draw(line, returnData);

The code to render a sprite is simple enough, we just test if the sprite is on the line being drawn currently, grab the data from a table of pre-shifted bytes and use or to put the bits into the line buffer. At the same time we check to see if the bits overlap with anything else on screen building this into a collision flag byte. The collision flags are cleared by the line rendering routine at the start of each video frame.

internal void Draw(int line, byte[] lineData)
    if ((line >= X) && (line < X + width))
        int x = line - X;
        int y = Y >> 3;
        byte c1 = data[Image, Y & 0x7, x, 0];
        byte c2 = data[Image, Y & 0x7, x, 1];
        collided |= (byte)(lineData[y] & c1);
        collided |= (byte)(lineData[y + 1] & c2);
        lineData[y] |= c1;
        lineData[y + 1] |= c2;

Each sprite can have multiple images so when it’s initialised in it’s constructor we create an array with an arrity of four (image, Y offset, X offset, vertical bytes) which we pre-shift our images into, each byte in the image can take up two bytes on screen once it’s been shifted vertically. For the C# version we do this in the constructor, for the STM8 version we will hard code these values at compile time (probably from the C# code) and structure them to be shift and mask friendly.

During testing however I noticed an issue with this code, since the players bullet is drawn first it doesn’t collide with the aliens bullets (or their explosions) and our logic for what do when we have a collision is called at a different point from the original invaders code. So the plan is to add an explicit collision test that renders just the bits around the sprite plus any other sprites then performs the collision test, which should help with accuracy.

Finally since our sprites are not drawn onto a full bitmap in memory when battle damage occurs to the players shields at the bottom of the screen, we need to emulate that damage. So we have this routine shown below which when the sprite is over a bitmap character removes pixels which overlap with the sprite.

int xOffs = X & 0x07;
int cellOffs = (Y >> 3) + (X >> 3) * LineRender.ScreenWidth;

for (int i = 0; i < width; i++)
    byte c1 = data[Image, Y & 0x7, i, 0];
    byte c2 = data[Image, Y & 0x7, i, 1];
    byte b = LineRender.Screen[cellOffs];
    if (b < 0x20)
        LineRender.BitmapChar[b * 8 + xOffs] &= (byte)(~c1);
    b = LineRender.Screen[cellOffs+1];
    if (b < 0x20)
        LineRender.BitmapChar[b * 8 + xOffs] &= (byte)(~c2);
    if (xOffs == 8)
        xOffs = 0;

Need to emulate the top / bottom half of the screen logic

The ISR in the original space invaders code decrements some counters and when the counter is zero moves the player, aliens and the various bullets around on screen. In an attempt to stop screen tearing (where the code writes to screen at the same time as it’s being displayed) the code runs the movement routines twice1, this is helped by the hardware as there are two ISR routines one which runs half way down the screen and one that runs at the bottom. Each routine sets a flag saying which half of the screen we’re on and the object movement code compares the current X position of the object against the flag before deciding to draw things.

Initially I didn’t bother to simulate this logic, but I found that it caused some issues when I was comparing my code against the Mame screen shots.

When you single step a frame with Mame, Mame stops on the first vblank interrupt, not the second one. The players bullet is aware of the screen halves but the players base isn’t. This means that the first time a bullet appears on screen the players base is missing. I added an additional render call to help make the screen shots match up and put in the screen half comparison code to the drawing logic.

The comparison code I initially wrote had a subtle bug, the original 8080 code just ands the current X position against the high bit of the current objects X position before deciding if it should draw or not. In order to convert from an X position to a screen position the 8080 code shifts it by 3 bits right and then masks off with and 0x3f and or in 0x20 to get the position in screen memory. The screen memory starts at 0x2400 so the X position has an offset of 0x20 added to it, the “where is a pixel” code takes a X in the H register and the Y in the L register so if we want to write to screen position x=25 (0x19) , y=146 (0x92) the HL register pair contains 0x3992 the pseudo code looks like this:

// Number of pixels to shift vertically
byte yShift=HL & 0x07;
// Where in the screen starting from 0x2400 to write
byte *ScreenAddress= ((HL>>3) & 0x3fff) | 0x2000;

The offset of 0x20 added to X when shifted right in the register pair HL gives an offset of 0x0400 which corrects for the screen offset of 0x2400 in memory. This caused a subtle on screen rendering bug as I originally had forgotten that the X coordinate I was comparing against for which half of the screen I was on was offset by 0x20 and meant that objects with an X position in the 0x60-0x7f range were moved on the wrong interrupt handler.

Never believe the comments

Another issue I spotted was the players bullet explosion being out by one pixel vertically and one horizontally, this was caused by me mis-reading the excellent disassembly of the 8080 code. In the disassembly the code sometimes uses X and Y and other times uses Yr and Xr to represent the screen being rotated. In this case the comments at 0x3df led me to adjust the position of the explosion incorrectly as I swapped the adjustment of X and Y by the wrong offsets.

In short the comments are usually right, but occasionally they can be misleading.

Mame oddity

There is another oddity with Mame and the demo playing. If you soft reset mame then the demo screens are slightly different to doing a hard reset, I suspect an uninitialised variable in the 8080 code but since it won’t affect the final game I may not spend much time investigating.

Current Nasty issue

There is timing issue where after the first lot of alien bullets have exploded the next set of bullets appear to be out by a cycle, I’m going to fix the sprite issue first and move the collision detection logic back into the game object code to see if this fixes this, however while testing and watching through the various animations of the splash screens I spotted this frame on the second time the game plays a demonstration.

Leftovers after an explosion
leftover characters on screen after explosions

The top half alien in the far right column row 5 appears first (and shouldn’t have exploded anyway but the sprite collision bug stopped the players bullet exploding when it collides with an alien bullet) and is then followed by the other weird half alien further down in row 2 when the players next bullet explodes. Right now I suspect this is caused by an error in the “where is” an alien logic as the rack is bumped down a row but that doesn’t explain the error further down screen.

So my current plan is to add a skip function to the game so I don’t have to wait for the game to play through properly and can just jump to a particular point in the screen rendering cycle by counting the number of ISR runs and getting debugging….

Update 28-12-20 I was correct it was an off by one error as the alien rack bumps down. Also the explosion was in wrong place on screen.

  1. The player and alien movement don’t bother with this for some reason.