True, but you could do this with a routine consisting of a single print_ret as well.
The advantage of always having them be routines is they can contain arbitrary additional logic. The disadvantage of course is the two bytes of overhead will lead to additional padding waste part of the time.
For making larger games, I suggest having the multiplier for routines and strings stored in the header (e.g. repurpose bytes at $38..$3F for new info). Perhaps require routine/string values to have their top bit set, or the top two bits distinguish the kind of value.
Agreed – with a real xor it would be a lot easier to explicitly code the random function and be able to optionally guarantee consistency between interpreters. My own interpreters (6502 and C++) use the same algorithm that was recommended in some forum thread here years ago.
random code
static uint16_t random_seed = 1;
uint16_t randomNumber(uint16_t &state) {
/* WARNING: State must never be initialized to 0 */
state ^= state << 7;
state ^= state >> 9;
state ^= state << 8;
return state;
}
uint16_t rangedRandom(uint16_t range) {
uint16_t value = (randomNumber(random_seed) * range) >> 16;
return value + 1;
}
Come to think of it, we wouldn’t even need an unsigned comparison opcode if the compiler put packed strings at the very end (for example). For a V8 game smaller than 256k, @jg would work to disambiguate routines from packed strings, and @jl with a negative value would work for games larger than 256k.
(This depends on where the split point between routines and packed strings really is though; there could be corner cases where packed strings “straddle” the point where a positive address becomes negative, and that would need some extra handling. Or, just insert some padding in the story in that case, I guess)
Having three dictionaries with 2, 4 and 6 bytes for the word wouldn’t add to much complexity when matching because I guess the input would indicate what dictionary to use to search for match, right?
Another alternative could be to use hash values for words instead of the letters, like NAIL does. I don’t know what size is needed for hash values to avoid different words having the same hash value, though.
I’m wary of using hash codes, particularly on larger games, but it’s clear they can be made to work.
For multiple dictionaries, for the worse case, you’d invoke tokenise multiple times across multiple dictionaries:
If a non-zero dictionary is supplied, it is used (if not, the ordinary game dictionary is). If the flag is set, unrecognised words are not written into the parse buffer and their slots are left unchanged: this is presumably so that if several tokenise instructions are performed in a row, each fills in more slots without wiping those filled by the others.
The interpreter would also have to be smart enough to handle different sized dictionarie, perhaps based on the “entry length” (normally the dictionary size is a property of the story file).
Having said that… it’s probably a lot of complexity for little gain unless your vocabulary stretched into thousands of words.
A long-term lesson I’ve learned: proposed Z-machine expansions sink or swim at the point of terp implementation. The vast majority of ideas are met with “It’s a mature format, any expansions will only run on a subset of Z-machines (if any), so fragmenting the ecosystem is a bigger harm than not having a XOR opcode.” etc.
Conversely, a proposal becomes Standard when a critical mass of terp developers actually want to support it. z8 memory model? Unicode? Lots of the early terp developers were on board, so it worked.
arc_image is a recent oddity that proves the rule. Lots of people have said “I wish z5 had images”; nothing happened until Stefan Vogt (1) came up with a well-thought-out proposal, (2) released multiple terps that supported it, (3) released a whole compiler that emitted it, and (4) got two of the most active terp developers on board (Chris Spiegel, of the flagship Bocfel, and Shawn Sijnstra, of… like… every Z80 retro platform ever made).
TL;DR intellectual exercises aside, the future is actually set by Developers and Lobbyists.
I’m wary of this idea. Those opcodes have very specific and expected behavior. Subverting them seems problematic. Save/restore can already be used separately on a block of memory written to a separate file.
It seems you could get the same effect by just launching the new game and having it read the saved data (not a full save state) from the previous game.
Likewise, xor can be implemented (in C syntax) via (a & ~b) | (b & ~a) which is five Z machine instructions, so maybe that one isn’t really worth the trouble?
EDIT - Read the wikipedia article on xor a bit more carefully, and it can actually be done in four instructions - return (a | b) & ~(a & b);
How about something like an option where the save and restore logic is limited to a “family” of games, and the filename is generated from all but (say) the last two digits of the IFID? Likewise, any other filenames are encoded against the full IFID. That way it’s basically impossible for filenames between different games in the same directory to conflict, but it’s still possible to (optionally) share data between multiple games.
My worry, being, of course, that if interpreters gain the ability to read and write “arbitrary” files things are going to need to be heavily sandboxed to avoid security issues. But also, we still want the ability for related games to share data.