"""Render simulation of Hell Tower with 6 floors and strict boundary checks.""" import struct from pathlib import Path from PIL import Image, ImageDraw, ImageFont ROOT = Path(__file__).resolve().parents[2] MENU_BIN = ROOT / "assets/animations/hall/helltower-menu.bin" ART_DIR = Path(r"C:\Users\IKER\.gemini\antigravity-cli\brain\c2d69e9d-8822-4f06-923a-36a9194468d6") # 6 Floors Y in logical 320x240 coordinates FLOOR_Y = [205, 174, 143, 112, 81, 50] BOSSES = [ "Бес Азарта", "Демон Костей", "Крупье Тьмы", "Судья Ада", "Хозяин Рулетки", "Владыка Башни" ] GAMES = [ "Орел и решка", "Кости", "21 / Очко", "Русская рулетка", "Казино-рулетка", "Buckshot Roulette" ] DESCS = [ "Угадай сторону монеты.", "Набери больше очков.", "Обыграй крупье в 21.", "1 патрон. Выживи!", "Угадай число рулетки.", "Дуэль на дробовике!" ] SUB_NOTES = [ "Один бросок решит все.", "Бросай кости против беса.", "Собери больше, но не перебор.", "Крути барабан револьвера.", "Ставь на черное или красное.", "Заряжай картечь и стреляй!" ] def render_menu_frame(stage: int, state: int = 0, callout_anim: int = 100): # 1. Load background data = MENU_BIN.read_bytes() magic, w, h, frames, b_len = struct.unpack("<4sIIII", data[:20]) raw_bgra = data[20:20 + b_len] bg = Image.frombytes("RGBA", (w, h), raw_bgra, "raw", "BGRA") # Scale down to 320x240 logical to replicate exact ASM blit logic canvas = bg.resize((320, 240), Image.Resampling.LANCZOS) draw = ImageDraw.Draw(canvas) floor_idx = max(0, min(5, stage - 1)) target_y = FLOOR_Y[floor_idx] # Sliding card dimensions card_h = 208 card_y = 16 card_w = max(10, 206 * callout_anim // 100) card_x = 214 - card_w # 1. Indicator line and cursor on tower draw.line([(214, target_y), (224, target_y)], fill=(255, 68, 68), width=2) draw.rectangle([224, target_y - 3, 230, target_y + 3], fill=(255, 51, 0), outline=(255, 221, 68)) draw.rectangle([234, target_y - 2, 238, target_y + 2], fill=(255, 170, 34)) # 2. Card outer/inner borders draw.rectangle([card_x, card_y, card_x + card_w, card_y + card_h], outline=(204, 34, 34), width=2) draw.rectangle([card_x + 2, card_y + 2, card_x + card_w - 2, card_y + card_h - 2], outline=(85, 17, 17), width=1) inner_w = card_w - 12 inner_x = card_x + 6 if card_w >= 120: # Header banner: "АДСКАЯ БАШНЯ" (Centered, NO "КАЗИНО 3D") hdr_w = len("АДСКАЯ БАШНЯ") * 8 hdr_x = inner_x + (inner_w - hdr_w) // 2 draw.rectangle([inner_x, 22, inner_x + inner_w, 40], fill=(34, 4, 4), outline=(255, 68, 68)) draw.text((hdr_x, 26), "АДСКАЯ БАШНЯ", fill=(255, 221, 102)) # Floor & Bankroll / Prize indicator draw.text((card_x + 12, 46), f"ЭТАЖ {stage} / 6", fill=(255, 102, 102)) draw.text((card_x + 105, 46), "КУШ: $66,600", fill=(255, 221, 68)) if state == 0: # BRIEFING # Subcard draw.rectangle([inner_x, 60, inner_x + inner_w, 130], fill=(30, 4, 4), outline=(136, 24, 24)) draw.text((card_x + 12, 66), f"ВРАГ: {BOSSES[floor_idx]}", fill=(255, 255, 255)) draw.text((card_x + 12, 80), f"ИГРА: {GAMES[floor_idx]}", fill=(255, 221, 102)) draw.text((card_x + 12, 96), DESCS[floor_idx], fill=(204, 204, 204)) draw.text((card_x + 12, 112), SUB_NOTES[floor_idx], fill=(170, 170, 170)) # Rules & warning box draw.rectangle([inner_x, 134, inner_x + inner_w, 166], fill=(24, 3, 3), outline=(102, 17, 17)) draw.text((card_x + 12, 139), "6 этажей без поражений", fill=(255, 170, 51)) draw.text((card_x + 12, 151), "Поражение - возврат!", fill=(255, 85, 85)) # Bank draw.text((card_x + 12, 172), "БАНК: $10,000", fill=(102, 255, 102)) draw.text((card_x + 115, 172), "НАГРАДА: +100%", fill=(255, 200, 50)) # Action Buttons btn_w = 93 draw.rectangle([inner_x, 188, inner_x + btn_w, 212], fill=(61, 12, 12), outline=(255, 68, 68)) draw.text((inner_x + 12, 196), "[ENTER] БОЙ", fill=(255, 255, 255)) draw.rectangle([inner_x + 101, 188, inner_x + 101 + btn_w, 212], fill=(34, 6, 6), outline=(136, 34, 34)) draw.text((inner_x + 113, 196), "[ESC] НАЗАД", fill=(170, 170, 170)) elif state == 2: # CLEAR draw.rectangle([inner_x, 62, inner_x + inner_w, 142], fill=(8, 34, 8), outline=(51, 204, 51), width=2) clr_w = len("ЭТАЖ ЗАЧИЩЕН!") * 8 draw.text((inner_x + (inner_w - clr_w) // 2, 76), "ЭТАЖ ЗАЧИЩЕН!", fill=(102, 255, 102)) sub_w = len("Враг повержен!") * 8 draw.text((inner_x + (inner_w - sub_w) // 2, 96), "Враг повержен!", fill=(255, 255, 255)) rwd_w = len("Путь наверх открыт!") * 8 draw.text((inner_x + (inner_w - rwd_w) // 2, 116), "Путь наверх открыт!", fill=(255, 221, 102)) draw.rectangle([card_x + 18, 176, card_x + 188, 202], fill=(17, 68, 17), outline=(68, 255, 68)) btn_w2 = len("[ENTER] ДАЛЬШЕ") * 8 draw.text((card_x + 18 + (170 - btn_w2) // 2, 184), "[ENTER] ДАЛЬШЕ", fill=(255, 255, 255)) elif state == 3: # DEFEAT draw.rectangle([inner_x, 62, inner_x + inner_w, 142], fill=(42, 4, 4), outline=(255, 34, 34), width=2) dft_w = len("ВЫ ПОГИБЛИ В БАШНЕ!") * 8 draw.text((inner_x + (inner_w - dft_w) // 2, 76), "ВЫ ПОГИБЛИ В БАШНЕ!", fill=(255, 68, 68)) sub_w = len("Душа сгорела в Аду.") * 8 draw.text((inner_x + (inner_w - sub_w) // 2, 98), "Душа сгорела в Аду.", fill=(255, 255, 255)) draw.rectangle([card_x + 18, 176, card_x + 188, 202], fill=(68, 16, 16), outline=(255, 68, 68)) btn_w2 = len("[ESC] В ХОЛЛ КАЗИНО") * 8 draw.text((card_x + 18 + (170 - btn_w2) // 2, 184), "[ESC] В ХОЛЛ КАЗИНО", fill=(255, 255, 255)) elif state == 4: # CHAMPION draw.rectangle([inner_x, 58, inner_x + inner_w, 150], fill=(51, 34, 0), outline=(255, 215, 0), width=2) chm_w = len("БАШНЯ ПОКОРЕНА!") * 8 draw.text((inner_x + (inner_w - chm_w) // 2, 70), "БАШНЯ ПОКОРЕНА!", fill=(255, 224, 102)) sub_w = len("Вы - Владыка Башни!") * 8 draw.text((inner_x + (inner_w - sub_w) // 2, 90), "Вы - Владыка Башни!", fill=(255, 255, 255)) prz_w = len("КУШ: +$66,600 В БАНК!") * 8 draw.text((inner_x + (inner_w - prz_w) // 2, 114), "КУШ: +$66,600 В БАНК!", fill=(102, 255, 102)) draw.rectangle([card_x + 18, 176, card_x + 188, 202], fill=(68, 51, 0), outline=(255, 204, 0)) btn_w2 = len("[ESC] В ХОЛЛ КАЗИНО") * 8 draw.text((card_x + 18 + (170 - btn_w2) // 2, 184), "[ESC] В ХОЛЛ КАЗИНО", fill=(255, 255, 255)) # Scale 2x for full HD 640x480 preview return canvas.resize((640, 480), Image.Resampling.NEAREST) def main(): ART_DIR.mkdir(parents=True, exist_ok=True) for st in range(1, 7): im = render_menu_frame(st, state=0) im.save(ART_DIR / f"helltower_6fl_floor{st}.png") print(f"[OK] Saved helltower_6fl_floor{st}.png") clr = render_menu_frame(1, state=2) clr.save(ART_DIR / "helltower_6fl_clear.png") dft = render_menu_frame(1, state=3) dft.save(ART_DIR / "helltower_6fl_defeat.png") chp = render_menu_frame(6, state=4) chp.save(ART_DIR / "helltower_6fl_champion.png") print("All previews generated successfully!") if __name__ == "__main__": main()