LIST OFF ; *** A I R - S E A B A T T L E *** ; Copyright 1977 Atari, Inc. ; Designer: Larry Kaplan ; Analyzed, labeled and commented ; by Dennis Debro ; Last Update: Oct. 5, 2004 ; ; - This game uses a horizontal positioning routine originally derived by Joe ; Decuir (see CalcXPos). This routine takes an x-position and calculates the ; horizontal delta for the fine position and the coarse value needed to ; reset the player's position. ; - The game speeds aren't adjusted for PAL. ; - The kernel zone height was adjusted for PAL. PAL kernel zones are 3 lines ; higher than NTSC. ; - D4 of SWCHB is set for output in the NTSC version. I assume this was done ; to save a bit when calculating missile size based on difficutly setting as ; D4 sets the missile to size 2. The problem is when the values of SWCHB are ; masked to get the difficulty setting, D4 is set to 0. This causes the ; EXPERT missile size to be 0 for NTSC. This isn't used for the PAL version. ; An EXPERT setting for PAL will set the missile to size 2 as intended. ; - It seems RAM locations $91 and $92 are not used. processor 6502 include vcs.h LIST ON ;=============================================================================== ; A S S E M B L E R - S W I T C H E S ;=============================================================================== NTSC = 0 PAL = 1 COMPILE_VERSION = NTSC ; change this to compile for different ; regions ;============================================================================ ; T I A - C O N S T A N T S ;============================================================================ HMOVE_L7 = $70 HMOVE_L6 = $60 HMOVE_L5 = $50 HMOVE_L4 = $40 HMOVE_L3 = $30 HMOVE_L2 = $20 HMOVE_L1 = $10 HMOVE_0 = $00 HMOVE_R1 = $F0 HMOVE_R2 = $E0 HMOVE_R3 = $D0 HMOVE_R4 = $C0 HMOVE_R5 = $B0 HMOVE_R6 = $A0 HMOVE_R7 = $90 HMOVE_R8 = $80 ; values for ENAMx and ENABL DISABLE_BM = %00 ENABLE_BM = %10 ; values for NUSIZx ONE_COPY = %000 DOUBLE_SIZE = %101 QUAD_SIZE = %111 MSBL_SIZE1 = %000000 MSBL_SIZE2 = %010000 MSBL_SIZE4 = %100000 MSBL_SIZE8 = %110000 ; values for REFPx NO_REFLECT = %0000 REFLECT = %1000 ; mask for SWCHB P1_DIFF_MASK = %10000000 BW_MASK = %00001000 SELECT_MASK = %00000010 RESET_MASK = %00000001 ; SWCHA joystick bits MOVE_RIGHT = %1000 MOVE_LEFT = %0100 MOVE_DOWN = %0010 MOVE_UP = %0001 NO_MOVE = %11111111 ;============================================================================ ; U S E R - C O N S T A N T S ;============================================================================ ROMTOP = $F000 ; color constants BLACK = $00 WHITE = $0E YELLOW = $10 RED = $30 PURPLE = $60 BLUE = $90 GREEN_BLUE = $A0 GREEN = $C0 IF COMPILE_VERSION = NTSC ; NTSC frame time values VBLANK_TIME = $2C ; NTSC color constants LIGHT_BLUE = $80 BROWN = $E0 ; NTSC kernel constants H_KERNEL = 102 MISSILE_VELOCITY = 48 MISSILE_YMAX = 231 ELSE ; PAL frame time values VBLANK_TIME = $2D ; PAL color constants LIGHT_BLUE = $B0 BROWN = $20 ; NTSC kernel constants H_KERNEL = 123 MISSILE_VELOCITY = 64 MISSILE_YMAX = 240 ENDIF XMIN = 8 XMAX = 158 MISSILE_XMAX = XMAX - 7 OBJECT_XMAX_SINGLE = XMAX-9 OBJECT_XMAX_DOUBLE = OBJECT_XMAX_SINGLE-9 YMIN = 255-H_KERNEL+7 YMAX = 224 YPOS_PLAYER1 = 255-H_KERNEL+12 YPOS_PLAYER2 = YPOS_PLAYER1+8 PLAYER1_GUN_XMIN = 2 PLAYER2_GUN_XMIN = 77 PLAYER1_GUN_XMAX = 69 PLAYER2_GUN_XMAX = 144 PLAYER1_STARTING_X = 32 PLAYER2_STARTING_X = 120 GUN_PIXEL_MOVEMENT = 2 MAX_GAME_SELECTION = 27 SELECT_DELAY = $3F SCORE_FLASH_DELAY = $30 BW_HUE_MASK = $0F COLOR_HUE_MASK = $FF NUM_KERNEL_ZONES = 9 STARTING_GAME_TIME = 128 EXPLOSION_TIME = 32 MAX_WAIT_TIME = 63 ;game state values SYSTEM_POWERUP = %00001010 GAME_RUNNING = %11111111 ; game variation flags POLARIS_OR_BOMBER = %10000000 MOVE_LEFT_RIGHT = %01000000 POLARIS_VS_BOMBER = %00100000 SINGLEPLAYER = %00010000 SHOOTING_GALLERY = %00001000 WATER_OBSTACLES = %00000100 OBSTACLES = %00000010 GUIDED_MISSILES = %00000001 ; objectType ids ID_LARGE_JET = 0 ID_SMALL_JET = 8 ID_747 = 16 ID_HELICOPTER = 24 ID_BLIMP = 32 ID_RABBIT = 40 ID_CLOWN = 48 ID_DUCK = 56 ID_AIRCRAFT_CARRIER = 64 ID_PT_BOAT = 72 ID_FREIGHTER = 80 ID_PIRATE_SHIP = 88 ID_MINE_0 = 96 ID_MINE_1 = 104 ; object score values (BCD) LARGE_JET_SCORE = $03 SMALL_JET_SCORE = $04 _747_SCORE = $01 HELICOPTER_SCORE = $02 BLIMP_SCORE = $00 RABBIT_SCORE = $03 CLOWN_SCORE = $01 DUCK_SCORE = $02 AIRCRAFT_CARRIER_SCORE = $03 PT_BOAT_SCORE = $04 FREIGHTER_SCORE = $01 PIRATE_SHIP_SCORE = $02 MINE_SCORE = $00 ; object state flags OBJECT_HIT_STATE = %10000000 OBJECT_DISABLED_STATE = %11000000 MAX_SCORE = $99 ; velocity values VELOCITY_SLOW = 0 VELOCITY_REST = 16 VELOCITY_FAST = VELOCITY_REST*2 OBJECT_VELOCITY_MEDIUM = 2 MISSILE_HORZ_VELOCITY_90 = 0 MISSILE_HORZ_VELOCITY_60 = 1 MISSILE_HORZ_VELOCITY_30 = 2 MISSILE_VERT_VELOCITY_30 = 1 MISSILE_VERT_VELOCITY_60 = 2 MISSILE_VERT_VELOCITY_90 = 2 ;============================================================================ ; Z P - V A R I A B L E S ;============================================================================ frameCount = $80 ; updated each frame currentScanline = $81 ; keeps track of scan line in kernel gameTimer = $82 selectDebounce = $83 ; debounce flag for the SELECT switch startingKernelScanline = $84 ; scan line to start the kernel scoreGraphics = $85 ; PF data to draw the score ;-------------------------------------- scoreGraphic1 = scoreGraphics scoreGraphic2 = scoreGraphics+1 temp = $87 ;-------------------------------------- tempMissileHorizPos = temp ;-------------------------------------- tempKernelZone = temp ;-------------------------------------- tempPlayerIndex = temp ;-------------------------------------- objectVelocity = temp objectXMax = $88 ;-------------------------------------- IF COMPILE_VERSION = PAL kernelZonePad = objectXMax; only used for PAL ; PAL kernel zones are 3 lines higher ENDIF ; than NTSC hueMask = $89 ; masks the color hues ;-------------------------------------- objectStateMask = hueMask ; used to show/hide objects colorXOR = $8A ; used to cycle colors during attract mode ;-------------------------------------- playerHitKernelZone = colorXOR ; saves which player hit in polaris games playerScores = $8B ;-------------------------------------- player1Score = playerScores player2Score = player1Score+1 scoreOffsets = $8D ;-------------------------------------- lsbScoreOffsets = scoreOffsets player1LSBOffset = lsbScoreOffsets player2LSBOffset = lsbScoreOffsets+1 ;-------------------------------------- msbScoreOffsets = scoreOffsets+2 player1MSBOffset = msbScoreOffsets player2MSBOffset = msbScoreOffsets+1 missileVertPos = $93 ; missile y-position ;-------------------------------------- missile2VertPos = missileVertPos missile1VertPos = missileVertPos+1 randomSeed = $95 ; 8-bit random number tempRandomSeed = $96 gameState = $97 ; see game state flags in constants gameSelection = $98 gameSelectionBCD = $99 ; holds game selection in BCD gameVariation = $9A ; see game variation flags in constants scoreMask = $9B ; used to turn on/off right digits playerVelocity = $9C ;-------------------------------------- player1Velocity = playerVelocity player2Velocity = playerVelocity+1 explosionSpriteOffset = $9E ; table offset for the explosion sprite gameBoardDone = $9F ; D7 = 0 done -- D7 = 1 not done objectShowState = $A0 ; 0 = show object 1 = don't show object playerKernelZones = $A1 ; kernel zone for player in polaris games ;-------------------------------------- player1KernelZone = playerKernelZones player2KernelZone = playerKernelZones+1 playerGraphicOffsets = $A3 ; table offset for the player sprites ;-------------------------------------- player1GraphicOffset = playerGraphicOffsets player2GraphicOffset = playerGraphicOffsets+1 joystickValues = $A5 ; saved joystick values for players ;-------------------------------------- player1JoystickValue = joystickValues player2JoystickValue = joystickValues+1 missileTrajectory = $A7 ; angle of missile movement ;-------------------------------------- player1MissileTraj = missileTrajectory player2MissileTraj = missileTrajectory+1 missileHorizPos = $A9 ; missile x-position ;-------------------------------------- missile1HorizPos = missileHorizPos missile2HorizPos = missileHorizPos+1 missileMask = $AB ; used to enable/disable missile in kernel ;-------------------------------------- player2MissileMask = missileMask player1MissileMask = missileMask+1 audioFrequencies = $AD ; $AD - $B0 objectGraphicIndex = $B1 objectAttributes = $B2 ; holds object's size and reflect state ;-------------------------------------- playerDiffState = objectAttributes+8 ;-------------------------------------- player1DiffState = playerDiffState player2DiffState = playerDiffState+1 zoneHorizPos = $BC ; x-position of the objects playerHorizPos = $C4 ; x-position of the players ;-------------------------------------- player1HorizPos = playerHorizPos player2HorizPos = playerHorizPos+1 kernelZoneHorizPos = $C6 ; the fine/coarse x-value of the objects playerFineCoarsePos = $CE ; the fine/coarse x-value of the players ;-------------------------------------- player1FineCoarsePos = playerFineCoarsePos player2FineCoarsePos = playerFineCoarsePos+1 objectIds = $D0 ; object in kernel zone and wait time ; before re-spawning missileCollisions = $D9 ; collision flags (one for each zone) playerColors = $E2 ; color of object's in a zone kernelZoneColors = $EC ; background color of a zone ;============================================================================ ; R O M - C O D E (Part 1) ;============================================================================ SEG Bank0 org ROMTOP Start ; ; Set up everything so the power up state is known. ; sei ; disable interrupts cld ; clear decimal mode lda #0 tax .clearLoop sta VSYNC,x inx bne .clearLoop lda #SYSTEM_POWERUP sta REFP1 ; REFLECT player 1 sta CTRLPF ; set to SCORE mode and non-relective PF sta gameState IF COMPILE_VERSION = NTSC lda #%00010000 sta SWBCNT ; set D4 of SWCHB as output ENDIF MainLoop VerticalSync lda #%00000010 sta WSYNC sta VBLANK ; disable TIA (D1 = 1) sta WSYNC ; wait 3 scan lines before starting new sta WSYNC ; frame sta WSYNC sta VSYNC ; start vertical sync (D1 = 1) inc frameCount ; increment frame count each new frame sta WSYNC ; first line of VSYNC sta WSYNC ; second line of VSYNC lda #0 sta WSYNC ; third line of VSYNC sta VSYNC ; end vertical sync (D1 = 0) lda #VBLANK_TIME sta TIM64T ; set timer for vertical blanking period ldx #$FF txs ; point stack to the beginning jsr GameCalculations lda #255-H_KERNEL ; the scan line variable is incremented sta currentScanline ; until it reaches 0 DisplayKernel SUBROUTINE .waitTime lda INTIM bne .waitTime sta WSYNC ; end last scan line sta HMOVE sta VBLANK ; enable TIA (D1 = 0) .scoreKernelWait sta WSYNC sta HMCLR ; clear all horizontal positioning inc currentScanline sta WSYNC ;-------------------------------------- lda currentScanline ; 3 continue looping until the cmp startingKernelScanline ; 2 appropriate starting scan line has bcc .scoreKernelWait ; 2³ been reached cmp #255-H_KERNEL+3 ; 2 bcs BeginPlayfieldKernel ; 2³ ScoreKernel sta WSYNC ;-------------------------------------- lda scoreGraphic1 ; 3 get the score graphic for display sta PF1 ; 3 = @06 ldy player1MSBOffset ; 3 lda NumberFonts,y ; 4 read the number fonts and #$F0 ; 2 mask the lower nybble sta scoreGraphic1 ; 3 save it in the score graphic ldy player1LSBOffset ; 3 lda NumberFonts,y ; 4 read the number fonts and #$0F ; 2 mask the upper nybble ora scoreGraphic1 ; 3 or with score graphic to get LSB sta scoreGraphic1 ; 3 value lda scoreGraphic2 ; 3 get the score graphic for display sta PF1 ; 3 = @39 ldy player2MSBOffset ; 3 lda NumberFonts,y ; 4 read the number fonts and #$F0 ; 2 mask the lower nybble sta scoreGraphic2 ; 3 save it in the score graphic ldy player2LSBOffset ; 3 lda NumberFonts,y ; 4 read the number fonts and scoreMask ; 3 scoreMask turns on/off right digits ora scoreGraphic2 ; 3 or with score graphic to get LSB sta scoreGraphic2 ; 3 value sta WSYNC ; 3 = @70 ;-------------------------------------- inc currentScanline ; 5 lda currentScanline ; 3 cmp #YMIN ; 2 bcs BeginPlayfieldKernel ; 2³ lda scoreGraphic1 ; 3 sta PF1 ; 3 = @18 inc player1LSBOffset ; 5 inc player1MSBOffset ; 5 inc player2LSBOffset ; 5 inc player2MSBOffset ; 5 lda scoreGraphic2 ; 3 sta PF1 ; 3 = @44 jmp ScoreKernel ; 3 BeginPlayfieldKernel ldx #0 ; 2 stx PF1 ; 3 = @18 clear PF1 so digit doesn't bleed stx objectGraphicIndex ; 3 .playfieldKernelLoop lda objectAttributes,x ; 4 sta NUSIZ0 ; 3 = @28 set the size of the object sta REFP0 ; 3 = @31 set the object's reflect state lda CXM0P ; 3 read the missile 0 collisions lsr ; 2 shift the values to D5 and D4 lsr ; 2 ora CXM1P ; 3 read the missile 1 collisions sta missileCollisions,x ; 4 store the collision value lda playerColors,x ; 4 get the colors for objects in zone sta COLUP0 ; 3 = @52 sta COLUP1 ; 3 = @55 lda kernelZoneColors,x ; 4 get the kernel zone color sta COLUBK ; 3 = @62 IF COMPILE_VERSION = PAL lda #3 ; 2 sta kernelZonePad ; 3 ENDIF sta WSYNC ;-------------------------------------- lda kernelZoneHorizPos,x ; 4 sta HMP0 ; 3 = @07 set the object's fine motion value lda kernelZoneHorizPos,x ; 4 read again to waste 4 cycles and #$0F ; 2 mask upper nybble for coarse value tay ; 2 .coarseMoveObject dey ; 2 bpl .coarseMoveObject ; 2³ sta RESP0 ; 3 set the object's coarse position sta WSYNC ;-------------------------------------- sta HMOVE ; 3 bmi .setMissileState ; 3 unconditional branch ZoneKernel sta WSYNC ;-------------------------------------- .setMissileState txa ; 2 save kernel zone in accumulator ldx #ENAM1 ; 2 load x with location of ENAM1 txs ; 2 set stack to point to ENAM1 tax ; 2 restore kernel zone in x sec ; 2 lda currentScanline ; 3 sbc missile1VertPos ; 3 and player1MissileMask ; 3 php ; 3 = @22 enables/disables missile 1 sec ; 2 lda currentScanline ; 3 sbc missile2VertPos ; 3 and player2MissileMask ; 3 sta temp ; 3 lda objectIds,x ; 4 bpl .determineSpriteOffset ; 2³ asl ; 2 bpl .setToExplosionSprite ; 2³ lda #0 ; 2 beq DrawObjectSprite ; 3 .setToExplosionSprite lda explosionSpriteOffset ; 3 .determineSpriteOffset ora objectGraphicIndex ; 3 tay ; 2 lda GameSprites,y ; 4 DrawObjectSprite inc currentScanline ; 5 sta WSYNC ;-------------------------------------- sta GRP0 ; 3 = @03 lda temp ; 3 php ; 3 = @09 inc objectGraphicIndex ; 5 lda objectGraphicIndex ; 3 get graphic index to see if we're and #$07 ; 2 done with the "zone" (same as sprite bne ZoneKernel ; 2³+1 height) IF COMPILE_VERSION = PAL dec objectGraphicIndex ; 5 dec index so inc above resets to 0 dec kernelZonePad ; 5 reduce PAL zone height until done bpl ZoneKernel ; 2³+1 ENDIF sta objectGraphicIndex ; 3 reset object graphic index (a = 0) inx ; 2 cpx #NUM_KERNEL_ZONES ; 2 bcc .playfieldKernelLoop ; 2³+1 sta HMP0 ; 3 = @33 sta WSYNC ;-------------------------------------- lda kernelZoneHorizPos,x ; 4 get player2's fine/coarse value sta HMP1 ; 3 = @07 set player2's fine motion lda kernelZoneHorizPos,x ; 4 waste 4 cycles and #$0F ; 2 mask fine motion tay ; 2 .coarseMovePlayer2 dey ; 2 bpl .coarseMovePlayer2 ; 2³ sta RESP1 ; 3 sta WSYNC ;-------------------------------------- sta HMOVE ; 3 lda #0 ; 2 sta ENAM0 ; 3 = @08 disable the missiles sta ENAM1 ; 3 = @11 lda playerColors+9 ; 3 sta COLUP1 ; 3 = @17 bit gameVariation ; 3 if this game doesn't have the gun bmi Overscan ; 2³ at the bottom then go to Overscan ldy player1GraphicOffset ; 3 ldx player2GraphicOffset ; 3 AntiAircraftKernel sta WSYNC ;-------------------------------------- lda AntiAircraftGuns,y ; 4 read player1's gun graphic sta GRP0 ; 3 = @07 lda AntiAircraftGuns,x ; 4 read player2's gun graphic sta GRP1 ; 3 = @14 sta WSYNC ;-------------------------------------- iny ; 2 inx ; 2 inc currentScanline ; 5 txa ; 2 and #$07 ; 2 see if we're done with the "zone" bne AntiAircraftKernel ; 2³ sta GRP0 ; 3 = @18 clear the player graphics (a = 0) sta GRP1 ; 3 = @21 lda kernelZoneColors+9 ; 3 sta COLUBK ; 3 = @27 Overscan .overscanWait sta WSYNC ; skip 2 scan lines because it's a 2LK sta WSYNC inc currentScanline bne .overscanWait jmp MainLoop GameCalculations ReadConsoleSwitches lda gameState ; get the current game state IF COMPILE_VERSION = NTSC sta SWCHB ; set D4 of SWCHB ENDIF cmp #SYSTEM_POWERUP ; show game selection if the game is beq .showGameSelection ; powering up lda SWCHB ; read the console switches ror ; RESET value now in carry bcs .skipGameReset ; ; start new game ; lda #GAME_RUNNING ; RESET pressed so show the game is in sta gameState ; progress lda #0 sta player1Score ; reset the player scores sta player2Score sta selectDebounce ; reset the select debounce value lda #STARTING_GAME_TIME sta gameTimer ; set the starting game time lda frameCount ; get the current frame count and #$01 ; make the value between 0 and 1 sta frameCount lda #$0F ; set score mask to show player2's score sta scoreMask jmp ResetPlayerPositions .skipGameReset ldy #255-H_KERNEL+1 ; initial scan line to start kernel lda gameTimer and gameState cmp #$F0 bcc .setKernelStartScanline lda frameCount ; get the current frame count and #SCORE_FLASH_DELAY ; see if the score is to drawn (flashing) bne .setKernelStartScanline ldy #YMIN ; ensures score is not drawn .setKernelStartScanline sty startingKernelScanline ; set scan line to start kernel lda frameCount ; get current frame count and #SELECT_DELAY ; the select switch is checked ~ every 60 bne .checkGameSelectSwitch ; frames or ~ every second sta selectDebounce ; reset select debounce flag inc gameTimer ; increment timer (rolls over at 255) bne .checkGameSelectSwitch sta gameState ; if timer rolls over set to game over .checkGameSelectSwitch lda SWCHB ; read the console switches and #SELECT_MASK ; mask to find SELECT value beq .selectSwitchPressed sta selectDebounce ; show SELECT not pressed this frame bne CheckMissileCollisions ; unconditional branch .selectSwitchPressed bit selectDebounce ; if SELECT held then skip SELECT button bmi CheckMissileCollisions ; logic lda #$FF sta selectDebounce ; show the SELECT button is held inc gameSelection ; increment game selection .showGameSelection lda gameSelectionBCD ; store the game selection (BCD) in sta player1Score ; player1's score ldx #0 stx player2Score ; reset player2's score stx scoreMask ; clear the score mask stx gameState ; show that game is over stx frameCount ; reset frame count lda gameSelection ; get the current game selection number cmp #MAX_GAME_SELECTION ; make sure it doesn't go over the max bcc .incrementGameSelection stx player1Score ; clear player1's score stx gameSelection ; wrap game selection around to 0 .incrementGameSelection ldy #$FF ; makes the game selection increment by 1 jsr CalculateScore lda player1Score ; get new game selection sta gameSelectionBCD ; save for next frame ldx #NUM_KERNEL_ZONES-1 lda #OBJECT_DISABLED_STATE | MAX_WAIT_TIME-1 .setObjectIds sta objectIds,x dex bpl .setObjectIds ldx gameSelection lda GameVariationTable,x ; set the game variation based on game sta gameVariation ; selection bmi .polarisOrBomberGameSelection ldy #1 and #SHOOTING_GALLERY|OBSTACLES beq .setObjectShowState lda #OBJECT_DISABLED_STATE bne .setObjectShowState ; unconditional branch .polarisOrBomberGameSelection rol ; shift value left 4 times so rol ; POLARIS_OR_BOMBER, MOVE_LEFT_RIGHT, rol ; and POLARIS_VS_BOMBER are in D2,D1,D0 rol and #$03 ; and value for lookup table tay ; y = 0 or 2 or 3 lda ObjectShowStateTable,y cpx #24 ; set objectShowState if the game type is bcc .setObjectShowState ; not a POLARIS_VS_BOMBER game and #%11100111 .setObjectShowState sta objectShowState lda Player1KernelZoneTable,y ; set the kernel zone for player 1 sta player1KernelZone lda Player2KernelZoneTable,y ; set the kernel zone for player 2 sta player2KernelZone ResetPlayerPositions lda #HMOVE_R2|2 sta player1FineCoarsePos ; sets player to pixel 98 sta RESMP0 ; lock missiles to players sta RESMP1 ; and disable them lda #HMOVE_0|8 sta player2FineCoarsePos ; sets player to pixel 147 lda #PLAYER1_STARTING_X sta player1HorizPos lda #PLAYER2_STARTING_X sta player2HorizPos bne DoneCollisionCheck ; unconditional branch CheckMissileCollisions ldx #1 .checkPlayerCollisionLoop lda CXM0P,x ; read missile collisions for this frame and MissileCollisionMask,x beq .checkNextPlayer ; if no collision then check next player ldy #-1 .checkZoneCollisions iny cpy #NUM_KERNEL_ZONES-1 bcs .checkNextPlayer lda missileCollisions+1,y ; get missile collision zone value and MissileCollisionZoneTable,x beq .checkZoneCollisions ; if none here then check next zone jsr CalculateScore ; collision found -- increment score .checkNextPlayer dex bpl .checkPlayerCollisionLoop DoneCollisionCheck sta CXCLR ; clear all collisions lda gameVariation ; get the game variation and #SINGLEPLAYER ; mask value to get SINGLEPLAYER flag tay lda #$F0 ; assume this is a one player game cpy #0 ; if one player game then branch to bne .shiftP1JoystickValues ; shift the joystick values lda SWCHA ; read the player joystick values and #$F0 ; mask out player 2's values .shiftP1JoystickValues lsr ; shift the value to the lower nybble lsr lsr lsr sta player1JoystickValue ; store player1's joystick value lda SWCHA ; read the joystick port and #$0F ; mask out player 1's values sta player2JoystickValue ; store player2's joystick value lda frameCount ; get the current frame count and #$01 ; make the value between 0 and 1 tax IF COMPILE_VERSION = NTSC lda SWCHB ; read the console switch values eor #$FF ; and flip the bits and DifficultySwitchMask,x ; mask to get difficulty values beq .setMissileSize ; set missile size for expert ; (size 1 for NTSC) lda #MSBL_SIZE4 ; make the missiles 4 clocks wide .setMissileSize sta playerDiffState,x sta NUSIZ0,x ; set missile size ELSE ldy #MSBL_SIZE2 ; assume EXPERT setting lda SWCHB ; read the console switch values and DifficultySwitchMask,x bne .setMissileSize ldy #MSBL_SIZE4 ; make the missiles 4 clocks wide .setMissileSize sty playerDiffState,x sty NUSIZ0,x ; set missile size ENDIF lda gameVariation ; get the current game variation bpl DeterminePlayerVelocity ; branch if not a polaris game and #GUIDED_MISSILES ; check for the guided missile option bne DeterminePlayerVelocity ; branch to guided missiles lda missileVertPos,x ; get the missile's vertical position bne .setMissileTrajectory ; skip logic if missile still active DeterminePlayerVelocity lda joystickValues,x ; get the player's joystick value and #MOVE_DOWN|MOVE_UP ; mask all but up and down values tay lda PlayerVelocityTable,y ; get player velocity from look up table sta playerVelocity,x clc adc #VELOCITY_REST*4 tay lda #SHOOTING_GALLERY bit gameVariation bne DeterminePlayerHorizMovement ; branch if not variable movement bvc DetermineAntiAirSpriteOffset ; branch if up down movement txa bne .setPlayerVelocity ; branch if player 1 lda gameVariation ; get the current game variation and #POLARIS_VS_BOMBER bne .setMissileTrajectory ; branch if Polaris vs. Bomber .setPlayerVelocity sty playerVelocity,x DeterminePlayerHorizMovement bit gameVariation bmi .setMissileTrajectory ; branch if Polaris style game lda joystickValues,x ; get the player's joystick value and #MOVE_RIGHT ; get the right motion flag beq .movePlayerRight lda joystickValues,x ; get the player's joystick value and #MOVE_LEFT ; get the left motion flag bne .skipPlayerHorizPos sec lda playerHorizPos,x ; get the player's horizontal position sbc #GUN_PIXEL_MOVEMENT ; reduce by gun pixel movement cmp GunXMinTable,x ; make sure the gun stays within range bcs .setPlayersHorizPos lda GunXMinTable,x ; set the player's position to the minimum bne .setPlayersHorizPos ; gun position (unconditional branch) .movePlayerRight clc lda playerHorizPos,x ; get the player's horizontal position adc #GUN_PIXEL_MOVEMENT ; increment by gun pixel movement cmp GunXMaxTable,x ; make sure the gun stays within range bcc .setPlayersHorizPos lda GunXMaxTable,x ; set the player's position to the maximum ; gun position .setPlayersHorizPos sta playerHorizPos,x jsr CalcXPos ; calculate player's fine/coarse value sta playerFineCoarsePos,x .skipPlayerHorizPos lda gameVariation and #SHOOTING_GALLERY|WATER_OBSTACLES cmp #WATER_OBSTACLES beq .setSubmarineSpriteOffset DetermineAntiAirSpriteOffset lda joystickValues,x ; get the player's joystick value and #MOVE_DOWN|MOVE_UP ; mask all but up and down values asl ; multiply by 8 to get sprite offset asl .setSubmarineSpriteOffset asl sta playerGraphicOffsets,x .setMissileTrajectory lda gameVariation and #GUIDED_MISSILES beq .skipGuidedMissiles lda joystickValues,x ; get the player's joystick value sta missileTrajectory,x ; save in trajectory for guided missiles .skipGuidedMissiles lda missileVertPos,x ; get the missile's vertical position bne .determineToDisableMissile ; branch if not off screen bit gameState ; check the game state bpl .determineIfGameBoardDone ; branch if game not in play lda INPT4+48,x ; read the player's fire button bpl DetermineMissilePosition ; branch if fire button not pressed lda gameVariation ; get the current game variation and #SINGLEPLAYER ; see if this is a two player game beq .determineIfGameBoardDone txa beq DetermineMissilePosition .determineIfGameBoardDone jmp DetermineIfGameBoardDone DetermineMissilePosition lda gameVariation ; get the current game variation and #POLARIS_VS_BOMBER ; mask Polaris vs. Bomber value tay lda #MISSILE_YMAX bit gameVariation bpl .setMissileVerticalPosition ; branch if not Polaris vs. Bomber lda PlayerVertPosTable,x bvc .setMissileVerticalPosition ; branch if cannot move left or right cpx #0 bne .incrementMissileVertPosition cpy #0 bne .setMissileVerticalPosition .incrementMissileVertPosition clc adc #MISSILE_VELOCITY .setMissileVerticalPosition sta missileVertPos,x lda #0 sta RESMP0,x lda joystickValues,x ; get the player's joystick value sta missileTrajectory,x ; save in trajectory lda #$1F sta audioFrequencies,x ; set audio frequency for firing lda #~DISABLE_BM sta missileMask,x ; set to disable missile lda playerHorizPos,x ; get the player's horizontal position sta missileHorizPos,x ; and set the missile's to the same bit gameVariation bvc .determineToDisableMissile ; branch if can't move left and right txa ; move player index to accumulator bne .setMissileMaskToEnable ; branch if player 2 lda gameVariation ; get the current game variation and #POLARIS_VS_BOMBER bne .determineToDisableMissile ; branch if Polaris vs. Bomber game .setMissileMaskToEnable lda #(ENABLE_BM ^ $FE) sta missileMask,x ; set to enable missile .determineToDisableMissile lda playerKernelZones,x ; get the kernel zone for the player bmi CalculateMissileXPos ; branch if not used for the game setting tay ; y holds index to obstacle object lda objectIds,y ; disable missile if the obstacle was bmi DisablePlayerMissile ; shot this frame CalculateMissileXPos lda missileHorizPos,x ; get the missile's horizontal position sta tempMissileHorizPos ; save it for later ldy #2 bit gameVariation bmi .setMissileXPosToPlayerXPos ; branch if this is a polaris style game bvc .setMissileXPosFromTrajectory; branch if can't move left or right lda gameVariation ; get the game variation and #GUIDED_MISSILES tay beq DetermineMissileXPos .setMissileXPosToPlayerXPos lda playerHorizPos,x ; get the player's horizontal position sta tempMissileHorizPos ; save it for later jmp DetermineMissileXPos .setMissileXPosFromTrajectory lda missileTrajectory,x ; get missile trajectory and #MOVE_DOWN|MOVE_UP tay lda MissileAngleTable,y cpx #0 ; if this is not player 1 then the value beq .incrementMissileXPos ; is negated so the missile angle can be clc ; subtracted below eor #$FF adc #1 .incrementMissileXPos clc adc missileHorizPos,x sta tempMissileHorizPos cmp #MISSILE_XMAX bcs DisablePlayerMissile DetermineMissileXPos sec lda missileHorizPos,x ; get the missile horizontal position sbc tempMissileHorizPos ; subtract by the temp position cmp #$F0 | (HMOVE_R4>>4) ; missule x-movement varies from R4 - L5 bcs .setMissileFineMotion cmp #(HMOVE_L5>>4) bcs DisablePlayerMissile .setMissileFineMotion asl ; move fine motion value to upper nybble asl asl asl sta HMM0,x ; set missile fine motion lda tempMissileHorizPos sta missileHorizPos,x lda #POLARIS_VS_BOMBER bit gameVariation bpl .moveMissileUp ; move missile up if not polaris type game bvc .moveMissileDown ; branch if can't move left or right beq .moveMissileUp ; branch if Polaris vs. Bomber txa bne .moveMissileUp ; branch if player 2 .moveMissileDown clc lda MissileVertVelocityTable,y adc missileVertPos,x ; increment the missile vertical position sta missileVertPos,x cmp #YMAX bcs DisablePlayerMissile ; disable missile if out of range bcc DetermineIfGameBoardDone ; unconditional branch .moveMissileUp sec lda missileVertPos,x sbc MissileVertVelocityTable,y sta missileVertPos,x cmp #YMIN bcs DetermineIfGameBoardDone DisablePlayerMissile lda #$02 sta RESMP0,x ; reset missile position and disable lda #0 sta missileVertPos,x ; reset the missile's vertical position lda #$FF ; set frequency high to disable this sta audioFrequencies,x ; frame DetermineIfGameBoardDone jsr NextRandom ; re-seed random number txa bne .skipDeterminingIfBoardDone ; branch if player 2 lda randomSeed ; get the current random number sta tempRandomSeed ; save for later ldy #5 ; max number of objects per board is 6 .checkIfGameBoardDone lda objectIds,y ; get the object id cmp #OBJECT_DISABLED_STATE | EXPLOSION_TIME ror ; rotate CARRY into D7 bpl .setGameBoardDoneState dey ; next objectId bpl .checkIfGameBoardDone .setGameBoardDoneState sta gameBoardDone .skipDeterminingIfBoardDone lda PlayerValueMasks,x ; get D7/D6 mask values sta objectStateMask ; set value of object state mask txa ; move player number to accumulator ora #6 tax ; x = 6 for player 1 x = 7 for player 2 SetObjectStates lda objectAttributes,x ; get the object attributes and #$0F ; mask the direction -- keep size ora player1DiffState ; or with difficulty for size of missile sta objectAttributes,x ; save it in object attributes ror ; shift D0 to carry lda #OBJECT_XMAX_DOUBLE ; assume this is a double size object bcs .setObjectXMax lda #OBJECT_XMAX_SINGLE .setObjectXMax sta objectXMax ldy #1 ; assume player 2 hit player 1 cpx player2KernelZone beq .setPlayerHitKernelZone dey ; check if player 2 hit player 2 cpx player1KernelZone beq .setPlayerHitKernelZone dey ; y = -1 -- show no players were hit .setPlayerHitKernelZone sty playerHitKernelZone lda objectIds,x ; get the object id bmi ObjectShot ; branch if shot jmp CheckToMoveObject ObjectShot cmp #OBJECT_DISABLED_STATE | EXPLOSION_TIME + 1 bcs .skipObjectDisableTimeIncrement inc objectIds,x ; increment the time to not show object .skipObjectDisableTimeIncrement lda #WATER_OBSTACLES bit gameVariation bpl .checkIncrementDisableTime ; branch if not a polaris type game bne .incrementObjectDisableTime ; branch if not a Torpedo game .checkIncrementDisableTime bit gameBoardDone bmi .setMissileSizeForZone ; branch if game board not done cpx #6 bcc .jmpNextObject .incrementObjectDisableTime inc objectIds,x ; increment the time to not show object bpl .setMissileSizeForZone .jmpNextObject jmp .nextObject .setMissileSizeForZone lda player1DiffState sta objectAttributes,x ; set missile size lda gameVariation ; get the current game variation and #OBSTACLES ; branch if this game has obstacles bne CheckToSpawnNewObject lda randomSeed ; get the current random number sta tempRandomSeed ; save it for later use CheckToSpawnNewObject lda objectShowState and objectStateMask bne .disableObject ldy playerHitKernelZone ; get player zone that was hit bmi SpawnNewShootingGalleryObject; branch if no player hit one lda playerVelocity,y and #$40 ; mask all but D6 jmp SetObjectId SpawnNewShootingGalleryObject lda gameVariation ; get the current game variation and #SHOOTING_GALLERY|WATER_OBSTACLES tay cmp #SHOOTING_GALLERY bne SpawnNewWaterObstacleObject lda tempRandomSeed and #$18 ; make value 0,8,16,or 24 (i.e. from clc ; ID_LARGE_JET to ID_HELICOPTER) adc #ID_RABBIT ; add to get shooting gallery objects cmp #ID_AIRCRAFT_CARRIER bcc SetObjectId ; set id if in shooting gallery range bcs .disableObject ; disable object if out of range SpawnNewWaterObstacleObject bit gameVariation bmi .polarisOrBomberGame cpx #6 bcc .polarisOrBomberGame lda #ID_BLIMP bne .determineNewObjectId ; unconditional branch .polarisOrBomberGame lda gameSelection ; get the current game selection cmp #24 ; if less than game 24 then determine bcc SpawnNewObject ; new object to spawn lda #ID_MINE_1 ; spawn a new mine bne SetObjectId SpawnNewObject lda randomSeed ; get the random seed lsr ; shift D0 to carry lda tempRandomSeed and #$18 ; make value 0,8,16,or 24 bcs .determineNewObjectId ; branch if randomSeed is odd .disableObject lda #OBJECT_DISABLED_STATE ; set value to not display object bne SetObjectId ; unconditional branch .determineNewObjectId cpy #0 ; set the object id if this is not a beq SetObjectId ; water obstacle game clc adc #ID_AIRCRAFT_CARRIER SetObjectId sta objectIds,x and #$08 bne .skipNoReflectSet lda #NO_REFLECT|DOUBLE_SIZE ; set to no reflect and double size ora player1DiffState ; or in player 1 missile size sta objectAttributes,x ; set object's attributes .skipNoReflectSet ldy #HMOVE_0|0 lda tempRandomSeed ; get the held random number value and #REFLECT >> 1 ; and the value to determine which side beq .setObjectInitHorizPos ; object should appear asl ora objectAttributes,x ; reflect the object (i.e. travel left) sta objectAttributes,x lda #OBJECT_XMAX_DOUBLE+1 ldy #HMOVE_R6|9 .setObjectInitHorizPos sta zoneHorizPos,x sty kernelZoneHorizPos,x jmp .nextObject CheckToMoveObject ldy playerHitKernelZone ; get the player zone that was hit bmi .skipVelocityLoad ; branch if no player hit lda playerVelocity,y ; get the hit player's velocity .skipVelocityLoad ldy #OBJECT_VELOCITY_MEDIUM ; set the object's velocity -- used below sty objectVelocity ; when calculating object movement and #ID_747 | ID_BLIMP | ID_CLOWN beq .determineObjectMoveTime ; branch if not either object dec objectVelocity ; slow the object down cmp #ID_BLIMP bcc .determineObjectMoveTime lda frameCount ; get the frame count and #$02 bne .nextObject .determineObjectMoveTime lda gameVariation ; get the game variation and #SHOOTING_GALLERY|WATER_OBSTACLES tay cmp #SHOOTING_GALLERY bne MoveObjects lda frameCount ; get the frame count and #$7C bne MoveObjects ; change ~every 2 seconds bit randomSeed bvc MoveObjects lda objectAttributes,x ; get the object's attributes eor #REFLECT ; change it's reflect/direction sta objectAttributes,x MoveObjects lda objectAttributes,x ; get the object's attributes and #REFLECT ; get it's reflect/direction beq .moveObjectRight lda zoneHorizPos,x ; get the object's horizontal position sec sbc objectVelocity ; move the object to the left bcs .setObjectHorizontalPosition lda objectXMax bne .disableTorpedoGameObject ; unconditional branch .moveObjectRight clc lda zoneHorizPos,x ; get the object's horizontal position adc objectVelocity ; move the object to the right cmp objectXMax bcc .setObjectHorizontalPosition lda #0 .disableTorpedoGameObject cpy #WATER_OBSTACLES bne .setObjectHorizontalPosition bit gameVariation bmi .setObjectHorizontalPosition lda #OBJECT_DISABLED_STATE ; Torpedo game--set value to not display sta objectIds,x ; object bne .nextObject ; unconditional branch .setObjectHorizontalPosition sta zoneHorizPos,x jsr CalcXPos sta kernelZoneHorizPos,x ldy playerHitKernelZone bmi .nextObject sta playerFineCoarsePos,y lda zoneHorizPos,x sta playerHorizPos,y .nextObject dex dex bmi DoneGameCalculations lsr objectStateMask ; right shift object state mask value lsr objectStateMask ; for next object iteration jsr NextRandom ; re-seed random number jmp SetObjectStates ; set states of all objects DoneGameCalculations SUBROUTINE ldx #1 .loop stx tempPlayerIndex lda gameVariation ; get the current game variation and #SHOOTING_GALLERY|WATER_OBSTACLES lsr ora tempPlayerIndex tay ; save for audio channel offset lda #0 sta AUDV0,x ; turn off sounds sta scoreGraphics,x ; clear the score graphics lda audioFrequencies,x ; get the frequency value bmi .checkNextAudioFrequency ; check next frequency if negative sta AUDF0,x lda AudioChannelTable,y sta AUDC0,x lda #8 sta AUDV0,x dec audioFrequencies,x .checkNextAudioFrequency lda audioFrequencies+2,x bmi CalculateScoreOffsets eor #$1F sta AUDF0,x lda AudioChannelTable+1,y sta AUDC0,x lda #8 sta AUDV0,x dec audioFrequencies+2,x CalculateScoreOffsets lda playerScores,x ; get the player's score and #$0F ; mask off the upper nybbles sta temp ; save the value for later asl ; shift the value left to multiply by 4 asl clc ; add in original so it's multiplied by 5 adc temp ; [i.e. x * 5 = (x * 4) + x] sta lsbScoreOffsets,x lda playerScores,x and #$F0 ; mask off the lower nybbles lsr ; divide the value by 4 lsr sta temp ; save the value for later lsr ; divide the value by 16 lsr clc ; add in original so it's multiplied by adc temp ; 5/16 [i.e. 5x/16 = (x / 16) + (x / 4)] sta msbScoreOffsets,x dex bpl .loop lda gameState ; get the current game state eor #GAME_RUNNING ; (#$00 = game over, #$FF = game running) and gameTimer sta colorXOR ; save to colorXOR (cycles colors during attract lda #BW_HUE_MASK ; mode) sta hueMask ; set default color hue mask (assume B/W) ldx #NUM_KERNEL_ZONES ldy #NUM_KERNEL_ZONES lda SWCHB ; read the console switch value and #BW_MASK ; get the B/W switch value beq .storePlayerColors lda #COLOR_HUE_MASK sta hueMask ; set hue mask for color setting ldy #NUM_KERNEL_ZONES*2+1 .storePlayerColors lda PlayerColorTable,y eor colorXOR and hueMask sta playerColors,x dey ; reduce table offset index dex bpl .storePlayerColors ldx #NUM_KERNEL_ZONES ldy #NUM_KERNEL_ZONES bit gameVariation bpl .storeKernelZoneColors ldy #NUM_KERNEL_ZONES*2+1 .storeKernelZoneColors lda KernelZoneColorTable,y eor colorXOR and hueMask sta kernelZoneColors,x dey ; reduce table offset index dex bpl .storeKernelZoneColors sta COLUBK lda frameCount and #$04 ; explosion updated every 4th frame asl ; now a = 0 or 8 (sprite height) ora #ExplosionSprites-GameSprites sta explosionSpriteOffset rts GameVariationTable ; ** Anti-Aircraft ** .byte OBSTACLES .byte OBSTACLES|GUIDED_MISSILES .byte SINGLEPLAYER|OBSTACLES .byte 0 .byte GUIDED_MISSILES .byte SINGLEPLAYER ; ** Torpedo ** .byte MOVE_LEFT_RIGHT|WATER_OBSTACLES|OBSTACLES .byte MOVE_LEFT_RIGHT|WATER_OBSTACLES|OBSTACLES|GUIDED_MISSILES .byte MOVE_LEFT_RIGHT|SINGLEPLAYER|WATER_OBSTACLES|OBSTACLES .byte MOVE_LEFT_RIGHT|WATER_OBSTACLES .byte MOVE_LEFT_RIGHT|WATER_OBSTACLES|GUIDED_MISSILES .byte MOVE_LEFT_RIGHT|SINGLEPLAYER|WATER_OBSTACLES ; ** Shooting Gallery .byte SHOOTING_GALLERY .byte SHOOTING_GALLERY|GUIDED_MISSILES .byte SINGLEPLAYER|SHOOTING_GALLERY ; ** Polaris ** .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT|GUIDED_MISSILES .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT|SINGLEPLAYER ; ** Bomber ** .byte POLARIS_OR_BOMBER|WATER_OBSTACLES .byte POLARIS_OR_BOMBER|WATER_OBSTACLES|GUIDED_MISSILES .byte POLARIS_OR_BOMBER|SINGLEPLAYER|WATER_OBSTACLES ; ** Polaris vs. Bomber ** .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT|POLARIS_VS_BOMBER|WATER_OBSTACLES .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT|POLARIS_VS_BOMBER|WATER_OBSTACLES|GUIDED_MISSILES .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT|POLARIS_VS_BOMBER|SINGLEPLAYER|WATER_OBSTACLES .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT|POLARIS_VS_BOMBER|WATER_OBSTACLES .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT|POLARIS_VS_BOMBER|WATER_OBSTACLES|GUIDED_MISSILES .byte POLARIS_OR_BOMBER|MOVE_LEFT_RIGHT|POLARIS_VS_BOMBER|SINGLEPLAYER|WATER_OBSTACLES ObjectShowStateTable .byte %00001100,%00000000,%00110000,%01111110 Player1KernelZoneTable .byte 0, -1, 6, 0 Player2KernelZoneTable .byte 1, -1, 7, 7 PlayerVertPosTable .byte YPOS_PLAYER1, YPOS_PLAYER2 KernelZoneColorTable .byte LIGHT_BLUE,LIGHT_BLUE+2,LIGHT_BLUE+2,LIGHT_BLUE+4,LIGHT_BLUE+6 .byte LIGHT_BLUE+8,LIGHT_BLUE+10,LIGHT_BLUE+12,LIGHT_BLUE+14,BROWN+8 .byte LIGHT_BLUE+2,LIGHT_BLUE+2,LIGHT_BLUE+2,LIGHT_BLUE+2,LIGHT_BLUE+6 .byte LIGHT_BLUE+8,LIGHT_BLUE+10,LIGHT_BLUE+12,LIGHT_BLUE+14,BROWN+8 PlayerColorTable .blackAndWhite .byte BLACK+12,BLACK+6,BLACK+10,BLACK+12,WHITE .byte BLACK,BLACK+6,BLACK,BLACK+8,BLACK+6 .color .byte GREEN_BLUE+8,RED+8,YELLOW+6,GREEN+12,BLUE+12 .byte PURPLE+6,GREEN_BLUE+6,RED+8,GREEN_BLUE+8,RED+8 PlayerVelocityTable .byte VELOCITY_FAST ; value not used .byte VELOCITY_FAST ; joystick pushed up .byte VELOCITY_SLOW ; joystick pulled back .byte VELOCITY_REST ; joystick at rest ScoreTable .byte LARGE_JET_SCORE,SMALL_JET_SCORE,_747_SCORE,HELICOPTER_SCORE .byte BLIMP_SCORE,RABBIT_SCORE,CLOWN_SCORE,DUCK_SCORE .byte AIRCRAFT_CARRIER_SCORE,PT_BOAT_SCORE,FREIGHTER_SCORE .byte PIRATE_SHIP_SCORE,MINE_SCORE,MINE_SCORE NumberFonts zero .byte $0E ; |....XXX.| .byte $0A ; |....X.X.| .byte $0A ; |....X.X.| .byte $0A ; |....X.X.| .byte $0E ; |....XXX.| one .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| two .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX.XXX.| three .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $66 ; |.XX..XX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| four .byte $AA ; |X.X.X.X.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| five .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| six .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| seven .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| eight .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| nine .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| GameSprites LargeJet .byte $00 ; |........| .byte $80 ; |X.......| .byte $86 ; |X....XX.| .byte $FF ; |XXXXXXXX| .byte $FF ; |XXXXXXXX| .byte $38 ; |..XXX...| .byte $30 ; |..XX....| .byte $00 ; |........| SmallJet .byte $00 ; |........| .byte $BE ; |X.XXXXX.| .byte $88 ; |X...X...| .byte $FF ; |XXXXXXXX| .byte $FF ; |XXXXXXXX| .byte $08 ; |....X...| .byte $3E ; |..XXXXX.| .byte $00 ; |........| _747 .byte $00 ; |........| .byte $80 ; |X.......| .byte $C0 ; |XX......| .byte $FE ; |XXXXXXX.| .byte $0F ; |....XXXX| .byte $18 ; |...XX...| .byte $30 ; |..XX....| .byte $00 ; |........| Helicopter .byte $1F ; |...XXXXX| .byte $84 ; |X....X..| .byte $CF ; |XX..XXXX| .byte $7D ; |.XXXXX.X| .byte $0D ; |....XX.X| .byte $0F ; |....XXXX| .byte $00 ; |........| .byte $00 ; |........| ObservationBlimp .byte $7E ; |.XXXXXX.| .byte $C3 ; |XX....XX| .byte $DB ; |XX.XX.XX| .byte $C3 ; |XX....XX| .byte $DB ; |XX.XX.XX| .byte $7E ; |.XXXXXX.| .byte $18 ; |...XX...| .byte $00 ; |........| Rabbit .byte $00 ; |........| .byte $00 ; |........| .byte $08 ; |....X...| .byte $44 ; |.X...X..| .byte $3A ; |..XXX.X.| .byte $7C ; |.XXXXX..| .byte $46 ; |.X...XX.| .byte $00 ; |........| Clown .byte $7E ; |.XXXXXX.| .byte $DB ; |XX.XX.XX| .byte $FF ; |XXXXXXXX| .byte $E7 ; |XXX..XXX| .byte $BD ; |X.XXXX.X| .byte $81 ; |X......X| .byte $FF ; |XXXXXXXX| .byte $00 ; |........| Duck .byte $00 ; |........| .byte $00 ; |........| .byte $0C ; |....XX..| .byte $0B ; |....X.XX| .byte $44 ; |.X...X..| .byte $FE ; |XXXXXXX.| .byte $7E ; |.XXXXXX.| .byte $00 ; |........| AircraftCarrier .byte $00 ; |........| .byte $10 ; |...X....| .byte $38 ; |..XXX...| .byte $FF ; |XXXXXXXX| .byte $FE ; |XXXXXXX.| .byte $7E ; |.XXXXXX.| .byte $00 ; |........| .byte $00 ; |........| PTBoat .byte $00 ; |........| .byte $10 ; |...X....| .byte $54 ; |.X.X.X..| .byte $7F ; |.XXXXXXX| .byte $FE ; |XXXXXXX.| .byte $FC ; |XXXXXX..| .byte $3C ; |..XXXX..| .byte $00 ; |........| Freighter .byte $10 ; |...X....| .byte $10 ; |...X....| .byte $36 ; |..XX.XX.| .byte $FF ; |XXXXXXXX| .byte $7E ; |.XXXXXX.| .byte $3C ; |..XXXX..| .byte $00 ; |........| .byte $00 ; |........| PirateShip .byte $28 ; |..X.X...| .byte $28 ; |..X.X...| .byte $28 ; |..X.X...| .byte $AB ; |X.X.X.XX| .byte $FF ; |XXXXXXXX| .byte $7E ; |.XXXXXX.| .byte $7C ; |.XXXXX..| .byte $00 ; |........| Mine_0 .byte $2A ; |..X.X.X.| .byte $1C ; |...XXX..| .byte $1C ; |...XXX..| .byte $2A ; |..X.X.X.| .byte $08 ; |....X...| .byte $30 ; |..XX....| .byte $C0 ; |XX......| .byte $00 ; |........| Mine_1 .byte $18 ; |...XX...| .byte $5A ; |.X.XX.X.| .byte $3C ; |..XXXX..| .byte $FF ; |XXXXXXXX| .byte $3C ; |..XXXX..| .byte $5A ; |.X.XX.X.| .byte $18 ; |...XX...| .byte $00 ; |........| ExplosionSprites Explosion_0 .byte $18 ; |...XX...| .byte $24 ; |..X..X..| .byte $42 ; |.X....X.| .byte $81 ; |X......X| .byte $42 ; |.X....X.| .byte $24 ; |..X..X..| .byte $18 ; |...XX...| .byte $00 ; |........| Explosion_1 .byte $42 ; |.X....X.| .byte $81 ; |X......X| .byte $99 ; |X..XX..X| .byte $24 ; |..X..X..| .byte $99 ; |X..XX..X| .byte $81 ; |X......X| .byte $42 ; |.X....X.| .byte $00 ; |........| AudioChannelTable .byte $08,$09,$04,$0C,$03,$01,$08 AntiAircraftGuns _60Degrees_0 .byte $1C ; |...XXX..| .byte $1C ; |...XXX..| .byte $38 ; |..XXX...| .byte $38 ; |..XXX...| .byte $70 ; |.XXX....| .byte $70 ; |.XXX....| .byte $FF ; |XXXXXXXX| .byte $FF ; |XXXXXXXX| _90Degress .byte $70 ; |.XXX....| .byte $70 ; |.XXX....| .byte $70 ; |.XXX....| .byte $70 ; |.XXX....| .byte $70 ; |.XXX....| .byte $70 ; |.XXX....| .byte $FF ; |XXXXXXXX| .byte $FF ; |XXXXXXXX| _30Degress .byte $00 ; |........| .byte $01 ; |.......X| .byte $07 ; |.....XXX| .byte $1E ; |...XXXX.| .byte $3C ; |..XXXX..| .byte $70 ; |.XXX....| .byte $FF ; |XXXXXXXX| .byte $FF ; |XXXXXXXX| _60Degress_1 .byte $1C ; |...XXX..| .byte $1C ; |...XXX..| .byte $38 ; |..XXX...| .byte $38 ; |..XXX...| .byte $70 ; |.XXX....| .byte $70 ; |.XXX....| .byte $FF ; |XXXXXXXX| .byte $FF ; |XXXXXXXX| MissileAngleTable .byte MISSILE_HORZ_VELOCITY_60 ; value not used .byte MISSILE_HORZ_VELOCITY_90 ; joystick pushed back .byte MISSILE_HORZ_VELOCITY_30 ; joystick pulled up .byte MISSILE_HORZ_VELOCITY_60 ; joystick at rest MissileVertVelocityTable .byte MISSILE_VERT_VELOCITY_60 ; value not used .byte MISSILE_VERT_VELOCITY_90 ; joystick pushed back .byte MISSILE_VERT_VELOCITY_30 ; joystick pushed forward .byte MISSILE_VERT_VELOCITY_60 ; joystick at rest MissileCollisionZoneTable .byte %00010000, %10000000 DifficultySwitchMask MissileCollisionMask PlayerValueMasks .byte $40,$80 GunXMinTable .byte PLAYER1_GUN_XMIN,PLAYER2_GUN_XMIN GunXMaxTable .byte PLAYER1_GUN_XMAX,PLAYER2_GUN_XMAX ;============================================================================ ; R O M - C O D E (Part 2) ;============================================================================ CalcXPos sta temp ; save off the x position bpl .determineCoarseValue ; this instruction isn't really needed cmp #XMAX ; make sure object not out of range bcc .determineCoarseValue ; if not compute coarse value lda #0 sta temp ; set to min value .determineCoarseValue lsr ; shift top nybble to lower nybble lsr lsr lsr tay ; save the value lda temp ; get the object's x position and #$0F ; mask upper nybble sty temp ; save coarse value for later clc adc temp ; add in coarse value (A = C + F) cmp #15 bcc .skipSubtractions sbc #15 ; subtract 15 iny ; and increment coarse value .skipSubtractions cmp #XMIN ; make sure hasn't gone pass min x value eor #$0F bcs .skipFineIncrement adc #1 ; increment fine motion value dey ; reduce coarse value .skipFineIncrement iny ; increment coarse value asl ; move fine motion value to upper nybble asl asl asl sta temp ; save it for later tya ; move coarse value to accumulator ora temp ; accumualtor holds fine/coarse value rts ; This is Larry Kaplan's typical LFSR psuedo-random number generator. It was ; derived from an article by Don Lancaster that appeared in BYTE magazine. ; Since this is an 8-bit LFSR the numbers will start to repeat after 255 ; iterations. ; Notice that the first time this routine is called the randomSeed is 0. This ; will cause a tap of D6 which will cause the initial value to be 64d. ; see... ; http://www.ciphersbyritter.com/GLOSSARY.HTM#LinearFeedbackShiftRegister ; http://www.cypressmicro.com/products/umselector/StdUM/prs8/PRS8.htm NextRandom lsr randomSeed rol eor randomSeed lsr lda randomSeed bcs .skipTap ora #%01000000 sta randomSeed .skipTap rts CalculateScore SUBROUTINE sty tempKernelZone ; save off zone where collision happened ldy #2 ; offset for 1 point lda tempKernelZone bmi .incrementScore cmp playerKernelZones,x ; leave routine if player was hit -- no beq .leaveRoutine ; score for shooting yourself :-) txa ; move player number to accumulator eor #1 ; XOR the value to check the next tay ; player lda tempKernelZone cmp playerKernelZones,y ; if a player was not hit then bne .determinePointValue ; determine point value lda gameVariation ; leave the routine (no points) if this and #POLARIS_VS_BOMBER ; is not a POLARIS_VS_BOMBER game beq .leaveRoutine ldy #2 ; offset for 1 point bne .incrementScore ; unconditional branch .determinePointValue ldy tempKernelZone lda objectIds,y bmi .leaveRoutine lsr ; divide the value by 8 to get point lsr ; value offset lsr tay ; move point value offset to y lda gameVariation ; get the current game variation and #OBSTACLES ; see if obstacles are present ; (i.e. every item is one point) beq .incrementScore tay ; move to y so score increments by 1 .incrementScore lda ScoreTable,y ; read the point value from table sed ; set to decimal mode clc adc playerScores,x ; increment player's score sta playerScores,x cld ; clear decimal mode bcs .maxScoreReached ; end game if score over 99 cmp #MAX_SCORE ; compare to the max score to see if game bne .setAudioFrequency ; should end .maxScoreReached lda #0 sta gameState ; show the game is over sta gameTimer ; reset the game timer sta frameCount ; reset the frame count lda #MAX_SCORE ; set the player's score to the maximum sta playerScores,x .setAudioFrequency ldy tempKernelZone lda #$1F sta audioFrequencies+2,x cpy #8 bcs .resetPlayerMissile lda #OBJECT_HIT_STATE | EXPLOSION_TIME sta objectIds,y .resetPlayerMissile lda #0 sta missileVertPos,x ; reset the missile's vertical position lda #2 sta RESMP0,x ; lock missile to player and disable .leaveRoutine ldy tempKernelZone ; restore y register rts IF COMPILE_VERSION = NTSC .org ROMTOP+2048-6, 106 ; 2K ROM .word Start ; NMI vector .word Start ; RESET vector .word Start ; BRK vector ELSE .org ROMTOP+2048-4, 234 ; 2K ROM .word Start ; RESET vector .word Start ; BRK vector ENDIF