Monday, 15 December 2014

Rocket Raid Under the Hood 11: Sprites

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

Sprites

Rocket Raid uses heavily on sprites, such as the player ship, rockets, fuel tanks and asteroids. In this post we will look at how these are stored and accessed. The code supports up to 20 sprites onscreen simultaneously.


Sprites Used in the Game

A total of 13 different sprite shapes are defined in the game, quite a small number. There are a couple of points of interest to note. One of them (number 7, which is a small shape like a rock) is one I have never actually seen displayed on screen; while the last one (number 12) looks strange at first glance but has actually been carefully designed so that it results in a cleanly-drawn sprite when plotting a rocket in motion (we will look at this more closely in a later post). 

0 Player's ship
1 Rocket



2 Fuel tank
3 Ultimate Robot (last level)
4 Alien (tunnel section)
5 Robot

6 Asteroid
7 Rock? (not encountered)

8 Ship explosion
9  Bullet
10 Bomb

11 Player's ship (small) - used to display the number of lives remaining
12 Rocket in motion

Storage of Sprite Shapes

The games stores following information to enable efficient sprite plotting:

  • Height of each sprite
  • Width of each sprite
  • Data size (number of bytes) of each sprite
  • Table of pointers to the start of the data for each sprite
  • Sprite data (stored consecutively)



Saturday, 13 December 2014

Rocket Raid Under the Hood 10: Hall Of Fame (cont.)

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

Displaying the Hall of Fame

In the last post, we looked at the screen which prompts a user to enter their name if they have achieved a high score at the end of a game. This time we will look at the straight-forward code which displays the Hall of Fame chart itself.

For each row in the table, the code logic prints the rank number (1-8) followed by four dots ("....") and then the score. Any leading zeroes in the score are displayed as a dot. Four more dots are placed to separate the name from the score.

The X register is used as an offset for the score and name look-up tables. Since the tables are stored in memory from lowest score to highest, X is initially set to point to the last entry and is decreased to move to the next rank entry.


Hall of Fame screen

BeebDis labels:
hallOfFameText $0486
hallOfFame $0605
printHighScoreTable $0619
printFourDots $06C2
moveDownOneLine $06C4
videoController $1C40

Disassembly:

.hallOfFameText
        EQUS    $16,$07                     ; MODE 7
        EQUS    $1F,$06,$01                 ; Move cursor to (6,1)
        EQUS    $81,$8D                     ; Double-height, red text
        EQUS    "Rocket Raid Hall Of Fame"
        EQUS    $1F,$06,$02                 ; Move cursor to (6,2)
        EQUS    $81,$8D                     ; Double-height, red text
        EQUS    "Rocket Raid Hall Of Fame"
        EQUS    $0A,$0A,$0A                 ; Move down 3 lines
        EQUS    $0D                         ; End of text

.pressSpaceText
        EQUS    $1F,$05,$16                 ; Move cursor to (5,22)
        EQUS    "Press SPACE BAR or Fire Button"
        EQUS    $1F,$0A,$17                 ; Move cursor to (10,23)
        EQUS    "On Joystick To Start"
        EQUS    $0D                         ; End of text

.hallOfFame
        LDX     #$86            ; 
        LDY     #$04            ; Point to hallOfFameText (&0486)
        JSR     printAtomString ; Print Hall of Fame heading

        LDX     #$0A            ; Select video controller register 10

                                ; (controls the cursor format)
        LDA     #$20            ; Want to set bit 6 (hide cursor)
        JSR     videoController ; Equivalent of VDU23,0,10,32;0;0;0

        LDX     #$18            ; Set X=24 (offset to rank 1 of score table)

        LDY     #$01            ;
        STY     temp            ; Set temp=1 (temp holds current rank)
.printHighScoreTable
        LDA     #$20            ; (not needed?)
        JSR     moveDownOneLine ; Move to next line

        LDA     temp            ;

        ORA     #$30            ; A=48+temp (i.e. ASCII value of 1, 2, 3...)
        JSR     OSWRCH          ; Print rank number

        JSR     printFourDots   ; Print four dots


        LDY     #$FF            ; Set leading zero flag

        LDA     scoreLadder+2,X ; Get top byte of score (BCD)
        JSR     printBCDNumber  ; Print highest two digits of score
        LDA     scoreLadder+1,X ; Point to next byte of score (BCD)
        JSR     printBCDNumber  ; Print next-highest digits
        LDA     scoreLadder,X   ; Point to last byte of score (BCD)
        JSR     printBCDNumber  ; Print lowest two digits.

        JSR     printFourDots   ; Print four dots


        TXA                     ;

        PHA                     ; Store current table offset on stack
        LDA     nameLookup+1,X  ;
        TAY                     ; Get MSB of name address from look-up table
                                ; i.e. Y=?(nameLookup+1+X)
        LDA     nameLookup,X    ; Get LSB of name address from look-up table
                                ; i.e. X=?(nameLookup+X)
        TAX                     ; 
        JSR     printAtomString ; Print the name
        PLA                     ;
        TAX                     ; Retrieve current table offset from stack

        LDA     #$0A            ; ASCII 10 = move to next line
        JSR     OSWRCH          ;
        JSR     OSWRCH          ; Move down two lines

        INC     temp            ; Increase rank counter

        DEX                     ;
        DEX                     ;
        DEX                     ; Decrease look-up index to next rank in table
        BNE     printHighScoreTable ; Look back until all ranks printed

        LDX     #$C6            ;

        LDY     #$04            ; Point to pressSpaceText (&04C6)
        JSR     printAtomString ; Print "Press SPACE BAR" text

The above code calls a short routine that affects the video controller hardware (a 6845 chip named Sheila) by passing the appropriate register number and value, in order to turn off the cursor. This is more efficient than calling OSWRCH several times to achieve the same result.

; update video controller (crtc)
; Pass register number in X, register value to set in A
; (equivalent of VDU23;X,A,0;0;0;)
.videocontroller
        STX FE00                 ; write to video controller (register number)
        STA FE01                 ; write to video controller (register value)
        RTS                      


Tuesday, 9 December 2014

Rocket Raid Under the Hood 9: Hall Of Fame (cont.)

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

Top Eight Name Entry Screen

Having made a brief diversion in the last post to look at some general text printing routines, we now return to code relating to the Hall of Fame. As we saw in this earlier post, after each game the player's score is checked to see if it qualifies for the Top Eight. If it does, then a screen is displayed prompting for their name.
An awesome score of above 1000
gets you into the Top Eight

Displaying the Entry Screen


Since the screen is in MODE 7 teletext, all the content information can be stored as a list of text and control characters, then displayed by calling the "Microsoft string" print routine from the last post

BeebDis labels:
topEightText $0400

Disassembly: 

.topEightText

        EQUS $86             ; Length of all text to display (&86 characters)
        EQUS $16,$07         ; Mode 7
        EQUS $1F,$07,$03     ; Move cursor to (7,3)
        EQUS $86,$9D         ; Background colour to Cyan
        EQUS $84,$8D         ; Text colour to blue, double-height
        EQUS "Congratulations!!"
        EQUS $9C             ; Background colour to Black
        EQUS $1F,$07,$04     ; Move cursor to (7,4)
        EQUS $86,$9D         ; Background colour to Cyan
        EQUS $84,$8D         ; Text colour to blue, double-height
        EQUS "Congratulations!!"
        EQUS $9C             ; Background colour to Black
        EQUS $1F,$03,$07     ; Move cursor to (3,7)
        EQUS $82             ; Text colour to Green
        EQUS "Your score is in the Top Eight."
        EQUS $1F,$07,$0A     ; Move cursor to (7,10)
        EQUS $81,$0A,$81     ; Text colour to Red,
                             ; move down one line, Text colour to Red
        EQUS "Please enter your name:"
        EQUS $1F,$07,$0F     ; Move cursor to (7,15)
        EQUS $86,$9D         ; Background colour to Cyan
        EQUS $84             ; Text colour to blue
        EQUS $1F,$1F,$0F     ; Move cursor to (31,15)
        EQUS $9C             ; Background colour to Black
        EQUS $1F,$0A,$0F     ; Move cursor to (10,15)


.qualified

        LDX #$00             ; Set address of screen
        LDY #$04             ; content (.topEightText)
        JSR printMicrosoftString ; and print it


Fetching the Player's Name


Next, the OSWORD routine for reading keyboard input (also from the last postis called to fetch the player's name. An OSBYTE call is made first to reset the Escape flag.

BeebDis labels:
inputParams $07E0
Disassembly:

        LDA     #$7E            ; 
        JSR     OSBYTE          ; OSBYTE routine &7E (clear Escape condition)

        LDY     #$07            ; X and Y point to

        LDX     #$E0            ; parameter block .inputParams (&07E0)
        LDA     #$E5            ;
        STA     inputParams     ; Store pointer to .inputBuffer (&07E5)
        STY     inputParams+1   ; as the first param
        LDA     #$13            ;  
        STA     inputParams+2   ; Set max characters to 19 (excluding RETURN)
        LDA     #$20            ;
        STA     inputParams+3   ; Minimum acceptable ASCII value=32
        LDA     #$7E            ;
        STA     inputParams+4   ; Maximum acceptable ASCII value=126
        LDA     #$00            ;
        JSR     OSWORD          ; Finally make call to OSWORD routine 0
                                ; (read a line of text from input)

A check is then made to see whether the player pressed RETURN to complete their input, as normal, or hit Escape. If they pressed Escape then the input is set to an empty string.


Copying to the Name Look-up Table


The text input is then copied into the lowest position of the Hall of Fame name table (the "rank 9" position).

It is now ready to be sorted to the correct rank, as covered in this earlier post.

BeebDis labels:
foundCarriageReturn $055B
copyFromBuffer $0567

Disassembly:

        BCC     foundCarriageReturn ; Check if Escape was pressed.

        LDA     #$0D             ; If yes, then store only a carriage return

        STA     inputBuffer      ; in the buffer i.e. an empty string.
.foundCarriageReturn             ;
        LDA     nameLookup       ; 
        STA     stringPtr        ; Point stringPtr (2 bytes) 
        LDA     nameLookup+1     ; to the start of the Hall of Fame's
        STA     stringPtr+1      ; name table.
        LDY     #$13             ; Set counter to 19
                                 ; (want to copy 20 bytes in total)
.copyFromBuffer
        LDA     inputBuffer,Y    ; Copy character from input buffer
        STA     (stringPtr),Y    ; to the lowest rank in the
                                 ; name table (rank 9).
        DEY                      ;
        BPL     copyFromBuffer   ; Copy next character while Y>=0

Saturday, 6 December 2014

Rocket Raid Under the Hood 8: Text Input and Output

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


Text Input and Output


In this post we'll look at routines used for displaying standard text on screen using OSWRCH (the standard operating system routine for writing a character) and for gathering text input from the user by making a call to OSWORD. In Rocket Raid these are used mainly for displaying screens relating to the Hall of Fame.


String Representation


Rocket Raid stores text strings in memory in two different formats: the first, which the author refers to as an "ATOM" string in his book, is a string of characters followed by a RETURN character (&0D); the second, which the author calls a Microsoft string, is where the length of the string is placed in the first byte, followed by the characters of the string.

Accordingly, the game contains two separate routines to handle printing each type of string. These are identical to the routines published in Creative Assembler.


"Microsoft" string print routine


A Microsoft string consists of the length of the string as the first byte, followed by the characters of the string. The game uses this format for storing the text of the congratulatory screen displayed to the user if their score has qualified for the Top Eight.

Before calling this routine the address of the string to print should be stored in X and Y.

BeebDis labels:
stringPtr $0000
stringPtr+1 $0001
stringLen $0002
printMicrosoftString $069B
printNextChar $06A6

Disassembly:

.printMicrosoftString
        STX stringPtr         ;
        STY stringPtr+1       
Store addr of string to print
        LDY #$00              ;
        LDA (stringPtr),Y     ; Fetch length (first byte)
        STA stringLen         ; Store length of string
        INY                   ; Move to first character
.printNextChar
        LDA (stringPtr),Y     ; Fetch character to print
        JSR OSWRCH            ; Print character
        INY                   ; Move to next character
        CPY stringLen         ;
        BNE printNextChar     ; Loop until length reached
        RTS



"ATOM" string print routine


An ATOM string is a string of characters followed by a RETURN character (&0D). The game uses this format for storing and printing short strings, such as when displaying the names in the Top Eight high score table.

The routine is very similar to the Microsoft string printing routine and, similarly, before calling this routine the address of the string to print should be stored in X and Y.



BeebDis labels:

printAtomString $06B1
printNextAtomChar $06B7

Disassembly:

.printAtomString
       STX stringPtr        ;
       STY stringPtr+1      ; Store addr of string to print
       LDY #$00             ; Start from first char
.printNextAtomChar
       LDA (stringPtr),Y    ; Fetch character to print
       JSR OSWRCH           ; Print character
       INY                  ; Move to next character
       CMP #$0D             ; Check for a RETURN character
       BNE printNextAtomChar; If not found, loop back
       RTS


Printing a BCD (Binary-Coded Decimal) Number


One other routine we will cover here is for printing numbers that have been stored as binary-coded decimals. This will be used for printing the scores in the high-score table. Each score is stored as a three-byte BCD number, where each byte encodes two digits of 4 bits each. This means that the maximum score achievable in the game is 999999.

This number printing routine is also contained in the author's book. Before calling the routine, A should be set to the BCD number to be printed, and Y is a "leading character suppression" flag to indicate whether the padding character (".") should be output if a leading zero is found.

The routine reuses the same logic for printing both "halves" of the BCD number by manipulating to ensure that the lower four bits of the A register are set to the single number to be printed.

BeebDis labels:
printBCDNumber $1D41
printDigit $1D4C
validChar $1D51
padLeading $1D58

Disassembly:

.printBCDNumber
        PHA                ; Save number to stack
        LSR     A          ;
        LSR     A          ;
        LSR     A          ;
        LSR     A          ; Shift 4 bits right to get first BCD digit
        JSR     printDigit ; Print first digit

        PLA                ; Retrieve number from stack
        AND     #$0F       ; Keep only second digit
.printDigit
        BNE     validChar  ; If digit is non-zero, jump to print
        TYA                ; Otherwise, is zero-padding enabled?
        BNE     padLeading ; If so, then jump ahead to pad

.validChar
        LDY     #$00       ; Clear padding flag
        ORA     #$30       ; Add 48 to get ASCII value of number,
        JMP     OSWRCH     ; print it and return.

.padLeading
        LDA     #$2E       ; Get ASCII value of "."
        JMP     OSWRCH     ; Print it and return.



Text Input


A line of text can be read from the keyboard by making a call to OSWORD with register A set to zero to select the "read from input" routine. Registers X and Y should be set to an address of a five-byte "parameter block" in memory which holds the following information:
  • (two bytes) The address of a buffer to hold the input
  • (one byte) Maximum input length accepted, in characters
  • (one byte) Minimum valid ASCII character value 
  • (one byte) Maximum valid ASCII character value 
The OSWORD routine can now be called:
JSR     OSWORD
On returning from this routine, the zero-carry flag (C) will be set if the input was terminated by the user pressing RETURN; if instead they hit the Escape key to terminate input, it will be cleared.