aboutsummaryrefslogtreecommitdiff
path: root/engines/agi/words.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/agi/words.cpp')
-rw-r--r--engines/agi/words.cpp369
1 files changed, 257 insertions, 112 deletions
diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp
index ff9049fdc5..7072f003c2 100644
--- a/engines/agi/words.cpp
+++ b/engines/agi/words.cpp
@@ -21,22 +21,23 @@
*/
#include "agi/agi.h"
+#include "agi/words.h"
#include "common/textconsole.h"
namespace Agi {
-//
-// Local implementation to avoid problems with strndup() used by
-// gcc 3.2 Cygwin (see #635984)
-//
-static char *myStrndup(const char *src, int n) {
- char *tmp = strncpy((char *)malloc(n + 1), src, n);
- tmp[n] = 0;
- return tmp;
+Words::Words(AgiEngine *vm) {
+ _vm = vm;
+
+ clearEgoWords();
+}
+
+Words::~Words() {
+ clearEgoWords();
}
-int AgiEngine::loadWords_v1(Common::File &f) {
+int Words::loadDictionary_v1(Common::File &f) {
char str[64];
int k;
@@ -55,18 +56,21 @@ int AgiEngine::loadWords_v1(Common::File &f) {
// And store it in our internal dictionary
if (k > 0) {
- AgiWord *w = new AgiWord;
- w->word = myStrndup(str, k + 1);
- w->id = f.readUint16LE();
- _game.words[str[0] - 'a'].push_back(w);
- debug(3, "'%s' (%d)", w->word, w->id);
+ WordEntry *newWord = new WordEntry;
+ byte firstCharNr = str[0] - 'a';
+
+ newWord->word = Common::String(str, k + 1); // myStrndup(str, k + 1);
+ newWord->id = f.readUint16LE();
+
+ _dictionaryWords[firstCharNr].push_back(newWord);
+ debug(3, "'%s' (%d)", newWord->word.c_str(), newWord->id);
}
- } while((uint8)str[0] != 0xFF);
+ } while ((uint8)str[0] != 0xFF);
return errOK;
}
-int AgiEngine::loadWords(const char *fname) {
+int Words::loadDictionary(const char *fname) {
Common::File fp;
if (!fp.open(fname)) {
@@ -99,10 +103,10 @@ int AgiEngine::loadWords(const char *fname) {
// See bug #3615061
if (str[0] == 'a' + i) {
// And store it in our internal dictionary
- AgiWord *w = new AgiWord;
- w->word = myStrndup(str, k);
- w->id = fp.readUint16BE();
- _game.words[i].push_back(w);
+ WordEntry *newWord = new WordEntry;
+ newWord->word = Common::String(str, k);
+ newWord->id = fp.readUint16BE();
+ _dictionaryWords[i].push_back(newWord);
}
k = fp.readByte();
@@ -119,113 +123,254 @@ int AgiEngine::loadWords(const char *fname) {
return errOK;
}
-void AgiEngine::unloadWords() {
- for (int i = 0; i < 26; i++)
- _game.words[i].clear();
-}
+void Words::unloadDictionary() {
+ for (int16 firstCharNr = 0; firstCharNr < 26; firstCharNr++) {
+ Common::Array<WordEntry *> &dictionary = _dictionaryWords[firstCharNr];
+ int16 dictionarySize = dictionary.size();
-/**
- * Find a word in the dictionary
- * Uses an algorithm hopefully like the one Sierra used. Returns the ID
- * of the word and the length in flen. Returns -1 if not found.
- */
-int AgiEngine::findWord(const char *word, int *flen) {
- int c;
- int result = -1;
-
- debugC(2, kDebugLevelScripts, "find_word(%s)", word);
-
- if (word[0] >= 'a' && word[0] <= 'z')
- c = word[0] - 'a';
- else
- return -1;
-
- *flen = 0;
- Common::Array<AgiWord *> &a = _game.words[c];
-
- for (int i = 0; i < (int)a.size(); i++) {
- int wlen = strlen(a[i]->word);
- // Keep looking till we find the word itself, or the whole phrase.
- // Try to find the best match (i.e. the longest matching phrase).
- if (!strncmp(a[i]->word, word, wlen) && (word[wlen] == 0 || word[wlen] == 0x20) && wlen >= *flen) {
- *flen = wlen;
- result = a[i]->id;
+ for (int16 dictionaryWordNr = 0; dictionaryWordNr < dictionarySize; dictionaryWordNr++) {
+ delete dictionary[dictionaryWordNr];
}
+
+ _dictionaryWords[firstCharNr].clear();
+ }
+}
+
+void Words::clearEgoWords() {
+ for (int16 wordNr = 0; wordNr < MAX_WORDS; wordNr++) {
+ _egoWords[wordNr].id = 0;
+ _egoWords[wordNr].word.clear();
}
+ _egoWordCount = 0;
+}
+
+
+static bool isCharSeparator(const char curChar) {
+ switch (curChar) {
+ case ' ':
+ case ',':
+ case '.':
+ case '?':
+ case '!':
+ case '(':
+ case ')':
+ case ';':
+ case ':':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
+}
- return result;
+static bool isCharInvalid(const char curChar) {
+ switch (curChar) {
+ case 0x27: // '
+ case 0x60: // `
+ case '-':
+ case '\\':
+ case '"':
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
}
-void AgiEngine::dictionaryWords(char *msg) {
- char *p = NULL;
- char *q = NULL;
- int wid, wlen;
-
- debugC(2, kDebugLevelScripts, "msg = \"%s\"", msg);
-
- cleanInput();
-
- for (p = msg; p && *p && getvar(vWordNotFound) == 0;) {
- if (*p == 0x20)
- p++;
-
- if (*p == 0)
- break;
-
- wid = findWord(p, &wlen);
- debugC(2, kDebugLevelScripts, "find_word(p) == %d", wid);
-
- switch (wid) {
- case -1:
- debugC(2, kDebugLevelScripts, "unknown word");
- _game.egoWords[_game.numEgoWords].word = strdup(p);
-
- q = _game.egoWords[_game.numEgoWords].word;
-
- _game.egoWords[_game.numEgoWords].id = 19999;
- setvar(vWordNotFound, 1 + _game.numEgoWords);
-
- _game.numEgoWords++;
-
- p += strlen(p);
- break;
- case 0:
- // ignore this word
- debugC(2, kDebugLevelScripts, "ignore word");
- p += wlen;
- q = NULL;
- break;
- default:
- // an OK word
- debugC(3, kDebugLevelScripts, "ok word (%d)", wid);
- _game.egoWords[_game.numEgoWords].id = wid;
- _game.egoWords[_game.numEgoWords].word = myStrndup(p, wlen);
- _game.numEgoWords++;
- p += wlen;
- break;
+void Words::cleanUpInput(const char *rawUserInput, Common::String &cleanInput) {
+ byte curChar = 0;
+
+ cleanInput.clear();
+
+ curChar = *rawUserInput;
+ while (curChar) {
+ // skip separators / invalid characters
+ if (isCharSeparator(curChar) || isCharInvalid(curChar)) {
+ rawUserInput++;
+ curChar = *rawUserInput;
+ } else {
+ do {
+ if (!isCharInvalid(curChar)) {
+ // not invalid char, add it to the cleaned up input
+ cleanInput += curChar;
+ }
+
+ rawUserInput++;
+ curChar = *rawUserInput;
+
+ if (isCharSeparator(curChar)) {
+ cleanInput += ' ';
+ break;
+ }
+ } while (curChar);
}
+ }
+ if (cleanInput.hasSuffix(" ")) {
+ // ends with a space? remove it
+ cleanInput.deleteLastChar();
+ }
+}
- if (p != NULL && *p) {
- debugC(2, kDebugLevelScripts, "p = %s", p);
- *p = 0;
- p++;
+int16 Words::findWordInDictionary(const Common::String &userInputLowcased, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen) {
+ uint16 userInputLeft = userInputLen - userInputPos;
+ uint16 wordStartPos = userInputPos;
+ int16 wordId = DICTIONARY_RESULT_UNKNOWN;
+ byte firstChar = userInputLowcased[userInputPos];
+ byte curUserInputChar = 0;
+
+ foundWordLen = 0;
+
+ if ((firstChar >= 'a') && (firstChar <= 'z')) {
+ // word has to start with a letter
+ if (((userInputPos + 1) < userInputLen) && (userInputLowcased[userInputPos + 1] == ' ')) {
+ // current word is 1 char only?
+ if ((firstChar == 'a') || (firstChar == 'i')) {
+ // and it's "a" or "i"? -> then set current type to ignore
+ wordId = DICTIONARY_RESULT_IGNORE;
+ }
}
- if (q != NULL) {
- for (; (*q != 0 && *q != 0x20); q++)
- ;
- if (*q) {
- *q = 0;
- q++;
+ Common::Array<WordEntry *> &dictionary = _dictionaryWords[firstChar - 'a'];
+ int16 dictionarySize = dictionary.size();
+
+ for (int16 dictionaryWordNr = 0; dictionaryWordNr < dictionarySize; dictionaryWordNr++) {
+ WordEntry *dictionaryEntry = dictionary[dictionaryWordNr];
+ uint16 dictionaryWordLen = dictionaryEntry->word.size();
+
+ if (dictionaryWordLen <= userInputLeft) {
+ // dictionary word is longer or same length as the remaining user input
+ uint16 curCompareLeft = dictionaryWordLen;
+ uint16 dictionaryWordPos = 0;
+ byte curDictionaryChar = 0;
+
+ userInputPos = wordStartPos;
+ while (curCompareLeft) {
+ curUserInputChar = userInputLowcased[userInputPos];
+ curDictionaryChar = dictionaryEntry->word[dictionaryWordPos];
+
+ if (curUserInputChar != curDictionaryChar)
+ break;
+
+ userInputPos++;
+ dictionaryWordPos++;
+ curCompareLeft--;
+ }
+
+ if (!curCompareLeft) {
+ // check, if there is also nothing more of user input left or if a space the follow-up char?
+ if ((userInputPos >= userInputLen) || (userInputLowcased[userInputPos] == ' ')) {
+ // so fully matched, remember match
+ wordId = dictionaryEntry->id;
+ foundWordLen = dictionaryWordLen;
+
+ // perfect match? -> exit loop
+ if (userInputLeft == foundWordLen) {
+ // perfect match -> break
+ break;
+ }
+ }
+ }
}
}
}
- debugC(4, kDebugLevelScripts, "num_ego_words = %d", _game.numEgoWords);
- if (_game.numEgoWords > 0) {
- setflag(fEnteredCli, true);
- setflag(fSaidAcceptedInput, false);
+ if (foundWordLen == 0) {
+ userInputPos = wordStartPos;
+ while (userInputPos < userInputLen) {
+ if (userInputLowcased[userInputPos] == ' ') {
+ break;
+ }
+ userInputPos++;
+ }
+ foundWordLen = userInputPos - wordStartPos;
}
+ return wordId;
+}
+
+void Words::parseUsingDictionary(const char *rawUserInput) {
+ Common::String userInput;
+ Common::String userInputLowcased;
+ const char *userInputPtr = nullptr;
+ uint16 userInputLen;
+ uint16 userInputPos = 0;
+ uint16 foundWordPos;
+ int16 foundWordId;
+ uint16 foundWordLen = 0;
+ uint16 wordCount = 0;
+
+ assert(rawUserInput);
+ debugC(2, kDebugLevelScripts, "parse: userinput = \"%s\"", rawUserInput);
+
+ // Reset result
+ clearEgoWords();
+
+ // clean up user input
+ cleanUpInput(rawUserInput, userInput);
+
+ // Sierra compared independent of upper case and lower case
+ userInputLowcased = userInput;
+ userInputLowcased.toLowercase();
+
+ userInputLen = userInput.size();
+ userInputPtr = userInput.c_str();
+
+ while (userInputPos < userInputLen) {
+ // Skip trailing space
+ if (userInput[userInputPos] == ' ')
+ userInputPos++;
+
+ foundWordPos = userInputPos;
+ foundWordId = findWordInDictionary(userInputLowcased, userInputLen, userInputPos, foundWordLen);
+
+ if (foundWordId != DICTIONARY_RESULT_IGNORE) {
+ // word not supposed to get ignored
+ // add it now
+ if (foundWordId != DICTIONARY_RESULT_UNKNOWN) {
+ // known word
+ _egoWords[wordCount].id = foundWordId;
+ }
+
+ _egoWords[wordCount].word = Common::String(userInputPtr + foundWordPos, foundWordLen);
+ debugC(2, kDebugLevelScripts, "found word %s (id %d)", _egoWords[wordCount].word.c_str(), _egoWords[wordCount].id);
+ wordCount++;
+
+ if (foundWordId == DICTIONARY_RESULT_UNKNOWN) {
+ // unknown word
+ _vm->setVar(VM_VAR_WORD_NOT_FOUND, wordCount);
+ break; // and exit now
+ }
+ }
+
+ userInputPos += foundWordLen;
+ }
+
+ _egoWordCount = wordCount;
+
+ debugC(4, kDebugLevelScripts, "ego word count = %d", _egoWordCount);
+ if (_egoWordCount > 0) {
+ _vm->setFlag(VM_FLAG_ENTERED_CLI, true);
+ } else {
+ _vm->setFlag(VM_FLAG_ENTERED_CLI, false);
+ }
+ _vm->setFlag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
+}
+
+uint16 Words::getEgoWordCount() {
+ return _egoWordCount;
+}
+const char *Words::getEgoWord(int16 wordNr) {
+ assert(wordNr >= 0 && wordNr < MAX_WORDS);
+ return _egoWords[wordNr].word.c_str();
+}
+uint16 Words::getEgoWordId(int16 wordNr) {
+ assert(wordNr >= 0 && wordNr < MAX_WORDS);
+ return _egoWords[wordNr].id;
}
} // End of namespace Agi