Add cookbook and tutorial showcase demos
tests/demo/: - cookbook_showcase.py: Interactive demo of cookbook recipes - tutorial_showcase.py: Visual walkthrough of tutorial content - tutorial_screenshots.py: Automated screenshot generation - new_features_showcase.py: Demo of modern API features - procgen_showcase.py: Procedural generation examples - simple_showcase.py: Minimal working examples Created during docs modernization to verify cookbook examples work. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
23afae69ad
commit
a1b692bb1f
6 changed files with 1734 additions and 0 deletions
495
tests/demo/cookbook_showcase.py
Normal file
495
tests/demo/cookbook_showcase.py
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cookbook Screenshot Showcase - Visual examples for cookbook recipes!
|
||||
|
||||
Generates beautiful screenshots for cookbook pages.
|
||||
Run with: xvfb-run -a ./build/mcrogueface --headless --exec tests/demo/cookbook_showcase.py
|
||||
|
||||
In headless mode, automation.screenshot() is SYNCHRONOUS - no timer dance needed!
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Output directory - in the docs site images folder
|
||||
OUTPUT_DIR = "/opt/goblincorps/repos/mcrogueface.github.io/images/cookbook"
|
||||
|
||||
# Tile sprites from the labeled tileset
|
||||
TILES = {
|
||||
'player_knight': 84,
|
||||
'player_mage': 85,
|
||||
'player_rogue': 86,
|
||||
'player_warrior': 87,
|
||||
'enemy_slime': 108,
|
||||
'enemy_orc': 120,
|
||||
'enemy_skeleton': 123,
|
||||
'floor_stone': 42,
|
||||
'wall_stone': 30,
|
||||
'wall_brick': 14,
|
||||
'torch': 72,
|
||||
'chest_closed': 89,
|
||||
'item_potion': 113,
|
||||
}
|
||||
|
||||
|
||||
def screenshot_health_bar():
|
||||
"""Create a health bar showcase."""
|
||||
scene = mcrfpy.Scene("health_bar")
|
||||
|
||||
# Dark background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600))
|
||||
bg.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Health Bar Recipe", pos=(50, 30))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Nested frames for dynamic UI elements", pos=(50, 60))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Example health bars at different levels
|
||||
y_start = 120
|
||||
bar_configs = [
|
||||
("Player - Full Health", 100, 100, mcrfpy.Color(50, 200, 50)),
|
||||
("Player - Damaged", 65, 100, mcrfpy.Color(200, 200, 50)),
|
||||
("Player - Critical", 20, 100, mcrfpy.Color(200, 50, 50)),
|
||||
("Boss - 3/4 Health", 750, 1000, mcrfpy.Color(150, 50, 150)),
|
||||
]
|
||||
|
||||
for i, (label, current, maximum, color) in enumerate(bar_configs):
|
||||
y = y_start + i * 100
|
||||
|
||||
# Label
|
||||
lbl = mcrfpy.Caption(text=label, pos=(50, y))
|
||||
lbl.fill_color = mcrfpy.Color(220, 220, 220)
|
||||
lbl.font_size = 18
|
||||
scene.children.append(lbl)
|
||||
|
||||
# Background bar
|
||||
bar_bg = mcrfpy.Frame(pos=(50, y + 30), size=(400, 30))
|
||||
bar_bg.fill_color = mcrfpy.Color(40, 40, 50)
|
||||
bar_bg.outline = 2
|
||||
bar_bg.outline_color = mcrfpy.Color(80, 80, 100)
|
||||
scene.children.append(bar_bg)
|
||||
|
||||
# Fill bar (scaled to current/maximum)
|
||||
fill_width = int(400 * (current / maximum))
|
||||
bar_fill = mcrfpy.Frame(pos=(50, y + 30), size=(fill_width, 30))
|
||||
bar_fill.fill_color = color
|
||||
scene.children.append(bar_fill)
|
||||
|
||||
# Text overlay
|
||||
hp_text = mcrfpy.Caption(text=f"{current}/{maximum}", pos=(60, y + 35))
|
||||
hp_text.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
hp_text.font_size = 16
|
||||
scene.children.append(hp_text)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "ui_health_bar.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_fog_of_war():
|
||||
"""Create a fog of war showcase."""
|
||||
scene = mcrfpy.Scene("fog_of_war")
|
||||
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 80),
|
||||
size=(700, 480),
|
||||
grid_size=(16, 12),
|
||||
texture=texture,
|
||||
zoom=2.8
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0) # Black for unknown areas
|
||||
scene.children.append(grid)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Fog of War Recipe", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Visible, discovered, and unknown areas", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Fill floor
|
||||
for y in range(12):
|
||||
for x in range(16):
|
||||
grid.at(x, y).tilesprite = TILES['floor_stone']
|
||||
|
||||
# Add walls
|
||||
for x in range(16):
|
||||
grid.at(x, 0).tilesprite = TILES['wall_stone']
|
||||
grid.at(x, 11).tilesprite = TILES['wall_stone']
|
||||
for y in range(12):
|
||||
grid.at(0, y).tilesprite = TILES['wall_stone']
|
||||
grid.at(15, y).tilesprite = TILES['wall_stone']
|
||||
|
||||
# Interior walls (to break LOS)
|
||||
for y in range(3, 8):
|
||||
grid.at(8, y).tilesprite = TILES['wall_brick']
|
||||
|
||||
# Player (mage with light)
|
||||
player = mcrfpy.Entity(grid_pos=(4, 6), texture=texture, sprite_index=TILES['player_mage'])
|
||||
grid.entities.append(player)
|
||||
|
||||
# Hidden enemies on the other side
|
||||
enemy1 = mcrfpy.Entity(grid_pos=(12, 4), texture=texture, sprite_index=TILES['enemy_orc'])
|
||||
grid.entities.append(enemy1)
|
||||
enemy2 = mcrfpy.Entity(grid_pos=(13, 8), texture=texture, sprite_index=TILES['enemy_skeleton'])
|
||||
grid.entities.append(enemy2)
|
||||
|
||||
# Torch in visible area
|
||||
torch = mcrfpy.Entity(grid_pos=(2, 3), texture=texture, sprite_index=TILES['torch'])
|
||||
grid.entities.append(torch)
|
||||
|
||||
grid.center = (4 * 16 + 8, 6 * 16 + 8)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "grid_fog_of_war.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_combat_melee():
|
||||
"""Create a melee combat showcase."""
|
||||
scene = mcrfpy.Scene("combat_melee")
|
||||
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 80),
|
||||
size=(700, 480),
|
||||
grid_size=(12, 9),
|
||||
texture=texture,
|
||||
zoom=3.5
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(grid)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Melee Combat Recipe", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Bump-to-attack mechanics with damage calculation", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Fill with dirt floor (battle arena feel)
|
||||
for y in range(9):
|
||||
for x in range(12):
|
||||
grid.at(x, y).tilesprite = 50 # dirt
|
||||
|
||||
# Brick walls
|
||||
for x in range(12):
|
||||
grid.at(x, 0).tilesprite = TILES['wall_brick']
|
||||
grid.at(x, 8).tilesprite = TILES['wall_brick']
|
||||
for y in range(9):
|
||||
grid.at(0, y).tilesprite = TILES['wall_brick']
|
||||
grid.at(11, y).tilesprite = TILES['wall_brick']
|
||||
|
||||
# Player knight engaging orc!
|
||||
player = mcrfpy.Entity(grid_pos=(4, 4), texture=texture, sprite_index=TILES['player_knight'])
|
||||
grid.entities.append(player)
|
||||
|
||||
enemy = mcrfpy.Entity(grid_pos=(6, 4), texture=texture, sprite_index=TILES['enemy_orc'])
|
||||
grid.entities.append(enemy)
|
||||
|
||||
# Fallen enemy (bones)
|
||||
bones = mcrfpy.Entity(grid_pos=(8, 6), texture=texture, sprite_index=75) # bones
|
||||
grid.entities.append(bones)
|
||||
|
||||
# Potion for healing
|
||||
potion = mcrfpy.Entity(grid_pos=(3, 2), texture=texture, sprite_index=TILES['item_potion'])
|
||||
grid.entities.append(potion)
|
||||
|
||||
grid.center = (5 * 16 + 8, 4 * 16 + 8)
|
||||
|
||||
# Combat log UI
|
||||
log_frame = mcrfpy.Frame(pos=(50, 520), size=(700, 60))
|
||||
log_frame.fill_color = mcrfpy.Color(30, 30, 40, 220)
|
||||
log_frame.outline = 1
|
||||
log_frame.outline_color = mcrfpy.Color(60, 60, 80)
|
||||
scene.children.append(log_frame)
|
||||
|
||||
msg1 = mcrfpy.Caption(text="You hit the Orc for 8 damage!", pos=(10, 10))
|
||||
msg1.fill_color = mcrfpy.Color(255, 200, 100)
|
||||
msg1.font_size = 14
|
||||
log_frame.children.append(msg1)
|
||||
|
||||
msg2 = mcrfpy.Caption(text="The Orc hits you for 4 damage!", pos=(10, 30))
|
||||
msg2.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
msg2.font_size = 14
|
||||
log_frame.children.append(msg2)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "combat_melee.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_dungeon_generator():
|
||||
"""Create a dungeon generator showcase."""
|
||||
scene = mcrfpy.Scene("dungeon_gen")
|
||||
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 80),
|
||||
size=(700, 480),
|
||||
grid_size=(24, 16),
|
||||
texture=texture,
|
||||
zoom=2.0
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(10, 10, 15)
|
||||
scene.children.append(grid)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Dungeon Generator Recipe", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Procedural rooms connected by corridors", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Fill with walls
|
||||
for y in range(16):
|
||||
for x in range(24):
|
||||
grid.at(x, y).tilesprite = TILES['wall_stone']
|
||||
|
||||
# Carve rooms
|
||||
rooms = [
|
||||
(2, 2, 6, 5), # Room 1
|
||||
(10, 2, 7, 5), # Room 2
|
||||
(18, 3, 5, 4), # Room 3
|
||||
(2, 9, 5, 5), # Room 4
|
||||
(10, 10, 6, 5), # Room 5
|
||||
(18, 9, 5, 6), # Room 6
|
||||
]
|
||||
|
||||
for rx, ry, rw, rh in rooms:
|
||||
for y in range(ry, ry + rh):
|
||||
for x in range(rx, rx + rw):
|
||||
if x < 24 and y < 16:
|
||||
grid.at(x, y).tilesprite = TILES['floor_stone']
|
||||
|
||||
# Carve corridors (horizontal and vertical)
|
||||
# Room 1 to Room 2
|
||||
for x in range(7, 11):
|
||||
grid.at(x, 4).tilesprite = 50 # dirt corridor
|
||||
# Room 2 to Room 3
|
||||
for x in range(16, 19):
|
||||
grid.at(x, 4).tilesprite = 50
|
||||
# Room 1 to Room 4
|
||||
for y in range(6, 10):
|
||||
grid.at(4, y).tilesprite = 50
|
||||
# Room 2 to Room 5
|
||||
for y in range(6, 11):
|
||||
grid.at(13, y).tilesprite = 50
|
||||
# Room 3 to Room 6
|
||||
for y in range(6, 10):
|
||||
grid.at(20, y).tilesprite = 50
|
||||
# Room 5 to Room 6
|
||||
for x in range(15, 19):
|
||||
grid.at(x, 12).tilesprite = 50
|
||||
|
||||
# Add player in first room
|
||||
player = mcrfpy.Entity(grid_pos=(4, 4), texture=texture, sprite_index=TILES['player_knight'])
|
||||
grid.entities.append(player)
|
||||
|
||||
# Add decorations
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(3, 3), texture=texture, sprite_index=TILES['torch']))
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(12, 4), texture=texture, sprite_index=TILES['torch']))
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(19, 11), texture=texture, sprite_index=TILES['chest_closed']))
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(13, 12), texture=texture, sprite_index=TILES['enemy_slime']))
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(20, 5), texture=texture, sprite_index=TILES['enemy_skeleton']))
|
||||
|
||||
grid.center = (12 * 16, 8 * 16)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "grid_dungeon_generator.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_floating_text():
|
||||
"""Create a floating text/damage numbers showcase."""
|
||||
scene = mcrfpy.Scene("floating_text")
|
||||
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 100),
|
||||
size=(700, 420),
|
||||
grid_size=(12, 8),
|
||||
texture=texture,
|
||||
zoom=3.5
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(grid)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Floating Text Recipe", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Animated damage numbers and status messages", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Fill floor
|
||||
for y in range(8):
|
||||
for x in range(12):
|
||||
grid.at(x, y).tilesprite = TILES['floor_stone']
|
||||
|
||||
# Walls
|
||||
for x in range(12):
|
||||
grid.at(x, 0).tilesprite = TILES['wall_stone']
|
||||
grid.at(x, 7).tilesprite = TILES['wall_stone']
|
||||
for y in range(8):
|
||||
grid.at(0, y).tilesprite = TILES['wall_stone']
|
||||
grid.at(11, y).tilesprite = TILES['wall_stone']
|
||||
|
||||
# Player and enemy in combat
|
||||
player = mcrfpy.Entity(grid_pos=(4, 4), texture=texture, sprite_index=TILES['player_warrior'])
|
||||
grid.entities.append(player)
|
||||
|
||||
enemy = mcrfpy.Entity(grid_pos=(7, 4), texture=texture, sprite_index=TILES['enemy_orc'])
|
||||
grid.entities.append(enemy)
|
||||
|
||||
grid.center = (5.5 * 16, 4 * 16)
|
||||
|
||||
# Floating damage numbers (as captions positioned over entities)
|
||||
# These would normally animate upward
|
||||
dmg1 = mcrfpy.Caption(text="-12", pos=(330, 240))
|
||||
dmg1.fill_color = mcrfpy.Color(255, 80, 80)
|
||||
dmg1.font_size = 24
|
||||
scene.children.append(dmg1)
|
||||
|
||||
dmg2 = mcrfpy.Caption(text="-5", pos=(500, 260))
|
||||
dmg2.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
dmg2.font_size = 20
|
||||
scene.children.append(dmg2)
|
||||
|
||||
crit = mcrfpy.Caption(text="CRITICAL!", pos=(280, 200))
|
||||
crit.fill_color = mcrfpy.Color(255, 200, 50)
|
||||
crit.font_size = 18
|
||||
scene.children.append(crit)
|
||||
|
||||
heal = mcrfpy.Caption(text="+8", pos=(320, 280))
|
||||
heal.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
heal.font_size = 20
|
||||
scene.children.append(heal)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "effects_floating_text.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_message_log():
|
||||
"""Create a message log showcase."""
|
||||
scene = mcrfpy.Scene("message_log")
|
||||
|
||||
# Dark background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600))
|
||||
bg.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Message Log Recipe", pos=(50, 30))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Scrollable combat and event messages", pos=(50, 60))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Message log frame
|
||||
log_frame = mcrfpy.Frame(pos=(50, 100), size=(700, 400))
|
||||
log_frame.fill_color = mcrfpy.Color(30, 30, 40)
|
||||
log_frame.outline = 2
|
||||
log_frame.outline_color = mcrfpy.Color(60, 60, 80)
|
||||
scene.children.append(log_frame)
|
||||
|
||||
# Sample messages with colors
|
||||
messages = [
|
||||
("Welcome to the dungeon!", mcrfpy.Color(200, 200, 255)),
|
||||
("You see a dark corridor ahead.", mcrfpy.Color(180, 180, 180)),
|
||||
("A goblin appears!", mcrfpy.Color(255, 200, 100)),
|
||||
("You hit the Goblin for 8 damage!", mcrfpy.Color(255, 255, 150)),
|
||||
("The Goblin hits you for 3 damage!", mcrfpy.Color(255, 100, 100)),
|
||||
("You hit the Goblin for 12 damage! Critical hit!", mcrfpy.Color(255, 200, 50)),
|
||||
("The Goblin dies!", mcrfpy.Color(150, 255, 150)),
|
||||
("You found a Healing Potion.", mcrfpy.Color(100, 200, 255)),
|
||||
("An Orc blocks your path!", mcrfpy.Color(255, 150, 100)),
|
||||
("You drink the Healing Potion. +15 HP", mcrfpy.Color(100, 255, 100)),
|
||||
("You hit the Orc for 6 damage!", mcrfpy.Color(255, 255, 150)),
|
||||
("The Orc hits you for 8 damage!", mcrfpy.Color(255, 100, 100)),
|
||||
]
|
||||
|
||||
for i, (msg, color) in enumerate(messages):
|
||||
caption = mcrfpy.Caption(text=msg, pos=(15, 15 + i * 30))
|
||||
caption.fill_color = color
|
||||
caption.font_size = 16
|
||||
log_frame.children.append(caption)
|
||||
|
||||
# Scroll indicator
|
||||
scroll = mcrfpy.Caption(text="▼ More messages below", pos=(580, 370))
|
||||
scroll.fill_color = mcrfpy.Color(100, 100, 120)
|
||||
scroll.font_size = 12
|
||||
log_frame.children.append(scroll)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "ui_message_log.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Generate all cookbook screenshots!"""
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
print("=== Cookbook Screenshot Showcase ===")
|
||||
print(f"Output: {OUTPUT_DIR}\n")
|
||||
|
||||
showcases = [
|
||||
('Health Bar UI', screenshot_health_bar),
|
||||
('Fog of War', screenshot_fog_of_war),
|
||||
('Melee Combat', screenshot_combat_melee),
|
||||
('Dungeon Generator', screenshot_dungeon_generator),
|
||||
('Floating Text', screenshot_floating_text),
|
||||
('Message Log', screenshot_message_log),
|
||||
]
|
||||
|
||||
for name, func in showcases:
|
||||
print(f"Generating {name}...")
|
||||
try:
|
||||
func()
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n=== All cookbook screenshots generated! ===")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
main()
|
||||
255
tests/demo/new_features_showcase.py
Normal file
255
tests/demo/new_features_showcase.py
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
New Features Screenshot Showcase - Alignment + Dijkstra-to-HeightMap
|
||||
|
||||
Generates screenshots for the new API cookbook recipes.
|
||||
Run with: xvfb-run -a ./build/mcrogueface --headless --exec tests/demo/new_features_showcase.py
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
|
||||
OUTPUT_DIR = "/opt/goblincorps/repos/mcrogueface.github.io/images/cookbook"
|
||||
|
||||
|
||||
def screenshot_alignment():
|
||||
"""Create an alignment system showcase."""
|
||||
scene = mcrfpy.Scene("alignment")
|
||||
|
||||
# Dark background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600))
|
||||
bg.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="UI Alignment System", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Auto-positioning with reactive resize", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Demo container
|
||||
container = mcrfpy.Frame(pos=(100, 100), size=(600, 400))
|
||||
container.fill_color = mcrfpy.Color(40, 40, 50)
|
||||
container.outline = 2
|
||||
container.outline_color = mcrfpy.Color(80, 80, 100)
|
||||
scene.children.append(container)
|
||||
|
||||
# Container label
|
||||
container_label = mcrfpy.Caption(text="Parent Container (600x400)", pos=(10, 10))
|
||||
container_label.fill_color = mcrfpy.Color(100, 100, 120)
|
||||
container_label.font_size = 12
|
||||
container.children.append(container_label)
|
||||
|
||||
# 9 alignment positions demo
|
||||
alignments = [
|
||||
(mcrfpy.Alignment.TOP_LEFT, "TL", mcrfpy.Color(200, 80, 80)),
|
||||
(mcrfpy.Alignment.TOP_CENTER, "TC", mcrfpy.Color(200, 150, 80)),
|
||||
(mcrfpy.Alignment.TOP_RIGHT, "TR", mcrfpy.Color(200, 200, 80)),
|
||||
(mcrfpy.Alignment.CENTER_LEFT, "CL", mcrfpy.Color(80, 200, 80)),
|
||||
(mcrfpy.Alignment.CENTER, "C", mcrfpy.Color(80, 200, 200)),
|
||||
(mcrfpy.Alignment.CENTER_RIGHT, "CR", mcrfpy.Color(80, 80, 200)),
|
||||
(mcrfpy.Alignment.BOTTOM_LEFT, "BL", mcrfpy.Color(150, 80, 200)),
|
||||
(mcrfpy.Alignment.BOTTOM_CENTER, "BC", mcrfpy.Color(200, 80, 200)),
|
||||
(mcrfpy.Alignment.BOTTOM_RIGHT, "BR", mcrfpy.Color(200, 80, 150)),
|
||||
]
|
||||
|
||||
for align, label, color in alignments:
|
||||
box = mcrfpy.Frame(pos=(0, 0), size=(60, 40))
|
||||
box.fill_color = color
|
||||
box.outline = 1
|
||||
box.outline_color = mcrfpy.Color(255, 255, 255)
|
||||
box.align = align
|
||||
if align != mcrfpy.Alignment.CENTER:
|
||||
box.margin = 15.0
|
||||
|
||||
# Label inside box
|
||||
text = mcrfpy.Caption(text=label, pos=(0, 0))
|
||||
text.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
text.font_size = 16
|
||||
text.align = mcrfpy.Alignment.CENTER
|
||||
box.children.append(text)
|
||||
|
||||
container.children.append(box)
|
||||
|
||||
# Legend
|
||||
legend = mcrfpy.Caption(text="TL=TOP_LEFT TC=TOP_CENTER TR=TOP_RIGHT etc.", pos=(100, 520))
|
||||
legend.fill_color = mcrfpy.Color(150, 150, 170)
|
||||
legend.font_size = 14
|
||||
scene.children.append(legend)
|
||||
|
||||
legend2 = mcrfpy.Caption(text="All boxes have margin=15 except CENTER", pos=(100, 545))
|
||||
legend2.fill_color = mcrfpy.Color(150, 150, 170)
|
||||
legend2.font_size = 14
|
||||
scene.children.append(legend2)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "ui_alignment.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_dijkstra_heightmap():
|
||||
"""Create a dijkstra-to-heightmap showcase."""
|
||||
scene = mcrfpy.Scene("dijkstra_hmap")
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Dijkstra to HeightMap", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Distance-based gradients for fog, difficulty, and visualization", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Create grid for dijkstra visualization
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 90),
|
||||
size=(350, 350),
|
||||
grid_size=(16, 16),
|
||||
texture=texture,
|
||||
zoom=1.3
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(grid)
|
||||
|
||||
# Initialize grid
|
||||
for y in range(16):
|
||||
for x in range(16):
|
||||
grid.at((x, y)).walkable = True
|
||||
grid.at((x, y)).tilesprite = 42 # floor
|
||||
|
||||
# Add some walls
|
||||
for i in range(5, 11):
|
||||
grid.at((i, 5)).walkable = False
|
||||
grid.at((i, 5)).tilesprite = 30 # wall
|
||||
grid.at((5, i)).walkable = False
|
||||
grid.at((5, i)).tilesprite = 30
|
||||
|
||||
# Player at center
|
||||
player = mcrfpy.Entity(grid_pos=(8, 8), texture=texture, sprite_index=84)
|
||||
grid.entities.append(player)
|
||||
|
||||
# Get dijkstra and create color visualization
|
||||
dijkstra = grid.get_dijkstra_map((8, 8))
|
||||
hmap = dijkstra.to_heightmap(unreachable=-1.0)
|
||||
|
||||
# Find max for normalization
|
||||
max_dist = 0
|
||||
for y in range(16):
|
||||
for x in range(16):
|
||||
d = hmap[(x, y)]
|
||||
if d > max_dist and d >= 0:
|
||||
max_dist = d
|
||||
|
||||
# Second visualization panel - color gradient
|
||||
viz_frame = mcrfpy.Frame(pos=(420, 90), size=(350, 350))
|
||||
viz_frame.fill_color = mcrfpy.Color(30, 30, 40)
|
||||
viz_frame.outline = 2
|
||||
viz_frame.outline_color = mcrfpy.Color(60, 60, 80)
|
||||
scene.children.append(viz_frame)
|
||||
|
||||
viz_label = mcrfpy.Caption(text="Distance Visualization", pos=(80, 10))
|
||||
viz_label.fill_color = mcrfpy.Color(200, 200, 220)
|
||||
viz_label.font_size = 16
|
||||
viz_frame.children.append(viz_label)
|
||||
|
||||
# Draw colored squares for each cell
|
||||
cell_size = 20
|
||||
offset_x = 15
|
||||
offset_y = 35
|
||||
|
||||
for y in range(16):
|
||||
for x in range(16):
|
||||
dist = hmap[(x, y)]
|
||||
|
||||
if dist < 0:
|
||||
# Unreachable - dark red
|
||||
color = mcrfpy.Color(60, 0, 0)
|
||||
elif dist == 0:
|
||||
# Source - bright yellow
|
||||
color = mcrfpy.Color(255, 255, 0)
|
||||
else:
|
||||
# Gradient: green (near) to blue (far)
|
||||
t = min(1.0, dist / max_dist)
|
||||
r = 0
|
||||
g = int(200 * (1 - t))
|
||||
b = int(200 * t)
|
||||
color = mcrfpy.Color(r, g, b)
|
||||
|
||||
cell = mcrfpy.Frame(
|
||||
pos=(offset_x + x * cell_size, offset_y + y * cell_size),
|
||||
size=(cell_size - 1, cell_size - 1)
|
||||
)
|
||||
cell.fill_color = color
|
||||
viz_frame.children.append(cell)
|
||||
|
||||
# Legend
|
||||
legend_frame = mcrfpy.Frame(pos=(50, 460), size=(720, 100))
|
||||
legend_frame.fill_color = mcrfpy.Color(30, 30, 40)
|
||||
legend_frame.outline = 1
|
||||
legend_frame.outline_color = mcrfpy.Color(60, 60, 80)
|
||||
scene.children.append(legend_frame)
|
||||
|
||||
leg1 = mcrfpy.Caption(text="Use Cases:", pos=(15, 10))
|
||||
leg1.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
leg1.font_size = 16
|
||||
legend_frame.children.append(leg1)
|
||||
|
||||
uses = [
|
||||
"Distance-based enemy difficulty",
|
||||
"Fog intensity gradients",
|
||||
"Pathfinding visualization",
|
||||
"Influence maps for AI",
|
||||
]
|
||||
for i, use in enumerate(uses):
|
||||
txt = mcrfpy.Caption(text=f"- {use}", pos=(15 + (i // 2) * 350, 35 + (i % 2) * 25))
|
||||
txt.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
txt.font_size = 14
|
||||
legend_frame.children.append(txt)
|
||||
|
||||
# Color key
|
||||
key_label = mcrfpy.Caption(text="Yellow=Source Green=Near Blue=Far Red=Blocked", pos=(420, 450))
|
||||
key_label.fill_color = mcrfpy.Color(150, 150, 170)
|
||||
key_label.font_size = 12
|
||||
scene.children.append(key_label)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "grid_dijkstra_heightmap.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
print("=== New Features Screenshot Showcase ===")
|
||||
print(f"Output: {OUTPUT_DIR}\n")
|
||||
|
||||
showcases = [
|
||||
('Alignment System', screenshot_alignment),
|
||||
('Dijkstra to HeightMap', screenshot_dijkstra_heightmap),
|
||||
]
|
||||
|
||||
for name, func in showcases:
|
||||
print(f"Generating {name}...")
|
||||
try:
|
||||
func()
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n=== New feature screenshots generated! ===")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
main()
|
||||
286
tests/demo/procgen_showcase.py
Normal file
286
tests/demo/procgen_showcase.py
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate screenshots for procgen cookbook recipes.
|
||||
|
||||
Uses Frame-based visualization since Grid cell colors use ColorLayer API.
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
OUTPUT_DIR = "/opt/goblincorps/repos/mcrogueface.github.io/images/cookbook"
|
||||
|
||||
# Simple PRNG
|
||||
_seed = 42
|
||||
|
||||
def random():
|
||||
global _seed
|
||||
_seed = (_seed * 1103515245 + 12345) & 0x7fffffff
|
||||
return (_seed >> 16) / 32768.0
|
||||
|
||||
def seed(n):
|
||||
global _seed
|
||||
_seed = n
|
||||
|
||||
def choice(lst):
|
||||
return lst[int(random() * len(lst))]
|
||||
|
||||
|
||||
def screenshot_cellular_caves():
|
||||
"""Generate cellular automata caves visualization."""
|
||||
print("Generating cellular automata caves...")
|
||||
|
||||
scene = mcrfpy.Scene("caves")
|
||||
scene.activate()
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
# Background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(640, 500))
|
||||
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||
scene.children.append(bg)
|
||||
|
||||
width, height = 50, 35
|
||||
cell_size = 12
|
||||
seed(42)
|
||||
|
||||
# Store cell data
|
||||
cells = [[False for _ in range(width)] for _ in range(height)]
|
||||
|
||||
# Step 1: Random noise (45% walls)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if x == 0 or x == width-1 or y == 0 or y == height-1:
|
||||
cells[y][x] = True # Border walls
|
||||
else:
|
||||
cells[y][x] = random() < 0.45
|
||||
|
||||
# Step 2: Smooth with cellular automata (5 iterations)
|
||||
for _ in range(5):
|
||||
new_cells = [[cells[y][x] for x in range(width)] for y in range(height)]
|
||||
for y in range(1, height - 1):
|
||||
for x in range(1, width - 1):
|
||||
wall_count = sum(
|
||||
1 for dy in [-1, 0, 1] for dx in [-1, 0, 1]
|
||||
if not (dx == 0 and dy == 0) and cells[y + dy][x + dx]
|
||||
)
|
||||
if wall_count >= 5:
|
||||
new_cells[y][x] = True
|
||||
elif wall_count <= 3:
|
||||
new_cells[y][x] = False
|
||||
cells = new_cells
|
||||
|
||||
# Find largest connected region
|
||||
visited = set()
|
||||
regions = []
|
||||
|
||||
def flood_fill(start_x, start_y):
|
||||
result = []
|
||||
stack = [(start_x, start_y)]
|
||||
while stack:
|
||||
x, y = stack.pop()
|
||||
if (x, y) in visited or x < 0 or x >= width or y < 0 or y >= height:
|
||||
continue
|
||||
if cells[y][x]: # Wall
|
||||
continue
|
||||
visited.add((x, y))
|
||||
result.append((x, y))
|
||||
stack.extend([(x+1, y), (x-1, y), (x, y+1), (x, y-1)])
|
||||
return result
|
||||
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if (x, y) not in visited and not cells[y][x]:
|
||||
region = flood_fill(x, y)
|
||||
if region:
|
||||
regions.append(region)
|
||||
|
||||
largest = max(regions, key=len) if regions else []
|
||||
largest_set = set(largest)
|
||||
|
||||
# Draw cells as colored frames
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
px = 20 + x * cell_size
|
||||
py = 20 + y * cell_size
|
||||
cell = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
||||
|
||||
if cells[y][x]:
|
||||
cell.fill_color = mcrfpy.Color(60, 40, 30) # Wall
|
||||
elif (x, y) in largest_set:
|
||||
cell.fill_color = mcrfpy.Color(50, 90, 100) # Main cave
|
||||
else:
|
||||
cell.fill_color = mcrfpy.Color(45, 35, 30) # Filled region
|
||||
|
||||
scene.children.append(cell)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Cellular Automata Caves", pos=(20, 445))
|
||||
title.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
title.font_size = 18
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="45% fill, 5 iterations, largest region preserved", pos=(20, 468))
|
||||
subtitle.fill_color = mcrfpy.Color(130, 130, 140)
|
||||
subtitle.font_size = 12
|
||||
scene.children.append(subtitle)
|
||||
|
||||
mcrfpy.step(0.1)
|
||||
automation.screenshot(OUTPUT_DIR + "/procgen_cellular_caves.png")
|
||||
print("Saved: procgen_cellular_caves.png")
|
||||
|
||||
|
||||
def screenshot_wfc():
|
||||
"""Generate WFC pattern visualization."""
|
||||
print("Generating WFC patterns...")
|
||||
|
||||
scene = mcrfpy.Scene("wfc")
|
||||
scene.activate()
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
# Background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(640, 500))
|
||||
bg.fill_color = mcrfpy.Color(15, 20, 15)
|
||||
scene.children.append(bg)
|
||||
|
||||
width, height = 40, 28
|
||||
cell_size = 15
|
||||
seed(123)
|
||||
|
||||
GRASS, DIRT, WATER, SAND = 0, 1, 2, 3
|
||||
colors = {
|
||||
GRASS: mcrfpy.Color(60, 120, 50),
|
||||
DIRT: mcrfpy.Color(100, 70, 40),
|
||||
WATER: mcrfpy.Color(40, 80, 140),
|
||||
SAND: mcrfpy.Color(180, 160, 90)
|
||||
}
|
||||
|
||||
rules = {
|
||||
GRASS: {'N': [GRASS, DIRT, SAND], 'S': [GRASS, DIRT, SAND],
|
||||
'E': [GRASS, DIRT, SAND], 'W': [GRASS, DIRT, SAND]},
|
||||
DIRT: {'N': [GRASS, DIRT], 'S': [GRASS, DIRT],
|
||||
'E': [GRASS, DIRT], 'W': [GRASS, DIRT]},
|
||||
WATER: {'N': [WATER, SAND], 'S': [WATER, SAND],
|
||||
'E': [WATER, SAND], 'W': [WATER, SAND]},
|
||||
SAND: {'N': [GRASS, WATER, SAND], 'S': [GRASS, WATER, SAND],
|
||||
'E': [GRASS, WATER, SAND], 'W': [GRASS, WATER, SAND]}
|
||||
}
|
||||
|
||||
tiles = set(rules.keys())
|
||||
possibilities = {(x, y): set(tiles) for y in range(height) for x in range(width)}
|
||||
result = {}
|
||||
|
||||
# Seed water lake
|
||||
for x in range(22, 32):
|
||||
for y in range(8, 18):
|
||||
possibilities[(x, y)] = {WATER}
|
||||
result[(x, y)] = WATER
|
||||
|
||||
# Seed dirt path
|
||||
for y in range(10, 18):
|
||||
possibilities[(3, y)] = {DIRT}
|
||||
result[(3, y)] = DIRT
|
||||
|
||||
directions = {'N': (0, -1), 'S': (0, 1), 'E': (1, 0), 'W': (-1, 0)}
|
||||
|
||||
def propagate(sx, sy):
|
||||
stack = [(sx, sy)]
|
||||
while stack:
|
||||
x, y = stack.pop()
|
||||
current = possibilities[(x, y)]
|
||||
for dir_name, (dx, dy) in directions.items():
|
||||
nx, ny = x + dx, y + dy
|
||||
if not (0 <= nx < width and 0 <= ny < height):
|
||||
continue
|
||||
neighbor = possibilities[(nx, ny)]
|
||||
if len(neighbor) == 1:
|
||||
continue
|
||||
allowed = set()
|
||||
for tile in current:
|
||||
if dir_name in rules[tile]:
|
||||
allowed.update(rules[tile][dir_name])
|
||||
new_opts = neighbor & allowed
|
||||
if new_opts and new_opts != neighbor:
|
||||
possibilities[(nx, ny)] = new_opts
|
||||
stack.append((nx, ny))
|
||||
|
||||
# Propagate from seeds
|
||||
for x in range(22, 32):
|
||||
for y in range(8, 18):
|
||||
propagate(x, y)
|
||||
for y in range(10, 18):
|
||||
propagate(3, y)
|
||||
|
||||
# Collapse
|
||||
for _ in range(width * height):
|
||||
best, best_e = None, 1000.0
|
||||
for pos, opts in possibilities.items():
|
||||
if len(opts) > 1:
|
||||
e = len(opts) + random() * 0.1
|
||||
if e < best_e:
|
||||
best_e, best = e, pos
|
||||
|
||||
if best is None:
|
||||
break
|
||||
|
||||
x, y = best
|
||||
opts = list(possibilities[(x, y)])
|
||||
if not opts:
|
||||
break
|
||||
|
||||
weights = {GRASS: 5, DIRT: 2, WATER: 1, SAND: 2}
|
||||
weighted = []
|
||||
for t in opts:
|
||||
weighted.extend([t] * weights.get(t, 1))
|
||||
chosen = choice(weighted) if weighted else GRASS
|
||||
|
||||
possibilities[(x, y)] = {chosen}
|
||||
result[(x, y)] = chosen
|
||||
propagate(x, y)
|
||||
|
||||
# Fill remaining
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if (x, y) not in result:
|
||||
opts = list(possibilities[(x, y)])
|
||||
result[(x, y)] = choice(opts) if opts else GRASS
|
||||
|
||||
# Draw
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
px = 20 + x * cell_size
|
||||
py = 20 + y * cell_size
|
||||
cell = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
||||
cell.fill_color = colors[result[(x, y)]]
|
||||
scene.children.append(cell)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Wave Function Collapse", pos=(20, 445))
|
||||
title.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
title.font_size = 18
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Constraint-based terrain (seeded lake + path)", pos=(20, 468))
|
||||
subtitle.fill_color = mcrfpy.Color(130, 140, 130)
|
||||
subtitle.font_size = 12
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Legend
|
||||
for i, (name, tid) in enumerate([("Grass", GRASS), ("Dirt", DIRT), ("Sand", SAND), ("Water", WATER)]):
|
||||
lx, ly = 480, 445 + i * 14
|
||||
swatch = mcrfpy.Frame(pos=(lx, ly), size=(12, 12))
|
||||
swatch.fill_color = colors[tid]
|
||||
scene.children.append(swatch)
|
||||
label = mcrfpy.Caption(text=name, pos=(lx + 16, ly))
|
||||
label.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
label.font_size = 11
|
||||
scene.children.append(label)
|
||||
|
||||
mcrfpy.step(0.1)
|
||||
automation.screenshot(OUTPUT_DIR + "/procgen_wfc.png")
|
||||
print("Saved: procgen_wfc.png")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
screenshot_cellular_caves()
|
||||
screenshot_wfc()
|
||||
print("\nDone!")
|
||||
sys.exit(0)
|
||||
103
tests/demo/simple_showcase.py
Normal file
103
tests/demo/simple_showcase.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Tutorial Screenshot Generator
|
||||
|
||||
This creates ONE screenshot - the part01 tutorial showcase.
|
||||
Run with: xvfb-run -a ./build/mcrogueface --headless --exec tests/demo/simple_showcase.py
|
||||
|
||||
NOTE: In headless mode, automation.screenshot() is SYNCHRONOUS - it renders
|
||||
and captures immediately. No timer dance needed!
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Output
|
||||
OUTPUT_PATH = "/opt/goblincorps/repos/mcrogueface.github.io/images/tutorials/part_01_grid_movement.png"
|
||||
|
||||
# Tile sprites from the labeled tileset
|
||||
PLAYER_KNIGHT = 84
|
||||
FLOOR_STONE = 42
|
||||
WALL_STONE = 30
|
||||
TORCH = 72
|
||||
BARREL = 73
|
||||
SKULL = 74
|
||||
|
||||
def main():
|
||||
"""Create the part01 showcase screenshot."""
|
||||
# Ensure output dir exists
|
||||
os.makedirs(os.path.dirname(OUTPUT_PATH), exist_ok=True)
|
||||
|
||||
# Create scene
|
||||
scene = mcrfpy.Scene("showcase")
|
||||
|
||||
# Load texture
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
|
||||
# Create grid - bigger zoom for visibility
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 80),
|
||||
size=(700, 480),
|
||||
grid_size=(12, 9),
|
||||
texture=texture,
|
||||
zoom=3.5
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(grid)
|
||||
|
||||
# Fill with floor
|
||||
for y in range(9):
|
||||
for x in range(12):
|
||||
grid.at(x, y).tilesprite = FLOOR_STONE
|
||||
|
||||
# Add wall border
|
||||
for x in range(12):
|
||||
grid.at(x, 0).tilesprite = WALL_STONE
|
||||
grid.at(x, 0).walkable = False
|
||||
grid.at(x, 8).tilesprite = WALL_STONE
|
||||
grid.at(x, 8).walkable = False
|
||||
for y in range(9):
|
||||
grid.at(0, y).tilesprite = WALL_STONE
|
||||
grid.at(0, y).walkable = False
|
||||
grid.at(11, y).tilesprite = WALL_STONE
|
||||
grid.at(11, y).walkable = False
|
||||
|
||||
# Add player entity - a knight!
|
||||
player = mcrfpy.Entity(
|
||||
grid_pos=(6, 4),
|
||||
texture=texture,
|
||||
sprite_index=PLAYER_KNIGHT
|
||||
)
|
||||
grid.entities.append(player)
|
||||
|
||||
# Add decorations
|
||||
for pos, sprite in [((2, 2), TORCH), ((9, 2), TORCH), ((2, 6), BARREL), ((9, 6), SKULL)]:
|
||||
entity = mcrfpy.Entity(grid_pos=pos, texture=texture, sprite_index=sprite)
|
||||
grid.entities.append(entity)
|
||||
|
||||
# Center camera on player
|
||||
grid.center = (6 * 16 + 8, 4 * 16 + 8)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption(text="Part 1: The '@' and the Dungeon Grid", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Creating a grid, placing entities, handling input", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Activate scene
|
||||
scene.activate()
|
||||
|
||||
# In headless mode, screenshot() is synchronous - renders then captures!
|
||||
result = automation.screenshot(OUTPUT_PATH)
|
||||
print(f"Screenshot saved: {OUTPUT_PATH} (result: {result})")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# Run it
|
||||
main()
|
||||
169
tests/demo/tutorial_screenshots.py
Normal file
169
tests/demo/tutorial_screenshots.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tutorial Screenshot Generator
|
||||
|
||||
Usage:
|
||||
./mcrogueface --headless --exec tests/demo/tutorial_screenshots.py
|
||||
|
||||
Extracts code from tutorial markdown files and generates screenshots.
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
# Paths
|
||||
DOCS_REPO = "/opt/goblincorps/repos/mcrogueface.github.io"
|
||||
TUTORIAL_DIR = os.path.join(DOCS_REPO, "tutorial")
|
||||
OUTPUT_DIR = os.path.join(DOCS_REPO, "images", "tutorials")
|
||||
|
||||
# Tutorials to process (in order)
|
||||
TUTORIALS = [
|
||||
"part_01_grid_movement.md",
|
||||
"part_02_tiles_collision.md",
|
||||
"part_03_dungeon_generation.md",
|
||||
"part_04_fov.md",
|
||||
"part_05_enemies.md",
|
||||
"part_06_combat.md",
|
||||
"part_07_ui.md",
|
||||
]
|
||||
|
||||
|
||||
def extract_code_from_markdown(filepath):
|
||||
"""Extract the main Python code block from a tutorial markdown file."""
|
||||
with open(filepath, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Find code blocks after "## The Complete Code" header
|
||||
# Look for the first python code block after that header
|
||||
complete_code_match = re.search(
|
||||
r'##\s+The Complete Code.*?```python\s*\n(.*?)```',
|
||||
content,
|
||||
re.DOTALL | re.IGNORECASE
|
||||
)
|
||||
|
||||
if complete_code_match:
|
||||
return complete_code_match.group(1)
|
||||
|
||||
# Fallback: just get the first large python code block
|
||||
code_blocks = re.findall(r'```python\s*\n(.*?)```', content, re.DOTALL)
|
||||
if code_blocks:
|
||||
# Return the largest code block (likely the main example)
|
||||
return max(code_blocks, key=len)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def add_screenshot_hook(code, screenshot_path):
|
||||
"""Add screenshot capture code to the end of the script."""
|
||||
# Add code to take screenshot after a brief delay
|
||||
hook_code = f'''
|
||||
|
||||
# === Screenshot capture hook (added by tutorial_screenshots.py) ===
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
_screenshot_taken = [False]
|
||||
|
||||
def _take_screenshot(timer, runtime):
|
||||
if not _screenshot_taken[0]:
|
||||
_screenshot_taken[0] = True
|
||||
automation.screenshot("{screenshot_path}")
|
||||
print(f"Screenshot saved: {screenshot_path}")
|
||||
sys.exit(0)
|
||||
|
||||
# Wait a moment for scene to render, then capture
|
||||
mcrfpy.Timer("_screenshot_hook", _take_screenshot, 200)
|
||||
'''
|
||||
return code + hook_code
|
||||
|
||||
|
||||
class TutorialScreenshotter:
|
||||
"""Manages tutorial screenshot generation."""
|
||||
|
||||
def __init__(self):
|
||||
self.tutorials = []
|
||||
self.current_index = 0
|
||||
|
||||
def load_tutorials(self):
|
||||
"""Load and parse all tutorial files."""
|
||||
for filename in TUTORIALS:
|
||||
filepath = os.path.join(TUTORIAL_DIR, filename)
|
||||
if not os.path.exists(filepath):
|
||||
print(f"Warning: {filepath} not found, skipping")
|
||||
continue
|
||||
|
||||
code = extract_code_from_markdown(filepath)
|
||||
if code:
|
||||
# Generate output filename
|
||||
base = os.path.splitext(filename)[0]
|
||||
screenshot_name = f"{base}.png"
|
||||
self.tutorials.append({
|
||||
'name': filename,
|
||||
'code': code,
|
||||
'screenshot': screenshot_name,
|
||||
'filepath': filepath,
|
||||
})
|
||||
print(f"Loaded: {filename}")
|
||||
else:
|
||||
print(f"Warning: No code found in {filename}")
|
||||
|
||||
def run(self):
|
||||
"""Generate all screenshots."""
|
||||
# Ensure output directory exists
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
print(f"\nGenerating {len(self.tutorials)} tutorial screenshots...")
|
||||
print(f"Output directory: {OUTPUT_DIR}\n")
|
||||
|
||||
self.process_next()
|
||||
|
||||
def process_next(self):
|
||||
"""Process the next tutorial."""
|
||||
if self.current_index >= len(self.tutorials):
|
||||
print("\nAll screenshots generated!")
|
||||
sys.exit(0)
|
||||
return
|
||||
|
||||
tutorial = self.tutorials[self.current_index]
|
||||
print(f"[{self.current_index + 1}/{len(self.tutorials)}] Processing {tutorial['name']}...")
|
||||
|
||||
# Add screenshot hook to the code
|
||||
screenshot_path = os.path.join(OUTPUT_DIR, tutorial['screenshot'])
|
||||
modified_code = add_screenshot_hook(tutorial['code'], screenshot_path)
|
||||
|
||||
# Write to temp file and execute
|
||||
temp_path = f"/tmp/tutorial_screenshot_{self.current_index}.py"
|
||||
with open(temp_path, 'w') as f:
|
||||
f.write(modified_code)
|
||||
|
||||
try:
|
||||
# Execute the code
|
||||
exec(compile(modified_code, temp_path, 'exec'), {'__name__': '__main__'})
|
||||
except Exception as e:
|
||||
print(f"Error processing {tutorial['name']}: {e}")
|
||||
self.current_index += 1
|
||||
self.process_next()
|
||||
finally:
|
||||
try:
|
||||
os.unlink(temp_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
screenshotter = TutorialScreenshotter()
|
||||
screenshotter.load_tutorials()
|
||||
|
||||
if not screenshotter.tutorials:
|
||||
print("No tutorials found to process!")
|
||||
sys.exit(1)
|
||||
|
||||
screenshotter.run()
|
||||
|
||||
|
||||
# Run when executed
|
||||
main()
|
||||
426
tests/demo/tutorial_showcase.py
Normal file
426
tests/demo/tutorial_showcase.py
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tutorial Screenshot Showcase - ALL THE SCREENSHOTS!
|
||||
|
||||
Generates beautiful screenshots for all tutorial parts.
|
||||
Run with: xvfb-run -a ./build/mcrogueface --headless --exec tests/demo/tutorial_showcase.py
|
||||
|
||||
In headless mode, automation.screenshot() is SYNCHRONOUS - no timer dance needed!
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Output directory
|
||||
OUTPUT_DIR = "/opt/goblincorps/repos/mcrogueface.github.io/images/tutorials"
|
||||
|
||||
# Tile meanings from the labeled tileset - the FUN sprites!
|
||||
TILES = {
|
||||
# Players - knights and heroes!
|
||||
'player_knight': 84,
|
||||
'player_mage': 85,
|
||||
'player_rogue': 86,
|
||||
'player_warrior': 87,
|
||||
'player_archer': 88,
|
||||
'player_alt1': 96,
|
||||
'player_alt2': 97,
|
||||
'player_alt3': 98,
|
||||
|
||||
# Enemies - scary!
|
||||
'enemy_slime': 108,
|
||||
'enemy_bat': 109,
|
||||
'enemy_spider': 110,
|
||||
'enemy_rat': 111,
|
||||
'enemy_orc': 120,
|
||||
'enemy_troll': 121,
|
||||
'enemy_ghost': 122,
|
||||
'enemy_skeleton': 123,
|
||||
'enemy_demon': 124,
|
||||
'enemy_boss': 92,
|
||||
|
||||
# Terrain
|
||||
'floor_stone': 42,
|
||||
'floor_wood': 49,
|
||||
'floor_grass': 48,
|
||||
'floor_dirt': 50,
|
||||
'wall_stone': 30,
|
||||
'wall_brick': 14,
|
||||
'wall_mossy': 28,
|
||||
|
||||
# Items
|
||||
'item_potion': 113,
|
||||
'item_scroll': 114,
|
||||
'item_key': 115,
|
||||
'item_coin': 116,
|
||||
|
||||
# Equipment
|
||||
'equip_sword': 101,
|
||||
'equip_shield': 102,
|
||||
'equip_helm': 103,
|
||||
'equip_armor': 104,
|
||||
|
||||
# Chests and doors
|
||||
'chest_closed': 89,
|
||||
'chest_open': 90,
|
||||
'door_closed': 33,
|
||||
'door_open': 35,
|
||||
|
||||
# Decorations
|
||||
'torch': 72,
|
||||
'barrel': 73,
|
||||
'skull': 74,
|
||||
'bones': 75,
|
||||
}
|
||||
|
||||
|
||||
class TutorialShowcase:
|
||||
"""Creates beautiful showcase screenshots for tutorials."""
|
||||
|
||||
def __init__(self, scene_name, output_name):
|
||||
self.scene = mcrfpy.Scene(scene_name)
|
||||
self.output_path = os.path.join(OUTPUT_DIR, output_name)
|
||||
self.grid = None
|
||||
|
||||
def setup_grid(self, width, height, zoom=3.0):
|
||||
"""Create a grid with nice defaults."""
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
self.grid = mcrfpy.Grid(
|
||||
pos=(50, 80),
|
||||
size=(700, 500),
|
||||
grid_size=(width, height),
|
||||
texture=texture,
|
||||
zoom=zoom
|
||||
)
|
||||
self.grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
self.scene.children.append(self.grid)
|
||||
self.texture = texture
|
||||
return self.grid
|
||||
|
||||
def add_title(self, text, subtitle=None):
|
||||
"""Add a title to the scene."""
|
||||
title = mcrfpy.Caption(text=text, pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
self.scene.children.append(title)
|
||||
|
||||
if subtitle:
|
||||
sub = mcrfpy.Caption(text=subtitle, pos=(50, 50))
|
||||
sub.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
sub.font_size = 16
|
||||
self.scene.children.append(sub)
|
||||
|
||||
def fill_floor(self, tile=None):
|
||||
"""Fill grid with floor tiles."""
|
||||
if tile is None:
|
||||
tile = TILES['floor_stone']
|
||||
w, h = int(self.grid.grid_size[0]), int(self.grid.grid_size[1])
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
self.grid.at(x, y).tilesprite = tile
|
||||
|
||||
def add_walls(self, tile=None):
|
||||
"""Add wall border."""
|
||||
if tile is None:
|
||||
tile = TILES['wall_stone']
|
||||
w, h = int(self.grid.grid_size[0]), int(self.grid.grid_size[1])
|
||||
for x in range(w):
|
||||
self.grid.at(x, 0).tilesprite = tile
|
||||
self.grid.at(x, 0).walkable = False
|
||||
self.grid.at(x, h-1).tilesprite = tile
|
||||
self.grid.at(x, h-1).walkable = False
|
||||
for y in range(h):
|
||||
self.grid.at(0, y).tilesprite = tile
|
||||
self.grid.at(0, y).walkable = False
|
||||
self.grid.at(w-1, y).tilesprite = tile
|
||||
self.grid.at(w-1, y).walkable = False
|
||||
|
||||
def add_entity(self, x, y, sprite):
|
||||
"""Add an entity to the grid."""
|
||||
entity = mcrfpy.Entity(
|
||||
grid_pos=(x, y),
|
||||
texture=self.texture,
|
||||
sprite_index=sprite
|
||||
)
|
||||
self.grid.entities.append(entity)
|
||||
return entity
|
||||
|
||||
def center_on(self, x, y):
|
||||
"""Center camera on a position."""
|
||||
self.grid.center = (x * 16 + 8, y * 16 + 8)
|
||||
|
||||
def screenshot(self):
|
||||
"""Take the screenshot - synchronous in headless mode!"""
|
||||
self.scene.activate()
|
||||
result = automation.screenshot(self.output_path)
|
||||
print(f" -> {self.output_path} (result: {result})")
|
||||
return result
|
||||
|
||||
|
||||
def part01_grid_movement():
|
||||
"""Part 1: Grid Movement - Knight in a dungeon room."""
|
||||
showcase = TutorialShowcase("part01", "part_01_grid_movement.png")
|
||||
showcase.setup_grid(12, 9, zoom=3.5)
|
||||
showcase.add_title("Part 1: The '@' and the Dungeon Grid",
|
||||
"Creating a grid, placing entities, handling input")
|
||||
|
||||
showcase.fill_floor(TILES['floor_stone'])
|
||||
showcase.add_walls(TILES['wall_stone'])
|
||||
|
||||
# Add the player (a cool knight, not boring @)
|
||||
showcase.add_entity(6, 4, TILES['player_knight'])
|
||||
|
||||
# Add some decorations to make it interesting
|
||||
showcase.add_entity(2, 2, TILES['torch'])
|
||||
showcase.add_entity(9, 2, TILES['torch'])
|
||||
showcase.add_entity(2, 6, TILES['barrel'])
|
||||
showcase.add_entity(9, 6, TILES['skull'])
|
||||
|
||||
showcase.center_on(6, 4)
|
||||
showcase.screenshot()
|
||||
|
||||
|
||||
def part02_tiles_collision():
|
||||
"""Part 2: Tiles and Collision - Walls and walkability."""
|
||||
showcase = TutorialShowcase("part02", "part_02_tiles_collision.png")
|
||||
showcase.setup_grid(14, 10, zoom=3.0)
|
||||
showcase.add_title("Part 2: Tiles, Collision, and Walkability",
|
||||
"Different tile types and blocking movement")
|
||||
|
||||
showcase.fill_floor(TILES['floor_stone'])
|
||||
showcase.add_walls(TILES['wall_brick'])
|
||||
|
||||
# Create some interior walls to show collision
|
||||
for y in range(2, 5):
|
||||
showcase.grid.at(5, y).tilesprite = TILES['wall_stone']
|
||||
showcase.grid.at(5, y).walkable = False
|
||||
for y in range(5, 8):
|
||||
showcase.grid.at(9, y).tilesprite = TILES['wall_stone']
|
||||
showcase.grid.at(9, y).walkable = False
|
||||
|
||||
# Add a door
|
||||
showcase.grid.at(5, 5).tilesprite = TILES['door_closed']
|
||||
showcase.grid.at(5, 5).walkable = False
|
||||
|
||||
# Player navigating the maze
|
||||
showcase.add_entity(3, 4, TILES['player_warrior'])
|
||||
|
||||
# Chest as goal
|
||||
showcase.add_entity(11, 5, TILES['chest_closed'])
|
||||
|
||||
showcase.center_on(7, 5)
|
||||
showcase.screenshot()
|
||||
|
||||
|
||||
def part03_dungeon_generation():
|
||||
"""Part 3: Dungeon Generation - Procedural rooms and corridors."""
|
||||
showcase = TutorialShowcase("part03", "part_03_dungeon_generation.png")
|
||||
showcase.setup_grid(20, 14, zoom=2.5)
|
||||
showcase.add_title("Part 3: Procedural Dungeon Generation",
|
||||
"Random rooms connected by corridors")
|
||||
|
||||
# Fill with walls first
|
||||
for y in range(14):
|
||||
for x in range(20):
|
||||
showcase.grid.at(x, y).tilesprite = TILES['wall_stone']
|
||||
showcase.grid.at(x, y).walkable = False
|
||||
|
||||
# Carve out two rooms
|
||||
# Room 1 (left)
|
||||
for y in range(3, 8):
|
||||
for x in range(2, 8):
|
||||
showcase.grid.at(x, y).tilesprite = TILES['floor_stone']
|
||||
showcase.grid.at(x, y).walkable = True
|
||||
|
||||
# Room 2 (right)
|
||||
for y in range(6, 12):
|
||||
for x in range(12, 18):
|
||||
showcase.grid.at(x, y).tilesprite = TILES['floor_stone']
|
||||
showcase.grid.at(x, y).walkable = True
|
||||
|
||||
# Corridor connecting them
|
||||
for x in range(7, 13):
|
||||
showcase.grid.at(x, 6).tilesprite = TILES['floor_dirt']
|
||||
showcase.grid.at(x, 6).walkable = True
|
||||
for y in range(6, 9):
|
||||
showcase.grid.at(12, y).tilesprite = TILES['floor_dirt']
|
||||
showcase.grid.at(12, y).walkable = True
|
||||
|
||||
# Player in first room
|
||||
showcase.add_entity(4, 5, TILES['player_knight'])
|
||||
|
||||
# Some loot in second room
|
||||
showcase.add_entity(14, 9, TILES['chest_closed'])
|
||||
showcase.add_entity(16, 8, TILES['item_potion'])
|
||||
|
||||
# Torches
|
||||
showcase.add_entity(3, 3, TILES['torch'])
|
||||
showcase.add_entity(6, 3, TILES['torch'])
|
||||
showcase.add_entity(13, 7, TILES['torch'])
|
||||
|
||||
showcase.center_on(10, 7)
|
||||
showcase.screenshot()
|
||||
|
||||
|
||||
def part04_fov():
|
||||
"""Part 4: Field of View - Showing explored vs visible areas."""
|
||||
showcase = TutorialShowcase("part04", "part_04_fov.png")
|
||||
showcase.setup_grid(16, 12, zoom=2.8)
|
||||
showcase.add_title("Part 4: Field of View and Fog of War",
|
||||
"What the player can see vs. the unknown")
|
||||
|
||||
showcase.fill_floor(TILES['floor_stone'])
|
||||
showcase.add_walls(TILES['wall_brick'])
|
||||
|
||||
# Some interior pillars to block sight
|
||||
for pos in [(5, 4), (5, 7), (10, 5), (10, 8)]:
|
||||
showcase.grid.at(pos[0], pos[1]).tilesprite = TILES['wall_mossy']
|
||||
showcase.grid.at(pos[0], pos[1]).walkable = False
|
||||
|
||||
# Player with "light"
|
||||
showcase.add_entity(8, 6, TILES['player_mage'])
|
||||
|
||||
# Hidden enemy (player wouldn't see this!)
|
||||
showcase.add_entity(12, 3, TILES['enemy_ghost'])
|
||||
|
||||
# Visible enemies
|
||||
showcase.add_entity(3, 5, TILES['enemy_bat'])
|
||||
showcase.add_entity(6, 8, TILES['enemy_spider'])
|
||||
|
||||
showcase.center_on(8, 6)
|
||||
showcase.screenshot()
|
||||
|
||||
|
||||
def part05_enemies():
|
||||
"""Part 5: Enemies - A dungeon full of monsters."""
|
||||
showcase = TutorialShowcase("part05", "part_05_enemies.png")
|
||||
showcase.setup_grid(18, 12, zoom=2.5)
|
||||
showcase.add_title("Part 5: Adding Enemies",
|
||||
"Different monster types with AI behavior")
|
||||
|
||||
showcase.fill_floor(TILES['floor_stone'])
|
||||
showcase.add_walls(TILES['wall_stone'])
|
||||
|
||||
# The hero
|
||||
showcase.add_entity(3, 5, TILES['player_warrior'])
|
||||
|
||||
# A variety of enemies
|
||||
showcase.add_entity(7, 3, TILES['enemy_slime'])
|
||||
showcase.add_entity(10, 6, TILES['enemy_bat'])
|
||||
showcase.add_entity(8, 8, TILES['enemy_spider'])
|
||||
showcase.add_entity(14, 4, TILES['enemy_orc'])
|
||||
showcase.add_entity(15, 8, TILES['enemy_skeleton'])
|
||||
showcase.add_entity(12, 5, TILES['enemy_rat'])
|
||||
|
||||
# Boss at the end
|
||||
showcase.add_entity(15, 6, TILES['enemy_boss'])
|
||||
|
||||
# Some decorations
|
||||
showcase.add_entity(5, 2, TILES['bones'])
|
||||
showcase.add_entity(13, 9, TILES['skull'])
|
||||
showcase.add_entity(2, 8, TILES['torch'])
|
||||
showcase.add_entity(16, 2, TILES['torch'])
|
||||
|
||||
showcase.center_on(9, 5)
|
||||
showcase.screenshot()
|
||||
|
||||
|
||||
def part06_combat():
|
||||
"""Part 6: Combat - Battle in progress!"""
|
||||
showcase = TutorialShowcase("part06", "part_06_combat.png")
|
||||
showcase.setup_grid(14, 10, zoom=3.0)
|
||||
showcase.add_title("Part 6: Combat System",
|
||||
"HP, attack, defense, and turn-based fighting")
|
||||
|
||||
showcase.fill_floor(TILES['floor_dirt'])
|
||||
showcase.add_walls(TILES['wall_brick'])
|
||||
|
||||
# Battle scene - player vs enemy
|
||||
showcase.add_entity(5, 5, TILES['player_knight'])
|
||||
showcase.add_entity(8, 5, TILES['enemy_orc'])
|
||||
|
||||
# Fallen enemies (show combat has happened)
|
||||
showcase.add_entity(4, 3, TILES['bones'])
|
||||
showcase.add_entity(9, 7, TILES['skull'])
|
||||
|
||||
# Equipment the player has
|
||||
showcase.add_entity(3, 6, TILES['equip_shield'])
|
||||
showcase.add_entity(10, 4, TILES['item_potion'])
|
||||
|
||||
showcase.center_on(6, 5)
|
||||
showcase.screenshot()
|
||||
|
||||
|
||||
def part07_ui():
|
||||
"""Part 7: User Interface - Health bars and menus."""
|
||||
showcase = TutorialShowcase("part07", "part_07_ui.png")
|
||||
showcase.setup_grid(12, 8, zoom=3.0)
|
||||
showcase.add_title("Part 7: User Interface",
|
||||
"Health bars, message logs, and menus")
|
||||
|
||||
showcase.fill_floor(TILES['floor_wood'])
|
||||
showcase.add_walls(TILES['wall_brick'])
|
||||
|
||||
# Player
|
||||
showcase.add_entity(6, 4, TILES['player_rogue'])
|
||||
|
||||
# Some items to interact with
|
||||
showcase.add_entity(4, 3, TILES['chest_open'])
|
||||
showcase.add_entity(8, 5, TILES['item_coin'])
|
||||
|
||||
# Add UI overlay example - health bar frame
|
||||
ui_frame = mcrfpy.Frame(pos=(50, 520), size=(200, 40))
|
||||
ui_frame.fill_color = mcrfpy.Color(40, 40, 50, 200)
|
||||
ui_frame.outline = 2
|
||||
ui_frame.outline_color = mcrfpy.Color(80, 80, 100)
|
||||
showcase.scene.children.append(ui_frame)
|
||||
|
||||
# Health label
|
||||
hp_label = mcrfpy.Caption(text="HP: 45/50", pos=(10, 10))
|
||||
hp_label.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
hp_label.font_size = 18
|
||||
ui_frame.children.append(hp_label)
|
||||
|
||||
# Health bar background
|
||||
hp_bg = mcrfpy.Frame(pos=(90, 12), size=(100, 16))
|
||||
hp_bg.fill_color = mcrfpy.Color(60, 20, 20)
|
||||
ui_frame.children.append(hp_bg)
|
||||
|
||||
# Health bar fill
|
||||
hp_fill = mcrfpy.Frame(pos=(90, 12), size=(90, 16)) # 90% health
|
||||
hp_fill.fill_color = mcrfpy.Color(200, 50, 50)
|
||||
ui_frame.children.append(hp_fill)
|
||||
|
||||
showcase.center_on(6, 4)
|
||||
showcase.screenshot()
|
||||
|
||||
|
||||
def main():
|
||||
"""Generate all showcase screenshots!"""
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
print("=== Tutorial Screenshot Showcase ===")
|
||||
print(f"Output: {OUTPUT_DIR}\n")
|
||||
|
||||
showcases = [
|
||||
('Part 1: Grid Movement', part01_grid_movement),
|
||||
('Part 2: Tiles & Collision', part02_tiles_collision),
|
||||
('Part 3: Dungeon Generation', part03_dungeon_generation),
|
||||
('Part 4: Field of View', part04_fov),
|
||||
('Part 5: Enemies', part05_enemies),
|
||||
('Part 6: Combat', part06_combat),
|
||||
('Part 7: UI', part07_ui),
|
||||
]
|
||||
|
||||
for name, func in showcases:
|
||||
print(f"Generating {name}...")
|
||||
try:
|
||||
func()
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
|
||||
print("\n=== All screenshots generated! ===")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue