********************************************************************************
* Missile Command (rev 3) *
* By Dave Theurer and Rich Adam *
* Copyright 1980 Atari, Inc. *
********************************************************************************
* Disassembly by Andy McFadden, using 6502bench SourceGen v1.8. *
* Last updated 2021/11/30. *
* *
* Thanks to: *
* + The various MAME contributors. *
* *
* POKEY register descriptions from https://en.wikipedia.org/wiki/POKEY . *
* *
* The binary used is a concatenation of the six 2KB ROMs that are addressable *
* by the 6502. The differences from the rev 2 ROM set are small in scope, *
* affecting only a couple of routines, but fix a pair of bugs that greatly *
* impact world-record score attempts (see the rev 2 diffs page). *
* *
* Terminology: I generally refer to incoming missiles as "ICBMs" (Inter- *
* Continental Ballistic Missile), missiles fired by the player as "ABMs" *
* (Anti-Ballistic Missiles), missiles that split as "MIRVs" (Multiple *
* Independent Rentry Vehicle), and planes and satellites as "fliers". The *
* terms aren't necessarily accurate, but they're short and avoid ambiguity. *
* *
* A few days after I started this, the original sources were posted to *
* https://github.com/historicalsource/missile-command . I was unaware of them *
* until after this was completed. I've updated the Notes in the project to *
* make it easier to match the diassembly to the sources (e.g. "A35820.1A" is *
* the name of a source file). *
********************************************************************************
* Memory map in a nutshell: *
* $0000-01ff: RAM (zero page, stack; most game state is here) *
* $0200-05ff: video RAM - 3rd color bit for last 32 lines *
* $0600-063f: RAM (game state) *
* $0640-3fff: video RAM - 2-bit color values *
* $4000-4fff: I/O ports *
* $5000-7fff: game ROM *
* *
* The video output has a resolution of 256x231. Most of the screen uses 2 *
* bits per pixel to index into a color palette; packed tightly into bytes, we *
* need 256x231/4 = $39c0 bytes (= $4000-$640). Bytes are arranged linearly, *
* but the bits are split across nibbles, and the low bit of the palette index *
* is zero. If the bits in the first byte of the line are %ABCDabcd, then the *
* color index for the pixel at X=0 is %00000Dd0, X=1 is %00000Cc0, etc. *
* *
* The bottom 32 lines, which includes the area with the cities, has a 3rd *
* color bit that is added as the low bit of the color index. This occupies *
* 1024 bytes at $0200. Adjacent lines are still separated by 64 bytes in *
* memory, but the first 16 lines are stored in even bytes and the second 16 in *
* odd bytes. For example, the bits for the leftmost 16 pixels of line 199 are *
* at $0200 and $0202, while those for line 215 are at $0201 and $0203. *
* *
* If you access memory with "LDA ($ZZ,X)" or "STA ($ZZ,X)", the address and *
* data is remapped to a layout with one byte per pixel (look for "MADSEL" in *
* the schematics). Color indices are stored as %XYZ00000, where XY are the *
* 2bpp color values, and Z is the third bit (always zero until you reach the *
* 199th line at $e000). For example, if you want to set the color at X=128 *
* Y=103 to %110, you can either set $2020=$11 with "STA addr", or set *
* $8080=$c0 with "STA (ZP,X)". *
* *
* I/O port definitions (from MAME driver and manual): *
* *
* IN0 ($4800 if CTRLD is 0): CCC12LMR *
* CCC=coin slots (R/C/L) *
* 1=player 1 start *
* 2=player 2 start *
* L=2nd player left fire (cocktail cabinet) *
* M=2nd player center fire *
* R=2nd player right fire *
* *
* IN0 ($4800 if CTRLD is 1): VVVVHHHH *
* HHHH: horizontal trackball displacement *
* VVVV: vertical trackball displacement *
* *
* OUT0 ($4800): UFCCC21D *
* U=unused? *
* F=screen flip (1=normal orientation) *
* CCC=coin counter (L/C/R) *
* 2=2-player start LED (0=lit) *
* 1=1-player start LED (0=lit) *
* D=CTRLD (0=read switches, 1=read trackball) *
* *
* IN1 ($4900): VTSHVLMR *
* V=VBLANK read (1=in VBLANK) *
* T=self test switch (0=do test) *
* S=slam switch (0=slammed) *
* H=horiz trackball direction input (?) *
* V=vert trackball direction input (?) *
* L=1st player left fire (0=pressed) *
* M=1st player center fire *
* R=1st player right fire *
* *
* Switch R8 ($4808, POKEY_ALLPOT): KBBBTQCC *
* K=cocktail cabinet (1=enable flip) *
* BBB=bonus city every N points *
* T=large trak-ball input (off for upright games) *
* Q=bonus credit for 4 successive quarters *
* CC=starting cities (4-7) *
* *
* Switch R10 ($4a00): ULLCCCPP *
* U=unused *
* LL=language (00=en, 01=fr, 10=de, 11=es) *
* CCC=coin mechanism config *
* PP=coins/play *
* *
* Color palette ($4b00-4b07): UUUURGBU x8 *
* UUUU=unused *
* RGB=color (0=on, 1=off, so 111 is black and 001 is yellow) *
* U=unused *
********************************************************************************
* The missile/smartbomb tracking tables have 16 entries. The first 8 are for *
* ABMs, the last 8 are for ICBMs or smartbombs. The current-position table is *
* followed by the flier's position, so some of the code treats the flier like *
* a 9th ICBM. Most code that deals with missiles takes a "slot index" (0-15), *
* but some of the ICBM code takes an "ICBM index" instead (0-7). *
********************************************************************************
FN_INIT_GAME .eq $00 {const}
SFX_SILO_LOW .eq $01 {const} ;sound effect: silo low warning
FN_PREP_WAVE .eq $02 {const}
SFX_EXPLOSION .eq $02 {const} ;sound effect: explosion
FN_PLAY_GAME .eq $04 {const}
SFX_ABM_LAUNCH .eq $04 {const} ;sound effect: ABM launch
FN_GAME_OVER .eq $06 {const}
FN_END_WAVE .eq $08 {const}
SFX_BONUS_PTS .eq $08 {const} ;sound effect: bonus points awarded
FN_ABM_BONUS .eq $0a {const}
FN_CITY_BONUS_CHK .eq $0c {const}
FN_CITY_BONUS_PTS .eq $0e {const}
FN_WAVE_DONE_UPD .eq $10 {const}
SFX_START_WAVE .eq $10 {const} ;sound effect: wave starting
FN_THE_END .eq $12 {const}
FN_SHOW_TITLE .eq $14 {const}
FN_TITLE_EXPLS .eq $16 {const}
FN_HS_CHECK .eq $18 {const}
FN_ENTER_INITIALS .eq $1a {const}
FN_SHOW_HS .eq $1c {const}
FN_HALF_CREDIT .eq $1e {const}
FN_WAIT_FULL_CREDIT .eq $20 {const}
SFX_GAME_OVER .eq $20 {const} ;sound effect: game over
FN_PAUSE .eq $22 {const}
SFX_BONUS_CITY .eq $40 {const} ;sound effect: bonus city used
SFX_CANT_FIRE .eq $80 {const} ;sound effect: can't fire ABM
tmp_02 .eq $02 ;general use
gfx_ptr .eq $06 {addr/2} ;pointer used for graphics
preserve_bg_flag .eq $08 ;if hi bit set, preserve bg when drawing bitmap
last_cross_xc .eq $09 ;previously-drawn crosshairs X-coord
last_cross_yc .eq $0a ;previously-drawn crosshairs Y-coord
draw_fg_color .eq $0b ;foreground color for bitmap drawing routines
draw_bg_color .eq $0c ;background color for bitmap drawing routines
cross_back_store .eq $0f {addr/14} ;backing store for crosshair drawing
txsc_out_idx .eq $25 ;text scroll: index into text-scroll message index table
txsc_msg_offset .eq $26 ;text scroll: offset within current message
txsc_char_delay .eq $27 ;text scroll: frames until next char is output
txsc_play_cost .eq $28 ;text scroll: play cost message
txsc_credits .eq $29 ;text scroll: last shown credit count
msg_base_ptr .eq $2a {addr/2} ;pointer used when drawing messages
hscore_initials .eq $2c {addr/24} ;8 scores, 3 ASCII chars each
hscore_scores .eq $44 {addr/24} ;8 scores, 3 BCD bytes each
coin_timer1? .eq $5c {addr/3} ;coin slot timers, 1 per slot
slam_tone_ctr1 .eq $5f ;set to $f0 on tilt (in IRQ)
coin_timer2? .eq $60 {addr/3} ;coin slot timers, 1 per slot
coin_drop_ctr .eq $63 {addr/3} ;left, center, right
credit_count .eq $66 ;number of credits purchased (0-40)
coins_inserted .eq $67 ;coins not yet spent on credits
recent_coins .eq $68 ;coins inserted since last game, for bonus-for-4
last_sound_started .eq $69 ;last sound started, used for bonus city
audio_indices .eq $6a {addr/8} ;index into sfx table, nonzero if active
audio_values .eq $72 {addr/8} ;values copied to POKEY $4000-4007
audio_dur_ctr .eq $7a {addr/8} ;duration counter, updated during IRQ
audio_rep_ctr .eq $82 {addr/8} ;repetition counter, updated during IRQ
engine_sfx_flags .eq $8a ;bits set for fliers and smartbombs
engine_sfx_freq .eq $8b ;current frequency for engine sound
num_live_icbms .eq $8c ;number of active ICBMs on screen
wave_icbm_count .eq $8d ;#of ICBMs appearing in this wave
num_explosions .eq $8e ;number of active explosions
num_expl_abms .eq $8f ;number of exploding ABMs
num_expl_icbms .eq $90 ;number of exploding ICBMs
func_index .eq $91 ;top-level function index (0-34, by 2)
next_func_index .eq $92 ;func to switch to after delay
play_mode_flag .eq $93 ;bool 00/ff: is a game in progress?
trk_h_delta .eq $94 ;trackball horizontal movement
trk_v_delta .eq $95 ;trackball vertical movement
next_expl_slot .eq $96 ;next explosion slot
slot_index .eq $97 ;used to pass slot index for ABMs and smart bombs
tmp_98 .eq $98 ;general use
tmp_99 .eq $99 ;general use
exec_flag .eq $9f ;incremented by IRQ, zeroed by main loop
abms_left .eq $a0 {addr/3} ;#of ABMs left at each silo (0-10)
cross_frac .eq $a3 {addr/2} ;fractional part of crosshair X/Y posn
cross_xc .eq $a5 ;current crosshair X-coord
cross_yc .eq $a6 ;current crosshair Y-coord, 0 if hidden
wave_num .eq $a7 ;current wave number, starts at 1
num_players .eq $ae ;number of players minus 1 (0 or 1)
frame4_delay_ctr .eq $af ;counts down frames before continuing in func $22
icbm_move_ctr .eq $b2 {addr/2} ;ICBM movement counter, as 8.8 value
exp_seq_index .eq $b4 ;explosion sequence index (0-4)
icbm_move_delay .eq $b7 {addr/2} ;delay between ICBM updates, as 8.8 value
cur_plyr_num .eq $b9 ;current player number (0/1)
base_score_bcd .eq $ba {addr/2} ;$25 * current score multiplier
active_abm_count .eq $bc ;number of ABMs in flight?
incoming_min_y .eq $bd ;Y coord of lowest bomb/icbm (unused?)
incoming_max_y .eq $be ;Y-coord of highest bomb/missile
full_speed_flag .eq $bf ;bool 00/ff: move everything at full speed?
num_cities .eq $c0 {addr/2} ;total cities, both live and bonus (per plyr)
mirv_slot .eq $c2 ;slot number of splittable ICBM
bonus_award_sc .eq $c3 {addr/2} ;score when last bonus city awarded (per plyr)
live_city_mask .eq $c5 {addr/2} ;bits for live cities (start hi) (per plyr)
flier_move_ctr .eq $c7 ;flier movement interval counter (1 or 2)
flier_move_dir .eq $c8 ;flier movement dir: $ff=left, $00=right
flier_fire_ctr .eq $c9 ;counts up time until flier can fire
game_frame_ctr .eq $ca ;incr once per game frame, <= 60Hz (non-IRQ)
city_targ_flags .eq $cb ;ICBM target flags for cities
silo_targ_flags .eq $cc ;ICBM target flags for silos
live_silo_mask .eq $cd ;bits for live silos, %LCR00000
score_color .eq $ce ;color to use for scores (%PPP00000)
cities_destroyed .eq $cf ;number of cities destroyed in current wave
sbomb_dir .eq $d0 ;direction smartbomb wants to move (0-15)
bcd3_acc .eq $d1 {addr/3} ;3-byte BCD accumulator
score_dirty_flag .eq $d4 ;bool 00/ff: scores need to be redrawn?
sbomb_prox .eq $d5 ;bit map of proximal explosions
flier_type .eq $d6 ;active flier type: 0=satellite, 1=plane
draw_1up_flag .eq $d7 ;draw phase of the flashing "1UP" glyph (0=draw)
indic_phase_counter .eq $d8 ;"1UP" display counter, decr every VBLANK
smartbomb_mask .eq $d9 ;indicates which of 8 ICBM slots hold bombs
num_live_bombs .eq $da ;number of active smartbombs on screen
wave_sbomb_count .eq $db ;#of smartbombs appearing in current wave
incoming_ctr .eq $dc ;ICBM slot number, during processing
score_mult .eq $dd ;score multiplier (1-6)
atk_create_ctr .eq $de ;number of attacks to create this frame
next_player .eq $df ;next player number (0/1)
slam_tone_ctr2 .eq $e0 ;set to $f0 on tilt (in main code)
flier_fire_delay .eq $e1 ;#of frames before flier can fire
flier_charge_int .eq $e2 ;#of frames to "recharge" flier
flier_charge_ctr .eq $e3 ;counts up, flier ready when exceeds $e2 val
color_palette .eq $e4 {addr/8} ;colors to copy to display H/W on next IRQ
color_cyc_mask .eq $ec ;mask of palette entries to cycle
in01_current .eq $ed {addr/2} ;current state of IN0/IN1
in01_prev .eq $ef {addr/2} ;previous state of IN0/IN1
in0_value .eq $f1 ;debounced IN0 value (switches)
in1_value .eq $f2 ;debounced IN1 value
r10_irq_mod .eq $f3 ;R10 at last IRQ, bit 1 inverted
r8_irq_inv .eq $f4 ;R8 at last IRQ, all bits inverted
out0_bits .eq $f5 ;bits last written to OUT0
in0_trkball1 .eq $f6 ;trackball input (used by IRQ)
in0_trk_prev .eq $f7 ;previous trackball read (used by IRQ)
in0_trkball2 .eq $f8 ;trackball input (used by IRQ)
prev_plyr_in01 .eq $f9 ;set by IRQ to IN0/IN1 for active player
fire_buttons .eq $fa ;pressed fire buttons, %00000LCR (set by IRQ)
irq_counter .eq $fb ;counts up every IRQ, used as time multiplier
cocktail_flip .eq $fc ;$00=upright, $80=ckt plyr 1, $ff=ckt plyr 2
STACK .eq $0100 {addr/256} ;6502 stack
stack_plus4 .eq $0104 ;stack offset
stack_plus6 .eq $0106 ;stack offset
abm_cur_xfrac .eq $0118 {addr/8} ;current ABM X coord, fractional part
icbm_cur_xfrac .eq $0120 {addr/8} ;(must follow ABM array)
abm_cur_x .eq $0128 {addr/8} ;current ABM X coord, int part
icbm_cur_x .eq $0130 {addr/8} ;(must follow ABM array)
plane_cur_x .eq $0138 ;flier X coord (follows ICBM)
expl_xpos .eq $0139 {addr/20} ;X coordinates of explosions
abm_cur_yfrac .eq $014d {addr/8} ;current ABM Y coord, fractional part
icbm_cur_yfrac .eq $0155 {addr/8} ;(must follow ABM array)
abm_cur_y .eq $015d {addr/8} ;current ABM Y coord, int part
icbm_cur_y .eq $0165 {addr/8} ;(must follow ABM array)
flier_cur_y .eq $016d ;flier Y coord, or 0 if inactive (follows ICBM)
expl_ypos .eq $016e {addr/20} ;Y coordinates of explosions
abm_start_x_arr .eq $0182 {addr/8} ;initial ABM X coord
icbm_start_x_arr .eq $018a {addr/8} ;(must follow ABM array)
abm_start_y_arr .eq $0192 {addr/8} ;initial ABM Y coord
icbm_start_y_arr .eq $019a {addr/8} ;(must follow ABM array)
abm_targ_x_arr .eq $01a2 {addr/8} ;X coords of ABM targets
icbm_targ_x_arr .eq $01aa {addr/8} ;(must follow ABM array)
abm_targ_y_arr .eq $01b2 {addr/8} ;Y coords of ABM targets
icbm_targ_y_arr .eq $01ba {addr/8} ;(must follow ABM array)
expl_radius_idx_arr .eq $01c2 {addr/20} ;index into expl radius table ($00-1b)
cur_score_lo .eq $01d6 {addr/2} ;current score, BCD low part (2 plyr)
cur_score_md .eq $01d8 {addr/2} ;current score, BCD mid part (2 plyr)
cur_score_hi .eq $01da {addr/2} ;current score, BCD hi part (2 plyr)
VIDEO_RAM_3 .eq $0200 {addr/1024} ;3rd color bit region
msl_inc_xfrac .eq $0600 {addr/16} ;missile X move increment, fractional part
msl_inc_xint .eq $0610 {addr/16} ;missile X move increment, int part
msl_inc_yfrac .eq $0620 {addr/16} ;missile Y move increment, fractional part
msl_inc_yint .eq $0630 {addr/16} ;missile Y move increment, int part
VIDEO_RAM_2 .eq $0640 {addr/14784} ;2bpp video RAM
POKEY_AUDF1 .eq $4000 ;W audio channel 1 frequency
POKEY_AUDC1 .eq $4001 ;W audio channel 1 control
POKEY_AUDF2 .eq $4002 ;W audio channel 2 frequency
POKEY_AUDC2 .eq $4003 ;W audio channel 2 control
POKEY_AUDC3 .eq $4005 ;W audio channel 3 control
POKEY_AUDF4 .eq $4006 ;W audio channel 4 frequency
POKEY_AUDC4 .eq $4007 ;W audio channel 4 control
POKEY_AUDCTL .eq $4008 ;W audio control
SWITCH_R8 .eq $4008 ;R switches at R8 (IN3 / POKEY_ALLPOT)
POKEY_RANDOM .eq $400a ;R random number
POKEY_POTGO .eq $400b ;W start POT scan sequence
POKEY_SKCTL .eq $400f ;W serial port control
IN0 .eq $4800 ;R coin, start, player 2 fire; CTRLD trackball
OUT0 .eq $4800 ;W flip, LEDs, CTRLD
IN1 .eq $4900 ;R buttons, tilt, service
SWITCH_R10 .eq $4a00 ;R pricing opt switches at R10 (IN2)
COLOR_PAL .eq $4b00 {addr/8} ;W color palette
WATCHDOG .eq $4c00 ;W watchdog timer reset
INT_ACK .eq $4d00 ;W interrupt acknowledge
.addrs $5000
5000: 29 .dd1 $29 ;checksum byte
********************************************************************************
* *
* Main code start point. Only executes after a reset. *
* *
********************************************************************************
5001: 58 START cli ;enable interrupts
5002: 20 e1 74 jsr InitDefScores ;initialize high score list to defaults
5005: 20 2f 67 jsr InitLangAndFlip ;init localization and screen flip
5008: 20 0c 7b jsr InitPokey ;init sound generation
500b: a9 14 lda #FN_SHOW_TITLE ;start by showing the title screen
500d: 85 91 sta func_index
;
; Main code loop. We spin until the IRQ tells us to go, which it does once per
; frame (~60Hz).
;
; The game uses a state machine to manage the higher-level activity, e.g.
; showing the title sequence vs. showing high scores vs. playing the game. The
; code for each state will update the function number (in $91) when it's time to
; move on.
;
; The main loop executes the current function, calls a routine to track any
; quarters that have dropped, and then loops around to wait until it's time for
; the next frame to execute. In some cases the code may take longer than one
; frame to execute, e.g. drawing the high score list takes 44 frames. At 128
; frames the IRQ handler will conclude that the main thread has stalled and
; reset the machine.
;
500f: 46 9f MainLoop lsr exec_flag ;shift low bit into carry flag
5011: 90 fc bcc MainLoop ;not set, loop until IRQ handler sets it
5013: a9 00 lda #$00
5015: 85 9f sta exec_flag ;clear flag
5017: 20 3e 50 jsr StateMachine ;execute current function
501a: 20 a6 50 jsr ScrollAndChkStart ;check start buttons, and scroll text
;
501d: a5 f2 lda in1_value ;get modified IN1
501f: 29 40 and #%01000000 ;check the self-test switch
5021: d0 ec bne MainLoop ;not in test mode, loop
5023: 4c 4b 7b jmp HandleReset ;test mode, reset the system
;
; Func $04: plays one frame of the game.
;
5026: 20 fb 55 F_PlayGame jsr LaunchAttacks ;launch missiles, bombs, and fliers
5029: 20 5a 52 jsr CheckFireButtons ;fire ABMs
502c: 20 46 61 jsr UpdateFlier ;move the flier
502f: 20 d0 53 jsr UpdateAbms ;move ABMs
5032: 20 21 53 jsr UpdateIcbms ;move ICBMs
5035: 20 58 54 jsr UpdateExplosions ;update explosions, check collisions
5038: 20 32 51 jsr UpdateCrossPosn ;move crosshairs
503b: 4c ee 59 jmp CheckWaveDone ;see if the wave is done yet
;
; Calls the function specified by $91.
;
503e: a6 91 StateMachine ldx func_index ;get index of function to execute
5040: bd 4a 50 lda :jump_table+1,x ;get address
5043: 48 pha ;push on stack as if it were
5044: bd 49 50 lda :jump_table,x ; a return address
5047: 48 pha
5048: 60 rts ;jump to function
5049: d0 5a :jump_table .dd2 F_InitGame-1 ;$00
504b: 0f 5b .dd2 F_PrepWave-1 ;$02
504d: 25 50 .dd2 F_PlayGame-1 ;$04
504f: 6c 5d .dd2 F_GameOver-1 ;$06
5051: e9 5b .dd2 F_EndOfWave-1 ;$08
5053: 1f 5c .dd2 F_AbmBonusPts-1 ;$0a
5055: 81 5c .dd2 F_CityBonusChk-1 ;$0c
5057: a9 5c .dd2 F_CityBonusPts-1 ;$0e
5059: 0b 5d .dd2 F_WaveDoneUpdate-1 ;$10
505b: a1 5d .dd2 F_ShowEndTimes-1 ;$12
505d: 81 5a .dd2 F_ShowTitle-1 ;$14
505f: b8 5a .dd2 F_TitleExpls-1 ;$16
5061: 3e 75 .dd2 F_HighScoreCheck-1 ;$18
5063: 44 76 .dd2 F_EnterInitials-1 ;$1a
5065: ff 76 .dd2 F_ShowHighScores-1 ;$1c
5067: 85 77 .dd2 F_HalfCredit-1 ;$1e
5069: 9e 77 .dd2 F_WaitFullCredit-1 ;$20
506b: 6c 50 .dd2 F_PrePlayPause-1 ;$22
;
; Func $22: pause between waves or other slow transition.
;
; After a delay specified in $af, the function index ($91) is set to the value
; in $92. The player is allowed to move the crosshairs around during this time.
;
506d: a5 ca F_PrePlayPause lda game_frame_ctr ;get frame counter
506f: 29 03 and #%00000011 ;discard all but the low 2 bits
5071: d0 30 bne Jmp_UpdCrossPosn ;skip the rest 3/4ths of the time, so the counter
5073: a4 af ldy frame4_delay_ctr ; decs once per 4 game frames (15Hz)
5075: f0 02 beq :AtZero ;already at zero, branch
5077: c6 af dec frame4_delay_ctr ;decrement counter
5079: d0 28 :AtZero bne Jmp_UpdCrossPosn ;if not at zero yet, skip the rest
;
507b: a5 92 lda next_func_index ;get index of next function
507d: 85 91 sta func_index ;set as current index
507f: c9 04 cmp #FN_PLAY_GAME ;are we switching to the game-play function?
5081: d0 20 bne Jmp_UpdCrossPosn ;no, branch
5083: a5 93 lda play_mode_flag ;is a game in progress?
5085: f0 1c beq Jmp_UpdCrossPosn ;no, branch
;
5087: a9 00 lda #$00 ;erase lines with these messages
5089: 20 49 6a jsr EraseMessageLine ;"PLAYER"
508c: a9 06 lda #$06
508e: 20 49 6a jsr EraseMessageLine ;"X POINTS"
5091: a9 1e lda #$1e
5093: 20 49 6a jsr EraseMessageLine ;"ATARI (C)(P) 1980"
5096: a9 15 lda #$15
5098: 20 49 6a jsr EraseMessageLine ;"DEFEND CITIES"
509b: a9 00 lda #$00
509d: 85 fa sta fire_buttons ;reset fire buttons
509f: 85 95 sta trk_v_delta ;clear trackball movement delta
50a1: 85 94 sta trk_h_delta
Jmp_UpdCrossPosn
50a3: 4c 32 51 jmp UpdateCrossPosn ;update the position of the crosshairs
;
; Updates the scrolling text, and checks to see if a start button has been
; pressed. If a game is in progress, redraws the score and the 1UP/2UP
; indicator if things aren't too busy.
;
; Called on every iteration of the main loop, regardless of which function is
; active.
;
ScrollAndChkStart
50a6: a5 93 lda play_mode_flag ;is a game in progress?
50a8: d0 55 bne :NotFree ;yes, skip most of this
;
50aa: 20 5d 5f jsr ScrollText ;update the scrolling text display
;
50ad: a5 66 lda credit_count ;get number of credits
50af: f0 32 beq :NoCredits ;if zero, branch
50b1: a0 00 ldy #$00 ;init number of players to zero
50b3: c9 02 cmp #$02 ;do we have at least 2 credits?
50b5: a5 f1 lda in0_value ;get IN0
50b7: 49 ff eor #$ff ;invert bits
50b9: 29 18 and #%00011000 ;check player 1+2 start buttons
50bb: f0 23 beq :NoStart ;not pressed, branch
50bd: b0 05 bcs :TwoCreds ;2+ credits, branch
50bf: 29 10 and #%00010000 ;ignore player 2 start
50c1: b8 clv
50c2: 50 05 bvc :OneOrMore ;(always)
50c4: c8 :TwoCreds iny ;increment number of players
50c5: c6 66 dec credit_count ;deduct 1 credit
50c7: 29 08 and #%00001000 ;check if 2-player start pressed
50c9: f0 03 :OneOrMore beq :SinglePlayer ;not, branch
50cb: c6 66 dec credit_count ;deduct a second credit
50cd: c8 iny ;increment number of players
50ce: 98 :SinglePlayer tya ;copy to A-reg (to set flags)
50cf: 85 ae sta num_players ;set the number of players
50d1: f0 0d beq :NoStart ;if 0 players (e.g. hit "start 2" w/1 credit), branch
; Start a game.
50d3: a0 ff ldy #$ff
50d5: 84 93 sty play_mode_flag ;set game-in-progress flag
50d7: c8 iny ;Y-reg=0
50d8: 84 68 sty recent_coins ;reset bonus-for-4-coins counter
50da: a9 00 lda #FN_INIT_GAME ;switch to game initialization func
50dc: 85 91 sta func_index
50de: c6 ae dec num_players ;adjust player count from [1,2] to [0,1]
50e0: b8 :NoStart clv
50e1: 50 12 bvc :CheckFree ;(always)
50e3: a5 67 :NoCredits lda coins_inserted ;get number of coins inserted but not credited
50e5: f0 0e beq :CheckFree ;none, branch
50e7: a5 91 lda func_index ;what are we doing right now?
50e9: c9 20 cmp #FN_WAIT_FULL_CREDIT ;waiting for the other half of a 2-coin credit?
50eb: f0 08 beq :CheckFree ;yes, continue
50ed: c9 12 cmp #FN_THE_END ;showing "the end"?
50ef: f0 04 beq :CheckFree ;yes, continue
50f1: a9 1e lda #FN_HALF_CREDIT ;ask for another coin
50f3: 85 91 sta func_index
;
50f5: a5 f3 :CheckFree lda r10_irq_mod ;get R10 switches
50f7: 29 03 and #%00000011 ;check coins/play setting
50f9: d0 04 bne :NotFree ;not free play, branch
50fb: a9 02 lda #$02 ;free play, set credits to 2
50fd: 85 66 sta credit_count
;
50ff: e6 ca :NotFree inc game_frame_ctr ;advance the game frame counter
5101: a5 9f lda exec_flag ;was this a slow frame?
5103: d0 1c bne :CheckSlam ;yes, skip the next couple of things
;
5105: a5 d4 lda score_dirty_flag ;do we need to redraw the score?
5107: f0 05 beq :UpdatePlyrIndic ;no, branch
5109: a4 b9 ldy cur_plyr_num ;get current player (0/1)
510b: 20 fc 5f jsr PrintPlyrScore ;print their score
;
:UpdatePlyrIndic
510e: a5 d8 lda indic_phase_counter ;has the "1UP" flash counter hit zero?
5110: d0 0f bne :CheckSlam ;not yet, branch
5112: 20 52 5a jsr UpdateFlashIndic ;draw/erase based on *current* value
5115: a5 d7 lda draw_1up_flag ;get visibility phase of the player indicator
5117: 49 01 eor #%00000001 ;change phase
5119: 85 d7 sta draw_1up_flag
511b: aa tax ;use as index
511c: bd 30 51 lda :plyr_indic_phase_tbl,x ;get number of frames to wait before redraw
511f: 85 d8 sta indic_phase_counter ;set counter (which is decr by IRQ handler)
;
5121: a5 5f :CheckSlam lda slam_tone_ctr1 ;are we whining about the slam switch?
5123: f0 04 beq :NoWhine ;no, branch
5125: a9 f0 lda #$f0 ;yes, set the second counter
5127: 85 e0 sta slam_tone_ctr2 ; (checked for nonzero in the sound code)
5129: a5 e0 :NoWhine lda slam_tone_ctr2 ;are we currently whining?
512b: f0 02 beq :Return ;no, bail
512d: c6 e0 dec slam_tone_ctr2 ;yes, count it down
512f: 60 :Return rts
;
; Player indicator arrow flash timing.
:plyr_indic_phase_tbl
5130: 0a .dd1 10 ;10 frames of invisible
5131: 32 .dd1 50 ;50 frames of visible
;
; Updates the position of the crosshairs, using either the trackball delta or
; the directions from the self-play code.
;
]trk_h_arg .var $b0 {addr/1}
]trk_v_arg .var $b1 {addr/1}
5132: a5 a6 UpdateCrossPosn lda cross_yc ;are crosshairs visible?
5134: f0 1e beq :Return ;no, bail
5136: a5 93 lda play_mode_flag ;playing a game?
5138: d0 06 bne :GetDelta ;yes, branch
513a: 20 55 51 jsr DemoGamePlay ;no, demo playing the game
513d: b8 clv
513e: 50 0e bvc :Move ;(always)
5140: a0 00 :GetDelta ldy #$00 ;zero out the trackball delta
5142: a5 95 lda trk_v_delta ;get vertical delta
5144: 84 95 sty trk_v_delta ;reset to zero
5146: 85 b1 sta ]trk_v_arg ;save in ZP as arg to func
5148: a5 94 lda trk_h_delta ;get horizontal delta
514a: 84 94 sty trk_h_delta ;reset to zero
514c: 85 b0 sta ]trk_h_arg ;save in ZP as arg to func
514e: 20 39 52 :Move jsr DoUpdateCrossPosn ;apply trackball to crosshairs
5151: 20 d3 64 jsr DrawCrosshairs ;erase and redraw
5154: 60 :Return rts
;
; Moves the crosshairs and fires ABMs during attract mode.
;
; If there are no ICBMs, this uses the coordinates of an empty ICBM slot. Empty
; slots have a Y-coord of zero, so the crosshairs will move down toward the
; cities.
;
; On exit:
; $b0-b1: crosshairs movement delta
;
]shift_hi .var $98 {addr/1}
]attract_targ .var $d0 {addr/1} ;(re-using a smartbomb global)
5155: a9 00 DemoGamePlay lda #$00
5157: 85 b1 sta ]trk_v_arg ;init fake trackball movement
5159: 85 b0 sta ]trk_h_arg
515b: a6 d0 ldx ]attract_targ ;get current target of interest
515d: bd 65 01 lda icbm_cur_y,x ;is it a live ICBM?
5160: d0 03 bne :LiveTarget ;yes, branch
5162: 20 e7 51 jsr PickIcbmTarget ;no, find an ICBM to target
5165: a6 d0 :LiveTarget ldx ]attract_targ ;get target index
5167: bd 65 01 lda icbm_cur_y,x ;get current Y coord (will be 0 if inactive)
516a: c5 a6 cmp cross_yc ;is it >= the crosshair position?
516c: b0 05 bcs :IcbmAbove ;yes, branch
516e: a9 fe lda #$fe ;no, move the crosshairs down
5170: b8 clv
5171: 50 12 bvc :GotV ;(always)
5173: 38 :IcbmAbove sec
5174: e5 a6 sbc cross_yc ;compute difference
5176: c9 0e cmp #14 ;14 pixels below?
5178: d0 05 bne :Not14 ;no, branch
517a: a9 00 lda #$00 ;no vertical movement
517c: b8 clv
517d: 50 06 bvc :GotV
517f: a9 02 :Not14 lda #$02 ;move crosshairs up two pixels
5181: b0 02 bcs :GotV ; if more than 14 above
5183: a9 fe lda #$fe ;else move down 2 pixels
5185: 85 b1 :GotV sta ]trk_v_arg ;set vertical movement
; Get the horizontal increment, which is an 8.8 value < 1.0. We right-shift it
; 4x, then use the low byte. It would also work to left-shift it 4x then use
; the high byte, which is more representative of what we're trying to do:
; estimate the ICBM's horizontal movement for the next 16 frames.
5187: bd 18 06 lda msl_inc_xint+8,x ;get ICBM's horizontal movement, int part
518a: 85 98 sta ]shift_hi ;copy to ZP
518c: bd 08 06 lda msl_inc_xfrac+8,x ;get fractional part
518f: 46 98 lsr ]shift_hi ;shift right 4x
5191: 6a ror A
5192: 46 98 lsr ]shift_hi
5194: 6a ror A
5195: 46 98 lsr ]shift_hi
5197: 6a ror A
5198: 46 98 lsr ]shift_hi
519a: 6a ror A
519b: 18 clc ;add the low byte, which is effectively the
519c: 7d 30 01 adc icbm_cur_x,x ; fractional part x16
519f: c5 a5 cmp cross_xc ;compare it to the current crosshair position
51a1: b0 05 bcs :RtOrStay ;need to move right or not at all; branch
51a3: a9 fe lda #$fe ;move 2 pixels left
51a5: b8 clv
51a6: 50 09 bvc :GotH ;(always)
51a8: d0 05 :RtOrStay bne :NotEqH ;branch if we need to move
51aa: a9 00 lda #$00 ;we're in the right place, don't move
51ac: b8 clv
51ad: 50 02 bvc :GotH ;(always)
51af: a9 02 :NotEqH lda #$02 ;move 2 pixels right
51b1: 85 b0 :GotH sta ]trk_h_arg ;set horizontal movement
;
51b3: 05 b1 ora ]trk_v_arg ;do we need to move at all?
51b5: d0 2f bne :Return ;yes, branch
; We're in the right place. Fire?
51b7: a5 bc lda active_abm_count ;get number of in-flight ABMs
51b9: c9 02 cmp #$02 ;>= 2?
51bb: b0 29 bcs :Return ;yes, don't fire any more just yet
51bd: a2 02 ldx #$02 ;default to silo on right
51bf: a5 a5 lda cross_xc ;get crosshair horizontal position
51c1: c9 60 cmp #96 ;>= 96?
51c3: b0 05 bcs :Ge96 ;yes, branch
51c5: a2 00 ldx #$00 ;no, use left silo
51c7: b8 clv
51c8: 50 06 bvc :Fire
51ca: c9 a0 :Ge96 cmp #160 ;>= 160?
51cc: b0 02 bcs :Fire ;yes, branch to use silo #2
51ce: a2 01 ldx #$01 ;no, use middle silo
;
51d0: a5 bc :Fire lda active_abm_count ;get number of in-flight ABMs
51d2: 18 clc
51d3: 65 8e adc num_explosions ;add the number of explosions
51d5: c5 8c cmp num_live_icbms ;compare to number of inbound ICBMs
51d7: b0 0d bcs :Return ;if ABMs + explosions >= ICBMs, bail
51d9: 20 bd 52 jsr LaunchAbm ;fire!
;
51dc: a0 00 ldy #$00
51de: 84 b0 sty ]trk_h_arg ;hold horizontal position
51e0: 88 dey ;Y-reg=$ff
51e1: 84 b1 sty ]trk_v_arg ;move down one line
51e3: 20 e7 51 jsr PickIcbmTarget ;pick a new target
51e6: 60 :Return rts
;
; Chooses an ICBM to target when playing in attract mode.
;
; Returns the first live ICBM in the list, starting the check with the current
; index so we'll tend to find older ones. If there are no live ICBMs, this
; leaves $d0 unchanged.
;
; On entry:
; $d0: index of previous target
;
; On exit:
; $d0: index of new target
;
51e7: a6 d0 PickIcbmTarget ldx ]attract_targ ;get current target index
51e9: ca :Loop dex ;decrement it
51ea: 10 02 bpl :StillPos ;if we didn't roll under, branch
51ec: a2 07 ldx #$07 ;start over with the 8th entry
51ee: e4 d0 :StillPos cpx ]attract_targ ;is this the original value?
51f0: d0 01 bne :Check ;no, branch
51f2: 60 rts ;yes, we wrapped around; no ICBMs to shoot
51f3: bd 65 01 :Check lda icbm_cur_y,x ;is this one alive?
51f6: f0 f1 beq :Loop ;no, loop
51f8: 86 d0 stx ]attract_targ ;yes, set it as the next target
51fa: 60 rts ; and return
;
; Applies trackball movement deltas to the crosshair position for one axis. The
; value will be clamped to [0,255].
;
; Trackball movement is accumulated across multiple IRQ events.
;
; On entry:
; X-reg: trackball axis (0=horz, 1=vert)
; $b0-b1: trackball movement deltas (H/V)
;
; On exit:
; A-reg: updated crosshair axis position (0-255)
;
]trk_axis_hi .var $9d {addr/2}
]trk_axis_lo .var $b0 {addr/2}
51fb: a0 00 ApplyTrackAxis ldy #$00 ;assume positive
51fd: b5 b0 lda ]trk_axis_lo,x ;get value
51ff: 10 01 bpl :IsPos ;is positive, branch
5201: 88 dey ;make negative
5202: 94 9d :IsPos sty ]trk_axis_hi,x ;sign-extend axis value
;
5204: a0 06 ldy #$06 ;start with shift 6x
5206: a5 fc lda cocktail_flip ;get cocktail flip mode
5208: 0a asl A ;shift the "is a cocktail cabinet" flag into carry
5209: a5 f4 lda r8_irq_inv ;get R8 switches
520b: 29 08 and #%00001000 ;get "mini trak-ball" flag
520d: 6a ror A ;shift cocktail cabinet flag into high bit
520e: 25 93 and play_mode_flag ;zero it out if game not in progress
5210: f0 01 beq :ShiftLoop ;branch if not cocktail and not mini trak
5212: c8 iny ;cocktail + mini, shift 7x instead
;
5213: 16 b0 :ShiftLoop asl ]trk_axis_lo,x ;multiply by 64 or 128, yielding
5215: 36 9d rol ]trk_axis_hi,x ; an 8.8 value
5217: 88 dey ;done with shift?
5218: 10 f9 bpl :ShiftLoop ;no, loop
;
521a: b5 a3 lda cross_frac,x ;update position of crosshairs
521c: 18 clc ;start with the fractional part
521d: 75 b0 adc ]trk_axis_lo,x
521f: 95 a3 sta cross_frac,x
5221: b5 a5 lda cross_xc,x ;now add the integer part
5223: 75 9d adc ]trk_axis_hi,x ;(note we don't actually store the result)
5225: d5 a5 cmp cross_xc,x ;did the integer part change?
5227: f0 0f beq :Return ;no, bail with result in A-reg
; Test for underflow/overflow. For example, if we add a positive value but the
; result became smaller, we know we rolled over.
5229: b4 9d ldy ]trk_axis_hi,x ;was this move in the positive direction?
522b: 10 07 bpl :PosMove ;yes, branch
522d: 90 02 bcc :Keep ;no; if new value < old value, return
522f: a9 00 lda #$00 ;underflow, clamp to 0
5231: b8 :Keep clv
5232: 50 04 bvc :Return ;(always)
5234: b0 02 :PosMove bcs :Return ;if new value >= old value, return
5236: a9 ff lda #$ff ;overflow, clamp to 255
5238: 60 :Return rts
;
; Updates the position of the crosshairs, clamping the movement to min/max
; values from a table. We don't want the crosshairs to be up in the score area,
; or down with the cities, or wrapping around left and right.
;
; On entry:
; $b0-b1: trackball movement deltas
;
DoUpdateCrossPosn
5239: a2 00 ldx #$00 ;update the X axis
523b: 20 40 52 jsr :UpdateAxis
523e: a2 01 ldx #$01 ;update the Y axis
;
5240: 20 fb 51 :UpdateAxis jsr ApplyTrackAxis ;apply movement deltas
5243: dd 56 52 cmp :min_coord,x ;>= min?
5246: b0 03 bcs :GeMin ;yes, branch
5248: bd 56 52 lda :min_coord,x ;no, clamp
524b: dd 58 52 :GeMin cmp :max_coord,x ;< max?
524e: 90 03 bcc :LtMax ;yes, branch
5250: bd 58 52 lda :max_coord,x ;no, clamp
5253: 95 a5 :LtMax sta cross_xc,x ;store result
5255: 60 rts
5256: 08 :min_coord .dd1 8 ;min X coord
5257: 2d .dd1 45 ;min Y coord (above cities)
5258: f7 :max_coord .dd1 247 ;max X coord
5259: ce .dd1 206 ;max Y coord (comfortably below top)
;
; Checks to see if a fire button is pressed. If we have ammo in the launcher,
; and there's an available slot for a missile, and there's something to shoot
; at, and things aren't too busy, we launch.
;
CheckFireButtons
525a: a5 93 lda play_mode_flag ;is a game in progress?
525c: f0 57 beq :Done ;no, branch
525e: a5 8d lda wave_icbm_count ;are there pending ICBMs?
5260: 05 8c ora num_live_icbms ;or in-flight ICBMs?
5262: 05 db ora wave_sbomb_count ;or pending smartbombs?
5264: 05 da ora num_live_bombs ;or in-flight smartbombs?
5266: 0d 6d 01 ora flier_cur_y ;or a flier?
5269: f0 47 beq :NoTargets ;if not, there's nothing to shoot at; branch
;
526b: a5 8f lda num_expl_abms ;get number of exploding ABMs
526d: 18 clc
526e: 65 bc adc active_abm_count ;add number of in-flight ABMs
5270: c9 10 cmp #16 ;>= 16?
5272: b0 10 bcs :Br_Overload ;yes, stop shooting
5274: 65 8c adc num_live_icbms ;add the number of live ICBMs
5276: 18 clc
5277: 65 da adc num_live_bombs ;and the number of live bombs
5279: 18 clc
527a: ae 6d 01 ldx flier_cur_y ;is there an active flier?
527d: f0 01 beq :NoFlier ;no, branch
527f: 38 sec ;add one for the flier
5280: 65 90 :NoFlier adc num_expl_icbms ;add number of exploding ICBMs
5282: c9 14 cmp #20 ;>= 20?
5284: b0 1e :Br_Overload bcs :Overload ;yes, stop shooting
; We're able to fire missiles. Check buttons and launchers.
5286: a2 02 ldx #$02 ;check all three fire buttons
5288: a5 fa :ButtonLoop lda fire_buttons ;get fire button state
528a: 3d ba 52 and :button_bits,x ;bit set?
528d: f0 0f beq :NextButton ;no, branch
528f: b5 a0 lda abms_left,x ;do we have ammo in this launcher?
5291: f0 06 beq :NoAmmo ;no, branch
5293: 20 bd 52 jsr LaunchAbm ;yes, launch an ABM
5296: b8 clv
5297: 50 05 bvc :NextButton
5299: a9 80 :NoAmmo lda #SFX_CANT_FIRE ;notify player that launcher has no ammo
529b: 20 ec 79 jsr StartSfx
529e: ca :NextButton dex ;checked all 3 buttons?
529f: 10 e7 bpl :ButtonLoop ;no, branch
52a1: b8 clv
52a2: 50 0b bvc :Br_Done ;bail
52a4: a5 fa :Overload lda fire_buttons ;get fire button state
52a6: 29 07 and #%00000111 ;is one of the buttons pressed?
52a8: f0 05 beq :Br_Done ;no, nothing to do
52aa: a9 80 lda #SFX_CANT_FIRE ;yes, notify player that launch isn't possible
52ac: 20 ec 79 jsr StartSfx
52af: b8 :Br_Done clv
52b0: 50 03 bvc :Done ;bail
52b2: 20 72 65 :NoTargets jsr HideCrosshairs ;nothing to shoot, hide crosshairs
52b5: a9 00 :Done lda #$00 ;reset fire button state
52b7: 85 fa sta fire_buttons
52b9: 60 rts
52ba: 04 02 01 :button_bits .bulk $04,$02,$01 ;bit masks for the three fire buttons
;
; Handles an ABM launch by finding an open slot and filling in the blanks.
;
; On entry:
; X-reg: launch silo index (0-2)
;
52bd: a0 07 LaunchAbm ldy #$07 ;8 slots for in-flight ABMs
52bf: b9 5d 01 :Loop lda abm_cur_y,y ;is this slot free?
52c2: d0 59 bne :Next ;no, branch
52c4: 84 97 sty slot_index ;yes, save index
;
52c6: a9 00 lda #$00 ;initialize ABM slot
52c8: 99 4d 01 sta abm_cur_yfrac,y ;set increment fraction to zero
52cb: 99 18 01 sta abm_cur_xfrac,y
52ce: bd e8 60 lda abm_silo_x_tbl,x ;get X coord of center of silo
52d1: 99 82 01 sta abm_start_x_arr,y ;set as initial X
52d4: 99 28 01 sta abm_cur_x,y ;set as current X
52d7: bd f4 60 lda abm_silo_top_tbl,x ;get Y coord from table
52da: 99 92 01 sta abm_start_y_arr,y ;set as initial Y
52dd: 99 5d 01 sta abm_cur_y,y ;set as current Y
;
52e0: a5 a5 lda cross_xc ;get X coord of crosshairs
52e2: 99 a2 01 sta abm_targ_x_arr,y ;set as target X
52e5: a5 a6 lda cross_yc ;get Y coord of crosshairs
52e7: 99 b2 01 sta abm_targ_y_arr,y ;set as target Y
52ea: 20 7c 65 jsr DrawTargetX ;draw a flashing 'X' at the target location
52ed: 20 f6 58 jsr CalcMissilePath ;configure missile path
;
52f0: a5 93 lda play_mode_flag ;game in progress?
52f2: f0 14 beq :SkipText1 ;no, don't show "LOW"
52f4: b5 a0 lda abms_left,x ;get number of ABMs remaining in silo
52f6: c9 04 cmp #$04 ;down to 4?
52f8: d0 09 bne :Not4 ;no, branch
52fa: 8a txa ;put silo number in A-reg
52fb: 20 24 68 jsr PrintLauncherLow ;print "LOW"
52fe: a9 01 lda #SFX_SILO_LOW ;"silo low" warning sound
5300: b8 clv
5301: 50 02 bvc :PlaySound ;(always)
5303: a9 04 :Not4 lda #SFX_ABM_LAUNCH ;"abm launched" sound
5305: 20 ec 79 :PlaySound jsr StartSfx
5308: d6 a0 :SkipText1 dec abms_left,x ;decrement number of ABMs available in silo
530a: d0 08 bne :SkipText2 ;if not zero, don't print "OUT"
530c: a5 93 lda play_mode_flag ;game in progress?
530e: f0 04 beq :SkipText2 ;no, don't show "OUT"
5310: 8a txa ;put silo number in A-reg
5311: 20 36 68 jsr PrintLauncherOut ;print "OUT"
5314: 8a :SkipText2 txa ;put silo number in A-reg
5315: a8 tay ;transfer to Y-reg
5316: 20 d1 67 jsr EraseAmmo ;erase one ammo icon from silo
5319: 98 tya ;put silo number in A-reg
531a: aa tax ;put it back in X-reg
531b: a0 00 ldy #$00 ;make the next BPL fall through
531d: 88 :Next dey
531e: 10 9f bpl :Loop ;loop if we're still looking for a slot
5320: 60 rts
;
; Updates the position of ICBMs and smartbombs, and draws them.
;
; Sets the "live incoming ordnance" counters at $8c/$da, and min/max Y-coord
; values.
;
5321: a5 b3 UpdateIcbms lda icbm_move_ctr+1 ;at zero?
5323: f0 05 beq :AtZero ;yes, branch
5325: c6 b3 dec icbm_move_ctr+1 ;no, decrement whole part
5327: b8 clv ;and bail
5328: 50 4e bvc :Return ;(always)
532a: a5 b2 :AtZero lda icbm_move_ctr ;add 8.8 delay value to counter
532c: 18 clc
532d: 65 b7 adc icbm_move_delay
532f: 85 b2 sta icbm_move_ctr
5331: a5 b3 lda icbm_move_ctr+1
5333: 65 b8 adc icbm_move_delay+1
5335: 85 b3 sta icbm_move_ctr+1
;
5337: a2 07 ldx #$07 ;walk through 8 icbms / bombs
5339: 86 dc stx incoming_ctr
533b: a9 ff lda #$ff
533d: 85 bd sta incoming_min_y ;init min Y (not used)
533f: 85 c2 sta mirv_slot
5341: a9 00 lda #$00
5343: 85 8c sta num_live_icbms ;init live ICBM counter
5345: 85 da sta num_live_bombs ;init live smartbomb counter
5347: 85 be sta incoming_max_y ;init max Y (used for decisions)
;
5349: a6 dc :Loop ldx incoming_ctr ;get counter
534b: bd 65 01 lda icbm_cur_y,x ;see if this ICBM is active
534e: f0 1a beq :NextInc ;no, branch
5350: 8a txa ;put counter in A-reg
5351: 18 clc
5352: 69 08 adc #$08 ;add 8 to get missile/bomb slot index
5354: 85 97 sta slot_index ;save to ZP
;
5356: bd 65 01 lda icbm_cur_y,x ;get current Y position
5359: c5 bd cmp incoming_min_y ;is it less than the minimum?
535b: b0 02 bcs :NotLowest ;no, branch
535d: 85 bd sta incoming_min_y ;yes, update min value
535f: c5 be :NotLowest cmp incoming_max_y ;is it >= the maximum?
5361: 90 02 bcc :NotGe ;no, branch
5363: 85 be sta incoming_max_y ;yes, update max value
5365: 25 93 :NotGe and play_mode_flag ;set to zero if not playing a game
5367: 20 79 53 jsr DoMoveInc ;erase / move / draw missile or bomb
;
536a: c6 dc :NextInc dec incoming_ctr ;move to next slot
536c: 10 db bpl :Loop ;loop if not done
;
536e: a4 8e ldy num_explosions ;get number of active explosions
5370: c0 0c cpy #12 ;< 12?
5372: 90 04 bcc :Return ;yes, branch
5374: a9 ff lda #$ff ;no, disallow MIRV split
5376: 85 c2 sta mirv_slot
5378: 60 :Return rts
;
; Moves an incoming ICBM / bomb. Looks for a MIRV slot candidate.
;
; The MIRV check is peculiar. If the maximum Y coordinate we've seen so far is
; within a certain range, we flag this slot as the MIRV candidate ($c2). If, on
; the next call, the max is still in the right range, we overwrite the previous
; value, regardless of the current ICBM's altitude.
;
; This does not ensure that the MIRV candidate is in the middle altitude range.
; It just requires that the current or any previous missile is in the middle of
; the screen (between 128 and 159). If a high-altitude ICBM appears early in
; the table, splitting will be blocked. (The presence of a high-altitude ICBM
; generally halts production, so that's not actually limiting.) It prevents
; splitting when all missiles are below 128, but any missile can split when any
; other missile is above 128.
;
; Skipping the slot update when $c2 is already set would be more deterministic,
; but perhaps that's undesirable. (Bug?)
;
; On entry:
; A-reg: largest Y coordinate seen so far, or 0 if in attract mode
; X-reg: ICBM slot number (0-7) to update
;
5379: a8 DoMoveInc tay
537a: a5 d9 lda smartbomb_mask ;get icbm vs. smartbomb bits
537c: 3d f7 60 and single_bits,x ;check current slot
537f: d0 13 bne :UpdateBomb ;bit is set, smartbomb; branch
5381: c0 80 cpy #128 ;is max height seen < 128?
5383: 90 06 bcc :NotMirv ;yes, branch
5385: c0 a0 cpy #160 ;is max height seen >= 160?
5387: b0 02 bcs :NotMirv ;yes, branch
5389: 86 c2 stx mirv_slot ;128 >= max > 160, or max==0; save slot num
538b: 20 f7 66 :NotMirv jsr DrawMslTrail ;overwrite the missile head pixel with a trail pixel
538e: 20 11 54 jsr MoveMissile ;move ICBM
5391: b8 clv
5392: 50 06 bvc :CheckExplode ;(always)
5394: 20 ac 77 :UpdateBomb jsr EraseSmartBomb ;erase the smartbomb
5397: 20 0d 63 jsr MoveSmartBomb ;update its position
;
539a: a6 dc :CheckExplode ldx incoming_ctr ;get ICBM slot number (0-7)
539c: 90 1d bcc :NoExplod ;branch if missile has not reached target
;
539e: a5 d9 lda smartbomb_mask ;get icbm vs. smartbomb bits
53a0: 3d f7 60 and single_bits,x ;check current slot
53a3: f0 06 beq :DetonateIcbm ;bit clear, icbm; branch
53a5: 20 e9 7a jsr DisableBombSound
53a8: b8 clv
53a9: 50 07 bvc :Explode ;(always)
53ab: a6 97 :DetonateIcbm ldx slot_index ;get the slot index (8-15)
53ad: 20 86 66 jsr EraseTrail ;erase the ICBM trail
53b0: a6 dc ldx incoming_ctr ;get the ICBM index (0-7)
;
53b2: 20 6b 58 :Explode jsr CreateExplosion ;create an explosion
53b5: 20 9f 55 jsr CheckGroundHit ;see if it hit a city or silo
53b8: b8 clv
53b9: 50 14 bvc :Return ;(always)
53bb: a5 d9 :NoExplod lda smartbomb_mask ;get icbm vs. smartbomb bits
53bd: 3d f7 60 and single_bits,x ;check current slot
53c0: d0 08 bne :CountBomb ;bit is set, smartbomb; branch
53c2: 20 f0 66 jsr DrawMslHead ;draw the head of the missile
53c5: e6 8c inc num_live_icbms ;keep track of how many are active
53c7: b8 clv
53c8: 50 05 bvc :Return ;(always)
53ca: 20 a8 77 :CountBomb jsr DrawSmartBomb ;draw the smartbomb
53cd: e6 da inc num_live_bombs ;keep track of how many are active
53cf: 60 :Return rts
;
; Updates the positions of all ABMs. When they reach their target, we erase the
; trail and create an explosion.
;
]abm_move_ctr .var $d5 {addr/1}
53d0: a9 00 UpdateAbms lda #$00
53d2: 85 bc sta active_abm_count ;reset active ABM count
53d4: a2 07 ldx #7 ;player can have up to 8 ABMs in air at once
53d6: bd 5d 01 :AbmLoop lda abm_cur_y,x ;get ABM Y-coord
53d9: f0 32 beq :NextAbm ;inactive slot, branch
53db: 86 97 stx slot_index ;store in slot index for other functions
53dd: a0 02 ldy #$02 ;default to moving 3x per frame
53df: bd 82 01 lda abm_start_x_arr,x ;get start position
53e2: c9 7b cmp #$7b ;was it fired from the middle launcher?
53e4: d0 02 bne :SlowAbm ;no, use default 3x movement
53e6: a0 06 ldy #$06 ;yes, move 7x per frame
53e8: 84 d5 :SlowAbm sty ]abm_move_ctr
53ea: 20 f7 66 :Loop jsr DrawMslTrail ;replace head color with trail color
53ed: 20 11 54 jsr MoveMissile ;advance missile one step
53f0: 90 0d bcc :StillMoving ;if it hasn't reached its target, branch
; ABM has reached destination. Blow it up.
53f2: 20 92 65 jsr EraseTargetX ;erase the flashing 'X'
53f5: 20 86 66 jsr EraseTrail ;erase the ABM's trail
53f8: 20 6b 58 jsr CreateExplosion ;create an explosion at this location
53fb: a9 00 lda #$00
53fd: 85 d5 sta ]abm_move_ctr ;stop movement
;
53ff: c6 d5 :StillMoving dec ]abm_move_ctr ;done moving this ABM?
5401: 10 e7 bpl :Loop ;not yet, loop
;
5403: bd 5d 01 lda abm_cur_y,x ;is this ABM still alive?
5406: f0 05 beq :NextAbm ;no, branch
5408: 20 f0 66 jsr DrawMslHead ;finally, draw the missile head
540b: e6 bc inc active_abm_count ;add one to the active ABM counter
540d: ca :NextAbm dex ;done with ABMs?
540e: 10 c6 bpl :AbmLoop ;no, loop
5410: 60 rts
;
; Updates missile position, and tests whether it has reached its destination.
; Used for ICBMs and ABMs, and for smartbombs when they're not evading
; explosions.
;
; The path calculation is imprecise, so we can't simply test to see if the
; current position is equal to the target position. Instead, we check to see if
; the current update moved us past it.
;
; On entry:
; $97: missile slot index (0-15)
;
; On exit:
; C-flag: set if missile has reached its target
;
5411: a4 97 MoveMissile ldy slot_index ;get missile slot index (0-15)
5413: b9 18 01 lda abm_cur_xfrac,y ;get X position, fractional part
5416: 18 clc
5417: 79 00 06 adc msl_inc_xfrac,y ;add increment
541a: 99 18 01 sta abm_cur_xfrac,y
541d: b9 28 01 lda abm_cur_x,y ;get X position, integer part
5420: 79 10 06 adc msl_inc_xint,y ;add increment
5423: 99 28 01 sta abm_cur_x,y
;
5426: b9 4d 01 lda abm_cur_yfrac,y ;get Y position, fractional part
5429: 18 clc
542a: 79 20 06 adc msl_inc_yfrac,y ;add increment
542d: 99 4d 01 sta abm_cur_yfrac,y
5430: b9 5d 01 lda abm_cur_y,y ;get Y position, integer part
5433: 79 30 06 adc msl_inc_yint,y ;add increment
5436: 99 5d 01 sta abm_cur_y,y
; See if we've reached the target. We don't stop when X or Y matches, but
; rather when we've moved past it. To understand the calculation, suppose an
; ICBM has a target altitude of 22. We compare by computing (current - target);
; if the result is positive, we haven't reached it yet. For an ABM moving
; upward, a negative result would indicate we haven't reached the target. We
; can handle both cases by comparing the post-compare carry flag to the sign of
; the increment.
5439: d9 b2 01 cmp abm_targ_y_arr,y ;compare current Y coord to target Y coord
543c: f0 08 beq :YEqual ;if they're equal, branch
543e: 6a ror A ;rotate carry into high bit
543f: 59 30 06 eor msl_inc_yint,y ;EOR with sign bit of increment
5442: 10 02 bpl :YEqual ;carry matches sign bit, branch
5444: 38 sec ;carry doesn't match, we've passed it
5445: 60 rts
5446: b9 28 01 :YEqual lda abm_cur_x,y ;get the current X coordinate
5449: d9 a2 01 cmp abm_targ_x_arr,y ;compare to target coordinate
544c: f0 08 beq :StillGoing ;if they're equal, branch
544e: 6a ror A ;rotate carry into high bit
544f: 59 10 06 eor msl_inc_xint,y ;EOR with sign bit of increment
5452: 10 02 bpl :StillGoing ;carry matches sign bit, branch
5454: 38 sec ;carry doesn't match, we've passed it
5455: 60 rts
5456: 18 :StillGoing clc
5457: 60 rts
;
; Updates explosions and checks for collisions.
;
; There are 20 sets of explosions, which are updated every 5 frames. To keep
; performance from bogging down when lots of stuff is blowing up, the updates
; are distributed into 5 sets of 4 explosions, and we update a different set on
; every frame. This means we only check for *collisions* every 5 frames, so
; it's possible for a missile to sneak through an explosion if it's moving
; quickly.
;
; Explosion slots are assigned in round-robin order. There isn't a fixed
; association between missile slots and explosion slots.
;
• Clear variables
]after_radius .var $9a {addr/1}
]expl_xc .var $9b {addr/1}
]expl_yc .var $9c {addr/1}
]before_radius .var $b5 {addr/1}
UpdateExplosions
5458: a4 b4 ldy exp_seq_index ;get sequence index
545a: 88 dey ;move to next set
545b: 10 02 bpl :Ge0 ;haven't dropped below zero yet; branch
545d: a0 04 ldy #4 ;restart on set 4
545f: 84 b4 :Ge0 sty exp_seq_index ;save updated sequence index
5461: a5 8e lda num_explosions ;is anything exploding?
5463: f0 4b beq :Return ;no, bail
;
5465: be b2 54 ldx :expl_set_tbl,y ;get index of first (actually last) entry in set
5468: bd 6e 01 :ExplLoop lda expl_ypos,x ;is this slot active?
546b: f0 3a beq :NextExpl ;no, branch
546d: 85 9c sta ]expl_yc ;yes, save Y coord to ZP
546f: fe c2 01 inc expl_radius_idx_arr,x ;advance the index
5472: bd c2 01 lda expl_radius_idx_arr,x ;get the value
5475: 29 7f and #$7f ;strip high bit
5477: a8 tay ;copy to Y-reg for later
5478: c9 1b cmp #$1b ;have we reached the end of the explosion array?
547a: 90 16 bcc :Draw ;not yet, branch
;
547c: bd c2 01 lda expl_radius_idx_arr,x ;does the index have its high bit set?
547f: 30 05 bmi :Icbm ;yes, it was an ICBM; branch
5481: c6 8f dec num_expl_abms ;no, was ABM; decrement exploding ABM count
5483: b8 clv
5484: 50 02 bvc :DecExpl ;(always)
5486: c6 90 :Icbm dec num_expl_icbms ;decrement number of exploding ICBMs
5488: c6 8e :DecExpl dec num_explosions ;decrement number of explosions
548a: a9 00 lda #$00
548c: 9d 6e 01 sta expl_ypos,x ;mark the slot as available
548f: b8 clv
5490: 50 15 bvc :NextExpl ;(always)
5492: bd 39 01 :Draw lda expl_xpos,x ;get explosion X coord
5495: 85 9b sta ]expl_xc
5497: b9 b7 54 lda :expl_radii_tbl,y ;get current size
549a: 85 b5 sta ]before_radius
549c: b9 b8 54 lda :expl_radii_tbl+1,y ;get new size
549f: 85 9a sta ]after_radius
54a1: 20 71 5e jsr DrawExplosion ;draw or erase a ring of the explosion
54a4: 20 d4 54 jsr CheckCollision ;check for collisions
;
54a7: ca :NextExpl dex ;move to next entry
54a8: a4 b4 ldy exp_seq_index ;get set index
54aa: 8a txa
54ab: d9 b1 54 cmp :expl_set_tbl-1,y ;have we reached the start of the next set?
54ae: d0 b8 bne :ExplLoop ;not yet, loop
54b0: 60 :Return rts
;
; Explosion set end values. The 20 explosion counters are broken into 5 sets:
; 0-3, 4-7, 8-11, 12-15, 16-19.
54b1: ff .dd1 $ff
54b2: 03 07 0b 0f+ :expl_set_tbl .bulk $03,$07,$0b,$0f,$13
;
; Explosion radii. The explosion radius index array at $01c2 holds an index
; into this table. The index ranges from $00-1b.
;
54b7: 00 00 02 03+ :expl_radii_tbl .bulk $00,$00,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d
54c5: 0d 0c 0b 0a+ .bulk $0d,$0c,$0b,$0a,$09,$08,$07,$06,$05,$04,$03,$02,$01,$00
54d3: 00 .dd1 $00 ;+$1c not actually used?
;
; Checks for collisions between an ICBM/smartbomb and an explosion.
;
; This doesn't check for collisions below line 33. The crosshairs are limited
; to line 45+, and explosions have a max radius of 13, so an explosion at or
; below line 32 should only be possible when a city or silo is being destroyed.
; The test is probably there so that, if multiple missiles target a city, the
; explosions don't stack up above it.
;
; We only check the ICBM/smartbomb table for collisions, not ABMs, which allows
; the player to shoot through explosion clouds. The flier's X/Y position is
; stored at the end of the ICBM table, so we can handle it by walking through 9
; entries instead of 8.
;
; On entry:
; $9a: explosion radius
; $9b: explosion X coord
; $9c: explosion Y coord
;
; On exit:
; (X-reg preserved)
;
]icbm_xc .var $9d {addr/1}
]icbm_yc .var $9e {addr/1}
]expl_radius .var $d5 {addr/1}
54d4: 8a CheckCollision txa ;preserve X-reg on stack
54d5: 48 pha
54d6: a5 9a lda ]after_radius ;copy explosion radius
54d8: 85 d5 sta ]expl_radius
54da: a0 08 ldy #$08 ;handle 8 ICBMs + 1 flier
54dc: a5 9c lda ]expl_yc ;get explosion Y coordinate
54de: c9 21 cmp #33 ;< 33? (crosshair min is 45)
54e0: 90 5e bcc :RxReturn ;yes, skip collision checking for this one
;
54e2: b9 65 01 :UnitLoop lda icbm_cur_y,y ;get Y-coord of ICBM/bomb/flier
54e5: f0 56 beq :NextIcbm ;slot not in use, move to next
54e7: 85 9e sta ]icbm_yc ;store in ZP
54e9: b9 30 01 lda icbm_cur_x,y ;copy X-coord to ZP
54ec: 85 9d sta ]icbm_xc
54ee: c0 08 cpy #$08 ;are we testing the flier?
54f0: d0 05 bne :NotFlier ;no, branch
54f2: a9 06 lda #$06 ;use size=6
54f4: b8 clv
54f5: 50 0e bvc :GotSize ;(always)
54f7: a5 d9 :NotFlier lda smartbomb_mask ;get smartbomb bit mask
54f9: 39 f7 60 and single_bits,y ;is this entry a bomb?
54fc: d0 05 bne :IsBomb
54fe: a9 01 lda #$01 ;use size=1
5500: b8 clv
5501: 50 02 bvc :GotSize ;(always)
5503: a9 03 :IsBomb lda #$03 ;use size=3
;
5505: 18 :GotSize clc
5506: 65 d5 adc ]expl_radius ;add missile/bomb/flier size to explosion radius
5508: 85 9a sta ]after_radius ;store in ZP
550a: 20 ab 58 jsr CheckDist ;see if missile is inside explosion radius
550d: b0 2e bcs :NextIcbm ;it's not, branch
;
550f: 98 tya ;preserve index
5510: 48 pha
5511: 18 clc
5512: 69 08 adc #$08 ;add 8 to get general missile slot index
5514: 85 97 sta slot_index
5516: c4 c2 cpy mirv_slot ;was this the MIRV candidate?
5518: d0 04 bne :NotMirv ;no, branch
551a: a9 ff lda #$ff ;yes, clear the MIRV candidate
551c: 85 c2 sta mirv_slot
551e: c0 08 :NotMirv cpy #$08 ;is this the flier?
5520: 90 06 bcc :NotFlier2 ;no, branch
5522: 20 43 55 jsr FlierHit ;destroy the flier
5525: b8 clv
5526: 50 10 bvc :PrepExpl ;(always)
5528: a5 d9 :NotFlier2 lda smartbomb_mask ;get the smartbomb mask
552a: 39 f7 60 and single_bits,y ;see if this was a smartbomb
552d: d0 06 bne :BombHit ;it was, branch
552f: 20 50 55 jsr IcbmHit ;no, was an ICBM; destroy it
5532: b8 clv
5533: 50 03 bvc :PrepExpl ;(always)
5535: 20 66 55 :BombHit jsr SmartBombHit ;destroy the smartbomb, then fall through
;
; Create an explosion for whatever was hit. This allows nifty chain reactions,
; and means we don't have to write code to erase the flier.
5538: 20 6b 58 :PrepExpl jsr CreateExplosion ;create an explosion where the missile/bomb/flier was
553b: 68 pla ;restore index
553c: a8 tay
553d: 88 :NextIcbm dey ;move to next entry
553e: 10 a2 bpl :UnitLoop ;if not done, loop
;
5540: 68 :RxReturn pla ;restore X-reg
5541: aa tax
5542: 60 rts
5543: 20 f8 7a FlierHit jsr DisableFlierSound ;stop flier sound
5546: a9 00 lda #$00
5548: 85 c9 sta flier_fire_ctr ;reset cooldown
554a: 85 e3 sta flier_charge_ctr
554c: a2 03 ldx #$03 ;multiply base value by 4 (100 * mult)
554e: d0 20 bne AddToScore ;(always)
5550: c6 8c IcbmHit dec num_live_icbms ;decrement the active ICBM count
5552: 20 7b 55 jsr UpdateTargetSet ;update set of targeted cities/silos
5555: a6 97 ldx slot_index ;get the slot index (0-15)
5557: 20 86 66 jsr EraseTrail ;erase the missile trail
555a: a2 00 ldx #$00 ;multiply base value by 1 (25 * mult)
555c: a4 97 ldy slot_index ;get the slot index (0-15)
555e: b9 5d 01 lda abm_cur_y,y ;get the missile's height
5561: c9 21 cmp #33 ;was it >= 33?
5563: b0 0b bcs AddToScore ;yes, add to score
5565: 60 rts ;no, don't give points for ICBMs that hit the ground
5566: c6 da SmartBombHit dec num_live_bombs ;decrement the active smartbomb count
5568: 20 7b 55 jsr UpdateTargetSet ;update set of targeted cities/silos
556b: 20 e9 7a jsr DisableBombSound
556e: a2 04 ldx #$04 ;multiply base value by 5 (125 * mult)
;
5570: 20 9f 5f AddToScore jsr SetBaseScoreX ;compute points earned
5573: a4 b9 ldy cur_plyr_num ;get player number in Y-reg (0/1)
5575: 20 13 60 jsr AddBcdToScore ;add to score
5578: 4c ea 79 jmp PlaySound02 ;make some noise
;
; Updates the target set after a missile or smartbomb is destroyed. We want to
; zero the flag for the target city/silo if nothing else is targeting it.
;
557b: a4 97 UpdateTargetSet ldy slot_index ;get missile slot index (0-15)
557d: b9 a2 01 lda abm_targ_x_arr,y ;get target X coord
5580: 48 pha ;preserve XC
5581: a9 00 lda #$00
5583: 99 a2 01 sta abm_targ_x_arr,y ;set entry for destroyed missile to zero
5586: 68 pla ;restore XC
;
5587: a0 07 ldy #$07 ;walk through all 8 ICBMs
5589: d9 aa 01 :ChkLoop cmp icbm_targ_x_arr,y ;is this one targeting the same place?
558c: d0 01 bne :NotSame ;no, branch
558e: 60 rts ;yes, leave flags alone
558f: 88 :NotSame dey ;move to the next slot
5590: 10 f7 bpl :ChkLoop ;if not done, loop
;
5592: a0 09 ldy #$09 ;check 6 cities + 3 silos
5594: 88 :Loop dey
5595: d9 e2 60 cmp city_x_tbl,y ;was this our target?
5598: d0 fa bne :Loop ;no, branch
559a: a9 00 lda #$00
559c: 4c 47 58 jmp UpdateTargetFlag ;clear flag N
;
; Checks to see if an exploding missile or smartbomb hit a city or silo.
;
; On entry:
; $97: index of ICBM / bomb (8-15)
;
559f: a4 97 CheckGroundHit ldy slot_index ;get ICBM slot (8-15)
55a1: b9 a2 01 lda abm_targ_x_arr,y ;get X coordinate of target
55a4: a0 09 ldy #9 ;check 6 cities and 3 launchers
55a6: 88 :FindCityLoop dey
55a7: d9 e2 60 cmp city_x_tbl,y ;does it match?
55aa: d0 fa bne :FindCityLoop ;no, branch
;
55ac: a9 00 lda #$00
55ae: 20 47 58 jsr UpdateTargetFlag ;remove flag from target mask (Y-reg preserved)
55b1: c0 06 cpy #$06 ;was target a city?
55b3: 90 21 bcc :CityAreaHit ;yes, branch
55b5: a9 00 lda #$00 ;no, silo was hit
55b7: 99 9a 00 sta abms_left-6,y ;zero out the number of ABMs available
55ba: b9 f1 60 lda single_bits-6,y ;get the bit for this silo
55bd: 49 e0 eor #%11100000 ;invert to form a mask
55bf: 25 cd and live_silo_mask ;remove it from set of live silos
55c1: c5 cd cmp live_silo_mask ;did it change?
55c3: f0 0e beq :WasDead ;no, silo already dead; branch
55c5: 85 cd sta live_silo_mask ;update mask
55c7: a9 02 lda #SFX_EXPLOSION
55c9: 20 ec 79 jsr StartSfx ;play destruction sound
55cc: 98 tya ;get silo index +6 (6-8)
55cd: 38 sec
55ce: e9 06 sbc #$06 ;reduce to 0-2
55d0: 20 36 68 jsr PrintLauncherOut ;print "OUT" message under it
55d3: b8 :WasDead clv
55d4: 50 24 bvc :Return ;(always)
55d6: b9 f7 60 :CityAreaHit lda single_bits,y ;get bit for city
55d9: 49 fc eor #%11111100 ;invert the 6 bits used for cities
55db: a4 b9 ldy cur_plyr_num ;get current player number
55dd: 39 c5 00 and live_city_mask,y ;clear the bit for the destroyed city
55e0: d9 c5 00 cmp live_city_mask,y ;did the mask change?
55e3: f0 15 beq :Return ;no, dead city; bail
55e5: 99 c5 00 sta live_city_mask,y ;save updated mask
55e8: b9 c0 00 lda num_cities,y ;get number of cities for this player
55eb: f0 06 beq :Already0 ;don't reduce it below 0
55ed: 38 sec
55ee: e9 01 sbc #$01 ;reduce by 1
55f0: 99 c0 00 sta num_cities,y ;save value
55f3: e6 cf :Already0 inc cities_destroyed ;increment city-destroyed counter
55f5: a9 02 lda #SFX_EXPLOSION
55f7: 20 ec 79 jsr StartSfx ;play explosion sound
55fa: 60 :Return rts
;
; Generates attacks, i.e. launches ICBMs, smartbombs, and fliers.
;
55fb: a5 8d LaunchAttacks lda wave_icbm_count ;pending ICBMs?
55fd: 05 db ora wave_sbomb_count ;pending smart bombs?
55ff: f0 76 beq :Return ;none of the above, bail
; This prevents attacks while the highest ICBM is above (202 - 2 * wave_num).
; This appears to be a mechanism to cause attacks to trickle in steadily at
; lower waves, but gather up and burst at higher waves. The height is capped at
; 180, but they limited the result rather than wave number, so at wave 101 the
; result becomes negative (effectively off the top of the screen). This bug was
; fixed in rev3, by resetting the wave number when it hits 40.
5601: a5 a7 lda wave_num ;get wave number
5603: 0a asl A ;double it
5604: 49 ff eor #$ff ;negate it
5606: 38 sec
5607: 69 ca adc #202 ;compute (202 - 2*wave_number)
5609: c9 b4 cmp #180 ;is it >= 180?
560b: b0 02 bcs :Ge180 ;yes, branch
560d: a9 b4 lda #180 ;minimum 180
560f: c5 be :Ge180 cmp incoming_max_y ;is highest ICBM above this limit?
5611: 90 64 bcc :Return ;yes, bail
;
5613: a9 07 lda #$07 ;create up to 8 attacks
5615: 38 sec
5616: ae 6d 01 ldx flier_cur_y ;is there a flier?
5619: f0 01 beq :NoFlier ;no, branch
561b: 18 clc ;subtract one extra for flier
561c: e5 90 :NoFlier sbc num_expl_icbms ;subtract exploding ICBMs
561e: 38 sec
561f: e5 8c sbc num_live_icbms ;subtract in-flight ICBMs
5621: 38 sec
5622: e5 da sbc num_live_bombs ;subtract in-flight smartbombs
5624: 30 0b bmi :BmiReturn ;if < 0, don't create anything this frame
5626: 85 de sta atk_create_ctr ;save to ZP
5628: 18 clc
5629: 69 0c adc #12 ;add 12
562b: 38 sec
562c: e5 8f sbc num_expl_abms ;subtract number of exploding ABMs
562e: 38 sec
562f: e5 bc sbc active_abm_count ;subtract number of in-flight ABMs
5631: 30 44 :BmiReturn bmi :Return ;if < 0, we're too congested; don't launch more
5633: c5 de cmp atk_create_ctr ;is the ABM value >= the attack-create value?
5635: b0 02 bcs :KeepAtk ;yes, keep the attack-create count
5637: 85 de sta atk_create_ctr ;no, keep the lower ABM-adjusted value
;
5639: 20 ff 60 :KeepAtk jsr CheckCreateFlier ;create a flier if one is available
563c: a5 de lda atk_create_ctr ;did we use up all our attacks?
563e: 30 37 bmi :Return ;yes, bail
5640: a5 db lda wave_sbomb_count ;do we have unlaunched smartbombs?
5642: d0 06 bne :TryBomb ;yes, branch
5644: 20 78 56 jsr LaunchIcbms ;no, go launch ICBMs
5647: b8 clv
5648: 50 2d bvc :Return ;(always)
564a: a5 da :TryBomb lda num_live_bombs ;check number of in-flight bombs
564c: c9 03 cmp #$03 ;>= 3?
564e: b0 24 bcs :IcbmInstead2 ;yes, branch to launch ICBMs
5650: 18 clc
5651: 65 8c adc num_live_icbms ;add number of live ICBMs
5653: c9 05 cmp #$05 ;>= 5?
5655: b0 1a bcs :Br_Return ;branch to launch nothing
5657: a5 8d lda wave_icbm_count ;get remaining ICBMs in this wave
5659: d0 06 bne :RndBomb ;some left, branch to roll dice
565b: 20 17 57 jsr CreateSmartBomb ;only smartbombs left, go create one
565e: b8 clv
565f: 50 10 bvc :Br_Return ;(always)
5661: ad 0a 40 :RndBomb lda POKEY_RANDOM ;get a random number
5664: 29 03 and #$03 ;reduce to 0-3
5666: d0 06 bne :IcbmInstead1 ;75% chance of ICBM
5668: 20 17 57 jsr CreateSmartBomb ;25% of smartbomb
566b: b8 clv
566c: 50 03 bvc :Br_Return ;(always)
566e: 20 78 56 :IcbmInstead1 jsr LaunchIcbms ;launch ICBMs
5671: b8 :Br_Return clv
5672: 50 03 bvc :Return ;(always)
5674: 20 78 56 :IcbmInstead2 jsr LaunchIcbms ;launch ICBMs
5677: 60 :Return rts
;
; Launches ICBMs from the top of the screen, the flier, or a MIRV head.
;
; Various tests are used to limit how much we send out at once. The most we'll
; send is 4. (You can see this on wave 1, which launches 4 ICBMs, pauses until
; the max-altitude test passes at $560f, then launches 4 more.)
;
5678: a2 ff LaunchIcbms ldx #$ff ;init count to -1
567a: a5 8d lda wave_icbm_count ;get pending ICBMs for this wave
567c: f0 2a beq :SetAtk ;none left, branch to clear attack counter
567e: 38 sec
567f: ad 6d 01 lda flier_cur_y ;do we have a flier?
5682: f0 01 beq :NoFlier ;no, branch
5684: 18 clc ;add 1 for flier
5685: a9 07 :NoFlier lda #$07 ;we can have up to 8 bombs/ICBMs
5687: e5 da sbc num_live_bombs ;subtract number of live bombs
5689: 38 sec
568a: e5 da sbc num_live_bombs ;smartbombs count double here
568c: 38 sec
568d: e5 8c sbc num_live_icbms ;subtract number of live ICBMs
568f: 30 17 bmi :SetAtk ;if result negative, clear attack counter
;
5691: aa tax ;copy result to X-reg
5692: e8 inx ;increment
5693: e0 04 cpx #$04 ;is it < 4?
5695: 90 02 bcc :Lt4 ;yes, branch
5697: a2 04 ldx #$04 ;no, cap it at 4
5699: e4 8d :Lt4 cpx wave_icbm_count ;is it >= the number of ICBMs left in this wave?
569b: 90 02 bcc :LtWave ;no, branch
569d: a6 8d ldx wave_icbm_count ;yes, cap to pending ICBM count
569f: e6 de :LtWave inc atk_create_ctr ;increment the attack counter
56a1: e4 de cpx atk_create_ctr ;compare to max we're allowed to create
56a3: 90 02 bcc :LtAtk ;branch if less
56a5: a6 de ldx atk_create_ctr ;it's more, use the lower value instead
56a7: ca :LtAtk dex ;undo the earlier inc
56a8: 86 de :SetAtk stx atk_create_ctr ;set attack count
56aa: a6 de ldx atk_create_ctr ;check value
56ac: 30 33 bmi :Return ;if < 0, bail
; Check to see if the flier can fire.
56ae: a5 a7 lda wave_num ;get wave number
56b0: c9 02 cmp #$02 ;on wave 1?
56b2: 90 1d bcc :NoFlier ;yes, branch (note: flier can't exist here)
56b4: ad 6d 01 lda flier_cur_y ;is there an active flier?
56b7: f0 18 beq :NoFlier ;no, branch
56b9: a5 c9 lda flier_fire_ctr ;has the pre-fire delay interval passed?
56bb: c5 e1 cmp flier_fire_delay
56bd: 90 12 bcc :NoFlier ;not yet, branch
;
56bf: ad 38 01 lda plane_cur_x ;get horizontal position
56c2: c9 30 cmp #48 ;too far left?
56c4: 90 0b bcc :NoFlier
56c6: c9 d0 cmp #208 ;too far right?
56c8: b0 07 bcs :NoFlier
56ca: a9 00 lda #$00
56cc: 85 c9 sta flier_fire_ctr ;reset the firing interval counter
56ce: 4c 39 57 jmp FlierFire ;fire one or more missiles from flier
; Check to see if an ICBM can split.
56d1: a5 a7 :NoFlier lda wave_num ;get wave number
56d3: c9 01 cmp #$01 ;is it < 1? (bug? should probably be #$02)
56d5: 90 07 bcc Jmp_CreateIcbms ;yes, no MIRV
56d7: a4 c2 ldy mirv_slot ;have we identified a MIRV candidate?
56d9: 30 03 bmi Jmp_CreateIcbms ;no, branch
56db: 4c 3b 57 jmp SplitMirv ;create some new missiles
56de: 4c 31 57 Jmp_CreateIcbms jmp CreateIcbms ;create ICBMs at the top of the screen
56e1: 60 :Return rts
;
; Creates a new ICBM or smartbomb at the top of the screen. They always start
; at the same row, right below the scores, but the horizontal position is
; completely random.
;
; On exit:
; (X-reg preserved)
;
]saved_x .var $ac {addr/1}
56e2: 86 ac DoCreateIcbm stx ]saved_x ;preserve X-reg
56e4: 20 82 57 jsr FindFreeIcbmSlot ;find first free slot
56e7: a6 97 ldx slot_index ;get ICBM slot index (0-7)
56e9: 30 29 bmi :RxReturn ;bail if negative (can this happen?)
56eb: a9 de lda #222 ;near top of screen, below scores
56ed: 9d 65 01 sta icbm_cur_y,x ;set as current Y coordinate
56f0: 9d 9a 01 sta icbm_start_y_arr,x ; and as initial Y coordinate
;
56f3: ad 0a 40 lda POKEY_RANDOM ;get random value (0-255)
56f6: a2 07 ldx #$07 ;check all 8 ICBM slots (even the empty ones?)
56f8: dd 8a 01 :DupLoop cmp icbm_start_x_arr,x ;did another ICBM start at this position?
56fb: d0 05 bne :NoMatch ;not this one; branch
56fd: ad 0a 40 lda POKEY_RANDOM ;get a new random value
5700: a2 08 ldx #$08 ;restart the search
5702: ca :NoMatch dex
5703: 10 f3 bpl :DupLoop
;
5705: a6 97 ldx slot_index ;get ICBM slot index (0-7)
5707: 9d 8a 01 sta icbm_start_x_arr,x ;save random value as initial position
570a: 9d 30 01 sta icbm_cur_x,x ; and as current position
570d: 20 91 57 jsr SetIcbmTarget ;pick a city or silo
5710: a9 ff lda #$ff
5712: 85 be sta incoming_max_y ;reset max ICBM altitude
5714: a6 ac :RxReturn ldx ]saved_x ;restore X-reg
5716: 60 rts
;
; Creates a smartbomb. We create an ICBM, set the "is smartbomb" flag, and then
; fix up the counters.
;
5717: 20 e2 56 CreateSmartBomb jsr DoCreateIcbm ;create ICBM
571a: a6 97 ldx slot_index ;check the slot index (0-15)
571c: 30 12 bmi :Return ;didn't work, bail
571e: 20 d1 7a jsr EnableBombSound
5721: e6 da inc num_live_bombs ;increment the number of active smartbombs
5723: c6 db dec wave_sbomb_count ;decrement the number of pending smartbombs
5725: e6 8d inc wave_icbm_count ;fix pending ICBM count
5727: c6 8c dec num_live_icbms ;fix live ICBM count
5729: a5 d9 lda smartbomb_mask ;get the smartbomb mask
572b: 1d ef 60 ora single_bits-8,x ;set the bit for this entry
572e: 85 d9 sta smartbomb_mask ;save it
5730: 60 :Return rts
;
; Creates one or more ICBMs at the top of the screen.
;
5731: 20 e2 56 CreateIcbms jsr DoCreateIcbm ;create ICBM
5734: c6 de dec atk_create_ctr ;decrement count
5736: 10 f9 bpl CreateIcbms ;if not done, loop
5738: 60 rts
;
; Launches one or more ICBMs from the flier.
;
]icbm_slot .var $ac {addr/1}
5739: a0 08 FlierFire ldy #$08 ;flier is in 9th ICBM slot
;
; Splits a missile. We create up to 3 additional missiles.
;
; On entry:
; Y-reg: "source" ICBM slot index (0-7) or flier index (8)
; $de: number of missiles to create, minus one
;
573b: a5 de SplitMirv lda atk_create_ctr ;get number of things to create
573d: c9 02 cmp #$02 ;is it 1?
573f: 90 02 bcc :Lt2 ;yes, branch
5741: a9 02 lda #$02 ;no, cap it at 2 (which means 3 new ICBMs)
5743: 85 de :Lt2 sta atk_create_ctr ;update counter
5745: 84 ac sty ]icbm_slot ;save "source" ICBM slot (0-8)
; Find the first free ICBM slot. If nothing is free, $97 is unmodified. We
; test the value here, but haven't explicitly set it to anything, so it's
; unclear whether the BMI that follows will ever be taken. (Also, the BMI is
; branching to a BPL that will fall through, rather than the RTS that follows...
; perhaps there was some conditionally-assembled code there in the source?)
5747: 20 82 57 :Loop jsr FindFreeIcbmSlot ;find a free ICBM slot (0-7)
574a: a6 97 ldx slot_index ;get result
574c: 30 31 bmi :BplLoop ;if it's negative, bail
;
574e: a4 ac ldy ]icbm_slot ;get "source" ICBM slot (0-8)
5750: a9 00 lda #$00 ;default offset to zero
; This appears to be trying to offset the missile launch location by +/-4, based
; on the flier's movement direction and screen orientation. The code doesn't
; seem to get executed though, because the initial test is wrong.
5752: c0 09 cpy #$09 ;(bug? should be #$08?)
5754: d0 0b bne :NotFlier ;not flier, branch
5756: a5 c8 lda flier_move_dir ;moving to the right?
5758: 10 05 bpl :FlyRight ;yes, branch
575a: a9 fc lda #$fc ;no, start at X-4
575c: b8 clv
575d: 50 02 bvc :NotFlier
575f: a9 04 :FlyRight lda #$04 ;start at X+4
5761: 24 fc :NotFlier bit cocktail_flip ;are we flipped?
5763: 50 02 bvc :NoFlip ;no, branch
5765: 49 ff eor #$ff ;negate the offset (not 2's complement?)
;
5767: 18 :NoFlip clc
5768: 79 30 01 adc icbm_cur_x,y ;add the offset to the X coord of the source
576b: 9d 30 01 sta icbm_cur_x,x ;set X coord of new missile
576e: 9d 8a 01 sta icbm_start_x_arr,x
5771: b9 65 01 lda icbm_cur_y,y ;get Y coord of source
5774: 9d 65 01 sta icbm_cur_y,x ;set Y coord of new missile
5777: 9d 9a 01 sta icbm_start_y_arr,x
577a: 20 91 57 jsr SetIcbmTarget ;pick a destination and set it
577d: c6 de dec atk_create_ctr ;have we created all new items?
577f: 10 c6 :BplLoop bpl :Loop ;not yet, branch
5781: 60 rts
;
; Finds the first free ICBM slot.
;
; The callers react to $97 being negative, as if that were expected to mean that
; no free slot was found. However, that can't happen unless the caller
; initializes $97 to a negative value, which they don't do. (Bug? Could be
; fixed by swapping the order of BNE and STX.)
;
; On exit:
; $97: ICBM slot (0-7) if one was found, unmodified if not
;
FindFreeIcbmSlot
5782: a2 07 ldx #$07
5784: bd 65 01 :Loop lda icbm_cur_y,x ;check current Y position
5787: d0 04 bne :InUse ;not zero, slot in use; branch
5789: 86 97 stx slot_index ;save index
578b: a2 00 ldx #$00 ;ensure BPL falls through
578d: ca :InUse dex
578e: 10 f4 bpl :Loop ;if we haven't found a slot, loop
5790: 60 rts
;
; Chooses a city or silo target for an ICBM or smartbomb. Sets the target
; coordinates in the specified ICBM slot, then sets up the path parameters.
;
; On entry:
; $97: ICBM slot index (0-7)
;
]untarg_city .var $98 {addr/1}
]untarg_silo .var $99 {addr/1}
5791: a6 b9 SetIcbmTarget ldx cur_plyr_num ;get current player number (0/1)
5793: b5 c5 lda live_city_mask,x ;get bit map of live cites
5795: 45 cb eor city_targ_flags ;remove cities that have been targeted
5797: 35 c5 and live_city_mask,x ;strip out cities that were targeted but are now dead
5799: 85 98 sta ]untarg_city ;save map of untargeted cities
579b: a5 cd lda live_silo_mask ;get bit map of live silos
579d: 45 cc eor silo_targ_flags ;remove silos that have been targeted
579f: 25 cd and live_silo_mask ;strip out silos that were targeted but are now dead
57a1: 85 99 sta ]untarg_silo ;save map of untargeted silos
; Add the number of cities destroyed in this wave to the number of targeted-but-
; live cities. If it's >= 3, aim at a silo instead. This ensures that we never
; destroy more than 3 cities in a wave.
57a3: a4 cf ldy cities_destroyed ;get number of cities destroyed so far this wave
57a5: a5 cb lda city_targ_flags ;get city target flags
57a7: 35 c5 and live_city_mask,x ;remove dead cities
57a9: 10 01 :Loop bpl :HiClear ;high bit not set, branch
57ab: c8 iny ;increment count
57ac: 0a :HiClear asl A ;shift next bit into high bit
57ad: d0 fa bne :Loop ;branch if cities remain
57af: c0 03 cpy #$03 ;is it >= 3?
57b1: b0 1e bcs :AimAtSilo ;yes, aim at a silo instead
;
57b3: a0 00 ldy #$00 ;pick a city (0+)
57b5: a5 98 lda ]untarg_city ;get bit map of untargeted cities
57b7: f0 06 beq :RndSilo ;no untargeted cities, branch to shoot silo instead
57b9: 20 1d 58 jsr PickRndBit ;pick an untargeted city at random
57bc: b8 clv
57bd: 50 0f bvc :Br_SetTarget ;(always)
57bf: a0 06 :RndSilo ldy #$06 ;pick a silo (6+)
57c1: a5 99 lda ]untarg_silo ;get bit map of untargeted silos
57c3: f0 06 beq :RndAny ;no untargeted silos, branch to pick anything
57c5: 20 1d 58 jsr PickRndBit ;pick an untargeted silo at random
57c8: b8 clv
57c9: 50 03 bvc :Br_SetTarget ;(always)
57cb: 20 0c 58 :RndAny jsr GetRnd9 ;pick any city or silo, alive or dead
57ce: b8 :Br_SetTarget clv
57cf: 50 0f bvc :SetTarget ;(always)
57d1: a0 06 :AimAtSilo ldy #$06 ;pick a silo (6+)
57d3: a5 99 lda ]untarg_silo ;get bit map of untargeted silos
57d5: f0 06 beq :DoubleCity ;no untargeted silos, double up on a city instead
57d7: 20 1d 58 jsr PickRndBit ;pick an untargeted silo at random
57da: b8 clv
57db: 50 03 bvc :SetTarget ;(always)
57dd: 20 17 58 :DoubleCity jsr PickRndTargCity ;pick an already-targeted city
;
; ICBM slot index (0-7) in $97, target city or silo in Y-reg (0-8).
57e0: a9 ff :SetTarget lda #$ff
57e2: 20 47 58 jsr UpdateTargetFlag ;update target flag bits for cities or silos
57e5: a6 97 ldx slot_index ;get ICBM slot index (0-7)
57e7: b9 e2 60 lda city_x_tbl,y ;get target's horizontal position
57ea: 9d aa 01 sta icbm_targ_x_arr,x ;set target X coord
57ed: b9 eb 60 lda city_y_tbl,y ;get target's vertical position
57f0: 9d ba 01 sta icbm_targ_y_arr,x ;set target Y coord
57f3: a9 00 lda #$00
57f5: 9d 20 01 sta icbm_cur_xfrac,x ;set fractional part to zero
57f8: 9d 55 01 sta icbm_cur_yfrac,x
57fb: bd f7 60 lda single_bits,x ;get bit for this slot
57fe: 49 ff eor #$ff ;invert to form an AND mask
5800: 25 d9 and smartbomb_mask ;mark it as not a smartbomb
5802: 85 d9 sta smartbomb_mask ;(this will be overridden later if it is a bomb)
5804: 20 fa 58 jsr CalcIcbmPath ;set up the path to the target
5807: c6 8d dec wave_icbm_count ;decrement the number of pending ICBMs
5809: e6 8c inc num_live_icbms ;increment the number of active ICBMs
580b: 60 rts ;(counters will be adjusted later for smartbombs)
;
; Returns a random number in the range [0,8]. Used to pick a ground target (6
; cities, 3 silos).
;
; Values 1-7 are more likely (1 in 8) than 0 or 8 (1 in 16 each).
;
; On exit:
; Y-reg: random value [0,8]
;
580c: ad 0a 40 GetRnd9 lda POKEY_RANDOM ;get random number
580f: 4a lsr A ;shift low bit into carry
5810: 29 07 and #$07 ;reduce to 0-7
5812: a8 tay
5813: 90 01 bcc :Return ;if the carry was clear, return
5815: c8 iny ;add one; max value now 8
5816: 60 :Return rts
;
; Inverts bit mask, then falls through to PickRndBit.
;
; Used to pick an already-targeted city.
;
; On entry:
; $98: bit map of untargeted cities (must not be $ff)
;
]untarg_city .var $98 {addr/1}
5817: a0 00 PickRndTargCity ldy #$00 ;set initial value to zero
5819: a5 98 lda ]untarg_city ;get mask
581b: 49 ff eor #$ff ;invert bits
;
; Given a byte with 1 or more bits set, picks a bit at random.
;
; This is used to pick a city or silo from a bit map, to attack or reconstruct.
;
; NOTE: if A-reg is zero, this will hang.
;
; On entry:
; Y-reg: initial value (e.g. $00 for cities, $06 for silos)
; A-reg: bit mask
;
; On exit:
; Y-reg: initial value + bit index
;
]num_set .var $98 {addr/1}
581d: 48 PickRndBit pha ;preserve bit mask
581e: a2 00 ldx #$00 ;init count
5820: 29 ff :Loop1 and #$ff ;no-op to set flags for A-reg
5822: 10 01 bpl :NotSet ;branch if high bit not set
5824: e8 inx ;increment count
5825: 0a :NotSet asl A ;shift high bit out
5826: d0 f8 bne :Loop1 ;loop if there are more bits
5828: 86 98 stx ]num_set ;save number of set bits
; We found N set bits. Generate a random value [0,N-1].
582a: ad 0a 40 :Loop2 lda POKEY_RANDOM ;get a random number
582d: 3d 3e 58 and :bit_masks-1,x ;strip off bits outside valid range
5830: c5 98 cmp ]num_set ;is it >= the number of bits we found?
5832: b0 f6 bcs :Loop2 ;yes, try again
; We want the Nth set bit. Figure out which bit number that is.
5834: aa tax ;use random value as counter
5835: 68 pla ;restore bit mask
5836: 88 dey ;decr so first incr is zero
5837: 0a :Loop3 asl A ;shift high bit into carry
5838: c8 iny ;increment counter
5839: 90 fc bcc :Loop3 ;not set, loop
583b: ca dex ;found set bit, decrement rnd val
583c: 10 f9 bpl :Loop3 ;if not at the bit we want, loop
583e: 60 rts ;result in Y-reg
583f: 00 01 03 03+ :bit_masks .bulk $00,$01,$03,$03,$07,$07,$07,$07 ;masks for roughly limiting the random number
;
; Sets or clears a target flag. This lets the game see which sites have been
; targeted already.
;
; On entry:
; Y-reg: target index: 0-5 city, 6-8 silo
; A-reg: < $80 to clear bit, >= $80 to set bit
;
; On exit:
; Y-reg: preserved
;
• Clear variables
]temp .var $98 {addr/1}
]saved_y .var $99 {addr/1}
UpdateTargetFlag
5847: 85 98 sta ]temp
5849: 84 99 sty ]saved_y ;preserve Y-reg
584b: a2 00 ldx #$00 ;assume city byte
584d: 98 tya
584e: c9 06 cmp #$06 ;is it 0-5?
5850: 90 06 bcc :CityByte ;yes, update the city byte
5852: 38 sec
5853: e9 06 sbc #$06 ;reduce 6-8 to 0-2
5855: a8 tay
5856: a2 01 ldx #$01 ;update the silo byte
;
5858: b9 f7 60 :CityByte lda single_bits,y ;get bit for index
585b: 49 ff eor #$ff ;invert to form AND mask
585d: 35 cb and city_targ_flags,x ;clear flag
585f: 24 98 bit ]temp ;is high bit of arg set?
5861: 10 03 bpl :ClearBit ;no, leave bit clear
5863: 19 f7 60 ora single_bits,y ;yes, set the bit
5866: 95 cb :ClearBit sta city_targ_flags,x ;save result
5868: a4 99 ldy ]saved_y ;restore Y-reg
586a: 60 rts
;
; Creates an explosion for an ABM, ICBM, or smartbomb.
;
; On entry:
; $97: slot of thing that exploded (0-15)
;
; On exit:
; (X-reg preserved)
;
]saved_x .var $ab {addr/1}
586b: 86 ab CreateExplosion stx ]saved_x ;preserve X-reg
586d: a6 97 ldx slot_index ;get slot index for exploding item
586f: bd 5d 01 lda abm_cur_y,x ;check current Y coordinate
5872: c9 d2 cmp #210 ;at or above line 210?
5874: b0 2a bcs :RetireOrdnance ;yes, could overwrite score; don't show
;
5876: a4 96 ldy next_expl_slot ;get next available explosion slot
5878: c6 96 dec next_expl_slot ;update slot number
587a: 10 04 bpl :SlotOk ;not negative, branch
587c: a9 13 lda #19 ;reset slot; total of 20 slots available
587e: 85 96 sta next_expl_slot
;
5880: e6 8e :SlotOk inc num_explosions ;increment the number of active explosions
5882: bd 28 01 lda abm_cur_x,x ;copy X coordinate to explosion table
5885: 99 39 01 sta expl_xpos,y
5888: bd 5d 01 lda abm_cur_y,x ;copy Y coordinate
588b: 99 6e 01 sta expl_ypos,y
588e: e0 08 cpx #$08 ;slot 0-7 (ABM)?
5890: 90 07 bcc :Abm ;yes, branch
5892: e6 90 inc num_expl_icbms ;no, increment number of ICBM explosions
5894: a9 80 lda #$80 ;set high bit on explosion radius index value
5896: b8 clv
5897: 50 04 bvc :SetRadius ;(always)
5899: e6 8f :Abm inc num_expl_abms ;increment number of ABM explosions
589b: a9 00 lda #$00 ;leave high bit clear on explosion radius index
;
589d: 99 c2 01 :SetRadius sta expl_radius_idx_arr,y ;set index into explosion radius array
58a0: a9 00 :RetireOrdnance lda #$00 ;send the abm, missile, or smartbomb to a nice
58a2: 9d 5d 01 sta abm_cur_y,x ; farm upstate
58a5: 9d 28 01 sta abm_cur_x,x
58a8: a6 ab ldx ]saved_x ;restore X-reg
58aa: 60 rts
;
; Checks to see if the distance between two points is less than or equal to a
; certain distance. Returns with the carry clear if the distance between the
; two points is less than the distance argument. Used for collision testing.
;
; The distance between two objects can be approximated with:
; distance = 0.41 * dx + 0.941246 * dy
; (for dy > dx; flip the values if dy < dx)
; We then approximate that further:
; distance = 0.375 * dx + 1.0 * dy
; This is easy because 0.375 is 3/8.
;
; On entry:
; $9b/9c: X/Y coordinate #1
; $9d/9e: X/Y coordinate #2
; $9a: distance to test
;
; On exit:
; C-flag: result of calc_dist CMP arg, i.e. clear if collision
;
• Clear variables
]small_part .var $98 {addr/1}
]large_part .var $99 {addr/1}
]dist .var $9a {addr/1}
]xc_1 .var $9b {addr/1}
]yc_1 .var $9c {addr/1}
]xc_2 .var $9d {addr/1}
]yc_2 .var $9e {addr/1}
]deltaY .var $b1 {addr/1}
58ab: a5 9e CheckDist lda ]yc_2 ;get Y2
58ad: c5 9c cmp ]yc_1 ;less than Y1?
58af: 90 05 bcc :Y2LtY1 ;yes, branch
58b1: e5 9c sbc ]yc_1 ;no, compute Y2-Y1
58b3: b8 clv
58b4: 50 05 bvc :ChkDeltaY
58b6: a5 9c :Y2LtY1 lda ]yc_1 ;compute Y1-Y2
58b8: 38 sec
58b9: e5 9e sbc ]yc_2
58bb: c5 9a :ChkDeltaY cmp ]dist ;is deltaY >= dist?
58bd: b0 36 bcs :Return ;yes, collision not possible; bail
58bf: 85 b1 sta ]deltaY
;
58c1: a5 9d lda ]xc_2 ;get X2
58c3: c5 9b cmp ]xc_1 ;less than X1?
58c5: 90 05 bcc :X2LtX1 ;yes, branch
58c7: e5 9b sbc ]xc_1 ;no, compute X2-X1
58c9: b8 clv
58ca: 50 05 bvc :ChkDeltaX
58cc: a5 9b :X2LtX1 lda ]xc_1 ;compute X1-X2
58ce: 38 sec
58cf: e5 9d sbc ]xc_2
58d1: c5 9a :ChkDeltaX cmp ]dist ;is deltaX >= dist?
58d3: b0 20 bcs :Return ;yes, collision not possible; bail
;
58d5: c5 b1 cmp ]deltaY ;is deltaX >= deltaY?
58d7: b0 09 bcs :DeltaXBig ;yes, branch
58d9: 85 98 sta ]small_part ;no, deltaY is larger
58db: a5 b1 lda ]deltaY
58dd: 85 99 sta ]large_part
58df: b8 clv
58e0: 50 06 bvc :DoCalc
58e2: 85 99 :DeltaXBig sta ]large_part ;deltaX is larger
58e4: a5 b1 lda ]deltaY
58e6: 85 98 sta ]small_part
;
58e8: a5 98 :DoCalc lda ]small_part ;get small part
58ea: 4a lsr A ;divide by 2
58eb: 18 clc
58ec: 65 98 adc ]small_part ;add to itself to get 3/2
58ee: 4a lsr A ;divide by 4 to get 3/8
58ef: 4a lsr A
58f0: 18 clc
58f1: 65 99 adc ]large_part ;add to large part
58f3: c5 9a cmp ]dist ;compare to distance arg
58f5: 60 :Return rts ;return with carry flag conditioned appropriately
;
; Sets up the math for a missile.
;
; (Note: 2 out of 3 call sites do an LDY with the ABM slot index immediately
; before calling here, which conditions the 'N' flag and makes the STY
; redundant. The call from $52ed doesn't do this, but the flag happens to be
; conditioned by restoring the X-reg, which holds a value from 0-2. So the BPL
; at $58f8 appears to be a branch-always.)
;
; On entry:
; Y-reg: ABM/ICBM slot index (0-15)
; N flag: clear
;
; On exit:
; $b0-b1: deltaX/deltaY
; (X/Y-reg preserved)
;
58f6: 84 97 CalcMissilePath sty slot_index
58f8: 10 06 bpl DoMissilePath ;(always?)
;
; Sets up the math for an ICBM. Adds 8 to the ICBM index to form a general slot
; index.
;
; On entry:
; X-reg: ICBM index (0-7)
;
; On exit:
; (X/Y-reg preserved)
;
58fa: 8a CalcIcbmPath txa
58fb: 18 clc
58fc: 69 08 adc #$08
58fe: 85 97 sta slot_index
;
; Sets up the path parameters for a missile.
;
; The start and end positions have already been set. This routine sets up the
; values that determine how the missile gets from one place to the other.
;
; On entry:
; $97: missile slot index (0-15)
;
; On exit:
; $b0-b1: deltaX/deltaY
; (X/Y-reg preserved)
;
• Clear variables
]small_axis .var $98 {addr/1}
]big_axis .var $99 {addr/1}
]result_int .var $a9 {addr/1}
]result_frac .var $aa {addr/1}
]deltaX .var $b0 {addr/1}
]deltaY .var $b1 {addr/1}
5900: 8a DoMissilePath txa
5901: 48 pha ;preserve X-reg
5902: 98 tya
5903: 48 pha ;preserve Y-reg
; Compute absolute value of deltaX and deltaY.
5904: a6 97 ldx slot_index ;get missile slot index
5906: bd b2 01 lda abm_targ_y_arr,x ;get target Y coord
5909: dd 5d 01 cmp abm_cur_y,x ;compare to initial Y coord
590c: 90 06 bcc :TargYSmaller
590e: fd 5d 01 sbc abm_cur_y,x ;compute diff
5911: b8 clv
5912: 50 07 bvc :SetDeltaY ;(always)
5914: bd 5d 01 :TargYSmaller lda abm_cur_y,x ;get initial position
5917: 38 sec
5918: fd b2 01 sbc abm_targ_y_arr,x ;subtract target position
591b: 85 b1 :SetDeltaY sta ]deltaY ;save absolute value of delta Y
;
591d: bd a2 01 lda abm_targ_x_arr,x ;get target X coord
5920: dd 28 01 cmp abm_cur_x,x ;compare to initial X coord
5923: 90 06 bcc :TargXSmaller ;target X smaller, branch
5925: fd 28 01 sbc abm_cur_x,x
5928: b8 clv
5929: 50 07 bvc :SetDeltaX ;(always)
592b: bd 28 01 :TargXSmaller lda abm_cur_x,x ;get initial position
592e: 38 sec
592f: fd a2 01 sbc abm_targ_x_arr,x ;subtract target position
5932: 85 b0 :SetDeltaX sta ]deltaX ;save absolute value of delta X
; Determine which is the "big" axis and which is the "small" axis.
5934: a5 b0 lda ]deltaX ;get deltaX (redundant?)
5936: c5 b1 cmp ]deltaY ;is it >= deltaY?
5938: b0 09 bcs :XBigger ;yes, branch
593a: 85 98 sta ]small_axis ;X is small
593c: a5 b1 lda ]deltaY
593e: 85 99 sta ]big_axis ;Y is large
5940: b8 clv
5941: 50 06 bvc :GotBigSmall
5943: 85 99 :XBigger sta ]big_axis ;X is large
5945: a5 b1 lda ]deltaY
5947: 85 98 sta ]small_axis ;Y is small
; Compute an approximation of the length of the line. This uses the same "large
; + 3/8 * small" formula as at $58ab. ICBM corner shots can exceed 255 (e.g.
; $b5,$de to $14,$16 is 260), so to keep it in 8 bits we limit the result.
5949: a5 98 :GotBigSmall lda ]small_axis ;get small axis delta
594b: 4a lsr A ;divide by 2
594c: 18 clc
594d: 65 98 adc ]small_axis ;add to itself to get 3/2
594f: 4a lsr A ;divide by 4 to get 3/8
5950: 4a lsr A
5951: 18 clc
5952: 65 99 adc ]big_axis ;add the major axis
5954: 90 02 bcc :NoOvfl ;branch if it fit in 8 bits
5956: a9 ff lda #$ff ;cap at 255
]distance .var $98 {addr/1}
5958: 85 98 :NoOvfl sta ]distance ;store in ZP
; Compute increments for X and Y that will advance the missile 1 unit toward its
; destination. If the missile's path is 200 units long, it will reach its
; target in 200 frames. Values are stored as 8.8 fixed-point. Because the
; distance is approximate, the line may not intersect the actual target pixel.
595a: a4 98 ldy ]distance
595c: a5 b1 lda ]deltaY
595e: 20 bb 59 jsr Divide8_8 ;compute (deltaY / distance)
5961: a5 a9 lda ]result_int ;save result
5963: 9d 30 06 sta msl_inc_yint,x
5966: a5 aa lda ]result_frac
5968: 9d 20 06 sta msl_inc_yfrac,x
;
596b: a4 98 ldy ]distance
596d: a5 b0 lda ]deltaX
596f: 20 bb 59 jsr Divide8_8 ;compute (deltaX / distance)
5972: a5 a9 lda ]result_int ;save result
5974: 9d 10 06 sta msl_inc_xint,x
5977: a5 aa lda ]result_frac
5979: 9d 00 06 sta msl_inc_xfrac,x
; The above was computed with absolute values. Negate values as needed.
597c: bd b2 01 lda abm_targ_y_arr,x ;get target Y coord
597f: dd 5d 01 cmp abm_cur_y,x ;is it >= the initial Y coord?
5982: b0 15 bcs :YOkay ;yes, branch
5984: bd 20 06 lda msl_inc_yfrac,x ;no, invert the 8.8 value (2's complement)
5987: 49 ff eor #$ff
5989: 18 clc
598a: 69 01 adc #$01
598c: 9d 20 06 sta msl_inc_yfrac,x
598f: bd 30 06 lda msl_inc_yint,x ;high byte too
5992: 49 ff eor #$ff
5994: 69 00 adc #$00
5996: 9d 30 06 sta msl_inc_yint,x
;
5999: bd a2 01 :YOkay lda abm_targ_x_arr,x ;get target X coord
599c: dd 28 01 cmp abm_cur_x,x ;is it >= the initial Y coord?
599f: b0 15 bcs :Done ;yes, branch
59a1: bd 00 06 lda msl_inc_xfrac,x ;no, invert the 8.8 value (2's complement)
59a4: 49 ff eor #$ff
59a6: 18 clc
59a7: 69 01 adc #$01
59a9: 9d 00 06 sta msl_inc_xfrac,x
59ac: bd 10 06 lda msl_inc_xint,x ;high byte too
59af: 49 ff eor #$ff
59b1: 69 00 adc #$00
59b3: 9d 10 06 sta msl_inc_xint,x
;
59b6: 68 :Done pla
59b7: a8 tay ;restore Y-reg
59b8: 68 pla
59b9: aa tax ;restore X-reg
59ba: 60 rts
;
; Divides an 8-bit value by an 8-bit value, yielding an 8.8 result.
;
; For our uses the denominator is always equal or larger, so the integer part of
; the quotient should always be one or zero.
;
; On entry:
; A-reg: numerator (deltaX or deltaY)
; Y-reg: denominator (distance)
;
; On exit:
; $a9/aa: 8.8 result
;
]work_bits .var $a8 {addr/1}
]saved_x .var $ab {addr/1}
]denom .var $ad {addr/1}
59bb: 84 ad Divide8_8 sty ]denom ;store denominator in ZP
59bd: 86 ab stx ]saved_x ;preserve X-reg
59bf: 20 d0 59 jsr :CalcQuot ;calculate integer quotient
59c2: 86 a9 stx ]result_int ;save integer part; note work bits are still in A-reg
59c4: a2 00 ldx #$00 ;treat numerator as 16-bit value by
59c6: 86 a8 stx ]work_bits ; sign-extending with zero
59c8: 20 d4 59 jsr :CalcRem ;calculate fractional part
59cb: 86 aa stx ]result_frac ;save fractional part
59cd: a6 ab ldx ]saved_x ;restore X-reg
59cf: 60 rts
59d0: 85 a8 :CalcQuot sta ]work_bits ;init to numerator
59d2: a9 00 lda #$00 ;init A-reg to zero
59d4: a0 07 :CalcRem ldy #$07 ;8 bits
59d6: 26 a8 :Loop1 rol ]work_bits ;roll carry into low, roll high bit into accumulator,
59d8: 2a rol A ; roll high bit into carry
59d9: 90 05 bcc :NoSub1
59db: e5 ad sbc ]denom ;carry set, acc must be larger, subtract denom
59dd: 38 sec
59de: b0 06 bcs :NoSub2 ;(always)
59e0: c5 ad :NoSub1 cmp ]denom ;is acc larger than denominator?
59e2: 90 02 bcc :NoSub2 ;no, branch
59e4: e5 ad sbc ]denom ;yes, subtract denominator
59e6: 88 :NoSub2 dey ;done yet?
59e7: 10 ed bpl :Loop1 ;no, loop
59e9: 26 a8 rol ]work_bits ;roll last bit in
59eb: a6 a8 ldx ]work_bits ;get as return value
59ed: 60 rts
;
; Checks to see if the wave is done. In some cases we can run everything at
; maximum speed, in others we can stop immediately.
;
• Clear variables
]have_abms .var $99 {addr/1}
59ee: a4 b9 CheckWaveDone ldy cur_plyr_num ;get current player number
59f0: a5 a0 lda abms_left ;see if there are any ABMs left
59f2: 05 a1 ora abms_left+1
59f4: 05 a2 ora abms_left+2
59f6: 85 99 sta ]have_abms ;save for later
59f8: d0 24 bne :StillFighting ;if player has some ABMs, branch
;
59fa: b9 c5 00 lda live_city_mask,y ;check if any cities remain
59fd: f0 08 beq :BneRarely ;no, branch (to not-taken BNE)
59ff: a5 cf lda cities_destroyed ;see how many were destroyed this wave
5a01: c9 03 cmp #$03 ;< 3?
5a03: 90 02 bcc :BneRarely ;yes, branch
5a05: a9 00 lda #$00 ;no, >= 3; load zero to ensure BNE is not taken
5a07: d0 15 :BneRarely bne :StillFighting ;branch if 1 or 2 cities were destroyed
; No ABMs, and no cities or 3+ cities destroyed this wave. Have mercy.
5a09: a9 00 lda #$00
5a0b: 85 c9 sta flier_fire_ctr ;restart flier firing counter
5a0d: 85 8d sta wave_icbm_count ;stop launching ICBMs and bombs
5a0f: 85 db sta wave_sbomb_count
5a11: ad 6d 01 lda flier_cur_y ;do we have a flier?
5a14: 05 bc ora active_abm_count ;or an in-flight ABM?
5a16: 05 8e ora num_explosions ;or an explosion?
5a18: d0 04 bne :StillFighting ;yes, branch
5a1a: a9 08 lda #FN_END_WAVE ;no, go straight to end-of-wave func, even if
5a1c: 85 91 sta func_index ; bombs/missiles are incoming (they're harmless)
;
5a1e: a5 bc :StillFighting lda active_abm_count ;do we have an in-flight ABM?
5a20: 05 8e ora num_explosions ;or an explosion?
5a22: 05 99 ora ]have_abms ;or unfired ABMs?
5a24: d0 04 bne :StaySlow ;yes, branch
5a26: a9 ff lda #$ff
5a28: 85 bf sta full_speed_flag ;set the "full speed" flag
5a2a: a5 bf :StaySlow lda full_speed_flag ;is "full speed" flag set?
5a2c: f0 0d beq :CheckBusy ;no, branch
;
5a2e: a9 00 lda #$00 ;zero out movement delays, so flier and
5a30: 85 c7 sta flier_move_ctr ; incoming ordnance move at max speed
5a32: 85 b3 sta icbm_move_ctr+1
5a34: 85 b8 sta icbm_move_delay+1
5a36: 85 b7 sta icbm_move_delay
5a38: 20 72 65 jsr HideCrosshairs ;hide the crosshairs
;
5a3b: a5 8d :CheckBusy lda wave_icbm_count ;are there pending ICBMs?
5a3d: 05 bc ora active_abm_count ;or in-flight ABMs?
5a3f: 05 db ora wave_sbomb_count ;or pending smartbombs?
5a41: 05 8c ora num_live_icbms ;or in-flight ICBMs?
5a43: 05 da ora num_live_bombs ;or in-flight smartbombs?
5a45: 0d 6d 01 ora flier_cur_y ;or a flier?
5a48: 05 8e ora num_explosions ;or an explosion?
5a4a: d0 04 bne :Return ;yes, branch
5a4c: a9 08 lda #FN_END_WAVE ;no, this wave is 100% complete, so
5a4e: 85 91 sta func_index ; go to end-of-wave func
5a50: 60 :Return rts
5a51: 4d .dd1 $4d ;checksum byte
;
; Updates the flashing indicator arrows.
;
; If a game is in progress, this draws the 1UP/2UP indicator next to the
; player's score. If a game is not in progress, and we're in the "play game"
; phase of attract mode, this draws a downward facing arrow over each city.
;
• Clear variables
]counter .var $ab {addr/1}
]color_tmp .var $b6 {addr/1}
UpdateFlashIndic
5a52: a5 93 lda play_mode_flag ;are we playing?
5a54: f0 03 beq :CityArrows ;no, branch
5a56: 4c f0 65 jmp UpdatePlyrIndic ;yes, go do the 1up/2up arrow
5a59: a5 91 :CityArrows lda func_index ;check which function we're in
5a5b: c9 04 cmp #FN_PLAY_GAME ;game play demo?
5a5d: d0 22 bne :Return ;no, bail
;
5a5f: a0 00 ldy #$00 ;fg color = 0
5a61: a5 d7 lda draw_1up_flag ;are we in the visible phase?
5a63: d0 02 bne :SetColor ;no, branch
5a65: a0 40 ldy #%01000000 ;fg color = 2
5a67: 84 b6 :SetColor sty ]color_tmp ;save color to ZP
;
5a69: a2 05 ldx #$05 ;6 cities
5a6b: 86 ab :Loop stx ]counter ;set ZP counter
5a6d: a5 b6 lda ]color_tmp ;get color
5a6f: 85 0b sta draw_fg_color ;set glyph foreground
5a71: a0 24 ldy #36 ;vertical position
5a73: bd e2 60 lda city_x_tbl,x ;get horizontal position for city
5a76: aa tax ;put in X-reg
5a77: a9 1c lda #$1c ;down arrow glyph
5a79: 20 00 66 jsr DrawLetterByIdx ;draw it
5a7c: a6 ab ldx ]counter
5a7e: ca dex ;done with all cities?
5a7f: 10 ea bpl :Loop ;not yet, loop
5a81: 60 :Return rts
;
; Func $14: draws the "MISSILE COMMAND" title.
;
; The words are drawn while the palette is entirely blacked out, so that instead
; of visibily rendering they just pop onto the screen.
;
]initial_delay_ctr .var $b2 {addr/1} ;(using ICBM move counter var)
]expl_rep_ctr .var $b3 {addr/1} ;(using ICBM move counter var)
5a82: a9 10 F_ShowTitle lda #16 ;set counter to repeat explosion pattern 16x
5a84: 85 b3 sta ]expl_rep_ctr
5a86: a9 40 lda #64 ;set initial delay to 64 frames (~1 sec)
5a88: 85 b2 sta ]initial_delay_ctr
5a8a: a9 00 lda #$00
5a8c: 85 93 sta play_mode_flag ;not playing a game right now
5a8e: 85 ec sta color_cyc_mask ;no cycling
;
5a90: a2 07 ldx #$07 ;palette has 8 entries
5a92: a9 0e lda #%00001110 ;init them to black
5a94: 95 e4 :Loop sta color_palette,x
5a96: ca dex
5a97: 10 fb bpl :Loop
;
5a99: 20 86 69 jsr ClearScreen ;erase screen to color 0
5a9c: a9 1c lda #$1c
5a9e: 20 51 6a jsr PrintMessage ;"MISSILE"
5aa1: a9 1d lda #$1d
5aa3: 20 51 6a jsr PrintMessage ;"COMMAND"
5aa6: a9 06 lda #%00000110 ;set the 6th entry to red
5aa8: 85 ea sta color_palette+6 ;this makes the words pop onto the screen
;
5aaa: a9 00 lda #$00
5aac: a2 13 ldx #19 ;20 entries in explosion table
5aae: 9d 6e 01 :Loop sta expl_ypos,x ;zero out the Y coord to mark them as unused
5ab1: ca dex
5ab2: 10 fa bpl :Loop
5ab4: a9 16 lda #FN_TITLE_EXPLS ;switch to title explosion func
5ab6: 85 91 sta func_index
5ab8: 60 rts
;
; Func $16: draws title screen explosion sequence.
;
; Draws and erases 20 explosions, sized and placed randomly.
;
; This uses the 16-bit ICBM movement counter ($b2-b3) as a pair of counters,
; initialized by func $14 (above).
;
5ab9: a5 b2 F_TitleExpls lda ]initial_delay_ctr ;has initial delay period finished?
5abb: f0 05 beq :ZeroB2 ;yes, branch
5abd: c6 b2 dec ]initial_delay_ctr ;no, update counter and bail
5abf: b8 clv
5ac0: 50 0e bvc :Return ;(always)
5ac2: c6 b3 :ZeroB2 dec ]expl_rep_ctr ;have we done enough exploding?
5ac4: d0 07 bne :DoExpl ;not yet, branch
;
5ac6: a9 00 lda #FN_INIT_GAME ;move on to game init func
5ac8: 85 91 sta func_index
5aca: b8 clv
5acb: 50 03 bvc :Return ;(always)
5acd: 20 b3 62 :DoExpl jsr DrawTitleExpl ;erase & redraw 20 explosions
5ad0: 60 :Return rts
;
; Func $00: initialize game data.
;
• Clear variables
5ad1: 20 2f 67 F_InitGame jsr InitLangAndFlip ;set language and orientation
5ad4: a9 40 lda #%01000000 ;color = 2
5ad6: 85 ce sta score_color ;set color to use for scores
5ad8: a5 93 lda play_mode_flag ;is a game in progress?
5ada: f0 03 beq :NoGame ;no, branch
5adc: 20 d6 5f jsr SetZeroScores ;zero out the scores
5adf: a9 00 :NoGame lda #$00 ;init some globals
5ae1: 85 df sta next_player
5ae3: 85 c1 sta num_cities+1
5ae5: 85 c6 sta live_city_mask+1
5ae7: 85 c3 sta bonus_award_sc
5ae9: 85 c4 sta bonus_award_sc+1
5aeb: a9 01 lda #$01
5aed: 85 a7 sta wave_num ;initialize wave number
;
5aef: a6 ae ldx num_players ;get number of players
5af1: a5 f4 lda r8_irq_inv ;get R8 switches
5af3: 29 03 and #%00000011 ;get initial city count setting
5af5: a8 tay ;use as index
5af6: b9 08 5b :Loop lda init_city_count,y ;get initial cities based on setting
5af9: 95 c0 sta num_cities,x ;set for current player
5afb: b9 0c 5b lda init_city_mask,y ;get "live city" bit mask
5afe: 95 c5 sta live_city_mask,x ;set for current player
5b00: ca dex ;done with both players?
5b01: 10 f3 bpl :Loop ;no, loop
;
5b03: a9 02 lda #FN_PREP_WAVE
5b05: 85 91 sta func_index ;switch to wave-prep function
5b07: 60 rts
;
; Initial number of cities, determined by R8 switches (Off=1, On=0):
; 00 = 6
; 01 = 4
; 10 = 5
; 11 = 7
;
5b08: 06 04 05 07 init_city_count .bulk $06,$04,$05,$07
;
; Live-city mask, indicating which cities are whole. Cities are not arranged
; from left to right; see the horizontal position table at $60e2.
5b0c: fc init_city_mask .dd1 %11111100 ;6 cities
5b0d: e8 .dd1 %11101000 ;4 cities
5b0e: f8 .dd1 %11111000 ;5 cities
5b0f: fc .dd1 %11111100 ;7 cities (6 + 1 bonus)
;
; Func $02: initializes values at the start of a wave.
;
5b10: a5 df F_PrepWave lda next_player ;get next-player number (0/1)
5b12: 85 b9 sta cur_plyr_num ;update current player number
5b14: 20 0c 7b jsr InitPokey ;reset sound
5b17: a9 10 lda #SFX_START_WAVE ;alert
5b19: 20 ec 79 jsr StartSfx
5b1c: a5 93 lda play_mode_flag ;is a game in progress?
5b1e: d0 09 bne :InitInc ;yes, branch
5b20: a0 01 ldy #$01 ;no, always be on wave #1
5b22: 84 a7 sty wave_num
5b24: a9 12 lda #18 ;but with 18 incoming missiles (instead of 12)
5b26: b8 clv
5b27: 50 0b bvc :SetInc ;(always)
; Set the number of ICBMs and smartbombs. In rev2, wave_num could be zero, and
; those table entries happen to be zero, making wave 256 a freebie.
5b29: a4 a7 :InitInc ldy wave_num ;get current wave number
5b2b: c0 13 cpy #19 ;is it >= 19?
5b2d: 90 02 bcc :Lt19 ;no, branch
5b2f: a0 13 ldy #19 ;cap at 19
5b31: b9 8f 60 :Lt19 lda icbm_count_tbl-1,y ;get incoming ICBM count for this wave
5b34: 85 8d :SetInc sta wave_icbm_count ;set counter
5b36: be c0 60 ldx sbomb_count_tbl-1,y ;get incoming smartbomb count for this wave
5b39: 86 db stx wave_sbomb_count ;set counter
;
5b3b: a9 30 lda #%00110000 ;configure color palette cycling to rotate
5b3d: 85 ec sta color_cyc_mask ; the colors for indices 4 and 5
5b3f: 20 1a 67 jsr PrepWaveScreen ;set colors, redraw city / silo area
5b42: 20 9e 5b jsr SetMultAndSpeed ;set score mult and incoming entity speeds
; Init per-wave globals.
5b45: a9 ff lda #$ff
5b47: 85 c2 sta mirv_slot
5b49: a9 00 lda #$00
5b4b: 85 bf sta full_speed_flag
5b4d: 85 96 sta next_expl_slot
5b4f: 85 8e sta num_explosions
5b51: 85 90 sta num_expl_icbms
5b53: 85 8f sta num_expl_abms
5b55: 85 da sta num_live_bombs
5b57: 85 c9 sta flier_fire_ctr
5b59: 85 d9 sta smartbomb_mask
5b5b: 85 8c sta num_live_icbms
5b5d: 85 d0 sta sbomb_dir
5b5f: 85 fa sta fire_buttons
5b61: 85 b4 sta exp_seq_index
5b63: 85 cc sta silo_targ_flags
5b65: 85 cb sta city_targ_flags
5b67: 85 b3 sta icbm_move_ctr+1
5b69: 85 cf sta cities_destroyed
; Init tables used for ABMs and ICBMs, and the flier.
5b6b: a2 aa ldx #$aa
5b6d: 9d 17 01 :ZLoop sta abm_cur_xfrac-1,x ;zero out $118-1c1
5b70: ca dex
5b71: d0 fa bne :ZLoop
;
5b73: a9 e0 lda #%11100000
5b75: 85 cd sta live_silo_mask ;mark all silos as alive
5b77: a5 93 lda play_mode_flag ;are we playing?
5b79: f0 03 beq :NoPlay ;no, branch
5b7b: 20 ce 65 jsr DrawPlyrIndic ;draw 1up/2up indicator
5b7e: 20 16 5f :NoPlay jsr PreWaveMsgs ;draw pre-wave messages
;
5b81: a0 02 ldy #2 ;three ABM silos
5b83: a9 0a :AbmLoop lda #10 ;10 ABMs per silo
5b85: 99 a0 00 sta abms_left,y ;full set of missiles
5b88: 20 6a 68 jsr DrawAllAmmo ;draw all ammo for this silo
5b8b: 88 dey ;finished all 3 silos?
5b8c: 10 f5 bpl :AbmLoop ;no, loop
;
5b8e: 20 5c 65 jsr InitCrosshairs ;put crosshairs in low-center position
5b91: a9 04 lda #FN_PLAY_GAME ;queue up the play-game func
5b93: 85 92 sta next_func_index
5b95: a9 22 lda #FN_PAUSE ;but pause briefly first
5b97: 85 91 sta func_index
5b99: a9 1e lda #30 ;delay for 30*4 frames (~2 sec)
5b9b: 85 af sta frame4_delay_ctr
5b9d: 60 rts
;
; Sets score multiplier based on the current wave. Sets the base score value,
; to 25 * multiplier. All scores awarded are multiples of this, except for
; unfired ABMs.
;
; Sets the wave-specific characteristics of ICBMs and fliers, such as speed.
;
; On exit:
; $ba-bb: base score configured
;
5b9e: a9 00 SetMultAndSpeed lda #$00 ;init base score to zero
5ba0: 85 ba sta base_score_bcd
5ba2: 85 bb sta base_score_bcd+1
5ba4: a5 a7 lda wave_num ;get current wave number (starts at 1)
5ba6: 18 clc
5ba7: 69 01 adc #$01 ;add 1 so wave 1/2 is 1x, wave 3/4 is 2x
5ba9: 4a lsr A ;cut it in half so we incr every other wave
5baa: c9 06 cmp #$06 ;less than 6?
5bac: 90 02 bcc :Lt6 ;yes, branch
5bae: a9 06 lda #$06 ;cap at 6x
5bb0: 85 dd :Lt6 sta score_mult ;save multiplier
5bb2: aa tax ;init count to multiplier
; Compute 25 * multiplier.
; In rev2, if wave_num is $ff (wave 255), adding one makes it $00, which causes
; this to execute 256 times. This yields a base score of 25*256=6400. The same
; problem also happens on wave 256: ($00+1)/2=0. Rev3 avoids the problem by
; resetting the wave number to 20 when it hits 40.
5bb3: f8 :MultLoop sed ;enable decimal mode
5bb4: a5 ba lda base_score_bcd ;get current value
5bb6: 18 clc
5bb7: 69 25 adc #$25 ;add 25
5bb9: 85 ba sta base_score_bcd
5bbb: a5 bb lda base_score_bcd+1
5bbd: 69 00 adc #$00 ;carry into high byte
5bbf: 85 bb sta base_score_bcd+1
5bc1: d8 cld ;disable decimal mode
5bc2: ca dex ;done yet?
5bc3: d0 ee bne :MultLoop ;no, branch
;
5bc5: a4 a7 ldy wave_num ;get wave number
5bc7: c0 0f cpy #15 ;less than 15?
5bc9: 90 02 bcc :Lt15 ;yes, branch
5bcb: a0 0f ldy #15 ;cap at 15
5bcd: b9 a2 60 :Lt15 lda icbm_spd_frac_tbl-1,y ;configure ICBM speed, as an 8.8 frame delay
5bd0: 85 b7 sta icbm_move_delay
5bd2: b9 b1 60 lda icbm_spd_int_tbl-1,y
5bd5: 85 b8 sta icbm_move_delay+1
;
5bd7: c0 08 cpy #8 ;wave number less than 8?
5bd9: 90 02 bcc :Lt8 ;yes, branch
5bdb: a0 08 ldy #8 ;cap at 8
5bdd: b9 d2 60 :Lt8 lda flier_msl_delay_tbl-2,y ;get flier firing delay
5be0: 85 e1 sta flier_fire_delay ;set value
5be2: b9 d9 60 lda flier_charge_int_tbl-2,y ;get flier recharge interval
5be5: 85 e2 sta flier_charge_int ;set value
5be7: 85 e3 sta flier_charge_ctr ;also set counter, so flier can launch immediately
5be9: 60 rts
;
; Func $08: end wave.
;
; Transitions to the ABM bonus tally, city bonus tally, or game-over check.
;
5bea: 20 37 5e F_EndOfWave jsr EraseIncoming ;remove ICBM trails and smartbombs
5bed: 20 72 65 jsr HideCrosshairs ;hide the crosshairs
5bf0: 20 96 5f jsr ZeroBcdAcc ;prep BCD accumulator for bonus tally
5bf3: a9 00 lda #$00
5bf5: 85 97 sta slot_index ;init counter used by ABM bonus screen
5bf7: a2 10 ldx #FN_WAVE_DONE_UPD ;assume next step will be post-wave update
5bf9: a5 93 lda play_mode_flag ;game in progress?
5bfb: f0 20 beq :StxReturn ;no, bail
;
5bfd: a5 a0 lda abms_left ;any ABMs left to fire?
5bff: 05 a1 ora abms_left+1
5c01: 05 a2 ora abms_left+2
5c03: f0 0a beq :NoAbms ;no, branch
5c05: a9 07 lda #$07 ;"BONUS POINTS"
5c07: 20 55 6a jsr PrintMessagePr
5c0a: a2 0a ldx #FN_ABM_BONUS ;next step: award points for leftover ABMs
5c0c: b8 clv
5c0d: 50 0e bvc :StxReturn
5c0f: a4 b9 :NoAbms ldy cur_plyr_num ;get current player number
5c11: b9 c5 00 lda live_city_mask,y ;any surviving cities?
5c14: f0 07 beq :StxReturn ;no, bail
5c16: a9 07 lda #$07
5c18: 20 55 6a jsr PrintMessagePr ;"BONUS POINTS"
5c1b: a2 0c ldx #FN_CITY_BONUS_CHK ;next step: award points for surviving cities
5c1d: 86 91 :StxReturn stx func_index
5c1f: 60 rts
;
; Func $0a: award bonus points for unfired ABMs.
;
; This is called repeatedly. If not enough time has passed, it returns
; immediately. Otherwise, it finds the next unfired ABM and awards bonus points
; for it.
;
; The global normally used to pass a slot index ($97) is used here to keep track
; of the screen position for the ABM icon display.
;
5c20: c6 b4 F_AbmBonusPts dec exp_seq_index ;decrement counter used to pace frames
5c22: 10 5d bpl :Return ;not ready to draw yet; bail
; Award bonus points for un-fired ABMs.
5c24: a0 02 ldy #$02 ;walk through 3 silos
5c26: b9 a0 00 :SiloLoop lda abms_left,y ;get number of ABMs remaining in silo
5c29: f0 3b beq :SiloEmpty
5c2b: 38 sec
5c2c: e9 01 sbc #$01 ;subtract 1
5c2e: 99 a0 00 sta abms_left,y ;write it back
5c31: 20 d1 67 jsr EraseAmmo ;erase that icon from the launcher
5c34: 20 bb 5f jsr AddBcd5x ;add (5 * multiplier) points
5c37: a2 40 ldx #64 ;X coord
5c39: a0 80 ldy #128 ;Y coord
5c3b: a9 40 lda #%01000000 ;color #2
5c3d: 20 f6 6a jsr PrintBcdNumber ;print score
5c40: a9 82 lda #130 ;vertical position
5c42: 49 ff eor #$ff ;invert to form pointer
5c44: 85 07 sta gfx_ptr+1 ;set as high byte
;
5c46: a5 97 lda slot_index ;get icon counter
5c48: 0a asl A ;multiply by 4
5c49: 0a asl A
5c4a: 18 clc
5c4b: 69 7a adc #122 ;start near center of screen
5c4d: 24 fc bit cocktail_flip ;are we flipped?
5c4f: 50 02 bvc :NoFlip ;no, branch
5c51: 49 ff eor #$ff ;invert X coord
5c53: 85 06 :NoFlip sta gfx_ptr ;set as low byte of pointer
;
5c55: a9 e0 lda #%11100000 ;color = 7
5c57: 20 ed 67 jsr DrawAmmoIcon ;draw ammo icon at pointer
5c5a: e6 97 inc slot_index ;increment icon counter
5c5c: a9 05 lda #$05
5c5e: 85 b4 sta exp_seq_index ;reset delay counter
5c60: a9 08 lda #SFX_BONUS_PTS
5c62: 20 ec 79 jsr StartSfx ;play "thup" sound
5c65: 60 rts
5c66: 88 :SiloEmpty dey ;move to next silo; done yet?
5c67: 10 bd bpl :SiloLoop ;no, loop
;
5c69: a9 0c lda #FN_CITY_BONUS_CHK ;check for city bonus next
5c6b: 85 92 sta next_func_index
5c6d: a2 0f ldx #15 ;delay for 15 frames (~0.25 sec)
5c6f: a4 b9 ldy cur_plyr_num ;get current player number
5c71: b9 c5 00 lda live_city_mask,y ;get bit map of surviving cities
5c74: f0 02 beq :NoCity ;wiped clean, branch
5c76: a2 00 ldx #$00 ;cancel delay
5c78: 86 af :NoCity stx frame4_delay_ctr ;store delay value
5c7a: 20 13 60 jsr AddBcdToScore ;add bonus points to player score
5c7d: a9 22 lda #FN_PAUSE ;pause briefly before moving on
5c7f: 85 91 sta func_index
5c81: 60 :Return rts
;
; Func $0c: checks to see if we need to award a bonus for cities.
;
5c82: a9 10 F_CityBonusChk lda #$10 ;default to end-of-wave housekeeping func
5c84: 85 91 sta func_index
5c86: a4 b9 ldy cur_plyr_num ;get current player number
5c88: b9 c5 00 lda live_city_mask,y ;get live city bitmap
5c8b: 25 93 and play_mode_flag ;zero it out if no game in progress
5c8d: f0 0b beq :NoBonus ;no cities, don't show bonus
; Count the number of surviving cities.
5c8f: a2 00 ldx #$00 ;init count
5c91: 0a :CountLoop asl A ;shift high bit into carry
5c92: 90 01 bcc :NotLive ;not set, branch
5c94: e8 inx ;set, add one to counter
5c95: c9 00 :NotLive cmp #$00 ;have we examined all bits?
5c97: d0 f8 bne :CountLoop ;not yet, branch
5c99: 8a txa ;copy to A-reg to set status flags
5c9a: f0 0d :NoBonus beq :Return ;no live cities, bail
;
5c9c: 86 97 stx slot_index ;save city count for func $0e
5c9e: a9 00 lda #$00
5ca0: 85 8d sta wave_icbm_count ;init bonus city icon position index
5ca2: a9 0e lda #FN_CITY_BONUS_PTS
5ca4: 85 91 sta func_index ;switch to the bonus city func
5ca6: 20 96 5f jsr ZeroBcdAcc ;prep BCD accumulator for bonus tally
5ca9: 60 :Return rts
;
; Func $0e: awards bonus points for surviving cities.
;
; Each call shows one more city. We delay for a few frames between each.
;
; The code here (mis-)uses a couple of gameplay globals. The explosion sequence
; index ($b4) is used as a delay timer, the ICBM counter ($8d) holds the index
; of the next city to draw, and the slot index ($97) has the number of live
; cities. The latter two were initialized in func $0c, above. The explosion
; sequence index is a small value (0-4) so not necessary to init.
;
]city_bits .var $98 {addr/1}
5caa: c6 b4 F_CityBonusPts dec exp_seq_index ;use this as delay timer
5cac: 10 5d bpl :Return ;not done yet, return
;
5cae: a4 b9 ldy cur_plyr_num ;get current player number
5cb0: b9 c5 00 lda live_city_mask,y ;get surviving cities
5cb3: 85 98 sta ]city_bits ;save in ZP
5cb5: a6 97 ldx slot_index ;get number of cities remaining
5cb7: a0 06 ldy #6 ;test six bits, starting with high bit
5cb9: 88 :FindLoop dey
5cba: a5 98 lda ]city_bits ;get bits
5cbc: 39 f7 60 and single_bits,y ;mask off all but bit (7-N)
5cbf: f0 01 beq :SkipDec ;not set, branch
5cc1: ca dex ;set, decrement counter
5cc2: e0 00 :SkipDec cpx #$00 ;has the counter reached zero?
5cc4: d0 f3 bne :FindLoop ;not yet, keep shifting
; Y-reg now holds the index of the live city.
5cc6: 20 53 67 jsr EraseCityNoFlip ;remove city from bottom of screen
5cc9: a2 03 ldx #$03 ;iterate 4x, because cities are worth
5ccb: 20 a2 5f jsr AddBaseScoreX ; (25 * mult) * 4 points
5cce: a2 40 ldx #64 ;put on left quarter of screen
5cd0: a0 60 ldy #96 ;set vertical position
5cd2: a9 40 lda #%01000000 ;color = 2
5cd4: 20 f6 6a jsr PrintBcdNumber ;print current tally
; Draw one city icon in bonus tabulation area.
5cd7: a5 8d lda wave_icbm_count ;get counter
5cd9: e6 8d inc wave_icbm_count ;increment for next iteration
5cdb: 0a asl A ;multiply by 16
5cdc: 0a asl A ; because city icons are 16x8
5cdd: 0a asl A
5cde: 0a asl A
5cdf: 18 clc
5ce0: 65 8d adc wave_icbm_count ;add one to make it x17
5ce2: 18 clc ; to leave a little space between city icons
5ce3: 69 80 adc #128 ;relative to center of screen
5ce5: aa tax ;use as horizontal position
5ce6: a0 60 ldy #96 ;set vertical position
5ce8: a9 e4 lda #%11100100 ;color 7 and 2
5cea: 20 95 67 jsr DrawCity ;draw the city
;
5ced: a9 08 lda #SFX_BONUS_PTS
5cef: 20 ec 79 jsr StartSfx ;make the bonus-point noise
5cf2: a9 0a lda #10 ;wait for 10 frames between cities
5cf4: 85 b4 sta exp_seq_index
5cf6: c6 97 dec slot_index ;more cities to do?
5cf8: d0 11 bne :Return ;yes, bail
; All city bonuses have been awarded; move on.
5cfa: a9 10 lda #FN_WAVE_DONE_UPD ;do the end-of-wave housekeeping func...
5cfc: 85 92 sta next_func_index
5cfe: a9 22 lda #FN_PAUSE ;...after we delay for a bit
5d00: 85 91 sta func_index
5d02: a9 0f lda #15 ;pause for 15*4 frames (about 1 sec)
5d04: 85 af sta frame4_delay_ctr
5d06: a4 b9 ldy cur_plyr_num ;get current player number
5d08: 20 13 60 jsr AddBcdToScore ;add the bonus points to the score
5d0b: 60 :Return rts
;
; Func $10: handles housekeeping at the end of a wave.
;
; If this is a two-player game, switch to the other player, unless one of them
; is dead. If player 1 is playing, increment the wave number.
;
; If both players are dead, tee up a high score check.
;
F_WaveDoneUpdate
5d0c: a9 02 lda #FN_PREP_WAVE ;do wave prep func next, after optional delay
5d0e: 85 92 sta next_func_index
5d10: a9 00 lda #$00 ;default to no delay
5d12: 85 af sta frame4_delay_ctr
5d14: a9 22 lda #FN_PAUSE ;do the pre-game delay func next
5d16: 85 91 sta func_index
5d18: 20 ed 5d jsr RebuildCities ;merge in bonus cities if needed
5d1b: a5 c0 lda num_cities ;get player 1 city count
5d1d: 05 c1 ora num_cities+1 ;combine with player 2 city count
5d1f: 25 93 and play_mode_flag ;zero it out if game not in progress
5d21: d0 07 bne :StillAlive
5d23: a9 18 lda #FN_HS_CHECK ;whoops, all dead; switch to high score check func
5d25: 85 91 sta func_index
5d27: b8 clv
5d28: 50 42 bvc :Return ;(always)
; At least one player is still alive. See if the current player died.
5d2a: a4 ae :StillAlive ldy num_players ;get number of players (0/1)
5d2c: f0 1d beq :NoNewDeath ;single player, must be alive; branch
5d2e: a4 b9 ldy cur_plyr_num ;get current player (0/1)
5d30: b9 c0 00 lda num_cities,y ;get number of cities
5d33: d0 16 bne :NoNewDeath ;still have a city, branch
; Say "game over" to current player, then move on to other player.
5d35: a9 07 lda #$07 ;erase lines with these messages
5d37: 20 49 6a jsr EraseMessageLine ;"BONUS POINTS"
5d3a: a9 09 lda #$09
5d3c: 20 49 6a jsr EraseMessageLine ;"GREAT SCORE"
5d3f: a9 0e lda #$0e ;break the bad news
5d41: 20 55 6a jsr PrintMessagePr ;"GAME OVER"
5d44: 20 48 5f jsr PrintPlayerN ;"PLAYER N"
5d47: a9 0f lda #15
5d49: 85 af sta frame4_delay_ctr ;configure 15*4 frame (~1 sec) pause
;
5d4b: a5 b9 :NoNewDeath lda cur_plyr_num ;get current player number
5d4d: 85 df sta next_player ;copy here
5d4f: a4 ae :Loop ldy num_players ;get number of players - 1 (0/1)
5d51: f0 10 beq :Single2
5d53: c4 df cpy next_player ;was player 2 playing?
5d55: d0 07 bne :Player1up ;no, branch
5d57: c6 df dec next_player ;yes, switch to player 1
5d59: e6 a7 inc wave_num ;advance the wave
5d5b: b8 clv
5d5c: 50 02 bvc :Br_CheckCity ;branch
5d5e: e6 df :Player1up inc next_player ;switch to player 2 (WITHOUT advancing wave)
5d60: b8 :Br_CheckCity clv
5d61: 50 02 bvc :CheckAlive ;(always)
5d63: e6 a7 :Single2 inc wave_num ;advance to next wave
;
5d65: a4 df :CheckAlive ldy next_player ;for 2-player game, make sure the player we
5d67: b9 c0 00 lda num_cities,y ; switched to is alive
5d6a: f0 e3 beq :Loop ;if not, loop to switch back
5d6c: 60 :Return rts
;
; Func $06: game over.
;
; Clean up some things and prep for the end-game animation.
;
5d6d: a2 1c F_GameOver ldx #FN_SHOW_HS ;default to showing high scores next
5d6f: a5 93 lda play_mode_flag ;is a game in progress?
5d71: f0 1e beq :NoGame ;no, branch
5d73: a9 06 lda #%00000110 ;red
5d75: 85 e4 sta color_palette ;set all palette entries except color 4
5d77: 85 e5 sta color_palette+1
5d79: 85 e6 sta color_palette+2
5d7b: 85 e7 sta color_palette+3
5d7d: 85 e9 sta color_palette+5
5d7f: 85 ea sta color_palette+6
5d81: 85 eb sta color_palette+7
5d83: a9 80 lda #%10000000 ;color = 4 (cycles)
5d85: 85 ce sta score_color
5d87: 20 e1 5f jsr PrintScores ;print player scores in flashing text
5d8a: a9 20 lda #SFX_GAME_OVER
5d8c: 20 ec 79 jsr StartSfx ;play long explosion sound
5d8f: a2 12 ldx #FN_THE_END ;show "the end" screen
5d91: 86 91 :NoGame stx func_index
;
5d93: a0 00 ldy #$00
5d95: 8c c3 01 sty expl_radius_idx_arr+1 ;init "before" expl size for "the end"
5d98: 84 ae sty num_players ;reset number of players
5d9a: 84 93 sty play_mode_flag ;clear game-in-progress flag
5d9c: a0 01 ldy #$01
5d9e: 8c c2 01 sty expl_radius_idx_arr ;init "next" expl size for "the end"
5da1: 60 rts
;
; Func $12: show end-game animation.
;
• Clear variables
]after_radius .var $9a {addr/1}
]center_x .var $9b {addr/1}
]center_y .var $9c {addr/1}
]before_radius .var $b5 {addr/1}
5da2: ad c2 01 F_ShowEndTimes lda expl_radius_idx_arr ;get current explosion radius from slot #0
5da5: 85 b5 sta ]before_radius ;save for use as arg
5da7: f0 3c beq :Next1c ;if we're at zero, animation complete; branch
5da9: c9 6d cmp #109 ;have we reached max size?
5dab: 90 05 bcc :CheckExpand ;not yet, branch
5dad: a9 6c lda #108 ;start contracting
5daf: b8 clv
5db0: 50 1b bvc :Draw ;(always)
5db2: cd c3 01 :CheckExpand cmp expl_radius_idx_arr+1 ;are we expanding or contracting?
5db5: b0 06 bcs :Expand ;no radius is bigger, expand
5db7: 38 sec
5db8: e9 01 sbc #$01 ;shrink by one
5dba: b8 clv
5dbb: 50 10 bvc :Draw ;(always)
5dbd: 18 :Expand clc
5dbe: 69 01 adc #$01 ;expand by one
5dc0: 48 pha ;save new radius
5dc1: c9 62 cmp #98 ;are we at the point where we draw text?
5dc3: d0 05 bne :NoText ;not yet, branch
;
5dc5: a9 08 lda #$08
5dc7: 20 55 6a jsr PrintMessagePr ;show "THE END"
;
5dca: c6 b5 :NoText dec ]before_radius ;make "before" one less than current
5dcc: 68 pla ;restore new radius
5dcd: 8d c2 01 :Draw sta expl_radius_idx_arr ;save new radius
5dd0: 85 9a sta ]after_radius ;use as the "after" value
5dd2: a4 b5 ldy ]before_radius ;copy "before" radius to explosion slot #1
5dd4: 8c c3 01 sty expl_radius_idx_arr+1
5dd7: a9 80 lda #128 ;draw at center of screen
5dd9: 85 9b sta ]center_x
5ddb: a9 73 lda #115
5ddd: 85 9c sta ]center_y
5ddf: 20 71 5e jsr DrawExplosion ;draw next ring of the octagon
5de2: b8 clv
5de3: 50 07 bvc :Return ;(always)
5de5: a9 1c :Next1c lda #FN_SHOW_HS ;show high scores next
5de7: 85 91 sta func_index
5de9: 20 0c 7b jsr InitPokey ;silence audio
5dec: 60 :Return rts
;
; Awards bonus cities, and fills craters if bonus cities are available.
;
• Clear variables
]city_map .var $98 {addr/1}
]counter .var $dc {addr/1}
5ded: a5 93 RebuildCities lda play_mode_flag ;is a game in progress?
5def: f0 45 beq :Return ;no, skip this
5df1: 20 40 60 jsr CheckBonusCity ;award bonus cities
5df4: a4 b9 ldy cur_plyr_num ;get player number
5df6: b6 c0 ldx num_cities,y ;get number of remaining cities
5df8: e0 06 cpx #$06 ;is it >= 6?
5dfa: 90 02 bcc :LE6 ;no, branch
;
5dfc: a2 06 ldx #$06 ;start the crater count at 6
5dfe: b9 c5 00 :LE6 lda live_city_mask,y ;get bit map of live cities
5e01: 85 98 sta ]city_map ;store in ZP
5e03: 0a :CountLoop asl A ;shift high bit into carry
5e04: 90 01 bcc :NotSet ;not set
5e06: ca dex ;set, reduce number of craters by one
5e07: a8 :NotSet tay ;set status flags for A-reg
5e08: d0 f9 bne :CountLoop ;more bits to go, loop
5e0a: 8a txa ;set status flags for X-reg
5e0b: f0 29 beq :Return ;no craters, bail
5e0d: 86 dc stx ]counter ;save crater count in ZP
;
5e0f: a4 b9 :Loop ldy cur_plyr_num ;get current player number
5e11: b9 c5 00 lda live_city_mask,y ;get mask with bits set for live cities
5e14: 49 fc eor #%11111100 ;invert, so bits are set for craters
5e16: a0 00 ldy #$00
5e18: 20 1d 58 jsr PickRndBit ;pick a crater at random
5e1b: a6 b9 ldx cur_plyr_num ;get player number again
5e1d: b5 c5 lda live_city_mask,x ;get live city bit mask
5e1f: 19 f7 60 ora single_bits,y ;mark the selected crater as alive again
5e22: 95 c5 sta live_city_mask,x ;save it
5e24: c6 dc dec ]counter ;done creating new cities?
5e26: d0 e7 bne :Loop ;no, loop
;
5e28: a9 40 lda #SFX_BONUS_CITY
5e2a: 20 ec 79 jsr StartSfx ;play series of short random tones
5e2d: a9 05 lda #$05
5e2f: 20 55 6a jsr PrintMessagePr ;"BONUS CITY"
5e32: a9 2d lda #45 ;pause for 45*4 frames (~3 sec)
5e34: 85 af sta frame4_delay_ctr
5e36: 60 :Return rts
;
; Erases all incoming missiles and smartbombs from the screen.
;
5e37: a5 8c EraseIncoming lda num_live_icbms ;get number of active ICBMs
5e39: 05 da ora num_live_bombs ;combine with number of active bombs
5e3b: f0 2c beq :NoLive
5e3d: a2 07 ldx #$07 ;walk through 8 entries
5e3f: 86 dc stx ]counter
5e41: a6 dc :Loop ldx ]counter
5e43: bd 65 01 lda icbm_cur_y,x ;is this entry active?
5e46: f0 1d beq :Next ;no, branch
5e48: 8a txa
5e49: 18 clc
5e4a: 69 08 adc #$08 ;add 8 to convert ICBM index to missile index
5e4c: 85 97 sta slot_index ;set slot index
5e4e: a5 d9 lda smartbomb_mask ;get the smartbomb mask
5e50: 3d f7 60 and single_bits,x ;is this entry a bomb?
5e53: d0 06 bne :Bomb ;yes, branch
5e55: 20 86 66 jsr EraseTrail ;no, erase missile trail
5e58: b8 clv
5e59: 50 0a bvc :Next ;(always)
5e5b: 20 ac 77 :Bomb jsr EraseSmartBomb ;erase smartbomb image
5e5e: a9 00 lda #$00
5e60: 85 da sta num_live_bombs ;set number of live bombs to zero
5e62: 20 e9 7a jsr DisableBombSound ;be quiet
;
5e65: c6 dc :Next dec ]counter ;done with all incoming ordnance?
5e67: 10 d8 bpl :Loop ;no, loop
;
5e69: 20 0c 7b :NoLive jsr InitPokey
5e6c: a9 0f lda #$0f ;clear the bottom line of the screen
5e6e: 4c 49 6a jmp EraseMessageLine ;"OUT" (left); erases full line
;
; Draws an explosion. Explosions look vaguely circular but are actually
; octagonal. They're drawn as a series of outline shapes, which over the course
; of multiple frames form a solid shape.
;
; This routine takes start/end radii and a center point. If the start radius is
; less than the end, the octagon is expanding, and will be drawn in the
; foreground color (4/5). If the start radius is greater, then octagon is
; collapsing, and will be drawn in the background color.
;
; On entry:
; $9a: end radius (exclusive)
; $9b: X coord
; $9c: Y coord
; $b5: start radius
;
; On exit:
; (X-reg preserved)
;
• Clear variables
]end_radius .var $9a {addr/1}
]xc .var $9b {addr/1}
]yc .var $9c {addr/1}
]saved_x .var $ac {addr/1}
]start_radius .var $b5 {addr/1}
]color .var $b6 {addr/1}
5e71: 86 ac DrawExplosion stx ]saved_x ;preserve X-reg
5e73: a5 9a lda ]end_radius ;get the end radius
5e75: c5 b5 cmp ]start_radius ;compare to start radius
5e77: f0 23 beq :Done ;if start == end, nothing to do
5e79: b0 12 bcs :Expand ;end > start, we're in the expansion phase
; Shrinking.
5e7b: a9 00 lda #$00 ;color = 0
5e7d: 85 b6 sta ]color
5e7f: 20 9f 5e :SLoop jsr DrawOctagon ;draw octagon outline
5e82: a5 b5 lda ]start_radius
5e84: c6 b5 dec ]start_radius ;decrement radius
5e86: c5 9a cmp ]end_radius ;reached the end?
5e88: d0 f5 bne :SLoop ;not yet, loop
5e8a: b8 clv
5e8b: 50 0f bvc :Done ;(always)
; Expanding.
5e8d: a9 80 :Expand lda #%10000000 ;color = 4 (cycles)
5e8f: 85 b6 sta ]color
5e91: 20 9f 5e :ELoop jsr DrawOctagon ;draw octagon outline
5e94: a5 b5 lda ]start_radius
5e96: c5 9a cmp ]end_radius ;reached the end?
5e98: e6 b5 inc ]start_radius ;increment radius
5e9a: 90 f5 bcc :ELoop ;not at end yet, loop
;
5e9c: a6 ac :Done ldx ]saved_x ;restore X-reg
5e9e: 60 rts
;
; Draws octagon for explosion effect.
;
; Essentially we draw a shallow line (slope=3/8) in the first octant, and
; reflect it to the 7 others. A perfect octagon would use slope=1/2, which you
; can compare by NOPing $5ebc-5ec0.
;
; The clearest way to see the shape is to watch the "THE END" animation. (FWIW,
; I think the perfect octagon looks better for small explosions, because it
; reduces the spikes at the corners, but this shape feels better for the big
; animation at the end.)
;
; Example with radius 24:
; start_radius is 24
; first iteration 24/0: we double-draw points at (-24,0) (24,0),
; and (0,-24) (0,24)
; next iteration 24/1: we draw points at (-24,-1) (-24,1) (24,-1) (24,1)
; and (-1,-24) (-1,24) (1,-24) (1,24)
; then 24/2
; then 23/3, 23/4, 23/5
; then 22/6, ...
; eventually the deltas meet at 18/18
;
; On entry:
; $9b/9c: center X/Y
; $b5: radius
; $b6: color (%PPx00000)
;
]deltaX .var $9d {addr/1}
]deltaY .var $9e {addr/1}
5e9f: a6 b5 DrawOctagon ldx ]start_radius
5ea1: 86 9d stx ]deltaX ;start with xdelta = radius
5ea3: a2 00 ldx #$00
5ea5: 86 9e stx ]deltaY ;start with ydelta = 0
;
5ea7: 20 cd 5e :Loop jsr DrawFourPix ;draw 4 reflected points
5eaa: a6 9d ldx ]deltaX ;transpose X/Y deltas
5eac: a5 9e lda ]deltaY
5eae: 86 9e stx ]deltaY
5eb0: 85 9d sta ]deltaX
5eb2: 20 cd 5e jsr DrawFourPix ;draw 4 reflected points
; Update delta values, un-transposing X/Y:
; ydelta = xdelta + 1
; xdelta = start_radius - (ydelta * 3) / 8
5eb5: e6 9d inc ]deltaX
5eb7: a5 9d lda ]deltaX
5eb9: 85 9e sta ]deltaY ;copy xdelta to ydelta
5ebb: 4a lsr A ;divide by 2
5ebc: 18 clc
5ebd: 65 9e adc ]deltaY ;add to original to get 3/2
5ebf: 4a lsr A ;divide whole thing by 4 to get 3/8
5ec0: 4a lsr A
5ec1: 49 ff eor #$ff ;negate
5ec3: 38 sec ;2's complement
5ec4: 65 b5 adc ]start_radius ;add to start radius
5ec6: 85 9d sta ]deltaX ;save as xdelta
5ec8: c5 9e cmp ]deltaY ;have we reached ydelta?
5eca: b0 db bcs :Loop ;no, loop
5ecc: 60 rts
;
; Draws four pixels at a specified distance from the center. Given the center
; and delta X/Y in the first quadrant, this repeats the draw mirrored in the
; other three quadrants.
;
; We carefully avoid altering the low bit of the screen colors, so that we don't
; chew up the city backdrop. The explosion itself is drawn in color 4, which
; cycles at the same rate as color 5.
;
; On entry:
; $9b/9c: center X/Y
; $9d/9e: delta X/Y
; $b6: color (%PPx00000) - only two high bits matter
;
]xplus .var $98 {addr/1}
]ptr .var $b0 {addr/2}
5ecd: a2 00 DrawFourPix ldx #$00
5ecf: a5 9b lda ]xc ;compute xc + deltaX
5ed1: 18 clc
5ed2: 65 9d adc ]deltaX
5ed4: 85 b0 sta ]ptr ;use as low byte of pointer
5ed6: 85 98 sta ]xplus ;save for later
5ed8: a5 9c lda ]yc ;compute yc + deltaY
5eda: 18 clc
5edb: 65 9e adc ]deltaY
5edd: 49 ff eor #$ff ;invert to form high byte of pointer
5edf: 85 b1 sta ]ptr+1
5ee1: a1 b0 lda (]ptr,x) ;get current pixel value
5ee3: 29 20 and #%00100000 ;strip away everything but low bit of color
5ee5: 05 b6 ora ]color ;merge our color in
5ee7: 81 b0 sta (]ptr,x) ;save to screen
;
5ee9: a5 9b lda ]xc ;repeat for (x - deltaX, y + deltaY)
5eeb: 38 sec
5eec: e5 9d sbc ]deltaX
5eee: 85 b0 sta ]ptr
5ef0: a1 b0 lda (]ptr,x)
5ef2: 29 20 and #%00100000
5ef4: 05 b6 ora ]color
5ef6: 81 b0 sta (]ptr,x)
;
5ef8: a5 9c lda ]yc ;repeat for (x - deltaX, y - deltaY)
5efa: 38 sec
5efb: e5 9e sbc ]deltaY
5efd: 49 ff eor #$ff
5eff: 85 b1 sta ]ptr+1
5f01: a1 b0 lda (]ptr,x)
5f03: 29 20 and #%00100000
5f05: 05 b6 ora ]color
5f07: 81 b0 sta (]ptr,x)
;
5f09: a5 98 lda ]xplus ;repeat for (x + deltaX, y - deltaY)
5f0b: 85 b0 sta ]ptr
5f0d: a1 b0 lda (]ptr,x)
5f0f: 29 20 and #%00100000
5f11: 05 b6 ora ]color
5f13: 81 b0 sta (]ptr,x)
5f15: 60 rts
;
; Show some messages before a wave starts.
;
; If this is a demo rather than a game in progress, then we just came from the
; title screen, so we should reset the text scrolling area.
;
• Clear variables
5f16: a5 93 PreWaveMsgs lda play_mode_flag ;is a game in progress?
5f18: f0 1d beq :NoGame ;no, branch
5f1a: 20 48 5f jsr PrintPlayerN ;print "player N"
5f1d: a9 06 lda #$06
5f1f: 20 55 6a jsr PrintMessagePr ;"X POINTS"
5f22: a5 dd lda score_mult ;get the score multiplier
5f24: a2 40 ldx #%01000000 ;fg color = 2
5f26: 86 0b stx draw_fg_color
5f28: a2 58 ldx #88
5f2a: a0 70 ldy #112
5f2c: 20 9d 6a jsr DrawSmallNum ;print multiplier
5f2f: a9 1e lda #$1e
5f31: 20 55 6a jsr PrintMessagePr ;"ATARI (C)(P) 1980"
5f34: b8 clv
5f35: 50 03 bvc :ShowDefMaybe
5f37: 20 33 6c :NoGame jsr TxscResetStream ;reset scrolling text
;
5f3a: a5 a7 :ShowDefMaybe lda wave_num ;get current wave number
5f3c: c9 04 cmp #$04 ;is it >= 4?
5f3e: b0 05 bcs :PrintScores ;yes, skip the "defend cities" message
5f40: a9 15 lda #$15
5f42: 20 51 6a jsr PrintMessage ;"DEFEND CITIES"
5f45: 4c 23 75 :PrintScores jmp PrintTopScores ;print player scores and return
;
; Draws "Player N" on the screen, in color #2.
;
5f48: a9 00 PrintPlayerN lda #$00
5f4a: 20 55 6a jsr PrintMessagePr ;"PLAYER"
5f4d: a5 b9 lda cur_plyr_num ;get current player number (0/1)
5f4f: 18 clc
5f50: 69 01 adc #$01 ;make it 1/2
5f52: a2 40 ldx #%01000000 ;fg color = 2
5f54: 86 0b stx draw_fg_color
5f56: a2 9c ldx #156
5f58: a0 90 ldy #144 ;same line as message $00
5f5a: 4c 9d 6a jmp DrawSmallNum
;
; Scrolls the text at the bottom of the screen one pixel left.
;
; This happens on alternate frames, yielding a ~30Hz update rate.
;
5f5d: a5 ca ScrollText lda game_frame_ctr ;get the frame counter
5f5f: 4a lsr A ;shift the low bit into the carry
5f60: b0 03 bcs :Return ;if set, bail
5f62: 20 66 5f jsr :DoScrollText ;if not, scroll the text line
5f65: 60 :Return rts
; This is done with the weirdly-mapped third pixel bit. The bottom of the
; screen is drawn in the city background color (palette #1), and the scrolling
; text in the general background color (palette #0), so we don't need to touch
; the 2bpp pixel area at all.
;
; Each byte holds 8 pixels, with the leftmost pixel in the low bit. We don't
; want to move it a full byte at a time, so we have to shift bits across 8 lines
; of 32 bytes. This is slightly complicated because each line spans 64 bytes,
; using every-other byte. Also, the leftmost pixel is in the low byte, so a
; "right shift" actually moves pixels to the left.
;
; Lines 223-226 start at $0401/0441/0481/04c1, and lines 227-230 start at
; $0501/0541/0581/05c1. We can access 4 lines with simple indexed addressing.
;
; We could to this from right to left one line at a time, smoothly propagating
; the bit down the line. Instead, this works one column at a time, from left to
; right. This is less efficient, but is also less susceptible to tearing.
;
5f66: 20 8c 6b :DoScrollText jsr UpdateScrollText ;output new character, if it's time
5f69: a2 00 ldx #$00 ;start at left end
5f6b: 8a :Loop txa
5f6c: 29 3f and #$3f ;mod 64 (length of the line in bytes)
5f6e: c9 3e cmp #$3e ;are we at the right edge of screen?
5f70: d0 08 bne :NotAtEnd ;no, branch
5f72: 38 sec ;rotate a 1 bit in to use city background
5f73: 7e 01 04 ror VIDEO_RAM_3+$201,x ;update the last byte
5f76: 38 sec
5f77: b8 clv
5f78: 50 0b bvc :Tail ;go do it for the last 4 lines
5f7a: bd 03 04 :NotAtEnd lda VIDEO_RAM_3+$203,x ;shift bit out; this covers first 4 lines
5f7d: 4a lsr A
5f7e: 7e 01 04 ror VIDEO_RAM_3+$201,x ;shift bit in
5f81: bd 03 05 lda VIDEO_RAM_3+$303,x ;shift bit out; this covers last 4 lines
5f84: 4a lsr A
5f85: 7e 01 05 :Tail ror VIDEO_RAM_3+$301,x ;shift bit in
5f88: 8a txa ;copy index to A-reg
5f89: 18 clc
5f8a: 69 40 adc #64 ;move to next line, 64 bytes down
5f8c: aa tax ;put it back in X-reg
5f8d: 90 dc bcc :Loop ;if we didn't overflow, loop
5f8f: e8 inx ;advance to next byte
5f90: e8 inx
5f91: e0 40 cpx #64 ;have we reached the end of the line?
5f93: 90 d6 bcc :Loop ;not yet, branch
5f95: 60 rts
;
; Initializes the 3-byte BCD accumulator to zero.
;
; On exit:
; $d1-d3: zero
;
5f96: a9 00 ZeroBcdAcc lda #$00
5f98: 85 d1 sta bcd3_acc
5f9a: 85 d2 sta bcd3_acc+1
5f9c: 85 d3 sta bcd3_acc+2
5f9e: 60 rts
;
; Sets the 3-byte BCD accumulator to the 2-byte argument multiplied by X.
;
; On entry:
; $ba-bb: base score ($25 * score mult)
; X-reg: number of iterations + 1 (0-127)
;
; On exit:
; $d1-d2: new value
; $d3: zero
;
5f9f: 20 96 5f SetBaseScoreX jsr ZeroBcdAcc ;start with zero, fall through
;
; Adds a 2-byte BCD score value to the 3-byte BCD accumulator X times.
;
; The base score (25 * mult) is added when you shoot down a missile, so X=0.
; Cities are worth (4 * 25 * mult), so X=3.
;
; On entry:
; $ba-bb: base score ($25 * score mult)
; X-reg: number of iterations + 1 (0-127)
;
; On exit:
; $d1-d3: new value + (X * old value)
;
5fa2: f8 AddBaseScoreX sed ;enable decimal mode
5fa3: a5 ba :Loop lda base_score_bcd ;get the base score
5fa5: 18 clc
5fa6: 65 d1 adc bcd3_acc ;add to accumulator
5fa8: 85 d1 sta bcd3_acc ;store it
5faa: a5 d2 lda bcd3_acc+1 ;repeat for middle byte
5fac: 65 bb adc base_score_bcd+1
5fae: 85 d2 sta bcd3_acc+1
5fb0: a5 d3 lda bcd3_acc+2 ;carry into high byte
5fb2: 69 00 adc #$00
5fb4: 85 d3 sta bcd3_acc+2
5fb6: ca dex ;done yet?
5fb7: 10 ea bpl :Loop ;no, branch
5fb9: d8 cld ;disable decimal mode
5fba: 60 rts
;
; Adds (5 * mult) to the BCD accumulator.
;
; On entry:
; $d1-d3: current value
; $dd: multiplier
;
; On exit:
; $d1-d3: += (5 * arg)
;
5fbb: a6 dd AddBcd5x ldx score_mult ;use score multiplier as counter
5fbd: f8 sed ;enable decimal mode
5fbe: a9 05 :Loop lda #5 ;add 5 points
5fc0: 18 clc
5fc1: 65 d1 adc bcd3_acc ;add to low byte
5fc3: 85 d1 sta bcd3_acc
5fc5: a9 00 lda #$00
5fc7: 65 d2 adc bcd3_acc+1 ;carry into middle byte
5fc9: 85 d2 sta bcd3_acc+1
5fcb: a5 d3 lda bcd3_acc+2
5fcd: 69 00 adc #$00 ;carry into high byte
5fcf: 85 d3 sta bcd3_acc+2
5fd1: ca dex ;done yet?
5fd2: d0 ea bne :Loop ;no, loop
5fd4: d8 cld ;disable decimal mode
5fd5: 60 rts
;
; Sets the current score to zero, for both players.
;
5fd6: a9 00 SetZeroScores lda #$00 ;set to zero
5fd8: a2 05 ldx #5 ;3 bytes * 2 players = 6 bytes
5fda: 9d d6 01 :Loop sta cur_score_lo,x
5fdd: ca dex ;done yet?
5fde: 10 fa bpl :Loop ;no, loop
5fe0: 60 rts
;
; Prints the scores for one or both players.
;
5fe1: a5 93 PrintScores lda play_mode_flag ;is a game in progress?
5fe3: d0 0c bne :Playing ;yes, branch
5fe5: ad d7 01 lda cur_score_lo+1 ;check if player 2's score is zero
5fe8: 0d d9 01 ora cur_score_md+1
5feb: 0d db 01 ora cur_score_hi+1
5fee: b8 clv
5fef: 50 02 bvc :ZeroOrOne ;(always)
5ff1: a5 ae :Playing lda num_players ;get number of players (0/1)
5ff3: f0 05 :ZeroOrOne beq :NoScore2 ;1 player *or* player 2's score is zero; branch
5ff5: a0 01 ldy #$01
5ff7: 20 fc 5f jsr PrintPlyrScore ;print player 2's score
5ffa: a0 00 :NoScore2 ldy #$00 ;fall through to print player 1's score
;
; Print the score of player 1 or 2, identified by Y-reg (0/1).
;
5ffc: 20 96 5f PrintPlyrScore jsr ZeroBcdAcc ;zero out the accumulator
5fff: 20 13 60 jsr AddBcdToScore ;copy score into BCD accumulator
6002: b9 11 60 lda :score_xc,y ;get horizontal position for player N's score
6005: aa tax ;hold in X-reg
6006: a9 00 lda #$00
6008: 85 d4 sta score_dirty_flag ;clear dirty flag
600a: a0 e2 ldy #226 ;position near top of screen (226-4+8=230)
600c: a5 ce lda score_color ;load color into A-reg
600e: 4c f6 6a jmp PrintBcdNumber ;print the score
;
; Horizontal position for scores.
6011: 1d :score_xc .dd1 29 ;player 1
6012: b0 .dd1 176 ;player 2
;
; Adds the contents of the BCD accumulator to the current score, and stores the
; result in both places.
;
; On entry:
; Y-reg: player number (0/1)
;
; On exit:
; $d4: $ff (dirty flag for score)
;
6013: a5 93 AddBcdToScore lda play_mode_flag ;is a game in progress?
6015: d0 03 bne :NoZero ;yes, branch
6017: 20 96 5f jsr ZeroBcdAcc ;set BCD accumulator to zero
;
601a: f8 :NoZero sed
601b: a5 d1 lda bcd3_acc ;get low byte of BCD accumulator
601d: 18 clc
601e: 79 d6 01 adc cur_score_lo,y ;add to low byte of BCD score for cur player
6021: 99 d6 01 sta cur_score_lo,y ;update player's score
6024: 85 d1 sta bcd3_acc ;save in acc
6026: b9 d8 01 lda cur_score_md,y ;repeat for middle byte
6029: 65 d2 adc bcd3_acc+1
602b: 99 d8 01 sta cur_score_md,y
602e: 85 d2 sta bcd3_acc+1
6030: b9 da 01 lda cur_score_hi,y ;repeat for high byte
6033: 65 d3 adc bcd3_acc+2
6035: 99 da 01 sta cur_score_hi,y
6038: 85 d3 sta bcd3_acc+2
;
603a: a9 ff lda #$ff ;rev2: CLD moved for code sharing
603c: 85 d4 sta score_dirty_flag ;mark scores as dirty
603e: d8 :ReturnD cld
603f: 60 :Return rts
;
; Awards bonus cities based on score.
;
; Player scores are stored as a 3-byte BCD value: ABC,DEF. Bonus cities are
; awarded every XY,000 points. We extract BC, subtract the value from the
; previous time it was awarded, and then compare it to XY.
;
; It's possible to earn multiple bonus cities on one wave.
;
; NOTE: this is the rev3 version, which does not have the famous "810" bug found
; in rev2.
;
; On entry:
; X-reg=player number (0/1)
;
]thousands .var $98 {addr/1}
]bonus_city_score .var $99 {addr/1}
6040: a5 f4 CheckBonusCity lda r8_irq_inv ;get R8 switches
6042: 29 70 and #%01110000 ;mask to get bonus city bits
6044: 4a lsr A ;right-shift to get 0-7
6045: 4a lsr A
6046: 4a lsr A
6047: 4a lsr A
6048: a8 tay
6049: b9 7b 60 lda bonus_city_sc_tbl,y ;get BCD score value
604c: 85 99 sta ]bonus_city_score ;save in ZP
604e: f0 ef beq :Return ;if it's zero, bonus cities disabled; bail
;
6050: a6 b9 ldx cur_plyr_num ;get current player number (0 or 1)
6052: bd d8 01 lda cur_score_md,x ;get middle BCD digits
6055: 4a lsr A ;right-shift to reduce to 1,000s digit
6056: 4a lsr A
6057: 4a lsr A
6058: 4a lsr A
6059: 85 98 sta ]thousands
605b: bd da 01 lda cur_score_hi,x ;get high BCD digits
605e: 0a asl A ;left-shift to move the 10,000s digit
605f: 0a asl A
6060: 0a asl A
6061: 0a asl A
6062: 05 98 ora ]thousands ;combine; given score xAB,xxx we now have AB
6064: f8 sed ;switch to decimal mode for mod 100 arithmetic
6065: 38 sec ;subtract the score from the last time we awarded;
6066: f5 c3 sbc bonus_award_sc,x ; this may roll under, e.g. 4 - 90 = 14
; With (current - previous) in A-reg, subtract bonus score repeatedly. This
; would fail if you could score >= 100K in one wave.
6068: 38 :BonusLoop sec
6069: e5 99 sbc ]bonus_city_score ;subtract the configured bonus score
606b: 90 d1 bcc :ReturnD ;rolled under, score isn't high enough yet
606d: f6 c0 inc num_cities,x ;give the player a city
606f: 48 pha ;push running total
;
6070: b5 c3 lda bonus_award_sc,x ;get last-awarded score
6072: 18 clc
6073: 65 99 adc ]bonus_city_score ;add bonus city points
6075: 95 c3 sta bonus_award_sc,x ;save it
6077: 68 pla ;pop running total
6078: 4c 68 60 jmp :BonusLoop ;loop
;
; Bonus cities are awarded every N points, based on the R8 switch setting. The
; values are stored here in BCD format. Note switch ON=0 and OFF=1.
;
; 000 - every 10,000 points
; 001 - every 12,000 points
; 010 - every 14,000 points
; 011 - every 15,000 points
; 100 - every 18,000 points
; 101 - every 20,000 points
; 110 - every 8,000 points
; 111 - no bonus city (not in table -- uses $00 from next table)
;
bonus_city_sc_tbl
607b: 10 12 14 15+ .bulk $10,$12,$14,$15,$18,$20,$08
;
; Bonus city score values, for display. These are BCD digits to which "00" must
; be appended.
6082: 00 01 bonus_city_bcd .dd2 $0100
6084: 20 01 .dd2 $0120
6086: 40 01 .dd2 $0140
6088: 50 01 .dd2 $0150
608a: 80 01 .dd2 $0180
608c: 00 02 .dd2 $0200
608e: 80 00 .dd2 $0080
;
; Number of missiles for wave. 19 entries.
;
; The code indexes from (address - 1) because waves start at 1 rather than zero.
;
; rev2 used different values for the last three entries ($60a0): $10 $12 $14
;
6090: 0c 0f 12 0c+ icbm_count_tbl .bulk $0c,$0f,$12,$0c,$10,$0e,$11,$0a,$0d,$10,$13,$0c,$0e,$10,$12,$0e
+ $11,$13,$16
;
; ICBM update frame delay. At wave 15 the delay is set to zero, meaning the
; missiles advance every frame.
;
; Each entry is an 8.8 value. The counter itself is reduced by 1 each time. By
; using a fractional value here, we can fine-tune the speed.
;
icbm_spd_frac_tbl
60a3: d0 e0 c0 08+ .bulk $d0,$e0,$c0,$08,$a0,$60,$40,$20,$10,$0a,$06,$04,$02,$01,$00
icbm_spd_int_tbl
60b2: 04 02 01 01+ .bulk $04,$02,$01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
;
; Number of smartbombs per wave. 19 entries.
;
60c1: 00 00 00 00+ sbomb_count_tbl .bulk $00,$00,$00,$00,$00,$01,$01,$02,$03,$04,$04,$05,$05,$06,$06,$07
+ $07,$07,$07
;
; Flier properties, for waves 1-8. Fliers don't appear until wave 2, so only 7
; entries are needed.
;
; The first table is the number of frames before the flier can fire. The second
; table is the "recharge" time before a new flier can be created.
;
flier_msl_delay_tbl
60d4: 80 60 40 30+ .bulk $80,$60,$40,$30,$20,$20,$10
flier_charge_int_tbl
60db: f0 a0 80 80+ .bulk $f0,$a0,$80,$80,$60,$40,$20
;
; Horizontal positions for the 6 cities.
;
; NOTE: cities are not stored left to right here. Visually the indices are:
; ^ 3 4 0 ^ 2 1 5 ^
; This is noticeable when the surviving cities are tabulated between rounds.
;
; Some of the code for attacking ground targets references this with an index of
; 0-8 instead of 0-5. This table must be followed by the silo positions.
;
60e2: 5f b4 94 2c+ city_x_tbl .bulk $5f,$b4,$94,$2c,$47,$d0
;
; Horizontal positions for the 3 ABM launcher sites. This table must follow the
; city table.
;
60e8: 14 7b f0 abm_silo_x_tbl .bulk $14,$7b,$f0
;
; Vertical positions for the 6 cities. They're not quite on the same line.
;
60eb: 10 15 12 12+ city_y_tbl .bulk $10,$15,$12,$12,$11,$11
;
; Vertical positions for the 3 ABM launcher silos. This table must follow the
; city table.
;
60f1: 16 16 16 abm_silo_y_tbl .bulk $16,$16,$16
;
; Vertical position of the top of the 3 ABM launcher silos. This is the Y
; coordinate from which the missile trails will start.
;
abm_silo_top_tbl
60f4: 18 18 18 .bulk $18,$18,$18
;
; Single-bit masks.
;
60f7: 80 40 20 10+ single_bits .bulk $80,$40,$20,$10,$08,$04,$02,$01
;
; Checks to see if we want to create a flier. Creates one randomly if we do.
;
• Clear variables
CheckCreateFlier
60ff: a5 8d lda wave_icbm_count ;any unfired ICBMs?
6101: f0 42 beq :Return ;no, bail
6103: ad 6d 01 lda flier_cur_y ;do we already have an active flier?
6106: d0 3d bne :Return ;yes, bail
6108: a4 a7 ldy wave_num ;get wave number in Y-reg
610a: c0 02 cpy #$02 ;are we on wave 1?
610c: 90 37 bcc :Return ;yes, bail
610e: a5 e3 lda flier_charge_ctr ;get the counter value
6110: c5 e2 cmp flier_charge_int ;has it exceeded the recharge interval?
6112: 90 31 bcc :Return ;not yet, bail
;
6114: a2 00 ldx #$00
6116: 86 c7 stx flier_move_ctr ;init movement counter
6118: 86 c9 stx flier_fire_ctr ;init firing counter
611a: ad 0a 40 lda POKEY_RANDOM ;get random number
611d: 10 01 bpl :MoveRight ;pick direction with high bit of value
611f: ca dex ;move left
6120: 86 c8 :MoveRight stx flier_move_dir ;set move dir ($ff=left, $00=right)
6122: 8e 38 01 stx plane_cur_x ;set horizontal position ($00=left side, $ff=right side)
6125: 29 01 and #%00000001 ;mask low bit of random value
6127: 85 d6 sta flier_type ;use it as the flier type (0=satellite, 1=plane)
;
6129: ad 0a 40 lda POKEY_RANDOM ;get random number (0-255)
612c: 4a lsr A ;divide by 8 (now 0-31)
612d: 4a lsr A
612e: 4a lsr A
612f: 69 64 adc #100 ;add 100 (now 100-131)
6131: c0 06 cpy #$06 ;is wave >= 6?
6133: b0 02 bcs :WaveGe6 ;yes, branch
6135: 69 20 adc #32 ;wave 2-5, move it 32 lines higher
6137: c0 04 :WaveGe6 cpy #$04 ;is wave >= 4?
6139: b0 02 bcs :WaveGe4 ;yes, branch
613b: 69 10 adc #16 ;wave 2-3, move it 16 lines higher
613d: 8d 6d 01 :WaveGe4 sta flier_cur_y ;set flier vertical position
;
6140: 20 dc 7a jsr EnableFlierSound
6143: c6 de dec atk_create_ctr ;decrement attacks to make this frame
6145: 60 :Return rts
;
; Updates position of flier, if present.
;
]vert_posn .var $b1 {addr/1}
6146: e6 e3 UpdateFlier inc flier_charge_ctr ;increment recharge counter
6148: d0 02 bne :NotZero
614a: c6 e3 dec flier_charge_ctr ;hit zero, back it off to $ff
614c: ad 6d 01 :NotZero lda flier_cur_y ;get vertical position
614f: f0 29 beq :Return ;if zero, filer is inactive; bail
6151: 85 b1 sta ]vert_posn ;save Y coord in ZP
6153: c6 c7 dec flier_move_ctr ;time to move yet?
6155: 10 23 bpl :Return ;no, bail
;
6157: e6 c9 inc flier_fire_ctr ;advance flier fire timer
6159: a4 d6 ldy flier_type ;get unit type (0 or 1)
615b: b9 7b 61 lda :move_freq,y ;get movement frequency (1 or 2)
615e: 85 c7 sta flier_move_ctr ;reset movement counter
6160: ad 38 01 lda plane_cur_x ;get horizontal position in A-reg
6163: 20 7d 61 jsr DrawFlier ;move and redraw
6166: d0 0f bne :StillVis
;
6168: a9 00 lda #$00 ;clear plane / satellite state
616a: 8d 6d 01 sta flier_cur_y ;this marks it as inactive
616d: 85 c9 sta flier_fire_ctr ;reset firing interval timer
616f: 85 e3 sta flier_charge_ctr ;reset "recharge" timer
6171: 20 f8 7a jsr DisableFlierSound
6174: b8 clv
6175: 50 03 bvc :Return ;(always)
6177: 8d 38 01 :StillVis sta plane_cur_x ;save updated position
617a: 60 :Return rts
617b: 01 02 :move_freq .bulk $01,$02 ;satellites move faster than planes
;
; Draws a plane or a satellite, advancing its horizontal position.
;
; On entry:
; A-reg: horizontal position
; Y-reg: shape to draw (0=satellite, 1=plane)
;
; On exit:
; A-reg: horizontal position (updated)
; Z-flag zero if still on screen
;
]pixel_ctr .var $98 {addr/1}
]chunk_index .var $99 {addr/1}
]unused_ab .var $ab {addr/1}
]horz_posn .var $b0 {addr/1}
]vert_posn .var $b1 {addr/1}
]color_index .var $b6 {addr/1}
617d: 84 ab DrawFlier sty ]unused_ab ;(not useful)
617f: 84 99 sty ]chunk_index ;(not useful)
6181: 85 b0 sta ]horz_posn ;store horizontal position in ZP
6183: a5 c8 lda flier_move_dir ;get horizontal movement direction
6185: 30 05 bmi :MoveLeft ;moving left, branch
6187: e6 b0 inc ]horz_posn ;increment horizontal posn
6189: b8 clv
618a: 50 02 bvc :Moved ;(always)
618c: c6 b0 :MoveLeft dec ]horz_posn ;decrement horizontal posn
618e: b9 ed 61 :Moved lda :chunk_parm_off,y ;get chunk parameter offset (3 or 5)
6191: 85 99 sta ]chunk_index ;use as initial value
;
6193: a6 99 :ChunkLoop ldx ]chunk_index ;get chunk index
6195: bd f5 61 lda :data_lengths,x ;get length of this chunk
6198: 85 98 sta ]pixel_ctr ;store in ZP
619a: bd fb 61 lda :colors,x ;get color for this chunk
619d: 85 b6 sta ]color_index ;store in ZP
619f: bc ef 61 ldy :data_offsets,x ;get first (actually last) offset
;
61a2: a6 99 :PixelLoop ldx ]chunk_index ;load this into X-reg for later
61a4: b9 63 62 lda :vert_offset,y ;get vertical offset of pixel
61a7: 18 clc
61a8: 65 b1 adc ]vert_posn ;add vertical position of flier
61aa: 49 ff eor #$ff ;invert to form high byte of pointer
61ac: 85 07 sta gfx_ptr+1 ;save to ZP
;
61ae: b9 13 62 lda :horz_offset,y ;get horizontal offset of pixel
61b1: 45 c8 eor flier_move_dir ;invert (negate, mostly) if moving right
61b3: 10 0a bpl :PosOff
61b5: 18 clc ;negative offset
61b6: 65 b0 adc ]horz_posn ;add to horizontal position
61b8: c5 b0 cmp ]horz_posn ;compare to horizontal position
61ba: b0 1c bcs :SkipDraw ;if H - X is >= H, we wrapped; branch
61bc: b8 clv
61bd: 50 07 bvc :CheckEdge
61bf: 18 :PosOff clc ;positive offset
61c0: 65 b0 adc ]horz_posn ;add to horizontal position
61c2: c5 b0 cmp ]horz_posn ;compare to horizontal position
61c4: 90 12 bcc :SkipDraw ;if H + X is < H, we wrapped; branch
;
61c6: dd 01 62 :CheckEdge cmp :horz_min,x ;is horizontal position < min for shape?
61c9: 90 0d bcc :SkipDraw ;yes, branch
61cb: dd 07 62 cmp :horz_max,x ;is horizontal position > max for shape?
61ce: b0 08 bcs :SkipDraw ;yes, branch
;
61d0: 85 06 sta gfx_ptr ;save horizontal position as low byte of pointer
61d2: a2 00 ldx #$00
61d4: a5 b6 lda ]color_index ;get foreground color
61d6: 81 06 sta (gfx_ptr,x) ;set pixel
61d8: c8 :SkipDraw iny ;advance to next entry
61d9: c6 98 dec ]pixel_ctr ;have we drawn all pixels in this chunk?
61db: 10 c5 bpl :PixelLoop ;no, loop
;
61dd: a4 99 ldy ]chunk_index ;get chunk index
61df: c6 99 dec ]chunk_index ;decrement the ZP value
61e1: b9 0d 62 lda :stop_flag,y ;was this the last chunk?
61e4: 10 ad bpl :ChunkLoop ;no, branch
; All done. Return with position in A-reg and Z-flag set.
61e6: a5 b0 lda ]horz_posn ;have we reached left edge of screen?
61e8: f0 02 beq :Return ;yes, return with Z-flag set
61ea: c9 ff cmp #$ff ;have we reached right edge of screen?
61ec: 60 :Return rts
;
; Shapes are drawn in multiple colors. The leading edge is drawn in one of the
; foreground colors, while the trailing edge is erased in the background color.
; The shape is never fully drawn; it just "smears" across the screen. This
; allows it to be rendered with fewer screen writes. There's no need for an
; erase function, as the exploding fireball takes care of that.
;
; Each of the two shapes is drawn in multiple chunks. Each chunk has a color
; value and multiple pixels, expressed as offsets from the center position.
;
;
; The first table holds the offset of the last entry in the shape chunk tables.
; This value is used as an index that is decremented. Satellites use entries 0-
; 3, planes use entries 4-5.
61ed: 03 05 :chunk_parm_off .bulk $03,$05
;
; Initial offset into the vertical/horizontal coordinate tables for chunk N.
61ef: 00 17 2c 30 :data_offsets .bulk $00,$17,$2c,$30
61f3: 36 43 .bulk $36,$43
;
; Number of items, minus 1, in the vertical/horizontal data tables for chunk N.
61f5: 16 14 03 05 :data_lengths .bulk $16,$14,$03,$05
61f9: 0c 0c .bulk $0c,$0c
;
; Color to draw (%PPP00000) for chunk N.
61fb: 40 00 80 e0 :colors .bulk $40,$00,$80,$e0 ;colors 2, 0, 4, 6
61ff: 40 00 .bulk $40,$00 ;colors 2, 0
;
; Minimum and maximum coordinates. There is a buffer zone at the edges of the
; screen. Normally there's enough going on that the player won't notice that
; the shape isn't appearing right at the edge.
;
; These are specified per-chunk, but really they're per-shape.
6201: 08 08 08 08 :horz_min .bulk $08,$08,$08,$08
6205: 09 09 .bulk $09,$09
6207: f8 f8 f8 f8 :horz_max .bulk $f8,$f8,$f8,$f8
620b: f7 f7 .bulk $f7,$f7
;
; Indicates the last chunk of the shape.
620d: ff 00 00 00 :stop_flag .bulk $ff,$00,$00,$00
6211: ff 00 .bulk $ff,$00
;
; Horizontal and vertical offsets for each pixel.
6213: 03 03 04 04+ :horz_offset .bulk $03,$03,$04,$04,$04,$03,$03,$01,$01,$05,$04,$04,$05,$fb,$fc,$fc
+ $fb,$00,$00,$00,$fc,$fc,$fc,$05,$04,$03,$03,$04,$05,$fe,$fe,$f9
+ $fa,$fb,$fc,$fc,$fb,$fb,$fb,$fc,$fc,$fb,$fa,$f9,$06,$06,$fa,$fa
+ $02,$02,$02,$fe,$fe,$fe,$07,$05,$03,$ff,$fe,$fd,$00,$ff,$fe,$fd
+ $fc,$f9,$fa,$fb,$fc,$fc,$f8,$f8,$f8,$f7,$fa,$fb,$fb,$fa,$fa,$f9
6263: 03 02 01 00+ :vert_offset .bulk $03,$02,$01,$00,$ff,$fe,$fd,$04,$fc,$05,$04,$fc,$fb,$05,$04,$fc
+ $fb,$01,$00,$ff,$01,$00,$ff,$06,$05,$04,$fc,$fb,$fa,$04,$fc,$06
+ $05,$04,$03,$02,$01,$00,$ff,$fe,$fd,$fc,$fb,$fa,$06,$fa,$06,$fa
+ $01,$00,$ff,$01,$00,$ff,$00,$01,$02,$03,$04,$05,$ff,$fe,$fd,$fc
+ $fb,$04,$03,$05,$04,$03,$04,$03,$02,$01,$00,$ff,$fe,$fd,$fc,$fb
;
; Draws explosions on top of the "MISSILE COMMAND" title screen. During the
; game the explosions expand outward over a series of frames. Here we just
; draw/erase each explosion in one go.
;
; Erasing and drawing 20 explosions takes longer than a single frame. Because
; we erase/draw each slot, rather than erasing all then drawing all, the
; animation has a rippling appearance. The color cycling makes it appear
; violent.
;
; This function was rewritten in rev3 to make it more compact, to make room for
; the short function that follows at $6300. The original function erased all 20
; explosions before drawing 20 new ones. Because of the color cycling, the
; difference is hard to notice unless explosions overlap. (If you see an
; "eclipse", where an explosion is partly blacked out, you know you have a rev3
; machine.)
;
• Clear variables
]end_radius .var $9a {addr/1}
]xc .var $9b {addr/1}
]yc .var $9c {addr/1}
]start_radius .var $b5 {addr/1}
62b3: a2 13 DrawTitleExpl ldx #19 ;20 explosions
62b5: a9 00 :ExplLoop lda #$00
62b7: 85 9a sta ]end_radius ;prep to erase (shrink to zero)
62b9: bd 6e 01 lda expl_ypos,x ;is there an explosion in this slot?
62bc: f0 06 beq :CreateNew ;no, branch
62be: bd c2 01 lda expl_radius_idx_arr,x ;get radius
62c1: 20 f1 62 jsr :DrawExpl ;call the draw routine to erase it
;
62c4: a9 fc :CreateNew lda #%11111100 ;cycle all colors except 0/1 (background)
62c6: 85 ec sta color_cyc_mask
62c8: ad 0a 40 lda POKEY_RANDOM ;get random number
62cb: 29 07 and #%00000111 ;reduce to 0-7
62cd: 09 04 ora #%00000100 ;make it 4-7
62cf: 9d c2 01 sta expl_radius_idx_arr,x ;use as radius
62d2: 85 9a sta ]end_radius
;
62d4: ad 0a 40 lda POKEY_RANDOM ;get random number
62d7: 9d 39 01 sta expl_xpos,x ;save as X position
62da: ad 0a 40 :Retry lda POKEY_RANDOM ;get random number
62dd: c9 d0 cmp #208
62df: b0 f9 bcs :Retry ;too high, loop
62e1: c9 38 cmp #56
62e3: 90 f5 bcc :Retry ;too low, loop
62e5: 9d 6e 01 sta expl_ypos,x ;save as Y position
;
62e8: a9 00 lda #$00 ;initial radius is zero
62ea: 20 f1 62 jsr :DrawExpl ;draw it
62ed: ca dex ;done yet?
62ee: 10 c5 bpl :ExplLoop ;no, loop
62f0: 60 rts
62f1: 85 b5 :DrawExpl sta ]start_radius ;save A-reg as starting radius
62f3: bd 6e 01 lda expl_ypos,x ;get XC/YC from arrays
62f6: 85 9c sta ]yc
62f8: bd 39 01 lda expl_xpos,x
62fb: 85 9b sta ]xc
62fd: 4c 71 5e jmp DrawExplosion ;draw the explosion
;
; Clamps the wave number to [0,39].
;
; This is a patch added in rev3, to prevent the weird stuff that happened on
; higher-numbered waves. Simply changing 40 to 20 works because the intensity
; peaks at wave 19, and the color scheme repeats every 20 waves.
;
6300: a5 a7 ClampWaveNum lda wave_num ;get the current wave number
6302: c9 28 cmp #40 ;have we reached wave 40?
6304: 90 02 bcc :Lt40 ;not yet, branch
6306: a9 14 lda #20 ;reset to 20
6308: 85 a7 :Lt40 sta wave_num
630a: 4c 86 69 jmp ClearScreen ;this patch replaced a call to ClearScreen
;
; Updates the position of a smartbomb.
;
; Bombs move in a direct path like missiles unless explosions are detected
; nearby.
;
630d: 20 4d 63 MoveSmartBomb jsr ScanForExpl ;look for nearby explosions
6310: a5 d5 lda sbomb_prox ;any flags set?
6312: d0 06 bne :Traffic ;yes, branch
6314: 20 11 54 jsr MoveMissile ;no, just move it like a missile
6317: b8 clv
6318: 50 32 bvc :Return ;(always)
631a: c9 ff :Traffic cmp #$ff ;totally surrounded?
631c: d0 06 bne :Dodge ;no, try to dodge
631e: 20 11 54 jsr MoveMissile ;yes, just make a run for it
6321: b8 clv
6322: 50 28 bvc :Return ;(always)
6324: 20 8c 63 :Dodge jsr CalcDesiredDir ;calculate desired angle (0-15)
6327: a5 d5 lda sbomb_prox ;get collision map
6329: a4 a7 ldy wave_num ;get current wave number
632b: c0 09 cpy #$09 ;>= 9?
632d: b0 02 bcs :Wave9 ;yes, branch
632f: 09 01 ora #$01 ;no, set a bit indicating an explosion above
6331: 85 d5 :Wave9 sta sbomb_prox ; so bomb won't try to move upward
;
6333: 20 00 64 jsr CalcEvasiveMove1 ;find an evasive move
6336: d0 06 bne :TrySingle ;no good, branch
6338: 20 5d 64 jsr DoEvasiveMove ;move evasively
633b: b8 clv
633c: 50 0e bvc :Return ;(always)
633e: 20 2d 64 :TrySingle jsr CalcEvasiveMove2 ;find an evasive move
6341: d0 06 bne :NoEvade ;no good, branch
6343: 20 5d 64 jsr DoEvasiveMove ;move evasively
6346: b8 clv
6347: 50 03 bvc :Return ;(always)
6349: 20 11 54 :NoEvade jsr MoveMissile
634c: 60 :Return rts
;
; Reads pixels from the screen to identify nearby explosions. Sets bits in a
; byte to identify which directions are obstructed.
;
; This can trigger on the target 'X' drawn when an ABM is fired, which is either
; a false-positive or a very prescient smart bomb. They can also false-positive
; on passing missile heads.
;
; On exit:
; $d5: proximal explosion flags
; (X-reg preserved)
;
• Clear variables
]saved_x .var $ac {addr/1}
]pix_ptr .var $b0 {addr/2}
634d: a9 00 ScanForExpl lda #$00
634f: 85 d5 sta sbomb_prox ;init result to zero
6351: 86 ac stx ]saved_x ;preserve X-reg
6353: a0 07 ldy #$07 ;8 locations in coord table
6355: a6 97 :Loop ldx slot_index ;get smartbomb slot index (8-15)
6357: bd 5d 01 lda abm_cur_y,x ;get current Y coordinate
635a: 18 clc
635b: 79 82 63 adc :peek_tbl,y ;add peek offset
635e: 49 ff eor #$ff ;invert to form pointer
6360: 85 b1 sta ]pix_ptr+1 ;set as high byte
6362: bd 28 01 lda abm_cur_x,x ;get current X coordinate
6365: 18 clc
6366: 79 84 63 adc :peek_tbl+2,y ;add peek offset
6369: 85 b0 sta ]pix_ptr ;set as low byte of pointer
;
636b: a2 00 ldx #$00
636d: a1 b0 lda (]pix_ptr,x) ;get pixel color
636f: 29 c0 and #%11000000 ;mask off low bits to ignore city bkgnd
6371: c9 80 cmp #%10000000 ;is it 4 (or 5), the explosion color?
6373: d0 07 bne :NotExpl ;no, branch
;
6375: a5 d5 lda sbomb_prox
6377: 19 f8 63 ora single_bits_rev,y ;set the bit for this entry
637a: 85 d5 sta sbomb_prox
637c: 88 :NotExpl dey ;done with all positions?
637d: 10 d6 bpl :Loop ;no, loop
637f: a6 ac ldx ]saved_x ;restore X-reg
6381: 60 rts
;
; Coordinate offset table. Used to peek at the pixels near the smartbomb.
;
; The X/Y coordinates overlap, using an arrangement similar to a cos/sin table
; (cos is 1/4 phase ahead). The set of pixels examined forms a roughly diamond
; shape, starting on the N corner and moving counter-clockwise:
;
; (0,8) 0
; (-6,6) (6,6) 1 7
; (-8,0) (8,0) bit#: 2 6
; (-6,-6) (6,-6) 3 5
; (0,-8) 4
;
; The smartbomb itself is a diamond with radius 3 (5 pixels across), so this
; gives us a 6-pixel early collison warning.
;
6382: 08 06 00 fa+ :peek_tbl .bulk $08,$06,$00,$fa,$f8,$fa,$00,$06,$08,$06
;
; Computes the desired angle of travel for a smartbomb.
;
; The result should be 0-15, but the code doesn't clamp the result. That value
; is only possible for vertical upward movement, and smartbombs don't move that
; way, so it shouldn't be an issue.
;
; On exit:
; $d0: angle (0-16)
;
• Clear variables
]slope_adj .var $98 {addr/1}
]result_int .var $a9 {addr/1}
]result_frac .var $aa {addr/1}
]deltaX .var $b0 {addr/1}
]deltaY .var $b1 {addr/1}
638c: a4 97 CalcDesiredDir ldy slot_index ;get smartbomb slot index (8-15)
638e: 20 f6 58 jsr CalcMissilePath ;calculate path from current position
6391: a4 b0 ldy ]deltaX
6393: a5 b1 lda ]deltaY
6395: d0 05 bne :NotHoriz ;not horizontal move, branch
6397: a9 04 lda #$04 ;max out for horizontal
6399: b8 clv ;(note: no divide-by-zero check)
639a: 50 16 bvc Set98 ;(always)
639c: 20 bb 59 :NotHoriz jsr Divide8_8 ;compute deltaY / deltaX (slope)
639f: a0 04 ldy #$04 ;compare to 5 entries
63a1: a5 a9 :Loop lda ]result_int
63a3: d9 de 63 cmp :incr_int,y ;does the integer part match?
63a6: d0 05 bne :NotEqual ;no, branch to test for < or >
63a8: a5 aa lda ]result_frac ;get fractional part of quotient
63aa: d9 e3 63 cmp :incr_frac,y ;compare to increment
63ad: 88 :NotEqual dey ;move to next entry
63ae: 90 f1 bcc :Loop ;while quotient < table value, loop
63b0: c8 iny ;undo last DEY (Y-reg now 0-4)
63b1: 98 tya
63b2: 85 98 Set98 sta ]slope_adj ;0-4
;
63b4: a6 97 ldx slot_index ;get slot index
63b6: bd 10 06 lda msl_inc_xint,x ;EOR sign bits for X and Y increments
63b9: 5d 30 06 eor msl_inc_yint,x
63bc: 10 07 bpl :SameSign ;if signs match, branch
63be: a9 04 lda #$04 ;set direction to (4 - direction) since
63c0: 38 sec ; angle increases other way
63c1: e5 98 sbc ]slope_adj
63c3: 85 98 sta ]slope_adj
;
63c5: bd 10 06 :SameSign lda msl_inc_xint,x ;get the X increment
63c8: 2a rol A ;roll sign bit into carry
63c9: bd 30 06 lda msl_inc_yint,x ;get the Y increment
63cc: 2a rol A ;roll sign bit into carry, X sign into low bit
63cd: 2a rol A ;signs are now in low bits: %??????XY
63ce: 29 03 and #%00000011 ;mask to get %000000XY
63d0: a8 tay ;use as index
63d1: b9 da 63 lda :move_dir,y ;get the base offset from the table
63d4: 18 clc
63d5: 65 98 adc ]slope_adj ;add the slope offset (0-4) to get 0-16
63d7: 85 d0 sta sbomb_dir ; (bug? should clamp to 0-15?)
63d9: 60 rts
;
; Values used to form a direction index, based on the sign bits of the path to
; the target. Smart bombs should be moving downward (-Y). The above routine
; combines these values with the slope index (0-4).
;
; %00 = +X/+Y -> $0c-0f
; %01 = +X/-Y -> $08-0b
; %10 = -X/+Y -> $00-03
; %11 = -X/-Y -> $04-07
;
; 0
; 1-3 | d-f
; 4 ---*--- c
; 5-7 | 9-b
; 8
;
; For example, a smartbomb dropping from left to right might have deltaX=44
; deltaY=97, yielding a slope of $0234 (2.203). That's greater than entry #3 in
; the table below, so we start with 3. It has +X/-Y, so we change it to (4-3) =
; 1. Combining the sign bits yields %01, so we get entry #1 below, which is
; $08. Add the two to get the direction: $08 + $01 = $09.
;
; As the slope gets closer to horizontal, we increase toward $0b. If the bomb
; were moving right to left, a near-vertical drop would be $07, and as the slope
; got closer to horizontal we would decrease toward $04.
63da: 0c 08 00 04 :move_dir .bulk $0c,$08,$00,$04
;
; Slope index thresholds. Entry 0 is for a very shallow angle (near
; horizontal), entry 4 is very steep (near vertical).
;
63de: 00 00 00 01+ :incr_int .bulk $00,$00,$00,$01,$05
63e3: 00 32 ab 7f+ :incr_frac .bulk $00,$32,$ab,$7f,$06
;
; This table is indexed with the smartbomb direction (0-15) formed earlier. The
; bit patterns are compared to the result from the proximity detection routine;
; see $6382 for the table, which uses a table arranged like this:
;
; 0
; 1 7
; 2 6
; 3 5
; 4
;
; For example, if there are explosions at angles 4/5/6, we generated a collision
; mask %01110000, and need to find an entry in the table below that masks off
; all the 1 bits. Entries 0-4 work, so we want to use whichever is closest to
; the desired travel direction. The entry number (0-15) indicates the direction
; the smartbomb should move. (If you look at the comment above $63da, you'll
; see that directions 0-4 are moving up and to the left, directly away from the
; explosions in this example.)
63e8: 83 evade_mask_tbl .dd1 %10000011
63e9: 87 .dd1 %10000111
63ea: 07 .dd1 %00000111
63eb: 0f .dd1 %00001111
63ec: 0e .dd1 %00001110
63ed: 1e .dd1 %00011110
63ee: 1c .dd1 %00011100
63ef: 3c .dd1 %00111100
63f0: 38 .dd1 %00111000
63f1: 78 .dd1 %01111000
63f2: 70 .dd1 %01110000
63f3: f0 .dd1 %11110000
63f4: e0 .dd1 %11100000
63f5: e1 .dd1 %11100001
63f6: c1 .dd1 %11000001
63f7: c3 .dd1 %11000011
;
; Like single_bits at $60f7, but in the opposite order. (This is arguably the
; correct order, since table entry 0 holds bit 0, unless you subscribe to the
; PowerPC documentation style.)
;
63f8: 01 02 04 08+ single_bits_rev .bulk $01,$02,$04,$08,$10,$20,$40,$80
;
; Find the evasive path that is closest to the direction we want to go. We
; start with the smartbomb's current direction and probe in both directions. At
; each step we check multiple collision bits to try to find the ideal path.
;
; On entry:
; $d0: smartbomb's desired direction index
; $d5: explosion proximity detection results
;
; On exit:
; Y-reg: evasive move index
; Z-flag: set if move found
;
]counter .var $98 {addr/1}
CalcEvasiveMove1
6400: a4 d0 ldy sbomb_dir ;get movement direction index
6402: a6 d0 ldx sbomb_dir ;in both X-reg and Y-reg
6404: a9 08 lda #$08 ;check 9 angles
6406: 85 98 sta ]counter
6408: a5 d5 :Loop1 lda sbomb_prox ;get results from proximity test
640a: 39 e8 63 and evade_mask_tbl,y ;mask bits with table
640d: d0 01 bne :NotY ;one or more bits still set, branch
640f: 60 rts ;return with Y-reg result, Z-flag set
6410: a5 d5 :NotY lda sbomb_prox ;get proximity flags
6412: 3d e8 63 and evade_mask_tbl,x ;mask bits with table
6415: d0 05 bne :NotX ;one or more bits still set, branch
6417: 8a txa ;want value in X-reg, so transfer that to Y-reg
6418: a8 tay ; to use as function result
6419: a9 00 lda #$00 ;set Z-flag
641b: 60 rts ;return with Z-flag set
641c: 88 :NotX dey ;move Y-reg one step clockwise
641d: 10 02 bpl :Ge0 ;branch if still > 0
641f: a0 0f ldy #15 ;reset to 15
6421: e8 :Ge0 inx ;move X-reg index one step counter-clockwise
6422: e0 10 cpx #16 ;reached 16?
6424: 90 02 bcc :Sub16 ;no, branch
6426: a2 00 ldx #$00 ;reset to 0
6428: c6 98 :Sub16 dec ]counter ;have we considered all possible angles?
642a: 10 dc bpl :Loop1 ;not yet, loop
642c: 60 rts ;return with Z-flag clear (counter went negative)
;
; Selects an evasive path if the previous function wasn't able to. We test
; single bits in the proximity bit map to find an explosion-free direction that
; is closest to our desired angle.
;
; On entry:
; $d0: smartbomb's desired direction index
; $d5: explosion proximity detection results
;
; On exit:
; Y-reg: evasive move index
; Z-flag: set if move found
;
CalcEvasiveMove2
642d: a5 d0 lda sbomb_dir ;get desired direction (0-15)
642f: 4a lsr A ;divide by 2
6430: a8 tay ;copy to X-reg and Y-reg
6431: aa tax
6432: a9 04 lda #$04 ;walk through 5 directions
6434: 85 98 sta ]counter
;
6436: a5 d5 :Loop lda sbomb_prox ;get proximity flags
6438: 39 f8 63 and single_bits_rev,y ;is there an explosion in the Y-reg direction?
643b: f0 09 beq :MoveY ;no, move that way
643d: a5 d5 lda sbomb_prox ;get proximity flags
643f: 3d f8 63 and single_bits_rev,x ;is there an explosion in the X-reg direction?
6442: d0 08 bne :NoMove ;yes, branch
;
6444: 8a txa ;copy X-reg to Y-reg
6445: a8 tay ; through the A-reg
6446: 98 :MoveY tya ;copy Y-reg to A-reg
6447: 0a asl A ;double it
6448: a8 tay ;back to Y-reg to use as result
6449: a9 00 lda #$00 ;set Z-flag
644b: 60 rts
644c: 88 :NoMove dey ;move Y-reg one step clockwise
644d: 10 02 bpl :Ge0 ;branch if still >= 0
644f: a0 07 ldy #$07 ;reset to 7
6451: e8 :Ge0 inx ;move X-reg one step counter-clockwise
6452: e0 08 cpx #$08 ;reached 8?
6454: 90 02 bcc :Le8 ;no, branch
6456: a2 00 ldx #$00 ;reset to 0
6458: c6 98 :Le8 dec ]counter ;have we considered all possible angles?
645a: 10 da bpl :Loop ;not yet, loop
645c: 60 rts ;return with Z-flag clear (counter went negative)
;
; Moves a smartbomb in an evasive path.
;
; If we're too close to the edge of the screen or to the cities, we just do a
; direct move instead.
;
; On entry:
; Y-reg: evasive move index
;
645d: a6 97 DoEvasiveMove ldx slot_index ;get smartbomb slot index (8-15)
645f: bd 28 01 lda abm_cur_x,x ;get current X coord
6462: c9 f7 cmp #$f7 ;near right edge of screen?
6464: b0 19 bcs :Jmp_MoveMissile ;yes, branch
6466: c9 08 cmp #$08 ;near left edge of screen?
6468: 90 15 bcc :Jmp_MoveMissile ;yes, branch
646a: bd 5d 01 lda abm_cur_y,x ;get current Y coord
646d: c9 ce cmp #206 ;near top of screen?
646f: b0 0e bcs :Jmp_MoveMissile ;yes, branch
6471: c9 2d cmp #45 ;near bottom of screen?
6473: 90 0a bcc :Jmp_MoveMissile ;yes, branch
6475: 20 82 64 jsr :EvasiveMove ;move the bomb in the requested direction
6478: a4 97 ldy slot_index ;get the slot index
647a: 20 f6 58 jsr CalcMissilePath ;recalculate direct path to target for next frame
647d: 18 clc
647e: 60 rts
:Jmp_MoveMissile
647f: 4c 11 54 jmp MoveMissile ;move directly toward target
6482: a6 97 :EvasiveMove ldx slot_index ;get smartbomb slot index (8-15)
6484: b9 af 64 lda :evade_frac+4,y ;get fractional part of X move
6487: 18 clc
6488: 7d 18 01 adc abm_cur_xfrac,x ;add to current position
648b: 9d 18 01 sta abm_cur_xfrac,x
648e: b9 c3 64 lda :evade_int+4,y ;get int part of X move
6491: 7d 28 01 adc abm_cur_x,x ;add to current position
6494: 9d 28 01 sta abm_cur_x,x
;
6497: b9 ab 64 lda :evade_frac,y ;get fractional part of Y move
649a: 18 clc
649b: 7d 4d 01 adc abm_cur_yfrac,x ;add to current position
649e: 9d 4d 01 sta abm_cur_yfrac,x
64a1: b9 bf 64 lda :evade_int,y ;get int part of Y move
64a4: 7d 5d 01 adc abm_cur_y,x ;add to current position
64a7: 9d 5d 01 sta abm_cur_y,x
64aa: 60 rts
;
; Smartbomb evasive movement tables. The evasive move direction index (0-15)
; determines which entry to use. Each entry has 8.8 movement increments for X
; and Y.
;
; The X and Y values overlap with an offset of 4 (like a cos/sin table with a
; quarter-phase offset).
;
; # X Y X Y
; 0: $0000,$0100 0.000, +1.000
; 1: $ff9e,$00ec -0.383, +0.922
; 2: $ff4c,$00b4 -0.703, +0.703
; 3: $ff14,$0062 -0.922, +0.383
; 4: $ff00,$0000 -1.000, 0.000
; 5: $ff14,$ff9e -0.922, -0.383
; 6: $ff4c,$ff4c -0.703, -0.703
; 7: $ff9e,$ff14 -0.383, -0.922
; 8: $0000,$ff00 0.000, -1.000
; 9: $0062,$ff14 +0.383, -0.922
; a: $00b4,$ff4c +0.703, -0.703
; b: $00ec,$ff9e +0.922, -0.383
; c: $0100,$0000 +1.000, 0.000
; d: $00ec,$0062 +0.922, +0.383
; e: $00b4,$00b4 +0.703, +0.703
; f: $0062,$00ec +0.383, +0.922
;
; This uses the same counter-clockwise angle system seen in earlier tables:
; 0
; 2 e
; 4 c
; 6 a
; 8
64ab: 00 ec b4 62 :evade_frac .bulk $00,$ec,$b4,$62 ;Y starts here
64af: 00 9e 4c 14 .bulk $00,$9e,$4c,$14 ;X starts here
64b3: 00 14 4c 9e .bulk $00,$14,$4c,$9e
64b7: 00 62 b4 ec .bulk $00,$62,$b4,$ec
64bb: 00 ec b4 62 .bulk $00,$ec,$b4,$62
64bf: 01 00 00 00 :evade_int .bulk $01,$00,$00,$00
64c3: 00 ff ff ff .bulk $00,$ff,$ff,$ff
64c7: ff ff ff ff .bulk $ff,$ff,$ff,$ff
64cb: 00 00 00 00 .bulk $00,$00,$00,$00
64cf: 01 00 00 00 .bulk $01,$00,$00,$00
;
; Draws crosshairs. The previous contents of the screen that were drawn over by
; the previous crosshairs are restored, then the crosshairs are drawn in the new
; position.
;
; The code uses two pointers, one to walk the vertical line and one to walk the
; horizontal line. The center pixel is saved and drawn twice.
;
; On entry:
; $09-0a: previously-drawn crosshair X/Y position
; $a5-a6: current crosshair X/Y position
;
; On exit:
; $09-0a: updated to $a5-a6
;
]new_v_ptr .var $00 {addr/2}
]last_h_ptr .var $03 {addr/2}
]last_v_ptr .var $06 {addr/2}
]new_h_ptr .var $0d {addr/2}
]color .var $98 {addr/1}
64d3: a9 e0 DrawCrosshairs lda #%11100000 ;color = 7
64d5: 4c da 64 jmp DoDrawCrosshair
;
; Draws crosshairs in the background color.
;
; This isn't about "erasing" them -- we do that every time as part of drawing --
; but rather making them disappear by drawing them in the background color.
;
64d8: a9 00 ClearCrosshairs lda #$00 ;color = 0
64da: 85 98 DoDrawCrosshair sta ]color ;save color in ZP
64dc: a5 0a lda last_cross_yc ;get vertical position (zero if crosshairs hidden)
64de: c9 18 cmp #24 ;was it visible before?
64e0: 90 78 bcc :Return ;no, bail
64e2: 18 clc
64e3: 69 03 adc #$03 ;add 3 to get top row
64e5: 49 ff eor #$ff ;invert to form high byte of pointer
64e7: 85 07 sta ]last_v_ptr+1 ;save it to ZP
64e9: a5 09 lda last_cross_xc ;get horizontal position
64eb: 85 06 sta ]last_v_ptr ;use as low byte of pointer
64ed: 38 sec
64ee: e9 03 sbc #$03 ;subtract 3 to get left column
64f0: 85 03 sta ]last_h_ptr ;save as low byte of pointer
64f2: a5 0a lda last_cross_yc ;get middle row
64f4: 49 ff eor #$ff ;invert to form high byte of pointer
64f6: 85 04 sta ]last_h_ptr+1 ;save to ZP
;
64f8: a5 a6 lda cross_yc ;get current crosshair Y coordinate
64fa: c9 18 cmp #24 ;is it actually drawable?
64fc: 90 5c bcc :Return ;no, bail
64fe: 85 0a sta last_cross_yc ;save for next time
6500: 18 clc
6501: 69 03 adc #$03 ;add 3 to get top row
6503: 49 ff eor #$ff ;invert to form high byte of pointer
6505: 85 01 sta ]new_v_ptr+1 ;save to ZP
6507: a5 a5 lda cross_xc ;use X coord as low byte of pointer
6509: 85 09 sta last_cross_xc
650b: 85 00 sta ]new_v_ptr
650d: 38 sec
650e: e9 03 sbc #$03 ;subtract 3 to get left edge
6510: 85 0d sta ]new_h_ptr ;use as low byte of pointer
6512: a5 a6 lda cross_yc ;get Y coord
6514: 49 ff eor #$ff ;invert to form high byte of pointer
6516: 85 0e sta ]new_h_ptr+1 ;save to ZP
; We now have four pointers: old horizontal, old vertical, new horizontal, new
; vertical. Walk through the old pointers, restoring the previous pixels if
; they weren't overwritten.
6518: a0 06 ldy #6 ;crosshairs are 7 pixels high/wide
651a: a2 00 ldx #$00
651c: a1 03 :RestoreLoop lda (]last_h_ptr,x) ;get pixel value from screen (horz bar)
651e: 29 e0 and #%11100000 ;mask the color bits
6520: c9 e0 cmp #%11100000 ;is it color 7?
6522: d0 05 bne :NoCopyH ;no, something overdrew crosshair pixel; branch
6524: b9 16 00 lda cross_back_store+7,y ;get previous pixel value
6527: 81 03 sta (]last_h_ptr,x) ;write to display
6529: a1 06 :NoCopyH lda (]last_v_ptr,x) ;get pixel value from screen (vert bar)
652b: 29 e0 and #%11100000 ;mask the color bits
652d: c9 e0 cmp #%11100000 ;is it color 7?
652f: d0 05 bne :NoCopyV ;no, something overdrew crosshair pixel; branch
6531: b9 0f 00 lda cross_back_store,y ;get previous pixel value
6534: 81 06 sta (]last_v_ptr,x) ;write to display
6536: e6 03 :NoCopyV inc ]last_h_ptr ;move right one column
6538: e6 07 inc ]last_v_ptr+1 ;move down one row
653a: 88 dey ;are we done yet?
653b: 10 df bpl :RestoreLoop ;no, loop
; Now draw the new crosshairs, preserving the background. We want to
; preserve/draw in the opposite order in which we restore so that the center
; pixel is handled correctly.
653d: a0 06 ldy #6 ;crosshairs are 7 pixels high/wide
653f: a2 00 ldx #$00
6541: a1 00 :DrawLoop lda (]new_v_ptr,x) ;get pixel value from screen (vert bar)
6543: 99 0f 00 sta cross_back_store,y ;save it
6546: a5 98 lda ]color
6548: 81 00 sta (]new_v_ptr,x) ;write the color to the screen
654a: e6 01 inc ]new_v_ptr+1 ;move down one row
654c: a1 0d lda (]new_h_ptr,x) ;get pixel value from screen (horz bar)
654e: 99 16 00 sta cross_back_store+7,y ;save it
6551: a5 98 lda ]color
6553: 81 0d sta (]new_h_ptr,x) ;write the color to the screen
6555: e6 0d inc ]new_h_ptr ;move right one column
6557: 88 dey ;are we done yet?
6558: 10 e7 bpl :DrawLoop ;no, loop
655a: 60 :Return rts
655b: 16 .dd1 $16 ;checksum byte
;
; Initializes the crosshairs, placing them in a low-center position.
; Initializes the backing store to the background color.
;
655c: a9 80 InitCrosshairs lda #128 ;set XC=128 (center of screen)
655e: 85 09 sta last_cross_xc
6560: 85 a5 sta cross_xc
6562: a9 50 lda #80 ;set YC=80 (lower third of screen)
6564: 85 0a sta last_cross_yc
6566: 85 a6 sta cross_yc
;
6568: a9 00 lda #$00 ;color = 0 (background)
656a: a2 0d ldx #13 ;7 vertical entries, 7 horizontal entries
656c: 95 0f :Loop sta cross_back_store,x ;init backing store
656e: ca dex
656f: 10 fb bpl :Loop
6571: 60 rts
;
; Hides the crosshairs. Draws them in black, then sets the current position to
; Y=0 so we won't try to draw them.
;
6572: 20 d8 64 HideCrosshairs jsr ClearCrosshairs ;erase crosshairs from screen
6575: a9 00 lda #$00
6577: 85 a6 sta cross_yc ;set current/previous positions to zero
6579: 85 0a sta last_cross_yc ; to indicate that crosshairs are hidden
657b: 60 rts
;
; Draws the flashing 'X' that marks an ABM's destination when the player fires.
;
; On exit:
; (X-reg preserved)
;
• Clear variables
]ptr1 .var $00 {addr/2}
]ptr2 .var $06 {addr/2}
]saved_x .var $23 {addr/1}
657c: 86 23 DrawTargetX stx ]saved_x ;preserve X-reg
657e: a9 80 lda #%10000000 ;fg color = 4 (cycles)
6580: 85 0b sta draw_fg_color
6582: a5 a6 lda cross_yc ;get target vertical position
6584: 49 ff eor #$ff ;invert to form pointer
6586: 38 sec
6587: e9 02 sbc #$02 ;subtract 2 to get top line
6589: 85 07 sta ]ptr2+1 ;set high bytes of pointers
658b: 85 01 sta ]ptr1+1
658d: a5 a5 lda cross_xc ;get target horizontal position
658f: 4c a9 65 jmp DoDrawTargetX ;jump to common code
;
; Erases the 'X' that marks the ABM's destination when the player fires. This
; is called before drawing the explosion.
;
; On entry:
; $97: index of ABM
;
; On exit:
; (X-reg preserved)
;
]counter .var $98 {addr/1}
6592: 86 23 EraseTargetX stx ]saved_x ;save X-reg
6594: a9 00 lda #$00
6596: 85 0b sta draw_fg_color ;fg color = index 0 (background)
6598: a6 97 ldx slot_index ;get index of interest
659a: bd b2 01 lda abm_targ_y_arr,x ;get vertical coordinate
659d: 49 ff eor #$ff ;invert bits to form high byte of pointer
659f: 38 sec
65a0: e9 02 sbc #$02 ;subtract 2 to get top line
65a2: 85 07 sta ]ptr2+1
65a4: 85 01 sta ]ptr1+1
65a6: bd a2 01 lda abm_targ_x_arr,x ;get horizontal coordinate
;
65a9: 18 DoDrawTargetX clc
65aa: 69 02 adc #$02 ;add 2 to get right side
65ac: 85 06 sta ]ptr2 ;use as low byte of pointer
65ae: 38 sec
65af: e9 04 sbc #$04 ;subtract 4 (i.e. original coord - 2) to get left
65b1: 85 00 sta ]ptr1 ;use as low byte of pointer
;
65b3: a2 00 ldx #$00 ;want X-reg=0 for (ZP,X)
65b5: a9 04 lda #$04 ;plot 5 points
65b7: 85 98 sta ]counter
65b9: a5 0b lda draw_fg_color ;get the color
65bb: 81 06 :Loop sta (]ptr2,x) ;set a pixel on one diagonal
65bd: 81 00 sta (]ptr1,x) ;set a pixel on the other diagonal
65bf: e6 07 inc ]ptr2+1 ;move down one line
65c1: c6 06 dec ]ptr2 ;move one pixel left
65c3: e6 01 inc ]ptr1+1 ;move down one line
65c5: e6 00 inc ]ptr1 ;move one pixel right
65c7: c6 98 dec ]counter ;done yet?
65c9: 10 f0 bpl :Loop ;no, branch
;
65cb: a6 23 ldx ]saved_x ;restore X-reg
65cd: 60 rts
;
; Draws the player indicator glyph, next to the score. This flashes about once
; a second during play. We also erase the glyph for the inactive player.
;
65ce: a0 e0 DrawPlyrIndic ldy #%11100000 ;fg color = 7
65d0: a5 b9 DrawPlyrIndicC lda cur_plyr_num ;get current player number (0 or 1)
65d2: 20 db 65 jsr :DoDraw ;draw it
65d5: a0 00 ldy #$00 ;fg color = 0 (background)
65d7: a5 b9 lda cur_plyr_num ;get player number
65d9: 49 01 eor #$01 ;flip it to the other player
65db: 84 0b :DoDraw sty draw_fg_color ;set color
65dd: aa tax
65de: bd ec 65 lda :glyph_index_tbl,x ;get index of arrow glyph
65e1: 48 pha
65e2: bd ee 65 lda :horz_posn_tbl,x ;get horizontal position
65e5: aa tax
65e6: a0 e2 ldy #226 ;set vertical position at the top
65e8: 68 pla
65e9: 4c 00 66 jmp DrawLetterByIdx ;draw it
:glyph_index_tbl
65ec: 21 .dd1 $21 ;left arrow glyph
65ed: 22 .dd1 $22 ;right arrow glyph
65ee: 4d :horz_posn_tbl .dd1 77
65ef: a8 .dd1 168
;
; Draws or erases the 1UP / 2UP arrows next to the player scores.
;
65f0: a5 d7 UpdatePlyrIndic lda draw_1up_flag ;are we in the "show" or "hide" phase?
65f2: d0 06 bne :Erase ;hide, draw in background color
65f4: 20 ce 65 jsr DrawPlyrIndic ;show, draw it
65f7: b8 clv
65f8: 50 05 bvc :Return ;(always)
65fa: a0 00 :Erase ldy #%00000000 ;fg color = 0 (background)
65fc: 20 d0 65 jsr DrawPlyrIndicC
65ff: 60 :Return rts
;
; Draws the specified letter, by index. The index is converted to an offset
; into the A-Z glyph set.
;
; Sometimes this is called with indices outside the range of letters, e.g.
; $21/22. This works because the letter set is followed in memory by the symbol
; set.
;
; On entry:
; A-reg: letter index
; X-reg: horizontal position
; Y-reg: vertical position
; $08: preserve background flag
; $0b: foreground color index (%PPP00000)
; $0c: background color index (%PPP00000)
;
• Clear variables
]xpos .var $00 {addr/1}
]ypos .var $01 {addr/1}
]glyph_ptr .var $0d {addr/2}
]vert_scale .var $21 {addr/1}
]horz_scale .var $22 {addr/1}
6600: 85 0d DrawLetterByIdx sta ]glyph_ptr ;init pointer with glyph index
6602: 86 00 stx ]xpos ;copy args to ZP
6604: 84 01 sty ]ypos
6606: a9 00 lda #$00
6608: 85 0e sta ]glyph_ptr+1 ;set high byte to zero
660a: a9 ff lda #$ff
660c: 85 08 sta preserve_bg_flag ;default to preserving the background pixels
660e: 06 0d asl ]glyph_ptr ;multiply glyph index by 8
6610: 26 0e rol ]glyph_ptr+1
6612: 06 0d asl ]glyph_ptr
6614: 26 0e rol ]glyph_ptr+1
6616: 06 0d asl ]glyph_ptr
6618: 26 0e rol ]glyph_ptr+1
661a: a9 62 lda #<glyphs_2 ;add to glyph base address to form pointer
661c: 18 clc
661d: 65 0d adc ]glyph_ptr
661f: 85 0d sta ]glyph_ptr
6621: a9 73 lda #>glyphs_2
6623: 65 0e adc ]glyph_ptr+1
6625: 85 0e sta ]glyph_ptr+1 ;(fall through to draw code)
;
; Draws the glyph at the pointer. The scale is set to 1x1.
;
; On entry:
; $00: center X (will have 4 subtracted)
; $01: center Y (will have 4 added)
; $08: preserve background flag
; $0b: foreground color index (%PPP00000)
; $0c: background color index (%PPP00000)
; $0d-0e: glyph pointer
;
DrawGlyphPtrNoSc
6627: a9 01 lda #$01 ;set scale to 1x1
6629: 85 22 sta ]horz_scale
662b: 85 21 sta ]vert_scale
;
; Draws an 8x8 1-bit bitmap. This is generally used for character glyphs, but
; is also used for the city bitmaps and a few arrows.
;
; The parameters determine the position, scale factor, colors, and whether the
; transparent parts are drawn in the background color or the existing pixels are
; preserved.
;
; On entry:
; $00: center X (will have 4 subtracted)
; $01: center Y (will have 4 added)
; $08: preserve background flag
; $0b: foreground color index (%PPP00000)
; $0c: background color index (%PPP00000)
; $0d-0e: glyph pointer
; $21: vertical glyph size multiplier
; $22: horizontal glyph size multiplier
;
]horz_counter .var $03 {addr/1}
]vert_counter .var $04 {addr/1}
]bit_counter .var $98 {addr/1}
662d: a0 07 DrawGlyphPtr ldy #$07 ;start with the last byte in the 8-byte glyph
662f: a5 01 lda ]ypos ;add 4 to Y to get top pixel
6631: 18 clc
6632: 69 04 adc #$04
6634: 49 ff eor #$ff ;invert to get the high byte of the address
6636: 85 07 sta gfx_ptr+1 ;save it
6638: a5 21 :ByteLoop lda ]vert_scale ;get vertical scale multiplier
663a: 85 04 sta ]vert_counter ;use as counter
663c: a5 00 :LineLoop lda ]xpos ;subtract 4 from X to get left pixel
663e: 38 sec
663f: e9 04 sbc #$04
6641: 24 fc bit cocktail_flip ;are we flipped?
6643: 50 02 bvc :NoFlip ;no, branch
6645: 49 ff eor #$ff ;invert the coordinate
6647: 85 06 :NoFlip sta gfx_ptr ;set as low byte of pointer
;
6649: a2 00 ldx #$00 ;set X-reg=0 and leave it that way
664b: a9 07 lda #$07 ;byte holds data for 8 pixels
664d: 85 98 sta ]bit_counter
664f: b1 0d lda (]glyph_ptr),y ;get byte from glyph data
6651: 0a :BitLoop asl A ;shift high (leftmost) bit into carry
6652: 48 pha ;save pixel data
6653: a5 22 lda ]horz_scale ;get horizontal scale multiplier
6655: 85 03 sta ]horz_counter ;use as counter
6657: a5 0b lda draw_fg_color ;load the foreground color
6659: b0 0b bcs :DrawPixel ;if bit set, draw it
665b: a5 08 lda preserve_bg_flag ;bit clear; draw or preserve background?
665d: 10 05 bpl :UseBgColor ;branch to draw the background color
665f: a1 06 lda (gfx_ptr,x) ;get the existing color value from the screen
6661: b8 clv ;(why not just skip the STA entirely?)
6662: 50 02 bvc :DrawPixel ;(always)
6664: a5 0c :UseBgColor lda draw_bg_color ;use the background color
6666: 81 06 :DrawPixel sta (gfx_ptr,x) ;write the color value to the pixel
;
6668: 24 fc bit cocktail_flip ;is the screen flipped?
666a: 50 05 bvc :NoFlip ;no, branch
666c: c6 06 dec gfx_ptr ;move one pixel right, in reverse
666e: b8 clv
666f: 50 02 bvc :Next ;(always)
6671: e6 06 :NoFlip inc gfx_ptr ;move one pixel right
6673: c6 03 :Next dec ]horz_counter ;decrement horz scale counter; are we done?
6675: d0 ef bne :DrawPixel ;not yet, loop (w/o fetching screen pix?)
6677: 68 pla ;restore shifted pixel data
6678: c6 98 dec ]bit_counter ;have we processed all the bits?
667a: 10 d5 bpl :BitLoop ;not yet, branch
;
667c: e6 07 inc gfx_ptr+1 ;move to next line
667e: c6 04 dec ]vert_counter ;decrement scale counter; are we done?
6680: d0 ba bne :LineLoop ;not yet, loop
6682: 88 dey ;advance to next byte of glyph data
6683: 10 b3 bpl :ByteLoop ;loop
6685: 60 rts
;
; Erases missile trails when the missile explodes. Used for both ICBMs and
; ABMs.
;
; On entry:
; $97: index of missile (0-15)
;
; On exit:
; (X/Y-reg preserved)
;
• Clear variables
]xint .var $00 {addr/1}
]yint .var $01 {addr/1}
]last_line_addr .var $02 {addr/1}
]xfrac .var $03 {addr/1}
]yfrac .var $04 {addr/1}
]saved_x .var $23 {addr/1}
]saved_y .var $24 {addr/1}
]xcounter .var $98 {addr/1}
]ycounter .var $99 {addr/1}
6686: 86 23 EraseTrail stx ]saved_x ;preserve X/Y-reg
6688: 84 24 sty ]saved_y
668a: a4 97 ldy slot_index ;get missile slot index
668c: a9 00 lda #$00 ;init counters
668e: 85 98 sta ]xcounter ;(the fractional part of the increment will be
6690: a9 ff lda #$ff ; added to or subtracted from these)
6692: 85 99 sta ]ycounter
;
6694: b9 92 01 lda abm_start_y_arr,y ;get start Y coord
6697: 49 ff eor #$ff ;invert to form pointer
6699: 85 07 sta gfx_ptr+1 ;set as high byte
669b: b9 82 01 lda abm_start_x_arr,y ;get start X coord
669e: 85 06 sta gfx_ptr ;set as low byte
;
66a0: b9 00 06 lda msl_inc_xfrac,y ;copy increments to ZP
66a3: 85 03 sta ]xfrac
66a5: b9 20 06 lda msl_inc_yfrac,y
66a8: 85 04 sta ]yfrac
66aa: b9 10 06 lda msl_inc_xint,y
66ad: 85 00 sta ]xint
66af: b9 30 06 lda msl_inc_yint,y
66b2: 85 01 sta ]yint
;
66b4: b9 5d 01 lda abm_cur_y,y ;get current Y coord
66b7: 49 ff eor #$ff ;invert so we can compare directly to pointer
66b9: 85 02 sta ]last_line_addr ;save in ZP
66bb: c5 07 cmp gfx_ptr+1 ;does start == end?
66bd: f0 2c beq :Bail ;yes, nothing to do; bail
;
66bf: a2 00 ldx #$00
66c1: a9 00 :Loop lda #$00 ;color = 0 (background)
66c3: 81 06 sta (gfx_ptr,x) ;set pixels
;
66c5: a5 98 lda ]xcounter ;update the fractional counter for X
66c7: 18 clc
66c8: 65 03 adc ]xfrac
66ca: 85 98 sta ]xcounter
66cc: a5 06 lda gfx_ptr ;if the add carried, update the pointer
66ce: 65 00 adc ]xint
66d0: 85 06 sta gfx_ptr
;
66d2: a5 99 lda ]ycounter ;update the fractional counter for Y
66d4: 38 sec ;subtract rather than add because we're incrementing
66d5: e5 04 sbc ]yfrac ; the pointer, not the coordinate, and pointers
66d7: 85 99 sta ]ycounter ; increase as they move down rather than up
66d9: a5 07 lda gfx_ptr+1 ;if the sub borrowed, update the pointer
66db: e5 01 sbc ]yint
66dd: 85 07 sta gfx_ptr+1
66df: c9 18 cmp #$18 ;are we < $1800 (off top or bottom of screen)?
66e1: 90 08 bcc :Bail ;yes, bail (bug? test should be #$19?)
66e3: c5 02 cmp ]last_line_addr ;have we reached the final Y-coordinate (inclusive)?
66e5: d0 da bne :Loop ;no, branch
;
66e7: a9 00 lda #$00 ;reached the end, erase last pixel
66e9: 81 06 sta (gfx_ptr,x)
66eb: a6 23 :Bail ldx ]saved_x ;restore X/Y-reg
66ed: a4 24 ldy ]saved_y
66ef: 60 rts
;
; Draws the head of a missile (ABM or ICBM).
;
; On entry:
; $97: missile slot index (0-15)
;
66f0: a4 97 DrawMslHead ldy slot_index
66f2: a9 80 lda #%10000000 ;color = 4 (cycles)
66f4: 4c 01 67 jmp DoDrawTrail
;
; Draws one pixel of a missile's trail (ABM or ICBM).
;
; On entry:
; $97: missile slot index (0-15)
;
66f7: a9 e0 DrawMslTrail lda #%11100000 ;color = 7 (ABM / outgoing)
66f9: a4 97 ldy slot_index ;check slot number
66fb: c0 08 cpy #$08 ;is it in the ABM range?
66fd: 90 02 bcc DoDrawTrail ;yes, branch
66ff: a9 40 lda #%01000000 ;color = 2 (ICBM / incoming)
;
; Missile slot index in Y-reg, color in A-reg.
;
]saved_x .var $98 {addr/1}
6701: 85 0b DoDrawTrail sta draw_fg_color ;set foreground color
6703: 86 98 stx ]saved_x ;preserve X-reg
6705: b9 28 01 lda abm_cur_x,y ;get X coordinate
6708: 85 06 sta gfx_ptr ;use as low byte of pointer
670a: b9 5d 01 lda abm_cur_y,y ;get Y coordinate
670d: 49 ff eor #$ff ;invert to form high byte of pointer
670f: 85 07 sta gfx_ptr+1
6711: a5 0b lda draw_fg_color ;get color
6713: a2 00 ldx #$00
6715: 81 06 sta (gfx_ptr,x) ;set pixel
6717: a6 98 ldx ]saved_x ;restore X-reg
6719: 60 rts
;
; Prepares for the next incoming wave. Erases the screen, draws the city
; background and cities, sets the color palette, and updates the screen flip.
;
671a: a2 07 PrepWaveScreen ldx #$07 ;color palette has 8 entries
671c: a9 0e lda #%00001110 ;black
671e: 95 e4 :Loop sta color_palette,x ;set the color
6720: ca dex
6721: 10 fb bpl :Loop
;
6723: 20 00 63 jsr ClampWaveNum ;rev2: JSR $6986 (clear screen) instead
6726: 20 7c 68 jsr DrawCityBkgnd ;draw the city backdrop
6729: 20 5e 67 jsr DrawCities ;draw the cities
672c: 20 21 69 jsr SetColorPalette ;set the color palette for this wave
;
; Configures a pointer to the language-specific string table, and initializes
; the cocktail / flip value.
;
; On exit:
; $2a-2b: points to start of locale-specific string table
; $fc: set to $00 (upright), $80 (cocktail + plyr 1) or $ff (cocktail + plyr
; 2)
;
672f: a5 f3 InitLangAndFlip lda r10_irq_mod ;get R10 switches
6731: 4a lsr A
6732: 4a lsr A
6733: 4a lsr A
6734: 4a lsr A ;shift language into bits 2/1
6735: 29 06 and #%00000110 ;mask off everything else
6737: a8 tay ;use as index
6738: b9 95 6a lda msg_lang_tbl,y ;copy to ZP pointer
673b: 85 2a sta msg_base_ptr
673d: b9 96 6a lda msg_lang_tbl+1,y
6740: 85 2b sta msg_base_ptr+1
;
6742: a9 00 lda #$00 ;start with zero
6744: a4 f4 ldy r8_irq_inv ;get R8 switches
6746: 10 08 bpl :Upright ;not cocktail cabinet, branch
6748: 09 80 ora #%10000000 ;set high bit
674a: a4 b9 ldy cur_plyr_num ;get player number (0/1)
674c: f0 02 beq :Upright ;if player 1, branch
674e: 09 7f ora #%01111111 ;player 2, set all bits (esp. bit 6 for BIT/BVC)
6750: 85 fc :Upright sta cocktail_flip ;note this feeds back into OUT0 during IRQ
6752: 60 rts
;
; Erases a city bitmap, drawing it with the background color.
;
; On entry:
; Y-reg: city index (0-5)
;
6753: be e2 60 EraseCityNoFlip ldx city_x_tbl,y ;get X coordinate of city center
6756: b9 eb 60 lda city_y_tbl,y ;get Y coordinate of city center
6759: a8 tay
675a: a9 00 lda #$00 ;color = 0/0
675c: f0 21 beq DrawCityNoFlip ;(always)
;
; Draws all live cities.
;
• Clear variables
]counter .var $99 {addr/1}
675e: a0 05 DrawCities ldy #$05 ;max of 6 cities
6760: 84 99 sty ]counter
6762: a4 b9 :CityLoop ldy cur_plyr_num ;get current player number
6764: b9 c5 00 lda live_city_mask,y ;get bit mask of live cities
6767: a4 99 ldy ]counter ;get bit to test
6769: 39 f7 60 and single_bits,y ;get Nth bit (actually 2 ^ (7-N))
676c: f0 0c beq :Destroyed ;bit not set, city is dead
;
676e: be e2 60 ldx city_x_tbl,y ;get X coordinate of city center
6771: b9 eb 60 lda city_y_tbl,y ;get Y coordinate of city center
6774: a8 tay
6775: a9 e6 lda #%11100110 ;color = 3/7
6777: 20 7f 67 jsr DrawCityNoFlip ;draw the city
677a: c6 99 :Destroyed dec ]counter ;done yet?
677c: 10 e4 bpl :CityLoop ;no, branch
677e: 60 rts
;
; Draws a city, suppressing the horizontal screen flip.
;
; City positions aren't flipped horizontally when the screen is rotated, so we
; want to disable that for the call.
;
; On entry:
; X-reg: X coordinate
; Y-reg: Y coordinate
; A-reg: two city color indices, as %AAA0BBB0
;
677f: 48 DrawCityNoFlip pha ;save A-reg (holds the colors)
6780: a5 fc lda cocktail_flip
6782: 29 bf and #%10111111 ;clear the overflow (V) bit to disable flip
6784: 85 fc sta cocktail_flip
6786: 68 pla ;restore A-reg
6787: 20 95 67 jsr DrawCity ;draw city with glyph renderer
678a: a5 fc lda cocktail_flip
678c: 29 3f and #%00111111 ;mask off cocktail / flip bits
678e: f0 04 beq :Return ;none of the low bits were set, bail
6790: 09 c0 ora #%11000000 ;yes, re-enable flip
6792: 85 fc sta cocktail_flip ;(even if player 1 is active... bug?)
6794: 60 :Return rts
;
; Draws a city at the specified coordinates.
;
; Uses the character glyph renderer to draw two pairs of overlapping 8x8 bitmaps
; in two different colors. This is used to draw the inter-wave bonus city tally
; as well as the game-play cities.
;
; On entry:
; X-reg: horizontal position
; Y-reg: vertical position
; A-reg: two city color indices, as %AAA0BBB0
;
]counter .var $05 {addr/1}
]xc .var $1d {addr/1}
]yc .var $1e {addr/1}
]colors .var $1f {addr/2}
6795: 86 1d DrawCity stx ]xc
6797: 84 1e sty ]yc
6799: 85 1f sta ]colors ;save color #0 (only the high 3 bits count)
679b: 0a asl A ;shift low nibble of color into high
679c: 0a asl A
679d: 0a asl A
679e: 0a asl A
679f: 85 20 sta ]colors+1 ;save color #1
67a1: a2 03 ldx #$03 ;4 glyphs to draw
67a3: 86 05 stx ]counter
;
67a5: a6 05 :Loop ldx ]counter
67a7: bc c5 67 ldy :city_color_idx,x
67aa: b9 1f 00 lda $001f,y ;[colors]
67ad: 85 0b sta draw_fg_color
;
67af: a4 1e ldy ]yc ;get vertical coord in Y-reg
67b1: bd c9 67 lda :city_color_glyph,x ;get the glyph index
67b4: 48 pha
67b5: a5 1d lda ]xc ;get horizontal coordinate
67b7: 18 clc
67b8: 7d cd 67 adc :city_x_offset,x ;adjust by table offset
67bb: aa tax ;move to X-reg
67bc: 68 pla ;put the fg color in A-reg
67bd: 20 00 66 jsr DrawLetterByIdx ;draw the glyph
67c0: c6 05 dec ]counter ;done yet?
67c2: 10 e1 bpl :Loop ;no, loop
67c4: 60 rts
;
; City rendering data. The code above draws 4 glyphs. The data below specifies
; the glyph index, color index, and horizontal offset for each part.
;
67c5: 01 00 01 00 :city_color_idx .bulk $01,$00,$01,$00 ;index into colors stored at $1f
:city_color_glyph
67c9: 1e 1d 20 1f .bulk $1e,$1d,$20,$1f ;index, past letters into symbols
67cd: fc fc 04 04 :city_x_offset .bulk $fc,$fc,$04,$04 ;+4/-4
;
; Erases an ABM icon (ammo indicator) from a silo.
;
; On entry:
; Y-reg: silo index (0-2)
;
]ptr .var $06 {addr/2}
67d1: be a0 00 EraseAmmo ldx: abms_left,y ;get number of ABMs remaining (0-9)
67d4: e8 inx ;add one to get index of ABM that was just fired
67d5: a9 20 lda #%00100000 ;color = 1 (city-area background)
;
; Draws an ABM icon (ammo indicator).
;
; On entry:
; Y-reg: silo index (0-2)
; X-reg: ABM index (1-10)
; A-reg: color (%PPP00000)
;
; On exit:
; (Y-reg preserved)
;
67d7: 18 DrawAmmo1 clc
67d8: 48 pha ;preserve A-reg
67d9: b9 e8 60 lda abm_silo_x_tbl,y ;get horizontal position of launcher
67dc: 7d 19 68 adc abm_xoffset_tbl-1,x ;add position of icon
67df: 85 06 sta ]ptr ;use as low byte of pointer
67e1: 18 clc
67e2: b9 f1 60 lda abm_silo_y_tbl,y ;get vertical position of launcher
67e5: 7d 0f 68 adc abm_yoffset_tbl-1,x ;add position of icon
67e8: 49 ff eor #$ff ;invert to form high byte of address
67ea: 85 07 sta ]ptr+1 ;use as high byte of pointer
67ec: 68 pla ;restore A-reg
;
; Draws an ABM ammo icon.
;
; The pattern is hard-coded into the instruction stream:
;
; #
; #
; #
; ###
; # #
;
; On entry:
; $06-07: pointer to top-center pixel
; A-reg: color (%PPP00000)
;
67ed: a2 00 DrawAmmoIcon ldx #$00
67ef: 81 06 sta (]ptr,x) ;manipulate the pointer directly to draw pixels
67f1: e6 07 inc ]ptr+1
67f3: 81 06 sta (]ptr,x)
67f5: e6 07 inc ]ptr+1
67f7: 81 06 sta (]ptr,x)
67f9: e6 07 inc ]ptr+1
67fb: 81 06 sta (]ptr,x)
67fd: c6 06 dec ]ptr
67ff: 81 06 sta (]ptr,x)
6801: e6 07 inc ]ptr+1
6803: 81 06 sta (]ptr,x)
6805: e6 06 inc ]ptr
6807: e6 06 inc ]ptr
6809: 81 06 sta (]ptr,x)
680b: c6 07 dec ]ptr+1
680d: 81 06 sta (]ptr,x)
680f: 60 rts
;
; There are 10 ABMs at each launcher site. As they are fired, the visual
; representation is updated in this order:
; 9
; 8 7
; 6 5 4
; 1 3 2 0
;
6810: 02 ff ff fc+ abm_yoffset_tbl .bulk $02,$ff,$ff,$fc,$fc,$fc,$f9,$f9,$f9,$f9
681a: 00 fd 03 fa+ abm_xoffset_tbl .bulk $00,$fd,$03,$fa,$00,$06,$fd,$03,$f7,$09
;
; Prints "LOW" below a missile launcher silo.
;
; On entry:
; A-reg: silo index (0-2)
;
; On exit:
; (X-reg / Y-reg preserved)
;
• Clear variables
]saved_a .var $02 {addr/1}
]saved_x .var $23 {addr/1}
]saved_y .var $24 {addr/1}
PrintLauncherLow
6824: 86 23 stx ]saved_x ;save X-reg / Y-reg
6826: 84 24 sty ]saved_y
6828: aa tax ;put silo index in X-reg
6829: bd 61 68 lda low_msg_tbl,x ;get "LOW" message index for this launcher
682c: 24 fc bit cocktail_flip ;are we flipped?
682e: 50 03 bvc :NoFlip ;no, branch
6830: bd 67 68 lda low_msg_tbl_r,x ;use other table
6833: 4c 56 68 :NoFlip jmp :Print
;
; Prints "OUT" below a missile launcher silo. The previous "LOW" indicator is
; erased first.
;
; On entry:
; A-reg: launcher index (0-2)
;
; On exit:
; (X-reg / Y-reg preserved)
;
PrintLauncherOut
6836: 85 02 sta ]saved_a ;preserve A/X/Y-reg
6838: 86 23 stx ]saved_x
683a: 84 24 sty ]saved_y
683c: aa tax ;put silo index in X-reg
683d: bd 61 68 lda low_msg_tbl,x ;get "LOW" message index for this launcher
6840: 24 fc bit cocktail_flip ;screen flipped?
6842: 50 03 bvc :NoFlip ;no, branch
6844: bd 67 68 lda low_msg_tbl_r,x ;yes, use alternate table
6847: 20 4d 6a :NoFlip jsr EraseMessage ;erase "LOW"
684a: a6 02 ldx ]saved_a ;get launcher index
684c: bd 5e 68 lda out_msg_tbl,x ;get "OUT" message index for this launcher
684f: 24 fc bit cocktail_flip ;screen flipped?
6851: 50 03 bvc :Print ;no, branch
6853: bd 64 68 lda out_msg_tbl_r,x ;yes, use alternate table
6856: 20 55 6a :Print jsr PrintMessagePr ;print message
6859: a6 23 ldx ]saved_x ;restore X-reg / Y-reg
685b: a4 24 ldy ]saved_y
685d: 60 rts
685e: 0f out_msg_tbl .dd1 $0f ;"OUT" (left)
685f: 10 .dd1 $10 ;"OUT" (middle)
6860: 11 .dd1 $11 ;"OUT" (right)
6861: 16 low_msg_tbl .dd1 $16 ;"LOW" (left)
6862: 17 .dd1 $17 ;"LOW" (middle)
6863: 18 .dd1 $18 ;"LOW" (right)
6864: 11 out_msg_tbl_r .dd1 $11 ;"OUT" (right)
6865: 10 .dd1 $10 ;"OUT" (middle)
6866: 0f .dd1 $0f ;"OUT" (left)
6867: 18 low_msg_tbl_r .dd1 $18 ;"LOW" (right)
6868: 17 .dd1 $17 ;"LOW" (middle)
6869: 16 .dd1 $16 ;"LOW (left)
;
; Draws all of the ammo icons for one silo.
;
; On entry:
; Y-reg: silo number (0-2)
;
]counter .var $02 {addr/1}
686a: be a0 00 DrawAllAmmo ldx: abms_left,y ;get number of ABMs left at this silo
686d: f0 0c beq :Return ;if zero, bail
686f: 86 02 :Loop stx ]counter ;save to ZP as counter
6871: a9 e0 lda #%11100000 ;color = 7
6873: 20 d7 67 jsr DrawAmmo1 ;draw ammo icon
6876: a6 02 ldx ]counter
6878: ca dex ;done yet?
6879: d0 f4 bne :Loop ;no, loop
687b: 60 :Return rts
;
; Draws the terrain that forms the background for the cities and for the text at
; the bottom of the screen. This is drawn with color index 1.
;
; Note this is NOT flipped when the screen is rotated.
;
• Clear variables
687c: a2 00 DrawCityBkgnd ldx #$00 ;set to zero (does not change)
687e: a0 00 ldy #$00 ;offset into data
6880: a9 e6 lda #$e6 ;set pointer to line #25 ($e600)
6882: 85 07 sta gfx_ptr+1
6884: b9 ab 68 :LineLoop lda :city_bk_data,y ;get offset of start position
6887: 85 06 sta gfx_ptr ;use as pointer low byte
6889: c8 iny ;advance to next byte of data
688a: a9 20 :PixelLoop lda #%00100000 ;color = 1
688c: 81 06 sta (gfx_ptr,x) ;set pixel
688e: a5 06 lda gfx_ptr ;get pointer low byte (== line offset)
6890: d9 ab 68 cmp :city_bk_data,y ;have we reached the end point?
6893: e6 06 inc gfx_ptr ;advance pointer
6895: 90 f3 bcc :PixelLoop ;not at end, loop
;
6897: c8 iny ;advance to next byte of data
6898: b9 ab 68 lda :city_bk_data,y ;get next start position
689b: c9 ff cmp #$ff ;is it the end-of-line marker?
689d: d0 09 bne :B_LineLoop ;no, loop (could BNE $6884 instead)
689f: c8 iny ;advance to next byte of data
68a0: c0 74 cpy #$74 ;are we near the end of the list?
68a2: 90 02 bcc :NotNearEnd ;no, branch
68a4: a0 73 ldy #$73 ;repeat last item in list [should be end - start]
68a6: e6 07 :NotNearEnd inc gfx_ptr+1 ;advance high byte of pointer to next line;
68a8: d0 da :B_LineLoop bne :LineLoop ; if we didn't roll over to $0000, loop
68aa: 60 rts
;
; Data for the city backdrop. Each screen line is represented with pairs of
; bytes, specifying inclusive start/end line offsets. The end of the line is
; marked with $ff.
;
; The last entry in the table is repeated until we've drawn all lines to the
; bottom of the screen.
;
68ab: 0c 0d 1d 1e+ :city_bk_data .bulk $0c,$0d,$1d,$1e,$75,$76,$81,$82,$e9,$ea,$fa,$fb,$ff ;line 25
68b8: 0b 0e 1c 1f+ .bulk $0b,$0e,$1c,$1f,$75,$77,$80,$83,$e7,$ea,$f9,$fc,$ff ;line 24
68c5: 0a 20 74 84+ .bulk $0a,$20,$74,$84,$e6,$fd,$ff
68cc: 09 21 73 84+ .bulk $09,$21,$73,$84,$e6,$fe,$ff
68d3: 08 22 73 85+ .bulk $08,$22,$73,$85,$e5,$fe,$ff
68da: 06 23 72 85+ .bulk $06,$23,$72,$85,$e4,$fe,$ff
68e1: 05 23 71 86+ .bulk $05,$23,$71,$86,$ac,$bb,$e3,$fe,$ff
68ea: 04 24 38 39+ .bulk $04,$24,$38,$39,$70,$87,$ab,$bc,$e2,$fe,$ff
68f5: 03 25 37 3a+ .bulk $03,$25,$37,$3a,$6f,$87,$aa,$bd,$e1,$ff,$ff
6900: 01 26 32 3b+ .bulk $01,$26,$32,$3b,$6b,$98,$a8,$bd,$df,$ff,$ff
690b: 00 3c 49 58+ .bulk $00,$3c,$49,$58,$67,$c0,$de,$fe,$ff
6914: 00 5a 65 ff+ .bulk $00,$5a,$65,$ff,$ff
6919: 00 5c 64 ff+ .bulk $00,$5c,$64,$ff,$ff ;line 13
691e: 00 ff ff .bulk $00,$ff,$ff ;line 12 (offset +$73)
;
; Sets the color palette for the current wave.
;
6921: a5 a7 SetColorPalette lda wave_num ;get wave number; starts at 1
6923: 38 sec
6924: e9 01 sbc #$01 ;make it 0-based
6926: 4a lsr A ;divide by 2 so colors change every 2 waves
6927: c9 0a :DivLoop cmp #10 ;is it < 10?
6929: 90 03 bcc :Sub10 ;yes, branch
692b: 38 sec ;no, subtract 10
692c: e9 0a sbc #10 ;do this repeatedly to divide by 10
692e: c9 0a :Sub10 cmp #10 ;done yet?
6930: b0 f5 bcs :DivLoop ;no, keep going
;
6932: aa tax ;use as index
6933: bc 4e 69 ldy :color_table,x ;get table offset
6936: a2 06 ldx #$06 ;start with entries 6/7
6938: b9 4e 69 :Loop lda :color_table,y ;get entry
693b: 29 0f and #$0f ;grab the low nibble
693d: 95 e5 sta color_palette+1,x ;store that in the odd entry
693f: b9 4e 69 lda :color_table,y ;get entry again
6942: 4a lsr A ;shift the high nibble down
6943: 4a lsr A
6944: 4a lsr A
6945: 4a lsr A
6946: 95 e4 sta color_palette,x ;store that in the even entry
6948: 88 dey ;move to previous table entry
6949: ca dex ;move to previous palette entry
694a: ca dex
694b: 10 eb bpl :Loop ;branch if not done
694d: 60 rts
;
; Color sets. The values here are used to configure the 8-entry color palette.
; There are 10 different sets.
;
; The first part is a table of offsets into the color set table. The offset
; identifies the LAST of the four bytes; for example, an offset of $0d (13)
; grabs the bytes at +13, +12, +11, and +10. The offset is calculated from the
; start of this 10-byte table.
;
694e: 19 1d 21 15+ :color_table .bulk $19,$1d,$21,$15,$25,$2d,$31,$0d,$11,$29
;
; Color table entries, packed two per byte. The hardware takes colors as
; %xxxxRGBx, so only those bits matter. The even entries apply to the whole
; screen, the odd entries can only appear in the 3bpp areas when the 3rd bit is
; set.
;
; Note that entries 4/5 don't usually matter, because those are marked for
; color-cycling, except to the extent that they're out of phase with each other.
;
; For example, wave #1 uses table #0, which is given offset $19 in the table
; above. That means the entries are at offsets $16-19. When unpacked, the
; palette entries will be:
; 0e 02 06 08 06 0e 0c 0c
;
6958: 2a e0 e2 66 .bulk $2a,$e0,$e2,$66 ;$0a-0d (#7)
695c: 06 42 40 aa .bulk $06,$42,$40,$aa ;$0e-11 (#8)
6960: e6 22 0a cc .bulk $e6,$22,$0a,$cc ;$12-15 (#3)
6964: e2 68 6e cc .bulk $e2,$68,$6e,$cc ;$16-19 (#0)
6968: e2 a8 ae cc .bulk $e2,$a8,$ae,$cc ;$1a-1d (#1)
696c: ec 62 64 aa .bulk $ec,$62,$64,$aa ;$1e-21 (#2)
6970: c2 64 6c ee .bulk $c2,$64,$6c,$ee ;$22-25 (#4)
6974: 62 ea e6 cc .bulk $62,$ea,$e6,$cc ;$26-29 (#9)
6978: 82 6e 68 cc .bulk $82,$6e,$68,$cc ;$2a-2d (#5)
697c: 4a ee 48 22 .bulk $4a,$ee,$48,$22 ;$2e-31 (#6)
;
; Zeroes out $0600-$2eff, which clears lines 230 through 68 on the screen, as
; well as the small RAM area at $0600-063f. This is used when entering initials
; for high scores, to clear most of the screen without erasing the city area.
;
ClearScreenPartial
6980: a9 06 lda #$06 ;start at $0600
6982: a2 28 ldx #$28 ;clear $29 256-byte pages
6984: d0 04 bne :DoZero ;(always)
;
; Zeroes out $0200-3fff, clearing the screen to color 0. Also erases the data
; area at $600-63f.
;
6986: a2 3d ClearScreen ldx #$3d ;clear $3e 256-byte pages
6988: a9 02 lda #$02 ;start at $0200
;
]mem_ptr .var $06 {addr/2}
698a: 85 07 :DoZero sta ]mem_ptr+1 ;set high byte of pointer
698c: a0 00 ldy #$00
698e: 84 06 sty ]mem_ptr ;set low byte of pointer to zero
;
6990: a9 00 lda #$00 ;color = 0
6992: 91 06 :Loop sta (]mem_ptr),y
6994: c8 iny
6995: d0 fb bne :Loop ;loop if not done with page
6997: e6 07 inc ]mem_ptr+1 ;advance pointer
6999: ca dex ;decrement count; done yet?
699a: 10 f6 bpl :Loop ;no, branch
699c: 60 rts
;
; Gets a pointer to a character glyph.
;
; On entry:
; A-reg: ASCII value of character to find
;
; On exit:
; $0d-0e: pointer to glyph
;
]glyph_ptr .var $0d {addr/2}
699d: 29 7f GetGlyphPtr and #$7f ;strip high bit
699f: c9 41 cmp #‘A’ ;lower than 'A'?
69a1: 90 07 bcc :Special ;yes, do special chars
69a3: 29 3f and #%00111111 ;convert $40-5f to $00-1f
69a5: a2 02 ldx #$02 ;table 2
69a7: b8 clv
69a8: 50 18 bvc :DoPrint ;(always)
69aa: c9 3a :Special cmp #‘:’ ;lower than ':'?
69ac: 90 06 bcc :CheckNums ;yes, see if it's a number
69ae: 20 d3 69 jsr MapSpecialChar ;no, try to map to a glyph
69b1: b8 clv
69b2: 50 0e bvc :DoPrint ;(always)
69b4: c9 30 :CheckNums cmp #‘0’ ;greater than '0'?
69b6: b0 06 bcs :IsNumber ;yes, it's a number
69b8: 20 d3 69 jsr MapSpecialChar ;no, try to map to a glyph
69bb: b8 clv
69bc: 50 04 bvc :DoPrint ;(always)
69be: 29 0f :IsNumber and #%00001111 ;convert $30-39 to $00-09
69c0: a2 00 ldx #$00 ;table 0
69c2: 0a :DoPrint asl A ;multiply glyph index by 8
69c3: 0a asl A
69c4: 0a asl A
69c5: 18 clc
69c6: 7d 1e 6a adc glyph_base_tbl,x ;add table base
69c9: 85 0d sta ]glyph_ptr
69cb: bd 1f 6a lda glyph_base_tbl+1,x
69ce: 69 00 adc #$00
69d0: 85 0e sta ]glyph_ptr+1
69d2: 60 rts
;
; Map special chars [ @:?#$%] to glyph indices.
;
; On entry:
; A-reg: char ASCII value
;
; On exit:
; A-reg: glyph, or $00 if no match found
; X-reg: table number: $06 if match found, $04 if not
;
69d3: a2 06 MapSpecialChar ldx #$06 ;start on last element
69d5: dd e8 69 :Loop cmp char_map_char_tbl,x ;compare to requested char
69d8: d0 06 bne :NoMatch ;didn't match, branch
69da: bd ef 69 lda char_map_glyph_tbl,x ;get index of glyph
69dd: a2 06 ldx #$06 ; in table 6
69df: 60 rts
69e0: ca :NoMatch dex ;move to next entry
69e1: 10 f2 bpl :Loop ;loop if not done
69e3: a9 00 lda #$00 ;space char
69e5: a2 04 ldx #$04 ; in table 4
69e7: 60 rts
;
; Map character values that don't have ASCII counterparts, or don't align with
; the ASCII table, to their corresponding glyph numbers.
;
char_map_char_tbl
69e8: 20 .dd1 ‘ ’ ;space is ASCII, but at index $00
69e9: 40 .dd1 ‘@’ ;(C) copyright symbol
69ea: 3a .dd1 ‘:’ ;colon is also ASCII, but at index $09
69eb: 3f .dd1 ‘?’ ;(P) sound copyright symbol
69ec: 23 .dd1 ‘#’ ;A + umlaut
69ed: 24 .dd1 ‘$’ ;O + umlaut
69ee: 25 .dd1 ‘%’ ;U + umlaut
char_map_glyph_tbl
69ef: 00 08 09 0a+ .bulk $00,$08,$09,$0a,$0b,$0c,$0d
;
; Prints a string at the specified X/Y position.
;
; On entry:
; $08: preserve background flag
; $0b: foreground color index (%PPP00000)
; $0c: background color index (%PPP00000)
; $1d-1e: string pointer
; $1f: initial horizontal position
; $20: initial vertical position
; $21: vertical glyph size multiplier
; $22: horizontal glyph size multiplier
;
]xpos .var $00 {addr/1}
]ypos .var $01 {addr/1}
]char_tmp .var $05 {addr/1}
]str_ptr .var $1d {addr/2}
]horiz_posn .var $1f {addr/1}
]vert_posn .var $20 {addr/1}
]vert_mult .var $21 {addr/1}
]horiz_mult .var $22 {addr/1}
69f6: a0 00 PrintString ldy #$00
69f8: b1 1d :Loop lda (]str_ptr),y ;get char
69fa: 85 05 sta ]char_tmp ;save for later
69fc: 20 9d 69 jsr GetGlyphPtr ;get pointer to glyph in $0d-0e
69ff: a5 1f lda ]horiz_posn ;copy coordinates
6a01: 85 00 sta ]xpos
6a03: a5 20 lda ]vert_posn
6a05: 85 01 sta ]ypos
6a07: 98 tya ;preserve Y-reg on stack
6a08: 48 pha
6a09: 20 2d 66 jsr DrawGlyphPtr ;draw the glyph
;
6a0c: a5 22 lda ]horiz_mult ;get the horizontal multiplier
6a0e: 0a asl A ;multiply by 8 (the width of a character glyph)
6a0f: 0a asl A
6a10: 0a asl A
6a11: 18 clc
6a12: 65 1f adc ]horiz_posn ;update horizontal position for next char
6a14: 85 1f sta ]horiz_posn
6a16: 68 pla ;restore Y-reg
6a17: a8 tay
6a18: c8 iny ;advance to next character
6a19: a5 05 lda ]char_tmp ;was previous char the end of the DCI string?
6a1b: 10 db bpl :Loop ;no, loop
6a1d: 60 rts
;
; Pointers to glyph bitmaps for general character output routine.
;
6a1e: 1a 73 glyph_base_tbl .dd2 glyphs_0 ;$00 '0' - '9'
6a20: 62 73 .dd2 glyphs_2 ;$02 'A' - 'Z'
; This points to a blank space glyph, followed by '0'-'9'.
6a22: 12 73 glyph_digit_tbl .dd2 glyphs_4 ;$04 (unknown char or suppressed leading '0')
6a24: 3a 74 .dd2 glyphs_6 ;$06 '@', ':', '?', '$', '%', cities, etc.
;
; Gets the pointers and horizontal position for message N.
;
; On entry:
; Y-reg: message index * 3
;
; On exit:
; $1f: horizontal position
; $1d-1e: pointer to string
;
]str_ptr .var $1d {addr/2}
]horz_posn .var $1f {addr/1}
6a26: b1 2a GetMessageN lda (msg_base_ptr),y ;get horizontal position
6a28: c0 54 cpy #84 ;message number > 28? (28*3=84)
6a2a: 90 10 bcc :LowMsg ;no, show language-specific version
6a2c: b9 12 6e lda msgs_en,y ;yes, pull from tail end of English table
6a2f: 85 1f sta ]horz_posn
6a31: b9 13 6e lda msgs_en+1,y
6a34: 85 1d sta ]str_ptr
6a36: b9 14 6e lda msgs_en+2,y
6a39: b8 clv
6a3a: 50 0a bvc :StaRet ;(always)
6a3c: 85 1f :LowMsg sta ]horz_posn
6a3e: c8 iny ;get the string pointer
6a3f: b1 2a lda (msg_base_ptr),y
6a41: 85 1d sta ]str_ptr
6a43: c8 iny
6a44: b1 2a lda (msg_base_ptr),y
6a46: 85 1e :StaRet sta ]str_ptr+1
6a48: 60 rts
;
; Entry points for message draw/erase.
;
; Erasing a specific message is useful for things like the "LOW" and "OUT"
; indicators. Erasing the entire line is handy for double-width messages and
; variable-width messages.
;
; (See DoPrintMessage for entry/exit conditions.)
;
• Clear variables
]index_tmp .var $1f {addr/1}
]msg_vert_posn .var $20 {addr/1}
]vert_mult .var $21 {addr/1}
]horz_mult .var $22 {addr/1}
EraseMessageLine
6a49: a0 a0 ldy #$a0
6a4b: 30 0a bmi DoPrintMessage ;(always)
6a4d: a0 80 EraseMessage ldy #$80
6a4f: 30 06 bmi DoPrintMessage ;(always)
6a51: a0 00 PrintMessage ldy #$00
6a53: 10 02 bpl DoPrintMessage ;(always)
6a55: a0 ff PrintMessagePr ldy #$ff
;
; Prints a message.
;
; On entry:
; Y-reg: action:
; $00: draw message, don't preserve background
; $ff: draw message, do preserve background
; $80: erase message
; $a0: erase entire line message is on
; A-reg: message index
;
6a57: 84 08 DoPrintMessage sty preserve_bg_flag ;store flag and "command" here
6a59: a8 tay
6a5a: b9 c5 6c lda msg_glyph_size_tbl,y ;get value
6a5d: 29 0f and #%00001111 ;mask off the Y multiplier
6a5f: 85 21 sta ]vert_mult ;save to ZP
6a61: b9 c5 6c lda msg_glyph_size_tbl,y ;get same value
6a64: 4a lsr A ;shift X multiplier into low nibble
6a65: 4a lsr A
6a66: 4a lsr A
6a67: 4a lsr A
6a68: 85 22 sta ]horz_mult ;save to ZP
6a6a: b9 ed 6c lda msg_vertical_tbl,y ;get vertical position
6a6d: 85 20 sta ]msg_vert_posn
;
6a6f: 98 tya ;put message index arg back in A-reg
6a70: 85 1f sta ]index_tmp ;save to ZP so we can multiply by 3
6a72: 0a asl A
6a73: 18 clc
6a74: 65 1f adc ]index_tmp
6a76: a8 tay
6a77: 20 26 6a jsr GetMessageN ;get pointer in $1d-1e, horiz posn in $1f
;
6a7a: a9 e0 lda #%11100000 ;fg color = 7
6a7c: 85 0b sta draw_fg_color
6a7e: a9 00 lda #$00 ;bg color = 0
6a80: 85 0c sta draw_bg_color
; Call an appropriate function. At this point we have:
; $1d-1e: pointer
; $1f: horizontal start position
; $20: vertical start position
; $21: horizontal size multiplier
; $22: vertical size multiplier
6a82: a5 08 lda preserve_bg_flag ;check command
6a84: c9 80 cmp #$80 ;string erase?
6a86: d0 03 bne :Not80 ;no, branch
6a88: 4c 38 6b jmp EraseStringPartial ;$80: erase the string
6a8b: c9 a0 :Not80 cmp #$a0 ;line erase?
6a8d: d0 03 bne :NotA0 ;no, branch
6a8f: 4c 30 6b jmp EraseStringLine ;$a0: erase entire line string is on
6a92: 4c f6 69 :NotA0 jmp PrintString ;$00/ff: print string
;
; Table of addresses of pointers to strings. One entry here for each language.
;
6a95: 12 6e msg_lang_tbl .dd2 msgs_en ;English
6a97: 16 6d .dd2 msgs_fr ;French
6a99: 6a 6d .dd2 msgs_de ;German
6a9b: be 6d .dd2 msgs_es ;Spanish
;
; Draws a number between 0 and 18. Values in the range [0,9] are drawn as a
; single digit (i.e no leading '0' or space for one).
;
; The background color ($0c) is set to 0, and the preserve-background flag ($08)
; is disabled.
;
; On entry:
; A-reg: 0-9, or $ff to draw a blank space (for leading 0 suppression)
; X-reg: glyph center horizontal position
; Y-reg: glyph center vertical position
; $0b: foreground color index (%PPP00000)
;
; On exit:
; (X/Y-reg preserved)
;
]xc .var $00 {addr/1}
]yc .var $01 {addr/1}
]tmp_a .var $02 {addr/1}
]ptr .var $0d {addr/2}
]msg_horiz_posn .var $1f {addr/1}
]saved_x .var $23 {addr/1}
]saved_y .var $24 {addr/1}
6a9d: 86 1f DrawSmallNum stx ]msg_horiz_posn ;save vertical / horizontal position
6a9f: 84 20 sty ]msg_vert_posn
6aa1: 86 23 DoDrawSmallNum stx ]saved_x ;preserve X/Y-reg
6aa3: 84 24 sty ]saved_y
6aa5: 85 02 sta ]tmp_a ;save number
6aa7: c9 13 cmp #19 ;is value >= 19?
6aa9: b0 13 bcs :DrawBlank ;yes, print a blank space instead of a number
6aab: c9 0a cmp #10 ;is it < 10?
6aad: 90 0a bcc :DoSingle ;yes, draw single digit
6aaf: 38 sec
6ab0: e9 0a sbc #10 ;subtract 10 to get 0-9
6ab2: 85 02 sta ]tmp_a ;update number
6ab4: a9 01 lda #$01
6ab6: 20 c8 6a jsr :DoDraw ;draw a '1'
;
6ab9: a5 02 :DoSingle lda ]tmp_a ;get value 0-9
6abb: b8 clv
6abc: 50 02 bvc :CallDoDraw ;(always)
6abe: a9 ff :DrawBlank lda #$ff ;draw a blank space instead of a number
;
6ac0: 20 c8 6a :CallDoDraw jsr :DoDraw ;draw the digit
6ac3: a6 23 ldx ]saved_x ;restore X/Y-reg
6ac5: a4 24 ldy ]saved_y
6ac7: 60 rts
; Draw the digit in the accumulator (0-9 or $ff).
6ac8: 18 :DoDraw clc
6ac9: 69 01 adc #$01 ;convert $ff/0-9 to 0-10
6acb: 0a asl A ;each glyph is 8 bytes
6acc: 0a asl A
6acd: 0a asl A
6ace: 18 clc
6acf: 6d 22 6a adc glyph_digit_tbl ;add pointer to space/digits from table
6ad2: 85 0d sta ]ptr
6ad4: ad 23 6a lda glyph_digit_tbl+1
6ad7: 69 00 adc #$00
6ad9: 85 0e sta ]ptr+1
6adb: a5 1f lda ]msg_horiz_posn ;set horizontal / vertical position
6add: 85 00 sta ]xc
6adf: a5 20 lda ]msg_vert_posn
6ae1: 85 01 sta ]yc
6ae3: a9 00 lda #%00000000 ;bg color = 0
6ae5: 85 0c sta draw_bg_color
6ae7: a9 00 lda #$00
6ae9: 85 08 sta preserve_bg_flag ;overwrite background
6aeb: 20 27 66 jsr DrawGlyphPtrNoSc ;draw the glyph, no scaling
6aee: a5 1f lda ]msg_horiz_posn ;advance the horizontal position
6af0: 18 clc
6af1: 69 08 adc #$08 ;each glyph is 8 pixels wide (we use scale=1)
6af3: 85 1f sta ]msg_horiz_posn ;only really matters for value >= 10 case
6af5: 60 rts
;
; Prints a 3-byte BCD number (e.g. score).
;
; On entry:
; A-reg: foreground color (%PPP00000)
; X-reg: horizontal position
; Y-reg: vertical position
; $d1-d3: BCD value
;
]print_zero_flag .var $05 {addr/1}
6af6: 86 1f PrintBcdNumber stx ]msg_horiz_posn ;set position
6af8: 84 20 sty ]msg_vert_posn
6afa: 85 0b sta draw_fg_color ;set color
6afc: a2 02 ldx #$02 ;print 3 bytes (0-2)
6afe: a0 00 ldy #$00
6b00: 84 05 sty ]print_zero_flag ;clear flag so we don't draw '0'
;
6b02: b5 d1 :Loop lda bcd3_acc,x ;get byte
6b04: 4a lsr A ;shift high nibble to low
6b05: 4a lsr A
6b06: 4a lsr A
6b07: 4a lsr A
6b08: 20 1e 6b jsr :PrintDigit ;print it
6b0b: b5 d1 lda bcd3_acc,x ;get same byte
6b0d: e0 00 cpx #$00 ;are we on the rightmost byte?
6b0f: d0 04 bne :NotRight ;no, branch
6b11: a0 ff ldy #$ff ;yes, set the "do print '0'" flag
6b13: 84 05 sty ]print_zero_flag
6b15: 29 0f :NotRight and #$0f ;strip excess bits
6b17: 20 1e 6b jsr :PrintDigit ;print it
6b1a: ca dex ;done yet?
6b1b: 10 e5 bpl :Loop ;no, loop
6b1d: 60 rts
6b1e: f0 05 :PrintDigit beq :PrintZero ;(flags set for A-reg) if zero, branch
6b20: 85 05 sta ]print_zero_flag ;nonzero, set the "do print '0'" flag
6b22: b8 clv
6b23: 50 08 bvc :JmpDraw ;(always)
6b25: a9 00 :PrintZero lda #$00 ;(should already be $00?)
6b27: a4 05 ldy ]print_zero_flag ;check "do print '0'" flag
6b29: d0 02 bne :JmpDraw ;it's set, branch
6b2b: a9 ff lda #$ff ;not set, replace $00 with $ff
6b2d: 4c a1 6a :JmpDraw jmp DoDrawSmallNum ;draw the glyph
;
; Erases the entire screen line that a string is on. The caller identified a
; specific string to erase, but the only thing we care about is its vertical
; position.
;
; Only affects the 2bpp pixel area.
;
• Clear variables
]string_ptr .var $1d {addr/2}
]horz_posn .var $1f {addr/1}
]vert_posn .var $20 {addr/1}
]counter .var $98 {addr/1}
]end_offset .var $99 {addr/1}
6b30: a9 04 EraseStringLine lda #$04 ;start at horizontal offset 4, because we subtract 4
6b32: 85 1f sta ]horz_posn ; later on
6b34: a0 20 ldy #32 ;screen is 256 wide, chars are 8x8, max 32 chars
6b36: d0 08 bne :EraseScreenChunk ;(always)
;
; Erases a string, overwriting it with the background color.
;
; On entry:
; $1d: string pointer
;
EraseStringPartial
6b38: a0 ff ldy #$ff ;start at -1, so first loop increments to zero
6b3a: c8 :Loop iny
6b3b: b1 1d lda (]string_ptr),y
6b3d: 10 fb bpl :Loop ;loop until we find end of DCI string
6b3f: c8 iny ;increment Y-reg so it's equal to string length
;
; Erase a chunk of the screen. At this point:
; Y-reg: string length (8 pixels per char)
; $1f: horizontal start position
; $20: vertical start position
;
; We set the 2bpp pixel values to zero. The 3rd pixel bit is not affected.
;
:EraseScreenChunk
6b40: 84 99 sty ]end_offset ;store the string len
6b42: 06 99 asl ]end_offset ;double it (8x8 per char, 4 pixels per byte)
6b44: c6 99 dec ]end_offset ;subtract 1 so we can use it as an offset
6b46: a5 1f lda ]horz_posn ;get horizontal start position
6b48: 38 sec
6b49: e9 04 sbc #$04 ;subtract 4 to match string print routine
6b4b: 24 fc bit cocktail_flip ;is screen flipped vertically?
6b4d: 50 02 bvc :NoFlip ;no, branch
6b4f: 49 ff eor #$ff ;yes, flip horizontally to complete rotation
6b51: 85 1f :NoFlip sta ]horz_posn ;save for use as low byte of pointer
; Form a pointer into the 2bpp region. We can do this quickly because the
; region ends at $4000. If we take the line number [0,230] and invert the bits
; with EOR #$ff, we get [$ff,$19], which works as the high byte of a MADSEL-mode
; pointer. For regular RAM we have 64 bytes per line rather than 256, so we
; just divide by 4, and get [$3f,$06].
;
; Example: message table puts string on line 3. $03 ^ $ff = $fc, -4 = $f8, /4 =
; $3e. $3e00 is the start of line 223, which is (230-3)-4.
6b53: a5 20 lda ]vert_posn ;get vertical center position
6b55: 49 ff eor #$ff ;invert it as part of forming pointer
6b57: 38 sec
6b58: e9 04 sbc #$04 ;subtract 4 from inv. val to match string routine
6b5a: 85 20 sta ]vert_posn ;save for use as high byte of pointer
6b5c: a9 07 lda #$07 ;init counter to erase 8 lines
6b5e: 85 98 sta ]counter
6b60: 46 20 lsr ]vert_posn ;divide by 4
6b62: 66 1f ror ]horz_posn ;consider line 3... we eor #$ff to get $fc, then
6b64: 46 20 lsr ]vert_posn ; subtract 4 to get $f8; divide by 4 to get $3e00
6b66: 66 1f ror ]horz_posn
6b68: 24 fc bit cocktail_flip ;is screen flipped?
6b6a: 50 07 bvc :LineLoop ;no, branch
6b6c: a5 1f lda ]horz_posn ;yes, flip start position
6b6e: 38 sec
6b6f: e5 99 sbc ]end_offset
6b71: 85 1f sta ]horz_posn
;
]ptr .var $1f {addr/2} ;horz/vert ZP now holds pointer
6b73: a4 99 :LineLoop ldy ]end_offset ;get offset of byte at end of range
6b75: a9 00 :EraseLoop lda #$00
6b77: 91 1f sta (]ptr),y ;zero out 4 pixels
6b79: 88 dey
6b7a: 10 f9 bpl :EraseLoop
;
6b7c: a5 1f lda ]ptr ;update pointer
6b7e: 18 clc
6b7f: 69 40 adc #64 ;64 bytes per line
6b81: 85 1f sta ]ptr
6b83: 90 02 bcc :NoInc ;branch if we didn't overflow low byte
6b85: e6 20 inc ]ptr+1 ;increment the high byte
6b87: c6 98 :NoInc dec ]counter ;done yet?
6b89: 10 e8 bpl :LineLoop ;no, branch
6b8b: 60 rts
;
; Updates scrolling text. This outputs one character if the time is right, and
; resets the entire thing if the number of purchased credits changes.
;
• Clear variables
]thing_ptr? .var $06 {addr/2}
]bcd_credits .var $98 {addr/1}
UpdateScrollText
6b8c: a9 00 lda #$00
6b8e: 85 b9 sta cur_plyr_num ;set to player 1
6b90: a5 fc lda cocktail_flip ;rotate screen toward player 1
6b92: 29 80 and #%10000000 ;clear flip bits (but keep cocktail-mode bit)
6b94: 85 fc sta cocktail_flip ; so we only have to scroll right-to-left
;
6b96: a5 66 lda credit_count ;get number of credits purchased
6b98: c9 28 cmp #40 ;more than 40?
6b9a: 90 04 bcc :Lt40Cred ;no, branch
6b9c: a9 28 lda #40 ;cap at 40
6b9e: 85 66 sta credit_count ;store it, discarding excess credits
; Convert credit count to BCD equivalent.
6ba0: f8 :Lt40Cred sed ;enable BCD mode
6ba1: 85 99 sta tmp_99 ;store credit count in ZP
6ba3: a9 00 lda #$00
6ba5: 85 98 sta ]bcd_credits ;init BCD form
6ba7: a0 07 ldy #$07 ;8 bits per byte
6ba9: 06 99 :ConvLoop asl tmp_99 ;shift high bit into carry
6bab: a5 98 lda ]bcd_credits ;get current value
6bad: 65 98 adc ]bcd_credits ;double credits in BCD mode, adding 1 if carry set
6baf: 85 98 sta ]bcd_credits ;write it back
6bb1: 88 dey ;done all 8 bits?
6bb2: 10 f5 bpl :ConvLoop ;no, branch
6bb4: d8 cld ;disable BCD mode
;
6bb5: a5 98 lda ]bcd_credits ;get BCD credit count
6bb7: c5 29 cmp txsc_credits ;has number of credits changed since last time?
6bb9: f0 22 beq :NoCreditChange ;no, branch
6bbb: 85 29 sta txsc_credits ;store in $29 for special message
6bbd: a5 66 lda credit_count ;is credit count zero?
6bbf: f0 05 beq :ZeroCred ;yes, branch
6bc1: a2 00 ldx #$00 ;start in "press start" series
6bc3: b8 clv
6bc4: 50 0c bvc :SetSeries
6bc6: a5 f3 :ZeroCred lda r10_irq_mod ;get R10 switches
6bc8: 29 03 and #%00000011 ;mask to get coins-per-play setting
6bca: aa tax
6bcb: bd 2f 6c lda play_cost_msg_tbl,x ;get message index
6bce: 85 28 sta txsc_play_cost ;store in $28 for special message
6bd0: a2 05 ldx #$05 ;start in "insert coins" series
6bd2: 86 25 :SetSeries stx txsc_out_idx
; We clear the message and start over when the number of credits changes.
6bd4: 20 b6 6c jsr SetBottomBit3 ;set bit 3 for all of bottom text area
6bd7: a9 00 lda #$00 ; (clearing it to the city background color)
6bd9: 85 27 sta txsc_char_delay ;zero delay so we start drawing immediately
6bdb: 85 26 sta txsc_msg_offset ;draw from the start of the string
;
6bdd: a5 27 :NoCreditChange lda txsc_char_delay ;time to draw next char?
6bdf: f0 05 beq :DrawNext ;yes, branch
6be1: c6 27 dec txsc_char_delay ;no, update countdown
6be3: b8 clv ; and bail
6be4: 50 48 bvc :Return ;(always)
6be6: a6 25 :DrawNext ldx txsc_out_idx ;get text scroll item index
6be8: bd 42 6c lda txsc_spec_tbl,x ;get text scroll item
6beb: c9 00 cmp #$00 ;something special?
6bed: d0 09 bne :SpecialMsg ;yes, branch
6bef: bd 38 6c lda txsc_msg_tbl,x ;no, just get message index
6bf2: 20 7f 6c jsr TxscDrawChar ;draw the next character
6bf5: b8 clv
6bf6: 50 1b bvc :CharDrawn ;(always)
6bf8: bd 38 6c :SpecialMsg lda txsc_msg_tbl,x ;get ZP address from table
6bfb: 85 06 sta ]thing_ptr? ;use as low byte of pointer
6bfd: a0 00 ldy #$00
6bff: 84 07 sty ]thing_ptr?+1 ;set high byte of pointer to zero
6c01: b1 06 lda (]thing_ptr?),y ;get value from ZP
6c03: bc 42 6c ldy txsc_spec_tbl,x ;get special index
6c06: c0 02 cpy #$02 ;is it special #2 (play cost)?
6c08: d0 06 bne :Not2 ;no, branch
6c0a: 20 7f 6c jsr TxscDrawChar ;draw message
6c0d: b8 clv
6c0e: 50 03 bvc :CharDrawn ;(always)
6c10: 20 56 6c :Not2 jsr TxscPrintBcd ;handles special #1 (number of credits)
;
6c13: a5 27 :CharDrawn lda txsc_char_delay ;check the end-of-message flag
6c15: c9 ff cmp #$ff
6c17: d0 15 bne :Return ;not at end yet, keep going
; We reached the end of the previous message. Pick a new one.
6c19: a6 25 ldx txsc_out_idx ;get table index
6c1b: bd 4c 6c lda txsc_inter_del_tbl,x ;get inter-message spacing
6c1e: 85 27 sta txsc_char_delay ;set delay
6c20: e8 inx ;advance to next table entry
6c21: bd 42 6c lda txsc_spec_tbl,x ;check next "special" value
6c24: c9 03 cmp #$03 ;is it 3 (end of list)?
6c26: d0 04 bne :Not3 ;no, branch
6c28: bd 38 6c lda txsc_msg_tbl,x ;special #3: get value from msg table
6c2b: aa tax ; and use that as next entry index
6c2c: 86 25 :Not3 stx txsc_out_idx ;update table index
6c2e: 60 :Return rts
;
; Message indices for coins-per-play settings.
;
play_cost_msg_tbl
6c2f: 25 .dd1 $25 ;"FREE PLAY"
6c30: 0a .dd1 $0a ;"1 COIN 2 PLAYS"
6c31: 0b .dd1 $0b ;"1 COIN 1 PLAY"
6c32: 0c .dd1 $0c ;"2 COINS 1 PLAY"
;
; Clears the credit count maintained by the text scroll code. This ensures that
; the current count will be different, so the code will clear the message area
; and restart the message stream.
;
6c33: a9 ff TxscResetStream lda #$ff
6c35: 85 29 sta txsc_credits
6c37: 60 rts
;
; Text scroll message indices. Special entries are noted as non-zero values in
; the second table. For non-special entries, this is the index of the message
; to display; for special entries it acts as a parameter. There are two
; separate series of messages here, one for zero credits, one for nonzero
; credits.
;
; 0: $00/$03="PRESS START"
; 1: $00/$0d="CREDITS:"
; 2: $01/$29=number of credits as BCD value, in ZP location $29
; 3: $00/$1e="ATARI (C)(P) 1980"
; 4: $03/$00=end of list, start over at #0
; 5: $00/$0e="GAME OVER"
; 6: $00/$04="INSERT COINS"
; 7: $02/$28=coins-per-play message, in ZP location $28
; 8: $00/$1e="ATARI (C)(P) 1980"
; 9: $03/$05=end of list, start over at #5
;
6c38: 03 0d 29 1e+ txsc_msg_tbl .bulk $03,$0d,$29,$1e,$00,$0e,$04,$28,$1e,$05
6c42: 00 00 01 00+ txsc_spec_tbl .bulk $00,$00,$01,$00,$03,$00,$00,$02,$00,$03
;
; Text scroll delay between messages. This is the number of pixels that the
; message should be scrolled before moving on to the next message. This is done
; to leave some space between them.
;
; These are all 32 (4 spaces) except for the second entry, which is a single
; space following "CREDITS:".
;
txsc_inter_del_tbl
6c4c: 20 08 20 20+ .bulk $20,$08,$20,$20,$20,$20,$20,$20,$20,$20
;
; Draws a one- or two-digit BCD value in the text scroll area.
;
6c56: a4 26 TxscPrintBcd ldy txsc_msg_offset ;get offset within message
6c58: f0 0a beq :FirstChar ;first char, branch
6c5a: a0 ff ldy #$ff
6c5c: 84 27 sty txsc_char_delay ;signal end of message
6c5e: c8 iny
6c5f: 84 26 sty txsc_msg_offset ;reset string offset to zero
6c61: b8 clv
6c62: 50 0e bvc :PrintNibble ;print low nibble
6c64: a0 07 :FirstChar ldy #$07
6c66: 84 27 sty txsc_char_delay ;set delay to 8 (pixels per glyph)
6c68: e6 26 inc txsc_msg_offset ;advance to next char (actually lower nibble)
6c6a: 4a lsr A ;shift high nibble into low
6c6b: 4a lsr A
6c6c: 4a lsr A
6c6d: 4a lsr A
6c6e: d0 02 bne :PrintNibble ;if nonzero, print it
6c70: a9 ff lda #$ff ;otherwise, skip it
6c72: 29 0f :PrintNibble and #%00001111 ;strip high bits
6c74: c9 0a cmp #10 ;is it >= 10?
6c76: b0 06 bcs :Return ;yes, bail
6c78: 18 clc
6c79: 69 30 adc #‘0’ ;convert to ASCII digit
6c7b: 20 9c 6c jsr DrawTxscChar2 ;draw digit
6c7e: 60 :Return rts
;
; Draws a character for the scrolling text display.
;
; On entry:
; A-reg: message index
; $26: char offset within string
;
; On exit:
; $26: incremented by one, or reset to zero
; $27: $07 if more chars remain, or $ff if done
;
• Clear variables
]xc .var $00 {addr/1}
]yc .var $01 {addr/1}
]tmp .var $1d {addr/1}
6c7f: 85 1d TxscDrawChar sta ]tmp ;store index in ZP
6c81: 0a asl A ;double it
6c82: 18 clc
6c83: 65 1d adc ]tmp ;add to itself
6c85: a8 tay ;put index * 3 in Y-reg
6c86: 20 26 6a jsr GetMessageN ;get pointer to string in $1d-1e
]char_ptr .var $1d {addr/2}
6c89: a4 26 ldy txsc_msg_offset ;get current offset
6c8b: e6 26 inc txsc_msg_offset ;increment for next time
6c8d: a9 07 lda #$07 ;scroll 8x before outputting next char
6c8f: 85 27 sta txsc_char_delay ;set this to 7
6c91: b1 1d lda (]char_ptr),y ;get next character
6c93: 10 07 bpl DrawTxscChar2 ;branch if not end of string
6c95: a0 ff ldy #$ff ;reached end of string, reset stuff
6c97: 84 27 sty txsc_char_delay ;set to $ff
6c99: c8 iny
6c9a: 84 26 sty txsc_msg_offset ;set to $00
; Draw char in bottom-right corner.
6c9c: 20 9d 69 DrawTxscChar2 jsr GetGlyphPtr ;get ptr in $0d-0e
6c9f: a9 fc lda #252 ;X pos (left pixels will be at X=248)
6ca1: 85 00 sta ]xc
6ca3: a9 03 lda #3 ;Y pos (top pixels will be at Y=7)
6ca5: 85 01 sta ]yc
6ca7: a9 00 lda #$00
6ca9: 85 08 sta preserve_bg_flag ;don't preserve background
6cab: a9 00 lda #$00 ;fg = color 0
6cad: 85 0b sta draw_fg_color
6caf: a9 20 lda #%00100000 ;bg = color 1
6cb1: 85 0c sta draw_bg_color
6cb3: 4c 27 66 jmp DrawGlyphPtrNoSc ;draw single glyph
;
; Sets the 3rd pixel bit for all pixels in the bottom 8 lines of the screen.
; Does not affect the 2bpp memory.
;
; This area is used for the LOW / OUT messages during the game, and scrolling
; text in "attract" mode. The effect of this function is to erase the area to
; the same color used for the city backdrop.
;
6cb6: a0 00 SetBottomBit3 ldy #$00
6cb8: a9 ff lda #$ff ;set all bits
6cba: 99 01 04 :Loop sta VIDEO_RAM_3+$201,y ;lines 223-226
6cbd: 99 01 05 sta VIDEO_RAM_3+$301,y ;lines 227-230
6cc0: c8 iny ;increment twice, because even-numbered bytes
6cc1: c8 iny ; affect different lines
6cc2: d0 f6 bne :Loop
6cc4: 60 rts
;
; Message glyph size stretch factor, stored as $XY. X is the horizontal
; multiplier, Y is the vertical multiplier.
;
; For example, the "MISSILE COMMAND" logo (messages $1c/$1d) uses $44 to expand
; the glyphs 4x vertically and horizontally, while "THE END" (message $08) is
; stretched 3x horizontally and 10x vertically.
msg_glyph_size_tbl
6cc5: 11 11 11 11+ .bulk $11,$11,$11,$11,$11,$11,$11,$11
6ccd: 3a 21 11 11+ .bulk $3a,$21,$11,$11,$11,$11,$11,$11
6cd5: 11 11 11 11+ .bulk $11,$11,$11,$11,$11,$11,$11,$11
6cdd: 11 11 11 11+ .bulk $11,$11,$11,$11,$44,$44,$11,$11
6ce5: 11 11 11 11+ .bulk $11,$11,$11,$11,$11,$11,$11,$11
;
; Message vertical position. Specifies the top position. Note 0 is at the
; bottom of the screen.
;
; (Drawing off the bottom of the screen will cause the system to reset.)
msg_vertical_tbl
6ced: 90 d0 03 03+ .bulk $90,$d0,$03,$03,$03,$40,$70,$a0
6cf5: a0 80 40 40+ .bulk $a0,$80,$40,$40,$40,$03,$a0,$03
6cfd: 03 03 70 60+ .bulk $03,$03,$70,$60,$50,$38,$03,$03
6d05: 03 58 58 58+ .bulk $03,$58,$58,$58,$a0,$70,$03,$70
6d0d: 90 70 90 80+ .bulk $90,$70,$90,$80,$80,$40,$34,$80
;
6d15: 95 .dd1 $95 ;checksum byte
;
; Pointers to message strings. Each entry is 3 bytes: the horizontal screen
; offset, followed by a 16-bit pointer to the text. The vertical position and
; size multipliers for each message come from the tables above.
;
6d16: 60 msgs_fr .dd1 $60 ;French
6d17: d2 6f .dd2 L6FD2
6d19: 40 .dd1 $40
6d1a: bb 70 .dd2 L70BB
6d1c: 06 .dd1 $06
6d1d: d8 6f .dd2 L6FD8
6d1f: 90 .dd1 $90
6d20: e5 6f .dd2 L6FE5
6d22: 78 .dd1 $78
6d23: f6 6f .dd2 L6FF6
6d25: 5c .dd1 $5c
6d26: 0b 70 .dd2 L700B
6d28: 68 .dd1 $68
6d29: 22 70 .dd2 L7022
6d2b: 50 .dd1 $50
6d2c: 16 70 .dd2 L7016
6d2e: 60 .dd1 $60
6d2f: 2a 70 .dd2 L702A
6d31: 10 .dd1 $10
6d32: 5f 70 .dd2 L705F
6d34: 40 .dd1 $40
6d35: 2d 70 .dd2 L702D
6d37: 40 .dd1 $40
6d38: 3e 70 .dd2 L703E
6d3a: 40 .dd1 $40
6d3b: 4e 70 .dd2 L704E
6d3d: b0 .dd1 $b0
6d3e: f6 6e .dd2 L6EF6
6d40: 54 .dd1 $54
6d41: d8 6f .dd2 L6FD8
6d43: 04 .dd1 $04
6d44: e1 70 .dd2 L70E1
6d46: 70 .dd1 $70
6d47: e1 70 .dd2 L70E1
6d49: e4 .dd1 $e4
6d4a: e1 70 .dd2 L70E1
6d4c: 24 .dd1 $24
6d4d: 6e 70 .dd2 L706E
6d4f: 04 .dd1 $04
6d50: 86 70 .dd2 L7086
6d52: 04 .dd1 $04
6d53: a1 70 .dd2 L70A1
6d55: 34 .dd1 $34
6d56: cb 70 .dd2 L70CB
6d58: 0c .dd1 $0c
6d59: de 70 .dd2 L70DE
6d5b: 74 .dd1 $74
6d5c: de 70 .dd2 L70DE
6d5e: e8 .dd1 $e8
6d5f: de 70 .dd2 L70DE
6d61: 88 .dd1 $88
6d62: e5 70 .dd2 L70E5
6d64: c8 .dd1 $c8
6d65: 24 70 .dd2 L7022+2
6d67: 28 .dd1 $28
6d68: 0b 70 .dd2 L700B
6d6a: 54 msgs_de .dd1 $54 ;German
6d6b: e6 70 .dd2 L70E6
6d6d: 50 .dd1 $50
6d6e: d0 71 .dd2 L71D0
6d70: 06 .dd1 $06
6d71: ed 70 .dd2 L70ED
6d73: 90 .dd1 $90
6d74: f6 70 .dd2 L70F6
6d76: 78 .dd1 $78
6d77: 09 71 .dd2 L7109
6d79: 5c .dd1 $5c
6d7a: 17 71 .dd2 L7117
6d7c: 68 .dd1 $68
6d7d: 2c 71 .dd2 L712C
6d7f: 58 .dd1 $58
6d80: 21 71 .dd2 L7121
6d82: 54 .dd1 $54
6d83: 34 71 .dd2 L7134
6d85: 14 .dd1 $14
6d86: 6e 71 .dd2 L716E
6d88: 40 .dd1 $40
6d89: 38 71 .dd2 L7138
6d8b: 40 .dd1 $40
6d8c: 47 71 .dd2 L7147
6d8e: 40 .dd1 $40
6d8f: 56 71 .dd2 L7156
6d91: b0 .dd1 $b0
6d92: 66 71 .dd2 L7166
6d94: 58 .dd1 $58
6d95: ed 70 .dd2 L70ED
6d97: 04 .dd1 $04
6d98: f7 71 .dd2 L71F7
6d9a: 70 .dd1 $70
6d9b: f7 71 .dd2 L71F7
6d9d: e4 .dd1 $e4
6d9e: f7 71 .dd2 L71F7
6da0: 14 .dd1 $14
6da1: 7c 71 .dd2 L717C
6da3: 04 .dd1 $04
6da4: 98 71 .dd2 L7198
6da6: 04 .dd1 $04
6da7: b7 71 .dd2 L71B7
6da9: 30 .dd1 $30
6daa: dc 71 .dd2 L71DC
6dac: 04 .dd1 $04
6dad: f2 71 .dd2 L71F2
6daf: 6c .dd1 $6c
6db0: f2 71 .dd2 L71F2
6db2: dc .dd1 $dc
6db3: f2 71 .dd2 L71F2
6db5: 70 .dd1 $70
6db6: fb 71 .dd2 L71FB
6db8: c8 .dd1 $c8
6db9: 2e 71 .dd2 L712C+2
6dbb: 18 .dd1 $18
6dbc: 17 71 .dd2 L7117
6dbe: 5c msgs_es .dd1 $5c ;Spanish
6dbf: ff 71 .dd2 L71FF
6dc1: 64 .dd1 $64
6dc2: eb 72 .dd2 L72EB
6dc4: 06 .dd1 $06
6dc5: 06 72 .dd2 L7206
6dc7: 90 .dd1 $90
6dc8: 15 72 .dd2 L7215
6dca: 78 .dd1 $78
6dcb: 21 72 .dd2 L7221
6dcd: 5c .dd1 $5c
6dce: 2f 72 .dd2 L722F
6dd0: 68 .dd1 $68
6dd1: 51 72 .dd2 L7251
6dd3: 30 .dd1 $30
6dd4: 3b 72 .dd2 L723B
6dd6: 60 .dd1 $60
6dd7: 2a 70 .dd2 L702A
6dd9: 24 .dd1 $24
6dda: 94 72 .dd2 L7294
6ddc: 40 .dd1 $40
6ddd: 59 72 .dd2 L7259
6ddf: 40 .dd1 $40
6de0: 6a 72 .dd2 L726A
6de2: 40 .dd1 $40
6de3: 7a 72 .dd2 L727A
6de5: b0 .dd1 $b0
6de6: 8b 72 .dd2 L728B
6de8: 48 .dd1 $48
6de9: 06 72 .dd2 L7206
6deb: 0c .dd1 $0c
6dec: 0b 73 .dd2 L730B
6dee: 74 .dd1 $74
6def: 0b 73 .dd2 L730B
6df1: e8 .dd1 $e8
6df2: 0b 73 .dd2 L730B
6df4: 38 .dd1 $38
6df5: a0 72 .dd2 L72A0
6df7: 04 .dd1 $04
6df8: b3 72 .dd2 L72B3
6dfa: 04 .dd1 $04
6dfb: d3 72 .dd2 L72D3
6dfd: 30 .dd1 $30
6dfe: f2 72 .dd2 L72F2
6e00: 04 .dd1 $04
6e01: 07 73 .dd2 L7307
6e03: 70 .dd1 $70
6e04: 07 73 .dd2 L7307
6e06: e4 .dd1 $e4
6e07: 07 73 .dd2 L7307
6e09: 70 .dd1 $70
6e0a: 0e 73 .dd2 L730E
6e0c: c8 .dd1 $c8
6e0d: 53 72 .dd2 L7251+2
6e0f: 08 .dd1 $08
6e10: 2f 72 .dd2 L722F
6e12: 60 msgs_en .dd1 96 ;$00 "PLAYER"
6e13: 8a 6e .dd2 L6E8A
6e15: 54 .dd1 84 ;$01 "HIGH SCORES"
6e16: 78 6f .dd2 L6F78
6e18: 06 .dd1 6 ;$02 "GAME OVER"
6e19: 90 6e .dd2 L6E90
6e1b: 90 .dd1 144 ;$03 "PRESS START"
6e1c: 99 6e .dd2 L6E99
6e1e: 78 .dd1 120 ;$04 "INSERT COINS"
6e1f: a4 6e .dd2 L6EA4
6e21: 5c .dd1 92 ;$05 "BONUS CITY"
6e22: b0 6e .dd2 L6EB0
6e24: 68 .dd1 104 ;$06 "X POINTS"
6e25: 22 70 .dd2 L7022
6e27: 50 .dd1 80 ;$07 "BONUS POINTS"
6e28: ba 6e .dd2 L6EBA
6e2a: 36 .dd1 54 ;$08 "THE END"
6e2b: c6 6e .dd2 L6EC6
6e2d: 2c .dd1 44 ;$09 "GREAT SCORE"
6e2e: 20 6f .dd2 L6F20
6e30: 40 .dd1 64 ;$0a "1 COIN 2 PLAYS"
6e31: cd 6e .dd2 L6ECD
6e33: 40 .dd1 64 ;$0b "1 COIN 1 PLAY"
6e34: db 6e .dd2 L6EDB
6e36: 40 .dd1 64 ;$0c "2 COINS 1 PLAY"
6e37: e8 6e .dd2 L6EE8
6e39: b0 .dd1 176 ;$0d "CREDITS:"
6e3a: f6 6e .dd2 L6EF6
6e3c: 5c .dd1 92 ;$0e "GAME OVER"
6e3d: 90 6e .dd2 L6E90
6e3f: 0c .dd1 12 ;$0f "OUT"
6e40: a6 6f .dd2 L6FA6
6e42: 74 .dd1 116 ;$10 "OUT"
6e43: a6 6f .dd2 L6FA6
6e45: e8 .dd1 232 ;$11 "OUT"
6e46: a6 6f .dd2 L6FA6
6e48: 38 .dd1 56 ;$12 "ENTER YOUR INITIALS"
6e49: 2b 6f .dd2 L6F2B
6e4b: 04 .dd1 4 ;$13 "SPIN BALL TO CHANGE LETTERS"
6e4c: 3e 6f .dd2 L6F3E
6e4e: 04 .dd1 4 ;$14 "PRESS ANY FIRE SWITCH TO SELECT"
6e4f: 59 6f .dd2 L6F59
6e51: 38 .dd1 56 ;$15 "DEFEND CITIES"
6e52: 83 6f .dd2 L6F83
6e54: 0c .dd1 12 ;$16 "LOW"
6e55: 96 6f .dd2 L6F96
6e57: 74 .dd1 116 ;$17 "LOW"
6e58: 96 6f .dd2 L6F96
6e5a: e8 .dd1 232 ;$18 "LOW"
6e5b: 96 6f .dd2 L6F96
6e5d: 68 .dd1 104 ;$19 "EVERY"
6e5e: c4 6f .dd2 L6FC4
6e60: c8 .dd1 200 ;$1a "POINTS"
6e61: 24 70 .dd2 L7022+2
6e63: 10 .dd1 16 ;$1b "BONUS CITY"
6e64: b0 6e .dd2 L6EB0
; Start of non-localized messages.
6e66: 20 .dd1 32 ;$1c "MISSILE"
6e67: b6 6f .dd2 L6FB6
6e69: 20 .dd1 32 ;$1d "COMMAND"
6e6a: bd 6f .dd2 L6FBD
6e6c: 50 .dd1 80 ;$1e "ATARI (C)(P) 1980"
6e6d: a9 6f .dd2 L6FA9
6e6f: 5c .dd1 92 ;$1f "RAM OK"
6e70: fe 6e .dd2 L6EFE
6e72: 5c .dd1 92 ;$20 "ROM OK"
6e73: 04 6f .dd2 L6F04
6e75: 5c .dd1 92 ;$21 "BAD RAM"
6e76: 0a 6f .dd2 L6F0A
6e78: 5c .dd1 92 ;$22 "BAD ROM"
6e79: 11 6f .dd2 L6F11
6e7b: 5c .dd1 92 ;$23 "BAD MAP"
6e7c: 9f 6f .dd2 L6F9F
6e7e: 5c .dd1 92 ;$24 "MAP OK"
6e7f: 99 6f .dd2 L6F99
6e81: 40 .dd1 64 ;$25 "FREE PLAY"
6e82: c9 6f .dd2 L6FC9
6e84: 50 .dd1 80 ;$26 "CITIES"
6e85: 90 6f .dd2 L6F83+13
6e87: 10 .dd1 16 ;$27 "BAD CHIP"
6e88: 18 6f .dd2 L6F18
; Localized strings, starting with English.
6e8a: 50 4c 41 59+ L6E8A .dstr ‘PLAYER’
6e90: 47 41 4d 45+ L6E90 .dstr ‘GAME OVER’
6e99: 50 52 45 53+ L6E99 .dstr ‘PRESS START’
6ea4: 49 4e 53 45+ L6EA4 .dstr ‘INSERT COINS’
6eb0: 42 4f 4e 55+ L6EB0 .dstr ‘BONUS CITY’
6eba: 42 4f 4e 55+ L6EBA .dstr ‘BONUS POINTS’
6ec6: 54 48 45 20+ L6EC6 .dstr ‘THE END’
6ecd: 31 20 43 4f+ L6ECD .dstr ‘1 COIN 2 PLAYS’
6edb: 31 20 43 4f+ L6EDB .dstr ‘1 COIN 1 PLAY’
6ee8: 32 20 43 4f+ L6EE8 .dstr ‘2 COINS 1 PLAY’
6ef6: 43 52 45 44+ L6EF6 .dstr ‘CREDITS:’
6efe: 52 41 4d 20+ L6EFE .dstr ‘RAM OK’
6f04: 52 4f 4d 20+ L6F04 .dstr ‘ROM OK’
6f0a: 42 41 44 20+ L6F0A .dstr ‘BAD RAM’
6f11: 42 41 44 20+ L6F11 .dstr ‘BAD ROM’
6f18: 42 41 44 20+ L6F18 .dstr ‘BAD CHIP’
6f20: 47 52 45 41+ L6F20 .dstr ‘GREAT SCORE’
6f2b: 45 4e 54 45+ L6F2B .dstr ‘ENTER YOUR INITIALS’
6f3e: 53 50 49 4e+ L6F3E .dstr ‘SPIN BALL TO CHANGE LETTERS’
6f59: 50 52 45 53+ L6F59 .dstr ‘PRESS ANY FIRE SWITCH TO SELECT’
6f78: 48 49 47 48+ L6F78 .dstr ‘HIGH SCORES’
6f83: 44 45 46 45+ L6F83 .dstr ‘DEFEND CITIES’
6f96: 4c 4f d7 L6F96 .dstr ‘LOW’
6f99: 4d 41 50 20+ L6F99 .dstr ‘MAP OK’
6f9f: 42 41 44 20+ L6F9F .dstr ‘BAD MAP’
6fa6: 4f 55 d4 L6FA6 .dstr ‘OUT’
6fa9: 41 54 41 52+ L6FA9 .dstr ‘ATARI @? 1980’
6fb6: 4d 49 53 53+ L6FB6 .dstr ‘MISSILE’
6fbd: 43 4f 4d 4d+ L6FBD .dstr ‘COMMAND’
6fc4: 45 56 45 52+ L6FC4 .dstr ‘EVERY’
6fc9: 46 52 45 45+ L6FC9 .dstr ‘FREE PLAY’
; French
6fd2: 4a 4f 55 45+ L6FD2 .dstr ‘JOUEUR’
6fd8: 46 49 4e 20+ L6FD8 .dstr ‘FIN DE PARTIE’
6fe5: 41 50 50 55+ L6FE5 .dstr ‘APPUYEZ SUR START’
6ff6: 49 4e 54 52+ L6FF6 .dstr ‘INTRODUIRE LES PIECES’
700b: 42 4f 4e 55+ L700B .dstr ‘BONUS VILLE’
7016: 42 4f 4e 55+ L7016 .dstr ‘BONUS POINTS’
7022: 58 20 50 4f+ L7022 .dstr ‘X POINTS’
702a: 46 49 ce L702A .dstr ‘FIN’
702d: 31 20 50 49+ L702D .dstr ‘1 PIECE 2 JOUEURS’
703e: 31 20 50 49+ L703E .dstr ‘1 PIECE 1 JOUEUR’
704e: 32 20 50 49+ L704E .dstr ‘2 PIECES 1 JOUEUR’
705f: 53 50 4c 45+ L705F .dstr ‘SPLENDIDE SCORE’
706e: 53 56 50 20+ L706E .dstr ‘SVP ENTREZ VOS INITIALES’
7086: 54 4f 55 52+ L7086 .dstr ‘TOURNEZ BOULE POUR INITIALE’
70a1: 50 4f 55 53+ L70A1 .dstr ‘POUSSEZ FEU QUAND CORRECTE’
70bb: 4d 45 49 4c+ L70BB .dstr ‘MEILLEURS SCORES’
70cb: 44 45 46 45+ L70CB .dstr ‘DEFENSEZ VILLES’
70de: 42 41 d3 L70DE .dstr ‘BAS’
70e1: 56 49 44 c5 L70E1 .dstr ‘VIDE’
70e5: c1 L70E5 .dstr ‘A’
; German
70e6: 53 50 49 45+ L70E6 .dstr ‘SPIELER’
70ed: 53 50 49 45+ L70ED .dstr ‘SPIELENDE’
70f6: 53 54 41 52+ L70F6 .dstr ‘STARTKN$PFE DR%CKEN’
7109: 47 45 4c 44+ L7109 .dstr ‘GELD EINWERFEN’
7117: 42 4f 4e 55+ L7117 .dstr ‘BONUSSTADT’
7121: 42 4f 4e 55+ L7121 .dstr ‘BONUSPUNKTE’
712c: 58 20 50 55+ L712C .dstr ‘X PUNKTE’
7134: 45 4e 44 c5 L7134 .dstr ‘ENDE’
7138: 31 20 4d 25+ L7138 .dstr ‘1 M%NZ 2 SPIELE’
7147: 31 20 4d 25+ L7147 .dstr ‘1 M%NZE 1 SPIEL’
7156: 32 20 4d 25+ L7156 .dstr ‘2 M%NZEN 1 SPIEL’
7166: 4b 52 45 44+ L7166 .dstr ‘KREDITE:’
716e: 50 52 49 4d+ L716E .dstr ‘PRIMA ERGEBNIS’
717c: 47 45 42 45+ L717C .dstr ‘GEBEN SIE IHRE INITIALEN EIN’
7198: 42 41 4c 4c+ L7198 .dstr ‘BALL DREHEN F%R ALLE BUCHSTABEN’
71b7: 46 49 52 45+ L71B7 .dstr ‘FIRE DR%CKEN WENN RICHTIG’
71d0: 48 24 43 48+ L71D0 .dstr ‘H$CHSTZAHLEN’
71dc: 53 54 23 44+ L71DC .dstr ‘ST#DTE VERTEIDIGEN’
71f2: 57 45 4e 49+ L71F2 .dstr ‘WENIG’
71f7: 4c 45 45 d2 L71F7 .dstr ‘LEER’
71fb: 4a 45 44 c5 L71FB .dstr ‘JEDE’
; Spanish
71ff: 4a 55 47 41+ L71FF .dstr ‘JUGADOR’
7206: 4a 55 45 47+ L7206 .dstr ‘JUEGO TERMINADO’
7215: 50 55 4c 53+ L7215 .dstr ‘PULSAR START’
7221: 49 4e 53 45+ L7221 .dstr ‘INSERTE FICHAS’
722f: 43 49 55 44+ L722F .dstr ‘CIUDAD EXTRA’
723b: 42 4f 4e 49+ L723B .dstr ‘BONIFICACION DE PUNTOS’
7251: 58 20 50 55+ L7251 .dstr ‘X PUNTOS’
7259: 31 20 4d 4f+ L7259 .dstr ‘1 MONEDA 2 JUEGOS’
726a: 31 20 4d 4f+ L726A .dstr ‘1 MONEDA 1 JUEGO’
727a: 32 20 4d 4f+ L727A .dstr ‘2 MONEDAS 1 JUEGO’
728b: 43 52 45 44+ L728B .dstr ‘CREDITOS:’
7294: 47 52 41 4e+ L7294 .dstr ‘GRAN PUNTAJE’
72a0: 45 4e 54 52+ L72A0 .dstr ‘ENTRE SUS INICIALES’
72b3: 47 49 52 45+ L72B3 .dstr ‘GIRE LA BOLA PARA CAMBIAR LETRAS’
72d3: 4f 50 52 49+ L72D3 .dstr ‘OPRIMA FIRE POR LA LETRA’
72eb: 52 45 43 4f+ L72EB .dstr ‘RECORDS’
72f2: 44 45 46 49+ L72F2 .dstr ‘DEFIENDA CIUDADES’
7307: 50 4f 43 cf L7307 .dstr ‘POCO’
730b: 53 49 ce L730B .dstr ‘SIN’
730e: 43 41 44 c1 L730E .dstr ‘CADA’
;
; 8x8 1-bit bitmaps, primarily font glyphs. The screen is organized with line 0
; at the bottom, and the glyphs are stored in the same order. The characters
; leave a blank row at the top and a blank column on the right.
;
;
; Blank bitmap, used for unknown characters and suppressed leading '0's.
;
7312: 00 00 00 00+ glyphs_4 .bulk $00,$00,$00,$00,$00,$00,$00,$00
;
; Glyphs '0' - '9'.
731a: 38 44 c6 c6+ glyphs_0 .bulk $38,$44,$c6,$c6,$c6,$44,$38,$00
7322: fc 30 30 30+ .bulk $fc,$30,$30,$30,$30,$70,$30,$00
732a: fe e0 78 3c+ .bulk $fe,$e0,$78,$3c,$0e,$c6,$7c,$00
7332: 7c c6 06 3c+ .bulk $7c,$c6,$06,$3c,$18,$0c,$7e,$00
733a: 0c 0c fe cc+ .bulk $0c,$0c,$fe,$cc,$6c,$3c,$1c,$00
7342: 7c c6 06 06+ .bulk $7c,$c6,$06,$06,$fc,$c0,$fc,$00
734a: 7c c6 c6 fc+ .bulk $7c,$c6,$c6,$fc,$c0,$60,$3c,$00
7352: 30 30 30 18+ .bulk $30,$30,$30,$18,$0c,$c6,$fe,$00
735a: 7c 86 9e 78+ .bulk $7c,$86,$9e,$78,$e4,$c4,$78,$00
7362: 78 0c 06 7e+ glyphs_2 .bulk $78,$0c,$06,$7e,$c6,$c6,$7c,$00
;
; Glyphs 'A' - 'Z'. Technically it's '@' - 'Z', but '@' gets remapped to a
; separate table, which is why the table's label is on the previous glyph.
736a: c6 c6 fe c6+ .bulk $c6,$c6,$fe,$c6,$c6,$6c,$38,$00 ;'A'=1
7372: fc c6 c6 fc+ .bulk $fc,$c6,$c6,$fc,$c6,$c6,$fc,$00
737a: 3c 66 c0 c0+ .bulk $3c,$66,$c0,$c0,$c0,$66,$3c,$00
7382: f8 cc c6 c6+ .bulk $f8,$cc,$c6,$c6,$c6,$cc,$f8,$00
738a: fc c0 c0 f8+ .bulk $fc,$c0,$c0,$f8,$c0,$c0,$fc,$00
7392: c0 c0 c0 fc+ .bulk $c0,$c0,$c0,$fc,$c0,$c0,$fe,$00
739a: 3e 66 c6 ce+ .bulk $3e,$66,$c6,$ce,$c0,$60,$3e,$00
73a2: c6 c6 c6 fe+ .bulk $c6,$c6,$c6,$fe,$c6,$c6,$c6,$00
73aa: fc 30 30 30+ .bulk $fc,$30,$30,$30,$30,$30,$fc,$00
73b2: 7c c6 06 06+ .bulk $7c,$c6,$06,$06,$06,$06,$06,$00
73ba: ce dc f8 f0+ .bulk $ce,$dc,$f8,$f0,$d8,$cc,$c6,$00
73c2: fc c0 c0 c0+ .bulk $fc,$c0,$c0,$c0,$c0,$c0,$c0,$00
73ca: c6 c6 d6 fe+ .bulk $c6,$c6,$d6,$fe,$fe,$ee,$c6,$00
73d2: c6 ce de fe+ .bulk $c6,$ce,$de,$fe,$f6,$e6,$c6,$00
73da: 7c c6 c6 c6+ .bulk $7c,$c6,$c6,$c6,$c6,$c6,$7c,$00
73e2: c0 c0 fc c6+ .bulk $c0,$c0,$fc,$c6,$c6,$c6,$fc,$00
73ea: 7a cc de c6+ .bulk $7a,$cc,$de,$c6,$c6,$c6,$7c,$00
73f2: ce dc f8 ce+ .bulk $ce,$dc,$f8,$ce,$c6,$c6,$fc,$00
73fa: 7c c6 06 7c+ .bulk $7c,$c6,$06,$7c,$c0,$c6,$7c,$00
7402: 30 30 30 30+ .bulk $30,$30,$30,$30,$30,$30,$fc,$00
740a: 7c c6 c6 c6+ .bulk $7c,$c6,$c6,$c6,$c6,$c6,$c6,$00
7412: 10 38 6c ee+ .bulk $10,$38,$6c,$ee,$c6,$c6,$c6,$00
741a: c6 ee fe fe+ .bulk $c6,$ee,$fe,$fe,$d6,$c6,$c6,$00
7422: c6 ee 7c 38+ .bulk $c6,$ee,$7c,$38,$7c,$ee,$c6,$00
742a: 30 30 30 78+ .bulk $30,$30,$30,$78,$cc,$cc,$cc,$00
7432: fe e0 70 38+ .bulk $fe,$e0,$70,$38,$1c,$0e,$fe,$00 ;'Z'=26
;
; Glyphs for symbols. ASCII ' ' and ':' are represented, and a few ('#', '$',
; '%') are remapped to A/O/U with umlauts. The latter can be found in the
; German-language strings.
;
; The arrows used to indicate player 1 vs. player 2 are here, as is the downward
; arrow for the "protect cities" instruction.
;
; This also has a set of four entries for the cities. The two "far" entries are
; drawn first in color #7, then partially drawn over by the "near" entries in
; color #3.
743a: 00 00 00 00+ glyphs_6 .bulk $00,$00,$00,$00,$00,$00,$00,$00 ;' ' (#27) - duplicate of entry in glyphs_4
7442: 18 3c 7e ff+ .bulk $18,$3c,$7e,$ff,$ff,$bd,$3c,$3c ;down arrow (#28)
744a: 7f ff 7f 7e+ .bulk $7f,$ff,$7f,$7e,$34,$30,$20,$00 ;city left/far
7452: 7c 37 25 00+ .bulk $7c,$37,$25,$00,$00,$00,$00,$00 ;city left/near
745a: fe ff fe fe+ .bulk $fe,$ff,$fe,$fe,$fa,$50,$10,$10 ;city right/far
7462: 3e 6c c8 c0+ .bulk $3e,$6c,$c8,$c0,$80,$00,$00,$00 ;city right/near
746a: 1c 38 7f ff+ .bulk $1c,$38,$7f,$ff,$ff,$7f,$38,$1c ;left arrow (#33)
7472: 38 1c fe ff+ .bulk $38,$1c,$fe,$ff,$ff,$fe,$1c,$38 ;right arrow (#34)
747a: 3c 42 99 91+ .bulk $3c,$42,$99,$91,$99,$42,$3c,$00 ;'@' -> (C) copyright symbol
7482: 30 30 00 30+ .bulk $30,$30,$00,$30,$30,$00,$00,$00 ;':'
748a: 3c 52 9d 95+ .bulk $3c,$52,$9d,$95,$9d,$42,$3c,$00 ;'?' -> (P) sound copyright symbol
7492: c6 fe c6 6c+ .bulk $c6,$fe,$c6,$6c,$38,$10,$44,$00 ;'#' -> A + umlaut
749a: 7c c6 c6 c6+ .bulk $7c,$c6,$c6,$c6,$7c,$00,$28,$00 ;'$' -> O + umlaut
74a2: 7c c6 c6 c6+ .bulk $7c,$c6,$c6,$c6,$c6,$00,$28,$00 ;'%' -> U + umlaut
;
; Prints "bonus city every N points". If the machine is configured for no bonus
; cities, nothing is displayed.
;
PrintBonusCityPts
74aa: a5 f4 lda r8_irq_inv ;get R8 switches
74ac: 29 70 and #%01110000 ;mask to get the bonus city setting
74ae: c9 70 cmp #%01110000 ;are bonus cities disabled?
74b0: f0 2e beq :Return ;yes, bail
74b2: a9 1b lda #$1b
74b4: 20 51 6a jsr PrintMessage ;"BONUS CITY"
74b7: a9 19 lda #$19
74b9: 20 51 6a jsr PrintMessage ;"EVERY"
74bc: a9 1a lda #$1a
74be: 20 51 6a jsr PrintMessage ;"POINTS"
74c1: a9 00 lda #$00
74c3: 85 d1 sta bcd3_acc ;set low byte of score to zero
74c5: a5 f4 lda r8_irq_inv ;get R8 switches
74c7: 29 70 and #%01110000 ;mask to get the bonus city setting
74c9: 4a lsr A
74ca: 4a lsr A
74cb: 4a lsr A ;convert to 0-15 (even)
74cc: aa tax ;use as index
74cd: bd 82 60 lda bonus_city_bcd,x ;get middle byte of score
74d0: 85 d2 sta bcd3_acc+1 ;copy to BCD accumulator
74d2: bd 83 60 lda bonus_city_bcd+1,x ;get high byte of score
74d5: 85 d3 sta bcd3_acc+2 ;copy to BCD acc
74d7: a2 90 ldx #144 ;horizontal position
74d9: a0 58 ldy #88 ;vertical position
74db: a9 40 lda #%01000000 ;fg color = 2
74dd: 20 f6 6a jsr PrintBcdNumber ;display number
74e0: 60 :Return rts
;
; Initializes high score list to defaults.
;
74e1: a2 17 InitDefScores ldx #23 ;8 entries * 3 bytes
74e3: bd f3 74 :Loop lda :def_score_names,x
74e6: 9d 2c 00 sta: hscore_initials,x ;copy initials
74e9: bd 0b 75 lda :def_scores,x
74ec: 9d 44 00 sta: hscore_scores,x ;copy scores
74ef: ca dex
74f0: 10 f1 bpl :Loop
74f2: 60 rts
;
; Default high scores, from lowest to highest. Initials are ASCII, scores are
; 3-byte BCD. For example, the highest default score is DFT with 7500 points.
;
; cf. https://arcadeblogger.com/2021/01/31/anatomy-of-arcade-high-score-tables/
;
:def_score_names
74f3: 47 4a 4c 44+ .dstr ‘GJLDEWJEDMJPRDASRCDLSDFT’
750b: 50 69 00 :def_scores .dd3 $006950 ;GJL - Gerry Lichac
750e: 05 70 00 .dd3 $007005 ;DEW - Dave Wiebenson
7511: 50 71 00 .dd3 $007150 ;JED - Jed Margolin
7514: 00 72 00 .dd3 $007200 ;MJP - Mary Pepper
7517: 50 72 00 .dd3 $007250 ;RDA - Rich D. Adam
751a: 30 73 00 .dd3 $007330 ;SRC - Steve Calfee
751d: 95 74 00 .dd3 $007495 ;DLS - Dave Sherman
7520: 00 75 00 .dd3 $007500 ;DFT - Dave F. Theurer
;
; Prints the highest score, then prints the scores for the two players, at the
; top of the screen.
;
7523: ad 59 00 PrintTopScores lda: hscore_scores+21 ;low score appears first, so grab the score from
7526: 85 d1 sta bcd3_acc ; the end of the list
7528: ad 5a 00 lda: hscore_scores+22
752b: 85 d2 sta bcd3_acc+1
752d: ad 5b 00 lda: hscore_scores+23
7530: 85 d3 sta bcd3_acc+2
7532: a2 64 ldx #100 ;a bit left of center
7534: a0 e2 ldy #226 ;near top of screen
7536: a9 40 lda #%01000000 ;fg color = 2
7538: 20 f6 6a jsr PrintBcdNumber ;print the score
753b: 4c e1 5f jmp PrintScores ;print scores for both players
753e: 22 .dd1 $22 ;checksum byte
;
; Func $18: checks to see if any high scores were beaten.
;
• Clear variables
]new_high_flag .var $8d {addr/1}
F_HighScoreCheck
753f: 20 e4 76 jsr ScrubHighScores ;start with a clean slate
7542: a5 93 lda play_mode_flag ;is a game in progress?
7544: d0 07 bne :CheckHigh ;yes, branch
7546: a0 1c ldy #FN_SHOW_HS ;show high scores next
7548: 84 91 sty func_index
754a: b8 clv
754b: 50 3d bvc :Return
754d: a2 01 :CheckHigh ldx #$01 ;start with player 2
754f: 86 8d stx ]new_high_flag ;set to a positive value
; Scores are stored from lowest to highest. Starting with the highest, walk
; through the list until we find a lower score.
7551: a0 15 :PlayerLoop ldy #21 ;7th entry, 3 bytes per entry
7553: b9 46 00 :CmpLoop lda hscore_scores+2,y ;see if our score beat this one
7556: dd da 01 cmp cur_score_hi,x ;compare high byte
7559: d0 0e bne :NotEq ;not equal, branch
755b: b9 45 00 lda hscore_scores+1,y
755e: dd d8 01 cmp cur_score_md,x ;compare mid byte
7561: d0 06 bne :NotEq ;not equal, branch
7563: b9 44 00 lda hscore_scores,y
7566: dd d6 01 cmp cur_score_lo,x ;compare low byte
7569: b0 03 :NotEq bcs :NotHigher ;previous high is >= current, branch
756b: 20 8b 75 jsr InsertScore ;insert score placeholder; sets Y-reg to $00
756e: 88 :NotHigher dey ;move to next entry
756f: 88 dey
7570: 88 dey
7571: 10 e0 bpl :CmpLoop ;if not done, loop
7573: ca dex ;next player
7574: 10 db bpl :PlayerLoop ;not done with players; loop
;
7576: 20 33 6c jsr TxscResetStream ;reset scrolling text
7579: a5 8d lda ]new_high_flag ;was this a new high score?
757b: 10 09 bpl :GoFunc06 ;no, bail
757d: 20 21 69 jsr SetColorPalette ;set palette for current wave
7580: 20 e4 75 jsr PrepEnterInitials ;configure to input initials
7583: b8 clv
7584: 50 04 bvc :Return ;(always)
7586: a0 06 :GoFunc06 ldy #FN_GAME_OVER ;game over time
7588: 84 91 sty func_index
758a: 60 :Return rts
;
; Inserts the current score into the high-score table at the specified position.
;
; The first score in the list is the lowest score, so we copy slot N+1 to slot N
; until we open the correct slot. The player number is written into the first
; byte of the initials table, so the initial-input code can find it later.
;
; On entry:
; X-reg: player number (0 or 1)
; Y-reg: score insertion slot x3
;
; On exit:
; Y-reg: $00
; $8d: $ff
;
]score_slot .var $98 {addr/1}
758b: 84 98 InsertScore sty ]score_slot ;store insertion slot x3 in ZP
758d: a0 00 ldy #$00 ;start at lowest score
758f: c4 98 :InsertLoop cpy ]score_slot ;is this the target slot?
7591: d0 27 bne :MoveDown ;no, copy it to a lower slot
;
7593: bd da 01 lda cur_score_hi,x ;copy player's score into high score table
7596: 99 46 00 sta hscore_scores+2,y
7599: bd d8 01 lda cur_score_md,x
759c: 99 45 00 sta hscore_scores+1,y
759f: bd d6 01 lda cur_score_lo,x
75a2: 99 44 00 sta hscore_scores,y
75a5: e8 inx ;increment player number
75a6: 8a txa ;copy to A-reg
75a7: ca dex ;undo increment
75a8: 99 2c 00 sta hscore_initials,y ;save player number + 1 in first initial
75ab: a9 5b lda #$5b ;store an unprintable character in other two
75ad: 99 2d 00 sta hscore_initials+1,y
75b0: 99 2e 00 sta hscore_initials+2,y
75b3: a0 00 ldy #$00 ;zero Y-reg so caller exits loop
75b5: a9 ff lda #$ff ;set flag indicating we stored the score
75b7: 85 8d sta ]new_high_flag
75b9: 60 rts
75ba: b9 49 00 :MoveDown lda hscore_scores+5,y ;move score to lower slot
75bd: 99 46 00 sta hscore_scores+2,y
75c0: b9 48 00 lda hscore_scores+4,y
75c3: 99 45 00 sta hscore_scores+1,y
75c6: b9 47 00 lda hscore_scores+3,y
75c9: 99 44 00 sta hscore_scores,y
75cc: b9 31 00 lda hscore_initials+5,y ;copy initials too
75cf: 99 2e 00 sta hscore_initials+2,y
75d2: b9 30 00 lda hscore_initials+4,y
75d5: 99 2d 00 sta hscore_initials+1,y
75d8: b9 2f 00 lda hscore_initials+3,y
75db: 99 2c 00 sta hscore_initials,y
;
75de: c8 iny ;advance to next slot
75df: c8 iny
75e0: c8 iny
75e1: 10 ac bpl :InsertLoop ;if not done, loop
75e3: 60 rts
;
; Prepares the system for input of player initials. We may need to accept
; initials from both players, so this is called again after initial entry is
; complete for the first player.
;
; We don't draw crosshairs while doing this, so we can use a couple bytes in the
; crosshair backing store for temporary storage.
;
PrepEnterInitials
75e4: a0 15 ldy #$15
75e6: b9 2c 00 :Loop lda hscore_initials,y ;player number is stored in first byte
75e9: f0 40 beq :NextEntry ;empty entry, branch
75eb: c9 03 cmp #$03 ;is it > 2?
75ed: b0 3c bcs :NextEntry ;yes, not a placeholder; branch
;
75ef: 85 b9 sta cur_plyr_num ;save as player number (1/2)
75f1: c6 b9 dec cur_plyr_num ;decrement to make it 0/1
75f3: 84 16 sty cross_back_store+7 ;save entry index
75f5: 20 80 69 jsr ClearScreenPartial ;clear top part of screen
75f8: 20 2f 67 jsr InitLangAndFlip ;flip screen toward appropriate player
75fb: 20 48 5f jsr PrintPlayerN ;indicate which player is entering initials
75fe: 20 ce 65 jsr DrawPlyrIndic
7601: 20 23 75 jsr PrintTopScores ;print scores for both players
7604: a9 09 lda #$09
7606: 20 51 6a jsr PrintMessage ;"GREAT SCORE"
7609: a9 12 lda #$12
760b: 20 51 6a jsr PrintMessage ;"ENTER YOUR INITIALS"
760e: a9 13 lda #$13
7610: 20 51 6a jsr PrintMessage ;"SPIN BALL ..."
7613: a9 14 lda #$14
7615: 20 51 6a jsr PrintMessage ;"PRESS ANY FIRE ..."
7618: a9 ff lda #$ff ;init timeout to 255 sets of 16 frames
761a: 85 0f sta cross_back_store ; (about 68 seconds)
761c: a2 00 ldx #$00
761e: 86 a5 stx cross_xc ;set letter to 'A'
7620: 86 fa stx fire_buttons ;clear inputs
7622: a9 02 lda #$02
7624: 85 0a sta last_cross_yc ;set index to first initial
7626: a9 1a lda #FN_ENTER_INITIALS ;enter initials
7628: 85 91 sta func_index
762a: 60 rts
762b: 88 :NextEntry dey ;move to next high score slot
762c: 88 dey
762d: 88 dey
762e: 10 b6 bpl :Loop ;loop until all slots checked, then fall through
;
; Cleans up after initials have been input for all eligible players. Sets the
; next function to show the high score table.
;
7630: 20 80 69 InitialsDone jsr ClearScreenPartial ;erase top part of screen
7633: a9 00 lda #$00
7635: 85 b9 sta cur_plyr_num ;set player number to 1
7637: a5 fc lda cocktail_flip
7639: 29 80 and #%10000000 ;clear flip bits (but keep cocktail-mode bit)
763b: 85 fc sta cocktail_flip
763d: a9 1c lda #FN_SHOW_HS ;show the high score table
763f: 85 91 sta func_index
7641: 60 rts
;
; Horizontal positions for letters when entering initials. We start with #2 and
; count down.
;
7642: 82 initial_pos_tbl .dd1 130
7643: 78 .dd1 120
7644: 6e .dd1 110
;
; Func $1a: enter initials.
;
; We use a couple of locations in the crosshair backing store to hold state: $0f
; has the timeout counter, and $16 has the index into the high score initials
; table.
;
; The initial we're entering (0-2) is kept in the crosshair Y position. The
; current letter (A-Z or ' ') is held in the crosshair X position.
;
; Trackball movement in both vertical and horizontal directions will cause the
; letter to change.
;
• Clear variables
]timeout .var $0f {addr/1}
]entry_index3 .var $16 {addr/1}
]horz_posn .var $1d {addr/1}
]vert_posn .var $1e {addr/1}
7645: a5 f1 F_EnterInitials lda in0_value ;get button state
7647: 49 ff eor #$ff ;invert bits so 1=pressed
7649: 29 18 and #%00011000 ;check the p1/p2 start buttons
764b: d0 e3 bne InitialsDone ;start button pressed, abandon entry
;
764d: a5 ca lda game_frame_ctr ;get frame counter
764f: 29 0f and #%00001111 ;mask off high nibble
7651: d0 07 bne :GetInitials ;branch unless low nibble is zero (every 16 frames)
7653: c6 0f dec ]timeout ;decrement timer
7655: d0 03 bne :GetInitials ;haven't timed out, branch
7657: 4c 30 76 jmp InitialsDone ;give up
765a: a2 00 :GetInitials ldx #$00
765c: a5 94 lda trk_h_delta ;get horizontal move
765e: 86 94 stx trk_h_delta ;reset delta
7660: 18 clc
7661: 65 95 adc trk_v_delta ;add to vertical move
7663: 86 95 stx trk_v_delta ;reset delta
7665: 0a asl A ;multiply by 4
7666: 0a asl A
7667: 10 02 bpl :MovePos ;branch if positive move
7669: c6 a5 dec cross_xc ;decrement int part for negative move
766b: 18 :MovePos clc
766c: 65 a3 adc cross_frac ;add fractional part
766e: 85 a3 sta cross_frac
7670: a9 00 lda #$00
7672: 65 a5 adc cross_xc ;add carry to int part
7674: 10 05 bpl :TestMag ;branch if >= 0
7676: a9 1a lda #26 ;set to ' ' (follows 'Z')
7678: b8 clv
7679: 50 06 bvc :GotLetter ;(always)
767b: c9 1b :TestMag cmp #27 ;is it < ' ' that follows 'Z'?
767d: 90 02 bcc :GotLetter ;yes, branch
767f: a9 00 lda #$00 ;set to 'A'
7681: 85 a5 :GotLetter sta cross_xc ;update letter
;
7683: a4 0a ldy last_cross_yc ;get initial number (0-2)
7685: b9 42 76 lda initial_pos_tbl,y ;get horizontal position
7688: 85 1d sta ]horz_posn ;set ZP
768a: a9 b0 lda #176
768c: 85 1e sta ]vert_posn ;set vertical position
768e: a5 a5 lda cross_xc ;get current letter (0-26)
7690: 18 clc
7691: 69 41 adc #‘A’ ;add 'A' to make it ASCII (space is $5b)
7693: 20 ba 76 jsr PrintPaddedChar ;print char
7696: a5 fa lda fire_buttons ;check the fire buttons
7698: 29 07 and #%00000111 ;are any of them pressed?
769a: f0 1d beq :Return ;no, bail
;
769c: a5 a5 lda cross_xc ;get current letter
769e: 18 clc
769f: 69 41 adc #‘A’ ;add 'A' to make it ASCII
76a1: a4 16 ldy ]entry_index3 ;get index into high score initials table
76a3: 99 2c 00 sta hscore_initials,y ;set value
76a6: e6 16 inc ]entry_index3 ;advance to next entry
;
76a8: a9 84 lda #132 ;132 * 16 = 2112 = ~35 sec
76aa: 85 0f sta ]timeout ;reset timeout
76ac: a2 00 ldx #$00
76ae: 86 fa stx fire_buttons ;reset button inputs
76b0: 86 a5 stx cross_xc ;reset to 'A'
76b2: c6 0a dec last_cross_yc ;have we done all 3 initials?
76b4: 10 03 bpl :Return ;not yet, bail
76b6: 20 e4 75 jsr PrepEnterInitials ;yes, clear state, init for next player
76b9: 60 :Return rts
;
; Prints a character with a little extra padding. Used for high-score initials
; and a couple of self-test features.
;
; On entry:
; A-reg: ASCII value of character to print
; $1d: X coordinate of top-left corner
; $1e: Y coordinate of top-left corner
;
; On exit:
; $1d: increased by 10 (one 8x8 cell + 2 pixels)
; $08: preserve background flag
; $0b: foreground color index (%PPP00000)
; $0c: background color index (%PPP00000)
; (X/Y-reg preserved)
;
• Clear variables
]xc .var $00 {addr/1}
]yc .var $01 {addr/1}
]horz_posn .var $1d {addr/1}
]vert_posn .var $1e {addr/1}
]saved_x .var $23 {addr/1}
]saved_y .var $24 {addr/1}
76ba: 84 24 PrintPaddedChar sty ]saved_y ;preserve X/Y-reg
76bc: 86 23 stx ]saved_x
76be: 20 9d 69 jsr GetGlyphPtr ;get pointer to glyph in $0d-0e
76c1: a5 1d lda ]horz_posn ;set up X/Y position
76c3: 85 00 sta ]xc
76c5: a5 1e lda ]vert_posn
76c7: 85 01 sta ]yc
76c9: a9 00 lda #$00 ;disable background preservation
76cb: 85 08 sta preserve_bg_flag
76cd: a9 40 lda #%01000000 ;fg color = 2
76cf: 85 0b sta draw_fg_color
76d1: a9 00 lda #$00 ;bg color = 0
76d3: 85 0c sta draw_bg_color
76d5: 20 27 66 jsr DrawGlyphPtrNoSc ;draw glyph
;
76d8: a5 1d lda ]horz_posn ;move horizontal posn right one char + 2 pixels
76da: 18 clc
76db: 69 0a adc #10
76dd: 85 1d sta ]horz_posn
76df: a6 23 ldx ]saved_x ;restore X/Y-reg
76e1: a4 24 ldy ]saved_y
76e3: 60 rts
;
; Ensures that there are no vestigial pending high scores.
;
; When one or both players get on the high score list, the first byte of the
; initials for the entry is replaced with the player number (0 or 1). This is
; overwritten when the initials are entered. This function ensures that we
; don't have any pending entries left over, replacing the initials with an
; invalid char that will be drawn as a blank.
;
76e4: a0 15 ScrubHighScores ldy #21 ;7th entry, 3 bytes per entry
76e6: b9 2c 00 :Loop lda hscore_initials,y ;get first initial byte
76e9: f0 0f beq :Valid ;zero is okay
76eb: c9 03 cmp #$03
76ed: b0 0b bcs :Valid ;>= 3 is okay
76ef: a9 5b lda #$5b ;value was 0 or 1 (player number); scrub
76f1: 99 2c 00 sta hscore_initials,y ; unfinished entry
76f4: 99 2d 00 sta hscore_initials+1,y
76f7: 99 2e 00 sta hscore_initials+2,y
76fa: 88 :Valid dey
76fb: 88 dey
76fc: 88 dey
76fd: 10 e7 bpl :Loop ;loop until all entries checked
76ff: 60 rts
;
; Func $1c: show high score list.
;
• Clear variables
]horz_out .var $1d {addr/1}
]vert_out .var $1e {addr/1}
]vert_posn .var $20 {addr/1}
F_ShowHighScores
7700: 20 80 69 jsr ClearScreenPartial ;erase top part of screen
7703: a9 00 lda #$00
7705: 85 b9 sta cur_plyr_num ;set player 1
7707: 85 93 sta play_mode_flag ;clear the game-in-progress flag
7709: 85 ae sta num_players
770b: a5 fc lda cocktail_flip
770d: 29 80 and #%10000000 ;clear flip bit (but keep cocktail-mode bit)
770f: 85 fc sta cocktail_flip
7711: 20 23 75 jsr PrintTopScores ;print player scores and highest score
7714: a9 01 lda #$01
7716: 20 51 6a jsr PrintMessage ;"HIGH SCORES"
7719: a9 c0 lda #192
771b: 85 20 sta ]vert_posn
771d: 20 21 69 jsr SetColorPalette ;set palette for current wave (redundant?)
7720: 20 e4 76 jsr ScrubHighScores ;purge vestigial pending scores
7723: a0 15 ldy #$15
7725: b9 46 00 :PrintScoreLoop lda hscore_scores+2,y ;see if score in table was 000000
7728: 19 45 00 ora hscore_scores+1,y ;(indicates end of list reached)
772b: 19 44 00 ora hscore_scores,y
772e: d0 04 bne :ValidScore ;score is nonzero, branch
7730: a8 tay ;set Y-reg=0 so loop exits
7731: b8 clv
7732: 50 3d bvc :NextScore ;(always)
7734: b9 46 00 :ValidScore lda hscore_scores+2,y ;copy score to BCD accumulator
7737: 85 d3 sta bcd3_acc+2
7739: b9 45 00 lda hscore_scores+1,y
773c: 85 d2 sta bcd3_acc+1
773e: b9 44 00 lda hscore_scores,y
7741: 85 d1 sta bcd3_acc
7743: 98 tya ;preserve Y-reg
7744: 48 pha
7745: a9 40 lda #$40
7747: a2 80 ldx #128 ;mid-screen
7749: a4 20 ldy ]vert_posn ;vert posn
774b: 20 f6 6a jsr PrintBcdNumber ;print the number
;
774e: 68 pla ;restore Y-reg
774f: a8 tay
7750: a9 50 lda #80 ;horizontal position
7752: 85 1d sta ]horz_out
7754: a5 20 lda ]vert_posn
7756: 85 1e sta ]vert_out
7758: b9 2c 00 lda hscore_initials,y ;get first letter
775b: 20 ba 76 jsr PrintPaddedChar ;print it (moves right one char)
775e: b9 2d 00 lda hscore_initials+1,y ;repeat for second letter
7761: 20 ba 76 jsr PrintPaddedChar
7764: b9 2e 00 lda hscore_initials+2,y ;and third letter
7767: 20 ba 76 jsr PrintPaddedChar
;
776a: a5 20 lda ]vert_posn ;shift vertical position down 10 lines
776c: 38 sec
776d: e9 0a sbc #10
776f: 85 20 sta ]vert_posn
7771: 88 :NextScore dey ;move to next entry
7772: 88 dey
7773: 88 dey
7774: 10 af bpl :PrintScoreLoop ;loop until done
;
7776: 20 aa 74 jsr PrintBonusCityPts ;show bonus city setting
7779: a9 14 lda #FN_SHOW_TITLE
777b: 85 92 sta next_func_index ;show the title screen...
777d: a9 22 lda #FN_PAUSE
777f: 85 91 sta func_index ;...after a brief delay
7781: a9 ff lda #$ff
7783: 85 af sta frame4_delay_ctr ;wait for 255*4 frames (~17 sec)
7785: 60 rts
;
; Func $1e: player has deposited first of 2 coins.
;
; Invoked when machine is in "2 coins 1 play" mode. We put a message up on the
; screen reminding them to insert another.
;
7786: 20 33 6c F_HalfCredit jsr TxscResetStream ;reset scrolling text
7789: 20 2f 67 jsr InitLangAndFlip ;reset language and orientation
778c: 20 86 69 jsr ClearScreen ;clear screen
778f: a9 01 lda #$01
7791: 85 a7 sta wave_num ;set wave number to 1
7793: 20 21 69 jsr SetColorPalette ;set colors for wave 1
7796: a9 0c lda #$0c
7798: 20 51 6a jsr PrintMessage ;"2 COINS 1 PLAY"
779b: a9 20 lda #FN_WAIT_FULL_CREDIT ;switch to next function to wait for coin
779d: 85 91 sta func_index
;
; Func $20: wait for second of two coins to drop.
;
F_WaitFullCredit
779f: a5 66 lda credit_count ;do they have any credits?
77a1: f0 04 beq :Return ;not yet, bail
77a3: a9 14 lda #FN_SHOW_TITLE
77a5: 85 91 sta func_index ;coin dropped, switch to the title screen
77a7: 60 :Return rts
;
; Draws a smartbomb, by plotting individual points. Smartbombs are symmetric
; and don't need to be flipped when the screen is rotated.
;
; On entry:
; $97: slot index of smart bomb (8-15)
;
• Clear variables
]color_mask .var $98 {addr/1}
77a8: a9 ff DrawSmartBomb lda #$ff ;pass all color bits
77aa: d0 02 bne :DoDraw
77ac: a9 00 EraseSmartBomb lda #$00 ;clear all color bits (sets color = 0)
77ae: 85 98 :DoDraw sta ]color_mask
77b0: a0 0c ldy #12 ;13 entries
77b2: a6 97 :Loop ldx slot_index
77b4: bd 5d 01 lda abm_cur_y,x ;get smart bomb center Y
77b7: 18 clc
77b8: 79 e2 77 adc :y_offset_tbl,y ;add offset from table
77bb: 49 ff eor #$ff ;invert bits to form high byte of pointer
77bd: 85 07 sta gfx_ptr+1
77bf: bd 28 01 lda abm_cur_x,x ;get smart bomb center X
77c2: 18 clc
77c3: 79 d5 77 adc :x_offset_tbl,y ;add offset from table
77c6: 85 06 sta gfx_ptr ;use as low byte of pointer
;
77c8: b9 ef 77 lda :color_tbl,y ;get color value from table
77cb: a2 00 ldx #$00
77cd: 25 98 and ]color_mask ;clear bits if we're erasing
77cf: 81 06 sta (gfx_ptr,x) ;set pixel
77d1: 88 dey ;done with all pixels?
77d2: 10 de bpl :Loop ;no, loop
77d4: 60 rts
;
; Smartbomb image data. 13 sets of X/Y offset and color. The color is
; %PPP0000, and is either 2 or 4, with 0 in the middle. (0 is background, 2 is
; used for ICBM trails, 4 is cycled.)
;
; Pattern:
; 2
; 2 4 2
; 2 4 0 4 2
; 2 4 2
; 2
77d5: 00 ff 00 01+ :x_offset_tbl .bulk $00,$ff,$00,$01,$fe,$ff,$00,$01,$02,$ff,$00,$01,$00
77e2: 02 01 01 01+ :y_offset_tbl .bulk $02,$01,$01,$01,$00,$00,$00,$00,$00,$ff,$ff,$ff,$fe
77ef: 40 40 80 40+ :color_tbl .bulk $40,$40,$80,$40,$40,$80,$00,$80,$40,$40,$80,$40,$40
;
; Handles coin slots and the slam sensor. Executed during IRQ.
;
77fc: a2 02 CoinsAndCredits ldx #$02 ;start with right coin slot
77fe: ad ed 00 :CoinLoop lda: in01_current ;get most recent IN0 (w/o debounce)
7801: e0 01 cpx #$01 ; high 3 bits are coin R/C/L
7803: f0 03 beq :Shift2 ;X-reg=1, branch to shift 2x (center)
7805: b0 02 bcs :Shift1 ;X-reg=2, branch to shift 1x (right)
7807: 0a asl A ;X-reg=0, shift 3x (left)
7808: 0a :Shift2 asl A ;shift the desired coin slot bit into the carry
7809: 0a :Shift1 asl A
780a: b5 5c lda coin_timer1?,x ;get coin timer
780c: 29 1f and #%00011111 ;mod 32
780e: b0 37 bcs :SlotBitSet ;if coin slot bit was set, branch
7810: f0 10 beq :SetValue1 ;if we're at zero, branch
7812: c9 1b cmp #27 ;>= 27?
7814: b0 0a bcs :Ge27 ;yes, branch
7816: a8 tay ;preserve A-reg
7817: a5 fb lda irq_counter ;get the IRQ counter
7819: 29 07 and #$07
781b: c9 07 cmp #$07 ;set carry every 8 IRQs
781d: 98 tya ;restore A-reg
781e: 90 02 bcc :SetValue1 ;branch most of the time
7820: e9 01 :Ge27 sbc #$01 ;subtract one 1/8th of the time
7822: 95 5c :SetValue1 sta coin_timer1?,x ;save value
; Check slam switch. If engaged, reset the coin state.
7824: ad ee 00 lda: in01_current+1 ;get most recent IN1 (w/o debounce)
7827: 29 20 and #%00100000 ;slam switch engaged?
7829: d0 04 bne :NoSlam ;no, branch
782b: a9 f0 lda #$f0 ;yes, request an annoying noise
782d: 85 5f sta slam_tone_ctr1
782f: a5 5f :NoSlam lda slam_tone_ctr1 ;are we in tilt-whine mode?
7831: f0 08 beq :NoWhine ;no, branch
7833: c6 5f dec slam_tone_ctr1 ;count it down
7835: a9 00 lda #$00
7837: 95 5c sta coin_timer1?,x ;reset the coin stuff so we ignore anything that
7839: 95 60 sta coin_timer2?,x ; was dropping
783b: 18 :NoWhine clc ;clear carry, so branch to BCS always falls through
783c: b5 60 lda coin_timer2?,x ;has value reached zero?
783e: f0 23 beq :MaybeNext ;yes, move on to next slot
7840: d6 60 dec coin_timer2?,x ;decrement
7842: d0 1f bne :MaybeNext ;not yet zero, move on to next slot
7844: 38 sec ;set carry for next part
7845: b0 1c bcs :MaybeNext ;(always)
7847: c9 1b :SlotBitSet cmp #27 ;fiddle with the timers
7849: b0 09 bcs :Ge27
784b: b5 5c lda coin_timer1?,x
784d: 69 20 adc #32
784f: 90 d1 bcc :SetValue1
7851: f0 01 beq :Ge27
7853: 18 clc
7854: a9 1f :Ge27 lda #31
7856: b0 ca bcs :SetValue1
7858: 95 5c sta coin_timer1?,x
785a: b5 60 lda coin_timer2?,x
785c: f0 01 beq :T20
785e: 38 sec
785f: a9 78 :T20 lda #120
7861: 95 60 sta coin_timer2?,x
7863: b0 03 :MaybeNext bcs :GotCoin
7865: 4c a3 78 jmp :Next
7868: a9 00 :GotCoin lda #$00 ;default slot config value
786a: e0 01 cpx #$01 ;left slot?
786c: 90 16 bcc :AddCred ;yes, no config for left; branch
786e: f0 0c beq :Center ;branch if center slot
7870: a5 f3 lda r10_irq_mod ;get R10 switches
7872: 29 0c and #%00001100 ;mask right coin slot multiplier (x1/x4/x5/x6)
7874: 4a lsr A ;shift right to get 0-3
7875: 4a lsr A ;also clears carry flag
7876: f0 0c beq :AddCred ;if zero, branch
7878: 69 02 adc #$02 ;change 1-3 to 3-5
787a: d0 08 bne :AddCred ;(always)
787c: a5 f3 :Center lda r10_irq_mod ;get R10 switches
787e: 29 10 and #%00010000 ;mask center coin slot multiplier (x1/x2)
7880: f0 02 beq :AddCred ;if zero, branch
7882: a9 01 lda #$01 ;set to 1
; Coin slot multiplier - 1 now in A-reg.
7884: 38 :AddCred sec ;set carry to add 1
7885: a8 tay ;copy multiplier to Y-reg
7886: 65 67 adc coins_inserted ;add to previously-inserted coins
7888: 85 67 sta coins_inserted
;
788a: a5 f4 lda r8_irq_inv ;get R8 switches
788c: 29 04 and #%00000100 ;check "bonus credit for 4 quarters" setting
788e: f0 11 beq :No5for4 ;not set, branch
7890: 98 tya ;get multiplier
7891: 38 sec ;add one
7892: 65 68 adc recent_coins ;add to coins inserted since last game
7894: c9 04 :BonusLoop cmp #$04 ;have at least 4 gone in?
7896: 90 07 bcc :SetCoins ;no, branch
7898: 38 sec
7899: e9 04 sbc #$04 ;subtract 4 from recent count
789b: e6 66 inc credit_count ;increment the number of credits
789d: d0 f5 bne :BonusLoop ;branch always, unless they deposit 256*4 coins
789f: 85 68 :SetCoins sta recent_coins ;update recent coin count
78a1: f6 63 :No5for4 inc coin_drop_ctr,x ;incr count for this slot
78a3: ca :Next dex ;move to next coin slot
78a4: 30 03 bmi :AddCredits ;all done, branch
78a6: 4c fe 77 jmp :CoinLoop ;not done, loop
78a9: a5 f3 :AddCredits lda r10_irq_mod ;get R10 switches; note IRQ does EOR %00000010
78ab: 29 03 and #%00000011 ;get coins-per-play setting
78ad: a8 tay ;save copy in Y-reg
78ae: f0 12 beq :UpdateCoins ;branch if zero (free play), with zero in A-reg
; The modified coins-per-play setting is:
; 0 - free play (handled above)
; 1 - 1 coin 2 plays
; 2 - 1 coin 1 play
; 3 - 2 coins 1 play
; By shifting right and adding the carry back in, we get 1 (1 coin 2 plays), 1
; (1 coin 1 play), or 2 (2 coins 1 play), which is the number of coins needed to
; get at least one credit. We subtract that from the number of coins inserted
; by negating and adding it.
78b0: 4a lsr A ;shift low bit into carry
78b1: 69 00 adc #$00 ;add carry back if set
78b3: 49 ff eor #$ff ;negate value by inverting bits
78b5: 38 sec ; and adding one
78b6: 65 67 adc coins_inserted ;add negated value to coins inserted
78b8: 90 0a bcc :UpdateCounters ;not enough coins, branch
78ba: c0 02 cpy #$02 ;are we in 1 coin 2 plays mode?
78bc: b0 02 bcs :OneCredit ;no, branch
78be: e6 66 inc credit_count ;increment credits
78c0: e6 66 :OneCredit inc credit_count ;increment credits
78c2: 85 67 :UpdateCoins sta coins_inserted ;update coins inserted
;
78c4: a5 fb :UpdateCounters lda irq_counter ;get IRQ counter
78c6: 4a lsr A ;shift low bit into carry
78c7: b0 27 bcs :Return ;bail every other time
; Update the counter values. If the counter is negative, the IRQ code writes a
; bit to OUT0.
78c9: a0 00 ldy #$00 ;init mod count to zero
78cb: a2 02 ldx #$02 ;walk through all 3 coin slots
78cd: b5 63 :CtrLoop1 lda coin_drop_ctr,x ;get count
78cf: f0 09 beq :NextCtr1 ;if zero, branch
78d1: c9 10 cmp #$10 ;is it < 16?
78d3: 90 05 bcc :NextCtr1 ;yes, branch
78d5: 69 ef adc #$ef ;add -16 (note carry is set)
78d7: c8 iny ;increment mod count
78d8: 95 63 sta coin_drop_ctr,x ;store updated value
78da: ca :NextCtr1 dex ;done with coin slots?
78db: 10 f0 bpl :CtrLoop1 ;no, branch
78dd: 98 tya ;did we update a counter?
78de: d0 10 bne :Return ;yes, bail
;
78e0: a2 02 ldx #$02 ;walk through all 3 coin slots
78e2: b5 63 :CtrLoop2 lda coin_drop_ctr,x ;get count
78e4: f0 07 beq :NextCtr2 ;if zero, branch
78e6: 18 clc
78e7: 69 ef adc #$ef ;add -17
78e9: 95 63 sta coin_drop_ctr,x ;store updated value
78eb: 30 03 bmi :Return ;if it went negative, bail
78ed: ca :NextCtr2 dex ;done with coin slots?
78ee: 10 f2 bpl :CtrLoop2 ;no, branch
78f0: 60 :Return rts
;
; Sound effect table offsets. For each of the eight sound effects, these are
; indices into the SFX audio data table for the start of the stream that drives
; the POKEY sound channels. Zero values indicate the register is not used by
; this effect.
;
; For example, effect #2 (explosion) uses two sound channels. AUDF1 is at
; offset +$01, AUDC1 is at offset +$07, AUDF2 is at offset +$0d, and AUDC2 is at
; offset +$13.
;
78f1: 00 00 00 00+ sfx_indices .bulk $00,$00,$00,$00,$00,$00,$7d,$83 ;$01 silo is low
78f9: 01 07 0d 13+ .bulk $01,$07,$0d,$13,$00,$00,$00,$00 ;$02 explosion
7901: 00 00 00 00+ .bulk $00,$00,$00,$00,$19,$33,$00,$00 ;$04 ABM launched
7909: 41 47 00 00+ .bulk $41,$47,$00,$00,$00,$00,$00,$00 ;$08 bonus points
7911: 5d 77 00 00+ .bulk $5d,$77,$00,$00,$00,$00,$00,$00 ;$10 start of wave
7919: 91 97 9d a3+ .bulk $91,$97,$9d,$a3,$00,$00,$00,$00 ;$20 game over
7921: 4d 53 00 00+ .bulk $4d,$53,$00,$00,$00,$00,$00,$00 ;$40 bonus city used
7929: 00 00 00 00+ .bulk $00,$00,$00,$00,$00,$00,$a9,$b3 ;$80 unable to fire
;
; POKEY audio has four channels, with two 8-bit I/O locations per channel (AUDFn
; and AUDCn). All four channels are used.
;
; The AUDFn setting determines frequency. Larger value == lower pitch.
;
; The AUDCn value is NNNFVVVV, where N is a noise / distortion setting, F is
; "forced volume-only output" enable, and V is the volume level.
;
; In the table below, each chunk has 4 values:
; +00 initial value
; +01 duration
; +03 increment
; +04 repetition count
;
; The sound specified by the value is played until the duration reaches zero.
; If the repetition count is nonzero, the value is increased or decreased by the
; increment, and the duration is reset. When the repetition count reaches zero,
; the next chunk is loaded. If the chunk has the value $00, the sequence ends.
; The counters are updated by the ~240Hz NMI.
;
; Because AUDFn and AUDCn are specified by different chunks, care must be taken
; to ensure the durations run out at the same time, i.e. (duration * rep_count)
; is equal.
;
; (FWIW, this is the same approach that Battlezone uses.)
;
7931: 00 sfx_audio_data .dd1 $00
7932: a0 10 04 10 .bulk $a0,$10,$04,$10 ;+$01: sound $02 F1 (explosion)
7936: 00 00 .bulk $00,$00
7938: 86 40 fe 04 .bulk $86,$40,$fe,$04 ;+$07: sound $02 C1
793c: 00 00 .bulk $00,$00
793e: c0 10 04 10 .bulk $c0,$10,$04,$10 ;+$0d: sound $02 F2 (explosion)
7942: 00 00 .bulk $00,$00
7944: 86 40 fe 04 .bulk $86,$40,$fe,$04 ;+$13: sound $02 C2
7948: 00 00 .bulk $00,$00
794a: 60 10 00 01 .bulk $60,$10,$00,$01 ;+$19: sound $04 F3 (ABM launch)
794e: 50 10 f8 01 .bulk $50,$10,$f8,$01
7952: 48 10 18 01 .bulk $48,$10,$18,$01
7956: 60 18 f0 01 .bulk $60,$18,$f0,$01
795a: 60 10 f8 01 .bulk $60,$10,$f8,$01
795e: 60 10 08 10 .bulk $60,$10,$08,$10
7962: 00 00 .bulk $00,$00
7964: 82 20 02 01 .bulk $82,$20,$02,$01 ;+$33: sound $04 C3
7968: 84 10 00 04 .bulk $84,$10,$00,$04
796c: 84 38 ff 04 .bulk $84,$38,$ff,$04
7970: 00 00 .bulk $00,$00
7972: 10 04 00 01 .bulk $10,$04,$00,$01 ;+$41: sound $08 F1 (bonus points)
7976: 00 00 .bulk $00,$00
7978: 2f 04 0f 01 .bulk $2f,$04,$0f,$01 ;+$47: sound $08 C1
797c: 00 00 .bulk $00,$00
797e: 18 18 00 18 .bulk $18,$18,$00,$18 ;+$4d: sound $40 F1 (bonus city; randomized)
7982: 00 00 .bulk $00,$00
7984: a4 18 00 18 .bulk $a4,$18,$00,$18 ;+$53: sound $40 C1
7988: a0 10 00 02 .bulk $a0,$10,$00,$02
798c: 00 00 .bulk $00,$00
798e: 10 02 01 20 .bulk $10,$02,$01,$20 ;+$5d: sound $10 F1 (wave start 6 whoops)
7992: 10 02 01 20 .bulk $10,$02,$01,$20
7996: 10 02 01 20 .bulk $10,$02,$01,$20
799a: 10 02 01 20 .bulk $10,$02,$01,$20
799e: 10 02 01 20 .bulk $10,$02,$01,$20
79a2: 10 02 01 20 .bulk $10,$02,$01,$20
79a6: 00 00 .bulk $00,$00
79a8: a4 02 00 c0 .bulk $a4,$02,$00,$c0 ;+$77: sound $10 C1
79ac: 00 00 .bulk $00,$00
79ae: 20 80 00 03 .bulk $20,$80,$00,$03 ;+$7d: sound $01 F4 (silo low)
79b2: 00 00 .bulk $00,$00
79b4: a3 40 fd 02 .bulk $a3,$40,$fd,$02 ;+$83: sound $01 C4
79b8: a3 40 fd 02 .bulk $a3,$40,$fd,$02
79bc: a3 40 fd 02 .bulk $a3,$40,$fd,$02
79c0: 00 00 .bulk $00,$00
79c2: 40 ff 02 08 .bulk $40,$ff,$02,$08 ;+$91: sound $20 F1 (game over)
79c6: 00 00 .bulk $00,$00 ; duration = 255*8/240Hz = ~8.5 sec
79c8: 88 ff ff 08 .bulk $88,$ff,$ff,$08 ;+$97: sound $20 C1
79cc: 00 00 .bulk $00,$00
79ce: c0 ff 02 08 .bulk $c0,$ff,$02,$08 ;+$9d: sound $20 F2
79d2: 00 00 .bulk $00,$00
79d4: 88 ff ff 08 .bulk $88,$ff,$ff,$08 ;+$a3: sound $20 C2
79d8: 00 00 .bulk $00,$00
79da: 18 02 ff 10 .bulk $18,$02,$ff,$10 ;+$a9: sound $80 F4 (unable to fire)
79de: 08 20 00 01 .bulk $08,$20,$00,$01
79e2: 00 00 .bulk $00,$00
79e4: a4 10 ff 04 .bulk $a4,$10,$ff,$04 ;+$b3: sound $80 C4
79e8: 00 00 .bulk $00,$00
;
; Plays sound $02 (explosion).
;
79ea: a9 02 PlaySound02 lda #SFX_EXPLOSION ;fall through
;
; Starts a fire-and-forget sound effect.
;
; The noise made by fliers and smartbombs is handled elsewhere.
;
; Sound values are passed as a single-bit value:
; $01: silo is low ("doot doot doot")
; $02: explosion (low rumble)
; $04: ABM launched ("whoosh")
; $08: bonus points awarded at end of wave ("thup")
; $10: start of wave (six sirens)
; $20: game over (extended low explosion)
; $40: bonus city used (several short random tones)
; $80: unable to fire ("dwing")
;
; Only the first (highest) set bit is handled; you cannot start multiple sound
; effects with one call. $00 and $01 are equivalent.
;
; There are four audio channels in the POKEY, each of which is controlled by two
; bytes: the frequency, and the control (volume/noise). Sounds here configure
; one or two of the channels.
;
; For some reason this uses absolute addressing to reference certain zero-page
; values.
;
; On entry:
; A-reg: sound index
; (X/Y-reg preserved)
;
79ec: 2c 93 00 StartSfx bit: play_mode_flag ;is a game in progress?
79ef: 10 3a bpl :Return ;no, bail (be silent in attract mode)
79f1: 8d 02 00 sta: tmp_02 ;save sound value
79f4: 85 69 sta last_sound_started ;save for IRQ code, which checks bit 6 (V)
79f6: 8e 98 00 stx: tmp_98 ;preserve X-reg
79f9: 8c 99 00 sty: tmp_99 ;preserve Y-reg
79fc: aa tax ;set flags for A-reg
79fd: f0 08 beq :SoundZero ;if zero, branch with X-reg=0
79ff: a2 08 ldx #$08 ;shift through all 8 bits
7a01: 0e 02 00 :BitLoop asl: tmp_02 ;shift the high bit into the carry
7a04: ca dex ;decrement the counter
7a05: 90 fa bcc :BitLoop ;if bit wasn't set, loop
;
7a07: 8a :SoundZero txa ;put bit number (0-7) in A-reg
7a08: 0a asl A ;multiply by 8 (0-56); clears carry
7a09: 0a asl A
7a0a: 0a asl A
7a0b: 69 07 adc #$07 ;add 7 to get offset of last byte
7a0d: a8 tay ;use as index
7a0e: c0 47 cpy #71 ;>= 71? (impossible?)
7a10: b0 13 bcs :XYReturn ;yes, bail
;
7a12: a2 07 ldx #$07 ;copy up to 8 bytes
7a14: b9 f1 78 :Loop lda sfx_indices,y ;get initial index
7a17: f0 08 beq :Skip ;if zero, don't overwite this entry
7a19: 95 6a sta audio_indices,x ;copy the index
7a1b: a9 01 lda #$01
7a1d: 95 7a sta audio_dur_ctr,x ;set counters to 1
7a1f: 95 82 sta audio_rep_ctr,x
7a21: 88 :Skip dey ;move to next entry
7a22: ca dex
7a23: 10 ef bpl :Loop ;loop until done
;
7a25: ae 98 00 :XYReturn ldx: tmp_98 ;restore X-reg
7a28: ac 99 00 ldy: tmp_99 ;restore Y-reg
7a2b: 60 :Return rts
;
; Makes an unpleasant noise. Executed during IRQ.
;
7a2c: a9 18 MakeSlamSound lda #$18 ;frequency (fairly high pitch)
7a2e: a0 af ldy #%10101111 ;max volume, no poly (pure tone)
7a30: 8d 00 40 sta POKEY_AUDF1 ;set channel 1
7a33: 8c 01 40 sty POKEY_AUDC1
7a36: 60 rts
;
; Updates sound effect generation. Executed during IRQ.
;
7a37: ad e0 00 UpdateAudio lda: slam_tone_ctr2 ;are we complaining about a slam?
7a3a: d0 f0 bne MakeSlamSound ;yes, do that and ignore the rest
;
7a3c: a2 07 ldx #$07
7a3e: d6 7a :Loop dec audio_dur_ctr,x ;decrement duration counter
7a40: d0 56 bne :SetAudio ;hasn't expired; branch
7a42: b4 6a ldy audio_indices,x ;get index of next thing
7a44: f0 52 beq :SetAudio ;end of list; play sound (next loop will silence it)
7a46: d6 82 dec audio_rep_ctr,x ;decrement repeat counter
7a48: d0 30 bne :RepsNonZero ;more reps to go, branch
7a4a: b9 31 79 lda sfx_audio_data,y ;get the new value
7a4d: 95 72 sta audio_values,x
7a4f: b9 32 79 lda sfx_audio_data+1,y ;and the new count
7a52: 95 7a sta audio_dur_ctr,x
7a54: d0 15 bne :MoreReps ;if we're still going, branch
7a56: 95 6a sta audio_indices,x ;otherwise, zero out the index
7a58: 8a txa
7a59: d0 3d bne :SetAudio ;finish up
;
7a5b: a9 00 lda #$00 ;disable sound on this channel
7a5d: 95 72 sta audio_values,x
7a5f: 9d 00 40 sta POKEY_AUDF1,x ;set frequency or volume to zero
7a62: a9 00 lda #$00
7a64: 95 6a sta audio_indices,x ;zero the index
7a66: ca dex ;done yet?
7a67: 10 d5 bpl :Loop ;no, loop
7a69: 30 35 bmi :Engine ;yes, go fiddle with engine sounds
7a6b: b9 34 79 :MoreReps lda sfx_audio_data+3,y ;get rep counter from index + 3
7a6e: 95 82 sta audio_rep_ctr,x
7a70: b5 6a lda audio_indices,x ;advance index by 4
7a72: 18 clc
7a73: 69 04 adc #$04
7a75: 95 6a sta audio_indices,x
7a77: 4c 98 7a jmp :SetAudio ;finish up
; Duration hit zero, but more reps to go.
7a7a: b9 2e 79 :RepsNonZero lda sfx_audio_data-3,y ;reload previous value; need to reference backward
7a7d: 95 7a sta audio_dur_ctr,x ; because we already incremented index
7a7f: b5 72 lda audio_values,x
7a81: 18 clc
7a82: 79 2f 79 adc sfx_audio_data-2,y
7a85: 24 69 bit last_sound_started ;is sound $40 active (bonus city used)?
7a87: 50 0d bvc :SetThing? ;no, branch
7a89: e0 01 cpx #$01 ;channel 1 ctl?
7a8b: f0 09 beq :SetThing? ;yes, branch; only the freq is randomized, and
7a8d: ad 0a 40 lda POKEY_RANDOM ; at this point all other channels are slient
7a90: 29 1e and #%00011110 ;keep 4 bits (0-30, even only)
7a92: d0 02 bne :SetThing? ;if not zero, keep it; branch
7a94: a9 1e lda #30 ;set to 30
7a96: 95 72 :SetThing? sta audio_values,x ;store value in POKEY list
;
7a98: b5 72 :SetAudio lda audio_values,x ;copy value to POKEY
7a9a: 9d 00 40 sta POKEY_AUDF1,x
7a9d: ca dex ;done with all eight?
7a9e: 10 9e bpl :Loop ;not yet, loop
;
; Handle engine sounds (fliers and bombs).
7aa0: a5 70 :Engine lda audio_indices+6 ;is channel 4 available?
7aa2: d0 26 bne :Return ;no, bail
7aa4: a5 8a lda engine_sfx_flags ;flier or bomb on screen?
7aa6: f0 22 beq :Return ;no, bail
7aa8: 2a rol A ;shift into low bits
7aa9: 2a rol A
7aaa: 2a rol A ;%000000BF (bomb/flier)
7aab: 29 03 and #$03 ;remove excess (not needed?)
7aad: aa tax ;use as index (1-3)
7aae: a5 8b lda engine_sfx_freq ;get current frequency
7ab0: dd ca 7a cmp :engine_freq_en-1,x ;have we reached the highest freq?
7ab3: d0 06 bne :UpdateFreq ;no, branch
7ab5: bd cd 7a lda :engine_freq_st-1,x ;reset the frequency
7ab8: b8 clv
7ab9: 50 03 bvc :SetFreq ;(always)
7abb: 38 :UpdateFreq sec
7abc: e9 02 sbc #$02 ;reduce by 2
7abe: 8d 06 40 :SetFreq sta POKEY_AUDF4 ;update frequency in POKEY
7ac1: 85 8b sta engine_sfx_freq ;store updated value
7ac3: a9 a4 lda #%10100100 ;volume=4, noise=pure
7ac5: 8d 07 40 sta POKEY_AUDC4 ;make sure channel 4 is configured correctly
7ac8: 85 79 sta audio_values+7 ;update the POKEY list
7aca: 60 :Return rts
;
; Entries for: flier only, bomb only, bomb+flier. Flier runs from $70 to $30,
; bomb runs from $30 to $00, and when both are onscreen you only hear the bomb.
7acb: 30 00 00 :engine_freq_en .bulk $30,$00,$00
7ace: 70 30 30 :engine_freq_st .bulk $70,$30,$30
;
; Enables continuous sound when smartbomb on screen.
;
7ad1: a9 30 EnableBombSound lda #$30
7ad3: 85 8b sta engine_sfx_freq ;init frequency
7ad5: a5 8a lda engine_sfx_flags ;add bomb to flags
7ad7: 09 80 ora #%10000000
7ad9: 85 8a sta engine_sfx_flags
7adb: 60 rts
;
; Enables continuous sound when flier on screen.
;
EnableFlierSound
7adc: a5 8a lda engine_sfx_flags ;add flier to flags
7ade: 09 40 ora #%01000000
7ae0: 85 8a sta engine_sfx_flags
7ae2: 30 04 bmi :Return ;branch if bomb is active
7ae4: a9 70 lda #$70 ;otherwise, init frequency
7ae6: 85 8b sta engine_sfx_freq
7ae8: 60 :Return rts
;
; Disables smartbomb sound.
;
DisableBombSound
7ae9: ad da 00 lda: num_live_bombs ;check number of live bombs
7aec: d0 1d bne :Return ;at least one still alive; bail
7aee: a5 8a lda engine_sfx_flags ;remove bomb from flags
7af0: 29 7f and #%01111111
7af2: 85 8a sta engine_sfx_flags
7af4: 24 8a bit engine_sfx_flags ;is the flier sfx enabled?
7af6: 70 e4 bvs EnableFlierSound ;yes, update audio; otherwise fall through
;
; Disables flier sound.
;
DisableFlierSound
7af8: a5 8a lda engine_sfx_flags ;remove flier from flags
7afa: 29 bf and #%10111111
7afc: 85 8a sta engine_sfx_flags
7afe: ad da 00 lda: num_live_bombs ;check number of live bombs
7b01: d0 08 bne :Return ;at least one bomb; bail
7b03: 85 8a sta engine_sfx_flags ;turn off engine sound (A-reg=0)
7b05: 8d 06 40 sta POKEY_AUDF4 ;silence audio channel 4
7b08: 8d 07 40 sta POKEY_AUDC4
7b0b: 60 :Return rts
;
; Initializes the POKEY, silencing all sound channels.
;
7b0c: a9 00 InitPokey lda #%00000000 ;disable everything
7b0e: 8d 0f 40 sta POKEY_SKCTL
7b11: a9 03 lda #%00000011 ;enable KB scan and debounce
7b13: 8d 0f 40 sta POKEY_SKCTL
;
7b16: a2 07 ldx #$07 ;write 8 locations
7b18: a9 00 lda #$00
7b1a: 9d 00 40 :Loop sta POKEY_AUDF1,x ;zero freq and control for all 4 channels
7b1d: 95 6a sta audio_indices,x ;init audio controller
7b1f: 95 72 sta audio_values,x
7b21: ca dex
7b22: 10 f6 bpl :Loop
;
7b24: a9 20 lda #%00100000 ;bit 5: set channel 3 frequency 1.79 MHz
7b26: 8d 08 40 sta POKEY_AUDCTL
7b29: 60 rts
;
; Handles IRQ.
;
; This fires every 4x per screen refresh, ~240Hz. If it arrives during VBLANK,
; we trigger the game code to execute after we finish.
;
; Start by confirming that the system is in a good state.
;
7b2a: 48 HandleIRQ pha ;preserve A/X/Y-reg
7b2b: 8a txa
7b2c: 48 pha
7b2d: 98 tya
7b2e: 48 pha
7b2f: d8 cld ;clear decimal mode (required on 6502)
7b30: ba tsx ;check stack level
7b31: e0 e0 cpx #$e0 ;dropped below $1e0?
7b33: 90 16 bcc HandleReset ;yes, reset system
7b35: bd 04 01 lda stack_plus4,x ;check saved P flags
7b38: 29 10 and #%00010000 ;check BRK bit
7b3a: d0 0f bne HandleReset ;if we hit a software break, fail
7b3c: bd 06 01 lda stack_plus6,x ;check high byte of interrupted PC
7b3f: c9 50 cmp #$50 ;< $5000?
7b41: 90 08 bcc HandleReset ;yes, fail
7b43: c9 80 cmp #$80 ;>= $8000?
7b45: b0 04 bcs HandleReset ;yes, fail
7b47: a5 9f lda exec_flag ;check if main loop is running
7b49: 10 1f bpl DoHandleIRQ ;all is well, branch to IRQ implementation
;
; Handles RESET signal. Various things branch here if they panic.
;
7b4b: a2 ff HandleReset ldx #$ff ;init stack pointer to top
7b4d: 9a txs
7b4e: d8 cld ;clear decimal mode, just in case
7b4f: e8 inx ;X-reg=0
7b50: 8a txa ;A-reg=0
7b51: 78 sei ;disable interrupts
7b52: 95 00 :ZeroLoop sta $00,x ;zero out zero page
7b54: 9d 00 01 sta STACK,x ;zero out stack
7b57: ca dex
7b58: d0 f8 bne :ZeroLoop
7b5a: a9 40 lda #%01000000 ;reset screen-flip bit, light LEDs
7b5c: 8d 00 48 sta OUT0
7b5f: 2c 00 49 bit IN1 ;check self test switch
7b62: 50 03 bvc :Jmp_SelfTest ;bit 6 clear, do self test
7b64: 4c 01 50 jmp START ;start game
7b67: 4c 82 7c :Jmp_SelfTest jmp SelfTest
;
; System seems fine, do the IRQ processing.
;
7b6a: 8d 00 4c DoHandleIRQ sta WATCHDOG ;feed the dog
7b6d: e6 fb inc irq_counter ;inc counter used for periodic events
7b6f: ad 00 49 lda IN1 ;check vblank flag
7b72: 10 34 bpl :NotVblank ;not set, branch
;
; We're in VBLANK, do some extra stuff.
;
7b74: e6 9f inc exec_flag ;tell non-interrupt code to run
7b76: a5 d8 lda indic_phase_counter ;update counter used to flash the "1UP" indicator
7b78: f0 02 beq :NoDec ;don't decrement below zero
7b7a: c6 d8 dec indic_phase_counter
; Copy color values, cycling if appropriate. Non-IRQ code should set the mask
; before setting values.
7b7c: a5 ec :NoDec lda color_cyc_mask
7b7e: 85 f3 sta r10_irq_mod ;save copy in ZP (uses $f3 as temporary)
7b80: a2 07 ldx #$07 ;check 8 entries
7b82: 06 f3 :ColorLoop asl r10_irq_mod ;test first bit
7b84: 90 02 bcc :NoInc ;not set, leave color alone
7b86: f6 e4 inc color_palette,x ;increment the value
7b88: b5 e4 :NoInc lda color_palette,x
7b8a: 9d 00 4b sta COLOR_PAL,x ;copy color to display hardware
7b8d: ca dex ;done yet?
7b8e: 10 f2 bpl :ColorLoop ;no, loop
; Latch R10. (Not sure why we only do this in VBLANK, though there's no value
; in doing it more often.)
7b90: ad 00 4a lda SWITCH_R10 ;get R10 dip switch values
7b93: 49 02 eor #%00000010 ;flip the sense of bit 1 (part of coins-per-play)
7b95: 85 f3 sta r10_irq_mod ;save in ZP
; Update OUT0 setting. We want to set bit 6 to 1 for normal orientation, 0 for
; flipped. The decision hinges on whether player 2 is up, so we need to check
; $fc (which inverts the meaning of bit 6, i.e. 1 is flipped).
7b97: a5 f5 lda out0_bits ;get previous OUT0 value
7b99: 29 bf and #%10111111 ;zero screen flip bit
7b9b: a8 tay ;copy to Y-reg
7b9c: a5 fc lda cocktail_flip ;get current flip bits
7b9e: 29 3e and #%00111110 ;check middle bits
7ba0: d0 04 bne :DoFlip ;nonzero, we're cocktail+player2, so branch
7ba2: 98 tya ;put previous OUT0 back in A-reg
7ba3: 09 40 ora #%01000000 ;set the not-flipped bit
7ba5: a8 tay ;back to Y-reg
7ba6: 84 f5 :DoFlip sty out0_bits ;set value to write to OUT0
; Done with VBLANK-only stuff.
;
; Read inputs.
7ba8: ae 00 49 :NotVblank ldx IN1 ;read current state of IN1
7bab: 86 ee stx in01_current+1 ;save in ZP
;
7bad: a5 f5 lda out0_bits ;get bits for OUT0
7baf: 29 fe and #%11111110 ;clear low bit (CTRLD)
7bb1: a8 tay ;save in Y-reg
7bb2: ae 00 48 ldx IN0 ;read current IN0 value (trackball)
7bb5: 86 f6 stx in0_trkball1 ;save in ZP
7bb7: 8c 00 48 sty OUT0 ;set CTRLD=0
7bba: ae 00 48 ldx IN0 ;read switches
7bbd: 86 ed stx in01_current ;save in ZP
7bbf: 09 01 ora #%00000001 ;set low bit (CTRLD)
7bc1: 8d 00 48 sta OUT0 ;set CTRLD=1
7bc4: ae 00 48 ldx IN0 ;read trackball again; this will be written to
7bc7: 86 f8 stx in0_trkball2 ; the "prev" value later
; Capture trackball movement. The value read from IN0 gives 4 bits of position
; horizontally and vertically. We subtract that from the previous position to
; get a signed 4-bit movement delta, which we add to results from previous
; frames. The accumulated delta gets zeroed out by the main program ($5140),
; but we run at least 4x as often.
7bc9: a5 f6 lda in0_trkball1 ;get movement values
7bcb: 38 sec
7bcc: e5 f7 sbc in0_trk_prev ;subtract previous value
7bce: 29 0f and #%00001111 ;mod 32
7bd0: c9 08 cmp #$08 ;was value positive (0-7)? (roll right)
7bd2: 90 02 bcc :PosH ;yes, branch
7bd4: 09 f0 ora #$f0 ;sign-extend negative
7bd6: 18 :PosH clc
7bd7: 65 94 adc trk_h_delta ;add to previous delta
7bd9: 85 94 sta trk_h_delta ;store result
;
7bdb: a5 f6 lda in0_trkball1 ;get movement values
7bdd: 38 sec
7bde: 09 0f ora #$0f ;fill low nibble with 1s to prevent borrow
7be0: e5 f7 sbc in0_trk_prev ;subtract previous value
7be2: 4a lsr A ;right-shift to get vertical displacement
7be3: 4a lsr A ;also performs mod 32
7be4: 4a lsr A
7be5: 4a lsr A
7be6: c9 08 cmp #$08 ;was value positive (0-7)? (roll upward)
7be8: 90 02 bcc :PosV ;yes, branch
7bea: 09 f0 ora #$f0 ;sign-extend negative
7bec: 18 :PosV clc
7bed: 65 95 adc trk_v_delta ;add to previous delta
7bef: 85 95 sta trk_v_delta ;store result
;
7bf1: a5 f8 lda in0_trkball2 ;get second IN0 read
7bf3: 85 f7 sta in0_trk_prev ;update previous-trackball value
; Read R8 switches, which show up at the POKEY_ALLPOT address.
7bf5: a9 07 lda #$07
7bf7: 8d 0f 40 sta POKEY_SKCTL ;enable pot scan and debounce
7bfa: 8d 0b 40 sta POKEY_POTGO ;start potentiometer scan
7bfd: ad 08 40 lda SWITCH_R8 ;read R8 switches
7c00: 49 7f eor #%01111111 ;invert bits, except bit 7 (unused?)
7c02: 85 f4 sta r8_irq_inv ;save to ZP
;
7c04: 24 f2 bit in1_value ;check debounced IN1
7c06: 50 06 bvc :DebounceIN01 ;self-test switch is enabled, skip the next couple things
7c08: 20 37 7a jsr UpdateAudio ;update audio output
7c0b: 20 fc 77 jsr CoinsAndCredits ;manage the coin slots
; Process IN0/IN1, with debouncing.
;
; $ed/ee hold the "current" values from IN0 and IN1. The low three bits of IN1
; have the fire button state for player 1, while the low three bits of IN0 have
; the fire button state for player 2.
;
; $ef/f0 hold the values from the "previous" IRQ period. These are overwritten
; with the "current" values.
;
; $f1/f2 holds a "merged" version, equal to:
; (current | previous) & ((current & previous) | merged)
;
; C P M -> P M
; 0 0 0 0 0
; 0 0 1 0 0
; 0 1 0 0 0
; 0 1 1 0 1
; 1 0 0 1 0
; 1 0 1 1 1
; 1 1 0 1 1
; 1 1 1 1 1
;
; The "merged" value matches the "current" value unless current/previous don't
; match, and current/merged don't match. So a button state change that is only
; visible for one IRQ period won't cause the merged value to change.
7c0e: a2 01 :DebounceIN01 ldx #$01
7c10: b4 ed :Loop ldy in01_current,x ;Y-reg = in0 or in1 value
7c12: b5 ef lda in01_prev,x ;A-reg = previous value
7c14: 94 ef sty in01_prev,x ;store new value
7c16: a8 tay ;put previous value in Y-reg
7c17: 35 ef and in01_prev,x ;AND new and previous values
7c19: 15 f1 ora in0_value,x ;combine with f1
7c1b: 95 f1 sta in0_value,x
7c1d: 98 tya ;put previous value in A-reg
7c1e: 15 ef ora in01_prev,x
7c20: 35 f1 and in0_value,x
7c22: 95 f1 sta in0_value,x
7c24: ca dex
7c25: 10 e9 bpl :Loop
; Read the fire buttons for the current player. The goal is to set a 1 bit in
; $fa when a button is pressed. The main code will clear it to zero after it
; has been read. A cocktail cabinet has two sets of buttons, so if player 2 is
; active we need to read the other set.
;
; Input bits are 0 when the button is pressed. We want to set bits in $fa when
; a button is pressed, and hold them until the main code has a chance to latch
; the value and reset $fa to zero. We don't want to set the bits again until
; after the button has been released.
;
; The state machine looks like this:
; Idle: IN1=111 f9=111 fa=000 -> fa=000 f9=111
; Press: IN1=110 f9=111 fa=000 -> fa=001 f9=110
; Wait: IN1=110 f9=110 fa=001 -> fa=001 f9=110
; Latch: IN1=110 f9=110 fa=000 -> fa=000 f9=110
; Hold: IN1=110 f9=110 fa=000 -> fa=000 f9=110
; Release: IN1=111 f9=110 fa=000 -> fa=000 f9=111
;
; If the player managed to release the button during "wait", before the result
; was latched, it still works correctly:
; Early: IN1=111 f9=110 fa=001 -> fa=001 f9=111
;
7c27: a4 f2 ldy in1_value ;get IN1 bits (incl. player 1 fire buttons)
7c29: a5 fc lda cocktail_flip ;check cocktail / flip flags
7c2b: 29 3e and #%00111110 ;are we both cocktail and player 2?
7c2d: f0 02 beq :Player1 ;no, branch
7c2f: a4 f1 ldy in0_value ;get IN0 bits (with the player 2 fire buttons)
7c31: 98 :Player1 tya
7c32: 49 ff eor #%11111111 ;do latch logic as described in comment above
7c34: 25 f9 and prev_plyr_in01 ; ...
7c36: 45 fa eor fire_buttons
7c38: 85 fa sta fire_buttons
7c3a: 84 f9 sty prev_plyr_in01
; Manage the LEDs on the start buttons.
7c3c: a5 f5 lda out0_bits ;get last value written to OUT0
7c3e: 29 40 and #%01000000 ;strip everything but the screen-flip bit
7c40: 09 06 ora #%00000110 ;set both "LED off" bits
7c42: a6 ae ldx num_players ;get number of players (0/1/2)
7c44: e8 inx ;increment (now 1/2/3)
7c45: a4 93 ldy play_mode_flag ;is a game in progress?
7c47: d0 10 bne :MaskPlyrLeds ;yes, branch
;
7c49: a2 00 ldx #$00 ;start with "both dark" mask index
7c4b: a4 fb ldy irq_counter ;use IRQ count to flash LEDs
7c4d: c0 40 cpy #64 ;leave dark 25% of the time (~1/4 sec)
7c4f: 90 08 bcc :MaskPlyrLeds ;branch to leave dark
7c51: a6 66 ldx credit_count ;get number of credits paid for
7c53: e0 02 cpx #$02 ;1 credit flashes player 1 LED, 2 credits flashes
7c55: 90 02 bcc :MaskPlyrLeds ; player 2 LED, 3+ credits flashes both
7c57: a2 03 ldx #$03
7c59: 3d 7e 7c :MaskPlyrLeds and led_flash_masks,x ;light up appropriate LEDs
; Do some coin counter housekeeping.
7c5c: a4 63 ldy coin_drop_ctr ;check left coin drop counter
7c5e: 10 02 bpl :NotLeft ;if value positive, branch
7c60: 09 20 ora #$20 ;set left coin counter bit
7c62: a4 64 :NotLeft ldy coin_drop_ctr+1 ;check center coin drop counter
7c64: 10 02 bpl :NotCenter ;if value positive, branch
7c66: 09 10 ora #$10 ;set center coin counter bit
7c68: a4 65 :NotCenter ldy coin_drop_ctr+2 ;check right coin drop counter
7c6a: 10 02 bpl :NotRight ;if value positive, branch
7c6c: 09 08 ora #$08 ;set right coin counter bit
7c6e: 85 f5 :NotRight sta out0_bits ;save OUT0 value, to be written next IRQ
;
7c70: 2c 00 49 :VWaitLoop bit IN1 ;check VBLANK
7c73: 30 fb bmi :VWaitLoop ;still in VBLANK, spin until we're not
7c75: 8d 00 4d sta INT_ACK ;acknowledge IRQ
7c78: 68 pla ;restore A/X/Y-reg
7c79: a8 tay
7c7a: 68 pla
7c7b: aa tax
7c7c: 68 pla
7c7d: 40 rti
7c7e: ff led_flash_masks .dd1 %11111111 ;both off
7c7f: fd .dd1 %11111101 ;player 1 LED on
7c80: fb .dd1 %11111011 ;player 2 LED on
7c81: f9 .dd1 %11111001 ;both LEDs on
;
; Performs self tests. The code switches from the main program to this when the
; self-test switch is enabled.
;
; On entry, interrupts are disabled.
;
• Clear variables
7c82: a9 00 SelfTest lda #$00 ;init audio
7c84: 8d 0f 40 sta POKEY_SKCTL
7c87: 8d 05 40 sta POKEY_AUDC3
7c8a: 8d 07 40 sta POKEY_AUDC4
7c8d: 8d 08 40 sta POKEY_AUDCTL
7c90: a9 03 lda #$03
7c92: 8d 0f 40 sta POKEY_SKCTL
7c95: a9 08 lda #%00001000 ;middle volume, some noise (sounds explody)
7c97: 8d 03 40 sta POKEY_AUDC2
7c9a: a9 c0 lda #$c0 ;low frequency
7c9c: 8d 02 40 sta POKEY_AUDF2
7c9f: a9 44 lda #%01000100 ;init screen flip, light player 1 LED
7ca1: 8d 00 48 sta OUT0
; Make sure VBLANK is working.
7ca4: a0 60 ldy #96 ;do this for about 1.5 seconds
7ca6: 2c 00 49 :WaitVEnd bit IN1 ;check VBLANK state
7ca9: 30 fb bmi :WaitVEnd ;spin until we're not in VBLANK
7cab: 2c 00 49 :WaitVStart bit IN1
7cae: 10 fb bpl :WaitVStart ;now spin until we're back in VBLANK
7cb0: 8d 00 4c sta WATCHDOG ;feed the dog
7cb3: 88 dey ;done yet?
7cb4: 10 f0 bpl :WaitVEnd ;no, loop
; Test zero page and stack RAM. We eventually set every location to all 256
; possible values. Each pass puts a different value into every location on the
; page, and different values are written to ZP/stack values at the same offset,
; to check that address decoding is working.
7cb6: a9 a2 lda #%10100010 ;switch to pure tone, drop volume
7cb8: 8d 03 40 sta POKEY_AUDC2
7cbb: a0 00 ldy #$00 ;init test value
7cbd: a2 00 :ValueLoop ldx #$00 ;init memory index
7cbf: 94 00 :WriteLoop sty $00,x ;write value to ZP
7cc1: c8 iny ;increment value
7cc2: 98 tya
7cc3: 9d 00 01 sta STACK,x ;write value+1 to stack
7cc6: e8 inx ;advance pointer
7cc7: d0 f6 bne :WriteLoop ;loop until full page written, and Y-reg
7cc9: 8d 00 4c sta WATCHDOG ; is back to its initial value
;
7ccc: 98 :CheckLoop tya ;get value in A-reg
7ccd: 55 00 eor $00,x ;test value in ZP, result should be zero
7ccf: 95 00 sta $00,x ;store the zero (to init ZP RAM for later)
7cd1: d0 1d bne :BadRam? ;wasn't zero, report failure
7cd3: c8 iny ;increment value
7cd4: 98 tya
7cd5: 5d 00 01 eor STACK,x ;test value on stack, result should be zero
7cd8: 9d 00 01 sta STACK,x ;store the zero to init stack RAM
7cdb: d0 13 bne :BadRam? ;wasn't zero, report failure
7cdd: e8 inx ;advance pointer; done yet?
7cde: d0 ec bne :CheckLoop ;no, branch
7ce0: c8 iny ;Y-reg back to initial value; increment it
7ce1: d0 da bne :ValueLoop ;branch if we haven't tried all 256 values
;
7ce3: a9 40 lda #$40 ;change to higher frequency
7ce5: 8d 02 40 sta POKEY_AUDF2
7ce8: a9 42 lda #%01000010 ;light player 2 LED
7cea: 8d 00 48 sta OUT0
7ced: 4c 39 7d jmp :CheckROM ;RAM done, go check ROM
;
; Report bad RAM. The self-test mechanism plays 8 tones. A low beep is a good
; chip, a high beep is bad RAM.
;
; The system has 16KB of RAM, distributed across 8 RAM chips. Each chip is
; 16Kx1, with each chip holding one bit of each byte. By storing a value and
; expecting zero, we can tell which chip is failing by checking which bit is set
; to 1.
;
; Chip locations (bit 7 to bit 0): P4 N4 M4 L4 K4 J4 H4 F4.
;
; On entry:
; A-reg: set bits indicate failure
;
7cf0: a2 07 :BadRam? ldx #$07 ;repeat 8x
7cf2: a0 00 ldy #$00 ;silence channel 2
7cf4: 8c 03 40 sty POKEY_AUDC2
7cf7: 9a txs ;hold count in stack pointer (don't trust RAM)
7cf8: ba :ErrSoundLoop tsx
7cf9: 2a rol A ;test high bit
7cfa: a0 a0 ldy #$a0 ;good tone (low pitch)
7cfc: 90 02 bcc :BitOk ;bit was clear, use the good tone
7cfe: a0 10 ldy #$10 ;bad tone (high pitch)
7d00: 8c 00 40 :BitOk sty POKEY_AUDF1 ;set on channel 1
7d03: a0 a8 ldy #%10101000 ;pure tone, middle volume
7d05: 8c 01 40 sty POKEY_AUDC1 ;play tone on channel 1
; Hold tone for 20 refreshes (~1/3rd of a second).
7d08: a0 20 ldy #$20 ;do this 20x
7d0a: 2c 00 49 :WaitVEnd1 bit IN1 ;check VBLANK
7d0d: 30 fb bmi :WaitVEnd1 ;in blank, loop
7d0f: 2c 00 49 :WaitVStart1 bit IN1 ;check VBLANK
7d12: 10 fb bpl :WaitVStart1 ;not in blank, loop
7d14: 8d 00 4c sta WATCHDOG ;feed the dog
7d17: 88 dey ;done yet?
7d18: 10 f0 bpl :WaitVEnd1 ;no, loop
7d1a: a0 00 ldy #$00 ;silence channel 1
7d1c: 8c 01 40 sty POKEY_AUDC1
; Play silence for 20 screen refreshes between each beep.
7d1f: a0 14 ldy #20 ;do this 20x
7d21: 2c 00 49 :WaitVEnd2 bit IN1 ;check VBLANK
7d24: 30 fb bmi :WaitVEnd2 ;in blank, loop
7d26: 2c 00 49 :WaitVStart2 bit IN1 ;check VBLANK
7d29: 10 fb bpl :WaitVStart2 ;not in blank, loop
7d2b: 8d 00 4c sta WATCHDOG ;feed the dog
7d2e: 88 dey ;done yet?
7d2f: 10 f0 bpl :WaitVEnd2 ;no, loop
7d31: ba tsx
7d32: ca dex ;decrement count held in stack pointer
7d33: 9a txs
7d34: 10 c2 bpl :ErrSoundLoop
7d36: 4c 4b 7b jmp HandleReset ;restart system
;
; Verify ROM checksums.
]vid_ptr .var $00 {addr/2}
]ptr .var $06 {addr/2}
]rom_fail_map .var $8d {addr/1}
]odd_thing? .var $99 {addr/1}
7d39: a2 00 :CheckROM ldx #$00
7d3b: 86 06 stx ]ptr ;set pointer low byte to zero
7d3d: 86 8d stx ]rom_fail_map ;init failure map
7d3f: 9a txs ;set stack pointer to zero
7d40: a9 50 lda #>START
7d42: 85 07 sta ]ptr+1 ;set high byte to start of ROM
7d44: a2 2f ldx #$2f ;page count ($5000 to 7fff)
7d46: a9 12 lda #18
7d48: 85 99 sta ]odd_thing? ;init to 18 (why?)
;
7d4a: a0 00 :RomLoop1 ldy #$00
7d4c: 8e 00 4c stx WATCHDOG ;feed the dog
7d4f: 51 06 :RomLoop2 eor (]ptr),y ;EOR all bytes in the page
7d51: c8 iny
7d52: d0 fb bne :RomLoop2
;
7d54: a8 tay ;copy result to Y-reg
7d55: 8a txa ;transfer page count to A-reg
7d56: 29 07 and #%00000111 ;mod 8 (2KB of pages)
7d58: c9 01 cmp #$01 ;have we finished a ROM chip?
7d5a: 98 tya ;transfer checksum result back to A-reg
7d5b: b0 18 bcs :NextPg ;if page count >= 1, move to next page
7d5d: f0 10 beq :UpdatePg1 ;if checksum was okay, branch
; Found a bad checksum. Push the bad value on the stack.
7d5f: 48 pha ;push failed value on stack (not used here)
7d60: 8a txa ;copy pages remaining (0-40) to A-reg
7d61: 48 pha ;preserve X-reg
7d62: 4a lsr A ;divide by 8 (0-5)
7d63: 4a lsr A ; to get ROM chip number
7d64: 4a lsr A
7d65: aa tax ;use as index
7d66: bd f9 60 lda single_bits+2,x ;get Nth bit
7d69: 05 8d ora ]rom_fail_map ;add to failure map
7d6b: 85 8d sta ]rom_fail_map
7d6d: 68 pla ;restore X-reg
7d6e: aa tax
7d6f: a5 99 :UpdatePg1 lda ]odd_thing? ;add 34 (why?)
7d71: 69 22 adc #34 ;(carry always clear)
7d73: 85 99 sta ]odd_thing? ;(34*7+18 = 256, but this isn't used?)
;
7d75: e6 07 :NextPg inc ]ptr+1 ;advance to next page
7d77: ca dex ;decrement page count
7d78: 10 d0 bpl :RomLoop1 ;branch if not done
; Prepare to display text.
7d7a: 58 cli ;enable interrupts
7d7b: 20 86 69 jsr ClearScreen ;erase screen
7d7e: 20 2f 67 jsr InitLangAndFlip ;prepare text output
7d81: a9 00 lda #%00000000 ;white
7d83: 85 ea sta color_palette+6 ; for color index 6
7d85: a9 0e lda #%00001110 ;black
7d87: 85 e4 sta color_palette ; for color index 0
7d89: 85 93 sta play_mode_flag
;
7d8b: a9 22 lda #$22 ;"BAD ROM"
7d8d: ba tsx ;did we push anything on the stack?
7d8e: d0 02 bne :RomComm ;yes, report bad ROM
7d90: a9 20 lda #$20 ;"ROM OK"
7d92: a2 ff :RomComm ldx #$ff ;reset stack pointer
7d94: 9a txs
7d95: 20 3b 7f jsr PrintSetBits ;print message and failed ROM numbers, if any
;
; Test video memory mapping. Write $c0 to $8080 using ZP indexed indirect
; addressing to trigger MADSEL. If the mapping is working, $11 will appear at
; $2020.
7d98: a9 80 lda #$80 ;create pointer to $8080
7d9a: 85 00 sta ]vid_ptr
7d9c: 85 01 sta ]vid_ptr+1
7d9e: a2 00 ldx #$00
7da0: a9 c0 lda #%11000000 ;set pixel to %110 (color 6)
7da2: 81 00 sta (]vid_ptr,x) ;write in MADSEL mode
7da4: a0 24 ldy #$24 ;"MAP OK"
7da6: ad 20 20 lda VIDEO_RAM_2+$19e0 ;read 2bpp pixel value
7da9: 29 11 and #%00010001 ;ignore adjacent pixels
7dab: c9 11 cmp #%00010001 ;confirm both bits were set
7dad: f0 02 beq :GoodVidWrite ;if so, branch
7daf: a0 23 ldy #$23 ;"BAD MAP"
7db1: 98 :GoodVidWrite tya
7db2: 20 51 6a jsr PrintMessage ;report result
;
; Test random number generation.
]prng_tmp .var $fd {addr/1}
7db5: ad 0a 40 lda POKEY_RANDOM ;generate random number
7db8: 85 fd sta ]prng_tmp ;stash it
7dba: a0 10 ldy #16 ;try 17x to get a different value
7dbc: ad 0a 40 :TstRndLoop lda POKEY_RANDOM ;generate new random number
7dbf: c5 fd cmp ]prng_tmp ;is it the one we just generated?
7dc1: d0 0a bne :NotSame ;no, PRNG is working; branch
7dc3: 85 fd sta ]prng_tmp ;write it to ZP (why?)
7dc5: 88 dey ;decrement counter
7dc6: 10 f4 bpl :TstRndLoop ;loop until done
7dc8: a9 27 lda #$27 ;"BAD CHIP"
7dca: 20 51 6a jsr PrintMessage
;
7dcd: a9 1f :NotSame lda #$1f ;"RAM OK" (we don't get this far if RAM is bad)
7dcf: 20 51 6a jsr PrintMessage
;
]r10_val .var $fd {addr/1}
]r8_val .var $fe {addr/1}
]draw_config_flag .var $ff {addr/1}
7dd2: a9 02 lda #$02
7dd4: 85 ae sta num_players ;set number of players to invalid value
7dd6: a9 00 lda #$00
7dd8: 85 b9 sta cur_plyr_num ;set player 1 as current
7dda: 20 5c 65 jsr InitCrosshairs ;init crosshair position
7ddd: a5 f3 lda r10_irq_mod ;get R10 switches
7ddf: 49 ff eor #$ff ;invert them
7de1: 85 fd sta ]r10_val ;stash in ZP
7de3: a9 01 lda #$01
7de5: 85 ff sta ]draw_config_flag ;set flag so we draw the config
7de7: 8d 08 40 sta POKEY_AUDCTL ;set frequency divider rate to 15KHz
;
; Main loop for test code. We wake on every IRQ and check for changes.
;
7dea: 46 9f :TestIdleLoop lsr exec_flag ;spin until the IRQ tells us to go
7dec: 90 fc bcc :TestIdleLoop
;
; Check for some special button combinations. These don't appear to be
; documented in the manual.
7dee: a5 f2 lda in1_value ;get processed IN1 value
7df0: 29 24 and #%00100100 ;slam switch and left fire button?
7df2: d0 05 bne :NotGrid ;no, branch
7df4: 85 ff sta ]draw_config_flag ;set to zero
7df6: 20 7f 7f jsr Test_DrawGrid ;draw grid
;
7df9: a5 f2 :NotGrid lda in1_value ;get processed IN1 value
7dfb: 29 21 and #%00100001 ;slam switch and right fire button?
7dfd: d0 05 bne :NotBars ;no, branch
7dff: 85 ff sta ]draw_config_flag ;set to zero
7e01: 20 62 7f jsr Test_DrawBars ;draw color bars
;
7e04: a9 00 :NotBars lda #$00
7e06: 8d 01 40 sta POKEY_AUDC1 ;silence channels 1 and 2
7e09: 8d 03 40 sta POKEY_AUDC2
7e0c: a5 f2 lda in1_value ;get processed IN1 value
7e0e: 29 22 and #%00100010 ;slam switch and middle fire button?
7e10: d0 05 bne :NotScan ;no, branch
7e12: 85 ff sta ]draw_config_flag ;set to zero
7e14: 20 eb 7e jsr Test_ScanVideo ;draw bars then scan down the screen
;
; React to the use of the controls.
;
7e17: 20 32 51 :NotScan jsr UpdateCrossPosn ;move crosshairs when trackball moves
7e1a: a5 f1 lda in0_value ;get IN0 value (switches)
7e1c: 49 ff eor #$ff ;invert bits
7e1e: 8d 00 40 sta POKEY_AUDF1 ;set as frequency on channel 1
7e21: f0 07 beq :NoBut0 ;if no buttons pressed, branch
7e23: a9 a8 lda #%10101000 ;at least one button pressed, enable sound
7e25: 8d 01 40 sta POKEY_AUDC1 ;medium volume, pure tone
7e28: e6 e4 inc color_palette ;change value of color #0
;
7e2a: a5 f2 :NoBut0 lda in1_value ;get IN1 value
7e2c: 49 ff eor #$ff ;invert bits
7e2e: 29 27 and #%00100111 ;mask off all but slam switch and fire buttons
7e30: 8d 02 40 sta POKEY_AUDF2 ;set as frequency on channel 2
7e33: f0 07 beq :NoBut1 ;if no buttons pressed, branch
7e35: a9 a8 lda #%10101000 ;at least one button pressed, enable sound
7e37: 8d 03 40 sta POKEY_AUDC2 ;medium volume, pure tone
7e3a: e6 e4 inc color_palette ;change value of color #0
;
7e3c: a5 fa :NoBut1 lda fire_buttons ;get fire buttons for current player
7e3e: 29 07 and #%00000111 ;mask just to be sure
7e40: f0 17 beq :NoFire ;if none pressed, branch
7e42: a5 b9 lda cur_plyr_num ;toggle current player number
7e44: 49 01 eor #%00000001 ;this flips the screen over in cocktail mode
7e46: 85 b9 sta cur_plyr_num
7e48: a9 00 lda #$00
7e4a: 85 fa sta fire_buttons ;reset fire buttons
7e4c: 20 86 69 jsr ClearScreen ;clear screen
7e4f: a5 fd lda ]r10_val ;get R10 switches
7e51: 49 ff eor #$ff ;invert bits
7e53: 85 fd sta ]r10_val ;save it back
7e55: a9 01 lda #$01
7e57: 85 ff sta ]draw_config_flag ;set to nonzero
;
7e59: 20 6b 7e :NoFire jsr Test_ShowConfig
7e5c: 24 f2 bit in1_value ;check the self-test switch
7e5e: 50 8a bvc :TestIdleLoop ;still enabled, loop
7e60: 4c 4b 7b jmp HandleReset ;no longer enabled, reset system
;
; Coin slot multiplier digits. The center slot can be configured for 1x or 2x,
; the right slot for 1x/4x/5x/6x. The bytes below contain all 8 possibilities.
coin_mult_digits
7e63: 11 14 15 16+ .bulk $11,$14,$15,$16,$21,$24,$25,$26
;
; Draws the system configuration if $ff is nonzero, or if R8/R10 have changed
; since the last time.
;
7e6b: a5 ff Test_ShowConfig lda ]draw_config_flag ;do we want to draw the config?
7e6d: f0 7b beq :Return ;no, bail
7e6f: a5 f3 lda r10_irq_mod ;get latest R10 values
7e71: c5 fd cmp ]r10_val ;does it match previous read?
7e73: 85 fd sta ]r10_val ;update previous
7e75: d0 06 bne :Mismatch ;mismatch, branch
7e77: a5 f4 lda r8_irq_inv ;get latest R8 values
7e79: c5 fe cmp ]r8_val ;does it match previous read?
7e7b: 85 fe sta ]r8_val ;update previous
7e7d: f0 6b :Mismatch beq :Return ;matched, bail
;
7e7f: a9 25 lda #$25 ;erase lines with these messages
7e81: 20 49 6a jsr EraseMessageLine ;"FREE PLAY"
7e84: a9 1b lda #$1b
7e86: 20 49 6a jsr EraseMessageLine ;"BONUS CITY"
7e89: 20 2f 67 jsr InitLangAndFlip ;init language, and flip based on current player
; Draw coin slot multipliers.
7e8c: a5 f3 lda r10_irq_mod ;get R10 switches
7e8e: 29 03 and #%00000011 ;mask all but the coins-per-play bits
7e90: aa tax
7e91: bd 2f 6c lda play_cost_msg_tbl,x ;get the appropriate message ID
7e94: 20 51 6a jsr PrintMessage
7e97: a5 f3 lda r10_irq_mod ;get R10 switches
7e99: 29 1c and #%00011100 ;mask to get coin mechanism config
7e9b: 4a lsr A ;shift to put value in low bits
7e9c: 4a lsr A
7e9d: aa tax ;use as index
7e9e: bd 63 7e lda coin_mult_digits,x ;get digits
7ea1: 48 pha ;preserve value
7ea2: 4a lsr A ;shift 4x to get center slot multiplier
7ea3: 4a lsr A
7ea4: 4a lsr A
7ea5: 4a lsr A
7ea6: a2 40 ldx #64 ;screen position
7ea8: a0 28 ldy #40
7eaa: 20 9d 6a jsr DrawSmallNum ;draw number
7ead: 68 pla ;restore value
7eae: 29 07 and #%00000111 ;mask excess to get right slot multiplier
7eb0: a2 50 ldx #80 ;position a little to the right
7eb2: 20 9d 6a jsr DrawSmallNum ;draw number
;
]horz_posn .var $1d {addr/1}
]vert_posn .var $1e {addr/1}
7eb5: a9 70 lda #112 ;horizontal position
7eb7: 85 1d sta ]horz_posn
7eb9: a9 28 lda #40 ;vertical position
7ebb: 85 1e sta ]vert_posn
7ebd: a5 f4 lda r8_irq_inv ;get R8 switches
7ebf: 29 08 and #%00001000 ;mask off mini-trakball setting
7ec1: f0 02 beq :NotMini ;not set, branch
7ec3: a9 46 lda #‘F’ ;display 'F' for mini trackball
7ec5: 20 ba 76 :NotMini jsr PrintPaddedChar ;or draw blank space for A-reg=0
;
7ec8: a5 f4 lda r8_irq_inv ;get R8 switches
7eca: 29 04 and #%00000100 ;mask off bonus credit for 4 coins setting
7ecc: f0 02 beq :NoCred ;not set, branch
7ece: a9 58 lda #‘X’ ;display 'X' if set
7ed0: 20 ba 76 :NoCred jsr PrintPaddedChar ;or draw blank space for A-reg=0
;
7ed3: a5 f4 lda r8_irq_inv ;get R8 switches
7ed5: 29 03 and #%00000011 ;get setting for number of starting cities
7ed7: aa tax
7ed8: bd 08 5b lda init_city_count,x ;get count
7edb: a2 40 ldx #64
7edd: a0 34 ldy #52
7edf: 20 9d 6a jsr DrawSmallNum ;draw number
7ee2: a9 26 lda #$26 ;"CITIES"
7ee4: 20 51 6a jsr PrintMessage
7ee7: 20 aa 74 jsr PrintBonusCityPts ;print number of points per bonus city
7eea: 60 :Return rts
;
; Exercises video RAM. Draws color bars, then clears the screen, then walks
; through all of video memory. For every byte, we set the byte, test the next
; 255 bytes to see if they're still zero, test the byte to see if it's still
; set, then clear the byte.
;
• Clear variables
]bad_bits .var $8d {addr/1}
]message .var $99 {addr/1}
7eeb: 20 62 7f Test_ScanVideo jsr Test_DrawBars ;draw the color bars with MADSEL writes
7eee: 20 86 69 jsr ClearScreen ;clear the screen with normal writes
7ef1: 78 sei ;disable interrupts
7ef2: a9 00 lda #$00 ;init bad bit set
7ef4: 85 8d sta ]bad_bits
7ef6: a9 1f lda #$1f ;"RAM OK"
7ef8: 85 99 sta ]message
7efa: a9 02 lda #$02 ;start at $0200
7efc: 85 07 sta gfx_ptr+1 ;(low byte and Y-reg zeroed by screen clear)
;
7efe: a9 aa :TestLoop lda #$aa ;alternating bit pattern
7f00: 91 06 sta (gfx_ptr),y ;write to video memory area
7f02: c8 iny ;advance pointer
;
7f03: b1 06 :ReadLoop lda (gfx_ptr),y ;get next byte, which should be zero
7f05: f0 03 beq :ReadOk ;branch if so
7f07: 20 32 7f jsr :Bad2 ;note bad pattern
7f0a: c8 :ReadOk iny ;advance pointer
7f0b: d0 f6 bne :ReadLoop ;loop until we've read 255 entries
;
7f0d: b1 06 lda (gfx_ptr),y ;get original entry
7f0f: c9 aa cmp #$aa ;is it what we set it to?
7f11: f0 03 beq :WriteOk ;yes, branch
7f13: 20 30 7f jsr :Bad1 ;note bad pattern
;
7f16: a9 00 :WriteOk lda #$00
7f18: 91 06 sta (gfx_ptr),y ;set byte back to zero
7f1a: 85 9f sta exec_flag ;clear the exec flag (shouldn't matter, IRQ disabled)
7f1c: 8d 00 4c sta WATCHDOG ;feed the dog
7f1f: e6 06 inc gfx_ptr ;advance pointer one byte
7f21: d0 db bne :TestLoop ;loop over all entries on this page
7f23: e6 07 inc gfx_ptr+1 ;increment high byte of pointer
7f25: a5 07 lda gfx_ptr+1
7f27: c9 3f cmp #$3f ;have we reached the end of video RAM?
7f29: d0 d3 bne :TestLoop ;not yet, loop
7f2b: a5 99 lda ]message
7f2d: 4c 3b 7f jmp PrintSetBits ;display message and failed bits, if any
7f30: 49 aa :Bad1 eor #$aa ;flip bits we stored
7f32: 05 8d :Bad2 ora ]bad_bits ;add in the bad bits
7f34: 85 8d sta ]bad_bits
7f36: a9 21 lda #$21 ;"BAD RAM"
7f38: 85 99 sta ]message
7f3a: 60 rts
;
; Prints set bits, as a series of single-digit numbers (0-7), vertically upward.
;
; On entry:
; A-reg: message index
; $8d: bit map
;
• Clear variables
]bit_set .var $8d {addr/1}
]saved_index .var $c5 {addr/1}
7f3b: 58 PrintSetBits cli ;enable interrupts
7f3c: 20 51 6a jsr PrintMessage ;print message
7f3f: a5 8d lda ]bit_set ;get the set of bits
7f41: a2 08 ldx #$08 ;walk through all 8
7f43: 0a :Loop asl A ;shift high bit into carry
7f44: 90 18 bcc :Next ;not set, branch
7f46: 48 pha ;preserve A-reg
7f47: 86 c5 stx ]saved_index ;preserve X-reg
7f49: a9 e0 lda #%11100000 ;color = 7 (bug? should be 6)
7f4b: 85 0b sta draw_fg_color
7f4d: 8a txa ;get bit index in A-reg
7f4e: 0a asl A ;multiply by 8 (the width of a glyph)
7f4f: 0a asl A
7f50: 0a asl A
7f51: 18 clc
7f52: 69 88 adc #136 ;add base vertical position
7f54: a8 tay ;copy to Y-reg
7f55: 8a txa ;use bit index (1-8) as number to draw
7f56: a2 a0 ldx #160 ;set horizontal position
7f58: 20 9d 6a jsr DrawSmallNum ;draw number
7f5b: a6 c5 ldx ]saved_index ;restore bit index
7f5d: 68 pla ;restore bit set
7f5e: ca :Next dex ;decrement bit index
7f5f: d0 e2 bne :Loop ;loop until done
7f61: 60 rts
;
; Draws color bars.
;
7f62: a2 07 Test_DrawBars ldx #$07 ;set 8 colors
7f64: 8a :ColorLoop txa
7f65: 0a asl A ;double index
7f66: 95 e4 sta color_palette,x ;use as color value
7f68: ca dex
7f69: 10 f9 bpl :ColorLoop
;
7f6b: e8 inx ;X-reg=0
7f6c: 86 06 stx gfx_ptr ;init low byte of pointer
7f6e: a0 18 :Loop1 ldy #$18 ;(should start at $1900? bug?)
7f70: 84 07 sty gfx_ptr+1
7f72: a5 06 :Loop2 lda gfx_ptr ;use the high 2 bits of the low byte of the
7f74: 81 06 sta (gfx_ptr,x) ; pointer as the color index
7f76: e6 07 inc gfx_ptr+1 ;incr high byte
7f78: d0 f8 bne :Loop2 ; until we walk off the end of memory
7f7a: e6 06 inc gfx_ptr ;incr low byte
7f7c: d0 f0 bne :Loop1 ;loop, resetting high byte
7f7e: 60 rts
;
; Draws a 5x5 test grid that spans the full screen.
;
]hindex .var $98 {addr/1}
7f7f: a9 0e Test_DrawGrid lda #%00001110 ;black
7f81: a2 07 ldx #$07
7f83: 95 e4 :Loop sta color_palette,x ;set all color values
7f85: ca dex
7f86: 10 fb bpl :Loop
7f88: a9 00 lda #%00000000 ;white
7f8a: 85 e6 sta color_palette+2 ;set color #2
7f8c: 20 86 69 jsr ClearScreen ;erase screen to color 0
;
7f8f: a2 00 ldx #$00
7f91: a0 05 ldy #$05 ;drawing 6 vertical lines
7f93: a9 19 :HGridLoop lda #$19 ;top line is at $1900
7f95: 85 07 sta gfx_ptr+1 ;set as high byte of pointer
7f97: b9 e2 7f lda :test_grid_horz,y ;get horizontal offset of this line
7f9a: 85 06 sta gfx_ptr ;set low byte of pointer
7f9c: a9 40 lda #%01000000 ;color 2
7f9e: 81 06 :HLineLoop sta (gfx_ptr,x) ;set pixel
7fa0: e6 07 inc gfx_ptr+1 ;advance high byte to move down screen
7fa2: d0 fa bne :HLineLoop ;continue until high byte rolls over
7fa4: 88 dey ;move to next line
7fa5: 10 ec bpl :HGridLoop ;loop until done
;
7fa7: a0 05 ldy #$05 ;drawing 6 horizontal lines
7fa9: b9 e8 7f :VGridLoop lda :test_grid_vert,y ;get high byte of row address
7fac: 85 07 sta gfx_ptr+1 ;set pointer
7fae: 86 06 stx gfx_ptr ;set low byte to zero
7fb0: a9 40 lda #%01000000 ;color 2
7fb2: 81 06 :VLineLoop sta (gfx_ptr,x) ;set pixel
7fb4: e6 06 inc gfx_ptr ;move across row
7fb6: d0 fa bne :VLineLoop ;continue until we reach the end
7fb8: 88 dey ;move to next line
7fb9: 10 ee bpl :VGridLoop ;loop until done
; Put dots in the boxes.
7fbb: a0 04 ldy #$04 ;5 boxes across
7fbd: b9 e2 7f :DotLoop1 lda :test_grid_horz,y ;get left side of box
7fc0: 18 clc
7fc1: 79 e3 7f adc :test_grid_horz+1,y ;add to right side of box
7fc4: 6a ror A ;divide by 2 to get center
7fc5: 85 06 sta gfx_ptr ;set as pointer low byte
7fc7: 84 98 sty ]hindex ;save index
;
7fc9: a0 04 ldy #$04 ;5 boxes high
7fcb: b9 e8 7f :DotLoop2 lda :test_grid_vert,y ;get top row pointer
7fce: 18 clc
7fcf: 79 e9 7f adc :test_grid_vert+1,y ;add bottom row pointer
7fd2: 6a ror A ;divide by 2 to get center
7fd3: 85 07 sta gfx_ptr+1 ;set as pointer high byte
7fd5: a9 40 lda #%01000000 ;color 2
7fd7: 81 06 sta (gfx_ptr,x) ;set pixel
7fd9: 88 dey ;done all boxes in this column?
7fda: 10 ef bpl :DotLoop2 ;not yet, loop
7fdc: a4 98 ldy ]hindex ;get horizontal index
7fde: 88 dey ;done all columns?
7fdf: 10 dc bpl :DotLoop1 ;not yet, loop
7fe1: 60 rts
;
; Vertical and horizontal positions for grid lines, stored as pointer values.
;
7fe2: 00 33 66 99+ :test_grid_horz .bulk $00,$33,$66,$99,$cc,$ff
7fe8: 19 46 74 a2+ :test_grid_vert .bulk $19,$46,$74,$a2,$d0,$ff
;
7fee: d8 .dd1 $d8 ;checksum byte
7fef: 00 00 00 00+ .junk 11 ;wasted space!
;
; 6502 vectors.
;
7ffa: 4b 7b .dd2 HandleReset ;NMI vector
7ffc: 4b 7b .dd2 HandleReset ;reset vector
7ffe: 2a 7b .dd2 HandleIRQ ;IRQ vector
.adrend ↑ $5000