Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created April 20, 2024 04:20
Show Gist options
  • Save samneggs/e724e37824158b7f8c6d555c6c0c43b0 to your computer and use it in GitHub Desktop.
Save samneggs/e724e37824158b7f8c6d555c6c0c43b0 to your computer and use it in GitHub Desktop.
Minesweeper Game in MicroPython on Pi Pico
# 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