You can play this game online using the JSBeeb emulator here:
How to play
Use Z and X to move the helicopter (green block in the thin version) left and right, and "shift" to thrust upward. Avoid the birds (red blocks) and collect coins (yellow blocks). Each coin you collect on a single trip doubles your bonus.
When you hand on the platform, the bonus is added to your score and zeroed, and you receive a new load of fuel. The aim of the game is simply to get the highest possible score.
If you run out of fuel (indicated by the red bar), you lose the ability to thrust upward, though you can still land if you're nimble enough. If you collide with a bird or miss the platform, you die. Press "space" to start a new game.
How to build
Building the resources requires python 2.7 with pygame. Type:
python gfx.py > gfx.s
Building the binary files requires atasm or equivalent:
atasm -r gfx.s
atasm -r -DHACKADAY_1K=1 -DHACKADAY_2K5=0 copter.s -othin.bin
atasm -r -DHACKADAY_1K=0 -DHACKADAY_2K5=1 copter.s -ofat.bin
How to launch
Load the disk image "copter.ssd" into your hardware or emulator (we recommend BeebEm for this).
If using real hardware, set up the keyboard links to boot into graphics mode 2 (see below).
If using an emulator which doesn't support the keyboard links, change to mode 2 on startup by typing:
To run the "fat" version, type:
To run the "thin" version, type:
BASIC scripts "GOFAT" and "GOTHIN" are provided to automate these steps. At startup, type:
On top of writing data to the display, we need to interact with the hardware in a couple of ways. The OS ROM is off limits, so we have to do this the old-fashioned way.
We poll for vblank by reading bit 1 of the system 6522 VIA interrupt status register at 0xfe4d. The vblank status is cleared by writing to the same register. We clear the vblank status immediately before the main loop to ensure we're in sync from the very first frame, as otherwise we see some flicker early on.
We scan the keyboard by disabling keyboard auto-scan at start of day, and then writing to and reading from the slow databus at address 0xfe4f. The (completely insane) annotated disassembly for Exile here:
was very helpful in figuring out how to do this.
The BBC Micro lacks hardware sprite support, so all our moving objects (birds, coins, the helicopter, the platform) need to be drawn to the framebuffer in software. This is made more exciting by the BBC's odd screen memory layout. Each byte contains two horizontally-adjacent pixels interleaved in bits (6,4,2,0) and (7,5,3,1), and pixel-pair byte addresses look like this:
0x000 0x008 0x010 ..
0x001 0x009 0x011 ..
0x002 0x00a 0x012 ..
0x003 0x00b 0x013 ..
0x004 0x00c 0x014 ..
0x005 0x00d 0x015 ..
0x006 0x00e 0x016 ..
0x007 0x00f 0x017 ..
: : : : : :
Our sprites are all 7*8 pixels in size (allowing us to support odd x coordinates using pre-shifted copies). The function "plot" does the necessary setup and uses one of three kernels ("pad", "store" and "blend") to do the actual writing.
Blend is the most interesting kernel. It uses a per-pixel mask stored in the "spare" senior bit of each pixel (the BBC Micro only supports 8 colors, so only three bits are required for the full palette) to do pixel-accurate masking and hit detection.
Chasing the raster
The game runs in graphics mode 2, whose framebuffer consumes 20K of the 32K of available RAM. Double buffering is out of the question, so to get flicker-free graphics we must carefully manage the position of the raster. In summary:
- The game runs "in a frame" at 50Hz.
- Frame processing starts immediately at the start of vblank.
- Coins and birds never overlap, and are designed to be self-erasing; the bird in particular is drawn padded with a pair of trailing cyan pixels to obscure its trail. This minimizes any artefacts that occur if the raster intersects the object while it is being drawn.
- The order of operations is: do helicopter control model; erase helicopter; draw coins; draw birds; draw helicopter.
- The status bar and sea can be drawn either first or last. If the helicopter is in the top half of the screen, they are drawn first, to allow the raster time to reach the bottom half. If the helicopter is in the bottom half of the screen, they are drawn last, so the helicopter has been redrawn before the raster reaches it.
We use only the base, official NMOS 6502 instructions: no 65C02 extensions or unofficial opcodes (though I would dearly have loved to resort to STZ on occasion). There's a fair bit of self-modifying code to:
- update address fields of load instructions to avoid zero-page setup
- implement cheap computed branches
- dynamically specialize the blend kernel to a store kernel
We use the 1- and 2-byte BIT hacks in a couple of places to provide multiple entry points to functions.
A lot of effort went into flag-propagation analysis to allow us to remove CLC and SEC instructions and substitute known-taken conditional branches (in particular BVC) for unconditional jumps. We preferentially use BCC (unsigned less than) and BCS (unsigned greater than or equal) over BMI and BPL to increase the fraction of the code for which the carry flag can be easily deduced.
The baseline "fat" version of the game is 1010 bytes in length, and loads to address 0x880. It requires a 1492-byte sprite and font file to be loaded at address 0x1300. You could stick your neck out and argue that this meets the spirit of the rules: the user can substitute any graphics of their choosing at this location (a script, gfx.py, is provided for this purpose) to customize the game, and so the graphics constitute "input".
On the other hand, you could argue this is bulls^H^H^H^H^Htendentious, and that graphics constitute "initialized data tables", and so count against the 1024-byte limit. To hedge against this, we've provided a "thin" version. This is exactly 1024 bytes in length, and includes both a numeric font and compressed "pong-o-vision" set of block graphics, which are unpacked to address 0x700 at startup.
One last point: the BBC Micro must already be in graphics mode 2 when the program is started. Why is this permissible, when OS code must have been executed to configure the graphics hardware? Well, handily it is possible to configure the startup video mode using three links at the front of the PCB circuit board (see page 489 of the Advanced User Guide).
Video mode setup therefore occurs during an "unavoidable hardcoded bootloader".
Code by Eben Upton. Graphics by Sam Alder and Alex Carter, based on resources developed for an event at the Centre for Computing History in Cambridge.