aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKostas Nakos2007-06-13 12:48:14 +0000
committerKostas Nakos2007-06-13 12:48:14 +0000
commita0cd025a16a365a5ef366ef8149c7e26216e7832 (patch)
tree63a6d3ed927586614d5a6483efdec2b3725f5ecf
parentee78a7427bce0b8a127560a4a8140e56902d18c6 (diff)
downloadscummvm-rg350-a0cd025a16a365a5ef366ef8149c7e26216e7832.tar.gz
scummvm-rg350-a0cd025a16a365a5ef366ef8149c7e26216e7832.tar.bz2
scummvm-rg350-a0cd025a16a365a5ef366ef8149c7e26216e7832.zip
implement predictive dictionary using ascii based operations, replacing the 10ary tree
svn-id: r27383
-rw-r--r--engines/agi/agi.cpp7
-rw-r--r--engines/agi/agi.h10
-rw-r--r--engines/agi/predictive.cpp294
3 files changed, 161 insertions, 150 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index baac4d86a4..d3c064d379 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -594,7 +594,9 @@ AgiEngine::AgiEngine(OSystem *syst) : Engine(syst) {
_oldMode = -1;
_predictiveDialogRunning = false;
- _searchTreeRoot = 0;
+ _predictiveDictText = NULL;
+ _predictiveDictLine = NULL;
+ _predictiveDictLines = 0;
_firstSlot = 0;
}
@@ -675,6 +677,9 @@ AgiEngine::~AgiEngine() {
_gfx->deinitMachine();
delete _rnd;
delete _console;
+
+ free(_predictiveDictLine);
+ free(_predictiveDictText);
}
int AgiEngine::init() {
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index 48e3966ec1..8294288a39 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -767,11 +767,11 @@ private:
void loadDict(void);
bool matchWord(void);
- SearchTree *_searchTreeRoot;
- SearchTree *_activeTreeNode;
-
- void insertSearchNode(const char *word);
-
+ // Predictive dialog
+ char *_predictiveDictText;
+ char **_predictiveDictLine;
+ int32 _predictiveDictLines;
+ char *_predictiveDictActLine;
String _currentCode;
String _currentWord;
int _wordNumber;
diff --git a/engines/agi/predictive.cpp b/engines/agi/predictive.cpp
index ec9ce78827..eaedba1d1a 100644
--- a/engines/agi/predictive.cpp
+++ b/engines/agi/predictive.cpp
@@ -36,78 +36,52 @@ namespace Agi {
#define kModeNum 1
#define kModeAbc 2
-static byte s_asciiToNumTable[256];
+#define MAXLINELEN 80
+
+uint8 findNumWords(char *str) {
+ char *ptr;
+
+ if (!str)
+ return 0;
-void setAsciiToNumTableBatch(const char *chars, byte value) {
- while (*chars) {
- s_asciiToNumTable[tolower(*chars)] = value;
- s_asciiToNumTable[toupper(*chars)] = value;
- chars++;
+ ptr = strchr(str, ' ');
+ if (!ptr) {
+ debug("Invalid dictionary line");
+ return 0;
}
-}
-void initAsciiToNumTable() {
- memset(s_asciiToNumTable, 0, sizeof(s_asciiToNumTable));
-
- setAsciiToNumTableBatch("1'-.&", 1);
- setAsciiToNumTableBatch("2abc", 2);
- setAsciiToNumTableBatch("3def", 3);
- setAsciiToNumTableBatch("4ghi", 4);
- setAsciiToNumTableBatch("5jkl", 5);
- setAsciiToNumTableBatch("6mno", 6);
- setAsciiToNumTableBatch("7pqrs", 7);
- setAsciiToNumTableBatch("8tuv", 8);
- setAsciiToNumTableBatch("9wxyz", 9);
+ uint8 num = 1;
+ ptr++;
+ while ((ptr = strchr(ptr, ' '))) {
+ ptr++;
+ num++;
+ }
+ return num;
}
-class SearchTree {
-public:
- //byte val;
- //SearchTree *parent; // TODO: Could be used to speed up re-searches
- SearchTree *children[10];
+void bringWordtoTop(char *str, int wordnum) {
Common::StringList words;
-
- SearchTree() {
- memset(children, 0, sizeof(children));
- }
-
- SearchTree *getChild(byte val) {
- assert(val < 10);
- if (children[val] == 0) {
- children[val] = new SearchTree();
- }
- return children[val];
- }
+ char buf[MAXLINELEN];
- SearchTree *findChildWithWords() {
- if (!words.empty())
- return this;
-
- SearchTree *child = 0;
- for (int i = 0; i < 10 && !child; ++i) {
- if (children[i])
- child = children[i]->findChildWithWords();
- }
-
- return child;
- }
-
-};
-
-
-void AgiEngine::insertSearchNode(const char *word) {
- // Insert the word into the tree
- SearchTree *tree = _searchTreeRoot;
- assert(tree);
- for (int i = 0; word[i] != 0; ++i) {
- byte key = s_asciiToNumTable[(int)word[i]];
- if (key == 0)
- return; // abort!
- tree = tree->getChild(key);
+ if (!str)
+ return;
+ strncpy(buf, str, MAXLINELEN);
+ char *word = strtok(buf, " ");
+ if (!word) {
+ debug("Invalid dictionary line");
+ return;
}
- // TODO: Sort words, remove duplicates... ?
- tree->words.push_back(word);
+ words.push_back(word);
+ while ((word = strtok(NULL, " ")) != NULL)
+ words.push_back(word);
+ words.insert_at(1, words.remove_at(wordnum + 1));
+
+ Common::String tmp;
+ for (uint8 i = 0; i < words.size(); i++)
+ tmp += words[i] + " ";
+ tmp.deleteLastChar();
+ memcpy(str, tmp.c_str(), strlen(str));
}
bool AgiEngine::predictiveDialog(void) {
@@ -122,9 +96,6 @@ bool AgiEngine::predictiveDialog(void) {
bool navigationwithkeys = false;
bool processkey;
- // FIXME: Move this to a more appropriate place.
- initAsciiToNumTable();
-
const char *buttonStr[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" };
const char *buttons[] = {
"(1)'-.&", "(2)abc", "(3)def",
@@ -146,12 +117,15 @@ bool AgiEngine::predictiveDialog(void) {
};
const char *modes[] = { "(*)Pre", "(*)123", "(*)Abc" };
- if (!_searchTreeRoot) {
+ // FIXME: Move this to a more appropriate place.
+ if (!_predictiveDictText) {
loadDict();
- if (!_searchTreeRoot)
+ if (!_predictiveDictText)
return false;
}
-
+ _predictiveDictActLine = NULL;
+ uint8 numMatchingWords = 0;
+
_predictiveDialogRunning = true;
_system->setFeatureState(OSystem::kFeatureDisableKeyFiltering, true);
@@ -194,15 +168,13 @@ bool AgiEngine::predictiveDialog(void) {
}
/* clear key queue */
- while (_gfx->keypress()) {
+ while (_gfx->keypress())
_gfx->getKey();
- }
prefix.clear();
_currentCode.clear();
_currentWord.clear();
_wordNumber = 0;
- _activeTreeNode = 0;
int mode = kModePre;
@@ -214,8 +186,8 @@ bool AgiEngine::predictiveDialog(void) {
int color1 = colors[i * 2];
int color2 = colors[i * 2 + 1];
- if (i == 9 && !((mode != kModeAbc && _activeTreeNode && _activeTreeNode->words.size() > 1) ||
- (mode == kModeAbc && _currentWord.size() && _currentWord.lastChar() != ' '))) { // Next
+ if (i == 9 && !((mode != kModeAbc && _predictiveDictActLine && numMatchingWords > 1)
+ || (mode == kModeAbc && _currentWord.size() && _currentWord.lastChar() != ' '))) { // Next
color2 = 7;
}
@@ -230,7 +202,7 @@ bool AgiEngine::predictiveDialog(void) {
}
}
- temp[MAXWORDLEN] = 0;
+ temp[MAXWORDLEN] = 0;
strncpy(temp, prefix.c_str(), MAXWORDLEN);
strncat(temp, _currentWord.c_str(), MAXWORDLEN);
@@ -385,17 +357,15 @@ bool AgiEngine::predictiveDialog(void) {
lastactive = active;
if (active == 15 && mode != kModeNum) { // Space
// bring MRU word at the top of the list when changing words
- if (mode == kModePre && _activeTreeNode && _activeTreeNode->words.size() > 1 && _wordNumber != 0) {
- String tmpstr = _activeTreeNode->words.remove_at(_wordNumber);
- _activeTreeNode->words.insert_at(0, tmpstr);
- }
-
+ if (mode == kModePre && _predictiveDictActLine && numMatchingWords > 1 && _wordNumber != 0)
+ bringWordtoTop(_predictiveDictActLine, _wordNumber);
strncpy(temp, _currentWord.c_str(), _currentCode.size());
temp[_currentCode.size()] = 0;
prefix += temp;
prefix += " ";
_currentCode.clear();
_currentWord.clear();
+ numMatchingWords = 0;
memset(repeatcount, 0, MAXWORDLEN);
} else if (active < 9 || active == 11 || active == 15) { // number or backspace
if (active == 11) { // backspace
@@ -423,6 +393,7 @@ bool AgiEngine::predictiveDialog(void) {
_currentCode.deleteLastChar();
matchWord();
}
+ numMatchingWords = findNumWords(_predictiveDictActLine);
break;
case kModeAbc:
for (x = 0; x < _currentCode.size(); x++)
@@ -432,13 +403,17 @@ bool AgiEngine::predictiveDialog(void) {
_currentWord = temp;
}
} else if (active == 9) { // next
- if (mode != kModeAbc) {
- int totalWordsNumber = _activeTreeNode ? _activeTreeNode->words.size() : 0;
- if (totalWordsNumber > 0) {
- _wordNumber = (_wordNumber + 1) % totalWordsNumber;
- _currentWord = String(_activeTreeNode->words[_wordNumber].c_str(), _currentCode.size());
+ if (mode == kModePre) {
+ if (_predictiveDictActLine && numMatchingWords > 1) {
+ _wordNumber = (_wordNumber + 1) % numMatchingWords;
+ char tmp[MAXLINELEN];
+ strncpy(tmp, _predictiveDictActLine, MAXLINELEN);
+ char *tok = strtok(tmp, " ");
+ for (uint8 i = 0; i <= _wordNumber; i++)
+ tok = strtok(NULL, " ");
+ _currentWord = String(tok, _currentCode.size());
}
- } else {
+ } else if (mode == kModeAbc){
x = _currentCode.size();
if (x) {
if (_currentCode.lastChar() == '1' || _currentCode.lastChar() == '7' || _currentCode.lastChar() == '9')
@@ -453,11 +428,8 @@ bool AgiEngine::predictiveDialog(void) {
debug(0, "add");
} else if (active == 13) { // Ok
// bring MRU word at the top of the list when ok'ed out of the dialog
- if (mode == kModePre && _activeTreeNode && _activeTreeNode->words.size() > 1 && _wordNumber != 0) {
- String tmpstr = _activeTreeNode->words.remove_at(_wordNumber);
- _activeTreeNode->words.insert_at(0, tmpstr);
- }
-
+ if (mode == kModePre && _predictiveDictActLine && numMatchingWords > 1 && _wordNumber != 0)
+ bringWordtoTop(_predictiveDictActLine, _wordNumber);
rc = true;
goto press;
} else if (active == 14) { // Mode
@@ -503,79 +475,113 @@ bool AgiEngine::predictiveDialog(void) {
return rc;
}
-#define MAXLINELEN 80
-
void AgiEngine::loadDict(void) {
- Common::File in;
- char buf[MAXLINELEN];
- int words = 0, lines = 0;
+ Common::File inFile;
+ int lines = 0;
ConfMan.registerDefault("predictive_dictionary", "pred.dic");
- if (!in.open(ConfMan.get("predictive_dictionary")))
+ uint32 time1 = _system->getMillis();
+ if (!inFile.open(ConfMan.get("predictive_dictionary")))
return;
- _searchTreeRoot = new SearchTree();
- words = 0;
-
- while (!in.eos() && in.readLine(buf, MAXLINELEN)) {
- // Skip leading & trailing whitespaces
- char *word = Common::trim(buf);
+ char *ptr;
+ int size = inFile.size();
- // Skip empty lines
- if (*word == 0)
- continue;
-
+ _predictiveDictText = (char *) malloc(size + 1);
+ if (!_predictiveDictText) {
+ warning("Not enough memory to load the predictive dictionary");
+ return;
+ }
+ inFile.read(_predictiveDictText, size);
+ _predictiveDictText[size] = 0;
+ uint32 time2 = _system->getMillis();
+ debug("Time to read %s: %d bytes, %d ms", inFile.name(), size, time2-time1);
+ inFile.close();
+
+ ptr = _predictiveDictText;
+ lines = 1;
+ while ( (ptr = (char *) strchr(ptr, '\n')) != (char *) NULL ) {
lines++;
+ ptr++;
+ }
- // The lines are of the form: "1234 word word"
- // I.e. first comes a T9 number, then a space separated list of
- // words with that T9 code. We simply ignore the T9 code, and then
- // insert the words in order of occurance.
- char *tok = strtok(word, " \t");
- if (tok) {
- while ((tok = strtok(NULL, " ")) != NULL) {
- insertSearchNode(tok);
- words++;
- }
- }
+ _predictiveDictLine = (char **) calloc(1, sizeof(char *) * lines);
+ if (_predictiveDictLine == NULL) {
+ warning("Cannot allocate memory for line index buffer.");
+ return;
}
+ _predictiveDictLine[0] = _predictiveDictText;
+ ptr = _predictiveDictText;
+ int i = 1;
+ while ( (ptr = (char *) strchr(ptr, '\n')) != (char *) NULL ) {
+ *ptr = 0;
+ ptr++;
+ _predictiveDictLine[i++] = ptr;
+ }
+ if (_predictiveDictLine[lines - 1][0] == 0)
+ lines--;
+
+ _predictiveDictLines = lines;
+ debug("Loaded %d lines", _predictiveDictLines);
- debug(0, "Loaded %d lines with %d words", lines, words);
+ uint32 time3 = _system->getMillis();
+ printf("Time to parse pred.dic: %d, total: %d\n", time3-time2, time3-time1);
}
bool AgiEngine::matchWord(void) {
if (_currentCode.empty()) {
return false;
}
-
- // Lookup word in the search tree
- SearchTree *tree = _searchTreeRoot;
- assert(tree);
- for (uint i = 0; i < _currentCode.size(); ++i) {
- int key = _currentCode[i] - '0';
- if (key < 1 || key > 9) {
- tree = 0;
- break; // Invalid key/code value, abort!
- }
- tree = tree->children[key];
- if (!tree)
- break; // No matching entry in the search tree, abort!
- }
-
- if (tree)
- tree = tree->findChildWithWords();
+ // Lookup word in the dictionary
+ int line, span, res, len, i;
+ char target[MAXWORDLEN];
+
+ strncpy(target, _currentCode.c_str(), MAXWORDLEN);
+ strcat(target, " ");
+
+ // do the search at most two times:
+ // first try to match the exact code, by mtaching also the space after the code
+ // if there is not an exact match, do it once more for the best matching prefix (drop the space)
+ i = 0;
+ len = _currentCode.size() + 1;
+ do {
+ line = (_predictiveDictLines + 1) / 2 - 1;
+ // find out the 2^upper_int(log2(_predictiveDictLines))
+ for (span = 1; span < _predictiveDictLines; span <<= 1);
+ span >>= 1;
+ do {
+ res = strncmp(_predictiveDictLine[line], target, len);
+ if (res > 0) {
+ span /= 2;
+ line -= span;
+ if (line < 0)
+ line = 0;
+ } else if (res < 0) {
+ span /= 2;
+ line += span;
+ if (line >= _predictiveDictLines)
+ line = _predictiveDictLines - 1;
+ }
+ } while (res && span);
+ len--;
+ i++;
+ } while (i < 2 && (i == 1 && res));
- _wordNumber = 0;
- _activeTreeNode = tree;
_currentWord.clear();
-
-
- if (tree) {
- _currentWord = String(_activeTreeNode->words[_wordNumber].c_str(), _currentCode.size());
+ _wordNumber = 0;
+ if (!strncmp(_predictiveDictLine[line], target, len)) {
+ _predictiveDictActLine = _predictiveDictLine[line];
+ char tmp[MAXLINELEN];
+ strncpy(tmp, _predictiveDictActLine, MAXLINELEN);
+ char *tok = strtok(tmp, " ");
+ tok = strtok(NULL, " ");
+ _currentWord = String(tok, _currentCode.size());
+ return true;
+ } else {
+ _predictiveDictActLine = NULL;
+ return false;
}
-
- return tree != 0;
}
} // End of namespace Agi