.include "consts.inc" .include "header.inc" .include "actor.inc" .include "reset.inc" .include "utils.inc" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Variables declared in RAM zero-page ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .segment "ZEROPAGE" Score: .res 4 ; 4 bytes for 1s, 10s, 100s, 1000s, For score Collision: .res 1 ; 0 or 1, if 0 no collision, if 1, collisison Buttons: .res 1 ; Pressed buttons (A|B|Sel|Start|Up|Dwn|Lft|Rgt) PrevButtons: .res 1 ; Stores the previous buttons from the last frame XPos: .res 1 ; Player X 16-bit position (8.8 fixed-point): hi+lo/256px YPos: .res 1 ; Player Y 16-bit position (8.8 fixed-point): hi+lo/256px XVel: .res 1 ; Player X (signed) velocity (in pixels per 256 frames) YVel: .res 1 ; Player Y (signed) velocity (in pixels per 256 frames) PrevSubmarine: .res 1 ; Stores the value in seconds that the last submarine was added PrevAirplane: .res 1 ; Stores the value in seconds that the last airplane was added Frame: .res 1 ; Counts frames (0 to 255 and repeats) IsDrawComplete: .res 1 ; Flag to indicate when VBlank is done drawing Clock60: .res 1 ; Counter that increments per second (60 frames) BgPtr: .res 2 ; Pointer to background address - 16bits (lo,hi) SprPtr: .res 2 ; Pointer to the sprite address - 16bits (lo,hi) BufPtr: .res 2 ; Pointer to the Buffer Address - 16bite (lo,hi) XScroll: .res 1 ; Store the horizontal scroll position CurrNametable: .res 1 ; Store the current starting nametable (0 or 1) Column: .res 1 ; Stores the column (of tiles) we are in the level NewColAddr: .res 2 ; The destination address of the new column in PPU SourceAddr: .res 2 ; The source address in ROM of the new column tiles ParamType: .res 1 ; Used as parameter to subroutine ParamXPos: .res 1 ; Used as parameter to subroutine ParamYPos: .res 1 ; Used as parameter to subroutine ParamTileNum: .res 1 ; Used as parameter to subroutine ParamNumTiles: .res 1 ; Used as parameter to subroutine ParamAttribs: .res 1 ; Used as parameter to subroutine ParamRectX1: .res 1 ; Used as parameter to subroutine ParamRectY1: .res 1 ; Used as parameter to subroutine ParamRectX2: .res 1 ; Used as parameter to subroutine ParamRectY2: .res 1 ; Used as parameter to subroutine PrevOAMCount: .res 1 ; Store the previous number of bytes that were sent to the OAM Seed: .res 2 ; Initialize the 16-bit seed to any value except 0 ActorsArray: .res MAX_ACTORS * .sizeof(Actor) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PRG-ROM code located at $8000 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .segment "CODE" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Routine to read controller state and store it inside "Buttons" in RAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc ReadControllers lda #1 ; A = 1 sta Buttons ; Buttons = 1 sta JOYPAD1 ; Set Latch=1 to begin 'Input'/collection mode lsr ; A = 0 sta JOYPAD1 ; Set Latch=0 to begin 'Output' mode LoopButtons: lda JOYPAD1 ; This reads a bit from the controller data line and inverts its value, ; And also sends a signal to the Clock line to shift the bits lsr ; We shift-right to place that 1-bit we just read into the Carry flag rol Buttons ; Rotate bits left, placing the Carry value into the 1st bit of 'Buttons' in RAM bcc LoopButtons ; Loop until Carry is set (from that initial 1 we loaded inside Buttons) rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Increment the score, simulating BCD mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc IncrementScore ; LittleEndian Increment1stDigit: lda Score+0 clc adc #1 sta Score+0 cmp #$A bne DoneIncrementing Increment10sDigit: lda #0 sta Score+0 lda Score+1 clc adc #1 sta Score+1 cmp #$A bne DoneIncrementing Increment100sDigit: lda #0 sta Score+1 lda Score+2 clc adc #1 sta Score+2 cmp #$A bne DoneIncrementing Increment1000sDigit: lda #0 sta Score+2 lda Score+3 clc adc #1 sta Score+3 cmp #$A bne DoneIncrementing DoneIncrementing: rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Draw the score in ther nametable/background using buffering ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Buffer format starting at Ram Address $7000 ;; 03 20 52 00 00 02 01 20 78 00 00 ;; | \___/ \______/ | \___/ | | ;; | | | | | | | ;; | | | | | | Length=0 (End of buffering) ;; | | | | | byte to copy ;; | | | | PPU Address $2078 ;; | | | Length=1 ;; | | bytes to copy ;; | PPU address $2052 ;; Length = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc DrawScore lda #$70 sta BufPtr+1 lda #$00 sta BufPtr+0 ldy #0 lda #3 ; send 3 bytes sta (BufPtr),y iny lda #$20 sta (BufPtr),y ; hi byte iny lda #$52 sta (BufPtr),y ; lo byte iny ;; Send the 3 digits of the score from MSB to LSB ;; Offset by #$60 to point to numbers lda Score+2 ; 100s clc adc #$60 sta (BufPtr),y iny lda Score+1 ; 10s clc adc #$60 sta (BufPtr),y iny lda Score+0 ; 1s clc adc #$60 sta (BufPtr),y iny lda #0 sta (BufPtr),y ; Length=0, to signal the end of the buffer iny rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Returns a random 8-bit number inside A (0-255), clobbers Y (0). ;; Requires a 2-byte value on the Zero page called "Seed" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; This is a 16-bit Galois Linerar Feedback Shift register with polynomial $0039 ;; The sequence of numbers it generates will repeat after 65535 calls. ;; Execution time is an average of 125 cycles (excluding jsr and rts) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc GetRandomNumber ldy #8 ; Loop counter (generates 8 bits) lda Seed+0 : asl ; Shift the register rol Seed+1 bcc :+ eor #$39 ; Apply XOR feedback when a 1 bit is shifted out : dey bne :-- sta Seed+0 ; Saves the value in A into the Seed cmp #0 ; Set flags rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to load all 32 color palette values from ROM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc LoadPalette PPU_SETADDR $3F00 ldy #0 ; Y = 0 : lda PaletteData,y ; Lookup byte in ROM sta PPU_DATA ; Set value to send to PPU_DATA iny ; Y++ cpy #32 ; Is Y equal to 32? bne :- ; Not yet, keep looping rts ; Return from subroutine .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Routine to draw a new column of tiles off-screen every 8 pixels ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc DrawNewColumn lda XScroll ; We'll set the NewColAddr lo-byte and hi-byte lsr lsr lsr ; Shift right 3 times to divide XScroll by 8 sta NewColAddr ; Set the lo-byte of the column address lda CurrNametable ; The hi-byte comes from the nametable eor #1 ; Invert the low bit (0 or 1) asl asl ; Multiply by 4 (A is $00 or $04) clc adc #$20 ; Add $20 (A is $20 or $24) for nametabe 0 or 1 sta NewColAddr+1 ; Set the hi-byte of the column address ($20xx or $24xx) lda Column ; Multiply (col * 32) to compute the data offset asl asl asl asl asl sta SourceAddr ; Store lo-byte (--XX) of column source address lda Column lsr lsr lsr ; Divide current Column by 8 (using 3 shift rights) sta SourceAddr+1 ; Store hi-byte (XX--) of column source addres ; Here we'll add the offset the column source address with the address of where the BackgroundData lda SourceAddr ; Lo-byte of the column data start + offset = address to load column data from clc adc #BackgroundData ; Add the hi-byte sta SourceAddr+1 ; Add the result of the offset back to the source address hi-byte DrawColumn: lda #%00000100 sta PPU_CTRL ; Tell the PPU that the increments will be +32 mode lda PPU_STATUS ; Hit PPU_STATUS to reset hi/lo address latch lda NewColAddr+1 sta PPU_ADDR ; Set the hi-byte of the new column start address lda NewColAddr sta PPU_ADDR ; Set the lo-byte of the new column start address ldx #30 ; We'll loop 30 times (=30 rows) ldy #0 DrawColumnLoop: lda (SourceAddr),y ; Copy from the address of the column source + y offset sta PPU_DATA iny ; Y++ dex ; X-- bne DrawColumnLoop ; Loop 30 times to draw all 30 rows of this column rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Routine to draw attributes off-screen every 32 pixels ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc DrawNewAttribs lda CurrNametable eor #1 ; Invert low bit (0 or 1) asl ; Multiuply by 2, ($00 or $02) asl ; Multiply by 2 again ($00 or $04) clc adc #$23 ; Add high byte of attribute base address ($23-- or $27--) sta NewColAddr+1 ; The hi-byte now has address = $23 or $27 for nametable 0 or 1 lda XScroll lsr lsr lsr lsr lsr ; Divide by 32 (shift right 5 times) clc adc #$C0 sta NewColAddr ; The lo-byte contains (attribute base + XScroll/32) lda Column ; (Column/4) * 8, since each row of attribute data in ROM is 8 bytes and #%11111100 ; Mask the lowest two bits to get the closest lowest multiple of 4 asl ; One shift left equivelant to a multiplication by 2 sta SourceAddr ; Stores the lo-byte of the source attribute address offset (in ROM) lda Column ; Proceed to compute the hi-byte of the source address offset in ROM lsr ; /2 lsr ; /4 lsr ; /8 lsr ; /16 lsr ; /32 lsr ; /64 lsr ; /128, shift right 7 times to divide by 128 sta SourceAddr+1 ; Stores the hi-byte of the Source address offset lda SourceAddr clc adc #AttributeData ; Add the hi-byte of the base address where AttributeData is in ROM sta SourceAddr+1 ; Stores the result of the add back into the hi-byte of the SourceAddr DrawAttribute: bit PPU_STATUS ; Hit PPU_STATUS to reset the high/low address latch ldy #0 ; Y = 0 DrawAttribLoop: lda NewColAddr+1 sta PPU_ADDR ; Write the hi-byte of attribute PPU destination address lda NewColAddr sta PPU_ADDR ; Write the lo-byte of attribute PPU destination address lda (SourceAddr),y ; Fetch attribute byte from ROM sta PPU_DATA ; Stores new attribute data into the PPU memory iny ; Y++ cpy #8 beq :+ ; Loop 8 times (to copy 8 attribute bytes) lda NewColAddr clc adc #8 sta NewColAddr ; Next attribute will be at (NewColAddr + 8) jmp DrawAttribLoop : rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to add new actor to the array in the first empty slot found ;; Params = ParamType, ParamXPos, ParamYPos ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc AddNewActor ldx #0 ; X = 0 ArrayLoop: cpx #MAX_ACTORS * .sizeof(Actor) ; Reached maximum number of actors allowed in the array? beq EndRoutine ; Then we skip and don't add a new actor lda ActorsArray+Actor::Type,x cmp #ActorType::NULL ; If the actor type of this array position is NULL beq AddNewActorToArray ; Then: we found an empty slot, proceed to add actor to position [x] NextActor: txa clc adc #.sizeof(Actor) ; Otherwise, we offset to the check the next actor in the array tax ; X += sizeof(Actor) jmp ArrayLoop AddNewActorToArray: ; Here we add a new actor at index [x] of the array lda ParamType ; Fetch parameter "actor type" from RAM sta ActorsArray+Actor::Type,x lda ParamXPos ; Fetch parameter "actor position X" from RAM sta ActorsArray+Actor::XPos,x lda ParamYPos ; Fetch parameter "actor position Y" from RAM sta ActorsArray+Actor::YPos,x EndRoutine: rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to spawn actors when certain conditions are met ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc SpawnActors SpawnSubmarine: lda Clock60 ; Submarines are added in intervals of 3 seconds sec sbc PrevSubmarine cmp #3 ; Only add a new submarine if the difference in time from the previous one is equal to 3 bne :+ lda #ActorType::SUBMARINE sta ParamType ; Load parameter for the actor type lda #223 sta ParamXPos ; Load parameter for actor position X jsr GetRandomNumber ; load random number into A lsr ; Divide by 8 (right shift thrice) lsr lsr clc adc #180 ; add 180 to lower it sta ParamYPos jsr AddNewActor ; Call the subroutine to add the new missile actor lda Clock60 sta PrevSubmarine ; Save the current Clock60 time as the submarine last spawn time : SpawnAirplane: lda Clock60 ; Submarines are added in intervals of 3 seconds sec sbc PrevAirplane cmp #2 ; Only add a new airplane if the difference in time from the previous one is equal to 2 bne :+ lda #ActorType::AIRPLANE sta ParamType ; Load parameter for the actor type lda #235 sta ParamXPos ; Load parameter for actor position X jsr GetRandomNumber ; load random number into A lsr ; Divide by 4 (right shift twice) lsr clc adc #35 ; add 35 to lower it, so its not in the status bar ;Adjust the random Y to be between top and bottom sta ParamYPos jsr AddNewActor ; Call the subroutine to add the new missile actor lda Clock60 sta PrevAirplane ; Save the current Clock60 time as the submarine last spawn time : rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to check collisions ;; PARAMS: ParamXPos, ParamYPos (x and y of missile) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc CheckEnemyCollision txa pha ; Push and save the X register in the stack ldx #0 stx Collision ; Collision = 0 EnemiesCollisionLoop: cpx #MAX_ACTORS * .sizeof(Actor) beq FinishCollisionCheck lda ActorsArray+Actor::Type,x cmp #ActorType::AIRPLANE bne NextEnemy ; If not an airplane - skip to next enemy type lda ActorsArray+Actor::Type,x ; Load the type of the actor we are looping cmp #ActorType::AIRPLANE bne NextEnemy ; If it's NOT Airplane, bypass this enemy and move check the next one ;; LOAD BOUNDING BOX X1, Y1, X2, and Y2 lda ActorsArray+Actor::XPos,x ; Bounding Box X1 sta ParamRectX1 lda ActorsArray+Actor::YPos,x ; Bouding Box Y1 sta ParamRectY1 lda ActorsArray+Actor::XPos,x clc adc #22 ; Get right value of the airplane bounding box by adding 22 pixels to the right sta ParamRectX2 ; Bounding Box X2 lda ActorsArray+Actor::YPos,x clc adc #8 ; Get the bottom of the airplane bounding box by adding 8 sta ParamRectY2 ; Bouding Box Y2 jsr IsPointInsideBoundingBox lda Collision beq NextEnemy lda #ActorType::NULL ; Set actor to null - TODO: maybe make an explosion sta ActorsArray+Actor::Type,x jmp FinishCollisionCheck NextEnemy: txa clc adc #.sizeof(Actor) tax jmp EnemiesCollisionLoop FinishCollisionCheck: pla tax rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to check if a point is inside a bounding box. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Params: ;; (ParamXPos, ParamYPos) - coords to be tested ;; (ParamRectX1,ParamRectY1,ParamRectX2,ParamRectY2) Are rectangle coords) ;; Output: ;; Collision flag is either 1 or 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc IsPointInsideBoundingBox lda ParamXPos cmp ParamRectX1 bcc PointIsOutside lda ParamYPos cmp ParamRectY1 bcc PointIsOutside lda ParamXPos cmp ParamRectX2 bcs PointIsOutside lda ParamYPos cmp ParamRectY2 bcs PointIsOutside PointIsInside: lda #1 sta Collision jmp EndCollisionCheck PointIsOutside: lda #0 sta Collision EndCollisionCheck: rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to loop all actors and update them (position, velocity, etc.) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc UpdateActors ldx #0 ActorsLoop: lda ActorsArray+Actor::Type,x cmp #ActorType::MISSILE bne :+ lda ActorsArray+Actor::YPos,x sec sbc #1 ; Decrement Y position of missiles by 1 sta ActorsArray+Actor::YPos,x bcs SkipMissile lda #ActorType::NULL sta ActorsArray+Actor::Type,x ; Remove the missile from the array SkipMissile: CheckCollision: lda ActorsArray+Actor::XPos,x clc adc #3 sta ParamXPos ; Missile position to be checked X += 3 lda ActorsArray+Actor::YPos,x clc adc #1 sta ParamYPos ; Missile position to be checked Y += 1 jsr CheckEnemyCollision ; Perform collision check between the missile and other enemy actor lda Collision beq NoCollisionFound lda #ActorType::NULL ; Delete missile Actor sta ActorsArray+Actor::Type,x jsr IncrementScore jsr DrawScore NoCollisionFound: jmp NextActor : cmp #ActorType::SUBMARINE bne :+ lda ActorsArray+Actor::XPos,x sec sbc #1 ; Decrement Y position of submarine by 1 sta ActorsArray+Actor::XPos,x bcs SkipSubmarine lda #ActorType::NULL sta ActorsArray+Actor::Type,x ; Remove the submarine from the array SkipSubmarine: jmp NextActor : cmp #ActorType::AIRPLANE bne :+ lda ActorsArray+Actor::XPos,x sec sbc #1 ; Decrement Y position of submarine by 1 sta ActorsArray+Actor::XPos,x bcs SkipAirplane lda #ActorType::NULL sta ActorsArray+Actor::Type,x ; Remove the submarine from the array SkipAirplane: jmp NextActor : NextActor: txa clc adc #.sizeof(Actor) tax cmp #MAX_ACTORS * .sizeof(Actor) bne ActorsLoop rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to loop all actors and send their tiles to the OAM-RAM at $200 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc RenderActors lda #$02 sta SprPtr+1 lda #$00 sta SprPtr ; Point SprPtr to $0200 ldy #0 ; Count how many tiles we are sending ldx #0 ; Counts how many actors we are looping ActorsLoop: lda ActorsArray+Actor::Type,x cmp #ActorType::SPRITE0 bne :+ lda ActorsArray+Actor::XPos,x sta ParamXPos lda ActorsArray+Actor::YPos,x sta ParamYPos lda #$70 sta ParamTileNum lda #%00100000 sta ParamAttribs lda #1 sta ParamNumTiles jsr DrawSprite ; Call routine to draw 1 SPRITE0 tile to the OAM jmp NextActor : cmp #ActorType::PLAYER bne :+ lda ActorsArray+Actor::XPos,x sta ParamXPos lda ActorsArray+Actor::YPos,x sta ParamYPos lda #$60 sta ParamTileNum lda #%00000000 sta ParamAttribs lda #4 sta ParamNumTiles jsr DrawSprite ; Call routine to draw 4 PLAYER tiles to the OAM jmp NextActor : cmp #ActorType::SUBMARINE bne :+ lda ActorsArray+Actor::XPos,x sta ParamXPos lda ActorsArray+Actor::YPos,x sta ParamYPos lda #$04 sta ParamTileNum lda #%00100000 sta ParamAttribs lda #4 sta ParamNumTiles jsr DrawSprite ; Call routine to draw 4 SUBMARINE tile to the OAM jmp NextActor : cmp #ActorType::AIRPLANE bne :+ lda ActorsArray+Actor::XPos,x sta ParamXPos lda ActorsArray+Actor::YPos,x sta ParamYPos lda #$10 sta ParamTileNum lda #%00000011 sta ParamAttribs lda #3 sta ParamNumTiles jsr DrawSprite ; Call routine to draw 4 SUBMARINE tile to the OAM jmp NextActor : cmp #ActorType::MISSILE bne :+ lda ActorsArray+Actor::XPos,x sta ParamXPos lda ActorsArray+Actor::YPos,x sta ParamYPos lda #$50 sta ParamTileNum lda #%00000001 sta ParamAttribs lda #1 sta ParamNumTiles jsr DrawSprite ; Call routine to draw 1 MISSILE tile to the OAM jmp NextActor : NextActor: txa clc adc #.sizeof(Actor) tax cmp #MAX_ACTORS * .sizeof(Actor) beq :+ jmp ActorsLoop ; Use absolute jump to avoid branch limit of [-128..127] : tya pha ; Save the Y register to the stack LoopTrailingTiles: cpy PrevOAMCount bcs :+ lda #$FF sta (SprPtr),y ; Set Y position to $FF (to hide tile) iny sta (SprPtr),y ; Set tile number as $FF iny sta (SprPtr),y ; Set attribs as $FF iny sta (SprPtr),y ; Set X position to $FF (to hide tile) iny jmp LoopTrailingTiles : pla ; Save the previous value of Y into PrevOAMCount sta PrevOAMCount ; This is the total number of bytes that we just sent to the OAM rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Routine to loop "NumTiles" times, sending bytes to the OAM-RAM. ;; Params = ParamXPos, ParamYPos, ParamTileNum, ParamAttribs, ParamNumTiles ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc DrawSprite txa pha ; Save the value of the X register before anything ldx #0 TileLoop: lda ParamYPos ; Send Y position to the OAM sta (SprPtr),y iny lda ParamTileNum ; Send the Tile # to the OAM sta (SprPtr),y inc ParamTileNum ; ParamTileNum++ iny lda ParamAttribs ; Send the attributes to the OAM sta (SprPtr),y iny lda ParamXPos ; Send X position to the OAM sta (SprPtr),y clc adc #8 sta ParamXPos ; ParamXPos += 8 iny inx ; X++ cpx ParamNumTiles ; Loop until X == NumTiles bne TileLoop pla tax ; Restore the previous value of X register rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Reset handler (called when the NES resets or powers on) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reset: INIT_NES ; Macro to initialize the NES to a known state InitVariables: lda #0 sta Frame ; Frame = 0 sta Clock60 ; Clock60 = 0 sta XScroll ; XScroll = 0 sta CurrNametable ; CurrNametable = 0 sta Column ; Column = 0 lda #113 sta XPos lda #165 sta YPos lda #$10 sta Seed+1 sta Seed+0 ; Initialize the seed with any value not zero Main: jsr LoadPalette ; Call LoadPalette subroutine to load 32 colors into our palette AddSprite0: lda #ActorType::SPRITE0 sta ParamType lda #0 sta ParamXPos lda #27 sta ParamYPos jsr AddNewActor ; Add new actor to array AddPlayer: lda #ActorType::PLAYER sta ParamType lda XPos sta ParamXPos lda YPos sta ParamYPos jsr AddNewActor ; Add new actor to array InitBackgroundTiles: lda #1 sta CurrNametable ; CurrNametable = 1 lda #0 sta XScroll ; XScroll = 0 sta Column ; Column = 0 InitBackgroundLoop: jsr DrawNewColumn ; Draw all rows of new column (from top to bottom) lda XScroll clc adc #8 sta XScroll ; XScroll += 8 inc Column ; Column++ lda Column cmp #32 bne InitBackgroundLoop ; Repeat all 32 columns of the first nametable lda #0 sta CurrNametable ; CurrNametable = 0 lda #1 sta XScroll ; Scroll = 1 jsr DrawNewColumn ; Draw first column of the second nametable inc Column ; Column++ lda #%00000000 sta PPU_CTRL ; Set PPU increment to +1 mode InitAttribs: lda #1 sta CurrNametable lda #0 sta XScroll sta Column InitAttribsLoop: jsr DrawNewAttribs ; Draw attributes in the correct place of the first nametable lda XScroll clc adc #32 sta XScroll ; XScroll += 32 lda Column ; Repeat for all elements of the first nametable clc adc #4 sta Column ; Column += 4 cmp #32 bne InitAttribsLoop ; Loop until we reach Column 32 lda #0 sta CurrNametable lda #1 sta XScroll jsr DrawNewAttribs ; Draw first attributes of second nametable inc Column ; Column = 33 EnableRendering: lda #%10010000 ; Enable NMI and set background to use the 2nd pattern table (at $1000) sta PPU_CTRL lda #0 sta PPU_SCROLL ; Disable scroll in X sta PPU_SCROLL ; Disable scroll in Y lda #%00011110 sta PPU_MASK ; Set PPU_MASK bits to render the background GameLoop: lda Buttons sta PrevButtons ; Stores the previously pressed buttons jsr ReadControllers ; Read joypad and load button state CheckAButton: lda Buttons and #BUTTON_A beq :+ lda Buttons and #BUTTON_A cmp PrevButtons ; Compare the current buttons and check if button A was previously pressed beq :+ ; If it was, we bypass the creation of a new missile lda #ActorType::MISSILE sta ParamType lda XPos sta ParamXPos lda YPos sta ParamYPos jsr AddNewActor ; Call the subroutine to add a new missile actor : jsr SpawnActors jsr UpdateActors jsr RenderActors WaitForVBlank: ; We lock the execution of the game logic here lda IsDrawComplete ; Here we check and only perform a game loop call once NMI is done drawing beq WaitForVBlank ; Otherwise, we keep looping lda #0 sta IsDrawComplete ; Once we're done, we set the DrawComplete flag back to 0 jmp GameLoop ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; NMI interrupt handler ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; NMI: PUSH_REGS ; Macro to save register values by pushing them to the stack inc Frame ; Frame++ OAMStartDMACopy: ; DMA copy of OAM data from RAM to PPU lda #$02 ; Every frame, we copy spite data starting at $02** sta PPU_OAM_DMA ; The OAM-DMA copy starts when we write to $4014 ;; TODO ;; Loop all bytes of the background buffer at $7000 until we find length=0 ;; Sending the tiles to the PPU nametable in VRAM BackgroundCopy: lda #$70 sta BufPtr+1 lda #$00 sta BufPtr ldy #$00 BufferLoop: lda (BufPtr),y ; Fetch the length beq EndBackgroundCopy ; if len is 0, stop reading from bg buffer tax ; x = length iny lda (BufPtr),y ; hi byte sta PPU_ADDR iny lda (BufPtr),y ; low byte sta PPU_ADDR iny DataLoop: lda (BufPtr),y sta PPU_DATA iny dex ; X-- bne DataLoop jmp BufferLoop EndBackgroundCopy: NewColumnCheck: lda XScroll and #%00000111 ; Check if the scroll a multiple of 8 bne :+ ; If it isn't, we still don't need to draw a new column jsr DrawNewColumn ; If it is a multiple of 8, we proceed to draw a new column of tiles! Clamp128Cols: lda Column clc adc #1 ; Column++ and #%01111111 ; Drop the left-most bit to wrap around 128 sta Column ; Clamping the value to never go beyond 128 : NewAttribsCheck: lda XScroll and #%00011111 ; Check if the scroll is a multiple of 32 (lowest 5 bits are 00000) bne :+ ; If it isn't, we still don't need to draw new attributes jsr DrawNewAttribs ; It it is a multiple of 32, we draw the new attributes! : SetPPUNoScroll: lda #0 sta PPU_SCROLL sta PPU_SCROLL ; Set *no* scroll for the status bar EnablePPUSprite0: lda #%10010000 ; Enable PPU sprites for sprite 0 sta PPU_CTRL lda #%00011110 ; Enable sprites and enable background sta PPU_MASK WaitForNoSprite0: lda PPU_STATUS and #%01000000 ; PPU address $2002 bit 6 is the sprite 0 hit flag bne WaitForNoSprite0 ; Loop until we do *not* have a sprite 0 hit WaitForSprite0: lda PPU_STATUS and #%01000000 ; PPU address $2002 bit 6 is the sprite 0 hit flag beq WaitForSprite0 ; Loop until we do have a sprite 0 hit ScrollBackground: inc XScroll ; XScroll++ lda XScroll bne :+ ; Check if XScroll rolled back to 0, then we swap nametables! lda CurrNametable eor #1 ; An XOR with %00000001 will flip the right-most bit. sta CurrNametable ; If it was 0, it becomes 1. If it was 1, it becomes 0. : lda XScroll sta PPU_SCROLL ; Set the horizontal X scroll first lda #0 sta PPU_SCROLL ; No vertical scrolling RefreshRendering: lda #%10010000 ; Enable NMI, sprites from Pattern Table 0, background from Pattern Table 1 ora CurrNametable ; OR with CurrNametable (0 or 1) to set PPU_CTRL bit-0 (starting nametable) sta PPU_CTRL lda #%00011110 ; Enable sprites, enable background, no clipping on left side sta PPU_MASK SetGameClock: lda Frame ; Increment Clock60 every time we reach 60 frames (NTSC = 60Hz) cmp #60 ; Is Frame equal to #60? bne :+ ; If not, bypass Clock60 increment inc Clock60 ; But if it is 60, then increment Clock60 and zero Frame counter lda #0 sta Frame : SetDrawComplete: lda #1 sta IsDrawComplete ; Set the DrawComplete flag to indicate we are done drawing to the PPU PULL_REGS rti ; Return from interrupt ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; IRQ interrupt handler ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; IRQ: rti ; Return from interrupt ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Hardcoded list of color values in ROM to be loaded by the PPU ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; PaletteData: .byte $1C,$0F,$22,$1C, $1C,$37,$3D,$0F, $1C,$37,$3D,$30, $1C,$0F,$3D,$30 ; Background palette .byte $1C,$0F,$2D,$10, $1C,$0F,$20,$27, $1C,$2D,$38,$18, $1C,$0F,$1A,$32 ; Sprite palette ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Background data (contains 4 screens that should scroll horizontally) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BackgroundData: .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$23,$33,$15,$21,$12,$00,$31,$31,$31,$55,$56,$00,$00 ; ---> screen column 1 (from top to bottom) .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$24,$34,$15,$15,$12,$00,$31,$31,$53,$56,$56,$00,$00 ; ---> screen column 2 (from top to bottom) .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$14,$11,$3e,$15,$12,$00,$00,$00,$31,$52,$56,$00,$00 ; ---> screen column 3 (from top to bottom) .byte $13,$13,$7f,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$44,$21,$21,$21,$14,$11,$3f,$15,$12,$00,$00,$00,$31,$5a,$56,$00,$00 ; ... .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$45,$21,$21,$21,$22,$32,$15,$15,$12,$00,$00,$00,$31,$58,$56,$00,$00 ; ... .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$46,$21,$21,$21,$26,$36,$15,$15,$12,$00,$00,$00,$51,$5c,$56,$00,$00 ; ... .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$27,$37,$15,$15,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$61,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$28,$38,$15,$15,$12,$00,$00,$00,$00,$5c,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$57,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$47,$21,$21,$21,$48,$21,$21,$22,$32,$3e,$15,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$4a,$21,$21,$23,$33,$4e,$15,$12,$00,$00,$00,$00,$59,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$21,$21,$21,$24,$34,$3f,$15,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$57,$56,$00,$00 .byte $13,$13,$6c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$59,$56,$00,$00 .byte $13,$13,$78,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$15,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$7b,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$15,$15,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$53,$56,$00,$00 .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$21,$15,$21,$21,$25,$35,$15,$15,$12,$00,$00,$00,$00,$54,$56,$00,$00 .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$26,$36,$15,$15,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$27,$37,$15,$15,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$48,$21,$21,$15,$27,$37,$15,$15,$12,$00,$00,$00,$00,$5d,$56,$00,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$21,$28,$38,$3e,$21,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$21,$22,$35,$3f,$21,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$21,$26,$36,$3f,$21,$12,$00,$00,$00,$00,$57,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$4a,$21,$21,$21,$27,$37,$21,$15,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$76,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$21,$21,$21,$28,$38,$15,$15,$12,$00,$00,$00,$00,$58,$56,$00,$00 .byte $13,$13,$72,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$14,$11,$3e,$21,$12,$00,$00,$00,$00,$59,$56,$00,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$21,$21,$21,$14,$11,$4e,$21,$12,$00,$00,$00,$51,$59,$56,$00,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$14,$11,$3f,$15,$12,$00,$00,$00,$00,$5c,$56,$00,$00 .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$29,$39,$21,$21,$12,$00,$00,$00,$00,$55,$56,$00,$00 .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$21,$48,$21,$2c,$2a,$3a,$3c,$21,$12,$00,$00,$00,$54,$56,$56,$00,$00 .byte $13,$13,$65,$13,$20,$21,$21,$21,$21,$21,$21,$21,$46,$21,$21,$21,$4a,$21,$2d,$2a,$3a,$3d,$15,$12,$00,$00,$00,$00,$52,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$2b,$3b,$15,$15,$12,$00,$00,$00,$00,$57,$56,$00,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$21,$12,$00,$31,$31,$31,$55,$56,$ff,$9a .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$21,$15,$21,$14,$11,$15,$15,$12,$00,$31,$31,$53,$56,$56,$ff,$5a .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$15,$15,$21,$14,$11,$3e,$15,$12,$00,$00,$00,$31,$52,$56,$ff,$5a .byte $13,$13,$7f,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$15,$15,$15,$21,$14,$11,$3f,$15,$12,$00,$00,$00,$31,$5a,$56,$ff,$56 .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$15,$15,$15,$21,$14,$11,$15,$15,$12,$00,$00,$00,$31,$58,$56,$ff,$59 .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$15,$15,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$51,$5c,$56,$ff,$5a .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$15,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$ff,$5a .byte $13,$13,$61,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$5c,$56,$ff,$5a .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$57,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$47,$21,$21,$21,$48,$21,$21,$14,$11,$3e,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$4a,$21,$21,$14,$11,$4e,$15,$12,$00,$00,$00,$00,$59,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$21,$21,$21,$25,$35,$3f,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$26,$36,$15,$15,$12,$00,$00,$00,$00,$57,$56,$aa,$00 .byte $13,$13,$6c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$21,$21,$21,$21,$27,$37,$15,$15,$12,$00,$00,$00,$00,$59,$56,$aa,$00 .byte $13,$13,$78,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$15,$21,$21,$21,$28,$38,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$7b,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$15,$15,$21,$21,$29,$39,$15,$15,$12,$00,$00,$00,$00,$53,$56,$aa,$00 .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$21,$15,$21,$1f,$2a,$3a,$3c,$15,$12,$00,$00,$00,$00,$54,$56,$aa,$00 .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$28,$3b,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$48,$21,$21,$15,$14,$11,$15,$15,$12,$00,$00,$00,$00,$5d,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$21,$14,$11,$3e,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$21,$14,$11,$3f,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$21,$14,$11,$3f,$21,$12,$00,$00,$00,$00,$57,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$4a,$21,$21,$21,$14,$11,$21,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$76,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$5a,$00 .byte $13,$13,$72,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$14,$11,$3e,$21,$12,$00,$00,$00,$00,$59,$56,$9a,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$21,$21,$21,$22,$32,$4e,$21,$12,$00,$00,$00,$51,$59,$56,$aa,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$23,$33,$3f,$15,$12,$00,$00,$00,$00,$5c,$56,$6a,$00 .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$24,$34,$21,$21,$12,$00,$00,$00,$00,$55,$56,$9a,$00 .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$21,$48,$15,$15,$14,$11,$15,$21,$12,$00,$00,$00,$54,$56,$56,$aa,$00 .byte $13,$13,$65,$13,$20,$21,$21,$21,$21,$21,$21,$21,$46,$21,$21,$21,$4a,$21,$15,$14,$11,$15,$15,$12,$00,$00,$00,$00,$52,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$57,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$21,$12,$00,$31,$31,$31,$58,$56,$ff,$9a .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$14,$11,$15,$15,$12,$00,$31,$31,$00,$5d,$56,$ff,$5a .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$14,$11,$3e,$15,$12,$00,$00,$00,$31,$58,$56,$ff,$5a .byte $13,$13,$7f,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$44,$21,$21,$21,$14,$11,$3f,$15,$12,$00,$00,$00,$31,$58,$56,$ff,$aa .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$45,$21,$21,$21,$22,$32,$15,$15,$12,$00,$00,$00,$31,$58,$56,$ff,$56 .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$46,$21,$21,$21,$26,$36,$15,$15,$12,$00,$00,$00,$51,$58,$56,$ff,$9a .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$27,$37,$15,$15,$12,$00,$00,$00,$00,$58,$56,$ff,$59 .byte $13,$13,$61,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$28,$38,$15,$15,$12,$00,$00,$00,$00,$55,$56,$ff,$5a .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$57,$56,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$47,$21,$21,$21,$48,$21,$21,$22,$32,$3e,$15,$12,$00,$00,$00,$00,$52,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$4a,$21,$21,$23,$33,$4e,$15,$12,$00,$00,$00,$00,$53,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$21,$21,$21,$24,$34,$3f,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$6c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$78,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$7b,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$59,$56,$aa,$00 .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$29,$39,$15,$15,$12,$00,$00,$00,$00,$59,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$1f,$2a,$3a,$3d,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$48,$21,$15,$2d,$2a,$3a,$3c,$15,$12,$00,$00,$00,$00,$5b,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$2f,$2a,$3a,$3d,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$21,$28,$3b,$3e,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$49,$21,$21,$21,$14,$11,$4e,$21,$12,$00,$00,$00,$51,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$4a,$21,$21,$21,$14,$11,$21,$15,$12,$00,$00,$00,$51,$58,$56,$aa,$00 .byte $13,$13,$76,$13,$20,$21,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$21,$21,$15,$29,$39,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$72,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$15,$2c,$2a,$3a,$3e,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$21,$21,$2e,$2a,$3a,$4e,$21,$12,$00,$00,$00,$51,$58,$56,$aa,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$1f,$2a,$3a,$3f,$15,$12,$00,$00,$00,$00,$5d,$56,$aa,$00 .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$15,$28,$3b,$3f,$21,$12,$00,$00,$00,$00,$57,$56,$aa,$00 .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$45,$21,$21,$21,$48,$21,$15,$14,$11,$15,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$65,$13,$20,$21,$21,$21,$21,$21,$21,$21,$46,$21,$21,$21,$4a,$21,$15,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$21,$12,$00,$31,$31,$31,$58,$56,$ff,$9a .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$14,$11,$15,$15,$12,$00,$31,$31,$00,$58,$56,$ff,$5a .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$31,$58,$56,$ff,$5a .byte $13,$13,$7f,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$15,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$31,$54,$56,$ff,$59 .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$15,$21,$21,$21,$14,$11,$3e,$15,$12,$00,$00,$00,$31,$54,$56,$ff,$56 .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$42,$15,$21,$21,$15,$21,$21,$21,$14,$11,$4e,$15,$12,$00,$00,$00,$51,$58,$56,$ff,$5a .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$21,$21,$21,$21,$14,$11,$4e,$15,$12,$00,$00,$00,$00,$58,$56,$ff,$59 .byte $13,$13,$61,$13,$20,$21,$21,$21,$21,$21,$21,$44,$21,$21,$21,$21,$21,$21,$21,$14,$11,$3f,$15,$12,$00,$00,$00,$00,$58,$56,$ff,$5a .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$45,$21,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$47,$15,$21,$21,$21,$15,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$53,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$15,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$21,$21,$21,$14,$11,$15,$15,$12,$00,$00,$00,$00,$57,$56,$aa,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$21,$21,$21,$29,$39,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$6c,$13,$20,$21,$21,$21,$21,$21,$48,$21,$15,$21,$21,$21,$21,$1d,$1e,$2a,$3a,$3c,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$78,$13,$20,$21,$21,$21,$21,$21,$49,$21,$21,$21,$21,$21,$21,$21,$21,$2b,$3b,$3e,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$7b,$13,$20,$21,$21,$21,$21,$21,$4a,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$4e,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$6e,$13,$20,$21,$21,$21,$21,$15,$48,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$4e,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$49,$21,$21,$21,$21,$21,$21,$21,$21,$14,$11,$3f,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$4a,$21,$21,$21,$21,$21,$21,$15,$15,$14,$11,$15,$15,$12,$00,$00,$00,$00,$59,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$15,$21,$15,$15,$14,$11,$15,$15,$12,$00,$00,$00,$00,$59,$56,$aa,$00 .byte $13,$13,$60,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$15,$21,$21,$15,$14,$11,$15,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$42,$21,$21,$21,$15,$21,$21,$21,$29,$39,$15,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$43,$21,$21,$21,$15,$21,$21,$2c,$2a,$3a,$3c,$21,$12,$00,$00,$00,$50,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$44,$15,$21,$21,$15,$21,$21,$2d,$2a,$3a,$3e,$15,$12,$00,$00,$00,$50,$58,$56,$aa,$00 .byte $13,$13,$76,$13,$20,$21,$21,$21,$21,$21,$21,$45,$15,$21,$21,$21,$21,$21,$15,$2b,$3b,$3f,$15,$12,$00,$00,$00,$00,$54,$56,$aa,$00 .byte $13,$13,$72,$13,$20,$21,$21,$21,$21,$21,$21,$46,$15,$21,$21,$21,$21,$15,$15,$14,$11,$3f,$21,$12,$00,$00,$00,$00,$59,$56,$aa,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$21,$21,$15,$14,$11,$15,$21,$12,$00,$00,$00,$51,$58,$56,$aa,$00 .byte $13,$13,$7c,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$21,$21,$15,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$75,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$21,$21,$15,$14,$11,$15,$21,$12,$00,$00,$00,$00,$5d,$56,$aa,$00 .byte $13,$13,$84,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$15,$21,$15,$14,$11,$15,$21,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$65,$13,$20,$21,$21,$21,$21,$21,$21,$21,$15,$21,$21,$21,$15,$15,$15,$14,$11,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 .byte $13,$13,$13,$13,$20,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$21,$22,$32,$15,$15,$12,$00,$00,$00,$00,$58,$56,$aa,$00 AttributeData: .byte $ff,$aa,$aa,$aa,$9a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$6a,$a6,$00,$00,$00 .byte $ff,$aa,$aa,$9a,$59,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$9a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$5a,$9a,$00,$00,$00 .byte $ff,$aa,$aa,$9a,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$6a,$56,$00,$00,$00 .byte $ff,$aa,$aa,$9a,$59,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$9a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$aa,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$56,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$9a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$59,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$9a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$59,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$56,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$59,$00,$00,$00 .byte $ff,$aa,$aa,$aa,$5a,$00,$00,$00 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Here we add the CHR-ROM data, included from an external .CHR file ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .segment "CHARS" .incbin "atlantico.chr" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Vectors with the addresses of the handlers that we always add at $FFFA ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .segment "VECTORS" .word NMI ; Address (2 bytes) of the NMI handler .word Reset ; Address (2 bytes) of the Reset handler .word IRQ ; Address (2 bytes) of the IRQ handler