Space Invaders - notes while coding

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

Space Invaders - notes while coding

Why write space invaders?

Currently I’m prototyping a space invaders clone for a little 8 bit microcontroller called the STM8, I bought one of these beasties as part of an order from RS components as I needed something to bounce the value up to hit the minimum for free postage and packing (I think postage was £5 and I was £7 under the magic value). I bought a STM8L-Discovery development board which has a little 24 segment LCD panel on it and a STM8L152C6T6 microcontroller. Of course now I’ve bought this little guy I need to think of something to do with him.

The STM8L Discovery part has 32K of ROM and 2KB of RAM on board, but it also has some fairly powerful DMA / SPI ports on it and should be able to generate PAL video using the SPI bus running at 8Mhz with a couple of resistors and a composite video lead. Providing you don’t want colour it has more power than the Atari VCS2600 so we should be able to write some video games for it.

Space invaders has fascinated me since I was a child but what’s interesting about it from the game point of view is that not much actually moves on screen for a given video frame. Basically the bullets, players base, one alien and the Saucer are the only things that move on each frame. So initially it takes 55 video frames for all of the aliens to move two pixels. So it should be possible to write space invaders for STM8 family. There is however a catch, the original hardware has a 256 x 224 bitmapped frame buffer which takes up 8K of video ram. The STM8 doesn’t have that much RAM so we’ll need to get creative, the current plan is to create space invaders using a character mapped system for the invaders, players base, saucer and RAM based characters for the players shields. We’ll use sprites for the bullets and some of the explosions as these can appear at any position on screen.

This uses 896 bytes of RAM for the screen, 256 bytes for the players shields and a bit more memory for storing the sprite data so we should comfortably fit within the 2K of RAM we have available. We’ll use 2K of program space to store the invaders images and bit bash a line of video for our display using the STM8 timers and DMA to shift everything out fast enough.

On a really good day we might even be able to fit into an STM8S103F3P6 development board system which can be bought from AliExpress, which at the time of writing cost around $0.70 on a breakout board, as eight rows of the space invaders screen are empty (or constant) so we can probably squeeze a single player game into the 1K of RAM and 8K of ROM available.

So in order to get from an idea loosely sketched out to working hardware I wanted to prototype the screen and everything else on my main PC, so the first step is fire up Visual Studio and get writing a WPF application. There is documented source code for space invaders available which makes the overall design a lot easier:

Code archaeology - Original 8080 source code using Z80 Mnemonics

Cycle accurate re-implementation in C - C based version of space invaders

I’ve been using Mame with a space invaders ROM to cross compare my results with an emulation of the arcade machine. The ability to step one frame at a time, debug the 8080 code and compare screen shots has been handy.

Enter WPF

The core of the code is a timer thread that waits on a ManualResetEvent with a short timeout attached, the event is signalled when the WPF window closes which then kills the thread. Before we start the thread we alter the systems multimedia timer by calling timeBeginPeriod to alter the system timer from the default to a more accurate 1ms (this does horrible things to battery life, so normally isn’t recommended) This gives us a pretty good approximation to a 60Hz frame rate.

private void WaitingTimer()
{
    // Set the multimedia timer to 1ms
    TimeBeginPeriod(1);
    // run at 60Hz by waiting for 16ms 17ms 17ms 
    // which gives 3 interrupts per 50ms.
    while (!dieEvent.WaitOne(16))
    {
        if (invokeTick) this.Dispatcher.InvokeAsync(IsrRoutine);
        if (dieEvent.WaitOne(17)) break;
        if (invokeTick) this.Dispatcher.InvokeAsync(IsrRoutine);
        if (dieEvent.WaitOne(17)) break;
        if (invokeTick) this.Dispatcher.InvokeAsync(IsrRoutine);
    }
    TimeEndPeriod(1);
}

The timer thread checks to see if we’re currently running the timer normally and if so it invokes it. The plan is that like the ZX81 the IsrRoutine first renders the screen, then in the vertical blanking period we run the rest of the code. For our WPF version we aren’t synchronised to the screen, and stopping the isr routine by setting a flag is a lot nicer when debugging. Using the System.Diagnostics.StopWatch the overall accuracy isn’t bad we seem to settle around 1010 ms to run 60 frames and spend around 31ms in our ISR routine, which given the somewhat nasty way we’re rendering the video for WPF seems acceptable.

We render the screen line by line as that’s what we’re going to do when we’re running on the STM8, every video frame we render a row in memory then shift it over the SPI bus at 8Mhz using DMA (the ZX81 cleverly executes NOPs instead). For WPF we have a WriteableBitmap which is a single 256 x 224 black and white image which we write to one line at time.

for (int line = 0; line < 224; line++)
{
    var renderResult = LineRender.RenderLine(line);
    Int32Rect rect = new Int32Rect(0, 0, 256, 1);
    frame.WritePixels(rect, renderResult, 32, 0, line);
}

The bitmap is then made the source of an image in the windows WPF xaml file and rotated round like the arcade machine monitor and zoomed to be twice the size so it’s a bit easier to see whats going on. This ability to zoom and rotate was part of the reason for using WPF.

<Image Height="512" Width="448" Stretch="None" RenderTransformOrigin="0.5,0.5">
    <Image.RenderTransform>
        <TransformGroup>
            <ScaleTransform ScaleX="2" ScaleY="2"/>
            <RotateTransform Angle="270"/>
        </TransformGroup>
    </Image.RenderTransform>
</Image>

The LineRender routine is below and it’s fairly simple to understand, for the players shields (and a few other cases) having a small set of characters that don’t come from the ROM is useful so the first 32 characters come from array of bitmap characters which will be in RAM. Otherwise we just have array of characters which are used as lookups to get the individual row from the character ROM and put together to make a line to display on screen. This routine runs 244 times on each timer tick (60Hz at the moment like the arcade machine, 50Hz for PAL video in the STM8) once per row on screen. For the sprites we ask each sprite if it’s visible to draw it’s contribution to the line individually by calling the sprite.Draw method (more on this in a future post).

public static byte[] Screen = new byte[ScreenWidth * ScreenHeight];
public static byte[] BitmapChar = new byte[32 * 8];
public static List<Sprite> Sprites = new List<Sprite>();

public static byte[] RenderLine(int line)
{
    var returnData = new byte[ScreenWidth];
    int startOfLine = (line / 8) * ScreenWidth;
    int cellLine = line & 0x07;
    for (int i = 0; i < ScreenWidth; i++)
    {
        byte c = Screen[startOfLine++];
        int index = c * 8 + cellLine;
        byte data = (c < 32) ? BitmapChar[index] : 
                               CharacterRom.Characters[index];
        returnData[i] = data;
    }
    foreach (var sprite in Sprites)
        if (sprite.Visible)
            sprite.Draw(line, returnData);
    return returnData;
}

This post was written while I’m still coding the WPF version as a break from debugging a timing issue (Mame and my code don’t quite agree), so I’ve no idea if the STM8 version will ever work (I have read the spec sheets and it looks possible). I am planning to put the code on github once I’ve finished (or at least documenting my failure) and I have some other variations planned.