Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created January 3, 2023 02:04
Show Gist options
  • Save samneggs/0032fefd3498cb53c267245731e4e645 to your computer and use it in GitHub Desktop.
Save samneggs/0032fefd3498cb53c267245731e4e645 to your computer and use it in GitHub Desktop.
Cave Adventure game on Pi Pico in MicroPython and Assembly
# cave
from machine import Pin,SPI,PWM,ADC
import framebuf, gc
import time, array
from time import sleep, ticks_us, ticks_diff
from lcd_1_8 import LCD_1inch8
from random import randint
from sys import exit
import _thread
from uctypes import addressof
MAXSCREEN_X = const(160)
MAXSCREEN_Y = const(128)
MAXBOARD_X = const(160)
MAXBOARD_Y = const(128)
TEXTURE_WIDTH = const(16)
TEXTURE_HEIGHT = const(16)
NUM_TEXTURES = const(6)
SCREEN_SCALE = const(8) # 8
BROWN = const(0x2159)
NUM_ENEMY = const(50)
PLAYER_X = const(69)
PLAYER_Y = const(50)
ROCK = const(1)
SOUND=0
# player/enemy array offsets
X = const(0)
Y = const(1)
HP = const(2)
DIR = const(3)
SWING = const(4)
C_SIZE= const(5)
board1=bytearray(MAXBOARD_X*MAXBOARD_Y)
board2=bytearray(MAXBOARD_X*MAXBOARD_Y)
chars=bytearray(TEXTURE_WIDTH*TEXTURE_HEIGHT*2*NUM_TEXTURES)
enemy=bytearray(NUM_ENEMY*10) # x,y,hp,dir
draw_ctl=array.array('I',())
player1=array.array('I',(80,64,100<<10,0,0,2,0)) #x,y,hp,dir,swing, size
health = array.array('H',())
rocks = array.array('H',())
class Player():
def __init__(self):
self.x_offset = 80
self.y_offset = 64
self.direction = 0
self.swing = 0
player=Player()
machine.freq(200_000_000)
def init_player():
player1[X] = 80
player1[Y] = 64
player1[HP] = 100<<10
player1[DIR] = 0
player1[SWING] = 0
player1[C_SIZE]= 2
player.x_offset = 80
player.y_offset = 64
def fade(color_array,input_color1, input_color2):
color1=input_color1<<8 | input_color1>>8 # byte swap to normal RBG565
red1 =color1>>11& 0b11111 # extract red #13
green1=color1>>6 & 0b11111 # extract green
blue1 =color1 & 0b11111 # extract blue
color2=input_color2<<8 | input_color2>>8 # byte swap
red2 =color2>>11& 0b11111 # extract red
green2=color2>>6 & 0b11111 # extract green
blue2 =color2 & 0b11111 # extract blue
inc_red =(red2- red1)/31 # find increment step
inc_green=(green2-green1)/31
inc_blue =(blue2- blue1)/31
for i in range(0,32):
red3 =red1 +int(i*inc_red) # build colors by steps
green3=green1+int(i*inc_green)
blue3 =blue1 +int(i*inc_blue)
color3=red3<<11 | green3<<6 | blue3 # combine RGB
color_array.append((color3 & 0x00FF)<<8 | (color3>>8)) # byte swap to LCD RGB565
# blit_image_file from Stewart Watkiss
# http://www.penguintutor.com/programming/picodisplayanimations
def blit_image_file(buf,filename,width,height,cw,ch): # file width, file height, char width, char height
with open (filename, "rb") as file:
file_position = 0
char_position = 0
ecount = 0
current_byte = file.read(4) # header
while file_position < (width * height * 2):
current_byte = file.read(1)
# if eof
if len(current_byte) == 0:
break
# copy to buffer
buf[char_position] = ord(current_byte)
char_position += 1
file_position += 1
file.close()
@micropython.viper
def draw_char(sprite:int,direction,rot:int,char_x:int,char_y:int):
char_ptr = ptr16(chars)
player_ptr = ptr32(player1)
screen_ptr = ptr16(LCD.buffer)
y1=0
y2=0
scale = player_ptr[C_SIZE]
if direction:
dir1=-1
dir2=15
else:
dir1=1
dir2=0
sprite = sprite*TEXTURE_HEIGHT*TEXTURE_WIDTH
for y in range((TEXTURE_HEIGHT)*scale):
x1=0
x2=0
for x in range((TEXTURE_WIDTH)*scale):
x2=x1//scale
d = x2*dir1+dir2
color = char_ptr[y2*TEXTURE_WIDTH+d+sprite]
if color:
if rot:
x3=x
y3=y
else:
x3=y
y3=x
if not rot and direction:
x3=(TEXTURE_WIDTH*scale) - x3
y3=(TEXTURE_HEIGHT*scale) - y3
y4=(char_y+y3)
x4=(char_x+x3)
#screen_ptr[(char_y+y3)*MAXSCREEN_X+(char_x+x3)] = color
if x4>0 and y4>0 and x4<MAXSCREEN_X and y4<MAXSCREEN_Y:
screen_ptr[y4*MAXSCREEN_X+x4] = color
x1+=1
y1+=1
y2=y1//scale
def init_pot():
global POT_X,POT_Y,POT_X_ZERO,POT_Y_ZERO
POT_X = machine.ADC(27)
POT_Y = machine.ADC(26)
POT_X_ZERO = 0
POT_Y_ZERO = 0
for i in range(1000):
POT_X_ZERO += POT_X.read_u16()
POT_Y_ZERO += POT_Y.read_u16()
POT_X_ZERO = POT_X_ZERO//1000
POT_Y_ZERO = POT_Y_ZERO//1000
pot_scale = 12
return
@micropython.viper
def read_pot():
global SOUND
board = ptr8(board1)
player_ptr = ptr32(player1)
pot_scale = 12
x_inc = int(POT_X.read_u16() - POT_X_ZERO)>>pot_scale
y_inc = int(POT_Y.read_u16() - POT_Y_ZERO)>>pot_scale
if int(abs(x_inc))<2:
x_inc=0
if int(abs(y_inc))<2:
y_inc=0
if x_inc>0:
player_ptr[DIR] = 1
if x_inc<0:
player_ptr[DIR] = 0
x = int(player.x_offset)
y = int(player.y_offset)
#print(x_inc,y_inc," ",end='\r')
x2 = x//SCREEN_SCALE+(MAXBOARD_X//SCREEN_SCALE//2)
y2 = y//SCREEN_SCALE+(MAXBOARD_Y//SCREEN_SCALE//2)
addr_org=y2*MAXBOARD_X + x2
x1 = x - x_inc
y1 = y + y_inc
x2 = x1//SCREEN_SCALE+(MAXBOARD_X//SCREEN_SCALE//2)
y2 = y1//SCREEN_SCALE+(MAXBOARD_Y//SCREEN_SCALE//2)
addr_new=y2*MAXBOARD_X + x2
wall=board[addr_new]
if wall==0:
board[addr_org] = 0
board[addr_new] = 2
SOUND = 1
if player_ptr[SWING] == 10:
if wall==1:
board[addr_new] = 0
player_ptr[HP] -= 10<<10
if wall==1 or x1<0 or x1>(MAXSCREEN_X*SCREEN_SCALE)-MAXSCREEN_X:
x1 = int(player.x_offset)
if wall==1 or y1<0 or y1>(MAXSCREEN_Y*SCREEN_SCALE)-MAXSCREEN_Y:
y1 = int(player.y_offset)
return (x1,y1)
def conway_go(num_frames):
counts = bytearray(50)
counts[3] = 10
count = 0
old_count = 1
i = 0
total = 0
while(counts[1] != counts[2] or counts[3] != counts[4]) and total < 350:
old_count = count
count=conway_step(board1,board2) # do 1 iteration
counts[i] = count
i += 1
total += 1
if i>10:
i=0
conway_step(board2,board1) # do 1 iteration
LCD.show()
# randomize the start
def conway_rand():
for x in range(1,MAXSCREEN_X-1): # loop over x coordinates
for y in range(1,MAXSCREEN_Y-1): # loop over y coordinates
y1 = y<<6
if randint(0,100)<45:
self = 1
else:
self = 0
#board1[x + (y<<6)] = self # set the pixel randomly
board1[x + (y*MAXSCREEN_X)] = self
#matrix_buffer[(x + y1)<<1] = board1[x + y1]
live_boarder()
def live_boarder():
x_offset=MAXBOARD_X//SCREEN_SCALE//2
y_offset=MAXBOARD_Y//SCREEN_SCALE//2
for x in range(MAXBOARD_X):
board1[x+(MAXBOARD_X*y_offset)] = ROCK # ok
board1[x+(MAXBOARD_X*(MAXBOARD_Y-y_offset))] = ROCK
for y in range(MAXBOARD_Y):
board1[(MAXBOARD_X-x_offset)+y*MAXBOARD_X] = ROCK
board1[y*MAXBOARD_X+x_offset] = ROCK # ok
x = player.x_offset + (MAXBOARD_X//SCREEN_SCALE//2)
y = player.y_offset + (MAXBOARD_Y//SCREEN_SCALE//2)
board1[y*MAXBOARD_X+x]=2
@micropython.viper
def conway_step(input_board,output_board)->int:
input_b=ptr8(input_board)
outpt_b=ptr8(output_board)
matrix=ptr16(LCD.buffer)
count = 0
live_boarder()
for x in range(1,MAXSCREEN_X-1): # loop over x coordinates
for y in range(1,MAXSCREEN_Y-1): # loop over y coordinates
# count number of neighbours
#y1=y<<6
y1= y*MAXSCREEN_X
y_LCD = y*MAXSCREEN_X
#y2=(y+1)<<6
#y3=(y-1)<<6
y2 = (y+1)*MAXSCREEN_X
y3 = (y-1)*MAXSCREEN_X
num_neighbours = int(
input_b[ x - 1 + y3]
+ input_b[x + y3]
+ input_b[x + 1 + y3]
+ input_b[x - 1 + y1]
+ input_b[x + 1 + y1]
+ input_b[x + 1 + y2]
+ input_b[x + y2]
+ input_b[x - 1 + y2]
)
# check if the centre cell is alive or not
self = input_b[x+y1]
outpt_b[ x + y1] = self
matrix[x + y_LCD] = self * BROWN
# apply the rules of life
# life = B3/S23
# cave = B678/S345678
#if self and not (2 <= num_neighbours <= 3): # life
if self and num_neighbours < 3 : # cave
outpt_b[ x + y1] = 0 # not enough, or too many neighbours: cell dies
matrix[x + y_LCD] = 0
#elif not self and num_neighbours == 3:
elif not self and num_neighbours > 5:#5
outpt_b[ x + y1] = 1 # exactly 3 neighbours around an empty cell: cell is born
matrix[x + y_LCD] = BROWN
count += 1
return count
def init_enemy():
enemys = [2,4,5]
while NUM_ENEMY==1: # testing
x = 10 + MAXBOARD_X//SCREEN_SCALE//2
y = 10 + MAXBOARD_Y//SCREEN_SCALE//2
enemy[0] = x
enemy[1] = y
board1[y*MAXBOARD_X+x] = 3
return
x = 20 + MAXBOARD_X//SCREEN_SCALE//2
y = 5 + MAXBOARD_Y//SCREEN_SCALE//2
enemy[10+0] = x
enemy[10+1] = y
board1[y*MAXBOARD_X+x] = 3
return
x_offset=MAXBOARD_X//SCREEN_SCALE//2
y_offset=MAXBOARD_Y//SCREEN_SCALE//2
for i in range(NUM_ENEMY):
empty=True
while empty:
x=randint(x_offset,MAXBOARD_X-x_offset)
y=randint(y_offset,MAXBOARD_Y-y_offset)
if board1[y*MAXBOARD_X+x] == 0:
e=enemys[randint(0,2)]
board1[y*MAXBOARD_X+x] = e
enemy[i*10+0] = x
enemy[i*10+1] = y
enemy[i*10+2] = 100 # hp
enemy[i*10+3] = 1 # dir
enemy[i*10+4] = e
empty = False
@micropython.viper
def draw_enemy():
global SOUND
enemy_ptr = ptr8(enemy)
board = ptr8(board1)
health_ptr = ptr16(health)
player_ptr = ptr32(player1)
swing = player_ptr[SWING]
px = int(player.x_offset)
py = int(player.y_offset)
i = 0
hit = 0
while i<NUM_ENEMY:
if int(randint(0,10))==10:
move_enemy(i)
ex = enemy_ptr[i*10+0]
ey = enemy_ptr[i*10+1]
dx = int(abs((ex*8-px-80)))
dy = int(abs((ey*8-py-64)))
dx2 = ex*7 + (ex-px) -16
dy2 = ey*7 + (ey-py) -16
if dx<90 and dy<80 and enemy_ptr[i*10+2]>0:
draw_char(enemy_ptr[i*10+4],1,enemy_ptr[i*10+3],dx2,dy2)
if dx<25 and dy<25:
player_ptr[HP] -= 200
#if player_ptr[HP]<=0:
# player_death()
hp = enemy_ptr[i*10+2]
LCD.text(str(hp),dx2,dy2-8,health_ptr[hp>>2])
if swing==10 and int(SOUND)==0:
enemy_ptr[i*10+2] -= 10
SOUND = 3
hit = 1
if enemy_ptr[i*10+2] <=0:
board[enemy_ptr[i*10+1]*MAXBOARD_X+enemy_ptr[i*10+0]]=0
SOUND = 4
i+=1
if swing==10 and hit == 0 and int(SOUND)==0:
SOUND = 2
if player_ptr[HP]<=0:
player_death()
@micropython.viper
def move_enemy(i:int):
enemy_ptr=ptr8(enemy) # x,y,hp,dir,type
board = ptr8(board1)
move_x = int(randint(-1,1))
move_y = int(randint(-1,1))
enemy_x = enemy_ptr[i*10+0]
enemy_y = enemy_ptr[i*10+1]
addr = (enemy_y + move_y) * MAXBOARD_X + (enemy_x + move_x)
if board[addr] == 0:
board[enemy_y * MAXBOARD_X + enemy_x] = 0
board[addr] = enemy_ptr[i*10+4]
enemy_ptr[i*10+0] = enemy_x + move_x
enemy_ptr[i*10+1] = enemy_y + move_y
@micropython.viper
def draw_screen(scale:int): # deprecated
board=ptr8(board1)
screen=ptr16(LCD.buffer)
y1=int(player.y_offset)
for y in range(MAXSCREEN_Y):
x1=int(player.x_offset)
y2=y1//scale
for x in range(MAXSCREEN_X):
x2=x1//scale
color = board[y2*MAXSCREEN_X+x2]
if color==1:
screen[y*MAXSCREEN_X+x] = 0x2159 #0xffff
if color==3:
screen[y*MAXSCREEN_X+x] = 0xfe00
x1+=1
y1+=1
SCREEN_CTL = const(0)
BOARD_CTL = const(4)
XOFFSET_CTL = const(8)
YOFFSET_CTL = const(12)
SCALE_CTL = const(16)
MAXSCREEN_X_CTL = const(20)
MAXSCREEN_Y_CTL = const(24)
COLOR_CTL = const(28)
X1_CTL = const(32)
Y2_CTL = const(36)
ROCKS_CTL = const(40)
@micropython.asm_thumb
def draw_asm(r0):
mov(r7,r0)
mov(r2,0) # r2 = y
label(LOOP_Y)
ldr(r0,[r7,YOFFSET_CTL]) # y1
ldr(r1,[r7,SCALE_CTL]) #
bl(DIVIDE) # y2 = y1//scale
str(r0,[r7,Y2_CTL])
mov(r3,0) # r3 = x
ldr(r5,[r7,XOFFSET_CTL]) # x1=int(player.x_offset)
str(r5,[r7,X1_CTL]) #
label(LOOP_X)
ldr(r0,[r7,X1_CTL]) # x1
ldr(r1,[r7,SCALE_CTL]) #
bl(DIVIDE) # r0 = x2=x1//scale
ldr(r5,[r7,MAXSCREEN_X_CTL]) #
ldr(r4,[r7,Y2_CTL])
mul(r4,r5) # y2*MAXSCREEN_X
add(r4,r4,r0) # y2*MAXSCREEN_X+x2
ldr(r5,[r7,BOARD_CTL]) #
add(r4,r4,r5) # board addr
ldrb(r4,[r4,0]) # r4 = color
cmp(r4,1)
bne(NEXT_X)
ldr(r5,[r7,MAXSCREEN_X_CTL])
mul(r5,r2) # y*MAXSCREEN_X
add(r5,r5,r3) # y*MAXSCREEN_X + x
add(r5,r5,r5) # double for strh
ldr(r6,[r7,SCREEN_CTL])
add(r6,r6,r5) # screen addr
ldr(r5,[r7,ROCKS_CTL])
add(r4,r4,r4) # double ldrh
add(r5,r5,r4)
ldrh(r5,[r5,0])
#ldr(r5,[r7,COLOR_CTL])
strh(r5,[r6,0])
label(NEXT_X)
ldr(r6,[r7,X1_CTL])
add(r6,r6,1)
str(r6,[r7,X1_CTL])
ldr(r5,[r7,MAXSCREEN_X_CTL])
add(r3,r3,1)
cmp(r3,r5) # x<MAXSCREEN_X
blt(LOOP_X)
label(NEXT_Y)
ldr(r6,[r7,YOFFSET_CTL])
add(r6,r6,1)
str(r6,[r7,YOFFSET_CTL])
ldr(r5,[r7,MAXSCREEN_Y_CTL])
add(r2,r2,1)
cmp(r2,r5) # y<MAXSCREEN_Y
blt(LOOP_Y)
b(EXIT)
# -------------------divide routine----------r0=r0//r1-------------
label(DIVIDE)
mov(r6,0xd0) # SIO_BASE _u(0xd000_0000)
lsl(r6,r6,24)
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(EXIT)
@micropython.viper
def button():
player_ptr = ptr32(player1)
if fire.value():
player_ptr[SWING] = 0
else:
if player_ptr[SWING] >1:
player_ptr[SWING] -= 1
else:
player_ptr[SWING] = 10
if player_ptr[SWING]<5:
draw_char(3,player_ptr[DIR] ^ 1,0,PLAYER_X,PLAYER_Y)
else:
draw_char(3,player_ptr[DIR],1,PLAYER_X,PLAYER_Y)
@micropython.viper
def draw_info(fps):
player_ptr = ptr32(player1)
color_ptr = ptr16(health)
LCD.text(str(fps),140,0,0xffff)
LCD.text(str(player_ptr[HP]>>10),110,0,0xffff)
LCD.rect(0,0,player_ptr[HP]>>10,5,color_ptr[player_ptr[HP]>>11],1)
def sound_thread():
global SOUND
sleep(1)
while(1):
if SOUND==1:
sound_move()
if SOUND==1:
SOUND = 0
if SOUND==2:
sound_miss()
if SOUND==2:
SOUND = 0
if SOUND==3:
sound_hit()
if SOUND==3:
SOUND = 0
if SOUND==4:
sound_die()
if SOUND==4:
SOUND = 0
def sound_move():
volume = 100
tone = 100
power.on()
Buzz.duty_u16(volume)
Buzz.freq(tone)
power.on()
for delay in range(20000):
pass
power.off()
def sound_miss():
volume = 1000
tone = 500
power.on()
for tone in range(1000,100,-1):
Buzz.duty_u16(volume)
Buzz.freq(tone)
for delay in range(50):
pass
power.off()
def sound_hit():
volume = 1000
tone = 500
power.on()
for tone in range(1000,500,-1):
Buzz.duty_u16(volume)
Buzz.freq(tone)
for delay in range(100):
pass
Buzz.duty_u16(20000)
Buzz.freq(1000)
for delay in range(10000):
pass
power.off()
def sound_die():
volume = 1000
power.on()
Buzz.duty_u16(volume)
for i in range(1000,900,-1):
Buzz.duty_u16(volume)
Buzz.freq(i)
volume -= 5
for delay in range(2000):
pass
power.off()
def player_death():
for i in range(2,8):
player1[C_SIZE]=i
LCD.fill(0)
draw_char(1,0,1,140//i,100//i)
LCD.show()
sleep(.2)
LCD.fill(0)
sleep(.1)
draw_char(2,0,1,140//i,100//i)
LCD.show()
sleep(1)
LCD.fill(0)
draw_char(2,0,0,140//i,100//i)
LCD.text('GAME OVER',40,20,0xffff)
LCD.show()
sleep(1)
player1[C_SIZE]=2
while fire.value():
pass
init_game()
def title():
player1[C_SIZE]=8
for i in range(-90,0,5):
LCD.fill(0)
draw_char(1,0,1,i,0)
draw_char(3,1,0,i,0)
draw_char(2,1,1,50-i,0)
LCD.show()
LCD.fill(0)
draw_char(1,0,1,i,0)
draw_char(3,0,1,i,0)
draw_char(2,1,1,50-i,0)
LCD.show()
sleep(.1)
LCD.fill(0)
draw_char(1,0,1,i,0)
draw_char(3,1,0,i,0)
draw_char(2,0,0,50-i,50)
LCD.text('CAVE',100,20,0xffff)
LCD.text('ADVENTURE',90,35,0xffff)
LCD.show()
while fire.value():
pass
player1[C_SIZE]=2
def init_game():
init_player()
conway_rand()
conway_go(100)
live_boarder()
init_enemy()
def color_test():
LCD.fill(0)
LCD.show()
#exit()
print(len(health), 100/len(health))
for i, color in enumerate(health):
#print(i,color)
LCD.line(i,0,i,127,color)
LCD.show
for d in range(100000):
pass
LCD.show
exit()
if __name__=='__main__':
fire = Pin(22, Pin.IN, Pin.PULL_UP)
power = Pin(16, machine.Pin.OUT)
Buzz = machine.PWM(Pin(17, machine.Pin.OUT))
pwm = PWM(Pin(13))
pwm.freq(1000)
pwm.duty_u16(32768)#max 65535
LCD = LCD_1inch8()
fade(rocks,BROWN,LCD.BLACK)
fade(health,LCD.RED,LCD.YELLOW)
fade(health,LCD.YELLOW,LCD.GREEN)
draw_ctl=array.array('I',(addressof(LCD.buffer),addressof(board1),XOFFSET_CTL,YOFFSET_CTL,
8,MAXSCREEN_X,MAXSCREEN_Y,BROWN,X1_CTL,Y2_CTL,addressof(rocks),0))
machine.mem32[0x40008048] = 1<<11 # enable peri_ctrl clock
blit_image_file(chars,"chars.bin",16,16*NUM_TEXTURES,16,16)
gc.collect()
print('mem free ',gc.mem_free())
init_pot()
title()
#player_death()
#exit()
init_game()
_thread.start_new_thread(sound_thread, ())
fps = 0
while(1):
gticks = ticks_us()
LCD.fill(0)
player.x_offset,player.y_offset = read_pot()
#draw_screen(SCREEN_SCALE)
draw_ctl[2] = player.x_offset
draw_ctl[3] = player.y_offset
draw_asm(draw_ctl)
draw_char(1,player1[DIR],1,PLAYER_X,PLAYER_Y)
draw_enemy()
button()
draw_info(fps)
LCD.show()
fps = 1_000_000//ticks_diff(ticks_us(),gticks)
exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment