- Fix Little-Endian DIB color inversion (0000FFFFh/0000D7FFh -> 00FFFF00h/00FFD700h) restoring luxurious Gold, Emerald Felt, and Crimson Red palettes across the entire game
- Extract authentic IBM VGA 8x8 font from app866.fon fixing Cyrillic glyph distortion ('М', 'Л', 'П', 'Д')
- Implement native in-engine 2.5D Dead by Daylight Skill Check (MODE_SKILL) eliminating external console popups
- Fix audio mappings to dedicated minigame sounds with zero gunshots outside Russian roulette
- Update visual rasterization test suite and verified 640x480 preview renders
451 lines
18 KiB
Python
451 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
Visual simulation & rasterization test for 2.5D Casino Minigames.
|
||
Renders pixel-perfect 640x480 (320x240 upscaled 2x) PNG previews of:
|
||
- Coin Flip (Монетка)
|
||
- Craps / Dice (Кости)
|
||
- Skulls & Cups (Напёрстки)
|
||
- Dead by Daylight Skill Check (Проверка реакции)
|
||
Uses authentic IBM VGA 8x8 font and correct 0x00RRGGBB Little-Endian DIB color space.
|
||
"""
|
||
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):
|
||
# 0x00RRGGBB format:
|
||
# In x86 Little-Endian StretchDIBits (BI_RGB 32-bit):
|
||
# byte 0 = Blue (c & 0xFF)
|
||
# byte 1 = Green ((c >> 8) & 0xFF)
|
||
# byte 2 = Red ((c >> 16) & 0xFF)
|
||
r = (c >> 16) & 0xFF
|
||
g = (c >> 8) & 0xFF
|
||
b = c & 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 Exception:
|
||
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 = 0x00FFFF00 # Bright Yellow border
|
||
fill_c = 0x001B4B22 # Casino Green fill
|
||
txt_c = 0x00FFFF00 # Bright Yellow text
|
||
else:
|
||
border_c = 0x00606870 # Slate border
|
||
fill_c = 0x001A1C22 # Charcoal fill
|
||
txt_c = 0x00D0D8E0 # Silver text
|
||
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):
|
||
for i in range(count):
|
||
cy = y - i * 4
|
||
self.draw_box(x + 2, cy + 5, 20, 2, 0x00061004) # shadow
|
||
self.draw_box_border(x, cy, 24, 6, 0x00B81515, 0x00FFFFFF, 1) # Red chip
|
||
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 = 0x009A7A00 # Dark Gold relief
|
||
gold_bright = 0x00FFD700
|
||
# Double-headed imperial eagle relief
|
||
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, gold_bright)
|
||
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 = 0x009A7A00
|
||
gold_bright = 0x00FFD700
|
||
# Roman numeral 'I' & laurel crown
|
||
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, gold_bright)
|
||
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, 0x00081403)
|
||
self.draw_box(x + size, y + 3, 3, size, 0x00081403)
|
||
body_color = 0x00FAF4F0
|
||
bevel_color = 0x00D0C6C0
|
||
border_color = 0x00403535
|
||
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 = 0x00C81515 # Red ruby pips
|
||
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, 0x0010040A)
|
||
else:
|
||
self.draw_box(x + 10, y + 42, 28, 2, 0x001A0612)
|
||
|
||
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, 0x00301A22, 0x00D0B000, 1)
|
||
self.draw_box(x + 18, lift_y + 28, 12, 8, 0x00452533)
|
||
self.draw_box(x + 22, lift_y + 28, 4, 8, 0x006E4055)
|
||
self.draw_box_border(x + 8, lift_y + 8, 32, 20, 0x00401E2D, 0x00FFD700, 1)
|
||
self.draw_box(x + 14, lift_y + 10, 6, 16, 0x00703550)
|
||
self.draw_box(x + 17, lift_y + 10, 3, 16, 0x00A05078)
|
||
self.draw_box_border(x + 4, lift_y + 2, 40, 7, 0x00200A15, 0x00FFFF00, 1)
|
||
self.draw_box(x + 12, lift_y + 4, 16, 2, 0x00FFFF00)
|
||
|
||
# Skull face relief on goblet
|
||
self.draw_box(x + 21, lift_y + 12, 6, 5, 0x00FFD700)
|
||
self.draw_box(x + 22, lift_y + 17, 4, 3, 0x00FFD700)
|
||
self.draw_pixel(x + 22, lift_y + 14, 0x00200A15)
|
||
self.draw_pixel(x + 24, lift_y + 14, 0x00200A15)
|
||
self.draw_string(x + 21, lift_y + 24, str(cup_id), 0x00FFFF00)
|
||
|
||
def draw_glowing_orb(self, cx, cy):
|
||
self.draw_circle(cx, cy, 14, 0x002E1E08)
|
||
self.draw_circle(cx, cy, 11, 0x00583A10)
|
||
self.draw_circle(cx, cy, 8, 0x008A581A)
|
||
self.draw_circle(cx, cy, 6, 0x00D0B000)
|
||
self.draw_circle(cx, cy, 4, 0x00FFE000)
|
||
self.draw_circle(cx - 1, cy - 1, 2, 0x00FFFF80)
|
||
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, 0x00140906) # Mahogany cabinet
|
||
b.draw_box_border(6, 6, 308, 228, 0x0022150F, 0x00482E1B, 3)
|
||
for cx in [12, 306]:
|
||
for cy in [12, 226]:
|
||
b.draw_box(cx, cy, 3, 3, 0x00FFD700)
|
||
b.draw_pixel(cx + 1, cy + 1, 0x00FFFFFF)
|
||
|
||
b.draw_box_border(20, 12, 280, 26, 0x001C100A, 0x00FFD700, 2)
|
||
b.draw_string(44, 21, '* * * З О Л О Т А Я М О Н Е Т А * * *', 0x00FFFF00)
|
||
for bx in range(24, 296, 12):
|
||
b.draw_pixel(bx, 13, 0x00FFFF00)
|
||
b.draw_pixel(bx, 36, 0x00FFD700)
|
||
|
||
# Status meters
|
||
b.draw_box_border(20, 44, 110, 18, 0x000E1A0A, 0x00224B1A, 1)
|
||
b.draw_string(24, 49, 'БАНК: $12500', 0x003CFF3C)
|
||
b.draw_box_border(190, 44, 110, 18, 0x001A100A, 0x003A2814, 1)
|
||
b.draw_string(194, 49, 'СТАВКА: $250', 0x00FFD700)
|
||
|
||
# Side indicators
|
||
# Left: Heads (Selected)
|
||
b.draw_box(20, 72, 76, 68, 0x000A101C)
|
||
b.draw_box_border(20, 72, 76, 68, 0x00101828, 0x00FFFF00, 2)
|
||
b.draw_string(30, 78, '[1] ОРЁЛ', 0x00FFFF00)
|
||
b.draw_coin_ellipse(58, 106, 14, 14, 0x00D0A000, 0x00FFD700, 1)
|
||
b.draw_string(24, 126, 'ВЫБРАНО', 0x003CFF3C)
|
||
|
||
# Right: Tails
|
||
b.draw_box(224, 72, 76, 68, 0x000A101C)
|
||
b.draw_box_border(224, 72, 76, 68, 0x00101828, 0x00404855, 1)
|
||
b.draw_string(234, 78, '[2] РЕШКА', 0x00D0D8E0)
|
||
b.draw_coin_ellipse(262, 106, 14, 14, 0x00A08000, 0x00D0B000, 2)
|
||
|
||
# Center coin spotlight
|
||
b.draw_circle(160, 110, 48, 0x00121620)
|
||
b.draw_circle(160, 110, 44, 0x00FFD700)
|
||
b.draw_circle(160, 110, 42, 0x000E141E)
|
||
b.draw_coin_ellipse(160, 110, 26, 26, 0x00D0A000, 0x00FFD700, 1)
|
||
|
||
# Result Banner
|
||
b.draw_box_border(44, 154, 232, 22, 0x000E141E, 0x003CFF3C, 1)
|
||
b.draw_string(60, 161, 'ПОБЕДА: +$500! СЕРИЯ x3', 0x003CFF3C)
|
||
|
||
# Bottom Keycaps
|
||
kx = 16
|
||
kx += b.draw_keycap(kx, 192, '[1] ОРЁЛ', is_active=True)
|
||
kx += b.draw_keycap(kx, 192, '[2] РЕШКА', is_active=False)
|
||
kx += b.draw_keycap(kx, 192, '[SPACE]', is_active=True)
|
||
b.draw_keycap(kx, 192, '[ESC]', is_active=False)
|
||
|
||
b.save(filename)
|
||
|
||
# ==============================================================================
|
||
# 2. RENDER DICE GAME (Кости)
|
||
# ==============================================================================
|
||
def render_dice(filename='dice_preview.png'):
|
||
b = RasterBuffer()
|
||
b.draw_box(0, 0, 320, 240, 0x00140906) # Mahogany frame
|
||
b.draw_box_border(6, 6, 308, 228, 0x000A3816, 0x00FFD700, 2) # Deep Green Felt
|
||
|
||
# Header
|
||
b.draw_box_border(20, 12, 280, 26, 0x00072E13, 0x00FFD700, 2)
|
||
b.draw_string(48, 21, '=== G R A N D C R A P S ===', 0x00FFFF00)
|
||
|
||
# Status
|
||
b.draw_box_border(20, 44, 110, 18, 0x00072E13, 0x001B4B22, 1)
|
||
b.draw_string(24, 49, 'БАНК: $12500', 0x003CFF3C)
|
||
b.draw_box_border(190, 44, 110, 18, 0x00072E13, 0x001B4B22, 1)
|
||
b.draw_string(194, 49, 'СТАВКА: $100', 0x00FFD700)
|
||
|
||
# Betting Pass Lines
|
||
# [1] ЧЁТ
|
||
b.draw_box_border(16, 70, 88, 48, 0x00072E13, 0x00FFFF00, 2)
|
||
b.draw_string(30, 76, '[1] ЧЁТ', 0x00FFFF00)
|
||
b.draw_string(22, 90, ' 2 4 6 8 10 ', 0x00FFD700)
|
||
b.draw_string(26, 104, 'ВЫБРАНО', 0x003CFF3C)
|
||
|
||
# [2] НЕЧЕТ
|
||
b.draw_box_border(216, 70, 88, 48, 0x00072E13, 0x001B4B22, 1)
|
||
b.draw_string(226, 76, '[2] НЕЧЕТ', 0x00D0D8E0)
|
||
b.draw_string(224, 90, ' 3 5 7 9 11 ', 0x0080B090)
|
||
|
||
# Center Craps Table: 2 Dice
|
||
b.draw_craps_die(116, 80, 36, 4)
|
||
b.draw_craps_die(168, 76, 36, 6)
|
||
|
||
# Sum & Result
|
||
b.draw_box_border(36, 132, 248, 36, 0x00072E13, 0x00FFD700, 1)
|
||
b.draw_string(48, 138, 'СУММА: 4 + 6 = 10 (ЧЁТ)', 0x00FFFF00)
|
||
b.draw_string(68, 152, 'ВЫИГРЫШ: +$200! (x2)', 0x003CFF3C)
|
||
|
||
# Keycaps
|
||
kx = 16
|
||
kx += b.draw_keycap(kx, 192, '[1] ЧЁТ', is_active=True)
|
||
kx += b.draw_keycap(kx, 192, '[2] НЕЧЕТ', is_active=False)
|
||
kx += b.draw_keycap(kx, 192, '[SPACE]', is_active=True)
|
||
b.draw_keycap(kx, 192, '[ESC]', is_active=False)
|
||
|
||
b.save(filename)
|
||
|
||
# ==============================================================================
|
||
# 3. RENDER SKULLS GAME (Напёрстки)
|
||
# ==============================================================================
|
||
def render_skulls(filename='skulls_preview.png'):
|
||
b = RasterBuffer()
|
||
b.draw_box(0, 0, 320, 240, 0x00140906)
|
||
b.draw_box_border(6, 6, 308, 228, 0x00300F22, 0x00FFD700, 2) # Gothic Purple
|
||
|
||
# Header
|
||
b.draw_box_border(20, 12, 280, 26, 0x001A0812, 0x00FFD700, 2)
|
||
b.draw_string(44, 21, '* * * Н А П Ё Р С Т К И * * *', 0x00FFFF00)
|
||
|
||
# Status
|
||
b.draw_box_border(20, 44, 110, 18, 0x001A0812, 0x00452033, 1)
|
||
b.draw_string(24, 49, 'БАНК: $12500', 0x003CFF3C)
|
||
b.draw_box_border(190, 44, 110, 18, 0x001A0812, 0x00452033, 1)
|
||
b.draw_string(194, 49, 'СТАВКА: $300', 0x00FFD700)
|
||
|
||
# Subtitle
|
||
b.draw_string(40, 62, 'ВЫИГРЫШ: +$900! (x3) ВЫ УГАДАЛИ!', 0x003CFF3C)
|
||
|
||
# 3 Cups: Cup 2 lifted revealing gold ball
|
||
b.draw_gothic_cup(36, 114, 1, is_lifted=False, has_ball=False)
|
||
b.draw_gothic_cup(136, 114, 2, is_lifted=True, has_ball=True)
|
||
b.draw_gothic_cup(236, 114, 3, is_lifted=False, has_ball=False)
|
||
|
||
# Selection indicators
|
||
b.draw_string(54, 168, '[ 1 ]', 0x00D0D8E0)
|
||
b.draw_string(150, 168, '[ 2 ]', 0x00FFFF00)
|
||
b.draw_string(250, 168, '[ 3 ]', 0x00D0D8E0)
|
||
|
||
# Keycaps
|
||
kx = 16
|
||
kx += b.draw_keycap(kx, 192, '[1]', is_active=False)
|
||
kx += b.draw_keycap(kx, 192, '[2]', is_active=True)
|
||
kx += b.draw_keycap(kx, 192, '[3]', is_active=False)
|
||
kx += b.draw_keycap(kx, 192, '[SPACE] СТАРТ', is_active=True)
|
||
b.draw_keycap(kx, 192, '[ESC]', is_active=False)
|
||
|
||
b.save(filename)
|
||
|
||
# ==============================================================================
|
||
# 4. RENDER SKILL CHECK GAME (Dead by Daylight Survivor Check)
|
||
# ==============================================================================
|
||
def render_skill(filename='skill_preview.png'):
|
||
b = RasterBuffer()
|
||
# Industrial survivor steel bezel
|
||
b.draw_box(0, 0, 320, 240, 0x00121416)
|
||
b.draw_box_border(6, 6, 308, 228, 0x001A1D20, 0x00FFD700, 2)
|
||
|
||
# Header
|
||
b.draw_box_border(20, 12, 280, 26, 0x00121418, 0x00FFD700, 2)
|
||
b.draw_string(44, 21, '* * * Н А П Ё Р С Т К И * * *', 0x00FFFF00)
|
||
|
||
# Status
|
||
b.draw_box_border(20, 44, 110, 18, 0x00121418, 0x002A3540, 1)
|
||
b.draw_string(24, 49, 'БАНК: $12500', 0x003CFF3C)
|
||
b.draw_box_border(190, 44, 110, 18, 0x00121418, 0x002A3540, 1)
|
||
b.draw_string(194, 49, 'СТАВКА: $200', 0x00FFD700)
|
||
|
||
# Survivor progress
|
||
b.draw_string(24, 70, 'ХИТ: 3 / 5', 0x003CFF3C)
|
||
b.draw_string(180, 70, 'ПРОМАХ: 1 / 3', 0x00FF6464)
|
||
|
||
# Tension Dial / Track: X=50 to 270, Y=100 to 134
|
||
track_x = 50
|
||
track_y = 102
|
||
track_w = 220
|
||
track_h = 32
|
||
|
||
# Outer frame
|
||
b.draw_box_border(track_x, track_y, track_w, track_h, 0x000F1114, 0x00404855, 2)
|
||
|
||
# Calibrated ticks
|
||
for tx in range(track_x + 10, track_x + track_w - 10, 12):
|
||
b.draw_box(tx, track_y + 2, 1, 6, 0x00303845)
|
||
b.draw_box(tx, track_y + track_h - 8, 1, 6, 0x00303845)
|
||
|
||
# Normal Success Zone (Green): X=150 to 198 (48px)
|
||
b.draw_box_border(150, track_y + 4, 48, track_h - 8, 0x00155020, 0x003CFF3C, 1)
|
||
|
||
# Great / Perfect Sweet Spot (Gold): X=186 to 198 (12px)
|
||
b.draw_box_border(186, track_y + 4, 12, track_h - 8, 0x00FFD700, 0x00FFFF00, 1)
|
||
b.draw_string(156, track_y + 10, 'HIT!', 0x003CFF3C)
|
||
|
||
# Needle at X=190 (Inside Great Zone!)
|
||
needle_x = 190
|
||
b.draw_box(needle_x - 1, track_y - 4, 3, track_h + 8, 0x00FFFFFF)
|
||
b.draw_box(needle_x - 2, track_y - 6, 5, 2, 0x00FF3030)
|
||
b.draw_box(needle_x - 2, track_y + track_h + 4, 5, 2, 0x00FF3030)
|
||
|
||
# Prompt text
|
||
b.draw_string(52, 150, 'ОТЛИЧНО! GREAT SKILL CHECK!', 0x00FFFF00)
|
||
b.draw_string(76, 166, 'НАЖМИТЕ [ПРОБЕЛ] В ЗОНЕ', 0x00D0D8E0)
|
||
|
||
# Bottom Keycaps
|
||
kx = 16
|
||
kx += b.draw_keycap(kx, 192, '[SPACE] РЕАКЦИЯ', is_active=True)
|
||
kx += b.draw_keycap(kx, 192, '[ВВЕРХ/ВНИЗ]', is_active=False)
|
||
b.draw_keycap(kx, 192, '[ESC]', is_active=False)
|
||
|
||
b.save(filename)
|
||
|
||
if __name__ == '__main__':
|
||
render_coin()
|
||
render_dice()
|
||
render_skulls()
|
||
render_skill()
|
||
print("[ALL DONE] All 4 previews successfully generated!")
|