""" game.py - Roguelike Template Main Entry Point A minimal but complete roguelike starter using McRogueFace. This template demonstrates: - Scene and grid setup - Procedural dungeon generation - Player entity with keyboard movement - Enemy entities (static, no AI) - Field of view using TCOD via Entity.update_visibility() - FOV visualization with grid color overlays Run with: ./mcrogueface Controls: - Arrow keys / WASD: Move player - Escape: Quit game The template is designed to be extended. Good next steps: - Add enemy AI (chase player, pathfinding) - Implement combat system - Add items and inventory - Add multiple dungeon levels """ import mcrfpy from typing import List, Tuple # Import our template modules from constants import ( MAP_WIDTH, MAP_HEIGHT, SPRITE_WIDTH, SPRITE_HEIGHT, FOV_RADIUS, COLOR_VISIBLE, COLOR_EXPLORED, COLOR_UNKNOWN, SPRITE_PLAYER, ) from dungeon import generate_dungeon, populate_grid, RectangularRoom from entities import ( create_player, create_enemies_in_rooms, move_entity, EntityStats, ) # ============================================================================= # GAME STATE # ============================================================================= # Global game state - in a larger game, you'd use a proper state management # system, but for a template this keeps things simple and visible. class GameState: """Container for all game state.""" def __init__(self): # Core game objects (set during initialization) self.grid: mcrfpy.Grid = None self.player: mcrfpy.Entity = None self.rooms: List[RectangularRoom] = [] self.enemies: List[Tuple[mcrfpy.Entity, EntityStats]] = [] # Texture reference self.texture: mcrfpy.Texture = None # Global game state instance game = GameState() # ============================================================================= # FOV (FIELD OF VIEW) SYSTEM # ============================================================================= def update_fov() -> None: """ Update the field of view based on player position. This function: 1. Calls update_visibility() on the player entity to compute FOV using TCOD 2. Applies color overlays to tiles based on visibility state The FOV creates the classic roguelike effect where: - Visible tiles are fully bright (no overlay) - Previously seen tiles are dimmed (remembered layout) - Never-seen tiles are completely dark TCOD handles the actual FOV computation based on the grid's walkable and transparent flags set during dungeon generation. """ if not game.player or not game.grid: return # Tell McRogueFace/TCOD to recompute visibility from player position game.player.update_visibility() grid_width, grid_height = game.grid.grid_size # Apply visibility colors to each tile for x in range(grid_width): for y in range(grid_height): point = game.grid.at(x, y) # Get the player's visibility state for this tile state = game.player.at(x, y) if state.visible: # Currently visible - no overlay (full brightness) point.color_overlay = COLOR_VISIBLE elif state.discovered: # Previously seen - dimmed overlay (memory) point.color_overlay = COLOR_EXPLORED else: # Never seen - completely dark point.color_overlay = COLOR_UNKNOWN # ============================================================================= # INPUT HANDLING # ============================================================================= def handle_keys(key: str, state: str) -> None: """ Handle keyboard input for player movement and game controls. This is the main input handler registered with McRogueFace. It processes key events and updates game state accordingly. Args: key: The key that was pressed (e.g., "W", "Up", "Escape") state: Either "start" (key pressed) or "end" (key released) """ # Only process key press events, not releases if state != "start": return # Movement deltas: (dx, dy) movement = { # Arrow keys "Up": (0, -1), "Down": (0, 1), "Left": (-1, 0), "Right": (1, 0), # WASD keys "W": (0, -1), "S": (0, 1), "A": (-1, 0), "D": (1, 0), # Numpad (for diagonal movement if desired) "Numpad8": (0, -1), "Numpad2": (0, 1), "Numpad4": (-1, 0), "Numpad6": (1, 0), "Numpad7": (-1, -1), "Numpad9": (1, -1), "Numpad1": (-1, 1), "Numpad3": (1, 1), } if key in movement: dx, dy = movement[key] # Get list of all entity objects for collision checking all_entities = [e for e, _ in game.enemies] # Attempt to move the player if move_entity(game.player, game.grid, dx, dy, all_entities): # Movement succeeded - update FOV update_fov() # Center camera on player px, py = game.player.pos game.grid.center = (px, py) elif key == "Escape": # Quit the game mcrfpy.exit() # ============================================================================= # GAME INITIALIZATION # ============================================================================= def initialize_game() -> None: """ Set up the game world. This function: 1. Creates the scene and loads resources 2. Generates the dungeon layout 3. Creates and places all entities 4. Initializes the FOV system 5. Sets up input handling """ # Create the game scene mcrfpy.createScene("game") ui = mcrfpy.sceneUI("game") # Load the tileset texture # The default McRogueFace texture works great for roguelikes game.texture = mcrfpy.Texture( "assets/kenney_tinydungeon.png", SPRITE_WIDTH, SPRITE_HEIGHT ) # Create the grid (tile-based game world) # Using keyword arguments for clarity - this is the preferred style game.grid = mcrfpy.Grid( pos=(0, 0), # Screen position in pixels size=(1024, 768), # Display size in pixels grid_size=(MAP_WIDTH, MAP_HEIGHT), # Map size in tiles texture=game.texture ) ui.append(game.grid) # Generate dungeon layout game.rooms = generate_dungeon() # Apply dungeon to grid (sets tiles, walkable flags, etc.) populate_grid(game.grid, game.rooms) # Place player in the center of the first room if game.rooms: start_x, start_y = game.rooms[0].center else: # Fallback if no rooms generated start_x, start_y = MAP_WIDTH // 2, MAP_HEIGHT // 2 game.player = create_player( grid=game.grid, texture=game.texture, x=start_x, y=start_y ) # Center camera on player game.grid.center = (start_x, start_y) # Spawn enemies in other rooms game.enemies = create_enemies_in_rooms( grid=game.grid, texture=game.texture, rooms=game.rooms, enemies_per_room=2, skip_first_room=True ) # Initial FOV calculation update_fov() # Register input handler mcrfpy.keypressScene(handle_keys) # Switch to game scene mcrfpy.setScene("game") # ============================================================================= # MAIN ENTRY POINT # ============================================================================= def main() -> None: """ Main entry point for the roguelike template. This function is called when the script starts. It initializes the game and McRogueFace handles the game loop automatically. """ initialize_game() # Display welcome message print("=" * 50) print(" ROGUELIKE TEMPLATE") print("=" * 50) print("Controls:") print(" Arrow keys / WASD - Move") print(" Escape - Quit") print() print(f"Dungeon generated with {len(game.rooms)} rooms") print(f"Enemies spawned: {len(game.enemies)}") print("=" * 50) # Run the game if __name__ == "__main__": main() else: # McRogueFace runs game.py directly, not as __main__ main()