diff --git a/CLAUDE.md b/CLAUDE.md index 5f82320..b9e553c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co 2. **Always Check Gitea First** - Before starting work: Check open issues for related tasks or blockers - - Before implementing: Read relevant wiki pages per the [Development Workflow](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Development-Workflow) consultation table - When using `/roadmap` command: Query Gitea for up-to-date issue status - When researching a feature: Search Gitea wiki and issues before grepping codebase - When encountering a bug: Check if an issue already exists @@ -30,10 +29,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co 4. **Document as You Go** - When work on one issue interacts with another system: Add notes to related issues - - When discovering undocumented behavior: Note it for wiki update - - When documentation misleads you: Note it for wiki correction - - After committing code changes: Update relevant wiki pages (with user permission) - - Follow the [Development Workflow](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Development-Workflow) for wiki update procedures + - When discovering undocumented behavior: Create task to document it + - When documentation misleads you: Create task to correct or expand it + - When implementing a feature: Update the Gitea wiki if appropriate 5. **Cross-Reference Everything** - Commit messages should reference issue numbers (e.g., "Fixes #104", "Addresses #125") diff --git a/docs/api_reference_dynamic.html b/docs/api_reference_dynamic.html index 14e58f6..6e93bc2 100644 --- a/docs/api_reference_dynamic.html +++ b/docs/api_reference_dynamic.html @@ -108,7 +108,7 @@
Generated on 2025-11-29 10:12:05
+Generated on 2025-10-30 21:14:43
This documentation was dynamically generated from the compiled module.
Returns: None No error is raised if the timer doesn't exist.
end_benchmark() -> strStop benchmark capture and write data to JSON file. - -Note:
-Returns: str: The filename of the written benchmark data
-Raises: RuntimeError: If no benchmark is currently running Returns the auto-generated filename (e.g., 'benchmark_12345_20250528_143022.json')
-exit() -> NoneCleanly shut down the game engine and exit the application. @@ -277,19 +263,6 @@ Note:
Returns: None Only one music track can play at a time. Loading new music stops the current track.
log_benchmark(message: str) -> NoneAdd a log message to the current benchmark frame. - -Note:
-Returns: None
-Raises: RuntimeError: If no benchmark is currently running Messages appear in the 'logs' array of each frame in the output JSON.
-playSound(buffer_id: int) -> NonePlay a sound effect using a previously loaded buffer.
@@ -312,18 +285,6 @@ Note:Raises: KeyError: If the specified scene doesn't exist
setDevConsole(enabled: bool) -> NoneEnable or disable the developer console overlay. - -Note:
-Returns: None When disabled, the grave/tilde key will not open the console. Use this to ship games without debug features.
-setMusicVolume(volume: int) -> NoneSet the global music volume.
@@ -383,15 +344,6 @@ Note:Returns: None If a timer with this name exists, it will be replaced. The handler receives the total runtime in seconds as its argument.
start_benchmark() -> NoneStart capturing benchmark data to a file. - -Note:
-Returns: None
-Raises: RuntimeError: If a benchmark is already running Benchmark filename is auto-generated from PID and timestamp. Use end_benchmark() to stop and get filename.
-Inherits from: Drawable
-Arc(center=None, radius=0, start_angle=0, end_angle=90, color=None, thickness=1, **kwargs) - -An arc UI element for drawing curved line segments. - -Args: - center (tuple, optional): Center position as (x, y). Default: (0, 0) - radius (float, optional): Arc radius in pixels. Default: 0 - start_angle (float, optional): Starting angle in degrees. Default: 0 - end_angle (float, optional): Ending angle in degrees. Default: 90 - color (Color, optional): Arc color. Default: White - thickness (float, optional): Line thickness. Default: 1.0 - -Keyword Args: - click (callable): Click handler. Default: None - visible (bool): Visibility state. Default: True - opacity (float): Opacity (0.0-1.0). Default: 1.0 - z_index (int): Rendering order. Default: 0 - name (str): Element name for finding. Default: None - -Attributes: - center (Vector): Center position - radius (float): Arc radius - start_angle (float): Starting angle in degrees - end_angle (float): Ending angle in degrees - color (Color): Arc color - thickness (float): Line thickness - visible (bool): Visibility state - opacity (float): Opacity value - z_index (int): Rendering order - name (str): Element name -
-get_bounds() -> tupleGet the bounding rectangle of this drawable element. - -Note:
-Returns: tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.
-move(dx: float, dy: float) -> NoneMove the element by a relative offset. - -Note:
-resize(width: float, height: float) -> NoneResize the element to new dimensions. - -Note:
-Inherits from: Drawable
@@ -577,71 +462,6 @@ Note:resize(width: float, height: float) -> NoneResize the element to new dimensions. -Note:
-Inherits from: Drawable
-Circle(radius=0, center=None, fill_color=None, outline_color=None, outline=0, **kwargs) - -A circle UI element for drawing filled or outlined circles. - -Args: - radius (float, optional): Circle radius in pixels. Default: 0 - center (tuple, optional): Center position as (x, y). Default: (0, 0) - fill_color (Color, optional): Fill color. Default: White - outline_color (Color, optional): Outline color. Default: Transparent - outline (float, optional): Outline thickness. Default: 0 (no outline) - -Keyword Args: - click (callable): Click handler. Default: None - visible (bool): Visibility state. Default: True - opacity (float): Opacity (0.0-1.0). Default: 1.0 - z_index (int): Rendering order. Default: 0 - name (str): Element name for finding. Default: None - -Attributes: - radius (float): Circle radius - center (Vector): Center position - fill_color (Color): Fill color - outline_color (Color): Outline color - outline (float): Outline thickness - visible (bool): Visibility state - opacity (float): Opacity value - z_index (int): Rendering order - name (str): Element name -
-get_bounds() -> tupleGet the bounding rectangle of this drawable element. - -Note:
-Returns: tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.
-move(dx: float, dy: float) -> NoneMove the element by a relative offset. - -Note:
-resize(width: float, height: float) -> NoneResize the element to new dimensions. - Note:
ColorLayer(z_index=-1, grid_size=None) - -A grid layer that stores RGBA colors per cell. - -Args: - z_index (int): Render order. Negative = below entities. Default: -1 - grid_size (tuple): Dimensions as (width, height). Default: parent grid size - -Attributes: - z_index (int): Layer z-order relative to entities - visible (bool): Whether layer is rendered - grid_size (tuple): Layer dimensions (read-only) - -Methods: - at(x, y): Get color at cell position - set(x, y, color): Set color at cell position - fill(color): Fill entire layer with color
-at(x, y) -> ColorGet the color at cell position (x, y).
-fill(color)Fill the entire layer with the specified color.
-set(x, y, color)Set the color at cell position (x, y).
-Base class for all drawable UI elements
@@ -860,44 +643,23 @@ when the entity moves if it has a grid with perspective set.append(entity)Add an entity to the end of the collection.
+append(...)count(entity) -> intCount occurrences of entity in the collection.
+count(...)extend(iterable)Add all entities from an iterable to the collection.
+extend(...)find(name) -> entity or listFind entities by name.
-Returns: Single entity if exact match, list if wildcard, None if not found.
+index(...)index(entity) -> intReturn index of first occurrence of entity. Raises ValueError if not found.
-insert(index, entity)Insert entity at index. Like list.insert(), indices past the end append.
-pop([index]) -> entityRemove and return entity at index (default: last entity).
-remove(entity)Remove first occurrence of entity. Raises ValueError if not found.
+remove(...)add_layer(type: str, z_index: int = -1, texture: Texture = None) -> ColorLayer | TileLayerAdd a new layer to the grid.
-Returns: The created ColorLayer or TileLayer object.
-at(...)compute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> NoneCompute field of view from a position.
+compute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> List[Tuple[int, int, bool, bool]]Compute field of view from a position and return visible cells.
Returns: List of tuples (x, y, visible, discovered) for all visible cells: - x, y: Grid coordinates - visible: True (all returned cells are visible) - discovered: True (FOV implies discovery) Also updates the internal FOV state for use with is_in_fov().
Returns: True if the cell is visible, False otherwise Must call compute_fov() first to calculate visibility.
layer(z_index: int) -> ColorLayer | TileLayer | NoneGet a layer by its z_index.
-Returns: The layer with the specified z_index, or None if not found.
-move(dx: float, dy: float) -> NoneMove the element by a relative offset. @@ -1155,14 +896,6 @@ Note:
remove_layer(layer: ColorLayer | TileLayer) -> NoneRemove a layer from the grid.
-resize(width: float, height: float) -> NoneResize the element to new dimensions. @@ -1187,69 +920,6 @@ Note:
Inherits from: Drawable
-Line(start=None, end=None, thickness=1.0, color=None, **kwargs) - -A line UI element for drawing straight lines between two points. - -Args: - start (tuple, optional): Starting point as (x, y). Default: (0, 0) - end (tuple, optional): Ending point as (x, y). Default: (0, 0) - thickness (float, optional): Line thickness in pixels. Default: 1.0 - color (Color, optional): Line color. Default: White - -Keyword Args: - click (callable): Click handler. Default: None - visible (bool): Visibility state. Default: True - opacity (float): Opacity (0.0-1.0). Default: 1.0 - z_index (int): Rendering order. Default: 0 - name (str): Element name for finding. Default: None - -Attributes: - start (Vector): Starting point - end (Vector): Ending point - thickness (float): Line thickness - color (Color): Line color - visible (bool): Visibility state - opacity (float): Opacity value - z_index (int): Rendering order - name (str): Element name -
-get_bounds() -> tupleGet the bounding rectangle of this drawable element. - -Note:
-Returns: tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.
-move(dx: float, dy: float) -> NoneMove the element by a relative offset. - -Note:
-resize(width: float, height: float) -> NoneResize the element to new dimensions. - -Note:
-Base class for object-oriented scenes
@@ -1359,45 +1029,6 @@ Note:TileLayer(z_index=-1, texture=None, grid_size=None) - -A grid layer that stores sprite indices per cell. - -Args: - z_index (int): Render order. Negative = below entities. Default: -1 - texture (Texture): Sprite atlas for tile rendering. Default: None - grid_size (tuple): Dimensions as (width, height). Default: parent grid size - -Attributes: - z_index (int): Layer z-order relative to entities - visible (bool): Whether layer is rendered - texture (Texture): Tile sprite atlas - grid_size (tuple): Layer dimensions (read-only) - -Methods: - at(x, y): Get tile index at cell position - set(x, y, index): Set tile index at cell position - fill(index): Fill entire layer with tile index
-at(x, y) -> intGet the tile index at cell position (x, y). Returns -1 if no tile.
-fill(index)Fill the entire layer with the specified tile index.
-set(x, y, index)Set the tile index at cell position (x, y). Use -1 for no tile.
-Timer(name, callback, interval, once=False) @@ -1475,50 +1106,23 @@ Note:
append(element)Add an element to the end of the collection.
+append(...)count(element) -> intCount occurrences of element in the collection.
+count(...)extend(iterable)Add all elements from an iterable to the collection.
+extend(...)find(name, recursive=False) -> element or listFind elements by name.
-Returns: Single element if exact match, list if wildcard, None if not found.
+index(...)index(element) -> intReturn index of first occurrence of element. Raises ValueError if not found.
-insert(index, element)Insert element at index. Like list.insert(), indices past the end append. - -Note: If using z_index for sorting, insertion order may not persist after -the next render. Use name-based .find() for stable element access.
-pop([index]) -> elementRemove and return element at index (default: last element). - -Note: If using z_index for sorting, indices may shift after render. -Use name-based .find() for stable element access.
-remove(element)Remove first occurrence of element. Raises ValueError if not found.
+remove(...)Returns: float: Dot product of the two vectors
-floor() -> VectorReturn a new vector with floored (integer) coordinates. - -Note:
-Returns: Vector: New Vector with floor(x) and floor(y) Useful for grid-based positioning. For a hashable tuple, use the .int property instead.
-magnitude() -> floatCalculate the length/magnitude of this vector.
diff --git a/docs/mcrfpy.3 b/docs/mcrfpy.3 index 8e4953d..5c46cba 100644 --- a/docs/mcrfpy.3 +++ b/docs/mcrfpy.3 @@ -14,11 +14,11 @@ . ftr VB CB . ftr VBI CBI .\} -.TH "MCRFPY" "3" "2025-11-29" "McRogueFace dev" "" +.TH "MCRFPY" "3" "2025-10-30" "McRogueFace dev" "" .hy .SH McRogueFace API Reference .PP -\f[I]Generated on 2025-11-29 10:12:05\f[R] +\f[I]Generated on 2025-10-30 20:49:34\f[R] .PP \f[I]This documentation was dynamically generated from the compiled module.\f[R] @@ -31,16 +31,10 @@ Classes .IP \[bu] 2 Animation .IP \[bu] 2 -Arc -.IP \[bu] 2 Caption .IP \[bu] 2 -Circle -.IP \[bu] 2 Color .IP \[bu] 2 -ColorLayer -.IP \[bu] 2 Drawable .IP \[bu] 2 Entity @@ -57,16 +51,12 @@ GridPoint .IP \[bu] 2 GridPointState .IP \[bu] 2 -Line -.IP \[bu] 2 Scene .IP \[bu] 2 Sprite .IP \[bu] 2 Texture .IP \[bu] 2 -TileLayer -.IP \[bu] 2 Timer .IP \[bu] 2 UICollection @@ -120,17 +110,6 @@ Note: .PP \f[B]Returns:\f[R] None No error is raised if the timer doesn\[cq]t exist. -.SS \f[V]end_benchmark() -> str\f[R] -.PP -Stop benchmark capture and write data to JSON file. -.PP -Note: -.PP -\f[B]Returns:\f[R] str: The filename of the written benchmark data -.PP -\f[B]Raises:\f[R] RuntimeError: If no benchmark is currently running -Returns the auto-generated filename (e.g., -`benchmark_12345_20250528_143022.json') .SS \f[V]exit() -> None\f[R] .PP Cleanly shut down the game engine and exit the application. @@ -201,19 +180,6 @@ OGG, FLAC) .PP \f[B]Returns:\f[R] None Only one music track can play at a time. Loading new music stops the current track. -.SS \f[V]log_benchmark(message: str) -> None\f[R] -.PP -Add a log message to the current benchmark frame. -.PP -Note: -.PP -\f[B]Arguments:\f[R] - \f[V]message\f[R]: Text to associate with the -current frame -.PP -\f[B]Returns:\f[R] None -.PP -\f[B]Raises:\f[R] RuntimeError: If no benchmark is currently running -Messages appear in the `logs' array of each frame in the output JSON. .SS \f[V]playSound(buffer_id: int) -> None\f[R] .PP Play a sound effect using a previously loaded buffer. @@ -235,18 +201,6 @@ If None, uses current scene in the scene .PP \f[B]Raises:\f[R] KeyError: If the specified scene doesn\[cq]t exist -.SS \f[V]setDevConsole(enabled: bool) -> None\f[R] -.PP -Enable or disable the developer console overlay. -.PP -Note: -.PP -\f[B]Arguments:\f[R] - \f[V]enabled\f[R]: True to enable the console -(default), False to disable -.PP -\f[B]Returns:\f[R] None When disabled, the grave/tilde key will not open -the console. -Use this to ship games without debug features. .SS \f[V]setMusicVolume(volume: int) -> None\f[R] .PP Set the global music volume. @@ -301,17 +255,6 @@ Note: \f[B]Returns:\f[R] None If a timer with this name exists, it will be replaced. The handler receives the total runtime in seconds as its argument. -.SS \f[V]start_benchmark() -> None\f[R] -.PP -Start capturing benchmark data to a file. -.PP -Note: -.PP -\f[B]Returns:\f[R] None -.PP -\f[B]Raises:\f[R] RuntimeError: If a benchmark is already running -Benchmark filename is auto-generated from PID and timestamp. -Use end_benchmark() to stop and get filename. .SS Classes .SS Animation .PP @@ -373,62 +316,6 @@ update in seconds \f[B]Returns:\f[R] bool: True if animation is still running, False if complete Typically called by AnimationManager automatically. Manual calls only needed for custom animation control. -.SS Arc -.PP -\f[I]Inherits from: Drawable\f[R] -.PP -Arc(center=None, radius=0, start_angle=0, end_angle=90, color=None, -thickness=1, **kwargs) -.PP -An arc UI element for drawing curved line segments. -.PP -Args: center (tuple, optional): Center position as (x, y). -Default: (0, 0) radius (float, optional): Arc radius in pixels. -Default: 0 start_angle (float, optional): Starting angle in degrees. -Default: 0 end_angle (float, optional): Ending angle in degrees. -Default: 90 color (Color, optional): Arc color. -Default: White thickness (float, optional): Line thickness. -Default: 1.0 -.PP -Keyword Args: click (callable): Click handler. -Default: None visible (bool): Visibility state. -Default: True opacity (float): Opacity (0.0-1.0). -Default: 1.0 z_index (int): Rendering order. -Default: 0 name (str): Element name for finding. -Default: None -.PP -Attributes: center (Vector): Center position radius (float): Arc radius -start_angle (float): Starting angle in degrees end_angle (float): Ending -angle in degrees color (Color): Arc color thickness (float): Line -thickness visible (bool): Visibility state opacity (float): Opacity -value z_index (int): Rendering order name (str): Element name -.PP -\f[B]Methods:\f[R] -.SS \f[V]get_bounds() -> tuple\f[R] -.PP -Get the bounding rectangle of this drawable element. -.PP -Note: -.PP -\f[B]Returns:\f[R] tuple: (x, y, width, height) representing the -element\[cq]s bounds The bounds are in screen coordinates and account -for current position and size. -.SS \f[V]move(dx: float, dy: float) -> None\f[R] -.PP -Move the element by a relative offset. -.PP -Note: -.PP -\f[B]Arguments:\f[R] - \f[V]dx\f[R]: Horizontal offset in pixels - -\f[V]dy\f[R]: Vertical offset in pixels -.SS \f[V]resize(width: float, height: float) -> None\f[R] -.PP -Resize the element to new dimensions. -.PP -Note: -.PP -\f[B]Arguments:\f[R] - \f[V]width\f[R]: New width in pixels - -\f[V]height\f[R]: New height in pixels .SS Caption .PP \f[I]Inherits from: Drawable\f[R] @@ -491,61 +378,6 @@ Note: .PP \f[B]Arguments:\f[R] - \f[V]width\f[R]: New width in pixels - \f[V]height\f[R]: New height in pixels -.SS Circle -.PP -\f[I]Inherits from: Drawable\f[R] -.PP -Circle(radius=0, center=None, fill_color=None, outline_color=None, -outline=0, **kwargs) -.PP -A circle UI element for drawing filled or outlined circles. -.PP -Args: radius (float, optional): Circle radius in pixels. -Default: 0 center (tuple, optional): Center position as (x, y). -Default: (0, 0) fill_color (Color, optional): Fill color. -Default: White outline_color (Color, optional): Outline color. -Default: Transparent outline (float, optional): Outline thickness. -Default: 0 (no outline) -.PP -Keyword Args: click (callable): Click handler. -Default: None visible (bool): Visibility state. -Default: True opacity (float): Opacity (0.0-1.0). -Default: 1.0 z_index (int): Rendering order. -Default: 0 name (str): Element name for finding. -Default: None -.PP -Attributes: radius (float): Circle radius center (Vector): Center -position fill_color (Color): Fill color outline_color (Color): Outline -color outline (float): Outline thickness visible (bool): Visibility -state opacity (float): Opacity value z_index (int): Rendering order name -(str): Element name -.PP -\f[B]Methods:\f[R] -.SS \f[V]get_bounds() -> tuple\f[R] -.PP -Get the bounding rectangle of this drawable element. -.PP -Note: -.PP -\f[B]Returns:\f[R] tuple: (x, y, width, height) representing the -element\[cq]s bounds The bounds are in screen coordinates and account -for current position and size. -.SS \f[V]move(dx: float, dy: float) -> None\f[R] -.PP -Move the element by a relative offset. -.PP -Note: -.PP -\f[B]Arguments:\f[R] - \f[V]dx\f[R]: Horizontal offset in pixels - -\f[V]dy\f[R]: Vertical offset in pixels -.SS \f[V]resize(width: float, height: float) -> None\f[R] -.PP -Resize the element to new dimensions. -.PP -Note: -.PP -\f[B]Arguments:\f[R] - \f[V]width\f[R]: New width in pixels - -\f[V]height\f[R]: New height in pixels .SS Color .PP SFML Color Object @@ -587,34 +419,6 @@ Note: \f[B]Returns:\f[R] str: Hex string in format `#RRGGBB' or `#RRGGBBAA' (if alpha < 255) Alpha component is only included if not fully opaque (< 255) -.SS ColorLayer -.PP -ColorLayer(z_index=-1, grid_size=None) -.PP -A grid layer that stores RGBA colors per cell. -.PP -Args: z_index (int): Render order. -Negative = below entities. -Default: -1 grid_size (tuple): Dimensions as (width, height). -Default: parent grid size -.PP -Attributes: z_index (int): Layer z-order relative to entities visible -(bool): Whether layer is rendered grid_size (tuple): Layer dimensions -(read-only) -.PP -Methods: at(x, y): Get color at cell position set(x, y, color): Set -color at cell position fill(color): Fill entire layer with color -.PP -\f[B]Methods:\f[R] -.SS \f[V]at(x, y) -> Color\f[R] -.PP -Get the color at cell position (x, y). -.SS \f[V]fill(color)\f[R] -.PP -Fill the entire layer with the specified color. -.SS \f[V]set(x, y, color)\f[R] -.PP -Set the color at cell position (x, y). .SS Drawable .PP Base class for all drawable UI elements @@ -727,36 +531,11 @@ perspective set. Iterable, indexable collection of Entities .PP \f[B]Methods:\f[R] -.SS \f[V]append(entity)\f[R] -.PP -Add an entity to the end of the collection. -.SS \f[V]count(entity) -> int\f[R] -.PP -Count occurrences of entity in the collection. -.SS \f[V]extend(iterable)\f[R] -.PP -Add all entities from an iterable to the collection. -.SS \f[V]find(name) -> entity or list\f[R] -.PP -Find entities by name. -.PP -\f[B]Returns:\f[R] Single entity if exact match, list if wildcard, None -if not found. -.SS \f[V]index(entity) -> int\f[R] -.PP -Return index of first occurrence of entity. -Raises ValueError if not found. -.SS \f[V]insert(index, entity)\f[R] -.PP -Insert entity at index. -Like list.insert(), indices past the end append. -.SS \f[V]pop([index]) -> entity\f[R] -.PP -Remove and return entity at index (default: last entity). -.SS \f[V]remove(entity)\f[R] -.PP -Remove first occurrence of entity. -Raises ValueError if not found. +.SS \f[V]append(...)\f[R] +.SS \f[V]count(...)\f[R] +.SS \f[V]extend(...)\f[R] +.SS \f[V]index(...)\f[R] +.SS \f[V]remove(...)\f[R] .SS Font .PP SFML Font Object @@ -789,8 +568,6 @@ Default: 0 w (float): Width override. Default: 0 h (float): Height override. Default: 0 clip_children (bool): Whether to clip children to frame bounds. -Default: False cache_subtree (bool): Cache rendering to texture for -performance. Default: False .PP Attributes: x, y (float): Position in pixels w, h (float): Size in @@ -800,7 +577,7 @@ thickness click (callable): Click event handler children (list): Collection of child drawable elements visible (bool): Visibility state opacity (float): Opacity value z_index (int): Rendering order name (str): Element name clip_children (bool): Whether to clip children to -frame bounds cache_subtree (bool): Cache subtree rendering to texture +frame bounds .PP \f[B]Methods:\f[R] .SS \f[V]get_bounds() -> tuple\f[R] @@ -876,17 +653,6 @@ visible (bool): Visibility state opacity (float): Opacity value z_index (int): Rendering order name (str): Element name .PP \f[B]Methods:\f[R] -.SS \f[V]add_layer(type: str, z_index: int = -1, texture: Texture = None) -> ColorLayer | TileLayer\f[R] -.PP -Add a new layer to the grid. -.PP -\f[B]Arguments:\f[R] - \f[V]type\f[R]: Layer type (`color' or `tile') - -\f[V]z_index\f[R]: Render order. -Negative = below entities, >= 0 = above entities. -Default: -1 - \f[V]texture\f[R]: Texture for tile layers. -Required for `tile' type. -.PP -\f[B]Returns:\f[R] The created ColorLayer or TileLayer object. .SS \f[V]at(...)\f[R] .SS \f[V]compute_astar_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\f[R] .PP @@ -907,15 +673,20 @@ Compute Dijkstra map from root position. \f[B]Arguments:\f[R] - \f[V]root_x\f[R]: X coordinate of the root/target - \f[V]root_y\f[R]: Y coordinate of the root/target - \f[V]diagonal_cost\f[R]: Cost of diagonal movement (default: 1.41) -.SS \f[V]compute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None\f[R] +.SS \f[V]compute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> List[Tuple[int, int, bool, bool]]\f[R] .PP -Compute field of view from a position. +Compute field of view from a position and return visible cells. .PP \f[B]Arguments:\f[R] - \f[V]x\f[R]: X coordinate of the viewer - \f[V]y\f[R]: Y coordinate of the viewer - \f[V]radius\f[R]: Maximum view distance (0 = unlimited) - \f[V]light_walls\f[R]: Whether walls are lit when visible - \f[V]algorithm\f[R]: FOV algorithm to use (FOV_BASIC, FOV_DIAMOND, FOV_SHADOW, FOV_PERMISSIVE_0-8) +.PP +\f[B]Returns:\f[R] List of tuples (x, y, visible, discovered) for all +visible cells: - x, y: Grid coordinates - visible: True (all returned +cells are visible) - discovered: True (FOV implies discovery) Also +updates the internal FOV state for use with is_in_fov(). .SS \f[V]find_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\f[R] .PP Find A* path between two points. @@ -965,15 +736,6 @@ Y coordinate to check .PP \f[B]Returns:\f[R] True if the cell is visible, False otherwise Must call compute_fov() first to calculate visibility. -.SS \f[V]layer(z_index: int) -> ColorLayer | TileLayer | None\f[R] -.PP -Get a layer by its z_index. -.PP -\f[B]Arguments:\f[R] - \f[V]z_index\f[R]: The z_index of the layer to -find. -.PP -\f[B]Returns:\f[R] The layer with the specified z_index, or None if not -found. .SS \f[V]move(dx: float, dy: float) -> None\f[R] .PP Move the element by a relative offset. @@ -982,11 +744,6 @@ Note: .PP \f[B]Arguments:\f[R] - \f[V]dx\f[R]: Horizontal offset in pixels - \f[V]dy\f[R]: Vertical offset in pixels -.SS \f[V]remove_layer(layer: ColorLayer | TileLayer) -> None\f[R] -.PP -Remove a layer from the grid. -.PP -\f[B]Arguments:\f[R] - \f[V]layer\f[R]: The layer to remove. .SS \f[V]resize(width: float, height: float) -> None\f[R] .PP Resize the element to new dimensions. @@ -1005,58 +762,6 @@ UIGridPoint object UIGridPointState object .PP \f[B]Methods:\f[R] -.SS Line -.PP -\f[I]Inherits from: Drawable\f[R] -.PP -Line(start=None, end=None, thickness=1.0, color=None, **kwargs) -.PP -A line UI element for drawing straight lines between two points. -.PP -Args: start (tuple, optional): Starting point as (x, y). -Default: (0, 0) end (tuple, optional): Ending point as (x, y). -Default: (0, 0) thickness (float, optional): Line thickness in pixels. -Default: 1.0 color (Color, optional): Line color. -Default: White -.PP -Keyword Args: click (callable): Click handler. -Default: None visible (bool): Visibility state. -Default: True opacity (float): Opacity (0.0-1.0). -Default: 1.0 z_index (int): Rendering order. -Default: 0 name (str): Element name for finding. -Default: None -.PP -Attributes: start (Vector): Starting point end (Vector): Ending point -thickness (float): Line thickness color (Color): Line color visible -(bool): Visibility state opacity (float): Opacity value z_index (int): -Rendering order name (str): Element name -.PP -\f[B]Methods:\f[R] -.SS \f[V]get_bounds() -> tuple\f[R] -.PP -Get the bounding rectangle of this drawable element. -.PP -Note: -.PP -\f[B]Returns:\f[R] tuple: (x, y, width, height) representing the -element\[cq]s bounds The bounds are in screen coordinates and account -for current position and size. -.SS \f[V]move(dx: float, dy: float) -> None\f[R] -.PP -Move the element by a relative offset. -.PP -Note: -.PP -\f[B]Arguments:\f[R] - \f[V]dx\f[R]: Horizontal offset in pixels - -\f[V]dy\f[R]: Vertical offset in pixels -.SS \f[V]resize(width: float, height: float) -> None\f[R] -.PP -Resize the element to new dimensions. -.PP -Note: -.PP -\f[B]Arguments:\f[R] - \f[V]width\f[R]: New width in pixels - -\f[V]height\f[R]: New height in pixels .SS Scene .PP Base class for object-oriented scenes @@ -1159,38 +864,6 @@ Note: SFML Texture Object .PP \f[B]Methods:\f[R] -.SS TileLayer -.PP -TileLayer(z_index=-1, texture=None, grid_size=None) -.PP -A grid layer that stores sprite indices per cell. -.PP -Args: z_index (int): Render order. -Negative = below entities. -Default: -1 texture (Texture): Sprite atlas for tile rendering. -Default: None grid_size (tuple): Dimensions as (width, height). -Default: parent grid size -.PP -Attributes: z_index (int): Layer z-order relative to entities visible -(bool): Whether layer is rendered texture (Texture): Tile sprite atlas -grid_size (tuple): Layer dimensions (read-only) -.PP -Methods: at(x, y): Get tile index at cell position set(x, y, index): Set -tile index at cell position fill(index): Fill entire layer with tile -index -.PP -\f[B]Methods:\f[R] -.SS \f[V]at(x, y) -> int\f[R] -.PP -Get the tile index at cell position (x, y). -Returns -1 if no tile. -.SS \f[V]fill(index)\f[R] -.PP -Fill the entire layer with the specified tile index. -.SS \f[V]set(x, y, index)\f[R] -.PP -Set the tile index at cell position (x, y). -Use -1 for no tile. .SS Timer .PP Timer(name, callback, interval, once=False) @@ -1264,43 +937,11 @@ Timer will fire after the remaining time elapses. Iterable, indexable collection of UI objects .PP \f[B]Methods:\f[R] -.SS \f[V]append(element)\f[R] -.PP -Add an element to the end of the collection. -.SS \f[V]count(element) -> int\f[R] -.PP -Count occurrences of element in the collection. -.SS \f[V]extend(iterable)\f[R] -.PP -Add all elements from an iterable to the collection. -.SS \f[V]find(name, recursive=False) -> element or list\f[R] -.PP -Find elements by name. -.PP -\f[B]Returns:\f[R] Single element if exact match, list if wildcard, None -if not found. -.SS \f[V]index(element) -> int\f[R] -.PP -Return index of first occurrence of element. -Raises ValueError if not found. -.SS \f[V]insert(index, element)\f[R] -.PP -Insert element at index. -Like list.insert(), indices past the end append. -.PP -Note: If using z_index for sorting, insertion order may not persist -after the next render. -Use name-based .find() for stable element access. -.SS \f[V]pop([index]) -> element\f[R] -.PP -Remove and return element at index (default: last element). -.PP -Note: If using z_index for sorting, indices may shift after render. -Use name-based .find() for stable element access. -.SS \f[V]remove(element)\f[R] -.PP -Remove first occurrence of element. -Raises ValueError if not found. +.SS \f[V]append(...)\f[R] +.SS \f[V]count(...)\f[R] +.SS \f[V]extend(...)\f[R] +.SS \f[V]index(...)\f[R] +.SS \f[V]remove(...)\f[R] .SS UICollectionIter .PP Iterator for a collection of UI objects @@ -1340,15 +981,6 @@ Calculate the dot product with another vector. \f[B]Arguments:\f[R] - \f[V]other\f[R]: The other vector .PP \f[B]Returns:\f[R] float: Dot product of the two vectors -.SS \f[V]floor() -> Vector\f[R] -.PP -Return a new vector with floored (integer) coordinates. -.PP -Note: -.PP -\f[B]Returns:\f[R] Vector: New Vector with floor(x) and floor(y) Useful -for grid-based positioning. -For a hashable tuple, use the .int property instead. .SS \f[V]magnitude() -> float\f[R] .PP Calculate the length/magnitude of this vector. diff --git a/src/PyScene.cpp b/src/PyScene.cpp index 57806bd..3288cd4 100644 --- a/src/PyScene.cpp +++ b/src/PyScene.cpp @@ -61,7 +61,7 @@ void PyScene::do_mouse_input(std::string button, std::string type) void PyScene::doAction(std::string name, std::string type) { - if (name.compare("left") == 0 || name.compare("right") == 0 || name.compare("wheel_up") == 0 || name.compare("wheel_down") == 0) { + if (name.compare("left") == 0 || name.compare("rclick") == 0 || name.compare("wheel_up") == 0 || name.compare("wheel_down") == 0) { do_mouse_input(name, type); } else if ACTIONONCE("debug_menu") { diff --git a/stubs/mcrfpy.pyi b/stubs/mcrfpy.pyi index b6654fa..919794b 100644 --- a/stubs/mcrfpy.pyi +++ b/stubs/mcrfpy.pyi @@ -96,159 +96,98 @@ class Drawable: ... class Frame(Drawable): - """Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, on_click=None, children=None) - + """Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None) + A rectangular frame UI element that can contain other drawable elements. """ - + @overload def __init__(self) -> None: ... @overload def __init__(self, x: float = 0, y: float = 0, w: float = 0, h: float = 0, fill_color: Optional[Color] = None, outline_color: Optional[Color] = None, - outline: float = 0, on_click: Optional[Callable] = None, + outline: float = 0, click: Optional[Callable] = None, children: Optional[List[UIElement]] = None) -> None: ... - + w: float h: float fill_color: Color outline_color: Color outline: float - on_click: Optional[Callable[[float, float, int], None]] - on_enter: Optional[Callable[[], None]] - on_exit: Optional[Callable[[], None]] - on_move: Optional[Callable[[float, float], None]] + click: Optional[Callable[[float, float, int], None]] children: 'UICollection' clip_children: bool class Caption(Drawable): - """Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, on_click=None) - + """Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None) + A text display UI element with customizable font and styling. """ - + @overload def __init__(self) -> None: ... @overload def __init__(self, text: str = '', x: float = 0, y: float = 0, font: Optional[Font] = None, fill_color: Optional[Color] = None, outline_color: Optional[Color] = None, outline: float = 0, - on_click: Optional[Callable] = None) -> None: ... - + click: Optional[Callable] = None) -> None: ... + text: str font: Font fill_color: Color outline_color: Color outline: float - on_click: Optional[Callable[[float, float, int], None]] - on_enter: Optional[Callable[[], None]] - on_exit: Optional[Callable[[], None]] - on_move: Optional[Callable[[float, float], None]] + click: Optional[Callable[[float, float, int], None]] w: float # Read-only, computed from text h: float # Read-only, computed from text class Sprite(Drawable): - """Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, on_click=None) - + """Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None) + A sprite UI element that displays a texture or portion of a texture atlas. """ - + @overload def __init__(self) -> None: ... @overload def __init__(self, x: float = 0, y: float = 0, texture: Optional[Texture] = None, sprite_index: int = 0, scale: float = 1.0, - on_click: Optional[Callable] = None) -> None: ... - + click: Optional[Callable] = None) -> None: ... + texture: Texture sprite_index: int scale: float - on_click: Optional[Callable[[float, float, int], None]] - on_enter: Optional[Callable[[], None]] - on_exit: Optional[Callable[[], None]] - on_move: Optional[Callable[[float, float], None]] + click: Optional[Callable[[float, float, int], None]] w: float # Read-only, computed from texture h: float # Read-only, computed from texture class Grid(Drawable): - """Grid(pos=None, size=None, grid_size=(20, 20), texture=None, on_click=None, layers=None) - + """Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None) + A grid-based tilemap UI element for rendering tile-based levels and game worlds. - Supports multiple rendering layers (ColorLayer, TileLayer) and entity management. """ - + @overload def __init__(self) -> None: ... @overload - def __init__(self, pos: Optional[Tuple[float, float]] = None, - size: Optional[Tuple[float, float]] = None, - grid_size: Tuple[int, int] = (20, 20), - texture: Optional[Texture] = None, - on_click: Optional[Callable] = None, - layers: Optional[Dict[str, str]] = None) -> None: ... - + def __init__(self, x: float = 0, y: float = 0, grid_size: Tuple[int, int] = (20, 20), + texture: Optional[Texture] = None, tile_width: int = 16, tile_height: int = 16, + scale: float = 1.0, click: Optional[Callable] = None) -> None: ... + grid_size: Tuple[int, int] - grid_x: int # Read-only grid width - grid_y: int # Read-only grid height + tile_width: int + tile_height: int texture: Texture - zoom: float - center: Tuple[float, float] - center_x: float - center_y: float - fill_color: Color + scale: float + points: List[List['GridPoint']] entities: 'EntityCollection' - children: 'UICollection' - layers: List[Union['ColorLayer', 'TileLayer']] - hovered_cell: Optional[Tuple[int, int]] - - # Mouse event handlers - on_click: Optional[Callable[[float, float, int], None]] - on_enter: Optional[Callable[[], None]] - on_exit: Optional[Callable[[], None]] - on_move: Optional[Callable[[float, float], None]] - - # Grid cell event handlers - on_cell_click: Optional[Callable[[int, int], None]] - on_cell_enter: Optional[Callable[[int, int], None]] - on_cell_exit: Optional[Callable[[int, int], None]] - + background_color: Color + click: Optional[Callable[[int, int, int], None]] + def at(self, x: int, y: int) -> 'GridPoint': """Get grid point at tile coordinates.""" ... - def add_layer(self, type: str, z_index: int = -1, - texture: Optional[Texture] = None) -> Union['ColorLayer', 'TileLayer']: - """Add a rendering layer. type='color' or 'tile'. z_index<0 = below entities.""" - ... - - def remove_layer(self, layer: Union['ColorLayer', 'TileLayer']) -> None: - """Remove a layer from the grid.""" - ... - - def compute_fov(self, x: int, y: int, radius: int) -> None: - """Compute field of view from a position.""" - ... - - def is_in_fov(self, x: int, y: int) -> bool: - """Check if a cell is visible in the current FOV.""" - ... - - def compute_dijkstra(self, sources: List[Tuple[int, int]], max_cost: float = -1) -> None: - """Compute Dijkstra distance map from source points.""" - ... - - def get_dijkstra_distance(self, x: int, y: int) -> float: - """Get distance to nearest source for a cell.""" - ... - - def get_dijkstra_path(self, x: int, y: int) -> List[Tuple[int, int]]: - """Get path from cell to nearest source.""" - ... - - def find_path(self, x1: int, y1: int, x2: int, y2: int) -> List[Tuple[int, int]]: - """Find A* path between two cells.""" - ... - class GridPoint: """Grid point representing a single tile.""" @@ -258,49 +197,10 @@ class GridPoint: class GridPointState: """State information for a grid point.""" - + texture_index: int color: Color -class ColorLayer: - """Grid layer that renders solid colors per cell.""" - - z_index: int - visible: bool - grid_size: Tuple[int, int] - - def fill(self, color: Color) -> None: - """Fill all cells with a color.""" - ... - - def set(self, x: int, y: int, color: Color) -> None: - """Set color at a specific cell.""" - ... - - def at(self, x: int, y: int) -> Color: - """Get color at a specific cell.""" - ... - -class TileLayer: - """Grid layer that renders texture tiles per cell.""" - - z_index: int - visible: bool - texture: Texture - grid_size: Tuple[int, int] - - def fill(self, sprite_index: int) -> None: - """Fill all cells with a sprite index.""" - ... - - def set(self, x: int, y: int, sprite_index: int) -> None: - """Set tile sprite at a specific cell.""" - ... - - def at(self, x: int, y: int) -> int: - """Get tile sprite index at a specific cell.""" - ... - class Entity(Drawable): """Entity(grid_x=0, grid_y=0, texture=None, sprite_index=0, name='') diff --git a/tests/demo/screens/grid_demo.py b/tests/demo/screens/grid_demo.py index 434c139..1797885 100644 --- a/tests/demo/screens/grid_demo.py +++ b/tests/demo/screens/grid_demo.py @@ -8,33 +8,29 @@ class GridDemo(DemoScreen): def setup(self): self.add_title("Grid System") - self.add_description("Multi-layer rendering with camera, zoom, and children support") + self.add_description("Tile-based rendering with camera, zoom, and children support") - # Create a grid with no default layers - grid = mcrfpy.Grid(grid_size=(15, 10), pos=(50, 120), size=(400, 280), layers={}) + # Create a grid + grid = mcrfpy.Grid(grid_size=(15, 10), pos=(50, 120), size=(400, 280)) grid.fill_color = mcrfpy.Color(20, 20, 40) - - # Add a color layer for the checkerboard pattern (z_index=-1 = below entities) - color_layer = grid.add_layer("color", z_index=-1) - # Center camera on middle of grid (in pixel coordinates: cells * cell_size / 2) # For 15x10 grid with 16x16 cells: center = (15*16/2, 10*16/2) = (120, 80) grid.center = (120, 80) self.ui.append(grid) - # Set tile colors via the color layer to create a pattern + # Set some tile colors to create a pattern for x in range(15): for y in range(10): point = grid.at(x, y) # Checkerboard pattern if (x + y) % 2 == 0: - color_layer.set(x, y, mcrfpy.Color(40, 40, 60)) + point.color = mcrfpy.Color(40, 40, 60) else: - color_layer.set(x, y, mcrfpy.Color(30, 30, 50)) + point.color = mcrfpy.Color(30, 30, 50) # Border if x == 0 or x == 14 or y == 0 or y == 9: - color_layer.set(x, y, mcrfpy.Color(80, 60, 40)) + point.color = mcrfpy.Color(80, 60, 40) point.walkable = False # Add some children to the grid @@ -57,12 +53,13 @@ class GridDemo(DemoScreen): props = [ "grid_size: (15, 10)", - "layers: [ColorLayer]", + "zoom: 1.0", "center: (120, 80)", + "fill_color: dark blue", "", "Features:", - "- Multi-layer rendering", "- Camera pan/zoom", + "- Tile colors", "- Children collection", "- FOV/pathfinding", ] @@ -72,9 +69,8 @@ class GridDemo(DemoScreen): info.children.append(cap) # Code example - code = """# Grid with layers -grid = mcrfpy.Grid(grid_size=(20, 15), pos=(50, 50), size=(320, 240), layers={}) -layer = grid.add_layer("color", z_index=-1) # Below entities -layer.set(5, 5, mcrfpy.Color(255, 0, 0)) # Red tile + code = """# Grid with children +grid = mcrfpy.Grid(grid_size=(20, 15), pos=(50, 50), size=(320, 240)) +grid.at(5, 5).color = mcrfpy.Color(255, 0, 0) # Red tile grid.children.append(mcrfpy.Caption("Label", pos=(80, 48)))""" self.add_code_example(code, y=420) diff --git a/tests/demo/screenshots/demo_03_grid_system.png b/tests/demo/screenshots/demo_03_grid_system.png index 3f24695..1488001 100644 Binary files a/tests/demo/screenshots/demo_03_grid_system.png and b/tests/demo/screenshots/demo_03_grid_system.png differ diff --git a/tests/demo/screenshots/demo_04_animation_system.png b/tests/demo/screenshots/demo_04_animation_system.png index 123d05c..08bb883 100644 Binary files a/tests/demo/screenshots/demo_04_animation_system.png and b/tests/demo/screenshots/demo_04_animation_system.png differ diff --git a/tests/vllm_demo/0_basic_vllm_demo.py b/tests/vllm_demo/0_basic_vllm_demo.py deleted file mode 100644 index bf37fa4..0000000 --- a/tests/vllm_demo/0_basic_vllm_demo.py +++ /dev/null @@ -1,293 +0,0 @@ -#!/usr/bin/env python3 -""" -VLLM Integration Demo for McRogueFace -===================================== - -Demonstrates using a local Vision-Language Model (Gemma 3) with -McRogueFace headless rendering to create an AI-driven agent. - -Requirements: -- Local VLLM running at http://192.168.1.100:8100 -- McRogueFace built with headless mode support - -This is a research-grade demo for issue #156. -""" - -import mcrfpy -from mcrfpy import automation -import sys -import requests -import base64 -import os -import random - -# VLLM configuration -VLLM_URL = "http://192.168.1.100:8100/v1/chat/completions" -SCREENSHOT_PATH = "/tmp/vllm_demo_screenshot.png" - -# Sprite constants from Crypt of Sokoban tileset -FLOOR_COMMON = 0 # 95% of floors -FLOOR_SPECKLE1 = 12 # 4% of floors -FLOOR_SPECKLE2 = 24 # 1% of floors -WALL_TILE = 40 # Wall sprite -PLAYER_SPRITE = 84 # Player character -RAT_SPRITE = 123 # Enemy/rat creature - -def file_to_base64(file_path): - """Convert any image file to base64 string.""" - with open(file_path, 'rb') as f: - return base64.b64encode(f.read()).decode('utf-8') - -def llm_chat_completion(messages: list): - """Chat completion endpoint of local LLM""" - try: - response = requests.post(VLLM_URL, json={'messages': messages}, timeout=60) - return response.json() - except requests.exceptions.RequestException as e: - return {"error": str(e)} - -def message_with_image(text, image_path): - """Create a message with an embedded image for vision models.""" - image_data = file_to_base64(image_path) - return { - "role": "user", - "content": [ - {"type": "text", "text": text}, - {"type": "image_url", "image_url": {"url": "data:image/png;base64," + image_data}} - ] - } - -def get_floor_tile(): - """Return a floor tile sprite with realistic distribution.""" - roll = random.random() - if roll < 0.95: - return FLOOR_COMMON - elif roll < 0.99: - return FLOOR_SPECKLE1 - else: - return FLOOR_SPECKLE2 - -def setup_scene(): - """Create a dungeon scene with player agent and NPC rat.""" - print("Setting up scene...") - - # Create and set scene - mcrfpy.createScene("vllm_demo") - mcrfpy.setScene("vllm_demo") - ui = mcrfpy.sceneUI("vllm_demo") - - # Load the game texture (16x16 tiles from Crypt of Sokoban) - texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) - - # Create grid: 1014px wide at position (5,5) - # Using 20x15 grid for a reasonable dungeon size - grid = mcrfpy.Grid( - grid_size=(20, 15), - texture=texture, - pos=(5, 5), - size=(1014, 700) - ) - grid.fill_color = mcrfpy.Color(20, 20, 30) - - # Set zoom factor to 2.0 for better visibility - grid.zoom = 2.0 - - ui.append(grid) - - # Set up floor tiles and walls with proper sprite distribution - for x in range(20): - for y in range(15): - point = grid.at(x, y) - # Create walls around the edges - if x == 0 or x == 19 or y == 0 or y == 14: - point.tilesprite = WALL_TILE - point.walkable = False - point.transparent = False # Walls block FOV - else: - # Floor inside with varied sprites - point.tilesprite = get_floor_tile() - point.walkable = True - point.transparent = True # Floors don't block FOV - - # Add some interior walls for interest - a room divider - for y in range(5, 10): - point = grid.at(10, y) - point.tilesprite = WALL_TILE - point.walkable = False - point.transparent = False - # Door opening - door = grid.at(10, 7) - door.tilesprite = get_floor_tile() - door.walkable = True - door.transparent = True - - # Create a ColorLayer for fog of war (z_index=10 to render on top) - fov_layer = grid.add_layer('color', z_index=10) - fov_layer.fill(mcrfpy.Color(0, 0, 0, 255)) # Start all black (unknown) - - # Create the player entity ("The Agent") - player = mcrfpy.Entity(grid_pos=(5, 7), texture=texture, sprite_index=PLAYER_SPRITE) - grid.entities.append(player) - - # Create an NPC rat entity (closer so it's visible in FOV) - rat = mcrfpy.Entity(grid_pos=(10, 7), texture=texture, sprite_index=RAT_SPRITE) - grid.entities.append(rat) - - # Bind the fog layer to player's perspective - # visible = transparent, discovered = dim, unknown = black - fov_layer.apply_perspective( - entity=player, - visible=mcrfpy.Color(0, 0, 0, 0), # Transparent when visible - discovered=mcrfpy.Color(40, 40, 60, 180), # Dark overlay when discovered but not visible - unknown=mcrfpy.Color(0, 0, 0, 255) # Black when never seen - ) - - # Update visibility from player's position - player.update_visibility() - - # Center the camera on the agent entity - px, py = int(player.pos[0]), int(player.pos[1]) - grid.center = (px * 16 + 8, py * 16 + 8) - - return grid, player, rat - -def check_entity_visible(grid, entity): - """Check if an entity is within the current FOV.""" - ex, ey = int(entity.pos[0]), int(entity.pos[1]) - return grid.is_in_fov(ex, ey) - -def build_grounded_prompt(grid, player, rat): - """Build a text prompt with visually grounded information.""" - observations = [] - - # Check what the agent can see - if check_entity_visible(grid, rat): - observations.append("You see a rat to the east.") - - # Could add more observations here: - # - walls blocking path - # - items on ground - # - doors/exits - - if not observations: - observations.append("The area appears clear.") - - return " ".join(observations) - -def run_demo(): - """Main demo function.""" - print("=" * 60) - print("VLLM Integration Demo (Research Mode)") - print("=" * 60) - print() - - # Setup the scene - grid, player, rat = setup_scene() - - # Advance simulation to ensure scene is ready - mcrfpy.step(0.016) - - # Take screenshot - print(f"Taking screenshot: {SCREENSHOT_PATH}") - result = automation.screenshot(SCREENSHOT_PATH) - if not result: - print("ERROR: Failed to take screenshot") - return False - - file_size = os.path.getsize(SCREENSHOT_PATH) - print(f"Screenshot saved: {file_size} bytes") - print() - - # Build grounded observations - grounded_text = build_grounded_prompt(grid, player, rat) - print(f"Grounded observations: {grounded_text}") - print() - - # Query 1: Ask VLLM to describe what it sees - print("-" * 40) - print("Query 1: Describe what you see") - print("-" * 40) - - system_prompt = """You are an AI agent in a roguelike dungeon game. You can see the game world through screenshots. -The view shows a top-down grid-based dungeon with tiles, walls, and creatures. -Your character is the humanoid figure. The dark areas are outside your field of vision. -Other creatures may be enemies or NPCs. Describe what you observe concisely.""" - - user_prompt = f"""Look at this game screenshot. {grounded_text} - -Describe what you see in the dungeon from your character's perspective. -Be specific about: -- Your position in the room -- Any creatures you can see -- The layout of walls and passages -- Areas obscured by fog of war (darkness)""" - - messages = [ - {"role": "system", "content": system_prompt}, - message_with_image(user_prompt, SCREENSHOT_PATH) - ] - - resp = llm_chat_completion(messages) - - if "error" in resp: - print(f"VLLM Error: {resp['error']}") - print("\nNote: The VLLM server may not be running or accessible.") - print("Screenshot is saved for manual inspection.") - description = "I can see a dungeon scene." - else: - description = resp.get('choices', [{}])[0].get('message', {}).get('content', 'No response') - print(f"\nVLLM Response:\n{description}") - print() - - # Query 2: Ask what action the agent would like to take - print("-" * 40) - print("Query 2: What would you like to do?") - print("-" * 40) - - messages.append({"role": "assistant", "content": description}) - messages.append({ - "role": "user", - "content": f"""Based on what you see, what action would you like to take? - -Available actions: -- GO NORTH / SOUTH / EAST / WEST - move in that direction -- WAIT - stay in place and observe -- LOOK - examine your surroundings more carefully - -{grounded_text} - -State your reasoning briefly, then declare your action clearly (e.g., "Action: GO EAST").""" - }) - - resp = llm_chat_completion(messages) - - if "error" in resp: - print(f"VLLM Error: {resp['error']}") - else: - action = resp.get('choices', [{}])[0].get('message', {}).get('content', 'No response') - print(f"\nVLLM Response:\n{action}") - print() - - print("=" * 60) - print("Demo Complete") - print("=" * 60) - print(f"\nScreenshot preserved at: {SCREENSHOT_PATH}") - print("Grid settings: zoom=2.0, FOV radius=8, perspective rendering enabled") - - return True - -# Main execution -if __name__ == "__main__": - try: - success = run_demo() - if success: - print("\nPASS") - sys.exit(0) - else: - print("\nFAIL") - sys.exit(1) - except Exception as e: - print(f"\nError: {e}") - import traceback - traceback.print_exc() - sys.exit(1) diff --git a/tests/vllm_demo/1_multi_agent_demo.py b/tests/vllm_demo/1_multi_agent_demo.py deleted file mode 100644 index 50e06fb..0000000 --- a/tests/vllm_demo/1_multi_agent_demo.py +++ /dev/null @@ -1,346 +0,0 @@ -#!/usr/bin/env python3 -""" -Multi-Agent VLLM Demo for McRogueFace -===================================== - -Demonstrates cycling through multiple agent perspectives, -each with their own FOV and grounded observations. - -Three agents: -- Wizard (left side) - can see the rat but not the other agents -- Blacksmith (right side) - can see the knight, rat, and the wall -- Knight (right side) - can see the blacksmith, rat, and the wall - -Each agent gets their own screenshot and VLLM query. -""" - -import mcrfpy -from mcrfpy import automation -import sys -import requests -import base64 -import os -import random - -# VLLM configuration -VLLM_URL = "http://192.168.1.100:8100/v1/chat/completions" -SCREENSHOT_DIR = "/tmp/vllm_multi_agent" - -# Sprite constants -FLOOR_COMMON = 0 -FLOOR_SPECKLE1 = 12 -FLOOR_SPECKLE2 = 24 -WALL_TILE = 40 - -# Agent sprites -WIZARD_SPRITE = 84 -BLACKSMITH_SPRITE = 86 -KNIGHT_SPRITE = 96 -RAT_SPRITE = 123 - - -def file_to_base64(file_path): - """Convert any image file to base64 string.""" - with open(file_path, 'rb') as f: - return base64.b64encode(f.read()).decode('utf-8') - - -def llm_chat_completion(messages: list): - """Chat completion endpoint of local LLM""" - try: - response = requests.post(VLLM_URL, json={'messages': messages}, timeout=60) - return response.json() - except requests.exceptions.RequestException as e: - return {"error": str(e)} - - -def message_with_image(text, image_path): - """Create a message with an embedded image for vision models.""" - image_data = file_to_base64(image_path) - return { - "role": "user", - "content": [ - {"type": "text", "text": text}, - {"type": "image_url", "image_url": {"url": "data:image/png;base64," + image_data}} - ] - } - - -def get_floor_tile(): - """Return a floor tile sprite with realistic distribution.""" - roll = random.random() - if roll < 0.95: - return FLOOR_COMMON - elif roll < 0.99: - return FLOOR_SPECKLE1 - else: - return FLOOR_SPECKLE2 - - -class Agent: - """Wrapper for an agent entity with metadata.""" - def __init__(self, name, entity, description): - self.name = name - self.entity = entity - self.description = description # e.g., "a wizard", "a blacksmith" - - @property - def pos(self): - return (int(self.entity.pos[0]), int(self.entity.pos[1])) - - -def setup_scene(): - """Create a dungeon scene with multiple agents.""" - print("Setting up multi-agent scene...") - - # Create and set scene - mcrfpy.createScene("multi_agent_demo") - mcrfpy.setScene("multi_agent_demo") - ui = mcrfpy.sceneUI("multi_agent_demo") - - # Load the game texture - texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) - - # Create grid - grid = mcrfpy.Grid( - grid_size=(25, 15), - texture=texture, - pos=(5, 5), - size=(1014, 700) - ) - grid.fill_color = mcrfpy.Color(20, 20, 30) - grid.zoom = 2.0 - ui.append(grid) - - # Set up floor tiles and walls - for x in range(25): - for y in range(15): - point = grid.at(x, y) - if x == 0 or x == 24 or y == 0 or y == 14: - point.tilesprite = WALL_TILE - point.walkable = False - point.transparent = False - else: - point.tilesprite = get_floor_tile() - point.walkable = True - point.transparent = True - - # Add a wall divider in the middle (blocks wizard's view of right side) - for y in range(3, 12): - point = grid.at(10, y) - point.tilesprite = WALL_TILE - point.walkable = False - point.transparent = False - - # Door opening in the wall - door = grid.at(10, 7) - door.tilesprite = get_floor_tile() - door.walkable = True - door.transparent = True - - # Create FOV layer for fog of war - fov_layer = grid.add_layer('color', z_index=10) - fov_layer.fill(mcrfpy.Color(0, 0, 0, 255)) - - # Create agents - agents = [] - - # Wizard on the left side - wizard_entity = mcrfpy.Entity(grid_pos=(4, 7), texture=texture, sprite_index=WIZARD_SPRITE) - grid.entities.append(wizard_entity) - agents.append(Agent("Wizard", wizard_entity, "a wizard")) - - # Blacksmith on the right side (upper) - blacksmith_entity = mcrfpy.Entity(grid_pos=(18, 5), texture=texture, sprite_index=BLACKSMITH_SPRITE) - grid.entities.append(blacksmith_entity) - agents.append(Agent("Blacksmith", blacksmith_entity, "a blacksmith")) - - # Knight on the right side (lower) - knight_entity = mcrfpy.Entity(grid_pos=(18, 10), texture=texture, sprite_index=KNIGHT_SPRITE) - grid.entities.append(knight_entity) - agents.append(Agent("Knight", knight_entity, "a knight")) - - # Rat in the middle-right area (visible to blacksmith and knight, maybe wizard through door) - rat_entity = mcrfpy.Entity(grid_pos=(14, 7), texture=texture, sprite_index=RAT_SPRITE) - grid.entities.append(rat_entity) - - return grid, fov_layer, agents, rat_entity - - -def switch_perspective(grid, fov_layer, agent): - """Switch the grid view to an agent's perspective.""" - # Reset fog layer to all unknown (black) before switching - # This prevents discovered tiles from one agent carrying over to another - fov_layer.fill(mcrfpy.Color(0, 0, 0, 255)) - - # Apply this agent's perspective - fov_layer.apply_perspective( - entity=agent.entity, - visible=mcrfpy.Color(0, 0, 0, 0), - discovered=mcrfpy.Color(40, 40, 60, 180), - unknown=mcrfpy.Color(0, 0, 0, 255) - ) - - # Update visibility from agent's position - agent.entity.update_visibility() - - # Center camera on this agent - px, py = agent.pos - grid.center = (px * 16 + 8, py * 16 + 8) - - -def get_visible_entities(grid, observer, all_agents, rat): - """Get list of entities visible to the observer.""" - visible = [] - ox, oy = observer.pos - - # Check rat visibility - rx, ry = int(rat.pos[0]), int(rat.pos[1]) - if grid.is_in_fov(rx, ry): - # Determine direction - direction = get_direction(ox, oy, rx, ry) - visible.append(f"a rat to the {direction}") - - # Check other agents - for agent in all_agents: - if agent.name == observer.name: - continue - ax, ay = agent.pos - if grid.is_in_fov(ax, ay): - direction = get_direction(ox, oy, ax, ay) - visible.append(f"{agent.description} to the {direction}") - - return visible - - -def get_direction(from_x, from_y, to_x, to_y): - """Get cardinal direction from one point to another.""" - dx = to_x - from_x - dy = to_y - from_y - - # Primary direction - if abs(dx) > abs(dy): - return "east" if dx > 0 else "west" - elif abs(dy) > abs(dx): - return "south" if dy > 0 else "north" - else: - # Diagonal - pick one - ns = "south" if dy > 0 else "north" - ew = "east" if dx > 0 else "west" - return f"{ns}{ew}" - - -def build_grounded_prompt(visible_entities): - """Build grounded text from visible entities.""" - if not visible_entities: - return "The area appears clear." - - if len(visible_entities) == 1: - return f"You see {visible_entities[0]}." - else: - items = ", ".join(visible_entities[:-1]) + f" and {visible_entities[-1]}" - return f"You see {items}." - - -def query_agent(agent, screenshot_path, grounded_text): - """Query VLLM for a single agent's perspective.""" - system_prompt = f"""You are {agent.description} in a roguelike dungeon game. You can see the game world through screenshots. -The view shows a top-down grid-based dungeon. Your character is centered in the view. -The dark areas are outside your field of vision. Other figures may be allies, enemies, or NPCs. -Describe what you observe concisely and decide on an action.""" - - user_prompt = f"""Look at this game screenshot from your perspective as {agent.description}. {grounded_text} - -Describe what you see briefly, then choose an action: -- GO NORTH / SOUTH / EAST / WEST -- WAIT -- LOOK - -State your reasoning in 1-2 sentences, then declare: "Action: