Tuesday, 17 August 2010

Restore Restart

I implemented a couple of routines over the weekend, the first of which was the Random Number Generator. Unsurprisingly, it bears a striking resemblence to the snippet of code which Muttley posted as a comment a while back. Since it works in almost exactly the same way (aside from using Zero Page instead of ROM as part of the generated value) it's a virtual carbon-copy, even down to the initial seed value - which is as good as any other, and there didn't seem to be any advantage to changing it:

;-------------------------------------------------------------------------------
; RANDOMGEN
; Generate a random number from 0-255
; Notes:    Returns value in .A, uses no other registers

randomgen    SUBROUTINE
             lda $0                     ; Get next ZP value
             adc .seed                  ; Add seed value
             adc RASTER                 ; Add current raster count
             sta .seed                  ; Update seed value
             inc randomgen+1            ; Increment ZP address in first instruction
             rts

             ; Seed value
.seed        DC.B $73                   ; Initial seed value

I plugged it in, and as if by magic it started returning a pseudo-random sequence of values from 0-255. So I now have a way of making 'random' decisions about Meanie movement, and indeed about any situation where chance is a factor.

I also decided I wanted a way to abort the game in progress and return to the title screen. Since I also needed a way to disable RunStop/Restore, it seemed like an obvious solution to combine the two - instead of the RS/R key combination generating an NMI which returns to BASIC, how about if it instead restarted the application?

The first part of the trick is to trap the NMI (Non-Maskable Interrupt) that RS/S produces. It's hard-wired into the VIC design that this key combination will fire the NMI, and, unlike the IRQ interrupt, there's no way to disable it - it's called non-maskable for a reason! However, those jolly nice chaps at Commodore set things up so that the first thing the Kernal NMI handler does is an indirect jump through a vector at $0318/0319. By default, that vector just points straight back into the Kernal routine, but of course you can make it point anywhere else. So now the initialisation section of the main Meanies program adjusts the NMI vector to point at this:

           ; NMI handler to handle 'Restore' interrupts
.nmitrap   bit V1PORTAO       ; Read VIA1 Port A Output Register to acknowledge NMI
           inc RESTART        ; Set restart flag
           rti                ; Return from the NMI

All we do here is a read of the VIA#1 Port A Output Register with the BIT instruction, set a flag we check in the main game loop, and return from the interrupt. By reading Port A, we trip the VIA handshake signal and the NMI is thereby 'acknowledged' so that further NMIs can happen. We use BIT because it affects no register values, except for flags in the Status Register - but that's OK, because the NMI logic in the 6502 is very well-behaved and saves both the PC and PSR registers when it gets invoked, and restores them after the RTI instruction. In other words, when we return from this custom NMI handler, the CPU state is exactly as we left it.

By incrementing the 'RESTART' flag value, we can have a simple test in the main game loop that looks for non-zero values - as long as the address contains zero, we carry on, but as soon as it gets incremented we exit the loop and go back to the title screen. So I now have a way to abort the game, and a handler for RS/S which prevents the game from being broken-into. Well, makes it harder to break into it, anyway. ;)

Sadly, it wasn't all sweetness and light whilst I was doing all this - after I'd got the RNG working, I started coding the routine to make the Meanies move based on the RNG output, and that was when the words of Field Marshall Helmuth Carl Bernard von Moltke came to mind:

'No battle plan ever survives contact with the enemy'

It turns out that CHARPLOT, my stable and reliable screen-plotting workhorse, isn't quite suitable for plotting mobile objects after all. It's fast enough, absolutely, and pretty flexible when it comes to putting things at predefined places, but as I started to write the code to move a Meanie around, I encountered a weakness - it's actually a bit of a drag having to recalculate a characters' position in terms of a base+offset, and then update the CHARPLOT structure each time. Plus, the structures have no concept of a 'last' and 'current' position, which means I have to do some faffing-around with cloned structures in order to be able to calculate a new position without overwriting the old data so that I can erase the object from its' old location and draw it at the new location.

So I'm having a rethink about the way CHARPLOT works. The fundamentals are good, but I'm toying with the idea of altering it to use an absolute address as the base, rather than a simple bit which indicates where the base is. If I do that, I can then carry-out simple 16-bit mathematics on the address rather than having to do some inelegant bit-twiddling; I can then also span repeated sequences across the page boundary in the middle of the screen, plus I get an easy way to check for out-of-bounds sequence overruns.

I'm still playing with this in my head, really just trying to figure-out if it'll then give me what I want and not trap me in a corner again further down the line. It means the CHARPLOT structure will grow by a byte, but I think the tradeoff in flexibility will make up for the slight increase in space requirements. I might write a test version later and see what it looks like.

0 comments:

Post a Comment