Created
April 20, 2024 03:36
-
-
Save samneggs/b0c46d6418c697ea922c555fe7c5fd35 to your computer and use it in GitHub Desktop.
Classic Snake Game In Assembly on Pi Pico
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# snake_asm | |
from lcd_1_8 import LCD_1inch8 | |
import machine, math | |
from machine import Pin, PWM | |
from uctypes import addressof | |
from time import sleep, ticks_us, ticks_diff, ticks_ms | |
import gc, _thread, array | |
from sys import exit | |
from micropython import const | |
from random import randint | |
from rp2 import bootsel_button as RESET_PB | |
MAXSCREEN_X = const(160) | |
MAXSCREEN_Y = const(128) | |
POT_MIN = const(15_000) | |
POT_MAX = const(50_000) | |
TIMELR = const(0x4005400c) | |
MAX_SEGS = const(5000) | |
FOOD_SIZE = const(5) | |
TAIL_GROW = const(20) | |
START_LENGTH = const(30) | |
FOOD_COLOR = const(0xffff) | |
SNAKE_PARAMS = const(10) | |
POS = const(0) | |
LEN = const(1) | |
NEW_LEN = const(2) | |
FOOD_X = const(3) | |
FOOD_Y = const(4) | |
POT_PARAMS = const(5) | |
X = const(0) | |
Y = const(1) | |
XV = const(2) | |
YV = const(3) | |
P_TOG = const(4) | |
GAME_PARAMS = const(10) | |
FPS = const(0) | |
LIVES = const(1) | |
ADC_BASE = const(0x4004c000) | |
ADC_DONE = const(8) | |
ADC_START0 = const(0b0_0000_0000_0101) | |
ADC_START1 = const(0b1_0000_0000_0101) | |
ADC_RESULT = const(4) | |
#ASM_CONTROL = array.array('i',(addressof(LCD.buffer),addressof(GAME),addressof(SNAKE),addressof(BODY), | |
# addressof(POT),POT_MIN,POT_MAX,ADC_BASE,ADC_START1,FOOD_COLOR,CTL_SEED)) | |
CTL_SCREEN = const(0) | |
CTL_GAME = const(4) | |
CTL_SNAKE = const(8) | |
CTL_BODY = const(12) | |
CTL_POT = const(16) | |
CTL_P_MIN = const(20) | |
CTL_P_MAX = const(24) | |
CTL_ADC_BASE = const(28) | |
CTL_ADC_ST1 = const(32) | |
CTL_FODCOLOR = const(36) | |
CTL_SEED = const(40) | |
@micropython.asm_thumb | |
def snake_asm(r0): | |
mov(r7,r0) #r7 = control | |
ldr(r0,[r7,CTL_POT]) | |
ldr(r1,[r0,P_TOG*4]) # pot read toggle X/Y | |
mov(r2,1) | |
eor(r1,r2) | |
str(r1,[r0,P_TOG*4]) # pot write toggle X/Y | |
cmp(r1,1) | |
beq(POT_TOGGLE) | |
bl(READ_XPOT) | |
b(POT_DONE) | |
label(POT_TOGGLE) | |
bl(READ_YPOT) | |
label(POT_DONE) | |
bl(CLEAR_SCREEN) # clear screen | |
bl(UPDATE_XY) # move head, check screen bounds | |
bl(DRAW_FOOD) # draw food circle | |
bl(EAT_FOOD) # check collision with food | |
bl(MOVE_BODY) # move snake body, return r0=1 game over | |
cmp(r0,1) | |
bne(NO_COLL) | |
b(GAME_OVER) | |
label(NO_COLL) | |
bl(DRAW_BODY) # draw snake body | |
b(EXIT) | |
label(READ_YPOT) # --------------------------------------- | |
ldr(r6,[r7,CTL_POT]) # r6 = POT address | |
ldr(r5,[r6,YV*4]) # r5 = y_inc = int(pot[YV]) | |
ldr(r0,[r7,CTL_ADC_BASE]) # r0 = ADC_BASE address | |
ldr(r1,[r7,CTL_ADC_ST1]) # start 0b1_0000_0000_0101 | |
str(r1,[r0,0]) # start ADC1 | |
mov(r2,1) | |
lsl(r2,r2,ADC_DONE) | |
label(CHECK_ADC0) # is ADC done? | |
ldr(r3,[r0,0]) # r3 = status | |
and_(r3,r2) | |
bne(CHECK_ADC0) | |
ldr(r1,[r0,ADC_RESULT]) # r1 = raw_y | |
mov(r2,55) | |
mul(r2,r2) # 3025 | |
cmp(r1,r2) # raw_y < 3025 | |
blt(DIR1) | |
cmp(r5,0) | |
blt(DIR1) # y_inc < 0 | |
mov(r4,0) | |
str(r4,[r6,XV*4]) # x_inc = 0 | |
add(r4,1) | |
str(r4,[r6,YV*4]) # y_inc = 1 | |
label(DIR1) | |
mov(r2,32) | |
mul(r2,r2) # 1024 | |
cmp(r1,r2) # raw_y < 1024 | |
bgt(Y_DONE) | |
cmp(r5,0) | |
bgt(Y_DONE) # y_inc > 0 | |
mov(r4,0) | |
str(r4,[r6,XV*4]) # x_inc = 0 | |
sub(r4,1) | |
str(r4,[r6,YV*4]) # y_inc = -1 | |
label(Y_DONE) | |
bx(lr) | |
label(READ_XPOT) # ------------------------------------ | |
ldr(r6,[r7,CTL_POT]) # r6 = POT address | |
ldr(r5,[r6,XV*4]) # r5 = x_inc = int(pot[XV]) | |
ldr(r0,[r7,CTL_ADC_BASE]) # r0 = ADC_BASE address | |
mov(r1,ADC_START0) | |
str(r1,[r0,0]) # start ADC0 | |
label(CHECK_ADC1) # is ADC done? | |
mov(r2,1) | |
lsl(r2,r2,ADC_DONE) # | |
ldr(r3,[r0,0]) # r3 = status | |
and_(r2,r3) | |
bne(CHECK_ADC1) | |
ldr(r1,[r0,ADC_RESULT]) | |
mov(r2,55) | |
mul(r2,r2) # 3025 | |
cmp(r1,r2) # raw_y < 3025 | |
blt(DIR2) | |
cmp(r5,0) | |
bgt(DIR2) # x_inc > 0 | |
mov(r4,0) | |
str(r4,[r6,YV*4]) # y_inc = 0 | |
sub(r4,1) | |
str(r4,[r6,XV*4]) # x_inc = -1 | |
label(DIR2) | |
mov(r2,32) | |
mul(r2,r2) # 1024 | |
cmp(r1,r2) # raw_y < 1024 | |
bgt(X_DONE) | |
cmp(r5,0) | |
blt(X_DONE) # x_inc < 0 | |
mov(r4,0) | |
str(r4,[r6,YV*4]) # y_inc = 0 | |
add(r4,1) | |
str(r4,[r6,XV*4]) # x_inc = -1 | |
label(X_DONE) | |
bx(lr) | |
label(UPDATE_XY) # ------------------------------------------- | |
ldr(r6,[r7,CTL_POT]) # r6 = POT address | |
ldr(r5,[r6,X*4]) # r5 = pot[X] | |
ldr(r4,[r6,Y*4]) # r4 = pot[Y] | |
ldr(r3,[r6,XV*4]) # r3 = pot[XV] | |
ldr(r2,[r6,YV*4]) # r2 = pot[YV] | |
add(r5,r5,r3) # pot[x]+=pot[XV] | |
cmp(r5,0) | |
bgt(X_LOW) | |
add(r5,MAXSCREEN_X) # if x < 0 : x += MAXSCREEN_X | |
label(X_LOW) | |
cmp(r5,MAXSCREEN_X) | |
blt(X_HIGH) | |
sub(r5,MAXSCREEN_X) # if x > MAXSCREEN_X : x -= MAXSCREEN_X | |
label(X_HIGH) | |
str(r5,[r6,X*4]) # | |
add(r4,r4,r2) | |
cmp(r4,0) | |
bgt(Y_LOW) | |
add(r4,MAXSCREEN_Y) # if y < 0 : y += MAXSCREEN_Y | |
label(Y_LOW) | |
cmp(r4,MAXSCREEN_Y) | |
blt(Y_HIGH) | |
sub(r4,MAXSCREEN_Y) # if y > MAXSCREEN_Y : y -= MAXSCREEN_Y | |
label(Y_HIGH) | |
str(r4,[r6,Y*4]) # | |
ldr(r0,[r7,CTL_BODY]) | |
mov(r1,MAXSCREEN_X) | |
mul(r4,r1) | |
add(r4,r4,r5) # y * MAXSCREEN_X + x | |
str(r4,[r0,0]) # body[0] = y * MAXSCREEN_X + x | |
bx(lr) | |
label(RANDINT) # r0: randint(r0:min,r1:max) ---------------------- | |
push({lr}) | |
sub(r1,r1,r0) # max = max - min | |
mov(r5,r0) # r5: min | |
ldr(r2,[r7,CTL_SEED]) # seed | |
add(r2,13) # scramble seed | |
mov(r4,r2) | |
mov(r3,9) | |
ror(r4,r3) | |
eor(r2,r4) # end scramble | |
str(r2,[r7,CTL_SEED]) | |
cmp(r2,0) | |
bge(NOT_NEG) | |
neg(r0,r2) | |
label(NOT_NEG) | |
bl(DIVIDE) # r1: seed % max | |
add(r0,r1,r5) # r0: randint(min,max) | |
pop({pc}) | |
#--divide routine-----r0=r0//r1----uses r0,r1,r6---------- | |
label(DIVIDE) | |
mov(r6,0xd0) | |
lsl(r6,r6,24) # 0d0000000 | |
add(r6,0x60) # offset so strh will work | |
str(r0, [r6, 8]) # SIO_DIV_SDIVIDEND_OFFSET _u(0x00000068)8 | |
str(r1, [r6, 12]) # SIO_DIV_SDIVISOR_OFFSET _u(0x0000006c)12 | |
b(DELAY1) | |
label(DELAY1) | |
b(DELAY2) | |
label(DELAY2) | |
b(DELAY3) | |
label(DELAY3) | |
b(DELAY4) | |
label(DELAY4) | |
ldr(r1, [r6, 20]) #SIO_DIV_REMAINDER_OFFSET _u(0x00000074)20 | |
ldr(r0, [r6, 16]) #SIO_DIV_QUOTIENT_OFFSET _u(0x00000070)16 | |
bx(lr) | |
label(DRAW_FOOD) # r7 = control, r0 = center x, r1 = center y, r2 = radius --------------- | |
push({lr}) | |
ldr(r3,[r7,CTL_SNAKE]) # SNAKE address | |
ldr(r0,[r3,FOOD_X*4]) # r0 = x | |
ldr(r1,[r3,FOOD_Y*4]) # r1 = y | |
mov(r2,FOOD_SIZE) # r2 = radius | |
mov(r3,r2) # r3: x = radius | |
mov(r4,0) # r4: y = 0 | |
mov(r5,1) # r5: p = 1 | |
sub(r5,r5,r2) # p = 1 - radius | |
label(CIR_LOOP) | |
cmp(r3,r4) # x,y | |
ble(CIR_LOOP_DONE) | |
add(r4,r4,1) # y += 1 | |
cmp(r5,0) | |
bgt(P_GT_ZERO) | |
add(r5,r5,r4) # p = p + y | |
add(r5,r5,r4) # p = p + 2y | |
add(r5,r5,1) # p = p + 2y +1 | |
b(IF_DONE) | |
label(P_GT_ZERO) | |
sub(r3,r3,1) # x -= 1 | |
add(r5,r5,r4) # p = p + y | |
add(r5,r5,r4) # p = p + 2y | |
sub(r5,r5,r3) # p = p + 2y - x | |
sub(r5,r5,r3) # p = p + 2y - 2x | |
add(r5,r5,1) # p = p + 2y - 2x + 1 | |
label(IF_DONE) | |
cmp(r3,r4) # x,y | |
blt(CIR_LOOP_DONE) # if x < y: break | |
#plot_line(x0 - x, y0 + y, x0 + x, y0 + y) | |
push({r0,r1,r2,r3,r4,r5,r6}) | |
mov(r5,r0) # r2: x0 | |
mov(r6,r1) # r3: y0 | |
sub(r0,r0,r3) # r0: x0 - x | |
add(r1,r1,r4) # r1: y0 + y | |
add(r2,r5,r3) # r2: x0 + x | |
add(r3,r6,r4) # r3: y0 + y | |
bl(DRAW_LINE) | |
#plot_line(x0 - y, y0 + x, x0 + y, y0 + x) | |
push({r0,r1,r2,r3,r4,r5,r6}) | |
mov(r5,r0) # r2: x0 | |
mov(r6,r1) # r3: y0 | |
sub(r0,r0,r4) # r0: x0 - y | |
add(r1,r1,r3) # r1: y0 + x | |
add(r2,r5,r4) # r2: x0 + y | |
add(r3,r6,r3) # r3: y0 + x | |
bl(DRAW_LINE) | |
#plot_line(x0 - x, y0 - y, x0 + x, y0 - y) | |
push({r0,r1,r2,r3,r4,r5,r6}) | |
mov(r5,r0) # r2: x0 | |
mov(r6,r1) # r3: y0 | |
sub(r0,r0,r3) # r0: x0 - x | |
sub(r1,r1,r4) # r1: y0 - y | |
add(r2,r5,r3) # r2: x0 + x | |
sub(r3,r6,r4) # r3: y0 - y | |
bl(DRAW_LINE) | |
#plot_line(x0 - y, y0 - x, x0 + y, y0 - x) | |
push({r0,r1,r2,r3,r4,r5,r6}) | |
mov(r5,r0) # r2: x0 | |
mov(r6,r1) # r3: y0 | |
sub(r0,r0,r4) # r0: x0 - y | |
sub(r1,r1,r3) # r1: y0 - x | |
add(r2,r5,r4) # r2: x0 + y | |
sub(r3,r6,r3) # r3: y0 - x | |
bl(DRAW_LINE) | |
b(CIR_LOOP) | |
label(CIR_LOOP_DONE) | |
#plot_line(x0 - radius, y0, x0 + radius, y0) | |
push({r0,r1,r2,r3,r4,r5,r6}) | |
mov(r5,r0) # r2: x0 | |
sub(r0,r5,r2) # r0: x0 - radius | |
add(r2,r5,r2) # r2: x0 + radius | |
mov(r3,r1) # r3: y0 | |
bl(DRAW_LINE) | |
pop({pc}) | |
label(DRAW_LINE) # r0,r1,r2,r3 = x1,y1,x2,y2 ----circle sub-------------- | |
cmp(r1,r3) # | |
bne(Y_LOOP) | |
label(X_LOOP) # if y1 == y2: | |
mov(r4,r0) # r4 : x = x1 | |
label(FOR_X) | |
mov(r5,MAXSCREEN_X) | |
mul(r5,r1) # y1 * MAXSCREEN_X | |
add(r5,r5,r4) # y1 * MAXSCREEN_X + x | |
add(r5,r5,r5) # double for strh | |
ldr(r6,[r7,CTL_SCREEN]) # screen address | |
add(r5,r5,r6) # | |
ldr(r6,[r7,CTL_FODCOLOR]) # food color | |
strh(r6,[r5,0]) # draw pixel | |
add(r4,r4,1) | |
cmp(r4,r2) | |
ble(FOR_X) | |
pop({r0,r1,r2,r3,r4,r5,r6}) | |
bx(lr) | |
label(Y_LOOP) # if y1 != y2: | |
mov(r4,r1) # r4 : y = y1 | |
label(FOR_Y) | |
mov(r5,MAXSCREEN_X) | |
mul(r5,r4) # y * MAXSCREEN_X | |
add(r5,r5,r0) # y * MAXSCREEN_X + x1 | |
add(r5,r5,r5) # double for strh | |
ldr(r6,[r7,CTL_SCREEN]) # screen address | |
add(r5,r5,r6) # | |
ldr(r6,[r7,CTL_FODCOLOR]) # food color | |
strh(r6,[r5,0]) # draw pixel | |
add(r4,r4,1) | |
cmp(r4,r3) | |
ble(FOR_Y) | |
pop({r0,r1,r2,r3,r4,r5,r6}) | |
bx(lr) | |
label(EAT_FOOD) # ----------------------------------------- | |
push({lr}) | |
ldr(r0,[r7,CTL_BODY]) # BODY address | |
ldr(r0,[r0,POS*4]) # BODY position | |
mov(r1,MAXSCREEN_X) | |
bl(DIVIDE) # r0 = y_head = pos//MAXSCREEN_X, r1 = x_head = pos % MAXSCREEN_X | |
ldr(r2,[r7,CTL_SNAKE]) # SNAKE address | |
ldr(r3,[r2,FOOD_X*4]) # r3: x_food | |
ldr(r2,[r2,FOOD_Y*4]) # r2: y_food | |
sub(r1,r1,r3) # x_head - x_food | |
mul(r1,r1) # ((x_head - x_food) * (x_head - x_food)) | |
sub(r0,r0,r2) # y_head - y_food | |
mul(r0,r0) # ((y_head - y_food) * (y_head - y_food)) | |
add(r0,r0,r1) # ((x_head - x_food) * (x_head - x_food)) + ((y_head - y_food) * (y_head - y_food)) | |
mov(r1,FOOD_SIZE) # | |
mul(r1,r1) # FOOD_SIZE * FOOD_SIZE | |
cmp(r0,r1) # distance_squared <= radius_squared | |
ble(COLLISION) | |
pop({pc}) | |
label(COLLISION) # new food | |
mov(r0,FOOD_SIZE*2) # min x | |
mov(r1,MAXSCREEN_X-FOOD_SIZE*2)# max x | |
bl(RANDINT) | |
ldr(r2,[r7,CTL_SNAKE]) # SNAKE address | |
str(r0,[r2,FOOD_X*4]) # x_food | |
mov(r0,FOOD_SIZE*2) # min y | |
mov(r1,MAXSCREEN_Y-FOOD_SIZE*2)# max y | |
bl(RANDINT) | |
ldr(r2,[r7,CTL_SNAKE]) # SNAKE address | |
str(r0,[r2,FOOD_Y*4]) # y_food | |
ldr(r3,[r2,NEW_LEN*4]) # new length | |
add(r3,TAIL_GROW) | |
str(r3,[r2,NEW_LEN*4]) | |
pop({pc}) | |
label(DRAW_BODY) # ------------------------------------------- | |
ldr(r2,[r7,CTL_SNAKE]) # SNAKE address | |
ldr(r2,[r2,LEN*4]) # r2: segments | |
ldr(r3,[r7,CTL_BODY]) # r3: BODY address | |
ldr(r4,[r7,CTL_SCREEN]) # r4: SCREEN address | |
mov(r5,0xff) # r5: body color | |
label(SEG_LOOP) | |
ldr(r0,[r3,0]) # r0: BODY[index] (position) | |
add(r0,r0,r0) # double for strh | |
add(r0,r0,r4) # screen + offset | |
strh(r5,[r0,0]) | |
add(r3,r3,4) # next address | |
sub(r2,r2,1) # seg-=1 | |
cmp(r2,0) | |
bgt(SEG_LOOP) | |
bx(lr) | |
label(CLEAR_SCREEN) # ------------------------ | |
ldr(r0,[r7,CTL_SCREEN]) # r0: SCREEN address | |
mov(r1,MAXSCREEN_X) | |
mov(r2,MAXSCREEN_Y) | |
mul(r1,r2) # r1: screen size | |
mov(r3,0) # r3: color | |
label(CLEAR_LOOP) | |
strh(r3,[r0,0]) | |
add(r0,r0,2) | |
sub(r1,r1,1) | |
cmp(r1,0) | |
bgt(CLEAR_LOOP) | |
bx(lr) | |
label(MOVE_BODY) #------------------------------------------- | |
ldr(r0,[r7,CTL_SNAKE]) # SNAKE address | |
ldr(r1,[r0,LEN*4]) # r1: segments = snake[LEN] | |
ldr(r2,[r0,NEW_LEN*4]) # new_length = snake[NEW_LEN] | |
cmp(r1,r2) | |
bge(NO_GROW) # segments => new_length | |
add(r2,r1,1) # snake[LEN] += 1 | |
str(r2,[r0,LEN*4]) | |
label(NO_GROW) | |
ldr(r0,[r7,CTL_BODY]) # r0: BODY address | |
ldr(r2,[r0,0]) # r2: head = body[0] | |
mov(r3,4) | |
mul(r3,r1) # segments * 4 | |
add(r3,r3,r0) # r3: body[segments] address | |
label(GROW_LOOP) | |
sub(r4,r3,4) # r4: body[segments-1] | |
ldr(r5,[r4,0]) # r5: body[segments-1] value | |
str(r5,[r3,0]) # body[i] = body[i-1] | |
sub(r3,r3,4) | |
cmp(r2,r5) | |
bne(NO_TAIL) | |
cmp(r1,1) | |
ble(NO_TAIL) | |
ldr(r0,[r7,CTL_SNAKE]) | |
mov(r1,1) | |
str(r1,[r0,LEN*4]) | |
mov(r0,1) | |
bx(lr) # exit game over | |
label(NO_TAIL) | |
sub(r1,r1,1) | |
cmp(r1,0) | |
bgt(GROW_LOOP) | |
mov(r0,0) | |
bx(lr) | |
label(EXIT) # Game exits --------------------------- | |
mov(r0,0) | |
label(GAME_OVER) | |
mov(r1,1) | |
def init_game(): | |
global GAME, SNAKE, BODY, POT, ASM_CONTROL | |
GAME = array.array('i',0 for _ in range(GAME_PARAMS)) | |
SNAKE = array.array('i',0 for _ in range(SNAKE_PARAMS)) | |
BODY = array.array('i',0 for _ in range(MAX_SEGS)) | |
POT = array.array('i',0 for _ in range(POT_PARAMS)) | |
ASM_CONTROL = array.array('i',(addressof(LCD.buffer),addressof(GAME),addressof(SNAKE),addressof(BODY), | |
addressof(POT),POT_MIN,POT_MAX,ADC_BASE,ADC_START1,FOOD_COLOR,CTL_SEED)) | |
ASM_CONTROL[CTL_SEED//4] = machine.mem32[TIMELR] | |
GAME[FPS] = 0 | |
GAME[LIVES] = 3 | |
POT[X] = MAXSCREEN_X//2 | |
POT[Y] = MAXSCREEN_Y//2 | |
POT[XV] = 1 | |
SNAKE[LEN] = START_LENGTH | |
SNAKE[NEW_LEN] = START_LENGTH | |
SNAKE[FOOD_X] = randint(FOOD_SIZE*2,MAXSCREEN_X-FOOD_SIZE*2) | |
SNAKE[FOOD_Y] = randint(FOOD_SIZE*2,MAXSCREEN_Y-FOOD_SIZE*2) | |
for i in range(5): | |
BODY[i] = MAXSCREEN_Y//2 * MAXSCREEN_X + MAXSCREEN_X//2 - i | |
@micropython.viper | |
def main(): | |
global OVER | |
init_game() | |
while not RESET_PB() and not OVER: | |
sleep(0.001) | |
OVER = int(snake_asm(ASM_CONTROL)) | |
LCD.show() | |
exit() | |
def shutdown(): | |
global EXIT | |
EXIT = True | |
Pin(13,Pin.OUT).low() # screen off | |
gc.collect() | |
print(gc.mem_free()) | |
print('Core0 Stop') | |
exit() | |
if __name__=='__main__': | |
COUNT = 0 | |
OVER = 0 | |
machine.mem32[0x4004c000+0] = 0b11_0000_0000_0000_1001 | |
FIRE_BUTTON = Pin(22, Pin.IN, Pin.PULL_UP) | |
#machine.freq(200_000_000) | |
#machine.mem32[0x40008048] = 1<<11 # enable peri_ctrl clock | |
pwm = PWM(Pin(13)) | |
pwm.freq(1000) | |
pwm.duty_u16(0x8fff)#max 0xffff | |
LCD = LCD_1inch8() | |
LCD.fill(0) | |
LCD.show() | |
EXIT = False | |
try: | |
main() | |
shutdown() | |
except KeyboardInterrupt : | |
shutdown() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment