aboutsummaryrefslogblamecommitdiffstats
path: root/main.s
blob: 7957c2bd40a6226c2cf5bd046d393440b68ef3bd (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                   

                   






                 








                                                                           
 
         
                  
                  
                  
                                                                 

                  
                  


                                                      
                  
                  
                                

                  

                  
                  
     


         

            

                    




                                                                             
               
      

                                         
          
                                          
          




                                                                              
 


                                                             





                                                                            



              



              
              

              
          
              


            
                               

          



               
 




           

           
           
           
 


                                                   
 
                                                           







                                                            
                                          

                                               
          

                                                              

                                          

              

               
     
              

              
 

                      





                       








                   
 
                

             
 
          




           




                  
     


               








              
       
 
             



           
 












                        
                

                                            

                




                           
                

                      

                
          
                     


                 

               

            
                     


                 

               

            
                     


                  

               

             
                     

                
                  


               
























                     




                 
       
                  
 
          

                 







                                                                
          

                                                                 
                           
 
                     

     
















                      
 
                
                 
     
                 


          
               
          

               
          
               
          

               











                         


                     
                  

          
              
 





                   
                  



            
                                                     
            

                
               
                
 
             



















                         

              

                       

                     




















                             

                 


                










           
          

                 
          
                 
 




               


                
               
 

                











                                                                         
          














                              
          

























                                 

                

               


                


           





           
 








































                                                                         


                
                




              


             




              


             







                         





             
           





                          

                 
















                    
                 












                    






               
                    

              
                   



              
 


               

           












































                    




















































                             




              
     



              
     



              
     

              
           











                                                                     


              

                                                                     
     


                                         
                          
                                         

                             
                                         

                             
                                         
     










                                                                              
     
                                                        
                       




                                                                             


         

                
                       
     
; configuration {{{
.asciitable
MAP "-" = 1
MAP "|" = 2
MAP ":" = 3
MAP "." = 4
MAP "," = 5
MAP "'" = 6
.enda
.struct point
x db
y db
.endst
; }}}
; memory layout {{{
; rom {{{
.ROMBANKMAP
BANKSTOTAL  2
BANKSIZE    $4000
BANKS       1
BANKSIZE    $2000
BANKS       1
.ENDRO
; }}}
; ram {{{
.MEMORYMAP
DEFAULTSLOT  0
SLOTSIZE     $4000
SLOT 0       $C000
SLOTSIZE     $2000
SLOT 1       $0000 ; location doesn't matter, CHR data isn't in main memory
.ENDME

.ENUM $00
buttons_pressed DB
prev_buttons    DB
sleeping        DB
game_state      DB ; 0: menu, 1: playing, 2: redrawing, 3: paused
head_x          DW
head_y          DW
length          DB
direction       DB ; 0: up, 1: down, 2: left, 3: right
frame_skip      DB
frame_count     DB
rand_state      DB
rand_out        DB
apple           INSTANCEOF point
vram_addr_low   DB
vram_addr_high  DB
body_test_x     DB
body_test_y     DB
num_draws       DB
.ENDE
; }}}
; }}}
; prg {{{
  .bank 0
  .org $0000
; main codepath {{{
; initialization {{{
; the ppu takes two frames to initialize, so we have some time to do whatever
; initialization of our own that we want to while we wait. we choose here to
; set up cpu flags in the first frame and clear out system ram in the second
; frame (clearing out ram isn't at all necessary, but we can't do anything
; useful at this point anyway, so we may as well in order to make things more
; predictable).
RESET:
  SEI              ; disable IRQs
  CLD              ; disable decimal mode
  LDX #$40
  STX $4017.w      ; disable APU frame IRQ
  LDX #$FF
  TXS              ; Set up stack (grows down from $FF to $00, at $0100-$01FF)
  INX              ; now X = 0
  STX $2000.w      ; disable NMI (we'll enable it later once the ppu is ready)
  STX $2001.w      ; disable rendering (same)
  STX $4010.w      ; disable DMC IRQs

  ; First wait for vblank to make sure PPU is ready
- BIT $2002        ; bit 7 of $2002 is reset once vblank ends
  BPL -            ; and bit 7 is what is checked by BPL

  ; set everything in ram ($0000-$07FF) to $00, except for $0200-$02FF which
  ; is conventionally used to hold sprite attribute data. we set that range
  ; to $FE, since that value as a position moves the sprites offscreen, and
  ; when the sprites are offscreen, it doesn't matter which sprites are
  ; selected or what their attributes are
clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x
  LDA #$80
  STA $0300, x
  LDA #$7D
  STA $0400, x
  INX
  BNE clrmem

  ; initialize variables in ram
  LDA #$03
  LDX #$01
  STA head_x, x
  LDA #$04
  LDX #$01
  STA head_y, x

  LDA #$07
  STA $0201
  LDA #$00
  STA $0202

  LDA #$00
  STA $0206
  STA $020A
  STA $020E

  ; Second wait for vblank, PPU is ready after this
- BIT $2002
  BPL -

  ; now that the ppu is ready, we can start initializing it
LoadPalettes:
  LDA $2002    ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006    ; write the high byte of $3F00 address
  LDA #$00
  STA $2006    ; write the low byte of $3F00 address
  LDX #$00
LoadPalettesLoop:
  LDA palette.w, x      ;load palette byte
  STA $2007             ;write to PPU
  INX                   ;set index to next byte
  CPX #$20
  BNE LoadPalettesLoop  ;if x = $20, 32 bytes copied, all done

  LDA #%10000000   ; enable NMI interrupts
  STA $2000

  JSR end_game
; }}}
; main loop {{{
loop:
  INC sleeping
- LDA sleeping
  BNE -

  JSR read_controller1

  LDA game_state
  BNE +
  JSR start_screen_loop
  JMP loop
+ JSR game_loop
  JMP loop
; }}}
; }}}
; nmi interrupt {{{
NMI:
  PHA
  TXA
  PHA
  TYA
  PHA

  LDA game_state
  CMP #$02
  BEQ end_nmi

draw_game:
  LDA #$00
  STA $2003
  LDA #$02
  STA $4014

- LDX num_draws
  BEQ done_drawing
  DEX
  STX num_draws
  LDA #$00
  CLC
  ADC num_draws
  ADC num_draws
  ADC num_draws
  TAX
  LDA $0700, x
  STA $2006
  INX
  LDA $0700, x
  STA $2006
  INX
  LDA $0700, x
  STA $2007
  JMP -

done_drawing:
  LDA #$20
  STA $2006
  LDA #$00
  STA $2006

end_nmi:
  LDA #$00
  STA sleeping

  PLA
  TAY
  PLA
  TAX
  PLA
  RTI
; }}}
; subroutines {{{
start_screen_loop: ; {{{
  LDX rand_state
- INX
  BEQ - ; lfsr prngs have 0 as a fixed point
  STX rand_state

handle_start:
  LDA buttons_pressed
  AND #%00010000
  CMP #$00
  BEQ end_start_screen_loop
  JSR start_game

end_start_screen_loop:
  RTS ; }}}
game_loop: ; {{{
handle_up:
  LDA buttons_pressed
  AND #%00001000
  CMP #$00
  BEQ handle_down
  LDA #$00
  STA direction

handle_down:
  LDA buttons_pressed
  AND #%00000100
  CMP #$00
  BEQ handle_left
  LDA #$01
  STA direction

handle_left:
  LDA buttons_pressed
  AND #%00000010
  CMP #$00
  BEQ handle_right
  LDA #$02
  STA direction

handle_right:
  LDA buttons_pressed
  AND #%00000001
  CMP #$00
  BEQ handle_pause
  LDA #$03
  STA direction

handle_pause:
  LDA buttons_pressed
  EOR prev_buttons
  AND #%00010000
  CMP #$00
  BEQ check_pause
  LDA buttons_pressed
  AND #%00010000
  CMP #$00
  BEQ check_pause
  LDA game_state
  CMP #$01
  BEQ +
  LDA #$01
  STA game_state
  JMP check_pause
+ LDA #$03
  STA game_state

check_pause:
  LDA game_state
  CMP #$03
  BNE handle_frame
  JMP end_game_loop

handle_frame:
  LDX frame_count
  INX
  STX frame_count
  CPX frame_skip
  BPL +
  JMP draw_sprites

+ LDA #$00
  STA frame_count

set_offset:
  LDX #$F8             ; i.e., -8
  LDA direction
  AND #%00000001       ; low bit determines negative or positive
  BEQ set_axis
  LDX #$08

set_axis:
  LDY #$00
  LDA direction
  AND #%00000010       ; high bit determines which axis to change
  BNE apply_direction_horiz

apply_direction_vert:
  TXA
  CLC
  ADC (head_y), y
  INC head_y
  STA (head_y), y
  LDA (head_x), y
  INC head_x
  STA (head_x), y
  JMP check_collisions

apply_direction_horiz:
  TXA
  CLC
  ADC (head_x), y
  INC head_x
  STA (head_x), y
  LDA (head_y), y
  INC head_y
  STA (head_y), y

check_collisions
  LDA (head_x), y
  TAX
  LDA (head_y), y
  TAY

  CPX #$40
  BCC collision
  CPX #$C0
  BCS collision

  CPY #$3D
  BCC collision
  CPY #$BD
  BCS collision

  STX body_test_x
  STY body_test_y
  DEC head_x
  DEC head_y
  JSR test_body_collision
  INC head_x
  INC head_y
  LDX body_test_x
  LDY body_test_y
  CMP #$01
  BEQ collision

  CPX apple.x
  BEQ maybe_eat_apple

  JMP draw_sprites

collision:
  JSR end_game

  JMP end_game_loop

maybe_eat_apple:
  CPY apple.y
  BEQ eat_apple

  JMP draw_sprites

eat_apple:
  LDX length
  INX
  BEQ collision ; for now - this is the win condition
  STX length
  LDA speed.w, x
  STA frame_skip
  JSR new_apple
  JSR draw_score

draw_sprites:
  LDX #$00
  JSR draw_sprite_at_head
  LDA head_x
  SEC
  SBC length
  STA head_x
  LDA head_y
  SEC
  SBC length
  STA head_y
  LDX #$20
  JSR draw_sprite_at_head
  LDA head_x
  CLC
  ADC length
  STA head_x
  LDA head_y
  CLC
  ADC length
  STA head_y

end_game_loop:
  RTS ; }}}
read_controller1: ; {{{
  LDA buttons_pressed
  STA prev_buttons
  ; latch
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016

  ; clock
  LDX #$00
read_controller1_values:
  CPX #$08
  BPL end_read_controller1

  LDA $4016
  AND #%00000001
  ASL buttons_pressed
  ORA buttons_pressed
  STA buttons_pressed
  INX
  JMP read_controller1_values

end_read_controller1:
  RTS ; }}}
start_game: ; {{{
  LDA #$02
  STA game_state

  LDA #$2D
  STA $0204
  STA $0208
  STA $020C
  LDA #$40
  STA $0207
  ADC #$08
  STA $020B
  ADC #$08
  STA $020F

  LDY #$00
  LDA #$80
  STA (head_x), y
  LDA #$7D
  STA (head_y), y

  LDA #$01
  STA length
  LDA #$00
  STA direction

  LDA #20
  STA frame_skip

  JSR new_apple

  JSR draw_score

- BIT $2002
  BPL -

  LDA #%00000000
  STA $2001 ; disable rendering (since this will take longer than vblank)

  LDA #$20
  STA $2006
  LDA #$00
  STA $2006

  LDA #$20
  LDY #$07
-- LDX #$00
- STA $2007
  INX
  CPX #$20
  BNE -
  DEY
  BNE --

  LDX #$00
- LDA game_background_top.w, x
  STA $2007
  INX
  CPX #$20
  BNE -

  LDY #$10
-- LDX #$00
- LDA game_background_middle.w, x
  STA $2007
  INX
  CPX #$20
  BNE -
  DEY
  BNE --

  LDX #$00
- LDA game_background_bottom.w, x
  STA $2007
  INX
  CPX #$20
  BNE -

  LDA #$20
  LDX #$00
- STA $2007
  INX
  CPX #$20
  BNE -

  LDA #%00011000
  STA $2001 ; reenable rendering

  LDA #$01
  STA game_state
  RTS ; }}}
end_game: ; {{{
  LDA #$02
  STA game_state

  LDA #$FE
  STA $0200
  STA $0203
  STA $0204
  STA $0207
  STA $0208
  STA $020B
  STA $020C
  STA $020F

- BIT $2002
  BPL -

  LDA #%00000000
  STA $2001 ; disable rendering (since this will take longer than vblank)

  LDA #$20
  STA $2006
  LDA #$00
  STA $2006

  LDA #$20
  LDX #$0F
-- LDY #$00
- STA $2007
  INY
  CPY #$20
  BNE -
  DEX
  BNE --

  LDY #$00
- LDA intro_screen, y
  STA $2007
  INY
  CPY #$20
  BNE -

  LDA #$20
  LDX #$0E
-- LDY #$00
- STA $2007
  INY
  CPY #$20
  BNE -
  DEX
  BNE --

  LDA #%00011000
  STA $2001 ; reenable rendering

  LDA #$00
  STA game_state
  RTS ; }}}
new_apple: ; {{{
  JSR rand4
  LDA rand_out
  ASL
  ASL
  ASL
  CLC
  ADC #$40
  STA apple.x
  JSR rand4
  LDA rand_out
  ASL
  ASL
  ASL
  CLC
  ADC #$3D
  STA apple.y

  LDA apple.x
  STA body_test_x
  LDA apple.y
  STA body_test_y
  JSR test_body_collision
  CMP #$01
  BEQ new_apple

  LDA apple.y
  STA $0200
  LDA apple.x
  STA $0203

  RTS ; }}}
draw_sprite_at_head: ; {{{
  LDA #$20
  STA vram_addr_high
  LDA #$E0
  STA vram_addr_low

  LDY #$00
  LDA (head_y), y
  SEC
  SBC #$35
  LSR
  LSR
  LSR
  TAY
- CLC
  LDA vram_addr_low
  ADC #$20
  STA vram_addr_low
  LDA vram_addr_high
  ADC #$00
  STA vram_addr_high
  DEY
  BNE -

  LDY #$00
  LDA (head_x), y
  SEC
  SBC #$40
  LSR
  LSR
  LSR
  CLC
  ADC #$08
  ADC vram_addr_low
  STA vram_addr_low
  LDA vram_addr_high
  ADC #$00
  STA vram_addr_high

  TXA
  TAY
  LDA #$00
  ADC num_draws
  ADC num_draws
  ADC num_draws
  TAX
  LDA vram_addr_high
  STA $0700, x
  INX
  LDA vram_addr_low
  STA $0700, x
  INX
  TYA
  STA $0700, x

  LDX num_draws
  INX
  STX num_draws

  RTS ; }}}
draw_score: ; {{{
  LDA #$30
  STA $0205
  STA $0209
  STA $020D

  LDA length
hundreds:
  CMP #100
  BMI tens
  SEC
  SBC #100
  LDX $0205
  INX
  TAY
  TXA
  STA $0205
  TYA
  JMP hundreds
tens:
  CMP #10
  BMI ones
  SEC
  SBC #10
  LDX $0209
  INX
  TAY
  TXA
  STA $0209
  TYA
  JMP tens
ones:
  CMP #1
  BMI end_draw_score
  SEC
  SBC #1
  LDX $020D
  INX
  TAY
  TXA
  STA $020D
  TYA
  JMP ones
end_draw_score:
  RTS ; }}}
test_body_collision ; {{{
  LDA head_x
  PHA
  LDA head_y
  PHA

  LDA head_x
  SEC
  SBC length
  TAX
  INX
  TXA
  STA head_x

  LDA head_y
  SEC
  SBC length
  TAX
  INX
  TXA
  STA head_y

  LDY length

- DEY
  LDA (head_x), y
  CMP body_test_x
  BEQ maybe_collision_found
  CPY #$00
  BNE -
  JMP collision_not_found

maybe_collision_found:
  LDA (head_y), y
  CMP body_test_y
  BEQ collision_found
  CPY #$00
  BNE -
  JMP collision_not_found

collision_found:
  LDX #$01
  JMP test_body_collision_end
collision_not_found:
  LDX #$00

test_body_collision_end:
  PLA
  STA head_y
  PLA
  STA head_x
  TXA
  RTS ; }}}
rand4: ; {{{
  JSR rand1
  LDX rand_out
  JSR rand1
  TXA
  ASL
  ORA rand_out
  TAX
  JSR rand1
  TXA
  ASL
  ORA rand_out
  TAX
  JSR rand1
  TXA
  ASL
  ORA rand_out
  STA rand_out
  RTS ; }}}
rand1: ; {{{
  ; galois linear feedback shift register with taps at 8, 6, 5, and 4
  LDA rand_state
  AND #$01
  STA rand_out
  LSR rand_state
  LDA rand_out
  BEQ +
  LDA rand_state
  EOR #%10111000
  STA rand_state
+ RTS ; }}}
; }}}
; data {{{
palette: ; {{{
  .db $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F
  .db $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C
; }}}
intro_screen: ; {{{
  .asc "           SNAKE                "
; }}}
game_background_top: ; {{{
  .asc "       ,----------------.       "
; }}}
game_background_middle: ; {{{
  .asc "       |                |       "
; }}}
game_background_bottom: ; {{{
  .asc "       '----------------:       "
; }}}
speed: ; {{{
  .db 20,20,20,19,19,19,19,19,19,19,18,18,18,18,18,18,18,17,17,17,17,17,17,17,
  .db 17,16,16,16,16,16,16,16,16,16,15,15,15,15,15,15,15,15,15,14,14,14,14,14,
  .db 14,14,14,14,13,13,13,13,13,13,13,13,13,13,13,12,12,12,12,12,12,12,12,12,
  .db 12,12,11,11,11,11,11,11,11,11,11,11,11,11,10,10,10,10,10,10,10,10,10,10,
  .db 10,10,10,10,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
  .db 8,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
  .db 6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,4,4,
  .db 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,3,3,3,
  .db 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
; }}}
; }}}
  .orga $FFFA    ;first of the three vectors starts here
; interrupt vectors {{{
  .dw NMI        ;when an NMI happens (once per frame if enabled) the 
                   ;processor will jump to the label NMI:
  .dw RESET      ;when the processor first turns on or is reset, it will jump
                   ;to the label RESET:
  .dw 0          ;external interrupt IRQ is not used in this tutorial
; }}}
; }}}
; chr {{{
  .bank 1 slot 1
  .org $0000
  .incbin "sprites.chr"
; }}}