Friday, March 14, 2025

Fifth Posting: LAB 3

 In this lab  we are making a "program". That means that we have a total freedom of what we can write on the 6502 emulator and write about it on blog.

As my hobby is a video game i went on decided to create a game that might be a bit interesting. As soon as i saw this lab's topic i thought of snake game, the game that everyone knows how to play.

But just making the snake game might be a bit boring, i want to extend this game into something more challenging as an extra.

So this is my basic idea:
So lets make an snake game and i believe  that should be good enough to fulfill all the requirements for the Lab so lets do that and maybe time to time we can extend this to something more fun....


Making muti-dimensional snake game where you play one snake with just normal arrow movement.

and at the same-time you play second snake in same screen??? But lets complete basic snake game first.


The basic idea is there but it will be more developed as I progress through out this lab. So lets dive into make our first snake.

Before we jump on making the snake game i guess there are a lot of things that we might need to think about

    - pixeled color location should be move-able when ever we press movement keys (as its changing locations as we press movement keys)

    - As the snake "eats" the target pixel then the original move-able pixel increase in length.

    - And this length should be also moving towards the direction that is following with movement key. So there should be head and tail.

    - Some of the movement should not be done. for an example in snake game the snake cannot move opposite direction right away in same row or column. It usually takes a full cycle of going right,down,left or left,right,right or right down or left down and so on.. There are many ways to go to opposite direction and we have to figure out what can be the direct reaction(output) when the condition is not met.

 I guess those are pretty much it that we might have to consider prior to creating one when it comes to "making game" so now let dive into coding this.


So first i believe that we nee the snake, since we have black back ground ready as default why don't we start with three dots



This is great! Now we can see the three dots on the top left of the emulator
LDX #$00 initializes X register to 0
Then inside of DrawLoop we Load our color, LDA #$FF
Then we increment X with INC
CPX #$03 to comp02
are Xposition to 3
Then if X is not 3 yet, loop back and write more pixels.

So now we can see that the white pixels are wirtten on ($FF) $0200, $0201, $02


Now we see that we have three dots, what could be the next?
Maybe making the pixles moveable with our keyboard?
When we are moving those we have to think about few things
-    We are not making it as "you have to continously press the moving key, pressing once will move the "snake"
-    We are moving whole pixels, not one or two minimum three and maximum as much as player stacks the target "apple"
-   Our moving key for this snake will be W,A,S,D



So now that we have displayed thre white pixels on the screen, lets start bringing them to life by introducting movement. The idea is to simulate a "snake" moving across the screen. At this stage, the snake moves automatically from left to right.

snakeX: .res 1      ; reserve 1 byte for snakeX
snakeY: .res 1      ; reserve 1 byte for snakeY
offset: .res 1      ; reserve 1 byte for offset

Start:
    LDA #$00
    STA snakeX
    STA snakeY

loop:
    JSR clearSnake
    INC snakeX
    JSR drawSnake
    JSR delay
    JMP loop

clearSnake:
    LDX snakeX
    LDY #$00
clearLoop:
    LDA #$00
    STA $0200,X
    INX
    INY
    CPY #$03
    BNE clearLoop
    RTS

drawSnake:
    LDX snakeX
    LDY #$00
drawLoop:
    LDA #$FF
    STA $0200,X
    INX
    INY
    CPY #$03
    BNE drawLoop
    RTS

delay:
    LDX #$FF
delayLoop:
    DEX
    BNE delayLoop
    RTS

    BRK



in here we enhanced our simple program from displaying just three pixels to animating basic horizontal movement.
Initialization: we have defined two variables snakeX, snakeY to track the snake's position and one temp vars offset to help calculate screen addresses.
initially set snake's starting position at the top left corner of the screen (0,0)

Main Loop: erase three pixels that is considered as snake's previous position by writing black as well ($00) and move snake by incrementing snakeXposition by one, moving the snake to the right. And draw three consecutive white pixels $FF starting from new snake's horizontal position and delay to make it a bit visible.

Next we implement basic movement with wasd, make sure to increase speed this time for making smoother movements.

define snakeHeadL     $10 ; low byte screen addr
define snakeHeadH     $11 ; high byte screen addr
define snakeBodyStart $12 ; first body segment (2 segments total)
define snakeDirection $02
define movingUp       1
define movingRight    2
define movingDown     4
define movingLeft     8

define ASCII_w        $77
define ASCII_a        $61
define ASCII_s        $73
define ASCII_d        $64

define sysLastKey     $ff
define SCREEN         $0200

  jsr initSnake

mainLoop:
  jsr readKeys
  jsr updateSnake
  jsr clearScreen
  jsr drawSnake
  jsr delay
  jmp mainLoop

initSnake:
  lda #movingRight
  sta snakeDirection

  lda #$00
  sta snakeHeadL
  lda #$03
  sta snakeHeadH

  ; body segment positions (behind head initially)
  lda #$1F
  sta $12       ; segment 1 low
  lda #$02
  sta $13       ; segment 1 high

  lda #$1E
  sta $14       ; segment 2 low
  lda #$02
  sta $15       ; segment 2 high
  rts

readKeys:
  lda sysLastKey
  cmp #ASCII_w
  beq upKey
  cmp #ASCII_d
  beq rightKey
  cmp #ASCII_s
  beq downKey
  cmp #ASCII_a
  beq leftKey
  rts

upKey:
  lda #movingDown
  bit snakeDirection
  bne illegalMove
  lda #movingUp
  sta snakeDirection
  rts

rightKey:
  lda #movingLeft
  bit snakeDirection
  bne illegalMove
  lda #movingRight
  sta snakeDirection
  rts

downKey:
  lda #movingUp
  bit snakeDirection
  bne illegalMove
  lda #movingDown
  sta snakeDirection
  rts

leftKey:
  lda #movingRight
  bit snakeDirection
  bne illegalMove
  lda #movingLeft
  sta snakeDirection
  rts

illegalMove:
  rts

updateSnake:
; shift body positions first (segments follow head)
  lda $14
  sta $12
  lda $15
  sta $13

  lda snakeHeadL
  sta $14
  lda snakeHeadH
  sta $15

; then update head based on direction
  lda snakeDirection
  lsr
  bcs up
  lsr
  bcs right
  lsr
  bcs down
  lsr
  bcs left
  rts

up:
  lda snakeHeadL
  sec
  sbc #$20
  sta snakeHeadL
  bcc upwrap
  rts
upwrap:
  dec snakeHeadH
  lda snakeHeadH
  cmp #$02
  bcc setBottom
  rts
setBottom:
  lda #$05
  sta snakeHeadH
  rts

right:
  inc snakeHeadL
  lda snakeHeadL
  and #$1f
  beq wrapRight
  rts
wrapRight:
  sec
  lda snakeHeadL
  sbc #$20
  sta snakeHeadL
  inc snakeHeadH
  lda snakeHeadH
  cmp #$06
  bne noWrapRight
  lda #$02
  sta snakeHeadH
noWrapRight:
  rts

down:
  lda snakeHeadL
  clc
  adc #$20
  sta snakeHeadL
  bcs downwrap
  rts
downwrap:
  inc snakeHeadH
  lda snakeHeadH
  cmp #$06
  bne noWrapDown
  lda #$02
  sta snakeHeadH
noWrapDown:
  rts

left:
  dec snakeHeadL
  lda snakeHeadL
  and #$1f
  cmp #$1f
  beq leftwrap
  rts
leftwrap:
  dec snakeHeadH
  lda snakeHeadH
  cmp #$02
  bcc wrapTop
  lda snakeHeadL
  ora #$1f
  sta snakeHeadL
  rts
wrapTop:
  lda #$05
  sta snakeHeadH
  lda snakeHeadL
  ora #$1f
  sta snakeHeadL
  rts

clearScreen:
  lda #0
  ldx #0
clrLoop:
  sta $0200,x
  sta $0300,x
  sta $0400,x
  sta $0500,x
  inx
  bne clrLoop
  rts

drawSnake:
  ldy #0
  lda #$FF
  
  ;draw head
  sta (snakeHeadL),y
  
  ;draw first body segment
  sta ($12),y
  
  ;draw second body segment
  sta ($14),y
  rts

delay:
  ldy #$20
delayloop:
  ldx #0
innerdelay:
  dex
  bne innerdelay
  dey
  bne delayloop
  rts


Video of moving with wasd

We now have a working snake smoothly moving aroudn the screen. First i introduced the initSnake which sets up the initial position and direction of our snake. The head position and two additional body segments are initialized here.

initSnake:
  lda #movingRight
  sta snakeDirection

  lda #$00
  sta snakeHeadL
  lda #$03
  sta snakeHeadH

  ; body segment positions (behind head initially)
  lda #$1F
  sta $12       ; segment 1 low
  lda #$02
  sta $13       ; segment 1 high

  lda #$1E
  sta $14       ; segment 2 low
  lda #$02
  sta $15       ; segment 2 high
  rts

The core logic of our game runs continously in mainLoop, ensuring constant updates and responsiveness. Each iteration of the loop processors user input (readKeys), updates the snake's position (updateSnake), clears the screen (clearScreen), redraws the snake (drawSnake), and introduces a visual delay.

mainLoop:
  jsr readKeys
  jsr updateSnake
  jsr clearScreen
  jsr drawSnake
  jsr delay
  jmp mainLoop

The readKeys connects with WASD keys to find direction and includes checks to prevent isntant reversal of the snake's direction, which we mentioned previously that it should be prohibited.

readKeys:
  lda sysLastKey
  cmp #ASCII_w
  beq upKey
  cmp #ASCII_d
  beq rightKey
  cmp #ASCII_s
  beq downKey
  cmp #ASCII_a
  beq leftKey
  rts

upKey:
  lda #movingDown
  bit snakeDirection
  bne illegalMove
  lda #movingUp
  sta snakeDirection
  rts

rightKey:
  lda #movingLeft
  bit snakeDirection
  bne illegalMove
  lda #movingRight
  sta snakeDirection
  rts

downKey:
  lda #movingUp
  bit snakeDirection
  bne illegalMove
  lda #movingDown
  sta snakeDirection
  rts

leftKey:
  lda #movingRight
  bit snakeDirection
  bne illegalMove
  lda #movingLeft
  sta snakeDirection
  rts

illegalMove:
  rts


In updateSnake each body segment's position is shifted forward, effectively following the head. Then the head is moved according to the current direction. Also made it reappear from opposite side of screen when it goes over the screen.

updateSnake:
; shift body positions first (segments follow head)
  lda $14
  sta $12
  lda $15
  sta $13

  lda snakeHeadL
  sta $14
  lda snakeHeadH
  sta $15

; then update head based on direction
  lda snakeDirection
  lsr
  bcs up
  lsr
  bcs right
  lsr
  bcs down
  lsr
  bcs left
  rts

And clearScreen resets all pixels to back ($00), ensuring only the current snake position is shown clearly:

clearScreen:
  lda #0
  ldx #0
clrLoop:
  sta $0200,x
  sta $0300,x
  sta $0400,x
  sta $0500,x
  inx
  bne clrLoop
  rts


Next we can implement apple and disappearing apple when snake hits them!

; === Definitions ===
define snakeHeadL     $10 ; head low byte
define snakeHeadH     $11 ; head high byte
define snakeBodyStart $12 ; body segments
define snakeDirection $02

define appleL         $20 ; apple low byte
define appleH         $21 ; apple high byte

define movingUp       1
define movingRight    2
define movingDown     4
define movingLeft     8

define ASCII_w        $77
define ASCII_a        $61
define ASCII_s        $73
define ASCII_d        $64

define sysLastKey     $ff
define sysRandom      $fe ; emulator random byte
define SCREEN         $0200

  jsr initSnake
  jsr spawnApple     ; NEW: initial apple spawn

mainLoop:
  jsr readKeys
  jsr updateSnake
  jsr checkApple     ; NEW: check collision with apple
  jsr clearScreen
  jsr drawApple      ; NEW: draw apple
  jsr drawSnake
  jsr delay
  jmp mainLoop

initSnake:
  lda #movingRight
  sta snakeDirection

  lda #$00
  sta snakeHeadL
  lda #$03
  sta snakeHeadH

  ; snake body (2 segments)
  lda #$1F
  sta $12
  lda #$02
  sta $13

  lda #$1E
  sta $14
  lda #$02
  sta $15
  rts

; === Spawn Apple at random ===
spawnApple:
  lda sysRandom
  and #$1F
  sta appleL

  lda sysRandom
  and #$03
  clc
  adc #$02
  sta appleH
  rts

; === Check collision snake-apple ===
checkApple:
  lda snakeHeadL
  cmp appleL
  bne noApple
  lda snakeHeadH
  cmp appleH
  bne noApple

  jsr spawnApple  ; respawn apple if eaten

noApple:
  rts

readKeys:
  lda sysLastKey
  cmp #ASCII_w
  beq upKey
  cmp #ASCII_d
  beq rightKey
  cmp #ASCII_s
  beq downKey
  cmp #ASCII_a
  beq leftKey
  rts

upKey:
  lda #movingDown
  bit snakeDirection
  bne illegalMove
  lda #movingUp
  sta snakeDirection
  rts

rightKey:
  lda #movingLeft
  bit snakeDirection
  bne illegalMove
  lda #movingRight
  sta snakeDirection
  rts

downKey:
  lda #movingUp
  bit snakeDirection
  bne illegalMove
  lda #movingDown
  sta snakeDirection
  rts

leftKey:
  lda #movingRight
  bit snakeDirection
  bne illegalMove
  lda #movingLeft
  sta snakeDirection
  rts

illegalMove:
  rts

updateSnake:
; shift body positions
  lda $14
  sta $12
  lda $15
  sta $13
  lda snakeHeadL
  sta $14
  lda snakeHeadH
  sta $15

; move head
  lda snakeDirection
  lsr
  bcs up
  lsr
  bcs right
  lsr
  bcs down
  lsr
  bcs left
  rts

up:
  lda snakeHeadL
  sec
  sbc #$20
  sta snakeHeadL
  bcc upwrap
  rts
upwrap:
  dec snakeHeadH
  lda snakeHeadH
  cmp #$02
  bcc setBottom
  rts
setBottom:
  lda #$05
  sta snakeHeadH
  rts

right:
  inc snakeHeadL
  lda snakeHeadL
  and #$1f
  beq wrapRight
  rts
wrapRight:
  sec
  lda snakeHeadL
  sbc #$20
  sta snakeHeadL
  inc snakeHeadH
  lda snakeHeadH
  cmp #$06
  bne noWrapRight
  lda #$02
  sta snakeHeadH
noWrapRight:
  rts

down:
  lda snakeHeadL
  clc
  adc #$20
  sta snakeHeadL
  bcs downwrap
  rts
downwrap:
  inc snakeHeadH
  lda snakeHeadH
  cmp #$06
  bne noWrapDown
  lda #$02
  sta snakeHeadH
noWrapDown:
  rts

left:
  dec snakeHeadL
  lda snakeHeadL
  and #$1f
  cmp #$1f
  beq leftwrap
  rts
leftwrap:
  dec snakeHeadH
  lda snakeHeadH
  cmp #$02
  bcc wrapTop
  lda snakeHeadL
  ora #$1f
  sta snakeHeadL
  rts
wrapTop:
  lda #$05
  sta snakeHeadH
  lda snakeHeadL
  ora #$1f
  sta snakeHeadL
  rts

clearScreen:
  lda #0
  ldx #0
clrLoop:
  sta $0200,x
  sta $0300,x
  sta $0400,x
  sta $0500,x
  inx
  bne clrLoop
  rts

; ===draw apple ===
drawApple:
  lda #$AA ; Apple color (distinct from snake)
  ldy #0
  sta (appleL),y
  rts

drawSnake:
  lda #$FF ; Snake color
  ldy #0
  sta (snakeHeadL),y
  sta ($12),y
  sta ($14),y
  rts

delay:
  ldy #$20
delayloop:
  ldx #0
innerdelay:
  dex
  bne innerdelay
  dey
  bne delayloop
  rts

We have now brought another part to our game here - apples! So for ths i started wth making spawnApple subroutine, which randomly positions apple on the screen each time the game begins and whenever snake hits them.

; === Spawn Apple at random ===
spawnApple:
  lda sysRandom
  and #$1F
  sta appleL

  lda sysRandom
  and #$03
  clc
  adc #$02
  sta appleH
  rts

in here sysRandom generates a random position within screen bounds, ensuring the apple appears unpredictable each time.



Next the drawApple subroutine visually represents the apple using a distinct color($AA)

; ===draw apple ===
drawApple:
  lda #$AA ; Apple color (distinct from snake)
  ldy #0
  sta (appleL),y
  rts


And now we have checkApple subroutine, which compares the apple's position with the snake head's current position and if they are matching then the apple has been eaten by snake. Then apple immediately respawns at a new random positon

; === Check collision snake-apple ===
checkApple:
  lda snakeHeadL
  cmp appleL
  bne noApple
  lda snakeHeadH
  cmp appleH
  bne noApple

  jsr spawnApple  ; respawn apple if eaten

noApple:
  rts



Okay i think now we have some important parts in placed, now lets go ahead and do the "Snake length increase on apple eating"


define snakeHeadL     $10
define snakeHeadH     $11
define snakeLength    $12
define snakeDirection $13

define snakeBodyStart $30 ; Snake body starts here (many segments allowed)

define appleL         $80 ; Apple moved to safer location
define appleH         $81

define movingUp       1
define movingRight    2
define movingDown     4
define movingLeft     8

define ASCII_w        $77
define ASCII_a        $61
define ASCII_s        $73
define ASCII_d        $64

define sysLastKey     $ff
define sysRandom      $fe
define SCREEN         $0200

  jsr initSnake
  jsr spawnApple

mainLoop:
  jsr readKeys
  jsr updateSnake
  jsr checkApple
  jsr clearScreen
  jsr drawApple
  jsr drawSnake
  jsr delay
  jmp mainLoop

initSnake:
  lda #movingRight
  sta snakeDirection
  lda #3
  sta snakeLength

  lda #$00
  sta snakeHeadL
  lda #$03
  sta snakeHeadH

  ; initial snake body segments
  lda #$1F
  sta $30
  lda #$02
  sta $31

  lda #$1E
  sta $32
  lda #$02
  sta $33
  rts

spawnApple:
  lda sysRandom
  and #$1F
  sta appleL
  lda sysRandom
  and #$03
  clc
  adc #$02
  sta appleH
  rts

checkApple:
  lda snakeHeadL
  cmp appleL
  bne noApple
  lda snakeHeadH
  cmp appleH
  bne noApple

  ; Snake eats apple
  inc snakeLength    ; Increase snake length by 1
  jsr spawnApple     ; Respawn apple after eaten

noApple:
  rts

readKeys:
  lda sysLastKey
  cmp #ASCII_w
  beq upKey
  cmp #ASCII_d
  beq rightKey
  cmp #ASCII_s
  beq downKey
  cmp #ASCII_a
  beq leftKey
  rts

upKey:
  lda #movingDown
  bit snakeDirection
  bne illegalMove
  lda #movingUp
  sta snakeDirection
  rts

rightKey:
  lda #movingLeft
  bit snakeDirection
  bne illegalMove
  lda #movingRight
  sta snakeDirection
  rts

downKey:
  lda #movingUp
  bit snakeDirection
  bne illegalMove
  lda #movingDown
  sta snakeDirection
  rts

leftKey:
  lda #movingRight
  bit snakeDirection
  bne illegalMove
  lda #movingLeft
  sta snakeDirection
  rts

illegalMove:
  rts

updateSnake:
  ldx snakeLength
  dex
  txa
  asl
  tax

shiftLoop:
  cpx #2
  bcc shiftDone
  lda $2E,x    ; snakeHeadL ($10,$11) = body-2 = $2E,$2F
  sta $30,x
  lda $2F,x
  sta $31,x
  dex
  dex
  jmp shiftLoop

shiftDone:
  lda snakeHeadL
  sta $30
  lda snakeHeadH
  sta $31

  lda snakeDirection
  lsr
  bcs up
  lsr
  bcs right
  lsr
  bcs down
  lsr
  bcs left
  rts

up:
  lda snakeHeadL
  sec
  sbc #$20
  sta snakeHeadL
  bcc upwrap
  rts
upwrap:
  dec snakeHeadH
  lda snakeHeadH
  cmp #$02
  bcc setBottom
  rts
setBottom:
  lda #$05
  sta snakeHeadH
  rts

right:
  inc snakeHeadL
  lda snakeHeadL
  and #$1f
  beq wrapRight
  rts
wrapRight:
  sec
  lda snakeHeadL
  sbc #$20
  sta snakeHeadL
  inc snakeHeadH
  lda snakeHeadH
  cmp #$06
  bne noWrapRight
  lda #$02
  sta snakeHeadH
noWrapRight:
  rts

down:
  lda snakeHeadL
  clc
  adc #$20
  sta snakeHeadL
  bcs downwrap
  rts
downwrap:
  inc snakeHeadH
  lda snakeHeadH
  cmp #$06
  bne noWrapDown
  lda #$02
  sta snakeHeadH
noWrapDown:
  rts

left:
  dec snakeHeadL
  lda snakeHeadL
  and #$1f
  cmp #$1f
  beq leftwrap
  rts
leftwrap:
  dec snakeHeadH
  lda snakeHeadH
  cmp #$02
  bcc wrapTop
  lda snakeHeadL
  ora #$1f
  sta snakeHeadL
  rts
wrapTop:
  lda #$05
  sta snakeHeadH
  lda snakeHeadL
  ora #$1f
  sta snakeHeadL
  rts

clearScreen:
  lda #0
  ldx #0
clrLoop:
  sta $0200,x
  sta $0300,x
  sta $0400,x
  sta $0500,x
  inx
  bne clrLoop
  rts

drawApple:
  lda #$AA
  ldy #0
  sta (appleL),y
  rts

drawSnake:
  ldy #0
  lda #$FF
  sta (snakeHeadL),y ; head

  ldx #0
drawBodyLoop:
  lda $30,x
  sta $00
  lda $31,x
  sta $01
  lda #$FF
  sta ($00),y
  inx
  inx
  cpx snakeLength
  bcc drawBodyLoop
  rts

delay:
  ldy #$20
delayloop:
  ldx #0
innerdelay:
  dex
  bne innerdelay
  dey
  bne delayloop
  rts


In the here we implemented logic that allows our snake to detect and respond when it successfully eats an apple. Each frame, the snake's head position is compared with apple's position, and if horizontal or vertical positions match same then we say snake has eaten the apple. And then we increment the snake's length with inc snakeLength to make it grow one segment.

checkApple:
  lda snakeHeadL
  cmp appleL
  bne noApple
  lda snakeHeadH
  cmp appleH
  bne noApple

  ; Snake eats apple
  inc snakeLength    ; Increase snake length by 1
  jsr spawnApple     ; Respawn apple after eaten

noApple:
  rts



reference:

https://skilldrick.github.io/easy6502/

No comments:

Post a Comment

10th Posting - Project: Stage 3

 Hello All! now we are on the final stage of the project which is project 3. If you remember correctly from my Stage 2 posting, i was able t...