Draft tutorials

This commit is contained in:
John McCardle 2025-07-09 22:34:43 -04:00
parent 9be3161a24
commit 64ffe1f699
9 changed files with 2070 additions and 0 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ obj
build build
lib lib
obj obj
__pycache__
.cache/ .cache/
7DRL2025 Release/ 7DRL2025 Release/

View File

@ -0,0 +1,33 @@
import mcrfpy
# Create a new scene called "hello"
mcrfpy.createScene("hello")
# Switch to our new scene
mcrfpy.setScene("hello")
# Get the UI container for our scene
ui = mcrfpy.sceneUI("hello")
# Create a text caption
caption = mcrfpy.Caption("Hello Roguelike!", 400, 300)
caption.font_size = 32
caption.fill_color = mcrfpy.Color(255, 255, 255) # White text
# Add the caption to our scene
ui.append(caption)
# Create a smaller instruction caption
instruction = mcrfpy.Caption("Press ESC to exit", 400, 350)
instruction.font_size = 16
instruction.fill_color = mcrfpy.Color(200, 200, 200) # Light gray
ui.append(instruction)
# Set up a simple key handler
def handle_keys(key, state):
if state == "start" and key == "Escape":
mcrfpy.setScene(None) # This exits the game
mcrfpy.keypressScene(handle_keys)
print("Hello Roguelike is running!")

View File

@ -0,0 +1,55 @@
import mcrfpy
# Create our test scene
mcrfpy.createScene("test")
mcrfpy.setScene("test")
ui = mcrfpy.sceneUI("test")
# Create a background frame
background = mcrfpy.Frame(0, 0, 1024, 768)
background.fill_color = mcrfpy.Color(20, 20, 30) # Dark blue-gray
ui.append(background)
# Title text
title = mcrfpy.Caption("McRogueFace Setup Test", 512, 100)
title.font_size = 36
title.fill_color = mcrfpy.Color(255, 255, 100) # Yellow
ui.append(title)
# Status text that will update
status_text = mcrfpy.Caption("Press any key to test input...", 512, 300)
status_text.font_size = 20
status_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(status_text)
# Instructions
instructions = [
"Arrow Keys: Test movement input",
"Space: Test action input",
"Mouse Click: Test mouse input",
"ESC: Exit"
]
y_offset = 400
for instruction in instructions:
inst_caption = mcrfpy.Caption(instruction, 512, y_offset)
inst_caption.font_size = 16
inst_caption.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(inst_caption)
y_offset += 30
# Input handler
def handle_input(key, state):
if state != "start":
return
if key == "Escape":
mcrfpy.setScene(None)
else:
status_text.text = f"You pressed: {key}"
status_text.fill_color = mcrfpy.Color(100, 255, 100) # Green
# Set up input handling
mcrfpy.keypressScene(handle_input)
print("Setup test is running! Try pressing different keys.")

View File

@ -0,0 +1,162 @@
import mcrfpy
# Window configuration
mcrfpy.createScene("game")
mcrfpy.setScene("game")
window = mcrfpy.Window.get()
window.title = "McRogueFace Roguelike - Part 1"
# Get the UI container for our scene
ui = mcrfpy.sceneUI("game")
# Create a dark background
background = mcrfpy.Frame(0, 0, 1024, 768)
background.fill_color = mcrfpy.Color(0, 0, 0)
ui.append(background)
# Load the ASCII tileset
tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
# Create the game grid
GRID_WIDTH = 50
GRID_HEIGHT = 30
grid = mcrfpy.Grid(grid_x=GRID_WIDTH, grid_y=GRID_HEIGHT, texture=tileset)
grid.position = (100, 100)
grid.size = (800, 480)
ui.append(grid)
def create_room():
"""Create a room with walls around the edges"""
# Fill everything with floor tiles first
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
cell.sprite_index = 46 # '.' character
cell.color = mcrfpy.Color(50, 50, 50) # Dark gray floor
# Create walls around the edges
for x in range(GRID_WIDTH):
# Top wall
cell = grid.at(x, 0)
cell.walkable = False
cell.transparent = False
cell.sprite_index = 35 # '#' character
cell.color = mcrfpy.Color(100, 100, 100) # Gray walls
# Bottom wall
cell = grid.at(x, GRID_HEIGHT - 1)
cell.walkable = False
cell.transparent = False
cell.sprite_index = 35 # '#' character
cell.color = mcrfpy.Color(100, 100, 100)
for y in range(GRID_HEIGHT):
# Left wall
cell = grid.at(0, y)
cell.walkable = False
cell.transparent = False
cell.sprite_index = 35 # '#' character
cell.color = mcrfpy.Color(100, 100, 100)
# Right wall
cell = grid.at(GRID_WIDTH - 1, y)
cell.walkable = False
cell.transparent = False
cell.sprite_index = 35 # '#' character
cell.color = mcrfpy.Color(100, 100, 100)
# Create the room
create_room()
# Create the player entity
player = mcrfpy.Entity(x=GRID_WIDTH // 2, y=GRID_HEIGHT // 2, grid=grid)
player.sprite_index = 64 # '@' character
player.color = mcrfpy.Color(255, 255, 255) # White
def move_player(dx, dy):
"""Move the player if the destination is walkable"""
# Calculate new position
new_x = player.x + dx
new_y = player.y + dy
# Check bounds
if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT:
return
# Check if the destination is walkable
destination = grid.at(new_x, new_y)
if destination.walkable:
# Move the player
player.x = new_x
player.y = new_y
def handle_input(key, state):
"""Handle keyboard input for player movement"""
# Only process key presses, not releases
if state != "start":
return
# Movement deltas
dx, dy = 0, 0
# Arrow keys
if key == "Up":
dy = -1
elif key == "Down":
dy = 1
elif key == "Left":
dx = -1
elif key == "Right":
dx = 1
# Numpad movement (for true roguelike feel!)
elif key == "Num7": # Northwest
dx, dy = -1, -1
elif key == "Num8": # North
dy = -1
elif key == "Num9": # Northeast
dx, dy = 1, -1
elif key == "Num4": # West
dx = -1
elif key == "Num6": # East
dx = 1
elif key == "Num1": # Southwest
dx, dy = -1, 1
elif key == "Num2": # South
dy = 1
elif key == "Num3": # Southeast
dx, dy = 1, 1
# Escape to quit
elif key == "Escape":
mcrfpy.setScene(None)
return
# If there's movement, try to move the player
if dx != 0 or dy != 0:
move_player(dx, dy)
# Register the input handler
mcrfpy.keypressScene(handle_input)
# Add UI elements
title = mcrfpy.Caption("McRogueFace Roguelike", 512, 30)
title.font_size = 24
title.fill_color = mcrfpy.Color(255, 255, 100)
ui.append(title)
instructions = mcrfpy.Caption("Arrow Keys or Numpad to move, ESC to quit", 512, 60)
instructions.font_size = 16
instructions.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(instructions)
status = mcrfpy.Caption("@ You", 100, 600)
status.font_size = 18
status.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(status)
print("Part 1: The @ symbol moves!")

View File

@ -0,0 +1,217 @@
import mcrfpy
class GameObject:
"""Base class for all game objects (player, monsters, items)"""
def __init__(self, x, y, sprite_index, color, name, blocks=False):
self.x = x
self.y = y
self.sprite_index = sprite_index
self.color = color
self.name = name
self.blocks = blocks
self._entity = None
self.grid = None
def attach_to_grid(self, grid):
"""Attach this game object to a McRogueFace grid"""
self.grid = grid
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
self._entity.sprite_index = self.sprite_index
self._entity.color = mcrfpy.Color(*self.color)
def move(self, dx, dy):
"""Move by the given amount if possible"""
if not self.grid:
return
new_x = self.x + dx
new_y = self.y + dy
self.x = new_x
self.y = new_y
if self._entity:
self._entity.x = new_x
self._entity.y = new_y
class GameMap:
"""Manages the game world"""
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = None
self.entities = []
def create_grid(self, tileset):
"""Create the McRogueFace grid"""
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
self.grid.position = (100, 100)
self.grid.size = (800, 480)
self.fill_with_walls()
return self.grid
def fill_with_walls(self):
"""Fill the entire map with wall tiles"""
for y in range(self.height):
for x in range(self.width):
self.set_tile(x, y, walkable=False, transparent=False,
sprite_index=35, color=(100, 100, 100))
def set_tile(self, x, y, walkable, transparent, sprite_index, color):
"""Set properties for a specific tile"""
if 0 <= x < self.width and 0 <= y < self.height:
cell = self.grid.at(x, y)
cell.walkable = walkable
cell.transparent = transparent
cell.sprite_index = sprite_index
cell.color = mcrfpy.Color(*color)
def create_room(self, x1, y1, x2, y2):
"""Carve out a room in the map"""
x1, x2 = min(x1, x2), max(x1, x2)
y1, y2 = min(y1, y2), max(y1, y2)
for y in range(y1, y2 + 1):
for x in range(x1, x2 + 1):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, color=(50, 50, 50))
def create_tunnel_h(self, x1, x2, y):
"""Create a horizontal tunnel"""
for x in range(min(x1, x2), max(x1, x2) + 1):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, color=(50, 50, 50))
def create_tunnel_v(self, y1, y2, x):
"""Create a vertical tunnel"""
for y in range(min(y1, y2), max(y1, y2) + 1):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, color=(50, 50, 50))
def is_blocked(self, x, y):
"""Check if a tile blocks movement"""
if x < 0 or x >= self.width or y < 0 or y >= self.height:
return True
if not self.grid.at(x, y).walkable:
return True
for entity in self.entities:
if entity.blocks and entity.x == x and entity.y == y:
return True
return False
def add_entity(self, entity):
"""Add a GameObject to the map"""
self.entities.append(entity)
entity.attach_to_grid(self.grid)
def get_blocking_entity_at(self, x, y):
"""Return any blocking entity at the given position"""
for entity in self.entities:
if entity.blocks and entity.x == x and entity.y == y:
return entity
return None
class Engine:
"""Main game engine that manages game state"""
def __init__(self):
self.game_map = None
self.player = None
self.entities = []
mcrfpy.createScene("game")
mcrfpy.setScene("game")
window = mcrfpy.Window.get()
window.title = "McRogueFace Roguelike - Part 2"
self.ui = mcrfpy.sceneUI("game")
background = mcrfpy.Frame(0, 0, 1024, 768)
background.fill_color = mcrfpy.Color(0, 0, 0)
self.ui.append(background)
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
self.setup_game()
self.setup_input()
self.setup_ui()
def setup_game(self):
"""Initialize the game world"""
self.game_map = GameMap(50, 30)
grid = self.game_map.create_grid(self.tileset)
self.ui.append(grid)
self.game_map.create_room(10, 10, 20, 20)
self.game_map.create_room(30, 15, 40, 25)
self.game_map.create_room(15, 22, 25, 28)
self.game_map.create_tunnel_h(20, 30, 15)
self.game_map.create_tunnel_v(20, 22, 20)
self.player = GameObject(15, 15, 64, (255, 255, 255), "Player", blocks=True)
self.game_map.add_entity(self.player)
npc = GameObject(35, 20, 64, (255, 255, 0), "NPC", blocks=True)
self.game_map.add_entity(npc)
self.entities.append(npc)
potion = GameObject(12, 12, 33, (255, 0, 255), "Potion", blocks=False)
self.game_map.add_entity(potion)
self.entities.append(potion)
def handle_movement(self, dx, dy):
"""Handle player movement"""
new_x = self.player.x + dx
new_y = self.player.y + dy
if not self.game_map.is_blocked(new_x, new_y):
self.player.move(dx, dy)
else:
target = self.game_map.get_blocking_entity_at(new_x, new_y)
if target:
print(f"You bump into the {target.name}!")
def setup_input(self):
"""Setup keyboard input handling"""
def handle_keys(key, state):
if state != "start":
return
movement = {
"Up": (0, -1), "Down": (0, 1),
"Left": (-1, 0), "Right": (1, 0),
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
"Num4": (-1, 0), "Num6": (1, 0),
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
}
if key in movement:
dx, dy = movement[key]
self.handle_movement(dx, dy)
elif key == "Escape":
mcrfpy.setScene(None)
mcrfpy.keypressScene(handle_keys)
def setup_ui(self):
"""Setup UI elements"""
title = mcrfpy.Caption("McRogueFace Roguelike - Part 2", 512, 30)
title.font_size = 24
title.fill_color = mcrfpy.Color(255, 255, 100)
self.ui.append(title)
instructions = mcrfpy.Caption("Explore the dungeon! ESC to quit", 512, 60)
instructions.font_size = 16
instructions.fill_color = mcrfpy.Color(200, 200, 200)
self.ui.append(instructions)
# Create and run the game
engine = Engine()
print("Part 2: Entities and Maps!")

View File

@ -0,0 +1,312 @@
import mcrfpy
import random
class GameObject:
"""Base class for all game objects"""
def __init__(self, x, y, sprite_index, color, name, blocks=False):
self.x = x
self.y = y
self.sprite_index = sprite_index
self.color = color
self.name = name
self.blocks = blocks
self._entity = None
self.grid = None
def attach_to_grid(self, grid):
"""Attach this game object to a McRogueFace grid"""
self.grid = grid
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
self._entity.sprite_index = self.sprite_index
self._entity.color = mcrfpy.Color(*self.color)
def move(self, dx, dy):
"""Move by the given amount"""
if not self.grid:
return
self.x += dx
self.y += dy
if self._entity:
self._entity.x = self.x
self._entity.y = self.y
class RectangularRoom:
"""A rectangular room with its position and size"""
def __init__(self, x, y, width, height):
self.x1 = x
self.y1 = y
self.x2 = x + width
self.y2 = y + height
@property
def center(self):
"""Return the center coordinates of the room"""
center_x = (self.x1 + self.x2) // 2
center_y = (self.y1 + self.y2) // 2
return center_x, center_y
@property
def inner(self):
"""Return the inner area of the room"""
return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1
def intersects(self, other):
"""Return True if this room overlaps with another"""
return (
self.x1 <= other.x2
and self.x2 >= other.x1
and self.y1 <= other.y2
and self.y2 >= other.y1
)
def tunnel_between(start, end):
"""Return an L-shaped tunnel between two points"""
x1, y1 = start
x2, y2 = end
if random.random() < 0.5:
corner_x = x2
corner_y = y1
else:
corner_x = x1
corner_y = y2
# Generate the coordinates
for x in range(min(x1, corner_x), max(x1, corner_x) + 1):
yield x, y1
for y in range(min(y1, corner_y), max(y1, corner_y) + 1):
yield corner_x, y
for x in range(min(corner_x, x2), max(corner_x, x2) + 1):
yield x, corner_y
for y in range(min(corner_y, y2), max(corner_y, y2) + 1):
yield x2, y
class GameMap:
"""Manages the game world"""
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = None
self.entities = []
self.rooms = []
def create_grid(self, tileset):
"""Create the McRogueFace grid"""
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
self.grid.position = (100, 100)
self.grid.size = (800, 480)
return self.grid
def fill_with_walls(self):
"""Fill the entire map with wall tiles"""
for y in range(self.height):
for x in range(self.width):
self.set_tile(x, y, walkable=False, transparent=False,
sprite_index=35, color=(100, 100, 100))
def set_tile(self, x, y, walkable, transparent, sprite_index, color):
"""Set properties for a specific tile"""
if 0 <= x < self.width and 0 <= y < self.height:
cell = self.grid.at(x, y)
cell.walkable = walkable
cell.transparent = transparent
cell.sprite_index = sprite_index
cell.color = mcrfpy.Color(*color)
def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player):
"""Generate a new dungeon map"""
self.fill_with_walls()
for r in range(max_rooms):
room_width = random.randint(room_min_size, room_max_size)
room_height = random.randint(room_min_size, room_max_size)
x = random.randint(0, self.width - room_width - 1)
y = random.randint(0, self.height - room_height - 1)
new_room = RectangularRoom(x, y, room_width, room_height)
if any(new_room.intersects(other_room) for other_room in self.rooms):
continue
self.carve_room(new_room)
if len(self.rooms) == 0:
player.x, player.y = new_room.center
if player._entity:
player._entity.x, player._entity.y = new_room.center
else:
self.carve_tunnel(self.rooms[-1].center, new_room.center)
self.rooms.append(new_room)
def carve_room(self, room):
"""Carve out a room"""
inner_x1, inner_y1, inner_x2, inner_y2 = room.inner
for y in range(inner_y1, inner_y2):
for x in range(inner_x1, inner_x2):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, color=(50, 50, 50))
def carve_tunnel(self, start, end):
"""Carve a tunnel between two points"""
for x, y in tunnel_between(start, end):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, color=(30, 30, 40))
def is_blocked(self, x, y):
"""Check if a tile blocks movement"""
if x < 0 or x >= self.width or y < 0 or y >= self.height:
return True
if not self.grid.at(x, y).walkable:
return True
for entity in self.entities:
if entity.blocks and entity.x == x and entity.y == y:
return True
return False
def add_entity(self, entity):
"""Add a GameObject to the map"""
self.entities.append(entity)
entity.attach_to_grid(self.grid)
class Engine:
"""Main game engine"""
def __init__(self):
self.game_map = None
self.player = None
self.entities = []
mcrfpy.createScene("game")
mcrfpy.setScene("game")
window = mcrfpy.Window.get()
window.title = "McRogueFace Roguelike - Part 3"
self.ui = mcrfpy.sceneUI("game")
background = mcrfpy.Frame(0, 0, 1024, 768)
background.fill_color = mcrfpy.Color(0, 0, 0)
self.ui.append(background)
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
self.setup_game()
self.setup_input()
self.setup_ui()
def setup_game(self):
"""Initialize the game world"""
self.game_map = GameMap(80, 45)
grid = self.game_map.create_grid(self.tileset)
self.ui.append(grid)
# Create player (before dungeon generation)
self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True)
# Generate the dungeon
self.game_map.generate_dungeon(
max_rooms=30,
room_min_size=6,
room_max_size=10,
player=self.player
)
# Add player to map
self.game_map.add_entity(self.player)
# Add some monsters in random rooms
for i in range(5):
if i < len(self.game_map.rooms) - 1: # Don't spawn in first room
room = self.game_map.rooms[i + 1]
x, y = room.center
# Create an orc
orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True)
self.game_map.add_entity(orc)
self.entities.append(orc)
def handle_movement(self, dx, dy):
"""Handle player movement"""
new_x = self.player.x + dx
new_y = self.player.y + dy
if not self.game_map.is_blocked(new_x, new_y):
self.player.move(dx, dy)
def setup_input(self):
"""Setup keyboard input handling"""
def handle_keys(key, state):
if state != "start":
return
movement = {
"Up": (0, -1), "Down": (0, 1),
"Left": (-1, 0), "Right": (1, 0),
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
"Num4": (-1, 0), "Num6": (1, 0),
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
}
if key in movement:
dx, dy = movement[key]
self.handle_movement(dx, dy)
elif key == "Escape":
mcrfpy.setScene(None)
elif key == "Space":
# Regenerate the dungeon
self.regenerate_dungeon()
mcrfpy.keypressScene(handle_keys)
def regenerate_dungeon(self):
"""Generate a new dungeon"""
# Clear existing entities
self.game_map.entities.clear()
self.game_map.rooms.clear()
self.entities.clear()
# Clear the entity list in the grid
if self.game_map.grid:
self.game_map.grid.entities.clear()
# Regenerate
self.game_map.generate_dungeon(
max_rooms=30,
room_min_size=6,
room_max_size=10,
player=self.player
)
# Re-add player
self.game_map.add_entity(self.player)
# Add new monsters
for i in range(5):
if i < len(self.game_map.rooms) - 1:
room = self.game_map.rooms[i + 1]
x, y = room.center
orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True)
self.game_map.add_entity(orc)
self.entities.append(orc)
def setup_ui(self):
"""Setup UI elements"""
title = mcrfpy.Caption("Procedural Dungeon Generation", 512, 30)
title.font_size = 24
title.fill_color = mcrfpy.Color(255, 255, 100)
self.ui.append(title)
instructions = mcrfpy.Caption("Arrow keys to move, SPACE to regenerate, ESC to quit", 512, 60)
instructions.font_size = 16
instructions.fill_color = mcrfpy.Color(200, 200, 200)
self.ui.append(instructions)
# Create and run the game
engine = Engine()
print("Part 3: Procedural Dungeon Generation!")
print("Press SPACE to generate a new dungeon")

View File

@ -0,0 +1,334 @@
import mcrfpy
import random
# Color configurations for visibility
COLORS_VISIBLE = {
'wall': (100, 100, 100),
'floor': (50, 50, 50),
'tunnel': (30, 30, 40),
}
class GameObject:
"""Base class for all game objects"""
def __init__(self, x, y, sprite_index, color, name, blocks=False):
self.x = x
self.y = y
self.sprite_index = sprite_index
self.color = color
self.name = name
self.blocks = blocks
self._entity = None
self.grid = None
def attach_to_grid(self, grid):
"""Attach this game object to a McRogueFace grid"""
self.grid = grid
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
self._entity.sprite_index = self.sprite_index
self._entity.color = mcrfpy.Color(*self.color)
def move(self, dx, dy):
"""Move by the given amount"""
if not self.grid:
return
self.x += dx
self.y += dy
if self._entity:
self._entity.x = self.x
self._entity.y = self.y
# Update FOV when player moves
if self.name == "Player":
self.update_fov()
def update_fov(self):
"""Update field of view from this entity's position"""
if self._entity and self.grid:
self._entity.update_fov(radius=8)
class RectangularRoom:
"""A rectangular room with its position and size"""
def __init__(self, x, y, width, height):
self.x1 = x
self.y1 = y
self.x2 = x + width
self.y2 = y + height
@property
def center(self):
center_x = (self.x1 + self.x2) // 2
center_y = (self.y1 + self.y2) // 2
return center_x, center_y
@property
def inner(self):
return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1
def intersects(self, other):
return (
self.x1 <= other.x2
and self.x2 >= other.x1
and self.y1 <= other.y2
and self.y2 >= other.y1
)
def tunnel_between(start, end):
"""Return an L-shaped tunnel between two points"""
x1, y1 = start
x2, y2 = end
if random.random() < 0.5:
corner_x = x2
corner_y = y1
else:
corner_x = x1
corner_y = y2
for x in range(min(x1, corner_x), max(x1, corner_x) + 1):
yield x, y1
for y in range(min(y1, corner_y), max(y1, corner_y) + 1):
yield corner_x, y
for x in range(min(corner_x, x2), max(corner_x, x2) + 1):
yield x, corner_y
for y in range(min(corner_y, y2), max(corner_y, y2) + 1):
yield x2, y
class GameMap:
"""Manages the game world"""
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = None
self.entities = []
self.rooms = []
def create_grid(self, tileset):
"""Create the McRogueFace grid"""
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
self.grid.position = (100, 100)
self.grid.size = (800, 480)
# Enable perspective rendering (0 = first entity = player)
self.grid.perspective = 0
return self.grid
def fill_with_walls(self):
"""Fill the entire map with wall tiles"""
for y in range(self.height):
for x in range(self.width):
self.set_tile(x, y, walkable=False, transparent=False,
sprite_index=35, tile_type='wall')
def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type):
"""Set properties for a specific tile"""
if 0 <= x < self.width and 0 <= y < self.height:
cell = self.grid.at(x, y)
cell.walkable = walkable
cell.transparent = transparent
cell.sprite_index = sprite_index
cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type])
def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player):
"""Generate a new dungeon map"""
self.fill_with_walls()
for r in range(max_rooms):
room_width = random.randint(room_min_size, room_max_size)
room_height = random.randint(room_min_size, room_max_size)
x = random.randint(0, self.width - room_width - 1)
y = random.randint(0, self.height - room_height - 1)
new_room = RectangularRoom(x, y, room_width, room_height)
if any(new_room.intersects(other_room) for other_room in self.rooms):
continue
self.carve_room(new_room)
if len(self.rooms) == 0:
player.x, player.y = new_room.center
if player._entity:
player._entity.x, player._entity.y = new_room.center
else:
self.carve_tunnel(self.rooms[-1].center, new_room.center)
self.rooms.append(new_room)
def carve_room(self, room):
"""Carve out a room"""
inner_x1, inner_y1, inner_x2, inner_y2 = room.inner
for y in range(inner_y1, inner_y2):
for x in range(inner_x1, inner_x2):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, tile_type='floor')
def carve_tunnel(self, start, end):
"""Carve a tunnel between two points"""
for x, y in tunnel_between(start, end):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, tile_type='tunnel')
def is_blocked(self, x, y):
"""Check if a tile blocks movement"""
if x < 0 or x >= self.width or y < 0 or y >= self.height:
return True
if not self.grid.at(x, y).walkable:
return True
for entity in self.entities:
if entity.blocks and entity.x == x and entity.y == y:
return True
return False
def add_entity(self, entity):
"""Add a GameObject to the map"""
self.entities.append(entity)
entity.attach_to_grid(self.grid)
class Engine:
"""Main game engine"""
def __init__(self):
self.game_map = None
self.player = None
self.entities = []
self.fov_radius = 8
mcrfpy.createScene("game")
mcrfpy.setScene("game")
window = mcrfpy.Window.get()
window.title = "McRogueFace Roguelike - Part 4"
self.ui = mcrfpy.sceneUI("game")
background = mcrfpy.Frame(0, 0, 1024, 768)
background.fill_color = mcrfpy.Color(0, 0, 0)
self.ui.append(background)
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
self.setup_game()
self.setup_input()
self.setup_ui()
def setup_game(self):
"""Initialize the game world"""
self.game_map = GameMap(80, 45)
grid = self.game_map.create_grid(self.tileset)
self.ui.append(grid)
# Create player
self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True)
# Generate the dungeon
self.game_map.generate_dungeon(
max_rooms=30,
room_min_size=6,
room_max_size=10,
player=self.player
)
# Add player to map
self.game_map.add_entity(self.player)
# Add monsters in random rooms
for i in range(10):
if i < len(self.game_map.rooms) - 1:
room = self.game_map.rooms[i + 1]
x, y = room.center
# Randomly offset from center
x += random.randint(-2, 2)
y += random.randint(-2, 2)
# Make sure position is walkable
if self.game_map.grid.at(x, y).walkable:
if i % 2 == 0:
# Create an orc
orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True)
self.game_map.add_entity(orc)
self.entities.append(orc)
else:
# Create a troll
troll = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True)
self.game_map.add_entity(troll)
self.entities.append(troll)
# Initial FOV calculation
self.player.update_fov()
def handle_movement(self, dx, dy):
"""Handle player movement"""
new_x = self.player.x + dx
new_y = self.player.y + dy
if not self.game_map.is_blocked(new_x, new_y):
self.player.move(dx, dy)
def setup_input(self):
"""Setup keyboard input handling"""
def handle_keys(key, state):
if state != "start":
return
movement = {
"Up": (0, -1), "Down": (0, 1),
"Left": (-1, 0), "Right": (1, 0),
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
"Num4": (-1, 0), "Num6": (1, 0),
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
}
if key in movement:
dx, dy = movement[key]
self.handle_movement(dx, dy)
elif key == "Escape":
mcrfpy.setScene(None)
elif key == "v":
# Toggle FOV on/off
if self.game_map.grid.perspective == 0:
self.game_map.grid.perspective = -1 # Omniscient
print("FOV disabled - omniscient view")
else:
self.game_map.grid.perspective = 0 # Player perspective
print("FOV enabled - player perspective")
elif key == "Plus" or key == "Equals":
# Increase FOV radius
self.fov_radius = min(self.fov_radius + 1, 20)
self.player._entity.update_fov(radius=self.fov_radius)
print(f"FOV radius: {self.fov_radius}")
elif key == "Minus":
# Decrease FOV radius
self.fov_radius = max(self.fov_radius - 1, 3)
self.player._entity.update_fov(radius=self.fov_radius)
print(f"FOV radius: {self.fov_radius}")
mcrfpy.keypressScene(handle_keys)
def setup_ui(self):
"""Setup UI elements"""
title = mcrfpy.Caption("Field of View", 512, 30)
title.font_size = 24
title.fill_color = mcrfpy.Color(255, 255, 100)
self.ui.append(title)
instructions = mcrfpy.Caption("Arrow keys to move | V to toggle FOV | +/- to adjust radius | ESC to quit", 512, 60)
instructions.font_size = 16
instructions.fill_color = mcrfpy.Color(200, 200, 200)
self.ui.append(instructions)
# FOV indicator
self.fov_text = mcrfpy.Caption(f"FOV Radius: {self.fov_radius}", 900, 100)
self.fov_text.font_size = 14
self.fov_text.fill_color = mcrfpy.Color(150, 200, 255)
self.ui.append(self.fov_text)
# Create and run the game
engine = Engine()
print("Part 4: Field of View!")
print("Press V to toggle FOV on/off")
print("Press +/- to adjust FOV radius")

View File

@ -0,0 +1,388 @@
import mcrfpy
import random
# Color configurations
COLORS_VISIBLE = {
'wall': (100, 100, 100),
'floor': (50, 50, 50),
'tunnel': (30, 30, 40),
}
# Actions
class Action:
"""Base class for all actions"""
pass
class MovementAction(Action):
"""Action for moving an entity"""
def __init__(self, dx, dy):
self.dx = dx
self.dy = dy
class WaitAction(Action):
"""Action for waiting/skipping turn"""
pass
class GameObject:
"""Base class for all game objects"""
def __init__(self, x, y, sprite_index, color, name, blocks=False):
self.x = x
self.y = y
self.sprite_index = sprite_index
self.color = color
self.name = name
self.blocks = blocks
self._entity = None
self.grid = None
def attach_to_grid(self, grid):
"""Attach this game object to a McRogueFace grid"""
self.grid = grid
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
self._entity.sprite_index = self.sprite_index
self._entity.color = mcrfpy.Color(*self.color)
def move(self, dx, dy):
"""Move by the given amount"""
if not self.grid:
return
self.x += dx
self.y += dy
if self._entity:
self._entity.x = self.x
self._entity.y = self.y
# Update FOV when player moves
if self.name == "Player":
self.update_fov()
def update_fov(self):
"""Update field of view from this entity's position"""
if self._entity and self.grid:
self._entity.update_fov(radius=8)
class RectangularRoom:
"""A rectangular room with its position and size"""
def __init__(self, x, y, width, height):
self.x1 = x
self.y1 = y
self.x2 = x + width
self.y2 = y + height
@property
def center(self):
center_x = (self.x1 + self.x2) // 2
center_y = (self.y1 + self.y2) // 2
return center_x, center_y
@property
def inner(self):
return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1
def intersects(self, other):
return (
self.x1 <= other.x2
and self.x2 >= other.x1
and self.y1 <= other.y2
and self.y2 >= other.y1
)
def tunnel_between(start, end):
"""Return an L-shaped tunnel between two points"""
x1, y1 = start
x2, y2 = end
if random.random() < 0.5:
corner_x = x2
corner_y = y1
else:
corner_x = x1
corner_y = y2
for x in range(min(x1, corner_x), max(x1, corner_x) + 1):
yield x, y1
for y in range(min(y1, corner_y), max(y1, corner_y) + 1):
yield corner_x, y
for x in range(min(corner_x, x2), max(corner_x, x2) + 1):
yield x, corner_y
for y in range(min(corner_y, y2), max(corner_y, y2) + 1):
yield x2, y
def spawn_enemies_in_room(room, game_map, max_enemies=2):
"""Spawn between 0 and max_enemies in a room"""
number_of_enemies = random.randint(0, max_enemies)
enemies_spawned = []
for i in range(number_of_enemies):
# Try to find a valid position
attempts = 10
while attempts > 0:
# Random position within room bounds
x = random.randint(room.x1 + 1, room.x2 - 1)
y = random.randint(room.y1 + 1, room.y2 - 1)
# Check if position is valid
if not game_map.is_blocked(x, y):
# 80% chance for orc, 20% for troll
if random.random() < 0.8:
enemy = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True)
else:
enemy = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True)
game_map.add_entity(enemy)
enemies_spawned.append(enemy)
break
attempts -= 1
return enemies_spawned
class GameMap:
"""Manages the game world"""
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = None
self.entities = []
self.rooms = []
def create_grid(self, tileset):
"""Create the McRogueFace grid"""
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
self.grid.position = (100, 100)
self.grid.size = (800, 480)
# Enable perspective rendering
self.grid.perspective = 0
return self.grid
def fill_with_walls(self):
"""Fill the entire map with wall tiles"""
for y in range(self.height):
for x in range(self.width):
self.set_tile(x, y, walkable=False, transparent=False,
sprite_index=35, tile_type='wall')
def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type):
"""Set properties for a specific tile"""
if 0 <= x < self.width and 0 <= y < self.height:
cell = self.grid.at(x, y)
cell.walkable = walkable
cell.transparent = transparent
cell.sprite_index = sprite_index
cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type])
def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player, max_enemies_per_room):
"""Generate a new dungeon map"""
self.fill_with_walls()
for r in range(max_rooms):
room_width = random.randint(room_min_size, room_max_size)
room_height = random.randint(room_min_size, room_max_size)
x = random.randint(0, self.width - room_width - 1)
y = random.randint(0, self.height - room_height - 1)
new_room = RectangularRoom(x, y, room_width, room_height)
if any(new_room.intersects(other_room) for other_room in self.rooms):
continue
self.carve_room(new_room)
if len(self.rooms) == 0:
# First room - place player
player.x, player.y = new_room.center
if player._entity:
player._entity.x, player._entity.y = new_room.center
else:
# All other rooms - add tunnel and enemies
self.carve_tunnel(self.rooms[-1].center, new_room.center)
spawn_enemies_in_room(new_room, self, max_enemies_per_room)
self.rooms.append(new_room)
def carve_room(self, room):
"""Carve out a room"""
inner_x1, inner_y1, inner_x2, inner_y2 = room.inner
for y in range(inner_y1, inner_y2):
for x in range(inner_x1, inner_x2):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, tile_type='floor')
def carve_tunnel(self, start, end):
"""Carve a tunnel between two points"""
for x, y in tunnel_between(start, end):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, tile_type='tunnel')
def get_blocking_entity_at(self, x, y):
"""Return any blocking entity at the given position"""
for entity in self.entities:
if entity.blocks and entity.x == x and entity.y == y:
return entity
return None
def is_blocked(self, x, y):
"""Check if a tile blocks movement"""
if x < 0 or x >= self.width or y < 0 or y >= self.height:
return True
if not self.grid.at(x, y).walkable:
return True
if self.get_blocking_entity_at(x, y):
return True
return False
def add_entity(self, entity):
"""Add a GameObject to the map"""
self.entities.append(entity)
entity.attach_to_grid(self.grid)
class Engine:
"""Main game engine"""
def __init__(self):
self.game_map = None
self.player = None
self.entities = []
mcrfpy.createScene("game")
mcrfpy.setScene("game")
window = mcrfpy.Window.get()
window.title = "McRogueFace Roguelike - Part 5"
self.ui = mcrfpy.sceneUI("game")
background = mcrfpy.Frame(0, 0, 1024, 768)
background.fill_color = mcrfpy.Color(0, 0, 0)
self.ui.append(background)
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
self.setup_game()
self.setup_input()
self.setup_ui()
def setup_game(self):
"""Initialize the game world"""
self.game_map = GameMap(80, 45)
grid = self.game_map.create_grid(self.tileset)
self.ui.append(grid)
# Create player
self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True)
# Generate the dungeon
self.game_map.generate_dungeon(
max_rooms=30,
room_min_size=6,
room_max_size=10,
player=self.player,
max_enemies_per_room=2
)
# Add player to map
self.game_map.add_entity(self.player)
# Store reference to all entities
self.entities = [e for e in self.game_map.entities if e != self.player]
# Initial FOV calculation
self.player.update_fov()
def handle_player_turn(self, action):
"""Process the player's action"""
if isinstance(action, MovementAction):
dest_x = self.player.x + action.dx
dest_y = self.player.y + action.dy
# Check what's at the destination
target = self.game_map.get_blocking_entity_at(dest_x, dest_y)
if target:
# We bumped into something!
print(f"You kick the {target.name} in the shins, much to its annoyance!")
self.status_text.text = f"You kick the {target.name}!"
elif not self.game_map.is_blocked(dest_x, dest_y):
# Move the player
self.player.move(action.dx, action.dy)
self.status_text.text = ""
else:
# Bumped into a wall
self.status_text.text = "Blocked!"
elif isinstance(action, WaitAction):
self.status_text.text = "You wait..."
def setup_input(self):
"""Setup keyboard input handling"""
def handle_keys(key, state):
if state != "start":
return
action = None
# Movement keys
movement = {
"Up": (0, -1), "Down": (0, 1),
"Left": (-1, 0), "Right": (1, 0),
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
"Num4": (-1, 0), "Num5": (0, 0), "Num6": (1, 0),
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
}
if key in movement:
dx, dy = movement[key]
if dx == 0 and dy == 0:
action = WaitAction()
else:
action = MovementAction(dx, dy)
elif key == "Period":
action = WaitAction()
elif key == "Escape":
mcrfpy.setScene(None)
return
# Process the action
if action:
self.handle_player_turn(action)
mcrfpy.keypressScene(handle_keys)
def setup_ui(self):
"""Setup UI elements"""
title = mcrfpy.Caption("Placing Enemies", 512, 30)
title.font_size = 24
title.fill_color = mcrfpy.Color(255, 255, 100)
self.ui.append(title)
instructions = mcrfpy.Caption("Arrow keys to move | . to wait | Bump into enemies! | ESC to quit", 512, 60)
instructions.font_size = 16
instructions.fill_color = mcrfpy.Color(200, 200, 200)
self.ui.append(instructions)
# Status text
self.status_text = mcrfpy.Caption("", 512, 600)
self.status_text.font_size = 18
self.status_text.fill_color = mcrfpy.Color(255, 200, 200)
self.ui.append(self.status_text)
# Entity count
entity_count = len(self.entities)
count_text = mcrfpy.Caption(f"Enemies: {entity_count}", 900, 100)
count_text.font_size = 14
count_text.fill_color = mcrfpy.Color(150, 150, 255)
self.ui.append(count_text)
# Create and run the game
engine = Engine()
print("Part 5: Placing Enemies!")
print("Try bumping into enemies - combat coming in Part 6!")

View File

@ -0,0 +1,568 @@
import mcrfpy
import random
# Color configurations
COLORS_VISIBLE = {
'wall': (100, 100, 100),
'floor': (50, 50, 50),
'tunnel': (30, 30, 40),
}
# Message colors
COLOR_PLAYER_ATK = (230, 230, 230)
COLOR_ENEMY_ATK = (255, 200, 200)
COLOR_PLAYER_DIE = (255, 100, 100)
COLOR_ENEMY_DIE = (255, 165, 0)
# Actions
class Action:
"""Base class for all actions"""
pass
class MovementAction(Action):
"""Action for moving an entity"""
def __init__(self, dx, dy):
self.dx = dx
self.dy = dy
class MeleeAction(Action):
"""Action for melee attacks"""
def __init__(self, attacker, target):
self.attacker = attacker
self.target = target
def perform(self):
"""Execute the attack"""
if not self.target.is_alive:
return None
damage = self.attacker.power - self.target.defense
if damage > 0:
attack_desc = f"{self.attacker.name} attacks {self.target.name} for {damage} damage!"
self.target.take_damage(damage)
# Choose color based on attacker
if self.attacker.name == "Player":
color = COLOR_PLAYER_ATK
else:
color = COLOR_ENEMY_ATK
return attack_desc, color
else:
attack_desc = f"{self.attacker.name} attacks {self.target.name} but does no damage."
return attack_desc, (150, 150, 150)
class WaitAction(Action):
"""Action for waiting/skipping turn"""
pass
class GameObject:
"""Base class for all game objects"""
def __init__(self, x, y, sprite_index, color, name,
blocks=False, hp=0, defense=0, power=0):
self.x = x
self.y = y
self.sprite_index = sprite_index
self.color = color
self.name = name
self.blocks = blocks
self._entity = None
self.grid = None
# Combat stats
self.max_hp = hp
self.hp = hp
self.defense = defense
self.power = power
@property
def is_alive(self):
"""Returns True if this entity can act"""
return self.hp > 0
def attach_to_grid(self, grid):
"""Attach this game object to a McRogueFace grid"""
self.grid = grid
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
self._entity.sprite_index = self.sprite_index
self._entity.color = mcrfpy.Color(*self.color)
def move(self, dx, dy):
"""Move by the given amount"""
if not self.grid:
return
self.x += dx
self.y += dy
if self._entity:
self._entity.x = self.x
self._entity.y = self.y
# Update FOV when player moves
if self.name == "Player":
self.update_fov()
def update_fov(self):
"""Update field of view from this entity's position"""
if self._entity and self.grid:
self._entity.update_fov(radius=8)
def take_damage(self, amount):
"""Apply damage to this entity"""
self.hp -= amount
# Check for death
if self.hp <= 0:
self.die()
def die(self):
"""Handle entity death"""
if self.name == "Player":
# Player death
self.sprite_index = 64 # Stay as @
self.color = (127, 0, 0) # Dark red
if self._entity:
self._entity.color = mcrfpy.Color(127, 0, 0)
else:
# Enemy death
self.sprite_index = 37 # % character for corpse
self.color = (127, 0, 0) # Dark red
self.blocks = False # Corpses don't block
self.name = f"remains of {self.name}"
if self._entity:
self._entity.sprite_index = 37
self._entity.color = mcrfpy.Color(127, 0, 0)
# Entity factories
def create_player(x, y):
"""Create the player entity"""
return GameObject(
x=x, y=y,
sprite_index=64, # @
color=(255, 255, 255),
name="Player",
blocks=True,
hp=30,
defense=2,
power=5
)
def create_orc(x, y):
"""Create an orc enemy"""
return GameObject(
x=x, y=y,
sprite_index=111, # o
color=(63, 127, 63),
name="Orc",
blocks=True,
hp=10,
defense=0,
power=3
)
def create_troll(x, y):
"""Create a troll enemy"""
return GameObject(
x=x, y=y,
sprite_index=84, # T
color=(0, 127, 0),
name="Troll",
blocks=True,
hp=16,
defense=1,
power=4
)
class RectangularRoom:
"""A rectangular room with its position and size"""
def __init__(self, x, y, width, height):
self.x1 = x
self.y1 = y
self.x2 = x + width
self.y2 = y + height
@property
def center(self):
center_x = (self.x1 + self.x2) // 2
center_y = (self.y1 + self.y2) // 2
return center_x, center_y
@property
def inner(self):
return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1
def intersects(self, other):
return (
self.x1 <= other.x2
and self.x2 >= other.x1
and self.y1 <= other.y2
and self.y2 >= other.y1
)
def tunnel_between(start, end):
"""Return an L-shaped tunnel between two points"""
x1, y1 = start
x2, y2 = end
if random.random() < 0.5:
corner_x = x2
corner_y = y1
else:
corner_x = x1
corner_y = y2
for x in range(min(x1, corner_x), max(x1, corner_x) + 1):
yield x, y1
for y in range(min(y1, corner_y), max(y1, corner_y) + 1):
yield corner_x, y
for x in range(min(corner_x, x2), max(corner_x, x2) + 1):
yield x, corner_y
for y in range(min(corner_y, y2), max(corner_y, y2) + 1):
yield x2, y
def spawn_enemies_in_room(room, game_map, max_enemies=2):
"""Spawn between 0 and max_enemies in a room"""
number_of_enemies = random.randint(0, max_enemies)
enemies_spawned = []
for i in range(number_of_enemies):
attempts = 10
while attempts > 0:
x = random.randint(room.x1 + 1, room.x2 - 1)
y = random.randint(room.y1 + 1, room.y2 - 1)
if not game_map.is_blocked(x, y):
# 80% chance for orc, 20% for troll
if random.random() < 0.8:
enemy = create_orc(x, y)
else:
enemy = create_troll(x, y)
game_map.add_entity(enemy)
enemies_spawned.append(enemy)
break
attempts -= 1
return enemies_spawned
class GameMap:
"""Manages the game world"""
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = None
self.entities = []
self.rooms = []
def create_grid(self, tileset):
"""Create the McRogueFace grid"""
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
self.grid.position = (100, 100)
self.grid.size = (800, 480)
# Enable perspective rendering
self.grid.perspective = 0
return self.grid
def fill_with_walls(self):
"""Fill the entire map with wall tiles"""
for y in range(self.height):
for x in range(self.width):
self.set_tile(x, y, walkable=False, transparent=False,
sprite_index=35, tile_type='wall')
def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type):
"""Set properties for a specific tile"""
if 0 <= x < self.width and 0 <= y < self.height:
cell = self.grid.at(x, y)
cell.walkable = walkable
cell.transparent = transparent
cell.sprite_index = sprite_index
cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type])
def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player, max_enemies_per_room):
"""Generate a new dungeon map"""
self.fill_with_walls()
for r in range(max_rooms):
room_width = random.randint(room_min_size, room_max_size)
room_height = random.randint(room_min_size, room_max_size)
x = random.randint(0, self.width - room_width - 1)
y = random.randint(0, self.height - room_height - 1)
new_room = RectangularRoom(x, y, room_width, room_height)
if any(new_room.intersects(other_room) for other_room in self.rooms):
continue
self.carve_room(new_room)
if len(self.rooms) == 0:
# First room - place player
player.x, player.y = new_room.center
if player._entity:
player._entity.x, player._entity.y = new_room.center
else:
# All other rooms - add tunnel and enemies
self.carve_tunnel(self.rooms[-1].center, new_room.center)
spawn_enemies_in_room(new_room, self, max_enemies_per_room)
self.rooms.append(new_room)
def carve_room(self, room):
"""Carve out a room"""
inner_x1, inner_y1, inner_x2, inner_y2 = room.inner
for y in range(inner_y1, inner_y2):
for x in range(inner_x1, inner_x2):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, tile_type='floor')
def carve_tunnel(self, start, end):
"""Carve a tunnel between two points"""
for x, y in tunnel_between(start, end):
self.set_tile(x, y, walkable=True, transparent=True,
sprite_index=46, tile_type='tunnel')
def get_blocking_entity_at(self, x, y):
"""Return any blocking entity at the given position"""
for entity in self.entities:
if entity.blocks and entity.x == x and entity.y == y:
return entity
return None
def is_blocked(self, x, y):
"""Check if a tile blocks movement"""
if x < 0 or x >= self.width or y < 0 or y >= self.height:
return True
if not self.grid.at(x, y).walkable:
return True
if self.get_blocking_entity_at(x, y):
return True
return False
def add_entity(self, entity):
"""Add a GameObject to the map"""
self.entities.append(entity)
entity.attach_to_grid(self.grid)
class Engine:
"""Main game engine"""
def __init__(self):
self.game_map = None
self.player = None
self.entities = []
self.messages = [] # Simple message log
self.max_messages = 5
mcrfpy.createScene("game")
mcrfpy.setScene("game")
window = mcrfpy.Window.get()
window.title = "McRogueFace Roguelike - Part 6"
self.ui = mcrfpy.sceneUI("game")
background = mcrfpy.Frame(0, 0, 1024, 768)
background.fill_color = mcrfpy.Color(0, 0, 0)
self.ui.append(background)
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
self.setup_game()
self.setup_input()
self.setup_ui()
def add_message(self, text, color=(255, 255, 255)):
"""Add a message to the log"""
self.messages.append((text, color))
if len(self.messages) > self.max_messages:
self.messages.pop(0)
self.update_message_display()
def update_message_display(self):
"""Update the message display"""
# Clear old messages
for caption in self.message_captions:
# Remove from UI (McRogueFace doesn't have remove, so we hide it)
caption.text = ""
# Display current messages
for i, (text, color) in enumerate(self.messages):
if i < len(self.message_captions):
self.message_captions[i].text = text
self.message_captions[i].fill_color = mcrfpy.Color(*color)
def setup_game(self):
"""Initialize the game world"""
self.game_map = GameMap(80, 45)
grid = self.game_map.create_grid(self.tileset)
self.ui.append(grid)
# Create player
self.player = create_player(0, 0)
# Generate the dungeon
self.game_map.generate_dungeon(
max_rooms=30,
room_min_size=6,
room_max_size=10,
player=self.player,
max_enemies_per_room=2
)
# Add player to map
self.game_map.add_entity(self.player)
# Store reference to all entities
self.entities = [e for e in self.game_map.entities if e != self.player]
# Initial FOV calculation
self.player.update_fov()
# Welcome message
self.add_message("Welcome to the dungeon!", (100, 100, 255))
def handle_player_turn(self, action):
"""Process the player's action"""
if not self.player.is_alive:
return
if isinstance(action, MovementAction):
dest_x = self.player.x + action.dx
dest_y = self.player.y + action.dy
# Check what's at the destination
target = self.game_map.get_blocking_entity_at(dest_x, dest_y)
if target:
# Attack!
attack = MeleeAction(self.player, target)
result = attack.perform()
if result:
text, color = result
self.add_message(text, color)
# Check if target died
if not target.is_alive:
death_msg = f"The {target.name.replace('remains of ', '')} is dead!"
self.add_message(death_msg, COLOR_ENEMY_DIE)
elif not self.game_map.is_blocked(dest_x, dest_y):
# Move the player
self.player.move(action.dx, action.dy)
elif isinstance(action, WaitAction):
pass # Do nothing
# Enemy turns
self.handle_enemy_turns()
def handle_enemy_turns(self):
"""Let all enemies take their turn"""
for entity in self.entities:
if entity.is_alive:
# Simple AI: if player is adjacent, attack. Otherwise, do nothing.
dx = entity.x - self.player.x
dy = entity.y - self.player.y
distance = abs(dx) + abs(dy)
if distance == 1: # Adjacent to player
attack = MeleeAction(entity, self.player)
result = attack.perform()
if result:
text, color = result
self.add_message(text, color)
# Check if player died
if not self.player.is_alive:
self.add_message("You have died!", COLOR_PLAYER_DIE)
def setup_input(self):
"""Setup keyboard input handling"""
def handle_keys(key, state):
if state != "start":
return
action = None
# Movement keys
movement = {
"Up": (0, -1), "Down": (0, 1),
"Left": (-1, 0), "Right": (1, 0),
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
"Num4": (-1, 0), "Num5": (0, 0), "Num6": (1, 0),
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
}
if key in movement:
dx, dy = movement[key]
if dx == 0 and dy == 0:
action = WaitAction()
else:
action = MovementAction(dx, dy)
elif key == "Period":
action = WaitAction()
elif key == "Escape":
mcrfpy.setScene(None)
return
# Process the action
if action:
self.handle_player_turn(action)
mcrfpy.keypressScene(handle_keys)
def setup_ui(self):
"""Setup UI elements"""
title = mcrfpy.Caption("Combat System", 512, 30)
title.font_size = 24
title.fill_color = mcrfpy.Color(255, 255, 100)
self.ui.append(title)
instructions = mcrfpy.Caption("Attack enemies by bumping into them!", 512, 60)
instructions.font_size = 16
instructions.fill_color = mcrfpy.Color(200, 200, 200)
self.ui.append(instructions)
# Player stats
self.hp_text = mcrfpy.Caption(f"HP: {self.player.hp}/{self.player.max_hp}", 50, 100)
self.hp_text.font_size = 18
self.hp_text.fill_color = mcrfpy.Color(255, 100, 100)
self.ui.append(self.hp_text)
# Message log
self.message_captions = []
for i in range(self.max_messages):
caption = mcrfpy.Caption("", 50, 620 + i * 20)
caption.font_size = 14
caption.fill_color = mcrfpy.Color(200, 200, 200)
self.ui.append(caption)
self.message_captions.append(caption)
# Timer to update HP display
def update_stats(dt):
self.hp_text.text = f"HP: {self.player.hp}/{self.player.max_hp}"
if self.player.hp <= 0:
self.hp_text.fill_color = mcrfpy.Color(127, 0, 0)
elif self.player.hp < self.player.max_hp // 3:
self.hp_text.fill_color = mcrfpy.Color(255, 100, 100)
else:
self.hp_text.fill_color = mcrfpy.Color(0, 255, 0)
mcrfpy.setTimer("update_stats", update_stats, 100)
# Create and run the game
engine = Engine()
print("Part 6: Combat System!")
print("Attack enemies to defeat them, but watch your HP!")