Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created July 2, 2023 23:00
Show Gist options
  • Save samneggs/f463bfdb423b787793595089a2f045cc to your computer and use it in GitHub Desktop.
Save samneggs/f463bfdb423b787793595089a2f045cc to your computer and use it in GitHub Desktop.
Tetris on PI Pico in MicroPython
# Tetris
import tetris_title
from lcd_1_8 import LCD_1inch8
import machine
from machine import Pin, PWM, Timer
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 music import music, tetris
MAXSCREEN_X = const(160)
MAXSCREEN_Y = const(128)
SCALE = const(13)
GREY = const(0xa852)
BACKCOLOR = const(0b0000111_0000_0000)
PLAYER_PARAMS = const(10)
NEWPOS = const(0) # 10x20
OLDPOS = const(1) # 10x20
NEWROT = const(2) # 0-3
OLDROT = const(3) # 0-3
NEWSHAPE = const(4) # 0-6
OLDSHAPE = const(5) # 0-6
NEXTSHAPE = const(6) # 0-6
DEBOUNCE = const(7) #
DEBOUNCE2 = const(8) #
#directions
DIR_RIGHT = const(0)
DIR_LEFT = const(1)
DIR_DOWN = const(2)
DIR_ROT = const(3)
GAME_PARAMS = const(10)
FPS = const(0)
LIVES = const(1)
SCORE = const(2)
SHAPES = array.array('i',(
0,1,10,11, 0,1,10,11, 0,1,10,11, 0,1,10,11, # O
-10,0,10,20, 9,10,11,12, -10,0,10,20, 9,10,11,12, # I
-10,-9,-1,0, -10,0,1,11, -10,-9,-1,0, -10,0,1,11, # S
-11,-10,0,1, -9,0,1,10, -11,-10,0,1, -9,0,1,10, # Z
-10,0,10,11, -1,0,1,9, -11,-10,0,10, -9,-1,0,1, # L
-10,0,9,10, -11,-1,0,1, -10,-9,0,10, -1,0,1,11, # J
-1,0,1,10, -10,-1,0,10, -10,-1,0,1, -10,0,1,10,# T
0,0,0,0))
COLORS = array.array('H',(
0xc0ff, 0xff07, 0xe007, 0x0078, 0xe0f9, 0x3ffb, 0x0f78)) # 0x0161
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(SCORE_BITMAP)
screen_ptr2 = ptr16(LCD.buffer)
size = 2 # 1,2,3
char = 0
offset = 50*y_offset+x_offset
first = 1
while num > 0 or first:
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
addr = size*y*50+x-(char*8)+offset
screen_ptr[addr] = color
if size>1:
screen_ptr[50+addr] = color
if size>2:
screen_ptr[2*50+addr] = color
char += 1
first = 0
screen_offset = 160*85+20
for y in range(50):
for x in range(50):
if screen_ptr[(49-x)*50+y]:
screen_ptr2[y*MAXSCREEN_X+x+screen_offset] = screen_ptr[(49-x)*50+y]
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():
global EXIT
player = ptr32(PLAYER)
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 not FIRE_BUTTON.value() and not player[DEBOUNCE2]:
temp = player[NEWSHAPE]
player[NEWSHAPE] = player[NEXTSHAPE]
player[NEXTSHAPE] = temp
player[DEBOUNCE2] = 4
else: player[DEBOUNCE2] -= 1
if player[DEBOUNCE2] < 0 : player[DEBOUNCE2] = 0
if y_inc < 0 and not player[DEBOUNCE]: # up -> rotate
if not check_loc(player[NEWPOS],player[NEWROT] + 1,DIR_ROT):
player[NEWROT] += 1
if player[NEWROT] > 3:
player[NEWROT] = 0
player[DEBOUNCE] = 2
else: player[DEBOUNCE] -= 1
if player[DEBOUNCE] < 0 : player[DEBOUNCE] = 0
if x_inc > 0:
if not check_loc(player[NEWPOS]-1,player[NEWROT],DIR_LEFT):
player[NEWPOS] -= 1
if x_inc < 0:
if not check_loc(player[NEWPOS]+1,player[NEWROT],DIR_RIGHT):
player[NEWPOS] += 1
if y_inc > 0:
if not check_loc(player[NEWPOS]+10,player[NEWROT],DIR_DOWN):
player[NEWPOS] += 10
else:
new_shape()
def init_player():
global PLAYER
PLAYER = array.array('i',0 for _ in range(PLAYER_PARAMS))
PLAYER[NEWPOS] = 14
PLAYER[OLDPOS] = 14
PLAYER[NEWSHAPE] = randint(0,6)
PLAYER[NEXTSHAPE] = randint(0,6)
def init_game():
global GAME, FPS_ARRY, MATRIX, SHAPES, CLEAR, SCORE_BITMAP
GAME = array.array('i',0 for _ in range(GAME_PARAMS))
MATRIX = array.array('i',0 for _ in range(10*20))
CLEAR = bytearray(10)
FPS_ARRY = bytearray(35)
SCORE_BITMAP = bytearray(50*50*2)
GAME[SCORE] = 0
@micropython.viper
def game_over():
global downtimer, speedtimer
downtimer.deinit()
speedtimer.deinit()
mySong.stop()
import framebuf
scorebuf = bytearray(50*50*2)
fbuf = framebuf.FrameBuffer(scorebuf, 50, 50, framebuf.RGB565)
screen_ptr = ptr16(LCD.buffer)
score = ptr16(scorebuf)
fbuf.text('GAME',5,5,0xff)
fbuf.text('OVER',5,15,0xff)
screen_offset = 160*20+60
for y in range(40):
for x in range(20,50):
#if scorebuf[(49-x)*50+y]:
screen_ptr[y*MAXSCREEN_X+x+screen_offset] = score[(49-x)*50+y]
LCD.show()
exit()
@micropython.viper
def check_loc(newpos:int,newrot:int,direction:int)->int:
player = ptr32(PLAYER)
shapes = ptr32(SHAPES)
matrix = ptr32(MATRIX)
newshape = player[NEWSHAPE]
for i in range(4):
index = (newshape * 16) + i + (newrot*4)
position = newpos + shapes[index]
if position > 20*10: # bottom of screen
return 1
if direction == DIR_ROT and not ((position-0) % 10): # left edge
return 1
if direction == DIR_LEFT and (not ((position+1) % 10) or matrix[position]): # left edge
return 1
if direction == DIR_RIGHT and (not ((position+0) % 10) or matrix[position]): # right edge
return 1
if direction == DIR_DOWN and matrix[position]:
return 1
return 0
@micropython.viper
def new_shape():
player = ptr32(PLAYER)
matrix = ptr32(MATRIX)
shapes = ptr32(SHAPES)
colors = ptr16(COLORS)
for i in range(4):
index = (player[NEWSHAPE] * 16) + i + (player[NEWROT]*4)
position = player[NEWPOS] + shapes[index]
matrix[position] = colors[player[NEWSHAPE]]
player[NEWSHAPE] = player[NEXTSHAPE]
player[NEXTSHAPE] = int(randint(0,6))
player[NEWPOS] = 14
player[OLDPOS] = 14
player[NEWROT] = 0
player[OLDROT] = 0
check_row()
if check_loc(player[NEWPOS],player[NEWROT],0):
game_over()
@micropython.viper
def check_row():
matrix = ptr32(MATRIX)
clear = ptr8(CLEAR)
game = ptr32(GAME)
screen_ptr = ptr16(SCORE_BITMAP)
cleared_row = 0
multi_row = 0
for y in range(20):
count_row = 0
for x in range(10):
if matrix[y*10+x]: count_row += 1
if count_row == 10:
clear[multi_row] = y
multi_row += 1
if not multi_row: return
game[SCORE] += 100 * (1<<(multi_row-1))
for i in range(50*50):
screen_ptr[i] = 0
flash_row()
for i in range(4):
if clear[i]:
for x in range(10):
matrix[clear[i]*10+x] = 0 # clear row
for y in range(clear[i],0,-1):
for x in range(10):
matrix[y*10+x] = matrix[(y-1)*10+x] # shift down
for x in range(10): matrix[x] = 0 # clear top
clear[i] = 0
@micropython.viper
def flash_row():
Pin(16,Pin.OUT).low()
clear = ptr8(CLEAR)
for color in COLORS:
for i in range(4):
if clear[i]:
y = clear[i]
for x in range(10):
x1 = MAXSCREEN_X - 7 - (y * 8)
y1 = x * 8 + 1
LCD.rect(x1,y1,7,7,color,1)
LCD.show()
sleep(0.05)
Pin(16,Pin.OUT).high()
@micropython.viper
def move():
shapes = ptr32(SHAPES)
matrix = ptr32(MATRIX)
colors = ptr16(COLORS)
player = ptr32(PLAYER)
newshape = player[NEWSHAPE]
oldshape = player[OLDSHAPE]
newpos = player[NEWPOS]
oldpos = player[OLDPOS]
for i in range(4):
index = (newshape * 16) + i + (player[OLDROT]*4)
position = oldpos + shapes[index]
matrix[position] = 0 # erase old shape
for i in range(4):
index = (newshape * 16) + i + (player[NEWROT]*4)
position = newpos + shapes[index]
matrix[position] = colors[newshape] # write new shape
player[OLDROT] = player[NEWROT]
player[OLDPOS] = player[NEWPOS]
@micropython.viper
def auto_down(t):
player = ptr32(PLAYER)
if not check_loc(player[NEWPOS]+10,player[NEWROT],DIR_DOWN):
player[NEWPOS] += 10
else:
new_shape()
def speed_up(t):
global downtimer, SPEED
if SPEED > 100: SPEED -= 10
downtimer.init(period=SPEED, mode=Timer.PERIODIC, callback=auto_down)
@micropython.viper
def draw():
game = ptr32(GAME)
shapes = ptr32(SHAPES)
screen = ptr16(LCD.buffer)
matrix = ptr32(MATRIX)
player = ptr32(PLAYER)
colors = ptr16(COLORS)
LCD.rect(0,0,MAXSCREEN_X,MAXSCREEN_Y,BACKCOLOR,1)
LCD.line(0,0,MAXSCREEN_X,0,0xff)
LCD.line(0,8*10,MAXSCREEN_X,8*10,0xff)
LCD.line(0,0,0,8*10,0xff)
for y in range(20):
for x in range(10):
color = matrix[y*10+x]
x1 = MAXSCREEN_X - 7 - (y * 8)
y1 = x * 8 + 1
LCD.rect(x1,y1,7,7,color,1)
for i in range(4):
index = (player[NEXTSHAPE] * 16) + i
position = shapes[index]+1
y = position//10
x = position % 10
color = colors[player[NEXTSHAPE]] # next shape
x1 = MAXSCREEN_X - 60 - (y * 8)
y1 = (x * 8) + 90
LCD.rect(x1,y1,7,7,color,1)
for i in range(4):
index = (player[NEWSHAPE] * 16) + i + (player[NEWROT]*4)
position = player[NEWPOS] + shapes[index]
matrix[position] = 0 # erase shape
show_num_viper(game[SCORE],30,0,0xff)
LCD.show()
@micropython.viper
def main():
Pin(16,Pin.OUT).high()
global downtimer, speedtimer, SPEED
init_pot()
init_game()
init_player()
game = ptr32(GAME)
move_ticks = 0
SPEED = 500
downtimer = Timer(period=SPEED, mode=Timer.PERIODIC, callback=auto_down)
speedtimer = Timer(period=5000, mode=Timer.PERIODIC, callback=speed_up)
x = True
start = 0
startinc = 1
while not EXIT:
gticks = int(ticks_ms())
sleep(.005)
if gticks - move_ticks > 50:
move_ticks = gticks
read_pot()
move()
draw()
x = bool(mySong.tick())
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__':
EXIT = False
mySong = music(tetris, pins=[Pin(17)], tempo=3, looping = True,duty = 2000) #
sleep(2)
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()
try:
main()
shutdown()
except KeyboardInterrupt :
shutdown()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment