diff options
Diffstat (limited to 'quacker')
-rw-r--r-- | quacker/macondo.cpp | 40 | ||||
-rw-r--r-- | quacker/macondo.h | 8 | ||||
-rw-r--r-- | quacker/macondobackend.cpp | 102 | ||||
-rw-r--r-- | quacker/macondobackend.h | 10 |
4 files changed, 137 insertions, 23 deletions
diff --git a/quacker/macondo.cpp b/quacker/macondo.cpp index a07eb4a..f92f89b 100644 --- a/quacker/macondo.cpp +++ b/quacker/macondo.cpp @@ -1,6 +1,9 @@ /* TODO: - configurable execPath +- detect Macondo crashing? +- stop Macondo solve when game position changes +- configurable max plies */ #include "macondo.h" @@ -12,16 +15,20 @@ TODO: Macondo::Macondo(Quackle::Game *game) : View() { m_game = game; - QGridLayout *layout = new QGridLayout(this); m_useMacondo = new QCheckBox(tr("Use Macondo for 'Simulate'")); - layout->setAlignment(Qt::AlignTop); - layout->addWidget(m_useMacondo, 0, 0); const char *home = getenv("HOME"); std::string execPath = home ? home : "/"; execPath += "/apps/macondo/macondo"; initOptions = std::make_unique<MacondoInitOptions>(execPath); m_backend = new MacondoBackend(game, *initOptions); connect(m_backend, SIGNAL(gotSimMoves(const Quackle::MoveList &)), this, SLOT(gotSimMoves(const Quackle::MoveList &))); + m_solve = new QPushButton(tr("Solve")); + m_solve->setDisabled(true); + QGridLayout *layout = new QGridLayout(this); + layout->setAlignment(Qt::AlignTop); + layout->addWidget(m_useMacondo, 0, 0); + layout->addWidget(m_solve, 1, 0); + connect(m_solve, SIGNAL(clicked()), this, SLOT(solve())); } Macondo::~Macondo() { @@ -36,6 +43,21 @@ void Macondo::simulate() { m_backend->simulate(options, m_movesFromKibitzer); } +void Macondo::solve() { + bool wasSolving = m_isSolving; + if (m_backend->isRunning()) + stop(); + clearMoves(); + if (wasSolving) { + m_solve->setText(tr("Solve")); + } else { + MacondoSolveOptions options; + m_backend->solve(options); + m_solve->setText(tr("Stop")); + m_isSolving = true; + } +} + void Macondo::gameChanged(Quackle::Game *game) { delete m_backend; m_backend = new MacondoBackend(game, *initOptions); @@ -45,6 +67,7 @@ void Macondo::gameChanged(Quackle::Game *game) { void Macondo::stop() { m_backend->stop(); m_anyUpdates = false; + m_isSolving = false; } bool Macondo::useForSimulation() const { @@ -60,5 +83,16 @@ void Macondo::positionChanged(const Quackle::GamePosition *position) { if (!m_backend->isRunning()) { // perhaps new moves were generated m_movesFromKibitzer = position->moves(); + } + int tilesLeft = position->unseenBag().size(); + if (!position->gameOver() && tilesLeft <= 7) { + m_solve->setText(tr("Solve endgame")); + m_solve->setDisabled(false); + } else if (!position->gameOver() && tilesLeft <= 10) { + m_solve->setText(tr("Solve pre-endgame")); + m_solve->setDisabled(false); + } else { + m_solve->setText(tr("Solve")); + m_solve->setDisabled(true); } } diff --git a/quacker/macondo.h b/quacker/macondo.h index 57f6acc..4fcf758 100644 --- a/quacker/macondo.h +++ b/quacker/macondo.h @@ -5,6 +5,7 @@ #include "game.h" class QCheckBox; +class QPushButton; class MacondoBackend; struct MacondoInitOptions; class MoveBox; @@ -28,18 +29,21 @@ public: } public slots: void simulate(); - virtual void gameChanged(Quackle::Game *game); - virtual void positionChanged(const Quackle::GamePosition *position); + void solve(); + void gameChanged(Quackle::Game *game) override; + void positionChanged(const Quackle::GamePosition *position) override; private slots: void gotSimMoves(const Quackle::MoveList &moves); private: QCheckBox *m_useMacondo; + QPushButton *m_solve; Quackle::Game *m_game; MacondoBackend *m_backend; Quackle::MoveList m_moves; Quackle::MoveList m_movesFromKibitzer; int m_viewingPlyNumber = 0; bool m_anyUpdates = false; + bool m_isSolving = false; std::unique_ptr<MacondoInitOptions> initOptions; }; diff --git a/quacker/macondobackend.cpp b/quacker/macondobackend.cpp index 1cbd06a..5837092 100644 --- a/quacker/macondobackend.cpp +++ b/quacker/macondobackend.cpp @@ -8,6 +8,7 @@ #include <QProcess> #include <QTimer> #include <QTextStream> +#include <QThread> #include <random> using std::string; @@ -33,7 +34,24 @@ static bool is_ascii_space(char c) { return strchr(" \r\n\f\t\v", c) != nullptr; } -static vector<string> split(const string &s) { +static vector<string> splitLines(const string &s) { + vector<string> lines; + size_t i = 0; + while (i < s.size()) { + size_t end = s.find('\n', i); + if (end == string::npos) end = s.size(); + if (end == i) { + i++; + continue; + } + string line = s.substr(i, end - i); + lines.push_back(line); + i = end + 1; + } + return lines; +} + +static vector<string> splitWords(const string &s) { vector<string> words; size_t i = 0; while (i < s.size()) { @@ -65,18 +83,28 @@ MacondoBackend::MacondoBackend(Quackle::Game *game, const MacondoInitOptions &op m_updateTimer->start(); } -void MacondoBackend::simulate(const MacondoSimulateOptions &options, const Quackle::MoveList &moves) { +void MacondoBackend::startProcess() { if (m_process) return; - printf("running macondo %s\n", m_execPath.c_str()); - m_movesToLoad = moves; m_process = new QProcess(this); QStringList args; m_process->start(m_execPath.c_str(), args); connect(m_process, SIGNAL(started()), this, SLOT(processStarted())); connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus))); +} + +void MacondoBackend::simulate(const MacondoSimulateOptions &options, const Quackle::MoveList &moves) { + startProcess(); + m_movesToLoad = moves; m_command = Command::Simulate; } +void MacondoBackend::solve(const MacondoSolveOptions &) { + startProcess(); + bool isEndgame = m_game->currentPosition().unseenBag().size() <= 7; + m_command = isEndgame ? Command::SolveEndgame : Command::SolvePreEndgame; +} + + static int parseScore(const string &scoreString) { size_t scoreLen; int score = 0; @@ -122,7 +150,7 @@ static double parseEquity(const string &equityString) { // parse move from Macondo sim static Quackle::Move extractSimMove(const string &play) { - vector<string> words = split(play); + vector<string> words = splitWords(play); bool plays7 = words.size() == 5; // bingo/exchange 7 => no leave in output if (!plays7 && words.size() != 6) { throw "want 6 or 7 space-separated parts in play"; @@ -200,10 +228,36 @@ static Quackle::MoveList extractSimMoves(QByteArray &processOutput) { return moves; } +static bool extractEndgameMove(QByteArray &processOutput, Quackle::Move &move) { + QByteArray spreadDiffMarker("Best sequence has a spread difference (value) of "); + if (!processOutput.contains(spreadDiffMarker)) { + // delete all output except for the last line (which may be incomplete) + int lastLineIndex = processOutput.lastIndexOf('\n'); + if (lastLineIndex >= 0) { + QByteArray lastLine(processOutput.data() + lastLineIndex + 1, + processOutput.size() - lastLineIndex - 1); + processOutput = lastLine; + } + return false; + } + QByteArray bestSeqMarker("Best sequence:\n"); + int seqStart = processOutput.indexOf(bestSeqMarker); + if (seqStart < 0) return false; + seqStart += bestSeqMarker.length(); + string sequenceStr(processOutput.data() + seqStart, processOutput.size() - seqStart); + vector<string> sequence = splitLines(sequenceStr); + printf("got sequence:\n"); + for (const string &moveStr: sequence) { + printf(" | %s\n",moveStr.c_str()); + } + processOutput.clear(); + return false; +} + void MacondoBackend::timer() { if (m_process) { QByteArray data = m_process->readAllStandardError(); - fprintf(stderr,"%s",data.constData()); + fprintf(stderr,"%.*s",data.size(), data.constData()); data = m_process->readAllStandardOutput(); m_processOutput.append(data); fflush(stdout); @@ -224,9 +278,22 @@ void MacondoBackend::timer() { } } break; - case Command::Solve: + case Command::SolvePreEndgame: // TODO break; + case Command::SolveEndgame: + { + if (m_processOutput.contains("Best sequence:")) { + // give Macondo a bit more time to write out the full sequence + QThread::msleep(60); + m_processOutput.append(m_process->readAllStandardOutput()); + } + Quackle::Move move; + if (extractEndgameMove(m_processOutput, move)) { + std::cout << "Move: " << move << std::endl; + } + } + break; } } @@ -262,25 +329,27 @@ void MacondoBackend::processStarted() { commands << "\n"; } - std::string lexicon = QUACKLE_LEXICON_PARAMETERS->lexiconName(); - for (size_t i = 0; i < lexicon.size(); i++) { - if (lexicon[i] >= 'a' && lexicon[i] <= 'z') { - lexicon[i] += 'A' - 'a'; - } - } - commands << "set lexicon " << lexicon << "\n"; commands << "sim\n"; m_process->write(commands.str().c_str()); m_runningSimulation = true; } break; - case Command::Solve: + case Command::SolvePreEndgame: // TODO break; + case Command::SolveEndgame: + m_process->write("endgame -plies 20\n"); + break; } } void MacondoBackend::loadGCG() { + std::string lexicon = QUACKLE_LEXICON_PARAMETERS->lexiconName(); + for (size_t i = 0; i < lexicon.size(); i++) { + if (lexicon[i] >= 'a' && lexicon[i] <= 'z') { + lexicon[i] += 'A' - 'a'; + } + } std::random_device randDev; std::mt19937 rand(randDev()); std::uniform_int_distribution<int> distribution(0, 25); @@ -301,7 +370,8 @@ void MacondoBackend::loadGCG() { gcg.write(*m_game, fileStream); } std::stringstream commands; - commands << "load " << filename << "\n" + commands << "set lexicon " << lexicon << "\n" + << "load " << filename << "\n" << "turn " << getPlyNumber(m_game->currentPosition()) << "\n"; m_process->write(commands.str().c_str()); } diff --git a/quacker/macondobackend.h b/quacker/macondobackend.h index 7d3c8da..dcc161d 100644 --- a/quacker/macondobackend.h +++ b/quacker/macondobackend.h @@ -15,13 +15,17 @@ struct MacondoInitOptions { struct MacondoSimulateOptions { inline MacondoSimulateOptions() {} }; +struct MacondoSolveOptions { + inline MacondoSolveOptions() {} +}; class MacondoBackend: public QObject { Q_OBJECT public: MacondoBackend(Quackle::Game *game, const MacondoInitOptions &); - void simulate(const MacondoSimulateOptions &options, const Quackle::MoveList &moves); ~MacondoBackend(); + void simulate(const MacondoSimulateOptions &options, const Quackle::MoveList &moves); + void solve(const MacondoSolveOptions &options); std::string getSimResults(); inline bool isRunning() const { return m_command != Command::None; } // stop current Macondo analysis @@ -36,8 +40,10 @@ private: enum class Command { None, Simulate, - Solve, + SolvePreEndgame, + SolveEndgame, }; + void startProcess(); void loadGCG(); void killProcess(); void removeTempGCG(); |