Created
March 28, 2023 23:06
-
-
Save samneggs/4d429fbb491809de81f5ebb62f28a39c to your computer and use it in GitHub Desktop.
Hill Climb Racer on Pi Pico in MicroPython
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
# 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