; From: http://www.deater.net/weave/vmwprod/tb1/tb_6502.html .segment "INIT" .segment "ONCE" ; don't use $800-$1fff :( .segment "STARTUP" jmp Start ; use $4000- :) .segment "CODE" .define EQU = ; blt = bcc ; bge = bcs ;; ZERO PAGE CH EQU $24 CV EQU $25 BASL EQU $28 BASH EQU $29 H2 EQU $2C COLOR EQU $30 YSAV EQU $34 YSAV1 EQU $35 RANDOM_SEED EQU $43 ;; Our Zero Page Allocations PADDLE_STATUS EQU $CA HISCORE_1 EQU $CB HISCORE_2 EQU $CC HISCORE_3 EQU $CD HISCORE_H EQU $CE HISCORE_L EQU $CF BOSS_X EQU $D0 BOSS_XADD EQU $D1 BOSS_COUNT EQU $D2 BOSS_SMOKE EQU $D3 BOSS_EXPLODING EQU $D4 BOSS_WAITING EQU $D5 BOSS_HITS EQU $D6 BOSS_SHOOTING EQU $D7 ENEMIES_SPAWNED EQU $D8 ENEMY_TYPE EQU $D9 ENEMY_WAVE EQU $DA CURRENT_INIT_X EQU $DB CURRENT_ENEMY_KIND EQU $DC TOTAL_ENEMIES_OUT EQU $DD SCROLL EQU $DE SOUND_ON EQU $DF SHIPX EQU $E0 SHIPXADD EQU $E1 ENEMY_PL EQU $E2 ENEMY_PH EQU $E3 MISSILE_PL EQU $E4 MISSILE_PH EQU $E5 GR_PAGE EQU $E6 LEVEL EQU $E7 SHIELDS EQU $E8 SCOREL EQU $E9 SCOREH EQU $EA BONUS_FLAGS EQU $EB BCD_BYTEH EQU $EC BCD_BYTE EQU $ED COL_X1 EQU $EC COL_X2 EQU $ED COL_X3 EQU $EE COL_X4 EQU $EF ENEMY_EXPLODING EQU $F0 ENEMY_KIND EQU $F1 ENEMY_X EQU $F2 ENEMY_Y EQU $F3 ENEMY_XADD EQU $F4 ENEMY_YADD EQU $F5 ENEMY_XMIN EQU $F6 ENEMY_XMAX EQU $F7 BETWEEN_DELAY EQU $F8 ENEMY_WAIT EQU $F9 STRINGL EQU $FA STRINGH EQU $FB PARAM2 EQU $FC RESULT EQU $FD LASTKEY EQU $FE TEMP EQU $FF ;; VECTORS BASIC EQU $3D0 ;; VECTOR for return to Applesoft KEYPRESS EQU $C000 KEYRESET EQU $C010 SPEAKER EQU $C030 ;; SOFT SWITCHES GR EQU $C050 TEXT EQU $C051 FULLGR EQU $C052 TEXTGR EQU $C053 PAGE0 EQU $C054 PAGE1 EQU $C055 LORES EQU $C056 HIRES EQU $C057 PADDLE_BUTTON0 EQU $C061 PADDL0 EQU $C064 PTRIG EQU $C070 ;; MONITOR ROUTINES HLINE EQU $F819 ;; HLINE Y,$2C at A VLINE EQU $F828 ;; VLINE A,$2D at Y CLRSCR EQU $F832 ;; Clear low-res screen CLRTOP EQU $F836 ;; clear only top of low-res screen SETCOL EQU $F864 ;; COLOR=A BASCALC EQU $FBC1 ;; HOME EQU $FC58 ;; Clear the text screen WAIT EQU $FCA8 ;; delay 1/2(26+27A+5A^2) us SETINV EQU $FE80 ;; INVERSE SETNORM EQU $FE84 ;; NORMAL COUT1 EQU $FDF0 ;; output A to screen ;; GAME PARAMETERS NUM_MISSILES EQU 2 NUM_ENEMIES EQU 6 UP_SHIELDS EQU 32 WAVE_SIZE EQU 16 WAVES_TILL_BOSS EQU 5 ;; BONUS_FLAGS PERFECT_SHIELDS EQU $80 PERFECT_KILLS EQU $40 PERFECT_AIM EQU $1 ;========================================================== ; MAIN() ;========================================================== ;============================== ; back up part of the zero page ;============================== Start: lda #>zero_page_save sta BASH lda #(score_string+31) sta STRINGH lda #<(score_string+31) sta STRINGL jsr print_high_score ;============= ; put vmw logo ;============= lda #$7 ; y=7 sta CV lda #$8 ; x=8 sta CH lda #>vmw_sprite sta STRINGH lda #vmw_string ; string = vmw_string sta STRINGH lda #opener_sprite sta STRINGH lda #opener_sprite_2 sta STRINGH lda #mercy_string ; string = MMMM sta STRINGH lda #help_string ; string = "H FOR HELP" sta STRINGH lda #zero_page_save sta BASH lda #new_game_string ; string starts at "NEW GAME" sta STRINGH lda #vince_sprite sta STRINGH lda #about_lines sta STRINGH lda #help_lines sta STRINGH lda #phobos_sprite sta STRINGH lda #story_lines sta STRINGH lda #evil_ship_sprite sta STRINGH lda #tom_sprite sta STRINGH lda #you_are_tom sta STRINGH lda #earth_sprite sta STRINGH lda #ending_lines sta STRINGH lda #susie_sprite sta STRINGH lda #susie_lines sta STRINGH lda #tom_head_sprite sta STRINGH lda #tom_sigh sta STRINGH lda #missile_0 ; clear the missile struct sta MISSILE_PH ; should make this clear all BSS lda #enemy_0 sta ENEMY_PH lda #(level_string+9) sta STRINGH lda #<(level_string+9) sta STRINGL lda #0 sta BCD_BYTEH lda LEVEL sta BCD_BYTE jsr print_bcd_byte ;====================== ; Print level on screen ;====================== lda #>(level_string_xy) sta STRINGH lda #<(level_string_xy) sta STRINGL jsr print_text_xy ldx #20 jsr wait_X_100msec ; pause for 3 seconds bit KEYRESET ; clear keyboard ;================================== ; Enter graphics mode, clear screen ;================================== jsr set_page0_gr ; set graphics mode jsr clear_screen ; clear screen draw_stars: ;===================== ; Setup star field ;===================== lda #>(star_field) sta STRINGH lda #<(star_field) sta STRINGL ldy #$0 star_init: jsr random_number and #$9f clc adc #$4 sta (STRINGL),Y iny lda #$0 sta (STRINGL),Y iny bne star_init lda #$0 sta SCROLL ;/========================\ ;+ + ;+ MAIN GAME LOOP + ;+ + ;\========================/ main_game_loop: jsr clear_screen ; clear screen jsr show_stars done_scrolling: ; ================================ ; put out new enemies (if needed) ; ================================ inc BETWEEN_DELAY ; inc how long we've delayed lda BETWEEN_DELAY ; load it in cmp ENEMY_WAIT ; have we waited long enough? beq reset_delay jmp move_enemies ; if not, go on to movement reset_delay: ; delay==wait, so attempt to put out new enemy lda BETWEEN_DELAY and #$1 sta BETWEEN_DELAY ; reset delay ; special case for boss lda #$9 ; if boss, don't keep track of cmp ENEMY_TYPE ; how many enemies were spawned bne not_boss_dont_clear lda #$1 ; store 1 so we don't increment wave sta ENEMIES_SPAWNED not_boss_dont_clear: ; see if we are at a new wave ; basically, if 16 have been spawned, change lda ENEMIES_SPAWNED and #$0f bne same_enemy_type ; if not 16 gone by, move on ;======================= ; change the enemy type inc ENEMIES_SPAWNED jsr random_number and #$7 ; get a random number 0-7 sta ENEMY_TYPE inc ENEMY_WAVE lda ENEMY_WAVE ; have we gone enough waves to reach boss? cmp #WAVES_TILL_BOSS bne not_boss_yet lda #$8 sta ENEMY_TYPE not_boss_yet: ; set various constants ; these may be overriden later lda #20 sec sbc LEVEL sta ENEMY_WAIT ; enemy_wait=20-level ; set kind and init x to be random by default lda #$ff sta CURRENT_ENEMY_KIND sta CURRENT_INIT_X same_enemy_type: ; find empty enemy slot ldy #$0 ; point to enemies[0] tya find_empty_enemy: pha lda (ENEMY_PL),Y ; get enemy[y].out beq add_enemy pla clc adc #$9 tay cpy #(NUM_ENEMIES*9) bne find_empty_enemy jmp move_enemies ; no empty, slots, move on add_enemy: pla ;============================================== ; First see if we must wait for enemy to clear ; types 2 and 8 lda ENEMY_TYPE cmp #$2 bne check_type_8 lda TOTAL_ENEMIES_OUT beq change_to_type_3 jmp move_enemies change_to_type_3: lda #$3 sta ENEMY_TYPE jsr random_number and #$8 sta CURRENT_ENEMY_KIND jsr random_number and #$1F ; mask off so 0-31 clc adc #$2 asl A sta CURRENT_INIT_X jmp setup_enemy_defaults check_type_8: cmp #$8 beq before_boss_stuff jmp check_type_9 before_boss_stuff: ;====================== ; before boss stuff lda TOTAL_ENEMIES_OUT beq prepare_for_boss jmp move_enemies prepare_for_boss: ;=============== ; HANDLE BONUSES ;=============== ; Set text mode jsr set_page0_text jsr HOME ; Print "BONUS POINTS" lda #>bonus_string sta STRINGH lda #bonus_shields sta STRINGH lda #bonus_kills sta STRINGH lda #bonus_aim sta STRINGH lda #no_bonus_string sta STRINGH lda #34 lda CURRENT_INIT_X bpl store_init_x jsr random_number and #$1f clc adc #$2 asl store_init_x: iny ; X sta (ENEMY_PL),Y ; enemy_y is always 0 by default iny ; Y lda #$0 sta (ENEMY_PL),Y lda #$0 iny sta (ENEMY_PL),Y ; xadd iny sta (ENEMY_PL),Y ; yadd lda #$2 iny sta (ENEMY_PL),Y ; xmin iny lda #$24 sta (ENEMY_PL),Y ; ymin dey ; xmin dey ; yadd dey ; xadd ;=========================================== ; Enemy specific inits lda ENEMY_TYPE beq enemy_type_0 cmp #$1 beq enemy_type_1 jmp enemy_type_2 enemy_type_0: enemy_type_1: ;================================ ; ENEMY TYPE 0 and 1 ; diagonal, no wait ; movement proportional to level lda LEVEL ; xadd = level sta (ENEMY_PL),Y iny lsr A ora #$1 sta (ENEMY_PL),Y ; yadd = level/2 jmp move_enemies enemy_type_2: ;===================== ; Enemy Type 2 ; just a place-holder ; waits for enemies to die then moves on to 3 cmp #$2 bne enemy_type_3 jmp move_enemies enemy_type_3: cmp #$3 bne enemy_type_4 ;====================== ; Enemy type 3 lda #$1 sta (ENEMY_PL),Y ; xadd=1 iny lda LEVEL sta (ENEMY_PL),Y ; yadd=level jmp move_enemies enemy_type_4: cmp #$4 bne enemy_type_5 ;========================= ; Enemy Type 4 ; Horizontal, then fall lda #$2 sta (ENEMY_PL),Y ; xadd = 2 iny jsr random_number ora #$80 ; set negative sta (ENEMY_PL),y ; yadd = -(random%128) ; this means bop back and forth a random ; time, then drop jmp move_enemies enemy_type_5: cmp #$5 bne enemy_type_6 ;======================== ; Enemy Type 5 ; "wiggle" lda #$1 sta (ENEMY_PL),y ; xadd=1 iny lda LEVEL sta (ENEMY_PL),y ; yadd=2 iny jsr random_number and #$0f clc adc #$2 sta (ENEMY_PL),y ; xmin=(rand%16)+2 dey ; yadd dey ; xadd dey ; y dey ;x asl A sta (ENEMY_PL),y iny ;y iny ; xadd iny ; yadd iny ; xmin jsr random_number and #$0f clc adc (ENEMY_PL),Y adc #$02 iny sta (ENEMY_PL),Y ; xmax = xmin+(rand%16)+2 jmp move_enemies enemy_type_6: cmp #$6 beq enemy_type_7 cmp #$7 beq enemy_type_7 jmp enemy_type_8 enemy_type_7: ;===================== ; Enemy Types 6+7 ; "Rain" jsr random_number and #6 bne no_use_own_x dey ; y dey ; x lda SHIPX cmp #$2 bpl shipx_ok lda #$2 ; stupid bug where gets stuck is < xmin shipx_ok: asl A sta (ENEMY_PL),Y ; one-in-four chance we use shipx as X iny ; y iny ; xadd no_use_own_x: lda #$0 sta (ENEMY_PL),Y ; xadd=0 iny lda #$1 sta (ENEMY_PL),Y ; yadd = 1 jmp move_enemies enemy_type_8: enemy_type_9: ;====================== ; Things flung by boss dey ; y dey ; x lda BOSS_X clc adc #$5 asl A sta (ENEMY_PL),Y ; enemy_x=boss_x+5 iny lda #$3 asl A asl A sta (ENEMY_PL),Y ; enemy_y=3 iny lda #$0 sta (ENEMY_PL),Y ; xadd=0 iny lda #$2 sta (ENEMY_PL),Y ; yadd=2 move_enemies: ;============================================== ; Move Enemies! (first thing, if no new added) ;============================================== ldy #$0 ; point to enemies[0] handle_enemies: tya pha ; store y on stack lda (ENEMY_PL),Y ; get enemy[y].out bne load_enemy_zero_page ; if enemy.out then we are good jmp skip_to_next_enemy ; enemy is not out, so skip to next ;========================================== ; load this enemy stuff into zero page for ; easier access ;========================================== load_enemy_zero_page: ldx #ENEMY_EXPLODING load_to_zero_page: iny ; point to exploding lda (ENEMY_PL),Y sta 0,X ; store to zero page inx cpx #(ENEMY_XMAX+1) ; see if reached end bne load_to_zero_page ; if not keep copying ;================================ ; skip all movement and collision ; if exploding ;================================ lda ENEMY_EXPLODING beq move_enemy_x jmp draw_enemy ;================================ ; Start the enemy movement engine ;================================ ;======== ; Move X ;======== move_enemy_x: clc lda ENEMY_X ; X adc ENEMY_XADD ; x+=xadd sta ENEMY_X lsr A cmp ENEMY_XMIN ; are we less than xmin? bmi switch_dir_enemy_x ; if so, switch direction cmp ENEMY_XMAX ; are we greater than xmax? bpl switch_dir_enemy_x ; if so, switch direction jmp move_enemy_y switch_dir_enemy_x: ; switch X direction lda #$0 ; load zero sec sbc ENEMY_XADD ; 0 - ENEMY_XADD sta ENEMY_XADD ; store it back out, negated jmp move_enemy_x ; re-add it in ;======== ; Move Y ;======== move_enemy_y: lda #$0 ; load in zero cmp ENEMY_YADD ; compare to YADD bmi no_y_special_case ; if minus, we have special case inc ENEMY_YADD bne done_enemy_y lda #$0 sta ENEMY_XADD lda #$2 sta ENEMY_YADD ; increment y ; is it > 0? ; if not keep going ; if so, yadd=level*2 jmp done_enemy_y no_y_special_case: clc lda ENEMY_Y ; get Y adc ENEMY_YADD ; y+=yadd sta ENEMY_Y ; store back out lsr A lsr A cmp #$12 ; is y<=12? bmi done_enemy_y ; if so no need to do anything beq done_enemy_y ; off screen pla ; pop saved Y off stack tay pha ; push y back on stack lda #$0 sta (ENEMY_PL),Y ; set enemy[i].out=0 dec TOTAL_ENEMIES_OUT lda BONUS_FLAGS and #<(~PERFECT_KILLS) sta BONUS_FLAGS jmp skip_to_next_enemy ; skip to next enemy done_enemy_y: ;=============== ; Done Movement ;=============== ;====================== ; Check for Collisions ;====================== ;================================== ; Check ENEMY <> MISSILE collision ;================================== check_enemy_missile_collision: ldy #$0 sty YSAV check_missile_loop: lda (MISSILE_PL),Y beq missile_not_out iny ; point to missile.x lda (MISSILE_PL),Y ; load missile.x sta COL_X1 sta COL_X2 lda ENEMY_X lsr A sta COL_X3 clc adc #3 sta COL_X4 jsr check_inside bcc missile_done x_in_range: iny lda (MISSILE_PL),Y ; load missile.y sta COL_X3 clc adc #2 sta COL_X4 lda ENEMY_Y lsr A lsr A sta COL_X1 clc adc #1 sta COL_X2 jsr check_inside bcc missile_done horrible_explosion: ; clear missile ldy YSAV lda #$0 sta (MISSILE_PL),Y ; clear enemy lda #$1 sta ENEMY_EXPLODING lda #$40 sta ENEMY_KIND jsr inc_score jmp draw_enemy missile_done: missile_not_out: ldy YSAV iny iny iny sty YSAV cpy #(NUM_MISSILES*3) bne check_missile_loop ;================================= ; Done missile <> enemy collision ;================================= ;==================================== ; check for ship <-> enemy collision ;==================================== lda SHIPX sta COL_X3 clc adc #8 sta COL_X4 ; big check is shipx - shipx+8 lda ENEMY_X lsr A sta COL_X1 clc adc #2 sta COL_X2 ; small check enemy_x - enemy_x+2 jsr check_inside ; check if overlap bcc draw_enemy ; if not, move ahead lda #16 sta COL_X3 lda #18 sta COL_X4 ; big check is 16 - 18 lda ENEMY_Y lsr A lsr A sta COL_X1 clc adc #$1 sta COL_X2 ; little check is enemy_y - enemy_y+1 jsr check_inside ; check if overlap bcc draw_enemy ; if not, move ahead ; make the enemy explode lda #$1 sta ENEMY_EXPLODING lda #$40 sta ENEMY_KIND dec SHIELDS jsr update_shields ; move shields down lda #<(~PERFECT_SHIELDS) ; (~PERFECT_SHIELDS) and BONUS_FLAGS ; remove perfect shield bonus sta BONUS_FLAGS ;===================================== ; Done ship <> enemy collision detect ;===================================== draw_enemy: ; See if the enemy is currently exploding ; if so, do explosion stuff check_enemy_explode: lda ENEMY_EXPLODING ; load enemy[i].exploding beq not_exploding ; if 0 then not exploding handle_exploding: jsr click ; make some noise clc lda ENEMY_KIND ; move to next step in explosion adc #$4 sta ENEMY_KIND cmp #$58 ; have we cycles through explosion? bne draw_enemy_sprite ; if not, we are still exploding dec TOTAL_ENEMIES_OUT ; total_enemies_out-- pla tay ; load y pha lda #$0 ; enemy[i].out=0 sta (ENEMY_PL),Y jmp skip_to_next_enemy ; point to enemies_x ; goto enemies_xy not_exploding: draw_enemy_sprite: ; point to proper sprite lda #>enemy_sprite0 ; point to the missile sprite sta STRINGH lda #boss_sprite sta STRINGH lda #smoke_sprite0 ; point to the missile sprite sta STRINGH lda #laser_sprite0 ; point to the missile sprite sta STRINGH lda #smoke_sprite0 ; point to the missile sprite sta STRINGH lda #missile_sprite ; point to the missile sprite sta STRINGH lda #ship_sprite sta STRINGH lda #shields_string sta STRINGH lda #score_string sta STRINGH lda #level_string sta STRINGH lda #game_over_string sta STRINGH lda #(score_string+31) sta STRINGH lda #<(score_string+31) sta STRINGL jsr print_high_score jsr HOME ; print new high score message lda #>new_high_score_string sta STRINGH lda #high_score_string sta STRINGH lda #shields_string sta STRINGH lda #(score_string+7) sta STRINGH lda #<(score_string+7) sta STRINGL tya pha ; save Y on stack ldy #$0 lda SCOREH sta BCD_BYTEH lda SCOREL sta BCD_BYTE jsr print_bcd_word pla ; restore Y tay rts ;====================== ; print high_score ;====================== ; location to output to in STRINGH/STRINGL print_high_score: tya pha ; save Y on stack ldy #$0 lda HISCORE_H sta BCD_BYTEH lda HISCORE_L sta BCD_BYTE jsr print_bcd_word pla ; restore Y tay rts ;========================================================== ; print_bcd_word ;========================================================== ; string to output in STRINGH/STRINGL ; byte to output in BCD_BYTE print_bcd_word: lda BCD_BYTEH lsr A lsr A lsr A lsr A and #$f ; mask low nybble bne to_ascii_thou lda #$A0 ; load a space jmp write_thousands to_ascii_thou: adc #$B0 ; covert to ascii write_thousands: sta (STRINGL),Y ; store output iny lda BCD_BYTEH and #$f bne to_ascii_hun cmp BCD_BYTEH bne to_ascii_hun lda #$A0 jmp write_hundreds to_ascii_hun: adc #$B0 write_hundreds: sta (STRINGL),Y iny print_bcd_byte: lda BCD_BYTE lsr A lsr A lsr A lsr A and #$f ; mask low nybble bne to_ascii_tens ; if not zero, convert to ascii cmp BCD_BYTEH bne to_ascii_tens lda #$A0 jmp write_tens to_ascii_tens: adc #$B0 ; covert to ascii write_tens: sta (STRINGL),Y ; store output iny ; point one lower lda BCD_BYTE clc and #$f adc #$B0 sta (STRINGL),Y rts ;========================================================== ; check inside ;========================================================== ; Simple collision detection. Have small line x1<->x2 ; Want to see if it overlaps long line x3<---------->x4 ; so: ; if ((x1>x3)&&(x1x3) && (x2 40? bmi bottom_loop ; if not, loop lda #$80 ; go to next line [they are $80 apart] clc adc BASL ; increment base sta BASL ; store it out lda #$0 ; load 0 into A adc BASH ; carry into top byte if need be sta BASH ; and store out inx ; increment line count cpx #$4 ; have we done 4 bcc bottom_y ; if not, loop rts ;========================================================== ; print X strings ;========================================================== ; ; print_x_strings: stx TEMP jsr print_text_xy ldx TEMP dex bne print_x_strings rts ;========================================================== ; Print text x,y ;========================================================== ; x=ch y=cv ; string=string_addr print_text_xy: ldy #$0 ; clear IY lda (STRINGL),Y ; load x from memory sta CH ; store to CH iny ; point to next value lda (STRINGL),Y ; load y from memory sta CV ; store to CV ; point to beginning of string clc lda #$2 adc STRINGL sta STRINGL lda #$0 adc STRINGH sta STRINGH print_text: ldy #$0 lda CV jsr BASCALC ; get the address of y in BASH:BASL clc ; clear the carry lda BASL ; load BASL adc CH ; add x sta BASL ; store BASL back out output_loop: lda (STRINGL),Y ; load char from string_addr+y beq print_done ; if null terminated, done sta (BASL),Y ; store to BASH:BASL iny ; IY++ jmp output_loop ; loop print_done: iny tya ; transfer y to accumulator adc STRINGL ; add y and stringl sta STRINGL ; and store it out lda #$0 ; clear accumulator adc STRINGH ; add with carry from prev stringh sta STRINGH ; and save it rts ;========================================================== ; clear_screen ;========================================================== ; clear_screen: ldx #$0 clear_0: cpx #$0 bne clear_1 lda GR_PAGE sta BASH lda #$0 sta BASL ldy #$78 jmp clear_it clear_1: cpx #$1 bne clear_2 lda #$80 sta BASL ldy #$78 jmp clear_it clear_2: cpx #$2 bne clear_3 clc lda #$1 adc GR_PAGE sta BASH ldy #$78 jmp clear_it clear_3: cpx #$3 bne clear_4 lda #$0 sta BASL ldy #$78 jmp clear_it clear_4: cpx #$4 bne clear_5 clc lda #$2 adc GR_PAGE sta BASH ldy #$50 jmp clear_it clear_5: cpx #$5 bne clear_6 lda #$80 sta BASL ldy #$50 jmp clear_it clear_6: cpx #$6 bne clear_7 clc lda #$3 adc GR_PAGE sta BASH ldy #$50 jmp clear_it clear_7: cpx #$7 bne clear_8 lda #$0 sta BASL ldy #$50 jmp clear_it clear_8: rts clear_it: lda #$00 clear_loop: dey sta (BASL),Y bne clear_loop inx jmp clear_0 ;========================================================== ; show_stars ;========================================================== ; show_stars: lda #>star_field ; Load the star offsets sta STRINGH ; array into lda #