Files
uv-k1-k5v3-firmware-custom/App/apps/minesweeper/gen_assets.py
T

192 lines
8.0 KiB
Python
Executable File

#!/usr/bin/env python3
# Minesweeper read-only assets: texts, the tile sheet, the status-line
# smileys, the cursor steps of keys 1..9, the board background and the
# 128x64 title screen.
#
# Tile sheet: one record of TILE_W column bytes per kind (LSB = top row),
# already placed inside the tile: column i is x + 1 + i and bit b is row
# y + b of the tile's frame-buffer page. render() ORs a record in with no
# shift, and loads the whole sheet onto its stack once per frame.
#
# ./gen_assets.py minesweeper_assets.bin minesweeper_assets.h [preview.pgm]
import os, sys
sys.dont_write_bytecode = True
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
from app_assets import Assets
from app_art import Canvas, W
COLS, ROWS, TILE = 15, 6, 8 # the board, as in minesweeper_app.c
FB_PAGES = 7 # frame-buffer pages under the status line
TILE_W = 7
def cols(rows):
"""Picture rows ('#' = pixel) -> column bytes, LSB = top row."""
return [sum(1 << r for r, row in enumerate(rows) if row[c] == "#")
for c in range(len(rows[0]))]
DIGITS = [[0x02, 0x1F, 0x00], [0x19, 0x15, 0x12], # gFont3x5 '1' .. '8', the
[0x11, 0x15, 0x0A], [0x07, 0x04, 0x1F], # glyphs print_tiny draws
[0x17, 0x15, 0x09], [0x1E, 0x15, 0x1D],
[0x19, 0x05, 0x03], [0x1F, 0x15, 0x1F]]
FLAG = cols([".##..", # pennant on a pole: unlike
".###.", # the digit 1 next to it
".####",
".#...",
"###.."])
MINE = cols(["...#...", # round, four horns and a
"..###..", # highlight, as the title's
".#.###.", # big mine
"#######",
".#####.",
"..###..",
"...#..."])
BOOM = [cols(["...#...", # the mine that went off: a
".#.#.#.", # white star on its black
"..###..", # cell, big then small (two
"#######", # frames)
"..###..",
".#.#.#.",
"...#..."]),
cols([".......",
"...#...",
"..###..",
".#####.",
"..###..",
"...#...",
"......."])]
CURSOR = cols(["##...##", # corner brackets
"#.....#",
".......",
".......",
".......",
"#.....#",
"##...##"])
FACES = [[0x00, 0x10, 0x22, 0x20, 0x20, 0x22, 0x10, 0x00], # playing
[0x00, 0x00, 0x32, 0x48, 0x48, 0x32, 0x00, 0x00], # STAR held
[0x02, 0x16, 0x26, 0x22, 0x22, 0x26, 0x16, 0x02], # won
[0x05, 0x22, 0x15, 0x10, 0x10, 0x15, 0x22, 0x05]] # lost
def interior(sprite, dx, dy):
"""A sprite drawn at (x + 1 + dx, y + dy) of its tile, as a sheet record."""
out = [0] * TILE_W
for i, c in enumerate(sprite):
if c << dy > 0xFF:
raise SystemExit("tile sprite crosses the tile's page")
out[dx + i] = c << dy
return out
TILES = [[0] * TILE_W] # 0: revealed, no mine around
TILES += [interior(d, 2, 2) for d in DIGITS] # 1..8: as print_tiny at (x + 3, y + 2)
K_FLAG, K_MINE, K_CURSOR, K_BOOM = range(len(TILES), len(TILES) + 4)
TILES += [interior(FLAG, 1, 2), # 5x5 at (x + 2, y + 2)
interior(MINE, 0, 1), interior(CURSOR, 0, 1)] # 7x7 at (x + 1, y + 1)
TILES += [interior(b, 0, 1) for b in BOOM] # K_BOOM, K_BOOM + 1
# The board in frame-buffer coordinates: it stays on page boundaries (tile row
# = page) right under the status line, BOARD_X px from the left. Its soft
# shadow is a 50 % dither SHADOW_R px wide right of the board and SHADOW_B px
# under it, SHADOW_GAP px away from it all along, as cast by a raised board:
# each band starts with a 45° cut in line with a corner of the board (top
# right, bottom left).
RIGHT, BOTTOM = COLS * TILE, ROWS * TILE
BOARD_X, SHADOW_GAP, SHADOW_R, SHADOW_B = 1, 1, 4, 3
assert BOARD_X + RIGHT + SHADOW_GAP + SHADOW_R < W
assert BOTTOM + SHADOW_GAP + SHADOW_B < FB_PAGES * 8
def board_background():
"""The board outline and its soft shadow, as the frame-buffer pages
render() copies in place of a clear before drawing the tiles."""
px = set()
left, right = BOARD_X, BOARD_X + RIGHT
for x in range(left, right + 1): # the outline, as draw_rect
px.add((x, 0)); px.add((x, BOTTOM))
for y in range(BOTTOM + 1):
px.add((left, y)); px.add((right, y))
edge_x, edge_y = right + SHADOW_GAP, BOTTOM + SHADOW_GAP # last blank column / row
for y in range(edge_y + SHADOW_B + 1): # the shadow, off the board
for x in range(left, edge_x + SHADOW_R + 1):
side = x > edge_x and y >= x - right # right band, cut in line with (right, 0)
under = y > edge_y and x >= left + y - BOTTOM # bottom band, in line with (left, BOTTOM)
if (side or under) and (x + y) % 2 == 0:
px.add((x, y))
return bytes(sum(1 << b for b in range(8) if (x, page * 8 + b) in px)
for page in range(FB_PAGES) for x in range(W))
# Cursor step (dx, dy) of keys 1..9: the keypad as a compass, 5 stays put.
DIRS = [-1, -1, 0, -1, 1, -1,
-1, 0, 0, 0, 1, 0,
-1, 1, 0, 1, 1, 1]
def tile(cv, kind, x, y, hidden):
"""A tile as render() draws it, its top-left corner at screen (x, y)."""
if hidden:
for k in range(TILE + 1):
cv.set(x + k, y); cv.set(x + k, y + TILE); cv.set(x, y + k); cv.set(x + TILE, y + k)
for i, c in enumerate(TILES[kind]):
for bit in range(8):
if c >> bit & 1:
cv.set(x + 1 + i, y + bit)
def big_mine(cv, cx, cy):
"""Round sea mine: an 11 px disc, eight horns and a highlight."""
for y in range(-5, 6):
for x in range(-5, 6):
if x * x + y * y <= 30: # r * r + r: a round pixel disc
cv.set(cx + x, cy + y)
for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)):
for k in (6, 7):
cv.set(cx + dx * k, cy + dy * k)
for dx, dy in ((-1, -1), (1, -1), (-1, 1), (1, 1)): # short, off the corners
cv.set(cx + dx * 4, cy + dy * 4)
for x, y in ((-2, -3), (-3, -2), (-2, -2)):
cv.set(cx + x, cy + y, 0)
def big_flag(cv, x, y):
"""A 14 px flag: pole, pennant pointing right, stepped base."""
for k in range(13):
cv.set(x + 3, y + k)
for r, w in enumerate([3, 5, 7, 9, 7, 5, 3]):
for k in range(1, w + 1):
cv.set(x + 3 + k, y + r)
for k in range(1, 6):
cv.set(x + k, y + 12)
for k in range(7):
cv.set(x + k, y + 13)
def title_screen():
"""MINE / SWEEPER between a big mine and a big flag, over a row of
tiles as the game draws them (screen y = frame-buffer y + 8)."""
cv = Canvas()
cv.two_words("MINE", "SWEEPER")
big_mine(cv, 16, 10)
big_flag(cv, 101, 3)
for i, t in enumerate("HHHF1..12FHHHHH"): # Hidden, Flag, '.' empty, digits
kind = K_FLAG if t == "F" else int(t) if t.isdigit() else 0
tile(cv, kind, 4 + i * 8, 46, t in "HF")
return cv
preview_path = sys.argv.pop(3) if len(sys.argv) == 4 else None
a = Assets("MINESWEEPER")
a.text("T_FLAGS", "FLAGS LEFT:")
a.text("T_WIN", "YOU WIN!")
a.text("T_LOST", "YOU LOST!")
a.text("T_PRESS", "PRESS MENU")
a.u8("TILES", sum(TILES, []))
a.const("TILE_W", TILE_W)
a.const("K_FLAG", K_FLAG)
a.const("K_MINE", K_MINE)
a.const("K_CURSOR", K_CURSOR)
a.const("K_BOOM", K_BOOM)
a.u8("FACE", sum(FACES, []))
a.const("FACE_W", len(FACES[0]))
for i, name in enumerate(("FACE_PLAY", "FACE_SCARED", "FACE_WON", "FACE_LOST")):
a.const(name, i)
a.i8("DIRS", DIRS)
a.raw("ART_BOARD", board_background())
a.const("BOARD_X", BOARD_X)
title = title_screen()
a.raw("ART_TITLE", title.pages())
a.main()
if preview_path:
title.preview(preview_path)