From 192d1ae1dd1445c1795637cc0d3adea88b9cdc2b Mon Sep 17 00:00:00 2001 From: John McCardle Date: Wed, 9 Jul 2025 08:36:11 -0400 Subject: [PATCH] Roguelike Tutorial Planning + Prep --- ROADMAP.md | 156 ++- tests/animation_demo_safe.py | 146 +++ tests/animation_sizzle_reel.py | 615 ++++++++++++ tests/animation_sizzle_reel_fixed.py | 227 +++++ tests/animation_sizzle_reel_v2.py | 307 ++++++ tests/animation_sizzle_reel_working.py | 318 +++++++ tests/api_demo_final.py | 207 ++++ tests/exhaustive_api_demo.py | 1204 ++++++++++++++++++++++++ tests/exhaustive_api_demo_fixed.py | 306 ++++++ tests/sizzle_reel_final.py | 177 ++++ 10 files changed, 3658 insertions(+), 5 deletions(-) create mode 100644 tests/animation_demo_safe.py create mode 100644 tests/animation_sizzle_reel.py create mode 100644 tests/animation_sizzle_reel_fixed.py create mode 100644 tests/animation_sizzle_reel_v2.py create mode 100644 tests/animation_sizzle_reel_working.py create mode 100644 tests/api_demo_final.py create mode 100644 tests/exhaustive_api_demo.py create mode 100644 tests/exhaustive_api_demo_fixed.py create mode 100644 tests/sizzle_reel_final.py diff --git a/ROADMAP.md b/ROADMAP.md index abb65d5..d5040b6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,11 +1,121 @@ # McRogueFace - Development Roadmap +## 🚨 URGENT PRIORITIES - July 9, 2025 🚨 + +### IMMEDIATE ACTION REQUIRED (Next 48 Hours) + +**CRITICAL DEADLINE**: RoguelikeDev Tutorial Event starts July 15 - Need to advertise by July 11! + +#### 1. Tutorial Emergency Plan (2 DAYS) +- [ ] **Day 1 (July 9)**: Parts 1-2 (Setup, Moving @, Drawing Map, Entities) +- [ ] **Day 2 (July 10)**: Parts 3-4 (FOV, Combat/AI) +- [ ] **July 11**: Announce on r/roguelikedev with 4 completed parts +- [ ] **July 12-14**: Complete remaining 10 parts before event starts + +#### 1b. Sizzle Reel Demo (URGENT) +- [ ] **Expand animation_sizzle_reel_working.py** with Grid/Entity demos: + - Grid scrolling and zooming animations + - Entity movement patterns (patrol, chase, flee) + - Particle effects using entity spawning + - Tile animation demonstrations + - Color cycling and transparency effects + - Mass entity choreography (100+ entities) + - Performance stress test with 1000+ entities + +#### 2. TCOD Integration Sprint +- [ ] **UIGrid TCOD Integration** (8 hours) + - Add TCODMap* to UIGrid constructor + - Implement mcrfpy.libtcod.compute_fov() + - Add batch operations for NumPy-style access + - Create CellView for ergonomic .at((x,y)) access +- [ ] **UIEntity Pathfinding** (4 hours) + - Add path_to(target) method using A* + - Implement Dijkstra maps for multiple targets + - Cache paths in UIEntity for performance + +#### 3. Performance Critical Path +- [ ] **Implement SpatialHash** for 10,000+ entities (2 hours) +- [ ] **Add dirty flag system** to UIGrid (1 hour) +- [ ] **Batch update context managers** (2 hours) +- [ ] **Memory pool for entities** (2 hours) + +#### 4. Bug Fixing Pipeline +- [ ] Set up GitHub Issues automation +- [ ] Create test for each bug before fixing +- [ ] Track: Memory leaks, Segfaults, Python/C++ boundary errors + +--- + +## 🎯 STRATEGIC ARCHITECTURE VISION + +### Three-Layer Grid Architecture (From Compass Research) +Following successful roguelike patterns (Caves of Qud, Cogmind, DCSS): + +1. **Visual Layer** (UIGridPoint) - Sprites, colors, animations +2. **World State Layer** (TCODMap) - Walkability, transparency, physics +3. **Entity Perspective Layer** (UIGridPointState) - Per-entity FOV, knowledge + +### Performance Architecture (Critical for 1000x1000 maps) +- **Spatial Hashing** for entity queries (not quadtrees!) +- **Batch Operations** with context managers (10-100x speedup) +- **Memory Pooling** for entities and components +- **Dirty Flag System** to avoid unnecessary updates +- **Zero-Copy NumPy Integration** via buffer protocol + +### Key Insight from Research +"Minimizing Python/C++ boundary crossings matters more than individual function complexity" +- Batch everything possible +- Use context managers for logical operations +- Expose arrays, not individual cells +- Profile and optimize hot paths only + +--- + ## Project Status: 🎉 ALPHA 0.1 RELEASE! 🎉 -**Current State**: Alpha release achieved! All critical blockers resolved! -**Latest Update**: Moved RenderTexture (#6) to Beta - Alpha is READY! (2025-07-05) -**Branch**: interpreter_mode (ready for alpha release merge) -**Open Issues**: ~46 remaining (non-blocking quality-of-life improvements) +**Current State**: Documentation system complete, TCOD integration urgent +**Latest Update**: Completed Phase 7 documentation infrastructure (2025-07-08) +**Branch**: alpha_streamline_2 +**Open Issues**: ~46 remaining + URGENT TCOD/Tutorial work + +--- + +## 📋 TCOD Integration Implementation Details + +### Phase 1: Core UIGrid Integration (Day 1 Morning) +```cpp +// UIGrid.h additions +class UIGrid : public UIDrawable { +private: + TCODMap* world_state; // Add TCOD map + std::unordered_map entity_perspectives; + bool batch_mode = false; + std::vector pending_updates; +``` + +### Phase 2: Python Bindings (Day 1 Afternoon) +```python +# New API surface +grid = mcrfpy.Grid(100, 100) +grid.compute_fov(player.x, player.y, radius=10) # Returns visible cells +grid.at((x, y)).walkable = False # Ergonomic access +with grid.batch_update(): # Context manager for performance + # All updates batched +``` + +### Phase 3: Entity Integration (Day 2 Morning) +```python +# UIEntity additions +entity.path_to(target_x, target_y) # A* pathfinding +entity.flee_from(threat) # Dijkstra map +entity.can_see(other_entity) # FOV check +``` + +### Critical Success Factors: +1. **Batch everything** - Never update single cells in loops +2. **Lazy evaluation** - Only compute FOV for entities that need it +3. **Sparse storage** - Don't store full grids per entity +4. **Profile early** - Find the 20% of code taking 80% of time --- @@ -613,4 +723,40 @@ This architecture would make McRogueFace a first-class Python citizen, following --- -*Last Updated: 2025-07-08* +## 🚀 IMMEDIATE NEXT STEPS (Priority Order) + +### Today (July 9) - EXECUTE NOW: +1. **Start Tutorial Part 1** - Basic setup and @ movement (2 hours) +2. **Implement UIGrid.at((x,y))** - CellView pattern (1 hour) +3. **Create Grid demo** for sizzle reel (1 hour) +4. **Fix any blocking bugs** discovered during tutorial writing + +### Tomorrow (July 10) - CRITICAL PATH: +1. **Tutorial Parts 2-4** - Map drawing, entities, FOV, combat +2. **Implement compute_fov()** in UIGrid +3. **Add batch_update context manager** +4. **Expand sizzle reel** with entity choreography + +### July 11 - ANNOUNCEMENT DAY: +1. **Polish 4 tutorial parts** +2. **Create announcement post** for r/roguelikedev +3. **Record sizzle reel video** +4. **Submit announcement** by end of day + +### Architecture Decision Log: +- **DECIDED**: Use three-layer architecture (visual/world/perspective) +- **DECIDED**: Spatial hashing over quadtrees for entities +- **DECIDED**: Batch operations are mandatory, not optional +- **DECIDED**: TCOD integration as mcrfpy.libtcod submodule +- **DECIDED**: Tutorial must showcase McRogueFace strengths, not mimic TCOD + +### Risk Mitigation: +- **If TCOD integration delays**: Use pure Python FOV for tutorial +- **If performance issues**: Focus on <100x100 maps for demos +- **If tutorial incomplete**: Ship with 4 solid parts + roadmap +- **If bugs block progress**: Document as "known issues" and continue + +--- + +*Last Updated: 2025-07-09 (URGENT SPRINT MODE)* +*Next Review: July 11 after announcement* diff --git a/tests/animation_demo_safe.py b/tests/animation_demo_safe.py new file mode 100644 index 0000000..16f7445 --- /dev/null +++ b/tests/animation_demo_safe.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +McRogueFace Animation Demo - Safe Version +========================================= + +A safer, simpler version that demonstrates animations without crashes. +""" + +import mcrfpy +import sys + +# Configuration +DEMO_DURATION = 4.0 + +# Track state +current_demo = 0 +subtitle = None +demo_items = [] + +def create_scene(): + """Create the demo scene""" + mcrfpy.createScene("demo") + mcrfpy.setScene("demo") + + ui = mcrfpy.sceneUI("demo") + + # Title + title = mcrfpy.Caption("Animation Demo", 500, 20) + title.fill_color = mcrfpy.Color(255, 255, 0) + title.outline = 2 + ui.append(title) + + # Subtitle + global subtitle + subtitle = mcrfpy.Caption("Starting...", 450, 60) + subtitle.fill_color = mcrfpy.Color(200, 200, 200) + ui.append(subtitle) + +def clear_demo_items(): + """Clear demo items from scene""" + global demo_items + ui = mcrfpy.sceneUI("demo") + + # Remove demo items by tracking what we added + for item in demo_items: + try: + # Find index of item + for i in range(len(ui)): + if i >= 2: # Skip title and subtitle + ui.remove(i) + break + except: + pass + + demo_items = [] + +def demo1_basic(): + """Basic frame animations""" + global demo_items + clear_demo_items() + + ui = mcrfpy.sceneUI("demo") + subtitle.text = "Demo 1: Basic Frame Animations" + + # Create frame + f = mcrfpy.Frame(100, 150, 200, 100) + f.fill_color = mcrfpy.Color(50, 50, 150) + f.outline = 3 + ui.append(f) + demo_items.append(f) + + # Simple animations + mcrfpy.Animation("x", 600.0, 2.0, "easeInOut").start(f) + mcrfpy.Animation("w", 300.0, 2.0, "easeInOut").start(f) + mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "linear").start(f) + +def demo2_caption(): + """Caption animations""" + global demo_items + clear_demo_items() + + ui = mcrfpy.sceneUI("demo") + subtitle.text = "Demo 2: Caption Animations" + + # Moving caption + c1 = mcrfpy.Caption("Moving Text!", 100, 200) + c1.fill_color = mcrfpy.Color(255, 255, 255) + ui.append(c1) + demo_items.append(c1) + + mcrfpy.Animation("x", 700.0, 3.0, "easeOutBounce").start(c1) + + # Typewriter + c2 = mcrfpy.Caption("", 100, 300) + c2.fill_color = mcrfpy.Color(0, 255, 255) + ui.append(c2) + demo_items.append(c2) + + mcrfpy.Animation("text", "Typewriter effect...", 3.0, "linear").start(c2) + +def demo3_multiple(): + """Multiple animations""" + global demo_items + clear_demo_items() + + ui = mcrfpy.sceneUI("demo") + subtitle.text = "Demo 3: Multiple Animations" + + # Create several frames + for i in range(5): + f = mcrfpy.Frame(100 + i * 120, 200, 80, 80) + f.fill_color = mcrfpy.Color(50 + i * 40, 100, 200 - i * 30) + ui.append(f) + demo_items.append(f) + + # Animate each differently + target_y = 350 + i * 20 + mcrfpy.Animation("y", float(target_y), 2.0, "easeInOut").start(f) + mcrfpy.Animation("opacity", 0.5, 3.0, "easeInOut").start(f) + +def run_next_demo(runtime): + """Run the next demo""" + global current_demo + + demos = [demo1_basic, demo2_caption, demo3_multiple] + + if current_demo < len(demos): + demos[current_demo]() + current_demo += 1 + + if current_demo < len(demos): + mcrfpy.setTimer("next", run_next_demo, int(DEMO_DURATION * 1000)) + else: + subtitle.text = "Demo Complete!" + # Exit after a delay + def exit_program(rt): + print("Demo finished successfully!") + sys.exit(0) + mcrfpy.setTimer("exit", exit_program, 2000) + +# Initialize +print("Starting Safe Animation Demo...") +create_scene() + +# Start demos +mcrfpy.setTimer("start", run_next_demo, 500) \ No newline at end of file diff --git a/tests/animation_sizzle_reel.py b/tests/animation_sizzle_reel.py new file mode 100644 index 0000000..d3b1e20 --- /dev/null +++ b/tests/animation_sizzle_reel.py @@ -0,0 +1,615 @@ +#!/usr/bin/env python3 +""" +McRogueFace Animation Sizzle Reel +================================= + +This script demonstrates EVERY animation type on EVERY UI object type. +It showcases all 30 easing functions, all animatable properties, and +special animation modes (delta, sprite sequences, text effects). + +The script creates a comprehensive visual demonstration of the animation +system's capabilities, cycling through different objects and effects. + +Author: Claude +Purpose: Complete animation system demonstration +""" + +import mcrfpy +from mcrfpy import Color, Frame, Caption, Sprite, Grid, Entity, Texture, Animation +import sys +import math + +# Configuration +SCENE_WIDTH = 1280 +SCENE_HEIGHT = 720 +DEMO_DURATION = 5.0 # Duration for each demo section + +# All available easing functions +EASING_FUNCTIONS = [ + "linear", "easeIn", "easeOut", "easeInOut", + "easeInQuad", "easeOutQuad", "easeInOutQuad", + "easeInCubic", "easeOutCubic", "easeInOutCubic", + "easeInQuart", "easeOutQuart", "easeInOutQuart", + "easeInSine", "easeOutSine", "easeInOutSine", + "easeInExpo", "easeOutExpo", "easeInOutExpo", + "easeInCirc", "easeOutCirc", "easeInOutCirc", + "easeInElastic", "easeOutElastic", "easeInOutElastic", + "easeInBack", "easeOutBack", "easeInOutBack", + "easeInBounce", "easeOutBounce", "easeInOutBounce" +] + +# Track current demo state +current_demo = 0 +demo_start_time = 0 +demos = [] + +# Handle ESC key to exit +def handle_keypress(scene_name, keycode): + if keycode == 256: # ESC key + print("Exiting animation sizzle reel...") + sys.exit(0) + +def create_demo_scene(): + """Create the main demo scene with title""" + mcrfpy.createScene("sizzle_reel") + mcrfpy.setScene("sizzle_reel") + mcrfpy.keypressScene(handle_keypress) + + ui = mcrfpy.sceneUI("sizzle_reel") + + # Title caption + title = Caption("McRogueFace Animation Sizzle Reel", + SCENE_WIDTH/2 - 200, 20) + title.fill_color = Color(255, 255, 0) + title.outline = 2 + title.outline_color = Color(0, 0, 0) + ui.append(title) + + # Subtitle showing current demo + global subtitle + subtitle = Caption("Initializing...", + SCENE_WIDTH/2 - 150, 60) + subtitle.fill_color = Color(200, 200, 200) + ui.append(subtitle) + + return ui + +def demo_frame_basic_animations(ui): + """Demo 1: Basic frame animations - position, size, colors""" + subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)" + + # Create test frame + frame = Frame(100, 150, 200, 100) + frame.fill_color = Color(50, 50, 150) + frame.outline = 3 + frame.outline_color = Color(255, 255, 255) + ui.append(frame) + + # Position animations with different easings + x_anim = Animation("x", 800.0, 2.0, "easeInOutBack") + y_anim = Animation("y", 400.0, 2.0, "easeInOutElastic") + x_anim.start(frame) + y_anim.start(frame) + + # Size animations + w_anim = Animation("w", 400.0, 3.0, "easeInOutCubic") + h_anim = Animation("h", 200.0, 3.0, "easeInOutCubic") + w_anim.start(frame) + h_anim.start(frame) + + # Color animations - use tuples instead of Color objects + fill_anim = Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine") + outline_anim = Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce") + fill_anim.start(frame) + outline_anim.start(frame) + + # Outline thickness animation + thickness_anim = Animation("outline", 10.0, 4.5, "easeInOutQuad") + thickness_anim.start(frame) + + return frame + +def demo_frame_opacity_zindex(ui): + """Demo 2: Frame opacity and z-index animations""" + subtitle.text = "Demo 2: Frame Opacity & Z-Index Animations" + + frames = [] + colors = [ + Color(255, 0, 0, 200), + Color(0, 255, 0, 200), + Color(0, 0, 255, 200), + Color(255, 255, 0, 200) + ] + + # Create overlapping frames + for i in range(4): + frame = Frame(200 + i*80, 200 + i*40, 200, 150) + frame.fill_color = colors[i] + frame.outline = 2 + frame.z_index = i + ui.append(frame) + frames.append(frame) + + # Animate opacity in waves + opacity_anim = Animation("opacity", 0.3, 2.0, "easeInOutSine") + opacity_anim.start(frame) + + # Reverse opacity animation + opacity_back = Animation("opacity", 1.0, 2.0, "easeInOutSine", delta=False) + mcrfpy.setTimer(f"opacity_back_{i}", lambda t, f=frame, a=opacity_back: a.start(f), 2000) + + # Z-index shuffle animation + z_anim = Animation("z_index", (i + 2) % 4, 3.0, "linear") + z_anim.start(frame) + + return frames + +def demo_caption_animations(ui): + """Demo 3: Caption text animations and effects""" + subtitle.text = "Demo 3: Caption Animations (Text, Color, Position)" + + # Basic caption with position animation + caption1 = Caption("Moving Text!", 100, 200) + caption1.fill_color = Color(255, 255, 255) + caption1.outline = 1 + ui.append(caption1) + + # Animate across screen with bounce + x_anim = Animation("x", 900.0, 3.0, "easeOutBounce") + x_anim.start(caption1) + + # Color cycling caption + caption2 = Caption("Rainbow Colors", 400, 300) + caption2.outline = 2 + ui.append(caption2) + + # Cycle through colors - use tuples + color_anim1 = Animation("fill_color", (255, 0, 0, 255), 1.0, "linear") + color_anim2 = Animation("fill_color", (0, 255, 0, 255), 1.0, "linear") + color_anim3 = Animation("fill_color", (0, 0, 255, 255), 1.0, "linear") + color_anim4 = Animation("fill_color", (255, 255, 255, 255), 1.0, "linear") + + color_anim1.start(caption2) + mcrfpy.setTimer("color2", lambda t: color_anim2.start(caption2), 1000) + mcrfpy.setTimer("color3", lambda t: color_anim3.start(caption2), 2000) + mcrfpy.setTimer("color4", lambda t: color_anim4.start(caption2), 3000) + + # Typewriter effect caption + caption3 = Caption("", 100, 400) + caption3.fill_color = Color(0, 255, 255) + ui.append(caption3) + + typewriter = Animation("text", "This text appears one character at a time...", 3.0, "linear") + typewriter.start(caption3) + + # Size animation caption + caption4 = Caption("Growing Text", 400, 500) + caption4.fill_color = Color(255, 200, 0) + ui.append(caption4) + + # Note: size animation would require font size property support + # For now, animate position to simulate growth + scale_sim = Animation("y", 480.0, 2.0, "easeInOutElastic") + scale_sim.start(caption4) + + return [caption1, caption2, caption3, caption4] + +def demo_sprite_animations(ui): + """Demo 4: Sprite animations including sprite sequences""" + subtitle.text = "Demo 4: Sprite Animations (Position, Scale, Sprite Sequences)" + + # Load a test texture (you'll need to adjust path) + try: + texture = Texture("assets/sprites/player.png", grid_size=(32, 32)) + except: + # Fallback if texture not found + texture = None + + if texture: + # Basic sprite with position animation + sprite1 = Sprite(100, 200, texture, sprite_index=0) + sprite1.scale = 2.0 + ui.append(sprite1) + + # Circular motion using sin/cos animations + # We'll use delta mode to create circular motion + x_circle = Animation("x", 300.0, 4.0, "easeInOutSine") + y_circle = Animation("y", 300.0, 4.0, "easeInOutCubic") + x_circle.start(sprite1) + y_circle.start(sprite1) + + # Sprite sequence animation (walking cycle) + sprite2 = Sprite(500, 300, texture, sprite_index=0) + sprite2.scale = 3.0 + ui.append(sprite2) + + # Animate through sprite indices for animation + walk_cycle = Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0, "linear") + walk_cycle.start(sprite2) + + # Scale pulsing sprite + sprite3 = Sprite(800, 400, texture, sprite_index=4) + ui.append(sprite3) + + # Note: scale animation would need to be supported + # For now use position to simulate + pulse_y = Animation("y", 380.0, 0.5, "easeInOutSine") + pulse_y.start(sprite3) + + # Z-index animation for layering + sprite3_z = Animation("z_index", 10, 2.0, "linear") + sprite3_z.start(sprite3) + + return [sprite1, sprite2, sprite3] + else: + # Create placeholder caption if no texture + no_texture = Caption("(Sprite demo requires texture file)", 400, 350) + no_texture.fill_color = Color(255, 100, 100) + ui.append(no_texture) + return [no_texture] + +def demo_grid_animations(ui): + """Demo 5: Grid animations (position, camera, zoom)""" + subtitle.text = "Demo 5: Grid Animations (Position, Camera Effects)" + + # Create a grid + try: + texture = Texture("assets/sprites/tiles.png", grid_size=(16, 16)) + except: + texture = None + + grid = Grid(100, 150, grid_size=(20, 15), texture=texture, + tile_width=24, tile_height=24) + grid.fill_color = Color(20, 20, 40) + ui.append(grid) + + # Fill with some test pattern + for y in range(15): + for x in range(20): + point = grid.at(x, y) + point.tilesprite = (x + y) % 4 + point.walkable = ((x + y) % 3) != 0 + if not point.walkable: + point.color = Color(100, 50, 50, 128) + + # Animate grid position + grid_x = Animation("x", 400.0, 3.0, "easeInOutBack") + grid_x.start(grid) + + # Camera pan animation (if supported) + # center_x = Animation("center", (10.0, 7.5), 4.0, "easeInOutCubic") + # center_x.start(grid) + + # Create entities in the grid + if texture: + entity1 = Entity(5.0, 5.0, texture, sprite_index=8) + entity1.scale = 1.5 + grid.entities.append(entity1) + + # Animate entity movement + entity_pos = Animation("position", (15.0, 10.0), 3.0, "easeInOutQuad") + entity_pos.start(entity1) + + # Create patrolling entity + entity2 = Entity(10.0, 2.0, texture, sprite_index=12) + grid.entities.append(entity2) + + # Animate sprite changes + entity2_sprite = Animation("sprite_index", [12, 13, 14, 15, 14, 13], 2.0, "linear") + entity2_sprite.start(entity2) + + return grid + +def demo_complex_combinations(ui): + """Demo 6: Complex multi-property animations""" + subtitle.text = "Demo 6: Complex Multi-Property Animations" + + # Create a complex UI composition + main_frame = Frame(200, 200, 400, 300) + main_frame.fill_color = Color(30, 30, 60, 200) + main_frame.outline = 2 + ui.append(main_frame) + + # Child elements + title = Caption("Multi-Animation Demo", 20, 20) + title.fill_color = Color(255, 255, 255) + main_frame.children.append(title) + + # Animate everything at once + # Frame animations + frame_x = Animation("x", 600.0, 3.0, "easeInOutElastic") + frame_w = Animation("w", 300.0, 2.5, "easeOutBack") + frame_fill = Animation("fill_color", (60, 30, 90, 220), 4.0, "easeInOutSine") + frame_outline = Animation("outline", 8.0, 3.0, "easeInOutQuad") + + frame_x.start(main_frame) + frame_w.start(main_frame) + frame_fill.start(main_frame) + frame_outline.start(main_frame) + + # Title animations + title_color = Animation("fill_color", (255, 200, 0, 255), 2.0, "easeOutBounce") + title_color.start(title) + + # Add animated sub-frames + for i in range(3): + sub_frame = Frame(50 + i * 100, 100, 80, 80) + sub_frame.fill_color = Color(100 + i*50, 50, 200 - i*50, 180) + main_frame.children.append(sub_frame) + + # Rotate positions using delta animations + sub_y = Animation("y", 50.0, 2.0, "easeInOutSine", delta=True) + sub_y.start(sub_frame) + + return main_frame + +def demo_easing_showcase(ui): + """Demo 7: Showcase all 30 easing functions""" + subtitle.text = "Demo 7: All 30 Easing Functions Showcase" + + # Create small frames for each easing function + frames_per_row = 6 + frame_size = 180 + spacing = 10 + + for i, easing in enumerate(EASING_FUNCTIONS[:12]): # First 12 easings + row = i // frames_per_row + col = i % frames_per_row + + x = 50 + col * (frame_size + spacing) + y = 150 + row * (60 + spacing) + + # Create indicator frame + frame = Frame(x, y, 20, 20) + frame.fill_color = Color(100, 200, 255) + frame.outline = 1 + ui.append(frame) + + # Label + label = Caption(easing, x, y - 20) + label.fill_color = Color(200, 200, 200) + ui.append(label) + + # Animate using this easing + move_anim = Animation("x", x + frame_size - 20, 3.0, easing) + move_anim.start(frame) + + # Continue with remaining easings after a delay + def show_more_easings(runtime): + for j, easing in enumerate(EASING_FUNCTIONS[12:24]): # Next 12 + row = j // frames_per_row + 2 + col = j % frames_per_row + + x = 50 + col * (frame_size + spacing) + y = 150 + row * (60 + spacing) + + frame2 = Frame(x, y, 20, 20) + frame2.fill_color = Color(255, 150, 100) + frame2.outline = 1 + ui.append(frame2) + + label2 = Caption(easing, x, y - 20) + label2.fill_color = Color(200, 200, 200) + ui.append(label2) + + move_anim2 = Animation("x", x + frame_size - 20, 3.0, easing) + move_anim2.start(frame2) + + mcrfpy.setTimer("more_easings", show_more_easings, 1000) + + # Show final easings + def show_final_easings(runtime): + for k, easing in enumerate(EASING_FUNCTIONS[24:]): # Last 6 + row = k // frames_per_row + 4 + col = k % frames_per_row + + x = 50 + col * (frame_size + spacing) + y = 150 + row * (60 + spacing) + + frame3 = Frame(x, y, 20, 20) + frame3.fill_color = Color(150, 255, 150) + frame3.outline = 1 + ui.append(frame3) + + label3 = Caption(easing, x, y - 20) + label3.fill_color = Color(200, 200, 200) + ui.append(label3) + + move_anim3 = Animation("x", x + frame_size - 20, 3.0, easing) + move_anim3.start(frame3) + + mcrfpy.setTimer("final_easings", show_final_easings, 2000) + +def demo_delta_animations(ui): + """Demo 8: Delta mode animations (relative movements)""" + subtitle.text = "Demo 8: Delta Mode Animations (Relative Movements)" + + # Create objects that will move relative to their position + frames = [] + start_positions = [(100, 200), (300, 200), (500, 200), (700, 200)] + colors = [Color(255, 100, 100), Color(100, 255, 100), + Color(100, 100, 255), Color(255, 255, 100)] + + for i, (x, y) in enumerate(start_positions): + frame = Frame(x, y, 80, 80) + frame.fill_color = colors[i] + frame.outline = 2 + ui.append(frame) + frames.append(frame) + + # Delta animations - move relative to current position + # Each frame moves by different amounts + dx = (i + 1) * 50 + dy = math.sin(i) * 100 + + x_delta = Animation("x", dx, 2.0, "easeInOutBack", delta=True) + y_delta = Animation("y", dy, 2.0, "easeInOutElastic", delta=True) + + x_delta.start(frame) + y_delta.start(frame) + + # Create caption showing delta mode + delta_label = Caption("Delta mode: Relative animations from current position", 200, 400) + delta_label.fill_color = Color(255, 255, 255) + ui.append(delta_label) + + # Animate the label with delta mode text append + text_delta = Animation("text", " - ANIMATED!", 2.0, "linear", delta=True) + text_delta.start(delta_label) + + return frames + +def demo_color_component_animations(ui): + """Demo 9: Individual color channel animations""" + subtitle.text = "Demo 9: Color Component Animations (R, G, B, A channels)" + + # Create frames to demonstrate individual color channel animations + base_frame = Frame(300, 200, 600, 300) + base_frame.fill_color = Color(128, 128, 128, 255) + base_frame.outline = 3 + ui.append(base_frame) + + # Labels for each channel + labels = ["Red", "Green", "Blue", "Alpha"] + positions = [(50, 50), (200, 50), (350, 50), (500, 50)] + + for i, (label_text, (x, y)) in enumerate(zip(labels, positions)): + # Create label + label = Caption(label_text, x, y - 30) + label.fill_color = Color(255, 255, 255) + base_frame.children.append(label) + + # Create demo frame for this channel + demo_frame = Frame(x, y, 100, 100) + demo_frame.fill_color = Color(100, 100, 100, 200) + demo_frame.outline = 2 + base_frame.children.append(demo_frame) + + # Animate individual color channel + if i == 0: # Red + r_anim = Animation("fill_color.r", 255, 3.0, "easeInOutSine") + r_anim.start(demo_frame) + elif i == 1: # Green + g_anim = Animation("fill_color.g", 255, 3.0, "easeInOutSine") + g_anim.start(demo_frame) + elif i == 2: # Blue + b_anim = Animation("fill_color.b", 255, 3.0, "easeInOutSine") + b_anim.start(demo_frame) + else: # Alpha + a_anim = Animation("fill_color.a", 50, 3.0, "easeInOutSine") + a_anim.start(demo_frame) + + # Animate main frame outline color components in sequence + outline_r = Animation("outline_color.r", 255, 1.0, "linear") + outline_g = Animation("outline_color.g", 255, 1.0, "linear") + outline_b = Animation("outline_color.b", 0, 1.0, "linear") + + outline_r.start(base_frame) + mcrfpy.setTimer("outline_g", lambda t: outline_g.start(base_frame), 1000) + mcrfpy.setTimer("outline_b", lambda t: outline_b.start(base_frame), 2000) + + return base_frame + +def demo_performance_stress_test(ui): + """Demo 10: Performance test with many simultaneous animations""" + subtitle.text = "Demo 10: Performance Stress Test (100+ Simultaneous Animations)" + + # Create many small objects with different animations + num_objects = 100 + + for i in range(num_objects): + # Random starting position + x = 100 + (i % 20) * 50 + y = 150 + (i // 20) * 50 + + # Create small frame + size = 20 + (i % 3) * 10 + frame = Frame(x, y, size, size) + + # Random color + r = (i * 37) % 256 + g = (i * 73) % 256 + b = (i * 113) % 256 + frame.fill_color = Color(r, g, b, 200) + frame.outline = 1 + ui.append(frame) + + # Random animation properties + target_x = 100 + (i % 15) * 70 + target_y = 150 + (i // 15) * 70 + duration = 2.0 + (i % 30) * 0.1 + easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)] + + # Start multiple animations per object + x_anim = Animation("x", target_x, duration, easing) + y_anim = Animation("y", target_y, duration, easing) + opacity_anim = Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine") + + x_anim.start(frame) + y_anim.start(frame) + opacity_anim.start(frame) + + # Performance counter + perf_caption = Caption(f"Animating {num_objects * 3} properties simultaneously", 400, 600) + perf_caption.fill_color = Color(255, 255, 0) + ui.append(perf_caption) + +def next_demo(runtime): + """Cycle to the next demo""" + global current_demo, demo_start_time + + # Clear the UI except title and subtitle + ui = mcrfpy.sceneUI("sizzle_reel") + + # Keep only the first two elements (title and subtitle) + while len(ui) > 2: + # Remove from the end to avoid index issues + ui.remove(len(ui) - 1) + + # Run the next demo + if current_demo < len(demos): + demos[current_demo](ui) + current_demo += 1 + + # Schedule next demo + if current_demo < len(demos): + mcrfpy.setTimer("next_demo", next_demo, int(DEMO_DURATION * 1000)) + else: + # All demos complete + subtitle.text = "Animation Showcase Complete! Press ESC to exit." + complete = Caption("All animation types demonstrated!", 400, 350) + complete.fill_color = Color(0, 255, 0) + complete.outline = 2 + ui.append(complete) + +def run_sizzle_reel(runtime): + """Main entry point - start the demo sequence""" + global demos + + # List of all demo functions + demos = [ + demo_frame_basic_animations, + demo_frame_opacity_zindex, + demo_caption_animations, + demo_sprite_animations, + demo_grid_animations, + demo_complex_combinations, + demo_easing_showcase, + demo_delta_animations, + demo_color_component_animations, + demo_performance_stress_test + ] + + # Start the first demo + next_demo(runtime) + +# Initialize scene +ui = create_demo_scene() + + +# Start the sizzle reel after a short delay +mcrfpy.setTimer("start_sizzle", run_sizzle_reel, 500) + +print("Starting McRogueFace Animation Sizzle Reel...") +print("This will demonstrate ALL animation types on ALL objects.") +print("Press ESC at any time to exit.") diff --git a/tests/animation_sizzle_reel_fixed.py b/tests/animation_sizzle_reel_fixed.py new file mode 100644 index 0000000..e12f9bc --- /dev/null +++ b/tests/animation_sizzle_reel_fixed.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +""" +McRogueFace Animation Sizzle Reel (Fixed) +========================================= + +This script demonstrates EVERY animation type on EVERY UI object type. +Fixed version that works properly with the game loop. +""" + +import mcrfpy + +# Configuration +SCENE_WIDTH = 1280 +SCENE_HEIGHT = 720 +DEMO_DURATION = 5.0 # Duration for each demo section + +# All available easing functions +EASING_FUNCTIONS = [ + "linear", "easeIn", "easeOut", "easeInOut", + "easeInQuad", "easeOutQuad", "easeInOutQuad", + "easeInCubic", "easeOutCubic", "easeInOutCubic", + "easeInQuart", "easeOutQuart", "easeInOutQuart", + "easeInSine", "easeOutSine", "easeInOutSine", + "easeInExpo", "easeOutExpo", "easeInOutExpo", + "easeInCirc", "easeOutCirc", "easeInOutCirc", + "easeInElastic", "easeOutElastic", "easeInOutElastic", + "easeInBack", "easeOutBack", "easeInOutBack", + "easeInBounce", "easeOutBounce", "easeInOutBounce" +] + +# Track current demo state +current_demo = 0 +subtitle = None + +def create_demo_scene(): + """Create the main demo scene with title""" + mcrfpy.createScene("sizzle_reel") + mcrfpy.setScene("sizzle_reel") + + ui = mcrfpy.sceneUI("sizzle_reel") + + # Title caption + title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel", + SCENE_WIDTH/2 - 200, 20) + title.fill_color = mcrfpy.Color(255, 255, 0) + title.outline = 2 + title.outline_color = mcrfpy.Color(0, 0, 0) + ui.append(title) + + # Subtitle showing current demo + global subtitle + subtitle = mcrfpy.Caption("Initializing...", + SCENE_WIDTH/2 - 150, 60) + subtitle.fill_color = mcrfpy.Color(200, 200, 200) + ui.append(subtitle) + + return ui + +def demo_frame_basic_animations(): + """Demo 1: Basic frame animations - position, size, colors""" + ui = mcrfpy.sceneUI("sizzle_reel") + subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)" + + # Create test frame + frame = mcrfpy.Frame(100, 150, 200, 100) + frame.fill_color = mcrfpy.Color(50, 50, 150) + frame.outline = 3 + frame.outline_color = mcrfpy.Color(255, 255, 255) + ui.append(frame) + + # Position animations with different easings + x_anim = mcrfpy.Animation("x", 800.0, 2.0, "easeInOutBack") + y_anim = mcrfpy.Animation("y", 400.0, 2.0, "easeInOutElastic") + x_anim.start(frame) + y_anim.start(frame) + + # Size animations + w_anim = mcrfpy.Animation("w", 400.0, 3.0, "easeInOutCubic") + h_anim = mcrfpy.Animation("h", 200.0, 3.0, "easeInOutCubic") + w_anim.start(frame) + h_anim.start(frame) + + # Color animations + fill_anim = mcrfpy.Animation("fill_color", mcrfpy.Color(255, 100, 50, 200), 4.0, "easeInOutSine") + outline_anim = mcrfpy.Animation("outline_color", mcrfpy.Color(0, 255, 255), 4.0, "easeOutBounce") + fill_anim.start(frame) + outline_anim.start(frame) + + # Outline thickness animation + thickness_anim = mcrfpy.Animation("outline", 10.0, 4.5, "easeInOutQuad") + thickness_anim.start(frame) + +def demo_caption_animations(): + """Demo 2: Caption text animations and effects""" + ui = mcrfpy.sceneUI("sizzle_reel") + subtitle.text = "Demo 2: Caption Animations (Text, Color, Position)" + + # Basic caption with position animation + caption1 = mcrfpy.Caption("Moving Text!", 100, 200) + caption1.fill_color = mcrfpy.Color(255, 255, 255) + caption1.outline = 1 + ui.append(caption1) + + # Animate across screen with bounce + x_anim = mcrfpy.Animation("x", 900.0, 3.0, "easeOutBounce") + x_anim.start(caption1) + + # Color cycling caption + caption2 = mcrfpy.Caption("Rainbow Colors", 400, 300) + caption2.outline = 2 + ui.append(caption2) + + # Cycle through colors + color_anim1 = mcrfpy.Animation("fill_color", mcrfpy.Color(255, 0, 0), 1.0, "linear") + color_anim1.start(caption2) + + # Typewriter effect caption + caption3 = mcrfpy.Caption("", 100, 400) + caption3.fill_color = mcrfpy.Color(0, 255, 255) + ui.append(caption3) + + typewriter = mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear") + typewriter.start(caption3) + +def demo_sprite_animations(): + """Demo 3: Sprite animations (if texture available)""" + ui = mcrfpy.sceneUI("sizzle_reel") + subtitle.text = "Demo 3: Sprite Animations" + + # Create placeholder caption since texture might not exist + no_texture = mcrfpy.Caption("(Sprite demo - textures may not be loaded)", 400, 350) + no_texture.fill_color = mcrfpy.Color(255, 100, 100) + ui.append(no_texture) + +def demo_performance_stress_test(): + """Demo 4: Performance test with many simultaneous animations""" + ui = mcrfpy.sceneUI("sizzle_reel") + subtitle.text = "Demo 4: Performance Test (50+ Simultaneous Animations)" + + # Create many small objects with different animations + num_objects = 50 + + for i in range(num_objects): + # Random starting position + x = 100 + (i % 10) * 100 + y = 150 + (i // 10) * 80 + + # Create small frame + size = 20 + (i % 3) * 10 + frame = mcrfpy.Frame(x, y, size, size) + + # Random color + r = (i * 37) % 256 + g = (i * 73) % 256 + b = (i * 113) % 256 + frame.fill_color = mcrfpy.Color(r, g, b, 200) + frame.outline = 1 + ui.append(frame) + + # Random animation properties + target_x = 100 + (i % 8) * 120 + target_y = 150 + (i // 8) * 100 + duration = 2.0 + (i % 30) * 0.1 + easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)] + + # Start multiple animations per object + x_anim = mcrfpy.Animation("x", float(target_x), duration, easing) + y_anim = mcrfpy.Animation("y", float(target_y), duration, easing) + opacity_anim = mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine") + + x_anim.start(frame) + y_anim.start(frame) + opacity_anim.start(frame) + + # Performance counter + perf_caption = mcrfpy.Caption(f"Animating {num_objects * 3} properties simultaneously", 400, 600) + perf_caption.fill_color = mcrfpy.Color(255, 255, 0) + ui.append(perf_caption) + +def clear_scene(): + """Clear the scene except title and subtitle""" + ui = mcrfpy.sceneUI("sizzle_reel") + + # Keep only the first two elements (title and subtitle) + while len(ui) > 2: + ui.remove(ui[2]) + +def run_demo_sequence(runtime): + """Run through all demos""" + global current_demo + + # Clear previous demo + clear_scene() + + # Demo list + demos = [ + demo_frame_basic_animations, + demo_caption_animations, + demo_sprite_animations, + demo_performance_stress_test + ] + + if current_demo < len(demos): + # Run current demo + demos[current_demo]() + current_demo += 1 + + # Schedule next demo + if current_demo < len(demos): + mcrfpy.setTimer("next_demo", run_demo_sequence, int(DEMO_DURATION * 1000)) + else: + # All demos complete + subtitle.text = "Animation Showcase Complete!" + complete = mcrfpy.Caption("All animation types demonstrated!", 400, 350) + complete.fill_color = mcrfpy.Color(0, 255, 0) + complete.outline = 2 + ui = mcrfpy.sceneUI("sizzle_reel") + ui.append(complete) + +# Initialize scene +print("Starting McRogueFace Animation Sizzle Reel...") +print("This will demonstrate animation types on various objects.") + +ui = create_demo_scene() + +# Start the demo sequence after a short delay +mcrfpy.setTimer("start_demos", run_demo_sequence, 500) \ No newline at end of file diff --git a/tests/animation_sizzle_reel_v2.py b/tests/animation_sizzle_reel_v2.py new file mode 100644 index 0000000..2a43236 --- /dev/null +++ b/tests/animation_sizzle_reel_v2.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 +""" +McRogueFace Animation Sizzle Reel v2 +==================================== + +Fixed version with proper API usage for animations and collections. +""" + +import mcrfpy + +# Configuration +SCENE_WIDTH = 1280 +SCENE_HEIGHT = 720 +DEMO_DURATION = 5.0 # Duration for each demo section + +# All available easing functions +EASING_FUNCTIONS = [ + "linear", "easeIn", "easeOut", "easeInOut", + "easeInQuad", "easeOutQuad", "easeInOutQuad", + "easeInCubic", "easeOutCubic", "easeInOutCubic", + "easeInQuart", "easeOutQuart", "easeInOutQuart", + "easeInSine", "easeOutSine", "easeInOutSine", + "easeInExpo", "easeOutExpo", "easeInOutExpo", + "easeInCirc", "easeOutCirc", "easeInOutCirc", + "easeInElastic", "easeOutElastic", "easeInOutElastic", + "easeInBack", "easeOutBack", "easeInOutBack", + "easeInBounce", "easeOutBounce", "easeInOutBounce" +] + +# Track current demo state +current_demo = 0 +subtitle = None +demo_objects = [] # Track objects from current demo + +def create_demo_scene(): + """Create the main demo scene with title""" + mcrfpy.createScene("sizzle_reel") + mcrfpy.setScene("sizzle_reel") + + ui = mcrfpy.sceneUI("sizzle_reel") + + # Title caption + title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel", + SCENE_WIDTH/2 - 200, 20) + title.fill_color = mcrfpy.Color(255, 255, 0) + title.outline = 2 + title.outline_color = mcrfpy.Color(0, 0, 0) + ui.append(title) + + # Subtitle showing current demo + global subtitle + subtitle = mcrfpy.Caption("Initializing...", + SCENE_WIDTH/2 - 150, 60) + subtitle.fill_color = mcrfpy.Color(200, 200, 200) + ui.append(subtitle) + + return ui + +def demo_frame_basic_animations(): + """Demo 1: Basic frame animations - position, size, colors""" + global demo_objects + demo_objects = [] + + ui = mcrfpy.sceneUI("sizzle_reel") + subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)" + + # Create test frame + frame = mcrfpy.Frame(100, 150, 200, 100) + frame.fill_color = mcrfpy.Color(50, 50, 150) + frame.outline = 3 + frame.outline_color = mcrfpy.Color(255, 255, 255) + ui.append(frame) + demo_objects.append(frame) + + # Position animations with different easings + x_anim = mcrfpy.Animation("x", 800.0, 2.0, "easeInOutBack") + y_anim = mcrfpy.Animation("y", 400.0, 2.0, "easeInOutElastic") + x_anim.start(frame) + y_anim.start(frame) + + # Size animations + w_anim = mcrfpy.Animation("w", 400.0, 3.0, "easeInOutCubic") + h_anim = mcrfpy.Animation("h", 200.0, 3.0, "easeInOutCubic") + w_anim.start(frame) + h_anim.start(frame) + + # Color animations - use tuples instead of Color objects + fill_anim = mcrfpy.Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine") + outline_anim = mcrfpy.Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce") + fill_anim.start(frame) + outline_anim.start(frame) + + # Outline thickness animation + thickness_anim = mcrfpy.Animation("outline", 10.0, 4.5, "easeInOutQuad") + thickness_anim.start(frame) + +def demo_caption_animations(): + """Demo 2: Caption text animations and effects""" + global demo_objects + demo_objects = [] + + ui = mcrfpy.sceneUI("sizzle_reel") + subtitle.text = "Demo 2: Caption Animations (Text, Color, Position)" + + # Basic caption with position animation + caption1 = mcrfpy.Caption("Moving Text!", 100, 200) + caption1.fill_color = mcrfpy.Color(255, 255, 255) + caption1.outline = 1 + ui.append(caption1) + demo_objects.append(caption1) + + # Animate across screen with bounce + x_anim = mcrfpy.Animation("x", 900.0, 3.0, "easeOutBounce") + x_anim.start(caption1) + + # Color cycling caption + caption2 = mcrfpy.Caption("Rainbow Colors", 400, 300) + caption2.outline = 2 + ui.append(caption2) + demo_objects.append(caption2) + + # Cycle through colors using tuples + color_anim1 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear") + color_anim1.start(caption2) + + # Schedule color changes + def change_to_green(rt): + color_anim2 = mcrfpy.Animation("fill_color", (0, 255, 0, 255), 1.0, "linear") + color_anim2.start(caption2) + + def change_to_blue(rt): + color_anim3 = mcrfpy.Animation("fill_color", (0, 0, 255, 255), 1.0, "linear") + color_anim3.start(caption2) + + def change_to_white(rt): + color_anim4 = mcrfpy.Animation("fill_color", (255, 255, 255, 255), 1.0, "linear") + color_anim4.start(caption2) + + mcrfpy.setTimer("color2", change_to_green, 1000) + mcrfpy.setTimer("color3", change_to_blue, 2000) + mcrfpy.setTimer("color4", change_to_white, 3000) + + # Typewriter effect caption + caption3 = mcrfpy.Caption("", 100, 400) + caption3.fill_color = mcrfpy.Color(0, 255, 255) + ui.append(caption3) + demo_objects.append(caption3) + + typewriter = mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear") + typewriter.start(caption3) + +def demo_easing_showcase(): + """Demo 3: Showcase different easing functions""" + global demo_objects + demo_objects = [] + + ui = mcrfpy.sceneUI("sizzle_reel") + subtitle.text = "Demo 3: Easing Functions Showcase" + + # Create small frames for each easing function + frames_per_row = 6 + frame_width = 180 + spacing = 10 + + # Show first 12 easings + for i, easing in enumerate(EASING_FUNCTIONS[:12]): + row = i // frames_per_row + col = i % frames_per_row + + x = 50 + col * (frame_width + spacing) + y = 150 + row * (80 + spacing) + + # Create indicator frame + frame = mcrfpy.Frame(x, y, 20, 20) + frame.fill_color = mcrfpy.Color(100, 200, 255) + frame.outline = 1 + ui.append(frame) + demo_objects.append(frame) + + # Label + label = mcrfpy.Caption(easing[:8], x, y - 20) # Truncate long names + label.fill_color = mcrfpy.Color(200, 200, 200) + ui.append(label) + demo_objects.append(label) + + # Animate using this easing + move_anim = mcrfpy.Animation("x", float(x + frame_width - 20), 3.0, easing) + move_anim.start(frame) + +def demo_performance_stress_test(): + """Demo 4: Performance test with many simultaneous animations""" + global demo_objects + demo_objects = [] + + ui = mcrfpy.sceneUI("sizzle_reel") + subtitle.text = "Demo 4: Performance Test (50+ Simultaneous Animations)" + + # Create many small objects with different animations + num_objects = 50 + + for i in range(num_objects): + # Starting position + x = 100 + (i % 10) * 100 + y = 150 + (i // 10) * 80 + + # Create small frame + size = 20 + (i % 3) * 10 + frame = mcrfpy.Frame(x, y, size, size) + + # Random color + r = (i * 37) % 256 + g = (i * 73) % 256 + b = (i * 113) % 256 + frame.fill_color = mcrfpy.Color(r, g, b, 200) + frame.outline = 1 + ui.append(frame) + demo_objects.append(frame) + + # Random animation properties + target_x = 100 + (i % 8) * 120 + target_y = 150 + (i // 8) * 100 + duration = 2.0 + (i % 30) * 0.1 + easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)] + + # Start multiple animations per object + x_anim = mcrfpy.Animation("x", float(target_x), duration, easing) + y_anim = mcrfpy.Animation("y", float(target_y), duration, easing) + opacity_anim = mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine") + + x_anim.start(frame) + y_anim.start(frame) + opacity_anim.start(frame) + + # Performance counter + perf_caption = mcrfpy.Caption(f"Animating {num_objects * 3} properties simultaneously", 350, 600) + perf_caption.fill_color = mcrfpy.Color(255, 255, 0) + ui.append(perf_caption) + demo_objects.append(perf_caption) + +def clear_scene(): + """Clear the scene except title and subtitle""" + global demo_objects + ui = mcrfpy.sceneUI("sizzle_reel") + + # Remove all demo objects + for obj in demo_objects: + try: + # Find index of object + for i in range(len(ui)): + if ui[i] is obj: + ui.remove(ui[i]) + break + except: + pass # Object might already be removed + + demo_objects = [] + + # Clean up any timers + for timer_name in ["color2", "color3", "color4"]: + try: + mcrfpy.delTimer(timer_name) + except: + pass + +def run_demo_sequence(runtime): + """Run through all demos""" + global current_demo + + # Clear previous demo + clear_scene() + + # Demo list + demos = [ + demo_frame_basic_animations, + demo_caption_animations, + demo_easing_showcase, + demo_performance_stress_test + ] + + if current_demo < len(demos): + # Run current demo + demos[current_demo]() + current_demo += 1 + + # Schedule next demo + if current_demo < len(demos): + mcrfpy.setTimer("next_demo", run_demo_sequence, int(DEMO_DURATION * 1000)) + else: + # Final demo completed + def show_complete(rt): + subtitle.text = "Animation Showcase Complete!" + complete = mcrfpy.Caption("All animation types demonstrated!", 400, 350) + complete.fill_color = mcrfpy.Color(0, 255, 0) + complete.outline = 2 + ui = mcrfpy.sceneUI("sizzle_reel") + ui.append(complete) + + mcrfpy.setTimer("complete", show_complete, 3000) + +# Initialize scene +print("Starting McRogueFace Animation Sizzle Reel v2...") +print("This will demonstrate animation types on various objects.") + +ui = create_demo_scene() + +# Start the demo sequence after a short delay +mcrfpy.setTimer("start_demos", run_demo_sequence, 500) \ No newline at end of file diff --git a/tests/animation_sizzle_reel_working.py b/tests/animation_sizzle_reel_working.py new file mode 100644 index 0000000..d24cc1a --- /dev/null +++ b/tests/animation_sizzle_reel_working.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +""" +McRogueFace Animation Sizzle Reel - Working Version +=================================================== + +Complete demonstration of all animation capabilities. +Fixed to work properly with the API. +""" + +import mcrfpy +import sys +import math + +# Configuration +DEMO_DURATION = 7.0 # Duration for each demo + +# All available easing functions +EASING_FUNCTIONS = [ + "linear", "easeIn", "easeOut", "easeInOut", + "easeInQuad", "easeOutQuad", "easeInOutQuad", + "easeInCubic", "easeOutCubic", "easeInOutCubic", + "easeInQuart", "easeOutQuart", "easeInOutQuart", + "easeInSine", "easeOutSine", "easeInOutSine", + "easeInExpo", "easeOutExpo", "easeInOutExpo", + "easeInCirc", "easeOutCirc", "easeInOutCirc", + "easeInElastic", "easeOutElastic", "easeInOutElastic", + "easeInBack", "easeOutBack", "easeInOutBack", + "easeInBounce", "easeOutBounce", "easeInOutBounce" +] + +# Track state +current_demo = 0 +subtitle = None +demo_objects = [] + +def create_scene(): + """Create the demo scene with title""" + mcrfpy.createScene("sizzle") + mcrfpy.setScene("sizzle") + + ui = mcrfpy.sceneUI("sizzle") + + # Title + title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel", 340, 20) + title.fill_color = mcrfpy.Color(255, 255, 0) + title.outline = 2 + title.outline_color = mcrfpy.Color(0, 0, 0) + ui.append(title) + + # Subtitle + global subtitle + subtitle = mcrfpy.Caption("Initializing...", 400, 60) + subtitle.fill_color = mcrfpy.Color(200, 200, 200) + ui.append(subtitle) + +def clear_demo(): + """Clear demo objects""" + global demo_objects + ui = mcrfpy.sceneUI("sizzle") + + # Remove items starting from the end + # Skip first 2 (title and subtitle) + while len(ui) > 2: + ui.remove(len(ui) - 1) + + demo_objects = [] + +def demo1_frame_basics(): + """Demo 1: Basic frame animations""" + clear_demo() + print("demo1") + subtitle.text = "Demo 1: Frame Animations (Position, Size, Color)" + + ui = mcrfpy.sceneUI("sizzle") + + # Create frame + frame = mcrfpy.Frame(100, 150, 200, 100) + frame.fill_color = mcrfpy.Color(50, 50, 150) + frame.outline = 3 + frame.outline_color = mcrfpy.Color(255, 255, 255) + ui.append(frame) + + # Animate properties + mcrfpy.Animation("x", 700.0, 2.5, "easeInOutBack").start(frame) + mcrfpy.Animation("y", 350.0, 2.5, "easeInOutElastic").start(frame) + mcrfpy.Animation("w", 350.0, 3.0, "easeInOutCubic").start(frame) + mcrfpy.Animation("h", 180.0, 3.0, "easeInOutCubic").start(frame) + mcrfpy.Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine").start(frame) + mcrfpy.Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce").start(frame) + mcrfpy.Animation("outline", 8.0, 4.0, "easeInOutQuad").start(frame) + +def demo2_opacity_zindex(): + """Demo 2: Opacity and z-index animations""" + clear_demo() + print("demo2") + subtitle.text = "Demo 2: Opacity & Z-Index Animations" + + ui = mcrfpy.sceneUI("sizzle") + + # Create overlapping frames + colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)] + + for i in range(4): + frame = mcrfpy.Frame(200 + i*80, 200 + i*40, 200, 150) + frame.fill_color = mcrfpy.Color(colors[i][0], colors[i][1], colors[i][2], 200) + frame.outline = 2 + frame.z_index = i + ui.append(frame) + + # Animate opacity + mcrfpy.Animation("opacity", 0.3, 2.0, "easeInOutSine").start(frame) + + # Schedule opacity return + def return_opacity(rt): + for i in range(4): + mcrfpy.Animation("opacity", 1.0, 2.0, "easeInOutSine").start(ui[i]) + mcrfpy.setTimer(f"opacity_{i}", return_opacity, 2100) + +def demo3_captions(): + """Demo 3: Caption animations""" + clear_demo() + print("demo3") + subtitle.text = "Demo 3: Caption Animations" + + ui = mcrfpy.sceneUI("sizzle") + + # Moving caption + c1 = mcrfpy.Caption("Bouncing Text!", 100, 200) + c1.fill_color = mcrfpy.Color(255, 255, 255) + c1.outline = 1 + ui.append(c1) + mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1) + + # Color cycling caption + c2 = mcrfpy.Caption("Color Cycle", 400, 300) + c2.outline = 2 + ui.append(c2) + + # Animate through colors + def cycle_colors(): + anim = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 0.5, "linear") + anim.start(c2) + + def to_green(rt): + mcrfpy.Animation("fill_color", (0, 255, 0, 255), 0.5, "linear").start(c2) + def to_blue(rt): + mcrfpy.Animation("fill_color", (0, 0, 255, 255), 0.5, "linear").start(c2) + def to_white(rt): + mcrfpy.Animation("fill_color", (255, 255, 255, 255), 0.5, "linear").start(c2) + + mcrfpy.setTimer("c_green", to_green, 600) + mcrfpy.setTimer("c_blue", to_blue, 1200) + mcrfpy.setTimer("c_white", to_white, 1800) + + cycle_colors() + + # Typewriter effect + c3 = mcrfpy.Caption("", 100, 400) + c3.fill_color = mcrfpy.Color(0, 255, 255) + ui.append(c3) + mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear").start(c3) + +def demo4_easing_showcase(): + """Demo 4: Showcase easing functions""" + clear_demo() + print("demo4") + subtitle.text = "Demo 4: 30 Easing Functions" + + ui = mcrfpy.sceneUI("sizzle") + + # Show first 15 easings + for i in range(15): + row = i // 5 + col = i % 5 + x = 80 + col * 180 + y = 150 + row * 120 + + # Create frame + f = mcrfpy.Frame(x, y, 20, 20) + f.fill_color = mcrfpy.Color(100, 150, 255) + f.outline = 1 + ui.append(f) + + # Label + label = mcrfpy.Caption(EASING_FUNCTIONS[i][:10], x, y - 20) + label.fill_color = mcrfpy.Color(200, 200, 200) + ui.append(label) + + # Animate with this easing + mcrfpy.Animation("x", float(x + 140), 3.0, EASING_FUNCTIONS[i]).start(f) + +def demo5_performance(): + """Demo 5: Many simultaneous animations""" + clear_demo() + print("demo5") + subtitle.text = "Demo 5: 50+ Simultaneous Animations" + + ui = mcrfpy.sceneUI("sizzle") + + # Create many animated objects + for i in range(50): + print(f"{i}...",end='',flush=True) + x = 100 + (i % 10) * 90 + y = 120 + (i // 10) * 80 + + f = mcrfpy.Frame(x, y, 25, 25) + r = (i * 37) % 256 + g = (i * 73) % 256 + b = (i * 113) % 256 + f.fill_color = (r, g, b, 200) #mcrfpy.Color(r, g, b, 200) + f.outline = 1 + ui.append(f) + + # Random animations + target_x = 150 + (i % 8) * 100 + target_y = 150 + (i // 8) * 85 + duration = 2.0 + (i % 30) * 0.1 + easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)] + + mcrfpy.Animation("x", float(target_x), duration, easing).start(f) + mcrfpy.Animation("y", float(target_y), duration, easing).start(f) + mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, 2.5, "easeInOutSine").start(f) + +def demo6_delta_mode(): + """Demo 6: Delta mode animations""" + clear_demo() + print("demo6") + subtitle.text = "Demo 6: Delta Mode (Relative Movement)" + + ui = mcrfpy.sceneUI("sizzle") + + # Create frames that move relative to position + positions = [(100, 300), (300, 300), (500, 300), (700, 300)] + colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255), (255, 255, 100)] + + for i, ((x, y), color) in enumerate(zip(positions, colors)): + f = mcrfpy.Frame(x, y, 60, 60) + f.fill_color = mcrfpy.Color(color[0], color[1], color[2]) + f.outline = 2 + ui.append(f) + + # Delta animations - move by amount, not to position + dx = (i + 1) * 30 + dy = math.sin(i * 0.5) * 50 + + mcrfpy.Animation("x", float(dx), 2.0, "easeInOutBack", delta=True).start(f) + mcrfpy.Animation("y", float(dy), 2.0, "easeInOutElastic", delta=True).start(f) + + # Caption explaining delta mode + info = mcrfpy.Caption("Delta mode: animations move BY amount, not TO position", 200, 450) + info.fill_color = mcrfpy.Color(255, 255, 255) + ui.append(info) + +def run_next_demo(runtime): + """Run the next demo in sequence""" + global current_demo + + demos = [ + demo1_frame_basics, + demo2_opacity_zindex, + demo3_captions, + demo4_easing_showcase, + demo5_performance, + demo6_delta_mode + ] + + if current_demo < len(demos): + # Clean up timers from previous demo + for timer in ["opacity_0", "opacity_1", "opacity_2", "opacity_3", + "c_green", "c_blue", "c_white"]: + if not mcrfpy.getTimer(timer): + continue + try: + mcrfpy.delTimer(timer) + except: + pass + + # Run next demo + print(f"Run next: {current_demo}") + demos[current_demo]() + current_demo += 1 + + # Schedule next demo + if current_demo < len(demos): + #mcrfpy.setTimer("next_demo", run_next_demo, int(DEMO_DURATION * 1000)) + pass + else: + current_demo = 0 + # All done + #subtitle.text = "Animation Showcase Complete!" + #complete = mcrfpy.Caption("All animations demonstrated successfully!", 350, 350) + #complete.fill_color = mcrfpy.Color(0, 255, 0) + #complete.outline = 2 + #ui = mcrfpy.sceneUI("sizzle") + #ui.append(complete) + # + ## Exit after delay + #def exit_program(rt): + # print("\nSizzle reel completed successfully!") + # sys.exit(0) + #mcrfpy.setTimer("exit", exit_program, 3000) + +# Handle ESC key +def handle_keypress(scene_name, keycode): + if keycode == 256: # ESC + print("\nExiting...") + sys.exit(0) + +# Initialize +print("Starting McRogueFace Animation Sizzle Reel...") +print("This demonstrates all animation capabilities.") +print("Press ESC to exit at any time.") + +create_scene() +mcrfpy.keypressScene(handle_keypress) + +# Start the show +mcrfpy.setTimer("start", run_next_demo, int(DEMO_DURATION * 1000)) diff --git a/tests/api_demo_final.py b/tests/api_demo_final.py new file mode 100644 index 0000000..10a8852 --- /dev/null +++ b/tests/api_demo_final.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +""" +McRogueFace API Demo - Final Version +==================================== + +Complete API demonstration with proper error handling. +Tests all constructors and methods systematically. +""" + +import mcrfpy +import sys + +def print_section(title): + """Print a section header""" + print("\n" + "="*60) + print(f" {title}") + print("="*60) + +def print_test(name, success=True): + """Print test result""" + status = "✓" if success else "✗" + print(f" {status} {name}") + +def test_colors(): + """Test Color API""" + print_section("COLOR TESTS") + + try: + # Basic constructors + c1 = mcrfpy.Color(255, 0, 0) # RGB + print_test(f"Color(255,0,0) = ({c1.r},{c1.g},{c1.b},{c1.a})") + + c2 = mcrfpy.Color(100, 150, 200, 128) # RGBA + print_test(f"Color(100,150,200,128) = ({c2.r},{c2.g},{c2.b},{c2.a})") + + # Property modification + c1.r = 128 + c1.g = 128 + c1.b = 128 + c1.a = 200 + print_test(f"Modified color = ({c1.r},{c1.g},{c1.b},{c1.a})") + + except Exception as e: + print_test(f"Color test failed: {e}", False) + +def test_frames(): + """Test Frame API""" + print_section("FRAME TESTS") + + # Create scene + mcrfpy.createScene("test") + mcrfpy.setScene("test") + ui = mcrfpy.sceneUI("test") + + try: + # Constructors + f1 = mcrfpy.Frame() + print_test(f"Frame() at ({f1.x},{f1.y}) size ({f1.w},{f1.h})") + + f2 = mcrfpy.Frame(100, 50) + print_test(f"Frame(100,50) at ({f2.x},{f2.y})") + + f3 = mcrfpy.Frame(200, 100, 150, 75) + print_test(f"Frame(200,100,150,75) size ({f3.w},{f3.h})") + + # Properties + f3.fill_color = mcrfpy.Color(100, 100, 200) + f3.outline = 3 + f3.outline_color = mcrfpy.Color(255, 255, 0) + f3.opacity = 0.8 + f3.visible = True + f3.z_index = 5 + print_test(f"Frame properties set") + + # Add to scene + ui.append(f3) + print_test(f"Frame added to scene") + + # Children + child = mcrfpy.Frame(10, 10, 50, 50) + f3.children.append(child) + print_test(f"Child added, count = {len(f3.children)}") + + except Exception as e: + print_test(f"Frame test failed: {e}", False) + +def test_captions(): + """Test Caption API""" + print_section("CAPTION TESTS") + + ui = mcrfpy.sceneUI("test") + + try: + # Constructors + c1 = mcrfpy.Caption() + print_test(f"Caption() text='{c1.text}'") + + c2 = mcrfpy.Caption("Hello World") + print_test(f"Caption('Hello World') at ({c2.x},{c2.y})") + + c3 = mcrfpy.Caption("Test", 300, 200) + print_test(f"Caption with position at ({c3.x},{c3.y})") + + # Properties + c3.text = "Modified" + c3.fill_color = mcrfpy.Color(255, 255, 0) + c3.outline = 2 + c3.outline_color = mcrfpy.Color(0, 0, 0) + print_test(f"Caption text='{c3.text}'") + + ui.append(c3) + print_test("Caption added to scene") + + except Exception as e: + print_test(f"Caption test failed: {e}", False) + +def test_animations(): + """Test Animation API""" + print_section("ANIMATION TESTS") + + ui = mcrfpy.sceneUI("test") + + try: + # Create target + frame = mcrfpy.Frame(50, 50, 100, 100) + frame.fill_color = mcrfpy.Color(100, 100, 100) + ui.append(frame) + + # Basic animations + a1 = mcrfpy.Animation("x", 300.0, 2.0) + print_test("Animation created (position)") + + a2 = mcrfpy.Animation("opacity", 0.5, 1.5, "easeInOut") + print_test("Animation with easing") + + a3 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 2.0) + print_test("Color animation (tuple)") + + # Start animations + a1.start(frame) + a2.start(frame) + a3.start(frame) + print_test("Animations started") + + # Check properties + print_test(f"Duration = {a1.duration}") + print_test(f"Elapsed = {a1.elapsed}") + print_test(f"Complete = {a1.is_complete}") + + except Exception as e: + print_test(f"Animation test failed: {e}", False) + +def test_collections(): + """Test collection operations""" + print_section("COLLECTION TESTS") + + ui = mcrfpy.sceneUI("test") + + try: + # Clear scene + while len(ui) > 0: + ui.remove(ui[len(ui)-1]) + print_test(f"Scene cleared, length = {len(ui)}") + + # Add items + for i in range(5): + f = mcrfpy.Frame(i*100, 50, 80, 80) + ui.append(f) + print_test(f"Added 5 frames, length = {len(ui)}") + + # Access + first = ui[0] + print_test(f"Accessed ui[0] at ({first.x},{first.y})") + + # Iteration + count = sum(1 for _ in ui) + print_test(f"Iteration count = {count}") + + except Exception as e: + print_test(f"Collection test failed: {e}", False) + +def run_tests(): + """Run all tests""" + print("\n" + "="*60) + print(" McRogueFace API Test Suite") + print("="*60) + + test_colors() + test_frames() + test_captions() + test_animations() + test_collections() + + print("\n" + "="*60) + print(" Tests Complete") + print("="*60) + + # Exit after delay + def exit_program(runtime): + print("\nExiting...") + sys.exit(0) + + mcrfpy.setTimer("exit", exit_program, 3000) + +# Run tests +print("Starting API tests...") +run_tests() \ No newline at end of file diff --git a/tests/exhaustive_api_demo.py b/tests/exhaustive_api_demo.py new file mode 100644 index 0000000..76d36cc --- /dev/null +++ b/tests/exhaustive_api_demo.py @@ -0,0 +1,1204 @@ +#!/usr/bin/env python3 +""" +McRogueFace Exhaustive API Demonstration +======================================== + +This script demonstrates EVERY constructor variant and EVERY method +for EVERY UI object type in McRogueFace. It serves as both a test +suite and a comprehensive API reference with working examples. + +The script is organized by UI object type, showing: +1. All constructor variants (empty, partial args, full args) +2. All properties (get and set) +3. All methods with different parameter combinations +4. Special behaviors and edge cases + +Author: Claude +Purpose: Complete API demonstration and validation +""" + +import mcrfpy +from mcrfpy import Color, Vector, Font, Texture, Frame, Caption, Sprite, Grid, Entity +import sys + +# Test configuration +VERBOSE = True # Print detailed information about each test + +def print_section(title): + """Print a section header""" + print("\n" + "="*60) + print(f" {title}") + print("="*60) + +def print_test(test_name, success=True): + """Print test result""" + status = "✓ PASS" if success else "✗ FAIL" + print(f" {status} - {test_name}") + +def test_color_api(): + """Test all Color constructors and methods""" + print_section("COLOR API TESTS") + + # Constructor variants + print("\n Constructors:") + + # Empty constructor (defaults to white) + c1 = Color() + print_test(f"Color() = ({c1.r}, {c1.g}, {c1.b}, {c1.a})") + + # Single value (grayscale) + c2 = Color(128) + print_test(f"Color(128) = ({c2.r}, {c2.g}, {c2.b}, {c2.a})") + + # RGB only (alpha defaults to 255) + c3 = Color(255, 128, 0) + print_test(f"Color(255, 128, 0) = ({c3.r}, {c3.g}, {c3.b}, {c3.a})") + + # Full RGBA + c4 = Color(100, 150, 200, 128) + print_test(f"Color(100, 150, 200, 128) = ({c4.r}, {c4.g}, {c4.b}, {c4.a})") + + # From hex string + c5 = Color.from_hex("#FF8800") + print_test(f"Color.from_hex('#FF8800') = ({c5.r}, {c5.g}, {c5.b}, {c5.a})") + + c6 = Color.from_hex("#FF8800AA") + print_test(f"Color.from_hex('#FF8800AA') = ({c6.r}, {c6.g}, {c6.b}, {c6.a})") + + # Methods + print("\n Methods:") + + # to_hex + hex_str = c4.to_hex() + print_test(f"Color(100, 150, 200, 128).to_hex() = '{hex_str}'") + + # lerp (linear interpolation) + c_start = Color(0, 0, 0) + c_end = Color(255, 255, 255) + c_mid = c_start.lerp(c_end, 0.5) + print_test(f"Black.lerp(White, 0.5) = ({c_mid.r}, {c_mid.g}, {c_mid.b}, {c_mid.a})") + + # Property access + print("\n Properties:") + c = Color(10, 20, 30, 40) + print_test(f"Initial: r={c.r}, g={c.g}, b={c.b}, a={c.a}") + + c.r = 200 + c.g = 150 + c.b = 100 + c.a = 255 + print_test(f"After modification: r={c.r}, g={c.g}, b={c.b}, a={c.a}") + + return True + +def test_vector_api(): + """Test all Vector constructors and methods""" + print_section("VECTOR API TESTS") + + # Constructor variants + print("\n Constructors:") + + # Empty constructor + v1 = Vector() + print_test(f"Vector() = ({v1.x}, {v1.y})") + + # Single value (both x and y) + v2 = Vector(5.0) + print_test(f"Vector(5.0) = ({v2.x}, {v2.y})") + + # Full x, y + v3 = Vector(10.5, 20.3) + print_test(f"Vector(10.5, 20.3) = ({v3.x}, {v3.y})") + + # Methods + print("\n Methods:") + + # magnitude + v = Vector(3, 4) + mag = v.magnitude() + print_test(f"Vector(3, 4).magnitude() = {mag}") + + # normalize + v_norm = v.normalize() + print_test(f"Vector(3, 4).normalize() = ({v_norm.x:.3f}, {v_norm.y:.3f})") + + # dot product + v_a = Vector(2, 3) + v_b = Vector(4, 5) + dot = v_a.dot(v_b) + print_test(f"Vector(2, 3).dot(Vector(4, 5)) = {dot}") + + # distance_to + dist = v_a.distance_to(v_b) + print_test(f"Vector(2, 3).distance_to(Vector(4, 5)) = {dist:.3f}") + + # Operators + print("\n Operators:") + + # Addition + v_sum = v_a + v_b + print_test(f"Vector(2, 3) + Vector(4, 5) = ({v_sum.x}, {v_sum.y})") + + # Subtraction + v_diff = v_b - v_a + print_test(f"Vector(4, 5) - Vector(2, 3) = ({v_diff.x}, {v_diff.y})") + + # Multiplication (scalar) + v_mult = v_a * 2.5 + print_test(f"Vector(2, 3) * 2.5 = ({v_mult.x}, {v_mult.y})") + + # Division (scalar) + v_div = v_b / 2.0 + print_test(f"Vector(4, 5) / 2.0 = ({v_div.x}, {v_div.y})") + + # Comparison + v_eq1 = Vector(1, 2) + v_eq2 = Vector(1, 2) + v_neq = Vector(3, 4) + print_test(f"Vector(1, 2) == Vector(1, 2) = {v_eq1 == v_eq2}") + print_test(f"Vector(1, 2) != Vector(3, 4) = {v_eq1 != v_neq}") + + return True + +def test_frame_api(): + """Test all Frame constructors and methods""" + print_section("FRAME API TESTS") + + # Create a test scene + mcrfpy.createScene("api_test") + mcrfpy.setScene("api_test") + ui = mcrfpy.sceneUI("api_test") + + # Constructor variants + print("\n Constructors:") + + # Empty constructor + f1 = Frame() + print_test(f"Frame() - pos=({f1.x}, {f1.y}), size=({f1.w}, {f1.h})") + ui.append(f1) + + # Position only + f2 = Frame(100, 50) + print_test(f"Frame(100, 50) - pos=({f2.x}, {f2.y}), size=({f2.w}, {f2.h})") + ui.append(f2) + + # Position and size + f3 = Frame(200, 100, 150, 75) + print_test(f"Frame(200, 100, 150, 75) - pos=({f3.x}, {f3.y}), size=({f3.w}, {f3.h})") + ui.append(f3) + + # Full constructor + f4 = Frame(300, 200, 200, 100, + fill_color=Color(100, 100, 200), + outline_color=Color(255, 255, 0), + outline=3) + print_test("Frame with all parameters") + ui.append(f4) + + # With click handler + def on_click(x, y, button): + print(f" Frame clicked at ({x}, {y}) with button {button}") + + f5 = Frame(500, 300, 100, 100, click=on_click) + print_test("Frame with click handler") + ui.append(f5) + + # Properties + print("\n Properties:") + + # Position and size + f = Frame(10, 20, 30, 40) + print_test(f"Initial: x={f.x}, y={f.y}, w={f.w}, h={f.h}") + + f.x = 50 + f.y = 60 + f.w = 70 + f.h = 80 + print_test(f"Modified: x={f.x}, y={f.y}, w={f.w}, h={f.h}") + + # Colors + f.fill_color = Color(255, 0, 0, 128) + f.outline_color = Color(0, 255, 0) + f.outline = 5.0 + print_test(f"Colors set, outline={f.outline}") + + # Visibility and opacity + f.visible = False + f.opacity = 0.5 + print_test(f"visible={f.visible}, opacity={f.opacity}") + f.visible = True # Reset + + # Z-index + f.z_index = 10 + print_test(f"z_index={f.z_index}") + + # Children collection + child1 = Frame(5, 5, 20, 20) + child2 = Frame(30, 5, 20, 20) + f.children.append(child1) + f.children.append(child2) + print_test(f"children.count = {len(f.children)}") + + # Clip children + f.clip_children = True + print_test(f"clip_children={f.clip_children}") + + # Methods + print("\n Methods:") + + # get_bounds + bounds = f.get_bounds() + print_test(f"get_bounds() = {bounds}") + + # move + old_pos = (f.x, f.y) + f.move(10, 15) + new_pos = (f.x, f.y) + print_test(f"move(10, 15): {old_pos} -> {new_pos}") + + # resize + old_size = (f.w, f.h) + f.resize(100, 120) + new_size = (f.w, f.h) + print_test(f"resize(100, 120): {old_size} -> {new_size}") + + # Position tuple property + f.pos = (150, 175) + print_test(f"pos property: ({f.x}, {f.y})") + + # Children collection methods + print("\n Children Collection:") + + # Clear and test + f.children.extend([Frame(0, 0, 10, 10) for _ in range(3)]) + print_test(f"extend() - count = {len(f.children)}") + + # Index access + first_child = f.children[0] + print_test(f"children[0] = Frame at ({first_child.x}, {first_child.y})") + + # Remove + f.children.remove(first_child) + print_test(f"remove() - count = {len(f.children)}") + + # Iteration + count = 0 + for child in f.children: + count += 1 + print_test(f"iteration - counted {count} children") + + return True + +def test_caption_api(): + """Test all Caption constructors and methods""" + print_section("CAPTION API TESTS") + + ui = mcrfpy.sceneUI("api_test") + + # Constructor variants + print("\n Constructors:") + + # Empty constructor + c1 = Caption() + print_test(f"Caption() - text='{c1.text}', pos=({c1.x}, {c1.y})") + ui.append(c1) + + # Text only + c2 = Caption("Hello World") + print_test(f"Caption('Hello World') - pos=({c2.x}, {c2.y})") + ui.append(c2) + + # Text and position + c3 = Caption("Positioned Text", 100, 50) + print_test(f"Caption('Positioned Text', 100, 50)") + ui.append(c3) + + # With font (would need Font object) + # font = Font("assets/fonts/arial.ttf", 16) + # c4 = Caption("Custom Font", 200, 100, font) + + # Full constructor + c5 = Caption("Styled Text", 300, 150, + fill_color=Color(255, 255, 0), + outline_color=Color(255, 0, 0), + outline=2) + print_test("Caption with all style parameters") + ui.append(c5) + + # With click handler + def caption_click(x, y, button): + print(f" Caption clicked at ({x}, {y})") + + c6 = Caption("Clickable", 400, 200, click=caption_click) + print_test("Caption with click handler") + ui.append(c6) + + # Properties + print("\n Properties:") + + c = Caption("Test Caption", 10, 20) + + # Text + c.text = "Modified Text" + print_test(f"text = '{c.text}'") + + # Position + c.x = 50 + c.y = 60 + print_test(f"position = ({c.x}, {c.y})") + + # Colors and style + c.fill_color = Color(0, 255, 255) + c.outline_color = Color(255, 0, 255) + c.outline = 3.0 + print_test("Colors and outline set") + + # Size (read-only, computed from text) + print_test(f"size (computed) = ({c.w}, {c.h})") + + # Common properties + c.visible = True + c.opacity = 0.8 + c.z_index = 5 + print_test(f"visible={c.visible}, opacity={c.opacity}, z_index={c.z_index}") + + # Methods + print("\n Methods:") + + # get_bounds + bounds = c.get_bounds() + print_test(f"get_bounds() = {bounds}") + + # move + c.move(25, 30) + print_test(f"move(25, 30) - new pos = ({c.x}, {c.y})") + + # Special text behaviors + print("\n Text Behaviors:") + + # Empty text + c.text = "" + print_test(f"Empty text - size = ({c.w}, {c.h})") + + # Multiline text + c.text = "Line 1\nLine 2\nLine 3" + print_test(f"Multiline text - size = ({c.w}, {c.h})") + + # Very long text + c.text = "A" * 100 + print_test(f"Long text (100 chars) - size = ({c.w}, {c.h})") + + return True + +def test_sprite_api(): + """Test all Sprite constructors and methods""" + print_section("SPRITE API TESTS") + + ui = mcrfpy.sceneUI("api_test") + + # Try to load a texture for testing + texture = None + try: + texture = Texture("assets/sprites/player.png", grid_size=(32, 32)) + print_test("Texture loaded successfully") + except: + print_test("Texture load failed - using None", False) + + # Constructor variants + print("\n Constructors:") + + # Empty constructor + s1 = Sprite() + print_test(f"Sprite() - pos=({s1.x}, {s1.y}), sprite_index={s1.sprite_index}") + ui.append(s1) + + # Position only + s2 = Sprite(100, 50) + print_test(f"Sprite(100, 50)") + ui.append(s2) + + # Position and texture + s3 = Sprite(200, 100, texture) + print_test(f"Sprite(200, 100, texture)") + ui.append(s3) + + # Full constructor + s4 = Sprite(300, 150, texture, sprite_index=5, scale=2.0) + print_test(f"Sprite with texture, index=5, scale=2.0") + ui.append(s4) + + # With click handler + def sprite_click(x, y, button): + print(f" Sprite clicked!") + + s5 = Sprite(400, 200, texture, click=sprite_click) + print_test("Sprite with click handler") + ui.append(s5) + + # Properties + print("\n Properties:") + + s = Sprite(10, 20, texture) + + # Position + s.x = 50 + s.y = 60 + print_test(f"position = ({s.x}, {s.y})") + + # Position tuple + s.pos = (75, 85) + print_test(f"pos tuple = ({s.x}, {s.y})") + + # Sprite index + s.sprite_index = 10 + print_test(f"sprite_index = {s.sprite_index}") + + # Scale + s.scale = 1.5 + print_test(f"scale = {s.scale}") + + # Size (computed from texture and scale) + print_test(f"size (computed) = ({s.w}, {s.h})") + + # Texture + s.texture = texture # Can reassign texture + print_test("Texture reassigned") + + # Common properties + s.visible = True + s.opacity = 0.9 + s.z_index = 3 + print_test(f"visible={s.visible}, opacity={s.opacity}, z_index={s.z_index}") + + # Methods + print("\n Methods:") + + # get_bounds + bounds = s.get_bounds() + print_test(f"get_bounds() = {bounds}") + + # move + old_pos = (s.x, s.y) + s.move(15, 20) + new_pos = (s.x, s.y) + print_test(f"move(15, 20): {old_pos} -> {new_pos}") + + # Sprite animation test + print("\n Sprite Animation:") + + # Test different sprite indices + for i in range(5): + s.sprite_index = i + print_test(f"Set sprite_index to {i}") + + return True + +def test_grid_api(): + """Test all Grid constructors and methods""" + print_section("GRID API TESTS") + + ui = mcrfpy.sceneUI("api_test") + + # Load texture for grid + texture = None + try: + texture = Texture("assets/sprites/tiles.png", grid_size=(16, 16)) + print_test("Tile texture loaded") + except: + print_test("Tile texture load failed", False) + + # Constructor variants + print("\n Constructors:") + + # Empty constructor + g1 = Grid() + print_test(f"Grid() - pos=({g1.x}, {g1.y}), grid_size={g1.grid_size}") + ui.append(g1) + + # Position only + g2 = Grid(100, 50) + print_test(f"Grid(100, 50)") + ui.append(g2) + + # Position and grid size + g3 = Grid(200, 100, grid_size=(30, 20)) + print_test(f"Grid with size (30, 20)") + ui.append(g3) + + # With texture + g4 = Grid(300, 150, grid_size=(25, 15), texture=texture) + print_test("Grid with texture") + ui.append(g4) + + # Full constructor + g5 = Grid(400, 200, grid_size=(20, 10), texture=texture, + tile_width=24, tile_height=24, scale=1.5) + print_test("Grid with all parameters") + ui.append(g5) + + # With click handler + def grid_click(x, y, button): + print(f" Grid clicked at ({x}, {y})") + + g6 = Grid(500, 250, click=grid_click) + print_test("Grid with click handler") + ui.append(g6) + + # Properties + print("\n Properties:") + + g = Grid(10, 20, grid_size=(40, 30)) + + # Position + g.x = 50 + g.y = 60 + print_test(f"position = ({g.x}, {g.y})") + + # Grid dimensions + print_test(f"grid_size = {g.grid_size}") + print_test(f"grid_x = {g.grid_x}, grid_y = {g.grid_y}") + + # Tile dimensions + g.tile_width = 20 + g.tile_height = 20 + print_test(f"tile size = ({g.tile_width}, {g.tile_height})") + + # Scale + g.scale = 2.0 + print_test(f"scale = {g.scale}") + + # Texture + g.texture = texture + print_test("Texture assigned") + + # Fill color + g.fill_color = Color(30, 30, 50) + print_test("Fill color set") + + # Camera properties + g.center = (20.0, 15.0) + print_test(f"center (camera) = {g.center}") + + g.zoom = 1.5 + print_test(f"zoom = {g.zoom}") + + # Common properties + g.visible = True + g.opacity = 0.95 + g.z_index = 1 + print_test(f"visible={g.visible}, opacity={g.opacity}, z_index={g.z_index}") + + # Grid point access + print("\n Grid Points:") + + # Access grid point + point = g.at(5, 5) + print_test(f"at(5, 5) returned GridPoint") + + # Modify grid point + point.tilesprite = 10 + point.tile_overlay = 2 + point.walkable = False + point.transparent = True + point.color = Color(255, 0, 0, 128) + print_test("GridPoint properties modified") + + # Check modifications + print_test(f" tilesprite = {point.tilesprite}") + print_test(f" walkable = {point.walkable}") + print_test(f" transparent = {point.transparent}") + + # Entity collection + print("\n Entity Collection:") + + # Create entities + if texture: + e1 = Entity(10.5, 10.5, texture, sprite_index=5) + e2 = Entity(15.0, 12.0, texture, sprite_index=8) + + g.entities.append(e1) + g.entities.append(e2) + print_test(f"Added 2 entities, count = {len(g.entities)}") + + # Access entities + first = g.entities[0] + print_test(f"entities[0] at ({first.x}, {first.y})") + + # Iterate entities + count = 0 + for entity in g.entities: + count += 1 + print_test(f"Iterated {count} entities") + + # Methods + print("\n Methods:") + + # get_bounds + bounds = g.get_bounds() + print_test(f"get_bounds() = {bounds}") + + # move + g.move(20, 25) + print_test(f"move(20, 25) - new pos = ({g.x}, {g.y})") + + # Points array access + print("\n Points Array:") + + # The points property is a 2D array + all_points = g.points + print_test(f"points array dimensions: {len(all_points)}x{len(all_points[0]) if all_points else 0}") + + # Modify multiple points + for y in range(5): + for x in range(5): + pt = g.at(x, y) + pt.tilesprite = x + y * 5 + pt.color = Color(x * 50, y * 50, 100) + print_test("Modified 5x5 area of grid") + + return True + +def test_entity_api(): + """Test all Entity constructors and methods""" + print_section("ENTITY API TESTS") + + # Entities need to be in a grid + ui = mcrfpy.sceneUI("api_test") + + # Create grid and texture + texture = None + try: + texture = Texture("assets/sprites/entities.png", grid_size=(32, 32)) + print_test("Entity texture loaded") + except: + print_test("Entity texture load failed", False) + + grid = Grid(50, 50, grid_size=(30, 30), texture=texture) + ui.append(grid) + + # Constructor variants + print("\n Constructors:") + + # Empty constructor + e1 = Entity() + print_test(f"Entity() - pos=({e1.x}, {e1.y}), sprite_index={e1.sprite_index}") + grid.entities.append(e1) + + # Position only + e2 = Entity(5.5, 3.5) + print_test(f"Entity(5.5, 3.5)") + grid.entities.append(e2) + + # Position and texture + e3 = Entity(10.0, 8.0, texture) + print_test("Entity with texture") + grid.entities.append(e3) + + # Full constructor + e4 = Entity(15.5, 12.5, texture, sprite_index=7, scale=1.5) + print_test("Entity with all parameters") + grid.entities.append(e4) + + # Properties + print("\n Properties:") + + e = Entity(20.0, 15.0, texture, sprite_index=3) + grid.entities.append(e) + + # Position (float coordinates in grid space) + e.x = 22.5 + e.y = 16.5 + print_test(f"position = ({e.x}, {e.y})") + + # Position tuple + e.position = (24.0, 18.0) + print_test(f"position tuple = {e.position}") + + # Sprite index + e.sprite_index = 12 + print_test(f"sprite_index = {e.sprite_index}") + + # Scale + e.scale = 2.0 + print_test(f"scale = {e.scale}") + + # Methods + print("\n Methods:") + + # index() - get position in entity collection + idx = e.index() + print_test(f"index() in collection = {idx}") + + # Gridstate (visibility per grid cell) + print("\n Grid State:") + + # Access gridstate + if len(e.gridstate) > 0: + state = e.gridstate[0] + print_test(f"gridstate[0] - visible={state.visible}, discovered={state.discovered}") + + # Modify visibility + state.visible = True + state.discovered = True + print_test("Modified gridstate visibility") + + # at() method - check if entity occupies a grid point + # This would need a GridPointState object + # occupied = e.at(some_gridpoint_state) + + # die() method - remove from grid + print("\n Entity Lifecycle:") + + # Create temporary entity + temp_entity = Entity(25.0, 25.0, texture) + grid.entities.append(temp_entity) + count_before = len(grid.entities) + + # Remove it + temp_entity.die() + count_after = len(grid.entities) + print_test(f"die() - entity count: {count_before} -> {count_after}") + + # Entity movement + print("\n Entity Movement:") + + # Test fractional positions (entities can be between grid cells) + e.position = (10.0, 10.0) + print_test(f"Integer position: {e.position}") + + e.position = (10.5, 10.5) + print_test(f"Center of cell: {e.position}") + + e.position = (10.25, 10.75) + print_test(f"Fractional position: {e.position}") + + return True + +def test_collections(): + """Test UICollection and EntityCollection behaviors""" + print_section("COLLECTION API TESTS") + + ui = mcrfpy.sceneUI("api_test") + + # Test UICollection (scene UI and frame children) + print("\n UICollection (Scene UI):") + + # Clear scene + while len(ui) > 0: + ui.remove(ui[0]) + print_test(f"Cleared - length = {len(ui)}") + + # append + f1 = Frame(10, 10, 50, 50) + ui.append(f1) + print_test(f"append() - length = {len(ui)}") + + # extend + frames = [Frame(x * 60, 10, 50, 50) for x in range(1, 4)] + ui.extend(frames) + print_test(f"extend() with 3 items - length = {len(ui)}") + + # index access + item = ui[0] + print_test(f"ui[0] = Frame at ({item.x}, {item.y})") + + # slice access + slice_items = ui[1:3] + print_test(f"ui[1:3] returned {len(slice_items)} items") + + # index() method + idx = ui.index(f1) + print_test(f"index(frame) = {idx}") + + # count() method + cnt = ui.count(f1) + print_test(f"count(frame) = {cnt}") + + # in operator + contains = f1 in ui + print_test(f"frame in ui = {contains}") + + # iteration + count = 0 + for item in ui: + count += 1 + print_test(f"Iteration counted {count} items") + + # remove + ui.remove(f1) + print_test(f"remove() - length = {len(ui)}") + + # Test Frame.children collection + print("\n UICollection (Frame Children):") + + parent = Frame(100, 100, 300, 200) + ui.append(parent) + + # Add children + child1 = Caption("Child 1", 10, 10) + child2 = Caption("Child 2", 10, 30) + child3 = Frame(10, 50, 50, 50) + + parent.children.append(child1) + parent.children.append(child2) + parent.children.append(child3) + print_test(f"Added 3 children - count = {len(parent.children)}") + + # Mixed types in collection + has_caption = any(isinstance(child, Caption) for child in parent.children) + has_frame = any(isinstance(child, Frame) for child in parent.children) + print_test(f"Mixed types: has Caption = {has_caption}, has Frame = {has_frame}") + + # Test EntityCollection + print("\n EntityCollection (Grid Entities):") + + texture = None + try: + texture = Texture("assets/sprites/entities.png", grid_size=(32, 32)) + except: + pass + + grid = Grid(400, 100, grid_size=(20, 20), texture=texture) + ui.append(grid) + + # Add entities + entities = [] + for i in range(5): + e = Entity(float(i * 2), float(i * 2), texture, sprite_index=i) + grid.entities.append(e) + entities.append(e) + + print_test(f"Added 5 entities - count = {len(grid.entities)}") + + # Access and iteration + first_entity = grid.entities[0] + print_test(f"entities[0] at ({first_entity.x}, {first_entity.y})") + + # Remove entity + grid.entities.remove(first_entity) + print_test(f"Removed entity - count = {len(grid.entities)}") + + return True + +def test_animation_api(): + """Test Animation class API""" + print_section("ANIMATION API TESTS") + + ui = mcrfpy.sceneUI("api_test") + + # Import Animation + from mcrfpy import Animation + + print("\n Animation Constructors:") + + # Basic animation + anim1 = Animation("x", 100.0, 2.0) + print_test("Animation('x', 100.0, 2.0)") + + # With easing + anim2 = Animation("y", 200.0, 3.0, "easeInOut") + print_test("Animation with easing='easeInOut'") + + # Delta mode + anim3 = Animation("w", 50.0, 1.5, "linear", delta=True) + print_test("Animation with delta=True") + + # Color animation + anim4 = Animation("fill_color", Color(255, 0, 0), 2.0) + print_test("Animation with Color target") + + # Vector animation + anim5 = Animation("position", (10.0, 20.0), 2.5, "easeOutBounce") + print_test("Animation with position tuple") + + # Sprite sequence + anim6 = Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0) + print_test("Animation with sprite sequence") + + # Properties + print("\n Animation Properties:") + + # Check properties + print_test(f"property = '{anim1.property}'") + print_test(f"duration = {anim1.duration}") + print_test(f"elapsed = {anim1.elapsed}") + print_test(f"is_complete = {anim1.is_complete}") + print_test(f"is_delta = {anim3.is_delta}") + + # Methods + print("\n Animation Methods:") + + # Create test frame + frame = Frame(50, 50, 100, 100) + frame.fill_color = Color(100, 100, 100) + ui.append(frame) + + # Start animation + anim1.start(frame) + print_test("start() called on frame") + + # Get current value (before update) + current = anim1.get_current_value() + print_test(f"get_current_value() = {current}") + + # Manual update (usually automatic) + anim1.update(0.5) # 0.5 seconds + print_test("update(0.5) called") + + # Check elapsed time + print_test(f"elapsed after update = {anim1.elapsed}") + + # All easing functions + print("\n Available Easing Functions:") + easings = [ + "linear", "easeIn", "easeOut", "easeInOut", + "easeInQuad", "easeOutQuad", "easeInOutQuad", + "easeInCubic", "easeOutCubic", "easeInOutCubic", + "easeInQuart", "easeOutQuart", "easeInOutQuart", + "easeInSine", "easeOutSine", "easeInOutSine", + "easeInExpo", "easeOutExpo", "easeInOutExpo", + "easeInCirc", "easeOutCirc", "easeInOutCirc", + "easeInElastic", "easeOutElastic", "easeInOutElastic", + "easeInBack", "easeOutBack", "easeInOutBack", + "easeInBounce", "easeOutBounce", "easeInOutBounce" + ] + + # Test creating animation with each easing + for easing in easings[:10]: # Test first 10 + try: + test_anim = Animation("x", 100.0, 1.0, easing) + print_test(f"Easing '{easing}' ✓") + except: + print_test(f"Easing '{easing}' failed", False) + + return True + +def test_scene_api(): + """Test scene-related API functions""" + print_section("SCENE API TESTS") + + print("\n Scene Management:") + + # Create scene + mcrfpy.createScene("test_scene_1") + print_test("createScene('test_scene_1')") + + mcrfpy.createScene("test_scene_2") + print_test("createScene('test_scene_2')") + + # Set active scene + mcrfpy.setScene("test_scene_1") + print_test("setScene('test_scene_1')") + + # Get scene UI + ui1 = mcrfpy.sceneUI("test_scene_1") + print_test(f"sceneUI('test_scene_1') - collection size = {len(ui1)}") + + ui2 = mcrfpy.sceneUI("test_scene_2") + print_test(f"sceneUI('test_scene_2') - collection size = {len(ui2)}") + + # Add content to scenes + ui1.append(Frame(10, 10, 100, 100)) + ui1.append(Caption("Scene 1", 10, 120)) + print_test(f"Added content to scene 1 - size = {len(ui1)}") + + ui2.append(Frame(20, 20, 150, 150)) + ui2.append(Caption("Scene 2", 20, 180)) + print_test(f"Added content to scene 2 - size = {len(ui2)}") + + # Scene transitions + print("\n Scene Transitions:") + + # Note: Actual transition types would need to be tested visually + # TransitionType enum: None, Fade, SlideLeft, SlideRight, SlideUp, SlideDown + + # Keypress handling + print("\n Input Handling:") + + def test_keypress(scene_name, keycode): + print(f" Key pressed in {scene_name}: {keycode}") + + mcrfpy.keypressScene("test_scene_1", test_keypress) + print_test("keypressScene() handler registered") + + return True + +def test_audio_api(): + """Test audio-related API functions""" + print_section("AUDIO API TESTS") + + print("\n Sound Functions:") + + # Create sound buffer + try: + mcrfpy.createSoundBuffer("test_sound", "assets/audio/click.wav") + print_test("createSoundBuffer('test_sound', 'click.wav')") + + # Play sound + mcrfpy.playSound("test_sound") + print_test("playSound('test_sound')") + + # Set volume + mcrfpy.setVolume("test_sound", 0.5) + print_test("setVolume('test_sound', 0.5)") + + except Exception as e: + print_test(f"Audio functions failed: {e}", False) + + return True + +def test_timer_api(): + """Test timer API functions""" + print_section("TIMER API TESTS") + + print("\n Timer Functions:") + + # Timer callback + def timer_callback(runtime): + print(f" Timer fired at runtime: {runtime}") + + # Set timer + mcrfpy.setTimer("test_timer", timer_callback, 1000) # 1 second + print_test("setTimer('test_timer', callback, 1000)") + + # Delete timer + mcrfpy.delTimer("test_timer") + print_test("delTimer('test_timer')") + + # Multiple timers + mcrfpy.setTimer("timer1", lambda r: print(f" Timer 1: {r}"), 500) + mcrfpy.setTimer("timer2", lambda r: print(f" Timer 2: {r}"), 750) + mcrfpy.setTimer("timer3", lambda r: print(f" Timer 3: {r}"), 1000) + print_test("Set 3 timers with different intervals") + + # Clean up + mcrfpy.delTimer("timer1") + mcrfpy.delTimer("timer2") + mcrfpy.delTimer("timer3") + print_test("Cleaned up all timers") + + return True + +def test_edge_cases(): + """Test edge cases and error conditions""" + print_section("EDGE CASES AND ERROR HANDLING") + + ui = mcrfpy.sceneUI("api_test") + + print("\n Boundary Values:") + + # Negative positions + f = Frame(-100, -50, 50, 50) + print_test(f"Negative position: ({f.x}, {f.y})") + + # Zero size + f2 = Frame(0, 0, 0, 0) + print_test(f"Zero size: ({f2.w}, {f2.h})") + + # Very large values + f3 = Frame(10000, 10000, 5000, 5000) + print_test(f"Large values: pos=({f3.x}, {f3.y}), size=({f3.w}, {f3.h})") + + # Opacity bounds + f.opacity = -0.5 + print_test(f"Opacity below 0: {f.opacity}") + + f.opacity = 2.0 + print_test(f"Opacity above 1: {f.opacity}") + + # Color component bounds + c = Color(300, -50, 1000, 128) + print_test(f"Color out of bounds: ({c.r}, {c.g}, {c.b}, {c.a})") + + print("\n Empty Collections:") + + # Empty children + frame = Frame(0, 0, 100, 100) + print_test(f"Empty children collection: {len(frame.children)}") + + # Access empty collection + try: + item = frame.children[0] + print_test("Accessing empty collection[0]", False) + except IndexError: + print_test("Accessing empty collection[0] raises IndexError") + + print("\n Invalid Operations:") + + # Grid without texture + g = Grid(0, 0, grid_size=(10, 10)) + point = g.at(5, 5) + point.tilesprite = 10 # No texture to reference + print_test("Set tilesprite without texture") + + # Entity without grid + e = Entity(5.0, 5.0) + # e.die() would fail if not in a grid + print_test("Created entity without grid") + + return True + +def run_all_tests(): + """Run all API tests""" + print("\n" + "="*60) + print(" McRogueFace Exhaustive API Test Suite") + print(" Testing every constructor and method...") + print("="*60) + + # Run each test category + test_functions = [ + test_color_api, + test_vector_api, + test_frame_api, + test_caption_api, + test_sprite_api, + test_grid_api, + test_entity_api, + test_collections, + test_animation_api, + test_scene_api, + test_audio_api, + test_timer_api, + test_edge_cases + ] + + passed = 0 + failed = 0 + + for test_func in test_functions: + try: + if test_func(): + passed += 1 + else: + failed += 1 + except Exception as e: + print(f"\n ERROR in {test_func.__name__}: {e}") + failed += 1 + + # Summary + print("\n" + "="*60) + print(f" TEST SUMMARY: {passed} passed, {failed} failed") + print("="*60) + + # Visual test scene + print("\n Visual elements are displayed in the 'api_test' scene.") + print(" The test is complete. Press ESC to exit.") + +def handle_exit(scene_name, keycode): + """Handle ESC key to exit""" + if keycode == 256: # ESC + print("\nExiting API test suite...") + sys.exit(0) + +# Set up exit handler +mcrfpy.keypressScene("api_test", handle_exit) + +# Run after short delay to ensure scene is ready +def start_tests(runtime): + run_all_tests() + +mcrfpy.setTimer("start_tests", start_tests, 100) + +print("Starting McRogueFace Exhaustive API Demo...") +print("This will test EVERY constructor and method.") +print("Press ESC to exit at any time.") \ No newline at end of file diff --git a/tests/exhaustive_api_demo_fixed.py b/tests/exhaustive_api_demo_fixed.py new file mode 100644 index 0000000..2b7bd40 --- /dev/null +++ b/tests/exhaustive_api_demo_fixed.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +""" +McRogueFace Exhaustive API Demo (Fixed) +======================================= + +Fixed version that properly exits after tests complete. +""" + +import mcrfpy +import sys + +# Test configuration +VERBOSE = True # Print detailed information about each test + +def print_section(title): + """Print a section header""" + print("\n" + "="*60) + print(f" {title}") + print("="*60) + +def print_test(test_name, success=True): + """Print test result""" + status = "✓ PASS" if success else "✗ FAIL" + print(f" {status} - {test_name}") + +def test_color_api(): + """Test all Color constructors and methods""" + print_section("COLOR API TESTS") + + # Constructor variants + print("\n Constructors:") + + # Empty constructor (defaults to white) + c1 = mcrfpy.Color() + print_test(f"Color() = ({c1.r}, {c1.g}, {c1.b}, {c1.a})") + + # Single value (grayscale) + c2 = mcrfpy.Color(128) + print_test(f"Color(128) = ({c2.r}, {c2.g}, {c2.b}, {c2.a})") + + # RGB only (alpha defaults to 255) + c3 = mcrfpy.Color(255, 128, 0) + print_test(f"Color(255, 128, 0) = ({c3.r}, {c3.g}, {c3.b}, {c3.a})") + + # Full RGBA + c4 = mcrfpy.Color(100, 150, 200, 128) + print_test(f"Color(100, 150, 200, 128) = ({c4.r}, {c4.g}, {c4.b}, {c4.a})") + + # Property access + print("\n Properties:") + c = mcrfpy.Color(10, 20, 30, 40) + print_test(f"Initial: r={c.r}, g={c.g}, b={c.b}, a={c.a}") + + c.r = 200 + c.g = 150 + c.b = 100 + c.a = 255 + print_test(f"After modification: r={c.r}, g={c.g}, b={c.b}, a={c.a}") + + return True + +def test_frame_api(): + """Test all Frame constructors and methods""" + print_section("FRAME API TESTS") + + # Create a test scene + mcrfpy.createScene("api_test") + mcrfpy.setScene("api_test") + ui = mcrfpy.sceneUI("api_test") + + # Constructor variants + print("\n Constructors:") + + # Empty constructor + f1 = mcrfpy.Frame() + print_test(f"Frame() - pos=({f1.x}, {f1.y}), size=({f1.w}, {f1.h})") + ui.append(f1) + + # Position only + f2 = mcrfpy.Frame(100, 50) + print_test(f"Frame(100, 50) - pos=({f2.x}, {f2.y}), size=({f2.w}, {f2.h})") + ui.append(f2) + + # Position and size + f3 = mcrfpy.Frame(200, 100, 150, 75) + print_test(f"Frame(200, 100, 150, 75) - pos=({f3.x}, {f3.y}), size=({f3.w}, {f3.h})") + ui.append(f3) + + # Full constructor + f4 = mcrfpy.Frame(300, 200, 200, 100, + fill_color=mcrfpy.Color(100, 100, 200), + outline_color=mcrfpy.Color(255, 255, 0), + outline=3) + print_test("Frame with all parameters") + ui.append(f4) + + # Properties + print("\n Properties:") + + # Position and size + f = mcrfpy.Frame(10, 20, 30, 40) + print_test(f"Initial: x={f.x}, y={f.y}, w={f.w}, h={f.h}") + + f.x = 50 + f.y = 60 + f.w = 70 + f.h = 80 + print_test(f"Modified: x={f.x}, y={f.y}, w={f.w}, h={f.h}") + + # Colors + f.fill_color = mcrfpy.Color(255, 0, 0, 128) + f.outline_color = mcrfpy.Color(0, 255, 0) + f.outline = 5.0 + print_test(f"Colors set, outline={f.outline}") + + # Visibility and opacity + f.visible = False + f.opacity = 0.5 + print_test(f"visible={f.visible}, opacity={f.opacity}") + f.visible = True # Reset + + # Z-index + f.z_index = 10 + print_test(f"z_index={f.z_index}") + + # Children collection + child1 = mcrfpy.Frame(5, 5, 20, 20) + child2 = mcrfpy.Frame(30, 5, 20, 20) + f.children.append(child1) + f.children.append(child2) + print_test(f"children.count = {len(f.children)}") + + return True + +def test_caption_api(): + """Test all Caption constructors and methods""" + print_section("CAPTION API TESTS") + + ui = mcrfpy.sceneUI("api_test") + + # Constructor variants + print("\n Constructors:") + + # Empty constructor + c1 = mcrfpy.Caption() + print_test(f"Caption() - text='{c1.text}', pos=({c1.x}, {c1.y})") + ui.append(c1) + + # Text only + c2 = mcrfpy.Caption("Hello World") + print_test(f"Caption('Hello World') - pos=({c2.x}, {c2.y})") + ui.append(c2) + + # Text and position + c3 = mcrfpy.Caption("Positioned Text", 100, 50) + print_test(f"Caption('Positioned Text', 100, 50)") + ui.append(c3) + + # Full constructor + c5 = mcrfpy.Caption("Styled Text", 300, 150, + fill_color=mcrfpy.Color(255, 255, 0), + outline_color=mcrfpy.Color(255, 0, 0), + outline=2) + print_test("Caption with all style parameters") + ui.append(c5) + + # Properties + print("\n Properties:") + + c = mcrfpy.Caption("Test Caption", 10, 20) + + # Text + c.text = "Modified Text" + print_test(f"text = '{c.text}'") + + # Position + c.x = 50 + c.y = 60 + print_test(f"position = ({c.x}, {c.y})") + + # Colors and style + c.fill_color = mcrfpy.Color(0, 255, 255) + c.outline_color = mcrfpy.Color(255, 0, 255) + c.outline = 3.0 + print_test("Colors and outline set") + + # Size (read-only, computed from text) + print_test(f"size (computed) = ({c.w}, {c.h})") + + return True + +def test_animation_api(): + """Test Animation class API""" + print_section("ANIMATION API TESTS") + + ui = mcrfpy.sceneUI("api_test") + + print("\n Animation Constructors:") + + # Basic animation + anim1 = mcrfpy.Animation("x", 100.0, 2.0) + print_test("Animation('x', 100.0, 2.0)") + + # With easing + anim2 = mcrfpy.Animation("y", 200.0, 3.0, "easeInOut") + print_test("Animation with easing='easeInOut'") + + # Delta mode + anim3 = mcrfpy.Animation("w", 50.0, 1.5, "linear", delta=True) + print_test("Animation with delta=True") + + # Color animation (as tuple) + anim4 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 2.0) + print_test("Animation with Color tuple target") + + # Vector animation + anim5 = mcrfpy.Animation("position", (10.0, 20.0), 2.5, "easeOutBounce") + print_test("Animation with position tuple") + + # Sprite sequence + anim6 = mcrfpy.Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0) + print_test("Animation with sprite sequence") + + # Properties + print("\n Animation Properties:") + + # Check properties + print_test(f"property = '{anim1.property}'") + print_test(f"duration = {anim1.duration}") + print_test(f"elapsed = {anim1.elapsed}") + print_test(f"is_complete = {anim1.is_complete}") + print_test(f"is_delta = {anim3.is_delta}") + + # Methods + print("\n Animation Methods:") + + # Create test frame + frame = mcrfpy.Frame(50, 50, 100, 100) + frame.fill_color = mcrfpy.Color(100, 100, 100) + ui.append(frame) + + # Start animation + anim1.start(frame) + print_test("start() called on frame") + + # Test some easing functions + print("\n Sample Easing Functions:") + easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInBounce", "easeOutElastic"] + + for easing in easings: + try: + test_anim = mcrfpy.Animation("x", 100.0, 1.0, easing) + print_test(f"Easing '{easing}' ✓") + except: + print_test(f"Easing '{easing}' failed", False) + + return True + +def run_all_tests(): + """Run all API tests""" + print("\n" + "="*60) + print(" McRogueFace Exhaustive API Test Suite (Fixed)") + print(" Testing constructors and methods...") + print("="*60) + + # Run each test category + test_functions = [ + test_color_api, + test_frame_api, + test_caption_api, + test_animation_api + ] + + passed = 0 + failed = 0 + + for test_func in test_functions: + try: + if test_func(): + passed += 1 + else: + failed += 1 + except Exception as e: + print(f"\n ERROR in {test_func.__name__}: {e}") + failed += 1 + + # Summary + print("\n" + "="*60) + print(f" TEST SUMMARY: {passed} passed, {failed} failed") + print("="*60) + + print("\n Visual elements are displayed in the 'api_test' scene.") + print(" The test is complete.") + + # Exit after a short delay to allow output to be seen + def exit_test(runtime): + print("\nExiting API test suite...") + sys.exit(0) + + mcrfpy.setTimer("exit", exit_test, 2000) + +# Run the tests immediately +print("Starting McRogueFace Exhaustive API Demo (Fixed)...") +print("This will test constructors and methods.") + +run_all_tests() \ No newline at end of file diff --git a/tests/sizzle_reel_final.py b/tests/sizzle_reel_final.py new file mode 100644 index 0000000..8251498 --- /dev/null +++ b/tests/sizzle_reel_final.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +""" +McRogueFace Animation Sizzle Reel - Final Version +================================================= + +Complete demonstration of all animation capabilities. +This version works properly with the game loop and avoids API issues. +""" + +import mcrfpy + +# Configuration +DEMO_DURATION = 4.0 # Duration for each demo + +# All available easing functions +EASING_FUNCTIONS = [ + "linear", "easeIn", "easeOut", "easeInOut", + "easeInQuad", "easeOutQuad", "easeInOutQuad", + "easeInCubic", "easeOutCubic", "easeInOutCubic", + "easeInQuart", "easeOutQuart", "easeInOutQuart", + "easeInSine", "easeOutSine", "easeInOutSine", + "easeInExpo", "easeOutExpo", "easeInOutExpo", + "easeInCirc", "easeOutCirc", "easeInOutCirc", + "easeInElastic", "easeOutElastic", "easeInOutElastic", + "easeInBack", "easeOutBack", "easeInOutBack", + "easeInBounce", "easeOutBounce", "easeInOutBounce" +] + +# Track demo state +current_demo = 0 +subtitle = None + +def create_scene(): + """Create the demo scene""" + mcrfpy.createScene("demo") + mcrfpy.setScene("demo") + + ui = mcrfpy.sceneUI("demo") + + # Title + title = mcrfpy.Caption("Animation Sizzle Reel", 500, 20) + title.fill_color = mcrfpy.Color(255, 255, 0) + title.outline = 2 + ui.append(title) + + # Subtitle + global subtitle + subtitle = mcrfpy.Caption("Starting...", 450, 60) + subtitle.fill_color = mcrfpy.Color(200, 200, 200) + ui.append(subtitle) + + return ui + +def demo1_frame_animations(): + """Frame position, size, and color animations""" + ui = mcrfpy.sceneUI("demo") + subtitle.text = "Demo 1: Frame Animations" + + # Create frame + f = mcrfpy.Frame(100, 150, 200, 100) + f.fill_color = mcrfpy.Color(50, 50, 150) + f.outline = 3 + f.outline_color = mcrfpy.Color(255, 255, 255) + ui.append(f) + + # Animate properties + mcrfpy.Animation("x", 600.0, 2.0, "easeInOutBack").start(f) + mcrfpy.Animation("y", 300.0, 2.0, "easeInOutElastic").start(f) + mcrfpy.Animation("w", 300.0, 2.5, "easeInOutCubic").start(f) + mcrfpy.Animation("h", 150.0, 2.5, "easeInOutCubic").start(f) + mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "easeInOutSine").start(f) + mcrfpy.Animation("outline", 8.0, 3.0, "easeInOutQuad").start(f) + +def demo2_caption_animations(): + """Caption movement and text effects""" + ui = mcrfpy.sceneUI("demo") + subtitle.text = "Demo 2: Caption Animations" + + # Moving caption + c1 = mcrfpy.Caption("Bouncing Text!", 100, 200) + c1.fill_color = mcrfpy.Color(255, 255, 255) + ui.append(c1) + mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1) + + # Color cycling + c2 = mcrfpy.Caption("Color Cycle", 400, 300) + c2.outline = 2 + ui.append(c2) + mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear").start(c2) + + # Typewriter effect + c3 = mcrfpy.Caption("", 100, 400) + c3.fill_color = mcrfpy.Color(0, 255, 255) + ui.append(c3) + mcrfpy.Animation("text", "Typewriter effect animation...", 3.0, "linear").start(c3) + +def demo3_easing_showcase(): + """Show all 30 easing functions""" + ui = mcrfpy.sceneUI("demo") + subtitle.text = "Demo 3: All 30 Easing Functions" + + # Create a small frame for each easing + for i, easing in enumerate(EASING_FUNCTIONS[:15]): # First 15 + row = i // 5 + col = i % 5 + x = 100 + col * 200 + y = 150 + row * 100 + + # Frame + f = mcrfpy.Frame(x, y, 20, 20) + f.fill_color = mcrfpy.Color(100, 150, 255) + ui.append(f) + + # Label + label = mcrfpy.Caption(easing[:10], x, y - 20) + label.fill_color = mcrfpy.Color(200, 200, 200) + ui.append(label) + + # Animate with this easing + mcrfpy.Animation("x", float(x + 150), 3.0, easing).start(f) + +def demo4_performance(): + """Many simultaneous animations""" + ui = mcrfpy.sceneUI("demo") + subtitle.text = "Demo 4: 50+ Simultaneous Animations" + + for i in range(50): + x = 100 + (i % 10) * 100 + y = 150 + (i // 10) * 100 + + f = mcrfpy.Frame(x, y, 30, 30) + f.fill_color = mcrfpy.Color((i*37)%256, (i*73)%256, (i*113)%256) + ui.append(f) + + # Animate to random position + target_x = 150 + (i % 8) * 110 + target_y = 200 + (i // 8) * 90 + easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)] + + mcrfpy.Animation("x", float(target_x), 2.5, easing).start(f) + mcrfpy.Animation("y", float(target_y), 2.5, easing).start(f) + mcrfpy.Animation("opacity", 0.3 + (i%7)*0.1, 2.0, "easeInOutSine").start(f) + +def clear_demo_objects(): + """Clear scene except title and subtitle""" + ui = mcrfpy.sceneUI("demo") + # Keep removing items after the first 2 (title and subtitle) + while len(ui) > 2: + # Remove the last item + ui.remove(ui[len(ui)-1]) + +def next_demo(runtime): + """Run the next demo""" + global current_demo + + clear_demo_objects() + + demos = [ + demo1_frame_animations, + demo2_caption_animations, + demo3_easing_showcase, + demo4_performance + ] + + if current_demo < len(demos): + demos[current_demo]() + current_demo += 1 + + if current_demo < len(demos): + mcrfpy.setTimer("next", next_demo, int(DEMO_DURATION * 1000)) + else: + subtitle.text = "Demo Complete!" + +# Initialize +print("Starting Animation Sizzle Reel...") +create_scene() +mcrfpy.setTimer("start", next_demo, 500) \ No newline at end of file