Skip to content

Instantly share code, notes, and snippets.

@tako2
Created June 9, 2026 22:55
Show Gist options
  • Select an option

  • Save tako2/7905e2185a390c9faa3fa0cc7c688d97 to your computer and use it in GitHub Desktop.

Select an option

Save tako2/7905e2185a390c9faa3fa0cc7c688d97 to your computer and use it in GitHub Desktop.
Z-machin の python 実装 xyppy に日本語翻訳を追加 (ops_impl.py の置き換え)
# ops_impl.py (as in this file implements the opcodes)
#
# the goal here is to have no z-machine version control flow in here,
# i.e. no 'if *.z5 then X else Y'. All that should be in ops_impl_compat.py
import random
from xyppy.debug import DBG, warn, err
from xyppy.zmath import to_signed_word
from xyppy.ops_impl_compat import *
import xyppy.quetzal as quetzal
import re
def get_var(env, var_num, pop_stack=True):
# if DBG:
# warn(' get_var(', get_var_name(var_num), ', pop_stack =', pop_stack, ')')
if var_num == 0:
frame = env.callstack[-1]
if pop_stack:
return frame.stack.pop()
else:
return frame.stack[-1]
elif var_num < 16:
frame = env.callstack[-1]
return frame.locals[var_num - 1]
elif var_num < 256:
g_idx = var_num - 16
g_base = env.hdr.global_var_base
return env.u16(g_base + 2*g_idx)
else:
err('illegal var num: '+str(var_num))
def set_var(env, var_num, result, push_stack=True):
# if DBG:
# warn(' set_var(', get_var_name(var_num), ',', result, ', push_stack =', push_stack, ')')
result &= 0xffff
if var_num == 0:
frame = env.callstack[-1]
if push_stack:
frame.stack.append(result)
else:
frame.stack[-1] = result
elif var_num < 16:
frame = env.callstack[-1]
frame.locals[var_num - 1] = result
elif var_num < 256:
g_idx = var_num - 16
g_base = env.hdr.global_var_base
env.write16(g_base + 2*g_idx, result)
else:
err('set_var: illegal var_num: '+str(var_num))
def get_var_name(var_num):
if var_num == 0:
return 'SP'
elif var_num < 16:
return 'L'+hex(var_num-1)[2:].zfill(2)
else:
return 'G'+hex(var_num-16)[2:].zfill(2)
def add(env, opinfo):
a = to_signed_word(opinfo.operands[0])
b = to_signed_word(opinfo.operands[1])
result = a+b
set_var(env, opinfo.store_var, result)
def sub(env, opinfo):
a = to_signed_word(opinfo.operands[0])
b = to_signed_word(opinfo.operands[1])
result = a-b
set_var(env, opinfo.store_var, result)
def mul(env, opinfo):
a = to_signed_word(opinfo.operands[0])
b = to_signed_word(opinfo.operands[1])
result = a*b
set_var(env, opinfo.store_var, result)
def div(env, opinfo):
a = to_signed_word(opinfo.operands[0])
b = to_signed_word(opinfo.operands[1])
num_neg = (a < 0) + (b < 0)
result = abs(a) // abs(b)
if num_neg == 1:
result = -result
set_var(env, opinfo.store_var, result)
def mod(env, opinfo):
a = to_signed_word(opinfo.operands[0])
b = to_signed_word(opinfo.operands[1])
result = abs(a) % abs(b)
if a < 0: # spec says a determines sign
result = -result
set_var(env, opinfo.store_var, result)
def load(env, opinfo):
var = opinfo.operands[0]
val = get_var(env, var, pop_stack=False)
set_var(env, opinfo.store_var, val)
def jz(env, opinfo):
result = opinfo.operands[0] == 0
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
def je(env, opinfo):
result = False
first = opinfo.operands[0]
for op in opinfo.operands[1:]:
if first == op:
result = True
break
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
def jl(env, opinfo):
a = to_signed_word(opinfo.operands[0])
b = to_signed_word(opinfo.operands[1])
result = a < b
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
def jg(env, opinfo):
a = to_signed_word(opinfo.operands[0])
b = to_signed_word(opinfo.operands[1])
result = a > b
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
def handle_branch(env, offset):
if offset == 0:
handle_return(env, 0)
elif offset == 1:
handle_return(env, 1)
else:
env.pc += offset - 2
def jump(env, opinfo):
offset = to_signed_word(opinfo.operands[0])
env.pc += offset - 2
def loadw(env, opinfo):
array_addr = opinfo.operands[0]
word_index = to_signed_word(opinfo.operands[1])
word_loc = 0xffff & (array_addr + 2*word_index)
set_var(env, opinfo.store_var, env.u16(word_loc))
def loadb(env, opinfo):
array_addr = opinfo.operands[0]
byte_index = to_signed_word(opinfo.operands[1])
byte_loc = 0xffff & (array_addr + byte_index)
set_var(env, opinfo.store_var, env.mem[byte_loc])
def storeb(env, opinfo):
array_addr = opinfo.operands[0]
byte_index = to_signed_word(opinfo.operands[1])
val = opinfo.operands[2] & 0xff
mem_loc = 0xffff & (array_addr + byte_index)
env.write8(mem_loc, val)
def storew(env, opinfo):
array_addr = opinfo.operands[0]
word_index = to_signed_word(opinfo.operands[1])
val = opinfo.operands[2]
word_loc = 0xffff & (array_addr + 2*word_index)
env.write16(word_loc, val)
def store(env, opinfo):
var = opinfo.operands[0]
val = opinfo.operands[1]
set_var(env, var, val, push_stack=False)
def and_(env, opinfo):
acc = 0xffff
for operand in opinfo.operands:
acc &= operand
set_var(env, opinfo.store_var, acc)
def or_(env, opinfo):
acc = 0
for operand in opinfo.operands:
acc |= operand
set_var(env, opinfo.store_var, acc)
def inc(env, opinfo):
var_num = opinfo.operands[0]
var_val = to_signed_word(get_var(env, var_num))
set_var(env, var_num, var_val+1)
def dec(env, opinfo):
var_num = opinfo.operands[0]
var_val = to_signed_word(get_var(env, var_num))
set_var(env, var_num, var_val-1)
def inc_chk(env, opinfo):
var_loc = opinfo.operands[0]
chk_val = to_signed_word(opinfo.operands[1])
var_val = to_signed_word(get_var(env, var_loc) + 1)
set_var(env, var_loc, var_val)
result = var_val > chk_val
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
def dec_chk(env, opinfo):
var_loc = opinfo.operands[0]
chk_val = to_signed_word(opinfo.operands[1])
var_val = to_signed_word(get_var(env, var_loc) - 1)
set_var(env, var_loc, var_val)
result = var_val < chk_val
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
def test(env, opinfo):
bitmap = opinfo.operands[0]
flags = opinfo.operands[1]
result = bitmap & flags == flags
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
if DBG:
warn(' bitmap', bin(bitmap))
warn(' flags', bin(flags))
def push(env, opinfo):
value = opinfo.operands[0]
frame = env.callstack[-1]
frame.stack.append(value)
def random_(env, opinfo):
rand_max = to_signed_word(opinfo.operands[0])
if rand_max < 0:
random.seed(rand_max)
result = 0
elif rand_max == 0:
random.seed()
result = 0
else:
result = random.randint(1, rand_max)
set_var(env, opinfo.store_var, result)
def jin(env, opinfo):
obj1 = opinfo.operands[0]
obj2 = opinfo.operands[1]
result = get_parent_num(env, obj1) == obj2
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
if DBG:
warn(' obj1', obj1, '(',get_obj_str(env,obj1),')')
warn(' obj2', obj2, '(',get_obj_str(env,obj2),')')
warn(' is_parent?', result)
def get_child(env, opinfo):
obj = opinfo.operands[0]
child_num = get_child_num(env, obj)
set_var(env, opinfo.store_var, child_num)
result = child_num != 0
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
if DBG:
warn(' obj', obj,'(',get_obj_str(env, obj),')')
warn(' child', child_num, '(',get_obj_str(env, child_num),')')
def get_sibling(env, opinfo):
obj = opinfo.operands[0]
sibling_num = get_sibling_num(env, obj)
set_var(env, opinfo.store_var, sibling_num)
result = sibling_num != 0
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
if DBG:
warn(' obj', obj,'(',get_obj_str(env, obj),')')
warn(' sibling', sibling_num, '(',get_obj_str(env, sibling_num),')')
def get_parent(env, opinfo):
obj = opinfo.operands[0]
parent_num = get_parent_num(env, obj)
set_var(env, opinfo.store_var, parent_num)
if DBG:
warn(' obj', obj,'(',get_obj_str(env, obj),')')
warn(' parent', parent_num, '(',get_obj_str(env, parent_num),')')
def handle_return(env, return_val):
frame = env.callstack.pop()
if frame.return_addr == 0:
err('returned from unreturnable/nonexistant function!')
if frame.return_val_loc != None:
set_var(env, frame.return_val_loc, return_val)
env.pc = frame.return_addr
if DBG:
warn(' helper: handle_return')
warn(' return_val', return_val)
if frame.return_val_loc:
warn(' return_val_loc', get_var_name(frame.return_val_loc))
else:
warn(' return_val_loc None')
warn(' return_addr', hex(frame.return_addr))
def ret(env, opinfo):
return_val = opinfo.operands[0]
handle_return(env, return_val)
def rtrue(env, opinfo):
handle_return(env, 1)
def rfalse(env, opinfo):
handle_return(env, 0)
def ret_popped(env, opinfo):
frame = env.callstack[-1]
ret_val = frame.stack.pop()
handle_return(env, ret_val)
def quit(env, opinfo):
env.quit()
msg_text = ""
def print_(env, opinfo):
global msg_text
string = unpack_string(env, opinfo.operands)
msg_text += string
if msg_text == ">":
write(env, msg_text)
msg_text = ""
#write(env, string)
def strip_rets(s):
result = re.sub(r'\n+', '\n', s)
if result[-1] == '\n':
result = result[:-1]
result = result.replace('\r', '<RET>')
return result
def print_ret(env, opinfo):
global msg_text
string = unpack_string(env, opinfo.operands)
msg_text += string
if msg_text in ("Taken.", "Dropped."):
write(env, msg_text+"\n")
msg_text = ""
handle_return(env, 1)
return
string = trans(msg_text)
if msg_text[-1] in '.!"':
write(env, msg_text+"\n")
else:
write(env, msg_text+" ")
string = "("+string+")"
string = strip_rets(string) + "\n"
write(env, "\033[32m" + string + "\033[0m")
msg_text = ""
handle_return(env, 1)
def print_paddr(env, opinfo):
addr = unpack_addr_print_paddr(env, opinfo.operands[0])
_print_addr(env, addr)
def _print_addr(env, addr):
global msg_text
packed_string = read_packed_string(env, addr)
string = unpack_string(env, packed_string)
msg_text += string
#write(env, string)
if DBG:
warn(' helper: _print_addr')
warn(' addr', addr)
def print_addr(env, opinfo):
addr = opinfo.operands[0]
_print_addr(env, addr)
def new_line(env, opinfo):
global msg_text
if msg_text == "":
write(env, "\n")
else:
string = trans(msg_text)
if msg_text[-1] in '.!"':
write(env, msg_text+"\n")
else:
write(env, msg_text+" ")
string = "("+string+")"
string = strip_rets(string) + "\n"
write(env, "\033[32m" + string + "\033[0m")
msg_text = ""
def print_num(env, opinfo):
global msg_text
num = to_signed_word(opinfo.operands[0])
string = str(num)
msg_text += string
#write(env, string)
def print_obj(env, opinfo):
global msg_text
obj = opinfo.operands[0]
string = get_obj_str(env, obj)
msg_text += string
#write(env, string)
if DBG:
warn(' obj', obj, '(', get_obj_str(env, obj), ')')
def print_char(env, opinfo):
global msg_text
char = zscii_to_ascii(env, [opinfo.operands[0]])
msg_text += char
if msg_text == ">":
write(env, msg_text)
msg_text = ""
#write(env, char)
def get_prop_len(env, opinfo):
prop_data_ptr = opinfo.operands[0]
if prop_data_ptr == 0: # to spec
size = 0
else:
sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
size = get_prop_size(env, sizenum_ptr)
set_var(env, opinfo.store_var, size)
# seems to be needed for practicality
# test case:
# Delusions (input: any_key, e, wait)
FORGIVING_GET_PROP = True
def get_prop(env, opinfo):
obj = opinfo.operands[0]
prop_num = opinfo.operands[1]
prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
is_default_prop = prop_data_ptr == 0
if is_default_prop:
base = env.hdr.obj_tab_base
result = env.u16(base + 2*(prop_num-1))
else:
sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
size = get_prop_size(env, sizenum_ptr)
if size == 1:
result = env.mem[prop_data_ptr]
elif size == 2 or FORGIVING_GET_PROP:
result = env.u16(prop_data_ptr)
else:
msg = 'illegal op: get_prop on outsized prop (not 1-2 bytes)'
msg += ' - prop '+str(prop_num)
msg += ' of obj '+str(obj)+' ('+get_obj_str(env, obj)+')'
msg += ' (sized at '+str(size)+' bytes)'
print_prop_list(env, obj)
err(msg)
set_var(env, opinfo.store_var, result)
if DBG:
warn(' obj', obj,'(',get_obj_str(env,obj),')')
warn(' prop_num', prop_num)
warn(' result', result)
warn(' is_default_prop', is_default_prop)
print_prop_list(env, obj)
def put_prop(env, opinfo):
obj = opinfo.operands[0]
prop_num = opinfo.operands[1]
val = opinfo.operands[2]
prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
if prop_data_ptr == 0:
msg = 'illegal op: put_prop on nonexistant property'
msg += ' - prop '+str(prop_num)
msg += ' not found on obj '+str(obj)+' ('+get_obj_str(env, obj)+')'
err(msg)
sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
size = get_prop_size(env, sizenum_ptr)
if size == 2:
env.write16(prop_data_ptr, val)
elif size == 1:
env.write8(prop_data_ptr, val & 0xff)
else:
msg = 'illegal op: put_prop on outsized prop (not 1-2 bytes)'
msg += ' - prop '+str(prop_num)
msg += ' of obj '+str(obj)+' ('+get_obj_str(obj)+')'
msg += ' (sized at '+size+' bytes)'
err(msg)
if DBG:
warn(' obj', obj,'(',get_obj_str(env,obj),')')
warn(' prop_num', prop_num)
warn(' val', val)
print_prop_list(env, obj)
def get_prop_addr(env, opinfo):
obj = opinfo.operands[0]
prop_num = opinfo.operands[1]
if obj == 0:
# from testing, this seems
# to be the expected behavior
result = 0
else:
result = get_prop_data_ptr_from_obj(env, obj, prop_num)
set_var(env, opinfo.store_var, result)
if DBG:
warn(' obj', obj,'(',get_obj_str(env,obj),')')
warn(' prop_num', prop_num)
warn(' result', result)
if obj:
print_prop_list(env, obj)
def get_next_prop(env, opinfo):
obj = opinfo.operands[0]
prop_num = opinfo.operands[1]
next_prop_num = 0
if obj:
if prop_num == 0:
prop_start = get_prop_list_start(env, obj)
next_prop_num = get_prop_num(env, prop_start)
else:
prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
if prop_data_ptr == 0:
msg = 'get_next_prop: passed nonexistant prop '
msg += str(prop_num)+' for obj '+str(obj)+' ('+get_obj_str(env,obj)+')'
print_prop_list(env, obj)
err(msg)
sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
size = get_prop_size(env, sizenum_ptr)
next_prop_num = get_prop_num(env, prop_data_ptr + size)
set_var(env, opinfo.store_var, next_prop_num)
if DBG:
warn(' prop_num', prop_num)
warn(' next_prop_num', next_prop_num)
print_prop_list(env, obj)
def not_(env, opinfo):
val = ~(opinfo.operands[0])
set_var(env, opinfo.store_var, val)
def insert_obj(env, opinfo):
obj = opinfo.operands[0]
dest = opinfo.operands[1]
if not obj or not dest:
return
# it doesn't say explicitly to make obj's parent
# field say dest, but *surely* that's the right
# thing to do. Right?
# (based on what the ops seems to expect, I think so)
# Also, should I remove it from its old parent?
# Looks like, based on the current bug I have.
_remove_obj(env, obj)
# Ok, Yep. That totally fixed things.
dest_child = get_child_num(env, dest)
set_parent_num(env, obj, dest)
set_sibling_num(env, obj, dest_child)
set_child_num(env, dest, obj)
if DBG:
warn(' obj', obj, '(', get_obj_str(env,obj), ')')
warn(' dest', dest, '(', get_obj_str(env,dest), ')')
def _remove_obj(env, obj):
obj_addr = get_obj_addr(env, obj)
parent = get_parent_num(env, obj)
sibling = get_sibling_num(env, obj)
set_parent_num(env, obj, 0)
set_sibling_num(env, obj, 0)
if parent == 0:
return
child_num = get_child_num(env, parent)
if child_num == obj:
set_child_num(env, parent, sibling)
else:
sibling_num = get_sibling_num(env, child_num)
while sibling_num and sibling_num != obj:
child_num = sibling_num
sibling_num = get_sibling_num(env, child_num)
if sibling_num != 0:
set_sibling_num(env, child_num, sibling)
if DBG:
warn(' helper: _remove_obj')
warn(' obj', obj, '(', get_obj_str(env,obj), ')')
warn(' parent', parent, '(', get_obj_str(env,parent), ')')
def remove_obj(env, opinfo):
obj = opinfo.operands[0]
if obj:
_remove_obj(env, obj)
def set_attr(env, opinfo):
obj = opinfo.operands[0]
attr = opinfo.operands[1]
if obj:
obj_addr = get_obj_addr(env, obj)
attr_byte = attr // 8
mask = 2**(7-attr%8)
old_val = env.mem[obj_addr+attr_byte]
env.write8(obj_addr+attr_byte, old_val|mask)
if DBG:
warn(' obj', obj, '(', get_obj_str(env,obj), ')')
warn(' attr', attr)
def clear_attr(env, opinfo):
obj = opinfo.operands[0]
attr = opinfo.operands[1]
if obj:
obj_addr = get_obj_addr(env, obj)
attr_byte = attr // 8
mask = 2**(7-attr%8)
old_val = env.mem[obj_addr+attr_byte]
env.write8(obj_addr+attr_byte, old_val & ~mask)
if DBG:
warn(' obj', obj, '(', get_obj_str(env,obj), ')')
warn(' attr', attr)
def test_attr(env, opinfo):
obj = opinfo.operands[0]
attr = opinfo.operands[1]
if obj:
obj_addr = get_obj_addr(env, obj)
attr_byte = attr // 8
shift_amt = 7 - attr%8
attr_val = (env.mem[obj_addr+attr_byte] >> shift_amt) & 1
result = attr_val == 1
else:
result = False
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
if DBG:
warn(' obj', obj, '(', get_obj_str(env,obj), ')')
warn(' attr', attr)
class Frame:
def __init__(self, return_addr, num_args=0, locals=[], return_val_loc=None, stack=[]):
self.return_addr = return_addr
self.num_args = num_args
self.locals = locals
self.stack = stack
self.return_val_loc = return_val_loc
# in xyppy, call does the job of all other call_* variants, as
# decode handles their differentiation.
def call(env, opinfo):
packed_addr = opinfo.operands[0]
if packed_addr == 0:
if opinfo.store_var != None:
set_var(env, opinfo.store_var, 0)
if DBG:
warn('op: calling 0 (returns false)')
return
return_addr = env.pc
fncache = env.fncache
if packed_addr in fncache:
call_addr, local_vars, code_ptr = fncache[packed_addr]
local_vars = local_vars[:] # leave cached vars for later hits
else:
call_addr = unpack_addr_call(env, packed_addr)
local_vars, code_ptr = parse_call_header(env, call_addr)
if call_addr >= env.hdr.static_mem_base:
fncache[packed_addr] = call_addr, local_vars, code_ptr
local_vars = local_vars[:] # leave cached vars for later hits
# args dropped if past len of locals arr
num_args = min(len(opinfo.operands)-1, len(local_vars))
local_vars[:num_args] = opinfo.operands[1:num_args+1]
env.callstack.append(Frame(return_addr,
num_args,
local_vars,
opinfo.store_var))
env.pc = code_ptr
if DBG:
warn(' calling', hex(call_addr))
warn(' returning to', hex(return_addr))
warn(' using args', opinfo.operands[1:])
if opinfo.store_var == None:
warn(' return val will be discarded')
else:
warn(' return val will be placed in', get_var_name(opinfo.store_var))
warn(' num locals:', env.mem[call_addr])
warn(' local vals:', local_vars)
warn(' code ptr:', hex(code_ptr))
warn(' first inst:', env.mem[code_ptr])
def check_arg_count(env, opinfo):
arg_num = opinfo.operands[0]
frame = env.callstack[-1]
result = frame.num_args >= arg_num
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
if DBG:
warn(' arg_num', arg_num)
warn(' num args in frame', frame.num_args)
warn(' branch_offset', opinfo.branch_offset)
warn(' branch_on', opinfo.branch_on)
warn(' result', result)
def handle_read(env, text_buffer, parse_buffer, time=0, routine=0):
if time != 0 or routine != 0:
if DBG:
err('interrupts requested but not impl\'d yet!')
prefilled = get_text_buffer_as_str(env, text_buffer)
user_input = ascii_to_zscii(env.screen.get_line_of_input(prompt='', prefilled=prefilled).lower())
fill_text_buffer(env, user_input, text_buffer)
if env.hdr.version < 5 or parse_buffer != 0:
handle_parse(env, text_buffer, parse_buffer)
# return ord('\r') as term char for now...
# TODO: the right thing
return ord('\r')
def aread(env, opinfo):
text_buffer = opinfo.operands[0]
if len(opinfo.operands) > 1:
parse_buffer = opinfo.operands[1]
else:
parse_buffer = 0
if len(opinfo.operands) == 4:
time, routine = opinfo.operands[2:4]
else:
time, routine = 0, 0
end_char = handle_read(env, text_buffer, parse_buffer, time, routine)
set_var(env, opinfo.store_var, end_char)
def sread(env, opinfo):
text_buffer = opinfo.operands[0]
if len(opinfo.operands) > 1:
parse_buffer = opinfo.operands[1]
else:
parse_buffer = 0
if len(opinfo.operands) == 4:
time, routine = opinfo.operands[2:4]
else:
time, routine = 0, 0
handle_read(env, text_buffer, parse_buffer, time, routine)
def tokenize(env, opinfo):
text_buffer = opinfo.operands[0]
parse_buffer = opinfo.operands[1]
if len(opinfo.operands) > 2:
dictionary = opinfo.operands[2]
else:
dictionary = 0
if len(opinfo.operands) > 3:
skip_unknown_words = opinfo.operands[3]
else:
skip_unknown_words = 0
handle_parse(env, text_buffer, parse_buffer, dictionary, skip_unknown_words)
def read_char(env, opinfo):
# NOTE: operands[0] must be 1, but I ran into a z5 that passed no operands
# (strictz) so let's just ignore the first operand instead...
if len(opinfo.operands) > 1:
if len(opinfo.operands) != 3:
err('read_char: num operands must be 1 or 3')
if opinfo.operands[1] != 0 or opinfo.operands[2] != 0:
if DBG:
warn('read_char: interrupts not impl\'d yet!')
c = ascii_to_zscii(env.screen.getch_or_esc_seq())[0]
set_var(env, opinfo.store_var, c)
def set_font(env, opinfo):
font_num = opinfo.operands[0]
if font_num == 0:
set_var(env, opinfo.store_var, 1)
if font_num != 1:
set_var(env, opinfo.store_var, 0)
else:
set_var(env, opinfo.store_var, 1)
def pop(env, opinfo):
frame = env.callstack[-1]
frame.stack.pop()
def pull(env, opinfo):
var = opinfo.operands[0]
frame = env.callstack[-1]
result = frame.stack.pop()
set_var(env, var, result, push_stack=False)
def buffer_mode(env, opinfo):
env.screen.finish_wrapping()
flag = opinfo.operands[0]
env.use_buffered_output = (flag == 1)
def output_stream(env, opinfo):
stream = to_signed_word(opinfo.operands[0])
if stream < 0:
stream = abs(stream)
if stream == 3:
table_addr = env.memory_ostream_stack.pop()
zscii_buffer = ascii_to_zscii(env.output_buffer[stream])
buflen = len(zscii_buffer)
env.write16(table_addr, buflen)
for i in range(len(zscii_buffer)):
env.write8(table_addr+2+i, zscii_buffer[i])
env.output_buffer[stream] = ''
if len(env.memory_ostream_stack) == 0:
env.selected_ostreams.discard(stream)
else:
env.selected_ostreams.discard(stream)
elif stream > 0:
env.selected_ostreams.add(stream)
if stream == 3:
table_addr = opinfo.operands[1]
if len(env.memory_ostream_stack) == 16:
err('too many memory-based ostreams (>16)')
env.memory_ostream_stack.append(table_addr)
def restart(env, opinfo):
env.reset()
def log_shift(env, opinfo):
number = opinfo.operands[0]
places = to_signed_word(opinfo.operands[1])
if places < 0:
result = number >> abs(places)
else:
result = number << places
set_var(env, opinfo.store_var, result)
def art_shift(env, opinfo):
number = to_signed_word(opinfo.operands[0])
places = to_signed_word(opinfo.operands[1])
if places < 0:
result = number >> abs(places)
else:
result = number << places
set_var(env, opinfo.store_var, result)
def get_file_len(env):
if env.hdr.version < 4:
return 2*env.hdr.file_len
elif env.hdr.version < 6:
return 4*env.hdr.file_len
else:
return 8*env.hdr.file_len
def verify(env, opinfo):
vsum = 0
for i in range(0x40, get_file_len(env)):
vsum += env.orig_mem[i]
vsum &= 0xffff
result = vsum == env.hdr.checksum
if result == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
if DBG:
warn(' vsum', vsum)
warn(' checksum in header', env.hdr.checksum)
warn(' branch_on', opinfo.branch_on)
warn(' result', result)
def piracy(env, opinfo):
handle_branch(env, opinfo.branch_offset)
def copy_table(env, opinfo):
first = opinfo.operands[0]
second = opinfo.operands[1]
size = to_signed_word(opinfo.operands[2])
if second == 0:
# zeros out first
size = abs(size)
for i in range(size):
env.write8(first+i, 0)
elif size > 0:
# protects against corruption of overlapping tables
tab = env.mem[first:first+size]
for i in range(size):
env.write8(second+i, tab[i])
elif size < 0:
# allows for the corruption of overlapping tables
size = abs(size)
for i in range(size):
env.write8(second+i, env.mem[first+i])
def scan_table(env, opinfo):
val = opinfo.operands[0]
tab_addr = opinfo.operands[1]
tab_len = opinfo.operands[2]
if len(opinfo.operands) > 3:
form = opinfo.operands[3]
else:
form = 0x82
val_size = (form >> 7) + 1 # word or byte
field_len = form & 127
addr = 0
for i in range(tab_len):
test_addr = tab_addr + i*field_len
if val_size == 2:
test_val = env.u16(test_addr)
else:
test_val = env.mem[test_addr]
if val == test_val:
addr = test_addr
break
found = addr != 0
set_var(env, opinfo.store_var, addr)
if found == opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
if DBG:
warn(' found', found)
warn(' addr', addr)
# TODO: make sure this actually works
def print_table(env, opinfo):
env.screen.finish_wrapping()
tab_addr = opinfo.operands[0]
width = opinfo.operands[1]
if len(opinfo.operands) > 2:
height = opinfo.operands[2]
else:
height = 1
if len(opinfo.operands) > 3:
skip = opinfo.operands[3]
else:
skip = 0
col = env.cursor[env.current_window][1]
for i in range(height):
row = env.cursor[env.current_window][0]
line = [env.mem[tab_addr + i*(width+skip) + j] for j in range(width)]
string = zscii_to_ascii(env, line)
string = "<<"+string+">>"
write(env, string)
if i < height - 1:
env.screen.finish_wrapping()
if (env.current_window == 0 and row < env.hdr.screen_height_units-1 or
env.current_window == 1 and row < env.top_window_height-1):
env.cursor[env.current_window] = row+1, col
else:
env.cursor[env.current_window] = row, col
def nop(env, opinfo):
# what'd you expect?
return
def erase_window(env, opinfo):
env.screen.finish_wrapping()
window = to_signed_word(opinfo.operands[0])
if window in [0, -1, -2]:
env.screen.blank_bottom_win()
if window in [1, -1, -2]:
env.screen.blank_top_win()
if window == -1:
env.top_window_height = 0
env.current_window = 0
if window in [0, -1, -2]:
env.cursor[0] = get_cursor_loc_after_erase(env, 0)
if window in [1, -1, -2]:
env.cursor[1] = get_cursor_loc_after_erase(env, 1)
def split_window(env, opinfo):
env.screen.finish_wrapping()
old_height = env.top_window_height
# an unfortunate hack, but makes Inform games look better,
# as they intentionally don't fill up the entire status bar (so
# this is me trying to keep the Trinity trick and those bars both
# looking good). only doing it on 0 to 1-bar transitions,
# because those sound like status bars being made, right?
if opinfo.operands[0] == 1 and env.top_window_height == 0:
env.screen.scroll_top_line_only()
env.top_window_height = opinfo.operands[0]
if env.top_window_height > env.hdr.screen_height_units:
err('split_window: requested split bigger than screen:', env.top_window_height)
# the spec suggests pushing the bottom window cursor down.
# to allow for more trinity-style tricks, we'll do that only
# when it's being written to (see env.screen.write).
def set_window(env, opinfo):
env.screen.finish_wrapping()
env.current_window = opinfo.operands[0]
if env.current_window == 1:
env.cursor[1] = (0,0)
if env.current_window not in [0,1]:
err('set_window: requested unknown window:', env.current_window)
def restore_z3(env, opinfo):
filename = env.screen.get_line_of_input('input save filename: ')
loaded = quetzal.load_to_env(env, filename)
if loaded:
# move past save inst's branch byte(s)
# (which quetzal gives as the PC)
env.pc += 1 if env.mem[env.pc] & 64 else 2
def restore(env, opinfo):
# TODO handle optional operands
if len(opinfo.operands) > 0:
if DBG:
warn('restore: found operands (not yet impld): '+str(opinfo.operands))
set_var(env, opinfo.store_var, 0)
return
filename = env.screen.get_line_of_input('input save filename: ')
loaded = quetzal.load_to_env(env, filename)
if loaded:
# set and move past save inst's svar byte
# (which quetzal gives as the PC)
set_var(env, env.mem[env.pc], 2)
env.pc += 1
else:
set_var(env, opinfo.store_var, 0)
def save_z3(env, opinfo):
filename = env.screen.get_line_of_input('input save filename: ')
saved = quetzal.write(env, filename)
if saved and opinfo.branch_on:
handle_branch(env, opinfo.branch_offset)
def save(env, opinfo):
# TODO handle optional operands
if len(opinfo.operands) > 0:
if DBG:
warn('restore: found operands (not yet impld): '+str(opinfo.operands))
set_var(env, opinfo.store_var, 0)
return
filename = env.screen.get_line_of_input('input save filename: ')
if quetzal.write(env, filename):
set_var(env, opinfo.store_var, 1)
else:
set_var(env, opinfo.store_var, 0)
def set_cursor(env, opinfo):
env.screen.finish_wrapping()
row = to_signed_word(opinfo.operands[0])
col = to_signed_word(opinfo.operands[1])
if row < 1: # why do we not error out here?
row = 1
if col < 1: # same question
col = 1
# ignores win 0 (S 8.7.2.3)
if env.current_window == 1:
if col > env.hdr.screen_width_units:
if DBG:
warn('set_cursor: set outside screen width', col)
col = env.hdr.screen_width_units
if row > env.hdr.screen_height_units:
if DBG:
warn('set_cursor: set outside screen height', row)
row = env.hdr.screen_height_units
# see 3rd to last note at bottom of section 8
env.top_window_height = max(env.top_window_height, row-1)
# fix that row,col have a 1,1 origin
env.cursor[env.current_window] = row-1, col-1
def set_colour(env, opinfo):
fg_col = opinfo.operands[0]
bg_col = opinfo.operands[1]
if fg_col > 9 or bg_col > 9 or fg_col < 0 or bg_col < 0:
err('set_color attempted illegal color')
if fg_col == 1:
fg_col = env.hdr.default_fg_color
if fg_col != 0:
env.fg_color = fg_col
if bg_col == 1:
bg_col = env.hdr.default_bg_color
if bg_col != 0:
env.bg_color = bg_col
def print_unicode(env, opinfo):
ucode = opinfo.operands[0]
if ucode < 128:
write(env, chr(ucode))
else:
write(env, translate_unicode(ucode))
def check_unicode(env, opinfo):
if opinfo.operands[0] < 128:
result = 3
else:
result = 0
set_var(env, opinfo.store_var, result)
def catch(env, opinfo):
set_var(env, opinfo.store_var, len(env.callstack))
def throw(env, opinfo):
ret_val = opinfo.operands[0]
callstack_len = opinfo.operands[1]
while len(env.callstack) > callstack_len:
env.callstack.pop()
handle_return(env, ret_val)
def show_status(env, opinfo):
if DBG:
warn(' (not impld)')
def set_text_style(env, opinfo):
style = opinfo.operands[0]
if style == 0:
env.text_style = 'normal'
if style & 1:
env.text_style = 'reverse_video'
# TODO: (?) these text styles? multiple text styles at once?
# if style & 2:
# env.text_style = 'bold'
# if style & 4:
# env.text_style = 'italic'
# if style & 8:
# env.text_style = 'fixed_pitch'
def sound_effect(env, opinfo):
if DBG:
warn(' (not impld)')
def save_undo(env, opinfo):
set_var(env, opinfo.store_var, -1)
if DBG:
warn(' (not impld for now)')
warn(' (but at least I can notify the game of that)')
# 翻訳関係
from googletrans import Translator
translator = Translator()
import os, json
import asyncio, sys
import time
# 翻訳結果のキャッシュをファイルにセーブ
CACHE_FILE = "zork_trans_cache.json"
# キャッシュをファイルからロード
trans_cache = {}
if os.path.exists(CACHE_FILE):
try:
with open(CACHE_FILE, "r", encoding="utf-8") as f:
trans_cache = json.load(f)
except Exception:
pass
# キャッシュをファイルにセーブ
def save_cache_to_file():
try:
with open(CACHE_FILE, "w", encoding="utf-8") as f:
json.dump(trans_cache, f, ensure_ascii=False, indent=2)
except Exception:
pass
last_trans_time = time.time()
def trans(s):
global last_trans_time
if s in trans_cache:
return trans_cache[s]
diff_time = time.time() - last_trans_time
if diff_time < 0.5:
time.sleep(0.5 - diff_time)
try:
result = asyncio.run(translator.translate(s, dest="ja"))
text = result.text
trans_cache[s] = text
save_cache_to_file()
except Exception as e:
try:
result = asyncio.run(translator.translate(s, dest="ja"))
text = result.text
trans_cache[s] = text
save_cache_to_file()
except Exception as e:
text = s + f" {type(e).__name__}: {e}"
last_trans_time = time.time()
return text
def write(env, text):
# stream 3 overrides all other output
if 3 in env.selected_ostreams:
env.output_buffer[3] += text
return
# TODO: (if I so choose): stream 2 (transcript stream)
# should also be able to wordwrap if buffer is on
for stream in env.selected_ostreams:
if stream == 1:
env.screen.write(text)
else:
env.output_buffer[stream] += text
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment