tb1/tb1_snes/level_1.s
Vince Weaver bd21fcabaf snes: merge in game engine from Apple II port
we'll see how that goes
2013-01-25 21:53:50 -05:00

2661 lines
40 KiB
ArmAsm

; Level 1
;; GAME PARAMETERS
NUM_MISSILES = 2
NUM_ENEMIES = 6
UP_SHIELDS = 32
WAVE_SIZE = 16
WAVES_TILL_BOSS = 5
;; BONUS_FLAGS
PERFECT_SHIELDS = $80
PERFECT_KILLS = $40
PERFECT_AIM = $1
; Page zero locations
joypad1 = $00
joypad2 = $01
shipx = $02
star_scroll = $03
star_scroll_h = $04 ; 0004 too, 16-bit
HISCORE_1 = $cb
HISCORE_2 = $cc
HISCORE_3 = $cd
HISCORE_H = $ce
HISCORE_L = $cf
BOSS_X = $d0
BOSS_XADD = $d1
BOSS_COUNT = $d2
BOSS_SMOKE = $d3
BOSS_EXPLODING = $d4
BOSS_WAITING = $d5
BOSS_HITS = $d6
BOSS_SHOOTING = $d7
ENEMIES_SPAWNED = $d8
ENEMY_TYPE = $d9
ENEMY_WAVE = $da
CURRENT_INIT_X = $db
CURRENT_ENEMY_KIND = $dc
TOTAL_ENEMIES_OUT = $dd
SCROLL = $de
SOUND_ON = $df
SHIPXADD = $e1
ENEMY_PL = $e2
ENEMY_PH = $e3
MISSILE_PL = $e4
MISSILE_PH = $e5
GR_PAGE = $e6
LEVEL = $e7
SHIELDS = $e8
SCOREL = $e9
SCOREH = $ea
BONUS_FLAGS = $eb
BCD_BYTEH = $ec
BCD_BYTE = $ed
COL_X1 = $ec
COL_X2 = $ed
COL_X3 = $ee
COL_X4 = $ef
ENEMY_EXPLODING = $f0
ENEMY_KIND = $f1
ENEMY_X = $f2
ENEMY_Y = $f3
ENEMY_XADD = $f4
ENEMY_YADD = $f5
ENEMY_XMIN = $f6
ENEMY_XMAX = $f7
BETWEEN_DELAY = $f8
ENEMY_WAIT = $f9
STRINGL = $fa
STRINGH = $fb
PARAM2 = $fc
RESULT = $fd
LASTKEY = $fe
TEMP = $ff
.setcpu "65816"
.segment "STARTUP"
;================================================
; LEVEL 1
;================================================
level_1:
sep #$20 ; mem/A = 8 bit
.a8
rep #$10 ; X/Y = 16 bit
.i16
;==========================
; Setup Background
;==========================
; we want the BG1 Tilemap to start at VRAM $f000
; Format is
; aaaa aass a is shifted by 10 for address
; ss = size of screen in tiles 00 = 32x32
;
; 0111 1000
lda #$78 ; BG1 Tilemap starts at VRAM $f000/2
sta $2107 ; bg1 src
; we want the BG2 Tilemap to start at VRAM $d000
; Format is
; aaaa aass a is shifted by 10 for address
; ss = size of screen in tiles 10 = 32x64
;
; 0110 1010
lda #$6a ; BG2 Tilemap starts at VRAM $d000/2
sta $2108 ; bg2 src
; aaaa bbbb a= BG2 tiles, b= BG1 tiles
; bbbb<<13
; 0000 0000
; our BG1 tiles are stored starting in VRAM $0000
lda #$32 ; bg2 tile data starts at VRAM 5000/2
sta $210b ; bg1 tile data starts at VRAM 4000/2
;===============================
; Load Level1 Background Palette
;===============================
stz $2121 ; start with color 0
ldy #(16*2) ; we have 16 colors
lda #^level1_background_palette
ldx #.LOWORD(level1_background_palette)
jsr svmw_load_palette
;======================
; Load Level1 Tile Data
;======================
ldx #$2000 ;
stx $2116 ; set adddress for VRAM read/write
; multiply by 2, so 0x4000
lda #^level1_background_tile_data
ldx #.LOWORD(level1_background_tile_data)
ldy #1824 ; Copy 57 tiles, which are 32 bytes each
; 8x8 tile with 4bpp
; (57*32) = 1824 = ????
jsr svmw_load_vram
;===================================
; Load Level1 Tile Map
;===================================
ldx #$7800 ;
stx $2116 ; set adddress for VRAM read/write
; multiply by 2, so 0xe000
lda #^level1_background_tilemap
ldx #.LOWORD(level1_background_tilemap)
ldy #$0700 ; 32x28 = 896 * 2 = 0x700
jsr svmw_load_vram
;===============================
; Load Level1 Star Palette
;===============================
lda #16
sta $2121 ; start with color 16
ldy #(16*2) ; we have 16 colors
lda #^star_background_palette
ldx #.LOWORD(star_background_palette)
jsr svmw_load_palette
;======================
; Load Level1 Star Data
;======================
ldx #$3000 ;
stx $2116 ; set adddress for VRAM read/write
; multiply by 2, so 0x4000
lda #^star_background_tile_data
ldx #.LOWORD(star_background_tile_data)
ldy #$4420 ; Copy 545 tiles, which are 32 bytes each
; 8x8 tile with 4bpp
; (545*32) = 17,440 = 0x4420
jsr svmw_load_vram
;===================================
; Load Level1 Star Tile Map
;===================================
ldx #$6800 ;
stx $2116 ; set adddress for VRAM read/write
; multiply by 2, so 0xd000
lda #^star_background_tilemap
ldx #.LOWORD(star_background_tilemap)
ldy #$1000 ; 32x64 = 2048 * 2 = 0x1000
jsr svmw_load_vram
;==========================
; Setup Sprite
;==========================
; Load Palette for our sprite
; Sprite Palettes start at color 128
lda #128 ; start with color 128
sta $2121 ;
ldy #(16*2) ; we have 16 colors
lda #^level1_pal0_palette
ldx #.LOWORD(level1_pal0_palette)
jsr svmw_load_palette
;=========================
; Load sprite data to VRAM
;=========================
lda #$80 ; increment after writing $2119
sta $2115
ldx #$0000 ;
stx $2116 ; set adddress for VRAM read/write
; multiply by 2, so 0x0000
lda #^level1_pal0_data
ldx #.LOWORD(level1_pal0_data)
ldy #$0800 ; 32 bytes * 64 tiles
jsr svmw_load_vram
; Init sprites to be offscreen
jsr svmw_move_sprites_offscreen
; Set our sprite active
; assume 544 byte sprite table in $0200
sep #$20 ; mem/A = 8 bit
.a8
lda #104
sta $0200 ; set sprite 0 X to 0
lda #192 ; set sprite 0 Y to 100
sta $0201
; Xxxxxxxxx yyyyyyy cccccccc vhoo pppN
;
stz $0202 ; set sprite 0
; 0010 0000
; no flip, priority 2, N=0 palette=0 (128)
lda #$20
sta $0203
; X high bit = 0 for sprite 0
; sprite size = 0 (smaller)
lda #%01010110
sta $0400
; Enable sprite
; sssnnbbb
; sss = size (16x16 and 32x32 in our case)
; nn = name
; bbb = base selection
lda #%01100000
sta $2101
jsr svmw_transfer_sprite
;======================
; Setup the Video Modes
;======================
level1_setup_video:
sep #$20 ; mem/A = 8 bit
.a8
rep #$10 ; X/Y = 16 bit
.i16
lda #$01 ; 8x8 tiles, Mode 1
sta $2105
lda #$13 ; Enable BG1,BG2, and sprites
sta $212c
stz $212d ; disable subscreen
lda #$00 ; Disable NMI (VBlank Interrupt) and joypads
sta $4200 ;
;==========================
; Setup VBLANK routine
;==========================
ldx #level1_vblank
stx vblank_vector
lda #$81 ; Enable NMI (VBlank Interrupt) and joypads
sta $4200 ;
jsr svmw_fade_in
;====================
; setup the high score
;=====================
lda #$01
sta HISCORE_H
lda #$00
sta HISCORE_L
; modestly make me the high scorer
lda #$D6
sta HISCORE_1
lda #$CD
sta HISCORE_2
lda #$D7
sta HISCORE_3
; lda #>(score_string+31)
; sta STRINGH
; lda #<(score_string+31)
; sta STRINGL
jsr print_high_score
;==========================================================
; START NEW GAME
;==========================================================
do_new_game:
;; set up struct pointers
lda #>missile_0 ; clear the missile struct
sta MISSILE_PH ; should make this clear all BSS
lda #<missile_0 ; at some point
sta MISSILE_PL
lda #>enemy_0
sta ENEMY_PH
lda #<enemy_0
sta ENEMY_PL
;; Clear BSS
ldy #$0
lda #$0
clear_bss:
sta start_bss,Y
iny
cpy #end_bss-start_bss
bne clear_bss
;; Init one-time vars
lda #104
sta shipx
stz star_scroll
stz star_scroll_h
lda #$8
sta SHIELDS ; shields start at 8
jsr update_shields
; lda #$12
; sta SHIPX ; shipx at start is 18
lda #$1
sta LEVEL ; start at level 1
lda #$0
sta SCOREL
sta SCOREH
jsr print_score
new_level:
;========================
; Setup various variables
;========================
lda #(PERFECT_AIM|PERFECT_SHIELDS|PERFECT_KILLS)
sta BONUS_FLAGS ; set perfect shot/shield/enemies
lda #$14
sta ENEMY_WAIT
lda #$0
sta BETWEEN_DELAY
sta SHIPXADD ; clear shipxadd
sta ENEMY_WAVE
sta TOTAL_ENEMIES_OUT
sta ENEMIES_SPAWNED
sta ENEMY_TYPE
;=======================
; Print "LEVEL X"
;=======================
; jsr set_page0_text
; jsr HOME
;===================
; set level to level
;===================
; 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
;==================================
level1_loop:
wai ; wait for interrupt
;/========================\
;+ +
;+ 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_string
; sta STRINGL
; jsr print_text_xy ; print text
; Check to see if we had any bonuses
lda #$C1
bit BONUS_FLAGS
beq no_bonus
; Check if we had no hits on shields
perfect_shields:
bpl perfect_kills
jsr score_plus_50
; lda #>bonus_shields
; sta STRINGH
; lda #<bonus_shields
; sta STRINGL
; jsr print_text_xy ; print text
; See if we killed all the enemies
perfect_kills:
bit BONUS_FLAGS
bvc perfect_aim
jsr score_plus_50
; lda #>bonus_kills
; sta STRINGH
; lda #<bonus_kills
; sta STRINGL
; jsr print_text_xy ; print text
; See if no missiles missed
perfect_aim:
lda #$01
bit BONUS_FLAGS
beq done_bonus
jsr score_plus_50
; lda #>bonus_aim
; sta STRINGH
; lda #<bonus_aim
; sta STRINGL
; jsr print_text_xy ; print text
jmp done_bonus
; we had no bonuses
no_bonus:
; lda #>no_bonus_string
; sta STRINGH
; lda #<no_bonus_string
; sta STRINGL
; jsr print_text_xy ; print text
; Wait until a keypress, and return to graphics mode
done_bonus:
ldx #30
; jsr wait_X_100msec ; pause for 3 seconds
; bit KEYRESET ; clear keyboard
; jsr wait_until_keypressed
; jsr set_page0_gr
;======================
; setup boss
lda #$0C
sta BOSS_X ; boss_x = 13
lda #$1
sta BOSS_XADD ; boss_xadd=1
sta BOSS_WAITING ; boss_waiting=1
; jsr random_number
and #$1f
sta BOSS_COUNT ; boss_count = rand%32
lda #$0
sta BOSS_SMOKE ; boss_smoke=0
sta BOSS_EXPLODING ; boss_exploding=0
sta BOSS_SHOOTING ; boss_shooting=0
lda LEVEL
asl A
clc
adc #$10
sta BOSS_HITS ; boss_hits=(level*2)+20
lda #$9
sta ENEMY_TYPE ; enemy_type=9
jmp move_enemies
check_type_9:
; if boss, and he's waiting,
; don't produce enemies
cmp #$9
bne setup_enemy_defaults
lda BOSS_WAITING
beq setup_enemy_defaults
jmp move_enemies
;========================
; setup enemy defaults
setup_enemy_defaults:
inc ENEMIES_SPAWNED
inc TOTAL_ENEMIES_OUT
lda #$1
sta (ENEMY_PL),Y ; enemy[i].out=1
lda #$0
iny ; exploding
sta (ENEMY_PL),Y ; enemy[i].exploding=0
iny ; kind
lda CURRENT_ENEMY_KIND ; if kind <0 then random
bpl store_enemy_kind
; jsr random_number
and #$38
jmp store_enemy_kind
store_enemy_kind:
sta (ENEMY_PL),Y
; determine enemy _x
; if < 0, make random between 2->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 #<enemy_sprite0
; sta STRINGL
lda ENEMY_KIND ; get kind
and #$F8
clc
adc STRINGL
sta STRINGL
lda #0
adc STRINGH
sta STRINGH
enemies_xy:
lda ENEMY_X ; get X
lsr A
; sta CH
lda ENEMY_Y ; load it
lsr A
lsr A
; sta CV
; jsr blit ; blit the missile sprite
save_zp_enemy_back:
; save zero page copy back to RAM
ldx #ENEMY_EXPLODING
pla
tay ; restore y pointer
pha
save_from_zero_page:
iny ; point to exploding
lda 0,X
sta (ENEMY_PL),Y
inx
cpx #(ENEMY_XMAX+1) ; see if reached end
bne save_from_zero_page ; if not keep copying
skip_to_next_enemy:
pla ; get saved value of Y
clc
adc #$9 ; add 9 to point to next
tay
cpy #NUM_ENEMIES*9 ; have we looped through them all?
beq draw_the_boss ; if not, loop
jmp handle_enemies
;===================================================
;===================================================
; BOSS STUFF
draw_the_boss:
;=======================
; if enemy_type==9
; we have a boss out
lda ENEMY_TYPE
cmp #$9
beq check_boss_exploding
jmp done_with_boss
check_boss_exploding:
;================================
; if not exploding, draw the boss
lda BOSS_EXPLODING
bne skip_draw_boss
lda #$0 ; boss_y=0
; sta CV
lda BOSS_X
; sta CH ; boss_x
; lda #>boss_sprite
; sta STRINGH
; lda #<boss_sprite
; sta STRINGL
; jsr blit ; blit the ship sprite
skip_draw_boss:
;================================
; Draw Smoke
lda BOSS_SMOKE
beq skip_draw_smoke
; point to proper sprite
; lda #>smoke_sprite0 ; point to the missile sprite
; sta STRINGH
; lda #<smoke_sprite0
; sta STRINGL
lda BOSS_SMOKE ; get kind
and #$fc ; mask off bottom 2 bits
clc
adc STRINGL
sta STRINGL
lda #0
adc STRINGH
sta STRINGH
lda BOSS_X ; get X
clc
adc #$5
; sta CH
lda #$3 ; load it
; sta CV
; jsr blit ; blit the missile sprite
lda BOSS_X
clc
adc #$5
sec
sbc BOSS_XADD
; sta CH
lda #$4
; sta CV
; jsr blit
dec BOSS_SMOKE
skip_draw_smoke:
;======================
; BOSS Laser Shoot
lda BOSS_SHOOTING
beq skip_boss_shooting
dec BOSS_SHOOTING
; jsr click
ldx #$0
boss_shoot_loop:
txa
pha
; point to proper sprite
; lda #>laser_sprite0 ; point to the missile sprite
; sta STRINGH
; lda #<laser_sprite0
; sta STRINGL
lda BOSS_SHOOTING ; get shooting
and #$1
beq got_right_laser
clc
lda #$5
adc STRINGL
sta STRINGL
lda #0
adc STRINGH
sta STRINGH
got_right_laser:
lda BOSS_X
; sta CH
txa
asl
clc
adc #$3
; sta CV
; jsr blit ; blit the laser sprite
lda BOSS_X
clc
adc #$C
; sta CH
pla
pha
asl
clc
adc #$3
; sta CV
; jsr blit
pla
tax
inx
cpx #$8
bne boss_shoot_loop
; jsr click
skip_boss_shooting:
;=============================
; boss is dead
lda BOSS_EXPLODING
beq boss_is_not_exploding
lda #$1
sta BOSS_WAITING
ldx #$20
big_explosion:
txa
pha
; point to proper sprite
; lda #>smoke_sprite0 ; point to the missile sprite
; sta STRINGH
; lda #<smoke_sprite0
; sta STRINGL
lda BOSS_EXPLODING ; get kind
lsr A
lsr A
and #$0c ; mask off bottom 2 bits
clc
adc STRINGL
sta STRINGL
lda #0
adc STRINGH
sta STRINGH
; jsr random_number
and #$03
; sta CV
; jsr random_number
and #$07
clc
adc BOSS_X
; sta CH
; jsr blit ; blit the missile sprite
pla
tax
dex
bne big_explosion
; jsr click
dec BOSS_EXPLODING
bne not_dead_yet
lda LEVEL ; only show ending after level 1
cmp #$1
bne no_ending
; jmp do_ending
no_ending:
inc SCOREH ; add 100 to score
jsr print_score
lda LEVEL
cmp #$7 ; level can't be higher than 7
beq start_new_level
inc LEVEL
inc SHIELDS
inc SHIELDS
lda #$A
cmp SHIELDS
bpl start_new_level
sta SHIELDS
start_new_level:
jsr update_shields
jmp new_level
not_dead_yet:
jmp move_boss
boss_is_not_exploding:
dec BOSS_COUNT
bne move_boss
;=========================================
; Toggle boss waiting state if count is up
lda BOSS_WAITING
beq make_boss_wait
stop_boss_waiting:
lda #$0
sta BOSS_WAITING ; boss_waiting=0
; jsr random_number
sta BOSS_COUNT ; boss_count=rand%256
jmp move_boss
make_boss_wait:
lda #$1
sta BOSS_WAITING
; jsr random_number
and #$01f
clc
adc #$30
sta BOSS_COUNT
lda #$20
sta BOSS_SHOOTING
move_boss:
lda BOSS_WAITING
bne laser_collision
lda BOSS_X
clc
adc BOSS_XADD
sta BOSS_X
cmp #26
bpl boss_reverse
boss_under:
cmp #$0
bpl laser_collision
boss_reverse:
lda #$0 ; load zero
sec
sbc BOSS_XADD ; 0 - ENEMY_XADD
sta BOSS_XADD ; store it back out, negated
jmp move_boss ; re-add it in
laser_collision:
;================================
; Collision detection for lasers
;
lda BOSS_SHOOTING
beq done_with_boss
left_laser:
lda BOSS_X
sta COL_X1
sta COL_X2
lda shipx
sta COL_X3
lda #$6
clc
adc shipx
sta COL_X4
jsr check_inside
bcs laser_hit
right_laser:
lda BOSS_X
clc
adc #$C
sta COL_X1
sta COL_X2
jsr check_inside
bcc done_with_boss
laser_hit:
lda BOSS_SHOOTING
and #$3 ; only take damage 1/8 the time
bne done_with_boss
dec SHIELDS
jsr update_shields
done_with_boss:
ldy #$0 ; point to missile[0]
move_missiles:
lda (MISSILE_PL),Y ; get missile[y]
beq loop_move_missiles ; if missile.out==0 skip
iny ; move to missile.y
iny
clc ; clear carry
lda (MISSILE_PL),Y ; get missile.y
adc #$FF ; move up (subtract 1)
sta (MISSILE_PL),Y ; store missile.y
bpl missile_collision_detection ; if not off screen, contine
dey ; back up to missile.out
dey
lda #$0
sta (MISSILE_PL),Y ; set missile.out=0
lda #<(~PERFECT_AIM) ; shot missed!
and BONUS_FLAGS ; clear perfect shot flag
sta BONUS_FLAGS
jmp loop_move_missiles ; continue
missile_collision_detection:
check_missile_boss:
lda ENEMY_TYPE
cmp #$9
bne done_missile_collision
dey ; missile x
lda (MISSILE_PL),Y
iny ; fix y
sta COL_X1
sta COL_X2
lda BOSS_X
sta COL_X3
clc
adc #$0d
sta COL_X4
jsr check_inside
bcc done_missile_collision
check_boss_y:
lda (MISSILE_PL),Y
sta COL_X1
clc
adc #$2
sta COL_X2
lda #$0
sta COL_X3
lda #$3
sta COL_X4
jsr check_inside
bcc done_missile_collision
hit_the_boss:
dey
dey
lda #$0
sta (MISSILE_PL),Y ; missile_out=0
lda #$B
sta BOSS_SMOKE
dec BOSS_HITS
bne loop_move_missiles
lda #$2F
sta BOSS_EXPLODING
lda #$0
sta BOSS_SHOOTING
jmp loop_move_missiles
done_missile_collision:
jmp loop_move_at_y
loop_move_missiles:
iny
iny
loop_move_at_y:
iny
cpy #NUM_MISSILES*3 ; have we checked all missiles?
bne move_missiles ; if not, loop
done_move_missiles:
ldy #$0 ; point to missiles[0]
draw_missiles:
lda (MISSILE_PL),Y ; get missile[y]
beq loop_draw_missiles ; if missile.out==0 skip
iny ; point to missile.x
lda (MISSILE_PL),Y ; load it
; sta CH
iny ; point to missile.y
lda (MISSILE_PL),Y ; load it
; sta CV
; sty YSAV1 ; save Y
; lda #>missile_sprite ; point to the missile sprite
; sta STRINGH
; lda #<missile_sprite
; sta STRINGL
; jsr blit ; blit the missile sprite
; ldy YSAV1 ; restore Y
jmp loop_draw_missiles_noadd
loop_draw_missiles:
iny
iny
loop_draw_missiles_noadd:
iny
cpy #NUM_MISSILES*3 ; have we looped through them all?
bne draw_missiles ; if not, loop
game_read_keyboard:
; jsr get_key
lda LASTKEY
bne game_q
jmp move_ship ; if no keypressed, move the ship
game_q:
cmp #'Q'
bne game_j
;; call verify_quit
jmp done_game
;; jmp set_pause_flag
game_j:
cmp #'J'
bne game_k
lda SHIPXADD ; load xadd
beq game_j_sub
bpl game_j_0 ; is switch dir, then 0 it
game_j_sub:
dec SHIPXADD ; else, dec XADD
jmp move_ship
game_j_0:
lda #$0
sta SHIPXADD
jmp move_ship
game_k:
cmp #'K'
bne game_c
lda SHIPXADD ; load xadd
bmi game_j_0 ; if we are switching dirs, set to zero
inc SHIPXADD ; else xadd++
jmp move_ship
game_c:
cmp #'C'
bne game_p
; lda PADDLE_STATUS
; eor #$80
; sta PADDLE_STATUS
jmp move_ship
game_p:
cmp #'P'
bne game_s
; bit KEYRESET
; jsr wait_until_keypressed
jmp move_ship
game_s:
cmp #'S'
bne game_h
lda SOUND_ON
eor #$ff
sta SOUND_ON
jmp move_ship
game_h:
cmp #'H'
bne game_space
; jsr do_help
; jsr set_page0_gr
game_space:
cmp #' '+128 ; +128 because of get_key 'feature'
bne game_unknown
ldy #$0 ; point to missile[y]
fire_missiles:
lda (MISSILE_PL),Y ; get missile[y].out
bne end_fire_loop ; if not out, skip ahead
lda #$1 ; set missile[y].out=1
sta (MISSILE_PL),Y
iny ; point to missile[y].x
lda #$3
clc
adc shipx ; missile[y].x=shipx+3
sta (MISSILE_PL),Y
iny ; point to missile[y].y
lda #$10 ; set to 16
sta (MISSILE_PL),Y
jmp done_fire_missiles
end_fire_loop:
iny
iny
iny
cpy #NUM_MISSILES*3 ; see if we have more missiles
bne fire_missiles ; if so, loop
done_fire_missiles:
jmp move_ship
game_unknown:
move_ship:
clc ; Clear carry
lda shipx ; load ship_x
adc SHIPXADD ; ship_x+=xadd
sta shipx ; store it back
check_x_under:
bpl check_x_over ; if positive, keep going
lda #$0 ; we were below zero
sta shipx ; so shipx=0
sta SHIPXADD ; xadd=0
jmp blit_ship ; go to blit
check_x_over:
cmp #$21 ; are we over 33?
bmi blit_ship ; if not, blit ship
lda #$0 ; shipxadd=0
sta SHIPXADD
lda #$21 ; shipx=33
sta shipx
blit_ship:
lda #$10 ; shipy=16
; sta CV
lda shipx
; sta CH ; load shipx
; lda #>ship_sprite
; sta STRINGH
; lda #<ship_sprite
; sta STRINGL
; jsr blit ; blit the ship sprite
; jsr clear_bottom
; lda #>shields_string
; sta STRINGH
; lda #<shields_string
; sta STRINGL
clc
lda #$2
; adc GR_PAGE
; sta BASH
; lda #$50
; sta BASL
ldy #$0
shield_print_loop:
;=============================
; CHECK TO SEE IF GAME IS OVER
;=============================
lda SHIELDS
bmi done_game
lda (STRINGL),Y
; sta (BASL),Y
iny
cpy #$1E ; string is 30 long
bne shield_print_loop
; inc BASH ; move to line 23
; lda #>score_string
; sta STRINGH
; lda #<score_string
; sta STRINGL
ldy #$0
score_print_loop:
; lda (STRINGL),Y
; sta (BASL),Y
iny
cpy #$26 ; string is 38 long
bne score_print_loop
clc
lda #$80
; adc BASL
; sta BASL ; move to line 24
;OPTIMIZE
; lda #>level_string
; sta STRINGH
; lda #<level_string
; sta STRINGL
ldy #$0
level_print_loop:
; lda (STRINGL),Y
; sta (BASL),Y
iny
cpy #$C ; string is 12 long
bne level_print_loop
;==========
; Flip Pages
; lda #$4
; bit GR_PAGE
; bne gr_page_1
;
;gr_page_0:
; bit PAGE1 ; switch to page 0
; lda #$4
; jmp write_out_gr_page
;gr_page_1:
; bit PAGE0 ; switch to page 1
; lda #$8
;write_out_gr_page:
; sta GR_PAGE
; lda #$8B ; 107 = 30080us = 30ms
; lda #$65
; jsr WAIT
jmp main_game_loop
done_game:
ldx #30
; jsr wait_X_100msec ; pause for 3 seconds
; bit KEYRESET ; clear keyboard
; jsr set_page0_text
; jsr HOME
; lda #>game_over_string
; sta STRINGH
; lda #<game_over_string
; sta STRINGL
; jsr print_text_xy
; jsr wait_until_keypressed
; jsr set_page0_text
see_if_new_hi_score:
sed
lda SCOREH
cmp HISCORE_H
cld
beq too_close
bpl new_high
jmp do_hi_score
too_close:
sed
lda SCOREL
cmp HISCORE_L
cld
bpl new_high
jmp do_hi_score
new_high:
; Actually set the high score
lda SCOREL
sta HISCORE_L
lda SCOREH
sta HISCORE_H
; 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 #<new_high_score_string
; sta STRINGL
; jsr print_text_xy
; jsr print_text_xy
; set high score to AAA
lda #$C1
sta HISCORE_1
sta HISCORE_2
sta HISCORE_3
; load initials address
lda #$06
sta STRINGH
lda #$12
sta STRINGL
; load pointer address
; lda #$06
; sta BASH
; lda #$92
; sta BASL
ldy #$0
ldx #$0
initials_loop:
; erase old pointer
ldy #$0
lda #$A0
; sta (BASL),Y
; iny
; sta (BASL),Y
; iny
; sta (BASL),Y
txa
tay
; draw pointer
; lda #$DE ; '^'
; sta (BASL),Y
ldy #$0
; draw initials
lda HISCORE_1
sta (STRINGL),Y
iny
lda HISCORE_2
sta (STRINGL),Y
iny
lda HISCORE_3
sta (STRINGL),Y
in_key:
; jsr wait_until_keypressed
lda LASTKEY
beq in_key
cmp #$D
beq do_hi_score
cmp #'I'
bne in_down
inc HISCORE_1,X
in_down:
cmp #'M'
bne in_left
dec HISCORE_1,X
in_left:
cmp #'K'
bne in_right
inx
in_right:
cmp #'J'
bne fix_limits
dex
fix_limits:
; Make sure X is between 0 and 2
cpx #$0
bpl x_high
ldx #$2
x_high:
cpx #$3
bmi x_lo
ldx #$0
x_lo:
jmp initials_loop
do_hi_score:
; jsr HOME
; lda #>high_score_string
; sta STRINGH
; lda #<high_score_string
; sta STRINGL
; jsr print_text_xy
; go to screen co-ords 15x12
lda #$06
sta STRINGH
lda #$37
sta STRINGL
; put the initials up
lda HISCORE_1
ldy #$0
sta (STRINGL),Y
lda HISCORE_2
iny
sta (STRINGL),Y
lda HISCORE_3
iny
sta (STRINGL),Y
; Print score to screen
;
lda #$06
sta STRINGH
lda #$3D
sta STRINGL
jsr print_high_score
;
; jsr wait_until_keypressed
; jmp opener
; scroll background
lda star_scroll
sta $2110
lda star_scroll_h
sta $2110
rep #$20 ; A/mem=16 bit
.a16
dec star_scroll
sep #$20 ; A/mem=8 bit
.a8
; handle keypress
lda joypad1
check_left:
bit #$02
beq check_right
dec shipx
bpl check_right
stz shipx
check_right:
bit #$01
beq no_keypress
inc shipx
no_keypress:
; update OAM
lda shipx
sta $0200 ; set sprite 0 X to shipx
jmp level1_loop
;=============================
; Level 1 VBLank Routine
;=============================
level1_vblank:
php ; save status register
rep #$30 ; Set A/mem=16 bits, X/Y=16 bits (to push all 16 bits)
.a16 ; tell assembler the A is 16-bits
phb ; save b
pha ; save A
phx ; save X
phy ; save Y
phd ; save zero page
sep #$20 ; A/mem=8 bit
.a8
l1_joypad_read:
lda $4212 ; get joypad status
and #$01 ; if joy is not ready
bne l1_joypad_read ; wait
lda $4219 ; read joypad (BYSTudlr)
sta joypad1
lda $4218 ; read joypad (AXLRiiii)
sta joypad2
done_joypad:
;=======================================
; Update the sprite info structure (OAM)
;=======================================
jsr svmw_transfer_sprite
done_vblank:
lda $4210 ; Clear NMI flag
rep #$30 ; A/Mem=16 bits, X/Y=16 bits
.a16
pld ; restore saved vaules from stack
ply
plx
pla
plb
plp
rti ; return from interrupt
;==========================================================
; Update Shields
;==========================================================
;
update_shields:
; lda #>shields_string
; sta STRINGH
; lda #<shields_string
; sta STRINGL
ldy #0
lda SHIELDS
bne normal_shields
flash_shields:
lda #$7f
and (STRINGL),Y
sta (STRINGL),Y
iny
cpy #$7
bne flash_shields
jmp shields_line
normal_shields:
lda #$80
ora (STRINGL),Y
sta (STRINGL),Y
iny
cpy #$7
bne normal_shields
shields_line:
ldy #$A
ldx #$0
shield_line_loop:
cpx SHIELDS
bmi shield_box
lda #'_'+128
jmp shield_char
shield_box:
lda #' '
shield_char:
sta (STRINGL),Y
iny
iny
inx
cpx #$A
bne shield_line_loop
rts
;==========================================================
; score_plus_50
;==========================================================
;
score_plus_50:
sed ; enter decimal mode
clc
lda SCOREL
adc #$50
sta SCOREL ; score+=50
lda SCOREH
adc #$0
sta SCOREH ; carry into high byte if needed
cld ; leave decimal mode
jmp print_score
;==========================================================
; inc_score
;==========================================================
;
inc_score:
sed ; enter decimal mode
clc
lda SCOREL
adc #$5
sta SCOREL ; score+=5
lda SCOREH
adc #$0
sta SCOREH ; carry into high byte if needed
cld ; leave decimal mode
lda SCOREL ; if score /100 =0 then inc shields
bne print_score
lda SHIELDS
cmp #$0A ; don't raise shields higher than 10
bpl print_score
inc SHIELDS
jsr update_shields
print_score:
; 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
;==========================================================
; check inside
;==========================================================
; Simple collision detection. Have small line x1<->x2
; Want to see if it overlaps long line x3<---------->x4
; so:
; if ((x1>x3)&&(x1<x4)) || ((x2>x3) && (x2<x4)) inside
; else outside
check_inside:
lda COL_X1
cmp COL_X3
bmi check_higher
cmp COL_X4
bmi inside
check_higher:
lda COL_X2
cmp COL_X3
bmi outside
cmp COL_X4
bpl outside
inside:
sec
rts
outside:
clc
rts
;; *********************
;; BSS
;; *********************
.bss
start_bss:
missile_0: .res NUM_MISSILES*3
enemy_0: .res NUM_ENEMIES*9
end_bss:
;============================================================================
; Sprite/Tile Data
;============================================================================
.segment "HIGHROM"
; sprite data
.include "level1_pal0.sprites"
.include "level1_background.tiles"
.include "star_background.tiles"