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 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):
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!
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):
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.
April 2017, Vienna, Austria
www.masswerk.at – contact me.
— This series is part of Retrochallenge 2017/04. —