Update all 13 tutorial scripts to current enum-based API, refs #167

All tutorial parts (1-13) used the old string-based key/action
comparison API removed in 6d5e99a. Every handle_keys function now
uses mcrfpy.Key.* and mcrfpy.InputState.PRESSED enums.

Additional fixes across all parts:
- Replace manual FOV computation with ColorLayer.draw_fov() which
  handles FOV calculation and explored-state tracking in one call
- Replace old grid.add_layer("color") with ColorLayer() constructor
- Fix entity removal bug: entities.remove(index) -> remove(entity_ref)
- Remove manual exploration tracking (draw_fov handles it internally)
- Use tuple positions for compute_fov/is_in_fov: (x, y) not x, y

All 14 parts (0-13) tested and passing in headless mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-04-09 23:23:35 -04:00
commit 1805b985bd
13 changed files with 388 additions and 765 deletions

View file

@ -75,30 +75,30 @@ pos_display.fill_color = mcrfpy.Color(200, 200, 100)
pos_display.font_size = 16 pos_display.font_size = 16
scene.children.append(pos_display) scene.children.append(pos_display)
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
"""Handle keyboard input to move the player. """Handle keyboard input to move the player.
Args: Args:
key: The key that was pressed (e.g., "W", "Up", "Space") key: A mcrfpy.Key enum value (e.g., Key.W, Key.UP)
action: Either "start" (key pressed) or "end" (key released) action: A mcrfpy.InputState enum value (PRESSED or RELEASED)
""" """
# Only respond to key press, not release # Only respond to key press, not release
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
# Get current player position # Get current player position
px, py = int(player.x), int(player.y) px, py = int(player.x), int(player.y)
# Calculate new position based on key # Calculate new position based on key
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
py -= 1 # Up decreases Y py -= 1 # Up decreases Y
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
py += 1 # Down increases Y py += 1 # Down increases Y
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
px -= 1 # Left decreases X px -= 1 # Left decreases X
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
px += 1 # Right increases X px += 1 # Right increases X
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
mcrfpy.exit() mcrfpy.exit()
return return

View file

@ -160,9 +160,9 @@ scene.children.append(status_display)
# Input Handling # Input Handling
# ============================================================================= # =============================================================================
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
"""Handle keyboard input with collision detection.""" """Handle keyboard input with collision detection."""
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
# Get current position # Get current position
@ -171,15 +171,15 @@ def handle_keys(key: str, action: str) -> None:
# Calculate intended new position # Calculate intended new position
new_x, new_y = px, py new_x, new_y = px, py
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
new_y -= 1 new_y -= 1
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
new_y += 1 new_y += 1
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
new_x -= 1 new_x -= 1
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
new_x += 1 new_x += 1
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
mcrfpy.exit() mcrfpy.exit()
return return
else: else:

View file

@ -316,26 +316,26 @@ def regenerate_dungeon() -> None:
pos_display.text = f"Position: ({new_x}, {new_y})" pos_display.text = f"Position: ({new_x}, {new_y})"
room_display.text = "New dungeon generated!" room_display.text = "New dungeon generated!"
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
"""Handle keyboard input.""" """Handle keyboard input."""
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
px, py = int(player.x), int(player.y) px, py = int(player.x), int(player.y)
new_x, new_y = px, py new_x, new_y = px, py
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
new_y -= 1 new_y -= 1
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
new_y += 1 new_y += 1
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
new_x -= 1 new_x -= 1
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
new_x += 1 new_x += 1
elif key == "R": elif key == mcrfpy.Key.R:
regenerate_dungeon() regenerate_dungeon()
return return
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
mcrfpy.exit() mcrfpy.exit()
return return
else: else:

View file

@ -71,24 +71,9 @@ class RectangularRoom:
# Exploration Tracking # Exploration Tracking
# ============================================================================= # =============================================================================
# Track which tiles have been discovered (seen at least once) # Note: The ColorLayer's draw_fov() method tracks exploration state
explored: list[list[bool]] = [] # internally - tiles that have been visible at least once are rendered
# with the 'discovered' color. No manual tracking needed!
def init_explored() -> None:
"""Initialize the explored array to all False."""
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
"""Mark a tile as explored."""
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
"""Check if a tile has been explored."""
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Dungeon Generation (from Part 3, with transparent property) # Dungeon Generation (from Part 3, with transparent property)
@ -151,7 +136,6 @@ def carve_l_tunnel(
def generate_dungeon(grid: mcrfpy.Grid) -> tuple[int, int]: def generate_dungeon(grid: mcrfpy.Grid) -> tuple[int, int]:
"""Generate a dungeon with rooms and tunnels.""" """Generate a dungeon with rooms and tunnels."""
fill_with_walls(grid) fill_with_walls(grid)
init_explored() # Reset exploration when generating new dungeon
rooms: list[RectangularRoom] = [] rooms: list[RectangularRoom] = []
@ -188,28 +172,25 @@ def generate_dungeon(grid: mcrfpy.Grid) -> tuple[int, int]:
def update_fov(grid: mcrfpy.Grid, fov_layer, player_x: int, player_y: int) -> None: def update_fov(grid: mcrfpy.Grid, fov_layer, player_x: int, player_y: int) -> None:
"""Update the field of view visualization. """Update the field of view visualization.
Uses the ColorLayer's built-in draw_fov() method, which computes FOV
via libtcod and paints visibility colors in a single call. The layer
tracks explored state automatically.
Args: Args:
grid: The game grid grid: The game grid
fov_layer: The ColorLayer for FOV visualization fov_layer: The ColorLayer for FOV visualization
player_x: Player's X position player_x: Player's X position
player_y: Player's Y position player_y: Player's Y position
""" """
# Compute FOV from player position # draw_fov computes FOV and paints the color layer in one step.
grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) # It tracks explored state internally so previously-seen tiles stay dimmed.
fov_layer.draw_fov(
# Update each tile's visibility (player_x, player_y),
for y in range(GRID_HEIGHT): radius=FOV_RADIUS,
for x in range(GRID_WIDTH): visible=COLOR_VISIBLE,
if grid.is_in_fov(x, y): discovered=COLOR_DISCOVERED,
# Currently visible - mark as explored and show clearly unknown=COLOR_UNKNOWN
mark_explored(x, y) )
fov_layer.set(x, y, COLOR_VISIBLE)
elif is_explored(x, y):
# Previously seen but not currently visible - show dimmed
fov_layer.set(x, y, COLOR_DISCOVERED)
else:
# Never seen - hide completely
fov_layer.set(x, y, COLOR_UNKNOWN)
# ============================================================================= # =============================================================================
# Collision Detection # Collision Detection
@ -244,12 +225,12 @@ grid = mcrfpy.Grid(
player_start_x, player_start_y = generate_dungeon(grid) player_start_x, player_start_y = generate_dungeon(grid)
# Add a color layer for FOV visualization (below entities) # Add a color layer for FOV visualization (below entities)
fov_layer = grid.add_layer("color", z_index=-1) # Create the layer object, then attach it to the grid
fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
grid.add_layer(fov_layer)
# Initialize the FOV layer to all black (unknown) # Initialize the FOV layer to all black (unknown)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
# Create the player # Create the player
player = mcrfpy.Entity( player = mcrfpy.Entity(
@ -312,34 +293,32 @@ def regenerate_dungeon() -> None:
player.y = new_y player.y = new_y
# Reset FOV layer to unknown # Reset FOV layer to unknown
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
# Calculate new FOV # Calculate new FOV
update_fov(grid, fov_layer, new_x, new_y) update_fov(grid, fov_layer, new_x, new_y)
pos_display.text = f"Position: ({new_x}, {new_y})" pos_display.text = f"Position: ({new_x}, {new_y})"
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
"""Handle keyboard input.""" """Handle keyboard input."""
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
px, py = int(player.x), int(player.y) px, py = int(player.x), int(player.y)
new_x, new_y = px, py new_x, new_y = px, py
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
new_y -= 1 new_y -= 1
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
new_y += 1 new_y += 1
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
new_x -= 1 new_x -= 1
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
new_x += 1 new_x += 1
elif key == "R": elif key == mcrfpy.Key.R:
regenerate_dungeon() regenerate_dungeon()
return return
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
mcrfpy.exit() mcrfpy.exit()
return return
else: else:

View file

@ -120,23 +120,8 @@ class RectangularRoom:
# Exploration Tracking (from Part 4) # Exploration Tracking (from Part 4)
# ============================================================================= # =============================================================================
explored: list[list[bool]] = [] # Note: The ColorLayer's draw_fov() method tracks exploration state
# internally - no manual tracking needed!
def init_explored() -> None:
"""Initialize the explored array to all False."""
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
"""Mark a tile as explored."""
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
"""Check if a tile has been explored."""
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Dungeon Generation (from Part 4) # Dungeon Generation (from Part 4)
@ -293,20 +278,15 @@ def clear_enemies(target_grid: mcrfpy.Grid) -> None:
"""Remove all enemies from the grid.""" """Remove all enemies from the grid."""
global entity_data global entity_data
# Get list of enemies to remove (not the player) # Collect enemies to remove (not the player)
enemies_to_remove = [] enemies_to_remove = []
for entity in target_grid.entities: for entity in target_grid.entities:
if entity in entity_data and not entity_data[entity].get("is_player", False): if entity in entity_data and not entity_data[entity].get("is_player", False):
enemies_to_remove.append(entity) enemies_to_remove.append(entity)
# Remove from grid and entity_data # Remove from grid (by entity reference) and from entity_data
for enemy in enemies_to_remove: for enemy in enemies_to_remove:
# Find and remove from grid.entities target_grid.entities.remove(enemy)
for i, e in enumerate(target_grid.entities):
if e == enemy:
target_grid.entities.remove(i)
break
# Remove from entity_data
if enemy in entity_data: if enemy in entity_data:
del entity_data[enemy] del entity_data[enemy]
@ -329,7 +309,7 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
# Other entities are only visible if in FOV # Other entities are only visible if in FOV
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
entity.visible = target_grid.is_in_fov(ex, ey) entity.visible = target_grid.is_in_fov((ex, ey))
# ============================================================================= # =============================================================================
# Field of View (from Part 4) # Field of View (from Part 4)
@ -337,21 +317,18 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
"""Update the field of view visualization.""" """Update the field of view visualization."""
# Compute FOV from player position # draw_fov computes FOV and paints the color layer in one step.
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) # It also tracks explored state internally.
target_fov_layer.draw_fov(
(player_x, player_y),
radius=FOV_RADIUS,
visible=COLOR_VISIBLE,
discovered=COLOR_DISCOVERED,
unknown=COLOR_UNKNOWN
)
# Update each tile's visibility # Update entity visibility (draw_fov calls compute_fov internally,
for y in range(GRID_HEIGHT): # so is_in_fov() reflects the current FOV state)
for x in range(GRID_WIDTH):
if target_grid.is_in_fov(x, y):
mark_explored(x, y)
target_fov_layer.set(x, y, COLOR_VISIBLE)
elif is_explored(x, y):
target_fov_layer.set(x, y, COLOR_DISCOVERED)
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
# Update entity visibility
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
# ============================================================================= # =============================================================================
@ -399,7 +376,6 @@ def generate_dungeon(target_grid: mcrfpy.Grid, texture: mcrfpy.Texture) -> tuple
# Fill with walls # Fill with walls
fill_with_walls(target_grid) fill_with_walls(target_grid)
init_explored()
rooms: list[RectangularRoom] = [] rooms: list[RectangularRoom] = []
@ -454,7 +430,6 @@ grid = mcrfpy.Grid(
# Generate the dungeon (without player first to get starting position) # Generate the dungeon (without player first to get starting position)
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
rooms: list[RectangularRoom] = [] rooms: list[RectangularRoom] = []
@ -489,10 +464,9 @@ else:
player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2 player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2
# Add FOV layer # Add FOV layer
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
# Create the player # Create the player
player = mcrfpy.Entity( player = mcrfpy.Entity(
@ -574,7 +548,6 @@ def regenerate_dungeon() -> None:
# Regenerate dungeon structure # Regenerate dungeon structure
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
rooms = [] rooms = []
@ -618,37 +591,35 @@ def regenerate_dungeon() -> None:
spawn_enemies_in_room(grid, room, texture) spawn_enemies_in_room(grid, room, texture)
# Reset FOV layer # Reset FOV layer
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
# Update FOV # Update FOV
update_fov(grid, fov_layer, new_x, new_y) update_fov(grid, fov_layer, new_x, new_y)
pos_display.text = f"Position: ({new_x}, {new_y})" pos_display.text = f"Position: ({new_x}, {new_y})"
status_display.text = "New dungeon generated!" status_display.text = "New dungeon generated!"
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
"""Handle keyboard input.""" """Handle keyboard input."""
global player, grid, fov_layer global player, grid, fov_layer
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
px, py = int(player.x), int(player.y) px, py = int(player.x), int(player.y)
new_x, new_y = px, py new_x, new_y = px, py
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
new_y -= 1 new_y -= 1
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
new_y += 1 new_y += 1
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
new_x -= 1 new_x -= 1
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
new_x += 1 new_x += 1
elif key == "R": elif key == mcrfpy.Key.R:
regenerate_dungeon() regenerate_dungeon()
return return
elif key == "Escape": elif key == mcrfpy.Key.ESCAPE:
mcrfpy.exit() mcrfpy.exit()
return return
else: else:

View file

@ -159,27 +159,7 @@ class RectangularRoom:
self.y2 >= other.y1 self.y2 >= other.y1
) )
# ============================================================================= # Exploration tracking is handled internally by draw_fov()
# Exploration Tracking
# =============================================================================
explored: list[list[bool]] = []
def init_explored() -> None:
"""Initialize the explored array to all False."""
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
"""Mark a tile as explored."""
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
"""Check if a tile has been explored."""
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Message Log # Message Log
@ -357,9 +337,9 @@ def get_blocking_entity_at(target_grid: mcrfpy.Grid, x: int, y: int, exclude: mc
def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
"""Remove an entity from the grid and data storage.""" """Remove an entity from the grid and data storage."""
# Find and remove from grid # Find and remove from grid
for i, e in enumerate(target_grid.entities): for e in target_grid.entities:
if e == entity: if e == entity:
target_grid.entities.remove(i) target_grid.entities.remove(e)
break break
# Remove from entity data # Remove from entity data
@ -487,21 +467,19 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
continue continue
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
entity.visible = target_grid.is_in_fov(ex, ey) entity.visible = target_grid.is_in_fov((ex, ey))
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
"""Update the field of view visualization.""" """Update the field of view visualization."""
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) # draw_fov computes FOV and paints the color layer in one step,
# and tracks exploration internally.
for y in range(GRID_HEIGHT): target_fov_layer.draw_fov(
for x in range(GRID_WIDTH): (player_x, player_y),
if target_grid.is_in_fov(x, y): radius=FOV_RADIUS,
mark_explored(x, y) visible=COLOR_VISIBLE,
target_fov_layer.set(x, y, COLOR_VISIBLE) discovered=COLOR_DISCOVERED,
elif is_explored(x, y): unknown=COLOR_UNKNOWN
target_fov_layer.set(x, y, COLOR_DISCOVERED) )
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
@ -595,7 +573,7 @@ def enemy_turn() -> None:
ex, ey = int(enemy.x), int(enemy.y) ex, ey = int(enemy.x), int(enemy.y)
# Only act if in player's FOV (aware of player) # Only act if in player's FOV (aware of player)
if not grid.is_in_fov(ex, ey): if not grid.is_in_fov((ex, ey)):
continue continue
# Check if adjacent to player # Check if adjacent to player
@ -691,7 +669,6 @@ grid = mcrfpy.Grid(
# Generate initial dungeon structure # Generate initial dungeon structure
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
rooms: list[RectangularRoom] = [] rooms: list[RectangularRoom] = []
@ -726,10 +703,9 @@ else:
player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2 player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2
# Add FOV layer # Add FOV layer
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
# Create the player # Create the player
player = mcrfpy.Entity( player = mcrfpy.Entity(
@ -826,11 +802,10 @@ def restart_game() -> None:
# Remove all entities from grid # Remove all entities from grid
while len(grid.entities) > 0: while len(grid.entities) > 0:
grid.entities.remove(0) grid.entities.remove(grid.entities[0])
# Regenerate dungeon # Regenerate dungeon
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
clear_messages() clear_messages()
rooms = [] rooms = []
@ -889,9 +864,7 @@ def restart_game() -> None:
spawn_enemies_in_room(grid, room, texture) spawn_enemies_in_room(grid, room, texture)
# Reset FOV layer # Reset FOV layer
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
# Update displays # Update displays
update_fov(grid, fov_layer, new_x, new_y) update_fov(grid, fov_layer, new_x, new_y)
@ -900,19 +873,19 @@ def restart_game() -> None:
add_message("A new adventure begins!", mcrfpy.Color(100, 100, 255)) add_message("A new adventure begins!", mcrfpy.Color(100, 100, 255))
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
"""Handle keyboard input.""" """Handle keyboard input."""
global game_over global game_over
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
# Handle restart # Handle restart
if key == "R": if key == mcrfpy.Key.R:
restart_game() restart_game()
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
mcrfpy.exit() mcrfpy.exit()
return return
@ -921,13 +894,13 @@ def handle_keys(key: str, action: str) -> None:
return return
# Movement and attack # Movement and attack
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
try_move_or_attack(0, -1) try_move_or_attack(0, -1)
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
try_move_or_attack(0, 1) try_move_or_attack(0, 1)
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
try_move_or_attack(-1, 0) try_move_or_attack(-1, 0)
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
try_move_or_attack(1, 0) try_move_or_attack(1, 0)
scene.on_key = handle_keys scene.on_key = handle_keys

View file

@ -405,24 +405,7 @@ class RectangularRoom:
self.y2 >= other.y1 self.y2 >= other.y1
) )
# ============================================================================= # Exploration tracking is handled internally by draw_fov()
# Exploration Tracking
# =============================================================================
explored: list[list[bool]] = []
def init_explored() -> None:
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Dungeon Generation # Dungeon Generation
@ -546,10 +529,7 @@ def get_blocking_entity_at(target_grid: mcrfpy.Grid, x: int, y: int, exclude: mc
return None return None
def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in entity_data: if entity in entity_data:
del entity_data[entity] del entity_data[entity]
@ -633,20 +613,16 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
continue continue
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
entity.visible = target_grid.is_in_fov(ex, ey) entity.visible = target_grid.is_in_fov((ex, ey))
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) target_fov_layer.draw_fov(
(player_x, player_y),
for y in range(GRID_HEIGHT): radius=FOV_RADIUS,
for x in range(GRID_WIDTH): visible=COLOR_VISIBLE,
if target_grid.is_in_fov(x, y): discovered=COLOR_DISCOVERED,
mark_explored(x, y) unknown=COLOR_UNKNOWN
target_fov_layer.set(x, y, COLOR_VISIBLE) )
elif is_explored(x, y):
target_fov_layer.set(x, y, COLOR_DISCOVERED)
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
@ -719,7 +695,7 @@ def enemy_turn() -> None:
ex, ey = int(enemy.x), int(enemy.y) ex, ey = int(enemy.x), int(enemy.y)
if not grid.is_in_fov(ex, ey): if not grid.is_in_fov((ex, ey)):
continue continue
dx = player_x - ex dx = player_x - ex
@ -791,7 +767,6 @@ grid = mcrfpy.Grid(
# Generate initial dungeon structure # Generate initial dungeon structure
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
rooms: list[RectangularRoom] = [] rooms: list[RectangularRoom] = []
@ -826,10 +801,9 @@ else:
player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2 player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2
# Add FOV layer # Add FOV layer
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
# Create the player # Create the player
player = mcrfpy.Entity( player = mcrfpy.Entity(
@ -930,11 +904,11 @@ def restart_game() -> None:
entity_data.clear() entity_data.clear()
while len(grid.entities) > 0: entities_to_clear = list(grid.entities)
grid.entities.remove(0) for e in entities_to_clear:
grid.entities.remove(e)
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
message_log.clear() message_log.clear()
rooms = [] rooms = []
@ -989,9 +963,7 @@ def restart_game() -> None:
continue continue
spawn_enemies_in_room(grid, room, texture) spawn_enemies_in_room(grid, room, texture)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, new_x, new_y) update_fov(grid, fov_layer, new_x, new_y)
@ -999,30 +971,30 @@ def restart_game() -> None:
update_ui() update_ui()
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
global game_over global game_over
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
if key == "R": if key == mcrfpy.Key.R:
restart_game() restart_game()
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
mcrfpy.exit() mcrfpy.exit()
return return
if game_over: if game_over:
return return
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
try_move_or_attack(0, -1) try_move_or_attack(0, -1)
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
try_move_or_attack(0, 1) try_move_or_attack(0, 1)
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
try_move_or_attack(-1, 0) try_move_or_attack(-1, 0)
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
try_move_or_attack(1, 0) try_move_or_attack(1, 0)
scene.on_key = handle_keys scene.on_key = handle_keys

View file

@ -436,24 +436,7 @@ class RectangularRoom:
self.y2 >= other.y1 self.y2 >= other.y1
) )
# ============================================================================= # Exploration tracking is handled internally by draw_fov()
# Exploration Tracking
# =============================================================================
explored: list[list[bool]] = []
def init_explored() -> None:
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Dungeon Generation # Dungeon Generation
@ -711,10 +694,7 @@ def use_item(index: int) -> bool:
def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
"""Remove an item entity from the grid and item_data.""" """Remove an item entity from the grid and item_data."""
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
@ -751,10 +731,7 @@ def get_blocking_entity_at(target_grid: mcrfpy.Grid, x: int, y: int, exclude: mc
return None return None
def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in entity_data: if entity in entity_data:
del entity_data[entity] del entity_data[entity]
@ -775,10 +752,7 @@ def clear_all_entities(target_grid: mcrfpy.Grid) -> None:
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
# ============================================================================= # =============================================================================
# Combat System # Combat System
@ -852,20 +826,16 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
continue continue
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
entity.visible = target_grid.is_in_fov(ex, ey) entity.visible = target_grid.is_in_fov((ex, ey))
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) target_fov_layer.draw_fov(
(player_x, player_y),
for y in range(GRID_HEIGHT): radius=FOV_RADIUS,
for x in range(GRID_WIDTH): visible=COLOR_VISIBLE,
if target_grid.is_in_fov(x, y): discovered=COLOR_DISCOVERED,
mark_explored(x, y) unknown=COLOR_UNKNOWN
target_fov_layer.set(x, y, COLOR_VISIBLE) )
elif is_explored(x, y):
target_fov_layer.set(x, y, COLOR_DISCOVERED)
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
@ -938,7 +908,7 @@ def enemy_turn() -> None:
ex, ey = int(enemy.x), int(enemy.y) ex, ey = int(enemy.x), int(enemy.y)
if not grid.is_in_fov(ex, ey): if not grid.is_in_fov((ex, ey)):
continue continue
dx = player_x - ex dx = player_x - ex
@ -1012,7 +982,6 @@ grid = mcrfpy.Grid(
# Generate initial dungeon # Generate initial dungeon
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
rooms: list[RectangularRoom] = [] rooms: list[RectangularRoom] = []
@ -1047,10 +1016,9 @@ else:
player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2 player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2
# Add FOV layer # Add FOV layer
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
# Create the player # Create the player
player = mcrfpy.Entity( player = mcrfpy.Entity(
@ -1156,11 +1124,10 @@ def restart_game() -> None:
entity_data.clear() entity_data.clear()
item_data.clear() item_data.clear()
while len(grid.entities) > 0: for e in list(grid.entities):
grid.entities.remove(0) grid.entities.remove(e)
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
message_log.clear() message_log.clear()
rooms = [] rooms = []
@ -1219,9 +1186,7 @@ def restart_game() -> None:
spawn_enemies_in_room(grid, room, texture) spawn_enemies_in_room(grid, room, texture)
spawn_items_in_room(grid, room, texture) spawn_items_in_room(grid, room, texture)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, new_x, new_y) update_fov(grid, fov_layer, new_x, new_y)
@ -1229,17 +1194,17 @@ def restart_game() -> None:
update_ui() update_ui()
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
global game_over global game_over
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
if key == "R": if key == mcrfpy.Key.R:
restart_game() restart_game()
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
mcrfpy.exit() mcrfpy.exit()
return return
@ -1247,20 +1212,20 @@ def handle_keys(key: str, action: str) -> None:
return return
# Movement # Movement
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
try_move_or_attack(0, -1) try_move_or_attack(0, -1)
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
try_move_or_attack(0, 1) try_move_or_attack(0, 1)
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
try_move_or_attack(-1, 0) try_move_or_attack(-1, 0)
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
try_move_or_attack(1, 0) try_move_or_attack(1, 0)
# Pickup # Pickup
elif key == "G" or key == ",": elif key == mcrfpy.Key.G:
pickup_item() pickup_item()
# Use items by number key # Use items by number key
elif key in ["1", "2", "3", "4", "5"]: elif key in [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5]:
index = int(key) - 1 index = [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5].index(key)
if use_item(index): if use_item(index):
enemy_turn() # Using an item takes a turn enemy_turn() # Using an item takes a turn
update_ui() update_ui()

View file

@ -457,24 +457,7 @@ class RectangularRoom:
self.y2 >= other.y1 self.y2 >= other.y1
) )
# ============================================================================= # Exploration tracking is handled internally by draw_fov()
# Exploration Tracking
# =============================================================================
explored: list[list[bool]] = []
def init_explored() -> None:
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Dungeon Generation # Dungeon Generation
@ -635,18 +618,12 @@ def get_blocking_entity_at(target_grid: mcrfpy.Grid, x: int, y: int, exclude: mc
return None return None
def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in entity_data: if entity in entity_data:
del entity_data[entity] del entity_data[entity]
def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
@ -666,10 +643,7 @@ def clear_all_entities(target_grid: mcrfpy.Grid) -> None:
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
# ============================================================================= # =============================================================================
# Targeting System # Targeting System
@ -701,11 +675,7 @@ def exit_targeting_mode() -> None:
global game_mode, target_cursor, grid global game_mode, target_cursor, grid
if target_cursor is not None: if target_cursor is not None:
# Remove cursor from grid grid.entities.remove(target_cursor)
for i, e in enumerate(grid.entities):
if e == target_cursor:
grid.entities.remove(i)
break
target_cursor = None target_cursor = None
game_mode = GameMode.NORMAL game_mode = GameMode.NORMAL
@ -723,7 +693,7 @@ def move_cursor(dx: int, dy: int) -> None:
return return
# Check if position is in FOV (can only target visible tiles) # Check if position is in FOV (can only target visible tiles)
if not grid.is_in_fov(new_x, new_y): if not grid.is_in_fov((new_x, new_y)):
message_log.add("You cannot see that location.", COLOR_INVALID) message_log.add("You cannot see that location.", COLOR_INVALID)
return return
@ -936,20 +906,16 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
continue continue
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
entity.visible = target_grid.is_in_fov(ex, ey) entity.visible = target_grid.is_in_fov((ex, ey))
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) target_fov_layer.draw_fov(
(player_x, player_y),
for y in range(GRID_HEIGHT): radius=FOV_RADIUS,
for x in range(GRID_WIDTH): visible=COLOR_VISIBLE,
if target_grid.is_in_fov(x, y): discovered=COLOR_DISCOVERED,
mark_explored(x, y) unknown=COLOR_UNKNOWN
target_fov_layer.set(x, y, COLOR_VISIBLE) )
elif is_explored(x, y):
target_fov_layer.set(x, y, COLOR_DISCOVERED)
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
@ -1022,7 +988,7 @@ def enemy_turn() -> None:
ex, ey = int(enemy.x), int(enemy.y) ex, ey = int(enemy.x), int(enemy.y)
if not grid.is_in_fov(ex, ey): if not grid.is_in_fov((ex, ey)):
continue continue
dx = player_x - ex dx = player_x - ex
@ -1095,7 +1061,6 @@ grid = mcrfpy.Grid(
# Generate initial dungeon # Generate initial dungeon
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
rooms: list[RectangularRoom] = [] rooms: list[RectangularRoom] = []
@ -1130,10 +1095,9 @@ else:
player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2 player_start_x, player_start_y = GRID_WIDTH // 2, GRID_HEIGHT // 2
# Add FOV layer # Add FOV layer
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
# Create the player # Create the player
player = mcrfpy.Entity( player = mcrfpy.Entity(
@ -1247,11 +1211,10 @@ def restart_game() -> None:
entity_data.clear() entity_data.clear()
item_data.clear() item_data.clear()
while len(grid.entities) > 0: for e in list(grid.entities):
grid.entities.remove(0) grid.entities.remove(e)
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
message_log.clear() message_log.clear()
rooms = [] rooms = []
@ -1309,9 +1272,7 @@ def restart_game() -> None:
spawn_enemies_in_room(grid, room, texture) spawn_enemies_in_room(grid, room, texture)
spawn_items_in_room(grid, room, texture) spawn_items_in_room(grid, room, texture)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, new_x, new_y) update_fov(grid, fov_layer, new_x, new_y)
@ -1320,18 +1281,18 @@ def restart_game() -> None:
mode_display.update(game_mode) mode_display.update(game_mode)
update_ui() update_ui()
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
global game_over, game_mode global game_over, game_mode
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
# Always allow restart and quit # Always allow restart and quit
if key == "R": if key == mcrfpy.Key.R:
restart_game() restart_game()
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
if game_mode == GameMode.TARGETING: if game_mode == GameMode.TARGETING:
exit_targeting_mode() exit_targeting_mode()
message_log.add("Targeting cancelled.", COLOR_INFO) message_log.add("Targeting cancelled.", COLOR_INFO)
@ -1349,41 +1310,41 @@ def handle_keys(key: str, action: str) -> None:
else: else:
handle_normal_input(key) handle_normal_input(key)
def handle_normal_input(key: str) -> None: def handle_normal_input(key) -> None:
"""Handle input in normal game mode.""" """Handle input in normal game mode."""
# Movement # Movement
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
try_move_or_attack(0, -1) try_move_or_attack(0, -1)
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
try_move_or_attack(0, 1) try_move_or_attack(0, 1)
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
try_move_or_attack(-1, 0) try_move_or_attack(-1, 0)
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
try_move_or_attack(1, 0) try_move_or_attack(1, 0)
# Ranged attack (enter targeting mode) # Ranged attack (enter targeting mode)
elif key == "F": elif key == mcrfpy.Key.F:
enter_targeting_mode() enter_targeting_mode()
# Pickup # Pickup
elif key == "G" or key == ",": elif key == mcrfpy.Key.G:
pickup_item() pickup_item()
# Use items # Use items
elif key in ["1", "2", "3", "4", "5"]: elif key in [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5]:
index = int(key) - 1 index = [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5].index(key)
if use_item(index): if use_item(index):
enemy_turn() enemy_turn()
update_ui() update_ui()
def handle_targeting_input(key: str) -> None: def handle_targeting_input(key) -> None:
"""Handle input in targeting mode.""" """Handle input in targeting mode."""
if key == "Up" or key == "W": if key == mcrfpy.Key.UP or key == mcrfpy.Key.W:
move_cursor(0, -1) move_cursor(0, -1)
elif key == "Down" or key == "S": elif key == mcrfpy.Key.DOWN or key == mcrfpy.Key.S:
move_cursor(0, 1) move_cursor(0, 1)
elif key == "Left" or key == "A": elif key == mcrfpy.Key.LEFT or key == mcrfpy.Key.A:
move_cursor(-1, 0) move_cursor(-1, 0)
elif key == "Right" or key == "D": elif key == mcrfpy.Key.RIGHT or key == mcrfpy.Key.D:
move_cursor(1, 0) move_cursor(1, 0)
elif key == "Return" or key == "Space": elif key == mcrfpy.Key.ENTER or key == mcrfpy.Key.SPACE:
confirm_target() confirm_target()
scene.on_key = handle_keys scene.on_key = handle_keys

View file

@ -511,24 +511,7 @@ class RectangularRoom:
self.y2 >= other.y1 self.y2 >= other.y1
) )
# ============================================================================= # Exploration tracking is handled internally by draw_fov()
# Exploration Tracking
# =============================================================================
explored: list[list[bool]] = []
def init_explored() -> None:
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Save/Load System # Save/Load System
@ -540,7 +523,7 @@ def save_game() -> bool:
Returns: Returns:
True if save succeeded, False otherwise True if save succeeded, False otherwise
""" """
global player, player_inventory, grid, explored, dungeon_level global player, player_inventory, grid, dungeon_level
try: try:
# Collect tile data # Collect tile data
@ -592,7 +575,6 @@ def save_game() -> bool:
"inventory": player_inventory.to_dict() "inventory": player_inventory.to_dict()
}, },
"tiles": tiles, "tiles": tiles,
"explored": [[explored[y][x] for x in range(GRID_WIDTH)] for y in range(GRID_HEIGHT)],
"enemies": enemies, "enemies": enemies,
"items": items_on_ground "items": items_on_ground
} }
@ -615,7 +597,7 @@ def load_game() -> bool:
Returns: Returns:
True if load succeeded, False otherwise True if load succeeded, False otherwise
""" """
global player, player_inventory, grid, explored, dungeon_level global player, player_inventory, grid, dungeon_level
global entity_data, item_data, fov_layer, game_over global entity_data, item_data, fov_layer, game_over
if not os.path.exists(SAVE_FILE): if not os.path.exists(SAVE_FILE):
@ -629,8 +611,8 @@ def load_game() -> bool:
entity_data.clear() entity_data.clear()
item_data.clear() item_data.clear()
while len(grid.entities) > 0: for e in list(grid.entities):
grid.entities.remove(0) grid.entities.remove(e)
# Restore dungeon level # Restore dungeon level
dungeon_level = save_data.get("dungeon_level", 1) dungeon_level = save_data.get("dungeon_level", 1)
@ -645,10 +627,7 @@ def load_game() -> bool:
cell.walkable = tile_data["walkable"] cell.walkable = tile_data["walkable"]
cell.transparent = tile_data["transparent"] cell.transparent = tile_data["transparent"]
# Restore explored state # Exploration state is tracked internally by draw_fov()
global explored
explored_data = save_data["explored"]
explored = [[explored_data[y][x] for x in range(GRID_WIDTH)] for y in range(GRID_HEIGHT)]
# Restore player # Restore player
player_data = save_data["player"] player_data = save_data["player"]
@ -695,9 +674,7 @@ def load_game() -> bool:
item_data[item_entity] = Item.from_dict(item_entry["item"]) item_data[item_entity] = Item.from_dict(item_entry["item"])
# Reset FOV layer # Reset FOV layer
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
# Compute initial FOV # Compute initial FOV
update_fov(grid, fov_layer, int(player.x), int(player.y)) update_fov(grid, fov_layer, int(player.x), int(player.y))
@ -890,18 +867,12 @@ def get_blocking_entity_at(target_grid: mcrfpy.Grid, x: int, y: int, exclude: mc
return None return None
def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in entity_data: if entity in entity_data:
del entity_data[entity] del entity_data[entity]
def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
@ -931,10 +902,7 @@ def exit_targeting_mode() -> None:
global game_mode, target_cursor, grid global game_mode, target_cursor, grid
if target_cursor is not None: if target_cursor is not None:
for i, e in enumerate(grid.entities): grid.entities.remove(target_cursor)
if e == target_cursor:
grid.entities.remove(i)
break
target_cursor = None target_cursor = None
game_mode = GameMode.NORMAL game_mode = GameMode.NORMAL
@ -949,7 +917,7 @@ def move_cursor(dx: int, dy: int) -> None:
if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT: if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT:
return return
if not grid.is_in_fov(new_x, new_y): if not grid.is_in_fov((new_x, new_y)):
message_log.add("You cannot see that location.", COLOR_INVALID) message_log.add("You cannot see that location.", COLOR_INVALID)
return return
@ -1144,20 +1112,16 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
continue continue
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
entity.visible = target_grid.is_in_fov(ex, ey) entity.visible = target_grid.is_in_fov((ex, ey))
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) target_fov_layer.draw_fov(
(player_x, player_y),
for y in range(GRID_HEIGHT): radius=FOV_RADIUS,
for x in range(GRID_WIDTH): visible=COLOR_VISIBLE,
if target_grid.is_in_fov(x, y): discovered=COLOR_DISCOVERED,
mark_explored(x, y) unknown=COLOR_UNKNOWN
target_fov_layer.set(x, y, COLOR_VISIBLE) )
elif is_explored(x, y):
target_fov_layer.set(x, y, COLOR_DISCOVERED)
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
@ -1230,7 +1194,7 @@ def enemy_turn() -> None:
ex, ey = int(enemy.x), int(enemy.y) ex, ey = int(enemy.x), int(enemy.y)
if not grid.is_in_fov(ex, ey): if not grid.is_in_fov((ex, ey)):
continue continue
dx = player_x - ex dx = player_x - ex
@ -1299,11 +1263,10 @@ def generate_new_game() -> None:
entity_data.clear() entity_data.clear()
item_data.clear() item_data.clear()
while len(grid.entities) > 0: for e in list(grid.entities):
grid.entities.remove(0) grid.entities.remove(e)
fill_with_walls(grid) fill_with_walls(grid)
init_explored()
message_log.clear() message_log.clear()
rooms: list[RectangularRoom] = [] rooms: list[RectangularRoom] = []
@ -1361,9 +1324,7 @@ def generate_new_game() -> None:
spawn_enemies_in_room(grid, room, texture) spawn_enemies_in_room(grid, room, texture)
spawn_items_in_room(grid, room, texture) spawn_items_in_room(grid, room, texture)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, new_x, new_y) update_fov(grid, fov_layer, new_x, new_y)
@ -1390,10 +1351,9 @@ grid = mcrfpy.Grid(
) )
# Add FOV layer # Add FOV layer
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
# Add grid to scene # Add grid to scene
scene.children.append(grid) scene.children.append(grid)
@ -1456,9 +1416,6 @@ message_log.add_to_scene(scene)
# Initialize Game (Load or New) # Initialize Game (Load or New)
# ============================================================================= # =============================================================================
# Initialize explored array
init_explored()
# Try to load existing save, otherwise generate new game # Try to load existing save, otherwise generate new game
if has_save_file(): if has_save_file():
message_log.add("Found saved game. Loading...", COLOR_INFO) message_log.add("Found saved game. Loading...", COLOR_INFO)
@ -1475,20 +1432,20 @@ else:
# Input Handling # Input Handling
# ============================================================================= # =============================================================================
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
global game_over, game_mode global game_over, game_mode
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
# Always allow restart # Always allow restart
if key == "R": if key == mcrfpy.Key.R:
delete_save() delete_save()
generate_new_game() generate_new_game()
message_log.add("A new adventure begins!", COLOR_INFO) message_log.add("A new adventure begins!", COLOR_INFO)
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
if game_mode == GameMode.TARGETING: if game_mode == GameMode.TARGETING:
exit_targeting_mode() exit_targeting_mode()
message_log.add("Targeting cancelled.", COLOR_INFO) message_log.add("Targeting cancelled.", COLOR_INFO)
@ -1500,14 +1457,8 @@ def handle_keys(key: str, action: str) -> None:
mcrfpy.exit() mcrfpy.exit()
return return
# Save game (Ctrl+S or just S when not moving)
if key == "S" and game_mode == GameMode.NORMAL:
# Check if this is meant to be a save (could add modifier check)
# For simplicity, we will use a dedicated save key
pass
# Dedicated save with period key # Dedicated save with period key
if key == "Period" and game_mode == GameMode.NORMAL and not game_over: if key == mcrfpy.Key.PERIOD and game_mode == GameMode.NORMAL and not game_over:
save_game() save_game()
return return
@ -1520,39 +1471,39 @@ def handle_keys(key: str, action: str) -> None:
else: else:
handle_normal_input(key) handle_normal_input(key)
def handle_normal_input(key: str) -> None: def handle_normal_input(key) -> None:
# Movement # Movement
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
try_move_or_attack(0, -1) try_move_or_attack(0, -1)
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
try_move_or_attack(0, 1) try_move_or_attack(0, 1)
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
try_move_or_attack(-1, 0) try_move_or_attack(-1, 0)
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
try_move_or_attack(1, 0) try_move_or_attack(1, 0)
# Ranged attack # Ranged attack
elif key == "F": elif key == mcrfpy.Key.F:
enter_targeting_mode() enter_targeting_mode()
# Pickup # Pickup
elif key == "G" or key == ",": elif key == mcrfpy.Key.G:
pickup_item() pickup_item()
# Use items # Use items
elif key in ["1", "2", "3", "4", "5"]: elif key in [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5]:
index = int(key) - 1 index = [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5].index(key)
if use_item(index): if use_item(index):
enemy_turn() enemy_turn()
update_ui() update_ui()
def handle_targeting_input(key: str) -> None: def handle_targeting_input(key) -> None:
if key == "Up" or key == "W": if key == mcrfpy.Key.UP or key == mcrfpy.Key.W:
move_cursor(0, -1) move_cursor(0, -1)
elif key == "Down" or key == "S": elif key == mcrfpy.Key.DOWN or key == mcrfpy.Key.S:
move_cursor(0, 1) move_cursor(0, 1)
elif key == "Left" or key == "A": elif key == mcrfpy.Key.LEFT or key == mcrfpy.Key.A:
move_cursor(-1, 0) move_cursor(-1, 0)
elif key == "Right" or key == "D": elif key == mcrfpy.Key.RIGHT or key == mcrfpy.Key.D:
move_cursor(1, 0) move_cursor(1, 0)
elif key == "Return" or key == "Space": elif key == mcrfpy.Key.ENTER or key == mcrfpy.Key.SPACE:
confirm_target() confirm_target()
scene.on_key = handle_keys scene.on_key = handle_keys

View file

@ -567,24 +567,7 @@ class RectangularRoom:
self.y2 >= other.y1 self.y2 >= other.y1
) )
# ============================================================================= # Exploration tracking is handled automatically by ColorLayer.draw_fov()
# Exploration Tracking
# =============================================================================
explored: list[list[bool]] = []
def init_explored() -> None:
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Dungeon Generation # Dungeon Generation
@ -821,18 +804,12 @@ def get_blocking_entity_at(target_grid: mcrfpy.Grid, x: int, y: int, exclude: mc
return None return None
def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in entity_data: if entity in entity_data:
del entity_data[entity] del entity_data[entity]
def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
@ -851,11 +828,7 @@ def clear_entities_except_player(target_grid: mcrfpy.Grid) -> None:
del entity_data[entity] del entity_data[entity]
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
target_grid.entities.remove(entity)
for i, e in enumerate(target_grid.entities):
if e == entity:
target_grid.entities.remove(i)
break
# ============================================================================= # =============================================================================
# Level Transition # Level Transition
@ -883,7 +856,6 @@ def descend_stairs() -> bool:
clear_entities_except_player(grid) clear_entities_except_player(grid)
# Generate new dungeon # Generate new dungeon
init_explored()
player_start = generate_dungeon(grid, dungeon_level) player_start = generate_dungeon(grid, dungeon_level)
# Move player to starting position # Move player to starting position
@ -896,9 +868,7 @@ def descend_stairs() -> bool:
spawn_entities_for_level(grid, texture, dungeon_level) spawn_entities_for_level(grid, texture, dungeon_level)
# Reset FOV # Reset FOV
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, player_start[0], player_start[1]) update_fov(grid, fov_layer, player_start[0], player_start[1])
@ -977,7 +947,7 @@ def spawn_entities_for_level(target_grid: mcrfpy.Grid, tex: mcrfpy.Texture, leve
def save_game() -> bool: def save_game() -> bool:
"""Save the current game state to a JSON file.""" """Save the current game state to a JSON file."""
global player, player_inventory, grid, explored, dungeon_level, stairs_position global player, player_inventory, grid, dungeon_level, stairs_position
try: try:
tiles = [] tiles = []
@ -1026,7 +996,6 @@ def save_game() -> bool:
"inventory": player_inventory.to_dict() "inventory": player_inventory.to_dict()
}, },
"tiles": tiles, "tiles": tiles,
"explored": [[explored[y][x] for x in range(GRID_WIDTH)] for y in range(GRID_HEIGHT)],
"enemies": enemies, "enemies": enemies,
"items": items_on_ground "items": items_on_ground
} }
@ -1043,7 +1012,7 @@ def save_game() -> bool:
def load_game() -> bool: def load_game() -> bool:
"""Load a saved game from JSON file.""" """Load a saved game from JSON file."""
global player, player_inventory, grid, explored, dungeon_level global player, player_inventory, grid, dungeon_level
global entity_data, item_data, fov_layer, game_over, stairs_position global entity_data, item_data, fov_layer, game_over, stairs_position
if not os.path.exists(SAVE_FILE): if not os.path.exists(SAVE_FILE):
@ -1057,7 +1026,7 @@ def load_game() -> bool:
item_data.clear() item_data.clear()
while len(grid.entities) > 0: while len(grid.entities) > 0:
grid.entities.remove(0) grid.entities.pop(0)
dungeon_level = save_data.get("dungeon_level", 1) dungeon_level = save_data.get("dungeon_level", 1)
stairs_position = tuple(save_data.get("stairs_position", [0, 0])) stairs_position = tuple(save_data.get("stairs_position", [0, 0]))
@ -1071,10 +1040,6 @@ def load_game() -> bool:
cell.walkable = tile_data["walkable"] cell.walkable = tile_data["walkable"]
cell.transparent = tile_data["transparent"] cell.transparent = tile_data["transparent"]
global explored
explored_data = save_data["explored"]
explored = [[explored_data[y][x] for x in range(GRID_WIDTH)] for y in range(GRID_HEIGHT)]
player_data = save_data["player"] player_data = save_data["player"]
player = mcrfpy.Entity( player = mcrfpy.Entity(
grid_pos=(player_data["x"], player_data["y"]), grid_pos=(player_data["x"], player_data["y"]),
@ -1114,9 +1079,7 @@ def load_game() -> bool:
grid.entities.append(item_entity) grid.entities.append(item_entity)
item_data[item_entity] = Item.from_dict(item_entry["item"]) item_data[item_entity] = Item.from_dict(item_entry["item"])
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, int(player.x), int(player.y)) update_fov(grid, fov_layer, int(player.x), int(player.y))
@ -1168,10 +1131,7 @@ def exit_targeting_mode() -> None:
global game_mode, target_cursor, grid global game_mode, target_cursor, grid
if target_cursor is not None: if target_cursor is not None:
for i, e in enumerate(grid.entities): grid.entities.remove(target_cursor)
if e == target_cursor:
grid.entities.remove(i)
break
target_cursor = None target_cursor = None
game_mode = GameMode.NORMAL game_mode = GameMode.NORMAL
@ -1186,7 +1146,7 @@ def move_cursor(dx: int, dy: int) -> None:
if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT: if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT:
return return
if not grid.is_in_fov(new_x, new_y): if not grid.is_in_fov((new_x, new_y)):
message_log.add("You cannot see that location.", COLOR_INVALID) message_log.add("You cannot see that location.", COLOR_INVALID)
return return
@ -1380,21 +1340,16 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
continue continue
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
entity.visible = target_grid.is_in_fov(ex, ey) entity.visible = target_grid.is_in_fov((ex, ey))
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) target_fov_layer.draw_fov(
(player_x, player_y),
for y in range(GRID_HEIGHT): radius=FOV_RADIUS,
for x in range(GRID_WIDTH): visible=COLOR_VISIBLE,
if target_grid.is_in_fov(x, y): discovered=COLOR_DISCOVERED,
mark_explored(x, y) unknown=COLOR_UNKNOWN
target_fov_layer.set(x, y, COLOR_VISIBLE) )
elif is_explored(x, y):
target_fov_layer.set(x, y, COLOR_DISCOVERED)
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
# ============================================================================= # =============================================================================
@ -1466,7 +1421,7 @@ def enemy_turn() -> None:
ex, ey = int(enemy.x), int(enemy.y) ex, ey = int(enemy.x), int(enemy.y)
if not grid.is_in_fov(ex, ey): if not grid.is_in_fov((ex, ey)):
continue continue
dx = player_x - ex dx = player_x - ex
@ -1535,9 +1490,8 @@ def generate_new_game() -> None:
item_data.clear() item_data.clear()
while len(grid.entities) > 0: while len(grid.entities) > 0:
grid.entities.remove(0) grid.entities.pop(0)
init_explored()
message_log.clear() message_log.clear()
player_start = generate_dungeon(grid, dungeon_level) player_start = generate_dungeon(grid, dungeon_level)
@ -1562,9 +1516,7 @@ def generate_new_game() -> None:
spawn_entities_for_level(grid, texture, dungeon_level) spawn_entities_for_level(grid, texture, dungeon_level)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, player_start[0], player_start[1]) update_fov(grid, fov_layer, player_start[0], player_start[1])
@ -1587,10 +1539,9 @@ grid = mcrfpy.Grid(
zoom=1.0 zoom=1.0
) )
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
scene.children.append(grid) scene.children.append(grid)
@ -1639,8 +1590,6 @@ message_log.add_to_scene(scene)
# Initialize Game # Initialize Game
# ============================================================================= # =============================================================================
init_explored()
if has_save_file(): if has_save_file():
message_log.add("Found saved game. Loading...", COLOR_INFO) message_log.add("Found saved game. Loading...", COLOR_INFO)
if not load_game(): if not load_game():
@ -1656,19 +1605,19 @@ else:
# Input Handling # Input Handling
# ============================================================================= # =============================================================================
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
global game_over, game_mode global game_over, game_mode
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
if key == "R": if key == mcrfpy.Key.R:
delete_save() delete_save()
generate_new_game() generate_new_game()
message_log.add("A new adventure begins!", COLOR_INFO) message_log.add("A new adventure begins!", COLOR_INFO)
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
if game_mode == GameMode.TARGETING: if game_mode == GameMode.TARGETING:
exit_targeting_mode() exit_targeting_mode()
message_log.add("Targeting cancelled.", COLOR_INFO) message_log.add("Targeting cancelled.", COLOR_INFO)
@ -1679,7 +1628,7 @@ def handle_keys(key: str, action: str) -> None:
mcrfpy.exit() mcrfpy.exit()
return return
if key == "Period" and game_mode == GameMode.NORMAL and not game_over: if key == mcrfpy.Key.PERIOD and game_mode == GameMode.NORMAL and not game_over:
save_game() save_game()
return return
@ -1691,38 +1640,38 @@ def handle_keys(key: str, action: str) -> None:
else: else:
handle_normal_input(key) handle_normal_input(key)
def handle_normal_input(key: str) -> None: def handle_normal_input(key) -> None:
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
try_move_or_attack(0, -1) try_move_or_attack(0, -1)
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
try_move_or_attack(0, 1) try_move_or_attack(0, 1)
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
try_move_or_attack(-1, 0) try_move_or_attack(-1, 0)
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
try_move_or_attack(1, 0) try_move_or_attack(1, 0)
elif key == "F": elif key == mcrfpy.Key.F:
enter_targeting_mode() enter_targeting_mode()
elif key == "G" or key == ",": elif key == mcrfpy.Key.G or key == mcrfpy.Key.COMMA:
pickup_item() pickup_item()
elif key == "Period" and mcrfpy.keypressed("LShift"): elif key == mcrfpy.Key.PERIOD and mcrfpy.keyboard.shift:
# Shift+. (>) to descend stairs # Shift+. (>) to descend stairs
descend_stairs() descend_stairs()
elif key in ["1", "2", "3", "4", "5"]: elif key in (mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5):
index = int(key) - 1 index = [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5].index(key)
if use_item(index): if use_item(index):
enemy_turn() enemy_turn()
update_ui() update_ui()
def handle_targeting_input(key: str) -> None: def handle_targeting_input(key) -> None:
if key == "Up" or key == "W": if key == mcrfpy.Key.UP or key == mcrfpy.Key.W:
move_cursor(0, -1) move_cursor(0, -1)
elif key == "Down" or key == "S": elif key == mcrfpy.Key.DOWN or key == mcrfpy.Key.S:
move_cursor(0, 1) move_cursor(0, 1)
elif key == "Left" or key == "A": elif key == mcrfpy.Key.LEFT or key == mcrfpy.Key.A:
move_cursor(-1, 0) move_cursor(-1, 0)
elif key == "Right" or key == "D": elif key == mcrfpy.Key.RIGHT or key == mcrfpy.Key.D:
move_cursor(1, 0) move_cursor(1, 0)
elif key == "Return" or key == "Space": elif key == mcrfpy.Key.ENTER or key == mcrfpy.Key.SPACE:
confirm_target() confirm_target()
scene.on_key = handle_keys scene.on_key = handle_keys

View file

@ -723,24 +723,7 @@ class RectangularRoom:
self.y2 >= other.y1 self.y2 >= other.y1
) )
# ============================================================================= # Exploration tracking is handled automatically by ColorLayer.draw_fov()
# Exploration Tracking
# =============================================================================
explored: list[list[bool]] = []
def init_explored() -> None:
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Dungeon Generation # Dungeon Generation
@ -972,18 +955,12 @@ def get_blocking_entity_at(target_grid: mcrfpy.Grid, x: int, y: int, exclude: mc
return None return None
def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in entity_data: if entity in entity_data:
del entity_data[entity] del entity_data[entity]
def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
@ -1001,11 +978,7 @@ def clear_entities_except_player(target_grid: mcrfpy.Grid) -> None:
del entity_data[entity] del entity_data[entity]
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
target_grid.entities.remove(entity)
for i, e in enumerate(target_grid.entities):
if e == entity:
target_grid.entities.remove(i)
break
# ============================================================================= # =============================================================================
# XP and Level Up # XP and Level Up
@ -1059,7 +1032,6 @@ def descend_stairs() -> bool:
clear_entities_except_player(grid) clear_entities_except_player(grid)
init_explored()
player_start = generate_dungeon(grid, dungeon_level) player_start = generate_dungeon(grid, dungeon_level)
player.x = player_start[0] player.x = player_start[0]
@ -1067,9 +1039,7 @@ def descend_stairs() -> bool:
spawn_entities_for_level(grid, texture, dungeon_level) spawn_entities_for_level(grid, texture, dungeon_level)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, player_start[0], player_start[1]) update_fov(grid, fov_layer, player_start[0], player_start[1])
@ -1084,7 +1054,7 @@ def descend_stairs() -> bool:
# ============================================================================= # =============================================================================
def save_game() -> bool: def save_game() -> bool:
global player, player_inventory, grid, explored, dungeon_level, stairs_position global player, player_inventory, grid, dungeon_level, stairs_position
try: try:
tiles = [] tiles = []
@ -1133,7 +1103,6 @@ def save_game() -> bool:
"inventory": player_inventory.to_dict() "inventory": player_inventory.to_dict()
}, },
"tiles": tiles, "tiles": tiles,
"explored": [[explored[y][x] for x in range(GRID_WIDTH)] for y in range(GRID_HEIGHT)],
"enemies": enemies, "enemies": enemies,
"items": items_on_ground "items": items_on_ground
} }
@ -1149,7 +1118,7 @@ def save_game() -> bool:
return False return False
def load_game() -> bool: def load_game() -> bool:
global player, player_inventory, grid, explored, dungeon_level global player, player_inventory, grid, dungeon_level
global entity_data, item_data, fov_layer, game_over, stairs_position global entity_data, item_data, fov_layer, game_over, stairs_position
if not os.path.exists(SAVE_FILE): if not os.path.exists(SAVE_FILE):
@ -1163,7 +1132,7 @@ def load_game() -> bool:
item_data.clear() item_data.clear()
while len(grid.entities) > 0: while len(grid.entities) > 0:
grid.entities.remove(0) grid.entities.pop(0)
dungeon_level = save_data.get("dungeon_level", 1) dungeon_level = save_data.get("dungeon_level", 1)
stairs_position = tuple(save_data.get("stairs_position", [0, 0])) stairs_position = tuple(save_data.get("stairs_position", [0, 0]))
@ -1177,10 +1146,6 @@ def load_game() -> bool:
cell.walkable = tile_data["walkable"] cell.walkable = tile_data["walkable"]
cell.transparent = tile_data["transparent"] cell.transparent = tile_data["transparent"]
global explored
explored_data = save_data["explored"]
explored = [[explored_data[y][x] for x in range(GRID_WIDTH)] for y in range(GRID_HEIGHT)]
player_data = save_data["player"] player_data = save_data["player"]
player = mcrfpy.Entity( player = mcrfpy.Entity(
grid_pos=(player_data["x"], player_data["y"]), grid_pos=(player_data["x"], player_data["y"]),
@ -1220,9 +1185,7 @@ def load_game() -> bool:
grid.entities.append(item_entity) grid.entities.append(item_entity)
item_data[item_entity] = Item.from_dict(item_entry["item"]) item_data[item_entity] = Item.from_dict(item_entry["item"])
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, int(player.x), int(player.y)) update_fov(grid, fov_layer, int(player.x), int(player.y))
@ -1274,10 +1237,7 @@ def exit_targeting_mode() -> None:
global game_mode, target_cursor, grid global game_mode, target_cursor, grid
if target_cursor is not None: if target_cursor is not None:
for i, e in enumerate(grid.entities): grid.entities.remove(target_cursor)
if e == target_cursor:
grid.entities.remove(i)
break
target_cursor = None target_cursor = None
game_mode = GameMode.NORMAL game_mode = GameMode.NORMAL
@ -1292,7 +1252,7 @@ def move_cursor(dx: int, dy: int) -> None:
if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT: if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT:
return return
if not grid.is_in_fov(new_x, new_y): if not grid.is_in_fov((new_x, new_y)):
message_log.add("You cannot see that location.", COLOR_INVALID) message_log.add("You cannot see that location.", COLOR_INVALID)
return return
@ -1488,21 +1448,16 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
continue continue
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
entity.visible = target_grid.is_in_fov(ex, ey) entity.visible = target_grid.is_in_fov((ex, ey))
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) target_fov_layer.draw_fov(
(player_x, player_y),
for y in range(GRID_HEIGHT): radius=FOV_RADIUS,
for x in range(GRID_WIDTH): visible=COLOR_VISIBLE,
if target_grid.is_in_fov(x, y): discovered=COLOR_DISCOVERED,
mark_explored(x, y) unknown=COLOR_UNKNOWN
target_fov_layer.set(x, y, COLOR_VISIBLE) )
elif is_explored(x, y):
target_fov_layer.set(x, y, COLOR_DISCOVERED)
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
# ============================================================================= # =============================================================================
@ -1574,7 +1529,7 @@ def enemy_turn() -> None:
ex, ey = int(enemy.x), int(enemy.y) ex, ey = int(enemy.x), int(enemy.y)
if not grid.is_in_fov(ex, ey): if not grid.is_in_fov((ex, ey)):
continue continue
dx = player_x - ex dx = player_x - ex
@ -1644,9 +1599,8 @@ def generate_new_game() -> None:
item_data.clear() item_data.clear()
while len(grid.entities) > 0: while len(grid.entities) > 0:
grid.entities.remove(0) grid.entities.pop(0)
init_explored()
message_log.clear() message_log.clear()
player_start = generate_dungeon(grid, dungeon_level) player_start = generate_dungeon(grid, dungeon_level)
@ -1673,9 +1627,7 @@ def generate_new_game() -> None:
spawn_entities_for_level(grid, texture, dungeon_level) spawn_entities_for_level(grid, texture, dungeon_level)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, player_start[0], player_start[1]) update_fov(grid, fov_layer, player_start[0], player_start[1])
@ -1698,10 +1650,9 @@ grid = mcrfpy.Grid(
zoom=1.0 zoom=1.0
) )
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
scene.children.append(grid) scene.children.append(grid)
@ -1756,8 +1707,6 @@ message_log.add_to_scene(scene)
# Initialize Game # Initialize Game
# ============================================================================= # =============================================================================
init_explored()
if has_save_file(): if has_save_file():
message_log.add("Found saved game. Loading...", COLOR_INFO) message_log.add("Found saved game. Loading...", COLOR_INFO)
if not load_game(): if not load_game():
@ -1773,19 +1722,19 @@ else:
# Input Handling # Input Handling
# ============================================================================= # =============================================================================
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
global game_over, game_mode global game_over, game_mode
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
if key == "R": if key == mcrfpy.Key.R:
delete_save() delete_save()
generate_new_game() generate_new_game()
message_log.add("A new adventure begins!", COLOR_INFO) message_log.add("A new adventure begins!", COLOR_INFO)
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
if game_mode == GameMode.TARGETING: if game_mode == GameMode.TARGETING:
exit_targeting_mode() exit_targeting_mode()
message_log.add("Targeting cancelled.", COLOR_INFO) message_log.add("Targeting cancelled.", COLOR_INFO)
@ -1796,7 +1745,7 @@ def handle_keys(key: str, action: str) -> None:
mcrfpy.exit() mcrfpy.exit()
return return
if key == "Period" and game_mode == GameMode.NORMAL and not game_over: if key == mcrfpy.Key.PERIOD and game_mode == GameMode.NORMAL and not game_over:
# Check for shift to descend # Check for shift to descend
descend_stairs() descend_stairs()
return return
@ -1809,35 +1758,35 @@ def handle_keys(key: str, action: str) -> None:
else: else:
handle_normal_input(key) handle_normal_input(key)
def handle_normal_input(key: str) -> None: def handle_normal_input(key) -> None:
if key == "W" or key == "Up": if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
try_move_or_attack(0, -1) try_move_or_attack(0, -1)
elif key == "S" or key == "Down": elif key == mcrfpy.Key.S or key == mcrfpy.Key.DOWN:
try_move_or_attack(0, 1) try_move_or_attack(0, 1)
elif key == "A" or key == "Left": elif key == mcrfpy.Key.A or key == mcrfpy.Key.LEFT:
try_move_or_attack(-1, 0) try_move_or_attack(-1, 0)
elif key == "D" or key == "Right": elif key == mcrfpy.Key.D or key == mcrfpy.Key.RIGHT:
try_move_or_attack(1, 0) try_move_or_attack(1, 0)
elif key == "F": elif key == mcrfpy.Key.F:
enter_targeting_mode() enter_targeting_mode()
elif key == "G" or key == ",": elif key == mcrfpy.Key.G or key == mcrfpy.Key.COMMA:
pickup_item() pickup_item()
elif key in ["1", "2", "3", "4", "5"]: elif key in (mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5):
index = int(key) - 1 index = [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5].index(key)
if use_item(index): if use_item(index):
enemy_turn() enemy_turn()
update_ui() update_ui()
def handle_targeting_input(key: str) -> None: def handle_targeting_input(key) -> None:
if key == "Up" or key == "W": if key == mcrfpy.Key.UP or key == mcrfpy.Key.W:
move_cursor(0, -1) move_cursor(0, -1)
elif key == "Down" or key == "S": elif key == mcrfpy.Key.DOWN or key == mcrfpy.Key.S:
move_cursor(0, 1) move_cursor(0, 1)
elif key == "Left" or key == "A": elif key == mcrfpy.Key.LEFT or key == mcrfpy.Key.A:
move_cursor(-1, 0) move_cursor(-1, 0)
elif key == "Right" or key == "D": elif key == mcrfpy.Key.RIGHT or key == mcrfpy.Key.D:
move_cursor(1, 0) move_cursor(1, 0)
elif key == "Return" or key == "Space": elif key == mcrfpy.Key.ENTER or key == mcrfpy.Key.SPACE:
confirm_target() confirm_target()
scene.on_key = handle_keys scene.on_key = handle_keys

View file

@ -801,24 +801,7 @@ class RectangularRoom:
def intersects(self, other: "RectangularRoom") -> bool: def intersects(self, other: "RectangularRoom") -> bool:
return self.x1 <= other.x2 and self.x2 >= other.x1 and self.y1 <= other.y2 and self.y2 >= other.y1 return self.x1 <= other.x2 and self.x2 >= other.x1 and self.y1 <= other.y2 and self.y2 >= other.y1
# ============================================================================= # Exploration tracking is handled automatically by ColorLayer.draw_fov()
# Exploration Tracking
# =============================================================================
explored: list[list[bool]] = []
def init_explored() -> None:
global explored
explored = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def mark_explored(x: int, y: int) -> None:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
explored[y][x] = True
def is_explored(x: int, y: int) -> bool:
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
return explored[y][x]
return False
# ============================================================================= # =============================================================================
# Dungeon Generation (abbreviated for space) # Dungeon Generation (abbreviated for space)
@ -1014,18 +997,12 @@ def get_blocking_entity_at(target_grid: mcrfpy.Grid, x: int, y: int, exclude: mc
return None return None
def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in entity_data: if entity in entity_data:
del entity_data[entity] del entity_data[entity]
def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None: def remove_item_entity(target_grid: mcrfpy.Grid, entity: mcrfpy.Entity) -> None:
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
@ -1041,10 +1018,7 @@ def clear_entities_except_player(target_grid: mcrfpy.Grid) -> None:
del entity_data[entity] del entity_data[entity]
if entity in item_data: if entity in item_data:
del item_data[entity] del item_data[entity]
for i, e in enumerate(target_grid.entities): target_grid.entities.remove(entity)
if e == entity:
target_grid.entities.remove(i)
break
# ============================================================================= # =============================================================================
# Equipment Actions # Equipment Actions
@ -1174,14 +1148,11 @@ def descend_stairs() -> bool:
dungeon_level += 1 dungeon_level += 1
clear_entities_except_player(grid) clear_entities_except_player(grid)
init_explored()
player_start = generate_dungeon(grid, dungeon_level) player_start = generate_dungeon(grid, dungeon_level)
player.x, player.y = player_start player.x, player.y = player_start
spawn_entities_for_level(grid, texture, dungeon_level) spawn_entities_for_level(grid, texture, dungeon_level)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, player_start[0], player_start[1]) update_fov(grid, fov_layer, player_start[0], player_start[1])
message_log.add(f"You descend to level {dungeon_level}...", COLOR_DESCEND) message_log.add(f"You descend to level {dungeon_level}...", COLOR_DESCEND)
@ -1194,7 +1165,7 @@ def descend_stairs() -> bool:
# ============================================================================= # =============================================================================
def save_game() -> bool: def save_game() -> bool:
global player, player_inventory, grid, explored, dungeon_level, stairs_position global player, player_inventory, grid, dungeon_level, stairs_position
try: try:
tiles = [] tiles = []
@ -1238,7 +1209,6 @@ def save_game() -> bool:
"inventory": player_inventory.to_dict() "inventory": player_inventory.to_dict()
}, },
"tiles": tiles, "tiles": tiles,
"explored": [[explored[y][x] for x in range(GRID_WIDTH)] for y in range(GRID_HEIGHT)],
"enemies": enemies, "enemies": enemies,
"items": items_on_ground "items": items_on_ground
} }
@ -1253,7 +1223,7 @@ def save_game() -> bool:
return False return False
def load_game() -> bool: def load_game() -> bool:
global player, player_inventory, grid, explored, dungeon_level global player, player_inventory, grid, dungeon_level
global entity_data, item_data, fov_layer, game_over, stairs_position global entity_data, item_data, fov_layer, game_over, stairs_position
if not os.path.exists(SAVE_FILE): if not os.path.exists(SAVE_FILE):
@ -1266,7 +1236,7 @@ def load_game() -> bool:
entity_data.clear() entity_data.clear()
item_data.clear() item_data.clear()
while len(grid.entities) > 0: while len(grid.entities) > 0:
grid.entities.remove(0) grid.entities.pop(0)
dungeon_level = save_data.get("dungeon_level", 1) dungeon_level = save_data.get("dungeon_level", 1)
stairs_position = tuple(save_data.get("stairs_position", [0, 0])) stairs_position = tuple(save_data.get("stairs_position", [0, 0]))
@ -1280,9 +1250,6 @@ def load_game() -> bool:
cell.walkable = tile_data["walkable"] cell.walkable = tile_data["walkable"]
cell.transparent = tile_data["transparent"] cell.transparent = tile_data["transparent"]
global explored
explored = save_data["explored"]
player_data = save_data["player"] player_data = save_data["player"]
player = mcrfpy.Entity( player = mcrfpy.Entity(
grid_pos=(player_data["x"], player_data["y"]), grid_pos=(player_data["x"], player_data["y"]),
@ -1321,9 +1288,7 @@ def load_game() -> bool:
grid.entities.append(item_entity) grid.entities.append(item_entity)
item_data[item_entity] = item item_data[item_entity] = item
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, int(player.x), int(player.y)) update_fov(grid, fov_layer, int(player.x), int(player.y))
game_over = False game_over = False
@ -1362,10 +1327,7 @@ def enter_targeting_mode() -> None:
def exit_targeting_mode() -> None: def exit_targeting_mode() -> None:
global game_mode, target_cursor global game_mode, target_cursor
if target_cursor: if target_cursor:
for i, e in enumerate(grid.entities): grid.entities.remove(target_cursor)
if e == target_cursor:
grid.entities.remove(i)
break
target_cursor = None target_cursor = None
game_mode = GameMode.NORMAL game_mode = GameMode.NORMAL
mode_display.update(game_mode) mode_display.update(game_mode)
@ -1375,7 +1337,7 @@ def move_cursor(dx: int, dy: int) -> None:
new_x, new_y = target_x + dx, target_y + dy new_x, new_y = target_x + dx, target_y + dy
if not (0 <= new_x < GRID_WIDTH and 0 <= new_y < GRID_HEIGHT): if not (0 <= new_x < GRID_WIDTH and 0 <= new_y < GRID_HEIGHT):
return return
if not grid.is_in_fov(new_x, new_y): if not grid.is_in_fov((new_x, new_y)):
message_log.add("Cannot see that location.", COLOR_INVALID) message_log.add("Cannot see that location.", COLOR_INVALID)
return return
distance = abs(new_x - int(player.x)) + abs(new_y - int(player.y)) distance = abs(new_x - int(player.x)) + abs(new_y - int(player.y))
@ -1515,19 +1477,16 @@ def update_entity_visibility(target_grid: mcrfpy.Grid) -> None:
if entity == player or entity == target_cursor: if entity == player or entity == target_cursor:
entity.visible = True entity.visible = True
else: else:
entity.visible = target_grid.is_in_fov(int(entity.x), int(entity.y)) entity.visible = target_grid.is_in_fov((int(entity.x), int(entity.y)))
def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None: def update_fov(target_grid: mcrfpy.Grid, target_fov_layer, player_x: int, player_y: int) -> None:
target_grid.compute_fov(player_x, player_y, FOV_RADIUS, mcrfpy.FOV.SHADOW) target_fov_layer.draw_fov(
for y in range(GRID_HEIGHT): (player_x, player_y),
for x in range(GRID_WIDTH): radius=FOV_RADIUS,
if target_grid.is_in_fov(x, y): visible=COLOR_VISIBLE,
mark_explored(x, y) discovered=COLOR_DISCOVERED,
target_fov_layer.set(x, y, COLOR_VISIBLE) unknown=COLOR_UNKNOWN
elif is_explored(x, y): )
target_fov_layer.set(x, y, COLOR_DISCOVERED)
else:
target_fov_layer.set(x, y, COLOR_UNKNOWN)
update_entity_visibility(target_grid) update_entity_visibility(target_grid)
# ============================================================================= # =============================================================================
@ -1577,7 +1536,7 @@ def enemy_turn() -> None:
if not fighter.is_alive: if not fighter.is_alive:
continue continue
ex, ey = int(entity.x), int(entity.y) ex, ey = int(entity.x), int(entity.y)
if not grid.is_in_fov(ex, ey): if not grid.is_in_fov((ex, ey)):
continue continue
dx, dy = px - ex, py - ey dx, dy = px - ex, py - ey
@ -1624,9 +1583,8 @@ def generate_new_game() -> None:
entity_data.clear() entity_data.clear()
item_data.clear() item_data.clear()
while len(grid.entities) > 0: while len(grid.entities) > 0:
grid.entities.remove(0) grid.entities.pop(0)
init_explored()
message_log.clear() message_log.clear()
player_start = generate_dungeon(grid, dungeon_level) player_start = generate_dungeon(grid, dungeon_level)
@ -1642,9 +1600,7 @@ def generate_new_game() -> None:
player_inventory = Inventory(capacity=10) player_inventory = Inventory(capacity=10)
spawn_entities_for_level(grid, texture, dungeon_level) spawn_entities_for_level(grid, texture, dungeon_level)
for y in range(GRID_HEIGHT): fov_layer.fill(COLOR_UNKNOWN)
for x in range(GRID_WIDTH):
fov_layer.set(x, y, COLOR_UNKNOWN)
update_fov(grid, fov_layer, player_start[0], player_start[1]) update_fov(grid, fov_layer, player_start[0], player_start[1])
mode_display.update(game_mode) mode_display.update(game_mode)
@ -1666,10 +1622,9 @@ grid = mcrfpy.Grid(
zoom=1.0 zoom=1.0
) )
fov_layer = grid.add_layer("color", z_index=-1) fov_layer = mcrfpy.ColorLayer(z_index=-1, name="fov")
for y in range(GRID_HEIGHT): grid.add_layer(fov_layer)
for x in range(GRID_WIDTH): fov_layer.fill(COLOR_UNKNOWN)
fov_layer.set(x, y, COLOR_UNKNOWN)
scene.children.append(grid) scene.children.append(grid)
@ -1706,8 +1661,6 @@ message_log = MessageLog(x=20, y=768 - UI_BOTTOM_HEIGHT + 10, width=990, height=
message_log.add_to_scene(scene) message_log.add_to_scene(scene)
# Initialize # Initialize
init_explored()
if has_save_file(): if has_save_file():
message_log.add("Loading saved game...", COLOR_INFO) message_log.add("Loading saved game...", COLOR_INFO)
if not load_game(): if not load_game():
@ -1722,19 +1675,19 @@ else:
# Input Handling # Input Handling
# ============================================================================= # =============================================================================
def handle_keys(key: str, action: str) -> None: def handle_keys(key, action) -> None:
global game_over, game_mode global game_over, game_mode
if action != "start": if action != mcrfpy.InputState.PRESSED:
return return
if key == "R": if key == mcrfpy.Key.R:
delete_save() delete_save()
generate_new_game() generate_new_game()
message_log.add("New adventure begins!", COLOR_INFO) message_log.add("New adventure begins!", COLOR_INFO)
return return
if key == "Escape": if key == mcrfpy.Key.ESCAPE:
if game_mode == GameMode.TARGETING: if game_mode == GameMode.TARGETING:
exit_targeting_mode() exit_targeting_mode()
message_log.add("Targeting cancelled.", COLOR_INFO) message_log.add("Targeting cancelled.", COLOR_INFO)
@ -1752,39 +1705,39 @@ def handle_keys(key: str, action: str) -> None:
else: else:
handle_normal_input(key) handle_normal_input(key)
def handle_normal_input(key: str) -> None: def handle_normal_input(key) -> None:
if key in ("W", "Up"): if key in (mcrfpy.Key.W, mcrfpy.Key.UP):
try_move_or_attack(0, -1) try_move_or_attack(0, -1)
elif key in ("S", "Down"): elif key in (mcrfpy.Key.S, mcrfpy.Key.DOWN):
try_move_or_attack(0, 1) try_move_or_attack(0, 1)
elif key in ("A", "Left"): elif key in (mcrfpy.Key.A, mcrfpy.Key.LEFT):
try_move_or_attack(-1, 0) try_move_or_attack(-1, 0)
elif key in ("D", "Right"): elif key in (mcrfpy.Key.D, mcrfpy.Key.RIGHT):
try_move_or_attack(1, 0) try_move_or_attack(1, 0)
elif key == "F": elif key == mcrfpy.Key.F:
enter_targeting_mode() enter_targeting_mode()
elif key in ("G", ","): elif key in (mcrfpy.Key.G, mcrfpy.Key.COMMA):
pickup_item() pickup_item()
elif key == "Period": elif key == mcrfpy.Key.PERIOD:
descend_stairs() descend_stairs()
elif key == "E": elif key == mcrfpy.Key.E:
message_log.add("Press 1-5 to equip an item from inventory.", COLOR_INFO) message_log.add("Press 1-5 to equip an item from inventory.", COLOR_INFO)
elif key in "12345": elif key in (mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5):
index = int(key) - 1 index = [mcrfpy.Key.NUM_1, mcrfpy.Key.NUM_2, mcrfpy.Key.NUM_3, mcrfpy.Key.NUM_4, mcrfpy.Key.NUM_5].index(key)
if use_item(index): if use_item(index):
enemy_turn() enemy_turn()
update_ui() update_ui()
def handle_targeting_input(key: str) -> None: def handle_targeting_input(key) -> None:
if key in ("Up", "W"): if key in (mcrfpy.Key.UP, mcrfpy.Key.W):
move_cursor(0, -1) move_cursor(0, -1)
elif key in ("Down", "S"): elif key in (mcrfpy.Key.DOWN, mcrfpy.Key.S):
move_cursor(0, 1) move_cursor(0, 1)
elif key in ("Left", "A"): elif key in (mcrfpy.Key.LEFT, mcrfpy.Key.A):
move_cursor(-1, 0) move_cursor(-1, 0)
elif key in ("Right", "D"): elif key in (mcrfpy.Key.RIGHT, mcrfpy.Key.D):
move_cursor(1, 0) move_cursor(1, 0)
elif key in ("Return", "Space"): elif key in (mcrfpy.Key.ENTER, mcrfpy.Key.SPACE):
confirm_target() confirm_target()
scene.on_key = handle_keys scene.on_key = handle_keys