draft tutorial revisions
This commit is contained in:
parent
838da4571d
commit
48359b5a48
70 changed files with 6216 additions and 28 deletions
232
docs/templates/complete/turns.py
vendored
Normal file
232
docs/templates/complete/turns.py
vendored
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
"""
|
||||
turns.py - Turn Management System for McRogueFace Roguelike
|
||||
|
||||
Handles the turn-based game flow: player turn, then enemy turns.
|
||||
"""
|
||||
|
||||
from enum import Enum, auto
|
||||
from typing import List, Optional, Callable, TYPE_CHECKING
|
||||
|
||||
from entities import Player, Enemy
|
||||
from combat import try_attack, process_kill, CombatResult
|
||||
from ai import process_enemy_turns
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dungeon import Dungeon
|
||||
|
||||
|
||||
class GameState(Enum):
|
||||
"""Current state of the game."""
|
||||
PLAYER_TURN = auto() # Waiting for player input
|
||||
ENEMY_TURN = auto() # Processing enemy actions
|
||||
PLAYER_DEAD = auto() # Player has died
|
||||
VICTORY = auto() # Player has won (optional)
|
||||
LEVEL_TRANSITION = auto() # Moving to next level
|
||||
|
||||
|
||||
class TurnManager:
|
||||
"""
|
||||
Manages the turn-based game loop.
|
||||
|
||||
The game follows this flow:
|
||||
1. Player takes action (move or attack)
|
||||
2. If action was valid, enemies take turns
|
||||
3. Check for game over conditions
|
||||
4. Return to step 1
|
||||
"""
|
||||
|
||||
def __init__(self, player: Player, enemies: List[Enemy], dungeon: 'Dungeon'):
|
||||
"""
|
||||
Initialize the turn manager.
|
||||
|
||||
Args:
|
||||
player: The player entity
|
||||
enemies: List of all enemies
|
||||
dungeon: The dungeon map
|
||||
"""
|
||||
self.player = player
|
||||
self.enemies = enemies
|
||||
self.dungeon = dungeon
|
||||
self.state = GameState.PLAYER_TURN
|
||||
self.turn_count = 0
|
||||
|
||||
# Callbacks for game events
|
||||
self.on_message: Optional[Callable[[str, tuple], None]] = None
|
||||
self.on_player_death: Optional[Callable[[], None]] = None
|
||||
self.on_enemy_death: Optional[Callable[[Enemy], None]] = None
|
||||
self.on_turn_end: Optional[Callable[[int], None]] = None
|
||||
|
||||
def reset(self, player: Player, enemies: List[Enemy], dungeon: 'Dungeon') -> None:
|
||||
"""Reset the turn manager with new game state."""
|
||||
self.player = player
|
||||
self.enemies = enemies
|
||||
self.dungeon = dungeon
|
||||
self.state = GameState.PLAYER_TURN
|
||||
self.turn_count = 0
|
||||
|
||||
def add_message(self, message: str, color: tuple = (255, 255, 255, 255)) -> None:
|
||||
"""Add a message to the log via callback."""
|
||||
if self.on_message:
|
||||
self.on_message(message, color)
|
||||
|
||||
def handle_player_action(self, dx: int, dy: int) -> bool:
|
||||
"""
|
||||
Handle a player movement or attack action.
|
||||
|
||||
Args:
|
||||
dx: X direction (-1, 0, or 1)
|
||||
dy: Y direction (-1, 0, or 1)
|
||||
|
||||
Returns:
|
||||
True if the action consumed a turn, False otherwise
|
||||
"""
|
||||
if self.state != GameState.PLAYER_TURN:
|
||||
return False
|
||||
|
||||
target_x = self.player.x + dx
|
||||
target_y = self.player.y + dy
|
||||
|
||||
# Check for attack
|
||||
result = try_attack(self.player, target_x, target_y, self.enemies)
|
||||
|
||||
if result:
|
||||
# Player attacked something
|
||||
self.add_message(result.message, result.message_color)
|
||||
|
||||
if result.killed:
|
||||
# Process kill
|
||||
xp = process_kill(self.player, result.defender)
|
||||
self.enemies.remove(result.defender)
|
||||
|
||||
if xp > 0:
|
||||
self.add_message(f"You gain {xp} XP!", (255, 255, 100, 255))
|
||||
|
||||
if self.on_enemy_death:
|
||||
self.on_enemy_death(result.defender)
|
||||
|
||||
# Action consumed a turn
|
||||
self._end_player_turn()
|
||||
return True
|
||||
|
||||
# No attack - try to move
|
||||
if self.dungeon.is_walkable(target_x, target_y):
|
||||
# Check for enemy blocking
|
||||
blocked = False
|
||||
for enemy in self.enemies:
|
||||
if enemy.is_alive and enemy.x == target_x and enemy.y == target_y:
|
||||
blocked = True
|
||||
break
|
||||
|
||||
if not blocked:
|
||||
self.player.move_to(target_x, target_y)
|
||||
self._end_player_turn()
|
||||
return True
|
||||
|
||||
# Movement blocked
|
||||
return False
|
||||
|
||||
def handle_wait(self) -> bool:
|
||||
"""
|
||||
Handle the player choosing to wait (skip turn).
|
||||
|
||||
Returns:
|
||||
True (always consumes a turn)
|
||||
"""
|
||||
if self.state != GameState.PLAYER_TURN:
|
||||
return False
|
||||
|
||||
self.add_message("You wait...", (150, 150, 150, 255))
|
||||
self._end_player_turn()
|
||||
return True
|
||||
|
||||
def _end_player_turn(self) -> None:
|
||||
"""End the player's turn and process enemy turns."""
|
||||
self.state = GameState.ENEMY_TURN
|
||||
self._process_enemy_turns()
|
||||
|
||||
def _process_enemy_turns(self) -> None:
|
||||
"""Process all enemy turns."""
|
||||
# Get combat results from enemy actions
|
||||
results = process_enemy_turns(
|
||||
self.enemies,
|
||||
self.player,
|
||||
self.dungeon
|
||||
)
|
||||
|
||||
# Report results
|
||||
for result in results:
|
||||
self.add_message(result.message, result.message_color)
|
||||
|
||||
# Check if player died
|
||||
if not self.player.is_alive:
|
||||
self.state = GameState.PLAYER_DEAD
|
||||
if self.on_player_death:
|
||||
self.on_player_death()
|
||||
else:
|
||||
# End turn
|
||||
self.turn_count += 1
|
||||
self.state = GameState.PLAYER_TURN
|
||||
|
||||
if self.on_turn_end:
|
||||
self.on_turn_end(self.turn_count)
|
||||
|
||||
def is_player_turn(self) -> bool:
|
||||
"""Check if it's the player's turn."""
|
||||
return self.state == GameState.PLAYER_TURN
|
||||
|
||||
def is_game_over(self) -> bool:
|
||||
"""Check if the game is over (player dead)."""
|
||||
return self.state == GameState.PLAYER_DEAD
|
||||
|
||||
def get_enemy_count(self) -> int:
|
||||
"""Get the number of living enemies."""
|
||||
return sum(1 for e in self.enemies if e.is_alive)
|
||||
|
||||
|
||||
class ActionResult:
|
||||
"""Result of a player action."""
|
||||
|
||||
def __init__(self, success: bool, message: str = "",
|
||||
color: tuple = (255, 255, 255, 255)):
|
||||
self.success = success
|
||||
self.message = message
|
||||
self.color = color
|
||||
|
||||
|
||||
def try_move_or_attack(player: Player, dx: int, dy: int,
|
||||
dungeon: 'Dungeon', enemies: List[Enemy]) -> ActionResult:
|
||||
"""
|
||||
Attempt to move or attack in a direction.
|
||||
|
||||
This is a simpler, standalone function for games that don't want
|
||||
the full TurnManager.
|
||||
|
||||
Args:
|
||||
player: The player
|
||||
dx: X direction
|
||||
dy: Y direction
|
||||
dungeon: The dungeon map
|
||||
enemies: List of enemies
|
||||
|
||||
Returns:
|
||||
ActionResult indicating success and any message
|
||||
"""
|
||||
target_x = player.x + dx
|
||||
target_y = player.y + dy
|
||||
|
||||
# Check for attack
|
||||
for enemy in enemies:
|
||||
if enemy.is_alive and enemy.x == target_x and enemy.y == target_y:
|
||||
result = try_attack(player, target_x, target_y, enemies)
|
||||
if result:
|
||||
if result.killed:
|
||||
process_kill(player, enemy)
|
||||
enemies.remove(enemy)
|
||||
return ActionResult(True, result.message, result.message_color)
|
||||
|
||||
# Check for movement
|
||||
if dungeon.is_walkable(target_x, target_y):
|
||||
player.move_to(target_x, target_y)
|
||||
return ActionResult(True)
|
||||
|
||||
return ActionResult(False, "You can't move there!", (150, 150, 150, 255))
|
||||
Loading…
Add table
Add a link
Reference in a new issue