******************************************************************************** * ABM for the Apple II, by Silas Warner * * Copyright 1980 Muse Software * * * * This is the main game code. It was extracted from a .NIB image of the 13- * * sector original disk. * ******************************************************************************** * Disassembly by Andy McFadden, using 6502bench SourceGen v1.7.1. * * Last updated 2020/08/13 * * * * This program makes heavy use of inline data following JSRs. These are * * automatically formatted by an extension script based on the label prefix: * * * * InA_* : JSR is followed by a two-byte address * * InXY_*: JSR is followed by a two-byte text screen position * * InS_* : JSR is followed by a null-terminated high-ASCII string * * * * If you rename one of these labels, do not change the prefix, or the inline * * formatting will vanish. * ******************************************************************************** * The state of inbound missiles are recorded from $4200-4cff. Each missile * * gets its own page, so there are a maximum of 11 inbound missiles. Inline * * references to addresses at $42xx are transparently remapped to the * * appropriate page. * * * * The first 150-ish bytes of each page record the column of the missile trail * * at the corresponding row, making it easy to erase the trail when the missile * * is destroyed. The last 30 bytes of each page holds the missile state * * (position, explosion progress, etc). * * * * $4d00-4dff holds the trail data and state for the ABM. Only one ABM may be * * launched at a time. * ******************************************************************************** * The Applesoft floating-point math routines are used heavily. The basic * * format of a float is 40 bits: * * * * EEEEEEEE SMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM * * * * The exponent uses a bias of 129 (IEEE 754 uses 127). For example, the * * number +25.0 would be: * * * * Applesoft: 0x8548000000: [10000101] [0][1001000 00000000 ...] * * IEEE 754 : 0x41c80000: [0][1000001 1][1001000 00000000 ...] * ******************************************************************************** TOK_CALL .eq $8c {const} O_DET_SIZE .eq $f4 {const} ;missile detonation size (96-176) O_DET_STAGE1 .eq $f5 {const} ;tracks detonation progress (white) O_DET_STAGE2 .eq $f6 {const} ;tracks detonation progress (black) O_ROW .eq $fa {const} ;current row, starts at $00; $ff=inactive O_DET_ROW .eq $fb {const} ;row at which missile detonates O_TARGET_CITY .eq $fc {const} ;targeted city (0-5) O_MIRV_ROW .eq $fd {const} ;row at which missile MIRVs ($00=never) missile_ptr .eq $17 {addr/2} ;points to page with missile state BAS_HCOLOR1 .eq $1c ;hi-res color mask MON_WNDTOP .eq $22 ;top of scroll window MON_CH .eq $24 ;cursor horizontal displacement MON_CV .eq $25 ;cursor vertical displacement BAS_HBASL .eq $26 ;base address for hi-res drawing (lo part) MON_BASL .eq $28 ;base address for text output (lo) BAS_HMASK .eq $30 ;hi-res graphics on-the-fly bit mask MON_INVFLAG .eq $32 ;text mask (255=normal, 127=flash, 63=inv) sfx_counter .eq $4a {addr/2} ;speaker click delay BAS_FAC .eq $9d {addr/6} ;floating point accumulator (6b) strobe_thing? .eq $cb ;not initialized, never used BAS_SCALE .eq $e7 ;hi-res graphics scale factor STACK .eq $0100 {addr/256} ;hardware stack ReadPdlX .eq $0300 ;reads paddle for cursor X position ReadPdlY .eq $0303 ;reads paddle for cursor Y position GetOuterBtn .eq $0306 ;checks button that fires outer/center launchers GetInnerBtn .eq $0309 ;checks button that fires inner launchers missile_cur_xc .eq $42e2 {addr/5} ;fp: current hi-res column of missile (0-139) missile_target_xc .eq $42e8 {addr/5} ;fp: target hi-res column of missile (10-135) missile_xmove .eq $42ee {addr/5} ;fp: per-frame horizontal movement abm_trail .eq $4d00 {addr/160} ;pixel columns for ABM trail abm_cur_xc .eq $4de2 {addr/5} ;fp: current ABM column (0-139) abm_target_xc .eq $4de8 {addr/5} ;fp: temp storage for ABM target column (0-139) abm_xmove .eq $4dee {addr/5} ;fp: ABM per-frame horizontal movement abm_max_size .eq $4df4 ;max size of exploding ABM ($50/$70) abm_det_size1 .eq $4df5 ;current size of ABM explosion (white) abm_det_size2 .eq $4df6 ;current size of ABM explosion (black) abm_row .eq $4dfa ;current row of ABM, or $ff if none fired abm_target_row .eq $4dfb ;row at which ABM will detonate RWTS_BUFFER .eq $73ef {addr/256} ;RWTS sector buffer HIGH_SCORE .eq $746f {addr/5} ;fp: high score RWTS_PARM .eq $77e8 {addr/17} ;RWTS parameter table DOS_32K_RWTS .eq $7d00 ;RWTS entry point KBD .eq $c000 ;R last key pressed + 128 KBDSTRB .eq $c010 ;RW keyboard strobe SPKR .eq $c030 ;RW toggle speaker STROBE .eq $c040 ;R game I/O strobe TXTSET .eq $c051 ;RW display text TXTPAGE1 .eq $c054 ;RW display page 1 LORES .eq $c056 ;RW display lo-res graphics BAS_GIVAYF .eq $e2f2 ;convert 16-bit (A,Y) to float, store in FAC BAS_SNGFLT .eq $e301 ;convert 8-bit int (Y) to FLOAT BAS_FSUB .eq $e7a7 ;FAC = (Y,A) - FAC BAS_FADD .eq $e7be ;FAC = (Y,A) + FAC BAS_CON_ONE .eq $e913 ;constant value 1.0 BAS_FMULT .eq $e97f ;FAC = (Y,A) * FAC BAS_LOAD_ARG_FROM_YA .eq $e9e3 ;unpack 5-byte val at (Y,A) into ARG BAS_MUL10 .eq $ea39 ;multiply FAC by 10 BAS_CON_TEN .eq $ea50 ;constant value 10.0 BAS_FDIV .eq $ea66 ;FAC = (Y,A) / FAC BAS_LOAD_FAC_FROM_YA .eq $eaf9 ;unpack 5-byte val at (Y,A) into FAC BAS_STORE_FAC_AT_YX_ROUNDED .eq $eb2b ;round FAC, store at (Y,X) BAS_SIGN .eq $eb82 ;test FAC sign; <,=,> 0 -> A={-1,0,1} BAS_FLOAT .eq $eb93 ;convert value in A to float in FAC BAS_ABS .eq $ebaf ;changes sign of FAC to + BAS_FCOMP .eq $ebb2 ;cmp (Y,A) with FAC; <,=,> -> A={1,0,-1} BAS_QINT .eq $ebf2 ;convert FAC to big-endian int in FAC+1..FAC+4 BAS_INT .eq $ec23 ;FAC = floor(FAC) BAS_LINPRT .eq $ed24 ;print float at (A,X) as decimal integer BAS_PRINT_FAC .eq $ed2e ;print FAC to screen BAS_CON_HALF .eq $ee64 ;constant value 0.5 BAS_FPWRT .eq $ee97 ;compute exponentiation BAS_NEGOP .eq $eed0 ;negate value in FAC BAS_RND .eq $efae ;generate random number BAS_CON_PI_HALF .eq $f066 ;constant value PI/2 BAS_CON_PI_DOUB .eq $f06b ;constant value PI*2 BAS_QUARTER .eq $f070 ;constant value 0.25 BAS_HGR .eq $f3e2 ;switch to hi-res mixed-mode and clear BAS_HPOSN .eq $f411 ;set hi-res position; horiz=(Y,X) vert=A BAS_HPLOT0 .eq $f457 ;plot point; horiz=(Y,X), vert=A BAS_HGLIN .eq $f53a ;draw line from last point to (A,X),Y BAS_DRAW0 .eq $f601 ;draw a shape from addr (Y,X) BAS_HCOLOR .eq $f6e9 {addr/4} ;set hi-res color to value in X MON_SETTXT .eq $fb39 ;set screen to text mode MON_VTAB .eq $fc22 ;tab to row specified in Acc MON_HOME .eq $fc58 ;clear screen and reset text output to top-left MON_WAIT .eq $fca8 ;delay for (26 + 27*Acc + 5*(Acc*Acc))/2 cycles MON_CROUT .eq $fd8e ;print a carriage return MON_COUT .eq $fded ;print Acc to output device via $36-37 MON_COUT1 .eq $fdf0 ;print Acc to screen .org $0801 ******************************************************************************** * Applesoft BASIC header. When run, calls assembly-language program at $810. * ******************************************************************************** 0801: 0b 08 .dd2 $080b ;pointer to start of next line 0803: 01 00 .dd2 $0001 ;line 1 0805: 8c .dd1 TOK_CALL ;"CALL 2064" 0806: 32 30 36 34 .str ‘2064’ 080a: 00 .dd1 $00 080b: 00 08 .dd2 $0800 ;(garbage pointer) 080d: 02 00 .dd2 $0002 ;line 2 080f: 8d .dd1 $8d ;junk ******************************************************************************** * Program entry point. * ******************************************************************************** 0810: 20 8c 09 Start jsr ReadHighScore ;read high score from disk 0813: 8d 56 c0 Restart sta LORES ;switch to lo-res graphics (why?) 0816: 8d 51 c0 sta TXTSET ;text mode 0819: 8d 54 c0 sta TXTPAGE1 ;text page 1 ; Draw the scroll-up ABM logo. 081c: a9 05 lda #$05 ;draw the logo animation 5x 081e: 8d 44 08 sta :_count+1 0821: 20 39 fb :LogoLoop jsr MON_SETTXT ;text mode, full-screen window 0824: 20 58 fc jsr MON_HOME ;clear screen 0827: 20 42 15 jsr InXY_SetTextPos 082a: 17 .dd1 23 ;row 23 (bottom of screen) 082b: 00 .dd1 0 ;col 0 082c: a2 ff ldx #$ff 082e: e8 :LogoLineLoop inx 082f: bd 05 09 lda abm_logo,x ;get horizontal offset 0832: 10 06 bpl :DrawSpc ;if not negative, draw a space 0834: 20 8e fd jsr MON_CROUT ;print CR to scroll text up one line 0837: 4c 2e 08 jmp :LogoLineLoop 083a: f0 07 :DrawSpc beq :_count ;if zero, we're all done 083c: a8 tay 083d: a9 20 lda #‘ ’ ;inverse space 083f: 91 28 sta (MON_BASL),y ;store on screen 0841: d0 eb bne :LogoLineLoop 0843: a9 00 :_count lda #$00 ;count is stored in self-mod operand 0845: f0 06 beq :LogoDone ;we're done, branch 0847: ce 44 08 dec :_count+1 084a: 4c 21 08 jmp :LogoLoop 084d: 20 42 15 :LogoDone jsr InXY_SetTextPos 0850: 05 .dd1 5 ;row 5 (6th row) 0851: 0a .dd1 10 ;col 10 (11th position) 0852: 20 2b 15 jsr InS_PrintString ;print high score notice 0855: c8 c9 c7 c8+ .zstr “HIGH SCORE IS ” 0864: 20 73 15 jsr InA_LoadFloat ;copy high score into FAC 0867: 31 0c .dd2 high_score 0869: 20 2e ed jsr BAS_PRINT_FAC ;print FAC 086c: 20 42 15 jsr InXY_SetTextPos 086f: 17 .dd1 23 ;row 23 (bottom of screen) 0870: 00 .dd1 0 0871: 20 2b 15 jsr InS_PrintString ;print multi-line string (scrolls 3x) 0874: a0 a0 a0 a0+ .zstr “ COPYRIGHT 1980, MUSE SOFTWARE”,$8d,“ ALL RIGHTS” + “ RESERVED”,$8d,$8d,“ PRESS A KEY TO START.” ; Wait for a key to be hit. If enough time passes, go into attract mode. 08d6: 8d 10 c0 sta KBDSTRB ;clear keyboard strobe 08d9: a2 f0 ldx #$f0 08db: 8e ef 0b stx temp+1 ;init delay counter to $f0f0 08de: 8e ee 0b stx temp 08e1: ee ee 0b :WaitForStart inc temp 08e4: d0 09 bne :ChkStart 08e6: ee ef 0b inc temp+1 08e9: d0 04 bne :ChkStart 08eb: a9 ff lda #$ff ;demo is playing 08ed: d0 10 bne :SetMode ;(always) 08ef: ad 00 c0 :ChkStart lda KBD ;key hit? 08f2: 30 06 bmi :KeyHit ;yes, branch 08f4: ca dex 08f5: d0 f8 bne :ChkStart 08f7: 4c e1 08 jmp :WaitForStart 08fa: 8d 10 c0 :KeyHit sta KBDSTRB 08fd: a9 00 lda #$00 ;player is playing 08ff: 8d f0 0b :SetMode sta auto_play_flag 0902: 4c d4 09 jmp StartGame ; ; Text "ABM" logo. Values are horizontal offsets where an inverse ' ' should be ; drawn. The result looks like this: ; ; ### ##### ## ## ; ## ## ## ## ### ### ; ## ## ## ## ####### ; ## ## ## ## ## # ## ; ## ## ##### ## ## ; ####### ## ## ## ## ; ## ## ## ## ## ## ; ## ## ## ## ## ## ; ## ## ##### ## ## 0905: 09 0a 0b 11+ abm_logo .bulk $09,$0a,$0b,$11,$12,$13,$14,$15,$1b,$1c,$20,$21,$c8 0912: 08 09 0b 0c+ .bulk $08,$09,$0b,$0c,$11,$12,$15,$16,$1b,$1c,$1d,$1f,$20,$21,$c8 0921: 07 08 0c 0d+ .bulk $07,$08,$0c,$0d,$11,$12,$16,$17,$1b,$1c,$1d,$1e,$1f,$20,$21,$c8 0931: 07 08 0c 0d+ .bulk $07,$08,$0c,$0d,$11,$12,$15,$16,$1b,$1c,$1e,$20,$21,$c8 093f: 07 08 0c 0d+ .bulk $07,$08,$0c,$0d,$11,$12,$13,$14,$15,$1b,$1c,$20,$21,$c8 094d: 07 08 09 0a+ .bulk $07,$08,$09,$0a,$0b,$0c,$0d,$11,$12,$15,$16,$1b,$1c,$20,$21,$c8 095d: 07 08 0c 0d+ .bulk $07,$08,$0c,$0d,$11,$12,$16,$17,$1b,$1c,$20,$21,$c8 096a: 07 08 0c 0d+ .bulk $07,$08,$0c,$0d,$11,$12,$15,$16,$1b,$1c,$20,$21,$c8 0977: 07 08 0c 0d+ .bulk $07,$08,$0c,$0d,$11,$12,$13,$14,$15,$1b,$1c,$20,$21,$c8 0985: c8 c8 c8 c8+ .bulk $c8,$c8,$c8,$c8,$c8,$c8 098b: 00 .dd1 $00 ;zero indicates end of list ; ; Reads the high score from disk. ; 098c: a9 01 ReadHighScore lda #$01 ;cmd=read 098e: 20 b1 09 jsr AccessHighScore ;read high score sector 0991: a0 74 ldy #>HIGH_SCORE 0993: a9 6f lda #<HIGH_SCORE 0995: 20 f9 ea jsr BAS_LOAD_FAC_FROM_YA ;copy score from sector buffer to FAC 0998: 20 ac 15 jsr InA_StoreFloat ;store rounded FAC at address below 099b: 31 0c .dd2 high_score 099d: 60 rts ; ; Writes the high score to disk. ; 099e: a9 01 WriteHighScore lda #$01 ;cmd=read 09a0: 20 b1 09 jsr AccessHighScore ;read high score sector 09a3: 20 73 15 jsr InA_LoadFloat ;copy score into FAC 09a6: 31 0c .dd2 high_score 09a8: a0 74 ldy #>HIGH_SCORE 09aa: a2 6f ldx #<HIGH_SCORE 09ac: 20 2b eb jsr BAS_STORE_FAC_AT_YX_ROUNDED ;copy score from FAC to sector buffer 09af: a9 02 lda #$02 ;cmd=write; fall through ; ; Common code for high score I/O. ; 09b1: 8d f4 77 AccessHighScore sta RWTS_PARM+12 09b4: a9 02 lda #$02 ;track 2 09b6: 8d ec 77 sta RWTS_PARM+4 09b9: a9 09 lda #$09 ;sector 9 09bb: 8d ed 77 sta RWTS_PARM+5 09be: a9 00 lda #$00 ;volume 0 (matches any) 09c0: 8d eb 77 sta RWTS_PARM+3 09c3: a9 ef lda #<RWTS_BUFFER ;set buffer address 09c5: 8d f0 77 sta RWTS_PARM+8 09c8: a9 73 lda #>RWTS_BUFFER 09ca: 8d f1 77 sta RWTS_PARM+9 09cd: a0 e8 ldy #<RWTS_PARM ;use DOS RWTS parameter list 09cf: a9 77 lda #>RWTS_PARM 09d1: 4c 00 7d jmp DOS_32K_RWTS ;write the sector ******************************************************************************** * Initializes state for a new game. * ******************************************************************************** 09d4: 20 39 fb StartGame jsr MON_SETTXT ;set text mode, reset window 09d7: 20 58 fc jsr MON_HOME ;clear the screen 09da: 20 e2 f3 jsr BAS_HGR ;switch to HGR w/window, and clear graphics 09dd: 8d 10 c0 sta KBDSTRB ;clear keyboard strobe 09e0: a2 03 ldx #$03 09e2: 20 ec f6 jsr BAS_HCOLOR+3 ;color=white ; Init game state. 09e5: a9 46 lda #70 09e7: 8d 79 13 sta cursor_x ;init cursor position 09ea: 8d 7a 13 sta cursor_y ;roughly middle of screen (X gets doubled) 09ed: 8d 81 12 sta auto_curs_x 09f0: 8d 82 12 sta auto_curs_y 09f3: a2 01 ldx #$01 09f5: 86 e7 stx BAS_SCALE ;set scale for shape drawing 09f7: a9 00 lda #$00 09f9: 8d ee 0b sta temp 09fc: 85 17 sta missile_ptr 09fe: 8d f3 0b sta abm_fired_cnt ;init score counters 0a01: 8d f4 0b sta abm_fired_cnt+1 0a04: 8d f1 0b sta mssl_destroy_cnt 0a07: 8d f2 0b sta mssl_destroy_cnt+1 0a0a: 8d 43 18 sta sound_fx_mod ;init sound effects ; Draw cities and launchers. 0a0d: ae ee 0b :DrawLoop ldx temp ;get start position 0a10: a0 00 ldy #$00 0a12: a9 9f lda #159 ;row 159 (just above text area) 0a14: 20 57 f4 jsr BAS_HPLOT0 ;plot a point 0a17: 20 7d 13 jsr InA_DrawShape ;draw a city at that position 0a1a: 4e 14 .dd2 shape_city 0a1c: ad ee 0b lda temp 0a1f: c9 fa cmp #250 ;did we draw the rightmost city? 0a21: b0 31 bcs :DrawText ;yes, done drawing 0a23: 69 1e adc #30 ;move right 30 pixels 0a25: 8d ee 0b sta temp 0a28: aa tax 0a29: a0 00 ldy #$00 0a2b: a9 9d lda #157 0a2d: 20 57 f4 jsr BAS_HPLOT0 ;plot a point 0a30: 20 7d 13 jsr InA_DrawShape ;draw the bottom of the launcher 0a33: b9 14 .dd2 shape_laun_bot 0a35: ad ee 0b lda temp 0a38: 18 clc 0a39: 69 06 adc #6 ;move right six pixels 0a3b: aa tax 0a3c: a0 00 ldy #$00 0a3e: a9 98 lda #152 ;come up 7 lines 0a40: 20 57 f4 jsr BAS_HPLOT0 0a43: 20 7d 13 jsr InA_DrawShape ;draw the top of the launcher 0a46: dd 14 .dd2 shape_laun_top 0a48: ad ee 0b lda temp 0a4b: 18 clc 0a4c: 69 14 adc #20 ;move right 20 pixels 0a4e: 8d ee 0b sta temp 0a51: 4c 0d 0a jmp :DrawLoop ;draw the next set ; Draw text area: city names, missile ratings, score. 0a54: 20 42 15 :DrawText jsr InXY_SetTextPos 0a57: 14 .dd1 20 0a58: 00 .dd1 0 0a59: 20 2b 15 jsr InS_PrintString 0a5c: a0 c2 cf d3+ .zstr “ BOS NYC PHL BAL WSH RCH”,$8d,“ 1 5 ” + “ 1 5 1”,$8d,“ ENEMY 0 0 ABM'S” 0ac3: a9 16 lda #22 ;set top of text window 0ac5: 85 22 sta MON_WNDTOP 0ac7: ad f0 0b lda auto_play_flag ;are we in attract mode? 0aca: f0 09 beq :NotAuto ;no, branch 0acc: a9 64 lda #$64 ;replace speaker click with harmless store 0ace: 8d 76 13 sta _Click+1 ; (attract mode is silent) 0ad1: a0 14 ldy #20 ;start with high spawn probability 0ad3: d0 07 bne :SetProb ;(always) 0ad5: a9 30 :NotAuto lda #$30 0ad7: 8d 76 13 sta _Click+1 0ada: a0 78 ldy #120 ;start with low spawn probability 0adc: 20 01 e3 :SetProb jsr BAS_SNGFLT ;convert Y-reg to float 0adf: 20 ac 15 jsr InA_StoreFloat ;save it 0ae2: f5 0b .dd2 spawn_prob 0ae4: a2 02 ldx #$02 0ae6: 20 ec f6 jsr BAS_HCOLOR+3 ;color=purple 0ae9: a2 00 ldx #$00 0aeb: a0 00 ldy #$00 0aed: a9 9f lda #159 0aef: 20 57 f4 jsr BAS_HPLOT0 ;plot at (0,159) 0af2: a2 01 ldx #1 0af4: a9 16 lda #22 0af6: a0 9f ldy #159 0af8: 20 3a f5 jsr BAS_HGLIN ;draw line to (278,159) 0afb: a2 03 ldx #$03 0afd: 20 ec f6 jsr BAS_HCOLOR+3 ;color=white ; Make a copy of the original row 155, so that as the game goes on we can ; compare the screen contents to the original state to evaluate damage. 0b00: a2 00 ldx #$00 0b02: a0 00 ldy #$00 0b04: a9 9b lda #155 0b06: 20 11 f4 jsr BAS_HPOSN ;get address of (0,155) 0b09: a0 00 ldy #$00 0b0b: b1 26 :Copy155Loop lda (BAS_HBASL),y 0b0d: 99 52 0b sta orig_row_155,y 0b10: c8 iny 0b11: c0 28 cpy #40 ;copy full 40-byte line 0b13: 90 f6 bcc :Copy155Loop ; Init launcher status to undamaged. 0b15: a0 00 ldy #$00 0b17: a9 98 lda #152 0b19: 99 7a 0b :InitLauLoop sta launcher_status,y 0b1c: c8 iny 0b1d: c0 05 cpy #$05 ;done with all five? 0b1f: d0 f8 bne :InitLauLoop ; Init city status to undamaged. 0b21: a9 ff lda #$ff 0b23: a0 00 ldy #$00 0b25: 99 4a 18 :InitCitLoop sta city_status,y 0b28: c8 iny 0b29: c0 06 cpy #$06 ;done with all six? 0b2b: d0 f8 bne :InitCitLoop ; Initialize missile data area from $4100-4dff. This is (mostly) zeroed out. 0b2d: a9 41 lda #$41 0b2f: 85 18 sta missile_ptr+1 0b31: e6 18 :InitMslLoop inc missile_ptr+1 0b33: a5 18 lda missile_ptr+1 0b35: c9 4e cmp #$4e ;reached last page? 0b37: 90 03 bcc :NotDone ;not yet, keep going 0b39: 4c 38 0c jmp GameLoop ;start the game 0b3c: 20 41 0b :NotDone jsr :InitPage 0b3f: d0 f0 bne :InitMslLoop ;(always) ; Zero the page pointed to by $17-18 to zeroes, then store $FF at +$FA. 0b41: a9 00 :InitPage lda #$00 0b43: a8 tay 0b44: 85 17 sta missile_ptr ;set low byte of pointer to zero 0b46: 91 17 :ZeroLoop sta (missile_ptr),y ;set 256 bytes to zero 0b48: c8 iny 0b49: d0 fb bne :ZeroLoop 0b4b: a0 fa ldy #O_ROW ;set missile status to inactive 0b4d: a9 ff lda #$ff 0b4f: 91 17 sta (missile_ptr),y 0b51: 60 rts ; ; Contents of hi-res row 155, which is 4 lines above the text window. The ; values here are compared against the current state to determine whether a city ; has been damaged. (When the on-screen value of the city center is zero, the ; city is considered destroyed.) 0b52: 00 00 00 00+ orig_row_155 .fill 40,$00 ; ; Launcher status. ; ; All values are initialized to 152, possibly to indicate the hi-res row from ; which ABMs launch, but in practice this is treated as a bool (zero/nonzero) ; indicating whether the launcher is alive. It's also set to zero when the ; launcher fires. 0b7a: 00 00 00 00+ launcher_status .bulk $00,$00,$00,$00,$00 ; ; Appears to be hi-res pixel column (0-255 of 279) from which ABMs launch from ; the five launchers. 0b7f: 24 56 88 ba+ launcher_posns .bulk $24,$56,$88,$ba,$ec ; ; After an ABM has finished exploding, check the state of all launchers of the ; same class (inner vs. outer). Redraw it if necessary, or mark it as disabled ; if it has been destroyed. The state of the launcher we just used will be $00 ; (inactive), so the goal here is to decide if we want to re-enable it. ; ; We do this *after* the ABM has exploded, which means a "dead" launcher can ; fire one last missile before it's marked as inactive. (BUG?) ; 0b84: a0 fe CheckOuterLau ldy #$fe ;start with even-numbered launchers 0b86: d0 02 bne :ChkLauLoop ;(always) 0b88: a0 ff CheckInnerLau ldy #$ff ;start with odd-numbered launchers 0b8a: c8 :ChkLauLoop iny ;increment twice so we only check odds or evens 0b8b: c8 iny 0b8c: 8c e7 0b sty :_NextLauncher+1 ;save index 0b8f: b9 7a 0b lda launcher_status,y ;check status 0b92: d0 52 bne :_NextLauncher ;launcher is enabled, skip 0b94: a2 03 ldx #$03 0b96: 20 ec f6 jsr BAS_HCOLOR+3 ;color=white 0b99: ae e7 0b ldx :_NextLauncher+1 ;get index 0b9c: bd 7f 0b lda launcher_posns,x ;get pixel column 0b9f: aa tax 0ba0: a0 00 ldy #$00 0ba2: a9 9b lda #155 0ba4: 20 11 f4 jsr BAS_HPOSN ;get address for (xc,155) 0ba7: b1 26 lda (BAS_HBASL),y ;grab a byte at that address 0ba9: d0 0a bne :StillAlive ;nonzero, launcher is still alive 0bab: ac e7 0b ldy :_NextLauncher+1 ;get index 0bae: a9 00 lda #$00 0bb0: 99 7a 0b sta launcher_status,y ;mark launcher as dead (redundant) 0bb3: f0 31 beq :_NextLauncher ;(always) 0bb5: d9 52 0b :StillAlive cmp orig_row_155,y ;is launcher mid-line undamaged? 0bb8: f0 16 beq :NoDamage ;yes, no need to redraw bottom 0bba: ac e7 0b ldy :_NextLauncher+1 0bbd: b9 7f 0b lda launcher_posns,y ;get the horizontal offset 0bc0: 38 sec 0bc1: e9 06 sbc #$06 ;slide back 6 pixels 0bc3: aa tax 0bc4: a0 00 ldy #$00 0bc6: a9 9d lda #157 0bc8: 20 57 f4 jsr BAS_HPLOT0 ;plot a point at xc,157 0bcb: 20 7d 13 jsr InA_DrawShape ;draw the launcher bottom 0bce: b9 14 .dd2 shape_laun_bot 0bd0: ae e7 0b :NoDamage ldx :_NextLauncher+1 0bd3: bd 7f 0b lda launcher_posns,x ;get horizontal position 0bd6: 9d 7a 0b sta launcher_status,x ;mark launcher as alive (posn is always nonzer) 0bd9: aa tax ;copy horiz position to X-reg 0bda: a0 00 ldy #$00 0bdc: a9 98 lda #152 0bde: 20 57 f4 jsr BAS_HPLOT0 ;plot a point at xc,152 0be1: 20 7d 13 jsr InA_DrawShape ;draw the launcher top 0be4: dd 14 .dd2 shape_laun_top 0be6: a0 00 :_NextLauncher ldy #$00 ;get current value of counter (self-mod) 0be8: c0 05 cpy #$05 ;processed all appropriate launchers? 0bea: 90 9e bcc :ChkLauLoop ;not yet, branch 0bec: 60 rts 0bed: 00 page_counter .dd1 $00 ;game loop counts 0-10 for $4200-4c00 0bee: 00 00 temp .dd2 $0000 ;general-purpose temporary storage 0bf0: 00 auto_play_flag .dd1 $00 ;bool 00/ff: are we in auto-play (attract) mode? mssl_destroy_cnt 0bf1: 00 00 .dd2 $0000 ;number of missiles destroyed 0bf3: 00 00 abm_fired_cnt .dd2 $0000 ;number of ABMs fired 0bf5: 88 48 00 00+ spawn_prob .bulk $88,$48,$00,$00,$00 ;fp: probability of missile spawn (lower=more likely) 0bfa: 88 00 00 00+ const_128 .bulk $88,$00,$00,$00,$00 ;fp: 128.0 0bff: 85 48 00 00+ const_25 .bulk $85,$48,$00,$00,$00 ;fp: 25.0 0c04: 87 48 00 00+ const_100 .bulk $87,$48,$00,$00,$00 ;fp: 100.0 0c09: 83 40 00 00+ const_6 .bulk $83,$40,$00,$00,$00 ;fp: 6.0 0c0e: 00 00 00 00+ .junk 10 0c18: 00 00 00 00+ xc_delta_temp .fill 5,$00 ;fp: temp storage for missile/abm delta X 0c1d: 00 00 00 00+ score_m_temp .fill 5,$00 ;fp: temporary storage while computing score 0c22: 00 00 00 00+ .junk 10 0c2c: 00 00 00 00+ score_temp .fill 5,$00 ;fp: temporary storage while computing score 0c31: 00 00 00 00+ high_score .fill 5,$00 ;fp: high score 0c36: 00 00 .junk 2 ******************************************************************************** * Main game loop. * * * * Each iteration, the page counter is incremented, cycling through 0-10. On * * cycles 0 through 5 we check to see if we want to spawn a missile. 6 through * * 10 are reserved for MIRV spawns. (When a missile splits, the initial slot * * plus 5 additional slots means all 6 cities can be targeted if no other MIRVs * * are active.) * * * * Missiles are only updated when it's their turn, so each missile is advanced * * once every 11 frames. * ******************************************************************************** 0c38: a5 cb GameLoop lda strobe_thing? ;(no idea what the point of this is) 0c3a: 4d 40 c0 eor STROBE 0c3d: 85 cb sta strobe_thing? 0c3f: ee ed 0b inc page_counter 0c42: ad ed 0b lda page_counter 0c45: c9 0b cmp #11 ;did counter hit 11? 0c47: 90 09 bcc :SkipSound ;not yet, branch 0c49: 20 5c 13 jsr PlaySounds ;update sound effects 0c4c: a9 00 lda #$00 ;reset counter 0c4e: 8d ed 0b sta page_counter 0c51: 18 clc 0c52: 69 42 :SkipSound adc #$42 ;set pointer to $4200, $4300, ... $4C00 0c54: 85 18 sta missile_ptr+1 0c56: a9 00 lda #$00 ;set pointer low byte 0c58: 85 17 sta missile_ptr 0c5a: a0 fa ldy #O_ROW 0c5c: b1 17 lda (missile_ptr),y ;check current row 0c5e: c9 ff cmp #$ff ;is it $FF? 0c60: f0 03 beq :SkipUpdate ;yes, missile is inactive; branch 0c62: 4c fe 0c jmp :UpdateMissile ;go update this missile 0c65: ad ed 0b :SkipUpdate lda page_counter 0c68: c9 06 cmp #$06 ;are we on page 0-5? 0c6a: b0 0f bcs :NoSpawn ;no, don't spawn new missile here 0c6c: 20 ae ef jsr BAS_RND ;generate random [0,1) 0c6f: 20 91 15 jsr InA_MulFloat ;multiply by spawn probability 0c72: f5 0b .dd2 spawn_prob 0c74: 20 f2 eb jsr BAS_QINT ;convert to int in FAC1..FAC4 0c77: a5 a1 lda BAS_FAC+4 ;check least-significant byte 0c79: f0 03 beq :DoSpawn ;if it's zero, spawn a new missile 0c7b: 4c 56 13 :NoSpawn jmp JmpUpdateAbm ; Create a new missile in this slot. Start by increasing the probably of ; missile and MIRV spawns. This reaches its maximum after 110 missiles. 0c7e: 20 73 15 :DoSpawn jsr InA_LoadFloat ;get spawn probability in FAC 0c81: f5 0b .dd2 spawn_prob 0c83: 20 a6 15 jsr InA_CmpFloat ;compare to 10.0 0c86: 50 ea .dd2 BAS_CON_TEN 0c88: 30 0d bmi :NoDec ;FAC < 10, don't reduce further; branch 0c8a: 20 8b 15 jsr InA_SubFloat ;FAC = 1 - FAC 0c8d: 13 e9 .dd2 BAS_CON_ONE 0c8f: 20 d0 ee jsr BAS_NEGOP ;negate value 0c92: 20 ac 15 jsr InA_StoreFloat ;store it (essentially "FAC = FAC - 1") 0c95: f5 0b .dd2 spawn_prob 0c97: 20 ae ef :NoDec jsr BAS_RND ;random number [0,1) 0c9a: 20 91 15 jsr InA_MulFloat ;multiply by 128.0 [0,128) 0c9d: fa 0b .dd2 const_128 0c9f: 20 85 15 jsr InA_AddFloat ;add 6.0 (6,134) 0ca2: 09 0c .dd2 const_6 0ca4: 20 ac 15 jsr InA_StoreFloat ;save it as the horizontal start position 0ca7: e2 42 .dd2 missile_cur_xc 0ca9: 20 ae ef jsr BAS_RND ;random number [0,1) 0cac: 20 91 15 jsr InA_MulFloat ;multiply by spawn probability 0caf: f5 0b .dd2 spawn_prob 0cb1: 20 91 15 jsr InA_MulFloat ;multiply by 0.25 0cb4: 70 f0 .dd2 BAS_QUARTER 0cb6: 20 f2 eb jsr BAS_QINT ;convert to int 0cb9: a4 a1 ldy BAS_FAC+4 ;get value as int (yields 3.3% - 33% chance) 0cbb: d0 16 bne :SkipMirv ;not zero, don't make it a MIRV ; This missile is a MIRV. Determine the height at which it will split. 0cbd: 20 ae ef jsr BAS_RND ;random number [0,1) 0cc0: 20 91 15 jsr InA_MulFloat ;multiply by 100.0 [0,100) 0cc3: 04 0c .dd2 const_100 0cc5: 20 91 15 jsr InA_MulFloat ;multiply by 0.5 [0,50) 0cc8: 64 ee .dd2 BAS_CON_HALF 0cca: 20 f2 eb jsr BAS_QINT ;convert to int [0,49] 0ccd: a5 a1 lda BAS_FAC+4 ;get least-significant byte 0ccf: 69 19 adc #25 ;add 25 [25,74] 0cd1: d0 02 bne :SkipLoad ;(always) ; Set MIRV row and missile status. 0cd3: a9 00 :SkipMirv lda #$00 0cd5: a0 fd :SkipLoad ldy #O_MIRV_ROW 0cd7: 91 17 sta (missile_ptr),y 0cd9: a9 00 lda #$00 0cdb: a0 fa ldy #O_ROW 0cdd: 91 17 sta (missile_ptr),y ;mark missile as alive ; Target a city. The game ends when all cities are dead, so we shouldn't be ; here unless there's something to target. 0cdf: 20 ae ef jsr BAS_RND ;random number [0,1) 0ce2: 20 91 15 jsr InA_MulFloat ;multiply by 6.0 [0,6) 0ce5: 09 0c .dd2 const_6 0ce7: 20 f2 eb jsr BAS_QINT ;convert to int [0,5] 0cea: a4 a1 ldy BAS_FAC+4 ;start with this city 0cec: b9 4a 18 :ScanCities lda city_status,y ;is city alive? 0cef: d0 09 bne :CityPicked ;yes, branch 0cf1: c8 iny 0cf2: c0 06 cpy #$06 ;end of list? 0cf4: d0 f6 bne :ScanCities ;not yet, loop 0cf6: a0 00 ldy #$00 ;start over at start of list 0cf8: f0 f2 beq :ScanCities ;loop (always) 0cfa: 98 :CityPicked tya 0cfb: 20 a0 0e jsr TargetCity ;configure missile to fly toward city ; ; Update the missile. Start by seeing if we've reached terminal height. ; 0cfe: a0 fa :UpdateMissile ldy #O_ROW 0d00: b1 17 lda (missile_ptr),y ;get current row 0d02: 8d ee 0b sta temp ;save for later 0d05: a0 fb ldy #O_DET_ROW 0d07: b1 17 lda (missile_ptr),y ;get detonation row (why not CMP here?) 0d09: cd ee 0b cmp temp ;have we reached it? 0d0c: f0 02 beq :Explode ;yes, explode 0d0e: b0 03 bcs :MoveDown ;no, keep moving 0d10: 4c c4 0d :Explode jmp MissileExploding ;update explosion animation ; Advance the missile. We move twice each frame. 0d13: ee ee 0b :MoveDown inc temp ;move to next row 0d16: 20 5c 13 jsr PlaySounds ; Start by overwriting the white missile head with green trail. 0d19: a2 01 ldx #$01 0d1b: 20 ec f6 jsr BAS_HCOLOR+3 ;color=green 0d1e: 20 73 15 jsr InA_LoadFloat ;get current column 0d21: e2 42 .dd2 missile_cur_xc 0d23: 20 f2 eb jsr BAS_QINT ;convert to int 0d26: ac ee 0b ldy temp ;get row 0d29: 88 dey ;move up so we're at current row 0d2a: 98 tya 0d2b: a6 a1 ldx BAS_FAC+4 ;get current column as int 0d2d: 20 07 14 jsr DrawPixel ;overwrite white head with green 0d30: 20 73 15 jsr InA_LoadFloat ;get current column 0d33: e2 42 .dd2 missile_cur_xc 0d35: 20 85 15 jsr InA_AddFloat ;add movement delta 0d38: ee 42 .dd2 missile_xmove 0d3a: 20 ac 15 jsr InA_StoreFloat ;save it 0d3d: e2 42 .dd2 missile_cur_xc 0d3f: 20 f2 eb jsr BAS_QINT ;convert to int 0d42: ac ee 0b ldy temp ;get next row 0d45: a5 a1 lda BAS_FAC+4 ;get next column 0d47: 91 17 sta (missile_ptr),y ;save in pixel list 0d49: 98 tya 0d4a: a0 fa ldy #O_ROW 0d4c: 91 17 sta (missile_ptr),y ;update current row 0d4e: a6 a1 ldx BAS_FAC+4 ;get column 0d50: a8 tay 0d51: 20 07 14 jsr DrawPixel ;draw green pixel 0d54: c9 03 cmp #$03 ;was screen previously white? 0d56: f0 45 beq :Collision ;yes, ran into something 0d58: ee ee 0b inc temp ;move to next row (+2) 0d5b: ad ee 0b lda temp 0d5e: a0 fa ldy #O_ROW 0d60: 91 17 sta (missile_ptr),y ;update current row (again) 0d62: a2 03 ldx #$03 0d64: 20 ec f6 jsr BAS_HCOLOR+3 ;color=white 0d67: 20 73 15 jsr InA_LoadFloat ;get current column 0d6a: e2 42 .dd2 missile_cur_xc 0d6c: 20 85 15 jsr InA_AddFloat ;add movement delta 0d6f: ee 42 .dd2 missile_xmove 0d71: 20 ac 15 jsr InA_StoreFloat ;save it 0d74: e2 42 .dd2 missile_cur_xc 0d76: 20 f2 eb jsr BAS_QINT ;convert to int 0d79: ac ee 0b ldy temp ;get row 0d7c: a5 a1 lda BAS_FAC+4 ;get column 0d7e: 91 17 sta (missile_ptr),y ;save in pixel list 0d80: a6 a1 ldx BAS_FAC+4 ;get column (TAX?) 0d82: ad ee 0b lda temp ;get row (TYA?) 0d85: 20 07 14 jsr DrawPixel ;draw white pixel 0d88: c9 03 cmp #$03 ;was screen previously white? 0d8a: f0 11 beq :Collision ;yes, ran into something 0d8c: a0 fd ldy #O_MIRV_ROW 0d8e: b1 17 lda (missile_ptr),y ;get MIRV row 0d90: f0 05 beq :NotMirv ;zero, not a MIRV 0d92: cd ee 0b cmp temp ;have we reached the split point? 0d95: 90 03 bcc :SplitMirv ;yes, handle that 0d97: 4c 56 13 :NotMirv jmp JmpUpdateAbm 0d9a: 4c c5 11 :SplitMirv jmp SplitMirv ; Missile has collided with something. Disable or detonate. 0d9d: ad ee 0b :Collision lda temp ;get current row 0da0: a0 fb ldy #O_DET_ROW 0da2: 91 17 sta (missile_ptr),y ;set this as the detonation row 0da4: a0 fa ldy #O_ROW 0da6: 91 17 sta (missile_ptr),y ;and the current row 0da8: 20 ae ef jsr BAS_RND ;get randon number [0,1) 0dab: 20 91 15 jsr InA_MulFloat ;multiply by 10.0 [0,10) 0dae: 50 ea .dd2 BAS_CON_TEN 0db0: 20 f2 eb jsr BAS_QINT ;convert to int [0,9] 0db3: a5 a1 lda BAS_FAC+4 ;get least significant byte 0db5: f0 0d beq MissileExploding ;if zero, detonate (10% chance) 0db7: a0 f4 ldy #O_DET_SIZE ;otherwise, mark as exploded for recycling 0db9: b1 17 lda (missile_ptr),y ;get detonation size 0dbb: c8 iny ;O_DET_STAGE1 0dbc: 91 17 sta (missile_ptr),y ;init stage 1 size to "done" 0dbe: c8 iny ;O_DET_STAGE2 0dbf: 91 17 sta (missile_ptr),y ;init stage 2 size to "done" 0dc1: 4c 56 13 jmp JmpUpdateAbm ;done with missile ; ; Update missile explosion. ; MissileExploding 0dc4: a0 f4 ldy #O_DET_SIZE 0dc6: b1 17 lda (missile_ptr),y ;get detonation size 0dc8: 8d ee 0b sta temp 0dcb: a0 f5 ldy #O_DET_STAGE1 0dcd: b1 17 lda (missile_ptr),y ;get stage 1 progress 0dcf: 8d c5 15 sta _DrawCircIndex+1 0dd2: cd ee 0b cmp temp ;are we finished? 0dd5: f0 37 beq :Stage2 ;yes, check stage 2 0dd7: c9 00 cmp #$00 ;have we started yet? 0dd9: d0 0c bne :InProgress1 ;yes, branch 0ddb: ad ee 0b lda temp ;no, tweak sound effects with det size 0dde: 4a lsr A ;divide by 8 0ddf: 4a lsr A 0de0: 4a lsr A 0de1: 6d 43 18 adc sound_fx_mod ;note carry set with last shifted bit 0de4: 8d 43 18 sta sound_fx_mod 0de7: a2 03 :InProgress1 ldx #$03 0de9: 20 ec f6 jsr BAS_HCOLOR+3 ;color=white 0dec: 20 73 15 jsr InA_LoadFloat ;get missile's horizontal position 0def: e2 42 .dd2 missile_cur_xc 0df1: 20 f2 eb jsr BAS_QINT ;convert to int 0df4: a0 fa ldy #O_ROW 0df6: b1 17 lda (missile_ptr),y ;get current row 0df8: a8 tay ;use as index 0df9: a5 a1 lda BAS_FAC+4 ;get column as int 0dfb: 91 17 sta (missile_ptr),y ;store in missile trail pixel list 0dfd: aa tax ;center column in X-reg 0dfe: 98 tya ;center row in A-reg 0dff: a0 10 ldy #$10 ;draw 16 more pixels 0e01: 20 b8 15 jsr DrawExplosion 0e04: a0 f5 ldy #O_DET_STAGE1 0e06: ad c5 15 lda _DrawCircIndex+1 ;get updated count 0e09: 91 17 sta (missile_ptr),y ;save it 0e0b: 4c 56 13 jmp JmpUpdateAbm ;done with missile 0e0e: a0 f6 :Stage2 ldy #O_DET_STAGE2 0e10: b1 17 lda (missile_ptr),y ;get stage 2 progress 0e12: 8d c5 15 sta _DrawCircIndex+1 0e15: cd ee 0b cmp temp ;are we finished? 0e18: f0 28 beq :DetComplete ;yes, go evaluate damage 0e1a: a2 00 ldx #$00 0e1c: 20 ec f6 jsr BAS_HCOLOR+3 ;color=black 0e1f: 20 73 15 jsr InA_LoadFloat ;get missile's horizontal position 0e22: e2 42 .dd2 missile_cur_xc 0e24: 20 f2 eb jsr BAS_QINT ;convert to int 0e27: a0 fa ldy #O_ROW 0e29: b1 17 lda (missile_ptr),y ;get current row 0e2b: a6 a1 ldx BAS_FAC+4 ;get column as int 0e2d: a0 10 ldy #$10 ;draw 16 more pixels 0e2f: 20 b8 15 jsr DrawExplosion 0e32: ce 43 18 dec sound_fx_mod ;update sound FX 0e35: ce 43 18 dec sound_fx_mod 0e38: a0 f6 ldy #O_DET_STAGE2 0e3a: ad c5 15 lda _DrawCircIndex+1 ;get updated count 0e3d: 91 17 sta (missile_ptr),y ;save it 0e3f: 4c 56 13 jmp JmpUpdateAbm ;done with missile ; Detonation complete. Redraw base line, update stats, and check effects. 0e42: a2 02 :DetComplete ldx #$02 0e44: 20 ec f6 jsr BAS_HCOLOR+3 ;color=purple 0e47: a2 00 ldx #$00 0e49: a0 00 ldy #$00 0e4b: a9 9f lda #159 0e4d: 20 57 f4 jsr BAS_HPLOT0 ;plot point at 0,159 0e50: a2 01 ldx #1 0e52: a9 16 lda #22 0e54: a0 9f ldy #159 0e56: 20 3a f5 jsr BAS_HGLIN ;draw line to 278,159 0e59: a2 00 ldx #$00 0e5b: 20 ec f6 jsr BAS_HCOLOR+3 ;set color to black 0e5e: ee f1 0b inc mssl_destroy_cnt ;increment the number of missiles destroyed 0e61: d0 03 bne :NoInc 0e63: ee f2 0b inc mssl_destroy_cnt+1 0e66: 20 5c 13 :NoInc jsr PlaySounds ;update sounds 0e69: 20 42 15 jsr InXY_SetTextPos ;set text position to score area 0e6c: 16 .dd1 22 0e6d: 0f .dd1 15 0e6e: ad f2 0b lda mssl_destroy_cnt+1 ;get destroyed count 0e71: ae f1 0b ldx mssl_destroy_cnt 0e74: 20 24 ed jsr BAS_LINPRT ;print it 0e77: 20 56 18 jsr EvalCityDamage ; Erase the missile trail. 0e7a: a0 fa ldy #O_ROW 0e7c: b1 17 lda (missile_ptr),y ;get final row 0e7e: 8d ef 0b sta temp+1 ;save it 0e81: a9 ff lda #$ff 0e83: 91 17 sta (missile_ptr),y ;mark missile slot as inactive 0e85: 8d ee 0b sta temp ;init counter to $ff so first inc makes it zero 0e88: ee ee 0b :EraseLoop inc temp 0e8b: ac ee 0b ldy temp ;get row 0e8e: cc ef 0b cpy temp+1 ;reached the bottom? 0e91: d0 03 bne :ErasePixel ;not yet 0e93: 4c 50 13 jmp GameLoopBottom ;yes, we're done 0e96: b1 17 :ErasePixel lda (missile_ptr),y ;get column we drew on for this row 0e98: aa tax 0e99: 98 tya 0e9a: 20 07 14 jsr DrawPixel ;draw black pixel here 0e9d: 4c 88 0e jmp :EraseLoop ; ; Configures a missile to target a specific city. ; ; On entry: ; A-reg: city to target (0-5) ; 0ea0: a0 fc TargetCity ldy #O_TARGET_CITY 0ea2: 91 17 sta (missile_ptr),y ;record the city being targeted ; Compute column of target city. 0ea4: 20 93 eb jsr BAS_FLOAT ;convert A-reg to float in FAC 0ea7: 20 91 15 jsr InA_MulFloat ;multiply by 25 (0-125) 0eaa: ff 0b .dd2 const_25 0eac: 20 85 15 jsr InA_AddFloat ;add 10 (10-135) 0eaf: 50 ea .dd2 BAS_CON_TEN 0eb1: 20 ac 15 jsr InA_StoreFloat 0eb4: e8 42 .dd2 missile_target_xc ; Compute size of missile explosion. 0eb6: 20 ae ef jsr BAS_RND ;generate random number [0,1) 0eb9: 20 91 15 jsr InA_MulFloat ;multiply by 6 [0,6) 0ebc: 09 0c .dd2 const_6 0ebe: 20 85 15 jsr InA_AddFloat ;add 6 [6,12) 0ec1: 09 0c .dd2 const_6 0ec3: 20 f2 eb jsr BAS_QINT ;convert to int [6,11] 0ec6: a5 a1 lda BAS_FAC+4 ;get least significant byte 0ec8: 18 clc 0ec9: 0a asl A ;multiply by 16 [96,176] 0eca: 0a asl A 0ecb: 0a asl A 0ecc: 0a asl A 0ecd: a0 f4 ldy #O_DET_SIZE 0ecf: 91 17 sta (missile_ptr),y ; Pick the detonation height. We must detonate above the level of the city, or ; the collision with a lit pixel will kill the missile. ; ; Note we pick our numbers in such a way that deltaX is < 1.0, so we're never in ; a position where we have to draw two pixels on the same row. 0ed1: 20 ae ef jsr BAS_RND ;generate random number [0,1) 0ed4: 20 91 15 jsr InA_MulFloat ;multiply by 10 [0,10) 0ed7: 50 ea .dd2 BAS_CON_TEN 0ed9: 20 f2 eb jsr BAS_QINT ;convert to int [0,9] 0edc: a5 a1 lda BAS_FAC+4 ;get least significant byte 0ede: 49 ff eor #$ff ;invert [$f6,$ff] 0ee0: 69 96 adc #150 ;add 150 [140,149] 0ee2: a0 fb ldy #O_DET_ROW 0ee4: 91 17 sta (missile_ptr),y ; Init explosion trackers. 0ee6: a9 00 lda #$00 0ee8: a0 f5 ldy #O_DET_STAGE1 0eea: 91 17 sta (missile_ptr),y 0eec: c8 iny 0eed: 91 17 sta (missile_ptr),y ; Compute how far the missile moves horizontally each frame. 0eef: 20 73 15 jsr InA_LoadFloat ;get starting X coordinate 0ef2: e2 42 .dd2 missile_cur_xc 0ef4: 20 8b 15 jsr InA_SubFloat ;subtract target X coordinate 0ef7: e8 42 .dd2 missile_target_xc 0ef9: 20 ac 15 jsr InA_StoreFloat ;save delta X 0efc: 18 0c .dd2 xc_delta_temp 0efe: a0 fa ldy #O_ROW 0f00: b1 17 lda (missile_ptr),y ;get missile's row (will be nonzero for MIRV) 0f02: 49 ff eor #$ff ;invert 0f04: 18 clc 0f05: 69 a0 adc #160 ;add 160 to get approximate delta Y 0f07: a8 tay 0f08: 20 01 e3 jsr BAS_SNGFLT ;convert to float in FAC 0f0b: 20 97 15 jsr InA_DivFloat ;FAC = deltaX / deltaY 0f0e: 18 0c .dd2 xc_delta_temp 0f10: 20 ac 15 jsr InA_StoreFloat ;save as per-frame horizontal movement 0f13: ee 42 .dd2 missile_xmove ; Record initial position. 0f15: 20 73 15 jsr InA_LoadFloat 0f18: e2 42 .dd2 missile_cur_xc ;get start column 0f1a: 20 f2 eb jsr BAS_QINT ;convert to int 0f1d: a0 fa ldy #O_ROW 0f1f: b1 17 lda (missile_ptr),y ;get missile's row 0f21: a8 tay ;use as index 0f22: a5 a1 lda BAS_FAC+4 ;get column as int 0f24: 91 17 sta (missile_ptr),y ;save it so we can erase it later 0f26: 60 rts ; ; Update the ABM, or check user input if none is in flight. ; 0f27: ad fa 4d UpdateAbmMaybe lda abm_row ;is an ABM in flight? 0f2a: c9 ff cmp #$ff 0f2c: f0 03 beq :GetInput ;no, get user input 0f2e: 4c 6f 10 jmp UpdateAbm ;yes, update it 0f31: ad f0 0b :GetInput lda auto_play_flag ;are we in attract mode? 0f34: f0 03 beq ReadUserInput ;no, get user input 0f36: 4c 85 12 jmp AutoPlay ;yes, auto-play ; ; Reads paddles and buttons. ; ; To avoid slowing the game down with excessive controller reads, and also to ; mitigate paddle cross-talk, the paddle reads are distributed across frames. ; We do two frames with no reads, one frame for paddle X, and one frame for ; paddle Y. ; ; The cursor is only on-screen while we are reading the paddles, which is why ; it's a flickery mess. ; ; This is not called while an ABM is in flight. ; 0f39: ee 3d 0f ReadUserInput inc :_IgnorePaddle+1 0f3c: a2 00 :_IgnorePaddle ldx #$00 ;do we want to check the paddles this time? 0f3e: 30 2d bmi :SkipPaddles ;no, skip it 0f40: d0 11 bne :ReadY ;if it's #$01, read the Y axis 0f42: 20 86 13 jsr DrawCursor ;draw the cursor 0f45: 20 5c 13 jsr PlaySounds ;play sound effects 0f48: 20 00 03 jsr ReadPdlX ;read X-axis paddle 0f4b: 4a lsr A ;halve it; note LSB is now in carry 0f4c: 69 06 adc #$06 ;add 6 to get [6,134] 0f4e: 8d 79 13 sta cursor_x 0f51: d0 14 bne :Cont ;(always) 0f53: 20 86 13 :ReadY jsr DrawCursor ;draw the cursor 0f56: 20 5c 13 jsr PlaySounds ;play sound effects 0f59: 20 03 03 jsr ReadPdlY ;read Y-axis paddle 0f5c: 4a lsr A ;halve it; note LSB is now in carry 0f5d: 69 03 adc #$03 ;add 3 to get [3,131] 0f5f: 8d 7a 13 sta cursor_y 0f62: a9 fd lda #$fd ;reset counter to -3 0f64: 8d 3d 0f sta :_IgnorePaddle+1 0f67: 20 5c 13 :Cont jsr PlaySounds ;play sound effects 0f6a: 20 be 13 jsr EraseCursor ;erase the cursor 0f6d: 20 06 03 :SkipPaddles jsr GetOuterBtn ;button for outer/center launchers pressed? 0f70: 90 09 bcc :NotOuter ;no, branch 0f72: a9 50 FireOuter lda #$50 ;explosion with 80 pixels 0f74: 8d f4 4d sta abm_max_size 0f77: a0 fe ldy #$fe ;check even-numbered launchers 0f79: d0 0f bne :FireCommon ;(always) 0f7b: 20 09 03 :NotOuter jsr GetInnerBtn ;button for inner launchers pressed? 0f7e: b0 03 bcs FireInner ;yes, fire 0f80: 4c 50 13 jmp GameLoopBottom 0f83: a9 70 FireInner lda #$70 ;explosion with 112 pixels 0f85: 8d f4 4d sta abm_max_size 0f88: a0 ff ldy #$ff ;check odd-numbered launchers 0f8a: 8c bc 0f :FireCommon sty :_Closest+1 0f8d: a9 64 lda #100 ;max horizontal delta for launcher 0f8f: 8d af 0f sta :_Val+1 ; (can't fire farther away than that) 0f92: c8 :NextLauncher iny ;incr twice so we check odd or even 0f93: c8 iny 0f94: c0 05 cpy #$05 0f96: b0 23 bcs :_Closest 0f98: b9 7a 0b lda launcher_status,y ;get launcher status 0f9b: f0 f5 beq :NextLauncher ;not available, branch 0f9d: b9 7f 0b lda launcher_posns,y ;get launcher position 0fa0: 4a lsr A ;halve it to get color coordinates (0-139) 0fa1: 38 sec 0fa2: ed 79 13 sbc cursor_x ;subtract cursor X position 0fa5: c9 00 cmp #$00 ;(?) 0fa7: 10 05 bpl :_Val ;positive, branch 0fa9: 49 ff eor #$ff ;negative, invert to get absolute value 0fab: 38 sec 0fac: 69 00 adc #$00 ;add 1 0fae: c9 00 :_Val cmp #$00 ;is it closer than previous value? 0fb0: b0 e0 bcs :NextLauncher ;no, check next 0fb2: 8d af 0f sta :_Val+1 ;yes, this is closest; update 0fb5: 8c bc 0f sty :_Closest+1 ;save launcher index 0fb8: 4c 92 0f jmp :NextLauncher ;check next 0fbb: a0 00 :_Closest ldy #$00 ;did we find a launcher? 0fbd: 10 03 bpl :FireAbm ;yes, fire ABM 0fbf: 4c 50 13 jmp GameLoopBottom ;no, nothing was in range 0fc2: a9 00 :FireAbm lda #$00 0fc4: 99 7a 0b sta launcher_status,y ;mark launcher as unavailable 0fc7: b9 7f 0b lda launcher_posns,y ;get launcher position 0fca: 8d f0 0f sta :_LaunchCol+1 ;set as horizontal coordinate for HPOSN 0fcd: 4a lsr A ;divide by two 0fce: a8 tay 0fcf: c8 iny ;add two 0fd0: c8 iny 0fd1: 20 01 e3 jsr BAS_SNGFLT ;convert to float 0fd4: 20 ac 15 jsr InA_StoreFloat 0fd7: e2 4d .dd2 abm_cur_xc 0fd9: ad 7a 13 lda cursor_y ;get target row 0fdc: 8d fb 4d sta abm_target_row ;save it 0fdf: ac 79 13 ldy cursor_x ;get target column 0fe2: 20 01 e3 jsr BAS_SNGFLT ;convert to float 0fe5: 20 ac 15 jsr InA_StoreFloat ;save it 0fe8: e8 4d .dd2 abm_target_xc 0fea: a2 00 ldx #$00 0fec: 20 ec f6 jsr BAS_HCOLOR+3 ;color=black 0fef: a2 00 :_LaunchCol ldx #$00 ;starting pixel column 0ff1: a9 98 lda #152 ;starting row 0ff3: 8d fa 4d sta abm_row ;save it 0ff6: ce fa 4d dec abm_row ;move up one row 0ff9: a0 00 ldy #$00 0ffb: 20 11 f4 jsr BAS_HPOSN ;get address of position 0ffe: 20 7d 13 jsr InA_DrawShape ;erase launcher top 1001: dd 14 .dd2 shape_laun_top 1003: a9 00 lda #$00 1005: 8d f5 4d sta abm_det_size1 ;init detonation counters 1008: 8d f6 4d sta abm_det_size2 100b: 20 73 15 jsr InA_LoadFloat ;get start column 100e: e2 4d .dd2 abm_cur_xc 1010: 20 8b 15 jsr InA_SubFloat ;subtract from target column 1013: e8 4d .dd2 abm_target_xc 1015: 20 ac 15 jsr InA_StoreFloat ;save delta X 1018: 18 0c .dd2 xc_delta_temp 101a: ad fa 4d lda abm_row ;get start row 101d: 38 sec 101e: ed fb 4d sbc abm_target_row ;subtract target row (delta Y) 1021: a8 tay 1022: 20 01 e3 jsr BAS_SNGFLT ;convert to float in FAC 1025: 20 97 15 jsr InA_DivFloat ;FAC = deltaX / deltaY 1028: 18 0c .dd2 xc_delta_temp 102a: 20 ac 15 jsr InA_StoreFloat ;save X movement 102d: ee 4d .dd2 abm_xmove 102f: 20 af eb jsr BAS_ABS ;compute absolute value 1032: 20 a6 15 jsr InA_CmpFloat ;compare to 1.0 1035: 13 e9 .dd2 BAS_CON_ONE 1037: 30 03 bmi :GoodAngle ;less than 1.0, keep going 1039: 4c a1 11 jmp StopAbm ;angle too shallow, won't draw right, disallow 103c: 20 73 15 :GoodAngle jsr InA_LoadFloat ;get initial column 103f: e2 4d .dd2 abm_cur_xc 1041: 20 f2 eb jsr BAS_QINT ;convert to int 1044: ac fa 4d ldy abm_row ;get current row 1047: a5 a1 lda BAS_FAC+4 ;get initial column as int 1049: 99 00 4d sta abm_trail,y ;record position in trail record 104c: ee f3 0b inc abm_fired_cnt ;increment number of ABMs fired 104f: d0 03 bne :NoInc 1051: ee f4 0b inc abm_fired_cnt+1 1054: 20 42 15 :NoInc jsr InXY_SetTextPos 1057: 16 .dd1 22 ;status row 1058: 14 .dd1 20 1059: ae f3 0b ldx abm_fired_cnt ;get number of ABMs fired 105c: ad f4 0b lda abm_fired_cnt+1 105f: 20 24 ed jsr BAS_LINPRT ;print value 1062: 20 2b 15 jsr InS_PrintString 1065: a0 c1 c2 cd+ .zstr “ ABM'S” 106c: 4c 50 13 jmp GameLoopBottom ; ; Updates ABM movement. ; 106f: 20 5c 13 UpdateAbm jsr PlaySounds 1072: ad fb 4d lda abm_target_row ;get target row 1075: cd fa 4d cmp abm_row ;compare to current row 1078: 90 03 bcc :StillFlying ;not there yet, branch 107a: 4c ef 10 jmp AbmExploding ;detonate! ; Move the ABM up two rows. 107d: ce fa 4d :StillFlying dec abm_row ;reduce row (move toward top of screen) 1080: a2 02 ldx #$02 1082: 20 ec f6 jsr BAS_HCOLOR+3 ;color=purple 1085: ac fa 4d ldy abm_row ;get next row 1088: c8 iny ;inc to get current row 1089: 98 tya 108a: be 00 4d ldx abm_trail,y ;get pixel position 108d: 20 07 14 jsr DrawPixel ;draw purple pixel on top of white head 1090: 20 73 15 jsr InA_LoadFloat ;get current column 1093: e2 4d .dd2 abm_cur_xc 1095: 20 85 15 jsr InA_AddFloat ;add movement delta 1098: ee 4d .dd2 abm_xmove 109a: 20 ac 15 jsr InA_StoreFloat ;save updated position 109d: e2 4d .dd2 abm_cur_xc 109f: 20 f2 eb jsr BAS_QINT ;convert to integer 10a2: ac fa 4d ldy abm_row ;get current row 10a5: a5 a1 lda BAS_FAC+4 ;get column as integer 10a7: 99 00 4d sta abm_trail,y ;save in ABM trail list 10aa: aa tax 10ab: 98 tya 10ac: 20 07 14 jsr DrawPixel ;draw trail 10af: c9 03 cmp #$03 ;did we hit something? 10b1: f0 36 beq :AbmCollision ;yes, branch 10b3: ce fa 4d dec abm_row ;move down a row 10b6: a2 03 ldx #$03 10b8: 20 ec f6 jsr BAS_HCOLOR+3 ;color=white 10bb: 20 73 15 jsr InA_LoadFloat ;get current column 10be: e2 4d .dd2 abm_cur_xc 10c0: 20 85 15 jsr InA_AddFloat ;add movement delta 10c3: ee 4d .dd2 abm_xmove 10c5: 20 ac 15 jsr InA_StoreFloat ;save updated position 10c8: e2 4d .dd2 abm_cur_xc 10ca: 20 f2 eb jsr BAS_QINT ;convert to integer 10cd: ac fa 4d ldy abm_row ;get current row 10d0: a5 a1 lda BAS_FAC+4 ;get column as integer 10d2: 99 00 4d sta abm_trail,y ;save in ABM trail list 10d5: aa tax 10d6: 98 tya 10d7: 20 07 14 jsr DrawPixel ;draw missile head 10da: c9 03 cmp #$03 ;did we hit something? 10dc: f0 0b beq :AbmCollision ;yes, branch 10de: 20 5c 13 jsr PlaySounds 10e1: a9 28 lda #$28 ;sleep for ~4.5 msec (feels a little smoother 10e3: 20 a8 fc jsr MON_WAIT ; when things aren't too busy) 10e6: 4c 50 13 jmp GameLoopBottom 10e9: ad fa 4d :AbmCollision lda abm_row ;set current = target so we know ABM is exploding 10ec: 8d fb 4d sta abm_target_row 10ef: 20 5c 13 AbmExploding jsr PlaySounds 10f2: ad f5 4d lda abm_det_size1 ;check stage 1 detonation size 10f5: 8d c5 15 sta _DrawCircIndex+1 10f8: cd f4 4d cmp abm_max_size ;done yet? 10fb: f0 34 beq :Stage2 ;yes, branch 10fd: c9 00 cmp #$00 ;initial detonation? 10ff: d0 0b bne :NotFirst ;no, skip sound adjust 1101: ad f4 4d lda abm_max_size ;get max size 1104: 4a lsr A ;divide by 2 1105: 18 clc 1106: 6d 43 18 adc sound_fx_mod ;tweak sound effects 1109: 8d 43 18 sta sound_fx_mod 110c: a2 03 :NotFirst ldx #$03 110e: 20 ec f6 jsr BAS_HCOLOR+3 ;color=white 1111: 20 73 15 jsr InA_LoadFloat ;get current column 1114: e2 4d .dd2 abm_cur_xc 1116: 20 f2 eb jsr BAS_QINT ;convert to int 1119: ac fa 4d ldy abm_row ;get current row 111c: a5 a1 lda BAS_FAC+4 ;get current col as int 111e: 99 00 4d sta abm_trail,y ;save in trail list 1121: aa tax 1122: 98 tya 1123: a0 08 ldy #$08 ;expand circle by 8 pixels 1125: 20 b8 15 jsr DrawExplosion ;draw it 1128: ad c5 15 lda _DrawCircIndex+1 ;get updated count 112b: 8d f5 4d sta abm_det_size1 ;save it 112e: 4c 50 13 jmp GameLoopBottom 1131: ad f6 4d :Stage2 lda abm_det_size2 ;get stage 2 detonation size 1134: 8d c5 15 sta _DrawCircIndex+1 1137: cd f4 4d cmp abm_max_size ;done yet? 113a: f0 2c beq :ExplodeDone ;yes, go clean up 113c: a2 00 ldx #$00 113e: 20 ec f6 jsr BAS_HCOLOR+3 ;color=black 1141: 20 73 15 jsr InA_LoadFloat ;get current column 1144: e2 4d .dd2 abm_cur_xc 1146: 20 f2 eb jsr BAS_QINT ;convert to int 1149: ad fa 4d lda abm_row ;get current row 114c: a6 a1 ldx BAS_FAC+4 ;get current col as int 114e: a0 08 ldy #$08 ;expand circle by 8 pixels 1150: 20 b8 15 jsr DrawExplosion ;draw it 1153: ce 43 18 dec sound_fx_mod ;update sound effects 1156: ce 43 18 dec sound_fx_mod 1159: ce 43 18 dec sound_fx_mod 115c: ce 43 18 dec sound_fx_mod 115f: ad c5 15 lda _DrawCircIndex+1 ;get updated count 1162: 8d f6 4d sta abm_det_size2 ;save it 1165: 4c 50 13 jmp GameLoopBottom 1168: 20 5c 13 :ExplodeDone jsr PlaySounds ; Redraw base line, then erase trail. 116b: a2 02 ldx #$02 116d: 20 ec f6 jsr BAS_HCOLOR+3 ;color=purple 1170: a2 00 ldx #$00 1172: a0 00 ldy #$00 1174: a9 9f lda #159 1176: 20 57 f4 jsr BAS_HPLOT0 ;plot point at 0,159 1179: a2 01 ldx #1 117b: a9 16 lda #22 117d: a0 9f ldy #159 117f: 20 3a f5 jsr BAS_HGLIN ;draw line to 278,159 1182: a2 00 ldx #$00 1184: 20 ec f6 jsr BAS_HCOLOR+3 ;color=black 1187: a9 99 lda #153 ;start row is 152, add 1 to compensate for 1189: 8d ee 0b sta temp ; initial DEC 118c: ce ee 0b :Loop dec temp 118f: ac ee 0b ldy temp ;get row 1192: cc fa 4d cpy abm_row ;reached end of trail? 1195: 90 0a bcc StopAbm ;yes, branch 1197: be 00 4d ldx abm_trail,y ;get pixel column 119a: 98 tya 119b: 20 07 14 jsr DrawPixel ;draw black pixel 119e: 4c 8c 11 jmp :Loop ; ABM is done exploding, reset state and check launcher. 11a1: a9 ff StopAbm lda #$ff 11a3: 8d fa 4d sta abm_row ;mark ABM as inactive 11a6: a2 03 ldx #$03 11a8: 20 ec f6 jsr BAS_HCOLOR+3 ;color=white 11ab: ad f4 4d lda abm_max_size ;get size of ABM we just fired 11ae: c9 50 cmp #$50 ;from outer/center launcher? 11b0: d0 06 bne :InnerLauncher ;no, branch 11b2: 20 84 0b jsr CheckOuterLau 11b5: 4c 50 13 jmp GameLoopBottom 11b8: 20 88 0b :InnerLauncher jsr CheckInnerLau 11bb: 4c 50 13 jmp GameLoopBottom temp_missile_col 11be: 00 00 00 00+ .fill 5,$00 ;fp: temp storage for missile column temp_missile_row 11c3: 00 .dd1 $00 11c4: 00 temp_city_off .dd1 $00 ; ; Split one missile into up to six, one per city. ; ; Note this does NOT zero out the MIRV split row, so the original missile will ; try to split continuously, limited only by slot availability. (Bug?) ; 11c5: 20 73 15 SplitMirv jsr InA_LoadFloat ;get missile's column 11c8: e2 42 .dd2 missile_cur_xc 11ca: 20 ac 15 jsr InA_StoreFloat ;save in temp 11cd: be 11 .dd2 temp_missile_col 11cf: a0 fa ldy #O_ROW 11d1: b1 17 lda (missile_ptr),y ;get missile's row 11d3: 18 clc 11d4: e9 00 sbc #$00 ;subtract 1 11d6: 8d c3 11 sta temp_missile_row ;save in temp 11d9: a9 02 lda #$02 11db: a0 fc ldy #O_TARGET_CITY ;set target city (redundant; target city 11dd: 91 17 sta (missile_ptr),y ; subroutine does this too) 11df: 20 a0 0e jsr TargetCity ;configure original missile to target city #2 11e2: a5 17 lda missile_ptr ;get current page pointer 11e4: 8d 7b 12 sta :_RestorePtr+1 ;save it off 11e7: a9 fd lda #$fd ;init city offset to -3 11e9: 8d c4 11 sta temp_city_off 11ec: a9 47 lda #$47 ;start at first page reserved for MIRVs ($48xx) 11ee: 85 18 sta missile_ptr+1 11f0: e6 18 :MirvLoop inc missile_ptr+1 ;advance to next page 11f2: a5 18 lda missile_ptr+1 11f4: c9 4d cmp #$4d ;did we reach end of slots? 11f6: 90 03 bcc :Cont ;no, branch 11f8: 4c 7a 12 jmp :_RestorePtr ;yes, jump to end 11fb: ee c4 11 :Cont inc temp_city_off ;increment the count 11fe: ad c4 11 lda temp_city_off 1201: d0 05 bne :NotZero ;not zero, branch 1203: a9 01 lda #$01 ;skip zero, go straight to +1 1205: 8d c4 11 sta temp_city_off 1208: 20 93 eb :NotZero jsr BAS_FLOAT ;convert to float in FAC 120b: 20 85 15 jsr InA_AddFloat ;add to missile column 120e: be 11 .dd2 temp_missile_col 1210: a0 fa ldy #O_ROW 1212: b1 17 lda (missile_ptr),y ;get row for this missile 1214: c9 ff cmp #$ff ;is it available? 1216: d0 d8 bne :MirvLoop ;no, move to next slot 1218: 20 ac 15 jsr InA_StoreFloat ;set column for this missile 121b: e2 42 .dd2 missile_cur_xc 121d: 20 f2 eb jsr BAS_QINT ;convert to integer 1220: a0 fa ldy #O_ROW 1222: ad c3 11 lda temp_missile_row ;get row - 1 1225: 91 17 sta (missile_ptr),y ;set missile's row 1227: a8 tay ;use row as index 1228: a5 a1 lda BAS_FAC+4 ;get column as int 122a: 91 17 sta (missile_ptr),y ;save column in trail list 122c: a6 a1 ldx BAS_FAC+4 ;get column as int (TAX?) 122e: ad c3 11 lda temp_missile_row ;get row - 1 1231: 8e 59 12 stx _Column+1 ;set column 1234: 20 07 14 jsr DrawPixel ;draw white pixel 1237: ad c4 11 lda temp_city_off ;get offset 123a: 18 clc 123b: 69 02 adc #$02 ;add 2 123d: a0 fc ldy #O_TARGET_CITY 123f: 91 17 sta (missile_ptr),y ;set as target city 1241: 20 a0 0e jsr TargetCity ;configure missile to hit city 1244: 20 73 15 jsr InA_LoadFloat ;get X movement delta 1247: ee 42 .dd2 missile_xmove 1249: 20 af eb jsr BAS_ABS ;compute absolute value 124c: 20 a6 15 jsr InA_CmpFloat ;compare to 1.0 124f: 13 e9 .dd2 BAS_CON_ONE 1251: 30 21 bmi :PathGood ;it's less, keep it ; MIRV path is too shallow, kill it, and give up. 1253: a2 00 ldx #$00 1255: 20 ec f6 jsr BAS_HCOLOR+3 ;color=black 1258: a2 00 _Column ldx #$00 ;missile column 125a: ad c3 11 lda temp_missile_row ;missile row 125d: 20 07 14 jsr DrawPixel ;erase head 1260: a9 ff lda #$ff 1262: a0 fa ldy #O_ROW 1264: 91 17 sta (missile_ptr),y ;mark missile as inactive 1266: ad 7b 12 lda :_RestorePtr+1 ;restore previous pointer value 1269: 85 18 sta missile_ptr+1 ;(redundant with code below) 126b: a9 00 lda #$00 126d: a0 fd ldy #O_MIRV_ROW 126f: 91 17 sta (missile_ptr),y ;set MIRV row to zero 1271: 4c 7a 12 jmp :_RestorePtr 1274: 20 5c 13 :PathGood jsr PlaySounds 1277: 4c f0 11 jmp :MirvLoop ;go make another 127a: a9 00 :_RestorePtr lda #$00 ;get previous pointer value 127c: 85 18 sta missile_ptr+1 ;restore 127e: 4c 38 0c jmp GameLoop 1281: 00 auto_curs_x .dd1 $00 ;random value, from 6 to 133 1282: 00 auto_curs_y .dd1 $00 ;random value, from 6 to 133 1283: 00 00 .junk 2 ; ; Controls the player in "attract mode", moving the cursor and firing ABMs. ; 1285: ad 00 c0 AutoPlay lda KBD ;check keyboard 1288: 10 03 bpl :DoPlay ;no key hit, keep going 128a: 4c 13 08 jmp Restart ;key hit, jump back to title screen 128d: 20 86 13 :DoPlay jsr DrawCursor ;draw cursor 1290: 20 ae ef jsr BAS_RND ;get random [0,1) 1293: 20 91 15 jsr InA_MulFloat ;multiply by 128 [0,128) 1296: fa 0b .dd2 const_128 1298: 20 f2 eb jsr BAS_QINT ;convert to integer [0,127] 129b: a5 a1 lda BAS_FAC+4 ;get value as int 129d: f0 7a beq :MoveFireOuter ;if zero, fire outer, move to new position 129f: c9 01 cmp #$01 12a1: f0 7f beq :MoveFireInner ;if 1, fire inner, move to new position 12a3: c9 08 cmp #$08 12a5: 90 32 bcc :AddWobble ;if 2-7, ? 12a7: 29 01 and #$01 ;odd or even? 12a9: f0 14 beq :MoveY ;even, move Y only 12ab: ad 79 13 lda cursor_x ;get current cursor X position 12ae: 38 sec 12af: ed 81 12 sbc auto_curs_x ;subtract desired position 12b2: f0 1f beq :MoveDone ;already there, bail 12b4: 30 06 bmi :MoveRight ;which way do we need to go? 12b6: ce 79 13 dec cursor_x ;move left 12b9: 4c bf 12 jmp :MoveY 12bc: ee 79 13 :MoveRight inc cursor_x ;move right 12bf: ad 7a 13 :MoveY lda cursor_y ;get current cursor Y position 12c2: 38 sec 12c3: ed 82 12 sbc auto_curs_y ;subtract desired position 12c6: f0 0b beq :MoveDone ;already there, bail 12c8: 30 06 bmi :MoveDown 12ca: ce 7a 13 dec cursor_y ;move up 12cd: 4c d3 12 jmp :MoveDone 12d0: ee 7a 13 :MoveDown inc cursor_y 12d3: 20 be 13 :MoveDone jsr EraseCursor ;erase the cursor 12d6: 4c 38 0c jmp GameLoop ; ; Add a bit of wobble to the cursor position. ; ; The computation seems wrong. It's calculating: ; (PI/2) - (PI/2) - (2*PI*random) ; which is equivalent to: ; -2*PI*random ; ; I think the author probably wanted PI - (2*PI*random), to get an integer value ; in the range [-3,3]. But he only had 2*PI and 0.5*PI, and tried to double-up ; the latter. ; ; There doesn't seem to be a point in using PI other than as a convenient ; constant. ; 12d9: 20 ae ef :AddWobble jsr BAS_RND ;get random number [0,1) 12dc: 20 91 15 jsr InA_MulFloat ;multiply by PI*2 [0,~6.28) 12df: 6b f0 .dd2 BAS_CON_PI_DOUB 12e1: 20 8b 15 jsr InA_SubFloat ;subtract from PI/2 12e4: 66 f0 .dd2 BAS_CON_PI_HALF 12e6: 20 8b 15 jsr InA_SubFloat ;again 12e9: 66 f0 .dd2 BAS_CON_PI_HALF 12eb: 20 f2 eb jsr BAS_QINT ;convert to integer [0,6] 12ee: a5 a1 lda BAS_FAC+4 ;get value as int 12f0: 6d 79 13 adc cursor_x ;add to cursor X position 12f3: 8d 79 13 sta cursor_x 12f6: 20 ae ef jsr BAS_RND ;repeat for Y position 12f9: 20 91 15 jsr InA_MulFloat ; ... 12fc: 6b f0 .dd2 BAS_CON_PI_DOUB 12fe: 20 8b 15 jsr InA_SubFloat 1301: 66 f0 .dd2 BAS_CON_PI_HALF 1303: 20 85 15 jsr InA_AddFloat 1306: 66 f0 .dd2 BAS_CON_PI_HALF 1308: 20 d0 ee jsr BAS_NEGOP 130b: 20 f2 eb jsr BAS_QINT 130e: a5 a1 lda BAS_FAC+4 1310: 6d 7a 13 adc cursor_y 1313: 8d 7a 13 sta cursor_y 1316: 4c d3 12 jmp :MoveDone 1319: 20 2b 13 :MoveFireOuter jsr RandomCursorPos ;pick new position 131c: 20 be 13 jsr EraseCursor ;erase cursor 131f: 4c 72 0f jmp FireOuter ;fire ABM from outer/center silo 1322: 20 2b 13 :MoveFireInner jsr RandomCursorPos ;pick new position 1325: 20 be 13 jsr EraseCursor ;erase cursor 1328: 4c 83 0f jmp FireInner ;fire ABM from inner silo ; Pick a random X,Y position for the cursor. 132b: 20 ae ef RandomCursorPos jsr BAS_RND ;get random [0,1) 132e: 20 91 15 jsr InA_MulFloat ;multiply by 128.0 [0,128) 1331: fa 0b .dd2 const_128 1333: 20 f2 eb jsr BAS_QINT ;convert to integer [0,127] 1336: a5 a1 lda BAS_FAC+4 1338: 69 06 adc #$06 ;add 6 [6,133] 133a: 8d 81 12 sta auto_curs_x ;save it 133d: 20 ae ef jsr BAS_RND ;do it again 1340: 20 91 15 jsr InA_MulFloat 1343: fa 0b .dd2 const_128 1345: 20 f2 eb jsr BAS_QINT 1348: a5 a1 lda BAS_FAC+4 134a: 69 06 adc #$06 ;(should be 3?) 134c: 8d 82 12 sta auto_curs_y ;save that too 134f: 60 rts ; ; Play sounds, jump to top of game loop. ; 1350: 20 5c 13 GameLoopBottom jsr PlaySounds 1353: 4c 38 0c jmp GameLoop ; ; Play sounds, jump to ABM/player update. ; 1356: 20 5c 13 JmpUpdateAbm jsr PlaySounds 1359: 4c 27 0f jmp UpdateAbmMaybe ; ; Plays sound effects. ; 135c: a5 4a PlaySounds lda sfx_counter 135e: 26 4b rol sfx_counter+1 1360: 2a rol A 1361: 90 02 bcc :NoInc 1363: e6 4b inc sfx_counter+1 1365: 45 4b :NoInc eor sfx_counter+1 1367: 4d 40 c0 eor STROBE ;(?) 136a: 85 4a sta sfx_counter 136c: 29 7f and #$7f 136e: cd 43 18 cmp sound_fx_mod 1371: f0 05 beq :Return 1373: b0 03 bcs :Return 1375: 8d 30 c0 _Click sta SPKR ;$c030 or (for attract mode) $c064 1378: 60 :Return rts 1379: 00 cursor_x .dd1 $00 ;ranges from 6 to 134 ($86) 137a: 00 cursor_y .dd1 $00 ;ranges from 3 to 131 ($83) 137b: 00 prev_cursor_x .dd1 $00 ;X posn where cursor was last drawn 137c: 00 prev_cursor_y .dd1 $00 ;Y posn where cursor was last drawn ; ; Draws an Applesoft shape, from an inline data pointer, at the position ; determined by the last HPOSN or HPLOT. ; 137d: 20 4c 15 InA_DrawShape jsr GetInlineWord 1380: aa tax ;get address in (Y,X) 1381: a9 00 lda #$00 ;rotation=0 1383: 4c 01 f6 jmp BAS_DRAW0 ;draw shape ; ; Draws the cursor, one pixel at a time. The previous screen contents are ; saved. ; 1386: a0 ff DrawCursor ldy #$ff ;init index (first inc will make it zero) 1388: 8c 94 13 sty :_Index+1 138b: a2 03 ldx #$03 ;color=3 (white) 138d: 20 ec f6 jsr BAS_HCOLOR+3 1390: ee 94 13 :Loop inc :_Index+1 1393: a0 00 :_Index ldy #$00 1395: ad 79 13 lda cursor_x ;get cursor X position 1398: 18 clc 1399: 79 e9 13 adc cursor_xoff,y ;add the X offset 139c: aa tax 139d: ad 7a 13 lda cursor_y ;get cursor Y position 13a0: 18 clc 13a1: 79 f3 13 adc cursor_yoff,y ;add the Y offset 13a4: 20 07 14 jsr DrawPixel ;draw the pixel 13a7: ac 94 13 ldy :_Index+1 13aa: 99 fd 13 sta saved_curs_pix,y ;save previous value 13ad: c0 09 cpy #$09 ;did we finish entry #9? 13af: 90 df bcc :Loop ;not yet, loop 13b1: ad 79 13 lda cursor_x ;remember where we drew it 13b4: 8d 7b 13 sta prev_cursor_x 13b7: ad 7a 13 lda cursor_y 13ba: 8d 7c 13 sta prev_cursor_y 13bd: 60 rts ; ; Erases the cursor, one pixel at a time. The previous screen contents are ; restored. ; 13be: a0 ff EraseCursor ldy #$ff ;init index (first inc will make it zero) 13c0: 8c c7 13 sty :_Index+1 13c3: ee c7 13 :Loop inc :_Index+1 13c6: a0 00 :_Index ldy #$00 13c8: b9 fd 13 lda saved_curs_pix,y ;get old color 13cb: aa tax 13cc: 20 ec f6 jsr BAS_HCOLOR+3 ;set color 13cf: ad 7b 13 lda prev_cursor_x ;get cursor's previous X position 13d2: 18 clc 13d3: 79 e9 13 adc cursor_xoff,y ;add the X offset 13d6: aa tax 13d7: ad 7c 13 lda prev_cursor_y ;get cursor's previous Y position 13da: 18 clc 13db: 79 f3 13 adc cursor_yoff,y ;add the Y offset 13de: 20 07 14 jsr DrawPixel ;draw the pixel 13e1: ac c7 13 ldy :_Index+1 13e4: c0 09 cpy #$09 ;did we finish entry #9? 13e6: 90 db bcc :Loop ;not yet, loop 13e8: 60 rts ; ; X/Y offsets for drawing cursor pixels. These are 8-bit signed values. ; ; (0,-2) ; (0,-1) ; (-1, 0) (0, 0) (1, 0) ; (-1, 1) (0, 1) (1, 1) ; (0, 2) ; (0, 3) 13e9: 00 00 ff ff+ cursor_xoff .bulk $00,$00,$ff,$ff,$00,$00,$01,$01,$00,$00 13f3: fe ff 00 01+ cursor_yoff .bulk $fe,$ff,$00,$01,$00,$01,$00,$01,$02,$03 ; ; Saved pixel colors. These hold the color value (0-3) of the pixel under the ; cursor pixel. Note these are color pixels (two basic pixels). 13fd: 00 00 00 00+ saved_curs_pix .bulk $00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; ; Draws a color pixel (two B&W pixels) at the specified position. The previous ; color value is returned. ; ; On entry: ; X-reg: X offset (0-139) ; A-reg: Y offset (0-191) ; ; On exit: ; A-reg: previous color pixel value (0-3) ; 1407: 48 DrawPixel pha ;preserve Y offset 1408: a9 00 lda #$00 140a: 8d 4c 14 sta :_ScreenColor+1 ;init return value 140d: 8a txa ;get X offset 140e: 0a asl A ;double it 140f: aa tax ;save low part in X-reg 1410: a9 00 lda #$00 1412: 69 00 adc #$00 1414: a8 tay ;save high part in Y-reg 1415: 68 pla ;restore Y offset (why?) 1416: 48 pha ;save it some more 1417: 8a txa ;get low part 1418: 29 fe and #%11111110 ;ensure it's even 141a: aa tax ;put that back in X-reg 141b: 49 01 eor #$01 ;ensure it's odd 141d: 8d 2e 14 sta :_XCoordLo+1 ;store odd X coord for second pass 1420: 68 pla ;restore Y offset 1421: 8d 30 14 sta :_YCoord+1 ;set as Y coord 1424: 8c 32 14 sty :_XCoordHi+1 ;set X coord (high) 1427: 20 33 14 jsr :DoDraw ;draw one pixel 142a: 0e 4c 14 asl :_ScreenColor+1 ;shift previous screen value 142d: a2 00 :_XCoordLo ldx #$00 ;now set up for the even pixel 142f: a9 00 :_YCoord lda #$00 1431: a0 00 :_XCoordHi ldy #$00 1433: 20 11 f4 :DoDraw jsr BAS_HPOSN ;set up DP for hi-res X/Y position 1436: b1 26 lda (BAS_HBASL),y ;get current value 1438: 25 30 and BAS_HMASK ;apply masks 143a: 29 7f and #$7f ;(probably not needed, nothing draws with hi set) 143c: f0 03 beq L1441 ;pixel not set, branch 143e: ee 4c 14 inc :_ScreenColor+1 ;bump return value 1441: a5 1c L1441 lda BAS_HCOLOR1 ;merge color pixels into hi-res screen 1443: 51 26 eor (BAS_HBASL),y 1445: 25 30 and BAS_HMASK ;don't touch the adjacent pixels 1447: 51 26 eor (BAS_HBASL),y 1449: 91 26 sta (BAS_HBASL),y 144b: a9 00 :_ScreenColor lda #$00 ;return previous color 144d: 60 rts ; ; Shapes for the cities and missile launchers. ; 144e: 24 24 24 35+ shape_city .bulk $24,$24,$24,$35,$36,$2e,$25,$24,$24,$2c,$2c,$24,$36,$35,$35,$36 + $36,$26,$24,$25,$24,$24,$25,$24,$2c,$24,$24,$2d,$36,$36,$35,$36 + $2e,$36,$36,$36,$3f,$37,$2e,$2d,$2d,$25,$3c,$3f,$24,$24,$2c,$2d + $36,$36,$26,$24,$24,$24,$24,$24,$2c,$2d,$35,$36,$36,$36,$36,$36 + $36,$26,$24,$24,$2d,$24,$24,$36,$36,$2d,$35,$36,$36,$3e,$3f,$3f + $3f,$3f,$3f,$3f,$3f,$3f,$3f,$3f,$3f,$3f,$3f,$2d,$2d,$2d,$2d,$2d + $2d,$2d,$2d,$2d,$2d,$2d,$2d,$2d,$2d,$05,$00 14b9: 1b 2e 21 24+ shape_laun_bot .bulk $1b,$2e,$21,$24,$09,$36,$2e,$21,$24,$24,$09,$36,$36,$2d,$24,$24 + $0d,$36,$36,$2f,$2e,$21,$24,$2c,$11,$36,$2e,$21,$2c,$11,$36,$1b + $1b,$08,$20,$00 14dd: 2d 2d 3f 27+ shape_laun_top .bulk $2d,$2d,$3f,$27,$24,$2c,$24,$34,$36,$35,$36,$06,$00 ; ; Returns a pointer stored as inline data following the caller's caller's JSR. ; ; On exit: ; $10-11: pointer ; A-reg/X-reg/Y-reg preserved ; ]ptr .var $10 {addr/2} 14ea: 48 GetInlinePtr pha ;preserve A-reg 14eb: 8a txa 14ec: 48 pha ;preserve X-reg 14ed: ba tsx 14ee: bd 05 01 lda STACK+5,x ;extract the pointer 14f1: 85 10 sta ]ptr 14f3: bd 06 01 lda STACK+6,x 14f6: 85 11 sta ]ptr+1 14f8: 68 pla 14f9: aa tax ;restore X-reg 14fa: 68 pla ;restore A-reg 14fb: 60 rts ; ; Adjusts the return address on the stack to move past the inline string. The ; caller must jump here, not JSR. ; ; On entry: ; Y-reg: string length ; ]tmp .var $10 {addr/1} 14fc: 98 AdjustRetAddr tya ;move length to A-reg (why?) 14fd: 85 10 sta ]tmp ;store in DP 14ff: 68 pla ;pull low byte of return addr 1500: 18 clc 1501: 65 10 adc ]tmp ;add the length 1503: 85 10 sta ]tmp ;store result in DP 1505: 90 04 bcc :NoInc ;if we didn't cross a page, branch 1507: 68 pla ;need to update high byte, so pull that 1508: 69 00 adc #$00 150a: 48 pha 150b: a5 10 :NoInc lda ]tmp ;get low byte 150d: 48 pha ;push it back on 150e: 60 rts ;return to updated address ; ; Unused function, probably used for issuing DOS commands. ; ]ptr .var $10 {addr/2} 150f: a9 84 InS_unref_150f? lda #$84 ;Ctrl+D 1511: 20 ed fd jsr MON_COUT 1514: 20 ea 14 jsr GetInlinePtr ;get pointer to string 1517: a0 00 ldy #$00 1519: 8c 20 15 sty :_Index+1 151c: ee 20 15 :Loop inc :_Index+1 151f: a0 00 :_Index ldy #$00 ;track index with self-mod code 1521: b1 10 lda (]ptr),y 1523: f0 d7 beq AdjustRetAddr ;done, move return addr to end of string 1525: 20 ed fd jsr MON_COUT ;use COUT to allow interception 1528: 4c 1c 15 jmp :Loop ; ; Prints a high-ASCII string that immediately follows the JSR to this function. ; 152b: 20 ea 14 InS_PrintString jsr GetInlinePtr ;get pointer to string 152e: a0 00 ldy #$00 1530: 8c 37 15 sty :_Index+1 1533: ee 37 15 :Loop inc :_Index+1 1536: a0 00 :_Index ldy #$00 ;track index with self-mod code 1538: b1 10 lda (]ptr),y 153a: f0 c0 beq AdjustRetAddr ;done, move return addr to end of string 153c: 20 f0 fd jsr MON_COUT1 ;use COUT1 to bypass I/O hooks 153f: 4c 33 15 jmp :Loop ; ; Sets the text position to a row and column specified by inline data following ; the call to this function. First byte is row (0-23), second byte is column ; (0-39). ; 1542: 20 4c 15 InXY_SetTextPos jsr GetInlineWord 1545: 85 25 sta MON_CV ;set text vertical position 1547: 84 24 sty MON_CH ;set text horizontal position 1549: 4c 22 fc jmp MON_VTAB ;update pointer ; ; Gets an inline word from the caller's caller. Update's the return address ; from the calling function to walk past it. ; ; If the retrieved value is $42xx, the high byte is replaced with the value at ; $18 (high byte of pointer at $17-18). ; ; On exit: ; A-reg: low byte ; Y-reg: high byte ; 154c: ba GetInlineWord tsx ;get stack pointer 154d: bd 03 01 lda STACK+3,x ;read from stack+3 to get caller's caller 1550: 85 10 sta ]ptr ;(address is last byte of JSR, e.g. $0829) 1552: bd 04 01 lda STACK+4,x 1555: 85 11 sta ]ptr+1 1557: a0 01 ldy #$01 ;add one to get byte following JSR 1559: b1 10 lda (]ptr),y ;get first (low) byte 155b: 48 pha ;save it 155c: c8 iny 155d: b1 10 lda (]ptr),y ;get second (high) byte 155f: c9 42 cmp #$42 ;$42xx? 1561: d0 02 bne :Not42 1563: a5 18 lda missile_ptr+1 ;substitute value in $18 1565: a8 :Not42 tay ;put high byte in Y-reg 1566: 68 pla ;put low byte in A-reg 1567: 20 6a 15 jsr :IncVal ;increment twice to get past value 156a: fe 03 01 :IncVal inc STACK+3,x 156d: d0 03 bne :NoInc 156f: fe 04 01 inc STACK+4,x 1572: 60 :NoInc rts ; ; Gets a 5-byte "packed" floating-point value, and loads it into Applesoft's ; FAC. The address is specified by inline data following the JSR to this ; function. ; ; On entry: ; A-reg: destination address (low) ; Y-reg: destination address (high) ; 1573: 20 4c 15 InA_LoadFloat jsr GetInlineWord ;get word from caller's caller 1576: 4c f9 ea jmp BAS_LOAD_FAC_FROM_YA ;load it into Applesoft FP register ; ; Converts an inline integer value to a float in FAC. ; 1579: 20 4c 15 unref_1579? jsr GetInlineWord 157c: 8c 81 15 sty :_Aval+1 ;Y-reg has high byte, but func wants it as low 157f: a8 tay ;move low byte to Y-reg 1580: a9 00 :_Aval lda #$00 ;high byte in A-reg 1582: 4c f2 e2 jmp BAS_GIVAYF ;convert value to float ; ; Computes FAC = (inline_ref) + FAC. ; 1585: 20 4c 15 InA_AddFloat jsr GetInlineWord 1588: 4c be e7 jmp BAS_FADD ; ; Computes FAC = (inline_ref) - FAC. ; 158b: 20 4c 15 InA_SubFloat jsr GetInlineWord 158e: 4c a7 e7 jmp BAS_FSUB ; ; Computes FAC = (inline_ref) * FAC. ; 1591: 20 4c 15 InA_MulFloat jsr GetInlineWord 1594: 4c 7f e9 jmp BAS_FMULT ; ; Computes FAC = (inline_ref) / FAC. ; 1597: 20 4c 15 InA_DivFloat jsr GetInlineWord 159a: 4c 66 ea jmp BAS_FDIV ; ; Computes FAC = (inline_ref) ^ FAC. ; InA_unref_ExpFloat 159d: 20 4c 15 jsr GetInlineWord 15a0: 20 e3 e9 jsr BAS_LOAD_ARG_FROM_YA 15a3: 4c 97 ee jmp BAS_FPWRT ; ; Compares float referenced by inline value to FAC. ; ; On exit: ; A-reg: {1,0,-1} as (Y,A) <,=,> FAC ; Status flags set according to A-reg ; 15a6: 20 4c 15 InA_CmpFloat jsr GetInlineWord 15a9: 4c b2 eb jmp BAS_FCOMP ; ; Rounds the floating point value in FAC, then stores the value at the address ; specified inline after the caller's JSR. ; ; On entry: ; X-reg: destination address (low) ; Y-reg: destination address (high) ; 15ac: 20 4c 15 InA_StoreFloat jsr GetInlineWord ;get address in (Y,A) 15af: aa tax 15b0: 4c 2b eb jmp BAS_STORE_FAC_AT_YX_ROUNDED ;round FAC, and store at (Y,X) ; ; Unused: print a float as an integer. ; unref_PrintFloat 15b3: aa tax 15b4: 98 tya 15b5: 4c 24 ed jmp BAS_LINPRT ;print float at (A,X) as integer ; ; Draws expanding circle for explosions. ; ; The tables define a circle as a series of pixels drawn from the center ; outward, sort of like drawing a spiral. The code starts at index M and stops ; at index N, drawing part of the circle on each call, expanding outward. By ; remembering the stop index and passing it in on subsequent iterations, the ; circle can be drawn incrementally. ; ; The number of iterations we do per visit determines how quickly the circle ; expands per frame. The total pixel count determines the radius. Each entry ; in the table determines 4 pixels, one per quadrant, and we're drawing "color ; pixels" which are twice as wide as they are high. So a 256-entry table covers ; 2048 pixels, which math says is enough for a circle of radius 25. The largest ; displacement in the table is 24, to which we add 1 for the center pixel. ; ; At max radius we offset +/- 24 pixels, so the maximum Y coordinate value ; (assuming a valid center point) is [-24,215], which we can check with a simple ; test. There's no risk of wrapping around the screen. ; ; ABMs use radius $50 (80) for outer/center, $70 (112) for inner. Missiles use ; a randomized radius between 96 and 176. ; ; Note to hackers: don't use a size larger than ($100 - iterations), or the code ; can get stuck. If you're using an iteration count of 8, don't exceed $f8. ; The integer rolls over and the game doesn't realize the explosion should stop. ; Make the size a multiple of the count. ; ; Set the HCOLOR before calling. ; ; On entry: ; X-reg: center X position (0-139) ; A-reg: center Y position (0-191) ; Y-reg: iterations (8 or 16) ; (set the initial index directly, starting from zero) ; ; On exit: ; Index updated (+= iteration count) ; 15b8: 8d d2 15 DrawExplosion sta :_DrawCircY+1 ;set center Y 15bb: 8e c7 15 stx :_DrawCircX+1 ;set center X 15be: 8c 3c 16 sty :_CircleCount+1 ;init iterations ; Note the DrawPixel function takes the column (0-139) in X-reg, and the row (0- ; 191) in A-reg. Y-reg doesn't matter. 15c1: ce 3c 16 :DrawCircLoop dec :_CircleCount+1 ;count down interations ; Start with bottom-right quadrant. 15c4: a0 00 _DrawCircIndex ldy #$00 15c6: a9 00 :_DrawCircX lda #$00 ;get center X 15c8: 18 clc 15c9: 79 43 16 adc circle_x_data,y ;add horizontal offset 15cc: aa tax ;copy to X-reg 15cd: c9 8c cmp #140 ;range check 0 <= X <= 140 15cf: b0 0b bcs :SkipBR ;went past, don't draw pixel 15d1: a9 00 :_DrawCircY lda #$00 ;get center Y 15d3: 18 clc 15d4: 79 43 17 adc circle_y_data,y ;add vertical offset 15d7: a0 00 ldy #$00 ;(?) 15d9: 20 07 14 jsr DrawPixel ;draw bottom-right pixel ; Bottom-Left quadrant. 15dc: ac c5 15 :SkipBR ldy _DrawCircIndex+1 15df: ad c7 15 lda :_DrawCircX+1 15e2: 38 sec 15e3: f9 43 16 sbc circle_x_data,y 15e6: aa tax 15e7: c9 8c cmp #140 ;check vs. left edge (negative) 15e9: b0 0c bcs :SkipBL 15eb: ad d2 15 lda :_DrawCircY+1 15ee: 18 clc 15ef: 79 43 17 adc circle_y_data,y ;not range-checked: we don't draw circles that low 15f2: a0 00 ldy #$00 ;(?) 15f4: 20 07 14 jsr DrawPixel ;draw bottom-left pixel ; Upper-right quadrant. 15f7: ac c5 15 :SkipBL ldy _DrawCircIndex+1 15fa: ad c7 15 lda :_DrawCircX+1 15fd: 18 clc 15fe: 79 43 16 adc circle_x_data,y 1601: aa tax 1602: c9 8c cmp #140 ;check vs. right edge 1604: b0 10 bcs :SkipUR 1606: ad d2 15 lda :_DrawCircY+1 1609: 38 sec 160a: f9 43 17 sbc circle_y_data,y 160d: c9 be cmp #190 ;check vs. top (negative) 160f: b0 05 bcs :SkipUR 1611: a0 00 ldy #$00 ;(?) 1613: 20 07 14 jsr DrawPixel ;draw upper-right pixel ; Upper-left quadrant. 1616: ac c5 15 :SkipUR ldy _DrawCircIndex+1 1619: ad c7 15 lda :_DrawCircX+1 161c: 38 sec 161d: f9 43 16 sbc circle_x_data,y 1620: aa tax 1621: c9 8c cmp #140 ;check vs. left edge (negative) 1623: b0 10 bcs :SkipUL 1625: ad d2 15 lda :_DrawCircY+1 1628: 38 sec 1629: f9 43 17 sbc circle_y_data,y 162c: c9 be cmp #190 ;check vs. top (negative) 162e: b0 05 bcs :SkipUL 1630: a0 00 ldy #$00 ;(?) 1632: 20 07 14 jsr DrawPixel ;draw upper-left pixel ; All pixels are drawn; update state. 1635: ee c5 15 :SkipUL inc _DrawCircIndex+1 1638: 20 5c 13 jsr PlaySounds ;update sound effects once per iteration 163b: a2 00 :_CircleCount ldx #$00 ;done yet? 163d: f0 03 beq :Return ;yes, bail 163f: 4c c1 15 jmp :DrawCircLoop 1642: 60 :Return rts ; ; Pixel offsets for incrementally-drawn circle. The X offsets are for double- ; wide color pixels, so represent twice the distance of the Y offsets. ; ; The values are all +X/+Y, representing the first quadrant. ; 1643: 00 00 00 01+ circle_x_data .bulk $00,$00,$00,$01,$01,$01,$00,$01,$00,$02,$02,$01,$02,$00,$02,$01 + $02,$00,$03,$03,$01,$03,$02,$03,$00,$02,$03,$01,$03,$00,$04,$02 + $04,$01,$04,$03,$04,$02,$00,$04,$01,$02,$03,$04,$02,$00,$03,$04 + $05,$05,$01,$05,$05,$04,$02,$05,$03,$00,$01,$05,$04,$03,$05,$02 + $00,$06,$04,$06,$01,$06,$05,$06,$03,$02,$06,$04,$05,$00,$06,$01 + $03,$06,$05,$02,$04,$06,$00,$07,$07,$01,$05,$07,$03,$07,$04,$06 + $02,$07,$05,$07,$00,$06,$01,$03,$07,$04,$02,$05,$06,$07,$00,$08 + $08,$01,$04,$07,$08,$03,$06,$08,$05,$02,$08,$07,$08,$06,$00,$04 + $03,$08,$01,$05,$07,$02,$08,$06,$07,$04,$08,$00,$09,$03,$05,$09 + $01,$09,$09,$08,$02,$06,$07,$09,$09,$04,$05,$08,$03,$09,$00,$01 + $07,$06,$09,$02,$08,$04,$09,$05,$07,$03,$00,$06,$08,$0a,$0a,$01 + $0a,$09,$0a,$02,$0a,$07,$05,$09,$04,$08,$0a,$06,$03,$0a,$00,$01 + $09,$0a,$07,$08,$02,$05,$04,$0a,$06,$09,$03,$08,$0a,$00,$0b,$07 + $0b,$01,$0b,$09,$0b,$02,$05,$0a,$0b,$04,$06,$0b,$08,$03,$07,$09 + $0b,$0a,$00,$01,$0b,$05,$06,$0a,$02,$08,$04,$0b,$09,$07,$03,$0b + $0a,$00,$0c,$0c,$01,$08,$09,$0c,$05,$0b,$06,$0c,$02,$0c,$04,$07 1743: 00 01 02 00+ circle_y_data .bulk $00,$01,$02,$00,$01,$02,$03,$03,$04,$00,$01,$04,$02,$05,$03,$05 + $04,$06,$00,$01,$06,$02,$05,$03,$07,$06,$04,$07,$05,$08,$00,$07 + $01,$08,$02,$06,$03,$08,$09,$04,$09,$09,$07,$05,$09,$0a,$08,$06 + $00,$01,$0a,$02,$03,$07,$0a,$04,$09,$0b,$0b,$05,$08,$0a,$06,$0b + $0c,$00,$09,$01,$0c,$02,$07,$03,$0b,$0c,$04,$0a,$08,$0d,$05,$0d + $0c,$06,$09,$0d,$0b,$07,$0e,$00,$01,$0e,$0a,$02,$0d,$03,$0c,$08 + $0e,$04,$0b,$05,$0f,$09,$0f,$0e,$06,$0d,$0f,$0c,$0a,$07,$10,$00 + $01,$10,$0e,$08,$02,$0f,$0b,$03,$0d,$10,$04,$09,$05,$0c,$11,$0f + $10,$06,$11,$0e,$0a,$11,$07,$0d,$0b,$10,$08,$12,$00,$11,$0f,$01 + $12,$02,$03,$09,$12,$0e,$0c,$04,$05,$11,$10,$0a,$12,$06,$13,$13 + $0d,$0f,$07,$13,$0b,$12,$08,$11,$0e,$13,$14,$10,$0c,$00,$01,$14 + $02,$09,$03,$14,$04,$0f,$12,$0a,$13,$0d,$05,$11,$14,$06,$15,$15 + $0b,$07,$10,$0e,$15,$13,$14,$08,$12,$0c,$15,$0f,$09,$16,$00,$11 + $01,$16,$02,$0d,$03,$16,$14,$0a,$04,$15,$13,$05,$10,$16,$12,$0e + $06,$0b,$17,$17,$07,$15,$14,$0c,$17,$11,$16,$08,$0f,$13,$17,$09 + $0d,$18,$00,$01,$18,$12,$10,$02,$16,$0a,$15,$03,$18,$04,$17,$14 ; ; Sound effect modifier. 1843: 00 sound_fx_mod .dd1 $00 1844: 00 00 00 00+ .junk 6 ; ; Status of the six cities: ; $ff - undamaged ; $80 - damaged ; $00 - destroyed 184a: ff ff ff ff+ city_status .bulk $ff,$ff,$ff,$ff,$ff,$ff ; ; Horizontal column of city center (0-39). 1850: 02 09 10 17+ city_cols .bulk $02,$09,$10,$17,$1e,$25 ; ; Evaluate damage done to cities. ; 1856: a2 ff EvalCityDamage ldx #$ff 1858: 8e 6c 18 stx :_Loop+1 185b: e8 inx ;X-reg=0 185c: a0 00 ldy #$00 ;init surviving city count to zero 185e: 8e be 18 stx :_CityLoopDone+1 1861: a9 9b lda #155 ;line 155 (five above text window), left column 1863: 20 11 f4 jsr BAS_HPOSN 1866: 20 42 15 jsr InXY_SetTextPos 1869: 14 .dd1 20 ;top row of text window 186a: 00 .dd1 0 ;left edge ; Loop over all six cities, checking for damage. 186b: a2 00 :_Loop ldx #$00 186d: e8 inx 186e: 8e 6c 18 stx :_Loop+1 1871: e0 06 cpx #$06 ;done yet? 1873: f0 48 beq :_CityLoopDone ;yes, branch 1875: bd 50 18 lda city_cols,x ;get horizontal offset of city center (0-39) 1878: a8 tay ;use as index into hi-res page 1879: b1 26 lda (BAS_HBASL),y ;read hi-res byte at (offset*7, 155) 187b: d0 1a bne :CityNotDead ;still some pixels here, city not destroyed 187d: bd 4a 18 lda city_status,x ;check status 1880: f0 e9 beq :_Loop ;already destroyed, nothing to do ; Destroy city. 1882: a9 00 lda #$00 1884: 9d 4a 18 sta city_status,x ;set status to destroyed 1887: 88 dey ;back up one space 1888: a2 00 ldx #$00 188a: a9 ad lda #“-” 188c: 91 28 :DashLoop sta (MON_BASL),y ;ovewrite city initials with hyphens 188e: c8 iny 188f: e8 inx 1890: e0 03 cpx #$03 1892: 90 f8 bcc :DashLoop 1894: 4c 6b 18 jmp :_Loop ;check next 1897: ee be 18 :CityNotDead inc :_CityLoopDone+1 ;increment number of surviving cities 189a: d9 52 0b cmp orig_row_155,y ;check for damage 189d: f0 cc beq :_Loop ;no damage, move on to next 189f: bd 4a 18 lda city_status,x ;check city status 18a2: c9 80 cmp #$80 ;already marked as damaged? 18a4: f0 c5 beq :_Loop ;yes, move on to next 18a6: a9 80 lda #$80 18a8: 9d 4a 18 sta city_status,x ;set status to damaged 18ab: 88 dey ;back up one space from center 18ac: a2 00 ldx #$00 18ae: b1 28 :InvertLoop lda (MON_BASL),y ;invert the city abbrev (or whatever is there) 18b0: 29 3f and #$3f 18b2: 91 28 sta (MON_BASL),y 18b4: c8 iny 18b5: e8 inx 18b6: e0 03 cpx #$03 18b8: 90 f4 bcc :InvertLoop 18ba: 4c 6b 18 jmp :_Loop 18bd: a9 00 :_CityLoopDone lda #$00 ;check surviving city count 18bf: f0 01 beq :NoCitiesLeft ;all destroyed, game is over 18c1: 60 rts 18c2: 68 :NoCitiesLeft pla ;pop return address 18c3: 68 pla 18c4: ad f0 0b lda auto_play_flag ;attract mode? 18c7: f0 03 beq ShowFinalScore ;no, show player their score 18c9: 4c 13 08 jmp Restart ;yes, just go back to title screen ; ; Shows the final score after the game ends. ; ; base_score = ((missiles_destroyed - 6) * 10) / (ABMs_fired + 100) ; score = floor(base_score * 100) * 10 ; 18cc: 8d 56 c0 ShowFinalScore sta LORES ;lo-res graphics (why?) 18cf: 8d 51 c0 sta TXTSET ;text mode (redundant with next call) 18d2: 20 39 fb jsr MON_SETTXT ;text mode, reset window 18d5: ad f2 0b lda mssl_destroy_cnt+1 ;get number of missiles destroyed 18d8: ac f1 0b ldy mssl_destroy_cnt 18db: 20 f2 e2 jsr BAS_GIVAYF ;convert to float 18de: 20 42 15 jsr InXY_SetTextPos 18e1: 0a .dd1 10 18e2: 0a .dd1 10 18e3: 20 2b 15 jsr InS_PrintString 18e6: d9 cf d5 a0+ .zstr “YOU SCORED ” 18f2: 20 8b 15 jsr InA_SubFloat ;subtract 6, because missiles "destroyed" by blowing 18f5: 09 0c .dd2 const_6 ; up a city don't count toward your score 18f7: 20 d0 ee jsr BAS_NEGOP 18fa: 20 82 eb jsr BAS_SIGN ;did it go negative? 18fd: 10 0c bpl :NotNeg ;no, branch 18ff: a9 00 lda #$00 ;yes, set it to zero 1901: 85 9d sta BAS_FAC 1903: 85 9e sta BAS_FAC+1 1905: 85 9f sta BAS_FAC+2 1907: 85 a0 sta BAS_FAC+3 1909: 85 a1 sta BAS_FAC+4 190b: 20 39 ea :NotNeg jsr BAS_MUL10 ;multiply by 10 190e: 20 ac 15 jsr InA_StoreFloat ;save it 1911: 1d 0c .dd2 score_m_temp 1913: ad f4 0b lda abm_fired_cnt+1 ;get number of ABMs launched 1916: ac f3 0b ldy abm_fired_cnt 1919: 20 f2 e2 jsr BAS_GIVAYF ;convert to float 191c: 20 85 15 jsr InA_AddFloat ;add 100 191f: 04 0c .dd2 const_100 1921: 20 97 15 jsr InA_DivFloat ;divide (missiles-6) by (ABM+100) 1924: 1d 0c .dd2 score_m_temp 1926: 20 39 ea jsr BAS_MUL10 ;multiply by 10 1929: 20 39 ea jsr BAS_MUL10 ;again (* 100) 192c: 20 23 ec jsr BAS_INT ;truncate fractional portion 192f: 20 39 ea jsr BAS_MUL10 ;multiply by 10 (* 1000) 1932: 20 ac 15 jsr InA_StoreFloat ;save it 1935: 2c 0c .dd2 score_temp 1937: 20 2e ed jsr BAS_PRINT_FAC ;print score 193a: 20 42 15 jsr InXY_SetTextPos ;move down a line 193d: 0b .dd1 11 193e: 0a .dd1 10 193f: 20 2b 15 jsr InS_PrintString 1942: c6 cf d2 a0+ .zstr “FOR DOWNING ” 194f: ad f2 0b lda mssl_destroy_cnt+1 ;get count of missiles destroyed 1952: ae f1 0b ldx mssl_destroy_cnt 1955: 20 24 ed jsr BAS_LINPRT ;print it 1958: 20 42 15 jsr InXY_SetTextPos ;move down a line 195b: 0c .dd1 12 195c: 0a .dd1 10 195d: 20 2b 15 jsr InS_PrintString 1960: cd c9 d3 d3+ .zstr “MISSILES WITH ” 196f: ad f4 0b lda abm_fired_cnt+1 ;get count of ABMs fired 1972: ae f3 0b ldx abm_fired_cnt 1975: 20 24 ed jsr BAS_LINPRT ;print it 1978: 20 42 15 jsr InXY_SetTextPos ;move down a line 197b: 0d .dd1 13 197c: 0a .dd1 10 197d: 20 2b 15 jsr InS_PrintString 1980: c1 c2 cd a7+ .zstr “ABM'S.” 1987: 20 42 15 jsr InXY_SetTextPos ;move up several rows 198a: 05 .dd1 5 198b: 0a .dd1 10 198c: 20 2b 15 jsr InS_PrintString 198f: c8 c9 c7 c8+ .zstr “HIGH SCORE IS ” 199e: 20 73 15 jsr InA_LoadFloat ;get high score 19a1: 31 0c .dd2 high_score 19a3: 20 2e ed jsr BAS_PRINT_FAC ;print it 19a6: 20 73 15 jsr InA_LoadFloat ;get current score in FAC 19a9: 2c 0c .dd2 score_temp 19ab: 20 a6 15 jsr InA_CmpFloat ;compare to high score 19ae: 31 0c .dd2 high_score 19b0: 30 2a bmi :NotHigh ;not new high score, branch 19b2: 20 ac 15 jsr InA_StoreFloat ;store new high score 19b5: 31 0c .dd2 high_score 19b7: a9 3f lda #$3f 19b9: 85 32 sta MON_INVFLAG ;inverse text 19bb: 20 42 15 jsr InXY_SetTextPos ;move down one row 19be: 06 .dd1 6 19bf: 0c .dd1 12 19c0: 20 2b 15 jsr InS_PrintString 19c3: c2 c5 c6 cf+ .zstr “BEFORE THIS GAME!” 19d5: a9 ff lda #$ff 19d7: 85 32 sta MON_INVFLAG ;normal text 19d9: 20 9e 09 jsr WriteHighScore ;save score to disk 19dc: 20 42 15 :NotHigh jsr InXY_SetTextPos ;move to bottom of screen 19df: 17 .dd1 23 19e0: 05 .dd1 5 19e1: 20 2b 15 jsr InS_PrintString 19e4: d0 d2 c5 d3+ .zstr “PRESS RETURN TO PLAY AGAIN.” ; Wait until key hit or timer expires. 1a00: 8d 10 c0 sta KBDSTRB ;clear keyboard strobe 1a03: a2 d0 ldx #$d0 1a05: 8e ef 0b stx temp+1 ;set timer 1a08: 8e ee 0b stx temp 1a0b: ee ee 0b :WaitLoop inc temp 1a0e: d0 08 bne :NotExp 1a10: ee ef 0b inc temp+1 1a13: d0 03 bne :NotExp 1a15: 4c 13 08 jmp Restart ;timer expired, restart at title 1a18: ad 00 c0 :NotExp lda KBD ;key hit? 1a1b: 30 06 bmi :KeyHit 1a1d: ca dex 1a1e: d0 f8 bne :NotExp 1a20: 4c 0b 1a jmp :WaitLoop 1a23: 8d 10 c0 :KeyHit sta KBDSTRB 1a26: a9 00 lda #$00 1a28: 8d f0 0b sta auto_play_flag ;clear auto-play flag (unnecessary?) 1a2b: 4c d4 09 jmp StartGame ;start a new game ; ; Unused junk saved with file. ; 1a2e: d2 d3 cf d2+ .junk 1489
No exported symbols found.