u/supergoat559

▲ 128 r/beneater

Dinosaur Game Version2!

I added a smooth jumping animation to my 6502 dinosaur game. I also made a git repository to store the code so you can try it for yourself!. The repository should be available at https://github.com/supergoat559/6502DinosaurGame/tree/main

This is the first git repository I've made so feel free to let me know what best practices are for that.

u/supergoat559 — 13 days ago

Dinosaur Game for the 6502

I made a simple version of the chrome dinosaur game for the 6502. The code is attached below if you want to try it out.

https://reddit.com/link/1u29dbc/video/78rduhtbsh6h1/player

PORTB = $6000
PORTA = $6001
DDRB = $6002
DDRA = $6003
PCR = $600c
IFR = $600d
IER = $600e


digit_stack = $0200 ; 3 bytes


Sprite_Pointer = $0000 ; 2 bytes
Char_Location_Pointer = $0002 ; 1 byte
Delay_Amount_Pointer = $0003 ; 1 byte
Cactus_Location = $0004  ;1 byte
Score = $0005 ; 1 byte



E  = %10000000
RW = %01000000
RS = %00100000


Dino_Char1 =  %00000000
Dino_Char2 =  %00000001
Cactus_Char = %00000010
Blank_Char =  %00100000


  .org $8000


reset:
  ldx #$ff ;initialize stack pointer
  txs
  cli


  lda #$82 ; set interrupt enable for CA1 pin
  sta IER


  lda #$00 ; set CA1 to be negative edge triggered
  sta PCR


  lda #%11111111 ; Set all pins on port B to output
  sta DDRB


  lda #%11100000 ; Set top 3 pins on port A to output
  sta DDRA


  lda #%00111000 ; Set 8-bit mode; 2-line display; 5x8 font
  jsr lcd_instruction


  lda #%00001100 ; Display on; cursor on; blink off
  jsr lcd_instruction


  lda #%00000110 ; Increment and shift cursor; don't shift display
  jsr lcd_instruction


  lda #%00000001 ; Clear display
  jsr lcd_instruction


  lda #<Dino_Walk_1
  sta Sprite_Pointer
  lda #>Dino_Walk_2
  sta Sprite_Pointer + 1
  lda #Dino_Char1
  sta Char_Location_Pointer
  jsr make_char      ; make a custom dino sprite character at custom char location 0000


  lda #<Dino_Walk_2
  sta Sprite_Pointer
  lda #>Dino_Walk_2
  sta Sprite_Pointer + 1
  lda #Dino_Char2
  sta Char_Location_Pointer
  jsr make_char      ; make the second dino sprite character at custom char location 0001


  lda #<Cactus_1
  sta Sprite_Pointer
  lda #>Cactus_1
  sta Sprite_Pointer + 1
  lda #Cactus_Char
  sta Char_Location_Pointer
  jsr make_char           ;make cactus sprite at location 0010



  lda #$0
  sta Score    ;set score to 0 and print it
  ldx #$d
  ldy #$0
  jsr lcd_goto
  lda Score
  jsr print_int
  


  lda #$f
  sta Cactus_Location  ;set cactus location to the end of the screen



  lda #$ff                  ;set delay amount to ff
  sta Delay_Amount_Pointer
loop:


  lda Cactus_Location
  cmp #$0
  beq death      ;check if cactus is colliding with the dino
  


  ldx #$1
  ldy #$1
  jsr lcd_goto    ;move cursor back to postion


  lda #Dino_Char1     ;print frame 1
  jsr print_char


  jsr delay


  ldx #$1
  ldy #$1        ; mvoe cursor back to position
  jsr lcd_goto
  
  lda #Dino_Char2     ;print frame 2
  jsr print_char


  jsr delay


  jsr move_cactus



  jmp loop


death:
  sei   ;disable interrupts
  ldx #$3
  ldy #$0
  jsr lcd_goto
  ldx #$0
print_death_message:
  lda Death_Message, x
  beq death_loop
  jsr print_char
  inx
  jmp print_death_message



death_loop:
  jmp death_loop




Dino_Walk_1:
  .byte %00000000, %00000000, %00000011, %00000101, %00010111, %00011110, %00001110, %00000100


Dino_Walk_2:
  .byte %00000000, %00000000, %00000011, %00000101, %00010111, %00011110, %00001110, %00000010


Cactus_1:
  .byte %00000000, %00000100, %00000101, %00010111, %00010100, %00011100, %00000100, %00000100


Death_Message:
  .asciiz "You Lost"



move_cactus:
  lda Cactus_Location
  cmp #$f
  beq clear_first_frame
  lda Cactus_Location
  tax
  inx
  ldy #$1
  jsr lcd_goto
  lda #Blank_Char
  jsr print_char         ;clear last cactus position


print_cactus:
  lda Cactus_Location
  pha
  tax
  ldy #$1
  jsr lcd_goto
  lda #Cactus_Char
  jsr print_char   ;print cactus


  pla
  cmp #$0
  beq reset_location
decrement:
  tax
  dex              ;decrement cactus location
  txa
  sta Cactus_Location
  rts


reset_location:
  ldx Score
  inx
  txa
  sta Score    ;increment score
  ldx #$d
  ldy #$0
  jsr lcd_goto
  lda Score
  jsr print_int  ;print score


  lda Delay_Amount_Pointer
  sbc #$5
  sta Delay_Amount_Pointer   ;reduce delay to speed up the game


  lda #$10     
  jmp decrement


clear_first_frame:
  ldx #$0
  ldy #$1
  jsr lcd_goto
  lda #Blank_Char
  jsr print_char
  jmp print_cactus



; =================================================================
; Subroutine: print_int
; Takes an 8-bit unsigned integer in A and prints it to the LCD.
; Input: A = Integer to print (0 - 255)
; Clobbers: A, X, Y
; =================================================================
print_int:
    ; Handle the special case where the input number is exactly 0
    cmp #0
    bne not_zero
    lda #'0'             ; ASCII for '0'
    jsr print_char
    rts


not_zero:
  ldx #0               ; X will keep track of how many digits we find


divide_loop:
  cmp #0               ; Is our remaining value 0?
  beq print_digits    ; If yes, we've extracted all digits


    ; Divide A by 10. 
    ; In 6502 assembly, we do this by repeatedly subtracting 10.
    ldy #0               ; Y will hold the Quotient (Value / 10)
sub_10:
  cmp #10
  bcc done_sub        ; If A < 10, division for this step is done
  sbc #10              ; Subtract 10 (Note: Carry is already set due to cmp)
  iny                  ; Increment Quotient
  jmp sub_10


done_sub:
  ; After the subtraction loop:
  ; A contains the Remainder (the individual digit, 0-9)
  ; Y contains the Quotient (the remaining value for the next pass)
    
  pha                  ; Save the remainder (digit) on the CPU stack temporarily
  tya                  ; Move the Quotient back into A for the next round
  inx                  ; Increment our digit counter
  jmp divide_loop


print_digits:
  ; Our digits are currently stored backward on the CPU stack.
  ; Pull them off one by one and send them to the LCD.
  pla                  ; Pull the highest-magnitude digit (e.g., hundreds, tens, then ones)
  clc
  adc #'0'             ; Convert raw number (0-9) to ASCII character code ('0'-'9')
  jsr print_char       ; Print it to the screen
  dex
  bne print_digits    ; Repeat until all extracted digits are printed


  rts



lcd_wait:
  pha
  lda #%00000000 ;set port b as input
  sta DDRB
lcd_busy:
  lda #RW
  sta PORTA
  lda #(RW | E)
  sta PORTA
  lda PORTB
  and #%10000000
  bne lcd_busy


  lda #RW
  sta PORTA
  lda #%11111111 ; set portb back to output
  sta DDRB
  pla
  rts



lcd_instruction:
  jsr lcd_wait
  sta PORTB
  lda #0         ; Clear RS/RW/E bits
  sta PORTA
  lda #E         ; Set E bit to send instruction
  sta PORTA
  lda #0         ; Clear RS/RW/E bits
  sta PORTA
  rts


print_char:
  jsr lcd_wait
  sta PORTB
  lda #RS         ; Set RS; Clear RW/E bits
  sta PORTA
  lda #(RS | E)   ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA
  rts


make_char:
  jsr lcd_wait
  lda Char_Location_Pointer
  asl a
  asl a
  asl a
  ora #%01000000
  jsr lcd_instruction
  ldy #0            ; Start index at 0
load_sprite_loop:
  lda (Sprite_Pointer), y ; Load the byte at (Sprite + X)
  jsr print_char    ; Send the byte as data to the LCD
  iny               ; Move to the next byte
  cpy #8            ; Have we sent all 8 bytes?
  bne load_sprite_loop ; If not, loop back


  lda #%10000000     ; Return Home (Resets address counter back to DDRAM)
  jsr lcd_instruction
  rts


; =================================================================
; Subroutine: lcd_goto
; Moves the cursor to an arbitrary position.
; Input: X register = Column (0 to 15)
;        Y register = Row (0 for Line 1, 1 for Line 2)
; Clobbers: A
; =================================================================
lcd_goto:
    cpy #0              ; Is it row 0 (Line 1)?
    beq .line1          ; If yes, skip adding the row offset
    
    ; If row is 1 (Line 2), start with base address $40
    txa                 ; Move column index to A
    clc
    adc #$40            ; Add $40 (Line 2 offset)
    jmp .send_cmd


.line1:
    txa                 ; For line 1, address is just the column index


.send_cmd:
    ora #%10000000      ; CRITICAL: Set Bit 7 to turn this into a DDRAM command
    jsr lcd_instruction ; Send the position command to the LCD
    rts


delay:
  pha
  txa
  pha
  tya
  pha
  ldy Delay_Amount_Pointer
  ldx #$ff
delay_loop:
  dex
  bne delay_loop
  dey
  bne delay_loop
  pla
  tay
  pla
  tax
  pla
  rts


nmi:
irq:
  pha
  txa
  pha
  tya
  pha


  ldx #$1
  ldy #$1
  jsr lcd_goto
  lda #%00100000   ;blank character
  jsr print_char   ;print the blank character at the normal dino position, clearing it


  ldx #$1
  ldy #$0
  jsr lcd_goto
  lda #%00000000 
  jsr print_char  ;print dino in upper position


exit_irq:



  jsr delay


  jsr move_cactus


  jsr delay
  jsr delay


  jsr move_cactus


  jsr delay


  bit PORTA ; clear interrupt


  ldx #$1
  ldy #$0
  jsr lcd_goto
  lda #%00100000
  jsr print_char   ;clear upper position


  pla
  tay
  pla
  tax
  pla
  rti


  .org $fffa
  .word nmi
  .word reset
  .word irq


  ;to compile use the command vasm6502_oldstyle -Fbin -dotdir -o Dino.bin Dinosaur_game.65C02
  ;to write to eeprom use minipro -p AT28C256 -w Dino.binPORTB = $6000
PORTA = $6001
DDRB = $6002
DDRA = $6003
PCR = $600c
IFR = $600d
IER = $600e


digit_stack = $0200 ; 3 bytes


Sprite_Pointer = $0000 ; 2 bytes
Char_Location_Pointer = $0002 ; 1 byte
Delay_Amount_Pointer = $0003 ; 1 byte
Cactus_Location = $0004  ;1 byte
Score = $0005 ; 1 byte



E  = %10000000
RW = %01000000
RS = %00100000


Dino_Char1 =  %00000000
Dino_Char2 =  %00000001
Cactus_Char = %00000010
Blank_Char =  %00100000


  .org $8000


reset:
  ldx #$ff ;initialize stack pointer
  txs
  cli


  lda #$82 ; set interrupt enable for CA1 pin
  sta IER


  lda #$00 ; set CA1 to be negative edge triggered
  sta PCR


  lda #%11111111 ; Set all pins on port B to output
  sta DDRB


  lda #%11100000 ; Set top 3 pins on port A to output
  sta DDRA


  lda #%00111000 ; Set 8-bit mode; 2-line display; 5x8 font
  jsr lcd_instruction


  lda #%00001100 ; Display on; cursor on; blink off
  jsr lcd_instruction


  lda #%00000110 ; Increment and shift cursor; don't shift display
  jsr lcd_instruction


  lda #%00000001 ; Clear display
  jsr lcd_instruction


  lda #<Dino_Walk_1
  sta Sprite_Pointer
  lda #>Dino_Walk_2
  sta Sprite_Pointer + 1
  lda #Dino_Char1
  sta Char_Location_Pointer
  jsr make_char      ; make a custom dino sprite character at custom char location 0000


  lda #<Dino_Walk_2
  sta Sprite_Pointer
  lda #>Dino_Walk_2
  sta Sprite_Pointer + 1
  lda #Dino_Char2
  sta Char_Location_Pointer
  jsr make_char      ; make the second dino sprite character at custom char location 0001


  lda #<Cactus_1
  sta Sprite_Pointer
  lda #>Cactus_1
  sta Sprite_Pointer + 1
  lda #Cactus_Char
  sta Char_Location_Pointer
  jsr make_char           ;make cactus sprite at location 0010



  lda #$0
  sta Score    ;set score to 0 and print it
  ldx #$d
  ldy #$0
  jsr lcd_goto
  lda Score
  jsr print_int
  


  lda #$f
  sta Cactus_Location  ;set cactus location to the end of the screen



  lda #$ff                  ;set delay amount to ff
  sta Delay_Amount_Pointer
loop:


  lda Cactus_Location
  cmp #$0
  beq death      ;check if cactus is colliding with the dino
  


  ldx #$1
  ldy #$1
  jsr lcd_goto    ;move cursor back to postion


  lda #Dino_Char1     ;print frame 1
  jsr print_char


  jsr delay


  ldx #$1
  ldy #$1        ; mvoe cursor back to position
  jsr lcd_goto
  
  lda #Dino_Char2     ;print frame 2
  jsr print_char


  jsr delay


  jsr move_cactus



  jmp loop


death:
  sei   ;disable interrupts
  ldx #$3
  ldy #$0
  jsr lcd_goto
  ldx #$0
print_death_message:
  lda Death_Message, x
  beq death_loop
  jsr print_char
  inx
  jmp print_death_message



death_loop:
  jmp death_loop




Dino_Walk_1:
  .byte %00000000, %00000000, %00000011, %00000101, %00010111, %00011110, %00001110, %00000100


Dino_Walk_2:
  .byte %00000000, %00000000, %00000011, %00000101, %00010111, %00011110, %00001110, %00000010


Cactus_1:
  .byte %00000000, %00000100, %00000101, %00010111, %00010100, %00011100, %00000100, %00000100


Death_Message:
  .asciiz "You Lost"



move_cactus:
  lda Cactus_Location
  cmp #$f
  beq clear_first_frame
  lda Cactus_Location
  tax
  inx
  ldy #$1
  jsr lcd_goto
  lda #Blank_Char
  jsr print_char         ;clear last cactus position


print_cactus:
  lda Cactus_Location
  pha
  tax
  ldy #$1
  jsr lcd_goto
  lda #Cactus_Char
  jsr print_char   ;print cactus


  pla
  cmp #$0
  beq reset_location
decrement:
  tax
  dex              ;decrement cactus location
  txa
  sta Cactus_Location
  rts


reset_location:
  ldx Score
  inx
  txa
  sta Score    ;increment score
  ldx #$d
  ldy #$0
  jsr lcd_goto
  lda Score
  jsr print_int  ;print score


  lda Delay_Amount_Pointer
  sbc #$5
  sta Delay_Amount_Pointer   ;reduce delay to speed up the game


  lda #$10     
  jmp decrement


clear_first_frame:
  ldx #$0
  ldy #$1
  jsr lcd_goto
  lda #Blank_Char
  jsr print_char
  jmp print_cactus



; =================================================================
; Subroutine: print_int
; Takes an 8-bit unsigned integer in A and prints it to the LCD.
; Input: A = Integer to print (0 - 255)
; Clobbers: A, X, Y
; =================================================================
print_int:
    ; Handle the special case where the input number is exactly 0
    cmp #0
    bne not_zero
    lda #'0'             ; ASCII for '0'
    jsr print_char
    rts


not_zero:
  ldx #0               ; X will keep track of how many digits we find


divide_loop:
  cmp #0               ; Is our remaining value 0?
  beq print_digits    ; If yes, we've extracted all digits


    ; Divide A by 10. 
    ; In 6502 assembly, we do this by repeatedly subtracting 10.
    ldy #0               ; Y will hold the Quotient (Value / 10)
sub_10:
  cmp #10
  bcc done_sub        ; If A < 10, division for this step is done
  sbc #10              ; Subtract 10 (Note: Carry is already set due to cmp)
  iny                  ; Increment Quotient
  jmp sub_10


done_sub:
  ; After the subtraction loop:
  ; A contains the Remainder (the individual digit, 0-9)
  ; Y contains the Quotient (the remaining value for the next pass)
    
  pha                  ; Save the remainder (digit) on the CPU stack temporarily
  tya                  ; Move the Quotient back into A for the next round
  inx                  ; Increment our digit counter
  jmp divide_loop


print_digits:
  ; Our digits are currently stored backward on the CPU stack.
  ; Pull them off one by one and send them to the LCD.
  pla                  ; Pull the highest-magnitude digit (e.g., hundreds, tens, then ones)
  clc
  adc #'0'             ; Convert raw number (0-9) to ASCII character code ('0'-'9')
  jsr print_char       ; Print it to the screen
  dex
  bne print_digits    ; Repeat until all extracted digits are printed


  rts



lcd_wait:
  pha
  lda #%00000000 ;set port b as input
  sta DDRB
lcd_busy:
  lda #RW
  sta PORTA
  lda #(RW | E)
  sta PORTA
  lda PORTB
  and #%10000000
  bne lcd_busy


  lda #RW
  sta PORTA
  lda #%11111111 ; set portb back to output
  sta DDRB
  pla
  rts



lcd_instruction:
  jsr lcd_wait
  sta PORTB
  lda #0         ; Clear RS/RW/E bits
  sta PORTA
  lda #E         ; Set E bit to send instruction
  sta PORTA
  lda #0         ; Clear RS/RW/E bits
  sta PORTA
  rts


print_char:
  jsr lcd_wait
  sta PORTB
  lda #RS         ; Set RS; Clear RW/E bits
  sta PORTA
  lda #(RS | E)   ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA
  rts


make_char:
  jsr lcd_wait
  lda Char_Location_Pointer
  asl a
  asl a
  asl a
  ora #%01000000
  jsr lcd_instruction
  ldy #0            ; Start index at 0
load_sprite_loop:
  lda (Sprite_Pointer), y ; Load the byte at (Sprite + X)
  jsr print_char    ; Send the byte as data to the LCD
  iny               ; Move to the next byte
  cpy #8            ; Have we sent all 8 bytes?
  bne load_sprite_loop ; If not, loop back


  lda #%10000000     ; Return Home (Resets address counter back to DDRAM)
  jsr lcd_instruction
  rts


; =================================================================
; Subroutine: lcd_goto
; Moves the cursor to an arbitrary position.
; Input: X register = Column (0 to 15)
;        Y register = Row (0 for Line 1, 1 for Line 2)
; Clobbers: A
; =================================================================
lcd_goto:
    cpy #0              ; Is it row 0 (Line 1)?
    beq .line1          ; If yes, skip adding the row offset
    
    ; If row is 1 (Line 2), start with base address $40
    txa                 ; Move column index to A
    clc
    adc #$40            ; Add $40 (Line 2 offset)
    jmp .send_cmd


.line1:
    txa                 ; For line 1, address is just the column index


.send_cmd:
    ora #%10000000      ; CRITICAL: Set Bit 7 to turn this into a DDRAM command
    jsr lcd_instruction ; Send the position command to the LCD
    rts


delay:
  pha
  txa
  pha
  tya
  pha
  ldy Delay_Amount_Pointer
  ldx #$ff
delay_loop:
  dex
  bne delay_loop
  dey
  bne delay_loop
  pla
  tay
  pla
  tax
  pla
  rts


nmi:
irq:
  pha
  txa
  pha
  tya
  pha


  ldx #$1
  ldy #$1
  jsr lcd_goto
  lda #%00100000   ;blank character
  jsr print_char   ;print the blank character at the normal dino position, clearing it


  ldx #$1
  ldy #$0
  jsr lcd_goto
  lda #%00000000 
  jsr print_char  ;print dino in upper position


exit_irq:



  jsr delay


  jsr move_cactus


  jsr delay
  jsr delay


  jsr move_cactus


  jsr delay


  bit PORTA ; clear interrupt


  ldx #$1
  ldy #$0
  jsr lcd_goto
  lda #%00100000
  jsr print_char   ;clear upper position


  pla
  tay
  pla
  tax
  pla
  rti


  .org $fffa
  .word nmi
  .word reset
  .word irq


  ;to compile use the command vasm6502_oldstyle -Fbin -dotdir -o Dino.bin Dinosaur_game.65C02
  ;to write to eeprom use minipro -p AT28C256 -w Dino.bin
reddit.com
u/supergoat559 — 26 days ago