07b3a531cb
For some reason, this causes graphical glitches on real hardware.
1507 lines
32 KiB
NASM
1507 lines
32 KiB
NASM
.INCLUDE "header.asm"


.INCLUDE "init.asm"


.INCLUDE "registers.asm"


.INCLUDE "memory.asm"








; TODO: define screen / ship / shot dimensions as constants.








; Sets A to 8bit (& enables 8bit "B" register).


.MACRO SetA8Bit


sep #%00100000 ; 8bit A/B.


.ENDM








; Sets A to 16bit.


.MACRO SetA16Bit


rep #%00100000 ; 16bit A.


.ENDM








; Sets X/Y to 16bit.


.MACRO SetXY16Bit


rep #%00010000 ; 16bit X/Y.


.ENDM








; Stores result to A.


; Assumes 16bit X & 8bit A.


; Modifies X.


; Updates randomBytePtr.


.MACRO GetRandomByte


ldx randomBytePtr


lda $028000, X ; $028000: beginning of ROM bank 2.


inx


cpx #$8000 ; This is the size of the entire ROM bank.


bne +++


ldx #0


+++


stx randomBytePtr


.ENDM








; Modifies X and Y to move to the next elements in the sprite tables.


.MACRO AdvanceSpritePointers


.rept 4


inx


.endr


iny


.ENDM






.BANK 0 SLOT 0


.ORG 0


.SECTION "MainCode"








Start:


InitSNES




; By default we assume 16bit X/Y and 8bit A.


; If any code wants to change this, it's expected to do so itself,


; and to change them back to the defaults before returning.


SetXY16Bit


SetA8Bit




jsr LoadPaletteAndTileData


jsr InitWorld




; Set screen mode: 16x16 tiles for backgrounds, mode 1.


lda #%11000001


sta BGMODE




; Set sprite size to 8x8 (small) and 32x32 (large).


lda #%00100000


sta OAMSIZE




; Main screen: enable sprites & BG3.


lda #%00010100


sta MSENABLE




; Turn on the screen.


; Format: x000bbbb


; x: 0 = screen on, 1 = screen off, bbbb: Brightness ($0$F)


lda #%00001111


sta INIDISP




jmp MainLoop








LoadPaletteAndTileData:


; For more details on DMA, see:


; http://wiki.superfamicom.org/snes/show/Grog%27s+Guide+to+DMA+and+HDMA+on+the+SNES


; http://wiki.superfamicom.org/snes/show/Making+a+Small+Game++TicTacToe


;


; A lot of the graphicsrelated registers are explained in Qwertie's doc:


; http://emudocs.org/Super%20NES/General/snesdoc.html


; ... but be careful, because there are some errors in this doc.


;


; bazz's tutorial (available from http://wiki.superfamicom.org/snes/) is


; quite helpful with palette / sprites / DMA, especially starting at


; http://wiki.superfamicom.org/snes/show/Working+with+VRAM++Loading+the+Palette




; Initialize the palette memory in a loop.


; We could also do this with a DMA transfer (like we do with the tile data


; below), but it seems overkill for just a few bytes. :)


; TODO: do it with a DMA transfer.




; First, sprite palette data:


ldx #0


lda #128 ; Palette entries for sprites start at 128.


sta CGADDR





lda.l SpritePalette, X


sta CGDATA


inx


cpx #32 ; 32 bytes of palette data.


bne 




; Now, BG3 palette data.


; Palette entries for BG3 start at 0.


ldx #0


lda #0


sta CGADDR





lda.l TilePalette, X


sta CGDATA


inx


cpx #8 ; 8 bytes of palette data.


bne 




; TODO: make the "DMA stuff into VRAM" a macro or function.


; Set VMADDR to where we want the DMA to start. We'll store sprite data


; at the beginning of VRAM.


ldx #$0000


stx VMADDR


; DMA 0 source address & bank.


ldx #SpriteData


stx DMA0SRC


lda #:SpriteData


sta DMA0SRCBANK


; DMA 0 transfer size. Equal to the size of sprites32.pic.


ldx #4096


stx DMA0SIZE


; DMA 0 control register.


; Transfer type 001 = 2 addresses, LH.


lda #%00000001


sta DMA0CTRL


; DMA 0 destination.


lda #$18 ; The upper byte is assumed to be $21, so this is $2118 & $2119.


sta DMA0DST


; Enable DMA channel 0.


lda #%00000001


sta DMAENABLE




; Store background tile data at byte $2000 of VRAM.


; (VMADDR is a word address, so multiply by 2 to get the byte address.)


ldx #$1000


stx VMADDR


; DMA 0 source address & bank.


ldx #TileData


stx DMA0SRC


lda #:TileData


sta DMA0SRCBANK


; DMA 0 transfer size. Equal to the size of tiles.pic.


ldx #512


stx DMA0SIZE


; DMA 0 control register.


; Transfer type 001 = 2 addresses, LH.


lda #%00000001


sta DMA0CTRL


; DMA 0 destination.


lda #$18 ; The upper byte is assumed to be $21, so this is $2118 & $2119.


sta DMA0DST


; Enable DMA channel 0.


lda #%00000001


sta DMAENABLE




; Tell the system that the BG3 tilemap starts at $4000.


lda #%00100000


sta BG3TILEMAP


; ... and that the background tile data for BG3 starts at $2000.


lda #%00000001


sta BG34NBA




; Set up the BG3 tilemap.


; VRAM write mode: increments the address every time we write a word.


lda #%10000000


sta VMAIN


; Set word address for accessing VRAM.


ldx #$2000 ; BG 3 tilemap starts here. (Byte address $4000.)


stx VMADDR


; Now write entries into the tile map.


ldy #0





GetRandomByte


sta $00


ldx #$0000 ; This is a blank tile.


; 1 in 8 chance that we choose a nonblank tile.


bit #%00000111


bne +


ldx #$0002


bit #%10000000


bne +


ldx #$8002 ; Flip vertically.


+


stx VMDATA


iny


; The tile map is 32x32 (1024 entries).


cpy #1024


bne 




rts








InitWorld:


; Clear the memory that's used to keep track of normal game state.


SetA16Bit


lda #0


ldx #$20





sta 0, X


inx


inx


cpx #$1000


bne 


SetA8Bit




jsr SetBackgroundColor




; Initial enemy shipspawn cooldown.


lda #30


sta enemyShipSpawnCooldown




; Player's initial starting location and health.


lda #(256 / 4)


sta playerX


lda #((224  32) / 2)


sta playerY


lda #10


sta playerHealth




; (xvelocity, yvelocity) of 4 different player shot patterns.


lda #6


sta shotVelocityTable


lda #0


sta shotVelocityTable + 1




lda #3


sta shotVelocityTable + 2


lda #3


sta shotVelocityTable + 3




lda #0


sta shotVelocityTable + 4


lda #6


sta shotVelocityTable + 5




lda #3


sta shotVelocityTable + 6


lda #3


sta shotVelocityTable + 7




lda #6


sta shotVelocityTable + 8


lda #0


sta shotVelocityTable + 9




lda #3


sta shotVelocityTable + 10


lda #3


sta shotVelocityTable + 11




lda #0


sta shotVelocityTable + 12


lda #6


sta shotVelocityTable + 13




lda #3


sta shotVelocityTable + 14


lda #3


sta shotVelocityTable + 15




rts








MainLoop:


lda #%10000001 ; Enable NMI interrupt & auto joypad read.


sta NMITIMEN


wai ; Wait for interrupt.


lda #%00000001 ; Disable NMI interrupt while processing.


sta NMITIMEN




jsr JoypadRead




lda playerHealth


cmp #0


beq +


jsr UpdateWorld


bra ++


+


jsr GameOver


++


jsr UpdateSprites


jsr FillSecondarySpriteTable


bra MainLoop








GameOver:


; Wait until the player hits Start.


lda joy1 + 1


bit #$10 ; Start


beq +


jsr InitWorld


+


rts








JoypadRead:


; Load joypad registers into RAM for easy inspection & manipulation.





lda HVBJOY


bit #$01 ; If autojoypad read is happening, loop.


bne 


ldx JOY1L


stx joy1


ldx JOY2L


stx joy2


rts








; TODO: move functions around to be in a more sensible order.


JoypadHandler:


JoypadUp:


lda joy1 + 1


bit #$08 ; Up


beq JoypadDown ; Button not pressed.


lda playerY


cmp #0


beq JoypadDown ; Value saturated.


dec playerY


dec playerY




JoypadDown:


lda joy1 + 1


bit #$04 ; Down


beq JoypadLeft ; Button not pressed.


lda playerY


cmp #(224  32  8  4) ; player height, bottom status bar, bottom padding


beq JoypadLeft ; Value saturated.


inc playerY


inc playerY




JoypadLeft:


lda joy1 + 1


bit #$02 ; Left


beq JoypadRight ; Button not pressed.


lda playerX


cmp #0


beq JoypadRight ; Value saturated.


dec playerX


dec playerX




JoypadRight:


lda joy1 + 1


bit #$01 ; Right


beq JoypadY ; Button not pressed.


lda playerX


cmp #(256  32)


beq JoypadY ; Value saturated.


inc playerX


inc playerX




JoypadY:


lda joy1 + 1


bit #$40 ; Y


beq JoypadX ; Button not pressed.


lda backgroundRed


cmp #0


beq JoypadX ; Value saturated.


dec backgroundRed




JoypadX:


lda joy1


bit #$40 ; X


beq JoypadL ; Button not pressed.


lda backgroundRed


cmp #31


beq JoypadL ; Value saturated.


inc backgroundRed




JoypadL:


lda joy1


bit #$20 ; L


beq JoypadR ; Button not pressed.


lda backgroundBlue


cmp #0


beq JoypadR ; Value saturated.


dec backgroundBlue




JoypadR:


lda joy1


bit #$10 ; R


beq JoypadB ; Button not pressed.


lda backgroundBlue


cmp #31


beq JoypadB ; Value saturated.


inc backgroundBlue




JoypadB:


lda joy1 + 1


bit #$80 ; B


beq JoypadDone


jsr MaybeShoot




JoypadDone:


rts








MaybeShoot:


; If the cooldown timer is nonzero, don't shoot.


lda shotCooldown


cmp #0


bne MaybeShootDone


; Find the first empty spot in the shots array.


ldx #playerShotArray





lda 0, X


cmp #0


beq +


.rept shotSize


inx


.endr


; If we went all the way to the end, bail out.


cpx #(playerShotArray + playerShotArrayLength * shotSize)


bne 


rts


+


; Enable shot; set its position based on player position.


lda #8 ; Sprite number.


sta 0, X




lda playerX


clc


adc #28


sta 1, X




lda playerY


clc


adc #14


sta 2, X




; Get x and yvelocity out of shotVelocityTable.


lda nextShotState


and #%00000000 ; 8 possibilities if we use #%00000111.


ldy #0





cmp #0


beq +


.rept 2


iny


.endr


dec A


bra 


+


inc nextShotState




; xvelocity.


lda shotVelocityTable, Y


sta 3, X


; yvelocity.


lda shotVelocityTable + 1, Y


sta 4, X




; Set cooldown timer.


lda #8


sta shotCooldown


MaybeShootDone:


rts








UpdateWorld:


jsr UpdateShotCooldown


jsr UpdateShotPositions


jsr JoypadHandler


jsr SpawnEnemyShips


jsr UpdateEnemyShips




jsr CheckCollisionsWithPlayer


jsr CheckCollisionsWithEnemies




jsr UpdateBackgroundScroll


jsr UpdateHighScore


rts








UpdateShotCooldown:


; Update shot cooldown.


lda shotCooldown


cmp #0


beq +


dec A


sta shotCooldown


+


rts








SpawnEnemyShips:


lda enemyShipSpawnCooldown


cmp #0


beq +


dec A


sta enemyShipSpawnCooldown


rts


+


GetRandomByte


and #%00011111


clc


adc #16


sta enemyShipSpawnCooldown




; Find an empty spot in the array.


ldy #0





lda enemyShipArray, Y


cmp #0


beq +


.rept enemyShipSize


iny


.endr


cpy #(enemyShipArrayLength * enemyShipSize)


bne 


rts ; Too many ships; bail.




+


lda #4 ; Sprite number.


sta enemyShipArray, Y




lda #254


sta enemyShipArray + 1, Y ; x.







GetRandomByte


cmp #(224  32  8  4)


bcs  ; Keep trying.


sta enemyShipArray + 2, Y ; y.




lda #0


sta enemyShipArray + 3, Y ; move AI type.


GetRandomByte


and #%00000011


cmp #1


beq +


lda #0


+


sta enemyShipArray + 4, Y ; shoot AI type.




lda #12


sta enemyShipArray + 5, Y ; shot cooldown.




rts








; TODO: reap ships if they move off the top, bottom, or right too.


UpdateEnemyShips:


ldy #0 ; Index into enemyShipArray.


sty $00 ; Index into enemyShotArray.





lda enemyShipArray, Y


cmp #0 ; If it's not enabled, skip it.


beq ++




; Move the ship.


; TODO: implement different movement based on AItype.


lda enemyShipArray + 1, Y ; x


clc


adc #3 ; xvelocity.


bcs +


lda #0


sta enemyShipArray, Y ; reap it.


bra ++


+


sta enemyShipArray + 1, Y ; move it.




lda enemyShipArray + 5, Y ; shot cooldown


cmp #0


beq +


dec A


sta enemyShipArray + 5, Y ; new shot cooldown


bra ++




+ ; Add a shot.


; TODO: implement different shooting based on shoottype.


lda #12


sta enemyShipArray + 5, Y ; new shot cooldown




lda enemyShipArray + 1, Y


sta $02 ; Enemy ship x.


lda enemyShipArray + 2, Y


sta $03 ; Enemy ship y.


lda enemyShipArray + 4, Y


sta $04 ; Enemy ship shootAI type.


phy


ldy $00


jsr SpawnEnemyShot


sty $00


ply




++ ; Done processing this ship.


.rept enemyShipSize


iny


.endr




cpy #(enemyShipArrayLength * enemyShipSize)


bne 


rts








; Expects:


; Y: index into enemyShotArray (bytes).


; $02: enemy ship xposition.


; $03: enemy ship yposition.


; $04: enemy ship shootAI type.


;


; Modifies:


; A.


; Y: to point at the next possible free index into enemyShotArray.


SpawnEnemyShot:





; Bail if at end of array.


cpy #(enemyShotArrayLength * shotSize)


bne +


rts


+




lda enemyShotArray, Y


cmp #0


beq +


; Try next slot.


.rept shotSize


iny


.endr


bra 


+




; OK, found a spot.


lda #9 ; Sprite number.


sta enemyShotArray, Y




lda $02 ; Get enemy x.


sta enemyShotArray + 1, Y ; Save as shot x.




lda $03 ; Get enemy y.


clc


adc #((32  4) / 2) ; Center it with enemy ship.


sta enemyShotArray + 2, Y ; Save as shot y.




lda #6


sta enemyShotArray + 3, Y ; xvelocity.




; Choose velocities based on shoot AI type.


lda $04


cmp #1


beq +


; Normal shot.


lda #0


sta enemyShotArray + 4, Y ; yvelocity.


rts




; Shot aimed toward player.


+


lda playerY


cmp $03


bcs +


lda #2


sta enemyShotArray + 4, Y ; yvelocity.


rts


+


lda #2


sta enemyShotArray + 4, Y ; yvelocity.


rts








UpdateShotPositions:


ldx #0




UpdateShot: ; Updates position of one shot.


lda playerShotArray, X


cmp #0


beq ShotDone


; Add to the xcoordinate. If the carry bit is set, we went off the edge


; of the screen, so disable the shot.


lda playerShotArray + 3, X ; xvelocity.


sta $00


bit #%10000000 ; Check whether the velocity is negative.


bne UpdateShotWithNegativeXVelocity


lda playerShotArray + 1, X


clc


adc $00


bcs DisableShot


sta playerShotArray + 1, X ; Store new xcoord.


bra UpdateShotY




UpdateShotWithNegativeXVelocity:


; TODO: wrap sprites when they go negative here, like we do with


; yvelocities.


lda playerShotArray + 1, X ; Current x.


clc


adc $00


bcc DisableShot


sta playerShotArray + 1, X




UpdateShotY:


; Add to the ycoordinate.


lda playerShotArray + 4, X ; yvelocity.


sta $00


bit #%10000000 ; Check whether the velocity is negative.


bne UpdateShotWithNegativeYVelocity




lda playerShotArray + 2, X


clc


adc $00


cmp #224


bcs DisableShot


sta playerShotArray + 2, X ; Store new ycoord.


bra ShotDone




UpdateShotWithNegativeYVelocity:


lda playerShotArray + 2, X ; Current y.


cmp #224


bcs + ; If the shot was "off the top" before moving, maybe we'll reap it.


adc $00 ; Otherwise, just update it,


sta playerShotArray + 2, X ; save the result,


bra ShotDone ; and we know it shouldn't be reaped.


+


clc


adc $00


cmp #224


bcc DisableShot ; If it's now wrapped around, reap it.


sta playerShotArray + 2, X


bra ShotDone




DisableShot:


stz playerShotArray, X




ShotDone:


; TODO: in places where we .rept inx (etc), is it faster to use actual


; addition?


.rept shotSize


inx


.endr


cpx #((playerShotArrayLength + enemyShotArrayLength) * shotSize)


bne UpdateShot




rts








; Expects:


; $00: xcoordinate of ship's center.


; $01: ycoordinate of ship's center.


; $02: half of the shot's size.


; $03: xcoordinate of shot's upperleft.


; $04: ycoordinate of shot's upperleft.


;


; Modifies:


; $05


; A: set to nonzero if there was a collision, zero otherwise.


CheckCollision:


lda $03


clc


adc $02 ; Get the center of the shot.


sbc $00


bpl + ; If the result is positive, great!


eor #$ff ; Otherwise, negate it.


inc A


+


; A now contains dx, guaranteed to be positive.


cmp #18 ; Threshold for "successful hit".


bcc +


lda #0 ; Already too far to be a hit; bail.


rts


+


sta $05 ; Save dx for later.


; Find dy.


lda $04


clc


adc $02 ; Get the center of the shot.


sbc $01


bpl + ; If the result is positive, great!


eor #$ff ; Otherwise, negate it.


inc A


+


; A now contains dy, guaranteed to be positive.


clc


adc $05 ; Add dx.


cmp #18 ; Threshold for "successful hit".


lda #0


bcs +


lda #1 ; Got a hit.


+


rts








CheckCollisionsWithPlayer:


lda #2 ; Half of shot's size.


sta $02




; Store player position statically.


clc


lda playerX


adc #16 ; Can't overflow.


sta $00 ; Store the center.


lda playerY


; Store the center. Our ship is actually 31 pixels tall, so offsetting by


; 15 feels more "fair": a shot that hits the invisible bottom edge of the


; ship won't count as a hit.


adc #15


sta $01




ldx #0





lda enemyShotArray, X


cmp #0 ; Check whether it's active.


beq +




lda enemyShotArray + 1, X ; x.


sta $03


lda enemyShotArray + 2, X ; y.


sta $04


jsr CheckCollision


cmp #0


beq +




; OK, we got a hit! Disable the shot.


lda #0


sta enemyShotArray, X




; ... and decrement the player's life.


lda playerHealth


cmp #0


beq +


dec playerHealth




+


.rept shotSize


inx


.endr




cpx #(enemyShotArrayLength * shotSize)


bne 


rts








CheckCollisionsWithEnemies:


lda #2 ; Half of shot's size.


sta $02




ldy #0 ; Index into enemyShipArray.





lda enemyShipArray, Y


cmp #0 ; Check whether it's active.


beq ++




; Store enemy position statically.


clc


lda enemyShipArray + 1, Y ; x.


adc #16 ; Can't overflow.


sta $00 ; Store the center.


lda enemyShipArray + 2, Y ; y.


adc #15


sta $01 ; Store the center.




ldx #0 ; Index into playerShotArray.





lda playerShotArray, X


cmp #0


beq +




lda playerShotArray + 1, X ; x.


sta $03


lda playerShotArray + 2, X ; y.


sta $04


jsr CheckCollision


cmp #0


beq +




; OK, we got a hit! Disable the shot.


lda #0


sta playerShotArray, X




; ... and also the enemy ship.


sta enemyShipArray, Y




; Give that player some points. Players love points.


SetA16Bit


sed ; Set decimal mode.


lda playerScore


clc


adc #1 ; We can't just "inc"; it doesn't know about decimal mode.


sta playerScore


cld ; Clear decimal mode.


SetA8Bit


bra ++ ; ... we're done with this ship; no need to check more shots.




+


.rept shotSize


inx


.endr




cpx #(playerShotArrayLength * shotSize)


bne 




++


.rept enemyShipSize


iny


.endr




cpy #(enemyShipArrayLength * enemyShipSize)


bne 


rts








UpdateBackgroundScroll:


; Make the background scroll. Horizontal over time; vertical depending on


; player's ycoordinate.


lda vBlankCounter


sta BG3HOFS


lda vBlankCounter + 1


sta BG3HOFS


lda playerY


.rept 3


lsr


.endr


sta BG3VOFS


stz BG3VOFS


rts








UpdateHighScore:


SetA16Bit


lda playerScore


cmp highScore


bcc +


sta highScore


+


SetA8Bit


rts








UpdateSprites: ; TODO: refactor into smaller pieces.


; This page is a good reference on SNES sprite formats:


; http://wiki.superfamicom.org/snes/show/SNES+Sprites


; It uses the same approach we're using, in which we keep a buffer of the


; sprite tables in RAM, and DMA the sprite tables to the system's OAM


; during VBlank.


; Sprite table 1 has 4 bytes per sprite, laid out as follows:


; Byte 1: xxxxxxxx x: X coordinate


; Byte 2: yyyyyyyy y: Y coordinate


; Byte 3: cccccccc c: Starting tile #


; Byte 4: vhoopppc v: vertical flip h: horizontal flip o: priority bits


; p: palette #


; Sprite table 2 has 2 bits per sprite, like so:


; bits 0,2,4,6  High bit of the sprite's xcoordinate.


; bits 1,3,5,7  Toggle Sprite size: 0  small size 1  large size


; Setting all the high bits keeps the sprites offscreen.




; Zero out the scratch space for the secondary sprite table.


ldx #0





stz spriteTableScratchStart, X


inx


cpx #numSprites


bne 




ldx #0 ; Index into sprite table 1.


ldy #0 ; Index into sprite table 2.




jsr MaybeGameOver




; Copy player coords into sprite table.


lda playerX


sta spriteTableStart, X


lda playerY


sta spriteTableStart + 1, X


lda #0


sta spriteTableStart + 2, X


; Set priority bits so that the sprite is drawn in front.


lda #%00010000


sta spriteTableStart + 3, X


lda #%11000000 ; Enable large sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




; Now add enemy ships.


sty $00 ; Save sprite table 2 index.


ldy #0 ; Index into enemyShipArray.





lda enemyShipArray, Y


cmp #0 ; If not enabled, skip to next ship.


beq +


; Update sprite table 1.


sta spriteTableStart + 2, X ; sprite number


lda enemyShipArray + 1, Y


sta spriteTableStart, X ; x


lda enemyShipArray + 2, Y


sta spriteTableStart + 1, X ; y


lda #%01000000 ; flip horizontally.


sta spriteTableStart + 3, X


; Update secondary sprite table.


phy ; Save enemyShipArray index.


ldy $00


lda #%11000000 ; Enable large sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers


sty $00


ply ; Restore enemyShipArray index.




+


.rept enemyShipSize


iny


.endr


cpy #(enemyShipArrayLength * enemyShipSize)


bne 


ldy $00 ; Restore Y to its rightful self.




; Now add shots.


sty $00 ; Save sprite table 2 index.


ldy #0 ; Index into playerShotArray.





lda playerShotArray, Y


cmp #0


beq + ; If not enabled, skip to next shot.


; Update sprite table 1.


sta spriteTableStart + 2, X ; sprite number


lda playerShotArray + 1, Y


sta spriteTableStart, X ; x


lda playerShotArray + 2, Y


sta spriteTableStart + 1, X ; y


; Update secondary sprite table.


phy ; Save playerShotArray index.


ldy $00


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers


sty $00


ply ; Restore playerShotArray index.




+


.rept shotSize


iny


.endr


cpy #((playerShotArrayLength + enemyShotArrayLength) * shotSize)


bne 


ldy $00 ; Restore Y to its rightful self.




; Now add sprites to show player health.


stz $01


lda #4


sta $02





lda $01


cmp playerHealth


beq + ; All done?


lda #10


sta spriteTableStart + 2, X ; sprite number


lda $02


sta spriteTableStart, X ; x


clc


adc #7


sta $02


lda #212


sta spriteTableStart + 1, X ; y


; Set priority bits so that the sprite is drawn in front.


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




inc $01


bra 


+




; Sprites to show player score.


lda #76


sta $00 ; xposition


lda #212


sta $01 ; yposition


stz $02 ; Don't render leading zeroes.


stz $03 ; ... not even for the second digit.


lda playerScore + 1


jsr RenderTwoDigits


lda playerScore


jsr RenderTwoDigits


inc $03 ; Render rightmost zero always.


lda #0


jsr RenderTwoDigits




; Sprites to show high score.


lda #(252  7 * 6)


sta $00


lda #212


sta $01


stz $02


stz $03


lda highScore + 1


jsr RenderTwoDigits


lda highScore


jsr RenderTwoDigits


inc $03 ; Render rightmost zero always.


lda #0


jsr RenderTwoDigits




; The little "HI" sprite next to highscore.


lda #(252  7 * 7  2)


sta spriteTableStart, X


lda #212


sta spriteTableStart + 1, X


lda #74


sta spriteTableStart + 2, X


; Set priority bits so that the sprite is drawn in front.


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




; Now clear out the unused entries in the sprite table.





cpx #spriteTable1Size


beq +


lda #1


sta spriteTableStart, X


AdvanceSpritePointers


bra 


+


rts








MaybeGameOver:


lda playerHealth


cmp #0


beq +


rts


+


; Sprites to show "Game Over" text.


lda #80 ; G


sta spriteTableStart + 2, X


lda #112


sta spriteTableStart, X


lda #104


sta spriteTableStart + 1, X


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




lda #81 ; A


sta spriteTableStart + 2, X


lda #120


sta spriteTableStart, X


lda #104


sta spriteTableStart + 1, X


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




lda #82 ; M


sta spriteTableStart + 2, X


lda #128


sta spriteTableStart, X


lda #104


sta spriteTableStart + 1, X


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




lda #83 ; E


sta spriteTableStart + 2, X


lda #136


sta spriteTableStart, X


lda #104


sta spriteTableStart + 1, X


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




lda #84 ; O


sta spriteTableStart + 2, X


lda #112


sta spriteTableStart, X


lda #114


sta spriteTableStart + 1, X


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




lda #85 ; V


sta spriteTableStart + 2, X


lda #120


sta spriteTableStart, X


lda #114


sta spriteTableStart + 1, X


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




lda #83 ; E


sta spriteTableStart + 2, X


lda #128


sta spriteTableStart, X


lda #114


sta spriteTableStart + 1, X


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers




lda #86 ; R


sta spriteTableStart + 2, X


lda #136


sta spriteTableStart, X


lda #114


sta spriteTableStart + 1, X


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers


rts








; Expects:


; A: number to display (a byte where each nybble is from 09).


; X/Y: pointing at appropriate locations into the sprite tables.


; $00: xposition to render the leftmost digit into.


; $01: yposition to render the leftmost digit into.


; $02: if set, render leading zeroes.


; $03: if set, always render the zero for the loworder digit.


;


; Updates:


; X & Y to point at the next locations in the sprite tables.


; The sprite tables, to add (up to) 2 sprites for digits.


; $00: xposition to render additional digits into.


; $02: whether to render leading zeroes.


; $04$06 (scratch).


RenderTwoDigits:


; Store the high digit in $05 and the low digit in $06.


sta $06


.rept 4


lsr


.endr


sta $05


lda $06


and #$0F


sta $06




; Render the first digit.


lda $05


jsr RenderDigit


lda $00


clc


adc #7


sta $00




; Set "render zero" for rightmost digit to true if requested.


lda $02


ora $03


sta $02




; Render the second digit.


lda $06


jsr RenderDigit


lda $00


clc


adc #7


sta $00


rts








; Expects:


; A: number to display (from 09).


; X/Y: pointing at appropriate locations into the sprite tables.


; $00: xposition to render the digit into.


; $01: yposition to render the digit into.


; $02: whether to render if the number is zero.


;


; Updates:


; X & Y to point at the next locations in the sprite tables.


; The sprite tables, to add (up to) 1 sprite for digits.


; $02: whether to render further leading zeroes.


; $04 (scratch).


RenderDigit:


sta $04


cmp #0


bne + ; Nonzero: render it regardless.


lda $02


cmp #0


bne + ; Render because "render zeroes" is set.


rts ; Nothing to render.


+


lda #1


sta $02 ; Render leading zeroes from here on out.


lda $04


clc


adc #64 ; Base index of digit sprites.


sta spriteTableStart + 2, X ; sprite number




lda $00


sta spriteTableStart, X ; x


lda $01


sta spriteTableStart + 1, X ; y




; Set priority bits so that the sprite is drawn in front.


lda #%00110000


sta spriteTableStart + 3, X


lda #%01000000 ; Enable small sprite.


sta spriteTableScratchStart, Y


AdvanceSpritePointers


rts








FillSecondarySpriteTable:


; The secondary sprite table wants 2 bits for each sprite: one to set the


; sprite's size, and one that's the high bit of the sprite's xcoordinate.


; It's annoying to deal with bitfields when thinking about business logic,


; so the spriteTableScratch array contains one byte for each sprite, in


; which the two most significant bits are the "size" and "upper x" bits.


; This function is meant to be called after UpdateWorld, and packs those


; bytes into the actual bitfield that the OAM wants for the secondary


; sprite table.


;


; The expected format of every byte in the scratch sprite table is:


; sx s = size (0 = small, 1 = large)


; x = flipped high xcoordinate (so 1 behaves like "enable").


ldx #0 ; Index into input table.


ldy #0 ; Index into output table.





stz $00 ; Current byte; filled out by a set of 4 input table entries.


.rept 4


; For each byte, the lowerorder bits correspond to the lowernumbered


; sprites; therefore we insert the current sprite's bits "at the top"


; and shift them right for each successive sprite.


lsr $00


lsr $00


lda spriteTableScratchStart, X


ora $00


sta $00


inx


.endr


lda $00


eor #%01010101


sta spriteTable2Start, Y


iny


cpx #numSprites


bne 


rts








SetBackgroundColor:


; The backgroundcolor bytes are (R, G, B), each ranging from [031].


; The palette color format is 15bit: [0bbbbbgg][gggrrrrr]




; Set the background color.


; Entry 0 corresponds to the SNES background color.


stz CGADDR




; Compute and the loworder byte and store it in CGDATA.


lda backgroundGreen


.rept 5


asl


.endr


ora backgroundRed


sta CGDATA




; Compute the highorder byte and store it in CGDATA.


lda backgroundBlue


.rept 2


asl


.endr


sta $00


lda backgroundGreen


.rept 3


lsr


.endr


ora $00


sta CGDATA


rts








VBlankHandler:


jsr VBlankCounter


jsr DMASpriteTables


rti








VBlankCounter:


; Increment a counter of how many VBlanks we've done.


; This is a 24bit counter. At 60 vblanks/second, this will take


; 77 hours to wrap around; that's good enough for me :)


inc vBlankCounter


bne +


inc vBlankCounter + 1


bne +


inc vBlankCounter + 2


+


rts








DMASpriteTables:


; Store at the base OAM address.


ldx #$0000


stx OAMADDR


; Default DMA control; destination $2104 (OAM data register).


stz DMA0CTRL


lda #$04


sta DMA0DST


; Our sprites start at $0100 in bank 0 and are #$220 bytes long.


ldx #spriteTableStart


stx DMA0SRC


stz DMA0SRCBANK


ldx #spriteTableSize


stx DMA0SIZE


; Kick off the DMA transfer.


lda #%00000001


sta DMAENABLE


rts








.ENDS








; Bank 1 is used for our graphics assets.


.BANK 1 SLOT 0


.ORG 0


.SECTION "GraphicsData"




SpriteData:


.INCBIN "sprites32.pic"


SpritePalette:


.INCBIN "sprites32.clr"




TileData:


.INCBIN "tiles.pic"


TilePalette:


.INCBIN "tiles.clr"




.ENDS








; Fill an entire bank with random numbers.


.SEED 1


.BANK 2 SLOT 0


.ORG 0


.SECTION "RandomBytes"


.DBRND 32 * 1024, 0, 255


.ENDS
