Last active
June 8, 2023 21:27
-
-
Save samneggs/c3dc7710438549c1fca93943f48d0ab3 to your computer and use it in GitHub Desktop.
Breakout game on 3.5" display in MicroPython
This file contains hidden or 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
from LCD_3inch5 import LCD_3inch5 | |
import framebuf, gc, _thread | |
from micropython import const | |
from machine import Timer, UART, Pin | |
#constants | |
BRICK_WIDTH = const(20) | |
BRICK_HEIGHT = const(10) | |
BALL_SIZE = const(5) | |
BALL_X_START = const(390) | |
BALL_Y_START = const(160) | |
THREADED = const(0) | |
SOUNDS = const(1) | |
DEMO = const(0) | |
USE_UART = const(0) | |
# 7 Segment Chars | |
# Tony Goodhew 14th July 2021 | |
nums =[0,1,1,1,1,1,1, # 0 # One row per digit | |
0,1,1,0,0,0,0, # 1 | |
1,1,0,1,1,0,1, # 2 | |
1,1,1,1,0,0,1, # 3 | |
1,1,1,0,0,1,0, # 4 | |
1,0,1,1,0,1,1, # 5 | |
1,0,1,1,1,1,1, # 6 | |
0,1,1,0,0,0,1, # 7 | |
1,1,1,1,1,1,1, # 8 | |
1,1,1,0,0,1,1, # 9 | |
1,1,1,1,1,0,1, # a = 10 - HEX characters | |
0,0,1,1,1,1,1, # b = 11 | |
0,0,0,1,1,0,1, # c = 12 | |
0,1,1,1,1,0,1, # d = 13 | |
1,1,0,1,1,1,1, # e = 14 | |
1,0,0,0,1,1,1, # f = 15 | |
1,1,1,1,0,1,1, # g needed for seg! | |
0,0,0,0,0,0,1, # - | |
0,0,0,0,0,0,0] # Blank | |
LCD = LCD_3inch5(200) | |
LCD.bl_ctrl(100) | |
LCD.FillRectangle(0,0, 480,320, LCD.BLACK) | |
colors = [LCD.RED,LCD.ORANGE,LCD.GREEN,LCD.YELLOW] | |
scoring = [7,5,3,1] | |
C = [LCD.BLACK,LCD.WHITE] | |
Buzz = machine.PWM(machine.Pin(14)) | |
class Circle(): | |
def __init__(self,x,y,w,h,buf): | |
self.x = x | |
self.y = y | |
self.ax = -1 | |
self.ay = -1 | |
self.w = w | |
self.h = h | |
self.buf = buf | |
class Brick(): | |
def __init__(self,row,col,x,y,color,points): | |
self.row = row | |
self.col = col | |
self.x = x | |
self.y = y | |
self.color = color | |
self.points = points | |
class Score(): | |
def __init__(self): | |
self.lives = 3 | |
self.score = 0 | |
self.bricks = 0 | |
self.show = False | |
class Paddle(): | |
def __init__(self): | |
self.x = 400 | |
self.y = 160 | |
self.ay = 1 | |
self.oldy = 0 | |
self.w = 40 | |
self.h = 5 | |
self.touchpos = 160 | |
self.buf = 0 | |
self.show = False | |
def init_paddle(self): | |
width = 5 | |
height = self.w+10 | |
display_buffer = bytearray(width * height * 2) | |
self.buf = framebuf.FrameBuffer(display_buffer,width,height, framebuf.RGB565) | |
self.buf.fill_rect(0,5,width,paddle.w,LCD.WHITE) | |
class Beep(): | |
def __init__(self): | |
self.brick = False | |
self.wall = False | |
self.paddle = False | |
self.miss = False | |
class Uart: | |
def __init__(self,num,tx_pin,rx_pin): | |
self.uart = UART(num,baudrate=115200,tx=Pin(tx_pin),rx=Pin(rx_pin)) | |
self.rx_buf = bytearray(6) | |
self.tx_buf = bytearray(6) | |
self.x = 0 | |
self.y = 0 | |
self.left_pushed = 0 | |
self.right_pushed = 0 | |
self.pot_raw = 0 | |
self.pot_old = 0 | |
def check(self): | |
if self.uart.any() != False: | |
self.uart.readinto(self.rx_buf) | |
print(self.rx_buf) | |
self.x = self.rx_buf[0]-self.rx_buf[1] | |
self.y = self.rx_buf[2]-self.rx_buf[3] | |
self.left_pushed = self.rx_buf[4] == 1 | |
self.right_pushed = self.rx_buf[4] == 2 | |
self.pot_raw = self.rx_buf[5] | |
print(self.pot_raw ) | |
def send(self): | |
if joystick.x>0: | |
self.tx_buf[0] = joystick.x | |
else: | |
self.tx_buf[0] = 0 | |
if joystick.x<0: | |
self.tx_buf[1] = abs(joystick.x) | |
else: | |
self.tx_buf[1] = 0 | |
if joystick.y>0: | |
self.tx_buf[2] = joystick.y | |
else: | |
self.tx_buf[2] = 0 | |
if joystick.y<0: | |
self.tx_buf[3] = abs(joystick.y) | |
else: | |
self.tx_buf[3] = 0 | |
self.tx_buf[4] = joystick.left_pushed|joystick.right_pushed<<1 | |
self.uart.write(self.tx_buf) | |
def seg(xx,yy,n,f): #,bg,fg): | |
global C | |
# (x, y, number, size-factor, background, foreground) | |
#c = [bg,fg] | |
p = n * 7 | |
LCD.FillRectangle(xx+0*f,yy+1*f,1*f,3*f,C[nums[p+6]]) | |
LCD.FillRectangle(xx+1*f,yy+4*f,3*f,1*f,C[nums[p+5]]) | |
LCD.FillRectangle(xx+5*f,yy+4*f,3*f,1*f,C[nums[p+4]]) | |
LCD.FillRectangle(xx+8*f,yy+1*f,1*f,3*f,C[nums[p+3]]) | |
LCD.FillRectangle(xx+5*f,yy+0*f,3*f,1*f,C[nums[p+2]]) | |
LCD.FillRectangle(xx+1*f,yy+0*f,3*f,1*f,C[nums[p+1]]) | |
LCD.FillRectangle(xx+4*f,yy+1*f,1*f,3*f,C[nums[p]]) | |
# 7 seg test | |
#for i in range(0,10): | |
# seg(400,290-i*30,i,5,LCD.BLACK,LCD.WHITE) # (x, y, number, size-factor, background, foreground) | |
def show_score(): | |
score.show = False | |
num = score.score | |
#num = 123 | |
places = 0 | |
if num==0: | |
seg(420,200,0,5) #,LCD.BLACK,LCD.WHITE) | |
while num>0: | |
digit = num % 10 | |
ypos = 200 + places | |
#draw_object2(score_list[digit],False) | |
seg(420,ypos,digit,5) #,LCD.BLACK,LCD.WHITE) | |
num //= 10 | |
places += 30 | |
def show_lives(): | |
seg(420,50,score.lives,5) #,LCD.BLACK,LCD.WHITE) | |
def play_beep(freq,dur=0): | |
Buzz.freq(freq) | |
Buzz.duty_u16(2512) | |
for i in range(3000+dur): | |
pass | |
for i in range(2512,0,-1): | |
Buzz.duty_u16(i) | |
Buzz.duty_u16(0) | |
def fill_circle(obj, x0, y0, radius, color): | |
# From Adafruit GFX Arduino library | |
# Filled circle drawing function. Will draw a filled circule with | |
# center at x0, y0 and the specified radius. | |
obj.vline(x0, y0 - radius, 2*radius + 1, color ) | |
f = 1 - radius | |
ddF_x = 1 | |
ddF_y = -2 * radius | |
x = 0 | |
y = radius | |
while x < y: | |
if f >= 0: | |
y -= 1 | |
ddF_y += 2 | |
f += ddF_y | |
x += 1 | |
ddF_x += 2 | |
f += ddF_x | |
obj.vline(x0 + x, y0 - y, 2*y + 1, color) | |
obj.vline(x0 + y, y0 - x, 2*x + 1, color) | |
obj.vline(x0 - x, y0 - y, 2*y + 1, color) | |
obj.vline(x0 - y, y0 - x, 2*x + 1, color) | |
return obj | |
def init_ball(radius): | |
width = radius * 2 | |
height = radius *2 | |
display_buffer = bytearray(width * height * 2) | |
ball_buffer=framebuf.FrameBuffer(display_buffer,width,height, framebuf.RGB565) | |
ball_buffer = fill_circle(ball_buffer,radius,radius,radius-2,LCD.WHITE) | |
return ball_buffer | |
def show_ball(obj): | |
if not score.show or not THREADED: | |
paddle.show = True | |
#LCD.FillRectangle(ball.x,ball.y,ball.h,ball.w,LCD.BLACK) | |
LCD.show_xy(obj.x,obj.y,obj.x+obj.w,obj.y+obj.h,obj.buf) | |
paddle.show = False | |
def move_ball(obj): | |
obj.x+=obj.ax | |
obj.y+=obj.ay | |
if obj.x > 400: | |
obj.x = 400 | |
obj.ax = -obj.ax | |
if not THREADED: | |
play_beep(250,9000) | |
else: | |
beep.miss = True | |
miss() | |
return | |
if obj.x < 5: | |
obj.x = 5 | |
obj.ax = -obj.ax | |
beep.wall = True | |
if obj.y > 310: | |
obj.y = 310 | |
obj.ay = -obj.ay | |
beep.wall = True | |
if obj.y < 5: | |
obj.y = 5 | |
obj.ay = -obj.ay | |
beep.wall = True | |
if obj.x+BALL_SIZE*2 > paddle.x and obj.x < paddle.x+paddle.h and obj.y>paddle.y and obj.y<paddle.y+paddle.w: # hit paddle | |
#LCD.FillRectangle(paddle.x,paddle.y,paddle.h,paddle.w,LCD.BLUE) | |
#LCD.FillRectangle(obj.x,obj.y,obj.h,obj.w,LCD.GREEN) | |
#exit() | |
obj.x = paddle.x-BALL_SIZE*2 | |
obj.ax = -obj.ax | |
beep.paddle = True | |
if obj.x < 90: | |
for i in wall: | |
if i.color != LCD.BLACK: | |
brick_x1=i.x #(BRICK_HEIGHT+2)*i.col-10 | |
brick_x2=i.x+BRICK_HEIGHT #(BRICK_HEIGHT+2)*i.col+10 | |
brick_y1=i.y #(BRICK_WIDTH+2)*i.row-12 | |
brick_y2=i.y+BRICK_WIDTH #(BRICK_WIDTH+2)*i.row+12 | |
if obj.x < brick_x2 and obj.x+BALL_SIZE*2 > brick_x1 and obj.y < brick_y2 and obj.y+BALL_SIZE*2 > brick_y1: | |
#LCD.FillRectangle(brick_x1,brick_y1, BRICK_HEIGHT,BRICK_WIDTH, LCD.BLUE) | |
#LCD.FillRectangle(obj.x,obj.y, BALL_SIZE*2,BALL_SIZE*2, LCD.BLUE) | |
beep.brick = True | |
i.color = LCD.BLACK | |
score.bricks += 1 | |
collx=obj.x-brick_x1 #i.col*BRICK_HEIGHT | |
colly=obj.y-brick_y1 - BALL_SIZE #i.row*BRICK_WIDTH | |
# print('Ball=',obj.x,obj.y) | |
# print('Brick=',i.x,i. | |
# print('Bricks=',score.bricks) | |
# print('coll x,y',collx,colly) | |
score.score+=i.points | |
if score.bricks==112: | |
reset_game() | |
return | |
#debug_coll(brick_x1,brick_y1,collx,colly) | |
if THREADED: | |
_thread.exit() | |
#exit() | |
LCD.FillRectangle(ball.x,ball.y,ball.h,ball.w,LCD.BLACK) # clear ball dropings | |
if abs(collx)>=abs(colly): # up/down collision | |
obj.ax=-obj.ax | |
if collx>=0: | |
obj.x=brick_x2 | |
else: | |
obj.x=brick_x1 | |
else: # left/right collision | |
obj.ay=-obj.ay | |
if colly>=0: | |
#debug_coll(brick_x1,brick_y1) | |
obj.y=brick_y2 | |
else: | |
obj.y=brick_y1 | |
#print(gc.mem_free()) | |
LCD.FillRectangle(i.col*(BRICK_HEIGHT+2),i.row*(BRICK_WIDTH+2), BRICK_HEIGHT,BRICK_WIDTH, i.color) | |
score.show = True | |
if not THREADED: | |
pass | |
#show_score() | |
#LCD.FillRectangle(ball.x,ball.y,ball.h,ball.w,LCD.BLACK) # clear ball dropings | |
#show_ball(ball) | |
return | |
def miss(): | |
LCD.FillRectangle(ball.x,ball.y,ball.h,ball.w,LCD.BLACK) # clear ball dropings | |
score.lives-=1 | |
show_lives() | |
reset_ball() | |
if score.lives == 0: | |
score.lives = 3 | |
score.score = 0 | |
reset_game() | |
def debug_coll(brick_x1,brick_y1,collx,colly): | |
LCD.FillRectangle(paddle.x,paddle.y,paddle.h,paddle.w,LCD.BLUE) | |
LCD.FillRectangle(ball.x,ball.y,ball.h,ball.w,LCD.GREEN) | |
LCD.FillRectangle(brick_x1,brick_y1, BRICK_HEIGHT,BRICK_WIDTH, LCD.BLUE) | |
print(collx,colly) | |
exit() | |
def init_wall(): | |
bricks = [] | |
i=0 | |
for row in range(0,8,2): | |
for col in range(0,14): | |
x=row*(BRICK_HEIGHT+2) | |
y=col*(BRICK_WIDTH+2) | |
bricks.append(Brick(col,row,x,y,colors[row//2],scoring[row//2])) | |
x=(row+1)*(BRICK_HEIGHT+2) | |
bricks.append(Brick(col,row+1,x,y,colors[row//2],scoring[row//2])) | |
i+=2 | |
return (bricks) | |
def reset_wall(): | |
i=0 | |
for row in range(0,8,2): | |
for col in range(0,14): | |
wall[i].color = colors[row//2] | |
wall[i+1].color = colors[row//2] | |
i+=2 | |
def reset_ball(): | |
ball.x = BALL_X_START | |
ball.y = BALL_Y_START | |
ball.ax = -1 | |
ball.ay = -1 | |
def reset_game(): | |
LCD.FillRectangle(0,0, 480,320, LCD.BLACK) | |
reset_wall() | |
show_wall() | |
reset_ball() | |
show_ball(ball) | |
score.bricks = 0 | |
show_lives() | |
show_score() | |
LCD.show_xy(paddle.x,paddle.y,paddle.x+paddle.h,paddle.y+paddle.w,paddle.buf) | |
def show_wall(): | |
for i in wall: | |
LCD.FillRectangle(i.x,i.y, BRICK_HEIGHT,BRICK_WIDTH, i.color) | |
def get_paddle(): | |
get = LCD.touch_get() | |
#print(get) | |
if get != None: | |
paddle.touchpos = 290-int((get-430)*320//3270) | |
def move_paddle(): | |
if paddle.y < paddle.touchpos+5: | |
paddle.y += paddle.ay | |
LCD.show_xy(paddle.x,paddle.y,paddle.x+paddle.h,paddle.y+paddle.w,paddle.buf) | |
elif paddle.y > paddle.touchpos+5: | |
paddle.y -=paddle.ay | |
LCD.show_xy(paddle.x,paddle.y,paddle.x+paddle.h,paddle.y+paddle.w,paddle.buf) | |
if paddle.y != paddle.touchpos: | |
pass | |
#LCD.FillRectangle(470,paddle.y, 20,5, LCD.WHITE) | |
#LCD.show_xy(paddle.x,paddle.y,paddle.x+paddle.h,paddle.y+paddle.w,paddle.buf) | |
def check_sound(): | |
if beep.brick: | |
beep.brick = False | |
play_beep(2600) | |
if beep.wall: | |
beep.wall = False | |
play_beep(1000) | |
if beep.paddle: | |
beep.paddle = False | |
play_beep(500) | |
if beep.miss: | |
beep.miss = False | |
play_beep(250,9000) | |
def second_thread(): # sound | |
while(1): | |
check_sound() | |
if score.show and not paddle.show and 0 : | |
show_ball(ball) | |
score.show = False | |
show_score() | |
uart0 = Uart(0,0,1) | |
beep=Beep() | |
paddle = Paddle() | |
paddle.init_paddle() | |
score = Score() | |
wall = init_wall() | |
ball=Circle(BALL_X_START,BALL_Y_START,BALL_SIZE*2-1,BALL_SIZE*2-1,init_ball(BALL_SIZE)) | |
reset_game() | |
def main(tim): | |
if THREADED: | |
wdt.feed() | |
elif SOUNDS: | |
check_sound() | |
if score.show==True and ball.x>90: | |
show_score() | |
if ball.x % 40 == 0 and not DEMO: | |
if USE_UART: | |
uart0.check() | |
paddle.y+=uart0.y | |
else: | |
get_paddle() | |
#print(uart0.y) | |
if DEMO: | |
paddle.y=ball.y-20 | |
move_paddle() | |
move_ball(ball) | |
show_ball(ball) | |
try: | |
tim=Timer() | |
tim.init(mode=Timer.PERIODIC, freq=150, callback=main) | |
while(1): | |
pass | |
except KeyboardInterrupt: | |
LCD.bl_ctrl(0) | |
tim.deinit() | |
if THREADED: | |
gc.collect() | |
print('core 1') | |
#a_lock = _thread.allocate_lock() | |
wdt=machine.WDT(id=0, timeout=8000) | |
_thread.start_new_thread(second_thread, ()) | |
scan = 0 | |
while(0): | |
scan+=1 | |
if THREADED: | |
wdt.feed() | |
elif SOUNDS: | |
check_sound() | |
if score.show==True and ball.x>90: | |
show_score() | |
if scan>49: | |
get_paddle() | |
move_paddle() | |
if scan >45 or ball.x<100: | |
move_ball(ball) | |
show_ball(ball) | |
#print(gc.mem_free()) | |
if scan==50: | |
scan = 0 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment