Compare commits

..

No commits in common. "74538756d5bb44dd475c435259d14a21e0e1390c" and "909ee0932bbd64796007b01b4ba214559082c893" have entirely different histories.

5 changed files with 6 additions and 58 deletions

View file

@ -36,20 +36,6 @@ class EmbeddedIFSession:
self._zmachine = ZMachine(story_bytes, self._ui) self._zmachine = ZMachine(story_bytes, self._ui)
self._filesystem = self._ui.filesystem self._filesystem = self._ui.filesystem
def _strip_prompt(self, output: str) -> str:
"""Strip trailing > prompt from game output (matches dfrotz behavior)."""
has_prompt = (
output.endswith("> ") or output.endswith(">\n") or output.endswith(">\r\n")
)
text = output.rstrip()
if text.endswith("\n>"):
return text[:-2].rstrip()
if text == ">":
return ""
if has_prompt and text.endswith(">"):
return text[:-1].rstrip()
return output
@property @property
def save_path(self) -> Path: def save_path(self) -> Path:
safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", self.player.name) safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", self.player.name)
@ -114,7 +100,6 @@ class EmbeddedIFSession:
# buffer), producing unwanted output. Suppress it and only show the # buffer), producing unwanted output. Suppress it and only show the
# restore confirmation. # restore confirmation.
return "restoring saved game...\r\nrestored." return "restoring saved game...\r\nrestored."
output = self._strip_prompt(output)
return output return output
async def handle_input(self, text: str) -> IFResponse: async def handle_input(self, text: str) -> IFResponse:
@ -145,7 +130,6 @@ class EmbeddedIFSession:
await loop.run_in_executor(None, wait_for_next_input) await loop.run_in_executor(None, wait_for_next_input)
output = self._screen.flush() output = self._screen.flush()
output = self._strip_prompt(output)
if self._done and self._error: if self._done and self._error:
output = f"{output}\r\n{self._error}" if output else self._error output = f"{output}\r\n{self._error}" if output else self._error
return IFResponse(output=output, done=self._done) return IFResponse(output=output, done=self._done)
@ -165,8 +149,7 @@ class EmbeddedIFSession:
except Exception as e: except Exception as e:
tb = traceback.format_exc() tb = traceback.format_exc()
logger.error(f"Interpreter crashed:\n{tb}") logger.error(f"Interpreter crashed:\n{tb}")
msg = str(e) or type(e).__name__ self._error = f"interpreter error: {e}"
self._error = f"interpreter error: {msg}"
finally: finally:
self._done = True self._done = True
self._keyboard._waiting.set() self._keyboard._waiting.set()

View file

@ -315,7 +315,8 @@ async def shell(
if player.mode == "editor" and player.editor: if player.mode == "editor" and player.editor:
_writer.write(f" {player.editor.cursor + 1}> ") _writer.write(f" {player.editor.cursor + 1}> ")
elif player.mode == "if" and player.if_session: elif player.mode == "if" and player.if_session:
_writer.write("> ") # IF mode: game writes its own prompt, don't add another
pass
else: else:
_writer.write("mud> ") _writer.write("mud> ")
await _writer.drain() await _writer.drain()

View file

@ -107,9 +107,7 @@ class MudInputStream(zstream.ZInputStream):
text = self._input_queue.get() text = self._input_queue.get()
if text: if text:
return ord(text[0]) return ord(text[0])
# Player hit Enter with no text — return 13 (ZSCII Enter/Return) return 0
# so "press any key" prompts work in a line-oriented MUD client.
return 13
def feed(self, text: str): def feed(self, text: str):
self._input_queue.put(text) self._input_queue.put(text)

View file

@ -226,27 +226,17 @@ class ZCpu:
return table return table
def step_fast(self): def step_fast(self):
"""Execute a single instruction with lightweight tracing. """Execute a single instruction without tracing.
Returns True if execution should continue. Returns True if execution should continue.
""" """
current_pc = self._opdecoder.program_counter
(opcode_class, opcode_number, operands) = self._opdecoder.get_next_instruction() (opcode_class, opcode_number, operands) = self._opdecoder.get_next_instruction()
entry = self._dispatch[opcode_class][opcode_number] entry = self._dispatch[opcode_class][opcode_number]
if entry is None: if entry is None:
self._trace.append(f" {current_pc:06x} ILLEGAL") raise ZCpuIllegalInstruction
self._dump_trace()
raise ZCpuIllegalInstruction(
f"illegal opcode class={opcode_class} num={opcode_number}"
f" at PC={current_pc:#x}"
)
implemented, func = entry implemented, func = entry
if not implemented: if not implemented:
return False return False
self._trace.append(
f" {current_pc:06x} {func.__name__}"
f"({', '.join(str(x) for x in operands)})"
)
try: try:
func(self, *operands) func(self, *operands)
except (ZCpuQuit, ZCpuRestart): except (ZCpuQuit, ZCpuRestart):

View file

@ -163,8 +163,6 @@ class ZObjectParser:
def get_attribute(self, objectnum, attrnum): def get_attribute(self, objectnum, attrnum):
"""Return value (0 or 1) of attribute number ATTRNUM of object """Return value (0 or 1) of attribute number ATTRNUM of object
number OBJECTNUM.""" number OBJECTNUM."""
if objectnum == 0:
return 0
object_addr = self._get_object_addr(objectnum) object_addr = self._get_object_addr(objectnum)
@ -185,8 +183,6 @@ class ZObjectParser:
def set_attribute(self, objectnum, attrnum): def set_attribute(self, objectnum, attrnum):
"""Set attribute number ATTRNUM of object number OBJECTNUM to 1.""" """Set attribute number ATTRNUM of object number OBJECTNUM to 1."""
if objectnum == 0:
return
object_addr = self._get_object_addr(objectnum) object_addr = self._get_object_addr(objectnum)
@ -210,8 +206,6 @@ class ZObjectParser:
def clear_attribute(self, objectnum, attrnum): def clear_attribute(self, objectnum, attrnum):
"""Clear attribute number ATTRNUM of object number OBJECTNUM to 0.""" """Clear attribute number ATTRNUM of object number OBJECTNUM to 0."""
if objectnum == 0:
return
object_addr = self._get_object_addr(objectnum) object_addr = self._get_object_addr(objectnum)
@ -253,24 +247,18 @@ class ZObjectParser:
def get_parent(self, objectnum): def get_parent(self, objectnum):
"""Return object number of parent of object number OBJECTNUM.""" """Return object number of parent of object number OBJECTNUM."""
if objectnum == 0:
return 0
[parent, sibling, child] = self._get_parent_sibling_child(objectnum) [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
return parent return parent
def get_child(self, objectnum): def get_child(self, objectnum):
"""Return object number of child of object number OBJECTNUM.""" """Return object number of child of object number OBJECTNUM."""
if objectnum == 0:
return 0
[parent, sibling, child] = self._get_parent_sibling_child(objectnum) [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
return child return child
def get_sibling(self, objectnum): def get_sibling(self, objectnum):
"""Return object number of sibling of object number OBJECTNUM.""" """Return object number of sibling of object number OBJECTNUM."""
if objectnum == 0:
return 0
[parent, sibling, child] = self._get_parent_sibling_child(objectnum) [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
return sibling return sibling
@ -310,8 +298,6 @@ class ZObjectParser:
def remove_object(self, objectnum): def remove_object(self, objectnum):
"""Detach object OBJECTNUM from its parent (unlink from sibling chain).""" """Detach object OBJECTNUM from its parent (unlink from sibling chain)."""
if objectnum == 0:
return
parent = self.get_parent(objectnum) parent = self.get_parent(objectnum)
if parent == 0: if parent == 0:
@ -349,8 +335,6 @@ class ZObjectParser:
Per the Z-spec: if new_child already has a parent, it is first Per the Z-spec: if new_child already has a parent, it is first
removed from that parent's child list, then made the first child removed from that parent's child list, then made the first child
of parent_object.""" of parent_object."""
if parent_object == 0 or new_child == 0:
return
# Remove from old parent first (spec says "first removed") # Remove from old parent first (spec says "first removed")
self.remove_object(new_child) self.remove_object(new_child)
@ -363,8 +347,6 @@ class ZObjectParser:
def get_shortname(self, objectnum): def get_shortname(self, objectnum):
"""Return 'short name' of object number OBJECTNUM as ascii string.""" """Return 'short name' of object number OBJECTNUM as ascii string."""
if objectnum == 0:
return ""
addr = self._get_proptable_addr(objectnum) addr = self._get_proptable_addr(objectnum)
return self._stringfactory.get(addr + 1) return self._stringfactory.get(addr + 1)
@ -372,8 +354,6 @@ class ZObjectParser:
def get_prop(self, objectnum, propnum): def get_prop(self, objectnum, propnum):
"""Return either a byte or word value of property PROPNUM of """Return either a byte or word value of property PROPNUM of
object OBJECTNUM.""" object OBJECTNUM."""
if objectnum == 0:
return 0
(addr, size) = self.get_prop_addr_len(objectnum, propnum) (addr, size) = self.get_prop_addr_len(objectnum, propnum)
if size == 1: if size == 1:
return self._memory[addr] return self._memory[addr]
@ -487,8 +467,6 @@ class ZObjectParser:
def get_property_data_address(self, objectnum, propnum): def get_property_data_address(self, objectnum, propnum):
"""Return the address of property PROPNUM's data bytes for object """Return the address of property PROPNUM's data bytes for object
OBJECTNUM. Return 0 if the object doesn't have that property.""" OBJECTNUM. Return 0 if the object doesn't have that property."""
if objectnum == 0:
return 0
try: try:
addr, size = self.get_prop_addr_len(objectnum, propnum) addr, size = self.get_prop_addr_len(objectnum, propnum)
@ -506,8 +484,6 @@ class ZObjectParser:
"""If PROPNUM is 0, return the first property number of object OBJECTNUM. """If PROPNUM is 0, return the first property number of object OBJECTNUM.
Otherwise, return the property number after PROPNUM in the property list. Otherwise, return the property number after PROPNUM in the property list.
Return 0 if there are no more properties.""" Return 0 if there are no more properties."""
if objectnum == 0:
return 0
if propnum == 0: if propnum == 0:
# Return first property number # Return first property number