diff --git a/docs/API_REFERENCE_COMPLETE.md b/docs/API_REFERENCE_COMPLETE.md new file mode 100644 index 0000000..e50d008 --- /dev/null +++ b/docs/API_REFERENCE_COMPLETE.md @@ -0,0 +1,1156 @@ +# McRogueFace API Reference + +## Overview + +McRogueFace Python API + +Core game engine interface for creating roguelike games with Python. + +This module provides: +- Scene management (createScene, setScene, currentScene) +- UI components (Frame, Caption, Sprite, Grid) +- Entity system for game objects +- Audio playback (sound effects and music) +- Timer system for scheduled events +- Input handling +- Performance metrics + +Example: + import mcrfpy + + # Create a new scene + mcrfpy.createScene('game') + mcrfpy.setScene('game') + + # Add UI elements + frame = mcrfpy.Frame(10, 10, 200, 100) + caption = mcrfpy.Caption('Hello World', 50, 50) + mcrfpy.sceneUI().extend([frame, caption]) + + +## Table of Contents + +- [Functions](#functions) + - [Scene Management](#scene-management) + - [Audio](#audio) + - [UI Utilities](#ui-utilities) + - [System](#system) +- [Classes](#classes) + - [UI Components](#ui-components) + - [Collections](#collections) + - [System Types](#system-types) + - [Other Classes](#other-classes) +- [Automation Module](#automation-module) + +## Functions + +### Scene Management + +### `createScene(name: str) -> None` + +Create a new empty scene with the given name. + +**Arguments:** +- `name` (*str*): Unique name for the new scene + +**Raises:** ValueError: If a scene with this name already exists + +**Note:** The scene is created but not made active. Use setScene() to switch to it. + +**Example:** +```python +mcrfpy.createScene("game_over") +``` + +--- + +### `setScene(scene: str, transition: str = None, duration: float = 0.0) -> None` + +Switch to a different scene with optional transition effect. + +**Arguments:** +- `scene` (*str*): Name of the scene to switch to +- `transition` (*str*): Transition type: "fade", "slide_left", "slide_right", "slide_up", "slide_down" +- `duration` (*float*): Transition duration in seconds (default: 0.0 for instant) + +**Raises:** KeyError: If the scene doesn't exist + +**Example:** +```python +mcrfpy.setScene("game", "fade", 0.5) +``` + +--- + +### `currentScene() -> str` + +Get the name of the currently active scene. + +**Returns:** str: Name of the current scene + +**Example:** +```python +scene_name = mcrfpy.currentScene() +``` + +--- + +### `sceneUI(scene: str = None) -> UICollection` + +Get all UI elements for a scene. + +**Arguments:** +- `scene` (*str*): Scene name. If None, uses current scene + +**Returns:** UICollection: All UI elements in the scene + +**Raises:** KeyError: If the specified scene doesn't exist + +**Example:** +```python +ui_elements = mcrfpy.sceneUI("game") +``` + +--- + +### `keypressScene(handler: callable) -> None` + +Set the keyboard event handler for the current scene. + +**Arguments:** +- `handler` (*callable*): Function that receives (key_name: str, is_pressed: bool) + +**Example:** +```python +def on_key(key, pressed): + if key == "SPACE" and pressed: + player.jump() +mcrfpy.keypressScene(on_key) +``` + +--- + +### Audio + +### `createSoundBuffer(filename: str) -> int` + +Load a sound effect from a file and return its buffer ID. + +**Arguments:** +- `filename` (*str*): Path to the sound file (WAV, OGG, FLAC) + +**Returns:** int: Buffer ID for use with playSound() + +**Raises:** RuntimeError: If the file cannot be loaded + +**Example:** +```python +jump_sound = mcrfpy.createSoundBuffer("assets/jump.wav") +``` + +--- + +### `loadMusic(filename: str, loop: bool = True) -> None` + +Load and immediately play background music from a file. + +**Arguments:** +- `filename` (*str*): Path to the music file (WAV, OGG, FLAC) +- `loop` (*bool*): Whether to loop the music (default: True) + +**Note:** Only one music track can play at a time. Loading new music stops the current track. + +**Example:** +```python +mcrfpy.loadMusic("assets/background.ogg", True) +``` + +--- + +### `playSound(buffer_id: int) -> None` + +Play a sound effect using a previously loaded buffer. + +**Arguments:** +- `buffer_id` (*int*): Sound buffer ID returned by createSoundBuffer() + +**Raises:** RuntimeError: If the buffer ID is invalid + +**Example:** +```python +mcrfpy.playSound(jump_sound) +``` + +--- + +### `getMusicVolume() -> int` + +Get the current music volume level. + +**Returns:** int: Current volume (0-100) + +**Example:** +```python +current_volume = mcrfpy.getMusicVolume() +``` + +--- + +### `getSoundVolume() -> int` + +Get the current sound effects volume level. + +**Returns:** int: Current volume (0-100) + +**Example:** +```python +current_volume = mcrfpy.getSoundVolume() +``` + +--- + +### `setMusicVolume(volume: int) -> None` + +Set the global music volume. + +**Arguments:** +- `volume` (*int*): Volume level from 0 (silent) to 100 (full volume) + +**Example:** +```python +mcrfpy.setMusicVolume(50) # Set to 50% volume +``` + +--- + +### `setSoundVolume(volume: int) -> None` + +Set the global sound effects volume. + +**Arguments:** +- `volume` (*int*): Volume level from 0 (silent) to 100 (full volume) + +**Example:** +```python +mcrfpy.setSoundVolume(75) # Set to 75% volume +``` + +--- + +### UI Utilities + +### `find(name: str, scene: str = None) -> UIDrawable | None` + +Find the first UI element with the specified name. + +**Arguments:** +- `name` (*str*): Exact name to search for +- `scene` (*str*): Scene to search in (default: current scene) + +**Returns:** UIDrawable or None: The found element, or None if not found + +**Note:** Searches scene UI elements and entities within grids. + +**Example:** +```python +button = mcrfpy.find("start_button") +``` + +--- + +### `findAll(pattern: str, scene: str = None) -> list` + +Find all UI elements matching a name pattern. + +**Arguments:** +- `pattern` (*str*): Name pattern with optional wildcards (* matches any characters) +- `scene` (*str*): Scene to search in (default: current scene) + +**Returns:** list: All matching UI elements and entities + +**Example:** +```python +enemies = mcrfpy.findAll("enemy_*") +``` + +--- + +### System + +### `exit() -> None` + +Cleanly shut down the game engine and exit the application. + +**Note:** This immediately closes the window and terminates the program. + +**Example:** +```python +mcrfpy.exit() +``` + +--- + +### `getMetrics() -> dict` + +Get current performance metrics. + +**Returns:** dict: Performance data with keys: +- frame_time: Last frame duration in seconds +- avg_frame_time: Average frame time +- fps: Frames per second +- draw_calls: Number of draw calls +- ui_elements: Total UI element count +- visible_elements: Visible element count +- current_frame: Frame counter +- runtime: Total runtime in seconds + +**Example:** +```python +metrics = mcrfpy.getMetrics() +``` + +--- + +### `setTimer(name: str, handler: callable, interval: int) -> None` + +Create or update a recurring timer. + +**Arguments:** +- `name` (*str*): Unique identifier for the timer +- `handler` (*callable*): Function called with (runtime: float) parameter +- `interval` (*int*): Time between calls in milliseconds + +**Note:** If a timer with this name exists, it will be replaced. + +**Example:** +```python +def update_score(runtime): + score += 1 +mcrfpy.setTimer("score_update", update_score, 1000) +``` + +--- + +### `delTimer(name: str) -> None` + +Stop and remove a timer. + +**Arguments:** +- `name` (*str*): Timer identifier to remove + +**Note:** No error is raised if the timer doesn't exist. + +**Example:** +```python +mcrfpy.delTimer("score_update") +``` + +--- + +### `setScale(multiplier: float) -> None` + +Scale the game window size. + +**Arguments:** +- `multiplier` (*float*): Scale factor (e.g., 2.0 for double size) + +**Note:** The internal resolution remains 1024x768, but the window is scaled. + +**Example:** +```python +mcrfpy.setScale(2.0) # Double the window size +``` + +--- + +## Classes + +### UI Components + +### class `Frame` + +A rectangular frame UI element that can contain other drawable elements. + +#### Methods + +#### `get_bounds()` + +Get the bounding rectangle of this drawable element. + +**Returns:** tuple: (x, y, width, height) representing the element's bounds + +**Note:** The bounds are in screen coordinates and account for current position and size. + +#### `resize(width, height)` + +Resize the element to new dimensions. + +**Arguments:** +- `width` (*float*): New width in pixels +- `height` (*float*): New height in pixels + +**Note:** For Caption and Sprite, this may not change actual size if determined by content. + +#### `move(dx, dy)` + +Move the element by a relative offset. + +**Arguments:** +- `dx` (*float*): Horizontal offset in pixels +- `dy` (*float*): Vertical offset in pixels + +**Note:** This modifies the x and y position properties by the given amounts. + +--- + +### class `Caption` + +A text display UI element with customizable font and styling. + +#### Methods + +#### `get_bounds()` + +Get the bounding rectangle of this drawable element. + +**Returns:** tuple: (x, y, width, height) representing the element's bounds + +**Note:** The bounds are in screen coordinates and account for current position and size. + +#### `resize(width, height)` + +Resize the element to new dimensions. + +**Arguments:** +- `width` (*float*): New width in pixels +- `height` (*float*): New height in pixels + +**Note:** For Caption and Sprite, this may not change actual size if determined by content. + +#### `move(dx, dy)` + +Move the element by a relative offset. + +**Arguments:** +- `dx` (*float*): Horizontal offset in pixels +- `dy` (*float*): Vertical offset in pixels + +**Note:** This modifies the x and y position properties by the given amounts. + +--- + +### class `Sprite` + +A sprite UI element that displays a texture or portion of a texture atlas. + +#### Methods + +#### `get_bounds()` + +Get the bounding rectangle of this drawable element. + +**Returns:** tuple: (x, y, width, height) representing the element's bounds + +**Note:** The bounds are in screen coordinates and account for current position and size. + +#### `resize(width, height)` + +Resize the element to new dimensions. + +**Arguments:** +- `width` (*float*): New width in pixels +- `height` (*float*): New height in pixels + +**Note:** For Caption and Sprite, this may not change actual size if determined by content. + +#### `move(dx, dy)` + +Move the element by a relative offset. + +**Arguments:** +- `dx` (*float*): Horizontal offset in pixels +- `dy` (*float*): Vertical offset in pixels + +**Note:** This modifies the x and y position properties by the given amounts. + +--- + +### class `Grid` + +A grid-based tilemap UI element for rendering tile-based levels and game worlds. + +#### Methods + +#### `at(x, y)` + +Get the GridPoint at the specified grid coordinates. + +**Arguments:** +- `x` (*int*): Grid x coordinate +- `y` (*int*): Grid y coordinate + +**Returns:** GridPoint or None: The grid point at (x, y), or None if out of bounds + +#### `get_bounds()` + +Get the bounding rectangle of this drawable element. + +**Returns:** tuple: (x, y, width, height) representing the element's bounds + +**Note:** The bounds are in screen coordinates and account for current position and size. + +#### `resize(width, height)` + +Resize the element to new dimensions. + +**Arguments:** +- `width` (*float*): New width in pixels +- `height` (*float*): New height in pixels + +**Note:** For Caption and Sprite, this may not change actual size if determined by content. + +#### `move(dx, dy)` + +Move the element by a relative offset. + +**Arguments:** +- `dx` (*float*): Horizontal offset in pixels +- `dy` (*float*): Vertical offset in pixels + +**Note:** This modifies the x and y position properties by the given amounts. + +--- + +### class `Entity` + +Game entity that can be placed in a Grid. + +#### Methods + +#### `die()` + +Remove this entity from its parent grid. + +**Note:** The entity object remains valid but is no longer rendered or updated. + +#### `move(dx, dy)` + +Move the element by a relative offset. + +**Arguments:** +- `dx` (*float*): Horizontal offset in pixels +- `dy` (*float*): Vertical offset in pixels + +**Note:** This modifies the x and y position properties by the given amounts. + +#### `at(x, y)` + +Check if this entity is at the specified grid coordinates. + +**Arguments:** +- `x` (*int*): Grid x coordinate to check +- `y` (*int*): Grid y coordinate to check + +**Returns:** bool: True if entity is at position (x, y), False otherwise + +#### `get_bounds()` + +Get the bounding rectangle of this drawable element. + +**Returns:** tuple: (x, y, width, height) representing the element's bounds + +**Note:** The bounds are in screen coordinates and account for current position and size. + +#### `index()` + +Get the index of this entity in its parent grid's entity list. + +**Returns:** int: Index position, or -1 if not in a grid + +#### `resize(width, height)` + +Resize the element to new dimensions. + +**Arguments:** +- `width` (*float*): New width in pixels +- `height` (*float*): New height in pixels + +**Note:** For Caption and Sprite, this may not change actual size if determined by content. + +--- + +### Collections + +### class `EntityCollection` + +Container for Entity objects in a Grid. Supports iteration and indexing. + +#### Methods + +#### `append(entity)` + +Add an entity to the end of the collection. + +**Arguments:** +- `entity` (*Entity*): The entity to add + +#### `remove(entity)` + +Remove the first occurrence of an entity from the collection. + +**Arguments:** +- `entity` (*Entity*): The entity to remove + +**Raises:** ValueError: If entity is not in collection + +#### `count(entity)` + +Count the number of occurrences of an entity in the collection. + +**Arguments:** +- `entity` (*Entity*): The entity to count + +**Returns:** int: Number of times entity appears in collection + +#### `index(entity)` + +Find the index of the first occurrence of an entity. + +**Arguments:** +- `entity` (*Entity*): The entity to find + +**Returns:** int: Index of entity in collection + +**Raises:** ValueError: If entity is not in collection + +#### `extend(iterable)` + +Add all entities from an iterable to the collection. + +**Arguments:** +- `iterable` (*Iterable[Entity]*): Entities to add + +--- + +### class `UICollection` + +Container for UI drawable elements. Supports iteration and indexing. + +#### Methods + +#### `append(drawable)` + +Add a drawable element to the end of the collection. + +**Arguments:** +- `drawable` (*UIDrawable*): The drawable element to add + +#### `remove(drawable)` + +Remove the first occurrence of a drawable from the collection. + +**Arguments:** +- `drawable` (*UIDrawable*): The drawable to remove + +**Raises:** ValueError: If drawable is not in collection + +#### `count(drawable)` + +Count the number of occurrences of a drawable in the collection. + +**Arguments:** +- `drawable` (*UIDrawable*): The drawable to count + +**Returns:** int: Number of times drawable appears in collection + +#### `index(drawable)` + +Find the index of the first occurrence of a drawable. + +**Arguments:** +- `drawable` (*UIDrawable*): The drawable to find + +**Returns:** int: Index of drawable in collection + +**Raises:** ValueError: If drawable is not in collection + +#### `extend(iterable)` + +Add all drawables from an iterable to the collection. + +**Arguments:** +- `iterable` (*Iterable[UIDrawable]*): Drawables to add + +--- + +### class `UICollectionIter` + +Iterator for UICollection. Automatically created when iterating over a UICollection. + +--- + +### class `UIEntityCollectionIter` + +Iterator for EntityCollection. Automatically created when iterating over an EntityCollection. + +--- + +### System Types + +### class `Color` + +RGBA color representation. + +#### Methods + +#### `from_hex(hex_string)` + +Create a Color from a hexadecimal color string. + +**Arguments:** +- `hex_string` (*str*): Hex color string (e.g., "#FF0000" or "FF0000") + +**Returns:** Color: New Color object from hex string + +**Example:** +```python +red = Color.from_hex("#FF0000") +``` + +#### `to_hex()` + +Convert this Color to a hexadecimal string. + +**Returns:** str: Hex color string in format "#RRGGBB" + +**Example:** +```python +hex_str = color.to_hex() # Returns "#FF0000" +``` + +#### `lerp(other, t)` + +Linearly interpolate between this color and another. + +**Arguments:** +- `other` (*Color*): The color to interpolate towards +- `t` (*float*): Interpolation factor from 0.0 to 1.0 + +**Returns:** Color: New interpolated Color object + +**Example:** +```python +mixed = red.lerp(blue, 0.5) # 50% between red and blue +``` + +--- + +### class `Vector` + +2D vector for positions and directions. + +#### Methods + +#### `magnitude()` + +Calculate the length/magnitude of this vector. + +**Returns:** float: The magnitude of the vector + +#### `distance_to(other)` + +Calculate the distance to another vector. + +**Arguments:** +- `other` (*Vector*): The other vector + +**Returns:** float: Distance between the two vectors + +#### `dot(other)` + +Calculate the dot product with another vector. + +**Arguments:** +- `other` (*Vector*): The other vector + +**Returns:** float: Dot product of the two vectors + +#### `angle()` + +Get the angle of this vector in radians. + +**Returns:** float: Angle in radians from positive x-axis + +#### `magnitude_squared()` + +Calculate the squared magnitude of this vector. + +**Returns:** float: The squared magnitude (faster than magnitude()) + +**Note:** Use this for comparisons to avoid expensive square root calculation. + +#### `copy()` + +Create a copy of this vector. + +**Returns:** Vector: New Vector object with same x and y values + +#### `normalize()` + +Return a unit vector in the same direction. + +**Returns:** Vector: New normalized vector with magnitude 1.0 + +**Raises:** ValueError: If vector has zero magnitude + +--- + +### class `Texture` + +Texture object for image data. + +--- + +### class `Font` + +Font object for text rendering. + +--- + +### Other Classes + +### class `Animation` + +Animate UI element properties over time. + +#### Properties + +- **`property`**: str: Name of the property being animated (e.g., "x", "y", "scale") +- **`duration`**: float: Total duration of the animation in seconds +- **`elapsed_time`**: float: Time elapsed since animation started (read-only) +- **`current_value`**: float: Current interpolated value of the animation (read-only) +- **`is_running`**: bool: True if animation is currently running (read-only) +- **`is_finished`**: bool: True if animation has completed (read-only) + +#### Methods + +#### `update(delta_time)` + +Update the animation by the given time delta. + +**Arguments:** +- `delta_time` (*float*): Time elapsed since last update in seconds + +**Returns:** bool: True if animation is still running, False if finished + +#### `start(target)` + +Start the animation on a target UI element. + +**Arguments:** +- `target` (*UIDrawable*): The UI element to animate + +**Note:** The target must have the property specified in the animation constructor. + +#### `get_current_value()` + +Get the current interpolated value of the animation. + +**Returns:** float: Current animation value between start and end + +--- + +### class `Drawable` + +Base class for all drawable UI elements. + +#### Methods + +#### `get_bounds()` + +Get the bounding rectangle of this drawable element. + +**Returns:** tuple: (x, y, width, height) representing the element's bounds + +**Note:** The bounds are in screen coordinates and account for current position and size. + +#### `resize(width, height)` + +Resize the element to new dimensions. + +**Arguments:** +- `width` (*float*): New width in pixels +- `height` (*float*): New height in pixels + +**Note:** For Caption and Sprite, this may not change actual size if determined by content. + +#### `move(dx, dy)` + +Move the element by a relative offset. + +**Arguments:** +- `dx` (*float*): Horizontal offset in pixels +- `dy` (*float*): Vertical offset in pixels + +**Note:** This modifies the x and y position properties by the given amounts. + +--- + +### class `GridPoint` + +Represents a single tile in a Grid. + +#### Properties + +- **`x`**: int: Grid x coordinate of this point +- **`y`**: int: Grid y coordinate of this point +- **`texture_index`**: int: Index of the texture/sprite to display at this point +- **`solid`**: bool: Whether this point blocks movement +- **`transparent`**: bool: Whether this point allows light/vision through +- **`color`**: Color: Color tint applied to the texture at this point + +--- + +### class `GridPointState` + +State information for a GridPoint. + +#### Properties + +- **`visible`**: bool: Whether this point is currently visible to the player +- **`discovered`**: bool: Whether this point has been discovered/explored +- **`custom_flags`**: int: Bitfield for custom game-specific flags + +--- + +### class `Scene` + +Base class for object-oriented scenes. + +#### Methods + +#### `register_keyboard(callable)` + +Register a keyboard event handler function for the scene. + +**Arguments:** +- `callable` (*callable*): Function that takes (key: str, action: str) parameters + +**Note:** Alternative to overriding the on_keypress method when subclassing Scene objects. + +**Example:** +```python +def handle_keyboard(key, action): + print(f"Key '{key}' was {action}") +scene.register_keyboard(handle_keyboard) +``` + +#### `activate()` + +Make this scene the active scene. + +**Note:** Equivalent to calling setScene() with this scene's name. + +#### `get_ui()` + +Get the UI element collection for this scene. + +**Returns:** UICollection: Collection of all UI elements in this scene + +#### `keypress(handler)` + +Register a keyboard handler function for this scene. + +**Arguments:** +- `handler` (*callable*): Function that takes (key_name: str, is_pressed: bool) + +**Note:** Alternative to overriding the on_keypress method. + +--- + +### class `Timer` + +Timer object for scheduled callbacks. + +#### Methods + +#### `restart()` + +Restart the timer from the beginning. + +**Note:** Resets the timer's internal clock to zero. + +#### `cancel()` + +Cancel the timer and remove it from the system. + +**Note:** After cancelling, the timer object cannot be reused. + +#### `pause()` + +Pause the timer, stopping its callback execution. + +**Note:** Use resume() to continue the timer from where it was paused. + +#### `resume()` + +Resume a paused timer. + +**Note:** Has no effect if timer is not paused. + +--- + +### class `Window` + +Window singleton for accessing and modifying the game window properties. + +#### Methods + +#### `get()` + +Get the Window singleton instance. + +**Returns:** Window: The singleton window object + +**Note:** This is a static method that returns the same instance every time. + +#### `screenshot(filename)` + +Take a screenshot and save it to a file. + +**Arguments:** +- `filename` (*str*): Path where to save the screenshot + +**Note:** Supports PNG, JPG, and BMP formats based on file extension. + +#### `center()` + +Center the window on the screen. + +**Note:** Only works if the window is not fullscreen. + +--- + +## Automation Module + +The `mcrfpy.automation` module provides testing and automation capabilities. + +### `automation.click` + +Click at position + +--- + +### `automation.doubleClick` + +Double click at position + +--- + +### `automation.dragRel` + +Drag mouse relative to current position + +--- + +### `automation.dragTo` + +Drag mouse to position + +--- + +### `automation.hotkey` + +Press a hotkey combination (e.g., hotkey('ctrl', 'c')) + +--- + +### `automation.keyDown` + +Press and hold a key + +--- + +### `automation.keyUp` + +Release a key + +--- + +### `automation.middleClick` + +Middle click at position + +--- + +### `automation.mouseDown` + +Press mouse button + +--- + +### `automation.mouseUp` + +Release mouse button + +--- + +### `automation.moveRel` + +Move mouse relative to current position + +--- + +### `automation.moveTo` + +Move mouse to absolute position + +--- + +### `automation.onScreen` + +Check if coordinates are within screen bounds + +--- + +### `automation.position` + +Get current mouse position as (x, y) tuple + +--- + +### `automation.rightClick` + +Right click at position + +--- + +### `automation.screenshot` + +Save a screenshot to the specified file + +--- + +### `automation.scroll` + +Scroll wheel at position + +--- + +### `automation.size` + +Get screen size as (width, height) tuple + +--- + +### `automation.tripleClick` + +Triple click at position + +--- + +### `automation.typewrite` + +Type text with optional interval between keystrokes + +--- diff --git a/docs/api_reference_complete.html b/docs/api_reference_complete.html index da95fee..73dd72a 100644 --- a/docs/api_reference_complete.html +++ b/docs/api_reference_complete.html @@ -183,7 +183,7 @@

McRogueFace API Reference - Complete Documentation

-

Generated on 2025-07-08 11:53:54

+

Generated on 2025-07-10 01:04:50

Table of Contents

Methods:

-
get_current_value()
-

Get the current interpolated value of the animation.

-
-Returns: float: Current animation value between start and end -
-
-
update(delta_time)

Update the animation by the given time delta.

@@ -662,6 +655,13 @@ The UI element to animate Note: The target must have the property specified in the animation constructor.
+
+
get_current_value()
+

Get the current interpolated value of the animation.

+
+Returns: float: Current animation value between start and end +
+

Caption

@@ -701,23 +701,6 @@ Attributes:
-
resize(width, height)
-

Resize the element to new dimensions.

-
-width -(float): -New width in pixels -
-
-height -(float): -New height in pixels -
-
-Note: For Caption and Sprite, this may not change actual size if determined by content. -
-
-
move(dx, dy)

Move the element by a relative offset.

@@ -734,12 +717,47 @@ Vertical offset in pixels Note: This modifies the x and y position properties by the given amounts.
+
+
resize(width, height)
+

Resize the element to new dimensions.

+
+width +(float): +New width in pixels +
+
+height +(float): +New height in pixels +
+
+Note: For Caption and Sprite, this may not change actual size if determined by content. +
+

Color

SFML Color Object

Methods:

+
from_hex(hex_string)
+

Create a Color from a hexadecimal color string.

+
+hex_string +(str): +Hex color string (e.g., "#FF0000" or "FF0000") +
+
+Returns: Color: New Color object from hex string +
+
+Example: +

+red = Color.from_hex("#FF0000")
+
+
+
+
lerp(other, t)

Linearly interpolate between this color and another.

@@ -775,24 +793,6 @@ hex_str = color.to_hex() # Returns "#FF0000"
-
-
from_hex(hex_string)
-

Create a Color from a hexadecimal color string.

-
-hex_string -(str): -Hex color string (e.g., "#FF0000" or "FF0000") -
-
-Returns: Color: New Color object from hex string -
-
-Example: -

-red = Color.from_hex("#FF0000")
-
-
-

Drawable

@@ -809,6 +809,23 @@ red = Color.from_hex("#FF0000")
+
move(dx, dy)
+

Move the element by a relative offset.

+
+dx +(float): +Horizontal offset in pixels +
+
+dy +(float): +Vertical offset in pixels +
+
+Note: This modifies the x and y position properties by the given amounts. +
+
+
resize(width, height)

Resize the element to new dimensions.

@@ -825,39 +842,12 @@ New height in pixels Note: For Caption and Sprite, this may not change actual size if determined by content.
-
-
move(dx, dy)
-

Move the element by a relative offset.

-
-dx -(float): -Horizontal offset in pixels -
-
-dy -(float): -Vertical offset in pixels -
-
-Note: This modifies the x and y position properties by the given amounts. -
-

Entity

UIEntity objects

Methods:

-
get_bounds()
-

Get the bounding rectangle of this drawable element.

-
-Returns: tuple: (x, y, width, height) representing the element's bounds -
-
-Note: The bounds are in screen coordinates and account for current position and size. -
-
-
move(dx, dy)

Move the element by a relative offset.

@@ -875,6 +865,36 @@ Vertical offset in pixels
+
resize(width, height)
+

Resize the element to new dimensions.

+
+width +(float): +New width in pixels +
+
+height +(float): +New height in pixels +
+
+Note: For Caption and Sprite, this may not change actual size if determined by content. +
+
+
+update_visibility(...) +
+
+
index()
+

Get the index of this entity in its parent grid's entity list.

+
+Returns: int: Index position, or -1 if not in a grid +
+
+
+path_to(...) +
+
at(x, y)

Check if this entity is at the specified grid coordinates.

@@ -892,27 +912,13 @@ Grid y coordinate to check
-
resize(width, height)
-

Resize the element to new dimensions.

-
-width -(float): -New width in pixels -
-
-height -(float): -New height in pixels +
get_bounds()
+

Get the bounding rectangle of this drawable element.

+
+Returns: tuple: (x, y, width, height) representing the element's bounds
-Note: For Caption and Sprite, this may not change actual size if determined by content. -
-
-
-
index()
-

Get the index of this entity in its parent grid's entity list.

-
-Returns: int: Index position, or -1 if not in a grid +Note: The bounds are in screen coordinates and account for current position and size.
@@ -937,21 +943,15 @@ The entity to remove
-
extend(iterable)
-

Add all entities from an iterable to the collection.

-
-iterable -(Iterable[Entity]): -Entities to add -
-
-
-
append(entity)
-

Add an entity to the end of the collection.

+
count(entity)
+

Count the number of occurrences of an entity in the collection.

entity (Entity): -The entity to add +The entity to count +
+
+Returns: int: Number of times entity appears in collection
@@ -967,15 +967,21 @@ The entity to find
-
count(entity)
-

Count the number of occurrences of an entity in the collection.

+
extend(iterable)
+

Add all entities from an iterable to the collection.

+
+iterable +(Iterable[Entity]): +Entities to add +
+
+
+
append(entity)
+

Add an entity to the end of the collection.

entity (Entity): -The entity to count -
-
-Returns: int: Number of times entity appears in collection +The entity to add
@@ -1022,23 +1028,6 @@ Attributes:
-
resize(width, height)
-

Resize the element to new dimensions.

-
-width -(float): -New width in pixels -
-
-height -(float): -New height in pixels -
-
-Note: For Caption and Sprite, this may not change actual size if determined by content. -
-
-
move(dx, dy)

Move the element by a relative offset.

@@ -1055,6 +1044,23 @@ Vertical offset in pixels Note: This modifies the x and y position properties by the given amounts.
+
+
resize(width, height)
+

Resize the element to new dimensions.

+
+width +(float): +New width in pixels +
+
+height +(float): +New height in pixels +
+
+Note: For Caption and Sprite, this may not change actual size if determined by content. +
+

Grid

@@ -1086,31 +1092,24 @@ Attributes: z_index (int): Rendering order

Methods:

-
get_bounds()
-

Get the bounding rectangle of this drawable element.

-
-Returns: tuple: (x, y, width, height) representing the element's bounds +
move(dx, dy)
+

Move the element by a relative offset.

+
+dx +(float): +Horizontal offset in pixels +
+
+dy +(float): +Vertical offset in pixels
-Note: The bounds are in screen coordinates and account for current position and size. +Note: This modifies the x and y position properties by the given amounts.
-
-
at(x, y)
-

Get the GridPoint at the specified grid coordinates.

-
-x -(int): -Grid x coordinate -
-
-y -(int): -Grid y coordinate -
-
-Returns: GridPoint or None: The grid point at (x, y), or None if out of bounds -
+
+compute_fov(...)
resize(width, height)
@@ -1129,21 +1128,49 @@ New height in pixels Note: For Caption and Sprite, this may not change actual size if determined by content.
+
+compute_dijkstra(...) +
+
+get_dijkstra_path(...) +
+
+is_in_fov(...) +
+
+find_path(...) +
+
+compute_astar_path(...) +
-
move(dx, dy)
-

Move the element by a relative offset.

+
at(x, y)
+

Get the GridPoint at the specified grid coordinates.

-dx -(float): -Horizontal offset in pixels +x +(int): +Grid x coordinate
-dy -(float): -Vertical offset in pixels +y +(int): +Grid y coordinate +
+
+Returns: GridPoint or None: The grid point at (x, y), or None if out of bounds +
+
+
+get_dijkstra_distance(...) +
+
+
get_bounds()
+

Get the bounding rectangle of this drawable element.

+
+Returns: tuple: (x, y, width, height) representing the element's bounds
-Note: This modifies the x and y position properties by the given amounts. +Note: The bounds are in screen coordinates and account for current position and size.
@@ -1273,23 +1300,6 @@ Attributes:
-
resize(width, height)
-

Resize the element to new dimensions.

-
-width -(float): -New width in pixels -
-
-height -(float): -New height in pixels -
-
-Note: For Caption and Sprite, this may not change actual size if determined by content. -
-
-
move(dx, dy)

Move the element by a relative offset.

@@ -1306,6 +1316,23 @@ Vertical offset in pixels Note: This modifies the x and y position properties by the given amounts.
+
+
resize(width, height)
+

Resize the element to new dimensions.

+
+width +(float): +New width in pixels +
+
+height +(float): +New height in pixels +
+
+Note: For Caption and Sprite, this may not change actual size if determined by content. +
+

Texture

@@ -1323,6 +1350,13 @@ Vertical offset in pixels
+
restart()
+

Restart the timer from the beginning.

+
+Note: Resets the timer's internal clock to zero. +
+
+
pause()

Pause the timer, stopping its callback execution.

@@ -1336,13 +1370,6 @@ Vertical offset in pixels Note: After cancelling, the timer object cannot be reused.
-
-
restart()
-

Restart the timer from the beginning.

-
-Note: Resets the timer's internal clock to zero. -
-

UICollection

@@ -1358,6 +1385,30 @@ The drawable to remove
+
count(drawable)
+

Count the number of occurrences of a drawable in the collection.

+
+drawable +(UIDrawable): +The drawable to count +
+
+Returns: int: Number of times drawable appears in collection +
+
+
+
index(drawable)
+

Find the index of the first occurrence of a drawable.

+
+drawable +(UIDrawable): +The drawable to find +
+
+Returns: int: Index of drawable in collection +
+
+
extend(iterable)

Add all drawables from an iterable to the collection.

@@ -1375,30 +1426,6 @@ Drawables to add The drawable element to add
-
-
index(drawable)
-

Find the index of the first occurrence of a drawable.

-
-drawable -(UIDrawable): -The drawable to find -
-
-Returns: int: Index of drawable in collection -
-
-
-
count(drawable)
-

Count the number of occurrences of a drawable in the collection.

-
-drawable -(UIDrawable): -The drawable to count -
-
-Returns: int: Number of times drawable appears in collection -
-

UICollectionIter

@@ -1413,28 +1440,10 @@ The drawable to count

SFML Vector Object

Methods:

-
magnitude()
-

Calculate the length/magnitude of this vector.

+
copy()
+

Create a copy of this vector.

-Returns: float: The magnitude of the vector -
-
-Example: -

-length = vector.magnitude()
-
-
-
-
-
distance_to(other)
-

Calculate the distance to another vector.

-
-other -(Vector): -The other vector -
-
-Returns: float: Distance between the two vectors +Returns: Vector: New Vector object with same x and y values
@@ -1457,6 +1466,19 @@ The other vector
+
magnitude()
+

Calculate the length/magnitude of this vector.

+
+Returns: float: The magnitude of the vector +
+
+Example: +

+length = vector.magnitude()
+
+
+
+
normalize()

Return a unit vector in the same direction.

@@ -1474,10 +1496,15 @@ The other vector
-
copy()
-

Create a copy of this vector.

+
distance_to(other)
+

Calculate the distance to another vector.

+
+other +(Vector): +The other vector +
-Returns: Vector: New Vector object with same x and y values +Returns: float: Distance between the two vectors
@@ -1486,6 +1513,16 @@ The other vector

Window singleton for accessing and modifying the game window properties

Methods:

+
get()
+

Get the Window singleton instance.

+
+Returns: Window: The singleton window object +
+
+Note: This is a static method that returns the same instance every time. +
+
+
screenshot(filename)

Take a screenshot and save it to a file.

@@ -1504,16 +1541,6 @@ Path where to save the screenshot Note: Only works if the window is not fullscreen.
-
-
get()
-

Get the Window singleton instance.

-
-Returns: Window: The singleton window object -
-
-Note: This is a static method that returns the same instance every time. -
-

Automation Module

The mcrfpy.automation module provides testing and automation capabilities.

diff --git a/src/PyTimer.cpp b/src/PyTimer.cpp index 10e2f77..df80bc5 100644 --- a/src/PyTimer.cpp +++ b/src/PyTimer.cpp @@ -35,7 +35,7 @@ int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) { PyObject* callback = nullptr; int interval = 0; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOi", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOi", const_cast(kwlist), &name, &callback, &interval)) { return -1; } diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index c8a053b..4143ed0 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -508,8 +508,22 @@ PyMethodDef UIEntity::methods[] = { {"at", (PyCFunction)UIEntity::at, METH_O}, {"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"}, {"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"}, - {"path_to", (PyCFunction)UIEntity::path_to, METH_VARARGS | METH_KEYWORDS, "Find path from entity to target position using Dijkstra pathfinding"}, - {"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS, "Update entity's visibility state based on current FOV"}, + {"path_to", (PyCFunction)UIEntity::path_to, METH_VARARGS | METH_KEYWORDS, + "path_to(x: int, y: int) -> bool\n\n" + "Find and follow path to target position using A* pathfinding.\n\n" + "Args:\n" + " x: Target X coordinate\n" + " y: Target Y coordinate\n\n" + "Returns:\n" + " True if a path was found and the entity started moving, False otherwise\n\n" + "The entity will automatically move along the path over multiple frames.\n" + "Call this again to change the target or repath."}, + {"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS, + "update_visibility() -> None\n\n" + "Update entity's visibility state based on current FOV.\n\n" + "Recomputes which cells are visible from the entity's position and updates\n" + "the entity's gridstate to track explored areas. This is called automatically\n" + "when the entity moves if it has a grid with perspective set."}, {NULL, NULL, 0, NULL} }; @@ -522,8 +536,22 @@ PyMethodDef UIEntity_all_methods[] = { {"at", (PyCFunction)UIEntity::at, METH_O}, {"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"}, {"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"}, - {"path_to", (PyCFunction)UIEntity::path_to, METH_VARARGS | METH_KEYWORDS, "Find path from entity to target position using Dijkstra pathfinding"}, - {"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS, "Update entity's visibility state based on current FOV"}, + {"path_to", (PyCFunction)UIEntity::path_to, METH_VARARGS | METH_KEYWORDS, + "path_to(x: int, y: int) -> bool\n\n" + "Find and follow path to target position using A* pathfinding.\n\n" + "Args:\n" + " x: Target X coordinate\n" + " y: Target Y coordinate\n\n" + "Returns:\n" + " True if a path was found and the entity started moving, False otherwise\n\n" + "The entity will automatically move along the path over multiple frames.\n" + "Call this again to change the target or repath."}, + {"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS, + "update_visibility() -> None\n\n" + "Update entity's visibility state based on current FOV.\n\n" + "Recomputes which cells are visible from the entity's position and updates\n" + "the entity's gridstate to track explored areas. This is called automatically\n" + "when the entity moves if it has a grid with perspective set."}, {NULL} // Sentinel }; diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index 251bba2..d6a109e 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -972,7 +972,7 @@ PyObject* UIGrid::py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* int light_walls = 1; int algorithm = FOV_BASIC; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|ipi", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|ipi", const_cast(kwlist), &x, &y, &radius, &light_walls, &algorithm)) { return NULL; } @@ -998,7 +998,7 @@ PyObject* UIGrid::py_find_path(PyUIGridObject* self, PyObject* args, PyObject* k int x1, y1, x2, y2; float diagonal_cost = 1.41f; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiii|f", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiii|f", const_cast(kwlist), &x1, &y1, &x2, &y2, &diagonal_cost)) { return NULL; } @@ -1026,7 +1026,7 @@ PyObject* UIGrid::py_compute_dijkstra(PyUIGridObject* self, PyObject* args, PyOb int root_x, root_y; float diagonal_cost = 1.41f; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|f", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|f", const_cast(kwlist), &root_x, &root_y, &diagonal_cost)) { return NULL; } @@ -1075,7 +1075,7 @@ PyObject* UIGrid::py_compute_astar_path(PyUIGridObject* self, PyObject* args, Py static const char* kwlist[] = {"x1", "y1", "x2", "y2", "diagonal_cost", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiii|f", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiii|f", const_cast(kwlist), &x1, &y1, &x2, &y2, &diagonal_cost)) { return NULL; } @@ -1096,19 +1096,77 @@ PyObject* UIGrid::py_compute_astar_path(PyUIGridObject* self, PyObject* args, Py PyMethodDef UIGrid::methods[] = { {"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS}, {"compute_fov", (PyCFunction)UIGrid::py_compute_fov, METH_VARARGS | METH_KEYWORDS, - "Compute field of view from a position. Args: x, y, radius=0, light_walls=True, algorithm=FOV_BASIC"}, + "compute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None\n\n" + "Compute field of view from a position.\n\n" + "Args:\n" + " x: X coordinate of the viewer\n" + " y: Y coordinate of the viewer\n" + " radius: Maximum view distance (0 = unlimited)\n" + " light_walls: Whether walls are lit when visible\n" + " algorithm: FOV algorithm to use (FOV_BASIC, FOV_DIAMOND, FOV_SHADOW, FOV_PERMISSIVE_0-8)\n\n" + "Updates the internal FOV state. Use is_in_fov() to check visibility after calling this.\n" + "When perspective is set, this also updates visibility overlays automatically."}, {"is_in_fov", (PyCFunction)UIGrid::py_is_in_fov, METH_VARARGS, - "Check if a cell is in the field of view. Args: x, y"}, + "is_in_fov(x: int, y: int) -> bool\n\n" + "Check if a cell is in the field of view.\n\n" + "Args:\n" + " x: X coordinate to check\n" + " y: Y coordinate to check\n\n" + "Returns:\n" + " True if the cell is visible, False otherwise\n\n" + "Must call compute_fov() first to calculate visibility."}, {"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS, - "Find A* path between two points. Args: x1, y1, x2, y2, diagonal_cost=1.41"}, + "find_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n" + "Find A* path between two points.\n\n" + "Args:\n" + " x1: Starting X coordinate\n" + " y1: Starting Y coordinate\n" + " x2: Target X coordinate\n" + " y2: Target Y coordinate\n" + " diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n" + "Returns:\n" + " List of (x, y) tuples representing the path, empty list if no path exists\n\n" + "Uses A* algorithm with walkability from grid cells."}, {"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS, - "Compute Dijkstra map from root position. Args: root_x, root_y, diagonal_cost=1.41"}, + "compute_dijkstra(root_x: int, root_y: int, diagonal_cost: float = 1.41) -> None\n\n" + "Compute Dijkstra map from root position.\n\n" + "Args:\n" + " root_x: X coordinate of the root/target\n" + " root_y: Y coordinate of the root/target\n" + " diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n" + "Precomputes distances from all reachable cells to the root.\n" + "Use get_dijkstra_distance() and get_dijkstra_path() to query results.\n" + "Useful for multiple entities pathfinding to the same target."}, {"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS, - "Get distance from Dijkstra root to position. Args: x, y. Returns float or None if invalid."}, + "get_dijkstra_distance(x: int, y: int) -> Optional[float]\n\n" + "Get distance from Dijkstra root to position.\n\n" + "Args:\n" + " x: X coordinate to query\n" + " y: Y coordinate to query\n\n" + "Returns:\n" + " Distance as float, or None if position is unreachable or invalid\n\n" + "Must call compute_dijkstra() first."}, {"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS, - "Get path from position to Dijkstra root. Args: x, y. Returns list of (x,y) tuples."}, + "get_dijkstra_path(x: int, y: int) -> List[Tuple[int, int]]\n\n" + "Get path from position to Dijkstra root.\n\n" + "Args:\n" + " x: Starting X coordinate\n" + " y: Starting Y coordinate\n\n" + "Returns:\n" + " List of (x, y) tuples representing path to root, empty if unreachable\n\n" + "Must call compute_dijkstra() first. Path includes start but not root position."}, {"compute_astar_path", (PyCFunction)UIGrid::py_compute_astar_path, METH_VARARGS | METH_KEYWORDS, - "Compute A* path between two points. Args: x1, y1, x2, y2, diagonal_cost=1.41. Returns list of (x,y) tuples. Note: diagonal_cost is currently ignored (uses default 1.41)."}, + "compute_astar_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n" + "Compute A* path between two points.\n\n" + "Args:\n" + " x1: Starting X coordinate\n" + " y1: Starting Y coordinate\n" + " x2: Target X coordinate\n" + " y2: Target Y coordinate\n" + " diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n" + "Returns:\n" + " List of (x, y) tuples representing the path, empty list if no path exists\n\n" + "Alternative A* implementation. Prefer find_path() for consistency."}, {NULL, NULL, 0, NULL} }; @@ -1120,19 +1178,77 @@ PyMethodDef UIGrid_all_methods[] = { UIDRAWABLE_METHODS, {"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS}, {"compute_fov", (PyCFunction)UIGrid::py_compute_fov, METH_VARARGS | METH_KEYWORDS, - "Compute field of view from a position. Args: x, y, radius=0, light_walls=True, algorithm=FOV_BASIC"}, + "compute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None\n\n" + "Compute field of view from a position.\n\n" + "Args:\n" + " x: X coordinate of the viewer\n" + " y: Y coordinate of the viewer\n" + " radius: Maximum view distance (0 = unlimited)\n" + " light_walls: Whether walls are lit when visible\n" + " algorithm: FOV algorithm to use (FOV_BASIC, FOV_DIAMOND, FOV_SHADOW, FOV_PERMISSIVE_0-8)\n\n" + "Updates the internal FOV state. Use is_in_fov() to check visibility after calling this.\n" + "When perspective is set, this also updates visibility overlays automatically."}, {"is_in_fov", (PyCFunction)UIGrid::py_is_in_fov, METH_VARARGS, - "Check if a cell is in the field of view. Args: x, y"}, + "is_in_fov(x: int, y: int) -> bool\n\n" + "Check if a cell is in the field of view.\n\n" + "Args:\n" + " x: X coordinate to check\n" + " y: Y coordinate to check\n\n" + "Returns:\n" + " True if the cell is visible, False otherwise\n\n" + "Must call compute_fov() first to calculate visibility."}, {"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS, - "Find A* path between two points. Args: x1, y1, x2, y2, diagonal_cost=1.41"}, + "find_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n" + "Find A* path between two points.\n\n" + "Args:\n" + " x1: Starting X coordinate\n" + " y1: Starting Y coordinate\n" + " x2: Target X coordinate\n" + " y2: Target Y coordinate\n" + " diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n" + "Returns:\n" + " List of (x, y) tuples representing the path, empty list if no path exists\n\n" + "Uses A* algorithm with walkability from grid cells."}, {"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS, - "Compute Dijkstra map from root position. Args: root_x, root_y, diagonal_cost=1.41"}, + "compute_dijkstra(root_x: int, root_y: int, diagonal_cost: float = 1.41) -> None\n\n" + "Compute Dijkstra map from root position.\n\n" + "Args:\n" + " root_x: X coordinate of the root/target\n" + " root_y: Y coordinate of the root/target\n" + " diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n" + "Precomputes distances from all reachable cells to the root.\n" + "Use get_dijkstra_distance() and get_dijkstra_path() to query results.\n" + "Useful for multiple entities pathfinding to the same target."}, {"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS, - "Get distance from Dijkstra root to position. Args: x, y. Returns float or None if invalid."}, + "get_dijkstra_distance(x: int, y: int) -> Optional[float]\n\n" + "Get distance from Dijkstra root to position.\n\n" + "Args:\n" + " x: X coordinate to query\n" + " y: Y coordinate to query\n\n" + "Returns:\n" + " Distance as float, or None if position is unreachable or invalid\n\n" + "Must call compute_dijkstra() first."}, {"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS, - "Get path from position to Dijkstra root. Args: x, y. Returns list of (x,y) tuples."}, + "get_dijkstra_path(x: int, y: int) -> List[Tuple[int, int]]\n\n" + "Get path from position to Dijkstra root.\n\n" + "Args:\n" + " x: Starting X coordinate\n" + " y: Starting Y coordinate\n\n" + "Returns:\n" + " List of (x, y) tuples representing path to root, empty if unreachable\n\n" + "Must call compute_dijkstra() first. Path includes start but not root position."}, {"compute_astar_path", (PyCFunction)UIGrid::py_compute_astar_path, METH_VARARGS | METH_KEYWORDS, - "Compute A* path between two points. Args: x1, y1, x2, y2, diagonal_cost=1.41. Returns list of (x,y) tuples. Note: diagonal_cost is currently ignored (uses default 1.41)."}, + "compute_astar_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n" + "Compute A* path between two points.\n\n" + "Args:\n" + " x1: Starting X coordinate\n" + " y1: Starting Y coordinate\n" + " x2: Target X coordinate\n" + " y2: Target Y coordinate\n" + " diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n" + "Returns:\n" + " List of (x, y) tuples representing the path, empty list if no path exists\n\n" + "Alternative A* implementation. Prefer find_path() for consistency."}, {NULL} // Sentinel }; @@ -1161,7 +1277,10 @@ PyGetSetDef UIGrid::getsetters[] = { {"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5 {"fill_color", (getter)UIGrid::get_fill_color, (setter)UIGrid::set_fill_color, "Background fill color of the grid", NULL}, - {"perspective", (getter)UIGrid::get_perspective, (setter)UIGrid::set_perspective, "Entity perspective index (-1 for omniscient view)", NULL}, + {"perspective", (getter)UIGrid::get_perspective, (setter)UIGrid::set_perspective, + "Entity perspective index for FOV rendering (-1 for omniscient view, 0+ for entity index). " + "When set to an entity index, only cells visible to that entity are rendered normally; " + "explored but not visible cells are darkened, and unexplored cells are black.", NULL}, {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID}, {"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIGRID}, UIDRAWABLE_GETSETTERS, diff --git a/tests/animation_demo.py b/tests/animation_demo.py index f12fc70..716cded 100644 --- a/tests/animation_demo.py +++ b/tests/animation_demo.py @@ -1,165 +1,208 @@ #!/usr/bin/env python3 -"""Animation System Demo - Shows all animation capabilities""" +""" +Animation Demo: Grid Center & Entity Movement +============================================= + +Demonstrates: +- Animated grid centering following entity +- Smooth entity movement along paths +- Perspective shifts with zoom transitions +- Field of view updates +""" import mcrfpy -import math +import sys -# Create main scene -mcrfpy.createScene("animation_demo") -ui = mcrfpy.sceneUI("animation_demo") -mcrfpy.setScene("animation_demo") +# Setup scene +mcrfpy.createScene("anim_demo") -# Title -title = mcrfpy.Caption((400, 30), "McRogueFace Animation System Demo", mcrfpy.default_font) -title.size = 24 -title.fill_color = (255, 255, 255) -# Note: centered property doesn't exist for Caption +# Create grid +grid = mcrfpy.Grid(grid_x=30, grid_y=20) +grid.fill_color = mcrfpy.Color(20, 20, 30) + +# Simple map +for y in range(20): + for x in range(30): + cell = grid.at(x, y) + # Create walls around edges and some obstacles + if x == 0 or x == 29 or y == 0 or y == 19: + cell.walkable = False + cell.transparent = False + cell.color = mcrfpy.Color(40, 30, 30) + elif (x == 10 and 5 <= y <= 15) or (y == 10 and 5 <= x <= 25): + cell.walkable = False + cell.transparent = False + cell.color = mcrfpy.Color(60, 40, 40) + else: + cell.walkable = True + cell.transparent = True + cell.color = mcrfpy.Color(80, 80, 100) + +# Create entities +player = mcrfpy.Entity(5, 5, grid=grid) +player.sprite_index = 64 # @ + +enemy = mcrfpy.Entity(25, 15, grid=grid) +enemy.sprite_index = 69 # E + +# Update visibility +player.update_visibility() +enemy.update_visibility() + +# UI setup +ui = mcrfpy.sceneUI("anim_demo") +ui.append(grid) +grid.position = (100, 100) +grid.size = (600, 400) + +title = mcrfpy.Caption("Animation Demo - Grid Center & Entity Movement", 200, 20) +title.fill_color = mcrfpy.Color(255, 255, 255) ui.append(title) -# 1. Position Animation Demo -pos_frame = mcrfpy.Frame(50, 100, 80, 80) -pos_frame.fill_color = (255, 100, 100) -pos_frame.outline = 2 -ui.append(pos_frame) +status = mcrfpy.Caption("Press 1: Move Player | 2: Move Enemy | 3: Perspective Shift | Q: Quit", 100, 50) +status.fill_color = mcrfpy.Color(200, 200, 200) +ui.append(status) -pos_label = mcrfpy.Caption((50, 80), "Position Animation", mcrfpy.default_font) -pos_label.fill_color = (200, 200, 200) -ui.append(pos_label) - -# 2. Size Animation Demo -size_frame = mcrfpy.Frame(200, 100, 50, 50) -size_frame.fill_color = (100, 255, 100) -size_frame.outline = 2 -ui.append(size_frame) - -size_label = mcrfpy.Caption((200, 80), "Size Animation", mcrfpy.default_font) -size_label.fill_color = (200, 200, 200) -ui.append(size_label) - -# 3. Color Animation Demo -color_frame = mcrfpy.Frame(350, 100, 80, 80) -color_frame.fill_color = (255, 0, 0) -ui.append(color_frame) - -color_label = mcrfpy.Caption((350, 80), "Color Animation", mcrfpy.default_font) -color_label.fill_color = (200, 200, 200) -ui.append(color_label) - -# 4. Easing Functions Demo -easing_y = 250 -easing_frames = [] -easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInElastic", "easeOutBounce"] - -for i, easing in enumerate(easings): - x = 50 + i * 120 - - frame = mcrfpy.Frame(x, easing_y, 20, 20) - frame.fill_color = (100, 150, 255) - ui.append(frame) - easing_frames.append((frame, easing)) - - label = mcrfpy.Caption((x, easing_y - 20), easing, mcrfpy.default_font) - label.size = 12 - label.fill_color = (200, 200, 200) - ui.append(label) - -# 5. Complex Animation Demo -complex_frame = mcrfpy.Frame(300, 350, 100, 100) -complex_frame.fill_color = (128, 128, 255) -complex_frame.outline = 3 -ui.append(complex_frame) - -complex_label = mcrfpy.Caption((300, 330), "Complex Multi-Property", mcrfpy.default_font) -complex_label.fill_color = (200, 200, 200) -ui.append(complex_label) - -# Start animations -def start_animations(runtime): - # 1. Position animation - back and forth - x_anim = mcrfpy.Animation("x", 500.0, 3.0, "easeInOut") - x_anim.start(pos_frame) - - # 2. Size animation - pulsing - w_anim = mcrfpy.Animation("w", 150.0, 2.0, "easeInOut") - h_anim = mcrfpy.Animation("h", 150.0, 2.0, "easeInOut") - w_anim.start(size_frame) - h_anim.start(size_frame) - - # 3. Color animation - rainbow cycle - color_anim = mcrfpy.Animation("fill_color", (0, 255, 255, 255), 2.0, "linear") - color_anim.start(color_frame) - - # 4. Easing demos - all move up with different easings - for frame, easing in easing_frames: - y_anim = mcrfpy.Animation("y", 150.0, 2.0, easing) - y_anim.start(frame) - - # 5. Complex animation - multiple properties - cx_anim = mcrfpy.Animation("x", 500.0, 4.0, "easeInOut") - cy_anim = mcrfpy.Animation("y", 400.0, 4.0, "easeOut") - cw_anim = mcrfpy.Animation("w", 150.0, 4.0, "easeInElastic") - ch_anim = mcrfpy.Animation("h", 150.0, 4.0, "easeInElastic") - outline_anim = mcrfpy.Animation("outline", 10.0, 4.0, "linear") - - cx_anim.start(complex_frame) - cy_anim.start(complex_frame) - cw_anim.start(complex_frame) - ch_anim.start(complex_frame) - outline_anim.start(complex_frame) - - # Individual color component animations - r_anim = mcrfpy.Animation("fill_color.r", 255.0, 4.0, "easeInOut") - g_anim = mcrfpy.Animation("fill_color.g", 100.0, 4.0, "easeInOut") - b_anim = mcrfpy.Animation("fill_color.b", 50.0, 4.0, "easeInOut") - - r_anim.start(complex_frame) - g_anim.start(complex_frame) - b_anim.start(complex_frame) - - print("All animations started!") - -# Reverse some animations -def reverse_animations(runtime): - # Position back - x_anim = mcrfpy.Animation("x", 50.0, 3.0, "easeInOut") - x_anim.start(pos_frame) - - # Size back - w_anim = mcrfpy.Animation("w", 50.0, 2.0, "easeInOut") - h_anim = mcrfpy.Animation("h", 50.0, 2.0, "easeInOut") - w_anim.start(size_frame) - h_anim.start(size_frame) - - # Color cycle continues - color_anim = mcrfpy.Animation("fill_color", (255, 0, 255, 255), 2.0, "linear") - color_anim.start(color_frame) - - # Easing frames back down - for frame, easing in easing_frames: - y_anim = mcrfpy.Animation("y", 250.0, 2.0, easing) - y_anim.start(frame) - -# Continue color cycle -def cycle_colors(runtime): - color_anim = mcrfpy.Animation("fill_color", (255, 255, 0, 255), 2.0, "linear") - color_anim.start(color_frame) - -# Info text -info = mcrfpy.Caption((400, 550), "Watch as different properties animate with various easing functions!", mcrfpy.default_font) -info.fill_color = (255, 255, 200) -# Note: centered property doesn't exist for Caption +info = mcrfpy.Caption("Perspective: Player", 500, 70) +info.fill_color = mcrfpy.Color(100, 255, 100) ui.append(info) -# Schedule animations -mcrfpy.setTimer("start", start_animations, 500) -mcrfpy.setTimer("reverse", reverse_animations, 4000) -mcrfpy.setTimer("cycle", cycle_colors, 2500) +# Movement functions +def move_player_demo(): + """Demo player movement with camera follow""" + # Calculate path to a destination + path = player.path_to(20, 10) + if not path: + status.text = "No path available!" + return + + status.text = f"Moving player along {len(path)} steps..." + + # Animate along path + for i, (x, y) in enumerate(path[:5]): # First 5 steps + delay = i * 500 # 500ms between steps + + # Schedule movement + def move_step(dt, px=x, py=y): + # Animate entity position + anim_x = mcrfpy.Animation("x", float(px), 0.4, "easeInOut") + anim_y = mcrfpy.Animation("y", float(py), 0.4, "easeInOut") + anim_x.start(player) + anim_y.start(player) + + # Update visibility + player.update_visibility() + + # Animate camera to follow + center_x = px * 16 # Assuming 16x16 tiles + center_y = py * 16 + cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.4, "easeOut") + cam_anim.start(grid) + + mcrfpy.setTimer(f"player_move_{i}", move_step, delay) -# Exit handler -def on_key(key): - if key == "Escape": - mcrfpy.exit() +def move_enemy_demo(): + """Demo enemy movement""" + # Calculate path + path = enemy.path_to(10, 5) + if not path: + status.text = "Enemy has no path!" + return + + status.text = f"Moving enemy along {len(path)} steps..." + + # Animate along path + for i, (x, y) in enumerate(path[:5]): # First 5 steps + delay = i * 500 + + def move_step(dt, ex=x, ey=y): + anim_x = mcrfpy.Animation("x", float(ex), 0.4, "easeInOut") + anim_y = mcrfpy.Animation("y", float(ey), 0.4, "easeInOut") + anim_x.start(enemy) + anim_y.start(enemy) + enemy.update_visibility() + + # If following enemy, update camera + if grid.perspective == 1: + center_x = ex * 16 + center_y = ey * 16 + cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.4, "easeOut") + cam_anim.start(grid) + + mcrfpy.setTimer(f"enemy_move_{i}", move_step, delay) -mcrfpy.keypressScene(on_key) +def perspective_shift_demo(): + """Demo dramatic perspective shift""" + status.text = "Perspective shift in progress..." + + # Phase 1: Zoom out + zoom_out = mcrfpy.Animation("zoom", 0.5, 1.5, "easeInExpo") + zoom_out.start(grid) + + # Phase 2: Switch perspective at peak + def switch_perspective(dt): + if grid.perspective == 0: + grid.perspective = 1 + info.text = "Perspective: Enemy" + info.fill_color = mcrfpy.Color(255, 100, 100) + target = enemy + else: + grid.perspective = 0 + info.text = "Perspective: Player" + info.fill_color = mcrfpy.Color(100, 255, 100) + target = player + + # Update camera to new target + center_x = target.x * 16 + center_y = target.y * 16 + cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.5, "linear") + cam_anim.start(grid) + + mcrfpy.setTimer("switch_persp", switch_perspective, 1600) + + # Phase 3: Zoom back in + def zoom_in(dt): + zoom_in_anim = mcrfpy.Animation("zoom", 1.0, 1.5, "easeOutExpo") + zoom_in_anim.start(grid) + status.text = "Perspective shift complete!" + + mcrfpy.setTimer("zoom_in", zoom_in, 2100) -print("Animation demo started! Press Escape to exit.") \ No newline at end of file +# Input handler +def handle_input(key, state): + if state != "start": + return + + if key == "q": + print("Exiting demo...") + sys.exit(0) + elif key == "1": + move_player_demo() + elif key == "2": + move_enemy_demo() + elif key == "3": + perspective_shift_demo() + +# Set scene +mcrfpy.setScene("anim_demo") +mcrfpy.keypressScene(handle_input) + +# Initial setup +grid.perspective = 0 +grid.zoom = 1.0 + +# Center on player initially +center_x = player.x * 16 +center_y = player.y * 16 +initial_cam = mcrfpy.Animation("center", (center_x, center_y), 0.5, "easeOut") +initial_cam.start(grid) + +print("Animation Demo Started!") +print("======================") +print("Press 1: Animate player movement with camera follow") +print("Press 2: Animate enemy movement") +print("Press 3: Dramatic perspective shift with zoom") +print("Press Q: Quit") +print() +print("Watch how the grid center smoothly follows entities") +print("and how perspective shifts create cinematic effects!") \ No newline at end of file