.segment "HEADER" INES_MAPPER = 0 INES_MIRROR = 1 INES_SRAM = 0 .byte 'N', 'E', 'S', $1A .byte $02 .byte $01 .byte INES_MIRROR .byte (INES_MAPPER & %11110000) .byte $0, $0, $0, $0, $0, $0, $0, $0 .segment "TILES" .incbin "background.chr" .incbin "sprite.chr" .segment "VECTORS" .word nmi .word reset .word irq .segment "CODE" reset: sei lda #%00100000 ; 8x16 sprites sta $2000 lda #0 sta $2001 sta $4015 sta $4010 lda #40 sta $4017 cld ldx #$FF txs bit $2002 : bit $2002 bpl :- lda #0 ldx #0 : sta $0000, X sta $0100, X sta $0200, X sta $0300, X sta $0400, X sta $0500, X sta $0600, X sta $0700, X inx bne :- lda #255 ldx #0 : sta oam, X inx inx inx inx bne :- : bit $2002 bpl :- lda #%10001000 sta $2000 jmp main .segment "ZEROPAGE" nmi_lock: .res 1 nmi_count: .res 1 nmi_ready: .res 1 nmt_update_len: .res 1 scroll_x: .res 1 scroll_y: .res 1 scroll_nmt: .res 1 temp: .res 1 .segment "BSS" nmt_update: .res 256 palette: .res 32 .segment "OAM" oam: .res 256 .segment "CODE" nmi: pha txa pha tya pha lda nmi_lock beq :+ jmp @nmi_end : lda #1 sta nmi_lock lda nmi_ready bne :+ jmp @ppu_update_end : cmp #2 bne :+ lda #%00000000 sta $2001 ldx #0 stx nmi_ready jmp @ppu_update_end : ldx #0 stx $2003 lda #>oam sta $4014 lda #%10001000 sta $2000 lda $2002 lda #$3F sta $2006 stx $2006 ldx #0 : lda palette, X sta $2007 inx cpx #32 bcc :- ldx #0 cpx nmt_update_len bcs @scroll @nmt_update_loop: lda nmt_update, X sta $2006 inx lda nmt_update, X sta $2006 inx lda nmt_update, X sta $2007 cpx nmt_update_len bcc @nmt_update_loop lda #0 sta nmt_update_len @scroll: lda scroll_nmt and #%00000011 ora #%10001000 sta $2000 lda scroll_x sta $2005 lda scroll_y sta $2005 lda #%00011110 sta $2001 ldx #0 stx nmi_ready @ppu_update_end: lda #0 sta nmi_lock @nmi_end: pla tay pla tax pla rti .segment "CODE" irq: rti .segment "CODE" ppu_update: lda #1 sta nmi_ready : lda nmi_ready bne :- rts ppu_skip: lda nmi_count : cmp nmi_count beq :- rts ppu_off: lda #2 sta nmi_ready : lda nmi_ready bne :- rts ppu_address_tile: lda $2002 tya lsr lsr lsr ora #$20 sta $2006 tya asl asl asl asl asl sta temp txa ora temp sta $2006 rts ppu_update_tile: pha txa pha ldx nmt_update_len tya lsr lsr lsr ora #$20 sta nmt_update, X inx tya asl asl asl asl asl sta temp pla ora temp sta nmt_update, X inx pla sta nmt_update, X inx stx nmt_update_len rts ppu_update_byte: pha tya pha ldy nmt_update_len txa sta nmt_update, Y iny pla sta nmt_update, Y iny pla sta nmt_update, Y iny sty nmt_update_len rts PAD_A = $01 PAD_B = $02 PAD_SELECT = $04 PAD_START = $08 PAD_U = $10 PAD_D = $20 PAD_L = $40 PAD_R = $80 .segment "ZEROPAGE" gamepad: .res 1 .segment "CODE" ; gamepad_poll: this reads the gamepad state into the variable labelled "gamepad" ; This only reads the first gamepad, and also if DPCM samples are played they can ; conflict with gamepad reading, which may give incorrect results. gamepad_poll: ; strobe the gamepad to latch current button state lda #1 sta $4016 lda #0 sta $4016 ; read 8 bytes from the interface at $4016 ldx #8 : pha lda $4016 ; combine low two bits and store in carry bit and #%00000011 cmp #%00000001 pla ; rotate carry into gamepad variable ror dex bne :- sta gamepad rts .segment "RODATA" level: .byte 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1 .byte 0,1,0,0,1,0,0,0,0,0,0,0,1,1,1,1 .byte 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1 .byte 0,1,0,0,1,0,0,0,0,0,0,0,1,1,1,1 .byte 0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0 .byte 0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0 .byte 0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0 .byte 0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 metatiles: .byte 0,0,0,0,0 .byte 3,4,5,6,1 example_palette: .byte $0F,$00,$3D,$20 ; greyscale .byte $0F,$09,$1A,$16 ; grass .byte $0F,$15,$26,$37 ; bg0 purple/pink .byte $0F,$09,$19,$29 ; bg1 green .byte $0F,$01,$11,$21 ; bg2 blue .byte $0F,$00,$10,$30 ; bg3 greyscale .byte $0F,$18,$28,$38 ; sp0 yellow .byte $0F,$14,$24,$34 ; sp1 purple .byte $0F,$1B,$2B,$3B ; sp2 teal .byte $0F,$12,$22,$32 ; sp3 marine .segment "ZEROPAGE" buttons: .res 1 vel_x: .res 1 vel_y: .res 1 cursor_x: .res 1 cursor_y: .res 1 temp_x: .res 1 temp_y: .res 1 temp_mul: .res 1 var_m: .res 1 var_n: .res 1 var_o: .res 1 var_p: .res 1 jump_pressed_last_frame: .res 1 frame_counter: .res 1 .segment "CODE" main: ldx #0 : lda example_palette, X sta palette, X inx cpx #32 bcc :- jsr setup_background jsr ppu_update lda #$40 sta cursor_x sta cursor_y lda #0 sta frame_counter jsr init_objects @loop: lda frame_counter clc adc #1 sta frame_counter jsr controller jsr movement @draw: jsr update_background jsr ppu_update jmp @loop init_objects: lda #0 sta var_n ldx #0 : lda var_n clc adc #8 sta var_n sta oam+0, X ; Set Y position sta oam+3, X ; Set X position txa lsr lsr sta oam+1, X inx inx inx inx cpx #(8*4) bne :- lda #0 sta vel_x sta vel_y rts draw: @end: rts movement: ; Horizontal velocity ldx #0 jsr btn_right cmp #0 beq :+ ldx #1 lda #3 sta vel_x : jsr btn_left cmp #0 beq :+ ldx #1 lda #253 sta vel_x : cpx #1 beq @end lda frame_counter and #1 cmp #0 bne @end lda #0 cmp vel_x beq @end ; If vel_x = 0, skip decay ; If vel_x > 0, decrement bpl :+ ldx vel_x dex stx vel_x jmp @end : ; Else if vel_x < 0, increment ldx vel_x inx stx vel_x @end: ; Jump jsr btn_a cmp #0 beq @fail_jump ; If jump not pressed, forget it lda jump_pressed_last_frame cmp #1 beq @fail_jump ; If jump last frame, forget it ; Jump newly pressed this frame lda #250 sta vel_y @fail_jump: jsr btn_a sta jump_pressed_last_frame ; Gravity lda frame_counter and #1 cmp #0 bne :+ lda vel_y clc adc #1 ; Add 2 to velocity sta vel_y : ; Apply Y velocity lda oam+0 clc adc vel_y sta oam+0 ; Keep grounded cmp #200 bcc :+ lda #0 sta vel_y lda #200 sta oam+0 : ; Bonk lda #8 cmp oam+0 bcc :+ lda #0 sta vel_y lda #8 sta oam+0 : ; Apply X velocity lda oam+3 clc adc vel_x sta oam+3 rts update_background: rts ;lda $2002 ;lda #$20 ;sta $2006 ;lda #$00 ;sta $2006 ldx #1 ldy #1 jsr ppu_update_tile lda #1 sta $2007 rts setup_background: lda $2002 ; reset latch lda #$20 sta $2006 lda #$00 sta $2006 ; empty nametable lda #0 ldy #30 ; 30 rows : ldx #32 ; 32 columns : sta $2007 dex bne :- dey bne :-- lda $2002 lda #$20 sta $2006 lda #$00 sta $2006 ldy #0 @row_start: ldx #0 : txa pha ; index is (Y * 16) + X stx temp_x lda #16 jsr mul_y clc adc temp_x tax lda level, X ldx #5 jsr mul_x tax lda metatiles+0, X sta $2007 lda metatiles+1, X sta $2007 pla tax inx cpx #16 bne :- ; again ldx #0 : txa pha ; index is (Y * 16) + X stx temp_x lda #16 jsr mul_y clc adc temp_x tax lda level, X ldx #5 jsr mul_x tax lda metatiles+2, X sta $2007 lda metatiles+3, X sta $2007 pla tax inx cpx #16 bne :- iny cpy #15 bne @row_start ; clear attributes lda #0 ldx #64 ; 64 bytes : lda #%11100100 sta $2007 dex bne :- rts lda $2002 ; reset latch lda #$20 sta $2006 lda #$00 sta $2006 ; empty nametable lda #0 ldy #30 ; 30 rows : ldx #32 ; 32 columns : sta $2007 dex bne :- dey bne :-- ; set all attributes to 0 lda #0 ldx #64 ; 64 bytes : txa and #%00000011 asl asl asl asl sta temp txa and #%00000011 ora temp lda #0 sta $2007 dex bne :- ; fill in an area in the middle with 1/2 checkerboard lda #1 ldy #0 ; start at row 8 : pha ; temporarily store A, it will be clobbered by ppu_address_tile routine ldx #8 ; start at column 8 jsr ppu_address_tile pla ; recover A ldx #8 : sta $2007 eor #$3 inx cpx #(32-8) bcc :- eor #$3 iny cpy #(30-8) bcc :-- lda #$24 sta $2006 lda #$00 sta $2006 lda #$00 ldy #30 : ldx #32 : sta $2007 clc adc #1 and #3 dex bne :- clc adc #1 and #3 dey bne :-- lda #0 ldy #4 : ldx #16 : sta $2007 dex bne :- clc adc #%01010101 dey bne :-- rts mul_x: cmp #0 beq @zero cpx #0 beq @zero sta temp_mul txa pha lda #0 : clc adc temp_mul dex bne :- sta temp_mul pla tax lda temp_mul rts @zero: lda #0 rts mul_y: cmp #0 beq @zero cpy #0 beq @zero sta temp_mul tya pha lda #0 : clc adc temp_mul dey bne :- sta temp_mul pla tay lda temp_mul rts @zero: lda #0 rts controller: lda #1 sta $4016 sta buttons lda #0 sta $4016 : lda $4016 lsr rol buttons bcc :- rts btn_right: lda buttons and #%00000001 rts btn_left: lda buttons and #%00000010 lsr rts btn_down: lda buttons and #%00000100 lsr lsr rts btn_up: lda buttons and #%00001000 lsr lsr lsr rts btn_start: lda buttons and #%00010000 lsr lsr lsr lsr rts btn_select: lda buttons and #%00100000 lsr lsr lsr lsr lsr rts btn_b: lda buttons and #%01000000 lsr lsr lsr lsr lsr lsr rts btn_a: lda buttons and #%10000000 lsr lsr lsr lsr lsr lsr lsr rts