diff --git a/tests/dijkstra_interactive_enhanced.py b/tests/dijkstra_interactive_enhanced.py new file mode 100644 index 0000000..34da805 --- /dev/null +++ b/tests/dijkstra_interactive_enhanced.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +""" +Enhanced Dijkstra Pathfinding Interactive Demo +============================================== + +Interactive visualization with entity pathfinding animations. + +Controls: +- Press 1/2/3 to select the first entity +- Press A/B/C to select the second entity +- Space to clear selection +- M to make selected entity move along path +- P to pause/resume animation +- R to reset entity positions +- Q or ESC to quit +""" + +import mcrfpy +import sys +import math + +# Colors +WALL_COLOR = mcrfpy.Color(60, 30, 30) +FLOOR_COLOR = mcrfpy.Color(200, 200, 220) +PATH_COLOR = mcrfpy.Color(200, 250, 220) +VISITED_COLOR = mcrfpy.Color(180, 230, 200) +ENTITY_COLORS = [ + mcrfpy.Color(255, 100, 100), # Entity 1 - Red + mcrfpy.Color(100, 255, 100), # Entity 2 - Green + mcrfpy.Color(100, 100, 255), # Entity 3 - Blue +] + +# Global state +grid = None +entities = [] +first_point = None +second_point = None +current_path = [] +animating = False +animation_progress = 0.0 +animation_speed = 2.0 # cells per second +original_positions = [] # Store original entity positions + +def create_map(): + """Create the interactive map with the layout specified by the user""" + global grid, entities, original_positions + + mcrfpy.createScene("dijkstra_enhanced") + + # Create grid - 14x10 as specified + grid = mcrfpy.Grid(grid_x=14, grid_y=10) + grid.fill_color = mcrfpy.Color(0, 0, 0) + + # Define the map layout from user's specification + # . = floor, W = wall, E = entity position + map_layout = [ + "..............", # Row 0 + "..W.....WWWW..", # Row 1 + "..W.W...W.EW..", # Row 2 + "..W.....W..W..", # Row 3 + "..W...E.WWWW..", # Row 4 + "E.W...........", # Row 5 + "..W...........", # Row 6 + "..W...........", # Row 7 + "..W.WWW.......", # Row 8 + "..............", # Row 9 + ] + + # Create the map + entity_positions = [] + for y, row in enumerate(map_layout): + for x, char in enumerate(row): + cell = grid.at(x, y) + + if char == 'W': + # Wall + cell.walkable = False + cell.transparent = False + cell.color = WALL_COLOR + else: + # Floor + cell.walkable = True + cell.transparent = True + cell.color = FLOOR_COLOR + + if char == 'E': + # Entity position + entity_positions.append((x, y)) + + # Create entities at marked positions + entities = [] + original_positions = [] + for i, (x, y) in enumerate(entity_positions): + entity = mcrfpy.Entity(x, y) + entity.sprite_index = 49 + i # '1', '2', '3' + grid.entities.append(entity) + entities.append(entity) + original_positions.append((x, y)) + + return grid + +def clear_path_highlight(): + """Clear any existing path highlighting""" + global current_path + + # Reset all floor tiles to original color + for y in range(grid.grid_y): + for x in range(grid.grid_x): + cell = grid.at(x, y) + if cell.walkable: + cell.color = FLOOR_COLOR + + current_path = [] + +def highlight_path(): + """Highlight the path between selected entities using entity.path_to()""" + global current_path + + if first_point is None or second_point is None: + return + + # Clear previous highlighting + clear_path_highlight() + + # Get entities + entity1 = entities[first_point] + entity2 = entities[second_point] + + # Use the new path_to method! + path = entity1.path_to(int(entity2.x), int(entity2.y)) + + if path: + current_path = path + + # Highlight the path + for i, (x, y) in enumerate(path): + cell = grid.at(x, y) + if cell.walkable: + # Use gradient for path visualization + if i < len(path) - 1: + cell.color = PATH_COLOR + else: + cell.color = VISITED_COLOR + + # Highlight start and end with entity colors + grid.at(int(entity1.x), int(entity1.y)).color = ENTITY_COLORS[first_point] + grid.at(int(entity2.x), int(entity2.y)).color = ENTITY_COLORS[second_point] + + # Update info + info_text.text = f"Path: Entity {first_point+1} to Entity {second_point+1} - {len(path)} steps" + else: + info_text.text = f"No path between Entity {first_point+1} and Entity {second_point+1}" + current_path = [] + +def animate_movement(dt): + """Animate entity movement along path""" + global animation_progress, animating, current_path + + if not animating or not current_path or first_point is None: + return + + entity = entities[first_point] + + # Update animation progress + animation_progress += animation_speed * dt + + # Calculate current position along path + path_index = int(animation_progress) + + if path_index >= len(current_path): + # Animation complete + animating = False + animation_progress = 0.0 + # Snap to final position + if current_path: + final_x, final_y = current_path[-1] + entity.x = float(final_x) + entity.y = float(final_y) + return + + # Interpolate between path points + if path_index < len(current_path) - 1: + curr_x, curr_y = current_path[path_index] + next_x, next_y = current_path[path_index + 1] + + # Calculate interpolation factor + t = animation_progress - path_index + + # Smooth interpolation + entity.x = curr_x + (next_x - curr_x) * t + entity.y = curr_y + (next_y - curr_y) * t + else: + # At last point + entity.x, entity.y = current_path[path_index] + +def handle_keypress(scene_name, keycode): + """Handle keyboard input""" + global first_point, second_point, animating, animation_progress + + # Number keys for first entity + if keycode == 49: # '1' + first_point = 0 + status_text.text = f"First: Entity 1 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}" + highlight_path() + elif keycode == 50: # '2' + first_point = 1 + status_text.text = f"First: Entity 2 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}" + highlight_path() + elif keycode == 51: # '3' + first_point = 2 + status_text.text = f"First: Entity 3 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}" + highlight_path() + + # Letter keys for second entity + elif keycode == 65 or keycode == 97: # 'A' or 'a' + second_point = 0 + status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 1" + highlight_path() + elif keycode == 66 or keycode == 98: # 'B' or 'b' + second_point = 1 + status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 2" + highlight_path() + elif keycode == 67 or keycode == 99: # 'C' or 'c' + second_point = 2 + status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 3" + highlight_path() + + # Movement control + elif keycode == 77 or keycode == 109: # 'M' or 'm' + if current_path and first_point is not None: + animating = True + animation_progress = 0.0 + control_text.text = "Animation: MOVING (press P to pause)" + + # Pause/Resume + elif keycode == 80 or keycode == 112: # 'P' or 'p' + animating = not animating + control_text.text = f"Animation: {'MOVING' if animating else 'PAUSED'} (press P to {'pause' if animating else 'resume'})" + + # Reset positions + elif keycode == 82 or keycode == 114: # 'R' or 'r' + animating = False + animation_progress = 0.0 + for i, entity in enumerate(entities): + entity.x, entity.y = original_positions[i] + control_text.text = "Entities reset to original positions" + highlight_path() # Re-highlight path after reset + + # Clear selection + elif keycode == 32: # Space + first_point = None + second_point = None + animating = False + animation_progress = 0.0 + clear_path_highlight() + status_text.text = "Press 1/2/3 for first entity, A/B/C for second" + info_text.text = "Space to clear, Q to quit" + control_text.text = "Press M to move, P to pause, R to reset" + + # Quit + elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC + print("\nExiting enhanced Dijkstra demo...") + sys.exit(0) + +# Timer callback for animation +def update_animation(dt): + """Update animation state""" + animate_movement(dt / 1000.0) # Convert ms to seconds + +# Create the visualization +print("Enhanced Dijkstra Pathfinding Demo") +print("==================================") +print("Controls:") +print(" 1/2/3 - Select first entity") +print(" A/B/C - Select second entity") +print(" M - Move first entity along path") +print(" P - Pause/Resume animation") +print(" R - Reset entity positions") +print(" Space - Clear selection") +print(" Q/ESC - Quit") + +# Create map +grid = create_map() + +# Set up UI +ui = mcrfpy.sceneUI("dijkstra_enhanced") +ui.append(grid) + +# Scale and position grid for better visibility +grid.size = (560, 400) # 14*40, 10*40 +grid.position = (120, 60) + +# Add title +title = mcrfpy.Caption("Enhanced Dijkstra Pathfinding", 250, 10) +title.fill_color = mcrfpy.Color(255, 255, 255) +ui.append(title) + +# Add status text +status_text = mcrfpy.Caption("Press 1/2/3 for first entity, A/B/C for second", 120, 480) +status_text.fill_color = mcrfpy.Color(255, 255, 255) +ui.append(status_text) + +# Add info text +info_text = mcrfpy.Caption("Space to clear, Q to quit", 120, 500) +info_text.fill_color = mcrfpy.Color(200, 200, 200) +ui.append(info_text) + +# Add control text +control_text = mcrfpy.Caption("Press M to move, P to pause, R to reset", 120, 520) +control_text.fill_color = mcrfpy.Color(150, 200, 150) +ui.append(control_text) + +# Add legend +legend1 = mcrfpy.Caption("Entities: 1=Red 2=Green 3=Blue", 120, 560) +legend1.fill_color = mcrfpy.Color(150, 150, 150) +ui.append(legend1) + +legend2 = mcrfpy.Caption("Colors: Dark=Wall Light=Floor Cyan=Path", 120, 580) +legend2.fill_color = mcrfpy.Color(150, 150, 150) +ui.append(legend2) + +# Mark entity positions with colored indicators +for i, entity in enumerate(entities): + marker = mcrfpy.Caption(str(i+1), + 120 + int(entity.x) * 40 + 15, + 60 + int(entity.y) * 40 + 10) + marker.fill_color = ENTITY_COLORS[i] + marker.outline = 1 + marker.outline_color = mcrfpy.Color(0, 0, 0) + ui.append(marker) + +# Set up input handling +mcrfpy.keypressScene(handle_keypress) + +# Set up animation timer (60 FPS) +mcrfpy.setTimer("animation", update_animation, 16) + +# Show the scene +mcrfpy.setScene("dijkstra_enhanced") + +print("\nVisualization ready!") +print("Entities are at:") +for i, entity in enumerate(entities): + print(f" Entity {i+1}: ({int(entity.x)}, {int(entity.y)})") \ No newline at end of file diff --git a/tests/pathfinding_showcase.py b/tests/pathfinding_showcase.py new file mode 100644 index 0000000..d4e082f --- /dev/null +++ b/tests/pathfinding_showcase.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +""" +Pathfinding Showcase Demo +========================= + +Demonstrates various pathfinding scenarios with multiple entities. + +Features: +- Multiple entities pathfinding simultaneously +- Chase mode: entities pursue targets +- Flee mode: entities avoid threats +- Patrol mode: entities follow waypoints +- Visual debugging: show Dijkstra distance field +""" + +import mcrfpy +import sys +import random + +# Colors +WALL_COLOR = mcrfpy.Color(40, 40, 40) +FLOOR_COLOR = mcrfpy.Color(220, 220, 240) +PATH_COLOR = mcrfpy.Color(180, 250, 180) +THREAT_COLOR = mcrfpy.Color(255, 100, 100) +GOAL_COLOR = mcrfpy.Color(100, 255, 100) +DIJKSTRA_COLORS = [ + mcrfpy.Color(50, 50, 100), # Far + mcrfpy.Color(70, 70, 150), + mcrfpy.Color(90, 90, 200), + mcrfpy.Color(110, 110, 250), + mcrfpy.Color(150, 150, 255), + mcrfpy.Color(200, 200, 255), # Near +] + +# Entity types +PLAYER = 64 # @ +ENEMY = 69 # E +TREASURE = 36 # $ +PATROL = 80 # P + +# Global state +grid = None +player = None +enemies = [] +treasures = [] +patrol_entities = [] +mode = "CHASE" +show_dijkstra = False +animation_speed = 3.0 + +def create_dungeon(): + """Create a dungeon-like map""" + global grid + + mcrfpy.createScene("pathfinding_showcase") + + # Create larger grid for showcase + grid = mcrfpy.Grid(grid_x=30, grid_y=20) + grid.fill_color = mcrfpy.Color(0, 0, 0) + + # Initialize all as floor + for y in range(20): + for x in range(30): + grid.at(x, y).walkable = True + grid.at(x, y).transparent = True + grid.at(x, y).color = FLOOR_COLOR + + # Create rooms and corridors + rooms = [ + (2, 2, 8, 6), # Top-left room + (20, 2, 8, 6), # Top-right room + (11, 8, 8, 6), # Center room + (2, 14, 8, 5), # Bottom-left room + (20, 14, 8, 5), # Bottom-right room + ] + + # Create room walls + for rx, ry, rw, rh in rooms: + # Top and bottom walls + for x in range(rx, rx + rw): + if 0 <= x < 30: + grid.at(x, ry).walkable = False + grid.at(x, ry).color = WALL_COLOR + grid.at(x, ry + rh - 1).walkable = False + grid.at(x, ry + rh - 1).color = WALL_COLOR + + # Left and right walls + for y in range(ry, ry + rh): + if 0 <= y < 20: + grid.at(rx, y).walkable = False + grid.at(rx, y).color = WALL_COLOR + grid.at(rx + rw - 1, y).walkable = False + grid.at(rx + rw - 1, y).color = WALL_COLOR + + # Create doorways + doorways = [ + (6, 2), (24, 2), # Top room doors + (6, 7), (24, 7), # Top room doors bottom + (15, 8), (15, 13), # Center room doors + (6, 14), (24, 14), # Bottom room doors + (11, 11), (18, 11), # Center room side doors + ] + + for x, y in doorways: + if 0 <= x < 30 and 0 <= y < 20: + grid.at(x, y).walkable = True + grid.at(x, y).color = FLOOR_COLOR + + # Add some corridors + # Horizontal corridors + for x in range(10, 20): + grid.at(x, 5).walkable = True + grid.at(x, 5).color = FLOOR_COLOR + grid.at(x, 16).walkable = True + grid.at(x, 16).color = FLOOR_COLOR + + # Vertical corridors + for y in range(5, 17): + grid.at(10, y).walkable = True + grid.at(10, y).color = FLOOR_COLOR + grid.at(19, y).walkable = True + grid.at(19, y).color = FLOOR_COLOR + +def spawn_entities(): + """Spawn various entity types""" + global player, enemies, treasures, patrol_entities + + # Clear existing entities + grid.entities.clear() + enemies = [] + treasures = [] + patrol_entities = [] + + # Spawn player in center room + player = mcrfpy.Entity(15, 11) + player.sprite_index = PLAYER + grid.entities.append(player) + + # Spawn enemies in corners + enemy_positions = [(4, 4), (24, 4), (4, 16), (24, 16)] + for x, y in enemy_positions: + enemy = mcrfpy.Entity(x, y) + enemy.sprite_index = ENEMY + grid.entities.append(enemy) + enemies.append(enemy) + + # Spawn treasures + treasure_positions = [(6, 5), (24, 5), (15, 10)] + for x, y in treasure_positions: + treasure = mcrfpy.Entity(x, y) + treasure.sprite_index = TREASURE + grid.entities.append(treasure) + treasures.append(treasure) + + # Spawn patrol entities + patrol = mcrfpy.Entity(10, 10) + patrol.sprite_index = PATROL + patrol.waypoints = [(10, 10), (19, 10), (19, 16), (10, 16)] # Square patrol + patrol.waypoint_index = 0 + grid.entities.append(patrol) + patrol_entities.append(patrol) + +def visualize_dijkstra(target_x, target_y): + """Visualize Dijkstra distance field""" + if not show_dijkstra: + return + + # Compute Dijkstra from target + grid.compute_dijkstra(target_x, target_y) + + # Color tiles based on distance + max_dist = 30.0 + for y in range(20): + for x in range(30): + if grid.at(x, y).walkable: + dist = grid.get_dijkstra_distance(x, y) + if dist is not None and dist < max_dist: + # Map distance to color index + color_idx = int((dist / max_dist) * len(DIJKSTRA_COLORS)) + color_idx = min(color_idx, len(DIJKSTRA_COLORS) - 1) + grid.at(x, y).color = DIJKSTRA_COLORS[color_idx] + +def move_enemies(dt): + """Move enemies based on current mode""" + if mode == "CHASE": + # Enemies chase player + for enemy in enemies: + path = enemy.path_to(int(player.x), int(player.y)) + if path and len(path) > 1: # Don't move onto player + # Move towards player + next_x, next_y = path[1] + # Smooth movement + dx = next_x - enemy.x + dy = next_y - enemy.y + enemy.x += dx * dt * animation_speed + enemy.y += dy * dt * animation_speed + + elif mode == "FLEE": + # Enemies flee from player + for enemy in enemies: + # Compute opposite direction + dx = enemy.x - player.x + dy = enemy.y - player.y + + # Find safe spot in that direction + target_x = int(enemy.x + dx * 2) + target_y = int(enemy.y + dy * 2) + + # Clamp to grid + target_x = max(0, min(29, target_x)) + target_y = max(0, min(19, target_y)) + + path = enemy.path_to(target_x, target_y) + if path and len(path) > 0: + next_x, next_y = path[0] + # Move away from player + dx = next_x - enemy.x + dy = next_y - enemy.y + enemy.x += dx * dt * animation_speed + enemy.y += dy * dt * animation_speed + +def move_patrols(dt): + """Move patrol entities along waypoints""" + for patrol in patrol_entities: + if not hasattr(patrol, 'waypoints'): + continue + + # Get current waypoint + target_x, target_y = patrol.waypoints[patrol.waypoint_index] + + # Check if reached waypoint + dist = abs(patrol.x - target_x) + abs(patrol.y - target_y) + if dist < 0.5: + # Move to next waypoint + patrol.waypoint_index = (patrol.waypoint_index + 1) % len(patrol.waypoints) + target_x, target_y = patrol.waypoints[patrol.waypoint_index] + + # Path to waypoint + path = patrol.path_to(target_x, target_y) + if path and len(path) > 0: + next_x, next_y = path[0] + dx = next_x - patrol.x + dy = next_y - patrol.y + patrol.x += dx * dt * animation_speed * 0.5 # Slower patrol speed + patrol.y += dy * dt * animation_speed * 0.5 + +def update_entities(dt): + """Update all entity movements""" + move_enemies(dt / 1000.0) # Convert to seconds + move_patrols(dt / 1000.0) + + # Update Dijkstra visualization + if show_dijkstra and player: + visualize_dijkstra(int(player.x), int(player.y)) + +def handle_keypress(scene_name, keycode): + """Handle keyboard input""" + global mode, show_dijkstra, player + + # Mode switching + if keycode == 49: # '1' + mode = "CHASE" + mode_text.text = "Mode: CHASE - Enemies pursue player" + clear_colors() + elif keycode == 50: # '2' + mode = "FLEE" + mode_text.text = "Mode: FLEE - Enemies avoid player" + clear_colors() + elif keycode == 51: # '3' + mode = "PATROL" + mode_text.text = "Mode: PATROL - Entities follow waypoints" + clear_colors() + + # Toggle Dijkstra visualization + elif keycode == 68 or keycode == 100: # 'D' or 'd' + show_dijkstra = not show_dijkstra + debug_text.text = f"Dijkstra Debug: {'ON' if show_dijkstra else 'OFF'}" + if not show_dijkstra: + clear_colors() + + # Move player with arrow keys or WASD + elif keycode in [87, 119]: # W/w - Up + if player.y > 0: + path = player.path_to(int(player.x), int(player.y) - 1) + if path: + player.y -= 1 + elif keycode in [83, 115]: # S/s - Down + if player.y < 19: + path = player.path_to(int(player.x), int(player.y) + 1) + if path: + player.y += 1 + elif keycode in [65, 97]: # A/a - Left + if player.x > 0: + path = player.path_to(int(player.x) - 1, int(player.y)) + if path: + player.x -= 1 + elif keycode in [68, 100]: # D/d - Right + if player.x < 29: + path = player.path_to(int(player.x) + 1, int(player.y)) + if path: + player.x += 1 + + # Reset + elif keycode == 82 or keycode == 114: # 'R' or 'r' + spawn_entities() + clear_colors() + + # Quit + elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC + print("\nExiting pathfinding showcase...") + sys.exit(0) + +def clear_colors(): + """Reset floor colors""" + for y in range(20): + for x in range(30): + if grid.at(x, y).walkable: + grid.at(x, y).color = FLOOR_COLOR + +# Create the showcase +print("Pathfinding Showcase Demo") +print("=========================") +print("Controls:") +print(" WASD - Move player") +print(" 1 - Chase mode (enemies pursue)") +print(" 2 - Flee mode (enemies avoid)") +print(" 3 - Patrol mode") +print(" D - Toggle Dijkstra visualization") +print(" R - Reset entities") +print(" Q/ESC - Quit") + +# Create dungeon +create_dungeon() +spawn_entities() + +# Set up UI +ui = mcrfpy.sceneUI("pathfinding_showcase") +ui.append(grid) + +# Scale and position +grid.size = (750, 500) # 30*25, 20*25 +grid.position = (25, 60) + +# Add title +title = mcrfpy.Caption("Pathfinding Showcase", 300, 10) +title.fill_color = mcrfpy.Color(255, 255, 255) +ui.append(title) + +# Add mode text +mode_text = mcrfpy.Caption("Mode: CHASE - Enemies pursue player", 25, 580) +mode_text.fill_color = mcrfpy.Color(255, 255, 200) +ui.append(mode_text) + +# Add debug text +debug_text = mcrfpy.Caption("Dijkstra Debug: OFF", 25, 600) +debug_text.fill_color = mcrfpy.Color(200, 200, 255) +ui.append(debug_text) + +# Add legend +legend = mcrfpy.Caption("@ Player E Enemy $ Treasure P Patrol", 25, 620) +legend.fill_color = mcrfpy.Color(150, 150, 150) +ui.append(legend) + +# Set up input handling +mcrfpy.keypressScene(handle_keypress) + +# Set up animation timer +mcrfpy.setTimer("entities", update_entities, 16) # 60 FPS + +# Show scene +mcrfpy.setScene("pathfinding_showcase") + +print("\nShowcase ready! Move with WASD and watch entities react.") \ No newline at end of file diff --git a/tests/test_pathfinding_integration.py b/tests/test_pathfinding_integration.py new file mode 100644 index 0000000..8f779f6 --- /dev/null +++ b/tests/test_pathfinding_integration.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Test pathfinding integration with demos""" + +import mcrfpy +import sys + +print("Testing pathfinding integration...") +print("=" * 50) + +# Create scene and grid +mcrfpy.createScene("test") +grid = mcrfpy.Grid(grid_x=10, grid_y=10) + +# Initialize grid +for y in range(10): + for x in range(10): + grid.at(x, y).walkable = True + +# Add some walls +for i in range(5): + grid.at(5, i + 2).walkable = False + +# Create entities +e1 = mcrfpy.Entity(2, 5) +e2 = mcrfpy.Entity(8, 5) +grid.entities.append(e1) +grid.entities.append(e2) + +# Test pathfinding between entities +print(f"Entity 1 at ({e1.x}, {e1.y})") +print(f"Entity 2 at ({e2.x}, {e2.y})") + +# Entity 1 finds path to Entity 2 +path = e1.path_to(int(e2.x), int(e2.y)) +print(f"\nPath from E1 to E2: {path}") +print(f"Path length: {len(path)} steps") + +# Test movement simulation +if path and len(path) > 1: + print("\nSimulating movement along path:") + for i, (x, y) in enumerate(path[:5]): # Show first 5 steps + print(f" Step {i}: Move to ({x}, {y})") + +# Test path in reverse +path_reverse = e2.path_to(int(e1.x), int(e1.y)) +print(f"\nPath from E2 to E1: {path_reverse}") +print(f"Reverse path length: {len(path_reverse)} steps") + +print("\n✓ Pathfinding integration working correctly!") +print("Enhanced demos are ready for interactive use.") + +# Quick animation test +def test_timer(dt): + print(f"Timer callback received: dt={dt}ms") + sys.exit(0) + +# Set a quick timer to test animation system +mcrfpy.setTimer("test", test_timer, 100) + +print("\nTesting timer system for animations...") \ No newline at end of file