Retrochallenge 2017/04:
Personal Computer Space Transactor 2001

Episode 7: Rocket (Phew!)

About some hassles, despair, and a rocket ship…

Welcome to what may have well been the last episode. But more on this later. We've a rocket ship on the screen, and while it also rotates, it doesn't do much else for the moment, thanks to circumstances which we'll discuss in a minute. However, this is what we eventually get:

Rocket ship in Personal Computer Space Transactor 2001

Rocket ship accompanied by friendly saucers.

Try it in in-browser emulation.

Programming a Rocket Ship — Or the Nerve-Wracking Tale of How I Grew a Gray Hair

Plans

As following readers may recall, we already had a sketch of what the rocket ship was to look like (along other elements of the game):

Elements of Personal Computer Space Transactor 2001

Sketch of game elements for Personal Computer Space Transactor 2001.

For our rocket ship this translates to the following data (starting with code 0 at the top row in the image above and advancing clockwise, all around to code 7):

screen      coors       thrust        direction      internal
codes       offset      offset         (dx/dy)        code

$67,$1C     x-1, x,     t: x/y+1       d: -1/-3         0

$2F,$65     x, x+1      t: x/y+1       d: 1/-3          1

$64,        y-1,        t: x-1/y       d: 3/-1          2
$2F         y

$1C,        y,          t: x-1/y       d: 3/1           3
$63         y+1

$1C,$65     x, x+1      t: x/y-1       d: 1/3           4

$67,$2F     x-1, x      t: x/y-1       d: -1/3          5

$2F,        y,          t: x+1/y       d: -3/1          6
$63         y+1

$64,        y-1,        t: x+1/y       d: -3/-1         7
$1C         y

Stepping out

Provided the procedures and abstractions we have already in place, this is easy to implement (at first). By the use of a dispatch table we're servicing the screen codes for our display, by pushing these along with the screen addresses to the drawing queue:


displayRocket ;pushes the rocket to the charQueue (VERSION 1)
                ldx rocketX
                dex                   ;2 pos offset
                dex
                stx qPosY
                ldy rocketX
                dey                   ;2 pos offset
                dey
                sty qPosX
                lda rocketDir         ;dispatch on value in rocketDir (0..7)
                rol                   ;×2
                tay                   ;AC -> Y
                lda .drJumpTable+1,y  ;Hi-byte
                sta PT1+1
                lda .drJumpTable,y    ;Lo-byte
                sta PT
                jmp (PT1)             ;dispatch (to .dr0 .. .dr7)
.dr0
                lda #$1C
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosX
                dex
                stx qPosX
                lda #$67
                sta qScreenCode
                jsr pushScreenCode
                rts
.dr1
                ;...code....
.dr2
                ;...code....
.dr3
                ;...code....
.dr4
                ;...code....
.dr5
                ;...code....
.dr6
                ;...code....
.dr7
                ;...code....

.drJumpTable
                !word .dr0
                !word .dr1
                !word .dr2
                !word .dr3
                !word .dr4
                !word .dr5
                !word .dr6
                !word .dr7

And a similar one for clearing the rocket. This works fine for putting the rocket on the display, but as we're trying to rotate it, the programm stops after a few iterations.

In Search for an Error

At first it looks like a timing issue and I'm making the program bulletproof regarding any colissions with the interrupt rountine (including using separate pointers during interrupt, while our repaint-flag should really take care of this sufficiently). — To still the same result.

Then I'm testing the program with various parts disabled. While the program stops at the progression of a certain second (the twentieth), it isn't related to the scores/time drawing routine. With all code regarding to this disabled, it's still the same.

After testing all variations, I'm pinning down the culprit: The clearRocket routine, which is in essence a copy of the drawing routine above, but pushing background characters to the reset queue. It looks fine, no error to be found. Nevertheless, let's try the same with static characters, taking the backround evaluation out of the equation. — Still the same.

Mhm, maybe it's related to the reset queue, then? Let's reorder the queues in memory. The same. Let's have the clearRocket push it's screen codes to the drawing queue… — Same result. — Let's have another call to the drawRocket routine, instead. You guess it, still the same result.

At this point, I'm beginning to mistrust our trusty, versatile pointers (PT1 & PT2) and the indirect jump instruction (jmp (PT1)). So I'm coming up with a classic dispatch scheme with self-modification, fixing up the jump address instead of setting up a pointer (here the drawing routine, of course I'm adressing the problematic clearRocket routine first):

                lda rocketDir         ;dispatch on value in rocketDir (0..7)
                rol                   ;×2
                tay                   ;AC -> Y
                lda .drJumpTable+1,y
                sta .drJmp+2
                lda .drJumpTable,y
                sta .drJmp+1
.drJmp          jmp $ffff             ;dummy address (fixed up)
.dr0
                ;...code...
.dr1
                ;...code...
;...
.dr7
                ;...code...

.drJumpTable
                !word .dr0
                !word .dr1
                !word .dr2
                !word .dr3
                !word .dr4
                !word .dr5
                !word .dr6
                !word .dr7

Still the same result.

Despair :-(

At this point, I'm at the end of my wits. The problem is reproducible, but it doesn't make any sense. It's clearly the dispatch, because, if I'm just going straight forward to case 0, without any jump, it works. On the other hand, if I'm dispatching all codes to case 0, the issue raises its nasty head again. ***?

Moreover, the exact point in time, when the program breaks, depends on the exact method and time of entry into the program. If I'm starting it in any automated manner, it's breaking at the exact same point each time, but specific to the method used to run the program. If I'm running it manually (by «LOAD "*",8» ... «RUN», thus starting at a random time depending on my typing skills) the time after which the program freezes or breaks seems rather random.

Is it the emulator???

I know that the emulator is not recreating the video hardware down to the last bit and that the V-SYNC is rather triggered by a smart guess than exact method. Is this rather a collision in the emulator, instead of in our program?

The big problem: I've no other option. VICE stopped working for me completely. I had a working Mac Cocoa build once, but this had a few keyboard issues with the PET emulation and I dumped it for a newer version. Sadly, the new version won't start at all (probably, because I'm not using the very latest iteration of the OS; this is still a production machine, meaning, no Jony Ive-UI). But there's still the X-Windows build! Sadly, it starts a PET in 80-columns mode and ROM 4.0 — and it doesn't expose any configurations, settings or options (nor is there a config-file in the file system). Moreover, the keyboard mapping is far from sane (support of localized keyboard layouts is completly out of question, of course), and I can't get the SHIFT-key working for some of the keys. So I may mount a prg-file, but I can't type «LOAD "*",8» as I am unable to type an asterisk; or the $ sign. (Regardless, if using localized or US mappings, regardless of X-Windows settings, which are, apparently, entirely ignored — as Xpet, inspite of the readme-file, isn't using the X-client at all.) No file loading for me. — Bummer!

VICE

What to do with a broken build?
BTW: Yes, this is the PET-icon reading "commodore basic 128…"

At this point, I'm really on the verge of giving up, rendering this my first failed project in 20 years. (I'm usally not giving up that easily.) I simply don't have the time for searching for a runnable legacy build of VICE (just to check, if the issue is related to the emulation) and finishing the project. — This may be sad, but it's only true. There's still a world out there…

Salvation and Still Open Questions

This said, I'm coming back to the program after a while. Let's give ist a last try. Maybe, just per chance, if we get rid of the role-left instruction?

What, if we split the table in a lo-byte and a hi-byte section, just as we've done it for the srceen-lines table?

displayRocket ;pushes the rocket to the charQueue
                ldx rocketX
                dex                   ;2 pos offset
                dex
                stx qPosY
                ldy rocketX
                dey                   ;2 pos offset
                dey
                sty qPosX
                ldy rocketDir         ;dispatch on value in rocketDir
                lda .drJumpTableHi,y
                sta .drJmp+2
                lda .drJumpTableLo,y
                sta .drJmp+1
.drJmp          jmp $ffff             ;dummy address (fixed up)
.dr0
                ;...code...
.dr1
                ;...code...
.dr2
                ;...code...
.dr3
                ;...code...
.dr4
                ;...code...
.dr5
                ;...code...
.dr6
                ;...code...
.dr7
                ;...code...

.drJumpTableLo
                !byte <.dr0
                !byte <.dr1
                !byte <.dr2
                !byte <.dr3
                !byte <.dr4
                !byte <.dr5
                !byte <.dr6
                !byte <.dr7

.drJumpTableHi
                !byte >.dr0
                !byte >.dr1
                !byte >.dr2
                !byte >.dr3
                !byte >.dr4
                !byte >.dr5
                !byte >.dr6
                !byte >.dr7

Now this actually works. — Flawlessly.   :-)

For unknown reasons.   =%-[]

I still don't know, what the issue really was. However, we've a working program.   :-/

And this is what it looks like in reverse video (at just about 3min run time):

Rocket ship in Personal Computer Space Transactor 2001 (reverse video)

In reverse video phase, 172 seconds into the program.

Try it in in-browser emulation.

*****

Code Listing

And here is our code, in its entirety:

!to "rocket.prg", cbm ;set output file and format

; symbols / constants

ticksPerSecond = 60   ;60: time in game corresponds to NTSC timing
resetQueue = $027A     ;start of cassette buffer, used as a drawing buffer
charQueue = resetQueue+60 ;buffer for screen resets

maxX = 45             ;x-coors max value
maxY = 30             ;y-coors max value

saucerSpeed = 7       ;frames
saucerOffset = 15     ;screen lines y offset

; zero-page
; BASIC input buffer at $23 .. $5A may be reused safely (cf, PET 2001 manual)

gameState = $23       ;0: attract, 1: active
fIRQ  = $24           ;flag to synchronize irq operations
fRepaint = $25        ;flag for video rendering/irq control
ticks = $26           ;ticks counter
videoMask = $27       ;0: normal, $80: reverse (xor-ed)
IRQVector = $28       ;backup of original irq vector (2 bytes)
frameCounter = $2A    ;counter for animations
qPosX = $2B           ;temp x coor for display purpose
qPosY = $2C           ;temp y coor for display purpose
qScreenCode = $2D     ;temp screen code for display purpose
charQueuePtr = $2E    ;pointer to top offset of charQueue
resetQueuePtr = $2F   ;pointer to top offset of charQueue
scoreRepaint = $30    ;flag to request a repaint (buffer to fRepaint)
ran = $31             ;random number (1 byte)
PT1 = $50             ;versatile pointer (2 bytes)
PT2 = $52             ;versatile pointer (2 bytes)
IPT1 = $54            ;versatile pointer for interrupt tasks (2 bytes)
IPT2 = $56            ;versatile pointer for interrupt tasks (2 bytes)

; intro

; insert a tiny BASIC program, calling our code at $044C (1100)
;
; 10 REM PERSONAL COMPUTER SPACE
; 20 REM TRANSACTOR 2001 (NL,2017)
; 30 SYS 1100

                * = $0401

                !byte $1F, $04, $0A, $00, $8F, $20, $50, $45 ; $0401
                !byte $52, $53, $4F, $4E, $41, $4C, $20, $43 ; $0409
                !byte $4F, $4D, $50, $55, $54, $45, $52, $20 ; $0411
                !byte $53, $50, $41, $43, $45, $00, $3F, $04 ; $0419
                !byte $14, $00, $8F, $20, $54, $52, $41, $4E ; $0421
                !byte $53, $41, $43, $54, $4F, $52, $20, $32 ; $0429
                !byte $30, $30, $31, $20, $28, $4E, $4C, $2C ; $0431
                !byte $32, $30, $31, $37, $29, $00, $4A, $04 ; $0439
                !byte $1E, $00, $9E, $20, $31, $31, $30, $30 ; $0441
                !byte $00, $00, $00 ; $0449 .. $044B


; main

                * = $044C
                ; reset / setup
                cld            ;reset BCD flag
                lda #0
                sta fRepaint

setup           ; setup irq vector
                sei
                lda $91
                and #$F0
                cmp #$E0       ;is it ROM 2.0 or higher?
                bne .rom1      ;no, it's ROM 1.0
.rom2           lda $90
                sta IRQVector
                lda $91
                sta IRQVector+1
                lda #<irqRoutine
                sta $90
                lda #>irqRoutine
                sta $91
                jmp .setupDone
.rom1           lda $219
                sta IRQVector
                lda $21A
                sta IRQVector+1
                lda #<irqRoutine
                sta $219
                lda #>irqRoutine
                sta $21A
.setupDone      cli

init
                lda #0
                sta gameState
                sta videoMask
                sta fRepaint
                jsr background
                lda #0
                sta score1
                sta score2
                sta time1
                sta time2
                sta ticks
                sta frameCounter
                sta charQueuePtr
                sta saucerCnt
                sta saucerLegCnt
                
                lda $E844      ; initialize random number from VIA timer 1 
                sta ran

                lda #10
                sta saucerY
                lda #18
                sta saucerX
                jsr displaySaucer
                jsr animateSaucer

                lda #3
                sta rocketDir
                lda #10
                sta rocketX
                lda #12
                sta rocketY
                jsr displayRocket

                lda #1
                sta fRepaint
                sta fIRQ


; main job loop
loop
                lda fIRQ
                bne loop

                lda #0                ;reset top-of-queue pointers
                sta charQueuePtr
                sta resetQueuePtr

                ;manage a frame
                lda #0
                sta scoreRepaint
                lda ticks             ;manage time
                sec
                sbc #ticksPerSecond   ;has a second passed?
                bcc .gameFrame        ;no
                sta ticks
                inc time1
                lda time1
                cmp #$0A
                bne .loopScoresFinal
                lda #0
                sta time1
                inc time2
                lda time2
                cmp #$0A
                bne .loopScoresFinal
                lda #0
                sta time2
                jsr revertVideo
.loopScoresFinal
                lda #1
                sta scoreRepaint

.gameFrame
                jsr saucerHandler
                jsr rocketHandler

.loopIter sei
                lda scoreRepaint
                ora resetQueuePtr
                ora charQueuePtr
                sta fRepaint
.loopEnd        lda #1
                sta fIRQ
                cli
                jmp loop


; irq handling

irqRoutine
                pha                   ;save registers
                txa
                pha
                tya
                pha

                inc ticks             ;manage time
                inc frameCounter

.checkRepaint
                lda fRepaint
                beq .irqDone
                jsr drawResetQueue
                jsr drawScores
                jsr drawCharQueue

.irqDone
                lda #0
                sta fRepaint
                sta fIRQ
                pla                    ;restore register
                tay
                pla
                tax
                pla
                jmp (IRQVector)


; subroutines

background ;fills the screen with stars
                ldx #24
.row            lda screenLinesLo, x
                sta PT1
                lda screenLinesHi, x
                sta PT1+1
                ldy #39
.col            jsr getStar
                sta (PT1), y
                dey
                bpl .col
                dex
                bpl .row
                rts


getStar ;returns a background screen code (in AC) for row X, col Y
                lda starMaskY, x
                beq .blank
                and starMaskX, y
                beq .blank
                lda #$2E ; return a dot
                rts
.blank
                lda #$20 ; return a blank
                rts


; score and time display
; screen locations of score and time numerals
screenAddressScore1 = $8000 + 4*40 + 36
screenAddressScore2 = $8000 + 10*40 + 36
screenAddressTime1 = $8000 + 16*40 + 36
screenAddressTime2 = $8000 + 16*40 + 33

drawScores ;draws scores and time display
                ldy score1
                lda #<screenAddressScore1
                sta IPT1
                lda #>screenAddressScore1
                sta IPT1+1
                jsr drawDigit
                ldy score2
                lda #<screenAddressScore2
                sta IPT1
                lda #>screenAddressScore2
                sta IPT1+1
                jsr drawDigit
                ldy time1
                lda #<screenAddressTime1
                sta IPT1
                lda #>screenAddressTime1
                sta IPT1+1
                jsr drawDigit
                ldy time2
                lda #<screenAddressTime2
                sta IPT1
                lda #>screenAddressTime2
                sta IPT1+1
                jsr drawDigit
                rts

drawDigit ;draws a digit (screen address in IPT1, digit in Y)
                ldx digitOffsets, y
                ldy #0
                lda #4
                sta IPT2
.dgRow          lda digits, x
                eor videoMask  ;adjust for normal/reverse video
                sta (IPT1), y
                inx
                iny
                lda digits, x
                eor videoMask
                sta (IPT1), y
                dec IPT2
                beq .dgDone
                inx
                dey            ;reset y to zero and increment IPT1 by a screen line
                clc
                lda IPT1
                adc #40
                sta IPT1
                bcc .dgRow
                inc IPT1+1
                jmp .dgRow
.dgDone         rts


revertVideo ;reverts the screen video
                lda videoMask
                eor #$80
                sta videoMask
                ldx #24
.rvRow          lda screenLinesLo, x
                sta PT1
                lda screenLinesHi, x
                sta PT1+1
                ldy #39
.rvCol          lda (PT1), y
                eor #$80
                sta (PT1), y
                dey
                bpl .rvCol
                dex
                bpl .rvRow
                rts


; draws chars in charQueue of (screenCode, addrLo, addrHi)*
; self-modifying (sets address at .dcqScreen, sta xxxx)
drawCharQueue
                ldx charQueuePtr          ;get top-of-queue pointer
                beq .dcqDone              ;exit, if empty
                dex
.dcqLoop        lda charQueue, x          ;get screen address hi-byte
                sta .dcqScreen+2          ;fix-up
                dex
                lda charQueue, x          ;get screen address lo-byte
                sta .dcqScreen+1          ;fix-up
                dex
                lda charQueue, x          ;get screen code
                eor videoMask             ;adjust for normal/reverse video
.dcqScreen      sta $ffff                 ;store it (dummy address)
                dex
                bpl .dcqLoop
.dcqDone        rts

; same as above, but for resetQueue
drawResetQueue
                ldx resetQueuePtr
                beq .drqDone
                dex
.drqLoop        lda resetQueue, x
                sta .drqScreen+2
                dex
                lda resetQueue, x
                sta .drqScreen+1
                dex
                lda resetQueue, x
                eor videoMask
.drqScreen      sta $ffff
                dex
                bpl .drqLoop
.drqDone        rts


; a single character 'sprite routine'
; pushes a screen code and address onto the charQueue, if on-screen
pushScreenCode
                lda qPosY
                bmi .pcqDone  ;negative
                cmp #25       ;gte 25 (off-screen to the bottom)?
                bcs .pcqDone
                lda qPosX
                bmi .pcqDone  ;negative
                cmp #40       ;gte 40 (off-screen to the right)?
                bcs .pcqDone

                ldx charQueuePtr
                lda qScreenCode
                sta charQueue, x
                inx
                ldy qPosY
                lda qPosX
                clc
                adc screenLinesLo, y
                sta charQueue, x
                inx
                lda #0
                adc screenLinesHi, y
                sta charQueue, x
                inx
                stx charQueuePtr

.pcqDone        rts

; same as above,but for resetQueue
pushScreenReset
                lda qPosY
                bmi .psrDone  ;negative
                cmp #25       ;gte 25 (off-screen to the bottom)?
                bcs .psrDone
                lda qPosX
                bmi .psrDone  ;negative
                cmp #40       ;gte 40 (off-screen to the right)?
                bcs .psrDone

                ldx resetQueuePtr
                lda qScreenCode
                sta resetQueue, x
                inx
                ldy qPosY
                lda qPosX
                clc
                adc screenLinesLo, y
                sta resetQueue, x
                inx
                lda #0
                adc screenLinesHi, y
                sta resetQueue, x
                inx
                stx resetQueuePtr

.psrDone        rts


random ; a simple random number generator
                lda ran
                ror
                lda ran
                ror
                eor %11011001
                sta ran
                rts


; saucer(s)

saucerHandler
                dec saucerCnt
                bmi .shUpdate
                lda scoreRepaint   ;do we have a score/time update?
                bne .shRedraw      ;yes, redraw the saucers
                jmp .shAnimate     ;just check the animation state
.shRedraw       jmp .shDisplay

.shUpdate       lda #saucerSpeed
                sta saucerCnt
                jsr clearSaucer
                jsr flipSaucer
                jsr clearSaucer
                jsr flipSaucer

                dec saucerLegCnt
                bpl .shMoveY
                jsr random
                and #15
                clc
                adc #7
                sta saucerLegCnt
                lda ran
                and #1
                sta saucerPhaseDir
                ldx #3
                lda ran
                bpl .shAnimSpeed
                ldx #7
.shAnimSpeed    stx saucerPhaseMask
                jsr random
                and #$3F
                beq .shStop
                and #3
                cmp #3
                bne .shSaveDx
                lda #0
.shSaveDx       sta saucerDx
                jsr random
                and #3
                cmp #3
                bne .shSaveDy
                lda #0
.shSaveDy       sta saucerDy

.shMoveY        ldx saucerY
                lda saucerDy
                beq .shMoveX
                cmp #1
                beq .shMoveY1
                inx
                cpx #maxY
                bcc .shSaveY
                ldx #0
                beq .shSaveY
.shMoveY1       dex
                bpl .shSaveY
                ldx #maxY-1
.shSaveY        stx saucerY

.shMoveX        ldx saucerX
                lda saucerDx
                beq .shDisplay
                cmp #1
                beq .shMoveX1
                inx
                cpx #maxX
                bcc .shSaveX
                ldx #0
                beq .shSaveX
.shMoveX1       dex
                bpl .shSaveX
                ldx #maxX-1
.shSaveX        stx saucerX

.shDisplay
                jsr displaySaucer
                jsr flipSaucer
                jsr displaySaucer
                jsr flipSaucer

.shAnimate      lda frameCounter
                and saucerPhaseMask
                bne .shDone
                jsr animateSaucer

.shDone         rts

.shStop
                lda #0
                sta saucerDx
                sta saucerDy
                jmp .shMoveY


flipSaucer ;flips saucerY by saucerOffset
                lda saucerY
                clc
                adc #saucerOffset
                cmp #maxY
                bcc .fpSave
                sec
                sbc #maxY
.fpSave         sta saucerY
                rts


; rocket

rocketHandler
                lda frameCounter
                and #31
                bne .rhDone
                jsr clearRocket
                clc
                lda rocketDir
                adc #1
                and #7
                sta rocketDir
                jsr displayRocket
.rhDone         rts


; display routines (display and clear moving objects)

displaySaucer ;pushes a saucer at saucerX /saucerY onto the charQueue
                ldx saucerY
                dex                ;2 pos offset
                dex
                dex                ;-1 for top row
                stx qPosY
                ldx saucerX
                dex                ;2 pos offset
                dex
                stx qPosX
                lda #$64
                sta qScreenCode
                jsr pushScreenCode  ;$64 at x, y-1
                ldx qPosY
                inx
                stx qPosY
                ldx qPosX
                dex
                stx qPosX
                lda #$73
                sta qScreenCode
                jsr pushScreenCode  ;$73 at x-1, y
                ldx qPosX
                inx
                stx qPosX
                ldx saucerPhase
                lda saucerPhases, x
                sta qScreenCode
                jsr pushScreenCode  ;center code at x, y
                ldx qPosX
                inx
                stx qPosX
                lda #$6B
                sta qScreenCode
                jsr pushScreenCode  ;$6B at x+1, y
                ldx qPosY
                inx
                stx qPosY
                ldx qPosX
                dex
                stx qPosX
                lda #$63
                sta qScreenCode
                jsr pushScreenCode ; $63 at x, y+1
                rts

clearSaucer ;pushes a saucer at saucerX /saucerY onto the resetQueue
                ldx saucerY
                dex                ;2 pos offset
                dex
                dex                ;-1 for top row
                stx qPosY
                ldy saucerX
                dey                ;2 pos offset
                dey
                sty qPosX
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                ldx qPosY
                inx
                stx qPosY
                ldy qPosX
                dey
                sty qPosX
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                ldx qPosY
                ldy qPosX
                iny
                sty qPosX
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                ldx qPosY
                ldy qPosX
                iny
                sty qPosX
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                ldx qPosY
                inx
                stx qPosY
                ldy qPosX
                dey
                sty qPosX
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                rts


animateSaucer ;saucer center animation
                lda saucerPhaseDir
                beq .asLeft
                ldx saucerPhase
                inx
                cpx #10
                bne .asNext
                ldx #0
                beq .asNext
.asLeft
                ldx saucerPhase
                dex
                bpl .asNext
                ldx #9
.asNext         stx saucerPhase
                lda saucerPhases, x
                sta qScreenCode
                ldx saucerX
                dex                ;2 pos offset
                dex
                stx qPosX
                ldx saucerY
                dex                ;2 pos offset
                dex
                stx qPosY
                jsr pushScreenCode
                rts


displayRocket ;pushes the rocket to the charQueue
                ldx rocketX
                dex                   ;2 pos offset
                dex
                stx qPosY
                ldy rocketX
                dey                   ;2 pos offset
                dey
                sty qPosX
                ldy rocketDir         ;dispatch on value in rocketDir
                lda .drJumpTableHi,y
                sta .drJmp+2
                lda .drJumpTableLo,y
                sta .drJmp+1
.drJmp          jmp $ffff             ;dummy address (fixed up)
.dr0
                lda #$1C
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosX
                dex
                stx qPosX
                lda #$67
                sta qScreenCode
                jsr pushScreenCode
                rts
.dr1
                lda #$2F
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosX
                inx
                stx qPosX
                lda #$65
                sta qScreenCode
                jsr pushScreenCode
                rts
.dr2
                lda #$2F
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosY
                dex
                stx qPosY
                lda #$64
                sta qScreenCode
                jsr pushScreenCode
                rts
.dr3
                lda #$1C
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosY
                inx
                stx qPosY
                lda #$63
                sta qScreenCode
                jsr pushScreenCode
                rts
.dr4
                lda #$1C
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosX
                inx
                stx qPosX
                lda #$65
                sta qScreenCode
                jsr pushScreenCode
                rts
.dr5
                lda #$2F
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosX
                dex
                stx qPosX
                lda #$67
                sta qScreenCode
                jsr pushScreenCode
                rts
.dr6
                lda #$2F
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosY
                inx
                stx qPosY
                lda #$63
                sta qScreenCode
                jsr pushScreenCode
                rts
.dr7
                lda #$1C
                sta qScreenCode
                jsr pushScreenCode
                ldx qPosY
                dex
                stx qPosY
                lda #$64
                sta qScreenCode
                jsr pushScreenCode
                rts

.drJumpTableLo
                !byte <.dr0
                !byte <.dr1
                !byte <.dr2
                !byte <.dr3
                !byte <.dr4
                !byte <.dr5
                !byte <.dr6
                !byte <.dr7

.drJumpTableHi
                !byte >.dr0
                !byte >.dr1
                !byte >.dr2
                !byte >.dr3
                !byte >.dr4
                !byte >.dr5
                !byte >.dr6
                !byte >.dr7


clearRocket ;pushes the rocket to the resetQueue
                ldx rocketX
                dex                   ;2 pos offset
                dex
                stx qPosY
                ldy rocketX
                dey                   ;2 pos offset
                dey
                sty qPosX
                ldy rocketDir         ;dispatch on value in rocketDir
                lda .crJumpTableHi,y
                sta .crJmp+2
                lda .crJumpTableLo,y
                sta .crJmp+1
.crJmp          jmp $ffff             ;dummy address (fixed up)
.cr0
                ldy qPosX
                ldx qPosY
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                ldy qPosX
                dey
                sty qPosX
                ldx qPosY
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                rts
.cr1
                ldy qPosX
                ldx qPosY
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                ldy qPosX
                iny
                sty qPosX
                ldx qPosY
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                rts
.cr2
                ldy qPosX
                ldx qPosY
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                ldy qPosX
                ldx qPosY
                dex
                stx qPosY
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                rts
.cr3
                ldy qPosX
                ldx qPosY
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                ldy qPosX
                ldx qPosY
                inx
                stx qPosY
                jsr getStar
                sta qScreenCode
                jsr pushScreenReset
                rts

.crJumpTableLo
                !byte <.cr0
                !byte <.cr1
                !byte <.cr2
                !byte <.cr3
                !byte <.cr1
                !byte <.cr0
                !byte <.cr3
                !byte <.cr2

.crJumpTableHi
                !byte >.cr0
                !byte >.cr1
                !byte >.cr2
                !byte >.cr3
                !byte >.cr1
                !byte >.cr0
                !byte >.cr3
                !byte >.cr2


; variables

score1          !byte 0
score2          !byte 0
time1           !byte 0
time2           !byte 0

saucerX         !byte 0
saucerY         !byte 0
saucerDx        !byte 0
saucerDy        !byte 0
saucerPhase     !byte 0
saucerPhaseDir  !byte 0
saucerPhaseMask !byte 0
saucerCnt       !byte 0
saucerLegCnt    !byte 0

rocketX         !byte 0
rocketY         !byte 0
rocketDir       !byte 0
rocketDx        !byte 0
rocketDy        !byte 0
rocketThrust    !byte 0


; data

starMaskX
                !byte $20, $00, $40, $0A, $08, $01, $82, $00
                !byte $00, $00, $00, $00, $40, $00, $00, $02
                !byte $00, $04, $20, $10, $88, $44, $00, $40
                !byte $00, $01, $20, $00, $00, $42, $14, $00
                !byte $48, $20, $00, $10, $18, $00, $00, $40

starMaskY
                !byte $40, $00, $01, $00, $00, $08, $00, $04
                !byte $00, $40, $00, $02, $00, $00, $01, $00
                !byte $04, $00, $10, $00, $20, $00, $01, $00
                !byte $80

screenLinesHi
                !byte $80
                !byte $80
                !byte $80
                !byte $80
                !byte $80
                !byte $80
                !byte $80
                !byte $81
                !byte $81
                !byte $81
                !byte $81
                !byte $81
                !byte $81
                !byte $82
                !byte $82
                !byte $82
                !byte $82
                !byte $82
                !byte $82
                !byte $82
                !byte $83
                !byte $83
                !byte $83
                !byte $83
                !byte $83

screenLinesLo
                !byte $00
                !byte $28
                !byte $50
                !byte $78
                !byte $A0
                !byte $C8
                !byte $F0
                !byte $18
                !byte $40
                !byte $68
                !byte $90
                !byte $B8
                !byte $E0
                !byte $08
                !byte $30
                !byte $58
                !byte $80
                !byte $A8
                !byte $D0
                !byte $F8
                !byte $20
                !byte $48
                !byte $70
                !byte $98
                !byte $C0

digits
                ;0
                !byte $62,$62
                !byte $61,$E1
                !byte $61,$E1
                !byte $FC,$FE

                ;1
                !byte $20,$6C
                !byte $20,$E1
                !byte $20,$E1
                !byte $20,$E1

                ;2
                !byte $62,$62
                !byte $20,$E1
                !byte $EC,$E2
                !byte $FC,$62

                ;3
                !byte $62,$62
                !byte $20,$E1
                !byte $7C,$FB
                !byte $62,$FE

                ;4
                !byte $7B,$6C
                !byte $61,$E1
                !byte $E2,$FB
                !byte $20,$E1

                ;5
                !byte $62,$62
                !byte $61,$20
                !byte $E2,$FB
                !byte $62,$FE

                ;6
                !byte $7B,$20
                !byte $61,$20
                !byte $EC,$FB
                !byte $FC,$FE

                ;7
                !byte $62,$62
                !byte $20,$E1
                !byte $20,$E1
                !byte $20,$E1

                ;8
                !byte $62,$62
                !byte $61,$E1
                !byte $EC,$FB
                !byte $FC,$FE

                ;9
                !byte $62,$62
                !byte $61,$E1
                !byte $E2,$FB
                !byte $20,$E1

digitOffsets
                !byte 0, 8, 16, 24, 32, 40, 48, 56, 64, 72

saucerPhases
                !byte $20,$65,$54,$47,$42,$5D,$48,$59,$67,$20

(Assembles to 1,736 bytes of binary code.)

 

— Stay tuned! —

 

Next:  Episode 8: Space Commander

Previous:  Episode 6: Attractive Saucers

Back to the index.

— This series is part of Retrochallenge 2017/04. —