Draft tutorials
This commit is contained in:
parent
9be3161a24
commit
64ffe1f699
|
@ -9,6 +9,7 @@ obj
|
|||
build
|
||||
lib
|
||||
obj
|
||||
__pycache__
|
||||
|
||||
.cache/
|
||||
7DRL2025 Release/
|
||||
|
|
|
@ -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!")
|
|
@ -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.")
|
|
@ -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!")
|
|
@ -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!")
|
|
@ -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")
|
|
@ -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")
|
|
@ -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!")
|
|
@ -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!")
|
Loading…
Reference in New Issue