/* 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: * * "[<>|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 * <>./<><> 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 #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; } self.val = self.rndList[_rand(ln)]; self.rndList -= self.val; return self.val; } instantiate(tk, l) = { local x = new MorphRedRandMod; x.len = l; x.name = tk; tmorph.sequences += x; x.rndList = []; return x; } ; 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. */