summaryrefslogtreecommitdiff
path: root/quacker
diff options
context:
space:
mode:
Diffstat (limited to 'quacker')
-rw-r--r--quacker/macondo.cpp40
-rw-r--r--quacker/macondo.h8
-rw-r--r--quacker/macondobackend.cpp102
-rw-r--r--quacker/macondobackend.h10
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();