Created
July 2, 2023 23:00
-
-
Save samneggs/f463bfdb423b787793595089a2f045cc to your computer and use it in GitHub Desktop.
Tetris 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
# 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