The main design goals of SIDE are clarity and simplicity, which by the way are also worth considering when writing any kind of code. And actually the program architecture is very simple, being made of three basic blocks, as shown below:
I8080 Emulator (portable) |
Arcade Machine Emulator (portable) |
Presentation: input, sounds and graphics (not portable) |
Space Invaders is based on the 8-bit Intel 8080 CPU, which is emulated by the I8080 class. This class provides methods to read and write CPU registers and to execute instructions. A CPU is not entirely self contained though, and it usually depends on external devices to perform input/output operations such as reading or writing memory and ports. In hardware these functions will be provided by some other chips on the motherboard and we have to emulate this behavior in software too.
A common approach is to call external functions in the CPU emulation code, such as read_memory(), write_port() and so on, and to leave to the rest of the program to provide an implementation for these fuctions. This approach works and is fast but has a few problems as well. For example, you cannot easily have machines emulated by the same program, because both CPUs will keep calling the same I/O functions. Because of this, it is better to use pointers to functions rather than plain externals, as the program keeps the freedom of providing any suitable implementation.
To clean up things I have created an interface for the required functions in the I8080Environment class. All of the I/O functions must be implemented by a descendant of the I8080Environment class, which represents the environment (i.e. the machine) where the CPU lives. This environment is then passed to the I8080 class constructor when it is created.
When the CPU is available, it is time to emulate the arcade machine itself, that is sounds, graphics and input devices. Being a machine of the late seventies, Space Invaders has a very simple hardware: a 256x224 monochrome display adapter, a circuit with hard-wired analog sounds, three buttons for left, right and fire and the usual coin-op stuff (coin hole, one and two player buttons). In the original machines color was added by placing a green transparent strip on the bottom of the screen, and a red strip on top. Anyway, the 8080 CPU communicates with this hardware in the standard way, with ports and memory. The arcade emulation then takes place in a InvadersMachine class that extends the I8080Environment base class.
Unless the CPU class, we face an additional problem when implementing the machine emulation: what do we do when the CPU tells us to play a sound? And where are we going to display screen changes? Again, I have choosen not to follow the usual path, trading speed for portability. So for example when the CPU updates the screen the InvadersMachine code updates a virtual screen, but does not try to write to the user's screen. In the same way, if the CPU writes to a port to play a sound the emulator doesn't play any sound, but simply flags a variable and keeps track that this event has occurred. In other words, all emulation and rendering takes place into a virtual machine implemented by the InvadersMachine class.
Since no output is produced by the machine emulator, there must be a final layer that gets input from the user and displays the emulator output. For SIDE, this is the program itself.
The mechanism of presentation is very simple. The emulator is run at full speed until a frame is rendered. (A frame is a static image that freezes the game output at a specified time. Frames are updated very frequently, say 50 times per second. By updating and changing these images so fast the final effect is that of a fluid animation.) Normally, thanks to the speed of modern personal computers, the emulator runs so much faster than the original game that a frame is available well before it has to be displayed. When this occurs the program simply waits idly for the right moment (say the next 1/50th of a second) and then reads the virtual screen from inside the emulator, converts it for the user screen, and shows it.
Sound playing works pretty much the same. The original hardware doesn't have a dedicated sound chip, but rather several analog sound generators which are triggered by a CPU signal. The CPU has no control over the sound volume or duration, it just ask a sound generator to start playing and then forgets about it. The only sound that can be stopped is the "alien spaceship" sound, which when acticated keeps playing again and again until stopped.
For input, the emulator provides a method that can be used by the program to signal that an external event has occurred, for example that the fire key has been released. The emulator only works with this kind of events and it is left to the program to map those to actual keys, such as the spacebar (which in fact fires the cannonship).
Thanks to the separation between the emulation and the presentation code, most of the emulator code can be written in standard C++ and be highly portable. As an experiment I installed Linux with KDE on a partition of my disk and tried to port SIDE using KDevelop. I was amazed by the power and simplicity of KDevelop and the QT libraries. With very little knowledge of KDE, I managed to have a version running in less than one hour (most of the time was spent browsing the KDevelop documentation). Unfortunately I have since lost that Linux partition so at present I can only offer this early version which has no sound, though I do remember that adding sound was quite simple as QT has a dedicated function for playing a sound sample.
Copyright (c) 2003 Alessandro Scotti. All rights reserved.