# -*- coding: utf-8 -*- import math from pathlib import Path from PIL import Image REPO_ROOT = Path(__file__).resolve().parents[2] OUTPUT_DIR = REPO_ROOT / 'docs' / 'previews_minigames' OUTPUT_DIR.mkdir(parents=True, exist_ok=True) # Parse font8x8.inc font_path = REPO_ROOT / 'font8x8.inc' font_bytes = bytearray(256 * 8) with open(font_path, 'r', encoding='latin1') as f: text = f.read() import re matches = re.findall(r'0([0-9A-Fa-f]{2})h', text) for i, m in enumerate(matches[:256*8]): font_bytes[i] = int(m, 16) SCREEN_W = 640 SCREEN_H = 480 class RasterBuffer: def __init__(self): self.pixels = [(0, 0, 0)] * (SCREEN_W * SCREEN_H) def to_rgb(self, c): # 0x00BBGGRR format r = c & 0xFF g = (c >> 8) & 0xFF b = (c >> 16) & 0xFF return (r, g, b) def draw_box(self, x, y, w, h, color): px = x * 2 py = y * 2 pw = w * 2 ph = h * 2 rgb = self.to_rgb(color) for row in range(py, min(py + ph, SCREEN_H)): if row < 0: continue base = row * SCREEN_W for col in range(px, min(px + pw, SCREEN_W)): if col < 0: continue self.pixels[base + col] = rgb def draw_box_border(self, x, y, w, h, fill_color, border_color, b_width): self.draw_box(x, y, w, h, border_color) self.draw_box(x + b_width, y + b_width, w - b_width * 2, h - b_width * 2, fill_color) def draw_pixel(self, x, y, color): self.draw_box(x, y, 1, 1, color) def draw_circle(self, cx, cy, radius, color): r_sq = radius * radius for dy in range(-radius, radius + 1): for dx in range(-radius, radius + 1): if dx * dx + dy * dy <= r_sq: self.draw_pixel(cx + dx, cy + dy, color) def draw_char(self, x, y, ch, color): if isinstance(ch, str): try: code = ch.encode('cp1251')[0] except: code = ord(ch) & 0xFF else: code = ch & 0xFF glyph_offset = code * 8 for row in range(8): row_bits = font_bytes[glyph_offset + row] for col in range(8): if row_bits & (0x80 >> col): self.draw_pixel(x + col, y + row, color) def draw_string(self, x, y, s, color): cur_x = x for ch in s: self.draw_char(cur_x, y, ch, color) cur_x += 8 def draw_keycap(self, x, y, text, is_active=False): w = len(text) * 8 + 8 h = 13 if is_active: border_c = 0x0000FFFF fill_c = 0x001B4B22 txt_c = 0x0000FFFF else: border_c = 0x00606870 fill_c = 0x001A1C22 txt_c = 0x00D0D8E0 self.draw_box(x, y + 2, w, h, 0x0008080C) # shadow self.draw_box_border(x, y, w, h, fill_c, border_c, 1) self.draw_string(x + 4, y + 3, text, txt_c) return w + 4 def draw_chip_stack(self, x, y, count=3): # Draw stack of 3D casino clay chips for i in range(count): cy = y - i * 4 self.draw_box(x + 2, cy + 5, 20, 2, 0x00041006) # shadow self.draw_box_border(x, cy, 24, 6, 0x001515B8, 0x00FFFFFF, 1) # white stripe inlays self.draw_box(x + 4, cy + 1, 3, 4, 0x00FFFFFF) self.draw_box(x + 17, cy + 1, 3, 4, 0x00FFFFFF) def draw_coin_ellipse(self, cx, cy, rx, ry, outer_color, inner_color, relief=0): if rx <= 0 or ry <= 0: return rx_sq = rx * rx ry_sq = ry * ry rx_ry_sq = rx_sq * ry_sq thresh_sq = (rx_ry_sq * 3) // 4 for dy in range(-ry, ry + 1): cur_y = cy + dy for dx in range(-rx, rx + 1): cur_x = cx + dx val = dx * dx * ry_sq + dy * dy * rx_sq if val <= rx_ry_sq: if val >= thresh_sq: self.draw_pixel(cur_x, cur_y, outer_color) else: self.draw_pixel(cur_x, cur_y, inner_color) if rx >= 14 and relief == 1: gold_dark = 0x00007A9A # Imperial Eagle self.draw_box(cx - 3, cy - 14, 7, 2, gold_dark) self.draw_box(cx - 4, cy - 12, 9, 2, gold_dark) self.draw_box(cx - 2, cy - 9, 5, 12, gold_dark) self.draw_box(cx - 1, cy - 8, 3, 10, 0x0000D7FF) self.draw_box(cx - 10, cy - 8, 7, 3, gold_dark) self.draw_box(cx - 13, cy - 5, 10, 3, gold_dark) self.draw_box(cx - 11, cy - 2, 7, 3, gold_dark) self.draw_box(cx + 4, cy - 8, 7, 3, gold_dark) self.draw_box(cx + 4, cy - 5, 10, 3, gold_dark) self.draw_box(cx + 5, cy - 2, 7, 3, gold_dark) self.draw_box(cx - 4, cy + 4, 9, 3, gold_dark) self.draw_box(cx - 3, cy + 7, 7, 3, gold_dark) elif rx >= 14 and relief == 2: gold_dark = 0x00007A9A # Roman numeral 'I' self.draw_box(cx - 5, cy - 10, 11, 3, gold_dark) self.draw_box(cx - 2, cy - 7, 5, 14, gold_dark) self.draw_box(cx - 1, cy - 6, 3, 12, 0x0000D7FF) self.draw_box(cx - 6, cy + 7, 13, 3, gold_dark) for w_dy in [-10, -5, 0, 5, 9]: self.draw_box(cx - 12, cy + w_dy, 2, 2, gold_dark) self.draw_box(cx + 11, cy + w_dy, 2, 2, gold_dark) def draw_craps_die(self, x, y, size, val): self.draw_box(x + 3, y + size, size, 3, 0x00031408) self.draw_box(x + size, y + 3, 3, size, 0x00031408) body_color = 0x00F0F4FA bevel_color = 0x00C0C6D0 border_color = 0x00353540 self.draw_box_border(x, y, size, size, body_color, border_color, 1) self.draw_box(x + 1, y + 1, size - 2, 1, 0x00FFFFFF) self.draw_box(x + 1, y + 1, 1, size - 2, 0x00FFFFFF) self.draw_box(x + 1, y + size - 2, size - 2, 1, bevel_color) self.draw_box(x + size - 2, y + 1, 1, size - 2, bevel_color) pip_c = 0x001515C8 pip_r = 3 if val in [1, 3, 5]: self.draw_circle(x + size // 2, y + size // 2, pip_r, pip_c) self.draw_pixel(x + size // 2 - 1, y + size // 2 - 1, 0x00FFFFFF) if val >= 2: self.draw_circle(x + 9, y + 9, pip_r, pip_c) self.draw_circle(x + size - 10, y + size - 10, pip_r, pip_c) if val >= 4: self.draw_circle(x + size - 10, y + 9, pip_r, pip_c) self.draw_circle(x + 9, y + size - 10, pip_r, pip_c) if val == 6: self.draw_circle(x + 9, y + size // 2, pip_r, pip_c) self.draw_circle(x + size - 10, y + size // 2, pip_r, pip_c) def draw_gothic_cup(self, x, y, cup_id, is_lifted=False, has_ball=False): lift_y = y - 36 if is_lifted else y if not is_lifted: self.draw_box(x + 6, y + 42, 36, 4, 0x000A0410) else: self.draw_box(x + 10, y + 42, 28, 2, 0x0012061A) if is_lifted and has_ball: self.draw_glowing_orb(x + 24, y + 26) self.draw_box_border(x + 6, lift_y + 36, 36, 6, 0x00221A30, 0x0000B0D0, 1) self.draw_box(x + 18, lift_y + 28, 12, 8, 0x00332545) self.draw_box(x + 22, lift_y + 28, 4, 8, 0x0055406E) self.draw_box_border(x + 8, lift_y + 8, 32, 20, 0x002D1E40, 0x0000D7FF, 1) self.draw_box(x + 14, lift_y + 10, 6, 16, 0x00503570) self.draw_box(x + 17, lift_y + 10, 3, 16, 0x007850A0) self.draw_box_border(x + 4, lift_y + 2, 40, 7, 0x00150A20, 0x0000FFFF, 1) self.draw_box(x + 12, lift_y + 4, 16, 2, 0x0000FFFF) self.draw_box(x + 21, lift_y + 12, 6, 5, 0x0000D7FF) self.draw_box(x + 22, lift_y + 17, 4, 3, 0x0000D7FF) self.draw_pixel(x + 22, lift_y + 14, 0x00150A20) self.draw_pixel(x + 24, lift_y + 14, 0x00150A20) self.draw_string(x + 21, lift_y + 24, str(cup_id), 0x0000FFFF) def draw_glowing_orb(self, cx, cy): self.draw_circle(cx, cy, 14, 0x00081E2E) self.draw_circle(cx, cy, 11, 0x00103A58) self.draw_circle(cx, cy, 8, 0x001A588A) self.draw_circle(cx, cy, 6, 0x0000B0E0) self.draw_circle(cx, cy, 4, 0x0000E0FF) self.draw_circle(cx - 1, cy - 1, 2, 0x00A0FFFF) self.draw_pixel(cx - 2, cy - 2, 0x00FFFFFF) def save(self, filename): img = Image.new('RGB', (SCREEN_W, SCREEN_H)) img.putdata(self.pixels) out_path = OUTPUT_DIR / filename img.save(out_path) print(f"Saved {out_path}") # ============================================================================== # 1. RENDER COIN GAME # ============================================================================== def render_coin(filename='coin_preview.png'): b = RasterBuffer() b.draw_box(0, 0, 320, 240, 0x00060914) # Mahogany cabinet b.draw_box_border(6, 6, 308, 228, 0x000F1522, 0x001B2E48, 3) for cx in [12, 306]: for cy in [12, 226]: b.draw_box(cx, cy, 3, 3, 0x0000D7FF) b.draw_pixel(cx + 1, cy + 1, 0x00FFFFFF) b.draw_box_border(20, 12, 280, 26, 0x000A101C, 0x0000D7FF, 2) b.draw_string(44, 21, '*** З О Л О Т А Я М О Н Е Т А ***', 0x0000FFFF) for bx in range(24, 296, 12): b.draw_pixel(bx, 13, 0x0000FFFF) b.draw_pixel(bx, 36, 0x0000D7FF) # Status meters b.draw_box_border(20, 44, 110, 18, 0x000A1A0E, 0x001A4B22, 1) b.draw_string(24, 49, 'БАНК: $12500', 0x0050FF50) b.draw_box_border(190, 44, 110, 18, 0x001A100A, 0x003A2814, 1) b.draw_string(194, 49, 'СТАВКА: $50 [W/S]', 0x0000D7FF) # Center Dish b.draw_circle(160, 110, 46, 0x000090C0) b.draw_circle(160, 110, 44, 0x0000D7FF) b.draw_circle(160, 110, 41, 0x00180824) # Coin at rest (Heads) b.draw_coin_ellipse(160, 110, 26, 26, 0x0000A0D0, 0x0000D7FF, relief=1) # Left: [1] ОРЕЛ (Active) b.draw_box_border(20, 72, 76, 52, 0x001C3822, 0x0000FFFF, 2) b.draw_string(28, 78, '[1] ОРЁЛ', 0x0000FFFF) b.draw_coin_ellipse(58, 106, 12, 12, 0x0000A0D0, 0x0000D7FF, relief=1) # Right: [2] РЕШКА b.draw_box_border(224, 72, 76, 52, 0x00121620, 0x00404855, 1) b.draw_string(232, 78, '[2] РЕШКА', 0x0090A0B0) b.draw_coin_ellipse(262, 106, 12, 12, 0x000080A0, 0x0000B0D0, relief=2) # Streak b.draw_box_border(44, 154, 232, 20, 0x000E141E, 0x0000D7FF, 1) b.draw_string(52, 160, 'СЕРИЯ: [X] [X] [X] [ ] [ ] x4!', 0x0000FFFF) # Keycaps b.draw_box(16, 194, 288, 1, 0x001E2E40) b.draw_keycap(22, 202, '1:ОРЁЛ', is_active=True) b.draw_keycap(88, 202, '2:РЕШКА') b.draw_keycap(160, 202, 'ПРОБЕЛ:БРОСОК', is_active=True) b.draw_keycap(274, 202, 'ESC') b.save(filename) # ============================================================================== # 2. RENDER DICE GAME # ============================================================================== def render_dice(filename='dice_preview.png'): b = RasterBuffer() # Emerald felt b.draw_box(0, 0, 320, 240, 0x00082E12) # Oak wood border & leather bumper b.draw_box_border(4, 4, 312, 232, 0x00093816, 0x00122244, 3) b.draw_box_border(8, 8, 304, 224, 0x000A3F19, 0x0005140A, 2) # Pyramidal backboard rubber diamond texture along top for bx in range(12, 308, 12): b.draw_box(bx + 4, 11, 4, 4, 0x00041006) b.draw_pixel(bx + 5, 12, 0x000F4820) # Header b.draw_string(48, 20, '==== G R A N D C R A P S T A B L E ====', 0x0000D7FF) b.draw_string(72, 32, '[ PASS LINE ] [ ANY CRAPS ]', 0x0000B0A0) # Status b.draw_box_border(16, 46, 100, 16, 0x0006240E, 0x000E5022, 1) b.draw_string(20, 50, 'БАНК: $12500', 0x0050FF50) b.draw_box_border(204, 46, 100, 16, 0x0006240E, 0x000E5022, 1) b.draw_string(208, 50, 'СТАВКА: $50 [W/S]', 0x0000D7FF) # Betting Zones # Left: [1] ЧЕТ (Active with chip stack!) b.draw_box_border(16, 68, 88, 54, 0x000B441D, 0x0000FFFF, 2) b.draw_string(24, 74, '[1] ЧЁТ', 0x0000FFFF) b.draw_string(20, 86, '2 4 6 8 10 12', 0x0000D7FF) b.draw_chip_stack(48, 110, count=4) # Right: [2] НЕЧЕТ b.draw_box_border(216, 68, 88, 54, 0x00073014, 0x001A602A, 1) b.draw_string(224, 74, '[2] НЕЧЕТ', 0x0080B090) b.draw_string(222, 86, '3 5 7 9 11', 0x00508060) # Center Dice Field b.draw_box_border(112, 68, 96, 54, 0x00072E13, 0x00145826, 1) # Draw Two Precision Dice b.draw_craps_die(120, 76, 36, 4) b.draw_craps_die(164, 76, 36, 6) # Score Plaque b.draw_box_border(36, 130, 248, 30, 0x00051D0C, 0x0000D7FF, 1) b.draw_string(44, 135, 'СУММА: 4 + 6 = 10 (ЧЁТ)', 0x0000FFFF) b.draw_string(68, 147, 'ВЫИГРЫШ: +$100! (x2)', 0x0050FF50) # Keycaps b.draw_box(16, 188, 288, 1, 0x00104820) b.draw_keycap(20, 198, '1:ЧЁТ', is_active=True) b.draw_keycap(82, 198, '2:НЕЧЕТ') b.draw_keycap(160, 198, 'ПРОБЕЛ:БРОСОК', is_active=True) b.draw_keycap(274, 198, 'ESC') b.save(filename) # ============================================================================== # 3. RENDER SKULLS GAME # ============================================================================== def render_skulls(filename='skulls_preview.png'): b = RasterBuffer() # Dark gothic table with purple velvet b.draw_box(0, 0, 320, 240, 0x000D0612) b.draw_box_border(4, 4, 312, 232, 0x00160B20, 0x00321845, 3) b.draw_box_border(8, 8, 304, 224, 0x00220F30, 0x0000D7FF, 1) # Header b.draw_box_border(24, 12, 272, 24, 0x0012081A, 0x0000D7FF, 2) b.draw_string(48, 20, '+ + + Н А П Ё Р С Т К И С К В Е Р Н Ы + + +', 0x0000FFFF) # Status b.draw_box_border(20, 42, 110, 16, 0x000D0616, 0x00401858, 1) b.draw_string(24, 46, 'БАНК: $12500', 0x0050FF50) b.draw_box_border(190, 42, 110, 16, 0x000D0616, 0x00401858, 1) b.draw_string(194, 46, 'СТАВКА: $50 [W/S]', 0x0000D7FF) # Table Runner b.draw_box_border(16, 66, 288, 114, 0x001A0B25, 0x00452060, 1) # Narrative banner b.draw_string(40, 74, 'СЛЕДИ ЗА ЗОЛОТЫМ ОКОМ СУДЬБЫ...', 0x0000FFFF) # Draw 3 Gothic Cups (Cup 2 is lifted revealing glowing orb!) b.draw_gothic_cup(48, 100, 1, is_lifted=False) b.draw_gothic_cup(136, 100, 2, is_lifted=True, has_ball=True) b.draw_gothic_cup(224, 100, 3, is_lifted=False) # Keycaps b.draw_box(16, 190, 288, 1, 0x003A1850) b.draw_keycap(18, 200, '1:КУБОК') b.draw_keycap(82, 200, '2:КУБОК', is_active=True) b.draw_keycap(146, 200, '3:КУБОК') b.draw_keycap(212, 200, 'ПРОБЕЛ', is_active=True) b.draw_keycap(274, 200, 'ESC') b.save(filename) if __name__ == '__main__': render_coin() render_dice() render_skulls() print("All 3 minigames previews rendered successfully!")