diff options
author | Filippos Karapetis | 2018-12-27 14:48:11 +0200 |
---|---|---|
committer | Filippos Karapetis | 2018-12-28 13:41:39 +0200 |
commit | 91490c27adfde5f41b21bb2f3a6f648aab4270d0 (patch) | |
tree | 9af66c83e768a1219c7164fcbf70e3e66a35be8f /engines | |
parent | bba8eadfaef76365429249368ca8e6d1385dc3e8 (diff) | |
download | scummvm-rg350-91490c27adfde5f41b21bb2f3a6f648aab4270d0.tar.gz scummvm-rg350-91490c27adfde5f41b21bb2f3a6f648aab4270d0.tar.bz2 scummvm-rg350-91490c27adfde5f41b21bb2f3a6f648aab4270d0.zip |
GLK: ALAN2: Initial work on the Alan2 subengine
Diffstat (limited to 'engines')
-rw-r--r-- | engines/glk/alan2/acode.h | 289 | ||||
-rw-r--r-- | engines/glk/alan2/alan2.cpp | 41 | ||||
-rw-r--r-- | engines/glk/alan2/alan2.h | 67 | ||||
-rw-r--r-- | engines/glk/alan2/decode.cpp | 124 | ||||
-rw-r--r-- | engines/glk/alan2/decode.h | 70 | ||||
-rw-r--r-- | engines/glk/alan2/execute.cpp | 1065 | ||||
-rw-r--r-- | engines/glk/alan2/execute.h | 134 | ||||
-rw-r--r-- | engines/glk/alan2/interpreter.cpp | 768 | ||||
-rw-r--r-- | engines/glk/alan2/interpreter.h | 59 | ||||
-rw-r--r-- | engines/glk/alan2/parse.cpp | 976 | ||||
-rw-r--r-- | engines/glk/alan2/parse.h | 112 | ||||
-rw-r--r-- | engines/glk/alan2/rules.cpp | 87 | ||||
-rw-r--r-- | engines/glk/alan2/rules.h | 51 | ||||
-rw-r--r-- | engines/glk/alan2/saveload.cpp | 218 | ||||
-rw-r--r-- | engines/glk/alan2/saveload.h | 55 | ||||
-rw-r--r-- | engines/glk/alan2/types.h | 271 | ||||
-rw-r--r-- | engines/glk/alan2/util.h | 88 | ||||
-rw-r--r-- | engines/glk/module.mk | 6 |
18 files changed, 4481 insertions, 0 deletions
diff --git a/engines/glk/alan2/acode.h b/engines/glk/alan2/acode.h new file mode 100644 index 0000000000..764eb1967b --- /dev/null +++ b/engines/glk/alan2/acode.h @@ -0,0 +1,289 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_ACODE +#define GLK_ALAN2_ACODE + +#include "common/scummsys.h" + +namespace Glk { +namespace Alan2 { + +typedef size_t Aptr; // Type for an ACODE memory address +typedef uint32 Aword; // Type for an ACODE word +typedef uint32 Aaddr; // Type for an ACODE address +typedef uint32 Abool; // Type for an ACODE Boolean value +typedef int32 Aint; // Type for an ACODE Integer value +typedef int CodeValue; // Definition for the packing process + +// Constants for the Acode file, words/block & bytes/block +#define BLOCKLEN 256L +#define BLOCKSIZE (BLOCKLEN*sizeof(Aword)) + + +// Definitions for the packing process +#define VALUEBITS 16 + +#define EOFChar 256 +#define TOPVALUE (((CodeValue)1<<VALUEBITS) - 1) // Highest value possible + +// Half and quarter points in the code value range +#define ONEQUARTER (TOPVALUE/4+1) // Point after first quarter +#define HALF (2*ONEQUARTER) // Point after first half +#define THREEQUARTER (3*ONEQUARTER) // Point after third quarter + + +// AMACHINE Word Classes +typedef int WrdKind; +#define WRD_SYN 0 // 1 - Synonym +#define WRD_ADJ 1 // 2 - Adjective +#define WRD_ALL 2 // 4 - All +#define WRD_BUT 3 // 8 - But +#define WRD_CONJ 4 // 16 - Conjunction +#define WRD_PREP 5 // 32 - Preposition +#define WRD_DIR 6 // 64 - Direction +#define WRD_IT 7 // 128 - It +#define WRD_NOISE 8 // 256 - Noise word +#define WRD_NOUN 9 // 512 - Noun +#define WRD_ACT 10 // 1024 - Actor +#define WRD_THEM 11 // 2048 - Them +#define WRD_VRB 12 // 4096 - Verb +#define WRD_CLASSES 13 + + + +// Syntax element classifications +#define EOS (-2) // End Of Syntax + +// Syntax element flag bits +#define MULTIPLEBIT 0x1 +#define OMNIBIT 0x2 + + +// Parameter Classes +typedef enum ClaKind { // NOTE! These must have the same order as + CLA_OBJ = 1, // the name classes in NAM.H + CLA_CNT = (int)CLA_OBJ<<1, + CLA_ACT = (int)CLA_CNT<<1, + CLA_NUM = (int)CLA_ACT<<1, + CLA_STR = (int)CLA_NUM<<1, + CLA_COBJ = (int)CLA_STR<<1, + CLA_CACT = (int)CLA_COBJ<<1 +} ClaKind; + + +// Verb Qualifiers +typedef enum QualClass { + Q_DEFAULT, + Q_AFTER, + Q_BEFORE, + Q_ONLY +} QualClass; + + +// The AMACHINE Operations +typedef enum OpClass { + C_CONST, + C_STMOP, + C_CURVAR +} OpClass; + +typedef enum InstClass { + I_PRINT, // Print a string from the text file + I_QUIT, + I_LOOK, + I_SAVE, + I_RESTORE, + I_LIST, // List contents of a container + I_EMPTY, + I_SCORE, + I_VISITS, + I_SCHEDULE, + I_CANCEL, + I_LOCATE, + I_MAKE, + I_SET, // Set a numeric attribute to the + // value on top of stack + I_STRSET, // Set a string valued attribute to a + // copy of the string on top of stack, + // deallocate current contents first + I_GETSTR, // Get a string contents from text + // file, create a copy and push it + // on top of stack + I_INCR, // Increment an attribute + I_DECR, // Decrement a numeric attribute + I_USE, + I_IN, + I_DESCRIBE, + I_SAY, + I_SAYINT, + I_SAYSTR, + I_IF, + I_ELSE, + I_ENDIF, + I_ATTRIBUTE, + I_STRATTR, // Push a copy of a string attribute + I_HERE, + I_NEAR, + I_WHERE, + I_AND, + I_OR, + I_NE, + I_EQ, + I_STREQ, // String compare + I_STREXACT, + I_LE, + I_GE, + I_LT, + I_GT, + I_PLUS, + I_MINUS, + I_MULT, + I_DIV, + I_NOT, + I_UMINUS, + I_RND, + I_SUM, // SUM-aggregate + I_MAX, // MAX-aggregate + I_COUNT, // COUNT-aggregate + I_RETURN, + I_SYSTEM, + I_RESTART, // INTRODUCED: v2.7 + I_BTW, // INTRODUCED: v2.8 + I_CONTAINS, // -""- + I_DEPSTART, // -""- + I_DEPCASE, // -""- + I_DEPEXEC, // -""- + I_DEPELSE, // -""- + I_DEPEND // -""- +} InstClass; + + +typedef enum VarClass { + V_PARAM, + V_CURLOC, + V_CURACT, + V_CURVRB, + V_SCORE +} VarClass; + + +#define I_CLASS(x) ((x)>>28) +#define I_OP(x) ((x&0x8000000)?(x)|0x0f0000000:(x)&0x0fffffff) + + +typedef struct AcdHdr { +// Important info + char vers[4]; // 01 - Version of compiler + Aword size; // 02 - Size of ACD-file in Awords +// Options + Abool pack; // 03 - Is the text packed ? + Aword paglen; // 04 - Length of a page + Aword pagwidth; // 05 - and width + Aword debug; // 06 - Option debug +// Data structures + Aaddr dict; // 07 - Dictionary + Aaddr oatrs; // 08 - Object default attributes + Aaddr latrs; // 09 - Location default attributes + Aaddr aatrs; // 0a - Actor default attributes + Aaddr acts; // 0b - Actor table + Aaddr objs; // 0c - Object table + Aaddr locs; // 0d - Location table + Aaddr stxs; // 0e - Syntax table + Aaddr vrbs; // 0f - Verb table + Aaddr evts; // 10 - Event table + Aaddr cnts; // 11 - Container table + Aaddr ruls; // 12 - Rule table + Aaddr init; // 13 - String init table + Aaddr start; // 14 - Start code + Aword msgs; // 15 - Messages table +// Miscellaneous + Aword objmin, objmax; // 16 - Interval for object codes + Aword actmin, actmax; // 18 - Interval for actor codes + Aword cntmin, cntmax; // 1a - Interval for container codes + Aword locmin, locmax; // 1c - Interval for location codes + Aword dirmin, dirmax; // 1e - Interval for direction codes + Aword evtmin, evtmax; // 20 - Interval for event codes + Aword rulmin, rulmax; // 22 - Interval for rule codes + Aword maxscore; // 24 - Maximum score + Aaddr scores; // 25 - Score table + Aaddr freq; // 26 - Address to Char freq's for coding + Aword acdcrc; // 27 - Checksum for acd code (excl. hdr) + Aword txtcrc; // 28 - Checksum for text data file +} AcdHdr; + +// Error message numbers +typedef enum MsgKind { + M_HUH, // Obsolete + M_WHAT, + M_WHAT_ALL, + M_WHAT_IT, + M_WHAT_THEM, + M_MULTIPLE, + M_WANT, + M_NOUN, + M_AFTER_BUT, + M_BUT_ALL, + M_NOT_MUCH, + M_WHICH_ONE, + M_NO_SUCH, + M_NO_WAY, + M_CANT0, + M_CANT, + M_NOTHING, // Obsolete + M_SEEOBJ1, + M_SEEOBJ2, + M_SEEOBJ3, + M_SEEOBJ4, + M_SEEACT, + M_CONTAINS1, + M_CONTAINS2, + M_CONTAINS3, + M_CONTAINS4, + M_CONTAINS5, + M_EMPTY1, + M_EMPTY2, + M_SCORE1, + M_SCORE2, + M_UNKNOWN_WORD, + M_MORE, + M_AGAIN, + M_SAVEWHERE, + M_SAVEOVERWRITE, + M_SAVEFAILED, + M_SAVEMISSING, + M_SAVEVERS, + M_SAVENAME, + M_RESTOREFROM, + M_REALLY, // CHANGED: v2.7 from M_RESTART + M_QUITACTION, // INTRODUCED: v2.7, so M_ARTICLE moved + M_ARTICLE, // INTRODUCED: v2.6 but replaced the M_NOMSG + MSGMAX +} MsgKind; + +#define M_ARTICLE26 M_QUITACTION +#define M_MSGMAX26 M_ARTICLE + +} // End of namespace Alan2 +} // Engine of namespace GLK + +#endif diff --git a/engines/glk/alan2/alan2.cpp b/engines/glk/alan2/alan2.cpp index 3869e34c90..c29a124f9a 100644 --- a/engines/glk/alan2/alan2.cpp +++ b/engines/glk/alan2/alan2.cpp @@ -21,6 +21,10 @@ */ #include "glk/alan2/alan2.h" +#include "glk/alan2/decode.h" +#include "glk/alan2/execute.h" +#include "glk/alan2/interpreter.h" +#include "glk/alan2/saveload.h" #include "common/config-manager.h" #include "common/translation.h" #include "common/error.h" @@ -32,14 +36,24 @@ namespace Glk { namespace Alan2 { +Alan2 *_vm = nullptr; Alan2::Alan2(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), vm_exited_cleanly(false) { + _vm = this; } void Alan2::runGame(Common::SeekableReadStream *gameFile) { _gameFile = gameFile; + // TODO: Initialize these properly + int tmp = 0; + Common::String gameFileName; + _decode = new Decode(nullptr, nullptr); + _execute = new Execute(); + _saveLoad = new SaveLoad(gameFileName, nullptr, nullptr, nullptr, nullptr, &tmp); + _interpreter = new Interpreter(_execute, _saveLoad, _stack); + if (!is_gamefile_valid()) return; @@ -70,5 +84,32 @@ bool Alan2::is_gamefile_valid() { return true; } +void Alan2::output(const Common::String str) +{ + // TODO +} + +void Alan2::printMessage(MsgKind msg) +{ + // TODO +} + +void Alan2::printError(MsgKind msg) +{ + // TODO +} + +void Alan2::paragraph() +{ + if (col != 1) + newLine(); + newLine(); +} + +void Alan2::newLine() +{ + // TODO +} + } // End of namespace Alan2 } // End of namespace Glk diff --git a/engines/glk/alan2/alan2.h b/engines/glk/alan2/alan2.h index f4515516f7..716e198c74 100644 --- a/engines/glk/alan2/alan2.h +++ b/engines/glk/alan2/alan2.h @@ -26,10 +26,20 @@ #include "common/scummsys.h" #include "common/stack.h" #include "glk/glk_api.h" +#include "glk/alan2/acode.h" +#include "glk/alan2/types.h" namespace Glk { namespace Alan2 { +typedef Common::FixedStack<Aptr, 100> Alan2Stack; +class Decode; +class Execute; +class Interpreter; +class SaveLoad; + +#define N_EVTS 100 + /** * Alan2 game interpreter */ @@ -67,8 +77,65 @@ public: * Save the game to the passed stream */ virtual Common::Error saveGameData(strid_t file, const Common::String &desc) override; + + /** + * Output a string to the screen + */ + void output(const Common::String str); + + /** + * Print a message from the message table + */ + void printMessage(MsgKind msg); + + /** + * Print an error from the message table, force new player input and abort + */ + void printError(MsgKind msg); + + /** + * Make a new paragraph, i.e one empty line (one or two newlines) + */ + void paragraph(); + + /** + * Print the the status line on the top of the screen + */ + void statusLine(); + + /** + * Make a newline, but check for screen full + */ + void newLine(); + + // Engine variables + Alan2Stack *_stack; + int pc; + ParamElem *params; + Aword *memory; // The Amachine memory + int memTop; // Top of memory + CurVars cur; // Amachine variables + int col; + bool fail; + int scores[100]; // FIXME: type + size + AcdHdr *header; + bool _needSpace; // originally "needsp" + + EvtElem *evts; // Event table pointer + bool looking = false; // LOOKING? flag + int dscrstkp = 0; // Describe-stack pointer + Common::File *_txtFile; + bool _anyOutput; + winid_t _bottomWindow; + + Decode *_decode; + Execute *_execute; + Interpreter *_interpreter; + SaveLoad *_saveLoad; }; +extern Alan2 *_vm; + } // End of namespace Alan2 } // End of namespace Glk diff --git a/engines/glk/alan2/decode.cpp b/engines/glk/alan2/decode.cpp new file mode 100644 index 0000000000..ba144123c6 --- /dev/null +++ b/engines/glk/alan2/decode.cpp @@ -0,0 +1,124 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/alan2/decode.h" + +namespace Glk { +namespace Alan2 { + +int Decode::inputBit() { + int bit; + + if (!_bitsToGo) { // More bits available ? + _decodeBuffer = _txtFile->readByte(); // No, so get more + if (_txtFile->eos()) { + _garbageBits++; + + if (_garbageBits > VALUEBITS - 2) + error("Error in encoded data file."); + } else + _bitsToGo = 8; // Another Char, 8 new bits + } + + bit = _decodeBuffer & 1; // Get next bit + _decodeBuffer = _decodeBuffer >> 1; // and remove it + _bitsToGo--; + + return bit; +} + +void Decode::startDecoding() { + _bitsToGo = 0; + _garbageBits = 0; + + _value = 0; + for (int i = 0; i < VALUEBITS; i++) + _value = 2 * _value + inputBit(); + + _low = 0; + _high = TOPVALUE; +} + +int Decode::decodeChar() { + const long range = (long)(_high - _low) + 1; + const int f = (((long)(_value - _low) + 1) * _freq[0] - 1) / range; + int symbol; + + // Find the symbol + for (symbol = 1; _freq[symbol] > f; symbol++); + + _high = _low + range * _freq[symbol - 1] / _freq[0] - 1; + _low = _low + range * _freq[symbol] / _freq[0]; + + for (;;) { + if (_high < HALF) { + // Do nothing + } else if (_low >= HALF) { + _value = _value - HALF; + _low = _low - HALF; + _high = _high - HALF; + } else if (_low >= ONEQUARTER && _high < THREEQUARTER) { + _value = _value - ONEQUARTER; + _low = _low - ONEQUARTER; + _high = _high - ONEQUARTER; + } else + break; + + // Scale up the range + _low = 2 * _low; + _high = 2 * _high + 1; + _value = 2 * _value + inputBit(); + } + + return symbol - 1; +} + +// Save so much about the decoding process, so it is possible to restore +// and continue later. +DecodeInfo* Decode::pushDecode() { + DecodeInfo *info = new DecodeInfo(); + + info->fpos = _txtFile->pos(); + info->buffer = _decodeBuffer; + info->bits = _bitsToGo; + info->value = _value; + info->high = _high; + info->low = _low; + + return info; +} + +// Restore enough info about the decoding process, so it is possible to +// continue after having decoded something else +void Decode::popDecode (DecodeInfo *info){ + _txtFile->seek(info->fpos, SEEK_CUR); + _decodeBuffer = info->buffer; + _bitsToGo = info->bits; + _value = info->value; + _high = info->high; + _low = info->low; + + delete info; +} + +} // End of namespace Alan2 +} // End of namespace Glk diff --git a/engines/glk/alan2/decode.h b/engines/glk/alan2/decode.h new file mode 100644 index 0000000000..fff6819878 --- /dev/null +++ b/engines/glk/alan2/decode.h @@ -0,0 +1,70 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_DECODE +#define GLK_ALAN2_DECODE + +#include "glk/alan2/alan2.h" +#include "glk/alan2/acode.h" +#include "common/file.h" + +namespace Glk { +namespace Alan2 { + +// Structure for saved decode info +typedef struct DecodeInfo { + long fpos; + int buffer; + int bits; + CodeValue value; + CodeValue high; + CodeValue low; +} DecodeInfo; + +class Decode +{ +public: + Decode(Common::File *txtFile, Aword *freq): _txtFile(txtFile), _freq(freq) {} + void startDecoding(); + int decodeChar(); + DecodeInfo *pushDecode(); + void popDecode(DecodeInfo *info); + int inputBit(); + +private: + // Bit output + int _decodeBuffer; // Bits to be input + int _bitsToGo; // Bits still in buffer + int _garbageBits; // Bits past EOF + + Aword *_freq; + Common::File *_txtFile; + + // Current state of decoding + CodeValue _value; // Currently seen code value + CodeValue _low, _high; // Current code region +}; + +} // End of namespace Alan2 +} // Engine of namespace GLK + +#endif diff --git a/engines/glk/alan2/execute.cpp b/engines/glk/alan2/execute.cpp new file mode 100644 index 0000000000..5b81b303d9 --- /dev/null +++ b/engines/glk/alan2/execute.cpp @@ -0,0 +1,1065 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/stack.h" +#include "glk/alan2/alan2.h" +#include "glk/alan2/execute.h" +#include "glk/alan2/interpreter.h" +#include "glk/alan2/saveload.h" +#include "glk/alan2/types.h" +#include "glk/alan2/util.h" +#include "common/debug.h" +#include "common/file.h" +#include "decode.h" + +namespace Glk { +namespace Alan2 { + +#define WIDTH 80 + +// Is there an exit from one location to another ? +bool Execute::exitto(int to, int from) { + if (_locs[from - LOCMIN].exts == 0) + return false; // No exits + + for (ExtElem *ext = (ExtElem *)addrTo(_locs[from - LOCMIN].exts); !endOfTable(ext); ext++) + if (ext->next == to) + return true; + + return false; +} + +/* + * Count the number of items in a container. + * + * @param cnt The container to count + */ +int Execute::count(int cnt) { + int j = 0; + + for (int i = OBJMIN; i <= OBJMAX; i++) + if (in(i, cnt)) + j++; // Then it's in this container also + + return j; +} + +/* + * Sum the values of one attribute in a container.Recursively. + * + * @param atr The attribute to sum over + * @param cnt the container to sum + */ +int Execute::sumatr(Aword atr, Aword cnt) { + int sum = 0; + + for (int i = OBJMIN; i <= OBJMAX; i++) { + if (_objs[i - OBJMIN].loc == cnt) { // Then it's in this container + if (_objs[i - OBJMIN].cont != 0) // This is also a container! + sum = sum + sumatr(atr, i); + sum = sum + attribute(i, atr); + } + } + + return sum; +} + +/** + * Checks if a limit for a container is exceeded. + * + * @param cnt Container code + * @param obj The object to add + */ +// +bool Execute::checklim(Aword cnt, Aword obj) { + LimElem *lim; + Aword props; + + _vm->fail = true; + if (!isCnt(cnt)) + error("Checking limits for a non-container."); + + // Find the container properties + if (isObj(cnt)) + props = _objs[cnt - OBJMIN].cont; + else if (isAct(cnt)) + props = _acts[cnt - ACTMIN].cont; + else + props = cnt; + + + if (_cnts[props - CNTMIN].lims != 0) { /* Any limits at all? */ + for (lim = (LimElem *)addrTo(_cnts[props - CNTMIN].lims); !endOfTable(lim); lim++) { + if (lim->atr == 0) { + if (count(cnt) >= lim->val) { + _vm->_interpreter->interpret(lim->stms); + return true; // Limit check failed + } + } else { + if (sumatr(lim->atr, cnt) + attribute(obj, lim->atr) > lim->val) { + _vm->_interpreter->interpret(lim->stms); + return true; + } + } + } + } + + _vm->fail = false; + + return false; +} + +void Execute::print(Aword fpos, Aword len) { + char str[2 * WIDTH]; // String buffer + int outlen = 0; // Current output length + int ch; + int i; + long savfp; // Temporary saved text file position + bool printFlag = false; // Printing already? + bool savedPrintFlag = printFlag; + DecodeInfo *info; // Saved decoding info + + + if (len == 0) + return; + + if (isHere(HERO)) { // Check if the player will see it + if (printFlag) { // Already printing? + // Save current text file position and/or decoding info + if (_vm->header->pack) + info = _vm->_decode->pushDecode(); + else + savfp = _vm->_txtFile->pos(); + } + + printFlag = true; // We're printing now! + _vm->_txtFile->seek(fpos, SEEK_CUR); // Position to start of text + if (_vm->header->pack) + _vm->_decode->startDecoding(); + + for (outlen = 0; outlen != len; outlen = outlen + strlen(str)) { + // Fill the buffer from the beginning + for (i = 0; i <= WIDTH || (i > WIDTH && ch != ' '); i++) { + if (outlen + i == len) // No more characters? + break; + if (_vm->header->pack) + ch = _vm->_decode->decodeChar(); + else + ch = _vm->_txtFile->readSByte(); + if (ch == EOFChar) // Or end of text? + break; + str[i] = ch; + } + + str[i] = '\0'; + + // TODO + /* +#if ISO == 0 + fromIso(str, str); +#endif + */ + _vm->output(str); + } + + // And restore + printFlag = savedPrintFlag; + if (printFlag) { + if (_vm->header->pack) + _vm->_decode->popDecode(info); + else + _vm->_txtFile->seek(savfp, SEEK_CUR); + } + } +} + +void Execute::sys(Aword fpos, Aword len) { + char *command; + + getstr(fpos, len); // Returns address to string on stack + command = (char *)_vm->_stack->pop(); + + warning("Request to execute system command %s", command); + free(command); +} + +void Execute::getstr(Aword fpos, Aword len) { + char *buf = new char[len + 1]; + + _vm->_stack->push((Aptr) buf); // Push the address to the string + _vm->_txtFile->seek(fpos, SEEK_CUR); // Position to start of text + if (_vm->header->pack) + _vm->_decode->startDecoding(); + while (len--) { + if (_vm->header->pack) + *(buf++) = _vm->_decode->decodeChar(); + else + *(buf++) = _vm->_txtFile->readSByte(); + } + *buf = '\0'; +} + +void Execute::score(Aword sc) { + char buf[80]; + + if (sc == 0) { + _vm->printMessage(M_SCORE1); + sprintf(buf, "%d", _vm->cur.score); + _vm->output(buf); + _vm->printMessage(M_SCORE2); + sprintf(buf, "%ld.", (unsigned long)_vm->header->maxscore); + _vm->output(buf); + } else { + _vm->cur.score += _vm->scores[sc - 1]; + _vm->scores[sc - 1] = 0; + } +} + +void Execute::visits(Aword v) { + _vm->cur.visits = v; +} + +bool Execute::confirm(MsgKind msgno) { + char buf[80]; + + // This is a bit of a hack since we really want to compare the input, + // it could be affirmative, but for now any input is NOT! + _vm->printMessage(msgno); + + // TODO +#if 0 + //_vm->glk_request_line_event(_bottomWindow, buf, 80 - 1, 0); + +#ifdef USE_READLINE + if (!readline(buf)) return true; +#else + if (gets(buf) == NULL) return true; +#endif + +#endif + _vm->col = 1; + + return (buf[0] == '\0'); +} + + +void Execute::quit() { + char buf[80]; + char choices[10]; + + _vm->paragraph(); + while (true) { + _vm->col = 1; + _vm->statusLine(); + _vm->printMessage(M_QUITACTION); + + // TODO +#if 0 +#ifdef USE_READLINE + if (!readline(buf)) terminate(0); +#else + if (gets(buf) == NULL) terminate(0); +#endif +#endif + + if (strcmp(buf, "restart") == 0) { + //longjmp(restart_label, true); // TODO + } else if (strcmp(buf, "restore") == 0) { + _vm->_saveLoad->restore(); + return; + } else if (strcmp(buf, "quit") == 0) { + _vm->quitGame(); + } + } + error("Fallthrough in QUIT"); +} + +void Execute::restart() { + _vm->paragraph(); + if (confirm(M_REALLY)) { + //longjmp(restart_label, true); // TODO + } else + return; + + error("Fallthrough in RESTART"); +} + +/*---------------------------------------------------------------------- + eventchk() + + Check if any events are pending. If so execute them. + */ + +void Execute::eventchk() { + while (etop != 0 && eventq[etop - 1].time == _vm->cur.tick) { + etop--; + if (isLoc(eventq[etop].where)) + _vm->cur.loc = eventq[etop].where; + else + _vm->cur.loc = where(eventq[etop].where); + + // TODO +#if 0 + if (trcflg) { + debug("\n<EVENT %d (at ", eventq[etop].event); + debugsay(_vm->cur.loc); + debug("):>\n"); + } +#endif + _vm->_interpreter->interpret(_vm->evts[eventq[etop].event - EVTMIN].code); + } +} + +void Execute::cancl(Aword evt) { + int i; + + for (i = etop - 1; i >= 0; i--) { + if (eventq[i].event == evt) { + while (i < etop - 1) { + eventq[i].event = eventq[i + 1].event; + eventq[i].time = eventq[i + 1].time; + eventq[i].where = eventq[i + 1].where; + i++; + } + + etop--; + return; + } + } +} + +void Execute::schedule(Aword evt, Aword whr, Aword aft) { + int i; + int time; + + cancl(evt); + // Check for overflow + if (etop == N_EVTS) + error("Out of event space."); + + time = _vm->cur.tick+aft; + + // Bubble this event down + for (i = etop; i >= 1 && eventq[i-1].time <= time; i--) { + eventq[i].event = eventq[i-1].event; + eventq[i].time = eventq[i-1].time; + eventq[i].where = eventq[i-1].where; + } + + eventq[i].time = time; + eventq[i].where = whr; + eventq[i].event = evt; + etop++; +} + +/** + * Get an attribute value from an attribute list + * + * @param atradr ACODE address to attribute table + * @param atr The attribute to read + */ +Aptr Execute::getatr(Aaddr atradr, Aaddr atr) { + AtrElem *at = (AtrElem *) addrTo(atradr); + return at[atr-1].val; +} + +/** + * Set a particular attribute to a value. + * + * @param atradr ACODE address to attribute table + * @param atr Attribute code + * @param val New value + */ +void Execute::setatr(Aaddr atradr, Aword atr, Aword val) { + AtrElem *at = (AtrElem *) addrTo(atradr); + at[atr-1].val = val; +} + +void Execute::makloc(Aword loc, Aword atr, Aword val) { + setatr(_locs[loc - LOCMIN].atrs, atr, val); +} + +void Execute::makobj(Aword obj, Aword atr, Aword val) { + setatr(_objs[obj - OBJMIN].atrs, atr, val); +} + +void Execute::makact(Aword act, Aword atr, Aword val) { + setatr(_acts[act - ACTMIN].atrs, atr, val); +} + +void Execute::make(Aword id, Aword atr, Aword val) { + if (isObj(id)) + makobj(id, atr, val); + else if (isLoc(id)) + makloc(id, atr, val); + else if (isAct(id)) + makact(id, atr, val); + else + error("Can't MAKE item (%ld).", (unsigned long)id); +} + +void Execute::setloc(Aword loc, Aword atr, Aword val) { + setatr(_locs[loc - LOCMIN].atrs, atr, val); + _locs[loc - LOCMIN].describe = 0; +} + +void Execute::setobj(Aword obj, Aword atr, Aword val) { + setatr(_objs[obj - OBJMIN].atrs, atr, val); +} + +void Execute::setact(Aword act, Aword atr, Aword val) { + setatr(_acts[act - ACTMIN].atrs, atr, val); +} + +void Execute::set(Aword id, Aword atr, Aword val) { + if (isObj(id)) + setobj(id, atr, val); + else if (isLoc(id)) + setloc(id, atr, val); + else if (isAct(id)) + setact(id, atr, val); + else + error("Can't SET item (%ld).", (unsigned long)id); +} + +void Execute::setstr(Aword id, Aword atr, Aword str) { + free((char *)attribute(id, atr)); + set(id, atr, str); +} + +/** + * Increment a particular attribute by a value. + * + * @param atradr ACODE address to attribute table + * @param atr Attribute code + * @param step Step to increment by + */ +void Execute::incratr(Aaddr atradr, Aword atr, Aword step) { + AtrElem *at = (AtrElem *) addrTo(atradr); + at[atr-1].val += step; +} + +void Execute::incrloc(Aword loc, Aword atr, Aword step) { + incratr(_locs[loc - LOCMIN].atrs, atr, step); + _locs[loc - LOCMIN].describe = 0; +} + +void Execute::incrobj(Aword obj, Aword atr, Aword step) { + incratr(_objs[obj - OBJMIN].atrs, atr, step); +} + +void Execute::incract(Aword act, Aword atr, Aword step) { + incratr(_acts[act - ACTMIN].atrs, atr, step); +} + +void Execute::incr(Aword id, Aword atr, Aword step) { + if (isObj(id)) + incrobj(id, atr, step); + else if (isLoc(id)) + incrloc(id, atr, step); + else if (isAct(id)) + incract(id, atr, step); + else + error("Can't INCR item (%ld).", (unsigned long)id); +} + +void Execute::decr(Aword id, Aword atr, Aword step) { + if (isObj(id)) + incrobj(id, atr, -step); + else if (isLoc(id)) + incrloc(id, atr, -step); + else if (isAct(id)) + incract(id, atr, -step); + else + error("Can't DECR item (%ld).", (unsigned long)id); +} + +Aptr Execute::locatr(Aword loc, Aword atr) { + return getatr(_locs[loc - LOCMIN].atrs, atr); +} + +Aptr Execute::objatr(Aword obj, Aword atr) { + return getatr(_objs[obj - OBJMIN].atrs, atr); +} + +Aptr Execute::actatr(Aword act, Aword atr) { + return getatr(_acts[act - ACTMIN].atrs, atr); +} + +Aptr Execute::litatr(Aword lit, Aword atr) { + if (atr == 1) + return litValues[lit - LITMIN].value; + else + error("Unknown attribute for literal (%ld).", (unsigned long) atr); +} + +Aptr Execute::attribute(Aword id, Aword atr) { + if (isObj(id)) + return objatr(id, atr); + else if (isLoc(id)) + return locatr(id, atr); + else if (isAct(id)) + return actatr(id, atr); + else if (isLit(id)) + return litatr(id, atr); + else + error("Can't ATTRIBUTE item (%ld).", (unsigned long) id); +} + +Aptr Execute::strattr(Aword id, Aword atr) { + Common::String result = (char *)attribute(id, atr); + return (Aptr)result.c_str(); +} + +Aword Execute::objloc(Aword obj) { + if (isCnt(_objs[obj - OBJMIN].loc)) { // In something ? + if (isObj(_objs[obj - OBJMIN].loc) || isAct(_objs[obj - OBJMIN].loc)) + return(where(_objs[obj - OBJMIN].loc)); + else // Containers not anywhere is where the hero is! + return(where(HERO)); + } else { + return(_objs[obj - OBJMIN].loc); + } +} + +Aword Execute::actloc(Aword act) { + return(_acts[act - ACTMIN].loc); +} + +Aword Execute::where(Aword id) { + if (isObj(id)) + return objloc(id); + else if (isAct(id)) + return actloc(id); + else + error("Can't WHERE item (%ld).", (unsigned long) id); +} + +Aint Execute::agrmax(Aword atr, Aword whr) { + Aword i; + Aint max = 0; + + for (i = OBJMIN; i <= OBJMAX; i++) { + if (isLoc(whr)) { + if (where(i) == whr && attribute(i, atr) > max) + max = attribute(i, atr); + } else if (_objs[i - OBJMIN].loc == whr && attribute(i, atr) > max) + max = attribute(i, atr); + } + + return(max); +} + +Aint Execute::agrsum(Aword atr, Aword whr) { + Aword i; + Aint sum = 0; + + for (i = OBJMIN; i <= OBJMAX; i++) { + if (isLoc(whr)) { + if (where(i) == whr) + sum += attribute(i, atr); + } else if (_objs[i-OBJMIN].loc == whr) + sum += attribute(i, atr); + } + + return(sum); +} + +Aint Execute::agrcount(Aword whr) { + Aword i; + Aword count = 0; + + for (i = OBJMIN; i <= OBJMAX; i++) { + if (isLoc(whr)) { + if (where(i) == whr) + count++; + } else if (_objs[i-OBJMIN].loc == whr) + count++; + } + + return(count); +} + +void Execute::locobj(Aword obj, Aword whr) { + if (isCnt(whr)) { // Into a container + if (whr == obj) + error("Locating something inside itself."); + if (checklim(whr, obj)) + return; + else + _objs[obj-OBJMIN].loc = whr; + } else { + _objs[obj-OBJMIN].loc = whr; + // Make sure the location is described since it's changed + _locs[whr-LOCMIN].describe = 0; + } +} + +void Execute::locact(Aword act, Aword whr) { + Aword prevact = _vm->cur.act; + Aword prevloc = _vm->cur.loc; + + _vm->cur.loc = whr; + _acts[act - ACTMIN].loc = whr; + + if (act == HERO) { + if (_locs[_acts[act - ACTMIN].loc-LOCMIN].describe % (_vm->cur.visits+1) == 0) + look(); + else { + if (_vm->_anyOutput) + _vm->paragraph(); + + say(where(HERO)); + _vm->printMessage(M_AGAIN); + _vm->newLine(); + dscrobjs(); + dscracts(); + } + + _locs[where(HERO)-LOCMIN].describe++; + _locs[where(HERO)-LOCMIN].describe %= (_vm->cur.visits+1); + } else + _locs[whr-LOCMIN].describe = 0; + + if (_locs[_vm->cur.loc-LOCMIN].does != 0) { + _vm->cur.act = act; + _vm->_interpreter->interpret(_locs[_vm->cur.loc-LOCMIN].does); + _vm->cur.act = prevact; + } + + if (_vm->cur.act != act) + _vm->cur.loc = prevloc; +} + + +void Execute::locate(Aword id, Aword whr) { + if (isObj(id)) + locobj(id, whr); + else if (isAct(id)) + locact(id, whr); + else + error("Can't LOCATE item (%ld).", (unsigned long) id); +} + +Abool Execute::objhere(Aword obj) { + if (isCnt(_objs[obj - OBJMIN].loc)) { // In something? + if (isObj(_objs[obj - OBJMIN].loc) || isAct(_objs[obj - OBJMIN].loc)) + return(isHere(_objs[obj - OBJMIN].loc)); + else // If the container wasn't anywhere, assume where HERO is! + return(where(HERO) == _vm->cur.loc); + } else + return(_objs[obj - OBJMIN].loc == _vm->cur.loc); +} + +Aword Execute::acthere(Aword act) { + return _acts[act - ACTMIN].loc == _vm->cur.loc; +} + +Abool Execute::isHere(Aword id) { + if (isObj(id)) + return objhere(id); + else if (isAct(id)) + return acthere(id); + else + error("Can't HERE item (%ld).", (unsigned long)id); +} + +Aword Execute::objnear(Aword obj) { + if (isCnt(_objs[obj-OBJMIN].loc)) { // In something? + if (isObj(_objs[obj-OBJMIN].loc) || isAct(_objs[obj-OBJMIN].loc)) + return(isNear(_objs[obj-OBJMIN].loc)); + else // If the container wasn't anywhere, assume here, so not nearby! + return(false); + } else { + return(exitto(where(obj), _vm->cur.loc)); + } +} + +Aword Execute::actnear(Aword act) { + return(exitto(where(act), _vm->cur.loc)); +} + + +Abool Execute::isNear(Aword id) { + if (isObj(id)) + return objnear(id); + else if (isAct(id)) + return actnear(id); + else + error("Can't NEAR item (%ld).", (unsigned long) id); +} + +Abool Execute::in(Aword obj, Aword cnt) { + if (!isObj(obj)) + return(false); + if (!isCnt(cnt)) + error("IN in a non-container."); + + return(_objs[obj - OBJMIN].loc == cnt); +} + +void Execute::sayloc(Aword loc) { + _vm->_interpreter->interpret(_locs[loc - LOCMIN].nams); +} + +void Execute::sayobj(Aword obj) { + _vm->_interpreter->interpret(_objs[obj - OBJMIN].dscr2); +} + +void Execute::sayact(Aword act) { + _vm->_interpreter->interpret(_acts[act - ACTMIN].nam); +} + +void Execute::sayint(Aword val) { + char buf[25]; + + if (isHere(HERO)) { + sprintf(buf, "%ld", (unsigned long) val); + _vm->output(buf); + } +} + +void Execute::saystr(char *str) { + if (isHere(HERO)) + _vm->output(str); + free(str); +} + +void Execute::saylit(Aword lit) { + if (isNum(lit)) + sayint(litValues[lit - LITMIN].value); + else { + Common::String str = (char *)litValues[lit - LITMIN].value; + saystr((char *)str.c_str()); + } +} + +void Execute::sayarticle(Aword id) { + if (!isObj(id)) + error("Trying to say article of something *not* an object."); + if (_objs[id - OBJMIN].art != 0) + _vm->_interpreter->interpret(_objs[id - OBJMIN].art); + else + _vm->printMessage(M_ARTICLE); +} + +void Execute::say(Aword id) { + if (isHere(HERO)) { + if (isObj(id)) + sayobj(id); + else if (isLoc(id)) + sayloc(id); + else if (isAct(id)) + sayact(id); + else if (isLit(id)) + saylit(id); + else + error("Can't SAY item (%ld).", (unsigned long)id); + } +} + +void Execute::dscrloc(Aword loc) { + if (_locs[loc - LOCMIN].dscr != 0) + _vm->_interpreter->interpret(_locs[loc - LOCMIN].dscr); +} + +void Execute::dscrobj(Aword obj) { + _objs[obj - OBJMIN].describe = false; + if (_objs[obj - OBJMIN].dscr1 != 0) + _vm->_interpreter->interpret(_objs[obj - OBJMIN].dscr1); + else { + _vm->printMessage(M_SEEOBJ1); + sayarticle(obj); + say(obj); + _vm->printMessage(M_SEEOBJ4); + if (_objs[obj - OBJMIN].cont != 0) + list(obj); + } +} + + +void Execute::dscract(Aword act) { + ScrElem *scr = NULL; + + if (_acts[act - ACTMIN].script != 0) { + for (scr = (ScrElem *) addrTo(_acts[act - ACTMIN].scradr); !endOfTable(scr); scr++) + if (scr->code == _acts[act - ACTMIN].script) + break; + if (endOfTable(scr)) + scr = NULL; + } + + if (scr != NULL && scr->dscr != 0) + _vm->_interpreter->interpret(scr->dscr); + else if (_acts[act - ACTMIN].dscr != 0) + _vm->_interpreter->interpret(_acts[act - ACTMIN].dscr); + else { + _vm->_interpreter->interpret(_acts[act - ACTMIN].nam); + _vm->printMessage(M_SEEACT); + } + + _acts[act - ACTMIN].describe = false; +} + +void Execute::describe(Aword id) { + for (int i = 0; i < _describeStack.size(); i++) { + _describeStack.push(id); + + if (isObj(id)) + dscrobj(id); + else if (isLoc(id)) + dscrloc(id); + else if (isAct(id)) + dscract(id); + else + error("Can't DESCRIBE item (%ld).", (unsigned long)id); + } + + _describeStack.pop(); +} + +void Execute::use(Aword act, Aword scr) { + if (!isAct(act)) + error("Item is not an Actor (%ld).", (unsigned long) act); + + _acts[act - ACTMIN].script = scr; + _acts[act - ACTMIN].step = 0; +} + +void Execute::list(Aword cnt) { + int i; + Aword props; + Aword prevobj; + bool found = false; + bool multiple = false; + + // Find container properties + if (isObj(cnt)) + props = _objs[cnt-OBJMIN].cont; + else if (isAct(cnt)) + props = _acts[cnt-ACTMIN].cont; + else + props = cnt; + + for (i = OBJMIN; i <= OBJMAX; i++) { + if (in(i, cnt)) { // Yes, it's in this container + if (!found) { + found = true; + if (_cnts[props-CNTMIN].header != 0) + _vm->_interpreter->interpret(_cnts[props-CNTMIN].header); + else { + _vm->printMessage(M_CONTAINS1); + if (_cnts[props-CNTMIN].nam != 0) // It has it's own name + _vm->_interpreter->interpret(_cnts[props-CNTMIN].nam); + else + say(_cnts[props-CNTMIN].parent); // It is actually an object or actor + + _vm->printMessage(M_CONTAINS2); + } + } else { + if (multiple) { + _vm->_needSpace = false; + _vm->printMessage(M_CONTAINS3); + } + + multiple = true; + sayarticle(prevobj); + say(prevobj); + } + + prevobj = i; + } + } + + if (found) { + if (multiple) + _vm->printMessage(M_CONTAINS4); + + sayarticle(prevobj); + say(prevobj); + _vm->printMessage(M_CONTAINS5); + } else { + if (_cnts[props-CNTMIN].empty != 0) + _vm->_interpreter->interpret(_cnts[props-CNTMIN].empty); + else { + _vm->printMessage(M_EMPTY1); + + if (_cnts[props-CNTMIN].nam != 0) // It has it's own name + _vm->_interpreter->interpret(_cnts[props-CNTMIN].nam); + else + say(_cnts[props-CNTMIN].parent); // It is actually an actor or object + + _vm->printMessage(M_EMPTY2); + } + } + + _vm->_needSpace = true; +} + +void Execute::empty(Aword cnt, Aword whr) { + for (int i = OBJMIN; i <= OBJMAX; i++) + if (in(i, cnt)) + locate(i, whr); +} + +// Description of current location +void Execute::dscrobjs() { + int i; + int prevobj; + bool found = false; + bool multiple = false; + + // First describe everything here with its own description + for (i = OBJMIN; i <= OBJMAX; i++) { + if (_objs[i - OBJMIN].loc == _vm->cur.loc && + _objs[i - OBJMIN].describe && + _objs[i - OBJMIN].dscr1) + describe(i); + } + + // Then list everything else here + for (i = OBJMIN; i <= OBJMAX; i++) { + if (_objs[i - OBJMIN].loc == _vm->cur.loc && + _objs[i - OBJMIN].describe) { + if (!found) { + _vm->printMessage(M_SEEOBJ1); + sayarticle(i); + say(i); + found = true; + } + else { + if (multiple) { + _vm->_needSpace = false; + _vm->printMessage(M_SEEOBJ2); + sayarticle(prevobj); + say(prevobj); + } + + multiple = true; + } + + prevobj = i; + } + } + + if (found) { + if (multiple) { + _vm->printMessage(M_SEEOBJ3); + sayarticle(prevobj); + say(prevobj); + } + + _vm->printMessage(M_SEEOBJ4); + } + + // Set describe flag for all objects + for (i = OBJMIN; i <= OBJMAX; i++) + _objs[i-OBJMIN].describe = true; +} + + +void Execute::dscracts() { + for (int i = HERO+1; i <= ACTMAX; i++) + if (_acts[i-ACTMIN].loc == _vm->cur.loc && + _acts[i-ACTMIN].describe) + describe(i); + + // Set describe flag for all actors + for (int i = HERO; i <= ACTMAX; i++) + _acts[i-ACTMIN].describe = true; +} + +void Execute::look() { + int i; + _vm->looking = true; + + // Set describe flag for all objects and actors + for (i = OBJMIN; i <= OBJMAX; i++) + _objs[i-OBJMIN].describe = true; + for (i = ACTMIN; i <= ACTMAX; i++) + _acts[i-ACTMIN].describe = true; + + if (_vm->_anyOutput) + _vm->paragraph(); + + //glk_set_style(style_Subheader); // TODO + _vm->_needSpace = false; + say(_vm->cur.loc); + + _vm->_needSpace = false; + _vm->output("."); + + //glk_set_style(style_Normal); // TODO + _vm->newLine(); + _vm->_needSpace = false; + describe(_vm->cur.loc); + dscrobjs(); + dscracts(); + _vm->looking = false; +} + +Aword Execute::rnd(Aword from, Aword to) { + if (to == from) + return to; + else if (to > from) + return to; // TODO + //return (rand()/10)%(to-from+1)+from; + else + return to; // TODO + //return (rand()/10)%(from-to+1)+to; +} + +// Between +Abool Execute::btw(Aint val, Aint low, Aint high) { + if (high > low) + return low <= val && val <= high; + else + return high <= val && val <= low; +} + +// TODO: Replace this with Common::String functionality +Aword Execute::contains(Aptr string, Aptr substring) { + Common::String str = (char *)string; + char *substr = (char *)substring; + bool result = str.contains(substr); + + free((char *)string); + free((char *)substring); + + return result; +} + +// TODO: Replace this with Common::String functionality +// Compare two strings approximately, ignore case +Abool Execute::streq(char *a, char *b) { + Common::String str1 = a; + Common::String str2 = b; + bool result = str1.equalsIgnoreCase(str2); + + free(a); + free(b); + + return result; +} + +} // End of namespace Alan2 +} // Engine of namespace GLK diff --git a/engines/glk/alan2/execute.h b/engines/glk/alan2/execute.h new file mode 100644 index 0000000000..82d5c070c6 --- /dev/null +++ b/engines/glk/alan2/execute.h @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_EXECUTE +#define GLK_ALAN2_EXECUTE + +#include "glk/alan2/acode.h" +#include "glk/alan2/rules.h" +#include "common/list.h" + +namespace Glk { +namespace Alan2 { + +class Execute { +public: + Execute() {} + + void sys(Aword fpos, Aword len); + bool confirm(MsgKind msgno); + Aptr attribute(Aword item, Aword atr); + void say(Aword item); + void saynum(Aword num); + void saystr(char *str); + Aptr strattr(Aword id, Aword atr); + void setstr(Aword id, Aword atr, Aword str); + void getstr(Aword fpos, Aword len); + void print(Aword fpos, Aword len); + void look(); + void make(Aword id, Aword atr, Aword val); + void set(Aword id, Aword atr, Aword val); + void incr(Aword id, Aword atr, Aword step); + void decr(Aword id, Aword atr, Aword step); + void use(Aword act, Aword scr); + void describe(Aword id); + void list(Aword cnt); + void locate(Aword id, Aword whr); + void empty(Aword cnt, Aword whr); + void score(Aword sc); + void visits(Aword v); + + void eventchk(); + void schedule(Aword evt, Aword whr, Aword aft); + void cancl(Aword evt); + + void quit(); + void restart(); + void sayint(Aword val); + Aword rnd(Aword from, Aword to); + Abool btw(Aint val, Aint from, Aint to); + Aword contains(Aptr string, Aptr substring); + Abool streq(char a[], char b[]); + Abool in(Aword obj, Aword cnt); + Aword where(Aword item); + Aint agrmax(Aword atr, Aword whr); + Aint agrsum(Aword atr, Aword whr); + Aint agrcount(Aword whr); + Abool isHere(Aword item); + Abool isNear(Aword item); + +private: + bool exitto(int to, int from); + int count(int cnt); + int sumatr(Aword atr, Aword cnt); + bool checklim(Aword cnt, Aword obj); + Aptr getatr(Aaddr atradr, Aaddr atr); + void setatr(Aaddr atradr, Aword atr, Aword val); + void makloc(Aword loc, Aword atr, Aword val); + void makobj(Aword obj, Aword atr, Aword val); + void makact(Aword act, Aword atr, Aword val); + void setloc(Aword loc, Aword atr, Aword val); + void setobj(Aword obj, Aword atr, Aword val); + void setact(Aword act, Aword atr, Aword val); + void incratr(Aaddr atradr, Aword atr, Aword step); + void incrloc(Aword loc, Aword atr, Aword step); + void incrobj(Aword obj, Aword atr, Aword step); + void incract(Aword act, Aword atr, Aword step); + Aword objloc(Aword obj); + Aword actloc(Aword act); + void locobj(Aword obj, Aword whr); + void locact(Aword act, Aword whr); + Abool objhere(Aword obj); + Aword acthere(Aword act); + Aword objnear(Aword obj); + Aword actnear(Aword act); + void sayloc(Aword loc); + void sayobj(Aword obj); + void sayact(Aword act); + void saylit(Aword lit); + void sayarticle(Aword id); + void dscrloc(Aword loc); + void dscrobj(Aword obj); + void dscract(Aword act); + void dscrobjs(); + void dscracts(); + Aptr locatr(Aword loc, Aword atr); + Aptr objatr(Aword obj, Aword atr); + Aptr actatr(Aword act, Aword atr); + Aptr litatr(Aword lit, Aword atr); + + // The event queue + EvtqElem *eventq; // Event queue + int etop; // Event queue top pointer + + // Amachine data structures + ActElem *_acts; // Actor table pointer + LocElem *_locs; // Location table pointer + ObjElem *_objs; // Object table pointer + CntElem *_cnts; // Container table pointer + Common::Stack<Aword> _describeStack; +}; + +} // End of namespace Alan2 +} // Engine of namespace GLK + +#endif diff --git a/engines/glk/alan2/interpreter.cpp b/engines/glk/alan2/interpreter.cpp new file mode 100644 index 0000000000..c8c53a0b4a --- /dev/null +++ b/engines/glk/alan2/interpreter.cpp @@ -0,0 +1,768 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/alan2/acode.h" +#include "glk/alan2/alan2.h" +#include "glk/alan2/execute.h" +#include "glk/alan2/interpreter.h" +#include "glk/alan2/saveload.h" +#include "glk/alan2/types.h" +#include "common/debug.h" + +namespace Glk { +namespace Alan2 { + +// TODO: Refactor these into debug flags +const bool trcflg = false; +const bool stpflg = false; + +void Interpreter::if_(Aword v) { + int lev = 1; + Aword i; + + if (!v) { + // Skip to next ELSE or ENDIF on same level + while (true) { + i = _vm->memory[_vm->pc++]; + if (I_CLASS(i) == (Aword)C_STMOP) { + switch (I_OP(i)) { + case I_ELSE: + if (lev == 1) + return; + break; + case I_IF: + lev++; + break; + case I_ENDIF: + lev--; + if (lev == 0) + return; + break; + default: + break; + } + } + } + } +} + +void Interpreter::else_() { + int lev = 1; + Aword i; + + while (true) { + // Skip to ENDIF on the same level + i = _vm->memory[_vm->pc++]; + if (I_CLASS(i) == (Aword)C_STMOP) { + switch (I_OP(i)) { + case I_ENDIF: + lev--; + if (lev == 0) + return; + break; + case I_IF: + lev++; + break; + default: + break; + } + } + } +} + +void Interpreter::depstart() { + // A DEPSTART was executed, so skip across the redundant DEPCASE to + // start at the first expression + _vm->pc++; +} + +void Interpreter::swap() { + Aptr v1 = _stack->pop(); + Aptr v2 = _stack->pop(); + + _stack->push(v1); + _stack->push(v2); +} + +void Interpreter::depexec(Aword v) { + int lev = 1; + Aword i; + + if (!v) { + // The expression was not true, skip to next CASE on the same + // level which could be a DEPCASE or DEPELSE + while (true) { + i = _vm->memory[_vm->pc++]; + if (I_CLASS(i) == (Aword)C_STMOP) { + switch (I_OP(i)) { + case I_DEPSTART: + lev++; + break; + case I_DEPEND: + if (lev == 1) + return; + lev--; + break; + case I_DEPCASE: + case I_DEPELSE: + if (lev == 1) + return; + break; + default: + break; + } + } + } + } +} + +void Interpreter::depcase() { + int lev = 1; + Aword i; + + // Skip to end of DEPENDING block (next DEPEND on same level) because + // we have just executed a DEPCASE/DEPELSE statement as a result of a DEPCASE + // catching + + while (true) { + i = _vm->memory[_vm->pc++]; + if (I_CLASS(i) == (Aword)C_STMOP) { + switch (I_OP(i)) { + case I_DEPSTART: + lev++; + break; + case I_DEPEND: + lev--; + if (lev == 0) return; + break; + default: + break; + } + } + } +} + +void Interpreter::curVar(Aword i) { + switch (I_OP(i)) { + case V_PARAM: + if (stpflg) + debug("PARAM \t%5ld\t\t(%ld)", _stack->top(), _vm->params[_stack->top() - 1].code); + _stack->push(_vm->params[_stack->pop() - 1].code); + break; + case V_CURLOC: + if (stpflg) + debug("CURLOC \t\t\t(%d)", _vm->cur.loc); + _stack->push(_vm->cur.loc); + break; + case V_CURACT: + if (stpflg) + debug("CURACT \t\t\t(%d)", _vm->cur.act); + _stack->push(_vm->cur.act); + break; + case V_CURVRB: + if (stpflg) + debug("CURVRB \t\t\t(%d)", _vm->cur.vrb); + _stack->push(_vm->cur.vrb); + break; + case V_SCORE: + if (stpflg) + debug("CURSCORE \t\t\t(%d)", _vm->cur.score); + _stack->push(_vm->cur.score); + break; + default: + error("Unknown CURVAR instruction."); + } +} + +void Interpreter::adaptOldOpcodes(Aword i) { + switch (I_OP(i)) { + case I_AND: + case I_OR: + case I_NE: + case I_EQ: + case I_STREQ: + case I_STREXACT: + case I_LE: + case I_GE: + case I_LT: + case I_GT: + case I_PLUS: + case I_MINUS: + case I_MULT: + case I_DIV: + if (_vm->header->vers[0] == 2 && _vm->header->vers[1] == 7) // Check for 2.7 version + swap(); + } +} + +void Interpreter::stMop(Aword i, Aaddr oldpc) { + Aptr arg1, arg2, arg3, lh, rh; + + adaptOldOpcodes(i); + + switch (I_OP(i)) { + case I_PRINT: + arg1 = _stack->pop(); // fpos + arg2 = _stack->pop(); // len + if (stpflg) { + debug("PRINT \t%5ld, %5ld\t\"", arg1, arg2); + _vm->col = 34; // To format it better! + } + _execute->print(arg1, arg2); + if (stpflg) + debug("\""); + break; + case I_SYSTEM: + arg1 = _stack->pop(); // fpos + arg2 = _stack->pop(); // len + if (stpflg) { + debug("SYSTEM \t%5ld, %5ld\t\"", arg1, arg2); + _vm->col = 34; // To format it better! + } + _execute->sys(arg1, arg2); + break; + case I_GETSTR: + arg1 = _stack->pop(); // fpos + arg2 = _stack->pop(); // len + if (stpflg) + debug("GETSTR\t%5ld, %5ld", arg1, arg2); + _execute->getstr(arg1, arg2); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_QUIT: + if (stpflg) + debug("QUIT"); + _execute->quit(); + break; + case I_LOOK: + if (stpflg) + debug("LOOK"); + _execute->look(); + break; + case I_SAVE: + if (stpflg) + debug("SAVE"); + _saveLoad->save(); + break; + case I_RESTORE: + if (stpflg) + debug("RESTORE"); + _saveLoad->restore(); + break; + case I_RESTART: + if (stpflg) + debug("RESTART"); + _execute->restart(); + break; + case I_LIST: + arg1 = _stack->pop(); // cnt + if (stpflg) + debug("LIST \t%5ld", arg1); + _execute->list(arg1); + break; + case I_EMPTY: + arg1 = _stack->pop(); // cnt + arg2 = _stack->pop(); // whr + if (stpflg) + debug("EMPTY \t%5ld, %5ld", arg1, arg2); + _execute->empty(arg1, arg2); + break; + case I_SCORE: + arg1 = _stack->pop(); // score + if (stpflg) + debug("SCORE \t%5ld\t\t(%ld)", arg1, _vm->scores[arg1 - 1]); + _execute->score(arg1); + break; + case I_VISITS: + arg1 = _stack->pop(); // visits + if (stpflg) + debug("VISITS \t%5ld", arg1); + _execute->visits(arg1); + break; + case I_SCHEDULE: + arg1 = _stack->pop(); // evt + arg2 = _stack->pop(); // whr + arg3 = _stack->pop(); // aft + if (stpflg) + debug("SCHEDULE \t%5ld, %5ld, %5ld", arg1, arg2, arg3); + _execute->schedule(arg1, arg2, arg3); + break; + case I_CANCEL: + arg1 = _stack->pop(); // evt + if (stpflg) + debug("CANCEL \t%5ld", arg1); + _execute->cancl(arg1); + break; + case I_MAKE: + arg1 = _stack->pop(); // id + arg2 = _stack->pop(); // atr + arg3 = _stack->pop(); // val + if (stpflg) { + debug("MAKE \t%5ld, %5ld, ", arg1, arg2); + if (arg3) + debug("TRUE"); + else + debug("FALSE"); + } + _execute->make(arg1, arg2, arg3); + break; + case I_SET: + arg1 = _stack->pop(); // id + arg2 = _stack->pop(); // atr + arg3 = _stack->pop(); // val + if (stpflg) + debug("SET \t%5ld, %5ld, %5ld", arg1, arg2, arg3); + _execute->set(arg1, arg2, arg3); + break; + case I_STRSET: + arg1 = _stack->pop(); // id + arg2 = _stack->pop(); // atr + arg3 = _stack->pop(); // str + if (stpflg) + debug("STRSET\t%5ld, %5ld, %5ld", arg1, arg2, arg3); + _execute->setstr(arg1, arg2, arg3); + break; + case I_INCR: + arg1 = _stack->pop(); // id + arg2 = _stack->pop(); // atr + arg3 = _stack->pop(); // step + if (stpflg) + debug("INCR\t%5ld, %5ld, %5ld", arg1, arg2, arg3); + _execute->incr(arg1, arg2, arg3); + break; + case I_DECR: + arg1 = _stack->pop(); // id + arg2 = _stack->pop(); // atr + arg3 = _stack->pop(); // step + if (stpflg) + debug("DECR\t%5ld, %5ld, %5ld", arg1, arg2, arg3); + _execute->decr(arg1, arg2, arg3); + break; + case I_ATTRIBUTE: + arg1 = _stack->pop(); // id + arg2 = _stack->pop(); // atr + if (stpflg) + debug("ATTRIBUTE %5ld, %5ld", arg1, arg2); + _stack->push(_execute->attribute(arg1, arg2)); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_STRATTR: + arg1 = _stack->pop(); // id + arg2 = _stack->pop(); // atr + if (stpflg) + debug("STRATTR \t%5ld, %5ld", arg1, arg2); + _stack->push(_execute->strattr(arg1, arg2)); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_LOCATE: + arg1 = _stack->pop(); // id + arg2 = _stack->pop(); // whr + if (stpflg) + debug("LOCATE \t%5ld, %5ld", arg1, arg2); + _execute->locate(arg1, arg2); + break; + case I_WHERE: + arg1 = _stack->pop(); // id + if (stpflg) + debug("WHERE \t%5ld", arg1); + _stack->push(_execute->where(arg1)); + if (stpflg) + debug("\t\t(%ld)", _stack->top()); + break; + case I_HERE: + arg1 = _stack->pop(); // id + if (stpflg) + debug("HERE \t%5ld", arg1); + _stack->push(_execute->isHere(arg1)); + if (stpflg) { + if (_stack->top()) + debug("\t(TRUE)"); + else + debug("\t(FALSE)"); + } + break; + case I_NEAR: + arg1 = _stack->pop(); // id + if (stpflg) + debug("NEAR \t%5ld", arg1); + _stack->push(_execute->isNear(arg1)); + if (stpflg) { + if (_stack->top()) + debug("\t(TRUE)"); + else + debug("\t(FALSE)"); + } + break; + case I_USE: + arg1 = _stack->pop(); // act + arg2 = _stack->pop(); // scr + if (stpflg) + debug("USE \t%5ld, %5ld", arg1, arg2); + _execute->use(arg1, arg2); + break; + case I_IN: + arg1 = _stack->pop(); // obj + arg2 = _stack->pop(); // cnt + if (stpflg) + debug("IN \t%5ld, %5ld ", arg1, arg2); + _stack->push(_execute->in(arg1, arg2)); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + break; + case I_DESCRIBE: + arg1 = _stack->pop(); // id + if (stpflg) { + debug("DESCRIBE \t%5ld\t", arg1); + _vm->col = 34; // To format it better! + } + _execute->describe(arg1); + break; + case I_SAY: + arg1 = _stack->pop(); // id + if (stpflg) + debug("SAY \t%5ld\t\t\"", arg1); + _execute->say(arg1); + if (stpflg) + debug("\""); + break; + case I_SAYINT: + arg1 = _stack->pop(); // val + if (stpflg) + debug("SAYINT\t%5ld\t\t\"", arg1); + _execute->sayint(arg1); + if (stpflg) + debug("\""); + break; + case I_SAYSTR: + arg1 = _stack->pop(); // adr + if (stpflg) + debug("SAYSTR\t%5ld\t\t\"", arg1); + _execute->saystr((char *)arg1); + if (stpflg) + debug("\""); + break; + case I_IF: + arg1 = _stack->pop(); // v + if (stpflg) { + debug("IF \t"); + if (arg1) debug(" TRUE"); else debug("FALSE"); + } + if_(arg1); + break; + case I_ELSE: + if (stpflg) + debug("ELSE"); + else_(); + break; + case I_ENDIF: + if (stpflg) + debug("ENDIF"); + break; + case I_AND: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) { + debug("AND \t"); + if (lh) debug("TRUE, "); else debug("FALSE, "); + if (rh) debug("TRUE"); else debug("FALSE"); + } + _stack->push(lh && rh); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + break; + case I_OR: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) { + debug("OR \t"); + if (lh) debug("TRUE, "); else debug("FALSE, "); + if (rh) debug("TRUE"); else debug("FALSE"); + } + _stack->push(lh || rh); + if (stpflg) { + if (_stack->top()) + debug("\t(TRUE)"); + else + debug("\t(FALSE)"); + } + break; + case I_NE: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("NE \t%5ld, %5ld", lh, rh); + _stack->push(lh != rh); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + break; + case I_EQ: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("EQ \t%5ld, %5ld", lh, rh); + _stack->push(lh == rh); + if (stpflg) { + if (_stack->top()) + debug("\t(TRUE)"); + else + debug("\t(FALSE)"); + } + break; + case I_STREQ: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("STREQ \t%5ld, %5ld", lh, rh); + _stack->push(_execute->streq((char *)lh, (char *)rh)); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + break; + case I_STREXACT: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("STREXACT \t%5ld, %5ld", lh, rh); + _stack->push(strcmp((char *)lh, (char *)rh) == 0); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + free((void *)lh); + free((void *)rh); + break; + case I_LE: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("LE \t%5ld, %5ld", lh, rh); + _stack->push(lh <= rh); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + break; + case I_GE: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("GE \t%5ld, %5ld", lh, rh); + _stack->push(lh >= rh); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + break; + case I_LT: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("LT \t%5ld, %5ld", lh, rh); + _stack->push((signed int)lh < (signed int)rh); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + break; + case I_GT: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("GT \t%5ld, %5ld", lh, rh); + _stack->push(lh > rh); + if (stpflg) + if (_stack->top()) debug("\t(TRUE)"); else debug("\t(FALSE)"); + break; + case I_PLUS: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("PLUS \t%5ld, %5ld", lh, rh); + _stack->push(lh + rh); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_MINUS: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("MINUS \t%5ld, %5ld", lh, rh); + _stack->push(lh - rh); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_MULT: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("MULT \t%5ld, %5ld", lh, rh); + _stack->push(lh * rh); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_DIV: + rh = _stack->pop(); + lh = _stack->pop(); + if (stpflg) + debug("DIV \t%5ld, %5ld", lh, rh); + _stack->push(lh / rh); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_NOT: + arg1 = _stack->pop(); // val + if (stpflg) { + debug("NOT \t"); + if (arg1) debug("TRUE"); else debug("FALSE"); + } + _stack->push(!arg1); + if (stpflg) + if (_stack->top()) debug("\t\t(TRUE)"); else debug("\t\t(FALSE)"); + break; + case I_MAX: + arg1 = _stack->pop(); // atr + arg2 = _stack->pop(); // whr + if (stpflg) + debug("MAX \t%5ld, %5ld", arg1, arg2); + _stack->push(_execute->agrmax(arg1, arg2)); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_SUM: + arg1 = _stack->pop(); // atr + arg2 = _stack->pop(); // whr + if (stpflg) + debug("SUM \t%5ld, %5ld", arg1, arg2); + _stack->push(_execute->agrsum(arg1, arg2)); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_COUNT: + arg1 = _stack->pop(); // whr + if (stpflg) + debug("COUNT \t%5ld", arg1); + _stack->push(_execute->agrcount(arg1)); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_RND: + arg1 = _stack->pop(); // from + arg2 = _stack->pop(); // to + if (stpflg) + debug("RANDOM \t%5ld, %5ld", arg1, arg2); + _stack->push(_execute->rnd(arg1, arg2)); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_BTW: + arg1 = _stack->pop(); // high + arg2 = _stack->pop(); // low + arg3 = _stack->pop(); // val + if (stpflg) + debug("BETWEEN \t%5ld, %5ld, %5ld", arg1, arg2, arg3); + _stack->push(_execute->btw(arg1, arg2, arg3)); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_CONTAINS: + arg1 = _stack->pop(); // substring + arg2 = _stack->pop(); // string + if (stpflg) + debug("CONTAINS \t%5ld, %5ld", arg2, arg1); + _stack->push(_execute->contains(arg2, arg1)); + if (stpflg) + debug("\t(%ld)", _stack->top()); + break; + case I_DEPSTART: + if (stpflg) + debug("DEPSTART"); + depstart(); + break; + case I_DEPCASE: + if (stpflg) + debug("DEPCASE"); + depcase(); + break; + case I_DEPEXEC: + arg1 = _stack->pop(); // v + if (stpflg) { + debug("DEPEXEC \t"); + if (arg1) debug(" TRUE"); else debug("FALSE"); + } + depexec(arg1); + break; + case I_DEPELSE: + if (stpflg) + debug("DEPELSE"); + depcase(); + break; + case I_DEPEND: + if (stpflg) + debug("DEPEND"); + break; + case I_RETURN: + if (stpflg) + debug("RETURN\n--------------------------------------------------\n"); + _vm->pc = oldpc; + return; + default: + error("Unknown STMOP instruction."); + } + + if (_vm->fail) { + _vm->pc = oldpc; + return; // TODO: Return true/false, and stop execution if necessary + } +} + +void Interpreter::interpret(Aaddr adr) { + Aaddr oldpc = _vm->pc; + Aword i; + + if (stpflg) + debug("\n++++++++++++++++++++++++++++++++++++++++++++++++++"); + + _vm->pc = adr; + + while(true) { + if (stpflg) + debug("\n%4x: ", _vm->pc); + + if (_vm->pc > _vm->memTop) + error("Interpreting outside program."); + + i = _vm->memory[_vm->pc++]; + + switch (I_CLASS(i)) { + case C_CONST: + if (stpflg) + debug("PUSH \t%5ld", I_OP(i)); + _stack->push(I_OP(i)); + break; + case C_CURVAR: + curVar(i); + break; + case C_STMOP: + stMop(i, oldpc); + break; + default: + error("Unknown instruction class."); + } + } +} + +} // End of namespace Alan2 +} // Engine of namespace GLK diff --git a/engines/glk/alan2/interpreter.h b/engines/glk/alan2/interpreter.h new file mode 100644 index 0000000000..dbc1558079 --- /dev/null +++ b/engines/glk/alan2/interpreter.h @@ -0,0 +1,59 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_INTERPRETER +#define GLK_ALAN2_INTERPRETER + +#include "glk/alan2/acode.h" +#include "glk/alan2/alan2.h" + +namespace Glk { +namespace Alan2 { + +class Execute; +class SaveLoad; + +class Interpreter { +public: + Interpreter(Execute *execute, SaveLoad *saveLoad, Alan2Stack *stack) : _execute(execute), _saveLoad(saveLoad), _stack(stack) {} + void interpret(Aaddr adr); + +private: + void if_(Aword v); + void else_(); + void depstart(); + void swap(); + void depexec(Aword v); + void depcase(); + void curVar(Aword i); + void stMop(Aword i, Aaddr oldpc); + void adaptOldOpcodes(Aword i); + + Execute *_execute; + SaveLoad *_saveLoad; + Alan2Stack *_stack; +}; + +} // End of namespace Alan2 +} // Engine of namespace GLK + +#endif diff --git a/engines/glk/alan2/parse.cpp b/engines/glk/alan2/parse.cpp new file mode 100644 index 0000000000..ebb0af94bf --- /dev/null +++ b/engines/glk/alan2/parse.cpp @@ -0,0 +1,976 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/stack.h" +#include "glk/alan2/alan2.h" +#include "glk/alan2/execute.h" +#include "glk/alan2/interpreter.h" +#include "glk/alan2/parse.h" +#include "glk/alan2/types.h" +#include "glk/alan2/util.h" +#include "common/debug.h" +#include "common/file.h" +#include "decode.h" + +namespace Glk { +namespace Alan2 { + +// All procedures for getting a command and turning it into a list of +// dictionary entries are placed here. + +Parser::Parser() { + wrds[0] = EOF; + + // TODO +} + +void Parser::unknown(char *inputStr) { + Common::String str = Common::String::format("'%s'?", inputStr); + + // TODO +#if 0 +#if ISO == 0 + fromIso(str, str); +#endif +#endif + + _vm->output(str); + eol = true; + _vm->printError(M_UNKNOWN_WORD); +} + +int Parser::lookup(char *wrd) { +#if 0 + for (int i = 0; !endOfTable(&dict[i]); i++) { + if (strcmp(wrd, (char *) addrTo(dict[i].wrd)) == 0) + return (i); + } +#endif + + unknown(wrd); + return(EOF); +} + +char *Parser::gettoken(char *tokenBuffer) { + static char *marker; + static char oldch; + + if (tokenBuffer == NULL) + *marker = oldch; + else + marker = tokenBuffer; + + while (*marker != '\0' && Common::isSpace(*marker) && *marker != '\n') marker++; + tokenBuffer = marker; + + if (Common::isAlpha(*marker)) + while (*marker && (Common::isAlpha(*marker) || Common::isDigit(*marker) || *marker == '\'')) marker++; + else if (Common::isDigit(*marker)) + while (Common::isDigit(*marker)) marker++; + else if (*marker == '\"') { + marker++; + while (*marker != '\"') marker++; + marker++; + } else if (*marker == '\0' || *marker == '\n') + return NULL; + else + marker++; + + oldch = *marker; + *marker = '\0'; + return tokenBuffer; +} + +void Parser::agetline() { + static char buf[LISTLEN + 1]; // The input buffer + static char isobuf[LISTLEN + 1]; // The input buffer in ISO + + _vm->paragraph(); + + // TODO +#if 0 + + do { +#if defined(HAVE_ANSI) || defined(GLK) + statusline(); +#endif + debug("> "); + +#if 0 + if (logflg) + fprintf(logfil, "> "); +#endif + +#ifdef USE_READLINE + if (!readline(buf)) { + newline(); + quit(); + } +#else + if (fgets(buf, LISTLEN, stdin) == NULL) { + newline(); + quit(); + } +#endif + + getPageSize(); + anyOutput = FALSE; + + if (logflg) + fprintf(logfil, "%s\n", buf); + +#if ISO == 0 + toIso(isobuf, buf, NATIVECHARSET); +#else + strcpy(isobuf, buf); +#endif + + token = gettoken(isobuf); + + if (token != NULL && strcmp("debug", token) == 0 && _vm->header->debug) { + dbgflg = true; + debug(); + token = NULL; + } + } while (token == NULL); + + eol = false; + lin = 1; + +#endif +} + +void Parser::scan() { + int i; + int w; + char *str; + + agetline(); + wrds[0] = 0; + + for (i = 0; i < litCount; i++) + if (litValues[i].type == TYPSTR && litValues[i].value != 0) + free((char *) litValues[i].value); + + litCount = 0; + + do { + if (Common::isAlpha(token[0])) { + Common::String tmp = token; + tmp.toLowercase(); + strcpy(token, tmp.c_str()); + + w = lookup(token); + + // TODO + //if (!isNoise(w)) + // wrds[i++] = w; + } else if (Common::isDigit(token[0])) { + if (litCount > MAXPARAMS) + error("Too many parameters."); + + wrds[i++] = dictsize + litCount; // Word outside dictionary = literal + litValues[litCount].type = TYPNUM; + litValues[litCount++].value = atoi(token); + } else if (token[0] == '\"') { + if (litCount > MAXPARAMS) + error("Too many parameters."); + + wrds[i++] = dictsize + litCount; // Word outside dictionary = literal + litValues[litCount].type = TYPSTR; + + // Remove the string quotes while copying + Common::String tmp = token; + tmp.deleteChar(0); + tmp.deleteLastChar(); + strcpy(str, tmp.c_str()); + + litValues[litCount++].value = (Aptr) str; + } else if (token[0] == ',') { + //wrds[i++] = conjWord; // TODO + } else + unknown(token); + + wrds[i] = EOF; + eol = (token = gettoken(NULL)) == NULL; + } while (!eol); +} + +// Search for a non-verb command +void Parser::nonverb() { + if (isDir(wrds[wrdidx])) { + wrdidx++; + if (wrds[wrdidx] != EOF && !isConj(wrds[wrdidx])) + _vm->printError(M_WHAT); +// TODO +#if 0 + else + go(dict[wrds[wrdidx-1]].code); +#endif + + if (wrds[wrdidx] != EOF) + wrdidx++; + } else + _vm->printError(M_WHAT); +} + +Abool Parser::objhere(Aword obj) { + if (isCnt(objs[obj - OBJMIN].loc)) { // In something? + if (isObj(objs[obj - OBJMIN].loc) || isAct(objs[obj - OBJMIN].loc)) + return(isHere(objs[obj - OBJMIN].loc)); +// TODO +#if 0 + else // If the container wasn't anywhere, assume where HERO is! + return(where(HERO) == _vm->cur.loc); +#endif + + } else + return(objs[obj - OBJMIN].loc == _vm->cur.loc); +} + + +Aword Parser::acthere(Aword act) { + return(acts[act - ACTMIN].loc == _vm->cur.loc); +} + +Abool Parser::isHere(Aword id) { + if (isObj(id)) + return objhere(id); + else if (isAct(id)) + return acthere(id); + else + error("Can't HERE item (%ld).", (unsigned long)id); +} + +// ---------------------------------------------------------------------------- + +// Build a list of objects matching 'all' +void Parser::buildall(ParamElem list[]) { + int o, i = 0; + bool found = false; + + for (o = OBJMIN; o <= OBJMAX; o++) { + if (isHere(o)) { + found = true; + list[i].code = o; + list[i++].firstWord = EOF; + } + } + + if (!found) + _vm->printError(M_WHAT_ALL); + else + list[i].code = EOF; +} + +void Parser::listCopy(ParamElem a[], ParamElem b[]) { + int i; + + for (i = 0; b[i].code != EOF; i++) + a[i] = b[i]; + + a[i].code = EOF; +} + +bool Parser::listContains(ParamElem l[], Aword e) { + int i; + + for (i = 0; l[i].code != EOF && l[i].code != e; i++); + + return (l[i].code == e); +} + +void Parser::listIntersection(ParamElem a[], ParamElem b[]) { + int i, last = 0; + + for (i = 0; a[i].code != EOF; i++) + if (listContains(b, a[i].code)) + a[last++] = a[i]; + + a[last].code = EOF; +} + +// Copy the refs (in dictionary) to a paramList +void Parser::listCopyFromDictionary(ParamElem p[], Aword r[]) { + int i; + + for (i = 0; r[i] != EOF; i++) { + p[i].code = r[i]; + p[i].firstWord = EOF; + } + + p[i].code = EOF; +} + +int Parser::listLength(ParamElem a[]) { + int i = 0; + + while (a[i].code != EOF) + i++; + + return (i); +} + +// Compact a list, i.e remove any NULL elements +void Parser::listCompact(ParamElem a[]) { + int i, j; + + for (i = 0, j = 0; a[j].code != EOF; j++) + if (a[j].code != 0) + a[i++] = a[j]; + + a[i].code = EOF; +} + +// Merge the paramElems of one list into the first +void Parser::listMerge(ParamElem a[], ParamElem b[]) { + int i, last; + + for (last = 0; a[last].code != EOF; last++); // Find end of list + + for (i = 0; b[i].code != EOF; i++) { + if (!listContains(a, b[i].code)) { + a[last++] = b[i]; + a[last].code = EOF; + } + } +} + +// Subtract two lists +void Parser::listSubtract(ParamElem a[], ParamElem b[]) { + for (int i = 0; a[i].code != EOF; i++) + if (listContains(b, a[i].code)) + a[i].code = 0; // Mark empty + + listCompact(a); +} + +// Match an unambigous object reference +void Parser::unambig(ParamElem plst[]) { + int i; + bool found = false; // Adjective or noun found? + static ParamElem *refs; // Entities referenced by word + static ParamElem *savlst; // Saved list for backup at EOF + int firstWord, lastWord; // The words the player used + + if (refs == NULL) + refs = new ParamElem[MAXENTITY + 1]; + + if (savlst == NULL) + savlst = new ParamElem[MAXENTITY + 1]; + + if (isLiteral(wrds[wrdidx])) { + // Transform the word into a reference to the literal value + plst[0].code = wrds[wrdidx++]-dictsize+LITMIN; + plst[0].firstWord = EOF; // No words used! + plst[1].code = EOF; + return; + } + + plst[0].code = EOF; // Make empty + + if (isIt(wrds[wrdidx])) { + wrdidx++; + // Use last object in previous command! + for (i = listLength(pparams)-1; i >= 0 && (pparams[i].code == 0 || pparams[i].code >= LITMIN); i--); + + if (i < 0) + _vm->printError(M_WHAT_IT); + + if (!isHere(pparams[i].code)) { + params[0].code = pparams[i].code; + params[0].firstWord = EOF; + params[1].code = EOF; + _vm->printError(M_NO_SUCH); + } + + plst[0] = pparams[i]; + plst[0].firstWord = EOF; // No words used! + plst[1].code = EOF; + return; + } + + firstWord = wrdidx; + + while (wrds[wrdidx] != EOF && isAdj(wrds[wrdidx])) { + // If this word can be a noun and there is no noun following break loop + if (isNoun(wrds[wrdidx]) && (wrds[wrdidx+1] == EOF || !isNoun(wrds[wrdidx+1]))) + break; + + listCopyFromDictionary(refs, (Aword *)addrTo(dict[wrds[wrdidx]].adjrefs)); + listCopy(savlst, plst); // To save it for backtracking + + if (found) + listIntersection(plst, refs); + else { + listCopy(plst, refs); + found = true; + } + + wrdidx++; + } + + if (wrds[wrdidx] != EOF) { + if (isNoun(wrds[wrdidx])) { + listCopyFromDictionary(refs, (Aword *)addrTo(dict[wrds[wrdidx]].nounrefs)); + if (found) + listIntersection(plst, refs); + else { + listCopy(plst, refs); + found = true; + } + + wrdidx++; + } else + _vm->printError(M_NOUN); + } else if (found) { + if (isNoun(wrds[wrdidx-1])) { + // Perhaps the last word was also a noun? + listCopy(plst, savlst); // Restore to before last adjective + listCopyFromDictionary(refs, (Aword *)addrTo(dict[wrds[wrdidx-1]].nounrefs)); + if (plst[0].code == EOF) + listCopy(plst, refs); + else + listIntersection(plst, refs); + } else + _vm->printError(M_NOUN); + } + + lastWord = wrdidx - 1; + + // Allow remote objects, but resolve ambiguities by presence + if (listLength(plst) > 1) { + for (i=0; plst[i].code != EOF; i++) + if (!isHere(plst[i].code)) + plst[i].code = 0; + + listCompact(plst); + } + + if (listLength(plst) > 1 || (found && listLength(plst) == 0)) { + params[0].code = 0; /* Just make it anything != EOF */ + params[0].firstWord = firstWord; /* Remember words for errors below */ + params[0].lastWord = lastWord; + params[1].code = EOF; /* But be sure to terminate */ + + if (listLength(plst) > 1) + _vm->printError(M_WHICH_ONE); + else if (found && listLength(plst) == 0) + _vm->printError(M_NO_SUCH); + } else { + plst[0].firstWord = firstWord; + plst[0].lastWord = lastWord; + } +} + +// Match a simple verb command +void Parser::simple(ParamElem olst[]) { + static ParamElem *tlst = NULL; + int savidx = wrdidx; + bool savplur = false; + int i; + + if (tlst == NULL) + tlst = new ParamElem[MAXENTITY + 1]; + + tlst[0].code = EOF; + + for (;;) { + if (isThem(wrds[wrdidx])) { + plural = true; + + for (i = 0; pmlst[i].code != EOF; i++) + if (!isHere(pmlst[i].code)) + pmlst[i].code = 0; + + listCompact(pmlst); + + if (listLength(pmlst) == 0) + _vm->printError(M_WHAT_THEM); + + listCopy(olst, pmlst); + olst[0].firstWord = EOF; // No words used + wrdidx++; + } else { + unambig(olst); // Look for unambigous noun phrase + + if (listLength(olst) == 0) { // Failed! + listCopy(olst, tlst); + wrdidx = savidx; + plural = savplur; + return; + } + } + + listMerge(tlst, olst); + + if (wrds[wrdidx] != EOF + && (isConj(wrds[wrdidx]) + && (isAdj(wrds[wrdidx+1]) || isNoun(wrds[wrdidx+1])))) { + // More parameters in a conjunction separated list ? + savplur = plural; + savidx = wrdidx; + wrdidx++; + plural = true; + } else { + listCopy(olst, tlst); + return; + } + } +} + +// Match a complex verb command +void Parser::complex(ParamElem olst[]) { + // Above this procedure we can use the is* tests, but not below since + // they work on words.Below all is converted to indices into the + // entity tables.Particularly this goes for literals... + + static ParamElem *alst = NULL; + + if (alst == NULL) + alst = new ParamElem[MAXENTITY + 1]; + + if (isAll(wrds[wrdidx])) { + plural = true; + buildall(alst); // Build list of all objects + wrdidx++; + if (wrds[wrdidx] != EOF && isBut(wrds[wrdidx])) { + wrdidx++; + simple(olst); + + if (listLength(olst) == 0) + _vm->printError(M_AFTER_BUT); + + listSubtract(alst, olst); + if (listLength(alst) == 0) + _vm->printError(M_NOT_MUCH); + } + + listCopy(olst, alst); + allLength = listLength(olst); + } else + simple(olst); // Look for simple noun group +} + +bool Parser::claCheck(ClaElem *cla) { + bool ok = false; + + if ((cla->classes&(Aword)CLA_OBJ) != 0) + ok = ok || isObj(params[cla->code-1].code); + if ((cla->classes&(Aword)CLA_CNT) != 0) + ok = ok || isCnt(params[cla->code-1].code); + if ((cla->classes&(Aword)CLA_ACT) != 0) + ok = ok || isAct(params[cla->code-1].code); + if ((cla->classes&(Aword)CLA_NUM) != 0) + ok = ok || isNum(params[cla->code-1].code); + if ((cla->classes&(Aword)CLA_STR) != 0) + ok = ok || isStr(params[cla->code-1].code); + if ((cla->classes&(Aword)CLA_COBJ) != 0) + ok = ok || (isCnt(params[cla->code-1].code) && isObj(params[cla->code-1].code)); + if ((cla->classes&(Aword)CLA_CACT) != 0) + ok = ok || (isCnt(params[cla->code-1].code) && isAct(params[cla->code-1].code)); + + return ok; +} + +// In case the syntax did not indicate omnipotent powers (allowed +// access to remote object), we need to remove non - present parameters +void Parser::resolve(ParamElem plst[]) { + if (allLength > 0) + return; // ALL has already done this + + // Resolve ambiguities by presence + for (int i = 0; plst[i].code != EOF; i++) { + if (plst[i].code < LITMIN) // Literals are always 'here' + if (!isHere(plst[i].code)) { + params[0] = plst[i]; // Copy error param as first one for message + params[1].code = EOF; // But be sure to terminate + _vm->printError(M_NO_SUCH); + } + } +} + +bool Parser::endOfTable(StxElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +bool Parser::endOfTable(ElmElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +bool Parser::endOfTable(ClaElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +bool Parser::endOfTable(VrbElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +bool Parser::endOfTable(AltElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +bool Parser::endOfTable(ChkElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +/** + * Find the verb alternative wanted in a verb list and return + * the address to it. + * + * @param vrbsadr Address to start of list + * @param param Which parameter to match + */ +AltElem *Parser::findalt(Aword vrbsadr, Aword param) { + VrbElem *vrb; + AltElem *alt; + + if (vrbsadr == 0) + return NULL; + + for (vrb = (VrbElem *)addrTo(vrbsadr); !endOfTable(vrb); vrb++) { + if (vrb->code == _vm->cur.vrb) { + for (alt = (AltElem *)addrTo(vrb->alts); !endOfTable(alt); alt++) + if (alt->param == param || alt->param == 0) + return alt; + return NULL; + } + } + + return NULL; +} + +/** + * Tries a check, returns TRUE if it passed, FALSE otherwise + * + * @param adr ACODE address to check table + * @param act Act if it fails? + */ +bool Parser::trycheck(Aaddr adr, bool act) { + ChkElem *chk = (ChkElem *)addrTo(adr); + + if (chk->exp == 0) { + _vm->_interpreter->interpret(chk->stms); + return false; + } else { + while (!endOfTable(chk)) { + _vm->_interpreter->interpret(chk->exp); + if (!(Abool)_vm->_stack->pop()) { + if (act) + _vm->_interpreter->interpret(chk->stms); + return false; + } + chk++; + } + return true; + } +} + +// Check if current action is possible according to the CHECKs. +bool Parser::possible() { + AltElem *alt[MAXPARAMS + 2]; // List of alt-pointers, one for each param + int i; // Parameter index + + _vm->fail = false; + alt[0] = findalt(_vm->header->vrbs, 0); + + // Perform global checks + if (alt[0] != 0 && alt[0]->checks != 0) { + if (!trycheck(alt[0]->checks, false)) + return false; + if (_vm->fail) + return false; + } + + // Now CHECKs in this location + alt[1] = findalt(locs[_vm->cur.loc - LOCMIN].vrbs, 0); + if (alt[1] != 0 && alt[1]->checks != 0) + if (!trycheck(alt[1]->checks, false)) + return false; + + for (i = 0; params[i].code != EOF; i++) { + alt[i + 2] = findalt(objs[params[i].code - OBJMIN].vrbs, i + 1); + // CHECKs in a possible parameter + if (alt[i + 2] != 0 && alt[i + 2]->checks != 0) + if (!trycheck(alt[i + 2]->checks, false)) + return false; + } + + for (i = 0; i < 2 || params[i - 2].code != EOF; i++) + if (alt[i] != 0 && alt[i]->action != 0) + break; + if (i >= 2 && params[i - 2].code == EOF) + // Didn't find any code for this verb/object combination + return false; + else + return true; +} + +void Parser::tryMatch(ParamElem mlst[]) { + ElmElem *elms; // Pointer to element list + StxElem *stx; // Pointer to syntax list + ClaElem *cla; // Pointer to class definitions + bool anyPlural = false; // Any parameter that was plural? + int i, p; + static ParamElem *tlst = NULL; // List of params found by complex() + static bool *checked = NULL; // Corresponding parameter checked? + + if (tlst == NULL) { + tlst = new ParamElem[MAXENTITY + 1]; + checked = new bool[MAXENTITY + 1]; + } + + for (stx = stxs; !endOfTable(stx); stx++) + if (stx->code == vrbcode) + break; + + if (endOfTable(stx)) + _vm->printError(M_WHAT); + + elms = (ElmElem *) addrTo(stx->elms); + + while (true) { + // End of input? + if (wrds[wrdidx] == EOF || isConj(wrds[wrdidx])) { + while (!endOfTable(elms) && elms->code != EOS) + elms++; + + if (endOfTable(elms)) + _vm->printError(M_WHAT); + else + break; + } else { + // A preposition? + if (isPrep(wrds[wrdidx])) { + while (!endOfTable(elms) && elms->code != dict[wrds[wrdidx]].code) + elms++; + + if (endOfTable(elms)) + _vm->printError(M_WHAT); + else + wrdidx++; + } else { + // Must be a parameter! + while (!endOfTable(elms) && elms->code != 0) + elms++; + + if (endOfTable(elms)) + _vm->printError(M_WHAT); + + // Get it! + plural = false; + complex(tlst); + + if (listLength(tlst) == 0) // No object!? + _vm->printError(M_WHAT); + + if ((elms->flags & OMNIBIT) == 0) // Omnipotent parameter? + resolve(tlst); // If its not an omnipotent parameter, resolve by presence + + if (plural) { + if ((elms->flags & MULTIPLEBIT) == 0) // Allowed multiple? + _vm->printError(M_MULTIPLE); + else { + // Mark this as the multiple position in which to insert + // actual parameter values later + params[paramidx++].code = 0; + listCopy(mlst, tlst); + anyPlural = true; + } + } else + params[paramidx++] = tlst[0]; + params[paramidx].code = EOF; + } + + elms = (ElmElem *) addrTo(elms->next); + } + } + + // Now perform class checks + if (elms->next == 0) // No verb code, verb not declared! + _vm->printError(M_CANT0); + + for (p = 0; params[p].code != EOF; p++) /* Mark all parameters unchecked */ + checked[p] = false; + + for (cla = (ClaElem *) addrTo(elms->next); !endOfTable(cla); cla++) { + if (params[cla->code - 1].code == 0) { + // This was a multiple parameter, so check all and remove failing + for (i = 0; mlst[i].code != EOF; i++) { + params[cla->code-1] = mlst[i]; + if (!claCheck(cla)) { + // Multiple could be both an explicit list of params and an ALL + if (allLength == 0) { + char marker[80]; + // It wasn't ALL, we need to say something about it, so + // prepare a printout with $1/2/3 + sprintf(marker, "($%ld)", (unsigned long) cla->code); + _vm->output(marker); + _vm->_interpreter->interpret(cla->stms); + _vm->paragraph(); + } + + mlst[i].code = 0; // In any case remove it from the list + } + } + + params[cla->code - 1].code = 0; + } else { + if (!claCheck(cla)) { + _vm->_interpreter->interpret(cla->stms); + _vm->printError(MSGMAX); // Return to player without saying anything + } + } + + checked[cla->code - 1] = true; // Remember that it's already checked + } + + // Now check the rest of the parameters, must be objects + for (p = 0; params[p].code != EOF; p++) { + if (!checked[p]) { + if (params[p].code == 0) { + // This was a multiple parameter, check all and remove failing + for (i = 0; mlst[i].code != EOF; i++) { + if (mlst[i].code != 0 && !isObj(mlst[i].code)) // Skip any empty slots + mlst[i].code = 0; + } + } else if (!isObj(params[p].code)) + _vm->printError(M_CANT0); + } + } + + // Set verb code + _vm->cur.vrb = ((Aword *) cla)[1]; // Take first word after end of table! + + // Finally, if ALL was used, try to find out what was applicable + if (allLength > 0) { + for (p = 0; params[p].code != 0; p++); // Find multiple marker + + for (i = 0; i < allLength; i++) { + if (mlst[i].code != 0) { // Already empty? + params[p] = mlst[i]; + + if (!possible()) + mlst[i].code = 0; // Remove this from list + } + } + + params[p].code = 0; // Restore multiple marker + listCompact(mlst); + + if (listLength(mlst) == 0) { + params[0].code = EOF; + _vm->printError(M_WHAT_ALL); + } + } else if (anyPlural) { + listCompact(mlst); + + // If there where multiple parameters but non left, exit without a + // word, assuming we have already said enough + if (listLength(mlst) == 0) + _vm->printError(MSGMAX); + } + + plural = anyPlural; // Remember that we found plural objects +} + +// Find the verb class (not used currently) and 'tryMatch()' +void Parser::match(ParamElem *mlst) { + tryMatch(mlst); // try to understand what the user said + + if (wrds[wrdidx] != EOF && !isConj(wrds[wrdidx])) + _vm->printError(M_WHAT); + if (wrds[wrdidx] != EOF) // More on this line? + wrdidx++; // If so skip the AND +} + +// Execute all activities commanded.Handles possible multiple actions +// such as THEM or lists of objects. +void Parser::action(ParamElem plst[]) { + int i, mpos; + char marker[10]; + + if (plural) { + // The code == 0 means this is a multiple position. We must loop + // over this position (and replace it by each present in the plst) + for (mpos = 0; params[mpos].code != 0; mpos++); // Find multiple position + + sprintf(marker, "($%d)", mpos + 1); // Prepare a printout with $1/2/3 + + for (i = 0; plst[i].code != EOF; i++) { + params[mpos] = plst[i]; + _vm->output(marker); + //do_it(); // TODO + + if (plst[i + 1].code != EOF) + _vm->paragraph(); + } + + params[mpos].code = 0; + } //else // TODO + //do_it(); +} + +void Parser::parse() { + if (mlst == NULL) { // Allocate large enough paramlists + mlst = new ParamElem[MAXENTITY + 1]; + mlst[0].code = EOF; + pmlst = new ParamElem[MAXENTITY + 1]; + params = new ParamElem[MAXENTITY + 1]; + params[0].code = EOF; + pparams = new ParamElem[MAXENTITY + 1]; + } + + if (wrds[wrdidx] == EOF) { + wrdidx = 0; + scan(); + } else if (false/*anyOutput*/) // TODO + _vm->paragraph(); + + allLength = 0; + paramidx = 0; + listCopy(pparams, params); + params[0].code = EOF; + listCopy(pmlst, mlst); + mlst[0].code = EOF; + + if (isVerb(wrds[wrdidx])) { + vrbwrd = wrds[wrdidx]; + vrbcode = dict[vrbwrd].code; + wrdidx++; + match(mlst); + action(mlst); // mlst contains possible multiple params + } else { + params[0].code = EOF; + pmlst[0].code = EOF; + nonverb(); + } +} + +} // End of namespace Alan2 +} // Engine of namespace GLK diff --git a/engines/glk/alan2/parse.h b/engines/glk/alan2/parse.h new file mode 100644 index 0000000000..4642e74ee0 --- /dev/null +++ b/engines/glk/alan2/parse.h @@ -0,0 +1,112 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_PARSE +#define GLK_ALAN2_PARSE + +namespace Glk { +namespace Alan2 { + +#define LISTLEN 100 + +class Parser { +public: + Parser(); + + /** + * Parse a new player command + */ + void parse(); + +private: + void unknown(char *inputStr); + int lookup(char *wrd); + char *gettoken(char *tokenBuffer); + void agetline(); + void scan(); + void nonverb(); + Abool objhere(Aword obj); + Aword acthere(Aword act); + Abool isHere(Aword id); + void buildall(ParamElem list[]); + void listCopy(ParamElem a[], ParamElem b[]); + bool listContains(ParamElem l[], Aword e); + void listIntersection(ParamElem a[], ParamElem b[]); + void listCopyFromDictionary(ParamElem p[], Aword r[]); + int listLength(ParamElem a[]); + void listCompact(ParamElem a[]); + void listMerge(ParamElem a[], ParamElem b[]); + void listSubtract(ParamElem a[], ParamElem b[]); + void unambig(ParamElem plst[]); + void simple(ParamElem olst[]); + void complex(ParamElem olst[]); + bool claCheck(ClaElem *cla); + void resolve(ParamElem plst[]); + bool endOfTable(StxElem *addr); + bool endOfTable(ElmElem *addr); + bool endOfTable(ClaElem *addr); + bool endOfTable(VrbElem *addr); + bool endOfTable(AltElem *addr); + bool endOfTable(ChkElem *addr); + AltElem *findalt(Aword vrbsadr, Aword param); + bool trycheck(Aaddr adr, bool act); + bool possible(); + void tryMatch(ParamElem mlst[]); + void match(ParamElem *mlst); + void action(ParamElem plst[]); + + // TODO: Initialize / move these + int wrds[LISTLEN / 2]; // List of parsed words + int wrdidx; // and an index into it + + bool plural = false; + + // Syntax Parameters + int paramidx; // Index in params + ParamElem *params; // List of params + static ParamElem *pparams; // Previous parameter list + static ParamElem *mlst; // Multiple objects list + static ParamElem *pmlst; // Previous multiple list + + StxElem *stxs; // Syntax table pointer + LocElem *locs; // Location table pointer + + // Literals + LitElem litValues[MAXPARAMS + 1]; + + // What did the user say? + int vrbwrd; // The word he used + int vrbcode; // The code for that verb + + bool eol; // Looking at End of line? Yes, initially + char *token; + + WrdElem *dict; // Dictionary pointer + int dictsize; + + int allLength; // No. of objects matching 'all' +}; + +} // End of namespace Alan2 +} // Engine of namespace GLK + +#endif diff --git a/engines/glk/alan2/rules.cpp b/engines/glk/alan2/rules.cpp new file mode 100644 index 0000000000..14c867133f --- /dev/null +++ b/engines/glk/alan2/rules.cpp @@ -0,0 +1,87 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/alan2/acode.h" +#include "glk/alan2/interpreter.h" +#include "glk/alan2/rules.h" +#include "glk/alan2/types.h" +#include "common/debug.h" + +namespace Glk { +namespace Alan2 { + +// TODO: Refactor these into debug flags +const bool trcflg = false; +const bool stpflg = false; + +void Rules::parseRules() { + bool change = true; + int i; + + for (i = 1; !endOfTable(&_ruls[i - 1]); i++) + _ruls[i - 1].run = false; + + while (change) { + change = false; + for (i = 1; !endOfTable(&_ruls[i - 1]); i++) { + if (!_ruls[i - 1].run) { + if (trcflg) { + debug("\n<RULE %d (at ", i); + //debugsay(cur.loc); // TODO + if (!stpflg) + debug("), Evaluating"); + else + debug("), Evaluating:>\n"); + } + + _interpreter->interpret(_ruls[i - 1].exp); + + if (_stack->pop()) { + change = true; + _ruls[i - 1].run = true; + + if (trcflg) + if (!stpflg) + debug(", Executing:>\n"); + else { + debug("\nRULE %d (at ", i); + //debugsay(cur.loc); // TODO + debug("), Executing:>\n"); + } + + _interpreter->interpret(_ruls[i - 1].stms); + + } else if (trcflg && !stpflg) { + debug(":>\n"); + } + } + } + } +} + +bool Rules::endOfTable(RulElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +} // End of namespace Alan2 +} // Engine of namespace GLK diff --git a/engines/glk/alan2/rules.h b/engines/glk/alan2/rules.h new file mode 100644 index 0000000000..9d5e8bf119 --- /dev/null +++ b/engines/glk/alan2/rules.h @@ -0,0 +1,51 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_RULES +#define GLK_ALAN2_RULES + +#include "common/stack.h" +#include "glk/alan2/alan2.h" +#include "glk/alan2/types.h" + +namespace Glk { +namespace Alan2 { + +class Interpreter; + +class Rules { +public: + Rules(RulElem *rules, Alan2Stack *stack, Interpreter *interpreter) : _ruls(rules), _stack(stack), _interpreter(interpreter) {} + void parseRules(); + +private: + bool endOfTable(RulElem *addr); + + RulElem *_ruls; + Alan2Stack *_stack; + Interpreter *_interpreter; +}; + +} // End of namespace Alan2 +} // Engine of namespace GLK + +#endif diff --git a/engines/glk/alan2/saveload.cpp b/engines/glk/alan2/saveload.cpp new file mode 100644 index 0000000000..c68c54d481 --- /dev/null +++ b/engines/glk/alan2/saveload.cpp @@ -0,0 +1,218 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/savefile.h" +#include "glk/alan2/acode.h" +#include "glk/alan2/alan2.h" +#include "glk/alan2/saveload.h" +#include "glk/alan2/types.h" + +namespace Glk { +namespace Alan2 { + +void SaveLoad::save() { + int i; + Common::SaveFileManager *saveFileMan = _vm->getSaveFileManager(); + Common::OutSaveFile *saveFile = nullptr; + Common::String str; + AtrElem *atr; + + // TODO +#if 0 + frefid_t fref = glk_fileref_create_by_prompt(fileusage_SavedGame, filemode_Write, 0); + if (fref == NULL) + _vm->printError(M_SAVEFAILED); + + strcpy(str, garglk_fileref_get_name(fref)); + glk_fileref_destroy(fref); +#endif + + if (str.empty()) + str = _prevSaveName; + + _vm->col = 1; + + // TODO +#if 0 + if ((savfil = fopen(str, READ_MODE)) != NULL) + // It already existed + if (!confirm(M_SAVEOVERWRITE)) + _vm->printError(MSGMAX); // Return to player without saying anything + + if ((savfil = fopen(str, WRITE_MODE)) == NULL) + _vm->printError(M_SAVEFAILED); +#endif + + saveFile = saveFileMan->openForSaving(str); + + _prevSaveName = str; + + // Save version of interpreter and name of game + saveFile->write(_vm->header->vers, sizeof(Aword)); + saveFile->writeString(_gameName); + saveFile->writeByte('\0'); + // Save current values + saveFile->write(&_vm->cur, sizeof(_vm->cur)); + + // Save actors + for (i = ACTMIN; i <= ACTMAX; i++) { + saveFile->writeUint32LE(_actors[i - ACTMIN].loc); + saveFile->writeUint32LE(_actors[i - ACTMIN].script); + saveFile->writeUint32LE(_actors[i - ACTMIN].step); + saveFile->writeUint32LE(_actors[i - ACTMIN].count); + if (_actors[i - ACTMIN].atrs) + for (atr = (AtrElem *)addrTo(_actors[i - ACTMIN].atrs); !endOfTable(atr); atr++) + saveFile->writeUint32LE(atr->val); + } + + // Save locations + for (i = LOCMIN; i <= LOCMAX; i++) { + saveFile->writeUint32LE(_locations[i - LOCMIN].describe); + if (_locations[i - LOCMIN].atrs) + for (atr = (AtrElem *)addrTo(_locations[i - LOCMIN].atrs); !endOfTable(atr); atr++) + saveFile->writeUint32LE(atr->val); + } + + // Save objects + for (i = OBJMIN; i <= OBJMAX; i++) { + saveFile->writeUint32LE(_objects[i - OBJMIN].loc); + if (_objects[i - OBJMIN].atrs) + for (atr = (AtrElem *)addrTo(_objects[i - OBJMIN].atrs); !endOfTable(atr); atr++) + saveFile->writeUint32LE(atr->val); + } + + // Save the event queue + _events[*_eventTop].time = 0; // Mark the top + for (i = 0; i < *_eventTop + 1; i++) + saveFile->write(&_events[i], sizeof(_events[0])); + + // Save scores + for (i = 0; _vm->scores[i] != EOF; i++) + saveFile->writeUint32LE(_vm->scores[i]); + + delete saveFile; +} + +void SaveLoad::restore() { + int i, tmp; + Common::SaveFileManager *saveFileMan = _vm->getSaveFileManager(); + Common::InSaveFile *saveFile = nullptr; + Common::String str; + AtrElem *atr; + char savedVersion[4]; + char savedName[256]; + + // TODO +#if 0 + frefid_t fref = glk_fileref_create_by_prompt(fileusage_SavedGame, filemode_Read, 0); + if (fref == NULL) + _vm->printError(M_SAVEFAILED); + + strcpy(str, garglk_fileref_get_name(fref)); + glk_fileref_destroy(fref); +#endif + + _vm->col = 1; + + if (str.empty()) + str = _prevSaveName; // Use the name temporarily + + if ((saveFile = saveFileMan->openForLoading(str)) == nullptr) { + _vm->printError(M_SAVEMISSING); + return; + } + + _prevSaveName = str; // Save it for future use + + saveFile->read(savedVersion, sizeof(Aword)); + + // 4f - save file version check doesn't seem to work on PC's! + if (strncmp(savedVersion, _vm->header->vers, 4)) { + delete saveFile; + _vm->printError(M_SAVEVERS); + return; + } + + i = 0; + + while ((savedName[i++] = saveFile->readByte()) != '\0'); + + if (savedName != _gameName) { + delete saveFile; + _vm->printError(M_SAVENAME); + return; + } + + // Restore current values + saveFile->read(&_vm->cur, sizeof(_vm->cur)); + + // Restore actors + for (i = ACTMIN; i <= ACTMAX; i++) { + _actors[i - ACTMIN].loc = saveFile->readUint32LE(); + _actors[i - ACTMIN].script = saveFile->readUint32LE(); + _actors[i - ACTMIN].step = saveFile->readUint32LE(); + _actors[i - ACTMIN].count = saveFile->readUint32LE(); + + if (_actors[i - ACTMIN].atrs) + for (atr = (AtrElem *)addrTo(_actors[i - ACTMIN].atrs); !endOfTable(atr); atr++) + atr->val = saveFile->readUint32LE(); + } + + // Restore locations + for (i = LOCMIN; i <= LOCMAX; i++) { + _locations[i - LOCMIN].describe = saveFile->readUint32LE(); + if (_locations[i - LOCMIN].atrs) + for (atr = (AtrElem *)addrTo(_locations[i - LOCMIN].atrs); !endOfTable(atr); atr++) + atr->val = saveFile->readUint32LE(); + } + + // Restore objects + for (i = OBJMIN; i <= OBJMAX; i++) { + _objects[i - OBJMIN].loc = saveFile->readUint32LE(); + if (_objects[i - OBJMIN].atrs) + for (atr = (AtrElem *)addrTo(_objects[i - OBJMIN].atrs); !endOfTable(atr); atr++) + atr->val = saveFile->readUint32LE(); + } + + // Restore the event queue + *_eventTop = 0; + do { + saveFile->read(&_events[*_eventTop], sizeof(_events[0])); + (*_eventTop)++; + } while (_events[*_eventTop - 1].time != 0); + + (*_eventTop)--; + + // Restore scores + for (i = 0; _vm->scores[i] != EOF; i++) + _vm->scores[i] = saveFile->readUint32LE(); + + delete saveFile; +} + +bool SaveLoad::endOfTable(AtrElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +} // End of namespace Alan2 +} // Engine of namespace GLK diff --git a/engines/glk/alan2/saveload.h b/engines/glk/alan2/saveload.h new file mode 100644 index 0000000000..32a02bb5c4 --- /dev/null +++ b/engines/glk/alan2/saveload.h @@ -0,0 +1,55 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_SAVELOAD +#define GLK_ALAN2_SAVELOAD + +#include "common/scummsys.h" +#include "glk/alan2/types.h" + +namespace Glk { +namespace Alan2 { + +class SaveLoad { +public: + SaveLoad(Common::String &gameName, ActElem *actors, LocElem *locations, ObjElem *objects, EvtqElem *events, int *eventTop) : + _gameName(gameName), _actors(actors), _locations(locations), _objects(objects), _events(events), _eventTop(eventTop) {} + void save(); + void restore(); +private: + bool endOfTable(AtrElem *addr); + + Common::String _prevSaveName; + + // Save state related variables + Common::String _gameName; + ActElem *_actors; + LocElem *_locations; + ObjElem *_objects; + EvtqElem *_events; + int *_eventTop; +}; + +} // End of namespace Alan2 +} // End of namespace Glk + +#endif diff --git a/engines/glk/alan2/types.h b/engines/glk/alan2/types.h new file mode 100644 index 0000000000..b403cf997c --- /dev/null +++ b/engines/glk/alan2/types.h @@ -0,0 +1,271 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_TYPES +#define GLK_ALAN2_TYPES + +#include "glk/alan2/acode.h" + +namespace Glk { +namespace Alan2 { + +// CONSTANTS + +#define ACTMIN (_vm->header->actmin) +#define ACTMAX (_vm->header->actmax) +#define OBJMIN (_vm->header->objmin) +#define OBJMAX (_vm->header->objmax) +#define LOCMIN (_vm->header->locmin) +#define LOCMAX (_vm->header->locmax) +#define CNTMIN (_vm->header->cntmin) +#define CNTMAX (_vm->header->cntmax) +#define LITMIN (_vm->header->locmax + 1) +#define LITMAX (_vm->header->locmax + 1 + litCount) +#define EVTMIN (_vm->header->evtmin) +#define EVTMAX (_vm->header->evtmax) + +#define HERO ACTMIN + + +#define addrTo(x) (&_vm->memory[x]) + +// The word classes are represented as numbers but in the dictonary they are generated as bits +#define isVerb(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_VRB))!=0) +#define isConj(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_CONJ))!=0) +#define isBut(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_BUT))!=0) +#define isThem(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_THEM))!=0) +#define isIt(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_IT))!=0) +#define isNoun(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_NOUN))!=0) +#define isAdj(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_ADJ))!=0) +#define isPrep(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_PREP))!=0) +#define isAll(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_ALL))!=0) +#define isDir(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_DIR))!=0) +#define isNoise(word) (word < dictsize && (dict[word].wordClass&((Aword)1L<<WRD_NOISE))!=0) +#define isLiteral(word) (word >= dictsize) + + +// TYPES + +// Amachine variables +typedef struct CurVars { + int + vrb, + obj, + loc, + act, + tick, + score, + visits; +} CurVars; + +// The various tables +typedef struct WrdElem { // Dictionary + Aaddr wrd; // ACODE address to string + Aword wordClass; // Word class + Aword code; + Aaddr adjrefs; // Address to reference list + Aaddr nounrefs; // Address to reference list +} WrdElem; + +typedef struct ActElem { // ACTOR TABLE + Aword loc; // Location + Abool describe; // Description flag + Aaddr nam; // Address to name printing code + Aaddr atrs; // Address to attribute list + Aword cont; // Code for the container props if any + Aword script; // Which script is he using + Aaddr scradr; // Address to script table + Aword step; + Aword count; + Aaddr vrbs; + Aaddr dscr; // Address of description code +} ActElem; + +typedef struct ScrElem { // SCRIPT TABLE + Aword code; // Script number + Aaddr dscr; // Optional description statements + Aaddr steps; // Address to steps +} ScrElem; + +typedef struct StepElem { // STEP TABLE + Aword after; // After how many ticks? + Aaddr exp; // Address to expression saying when + Aaddr stm; // Address to the actual code +} StepElem; + +typedef struct LocElem { // LOCATION TABLE + Aaddr nams; // Address of name printing code + Aaddr dscr; // Address of description code + Aaddr does; // Address of does code + Aword describe; // Description flag & counter + Aaddr atrs; // Address of attribute list + Aaddr exts; // Address of exit list + Aaddr vrbs; // Address of local verb list +} LocElem; + +typedef struct ExtElem { // EXIT TABLE structure + Abool done; // Flag for reverse/convert process + Aword code; // Direction code + Aaddr checks; // Address of check table + Aaddr action; // Address of action code + Aword next; // Number of next location +} ExtElem; + +typedef struct ChkElem { // CHECK TABLE + Aaddr exp; // ACODE address to expression code + Aaddr stms; // ACODE address to statement code +} ChkElem; + +typedef struct VrbElem { // VERB TABLE + Aword code; // Code for the verb + Aaddr alts; // Address to alternatives +} VrbElem; + +typedef struct StxElem { // SYNTAX TABLE + Aword code; // Code for verb word + Aaddr elms; // Address to element tables +} StxElem; + +typedef struct ElmElem26 { // ELEMENT TABLES + Aword code; // Code for this element, 0 -> parameter + Abool multiple; // May be multiple (if parameter) + Aaddr next; // Address to next element table ... + // ... or class check if EOS +} ElmElem26; + +typedef struct ElmElem { // ELEMENT TABLES + Aword code; // Code for this element, 0 -> parameter + Aword flags; // Flags for multiple/omni (if parameter) + // CHANGED: v2.7 from Abool for multiple + Aaddr next; // Address to next element table ... + // ... or class check if EOS +} ElmElem; + +typedef struct ClaElem { // CLASS DEFINITION TABLE + Aword code; // Parameter number + Aword classes; // Parameter classes + Aaddr stms; // Exception statements +} ClaElem; + +typedef struct AltElem { // VERB ALTERNATIVE TABLE + Abool done; // Flag for patching (reverse/convert) process + Aword param; // Parameter number + Aword qual; // Verb execution qualifier + Aaddr checks; // Address of the check table + Aaddr action; // Address of the action code +} AltElem; + +typedef struct AtrElem { // ATTRIBUTE LIST + Aword val; // Its value + Aaddr stradr; // Address to the name +} AtrElem; + +typedef struct ObjElem25 { // OBJECT TABLE of 2.5 format + Aword loc; // Current location + Abool describe; // Describe flag + Aaddr atrs; // Address of attribute list + Aword cont; // Index to container properties if any + Aaddr vrbs; // Address to local verb table + Aaddr dscr1; // Address to Aword description code + Aaddr dscr2; // Address to short description code +} ObjElem25; + +typedef struct ObjElem { // OBJECT TABLE + Aword loc; // Current location + Abool describe; // Describe flag + Aaddr atrs; // Address of attribute list + Aword cont; // Index to container properties if any + Aaddr vrbs; // Address to local verb table + Aaddr dscr1; // Address to Aword description code + Aaddr art; // Article printing code? Else use default + // INTRODUCED: v2.6 + Aaddr dscr2; // Address to short description code +} ObjElem; + +typedef struct CntElem { // CONTAINER TABLE + Aaddr lims; // Address to limit check code + Aaddr header; // Address to header code + Aaddr empty; // Address to empty code + Aword parent; // Object or actor index + Aaddr nam; // Address to statement printing name +} CntElem; + +typedef struct LimElem { // LIMIT Type + Aword atr; // Attribute that limits + Aword val; // And the limiting value + Aaddr stms; // Statements if fail +} LimElem; + +typedef struct RulElem { // RULE TABLE + Abool run; // Is rule already run? + Aaddr exp; // Address to expression code + Aaddr stms; // Address to run +} RulElem; + +typedef struct EvtElem { // EVENT TABLE + Aaddr stradr; // Address to name string + Aaddr code; // Address of code to run +} EvtElem; + +typedef struct EvtqElem { // EVENT QUEUE ELEMENT + int time; + int event; + int where; +} EvtqElem; + +typedef struct IniElem { // STRING INITIALISATION TABLE + Aword fpos; // File position + Aword len; // Length + Aword adr; // Where to store the string +} IniElem; + +typedef struct MsgElem26 { // MESSAGE TABLE + Aword fpos; // File position + Aword len; // Length of message +} MsgElem26; + +typedef struct MsgElem { // MESSAGE TABLE + Aaddr stms; // Address to statements + // Changed v2.7 from fpos+len in .dat +} MsgElem; + + +typedef struct ParamElem { // PARAMETER + Aword code; // Code for this parameter (0=multiple) + Aword firstWord; // Index to first word used by player + Aword lastWord; // d:o to last +} ParamElem; + +typedef enum Type {TYPNUM, TYPSTR} Type; + +typedef struct LitElem { // LITERAL + Type type; + Aptr value; +} LitElem; + +#define MAXPARAMS 9 +#define MAXENTITY (_vm->header->actmax) + +} // End of namespace Alan2 +} // Engine of namespace GLK + +#endif diff --git a/engines/glk/alan2/util.h b/engines/glk/alan2/util.h new file mode 100644 index 0000000000..3f5a482cd7 --- /dev/null +++ b/engines/glk/alan2/util.h @@ -0,0 +1,88 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_ALAN2_UTIL +#define GLK_ALAN2_UTIL + +#include "glk/alan2/acode.h" +#include "glk/alan2/alan2.h" +#include "glk/alan2/types.h" + +namespace Glk { +namespace Alan2 { + +// TODO: Move these +LitElem litValues[]; +uint32 litCount = 0; // for LITMAX +extern ActElem *acts; // Actor table pointer +extern ObjElem *objs; // Object table pointer + +// Type checks +bool isObj(Aword x) { + return x >= OBJMIN && x <= OBJMAX; +} + +bool isAct(Aword x) { + return x >= ACTMIN && x <= ACTMAX; +} + +bool isCnt(Aword x) { + return (x >= CNTMIN && x <= CNTMAX) || + (isObj(x) && objs[x - OBJMIN].cont != 0) || + (isAct(x) && acts[x - ACTMIN].cont != 0); +} + +bool isLoc(Aword x) { + return x >= LOCMIN && x <= LOCMAX; +} + +bool isNum(Aword x) { + return x >= LITMIN && x <= LITMAX && litValues[x - LITMIN].type == TYPNUM; +} + +bool isStr(Aword x) { + return x >= LITMIN && x <= LITMAX && litValues[x - LITMIN].type == TYPSTR; +} + +bool isLit(Aword x) { + return x >= LITMIN && x <= LITMAX; +} + +bool endOfTable(LimElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +bool endOfTable(ScrElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +bool endOfTable(ExtElem *addr) { + Aword *x = (Aword *)addr; + return *x == EOF; +} + +} // End of namespace Alan2 +} // Engine of namespace GLK + +#endif diff --git a/engines/glk/module.mk b/engines/glk/module.mk index 13d630544d..8125a02f9b 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -23,7 +23,13 @@ MODULE_OBJS := \ window_text_buffer.o \ window_text_grid.o \ alan2/alan2.o \ + alan2/decode.o \ alan2/detection.o \ + alan2/execute.o \ + alan2/interpreter.o \ + alan2/parse.o \ + alan2/rules.o \ + alan2/saveload.o \ frotz/config.o \ frotz/detection.o \ frotz/frotz.o \ |