Cleanup: mcrfpy.libtcod #215

Closed
opened 2026-01-15 02:09:01 +00:00 by john · 3 comments
Owner

Research spike story

mcrfpy.libtcod.line

    line(...)
        line(x1, y1, x2, y2)

        Get cells along a line using Bresenham's algorithm.

Should accept mcrfpy.Vector, tuple, or list - not separate X and Y values.

Should return tuple(mcrfpy.Vector).

mcrfpy.libtcod.compute_fov

    compute_fov(...)
        compute_fov(grid, x, y, radius, light_walls=True, algorithm=mcrfpy.FOV.BASIC)

Should be a grid method and take tuple/list/mcrfpy.Vector for a position.

Other missing methods

libtcod.line is useful because it's a list of points. They probably apply to a grid, but it's just (x,y) positions. It would also apply to heightmap positions now.

What else does libtcod have that we should expose? HeightMap, BSP, and NoiseSource cover a lot of the procedural generation. The pathfinding is largely moved to Grid's methods. Should we import markov chains for text and other utility functions, for completeness? These capabilities are already in the libraries we ship.

# Research spike story ## mcrfpy.libtcod.line ``` line(...) line(x1, y1, x2, y2) Get cells along a line using Bresenham's algorithm. ``` Should accept mcrfpy.Vector, tuple, or list - not separate X and Y values. Should return tuple(mcrfpy.Vector). ## mcrfpy.libtcod.compute_fov ``` compute_fov(...) compute_fov(grid, x, y, radius, light_walls=True, algorithm=mcrfpy.FOV.BASIC) ``` Should be a grid method and take tuple/list/mcrfpy.Vector for a position. ## Other missing methods libtcod.line is useful because it's a list of points. They probably apply to a grid, but it's just (x,y) positions. It would also apply to heightmap positions now. What else does libtcod have that we should expose? HeightMap, BSP, and NoiseSource cover a lot of the procedural generation. The pathfinding is largely moved to Grid's methods. Should we import markov chains for text and other utility functions, for completeness? These capabilities are already in the libraries we ship.
Author
Owner

libtcod API Exposure Research Complete

Full research documents created in docs/libtcod_research/:

Summary

Already Covered (no action needed):

  • BSP, HeightMap, NoiseSource, FOV, Pathfinding, Colors

High Priority (implement for 7DRL):

  1. RandomSource - Critical for reproducible procedural generation. Same seed should produce identical dungeons across BSP/Noise/game logic.
  2. Line Enhancement - Accept Vector/tuple/Entity inputs, add grid integration for LOS checks, endpoint control

Medium Priority (polish features):
3. NameGenerator - Syllable-based name generation for NPCs, places, items
4. Image Toolkit - Load images and convert to Grid/HeightMap data

Low Priority:
5. Clipboard - Use SFML's clipboard API (not libtcod's deprecated SDL-based one)

Skip:

  • Config Parser - Use Python's TOML instead

Estimated Implementation Time

Based on existing NoiseSource/BSP/HeightMap patterns:

  • RandomSource: 30-60 minutes
  • Line Enhancement: 30-60 minutes
  • NameGenerator: 30-60 minutes
  • Image Toolkit: 1-2 hours
  • Clipboard: 15-30 minutes

Total: ~3-5 hours for everything

Files Created

docs/libtcod_research/
├── 00_SUMMARY.md          # Executive summary with priorities
├── 01_clipboard.md        # System layer / clipboard
├── 02_line_drawing.md     # Line algorithm enhancements
├── 03_image_toolkit.md    # Image loading and conversion
├── 04_random_generator.md # Pseudorandom number generation
├── 05_config_parser.md    # Config file parsing (skip)
└── 06_name_generator.md   # Procedural name generation
## libtcod API Exposure Research Complete Full research documents created in `docs/libtcod_research/`: ### Summary **Already Covered** (no action needed): - BSP, HeightMap, NoiseSource, FOV, Pathfinding, Colors **High Priority** (implement for 7DRL): 1. **RandomSource** - Critical for reproducible procedural generation. Same seed should produce identical dungeons across BSP/Noise/game logic. 2. **Line Enhancement** - Accept Vector/tuple/Entity inputs, add grid integration for LOS checks, endpoint control **Medium Priority** (polish features): 3. **NameGenerator** - Syllable-based name generation for NPCs, places, items 4. **Image Toolkit** - Load images and convert to Grid/HeightMap data **Low Priority**: 5. **Clipboard** - Use SFML's clipboard API (not libtcod's deprecated SDL-based one) **Skip**: - Config Parser - Use Python's TOML instead ### Estimated Implementation Time Based on existing NoiseSource/BSP/HeightMap patterns: - RandomSource: 30-60 minutes - Line Enhancement: 30-60 minutes - NameGenerator: 30-60 minutes - Image Toolkit: 1-2 hours - Clipboard: 15-30 minutes **Total: ~3-5 hours for everything** ### Files Created ``` docs/libtcod_research/ ├── 00_SUMMARY.md # Executive summary with priorities ├── 01_clipboard.md # System layer / clipboard ├── 02_line_drawing.md # Line algorithm enhancements ├── 03_image_toolkit.md # Image loading and conversion ├── 04_random_generator.md # Pseudorandom number generation ├── 05_config_parser.md # Config file parsing (skip) └── 06_name_generator.md # Procedural name generation ```
Author
Owner

Line Enhancement Analysis

Existing APIs for Comparison

Grid Methods:

grid.compute_fov(x, y, radius, light_walls=True, algorithm=FOV.BASIC)
grid.is_in_fov(x, y) -> bool
grid.find_path(start, end) -> AStarPath | None  # Uses ExtractPosition()
grid.get_dijkstra_map(root) -> DijkstraMap
grid.entities_in_radius(pos, radius) -> list[Entity]

Entity Methods:

entity.path_to(x, y)              # Flexible position parsing
entity.path_to((x, y))            # tuple
entity.path_to(pos=(x, y))        # keyword -> list[(x, y)]
entity.visible_entities(fov=None, radius=None) -> list[Entity]
entity.at(x, y) -> GridPointState

Current Line:

mcrfpy.libtcod.line(x1, y1, x2, y2) -> list[(x, y)]  # Only integers!

Question 1: API Style Consistency

The original research doc proposed:

  • line(player, enemy) - Accepting raw Entity objects
  • line(..., grid=grid, filter='transparent') - String-based filtering
  • has_line_of_sight(a, b, grid=grid) - Convenience wrapper

Problems:

  • No other McRogueFace method accepts Entity objects directly for positions
  • String-based filters are un-Pythonic; McRogueFace uses property access
  • has_line_of_sight duplicates FOV functionality

Recommendation: Use existing ExtractPosition() helper pattern:

mcrfpy.libtcod.line(start, end, *, include_start=True, include_end=True) -> list[tuple]
# start/end accept: (x, y), tuple, Vector - NOT Entity objects directly

Question 2: "Additional, Worse" Methods

Proposed Feature Verdict Reasoning
has_line_of_sight(a, b, grid) Worse Duplicates grid.compute_fov() + grid.is_in_fov()
line(..., grid=grid, filter='transparent') Worse FOV handles visibility correctly with shadow-casting
line(..., stop_on_obstacle=True) ⚠️ Overlap ONE valid use: projectile pathing
line_iter() lazy iterator Dubious Grid sizes ~200x200 max; ~1.6KB list is negligible
include_start=False Valid "cells between me and target, excluding myself"
include_end=False Valid "cells projectile passes through before hitting"

The Archery Use Case:

"Can an arrow fly from A to B?" is not an FOV question:

  • FOV: Shadow-casting, considers cell geometry, handles partial occlusion, returns an area
  • Line: Exact cells a perfectly straight projectile would traverse
# Check each cell along straight path
for x, y in mcrfpy.libtcod.line(archer.grid_pos, target.grid_pos):
    if not grid.at(x, y).transparent:
        return False  # Blocked
return True

A target could be "in FOV" but blocked by a narrow pillar. Or vice versa.


Question 3: Completeness

libtcod Feature Exposed? Justification
line(x1,y1,x2,y2) → list Yes Current API
init()/step() stateful No Python iteration is cleaner
line() with callback No Python for-loop + break is better
BresenhamLine iterator No List fine for typical grid sizes
.without_start() No Add via include_start=False
.without_end() No Add via include_end=False
.adjust_range() No Too esoteric; use Python slicing

Recommendation: Minimal Enhancement

Add only:

mcrfpy.libtcod.line(start, end, *, include_start=True, include_end=True) -> list[tuple]

Implementation:

  1. Use ExtractPosition() for flexible position parsing
  2. Add two boolean parameters for endpoint control
  3. Return list of tuples (unchanged)

Example - Archery:

def can_arrow_reach(archer, target, grid):
    """Check if straight arrow path is clear."""
    path = mcrfpy.libtcod.line(archer.grid_pos, target.grid_pos, include_start=False)
    for x, y in path:
        if not grid.at(x, y).transparent:
            return False
    return True

def get_projectile_hit_point(shooter, target, grid):
    """Find where projectile would hit."""
    path = mcrfpy.libtcod.line(shooter.grid_pos, target.grid_pos, include_start=False)
    for x, y in path:
        if not grid.at(x, y).transparent:
            return (x, y)  # Impact point
    return target.grid_pos  # Clear shot

What NOT to add:

  • grid= parameter (user checks cells manually - more explicit)
  • filter= parameter (string-based filtering is un-Pythonic)
  • has_line_of_sight() (use FOV instead)
  • line_iter() (memory savings not worth complexity)
  • Entity object support (inconsistent with rest of API)
## Line Enhancement Analysis ### Existing APIs for Comparison **Grid Methods:** ```python grid.compute_fov(x, y, radius, light_walls=True, algorithm=FOV.BASIC) grid.is_in_fov(x, y) -> bool grid.find_path(start, end) -> AStarPath | None # Uses ExtractPosition() grid.get_dijkstra_map(root) -> DijkstraMap grid.entities_in_radius(pos, radius) -> list[Entity] ``` **Entity Methods:** ```python entity.path_to(x, y) # Flexible position parsing entity.path_to((x, y)) # tuple entity.path_to(pos=(x, y)) # keyword -> list[(x, y)] entity.visible_entities(fov=None, radius=None) -> list[Entity] entity.at(x, y) -> GridPointState ``` **Current Line:** ```python mcrfpy.libtcod.line(x1, y1, x2, y2) -> list[(x, y)] # Only integers! ``` --- ### Question 1: API Style Consistency The original research doc proposed: - `line(player, enemy)` - Accepting raw Entity objects - `line(..., grid=grid, filter='transparent')` - String-based filtering - `has_line_of_sight(a, b, grid=grid)` - Convenience wrapper **Problems:** - No other McRogueFace method accepts Entity objects directly for positions - String-based filters are un-Pythonic; McRogueFace uses property access - `has_line_of_sight` duplicates FOV functionality **Recommendation:** Use existing `ExtractPosition()` helper pattern: ```python mcrfpy.libtcod.line(start, end, *, include_start=True, include_end=True) -> list[tuple] # start/end accept: (x, y), tuple, Vector - NOT Entity objects directly ``` --- ### Question 2: "Additional, Worse" Methods | Proposed Feature | Verdict | Reasoning | |------------------|---------|-----------| | `has_line_of_sight(a, b, grid)` | ❌ Worse | Duplicates `grid.compute_fov()` + `grid.is_in_fov()` | | `line(..., grid=grid, filter='transparent')` | ❌ Worse | FOV handles visibility correctly with shadow-casting | | `line(..., stop_on_obstacle=True)` | ⚠️ Overlap | ONE valid use: **projectile pathing** | | `line_iter()` lazy iterator | ❌ Dubious | Grid sizes ~200x200 max; ~1.6KB list is negligible | | `include_start=False` | ✅ Valid | "cells between me and target, excluding myself" | | `include_end=False` | ✅ Valid | "cells projectile passes through before hitting" | **The Archery Use Case:** "Can an arrow fly from A to B?" is **not** an FOV question: - **FOV**: Shadow-casting, considers cell geometry, handles partial occlusion, returns an *area* - **Line**: Exact cells a perfectly straight projectile would traverse ```python # Check each cell along straight path for x, y in mcrfpy.libtcod.line(archer.grid_pos, target.grid_pos): if not grid.at(x, y).transparent: return False # Blocked return True ``` A target could be "in FOV" but blocked by a narrow pillar. Or vice versa. --- ### Question 3: Completeness | libtcod Feature | Exposed? | Justification | |-----------------|----------|---------------| | `line(x1,y1,x2,y2)` → list | ✅ Yes | Current API | | `init()/step()` stateful | ❌ No | Python iteration is cleaner | | `line()` with callback | ❌ No | Python for-loop + break is better | | `BresenhamLine` iterator | ❌ No | List fine for typical grid sizes | | `.without_start()` | ❌ No | **Add via `include_start=False`** | | `.without_end()` | ❌ No | **Add via `include_end=False`** | | `.adjust_range()` | ❌ No | Too esoteric; use Python slicing | --- ## Recommendation: Minimal Enhancement **Add only:** ```python mcrfpy.libtcod.line(start, end, *, include_start=True, include_end=True) -> list[tuple] ``` **Implementation:** 1. Use `ExtractPosition()` for flexible position parsing 2. Add two boolean parameters for endpoint control 3. Return list of tuples (unchanged) **Example - Archery:** ```python def can_arrow_reach(archer, target, grid): """Check if straight arrow path is clear.""" path = mcrfpy.libtcod.line(archer.grid_pos, target.grid_pos, include_start=False) for x, y in path: if not grid.at(x, y).transparent: return False return True def get_projectile_hit_point(shooter, target, grid): """Find where projectile would hit.""" path = mcrfpy.libtcod.line(shooter.grid_pos, target.grid_pos, include_start=False) for x, y in path: if not grid.at(x, y).transparent: return (x, y) # Impact point return target.grid_pos # Clear shot ``` **What NOT to add:** - `grid=` parameter (user checks cells manually - more explicit) - `filter=` parameter (string-based filtering is un-Pythonic) - `has_line_of_sight()` (use FOV instead) - `line_iter()` (memory savings not worth complexity) - Entity object support (inconsistent with rest of API)
Author
Owner

Implementation Plan

Decision: Remove mcrfpy.libtcod namespace entirely. Expose enhanced line algorithm as top-level mcrfpy.bresenham().

Rationale:

  • libtcod.compute_fov already duplicated as grid.compute_fov() - redundant
  • Python's random module covers RandomSource use case (Mersenne Twister, serializable state)
  • NameGenerator can be added later without needing a namespace for just one function
  • "bresenham" is canonical, googleable, and concise

New API:

mcrfpy.bresenham(start, end, *, include_start=True, include_end=True) -> list[tuple[int, int]]
  • Uses ExtractPosition() for flexible input (tuple, Vector, list)
  • Returns list of (x, y) integer tuples
  • include_start=False for "cells between me and target, excluding myself"
  • include_end=False for "cells projectile passes through before hitting"

Example - Archery:

def can_arrow_reach(archer, target, grid):
    path = mcrfpy.bresenham(archer.grid_pos, target.grid_pos, include_start=False)
    for x, y in path:
        if not grid.at(x, y).transparent:
            return False
    return True

Implementation:

  1. Add mcrfpy.bresenham() to McRFPy_API.cpp
  2. Use existing Bresenham implementation from libtcod or reimplement (simple algorithm)
  3. Remove mcrfpy.libtcod submodule registration
  4. Delete McRFPy_Libtcod.cpp/h or repurpose
## Implementation Plan **Decision:** Remove `mcrfpy.libtcod` namespace entirely. Expose enhanced line algorithm as top-level `mcrfpy.bresenham()`. **Rationale:** - `libtcod.compute_fov` already duplicated as `grid.compute_fov()` - redundant - Python's `random` module covers RandomSource use case (Mersenne Twister, serializable state) - NameGenerator can be added later without needing a namespace for just one function - "bresenham" is canonical, googleable, and concise **New API:** ```python mcrfpy.bresenham(start, end, *, include_start=True, include_end=True) -> list[tuple[int, int]] ``` - Uses `ExtractPosition()` for flexible input (tuple, Vector, list) - Returns list of (x, y) integer tuples - `include_start=False` for "cells between me and target, excluding myself" - `include_end=False` for "cells projectile passes through before hitting" **Example - Archery:** ```python def can_arrow_reach(archer, target, grid): path = mcrfpy.bresenham(archer.grid_pos, target.grid_pos, include_start=False) for x, y in path: if not grid.at(x, y).transparent: return False return True ``` **Implementation:** 1. Add `mcrfpy.bresenham()` to McRFPy_API.cpp 2. Use existing Bresenham implementation from libtcod or reimplement (simple algorithm) 3. Remove `mcrfpy.libtcod` submodule registration 4. Delete McRFPy_Libtcod.cpp/h or repurpose
john closed this issue 2026-01-20 05:10:23 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
john/McRogueFace#215
No description provided.