summaryrefslogtreecommitdiff
path: root/test/testharness.cpp
diff options
context:
space:
mode:
authorJason Katz-Brown <jason@airbnb.com>2013-08-25 02:17:13 -0700
committerJason Katz-Brown <jason@airbnb.com>2013-08-25 02:17:13 -0700
commit9306cb60c32082c5403931de0823a9fd5daa196c (patch)
treeca1b6eb695fdf3f0c2294e92416b272164bae642 /test/testharness.cpp
parent8fb2c681cecc01b46b0f4ba02d5cc177c4747b1c (diff)
Initial git commit.
Diffstat (limited to 'test/testharness.cpp')
-rw-r--r--test/testharness.cpp796
1 files changed, 796 insertions, 0 deletions
diff --git a/test/testharness.cpp b/test/testharness.cpp
new file mode 100644
index 0000000..6febbb9
--- /dev/null
+++ b/test/testharness.cpp
@@ -0,0 +1,796 @@
+/*
+ * Quackle -- Crossword game artificial intelligence and analysis tool
+ * Copyright (C) 2005-2006 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <QtCore>
+
+#include <iostream>
+#include <limits>
+#include <algorithm>
+
+#include <bogowinplayer.h>
+#include <computerplayercollection.h>
+#include <resolvent.h>
+#include <datamanager.h>
+#include <endgameplayer.h>
+#include <game.h>
+#include <gameparameters.h>
+#include <lexiconparameters.h>
+#include <strategyparameters.h>
+#include <enumerator.h>
+#include <reporter.h>
+
+#include <quackleio/dictimplementation.h>
+#include <quackleio/flexiblealphabet.h>
+#include <quackleio/froggetopt.h>
+#include <quackleio/gcgio.h>
+#include <quackleio/util.h>
+
+#include "trademarkedboards.h"
+#include "testharness.h"
+
+using namespace Quackle;
+
+TestHarness::TestHarness()
+ : m_computerPlayerToTest(0), m_quiet(false)
+{
+ m_gamesDir = "games";
+ m_dataManager.setComputerPlayers(Quackle::ComputerPlayerCollection::fullCollection());
+}
+
+TestHarness::~TestHarness()
+{
+}
+
+const char *usage =
+"Optional arguments:\n"
+"--computer=; sets the computer player (default 'Speedy Player').\n"
+"--mode=\n"
+" 'positions' (default) runs computer player all positions.\n"
+" 'report' asks computer player for report on all positions.\n"
+" 'htmlreport' asks for html report on all positions.\n"
+" 'selfplay' runs 1000 selfplay games.\n"
+" 'playability' output info for computing playability values.\n"
+" 'enumerate' lists all racks.\n"
+" 'staticleaves' output static leave values of racks in 'racks' file.\n"
+" 'randomracks' spit out random racks (forever?).\n"
+" 'leavecalc' spit out roughish values of leaves in 'leaves' file.\n"
+" 'anagram' anagrams letters supplied in --letters.\n"
+"--position=game.gcg; this option can be repeated to specify positions\n"
+" to test.\n"
+"--lexicon=; sets the lexicon (default 'twl06').\n"
+"--alphabet=; sets the alphabet (default 'english').\n"
+"--seed=integer; set the random seed for reproducability.\n"
+"--report; generate reports for selfplay games (default false).\n"
+"--letters; letters to anagram.\n"
+"--build; when mode is anagram, do not require that all letters be used.\n"
+"--quiet; print nothing during selfplay games (default false).\n"
+"--repetitions=integer; the number of games for selfplay (default 1000).\n";
+
+void TestHarness::executeFromArguments()
+{
+ GetOpt opts;
+
+ QString mode;
+ QString computer;
+ QString seedString;
+ QString repString;
+ bool build;
+ QString letters;
+ bool help;
+ bool report;
+ unsigned int seed = numeric_limits<unsigned int>::max();
+ unsigned int reps = 1000;
+
+ opts.addOption('c', "computer", &computer);
+ opts.addOption('a', "alphabet", &m_alphabet);
+ opts.addOption('l', "lexicon", &m_lexicon);
+ opts.addOption('m', "mode", &mode);
+ opts.addOption('s', "seed", &seedString);
+ opts.addOption('r', "repetitions", &repString);
+ opts.addOption('t', "letters", &letters);
+ opts.addRepeatableOption("position", &m_positions);
+
+ opts.addSwitch("report", &report);
+ opts.addSwitch("build", &build);
+ opts.addSwitch("quiet", &m_quiet);
+ opts.addSwitch("help", &help);
+
+ if (!opts.parse())
+ return;
+
+ if (help)
+ {
+ UVcout << usage << endl;
+ return;
+ }
+
+ if (mode.isNull())
+ mode = "positions";
+ if (computer.isNull())
+ computer = "Speedy Player";
+ if (m_lexicon.isNull())
+ m_lexicon = "twl06";
+ if (m_alphabet.isNull())
+ m_alphabet = "english";
+ if (!seedString.isNull())
+ seed = seedString.toUInt();
+ if (!repString.isNull())
+ reps = repString.toUInt();
+
+
+ bool playerFound = false;
+ const Quackle::Player &player = QUACKLE_COMPUTER_PLAYERS.playerForName(QuackleIO::Util::qstringToString(computer), playerFound);
+ if (playerFound)
+ {
+ m_computerPlayerToTest = player.computerPlayer();
+ }
+ else
+ {
+ UVcout << "Computer " << QuackleIO::Util::qstringToString(computer) << " not found!" << endl;
+ return;
+ }
+
+ startUp();
+
+ if (mode == "positions")
+ testPositions();
+ else if (mode == "report")
+ testReport(false);
+ else if (mode == "htmlreport")
+ testReport(true);
+ else if (mode == "enumerate")
+ enumerateAll();
+ else if (mode == "staticleaves")
+ staticLeaves(QString("racks"));
+ else if (mode == "randomracks")
+ randomRacks();
+ else if (mode == "leavecalc")
+ leaveCalc(QString("leaves"));
+ else if (mode == "anagram")
+ anagram(letters, build);
+ else if (mode == "selfplay")
+ selfPlayGames(seed, reps, report, false);
+ else if (mode == "playability")
+ selfPlayGames(seed, reps, report, true);
+ else if (mode == "worddump")
+ wordDump();
+ else if (mode == "bingos")
+ bingos();
+}
+
+void TestHarness::startUp()
+{
+ UVcout << "Starting up.";
+
+ m_dataManager.setBackupLexicon("twl06");
+ m_dataManager.setDataDirectory("../data");
+
+ QString alphabetFile = QuackleIO::Util::stdStringToQString(Quackle::AlphabetParameters::findAlphabetFile(QuackleIO::Util::qstringToStdString(m_alphabet) + ".quackle_alphabet"));
+ QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters;
+ if (flexure->load(alphabetFile))
+ {
+ m_dataManager.setAlphabetParameters(flexure);
+ }
+ else
+ {
+ UVcerr << "Couldn't load alphabet " << QuackleIO::Util::qstringToString(m_alphabet) << endl;
+ delete flexure;
+ }
+
+ m_dataManager.setBoardParameters(new ScrabbleBoard());
+
+ m_dataManager.lexiconParameters()->loadGaddag(Quackle::LexiconParameters::findDictionaryFile(QuackleIO::Util::qstringToStdString(m_lexicon + ".gaddag")));
+ UVcout << ".";
+
+ m_dataManager.lexiconParameters()->loadDawg(Quackle::LexiconParameters::findDictionaryFile(QuackleIO::Util::qstringToStdString(m_lexicon + ".dawg")));
+
+ m_dataManager.strategyParameters()->initialize(QuackleIO::Util::qstringToStdString(m_lexicon));
+ UVcout << ".";
+
+ UVcout << endl;
+
+ m_gamesDir = QString("games_PLAYERNAME_%1").arg(QDateTime::currentDateTime().toString("dd.MM_hh.mm.ss"));
+}
+
+void TestHarness::testFromFile(const QString &file)
+{
+ UVcout << "Testing game from " << QuackleIO::Util::qstringToString(file) << endl;
+ Quackle::Game *game = createNewGame(file);
+ if (game)
+ {
+ testPosition(game->currentPosition(), computerPlayerToTest());
+ }
+
+ delete game;
+}
+
+double TestHarness::leaveSim(const Rack &R, int iterations)
+{
+ double sum = 0.0;
+
+ UVcout << R << endl;
+ Bag B;
+ B.removeLetters(R.tiles());
+
+ int tilesToLeave = 14 + rand() % (93 - 14);
+
+ for (int i = 0; i < iterations; i++)
+ {
+ Quackle::Game game;
+
+ Quackle::PlayerList players;
+
+ Quackle::Player compyA(m_computerPlayerToTest->name() + MARK_UV(" A"), Quackle::Player::ComputerPlayerType, 0);
+ compyA.setAbbreviatedName(MARK_UV("A"));
+ compyA.setComputerPlayer(new Quackle::StaticPlayer());
+ players.push_back(compyA);
+
+ Quackle::Player compyB(m_computerPlayerToTest->name() + MARK_UV(" B"), Quackle::Player::ComputerPlayerType, 1);
+ compyB.setAbbreviatedName(MARK_UV("B"));
+ compyB.setComputerPlayer(new Quackle::StaticPlayer());
+ players.push_back(compyB);
+
+ game.setPlayers(players);
+ game.associateKnownComputerPlayers();
+
+ game.addPosition();
+
+ game.currentPosition().setCurrentPlayerRack(Rack(""), true);
+ game.currentPosition().setOppRack(Rack(""), true);
+
+ Bag startBag = B;
+ game.currentPosition().setBag(startBag);
+
+ game.addPosition();
+
+ UVcout << "NEW GAME" << endl;
+
+ while (game.currentPosition().bag().size() > tilesToLeave)
+ {
+ if (game.currentPosition().gameOver())
+ {
+ UVcout << "GAME OVER" << endl;
+ break;
+ }
+
+ const Quackle::Player player(game.currentPosition().currentPlayer());
+ Quackle::Move compMove(game.haveComputerPlay());
+ UVcout << "with " << player.rack() << ", " << player.name() << " commits to " << compMove << endl;
+ }
+
+ game.currentPosition().setCurrentPlayerRack(R, true);
+ Quackle::Move compMove(game.haveComputerPlay());
+ sum += compMove.equity;
+ }
+
+ return sum / iterations;
+}
+
+void TestHarness::leaveCalc(const QString &filename)
+{
+ QuackleIO::GCGIO io;
+ QFile file(filename);
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ UVcout << "Could not open file " << QuackleIO::Util::qstringToString(filename) << endl;
+ return;
+ }
+
+ QTextStream in(&file);
+
+ const int iterations = 10;
+
+ while (!in.atEnd())
+ {
+ QString line = in.readLine();
+ Rack rack(QuackleIO::Util::encode(line));
+ double value = leaveSim(rack, iterations);
+ UVcout << "leavecalc: " << rack << " " << value << endl;
+ }
+
+ file.close();
+
+}
+
+void TestHarness::randomRacks()
+{
+ for (;;)
+ {
+ Game game;
+
+ Quackle::PlayerList players;
+
+ Player compyA(m_computerPlayerToTest->name() + MARK_UV(" A"), Quackle::Player::ComputerPlayerType, 0);
+ compyA.setAbbreviatedName(MARK_UV("A"));
+ compyA.setComputerPlayer(m_computerPlayerToTest);
+ players.push_back(compyA);
+
+ game.setPlayers(players);
+
+ game.addPosition();
+
+ UVcout << game.currentPosition().currentPlayer().rack() << endl;
+ }
+}
+
+void TestHarness::staticLeaves(const QString &filename)
+{
+ QuackleIO::GCGIO io;
+ QFile file(filename);
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ UVcout << "Could not open file " << QuackleIO::Util::qstringToString(filename) << endl;
+ return;
+ }
+
+ QTextStream in(&file);
+
+ Quackle::Game game;
+
+ Quackle::PlayerList players;
+
+ Quackle::Player compyA(m_computerPlayerToTest->name() + MARK_UV(" A"), Quackle::Player::ComputerPlayerType, 0);
+ compyA.setAbbreviatedName(MARK_UV("A"));
+ compyA.setComputerPlayer(m_computerPlayerToTest);
+ players.push_back(compyA);
+
+ Quackle::Player compyB(m_computerPlayerToTest->name() + MARK_UV(" B"), Quackle::Player::ComputerPlayerType, 1);
+ compyB.setAbbreviatedName(MARK_UV("B"));
+ compyB.setComputerPlayer(m_computerPlayerToTest);
+ players.push_back(compyB);
+
+ game.setPlayers(players);
+ game.associateKnownComputerPlayers();
+
+ game.addPosition();
+
+ while (!in.atEnd())
+ {
+ QString line = in.readLine();
+ Rack rack(QuackleIO::Util::encode(line));
+ game.currentPosition().setCurrentPlayerRack(rack);
+ Move move = game.currentPosition().staticBestMove();
+ //Move scoredExch = game.currentPosition().scoreMove(exchZero);
+ //UVcout << rack << " " << scoredExch << endl;
+ UVcout << rack << " " << move << endl;
+ }
+
+ file.close();
+}
+
+void TestHarness::enumerateAll()
+{
+ Quackle::Bag B;
+ Enumerator E(B);
+ ProbableRackList racks;
+ E.enumerate(&racks);
+ for (ProbableRackList::iterator it = racks.begin(); it != racks.end(); ++it)
+ UVcout << (*it).rack << " " << (*it).probability << endl;
+}
+
+struct PowerRack
+{
+ PowerRack(ProbableRack &r) : rack(r.rack),
+ stemProbability(r.probability),
+ usableTiles(0),
+ power(0) {}
+
+ Rack rack;
+ double stemProbability; /* MSP */
+ int usableTiles; /* UT */
+ double power; /* MMPR */
+};
+
+typedef vector<PowerRack> PowerRackList;
+
+static bool powerSort(const PowerRack& r1,
+ const PowerRack& r2)
+{
+ return r1.power > r2.power;
+}
+
+// Looks for a single letter that acts to pluralize words based on the assumption
+// that it will make a very common last letter in the lexicon.
+static Letter findPluarlizer()
+{
+ // generate all the words in the lexicon
+ Generator gen;
+ int flags = Generator::AnagramRearrange | Generator::AddAnyLetters
+ | Generator::ClearBlanknesses;
+ WordList all = gen.anagramLetters("", flags);
+
+ // count the frequency of occurrence of the final letter
+ const int allLetters = QUACKLE_FIRST_LETTER + QUACKLE_MAXIMUM_ALPHABET_SIZE;
+ int endCounts[allLetters];
+ for (int j = 0; j < allLetters; j++) {
+ endCounts[j] = 0;
+ }
+ for (WordList::iterator it = all.begin(); it != all.end(); ++it) {
+ LetterString word = *it;
+ Letter last = word[word.length() - 1];
+ endCounts[last]++;
+ }
+
+ // find the most common and 2nd most common final letters
+ int maxEndCnt = 0;
+ int maxEndLetter = QUACKLE_NULL_MARK;
+ int max2EndCnt = 0;
+ int max2EndLetter = QUACKLE_NULL_MARK;
+ for (int j = 0; j < allLetters; j++) {
+ if (endCounts[j] > maxEndCnt) {
+ max2EndCnt = maxEndCnt;
+ max2EndLetter = maxEndLetter;
+ maxEndCnt = endCounts[j];
+ maxEndLetter = j;
+ } else if (endCounts[j] > max2EndCnt) {
+ max2EndCnt = endCounts[j];
+ max2EndLetter = j;
+ }
+ }
+
+// UVcout << QUACKLE_ALPHABET_PARAMETERS->userVisible(maxEndLetter)
+// << " " << endCounts[maxEndLetter] << endl;
+// UVcout << QUACKLE_ALPHABET_PARAMETERS->userVisible(max2EndLetter)
+// << " " << endCounts[max2EndLetter] << endl;
+
+ // We found a pluralizer if it is much more common, otherwise this
+ // language doesn't seem to have one.
+ if (endCounts[maxEndLetter] > 2 * endCounts[max2EndLetter]) {
+ UVcout << QUACKLE_ALPHABET_PARAMETERS->userVisible(maxEndLetter)
+ << " is the pluralizer" << endl;
+ return maxEndLetter;
+ }
+ return QUACKLE_NULL_MARK;
+}
+
+void TestHarness::bingos()
+{
+ Letter pluralizer = findPluarlizer();
+
+ // enumerate all racks not considering blanks
+ Quackle::Bag B;
+ int blankCnt = 0;
+ while (B.removeLetter(QUACKLE_BLANK_MARK)) {
+ ++blankCnt;
+ }
+ Enumerator E(B);
+ ProbableRackList enumRacks;
+ E.enumerate(&enumRacks, 6);
+
+ // convert probable racks to power racks for extra fields
+ PowerRackList racks;
+ for (ProbableRackList::iterator it = enumRacks.begin();
+ it != enumRacks.end(); ++it) {
+ racks.push_back(PowerRack(*it));
+ }
+
+ // get counts of all non-blank letters
+ Letter start = QUACKLE_ALPHABET_PARAMETERS->firstLetter();
+ Letter end = QUACKLE_ALPHABET_PARAMETERS->lastLetter();
+ char bagCounts[QUACKLE_FIRST_LETTER + QUACKLE_MAXIMUM_ALPHABET_SIZE];
+ B.letterCounts(bagCounts);
+
+ Generator gen;
+ int cnt = 0;
+ for (PowerRackList::iterator it = racks.begin(); it != racks.end(); ++it) {
+ LetterString letters(it->rack.tiles());
+
+ char rackCounts[QUACKLE_FIRST_LETTER + QUACKLE_MAXIMUM_ALPHABET_SIZE];
+ String::counts(letters, rackCounts);
+
+ if (++cnt % 1000 == 0) {
+ UVcout << "stem: " << QUACKLE_ALPHABET_PARAMETERS->userVisible(letters)
+ << " (" << cnt << " / " << racks.size() << ")" << endl;
+ }
+
+ // Count how many tiles combine with rack to make a bingo
+ int usableTiles = 0;
+ for (Letter c = start; c <= end; ++c) {
+ if (bagCounts[c] <= rackCounts[c]) {
+ continue; // none left in bag to draw
+ }
+ int flags = Generator::AnagramRearrange | Generator::SingleMatch;
+ WordList anagrams = gen.anagramLetters(letters + c, flags);
+ if (anagrams.size() > 0) {
+ usableTiles += bagCounts[c] - rackCounts[c]; // count all in the bag
+ }
+ }
+
+ // if we can make anything we can also do so with blanks
+ if (usableTiles > 0) {
+ usableTiles += blankCnt;
+ }
+ it->usableTiles = usableTiles;
+ if (rackCounts[pluralizer]) {
+ // Baron: "However, due to the frequent retention of the S in
+ // actual play, its attributed frequency was subsequently increased
+ // artificially 50%". I generalized 'S' to a pluralizer for
+ // multi-language support.
+ it->stemProbability *= 1.5;
+ }
+ it->power = it->stemProbability * usableTiles;
+ }
+
+ sort(racks.begin(), racks.end(), powerSort);
+
+ // Normalize max MSP to 1 (or 1.5 if it contains pluralizer) and MMPR
+ double scale = 1 / racks.begin()->stemProbability;
+ if (racks.begin()->rack.contains(LetterString(1, pluralizer))) {
+ scale *= 1.5;
+ }
+ for (PowerRackList::iterator it = racks.begin(); it != racks.end(); ++it) {
+ it->stemProbability *= scale;
+ it->power *= scale;
+ }
+
+ // dump results
+ for (PowerRackList::iterator it = racks.begin(); it != racks.end(); ++it) {
+ UVcout << it->rack << " " << it->stemProbability << " "
+ << it->usableTiles << " " << it->power << endl;
+ }
+}
+
+void TestHarness::testPosition(const Quackle::GamePosition &position, Quackle::ComputerPlayer *player)
+{
+ player->setPosition(position);
+
+ ProbableRackList racks;
+ Quackle::Bag unseenBag = position.unseenBag();
+
+ if (unseenBag.size() <= QUACKLE_PARAMETERS->rackSize() + 3)
+ {
+ Enumerator enumerator(unseenBag);
+ enumerator.enumerate(&racks);
+ UVcout << racks.size() << " enumerations: " << endl;
+ for (ProbableRackList::iterator it = racks.begin(); it != racks.end(); ++it)
+ UVcout << (*it).rack << " " << (*it).probability << endl;
+ }
+
+ const int movesToShow = 10;
+ UVcout << "Testing " << computerPlayerToTest()->name() << " on:" << endl;
+ UVcout << position << endl;
+ UVcout << "Generating moves..." << endl;
+
+ MoveList moves = computerPlayerToTest()->moves(movesToShow);
+
+ for (Quackle::MoveList::const_iterator it = moves.begin(); it != moves.end(); ++it)
+ {
+ UVcout << *it << endl;
+ }
+}
+
+void TestHarness::anagram(const QString &letters, bool build)
+{
+ QuackleIO::DictImplementation dict;
+ Dict::WordList list = dict.query(letters, build? Dict::Querier::NoRequireAllLetters : Dict::Querier::None);
+
+ for (Dict::WordList::Iterator it = list.begin(); it != list.end(); ++it)
+ {
+ UVcout << QuackleIO::Util::qstringToString((*it).word);
+ if ((*it).british)
+ UVcout << "#";
+ UVcout << endl;
+ }
+}
+
+Quackle::Game *TestHarness::createNewGame(const QString &filename)
+{
+ QuackleIO::GCGIO io;
+ QFile file(filename);
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ UVcout << "Could not open gcg " << QuackleIO::Util::qstringToString(filename) << endl;
+ return 0;
+ }
+
+ QTextStream in(&file);
+ Quackle::Game *game = io.read(in, QuackleIO::Logania::MaintainBoardPreparation);
+ file.close();
+
+ return game;
+}
+
+void TestHarness::testPositions()
+{
+ UVcout << "Testing " << m_positions.size() << " positions with " << m_computerPlayerToTest->name() << "." << endl;
+ for (QStringList::iterator it = m_positions.begin(); it != m_positions.end(); ++it)
+ testFromFile(*it);
+}
+
+void TestHarness::testReport(bool html)
+{
+ UVcout << "Reporting on " << m_positions.size() << " positions with " << m_computerPlayerToTest->name() << "." << endl;
+ for (QStringList::iterator it = m_positions.begin(); it != m_positions.end(); ++it)
+ {
+ Quackle::Game *game = createNewGame(*it);
+ if (game)
+ {
+ if (html)
+ {
+ UVcout << game->currentPosition().board().htmlBoard(45) << endl;
+ }
+ else
+ {
+ UVString report;
+ Quackle::Reporter::reportGame(*game, m_computerPlayerToTest, &report);
+ UVcout << report << endl;
+ }
+ }
+
+ delete game;
+ }
+}
+
+void TestHarness::selfPlayGames(unsigned int seed, unsigned int reps, bool reports, bool playability)
+{
+ if (seed != numeric_limits<unsigned int>::max()) {
+ UVcout << "using seed " << seed << endl;
+ m_dataManager.seedRandomNumbers(seed);
+ }
+
+ for (unsigned int i = 0; i < reps; i++)
+ selfPlayGame(i, reports, playability);
+}
+
+void TestHarness::selfPlayGame(unsigned int gameNumber, bool reports, bool playability)
+{
+ Quackle::Game game;
+
+ Quackle::PlayerList players;
+
+ Quackle::Player compyA(m_computerPlayerToTest->name() + MARK_UV(" A"), Quackle::Player::ComputerPlayerType, 0);
+ compyA.setAbbreviatedName(MARK_UV("A"));
+ compyA.setComputerPlayer(m_computerPlayerToTest);
+ players.push_back(compyA);
+
+ Quackle::Player compyB(m_computerPlayerToTest->name() + MARK_UV(" B"), Quackle::Player::ComputerPlayerType, 1);
+ compyB.setAbbreviatedName(MARK_UV("B"));
+ compyB.setComputerPlayer(m_computerPlayerToTest);
+ players.push_back(compyB);
+
+ game.setPlayers(players);
+ game.associateKnownComputerPlayers();
+
+ game.addPosition();
+
+ UVcout << "NEW GAME (#" << gameNumber << ")" << endl;
+
+ QTime time;
+ time.start();
+
+ const int playahead = 50;
+ int i;
+ for (i = 0; i < playahead; ++i)
+ {
+ if (game.currentPosition().gameOver())
+ {
+ if (!m_quiet) { UVcout << "GAME OVER" << endl; }
+ break;
+ }
+
+ const Quackle::Player player(game.currentPosition().currentPlayer());
+
+ if (!m_quiet) {
+ if (playability) {
+ game.currentPosition().kibitz(100);
+ Quackle::MoveList moves = game.currentPosition().moves();
+ float bestEquity = moves.front().equity - 0.0001f;
+ Quackle::MoveList tops;
+ for (MoveList::iterator it = moves.begin(); it != moves.end(); ++it) {
+ if ((*it).equity >= bestEquity) {
+ tops.push_back(*it);
+ }
+ }
+ int numTops = tops.size();
+ for (MoveList::iterator it = tops.begin(); it != tops.end(); ++it) {
+ MoveList words = game.currentPosition().allWordsFormedBy(*it);
+ for (MoveList::iterator it2 = words.begin(); it2 != words.end(); ++it2) {
+ Rack word = (*it2).prettyTiles();
+ UVcout << word << " " << numTops << endl;
+ }
+ }
+ int toPlay = rand() % numTops;
+ //UVcout << "playing move #" << toPlay << endl;
+ game.commitMove(tops[toPlay]);
+ } else {
+ Quackle::Move compMove(game.haveComputerPlay());
+ UVcout << "with " << player.rack() << ", " << player.name()
+ << " commits to " << compMove << endl;
+ }
+ }
+ }
+
+ int secondsElapsed = static_cast<int>(time.elapsed() / 1000);
+ if (!m_quiet) {
+ UVcout << "Game " << gameNumber << " played in " << secondsElapsed
+ << " seconds with " << i << " moves" << endl;
+ }
+
+ if (!reports) {
+ return;
+ }
+
+ Quackle::StaticPlayer playah;
+ UVString report;
+ Quackle::Reporter::reportGame(game, &playah, &report);
+ if (!m_quiet) { UVcout << report << endl; }
+
+ QString gamesDir = m_gamesDir;
+ gamesDir.replace("PLAYERNAME", QuackleIO::Util::uvStringToQString(m_computerPlayerToTest->name()));
+ gamesDir.replace(" ", "_");
+ QDir::current().mkdir(gamesDir);
+
+ QString joinedCompyName = QuackleIO::Util::uvStringToQString(m_computerPlayerToTest->name());
+ joinedCompyName.replace(" ", "_");
+ QFile outFile(QString("%1/%2-game-%3.gcg").arg(gamesDir).arg(joinedCompyName).arg(gameNumber));
+
+ if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ UVcout << "Could not open gcg output file" << endl;
+ return;
+ }
+
+ QuackleIO::GCGIO io;
+ QTextStream out(&outFile);
+ io.write(game, out);
+
+ QFile outFileReport(QString("%1/%2-game-%3.report").arg(gamesDir).arg(joinedCompyName).arg(gameNumber));
+
+ if (!outFileReport.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ UVcout << "Could not open report output file" << endl;
+ return;
+ }
+ QTextStream outReport(&outFileReport);
+ outReport << QuackleIO::Util::uvStringToQString(report);
+ outReport << "Game played in " << secondsElapsed << " seconds." << endl;
+
+ outFile.close();
+ outFileReport.close();
+}
+
+static void dumpGaddag(const GaddagNode *node, const LetterString &prefix)
+{
+ for (const GaddagNode* child = node->firstChild(); child; child = child->nextSibling()) {
+ Letter childLetter = child->letter();
+ LetterString newPrefix(prefix);
+ newPrefix += childLetter;
+
+ if (child->isTerminal()) {
+ UVcout << "wordDump: " << QUACKLE_ALPHABET_PARAMETERS->userVisible(newPrefix) << endl;
+ }
+ if (child->firstChild()) {
+ dumpGaddag(child, newPrefix);
+ }
+ }
+}
+
+void TestHarness::wordDump()
+{
+ if (QUACKLE_LEXICON_PARAMETERS->hasGaddag()) {
+ dumpGaddag(QUACKLE_LEXICON_PARAMETERS->gaddagRoot(),
+ LetterString());
+ } else {
+ UVcout << "wordDump: no gaddag" << endl;
+ }
+}