Compare commits
No commits in common. "51dc583818a0df2b88db21fbdd37bc40e01606ee" and "544b7dfee187c0c5b9e8677cb86d7356030039c5" have entirely different histories.
51dc583818
...
544b7dfee1
5 changed files with 5 additions and 522 deletions
|
|
@ -70,6 +70,5 @@ lessons
|
||||||
plans
|
plans
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- ``docs/plans/roadmap.rst`` — master plan: 12 phases from object model through DSL (start here)
|
|
||||||
- ``docs/plans/if-terminal.txt`` — level 1 IF architecture design (predates implementation)
|
- ``docs/plans/if-terminal.txt`` — level 1 IF architecture design (predates implementation)
|
||||||
- ``docs/plans/zmachine-game-compatibility.rst`` — game test plan for interpreter
|
- ``docs/plans/zmachine-game-compatibility.rst`` — game test plan for interpreter
|
||||||
|
|
|
||||||
|
|
@ -1,400 +0,0 @@
|
||||||
roadmap — order of operations
|
|
||||||
=============================
|
|
||||||
|
|
||||||
the master plan. what to build and in what order.
|
|
||||||
|
|
||||||
current state: the engine works. command system, game loop, mode stack,
|
|
||||||
combat (state machine + TOML moves), persistence (SQLite), editor mode,
|
|
||||||
IF/z-machine (dfrotz + embedded interpreter), terrain generation, content
|
|
||||||
loading — all built. what's unbuilt is the object model and everything it
|
|
||||||
enables: zones, things, inventory, containers, portals, verbs, multi-zone
|
|
||||||
worlds, rich NPCs, player building, and eventually the DSL.
|
|
||||||
|
|
||||||
the architecture-plan.txt priority list (items 1-8) is essentially done.
|
|
||||||
this plan picks up from there.
|
|
||||||
|
|
||||||
each phase builds on the one before it. the MUD stays functional
|
|
||||||
throughout — no phase leaves things broken.
|
|
||||||
|
|
||||||
|
|
||||||
phase 1: object foundation
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
the base class everything inherits from. pure additive — nothing breaks,
|
|
||||||
existing tests keep passing.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- Object base class: name, location (Object | None), x (int | None),
|
|
||||||
y (int | None)
|
|
||||||
- contents property — reverse lookup: everything whose location is self
|
|
||||||
- can_accept(obj) method — default: False
|
|
||||||
- Entity inherits from Object instead of being a standalone dataclass
|
|
||||||
- Player, Mob unchanged externally — they still work exactly as now
|
|
||||||
|
|
||||||
the location field exists but isn't wired up yet. Entity still has x, y
|
|
||||||
from before, they just come through Object now. no behavioral change.
|
|
||||||
|
|
||||||
depends on: nothing
|
|
||||||
enables: everything below
|
|
||||||
refs: object-model.rst (hierarchy, containment mechanics)
|
|
||||||
|
|
||||||
|
|
||||||
phase 2: zones + overworld migration
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
the big migration. the overworld becomes a Zone object. players live
|
|
||||||
inside it. movement, rendering, persistence, mob spawning all update.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- Zone class: width, height, toroidal flag, terrain data, generator
|
|
||||||
- Zone.can_accept() returns True (zones accept everything)
|
|
||||||
- Zone.contents_at(x, y) for spatial queries
|
|
||||||
- convert current World terrain generator into Zone initialization — the
|
|
||||||
procedural overworld becomes a Zone instance loaded at startup
|
|
||||||
- Entity.location = zone (players and mobs live in the overworld zone)
|
|
||||||
- movement system goes through zone: wrapping, passability checks come
|
|
||||||
from player.location (the zone), not a global world reference
|
|
||||||
- viewport rendering reads terrain from player.location
|
|
||||||
- persistence adds zone name to save/load schema
|
|
||||||
- mob spawning sets location=zone
|
|
||||||
- remove world injection into command modules — commands access
|
|
||||||
player.location instead of module-level globals
|
|
||||||
- send_nearby_message uses zone.contents_at() instead of iterating the
|
|
||||||
global players dict
|
|
||||||
|
|
||||||
the global ``_world`` singleton goes away. ``players`` dict can stay for
|
|
||||||
session management but spatial queries go through zones.
|
|
||||||
|
|
||||||
migration strategy:
|
|
||||||
|
|
||||||
- keep World class temporarily as Zone's terrain generator
|
|
||||||
- migrate one system at a time (movement → rendering → persistence →
|
|
||||||
broadcasting) with tests green at every step
|
|
||||||
- object-model.rst "how existing code maps in" section traces each path
|
|
||||||
|
|
||||||
depends on: phase 1
|
|
||||||
enables: multi-zone, portals, interior spaces
|
|
||||||
refs: object-model.rst (Zone section, migration paths),
|
|
||||||
zones-and-building.rst (zone properties, wrapping, generators)
|
|
||||||
|
|
||||||
|
|
||||||
phase 3: things + inventory
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
items exist. you can carry them. the world has stuff in it.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- Thing class: description, portable flag (from Object)
|
|
||||||
- Entity.can_accept() allows portable Things (inventory)
|
|
||||||
- pick up / drop commands
|
|
||||||
- inventory command (list Things whose location is the player)
|
|
||||||
- items on the ground: Thing at location=zone, x=5, y=10
|
|
||||||
- look shows nearby items at your coordinates
|
|
||||||
- thing templates in TOML (content/things/) loaded at startup
|
|
||||||
- thing spawning: template → instance with location=zone
|
|
||||||
- viewport shows ground items (new symbol or marker)
|
|
||||||
- persistence: player inventory persists to SQLite; ground items respawn
|
|
||||||
from templates on restart
|
|
||||||
|
|
||||||
depends on: phase 2 (things need zones to exist in)
|
|
||||||
enables: equipment, crafting ingredients, quest items, loot
|
|
||||||
refs: object-model.rst (Thing section), things-and-building.rst (decorator
|
|
||||||
pattern, content format)
|
|
||||||
|
|
||||||
|
|
||||||
phase 4: containers + portals
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
nesting and zone transitions. both depend on Thing existing.
|
|
||||||
|
|
||||||
containers:
|
|
||||||
|
|
||||||
- Container class: capacity, closed (bool), locked (bool)
|
|
||||||
- Container.can_accept() checks capacity + open state
|
|
||||||
- put / get commands (move Thing into/out of Container)
|
|
||||||
- open / close commands
|
|
||||||
- lock / unlock (requires key item — first real item interaction)
|
|
||||||
- nested inventory display (backpack contains: ...)
|
|
||||||
|
|
||||||
portals:
|
|
||||||
|
|
||||||
- Portal class: target_zone, target_x, target_y, portable=False
|
|
||||||
- zone transition: step on portal → location changes to target zone,
|
|
||||||
position changes to target coords
|
|
||||||
- enter / go command for explicit portal use
|
|
||||||
- build at least one interior zone (tavern or test room) with hand-built
|
|
||||||
tile data from TOML
|
|
||||||
- two-way portals (portal in each zone pointing at the other)
|
|
||||||
- look shows portal presence, portal descriptions
|
|
||||||
|
|
||||||
depends on: phase 3 (portals and containers are Things)
|
|
||||||
enables: multi-zone world, interior spaces, locked doors, bags, keys
|
|
||||||
refs: object-model.rst (Container, Portal sections),
|
|
||||||
zones-and-building.rst (zone data format, hand-built zones)
|
|
||||||
|
|
||||||
|
|
||||||
phase 5: verbs + interaction
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
the layer that makes the world feel alive. drink from a fountain, pet a
|
|
||||||
mob, read a sign. anything can have verbs.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- @verb decorator on Object — any object in the world can have verbs
|
|
||||||
- @thing decorator for spawnable templates with verbs pre-attached
|
|
||||||
- verb resolution: "drink fountain" → find fountain at player's
|
|
||||||
coordinates → call its drink verb handler
|
|
||||||
- verb dispatch integrates with command system — verbs checked as
|
|
||||||
fallback after global commands fail to match
|
|
||||||
- content format: verbs defined in TOML alongside thing definitions,
|
|
||||||
or in python via the decorator
|
|
||||||
- default verbs: look at / examine (work on everything with a
|
|
||||||
description)
|
|
||||||
|
|
||||||
depends on: phase 4 (need objects with verbs to interact with)
|
|
||||||
enables: rich interaction, builder content, puzzle creation
|
|
||||||
refs: things-and-building.rst (@thing/@verb decorator system, full
|
|
||||||
PepsiCan example)
|
|
||||||
|
|
||||||
|
|
||||||
phase 6: multi-zone content + building
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
the world has places. builders can create more.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- zone loading from TOML files (hand-built zones with tile grids)
|
|
||||||
- hub / phone-switch zone with portals to different areas
|
|
||||||
- tutorial zone for new players
|
|
||||||
- tavern / social zone (interior, bounded, not toroidal)
|
|
||||||
- paint mode: admin tool for editing zone terrain tile-by-tile
|
|
||||||
- zone TOML export (runtime state → version-controlled files)
|
|
||||||
- spawn points per zone (where you appear on entry)
|
|
||||||
- zone-specific ambient messages
|
|
||||||
- per-zone mob spawning rules (what mobs live here, density, respawn)
|
|
||||||
|
|
||||||
depends on: phase 4 (portals connect zones)
|
|
||||||
enables: a world with actual geography, builder workflow
|
|
||||||
refs: zones-and-building.rst (zone format, paint mode, building tools,
|
|
||||||
portal network design)
|
|
||||||
|
|
||||||
|
|
||||||
phase 7: GMCP + rich client support
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
structured data for graphical MUD clients (Mudlet, Blightmud, etc.).
|
|
||||||
independent of the object model — can be done earlier if desired.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- GMCP option negotiation in telnet handshake (telnetlib3 supports this)
|
|
||||||
- Char.Vitals package: pl, stamina, max_stamina
|
|
||||||
- Char.Status package: flying, resting, mode, combat state
|
|
||||||
- Room.Info package: zone name, x, y, terrain type
|
|
||||||
- MSDP for real-time variable updates (client-side HP bars, gauges)
|
|
||||||
- map data for clients that support structured map rendering
|
|
||||||
|
|
||||||
telnetlib3 already handles the protocol layer. this phase is defining the
|
|
||||||
data packages and wiring them to game state changes.
|
|
||||||
|
|
||||||
depends on: nothing (can start anytime)
|
|
||||||
enables: rich MUD client experience alongside raw telnet
|
|
||||||
refs: DREAMBOOK.md (things to explore: GMCP, MSDP)
|
|
||||||
|
|
||||||
|
|
||||||
phase 8: NPC evolution
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
mobs become characters, not just combat targets.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- dialogue trees: TOML-defined, triggered by "talk to" or "ask" commands
|
|
||||||
- non-combat behaviors: patrol, wander, idle, shopkeeper, guard
|
|
||||||
- behavior trees or state machines defined in TOML
|
|
||||||
- mob reactions to verbs ("pet" the cat, "ask" the bartender about X)
|
|
||||||
- spawn/respawn system using location=None as template state (die →
|
|
||||||
location=None, timer → respawn at zone with fresh stats)
|
|
||||||
- mob schedules (bartender appears in tavern during the day, gone at
|
|
||||||
night)
|
|
||||||
- shopkeeper mobs (buy/sell interface using inventory system)
|
|
||||||
|
|
||||||
depends on: phase 5 (verbs for interaction), phase 6 (zones for mobs
|
|
||||||
to inhabit)
|
|
||||||
enables: living world, quests, commerce
|
|
||||||
refs: architecture-plan.txt (entity model section),
|
|
||||||
object-model.rst (location=None for templates/despawned)
|
|
||||||
|
|
||||||
|
|
||||||
phase 9: world systems
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
time, weather, seasons. the world breathes.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- game time cycle: ticks → hours → days → seasons
|
|
||||||
- time of day affects descriptions ("the tavern is dim in the evening
|
|
||||||
light" vs "sunlight streams through the windows")
|
|
||||||
- weather system: rain, wind, storms (per zone or global)
|
|
||||||
- weather affects gameplay: rain slows movement, storms block flying,
|
|
||||||
cold water is worse in winter
|
|
||||||
- seasonal terrain variation: snow in winter, flowers in spring, color
|
|
||||||
palette shifts
|
|
||||||
- ambient messages tied to time/weather ("a cold wind picks up")
|
|
||||||
|
|
||||||
depends on: phase 6 (zones with descriptions to vary)
|
|
||||||
enables: atmosphere, seasonal events, weather-dependent gameplay
|
|
||||||
refs: DREAMBOOK.md (seasons, weather, time of day)
|
|
||||||
|
|
||||||
|
|
||||||
phase 10: player creation + housing
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
players shape the world. the MOO dream.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- player-owned zones (personal rooms, houses, pocket dimensions)
|
|
||||||
- building commands: create zone, set dimensions, paint terrain, place
|
|
||||||
things
|
|
||||||
- in-world editing via editor mode (descriptions, verb scripts)
|
|
||||||
- player-created content stored in SQLite
|
|
||||||
- admin review + export to files (sqlite → TOML for version control)
|
|
||||||
- permissions: who can build, who can edit what, visitor access controls
|
|
||||||
- crafting system: combine things → new thing, recipes in TOML
|
|
||||||
- IF creation tools: use the IF primitives to build small adventures
|
|
||||||
from inside the MUD
|
|
||||||
|
|
||||||
depends on: phase 6 (zones), phase 5 (verbs), persistence
|
|
||||||
enables: the MOO vision — player-built, player-programmed world
|
|
||||||
refs: DREAMBOOK.md (user-created content, presence and community),
|
|
||||||
architecture-plan.txt (player-created content in sqlite,
|
|
||||||
engine/content boundary)
|
|
||||||
|
|
||||||
|
|
||||||
phase 11: the DSL
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
replace python callables with a scripting language writable from inside
|
|
||||||
the MUD. this is the hardest design challenge in the project.
|
|
||||||
|
|
||||||
build:
|
|
||||||
|
|
||||||
- language design: informed by what phases 1-10 needed to express
|
|
||||||
- interpreter/runtime: sandboxed, no file/network access
|
|
||||||
- content definitions gain ``body: dsl:script_name`` option alongside
|
|
||||||
``body: python:module.function``
|
|
||||||
- DSL scripts editable in-world via editor mode
|
|
||||||
- help system teaches the DSL from inside the MUD
|
|
||||||
- standard library: common verbs, triggers, conditions
|
|
||||||
- migration path: existing python handlers get DSL equivalents over time
|
|
||||||
|
|
||||||
defer specifics until phases 1-10 reveal exactly what the DSL needs to
|
|
||||||
express. the ecosystem research (dsls.md) catalogs prior art: MOO's
|
|
||||||
language, LPC, MUSH softcode.
|
|
||||||
|
|
||||||
depends on: everything above being stable
|
|
||||||
enables: non-programmers creating game content from a telnet session
|
|
||||||
refs: architecture-plan.txt (DSL section, content definition format),
|
|
||||||
docs/research/dsls.md (prior art)
|
|
||||||
|
|
||||||
|
|
||||||
phase 12: horizons
|
|
||||||
------------------
|
|
||||||
|
|
||||||
things from the dreambook's "explore later" list. order is flexible —
|
|
||||||
tackle based on interest and what the world needs.
|
|
||||||
|
|
||||||
- web client: xterm.js terminal emulator pointed at telnet port. honor
|
|
||||||
the protocol even in the browser
|
|
||||||
- MCCP compression for bandwidth-heavy sessions
|
|
||||||
- inter-MUD communication (IMC2, I3)
|
|
||||||
- AI-driven NPCs: LLM-backed conversation, personality, memory
|
|
||||||
- alternate screen buffer: split views (map panel + text panel)
|
|
||||||
- sound via MSP or links
|
|
||||||
- procedural dungeon generation: zones with random layout per visit
|
|
||||||
- quest system: objectives, tracking, rewards, quest-giver NPCs
|
|
||||||
|
|
||||||
|
|
||||||
dependency graph
|
|
||||||
----------------
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
phase 1: object foundation
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
phase 2: zones + migration
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
phase 3: things + inventory
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
phase 4: containers + portals
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
phase 5: verbs + interaction
|
|
||||||
│ phase 7: GMCP (independent)
|
|
||||||
▼
|
|
||||||
phase 6: multi-zone content
|
|
||||||
│
|
|
||||||
├────────────────┐
|
|
||||||
▼ ▼
|
|
||||||
phase 8: NPCs phase 9: world systems
|
|
||||||
│ │
|
|
||||||
└───────┬────────┘
|
|
||||||
▼
|
|
||||||
phase 10: player creation
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
phase 11: DSL
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
phase 12: horizons
|
|
||||||
|
|
||||||
|
|
||||||
notes
|
|
||||||
-----
|
|
||||||
|
|
||||||
**the phase 2 migration is the riskiest step.** it touches movement,
|
|
||||||
rendering, persistence, mob spawning, and message broadcasting. the
|
|
||||||
strategy is incremental: migrate one subsystem at a time, keep tests
|
|
||||||
green, keep the MUD playable. the object-model.rst doc has a detailed
|
|
||||||
"how existing code maps in" section for this.
|
|
||||||
|
|
||||||
**phase 7 (GMCP) floats.** it doesn't depend on the object model and
|
|
||||||
can be done whenever energy or interest is there. it's slotted at 7
|
|
||||||
because the data it reports (zone name, position, combat state) gets
|
|
||||||
richer as the object model matures, but basic vitals work anytime.
|
|
||||||
|
|
||||||
**phases 8-10 have some flexibility.** world systems (weather, time)
|
|
||||||
could come before NPC evolution. player creation could come before
|
|
||||||
either. the dependency shown is the tightest path — if housing needs
|
|
||||||
NPCs (shopkeepers, guards), then 8 before 10. if not, reorder freely.
|
|
||||||
|
|
||||||
**the DSL (phase 11) is intentionally last.** the architecture-plan.txt
|
|
||||||
says "defer it until we know exactly what it needs to express." phases
|
|
||||||
1-10 discover that organically.
|
|
||||||
|
|
||||||
|
|
||||||
related docs
|
|
||||||
-------------
|
|
||||||
|
|
||||||
- ``DREAMBOOK.md`` — vision, philosophy, wild ideas
|
|
||||||
- ``docs/how/architecture-plan.txt`` — engine/content boundary, original
|
|
||||||
priority list (items 1-8 now complete)
|
|
||||||
- ``docs/how/object-model.rst`` — class hierarchy, containment, verbs,
|
|
||||||
migration paths
|
|
||||||
- ``docs/how/zones-and-building.rst`` — zone format, building tools,
|
|
||||||
paint mode, portal network
|
|
||||||
- ``docs/how/things-and-building.rst`` — @thing/@verb decorator system
|
|
||||||
- ``docs/how/combat.rst`` — combat system (built, referenced by phase 8)
|
|
||||||
- ``docs/how/commands.txt`` — command system (built, extended by new
|
|
||||||
phases)
|
|
||||||
- ``docs/research/dsls.md`` — scripting language catalog (phase 11)
|
|
||||||
|
|
@ -2,20 +2,14 @@
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from mudlib.object import Object
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Entity(Object):
|
class Entity:
|
||||||
"""Base class for anything with position and identity in the world.
|
"""Base class for anything with position and identity in the world."""
|
||||||
|
|
||||||
Inherits name, location from Object. Narrows x, y to int (entities
|
name: str
|
||||||
always have spatial coordinates).
|
x: int
|
||||||
"""
|
y: int
|
||||||
|
|
||||||
# Entities always have spatial coordinates (override Object's int | None)
|
|
||||||
x: int = 0
|
|
||||||
y: int = 0
|
|
||||||
# Combat stats
|
# Combat stats
|
||||||
pl: float = 100.0 # power level (health and damage multiplier)
|
pl: float = 100.0 # power level (health and damage multiplier)
|
||||||
stamina: float = 100.0 # current stamina
|
stamina: float = 100.0 # current stamina
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
"""Base class for everything in the world."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Object:
|
|
||||||
"""Base class for everything in the world.
|
|
||||||
|
|
||||||
Every object has a location (another Object, or None for top-level things
|
|
||||||
like zones). The location pointer is the entire containment system —
|
|
||||||
contents is just the reverse lookup.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
location: Object | None = None
|
|
||||||
x: int | None = None
|
|
||||||
y: int | None = None
|
|
||||||
|
|
||||||
_contents: list[Object] = field(
|
|
||||||
default_factory=list, init=False, repr=False, compare=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
|
||||||
if self.location is not None:
|
|
||||||
self.location._contents.append(self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def contents(self) -> list[Object]:
|
|
||||||
"""Everything whose location is this object."""
|
|
||||||
return list(self._contents)
|
|
||||||
|
|
||||||
def can_accept(self, obj: Object) -> bool:
|
|
||||||
"""Whether this object accepts obj as contents. Default: no."""
|
|
||||||
return False
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
"""Tests for the Object base class."""
|
|
||||||
|
|
||||||
from mudlib.object import Object
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_creation_minimal():
|
|
||||||
"""Object can be created with just a name."""
|
|
||||||
obj = Object(name="rock")
|
|
||||||
assert obj.name == "rock"
|
|
||||||
assert obj.location is None
|
|
||||||
assert obj.x is None
|
|
||||||
assert obj.y is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_creation_with_position():
|
|
||||||
"""Object can have spatial coordinates."""
|
|
||||||
obj = Object(name="tree", x=5, y=10)
|
|
||||||
assert obj.x == 5
|
|
||||||
assert obj.y == 10
|
|
||||||
|
|
||||||
|
|
||||||
def test_object_creation_with_location():
|
|
||||||
"""Object can be placed inside another object."""
|
|
||||||
zone = Object(name="overworld")
|
|
||||||
tree = Object(name="oak", location=zone, x=3, y=7)
|
|
||||||
assert tree.location is zone
|
|
||||||
|
|
||||||
|
|
||||||
def test_contents_empty_by_default():
|
|
||||||
"""An object with nothing inside has empty contents."""
|
|
||||||
obj = Object(name="empty")
|
|
||||||
assert obj.contents == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_contents_tracks_children():
|
|
||||||
"""Contents lists objects whose location is self."""
|
|
||||||
zone = Object(name="overworld")
|
|
||||||
tree = Object(name="oak", location=zone, x=3, y=7)
|
|
||||||
rock = Object(name="rock", location=zone, x=1, y=1)
|
|
||||||
assert tree in zone.contents
|
|
||||||
assert rock in zone.contents
|
|
||||||
assert len(zone.contents) == 2
|
|
||||||
|
|
||||||
|
|
||||||
def test_contents_nested():
|
|
||||||
"""Containment can nest: zone > player > backpack > item."""
|
|
||||||
zone = Object(name="overworld")
|
|
||||||
player = Object(name="jared", location=zone, x=5, y=5)
|
|
||||||
backpack = Object(name="backpack", location=player)
|
|
||||||
gem = Object(name="gem", location=backpack)
|
|
||||||
|
|
||||||
assert player in zone.contents
|
|
||||||
assert backpack in player.contents
|
|
||||||
assert gem in backpack.contents
|
|
||||||
# gem is NOT directly in zone or player
|
|
||||||
assert gem not in zone.contents
|
|
||||||
assert gem not in player.contents
|
|
||||||
|
|
||||||
|
|
||||||
def test_can_accept_default_false():
|
|
||||||
"""Object.can_accept() returns False by default."""
|
|
||||||
obj = Object(name="rock")
|
|
||||||
other = Object(name="sword")
|
|
||||||
assert obj.can_accept(other) is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_contents_returns_copy():
|
|
||||||
"""Contents returns a copy, not the internal list."""
|
|
||||||
zone = Object(name="zone")
|
|
||||||
Object(name="thing", location=zone)
|
|
||||||
contents = zone.contents
|
|
||||||
contents.append(Object(name="intruder"))
|
|
||||||
assert len(zone.contents) == 1 # internal list unchanged
|
|
||||||
Loading…
Reference in a new issue