Skip to content

Instantly share code, notes, and snippets.

@wkjagt
Last active October 7, 2025 15:30
Show Gist options
  • Save wkjagt/9043907 to your computer and use it in GitHub Desktop.
Save wkjagt/9043907 to your computer and use it in GitHub Desktop.

Revisions

  1. wkjagt revised this gist Feb 17, 2014. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions snake6502.asm
    Original file line number Diff line number Diff line change
    @@ -3,8 +3,10 @@
    ; \__ \ ' \/ _` | / / -_) _ \__ \ () / /
    ; |___/_||_\__,_|_\_\___\___/___/\__/___|

    ; An annotated version of the snake example on http://skilldrick.github.io/easy6502/
    ; that I mainly created as an exercise for myself to learn a little bit about assembly.
    ; An annotated version of the snake example from Nick Morgan's 6502 assembly tutorial
    ; on http://skilldrick.github.io/easy6502/ that I created as an exercise for myself
    ; to learn a little bit about assembly. I **think** I understood everything, but I may
    ; also be completely wrong :-)

    ; Change direction with keys: W A S D

  2. wkjagt revised this gist Feb 17, 2014. 1 changed file with 60 additions and 57 deletions.
    117 changes: 60 additions & 57 deletions snake6502.asm
    Original file line number Diff line number Diff line change
    @@ -5,12 +5,11 @@

    ; An annotated version of the snake example on http://skilldrick.github.io/easy6502/
    ; that I mainly created as an exercise for myself to learn a little bit about assembly.
    ;

    ; Change direction: W A S D
    ; Change direction with keys: W A S D

    ; $00-01 => screen location of apple, stored as two bytes, where the first byte is the least
    ; significant.
    ; $00-01 => screen location of apple, stored as two bytes, where the first
    ; byte is the least significant.
    ; $10-11 => screen location of snake head stored as two bytes
    ; $12-?? => snake body (in byte pairs)
    ; $02 => direction ; 1 => up (bin 0001)
    @@ -79,41 +78,41 @@ initSnake:

    ;initial snake direction (2 => right)
    lda #2 ;start direction, put the dec number 2 in register A
    sta $02 ;store value of register A in address $02
    sta $02 ;store value of register A at address $02

    ;initial snake length of 4
    lda #4 ;start length, put the dec number 4 (the snake is 4 bytes long)
    ;in register A
    sta $03 ;store value of register A in address $03
    sta $03 ;store value of register A at address $03

    ;initial snake head's location's least significant byte to determine
    ;Initial snake head's location's least significant byte to determine
    ;where in a 8x32 strip the head will start. hex $11 is just right
    ;of the center of the first row of a strip
    lda #$11 ;put the hex number $11 (dec 17) in register A
    sta $10 ;store value of register A in address hex 10
    sta $10 ;store value of register A at address hex 10

    ;initial snake body, two least significant bytes set to hex $10
    ;Initial snake body, two least significant bytes set to hex $10
    ;and hex $0f, one and two places left of the head respectively
    lda #$10 ;put the hex number $10 (dec 16) in register A
    sta $12 ;store value of register A in address hex $12
    sta $12 ;store value of register A at address hex $12
    lda #$0f ;put the hex number $0f (dec 15) in register A
    sta $14 ;store value of register A in address hex $14
    sta $14 ;store value of register A at address hex $14

    ;the most significant bytes of the head and body of the snake
    ;are all set to hex $04, which is the third 8x32 strip.
    lda #$04 ;put the hex number $04 in register A
    sta $11 ;store value of register A in address hex 11
    sta $13 ;store value of register A in address hex 13
    sta $15 ;store value of register A in address hex 15
    sta $11 ;store value of register A at address hex 11
    sta $13 ;store value of register A at address hex 13
    sta $15 ;store value of register A at address hex 15
    rts ;return


    generateApplePosition:
    ;least significant byte of the apple position. This will determine where
    ;Th least significant byte of the apple position will determine where
    ;in a 8x32 strip the apple is placed. This number can be any one byte value because
    ;the size of one 8x32 strip fits exactly in one byte
    ;the size of one 8x32 strip fits exactly in one out of 256 bytes
    lda $fe ;load a random number between 0 and 255 from address $fe into register A
    sta $00 ;store value of register A in address hex 00
    sta $00 ;store value of register A at address hex 00

    ;load a new random number from 2 to 5 into $01 for the most significant byte of
    ;the apple position. This will determine in which 8x32 strip the apple is placed
    @@ -123,7 +122,7 @@ generateApplePosition:
    ;register A. Hex 03 is binary 00000011, so only the two least significant bits
    ;are kept, resulting in a value between 0 (bin 00000000) and 3 (bin 00000011).
    ;Add 2 to the result, giving a random value between 2 and 5
    and #$03 ;mask out lowest 2 bits by
    and #$03 ;mask out lowest 2 bits
    clc ;clear carry flag
    adc #2 ;add to register A, using carry bit for overflow.
    sta $01 ;store value of y coordinate from register A into address $01
    @@ -143,7 +142,10 @@ loop:


    readKeys:
    lda $ff ;load the value of the latest keypress from location $ff into register A
    ;for getting keypresses, the last address ($ff) in the zero page contains
    ;the hex code of the last pressed key

    lda $ff ;load the value of the latest keypress from address $ff into register A
    cmp #$77 ;compare value in register A to hex $77 (W)
    beq upKey ;Branch On Equal, to upKey
    cmp #$64 ;compare value in register A to hex $64 (D)
    @@ -156,14 +158,14 @@ readKeys:

    upKey:
    lda #4 ;load value 4 into register A, correspoding to the value for DOWN
    bit $02 ;AND with value in address $02 (current direction), returning
    bit $02 ;AND with value at address $02 (the current direction),
    ;setting the zero flag if the result of ANDing the two values
    ;is 0. So comparing to 4 (bin 0100) only sets zero flag if
    ;current direction is 4 (DOWN). So for an illegal move (current
    ;direction is DOWN), the result of an AND would be a non zero value
    ;so the zero flag would not be set. For a legal move the bit in the
    ;new direction should not the same as the one set for DOWN, so the zero
    ;flag needs to set
    ;new direction should not be the same as the one set for DOWN,
    ;so the zero flag needs to be set
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    lda #1 ;Ending up here means the move is legal, load the value 1 (UP) into
    @@ -173,7 +175,7 @@ upKey:

    rightKey:
    lda #8 ;load value 8 into register A, corresponding to the value for LEFT
    bit $02 ;AND with current direction in address $02 and check if result
    bit $02 ;AND with current direction at address $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    @@ -184,7 +186,7 @@ rightKey:

    downKey:
    lda #1 ;load value 1 into register A, correspoding to the value for UP
    bit $02 ;AND with current direction in address $02 and check if result
    bit $02 ;AND with current direction at address $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    @@ -195,7 +197,7 @@ downKey:

    leftKey:
    lda #2 ;load value 1 into register A, correspoding to the value for RIGHT
    bit $02 ;AND with current direction in address $02 and check if result
    bit $02 ;AND with current direction at address $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    @@ -218,15 +220,15 @@ checkCollision:
    checkAppleCollision:
    ;check if the snake collided with the apple by comparing the least significant
    ;and most significant byte of the position of the snake's head and the apple.
    lda $00 ;load value of address $00 (the least significant
    lda $00 ;load value at address $00 (the least significant
    ;byte of the apple's position) into register A
    cmp $10 ;compare to the value stored in address $10
    cmp $10 ;compare to the value stored at address $10
    ;(the least significant byte of the position of the snake's head)
    bne doneCheckingAppleCollision ;if different, branch to doneCheckingAppleCollision
    lda $01 ;load value of address $01 (the most significant byte
    ;of the apple's position) into register A
    cmp $11 ;compare to memory locatoin $11 (the most significant byte
    ;of the position of the snake's head)
    cmp $11 ;compare the value stored at address $11 (the most
    ;significant byte of the position of the snake's head)
    bne doneCheckingAppleCollision ;if different, branch to doneCheckingAppleCollision

    ;Ending up here means the coordinates of the snake head are equal to that of
    @@ -246,22 +248,23 @@ checkSnakeCollision:
    ldx #2 ;Load the value 2 into the X register, so we start with the first segment

    snakeCollisionLoop:
    lda $10,x ;load the value of address $10 (x coordinate of head) plus
    ;the value of the x register (2 in the first iteration) to get the
    ;least significant byte of the position of the next snake segment
    cmp $10 ;compare to the value in address $10 (the least significant
    lda $10,x ;load the value stored at address $10 (the least significant byte of
    ;the location of the snake's head) plus the value of the x register
    ;(2 in the first iteration) to get the least significant byte of the
    ;position of the next snake segment
    cmp $10 ;compare to the value at address $10 (the least significant
    ;byte of the position of the snake's head
    bne continueCollisionLoop ;if not equals, we haven't found a collision yet,
    ;branch to continueCollisionLoop to continue the loop

    maybeCollided:
    ;ending up here means we found a segment of the snake's body that
    ;has an a least significant byte that's equal to that of the head
    lda $11,x ;load the value of address $11 (most significant byte of
    ;the location of the head) plus the value of the x register
    ;has a least significant byte that's equal to that of the snake's head.
    lda $11,x ;load the value stored at address $11 (most significant byte of
    ;the location of the snake's head) plus the value of the x register
    ;(2 in the first iteration) to get the most significant byte
    ;of the position of the next snake segment
    cmp $11 ;compare to the value in address $11 (the most significant
    cmp $11 ;compare to the value at address $11 (the most significant
    ;byte of the position of the snake head)
    beq didCollide ;both position bytes of the compared segment of the snake body
    ;are equal to those of the head, so we have a collision of the
    @@ -272,8 +275,8 @@ continueCollisionLoop:
    ;the coordinates for snake head and body segments
    inx ;increment the value of the x register
    inx ;increment the value of the x register
    cpx $03 ;compare the value in the x register to the value in memory
    ;location $03 (snake length).
    cpx $03 ;compare the value in the x register to the value stored at
    ;address $03 (snake length).
    beq didntCollide ;if equals, we got to last section with no collision: branch
    ;to didntCollide

    @@ -292,7 +295,7 @@ didntCollide:
    updateSnake:
    ;collision checks have been done, update the snake. Load the length of the snake
    ;minus one into the A register
    ldx $03 ;load the value of address $03 (snake length) into register X
    ldx $03 ;load the value stored at address $03 (snake length) into register X
    dex ;decrement the value in the X register
    txa ;transfer the value stored in the X register into the A register. WHY?

    @@ -309,7 +312,7 @@ updateloop:
    ;
    ;from: x===
    ;to: ===
    lda $10,x ;load the value of address $10 + x into register A
    lda $10,x ;load the value stored at address $10 + x into register A
    sta $12,x ;store the value of register A into address $12
    ;plus the value of register X
    dex ;decrement X, and set negative flag if value becomes negative
    @@ -328,13 +331,13 @@ updateloop:
    lsr ;shift to right
    bcs left ;if a 1 "fell off", we started with bin 1000, so the snakes needs to go left
    up:
    lda $10 ;put value from address $10 (the least significant byte, meaning the
    lda $10 ;put value stored at address $10 (the least significant byte, meaning the
    ;position in a 8x32 strip) in register A
    sec ;set carry flag
    sbc #$20 ;Subtract with Carry: subtract hex $20 (dec 32) together with the not of the
    sbc #$20 ;Subtract with Carry: subtract hex $20 (dec 32) together with the NOT of the
    ;carry bit from value in register A. If overflow occurs the carry bit is clear.
    ;This moves the snake up one row in its strip and checks for overflow
    sta $10 ;store value of register A in address $10 (the least significant byte
    sta $10 ;store value of register A at address $10 (the least significant byte
    ;of the head's position)
    bcc upup ;If the carry flag is clear, we had an overflow because of the subtraction,
    ;so we need to move to the strip above the current one
    @@ -344,19 +347,19 @@ upup:
    dec $11 ;decrement the most significant byte of the snake's head's position to
    ;move the snake's head to the next up 8x32 strip
    lda #$1 ;load hex value $1 (dec 1) into register A
    cmp $11 ;compare the value in address $11 (snake head's most significant
    cmp $11 ;compare the value at address $11 (snake head's most significant
    ;byte, determining which strip it's in). If it's 1, we're one strip too
    ;(the first one has a most significant byte of $02), which means we've
    ;the top of the screen
    ;(the first one has a most significant byte of $02), which means the snake
    ;hit the top of the screen

    beq collision ;branch if equal to collision
    rts ;return
    right:
    inc $10 ;increment the value in address $10 (snake head's least
    inc $10 ;increment the value at address $10 (snake head's least
    ;significant byte, determining where in the 8x32 strip the head is
    ;located) to move the head to the right
    lda #$1f ;load value hex $1f (dec 31) into register A
    bit $10 ;the value in address $10 (the snake head coordinate) is ANDed
    bit $10 ;the value stored at address $10 (the snake head coordinate) is ANDed
    ;with hex $1f (bin 11111), meaning all multiples of hex $20 (dec 32)
    ;will be zero (because they all end with bit patterns ending in 5 zeros)
    ;if it's zero, it means we hit the right of the screen
    @@ -368,7 +371,7 @@ down:
    clc ;clear carry flag
    adc #$20 ;add hex $20 (dec 32) to the value in register A and set the carry flag
    ;if overflow occurs
    sta $10 ;store the result in address $10
    sta $10 ;store the result at address $10
    bcs downdown ;if the carry flag is set, an overflow occurred when adding hex $20 to the
    ;least significant byte of the location of the snake's head, so we need to move
    ;the next 8x3 strip
    @@ -404,29 +407,29 @@ collision:

    drawApple:
    ldy #0 ;load the value 0 into the Y register
    lda $fe ;load the value of address $fe (the random number generator)
    lda $fe ;load the value stored at address $fe (the random number generator)
    ;into register A
    sta ($00),y ;dereference to the address stored in address $00 and $01
    sta ($00),y ;dereference to the address stored at address $00 and $01
    ;(the address of the apple on the screen) and set the value to
    ;the value of register A and add the value of Y (0) to it. This results
    ;in the apple getting a random color
    rts ;return


    drawSnake:
    ldx #0 ;set the value of the x register to 0
    lda #1 ;set the value of the a register to 1
    sta ($10,x) ;dereference to the memory address that's stored in address
    ldx #0 ;set the value of the X register to 0
    lda #1 ;set the value of the A register to 1
    sta ($10,x) ;dereference to the memory address that's stored at address
    ;$10 (the two bytes for the location of the head of the snake) and
    ;set its value to the one stored in register A
    ldx $03 ;set the value of the x register to the value stored in memory at
    ;location $03 (the length of the snake)
    lda #0 ;set the value of the a register to 0
    sta ($10,x) ;dereference to the memory address that's stored in address
    sta ($10,x) ;dereference to the memory address that's stored at address
    ;$10, add the length of the snake to it, and store the value of
    ;register A (0) in the resulting address. This draws a black pixel on the
    ;tail. Because the snake is moving, the head "draws" on the screen in
    ;white as it moves, and the tail works as an eraser, erasing the white
    ;white as it moves, and the tail works as an eraser, erasing the white trail
    ;using black pixels
    rts ;return

  3. wkjagt revised this gist Feb 17, 2014. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions snake6502.asm
    Original file line number Diff line number Diff line change
    @@ -109,8 +109,6 @@ initSnake:


    generateApplePosition:
    ;load a new random byte into $00

    ;least significant byte of the apple position. This will determine where
    ;in a 8x32 strip the apple is placed. This number can be any one byte value because
    ;the size of one 8x32 strip fits exactly in one byte
  4. wkjagt revised this gist Feb 17, 2014. 1 changed file with 84 additions and 41 deletions.
    125 changes: 84 additions & 41 deletions snake6502.asm
    Original file line number Diff line number Diff line change
    @@ -20,6 +20,48 @@
    ; $03 => snake length, in number of bytes, not segments


    ;The screens is divided in 8 strips of 8x32 "pixels". Each strip
    ;is stored in a page, having their own most significant byte. Each
    ;page has 256 bytes, starting at $00 and ending at $ff.

    ; ------------------------------------------------------------
    ;1 | $0200 - $02ff |
    ;2 | |
    ;3 | |
    ;4 | |
    ;5 | |
    ;6 | |
    ;7 | |
    ;8 | |
    ; ------------------------------------------------------------
    ;9 | $03 - $03ff |
    ;10 | |
    ;11 | |
    ;12 | |
    ;13 | |
    ;14 | |
    ;15 | |
    ;16 | |
    ; ------------------------------------------------------------
    ;17 | $04 - $03ff |
    ;18 | |
    ;19 | |
    ;20 | |
    ;21 | |
    ;22 | |
    ;23 | |
    ;24 | |
    ; ------------------------------------------------------------
    ;25 | $05 - $03ff |
    ;26 | |
    ;27 | |
    ;28 | |
    ;29 | |
    ;30 | |
    ;31 | |
    ;32 | |
    ; ------------------------------------------------------------

    jsr init ;jump to subroutine init
    jsr loop ;jump to subroutine loop

    @@ -37,31 +79,32 @@ initSnake:

    ;initial snake direction (2 => right)
    lda #2 ;start direction, put the dec number 2 in register A
    sta $02 ;store value of register A in memory location $02
    sta $02 ;store value of register A in address $02

    ;initial snake length of 4
    lda #4 ;start length, put the dec number 4 in register A
    sta $03 ;store value of register A in memory location $03
    lda #4 ;start length, put the dec number 4 (the snake is 4 bytes long)
    ;in register A
    sta $03 ;store value of register A in address $03

    ;initial snake head location least significant byte to determine
    ;initial snake head's location's least significant byte to determine
    ;where in a 8x32 strip the head will start. hex $11 is just right
    ;of the center of the first row of a strip
    lda #$11 ;put the hex number $11 (dec 17) in register A
    sta $10 ;store value of register A in memory location hex 10
    sta $10 ;store value of register A in address hex 10

    ;initial snake body, two least significant bytes set to hex $10
    ;and hex $0f, one and two places left of the head respectively
    lda #$10 ;put the hex number $10 (dec 16) in register A
    sta $12 ;store value of register A in memory location hex 12
    sta $12 ;store value of register A in address hex $12
    lda #$0f ;put the hex number $0f (dec 15) in register A
    sta $14 ;store value of register A in memory location hex 14
    sta $14 ;store value of register A in address hex $14

    ;the most significant bytes of the head and body of the snake
    ;are all set to hex $04, which is the third 8x32 strip.
    lda #$04 ;put the hex number $04 in register A
    sta $11 ;store value of register A in memory location hex 11
    sta $13 ;store value of register A in memory location hex 13
    sta $15 ;store value of register A in memory location hex 15
    sta $11 ;store value of register A in address hex 11
    sta $13 ;store value of register A in address hex 13
    sta $15 ;store value of register A in address hex 15
    rts ;return


    @@ -71,12 +114,12 @@ generateApplePosition:
    ;least significant byte of the apple position. This will determine where
    ;in a 8x32 strip the apple is placed. This number can be any one byte value because
    ;the size of one 8x32 strip fits exactly in one byte
    lda $fe ;load a random number between 0 and 255 from memory location $fe into register A
    sta $00 ;store value of register A in memory location hex 00
    lda $fe ;load a random number between 0 and 255 from address $fe into register A
    sta $00 ;store value of register A in address hex 00

    ;load a new random number from 2 to 5 into $01 for the most significant byte of
    ;the apple position. This will determine in which 8x32 strip the apple is placed
    lda $fe ;load a random number from memory location $fe into register A
    lda $fe ;load a random number from address $fe into register A

    ;AND: logical AND with accumulator. Apply logical AND with hex $03 to value in
    ;register A. Hex 03 is binary 00000011, so only the two least significant bits
    @@ -85,7 +128,7 @@ generateApplePosition:
    and #$03 ;mask out lowest 2 bits by
    clc ;clear carry flag
    adc #2 ;add to register A, using carry bit for overflow.
    sta $01 ;store value of y coordinate from register A into memory location $01
    sta $01 ;store value of y coordinate from register A into address $01

    rts ;return

    @@ -115,7 +158,7 @@ readKeys:

    upKey:
    lda #4 ;load value 4 into register A, correspoding to the value for DOWN
    bit $02 ;AND with value in memory location $02 (current direction), returning
    bit $02 ;AND with value in address $02 (current direction), returning
    ;setting the zero flag if the result of ANDing the two values
    ;is 0. So comparing to 4 (bin 0100) only sets zero flag if
    ;current direction is 4 (DOWN). So for an illegal move (current
    @@ -132,7 +175,7 @@ upKey:

    rightKey:
    lda #8 ;load value 8 into register A, corresponding to the value for LEFT
    bit $02 ;AND with current direction in memory location $02 and check if result
    bit $02 ;AND with current direction in address $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    @@ -143,7 +186,7 @@ rightKey:

    downKey:
    lda #1 ;load value 1 into register A, correspoding to the value for UP
    bit $02 ;AND with current direction in memory location $02 and check if result
    bit $02 ;AND with current direction in address $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    @@ -154,7 +197,7 @@ downKey:

    leftKey:
    lda #2 ;load value 1 into register A, correspoding to the value for RIGHT
    bit $02 ;AND with current direction in memory location $02 and check if result
    bit $02 ;AND with current direction in address $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    @@ -177,12 +220,12 @@ checkCollision:
    checkAppleCollision:
    ;check if the snake collided with the apple by comparing the least significant
    ;and most significant byte of the position of the snake's head and the apple.
    lda $00 ;load value of memory location $00 (the least significant
    lda $00 ;load value of address $00 (the least significant
    ;byte of the apple's position) into register A
    cmp $10 ;compare to the value stored in memory location $10
    cmp $10 ;compare to the value stored in address $10
    ;(the least significant byte of the position of the snake's head)
    bne doneCheckingAppleCollision ;if different, branch to doneCheckingAppleCollision
    lda $01 ;load value of memory location $01 (the most significant byte
    lda $01 ;load value of address $01 (the most significant byte
    ;of the apple's position) into register A
    cmp $11 ;compare to memory locatoin $11 (the most significant byte
    ;of the position of the snake's head)
    @@ -205,22 +248,22 @@ checkSnakeCollision:
    ldx #2 ;Load the value 2 into the X register, so we start with the first segment

    snakeCollisionLoop:
    lda $10,x ;load the value of memory location $10 (x coordinate of head) plus
    lda $10,x ;load the value of address $10 (x coordinate of head) plus
    ;the value of the x register (2 in the first iteration) to get the
    ;least significant byte of the position of the next snake segment
    cmp $10 ;compare to the value in memory location $10 (the least significant
    cmp $10 ;compare to the value in address $10 (the least significant
    ;byte of the position of the snake's head
    bne continueCollisionLoop ;if not equals, we haven't found a collision yet,
    ;branch to continueCollisionLoop to continue the loop

    maybeCollided:
    ;ending up here means we found a segment of the snake's body that
    ;has an a least significant byte that's equal to that of the head
    lda $11,x ;load the value of memory location $11 (most significant byte of
    lda $11,x ;load the value of address $11 (most significant byte of
    ;the location of the head) plus the value of the x register
    ;(2 in the first iteration) to get the most significant byte
    ;of the position of the next snake segment
    cmp $11 ;compare to the value in memory location $11 (the most significant
    cmp $11 ;compare to the value in address $11 (the most significant
    ;byte of the position of the snake head)
    beq didCollide ;both position bytes of the compared segment of the snake body
    ;are equal to those of the head, so we have a collision of the
    @@ -251,7 +294,7 @@ didntCollide:
    updateSnake:
    ;collision checks have been done, update the snake. Load the length of the snake
    ;minus one into the A register
    ldx $03 ;load the value of memory location $03 (snake length) into register X
    ldx $03 ;load the value of address $03 (snake length) into register X
    dex ;decrement the value in the X register
    txa ;transfer the value stored in the X register into the A register. WHY?

    @@ -268,16 +311,16 @@ updateloop:
    ;
    ;from: x===
    ;to: ===
    lda $10,x ;load the value of memory location $10 + x into register A
    sta $12,x ;store the value of register A into memory location $12
    lda $10,x ;load the value of address $10 + x into register A
    sta $12,x ;store the value of register A into address $12
    ;plus the value of register X
    dex ;decrement X, and set negative flag if value becomes negative
    bpl updateloop ;branch to updateLoop if positive (negative flag not set)

    ;now determine where to move the head, based on the direction of the snake
    ;lsr: Logical Shift Right. Shift all bits in register A one bit to the right
    ;the bit that "falls off" is stored in the carry flag
    lda $02 ;load the value from memory location $02 (direction) into register A
    lda $02 ;load the value from address $02 (direction) into register A
    lsr ;shift to right
    bcs up ;if a 1 "fell off", we started with bin 0001, so the snakes needs to go up
    lsr ;shift to right
    @@ -287,13 +330,13 @@ updateloop:
    lsr ;shift to right
    bcs left ;if a 1 "fell off", we started with bin 1000, so the snakes needs to go left
    up:
    lda $10 ;put value from memory location $10 (the least significant byte, meaning the
    lda $10 ;put value from address $10 (the least significant byte, meaning the
    ;position in a 8x32 strip) in register A
    sec ;set carry flag
    sbc #$20 ;Subtract with Carry: subtract hex $20 (dec 32) together with the not of the
    ;carry bit from value in register A. If overflow occurs the carry bit is clear.
    ;This moves the snake up one row in its strip and checks for overflow
    sta $10 ;store value of register A in memory location $10 (the least significant byte
    sta $10 ;store value of register A in address $10 (the least significant byte
    ;of the head's position)
    bcc upup ;If the carry flag is clear, we had an overflow because of the subtraction,
    ;so we need to move to the strip above the current one
    @@ -303,31 +346,31 @@ upup:
    dec $11 ;decrement the most significant byte of the snake's head's position to
    ;move the snake's head to the next up 8x32 strip
    lda #$1 ;load hex value $1 (dec 1) into register A
    cmp $11 ;compare the value in memory location $11 (snake head's most significant
    cmp $11 ;compare the value in address $11 (snake head's most significant
    ;byte, determining which strip it's in). If it's 1, we're one strip too
    ;(the first one has a most significant byte of $02), which means we've
    ;the top of the screen

    beq collision ;branch if equal to collision
    rts ;return
    right:
    inc $10 ;increment the value in memory location $10 (snake head's least
    inc $10 ;increment the value in address $10 (snake head's least
    ;significant byte, determining where in the 8x32 strip the head is
    ;located) to move the head to the right
    lda #$1f ;load value hex $1f (dec 31) into register A
    bit $10 ;the value in memory location $10 (the snake head coordinate) is ANDed
    bit $10 ;the value in address $10 (the snake head coordinate) is ANDed
    ;with hex $1f (bin 11111), meaning all multiples of hex $20 (dec 32)
    ;will be zero (because they all end with bit patterns ending in 5 zeros)
    ;if it's zero, it means we hit the right of the screen
    beq collision ;branch to collision if zero flag is set
    rts ;return
    down:
    lda $10 ;put value from memory location $10 (the least significant byte, meaning the
    lda $10 ;put value from address $10 (the least significant byte, meaning the
    ;position in a 8x32 strip) in register A
    clc ;clear carry flag
    adc #$20 ;add hex $20 (dec 32) to the value in register A and set the carry flag
    ;if overflow occurs
    sta $10 ;store the result in memory location $10
    sta $10 ;store the result in address $10
    bcs downdown ;if the carry flag is set, an overflow occurred when adding hex $20 to the
    ;least significant byte of the location of the snake's head, so we need to move
    ;the next 8x3 strip
    @@ -363,10 +406,10 @@ collision:

    drawApple:
    ldy #0 ;load the value 0 into the Y register
    lda $fe ;load the value of memory location $fe (the random number generator)
    lda $fe ;load the value of address $fe (the random number generator)
    ;into register A
    sta ($00),y ;dereference to the memory location stored in memory location $00 and $01
    ;(the memory location of the apple on the screen) and set the value to
    sta ($00),y ;dereference to the address stored in address $00 and $01
    ;(the address of the apple on the screen) and set the value to
    ;the value of register A and add the value of Y (0) to it. This results
    ;in the apple getting a random color
    rts ;return
    @@ -375,13 +418,13 @@ drawApple:
    drawSnake:
    ldx #0 ;set the value of the x register to 0
    lda #1 ;set the value of the a register to 1
    sta ($10,x) ;dereference to the memory address that's stored in memory location
    sta ($10,x) ;dereference to the memory address that's stored in address
    ;$10 (the two bytes for the location of the head of the snake) and
    ;set its value to the one stored in register A
    ldx $03 ;set the value of the x register to the value stored in memory at
    ;location $03 (the length of the snake)
    lda #0 ;set the value of the a register to 0
    sta ($10,x) ;dereference to the memory address that's stored in memory location
    sta ($10,x) ;dereference to the memory address that's stored in address
    ;$10, add the length of the snake to it, and store the value of
    ;register A (0) in the resulting address. This draws a black pixel on the
    ;tail. Because the snake is moving, the head "draws" on the screen in
  5. wkjagt revised this gist Feb 17, 2014. 1 changed file with 9 additions and 3 deletions.
    12 changes: 9 additions & 3 deletions snake6502.asm
    Original file line number Diff line number Diff line change
    @@ -4,13 +4,19 @@
    ; |___/_||_\__,_|_\_\___\___/___/\__/___|

    ; An annotated version of the snake example on http://skilldrick.github.io/easy6502/
    ; that I mainly created as an exercise for myself to learn a little bit about assembly.
    ;

    ; Change direction: W A S D

    ; $00-01 => screen location of apple
    ; $10-11 => screen location of snake head
    ; $00-01 => screen location of apple, stored as two bytes, where the first byte is the least
    ; significant.
    ; $10-11 => screen location of snake head stored as two bytes
    ; $12-?? => snake body (in byte pairs)
    ; $02 => direction (1 => up, 2 => right, 4 => down, 8 => left)
    ; $02 => direction ; 1 => up (bin 0001)
    ; 2 => right (bin 0010)
    ; 4 => down (bin 0100)
    ; 8 => left (bin 1000)
    ; $03 => snake length, in number of bytes, not segments


  6. wkjagt created this gist Feb 17, 2014.
    400 changes: 400 additions & 0 deletions snake6502.asm
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,400 @@
    ; ___ _ __ ___ __ ___
    ; / __|_ _ __ _| |_____ / /| __|/ \_ )
    ; \__ \ ' \/ _` | / / -_) _ \__ \ () / /
    ; |___/_||_\__,_|_\_\___\___/___/\__/___|

    ; An annotated version of the snake example on http://skilldrick.github.io/easy6502/

    ; Change direction: W A S D

    ; $00-01 => screen location of apple
    ; $10-11 => screen location of snake head
    ; $12-?? => snake body (in byte pairs)
    ; $02 => direction (1 => up, 2 => right, 4 => down, 8 => left)
    ; $03 => snake length, in number of bytes, not segments


    jsr init ;jump to subroutine init
    jsr loop ;jump to subroutine loop

    init:
    jsr initSnake ;jump to subroutine initSnake
    jsr generateApplePosition ;jump to subroutine generateApplePosition
    rts ;return


    initSnake:
    ;start the snake in a horizontal position in the middle of the game field
    ;having a total length of one head and 4 bytes for the segments, meaning a
    ;total length of 3: the head and two segments.
    ;The head is looking right, and the snaking moving to the right.

    ;initial snake direction (2 => right)
    lda #2 ;start direction, put the dec number 2 in register A
    sta $02 ;store value of register A in memory location $02

    ;initial snake length of 4
    lda #4 ;start length, put the dec number 4 in register A
    sta $03 ;store value of register A in memory location $03

    ;initial snake head location least significant byte to determine
    ;where in a 8x32 strip the head will start. hex $11 is just right
    ;of the center of the first row of a strip
    lda #$11 ;put the hex number $11 (dec 17) in register A
    sta $10 ;store value of register A in memory location hex 10

    ;initial snake body, two least significant bytes set to hex $10
    ;and hex $0f, one and two places left of the head respectively
    lda #$10 ;put the hex number $10 (dec 16) in register A
    sta $12 ;store value of register A in memory location hex 12
    lda #$0f ;put the hex number $0f (dec 15) in register A
    sta $14 ;store value of register A in memory location hex 14

    ;the most significant bytes of the head and body of the snake
    ;are all set to hex $04, which is the third 8x32 strip.
    lda #$04 ;put the hex number $04 in register A
    sta $11 ;store value of register A in memory location hex 11
    sta $13 ;store value of register A in memory location hex 13
    sta $15 ;store value of register A in memory location hex 15
    rts ;return


    generateApplePosition:
    ;load a new random byte into $00

    ;least significant byte of the apple position. This will determine where
    ;in a 8x32 strip the apple is placed. This number can be any one byte value because
    ;the size of one 8x32 strip fits exactly in one byte
    lda $fe ;load a random number between 0 and 255 from memory location $fe into register A
    sta $00 ;store value of register A in memory location hex 00

    ;load a new random number from 2 to 5 into $01 for the most significant byte of
    ;the apple position. This will determine in which 8x32 strip the apple is placed
    lda $fe ;load a random number from memory location $fe into register A

    ;AND: logical AND with accumulator. Apply logical AND with hex $03 to value in
    ;register A. Hex 03 is binary 00000011, so only the two least significant bits
    ;are kept, resulting in a value between 0 (bin 00000000) and 3 (bin 00000011).
    ;Add 2 to the result, giving a random value between 2 and 5
    and #$03 ;mask out lowest 2 bits by
    clc ;clear carry flag
    adc #2 ;add to register A, using carry bit for overflow.
    sta $01 ;store value of y coordinate from register A into memory location $01

    rts ;return


    loop:
    ;the main game loop
    jsr readKeys ;jump to subroutine readKeys
    jsr checkCollision ;jump to subroutine checkCollision
    jsr updateSnake ;jump to subroutine updateSnake
    jsr drawApple ;jump to subroutine drawApple
    jsr drawSnake ;jump to subroutine drawSnake
    jsr spinWheels ;jump to subroutine spinWheels
    jmp loop ;jump to loop (this is what makes it loop)


    readKeys:
    lda $ff ;load the value of the latest keypress from location $ff into register A
    cmp #$77 ;compare value in register A to hex $77 (W)
    beq upKey ;Branch On Equal, to upKey
    cmp #$64 ;compare value in register A to hex $64 (D)
    beq rightKey ;Branch On Equal, to rightKey
    cmp #$73 ;compare value in register A to hex $73 (S)
    beq downKey ;Branch On Equal, to downKey
    cmp #$61 ;compare value in register A to hex $61 (A)
    beq leftKey ;Branch On Equal, to leftKey
    rts ;return

    upKey:
    lda #4 ;load value 4 into register A, correspoding to the value for DOWN
    bit $02 ;AND with value in memory location $02 (current direction), returning
    ;setting the zero flag if the result of ANDing the two values
    ;is 0. So comparing to 4 (bin 0100) only sets zero flag if
    ;current direction is 4 (DOWN). So for an illegal move (current
    ;direction is DOWN), the result of an AND would be a non zero value
    ;so the zero flag would not be set. For a legal move the bit in the
    ;new direction should not the same as the one set for DOWN, so the zero
    ;flag needs to set
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    lda #1 ;Ending up here means the move is legal, load the value 1 (UP) into
    ;register A
    sta $02 ;Store the value of A (the new direction) into register A
    rts ;return

    rightKey:
    lda #8 ;load value 8 into register A, corresponding to the value for LEFT
    bit $02 ;AND with current direction in memory location $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    lda #2 ;Ending up here means the move is legal, load the value 2 (RIGHT) into
    ;register A
    sta $02 ;Store the value of A (the new direction) into register A
    rts ;return

    downKey:
    lda #1 ;load value 1 into register A, correspoding to the value for UP
    bit $02 ;AND with current direction in memory location $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    lda #4 ;Ending up here means the move is legal, load the value 4 (DOWN) into
    ;register A
    sta $02 ;Store the value of A (the new direction) into register A
    rts ;return

    leftKey:
    lda #2 ;load value 1 into register A, correspoding to the value for RIGHT
    bit $02 ;AND with current direction in memory location $02 and check if result
    ;is zero
    bne illegalMove ;Branch If Not Equal: meaning the zero flag is not set.

    lda #8 ;Ending up here means the move is legal, load the value 8 (LEFT) into
    ;register A
    sta $02 ;Store the value of A (the new direction) into register A
    rts ;return

    illegalMove:
    ;for an illegal move, just return, so the keypress is ignored
    rts ;return


    checkCollision:
    jsr checkAppleCollision ;jump to subroutine checkAppleCollision
    jsr checkSnakeCollision ;jump to subroutine checkSnakeCollision
    rts ;return


    checkAppleCollision:
    ;check if the snake collided with the apple by comparing the least significant
    ;and most significant byte of the position of the snake's head and the apple.
    lda $00 ;load value of memory location $00 (the least significant
    ;byte of the apple's position) into register A
    cmp $10 ;compare to the value stored in memory location $10
    ;(the least significant byte of the position of the snake's head)
    bne doneCheckingAppleCollision ;if different, branch to doneCheckingAppleCollision
    lda $01 ;load value of memory location $01 (the most significant byte
    ;of the apple's position) into register A
    cmp $11 ;compare to memory locatoin $11 (the most significant byte
    ;of the position of the snake's head)
    bne doneCheckingAppleCollision ;if different, branch to doneCheckingAppleCollision

    ;Ending up here means the coordinates of the snake head are equal to that of
    ;the apple: eat apple
    inc $03 ;increment the value held in memory $03 (snake length)
    inc $03 ;twice because we're adding two bytes for one segment

    ;create a new apple
    jsr generateApplePosition ;jump to subroutine generateApplePosition

    doneCheckingAppleCollision:
    ;the snake head was not on the apple. Don't do anything with the apple
    rts ;return


    checkSnakeCollision:
    ldx #2 ;Load the value 2 into the X register, so we start with the first segment

    snakeCollisionLoop:
    lda $10,x ;load the value of memory location $10 (x coordinate of head) plus
    ;the value of the x register (2 in the first iteration) to get the
    ;least significant byte of the position of the next snake segment
    cmp $10 ;compare to the value in memory location $10 (the least significant
    ;byte of the position of the snake's head
    bne continueCollisionLoop ;if not equals, we haven't found a collision yet,
    ;branch to continueCollisionLoop to continue the loop

    maybeCollided:
    ;ending up here means we found a segment of the snake's body that
    ;has an a least significant byte that's equal to that of the head
    lda $11,x ;load the value of memory location $11 (most significant byte of
    ;the location of the head) plus the value of the x register
    ;(2 in the first iteration) to get the most significant byte
    ;of the position of the next snake segment
    cmp $11 ;compare to the value in memory location $11 (the most significant
    ;byte of the position of the snake head)
    beq didCollide ;both position bytes of the compared segment of the snake body
    ;are equal to those of the head, so we have a collision of the
    ;snake's head with its own body.

    continueCollisionLoop:
    ;increment the value in the x register twice because we use two bytes to store
    ;the coordinates for snake head and body segments
    inx ;increment the value of the x register
    inx ;increment the value of the x register
    cpx $03 ;compare the value in the x register to the value in memory
    ;location $03 (snake length).
    beq didntCollide ;if equals, we got to last section with no collision: branch
    ;to didntCollide

    ;ending up here means we haven't checked all snake body segments yet
    jmp snakeCollisionLoop;jump to snakeCollisionLoop to continue the loop

    didCollide:
    ;there was a collision
    jmp gameOver ;jump to gameOver

    didntCollide:
    ;there was no collision, continue the game
    rts ;return


    updateSnake:
    ;collision checks have been done, update the snake. Load the length of the snake
    ;minus one into the A register
    ldx $03 ;load the value of memory location $03 (snake length) into register X
    dex ;decrement the value in the X register
    txa ;transfer the value stored in the X register into the A register. WHY?

    updateloop:

    ;Example: the length of the snake is 4 bytes (two segments). In the lines above
    ;the X register has been set to 3. The snake coordinates are now stored as follows:
    ;$10,$11 : the snake head
    ;$12,$13,$14,$15: the snake body segments (two bytes for each of the 2 segments)
    ;
    ;The loop shifts all coordinates of the snake two places further in memory,
    ;calculating the offset of the origin from $10 and place it in memory offset to
    ;$12, effectively shifting each of the snake's segments one place further:
    ;
    ;from: x===
    ;to: ===
    lda $10,x ;load the value of memory location $10 + x into register A
    sta $12,x ;store the value of register A into memory location $12
    ;plus the value of register X
    dex ;decrement X, and set negative flag if value becomes negative
    bpl updateloop ;branch to updateLoop if positive (negative flag not set)

    ;now determine where to move the head, based on the direction of the snake
    ;lsr: Logical Shift Right. Shift all bits in register A one bit to the right
    ;the bit that "falls off" is stored in the carry flag
    lda $02 ;load the value from memory location $02 (direction) into register A
    lsr ;shift to right
    bcs up ;if a 1 "fell off", we started with bin 0001, so the snakes needs to go up
    lsr ;shift to right
    bcs right ;if a 1 "fell off", we started with bin 0010, so the snakes needs to go right
    lsr ;shift to right
    bcs down ;if a 1 "fell off", we started with bin 0100, so the snakes needs to go down
    lsr ;shift to right
    bcs left ;if a 1 "fell off", we started with bin 1000, so the snakes needs to go left
    up:
    lda $10 ;put value from memory location $10 (the least significant byte, meaning the
    ;position in a 8x32 strip) in register A
    sec ;set carry flag
    sbc #$20 ;Subtract with Carry: subtract hex $20 (dec 32) together with the not of the
    ;carry bit from value in register A. If overflow occurs the carry bit is clear.
    ;This moves the snake up one row in its strip and checks for overflow
    sta $10 ;store value of register A in memory location $10 (the least significant byte
    ;of the head's position)
    bcc upup ;If the carry flag is clear, we had an overflow because of the subtraction,
    ;so we need to move to the strip above the current one
    rts ;return
    upup:
    ;An overflow occurred when subtracting 20 from the least significant byte
    dec $11 ;decrement the most significant byte of the snake's head's position to
    ;move the snake's head to the next up 8x32 strip
    lda #$1 ;load hex value $1 (dec 1) into register A
    cmp $11 ;compare the value in memory location $11 (snake head's most significant
    ;byte, determining which strip it's in). If it's 1, we're one strip too
    ;(the first one has a most significant byte of $02), which means we've
    ;the top of the screen

    beq collision ;branch if equal to collision
    rts ;return
    right:
    inc $10 ;increment the value in memory location $10 (snake head's least
    ;significant byte, determining where in the 8x32 strip the head is
    ;located) to move the head to the right
    lda #$1f ;load value hex $1f (dec 31) into register A
    bit $10 ;the value in memory location $10 (the snake head coordinate) is ANDed
    ;with hex $1f (bin 11111), meaning all multiples of hex $20 (dec 32)
    ;will be zero (because they all end with bit patterns ending in 5 zeros)
    ;if it's zero, it means we hit the right of the screen
    beq collision ;branch to collision if zero flag is set
    rts ;return
    down:
    lda $10 ;put value from memory location $10 (the least significant byte, meaning the
    ;position in a 8x32 strip) in register A
    clc ;clear carry flag
    adc #$20 ;add hex $20 (dec 32) to the value in register A and set the carry flag
    ;if overflow occurs
    sta $10 ;store the result in memory location $10
    bcs downdown ;if the carry flag is set, an overflow occurred when adding hex $20 to the
    ;least significant byte of the location of the snake's head, so we need to move
    ;the next 8x3 strip
    rts ;return
    downdown:
    inc $11 ;increment the value in location hex $11, holding the most significatnt byte
    ;of the location of the snake's head.
    lda #$6 ;load the value hex $6 into the A register
    cmp $11 ;if the most significant byte of the head's location is equals to 6, we're
    ;one strip to far down (the last one was hex $05)
    beq collision ;if equals to 6, the snake collided with the bottom of the screen
    rts ;return

    left:
    ;A collision with the left side of the screen happens if the head wraps around to
    ;the previous row, on the right most side of the screen, where, because the screen
    ;is 32 wide, the right most positions always have a least significant byte that ends
    ;in 11111 in binary form (hex $1f). ANDing with hex $1f in this column will always
    ;return hex $1f, so comparing the result of the AND with hex $1f will determine if
    ;the snake collided with the left side of the screen.

    dec $10 ;subtract one from the value held in memory position $10 (least significant
    ;byte of the snake head position) to make it move left.
    lda $10 ;load value held in memory position $10 (least significant byte of the
    ;snake head position) into register A
    and #$1f ;AND the value hex $1f (bin 11111) with the value in register A
    cmp #$1f ;compare the ANDed value above with bin 11111.
    beq collision ;branch to collision if equals
    rts ;return
    collision:
    jmp gameOver ;jump to gameOver


    drawApple:
    ldy #0 ;load the value 0 into the Y register
    lda $fe ;load the value of memory location $fe (the random number generator)
    ;into register A
    sta ($00),y ;dereference to the memory location stored in memory location $00 and $01
    ;(the memory location of the apple on the screen) and set the value to
    ;the value of register A and add the value of Y (0) to it. This results
    ;in the apple getting a random color
    rts ;return


    drawSnake:
    ldx #0 ;set the value of the x register to 0
    lda #1 ;set the value of the a register to 1
    sta ($10,x) ;dereference to the memory address that's stored in memory location
    ;$10 (the two bytes for the location of the head of the snake) and
    ;set its value to the one stored in register A
    ldx $03 ;set the value of the x register to the value stored in memory at
    ;location $03 (the length of the snake)
    lda #0 ;set the value of the a register to 0
    sta ($10,x) ;dereference to the memory address that's stored in memory location
    ;$10, add the length of the snake to it, and store the value of
    ;register A (0) in the resulting address. This draws a black pixel on the
    ;tail. Because the snake is moving, the head "draws" on the screen in
    ;white as it moves, and the tail works as an eraser, erasing the white
    ;using black pixels
    rts ;return


    spinWheels:
    ;slow the game down by wasting cycles
    ldx #0 ;load zero in the X register
    spinloop:
    nop ;no operation, just skip a cycle
    nop ;no operation, just skip a cycle
    dex ;subtract one from the value stored in register x
    bne spinloop ;if the zero flag is clear, loop. The first dex above wrapped the
    ;value of x to hex $ff, so the next zero value is 255 (hex $ff)
    ;loops later.
    rts ;return


    gameOver: ;game over is literally the end of the program