Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created March 28, 2023 23:06
Show Gist options
  • Save samneggs/4d429fbb491809de81f5ebb62f28a39c to your computer and use it in GitHub Desktop.
Save samneggs/4d429fbb491809de81f5ebb62f28a39c to your computer and use it in GitHub Desktop.
Hill Climb Racer on Pi Pico in MicroPython
# Hill Climb
from lcd_1_8 import LCD_1inch8
import machine
from machine import Pin, PWM
from uctypes import addressof
from time import sleep, ticks_us, ticks_diff, ticks_ms
import gc, _thread, array, framebuf
from sys import exit
from micropython import const
from random import randint
from math import sin,cos,tan,radians,sqrt, atan
from car_img import car_img
car_img2 = car_img[:]
MAXSCREEN_X = const(160)
MAXSCREEN_Y = const(128)
SCALE = const(13)
BROWN = const(0x2159)
MAX_LAND = const(5000)
# gggbbbbb_rrrrrggg
SKYBLUE = const(0b11111111_00111011)
DRKGREEN = const(0b11100000_00000001)
CAR_PARAMS = const(13)
CAR_X1 = const(0)
CAR_Y1 = const(1)
CAR_X2 = const(2)
CAR_Y2 = const(3)
CAR_VX1 = const(4)
CAR_VY1 = const(5)
CAR_VX2 = const(6)
CAR_VY2 = const(7)
CAR_GROUND1 = const(8)
CAR_GROUND2 = const(9)
CAR_LENGTH = const(10)
CAR_POS = const(11)
CAR_DEG = const(12)
GAME_PARAMS = const(10)
GAME_FPS = const(0)
char_map=array.array('b',(
0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00, # U+0030 (0)
0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00, # U+0031 (1)
0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00, # U+0032 (2)
0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00, # U+0033 (3)
0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00, # U+0034 (4)
0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00, # U+0035 (5)
0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00, # U+0036 (6)
0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00, # U+0037 (7)
0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00, # U+0038 (8)
0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00)) # U+0039 (9)
@micropython.viper
def show_num_viper(num:int,x_offset:int,y_offset:int,color:int):
char_ptr = ptr8(char_map)
screen_ptr = ptr16(LCD.buffer)
size = 1 # 1,2,3
char = 0
offset = MAXSCREEN_X*y_offset+x_offset
if num < 0: num = num * -1
while num > 0:
total = num//10
digit = num - (total * 10)
num = total
for y in range(8):
row_data = char_ptr[digit*8+y]
for x in range(8):
if row_data & (1<<x) > 0:
addr = size*y*MAXSCREEN_X+x-(char*8)+offset
screen_ptr[addr] = color
if size>1:
screen_ptr[MAXSCREEN_X+addr] = color
if size>2:
screen_ptr[2*MAXSCREEN_X+addr] = color
char += 1
@micropython.viper
def rot_viper(deg:int, width:int, height:int,source:ptr16,dest:ptr16): # 0-360 degrees, pixel width and height of bitmap
car = ptr32(CAR)
if car[CAR_Y1] > car[CAR_Y2]:
deg = 180 + deg
else:
deg = 180 - deg
sin=ptr32(isin)
cos=ptr32(icos)
offset_x = width//2
offset_y = height//2
cos_rad = cos[deg]
sin_rad = sin[deg]
for i in range(width*height):
dest[i]=0
y=height-1
while y>=0:
x=width-1
while x>=0:
i=y*height+x
color=source[i]
if color or 1:
x2 = width - x
adjusted_x = (x2 - offset_x)
adjusted_y = (y - offset_y)
qx = offset_x
qx+= (cos_rad * adjusted_x)>>SCALE
qx+= (sin_rad * adjusted_y)>>SCALE
if qx>=0 and qx<width:
qy = offset_y
qy+= (sin_rad * adjusted_x)>>SCALE
qy-= (cos_rad * adjusted_y)>>SCALE
if qy>=0 and qy<height:
i = qy * width + qx
dest[i]=color
x-=1
y-=1
def init_imath(): #integer math
global isin,icos,iatan
isin = array.array('i',())
icos = array.array('i',())
for i in range(0,360):
isin.append(int(sin(radians(i))*(1<<SCALE)))
icos.append(int(cos(radians(i))*(1<<SCALE)))
def isqrt(n):
return int(sqrt(n))
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
@micropython.viper
def read_pot():
car = ptr32(CAR)
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 car[CAR_GROUND1] == 0 and car[CAR_GROUND2] == 0: # in air
car[CAR_VY1] += (y_inc<<6)
car[CAR_VX1] -= (x_inc<<7)
car[CAR_VX2] -= (x_inc<<7)
if not FIRE_BUTTON.value():
car[CAR_VX1] -= (car[CAR_VX1]>>3) # brake
car[CAR_VX2] -= (car[CAR_VX2]>>3) # brake
def init_land():
global HILL, HILL_EXP
HILL = bytearray(MAX_LAND)
HILL_EXP = bytearray(MAX_LAND)
h=0
s=0
d=1
steep = 50
max_height = 1
start = max_height
min_height = 1
LCD.rect(0,0,MAXSCREEN_X,80,0b11111111_00111011,1) # sky blue
LCD.rect(0,80,MAXSCREEN_X,MAXSCREEN_Y-80,0b11100000_00000001,1)
for x in range(MAX_LAND):
if x> 60-start:
s+=d
h+=s
h2 = h//steep # hill steepness
old_d = d
if h2>max_height: # change direction
d=-1
if h2<min_height:
d=1
if old_d != d:
max_height = randint(0,0) # :(
min_height = randint(0,0)
steep = randint(50,100)
y = 100-h2
else:
y=100
if y > MAXSCREEN_Y:
y = MAXSCREEN_Y
d = 1
HILL[x] = y
HILL_EXP[x] = y
def init_car():
global CAR, CAR_POLY
CAR = array.array('i',0 for _ in range(CAR_PARAMS))
CAR_POLY = array.array('h',(-5,0,25,0,25,-10,-5,-10,-5,0))
CAR[CAR_X1] = 10<<SCALE
CAR[CAR_Y1] = 50<<SCALE
CAR[CAR_X2] = CAR[CAR_X1]
CAR[CAR_Y2] = CAR[CAR_Y1]
CAR[CAR_VX1] = 2<<SCALE
CAR[CAR_VY1] = 1<<SCALE
CAR[CAR_VX2] = CAR[CAR_VX1]
CAR[CAR_VY2] = CAR[CAR_VY1]
CAR[CAR_LENGTH] = 20
CAR[CAR_POS] = 40
def init_game():
global GAME
GAME = array.array('i',0 for _ in range(GAME_PARAMS))
GAME[GAME_FPS] = 0
@micropython.viper
def draw():
hill = ptr8(HILL)
car = ptr32(CAR)
game = ptr32(GAME)
poly = ptr16(CAR_POLY)
c_x1 = car[CAR_X1]>>SCALE
c_y1 = car[CAR_Y1]>>SCALE
c_x2 = car[CAR_X2]>>SCALE
c_y2 = car[CAR_Y2]>>SCALE
LCD.text('FPS:',0,0,0xff)
show_num_viper(game[GAME_FPS],41,0,0xff)
show_num_viper(c_x1,41,10,0xff)
for h_x in range(MAXSCREEN_X):
h_y = hill[h_x+(car[CAR_X1]>>SCALE)]
LCD.line(h_x,MAXSCREEN_Y,h_x,h_y,BROWN) # hill
poly[3] = c_y1-c_y2
poly[5] = c_y1-c_y2 - 10
LCD.ellipse(car[CAR_POS],c_y1,4,4,0x0,True) # right wheel
LCD.ellipse(car[CAR_POS]-car[CAR_LENGTH],c_y2,4,4,0x0,True) # left wheel
if car[CAR_Y1] >= car[CAR_Y2]:
LCD.blit(car_fb,car[CAR_POS]-car[CAR_LENGTH]-6,c_y2-car[CAR_LENGTH],0)
else:
LCD.blit(car_fb,car[CAR_POS]-car[CAR_LENGTH]-6,c_y2-18-car[CAR_LENGTH]//5,0)
#LCD.poly(car[CAR_POS]-car[CAR_LENGTH],c_y2,CAR_POLY,31<<3,1)
LCD.show()
LCD.rect(0,0,MAXSCREEN_X,80,SKYBLUE,1) # sky blue
LCD.rect(0,80,MAXSCREEN_X,MAXSCREEN_Y-80,DRKGREEN,1) # green ground
@micropython.viper
def calc():
car = ptr32(CAR)
hill = ptr8(HILL)
h_y1 = hill[(car[CAR_X1]>>SCALE)+car[CAR_POS]]
h_y2 = hill[(car[CAR_X2]>>SCALE)+car[CAR_POS]-car[CAR_LENGTH]]
car[CAR_X1] += car[CAR_VX1]
car[CAR_Y1] += car[CAR_VY1]
car[CAR_X2] += car[CAR_VX2]
car[CAR_Y2] += car[CAR_VY2]
car[CAR_VY1] += 100
car[CAR_VY2] += 100
if car[CAR_X1] < 0:
car[CAR_X1] = 0
if car[CAR_X2] < 0:
car[CAR_X2] = 0
if car[CAR_X1]>>SCALE > MAX_LAND - MAXSCREEN_X:
car[CAR_X1] = 0
car[CAR_X2] = 0
if car[CAR_Y1]>>SCALE > h_y1: # on ground
car[CAR_Y1] = h_y1<<SCALE
car[CAR_VY1] -= 500
car[CAR_GROUND1] = 1
else:
car[CAR_GROUND1] = 0
if car[CAR_Y2]>>SCALE > h_y2: # on ground
car[CAR_Y2] = h_y2<<SCALE
car[CAR_VY2] -= 500
car[CAR_GROUND2] = 1
else:
car[CAR_GROUND2] = 0
x1 = car[CAR_X1]>>4
y1 = car[CAR_Y1]>>4
x2 = car[CAR_X2]>>4
y2 = car[CAR_Y2]>>4
dist = int(isqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))) # delta between wheels
car[CAR_LENGTH] = 20 - (dist>>10)
car[CAR_DEG] = 45*dist//6000 # angle of the car
if car[CAR_DEG]>359: car[CAR_DEG] -= 360
rot_viper(car[CAR_DEG], 32, 32,car_img,car_img2)
def main():
init_imath()
init_pot()
init_car()
init_game()
init_land()
debug_ticks = 0
pot_ticks = 0
gc.collect()
print(gc.mem_free())
while not EXIT:
gticks = ticks_ms()
if gticks-pot_ticks>30:
pot_ticks = gticks
read_pot()
if gticks-debug_ticks>500:
debug_ticks = gticks
calc()
draw()
GAME[GAME_FPS] = 1_000//ticks_diff(ticks_ms()+1,gticks)
def shutdown():
global EXIT
EXIT = True
Pin(16,Pin.OUT).low() # buzzer off
pwm.deinit()
Pin(13,Pin.OUT).low() # screen off
gc.collect()
print(gc.mem_free())
print('Core0 Stop')
exit()
if __name__=='__main__':
FIRE_BUTTON = Pin(22, Pin.IN, Pin.PULL_UP)
Pin(16,Pin.OUT).high()
machine.freq(200_000_000)
machine.mem32[0x40008048] = 1<<11 # enable peri_ctrl clock
pwm = PWM(Pin(13))
pwm.freq(1000)
pwm.duty_u16(0x7fff)#max 0xffff
LCD = LCD_1inch8()
LCD.fill(0)
LCD.show()
EXIT = 0
car_fb = framebuf.FrameBuffer(car_img2, 32, 32, framebuf.RGB565)
# _thread.start_new_thread(core1, ())
try:
main()
shutdown()
except KeyboardInterrupt :
shutdown()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment