Sunday 30 November 2014

Rocket Raid Under the Hood 6: Hall of Fame

This is the sixth part in a series of posts on digging into the code of the Acornsoft side-scrolling arcade game, Rocket Raid.

Note: in the previous post we replaced some of the labels that had been automatically assigned by BeebDis with custom labels to make the code more readable (such as replacing the label "L3402" with "copyBytes"). To help make the code more readable going forward, I’ll show the disassembly snippets with labels that I have replaced.

Hall of Fame Set-up

Next we'll look at code that sets the starting scores and names for the "Top Eight" Hall of Fame that is displayed at the end of each game, and the code that inserts a player's qualifying score into the table after a game.

The default scores and names are set during program initialization.

Default Hall of Fame Score Data


After setting a flag to mark that sound effects are "on", the program fills out a table, scoreLadder, to hold the default high score entries for the game.

Relevant BeebDis custom labels:
scoreLadder     $06D0
scoreLadder+1 $06D1
scoreLadder+2 $06D2
soundFlag        $0075
initHighScores $341C
Disassembly:
        DEY                      ; Y at this point is zero, so subtract 1 to get &FF
        STY     soundFlag        ; Set the sound flag to "on"
        LDY     #$1E             ; Set starting table offset to 30 
.initHighScores
        LDA     #$00             ;
        STA     scoreLadder,Y    ;
        STA     scoreLadder+2,Y  ;
        LDA     #$10             ;
        STA     scoreLadder+1,Y  ; Set 3-byte score to 1000 points (BCD)
        DEY
        DEY
        DEY                      ; Move down 3 bytes for next entry
        BPL     initHighScores   ; Loop while Y>=0
Scores in Rocket Raid are represented internally using 3 bytes of data, in binary-coded decimal (BCD) format. Hence each entry in the scoreLadder table has 3 bytes which are set to &00, &10 and &00, representing a score of 1000 points for each entry.

There is actually a small bug in the logic above, although this is not obvious without looking at the rest of the code: namely that one entry too many is created (counting down from an offset of 30 to 0 writes 11 entries instead of 10), placing one entry in data space that will be overwritten by other data. Fortunately, this doesn't cause a problem when the game runs because later high score logic does not refer to this extra entry. The fix is to set the Y offset to 27, not 30, at the start of the loop.

Also, only nine of the entries are used in practice; of which eight represent the Top Eight scores for display in the Hall of Fame, while the ninth serves as a work area when re-sorting the high score table. The tenth entry is not used.

Default Hall of Fame Name Data


Next the names associated with the default high scores are set - in this case, being the all-familiar "Acornsoft". The data buffer for the names, which I have labelled "nameLadder", has space for 10 entries of 20 bytes each.

Relevant BeebDis custom labels:
nameLadder-1 $070B
nameLadder $070C
initNames $3430
copyChar $3432

Disassembly:
        LDY     #$C8                    ; Set total byte counter to 200
.initNames
        LDX     #$09                    ; Length of "Acornsoft"
.copyChar
        LDA     acornsoftString,X
        STA     nameLadder-1,Y
        DEY
        DEX
        BPL     copyChar                ; Copy 10 bytes total (string chars + CR) to nameLadder
        TYA
        BNE     initNames               ; Repeat a total of 20 times

The code uses a trick to simplify the logic, which explains why it repeats 20 times instead of 10 times: instead of copying the "Acornsoft" string once and skipping 10 bytes to the next entry, the loop writes it twice consecutively, relying on the fact that the length of the string plus carriage return is exactly 10 bytes. (The second occurrence of the string within an entry will be ignored by the string output routine). So by writing it twice, the counter neatly lines up with the start of the next entry.


Look-up Table for Hall of Fame Names



The third part of the set-up creates a look-up table of pointers, called nameLookup, which point to the Hall of Fame strings in the nameLadder table we looked at above.

The nameLookup table contains space for 10 address pointers. Having a look-up table for the strings makes it easier to manipulate, for example when inserting a new entry in the high score table, by swapping around pointers rather than their underlying data strings.

Each pointer is allocated 3 bytes which gives it a structure consistent with the scoreLadder table, although only two of these bytes are needed to store the address (ordered LSB, MSB). The third byte of each entry is not set or used.

Relevant BeebDis custom labels:
nameLookup $06EE
nameLookup+1 $06EF
initNameLookup $3443
acornsoftString $3493


Disassembly:
        LDX     #$0C              ; &0C is the least-significant byte of nameLadder table at &070C
        LDY     #$1B              ; Fill from the 10th pointer position (27 byte offset)
.initNameLookup
        TXA
        STA     nameLookup,Y      ; Store LSB of address pointer in the look-up table
        CLC
        ADC     #$14              ; Point to next string in nameLadder (20 bytes higher)
        TAX
        LDA     #$07              ; &07 is the most-significant byte of nameLadder table at &070C
        STA     nameLookup+1,Y    ; Store MSB of address pointer in the look-up table
        DEY
        DEY
        DEY
        BPL     initNameLookup    ; Decrease offset & loop back to create 10 entries
The string data is defined a little further down, at the end of the program:
.acornsoftString
        EQUS "Acornsoft"
        EQUB $0D                  ; carriage return
After the above loop is carried out, the nameLadder table is filled with pointers as follows:

nameLookup        -> (nameLadder + 180) [Rank 9 name]
(nameLookup + 3)  -> (nameLadder + 160) [Rank 8 name]
(nameLookup + 6)  -> (nameLadder + 140) [Rank 7 name]
(nameLookup + 9)  -> (nameLadder + 120) [Rank 6 name]
(nameLookup + 12) -> (nameLadder + 100) [Rank 5 name]
(nameLookup + 15) -> (nameLadder + 80)  [Rank 4 name]
(nameLookup + 18) -> (nameLadder + 60)  [Rank 3 name]
(nameLookup + 21) -> (nameLadder + 40)  [Rank 2 name]
(nameLookup + 24) -> (nameLadder + 20)  [Rank 1 name]
(nameLookup + 27) -> nameLadder         [unused]

In the next post we'll look at how the nameLookup and scoreLadder tables are updated at the end of a game to display the Hall of Fame .

No comments:

Post a Comment