Created
April 20, 2024 04:20
-
-
Save samneggs/e724e37824158b7f8c6d555c6c0c43b0 to your computer and use it in GitHub Desktop.
Minesweeper Game in MicroPython on Pi Pico
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
# minesweeper | |
from lcd_1_8 import LCD_1inch8 | |
import machine | |
from machine import Pin, PWM | |
from time import sleep, ticks_us, ticks_diff, ticks_ms | |
import gc, array | |
from sys import exit | |
from micropython import const | |
from random import randint | |
from rp2 import bootsel_button as RESET_PB | |
MAXSCREEN_X = const(160) | |
MAXSCREEN_Y = const(128) | |
SCALE = const(13) | |
GRID_MAX_X = const(50) | |
GRID_MAX_Y = const(50) | |
LT_BLUE = const(0b000_11111_10000_100) | |
DK_RED = const(0b000_00000_10111_000) | |
LT_GREEN = const(0b111_10000_10000_111) | |
GREEN = const(0b111_00000_00000_111) | |
LONG_PRESS = const(5) | |
PLAYER_PARAMS = const(4) | |
X = const(0) | |
Y = const(1) | |
SEL= const(2) | |
GAME_PARAMS = const(10) | |
FPS = const(0) | |
LIVES = const(1) | |
MAX_X = const(2) | |
MAX_Y = const(3) | |
BOMB_DENS = const(4) | |
REC = const(5) | |
TOT_BOMBS = const(6) | |
NUM_FLAGS = const(7) | |
GRID_PARAMS = const(3) | |
VISIBLE = const(0) | |
CONTENTS = const(1) | |
FLAG = const(2) | |
NEIGHBORS = [ | |
(-1, -1), (-1, 0), (-1, 1), | |
(0, -1), (0, 1), | |
(1, -1), (1, 0), (1, 1) | |
] | |
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 | |
first = 1 | |
while num > 0 or first: | |
first = 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 | |
def test_text(): | |
index = 0 | |
for y in range(16): | |
for x in range(20): | |
x1 = x * 8 | |
y1 = y * 8 | |
LCD.text(chr(index),x1,y1,0xffff) | |
index += 1 | |
LCD.show() | |
exit() | |
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 | |
game = ptr32(GAME) | |
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 | |
x = player[X] - (x_inc) | |
y = player[Y] + (y_inc) | |
if 0 < x < game[MAX_X]<<4: | |
player[X] = x | |
if 0 < y < game[MAX_Y]<<4: | |
player[Y] = y | |
if player[SEL] == LONG_PRESS: | |
set_flag(1) | |
if not FIRE_BUTTON.value(): | |
player[SEL] += 1 | |
elif 0 < player[SEL] <= LONG_PRESS: | |
set_flag(0) | |
player[SEL] = 0 | |
choose() | |
count_flags() | |
else: | |
player[SEL] = 0 | |
#print(player[SEL]) | |
def init_player(): | |
global PLAYER | |
PLAYER = array.array('i',0 for _ in range(PLAYER_PARAMS)) | |
PLAYER[X] = GAME[MAX_X]<<3 | |
PLAYER[Y] = GAME[MAX_Y]<<3 | |
def init_game(): | |
global GAME, FPS_ARRY, GRID | |
GAME = array.array('i',0 for _ in range(GAME_PARAMS)) | |
GRID = bytearray(GRID_MAX_X*GRID_MAX_Y*GRID_PARAMS) | |
FPS_ARRY = bytearray(35) | |
GAME[FPS] = 0 | |
GAME[LIVES] = 3 | |
GAME[BOMB_DENS] = 18 # percent | |
GAME[MAX_X] = 15 | |
GAME[MAX_Y] = 12 | |
def init_grid(): | |
global GRID | |
max_elements = GAME[MAX_X] * GAME[MAX_Y] | |
for index in range(max_elements): | |
i = index * GRID_PARAMS | |
GRID[i + VISIBLE] = 0 # 1 for visible | |
temp_grid = [i for i in range(max_elements)] | |
num_bombs = max_elements * GAME[BOMB_DENS] // 100 | |
GAME[TOT_BOMBS] = num_bombs | |
for i in range(num_bombs): | |
random = randint(0,max_elements-1) | |
GRID[temp_grid[random] * GRID_PARAMS + CONTENTS] = 9 | |
temp_grid.pop(random) | |
max_elements -= 1 | |
del temp_grid | |
for y in range(GAME[MAX_Y]): | |
for x in range(GAME[MAX_X]): | |
i = (y * GAME[MAX_X] + x) * GRID_PARAMS | |
count = bomb_count(x,y) | |
if GRID[i + CONTENTS] < 9: | |
GRID[i + CONTENTS] = count | |
def find_space(): | |
for y in range(GAME[MAX_Y]): | |
for x in range(GAME[MAX_X]): | |
i = (y * GAME[MAX_X] + x) * GRID_PARAMS | |
if GRID[i + CONTENTS] == 0: | |
PLAYER[X] = x<<4 | |
PLAYER[Y] = y<<4 | |
choose() | |
#return | |
def bomb_count(x,y): | |
count = 0 | |
for dx, dy in NEIGHBORS: | |
new_x, new_y = x + dx, y + dy | |
if 0 <= new_x < GAME[MAX_X] and 0 <= new_y < GAME[MAX_Y]: | |
new_i = (new_y * GAME[MAX_X] + new_x) * GRID_PARAMS | |
if GRID[new_i + CONTENTS] == 9: | |
count += 1 | |
return count | |
@micropython.viper | |
def set_flag(flag:int): | |
player = ptr32(PLAYER) | |
grid = ptr8(GRID) | |
game = ptr32(GAME) | |
x = player[X] >> 4 | |
y = player[Y] >> 4 | |
max_x = game[MAX_X] | |
index = (y * max_x + x) * GRID_PARAMS | |
grid[index + FLAG] = flag | |
count_flags() | |
@micropython.viper | |
def count_flags(): | |
grid = ptr8(GRID) | |
game = ptr32(GAME) | |
max_x = game[MAX_X] | |
max_y = game[MAX_Y] | |
game[NUM_FLAGS] = 0 | |
correct = 0 | |
for index in range(max_x * max_y): # cound flags | |
i = index * GRID_PARAMS | |
if grid[i + FLAG] == 1: | |
game[NUM_FLAGS] += 1 | |
if grid[i + FLAG] == 1 and grid[i + CONTENTS] == 9: | |
correct += 1 | |
if correct == game[TOT_BOMBS]: | |
print('YOU WIN!!') | |
exit() | |
@micropython.viper | |
def choose(): | |
player = ptr32(PLAYER) | |
grid = ptr8(GRID) | |
game = ptr32(GAME) | |
x = player[X] >> 4 | |
y = player[Y] >> 4 | |
max_x = game[MAX_X] | |
index = (y * max_x + x) * GRID_PARAMS | |
grid[index + VISIBLE] = 1 | |
grid[index + FLAG] = 0 | |
count_flags() | |
if grid[index + CONTENTS] == 0: | |
fill(x,y) | |
def fill(x,y): | |
#GAME[REC] += 1 | |
#print(GAME[REC]) | |
max_x = GAME[MAX_X] | |
max_y = GAME[MAX_Y] | |
i = (y * max_x + x) * GRID_PARAMS | |
for dx, dy in NEIGHBORS: | |
new_x, new_y = x + dx, y + dy | |
if 0 <= new_x < max_x and 0 <= new_y < max_y: | |
new_i = (new_y * max_x + new_x) * GRID_PARAMS | |
count = GRID[new_i + CONTENTS] | |
visible = GRID[new_i + VISIBLE] | |
GRID[new_i + VISIBLE] = 1 | |
if count == 0 and not visible: | |
fill(new_x,new_y) # recursion! | |
@micropython.viper | |
def draw_grid(): | |
player = ptr32(PLAYER) | |
grid = ptr8(GRID) | |
game = ptr32(GAME) | |
dist = 10 | |
x_offset = 2 | |
y_offset = 2 | |
max_x = game[MAX_X] | |
max_y = game[MAX_Y] | |
p_x = player[X]>>4 | |
p_y = player[Y]>>4 | |
for y in range(12): | |
for x in range(15): | |
#LCD.rect(x*dist,y*dist,dist+1,dist+1,0b000_11100_11100_111) | |
index = (y * max_x + x) * GRID_PARAMS | |
visible = grid[index + VISIBLE] | |
contents = grid[index + CONTENTS] | |
flag = grid[index + FLAG] | |
if flag: | |
LCD.rect(x*dist+1,y*dist+1,dist,dist,DK_RED,1) | |
elif visible or 0: | |
if 0<contents<9: | |
show_num_viper(contents,x*dist+x_offset,y*dist+y_offset,0x0) | |
elif contents == 0: | |
LCD.rect(x*dist+1,y*dist+1,dist,dist,LT_BLUE,1) | |
elif contents == 9: | |
LCD.text('*',x*dist+2,y*dist+2,0) | |
LCD.rect(p_x*dist,p_y*dist,dist+1,dist+1,0x0) # full background | |
bombs_bar = game[NUM_FLAGS]* 128 // ((game[TOT_BOMBS]+1)) | |
LCD.rect(151,0,9,128,0,1) | |
LCD.rect(152,128 - bombs_bar,8,bombs_bar,GREEN,1) | |
#print(visible, end = ' ') | |
#print() | |
#exit() | |
@micropython.viper | |
def draw(): | |
g = ptr32(GAME) | |
draw_grid() | |
#LCD.text('FPS',0,0,0xff) | |
#show_num_viper(g[FPS],20,7,0xff) | |
#show_num_viper(gc.mem_free(),40,17,0xff) | |
#LCD.text('LIVES',115,0,0xff) | |
#show_num_viper(g[LIVES],125,10,0xff) | |
LCD.show() | |
LCD.rect(1,1,MAXSCREEN_X-9,MAXSCREEN_Y-8,LT_GREEN,1) | |
@micropython.asm_thumb | |
def avg_fps_asm(r0,r1): # r0 = fps[] , r1 = current_fps | |
ldrb(r2,[r0,0]) # r2 = fps[0] | |
add(r2,r2,1) # fps[0] += 1 | |
cmp(r2,33) | |
blt(LT_32) # if fps[0] > 32: | |
mov(r2,1) | |
label(LT_32) | |
strb(r2,[r0,0]) # fps[0] = new index | |
add(r2,r2,r0) | |
strb(r1,[r2,0]) # fps[fps[0]] = current_fps | |
mov(r2,1) # r2 = i | |
mov(r3,0) # r3 = tot | |
label(LOOP) | |
add(r0,r0,1) | |
ldrb(r4,[r0,0]) # r4 = fps[i] | |
add(r3,r3,r4) # tot += fps[i] | |
add(r2,r2,1) | |
cmp(r2,33) #33 | |
blt(LOOP) | |
asr(r0,r3,5) | |
@micropython.viper | |
def main(): | |
init_pot() | |
init_game() | |
init_grid() | |
init_player() | |
g = ptr32(GAME) | |
pot_ticks = 0 | |
find_space() | |
while not EXIT and not RESET_PB(): | |
gticks = int(ticks_ms()) | |
sleep(0.001) | |
if gticks - pot_ticks >50: | |
pot_ticks = int(ticks_ms()) | |
read_pot() | |
draw() | |
g[FPS] = int(avg_fps_asm(FPS_ARRY,1_000//int(ticks_diff(ticks_ms(),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) | |
#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() | |
LCD.fill(0) | |
LCD.show() | |
EXIT = False | |
#_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