mud/src/mudlib/zmachine/zscreen.py
Jared Miller 5ea030a0ac
Re-copy fixed repos/zvm source into src/mudlib/zmachine
Copies the cleaned-up zvm source (ruff-compliant, ty-clean) back into
the zmachine module. Adds __init__.py with proper exports and updates
.gitignore for debug.log/disasm.log.
2026-02-09 20:44:21 -05:00

290 lines
9.4 KiB
Python

#
# A template class representing the screen of a z-machine.
#
# Third-party programs are expected to subclass zscreen and override all
# the methods, then pass an instance of their class to be driven by
# the main z-machine engine.
#
# For the license of this file, please consult the LICENSE file in the
# root directory of this distribution.
#
from . import zstream
# Constants for window numbers.
#
# TODO: The Z-Machine standard mentions upper and lower windows and
# window numbers, but never appears to define a mapping between the
# two. So the following values are simply a best guess and may need
# to be changed in the future.
WINDOW_UPPER = 1
WINDOW_LOWER = 2
# Constants for fonts. These are human-readable names for the font ID
# numbers as described in section 8.1.2 of the Z-Machine Standards
# Document.
FONT_NORMAL = 1
FONT_PICTURE = 2
FONT_CHARACTER_GRAPHICS = 3
FONT_FIXED_PITCH = 4
# Constants for text styles. These are human-readable names for the
# 'style' operand of the Z-Machine's 'set_text_style' opcode.
STYLE_ROMAN = 0
STYLE_REVERSE_VIDEO = 1
STYLE_BOLD = 2
STYLE_ITALIC = 4
STYLE_FIXED_PITCH = 8
# Constants for colors. These are human-readable names for the color
# codes as described in section 8.3.1 of the Z-Machine Standards
# Document. Note that the colors defined by Z-Machine Version 6 are
# not defined here, since we are not currently supporting that
# version.
COLOR_CURRENT = 0
COLOR_DEFAULT = 1
COLOR_BLACK = 2
COLOR_RED = 3
COLOR_GREEN = 4
COLOR_YELLOW = 5
COLOR_BLUE = 6
COLOR_MAGENTA = 7
COLOR_CYAN = 8
COLOR_WHITE = 9
# The number of screen rows that represents an "infinite" screen
# height; a screen with its rows set to this value should never
# display a [MORE] prompt, as described in section 8.4.1 of the
# Z-Machine Standards Document.
INFINITE_ROWS = 255
class ZScreenObserver:
"""Observer that is notified of changes in the state of a ZScreen
object.
Note that all methods in this class may be called by any thread at
any time, so they should take any necessary precautions to ensure
the integrity of any data they modify."""
def on_screen_size_change(self, zscreen):
"""Called when the screen size of a ZScreen changes."""
pass
def on_font_size_change(self, zscreen):
"""Called when the font size of a ZScreen changes."""
pass
class ZScreen(zstream.ZBufferableOutputStream):
"""Subclass of zstream.ZBufferableOutputStream that provides an
abstraction of a computer screen."""
def __init__(self):
"Constructor for the screen."
zstream.ZBufferableOutputStream.__init__(self)
# The size of the screen.
self._columns = 79
self._rows = 24
# The size of the current font, in characters
self._fontheight = 1
self._fontwidth = 1
# List of our observers; clients can directly append to and remove
# from this.
self.observers = []
# Subclasses must define real values for all the features they
# support (or don't support).
self.features = {
"has_status_line": False,
"has_upper_window": False,
"has_graphics_font": False,
"has_text_colors": False,
}
# Window Management
#
# The z-machine has 2 windows for displaying text, "upper" and
# "lower". (The upper window has an inital height of 0.)
#
# The upper window is not necessarily where the "status line"
# appears; see section 8.6.1.1 of the Z-Machine Standards Document.
#
# The UI is responsible for making the lower window scroll properly,
# as well as wrapping words ("buffering"). The upper window,
# however, should *never* scroll or wrap words.
#
# The UI is also responsible for displaying [MORE] prompts when
# printing more text than the screen's rows can display. (Note: if
# the number of screen rows is INFINITE_ROWS, then it should never
# prompt [MORE].)
def get_screen_size(self):
"""Return the current size of the screen as [rows, columns]."""
return [self._rows, self._columns]
def select_window(self, window):
"""Select a window to be the 'active' window, and move that
window's cursor to the upper left.
WINDOW should be one of WINDOW_UPPER or WINDOW_LOWER.
This method should only be implemented if the
has_upper_window feature is enabled."""
raise NotImplementedError()
def split_window(self, height):
"""Make the upper window appear and be HEIGHT lines tall. To
'unsplit' a window, call with a height of 0 lines.
This method should only be implemented if the has_upper_window
feature is enabled."""
raise NotImplementedError()
def set_cursor_position(self, x, y):
"""Set the cursor to (row, column) coordinates (X,Y) in the
current window, where (1,1) is the upper-left corner.
This function only does something if the current window is the
upper window; if the current window is the lower window, this
function has no effect.
This method should only be implemented if the has_upper_window
feature is enabled, as the upper window is the only window that
supports cursor positioning."""
raise NotImplementedError()
def erase_window(self, window=WINDOW_LOWER, color=COLOR_CURRENT):
"""Erase WINDOW to background COLOR.
WINDOW should be one of WINDOW_UPPER or WINDOW_LOWER.
If the has_upper_window feature is not supported, WINDOW is
ignored (in such a case, this function should clear the entire
screen).
COLOR should be one of the COLOR_* constants.
If the has_text_colors feature is not supported, COLOR is ignored."""
raise NotImplementedError()
def erase_line(self):
"""Erase from the current cursor position to the end of its line
in the current window.
This method should only be implemented if the has_upper_window
feature is enabled, as the upper window is the only window that
supports cursor positioning."""
raise NotImplementedError()
# Status Line
#
# These routines are only called if the has_status_line capability
# is set. Specifically, one of them is called whenever the
# show_status opcode is executed, and just before input is read from
# the user.
def print_status_score_turns(self, text, score, turns):
"""Print a status line in the upper window, as follows:
On the left side of the status line, print TEXT.
On the right side of the status line, print SCORE/TURNS.
This method should only be implemented if the has_status_line
feature is enabled.
"""
raise NotImplementedError()
def print_status_time(self, hours, minutes):
"""Print a status line in the upper window, as follows:
On the left side of the status line, print TEXT.
On the right side of the status line, print HOURS:MINUTES.
This method should only be implemented if the has_status_line
feature is enabled.
"""
raise NotImplementedError()
# Text Appearances
#
def get_font_size(self):
"""Return the current font's size as [width, height]."""
return [self._fontwidth, self._fontheight]
def set_font(self, font_number):
"""Set the current window's font to one of
FONT_NORMAL - normal font
FONT_PICTURE - picture font (IGNORE, this means nothing)
FONT_CHARACTER_GRAPHICS - character graphics font
FONT_FIXED_WIDTH - fixed-width font
If a font is not available, return None. Otherwise, set the
new font, and return the number of the *previous* font.
The only font that must be supported is FONT_NORMAL; all others
are optional, as per section 8.1.3 of the Z-Machine Standards
Document."""
raise NotImplementedError()
def set_text_style(self, style):
"""Set the current text style to the given text style.
STYLE is a sequence, each element of which should be one of the
following values:
STYLE_ROMAN - Roman
STYLE_REVERSE_VIDEO - Reverse video
STYLE_BOLD - Bold
STYLE_ITALIC - Italic
STYLE_FIXED_PITCH - Fixed-width
It is not a requirement that the screen implementation support
every combination of style; if no combinations are possible, it is
acceptable to simply use the first style in the sequence and ignore
the rest.
As per section 8.7.1.1 of the Z-Machine Standards Document, the
implementation need not provide bold or italic, and is free to
interpret them broadly.
"""
raise NotImplementedError()
def set_text_color(self, foreground_color, background_color):
"""Set current text foreground and background color. Each color
should correspond to one of the COLOR_* constants.
This method should only be implemented if the has_text_colors
feature is enabled.
"""
raise NotImplementedError()
# Standard output
def write(self, string):
"""Implementation of the ZOutputStream method. Prints the given
unicode string to the currently active window, using the current
text style settings."""
raise NotImplementedError()