diff --git a/docs/cookbook/combat/combat_animated_movement_basic.py b/docs/cookbook/combat/combat_animated_movement_basic.py deleted file mode 100644 index a1e9b05..0000000 --- a/docs/cookbook/combat/combat_animated_movement_basic.py +++ /dev/null @@ -1,13 +0,0 @@ -"""McRogueFace - Animated Movement (basic) - -Documentation: https://mcrogueface.github.io/cookbook/combat_animated_movement -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_animated_movement_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -if new_x != current_x: - anim = mcrfpy.Animation("x", float(new_x), duration, "easeInOut", callback=done) - else: - anim = mcrfpy.Animation("y", float(new_y), duration, "easeInOut", callback=done) \ No newline at end of file diff --git a/docs/cookbook/combat/combat_animated_movement_basic_2.py b/docs/cookbook/combat/combat_animated_movement_basic_2.py deleted file mode 100644 index 68fba47..0000000 --- a/docs/cookbook/combat/combat_animated_movement_basic_2.py +++ /dev/null @@ -1,12 +0,0 @@ -"""McRogueFace - Animated Movement (basic_2) - -Documentation: https://mcrogueface.github.io/cookbook/combat_animated_movement -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_animated_movement_basic_2.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -current_anim = mcrfpy.Animation("x", 100.0, 0.5, "linear") - current_anim.start(entity) - # Later: current_anim = None # Let it complete or create new one \ No newline at end of file diff --git a/docs/cookbook/combat/combat_enemy_ai_basic.py b/docs/cookbook/combat/combat_enemy_ai_basic.py deleted file mode 100644 index a462cd3..0000000 --- a/docs/cookbook/combat/combat_enemy_ai_basic.py +++ /dev/null @@ -1,45 +0,0 @@ -"""McRogueFace - Basic Enemy AI (basic) - -Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import random - -def wander(enemy, grid): - """Move randomly to an adjacent walkable tile.""" - ex, ey = int(enemy.x), int(enemy.y) - - # Get valid adjacent tiles - directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] - random.shuffle(directions) - - for dx, dy in directions: - new_x, new_y = ex + dx, ey + dy - - if is_walkable(grid, new_x, new_y) and not is_occupied(new_x, new_y): - enemy.x = new_x - enemy.y = new_y - return - - # No valid moves - stay in place - -def is_walkable(grid, x, y): - """Check if a tile can be walked on.""" - grid_w, grid_h = grid.grid_size - if x < 0 or x >= grid_w or y < 0 or y >= grid_h: - return False - return grid.at(x, y).walkable - -def is_occupied(x, y, entities=None): - """Check if a tile is occupied by another entity.""" - if entities is None: - return False - - for entity in entities: - if int(entity.x) == x and int(entity.y) == y: - return True - return False \ No newline at end of file diff --git a/docs/cookbook/combat/combat_enemy_ai_multi.py b/docs/cookbook/combat/combat_enemy_ai_multi.py deleted file mode 100644 index 9f78b97..0000000 --- a/docs/cookbook/combat/combat_enemy_ai_multi.py +++ /dev/null @@ -1,11 +0,0 @@ -"""McRogueFace - Basic Enemy AI (multi) - -Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_multi.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -# Filter to cardinal directions only - path = [p for p in path if abs(p[0] - ex) + abs(p[1] - ey) == 1] \ No newline at end of file diff --git a/docs/cookbook/combat/combat_enemy_ai_multi_2.py b/docs/cookbook/combat/combat_enemy_ai_multi_2.py deleted file mode 100644 index 205eba6..0000000 --- a/docs/cookbook/combat/combat_enemy_ai_multi_2.py +++ /dev/null @@ -1,14 +0,0 @@ -"""McRogueFace - Basic Enemy AI (multi_2) - -Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_multi_2.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def alert_nearby(x, y, radius, enemies): - for enemy in enemies: - dist = abs(enemy.entity.x - x) + abs(enemy.entity.y - y) - if dist <= radius and hasattr(enemy.ai, 'alert'): - enemy.ai.alert = True \ No newline at end of file diff --git a/docs/cookbook/combat/combat_melee_basic.py b/docs/cookbook/combat/combat_melee_basic.py deleted file mode 100644 index b488d71..0000000 --- a/docs/cookbook/combat/combat_melee_basic.py +++ /dev/null @@ -1,82 +0,0 @@ -"""McRogueFace - Melee Combat System (basic) - -Documentation: https://mcrogueface.github.io/cookbook/combat_melee -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -class CombatLog: - """Scrolling combat message log.""" - - def __init__(self, x, y, width, height, max_messages=10): - self.x = x - self.y = y - self.width = width - self.height = height - self.max_messages = max_messages - self.messages = [] - self.captions = [] - - ui = mcrfpy.sceneUI(mcrfpy.currentScene()) - - # Background - self.frame = mcrfpy.Frame(x, y, width, height) - self.frame.fill_color = mcrfpy.Color(0, 0, 0, 180) - ui.append(self.frame) - - def add_message(self, text, color=None): - """Add a message to the log.""" - if color is None: - color = mcrfpy.Color(200, 200, 200) - - self.messages.append((text, color)) - - # Keep only recent messages - if len(self.messages) > self.max_messages: - self.messages.pop(0) - - self._refresh_display() - - def _refresh_display(self): - """Redraw all messages.""" - ui = mcrfpy.sceneUI(mcrfpy.currentScene()) - - # Remove old captions - for caption in self.captions: - try: - ui.remove(caption) - except: - pass - self.captions.clear() - - # Create new captions - line_height = 18 - for i, (text, color) in enumerate(self.messages): - caption = mcrfpy.Caption(text, self.x + 5, self.y + 5 + i * line_height) - caption.fill_color = color - ui.append(caption) - self.captions.append(caption) - - def log_attack(self, attacker_name, defender_name, damage, killed=False, critical=False): - """Log an attack event.""" - if critical: - text = f"{attacker_name} CRITS {defender_name} for {damage}!" - color = mcrfpy.Color(255, 255, 0) - else: - text = f"{attacker_name} hits {defender_name} for {damage}." - color = mcrfpy.Color(200, 200, 200) - - self.add_message(text, color) - - if killed: - self.add_message(f"{defender_name} is defeated!", mcrfpy.Color(255, 100, 100)) - - -# Global combat log -combat_log = None - -def init_combat_log(): - global combat_log - combat_log = CombatLog(10, 500, 400, 200) \ No newline at end of file diff --git a/docs/cookbook/combat/combat_melee_complete.py b/docs/cookbook/combat/combat_melee_complete.py deleted file mode 100644 index b7f3e87..0000000 --- a/docs/cookbook/combat/combat_melee_complete.py +++ /dev/null @@ -1,15 +0,0 @@ -"""McRogueFace - Melee Combat System (complete) - -Documentation: https://mcrogueface.github.io/cookbook/combat_melee -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_complete.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def die_with_animation(entity): - # Play death animation - anim = mcrfpy.Animation("opacity", 0.0, 0.5, "linear") - anim.start(entity) - # Remove after animation - mcrfpy.setTimer("remove", lambda dt: remove_entity(entity), 500) \ No newline at end of file diff --git a/docs/cookbook/combat/combat_melee_complete_2.py b/docs/cookbook/combat/combat_melee_complete_2.py deleted file mode 100644 index c936d9f..0000000 --- a/docs/cookbook/combat/combat_melee_complete_2.py +++ /dev/null @@ -1,14 +0,0 @@ -"""McRogueFace - Melee Combat System (complete_2) - -Documentation: https://mcrogueface.github.io/cookbook/combat_melee -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_complete_2.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -@dataclass - class AdvancedFighter(Fighter): - fire_resist: float = 0.0 - ice_resist: float = 0.0 - physical_resist: float = 0.0 \ No newline at end of file diff --git a/docs/cookbook/combat/combat_status_effects_basic.py b/docs/cookbook/combat/combat_status_effects_basic.py deleted file mode 100644 index ec87939..0000000 --- a/docs/cookbook/combat/combat_status_effects_basic.py +++ /dev/null @@ -1,56 +0,0 @@ -"""McRogueFace - Status Effects (basic) - -Documentation: https://mcrogueface.github.io/cookbook/combat_status_effects -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_status_effects_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -class StackableEffect(StatusEffect): - """Effect that stacks intensity.""" - - def __init__(self, name, duration, intensity=1, max_stacks=5, **kwargs): - super().__init__(name, duration, **kwargs) - self.intensity = intensity - self.max_stacks = max_stacks - self.stacks = 1 - - def add_stack(self): - """Add another stack.""" - if self.stacks < self.max_stacks: - self.stacks += 1 - return True - return False - - -class StackingEffectManager(EffectManager): - """Effect manager with stacking support.""" - - def add_effect(self, effect): - if isinstance(effect, StackableEffect): - # Check for existing stacks - for existing in self.effects: - if existing.name == effect.name: - if existing.add_stack(): - # Refresh duration - existing.duration = max(existing.duration, effect.duration) - return - else: - return # Max stacks - - # Default behavior - super().add_effect(effect) - - -# Stacking poison example -def create_stacking_poison(base_damage=1, duration=5): - def on_tick(target): - # Find the poison effect to get stack count - effect = target.effects.get_effect("poison") - if effect: - damage = base_damage * effect.stacks - target.hp -= damage - print(f"{target.name} takes {damage} poison damage! ({effect.stacks} stacks)") - - return StackableEffect("poison", duration, on_tick=on_tick, max_stacks=5) \ No newline at end of file diff --git a/docs/cookbook/combat/combat_status_effects_basic_2.py b/docs/cookbook/combat/combat_status_effects_basic_2.py deleted file mode 100644 index 10fe619..0000000 --- a/docs/cookbook/combat/combat_status_effects_basic_2.py +++ /dev/null @@ -1,16 +0,0 @@ -"""McRogueFace - Status Effects (basic_2) - -Documentation: https://mcrogueface.github.io/cookbook/combat_status_effects -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_status_effects_basic_2.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def apply_effect(self, effect): - if effect.name in self.immunities: - print(f"{self.name} is immune to {effect.name}!") - return - if effect.name in self.resistances: - effect.duration //= 2 # Half duration - self.effects.add_effect(effect) \ No newline at end of file diff --git a/docs/cookbook/combat/combat_status_effects_basic_3.py b/docs/cookbook/combat/combat_status_effects_basic_3.py deleted file mode 100644 index 567ea44..0000000 --- a/docs/cookbook/combat/combat_status_effects_basic_3.py +++ /dev/null @@ -1,12 +0,0 @@ -"""McRogueFace - Status Effects (basic_3) - -Documentation: https://mcrogueface.github.io/cookbook/combat_status_effects -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_status_effects_basic_3.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def serialize_effects(effect_manager): - return [{"name": e.name, "duration": e.duration} - for e in effect_manager.effects] \ No newline at end of file diff --git a/docs/cookbook/combat/combat_turn_system.py b/docs/cookbook/combat/combat_turn_system.py deleted file mode 100644 index 61194e5..0000000 --- a/docs/cookbook/combat/combat_turn_system.py +++ /dev/null @@ -1,45 +0,0 @@ -"""McRogueFace - Turn-Based Game Loop (combat_turn_system) - -Documentation: https://mcrogueface.github.io/cookbook/combat_turn_system -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_turn_system.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def create_turn_order_ui(turn_manager, x=800, y=50): - """Create a visual turn order display.""" - ui = mcrfpy.sceneUI(mcrfpy.currentScene()) - - # Background frame - frame = mcrfpy.Frame(x, y, 200, 300) - frame.fill_color = mcrfpy.Color(30, 30, 30, 200) - frame.outline = 2 - frame.outline_color = mcrfpy.Color(100, 100, 100) - ui.append(frame) - - # Title - title = mcrfpy.Caption("Turn Order", x + 10, y + 10) - title.fill_color = mcrfpy.Color(255, 255, 255) - ui.append(title) - - return frame - -def update_turn_order_display(frame, turn_manager, x=800, y=50): - """Update the turn order display.""" - ui = mcrfpy.sceneUI(mcrfpy.currentScene()) - - # Clear old entries (keep frame and title) - # In practice, store references to caption objects and update them - - for i, actor_data in enumerate(turn_manager.actors): - actor = actor_data["actor"] - is_current = (i == turn_manager.current) - - # Actor name/type - name = getattr(actor, 'name', f"Actor {i}") - color = mcrfpy.Color(255, 255, 0) if is_current else mcrfpy.Color(200, 200, 200) - - caption = mcrfpy.Caption(name, x + 10, y + 40 + i * 25) - caption.fill_color = color - ui.append(caption) \ No newline at end of file diff --git a/docs/cookbook/effects/effects_color_pulse_basic.py b/docs/cookbook/effects/effects_color_pulse_basic.py deleted file mode 100644 index 441b852..0000000 --- a/docs/cookbook/effects/effects_color_pulse_basic.py +++ /dev/null @@ -1,118 +0,0 @@ -"""McRogueFace - Color Pulse Effect (basic) - -Documentation: https://mcrogueface.github.io/cookbook/effects_color_pulse -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_color_pulse_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class PulsingCell: - """A cell that continuously pulses until stopped.""" - - def __init__(self, grid, x, y, color, period=1.0, max_alpha=180): - """ - Args: - grid: Grid with color layer - x, y: Cell position - color: RGB tuple - period: Time for one complete pulse cycle - max_alpha: Maximum alpha value (0-255) - """ - self.grid = grid - self.x = x - self.y = y - self.color = color - self.period = period - self.max_alpha = max_alpha - self.is_pulsing = False - self.pulse_id = 0 - self.cell = None - - self._setup_layer() - - def _setup_layer(self): - """Ensure color layer exists and get cell reference.""" - color_layer = None - for layer in self.grid.layers: - if isinstance(layer, mcrfpy.ColorLayer): - color_layer = layer - break - - if not color_layer: - self.grid.add_layer("color") - color_layer = self.grid.layers[-1] - - self.cell = color_layer.at(self.x, self.y) - if self.cell: - self.cell.color = mcrfpy.Color(self.color[0], self.color[1], - self.color[2], 0) - - def start(self): - """Start continuous pulsing.""" - if self.is_pulsing or not self.cell: - return - - self.is_pulsing = True - self.pulse_id += 1 - self._pulse_up() - - def _pulse_up(self): - """Animate alpha increasing.""" - if not self.is_pulsing: - return - - current_id = self.pulse_id - half_period = self.period / 2 - - anim = mcrfpy.Animation("a", float(self.max_alpha), half_period, "easeInOut") - anim.start(self.cell.color) - - def next_phase(timer_name): - if self.is_pulsing and self.pulse_id == current_id: - self._pulse_down() - - mcrfpy.Timer(f"pulse_up_{id(self)}_{current_id}", - next_phase, int(half_period * 1000), once=True) - - def _pulse_down(self): - """Animate alpha decreasing.""" - if not self.is_pulsing: - return - - current_id = self.pulse_id - half_period = self.period / 2 - - anim = mcrfpy.Animation("a", 0.0, half_period, "easeInOut") - anim.start(self.cell.color) - - def next_phase(timer_name): - if self.is_pulsing and self.pulse_id == current_id: - self._pulse_up() - - mcrfpy.Timer(f"pulse_down_{id(self)}_{current_id}", - next_phase, int(half_period * 1000), once=True) - - def stop(self): - """Stop pulsing and fade out.""" - self.is_pulsing = False - if self.cell: - anim = mcrfpy.Animation("a", 0.0, 0.2, "easeOut") - anim.start(self.cell.color) - - def set_color(self, color): - """Change pulse color.""" - self.color = color - if self.cell: - current_alpha = self.cell.color.a - self.cell.color = mcrfpy.Color(color[0], color[1], color[2], current_alpha) - - -# Usage -objective_pulse = PulsingCell(grid, 10, 10, (0, 255, 100), period=1.5) -objective_pulse.start() - -# Later, when objective is reached: -objective_pulse.stop() \ No newline at end of file diff --git a/docs/cookbook/effects/effects_color_pulse_multi.py b/docs/cookbook/effects/effects_color_pulse_multi.py deleted file mode 100644 index e39946c..0000000 --- a/docs/cookbook/effects/effects_color_pulse_multi.py +++ /dev/null @@ -1,61 +0,0 @@ -"""McRogueFace - Color Pulse Effect (multi) - -Documentation: https://mcrogueface.github.io/cookbook/effects_color_pulse -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_color_pulse_multi.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -def ripple_effect(grid, center_x, center_y, color, max_radius=5, duration=1.0): - """ - Create an expanding ripple effect. - - Args: - grid: Grid with color layer - center_x, center_y: Ripple origin - color: RGB tuple - max_radius: Maximum ripple size - duration: Total animation time - """ - # Get color layer - color_layer = None - for layer in grid.layers: - if isinstance(layer, mcrfpy.ColorLayer): - color_layer = layer - break - - if not color_layer: - grid.add_layer("color") - color_layer = grid.layers[-1] - - step_duration = duration / max_radius - - for radius in range(max_radius + 1): - # Get cells at this radius (ring, not filled) - ring_cells = [] - for dy in range(-radius, radius + 1): - for dx in range(-radius, radius + 1): - dist_sq = dx * dx + dy * dy - # Include cells approximately on the ring edge - if radius * radius - radius <= dist_sq <= radius * radius + radius: - cell = color_layer.at(center_x + dx, center_y + dy) - if cell: - ring_cells.append(cell) - - # Schedule this ring to animate - def animate_ring(timer_name, cells=ring_cells, c=color): - for cell in cells: - cell.color = mcrfpy.Color(c[0], c[1], c[2], 200) - # Fade out - anim = mcrfpy.Animation("a", 0.0, step_duration * 2, "easeOut") - anim.start(cell.color) - - delay = int(radius * step_duration * 1000) - mcrfpy.Timer(f"ripple_{radius}", animate_ring, delay, once=True) - - -# Usage -ripple_effect(grid, 10, 10, (100, 200, 255), max_radius=6, duration=0.8) \ No newline at end of file diff --git a/docs/cookbook/effects/effects_damage_flash_basic.py b/docs/cookbook/effects/effects_damage_flash_basic.py deleted file mode 100644 index ad6c42c..0000000 --- a/docs/cookbook/effects/effects_damage_flash_basic.py +++ /dev/null @@ -1,41 +0,0 @@ -"""McRogueFace - Damage Flash Effect (basic) - -Documentation: https://mcrogueface.github.io/cookbook/effects_damage_flash -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_damage_flash_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -# Add a color layer to your grid (do this once during setup) -grid.add_layer("color") -color_layer = grid.layers[-1] # Get the color layer - -def flash_cell(grid, x, y, color, duration=0.3): - """Flash a grid cell with a color overlay.""" - # Get the color layer (assumes it's the last layer added) - color_layer = None - for layer in grid.layers: - if isinstance(layer, mcrfpy.ColorLayer): - color_layer = layer - break - - if not color_layer: - return - - # Set cell to flash color - cell = color_layer.at(x, y) - cell.color = mcrfpy.Color(color[0], color[1], color[2], 200) - - # Animate alpha back to 0 - anim = mcrfpy.Animation("a", 0.0, duration, "easeOut") - anim.start(cell.color) - -def damage_at_position(grid, x, y, duration=0.3): - """Flash red at a grid position when damage occurs.""" - flash_cell(grid, x, y, (255, 0, 0), duration) - -# Usage when entity takes damage -damage_at_position(grid, int(enemy.x), int(enemy.y)) \ No newline at end of file diff --git a/docs/cookbook/effects/effects_damage_flash_complete.py b/docs/cookbook/effects/effects_damage_flash_complete.py deleted file mode 100644 index f123a5e..0000000 --- a/docs/cookbook/effects/effects_damage_flash_complete.py +++ /dev/null @@ -1,85 +0,0 @@ -"""McRogueFace - Damage Flash Effect (complete) - -Documentation: https://mcrogueface.github.io/cookbook/effects_damage_flash -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_damage_flash_complete.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class DamageEffects: - """Manages visual damage feedback effects.""" - - # Color presets - DAMAGE_RED = (255, 50, 50) - HEAL_GREEN = (50, 255, 50) - POISON_PURPLE = (150, 50, 200) - FIRE_ORANGE = (255, 150, 50) - ICE_BLUE = (100, 200, 255) - - def __init__(self, grid): - self.grid = grid - self.color_layer = None - self._setup_color_layer() - - def _setup_color_layer(self): - """Ensure grid has a color layer for effects.""" - self.grid.add_layer("color") - self.color_layer = self.grid.layers[-1] - - def flash_entity(self, entity, color, duration=0.3): - """Flash an entity with a color tint.""" - # Flash at entity's grid position - x, y = int(entity.x), int(entity.y) - self.flash_cell(x, y, color, duration) - - def flash_cell(self, x, y, color, duration=0.3): - """Flash a specific grid cell.""" - if not self.color_layer: - return - - cell = self.color_layer.at(x, y) - if cell: - cell.color = mcrfpy.Color(color[0], color[1], color[2], 180) - - # Fade out - anim = mcrfpy.Animation("a", 0.0, duration, "easeOut") - anim.start(cell.color) - - def damage(self, entity, amount, duration=0.3): - """Standard damage flash.""" - self.flash_entity(entity, self.DAMAGE_RED, duration) - - def heal(self, entity, amount, duration=0.4): - """Healing effect - green flash.""" - self.flash_entity(entity, self.HEAL_GREEN, duration) - - def poison(self, entity, duration=0.5): - """Poison damage - purple flash.""" - self.flash_entity(entity, self.POISON_PURPLE, duration) - - def fire(self, entity, duration=0.3): - """Fire damage - orange flash.""" - self.flash_entity(entity, self.FIRE_ORANGE, duration) - - def ice(self, entity, duration=0.4): - """Ice damage - blue flash.""" - self.flash_entity(entity, self.ICE_BLUE, duration) - - def area_damage(self, center_x, center_y, radius, color, duration=0.4): - """Flash all cells in a radius.""" - for dy in range(-radius, radius + 1): - for dx in range(-radius, radius + 1): - if dx * dx + dy * dy <= radius * radius: - self.flash_cell(center_x + dx, center_y + dy, color, duration) - -# Setup -effects = DamageEffects(grid) - -# Usage examples -effects.damage(player, 10) # Red flash -effects.heal(player, 5) # Green flash -effects.poison(enemy) # Purple flash -effects.area_damage(5, 5, 3, effects.FIRE_ORANGE) # Area effect \ No newline at end of file diff --git a/docs/cookbook/effects/effects_damage_flash_multi.py b/docs/cookbook/effects/effects_damage_flash_multi.py deleted file mode 100644 index cccddc3..0000000 --- a/docs/cookbook/effects/effects_damage_flash_multi.py +++ /dev/null @@ -1,25 +0,0 @@ -"""McRogueFace - Damage Flash Effect (multi) - -Documentation: https://mcrogueface.github.io/cookbook/effects_damage_flash -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_damage_flash_multi.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -def multi_flash(grid, x, y, color, flashes=3, flash_duration=0.1): - """Flash a cell multiple times for emphasis.""" - delay = 0 - - for i in range(flashes): - # Schedule each flash with increasing delay - def do_flash(timer_name, fx=x, fy=y, fc=color, fd=flash_duration): - flash_cell(grid, fx, fy, fc, fd) - - mcrfpy.Timer(f"flash_{x}_{y}_{i}", do_flash, int(delay * 1000), once=True) - delay += flash_duration * 1.5 # Gap between flashes - -# Usage for critical hit -multi_flash(grid, int(enemy.x), int(enemy.y), (255, 255, 0), flashes=3) \ No newline at end of file diff --git a/docs/cookbook/effects/effects_floating_text.py b/docs/cookbook/effects/effects_floating_text.py deleted file mode 100644 index d6ba2a0..0000000 --- a/docs/cookbook/effects/effects_floating_text.py +++ /dev/null @@ -1,42 +0,0 @@ -"""McRogueFace - Floating Damage Numbers (effects_floating_text) - -Documentation: https://mcrogueface.github.io/cookbook/effects_floating_text -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_floating_text.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -class StackedFloatingText: - """Prevents overlapping text by stacking vertically.""" - - def __init__(self, scene_name, grid=None): - self.manager = FloatingTextManager(scene_name, grid) - self.position_stack = {} # Track recent spawns per position - - def spawn_stacked(self, x, y, text, color, **kwargs): - """Spawn with automatic vertical stacking.""" - key = (int(x), int(y)) - - # Calculate offset based on recent spawns at this position - offset = self.position_stack.get(key, 0) - actual_y = y - (offset * 20) # 20 pixels between stacked texts - - self.manager.spawn(x, actual_y, text, color, **kwargs) - - # Increment stack counter - self.position_stack[key] = offset + 1 - - # Reset stack after delay - def reset_stack(timer_name, k=key): - if k in self.position_stack: - self.position_stack[k] = max(0, self.position_stack[k] - 1) - - mcrfpy.Timer(f"stack_reset_{x}_{y}_{offset}", reset_stack, 300, once=True) - -# Usage -stacked = StackedFloatingText("game", grid) -# Rapid hits will stack vertically instead of overlapping -stacked.spawn_stacked(5, 5, "-10", (255, 0, 0), is_grid_pos=True) -stacked.spawn_stacked(5, 5, "-8", (255, 0, 0), is_grid_pos=True) -stacked.spawn_stacked(5, 5, "-12", (255, 0, 0), is_grid_pos=True) \ No newline at end of file diff --git a/docs/cookbook/effects/effects_path_animation.py b/docs/cookbook/effects/effects_path_animation.py deleted file mode 100644 index 977591d..0000000 --- a/docs/cookbook/effects/effects_path_animation.py +++ /dev/null @@ -1,65 +0,0 @@ -"""McRogueFace - Path Animation (Multi-Step Movement) (effects_path_animation) - -Documentation: https://mcrogueface.github.io/cookbook/effects_path_animation -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_path_animation.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class CameraFollowingPath: - """Path animator that also moves the camera.""" - - def __init__(self, entity, grid, path, step_duration=0.2): - self.entity = entity - self.grid = grid - self.path = path - self.step_duration = step_duration - self.index = 0 - self.on_complete = None - - def start(self): - self.index = 0 - self._next() - - def _next(self): - if self.index >= len(self.path): - if self.on_complete: - self.on_complete(self) - return - - x, y = self.path[self.index] - - def done(anim, target): - self.index += 1 - self._next() - - # Animate entity - if self.entity.x != x: - anim = mcrfpy.Animation("x", float(x), self.step_duration, - "easeInOut", callback=done) - anim.start(self.entity) - elif self.entity.y != y: - anim = mcrfpy.Animation("y", float(y), self.step_duration, - "easeInOut", callback=done) - anim.start(self.entity) - else: - done(None, None) - return - - # Animate camera to follow - cam_x = mcrfpy.Animation("center_x", (x + 0.5) * 16, - self.step_duration, "easeInOut") - cam_y = mcrfpy.Animation("center_y", (y + 0.5) * 16, - self.step_duration, "easeInOut") - cam_x.start(self.grid) - cam_y.start(self.grid) - - -# Usage -path = [(5, 5), (5, 10), (10, 10)] -mover = CameraFollowingPath(player, grid, path) -mover.on_complete = lambda m: print("Journey complete!") -mover.start() \ No newline at end of file diff --git a/docs/cookbook/effects/effects_scene_transitions.py b/docs/cookbook/effects/effects_scene_transitions.py deleted file mode 100644 index 0a28bea..0000000 --- a/docs/cookbook/effects/effects_scene_transitions.py +++ /dev/null @@ -1,166 +0,0 @@ -"""McRogueFace - Scene Transition Effects (effects_scene_transitions) - -Documentation: https://mcrogueface.github.io/cookbook/effects_scene_transitions -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_scene_transitions.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class TransitionManager: - """Manages scene transitions with multiple effect types.""" - - def __init__(self, screen_width=1024, screen_height=768): - self.width = screen_width - self.height = screen_height - self.is_transitioning = False - - def go_to(self, scene_name, effect="fade", duration=0.5, **kwargs): - """ - Transition to a scene with the specified effect. - - Args: - scene_name: Target scene - effect: "fade", "flash", "wipe", "instant" - duration: Transition duration - **kwargs: Effect-specific options (color, direction) - """ - if self.is_transitioning: - return - - self.is_transitioning = True - - if effect == "instant": - mcrfpy.setScene(scene_name) - self.is_transitioning = False - - elif effect == "fade": - color = kwargs.get("color", (0, 0, 0)) - self._fade(scene_name, duration, color) - - elif effect == "flash": - color = kwargs.get("color", (255, 255, 255)) - self._flash(scene_name, duration, color) - - elif effect == "wipe": - direction = kwargs.get("direction", "right") - color = kwargs.get("color", (0, 0, 0)) - self._wipe(scene_name, duration, direction, color) - - def _fade(self, scene, duration, color): - half = duration / 2 - ui = mcrfpy.sceneUI(mcrfpy.currentScene()) - - overlay = mcrfpy.Frame(0, 0, self.width, self.height) - overlay.fill_color = mcrfpy.Color(color[0], color[1], color[2], 0) - overlay.z_index = 9999 - ui.append(overlay) - - anim = mcrfpy.Animation("opacity", 1.0, half, "easeIn") - anim.start(overlay) - - def phase2(timer_name): - mcrfpy.setScene(scene) - new_ui = mcrfpy.sceneUI(scene) - - new_overlay = mcrfpy.Frame(0, 0, self.width, self.height) - new_overlay.fill_color = mcrfpy.Color(color[0], color[1], color[2], 255) - new_overlay.z_index = 9999 - new_ui.append(new_overlay) - - anim2 = mcrfpy.Animation("opacity", 0.0, half, "easeOut") - anim2.start(new_overlay) - - def cleanup(timer_name): - for i, elem in enumerate(new_ui): - if elem is new_overlay: - new_ui.remove(i) - break - self.is_transitioning = False - - mcrfpy.Timer("fade_done", cleanup, int(half * 1000) + 50, once=True) - - mcrfpy.Timer("fade_switch", phase2, int(half * 1000), once=True) - - def _flash(self, scene, duration, color): - quarter = duration / 4 - ui = mcrfpy.sceneUI(mcrfpy.currentScene()) - - overlay = mcrfpy.Frame(0, 0, self.width, self.height) - overlay.fill_color = mcrfpy.Color(color[0], color[1], color[2], 0) - overlay.z_index = 9999 - ui.append(overlay) - - anim = mcrfpy.Animation("opacity", 1.0, quarter, "easeOut") - anim.start(overlay) - - def phase2(timer_name): - mcrfpy.setScene(scene) - new_ui = mcrfpy.sceneUI(scene) - - new_overlay = mcrfpy.Frame(0, 0, self.width, self.height) - new_overlay.fill_color = mcrfpy.Color(color[0], color[1], color[2], 255) - new_overlay.z_index = 9999 - new_ui.append(new_overlay) - - anim2 = mcrfpy.Animation("opacity", 0.0, duration / 2, "easeIn") - anim2.start(new_overlay) - - def cleanup(timer_name): - for i, elem in enumerate(new_ui): - if elem is new_overlay: - new_ui.remove(i) - break - self.is_transitioning = False - - mcrfpy.Timer("flash_done", cleanup, int(duration * 500) + 50, once=True) - - mcrfpy.Timer("flash_switch", phase2, int(quarter * 2000), once=True) - - def _wipe(self, scene, duration, direction, color): - # Simplified wipe - right direction only for brevity - half = duration / 2 - ui = mcrfpy.sceneUI(mcrfpy.currentScene()) - - overlay = mcrfpy.Frame(0, 0, 0, self.height) - overlay.fill_color = mcrfpy.Color(color[0], color[1], color[2], 255) - overlay.z_index = 9999 - ui.append(overlay) - - anim = mcrfpy.Animation("w", float(self.width), half, "easeInOut") - anim.start(overlay) - - def phase2(timer_name): - mcrfpy.setScene(scene) - new_ui = mcrfpy.sceneUI(scene) - - new_overlay = mcrfpy.Frame(0, 0, self.width, self.height) - new_overlay.fill_color = mcrfpy.Color(color[0], color[1], color[2], 255) - new_overlay.z_index = 9999 - new_ui.append(new_overlay) - - anim2 = mcrfpy.Animation("x", float(self.width), half, "easeInOut") - anim2.start(new_overlay) - - def cleanup(timer_name): - for i, elem in enumerate(new_ui): - if elem is new_overlay: - new_ui.remove(i) - break - self.is_transitioning = False - - mcrfpy.Timer("wipe_done", cleanup, int(half * 1000) + 50, once=True) - - mcrfpy.Timer("wipe_switch", phase2, int(half * 1000), once=True) - - -# Usage -transitions = TransitionManager() - -# Various transition styles -transitions.go_to("game", effect="fade", duration=0.5) -transitions.go_to("menu", effect="flash", color=(255, 255, 255), duration=0.4) -transitions.go_to("next_level", effect="wipe", direction="right", duration=0.6) -transitions.go_to("options", effect="instant") \ No newline at end of file diff --git a/docs/cookbook/effects/effects_screen_shake_basic.py b/docs/cookbook/effects/effects_screen_shake_basic.py deleted file mode 100644 index 710d722..0000000 --- a/docs/cookbook/effects/effects_screen_shake_basic.py +++ /dev/null @@ -1,38 +0,0 @@ -"""McRogueFace - Screen Shake Effect (basic) - -Documentation: https://mcrogueface.github.io/cookbook/effects_screen_shake -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_screen_shake_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -def screen_shake(frame, intensity=5, duration=0.2): - """ - Shake a frame/container by animating its position. - - Args: - frame: The UI Frame to shake (often a container for all game elements) - intensity: Maximum pixel offset - duration: Total shake duration in seconds - """ - original_x = frame.x - original_y = frame.y - - # Quick shake to offset position - shake_x = mcrfpy.Animation("x", float(original_x + intensity), duration / 4, "easeOut") - shake_x.start(frame) - - # Schedule return to center - def return_to_center(timer_name): - anim = mcrfpy.Animation("x", float(original_x), duration / 2, "easeInOut") - anim.start(frame) - - mcrfpy.Timer("shake_return", return_to_center, int(duration * 250), once=True) - -# Usage - wrap your game content in a Frame -game_container = mcrfpy.Frame(0, 0, 1024, 768) -# ... add game elements to game_container.children ... -screen_shake(game_container, intensity=8, duration=0.3) \ No newline at end of file diff --git a/docs/cookbook/effects/effects_screen_shake_multi.py b/docs/cookbook/effects/effects_screen_shake_multi.py deleted file mode 100644 index dbb1285..0000000 --- a/docs/cookbook/effects/effects_screen_shake_multi.py +++ /dev/null @@ -1,58 +0,0 @@ -"""McRogueFace - Screen Shake Effect (multi) - -Documentation: https://mcrogueface.github.io/cookbook/effects_screen_shake -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/effects/effects_screen_shake_multi.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy -import math - -def directional_shake(shaker, direction_x, direction_y, intensity=10, duration=0.2): - """ - Shake in a specific direction (e.g., direction of impact). - - Args: - shaker: ScreenShakeManager instance - direction_x, direction_y: Direction vector (will be normalized) - intensity: Shake strength - duration: Shake duration - """ - # Normalize direction - length = math.sqrt(direction_x * direction_x + direction_y * direction_y) - if length == 0: - return - - dir_x = direction_x / length - dir_y = direction_y / length - - # Shake in the direction, then opposite, then back - shaker._animate_position( - shaker.original_x + dir_x * intensity, - shaker.original_y + dir_y * intensity, - duration / 3 - ) - - def reverse(timer_name): - shaker._animate_position( - shaker.original_x - dir_x * intensity * 0.5, - shaker.original_y - dir_y * intensity * 0.5, - duration / 3 - ) - - def reset(timer_name): - shaker._animate_position( - shaker.original_x, - shaker.original_y, - duration / 3 - ) - shaker.is_shaking = False - - mcrfpy.Timer("dir_shake_rev", reverse, int(duration * 333), once=True) - mcrfpy.Timer("dir_shake_reset", reset, int(duration * 666), once=True) - -# Usage: shake away from impact direction -hit_from_x, hit_from_y = -1, 0 # Hit from the left -directional_shake(shaker, hit_from_x, hit_from_y, intensity=12) \ No newline at end of file diff --git a/docs/cookbook/grid/grid_cell_highlighting_animated.py b/docs/cookbook/grid/grid_cell_highlighting_animated.py deleted file mode 100644 index 14ae702..0000000 --- a/docs/cookbook/grid/grid_cell_highlighting_animated.py +++ /dev/null @@ -1,74 +0,0 @@ -"""McRogueFace - Cell Highlighting (Targeting) (animated) - -Documentation: https://mcrogueface.github.io/cookbook/grid_cell_highlighting -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_cell_highlighting_animated.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -class TargetingSystem: - """Handle ability targeting with visual feedback.""" - - def __init__(self, grid, player): - self.grid = grid - self.player = player - self.highlights = HighlightManager(grid) - self.current_ability = None - self.valid_targets = set() - - def start_targeting(self, ability): - """Begin targeting for an ability.""" - self.current_ability = ability - px, py = self.player.pos - - # Get valid targets based on ability - if ability.target_type == 'self': - self.valid_targets = {(px, py)} - elif ability.target_type == 'adjacent': - self.valid_targets = get_adjacent(px, py) - elif ability.target_type == 'ranged': - self.valid_targets = get_radius_range(px, py, ability.range) - elif ability.target_type == 'line': - self.valid_targets = get_line_range(px, py, ability.range) - - # Filter to visible tiles only - self.valid_targets = { - (x, y) for x, y in self.valid_targets - if grid.is_in_fov(x, y) - } - - # Show valid targets - self.highlights.add('attack', self.valid_targets) - - def update_hover(self, x, y): - """Update when cursor moves.""" - if not self.current_ability: - return - - # Clear previous AoE preview - self.highlights.remove('danger') - - if (x, y) in self.valid_targets: - # Valid target - highlight it - self.highlights.add('select', [(x, y)]) - - # Show AoE if applicable - if self.current_ability.aoe_radius > 0: - aoe = get_radius_range(x, y, self.current_ability.aoe_radius, True) - self.highlights.add('danger', aoe) - else: - self.highlights.remove('select') - - def confirm_target(self, x, y): - """Confirm target selection.""" - if (x, y) in self.valid_targets: - self.cancel_targeting() - return (x, y) - return None - - def cancel_targeting(self): - """Cancel targeting mode.""" - self.current_ability = None - self.valid_targets = set() - self.highlights.clear() \ No newline at end of file diff --git a/docs/cookbook/grid/grid_cell_highlighting_basic.py b/docs/cookbook/grid/grid_cell_highlighting_basic.py deleted file mode 100644 index 830e17e..0000000 --- a/docs/cookbook/grid/grid_cell_highlighting_basic.py +++ /dev/null @@ -1,74 +0,0 @@ -"""McRogueFace - Cell Highlighting (Targeting) (basic) - -Documentation: https://mcrogueface.github.io/cookbook/grid_cell_highlighting -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_cell_highlighting_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def get_line_range(start_x, start_y, max_range): - """Get cells in cardinal directions (ranged attack).""" - cells = set() - - for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: - for dist in range(1, max_range + 1): - x = start_x + dx * dist - y = start_y + dy * dist - - # Stop if wall blocks line of sight - if not grid.at(x, y).transparent: - break - - cells.add((x, y)) - - return cells - -def get_radius_range(center_x, center_y, radius, include_center=False): - """Get cells within a radius (spell area).""" - cells = set() - - for x in range(center_x - radius, center_x + radius + 1): - for y in range(center_y - radius, center_y + radius + 1): - # Euclidean distance - dist = ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5 - if dist <= radius: - if include_center or (x, y) != (center_x, center_y): - cells.add((x, y)) - - return cells - -def get_cone_range(origin_x, origin_y, direction, length, spread): - """Get cells in a cone (breath attack).""" - import math - cells = set() - - # Direction angles (in radians) - angles = { - 'n': -math.pi / 2, - 's': math.pi / 2, - 'e': 0, - 'w': math.pi, - 'ne': -math.pi / 4, - 'nw': -3 * math.pi / 4, - 'se': math.pi / 4, - 'sw': 3 * math.pi / 4 - } - - base_angle = angles.get(direction, 0) - half_spread = math.radians(spread / 2) - - for x in range(origin_x - length, origin_x + length + 1): - for y in range(origin_y - length, origin_y + length + 1): - dx = x - origin_x - dy = y - origin_y - dist = (dx * dx + dy * dy) ** 0.5 - - if dist > 0 and dist <= length: - angle = math.atan2(dy, dx) - angle_diff = abs((angle - base_angle + math.pi) % (2 * math.pi) - math.pi) - - if angle_diff <= half_spread: - cells.add((x, y)) - - return cells \ No newline at end of file diff --git a/docs/cookbook/grid/grid_cell_highlighting_multi.py b/docs/cookbook/grid/grid_cell_highlighting_multi.py deleted file mode 100644 index 57112af..0000000 --- a/docs/cookbook/grid/grid_cell_highlighting_multi.py +++ /dev/null @@ -1,23 +0,0 @@ -"""McRogueFace - Cell Highlighting (Targeting) (multi) - -Documentation: https://mcrogueface.github.io/cookbook/grid_cell_highlighting -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_cell_highlighting_multi.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def show_path_preview(start, end): - """Highlight the path between two points.""" - path = find_path(start, end) # Your pathfinding function - - if path: - highlights.add('path', path) - - # Highlight destination specially - highlights.add('select', [end]) - -def hide_path_preview(): - """Clear path display.""" - highlights.remove('path') - highlights.remove('select') \ No newline at end of file diff --git a/docs/cookbook/grid/grid_dijkstra_basic.py b/docs/cookbook/grid/grid_dijkstra_basic.py deleted file mode 100644 index dd318b1..0000000 --- a/docs/cookbook/grid/grid_dijkstra_basic.py +++ /dev/null @@ -1,31 +0,0 @@ -"""McRogueFace - Dijkstra Distance Maps (basic) - -Documentation: https://mcrogueface.github.io/cookbook/grid_dijkstra -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_dijkstra_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def ai_flee(entity, threat_x, threat_y): - """Move entity away from threat using Dijkstra map.""" - grid.compute_dijkstra(threat_x, threat_y) - - ex, ey = entity.pos - current_dist = grid.get_dijkstra_distance(ex, ey) - - # Find neighbor with highest distance - best_move = None - best_dist = current_dist - - for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: - nx, ny = ex + dx, ey + dy - - if grid.at(nx, ny).walkable: - dist = grid.get_dijkstra_distance(nx, ny) - if dist > best_dist: - best_dist = dist - best_move = (nx, ny) - - if best_move: - entity.pos = best_move \ No newline at end of file diff --git a/docs/cookbook/grid/grid_dijkstra_multi.py b/docs/cookbook/grid/grid_dijkstra_multi.py deleted file mode 100644 index 32d011e..0000000 --- a/docs/cookbook/grid/grid_dijkstra_multi.py +++ /dev/null @@ -1,44 +0,0 @@ -"""McRogueFace - Dijkstra Distance Maps (multi) - -Documentation: https://mcrogueface.github.io/cookbook/grid_dijkstra -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_dijkstra_multi.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -# Cache Dijkstra maps when possible -class CachedDijkstra: - """Cache Dijkstra computations.""" - - def __init__(self, grid): - self.grid = grid - self.cache = {} - self.cache_valid = False - - def invalidate(self): - """Call when map changes.""" - self.cache = {} - self.cache_valid = False - - def get_distance(self, from_x, from_y, to_x, to_y): - """Get cached distance or compute.""" - key = (to_x, to_y) # Cache by destination - - if key not in self.cache: - self.grid.compute_dijkstra(to_x, to_y) - # Store all distances from this computation - self.cache[key] = self._snapshot_distances() - - return self.cache[key].get((from_x, from_y), float('inf')) - - def _snapshot_distances(self): - """Capture current distance values.""" - grid_w, grid_h = self.grid.grid_size - distances = {} - for x in range(grid_w): - for y in range(grid_h): - dist = self.grid.get_dijkstra_distance(x, y) - if dist != float('inf'): - distances[(x, y)] = dist - return distances \ No newline at end of file diff --git a/docs/cookbook/grid/grid_dungeon_generator_basic.py b/docs/cookbook/grid/grid_dungeon_generator_basic.py deleted file mode 100644 index fd36d26..0000000 --- a/docs/cookbook/grid/grid_dungeon_generator_basic.py +++ /dev/null @@ -1,125 +0,0 @@ -"""McRogueFace - Room and Corridor Generator (basic) - -Documentation: https://mcrogueface.github.io/cookbook/grid_dungeon_generator -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_dungeon_generator_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -class BSPNode: - """Node in a BSP tree for dungeon generation.""" - - MIN_SIZE = 6 - - def __init__(self, x, y, w, h): - self.x = x - self.y = y - self.w = w - self.h = h - self.left = None - self.right = None - self.room = None - - def split(self): - """Recursively split this node.""" - if self.left or self.right: - return False - - # Choose split direction - if self.w > self.h and self.w / self.h >= 1.25: - horizontal = False - elif self.h > self.w and self.h / self.w >= 1.25: - horizontal = True - else: - horizontal = random.random() < 0.5 - - max_size = (self.h if horizontal else self.w) - self.MIN_SIZE - if max_size <= self.MIN_SIZE: - return False - - split = random.randint(self.MIN_SIZE, max_size) - - if horizontal: - self.left = BSPNode(self.x, self.y, self.w, split) - self.right = BSPNode(self.x, self.y + split, self.w, self.h - split) - else: - self.left = BSPNode(self.x, self.y, split, self.h) - self.right = BSPNode(self.x + split, self.y, self.w - split, self.h) - - return True - - def create_rooms(self, grid): - """Create rooms in leaf nodes and connect siblings.""" - if self.left or self.right: - if self.left: - self.left.create_rooms(grid) - if self.right: - self.right.create_rooms(grid) - - # Connect children - if self.left and self.right: - left_room = self.left.get_room() - right_room = self.right.get_room() - if left_room and right_room: - connect_points(grid, left_room.center, right_room.center) - else: - # Leaf node - create room - w = random.randint(3, self.w - 2) - h = random.randint(3, self.h - 2) - x = self.x + random.randint(1, self.w - w - 1) - y = self.y + random.randint(1, self.h - h - 1) - self.room = Room(x, y, w, h) - carve_room(grid, self.room) - - def get_room(self): - """Get a room from this node or its children.""" - if self.room: - return self.room - - left_room = self.left.get_room() if self.left else None - right_room = self.right.get_room() if self.right else None - - if left_room and right_room: - return random.choice([left_room, right_room]) - return left_room or right_room - - -def generate_bsp_dungeon(grid, iterations=4): - """Generate a BSP-based dungeon.""" - grid_w, grid_h = grid.grid_size - - # Fill with walls - for x in range(grid_w): - for y in range(grid_h): - point = grid.at(x, y) - point.tilesprite = TILE_WALL - point.walkable = False - point.transparent = False - - # Build BSP tree - root = BSPNode(0, 0, grid_w, grid_h) - nodes = [root] - - for _ in range(iterations): - new_nodes = [] - for node in nodes: - if node.split(): - new_nodes.extend([node.left, node.right]) - nodes = new_nodes or nodes - - # Create rooms and corridors - root.create_rooms(grid) - - # Collect all rooms - rooms = [] - def collect_rooms(node): - if node.room: - rooms.append(node.room) - if node.left: - collect_rooms(node.left) - if node.right: - collect_rooms(node.right) - - collect_rooms(root) - return rooms \ No newline at end of file diff --git a/docs/cookbook/grid/grid_dungeon_generator_complete.py b/docs/cookbook/grid/grid_dungeon_generator_complete.py deleted file mode 100644 index e795dfa..0000000 --- a/docs/cookbook/grid/grid_dungeon_generator_complete.py +++ /dev/null @@ -1,148 +0,0 @@ -"""McRogueFace - Room and Corridor Generator (complete) - -Documentation: https://mcrogueface.github.io/cookbook/grid_dungeon_generator -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_dungeon_generator_complete.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy -import random - -# Tile indices (adjust for your tileset) -TILE_FLOOR = 0 -TILE_WALL = 1 -TILE_DOOR = 2 -TILE_STAIRS_DOWN = 3 -TILE_STAIRS_UP = 4 - -class DungeonGenerator: - """Procedural dungeon generator with rooms and corridors.""" - - def __init__(self, grid, seed=None): - self.grid = grid - self.grid_w, self.grid_h = grid.grid_size - self.rooms = [] - - if seed is not None: - random.seed(seed) - - def generate(self, room_count=8, min_room=4, max_room=10): - """Generate a complete dungeon level.""" - self.rooms = [] - - # Fill with walls - self._fill_walls() - - # Place rooms - attempts = 0 - max_attempts = room_count * 10 - - while len(self.rooms) < room_count and attempts < max_attempts: - attempts += 1 - - # Random room size - w = random.randint(min_room, max_room) - h = random.randint(min_room, max_room) - - # Random position (leaving border) - x = random.randint(1, self.grid_w - w - 2) - y = random.randint(1, self.grid_h - h - 2) - - room = Room(x, y, w, h) - - # Check overlap - if not any(room.intersects(r) for r in self.rooms): - self._carve_room(room) - - # Connect to previous room - if self.rooms: - self._dig_corridor(self.rooms[-1].center, room.center) - - self.rooms.append(room) - - # Place stairs - if len(self.rooms) >= 2: - self._place_stairs() - - return self.rooms - - def _fill_walls(self): - """Fill the entire grid with wall tiles.""" - for x in range(self.grid_w): - for y in range(self.grid_h): - point = self.grid.at(x, y) - point.tilesprite = TILE_WALL - point.walkable = False - point.transparent = False - - def _carve_room(self, room): - """Carve out a room, making it walkable.""" - for x in range(room.x, room.x + room.width): - for y in range(room.y, room.y + room.height): - self._set_floor(x, y) - - def _set_floor(self, x, y): - """Set a single tile as floor.""" - if 0 <= x < self.grid_w and 0 <= y < self.grid_h: - point = self.grid.at(x, y) - point.tilesprite = TILE_FLOOR - point.walkable = True - point.transparent = True - - def _dig_corridor(self, start, end): - """Dig an L-shaped corridor between two points.""" - x1, y1 = start - x2, y2 = end - - # Randomly choose horizontal-first or vertical-first - if random.random() < 0.5: - # Horizontal then vertical - self._dig_horizontal(x1, x2, y1) - self._dig_vertical(y1, y2, x2) - else: - # Vertical then horizontal - self._dig_vertical(y1, y2, x1) - self._dig_horizontal(x1, x2, y2) - - def _dig_horizontal(self, x1, x2, y): - """Dig a horizontal tunnel.""" - for x in range(min(x1, x2), max(x1, x2) + 1): - self._set_floor(x, y) - - def _dig_vertical(self, y1, y2, x): - """Dig a vertical tunnel.""" - for y in range(min(y1, y2), max(y1, y2) + 1): - self._set_floor(x, y) - - def _place_stairs(self): - """Place stairs in first and last rooms.""" - # Stairs up in first room - start_room = self.rooms[0] - sx, sy = start_room.center - point = self.grid.at(sx, sy) - point.tilesprite = TILE_STAIRS_UP - - # Stairs down in last room - end_room = self.rooms[-1] - ex, ey = end_room.center - point = self.grid.at(ex, ey) - point.tilesprite = TILE_STAIRS_DOWN - - return (sx, sy), (ex, ey) - - def get_spawn_point(self): - """Get a good spawn point for the player.""" - if self.rooms: - return self.rooms[0].center - return (self.grid_w // 2, self.grid_h // 2) - - def get_random_floor(self): - """Get a random walkable floor tile.""" - floors = [] - for x in range(self.grid_w): - for y in range(self.grid_h): - if self.grid.at(x, y).walkable: - floors.append((x, y)) - return random.choice(floors) if floors else None \ No newline at end of file diff --git a/docs/cookbook/grid/grid_fog_of_war.py b/docs/cookbook/grid/grid_fog_of_war.py deleted file mode 100644 index 5a2fc7f..0000000 --- a/docs/cookbook/grid/grid_fog_of_war.py +++ /dev/null @@ -1,20 +0,0 @@ -"""McRogueFace - Basic Fog of War (grid_fog_of_war) - -Documentation: https://mcrogueface.github.io/cookbook/grid_fog_of_war -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_fog_of_war.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -# Shadowcasting (default) - fast and produces nice results -grid.compute_fov(x, y, 10, mcrfpy.FOV.SHADOW) - -# Recursive shadowcasting - slightly different corner behavior -grid.compute_fov(x, y, 10, mcrfpy.FOV.RECURSIVE_SHADOW) - -# Diamond - simple but produces diamond-shaped FOV -grid.compute_fov(x, y, 10, mcrfpy.FOV.DIAMOND) - -# Permissive - sees more tiles, good for tactical games -grid.compute_fov(x, y, 10, mcrfpy.FOV.PERMISSIVE) \ No newline at end of file diff --git a/docs/cookbook/grid/grid_multi_layer_basic.py b/docs/cookbook/grid/grid_multi_layer_basic.py deleted file mode 100644 index a9dda3c..0000000 --- a/docs/cookbook/grid/grid_multi_layer_basic.py +++ /dev/null @@ -1,114 +0,0 @@ -"""McRogueFace - Multi-Layer Tiles (basic) - -Documentation: https://mcrogueface.github.io/cookbook/grid_multi_layer -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_multi_layer_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -class EffectLayer: - """Manage visual effects with color overlays.""" - - def __init__(self, grid, z_index=2): - self.grid = grid - self.layer = grid.add_layer("color", z_index=z_index) - self.effects = {} # (x, y) -> effect_data - - def add_effect(self, x, y, effect_type, duration=None, **kwargs): - """Add a visual effect.""" - self.effects[(x, y)] = { - 'type': effect_type, - 'duration': duration, - 'time': 0, - **kwargs - } - - def remove_effect(self, x, y): - """Remove an effect.""" - if (x, y) in self.effects: - del self.effects[(x, y)] - self.layer.set(x, y, mcrfpy.Color(0, 0, 0, 0)) - - def update(self, dt): - """Update all effects.""" - import math - - to_remove = [] - - for (x, y), effect in self.effects.items(): - effect['time'] += dt - - # Check expiration - if effect['duration'] and effect['time'] >= effect['duration']: - to_remove.append((x, y)) - continue - - # Calculate color based on effect type - color = self._calculate_color(effect) - self.layer.set(x, y, color) - - for pos in to_remove: - self.remove_effect(*pos) - - def _calculate_color(self, effect): - """Get color for an effect at current time.""" - import math - - t = effect['time'] - effect_type = effect['type'] - - if effect_type == 'fire': - # Flickering orange/red - flicker = 0.7 + 0.3 * math.sin(t * 10) - return mcrfpy.Color( - 255, - int(100 + 50 * math.sin(t * 8)), - 0, - int(180 * flicker) - ) - - elif effect_type == 'poison': - # Pulsing green - pulse = 0.5 + 0.5 * math.sin(t * 3) - return mcrfpy.Color(0, 200, 0, int(100 * pulse)) - - elif effect_type == 'ice': - # Static blue with shimmer - shimmer = 0.8 + 0.2 * math.sin(t * 5) - return mcrfpy.Color(100, 150, 255, int(120 * shimmer)) - - elif effect_type == 'blood': - # Fading red - duration = effect.get('duration', 5) - fade = 1 - (t / duration) if duration else 1 - return mcrfpy.Color(150, 0, 0, int(150 * fade)) - - elif effect_type == 'highlight': - # Pulsing highlight - pulse = 0.5 + 0.5 * math.sin(t * 4) - base = effect.get('color', mcrfpy.Color(255, 255, 0, 100)) - return mcrfpy.Color(base.r, base.g, base.b, int(base.a * pulse)) - - return mcrfpy.Color(128, 128, 128, 50) - - -# Usage -effects = EffectLayer(grid) - -# Add fire effect (permanent) -effects.add_effect(5, 5, 'fire') - -# Add blood stain (fades over 10 seconds) -effects.add_effect(10, 10, 'blood', duration=10) - -# Add poison cloud -for x in range(8, 12): - for y in range(8, 12): - effects.add_effect(x, y, 'poison', duration=5) - -# Update in game loop -def game_update(runtime): - effects.update(0.016) # 60 FPS - -mcrfpy.setTimer("effects", game_update, 16) \ No newline at end of file diff --git a/docs/cookbook/grid/grid_multi_layer_complete.py b/docs/cookbook/grid/grid_multi_layer_complete.py deleted file mode 100644 index c002ab2..0000000 --- a/docs/cookbook/grid/grid_multi_layer_complete.py +++ /dev/null @@ -1,38 +0,0 @@ -"""McRogueFace - Multi-Layer Tiles (complete) - -Documentation: https://mcrogueface.github.io/cookbook/grid_multi_layer -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/grid/grid_multi_layer_complete.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -class OptimizedLayers: - """Performance-optimized layer management.""" - - def __init__(self, grid): - self.grid = grid - self.dirty_effects = set() # Only update changed cells - self.batch_updates = [] - - def mark_dirty(self, x, y): - """Mark a cell as needing update.""" - self.dirty_effects.add((x, y)) - - def batch_set(self, layer, cells_and_values): - """Queue batch updates.""" - self.batch_updates.append((layer, cells_and_values)) - - def flush(self): - """Apply all queued updates.""" - for layer, updates in self.batch_updates: - for x, y, value in updates: - layer.set(x, y, value) - self.batch_updates = [] - - def update_dirty_only(self, effect_layer, effect_calculator): - """Only update cells marked dirty.""" - for x, y in self.dirty_effects: - color = effect_calculator(x, y) - effect_layer.set(x, y, color) - self.dirty_effects.clear() \ No newline at end of file diff --git a/docs/cookbook/ui/ui_health_bar_animated.py b/docs/cookbook/ui/ui_health_bar_animated.py deleted file mode 100644 index 4856475..0000000 --- a/docs/cookbook/ui/ui_health_bar_animated.py +++ /dev/null @@ -1,120 +0,0 @@ -"""McRogueFace - Health Bar Widget (animated) - -Documentation: https://mcrogueface.github.io/cookbook/ui_health_bar -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_health_bar_animated.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class AnimatedHealthBar: - """Health bar with smooth fill animation.""" - - def __init__(self, x, y, w, h, current, maximum): - self.x = x - self.y = y - self.w = w - self.h = h - self.current = current - self.display_current = current # What's visually shown - self.maximum = maximum - self.timer_name = f"hp_anim_{id(self)}" - - # Background - self.background = mcrfpy.Frame(x, y, w, h) - self.background.fill_color = mcrfpy.Color(40, 40, 40) - self.background.outline = 2 - self.background.outline_color = mcrfpy.Color(60, 60, 60) - - # Damage preview (shows recent damage in different color) - self.damage_fill = mcrfpy.Frame(x + 2, y + 2, w - 4, h - 4) - self.damage_fill.fill_color = mcrfpy.Color(180, 50, 50) - self.damage_fill.outline = 0 - - # Main fill - self.fill = mcrfpy.Frame(x + 2, y + 2, w - 4, h - 4) - self.fill.fill_color = mcrfpy.Color(50, 200, 50) - self.fill.outline = 0 - - self._update_display() - - def _update_display(self): - """Update the visual fill based on display_current.""" - ratio = max(0, min(1, self.display_current / self.maximum)) - self.fill.w = (self.w - 4) * ratio - - # Color based on ratio - if ratio > 0.6: - self.fill.fill_color = mcrfpy.Color(50, 200, 50) - elif ratio > 0.3: - self.fill.fill_color = mcrfpy.Color(230, 180, 30) - else: - self.fill.fill_color = mcrfpy.Color(200, 50, 50) - - def set_health(self, new_current, animate=True): - """ - Set health with optional animation. - - Args: - new_current: New health value - animate: Whether to animate the transition - """ - old_current = self.current - self.current = max(0, min(self.maximum, new_current)) - - if not animate: - self.display_current = self.current - self._update_display() - return - - # Show damage preview immediately - if self.current < old_current: - damage_ratio = self.current / self.maximum - self.damage_fill.w = (self.w - 4) * (old_current / self.maximum) - - # Animate the fill - self._start_animation() - - def _start_animation(self): - """Start animating toward target health.""" - mcrfpy.delTimer(self.timer_name) - - def animate_step(dt): - # Lerp toward target - diff = self.current - self.display_current - if abs(diff) < 0.5: - self.display_current = self.current - mcrfpy.delTimer(self.timer_name) - # Also update damage preview - self.damage_fill.w = self.fill.w - else: - # Move 10% of the way each frame - self.display_current += diff * 0.1 - - self._update_display() - - mcrfpy.setTimer(self.timer_name, animate_step, 16) - - def damage(self, amount): - """Apply damage with animation.""" - self.set_health(self.current - amount, animate=True) - - def heal(self, amount): - """Apply healing with animation.""" - self.set_health(self.current + amount, animate=True) - - def add_to_scene(self, ui): - """Add all frames to scene.""" - ui.append(self.background) - ui.append(self.damage_fill) - ui.append(self.fill) - - -# Usage -hp_bar = AnimatedHealthBar(50, 50, 300, 30, current=100, maximum=100) -hp_bar.add_to_scene(ui) - -# Damage will animate smoothly -hp_bar.damage(40) \ No newline at end of file diff --git a/docs/cookbook/ui/ui_health_bar_basic.py b/docs/cookbook/ui/ui_health_bar_basic.py deleted file mode 100644 index 4e711e6..0000000 --- a/docs/cookbook/ui/ui_health_bar_basic.py +++ /dev/null @@ -1,43 +0,0 @@ -"""McRogueFace - Health Bar Widget (basic) - -Documentation: https://mcrogueface.github.io/cookbook/ui_health_bar -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_health_bar_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -mcrfpy.createScene("game") -mcrfpy.setScene("game") -ui = mcrfpy.sceneUI("game") - -# Player health bar at top -player_hp = EnhancedHealthBar(10, 10, 300, 30, 100, 100) -player_hp.add_to_scene(ui) - -# Enemy health bar -enemy_hp = EnhancedHealthBar(400, 10, 200, 20, 50, 50) -enemy_hp.add_to_scene(ui) - -# Simulate combat -def combat_tick(dt): - import random - if random.random() < 0.3: - player_hp.damage(random.randint(5, 15)) - if random.random() < 0.4: - enemy_hp.damage(random.randint(3, 8)) - -mcrfpy.setTimer("combat", combat_tick, 1000) - -# Keyboard controls for testing -def on_key(key, state): - if state != "start": - return - if key == "H": - player_hp.heal(20) - elif key == "D": - player_hp.damage(10) - -mcrfpy.keypressScene(on_key) \ No newline at end of file diff --git a/docs/cookbook/ui/ui_health_bar_enhanced.py b/docs/cookbook/ui/ui_health_bar_enhanced.py deleted file mode 100644 index e0c128d..0000000 --- a/docs/cookbook/ui/ui_health_bar_enhanced.py +++ /dev/null @@ -1,123 +0,0 @@ -"""McRogueFace - Health Bar Widget (enhanced) - -Documentation: https://mcrogueface.github.io/cookbook/ui_health_bar -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_health_bar_enhanced.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class EnhancedHealthBar: - """Health bar with text display, color transitions, and animations.""" - - def __init__(self, x, y, w, h, current, maximum, show_text=True): - self.x = x - self.y = y - self.w = w - self.h = h - self.current = current - self.maximum = maximum - self.show_text = show_text - - # Color thresholds (ratio -> color) - self.colors = { - 0.6: mcrfpy.Color(50, 205, 50), # Green when > 60% - 0.3: mcrfpy.Color(255, 165, 0), # Orange when > 30% - 0.0: mcrfpy.Color(220, 20, 20), # Red when <= 30% - } - - # Background frame with dark fill - self.background = mcrfpy.Frame(x, y, w, h) - self.background.fill_color = mcrfpy.Color(30, 30, 30) - self.background.outline = 2 - self.background.outline_color = mcrfpy.Color(100, 100, 100) - - # Fill frame (nested inside background conceptually) - padding = 2 - self.fill = mcrfpy.Frame( - x + padding, - y + padding, - w - padding * 2, - h - padding * 2 - ) - self.fill.outline = 0 - - # Text label - self.label = None - if show_text: - self.label = mcrfpy.Caption( - "", - mcrfpy.default_font, - x + w / 2 - 20, - y + h / 2 - 8 - ) - self.label.fill_color = mcrfpy.Color(255, 255, 255) - self.label.outline = 1 - self.label.outline_color = mcrfpy.Color(0, 0, 0) - - self._update() - - def _get_color_for_ratio(self, ratio): - """Get the appropriate color based on health ratio.""" - for threshold, color in sorted(self.colors.items(), reverse=True): - if ratio > threshold: - return color - # Return the lowest threshold color if ratio is 0 or below - return self.colors[0.0] - - def _update(self): - """Update fill width, color, and text.""" - ratio = max(0, min(1, self.current / self.maximum)) - - # Update fill width (accounting for padding) - padding = 2 - self.fill.w = (self.w - padding * 2) * ratio - - # Update color based on ratio - self.fill.fill_color = self._get_color_for_ratio(ratio) - - # Update text - if self.label: - self.label.text = f"{int(self.current)}/{int(self.maximum)}" - # Center the text - text_width = len(self.label.text) * 8 # Approximate - self.label.x = self.x + (self.w - text_width) / 2 - - def set_health(self, current, maximum=None): - """Update health values.""" - self.current = max(0, current) - if maximum is not None: - self.maximum = maximum - self._update() - - def damage(self, amount): - """Apply damage (convenience method).""" - self.set_health(self.current - amount) - - def heal(self, amount): - """Apply healing (convenience method).""" - self.set_health(min(self.maximum, self.current + amount)) - - def add_to_scene(self, ui): - """Add all components to scene UI.""" - ui.append(self.background) - ui.append(self.fill) - if self.label: - ui.append(self.label) - - -# Usage -mcrfpy.createScene("demo") -mcrfpy.setScene("demo") -ui = mcrfpy.sceneUI("demo") - -# Create enhanced health bar -hp = EnhancedHealthBar(50, 50, 250, 25, current=100, maximum=100) -hp.add_to_scene(ui) - -# Simulate damage -hp.damage(30) # Now 70/100, shows green -hp.damage(25) # Now 45/100, shows orange -hp.damage(20) # Now 25/100, shows red \ No newline at end of file diff --git a/docs/cookbook/ui/ui_health_bar_multi.py b/docs/cookbook/ui/ui_health_bar_multi.py deleted file mode 100644 index fc20554..0000000 --- a/docs/cookbook/ui/ui_health_bar_multi.py +++ /dev/null @@ -1,108 +0,0 @@ -"""McRogueFace - Health Bar Widget (multi) - -Documentation: https://mcrogueface.github.io/cookbook/ui_health_bar -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_health_bar_multi.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class ResourceBar: - """Generic resource bar that can represent any stat.""" - - def __init__(self, x, y, w, h, current, maximum, - fill_color, bg_color=None, label=""): - self.x = x - self.y = y - self.w = w - self.h = h - self.current = current - self.maximum = maximum - self.label_text = label - - if bg_color is None: - bg_color = mcrfpy.Color(30, 30, 30) - - # Background - self.background = mcrfpy.Frame(x, y, w, h) - self.background.fill_color = bg_color - self.background.outline = 1 - self.background.outline_color = mcrfpy.Color(60, 60, 60) - - # Fill - self.fill = mcrfpy.Frame(x + 1, y + 1, w - 2, h - 2) - self.fill.fill_color = fill_color - self.fill.outline = 0 - - # Label (left side) - self.label = mcrfpy.Caption(label, mcrfpy.default_font, x - 30, y + 2) - self.label.fill_color = mcrfpy.Color(200, 200, 200) - - self._update() - - def _update(self): - ratio = max(0, min(1, self.current / self.maximum)) - self.fill.w = (self.w - 2) * ratio - - def set_value(self, current, maximum=None): - self.current = max(0, current) - if maximum: - self.maximum = maximum - self._update() - - def add_to_scene(self, ui): - if self.label_text: - ui.append(self.label) - ui.append(self.background) - ui.append(self.fill) - - -class PlayerStats: - """Collection of resource bars for a player.""" - - def __init__(self, x, y): - bar_width = 200 - bar_height = 18 - spacing = 25 - - self.hp = ResourceBar( - x, y, bar_width, bar_height, - current=100, maximum=100, - fill_color=mcrfpy.Color(220, 50, 50), - label="HP" - ) - - self.mp = ResourceBar( - x, y + spacing, bar_width, bar_height, - current=50, maximum=50, - fill_color=mcrfpy.Color(50, 100, 220), - label="MP" - ) - - self.stamina = ResourceBar( - x, y + spacing * 2, bar_width, bar_height, - current=80, maximum=80, - fill_color=mcrfpy.Color(50, 180, 50), - label="SP" - ) - - def add_to_scene(self, ui): - self.hp.add_to_scene(ui) - self.mp.add_to_scene(ui) - self.stamina.add_to_scene(ui) - - -# Usage -mcrfpy.createScene("stats_demo") -mcrfpy.setScene("stats_demo") -ui = mcrfpy.sceneUI("stats_demo") - -stats = PlayerStats(80, 20) -stats.add_to_scene(ui) - -# Update individual stats -stats.hp.set_value(75) -stats.mp.set_value(30) -stats.stamina.set_value(60) \ No newline at end of file diff --git a/docs/cookbook/ui/ui_menu_basic.py b/docs/cookbook/ui/ui_menu_basic.py deleted file mode 100644 index 184e4b4..0000000 --- a/docs/cookbook/ui/ui_menu_basic.py +++ /dev/null @@ -1,53 +0,0 @@ -"""McRogueFace - Selection Menu Widget (basic) - -Documentation: https://mcrogueface.github.io/cookbook/ui_menu -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_menu_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -# Setup -mcrfpy.createScene("main_menu") -mcrfpy.setScene("main_menu") -ui = mcrfpy.sceneUI("main_menu") - -# Background -bg = mcrfpy.Frame(0, 0, 1024, 768) -bg.fill_color = mcrfpy.Color(20, 20, 35) -ui.append(bg) - -# Title -title = mcrfpy.Caption("DUNGEON QUEST", mcrfpy.default_font, 350, 100) -title.fill_color = mcrfpy.Color(255, 200, 50) -ui.append(title) - -# Menu -def start_game(): - print("Starting game...") - -def show_options(): - print("Options...") - -menu = Menu( - 362, 250, - ["New Game", "Continue", "Options", "Quit"], - lambda i, opt: { - 0: start_game, - 1: lambda: print("Continue..."), - 2: show_options, - 3: mcrfpy.exit - }.get(i, lambda: None)(), - title="Main Menu" -) -menu.add_to_scene(ui) - -# Input -def on_key(key, state): - if state != "start": - return - menu.handle_key(key) - -mcrfpy.keypressScene(on_key) \ No newline at end of file diff --git a/docs/cookbook/ui/ui_menu_enhanced.py b/docs/cookbook/ui/ui_menu_enhanced.py deleted file mode 100644 index b0e25cc..0000000 --- a/docs/cookbook/ui/ui_menu_enhanced.py +++ /dev/null @@ -1,159 +0,0 @@ -"""McRogueFace - Selection Menu Widget (enhanced) - -Documentation: https://mcrogueface.github.io/cookbook/ui_menu -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_menu_enhanced.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class MenuBar: - """Horizontal menu bar with dropdown submenus.""" - - def __init__(self, y=0, items=None): - """ - Create a menu bar. - - Args: - y: Y position (usually 0 for top) - items: List of dicts with 'label' and 'options' keys - """ - self.y = y - self.items = items or [] - self.selected_item = 0 - self.dropdown_open = False - self.dropdown_selected = 0 - - self.item_width = 100 - self.height = 30 - - # Main bar frame - self.bar = mcrfpy.Frame(0, y, 1024, self.height) - self.bar.fill_color = mcrfpy.Color(50, 50, 70) - self.bar.outline = 0 - - # Item captions - self.item_captions = [] - for i, item in enumerate(items): - cap = mcrfpy.Caption( - item['label'], - mcrfpy.default_font, - 10 + i * self.item_width, - y + 7 - ) - cap.fill_color = mcrfpy.Color(200, 200, 200) - self.item_captions.append(cap) - - # Dropdown panel (hidden initially) - self.dropdown = None - self.dropdown_captions = [] - - def _update_highlight(self): - """Update visual selection on bar.""" - for i, cap in enumerate(self.item_captions): - if i == self.selected_item and self.dropdown_open: - cap.fill_color = mcrfpy.Color(255, 255, 100) - else: - cap.fill_color = mcrfpy.Color(200, 200, 200) - - def _show_dropdown(self, ui): - """Show dropdown for selected item.""" - # Remove existing dropdown - self._hide_dropdown(ui) - - item = self.items[self.selected_item] - options = item.get('options', []) - - if not options: - return - - x = 5 + self.selected_item * self.item_width - y = self.y + self.height - width = 150 - height = len(options) * 25 + 10 - - self.dropdown = mcrfpy.Frame(x, y, width, height) - self.dropdown.fill_color = mcrfpy.Color(40, 40, 60, 250) - self.dropdown.outline = 1 - self.dropdown.outline_color = mcrfpy.Color(80, 80, 100) - ui.append(self.dropdown) - - self.dropdown_captions = [] - for i, opt in enumerate(options): - cap = mcrfpy.Caption( - opt['label'], - mcrfpy.default_font, - x + 10, - y + 5 + i * 25 - ) - cap.fill_color = mcrfpy.Color(200, 200, 200) - self.dropdown_captions.append(cap) - ui.append(cap) - - self.dropdown_selected = 0 - self._update_dropdown_highlight() - - def _hide_dropdown(self, ui): - """Hide dropdown menu.""" - if self.dropdown: - try: - ui.remove(self.dropdown) - except: - pass - self.dropdown = None - - for cap in self.dropdown_captions: - try: - ui.remove(cap) - except: - pass - self.dropdown_captions = [] - - def _update_dropdown_highlight(self): - """Update dropdown selection highlight.""" - for i, cap in enumerate(self.dropdown_captions): - if i == self.dropdown_selected: - cap.fill_color = mcrfpy.Color(255, 255, 100) - else: - cap.fill_color = mcrfpy.Color(200, 200, 200) - - def add_to_scene(self, ui): - ui.append(self.bar) - for cap in self.item_captions: - ui.append(cap) - - def handle_key(self, key, ui): - """Handle keyboard navigation.""" - if not self.dropdown_open: - if key == "Left": - self.selected_item = (self.selected_item - 1) % len(self.items) - self._update_highlight() - elif key == "Right": - self.selected_item = (self.selected_item + 1) % len(self.items) - self._update_highlight() - elif key == "Return" or key == "Down": - self.dropdown_open = True - self._show_dropdown(ui) - self._update_highlight() - else: - if key == "Up": - options = self.items[self.selected_item].get('options', []) - self.dropdown_selected = (self.dropdown_selected - 1) % len(options) - self._update_dropdown_highlight() - elif key == "Down": - options = self.items[self.selected_item].get('options', []) - self.dropdown_selected = (self.dropdown_selected + 1) % len(options) - self._update_dropdown_highlight() - elif key == "Return": - opt = self.items[self.selected_item]['options'][self.dropdown_selected] - if opt.get('action'): - opt['action']() - self.dropdown_open = False - self._hide_dropdown(ui) - self._update_highlight() - elif key == "Escape": - self.dropdown_open = False - self._hide_dropdown(ui) - self._update_highlight() \ No newline at end of file diff --git a/docs/cookbook/ui/ui_message_log_basic.py b/docs/cookbook/ui/ui_message_log_basic.py deleted file mode 100644 index 0722573..0000000 --- a/docs/cookbook/ui/ui_message_log_basic.py +++ /dev/null @@ -1,54 +0,0 @@ -"""McRogueFace - Message Log Widget (basic) - -Documentation: https://mcrogueface.github.io/cookbook/ui_message_log -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_message_log_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -# Initialize -mcrfpy.createScene("game") -mcrfpy.setScene("game") -ui = mcrfpy.sceneUI("game") - -# Create log at bottom of screen -log = EnhancedMessageLog(10, 500, 700, 250, line_height=20) -ui.append(log.frame) - -# Simulate game events -def simulate_combat(dt): - import random - events = [ - ("You swing your sword!", "combat"), - ("The orc dodges!", "combat"), - ("Critical hit!", "combat"), - ("You found a potion!", "loot"), - ] - event = random.choice(events) - log.add(event[0], event[1]) - -# Add messages every 2 seconds for demo -mcrfpy.setTimer("combat_sim", simulate_combat, 2000) - -# Keyboard controls -def on_key(key, state): - if state != "start": - return - if key == "PageUp": - log.scroll_up(3) - elif key == "PageDown": - log.scroll_down(3) - elif key == "C": - log.set_filter('combat') - elif key == "L": - log.set_filter('loot') - elif key == "A": - log.set_filter(None) # All - -mcrfpy.keypressScene(on_key) - -log.system("Press PageUp/PageDown to scroll") -log.system("Press C for combat, L for loot, A for all") \ No newline at end of file diff --git a/docs/cookbook/ui/ui_message_log_enhanced.py b/docs/cookbook/ui/ui_message_log_enhanced.py deleted file mode 100644 index e26b76e..0000000 --- a/docs/cookbook/ui/ui_message_log_enhanced.py +++ /dev/null @@ -1,27 +0,0 @@ -"""McRogueFace - Message Log Widget (enhanced) - -Documentation: https://mcrogueface.github.io/cookbook/ui_message_log -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_message_log_enhanced.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -def handle_keys(key, state): - if state != "start": - return - - if key == "PageUp": - log.scroll_up(5) - elif key == "PageDown": - log.scroll_down(5) - -mcrfpy.keypressScene(handle_keys) - -# Or with mouse scroll on the frame -def on_log_scroll(x, y, button, action): - # Note: You may need to implement scroll detection - # based on your input system - pass - -log.frame.click = on_log_scroll \ No newline at end of file diff --git a/docs/cookbook/ui/ui_modal_dialog_basic.py b/docs/cookbook/ui/ui_modal_dialog_basic.py deleted file mode 100644 index 9f56efa..0000000 --- a/docs/cookbook/ui/ui_modal_dialog_basic.py +++ /dev/null @@ -1,69 +0,0 @@ -"""McRogueFace - Modal Dialog Widget (basic) - -Documentation: https://mcrogueface.github.io/cookbook/ui_modal_dialog -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_modal_dialog_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -# Scene setup -mcrfpy.createScene("game") -mcrfpy.setScene("game") -ui = mcrfpy.sceneUI("game") - -# Game background -bg = mcrfpy.Frame(0, 0, 1024, 768) -bg.fill_color = mcrfpy.Color(25, 35, 45) -ui.append(bg) - -title = mcrfpy.Caption("My Game", mcrfpy.default_font, 450, 50) -title.fill_color = mcrfpy.Color(255, 255, 255) -ui.append(title) - -# Quit button -quit_btn = mcrfpy.Frame(430, 400, 160, 50) -quit_btn.fill_color = mcrfpy.Color(150, 50, 50) -quit_btn.outline = 2 -quit_btn.outline_color = mcrfpy.Color(200, 100, 100) -ui.append(quit_btn) - -quit_label = mcrfpy.Caption("Quit Game", mcrfpy.default_font, 460, 415) -quit_label.fill_color = mcrfpy.Color(255, 255, 255) -ui.append(quit_label) - -# Confirmation dialog -confirm_dialog = None - -def show_quit_confirm(): - global confirm_dialog - - def on_response(index, label): - if label == "Yes": - mcrfpy.exit() - - confirm_dialog = EnhancedDialog( - "Quit Game?", - "Are you sure you want to quit?\nUnsaved progress will be lost.", - ["Yes", "No"], - DialogStyle.WARNING, - on_response - ) - confirm_dialog.add_to_scene(ui) - confirm_dialog.show() - -quit_btn.click = lambda x, y, b, a: show_quit_confirm() if a == "start" else None - -def on_key(key, state): - if state != "start": - return - - if confirm_dialog and confirm_dialog.handle_key(key): - return - - if key == "Escape": - show_quit_confirm() - -mcrfpy.keypressScene(on_key) \ No newline at end of file diff --git a/docs/cookbook/ui/ui_modal_dialog_enhanced.py b/docs/cookbook/ui/ui_modal_dialog_enhanced.py deleted file mode 100644 index 90f0cf8..0000000 --- a/docs/cookbook/ui/ui_modal_dialog_enhanced.py +++ /dev/null @@ -1,78 +0,0 @@ -"""McRogueFace - Modal Dialog Widget (enhanced) - -Documentation: https://mcrogueface.github.io/cookbook/ui_modal_dialog -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_modal_dialog_enhanced.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -class DialogManager: - """Manages a queue of dialogs.""" - - def __init__(self, ui): - self.ui = ui - self.queue = [] - self.current = None - - def show(self, title, message, buttons=None, style=None, callback=None): - """ - Queue a dialog to show. - - If no dialog is active, shows immediately. - Otherwise, queues for later. - """ - dialog_data = { - 'title': title, - 'message': message, - 'buttons': buttons or ["OK"], - 'style': style or DialogStyle.INFO, - 'callback': callback - } - - if self.current is None: - self._show_dialog(dialog_data) - else: - self.queue.append(dialog_data) - - def _show_dialog(self, data): - """Actually display a dialog.""" - def on_close(index, label): - if data['callback']: - data['callback'](index, label) - self._on_dialog_closed() - - self.current = EnhancedDialog( - data['title'], - data['message'], - data['buttons'], - data['style'], - on_close - ) - self.current.add_to_scene(self.ui) - self.current.show() - - def _on_dialog_closed(self): - """Handle dialog close, show next if queued.""" - self.current = None - - if self.queue: - next_dialog = self.queue.pop(0) - self._show_dialog(next_dialog) - - def handle_key(self, key): - """Forward key events to current dialog.""" - if self.current: - return self.current.handle_key(key) - return False - - -# Usage -manager = DialogManager(ui) - -# Queue multiple dialogs -manager.show("First", "This is the first message") -manager.show("Second", "This appears after closing the first") -manager.show("Third", "And this is last", ["Done"]) \ No newline at end of file diff --git a/docs/cookbook/ui/ui_tooltip_basic.py b/docs/cookbook/ui/ui_tooltip_basic.py deleted file mode 100644 index 4530d90..0000000 --- a/docs/cookbook/ui/ui_tooltip_basic.py +++ /dev/null @@ -1,65 +0,0 @@ -"""McRogueFace - Tooltip on Hover (basic) - -Documentation: https://mcrogueface.github.io/cookbook/ui_tooltip -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_tooltip_basic.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -mcrfpy.createScene("game") -mcrfpy.setScene("game") -ui = mcrfpy.sceneUI("game") - -# Background -bg = mcrfpy.Frame(0, 0, 1024, 768) -bg.fill_color = mcrfpy.Color(25, 25, 35) -ui.append(bg) - -# Create inventory slots with tooltips -class InventorySlot: - def __init__(self, x, y, item_name, item_desc, tooltip_mgr): - self.frame = mcrfpy.Frame(x, y, 50, 50) - self.frame.fill_color = mcrfpy.Color(50, 50, 60) - self.frame.outline = 1 - self.frame.outline_color = mcrfpy.Color(80, 80, 90) - - self.label = mcrfpy.Caption(item_name[:3], mcrfpy.default_font, x + 10, y + 15) - self.label.fill_color = mcrfpy.Color(200, 200, 200) - - tooltip_mgr.register(self.frame, item_desc, title=item_name) - - def add_to_scene(self, ui): - ui.append(self.frame) - ui.append(self.label) - -# Setup tooltip manager -tips = TooltipManager() -tips.hover_delay = 300 - -# Create inventory -items = [ - ("Health Potion", "Restores 50 HP\nConsumable"), - ("Mana Crystal", "Restores 30 MP\nConsumable"), - ("Iron Key", "Opens iron doors\nQuest Item"), - ("Gold Ring", "Worth 100 gold\nSell to merchant"), -] - -slots = [] -for i, (name, desc) in enumerate(items): - slot = InventorySlot(100 + i * 60, 100, name, desc, tips) - slot.add_to_scene(ui) - slots.append(slot) - -# Add tooltip last -tips.add_to_scene(ui) - -# Update loop -def update(dt): - from mcrfpy import automation - x, y = automation.position() - tips.update(x, y) - -mcrfpy.setTimer("update", update, 50) \ No newline at end of file diff --git a/docs/cookbook/ui/ui_tooltip_multi.py b/docs/cookbook/ui/ui_tooltip_multi.py deleted file mode 100644 index 069fda3..0000000 --- a/docs/cookbook/ui/ui_tooltip_multi.py +++ /dev/null @@ -1,80 +0,0 @@ -"""McRogueFace - Tooltip on Hover (multi) - -Documentation: https://mcrogueface.github.io/cookbook/ui_tooltip -Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/ui/ui_tooltip_multi.py - -This code is extracted from the McRogueFace documentation and can be -run directly with: ./mcrogueface path/to/this/file.py -""" - -import mcrfpy - -def create_info_icon(x, y, tooltip_text, ui): - """ - Create an info icon that shows tooltip on hover. - - Args: - x, y: Position of the icon - tooltip_text: Text to show - ui: Scene UI to add elements to - """ - # Info icon (small circle with "i") - icon = mcrfpy.Frame(x, y, 20, 20) - icon.fill_color = mcrfpy.Color(70, 130, 180) - icon.outline = 1 - icon.outline_color = mcrfpy.Color(100, 160, 210) - - icon_label = mcrfpy.Caption("i", mcrfpy.default_font, x + 6, y + 2) - icon_label.fill_color = mcrfpy.Color(255, 255, 255) - - # Tooltip (positioned to the right of icon) - tip_frame = mcrfpy.Frame(x + 25, y - 5, 180, 50) - tip_frame.fill_color = mcrfpy.Color(40, 40, 55, 240) - tip_frame.outline = 1 - tip_frame.outline_color = mcrfpy.Color(80, 80, 100) - tip_frame.visible = False - - tip_text = mcrfpy.Caption(tooltip_text, mcrfpy.default_font, x + 33, y + 3) - tip_text.fill_color = mcrfpy.Color(220, 220, 220) - tip_text.visible = False - - # Hover behavior - def on_icon_hover(mx, my, button, action): - tip_frame.visible = True - tip_text.visible = True - - icon.click = on_icon_hover - - # Track when to hide - def check_hover(dt): - from mcrfpy import automation - mx, my = automation.position() - if not (icon.x <= mx <= icon.x + icon.w and - icon.y <= my <= icon.y + icon.h): - if tip_frame.visible: - tip_frame.visible = False - tip_text.visible = False - - timer_name = f"info_hover_{id(icon)}" - mcrfpy.setTimer(timer_name, check_hover, 100) - - # Add to scene - ui.append(icon) - ui.append(icon_label) - ui.append(tip_frame) - ui.append(tip_text) - - return icon - - -# Usage -mcrfpy.createScene("info_demo") -mcrfpy.setScene("info_demo") -ui = mcrfpy.sceneUI("info_demo") - -# Setting with info icon -setting_label = mcrfpy.Caption("Difficulty:", mcrfpy.default_font, 100, 100) -setting_label.fill_color = mcrfpy.Color(200, 200, 200) -ui.append(setting_label) - -create_info_icon(200, 98, "Affects enemy\nHP and damage", ui) \ No newline at end of file diff --git a/docs/templates/complete/ai.py b/docs/templates/complete/ai.py deleted file mode 100644 index 8d5902f..0000000 --- a/docs/templates/complete/ai.py +++ /dev/null @@ -1,289 +0,0 @@ -""" -ai.py - Enemy AI System for McRogueFace Roguelike - -Simple AI behaviors for enemies: chase player when visible, wander otherwise. -Uses A* pathfinding via entity.path_to() for movement. -""" - -from typing import List, Tuple, Optional, TYPE_CHECKING -import random - -from entities import Enemy, Player, Actor -from combat import melee_attack, CombatResult - -if TYPE_CHECKING: - from dungeon import Dungeon - - -class AIBehavior: - """Base class for AI behaviors.""" - - def take_turn(self, enemy: Enemy, player: Player, dungeon: 'Dungeon', - enemies: List[Enemy]) -> Optional[CombatResult]: - """ - Execute one turn of AI behavior. - - Args: - enemy: The enemy taking a turn - player: The player to potentially chase/attack - dungeon: The dungeon map - enemies: List of all enemies (for collision avoidance) - - Returns: - CombatResult if combat occurred, None otherwise - """ - raise NotImplementedError - - -class BasicChaseAI(AIBehavior): - """ - Simple chase AI: If player is visible, move toward them. - If adjacent, attack. Otherwise, stand still or wander. - """ - - def __init__(self, sight_range: int = 8): - """ - Args: - sight_range: How far the enemy can see - """ - self.sight_range = sight_range - - def can_see_player(self, enemy: Enemy, player: Player, - dungeon: 'Dungeon') -> bool: - """Check if enemy can see the player.""" - # Simple distance check combined with line of sight - distance = enemy.distance_to(player) - - if distance > self.sight_range: - return False - - # Check line of sight using Bresenham's line - return self._has_line_of_sight(enemy.x, enemy.y, player.x, player.y, dungeon) - - def _has_line_of_sight(self, x1: int, y1: int, x2: int, y2: int, - dungeon: 'Dungeon') -> bool: - """ - Check if there's a clear line of sight between two points. - Uses Bresenham's line algorithm. - """ - dx = abs(x2 - x1) - dy = abs(y2 - y1) - x, y = x1, y1 - sx = 1 if x1 < x2 else -1 - sy = 1 if y1 < y2 else -1 - - if dx > dy: - err = dx / 2 - while x != x2: - if not dungeon.is_transparent(x, y): - return False - err -= dy - if err < 0: - y += sy - err += dx - x += sx - else: - err = dy / 2 - while y != y2: - if not dungeon.is_transparent(x, y): - return False - err -= dx - if err < 0: - x += sx - err += dy - y += sy - - return True - - def get_path_to_player(self, enemy: Enemy, player: Player) -> List[Tuple[int, int]]: - """ - Get a path from enemy to player using A* pathfinding. - - Uses the entity's built-in path_to method. - """ - try: - path = enemy.entity.path_to(player.x, player.y) - # Convert path to list of tuples - return [(int(p[0]), int(p[1])) for p in path] if path else [] - except (AttributeError, TypeError): - # Fallback: simple direction-based movement - return [] - - def is_position_blocked(self, x: int, y: int, dungeon: 'Dungeon', - enemies: List[Enemy], player: Player) -> bool: - """Check if a position is blocked by terrain or another actor.""" - # Check terrain - if not dungeon.is_walkable(x, y): - return True - - # Check player position - if player.x == x and player.y == y: - return True - - # Check other enemies - for other in enemies: - if other.is_alive and other.x == x and other.y == y: - return True - - return False - - def move_toward(self, enemy: Enemy, target_x: int, target_y: int, - dungeon: 'Dungeon', enemies: List[Enemy], - player: Player) -> bool: - """ - Move one step toward the target position. - - Returns True if movement occurred, False otherwise. - """ - # Try pathfinding first - path = self.get_path_to_player(enemy, player) - - if path and len(path) > 1: - # First element is current position, second is next step - next_x, next_y = path[1] - else: - # Fallback: move in the general direction - dx = 0 - dy = 0 - - if target_x < enemy.x: - dx = -1 - elif target_x > enemy.x: - dx = 1 - - if target_y < enemy.y: - dy = -1 - elif target_y > enemy.y: - dy = 1 - - next_x = enemy.x + dx - next_y = enemy.y + dy - - # Check if the position is blocked - if not self.is_position_blocked(next_x, next_y, dungeon, enemies, player): - enemy.move_to(next_x, next_y) - return True - - # Try moving in just one axis - if next_x != enemy.x: - if not self.is_position_blocked(next_x, enemy.y, dungeon, enemies, player): - enemy.move_to(next_x, enemy.y) - return True - - if next_y != enemy.y: - if not self.is_position_blocked(enemy.x, next_y, dungeon, enemies, player): - enemy.move_to(enemy.x, next_y) - return True - - return False - - def take_turn(self, enemy: Enemy, player: Player, dungeon: 'Dungeon', - enemies: List[Enemy]) -> Optional[CombatResult]: - """Execute the enemy's turn.""" - if not enemy.is_alive: - return None - - # Check if adjacent to player (can attack) - if enemy.distance_to(player) == 1: - return melee_attack(enemy, player) - - # Check if can see player - if self.can_see_player(enemy, player, dungeon): - # Move toward player - self.move_toward(enemy, player.x, player.y, dungeon, enemies, player) - - return None - - -class WanderingAI(BasicChaseAI): - """ - AI that wanders randomly when it can't see the player. - More active than BasicChaseAI. - """ - - def __init__(self, sight_range: int = 8, wander_chance: float = 0.3): - """ - Args: - sight_range: How far the enemy can see - wander_chance: Probability of wandering each turn (0.0 to 1.0) - """ - super().__init__(sight_range) - self.wander_chance = wander_chance - - def wander(self, enemy: Enemy, dungeon: 'Dungeon', - enemies: List[Enemy], player: Player) -> bool: - """ - Move in a random direction. - - Returns True if movement occurred. - """ - # Random direction - directions = [ - (-1, 0), (1, 0), (0, -1), (0, 1), # Cardinal - (-1, -1), (1, -1), (-1, 1), (1, 1) # Diagonal - ] - random.shuffle(directions) - - for dx, dy in directions: - new_x = enemy.x + dx - new_y = enemy.y + dy - - if not self.is_position_blocked(new_x, new_y, dungeon, enemies, player): - enemy.move_to(new_x, new_y) - return True - - return False - - def take_turn(self, enemy: Enemy, player: Player, dungeon: 'Dungeon', - enemies: List[Enemy]) -> Optional[CombatResult]: - """Execute the enemy's turn with wandering behavior.""" - if not enemy.is_alive: - return None - - # Check if adjacent to player (can attack) - if enemy.distance_to(player) == 1: - return melee_attack(enemy, player) - - # Check if can see player - if self.can_see_player(enemy, player, dungeon): - # Chase player - self.move_toward(enemy, player.x, player.y, dungeon, enemies, player) - else: - # Wander randomly - if random.random() < self.wander_chance: - self.wander(enemy, dungeon, enemies, player) - - return None - - -# Default AI instance -default_ai = WanderingAI(sight_range=8, wander_chance=0.3) - - -def process_enemy_turns(enemies: List[Enemy], player: Player, - dungeon: 'Dungeon', - ai: AIBehavior = None) -> List[CombatResult]: - """ - Process turns for all enemies. - - Args: - enemies: List of all enemies - player: The player - dungeon: The dungeon map - ai: AI behavior to use (defaults to WanderingAI) - - Returns: - List of combat results from this round of enemy actions - """ - if ai is None: - ai = default_ai - - results = [] - - for enemy in enemies: - if enemy.is_alive: - result = ai.take_turn(enemy, player, dungeon, enemies) - if result: - results.append(result) - - return results diff --git a/docs/templates/complete/combat.py b/docs/templates/complete/combat.py deleted file mode 100644 index b0f55e7..0000000 --- a/docs/templates/complete/combat.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -combat.py - Combat System for McRogueFace Roguelike - -Handles attack resolution, damage calculation, and combat outcomes. -""" - -from dataclasses import dataclass -from typing import Tuple, Optional -import random - -from entities import Actor, Player, Enemy -from constants import ( - MSG_PLAYER_ATTACK, MSG_PLAYER_KILL, MSG_PLAYER_MISS, - MSG_ENEMY_ATTACK, MSG_ENEMY_MISS -) - - -@dataclass -class CombatResult: - """ - Result of a combat action. - - Attributes: - attacker: The attacking actor - defender: The defending actor - damage: Damage dealt (after defense) - killed: Whether the defender was killed - message: Human-readable result message - message_color: Color tuple for the message - """ - attacker: Actor - defender: Actor - damage: int - killed: bool - message: str - message_color: Tuple[int, int, int, int] - - -def calculate_damage(attack: int, defense: int, variance: float = 0.2) -> int: - """ - Calculate damage with some randomness. - - Args: - attack: Attacker's attack power - defense: Defender's defense value - variance: Random variance as percentage (0.2 = +/-20%) - - Returns: - Final damage amount (minimum 0) - """ - # Base damage is attack vs defense - base_damage = attack - defense - - # Add some variance - if base_damage > 0: - variance_amount = int(base_damage * variance) - damage = base_damage + random.randint(-variance_amount, variance_amount) - else: - # Small chance to do 1 damage even with high defense - damage = 1 if random.random() < 0.1 else 0 - - return max(0, damage) - - -def attack(attacker: Actor, defender: Actor) -> CombatResult: - """ - Perform an attack from one actor to another. - - Args: - attacker: The actor making the attack - defender: The actor being attacked - - Returns: - CombatResult with outcome details - """ - # Calculate damage - damage = calculate_damage( - attacker.fighter.attack, - defender.fighter.defense - ) - - # Apply damage - actual_damage = defender.fighter.take_damage(damage + defender.fighter.defense) - # Note: take_damage applies defense internally, so we add it back - # Actually, we calculated damage already reduced by defense, so just apply it: - defender.fighter.hp = max(0, defender.fighter.hp - damage + actual_damage) - # Simplified: just use take_damage properly - # Reset and do it right: - - # Apply raw damage (defense already calculated) - defender.fighter.hp = max(0, defender.fighter.hp - damage) - killed = not defender.is_alive - - # Generate message based on attacker/defender types - if isinstance(attacker, Player): - if killed: - message = MSG_PLAYER_KILL % defender.name - color = (255, 255, 100, 255) # Yellow for kills - elif damage > 0: - message = MSG_PLAYER_ATTACK % (defender.name, damage) - color = (255, 255, 255, 255) # White for hits - else: - message = MSG_PLAYER_MISS % defender.name - color = (150, 150, 150, 255) # Gray for misses - else: - if damage > 0: - message = MSG_ENEMY_ATTACK % (attacker.name, damage) - color = (255, 100, 100, 255) # Red for enemy hits - else: - message = MSG_ENEMY_MISS % attacker.name - color = (150, 150, 150, 255) # Gray for misses - - return CombatResult( - attacker=attacker, - defender=defender, - damage=damage, - killed=killed, - message=message, - message_color=color - ) - - -def melee_attack(attacker: Actor, defender: Actor) -> CombatResult: - """ - Perform a melee attack (bump attack). - This is the standard roguelike bump-to-attack. - - Args: - attacker: The actor making the attack - defender: The actor being attacked - - Returns: - CombatResult with outcome details - """ - return attack(attacker, defender) - - -def try_attack(attacker: Actor, target_x: int, target_y: int, - enemies: list, player: Optional[Player] = None) -> Optional[CombatResult]: - """ - Attempt to attack whatever is at the target position. - - Args: - attacker: The actor making the attack - target_x: X coordinate to attack - target_y: Y coordinate to attack - enemies: List of Enemy actors - player: The player (if attacker is an enemy) - - Returns: - CombatResult if something was attacked, None otherwise - """ - # Check if player is attacking - if isinstance(attacker, Player): - # Look for enemy at position - for enemy in enemies: - if enemy.is_alive and enemy.x == target_x and enemy.y == target_y: - return melee_attack(attacker, enemy) - else: - # Enemy attacking - check if player is at position - if player and player.x == target_x and player.y == target_y: - return melee_attack(attacker, player) - - return None - - -def process_kill(attacker: Actor, defender: Actor) -> int: - """ - Process the aftermath of killing an enemy. - - Args: - attacker: The actor that made the kill - defender: The actor that was killed - - Returns: - XP gained (if attacker is player and defender is enemy) - """ - xp_gained = 0 - - if isinstance(attacker, Player) and isinstance(defender, Enemy): - xp_gained = defender.xp_reward - attacker.gain_xp(xp_gained) - - # Remove the dead actor from the grid - defender.remove() - - return xp_gained diff --git a/docs/templates/complete/constants.py b/docs/templates/complete/constants.py deleted file mode 100644 index cca8c24..0000000 --- a/docs/templates/complete/constants.py +++ /dev/null @@ -1,210 +0,0 @@ -""" -constants.py - Game Constants for McRogueFace Complete Roguelike Template - -All configuration values in one place for easy tweaking. -""" - -# ============================================================================= -# WINDOW AND DISPLAY -# ============================================================================= -SCREEN_WIDTH = 1024 -SCREEN_HEIGHT = 768 - -# Grid display area (where the dungeon is rendered) -GRID_X = 0 -GRID_Y = 0 -GRID_WIDTH = 800 -GRID_HEIGHT = 600 - -# Tile dimensions (must match your texture) -TILE_WIDTH = 16 -TILE_HEIGHT = 16 - -# ============================================================================= -# DUNGEON GENERATION -# ============================================================================= -# Size of the dungeon in tiles -DUNGEON_WIDTH = 80 -DUNGEON_HEIGHT = 45 - -# Room size constraints -ROOM_MIN_SIZE = 6 -ROOM_MAX_SIZE = 12 -MAX_ROOMS = 15 - -# Enemy spawning per room -MAX_ENEMIES_PER_ROOM = 3 -MIN_ENEMIES_PER_ROOM = 0 - -# ============================================================================= -# SPRITE INDICES (for kenney_tinydungeon.png - 16x16 tiles) -# Adjust these if using a different tileset -# ============================================================================= -# Terrain -SPRITE_FLOOR = 48 # Dungeon floor -SPRITE_WALL = 33 # Wall tile -SPRITE_STAIRS_DOWN = 50 # Stairs going down -SPRITE_DOOR = 49 # Door tile - -# Player sprites -SPRITE_PLAYER = 84 # Player character (knight) - -# Enemy sprites -SPRITE_GOBLIN = 111 # Goblin enemy -SPRITE_ORC = 112 # Orc enemy -SPRITE_TROLL = 116 # Troll enemy - -# Items (for future expansion) -SPRITE_POTION = 89 # Health potion -SPRITE_CHEST = 91 # Treasure chest - -# ============================================================================= -# COLORS (R, G, B, A) -# ============================================================================= -# Map colors -COLOR_DARK_WALL = (50, 50, 100, 255) -COLOR_DARK_FLOOR = (30, 30, 50, 255) -COLOR_LIGHT_WALL = (100, 100, 150, 255) -COLOR_LIGHT_FLOOR = (80, 80, 100, 255) - -# FOV overlay colors -COLOR_FOG = (0, 0, 0, 200) # Unexplored areas -COLOR_REMEMBERED = (0, 0, 0, 128) # Seen but not visible -COLOR_VISIBLE = (0, 0, 0, 0) # Currently visible (transparent) - -# UI Colors -COLOR_UI_BG = (20, 20, 30, 230) -COLOR_UI_BORDER = (80, 80, 120, 255) -COLOR_TEXT = (255, 255, 255, 255) -COLOR_TEXT_HIGHLIGHT = (255, 255, 100, 255) - -# Health bar colors -COLOR_HP_BAR_BG = (80, 0, 0, 255) -COLOR_HP_BAR_FILL = (0, 180, 0, 255) -COLOR_HP_BAR_WARNING = (180, 180, 0, 255) -COLOR_HP_BAR_CRITICAL = (180, 0, 0, 255) - -# Message log colors -COLOR_MSG_DEFAULT = (255, 255, 255, 255) -COLOR_MSG_DAMAGE = (255, 100, 100, 255) -COLOR_MSG_HEAL = (100, 255, 100, 255) -COLOR_MSG_INFO = (100, 100, 255, 255) -COLOR_MSG_IMPORTANT = (255, 255, 100, 255) - -# ============================================================================= -# PLAYER STATS -# ============================================================================= -PLAYER_START_HP = 30 -PLAYER_START_ATTACK = 5 -PLAYER_START_DEFENSE = 2 - -# ============================================================================= -# ENEMY STATS -# Each enemy type: (hp, attack, defense, xp_reward, name) -# ============================================================================= -ENEMY_STATS = { - 'goblin': { - 'hp': 10, - 'attack': 3, - 'defense': 0, - 'xp': 35, - 'sprite': SPRITE_GOBLIN, - 'name': 'Goblin' - }, - 'orc': { - 'hp': 16, - 'attack': 4, - 'defense': 1, - 'xp': 50, - 'sprite': SPRITE_ORC, - 'name': 'Orc' - }, - 'troll': { - 'hp': 24, - 'attack': 6, - 'defense': 2, - 'xp': 100, - 'sprite': SPRITE_TROLL, - 'name': 'Troll' - } -} - -# Enemy spawn weights per dungeon level -# Format: {level: [(enemy_type, weight), ...]} -# Higher weight = more likely to spawn -ENEMY_SPAWN_WEIGHTS = { - 1: [('goblin', 100)], - 2: [('goblin', 80), ('orc', 20)], - 3: [('goblin', 60), ('orc', 40)], - 4: [('goblin', 40), ('orc', 50), ('troll', 10)], - 5: [('goblin', 20), ('orc', 50), ('troll', 30)], -} - -# Default weights for levels beyond those defined -DEFAULT_SPAWN_WEIGHTS = [('goblin', 10), ('orc', 50), ('troll', 40)] - -# ============================================================================= -# FOV (Field of View) SETTINGS -# ============================================================================= -FOV_RADIUS = 8 # How far the player can see -FOV_LIGHT_WALLS = True # Whether walls at FOV edge are visible - -# ============================================================================= -# INPUT KEYS -# Key names as returned by McRogueFace keypressScene -# ============================================================================= -KEY_UP = ['Up', 'W', 'Numpad8'] -KEY_DOWN = ['Down', 'S', 'Numpad2'] -KEY_LEFT = ['Left', 'A', 'Numpad4'] -KEY_RIGHT = ['Right', 'D', 'Numpad6'] - -# Diagonal movement (numpad) -KEY_UP_LEFT = ['Numpad7'] -KEY_UP_RIGHT = ['Numpad9'] -KEY_DOWN_LEFT = ['Numpad1'] -KEY_DOWN_RIGHT = ['Numpad3'] - -# Actions -KEY_WAIT = ['Period', 'Numpad5'] # Skip turn -KEY_DESCEND = ['Greater', 'Space'] # Go down stairs (> key or space) - -# ============================================================================= -# GAME MESSAGES -# ============================================================================= -MSG_WELCOME = "Welcome to the dungeon! Find the stairs to descend deeper." -MSG_DESCEND = "You descend the stairs to level %d..." -MSG_PLAYER_ATTACK = "You attack the %s for %d damage!" -MSG_PLAYER_KILL = "You have slain the %s!" -MSG_PLAYER_MISS = "You attack the %s but do no damage." -MSG_ENEMY_ATTACK = "The %s attacks you for %d damage!" -MSG_ENEMY_MISS = "The %s attacks you but does no damage." -MSG_BLOCKED = "You can't move there!" -MSG_STAIRS = "You see stairs leading down here. Press > or Space to descend." -MSG_DEATH = "You have died! Press R to restart." -MSG_NO_STAIRS = "There are no stairs here." - -# ============================================================================= -# UI LAYOUT -# ============================================================================= -# Health bar -HP_BAR_X = 10 -HP_BAR_Y = 620 -HP_BAR_WIDTH = 200 -HP_BAR_HEIGHT = 24 - -# Message log -MSG_LOG_X = 10 -MSG_LOG_Y = 660 -MSG_LOG_WIDTH = 780 -MSG_LOG_HEIGHT = 100 -MSG_LOG_MAX_LINES = 5 - -# Dungeon level display -LEVEL_DISPLAY_X = 700 -LEVEL_DISPLAY_Y = 620 - -# ============================================================================= -# ASSET PATHS -# ============================================================================= -TEXTURE_PATH = "assets/kenney_tinydungeon.png" -FONT_PATH = "assets/JetbrainsMono.ttf" diff --git a/docs/templates/complete/dungeon.py b/docs/templates/complete/dungeon.py deleted file mode 100644 index 9a009a4..0000000 --- a/docs/templates/complete/dungeon.py +++ /dev/null @@ -1,298 +0,0 @@ -""" -dungeon.py - Procedural Dungeon Generation for McRogueFace - -Generates a roguelike dungeon with rooms connected by corridors. -Includes stairs placement for multi-level progression. -""" - -import random -from dataclasses import dataclass -from typing import List, Tuple, Optional - -from constants import ( - DUNGEON_WIDTH, DUNGEON_HEIGHT, - ROOM_MIN_SIZE, ROOM_MAX_SIZE, MAX_ROOMS, - SPRITE_FLOOR, SPRITE_WALL, SPRITE_STAIRS_DOWN, - MAX_ENEMIES_PER_ROOM, MIN_ENEMIES_PER_ROOM, - ENEMY_SPAWN_WEIGHTS, DEFAULT_SPAWN_WEIGHTS -) - - -@dataclass -class Rect: - """A rectangle representing a room in the dungeon.""" - x: int - y: int - width: int - height: int - - @property - def x2(self) -> int: - return self.x + self.width - - @property - def y2(self) -> int: - return self.y + self.height - - @property - def center(self) -> Tuple[int, int]: - """Return the center coordinates of this room.""" - center_x = (self.x + self.x2) // 2 - center_y = (self.y + self.y2) // 2 - return center_x, center_y - - def intersects(self, other: 'Rect') -> bool: - """Check if this room overlaps with another (with 1 tile buffer).""" - return (self.x <= other.x2 + 1 and self.x2 + 1 >= other.x and - self.y <= other.y2 + 1 and self.y2 + 1 >= other.y) - - def inner(self) -> Tuple[int, int, int, int]: - """Return the inner area of the room (excluding walls).""" - return self.x + 1, self.y + 1, self.width - 2, self.height - 2 - - -class Tile: - """Represents a single tile in the dungeon.""" - - def __init__(self, walkable: bool = False, transparent: bool = False, - sprite: int = SPRITE_WALL): - self.walkable = walkable - self.transparent = transparent - self.sprite = sprite - self.explored = False - self.visible = False - - -class Dungeon: - """ - The dungeon map with rooms, corridors, and tile data. - - Attributes: - width: Width of the dungeon in tiles - height: Height of the dungeon in tiles - level: Current dungeon depth - tiles: 2D array of Tile objects - rooms: List of rooms (Rect objects) - player_start: Starting position for the player - stairs_pos: Position of the stairs down - """ - - def __init__(self, width: int = DUNGEON_WIDTH, height: int = DUNGEON_HEIGHT, - level: int = 1): - self.width = width - self.height = height - self.level = level - self.tiles: List[List[Tile]] = [] - self.rooms: List[Rect] = [] - self.player_start: Tuple[int, int] = (0, 0) - self.stairs_pos: Tuple[int, int] = (0, 0) - - # Initialize all tiles as walls - self._init_tiles() - - def _init_tiles(self) -> None: - """Fill the dungeon with wall tiles.""" - self.tiles = [ - [Tile(walkable=False, transparent=False, sprite=SPRITE_WALL) - for _ in range(self.height)] - for _ in range(self.width) - ] - - def in_bounds(self, x: int, y: int) -> bool: - """Check if coordinates are within dungeon bounds.""" - return 0 <= x < self.width and 0 <= y < self.height - - def is_walkable(self, x: int, y: int) -> bool: - """Check if a tile can be walked on.""" - if not self.in_bounds(x, y): - return False - return self.tiles[x][y].walkable - - def is_transparent(self, x: int, y: int) -> bool: - """Check if a tile allows light to pass through.""" - if not self.in_bounds(x, y): - return False - return self.tiles[x][y].transparent - - def get_tile(self, x: int, y: int) -> Optional[Tile]: - """Get the tile at the given position.""" - if not self.in_bounds(x, y): - return None - return self.tiles[x][y] - - def set_tile(self, x: int, y: int, walkable: bool, transparent: bool, - sprite: int) -> None: - """Set properties of a tile.""" - if self.in_bounds(x, y): - tile = self.tiles[x][y] - tile.walkable = walkable - tile.transparent = transparent - tile.sprite = sprite - - def carve_room(self, room: Rect) -> None: - """Carve out a room in the dungeon (make tiles walkable).""" - inner_x, inner_y, inner_w, inner_h = room.inner() - - for x in range(inner_x, inner_x + inner_w): - for y in range(inner_y, inner_y + inner_h): - self.set_tile(x, y, walkable=True, transparent=True, - sprite=SPRITE_FLOOR) - - def carve_tunnel_h(self, x1: int, x2: int, y: int) -> None: - """Carve a horizontal tunnel.""" - for x in range(min(x1, x2), max(x1, x2) + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite=SPRITE_FLOOR) - - def carve_tunnel_v(self, y1: int, y2: int, x: int) -> None: - """Carve a vertical tunnel.""" - for y in range(min(y1, y2), max(y1, y2) + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite=SPRITE_FLOOR) - - def connect_rooms(self, room1: Rect, room2: Rect) -> None: - """Connect two rooms with an L-shaped corridor.""" - x1, y1 = room1.center - x2, y2 = room2.center - - # Randomly choose to go horizontal then vertical, or vice versa - if random.random() < 0.5: - self.carve_tunnel_h(x1, x2, y1) - self.carve_tunnel_v(y1, y2, x2) - else: - self.carve_tunnel_v(y1, y2, x1) - self.carve_tunnel_h(x1, x2, y2) - - def place_stairs(self) -> None: - """Place stairs in the last room.""" - if self.rooms: - # Stairs go in the center of the last room - self.stairs_pos = self.rooms[-1].center - x, y = self.stairs_pos - self.set_tile(x, y, walkable=True, transparent=True, - sprite=SPRITE_STAIRS_DOWN) - - def generate(self) -> None: - """Generate the dungeon using BSP-style room placement.""" - self._init_tiles() - self.rooms.clear() - - for _ in range(MAX_ROOMS): - # Random room dimensions - w = random.randint(ROOM_MIN_SIZE, ROOM_MAX_SIZE) - h = random.randint(ROOM_MIN_SIZE, ROOM_MAX_SIZE) - - # Random position (ensure room fits in dungeon) - x = random.randint(1, self.width - w - 1) - y = random.randint(1, self.height - h - 1) - - new_room = Rect(x, y, w, h) - - # Check for intersections with existing rooms - if any(new_room.intersects(other) for other in self.rooms): - continue - - # Room is valid - carve it out - self.carve_room(new_room) - - if self.rooms: - # Connect to previous room - self.connect_rooms(self.rooms[-1], new_room) - else: - # First room - player starts here - self.player_start = new_room.center - - self.rooms.append(new_room) - - # Place stairs in the last room - self.place_stairs() - - def get_spawn_positions(self) -> List[Tuple[int, int]]: - """ - Get valid spawn positions for enemies. - Returns positions from all rooms except the first (player start). - """ - positions = [] - - for room in self.rooms[1:]: # Skip first room (player start) - inner_x, inner_y, inner_w, inner_h = room.inner() - - for x in range(inner_x, inner_x + inner_w): - for y in range(inner_y, inner_y + inner_h): - # Don't spawn on stairs - if (x, y) != self.stairs_pos: - positions.append((x, y)) - - return positions - - def get_enemy_spawns(self) -> List[Tuple[str, int, int]]: - """ - Determine which enemies to spawn and where. - Returns list of (enemy_type, x, y) tuples. - """ - spawns = [] - - # Get spawn weights for this level - weights = ENEMY_SPAWN_WEIGHTS.get(self.level, DEFAULT_SPAWN_WEIGHTS) - - # Create weighted list for random selection - enemy_types = [] - for enemy_type, weight in weights: - enemy_types.extend([enemy_type] * weight) - - # Spawn enemies in each room (except the first) - for room in self.rooms[1:]: - num_enemies = random.randint(MIN_ENEMIES_PER_ROOM, MAX_ENEMIES_PER_ROOM) - - # Scale up enemies slightly with dungeon level - num_enemies = min(num_enemies + (self.level - 1) // 2, MAX_ENEMIES_PER_ROOM + 2) - - inner_x, inner_y, inner_w, inner_h = room.inner() - used_positions = set() - - for _ in range(num_enemies): - # Find an unused position - attempts = 0 - while attempts < 20: - x = random.randint(inner_x, inner_x + inner_w - 1) - y = random.randint(inner_y, inner_y + inner_h - 1) - - if (x, y) not in used_positions and (x, y) != self.stairs_pos: - enemy_type = random.choice(enemy_types) - spawns.append((enemy_type, x, y)) - used_positions.add((x, y)) - break - - attempts += 1 - - return spawns - - def apply_to_grid(self, grid) -> None: - """ - Apply the dungeon data to a McRogueFace Grid object. - - Args: - grid: A mcrfpy.Grid object to update - """ - for x in range(self.width): - for y in range(self.height): - tile = self.tiles[x][y] - point = grid.at(x, y) - point.tilesprite = tile.sprite - point.walkable = tile.walkable - point.transparent = tile.transparent - - -def generate_dungeon(level: int = 1) -> Dungeon: - """ - Convenience function to generate a new dungeon. - - Args: - level: The dungeon depth (affects enemy spawns) - - Returns: - A fully generated Dungeon object - """ - dungeon = Dungeon(level=level) - dungeon.generate() - return dungeon diff --git a/docs/templates/complete/entities.py b/docs/templates/complete/entities.py deleted file mode 100644 index bb52323..0000000 --- a/docs/templates/complete/entities.py +++ /dev/null @@ -1,319 +0,0 @@ -""" -entities.py - Player and Enemy Entity Definitions - -Defines the game actors with stats, rendering, and basic behaviors. -Uses composition with McRogueFace Entity objects for rendering. -""" - -from dataclasses import dataclass, field -from typing import Optional, List, Tuple, TYPE_CHECKING -import mcrfpy - -from constants import ( - PLAYER_START_HP, PLAYER_START_ATTACK, PLAYER_START_DEFENSE, - SPRITE_PLAYER, ENEMY_STATS, FOV_RADIUS -) - -if TYPE_CHECKING: - from dungeon import Dungeon - - -@dataclass -class Fighter: - """ - Combat statistics component for entities that can fight. - - Attributes: - hp: Current hit points - max_hp: Maximum hit points - attack: Attack power - defense: Damage reduction - """ - hp: int - max_hp: int - attack: int - defense: int - - @property - def is_alive(self) -> bool: - """Check if this fighter is still alive.""" - return self.hp > 0 - - @property - def hp_percent(self) -> float: - """Return HP as a percentage (0.0 to 1.0).""" - if self.max_hp <= 0: - return 0.0 - return self.hp / self.max_hp - - def heal(self, amount: int) -> int: - """ - Heal by the given amount, up to max_hp. - - Returns: - The actual amount healed. - """ - old_hp = self.hp - self.hp = min(self.hp + amount, self.max_hp) - return self.hp - old_hp - - def take_damage(self, amount: int) -> int: - """ - Take damage, reduced by defense. - - Args: - amount: Raw damage before defense calculation - - Returns: - The actual damage taken after defense. - """ - # Defense reduces damage, minimum 0 - actual_damage = max(0, amount - self.defense) - self.hp = max(0, self.hp - actual_damage) - return actual_damage - - -class Actor: - """ - Base class for all game actors (player and enemies). - - Wraps a McRogueFace Entity and adds game logic. - """ - - def __init__(self, x: int, y: int, sprite: int, name: str, - texture: mcrfpy.Texture, grid: mcrfpy.Grid, - fighter: Fighter): - """ - Create a new actor. - - Args: - x: Starting X position - y: Starting Y position - sprite: Sprite index for rendering - name: Display name of this actor - texture: Texture for the entity sprite - grid: Grid to add the entity to - fighter: Combat statistics - """ - self.name = name - self.fighter = fighter - self.grid = grid - self._x = x - self._y = y - - # Create the McRogueFace entity - self.entity = mcrfpy.Entity((x, y), texture, sprite) - grid.entities.append(self.entity) - - @property - def x(self) -> int: - return self._x - - @x.setter - def x(self, value: int) -> None: - self._x = value - self.entity.pos = (value, self._y) - - @property - def y(self) -> int: - return self._y - - @y.setter - def y(self, value: int) -> None: - self._y = value - self.entity.pos = (self._x, value) - - @property - def pos(self) -> Tuple[int, int]: - return (self._x, self._y) - - @pos.setter - def pos(self, value: Tuple[int, int]) -> None: - self._x, self._y = value - self.entity.pos = value - - @property - def is_alive(self) -> bool: - return self.fighter.is_alive - - def move(self, dx: int, dy: int) -> None: - """Move by the given delta.""" - self.x += dx - self.y += dy - - def move_to(self, x: int, y: int) -> None: - """Move to an absolute position.""" - self.pos = (x, y) - - def distance_to(self, other: 'Actor') -> int: - """Calculate Manhattan distance to another actor.""" - return abs(self.x - other.x) + abs(self.y - other.y) - - def remove(self) -> None: - """Remove this actor's entity from the grid.""" - try: - idx = self.entity.index() - self.grid.entities.remove(idx) - except (ValueError, RuntimeError): - pass # Already removed - - -class Player(Actor): - """ - The player character with additional player-specific functionality. - """ - - def __init__(self, x: int, y: int, texture: mcrfpy.Texture, - grid: mcrfpy.Grid): - fighter = Fighter( - hp=PLAYER_START_HP, - max_hp=PLAYER_START_HP, - attack=PLAYER_START_ATTACK, - defense=PLAYER_START_DEFENSE - ) - super().__init__( - x=x, y=y, - sprite=SPRITE_PLAYER, - name="Player", - texture=texture, - grid=grid, - fighter=fighter - ) - self.xp = 0 - self.level = 1 - self.dungeon_level = 1 - - def gain_xp(self, amount: int) -> bool: - """ - Gain experience points. - - Args: - amount: XP to gain - - Returns: - True if the player leveled up - """ - self.xp += amount - xp_to_level = self.xp_for_next_level - - if self.xp >= xp_to_level: - self.level_up() - return True - return False - - @property - def xp_for_next_level(self) -> int: - """XP required for the next level.""" - return self.level * 100 - - def level_up(self) -> None: - """Level up the player, improving stats.""" - self.level += 1 - - # Improve stats - hp_increase = 5 - attack_increase = 1 - defense_increase = 1 if self.level % 3 == 0 else 0 - - self.fighter.max_hp += hp_increase - self.fighter.hp += hp_increase # Heal the increase amount - self.fighter.attack += attack_increase - self.fighter.defense += defense_increase - - def update_fov(self, dungeon: 'Dungeon') -> None: - """ - Update field of view based on player position. - - Uses entity.update_visibility() for TCOD FOV calculation. - """ - # Update the entity's visibility data - self.entity.update_visibility() - - # Apply FOV to dungeon tiles - for x in range(dungeon.width): - for y in range(dungeon.height): - state = self.entity.at(x, y) - tile = dungeon.get_tile(x, y) - - if tile: - tile.visible = state.visible - if state.visible: - tile.explored = True - - -class Enemy(Actor): - """ - An enemy actor with AI behavior. - """ - - def __init__(self, x: int, y: int, enemy_type: str, - texture: mcrfpy.Texture, grid: mcrfpy.Grid): - """ - Create a new enemy. - - Args: - x: Starting X position - y: Starting Y position - enemy_type: Key into ENEMY_STATS dictionary - texture: Texture for the entity sprite - grid: Grid to add the entity to - """ - stats = ENEMY_STATS.get(enemy_type, ENEMY_STATS['goblin']) - - fighter = Fighter( - hp=stats['hp'], - max_hp=stats['hp'], - attack=stats['attack'], - defense=stats['defense'] - ) - - super().__init__( - x=x, y=y, - sprite=stats['sprite'], - name=stats['name'], - texture=texture, - grid=grid, - fighter=fighter - ) - - self.enemy_type = enemy_type - self.xp_reward = stats['xp'] - - # AI state - self.target: Optional[Actor] = None - self.path: List[Tuple[int, int]] = [] - - -def create_player(x: int, y: int, texture: mcrfpy.Texture, - grid: mcrfpy.Grid) -> Player: - """ - Factory function to create the player. - - Args: - x: Starting X position - y: Starting Y position - texture: Texture for player sprite - grid: Grid to add player to - - Returns: - A new Player instance - """ - return Player(x, y, texture, grid) - - -def create_enemy(x: int, y: int, enemy_type: str, - texture: mcrfpy.Texture, grid: mcrfpy.Grid) -> Enemy: - """ - Factory function to create an enemy. - - Args: - x: Starting X position - y: Starting Y position - enemy_type: Type of enemy ('goblin', 'orc', 'troll') - texture: Texture for enemy sprite - grid: Grid to add enemy to - - Returns: - A new Enemy instance - """ - return Enemy(x, y, enemy_type, texture, grid) diff --git a/docs/templates/complete/game.py b/docs/templates/complete/game.py deleted file mode 100644 index 31dfa1a..0000000 --- a/docs/templates/complete/game.py +++ /dev/null @@ -1,313 +0,0 @@ -""" -game.py - Main Entry Point for McRogueFace Complete Roguelike Template - -This is the main game file that ties everything together: -- Scene setup -- Input handling -- Game loop -- Level transitions - -To run: Copy this template to your McRogueFace scripts/ directory -and rename to game.py (or import from game.py). -""" - -import mcrfpy -from typing import List, Optional - -# Import game modules -from constants import ( - SCREEN_WIDTH, SCREEN_HEIGHT, - GRID_X, GRID_Y, GRID_WIDTH, GRID_HEIGHT, - DUNGEON_WIDTH, DUNGEON_HEIGHT, - TEXTURE_PATH, FONT_PATH, - KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, - KEY_UP_LEFT, KEY_UP_RIGHT, KEY_DOWN_LEFT, KEY_DOWN_RIGHT, - KEY_WAIT, KEY_DESCEND, - MSG_WELCOME, MSG_DESCEND, MSG_BLOCKED, MSG_STAIRS, MSG_DEATH, MSG_NO_STAIRS, - FOV_RADIUS, COLOR_FOG, COLOR_REMEMBERED, COLOR_VISIBLE -) -from dungeon import Dungeon, generate_dungeon -from entities import Player, Enemy, create_player, create_enemy -from turns import TurnManager, GameState -from ui import GameUI, DeathScreen - - -class Game: - """ - Main game class that manages the complete roguelike experience. - """ - - def __init__(self): - """Initialize the game.""" - # Load resources - self.texture = mcrfpy.Texture(TEXTURE_PATH, 16, 16) - self.font = mcrfpy.Font(FONT_PATH) - - # Create scene - mcrfpy.createScene("game") - self.ui_collection = mcrfpy.sceneUI("game") - - # Create grid - self.grid = mcrfpy.Grid( - DUNGEON_WIDTH, DUNGEON_HEIGHT, - self.texture, - GRID_X, GRID_Y, - GRID_WIDTH, GRID_HEIGHT - ) - self.ui_collection.append(self.grid) - - # Game state - self.dungeon: Optional[Dungeon] = None - self.player: Optional[Player] = None - self.enemies: List[Enemy] = [] - self.turn_manager: Optional[TurnManager] = None - self.current_level = 1 - - # UI - self.game_ui = GameUI(self.font) - self.game_ui.add_to_scene(self.ui_collection) - - self.death_screen: Optional[DeathScreen] = None - self.game_over = False - - # Set up input handling - mcrfpy.keypressScene(self.handle_keypress) - - # Start the game - self.new_game() - - # Switch to game scene - mcrfpy.setScene("game") - - def new_game(self) -> None: - """Start a new game from level 1.""" - self.current_level = 1 - self.game_over = False - - # Clear any death screen - if self.death_screen: - self.death_screen.remove_from_scene(self.ui_collection) - self.death_screen = None - - # Generate first level - self.generate_level() - - # Welcome message - self.game_ui.clear_messages() - self.game_ui.add_message(MSG_WELCOME, (255, 255, 100, 255)) - - def generate_level(self) -> None: - """Generate a new dungeon level.""" - # Clear existing entities from grid - while len(self.grid.entities) > 0: - self.grid.entities.remove(0) - - self.enemies.clear() - - # Generate dungeon - self.dungeon = generate_dungeon(self.current_level) - self.dungeon.apply_to_grid(self.grid) - - # Create player at start position - start_x, start_y = self.dungeon.player_start - self.player = create_player(start_x, start_y, self.texture, self.grid) - self.player.dungeon_level = self.current_level - - # Spawn enemies - enemy_spawns = self.dungeon.get_enemy_spawns() - for enemy_type, x, y in enemy_spawns: - enemy = create_enemy(x, y, enemy_type, self.texture, self.grid) - self.enemies.append(enemy) - - # Set up turn manager - self.turn_manager = TurnManager(self.player, self.enemies, self.dungeon) - self.turn_manager.on_message = self.game_ui.add_message - self.turn_manager.on_player_death = self.on_player_death - - # Update FOV - self.update_fov() - - # Center camera on player - self.center_camera() - - # Update UI - self.game_ui.update_level(self.current_level) - self.update_ui() - - def descend(self) -> None: - """Go down to the next dungeon level.""" - # Check if player is on stairs - if self.player.pos != self.dungeon.stairs_pos: - self.game_ui.add_message(MSG_NO_STAIRS, (150, 150, 150, 255)) - return - - self.current_level += 1 - self.game_ui.add_message(MSG_DESCEND % self.current_level, (100, 100, 255, 255)) - - # Keep player stats - old_hp = self.player.fighter.hp - old_max_hp = self.player.fighter.max_hp - old_attack = self.player.fighter.attack - old_defense = self.player.fighter.defense - old_xp = self.player.xp - old_level = self.player.level - - # Generate new level - self.generate_level() - - # Restore player stats - self.player.fighter.hp = old_hp - self.player.fighter.max_hp = old_max_hp - self.player.fighter.attack = old_attack - self.player.fighter.defense = old_defense - self.player.xp = old_xp - self.player.level = old_level - - self.update_ui() - - def update_fov(self) -> None: - """Update field of view and apply to grid tiles.""" - if not self.player or not self.dungeon: - return - - # Use entity's built-in FOV calculation - self.player.entity.update_visibility() - - # Apply visibility to tiles - for x in range(self.dungeon.width): - for y in range(self.dungeon.height): - point = self.grid.at(x, y) - tile = self.dungeon.get_tile(x, y) - - if tile: - state = self.player.entity.at(x, y) - - if state.visible: - # Currently visible - tile.explored = True - tile.visible = True - point.color_overlay = mcrfpy.Color(*COLOR_VISIBLE) - elif tile.explored: - # Explored but not visible - tile.visible = False - point.color_overlay = mcrfpy.Color(*COLOR_REMEMBERED) - else: - # Never seen - point.color_overlay = mcrfpy.Color(*COLOR_FOG) - - def center_camera(self) -> None: - """Center the camera on the player.""" - if self.player: - self.grid.center = (self.player.x, self.player.y) - - def update_ui(self) -> None: - """Update all UI elements.""" - if self.player: - self.game_ui.update_hp( - self.player.fighter.hp, - self.player.fighter.max_hp - ) - - def on_player_death(self) -> None: - """Handle player death.""" - self.game_over = True - self.game_ui.add_message(MSG_DEATH, (255, 0, 0, 255)) - - # Show death screen - self.death_screen = DeathScreen(self.font) - self.death_screen.add_to_scene(self.ui_collection) - - def handle_keypress(self, key: str, state: str) -> None: - """ - Handle keyboard input. - - Args: - key: Key name - state: "start" for key down, "end" for key up - """ - # Only handle key down events - if state != "start": - return - - # Handle restart when dead - if self.game_over: - if key == "R": - self.new_game() - return - - # Handle movement - dx, dy = 0, 0 - - if key in KEY_UP: - dy = -1 - elif key in KEY_DOWN: - dy = 1 - elif key in KEY_LEFT: - dx = -1 - elif key in KEY_RIGHT: - dx = 1 - elif key in KEY_UP_LEFT: - dx, dy = -1, -1 - elif key in KEY_UP_RIGHT: - dx, dy = 1, -1 - elif key in KEY_DOWN_LEFT: - dx, dy = -1, 1 - elif key in KEY_DOWN_RIGHT: - dx, dy = 1, 1 - elif key in KEY_WAIT: - # Skip turn - self.turn_manager.handle_wait() - self.after_turn() - return - elif key in KEY_DESCEND: - # Try to descend - self.descend() - return - elif key == "Escape": - # Quit game - mcrfpy.exit() - return - - # Process movement/attack - if dx != 0 or dy != 0: - if self.turn_manager.handle_player_action(dx, dy): - self.after_turn() - else: - # Movement was blocked - self.game_ui.add_message(MSG_BLOCKED, (150, 150, 150, 255)) - - def after_turn(self) -> None: - """Called after each player turn.""" - # Update FOV - self.update_fov() - - # Center camera - self.center_camera() - - # Update UI - self.update_ui() - - # Check if standing on stairs - if self.player.pos == self.dungeon.stairs_pos: - self.game_ui.add_message(MSG_STAIRS, (100, 255, 100, 255)) - - # Clean up dead enemies - self.enemies = [e for e in self.enemies if e.is_alive] - - -# ============================================================================= -# ENTRY POINT -# ============================================================================= - -# Global game instance -game: Optional[Game] = None - - -def start_game(): - """Start the game.""" - global game - game = Game() - - -# Auto-start when this script is loaded -start_game() diff --git a/docs/templates/complete/turns.py b/docs/templates/complete/turns.py deleted file mode 100644 index 147e9ec..0000000 --- a/docs/templates/complete/turns.py +++ /dev/null @@ -1,232 +0,0 @@ -""" -turns.py - Turn Management System for McRogueFace Roguelike - -Handles the turn-based game flow: player turn, then enemy turns. -""" - -from enum import Enum, auto -from typing import List, Optional, Callable, TYPE_CHECKING - -from entities import Player, Enemy -from combat import try_attack, process_kill, CombatResult -from ai import process_enemy_turns - -if TYPE_CHECKING: - from dungeon import Dungeon - - -class GameState(Enum): - """Current state of the game.""" - PLAYER_TURN = auto() # Waiting for player input - ENEMY_TURN = auto() # Processing enemy actions - PLAYER_DEAD = auto() # Player has died - VICTORY = auto() # Player has won (optional) - LEVEL_TRANSITION = auto() # Moving to next level - - -class TurnManager: - """ - Manages the turn-based game loop. - - The game follows this flow: - 1. Player takes action (move or attack) - 2. If action was valid, enemies take turns - 3. Check for game over conditions - 4. Return to step 1 - """ - - def __init__(self, player: Player, enemies: List[Enemy], dungeon: 'Dungeon'): - """ - Initialize the turn manager. - - Args: - player: The player entity - enemies: List of all enemies - dungeon: The dungeon map - """ - self.player = player - self.enemies = enemies - self.dungeon = dungeon - self.state = GameState.PLAYER_TURN - self.turn_count = 0 - - # Callbacks for game events - self.on_message: Optional[Callable[[str, tuple], None]] = None - self.on_player_death: Optional[Callable[[], None]] = None - self.on_enemy_death: Optional[Callable[[Enemy], None]] = None - self.on_turn_end: Optional[Callable[[int], None]] = None - - def reset(self, player: Player, enemies: List[Enemy], dungeon: 'Dungeon') -> None: - """Reset the turn manager with new game state.""" - self.player = player - self.enemies = enemies - self.dungeon = dungeon - self.state = GameState.PLAYER_TURN - self.turn_count = 0 - - def add_message(self, message: str, color: tuple = (255, 255, 255, 255)) -> None: - """Add a message to the log via callback.""" - if self.on_message: - self.on_message(message, color) - - def handle_player_action(self, dx: int, dy: int) -> bool: - """ - Handle a player movement or attack action. - - Args: - dx: X direction (-1, 0, or 1) - dy: Y direction (-1, 0, or 1) - - Returns: - True if the action consumed a turn, False otherwise - """ - if self.state != GameState.PLAYER_TURN: - return False - - target_x = self.player.x + dx - target_y = self.player.y + dy - - # Check for attack - result = try_attack(self.player, target_x, target_y, self.enemies) - - if result: - # Player attacked something - self.add_message(result.message, result.message_color) - - if result.killed: - # Process kill - xp = process_kill(self.player, result.defender) - self.enemies.remove(result.defender) - - if xp > 0: - self.add_message(f"You gain {xp} XP!", (255, 255, 100, 255)) - - if self.on_enemy_death: - self.on_enemy_death(result.defender) - - # Action consumed a turn - self._end_player_turn() - return True - - # No attack - try to move - if self.dungeon.is_walkable(target_x, target_y): - # Check for enemy blocking - blocked = False - for enemy in self.enemies: - if enemy.is_alive and enemy.x == target_x and enemy.y == target_y: - blocked = True - break - - if not blocked: - self.player.move_to(target_x, target_y) - self._end_player_turn() - return True - - # Movement blocked - return False - - def handle_wait(self) -> bool: - """ - Handle the player choosing to wait (skip turn). - - Returns: - True (always consumes a turn) - """ - if self.state != GameState.PLAYER_TURN: - return False - - self.add_message("You wait...", (150, 150, 150, 255)) - self._end_player_turn() - return True - - def _end_player_turn(self) -> None: - """End the player's turn and process enemy turns.""" - self.state = GameState.ENEMY_TURN - self._process_enemy_turns() - - def _process_enemy_turns(self) -> None: - """Process all enemy turns.""" - # Get combat results from enemy actions - results = process_enemy_turns( - self.enemies, - self.player, - self.dungeon - ) - - # Report results - for result in results: - self.add_message(result.message, result.message_color) - - # Check if player died - if not self.player.is_alive: - self.state = GameState.PLAYER_DEAD - if self.on_player_death: - self.on_player_death() - else: - # End turn - self.turn_count += 1 - self.state = GameState.PLAYER_TURN - - if self.on_turn_end: - self.on_turn_end(self.turn_count) - - def is_player_turn(self) -> bool: - """Check if it's the player's turn.""" - return self.state == GameState.PLAYER_TURN - - def is_game_over(self) -> bool: - """Check if the game is over (player dead).""" - return self.state == GameState.PLAYER_DEAD - - def get_enemy_count(self) -> int: - """Get the number of living enemies.""" - return sum(1 for e in self.enemies if e.is_alive) - - -class ActionResult: - """Result of a player action.""" - - def __init__(self, success: bool, message: str = "", - color: tuple = (255, 255, 255, 255)): - self.success = success - self.message = message - self.color = color - - -def try_move_or_attack(player: Player, dx: int, dy: int, - dungeon: 'Dungeon', enemies: List[Enemy]) -> ActionResult: - """ - Attempt to move or attack in a direction. - - This is a simpler, standalone function for games that don't want - the full TurnManager. - - Args: - player: The player - dx: X direction - dy: Y direction - dungeon: The dungeon map - enemies: List of enemies - - Returns: - ActionResult indicating success and any message - """ - target_x = player.x + dx - target_y = player.y + dy - - # Check for attack - for enemy in enemies: - if enemy.is_alive and enemy.x == target_x and enemy.y == target_y: - result = try_attack(player, target_x, target_y, enemies) - if result: - if result.killed: - process_kill(player, enemy) - enemies.remove(enemy) - return ActionResult(True, result.message, result.message_color) - - # Check for movement - if dungeon.is_walkable(target_x, target_y): - player.move_to(target_x, target_y) - return ActionResult(True) - - return ActionResult(False, "You can't move there!", (150, 150, 150, 255)) diff --git a/docs/templates/complete/ui.py b/docs/templates/complete/ui.py deleted file mode 100644 index b3e16d1..0000000 --- a/docs/templates/complete/ui.py +++ /dev/null @@ -1,330 +0,0 @@ -""" -ui.py - User Interface Components for McRogueFace Roguelike - -Contains the health bar and message log UI elements. -""" - -from typing import List, Tuple, Optional -from dataclasses import dataclass -import mcrfpy - -from constants import ( - HP_BAR_X, HP_BAR_Y, HP_BAR_WIDTH, HP_BAR_HEIGHT, - MSG_LOG_X, MSG_LOG_Y, MSG_LOG_WIDTH, MSG_LOG_HEIGHT, MSG_LOG_MAX_LINES, - LEVEL_DISPLAY_X, LEVEL_DISPLAY_Y, - COLOR_UI_BG, COLOR_UI_BORDER, COLOR_TEXT, - COLOR_HP_BAR_BG, COLOR_HP_BAR_FILL, COLOR_HP_BAR_WARNING, COLOR_HP_BAR_CRITICAL, - COLOR_MSG_DEFAULT -) - - -@dataclass -class Message: - """A message in the message log.""" - text: str - color: Tuple[int, int, int, int] - - -class HealthBar: - """ - Visual health bar displaying player HP. - - Uses nested Frames: an outer background frame and an inner fill frame - that resizes based on HP percentage. - """ - - def __init__(self, x: int = HP_BAR_X, y: int = HP_BAR_Y, - width: int = HP_BAR_WIDTH, height: int = HP_BAR_HEIGHT, - font: mcrfpy.Font = None): - """ - Create a health bar. - - Args: - x: X position - y: Y position - width: Total width of the bar - height: Height of the bar - font: Font for the HP text - """ - self.x = x - self.y = y - self.width = width - self.height = height - self.font = font or mcrfpy.default_font - - # Background frame - self.bg_frame = mcrfpy.Frame(x, y, width, height) - self.bg_frame.fill_color = mcrfpy.Color(*COLOR_HP_BAR_BG) - self.bg_frame.outline = 2 - self.bg_frame.outline_color = mcrfpy.Color(*COLOR_UI_BORDER) - - # Fill frame (inside background) - self.fill_frame = mcrfpy.Frame(x + 2, y + 2, width - 4, height - 4) - self.fill_frame.fill_color = mcrfpy.Color(*COLOR_HP_BAR_FILL) - self.fill_frame.outline = 0 - - # HP text - self.hp_text = mcrfpy.Caption("HP: 0 / 0", self.font, x + 8, y + 4) - self.hp_text.fill_color = mcrfpy.Color(*COLOR_TEXT) - - self._max_fill_width = width - 4 - - def add_to_scene(self, ui: mcrfpy.UICollection) -> None: - """Add all health bar components to a scene.""" - ui.append(self.bg_frame) - ui.append(self.fill_frame) - ui.append(self.hp_text) - - def update(self, current_hp: int, max_hp: int) -> None: - """ - Update the health bar display. - - Args: - current_hp: Current hit points - max_hp: Maximum hit points - """ - # Calculate fill percentage - if max_hp <= 0: - percent = 0.0 - else: - percent = max(0.0, min(1.0, current_hp / max_hp)) - - # Update fill bar width - self.fill_frame.w = int(self._max_fill_width * percent) - - # Update color based on HP percentage - if percent > 0.6: - color = COLOR_HP_BAR_FILL - elif percent > 0.3: - color = COLOR_HP_BAR_WARNING - else: - color = COLOR_HP_BAR_CRITICAL - - self.fill_frame.fill_color = mcrfpy.Color(*color) - - # Update text - self.hp_text.text = f"HP: {current_hp} / {max_hp}" - - -class MessageLog: - """ - Scrolling message log displaying game events. - - Uses a Frame container with Caption children for each line. - """ - - def __init__(self, x: int = MSG_LOG_X, y: int = MSG_LOG_Y, - width: int = MSG_LOG_WIDTH, height: int = MSG_LOG_HEIGHT, - max_lines: int = MSG_LOG_MAX_LINES, - font: mcrfpy.Font = None): - """ - Create a message log. - - Args: - x: X position - y: Y position - width: Width of the log - height: Height of the log - max_lines: Maximum number of visible lines - font: Font for the messages - """ - self.x = x - self.y = y - self.width = width - self.height = height - self.max_lines = max_lines - self.font = font or mcrfpy.default_font - - # Container frame - self.frame = mcrfpy.Frame(x, y, width, height) - self.frame.fill_color = mcrfpy.Color(*COLOR_UI_BG) - self.frame.outline = 1 - self.frame.outline_color = mcrfpy.Color(*COLOR_UI_BORDER) - - # Message storage - self.messages: List[Message] = [] - self.captions: List[mcrfpy.Caption] = [] - - # Line height (approximate based on font) - self.line_height = 18 - - # Create caption objects for each line - self._init_captions() - - def _init_captions(self) -> None: - """Initialize caption objects for message display.""" - for i in range(self.max_lines): - caption = mcrfpy.Caption( - "", - self.font, - self.x + 5, - self.y + 5 + i * self.line_height - ) - caption.fill_color = mcrfpy.Color(*COLOR_MSG_DEFAULT) - self.captions.append(caption) - - def add_to_scene(self, ui: mcrfpy.UICollection) -> None: - """Add the message log to a scene.""" - ui.append(self.frame) - for caption in self.captions: - ui.append(caption) - - def add_message(self, text: str, - color: Tuple[int, int, int, int] = COLOR_MSG_DEFAULT) -> None: - """ - Add a message to the log. - - Args: - text: Message text - color: Text color as (R, G, B, A) - """ - self.messages.append(Message(text, color)) - - # Trim old messages - if len(self.messages) > 100: - self.messages = self.messages[-100:] - - # Update display - self._update_display() - - def _update_display(self) -> None: - """Update the displayed messages.""" - # Get the most recent messages - recent = self.messages[-self.max_lines:] - - for i, caption in enumerate(self.captions): - if i < len(recent): - msg = recent[i] - caption.text = msg.text - caption.fill_color = mcrfpy.Color(*msg.color) - else: - caption.text = "" - - def clear(self) -> None: - """Clear all messages.""" - self.messages.clear() - self._update_display() - - -class LevelDisplay: - """Simple display showing current dungeon level.""" - - def __init__(self, x: int = LEVEL_DISPLAY_X, y: int = LEVEL_DISPLAY_Y, - font: mcrfpy.Font = None): - """ - Create a level display. - - Args: - x: X position - y: Y position - font: Font for the text - """ - self.font = font or mcrfpy.default_font - - self.caption = mcrfpy.Caption("Level: 1", self.font, x, y) - self.caption.fill_color = mcrfpy.Color(*COLOR_TEXT) - - def add_to_scene(self, ui: mcrfpy.UICollection) -> None: - """Add to a scene.""" - ui.append(self.caption) - - def update(self, level: int) -> None: - """Update the displayed level.""" - self.caption.text = f"Dungeon Level: {level}" - - -class GameUI: - """ - Container for all UI elements. - - Provides a single point of access for updating the entire UI. - """ - - def __init__(self, font: mcrfpy.Font = None): - """ - Create the game UI. - - Args: - font: Font for all UI elements - """ - self.font = font or mcrfpy.default_font - - # Create UI components - self.health_bar = HealthBar(font=self.font) - self.message_log = MessageLog(font=self.font) - self.level_display = LevelDisplay(font=self.font) - - def add_to_scene(self, ui: mcrfpy.UICollection) -> None: - """Add all UI elements to a scene.""" - self.health_bar.add_to_scene(ui) - self.message_log.add_to_scene(ui) - self.level_display.add_to_scene(ui) - - def update_hp(self, current_hp: int, max_hp: int) -> None: - """Update the health bar.""" - self.health_bar.update(current_hp, max_hp) - - def add_message(self, text: str, - color: Tuple[int, int, int, int] = COLOR_MSG_DEFAULT) -> None: - """Add a message to the log.""" - self.message_log.add_message(text, color) - - def update_level(self, level: int) -> None: - """Update the dungeon level display.""" - self.level_display.update(level) - - def clear_messages(self) -> None: - """Clear the message log.""" - self.message_log.clear() - - -class DeathScreen: - """Game over screen shown when player dies.""" - - def __init__(self, font: mcrfpy.Font = None): - """ - Create the death screen. - - Args: - font: Font for text - """ - self.font = font or mcrfpy.default_font - self.elements: List = [] - - # Semi-transparent overlay - self.overlay = mcrfpy.Frame(0, 0, 1024, 768) - self.overlay.fill_color = mcrfpy.Color(0, 0, 0, 180) - self.elements.append(self.overlay) - - # Death message - self.death_text = mcrfpy.Caption( - "YOU HAVE DIED", - self.font, - 362, 300 - ) - self.death_text.fill_color = mcrfpy.Color(255, 0, 0, 255) - self.death_text.outline = 2 - self.death_text.outline_color = mcrfpy.Color(0, 0, 0, 255) - self.elements.append(self.death_text) - - # Restart prompt - self.restart_text = mcrfpy.Caption( - "Press R to restart", - self.font, - 400, 400 - ) - self.restart_text.fill_color = mcrfpy.Color(200, 200, 200, 255) - self.elements.append(self.restart_text) - - def add_to_scene(self, ui: mcrfpy.UICollection) -> None: - """Add death screen elements to a scene.""" - for element in self.elements: - ui.append(element) - - def remove_from_scene(self, ui: mcrfpy.UICollection) -> None: - """Remove death screen elements from a scene.""" - for element in self.elements: - try: - ui.remove(element) - except (ValueError, RuntimeError): - pass diff --git a/docs/templates/minimal/game.py b/docs/templates/minimal/game.py deleted file mode 100644 index bedafad..0000000 --- a/docs/templates/minimal/game.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -McRogueFace Minimal Template -============================ - -A starting point for simple roguelike prototypes. - -This template demonstrates: -- Scene object pattern (preferred OOP approach) -- Grid-based movement with boundary checking -- Keyboard input handling -- Entity positioning on a grid - -Usage: - Place this file in your McRogueFace scripts directory and run McRogueFace. - Use arrow keys to move the @ symbol. Press Escape to exit. -""" - -import mcrfpy - -# ============================================================================= -# CONSTANTS -# ============================================================================= - -# Grid dimensions (in tiles) -GRID_WIDTH: int = 20 -GRID_HEIGHT: int = 15 - -# Tile size in pixels (must match your sprite sheet) -TILE_SIZE: int = 16 - -# CP437 sprite indices (standard roguelike character mapping) -# In CP437, character codes map to sprite indices: '@' = 64, '.' = 46, etc. -SPRITE_PLAYER: int = 64 # '@' symbol -SPRITE_FLOOR: int = 46 # '.' symbol - -# Colors (RGBA tuples) -COLOR_BACKGROUND: tuple[int, int, int] = (20, 20, 30) - -# ============================================================================= -# GAME STATE -# ============================================================================= - -# Player position in grid coordinates -player_x: int = GRID_WIDTH // 2 -player_y: int = GRID_HEIGHT // 2 - -# Reference to player entity (set during setup) -player_entity: mcrfpy.Entity = None - -# ============================================================================= -# MOVEMENT LOGIC -# ============================================================================= - -def try_move(dx: int, dy: int) -> bool: - """ - Attempt to move the player by (dx, dy) tiles. - - Args: - dx: Horizontal movement (-1 = left, +1 = right, 0 = none) - dy: Vertical movement (-1 = up, +1 = down, 0 = none) - - Returns: - True if movement succeeded, False if blocked by boundary - """ - global player_x, player_y - - new_x = player_x + dx - new_y = player_y + dy - - # Boundary checking: ensure player stays within grid - if 0 <= new_x < GRID_WIDTH and 0 <= new_y < GRID_HEIGHT: - player_x = new_x - player_y = new_y - - # Update the entity's position on the grid - player_entity.x = player_x - player_entity.y = player_y - return True - - return False - -# ============================================================================= -# INPUT HANDLING -# ============================================================================= - -def handle_keypress(key: str, action: str) -> None: - """ - Handle keyboard input for the game scene. - - Args: - key: The key that was pressed (e.g., "Up", "Down", "Escape", "a", "W") - action: Either "start" (key pressed) or "end" (key released) - - Note: - We only process on "start" to avoid double-triggering on key release. - """ - if action != "start": - return - - # Movement keys (both arrow keys and WASD) - if key == "Up" or key == "W" or key == "w": - try_move(0, -1) - elif key == "Down" or key == "S" or key == "s": - try_move(0, 1) - elif key == "Left" or key == "A" or key == "a": - try_move(-1, 0) - elif key == "Right" or key == "D" or key == "d": - try_move(1, 0) - - # Exit on Escape - elif key == "Escape": - mcrfpy.exit() - -# ============================================================================= -# SCENE SETUP -# ============================================================================= - -def setup_game() -> mcrfpy.Scene: - """ - Create and configure the game scene. - - Returns: - The configured Scene object, ready to be activated. - """ - global player_entity - - # Create the scene using the OOP pattern (preferred over createScene) - scene = mcrfpy.Scene("game") - - # Load the sprite sheet texture - # Adjust the path and tile size to match your assets - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", TILE_SIZE, TILE_SIZE) - - # Create the game grid - # Grid(pos, size, grid_size) where: - # pos = pixel position on screen - # size = pixel dimensions of the grid display - # grid_size = number of tiles (columns, rows) - grid = mcrfpy.Grid( - pos=(32, 32), - grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture - ) - grid.fill_color = mcrfpy.Color(*COLOR_BACKGROUND) - - # Fill the grid with floor tiles - for x in range(GRID_WIDTH): - for y in range(GRID_HEIGHT): - point = grid.at(x, y) - point.tilesprite = SPRITE_FLOOR - point.walkable = True - point.transparent = True - - # Create the player entity - player_entity = mcrfpy.Entity( - pos=(player_x, player_y), - texture=texture, - sprite_index=SPRITE_PLAYER - ) - grid.entities.append(player_entity) - - # Add the grid to the scene's UI - scene.children.append(grid) - - # Set up keyboard input handler for this scene - scene.on_key = handle_keypress - - return scene - -# ============================================================================= -# MAIN ENTRY POINT -# ============================================================================= - -# Create and activate the game scene -game_scene = setup_game() -game_scene.activate() diff --git a/docs/templates/roguelike/constants.py b/docs/templates/roguelike/constants.py deleted file mode 100644 index 81413e9..0000000 --- a/docs/templates/roguelike/constants.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -constants.py - Roguelike Template Constants - -This module defines all the constants used throughout the roguelike template, -including sprite indices for CP437 tileset, colors for FOV system, and -game configuration values. - -CP437 is the classic IBM PC character set commonly used in traditional roguelikes. -The sprite indices correspond to ASCII character codes in a CP437 tileset. -""" - -import mcrfpy - -# ============================================================================= -# SPRITE INDICES (CP437 Character Codes) -# ============================================================================= -# These indices correspond to characters in a CP437-style tileset. -# The default McRogueFace tileset uses 16x16 sprites arranged in a grid. - -# Terrain sprites -SPRITE_FLOOR = 46 # '.' - Standard floor tile -SPRITE_WALL = 35 # '#' - Wall/obstacle tile -SPRITE_DOOR_CLOSED = 43 # '+' - Closed door -SPRITE_DOOR_OPEN = 47 # '/' - Open door -SPRITE_STAIRS_DOWN = 62 # '>' - Stairs going down -SPRITE_STAIRS_UP = 60 # '<' - Stairs going up - -# Player sprite -SPRITE_PLAYER = 64 # '@' - The classic roguelike player symbol - -# Enemy sprites -SPRITE_ORC = 111 # 'o' - Orc enemy -SPRITE_TROLL = 84 # 'T' - Troll enemy -SPRITE_GOBLIN = 103 # 'g' - Goblin enemy -SPRITE_RAT = 114 # 'r' - Giant rat -SPRITE_SNAKE = 115 # 's' - Snake -SPRITE_ZOMBIE = 90 # 'Z' - Zombie - -# Item sprites -SPRITE_POTION = 33 # '!' - Potion -SPRITE_SCROLL = 63 # '?' - Scroll -SPRITE_GOLD = 36 # '$' - Gold/treasure -SPRITE_WEAPON = 41 # ')' - Weapon -SPRITE_ARMOR = 91 # '[' - Armor -SPRITE_RING = 61 # '=' - Ring - -# ============================================================================= -# FOV/VISIBILITY COLORS -# ============================================================================= -# These colors are applied as overlays to grid tiles to create the fog of war -# effect. The alpha channel determines how much of the original tile shows through. - -# Fully visible - no overlay (alpha = 0 means completely transparent overlay) -COLOR_VISIBLE = mcrfpy.Color(0, 0, 0, 0) - -# Previously explored but not currently visible - dim blue-gray overlay -# This creates the "memory" effect where you can see the map layout -# but not current enemy positions -COLOR_EXPLORED = mcrfpy.Color(50, 50, 80, 180) - -# Never seen - completely black (alpha = 255 means fully opaque) -COLOR_UNKNOWN = mcrfpy.Color(0, 0, 0, 255) - -# ============================================================================= -# TILE COLORS -# ============================================================================= -# Base colors for different tile types (applied to the tile's color property) - -COLOR_FLOOR = mcrfpy.Color(50, 50, 50) # Dark gray floor -COLOR_WALL = mcrfpy.Color(100, 100, 100) # Lighter gray walls -COLOR_FLOOR_LIT = mcrfpy.Color(100, 90, 70) # Warm lit floor -COLOR_WALL_LIT = mcrfpy.Color(130, 110, 80) # Warm lit walls - -# ============================================================================= -# ENTITY COLORS -# ============================================================================= -# Colors applied to entity sprites - -COLOR_PLAYER = mcrfpy.Color(255, 255, 255) # White player -COLOR_ORC = mcrfpy.Color(63, 127, 63) # Green orc -COLOR_TROLL = mcrfpy.Color(0, 127, 0) # Darker green troll -COLOR_GOBLIN = mcrfpy.Color(127, 127, 0) # Yellow-green goblin - -# ============================================================================= -# GAME CONFIGURATION -# ============================================================================= - -# Map dimensions (in tiles) -MAP_WIDTH = 80 -MAP_HEIGHT = 45 - -# Room generation parameters -ROOM_MIN_SIZE = 6 # Minimum room dimension -ROOM_MAX_SIZE = 12 # Maximum room dimension -MAX_ROOMS = 30 # Maximum number of rooms to generate - -# FOV settings -FOV_RADIUS = 8 # How far the player can see - -# Display settings -GRID_PIXEL_WIDTH = 1024 # Grid display width in pixels -GRID_PIXEL_HEIGHT = 768 # Grid display height in pixels - -# Sprite size (should match your tileset) -SPRITE_WIDTH = 16 -SPRITE_HEIGHT = 16 - -# ============================================================================= -# ENEMY DEFINITIONS -# ============================================================================= -# Dictionary of enemy types with their properties for easy spawning - -ENEMY_TYPES = { - "orc": { - "sprite": SPRITE_ORC, - "color": COLOR_ORC, - "name": "Orc", - "hp": 10, - "power": 3, - "defense": 0, - }, - "troll": { - "sprite": SPRITE_TROLL, - "color": COLOR_TROLL, - "name": "Troll", - "hp": 16, - "power": 4, - "defense": 1, - }, - "goblin": { - "sprite": SPRITE_GOBLIN, - "color": COLOR_GOBLIN, - "name": "Goblin", - "hp": 6, - "power": 2, - "defense": 0, - }, -} diff --git a/docs/templates/roguelike/dungeon.py b/docs/templates/roguelike/dungeon.py deleted file mode 100644 index 5b4d788..0000000 --- a/docs/templates/roguelike/dungeon.py +++ /dev/null @@ -1,340 +0,0 @@ -""" -dungeon.py - Procedural Dungeon Generation - -This module provides classic roguelike dungeon generation using the -"rooms and corridors" algorithm: - -1. Generate random non-overlapping rectangular rooms -2. Connect rooms with L-shaped corridors -3. Mark tiles as walkable/transparent based on terrain type - -The algorithm is simple but effective, producing dungeons similar to -the original Rogue game. -""" - -from __future__ import annotations -import random -from typing import Iterator, Tuple, List, TYPE_CHECKING - -if TYPE_CHECKING: - import mcrfpy - -from constants import ( - MAP_WIDTH, MAP_HEIGHT, - ROOM_MIN_SIZE, ROOM_MAX_SIZE, MAX_ROOMS, - SPRITE_FLOOR, SPRITE_WALL, - COLOR_FLOOR, COLOR_WALL, -) - - -class RectangularRoom: - """ - A rectangular room in the dungeon. - - This class represents a single room and provides utilities for - working with room geometry. Rooms are defined by their top-left - corner (x1, y1) and bottom-right corner (x2, y2). - - Attributes: - x1, y1: Top-left corner coordinates - x2, y2: Bottom-right corner coordinates - """ - - def __init__(self, x: int, y: int, width: int, height: int) -> None: - """ - Create a new rectangular room. - - Args: - x: X coordinate of the top-left corner - y: Y coordinate of the top-left corner - width: Width of the room in tiles - height: Height of the room in tiles - """ - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self) -> Tuple[int, int]: - """ - Return the center coordinates of the room. - - This is useful for connecting rooms with corridors and - for placing the player in the starting room. - - Returns: - Tuple of (center_x, center_y) - """ - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self) -> Tuple[slice, slice]: - """ - Return the inner area of the room as a pair of slices. - - The inner area excludes the walls (1 tile border), giving - the floor area where entities can be placed. - - Returns: - Tuple of (x_slice, y_slice) for array indexing - """ - # Add 1 to exclude the walls on all sides - return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2) - - def intersects(self, other: RectangularRoom) -> bool: - """ - Check if this room overlaps with another room. - - Used during generation to ensure rooms don't overlap. - - Args: - other: Another RectangularRoom to check against - - Returns: - True if the rooms overlap, False otherwise - """ - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - - def inner_tiles(self) -> Iterator[Tuple[int, int]]: - """ - Iterate over all floor tile coordinates in the room. - - Yields coordinates for the interior of the room (excluding walls). - - Yields: - Tuples of (x, y) coordinates - """ - for x in range(self.x1 + 1, self.x2): - for y in range(self.y1 + 1, self.y2): - yield x, y - - -def tunnel_between( - start: Tuple[int, int], - end: Tuple[int, int] -) -> Iterator[Tuple[int, int]]: - """ - Generate an L-shaped tunnel between two points. - - The tunnel goes horizontally first, then vertically (or vice versa, - chosen randomly). This creates the classic roguelike corridor style. - - Args: - start: Starting (x, y) coordinates - end: Ending (x, y) coordinates - - Yields: - Tuples of (x, y) coordinates for each tile in the tunnel - """ - x1, y1 = start - x2, y2 = end - - # Randomly choose whether to go horizontal-first or vertical-first - if random.random() < 0.5: - # Horizontal first, then vertical - corner_x, corner_y = x2, y1 - else: - # Vertical first, then horizontal - corner_x, corner_y = x1, y2 - - # Generate the horizontal segment - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - - # Generate the vertical segment - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - - # Generate to the endpoint (if needed) - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - - -def generate_dungeon( - max_rooms: int = MAX_ROOMS, - room_min_size: int = ROOM_MIN_SIZE, - room_max_size: int = ROOM_MAX_SIZE, - map_width: int = MAP_WIDTH, - map_height: int = MAP_HEIGHT, -) -> List[RectangularRoom]: - """ - Generate a dungeon using the rooms-and-corridors algorithm. - - This function creates a list of non-overlapping rooms. The actual - tile data should be applied to a Grid using populate_grid(). - - Algorithm: - 1. Try to place MAX_ROOMS rooms randomly - 2. Reject rooms that overlap existing rooms - 3. Connect each new room to the previous room with a corridor - - Args: - max_rooms: Maximum number of rooms to generate - room_min_size: Minimum room dimension - room_max_size: Maximum room dimension - map_width: Width of the dungeon in tiles - map_height: Height of the dungeon in tiles - - Returns: - List of RectangularRoom objects representing the dungeon layout - """ - rooms: List[RectangularRoom] = [] - - for _ in range(max_rooms): - # Random room dimensions - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - # Random position (ensuring room fits within map bounds) - x = random.randint(0, map_width - room_width - 1) - y = random.randint(0, map_height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - # Check if this room overlaps with any existing room - if any(new_room.intersects(other) for other in rooms): - continue # Skip this room, try again - - # Room is valid, add it - rooms.append(new_room) - - return rooms - - -def populate_grid(grid: mcrfpy.Grid, rooms: List[RectangularRoom]) -> None: - """ - Apply dungeon layout to a McRogueFace Grid. - - This function: - 1. Fills the entire grid with walls - 2. Carves out floor tiles for each room - 3. Carves corridors connecting adjacent rooms - 4. Sets walkable/transparent flags appropriately - - Args: - grid: The McRogueFace Grid to populate - rooms: List of RectangularRoom objects from generate_dungeon() - """ - grid_width, grid_height = grid.grid_size - - # Step 1: Fill entire map with walls - for x in range(grid_width): - for y in range(grid_height): - point = grid.at(x, y) - point.tilesprite = SPRITE_WALL - point.walkable = False - point.transparent = False - point.color = COLOR_WALL - - # Step 2: Carve out rooms - for room in rooms: - for x, y in room.inner_tiles(): - # Bounds check (room might extend past grid) - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - point.tilesprite = SPRITE_FLOOR - point.walkable = True - point.transparent = True - point.color = COLOR_FLOOR - - # Step 3: Carve corridors between adjacent rooms - for i in range(1, len(rooms)): - # Connect each room to the previous room - start = rooms[i - 1].center - end = rooms[i].center - - for x, y in tunnel_between(start, end): - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - point.tilesprite = SPRITE_FLOOR - point.walkable = True - point.transparent = True - point.color = COLOR_FLOOR - - -def get_random_floor_position( - grid: mcrfpy.Grid, - rooms: List[RectangularRoom], - exclude_first_room: bool = False -) -> Tuple[int, int]: - """ - Get a random walkable floor position for entity placement. - - This is useful for placing enemies, items, or other entities - in valid floor locations. - - Args: - grid: The populated Grid to search - rooms: List of rooms (used for faster random selection) - exclude_first_room: If True, won't return positions from the - first room (where the player usually starts) - - Returns: - Tuple of (x, y) coordinates of a walkable floor tile - """ - available_rooms = rooms[1:] if exclude_first_room and len(rooms) > 1 else rooms - - if not available_rooms: - # Fallback: find any walkable tile - grid_width, grid_height = grid.grid_size - walkable_tiles = [] - for x in range(grid_width): - for y in range(grid_height): - if grid.at(x, y).walkable: - walkable_tiles.append((x, y)) - return random.choice(walkable_tiles) if walkable_tiles else (1, 1) - - # Pick a random room and a random position within it - room = random.choice(available_rooms) - floor_tiles = list(room.inner_tiles()) - return random.choice(floor_tiles) - - -def get_spawn_positions( - rooms: List[RectangularRoom], - count: int, - exclude_first_room: bool = True -) -> List[Tuple[int, int]]: - """ - Get multiple spawn positions for enemies. - - Distributes enemies across different rooms for better gameplay. - - Args: - rooms: List of rooms from dungeon generation - count: Number of positions to generate - exclude_first_room: If True, won't spawn in the player's starting room - - Returns: - List of (x, y) coordinate tuples - """ - available_rooms = rooms[1:] if exclude_first_room and len(rooms) > 1 else rooms - - if not available_rooms: - return [] - - positions = [] - for i in range(count): - # Cycle through rooms to distribute enemies - room = available_rooms[i % len(available_rooms)] - floor_tiles = list(room.inner_tiles()) - - # Try to avoid placing on the same tile - available_tiles = [t for t in floor_tiles if t not in positions] - if available_tiles: - positions.append(random.choice(available_tiles)) - elif floor_tiles: - positions.append(random.choice(floor_tiles)) - - return positions diff --git a/docs/templates/roguelike/entities.py b/docs/templates/roguelike/entities.py deleted file mode 100644 index e6c1c23..0000000 --- a/docs/templates/roguelike/entities.py +++ /dev/null @@ -1,364 +0,0 @@ -""" -entities.py - Entity Management for Roguelike Template - -This module provides entity creation and management utilities for the -roguelike template. Entities in McRogueFace are game objects that exist -on a Grid, such as the player, enemies, items, and NPCs. - -The module includes: -- Entity factory functions for creating common entity types -- Helper functions for entity management -- Simple data containers for entity stats (for future expansion) - -Note: McRogueFace entities are simple position + sprite objects. For -complex game logic like AI, combat, and inventory, you'll want to wrap -them in Python classes that reference the underlying Entity. -""" - -from __future__ import annotations -from typing import Tuple, Optional, List, Dict, Any, TYPE_CHECKING -from dataclasses import dataclass - -if TYPE_CHECKING: - import mcrfpy - -from constants import ( - SPRITE_PLAYER, SPRITE_ORC, SPRITE_TROLL, SPRITE_GOBLIN, - COLOR_PLAYER, COLOR_ORC, COLOR_TROLL, COLOR_GOBLIN, - ENEMY_TYPES, -) - - -@dataclass -class EntityStats: - """ - Optional stats container for game entities. - - This dataclass can be used to track stats for entities that need them. - Attach it to your entity wrapper class for combat, leveling, etc. - - Attributes: - hp: Current hit points - max_hp: Maximum hit points - power: Attack power - defense: Damage reduction - name: Display name for the entity - """ - hp: int = 10 - max_hp: int = 10 - power: int = 3 - defense: int = 0 - name: str = "Unknown" - - @property - def is_alive(self) -> bool: - """Check if the entity is still alive.""" - return self.hp > 0 - - def take_damage(self, amount: int) -> int: - """ - Apply damage, accounting for defense. - - Args: - amount: Raw damage amount - - Returns: - Actual damage dealt after defense - """ - actual_damage = max(0, amount - self.defense) - self.hp = max(0, self.hp - actual_damage) - return actual_damage - - def heal(self, amount: int) -> int: - """ - Heal the entity. - - Args: - amount: Amount to heal - - Returns: - Actual amount healed (may be less if near max HP) - """ - old_hp = self.hp - self.hp = min(self.max_hp, self.hp + amount) - return self.hp - old_hp - - -def create_player( - grid: mcrfpy.Grid, - texture: mcrfpy.Texture, - x: int, - y: int -) -> mcrfpy.Entity: - """ - Create and place the player entity on the grid. - - The player uses the classic '@' symbol (sprite index 64 in CP437). - - Args: - grid: The Grid to place the player on - texture: The texture/tileset to use - x: Starting X position - y: Starting Y position - - Returns: - The created player Entity - """ - import mcrfpy - - player = mcrfpy.Entity( - pos=(x, y), - texture=texture, - sprite_index=SPRITE_PLAYER - ) - grid.entities.append(player) - - return player - - -def create_enemy( - grid: mcrfpy.Grid, - texture: mcrfpy.Texture, - x: int, - y: int, - enemy_type: str = "orc" -) -> Tuple[mcrfpy.Entity, EntityStats]: - """ - Create an enemy entity with associated stats. - - Enemy types are defined in constants.py. Currently available: - - "orc": Standard enemy, balanced stats - - "troll": Tough enemy, high HP and power - - "goblin": Weak enemy, low stats - - Args: - grid: The Grid to place the enemy on - texture: The texture/tileset to use - x: X position - y: Y position - enemy_type: Key from ENEMY_TYPES dict - - Returns: - Tuple of (Entity, EntityStats) for the created enemy - """ - import mcrfpy - - # Get enemy definition, default to orc if not found - enemy_def = ENEMY_TYPES.get(enemy_type, ENEMY_TYPES["orc"]) - - entity = mcrfpy.Entity( - pos=(x, y), - texture=texture, - sprite_index=enemy_def["sprite"] - ) - grid.entities.append(entity) - - stats = EntityStats( - hp=enemy_def["hp"], - max_hp=enemy_def["hp"], - power=enemy_def["power"], - defense=enemy_def["defense"], - name=enemy_def["name"] - ) - - return entity, stats - - -def create_enemies_in_rooms( - grid: mcrfpy.Grid, - texture: mcrfpy.Texture, - rooms: list, - enemies_per_room: int = 2, - skip_first_room: bool = True -) -> List[Tuple[mcrfpy.Entity, EntityStats]]: - """ - Populate dungeon rooms with enemies. - - This helper function places random enemies throughout the dungeon, - typically skipping the first room (where the player starts). - - Args: - grid: The Grid to populate - texture: The texture/tileset to use - rooms: List of RectangularRoom objects from dungeon generation - enemies_per_room: Maximum enemies to spawn per room - skip_first_room: If True, don't spawn enemies in the first room - - Returns: - List of (Entity, EntityStats) tuples for all created enemies - """ - import random - - enemies = [] - enemy_type_keys = list(ENEMY_TYPES.keys()) - - # Iterate through rooms, optionally skipping the first - rooms_to_populate = rooms[1:] if skip_first_room else rooms - - for room in rooms_to_populate: - # Random number of enemies (0 to enemies_per_room) - num_enemies = random.randint(0, enemies_per_room) - - # Get available floor tiles in this room - floor_tiles = list(room.inner_tiles()) - - for _ in range(num_enemies): - if not floor_tiles: - break - - # Pick a random position and remove it from available - pos = random.choice(floor_tiles) - floor_tiles.remove(pos) - - # Pick a random enemy type (weighted toward weaker enemies) - if random.random() < 0.8: - enemy_type = "orc" # 80% orcs - else: - enemy_type = "troll" # 20% trolls - - x, y = pos - entity, stats = create_enemy(grid, texture, x, y, enemy_type) - enemies.append((entity, stats)) - - return enemies - - -def get_blocking_entity_at( - entities: List[mcrfpy.Entity], - x: int, - y: int -) -> Optional[mcrfpy.Entity]: - """ - Check if there's a blocking entity at the given position. - - Useful for collision detection - checks if an entity exists at - the target position before moving there. - - Args: - entities: List of entities to check - x: X coordinate to check - y: Y coordinate to check - - Returns: - The entity at that position, or None if empty - """ - for entity in entities: - if entity.pos[0] == x and entity.pos[1] == y: - return entity - return None - - -def move_entity( - entity: mcrfpy.Entity, - grid: mcrfpy.Grid, - dx: int, - dy: int, - entities: List[mcrfpy.Entity] = None -) -> bool: - """ - Attempt to move an entity by a delta. - - Checks for: - - Grid bounds - - Walkable terrain - - Other blocking entities (if entities list provided) - - Args: - entity: The entity to move - grid: The grid for terrain collision - dx: Delta X (-1, 0, or 1 typically) - dy: Delta Y (-1, 0, or 1 typically) - entities: Optional list of entities to check for collision - - Returns: - True if movement succeeded, False otherwise - """ - dest_x = entity.pos[0] + dx - dest_y = entity.pos[1] + dy - - # Check grid bounds - grid_width, grid_height = grid.grid_size - if not (0 <= dest_x < grid_width and 0 <= dest_y < grid_height): - return False - - # Check if tile is walkable - if not grid.at(dest_x, dest_y).walkable: - return False - - # Check for blocking entities - if entities and get_blocking_entity_at(entities, dest_x, dest_y): - return False - - # Move is valid - entity.pos = (dest_x, dest_y) - return True - - -def distance_between( - entity1: mcrfpy.Entity, - entity2: mcrfpy.Entity -) -> float: - """ - Calculate the Chebyshev distance between two entities. - - Chebyshev distance (also called chessboard distance) counts - diagonal moves as 1, which is standard for roguelikes. - - Args: - entity1: First entity - entity2: Second entity - - Returns: - Distance in tiles (diagonal = 1) - """ - dx = abs(entity1.pos[0] - entity2.pos[0]) - dy = abs(entity1.pos[1] - entity2.pos[1]) - return max(dx, dy) - - -def entities_in_radius( - center: mcrfpy.Entity, - entities: List[mcrfpy.Entity], - radius: float -) -> List[mcrfpy.Entity]: - """ - Find all entities within a given radius of a center entity. - - Uses Chebyshev distance for roguelike-style radius. - - Args: - center: The entity to search around - entities: List of entities to check - radius: Maximum distance in tiles - - Returns: - List of entities within the radius (excluding center) - """ - nearby = [] - for entity in entities: - if entity is not center: - if distance_between(center, entity) <= radius: - nearby.append(entity) - return nearby - - -def remove_entity( - entity: mcrfpy.Entity, - grid: mcrfpy.Grid -) -> bool: - """ - Remove an entity from a grid. - - Args: - entity: The entity to remove - grid: The grid containing the entity - - Returns: - True if removal succeeded, False otherwise - """ - try: - idx = entity.index() - grid.entities.remove(idx) - return True - except (ValueError, AttributeError): - return False diff --git a/docs/templates/roguelike/game.py b/docs/templates/roguelike/game.py deleted file mode 100644 index 2eca8be..0000000 --- a/docs/templates/roguelike/game.py +++ /dev/null @@ -1,290 +0,0 @@ -""" -game.py - Roguelike Template Main Entry Point - -A minimal but complete roguelike starter using McRogueFace. - -This template demonstrates: -- Scene and grid setup -- Procedural dungeon generation -- Player entity with keyboard movement -- Enemy entities (static, no AI) -- Field of view using TCOD via Entity.update_visibility() -- FOV visualization with grid color overlays - -Run with: ./mcrogueface - -Controls: -- Arrow keys / WASD: Move player -- Escape: Quit game - -The template is designed to be extended. Good next steps: -- Add enemy AI (chase player, pathfinding) -- Implement combat system -- Add items and inventory -- Add multiple dungeon levels -""" - -import mcrfpy -from typing import List, Tuple - -# Import our template modules -from constants import ( - MAP_WIDTH, MAP_HEIGHT, - SPRITE_WIDTH, SPRITE_HEIGHT, - FOV_RADIUS, - COLOR_VISIBLE, COLOR_EXPLORED, COLOR_UNKNOWN, - SPRITE_PLAYER, -) -from dungeon import generate_dungeon, populate_grid, RectangularRoom -from entities import ( - create_player, - create_enemies_in_rooms, - move_entity, - EntityStats, -) - - -# ============================================================================= -# GAME STATE -# ============================================================================= -# Global game state - in a larger game, you'd use a proper state management -# system, but for a template this keeps things simple and visible. - -class GameState: - """Container for all game state.""" - - def __init__(self): - # Core game objects (set during initialization) - self.grid: mcrfpy.Grid = None - self.player: mcrfpy.Entity = None - self.rooms: List[RectangularRoom] = [] - self.enemies: List[Tuple[mcrfpy.Entity, EntityStats]] = [] - - # Texture reference - self.texture: mcrfpy.Texture = None - - -# Global game state instance -game = GameState() - - -# ============================================================================= -# FOV (FIELD OF VIEW) SYSTEM -# ============================================================================= - -def update_fov() -> None: - """ - Update the field of view based on player position. - - This function: - 1. Calls update_visibility() on the player entity to compute FOV using TCOD - 2. Applies color overlays to tiles based on visibility state - - The FOV creates the classic roguelike effect where: - - Visible tiles are fully bright (no overlay) - - Previously seen tiles are dimmed (remembered layout) - - Never-seen tiles are completely dark - - TCOD handles the actual FOV computation based on the grid's - walkable and transparent flags set during dungeon generation. - """ - if not game.player or not game.grid: - return - - # Tell McRogueFace/TCOD to recompute visibility from player position - game.player.update_visibility() - - grid_width, grid_height = game.grid.grid_size - - # Apply visibility colors to each tile - for x in range(grid_width): - for y in range(grid_height): - point = game.grid.at(x, y) - - # Get the player's visibility state for this tile - state = game.player.at(x, y) - - if state.visible: - # Currently visible - no overlay (full brightness) - point.color_overlay = COLOR_VISIBLE - elif state.discovered: - # Previously seen - dimmed overlay (memory) - point.color_overlay = COLOR_EXPLORED - else: - # Never seen - completely dark - point.color_overlay = COLOR_UNKNOWN - - -# ============================================================================= -# INPUT HANDLING -# ============================================================================= - -def handle_keys(key: str, state: str) -> None: - """ - Handle keyboard input for player movement and game controls. - - This is the main input handler registered with McRogueFace. - It processes key events and updates game state accordingly. - - Args: - key: The key that was pressed (e.g., "W", "Up", "Escape") - state: Either "start" (key pressed) or "end" (key released) - """ - # Only process key press events, not releases - if state != "start": - return - - # Movement deltas: (dx, dy) - movement = { - # Arrow keys - "Up": (0, -1), - "Down": (0, 1), - "Left": (-1, 0), - "Right": (1, 0), - # WASD keys - "W": (0, -1), - "S": (0, 1), - "A": (-1, 0), - "D": (1, 0), - # Numpad (for diagonal movement if desired) - "Numpad8": (0, -1), - "Numpad2": (0, 1), - "Numpad4": (-1, 0), - "Numpad6": (1, 0), - "Numpad7": (-1, -1), - "Numpad9": (1, -1), - "Numpad1": (-1, 1), - "Numpad3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - - # Get list of all entity objects for collision checking - all_entities = [e for e, _ in game.enemies] - - # Attempt to move the player - if move_entity(game.player, game.grid, dx, dy, all_entities): - # Movement succeeded - update FOV - update_fov() - - # Center camera on player - px, py = game.player.pos - game.grid.center = (px, py) - - elif key == "Escape": - # Quit the game - mcrfpy.exit() - - -# ============================================================================= -# GAME INITIALIZATION -# ============================================================================= - -def initialize_game() -> None: - """ - Set up the game world. - - This function: - 1. Creates the scene and loads resources - 2. Generates the dungeon layout - 3. Creates and places all entities - 4. Initializes the FOV system - 5. Sets up input handling - """ - # Create the game scene - mcrfpy.createScene("game") - ui = mcrfpy.sceneUI("game") - - # Load the tileset texture - # The default McRogueFace texture works great for roguelikes - game.texture = mcrfpy.Texture( - "assets/kenney_tinydungeon.png", - SPRITE_WIDTH, - SPRITE_HEIGHT - ) - - # Create the grid (tile-based game world) - # Using keyword arguments for clarity - this is the preferred style - game.grid = mcrfpy.Grid( - pos=(0, 0), # Screen position in pixels - size=(1024, 768), # Display size in pixels - grid_size=(MAP_WIDTH, MAP_HEIGHT), # Map size in tiles - texture=game.texture - ) - ui.append(game.grid) - - # Generate dungeon layout - game.rooms = generate_dungeon() - - # Apply dungeon to grid (sets tiles, walkable flags, etc.) - populate_grid(game.grid, game.rooms) - - # Place player in the center of the first room - if game.rooms: - start_x, start_y = game.rooms[0].center - else: - # Fallback if no rooms generated - start_x, start_y = MAP_WIDTH // 2, MAP_HEIGHT // 2 - - game.player = create_player( - grid=game.grid, - texture=game.texture, - x=start_x, - y=start_y - ) - - # Center camera on player - game.grid.center = (start_x, start_y) - - # Spawn enemies in other rooms - game.enemies = create_enemies_in_rooms( - grid=game.grid, - texture=game.texture, - rooms=game.rooms, - enemies_per_room=2, - skip_first_room=True - ) - - # Initial FOV calculation - update_fov() - - # Register input handler - mcrfpy.keypressScene(handle_keys) - - # Switch to game scene - mcrfpy.setScene("game") - - -# ============================================================================= -# MAIN ENTRY POINT -# ============================================================================= - -def main() -> None: - """ - Main entry point for the roguelike template. - - This function is called when the script starts. It initializes - the game and McRogueFace handles the game loop automatically. - """ - initialize_game() - - # Display welcome message - print("=" * 50) - print(" ROGUELIKE TEMPLATE") - print("=" * 50) - print("Controls:") - print(" Arrow keys / WASD - Move") - print(" Escape - Quit") - print() - print(f"Dungeon generated with {len(game.rooms)} rooms") - print(f"Enemies spawned: {len(game.enemies)}") - print("=" * 50) - - -# Run the game -if __name__ == "__main__": - main() -else: - # McRogueFace runs game.py directly, not as __main__ - main() diff --git a/docs/tutorials/part_01_grid_movement/part_01_grid_movement.py b/docs/tutorials/part_01_grid_movement/part_01_grid_movement.py index ef5ee40..53c236e 100644 --- a/docs/tutorials/part_01_grid_movement/part_01_grid_movement.py +++ b/docs/tutorials/part_01_grid_movement/part_01_grid_movement.py @@ -29,11 +29,13 @@ texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) grid = mcrfpy.Grid( pos=(100, 80), # Position on screen (pixels) size=(640, 480), # Display size (pixels) - zoom = 2.0, grid_size=(GRID_WIDTH, GRID_HEIGHT), # Size in tiles texture=texture ) +# Set the zoom level for better visibility +grid.zoom = 2.0 + # Fill the grid with floor tiles for y in range(GRID_HEIGHT): for x in range(GRID_WIDTH): @@ -116,4 +118,4 @@ scene.on_key = handle_keys # Activate the scene scene.activate() -print("Part 1 loaded! Use WASD or Arrow keys to move.") +print("Part 1 loaded! Use WASD or Arrow keys to move.") \ No newline at end of file diff --git a/docs/tutorials/part_02_tiles_collision/part_02_tiles_collision.py b/docs/tutorials/part_02_tiles_collision/part_02_tiles_collision.py index e8a09d3..66feaa4 100644 --- a/docs/tutorials/part_02_tiles_collision/part_02_tiles_collision.py +++ b/docs/tutorials/part_02_tiles_collision/part_02_tiles_collision.py @@ -102,9 +102,9 @@ grid = mcrfpy.Grid( pos=(80, 100), size=(720, 480), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.5 + texture=texture ) +grid.zoom = 1.5 # Build the map create_map(grid) diff --git a/docs/tutorials/part_03_dungeon_generation/part_03_dungeon_generation.py b/docs/tutorials/part_03_dungeon_generation/part_03_dungeon_generation.py index d3e3874..632ad2f 100644 --- a/docs/tutorials/part_03_dungeon_generation/part_03_dungeon_generation.py +++ b/docs/tutorials/part_03_dungeon_generation/part_03_dungeon_generation.py @@ -250,9 +250,9 @@ grid = mcrfpy.Grid( pos=(50, 80), size=(800, 560), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 # Generate the dungeon and get player start position player_start_x, player_start_y = generate_dungeon(grid) diff --git a/docs/tutorials/part_04_fov/part_04_fov.py b/docs/tutorials/part_04_fov/part_04_fov.py index c7c89d7..97d9187 100644 --- a/docs/tutorials/part_04_fov/part_04_fov.py +++ b/docs/tutorials/part_04_fov/part_04_fov.py @@ -236,9 +236,9 @@ grid = mcrfpy.Grid( pos=(50, 80), size=(800, 560), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 # Generate the dungeon player_start_x, player_start_y = generate_dungeon(grid) diff --git a/docs/tutorials/part_05_enemies/part_05_enemies.py b/docs/tutorials/part_05_enemies/part_05_enemies.py index 51d0299..9abfc42 100644 --- a/docs/tutorials/part_05_enemies/part_05_enemies.py +++ b/docs/tutorials/part_05_enemies/part_05_enemies.py @@ -448,9 +448,9 @@ grid = mcrfpy.Grid( pos=(50, 80), size=(800, 560), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 # Generate the dungeon (without player first to get starting position) fill_with_walls(grid) diff --git a/docs/tutorials/part_06_combat/part_06_combat.py b/docs/tutorials/part_06_combat/part_06_combat.py index fd6a62f..59d6ab2 100644 --- a/docs/tutorials/part_06_combat/part_06_combat.py +++ b/docs/tutorials/part_06_combat/part_06_combat.py @@ -685,9 +685,9 @@ grid = mcrfpy.Grid( pos=(50, 80), size=(800, 480), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 # Generate initial dungeon structure fill_with_walls(grid) diff --git a/docs/tutorials/part_07_ui/part_07_ui.py b/docs/tutorials/part_07_ui/part_07_ui.py index 0ec3da3..459adee 100644 --- a/docs/tutorials/part_07_ui/part_07_ui.py +++ b/docs/tutorials/part_07_ui/part_07_ui.py @@ -785,9 +785,9 @@ grid = mcrfpy.Grid( pos=(20, GAME_AREA_Y), size=(800, GAME_AREA_HEIGHT - 20), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 # Generate initial dungeon structure fill_with_walls(grid) diff --git a/docs/tutorials/part_08_items/part_08_items.py b/docs/tutorials/part_08_items/part_08_items.py index d3829d9..e8f271e 100644 --- a/docs/tutorials/part_08_items/part_08_items.py +++ b/docs/tutorials/part_08_items/part_08_items.py @@ -1006,9 +1006,9 @@ grid = mcrfpy.Grid( pos=(20, GAME_AREA_Y), size=(700, GAME_AREA_HEIGHT - 20), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 # Generate initial dungeon fill_with_walls(grid) diff --git a/docs/tutorials/part_09_ranged/part_09_ranged.py b/docs/tutorials/part_09_ranged/part_09_ranged.py index 5605629..f855a75 100644 --- a/docs/tutorials/part_09_ranged/part_09_ranged.py +++ b/docs/tutorials/part_09_ranged/part_09_ranged.py @@ -1089,9 +1089,9 @@ grid = mcrfpy.Grid( pos=(20, GAME_AREA_Y), size=(700, GAME_AREA_HEIGHT - 20), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 # Generate initial dungeon fill_with_walls(grid) diff --git a/docs/tutorials/part_10_save_load/part_10_save_load.py b/docs/tutorials/part_10_save_load/part_10_save_load.py index b0a911d..a0c6380 100644 --- a/docs/tutorials/part_10_save_load/part_10_save_load.py +++ b/docs/tutorials/part_10_save_load/part_10_save_load.py @@ -1385,9 +1385,9 @@ grid = mcrfpy.Grid( pos=(20, GAME_AREA_Y), size=(700, GAME_AREA_HEIGHT - 20), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 # Add FOV layer fov_layer = grid.add_layer("color", z_index=-1) diff --git a/docs/tutorials/part_11_levels/part_11_levels.py b/docs/tutorials/part_11_levels/part_11_levels.py index da2573c..ee31c04 100644 --- a/docs/tutorials/part_11_levels/part_11_levels.py +++ b/docs/tutorials/part_11_levels/part_11_levels.py @@ -1583,9 +1583,9 @@ grid = mcrfpy.Grid( pos=(20, GAME_AREA_Y), size=(700, GAME_AREA_HEIGHT - 20), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 fov_layer = grid.add_layer("color", z_index=-1) for y in range(GRID_HEIGHT): diff --git a/docs/tutorials/part_12_experience/part_12_experience.py b/docs/tutorials/part_12_experience/part_12_experience.py index 6a3034e..ec59009 100644 --- a/docs/tutorials/part_12_experience/part_12_experience.py +++ b/docs/tutorials/part_12_experience/part_12_experience.py @@ -1694,9 +1694,9 @@ grid = mcrfpy.Grid( pos=(20, GAME_AREA_Y), size=(700, GAME_AREA_HEIGHT - 20), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 fov_layer = grid.add_layer("color", z_index=-1) for y in range(GRID_HEIGHT): diff --git a/docs/tutorials/part_13_equipment/part_13_equipment.py b/docs/tutorials/part_13_equipment/part_13_equipment.py index bca3f3d..725f20b 100644 --- a/docs/tutorials/part_13_equipment/part_13_equipment.py +++ b/docs/tutorials/part_13_equipment/part_13_equipment.py @@ -1662,9 +1662,9 @@ grid = mcrfpy.Grid( pos=(20, GAME_AREA_Y), size=(700, GAME_AREA_HEIGHT - 20), grid_size=(GRID_WIDTH, GRID_HEIGHT), - texture=texture, - zoom=1.0 + texture=texture ) +grid.zoom = 1.0 fov_layer = grid.add_layer("color", z_index=-1) for y in range(GRID_HEIGHT): diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index dcbcb74..e5aec91 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -9,7 +9,6 @@ #include "PyWindow.h" #include "PySceneObject.h" #include "PyFOV.h" -#include "PyTransition.h" #include "PySound.h" #include "PyMusic.h" #include "PyKeyboard.h" @@ -52,14 +51,6 @@ static PyObject* mcrfpy_module_getattr(PyObject* self, PyObject* args) return McRFPy_API::api_get_scenes(); } - if (strcmp(name, "default_transition") == 0) { - return PyTransition::to_python(PyTransition::default_transition); - } - - if (strcmp(name, "default_transition_duration") == 0) { - return PyFloat_FromDouble(PyTransition::default_duration); - } - // Attribute not found - raise AttributeError PyErr_Format(PyExc_AttributeError, "module 'mcrfpy' has no attribute '%s'", name); return NULL; @@ -80,33 +71,6 @@ static int mcrfpy_module_setattro(PyObject* self, PyObject* name, PyObject* valu return -1; } - if (strcmp(name_str, "default_transition") == 0) { - TransitionType trans; - if (!PyTransition::from_arg(value, &trans, nullptr)) { - return -1; - } - PyTransition::default_transition = trans; - return 0; - } - - if (strcmp(name_str, "default_transition_duration") == 0) { - double duration; - if (PyFloat_Check(value)) { - duration = PyFloat_AsDouble(value); - } else if (PyLong_Check(value)) { - duration = PyLong_AsDouble(value); - } else { - PyErr_SetString(PyExc_TypeError, "default_transition_duration must be a number"); - return -1; - } - if (duration < 0.0) { - PyErr_SetString(PyExc_ValueError, "default_transition_duration must be non-negative"); - return -1; - } - PyTransition::default_duration = static_cast(duration); - return 0; - } - // For other attributes, use default module setattr return PyObject_GenericSetAttr(self, name, value); } @@ -138,6 +102,54 @@ static PyTypeObject McRFPyModuleType = { static PyMethodDef mcrfpyMethods[] = { + {"sceneUI", McRFPy_API::_sceneUI, METH_VARARGS, + MCRF_FUNCTION(sceneUI, + MCRF_SIG("(scene: str = None)", "list"), + MCRF_DESC("Get all UI elements for a scene."), + MCRF_ARGS_START + MCRF_ARG("scene", "Scene name. If None, uses current scene") + MCRF_RETURNS("list: All UI elements (Frame, Caption, Sprite, Grid) in the scene") + MCRF_RAISES("KeyError", "If the specified scene doesn't exist") + )}, + + {"currentScene", McRFPy_API::_currentScene, METH_NOARGS, + MCRF_FUNCTION(currentScene, + MCRF_SIG("()", "str"), + MCRF_DESC("Get the name of the currently active scene."), + MCRF_RETURNS("str: Name of the current scene") + )}, + {"setScene", McRFPy_API::_setScene, METH_VARARGS, + MCRF_FUNCTION(setScene, + MCRF_SIG("(scene: str, transition: str = None, duration: float = 0.0)", "None"), + MCRF_DESC("Switch to a different scene with optional transition effect."), + MCRF_ARGS_START + MCRF_ARG("scene", "Name of the scene to switch to") + MCRF_ARG("transition", "Transition type ('fade', 'slide_left', 'slide_right', 'slide_up', 'slide_down')") + MCRF_ARG("duration", "Transition duration in seconds (default: 0.0 for instant)") + MCRF_RETURNS("None") + MCRF_RAISES("KeyError", "If the scene doesn't exist") + MCRF_RAISES("ValueError", "If the transition type is invalid") + )}, + {"createScene", McRFPy_API::_createScene, METH_VARARGS, + MCRF_FUNCTION(createScene, + MCRF_SIG("(name: str)", "None"), + MCRF_DESC("Create a new empty scene."), + MCRF_ARGS_START + MCRF_ARG("name", "Unique name for the new scene") + MCRF_RETURNS("None") + MCRF_RAISES("ValueError", "If a scene with this name already exists") + MCRF_NOTE("The scene is created but not made active. Use setScene() to switch to it.") + )}, + {"keypressScene", McRFPy_API::_keypressScene, METH_VARARGS, + MCRF_FUNCTION(keypressScene, + MCRF_SIG("(handler: callable)", "None"), + MCRF_DESC("Set the keyboard event handler for the current scene."), + MCRF_ARGS_START + MCRF_ARG("handler", "Callable that receives (key_name: str, is_pressed: bool)") + MCRF_RETURNS("None") + MCRF_NOTE("Example: def on_key(key, pressed): if key == 'A' and pressed: print('A key pressed') mcrfpy.keypressScene(on_key)") + )}, + {"setTimer", McRFPy_API::_setTimer, METH_VARARGS, MCRF_FUNCTION(setTimer, MCRF_SIG("(name: str, handler: callable, interval: int)", "None"), @@ -264,7 +276,7 @@ static PyModuleDef mcrfpyModule = { PyDoc_STR("McRogueFace Python API\n\n" "Core game engine interface for creating roguelike games with Python.\n\n" "This module provides:\n" - "- Scene management via Scene objects (mcrfpy.Scene, mcrfpy.current_scene)\n" + "- Scene management (createScene, setScene, currentScene)\n" "- UI components (Frame, Caption, Sprite, Grid)\n" "- Entity system for game objects\n" "- Audio playback (sound effects and music)\n" @@ -274,17 +286,14 @@ static PyModuleDef mcrfpyModule = { "Example:\n" " import mcrfpy\n" " \n" - " # Create and activate a scene\n" - " scene = mcrfpy.Scene('game')\n" - " scene.activate()\n" + " # Create a new scene\n" + " mcrfpy.createScene('game')\n" + " mcrfpy.setScene('game')\n" " \n" " # Add UI elements\n" " frame = mcrfpy.Frame(10, 10, 200, 100)\n" " caption = mcrfpy.Caption('Hello World', 50, 50)\n" - " scene.children.extend([frame, caption])\n" - " \n" - " # Set keyboard handler\n" - " scene.on_key = lambda key, action: print(f'{key} {action}')\n"), + " mcrfpy.sceneUI().extend([frame, caption])\n"), -1, /* m_size - Setting m_size to -1 means that the module does not support sub-interpreters, because it has global state. */ mcrfpyMethods, /* m_methods */ NULL, /* m_slots - An array of slot definitions ... When using single-phase initialization, m_slots must be NULL. */ @@ -428,16 +437,7 @@ PyObject* PyInit_mcrfpy() // Fallback to integer if enum failed PyModule_AddIntConstant(m, "default_fov", FOV_BASIC); } - - // Add Transition enum class (uses Python's IntEnum) - PyObject* transition_class = PyTransition::create_enum_class(m); - if (!transition_class) { - // If enum creation fails, continue without it (non-fatal) - PyErr_Clear(); - } - // Note: default_transition and default_transition_duration are handled via - // mcrfpy_module_getattr/setattro using PyTransition::default_transition/default_duration - + // Add automation submodule PyObject* automation_module = McRFPy_Automation::init_automation_module(); if (automation_module != NULL) { @@ -837,7 +837,7 @@ PyObject* McRFPy_API::_camFollow(PyObject* self, PyObject* args) { } */ -// Internal use - called by PySceneClass_get_children() +//McRFPy_API::_sceneUI PyObject* McRFPy_API::_sceneUI(PyObject* self, PyObject* args) { using namespace mcrfpydef; const char *scene_cstr; @@ -858,7 +858,10 @@ PyObject* McRFPy_API::_sceneUI(PyObject* self, PyObject* args) { return (PyObject*)o; } -// Internal use - called by PySceneObject::activate() +PyObject* McRFPy_API::_currentScene(PyObject* self, PyObject* args) { + return Py_BuildValue("s", game->scene.c_str()); +} + PyObject* McRFPy_API::_setScene(PyObject* self, PyObject* args) { const char* newscene; const char* transition_str = nullptr; @@ -883,6 +886,38 @@ PyObject* McRFPy_API::_setScene(PyObject* self, PyObject* args) { return Py_None; } +PyObject* McRFPy_API::_createScene(PyObject* self, PyObject* args) { + const char* newscene; + if (!PyArg_ParseTuple(args, "s", &newscene)) return NULL; + game->createScene(newscene); + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* McRFPy_API::_keypressScene(PyObject* self, PyObject* args) { + PyObject* callable; + if (!PyArg_ParseTuple(args, "O", &callable)) return NULL; + + // Validate that the argument is callable + if (!PyCallable_Check(callable)) { + PyErr_SetString(PyExc_TypeError, "keypressScene() argument must be callable"); + return NULL; + } + + /* + if (game->currentScene()->key_callable != NULL and game->currentScene()->key_callable != Py_None) + { + Py_DECREF(game->currentScene()->key_callable); + } + Py_INCREF(callable); + game->currentScene()->key_callable = callable; + Py_INCREF(Py_None); + */ + game->currentScene()->key_callable = std::make_unique(callable); + Py_INCREF(Py_None); + return Py_None; +} + PyObject* McRFPy_API::_setTimer(PyObject* self, PyObject* args) { // TODO - compare with UIDrawable mouse & Scene Keyboard methods - inconsistent responsibility for incref/decref around mcrogueface const char* name; PyObject* callable; diff --git a/src/McRFPy_API.h b/src/McRFPy_API.h index b04d893..e6dfd95 100644 --- a/src/McRFPy_API.h +++ b/src/McRFPy_API.h @@ -37,11 +37,13 @@ public: static void REPL_device(FILE * fp, const char *filename); static void REPL(); - // Internal - used by PySceneClass_get_children() static PyObject* _sceneUI(PyObject*, PyObject*); - // Internal - used by PySceneObject::activate() + // scene control static PyObject* _setScene(PyObject*, PyObject*); + static PyObject* _currentScene(PyObject*, PyObject*); + static PyObject* _createScene(PyObject*, PyObject*); + static PyObject* _keypressScene(PyObject*, PyObject*); // timer control static PyObject* _setTimer(PyObject*, PyObject*); diff --git a/src/PySceneObject.cpp b/src/PySceneObject.cpp index 3ccfc1c..04711f0 100644 --- a/src/PySceneObject.cpp +++ b/src/PySceneObject.cpp @@ -3,7 +3,6 @@ #include "GameEngine.h" #include "McRFPy_API.h" #include "McRFPy_Doc.h" -#include "PyTransition.h" #include // Static map to store Python scene objects by name @@ -76,54 +75,13 @@ PyObject* PySceneClass::__repr__(PySceneObject* self) return PyUnicode_FromFormat("", self->name.c_str()); } -PyObject* PySceneClass::activate(PySceneObject* self, PyObject* args, PyObject* kwds) +PyObject* PySceneClass::activate(PySceneObject* self, PyObject* args) { - static const char* keywords[] = {"transition", "duration", nullptr}; - PyObject* transition_arg = nullptr; - PyObject* duration_arg = nullptr; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", const_cast(keywords), - &transition_arg, &duration_arg)) { - return NULL; - } - - // Get transition type (use default if not provided) - TransitionType transition_type; - bool trans_was_none = false; - if (transition_arg) { - if (!PyTransition::from_arg(transition_arg, &transition_type, &trans_was_none)) { - return NULL; - } - } else { - transition_type = PyTransition::default_transition; - } - - // Get duration (use default if not provided) - float duration; - if (duration_arg && duration_arg != Py_None) { - if (PyFloat_Check(duration_arg)) { - duration = static_cast(PyFloat_AsDouble(duration_arg)); - } else if (PyLong_Check(duration_arg)) { - duration = static_cast(PyLong_AsLong(duration_arg)); - } else { - PyErr_SetString(PyExc_TypeError, "duration must be a number"); - return NULL; - } - } else { - duration = PyTransition::default_duration; - } - - // Build transition string for _setScene (or call game->changeScene directly) - GameEngine* game = McRFPy_API::game; - if (!game) { - PyErr_SetString(PyExc_RuntimeError, "No game engine"); - return NULL; - } - - // Call game->changeScene directly with proper transition - game->changeScene(self->name, transition_type, duration); - - Py_RETURN_NONE; + // Call the static method from McRFPy_API + PyObject* py_args = Py_BuildValue("(s)", self->name.c_str()); + PyObject* result = McRFPy_API::_setScene(NULL, py_args); + Py_DECREF(py_args); + return result; } // children property getter (replaces get_ui method) @@ -497,15 +455,12 @@ PyGetSetDef PySceneClass::getsetters[] = { // Methods PyMethodDef PySceneClass::methods[] = { - {"activate", (PyCFunction)activate, METH_VARARGS | METH_KEYWORDS, + {"activate", (PyCFunction)activate, METH_NOARGS, MCRF_METHOD(SceneClass, activate, - MCRF_SIG("(transition: Transition = None, duration: float = None)", "None"), - MCRF_DESC("Make this the active scene with optional transition effect."), - MCRF_ARGS_START - MCRF_ARG("transition", "Transition type (mcrfpy.Transition enum). Defaults to mcrfpy.default_transition") - MCRF_ARG("duration", "Transition duration in seconds. Defaults to mcrfpy.default_transition_duration") + MCRF_SIG("()", "None"), + MCRF_DESC("Make this the active scene."), MCRF_RETURNS("None") - MCRF_NOTE("Deactivates the current scene and activates this one. Lifecycle callbacks (on_exit, on_enter) are triggered.") + MCRF_NOTE("Deactivates the current scene and activates this one. Scene transitions and lifecycle callbacks are triggered.") )}, {"register_keyboard", (PyCFunction)register_keyboard, METH_VARARGS, MCRF_METHOD(SceneClass, register_keyboard, @@ -620,8 +575,9 @@ int McRFPy_API::api_set_current_scene(PyObject* value) return -1; } - // Use changeScene with default transition settings - game->changeScene(scene_name, PyTransition::default_transition, PyTransition::default_duration); + std::string old_scene = game->scene; + game->scene = scene_name; + McRFPy_API::triggerSceneChange(old_scene, scene_name); return 0; } diff --git a/src/PySceneObject.h b/src/PySceneObject.h index fdba708..22ef8ab 100644 --- a/src/PySceneObject.h +++ b/src/PySceneObject.h @@ -26,7 +26,7 @@ public: static PyObject* __repr__(PySceneObject* self); // Scene methods - static PyObject* activate(PySceneObject* self, PyObject* args, PyObject* kwds); + static PyObject* activate(PySceneObject* self, PyObject* args); static PyObject* register_keyboard(PySceneObject* self, PyObject* args); // Properties diff --git a/src/PyTransition.cpp b/src/PyTransition.cpp deleted file mode 100644 index 7a328e1..0000000 --- a/src/PyTransition.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "PyTransition.h" -#include "McRFPy_API.h" - -// Static storage -PyObject* PyTransition::transition_enum_class = nullptr; -TransitionType PyTransition::default_transition = TransitionType::None; -float PyTransition::default_duration = 1.0f; - -PyObject* PyTransition::create_enum_class(PyObject* module) { - // Import IntEnum from enum module - PyObject* enum_module = PyImport_ImportModule("enum"); - if (!enum_module) { - return NULL; - } - - PyObject* int_enum = PyObject_GetAttrString(enum_module, "IntEnum"); - Py_DECREF(enum_module); - if (!int_enum) { - return NULL; - } - - // Create dict of enum members - PyObject* members = PyDict_New(); - if (!members) { - Py_DECREF(int_enum); - return NULL; - } - - // Add all Transition type members - // Values match the C++ TransitionType enum - struct { - const char* name; - int value; - } transition_members[] = { - {"NONE", static_cast(TransitionType::None)}, - {"FADE", static_cast(TransitionType::Fade)}, - {"SLIDE_LEFT", static_cast(TransitionType::SlideLeft)}, - {"SLIDE_RIGHT", static_cast(TransitionType::SlideRight)}, - {"SLIDE_UP", static_cast(TransitionType::SlideUp)}, - {"SLIDE_DOWN", static_cast(TransitionType::SlideDown)}, - }; - - for (const auto& m : transition_members) { - PyObject* value = PyLong_FromLong(m.value); - if (!value) { - Py_DECREF(members); - Py_DECREF(int_enum); - return NULL; - } - if (PyDict_SetItemString(members, m.name, value) < 0) { - Py_DECREF(value); - Py_DECREF(members); - Py_DECREF(int_enum); - return NULL; - } - Py_DECREF(value); - } - - // Call IntEnum("Transition", members) to create the enum class - PyObject* name = PyUnicode_FromString("Transition"); - if (!name) { - Py_DECREF(members); - Py_DECREF(int_enum); - return NULL; - } - - // IntEnum(name, members) using functional API - PyObject* args = PyTuple_Pack(2, name, members); - Py_DECREF(name); - Py_DECREF(members); - if (!args) { - Py_DECREF(int_enum); - return NULL; - } - - PyObject* transition_class = PyObject_Call(int_enum, args, NULL); - Py_DECREF(args); - Py_DECREF(int_enum); - - if (!transition_class) { - return NULL; - } - - // Cache the reference for fast type checking - transition_enum_class = transition_class; - Py_INCREF(transition_enum_class); - - // Add to module - if (PyModule_AddObject(module, "Transition", transition_class) < 0) { - Py_DECREF(transition_class); - transition_enum_class = nullptr; - return NULL; - } - - return transition_class; -} - -int PyTransition::from_arg(PyObject* arg, TransitionType* out_type, bool* was_none) { - if (was_none) *was_none = false; - - // Accept None -> caller should use default - if (arg == Py_None || arg == NULL) { - if (was_none) *was_none = true; - *out_type = default_transition; - return 1; - } - - // Accept Transition enum member (check if it's an instance of our enum) - if (transition_enum_class && PyObject_IsInstance(arg, transition_enum_class)) { - // IntEnum members have a 'value' attribute - PyObject* value = PyObject_GetAttrString(arg, "value"); - if (!value) { - return 0; - } - long val = PyLong_AsLong(value); - Py_DECREF(value); - if (val == -1 && PyErr_Occurred()) { - return 0; - } - *out_type = static_cast(val); - return 1; - } - - // Accept int (for flexibility) - if (PyLong_Check(arg)) { - long val = PyLong_AsLong(arg); - if (val == -1 && PyErr_Occurred()) { - return 0; - } - if (val < 0 || val > static_cast(TransitionType::SlideDown)) { - PyErr_Format(PyExc_ValueError, - "Invalid Transition value: %ld. Must be 0-5 or use mcrfpy.Transition enum.", - val); - return 0; - } - *out_type = static_cast(val); - return 1; - } - - PyErr_SetString(PyExc_TypeError, - "transition must be mcrfpy.Transition enum member, int, or None"); - return 0; -} - -PyObject* PyTransition::to_python(TransitionType type) { - if (!transition_enum_class) { - PyErr_SetString(PyExc_RuntimeError, "Transition enum not initialized"); - return NULL; - } - - // Get the enum member by value - PyObject* value = PyLong_FromLong(static_cast(type)); - if (!value) return NULL; - - PyObject* result = PyObject_CallFunctionObjArgs(transition_enum_class, value, NULL); - Py_DECREF(value); - return result; -} diff --git a/src/PyTransition.h b/src/PyTransition.h deleted file mode 100644 index 2218305..0000000 --- a/src/PyTransition.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "Common.h" -#include "Python.h" -#include "SceneTransition.h" - -// Module-level Transition enum class (created at runtime using Python's IntEnum) -// Stored as a module attribute: mcrfpy.Transition - -class PyTransition { -public: - // Create the Transition enum class and add to module - // Returns the enum class (new reference), or NULL on error - static PyObject* create_enum_class(PyObject* module); - - // Helper to extract transition type from Python arg (accepts Transition enum, int, or None) - // Returns 1 on success, 0 on error (with exception set) - // If arg is None, sets *out_type to the default and sets *was_none to true - static int from_arg(PyObject* arg, TransitionType* out_type, bool* was_none = nullptr); - - // Convert TransitionType to Python enum member - static PyObject* to_python(TransitionType type); - - // Cached reference to the Transition enum class for fast type checking - static PyObject* transition_enum_class; - - // Module-level defaults - static TransitionType default_transition; - static float default_duration; -}; diff --git a/src/scripts/example_text_widgets.py b/src/scripts/example_text_widgets.py index a8c165e..913e913 100644 --- a/src/scripts/example_text_widgets.py +++ b/src/scripts/example_text_widgets.py @@ -28,10 +28,10 @@ focus_mgr.register(name_input) # Create demo scene import mcrfpy -text_example = mcrfpy.Scene("text_example") -text_example.activate() +mcrfpy.createScene("text_example") +mcrfpy.setScene("text_example") -ui = text_example.children +ui = mcrfpy.sceneUI("text_example") # Add to scene #ui.append(name_input) # don't do this, only the internal Frame class can go into the UI; have to manage derived objects "carefully" (McRogueFace alpha anti-feature) name_input.add_to_scene(ui) @@ -44,5 +44,5 @@ def handle_keys(key, state): focus_mgr.focus_next() # McRogueFace alpha anti-feature: only the active scene can be given a keypress callback -text_example.on_key = handle_keys +mcrfpy.keypressScene(handle_keys) diff --git a/src/scripts/game.py b/src/scripts/game.py index 3d54841..cc2fcb0 100644 --- a/src/scripts/game.py +++ b/src/scripts/game.py @@ -42,7 +42,7 @@ resources = Resources() class Crypt: def __init__(self): - play = mcrfpy.Scene("play") + mcrfpy.createScene("play") self.ui = mcrfpy.sceneUI("play") entity_frame = mcrfpy.Frame(pos=(815, 10), size=(194, 595), fill_color=frame_color) @@ -244,8 +244,8 @@ class Crypt: def start(self): resources.play_sfx(1) - play.activate() - play.on_key = self.cos_keys + mcrfpy.setScene("play") + mcrfpy.keypressScene(self.cos_keys) def add_entity(self, e:ce.COSEntity): self.entities.append(e) @@ -490,9 +490,9 @@ class SweetButton: class MainMenu: def __init__(self): - menu = mcrfpy.Scene("menu") + mcrfpy.createScene("menu") self.ui = mcrfpy.sceneUI("menu") - menu.activate() + mcrfpy.setScene("menu") self.crypt = None components = [] diff --git a/tests/benchmarks/benchmark_moving_entities.py b/tests/benchmarks/benchmark_moving_entities.py index c4c7b50..23d79d6 100644 --- a/tests/benchmarks/benchmark_moving_entities.py +++ b/tests/benchmarks/benchmark_moving_entities.py @@ -21,11 +21,11 @@ import sys import random # Create the benchmark scene -benchmark = mcrfpy.Scene("benchmark") -benchmark.activate() +mcrfpy.createScene("benchmark") +mcrfpy.setScene("benchmark") # Get scene UI -ui = benchmark.children +ui = mcrfpy.sceneUI("benchmark") # Create a 100x100 grid grid = mcrfpy.Grid( @@ -94,7 +94,7 @@ def handle_key(key, state): print("\nBenchmark ended by user") sys.exit(0) -benchmark.on_key = handle_key +mcrfpy.keypressScene(handle_key) # Update entity positions def update_entities(ms): diff --git a/tests/benchmarks/benchmark_suite.py b/tests/benchmarks/benchmark_suite.py index 9a8f2c7..72c24c7 100644 --- a/tests/benchmarks/benchmark_suite.py +++ b/tests/benchmarks/benchmark_suite.py @@ -158,14 +158,14 @@ def run_next_scenario(): def setup_empty_scene(): """Scenario 1: Empty scene - pure engine overhead.""" - bench_empty = mcrfpy.Scene("bench_empty") - bench_empty.activate() + mcrfpy.createScene("bench_empty") + mcrfpy.setScene("bench_empty") def setup_static_100(): """Scenario 2: 100 static frames - best case for caching.""" - bench_static = mcrfpy.Scene("bench_static") - ui = bench_static.children + mcrfpy.createScene("bench_static") + ui = mcrfpy.sceneUI("bench_static") # Create 100 frames in a 10x10 grid for i in range(100): @@ -183,13 +183,13 @@ def setup_static_100(): ui.append(frame) - bench_static.activate() + mcrfpy.setScene("bench_static") def setup_animated_100(): """Scenario 3: 100 frames all animating - worst case for caching.""" - bench_animated = mcrfpy.Scene("bench_animated") - ui = bench_animated.children + mcrfpy.createScene("bench_animated") + ui = mcrfpy.sceneUI("bench_animated") frames = [] for i in range(100): @@ -200,7 +200,7 @@ def setup_animated_100(): frames.append(frame) ui.append(frame) - bench_animated.activate() + mcrfpy.setScene("bench_animated") # Start animations on all frames (color animation = content change) for i, frame in enumerate(frames): @@ -212,8 +212,8 @@ def setup_animated_100(): def setup_mixed_100(): """Scenario 4: 100 frames, only 10 animating - realistic case.""" - bench_mixed = mcrfpy.Scene("bench_mixed") - ui = bench_mixed.children + mcrfpy.createScene("bench_mixed") + ui = mcrfpy.sceneUI("bench_mixed") frames = [] for i in range(100): @@ -224,7 +224,7 @@ def setup_mixed_100(): frames.append(frame) ui.append(frame) - bench_mixed.activate() + mcrfpy.setScene("bench_mixed") # Animate only 10 frames (every 10th) for i in range(0, 100, 10): @@ -235,8 +235,8 @@ def setup_mixed_100(): def setup_deep_hierarchy(): """Scenario 5: 5 levels of nesting - test dirty flag propagation cost.""" - bench_deep = mcrfpy.Scene("bench_deep") - ui = bench_deep.children + mcrfpy.createScene("bench_deep") + ui = mcrfpy.sceneUI("bench_deep") # Create 10 trees, each with 5 levels of nesting deepest_frames = [] @@ -263,7 +263,7 @@ def setup_deep_hierarchy(): if level == 4: # Deepest level deepest_frames.append(frame) - bench_deep.activate() + mcrfpy.setScene("bench_deep") # Animate the deepest frames - tests propagation up the hierarchy for frame in deepest_frames: @@ -273,8 +273,8 @@ def setup_deep_hierarchy(): def setup_grid_stress(): """Scenario 6: Large grid with entities - known performance bottleneck.""" - bench_grid = mcrfpy.Scene("bench_grid") - ui = bench_grid.children + mcrfpy.createScene("bench_grid") + ui = mcrfpy.sceneUI("bench_grid") # Create a 50x50 grid (2500 cells) grid = mcrfpy.Grid(grid_size=(50, 50), pos=(50, 50), size=(700, 700)) @@ -303,7 +303,7 @@ def setup_grid_stress(): except Exception as e: print(f" Note: Could not create entities: {e}") - bench_grid.activate() + mcrfpy.setScene("bench_grid") # ============================================================================ diff --git a/tests/benchmarks/entity_scale_benchmark.py b/tests/benchmarks/entity_scale_benchmark.py index 74fa472..07d562a 100644 --- a/tests/benchmarks/entity_scale_benchmark.py +++ b/tests/benchmarks/entity_scale_benchmark.py @@ -24,14 +24,13 @@ import random # Configuration # Use smaller grid for denser entity distribution (more realistic visibility tests) -#GRID_SIZE = (100, 100) # 10,000 cells - entities will actually see each other -GRID_SIZE = (1250, 1250) +GRID_SIZE = (100, 100) # 10,000 cells - entities will actually see each other # Full suite - may timeout on large counts due to O(n²) visibility # ENTITY_COUNTS = [100, 500, 1000, 2500, 5000, 10000] # Extended suite to validate scalability (on 100x100 grid) -ENTITY_COUNTS = [100, 500, 1000, 5000] +ENTITY_COUNTS = [100, 500, 1000, 2000, 5000] QUERY_RADIUS = 15 # Smaller radius for smaller grid MOVEMENT_PERCENT = 0.10 # 10% of entities move each frame N2N_SAMPLE_SIZE = 50 # Sample size for N×N visibility test @@ -45,12 +44,11 @@ def setup_grid_with_entities(n_entities): global texture scene_name = f"bench_{n_entities}" - _scene = mcrfpy.Scene(scene_name) - ui = _scene.children + mcrfpy.createScene(scene_name) + ui = mcrfpy.sceneUI(scene_name) # Create grid - minimal rendering size since we're testing entity operations - #grid = mcrfpy.Grid(grid_size=GRID_SIZE, pos=(0, 0), size=(100, 100)) - grid = mcrfpy.Grid(grid_size=GRID_SIZE, pos=(0, 0), size=(1024, 768)) + grid = mcrfpy.Grid(grid_size=GRID_SIZE, pos=(0, 0), size=(100, 100)) ui.append(grid) # Load texture once @@ -68,7 +66,7 @@ def setup_grid_with_entities(n_entities): entity = mcrfpy.Entity((x, y), texture, 0, grid) grid.entities.append(entity) - mcrfpy.current_scene = scene_name + mcrfpy.setScene(scene_name) return grid, scene_name @@ -77,8 +75,8 @@ def benchmark_creation(n_entities): global texture scene_name = "bench_create_test" - _scene = mcrfpy.Scene(scene_name) - ui = _scene.children + mcrfpy.createScene(scene_name) + ui = mcrfpy.sceneUI(scene_name) grid = mcrfpy.Grid(grid_size=GRID_SIZE, pos=(0, 0), size=(100, 100)) ui.append(grid) diff --git a/tests/benchmarks/layer_performance_test.py b/tests/benchmarks/layer_performance_test.py index b62844b..6649a6b 100644 --- a/tests/benchmarks/layer_performance_test.py +++ b/tests/benchmarks/layer_performance_test.py @@ -99,8 +99,8 @@ def run_next_test(): def setup_base_layer_static(): """ColorLayer with per-cell set() calls - static after initial fill.""" - test_base_static = mcrfpy.Scene("test_base_static") - ui = test_base_static.children + mcrfpy.createScene("test_base_static") + ui = mcrfpy.sceneUI("test_base_static") grid = mcrfpy.Grid(grid_size=(GRID_SIZE, GRID_SIZE), pos=(10, 10), size=(600, 600)) @@ -112,13 +112,13 @@ def setup_base_layer_static(): for x in range(GRID_SIZE): layer.set(x, y, mcrfpy.Color((x * 2) % 256, (y * 2) % 256, 128, 255)) - test_base_static.activate() + mcrfpy.setScene("test_base_static") def setup_base_layer_modified(): """ColorLayer with single cell modified each frame - tests dirty flag.""" - test_base_mod = mcrfpy.Scene("test_base_mod") - ui = test_base_mod.children + mcrfpy.createScene("test_base_mod") + ui = mcrfpy.sceneUI("test_base_mod") grid = mcrfpy.Grid(grid_size=(GRID_SIZE, GRID_SIZE), pos=(10, 10), size=(600, 600)) @@ -136,14 +136,14 @@ def setup_base_layer_modified(): layer.set(x, y, mcrfpy.Color(255, 0, 0, 255)) mod_counter[0] += 1 - test_base_mod.activate() + mcrfpy.setScene("test_base_mod") mcrfpy.setTimer("modify", modify_cell, 1) def setup_color_layer_static(): """New ColorLayer with dirty flag caching - static after fill.""" - test_color_static = mcrfpy.Scene("test_color_static") - ui = test_color_static.children + mcrfpy.createScene("test_color_static") + ui = mcrfpy.sceneUI("test_color_static") grid = mcrfpy.Grid(grid_size=(GRID_SIZE, GRID_SIZE), pos=(10, 10), size=(600, 600)) @@ -153,13 +153,13 @@ def setup_color_layer_static(): layer = grid.add_layer("color", z_index=-1) layer.fill(mcrfpy.Color(100, 150, 200, 128)) - test_color_static.activate() + mcrfpy.setScene("test_color_static") def setup_color_layer_modified(): """ColorLayer with single cell modified each frame - tests dirty flag.""" - test_color_mod = mcrfpy.Scene("test_color_mod") - ui = test_color_mod.children + mcrfpy.createScene("test_color_mod") + ui = mcrfpy.sceneUI("test_color_mod") grid = mcrfpy.Grid(grid_size=(GRID_SIZE, GRID_SIZE), pos=(10, 10), size=(600, 600)) @@ -176,14 +176,14 @@ def setup_color_layer_modified(): layer.set(x, y, mcrfpy.Color(255, 0, 0, 255)) mod_counter[0] += 1 - test_color_mod.activate() + mcrfpy.setScene("test_color_mod") mcrfpy.setTimer("modify", modify_cell, 1) def setup_tile_layer_static(): """TileLayer with caching - static after fill.""" - test_tile_static = mcrfpy.Scene("test_tile_static") - ui = test_tile_static.children + mcrfpy.createScene("test_tile_static") + ui = mcrfpy.sceneUI("test_tile_static") try: texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) @@ -198,13 +198,13 @@ def setup_tile_layer_static(): layer = grid.add_layer("tile", z_index=-1, texture=texture) layer.fill(5) - test_tile_static.activate() + mcrfpy.setScene("test_tile_static") def setup_tile_layer_modified(): """TileLayer with single cell modified each frame.""" - test_tile_mod = mcrfpy.Scene("test_tile_mod") - ui = test_tile_mod.children + mcrfpy.createScene("test_tile_mod") + ui = mcrfpy.sceneUI("test_tile_mod") try: texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) @@ -229,14 +229,14 @@ def setup_tile_layer_modified(): layer.set(x, y, (mod_counter[0] % 20)) mod_counter[0] += 1 - test_tile_mod.activate() + mcrfpy.setScene("test_tile_mod") mcrfpy.setTimer("modify", modify_cell, 1) def setup_multi_layer_static(): """Multiple layers (5 color, 5 tile) - all static.""" - test_multi_static = mcrfpy.Scene("test_multi_static") - ui = test_multi_static.children + mcrfpy.createScene("test_multi_static") + ui = mcrfpy.sceneUI("test_multi_static") try: texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) @@ -259,13 +259,13 @@ def setup_multi_layer_static(): layer.fill(i * 4) print(f" Created {len(grid.layers)} layers") - test_multi_static.activate() + mcrfpy.setScene("test_multi_static") def setup_base_vs_layer_comparison(): """Direct comparison: same visual using base API vs layer API.""" - test_comparison = mcrfpy.Scene("test_comparison") - ui = test_comparison.children + mcrfpy.createScene("test_comparison") + ui = mcrfpy.sceneUI("test_comparison") # Grid using ONLY the new layer system (no base layer colors) grid = mcrfpy.Grid(grid_size=(GRID_SIZE, GRID_SIZE), @@ -280,7 +280,7 @@ def setup_base_vs_layer_comparison(): for x in range(GRID_SIZE): layer.set(x, y, mcrfpy.Color((x * 2) % 256, (y * 2) % 256, 128, 255)) - test_comparison.activate() + mcrfpy.setScene("test_comparison") # ============================================================================ diff --git a/tests/benchmarks/stress_test_suite.py b/tests/benchmarks/stress_test_suite.py index ba2546d..598cd74 100644 --- a/tests/benchmarks/stress_test_suite.py +++ b/tests/benchmarks/stress_test_suite.py @@ -55,7 +55,7 @@ class StressTestRunner: # Setup scene scene_name = f"stress_{self.current_test}" - _scene = mcrfpy.Scene(scene_name) + mcrfpy.createScene(scene_name) # Start benchmark mcrfpy.start_benchmark() @@ -67,7 +67,7 @@ class StressTestRunner: except Exception as e: print(f" SETUP ERROR: {e}") - mcrfpy.current_scene = scene_name + mcrfpy.setScene(scene_name) self.frames_counted = 0 def end_current_test(self): @@ -133,10 +133,10 @@ class StressTestRunner: print("="*50) print(f"Tests: {len(self.tests)}, Duration: {TEST_DURATION_MS}ms each") - init = mcrfpy.Scene("init") - ui = init.children + mcrfpy.createScene("init") + ui = mcrfpy.sceneUI("init") ui.append(mcrfpy.Frame(pos=(0,0), size=(10,10))) # Required for timer to fire - init.activate() + mcrfpy.setScene("init") mcrfpy.setTimer("tick", self.tick, TIMER_INTERVAL_MS) @@ -146,7 +146,7 @@ class StressTestRunner: def test_many_frames(scene_name): """1000 Frame elements""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) for i in range(1000): frame = mcrfpy.Frame( pos=((i % 32) * 32, (i // 32) * 24), @@ -158,7 +158,7 @@ def test_many_frames(scene_name): def test_many_sprites(scene_name): """500 Sprite elements""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) for i in range(500): sprite = mcrfpy.Sprite( @@ -173,7 +173,7 @@ def test_many_sprites(scene_name): def test_many_captions(scene_name): """500 Caption elements""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) for i in range(500): caption = mcrfpy.Caption( text=f"Text #{i}", @@ -184,7 +184,7 @@ def test_many_captions(scene_name): def test_deep_nesting(scene_name): """15-level nested frames""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) current = ui for level in range(15): frame = mcrfpy.Frame( @@ -202,7 +202,7 @@ def test_deep_nesting(scene_name): def test_large_grid(scene_name): """100x100 grid with 500 entities""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) grid = mcrfpy.Grid(pos=(50, 50), size=(900, 650), grid_size=(100, 100), texture=texture) ui.append(grid) @@ -223,7 +223,7 @@ def test_large_grid(scene_name): def test_animation_stress(scene_name): """100 frames with 200 animations""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) for i in range(100): frame = mcrfpy.Frame( pos=((i % 10) * 100 + 10, (i // 10) * 70 + 10), @@ -241,7 +241,7 @@ def test_animation_stress(scene_name): def test_static_scene(scene_name): """Static game scene (ideal for caching)""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) # Background @@ -270,7 +270,7 @@ def test_static_scene(scene_name): def test_static_scene_cached(scene_name): """Static game scene with cache_subtree enabled (#144)""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) # Background with caching enabled @@ -299,7 +299,7 @@ def test_static_scene_cached(scene_name): def test_deep_nesting_cached(scene_name): """15-level nested frames with cache_subtree on outer frame (#144)""" - ui = _scene.children # TODO: Replace _scene with correct Scene object + ui = mcrfpy.sceneUI(scene_name) # Outer frame with caching - entire subtree cached outer = mcrfpy.Frame( diff --git a/tests/benchmarks/tcod_fov_isolated.py b/tests/benchmarks/tcod_fov_isolated.py index ee491e3..b15af17 100644 --- a/tests/benchmarks/tcod_fov_isolated.py +++ b/tests/benchmarks/tcod_fov_isolated.py @@ -12,8 +12,8 @@ def run_test(runtime): print("=" * 60) # Create a 1000x1000 grid - test = mcrfpy.Scene("test") - ui = test.children + mcrfpy.createScene("test") + ui = mcrfpy.sceneUI("test") texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) print("\nCreating 1000x1000 grid...") @@ -94,6 +94,6 @@ def run_test(runtime): sys.exit(0) -init = mcrfpy.Scene("init") -init.activate() +mcrfpy.createScene("init") +mcrfpy.setScene("init") mcrfpy.setTimer("test", run_test, 100) diff --git a/tests/benchmarks/tcod_scale_test.py b/tests/benchmarks/tcod_scale_test.py index 6bb38da..cb5ff0e 100644 --- a/tests/benchmarks/tcod_scale_test.py +++ b/tests/benchmarks/tcod_scale_test.py @@ -19,8 +19,8 @@ def benchmark_grid_size(grid_x, grid_y): # Create scene and grid scene_name = f"bench_{grid_x}x{grid_y}" - _scene = mcrfpy.Scene(scene_name) - ui = _scene.children # TODO: Replace _scene with correct Scene object + mcrfpy.createScene(scene_name) + ui = mcrfpy.sceneUI(scene_name) texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) @@ -130,8 +130,8 @@ def main(): sys.exit(0) # Run immediately (no timer needed for this test) -init = mcrfpy.Scene("init") -init.activate() +mcrfpy.createScene("init") +mcrfpy.setScene("init") # Use a timer to let the engine initialize def run_benchmark(runtime): diff --git a/tests/demo/demo_main.py b/tests/demo/demo_main.py index ceae6c2..67bc94f 100644 --- a/tests/demo/demo_main.py +++ b/tests/demo/demo_main.py @@ -65,8 +65,8 @@ class DemoRunner: def create_menu(self): """Create the main menu screen.""" - menu = mcrfpy.Scene("menu") - ui = menu.children + mcrfpy.createScene("menu") + ui = mcrfpy.sceneUI("menu") # Title title = mcrfpy.Caption(text="McRogueFace Demo", pos=(400, 30)) @@ -122,7 +122,7 @@ class DemoRunner: sys.exit(0) return screen = self.screens[self.current_index] - mcrfpy.current_scene = screen + mcrfpy.setScene(screen.scene_name) self.render_wait = 1 elif self.render_wait < 2: # Wait additional frame @@ -157,23 +157,23 @@ class DemoRunner: # ESC returns to menu elif key == "Escape": - menu.activate() + mcrfpy.setScene("menu") # Q quits elif key == "Q": sys.exit(0) # Register keyboard handler on menu scene - menu.activate() - menu.on_key = handle_key + mcrfpy.setScene("menu") + mcrfpy.keypressScene(handle_key) # Also register keyboard handler on all demo scenes for screen in self.screens: - mcrfpy.current_scene = screen - menu.on_key = handle_key + mcrfpy.setScene(screen.scene_name) + mcrfpy.keypressScene(handle_key) # Start on menu - menu.activate() + mcrfpy.setScene("menu") def main(): """Main entry point.""" diff --git a/tests/demo/perspective_patrol_demo.py b/tests/demo/perspective_patrol_demo.py index 2bab026..9ca0bab 100644 --- a/tests/demo/perspective_patrol_demo.py +++ b/tests/demo/perspective_patrol_demo.py @@ -37,10 +37,10 @@ def setup_scene(): """Create the demo scene""" global g_grid, g_patrol, g_fov_layer - patrol_demo = mcrfpy.Scene("patrol_demo") - patrol_demo.activate() + mcrfpy.createScene("patrol_demo") + mcrfpy.setScene("patrol_demo") - ui = patrol_demo.children + ui = mcrfpy.sceneUI("patrol_demo") # Title title = mcrfpy.Caption(text="Perspective Patrol Demo", pos=(10, 10)) @@ -123,7 +123,7 @@ def setup_scene(): ui.append(status) # Set up keyboard handler - patrol_demo.on_key = on_keypress + mcrfpy.keypressScene(on_keypress) # Start patrol timer mcrfpy.setTimer("patrol", patrol_step, move_timer_ms) @@ -173,7 +173,7 @@ def on_keypress(key, state): else: update_status("Status: Patrolling") elif key == "Q": - mcrfpy.current_scene = None + mcrfpy.setScene(None) def reset_vision(): """Reset entity's discovered state to demonstrate unknown vs discovered""" @@ -194,7 +194,7 @@ def reset_vision(): def update_status(text): """Update status caption""" - ui = patrol_demo.children + ui = mcrfpy.sceneUI("patrol_demo") for element in ui: if hasattr(element, 'name') and element.name == "status": element.text = text diff --git a/tests/demo/screens/base.py b/tests/demo/screens/base.py index 82f4d79..8e32206 100644 --- a/tests/demo/screens/base.py +++ b/tests/demo/screens/base.py @@ -9,7 +9,7 @@ class DemoScreen: def __init__(self, scene_name): self.scene_name = scene_name - _scene = mcrfpy.Scene(scene_name) + mcrfpy.createScene(scene_name) self.ui = mcrfpy.sceneUI(scene_name) def setup(self): diff --git a/tests/demo/screens/focus_system_demo.py b/tests/demo/screens/focus_system_demo.py index 4d8bd8f..fc3ba88 100644 --- a/tests/demo/screens/focus_system_demo.py +++ b/tests/demo/screens/focus_system_demo.py @@ -600,8 +600,8 @@ def create_demo_scene(): """Create and populate the focus system demo scene.""" # Create scene - focus_demo = mcrfpy.Scene("focus_demo") - ui = focus_demo.children + mcrfpy.createScene("focus_demo") + ui = mcrfpy.sceneUI("focus_demo") # Background bg = mcrfpy.Frame( @@ -752,10 +752,10 @@ def create_demo_scene(): status_text.text = "No widget focused" # Activate scene first (keypressScene sets handler for CURRENT scene) - focus_demo.activate() + mcrfpy.setScene("focus_demo") # Register key handler for the now-current scene - focus_demo.on_key = on_key + mcrfpy.keypressScene(on_key) # Set initial focus focus_mgr.focus(0) diff --git a/tests/geometry_demo/geometry_main.py b/tests/geometry_demo/geometry_main.py index 92c90ae..e0aaa4f 100644 --- a/tests/geometry_demo/geometry_main.py +++ b/tests/geometry_demo/geometry_main.py @@ -66,8 +66,8 @@ class GeometryDemoRunner: def create_menu(self): """Create the main menu screen.""" - geo_menu = mcrfpy.Scene("geo_menu") - ui = geo_menu.children + mcrfpy.createScene("geo_menu") + ui = mcrfpy.sceneUI("geo_menu") # Screen dimensions SCREEN_WIDTH = 1024 @@ -142,7 +142,7 @@ class GeometryDemoRunner: sys.exit(0) return screen = self.screens[self.current_index] - mcrfpy.current_scene = screen + mcrfpy.setScene(screen.scene_name) self.render_wait = 1 elif self.render_wait < 3: # Wait for animated demos to show initial state @@ -188,21 +188,21 @@ class GeometryDemoRunner: elif key == "Escape": for screen in self.screens: screen.cleanup() - geo_menu.activate() + mcrfpy.setScene("geo_menu") # Q quits elif key == "Q": sys.exit(0) # Register keyboard handler on all scenes - geo_menu.activate() - geo_menu.on_key = handle_key + mcrfpy.setScene("geo_menu") + mcrfpy.keypressScene(handle_key) for screen in self.screens: - mcrfpy.current_scene = screen - geo_menu.on_key = handle_key + mcrfpy.setScene(screen.scene_name) + mcrfpy.keypressScene(handle_key) - geo_menu.activate() + mcrfpy.setScene("geo_menu") def main(): diff --git a/tests/geometry_demo/screens/base.py b/tests/geometry_demo/screens/base.py index 992208e..ae64303 100644 --- a/tests/geometry_demo/screens/base.py +++ b/tests/geometry_demo/screens/base.py @@ -35,7 +35,7 @@ class GeometryDemoScreen: def __init__(self, scene_name): self.scene_name = scene_name - _scene = mcrfpy.Scene(scene_name) + mcrfpy.createScene(scene_name) self.ui = mcrfpy.sceneUI(scene_name) self.timers = [] # Track timer names for cleanup self._timer_configs = [] # Store timer configs for restart diff --git a/tests/integration/astar_vs_dijkstra.py b/tests/integration/astar_vs_dijkstra.py index 5f9e6ad..be75ea2 100644 --- a/tests/integration/astar_vs_dijkstra.py +++ b/tests/integration/astar_vs_dijkstra.py @@ -28,7 +28,7 @@ def create_map(): """Create a map with obstacles to show pathfinding differences""" global grid, color_layer - pathfinding_comparison = mcrfpy.Scene("pathfinding_comparison") + mcrfpy.createScene("pathfinding_comparison") # Create grid grid = mcrfpy.Grid(grid_x=30, grid_y=20) @@ -198,7 +198,7 @@ print("Dijkstra explores in all directions (good for multiple targets)") create_map() # Set up UI -ui = pathfinding_comparison.children +ui = mcrfpy.sceneUI("pathfinding_comparison") ui.append(grid) # Scale and position @@ -230,8 +230,8 @@ legend2.fill_color = mcrfpy.Color(150, 150, 150) ui.append(legend2) # Set scene and input -pathfinding_comparison.activate() -pathfinding_comparison.on_key = handle_keypress +mcrfpy.setScene("pathfinding_comparison") +mcrfpy.keypressScene(handle_keypress) # Show initial A* path show_astar() diff --git a/tests/integration/debug_visibility.py b/tests/integration/debug_visibility.py index 075e1df..89a4ab7 100644 --- a/tests/integration/debug_visibility.py +++ b/tests/integration/debug_visibility.py @@ -7,7 +7,7 @@ import sys print("Debug visibility...") # Create scene and grid -debug = mcrfpy.Scene("debug") +mcrfpy.createScene("debug") grid = mcrfpy.Grid(grid_x=5, grid_y=5) # Initialize grid diff --git a/tests/integration/dijkstra_all_paths.py b/tests/integration/dijkstra_all_paths.py index fcece2b..79ce919 100644 --- a/tests/integration/dijkstra_all_paths.py +++ b/tests/integration/dijkstra_all_paths.py @@ -30,7 +30,7 @@ def create_map(): """Create the map with entities""" global grid, color_layer, entities, all_combinations - dijkstra_all = mcrfpy.Scene("dijkstra_all") + mcrfpy.createScene("dijkstra_all") # Create grid grid = mcrfpy.Grid(grid_x=14, grid_y=10) @@ -178,7 +178,7 @@ print() create_map() # Set up UI -ui = dijkstra_all.children +ui = mcrfpy.sceneUI("dijkstra_all") ui.append(grid) # Scale and position @@ -221,8 +221,8 @@ expected.fill_color = mcrfpy.Color(255, 150, 150) ui.append(expected) # Set scene first, then set up input handler -dijkstra_all.activate() -dijkstra_all.on_key = handle_keypress +mcrfpy.setScene("dijkstra_all") +mcrfpy.keypressScene(handle_keypress) # Show first combination show_combination(0) diff --git a/tests/integration/dijkstra_cycle_paths.py b/tests/integration/dijkstra_cycle_paths.py index 5336e40..2f71862 100644 --- a/tests/integration/dijkstra_cycle_paths.py +++ b/tests/integration/dijkstra_cycle_paths.py @@ -28,7 +28,7 @@ def create_map(): """Create the map with entities""" global grid, color_layer, entities - dijkstra_cycle = mcrfpy.Scene("dijkstra_cycle") + mcrfpy.createScene("dijkstra_cycle") # Create grid grid = mcrfpy.Grid(grid_x=14, grid_y=10) @@ -189,7 +189,7 @@ print() create_map() # Set up UI -ui = dijkstra_cycle.children +ui = mcrfpy.sceneUI("dijkstra_cycle") ui.append(grid) # Scale and position @@ -222,8 +222,8 @@ legend.fill_color = mcrfpy.Color(150, 150, 150) ui.append(legend) # Show first valid path -dijkstra_cycle.activate() -dijkstra_cycle.on_key = handle_keypress +mcrfpy.setScene("dijkstra_cycle") +mcrfpy.keypressScene(handle_keypress) # Display initial path if path_combinations: diff --git a/tests/integration/dijkstra_debug.py b/tests/integration/dijkstra_debug.py index 5b9ee5d..6538fae 100644 --- a/tests/integration/dijkstra_debug.py +++ b/tests/integration/dijkstra_debug.py @@ -27,7 +27,7 @@ def create_simple_map(): """Create a simple test map""" global grid, color_layer, entities - dijkstra_debug = mcrfpy.Scene("dijkstra_debug") + mcrfpy.createScene("dijkstra_debug") # Small grid for easy debugging grid = mcrfpy.Grid(grid_x=10, grid_y=10) @@ -140,7 +140,7 @@ grid = create_simple_map() test_path_highlighting() # Set up UI -ui = dijkstra_debug.children +ui = mcrfpy.sceneUI("dijkstra_debug") ui.append(grid) # Position and scale @@ -158,8 +158,8 @@ info.fill_color = mcrfpy.Color(200, 200, 200) ui.append(info) # Set up scene -dijkstra_debug.on_key = handle_keypress -dijkstra_debug.activate() +mcrfpy.keypressScene(handle_keypress) +mcrfpy.setScene("dijkstra_debug") print("\nScene ready. The path should be highlighted in cyan.") print("If you don't see the path, there may be a rendering issue.") diff --git a/tests/integration/dijkstra_interactive.py b/tests/integration/dijkstra_interactive.py index e91153e..c9deeae 100644 --- a/tests/integration/dijkstra_interactive.py +++ b/tests/integration/dijkstra_interactive.py @@ -38,7 +38,7 @@ def create_map(): """Create the interactive map with the layout specified by the user""" global grid, color_layer, entities - dijkstra_interactive = mcrfpy.Scene("dijkstra_interactive") + mcrfpy.createScene("dijkstra_interactive") # Create grid - 14x10 as specified grid = mcrfpy.Grid(grid_x=14, grid_y=10) @@ -194,7 +194,7 @@ print(" Q/ESC - Quit") grid = create_map() # Set up UI -ui = dijkstra_interactive.children +ui = mcrfpy.sceneUI("dijkstra_interactive") ui.append(grid) # Scale and position grid for better visibility @@ -235,10 +235,10 @@ for i, entity in enumerate(entities): ui.append(marker) # Set up input handling -dijkstra_interactive.on_key = handle_keypress +mcrfpy.keypressScene(handle_keypress) # Show the scene -dijkstra_interactive.activate() +mcrfpy.setScene("dijkstra_interactive") print("\nVisualization ready!") print("Entities are at:") diff --git a/tests/integration/dijkstra_interactive_enhanced.py b/tests/integration/dijkstra_interactive_enhanced.py index 69e57fe..35c8655 100644 --- a/tests/integration/dijkstra_interactive_enhanced.py +++ b/tests/integration/dijkstra_interactive_enhanced.py @@ -46,7 +46,7 @@ def create_map(): """Create the interactive map with the layout specified by the user""" global grid, color_layer, entities, original_positions - dijkstra_enhanced = mcrfpy.Scene("dijkstra_enhanced") + mcrfpy.createScene("dijkstra_enhanced") # Create grid - 14x10 as specified grid = mcrfpy.Grid(grid_x=14, grid_y=10) @@ -286,7 +286,7 @@ print(" Q/ESC - Quit") grid = create_map() # Set up UI -ui = dijkstra_enhanced.children +ui = mcrfpy.sceneUI("dijkstra_enhanced") ui.append(grid) # Scale and position grid for better visibility @@ -332,13 +332,13 @@ for i, entity in enumerate(entities): ui.append(marker) # Set up input handling -dijkstra_enhanced.on_key = handle_keypress +mcrfpy.keypressScene(handle_keypress) # Set up animation timer (60 FPS) mcrfpy.setTimer("animation", update_animation, 16) # Show the scene -dijkstra_enhanced.activate() +mcrfpy.setScene("dijkstra_enhanced") print("\nVisualization ready!") print("Entities are at:") diff --git a/tests/integration/dijkstra_test.py b/tests/integration/dijkstra_test.py index 79da530..928a56e 100644 --- a/tests/integration/dijkstra_test.py +++ b/tests/integration/dijkstra_test.py @@ -12,7 +12,7 @@ import sys def create_test_map(): """Create a test map with obstacles""" - dijkstra_test = mcrfpy.Scene("dijkstra_test") + mcrfpy.createScene("dijkstra_test") # Create grid grid = mcrfpy.Grid(grid_x=20, grid_y=12) @@ -120,7 +120,7 @@ print("Creating Dijkstra pathfinding test...") grid, entities = create_test_map() # Set up UI -ui = dijkstra_test.children +ui = mcrfpy.sceneUI("dijkstra_test") ui.append(grid) # Position and scale grid @@ -138,7 +138,7 @@ legend.fill_color = mcrfpy.Color(180, 180, 180) ui.append(legend) # Set scene -dijkstra_test.activate() +mcrfpy.setScene("dijkstra_test") # Run test after scene loads mcrfpy.setTimer("test", run_test, 100) diff --git a/tests/integration/interactive_visibility.py b/tests/integration/interactive_visibility.py index 762b81c..dcb386d 100644 --- a/tests/integration/interactive_visibility.py +++ b/tests/integration/interactive_visibility.py @@ -15,7 +15,7 @@ import mcrfpy import sys # Create scene and grid -visibility_demo = mcrfpy.Scene("visibility_demo") +mcrfpy.createScene("visibility_demo") grid = mcrfpy.Grid(grid_x=30, grid_y=20) grid.fill_color = mcrfpy.Color(20, 20, 30) # Dark background @@ -77,7 +77,7 @@ current_perspective = -1 perspective_names = ["Omniscient", "Player", "Enemy"] # UI Setup -ui = visibility_demo.children +ui = mcrfpy.sceneUI("visibility_demo") ui.append(grid) grid.position = (50, 100) grid.size = (900, 600) # 30*30, 20*30 @@ -187,10 +187,10 @@ def handle_keys(key, state): update_info() # Set scene first -visibility_demo.activate() +mcrfpy.setScene("visibility_demo") # Register key handler (operates on current scene) -visibility_demo.on_key = handle_keys +mcrfpy.keypressScene(handle_keys) print("Interactive Visibility Demo") print("===========================") diff --git a/tests/integration/simple_interactive_visibility.py b/tests/integration/simple_interactive_visibility.py index 6ca9ac1..6243ecb 100644 --- a/tests/integration/simple_interactive_visibility.py +++ b/tests/integration/simple_interactive_visibility.py @@ -6,7 +6,7 @@ import sys # Create scene and grid print("Creating scene...") -vis_test = mcrfpy.Scene("vis_test") +mcrfpy.createScene("vis_test") print("Creating grid...") grid = mcrfpy.Grid(grid_x=10, grid_y=10) @@ -33,7 +33,7 @@ entity.update_visibility() # Set up UI print("Setting up UI...") -ui = vis_test.children +ui = mcrfpy.sceneUI("vis_test") ui.append(grid) grid.position = (50, 50) grid.size = (300, 300) @@ -44,6 +44,6 @@ grid.perspective = -1 # Omniscient print(f"Perspective set to: {grid.perspective}") print("Setting scene...") -vis_test.activate() +mcrfpy.setScene("vis_test") print("Ready!") \ No newline at end of file diff --git a/tests/integration/simple_visibility_test.py b/tests/integration/simple_visibility_test.py index a29c081..1e00b73 100644 --- a/tests/integration/simple_visibility_test.py +++ b/tests/integration/simple_visibility_test.py @@ -7,7 +7,7 @@ import sys print("Simple visibility test...") # Create scene and grid -simple = mcrfpy.Scene("simple") +mcrfpy.createScene("simple") print("Scene created") grid = mcrfpy.Grid(grid_x=5, grid_y=5) diff --git a/tests/notes/test_exception_exit.py b/tests/notes/test_exception_exit.py index 0acc236..348c88b 100644 --- a/tests/notes/test_exception_exit.py +++ b/tests/notes/test_exception_exit.py @@ -14,8 +14,8 @@ def timer_that_raises(runtime): raise ValueError("Intentional test exception") # Create a test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule the timer - it will fire after 50ms mcrfpy.setTimer("raise_exception", timer_that_raises, 50) diff --git a/tests/regression/issue_123_chunk_system_test.py b/tests/regression/issue_123_chunk_system_test.py index f26e260..f8be052 100644 --- a/tests/regression/issue_123_chunk_system_test.py +++ b/tests/regression/issue_123_chunk_system_test.py @@ -181,8 +181,8 @@ if __name__ == "__main__": print("Issue #123: Grid Sub-grid Chunk System Test") print("=" * 60) - test = mcrfpy.Scene("test") - test.activate() + mcrfpy.createScene("test") + mcrfpy.setScene("test") # Run tests after scene is active mcrfpy.setTimer("test", run_test, 100) diff --git a/tests/regression/issue_146_fov_returns_none.py b/tests/regression/issue_146_fov_returns_none.py index c8b9d8f..8cecd3b 100644 --- a/tests/regression/issue_146_fov_returns_none.py +++ b/tests/regression/issue_146_fov_returns_none.py @@ -20,8 +20,8 @@ def run_test(runtime): print("=" * 60) # Create a test grid - test = mcrfpy.Scene("test") - ui = test.children + mcrfpy.createScene("test") + ui = mcrfpy.sceneUI("test") texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) grid = mcrfpy.Grid(pos=(0,0), size=(400,300), grid_size=(50, 50), texture=texture) @@ -109,6 +109,6 @@ def run_test(runtime): sys.exit(0) # Initialize and run -init = mcrfpy.Scene("init") -init.activate() +mcrfpy.createScene("init") +mcrfpy.setScene("init") mcrfpy.setTimer("test", run_test, 100) diff --git a/tests/regression/issue_147_grid_layers.py b/tests/regression/issue_147_grid_layers.py index 0f51d26..3f1b8b9 100644 --- a/tests/regression/issue_147_grid_layers.py +++ b/tests/regression/issue_147_grid_layers.py @@ -17,8 +17,8 @@ def run_test(runtime): print("=" * 60) # Create test scene - test = mcrfpy.Scene("test") - ui = test.children + mcrfpy.createScene("test") + ui = mcrfpy.sceneUI("test") texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) # Create grid with explicit empty layers (#150 migration) @@ -188,6 +188,6 @@ def run_test(runtime): sys.exit(0) # Initialize and run -init = mcrfpy.Scene("init") -init.activate() +mcrfpy.createScene("init") +mcrfpy.setScene("init") mcrfpy.setTimer("test", run_test, 100) diff --git a/tests/regression/issue_148_layer_dirty_flags.py b/tests/regression/issue_148_layer_dirty_flags.py index fbe53c5..cc338da 100644 --- a/tests/regression/issue_148_layer_dirty_flags.py +++ b/tests/regression/issue_148_layer_dirty_flags.py @@ -20,14 +20,14 @@ def run_test(runtime): print("=" * 60) # Create test scene - test = mcrfpy.Scene("test") - ui = test.children + mcrfpy.createScene("test") + ui = mcrfpy.sceneUI("test") texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) # Create grid with larger size for performance testing grid = mcrfpy.Grid(pos=(50, 50), size=(500, 400), grid_size=(50, 40), texture=texture) ui.append(grid) - test.activate() + mcrfpy.setScene("test") print("\n--- Test 1: Layer creation (starts dirty) ---") color_layer = grid.add_layer("color", z_index=-1) @@ -106,7 +106,7 @@ def run_test(runtime): # First render will be slow (cache miss) start = time.time() - test.activate() # Force render + mcrfpy.setScene("test") # Force render first_render = time.time() - start print(f" First render (cache build): {first_render*1000:.2f}ms") @@ -152,6 +152,6 @@ def run_test(runtime): sys.exit(0) # Initialize and run -init = mcrfpy.Scene("init") -init.activate() +mcrfpy.createScene("init") +mcrfpy.setScene("init") mcrfpy.setTimer("test", run_test, 100) diff --git a/tests/regression/issue_37_simple_test.py b/tests/regression/issue_37_simple_test.py index 71e68cd..a6d17b5 100644 --- a/tests/regression/issue_37_simple_test.py +++ b/tests/regression/issue_37_simple_test.py @@ -15,7 +15,7 @@ print(f"Current working directory: {os.getcwd()}") print(f"Script location: {__file__}") # Create a simple scene to verify everything is working -issue37_test = mcrfpy.Scene("issue37_test") +mcrfpy.createScene("issue37_test") print("PASS: Issue #37 - Script loading working correctly") sys.exit(0) \ No newline at end of file diff --git a/tests/regression/issue_37_test.py b/tests/regression/issue_37_test.py index 16f3dbe..d0f882e 100644 --- a/tests/regression/issue_37_test.py +++ b/tests/regression/issue_37_test.py @@ -27,7 +27,7 @@ def test_script_loading(): test_script = """ import mcrfpy print("TEST SCRIPT LOADED SUCCESSFULLY") -test_scene = mcrfpy.Scene("test_scene") +mcrfpy.createScene("test_scene") """ # Save the original game.py diff --git a/tests/regression/issue_76_test.py b/tests/regression/issue_76_test.py index 52dbadd..ecd985d 100644 --- a/tests/regression/issue_76_test.py +++ b/tests/regression/issue_76_test.py @@ -81,8 +81,8 @@ def run_test(runtime): sys.exit(0) # Set up the test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test to run after game loop starts mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/regression/issue_79_color_properties_test.py b/tests/regression/issue_79_color_properties_test.py index 97b9c3c..05233b2 100644 --- a/tests/regression/issue_79_color_properties_test.py +++ b/tests/regression/issue_79_color_properties_test.py @@ -163,8 +163,8 @@ def run_test(runtime): sys.exit(0) # Set up the test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test to run after game loop starts mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/regression/issue_99_texture_font_properties_test.py b/tests/regression/issue_99_texture_font_properties_test.py index 6f5e1f3..1ee5277 100644 --- a/tests/regression/issue_99_texture_font_properties_test.py +++ b/tests/regression/issue_99_texture_font_properties_test.py @@ -217,8 +217,8 @@ def run_test(runtime): sys.exit(0) # Set up the test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test to run after game loop starts mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/regression/issue_9_minimal_test.py b/tests/regression/issue_9_minimal_test.py index 31e769e..09eb9c6 100644 --- a/tests/regression/issue_9_minimal_test.py +++ b/tests/regression/issue_9_minimal_test.py @@ -21,7 +21,7 @@ def run_test(runtime): grid.h = 300 # Add to scene - scene_ui = test.children + scene_ui = mcrfpy.sceneUI("test") scene_ui.append(grid) # Test accessing grid points @@ -60,8 +60,8 @@ def run_test(runtime): sys.exit(0) # Create and set scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/regression/issue_9_rendertexture_resize_test.py b/tests/regression/issue_9_rendertexture_resize_test.py index b6060a0..8d643b5 100644 --- a/tests/regression/issue_9_rendertexture_resize_test.py +++ b/tests/regression/issue_9_rendertexture_resize_test.py @@ -45,7 +45,7 @@ def test_rendertexture_resize(): """Test RenderTexture behavior with various grid sizes""" print("=== Testing UIGrid RenderTexture Resize (Issue #9) ===\n") - scene_ui = test.children + scene_ui = mcrfpy.sceneUI("test") # Test 1: Small grid (should work fine) print("--- Test 1: Small Grid (400x300) ---") @@ -222,8 +222,8 @@ def run_test(runtime): sys.exit(0) # Set up the test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test to run after game loop starts mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/regression/issue_9_test.py b/tests/regression/issue_9_test.py index e7b2cd2..39a1f22 100644 --- a/tests/regression/issue_9_test.py +++ b/tests/regression/issue_9_test.py @@ -20,7 +20,7 @@ def run_test(runtime): grid.h = 200 # Add grid to scene - scene_ui = test.children + scene_ui = mcrfpy.sceneUI("test") scene_ui.append(grid) # Take initial screenshot @@ -82,8 +82,8 @@ def run_test(runtime): sys.exit(0) # Set up the test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test to run after game loop starts mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/regression/test_type_preservation_solution.py b/tests/regression/test_type_preservation_solution.py index 5f297c7..b2d024e 100644 --- a/tests/regression/test_type_preservation_solution.py +++ b/tests/regression/test_type_preservation_solution.py @@ -78,6 +78,6 @@ def run_test(runtime): sys.exit(0) # Set up scene and run -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/unit/WORKING_automation_test_example.py b/tests/unit/WORKING_automation_test_example.py index ba23ce8..91daaef 100644 --- a/tests/unit/WORKING_automation_test_example.py +++ b/tests/unit/WORKING_automation_test_example.py @@ -46,9 +46,9 @@ def run_automation_tests(): print("=== Setting Up Test Scene ===") # Create scene with visible content -timer_test_scene = mcrfpy.Scene("timer_test_scene") -timer_test_scene.activate() -ui = timer_test_scene.children +mcrfpy.createScene("timer_test_scene") +mcrfpy.setScene("timer_test_scene") +ui = mcrfpy.sceneUI("timer_test_scene") # Add a bright red frame that should be visible frame = mcrfpy.Frame(pos=(100, 100), size=(400, 300), diff --git a/tests/unit/api_createScene_test.py b/tests/unit/api_createScene_test.py index 8cb17b1..b5e336e 100644 --- a/tests/unit/api_createScene_test.py +++ b/tests/unit/api_createScene_test.py @@ -9,7 +9,7 @@ def test_createScene(): for scene_name in test_scenes: try: - _scene = mcrfpy.Scene(scene_name) + mcrfpy.createScene(scene_name) print(f"✓ Created scene: {scene_name}") except Exception as e: print(f"✗ Failed to create scene {scene_name}: {e}") @@ -17,8 +17,8 @@ def test_createScene(): # Try to set scene to verify it was created try: - test_scene1.activate() # Note: ensure scene was created - current = (mcrfpy.current_scene.name if mcrfpy.current_scene else None) + mcrfpy.setScene("test_scene1") + current = mcrfpy.currentScene() if current == "test_scene1": print("✓ Scene switching works correctly") else: diff --git a/tests/unit/api_setScene_currentScene_test.py b/tests/unit/api_setScene_currentScene_test.py index 299af65..0e25d0e 100644 --- a/tests/unit/api_setScene_currentScene_test.py +++ b/tests/unit/api_setScene_currentScene_test.py @@ -7,7 +7,7 @@ print("Starting setScene/currentScene test...") # Create test scenes first scenes = ["scene_A", "scene_B", "scene_C"] for scene in scenes: - _scene = mcrfpy.Scene(scene) + mcrfpy.createScene(scene) print(f"Created scene: {scene}") results = [] @@ -15,8 +15,8 @@ results = [] # Test switching between scenes for scene in scenes: try: - mcrfpy.current_scene = scene - current = (mcrfpy.current_scene.name if mcrfpy.current_scene else None) + mcrfpy.setScene(scene) + current = mcrfpy.currentScene() if current == scene: results.append(f"✓ setScene/currentScene works for '{scene}'") else: @@ -25,9 +25,9 @@ for scene in scenes: results.append(f"✗ Error with scene '{scene}': {e}") # Test invalid scene - it should not change the current scene -current_before = (mcrfpy.current_scene.name if mcrfpy.current_scene else None) -nonexistent_scene.activate() # Note: ensure scene was created -current_after = (mcrfpy.current_scene.name if mcrfpy.current_scene else None) +current_before = mcrfpy.currentScene() +mcrfpy.setScene("nonexistent_scene") +current_after = mcrfpy.currentScene() if current_before == current_after: results.append(f"✓ setScene correctly ignores nonexistent scene (stayed on '{current_after}')") else: diff --git a/tests/unit/automation_screenshot_test_simple.py b/tests/unit/automation_screenshot_test_simple.py index de16a8d..75dbf77 100644 --- a/tests/unit/automation_screenshot_test_simple.py +++ b/tests/unit/automation_screenshot_test_simple.py @@ -6,8 +6,8 @@ import os import sys # Create a simple scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Take a screenshot immediately try: diff --git a/tests/unit/benchmark_logging_test.py b/tests/unit/benchmark_logging_test.py index 2600b05..387770c 100644 --- a/tests/unit/benchmark_logging_test.py +++ b/tests/unit/benchmark_logging_test.py @@ -128,8 +128,8 @@ mcrfpy.log_benchmark("Test log message") print("Logged test message") # Set up scene and run for a few frames -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test completion after ~100ms (to capture some frames) mcrfpy.setTimer("test", run_test, 100) diff --git a/tests/unit/collection_find_test.py b/tests/unit/collection_find_test.py index e7ae26f..86a1733 100644 --- a/tests/unit/collection_find_test.py +++ b/tests/unit/collection_find_test.py @@ -12,8 +12,8 @@ def test_uicollection_find(): print("Testing UICollection.find()...") # Create a scene with named elements - test_find = mcrfpy.Scene("test_find") - ui = test_find.children + mcrfpy.createScene("test_find") + ui = mcrfpy.sceneUI("test_find") # Create frames with names frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100)) @@ -94,8 +94,8 @@ def test_entitycollection_find(): print("\nTesting EntityCollection.find()...") # Create a grid with entities - test_entity_find = mcrfpy.Scene("test_entity_find") - ui = test_entity_find.children + mcrfpy.createScene("test_entity_find") + ui = mcrfpy.sceneUI("test_entity_find") grid = mcrfpy.Grid(grid_size=(10, 10), pos=(0, 0), size=(400, 400)) ui.append(grid) @@ -149,8 +149,8 @@ def test_recursive_find(): """Test recursive find in nested Frame children.""" print("\nTesting recursive find in nested frames...") - test_recursive = mcrfpy.Scene("test_recursive") - ui = test_recursive.children + mcrfpy.createScene("test_recursive") + ui = mcrfpy.sceneUI("test_recursive") # Create nested structure parent = mcrfpy.Frame(pos=(0, 0), size=(400, 400)) diff --git a/tests/unit/collection_list_methods_test.py b/tests/unit/collection_list_methods_test.py index 30beae8..7035099 100644 --- a/tests/unit/collection_list_methods_test.py +++ b/tests/unit/collection_list_methods_test.py @@ -12,8 +12,8 @@ def test_uicollection_remove(): """Test UICollection.remove() takes a value, not an index.""" print("Testing UICollection.remove()...") - test_remove = mcrfpy.Scene("test_remove") - ui = test_remove.children + mcrfpy.createScene("test_remove") + ui = mcrfpy.sceneUI("test_remove") frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100)) frame2 = mcrfpy.Frame(pos=(100, 0), size=(100, 100)) @@ -58,8 +58,8 @@ def test_uicollection_pop(): """Test UICollection.pop() removes and returns element at index.""" print("\nTesting UICollection.pop()...") - test_pop = mcrfpy.Scene("test_pop") - ui = test_pop.children + mcrfpy.createScene("test_pop") + ui = mcrfpy.sceneUI("test_pop") frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100)) frame1.name = "first" @@ -108,8 +108,8 @@ def test_uicollection_insert(): """Test UICollection.insert() inserts at given index.""" print("\nTesting UICollection.insert()...") - test_insert = mcrfpy.Scene("test_insert") - ui = test_insert.children + mcrfpy.createScene("test_insert") + ui = mcrfpy.sceneUI("test_insert") frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100)) frame1.name = "first" @@ -160,8 +160,8 @@ def test_entitycollection_pop_insert(): """Test EntityCollection.pop() and insert().""" print("\nTesting EntityCollection.pop() and insert()...") - test_entity_pop = mcrfpy.Scene("test_entity_pop") - ui = test_entity_pop.children + mcrfpy.createScene("test_entity_pop") + ui = mcrfpy.sceneUI("test_entity_pop") grid = mcrfpy.Grid(grid_size=(10, 10), pos=(0, 0), size=(400, 400)) ui.append(grid) @@ -204,8 +204,8 @@ def test_index_and_count(): """Test index() and count() methods.""" print("\nTesting index() and count()...") - test_index_count = mcrfpy.Scene("test_index_count") - ui = test_index_count.children + mcrfpy.createScene("test_index_count") + ui = mcrfpy.sceneUI("test_index_count") frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100)) frame2 = mcrfpy.Frame(pos=(100, 0), size=(100, 100)) diff --git a/tests/unit/debug_empty_paths.py b/tests/unit/debug_empty_paths.py index 6aea589..1485177 100644 --- a/tests/unit/debug_empty_paths.py +++ b/tests/unit/debug_empty_paths.py @@ -7,7 +7,7 @@ import sys print("Debugging empty paths...") # Create scene and grid -debug = mcrfpy.Scene("debug") +mcrfpy.createScene("debug") grid = mcrfpy.Grid(grid_x=10, grid_y=10) # Initialize grid - all walkable @@ -72,9 +72,9 @@ def timer_cb(dt): sys.exit(0) # Quick UI setup -ui = debug.children +ui = mcrfpy.sceneUI("debug") ui.append(grid) -debug.activate() +mcrfpy.setScene("debug") mcrfpy.setTimer("exit", timer_cb, 100) print("\nStarting timer...") \ No newline at end of file diff --git a/tests/unit/debug_render_test.py b/tests/unit/debug_render_test.py index 0c17791..1442f09 100644 --- a/tests/unit/debug_render_test.py +++ b/tests/unit/debug_render_test.py @@ -11,13 +11,13 @@ print(f"Automation available: {'automation' in dir(mcrfpy)}") # Try to understand the scene state print("\nCreating and checking scene...") -debug_scene = mcrfpy.Scene("debug_scene") -debug_scene.activate() -current = (mcrfpy.current_scene.name if mcrfpy.current_scene else None) +mcrfpy.createScene("debug_scene") +mcrfpy.setScene("debug_scene") +current = mcrfpy.currentScene() print(f"Current scene: {current}") # Get UI collection -ui = debug_scene.children +ui = mcrfpy.sceneUI("debug_scene") print(f"UI collection type: {type(ui)}") print(f"Initial UI elements: {len(ui)}") diff --git a/tests/unit/generate_docs_screenshots.py b/tests/unit/generate_docs_screenshots.py index cd5b085..53393fd 100755 --- a/tests/unit/generate_docs_screenshots.py +++ b/tests/unit/generate_docs_screenshots.py @@ -32,8 +32,8 @@ def create_caption(x, y, text, font_size=16, text_color=WHITE, outline_color=BLA def create_caption_example(): """Create a scene showing Caption UI element examples""" - caption_example = mcrfpy.Scene("caption_example") - ui = caption_example.children + mcrfpy.createScene("caption_example") + ui = mcrfpy.sceneUI("caption_example") # Background frame bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR) @@ -61,8 +61,8 @@ def create_caption_example(): def create_sprite_example(): """Create a scene showing Sprite UI element examples""" - sprite_example = mcrfpy.Scene("sprite_example") - ui = sprite_example.children + mcrfpy.createScene("sprite_example") + ui = mcrfpy.sceneUI("sprite_example") # Background frame bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR) @@ -122,8 +122,8 @@ def create_sprite_example(): def create_frame_example(): """Create a scene showing Frame UI element examples""" - frame_example = mcrfpy.Scene("frame_example") - ui = frame_example.children + mcrfpy.createScene("frame_example") + ui = mcrfpy.sceneUI("frame_example") # Background bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR) @@ -184,8 +184,8 @@ def create_frame_example(): def create_grid_example(): """Create a scene showing Grid UI element examples""" - grid_example = mcrfpy.Scene("grid_example") - ui = grid_example.children + mcrfpy.createScene("grid_example") + ui = mcrfpy.sceneUI("grid_example") # Background bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR) @@ -234,8 +234,8 @@ def create_grid_example(): def create_entity_example(): """Create a scene showing Entity examples in a Grid""" - entity_example = mcrfpy.Scene("entity_example") - ui = entity_example.children + mcrfpy.createScene("entity_example") + ui = mcrfpy.sceneUI("entity_example") # Background bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR) @@ -310,8 +310,8 @@ def create_entity_example(): def create_combined_example(): """Create a scene showing all UI elements combined""" - combined_example = mcrfpy.Scene("combined_example") - ui = combined_example.children + mcrfpy.createScene("combined_example") + ui = mcrfpy.sceneUI("combined_example") # Background bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR) @@ -417,7 +417,7 @@ def take_screenshots(runtime): scene_name, filename = screenshots[current_screenshot] # Switch to the scene - mcrfpy.current_scene = scene_name + mcrfpy.setScene(scene_name) # Take screenshot after a short delay to ensure rendering def capture(): @@ -435,7 +435,7 @@ def take_screenshots(runtime): mcrfpy.setTimer("capture", lambda r: capture(), 100) # Start with the first scene -caption_example.activate() +mcrfpy.setScene("caption_example") # Start the screenshot process print(f"\nStarting screenshot capture of {len(screenshots)} scenes...") diff --git a/tests/unit/generate_grid_screenshot.py b/tests/unit/generate_grid_screenshot.py index 51754e7..8c4500d 100644 --- a/tests/unit/generate_grid_screenshot.py +++ b/tests/unit/generate_grid_screenshot.py @@ -16,7 +16,7 @@ def capture_grid(runtime): sys.exit(0) # Create scene -grid = mcrfpy.Scene("grid") +mcrfpy.createScene("grid") # Load texture texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) @@ -92,7 +92,7 @@ for i, label in enumerate(labels): l.font = mcrfpy.default_font l.font_size = 10 l.fill_color = mcrfpy.Color(255, 255, 255) - grid.children.append(l) + mcrfpy.sceneUI("grid").append(l) # Add info caption info = mcrfpy.Caption(pos=(100, 680), text="Grid supports tiles and entities. Entities can move independently of the tile grid.") @@ -101,7 +101,7 @@ info.font_size = 14 info.fill_color = mcrfpy.Color(200, 200, 200) # Add all elements to scene -ui = grid.children +ui = mcrfpy.sceneUI("grid") ui.append(title) ui.append(grid) ui.append(palette_label) @@ -109,7 +109,7 @@ ui.append(palette) ui.append(info) # Switch to scene -grid.activate() +mcrfpy.setScene("grid") # Set timer to capture after rendering starts mcrfpy.setTimer("capture", capture_grid, 100) \ No newline at end of file diff --git a/tests/unit/generate_sprite_screenshot.py b/tests/unit/generate_sprite_screenshot.py index d45197c..ff6114c 100644 --- a/tests/unit/generate_sprite_screenshot.py +++ b/tests/unit/generate_sprite_screenshot.py @@ -16,7 +16,7 @@ def capture_sprites(runtime): sys.exit(0) # Create scene -sprites = mcrfpy.Scene("sprites") +mcrfpy.createScene("sprites") # Load texture texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) @@ -129,10 +129,10 @@ for i, scale in enumerate([1.0, 2.0, 3.0, 4.0]): s.texture = texture s.sprite_index = 84 # Player s.scale = (scale, scale) - sprites.children.append(s) + mcrfpy.sceneUI("sprites").append(s) # Add all elements to scene -ui = sprites.children +ui = mcrfpy.sceneUI("sprites") ui.append(frame) ui.append(title) ui.append(player_label) @@ -154,7 +154,7 @@ ui.append(armor) ui.append(scale_label) # Switch to scene -sprites.activate() +mcrfpy.setScene("sprites") # Set timer to capture after rendering starts mcrfpy.setTimer("capture", capture_sprites, 100) \ No newline at end of file diff --git a/tests/unit/keypress_scene_validation_test.py b/tests/unit/keypress_scene_validation_test.py index de1b8f4..4bd2982 100644 --- a/tests/unit/keypress_scene_validation_test.py +++ b/tests/unit/keypress_scene_validation_test.py @@ -11,15 +11,15 @@ def test_keypress_validation(timer_name): print("Testing keypressScene() validation...") # Create test scene - test = mcrfpy.Scene("test") - test.activate() + mcrfpy.createScene("test") + mcrfpy.setScene("test") # Test 1: Valid callable (function) def key_handler(key, action): print(f"Key pressed: {key}, action: {action}") try: - test.on_key = key_handler + mcrfpy.keypressScene(key_handler) print("✓ Accepted valid function as key handler") except Exception as e: print(f"✗ Rejected valid function: {e}") @@ -27,7 +27,7 @@ def test_keypress_validation(timer_name): # Test 2: Valid callable (lambda) try: - test.on_key = lambda k, a: None + mcrfpy.keypressScene(lambda k, a: None) print("✓ Accepted valid lambda as key handler") except Exception as e: print(f"✗ Rejected valid lambda: {e}") @@ -35,7 +35,7 @@ def test_keypress_validation(timer_name): # Test 3: Invalid - string try: - test.on_key = "not callable" + mcrfpy.keypressScene("not callable") print("✗ Should have rejected string as key handler") except TypeError as e: print(f"✓ Correctly rejected string: {e}") @@ -45,7 +45,7 @@ def test_keypress_validation(timer_name): # Test 4: Invalid - number try: - test.on_key = 42 + mcrfpy.keypressScene(42) print("✗ Should have rejected number as key handler") except TypeError as e: print(f"✓ Correctly rejected number: {e}") @@ -55,7 +55,7 @@ def test_keypress_validation(timer_name): # Test 5: Invalid - None try: - test.on_key = None + mcrfpy.keypressScene(None) print("✗ Should have rejected None as key handler") except TypeError as e: print(f"✓ Correctly rejected None: {e}") @@ -65,7 +65,7 @@ def test_keypress_validation(timer_name): # Test 6: Invalid - dict try: - test.on_key = {"not": "callable"} + mcrfpy.keypressScene({"not": "callable"}) print("✗ Should have rejected dict as key handler") except TypeError as e: print(f"✓ Correctly rejected dict: {e}") @@ -79,7 +79,7 @@ def test_keypress_validation(timer_name): print(f"Class handler: {key}, {action}") try: - test.on_key = KeyHandler() + mcrfpy.keypressScene(KeyHandler()) print("✓ Accepted valid callable class instance") except Exception as e: print(f"✗ Rejected valid callable class: {e}") diff --git a/tests/unit/screenshot_transparency_fix_test.py b/tests/unit/screenshot_transparency_fix_test.py index 7f9e92d..5d5e333 100644 --- a/tests/unit/screenshot_transparency_fix_test.py +++ b/tests/unit/screenshot_transparency_fix_test.py @@ -10,9 +10,9 @@ def test_transparency_workaround(): print("=== Screenshot Transparency Fix Test ===\n") # Create a scene - opaque_test = mcrfpy.Scene("opaque_test") - opaque_test.activate() - ui = opaque_test.children + mcrfpy.createScene("opaque_test") + mcrfpy.setScene("opaque_test") + ui = mcrfpy.sceneUI("opaque_test") # WORKAROUND: Create a full-window opaque frame as the first element # This acts as an opaque background since the scene clears with transparent diff --git a/tests/unit/simple_screenshot_test.py b/tests/unit/simple_screenshot_test.py index a23aeae..3117a81 100644 --- a/tests/unit/simple_screenshot_test.py +++ b/tests/unit/simple_screenshot_test.py @@ -28,7 +28,7 @@ def take_screenshot(runtime): sys.exit(0) # Create minimal scene -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") # Add a visible element caption = mcrfpy.Caption(pos=(100, 100), text="Screenshot Test") @@ -36,8 +36,8 @@ caption.font = mcrfpy.default_font caption.fill_color = mcrfpy.Color(255, 255, 255) caption.font_size = 24 -test.children.append(caption) -test.activate() +mcrfpy.sceneUI("test").append(caption) +mcrfpy.setScene("test") # Use timer to ensure rendering has started print("Setting timer...") diff --git a/tests/unit/simple_timer_screenshot_test.py b/tests/unit/simple_timer_screenshot_test.py index cfa61fc..d4aa001 100644 --- a/tests/unit/simple_timer_screenshot_test.py +++ b/tests/unit/simple_timer_screenshot_test.py @@ -25,9 +25,9 @@ def take_screenshot_and_exit(): # Set up a simple scene print("Creating test scene...") -test = mcrfpy.Scene("test") -test.activate() -ui = test.children +mcrfpy.createScene("test") +mcrfpy.setScene("test") +ui = mcrfpy.sceneUI("test") # Add visible content - a white frame on default background frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200), diff --git a/tests/unit/test_animation_callback_simple.py b/tests/unit/test_animation_callback_simple.py index 9a759c6..f3c0819 100644 --- a/tests/unit/test_animation_callback_simple.py +++ b/tests/unit/test_animation_callback_simple.py @@ -17,12 +17,12 @@ def my_callback(anim, target): def setup_and_run(): """Set up scene and run animation with callback""" # Create scene - callback_demo = mcrfpy.Scene("callback_demo") - callback_demo.activate() + mcrfpy.createScene("callback_demo") + mcrfpy.setScene("callback_demo") # Create a frame to animate frame = mcrfpy.Frame((100, 100), (200, 200), fill_color=(255, 0, 0)) - ui = callback_demo.children + ui = mcrfpy.sceneUI("callback_demo") ui.append(frame) # Create animation with callback @@ -42,7 +42,7 @@ def check_result(runtime): # Test 2: Animation without callback print("\nTesting animation without callback...") - ui = callback_demo.children + ui = mcrfpy.sceneUI("callback_demo") frame = ui[0] anim2 = mcrfpy.Animation("y", 300.0, 0.5, "linear") diff --git a/tests/unit/test_animation_chaining.py b/tests/unit/test_animation_chaining.py index 2e069dd..7b3700a 100644 --- a/tests/unit/test_animation_chaining.py +++ b/tests/unit/test_animation_chaining.py @@ -67,7 +67,7 @@ class PathAnimator: self._animate_next_step() # Create test scene -chain_test = mcrfpy.Scene("chain_test") +mcrfpy.createScene("chain_test") # Create grid grid = mcrfpy.Grid(grid_x=20, grid_y=15) @@ -97,7 +97,7 @@ enemy = mcrfpy.Entity((17, 12), grid=grid) enemy.sprite_index = 69 # E # UI setup -ui = chain_test.children +ui = mcrfpy.sceneUI("chain_test") ui.append(grid) grid.position = (100, 100) grid.size = (600, 450) @@ -201,8 +201,8 @@ def handle_input(key, state): info.text = "Positions reset" # Setup -chain_test.activate() -chain_test.on_key = handle_input +mcrfpy.setScene("chain_test") +mcrfpy.keypressScene(handle_input) # Camera update timer mcrfpy.setTimer("cam_update", update_camera, 100) diff --git a/tests/unit/test_animation_debug.py b/tests/unit/test_animation_debug.py index 9f1c4ce..16c21a7 100644 --- a/tests/unit/test_animation_debug.py +++ b/tests/unit/test_animation_debug.py @@ -59,7 +59,7 @@ class AnimationTracker: mcrfpy.delTimer(f"check_{self.name}") # Create test scene -anim_debug = mcrfpy.Scene("anim_debug") +mcrfpy.createScene("anim_debug") # Simple grid grid = mcrfpy.Grid(grid_x=15, grid_y=10) @@ -75,7 +75,7 @@ entity = mcrfpy.Entity((5, 5), grid=grid) entity.sprite_index = 64 # UI -ui = anim_debug.children +ui = mcrfpy.sceneUI("anim_debug") ui.append(grid) grid.position = (100, 150) grid.size = (450, 300) @@ -215,8 +215,8 @@ def handle_input(key, state): print("Reset entity and cleared log") # Setup -anim_debug.activate() -anim_debug.on_key = handle_input +mcrfpy.setScene("anim_debug") +mcrfpy.keypressScene(handle_input) mcrfpy.setTimer("update", update_display, 100) print("Animation Debug Tool") diff --git a/tests/unit/test_animation_immediate.py b/tests/unit/test_animation_immediate.py index d9127d1..e78c63c 100644 --- a/tests/unit/test_animation_immediate.py +++ b/tests/unit/test_animation_immediate.py @@ -6,11 +6,11 @@ Test Animation creation without timer import mcrfpy print("1. Creating scene...") -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") print("2. Getting UI...") -ui = test.children +ui = mcrfpy.sceneUI("test") print("3. Creating frame...") frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) diff --git a/tests/unit/test_animation_property_locking.py b/tests/unit/test_animation_property_locking.py index 694206e..3b25892 100644 --- a/tests/unit/test_animation_property_locking.py +++ b/tests/unit/test_animation_property_locking.py @@ -30,7 +30,7 @@ def test_result(name, passed, details=""): def test_1_replace_mode_default(): """Test that REPLACE mode is the default and works correctly""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -55,7 +55,7 @@ def test_1_replace_mode_default(): def test_2_replace_mode_explicit(): """Test explicit REPLACE mode""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -73,7 +73,7 @@ def test_2_replace_mode_explicit(): def test_3_queue_mode(): """Test QUEUE mode - animation should be queued""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -95,7 +95,7 @@ def test_3_queue_mode(): def test_4_error_mode(): """Test ERROR mode - should raise RuntimeError""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -120,7 +120,7 @@ def test_4_error_mode(): def test_5_invalid_conflict_mode(): """Test that invalid conflict_mode raises ValueError""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -140,7 +140,7 @@ def test_5_invalid_conflict_mode(): def test_6_different_properties_no_conflict(): """Test that different properties can animate simultaneously""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -164,7 +164,7 @@ def test_6_different_properties_no_conflict(): def test_7_different_targets_no_conflict(): """Test that same property on different targets doesn't conflict""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame1 = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) frame2 = mcrfpy.Frame(pos=(200, 200), size=(100, 100)) ui.append(frame1) @@ -187,7 +187,7 @@ def test_7_different_targets_no_conflict(): def test_8_replace_completes_old(): """Test that REPLACE mode completes the old animation's value""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100)) ui.append(frame) @@ -242,8 +242,8 @@ def run_all_tests(runtime): # Setup and run -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Start tests after a brief delay to allow scene to initialize mcrfpy.setTimer("start", run_all_tests, 100) diff --git a/tests/unit/test_animation_raii.py b/tests/unit/test_animation_raii.py index b7e556b..53de59b 100644 --- a/tests/unit/test_animation_raii.py +++ b/tests/unit/test_animation_raii.py @@ -29,7 +29,7 @@ def test_result(name, passed, details=""): def test_1_basic_animation(): """Test that basic animations still work""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -48,7 +48,7 @@ def test_1_basic_animation(): def test_2_remove_animated_object(): """Test removing object with active animation""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -72,7 +72,7 @@ def test_2_remove_animated_object(): def test_3_complete_animation(): """Test completing animation immediately""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) ui.append(frame) @@ -97,7 +97,7 @@ def test_4_multiple_animations_timer(): def create_animations(runtime): nonlocal success try: - ui = test.children + ui = mcrfpy.sceneUI("test") frame = mcrfpy.Frame(pos=(200, 200), size=(100, 100)) ui.append(frame) @@ -113,7 +113,7 @@ def test_4_multiple_animations_timer(): mcrfpy.setTimer("exit", lambda t: None, 100) # Clear scene - ui = test.children + ui = mcrfpy.sceneUI("test") while len(ui) > 0: ui.remove(len(ui) - 1) @@ -124,10 +124,10 @@ def test_5_scene_cleanup(): """Test that changing scenes cleans up animations""" try: # Create a second scene - test2 = mcrfpy.Scene("test2") + mcrfpy.createScene("test2") # Add animated objects to first scene - ui = test.children + ui = mcrfpy.sceneUI("test") for i in range(5): frame = mcrfpy.Frame(pos=(50 * i, 100), size=(40, 40)) ui.append(frame) @@ -135,10 +135,10 @@ def test_5_scene_cleanup(): anim.start(frame) # Switch scenes (animations should become invalid) - test2.activate() + mcrfpy.setScene("test2") # Switch back - test.activate() + mcrfpy.setScene("test") test_result("Scene change cleanup", True) except Exception as e: @@ -147,7 +147,7 @@ def test_5_scene_cleanup(): def test_6_animation_after_clear(): """Test animations after clearing UI""" try: - ui = test.children + ui = mcrfpy.sceneUI("test") # Create and animate frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) @@ -202,11 +202,11 @@ def print_results(runtime): mcrfpy.setTimer("exit", lambda t: sys.exit(0 if tests_failed == 0 else 1), 500) # Setup and run -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Add a background -ui = test.children +ui = mcrfpy.sceneUI("test") bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768)) bg.fill_color = mcrfpy.Color(20, 20, 30) ui.append(bg) diff --git a/tests/unit/test_animation_removal.py b/tests/unit/test_animation_removal.py index ec0191f..3aac09d 100644 --- a/tests/unit/test_animation_removal.py +++ b/tests/unit/test_animation_removal.py @@ -10,7 +10,7 @@ def clear_and_recreate(runtime): """Clear UI and recreate - mimics demo switching""" print(f"\nTimer called at {runtime}") - ui = test.children + ui = mcrfpy.sceneUI("test") # Remove all but first 2 items (like clear_demo_objects) print(f"Scene has {len(ui)} elements before clearing") @@ -37,9 +37,9 @@ def clear_and_recreate(runtime): # Create initial scene print("Creating scene...") -test = mcrfpy.Scene("test") -test.activate() -ui = test.children +mcrfpy.createScene("test") +mcrfpy.setScene("test") +ui = mcrfpy.sceneUI("test") # Add title and subtitle (to preserve during clearing) title = mcrfpy.Caption(pos=(400, 20), text="Test Title") diff --git a/tests/unit/test_astar.py b/tests/unit/test_astar.py index db49633..f0afadb 100644 --- a/tests/unit/test_astar.py +++ b/tests/unit/test_astar.py @@ -14,7 +14,7 @@ print("A* Pathfinding Test") print("==================") # Create scene and grid -astar_test = mcrfpy.Scene("astar_test") +mcrfpy.createScene("astar_test") grid = mcrfpy.Grid(grid_x=20, grid_y=20) # Initialize grid - all walkable @@ -119,12 +119,12 @@ def visual_test(runtime): sys.exit(0) # Set up minimal UI for visual test -ui = astar_test.children +ui = mcrfpy.sceneUI("astar_test") ui.append(grid) grid.position = (50, 50) grid.size = (400, 400) -astar_test.activate() +mcrfpy.setScene("astar_test") mcrfpy.setTimer("visual", visual_test, 100) print("\nStarting visual test...") \ No newline at end of file diff --git a/tests/unit/test_audio_cleanup.py b/tests/unit/test_audio_cleanup.py index db49bae..a2ca61f 100644 --- a/tests/unit/test_audio_cleanup.py +++ b/tests/unit/test_audio_cleanup.py @@ -6,6 +6,6 @@ import sys print("Testing audio cleanup...") # Create a scene and immediately exit -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") print("Exiting now...") sys.exit(0) \ No newline at end of file diff --git a/tests/unit/test_bounds_hit_testing.py b/tests/unit/test_bounds_hit_testing.py index a4ce7e7..93edd61 100644 --- a/tests/unit/test_bounds_hit_testing.py +++ b/tests/unit/test_bounds_hit_testing.py @@ -8,8 +8,8 @@ def test_bounds_property(): """Test bounds property returns correct local bounds""" print("Testing bounds property...") - test_bounds = mcrfpy.Scene("test_bounds") - ui = test_bounds.children + mcrfpy.createScene("test_bounds") + ui = mcrfpy.sceneUI("test_bounds") frame = mcrfpy.Frame(pos=(50, 75), size=(200, 150)) ui.append(frame) @@ -27,8 +27,8 @@ def test_global_bounds_no_parent(): """Test global_bounds equals bounds when no parent""" print("Testing global_bounds without parent...") - test_gb1 = mcrfpy.Scene("test_gb1") - ui = test_gb1.children + mcrfpy.createScene("test_gb1") + ui = mcrfpy.sceneUI("test_gb1") frame = mcrfpy.Frame(pos=(100, 100), size=(50, 50)) ui.append(frame) @@ -45,8 +45,8 @@ def test_global_bounds_with_parent(): """Test global_bounds correctly adds parent offset""" print("Testing global_bounds with parent...") - test_gb2 = mcrfpy.Scene("test_gb2") - ui = test_gb2.children + mcrfpy.createScene("test_gb2") + ui = mcrfpy.sceneUI("test_gb2") parent = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) ui.append(parent) @@ -67,8 +67,8 @@ def test_global_bounds_nested(): """Test global_bounds with deeply nested hierarchy""" print("Testing global_bounds with nested hierarchy...") - test_gb3 = mcrfpy.Scene("test_gb3") - ui = test_gb3.children + mcrfpy.createScene("test_gb3") + ui = mcrfpy.sceneUI("test_gb3") # Create 3-level hierarchy root = mcrfpy.Frame(pos=(10, 10), size=(300, 300)) @@ -92,8 +92,8 @@ def test_all_drawable_types_have_bounds(): """Test that all drawable types have bounds properties""" print("Testing bounds on all drawable types...") - test_types = mcrfpy.Scene("test_types") - ui = test_types.children + mcrfpy.createScene("test_types") + ui = mcrfpy.sceneUI("test_types") types_to_test = [ ("Frame", mcrfpy.Frame(pos=(0, 0), size=(100, 100))), diff --git a/tests/unit/test_builtin_context.py b/tests/unit/test_builtin_context.py index bac8882..271f8e6 100644 --- a/tests/unit/test_builtin_context.py +++ b/tests/unit/test_builtin_context.py @@ -37,7 +37,7 @@ print() print("Test 3: Function creating mcrfpy objects") def create_scene(): try: - test = mcrfpy.Scene("test") + mcrfpy.createScene("test") print(" ✓ Created scene") # Now try range @@ -69,7 +69,7 @@ print() print("Test 4: Exact failing pattern") def failing_pattern(): try: - failing_test = mcrfpy.Scene("failing_test") + mcrfpy.createScene("failing_test") grid = mcrfpy.Grid(grid_x=14, grid_y=10) # This is where it fails in the demos diff --git a/tests/unit/test_color_fix.py b/tests/unit/test_color_fix.py index 9728544..d9fa7dc 100644 --- a/tests/unit/test_color_fix.py +++ b/tests/unit/test_color_fix.py @@ -7,7 +7,7 @@ print("Testing Color fix...") # Test 1: Create grid try: - test = mcrfpy.Scene("test") + mcrfpy.createScene("test") grid = mcrfpy.Grid(grid_x=5, grid_y=5) print("✓ Grid created") except Exception as e: diff --git a/tests/unit/test_color_helpers.py b/tests/unit/test_color_helpers.py index 95e4aa3..49e8b65 100644 --- a/tests/unit/test_color_helpers.py +++ b/tests/unit/test_color_helpers.py @@ -178,5 +178,5 @@ def test_color_helpers(runtime): sys.exit(0 if all_pass else 1) # Run test -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") mcrfpy.setTimer("test", test_color_helpers, 100) \ No newline at end of file diff --git a/tests/unit/test_color_operations.py b/tests/unit/test_color_operations.py index 8ce5cf0..61c278d 100644 --- a/tests/unit/test_color_operations.py +++ b/tests/unit/test_color_operations.py @@ -9,7 +9,7 @@ print("=" * 50) # Test 1: Basic Color assignment print("Test 1: Color assignment in grid") try: - test1 = mcrfpy.Scene("test1") + mcrfpy.createScene("test1") grid = mcrfpy.Grid(grid_x=25, grid_y=15) # Assign color to a cell @@ -27,7 +27,7 @@ except Exception as e: # Test 2: Multiple color assignments print("\nTest 2: Multiple color assignments") try: - test2 = mcrfpy.Scene("test2") + mcrfpy.createScene("test2") grid = mcrfpy.Grid(grid_x=25, grid_y=15) # Multiple properties including color @@ -54,7 +54,7 @@ print("\nTest 3: Exact pattern from dijkstra_demo_final.py") try: # Recreate the exact function def create_demo(): - dijkstra_demo = mcrfpy.Scene("dijkstra_demo") + mcrfpy.createScene("dijkstra_demo") # Create grid grid = mcrfpy.Grid(grid_x=25, grid_y=15) diff --git a/tests/unit/test_color_setter_bug.py b/tests/unit/test_color_setter_bug.py index 082e626..97b5b7d 100644 --- a/tests/unit/test_color_setter_bug.py +++ b/tests/unit/test_color_setter_bug.py @@ -9,7 +9,7 @@ print("=" * 50) # Test 1: Setting color with tuple (old way) print("Test 1: Setting color with tuple") try: - test1 = mcrfpy.Scene("test1") + mcrfpy.createScene("test1") grid = mcrfpy.Grid(grid_x=5, grid_y=5) # This should work (PyArg_ParseTuple expects tuple) @@ -26,7 +26,7 @@ print() # Test 2: Setting color with Color object (the bug) print("Test 2: Setting color with Color object") try: - test2 = mcrfpy.Scene("test2") + mcrfpy.createScene("test2") grid = mcrfpy.Grid(grid_x=5, grid_y=5) # This will fail in PyArg_ParseTuple but not report it @@ -45,7 +45,7 @@ print() # Test 3: Multiple color assignments print("Test 3: Multiple Color assignments (reproducing original bug)") try: - test3 = mcrfpy.Scene("test3") + mcrfpy.createScene("test3") grid = mcrfpy.Grid(grid_x=25, grid_y=15) # Do multiple color assignments diff --git a/tests/unit/test_dijkstra_pathfinding.py b/tests/unit/test_dijkstra_pathfinding.py index 7276e13..a28b103 100644 --- a/tests/unit/test_dijkstra_pathfinding.py +++ b/tests/unit/test_dijkstra_pathfinding.py @@ -16,7 +16,7 @@ import sys def create_test_grid(): """Create a test grid with obstacles""" - dijkstra_test = mcrfpy.Scene("dijkstra_test") + mcrfpy.createScene("dijkstra_test") # Create grid grid = mcrfpy.Grid(grid_x=20, grid_y=20) @@ -212,7 +212,7 @@ print("=====================================") # Set up scene grid = create_test_grid() -ui = dijkstra_test.children +ui = mcrfpy.sceneUI("dijkstra_test") ui.append(grid) # Add title @@ -224,4 +224,4 @@ ui.append(title) mcrfpy.setTimer("test", run_test, 100) # Show scene -dijkstra_test.activate() \ No newline at end of file +mcrfpy.setScene("dijkstra_test") \ No newline at end of file diff --git a/tests/unit/test_empty_animation_manager.py b/tests/unit/test_empty_animation_manager.py index 52955ae..c86905a 100644 --- a/tests/unit/test_empty_animation_manager.py +++ b/tests/unit/test_empty_animation_manager.py @@ -6,8 +6,8 @@ Test if AnimationManager crashes with no animations import mcrfpy print("Creating empty scene...") -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") print("Scene created, no animations added") print("Starting game loop in 100ms...") diff --git a/tests/unit/test_entity_animation.py b/tests/unit/test_entity_animation.py index d28a89a..2cf539e 100644 --- a/tests/unit/test_entity_animation.py +++ b/tests/unit/test_entity_animation.py @@ -11,7 +11,7 @@ import mcrfpy import sys # Create scene -test_anim = mcrfpy.Scene("test_anim") +mcrfpy.createScene("test_anim") # Create simple grid grid = mcrfpy.Grid(grid_x=15, grid_y=15) @@ -42,7 +42,7 @@ entity = mcrfpy.Entity((5, 5), grid=grid) entity.sprite_index = 64 # @ # UI setup -ui = test_anim.children +ui = mcrfpy.sceneUI("test_anim") ui.append(grid) grid.position = (100, 100) grid.size = (450, 450) # 15 * 30 pixels per cell @@ -182,8 +182,8 @@ def handle_input(key, state): print(f"Reset entity to ({entity.x}, {entity.y})") # Set scene -test_anim.activate() -test_anim.on_key = handle_input +mcrfpy.setScene("test_anim") +mcrfpy.keypressScene(handle_input) # Start position update timer mcrfpy.setTimer("update_pos", update_position_display, 200) diff --git a/tests/unit/test_entity_collection_remove.py b/tests/unit/test_entity_collection_remove.py index d6bbd83..0ae8068 100644 --- a/tests/unit/test_entity_collection_remove.py +++ b/tests/unit/test_entity_collection_remove.py @@ -12,11 +12,11 @@ def test_remove_by_entity(): # Create a test scene and grid scene_name = "test_entity_remove" - _scene = mcrfpy.Scene(scene_name) + mcrfpy.createScene(scene_name) # Create a grid (entities need a grid) grid = mcrfpy.Grid() # Default 2x2 grid is fine for testing - _scene.children.append(grid) # TODO: Replace _scene with correct Scene object + mcrfpy.sceneUI(scene_name).append(grid) # Get the entity collection entities = grid.entities diff --git a/tests/unit/test_entity_constructor.py b/tests/unit/test_entity_constructor.py index cf38d89..56f9463 100644 --- a/tests/unit/test_entity_constructor.py +++ b/tests/unit/test_entity_constructor.py @@ -2,8 +2,8 @@ import mcrfpy # Create scene and grid -test = mcrfpy.Scene("test") -ui = test.children +mcrfpy.createScene("test") +ui = mcrfpy.sceneUI("test") # Create texture and grid texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) diff --git a/tests/unit/test_entity_fix.py b/tests/unit/test_entity_fix.py index 6f35167..eef131b 100644 --- a/tests/unit/test_entity_fix.py +++ b/tests/unit/test_entity_fix.py @@ -27,7 +27,7 @@ print("UIEntity::setProperty for 'x' and 'y' properties.") print() # Create scene to demonstrate -fix_demo = mcrfpy.Scene("fix_demo") +mcrfpy.createScene("fix_demo") # Create grid grid = mcrfpy.Grid(grid_x=15, grid_y=10) @@ -49,7 +49,7 @@ entity = mcrfpy.Entity((2, 2), grid=grid) entity.sprite_index = 64 # @ # UI -ui = fix_demo.children +ui = mcrfpy.sceneUI("fix_demo") ui.append(grid) grid.position = (100, 150) grid.size = (450, 300) @@ -111,8 +111,8 @@ def handle_input(key, state): status.text = "Reset entity to (2,2)" # Setup -fix_demo.activate() -fix_demo.on_key = handle_input +mcrfpy.setScene("fix_demo") +mcrfpy.keypressScene(handle_input) mcrfpy.setTimer("update", update_display, 100) print("Ready to demonstrate the issue.") diff --git a/tests/unit/test_entity_path_to.py b/tests/unit/test_entity_path_to.py index 6d3bcc8..caeb4c1 100644 --- a/tests/unit/test_entity_path_to.py +++ b/tests/unit/test_entity_path_to.py @@ -7,7 +7,7 @@ print("Testing Entity.path_to() method...") print("=" * 50) # Create scene and grid -path_test = mcrfpy.Scene("path_test") +mcrfpy.createScene("path_test") grid = mcrfpy.Grid(grid_x=10, grid_y=10) # Set up a simple map with some walls diff --git a/tests/unit/test_entity_path_to_edge_cases.py b/tests/unit/test_entity_path_to_edge_cases.py index 2a3fcbd..ef67d8f 100644 --- a/tests/unit/test_entity_path_to_edge_cases.py +++ b/tests/unit/test_entity_path_to_edge_cases.py @@ -19,7 +19,7 @@ except Exception as e: # Test 2: Entity in grid with walls blocking path print("\nTest 2: Completely blocked path") -blocked_test = mcrfpy.Scene("blocked_test") +mcrfpy.createScene("blocked_test") grid = mcrfpy.Grid(grid_x=5, grid_y=5) # Make all tiles walkable first diff --git a/tests/unit/test_exact_failure.py b/tests/unit/test_exact_failure.py index d4215db..b4e5924 100644 --- a/tests/unit/test_exact_failure.py +++ b/tests/unit/test_exact_failure.py @@ -12,7 +12,7 @@ FLOOR_COLOR = mcrfpy.Color(200, 200, 220) def test_exact_pattern(): """Exact code from dijkstra_demo_final.py""" - dijkstra_demo = mcrfpy.Scene("dijkstra_demo") + mcrfpy.createScene("dijkstra_demo") # Create grid grid = mcrfpy.Grid(grid_x=25, grid_y=15) @@ -48,7 +48,7 @@ print("Test 2: Breaking it down step by step...") # Step 1: Scene and grid try: - test2 = mcrfpy.Scene("test2") + mcrfpy.createScene("test2") grid = mcrfpy.Grid(grid_x=25, grid_y=15) print(" ✓ Step 1: Scene and grid created") except Exception as e: diff --git a/tests/unit/test_frame_clipping.py b/tests/unit/test_frame_clipping.py index c568a1a..5917aca 100644 --- a/tests/unit/test_frame_clipping.py +++ b/tests/unit/test_frame_clipping.py @@ -22,7 +22,7 @@ def take_second_screenshot(runtime): def animate_frames(runtime): """Animate frames to demonstrate clipping""" mcrfpy.delTimer("animate") - scene = test.children + scene = mcrfpy.sceneUI("test") # Move child frames parent1 = scene[0] parent2 = scene[1] @@ -36,7 +36,7 @@ def test_clipping(runtime): print("Testing UIFrame clipping functionality...") - scene = test.children + scene = mcrfpy.sceneUI("test") # Create parent frame with clipping disabled (default) parent1 = Frame(pos=(50, 50), size=(200, 150), @@ -119,15 +119,15 @@ def test_clipping(runtime): def handle_keypress(key, modifiers): if key == "c": - scene = test.children + scene = mcrfpy.sceneUI("test") parent1 = scene[0] parent1.clip_children = not parent1.clip_children print(f"Toggled parent1 clip_children to: {parent1.clip_children}") # Main execution print("Creating test scene...") -test = mcrfpy.Scene("test") -test.activate() -test.on_key = handle_keypress +mcrfpy.createScene("test") +mcrfpy.setScene("test") +mcrfpy.keypressScene(handle_keypress) mcrfpy.setTimer("test_clipping", test_clipping, 100) print("Test scheduled, running...") diff --git a/tests/unit/test_frame_clipping_advanced.py b/tests/unit/test_frame_clipping_advanced.py index 2b50e02..b7e9a33 100644 --- a/tests/unit/test_frame_clipping_advanced.py +++ b/tests/unit/test_frame_clipping_advanced.py @@ -12,7 +12,7 @@ def test_nested_clipping(runtime): print("Testing advanced UIFrame clipping with nested frames...") # Create test scene - scene = test.children + scene = mcrfpy.sceneUI("test") # Create outer frame with clipping enabled outer = Frame(pos=(50, 50), size=(400, 300), @@ -94,8 +94,8 @@ def test_nested_clipping(runtime): # Main execution print("Creating advanced test scene...") -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule the test mcrfpy.setTimer("test_nested_clipping", test_nested_clipping, 100) diff --git a/tests/unit/test_grid_background.py b/tests/unit/test_grid_background.py index 4fee8b9..b74daf4 100644 --- a/tests/unit/test_grid_background.py +++ b/tests/unit/test_grid_background.py @@ -9,8 +9,8 @@ def test_grid_background(): print("Testing Grid Background Color...") # Create a test scene - test = mcrfpy.Scene("test") - ui = test.children + mcrfpy.createScene("test") + ui = mcrfpy.sceneUI("test") # Create a grid with default background grid = mcrfpy.Grid(pos=(50, 50), size=(400, 300), grid_size=(20, 15)) @@ -40,7 +40,7 @@ def test_grid_background(): info_frame.children.append(color_display) # Activate the scene - test.activate() + mcrfpy.setScene("test") def run_tests(dt): """Run background color tests""" diff --git a/tests/unit/test_grid_cell_events.py b/tests/unit/test_grid_cell_events.py index 9620d51..f367fbf 100644 --- a/tests/unit/test_grid_cell_events.py +++ b/tests/unit/test_grid_cell_events.py @@ -9,8 +9,8 @@ def test_properties(): """Test grid cell event properties exist and work""" print("Testing grid cell event properties...") - test_props = mcrfpy.Scene("test_props") - ui = test_props.children + mcrfpy.createScene("test_props") + ui = mcrfpy.sceneUI("test_props") grid = mcrfpy.Grid(grid_size=(5, 5), pos=(100, 100), size=(200, 200)) ui.append(grid) @@ -45,9 +45,9 @@ def test_cell_hover(): """Test cell hover events""" print("Testing cell hover events...") - test_hover = mcrfpy.Scene("test_hover") - ui = test_hover.children - test_hover.activate() + mcrfpy.createScene("test_hover") + ui = mcrfpy.sceneUI("test_hover") + mcrfpy.setScene("test_hover") grid = mcrfpy.Grid(grid_size=(5, 5), pos=(100, 100), size=(200, 200)) ui.append(grid) @@ -89,9 +89,9 @@ def test_cell_click(): """Test cell click events""" print("Testing cell click events...") - test_click = mcrfpy.Scene("test_click") - ui = test_click.children - test_click.activate() + mcrfpy.createScene("test_click") + ui = mcrfpy.sceneUI("test_click") + mcrfpy.setScene("test_click") grid = mcrfpy.Grid(grid_size=(5, 5), pos=(100, 100), size=(200, 200)) ui.append(grid) diff --git a/tests/unit/test_grid_children.py b/tests/unit/test_grid_children.py index 26f7f39..5615886 100644 --- a/tests/unit/test_grid_children.py +++ b/tests/unit/test_grid_children.py @@ -18,7 +18,7 @@ def run_test(runtime): mcrfpy.delTimer("test") # Get the scene UI - ui = test.children + ui = mcrfpy.sceneUI("test") # Create a grid without texture (uses default 16x16 cells) print("Test 1: Creating Grid with children...") @@ -122,8 +122,8 @@ def run_test(runtime): mcrfpy.setTimer("screenshot", take_screenshot, 100) # Create a test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test to run after game loop starts mcrfpy.setTimer("test", run_test, 50) diff --git a/tests/unit/test_grid_creation.py b/tests/unit/test_grid_creation.py index 3f37b36..c4d0b59 100644 --- a/tests/unit/test_grid_creation.py +++ b/tests/unit/test_grid_creation.py @@ -8,7 +8,7 @@ print("Testing grid creation...") # First create scene try: - test = mcrfpy.Scene("test") + mcrfpy.createScene("test") print("✓ Created scene") except Exception as e: print(f"✗ Failed to create scene: {e}") diff --git a/tests/unit/test_grid_error.py b/tests/unit/test_grid_error.py index 0b437ba..fdbfb51 100644 --- a/tests/unit/test_grid_error.py +++ b/tests/unit/test_grid_error.py @@ -8,7 +8,7 @@ import traceback print("Testing grid creation with detailed error...") # Create scene first -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") # Try to create grid and get detailed error try: diff --git a/tests/unit/test_grid_iteration.py b/tests/unit/test_grid_iteration.py index 218ab85..4a80e0c 100644 --- a/tests/unit/test_grid_iteration.py +++ b/tests/unit/test_grid_iteration.py @@ -9,7 +9,7 @@ print("=" * 50) # Test 1: Basic grid.at() calls print("Test 1: Basic grid.at() calls") try: - test1 = mcrfpy.Scene("test1") + mcrfpy.createScene("test1") grid = mcrfpy.Grid(grid_x=5, grid_y=5) # Single call @@ -32,7 +32,7 @@ print() # Test 2: Grid.at() in a loop print("Test 2: Grid.at() in simple loop") try: - test2 = mcrfpy.Scene("test2") + mcrfpy.createScene("test2") grid = mcrfpy.Grid(grid_x=5, grid_y=5) for i in range(3): @@ -50,7 +50,7 @@ print() # Test 3: Nested loops with grid.at() print("Test 3: Nested loops with grid.at()") try: - test3 = mcrfpy.Scene("test3") + mcrfpy.createScene("test3") grid = mcrfpy.Grid(grid_x=5, grid_y=5) for y in range(3): @@ -68,7 +68,7 @@ print() # Test 4: Exact pattern from failing code print("Test 4: Exact failing pattern") try: - test4 = mcrfpy.Scene("test4") + mcrfpy.createScene("test4") grid = mcrfpy.Grid(grid_x=25, grid_y=15) grid.fill_color = mcrfpy.Color(0, 0, 0) @@ -109,7 +109,7 @@ print() # Test 5: Is it related to the number of grid.at() calls? print("Test 5: Testing grid.at() call limits") try: - test5 = mcrfpy.Scene("test5") + mcrfpy.createScene("test5") grid = mcrfpy.Grid(grid_x=10, grid_y=10) count = 0 diff --git a/tests/unit/test_headless_benchmark.py b/tests/unit/test_headless_benchmark.py index 898dc58..c6f328e 100644 --- a/tests/unit/test_headless_benchmark.py +++ b/tests/unit/test_headless_benchmark.py @@ -17,9 +17,9 @@ def run_tests(): print("=== Headless Benchmark Tests ===\n") # Create a test scene - benchmark_test = mcrfpy.Scene("benchmark_test") - benchmark_test.activate() - ui = benchmark_test.children + mcrfpy.createScene("benchmark_test") + mcrfpy.setScene("benchmark_test") + ui = mcrfpy.sceneUI("benchmark_test") # Add some UI elements to have something to render frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) diff --git a/tests/unit/test_headless_click.py b/tests/unit/test_headless_click.py index 5078bed..eaddec1 100644 --- a/tests/unit/test_headless_click.py +++ b/tests/unit/test_headless_click.py @@ -13,9 +13,9 @@ def test_headless_click(): """Test that clicks work in headless mode via automation API""" print("Testing headless click events...") - test_click = mcrfpy.Scene("test_click") - ui = test_click.children - test_click.activate() + mcrfpy.createScene("test_click") + ui = mcrfpy.sceneUI("test_click") + mcrfpy.setScene("test_click") # Create a frame at known position frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) @@ -64,9 +64,9 @@ def test_click_miss(): click_count = 0 click_positions = [] - test_miss = mcrfpy.Scene("test_miss") - ui = test_miss.children - test_miss.activate() + mcrfpy.createScene("test_miss") + ui = mcrfpy.sceneUI("test_miss") + mcrfpy.setScene("test_miss") # Create a frame at known position frame = mcrfpy.Frame(pos=(100, 100), size=(100, 100)) diff --git a/tests/unit/test_headless_detection.py b/tests/unit/test_headless_detection.py index 658d93b..babe65d 100644 --- a/tests/unit/test_headless_detection.py +++ b/tests/unit/test_headless_detection.py @@ -6,9 +6,9 @@ from mcrfpy import automation import sys # Create scene -detect_test = mcrfpy.Scene("detect_test") -ui = detect_test.children -detect_test.activate() +mcrfpy.createScene("detect_test") +ui = mcrfpy.sceneUI("detect_test") +mcrfpy.setScene("detect_test") # Create a frame frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) diff --git a/tests/unit/test_headless_modes.py b/tests/unit/test_headless_modes.py index ab80a4f..3e36658 100644 --- a/tests/unit/test_headless_modes.py +++ b/tests/unit/test_headless_modes.py @@ -5,9 +5,9 @@ import mcrfpy import sys # Create scene -headless_test = mcrfpy.Scene("headless_test") -ui = headless_test.children -headless_test.activate() +mcrfpy.createScene("headless_test") +ui = mcrfpy.sceneUI("headless_test") +mcrfpy.setScene("headless_test") # Create a visible indicator frame = mcrfpy.Frame(pos=(200, 200), size=(400, 200)) diff --git a/tests/unit/test_metrics.py b/tests/unit/test_metrics.py index 06b6f24..885e2c5 100644 --- a/tests/unit/test_metrics.py +++ b/tests/unit/test_metrics.py @@ -108,11 +108,11 @@ def test_metrics(runtime): # Set up test scene print("Setting up metrics test scene...") -metrics_test = mcrfpy.Scene("metrics_test") -metrics_test.activate() +mcrfpy.createScene("metrics_test") +mcrfpy.setScene("metrics_test") # Add some UI elements -ui = metrics_test.children +ui = mcrfpy.sceneUI("metrics_test") # Create various UI elements frame1 = mcrfpy.Frame(pos=(10, 10), size=(200, 150)) diff --git a/tests/unit/test_mouse_enter_exit.py b/tests/unit/test_mouse_enter_exit.py index c2fa7a4..eb033a8 100644 --- a/tests/unit/test_mouse_enter_exit.py +++ b/tests/unit/test_mouse_enter_exit.py @@ -15,8 +15,8 @@ def test_callback_assignment(): """Test that on_enter and on_exit callbacks can be assigned""" print("Testing callback assignment...") - test_assign = mcrfpy.Scene("test_assign") - ui = test_assign.children + mcrfpy.createScene("test_assign") + ui = mcrfpy.sceneUI("test_assign") frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) ui.append(frame) @@ -50,8 +50,8 @@ def test_hovered_property(): """Test that hovered property exists and is initially False""" print("Testing hovered property...") - test_hovered = mcrfpy.Scene("test_hovered") - ui = test_hovered.children + mcrfpy.createScene("test_hovered") + ui = mcrfpy.sceneUI("test_hovered") frame = mcrfpy.Frame(pos=(50, 50), size=(100, 100)) ui.append(frame) @@ -77,8 +77,8 @@ def test_all_types_have_events(): """Test that all drawable types have on_enter/on_exit properties""" print("Testing events on all drawable types...") - test_types = mcrfpy.Scene("test_types") - ui = test_types.children + mcrfpy.createScene("test_types") + ui = mcrfpy.sceneUI("test_types") types_to_test = [ ("Frame", mcrfpy.Frame(pos=(0, 0), size=(100, 100))), @@ -121,9 +121,9 @@ def test_enter_exit_simulation(): enter_positions = [] exit_positions = [] - test_sim = mcrfpy.Scene("test_sim") - ui = test_sim.children - test_sim.activate() + mcrfpy.createScene("test_sim") + ui = mcrfpy.sceneUI("test_sim") + mcrfpy.setScene("test_sim") # Create a frame at known position frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) diff --git a/tests/unit/test_no_arg_constructors.py b/tests/unit/test_no_arg_constructors.py index 238d136..b5f18a8 100644 --- a/tests/unit/test_no_arg_constructors.py +++ b/tests/unit/test_no_arg_constructors.py @@ -85,7 +85,7 @@ def test_ui_constructors(runtime): sys.exit(0) # Create a basic scene so the game can start -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") # Schedule the test to run after game initialization mcrfpy.setTimer("test", test_ui_constructors, 100) \ No newline at end of file diff --git a/tests/unit/test_on_move.py b/tests/unit/test_on_move.py index 957c1a7..b1e78e7 100644 --- a/tests/unit/test_on_move.py +++ b/tests/unit/test_on_move.py @@ -9,8 +9,8 @@ def test_on_move_property(): """Test that on_move property exists and can be assigned""" print("Testing on_move property...") - test_move_prop = mcrfpy.Scene("test_move_prop") - ui = test_move_prop.children + mcrfpy.createScene("test_move_prop") + ui = mcrfpy.sceneUI("test_move_prop") frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) ui.append(frame) @@ -33,9 +33,9 @@ def test_on_move_fires(): """Test that on_move fires when mouse moves within bounds""" print("Testing on_move callback firing...") - test_move = mcrfpy.Scene("test_move") - ui = test_move.children - test_move.activate() + mcrfpy.createScene("test_move") + ui = mcrfpy.sceneUI("test_move") + mcrfpy.setScene("test_move") # Create a frame at known position frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) @@ -78,9 +78,9 @@ def test_on_move_not_outside(): """Test that on_move doesn't fire when mouse is outside bounds""" print("Testing on_move doesn't fire outside bounds...") - test_move_outside = mcrfpy.Scene("test_move_outside") - ui = test_move_outside.children - test_move_outside.activate() + mcrfpy.createScene("test_move_outside") + ui = mcrfpy.sceneUI("test_move_outside") + mcrfpy.setScene("test_move_outside") # Frame at 100-300, 100-300 frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) @@ -117,8 +117,8 @@ def test_all_types_have_on_move(): """Test that all drawable types have on_move property""" print("Testing on_move on all drawable types...") - test_types = mcrfpy.Scene("test_types") - ui = test_types.children + mcrfpy.createScene("test_types") + ui = mcrfpy.sceneUI("test_types") types_to_test = [ ("Frame", mcrfpy.Frame(pos=(0, 0), size=(100, 100))), diff --git a/tests/unit/test_oneline_for.py b/tests/unit/test_oneline_for.py index 6ba30e1..94e336b 100644 --- a/tests/unit/test_oneline_for.py +++ b/tests/unit/test_oneline_for.py @@ -47,7 +47,7 @@ print() # Test 4: After creating mcrfpy objects print("Test 4: After creating mcrfpy scene/grid") try: - test = mcrfpy.Scene("test") + mcrfpy.createScene("test") grid = mcrfpy.Grid(grid_x=10, grid_y=10) walls = [] @@ -63,7 +63,7 @@ print() # Test 5: Check line number in error print("Test 5: Checking exact error location") def test_exact_pattern(): - dijkstra_demo = mcrfpy.Scene("dijkstra_demo") + mcrfpy.createScene("dijkstra_demo") grid = mcrfpy.Grid(grid_x=25, grid_y=15) grid.fill_color = mcrfpy.Color(0, 0, 0) diff --git a/tests/unit/test_parent_child_system.py b/tests/unit/test_parent_child_system.py index 3f2d6dd..2cdd4c9 100644 --- a/tests/unit/test_parent_child_system.py +++ b/tests/unit/test_parent_child_system.py @@ -13,8 +13,8 @@ def test_parent_property(): print("Testing parent property...") # Create scene and get UI - test = mcrfpy.Scene("test") - ui = test.children + mcrfpy.createScene("test") + ui = mcrfpy.sceneUI("test") # Create a parent frame parent = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) @@ -44,8 +44,8 @@ def test_global_position(): print("Testing global_position property...") # Create scene and get UI - test2 = mcrfpy.Scene("test2") - ui = test2.children + mcrfpy.createScene("test2") + ui = mcrfpy.sceneUI("test2") # Create nested hierarchy: # root (50, 50) @@ -81,8 +81,8 @@ def test_parent_changes_on_move(): """Test that moving child to different parent updates parent reference""" print("Testing parent changes on move...") - test3 = mcrfpy.Scene("test3") - ui = test3.children + mcrfpy.createScene("test3") + ui = mcrfpy.sceneUI("test3") parent1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100), fill_color=(255, 0, 0, 255)) parent2 = mcrfpy.Frame(pos=(200, 0), size=(100, 100), fill_color=(0, 255, 0, 255)) @@ -116,8 +116,8 @@ def test_remove_clears_parent(): """Test that removing child clears parent reference""" print("Testing remove clears parent...") - test4 = mcrfpy.Scene("test4") - ui = test4.children + mcrfpy.createScene("test4") + ui = mcrfpy.sceneUI("test4") parent = mcrfpy.Frame(pos=(0, 0), size=(100, 100)) ui.append(parent) @@ -140,8 +140,8 @@ def test_scene_level_elements(): """Test that scene-level elements have no parent""" print("Testing scene-level elements...") - test5 = mcrfpy.Scene("test5") - ui = test5.children + mcrfpy.createScene("test5") + ui = mcrfpy.sceneUI("test5") frame = mcrfpy.Frame(pos=(10, 10), size=(50, 50)) ui.append(frame) @@ -160,8 +160,8 @@ def test_all_drawable_types(): """Test parent/global_position on all drawable types""" print("Testing all drawable types...") - test6 = mcrfpy.Scene("test6") - ui = test6.children + mcrfpy.createScene("test6") + ui = mcrfpy.sceneUI("test6") parent = mcrfpy.Frame(pos=(100, 100), size=(300, 300)) ui.append(parent) @@ -190,8 +190,8 @@ def test_parent_setter(): """Test parent property setter (assign parent directly)""" print("Testing parent setter...") - test7 = mcrfpy.Scene("test7") - ui = test7.children + mcrfpy.createScene("test7") + ui = mcrfpy.sceneUI("test7") # Create parent frame and child parent = mcrfpy.Frame(pos=(100, 100), size=(200, 200)) @@ -223,8 +223,8 @@ def test_reparenting_via_setter(): """Test moving a child from one parent to another via setter""" print("Testing reparenting via setter...") - test8 = mcrfpy.Scene("test8") - ui = test8.children + mcrfpy.createScene("test8") + ui = mcrfpy.sceneUI("test8") parent1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100)) parent2 = mcrfpy.Frame(pos=(200, 200), size=(100, 100)) diff --git a/tests/unit/test_path_colors.py b/tests/unit/test_path_colors.py index 0a95932..1bcd9cd 100644 --- a/tests/unit/test_path_colors.py +++ b/tests/unit/test_path_colors.py @@ -8,7 +8,7 @@ print("Testing path color changes...") print("=" * 50) # Create scene and small grid -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") grid = mcrfpy.Grid(grid_x=5, grid_y=5) # Add color layer for cell coloring @@ -75,12 +75,12 @@ def check_visual(runtime): sys.exit(0) # Set up minimal UI to test rendering -ui = test.children +ui = mcrfpy.sceneUI("test") ui.append(grid) grid.position = (50, 50) grid.size = (250, 250) -test.activate() +mcrfpy.setScene("test") mcrfpy.setTimer("check", check_visual, 500) print("\nStarting render test...") \ No newline at end of file diff --git a/tests/unit/test_pathfinding_integration.py b/tests/unit/test_pathfinding_integration.py index a5d3836..a27f6a5 100644 --- a/tests/unit/test_pathfinding_integration.py +++ b/tests/unit/test_pathfinding_integration.py @@ -8,7 +8,7 @@ print("Testing pathfinding integration...") print("=" * 50) # Create scene and grid -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") grid = mcrfpy.Grid(grid_x=10, grid_y=10) # Initialize grid diff --git a/tests/unit/test_profiler_quick.py b/tests/unit/test_profiler_quick.py index 5ab0a2b..2aa5265 100644 --- a/tests/unit/test_profiler_quick.py +++ b/tests/unit/test_profiler_quick.py @@ -5,9 +5,9 @@ import mcrfpy import sys # Create a simple scene -test = mcrfpy.Scene("test") -test.activate() -ui = test.children +mcrfpy.createScene("test") +mcrfpy.setScene("test") +ui = mcrfpy.sceneUI("test") # Create a small grid grid = mcrfpy.Grid( diff --git a/tests/unit/test_properties_quick.py b/tests/unit/test_properties_quick.py index 1a6bb69..e16774a 100644 --- a/tests/unit/test_properties_quick.py +++ b/tests/unit/test_properties_quick.py @@ -53,5 +53,5 @@ def test_properties(runtime): sys.exit(0) -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") mcrfpy.setTimer("test_properties", test_properties, 100) \ No newline at end of file diff --git a/tests/unit/test_python_object_cache.py b/tests/unit/test_python_object_cache.py index 6d882f5..e7c2831 100644 --- a/tests/unit/test_python_object_cache.py +++ b/tests/unit/test_python_object_cache.py @@ -36,7 +36,7 @@ def run_tests(runtime): # Test 2: Create instance and add to scene frame = MyFrame(50, 50) - scene_ui = test_scene.children + scene_ui = mcrfpy.sceneUI("test_scene") scene_ui.append(frame) # Test 3: Retrieve from collection and check type @@ -142,8 +142,8 @@ def run_tests(runtime): sys.exit(0 if test_passed else 1) # Create test scene -test_scene = mcrfpy.Scene("test_scene") -test_scene.activate() +mcrfpy.createScene("test_scene") +mcrfpy.setScene("test_scene") # Schedule tests to run after game loop starts mcrfpy.setTimer("test", run_tests, 100) diff --git a/tests/unit/test_range_25_bug.py b/tests/unit/test_range_25_bug.py index 3dc051d..2d5826a 100644 --- a/tests/unit/test_range_25_bug.py +++ b/tests/unit/test_range_25_bug.py @@ -18,7 +18,7 @@ except Exception as e: # Test 2: range(25) after creating scene/grid print("\nTest 2: range(25) after creating 25x15 grid") try: - test = mcrfpy.Scene("test") + mcrfpy.createScene("test") grid = mcrfpy.Grid(grid_x=25, grid_y=15) for i in range(25): @@ -30,7 +30,7 @@ except Exception as e: # Test 3: The killer combination print("\nTest 3: range(25) after 15x25 grid.at() operations") try: - test3 = mcrfpy.Scene("test3") + mcrfpy.createScene("test3") grid = mcrfpy.Grid(grid_x=25, grid_y=15) # Do the nested loop that triggers the bug @@ -54,7 +54,7 @@ except Exception as e: # Test 4: Does range(24) still work? print("\nTest 4: range(24) after same operations") try: - test4 = mcrfpy.Scene("test4") + mcrfpy.createScene("test4") grid = mcrfpy.Grid(grid_x=25, grid_y=15) for y in range(15): @@ -76,7 +76,7 @@ except Exception as e: # Test 5: Is it about the specific combination of 15 and 25? print("\nTest 5: Different grid dimensions") try: - test5 = mcrfpy.Scene("test5") + mcrfpy.createScene("test5") grid = mcrfpy.Grid(grid_x=30, grid_y=20) for y in range(20): diff --git a/tests/unit/test_range_threshold.py b/tests/unit/test_range_threshold.py index fd04a19..c1737f2 100644 --- a/tests/unit/test_range_threshold.py +++ b/tests/unit/test_range_threshold.py @@ -69,7 +69,7 @@ print("Testing if it's about grid size vs range size...") try: # Small grid, large range - test_small_grid = mcrfpy.Scene("test_small_grid") + mcrfpy.createScene("test_small_grid") grid = mcrfpy.Grid(grid_x=5, grid_y=5) # Do minimal grid operations @@ -85,7 +85,7 @@ except Exception as e: try: # Large grid, see what happens - test_large_grid = mcrfpy.Scene("test_large_grid") + mcrfpy.createScene("test_large_grid") grid = mcrfpy.Grid(grid_x=20, grid_y=20) # Do operations on large grid diff --git a/tests/unit/test_scene_properties.py b/tests/unit/test_scene_properties.py index cf31590..9557e54 100644 --- a/tests/unit/test_scene_properties.py +++ b/tests/unit/test_scene_properties.py @@ -4,7 +4,7 @@ import mcrfpy import sys # Create test scenes -test_scene = mcrfpy.Scene("test_scene") +mcrfpy.createScene("test_scene") def test_scene_pos(): """Test Scene pos property""" diff --git a/tests/unit/test_scene_transitions.py b/tests/unit/test_scene_transitions.py index 03dae42..ea541b6 100644 --- a/tests/unit/test_scene_transitions.py +++ b/tests/unit/test_scene_transitions.py @@ -5,65 +5,63 @@ import mcrfpy import sys import time -red_scene, blue_scene, green_scene, menu_scene = None, None, None, None # global scoping - def create_test_scenes(): """Create several test scenes with different colored backgrounds.""" - global red_scene, blue_scene, green_scene, menu_scene + # Scene 1: Red background - red_scene = mcrfpy.Scene("red_scene") - ui1 = red_scene.children + mcrfpy.createScene("red_scene") + ui1 = mcrfpy.sceneUI("red_scene") bg1 = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(255, 0, 0, 255)) - label1 = mcrfpy.Caption(pos=(512, 384), text="RED SCENE", font=mcrfpy.default_font) + label1 = mcrfpy.Caption(pos=(512, 384), text="RED SCENE", font=mcrfpy.Font.font_ui) label1.fill_color = mcrfpy.Color(255, 255, 255, 255) ui1.append(bg1) ui1.append(label1) # Scene 2: Blue background - blue_scene = mcrfpy.Scene("blue_scene") - ui2 = blue_scene.children + mcrfpy.createScene("blue_scene") + ui2 = mcrfpy.sceneUI("blue_scene") bg2 = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(0, 0, 255, 255)) - label2 = mcrfpy.Caption(pos=(512, 384), text="BLUE SCENE", font=mcrfpy.default_font) + label2 = mcrfpy.Caption(pos=(512, 384), text="BLUE SCENE", font=mcrfpy.Font.font_ui) label2.fill_color = mcrfpy.Color(255, 255, 255, 255) ui2.append(bg2) ui2.append(label2) # Scene 3: Green background - green_scene = mcrfpy.Scene("green_scene") - ui3 = green_scene.children + mcrfpy.createScene("green_scene") + ui3 = mcrfpy.sceneUI("green_scene") bg3 = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(0, 255, 0, 255)) - label3 = mcrfpy.Caption(pos=(512, 384), text="GREEN SCENE", font=mcrfpy.default_font) + label3 = mcrfpy.Caption(pos=(512, 384), text="GREEN SCENE", font=mcrfpy.Font.font_ui) label3.fill_color = mcrfpy.Color(0, 0, 0, 255) # Black text on green ui3.append(bg3) ui3.append(label3) # Scene 4: Menu scene with buttons - menu_scene = mcrfpy.Scene("menu_scene") - ui4 = menu_scene.children + mcrfpy.createScene("menu_scene") + ui4 = mcrfpy.sceneUI("menu_scene") bg4 = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(50, 50, 50, 255)) - title = mcrfpy.Caption(pos=(512, 100), text="SCENE TRANSITION DEMO", font=mcrfpy.default_font) + title = mcrfpy.Caption(pos=(512, 100), text="SCENE TRANSITION DEMO", font=mcrfpy.Font.font_ui) title.fill_color = mcrfpy.Color(255, 255, 255, 255) ui4.append(bg4) ui4.append(title) # Add instruction text - instructions = mcrfpy.Caption(pos=(512, 200), text="Press keys 1-6 for different transitions", font=mcrfpy.default_font) + instructions = mcrfpy.Caption(pos=(512, 200), text="Press keys 1-6 for different transitions", font=mcrfpy.Font.font_ui) instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) ui4.append(instructions) - controls = mcrfpy.Caption(pos=(512, 250), text="1: Fade | 2: Slide Left | 3: Slide Right | 4: Slide Up | 5: Slide Down | 6: Instant", font=mcrfpy.default_font) + controls = mcrfpy.Caption(pos=(512, 250), text="1: Fade | 2: Slide Left | 3: Slide Right | 4: Slide Up | 5: Slide Down | 6: Instant", font=mcrfpy.Font.font_ui) controls.fill_color = mcrfpy.Color(150, 150, 150, 255) ui4.append(controls) - scene_info = mcrfpy.Caption(pos=(512, 300), text="R: Red Scene | B: Blue Scene | G: Green Scene | M: Menu", font=mcrfpy.default_font) + scene_info = mcrfpy.Caption(pos=(512, 300), text="R: Red Scene | B: Blue Scene | G: Green Scene | M: Menu", font=mcrfpy.Font.font_ui) scene_info.fill_color = mcrfpy.Color(150, 150, 150, 255) ui4.append(scene_info) print("Created test scenes: red_scene, blue_scene, green_scene, menu_scene") # Track current transition type -current_transition = mcrfpy.Transition.FADE +current_transition = "fade" transition_duration = 1.0 def handle_key(key, action): @@ -73,77 +71,57 @@ def handle_key(key, action): if action != "start": return - current_scene = (mcrfpy.current_scene.name if mcrfpy.current_scene else None) + current_scene = mcrfpy.currentScene() # Number keys set transition type - keyselections = { - "Num1": mcrfpy.Transition.FADE, - "Num2": mcrfpy.Transition.SLIDE_LEFT, - "Num3": mcrfpy.Transition.SLIDE_RIGHT, - "Num4": mcrfpy.Transition.SLIDE_UP, - "Num5": mcrfpy.Transition.SLIDE_DOWN, - "Num6": mcrfpy.Transition.NONE - } - if key in keyselections: - current_transition = keyselections[key] - print(f"Transition set to: {current_transition}") - #if key == "Num1": - # current_transition = "fade" - # print("Transition set to: fade") - #elif key == "Num2": - # current_transition = "slide_left" - # print("Transition set to: slide_left") - #elif key == "Num3": - # current_transition = "slide_right" - # print("Transition set to: slide_right") - #elif key == "Num4": - # current_transition = "slide_up" - # print("Transition set to: slide_up") - #elif key == "Num5": - # current_transition = "slide_down" - # print("Transition set to: slide_down") - #elif key == "Num6": - # current_transition = None # Instant - # print("Transition set to: instant") + if key == "Num1": + current_transition = "fade" + print("Transition set to: fade") + elif key == "Num2": + current_transition = "slide_left" + print("Transition set to: slide_left") + elif key == "Num3": + current_transition = "slide_right" + print("Transition set to: slide_right") + elif key == "Num4": + current_transition = "slide_up" + print("Transition set to: slide_up") + elif key == "Num5": + current_transition = "slide_down" + print("Transition set to: slide_down") + elif key == "Num6": + current_transition = None # Instant + print("Transition set to: instant") # Letter keys change scene - keytransitions = { - "R": red_scene, - "B": blue_scene, - "G": green_scene, - "M": menu_scene - } - if key in keytransitions: - if mcrfpy.current_scene != keytransitions[key]: - keytransitions[key].activate(current_transition, transition_duration) - #elif key == "R": - # if current_scene != "red_scene": - # print(f"Transitioning to red_scene with {current_transition}") - # if current_transition: - # mcrfpy.setScene("red_scene", current_transition, transition_duration) - # else: - # red_scene.activate() - #elif key == "B": - # if current_scene != "blue_scene": - # print(f"Transitioning to blue_scene with {current_transition}") - # if current_transition: - # mcrfpy.setScene("blue_scene", current_transition, transition_duration) - # else: - # blue_scene.activate() - #elif key == "G": - # if current_scene != "green_scene": - # print(f"Transitioning to green_scene with {current_transition}") - # if current_transition: - # mcrfpy.setScene("green_scene", current_transition, transition_duration) - # else: - # green_scene.activate() - #elif key == "M": - # if current_scene != "menu_scene": - # print(f"Transitioning to menu_scene with {current_transition}") - # if current_transition: - # mcrfpy.setScene("menu_scene", current_transition, transition_duration) - # else: - # menu_scene.activate() + elif key == "R": + if current_scene != "red_scene": + print(f"Transitioning to red_scene with {current_transition}") + if current_transition: + mcrfpy.setScene("red_scene", current_transition, transition_duration) + else: + mcrfpy.setScene("red_scene") + elif key == "B": + if current_scene != "blue_scene": + print(f"Transitioning to blue_scene with {current_transition}") + if current_transition: + mcrfpy.setScene("blue_scene", current_transition, transition_duration) + else: + mcrfpy.setScene("blue_scene") + elif key == "G": + if current_scene != "green_scene": + print(f"Transitioning to green_scene with {current_transition}") + if current_transition: + mcrfpy.setScene("green_scene", current_transition, transition_duration) + else: + mcrfpy.setScene("green_scene") + elif key == "M": + if current_scene != "menu_scene": + print(f"Transitioning to menu_scene with {current_transition}") + if current_transition: + mcrfpy.setScene("menu_scene", current_transition, transition_duration) + else: + mcrfpy.setScene("menu_scene") elif key == "Escape": print("Exiting...") sys.exit(0) @@ -166,7 +144,7 @@ def test_automatic_transitions(delay): mcrfpy.setScene(scene, trans_type, 1.0) else: print(f"Transition {i+1}: instant to {scene}") - mcrfpy.current_scene = scene + mcrfpy.setScene(scene) time.sleep(2) # Wait for transition to complete plus viewing time print("Automatic test complete!") @@ -177,16 +155,14 @@ print("=== Scene Transition Test ===") create_test_scenes() # Start with menu scene -menu_scene.activate() +mcrfpy.setScene("menu_scene") # Set up keyboard handler -for s in (red_scene, blue_scene, green_scene, menu_scene): - s.on_key = handle_key -#menu_scene.on_key = handle_key +mcrfpy.keypressScene(handle_key) # Option to run automatic test if len(sys.argv) > 1 and sys.argv[1] == "--auto": mcrfpy.setTimer("auto_test", test_automatic_transitions, 1000) else: print("\nManual test mode. Use keyboard controls shown on screen.") - print("Run with --auto flag for automatic transition demo.") + print("Run with --auto flag for automatic transition demo.") \ No newline at end of file diff --git a/tests/unit/test_scene_transitions_headless.py b/tests/unit/test_scene_transitions_headless.py index f8b79bc..1e9b571 100644 --- a/tests/unit/test_scene_transitions_headless.py +++ b/tests/unit/test_scene_transitions_headless.py @@ -11,14 +11,14 @@ def test_scene_transitions(): print("Creating test scenes...") # Scene 1 - scene1 = mcrfpy.Scene("scene1") - ui1 = scene1.children + mcrfpy.createScene("scene1") + ui1 = mcrfpy.sceneUI("scene1") frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100), fill_color=mcrfpy.Color(255, 0, 0)) ui1.append(frame1) # Scene 2 - scene2 = mcrfpy.Scene("scene2") - ui2 = scene2.children + mcrfpy.createScene("scene2") + ui2 = mcrfpy.sceneUI("scene2") frame2 = mcrfpy.Frame(pos=(0, 0), size=(100, 100), fill_color=mcrfpy.Color(0, 0, 255)) ui2.append(frame2) @@ -35,20 +35,20 @@ def test_scene_transitions(): print("\nTesting scene transitions:") # Start with scene1 - scene1.activate() - print(f"Initial scene: {(mcrfpy.current_scene.name if mcrfpy.current_scene else None)}") + mcrfpy.setScene("scene1") + print(f"Initial scene: {mcrfpy.currentScene()}") for trans_type, duration in transitions: - target = "scene2" if (mcrfpy.current_scene.name if mcrfpy.current_scene else None) == "scene1" else "scene1" + target = "scene2" if mcrfpy.currentScene() == "scene1" else "scene1" if trans_type: print(f"\nTransitioning to {target} with {trans_type} (duration: {duration}s)") mcrfpy.setScene(target, trans_type, duration) else: print(f"\nTransitioning to {target} instantly") - mcrfpy.current_scene = target + mcrfpy.setScene(target) - print(f"Current scene after transition: {(mcrfpy.current_scene.name if mcrfpy.current_scene else None)}") + print(f"Current scene after transition: {mcrfpy.currentScene()}") print("\n✓ All scene transition types tested successfully!") print("\nNote: Visual transitions cannot be verified in headless mode.") diff --git a/tests/unit/test_simple_callback.py b/tests/unit/test_simple_callback.py index 5e6aa47..307cb9d 100644 --- a/tests/unit/test_simple_callback.py +++ b/tests/unit/test_simple_callback.py @@ -6,8 +6,8 @@ import sys def cb(a, t): print("CB") -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") e = mcrfpy.Entity((0, 0), texture=None, sprite_index=0) a = mcrfpy.Animation("x", 1.0, 0.1, "linear", callback=cb) a.start(e) diff --git a/tests/unit/test_simple_drawable.py b/tests/unit/test_simple_drawable.py index 5943b36..8a03baf 100644 --- a/tests/unit/test_simple_drawable.py +++ b/tests/unit/test_simple_drawable.py @@ -26,5 +26,5 @@ def simple_test(runtime): sys.exit(0) -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") mcrfpy.setTimer("simple_test", simple_test, 100) \ No newline at end of file diff --git a/tests/unit/test_stdin_theory.py b/tests/unit/test_stdin_theory.py index ad1120d..88d1d28 100644 --- a/tests/unit/test_stdin_theory.py +++ b/tests/unit/test_stdin_theory.py @@ -9,8 +9,8 @@ print(f"stdin.isatty(): {sys.stdin.isatty()}") print(f"stdin fileno: {sys.stdin.fileno()}") # Set up a basic scene -stdin_test = mcrfpy.Scene("stdin_test") -stdin_test.activate() +mcrfpy.createScene("stdin_test") +mcrfpy.setScene("stdin_test") # Try to prevent interactive mode by closing stdin print("\nAttempting to prevent interactive mode...") diff --git a/tests/unit/test_step_function.py b/tests/unit/test_step_function.py index d92a4e4..1db5b52 100644 --- a/tests/unit/test_step_function.py +++ b/tests/unit/test_step_function.py @@ -108,8 +108,8 @@ def run_tests(): if __name__ == "__main__": try: # Create a scene for the test - test_step = mcrfpy.Scene("test_step") - test_step.activate() + mcrfpy.createScene("test_step") + mcrfpy.setScene("test_step") if run_tests(): print("\nPASS") diff --git a/tests/unit/test_synchronous_screenshot.py b/tests/unit/test_synchronous_screenshot.py index 45d0df7..9e3bc2f 100644 --- a/tests/unit/test_synchronous_screenshot.py +++ b/tests/unit/test_synchronous_screenshot.py @@ -22,9 +22,9 @@ def run_tests(): print("=== Synchronous Screenshot Tests ===\n") # Create a test scene with UI elements - screenshot_test = mcrfpy.Scene("screenshot_test") - screenshot_test.activate() - ui = screenshot_test.children + mcrfpy.createScene("screenshot_test") + mcrfpy.setScene("screenshot_test") + ui = mcrfpy.sceneUI("screenshot_test") # Test 1: Basic screenshot works print("Test 1: Basic screenshot functionality") diff --git a/tests/unit/test_tcod_complete.py b/tests/unit/test_tcod_complete.py index eba8577..21934ab 100644 --- a/tests/unit/test_tcod_complete.py +++ b/tests/unit/test_tcod_complete.py @@ -9,7 +9,7 @@ def run_tests(): # Test 1: Basic Grid Creation print("Test 1: Grid Creation") - tcod_test = mcrfpy.Scene("tcod_test") + mcrfpy.createScene("tcod_test") grid = mcrfpy.Grid(grid_x=10, grid_y=10, texture=None, pos=(10, 10), size=(160, 160)) print("✓ Grid created successfully\n") diff --git a/tests/unit/test_tcod_fov.py b/tests/unit/test_tcod_fov.py index 15f7c54..cfbdc33 100644 --- a/tests/unit/test_tcod_fov.py +++ b/tests/unit/test_tcod_fov.py @@ -6,7 +6,7 @@ import sys try: print("1. Creating scene and grid...") - test = mcrfpy.Scene("test") + mcrfpy.createScene("test") grid = mcrfpy.Grid(grid_x=5, grid_y=5, texture=None, pos=(0, 0), size=(80, 80)) print(" Grid created") diff --git a/tests/unit/test_tcod_minimal.py b/tests/unit/test_tcod_minimal.py index 95e9319..af1e4ef 100644 --- a/tests/unit/test_tcod_minimal.py +++ b/tests/unit/test_tcod_minimal.py @@ -8,7 +8,7 @@ try: print("1. Module loaded") print("2. Creating scene...") - test = mcrfpy.Scene("test") + mcrfpy.createScene("test") print(" Scene created") print("3. Creating grid with explicit parameters...") diff --git a/tests/unit/test_tcod_pathfinding.py b/tests/unit/test_tcod_pathfinding.py index 93001a9..0bd0aef 100644 --- a/tests/unit/test_tcod_pathfinding.py +++ b/tests/unit/test_tcod_pathfinding.py @@ -6,7 +6,7 @@ import sys try: print("1. Creating scene and grid...") - test = mcrfpy.Scene("test") + mcrfpy.createScene("test") grid = mcrfpy.Grid(grid_x=7, grid_y=7, texture=None, pos=(0, 0), size=(112, 112)) print(" Grid created") diff --git a/tests/unit/test_text_input.py b/tests/unit/test_text_input.py index 007bdc4..bc39a7f 100644 --- a/tests/unit/test_text_input.py +++ b/tests/unit/test_text_input.py @@ -14,8 +14,8 @@ from text_input_widget import FocusManager, TextInput def create_demo(): """Create demo scene with text inputs""" # Create scene - text_demo = mcrfpy.Scene("text_demo") - scene = text_demo.children + mcrfpy.createScene("text_demo") + scene = mcrfpy.sceneUI("text_demo") # Background bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600)) @@ -86,8 +86,8 @@ def create_demo(): print(f" Field {i+1}: '{inp.get_text()}'") sys.exit(0) - text_demo.on_key = "text_demo", handle_keys - text_demo.activate() + mcrfpy.keypressScene("text_demo", handle_keys) + mcrfpy.setScene("text_demo") # Run demo test def run_test(timer_name): diff --git a/tests/unit/test_timer_callback.py b/tests/unit/test_timer_callback.py index 24f71f6..7b131a5 100644 --- a/tests/unit/test_timer_callback.py +++ b/tests/unit/test_timer_callback.py @@ -23,8 +23,8 @@ def new_style_callback(arg1, arg2=None): sys.exit(0) # Set up the scene -test_scene = mcrfpy.Scene("test_scene") -test_scene.activate() +mcrfpy.createScene("test_scene") +mcrfpy.setScene("test_scene") print("Testing old style timer with setTimer...") mcrfpy.setTimer("old_timer", old_style_callback, 100) diff --git a/tests/unit/test_timer_legacy.py b/tests/unit/test_timer_legacy.py index f1a7863..6dbed87 100644 --- a/tests/unit/test_timer_legacy.py +++ b/tests/unit/test_timer_legacy.py @@ -17,8 +17,8 @@ def timer_callback(runtime): sys.exit(0) # Set up the scene -test_scene = mcrfpy.Scene("test_scene") -test_scene.activate() +mcrfpy.createScene("test_scene") +mcrfpy.setScene("test_scene") # Create a timer the old way mcrfpy.setTimer("test_timer", timer_callback, 100) diff --git a/tests/unit/test_timer_object.py b/tests/unit/test_timer_object.py index a9ab7b0..96d1e0c 100644 --- a/tests/unit/test_timer_object.py +++ b/tests/unit/test_timer_object.py @@ -132,8 +132,8 @@ def run_tests(runtime): mcrfpy.setTimer("final_check", final_check, 2000) # Create a minimal scene -timer_test = mcrfpy.Scene("timer_test") -timer_test.activate() +mcrfpy.createScene("timer_test") +mcrfpy.setScene("timer_test") # Start tests after game loop begins mcrfpy.setTimer("run_tests", run_tests, 100) diff --git a/tests/unit/test_timer_once.py b/tests/unit/test_timer_once.py index 8e7e4fd..84d48fd 100644 --- a/tests/unit/test_timer_once.py +++ b/tests/unit/test_timer_once.py @@ -31,8 +31,8 @@ def check_results(runtime): sys.exit(1) # Set up the scene -test_scene = mcrfpy.Scene("test_scene") -test_scene.activate() +mcrfpy.createScene("test_scene") +mcrfpy.setScene("test_scene") # Create timers print("Creating once timer with once=True...") diff --git a/tests/unit/test_uiarc.py b/tests/unit/test_uiarc.py index a308202..40613df 100644 --- a/tests/unit/test_uiarc.py +++ b/tests/unit/test_uiarc.py @@ -18,7 +18,7 @@ def run_test(runtime): mcrfpy.delTimer("test") # Get the scene UI - ui = test.children + ui = mcrfpy.sceneUI("test") # Test 1: Create arcs with different parameters print("Test 1: Creating arcs...") @@ -130,8 +130,8 @@ def run_test(runtime): mcrfpy.setTimer("screenshot", take_screenshot, 50) # Create a test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test to run after game loop starts mcrfpy.setTimer("test", run_test, 50) diff --git a/tests/unit/test_uicaption_visual.py b/tests/unit/test_uicaption_visual.py index 6f0946c..a4e3fc5 100644 --- a/tests/unit/test_uicaption_visual.py +++ b/tests/unit/test_uicaption_visual.py @@ -11,7 +11,7 @@ def run_visual_test(runtime): print("\nRunning visual tests...") # Get our captions - ui = test.children + ui = mcrfpy.sceneUI("test") # Test 1: Make caption2 invisible print("Test 1: Making caption2 invisible") @@ -65,8 +65,8 @@ def main(): print("=== UICaption Visual Test ===\n") # Create test scene - test = mcrfpy.Scene("test") - test.activate() + mcrfpy.createScene("test") + mcrfpy.setScene("test") # Create multiple captions for testing caption1 = mcrfpy.Caption(pos=(50, 50), text="Caption 1: Normal", fill_color=mcrfpy.Color(255, 255, 255)) @@ -76,7 +76,7 @@ def main(): caption5 = mcrfpy.Caption(pos=(50, 250), text="Caption 5: 0% opacity", fill_color=mcrfpy.Color(255, 255, 200)) # Add captions to scene - ui = test.children + ui = mcrfpy.sceneUI("test") ui.append(caption1) ui.append(caption2) ui.append(caption3) diff --git a/tests/unit/test_uicircle.py b/tests/unit/test_uicircle.py index 2116fe3..4481f38 100644 --- a/tests/unit/test_uicircle.py +++ b/tests/unit/test_uicircle.py @@ -18,7 +18,7 @@ def run_test(runtime): mcrfpy.delTimer("test") # Get the scene UI - ui = test.children + ui = mcrfpy.sceneUI("test") # Test 1: Create circles with different parameters print("Test 1: Creating circles...") @@ -121,8 +121,8 @@ def run_test(runtime): mcrfpy.setTimer("screenshot", take_screenshot, 50) # Create a test scene -test = mcrfpy.Scene("test") -test.activate() +mcrfpy.createScene("test") +mcrfpy.setScene("test") # Schedule test to run after game loop starts mcrfpy.setTimer("test", run_test, 50) diff --git a/tests/unit/test_utf8_encoding.py b/tests/unit/test_utf8_encoding.py index 4980a42..168bbf9 100644 --- a/tests/unit/test_utf8_encoding.py +++ b/tests/unit/test_utf8_encoding.py @@ -31,5 +31,5 @@ def test_utf8(runtime): sys.exit(0) # Run test -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") mcrfpy.setTimer("test", test_utf8, 100) \ No newline at end of file diff --git a/tests/unit/test_vector_arithmetic.py b/tests/unit/test_vector_arithmetic.py index 5a8390b..2bfc9b6 100644 --- a/tests/unit/test_vector_arithmetic.py +++ b/tests/unit/test_vector_arithmetic.py @@ -243,5 +243,5 @@ def test_vector_arithmetic(runtime): sys.exit(0 if all_pass else 1) # Run test -test = mcrfpy.Scene("test") +mcrfpy.createScene("test") mcrfpy.setTimer("test", test_vector_arithmetic, 100) \ No newline at end of file diff --git a/tests/unit/test_viewport_scaling.py b/tests/unit/test_viewport_scaling.py index 0576d3c..bf07f9c 100644 --- a/tests/unit/test_viewport_scaling.py +++ b/tests/unit/test_viewport_scaling.py @@ -20,7 +20,7 @@ def test_viewport_modes(runtime): print(f"Window resolution: {window.resolution}") # Create test scene with visual elements - scene = test.children + scene = mcrfpy.sceneUI("test") # Create a frame that fills the game resolution to show boundaries game_res = window.game_resolution @@ -188,7 +188,7 @@ def handle_keypress(key, state): return window = Window.get() - scene = test.children + scene = mcrfpy.sceneUI("test") mode_text = None for elem in scene: if hasattr(elem, 'name') and elem.name == "mode_text": @@ -235,9 +235,9 @@ def handle_keypress(key, state): # Main execution print("Creating viewport test scene...") -test = mcrfpy.Scene("test") -test.activate() -test.on_key = handle_keypress +mcrfpy.createScene("test") +mcrfpy.setScene("test") +mcrfpy.keypressScene(handle_keypress) # Schedule the test mcrfpy.setTimer("test_viewport", test_viewport_modes, 100) diff --git a/tests/unit/test_visibility.py b/tests/unit/test_visibility.py index c735558..b866078 100644 --- a/tests/unit/test_visibility.py +++ b/tests/unit/test_visibility.py @@ -14,7 +14,7 @@ print("Knowledge Stubs 1 - Visibility System Test") print("==========================================") # Create scene and grid -visibility_test = mcrfpy.Scene("visibility_test") +mcrfpy.createScene("visibility_test") grid = mcrfpy.Grid(grid_x=20, grid_y=15) grid.fill_color = mcrfpy.Color(20, 20, 30) # Dark background @@ -135,7 +135,7 @@ visible_count = sum(1 for state in entity.gridstate if state.visible) print(f" Visible cells after move: {visible_count}") # Set up UI -ui = visibility_test.children +ui = mcrfpy.sceneUI("visibility_test") ui.append(grid) grid.position = (50, 50) grid.size = (600, 450) # 20*30, 15*30 @@ -156,7 +156,7 @@ legend.fill_color = mcrfpy.Color(150, 150, 150) ui.append(legend) # Set scene -visibility_test.activate() +mcrfpy.setScene("visibility_test") # Set timer to cycle perspectives mcrfpy.setTimer("cycle", visual_test, 2000) # Every 2 seconds diff --git a/tests/unit/test_visual_path.py b/tests/unit/test_visual_path.py index 7c8c133..11a8c71 100644 --- a/tests/unit/test_visual_path.py +++ b/tests/unit/test_visual_path.py @@ -10,7 +10,7 @@ FLOOR_COLOR = mcrfpy.Color(200, 200, 220) PATH_COLOR = mcrfpy.Color(100, 255, 100) # Create scene -visual_test = mcrfpy.Scene("visual_test") +mcrfpy.createScene("visual_test") # Create grid grid = mcrfpy.Grid(grid_x=5, grid_y=5) @@ -64,7 +64,7 @@ if path: print(f" Set ({x},{y}) to green") # Set up UI -ui = visual_test.children +ui = mcrfpy.sceneUI("visual_test") ui.append(grid) grid.position = (50, 50) grid.size = (250, 250) @@ -75,7 +75,7 @@ title.fill_color = mcrfpy.Color(255, 255, 255) ui.append(title) # Set scene -visual_test.activate() +mcrfpy.setScene("visual_test") # Set timer to check rendering mcrfpy.setTimer("check", check_render, 500) diff --git a/tests/unit/ui_Entity_issue73_test.py b/tests/unit/ui_Entity_issue73_test.py index 304db18..7f2b3cd 100644 --- a/tests/unit/ui_Entity_issue73_test.py +++ b/tests/unit/ui_Entity_issue73_test.py @@ -8,9 +8,9 @@ print("Test script starting...") def test_Entity(): """Test Entity class and index() method for collection removal""" # Create test scene with grid - entity_test = mcrfpy.Scene("entity_test") - entity_test.activate() - ui = entity_test.children + mcrfpy.createScene("entity_test") + mcrfpy.setScene("entity_test") + ui = mcrfpy.sceneUI("entity_test") # Create a grid grid = mcrfpy.Grid(10, 10, diff --git a/tests/unit/ui_Frame_test_detailed.py b/tests/unit/ui_Frame_test_detailed.py index de50c0f..938a5a4 100644 --- a/tests/unit/ui_Frame_test_detailed.py +++ b/tests/unit/ui_Frame_test_detailed.py @@ -8,9 +8,9 @@ def test_issue_38_children(): print("\n=== Testing Issue #38: children argument in Frame constructor ===") # Create test scene - issue38_test = mcrfpy.Scene("issue38_test") - issue38_test.activate() - ui = issue38_test.children + mcrfpy.createScene("issue38_test") + mcrfpy.setScene("issue38_test") + ui = mcrfpy.sceneUI("issue38_test") # Test 1: Try to pass children in constructor print("\nTest 1: Passing children argument to Frame constructor") @@ -54,9 +54,9 @@ def test_issue_42_click_callback(): print("\n\n=== Testing Issue #42: click callback arguments ===") # Create test scene - issue42_test = mcrfpy.Scene("issue42_test") - issue42_test.activate() - ui = issue42_test.children + mcrfpy.createScene("issue42_test") + mcrfpy.setScene("issue42_test") + ui = mcrfpy.sceneUI("issue42_test") # Test 1: Callback with correct signature print("\nTest 1: Click callback with correct signature (x, y, button)") diff --git a/tests/unit/ui_Grid_none_texture_test.py b/tests/unit/ui_Grid_none_texture_test.py index 668ac7e..a283cee 100644 --- a/tests/unit/ui_Grid_none_texture_test.py +++ b/tests/unit/ui_Grid_none_texture_test.py @@ -17,7 +17,7 @@ def test_grid_none_texture(runtime): sys.exit(1) # Add to UI - ui = grid_none_test.children + ui = mcrfpy.sceneUI("grid_none_test") ui.append(grid) # Test 2: Verify grid properties @@ -83,11 +83,11 @@ def test_grid_none_texture(runtime): # Set up test scene print("Creating test scene...") -grid_none_test = mcrfpy.Scene("grid_none_test") -grid_none_test.activate() +mcrfpy.createScene("grid_none_test") +mcrfpy.setScene("grid_none_test") # Add a background frame so we can see the grid -ui = grid_none_test.children +ui = mcrfpy.sceneUI("grid_none_test") background = mcrfpy.Frame(pos=(0, 0), size=(800, 600), fill_color=mcrfpy.Color(200, 200, 200), outline_color=mcrfpy.Color(0, 0, 0), diff --git a/tests/unit/ui_Grid_null_texture_test.py b/tests/unit/ui_Grid_null_texture_test.py index 2ec0cae..fdac956 100644 --- a/tests/unit/ui_Grid_null_texture_test.py +++ b/tests/unit/ui_Grid_null_texture_test.py @@ -8,9 +8,9 @@ def test_grid_null_texture(): print("=== Testing Grid with null texture ===") # Create test scene - grid_null_test = mcrfpy.Scene("grid_null_test") - grid_null_test.activate() - ui = grid_null_test.children + mcrfpy.createScene("grid_null_test") + mcrfpy.setScene("grid_null_test") + ui = mcrfpy.sceneUI("grid_null_test") # Test 1: Try with None try: diff --git a/tests/unit/ui_Grid_test_no_grid.py b/tests/unit/ui_Grid_test_no_grid.py index ac485e9..836543e 100644 --- a/tests/unit/ui_Grid_test_no_grid.py +++ b/tests/unit/ui_Grid_test_no_grid.py @@ -6,11 +6,11 @@ print("Starting test...") # Create test scene print("[DEBUG] Creating scene...") -grid_test = mcrfpy.Scene("grid_test") +mcrfpy.createScene("grid_test") print("[DEBUG] Setting scene...") -grid_test.activate() +mcrfpy.setScene("grid_test") print("[DEBUG] Getting UI...") -ui = grid_test.children +ui = mcrfpy.sceneUI("grid_test") print("[DEBUG] UI retrieved") # Test texture creation diff --git a/tests/unit/ui_Sprite_issue19_test.py b/tests/unit/ui_Sprite_issue19_test.py index b8cefe5..65539e9 100644 --- a/tests/unit/ui_Sprite_issue19_test.py +++ b/tests/unit/ui_Sprite_issue19_test.py @@ -5,9 +5,9 @@ import mcrfpy print("Testing Sprite texture methods (Issue #19)...") # Create test scene -sprite_texture_test = mcrfpy.Scene("sprite_texture_test") -sprite_texture_test.activate() -ui = sprite_texture_test.children +mcrfpy.createScene("sprite_texture_test") +mcrfpy.setScene("sprite_texture_test") +ui = mcrfpy.sceneUI("sprite_texture_test") # Create sprites # Based on sprite2 syntax: Sprite(x, y, texture, sprite_index, scale) diff --git a/tests/unit/ui_UICollection_issue69_test.py b/tests/unit/ui_UICollection_issue69_test.py index 86fd3ef..44af8d2 100644 --- a/tests/unit/ui_UICollection_issue69_test.py +++ b/tests/unit/ui_UICollection_issue69_test.py @@ -6,9 +6,9 @@ from datetime import datetime def test_UICollection(): """Test UICollection sequence protocol compliance""" # Create test scene - collection_test = mcrfpy.Scene("collection_test") - collection_test.activate() - ui = collection_test.children + mcrfpy.createScene("collection_test") + mcrfpy.setScene("collection_test") + ui = mcrfpy.sceneUI("collection_test") # Add various UI elements frame = mcrfpy.Frame(pos=(10, 10), size=(100, 100)) diff --git a/tests/unit/validate_screenshot_test.py b/tests/unit/validate_screenshot_test.py index 96c8652..7e1a068 100644 --- a/tests/unit/validate_screenshot_test.py +++ b/tests/unit/validate_screenshot_test.py @@ -10,9 +10,9 @@ def test_screenshot_validation(): print("=== Screenshot Validation Test ===\n") # Create a scene with bright, visible content - screenshot_validation = mcrfpy.Scene("screenshot_validation") - screenshot_validation.activate() - ui = screenshot_validation.children + mcrfpy.createScene("screenshot_validation") + mcrfpy.setScene("screenshot_validation") + ui = mcrfpy.sceneUI("screenshot_validation") # Create multiple colorful elements to ensure visibility print("Creating UI elements...") diff --git a/tests/unit/working_timer_test.py b/tests/unit/working_timer_test.py index bddeff4..a9d96c5 100644 --- a/tests/unit/working_timer_test.py +++ b/tests/unit/working_timer_test.py @@ -6,9 +6,9 @@ from mcrfpy import automation print("Setting up timer test...") # Create a scene -timer_works = mcrfpy.Scene("timer_works") -timer_works.activate() -ui = timer_works.children +mcrfpy.createScene("timer_works") +mcrfpy.setScene("timer_works") +ui = mcrfpy.sceneUI("timer_works") # Add visible content frame = mcrfpy.Frame(pos=(100, 100), size=(300, 200), diff --git a/tests/vllm_demo/0_basic_vllm_demo.py b/tests/vllm_demo/0_basic_vllm_demo.py index 1198dda..bf37fa4 100644 --- a/tests/vllm_demo/0_basic_vllm_demo.py +++ b/tests/vllm_demo/0_basic_vllm_demo.py @@ -72,9 +72,9 @@ def setup_scene(): print("Setting up scene...") # Create and set scene - vllm_demo = mcrfpy.Scene("vllm_demo") - vllm_demo.activate() - ui = vllm_demo.children + mcrfpy.createScene("vllm_demo") + mcrfpy.setScene("vllm_demo") + ui = mcrfpy.sceneUI("vllm_demo") # Load the game texture (16x16 tiles from Crypt of Sokoban) texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) diff --git a/tests/vllm_demo/1_multi_agent_demo.py b/tests/vllm_demo/1_multi_agent_demo.py index d6234b3..b69bccb 100644 --- a/tests/vllm_demo/1_multi_agent_demo.py +++ b/tests/vllm_demo/1_multi_agent_demo.py @@ -100,9 +100,9 @@ def setup_scene(): print("Setting up multi-agent scene...") # Create and set scene - multi_agent_demo = mcrfpy.Scene("multi_agent_demo") - multi_agent_demo.activate() - ui = multi_agent_demo.children + mcrfpy.createScene("multi_agent_demo") + mcrfpy.setScene("multi_agent_demo") + ui = mcrfpy.sceneUI("multi_agent_demo") # Load the game texture texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) diff --git a/tests/vllm_demo/2_integrated_demo.py b/tests/vllm_demo/2_integrated_demo.py index 38d25b8..f499079 100644 --- a/tests/vllm_demo/2_integrated_demo.py +++ b/tests/vllm_demo/2_integrated_demo.py @@ -126,9 +126,9 @@ def setup_scene_from_world(world: WorldGraph): Carves out rooms and places doors based on WorldGraph data. """ - integrated_demo = mcrfpy.Scene("integrated_demo") - integrated_demo.activate() - ui = integrated_demo.children + mcrfpy.createScene("integrated_demo") + mcrfpy.setScene("integrated_demo") + ui = mcrfpy.sceneUI("integrated_demo") texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) diff --git a/tests/vllm_demo/3_multi_turn_demo.py b/tests/vllm_demo/3_multi_turn_demo.py index 6f96f81..3e830c3 100644 --- a/tests/vllm_demo/3_multi_turn_demo.py +++ b/tests/vllm_demo/3_multi_turn_demo.py @@ -131,9 +131,9 @@ What do you do? Brief reasoning (1-2 sentences), then Action: """ def setup_scene(world: WorldGraph): """Create McRogueFace scene from WorldGraph.""" - multi_turn = mcrfpy.Scene("multi_turn") - multi_turn.activate() - ui = multi_turn.children + mcrfpy.createScene("multi_turn") + mcrfpy.setScene("multi_turn") + ui = mcrfpy.sceneUI("multi_turn") texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) diff --git a/tests/vllm_demo/4_enhanced_action_demo.py b/tests/vllm_demo/4_enhanced_action_demo.py index ce17e1e..2986733 100644 --- a/tests/vllm_demo/4_enhanced_action_demo.py +++ b/tests/vllm_demo/4_enhanced_action_demo.py @@ -180,9 +180,9 @@ Always end your final response with: Action: """ def setup_scene(world: WorldGraph): """Create McRogueFace scene from WorldGraph.""" - enhanced_demo = mcrfpy.Scene("enhanced_demo") - enhanced_demo.activate() - ui = enhanced_demo.children + mcrfpy.createScene("enhanced_demo") + mcrfpy.setScene("enhanced_demo") + ui = mcrfpy.sceneUI("enhanced_demo") texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)