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:
▶ 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):
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!
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):
▶ 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. —