diff options
63 files changed, 1966 insertions, 1630 deletions
@@ -20,3 +20,4 @@ ChangeLog dawginput.raw playabilities.raw smaller.raw +DerivedData diff --git a/alphabetparameters.cpp b/alphabetparameters.cpp index 851d8ff..4830313 100644 --- a/alphabetparameters.cpp +++ b/alphabetparameters.cpp @@ -214,7 +214,7 @@ LetterString AlphabetParameters::encode(const UVString &word, UVString *leftover if (lookupIt == lookupEnd) { for (alphabetIt = m_alphabet.begin(); alphabetIt != alphabetEnd; ++alphabetIt) - if ((*alphabetIt).blankText() == query) + if (alphabetIt->blankText() == query) break; if (alphabetIt == alphabetEnd) @@ -224,7 +224,7 @@ LetterString AlphabetParameters::encode(const UVString &word, UVString *leftover } blank = true; - ret += blank? setBlankness((*alphabetIt).letter()) : (*alphabetIt).letter(); + ret += blank? setBlankness(alphabetIt->letter()) : alphabetIt->letter(); } else { ret += blank? setBlankness(m_alphabet[lookupIt->second].letter()) : m_alphabet[lookupIt->second].letter(); } @@ -241,7 +241,7 @@ LetterString AlphabetParameters::encode(const UVString &word, UVString *leftover string AlphabetParameters::findAlphabetFile(const string &alphabet) { - return DataManager::self()->findDataFile("alphabets", alphabet); + return DataManager::self()->findDataFile("alphabets", alphabet + ".quackle_alphabet"); } //////// @@ -744,8 +744,8 @@ void Board::prepareEmptyBoard() { m_letters[i][j] = QUACKLE_NULL_MARK; m_isBlank[i][j] = false; - m_vcross[i][j] = Utility::Max; - m_hcross[i][j] = Utility::Max; + m_vcross[i][j].set(); + m_hcross[i][j].set(); } } } @@ -20,6 +20,7 @@ #define QUACKLE_BOARD_H #include <vector> +#include <bitset> #include "alphabetparameters.h" #include "bag.h" @@ -27,7 +28,9 @@ #include "rack.h" using namespace std; - + +typedef bitset<QUACKLE_MAXIMUM_ALPHABET_SIZE> LetterBitset; + #define QUACKLE_MAXIMUM_BOARD_SIZE LETTER_STRING_MAXIMUM_LENGTH #define QUACKLE_MINIMUM_BOARD_SIZE 7 @@ -122,11 +125,11 @@ public: bool isBlank(int row, int col) const; bool isBritish(int row, int col) const; - int vcross(int row, int col) const; - void setVCross(int row, int col, int vcross); + const LetterBitset &vcross(int row, int col) const; + void setVCross(int row, int col, const LetterBitset &vcross); - int hcross(int row, int col) const; - void setHCross(int row, int col, int hcross); + const LetterBitset &hcross(int row, int col) const; + void setHCross(int row, int col, const LetterBitset &hcross); protected: int m_width; @@ -137,8 +140,8 @@ protected: bool m_isBlank[QUACKLE_MAXIMUM_BOARD_SIZE][QUACKLE_MAXIMUM_BOARD_SIZE]; bool m_isBritish[QUACKLE_MAXIMUM_BOARD_SIZE][QUACKLE_MAXIMUM_BOARD_SIZE]; - int m_vcross[QUACKLE_MAXIMUM_BOARD_SIZE][QUACKLE_MAXIMUM_BOARD_SIZE]; - int m_hcross[QUACKLE_MAXIMUM_BOARD_SIZE][QUACKLE_MAXIMUM_BOARD_SIZE]; + LetterBitset m_vcross[QUACKLE_MAXIMUM_BOARD_SIZE][QUACKLE_MAXIMUM_BOARD_SIZE]; + LetterBitset m_hcross[QUACKLE_MAXIMUM_BOARD_SIZE][QUACKLE_MAXIMUM_BOARD_SIZE]; inline bool isNonempty(int row, int column) const; }; @@ -163,22 +166,22 @@ inline bool Board::isBritish(int row, int col) const return m_isBritish[row][col]; } -inline int Board::vcross(int row, int col) const +inline const LetterBitset &Board::vcross(int row, int col) const { return m_vcross[row][col]; } -inline void Board::setVCross(int row, int col, int vcross) +inline void Board::setVCross(int row, int col, const LetterBitset &vcross) { m_vcross[row][col] = vcross; } -inline int Board::hcross(int row, int col) const +inline const LetterBitset &Board::hcross(int row, int col) const { return m_hcross[row][col]; } -inline void Board::setHCross(int row, int col, int hcross) +inline void Board::setHCross(int row, int col, const LetterBitset &hcross) { m_hcross[row][col] = hcross; } diff --git a/data/lexica/copyrights.txt b/data/lexica/copyrights.txt new file mode 100644 index 0000000..1e3e553 --- /dev/null +++ b/data/lexica/copyrights.txt @@ -0,0 +1,2 @@ +eea8dfe5:Collins Scrabble™ Words 2012, ©HarperCollins Publishers Ltd 2015 +48dea2c8:Collins Scrabble™ Words 2015, ©HarperCollins Publishers Ltd 2015 diff --git a/data/lexica/csw12.dawg b/data/lexica/csw12.dawg Binary files differindex f98ef8b..822bd95 100644 --- a/data/lexica/csw12.dawg +++ b/data/lexica/csw12.dawg diff --git a/data/lexica/csw12.gaddag b/data/lexica/csw12.gaddag Binary files differdeleted file mode 100644 index 511c99f..0000000 --- a/data/lexica/csw12.gaddag +++ /dev/null diff --git a/data/lexica/csw15.dawg b/data/lexica/csw15.dawg Binary files differnew file mode 100644 index 0000000..da4defa --- /dev/null +++ b/data/lexica/csw15.dawg diff --git a/data/lexica/cswapr07.dawg b/data/lexica/cswapr07.dawg Binary files differindex a2863ac..6e6dca7 100644 --- a/data/lexica/cswapr07.dawg +++ b/data/lexica/cswapr07.dawg diff --git a/data/lexica/greek.dawg b/data/lexica/greek.dawg Binary files differindex 90835e5..b8666ec 100644 --- a/data/lexica/greek.dawg +++ b/data/lexica/greek.dawg diff --git a/data/lexica/korean.dawg b/data/lexica/korean.dawg Binary files differindex 6aa5d3e..c55769e 100644 --- a/data/lexica/korean.dawg +++ b/data/lexica/korean.dawg diff --git a/data/lexica/norwegian.dawg b/data/lexica/norwegian.dawg Binary files differindex 0acdb33..2db2269 100644 --- a/data/lexica/norwegian.dawg +++ b/data/lexica/norwegian.dawg diff --git a/data/lexica/ods5.dawg b/data/lexica/ods5.dawg Binary files differindex eabc68a..8d440d9 100644 --- a/data/lexica/ods5.dawg +++ b/data/lexica/ods5.dawg diff --git a/data/lexica/osps.dawg b/data/lexica/osps.dawg Binary files differindex 6c02ecd..6d81d3d 100644 --- a/data/lexica/osps.dawg +++ b/data/lexica/osps.dawg diff --git a/data/lexica/russian.dawg b/data/lexica/russian.dawg Binary files differdeleted file mode 100644 index 3b01c8e..0000000 --- a/data/lexica/russian.dawg +++ /dev/null diff --git a/data/lexica/sowpods.dawg b/data/lexica/sowpods.dawg Binary files differindex b5d89df..e052222 100644 --- a/data/lexica/sowpods.dawg +++ b/data/lexica/sowpods.dawg diff --git a/data/lexica/tuvan.dawg b/data/lexica/tuvan.dawg Binary files differdeleted file mode 100644 index f5f31b3..0000000 --- a/data/lexica/tuvan.dawg +++ /dev/null diff --git a/data/lexica/twl06.dawg b/data/lexica/twl06.dawg Binary files differindex 9388487..e0afed7 100644 --- a/data/lexica/twl06.dawg +++ b/data/lexica/twl06.dawg diff --git a/data/lexica/twl06.gaddag b/data/lexica/twl06.gaddag Binary files differdeleted file mode 100644 index db93e2e..0000000 --- a/data/lexica/twl06.gaddag +++ /dev/null diff --git a/data/lexica/twl06_wild.dawg b/data/lexica/twl06_wild.dawg Binary files differdeleted file mode 100644 index 34e78a0..0000000 --- a/data/lexica/twl06_wild.dawg +++ /dev/null diff --git a/data/lexica/twl98.dawg b/data/lexica/twl98.dawg Binary files differindex a835b7c..e6f451e 100644 --- a/data/lexica/twl98.dawg +++ b/data/lexica/twl98.dawg diff --git a/datamanager.cpp b/datamanager.cpp index e188668..916610a 100644 --- a/datamanager.cpp +++ b/datamanager.cpp @@ -41,7 +41,8 @@ DataManager::DataManager() : m_evaluator(0), m_parameters(0), m_alphabetParameters(0), m_boardParameters(0), m_lexiconParameters(0), m_strategyParameters(0) { m_self = this; - setDataDirectory("."); + setAppDataDirectory("."); + setUserDataDirectory("."); seedRandomNumbers((int)time(NULL)); m_alphabetParameters = new EnglishAlphabetParameters; @@ -122,6 +123,7 @@ void DataManager::cleanupComputerPlayers() bool DataManager::fileExists(const string &filename) { + // fixme: convert to wchar struct stat buf; int i = stat(filename.c_str(), &buf); if (i == 0) @@ -130,36 +132,44 @@ bool DataManager::fileExists(const string &filename) return false; } -string DataManager::findDataFile(const string &subDirectory, const string &lexicon, string file) +string DataManager::findDataFile(const string &subDirectory, const string &lexicon, const string &file) { - string firstTry = makeDataFilename(subDirectory, lexicon, file); - if (fileExists(firstTry)) - return firstTry; - - string secondTry = makeDataFilename(subDirectory, m_backupLexicon, file); - if (fileExists(secondTry)) - return secondTry; + string fname = makeDataFilename(subDirectory, lexicon, file, true); + if (!fileExists(fname)) + fname = makeDataFilename(subDirectory, lexicon, file, false); + if (!fileExists(fname)) + fname = makeDataFilename(subDirectory, m_backupLexicon, file, false); + if (!fileExists(fname)) + fname = string(); - return string(); + return fname; } -string DataManager::findDataFile(const string &subDirectory, string file) +string DataManager::findDataFile(const string &subDirectory, const string &file) { - string firstTry = makeDataFilename(subDirectory, file); - if (fileExists(firstTry)) - return firstTry; + string fname = makeDataFilename(subDirectory, file, true); + if (!fileExists(fname)) + fname = makeDataFilename(subDirectory, file, false); + if (!fileExists(fname)) + fname = string(); + + return fname; +} - return string(); +bool DataManager::hasUserDataFile(const string &subDirectory, const string &file) +{ + string fname = makeDataFilename(subDirectory, file, true); + return fileExists(fname); } -string DataManager::makeDataFilename(const string &subDirectory, const string &lexicon, string file) +string DataManager::makeDataFilename(const string &subDirectory, const string &lexicon, const string &file, bool user) { - return m_dataDirectory + "/" + subDirectory + "/" + lexicon + "/" + file; + return (user ? m_userDataDirectory : m_appDataDirectory) + "/" + subDirectory + "/" + lexicon + "/" + file; } -string DataManager::makeDataFilename(const string &subDirectory, string file) +string DataManager::makeDataFilename(const string &subDirectory, const string &file, bool user) { - return m_dataDirectory + "/" + subDirectory + "/" + file; + return (user ? m_userDataDirectory : m_appDataDirectory) + "/" + subDirectory + "/" + file; } void DataManager::seedRandomNumbers(unsigned int seed) diff --git a/datamanager.h b/datamanager.h index 15e793a..75bce54 100644 --- a/datamanager.h +++ b/datamanager.h @@ -99,21 +99,27 @@ public: // Find a file at datadir/subdir/lexicon/file. // If this doesn't exist, tries backupLexicon instead of lexicon. // Returns empty string if the file is not found. - string findDataFile(const string &subDirectory, const string &lexicon, string file); + string findDataFile(const string &subDirectory, const string &lexicon, const string &file); // Find a file at datadir/subdir/file. // Returns empty string if the file is not found. - string findDataFile(const string &subDirectory, string file); + string findDataFile(const string &subDirectory, const string &file); + + // Returns true if the data file is in user-land. + bool hasUserDataFile(const string &subDirectory, const string &file); // returns similarly-named file - string makeDataFilename(const string &subDirectory, const string &lexicon, string file); - string makeDataFilename(const string &subDirectory, string file); + string makeDataFilename(const string &subDirectory, const string &lexicon, const string &file, bool user); + string makeDataFilename(const string &subDirectory, const string &file, bool user); void setBackupLexicon(string backupLexicon) { m_backupLexicon = backupLexicon; } string backupLexicon() { return m_backupLexicon; } - void setDataDirectory(string directory) { m_dataDirectory = directory; } - string dataDirectory() { return m_dataDirectory; } + void setAppDataDirectory(string directory) { m_appDataDirectory = directory; } + string appDataDirectory() { return m_appDataDirectory; } + + void setUserDataDirectory(string directory) { m_userDataDirectory = directory; } + string userDataDirectory() { return m_userDataDirectory; } void seedRandomNumbers(unsigned int seed); int randomNumber(); @@ -123,7 +129,9 @@ private: bool fileExists(const string &filename); - string m_dataDirectory; + string m_appDataDirectory; + + string m_userDataDirectory; // lexicon that has all data files string m_backupLexicon; diff --git a/encodeleaves/encodeleaves.pro b/encodeleaves/encodeleaves.pro index f1a51bb..559df28 100644 --- a/encodeleaves/encodeleaves.pro +++ b/encodeleaves/encodeleaves.pro @@ -11,16 +11,20 @@ CONFIG -= app_bundle debug { OBJECTS_DIR = obj/debug + QMAKE_LIBDIR += ../lib/debug ../quackleio/lib/debug } release { OBJECTS_DIR = obj/release + QMAKE_LIBDIR += ../lib/release ../quackleio/lib/release } -LIBS += -lquackleio -lquackle +win32:!win32-g++ { + LIBS += -lquackleio -llibquackle +} else { + LIBS += -lquackleio -lquackle +} -QMAKE_LFLAGS_RELEASE += -L../lib/release -L../quackleio/lib/release -QMAKE_LFLAGS_DEBUG += -L../lib/debug -L../quackleio/lib/debug # Input SOURCES += encodeleaves.cpp diff --git a/fixedstring.h b/fixedstring.h index 46d1011..a31ecd6 100644 --- a/fixedstring.h +++ b/fixedstring.h @@ -54,6 +54,8 @@ class FixedLengthString size_type size() const { return length(); } void clear() { m_end = m_data; } void push_back(char c); + void pop_back(); + const char* constData() const { return m_data; } int compare(const FixedLengthString& s) const; @@ -220,6 +222,13 @@ FixedLengthString::push_back(char c) *this += c; } +inline void +FixedLengthString::pop_back() +{ + assert(size() > 0); + m_end--; +} + inline int FixedLengthString::compare(const FixedLengthString& s) const { diff --git a/gaddagize/.gitignore b/gaddagize/.gitignore index f878785..3c70113 100644 --- a/gaddagize/.gitignore +++ b/gaddagize/.gitignore @@ -4,3 +4,4 @@ Makefile.Debug Makefile.Release debug release +gaddagize diff --git a/generator.cpp b/generator.cpp index 2bfc199..d20b320 100644 --- a/generator.cpp +++ b/generator.cpp @@ -132,7 +132,7 @@ void Generator::allCrosses() int col = vcols[i]; if (QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col))) { - board().setVCross(row, col, 0); + board().setVCross(row, col, LetterBitset()); } else { LetterString pre; @@ -167,7 +167,7 @@ void Generator::allCrosses() #endif if (pre.empty() && suf.empty()) { - board().setVCross(row, col, Utility::Max); + board().setVCross(row, col, LetterBitset().set()); } else { board().setVCross(row, col, fitbetween(pre, suf)); @@ -184,7 +184,7 @@ void Generator::allCrosses() int col = hcols[i]; if (QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col))) { - board().setHCross(row, col, 0); + board().setHCross(row, col, LetterBitset()); } else { LetterString pre; @@ -214,7 +214,7 @@ void Generator::allCrosses() } } if (pre.empty() && suf.empty()) { - board().setHCross(row, col, Utility::Max); + board().setHCross(row, col, LetterBitset().set()); } else { board().setHCross(row, col, fitbetween(pre, suf)); @@ -340,7 +340,7 @@ void Generator::makeMove(const Move &move, bool regenerateCrosses) int row = vrows[i]; int col = vcols[i]; if (QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col))) { - board().setVCross(row, col, 0); + board().setVCross(row, col, LetterBitset()); } else { LetterString pre; @@ -374,7 +374,7 @@ void Generator::makeMove(const Move &move, bool regenerateCrosses) #endif if ((pre.empty()) && (suf.empty())) { - board().setVCross(row, col, Utility::Max); + board().setVCross(row, col, LetterBitset().set()); } else { board().setVCross(row, col, fitbetween(pre, suf)); @@ -391,7 +391,7 @@ void Generator::makeMove(const Move &move, bool regenerateCrosses) int col = hcols[i]; if (QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col))) { - board().setHCross(row, col, 0); + board().setHCross(row, col, LetterBitset()); } else { LetterString pre; @@ -425,7 +425,7 @@ void Generator::makeMove(const Move &move, bool regenerateCrosses) #endif if ((pre.empty()) && (suf.empty())) { - board().setHCross(row, col, Utility::Max); + board().setHCross(row, col, LetterBitset().set()); } else { board().setHCross(row, col, fitbetween(pre, suf)); @@ -440,16 +440,7 @@ void Generator::makeMove(const Move &move, bool regenerateCrosses) void Generator::readFromDawg(int index, unsigned int &p, Letter &letter, bool &t, bool &lastchild, bool &british, int &playability) const { - index *= 7; - p = (QUACKLE_LEXICON_PARAMETERS->dawgAt(index) << 16) + (QUACKLE_LEXICON_PARAMETERS->dawgAt(index + 1) << 8) + (QUACKLE_LEXICON_PARAMETERS->dawgAt(index + 2)); - letter = QUACKLE_LEXICON_PARAMETERS->dawgAt(index + 3); - - t = (letter & 32) != 0; - lastchild = (letter & 64) != 0; - british = !(letter & 128); - letter = (letter & 31) + QUACKLE_FIRST_LETTER; - - playability = (QUACKLE_LEXICON_PARAMETERS->dawgAt(index + 4) << 16) + (QUACKLE_LEXICON_PARAMETERS->dawgAt(index + 5) << 8) + (QUACKLE_LEXICON_PARAMETERS->dawgAt(index + 6)); + QUACKLE_LEXICON_PARAMETERS->dawgAt(index, p, letter, t, lastchild, british, playability); } bool Generator::checksuffix(int i, const LetterString &suffix) { @@ -495,13 +486,13 @@ bool Generator::checksuffix(int i, const LetterString &suffix) { } } -int Generator::gaddagFitbetween(const LetterString &pre, const LetterString &suf) +LetterBitset Generator::gaddagFitbetween(const LetterString &pre, const LetterString &suf) { // UVcout << "fit " // << QUACKLE_ALPHABET_PARAMETERS->userVisible(pre) // << "_" // << QUACKLE_ALPHABET_PARAMETERS->userVisible(suf) << endl; - int crosses = 0; + LetterBitset crosses; /* process the suffix once */ const GaddagNode *sufNode = QUACKLE_LEXICON_PARAMETERS->gaddagRoot(); int sufLen = suf.length(); @@ -527,13 +518,13 @@ int Generator::gaddagFitbetween(const LetterString &pre, const LetterString &suf } if (n && n->isTerminal()) { - crosses |= Utility::Bits[(int)(childLetter - QUACKLE_FIRST_LETTER)]; + crosses.set(childLetter - QUACKLE_FIRST_LETTER); } } return crosses; } -int Generator::fitbetween(const LetterString &pre, const LetterString &suf) +LetterBitset Generator::fitbetween(const LetterString &pre, const LetterString &suf) { if (QUACKLE_LEXICON_PARAMETERS->hasGaddag()) { return gaddagFitbetween(pre, suf); @@ -542,7 +533,7 @@ int Generator::fitbetween(const LetterString &pre, const LetterString &suf) //UVcout << QUACKLE_ALPHABET_PARAMETERS->userVisible(pre) << "_" << // QUACKLE_ALPHABET_PARAMETERS->userVisible(suf) << endl; - int crosses = 0; + LetterBitset crosses; for (Letter c = QUACKLE_FIRST_LETTER; c <= QUACKLE_ALPHABET_PARAMETERS->lastLetter(); c++) { /* @@ -553,7 +544,7 @@ int Generator::fitbetween(const LetterString &pre, const LetterString &suf) */ if (checksuffix(1, pre + c + suf)) { // subtract first letter because crosses hold values starting from zero - crosses |= Utility::Bits[c - QUACKLE_FIRST_LETTER]; + crosses.set(c - QUACKLE_FIRST_LETTER); //UVcout << " that's a word" << endl; // UVcout << c; } @@ -574,12 +565,12 @@ UVString Generator::counts2string() return ret; } -UVString Generator::cross2string(int cross) +UVString Generator::cross2string(const LetterBitset &cross) { UVString ret; for (int i = 0; i < QUACKLE_ALPHABET_PARAMETERS->length(); i++) - if (cross & Utility::Bits[i]) + if (cross.test(i)) ret += QUACKLE_ALPHABET_PARAMETERS->userVisible(QUACKLE_FIRST_LETTER + i); return ret; @@ -820,7 +811,7 @@ void Generator::gordongen(int pos, const LetterString &word, const GaddagNode *n currow += pos; } - int cross; + LetterBitset cross; if (m_gordonhoriz) { cross = board().vcross(currow, curcol); } @@ -844,7 +835,7 @@ void Generator::gordongen(int pos, const LetterString &word, const GaddagNode *n Letter childLetter = child->letter(); if ((m_counts[childLetter] <= 0) - || !(cross & Utility::Bits[(int)(childLetter - QUACKLE_FIRST_LETTER)])) { + || !cross.test(childLetter - QUACKLE_FIRST_LETTER)) { continue; } @@ -871,7 +862,7 @@ void Generator::gordongen(int pos, const LetterString &word, const GaddagNode *n break; } - if (cross & Utility::Bits[(int)(childLetter - QUACKLE_FIRST_LETTER)]) { + if (cross.test(childLetter - QUACKLE_FIRST_LETTER)) { m_counts[QUACKLE_BLANK_MARK]--; m_laid++; // UVcout << " yeah that'll work" << endl; @@ -926,14 +917,14 @@ void Generator::extendright(const LetterString &partial, int i, if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(rowpos, colpos))) { if (m_counts[c] >= 1) { - int cross; + LetterBitset cross; if (horizontal) { cross = board().vcross(rowpos, colpos); } else { cross = board().hcross(rowpos, colpos); } - if (cross & Utility::Bits[(int)(c - QUACKLE_FIRST_LETTER)]) { + if (cross.test(c - QUACKLE_FIRST_LETTER)) { if (t) { bool couldend = true; if (dirpos < edgeDirpos) { @@ -962,7 +953,7 @@ void Generator::extendright(const LetterString &partial, int i, int laid = move.wordTilesWithNoPlayThru().length(); bool onetilevert = (!move.horizontal) && (laid == 1); - bool ignore = onetilevert && (board().hcross(row, col) != Utility::Max); + bool ignore = onetilevert && !board().hcross(row, col).all(); if (1 || !ignore) { @@ -991,14 +982,14 @@ void Generator::extendright(const LetterString &partial, int i, } } if ((m_counts[QUACKLE_BLANK_MARK] >= 1)) { - int cross; + LetterBitset cross; if (horizontal) { cross = board().vcross(rowpos, colpos); } else { cross = board().hcross(rowpos, colpos); } - if (cross & Utility::Bits[(int)(c - QUACKLE_FIRST_LETTER)]) { + if (cross.test(c - QUACKLE_FIRST_LETTER)) { if (t) { bool couldend = true; if (dirpos < edgeDirpos) { @@ -1024,7 +1015,7 @@ void Generator::extendright(const LetterString &partial, int i, int laid = move.wordTilesWithNoPlayThru().length(); bool onetilevert = (!move.horizontal) && (laid == 1); - bool ignore = onetilevert && (board().hcross(row, col) != Utility::Max); + bool ignore = onetilevert && !board().hcross(row, col).all(); if (1 || !ignore) { @@ -1115,7 +1106,7 @@ void Generator::extendright(const LetterString &partial, int i, int laid = move.wordTilesWithNoPlayThru().length(); bool onetilevert = (!move.horizontal) && (laid == 1); - bool ignore = onetilevert && (board().hcross(row, col) != Utility::Max); + bool ignore = onetilevert && !board().hcross(row, col).all(); if (1 || !ignore) { @@ -1215,7 +1206,7 @@ Move Generator::generate() // what defines an anchor square? bool anchor = false; - if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col)) && (board().vcross(row, col) != Utility::Max)) { + if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col)) && !board().vcross(row, col).all()) { if (col == 0) { anchor = true; } @@ -1238,7 +1229,7 @@ Move Generator::generate() { // UVcout << "board().vcross[" << row << "][" << i << "] = " << board().vcross(row, i) << endl; - if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, i)) && (board().vcross(row, i) == Utility::Max)) { + if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, i)) && board().vcross(row, i).all()) { if (i == 0) { k++; } @@ -1265,7 +1256,7 @@ Move Generator::generate() // what defines an anchor square? anchor = false; - if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col)) && (board().hcross(row, col) != Utility::Max)) { + if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col)) && !board().hcross(row, col).all()) { if (row == 0) { anchor = true; } @@ -1286,7 +1277,7 @@ Move Generator::generate() int k = 0; for (int i = row - 1; i >= 0; i--) { // UVcout << "board().vcross[" << row << "][" << i << "] = " << board().vcross(row, i) << endl; - if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(i, col)) && (board().hcross(i, col) == Utility::Max)) { + if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(i, col)) && board().hcross(i, col).all()) { if (i == 0) { k++; } @@ -1322,7 +1313,7 @@ Move Generator::gordongenerate() // generate horizontal plays // what defines an anchor square? bool anchor = false; - if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col)) && (board().vcross(row, col) != Utility::Max)) { + if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col)) && !board().vcross(row, col).all()) { if (col == 0) { if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col + 1))) { anchor = true; @@ -1359,7 +1350,7 @@ Move Generator::gordongenerate() } for (int i = col - k - 1; i >= 0; i--) { // UVcout << "board().vcross[" << row << "][" << i << "] = " << board().vcross(row, i) << endl; - if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, i)) && (board().vcross(row, i) == Utility::Max)) { + if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, i)) && board().vcross(row, i).all()) { if (i == 0) { k++; } @@ -1389,7 +1380,7 @@ Move Generator::gordongenerate() // what defines an anchor square? anchor = false; - if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col)) && (board().hcross(row, col) != Utility::Max)) { + if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row, col)) && !board().hcross(row, col).all()) { if (row == 0) { if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(row + 1, col))) { anchor = true; @@ -1426,7 +1417,7 @@ Move Generator::gordongenerate() } for (int i = row - k - 1; i >= 0; i--) { // UVcout << "board().vcross[" << row << "][" << i << "] = " << board().vcross(row, i) << endl; - if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(i, col)) && (board().hcross(i, col) == Utility::Max)) { + if (!QUACKLE_ALPHABET_PARAMETERS->isSomeLetter(board().letter(i, col)) && board().hcross(i, col).all()) { if (i == 0) { k++; } @@ -1618,7 +1609,7 @@ Move Generator::exchange() { LetterString thrown; for (int j = 0; j < rackSize; j++) - if (i & Utility::Bits[j]) + if (i & (1 << j)) thrown += rack().tiles()[j]; Move move; diff --git a/generator.h b/generator.h index 70908b1..6d207e0 100644 --- a/generator.h +++ b/generator.h @@ -125,7 +125,7 @@ private: void readFromDawg(int index, unsigned int &p, Letter &letter, bool &t, bool &lastchild, bool &british, int &playability) const; bool checksuffix(int i, const LetterString &suffix); - int fitbetween(const LetterString &pre, const LetterString &suf); + LetterBitset fitbetween(const LetterString &pre, const LetterString &suf); void extendright(const LetterString &partial, int i, int row, int col, int edge, int righttiles, bool horizontal); @@ -134,7 +134,7 @@ private: void spit(int i, const LetterString &prefix, int flags); void wordspit(int i, const LetterString &prefix, int flags); - int gaddagFitbetween(const LetterString &pre, const LetterString &suf); + LetterBitset gaddagFitbetween(const LetterString &pre, const LetterString &suf); void gaddagAnagram(const GaddagNode *node, const LetterString &prefix, int flags); void gordongen(int pos, const LetterString &word, const GaddagNode *node); void gordongoon(int pos, char L, LetterString word, const GaddagNode *node); @@ -143,7 +143,7 @@ private: // debug stuff UVString counts2string(); - UVString cross2string(int cross); + UVString cross2string(const LetterBitset &cross); Move best; @@ -167,46 +167,6 @@ private: int m_anchorrow, m_anchorcol; }; -namespace Utility -{ - const int Max = 0xFFFFFFFF; - - const int Bits[32] = - { - 1 << 0, - 1 << 1, - 1 << 2, - 1 << 3, - 1 << 4, - 1 << 5, - 1 << 6, - 1 << 7, - 1 << 8, - 1 << 9, - 1 << 10, - 1 << 11, - 1 << 12, - 1 << 13, - 1 << 14, - 1 << 15, - 1 << 16, - 1 << 17, - 1 << 18, - 1 << 19, - 1 << 20, - 1 << 21, - 1 << 22, - 1 << 23, - 1 << 24, - 1 << 25, - 1 << 26, - 1 << 27, - 1 << 28, - 1 << 29, - 1 << 30, - 1 << 31 - }; -}; inline void Generator::setPosition(const GamePosition &position) { diff --git a/installer.iss b/installer.iss index c022434..23e770f 100644 --- a/installer.iss +++ b/installer.iss @@ -3,14 +3,14 @@ [Setup] AppName=Quackle -AppVerName=Quackle 0.98 +AppVerName=Quackle 1.0 DefaultDirName={pf}\Quackle DefaultGroupName=Quackle ChangesAssociations=yes UninstallDisplayIcon={app}\Quackle.exe [Icons] -Name: "{group}\Quackle 0.98"; Filename: "{app}\Quackle.exe"; WorkingDir: "{app}" +Name: "{group}\Quackle 1.0"; Filename: "{app}\Quackle.exe"; WorkingDir: "{app}" [Registry] Root: HKCU; Subkey: "Software\Quackle.org" diff --git a/lexiconparameters.cpp b/lexiconparameters.cpp index b32f632..f04a941 100644 --- a/lexiconparameters.cpp +++ b/lexiconparameters.cpp @@ -19,15 +19,122 @@ #include <iostream> #include <fstream> + #include "datamanager.h" #include "lexiconparameters.h" #include "uv.h" using namespace Quackle; +class Quackle::V0LexiconInterpreter : public LexiconInterpreter +{ + + virtual void loadDawg(ifstream &file, LexiconParameters &lexparams) + { + int i = 0; + while (!file.eof()) + { + file.read((char*)(lexparams.m_dawg) + i, 7); + i += 7; + } + } + + virtual void loadGaddag(ifstream &file, LexiconParameters &lexparams) + { + int i = 0; + while (!file.eof()) + { + file.read((char*)(lexparams.m_gaddag) + i, 4); + i += 4; + } + } + + virtual void dawgAt(const unsigned char *dawg, int index, unsigned int &p, Letter &letter, bool &t, bool &lastchild, bool &british, int &playability) const + { + index *= 7; + p = (dawg[index] << 16) + (dawg[index + 1] << 8) + (dawg[index + 2]); + letter = dawg[index + 3]; + + t = (letter & 32) != 0; + lastchild = (letter & 64) != 0; + british = !(letter & 128); + letter = (letter & 31) + QUACKLE_FIRST_LETTER; + + playability = (dawg[index + 4] << 16) + (dawg[index + 5] << 8) + (dawg[index + 6]); + } + virtual int versionNumber() const { return 0; } +}; + +class Quackle::V1LexiconInterpreter : public LexiconInterpreter +{ + + virtual void loadDawg(ifstream &file, LexiconParameters &lexparams) + { + int i = 0; + unsigned char bytes[3]; + file.get(); // skip past version byte + file.read(lexparams.m_hash, sizeof(lexparams.m_hash)); + file.read((char*)bytes, 3); + + lexparams.m_utf8Alphabet.resize(file.get()); + for (size_t i = 0; i < lexparams.m_utf8Alphabet.size(); i++) + { + file >> lexparams.m_utf8Alphabet[i]; + file.get(); // separator space + } + while (!file.eof()) + { + file.read((char*)(lexparams.m_dawg) + i, 7); + i += 7; + } + } + + virtual void loadGaddag(ifstream &file, LexiconParameters &lexparams) + { + char hash[16]; + file.get(); // skip past version byte + file.read(hash, sizeof(hash)); + if (memcmp(hash, lexparams.m_hash, sizeof(hash))) + { + // If we're using a v0 DAWG, then ignore the hash + for (size_t i = 0; i < sizeof(lexparams.m_hash); i++) + { + if (lexparams.m_hash[0] != 0) + { + lexparams.unloadGaddag(); // don't use a mismatched gaddag + return; + } + } + } + + size_t i = 0; + while (!file.eof()) + { + file.read((char*)(lexparams.m_gaddag) + i, 4); + i += 4; + } + } + + virtual void dawgAt(const unsigned char *dawg, int index, unsigned int &p, Letter &letter, bool &t, bool &lastchild, bool &british, int &playability) const + { + index *= 7; + p = (dawg[index] << 16) + (dawg[index + 1] << 8) + (dawg[index + 2]); + letter = dawg[index + 3]; + + lastchild = ((letter & 64) != 0); + british = !(letter & 128); + letter = (letter & 63) + QUACKLE_FIRST_LETTER; + + playability = (dawg[index + 4] << 16) + (dawg[index + 5] << 8) + (dawg[index + 6]); + t = (playability != 0); + } + virtual int versionNumber() const { return 1; } +}; + LexiconParameters::LexiconParameters() - : m_dawg(0), m_gaddag(0) + : m_dawg(NULL), m_gaddag(NULL), m_interpreter(NULL) { + memset(m_hash, 0, sizeof(m_hash)); } LexiconParameters::~LexiconParameters() @@ -44,13 +151,15 @@ void LexiconParameters::unloadAll() void LexiconParameters::unloadDawg() { delete[] m_dawg; - m_dawg = 0; + m_dawg = NULL; + delete m_interpreter; + m_interpreter = NULL; } void LexiconParameters::unloadGaddag() { delete[] m_gaddag; - m_gaddag = 0; + m_gaddag = NULL; } void LexiconParameters::loadDawg(const string &filename) @@ -64,14 +173,19 @@ void LexiconParameters::loadDawg(const string &filename) return; } - m_dawg = new unsigned char[7000000]; - - int i = 0; - while (!file.eof()) + char versionByte = file.get(); + m_interpreter = createInterpreter(versionByte); + if (m_interpreter == NULL) { - file.read((char*)(m_dawg) + i, 7); - i += 7; + UVcout << "couldn't open file " << filename.c_str() << endl; + return; } + + file.seekg(0, ios_base::end); + m_dawg = new unsigned char[file.tellg()]; + file.seekg(0, ios_base::beg); + + m_interpreter->loadDawg(file, *this); } void LexiconParameters::loadGaddag(const string &filename) @@ -86,18 +200,74 @@ void LexiconParameters::loadGaddag(const string &filename) return; } - m_gaddag = new unsigned char[40000000]; + char versionByte = file.get(); + if (versionByte < m_interpreter->versionNumber()) + return; + file.seekg(0, ios_base::end); + m_gaddag = new unsigned char[file.tellg()]; + file.seekg(0, ios_base::beg); - int i = 0; - while (!file.eof()) + // must create a local interpreter because dawg/gaddag versions might not match + LexiconInterpreter* interpreter = createInterpreter(versionByte); + if (interpreter != NULL) { - file.read((char*)(m_gaddag) + i, 4); - i += 4; + interpreter->loadGaddag(file, *this); + delete interpreter; } + else + unloadGaddag(); } string LexiconParameters::findDictionaryFile(const string &lexicon) { - return DataManager::self()->findDataFile("lexica", lexicon); + return QUACKLE_DATAMANAGER->findDataFile("lexica", lexicon); +} + +bool LexiconParameters::hasUserDictionaryFile(const string &lexicon) +{ + return QUACKLE_DATAMANAGER->hasUserDataFile("lexica", lexicon); +} + +string LexiconParameters::hashString(bool shortened) const +{ + const char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + string hashStr; + for (size_t i = 0; i < sizeof(m_hash); i++) + { + hashStr.push_back(hex[(m_hash[i] & 0xF0) >> 4]); + hashStr.push_back(hex[m_hash[i] & 0x0F]); + if (shortened && i == 3) + break; + } + return hashStr; +} + +string LexiconParameters::copyrightString() const +{ + string copyrightsFilename = QUACKLE_DATAMANAGER->makeDataFilename("lexica", "copyrights.txt", false); + fstream copyrightsFile(copyrightsFilename, ios_base::in); + while (copyrightsFile.good() && !copyrightsFile.eof()) + { + string line; + getline(copyrightsFile, line); + if (line.size() < 9 || line.find_first_of(':') != 8) + continue; + if (hashString(true).compare(line.substr(0,8)) != 0) + continue; + return line.substr(9, line.size()); + } + return string(); } +LexiconInterpreter* LexiconParameters::createInterpreter(char version) const +{ + switch(version) + { + case 0: + return new V0LexiconInterpreter(); + case 1: + return new V1LexiconInterpreter(); + default: + return NULL; + } +} diff --git a/lexiconparameters.h b/lexiconparameters.h index 612c103..d19f695 100644 --- a/lexiconparameters.h +++ b/lexiconparameters.h @@ -19,14 +19,33 @@ #ifndef QUACKLE_LEXICONPARAMETERS_H #define QUACKLE_LEXICONPARAMETERS_H -#include "alphabetparameters.h" +#include <vector> + #include "gaddag.h" namespace Quackle { +class LexiconParameters; + +class LexiconInterpreter +{ +public: + virtual void loadDawg(ifstream &file, LexiconParameters &lexparams) = 0; + virtual void loadGaddag(ifstream &file, LexiconParameters &lexparams) = 0; + virtual void dawgAt(const unsigned char *dawg, int index, unsigned int &p, Letter &letter, bool &t, bool &lastchild, bool &british, int &playability) const = 0; + virtual int versionNumber() const = 0; + virtual ~LexiconInterpreter() {}; +}; + +class V0LexiconInterpreter; +class V1LexiconInterpreter; + class LexiconParameters { + friend class Quackle::V0LexiconInterpreter; + friend class Quackle::V1LexiconInterpreter; + public: LexiconParameters(); ~LexiconParameters(); @@ -34,68 +53,46 @@ public: void unloadAll(); // true if we have a dawg or a gaddag - bool hasSomething() const; + bool hasSomething() const { return hasDawg() || hasGaddag(); }; // loadDawg unloads the dawg if filename can't be opened void loadDawg(const string &filename); void unloadDawg(); - bool hasDawg() const; + bool hasDawg() const { return m_dawg != NULL; }; + int dawgVersion() const { return m_interpreter->versionNumber(); }; // loadGaddag unloads the gaddag if filename can't be opened void loadGaddag(const string &filename); void unloadGaddag(); - bool hasGaddag() const; + bool hasGaddag() const { return m_gaddag != NULL; }; // finds a file in the lexica data directory static string findDictionaryFile(const string &lexicon); + static bool hasUserDictionaryFile(const string &lexicon); // a convenience field; this is unused by libquackle - string lexiconName() const; - void setLexiconName(const string &name); + string lexiconName() const { return m_lexiconName; }; + void setLexiconName(const string &name) { m_lexiconName = name; }; - unsigned char dawgAt(int index) const; - const GaddagNode *gaddagRoot() const; + void dawgAt(int index, unsigned int &p, Letter &letter, bool &t, bool &lastchild, bool &british, int &playability) const + { + m_interpreter->dawgAt(m_dawg, index, p, letter, t, lastchild, british, playability); + } + const GaddagNode *gaddagRoot() const { return (GaddagNode *) &m_gaddag[0]; }; + + string hashString(bool shortened) const; + string copyrightString() const; protected: unsigned char *m_dawg; unsigned char *m_gaddag; string m_lexiconName; -}; - -inline bool LexiconParameters::hasSomething() const -{ - return hasDawg() || hasGaddag(); -} - -inline bool LexiconParameters::hasDawg() const -{ - return m_dawg != NULL; -} + LexiconInterpreter *m_interpreter; + char m_hash[16]; + vector<string> m_utf8Alphabet; -inline bool LexiconParameters::hasGaddag() const -{ - return m_gaddag != NULL; -} - -inline unsigned char LexiconParameters::dawgAt(int index) const -{ - return m_dawg[index]; -} - -inline const GaddagNode *LexiconParameters::gaddagRoot() const -{ - return (GaddagNode *) &m_gaddag[0]; -} - -inline string LexiconParameters::lexiconName() const -{ - return m_lexiconName; -} - -inline void LexiconParameters::setLexiconName(const string &name) -{ - m_lexiconName = name; -} + LexiconInterpreter* createInterpreter(char version) const; +}; } #endif diff --git a/makegaddag/.gitignore b/makegaddag/.gitignore index f878785..dbd3941 100644 --- a/makegaddag/.gitignore +++ b/makegaddag/.gitignore @@ -4,3 +4,5 @@ Makefile.Debug Makefile.Release debug release +makegaddag + diff --git a/makegaddag/makegaddag.cpp b/makegaddag/makegaddag.cpp index 8e2ffac..ef2c439 100644 --- a/makegaddag/makegaddag.cpp +++ b/makegaddag/makegaddag.cpp @@ -29,79 +29,12 @@ #include <QtCore> -#include <gaddag.h> -#include <quackleio/flexiblealphabet.h> -#include <quackleio/froggetopt.h> -#include <quackleio/util.h> +#include "quackleio/froggetopt.h" +#include "quackleio/gaddagfactory.h" +#include "quackleio/util.h" using namespace std; -class Node { - public: - Quackle::Letter c; - bool t; - vector<Node> children; - int pointer; - bool lastchild; - void pushword(Quackle::LetterString word); - void print(Quackle::LetterString prefix); -}; - -vector< Node* > nodelist; - -void Node::print(Quackle::LetterString prefix) { - if (t) { - //UVcout << QUACKLE_ALPHABET_PARAMETERS->userVisible(prefix)) << endl; - } - - // UVcout << "prefix: " << QUACKLE_ALPHABET_PARAMETERS->userVisible(prefix) << ", children: " << children.size() << endl; - - if (children.size() > 0) { - pointer = nodelist.size(); - children[children.size() - 1].lastchild = true; - } - - for (size_t i = 0; i < children.size(); i++) { - nodelist.push_back(&children[i]); - } - - for (size_t i = 0; i < children.size(); i++) { - children[i].print(prefix + children[i].c); - } -} - - -void Node::pushword(Quackle::LetterString word) { - if (word.length() == 0) { - t = true; - } - else { - Quackle::Letter first = Quackle::String::front(word); - Quackle::LetterString rest = Quackle::String::allButFront(word); - int index = -1; - - // cout << "first: " << first << ", rest: " << rest << endl; - - for (size_t i = 0; i < children.size(); i++) { - if (children[i].c == first) { - index = i; - i = children.size(); - } - } - - if (index == -1) { - Node n; - n.c = first; - n.t = false; - n.pointer = 0; - n.lastchild = false; - children.push_back(n); - index = children.size() - 1; - } - - children[index].pushword(rest); - } -} int main(int argc, char **argv) @@ -127,21 +60,9 @@ int main(int argc, char **argv) if (outputFilename.isNull()) outputFilename = "output.gaddag"; - Quackle::AlphabetParameters *alphas = 0; QString alphabetFile = QString("../data/alphabets/%1.quackle_alphabet").arg(alphabet); UVcout << "Using alphabet file: " << QuackleIO::Util::qstringToString(alphabetFile) << endl; - QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters; - flexure->load(alphabetFile); - alphas = flexure; - - // So the separator is sorted to last. - Quackle::Letter internalSeparatorRepresentation = QUACKLE_FIRST_LETTER + QUACKLE_MAXIMUM_ALPHABET_SIZE; - - Node root; - root.t = false; - root.c = QUACKLE_NULL_MARK; // "_" - root.pointer = 0; - root.lastchild = true; + GaddagFactory factory(QuackleIO::Util::qstringToString(alphabetFile)); QFile file(inputFilename); if (!file.exists()) @@ -159,11 +80,6 @@ int main(int argc, char **argv) QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); - int encodableWords = 0; - int unencodableWords = 0; - - Quackle::WordList gaddagizedWords; - while (!stream.atEnd()) { QString originalQString; @@ -172,115 +88,27 @@ int main(int argc, char **argv) if (stream.atEnd()) break; - UVString originalString = QuackleIO::Util::qstringToString(originalQString); - - UVString leftover; - Quackle::LetterString encodedWord = alphas->encode(originalString, &leftover); - if (leftover.empty()) - { - //for (Quackle::LetterString::iterator it = encodedWord.begin(); it != encodedWord.end(); ++it) - //UVcout << "got encoded letter: " << (int)(*it) << endl; - - ++encodableWords; - - for (unsigned i = 1; i <= encodedWord.length(); i++) { - Quackle::LetterString newword; - - for (int j = i - 1; j >= 0; j--) { - newword.push_back(encodedWord[j]); - } - - if (i < encodedWord.length()) { - newword.push_back(internalSeparatorRepresentation); // "^" - for (unsigned j = i; j < encodedWord.length(); j++) { - newword.push_back(encodedWord[j]); - } - } - gaddagizedWords.push_back(newword); - } - } - else - { - UVcout << "not encodable without leftover: " << originalString << endl; - ++unencodableWords; - } + if (!factory.pushWord(QuackleIO::Util::qstringToString(originalQString))) + UVcout << "not encodable without leftover: " << QuackleIO::Util::qstringToString(originalQString) << endl; } - UVcout << "Sorting " << gaddagizedWords.size () << " words..." << endl; - sort(gaddagizedWords.begin(), gaddagizedWords.end()); + UVcout << "Sorting " << factory.wordCount() << " words..." << endl; + factory.sortWords(); UVcout << "Generating nodes..."; - Quackle::WordList::const_iterator wordsEnd = gaddagizedWords.end(); - for (Quackle::WordList::const_iterator wordsIt = gaddagizedWords.begin(); wordsIt != wordsEnd; ++wordsIt) - { - root.pushword(*wordsIt); - } + factory.generate(); UVcout << "Writing index..."; - - nodelist.push_back(&root); - - root.print(""); - - ofstream out(QuackleIO::Util::qstringToStdString(outputFilename).c_str(), ios::out | ios::binary); - - for (size_t i = 0; i < nodelist.size(); i++) { - // UVcout << nodelist[i]->c << " " << nodelist[i]->pointer << " " << nodelist[i]->t << " " << nodelist[i]->lastchild << endl; - - unsigned int p = (unsigned int)(nodelist[i]->pointer); - if (p != 0) { - p -= i; // offset indexing - } - - char bytes[4]; - unsigned char n1 = (p & 0x00FF0000) >> 16; - /* - UVcout << "byte 1: " << ((p & 0xFF000000) >> 24); - UVcout << ", byte 2: " << ((p & 0x00FF0000) >> 8); - UVcout << ", byte 3: " << ((p & 0x0000FF00) >> 8); - UVcout << ", byte 4: " << ((p & 0x000000FF) >> 0) << endl; - */ - - unsigned char n2 = (p & 0x0000FF00) >> 8; - unsigned char n3 = (p & 0x000000FF) >> 0; - unsigned char n4; - - /* - UVcout << "p: " << p << ", crap: " << (((unsigned int)(n1) << 24) | - ((unsigned int)(n2) << 16) | - ((unsigned int)(n3) << 8)) << endl; - */ - n4 = nodelist[i]->c; - if (n4 == internalSeparatorRepresentation) - n4 = QUACKLE_GADDAG_SEPARATOR; - - if (nodelist[i]->t) { - n4 |= 64; - } - if (nodelist[i]->lastchild) { - n4 |= 128; - } - - /* - UVcout << "p: " << p << endl;; - UVcout << "n4:" << (int)(n4) << - ", n1: " << (int)(n1) << - ", n2: " << (int)(n2) << - ", n3: " << (int)(n3) << endl; - */ - - //bytes[0] = n4; bytes[1] = n1; bytes[2] = n2; bytes[3] = n3; - bytes[0] = n1; bytes[1] = n2; bytes[2] = n3; bytes[3] = n4; - //out.write((const char*) &p, 4); - out.write(bytes, 4); - } + factory.writeIndex(outputFilename.toUtf8().constData()); UVcout << endl; - UVcout << "Wrote " << encodableWords << " words over " << nodelist.size() << " nodes to " << QuackleIO::Util::qstringToString(outputFilename) << "." << endl; + UVcout << "Wrote " << factory.encodableWords() << " words over " << factory.nodeCount() << " nodes to " << QuackleIO::Util::qstringToString(outputFilename) << "." << endl; + + UVcout << "Hash: " << QString(QByteArray(factory.hashBytes(), 16).toHex()).toStdString() << endl; - if (unencodableWords > 0) - UVcout << "There were " << unencodableWords << " words left out." << endl; + if (factory.unencodableWords() > 0) + UVcout << "There were " << factory.unencodableWords() << " words left out." << endl; return 0; } diff --git a/makegaddag/makegaddag.pro b/makegaddag/makegaddag.pro index dfd1259..9895c99 100755 --- a/makegaddag/makegaddag.pro +++ b/makegaddag/makegaddag.pro @@ -5,10 +5,12 @@ CONFIG += release debug { OBJECTS_DIR = obj/debug + QMAKE_LIBDIR += ../lib/debug ../quackleio/lib/debug } release { OBJECTS_DIR = obj/release + QMAKE_LIBDIR += ../lib/release ../quackleio/lib/release } MOC_DIR = moc @@ -19,10 +21,12 @@ MOC_DIR = moc CONFIG += console CONFIG -= app_bundle -LIBS += -lquackleio -lquackle +win32:!win32-g++ { + LIBS += -lquackleio -llibquackle +} else { + LIBS += -lquackleio -lquackle +} -QMAKE_LFLAGS_RELEASE += -L../lib/release -L../quackleio/lib/release -QMAKE_LFLAGS_DEBUG += -L../lib/debug -L../quackleio/lib/debug # Input SOURCES += makegaddag.cpp diff --git a/makeminidawg/makeminidawg.cpp b/makeminidawg/makeminidawg.cpp deleted file mode 100644 index 4973ec8..0000000 --- a/makeminidawg/makeminidawg.cpp +++ /dev/null @@ -1,514 +0,0 @@ -/* - * Quackle -- Crossword game artificial intelligence and analysis tool - * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. - * - * 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 3 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, see <http://www.gnu.org/licenses/>. - */ - -#include <string> -#include <iostream> -#include <iomanip> -#include <vector> -#include <map> - -#include <QtCore> - -#include <quackleio/flexiblealphabet.h> -#include <quackleio/froggetopt.h> -#include <quackleio/util.h> - -using namespace std; - -class Node { -public: - void pushword(Quackle::LetterString word, bool inSmaller, int pb); - void print(Quackle::LetterString prefix); - - int depth(); - int subtreesize(); - int lettersum(); - bool equals(Node &n); - - Quackle::Letter c; - bool t; - bool insmallerdict; - int playability; - - vector<Node> children; - Node* parent; - int pointer; - int location; - int oldpointer; - - bool lastchild; - - bool dexplored; - bool sexplored; - bool lexplored; - - int d; - int s; - int l; - - bool deleted; - Node* cloneof; - bool written; -}; - - -vector< Node* > nodelist; -Node root; - -vector< int > ofdepth[20][100]; - -map< QString, bool> smallerMap; -map< QString, int> playabilityMap; - -bool Node::equals(Node &n) -{ - if (playability != n.playability) - return false; - if (c != n.c) - return false; - if (children.size() != n.children.size()) - return false; - if (t != n.t) - return false; - if (insmallerdict != n.insmallerdict) - return false; - if (l != n.l) - return false; - if (s != n.s) - return false; - if (d != n.d) - return false; - - for (unsigned int i = 0; i < children.size(); i++) - if (!children[i].equals(n.children[i])) - return false; - - return true; -} - -int Node::depth() -{ - if (dexplored) - return d; - - dexplored = true; - - if (children.size() == 0) - { - d = 1; - return d; - } - - int childmax = 0; - - for (unsigned int i = 0; i < children.size(); i++) - { - int d = children[i].depth(); - if (d > childmax) - childmax = d; - } - - d = 1 + childmax; - return d; -} - - -int Node::subtreesize() -{ - if (sexplored) - return s; - - sexplored = true; - - if (children.size() == 0) - { - s = 1; - return s; - } - - int childsum = 0; - - for (unsigned int i = 0; i < children.size(); i++) - { - int s = children[i].subtreesize(); - childsum += s; - } - - s = 1 + childsum; - return s; -} - - -int Node::lettersum() -{ - if (lexplored) - return l; - - lexplored = true; - - int thisletter = c; - - if (children.size() == 0) - { - l = thisletter; - return l; - } - - int childsum = 0; - - for (unsigned int i = 0; i < children.size(); i++) - { - int s = children[i].lettersum(); - childsum += s; - } - - l = thisletter + childsum; - return l; -} - - -void Node::print(Quackle::LetterString prefix) { - - written = true; - - if (t) { - //cout << prefix << endl; - } - - //cout << "prefix: " << prefix << ", children: " << children.size() << ", deleted: " << deleted << ", oldpointer: " << oldpointer << ", nthChild: " << nthChild << endl; - - if (children.size() == 0) - { - //cout << " no children. nothing to do." << endl; - return; - } - - if (!deleted) - { - //cout << " Setting pointer to " << nodelist.size() << " before I push_back the children." << endl; - pointer = nodelist.size(); - } - else - { - pointer = cloneof->pointer; - //cout << " Setting pointer to clone's (" << pointer << ") and not pushing anything." << endl; - } - - if (!deleted) - { - for (unsigned int i = 0; i < children.size(); i++) { - children[i].parent = this; - nodelist.push_back(&children[i]); - } - - for (unsigned int i = 0; i < children.size(); i++) { - if (!children[i].deleted) - children[i].print(prefix + children[i].c); - else if (!children[i].cloneof->written) - children[i].cloneof->print(prefix + children[i].c); - } - } - - if (children.size() > 0) - children[children.size() - 1].lastchild = true; -} - - -void Node::pushword(Quackle::LetterString word, bool inSmaller, int pb) { - if (word.length() == 0) { - t = true; - playability = pb; - insmallerdict = inSmaller; - } - else { - char first = word[0]; - Quackle::LetterString rest = word.substr(1, word.length() - 1); - int index = -1; - - // cout << "first: " << first << ", rest: " << rest << endl; - - for (unsigned int i = 0; i < children.size(); i++) { - if (children[i].c == first) { - index = i; - i = children.size(); - } - } - - if (index == -1) { - Node n; - n.c = first; - n.t = false; - n.playability = 0; - n.insmallerdict = false; - n.pointer = 0; - n.oldpointer = 0; - n.lastchild = false; - children.push_back(n); - index = children.size() - 1; - } - - children[index].pushword(rest, inSmaller, pb); - } - - dexplored = false; - sexplored = false; - lexplored = false; - deleted = false; - written = false; -} - - -void minimize() -{ - nodelist[0]->depth(); - nodelist[0]->subtreesize(); - nodelist[0]->lettersum(); - - for (unsigned int i = 0; i < nodelist.size(); i++) - { - int d = nodelist[i]->d; - if ((d >= 1) & (d <= 20)) - ofdepth[d - 1][nodelist[i]->l%100].push_back(i); - } - - for (int d = 0; d < 20; d++) - { - for (int l = 0; l < 100; l++) - { - //cout << "l: " << l << endl; - if (ofdepth[d][l].size() > 0) - for (vector<int>::iterator it = ofdepth[d][l].begin(); it != ofdepth[d][l].end() - 1; it++) - { - if (!nodelist[(*it)]->deleted) - { - for (vector<int>::iterator jt = it + 1; jt != ofdepth[d][l].end(); jt++) - { - if (!nodelist[(*jt)]->deleted) - { - // cout << "Comparing " << (*it) << " and " << (*jt) << endl; - if (nodelist[(*it)]->equals(nodelist[(*jt)][0])) - { - //cout << "Hey! " << (*it) << " == " << (*jt) << endl; - // ones[l].erase(jt); - nodelist[(*jt)]->deleted = true; - nodelist[(*jt)]->cloneof = nodelist[(*it)]; - } - } - } - } - } - } - - for (unsigned int i = 0; i < nodelist.size(); i++) - { - nodelist[i]->oldpointer = nodelist[i]->pointer; - nodelist[i]->pointer = 0; - nodelist[i]->written = false; - } - - } - - nodelist.clear(); - nodelist.push_back(&root); - root.print(""); - UVcout << "nodelist.size(): " << nodelist.size() << endl; -} - -int main(int argc, char **argv) { - QCoreApplication a(argc, argv); - - GetOpt opts; - QString alphabet; - opts.addOption('a', "alphabet", &alphabet); - if (!opts.parse()) - return 1; - - if (alphabet.isNull()) - alphabet = "english"; - - Quackle::AlphabetParameters *alphas = 0; - QString alphabetFile = QString("../data/alphabets/%1.quackle_alphabet").arg(alphabet); - UVcout << "Using alphabet file: " << QuackleIO::Util::qstringToString(alphabetFile) << endl; - QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters; - flexure->load(alphabetFile); - alphas = flexure; - - root.t = false; - root.insmallerdict = false; - root.playability = 0; - root.c = QUACKLE_BLANK_MARK; - root.pointer = 0; - root.lastchild = true; - - QString smallerDictFilename = "smaller.raw"; - QFile smallerDict(smallerDictFilename); - if (!smallerDict.exists()) - { - UVcout << "smaller dictionary does not exist: " << QuackleIO::Util::qstringToString(smallerDictFilename) << endl; - return false; - } - - if (!smallerDict.open(QIODevice::ReadOnly | QIODevice::Text)) - { - UVcout << "Could not open " << QuackleIO::Util::qstringToString(smallerDictFilename) << endl; - return false; - } - - QTextStream smallerStream(&smallerDict); - smallerStream.setCodec(QTextCodec::codecForName("UTF-8")); - - while (!smallerStream.atEnd()) - { - QString originalQString; - smallerStream >> originalQString; - //UVcout << "this word is in the smaller dictionary: " << QuackleIO::Util::qstringToString(originalQString) << endl; - smallerMap[originalQString] = true; - } - - QString playabilityFilename = "playabilities.raw"; - QFile playability(playabilityFilename); - if (!playability.exists()) - { - UVcout << "playability does not exist: " << QuackleIO::Util::qstringToString(playabilityFilename) << endl; - return false; - } - - if (!playability.open(QIODevice::ReadOnly | QIODevice::Text)) - { - UVcout << "Could not open " << QuackleIO::Util::qstringToString(playabilityFilename) << endl; - return false; - } - - QTextStream playabilityStream(&playability); - playabilityStream.setCodec(QTextCodec::codecForName("UTF-8")); - - while (!playabilityStream.atEnd()) - { - int pb; - playabilityStream >> pb; - QString originalQString; - playabilityStream >> originalQString; - //UVcout << "playability: " << QuackleIO::Util::qstringToString(originalQString) << " " << pb << endl; - playabilityMap[originalQString] = pb; - } - - QString dawgFilename = "dawginput.raw"; - QFile file(dawgFilename); - if (!file.exists()) - { - UVcout << "dawg does not exist: " << QuackleIO::Util::qstringToString(dawgFilename) << endl; - return false; - } - - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - UVcout << "Could not open " << QuackleIO::Util::qstringToString(dawgFilename) << endl; - return false; - } - - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - int encodableWords = 0; - int unencodableWords = 0; - - while (!stream.atEnd()) - { - QString originalQString; - stream >> originalQString; - - bool inSmaller = smallerMap[originalQString]; - int pb = playabilityMap[originalQString]; - - if (stream.atEnd()) - break; - - UVString originalString = QuackleIO::Util::qstringToString(originalQString); - - //UVcout << "read original string: " << originalString; - //if (!inSmaller) UVcout << "#"; - //UVcout << endl; - - UVString leftover; - Quackle::LetterString encodedWord = alphas->encode(originalString, &leftover); - if (leftover.empty()) - { - //for (Quackle::LetterString::iterator it = encodedWord.begin(); it != encodedWord.end(); ++it) - //UVcout << "got encoded letter: " << (int)(*it) << endl; - - root.pushword(encodedWord, inSmaller, pb); - ++encodableWords; - } - else - { - UVcout << "not encodable without leftover: " << originalString << endl; - ++unencodableWords; - } - } - - file.close(); - delete alphas; - - UVcout << "encodable words: " << encodableWords << ", unencodable words: " << unencodableWords << endl; - - nodelist.push_back(&root); - root.print(""); - UVcout << "nodelist.size(): " << nodelist.size() << endl; - - minimize(); - - ofstream out("output.dawg", ios::out | ios::binary); - - for (unsigned int i = 0; i < nodelist.size(); i++) { - //cout << nodelist[i]->c << " " << nodelist[i]->pointer << " " << nodelist[i]->t << " " << nodelist[i]->lastchild << endl; - Node* n = nodelist[i]; - unsigned int p; - if (nodelist[i]->deleted) - { - p = (unsigned int)(nodelist[i]->cloneof->pointer); - // n = nodelist[i]->cloneof; - } - else - p = (unsigned int)(nodelist[i]->pointer); - - char bytes[7]; - unsigned char n1 = (p & 0x00FF0000) >> 16; - unsigned char n2 = (p & 0x0000FF00) >> 8; - unsigned char n3 = (p & 0x000000FF); - unsigned char n4 = n->c - QUACKLE_FIRST_LETTER; - - unsigned int pb = n->playability; - unsigned char n5 = (pb & 0x00FF0000) >> 16; - unsigned char n6 = (pb & 0x0000FF00) >> 8; - unsigned char n7 = (pb & 0x000000FF); - - if (n->t) { - n4 |= 32; - } - if (n->lastchild) { - n4 |= 64; - } - if (n->insmallerdict) { - n4 |= 128; - } - - bytes[0] = n1; bytes[1] = n2; bytes[2] = n3; bytes[3] = n4; - bytes[4] = n5; bytes[5] = n6; bytes[6] = n7; - out.write(bytes, 7); - } -} diff --git a/makeminidawg/makeminidawg.pro b/makeminidawg/makeminidawg.pro index 8af3bb2..e728a8a 100644 --- a/makeminidawg/makeminidawg.pro +++ b/makeminidawg/makeminidawg.pro @@ -12,20 +12,24 @@ CONFIG -= app_bundle debug { OBJECTS_DIR = obj/debug + QMAKE_LIBDIR += ../lib/debug ../quackleio/lib/debug } release { OBJECTS_DIR = obj/release + QMAKE_LIBDIR += ../lib/release ../quackleio/lib/release } -LIBS += -lquackleio -lquackle +win32:!win32-g++ { + LIBS += -lquackleio -llibquackle +} else { + LIBS += -lquackleio -lquackle +} -QMAKE_LFLAGS_RELEASE += -L../lib/release -L../quackleio/lib/release -QMAKE_LFLAGS_DEBUG += -L../lib/debug -L../quackleio/lib/debug # Input -HEADERS += minidawgmaker.h -SOURCES += minidawgmaker.cpp makeminidawgmain.cpp +HEADERS += +SOURCES += makeminidawgmain.cpp macx-g++ { diff --git a/makeminidawg/makeminidawgmain.cpp b/makeminidawg/makeminidawgmain.cpp index 82871be..dfd3c82 100644 --- a/makeminidawg/makeminidawgmain.cpp +++ b/makeminidawg/makeminidawgmain.cpp @@ -16,16 +16,132 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <QCoreApplication> -#include <QStringList> +#include <map> +#include <QtCore> -#include "minidawgmaker.h" +#include "quackleio/dawgfactory.h" +#include "quackleio/froggetopt.h" +#include "quackleio/util.h" + +std::map< QString, bool> smallerMap; +std::map< QString, int> playabilityMap; int main(int argc, char **argv) { QCoreApplication a(argc, argv); - MiniDawgMaker maker; - return maker.executeFromArguments(); + GetOpt opts; + QString alphabet; + opts.addOption('a', "alphabet", &alphabet); + if (!opts.parse()) + return 1; + + if (alphabet.isNull()) + alphabet = "english"; + + QString alphabetFile = QString("../data/alphabets/%1.quackle_alphabet").arg(alphabet); + UVcout << "Using alphabet file: " << QuackleIO::Util::qstringToString(alphabetFile) << endl; + + DawgFactory factory(alphabetFile); + + + QString smallerDictFilename = "smaller.raw"; + QFile smallerDict(smallerDictFilename); + if (!smallerDict.exists()) + { + UVcout << "smaller dictionary does not exist: " << QuackleIO::Util::qstringToString(smallerDictFilename) << endl; + return false; + } + + if (!smallerDict.open(QIODevice::ReadOnly | QIODevice::Text)) + { + UVcout << "Could not open " << QuackleIO::Util::qstringToString(smallerDictFilename) << endl; + return false; + } + + QTextStream smallerStream(&smallerDict); + smallerStream.setCodec(QTextCodec::codecForName("UTF-8")); + + while (!smallerStream.atEnd()) + { + QString originalQString; + smallerStream >> originalQString; + //UVcout << "this word is in the smaller dictionary: " << QuackleIO::Util::qstringToString(originalQString) << endl; + smallerMap[originalQString] = true; + } + + QString playabilityFilename = "playabilities.raw"; + QFile playability(playabilityFilename); + if (!playability.exists()) + { + UVcout << "playability does not exist: " << QuackleIO::Util::qstringToString(playabilityFilename) << endl; + return false; + } + + if (!playability.open(QIODevice::ReadOnly | QIODevice::Text)) + { + UVcout << "Could not open " << QuackleIO::Util::qstringToString(playabilityFilename) << endl; + return false; + } + + QTextStream playabilityStream(&playability); + playabilityStream.setCodec(QTextCodec::codecForName("UTF-8")); + + while (!playabilityStream.atEnd()) + { + int pb; + playabilityStream >> pb; + QString originalQString; + playabilityStream >> originalQString; + //UVcout << "playability: " << QuackleIO::Util::qstringToString(originalQString) << " " << pb << endl; + playabilityMap[originalQString] = pb; + } + + QString dawgFilename = "dawginput.raw"; + QFile file(dawgFilename); + if (!file.exists()) + { + UVcout << "dawg does not exist: " << QuackleIO::Util::qstringToString(dawgFilename) << endl; + return false; + } + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + UVcout << "Could not open " << QuackleIO::Util::qstringToString(dawgFilename) << endl; + return false; + } + + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + while (!stream.atEnd()) + { + QString word; + stream >> word; + + bool inSmaller = smallerMap[word]; + int pb = playabilityMap[word]; + + if (stream.atEnd()) + break; + + if (!factory.pushWord(QuackleIO::Util::qstringToString(word), inSmaller, pb)) + UVcout << "not encodable without leftover: " << QuackleIO::Util::qstringToString(word) << endl; + } + + file.close(); + + UVcout << "encodable words: " << factory.encodableWords() << ", unencodable words: " << factory.unencodableWords() << endl; + + UVcout << "nodelist.size(): " << factory.nodeCount() << endl; + + factory.generate(); + UVcout << "Compressed nodelist.size(): " << factory.nodeCount() << endl; + + UVcout << "Hash: " << QString(QByteArray(factory.hashBytes(), 16).toHex()).toStdString() << endl; + + factory.writeIndex("output.dawg"); + + return 0; } diff --git a/makeminidawg/minidawgmaker.cpp b/makeminidawg/minidawgmaker.cpp deleted file mode 100644 index fa4df2f..0000000 --- a/makeminidawg/minidawgmaker.cpp +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Quackle -- Crossword game artificial intelligence and analysis tool - * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. - * - * 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 3 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, see <http://www.gnu.org/licenses/>. - */ - -#include <string> -#include <iostream> -#include <iomanip> -#include <vector> -#include <map> - -#include <QtCore> - -#include <quackleio/flexiblealphabet.h> -#include <quackleio/froggetopt.h> -#include <quackleio/util.h> - -#include "minidawgmaker.h" - -using namespace std; - -class Node { -public: - void pushword(Quackle::LetterString word, bool inSmaller, int pb); - void print(Quackle::LetterString prefix); - - int depth(); - int subtreesize(); - int lettersum(); - bool equals(Node &n); - - Quackle::Letter c; - bool t; - bool insmallerdict; - int playability; - - vector<Node> children; - Node* parent; - int pointer; - int location; - int oldpointer; - - bool lastchild; - - bool dexplored; - bool sexplored; - bool lexplored; - - int d; - int s; - int l; - - bool deleted; - Node* cloneof; - bool written; -}; - - -vector< Node* > nodelist; -Node root; - -vector< int > ofdepth[20][100]; - -map< QString, bool> smallerMap; -map< QString, int> playabilityMap; - -bool Node::equals(Node &n) -{ - if (playability != n.playability) - return false; - if (c != n.c) - return false; - if (children.size() != n.children.size()) - return false; - if (t != n.t) - return false; - if (insmallerdict != n.insmallerdict) - return false; - if (l != n.l) - return false; - if (s != n.s) - return false; - if (d != n.d) - return false; - - for (unsigned int i = 0; i < children.size(); i++) - if (!children[i].equals(n.children[i])) - return false; - - return true; -} - -int Node::depth() -{ - if (dexplored) - return d; - - dexplored = true; - - if (children.size() == 0) - { - d = 1; - return d; - } - - int childmax = 0; - - for (unsigned int i = 0; i < children.size(); i++) - { - int d = children[i].depth(); - if (d > childmax) - childmax = d; - } - - d = 1 + childmax; - return d; -} - - -int Node::subtreesize() -{ - if (sexplored) - return s; - - sexplored = true; - - if (children.size() == 0) - { - s = 1; - return s; - } - - int childsum = 0; - - for (unsigned int i = 0; i < children.size(); i++) - { - int s = children[i].subtreesize(); - childsum += s; - } - - s = 1 + childsum; - return s; -} - - -int Node::lettersum() -{ - if (lexplored) - return l; - - lexplored = true; - - int thisletter = c; - - if (children.size() == 0) - { - l = thisletter; - return l; - } - - int childsum = 0; - - for (unsigned int i = 0; i < children.size(); i++) - { - int s = children[i].lettersum(); - childsum += s; - } - - l = thisletter + childsum; - return l; -} - - -void Node::print(Quackle::LetterString prefix) { - - written = true; - - if (t) { - //cout << prefix << endl; - } - - //cout << "prefix: " << prefix << ", children: " << children.size() << ", deleted: " << deleted << ", oldpointer: " << oldpointer << ", nthChild: " << nthChild << endl; - - if (children.size() == 0) - { - //cout << " no children. nothing to do." << endl; - return; - } - - if (!deleted) - { - //cout << " Setting pointer to " << nodelist.size() << " before I push_back the children." << endl; - pointer = nodelist.size(); - } - else - { - pointer = cloneof->pointer; - //cout << " Setting pointer to clone's (" << pointer << ") and not pushing anything." << endl; - } - - if (!deleted) - { - for (unsigned int i = 0; i < children.size(); i++) { - children[i].parent = this; - nodelist.push_back(&children[i]); - } - - for (unsigned int i = 0; i < children.size(); i++) { - if (!children[i].deleted) - children[i].print(prefix + children[i].c); - else if (!children[i].cloneof->written) - children[i].cloneof->print(prefix + children[i].c); - } - } - - if (children.size() > 0) - children[children.size() - 1].lastchild = true; -} - - -void Node::pushword(Quackle::LetterString word, bool inSmaller, int pb) { - if (word.length() == 0) { - t = true; - playability = pb; - insmallerdict = inSmaller; - } - else { - char first = word[0]; - Quackle::LetterString rest = word.substr(1, word.length() - 1); - int index = -1; - - // cout << "first: " << first << ", rest: " << rest << endl; - - for (unsigned int i = 0; i < children.size(); i++) { - if (children[i].c == first) { - index = i; - i = children.size(); - } - } - - if (index == -1) { - Node n; - n.c = first; - n.t = false; - n.playability = 0; - n.insmallerdict = false; - n.pointer = 0; - n.oldpointer = 0; - n.lastchild = false; - children.push_back(n); - index = children.size() - 1; - } - - children[index].pushword(rest, inSmaller, pb); - } - - dexplored = false; - sexplored = false; - lexplored = false; - deleted = false; - written = false; -} - - -void minimize() -{ - nodelist[0]->depth(); - nodelist[0]->subtreesize(); - nodelist[0]->lettersum(); - - for (unsigned int i = 0; i < nodelist.size(); i++) - { - int d = nodelist[i]->d; - if ((d >= 1) & (d <= 20)) - ofdepth[d - 1][nodelist[i]->l%100].push_back(i); - } - - for (int d = 0; d < 20; d++) - { - for (int l = 0; l < 100; l++) - { - //cout << "l: " << l << endl; - if (ofdepth[d][l].size() > 0) - for (vector<int>::iterator it = ofdepth[d][l].begin(); it != ofdepth[d][l].end() - 1; it++) - { - if (!nodelist[(*it)]->deleted) - { - for (vector<int>::iterator jt = it + 1; jt != ofdepth[d][l].end(); jt++) - { - if (!nodelist[(*jt)]->deleted) - { - // cout << "Comparing " << (*it) << " and " << (*jt) << endl; - if (nodelist[(*it)]->equals(nodelist[(*jt)][0])) - { - //cout << "Hey! " << (*it) << " == " << (*jt) << endl; - // ones[l].erase(jt); - nodelist[(*jt)]->deleted = true; - nodelist[(*jt)]->cloneof = nodelist[(*it)]; - } - } - } - } - } - } - - for (unsigned int i = 0; i < nodelist.size(); i++) - { - nodelist[i]->oldpointer = nodelist[i]->pointer; - nodelist[i]->pointer = 0; - nodelist[i]->written = false; - } - - } - - nodelist.clear(); - nodelist.push_back(&root); - root.print(""); - UVcout << "nodelist.size(): " << nodelist.size() << endl; -} - -int MiniDawgMaker::executeFromArguments() -{ - GetOpt opts; - QString alphabet; - opts.addOption('a', "alphabet", &alphabet); - if (!opts.parse()) - return 1; - - if (alphabet.isNull()) - alphabet = "english"; - - Quackle::AlphabetParameters *alphas = 0; - QString alphabetFile = QString("../data/alphabets/%1.quackle_alphabet").arg(alphabet); - UVcout << "Using alphabet file: " << QuackleIO::Util::qstringToString(alphabetFile) << endl; - QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters; - flexure->load(alphabetFile); - alphas = flexure; - - root.t = false; - root.insmallerdict = false; - root.playability = 0; - root.c = QUACKLE_BLANK_MARK; - root.pointer = 0; - root.lastchild = true; - - QString smallerDictFilename = "smaller.raw"; - QFile smallerDict(smallerDictFilename); - if (!smallerDict.exists()) - { - UVcout << "smaller dictionary does not exist: " << QuackleIO::Util::qstringToString(smallerDictFilename) << endl; - return false; - } - - if (!smallerDict.open(QIODevice::ReadOnly | QIODevice::Text)) - { - UVcout << "Could not open " << QuackleIO::Util::qstringToString(smallerDictFilename) << endl; - return false; - } - - QTextStream smallerStream(&smallerDict); - smallerStream.setCodec(QTextCodec::codecForName("UTF-8")); - - while (!smallerStream.atEnd()) - { - QString originalQString; - smallerStream >> originalQString; - //UVcout << "this word is in the smaller dictionary: " << QuackleIO::Util::qstringToString(originalQString) << endl; - smallerMap[originalQString] = true; - } - - QString playabilityFilename = "playabilities.raw"; - QFile playability(playabilityFilename); - if (!playability.exists()) - { - UVcout << "playability does not exist: " << QuackleIO::Util::qstringToString(playabilityFilename) << endl; - return false; - } - - if (!playability.open(QIODevice::ReadOnly | QIODevice::Text)) - { - UVcout << "Could not open " << QuackleIO::Util::qstringToString(playabilityFilename) << endl; - return false; - } - - QTextStream playabilityStream(&playability); - playabilityStream.setCodec(QTextCodec::codecForName("UTF-8")); - - while (!playabilityStream.atEnd()) - { - int pb; - playabilityStream >> pb; - QString originalQString; - playabilityStream >> originalQString; - //UVcout << "playability: " << QuackleIO::Util::qstringToString(originalQString) << " " << pb << endl; - playabilityMap[originalQString] = pb; - } - - QString dawgFilename = "dawginput.raw"; - QFile file(dawgFilename); - if (!file.exists()) - { - UVcout << "dawg does not exist: " << QuackleIO::Util::qstringToString(dawgFilename) << endl; - return false; - } - - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - UVcout << "Could not open " << QuackleIO::Util::qstringToString(dawgFilename) << endl; - return false; - } - - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - int encodableWords = 0; - int unencodableWords = 0; - - while (!stream.atEnd()) - { - QString originalQString; - stream >> originalQString; - - bool inSmaller = smallerMap[originalQString]; - int pb = playabilityMap[originalQString]; - - if (stream.atEnd()) - break; - - UVString originalString = QuackleIO::Util::qstringToString(originalQString); - - //UVcout << "read original string: " << originalString; - //if (!inSmaller) UVcout << "#"; - //UVcout << endl; - - UVString leftover; - Quackle::LetterString encodedWord = alphas->encode(originalString, &leftover); - if (leftover.empty()) - { - //for (Quackle::LetterString::iterator it = encodedWord.begin(); it != encodedWord.end(); ++it) - //UVcout << "got encoded letter: " << (int)(*it) << endl; - - root.pushword(encodedWord, inSmaller, pb); - ++encodableWords; - } - else - { - UVcout << "not encodable without leftover: " << originalString << endl; - ++unencodableWords; - } - } - - file.close(); - delete alphas; - - UVcout << "encodable words: " << encodableWords << ", unencodable words: " << unencodableWords << endl; - - nodelist.push_back(&root); - root.print(""); - UVcout << "nodelist.size(): " << nodelist.size() << endl; - - minimize(); - - ofstream out("output.dawg", ios::out | ios::binary); - - for (unsigned int i = 0; i < nodelist.size(); i++) { - //cout << nodelist[i]->c << " " << nodelist[i]->pointer << " " << nodelist[i]->t << " " << nodelist[i]->lastchild << endl; - Node* n = nodelist[i]; - unsigned int p; - if (nodelist[i]->deleted) - { - p = (unsigned int)(nodelist[i]->cloneof->pointer); - // n = nodelist[i]->cloneof; - } - else - p = (unsigned int)(nodelist[i]->pointer); - - char bytes[7]; - unsigned char n1 = (p & 0x00FF0000) >> 16; - unsigned char n2 = (p & 0x0000FF00) >> 8; - unsigned char n3 = (p & 0x000000FF); - unsigned char n4 = n->c - QUACKLE_FIRST_LETTER; - - unsigned int pb = n->playability; - unsigned char n5 = (pb & 0x00FF0000) >> 16; - unsigned char n6 = (pb & 0x0000FF00) >> 8; - unsigned char n7 = (pb & 0x000000FF); - - if (n->t) { - n4 |= 32; - } - if (n->lastchild) { - n4 |= 64; - } - if (n->insmallerdict) { - n4 |= 128; - } - - bytes[0] = n1; bytes[1] = n2; bytes[2] = n3; bytes[3] = n4; - bytes[4] = n5; bytes[5] = n6; bytes[6] = n7; - out.write(bytes, 7); - } - return 0; -} diff --git a/makeminidawg/minidawgmaker.h b/makeminidawg/minidawgmaker.h deleted file mode 100644 index 7c8deee..0000000 --- a/makeminidawg/minidawgmaker.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Quackle -- Crossword game artificial intelligence and analysis tool - * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. - * - * 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 3 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, see <http://www.gnu.org/licenses/>. - */ - -#ifndef QUACKLE_MINIDAWGMAKER_H -#define QUACKLE_MINIDAWGMAKER_H - -#include <QStringList> - -class MiniDawgMaker -{ -public: - MiniDawgMaker() {} - ~MiniDawgMaker() {} - - // parse and execute commands specified on command line - int executeFromArguments(); - - // TOOD further classify this code -}; - -#endif - diff --git a/quacker/Info.plist b/quacker/Info.plist index 015f161..854c8a9 100644 --- a/quacker/Info.plist +++ b/quacker/Info.plist @@ -26,7 +26,7 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>0.98</string> + <string>1.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> diff --git a/quacker/boardsetupdialog.cpp b/quacker/boardsetupdialog.cpp index 7198b17..2bfd5ff 100644 --- a/quacker/boardsetupdialog.cpp +++ b/quacker/boardsetupdialog.cpp @@ -33,7 +33,7 @@ BoardSetupDialog::BoardSetupDialog(QWidget *parent) : QDialog(parent) { - resize(600,450); + resize(700,550); setSizeGripEnabled(true); // construct the board @@ -58,6 +58,7 @@ BoardSetupDialog::BoardSetupDialog(QWidget *parent) : QDialog(parent) m_saveChanges = new QPushButton(tr("&Save Changes")); m_cancel = new QPushButton(tr("&Cancel")); m_undoAll = new QPushButton(tr("&Undo All Changes")); + m_deleteBoard = new QPushButton(tr("&Delete Board")); QVBoxLayout * superLayout = new QVBoxLayout; Geometry::setupFramedLayout(superLayout); @@ -96,6 +97,8 @@ BoardSetupDialog::BoardSetupDialog(QWidget *parent) : QDialog(parent) leftSideLayout->addWidget(dimensionGroup); leftSideLayout->addWidget(symmetryGroup); leftSideLayout->addWidget(m_undoAll); + if (!m_originalName.isEmpty()) + leftSideLayout->addWidget(m_deleteBoard); leftSideLayout->addStretch(); mainLayout->addLayout(leftSideLayout); @@ -118,6 +121,7 @@ BoardSetupDialog::BoardSetupDialog(QWidget *parent) : QDialog(parent) connect(m_saveChanges, SIGNAL(clicked()), this, SLOT(accept())); connect(m_cancel, SIGNAL(clicked()), this, SLOT(reject())); connect(m_undoAll, SIGNAL(clicked()), this, SLOT(undoAllChanges())); + connect(m_deleteBoard, SIGNAL(clicked()), this, SLOT(deleteBoard())); connect(m_horizontalSymmetry, SIGNAL(stateChanged(int)), this, SLOT(symmetryChanged())); connect(m_verticalSymmetry, SIGNAL(stateChanged(int)), this, SLOT(symmetryChanged())); connect(m_diagonalSymmetry, SIGNAL(stateChanged(int)), this, SLOT(symmetryChanged())); @@ -239,3 +243,20 @@ void BoardSetupDialog::undoAllChanges() QUACKLE_DATAMANAGER->setBoardParameters(Quackle::BoardParameters::Deserialize(boardStream)); parametersChanged(QString()); } + +void BoardSetupDialog::deleteBoard() +{ + QString message = "Do you really want to delete the game board \""; + message += m_originalName; + message += "\"?"; + if (QMessageBox::warning(NULL, QString("Confirm Deletion"), message, + QMessageBox::Yes | QMessageBox::Default, + QMessageBox::No | QMessageBox::Escape) == QMessageBox::Yes) + { + CustomQSettings settings; + settings.beginGroup("quackle/boardparameters"); + settings.remove(m_originalName); + QDialog::reject(); + } +} + diff --git a/quacker/boardsetupdialog.h b/quacker/boardsetupdialog.h index e5432cc..0176d1a 100644 --- a/quacker/boardsetupdialog.h +++ b/quacker/boardsetupdialog.h @@ -48,6 +48,7 @@ protected slots: void parametersChanged(const QString &unused); void symmetryChanged(); void undoAllChanges(); + void deleteBoard(); private: QCheckBox *m_horizontalSymmetry; @@ -62,6 +63,7 @@ private: QPushButton *m_saveChanges; QPushButton *m_cancel; QPushButton *m_undoAll; + QPushButton *m_deleteBoard; Quackle::Game m_game; BoardSetupFrame * m_boardFrame; diff --git a/quacker/graphicalreporter.cpp b/quacker/graphicalreporter.cpp index f7c9ce7..2fb0842 100644 --- a/quacker/graphicalreporter.cpp +++ b/quacker/graphicalreporter.cpp @@ -227,5 +227,6 @@ void GraphicalReporter::openIndex() m_indexStream.setDevice(&m_indexFile); } + m_indexStream.setCodec(QTextCodec::codecForName("UTF-8")); } diff --git a/quacker/lexicondialog.cpp b/quacker/lexicondialog.cpp new file mode 100644 index 0000000..91eb676 --- /dev/null +++ b/quacker/lexicondialog.cpp @@ -0,0 +1,306 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include <sstream> +#include <QtGui> +#include <datamanager.h> +#include <quackleio/util.h> + +#include "lexicondialog.h" +#include "customqsettings.h" +#include "settings.h" +#include "geometry.h" +#include "quackleio/dawgfactory.h" + +class FileNameValidator : public QValidator +{ +public: + virtual State validate(QString &input, int &pos) const + { + for (QString::ConstIterator i = input.begin(); i != input.end(); ++i) + if (*i == '/' || *i == '?' || *i == '\\' || *i == '*') + return Invalid; + return Acceptable; + } +}; + +LexiconDialog::LexiconDialog(QWidget *parent, const QString &originalName) : QDialog(parent), + m_deleted(false), m_wordFactory(NULL) +{ + m_originalName = originalName; + + resize(450,350); + + // construct the UI elements + m_lexiconName = new QLineEdit(); + m_alphabetCombo = new QComboBox(); + m_fileNameValidator = new FileNameValidator(); + + m_addWordsFromFile = new QPushButton(tr("Add words from &file...")); + m_clearAllWords = new QPushButton(tr("Clear &words and start again")); + + + m_lexiconInformation = new QLabel(""); + m_lexiconInformation->setWordWrap(true); + m_lexiconInformation->setTextInteractionFlags(Qt::TextBrowserInteraction); + + m_saveChanges = new QPushButton(tr("&Save Changes")); + m_cancel = new QPushButton(tr("&Cancel")); + m_deleteLexicon = new QPushButton(tr("&Delete Lexicon")); + + QLabel * lexiconNameLabel = new QLabel(tr("&Lexicon name:")); + QLabel * alphabetLabel = new QLabel(tr("&Alphabet:")); + lexiconNameLabel->setBuddy(m_lexiconName); + alphabetLabel->setBuddy(m_alphabetCombo); + + QVBoxLayout * layout = new QVBoxLayout; + Geometry::setupFramedLayout(layout); + QHBoxLayout * lexiconRow = new QHBoxLayout; + Geometry::setupInnerLayout(lexiconRow); + QHBoxLayout * addRemoveWordsRow = new QHBoxLayout; + Geometry::setupInnerLayout(addRemoveWordsRow); + QHBoxLayout * buttonRow = new QHBoxLayout; + Geometry::setupInnerLayout(buttonRow); + QGroupBox * lexiconInformationGroup = new QGroupBox(tr("Lexicon information")); + QVBoxLayout * lexiconInformationLayout = new QVBoxLayout(lexiconInformationGroup); + + // build the layout + lexiconRow->addWidget(lexiconNameLabel); + lexiconRow->addWidget(m_lexiconName); + lexiconRow->addStretch(); + lexiconRow->addWidget(alphabetLabel); + lexiconRow->addWidget(m_alphabetCombo); + + addRemoveWordsRow->addWidget(m_addWordsFromFile); + addRemoveWordsRow->addWidget(m_clearAllWords); + + lexiconInformationLayout->addWidget(m_lexiconInformation); + + buttonRow->addWidget(m_deleteLexicon); + buttonRow->addStretch(); + buttonRow->addWidget(m_cancel); + buttonRow->addWidget(m_saveChanges); + + layout->addLayout(lexiconRow); + layout->addLayout(addRemoveWordsRow); + layout->addWidget(lexiconInformationGroup); + layout->addStretch(); + layout->addLayout(buttonRow); + + setLayout(layout); + m_saveChanges->setDefault(true); + + // hook up signals and slots + connect(m_lexiconName, SIGNAL(textEdited(const QString &)), this, SLOT(parametersChanged(const QString &))); + connect(m_addWordsFromFile, SIGNAL(clicked()), this, SLOT(addWordsFromFile())); + connect(m_clearAllWords, SIGNAL(clicked()), this, SLOT(loadOriginalDictionary())); + connect(m_saveChanges, SIGNAL(clicked()), this, SLOT(accept())); + connect(m_cancel, SIGNAL(clicked()), this, SLOT(reject())); + connect(m_deleteLexicon, SIGNAL(clicked()), this, SLOT(deleteLexicon())); + connect(m_alphabetCombo, SIGNAL(activated(const QString &)), this, SLOT(alphabetChanged(const QString &))); + + setWindowTitle(tr("Configure Lexicon - Quackle")); + + Settings::populateComboFromFilenames(m_alphabetCombo, "alphabets", ".quackle_alphabet", ""); + m_alphabetCombo->setCurrentIndex(m_alphabetCombo->findText(QuackleIO::Util::stdStringToQString(QUACKLE_ALPHABET_PARAMETERS->alphabetName()))); + alphabetChanged(m_alphabetCombo->currentText()); + + m_lexiconName->setValidator(m_fileNameValidator); + m_lexiconName->setText(m_originalName); + + loadOriginalDictionary(); +} + +LexiconDialog::~LexiconDialog() +{ + delete m_fileNameValidator; + delete m_wordFactory; +} + +void LexiconDialog::deleteLexicon() +{ + string lexiconNameStr = m_originalName.toStdString(); + string filename = QUACKLE_DATAMANAGER->makeDataFilename("lexica", lexiconNameStr + ".dawg", true); + QFile(QString::fromStdString(filename)).remove(); + m_deleted = true; + QDialog::accept(); +} + +void LexiconDialog::addWordsFromFile() +{ + QFileDialog browser(this, tr("Choose a file containing words to be added to the lexicon...")); + QStringList filters; + filters << "Dictionary files (*.txt *.dawg *.raw)" + << "All files (*.*)"; + browser.setNameFilters(filters); + browser.setFileMode(QFileDialog::ExistingFiles); + browser.exec(); + + QStringList files = browser.selectedFiles(); + for (QList<QString>::const_iterator it = files.begin(); it != files.end(); it++) + { + if (it->endsWith(".dawg", Qt::CaseInsensitive)) + addWordsFromDawgFile(*it); + else + addWordsFromTextFile(*it); + } + updateLexiconInformation(); +} + +void LexiconDialog::alphabetChanged(const QString &alphabet) +{ + delete m_wordFactory; + m_wordFactory = NULL; + updateLexiconInformation(); + m_alphabetFileName = QString::fromStdString(AlphabetParameters::findAlphabetFile(QuackleIO::Util::qstringToStdString(alphabet))); +} + +void LexiconDialog::addWordsFromDawgFile(const QString &dawgfile) +{ + if (!m_wordFactory) + m_wordFactory = new DawgFactory(m_alphabetFileName); + LexiconParameters lexParams; + lexParams.loadDawg(QuackleIO::Util::qstringToStdString(dawgfile)); + if (!lexParams.hasDawg()) + return; + + Quackle::LetterString word; + + addWordsFromDawgRecursive(lexParams, word, 1); +} + +void LexiconDialog::addWordsFromDawgRecursive(const LexiconParameters &lexParams, Quackle::LetterString &word, int index) +{ + unsigned int p; + Quackle::Letter letter; + bool t; + bool lastchild; + bool british; + int playability; + + do + { + lexParams.dawgAt(index, p, letter, t, lastchild, british, playability); + word.push_back(letter); + if (t) + m_wordFactory->pushWord(word, !british, playability); + if (p) + addWordsFromDawgRecursive(lexParams, word, p); + index++; + word.pop_back(); + } while (!lastchild); +} + +void LexiconDialog::addWordsFromTextFile(const QString &textFile) +{ + if (!m_wordFactory) + m_wordFactory = new DawgFactory(m_alphabetFileName); + + QFile file(textFile); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + QString word; + while (!stream.atEnd()) + { + stream >> word; + word = word.trimmed().toUpper(); + if (word.isEmpty()) + continue; + QChar firstChar = word[0]; + if (firstChar < 'A') + continue; // allows the usage of most punctuation characters as comments + int playability = 0; + for (int i = word.size() - 1; i > 0; i--) + { + if (word[i].isDigit()) + playability = playability * 10 + word[i].digitValue(); + } + m_wordFactory->pushWord(QuackleIO::Util::qstringToString(word), true, playability); + } +} + +void LexiconDialog::loadOriginalDictionary() +{ + delete m_wordFactory; + m_wordFactory = NULL; + string dawgFileName = m_originalName.toStdString() + ".dawg"; + QString dawgFullFileName; + if (!m_originalName.isEmpty()) + dawgFullFileName = QString::fromStdString(Quackle::LexiconParameters::findDictionaryFile(dawgFileName)); + + if (!dawgFullFileName.isEmpty()) + { + m_deleteLexicon->setEnabled(Quackle::LexiconParameters::hasUserDictionaryFile(dawgFileName)); + m_lexiconInformation->setText(tr("Loading dictionary...")); + show(); + qApp->processEvents(); + addWordsFromDawgFile(dawgFullFileName); + } + else + m_deleteLexicon->setEnabled(false); + + updateLexiconInformation(true); +} + +void LexiconDialog::accept() +{ + string lexiconNameStr = m_lexiconName->text().toStdString(); + string filename = QUACKLE_DATAMANAGER->makeDataFilename("lexica", lexiconNameStr + ".dawg", true); + m_lexiconInformation->setText(tr("Compressing and writing dictionary file...\nThis may take a few minutes.")); + qApp->processEvents(); + m_wordFactory->generate(); + m_lexiconInformation->setText(tr("Writing dictionary file...")); + qApp->processEvents(); + m_wordFactory->writeIndex(filename); + m_finalLexiconName = m_lexiconName->text(); + QDialog::accept(); +} + +void LexiconDialog::updateLexiconInformation(bool firstTime) +{ + QByteArray hash = m_wordFactory ? QByteArray(m_wordFactory->hashBytes(), 16).toHex() : ""; + QString text; + QString lengthText; + + int wordCount = m_wordFactory ? m_wordFactory->wordCount() : 0; + if (wordCount == 0) + { + delete m_wordFactory; + m_wordFactory = NULL; + } + if (m_wordFactory) + lengthText = QString::fromStdString(m_wordFactory->letterCountString()); + + if (firstTime) + m_originalHash = hash; + + text.append(tr("Word count: ")); + text.append(QString("%L1").arg(wordCount)); + text.append("\n"); + text.append(lengthText); + text.append(tr("\nLexicon hash: ")); + text.append(hash.left(8)); + + m_lexiconInformation->setText(text); + + m_saveChanges->setEnabled(true/*hash != m_originalHash && !m_lexiconName->text().isEmpty()*/); + m_clearAllWords->setEnabled(hash != m_originalHash); +} diff --git a/quacker/lexicondialog.h b/quacker/lexicondialog.h new file mode 100644 index 0000000..1f1605b --- /dev/null +++ b/quacker/lexicondialog.h @@ -0,0 +1,86 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QUACKER_LEXICONDIALOG_H +#define QUACKER_LEXICONDIALOG_H + +#include <string> +#include "game.h" +#include "lexiconparameters.h" + +#include <QWidget> +#include <QDialog> + +using namespace std; +using namespace Quackle; + +class QComboBox; +class QLabel; +class QLineEdit; +class QPushButton; +class DawgFactory; +class FileNameValidator; + +class LexiconDialog : public QDialog +{ +Q_OBJECT + +public: + LexiconDialog(QWidget *parent = 0, const QString &originalName = QString()); + ~LexiconDialog(); + virtual void accept(); + + bool itemWasDeleted() { return m_deleted; }; + const QString &lexiconName() { return m_finalLexiconName; }; + + void updateLexiconInformation(bool firstTime = false); + +protected slots: + void parametersChanged(const QString &) { updateLexiconInformation(); }; + void deleteLexicon(); + void addWordsFromFile(); + void alphabetChanged(const QString &); + void loadOriginalDictionary(); + +protected: + void addWordsFromDawgFile(const QString &dawgfile); + void addWordsFromDawgRecursive(const LexiconParameters &lexParams, Quackle::LetterString &word, int index); + void addWordsFromTextFile(const QString &textFile); + +private: + QLineEdit *m_lexiconName; + QComboBox *m_alphabetCombo; + QPushButton *m_addWordsFromFile; + QPushButton *m_clearAllWords; + QLabel *m_lexiconInformation; + FileNameValidator * m_fileNameValidator; + + QPushButton *m_saveChanges; + QPushButton *m_cancel; + QPushButton *m_deleteLexicon; + + QString m_originalName; + QString m_alphabetFileName; + QByteArray m_originalHash; + QString m_finalLexiconName; + bool m_deleted; + + DawgFactory *m_wordFactory; +}; + +#endif diff --git a/quacker/lister.cpp b/quacker/lister.cpp index 278bff1..be3e335 100644 --- a/quacker/lister.cpp +++ b/quacker/lister.cpp @@ -667,7 +667,6 @@ void NumAnagramsFilter::apply() { int twl = 0; int british = 0; - int playability = 0; QString alphagram(QuackleIO::DictFactory::querier()->alphagram((*it).word)); for (Dict::WordList::Iterator word = map[alphagram].begin(); word != map[alphagram].end(); ++word) @@ -676,8 +675,6 @@ void NumAnagramsFilter::apply() british++; else twl++; - - playability += (*word).playability; } if ((twl == numTwlAnagrams) && (british == numOswOnlyAnagrams)) diff --git a/quacker/quacker.cpp b/quacker/quacker.cpp index 098ff84..2f407fa 100644 --- a/quacker/quacker.cpp +++ b/quacker/quacker.cpp @@ -155,13 +155,10 @@ void TopLevel::finishInitialization() void TopLevel::introduceToUser() { CustomQSettings settings; - QString lexiconName = settings.value("quackle/settings/lexicon-name", QString("twl06")).toString(); - if (lexiconName == "csw12") { - statusMessage(tr("The WESPA wordlist (CSW12) is copyright Harper Collins 2011.")); - } else { - statusMessage(tr("Enjoy your quackling. Choose \"New game...\" from the Game menu to begin.")); - } - + QString statusText = QString::fromUtf8(QUACKLE_LEXICON_PARAMETERS->copyrightString().c_str()); + if (statusText.isEmpty()) + statusText = tr("Enjoy your quackling. Choose \"New game...\" from the Game menu to begin."); + statusMessage(statusText); parseCommandLineOptions(); if (!CustomQSettings().contains("quackle/hasBeenRun")) @@ -2120,17 +2117,34 @@ void TopLevel::firstTimeRun() void TopLevel::about() { - QMessageBox::about(this, tr("About Quackle 0.98"), dialogText(tr( -"<p><b>Quackle</b> 0.98 is a crossword game playing, analysis, and study tool. Visit the Quackle homepage at <tt><a href=\"http://quackle.org\">http://quackle.org</a></tt> for more information.</p>" + QString aboutText = tr( +"<p><b>Quackle</b> 1.0 is a crossword game playing, analysis, and study tool. Visit the Quackle homepage at <tt><a href=\"http://quackle.org\">http://quackle.org</a></tt> for more information.</p>" "<p>Quackle was written by Jason Katz-Brown, John O'Laughlin, John Fultz, Matt Liberty, and Anand Buddhdev. We thank the anonymous donor who made this software free.</p>" -"<p>Copyright 2005-2014 by</p>" +"<p>Copyright 2005-2015 by</p>" "<ul>" "<li>Jason Katz-Brown <jasonkatzbrown@gmail.com></li>" "<li>John O'Laughlin <olaughlin@gmail.com></li>" "</ul>" "<p>Quackle is free, open-source software licensed under the terms of the GNU General Public License Version 3. See</p>" "<p><tt><a href=\"http://quackle.org/LICENSE\">http://quackle.org/LICENSE</a></tt></p>" -))); +"<p>Dictionary copyrights</p><ul>" +); + + FILE* file = fopen(QUACKLE_DATAMANAGER->makeDataFilename("lexica", "copyrights.txt", false).c_str(), "r"); + if (file) + { + QTextStream strm(file); + while (!strm.atEnd()) + { + QString line = strm.readLine(); + int pos = line.indexOf(':'); + if (pos != -1 && pos + 1 < line.size()) + aboutText += "<li>" + line.mid(pos + 1) + "</li>"; + } + fclose(file); + aboutText += "</ul>"; + } + QMessageBox::about(this, tr("About Quackle 1.0"), dialogText(aboutText)); } void TopLevel::hints() diff --git a/quacker/quacker.plist b/quacker/quacker.plist index bf6b31e..eef0ac9 100644 --- a/quacker/quacker.plist +++ b/quacker/quacker.plist @@ -21,6 +21,6 @@ <key>NSPrincipalClass</key> <string>NSApplication</string> <key>NSHumanReadableCopyright</key> - <string>Copyright © 2005-2013 by Jason Katz-Brown & John O'Laughlin</string> + <string>Copyright © 2005-2015 by Jason Katz-Brown & John O'Laughlin</string> </dict> </plist> diff --git a/quacker/quacker.pro b/quacker/quacker.pro index 9906e45..6e3f2e0 100644 --- a/quacker/quacker.pro +++ b/quacker/quacker.pro @@ -1,5 +1,5 @@ TEMPLATE = app -VERSION = 0.99 +VERSION = 1.0 TARGET = Quackle DEPENDPATH += .. ../quackleio INCLUDEPATH += . .. @@ -10,19 +10,18 @@ MOC_DIR = moc #CONFIG += debug CONFIG += release +#Um, why is this necessary? I don't know. But if this isn't here, +#qmake messes up resulting Visual Studio project files. +CONFIG -= debug + debug { OBJECTS_DIR = obj/debug + QMAKE_LIBDIR += ../lib/debug ../quackleio/lib/debug } release { OBJECTS_DIR = obj/release -} - -debug { - QMAKE_LIBDIR += ../lib/debug ../quackleio/lib/debug -} -release { - QMAKE_LIBDIR += ../lib/release ../quackleio/lib/release + QMAKE_LIBDIR += ../lib/release ../quackleio/lib/release } win32:!win32-g++ { @@ -32,6 +31,8 @@ win32:!win32-g++ { } macx:LIBS += -framework CoreFoundation +QMAKE_CXXFLAGS += -std=c++11 + # Input HEADERS += *.h SOURCES += *.cpp diff --git a/quacker/settings.cpp b/quacker/settings.cpp index 301608e..e22a29e 100644 --- a/quacker/settings.cpp +++ b/quacker/settings.cpp @@ -45,6 +45,7 @@ #include "boardsetupdialog.h" #include "customqsettings.h" #include "graphicalboard.h" +#include "lexicondialog.h" Settings *Settings::m_self = 0; Settings *Settings::self() @@ -80,17 +81,20 @@ Settings::Settings(QWidget *parent) #endif if (QFile::exists("data")) - m_dataDir = "data"; + m_appDataDir = "data"; else if (QFile::exists("../data")) - m_dataDir = "../data"; + m_appDataDir = "../data"; else if (QFile::exists("Quackle.app/Contents/data")) - m_dataDir = "Quackle.app/Contents/data"; + m_appDataDir = "Quackle.app/Contents/data"; else { if (!directory.cd("data") || !directory.cd("../data")) QMessageBox::critical(0, tr("Error Initializing Data Files - Quacker"), tr("<p>Could not open data directory. Quackle will be useless. Try running the quacker executable with quackle/quacker/ as the current directory.</p>")); - m_dataDir = directory.absolutePath(); + m_appDataDir = directory.absolutePath(); } + m_userDataDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); + QDir qdir(m_userDataDir); + qdir.mkpath("lexica"); } void Settings::createGUI() @@ -98,93 +102,88 @@ void Settings::createGUI() if (m_lexiconNameCombo != 0) return; - QVBoxLayout *vlayout = new QVBoxLayout(this); + QGridLayout *layout = new QGridLayout(this); m_lexiconNameCombo = new QComboBox; connect(m_lexiconNameCombo, SIGNAL(activated(const QString &)), this, SLOT(lexiconChanged(const QString &))); - QStringList items; - populateListFromFilenames(items, m_dataDir + "/lexica"); - m_lexiconNameCombo->addItems(items); + populateComboFromFilenames(m_lexiconNameCombo, "lexica", ".dawg", "lexicon"); - QLabel *cswText = new QLabel(tr("The WESPA wordlist (CSW12) is copyright Harper Collins 2011.")); - - QHBoxLayout *lexiconLayout = new QHBoxLayout; QLabel *lexiconNameLabel = new QLabel(tr("&Lexicon:")); lexiconNameLabel->setBuddy(m_lexiconNameCombo); - - lexiconLayout->addWidget(lexiconNameLabel); - lexiconLayout->addWidget(m_lexiconNameCombo); + m_editLexicon = new QPushButton(tr("Edit...")); + m_editLexicon->setMaximumWidth(60); + connect(m_editLexicon, SIGNAL(clicked()), this, SLOT(editLexicon())); m_alphabetNameCombo = new QComboBox; connect(m_alphabetNameCombo, SIGNAL(activated(const QString &)), this, SLOT(alphabetChanged(const QString &))); - QStringList alphabetItems; - populateListFromFilenames(alphabetItems, m_dataDir + "/alphabets"); - m_alphabetNameCombo->addItems(alphabetItems); + populateComboFromFilenames(m_alphabetNameCombo, "alphabets", ".quackle_alphabet", ""); - QHBoxLayout *alphabetLayout = new QHBoxLayout; QLabel *alphabetNameLabel = new QLabel(tr("&Alphabet:")); alphabetNameLabel->setBuddy(m_alphabetNameCombo); - - alphabetLayout->addWidget(alphabetNameLabel); - alphabetLayout->addWidget(m_alphabetNameCombo); + m_editAlphabet = new QPushButton(tr("Edit...")); + m_editAlphabet->setMaximumWidth(60); + connect(m_editAlphabet, SIGNAL(clicked()), this, SLOT(editAlphabet())); m_themeNameCombo = new QComboBox; connect(m_themeNameCombo, SIGNAL(activated(const QString &)), this, SLOT(themeChanged(const QString &))); - QStringList themeItems; - populateListFromFilenames(themeItems, m_dataDir + "/themes"); - m_themeNameCombo->addItems(themeItems); + populateComboFromFilenames(m_themeNameCombo, "themes", ".ini", ""); - QHBoxLayout *themeLayout = new QHBoxLayout; QLabel *themeNameLabel = new QLabel(tr("&Theme:")); themeNameLabel->setBuddy(m_themeNameCombo); + m_editTheme = new QPushButton(tr("Edit...")); + m_editTheme->setMaximumWidth(60); + connect(m_editTheme, SIGNAL(clicked()), this, SLOT(editTheme())); - themeLayout->addWidget(themeNameLabel); - themeLayout->addWidget(m_themeNameCombo); - - m_boardNameCombo = new QComboBox(); + m_boardNameCombo = new QComboBox; connect(m_boardNameCombo, SIGNAL(activated(const QString &)), this, SLOT(boardChanged(const QString &))); - m_addBoard = new QPushButton(tr("Add Board")); - connect(m_addBoard, SIGNAL(clicked()), this, SLOT(addBoard())); + populateComboFromFilenames(m_boardNameCombo, "boards", "", "board"); - m_editBoard = new QPushButton(tr("&Edit Board")); + QLabel *boardNameLabel = new QLabel(tr("&Board:")); + boardNameLabel->setBuddy(m_boardNameCombo); + m_editBoard = new QPushButton(tr("Edit...")); + m_editBoard->setMaximumWidth(60); connect(m_editBoard, SIGNAL(clicked()), this, SLOT(editBoard())); - m_deleteBoard = new QPushButton(tr("&Delete Board")); - connect(m_deleteBoard, SIGNAL(clicked()), this, SLOT(deleteBoard())); + m_copyrightLabel = new QLabel(); - loadBoardNameCombo(); + layout->addWidget(lexiconNameLabel, 0, 0, Qt::AlignRight); + layout->addWidget(m_lexiconNameCombo, 0, 1); + layout->addWidget(m_editLexicon, 0, 2); + layout->addWidget(alphabetNameLabel, 1, 0, Qt::AlignRight); + layout->addWidget(m_alphabetNameCombo, 1, 1); + // layout->addWidget(m_editAlphabet, 1, 2); + layout->addWidget(themeNameLabel, 2, 0, Qt::AlignRight); + layout->addWidget(m_themeNameCombo, 2, 1); + // layout->addWidget(m_editTheme, 2, 2); + layout->addWidget(boardNameLabel, 3, 0, Qt::AlignRight); + layout->addWidget(m_boardNameCombo, 3, 1); + layout->addWidget(m_editBoard, 3, 2); + layout->addWidget(m_copyrightLabel, 4, 0, 1, -1, Qt::AlignTop); - QGroupBox *boardGroup = new QGroupBox("Game Board Definitions"); - QGridLayout *boardLayout = new QGridLayout(boardGroup); - QLabel *boardNameLabel = new QLabel(tr("&Board:")); - boardNameLabel->setBuddy(m_boardNameCombo); + layout->setColumnMinimumWidth(3, 0); + layout->setColumnStretch(3, 1); + layout->setRowMinimumHeight(4, 0); + layout->setRowStretch(4, 1); - boardLayout->addWidget(boardNameLabel, 0, 0); - boardLayout->addWidget(m_boardNameCombo, 0, 1, 1, -1); - boardLayout->addWidget(m_addBoard, 1, 0); - boardLayout->addWidget(m_editBoard, 1, 1); - boardLayout->addWidget(m_deleteBoard, 1, 2); - - vlayout->addWidget(cswText); - vlayout->addLayout(lexiconLayout); - vlayout->addLayout(alphabetLayout); - vlayout->addLayout(themeLayout); - vlayout->addWidget(boardGroup); - vlayout->addStretch(); load(); } void Settings::load() { - m_lexiconNameCombo->setCurrentIndex(m_lexiconNameCombo->findText(QuackleIO::Util::stdStringToQString(QUACKLE_LEXICON_PARAMETERS->lexiconName()))); - m_alphabetNameCombo->setCurrentIndex(m_alphabetNameCombo->findText(QuackleIO::Util::stdStringToQString(QUACKLE_ALPHABET_PARAMETERS->alphabetName()))); + m_lexiconNameCombo->setCurrentIndex(m_lexiconNameCombo->findText(QString::fromUtf8(QUACKLE_LEXICON_PARAMETERS->lexiconName().c_str()))); + if (m_lexiconNameCombo->currentIndex() == -1) + m_lexiconNameCombo->setCurrentIndex(m_lexiconNameCombo->findText(QString::fromUtf8(QUACKLE_LEXICON_PARAMETERS->lexiconName().c_str()) + "*")); + m_lastGoodLexiconValue = m_lexiconNameCombo->currentIndex(); + m_alphabetNameCombo->setCurrentIndex(m_alphabetNameCombo->findText(QString::fromUtf8(QUACKLE_ALPHABET_PARAMETERS->alphabetName().c_str()))); m_themeNameCombo->setCurrentIndex(m_themeNameCombo->findText(m_themeName)); m_boardNameCombo->setCurrentIndex(m_boardNameCombo->findText(QuackleIO::Util::uvStringToQString(QUACKLE_BOARD_PARAMETERS->name()))); + m_lastGoodBoardValue = m_boardNameCombo->currentIndex(); + m_copyrightLabel->setText(QString::fromUtf8(QUACKLE_LEXICON_PARAMETERS->copyrightString().c_str())); } void Settings::preInitialize() @@ -198,7 +197,8 @@ void Settings::initialize() CustomQSettings settings; QUACKLE_DATAMANAGER->setBackupLexicon("twl06"); - QUACKLE_DATAMANAGER->setDataDirectory(m_dataDir.toStdString()); + QUACKLE_DATAMANAGER->setAppDataDirectory(m_appDataDir.toStdString()); + QUACKLE_DATAMANAGER->setUserDataDirectory(m_userDataDir.toStdString()); QString lexiconName = settings.value("quackle/settings/lexicon-name", QString("twl06")).toString(); @@ -206,50 +206,98 @@ void Settings::initialize() if (lexiconName == "cswfeb07") lexiconName = "cswapr07"; - setQuackleToUseLexiconName(QuackleIO::Util::qstringToStdString(lexiconName)); - setQuackleToUseAlphabetName(QuackleIO::Util::qstringToStdString(settings.value("quackle/settings/alphabet-name", QString("english")).toString())); + setQuackleToUseLexiconName(lexiconName); + setQuackleToUseAlphabetName(settings.value("quackle/settings/alphabet-name", QString("english")).toString()); setQuackleToUseThemeName(settings.value("quackle/settings/theme-name", QString("traditional")).toString()); setQuackleToUseBoardName(settings.value("quackle/settings/board-name", QString("")).toString()); } -void Settings::setQuackleToUseLexiconName(const string &lexiconName) +void Settings::buildGaddag(const string &filename) +{ + GaddagFactory factory((UVString())); + Quackle::LetterString word; + + pushIndex(factory, word, 1); + factory.generate(); + factory.writeIndex(filename); +} + +void Settings::pushIndex(GaddagFactory &factory, Quackle::LetterString &word, int index) { - if (QUACKLE_LEXICON_PARAMETERS->lexiconName() != lexiconName) + unsigned int p; + Quackle::Letter letter; + bool t; + bool lastchild; + bool british; + int playability; + + do { - QUACKLE_LEXICON_PARAMETERS->setLexiconName(lexiconName); + QUACKLE_LEXICON_PARAMETERS->dawgAt(index, p, letter, t, lastchild, british, playability); + word.push_back(letter); + if (t) + factory.pushWord(word); + if (p) + pushIndex(factory, word, p); + index++; + word.pop_back(); + } while (!lastchild); +} - string gaddagFile = Quackle::LexiconParameters::findDictionaryFile(lexiconName + ".gaddag"); - if (gaddagFile.empty()) - { - UVcout << "Gaddag for lexicon '" << lexiconName << "' does not exist." << endl; - QUACKLE_LEXICON_PARAMETERS->unloadGaddag(); - } - else - QUACKLE_LEXICON_PARAMETERS->loadGaddag(gaddagFile); +void Settings::setQuackleToUseLexiconName(const QString &lexiconName) +{ + string lexiconNameStr = lexiconName.toStdString(); + if (QUACKLE_LEXICON_PARAMETERS->lexiconName() != lexiconNameStr) + { + QUACKLE_LEXICON_PARAMETERS->setLexiconName(lexiconNameStr); - string dawgFile = Quackle::LexiconParameters::findDictionaryFile(lexiconName + ".dawg"); + string dawgFile = Quackle::LexiconParameters::findDictionaryFile(lexiconNameStr + ".dawg"); if (dawgFile.empty()) { - UVcout << "Dawg for lexicon '" << lexiconName << "' does not exist." << endl; + UVcout << "Dawg for lexicon '" << lexiconNameStr << "' does not exist." << endl; QUACKLE_LEXICON_PARAMETERS->unloadDawg(); } else QUACKLE_LEXICON_PARAMETERS->loadDawg(dawgFile); - QUACKLE_STRATEGY_PARAMETERS->initialize(lexiconName); + if (!QUACKLE_LEXICON_PARAMETERS->hasDawg()) + { + QUACKLE_LEXICON_PARAMETERS->unloadGaddag(); + return; + } + + string gaddagFile = Quackle::LexiconParameters::findDictionaryFile(lexiconNameStr + ".gaddag"); + if (gaddagFile.empty()) + { + UVcout << "Gaddag for lexicon '" << lexiconNameStr << "' does not exist." << endl; + QUACKLE_LEXICON_PARAMETERS->unloadGaddag(); + } + else + QUACKLE_LEXICON_PARAMETERS->loadGaddag(gaddagFile); + + // if (!QUACKLE_LEXICON_PARAMETERS->hasGaddag()) + // { + // gaddagFile = QUACKLE_DATAMANAGER->makeDataFilename("lexica", lexiconNameStr + ".gaddag", true); + // buildGaddag(gaddagFile); + // QUACKLE_LEXICON_PARAMETERS->loadGaddag(gaddagFile); + // } + + QUACKLE_STRATEGY_PARAMETERS->initialize(lexiconNameStr); + m_copyrightLabel->setText(QString::fromUtf8(QUACKLE_LEXICON_PARAMETERS->copyrightString().c_str())); } } -void Settings::setQuackleToUseAlphabetName(const string &alphabetName) +void Settings::setQuackleToUseAlphabetName(const QString &alphabetName) { - if (QUACKLE_ALPHABET_PARAMETERS->alphabetName() != alphabetName) + string alphabetNameStr = alphabetName.toStdString(); + if (QUACKLE_ALPHABET_PARAMETERS->alphabetName() != alphabetNameStr) { - QString alphabetFile = QuackleIO::Util::stdStringToQString(Quackle::AlphabetParameters::findAlphabetFile(alphabetName + ".quackle_alphabet")); + QString alphabetFileStr = QuackleIO::Util::stdStringToQString(Quackle::AlphabetParameters::findAlphabetFile(alphabetNameStr)); QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters; - flexure->setAlphabetName(alphabetName); - if (flexure->load(alphabetFile)) + flexure->setAlphabetName(alphabetNameStr); + if (flexure->load(alphabetFileStr)) { if (flexure->length() != QUACKLE_ALPHABET_PARAMETERS->length() && QUACKLE_ALPHABET_PARAMETERS->alphabetName() != "default") { @@ -269,11 +317,13 @@ void Settings::setQuackleToUseAlphabetName(const string &alphabetName) void Settings::setQuackleToUseThemeName(const QString &themeName) { m_themeName = themeName; - QString themeFile = m_dataDir + "/themes/" + themeName + ".ini"; + QString themeFile = m_userDataDir + "/themes/" + themeName + ".ini"; + if (!QFile::exists(themeFile)) + themeFile = m_appDataDir + "/themes/" + themeName + ".ini"; if (!QFile::exists(themeFile)) { m_themeName = "traditional"; - themeFile = m_dataDir + "/themes/traditional.ini"; + themeFile = m_appDataDir + "/themes/traditional.ini"; } PixmapCacher::self()->readTheme(themeFile); } @@ -300,19 +350,34 @@ void Settings::setQuackleToUseBoardName(const QString &boardName) void Settings::lexiconChanged(const QString &lexiconName) { - string lexiconNameString = QuackleIO::Util::qstringToStdString(lexiconName); - setQuackleToUseLexiconName(lexiconNameString); + QString lexicon = lexiconName; + if (lexicon.endsWith("*")) + lexicon.truncate(lexicon.size() - 1); + if (m_lexiconNameCombo->currentIndex() == m_lexiconNameCombo->count() - 1) + { + editLexicon(); + if (m_lexiconNameCombo->currentIndex() == m_lexiconNameCombo->count() - 1 && + m_lexiconNameCombo->currentIndex() != 0) + m_lexiconNameCombo->setCurrentIndex(m_lastGoodLexiconValue); + return; + } + setQuackleToUseLexiconName(lexicon); + m_lastGoodLexiconValue = m_lexiconNameCombo->currentIndex(); CustomQSettings settings; - settings.setValue("quackle/settings/lexicon-name", lexiconName); + settings.setValue("quackle/settings/lexicon-name", lexicon); emit refreshViews(); } void Settings::alphabetChanged(const QString &alphabetName) { - string alphabetNameString = QuackleIO::Util::qstringToStdString(alphabetName); - setQuackleToUseAlphabetName(alphabetNameString); + if (m_alphabetNameCombo->currentIndex() == m_alphabetNameCombo->count() - 1) + { + editAlphabet(); + return; + } + setQuackleToUseAlphabetName(alphabetName); CustomQSettings settings; settings.setValue("quackle/settings/alphabet-name", alphabetName); @@ -322,6 +387,11 @@ void Settings::alphabetChanged(const QString &alphabetName) void Settings::themeChanged(const QString &themeName) { + if (m_themeNameCombo->currentIndex() == m_themeNameCombo->count() - 1) + { + editTheme(); + return; + } setQuackleToUseThemeName(themeName); CustomQSettings settings; @@ -332,6 +402,14 @@ void Settings::themeChanged(const QString &themeName) void Settings::boardChanged(const QString &boardName) { + if (m_boardNameCombo->currentIndex() == m_boardNameCombo->count() - 1) + { + addBoard(); + if (m_boardNameCombo->currentIndex() == m_boardNameCombo->count() - 1 && + m_boardNameCombo->currentIndex() != 0) + m_boardNameCombo->setCurrentIndex(m_lastGoodBoardValue); + return; + } CustomQSettings settings; settings.setValue("quackle/settings/board-name", boardName); @@ -358,6 +436,7 @@ void Settings::addBoard() (const uchar *)boardParameterStream.str().data(), boardParameterStream.str().size()); settings.setValue(boardName, QVariant(boardParameterBytes)); + m_boardNameCombo->setCurrentIndex(-1); boardChanged(boardName); } else @@ -391,35 +470,12 @@ void Settings::editBoard() boardChanged(newBoardName); } PixmapCacher::self()->invalidate(); -} - -void Settings::deleteBoard() -{ - int oldIndex = m_boardNameCombo->currentIndex(); - QString boardName = m_boardNameCombo->currentText(); - QString message = "Do you really want to delete the game board \""; - message += boardName; - message += "\"?"; - if (QMessageBox::warning(NULL, QString("Confirm Deletion"), message, - QMessageBox::Yes | QMessageBox::Default, - QMessageBox::No | QMessageBox::Escape) == QMessageBox::Yes) - { - CustomQSettings settings; - settings.beginGroup("quackle/boardparameters"); - settings.remove(boardName); - loadBoardNameCombo(); - if (oldIndex != 0) - oldIndex--; - m_boardNameCombo->setCurrentIndex(oldIndex); - boardChanged(m_boardNameCombo->currentText()); - } + loadBoardNameCombo(); + emit refreshViews(); } void Settings::loadBoardNameCombo() { - if (m_lexiconNameCombo == 0) - return; - while (m_boardNameCombo->count() > 0) m_boardNameCombo->removeItem(0); @@ -428,26 +484,100 @@ void Settings::loadBoardNameCombo() QStringList boardNames = settings.childKeys(); boardNames.sort(); m_boardNameCombo->addItems(boardNames); + m_boardNameCombo->addItem("Add new board..."); settings.endGroup(); QString currentItem = settings.value("quackle/settings/board-name", QString("")).toString(); - m_boardNameCombo->setCurrentIndex(m_boardNameCombo->findText(currentItem)); + int currentItemIndex = m_boardNameCombo->findText(currentItem); + if (m_boardNameCombo->count() > 0 && currentItemIndex < 0) + currentItemIndex = 0; + m_boardNameCombo->setCurrentIndex(currentItemIndex); +} + +void Settings::editLexicon() +{ + QString name = m_lexiconNameCombo->currentText(); + if (name.endsWith("*")) + name.truncate(name.size() - 1); + if (m_lexiconNameCombo->currentIndex() == m_lexiconNameCombo->count() - 1) + name = ""; + LexiconDialog dialog(this, name); + if (dialog.exec()) + { + populateComboFromFilenames(m_lexiconNameCombo, "lexica", ".dawg", "lexicon"); + qApp->processEvents(); + if (dialog.itemWasDeleted()) + { + m_lexiconNameCombo->setCurrentIndex(m_lexiconNameCombo->findText(name)); + QUACKLE_LEXICON_PARAMETERS->setLexiconName(""); // force lexicon to reload + QUACKLE_LEXICON_PARAMETERS->unloadAll(); + if (m_lexiconNameCombo->currentIndex() != -1) + setQuackleToUseLexiconName(name); + } + else if (!dialog.lexiconName().isEmpty()) + { + QUACKLE_LEXICON_PARAMETERS->setLexiconName(""); // force lexicon to reload + QUACKLE_LEXICON_PARAMETERS->unloadAll(); + setQuackleToUseLexiconName(dialog.lexiconName()); + m_lexiconNameCombo->setCurrentIndex(m_lexiconNameCombo->findText(name + "*")); + } + load(); + } +} - m_editBoard->setEnabled(boardNames.count() > 0); - m_deleteBoard->setEnabled(boardNames.count() > 0); +void Settings::editAlphabet() +{ +#if 0 + QString name = m_alphabetNameCombo->currentText(); + if (m_alphabetNameCombo->currentIndex() == m_alphabetNameCombo->count() - 1) + name = ""; + AlphabetDialog dialog(this); + if (dialog.exec()) + { + populateComboFromFilenames(m_alphabetNameCombo, "alphabets", ".quackle_alphabet", "alphabet"); + load(); + } +#endif // 0 +} + +void Settings::editTheme() +{ +#if 0 + QString name = m_themeNameCombo->currentText(); + if (m_themeNameCombo->currentIndex() == m_themeNameCombo->count() - 1) + name = ""; + ThemeDialog dialog(this); + if (dialog.exec()) + { + populateThemeFromFilenames(m_themeNameCombo, "themes", "theme"); + load(); + } +#endif // 0 } -void Settings::populateListFromFilenames(QStringList& list, const QString &path) +void Settings::populateComboFromFilenames(QComboBox* combo, const QString &path, const QString &extension, const QString &label) { - QDir dir(path); - QStringList fileList = dir.entryList(QDir::Files | QDir::Readable, QDir::Name); - QStringListIterator i(fileList); + while (combo->count() > 0) + combo->removeItem(0); + + QStringList fileList; + QDir dir(self()->m_appDataDir); + if (dir.cd(path)) + fileList << dir.entryList(QDir::Files | QDir::Readable, QDir::Name); + dir = QDir(self()->m_userDataDir); + if (dir.cd(path)) + fileList << dir.entryList(QDir::Files | QDir::Readable, QDir::Name); + + QStringList::iterator i; QString fileName; + QStringList list; int periodPos; - while (i.hasNext()) + for (i = fileList.begin(); i != fileList.end(); ++i) { - fileName = i.next(); + fileName = *i; + if (!fileName.endsWith(extension)) + continue; periodPos = fileName.indexOf('.'); if (periodPos) { @@ -455,5 +585,21 @@ void Settings::populateListFromFilenames(QStringList& list, const QString &path) list << fileName; } } - list.removeDuplicates(); + + for (i = list.begin(); i != list.end(); ++i) + { + for (QStringList::iterator j = i + 1; j != list.end(); ++j) + { + if (*i == *j) + { + *i = *i + "*"; + list.erase(j); + break; + } + } + } + + combo->addItems(list); + if (label.size() > 0) + combo->addItem(QString(tr("Add new ")).append(path).append("...")); } diff --git a/quacker/settings.h b/quacker/settings.h index 785b726..ef3449e 100644 --- a/quacker/settings.h +++ b/quacker/settings.h @@ -24,9 +24,12 @@ #include <QWidget> #include <QSettings> +#include "quackleio/gaddagfactory.h" + class QComboBox; class QCheckBox; class QPushButton; +class QLabel; using namespace std; @@ -39,6 +42,9 @@ public: static Settings *self(); + // load up an item list based on a list of filenames + static void populateComboFromFilenames(QComboBox* combo, const QString &path, const QString &extension, const QString &label); + signals: void refreshViews(); @@ -64,10 +70,13 @@ protected slots: void addBoard(); void editBoard(); - void deleteBoard(); - void setQuackleToUseLexiconName(const string &lexiconName); - void setQuackleToUseAlphabetName(const string &alphabetName); + void editLexicon(); + void editAlphabet(); + void editTheme(); + + void setQuackleToUseLexiconName(const QString &lexiconName); + void setQuackleToUseAlphabetName(const QString &alphabetName); void setQuackleToUseThemeName(const QString &themeName); void setQuackleToUseBoardName(const QString &lexiconName); @@ -76,20 +85,25 @@ protected: QComboBox *m_alphabetNameCombo; QComboBox *m_themeNameCombo; QComboBox *m_boardNameCombo; - QPushButton *m_addBoard; + QPushButton *m_editLexicon; + QPushButton *m_editAlphabet; + QPushButton *m_editTheme; QPushButton *m_editBoard; - QPushButton *m_deleteBoard; - QString m_dataDir; + QLabel *m_copyrightLabel; + QString m_appDataDir; + QString m_userDataDir; QString m_themeName; private: // populate the popup based on what's in QSettings void loadBoardNameCombo(); - // load up an item list based on a list of filenames - void populateListFromFilenames(QStringList& list, const QString &path); - + void buildGaddag(const string &filename); + void pushIndex(GaddagFactory &factory, Quackle::LetterString &word, int index); + static Settings *m_self; + int m_lastGoodLexiconValue; + int m_lastGoodBoardValue; }; #endif diff --git a/quackle.pro b/quackle.pro index 18d9a33..4bd40f4 100644 --- a/quackle.pro +++ b/quackle.pro @@ -17,6 +17,8 @@ release { DESTDIR = lib/release } +QMAKE_CXXFLAGS += -std=c++11 + # enable/disable debug symbols #CONFIG += debug staticlib CONFIG += release staticlib diff --git a/quackle.sublime-project b/quackle.sublime-project index 167db4b..c7f47cd 100644 --- a/quackle.sublime-project +++ b/quackle.sublime-project @@ -5,9 +5,10 @@ "path": ".", "file_exclude_patterns" : ["*.tgz", "*.sublime-workspace", ".tags*", "dawginput.raw", "playabilities.raw", "smaller.raw", ".gitattributes", - "*.Debug", "*.Release", "*.pfx", "*.cer"], - "folder_exclude_patterns" : ["obj", "moc", "build", "*.xcodeproj", "lib", "lexica", - "strategy", "debug", "release", "makeswelexicon", "lisp"] + "*.Debug", "*.Release", "*.pfx", "*.cer", + "makegaddag", "makeminidawg", "gaddagize", "Makefile", "*.dawg", "*.gaddag"], + "folder_exclude_patterns" : ["obj", "moc", "build", "*.xcodeproj", "lib", + "strategy", "debug", "release", "makeswelexicon", "lisp", "DerivedData"] } ] } diff --git a/quackleio/dawgfactory.cpp b/quackleio/dawgfactory.cpp new file mode 100644 index 0000000..4dd4ec3 --- /dev/null +++ b/quackleio/dawgfactory.cpp @@ -0,0 +1,326 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + + +#include <iomanip> +#include <ios> +#include <iostream> +#include <QtCore> +#include <QCryptographicHash> + +#include "dawgfactory.h" +#include "util.h" + + +DawgFactory::DawgFactory(const QString &alphabetFile) + : m_encodableWords(0), m_unencodableWords(0), m_duplicateWords(0), + m_countsByLength(Quackle::FixedLengthString::maxSize, 0) +{ + QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters; + flexure->load(alphabetFile); + m_alphas = flexure; + + m_root.insmallerdict = false; + m_root.playability = 0; + m_root.c = QUACKLE_BLANK_MARK; + m_root.pointer = 0; + m_root.lastchild = true; + + m_hash.int32ptr[0] = m_hash.int32ptr[1] = m_hash.int32ptr[2] = m_hash.int32ptr[3] = 0; +} + +DawgFactory::~DawgFactory() +{ + delete m_alphas; +} + +bool DawgFactory::pushWord(const UVString &word, bool inSmaller, int playability) +{ + UVString leftover; + Quackle::LetterString encodedWord = m_alphas->encode(word, &leftover); + if (leftover.empty()) + return pushWord(encodedWord, inSmaller, playability); + + ++m_unencodableWords; + return false; +} + +bool DawgFactory::pushWord(const Quackle::LetterString &word, bool inSmaller, int playability) +{ + if (m_root.pushWord(word, inSmaller, playability)) + { + ++m_encodableWords; + ++m_countsByLength[word.length()]; + hashWord(word); + return true; + } + ++m_duplicateWords; + return false; +} + +void DawgFactory::hashWord(const Quackle::LetterString &word) +{ + QCryptographicHash wordhash(QCryptographicHash::Md5); + wordhash.addData(word.constData(), word.length()); + QByteArray wordhashbytes = wordhash.result(); + m_hash.int32ptr[0] ^= ((const int32_t*)wordhashbytes.constData())[0]; + m_hash.int32ptr[1] ^= ((const int32_t*)wordhashbytes.constData())[1]; + m_hash.int32ptr[2] ^= ((const int32_t*)wordhashbytes.constData())[2]; + m_hash.int32ptr[3] ^= ((const int32_t*)wordhashbytes.constData())[3]; +} + +void DawgFactory::generate() +{ + const int bucketcount = 2000; + vector< int > bucket[bucketcount]; + + m_nodelist.clear(); + m_nodelist.push_back(&m_root); + m_root.print(m_nodelist); + + m_nodelist[0]->letterSum(); + + for (unsigned int i = 0; i < m_nodelist.size(); i++) + { + bucket[m_nodelist[i]->sum % bucketcount].push_back(i); + m_nodelist[i]->pointer = 0; + m_nodelist[i]->written = false; + m_nodelist[i]->deleted = false; + m_nodelist[i]->cloneof = NULL; + } + + for (int b = 0; b < bucketcount; b++) + { + if (bucket[b].size() == 0) + continue; + for (vector<int>::iterator it = bucket[b].begin(); it != bucket[b].end() - 1; it++) + { + if (!m_nodelist[(*it)]->deleted) + { + for (vector<int>::iterator jt = it + 1; jt != bucket[b].end(); jt++) + { + if (!m_nodelist[(*jt)]->deleted) + { + // cout << "Comparing " << (*it) << " and " << (*jt) << endl; + if (m_nodelist[(*it)]->equals(m_nodelist[(*jt)][0])) + { + //cout << "Hey! " << (*it) << " == " << (*jt) << endl; + // ones[l].erase(jt); + m_nodelist[(*jt)]->deleted = true; + m_nodelist[(*jt)]->cloneof = m_nodelist[(*it)]; + } + } + } + } + } + } + + m_nodelist.clear(); + m_nodelist.push_back(&m_root); + m_root.print(m_nodelist); +} + +void DawgFactory::writeIndex(const string &filename) +{ + ofstream out(filename.c_str(), ios::out | ios::binary); + unsigned char bytes[7]; + + bytes[0] = (m_encodableWords & 0x00FF0000) >> 16; + bytes[1] = (m_encodableWords & 0x0000FF00) >> 8; + bytes[2] = (m_encodableWords & 0x000000FF); + + out.put(1); // DAWG format version 1 + out.write(m_hash.charptr, sizeof(m_hash.charptr)); + out.write((char*)bytes, 3); + out.put((char)m_alphas->length()); + for (Quackle::Letter i = m_alphas->firstLetter(); i <= m_alphas->lastLetter(); i++) + { + QString letterText = QuackleIO::Util::uvStringToQString(m_alphas->letterParameter(i).text()); + QByteArray utf8bytes = letterText.toUtf8(); + string utf8LetterText(utf8bytes.constData()); + out << utf8LetterText << ' '; + } + + for (unsigned int i = 0; i < m_nodelist.size(); i++) { + //cout << m_nodelist[i]->c << " " << m_nodelist[i]->pointer << " " << m_nodelist[i]->t << " " << m_nodelist[i]->lastchild << endl; + Node* n = m_nodelist[i]; + unsigned int p; + if (m_nodelist[i]->deleted) + { + p = (unsigned int)(m_nodelist[i]->cloneof->pointer); + // n = m_nodelist[i]->cloneof; + } + else + p = (unsigned int)(m_nodelist[i]->pointer); + + bytes[0] = (p & 0x00FF0000) >> 16; + bytes[1] = (p & 0x0000FF00) >> 8; + bytes[2] = (p & 0x000000FF); + bytes[3] = n->c - QUACKLE_FIRST_LETTER; + + unsigned int pb = n->playability; + bytes[4] = (pb & 0x00FF0000) >> 16; + bytes[5] = (pb & 0x0000FF00) >> 8; + bytes[6] = (pb & 0x000000FF); + + if (n->lastchild) { + bytes[3] |= 64; + } + if (n->insmallerdict) { + bytes[3] |= 128; + } + + out.write((char*)bytes, 7); + } +} + +string DawgFactory::letterCountString() const +{ + ostringstream str; + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + const int letterCount = j * 4 + i + 2; + if (j != 0) + str << "\t"; + if (m_countsByLength[letterCount] > 0) + str << letterCount << "s: " << std::setw(7) << std::right << std::setfill(' ') << m_countsByLength[letterCount]; + } + str << "\n"; + } + return str.str(); +} + + +void DawgFactory::Node::print(vector< Node* > &nodelist) +{ + written = true; + + if (children.size() == 0) + return; + + if (!deleted) + { + //cout << " Setting pointer to " << nodelist.size() << " before I push_back the children." << endl; + pointer = nodelist.size(); + } + else + { + pointer = cloneof->pointer; + //cout << " Setting pointer to clone's (" << pointer << ") and not pushing anything." << endl; + } + + if (!deleted) + { + for (unsigned int i = 0; i < children.size(); i++) { + nodelist.push_back(&children[i]); + } + + for (unsigned int i = 0; i < children.size(); i++) { + if (!children[i].deleted) + children[i].print(nodelist); + else if (!children[i].cloneof->written) + children[i].cloneof->print(nodelist); + } + } + + if (children.size() > 0) + children[children.size() - 1].lastchild = true; +} + + +// returns true if the word was actually added...false if it's a duplicate. +bool DawgFactory::Node::pushWord(const Quackle::LetterString &word, bool inSmaller, int pb) +{ + bool added; + if (word.length() == 0) { + added = (playability == 0); + playability = (pb == 0) ? 1 : pb; // word terminators nodes are marked by nonzero playability in the v1 DAWG format + insmallerdict = inSmaller; + } + else { + char first = word[0]; + Quackle::LetterString rest = word.substr(1, word.length() - 1); + int index = -1; + + // cout << "first: " << first << ", rest: " << rest << endl; + + for (unsigned int i = 0; i < children.size(); i++) { + if (children[i].c == first) { + index = i; + break; + } + } + + if (index == -1) { + Node n; + n.c = first; + n.playability = 0; + n.insmallerdict = false; + n.pointer = 0; + n.lastchild = false; + children.push_back(n); + index = children.size() - 1; + } + + added = children[index].pushWord(rest, inSmaller, pb); + } + + sumexplored = false; + deleted = false; + written = false; + return added; +} + + +bool DawgFactory::Node::equals(const Node &n) const +{ + if (playability != n.playability) + return false; + if (c != n.c) + return false; + if (children.size() != n.children.size()) + return false; + if (insmallerdict != n.insmallerdict) + return false; + if (sum != n.sum) + return false; + + for (unsigned int i = 0; i < children.size(); i++) + if (!children[i].equals(n.children[i])) + return false; + + return true; +} + +int DawgFactory::Node::letterSum() const +{ + if (sumexplored) + return sum; + + sumexplored = true; + + // djb2 checksum + sum = 5381 * 33 + (int) c; + + for (unsigned int i = 0; i < children.size(); i++) + sum = (sum << 5) + sum + children[i].letterSum(); + + return sum; +} diff --git a/quackleio/dawgfactory.h b/quackleio/dawgfactory.h new file mode 100644 index 0000000..7af4d68 --- /dev/null +++ b/quackleio/dawgfactory.h @@ -0,0 +1,94 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QUACKLE_DAWGFACTORY_H +#define QUACKLE_DAWGFACTORY_H + +#include <cstdint> +#include <string> +#include <vector> +#include "flexiblealphabet.h" + + +class DawgFactory { +public: + + DawgFactory(const QString &alphabetFile); + ~DawgFactory(); + + int wordCount() const { return m_encodableWords; }; + string letterCountString() const; + int nodeCount() const { return m_nodelist.size(); }; + int encodableWords() const { return m_encodableWords; }; + int unencodableWords() const { return m_unencodableWords; }; + int duplicateWords() const { return m_duplicateWords; }; + + bool pushWord(const UVString &word, bool inSmaller, int playability); + bool pushWord(const Quackle::LetterString &word, bool inSmaller, int playability); + void hashWord(const Quackle::LetterString &word); + void generate(); + void writeIndex(const string &filename); + + const char* hashBytes() { return m_hash.charptr; }; + +private: + class Node { + public: + bool pushWord(const Quackle::LetterString& word, bool inSmaller, int pb); + void print(vector< Node* > &m_nodelist); + + int letterSum() const; + bool equals(const Node &n) const; + + Quackle::Letter c; + bool insmallerdict; + int playability; // if nonzero, then terminates word + + vector<Node> children; + int pointer; + int location; + + bool lastchild; + + mutable bool sumexplored; + mutable unsigned int sum; + mutable vector<int> counts; + + bool deleted; + Node* cloneof; + bool written; + }; + + int m_encodableWords; + int m_unencodableWords; + int m_duplicateWords; + vector< Node* > m_nodelist; + vector<unsigned int> m_countsByLength; + Quackle::AlphabetParameters *m_alphas; + Node m_root; + union { + char charptr[16]; + std::int32_t int32ptr[4]; + } m_hash; + + static const char m_versionNumber = 1; +}; + +#endif + + diff --git a/quackleio/flexiblealphabet.h b/quackleio/flexiblealphabet.h index 89bd1f4..d5db68a 100644 --- a/quackleio/flexiblealphabet.h +++ b/quackleio/flexiblealphabet.h @@ -21,8 +21,6 @@ #include "alphabetparameters.h" -class QString; - namespace QuackleIO { diff --git a/quackleio/gaddagfactory.cpp b/quackleio/gaddagfactory.cpp new file mode 100644 index 0000000..53ccf04 --- /dev/null +++ b/quackleio/gaddagfactory.cpp @@ -0,0 +1,200 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + + +#include <iostream> +#include <QtCore> +#include <QCryptographicHash> + +#include "gaddagfactory.h" +#include "util.h" + +GaddagFactory::GaddagFactory(const UVString &alphabetFile) + : m_encodableWords(0), m_unencodableWords(0), m_alphas(NULL) +{ + if (!alphabetFile.empty()) + { + QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters; + flexure->load(QuackleIO::Util::uvStringToQString(alphabetFile)); + m_alphas = flexure; + } + + // So the separator is sorted to last. + m_root.t = false; + m_root.c = QUACKLE_NULL_MARK; // "_" + m_root.pointer = 0; + m_root.lastchild = true; + + m_hash.int32ptr[0] = m_hash.int32ptr[1] = m_hash.int32ptr[2] = m_hash.int32ptr[3] = 0; +} + +GaddagFactory::~GaddagFactory() +{ + delete m_alphas; +} + +bool GaddagFactory::pushWord(const UVString &word) +{ + UVString leftover; + Quackle::LetterString encodedWord = m_alphas->encode(word, &leftover); + if (leftover.empty()) + { + pushWord(encodedWord); + return true; + } + + ++m_unencodableWords; + return false; +} + +bool GaddagFactory::pushWord(const Quackle::LetterString &word) +{ + ++m_encodableWords; + hashWord(word); + // FIXME: This hash will fail if duplicate words are passed in. + // But testing for duplicate words isn't so easy without keeping + // an entirely separate list. + + for (unsigned i = 1; i <= word.length(); i++) + { + Quackle::LetterString newword; + + for (int j = i - 1; j >= 0; j--) + newword.push_back(word[j]); + + if (i < word.length()) + { + newword.push_back(internalSeparatorRepresentation); // "^" + for (unsigned j = i; j < word.length(); j++) + newword.push_back(word[j]); + } + m_gaddagizedWords.push_back(newword); + } + return true; +} + +void GaddagFactory::hashWord(const Quackle::LetterString &word) +{ + QCryptographicHash wordhash(QCryptographicHash::Md5); + wordhash.addData(word.constData(), word.length()); + QByteArray wordhashbytes = wordhash.result(); + m_hash.int32ptr[0] ^= ((const int32_t*)wordhashbytes.constData())[0]; + m_hash.int32ptr[1] ^= ((const int32_t*)wordhashbytes.constData())[1]; + m_hash.int32ptr[2] ^= ((const int32_t*)wordhashbytes.constData())[2]; + m_hash.int32ptr[3] ^= ((const int32_t*)wordhashbytes.constData())[3]; +} + +void GaddagFactory::generate() +{ + sort(m_gaddagizedWords.begin(), m_gaddagizedWords.end()); + Quackle::WordList::const_iterator wordsEnd = m_gaddagizedWords.end(); + for (Quackle::WordList::const_iterator wordsIt = m_gaddagizedWords.begin(); wordsIt != wordsEnd; ++wordsIt) + m_root.pushWord(*wordsIt); + // for (const auto& words : gaddaggizedWords) + // m_root.pushWord(words); +} + +void GaddagFactory::writeIndex(const string &fname) +{ + m_nodelist.push_back(&m_root); + + m_root.print(m_nodelist); + + ofstream out(fname.c_str(), ios::out | ios::binary); + + out.put(1); // GADDAG format version 1 + out.write(m_hash.charptr, sizeof(m_hash.charptr)); + + for (size_t i = 0; i < m_nodelist.size(); i++) + { + unsigned int p = (unsigned int)(m_nodelist[i]->pointer); + if (p != 0) + p -= i; // offset indexing + + char bytes[4]; + unsigned char n1 = (p & 0x00FF0000) >> 16; + unsigned char n2 = (p & 0x0000FF00) >> 8; + unsigned char n3 = (p & 0x000000FF) >> 0; + unsigned char n4; + + n4 = m_nodelist[i]->c; + if (n4 == internalSeparatorRepresentation) + n4 = QUACKLE_NULL_MARK; + + if (m_nodelist[i]->t) + n4 |= 64; + + if (m_nodelist[i]->lastchild) + n4 |= 128; + + bytes[0] = n1; bytes[1] = n2; bytes[2] = n3; bytes[3] = n4; + out.write(bytes, 4); + } +} + + +void GaddagFactory::Node::print(vector< Node* >& nodelist) +{ + if (children.size() > 0) + { + pointer = nodelist.size(); + children[children.size() - 1].lastchild = true; + } + + for (size_t i = 0; i < children.size(); i++) + nodelist.push_back(&children[i]); + + for (size_t i = 0; i < children.size(); i++) + children[i].print(nodelist); +} + + +void GaddagFactory::Node::pushWord(const Quackle::LetterString& word) +{ + if (word.length() == 0) + { + t = true; + return; + } + + Quackle::Letter first = Quackle::String::front(word); + Quackle::LetterString rest = Quackle::String::allButFront(word); + int index = -1; + + for (size_t i = 0; i < children.size(); i++) + { + if (children[i].c == first) + { + index = i; + i = children.size(); + } + } + + if (index == -1) + { + Node n; + n.c = first; + n.t = false; + n.pointer = 0; + n.lastchild = false; + children.push_back(n); + index = children.size() - 1; + } + + children[index].pushWord(rest); +} diff --git a/quackleio/gaddagfactory.h b/quackleio/gaddagfactory.h new file mode 100644 index 0000000..3017085 --- /dev/null +++ b/quackleio/gaddagfactory.h @@ -0,0 +1,74 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QUACKLE_GADDAGFACTORY_H +#define QUACKLE_GADDAGFACTORY_H + +#include <cstdint> +#include "flexiblealphabet.h" + + +class GaddagFactory { +public: + + static const Quackle::Letter internalSeparatorRepresentation = QUACKLE_FIRST_LETTER + QUACKLE_MAXIMUM_ALPHABET_SIZE; + + GaddagFactory(const UVString &alphabetFile); + ~GaddagFactory(); + + int wordCount() const { return m_gaddagizedWords.size(); }; + int nodeCount() const { return m_nodelist.size(); }; + int encodableWords() const { return m_encodableWords; }; + int unencodableWords() const { return m_unencodableWords; }; + + bool pushWord(const UVString &word); + bool pushWord(const Quackle::LetterString &word); + void hashWord(const Quackle::LetterString &word); + void sortWords() { sort(m_gaddagizedWords.begin(), m_gaddagizedWords.end()); }; + void generate(); + void writeIndex(const string &fname); + + const char* hashBytes() { return m_hash.charptr; }; + + +private: + class Node { + public: + Quackle::Letter c; + bool t; + vector<Node> children; + int pointer; + bool lastchild; + void pushWord(const Quackle::LetterString& word); + void print(vector< Node* >& m_nodelist); + }; + + int m_encodableWords; + int m_unencodableWords; + Quackle::WordList m_gaddagizedWords; + vector< Node* > m_nodelist; + Quackle::AlphabetParameters *m_alphas; + Node m_root; + union { + char charptr[16]; + std::int32_t int32ptr[4]; + } m_hash; +}; + +#endif + diff --git a/quackleio/iotest/iotest.pro b/quackleio/iotest/iotest.pro index 0bf472e..c7e994f 100644 --- a/quackleio/iotest/iotest.pro +++ b/quackleio/iotest/iotest.pro @@ -8,16 +8,20 @@ CONFIG += release debug { OBJECTS_DIR = obj/debug + QMAKE_LIBDIR += ../../lib/debug ../../quackleio/lib/debug } release { OBJECTS_DIR = obj/release + QMAKE_LIBDIR += ../../lib/release ../../quackleio/lib/release } -LIBS += -lquackleio -lquackle +win32:!win32-g++ { + LIBS += -lquackleio -llibquackle +} else { + LIBS += -lquackleio -lquackle +} -QMAKE_LFLAGS_RELEASE += -L../../lib/release -L../../quackleio/lib/release -QMAKE_LFLAGS_DEBUG += -L../../lib/debug -L../../quackleio/lib/debug # Input HEADERS += trademarkedboards.h diff --git a/quackleio/quackleio.pro b/quackleio/quackleio.pro index 195b0ad..8e43d29 100644 --- a/quackleio/quackleio.pro +++ b/quackleio/quackleio.pro @@ -15,6 +15,8 @@ release { MOC_DIR = moc +QMAKE_CXXFLAGS += -std=c++11 + # enable/disable debug symbols #CONFIG += debug staticlib CONFIG += release staticlib diff --git a/quackletest.cpp b/quackletest.cpp index 5f3066c..7ea5d10 100644 --- a/quackletest.cpp +++ b/quackletest.cpp @@ -45,9 +45,9 @@ int main() { Quackle::DataManager dataManager; - dataManager.setDataDirectory("data"); + dataManager.setAppDataDirectory("data"); dataManager.lexiconParameters()->loadDawg(Quackle::LexiconParameters::findDictionaryFile("twl06.dawg")); - dataManager.lexiconParameters()->loadGaddag(Quackle::LexiconParameters::findDictionaryFile("twl06.gaddag")); + dataManager.lexiconParameters()->loadGaddag(Quackle::LexiconParameters::findDictionaryFile("twl06.gaddag")); dataManager.strategyParameters()->initialize("twl06"); dataManager.setBoardParameters(new Quackle::EnglishBoard()); @@ -58,7 +58,7 @@ int main() const int gameCnt = 1000; //const int gameCnt = 1; for (int game = 0; game < gameCnt; ++game) { - testGame(); + testGame(); } return 0; diff --git a/test/test.pro b/test/test.pro index 0284f47..99a378e 100644 --- a/test/test.pro +++ b/test/test.pro @@ -12,16 +12,20 @@ CONFIG += release debug { OBJECTS_DIR = obj/debug + QMAKE_LIBDIR += ../lib/debug ../quackleio/lib/debug } release { OBJECTS_DIR = obj/release + QMAKE_LIBDIR += ../lib/release ../quackleio/lib/release } -LIBS += -lquackleio -lquackle +win32:!win32-g++ { + LIBS += -lquackleio -llibquackle +} else { + LIBS += -lquackleio -lquackle +} -QMAKE_LFLAGS_RELEASE += -L../lib/release -L../quackleio/lib/release -QMAKE_LFLAGS_DEBUG += -L../lib/debug -L../quackleio/lib/debug # Input HEADERS += testharness.h trademarkedboards.h diff --git a/test/testharness.cpp b/test/testharness.cpp index 64847d1..172e403 100644 --- a/test/testharness.cpp +++ b/test/testharness.cpp @@ -191,9 +191,9 @@ void TestHarness::startUp() UVcout << "Starting up."; m_dataManager.setBackupLexicon("twl06"); - m_dataManager.setDataDirectory("../data"); + m_dataManager.setAppDataDirectory("../data"); - QString alphabetFile = QuackleIO::Util::stdStringToQString(Quackle::AlphabetParameters::findAlphabetFile(QuackleIO::Util::qstringToStdString(m_alphabet) + ".quackle_alphabet")); + QString alphabetFile = QuackleIO::Util::stdStringToQString(Quackle::AlphabetParameters::findAlphabetFile(QuackleIO::Util::qstringToStdString(m_alphabet))); QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters; if (flexure->load(alphabetFile)) { @@ -207,13 +207,13 @@ void TestHarness::startUp() m_dataManager.setBoardParameters(new ScrabbleBoard()); - m_dataManager.lexiconParameters()->loadGaddag(Quackle::LexiconParameters::findDictionaryFile(QuackleIO::Util::qstringToStdString(m_lexicon + ".gaddag"))); + m_dataManager.lexiconParameters()->loadDawg(Quackle::LexiconParameters::findDictionaryFile(QuackleIO::Util::qstringToStdString(m_lexicon + ".dawg"))); UVcout << "."; - m_dataManager.lexiconParameters()->loadDawg(Quackle::LexiconParameters::findDictionaryFile(QuackleIO::Util::qstringToStdString(m_lexicon + ".dawg"))); + m_dataManager.lexiconParameters()->loadGaddag(Quackle::LexiconParameters::findDictionaryFile(QuackleIO::Util::qstringToStdString(m_lexicon + ".gaddag"))); + UVcout << "."; m_dataManager.strategyParameters()->initialize(QuackleIO::Util::qstringToStdString(m_lexicon)); - UVcout << "."; UVcout << endl; |