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.
290 lines
9.4 KiB
Python
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()
|