I ported Gridrunner to the Nintendo Entertainment System (NES). Possibly a technical miracle. Possibly not.
'Porting' is probably overstating it. Like the Commodore 64, the NES had a 6502 CPU so if you have the code for Gridrunner to hand it is possible to do a very basic port such as mine with very little difficulty.
Unfortunately, we don't have the code of Gridrunner to hand. It is probably sitting on an antique disk somewhere in a cottage in Wales.
Fortunately, disassembling the game binary back into assembly language is relatively easy. So relatively-easy I did it a couple of years ago. So we have the code to hand after all!
The approach I took is very basic indeed. All of the NES
-specific code lives in this file called nes.asm
.
The rest lives in, you guessed it, gridrunner.asm
. This
is mostly unmodified code of the C64 original. Some stuff had to be deleted obviously to make way for the NES-specific implementation, more of which later.
Finally, there's charset.asm
where the definition for this little
fellow lives:
.BYTE $18,$3C,$66,$18,$7E,$FF,$E7,$C3,$18,$3C,$66,$18,$7E,$FF,$E7,$C3 ; CHARACTER $07, SHIP ; 00011000 ** ; 00111100 **** ; 01100110 ** ** ; 00011000 ** ; 01111110 ****** ; 11111111 ******** ; 11100111 *** *** ; 11000011 ** **
The C64 is a very friendly system to develop on - you write a value to a location in memory and it magically appears on screen. You write a value to a different location, it changes color. The NES on the other hand requires a lot more convolutions from you. While it allows up to 64 8x8 sprites, it does not really envisage you writing lots of characters to screen every frame - which is what we're doing here, just like the original Gridrunner. Of course, there is a world in which I could have just used sprites instead (and maybe there will be) but for a first pass what I have done is write as many as characters as possible to screen as I can in the small interval allowed by the NES between each frame.
This all happens in the MainNMIInterruptHandler
, which is the routine that runs every
time the NES takes a little rest between drawing a frame on the screen. This little rest is known as the VBLANK, or
Vertical Blank. Every time Gridrunner wants to write a character to the screen, such as when moving the ship to a new
position, I instead write it to a buffer array called NMT_UPDATE
.
This is done using a few different utility functions, depending on the use case, but mainly WriteCurrentCharacterToCurrentXYPosBatch
which will append updates
to the list in NMT_UPDATE
until there are enough of them to warrant writing to the screen itself in MainNMIInterruptHandler
.
WriteCurrentCharacterToCurrentXYPosBatch JSR GetLinePtrForCurrentYPosition LDA #47 STA BATCH_SIZE JSR AddPixelToNMTUpdate ; If we've got a few to write, let them do that now. CPX #BATCH_SIZE BMI @UpdateComplete JSR PPU_Update ;FIXME: Get Color ; LDA colorForCurrentCharacter @UpdateComplete RTS
AddPixelToNMTUpdate
does the actual writing to the NMT_UPDATE
array:
AddPixelToNMTUpdate JSR GetCharacterAtCurrentXYPos CMP currentCharacter BEQ @Return LDA currentCharacter STA (screenBufferLoPtr),Y ; Write to the actual screen (the PPU). LDX NMT_UPDATE_LEN LDA screenLineHiPtr STA NMT_UPDATE, X INX LDA screenLineLoPtr CLC ADC currentXPosition STA NMT_UPDATE, X INX LDA currentCharacter STA NMT_UPDATE, X INX STX NMT_UPDATE_LEN @Return RTS
Crappy, almost non-existent. I stopped short of figuring out how to make sound work in NES properly.
The NES version seems faster than the original Gridrunner. This is not because I'm a coding genius, it's because it's the way it worked when I got the thing into a functioning state.
Easiest thing is just to play it online here.
Maybe it will even work if you try to play it online in the iframe below. Worth a try maybe? A
is the Fire key. Arrows for moving around.
You may have to click the screen a few times to get it going. Also, the browser experience is substantially slower than
running it natively.