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
BRKin 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:
rtsOkay 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