Skip to content

Instantly share code, notes, and snippets.

@bpavuk
Created March 25, 2025 17:35
Show Gist options
  • Save bpavuk/ffad67c504ffe8329853538781138368 to your computer and use it in GitHub Desktop.
Save bpavuk/ffad67c504ffe8329853538781138368 to your computer and use it in GitHub Desktop.
Gemini 2.5 Pro test - Procedura RPG
# Single file Text Adventure Game: Procedural Worlds & Rule-Based Foes
# Standard Library Only - Python Implementation
import random
import sys
import textwrap # For wrapping long descriptions
import time # Optional: for slight pauses
# --- Constants ---
DIRECTIONS = ["north", "south", "east", "west"] # Basic directions
MAX_ROOMS = 7 # Minimum 5 required, using 7 for a bit more space
WIN_CONDITION_ENEMY_TYPE = "Dragon" # Type of enemy to defeat to win
PLAYER_BASE_HEALTH = 50
PLAYER_BASE_ATTACK = 5
# --- Helper Functions ---
def wrap_text(text, width=70):
"""Wraps text for better display."""
return "\n".join(textwrap.wrap(text, width))
def get_opposite_direction(direction):
"""Gets the opposite direction."""
opposites = {"north": "south", "south": "north", "east": "west", "west": "east"}
return opposites.get(direction)
# --- Class Definitions ---
class Item:
"""Represents an item in the game."""
def __init__(self, name, description, effect_type, effect_value=0):
self.name = name
self.description = description
# effect_type: 'heal', 'weapon', 'boost_attack', 'junk', 'key' (optional)
self.effect_type = effect_type
# effect_value: amount healed, damage dealt/bonus, attack increase, etc.
self.effect_value = effect_value
def __str__(self):
return self.name
class Character:
"""Base class for Player and Enemy."""
def __init__(self, name, description, health, attack, current_room_id):
self.name = name
self.description = description
self.max_health = health
self.health = health
self.attack_power = attack
self.current_room_id = current_room_id # ID of the room the character is in
def is_alive(self):
"""Checks if the character's health is above 0."""
return self.health > 0
def take_damage(self, damage):
"""Reduces character health by the damage amount."""
self.health -= damage
if self.health < 0:
self.health = 0
print(f"{self.name} takes {damage} damage! Remaining health: {self.health}/{self.max_health}")
def attack(self, target):
"""Performs an attack on the target character."""
# Add a bit of randomness to damage for variability
damage = max(1, self.attack_power + random.randint(-1, 2))
print(f"{self.name} attacks {target.name}!")
# Optional: Add a small pause for readability
# time.sleep(0.5)
target.take_damage(damage)
class Player(Character):
"""Represents the player character."""
def __init__(self, current_room_id):
super().__init__("Player", "It's you!", health=PLAYER_BASE_HEALTH, attack=PLAYER_BASE_ATTACK, current_room_id=current_room_id)
self.inventory = []
self.base_attack_power = PLAYER_BASE_ATTACK
self.equipped_weapon = None
def add_item(self, item):
"""Adds an item to the player's inventory and handles weapon equipping."""
self.inventory.append(item)
print(f"You pick up the {item.name}.")
# Simple auto-equip best weapon logic
if item.effect_type == 'weapon':
if self.equipped_weapon is None or item.effect_value > self.equipped_weapon.effect_value:
self.equip_weapon(item)
def equip_weapon(self, weapon_item):
"""Equips a weapon item, updating attack power."""
if weapon_item.effect_type == 'weapon':
# If replacing, show message maybe? For simplicity, just equip.
self.equipped_weapon = weapon_item
# Attack power is base + weapon bonus
self.attack_power = self.base_attack_power + weapon_item.effect_value
print(f"You equip the {weapon_item.name}. Your attack is now {self.attack_power}.")
else:
print(f"{weapon_item.name} is not a weapon.")
def use_item(self, item_name, game_state):
"""Uses an item from the inventory, applying its effect."""
item_to_use = None
item_index = -1
# Find the item by name (case-insensitive)
for i, item in enumerate(self.inventory):
if item.name.lower() == item_name.lower():
item_to_use = item
item_index = i
break
if not item_to_use:
print(f"You don't have '{item_name}'.")
return False # Indicate item not used
effect = item_to_use.effect_type
value = item_to_use.effect_value
used = False
if effect == 'heal':
heal_amount = min(value, self.max_health - self.health) # Can't heal above max
if heal_amount > 0:
self.health += heal_amount
print(f"You use the {item_to_use.name} and restore {heal_amount} HP. Current health: {self.health}/{self.max_health}")
used = True
else:
print("Your health is already full.")
elif effect == 'boost_attack': # Permanent boost
self.base_attack_power += value
# Recalculate total attack power with equipped weapon
self.attack_power = self.base_attack_power + (self.equipped_weapon.effect_value if self.equipped_weapon else 0)
print(f"You consume the {item_to_use.name}. Your base attack permanently increases by {value}! Current attack: {self.attack_power}")
used = True
# Add other effects like 'key' if implementing puzzles
elif effect == 'weapon':
print(f"You wield the {item_to_use.name}. Use 'inventory' to see stats. Items are usually equipped automatically on pickup if better.")
# Allow explicit equipping via 'use' ? Could be complex. Auto-equip is simpler.
elif effect == 'junk':
print(f"The {item_to_use.name} doesn't seem to do anything useful.")
# Don't consume junk items, maybe? Or maybe using it reveals it's junk.
used = False # Keep it simple: doesn't consume, does nothing.
else:
print(f"You can't figure out how to use the {item_to_use.name} right now.")
# Remove consumable items from inventory after use
if used and item_to_use.effect_type != 'weapon': # Weapons are equipped, not consumed by 'use'
del self.inventory[item_index]
return used # Return whether item was successfully used and consumed
def show_inventory(self):
"""Displays the player's current inventory and equipped weapon."""
if not self.inventory:
print("Your inventory is empty.")
else:
print("Inventory:")
for item in self.inventory:
# Show item value for weapons and healing items
details = ""
if item.effect_type == 'weapon':
details = f"(+{item.effect_value} Atk)"
elif item.effect_type == 'heal':
details = f"(+{item.effect_value} HP)"
elif item.effect_type == 'boost_attack':
details = f"(+{item.effect_value} Base Atk)"
print(f"- {item.name} {details} [{item.description}]")
if self.equipped_weapon:
print(f"Equipped Weapon: {self.equipped_weapon.name} (+{self.equipped_weapon.effect_value} Attack)")
else:
print("Equipped Weapon: None")
class Enemy(Character):
"""Represents an enemy character with AI."""
def __init__(self, name, description, health, attack, current_room_id, ai_type, enemy_id):
super().__init__(name, description, health, attack, current_room_id)
self.ai_type = ai_type # e.g., 'goblin', 'orc', 'spider', 'dragon'
self.id = enemy_id # Unique ID for tracking
class Room:
"""Represents a location (room) in the game world."""
def __init__(self, room_id, name, description):
self.id = room_id
self.name = name
self.description = description
self.exits = {} # Mapping: {"north": room_id, "south": room_id, ...}
self.items = [] # List of Item objects currently in this room
self.enemy_ids = [] # List of enemy IDs currently in this room
def add_exit(self, direction, adjacent_room_id):
"""Adds a one-way exit from this room."""
self.exits[direction] = adjacent_room_id
def link_rooms(self, direction, adjacent_room):
"""Creates a two-way link between this room and an adjacent one."""
opposite = get_opposite_direction(direction)
if opposite:
self.add_exit(direction, adjacent_room.id)
adjacent_room.add_exit(opposite, self.id)
else:
print(f"Warning: Invalid direction '{direction}' for linking rooms.")
def describe(self, game_state):
"""Prints a description of the room, including items, enemies, and exits."""
print("\n" + "=" * len(self.name))
print(self.name)
print("=" * len(self.name))
print(wrap_text(self.description))
# Show items
if self.items:
print("\nYou see the following items:")
for item in self.items:
print(f"- {item.name}")
else:
# Optional: Add flavor text for empty rooms
# print("\nThe room seems empty of useful items.")
pass
# Show enemies
living_enemies = [game_state.enemies[eid] for eid in self.enemy_ids if game_state.enemies[eid].is_alive()]
if living_enemies:
print("\nDanger! Enemies present:")
for enemy in living_enemies:
print(f"- {enemy.name} ({enemy.description}) [{enemy.health}/{enemy.max_health} HP]")
# Optional: Add flavor text if no enemies
# else:
# print("\nYou see no immediate threats.")
# Show exits
if self.exits:
print("\nAvailable Exits:")
print(", ".join([d.capitalize() for d in self.exits.keys()]))
else:
print("\nThere are no obvious exits from here.")
class GameState:
"""Holds the entire state of the game, including rooms, entities, and status."""
def __init__(self):
self.rooms = {} # Dictionary mapping {room_id: Room object}
self.enemies = {} # Dictionary mapping {enemy_id: Enemy object}
self.player = None
self.game_over = False
self.player_won = False
self.enemy_id_counter = 0 # Simple counter for unique enemy IDs
def get_room(self, room_id):
"""Safely retrieves a room object by its ID."""
return self.rooms.get(room_id)
def get_current_room(self):
"""Gets the Room object where the player currently is."""
if self.player:
return self.get_room(self.player.current_room_id)
return None
def add_enemy_to_room(self, enemy_id, room_id):
"""Adds an enemy ID to a room's list and updates the enemy's location."""
enemy = self.enemies.get(enemy_id)
room = self.get_room(room_id)
if enemy and room:
if enemy_id not in room.enemy_ids:
room.enemy_ids.append(enemy_id)
enemy.current_room_id = room_id # Ensure enemy object knows its room
def remove_enemy_from_room(self, enemy_id, room_id):
"""Removes an enemy ID from a room's list."""
room = self.get_room(room_id)
if room and enemy_id in room.enemy_ids:
try:
room.enemy_ids.remove(enemy_id)
except ValueError:
# Should not happen if check passes, but good practice
pass # Enemy already removed
# --- Procedural Generation ---
def generate_world(game_state):
"""Generates the game world: rooms, items, enemies based on constants."""
print("Generating world...")
# 1. Generate Rooms (ensure at least 5)
num_rooms = max(5, MAX_ROOMS)
room_ids = list(range(num_rooms))
for i in room_ids:
game_state.rooms[i] = generate_room(i)
# 2. Create Layout/Connections
# Simple strategy: Try to connect rooms sequentially, then add a few random links.
for i in range(num_rooms - 1):
room1 = game_state.get_room(i)
room2 = game_state.get_room(i+1)
# Try to find an available direction pair
possible_dirs = list(DIRECTIONS)
random.shuffle(possible_dirs)
connected = False
for d1 in possible_dirs:
d2 = get_opposite_direction(d1)
# Check if both exits are free in the respective rooms
if d1 not in room1.exits and d2 not in room2.exits:
room1.link_rooms(d1, room2)
# print(f"DEBUG: Linked Room {i} ({d1}) <-> Room {i+1} ({d2})") # Debug
connected = True
break
if not connected:
# Fallback: If no ideal pair found (e.g., room full), just force a one-way link if possible
# This might create dead ends or non-reciprocal paths, adding challenge/quirkiness
for d1 in possible_dirs:
if d1 not in room1.exits:
room1.add_exit(d1, room2.id)
# print(f"DEBUG: Forced link Room {i} --({d1})--> Room {i+1}") # Debug
break
# Add a couple of random extra connections for complexity (potential loops/shortcuts)
extra_links = num_rooms // 3
for _ in range(extra_links):
r_id1, r_id2 = random.sample(room_ids, 2) # Pick two distinct rooms
room1 = game_state.get_room(r_id1)
room2 = game_state.get_room(r_id2)
possible_dirs = list(DIRECTIONS)
random.shuffle(possible_dirs)
linked = False
for d1 in possible_dirs:
d2 = get_opposite_direction(d1)
# Check if exits are free and they aren't already linked directly
if d1 not in room1.exits and d2 not in room2.exits and room1.exits.get(d1) != r_id2:
room1.link_rooms(d1, room2)
# print(f"DEBUG: Added extra link Room {r_id1} ({d1}) <-> Room {r_id2} ({d2})") # Debug
linked = True
break
# If linking fails, just skip (world remains connected anyway)
# 3. Generate and Place Items (at least 3 types)
item_templates = [
# (Name, Description, Type, Value)
("Healing Potion", "A fizzing green liquid. Restores health.", "heal", 15),
("Large Healing Potion", "A potent blue potion. Restores more health.", "heal", 30),
("Rusty Sword", "Barely holds an edge.", "weapon", 2),
("Iron Sword", "A standard, reliable blade.", "weapon", 4),
("Steel Longsword", "A well-crafted weapon.", "weapon", 6),
("Strength Elixir", "A thick, metallic tasting liquid.", "boost_attack", 1),
("Mysterious Orb", "It hums faintly.", "junk", 0),
("Tattered Scroll", "Illegible magical script.", "junk", 0),
]
# Ensure at least 3 *types* are possible (heal, weapon, junk/boost) - guaranteed by templates
num_items_to_place = num_rooms + random.randint(0, num_rooms // 2) # Place a decent number of items
# Ensure at least one healing and one weapon exists somewhere
placed_heal = False
placed_weapon = False
for i in range(num_items_to_place):
template = random.choice(item_templates)
# Force placement of needed types if missing near the end
if i >= num_items_to_place - 2:
if not placed_heal:
template = next(t for t in item_templates if t[2] == 'heal')
elif not placed_weapon:
template = next(t for t in item_templates if t[2] == 'weapon')
item = Item(*template)
# Place in a random room (avoiding start room 0 maybe?)
room_id = random.choice(room_ids[1:]) if num_rooms > 1 else 0
game_state.get_room(room_id).items.append(item)
# print(f"DEBUG: Placed {item.name} in Room {room_id}") # Debug
if item.effect_type == 'heal': placed_heal = True
if item.effect_type == 'weapon': placed_weapon = True
# If somehow still missing vital types (unlikely), add them to start room
if not placed_heal: game_state.get_room(0).items.append(Item(*next(t for t in item_templates if t[2] == 'heal')))
if not placed_weapon: game_state.get_room(0).items.append(Item(*next(t for t in item_templates if t[2] == 'weapon')))
# 4. Generate and Place Enemies (at least 2 types + Boss)
enemy_templates = [
# (Name, Description, Health Range, Attack Range, AI Type)
("Goblin Sneak", "Small, quick, and vicious.", (10, 15), (2, 4), "goblin"),
("Orc Grunt", "Muscular and wielding a crude axe.", (20, 30), (4, 6), "orc"),
("Giant Spider", "Clicks its mandibles menacingly.", (15, 22), (3, 5), "spider"),
# Ensure boss template exists
(WIN_CONDITION_ENEMY_TYPE, "A colossal beast wreathed in smoke!", (60, 80), (8, 12), "dragon"),
]
# Ensure at least 2 normal types + boss type are defined
normal_enemy_templates = [t for t in enemy_templates if t[4] != 'dragon']
boss_template = next(t for t in enemy_templates if t[4] == 'dragon')
num_enemies_to_place = num_rooms // 2 + random.randint(1, 3) # Adjust density as needed
# Place the boss in the last room (or a random distant room)
boss_room_id = num_rooms - 1
name, desc, health_r, attack_r, ai_type = boss_template
health = random.randint(*health_r)
attack = random.randint(*attack_r)
enemy_id = game_state.enemy_id_counter
game_state.enemy_id_counter += 1
boss = Enemy(name, desc, health, attack, boss_room_id, ai_type, enemy_id)
game_state.enemies[enemy_id] = boss
game_state.add_enemy_to_room(enemy_id, boss_room_id)
# print(f"DEBUG: Placed BOSS {boss.name} in Room {boss_room_id}") # Debug
# Place other enemies
for _ in range(num_enemies_to_place - 1): # -1 because boss is already placed
template = random.choice(normal_enemy_templates)
name, desc, health_r, attack_r, ai_type = template
health = random.randint(*health_r)
attack = random.randint(*attack_r)
enemy_id = game_state.enemy_id_counter
game_state.enemy_id_counter += 1
# Place in random room, avoiding start room (0) and boss room
possible_room_ids = [rid for rid in room_ids if rid != 0 and rid != boss_room_id]
if not possible_room_ids: possible_room_ids = [0] # Fallback if only start/boss room exist
room_id = random.choice(possible_room_ids)
enemy = Enemy(name, desc, health, attack, room_id, ai_type, enemy_id)
game_state.enemies[enemy_id] = enemy
game_state.add_enemy_to_room(enemy_id, room_id)
# print(f"DEBUG: Placed {enemy.name} in Room {room_id}") # Debug
# 5. Create and Place Player
start_room_id = 0
game_state.player = Player(start_room_id)
print("World generation complete.")
def generate_room(room_id):
"""Generates a single room with a procedural name and description."""
# Templates for generation
environments = ["Cavern", "Forest Glade", "Ruined Chamber", "Dusty Corridor", "Dank Cellar", "Overgrown Path", "Stone Hall", "Musty Library", "Chapel Ruins"]
adj_primary = ["Dimly lit", "Echoing", "Ancient", "Eerie", "Cold", "Damp", "Shadowy", "Crumbling", "Vast", "Narrow", "Quiet", "Drafty"]
adj_secondary = [" unsettling", " mysterious", " forgotten", " foreboding", " strangely peaceful", " unnervingly silent", " filled with debris", " surprisingly ornate"]
details = [
"Water drips rhythmically from unseen heights.",
"Strange symbols cover the walls, faded with time.",
"The air is thick with the smell of dust and decay.",
"You hear faint rustling sounds from the corners.",
"A cool breeze whispers through cracks in the stone.",
"Broken furniture lies overturned and decaying.",
"Flickering torchlight (or bioluminescence?) casts dancing shadows.",
"The floor is uneven and treacherous.",
"Cobwebs hang like macabre decorations.",
"A faint, unpleasant odor hangs in the air."
]
env = random.choice(environments)
adj1 = random.choice(adj_primary)
adj2 = random.choice(adj_secondary)
detail1 = random.choice(details)
detail2 = random.choice(details)
while detail1 == detail2: # Ensure different details
detail2 = random.choice(details)
name = f"Room {room_id}: {adj1} {env}"
description = f"You stand in a {adj1.lower()} {env.lower()} that feels{adj2}. {detail1} {detail2}"
return Room(room_id, name, description)
# --- AI Logic ---
def run_ai_turn(game_state):
"""Processes AI actions for all living enemies in the game."""
player_room_id = game_state.player.current_room_id
# Process a copy of IDs, as movement might change dict/lists during iteration
# Only process enemies that are currently alive
enemy_ids_to_process = [eid for eid, enemy in game_state.enemies.items() if enemy.is_alive()]
if not enemy_ids_to_process:
return # No living enemies left
# print("--- AI Turn ---") # Optional debug message
for enemy_id in enemy_ids_to_process:
enemy = game_state.enemies.get(enemy_id)
# Double check if enemy still exists and is alive (could be defeated mid-turn by multi-attack?)
if not enemy or not enemy.is_alive():
continue
current_room = game_state.get_room(enemy.current_room_id)
if not current_room: continue # Should not happen normally
# Determine if player is in the same room as the enemy
player_in_room = enemy.current_room_id == player_room_id
# --- Apply AI rules based on enemy type ---
acted = False # Track if the enemy performed an action
if enemy.ai_type == "goblin":
acted = ai_goblin(enemy, game_state, player_in_room, current_room)
elif enemy.ai_type == "orc":
acted = ai_orc(enemy, game_state, player_in_room, current_room)
elif enemy.ai_type == "spider":
acted = ai_spider(enemy, game_state, player_in_room, current_room)
elif enemy.ai_type == "dragon":
acted = ai_dragon(enemy, game_state, player_in_room, current_room)
# Add more AI types here...
# Check if player was defeated by this enemy's action
if not game_state.player.is_alive():
game_state.game_over = True
return # Stop AI processing if player is dead
# --- Specific AI Type Implementations ---
def ai_goblin(enemy, game_state, player_in_room, current_room):
"""Rule-based AI for Goblins: Aggressive, but may flee when low health."""
if player_in_room:
# Rule 1: If health < 30% and random chance, try to flee.
if enemy.health < enemy.max_health * 0.3 and random.random() < 0.4: # 40% chance to flee
if flee(enemy, game_state, current_room):
print(f"The cowardly {enemy.name} tries to flee!")
return True # Action taken: fled
# Rule 2: Otherwise, attack the player.
enemy.attack(game_state.player)
return True # Action taken: attacked
else:
# Rule 3: If player not present, small chance to patrol randomly.
if random.random() < 0.25: # 25% chance to move
if move_randomly(enemy, game_state, current_room):
# Optional: print(f"You hear scurrying sounds nearby.") # Player might hear movement
return True # Action taken: moved
return False # No action taken
def ai_orc(enemy, game_state, player_in_room, current_room):
"""Rule-based AI for Orcs: Aggressive, less likely to flee, may patrol."""
if player_in_room:
# Rule 1: Orcs are tough, low chance to flee only if near death.
if enemy.health < enemy.max_health * 0.15 and random.random() < 0.1: # 10% chance if very low HP
if flee(enemy, game_state, current_room):
print(f"The wounded {enemy.name} attempts a clumsy retreat!")
return True
# Rule 2: Always attack player if present and didn't flee.
enemy.attack(game_state.player)
return True
else:
# Rule 3: Lower chance to patrol compared to goblins. They are less active.
if random.random() < 0.1: # 10% chance to move
if move_randomly(enemy, game_state, current_room):
# Optional: print(f"You hear heavy footsteps fading away.")
return True
return False
def ai_spider(enemy, game_state, player_in_room, current_room):
"""Rule-based AI for Spiders: Territorial ambush predators."""
if player_in_room:
# Rule 1: Always attack if player is in the room.
enemy.attack(game_state.player)
return True
# Rule 2: Spiders generally stay put, guarding their territory (no patrol).
# Optional enhancement: Could add logic to chase if player *just* left the room.
return False
def ai_dragon(enemy, game_state, player_in_room, current_room):
"""Rule-based AI for the Dragon Boss: Powerful, stationary guardian."""
if player_in_room:
# Rule 1: Always attack the player with great force.
print(f"The mighty {enemy.name} roars and attacks!")
enemy.attack(game_state.player)
# Maybe add a special attack chance? (Optional enhancement)
# if random.random() < 0.2: print("It breathes fire!") # Flavor
return True
# Rule 2: The Dragon never leaves its lair.
return False
# --- AI Helper Actions ---
def flee(enemy, game_state, current_room):
"""Enemy attempts to move to a random adjacent room NOT containing the player."""
player_room_id = game_state.player.current_room_id
possible_exits = list(current_room.exits.items())
safe_exits = [(d, rid) for d, rid in possible_exits if rid != player_room_id]
# Prefer fleeing to a room without the player
target_exits = safe_exits if safe_exits else possible_exits # Flee anywhere if no safe exit
if target_exits:
direction, new_room_id = random.choice(target_exits)
return move_enemy(enemy, new_room_id, game_state)
return False # Cannot flee (no exits or only exit leads to player)
def move_randomly(enemy, game_state, current_room):
"""Enemy moves to a random adjacent connected room."""
possible_exits = list(current_room.exits.values())
if possible_exits:
new_room_id = random.choice(possible_exits)
return move_enemy(enemy, new_room_id, game_state)
return False # Cannot move (no exits)
def move_enemy(enemy, new_room_id, game_state):
"""Moves an enemy entity from its current room to a new room ID."""
old_room_id = enemy.current_room_id
# Update room lists
game_state.remove_enemy_from_room(enemy.id, old_room_id)
game_state.add_enemy_to_room(enemy.id, new_room_id)
# print(f"DEBUG: AI moved {enemy.name} from room {old_room_id} to {new_room_id}") # Debug message
return True
# --- Command Parsing and Execution ---
def parse_command(input_str):
"""Parses player input string into command and list of arguments."""
parts = input_str.lower().strip().split()
if not parts:
return None, [] # Return None command if input is empty
command = parts[0]
args = parts[1:]
return command, args
def execute_command(game_state, command, args):
"""Executes the parsed player command, modifying game_state."""
player = game_state.player
current_room = game_state.get_current_room()
if not current_room:
print("Error: Player is in an invalid room.")
game_state.game_over = True
return False # Indicate turn should not proceed
action_taken = True # Assume player action takes a turn unless specified otherwise
# --- Handle Commands ---
if command == "help":
print("\n--- Available Commands ---")
print(" go [direction] - Move north, south, east, or west.")
print(" look - Describe the current room and its contents.")
print(" take [item] - Pick up an item from the room.")
print(" inventory (i) - Show your inventory and equipped items.")
print(" use [item] - Use an item from your inventory.")
print(" attack [enemy] - Attack an enemy in the current room.")
print(" status - Show your current health and stats.")
print(" help - Show this help message again.")
print(" quit - Exit the game.")
action_taken = False # Viewing help doesn't take a turn
elif command == "quit":
print("Are you sure you want to quit? (yes/no)")
confirm = input("> ").lower().strip()
if confirm == 'yes':
print("Quitting game. Goodbye!")
game_state.game_over = True
else:
print("Quit cancelled.")
action_taken = False # Cancelling quit doesn't take a turn
elif command == "look":
current_room.describe(game_state)
action_taken = False # Looking around doesn't usually take a game turn
elif command == "status":
print("\n--- Player Status ---")
print(f"Health: {player.health}/{player.max_health}")
print(f"Base Attack: {player.base_attack_power}")
print(f"Total Attack: {player.attack_power}")
player.show_inventory()
action_taken = False # Checking status doesn't take a turn
elif command == "go":
if not args:
print("Go where? (Specify a direction like 'go north')")
action_taken = False
else:
direction = args[0]
if direction not in DIRECTIONS:
print(f"Unknown direction '{direction}'. Try: {', '.join(DIRECTIONS)}.")
action_taken = False
else:
next_room_id = current_room.exits.get(direction)
if next_room_id is not None:
player.current_room_id = next_room_id
print(f"\nYou move {direction}.")
# Describe the new room immediately after moving
game_state.get_current_room().describe(game_state)
else:
print("You bump into a wall. You can't go that way.")
action_taken = False # Failed move doesn't take a turn
elif command == "take":
if not args:
print("Take what?")
action_taken = False
else:
item_name_query = " ".join(args)
item_to_take = None
item_index = -1
# Find item by exact or partial match (prefer exact)
for i, item in enumerate(current_room.items):
if item.name.lower() == item_name_query:
item_to_take = item
item_index = i
break
# If no exact match, try partial match (e.g., "take potion")
if not item_to_take:
found_partial = []
for i, item in enumerate(current_room.items):
if item_name_query in item.name.lower():
found_partial.append((item, i))
if len(found_partial) == 1:
item_to_take, item_index = found_partial[0]
elif len(found_partial) > 1:
print(f"Which '{item_name_query}' did you mean?")
for j, (item, _) in enumerate(found_partial): print(f" {j+1}. {item.name}")
# Basic choice handling - can be expanded
action_taken = False # Taking requires clarification
# Else: No partial match found either
if item_to_take:
player.add_item(item_to_take)
del current_room.items[item_index] # Remove item from room
else:
print(f"There is no '{item_name_query}' here.")
action_taken = False # Failed take doesn't take a turn
elif command in ["inventory", "i"]:
player.show_inventory()
action_taken = False # Checking inventory doesn't take a turn
elif command == "use":
if not args:
print("Use what?")
action_taken = False
else:
item_name_query = " ".join(args)
# player.use_item returns True if item was used+consumed, False otherwise
action_taken = player.use_item(item_name_query, game_state)
elif command == "attack":
if not args:
print("Attack what?")
action_taken = False
else:
enemy_name_query = " ".join(args) # Allow multi-word enemy names
target_enemy = None
# Get living enemies in the current room
living_enemies = {eid: game_state.enemies[eid] for eid in current_room.enemy_ids if game_state.enemies[eid].is_alive()}
if not living_enemies:
print("There are no enemies here to attack.")
action_taken = False
else:
# Try exact match first
for eid, enemy in living_enemies.items():
if enemy.name.lower() == enemy_name_query:
target_enemy = enemy
break
# If no exact match, try partial match (first word often enough)
if not target_enemy:
first_word_query = args[0]
potential_targets = []
for eid, enemy in living_enemies.items():
# Check if query matches start of name or is contained within
if enemy.name.lower().startswith(first_word_query) or first_word_query in enemy.name.lower().split():
potential_targets.append(enemy)
if len(potential_targets) == 1:
target_enemy = potential_targets[0]
elif len(potential_targets) > 1:
print(f"Which enemy do you want to attack?")
for i, e in enumerate(potential_targets):
print(f" {i+1}. {e.name} ({e.health}/{e.max_health} HP)")
try:
choice = input(f"Enter number (1-{len(potential_targets)}): ").strip()
index = int(choice) - 1
if 0 <= index < len(potential_targets):
target_enemy = potential_targets[index]
else:
print("Invalid choice.")
action_taken = False # Requires valid choice
except ValueError:
print("Invalid input. Please enter a number.")
action_taken = False # Requires valid choice
# Else: No partial match found either
# Perform the attack if a target was successfully identified
if target_enemy:
if target_enemy.is_alive():
player.attack(target_enemy)
# Check if enemy died from the attack
if not target_enemy.is_alive():
print(f"You defeated the {target_enemy.name}!")
# Check win condition: Was this the boss?
if target_enemy.name == WIN_CONDITION_ENEMY_TYPE:
game_state.player_won = True
game_state.game_over = True # End game immediately on win
else:
print(f"The {target_enemy.name} is already defeated.")
action_taken = False # Attacking dead enemy wastes no turn
elif action_taken: # Only print if target wasn't found and choice wasn't pending
print(f"Cannot find an enemy called '{enemy_name_query}' here.")
action_taken = False # Failed attack doesn't take a turn
else:
print(f"Unknown command: '{command}'. Type 'help' for commands.")
action_taken = False # Unknown command doesn't take a turn
return action_taken # Return whether the player's action consumed their turn
# --- Main Game Loop ---
def game_loop():
"""Initializes the game state and runs the main interactive loop."""
# Initialization
game_state = GameState()
# Optional: Set seed for reproducible testing
# random.seed(42)
generate_world(game_state)
player = game_state.player
if not player:
print("Fatal Error: Player object not created during world generation.")
return
# Welcome Message
print("\n" + "*" * 30)
print(" Welcome to the Depths of Procedura!")
print("*" * 30)
print(wrap_text(f"Your quest is to navigate the generated dungeon, gather what aid you can, and defeat the fearsome {WIN_CONDITION_ENEMY_TYPE} lurking within."))
print("\nType 'help' for a list of commands.")
# Show initial room description
game_state.get_current_room().describe(game_state)
# --- Main Loop ---
while not game_state.game_over:
print("-" * 20) # Separator for turns
# Display Player Prompt
try:
raw_input = input("What do you do? > ").strip()
if not raw_input:
print("Please enter a command or type 'help'.")
continue # Skip turn if input is empty
command, args = parse_command(raw_input)
# Execute Player Command & Check if Turn Passes
player_turn_taken = execute_command(game_state, command, args)
# Check game end conditions immediately after player action (win/loss/quit)
if game_state.game_over:
break # Exit loop if game ended
# --- AI Turn ---
# AI only acts if the player performed an action that took a turn
if player_turn_taken:
# Check if there are any enemies in the player's current room that might react immediately
current_room = game_state.get_current_room()
living_enemies_in_room = [game_state.enemies[eid] for eid in current_room.enemy_ids if game_state.enemies[eid].is_alive()]
if living_enemies_in_room:
print("\n--- Enemy Actions ---")
# Run AI for all enemies (handles movement and attacks)
run_ai_turn(game_state)
# else: No enemies in room, AI turn might still involve movement elsewhere if implemented globally
# Check player death after AI turn
if not player.is_alive():
game_state.game_over = True
# Player death message is handled in take_damage generally, but confirm here.
# print("You succumb to your wounds...") # Redundant if take_damage prints well
# Graceful exit on Ctrl+C / Ctrl+D
except KeyboardInterrupt:
print("\nCaught interrupt signal. Quitting game.")
game_state.game_over = True
except EOFError:
print("\nEnd of input detected. Quitting game.")
game_state.game_over = True
# Catch other potential errors during gameplay loop if needed
# except Exception as e:
# print(f"\nAn unexpected error occurred: {e}")
# print("Attempting to exit gracefully.")
# game_state.game_over = True
# --- Game Over ---
print("\n" + "=" * 30)
if game_state.player_won:
print(" *** Victory! ***")
print(f"You have vanquished the mighty {WIN_CONDITION_ENEMY_TYPE}!")
print(" Congratulations!")
elif player and not player.is_alive():
print(" --- You Have Died ---")
print(" Your adventure ends here.")
else: # Game ended via 'quit' or other means
print(" --- Game Over ---")
print("=" * 30 + "\n")
# --- Start the game ---
if __name__ == "__main__":
game_loop()

Task Title: Text Adventure Game Challenge: Procedural Worlds & Rule-Based Foes (Standard Library Only)

Task Description:

Your mission is to create a playable text-based adventure game. The game world, including rooms, items, and enemies, must be procedurally generated. Opponents within the game should be controlled by a rule-based Artificial Intelligence (AI) system. The game should be fully functional, text-based, and have a clear win or lose condition for the player.

Constraints:

  • Programming Language: You MUST use Python, Kotlin, JavaScript, or Rust. Choose ONE language and stick to it.
  • No External Libraries: You are STRICTLY FORBIDDEN from using any external libraries or packages beyond the standard library of your chosen language. This includes, but is not limited to, libraries for game development, AI/ML, or advanced data structures that are not part of the core language distribution. The solution must be self-contained within the standard library.
  • Single File: Your entire game code MUST be contained within a single file.

Core Requirements:

Your game MUST implement the following features:

  1. Procedural World Generation:

    • Generate at least 5 unique interconnected rooms/locations. The structure can be a simple linear path, a small grid, or a slightly more complex branching structure.
    • Each room must have a procedurally generated descriptive text (at least 2-3 sentences) detailing the environment and any notable features.
    • Procedurally generate at least 3 different types of items that can be found in rooms. Items should have properties like name, description, and a simple effect (e.g., healing potion, damage weapon).
    • Procedurally generate at least 2 different types of enemy/monster opponents with names, descriptions, and basic combat stats (e.g., health, attack).
  2. Game Logic and Player Interaction:

    • Implement a text-based command parser that understands at least the following commands: go [direction] (north, south, east, west, up, down), take [item], use [item], attack [enemy], inventory, look (describe current room again), and help (display available commands).
    • Implement actions for each command: moving between rooms, picking up items, using items (with effects), initiating combat, displaying inventory, describing the room, and showing help text.
    • Implement a turn-based combat system between the player and AI opponents. Combat should involve basic calculations based on stats and potentially simple randomness.
    • The player must have an inventory to store collected items.
    • Define clear win and lose conditions for the game (e.g., defeat a specific enemy, reach a certain location to win; health reaching zero to lose).
  3. Rule-Based AI for Opponents:

    • Implement a rule-based AI system to control enemy behavior. Enemies should have rules for at least:
      • Movement: Rules for deciding whether to move to a different room (e.g., patrol, chase player if seen, flee if low health).
      • Combat Actions: Rules for choosing combat actions (attack, defend, potentially use items if you choose to implement item usage for enemies as an enhancement). These rules should consider factors like enemy health, player health, and distance (if applicable in your room structure).
    • Implement at least two distinct sets of AI rules to differentiate the behavior of the different enemy types. Even simple variations are acceptable.
  4. Game Loop and Text-Based UI:

    • Implement a main game loop that continuously:
      • Displays the current room description and game state information to the player.
      • Prompts the player for input.
      • Parses and executes the player's command.
      • Executes AI opponent actions (if any are present in the current room or globally relevant).
      • Updates the game state.
    • The game output should be entirely text-based, presented in a clear and readable format.

Optional Enhancements (Not Required for Basic Functionality, but can improve score):

  • More complex room descriptions and world generation (e.g., using templates, more varied vocabulary).
  • More diverse item and monster types with more interesting properties and effects.
  • Simple puzzles within the game world.
  • More sophisticated rule-based AI with more complex decision-making logic.
  • Scoring or experience points system.
  • Saving and loading game progress (within the single file and standard library constraint).

Evaluation Criteria (Benchmark):

Solutions will be evaluated based on the following criteria:

  • Correctness of Execution: Does the code run without errors and complete the task?
  • Completeness of Functionality: Does the game implement all the core requirements outlined above?
  • Code Structure and Readability: Is the code well-organized, modular (using functions or classes where appropriate), and easy to understand (within the single-file constraint)?
  • Quality of Procedural Generation: Is the procedurally generated content (rooms, descriptions, items, monsters) varied, somewhat coherent, and functional within the game?
  • Effectiveness of Rule-Based AI: Does the rule-based AI control opponents in a functional and somewhat believable way? Do different enemy types exhibit different behaviors based on their rules? Is the AI challenging enough to make the game engaging (at a basic level)?
  • Playability: Is the game actually playable from start to (win or lose) finish? Is the text-based interface reasonably user-friendly?

Example Game Interaction (Illustrative):

You are in a dimly lit cavern.  Water drips from the ceiling.  To the north, you see a narrow passage.  There is a rusty sword on the ground.  A Goblin lurks in the shadows!

> help
Available commands: go, take, use, attack, inventory, look, help

> look
You are in a dimly lit cavern.  Water drips from the ceiling.  To the north, you see a narrow passage.  There is a rusty sword on the ground.  A Goblin lurks in the shadows!

> take sword
You pick up the rusty sword.

> inventory
Inventory: rusty sword

> attack goblin
You attack the Goblin with your rusty sword!
The Goblin hisses and attacks you back!
... (Combat sequence would continue) ...

Good luck, and may your procedurally generated worlds be interesting and your rule-based foes challenging!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment