;----------------------------------------------------------------------------- ; game.inc ; Part of penetrator, the zx spectrum game, made for Apple II ; ; Stefan Wessels, 2019 ; This is free and unencumbered software released into the public domain. ;----------------------------------------------------------------------------- .segment "CODE" ;----------------------------------------------------------------------------- .proc gamePlay jsr gameInit outerloop: lda lives bne :+ rts ; no lives left = Game Over : jsr gameLevelInit ; prep whatever level the active player is at jsr gameShowPlayer ; show the pre-play who's turn message jsr uiShowGameLabels ; show the lines for score, danger, etc. preamble: ; bring terrain on-screen with no clock delay jsr drawPresent jsr gameWorldMove jsr gameWorldMove jsr terrainShow ; show terrain 1st because it collides with nothing jsr drawEnemies ; enemy missiles collide with terrain dec terrainOrigin ; get the start of terrain drawing to col 0 dec terrainOrigin ; get the start of terrain drawing to col 0 bpl preamble ; and draw even at 0 inc terrainOrigin ; origin is now -1 so set it back to 0 jsr gamePlaceShip ; find a safe spot for the player to spawn jsr drawPlayer ; now show the player ship jsr drawPresent ; flip layers 0/1 (bring backLayer to visible) bit KBDSTRB wait #$40 ; give the user a moment to get ready loop: jsr inputGame ; read joystick and move player, check for pause key cmp #$9b ; check for ESC bne skip ; if not, check for pause rts ; on ESC, just quit skip: lda playerDead ; if the player is dead the world doesn't move bne :+ jsr gameWorldMove ; scroll the world inc bufferDraw ; and move the draw start also jsr gameCheckStage ; see if a stage is ending : lda flipFlop ; the flipFlop is 0->1->0->1, etc. eor #1 sta flipFlop jsr gameAI ; run the "AI" - this moves enemies and animates everything, etc. next: dec bulletIndex ; Bullets move every frame (2x for 1 world move) dec bulletIndex draw: jsr terrainShow ; show terrain 1st because it collides with nothing jsr drawEnemies ; enemy missiles collide with terrain jsr drawPlayer ; player collides with enemies only jsr drawBullets ; checks for enemy collision jsr drawBombs ; checks for enemy collision (but won't destroy player) jsr drawExplosions ; explosions draw over everything and collide with nothing lda updateHUD ; only update the hud when it changes beq delay jsr uiUpdateGameHud delay: jsr drawPresent ; flip layers 0/1 (bring backLayer to visible) lda victory ; when set, either a nuke or back home bne win jsr serviceAudio ; run the audio lda playerDead beq loop ; playerDead = 0 means alive so keep going died: dec playerDead ; the counter holds the explosion state bne skip ; don't get user input jsr gameNextPlayer ; switch players if a 2p game jmp outerloop win: jsr gameWinSequence ; flash screen and show the win screen (wow or home) jmp outerloop .endproc ;----------------------------------------------------------------------------- .proc gameInit jsr drawClearScreen ; make sure the screen is clear before adjusting the lda #NUM_LIVES ; lives to start sta lives lda #0 ; init some variables - stage is set before this sta direction ; go to the right sta activePlayer ; player 1 is always 1st to go sta score sta score + 1 sta score + 2 ldx #NUM_STAGES-1 ; mark all stages as all rockets : sta makeMonster, x dex bpl :- lda #NUM_SKIP_MONST ; make this stage (NUM_SKIP_MONST) be a monster stage sta monsterCnt ldx #3 ; set stage 3 (zero based) as some rockets become monsters sta makeMonster, x ldy #0 ; init player 1 jsr gameStatsToPlayer ldy #1 ; init player 2 jmp gameStatsToPlayer .endproc ;----------------------------------------------------------------------------- .proc gameLevelInit lda #0 ; Lots of values need a 0 init sta victory sta playerDead sta zCollision sta stopScrolling sta bufferInsert sta dangerTickIdx sta moveHorz sta moveVert sta flipFlop sta pause sta fireCoolDown tax ; clear the buffers for tracking objects : sta worldBBuffer, x sta worldTBuffer, x sta enemyBuffer, x sta enemyHgtBuffer, x sta explosionBuffer, x sta bulletsBuffer, x dex bne :- ldx #(bombDir + (NUM_BOMBS - 1) - bombX) ; clear the bomb area : sta bombX, x dex bpl :- lda #XSIZE-1 ; place where terrain will start drawing (right hand) sta terrainOrigin lda #AUDIO_MOTOR_PLAY ; Init th eaudio-frame with the engine sta audioFrame lda #$ff sta lastInput ; set no keys down all 1's sta nextStage lda #DANGER_TICKS sta dangerTickCount ldx #(textEnd-textStage)-1 ; empty the text areas to spaces store: lda textStage, x beq :+ ; skip the null terminators lda #$20 ; load the space (" ") character sta textStage, x ; write it to the text areas : dex bpl store jsr gameFindStage ; point zWorldPtr at the stage start jsr gameWorldMove ; prime the draw buffers lda bufferInsert sta bufferDraw ; point the tail at the head inc bufferDraw ; advance the tail (this will correct itself) rts .endproc ;----------------------------------------------------------------------------- .proc gameShowPlayer jsr drawClearScreen print textPlayer, #(08), #(8*08) lda activePlayer clc adc #$31 sta textPlayerNum print textPlayerNum, #(23), #(8*08) jsr drawPresent wait #$40 jmp drawClearScreen .endproc ;----------------------------------------------------------------------------- .proc gamePlaceShip bottom = tempBlock top = tempBlock + 1 lda #$ff ; init markers with extremes sta bottom lda #0 sta top lda #(XSIZE/3) ; some way into the screen sta playerShipX ; put the ship clc adc bufferDraw ; make worldBBuffer relative to screen col 0 tay ldx #SHIP_WIDTH loop: lda worldBBuffer, y ; get the bottom at this col under the ship cmp bottom ; see if it's higher than the marker bcs :+ sta bottom ; is higher, update the marker : lda worldTBuffer, y ; get the top above the ship cmp top ; see if it's lower than the marker bcc :+ sta top ; if lower, update the top marker : iny dex bne loop ; do for all terrain under/above the ship lda bottom ; put the ship SHIP_START above the bottom sec sbc #SHIP_START sta playerShipY sec ; see how high the top is now sbc #SHIP_HEIGHT cmp top ; see if the top is now above the world top bcs done lda #SHIP_HEIGHT ; if it is, adjust the ship down to adc top ; be directly under the top - this happens in narrow passages sta playerShipY done: rts .endproc ;----------------------------------------------------------------------------- ; Manage the nextStage counter based on the stage variable changing ; This variable is needed because the stage changes relative to the marker ; based on direction. .proc gameCheckStage ldy #2 ; check for the current stage end marker lda (zWorldPtr), y bit Bit7Mask ; end of/start of stage marker beq noMarker ; not set means no marker lda direction ; there is a marker - see which direction bne :+ lda #XSIZE : sta nextStage noMarker: lda nextStage ; now deal with the nextStage marker counter, if set bmi done ; -1 means nothing to do lda stopScrolling ; if stopScrolling is set bne done ; then the stage counter does not count down dec nextStage ; count down to -1 eventually bne done ; non-zero - nothing to do dec nextStage ; at zero - take action and set to -1 so this inly triggers once sty updateHUD ; y = 2 so force HUD update draws for the 2 buffers lda direction ; based on direction, adjust the stage counter beq stageUp ; going right stagedown: dec stage ; going left so dec stage bpl done ; stage 0 and above - keep going lda #0 ; stage -1 means back home, sta enemyHit ; add score for a non-flying nuke (1000 pts) jsr gameAddScore lda #1 ; mark the "home run" as a victory sta victory rts stageUp: inc stage ; going right - stage up done: rts .endproc ;----------------------------------------------------------------------------- ; This processes the buffers for enemies, bullets, explosions and moves the bombs. .proc gameAI zaBombHeight = tempBlock + 1 zaTempFrame = tempBlock + 2 zaTempHeight = tempBlock + 2 zaEnd = tempBlock + 3 lda inFlight + 1 ; launches counted last frame sta inFlight ; use it this frame lda #0 ; reset the counter from last frame sta inFlight + 1 ; to count again in this frame ldy bufferInsert ; this is the end of the world iny ; but radar's can extend up to 3 beyond iny ; and the end is 1 past the last iny ; thing to process iny sty zaEnd ; so mark the end of processing ldy bufferDraw ; make enemyBuffer relative to screen col 0 loop: lda enemyBuffer, y beq explosion ; ignore blank columns bit Bit12Mask ; check if it needs animating beq :+ ; ignore monsters (%nnnnnn00) adc #%00010000 ; advance to next anim frame (radars & missiles) and #%01110000 ; restrict to these 3 bits sta zaTempFrame ; save only the frame counter lda enemyBuffer, y ; get the original and #%10001111 ; mask out the frame ora zaTempFrame ; add in the new frame (and thus preserve bit 8) sta enemyBuffer, y ; save this enemy now (post anim update) : bit Bit8432Mask ; check if this is a radar, 2nd column of a missile or a launched missile bne doHeight ; skip it if it is one of those things lda inFlight ; see how many rockets are in-flight cmp #NUM_INFLIGHT * 2 ; limit to 3 (both halves count so looks like 6 launches) bcs :+ lda enemyHgtBuffer, y ; this is the left hand column of a missile - see if it needs to launch sec sbc playerShipY ; get the distance from the player lsr ; put it in the same units as the X lsr cmp #31 ; distance cut off - > delta then rockets don't launch bcs :+ sta tempBlock + 4 ; approx 4/3 * height lsr tempBlock + 4 ; since the missiles move 3 rows up for every lsr tempBlock + 4 ; 4 pixels the world moves clc adc tempBlock + 4 sta zaTempHeight ; save it tya ; get the distance of this rocket from the player in X sec ; start by calculating the screen X position of the rocket sbc bufferDraw ; make enemyBuffer relative to screen col 0 sbc playerShipX ; and then subtracting the screen x position of the ship bcc :+ ; player is to the right so ignore cmp zaTempHeight ; is dist y to missile = dist x to missile (in column units?) beq launch ; if equal, do a launch : lda enemyBuffer, y bne doHeight ; no match, so no launch launch: iny ; mark the right hand column as launched lda enemyBuffer, y ora #%10000000 sta enemyBuffer, y dey lda enemyBuffer, y ; mark left column as launched ora #%10000000 sta enemyBuffer, y doHeight: bit Bit8Mask ; see if this needs height adjustment beq explosion ; don't move non-launched stuff inc inFlight + 1 ; count launched rockets bit Bit1Mask ; see if this is a missile bne :+ ; this is a missile bit Bit34Mask ; don't do anything unless this is bne explosion ; the 1st column of the monster jsr gameMonsterAI ; This is a monster - run its AI jmp explosion : lda enemyHgtBuffer, y sec sbc #3 cmp #WORLD_START bcs :+ ; out of existence at the top, kill it off lda #0 sta enemyBuffer, y : sta enemyHgtBuffer, y explosion: lda explosionBuffer, y ; advance the explosion sprite frame beq nextBufferEntry clc adc #1 bit Bit3Mask ; at frame 4, stop the explosion beq :+ lda #0 : sta explosionBuffer, y nextBufferEntry: ; advance processing to the next enemy iny cpy zaEnd ; see if all enemies processed clc beq doBombs jmp loop doBombs: ldx #NUM_BOMBS - 1 ; move the bombs loopb: lda bombY, x beq nextb ; 0 in Y means inactive clc adc #3 ; move down 4 rows sta bombY, x lda bombDir, x ; get the travel direction beq :+ ; if 0, it's going straight down dec bombDir, x ; decrease the columns it will move forward for inc bombX, x ; <> 0, still moving forward, inc it's x position bne skbd ; always (jmp) : lda stopScrolling ; see if the world is moving bne skbd ; if not, don't adjust the bomb X dec bombX, x ; scroll bomb with world to go straight down skbd: lda bombX, x ; get the screen column bmi bol ; if < 0 then bomb out left hand side of screen adc bufferDraw ; make enemyBuffer relative to screen col 0 tay lda worldBBuffer, y ; get the terrain bottom height in this col sec sbc bombY, x ; subtract the bomb height bcs nextb ; if the bomb < terrain (carry set), all is well bol: lda #0 ; bomb too low, write 0 into the bomb height sta bombY, x nextb: dex bpl loopb ; repeat for all bombs clc rts .endproc ;----------------------------------------------------------------------------- ; This moves the monster up and down between the top and bottom of the world ; where it will pause for a moment. When the monster overlaps the player, it ; will also pause for a moment before going back to the bounce ; called with y the index in enemyBuffer to the 1st strip of the monster and ; acc the value in enemyBuffer, y .proc gameMonsterAI zaTempIndex = tempBlock + 2 ; y reg saved as Index to 1st strip in monster and #%01100000 ; check if the stop counter has a value beq moving ; if not, monster is moving paused: ; the monster is at top/bottom or overlapped player lda #%00100000 adc enemyBuffer, y ; add to the monster value ora #%10000000 ; make sure the flying bit stays on sta enemyBuffer, y ; save the update rts moving: ; the monster is bouncing up or down sty zaTempIndex ; save the index - need to work on 3 strips ldx #MONSTER_WIDTH ; load x with the width (3) lda enemyBuffer, y ; reload the enemy (acc destroyed by and above) bit Bit5Mask ; bit 5 is direction - 0 is up and 1 is down bne down up: lda enemyHgtBuffer, y ; monster is at same height in all its columns sec sbc #MONSTER_SPEED ; move up to where it would be sbc #MONSTER_HEIGHT ; but need the top of the monster to check if it hit the ceiling dey ; adjust for alignment with world buffer chktop: cmp worldTBuffer, y ; compare to the world ceiling height beq changedir ; if equal, change direction don't go up bcc changedir ; if less than, also don't go up but change dir iny ; do for all the monster strips dex bne chktop ldx #MONSTER_WIDTH ; not in ceiling collision so adjust height ldy zaTempIndex ; start at the 1st strip : lda enemyHgtBuffer, y ; move the strips upwards sec sbc #MONSTER_SPEED ; speed at which monster moves sta enemyHgtBuffer, y ; save updated position iny ; repeat for all strips dex bne :- chkplayerhgt: ; see if the monster is now overlapping the enemy ship clc ldy zaTempIndex ; get the 1st strip index ldx #MONSTER_SPEED ; check as many columns as moved (faster for than overlap calc) : cmp playerShipY ; is it matching the player beq stop ; if yes, stop the monster adc #1 ; try each position in the step dex ; and repeat for all increments in a monster step (speed) bne :- rts changedir: ldy zaTempIndex ; go back to the 1st strip lda enemyBuffer, y ; get the enemy eor #%00110000 ; turn on stop and change dir in one go sta enemyBuffer, y ; save rts stop: lda enemyBuffer, y ; get the 1st strip ora #%00100000 ; set stop counter to 1 sta enemyBuffer, y ; save rts down: lda enemyHgtBuffer, y ; monster is at same height in all its columns clc adc #MONSTER_SPEED ; height down by speed to where it would go ldx #MONSTER_WIDTH ; check for a bottom collision dey ; adjust for the world offset chkbot: cmp worldBBuffer, y ; get the world below this strip bcs changedir ; if the world is higher than the base of the monster, change direction iny ; check all strips dex bne chkbot ldx #MONSTER_WIDTH ; height okay, so finally move the monster ldy zaTempIndex ; starting at strip 1 : lda enemyHgtBuffer, y ; get the strip height clc adc #MONSTER_SPEED ; adjust the height sta enemyHgtBuffer, y ; save it iny ; do for all strips dex bne :- beq chkplayerhgt ; JuMP to see if the monster and player now overlap done: rts .endproc ;----------------------------------------------------------------------------- .proc gameWorldMove jsr gameUpdateDangerBar ; when the world moves, the danger bar needs to be updated clc inc bufferInsert ; advance where buffer data is inserted ldx bufferInsert ; get the new right edge inx ; step past where radar columns might be inx inx lda #0 ; write a null to kill old circular data sta enemyBuffer, x sta enemyHgtBuffer, x ldx bufferInsert ; get the right edge again lda direction ; world prt moves based on level forward/reverse direction bne left clc ; moving through to the right, advance the world 1 column lda zWorldPtr adc #3 sta zWorldPtr bcc :+ inc zWorldPtr + 1 clc : ldy #0 ; get the world bottom lda (zWorldPtr), y sta worldBBuffer, x iny ; get the world top lda (zWorldPtr), y sta worldTBuffer, x lda terrainOrigin ; if the terrain is still scrolling in bne done ; then don't populate enemies iny ; get the enemy flags lda (zWorldPtr), y ; get the enemy flag beq done ; no enemies, carry on jmp gameWorldFlag ; populate the world with enemies left: ; to the left (in the data, back to front), 1 column "subtract" sec lda zWorldPtr sbc #$03 sta zWorldPtr bcs :+ dec zWorldPtr + 1 : clc ; make carry clear since gameWorldFlag expects to be called with carry clear ldy #6 ; get the world bottom lda (zWorldPtr), y sta worldBBuffer, x iny ; get the world top lda (zWorldPtr), y sta worldTBuffer, x lda terrainOrigin ; if the terrain is still scrolling in bne done ; then don't populate enemies ldy #8 ; look ahead 1 col lda (zWorldPtr), y ; get the enemy flag bit Bit1Mask ; is this a missile beq radar ; not a missile, maybe there's a radar coming? jmp gameWorldFlag ; this is a missile - go add it radar: ldy #2 ; Look ahead even further for a radar coming on-screen lda (zWorldPtr), y ; get the enemy flag bit Bit2Mask ; is this a radar beq done ; no, then nothing more to do jmp gameWorldFlag ; yes - add the radar done: rts .endproc ;----------------------------------------------------------------------------- .proc gameFindStage zaStage = tempBlock + 1 ldy #2 ; world data is bottom, top and then flags. need flags in this routine lda direction ; world prt moves based on level forward/reverse direction bne left lda #worldDataStart sta zWorldPtr + 1 lda stage ; see how many stage markers to skip beq done ; stage 0 - skip none, start of data is good sta zaStage rloop: lda (zWorldPtr), y ; get the flags bit Bit7Mask ; see if it's a stage marker beq :+ dec zaStage ; found a stage but step once more not to trigger in-game : lda zWorldPtr ; step the pointer over the data triplet clc adc #3 sta zWorldPtr bcc :+ inc zWorldPtr + 1 : jsr gameUpdateDangerBar ; update the danger meter lda zaStage ; now check if the stage is found beq done ; and exit if it has bne rloop left: ; world is moving backwards so lda #<(worldDataEnd - 3) ; start at the end of the data, looking sta zWorldPtr ; at the 1st triplet of data lda #>(worldDataEnd - 3) sta zWorldPtr + 1 lda #(NUM_STAGES-1) ; see how many stage markers to skip sec sbc stage beq done sta zaStage ; save the counter lloop: lda (zWorldPtr), y ; get the flags bit Bit7Mask ; and see if it's a stage flag beq :+ dec zaStage ; may be the stage being sought but step once more : lda zWorldPtr ; move the ptr backwards through the data sec ; to the previous data triplet sbc #3 sta zWorldPtr bcs :+ dec zWorldPtr + 1 : jsr gameUpdateDangerBar ; update the danger meter lda zaStage ; see if this is the stage being sought beq done ; and exit if it is bne lloop done: clc rts .endproc ;----------------------------------------------------------------------------- ; Use the 4 user font characters (at $80+) to draw a dotted line danger bar .proc gameUpdateDangerBar dec dangerTickCount ; count down to adding a dot beq tick ; time to add a dot? rts tick: lda #DANGER_TICKS ; reset the countdown sta dangerTickCount ldx dangerTickIdx ; get the index for how far into the count lda textDangerBar, x ; get the character cmp #' ' ; 1st time - this is a space so init beq dot cmp #$84 ; 4 dots last character so move to next column? beq :+ inc textDangerBar, x ; add a dot by advancing the font bne hud ; JuMP to hud update : inx ; move to next character in the display stx dangerTickIdx ; save the index dot: lda #$80 ; 1 dot - load the single dot font character sta textDangerBar, x ; put it in the display hud: lda #2 ; update the hud twice so both buffers sta updateHUD ; get updated clc rts .endproc ;----------------------------------------------------------------------------- ; Process the flags in the world data stream. carry must be clear when called ; radar is %01, missile is %10 .proc gameWorldFlag bit Bit2Mask ; radar? beq missile ; no, see if it's a missile lda gameMode ; In edit mode, don't draw nuke's for radars bit Bit2Mask ; GAME_MODE_EDIT is $02 so if bit set then skip nuke check bne :+ lda stage cmp #(NUM_STAGES - 1) ; stages are 0 based - this is the last stage clc beq nuke ; in stage 4 the radars become nuke's : lda worldBBuffer, x ; load the height of the world below this enemy sbc #0 ; carry is clear so this is -1 tay ; save the height in y sta enemyHgtBuffer, x ; set the height for the 1st radar strip lda #SPR_RADAR0 ; put the strips into the enemy buffer sta enemyBuffer, x inx ; go to the next strip tya ; get the height from y sta enemyHgtBuffer, x ; and set up this strip and the remaining strips lda #SPR_RADAR1 sta enemyBuffer, x inx tya sta enemyHgtBuffer, x lda #SPR_RADAR2 sta enemyBuffer, x inx tya sta enemyHgtBuffer, x lda #SPR_RADAR3 sta enemyBuffer, x clc rts nuke: lda worldBBuffer, x ; put the nuke strips in sbc #0 tay sta enemyHgtBuffer, x lda #SPR_NUKE0 sta enemyBuffer, x inx tya sta enemyHgtBuffer, x lda #SPR_NUKE1 sta enemyBuffer, x clc rts missile: bit Bit1Mask ; is it a missile beq done ; no, move on ldy stage ; see if in this stage some missiles become monsters lda makeMonster, y beq :+ ; 0 means no, not in this stage dec monsterCnt beq monster ; when this is 0, make a missile a monster : lda worldBBuffer, x ; get the height sbc #0 ; -1 (carry clear) to put it above ground tay sta enemyHgtBuffer, x ; mark as the height lda #SPR_MISSILE0 sta enemyBuffer, x inx tya sta enemyHgtBuffer, x lda #SPR_MISSILE1 sta enemyBuffer, x clc rts monster: lda #NUM_SKIP_MONST sta monsterCnt lda worldBBuffer, x sbc #0 tay sta enemyHgtBuffer, x ; set the height for the 4 radar strips lda #SPR_MONSTER0 ; put the strips into the enemy buffer sta enemyBuffer, x inx tya sta enemyHgtBuffer, x lda #SPR_MONSTER1 sta enemyBuffer, x inx tya sta enemyHgtBuffer, x lda #SPR_MONSTER2 sta enemyBuffer, x clc rts done: rts .endproc ;----------------------------------------------------------------------------- .proc gamePlayerCollision lda #0 sta zCollision ; reset the collision flag lda playerDead ; already marked as dead? beq :+ rts : lda #$20 ; mark the death and hold for sta playerDead ; some frames lda #2 ; force a HUD redraw to show sta updateHUD ; reduced lives lda #1 ; stop the world moving sta stopScrolling lda gameMode ; get the game mode bne done ; and only take a life if in gameplay (= 0) sed ; take away a life lda lives ; lives are done in decimal sec ; for easier printing sbc #1 sta lives cld clc done: rts .endproc ;----------------------------------------------------------------------------- .proc gameNextPlayer lda lives bne stillOk print textGameOver, #(0), #(8*05) ; show the game over text jsr drawPresent lda #0 bit KBDSTRB ; flush the keyboard buffer sta lastInput : jsr inputCheckForInput ; wait for user interaction beq :- jsr uiCheckHighScore ; Did this player make it onto the high score table stillOk: lda numPlayers ; see if it's a 2-player game beq done ldy activePlayer ; save the outgoing players' stats jsr gameStatsToPlayer ldy #0 ; assume p1 was active lda activePlayer ; get the active player eor #1 ; swap 0->1 or 1->0 tax ; next player in x beq :+ ; if 0 is next, y is set up already ldy #(rsEnd-rsStart) ; 1 is next, so set y up to point at p1 : lda playerStats, y ; get the lives (1st element is "struct") beq done ; if none, then don't switch players ldy activePlayer ; incoming player has lives - save current active in Y stx activePlayer ; switch to this (incoming) player ldy activePlayer ; install the players' stats jsr gamePlayerToStats done: rts .endproc ;----------------------------------------------------------------------------- ; Copy the "live" stats to the backup location for a player ; y is the index of the player (0 or 1) .proc gameStatsToPlayer ldx #(rsEnd-rsStart) - 1 ; assume p0 destination dey bne :+ ldx #((rsEnd-rsStart) * 2) - 1 ; actually p1 so adjust copy dest location : ldy #(rsEnd-rsStart) - 1 ; set y to the end of the runtime stats buffer : lda rsStart, y ; load runtime stat sta playerStats, x ; save as player stat dex ; do for all bytes needing to be copied dey bpl :- ; copy all bytes rts .endproc ;----------------------------------------------------------------------------- ; Copy the backup player stats to the "live" stats location ; y is the index of the player (0 or 1) .proc gamePlayerToStats ldx #(rsEnd-rsStart) - 1 ; assume p0 destination dey bne :+ ldx #((rsEnd-rsStart) * 2) - 1 ; actually p1 so adjust destination : ldy #(rsEnd-rsStart) - 1 ; set y to the end of the runtime stats : lda playerStats, x ; load a player stat byte sta rsStart, y ; copy it to a runtime stat byte dex ; next bytes dey bpl :- ; do for all bytes that need copying rts .endproc ;----------------------------------------------------------------------------- .proc gameWinSequence iterations = tempBlock + 0 ; how many times to do this loop inc lives ; give the player another life lda #1 sta updateHUD jsr uiUpdateGameHud jsr drawPresent ; get the score and live visible lda #$07 ; flash the screen iteration times sta iterations : jsr drawInvertVisibleScreen dec iterations bne :- lda direction ; flip the direction eor #1 sta direction beq :+ ; if dir zero - going out again from stage 0 lda #3 ; zero based - second last stage - don't play nuke stage again : sta stage jsr uiWinScreen ; show the BONUS screen ldx #(NUM_STAGES - 2) ; start at the stage before the last (0 based) : dex ; step back - but the second last stage was already marked bmi :+ ; if at -1 then all stages checked lda makeMonster, x ; see if this stage has rockets turn into monsters yet bne :- ; if yes, then keep looking for a stage without that lda #1 ; mark this stage sta makeMonster, x ; as now also having monster-rockets : ldy activePlayer ; make sure the stats are backed up jmp gameStatsToPlayer .endproc ;----------------------------------------------------------------------------- ; Removes an enemy from screen that overlaps with zEnemyCol ; zaDrawSpriteWidth says how many columns to check - the bomb could ; kill in one of 2 columns, otherwise just one .proc gameKillEnemy zaDrawSprWidth = tempBlock + 3 ; Parameter zaSprColumn = tempBlock + 2 ; Internal segment/column of enemy sprite zaEnemyColLocal = tempBlock + 4 ; Internal Backup copy of zEnemyCol but modified lda zEnemyCol sta zaEnemyColLocal ; make a backup that's safe to change lda #0 ; reset the collision flag sta zCollision ldy #$ff sty enemyHitType loop: ; for the width to check ldx zaEnemyColLocal lda enemyBuffer, x ; see if there's an enemy beq next sta enemyHit ; save the flags of the enemy that was struck lsr ; see if it's a radar or missile bcc other ; lsb clear means not a missile lsr ; missile or nuke. 1 more shift to get column in lsb's bcc missile ldy #1 ; hit a nuke sty victory ldy #0 ; nuke is 0 in the score table beq continue missile: ldy #1 ; missile and nuke 2 columns wide bne continue other: lsr ; check next bit bcc monster ; if also clear then a monster ldy #3 ; radar is 4 columns wide bne continue monster: ldy #2 ; monster is 3 columns wide continue: sty enemyHitType ; 0 = nuke, 1 = missile, 2 = monster, 3 = radar, $ff - nothing bne :+ ; now Y is being used as column (width - 1) of the enemy iny ; for the nuke, make y = 1 (it's still 0 here as the type) : and #%00000011 ; mask column (or strip of the sprite) sta zaSprColumn ; save it lda audioFrame ora #AUDIO_EXPLOSION ; Add explosion to audio frame sta audioFrame lda #0 ; start the explosion at frame 0 sta audioExplFrame lda zaEnemyColLocal ; see where the sprite starts sec ; by subtracting the strip number (index) sbc zaSprColumn ; from the current world column tax ; and save that lda enemyHgtBuffer, x ; get the height of the enemy that's being killed and #%11111000 ; mask off 3 bits for the explosion sprite frame cntr sta explosionBuffer, x ; save as the explosion lda #0 ; write 0 to the enemy strip columns sta enemyBuffer, x sta enemyHgtBuffer, x inx sta enemyBuffer, x sta enemyHgtBuffer, x dey beq next ; early out on missiles and nukes inx sta enemyBuffer, x sta enemyHgtBuffer, x dey beq next ; early out on monsters inx sta enemyBuffer, x sta enemyHgtBuffer, x next: inc zaEnemyColLocal dec zaDrawSprWidth bne loop clc rts .endproc ;----------------------------------------------------------------------------- ; Add score for the enemy destroyed and mark the HUD as needing an update ; Acc is 0 = nuke, 1 = missile, 2 = monster, 3 = radar from enemyHitType ; The scoreTable score is "amended" in that flying objects get 4 added ; Scores appear on the hud at 10x the score variable value .proc gameAddScore asl ; score table is 2 bytes wide tax ; save the type lda enemyHit ; see if this enemy was flying and #%10000000 bne flying lda #0 ; not flying beq :+ flying: lda #4 ; was flying : sed ; use decimal mode adc scoreTable, x ; add across all 3 score bytes, as needed (3 bytes up to 999999 score) adc score sta score inx lda scoreTable, x adc score + 1 sta score + 1 bcc :+ lda #0 ; only adc and sbc work right in sed adc score + 2 sta score + 2 clc : cld ; turn off decimal mode lda #2 sta updateHUD rts .endproc