Last active
May 17, 2025 15:40
-
-
Save samneggs/4c93714c2be20085751c95cd5b615437 to your computer and use it in GitHub Desktop.
Boulder Dash on Pi Pico 2 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
# Boulder Dash Pico2 | |
from st7796 import LCD_3inch5 | |
from gfx import Gfx | |
from machine import Pin, PWM, SPI | |
from uctypes import addressof | |
from time import sleep, sleep_ms, ticks_us, ticks_diff, ticks_ms | |
import gc, array, re, _thread | |
from random import randint, random | |
MAXSCREEN_X = const(240) | |
MAXSCREEN_Y = const(160) | |
SCALE = const(13) | |
SHOWING = const(0) | |
SHUTDOWN = const(1) | |
PLAYER_PARAMS = const(10) | |
POS = const(0) | |
DIR = const(1) | |
EXTRA_L = const(2) | |
GAME_PARAMS = const(10) | |
FPS = const(0) | |
LIVES = const(1) | |
ANIMATE = const(2) | |
TOT_DIAM = const(3) | |
TIME = const(4) | |
SCORE = const(5) | |
STATE = const(6) | |
LEV_DIAM = const(7) | |
#game states | |
PLAYING = const(0) | |
TIME_UP = const(1) | |
EXPLODE = const(2) | |
LEVDONE = const(3) | |
BOARD_WIDTH = const(40) | |
BOARD_HEIGHT = const(22) | |
NUM_BOULDERS = const(30) | |
NUM_DIAMONDS = const(15) | |
NUM_FIREFLIES = const(3) # 3 | |
NUM_BUTTERFLIES = const(2) # 2 | |
NUM_SPRITES = const(14) | |
EXTRA_SCORE = const(500) | |
# Constants for tile types | |
EMPTY = const(0) | |
WALL = const(1) | |
DIRT = const(2) | |
BOULDER = const(3) | |
DIAMOND = const(4) | |
PLAYER = const(5) | |
MAGIC_WALL = const(6) | |
AMOEBA = const(7) | |
FIREFLY = const(8) | |
BUTTERFLY = const(9) | |
EXIT = const(10) | |
STANDING = const(11) | |
EXPLOSION = const(12) | |
LIFE_UP = const(13) | |
TILE_SIZE = const(16) # Size of each tile in pixels | |
SCREEN_TILES_X = const(MAXSCREEN_X // TILE_SIZE) | |
SCREEN_TILES_Y = const(MAXSCREEN_Y // TILE_SIZE) | |
SCROLL_MARGIN = const(2) # Number of tiles from edge before scrolling | |
# Configuration for sprites: (sprite_number, frame_count) | |
SPRITE_CONFIG = { | |
EMPTY: (0, 1), | |
DIRT: (1, 1), | |
MAGIC_WALL: (2, 3), | |
BOULDER: (5, 1), | |
WALL: (9, 1), | |
EXPLOSION: (15,3), | |
STANDING: (18, 1), | |
BUTTERFLY: (20, 8), | |
DIAMOND: (28, 8), | |
FIREFLY: (36, 8), | |
AMOEBA: (44, 4), | |
PLAYER: (60, 8), | |
EXIT: (116, 6), | |
LIFE_UP: (132, 8) | |
} | |
char_map=array.array('b',( | |
0x00, 0x3e, 0x67, 0x67, 0x67, 0x67, 0x7f, 0x3e, # U+0030 (0) | |
0x00, 0x18, 0x1c, 0x1c, 0x18, 0x18, 0x7e, 0x7e, # U+0031 (1) | |
0x00, 0x3e, 0x73, 0x38, 0x1c, 0x0e, 0x7f, 0x7f, # U+0032 (2) | |
0x00, 0x7e, 0x30, 0x18, 0x30, 0x67, 0x7f, 0x3e, # U+0033 (3) | |
0x00, 0x03, 0x03, 0x3b, 0x7f, 0x38, 0x38, 0x38, # U+0034 (4) | |
0x00, 0x7f, 0x07, 0x3f, 0x60, 0x67, 0x7f, 0x3e, # U+0035 (5) | |
0x00, 0x3e, 0x07, 0x3f, 0x67, 0x67, 0x7f, 0x3e, # U+0036 (6) | |
0x00, 0x7f, 0x71, 0x38, 0x1c, 0x0e, 0x07, 0x07, # U+0037 (7) | |
0x00, 0x3e, 0x67, 0x3e, 0x67, 0x67, 0x7f, 0x3e, # U+0038 (8) | |
0x00, 0x3e, 0x67, 0x67, 0x7e, 0x38, 0x1c, 0x0e, # U+0039 (9) | |
)) | |
@micropython.viper | |
def show_num_viper(num:int,x_offset:int,y_offset:int,color:int): | |
if num < 0: return | |
char_ptr = ptr8(char_map) | |
screen_ptr = ptr16(LCD.fbdraw) | |
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 calculate_total_frames(): | |
return sum(frame_count for _, frame_count in SPRITE_CONFIG.values()) | |
def init_sprites(filename, tile_size): | |
global SPRITES, SPRITE_START, FRAMES | |
total_frames = calculate_total_frames() | |
SPRITES = bytearray() | |
SPRITE_START = bytearray(NUM_SPRITES) # number sprite types | |
FRAMES = bytearray(NUM_SPRITES) | |
with open(filename, "rb") as file: | |
file.read(4) # Read and skip the header | |
sprite_index = 0 | |
for sprite_type in range(NUM_SPRITES): # 11 sprite types | |
sprite_number, frame_count = SPRITE_CONFIG.get(sprite_type, (0, 0)) | |
SPRITE_START[sprite_type] = sprite_index | |
FRAMES[sprite_type] = frame_count | |
for frame in range(frame_count): | |
# Calculate the position of the sprite in the file | |
file_position = 4 + (sprite_number + frame) * (tile_size * tile_size * 2) | |
file.seek(file_position) | |
# Read the sprite data and extend SPRITES | |
sprite_data = file.read(tile_size * tile_size * 2) | |
SPRITES.extend(sprite_data) | |
sprite_index += 1 | |
def init_pot(): | |
global POT_X,POT_Y,POT_X_ZERO,POT_Y_ZERO | |
POT_X = machine.ADC(26) | |
POT_Y = machine.ADC(27) | |
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(): | |
g = ptr32(GAME) | |
global PLAYER_ARRY, BOARD | |
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 -2 < x_inc < 2: | |
x_inc = 0 | |
if -5 < y_inc < 5: | |
y_inc = 0 | |
player = ptr32(PLAYER_ARRY) | |
board = ptr8(BOARD) | |
width = int(BOARD_WIDTH) | |
current_pos = int(player[POS]) | |
new_pos = current_pos | |
if x_inc < 0: | |
new_pos -= 1 | |
player[DIR] = 1 | |
elif x_inc > 0: | |
new_pos += 1 | |
player[DIR] = 0 | |
elif y_inc < 0: | |
new_pos += width | |
elif y_inc > 0: | |
new_pos -= width | |
length = int(len(BOARD)) | |
# Check if the new position is valid and update if possible | |
if new_pos == current_pos: board[new_pos] = STANDING | |
if 0 <= new_pos < length: | |
if board[new_pos] == BUTTERFLY or board[new_pos] == FIREFLY: | |
player_hit() | |
return | |
elif board[new_pos] == EMPTY or board[new_pos] == DIRT or board[new_pos] == DIAMOND: | |
if board[new_pos] == DIAMOND: | |
g[TOT_DIAM] += 1 | |
g[LEV_DIAM] += 1 | |
g[SCORE] += 10 | |
if not (g[SCORE] % EXTRA_SCORE): | |
player[EXTRA_L] = 1 | |
board[current_pos] = EMPTY | |
board[new_pos] = PLAYER | |
player[POS] = new_pos | |
elif board[new_pos] == BOULDER and (x_inc != 0): # Check if trying to push boulder horizontally | |
push_pos = new_pos + (1 if x_inc < 0 else -1) # Position to push boulder to | |
if 0 <= push_pos < length: # and board[push_pos] == EMPTY or board[push_pos] == BUTTERFLY: | |
# Push the boulder | |
if board[push_pos] == EMPTY: | |
board[push_pos] = BOULDER | |
board[new_pos] = PLAYER | |
board[current_pos] = EMPTY | |
player[POS] = new_pos | |
elif board[push_pos] == BUTTERFLY: | |
board[current_pos] = EMPTY | |
fill_box(push_pos, DIAMOND) | |
player[POS] = new_pos | |
elif board[push_pos] == FIREFLY: | |
board[current_pos] = EMPTY | |
fill_box(push_pos, EMPTY) | |
player[POS] = new_pos | |
elif board[push_pos] == MAGIC_WALL: | |
board[current_pos] = EMPTY | |
player[POS] = new_pos | |
board[push_pos] = DIAMOND | |
elif board[new_pos] == EXIT and g[LEV_DIAM] > 8: | |
g[STATE] = LEVDONE | |
def init_game(): | |
global GAME, FPS_ARRY, TO_UPDATE, ENEMY_DIRS, MOVED, DX, DY, BOARD, FALLING, PLAYER_ARRY | |
size = int(BOARD_WIDTH * BOARD_HEIGHT) | |
BOARD = bytearray(size) | |
PLAYER_ARRY = array.array('i',0 for _ in range(PLAYER_PARAMS)) | |
GAME = array.array('i',0 for _ in range(GAME_PARAMS)) | |
ENEMY_DIRS = bytearray(BOARD_WIDTH * BOARD_HEIGHT) | |
FPS_ARRY = bytearray(35) | |
TO_UPDATE = bytearray(BOARD_WIDTH * BOARD_HEIGHT) | |
FALLING = bytearray(BOARD_WIDTH * BOARD_HEIGHT) | |
MOVED = bytearray(BOARD_WIDTH * BOARD_HEIGHT) | |
# Directions: 0 = Left, 1 = Up, 2 = Right, 3 = Down | |
DX = array.array('i',[int(-1), int(0), int(1), int(0)]) | |
DY = array.array('i',[int(0), int(-1), int(0), int(1)]) | |
GAME[FPS] = 0 | |
GAME[LIVES] = 3 | |
GAME[SCORE] = 0 | |
def parse_level_file(filename, board, width, height): | |
# Dictionary to map object names to sprite numbers | |
object_map = { | |
'SPACE': EMPTY, # SPACE is now correctly mapped to EMPTY | |
'DIRT': DIRT, | |
'WALL': WALL, | |
'BOULDER': BOULDER, | |
'DIAMOND': DIAMOND, | |
'INBOX': PLAYER, | |
'OUTBOX': EXIT | |
} | |
# Initialize the entire board with DIRT | |
#board = [DIRT for _ in range(width * height)] | |
with open(filename, 'r') as file: | |
content = file.readlines() | |
# Parse RandomFill | |
for line in content: | |
if line.startswith('RandomFill='): | |
fill_items = line.strip().split('=')[1].split() | |
total_weight = sum(int(fill_items[i+1]) for i in range(0, len(fill_items), 2)) | |
for pos in range(width * height): | |
rand_val = random() * 256 #total_weight | |
cumulative_weight = 0 | |
if board[pos] == DIRT: | |
for i in range(0, len(fill_items), 2): | |
item_name = fill_items[i] | |
weight = int(fill_items[i+1]) | |
cumulative_weight += weight | |
#print(rand_val,cumulative_weight) | |
#if rand_val <= cumulative_weight: | |
if rand_val <= weight: | |
board[pos] = object_map.get(item_name, EMPTY) | |
#break | |
break # Stop after processing RandomFill | |
# Parse objects | |
in_objects_section = False | |
for line in content: | |
if line.strip() == '[objects]': | |
in_objects_section = True | |
continue | |
if line.strip() == '[/objects]': | |
break | |
if in_objects_section and '=' in line: | |
obj_type, obj_data = line.strip().split('=') | |
obj_data = obj_data.split() | |
if obj_type == 'Line': | |
x1, y1, x2, y2 = map(int, obj_data[:4]) | |
item = object_map.get(obj_data[4], DIRT) | |
for x in range(x1, x2+1): | |
for y in range(y1, y2+1): | |
board[y * width + x] = item | |
elif obj_type == 'Point': | |
x, y = map(int, obj_data[:2]) | |
item = object_map.get(obj_data[2], DIRT) | |
board[y * width + x] = item | |
if obj_data[2] == 'INBOX': | |
PLAYER_ARRY[POS] = y * width + x | |
return board | |
def init_game_board(width: int, height: int): | |
size = int(width * height) | |
# Fill the board with dirt | |
for i in range(size): | |
BOARD[i] = DIRT | |
# Add walls around the edges | |
for i in range(width): | |
BOARD[i] = WALL # Top wall | |
BOARD[size - width + i] = WALL # Bottom wall | |
for i in range(height): | |
BOARD[i * width] = WALL # Left wall | |
BOARD[(i + 1) * width - 1] = WALL # Right wall | |
@micropython.viper | |
def init_enemy_dirs(width: int, height: int): | |
board = ptr8(BOARD) | |
enemy_dirs = ptr8(ENEMY_DIRS) | |
size = int(width * height) | |
for pos in range(size): | |
if board[pos] == FIREFLY or board[pos] == BUTTERFLY: | |
enemy_dirs[pos] = int(randint(0, 3)) | |
else: | |
enemy_dirs[pos] = 0 # Default direction for non-enemy tiles | |
def add_items(width: int, height: int, num_boulders: int, num_diamonds: int, | |
num_fireflies: int, num_butterflies: int): | |
board = BOARD | |
playable_width = width - 2 # Exclude left and right walls | |
playable_height = height - 2 # Exclude top and bottom walls | |
playable_size = playable_width * playable_height | |
items_to_add = [(BOULDER, num_boulders), (DIAMOND, num_diamonds), | |
(FIREFLY, num_fireflies), (BUTTERFLY, num_butterflies)] | |
def get_playable_position(): | |
while True: | |
pos = randint(0, playable_size - 1) | |
x = pos % playable_width + 1 # Add 1 to avoid left wall | |
y = pos // playable_width + 1 # Add 1 to avoid top wall | |
board_pos = y * width + x | |
if board[board_pos] == DIRT: | |
return board_pos | |
for item, count in items_to_add: | |
for _ in range(count): | |
pos = get_playable_position() | |
board[pos] = item | |
# Add player and exit | |
player_pos = get_playable_position() | |
board[player_pos] = PLAYER | |
PLAYER_ARRY[POS] = player_pos | |
exit_pos = get_playable_position() | |
board[exit_pos] = EXIT | |
# Add some magic walls (just a few for now) | |
for _ in range(3): | |
magic_wall_pos = get_playable_position() | |
board[magic_wall_pos] = MAGIC_WALL | |
# Add a small amoeba | |
amoeba_pos = get_playable_position() | |
board[amoeba_pos] = AMOEBA | |
@micropython.viper | |
def fill_box(pos: int, sprite: int): | |
board = ptr8(BOARD) | |
width = int(BOARD_WIDTH) | |
height = int(BOARD_HEIGHT) | |
x = pos % width | |
y = pos // width | |
for dy in range(-1, 2): | |
for dx in range(-1, 2): | |
new_x = x + dx | |
new_y = y + dy | |
# Check if the new position is within the board boundaries | |
if 0 <= new_x < width and 0 <= new_y < height: | |
pos = new_y * width + new_x | |
board[pos] = sprite | |
@micropython.viper | |
def apply_boulder_physics(width: int, height: int): | |
board = ptr8(BOARD) | |
g = ptr32(GAME) | |
size = int(width * height) | |
to_update = ptr8(TO_UPDATE) | |
falling = ptr8(FALLING) # Persistent array to track falling objects | |
for i in range(size): | |
to_update[i] = 0 | |
# First pass: mark objects that need to be updated | |
for y in range(height - 2, 0, -1): | |
for x in range(1, width - 1): | |
pos = int(y * width + x) | |
below_pos = int(pos + width) | |
if board[pos] == BOULDER or board[pos] == DIAMOND: | |
if board[below_pos] == EMPTY: | |
to_update[pos] = 1 # Fall straight down | |
elif board[below_pos] == BOULDER or board[below_pos] == DIAMOND: | |
# Check if it can roll | |
left_pos = int(pos - 1) | |
right_pos = int(pos + 1) | |
if board[left_pos] == EMPTY and board[left_pos + width] == EMPTY: | |
to_update[pos] = 2 # Roll left | |
elif board[right_pos] == EMPTY and board[right_pos + width] == EMPTY: | |
to_update[pos] = 3 # Roll right | |
else: | |
falling[pos] = 0 # Reset falling state if it can't move | |
elif board[below_pos] == MAGIC_WALL: | |
to_update[pos] = 4 # Transform through magic wall | |
elif (board[below_pos] == PLAYER or board[below_pos] == STANDING) and falling[pos]: | |
to_update[pos] = 5 # Falling object hits player | |
elif board[below_pos] == DIRT: | |
falling[pos] = 0 # Reset falling state if it can't move | |
elif board[below_pos] == BUTTERFLY or board[below_pos] == FIREFLY: | |
to_update[pos] = 6 | |
# Second pass: apply updates | |
for pos in range(size): | |
update = to_update[pos] | |
if update == 1: # Fall straight down | |
board[pos + width] = board[pos] | |
board[pos] = EMPTY | |
falling[pos + width] = 1 # Transfer falling state | |
falling[pos] = 0 # Reset old position | |
elif update == 2: # Roll left | |
board[pos - 1] = board[pos] | |
board[pos] = EMPTY | |
falling[pos - 1] = falling[pos] # Transfer falling state | |
falling[pos] = 0 # Reset old position | |
elif update == 3: # Roll right | |
board[pos + 1] = board[pos] | |
board[pos] = EMPTY | |
falling[pos + 1] = falling[pos] # Transfer falling state | |
falling[pos] = 0 # Reset old position | |
elif update == 4: # Transform through magic wall | |
if board[pos] == BOULDER: | |
board[pos + width] = DIAMOND | |
else: | |
board[pos + width] = BOULDER | |
board[pos] = EMPTY | |
falling[pos + width] = 1 # Mark as falling after transformation | |
falling[pos] = 0 # Reset old position | |
elif update == 5: # Falling object hits player | |
player_hit() | |
elif update == 6: | |
fill_box(pos, DIAMOND) | |
@micropython.viper | |
def update_amoeba(width: int, height: int): | |
board = ptr8(BOARD) | |
size = int(width * height) | |
to_update = ptr8(TO_UPDATE) | |
for i in range(int(len(TO_UPDATE))): | |
to_update[i] = 0 | |
# Mark cells where amoeba can grow | |
for y in range(1, height - 1): | |
for x in range(1, width - 1): | |
pos = int(y * width + x) | |
if board[pos] == AMOEBA: | |
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: | |
new_pos = int(pos + int(dy) * width + int(dx)) | |
if board[new_pos] == EMPTY or board[new_pos]== DIRT: | |
to_update[new_pos] = 1 | |
# Grow amoeba | |
for i in range(size): | |
if to_update[i] == 1 and (board[i] == EMPTY or board[i] == DIRT): | |
board[i] = AMOEBA | |
@micropython.viper | |
def move_enemies(width: int, height: int): | |
board = ptr8(BOARD) | |
enemy_dirs = ptr8(ENEMY_DIRS) | |
dx = ptr32(DX) | |
dy = ptr32(DY) | |
moved = ptr8(MOVED) | |
size = int(width * height) | |
for i in range(size): | |
moved[i] = 0 | |
for pos in range(size): | |
if (board[pos] == FIREFLY or board[pos] == BUTTERFLY) and not moved[pos]: | |
current_dir = enemy_dirs[pos] | |
x = pos % width | |
y = pos // width | |
# Try to move in the current direction | |
new_x = x + dx[current_dir] | |
new_y = y + dy[current_dir] | |
new_pos = new_y * width + new_x | |
if 0 < new_x < width - 1 and 0 < new_y < height - 1: | |
if board[new_pos] == EMPTY: | |
# Move in the current direction | |
board[new_pos] = board[pos] | |
board[pos] = EMPTY | |
enemy_dirs[new_pos] = current_dir | |
moved[new_pos] = 1 | |
elif board[new_pos] == PLAYER or board[new_pos] == STANDING: | |
player_hit() | |
return | |
else: | |
# Turn clockwise | |
new_dir = (current_dir + 1) % 4 | |
enemy_dirs[pos] = new_dir | |
moved[pos] = 1 | |
else: | |
# If the enemy is at the edge, turn clockwise | |
new_dir = (current_dir + 1) % 4 | |
enemy_dirs[pos] = new_dir | |
moved[pos] = 1 | |
def player_hit(): | |
GAME[LIVES]-= 1 | |
GAME[STATE] = EXPLODE | |
def reset_level(): | |
init_game_board(BOARD_WIDTH, BOARD_HEIGHT) | |
#add_items(BOARD_WIDTH, BOARD_HEIGHT, NUM_BOULDERS, NUM_DIAMONDS, NUM_FIREFLIES, NUM_BUTTERFLIES) | |
#init_enemy_dirs(BOARD_WIDTH, BOARD_HEIGHT) | |
parse_level_file('level1.bd', BOARD, BOARD_WIDTH, BOARD_HEIGHT) | |
GAME[TIME] = 150 | |
GAME[LEV_DIAM] = 0 | |
GAME[STATE] = PLAYING | |
def level_done(): | |
pass | |
@micropython.viper | |
def draw_board(): | |
g = ptr32(GAME) | |
sprite = ptr16(SPRITES) | |
frames = ptr8(FRAMES) | |
start_pos = ptr8(SPRITE_START) | |
board = ptr8(BOARD) | |
buffer = ptr16(LCD.fbdraw) | |
width = int(BOARD_WIDTH) | |
height = int(BOARD_HEIGHT) | |
max_x = int(MAXSCREEN_X) | |
max_y = int(MAXSCREEN_Y) | |
player = ptr32(PLAYER_ARRY) | |
player_x = int(player[POS] % width) | |
player_y = int(player[POS] // width) | |
player_dir = int(player[DIR]) | |
# Calculate the top-left corner of the viewable area | |
view_x = player_x - SCREEN_TILES_X // 2 | |
view_x = 0 if view_x < 0 else (width - SCREEN_TILES_X if view_x > width - SCREEN_TILES_X else view_x) | |
view_y = player_y - SCREEN_TILES_Y // 2 | |
view_y = 0 if view_y < 0 else (height - SCREEN_TILES_Y if view_y > height - SCREEN_TILES_Y else view_y) | |
for y in range(SCREEN_TILES_Y): | |
for x in range(SCREEN_TILES_X): | |
board_x = view_x + x | |
board_y = view_y + y | |
tile = board[board_y * width + board_x] | |
if tile == EMPTY and player[EXTRA_L]: | |
tile = LIFE_UP | |
animate = g[ANIMATE] % frames[tile] | |
offset = (start_pos[tile] + animate) * TILE_SIZE * TILE_SIZE | |
# Draw a 16x16 square for each tile | |
x2 = x * TILE_SIZE | |
y2 = y * TILE_SIZE | |
for dy in range(TILE_SIZE): | |
y3 = dy * TILE_SIZE | |
y4 = (y2 + dy) * max_x + x2 | |
if tile == PLAYER and player_dir == 0: | |
# Reverse player sprite horizontally | |
for dx in range(TILE_SIZE): | |
color = sprite[y3 + (TILE_SIZE - 1 - dx) + offset] | |
buffer[y4 + dx] = color | |
else: | |
# Draw other sprites normally | |
for dx in range(TILE_SIZE): | |
color = sprite[y3 + dx + offset] | |
if color: | |
buffer[y4 + dx] = color | |
@micropython.viper | |
def test_draw(): | |
g = ptr32(GAME) | |
sprite = ptr16(SPRITES) | |
frames = ptr8(FRAMES) | |
start_pos = ptr8(SPRITE_START) | |
board = ptr8(BOARD) | |
buffer = ptr16(LCD.fbdraw) | |
width = int(BOARD_WIDTH) | |
height = int(BOARD_HEIGHT) | |
max_x = int(MAXSCREEN_X) | |
max_y = int(MAXSCREEN_Y) | |
player = ptr32(PLAYER_ARRY) | |
player_x = int(player[POS] % width) | |
player_y = int(player[POS] // width) | |
tile = PLAYER | |
y = 5 | |
g[ANIMATE] = 1 | |
for x in range(8): | |
for dy in range(TILE_SIZE): | |
for dx in range(TILE_SIZE): | |
animate = g[ANIMATE] % frames[tile] | |
animate = x | |
offset = start_pos[tile] + animate | |
color = sprite[(dy * TILE_SIZE + dx) + offset * TILE_SIZE * TILE_SIZE] | |
buffer[(y * TILE_SIZE + dy) * max_x + (x * TILE_SIZE + dx)] = color | |
@micropython.viper | |
def draw(): | |
status = ptr8(LCD.aux) | |
while not status[SHOWING]: sleep_ms(1) | |
LCD.fbdraw.rect(0,0,MAXSCREEN_X,MAXSCREEN_Y,0,1) | |
draw_board() | |
game = ptr32(GAME) | |
LCD.fbdraw.rect(0,8,8*2,9,0,1) # FPS background | |
show_num_viper(game[FPS],8,8,0xff) # FPS | |
LCD.fbdraw.rect(24,0,8*2,9,0,1) # diamonds background | |
show_num_viper(game[TOT_DIAM],32,0,0xff) # diamonds | |
LCD.fbdraw.rect(0,0,8*2,9,0,1) | |
show_num_viper(game[LIVES],8,0,0xff) | |
LCD.fbdraw.rect(80-(8*2),0,8*3,9,0,1) | |
show_num_viper(game[TIME],80,0,0xff) | |
LCD.fbdraw.rect(160-(8*4),0,8*4,9,0,1) | |
show_num_viper(game[SCORE],150,0,0xff) | |
status[SHOWING] = 0 | |
@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_sprites('Enhanced2.bin', TILE_SIZE) | |
init_pot() | |
init_game() | |
player = ptr32(PLAYER_ARRY) | |
reset_level() | |
g = ptr32(GAME) | |
pot_ticks = 0 | |
enemy_ticks = 0 | |
amoeba_ticks = 0 | |
boulder_ticks = 0 | |
animate_ticks = 0 | |
second_ticks = 0 | |
explode_ticks = 0 | |
extra_ticks = 0 | |
ms250_ticks = 0 | |
while 1:#not RESET_PB(): | |
gticks = int(ticks_ms()) | |
sleep(0.001) # allows ctrl-c | |
if g[STATE] == PLAYING: | |
if gticks - second_ticks > 1000: | |
second_ticks = gticks | |
g[TIME] -= 1 | |
if g[TIME] < 0 : | |
g[STATE] = EXPLODE | |
if gticks - pot_ticks > 150: | |
pot_ticks = gticks | |
read_pot() | |
if gticks - boulder_ticks > 300: | |
boulder_ticks = gticks | |
apply_boulder_physics(BOARD_WIDTH, BOARD_HEIGHT) | |
if gticks - enemy_ticks > 500: | |
enemy_ticks = gticks | |
move_enemies(BOARD_WIDTH, BOARD_HEIGHT) | |
if gticks - amoeba_ticks > 100_000: | |
amoeba_ticks = gticks | |
update_amoeba(BOARD_WIDTH, BOARD_HEIGHT) | |
if player[EXTRA_L]: | |
if extra_ticks == 0: | |
extra_ticks = gticks | |
if gticks - extra_ticks > 2000: | |
extra_ticks = 0 | |
player[EXTRA_L] = 0 | |
if g[STATE] == EXPLODE: | |
if explode_ticks == 0: | |
explode_ticks = gticks | |
pos = PLAYER_ARRY[POS] | |
fill_box(pos,EXPLOSION) | |
elif gticks - explode_ticks > 2000: | |
explode_ticks = 0 | |
reset_level() | |
elif gticks - explode_ticks > 250: | |
pos = PLAYER_ARRY[POS] | |
fill_box(pos,EMPTY) | |
if gticks - animate_ticks > 100: | |
animate_ticks = gticks | |
g[ANIMATE] += 1 | |
if int(g[ANIMATE]) > 7: g[ANIMATE] = 0 | |
if g[STATE] == LEVDONE and gticks - ms250_ticks > 250 : | |
g[SCORE] += 1 | |
g[TIME] -= 1 | |
if g[TIME] == 0: | |
reset_level() | |
draw() | |
g[FPS] = int(avg_fps_asm(FPS_ARRY,1_000//int(ticks_diff(ticks_ms(),gticks)))) | |
def shutdown(): | |
LCD.aux[SHUTDOWN] = 1 | |
sleep_ms(200) | |
LCD.writecommand(0x28) #LCD off | |
sleep_ms(200) | |
machine.freq(150_000_000) | |
print('core0 done') | |
@micropython.viper | |
def core1(): | |
status = ptr8(LCD.aux) | |
sleep_ms(200) | |
while not status[SHUTDOWN]: | |
gticks=int(ticks_ms()) | |
status[SHOWING] = 1 | |
LCD.show_all() | |
#sleep_ms(3) | |
LCD.flip() | |
LCD.fps = (1_000//int(ticks_diff(int(ticks_ms()),gticks))) | |
print('core1 done') | |
if __name__=='__main__': | |
FIRE_BUTTON = Pin(14, Pin.IN, Pin.PULL_UP) | |
machine.freq(220_000_000) #220 | |
machine.mem32[0x40010048] = 1<<11 # enable peri_ctrl clock | |
LCD = LCD_3inch5() | |
gfx = Gfx(LCD.fbdraw,240,160) | |
_thread.start_new_thread(core1, ()) | |
sleep_ms(200) | |
try: | |
main() | |
except KeyboardInterrupt : | |
print('CTRL-C!') | |
shutdown() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment