/* TMORPH2.T
* This library imitates Kevin Forchione's tmorph.t library. However, it fixes
* a major problem with the original library, which could not handle strings
* with embedded code (i.e. code in <<>>). This version also handles recursive
* expressions.
*
* For the copyright notice to the original tmorph.t, see the end of this
* file. While this file is not simply a modified version of the original (I
* wrote most of it from scratch), there are a few portions which are almost
* direct copies.
*
* This library Copyright 2003 by Shadow Wolf. All Rights Reserved. Distribution
* terms are the same as Kevin Forchione's original library:
*
* You may modify and use this file in any way you want, provided that
* if you redistribute modified copies of this file in source form, the
* copies must include the original copyright notice (including this
* paragraph), and must be clearly marked as modified from the original
* version.
*/
/*******************************************************
* Using TMORPH2.T
*
* Morph expressions are embedded within the text string you want to display,
* using square brackets '[]' to delimit them. Each morph expression consists of
* the following elements:
* An optional initializer followed by a pipe '|': The initializer can either
* be a number (typically generated by an embedded expression) or a name and
* type pair. If the pipe is used without an initializer, a random value will
* be used. If no initializer or pipe is used, then the most recently
* generated value will be used.
*
* After the initializer is a list of alternative strings, separated by
* slashes '/'. You can leave an element blank in order to not display any
* text.
*
* This improved version of the text morphing library allows for recursive
* expressions. The innermost morph expression is generated first, with its
* result placed in the outer expression. Evaluated expressions (code in << >>
* delimiters) are also evaluated before the morph expression containing them.
*
* This version also handles HTML tags (by ignoring them), if USE_HTML_PROMPT is
* defined. (I figure anyone writing an HTML game is going to use HTML prompts as a
* minimum.)
*
* Example:
*
* (From Kevin Forchione's original library)
* "[|A ball/Balls] of searing flame burst[s/]
* out of your magic ring, rebound[s/] off of the ground, and
* vaporize[s/] the kni[fe/ves] before [it/they]
* can reach you.";
*
* The first expression "[|A ball/Balls] has the pipe to initialize the
* sequence, and the remaining expressions use the same value.
*
* Dynamic Control: Since embedded expressions can be evaluated, simply
* include the expression before the pipe:
*
* "[<<object.knifecount>>|A ball/Balls/A mighty fireball]"
*
* No need to modify global variables (I'm not using a global anyway :-)
*
* Recursive Example:
*
* magicRing: clothingItem
* sdesc = "[|gold /magic /plain gold /]ring"
* noun = 'ring'
* adjective = 'plain' 'gold' 'magic'
* putOnDesc = "[ringPutOnDesc rrm|%You% put%s% on
* <<self.thedesc>>./<<caps()>><<self.thedesc>> fits neatly on %your%
* finger./Worn.] "
* location = startroom
* ;
*
* Special Sequences:
*
* There are five types of sequences, encoded with seq, rnd, mod, rrs, rrm.
*
* MorphSequencer (seq)
* --------------------
*
* [myseq seq|A ball/Balls/A mighty fireball]
*
* Creates a sequence named 'myseq', which produces 'A ball', 'Balls', and
* 'A mighty fireball' in that order, continuing with 'A mighty fireball' for
* all subsequent calls.
*
* MorphRandomizer (rnd)
* ---------------------
*
* [myrnd rnd|A ball/Balls/A mighty fireball]
*
* Produces output in a completely random fashion. Equivalent to not
* specifying a sequence (i.e. pipe only).
*
* MorphModulus (mod)
* ------------------
*
* [mymod mod|A ball/Balls/A mighty fireball]
*
* Similar to MorphSeqencer, except it wraps around to the beginning when the
* list is exhausted.
*
* MorphRedRndSeq (rrs)
* --------------------
*
* [myrrs rrs|A ball/Balls/A mighty fireball]
*
* Short for Reducing Random Sequence, I believe. When a particular selection
* is used, it is removed from the list in subsequent calls. (Like dealing
* from a deck). When the list is exhausted, the last displayed choice is used.
*
* MorphRedRndMod (rrm)
* --------------------
*
* [myrrm rrm|A ball/Balls/A mighty fireball]
*
* Like rrs above, except that when the list is exhausted it is re-shuffled.
*
*/
#ifndef __TMORPH_MODULE_
#define __TMORPH_MODULE_
// The following module by Kevin Forchione is required for parsing the
// initializer strings. Get it at www.ifarchive.org or mirrors, as
// if-archive/programming/tads2/examples/parseword.t
#include <parseword.t>
#pragma C+
class morphExpr: object
expr = '' // The current expression string
evaluate = {
local initial, str;
local lst, ret, i, w, s, intag;
// Split expression into initializer and list portion
str = self.expr;
ret = reSearch('%|', str);
if (ret == nil) {
initial = nil;
} else {
initial = substr(str, 1, ret[1] -1);
str = substr(str, ret[1] + 1, length(str));
}
// Create list of alternatives (basically copied from tmorph.t):
lst = []; w = ''; intag = nil;
for (i = 1; i <= length(str); i++) {
s = substr(str, i, 1);
#ifdef USE_HTML_PROMPT
if (s == '<') {intag = true; w += s;}
else if (s == '>' and intag) {intag = nil; w += s;}
else
#endif
if (not intag and s == '/') {
lst += w; w = '';
} else w += s;
}
lst += w;
if (initial != nil)
self.parseInitializer (initial, length(lst));
return lst[tmorph.choice];
}
parseInitializer(str, len) = {
// Much of this method is a near-copy of the morphTracker function
// in Kevin's original tmorph.t
local tk, o, mt = 'rnd';
local tokenList = parseWord(str);
if (length(tokenList) < 1) {
tmorph.choice = _rand(len);
return;
}
tk = tokenList[1];
if (length(tokenList) == 2) mt = tokenList[2];
// You can use a number or evaluated expression as an initializer!
if (cvtnum(tk) > 0) {
o = cvtnum(tk) % len;
if (o == 0) o = len;
tmorph.choice = o;
return;
}
o = tmorph.getSequence(tk);
if (o == nil) {
switch (mt) {
case 'seq':
o = MorphSequencer.instantiate(tk, len);
break;
case 'mod':
o = MorphModulus.instantiate(tk, len);
break;
case 'rrm':
o = MorphRedRandMod.instantiate(tk, len);
break;
case 'rrs':
o = MorphRedRandSeq.instantiate(tk, len);
break;
default:
tmorph.choice = _rand(len);
return;
}
}
tmorph.choice = o.getval;
}
;
// tmorph:
// This object carries the necessary global state for the library. I could
// have modified global, but I prefer not to clutter the namespace.
tmorph: object
choice = 1 // choice variable, set by user or | operator
curr = nil // Current morph expression
stack = [] // stack of previous morph expressions
push = {
// push current expression onto the stack, begin new expr
if (self.curr != nil)
self.stack = [self.curr] + self.stack;
self.curr = new morphExpr;
self.curr.expr = '';
}
pop = {
if (self.curr != nil)
delete self.curr;
self.curr = nil;
if (car(self.stack)) {
self.curr = car(self.stack);
self.stack = cdr(self.stack);
}
}
sequences = []
getSequence (tk) = {
// This function is also mostly from tmorph.t
local l = self.sequences;
local c = car(l);
while(c) {
l = cdr(l);
if (c.name == tk) return c;
c = car(l);
}
return nil;
}
inTag = nil // in HTML tag
;
morphFilter: function (s)
{
local print = '';
local ret, tmp;
while (s != '') {
#ifdef USE_HTML_PROMPT
ret = reSearch('%[|%]|<|>', s);
#else
ret = reSearch('%[|%]',s);
#endif
if (ret == nil) { // finished
if (tmorph.curr == nil) {
print += s;
return print;
} else {
tmorph.curr.expr += s;
return print; // print the available part
}
}
#ifdef USE_HTML_PROMPT
if (tmorph.inTag) {
tmp = substr(s, 1, ret[1]);
s = substr(s, ret[1]+1, length(s));
if (tmorph.curr == nil)
print += tmp;
else
tmorph.curr.expr += tmp;
if (ret[3] == '>') tmorph.inTag = nil;
} else if (ret[3] == '<') {
tmorph.inTag = true;
tmp = substr(s, 1, ret[1]);
s = substr(s, ret[1]+1, length(s));
if (tmorph.curr == nil)
print += tmp;
else tmorph.curr.expr += tmp;
} else if (ret[3] == '>') {
tmp = substr(s, 1, ret[1]);
s = substr(s, ret[1]+1, length(s));
if (tmorph.curr == nil)
print += tmp;
else tmorph.curr.expr += tmp;
} else
#endif
if (tmorph.curr==nil and ret[3] == '[') // not in an expression
{
print += substr(s, 1, ret[1]-1);
s = substr(s, ret[1]+1, length(s));
tmorph.push;
} else if (tmorph.curr == nil and ret[3] == ']' ){
return '\b[ERROR: unbalanced square brackets]\b';
} else if (ret[3] == '[') {
tmorph.curr.expr += substr(s, 1, ret[1] - 1);
s = substr(s, ret[1]+1, length(s));
tmorph.push; // push current expr, begin a new one
} else if (ret[3] == ']') {
tmorph.curr.expr += substr(s, 1, ret[1] -1);
s = substr(s, ret[1] +1, length(s));
tmp = tmorph.curr.evaluate;
tmorph.pop; // pop the stack
if (tmorph.curr == nil) {
print += tmp;
} else {
tmorph.curr.expr += tmp;
}
} else {
return '\b[ERROR: matched a non-bracket!]\b';
}
}
return print;
}
// MorphSequencer and its derivatives are taken with only slight modification
// from the original tmorph.t by Kevin Forchione
class MorphSequencer: object
len = 0
val = 0
name = ''
getval = {
self.val++;
if (self.val > self.len) self.val = self.len;
return self.val;
}
instantiate(tk, l) = {
local x = new MorphSequencer;
x.len = l;
x.name = tk;
tmorph.sequences += x;
return x;
}
;
class MorphRandomizer: MorphSequencer
getval = {
self.val = _rand(self.len);
return self.val;
}
instantiate(tk, l) = {
local x = new MorphRandomizer;
x.len = l;
x.name = tk;
tmorph.sequences += x;
return x;
}
;
class MorphModulus: MorphSequencer
getval = {
self.val++;
if (self.val > self.len) self.val = 1;
return self.val;
}
instantiate(tk, l) = {
local x = new MorphModulus;
x.len = l;
x.name = tk;
tmorph.sequences += x;
return x;
}
;
class MorphRedRandMod: MorphSequencer
rndList = []
getval = {
local i, ln;
ln = length(self.rndList);
if (ln == 0) {
// (re)build rndList
ln = self.len;
self.rndList = [];
for (i = 1; i <= ln; ++i)
self.rndList += i;
}
class MorphRedRandSeq: MorphSequencer
rndList = []
getval = {
local i, ln;
ln = length (self.rndList);
if (ln == 0) {
return self.val;
}
self.val = self.rndList[_rand(ln)];
self.rndList -= self.val;
return self.val;
}
instantiate(tk, l) = {
local x = new MorphRedRandSeq;
local i;
x.len = l;
x.name = tk;
tmorph.sequences += x;
x.rndList = [];
for (i = 1; i <= l; ++i)
x.rndList += i;
return x;
}
;
#endif // TMORPH_MODULE
// Below is the copyright information from the original tmorph.t module.
/* Copyright (c) 2000 by Kevin Forchione. All Rights Reserved. */
/*----------------------------------------------------------------------
* COPYRIGHT NOTICE
*
* You may modify and use this file in any way you want, provided that
* if you redistribute modified copies of this file in source form, the
* copies must include the original copyright notice (including this
* paragraph), and must be clearly marked as modified from the original
* version.
*
*------------------------------------------------------------------------------
* REVISION HISTORY
*
* 20-Feb-00: Creation.
* 21-Feb-00: Modified to include init in the function call.
*/