McRogueFace/docs/templates/roguelike/game.py

290 lines
8.2 KiB
Python

"""
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()