summaryrefslogtreecommitdiff
path: root/quacker
diff options
context:
space:
mode:
Diffstat (limited to 'quacker')
-rw-r--r--quacker/.gitignore11
-rw-r--r--quacker/LICENSE4
-rw-r--r--quacker/TODO66
-rw-r--r--quacker/bagdisplay.cpp131
-rw-r--r--quacker/bagdisplay.h49
-rw-r--r--quacker/boarddisplay.cpp221
-rw-r--r--quacker/boarddisplay.h84
-rw-r--r--quacker/boardsetup.cpp164
-rw-r--r--quacker/boardsetup.h77
-rw-r--r--quacker/boardsetupdialog.cpp232
-rw-r--r--quacker/boardsetupdialog.h78
-rw-r--r--quacker/brb.cpp81
-rw-r--r--quacker/brb.h54
-rw-r--r--quacker/configdialog.cpp105
-rw-r--r--quacker/configdialog.h68
-rw-r--r--quacker/configpages.cpp131
-rw-r--r--quacker/configpages.h60
-rw-r--r--quacker/customqsettings.h37
-rw-r--r--quacker/dashboard.cpp131
-rw-r--r--quacker/dashboard.h85
-rw-r--r--quacker/geometry.cpp32
-rw-r--r--quacker/geometry.h36
-rw-r--r--quacker/graphicalboard.cpp1494
-rw-r--r--quacker/graphicalboard.h330
-rw-r--r--quacker/graphicalreporter.cpp233
-rw-r--r--quacker/graphicalreporter.h65
-rw-r--r--quacker/history.cpp158
-rw-r--r--quacker/history.h66
-rw-r--r--quacker/letterbox.cpp1449
-rw-r--r--quacker/letterbox.h295
-rw-r--r--quacker/letterboxsettings.cpp92
-rw-r--r--quacker/letterboxsettings.h63
-rw-r--r--quacker/lister.cpp723
-rw-r--r--quacker/lister.h204
-rw-r--r--quacker/main.cpp32
-rw-r--r--quacker/movebox.cpp301
-rw-r--r--quacker/movebox.h74
-rw-r--r--quacker/newgame.cpp425
-rw-r--r--quacker/newgame.h112
-rw-r--r--quacker/noteeditor.cpp83
-rw-r--r--quacker/noteeditor.h51
-rw-r--r--quacker/oppothread.cpp112
-rw-r--r--quacker/oppothread.h101
-rw-r--r--quacker/oppothreadprogressbar.cpp51
-rw-r--r--quacker/oppothreadprogressbar.h50
-rw-r--r--quacker/quacker.cpp2319
-rw-r--r--quacker/quacker.h445
-rw-r--r--quacker/quacker.icobin0 -> 72566 bytes
-rw-r--r--quacker/quacker.pro41
-rw-r--r--quacker/quacker.rc1
-rw-r--r--quacker/quackersettings.cpp65
-rw-r--r--quacker/quackersettings.h58
-rw-r--r--quacker/rackdisplay.cpp279
-rw-r--r--quacker/rackdisplay.h80
-rw-r--r--quacker/settings.cpp377
-rw-r--r--quacker/settings.h89
-rw-r--r--quacker/simviewer.cpp185
-rw-r--r--quacker/simviewer.h70
-rw-r--r--quacker/uisuggestions.txt51
-rw-r--r--quacker/view.cpp85
-rw-r--r--quacker/view.h119
-rw-r--r--quacker/widgetfactory.cpp72
-rw-r--r--quacker/widgetfactory.h62
63 files changed, 12899 insertions, 0 deletions
diff --git a/quacker/.gitignore b/quacker/.gitignore
new file mode 100644
index 0000000..fa22076
--- /dev/null
+++ b/quacker/.gitignore
@@ -0,0 +1,11 @@
+Makefile
+moc_*
+moc
+obj
+quacker
+Makefile.Debug
+Makefile.Release
+debug
+release
+object_script.quacker.Debug
+object_script.quacker.Release
diff --git a/quacker/LICENSE b/quacker/LICENSE
new file mode 100644
index 0000000..9e775d9
--- /dev/null
+++ b/quacker/LICENSE
@@ -0,0 +1,4 @@
+Copyright (c) 2005-2006, Jason Katz-Brown and John O'Laughlin.
+
+Quacker, the Quackle UI, is under the same license as libquackle in the parent
+directory. Please see quackle/LICENSE for license information.
diff --git a/quacker/TODO b/quacker/TODO
new file mode 100644
index 0000000..530bf8c
--- /dev/null
+++ b/quacker/TODO
@@ -0,0 +1,66 @@
+Quackle 0.96 Checklist
+- Allow setting how many tiles must be in the bag to allow exchange. Spanish Scrabble wants this to be 1.
+- Improve the notes editor and 5-point challenge UI.
+
+Letterbox / Playability+Britishness DAWG detritus cleanup
+- don't keep regenerating extension QString
+- get britishness information at same time as anagramming
+- make dict into a singleton
+- give dict a factory method that returns a dict implementation
+
+TODO
+- clock information stored in GamePosition, clock below history
+- allow using keyboard arrows to move around in move box
+- clicking 'shuffle rack' shouldn't cause redraw of board (e.g., it removes a candidate play that's been typed onto the board.)
+- consider passing more often
+- arbitrary score adjustments that can be negative
+- when saving a game with no plays and only racks, loading it doesn't load anything!
+- store type of computer player in gcg files and load that computer player when opening the game. Assume human player type if no computer type is mentioned in the gcg file.
+- option to see how many possible plays there are
+
+CHEW TODO:
+- improve printf formats of small decimal values in game reports
+- parametrize championship players
+- confidence intervals in reports
+- time penalty applied throughout game
+
+KURNIA TODO:
+2. Human with Unknown Racks doesn't draw tiles
+4. Prev/Next browsing buttons
+5. Keep ERS exchange syntax
+7. A keyboard-friendly way to enter plays on the graphical board (i.e. a shortcut to activate the board; arrow keys to move the pointer and to change its direction; backspace that works even if the rightmost column or bottommost row has just been exceeded; ...)
+8. Command-line quackle binary.
+9. Reports should also list, for each move, all anagrams of (rack) and (rack plus one blank). Something like ddbot. The list should be ordered by length and then by alphagram of the blanks; blanks shown as lowercase.
+
+LEAVE HEURISTICS
+
+In Landau we precalculated BaSiC leaves for 1-6 tiles, but that was
+simply in the interest of speed. That is, they were based only on 1
+tile values and v/c. BobBot actually had simmed leave values for
+almost all of the leaves, and I think that's what we should do too. I
+generated 1-4 tile leave values (except for 4 tile leaves with a
+blank) with Landau, but I think I know how to do them better than I
+did before, and of course it would be nice to do them with TWL06. So
+we should
+
+1. Write code to load precalcuated leave values for 1-6 tile leaves
+into a hash table.
+2. Make a list of leave values using my Landau stuff for 1-4 tiles.
+For leaves we haven't simmed yet, use 1 tile values + synergies +
+Maven v/c (from the CanAm book).
+3. Generate TWL06 leave values with Quackle. Using all the computers
+we have available to us, we should be able to get a lot done in a
+month. I'll leave stuff running on both my PCs while I'm in London,
+and also on my Mac whenever I can. First priority is to get all 1 to
+4 tile leaves. Five and six tile leaves will take longer, so we can
+prioritize these by running a millionish games and ranking the leaves
+by how often they are in the kibitzer's top ten. The sims will run
+faster if we use a GADDAG, so of course we should get that working
+first.
+
+LEAVE HASHING
+Our neat trick for hashing Leave values without sorting was to assign
+each letter a prime number, like E = 2, A = 3, I = 5 ... X = 101, X =
+103 (or whatever). Multiply the letters together into a 64 bit int
+and you've got your hash key. Way faster than alphagramming.
+
diff --git a/quacker/bagdisplay.cpp b/quacker/bagdisplay.cpp
new file mode 100644
index 0000000..8a980ad
--- /dev/null
+++ b/quacker/bagdisplay.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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 <math.h>
+
+#include <QtGui>
+
+#include <bag.h>
+#include <game.h>
+#include <quackleio/util.h>
+
+#include "bagdisplay.h"
+#include "geometry.h"
+
+BagDisplay::BagDisplay(QWidget *parent)
+ : View(parent)
+{
+ QVBoxLayout *layout = new QVBoxLayout(this);
+ Geometry::setupInnerLayout(layout);
+
+ m_textEdit = new QTextEdit;
+ m_textEdit->setReadOnly(true);
+ m_textEdit->setFontFamily("Courier");
+ m_textEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+
+ m_label = new QLabel;
+ m_label->setWordWrap(true);
+ m_label->setBuddy(m_textEdit);
+ layout->addWidget(m_label);
+ layout->addWidget(m_textEdit);
+
+ layout->setStretchFactor(m_textEdit, 10);
+
+ showTiles(Quackle::LongLetterString());
+}
+
+BagDisplay::~BagDisplay()
+{
+}
+
+void BagDisplay::positionChanged(const Quackle::GamePosition &position)
+{
+ showTiles(position.unseenBag().tiles());
+
+ // Birthday
+ const Quackle::PlayerList &players = position.players();
+ for (Quackle::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
+ {
+ if ((*it).name() == "zorbonauts")
+ {
+ m_label->setText(tr("The bag is collapsed in a transparent dead jellyfish-like heap on the table while flies buzz round"));
+ break;
+ }
+ }
+}
+
+void BagDisplay::showTiles(const Quackle::LongLetterString &tiles)
+{
+ if (tiles.empty())
+ {
+ m_label->setText(tr("&Bag is collapsed in a wrinkled heap on the table"));
+ return;
+ }
+
+ QMap<Quackle::Letter, int> counts;
+ QString text;
+
+ for (Quackle::LongLetterString::const_iterator it = tiles.begin(); it != tiles.end(); ++it)
+ {
+ if (counts.contains(*it))
+ counts[*it] += 1;
+ else
+ counts.insert(*it, 1);
+ }
+
+ QFontMetrics metrics(m_textEdit->currentFont());
+ int maxLineWidth = 0;
+
+ for (QMap<Quackle::Letter, int>::iterator it = counts.begin(); it != counts.end(); ++it)
+ {
+ const int count = it.value();
+
+ QString line;
+
+ const QString qstring = QuackleIO::Util::letterToQString(it.key());
+ const QString sanitizedQString = QuackleIO::Util::sanitizeUserVisibleLetterString(qstring);
+ const bool separateWithSpaces = qstring != sanitizedQString;
+ for (int i = 0; i < count; ++i)
+ {
+ if (separateWithSpaces && i > 0) line += " ";
+ line += sanitizedQString;
+ }
+
+ const int lineWidth = metrics.width(line);
+ if (lineWidth > maxLineWidth)
+ maxLineWidth = lineWidth;
+
+ text += line;
+ text += "\n";
+ }
+
+ m_label->setText(tr("%1 unseen tiles").arg(tiles.length()));
+ m_textEdit->setPlainText(text);
+
+ const int minimumMaxLineWidth = 16;
+ if (maxLineWidth < minimumMaxLineWidth)
+ maxLineWidth = minimumMaxLineWidth;
+
+ const int maximumWidth = maxLineWidth + m_textEdit->frameWidth() * 2 + (m_textEdit->verticalScrollBar()->isVisible()? m_textEdit->verticalScrollBar()->width() : 0) + 10;
+ m_textEdit->setMaximumSize(maximumWidth, 26 * 100);
+
+ m_textEdit->resize(m_textEdit->maximumSize());
+}
+
diff --git a/quacker/bagdisplay.h b/quacker/bagdisplay.h
new file mode 100644
index 0000000..dbc0c3f
--- /dev/null
+++ b/quacker/bagdisplay.h
@@ -0,0 +1,49 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_BAGDISPLAY_H
+#define QUACKER_BAGDISPLAY_H
+
+#include <alphabetparameters.h>
+#include "view.h"
+
+class QLabel;
+class QTextEdit;
+
+class BagDisplay : public View
+{
+Q_OBJECT
+
+public:
+ BagDisplay(QWidget *parent = 0);
+ virtual ~BagDisplay();
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition &position);
+
+protected slots:
+ virtual void showTiles(const Quackle::LongLetterString &tiles);
+
+private:
+ QLabel *m_label;
+ QTextEdit *m_textEdit;
+};
+
+#endif
diff --git a/quacker/boarddisplay.cpp b/quacker/boarddisplay.cpp
new file mode 100644
index 0000000..68993b4
--- /dev/null
+++ b/quacker/boarddisplay.cpp
@@ -0,0 +1,221 @@
+/*
+ * 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 <QtGui>
+
+#include <alphabetparameters.h>
+#include <board.h>
+#include <game.h>
+#include <quackleio/util.h>
+
+#include "boarddisplay.h"
+#include "geometry.h"
+
+BoardWithQuickEntry::BoardWithQuickEntry(QWidget *parent)
+ : View(parent)
+{
+ m_vlayout = new QVBoxLayout(this);
+ Geometry::setupInnerLayout(m_vlayout);
+
+ m_lineEdit = new QLineEdit;
+ connect(m_lineEdit, SIGNAL(returnPressed()), this, SLOT(quickEditReturnPressed()));
+
+ QLabel *placeLabel = new QLabel(tr("Move: '<position> <word>' or 'exchange <tiles|number>'"));
+ placeLabel->setBuddy(m_lineEdit);
+ m_vlayout->addWidget(placeLabel);
+
+ QHBoxLayout *placeEditLayout = new QHBoxLayout;
+ Geometry::setupInnerLayout(placeEditLayout);
+ m_vlayout->addLayout(placeEditLayout);
+ placeEditLayout->addWidget(m_lineEdit);
+
+ QPushButton *placeButton = new QPushButton(tr("Enter move"));
+ connect(placeButton, SIGNAL(clicked()), this, SLOT(quickEditReturnPressed()));
+ //placeEditLayout->addWidget(placeButton);
+
+ QPushButton *scoreAdditionButton = new QPushButton(tr("+5"));
+ connect(scoreAdditionButton, SIGNAL(clicked()), this, SLOT(plusFive()));
+ placeEditLayout->addWidget(scoreAdditionButton);
+
+ m_commitButton = new QPushButton(tr("Commit"));
+ connect(m_commitButton, SIGNAL(clicked()), this, SLOT(performCommit()));
+ placeEditLayout->addWidget(m_commitButton);
+
+ QPushButton *resetButton = new QPushButton(tr("Rese&t"));
+ connect(resetButton, SIGNAL(clicked()), this, SLOT(reset()));
+ //placeEditLayout->addWidget(resetButton);
+}
+
+BoardWithQuickEntry::~BoardWithQuickEntry()
+{
+}
+
+void BoardWithQuickEntry::positionChanged(const Quackle::GamePosition &position)
+{
+ View::positionChanged(position);
+ setLocalCandidate(position.moveMade());
+}
+
+void BoardWithQuickEntry::setLocalCandidate(const Quackle::Move &candidate)
+{
+ m_localCandidateMove = candidate;
+
+ if (candidate.isAMove())
+ {
+ m_lineEdit->setText(QuackleIO::Util::moveToDetailedString(candidate));
+ m_commitButton->setText(tr("Commit %1").arg(QuackleIO::Util::moveToDetailedString(candidate)));
+ }
+ else
+ {
+ m_lineEdit->clear();
+ m_commitButton->setText(tr("Commit"));
+ }
+
+ m_commitButton->setEnabled(candidate.isAMove());
+}
+
+void BoardWithQuickEntry::quickEditReturnPressed()
+{
+ processCommand(m_lineEdit->text());
+
+ m_lineEdit->clear();
+}
+
+void BoardWithQuickEntry::plusFive()
+{
+ m_localCandidateMove.setScoreAddition(m_localCandidateMove.scoreAddition() + 5);
+ emit setCandidateMove(m_localCandidateMove);
+}
+
+void BoardWithQuickEntry::performCommit()
+{
+ emit setCandidateMove(m_localCandidateMove);
+ emit commit();
+}
+
+void BoardWithQuickEntry::reset()
+{
+ emit setCandidateMove(Quackle::Move::createNonmove());
+}
+
+void BoardWithQuickEntry::provideHelp()
+{
+ QMessageBox::information(this, tr("Entering Moves - Quackle"), QString("<html>") + tr("To enter a move, click on the board once or twice and start typing. Hold down the Shift key for blanks. To exchange, type something like \"exchange QWUV\" or \"pass\" into the move editor, then press the Enter key or click \"Enter move\".") + "</html>");
+}
+
+void BoardWithQuickEntry::processCommand(const QString &command)
+{
+ QStringList items(command.split(" ", QString::SkipEmptyParts));
+ Quackle::Move move(Quackle::Move::createNonmove());
+
+ if (items.size() <= 0)
+ {
+ provideHelp();
+ return;
+ }
+
+ const QString verb(items.first().toLower());
+
+ if (verb.startsWith("pass"))
+ move = Quackle::Move::createPassMove();
+ else
+ {
+ if (items.size() != 2)
+ {
+ provideHelp();
+ return;
+ }
+
+ if (verb.startsWith(tr("ex")))
+ {
+ QString letters = items.at(1);
+ bool isIntConvertable = false;
+ int exchangeLength = letters.toInt(&isIntConvertable);
+ bool isPass = false;
+
+ Quackle::LetterString encodedLetters;
+
+ if (isIntConvertable)
+ {
+ if (exchangeLength == 0)
+ {
+ isPass = true;
+ }
+ else
+ {
+ for (int i = 0; i < exchangeLength; ++i)
+ encodedLetters.push_back(QUACKLE_BLANK_MARK);
+ }
+ }
+ else
+ {
+ encodedLetters = QuackleIO::Util::nonBlankEncode(letters);
+ }
+
+ if (isPass)
+ move = Quackle::Move::createPassMove();
+ else
+ move = Quackle::Move::createExchangeMove(encodedLetters);
+ }
+ else
+ {
+ QString prettyLetters(items.at(1));
+ QString letters;
+
+ bool replace = false;
+ for (int i = 0; i < prettyLetters.length(); ++i)
+ {
+ QChar character = prettyLetters.at(i);
+ if (character == '(')
+ replace = true;
+ else if (character == ')')
+ replace = false;
+ else if (replace)
+ letters += ".";
+ else
+ letters += character;
+ }
+ move = Quackle::Move::createPlaceMove(QuackleIO::Util::qstringToString(items.first()), QuackleIO::Util::encode(letters));
+ }
+ }
+
+ if (move.isAMove())
+ emit setCandidateMove(move);
+}
+
+///////////
+
+TextBoard::TextBoard(QWidget *parent)
+ : BoardWithQuickEntry(parent)
+{
+ m_textEdit = new QTextEdit;
+ m_textEdit->setFontPointSize(16);
+ m_textEdit->setFontFamily("Courier");
+ m_vlayout->addWidget(m_textEdit);
+
+ m_textEdit->setReadOnly(true);
+}
+
+void TextBoard::positionChanged(const Quackle::GamePosition &position)
+{
+ BoardWithQuickEntry::positionChanged(position);
+ //m_textEdit->setHtml(QString("<html><font size=\"+4\"><pre>%1</pre></font></html>").arg(QuackleIO::Util::uvStringToQString(position.boardAfterMoveMade().toString())));
+ m_textEdit->setPlainText(QString("%1").arg(QuackleIO::Util::uvStringToQString(position.boardAfterMoveMade().toString())));
+}
diff --git a/quacker/boarddisplay.h b/quacker/boarddisplay.h
new file mode 100644
index 0000000..2b8d155
--- /dev/null
+++ b/quacker/boarddisplay.h
@@ -0,0 +1,84 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_BOARDDISPLAY_H
+#define QUACKER_BOARDDISPLAY_H
+
+#include <move.h>
+
+#include "view.h"
+
+class QLineEdit;
+class QPushButton;
+class QTextEdit;
+class QVBoxLayout;
+
+namespace Quackle
+{
+ class GamePosition;
+}
+
+class BoardWithQuickEntry : public View
+{
+Q_OBJECT
+
+public:
+ BoardWithQuickEntry(QWidget *parent = 0);
+ virtual ~BoardWithQuickEntry();
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition &position);
+
+protected slots:
+ void setLocalCandidate(const Quackle::Move &candidate);
+
+private slots:
+ void quickEditReturnPressed();
+ void plusFive();
+ void performCommit();
+ void reset();
+
+protected:
+ virtual void processCommand(const QString &command);
+ void provideHelp();
+
+ QVBoxLayout *m_vlayout;
+
+private:
+ QLineEdit *m_lineEdit;
+ QPushButton *m_commitButton;
+ Quackle::Move m_localCandidateMove;
+};
+
+class TextBoard : public BoardWithQuickEntry
+{
+Q_OBJECT
+
+public:
+ TextBoard(QWidget *parent = 0);
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition &position);
+
+private:
+ QTextEdit *m_textEdit;
+};
+
+#endif
diff --git a/quacker/boardsetup.cpp b/quacker/boardsetup.cpp
new file mode 100644
index 0000000..69b5097
--- /dev/null
+++ b/quacker/boardsetup.cpp
@@ -0,0 +1,164 @@
+/*
+ * 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 <iostream>
+#include <math.h>
+
+#include <QtGui>
+
+#include <game.h>
+#include <move.h>
+#include <boardparameters.h>
+#include <quackleio/util.h>
+
+#include "boardsetup.h"
+#include "geometry.h"
+
+BoardSetup::BoardSetup(QWidget *parent)
+ : View(parent)
+{
+ m_vlayout = new QVBoxLayout(this);
+ Geometry::setupInnerLayout(m_vlayout);
+
+ m_boardFrame = new BoardSetupFrame;
+ m_boardWrapper = new QWidget;
+
+ QLabel *helperLabel = new QLabel(tr("Click or right-click on a board square to cycle through bonuses. Shift-click on a square to designate it as the square that starts the game."));
+ helperLabel->setWordWrap(true);
+
+ QVBoxLayout *helperLayout = new QVBoxLayout(m_boardWrapper);
+ Geometry::setupInnerLayout(helperLayout);
+
+ m_vlayout->addWidget(helperLabel);
+ m_vlayout->addWidget(m_boardWrapper);
+ helperLayout->addWidget(m_boardFrame);
+ m_vlayout->setStretchFactor(m_boardWrapper, 10);
+
+ m_subviews.push_back(m_boardFrame);
+ connectSubviewSignals();
+}
+
+void BoardSetup::expandToFullWidth()
+{
+ m_boardFrame->expandToSize(m_boardWrapper->size());
+}
+
+void BoardSetup::resizeEvent(QResizeEvent * /* event */)
+{
+ QTimer::singleShot(0, this, SLOT(expandToFullWidth()));
+}
+
+///////////////////
+
+BoardSetupFrame::BoardSetupFrame(QWidget *parent)
+ : GraphicalBoardFrame(parent)
+{
+ m_alwaysShowVerboseLabels = true;
+}
+
+BoardSetupFrame::~BoardSetupFrame()
+{
+}
+
+
+void BoardSetupFrame::setBoard(const Quackle::Board &board)
+{
+ m_board = board;
+}
+
+void BoardSetupFrame::setSymmetry(bool horizontal, bool vertical)
+{
+ horizontalSymmetry = horizontal;
+ verticalSymmetry = vertical;
+}
+
+void BoardSetupFrame::parametersChanged()
+{
+ flushPixmapsAndRedraw();
+}
+
+void BoardSetupFrame::tileClicked(const QSize &tileLocation, const QMouseEvent *event)
+{
+ const int row = tileLocation.height();
+ const int col = tileLocation.width();
+
+ // set starting point...
+ if (event->button() == Qt::LeftButton && (event->modifiers() & Qt::SHIFT) != 0)
+ {
+ QUACKLE_BOARD_PARAMETERS->setStartRow(row);
+ QUACKLE_BOARD_PARAMETERS->setStartColumn(col);
+ prepare();
+ return;
+ }
+
+ // or change the value of a square
+ const int maxLetterMultiplier = (int) Quackle::BoardParameters::lsCount;
+ const int maxWordMultiplier = (int) Quackle::BoardParameters::wsCount;
+ const int maxMultiplier = maxLetterMultiplier + maxWordMultiplier - 1;
+ int wordMultiplier = QUACKLE_BOARD_PARAMETERS->wordMultiplier(row, col);
+ int letterMultiplier = QUACKLE_BOARD_PARAMETERS->letterMultiplier(row, col);
+ int combinedMultipliers = (wordMultiplier == 1) ? letterMultiplier : (maxLetterMultiplier + wordMultiplier - 1);
+
+ if (event->button() == Qt::LeftButton)
+ combinedMultipliers++;
+ else
+ combinedMultipliers--;
+
+ if (combinedMultipliers < 1)
+ combinedMultipliers = maxMultiplier;
+ else if (combinedMultipliers > maxMultiplier)
+ combinedMultipliers = 1;
+
+ if (combinedMultipliers <= maxLetterMultiplier)
+ {
+ wordMultiplier = 1;
+ letterMultiplier = combinedMultipliers;
+ }
+ else
+ {
+ wordMultiplier = combinedMultipliers - maxLetterMultiplier + 1;
+ letterMultiplier = 1;
+ }
+
+ setMultipliers(row, col, wordMultiplier, letterMultiplier);
+
+ const int height = QUACKLE_BOARD_PARAMETERS->height();
+ const int width = QUACKLE_BOARD_PARAMETERS->width();
+
+ if (horizontalSymmetry)
+ setMultipliers(row, width - 1 - col, wordMultiplier, letterMultiplier);
+ if (verticalSymmetry)
+ setMultipliers(height - 1 - row, col, wordMultiplier, letterMultiplier);
+ if (horizontalSymmetry && verticalSymmetry)
+ setMultipliers(height - 1 - row, width - 1 - col, wordMultiplier, letterMultiplier);
+
+ prepare();
+}
+
+bool BoardSetupFrame::wantMousePressEvent(const QMouseEvent *event) const
+{
+ return (event->button() == Qt::LeftButton || event->button() == Qt::RightButton);
+}
+
+void BoardSetupFrame::setMultipliers(int row, int col, int word, int letter)
+{
+ QUACKLE_BOARD_PARAMETERS->setWordMultiplier(row, col, (Quackle::BoardParameters::WordMultiplier) word);
+ QUACKLE_BOARD_PARAMETERS->setLetterMultiplier(row, col, (Quackle::BoardParameters::LetterMultiplier) letter);
+}
diff --git a/quacker/boardsetup.h b/quacker/boardsetup.h
new file mode 100644
index 0000000..ed335d1
--- /dev/null
+++ b/quacker/boardsetup.h
@@ -0,0 +1,77 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_BOARDSETUP_H
+#define QUACKER_BOARDSETUP_H
+
+#include "graphicalboard.h"
+
+class BoardSetupFrame;
+
+class BoardSetup : public View
+{
+Q_OBJECT
+
+public:
+ BoardSetup(QWidget *parent = 0);
+ ~BoardSetup() {};
+
+ BoardSetupFrame *boardFrame() { return m_boardFrame; }
+
+protected slots:
+ virtual void expandToFullWidth();
+ virtual void resizeEvent(QResizeEvent *event);
+
+private:
+ BoardSetupFrame *m_boardFrame;
+ QWidget *m_boardWrapper;
+ QVBoxLayout *m_vlayout;
+};
+
+class BoardSetupFrame : public GraphicalBoardFrame
+{
+Q_OBJECT
+
+public:
+ BoardSetupFrame(QWidget *parent = 0);
+ ~BoardSetupFrame();
+ void setBoard(const Quackle::Board &board);
+ void setSymmetry(bool horizontal, bool vertical);
+ void parametersChanged();
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition & /* position */) {};
+
+protected slots:
+ virtual void tileClicked(const QSize &tileLocation, const QMouseEvent *event);
+
+protected:
+ virtual void keyPressEvent(QKeyEvent * /* e */) {};
+
+ virtual bool wantMousePressEvent(const QMouseEvent *event) const;
+
+private:
+ bool horizontalSymmetry;
+ bool verticalSymmetry;
+
+ void setMultipliers(int row, int col, int word, int letter);
+};
+
+#endif
diff --git a/quacker/boardsetupdialog.cpp b/quacker/boardsetupdialog.cpp
new file mode 100644
index 0000000..5cbc8db
--- /dev/null
+++ b/quacker/boardsetupdialog.cpp
@@ -0,0 +1,232 @@
+/*
+ * 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 <sstream>
+#include <QtGui>
+#include <boardparameters.h>
+#include <board.h>
+#include <datamanager.h>
+#include <quackleio/util.h>
+
+#include "boardsetupdialog.h"
+#include "boardsetup.h"
+#include "brb.h"
+#include "customqsettings.h"
+#include "geometry.h"
+#include "widgetfactory.h"
+#include "settings.h"
+
+BoardSetupDialog::BoardSetupDialog(QWidget *parent) : QDialog(parent)
+{
+ resize(600,450);
+ setSizeGripEnabled(true);
+
+ // construct the board
+ BoardSetupFactory factory;
+ BRB * brb = new BRB(&factory);
+ m_boardFrame = static_cast<BoardSetup *>(brb->getBoardView())->boardFrame();
+ initializeBoardName();
+
+ // construct the UI elements
+ m_horizontalSymmetry = new QCheckBox(tr("Horizontal"));
+ m_verticalSymmetry = new QCheckBox(tr("Vertical"));
+ m_horizontalSymmetry->setCheckState(Qt::Checked);
+ m_verticalSymmetry->setCheckState(Qt::Checked);
+
+ m_horizontalDimension = constructDimensionComboBox(QUACKLE_BOARD_PARAMETERS->width());
+ m_verticalDimension = constructDimensionComboBox(QUACKLE_BOARD_PARAMETERS->height());
+
+ m_boardName = new QLineEdit(QuackleIO::Util::uvStringToQString(QUACKLE_BOARD_PARAMETERS->name()));
+
+ m_saveChanges = new QPushButton(tr("&Save Changes"));
+ m_cancel = new QPushButton(tr("&Cancel"));
+ m_undoAll = new QPushButton(tr("&Undo All Changes"));
+
+ QVBoxLayout * superLayout = new QVBoxLayout;
+ Geometry::setupFramedLayout(superLayout);
+ QHBoxLayout * mainLayout = new QHBoxLayout;
+ Geometry::setupInnerLayout(mainLayout);
+ QVBoxLayout * leftSideLayout = new QVBoxLayout;
+ Geometry::setupInnerLayout(leftSideLayout);
+ QLabel * boardNameLabel = new QLabel(tr("&Board name:"));
+ boardNameLabel->setBuddy(m_boardName);
+ QGroupBox * dimensionGroup = new QGroupBox(tr("Board dimensions"));
+ QHBoxLayout * dimensionRow = new QHBoxLayout(dimensionGroup);
+ Geometry::setupFramedLayout(dimensionRow);
+ QLabel * dimensionLabel = new QLabel(tr(" by "));
+ QGroupBox * symmetryGroup = new QGroupBox(tr("Board symmetry"));
+ QVBoxLayout * symmetryCol = new QVBoxLayout(symmetryGroup);
+ Geometry::setupFramedLayout(symmetryCol);
+ QHBoxLayout * buttonRow = new QHBoxLayout;
+ Geometry::setupInnerLayout(buttonRow);
+
+ // build the layout
+ dimensionRow->addWidget(m_horizontalDimension);
+ dimensionRow->addWidget(dimensionLabel);
+ dimensionRow->addWidget(m_verticalDimension);
+ dimensionRow->addStretch();
+
+ symmetryCol->addWidget(m_horizontalSymmetry);
+ symmetryCol->addWidget(m_verticalSymmetry);
+
+ buttonRow->addStretch(1);
+ buttonRow->addWidget(m_cancel);
+ buttonRow->addWidget(m_saveChanges);
+
+ leftSideLayout->addWidget(boardNameLabel);
+ leftSideLayout->addWidget(m_boardName);
+ leftSideLayout->addWidget(dimensionGroup);
+ leftSideLayout->addWidget(symmetryGroup);
+ leftSideLayout->addWidget(m_undoAll);
+ leftSideLayout->addStretch();
+
+ mainLayout->addLayout(leftSideLayout);
+ mainLayout->addWidget(brb);
+ mainLayout->setStretchFactor(leftSideLayout, 0);
+ mainLayout->setStretchFactor(brb, 10);
+
+ superLayout->addLayout(mainLayout);
+ superLayout->addLayout(buttonRow);
+
+ setLayout(superLayout);
+ m_saveChanges->setDefault(true);
+
+ // hook up signals and slots
+ connect(m_horizontalDimension, SIGNAL(activated(const QString &)), this, SLOT(parametersChanged(const QString &)));
+ connect(m_verticalDimension, SIGNAL(activated(const QString &)), this, SLOT(parametersChanged(const QString &)));
+ connect(m_boardName, SIGNAL(textEdited(const QString &)), this, SLOT(parametersChanged(const QString &)));
+ 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_horizontalSymmetry, SIGNAL(stateChanged(int)), this, SLOT(symmetryChanged()));
+ connect(m_verticalSymmetry, SIGNAL(stateChanged(int)), this, SLOT(symmetryChanged()));
+
+ setWindowTitle(tr("Configure Board - Quackle"));
+
+ // sync game board with control states and draw board
+ ostringstream boardStream;
+ QUACKLE_BOARD_PARAMETERS->Serialize(boardStream);
+ m_serializedOriginalBoard = boardStream.str();
+
+ parametersChanged(QString());
+ symmetryChanged();
+}
+
+BoardSetupDialog::~BoardSetupDialog()
+{
+}
+
+QComboBox * BoardSetupDialog::constructDimensionComboBox(int defaultDimension)
+{
+ QComboBox * returnValue = new QComboBox;
+
+ for (int i = QUACKLE_MINIMUM_BOARD_SIZE; i <= QUACKLE_MAXIMUM_BOARD_SIZE; i++)
+ {
+ returnValue->addItem(QString::number(i));
+ if (i == defaultDimension)
+ returnValue->setCurrentIndex(returnValue->count() - 1);
+ }
+
+ return returnValue;
+}
+
+void BoardSetupDialog::initializeBoardName()
+{
+ m_originalName = QuackleIO::Util::uvStringToQString(QUACKLE_BOARD_PARAMETERS->name());
+
+ if (m_originalName.isEmpty())
+ {
+ CustomQSettings settings;
+ QString generatedName = "New Board";
+ int i = 1;
+ settings.beginGroup("quackle/boardparameters");
+ while (settings.contains(generatedName))
+ {
+ generatedName = "New Board ";
+ generatedName += QString::number(i++);
+ }
+ QUACKLE_BOARD_PARAMETERS->setName(QuackleIO::Util::qstringToString(generatedName));
+ }
+}
+
+void BoardSetupDialog::parametersChanged(const QString &)
+{
+ QUACKLE_BOARD_PARAMETERS->setWidth(m_horizontalDimension->currentIndex() + QUACKLE_MINIMUM_BOARD_SIZE);
+ QUACKLE_BOARD_PARAMETERS->setHeight(m_verticalDimension->currentIndex() + QUACKLE_MINIMUM_BOARD_SIZE);
+
+ if (QUACKLE_BOARD_PARAMETERS->startColumn() >= QUACKLE_BOARD_PARAMETERS->width())
+ QUACKLE_BOARD_PARAMETERS->setStartColumn(QUACKLE_BOARD_PARAMETERS->width() - 1);
+ if (QUACKLE_BOARD_PARAMETERS->startRow() >= QUACKLE_BOARD_PARAMETERS->height())
+ QUACKLE_BOARD_PARAMETERS->setStartRow(QUACKLE_BOARD_PARAMETERS->height() - 1);
+ UVString boardName = QuackleIO::Util::qstringToString(m_boardName->text());
+ QUACKLE_BOARD_PARAMETERS->setName(boardName);
+
+ m_game.reset();
+ m_game.addPosition();
+ m_boardFrame->setBoard(m_game.currentPosition().board());
+ m_boardFrame->parametersChanged();
+}
+
+void BoardSetupDialog::symmetryChanged()
+{
+ m_boardFrame->setSymmetry(
+ m_horizontalSymmetry->checkState() == Qt::Checked,
+ m_verticalSymmetry->checkState() == Qt::Checked);
+}
+
+void BoardSetupDialog::accept()
+{
+ if (m_boardName->text().isEmpty())
+ {
+ QMessageBox::critical(this, tr("Missing board name"),
+ "You must type in a board name before saving this change.",
+ QMessageBox::Ok, QMessageBox::NoButton);
+ return;
+ }
+ else if (m_boardName->text() != m_originalName)
+ {
+ CustomQSettings settings;
+ settings.beginGroup("quackle/boardparameters");
+ if (settings.contains(m_boardName->text()))
+ {
+ if (QMessageBox::warning(this, tr("Overwrite existing board?"),
+ tr("You've specified a board name which already exists. Do you want to overwrite the existing board?"),
+ QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
+ return;
+ }
+ }
+
+ PixmapCacher::self()->invalidate();
+ QDialog::accept();
+}
+
+void BoardSetupDialog::reject()
+{
+ undoAllChanges();
+ QDialog::reject();
+}
+
+void BoardSetupDialog::undoAllChanges()
+{
+ istringstream boardStream(m_serializedOriginalBoard);
+
+ QUACKLE_DATAMANAGER->setBoardParameters(Quackle::BoardParameters::Deserialize(boardStream));
+ parametersChanged(QString());
+}
diff --git a/quacker/boardsetupdialog.h b/quacker/boardsetupdialog.h
new file mode 100644
index 0000000..6c58bc5
--- /dev/null
+++ b/quacker/boardsetupdialog.h
@@ -0,0 +1,78 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_BOARDSETUPDIALOG_H
+#define QUACKER_BOARDSETUPDIALOG_H
+
+#include <string>
+#include <game.h>
+
+#include <QWidget>
+#include <QDialog>
+
+using namespace std;
+
+class QCheckBox;
+class QComboBox;
+class QLineEdit;
+class QPushButton;
+
+class BoardSetupFrame;
+
+class BoardSetupDialog : public QDialog
+{
+Q_OBJECT
+
+public:
+ BoardSetupDialog(QWidget *parent = 0);
+ ~BoardSetupDialog();
+ virtual void accept();
+ virtual void reject();
+
+protected slots:
+ void parametersChanged(const QString &unused);
+ void symmetryChanged();
+ void undoAllChanges();
+
+private:
+ QCheckBox *m_horizontalSymmetry;
+ QCheckBox *m_verticalSymmetry;
+
+ QComboBox *m_horizontalDimension;
+ QComboBox *m_verticalDimension;
+
+ QLineEdit *m_boardName;
+
+ QPushButton *m_saveChanges;
+ QPushButton *m_cancel;
+ QPushButton *m_undoAll;
+
+ Quackle::Game m_game;
+ BoardSetupFrame * m_boardFrame;
+
+ QString m_originalName;
+
+ string m_serializedOriginalBoard;
+
+ QComboBox * constructDimensionComboBox(int defaultDimension);
+ void initializeBoardName();
+};
+
+#endif
diff --git a/quacker/brb.cpp b/quacker/brb.cpp
new file mode 100644
index 0000000..3a564f8
--- /dev/null
+++ b/quacker/brb.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 <QtGui>
+
+#include <game.h>
+
+#include "bagdisplay.h"
+#include "brb.h"
+#include "boarddisplay.h"
+#include "geometry.h"
+#include "rackdisplay.h"
+#include "widgetfactory.h"
+
+BRB::BRB(WidgetFactory *widgetFactory, QWidget *parent)
+ : View(parent)
+{
+ QHBoxLayout *topHorizontalLayout = new QHBoxLayout;
+ Geometry::setupFramedLayout(topHorizontalLayout);
+ setLayout(topHorizontalLayout);
+
+ QVBoxLayout *leftVerticalLayout = new QVBoxLayout;
+ Geometry::setupInnerLayout(leftVerticalLayout);
+ topHorizontalLayout->addLayout(leftVerticalLayout);
+ QVBoxLayout *rightVerticalLayout = new QVBoxLayout;
+ Geometry::setupInnerLayout(rightVerticalLayout);
+ topHorizontalLayout->addLayout(rightVerticalLayout);
+
+ m_boardDisplay = widgetFactory->createBoardDisplay();
+ leftVerticalLayout->addWidget(m_boardDisplay);
+
+ m_rackDisplay = widgetFactory->createRackDisplay();
+ leftVerticalLayout->addWidget(m_rackDisplay);
+
+ topHorizontalLayout->setStretchFactor(leftVerticalLayout, 10);
+
+ m_bagDisplay = widgetFactory->createBagDisplay();
+ rightVerticalLayout->addWidget(m_bagDisplay);
+
+ m_subviews.push_back(m_boardDisplay);
+ m_subviews.push_back(m_rackDisplay);
+ m_subviews.push_back(m_bagDisplay);
+ connectSubviewSignals();
+}
+
+BRB::~BRB()
+{
+}
+
+View * BRB::getBoardView() const
+{
+ return m_boardDisplay;
+}
+
+void BRB::grabFocus()
+{
+ m_rackDisplay->grabFocus();
+}
+
+void BRB::positionChanged(const Quackle::GamePosition &position)
+{
+ View::positionChanged(position);
+}
+
diff --git a/quacker/brb.h b/quacker/brb.h
new file mode 100644
index 0000000..dd1bcc0
--- /dev/null
+++ b/quacker/brb.h
@@ -0,0 +1,54 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_BRB_H
+#define QUACKER_BRB_H
+
+#include <QWidget>
+
+#include "view.h"
+
+namespace Quackle
+{
+ class GamePosition;
+}
+
+class WidgetFactory;
+
+class BRB : public View
+{
+Q_OBJECT
+
+public:
+ BRB(WidgetFactory *widgetFactory, QWidget *parent = 0);
+ virtual ~BRB();
+ View * getBoardView() const;
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition &position);
+ virtual void grabFocus();
+
+private:
+ View *m_boardDisplay;
+ View *m_rackDisplay;
+ View *m_bagDisplay;
+};
+
+#endif
diff --git a/quacker/configdialog.cpp b/quacker/configdialog.cpp
new file mode 100644
index 0000000..e5c6ed4
--- /dev/null
+++ b/quacker/configdialog.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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 <QtGui>
+
+#include "configpages.h"
+#include "configdialog.h"
+
+ConfigPage::ConfigPage(QWidget *parent)
+ : QWidget(parent)
+{
+}
+
+ConfigDialog::ConfigDialog()
+{
+ m_contentsWidget = new QListWidget;
+ m_contentsWidget->setMovement(QListView::Static);
+
+ m_pagesWidget = new QStackedWidget;
+
+ m_configPages.push_back(new InterfacePage);
+ //m_configPages.push_back(new LetterboxPage);
+
+ for (QList<ConfigPage *>::iterator it = m_configPages.begin(); it != m_configPages.end(); ++it)
+ {
+ m_pagesWidget->addWidget(*it);
+ (*it)->readConfig();
+
+ QListWidgetItem *button = new QListWidgetItem((*it)->pageTitle(), m_contentsWidget);
+ button->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+ }
+
+ connect(m_contentsWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(changePage(QListWidgetItem *, QListWidgetItem*)));
+
+ QPushButton *applyButton = new QPushButton(tr("&Apply"));
+ QPushButton *okButton = new QPushButton(tr("&OK"));
+ QPushButton *cancelButton = new QPushButton(tr("&Cancel"));
+ connect(cancelButton, SIGNAL(clicked()), this, SLOT(close()));
+ connect(okButton, SIGNAL(clicked()), this, SLOT(submit()));
+ connect(applyButton, SIGNAL(clicked()), this, SLOT(apply()));
+
+ m_contentsWidget->setCurrentRow(0);
+
+ QHBoxLayout *horizontalLayout = new QHBoxLayout;
+ horizontalLayout->addWidget(m_contentsWidget);
+ horizontalLayout->addWidget(m_pagesWidget, 1);
+ horizontalLayout->setStretchFactor(m_pagesWidget, 2);
+
+ QHBoxLayout *buttonsLayout = new QHBoxLayout;
+ buttonsLayout->addStretch(1);
+ buttonsLayout->addWidget(okButton);
+ buttonsLayout->addWidget(applyButton);
+ buttonsLayout->addWidget(cancelButton);
+
+ QVBoxLayout *mainLayout = new QVBoxLayout;
+ mainLayout->addLayout(horizontalLayout);
+ mainLayout->addStretch(1);
+ mainLayout->addSpacing(12);
+ mainLayout->addLayout(buttonsLayout);
+ setLayout(mainLayout);
+
+ setWindowTitle(tr("Configure - Quackle"));
+}
+
+void ConfigDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous)
+{
+ if (!current)
+ current = previous;
+
+ m_pagesWidget->setCurrentIndex(m_contentsWidget->row(current));
+}
+
+void ConfigDialog::apply()
+{
+ for (QList<ConfigPage *>::iterator it = m_configPages.begin(); it != m_configPages.end(); ++it)
+ {
+ (*it)->writeConfig();
+ }
+
+ emit refreshViews();
+}
+
+void ConfigDialog::submit()
+{
+ apply();
+ close();
+}
+
diff --git a/quacker/configdialog.h b/quacker/configdialog.h
new file mode 100644
index 0000000..3bcdd7e
--- /dev/null
+++ b/quacker/configdialog.h
@@ -0,0 +1,68 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_CONFIG_DIALOG_H
+#define QUACKER_CONFIG_DIALOG_H
+
+#include <QDialog>
+
+class QListWidget;
+class QListWidgetItem;
+class QStackedWidget;
+
+class ConfigPage : public QWidget
+{
+Q_OBJECT
+
+public:
+ ConfigPage(QWidget *parent = 0);
+
+ virtual void readConfig() = 0;
+ virtual void writeConfig() = 0;
+
+ QString pageTitle() const { return m_pageTitle; };
+
+protected:
+ QString m_pageTitle;
+};
+
+class ConfigDialog : public QDialog
+{
+Q_OBJECT
+
+public:
+ ConfigDialog();
+
+signals:
+ void refreshViews();
+
+public slots:
+ void changePage(QListWidgetItem *current, QListWidgetItem *previous);
+ void apply();
+ void submit();
+
+private:
+ QListWidget *m_contentsWidget;
+ QStackedWidget *m_pagesWidget;
+ QList<ConfigPage *> m_configPages;
+};
+
+#endif
+
diff --git a/quacker/configpages.cpp b/quacker/configpages.cpp
new file mode 100644
index 0000000..ff6d991
--- /dev/null
+++ b/quacker/configpages.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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 <QtGui>
+
+#include <uv.h>
+
+#include "configpages.h"
+#include "letterboxsettings.h"
+#include "quackersettings.h"
+
+LetterboxPage::LetterboxPage(QWidget *parent)
+ : ConfigPage(parent)
+{
+ m_pageTitle = tr("Letterbox");
+
+ QGroupBox *timingsGroup = new QGroupBox(tr("Quiz timings"));
+
+ QLabel *baseLabel = new QLabel(tr("Initial wait:"));
+ m_baseSpin = new QSpinBox;
+ m_baseSpin->setSuffix(tr(" msec"));
+ m_baseSpin->setMinimum(0);
+ m_baseSpin->setMaximum(100000);
+ m_baseSpin->setSingleStep(500);
+
+ QLabel *extraLabel = new QLabel(tr("Extra wait per anagram:"));
+ m_extraSpin = new QSpinBox;
+ m_extraSpin->setSuffix(tr(" msec"));
+ m_extraSpin->setMinimum(0);
+ m_extraSpin->setMaximum(100000);
+ m_extraSpin->setSingleStep(500);
+
+ QGridLayout *timingsLayout = new QGridLayout;
+ timingsLayout->addWidget(baseLabel, 0, 0);
+ timingsLayout->addWidget(m_baseSpin, 0, 1);
+ timingsLayout->addWidget(extraLabel, 1, 0);
+ timingsLayout->addWidget(m_extraSpin, 1, 1);
+ timingsGroup->setLayout(timingsLayout);
+
+ QVBoxLayout *mainLayout = new QVBoxLayout;
+ mainLayout->addWidget(timingsGroup);
+ mainLayout->addStretch(1);
+ setLayout(mainLayout);
+}
+
+void LetterboxPage::readConfig()
+{
+ m_baseSpin->setValue(LetterboxSettings::self()->msecWaitBase);
+ m_extraSpin->setValue(LetterboxSettings::self()->msecWaitExtraPerSolution);
+}
+
+void LetterboxPage::writeConfig()
+{
+ LetterboxSettings::self()->msecWaitBase = m_baseSpin->value();
+ LetterboxSettings::self()->msecWaitExtraPerSolution = m_extraSpin->value();
+}
+
+InterfacePage::InterfacePage(QWidget *parent)
+ : ConfigPage(parent)
+{
+ m_pageTitle = tr("Interface");
+
+ QGroupBox *boardGroup = new QGroupBox(tr("Board"));
+
+ m_verboseLabelsCheck = new QCheckBox(tr("Show &bonus square labels"));
+ m_scoreLabelsCheck = new QCheckBox(tr("Show &letter score labels"));
+
+ QLabel *britishColoringLabel = new QLabel(tr("British coloring:"));
+ m_britishColoringCombo = new QComboBox;
+ QStringList britishColorings;
+ britishColorings << tr("None") << tr("Text") << tr("Tile");
+ m_britishColoringCombo->addItems(britishColorings);
+
+ QGridLayout *checkersLayout = new QGridLayout;
+ checkersLayout->addWidget(britishColoringLabel, 0, 0);
+ checkersLayout->addWidget(m_britishColoringCombo, 0, 1);
+ checkersLayout->addWidget(m_verboseLabelsCheck, 1, 0, 1, 2);
+ checkersLayout->addWidget(m_scoreLabelsCheck, 2, 0, 1, 2);
+ boardGroup->setLayout(checkersLayout);
+
+ QGroupBox *miscellanyGroup = new QGroupBox(tr("Miscellany"));
+ m_vowelFirstCheck = new QCheckBox(tr("&Vowel-first alphabetizing"));
+ m_octothorpCheck = new QCheckBox(tr("&Octothorp British words"));
+
+ QGridLayout *miscellanyLayout = new QGridLayout;
+ miscellanyLayout->addWidget(m_vowelFirstCheck, 0, 0);
+ miscellanyLayout->addWidget(m_octothorpCheck, 1, 0);
+ miscellanyGroup->setLayout(miscellanyLayout);
+
+ QVBoxLayout *mainLayout = new QVBoxLayout;
+ mainLayout->addWidget(boardGroup);
+ mainLayout->addWidget(miscellanyGroup);
+ mainLayout->addStretch(1);
+ setLayout(mainLayout);
+}
+
+void InterfacePage::readConfig()
+{
+ m_britishColoringCombo->setCurrentIndex(QuackerSettings::self()->britishColoring);
+ m_vowelFirstCheck->setChecked(QuackleIO::UtilSettings::self()->vowelFirst);
+ m_verboseLabelsCheck->setChecked(QuackerSettings::self()->verboseLabels);
+ m_scoreLabelsCheck->setChecked(QuackerSettings::self()->scoreLabels);
+ m_octothorpCheck->setChecked(QuackleIO::UtilSettings::self()->octothorpBritish);
+}
+
+void InterfacePage::writeConfig()
+{
+ QuackerSettings::self()->britishColoring = m_britishColoringCombo->currentIndex();
+ QuackleIO::UtilSettings::self()->vowelFirst = m_vowelFirstCheck->isChecked();
+ QuackerSettings::self()->verboseLabels = m_verboseLabelsCheck->isChecked();
+ QuackerSettings::self()->scoreLabels = m_scoreLabelsCheck->isChecked();
+ QuackleIO::UtilSettings::self()->octothorpBritish = m_octothorpCheck->isChecked();
+}
+
diff --git a/quacker/configpages.h b/quacker/configpages.h
new file mode 100644
index 0000000..a2b12f3
--- /dev/null
+++ b/quacker/configpages.h
@@ -0,0 +1,60 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_CONFIG_PAGES_H
+#define QUACKER_CONFIG_PAGES_H
+
+#include <QWidget>
+
+#include "configdialog.h"
+
+class QComboBox;
+class QSpinBox;
+
+class LetterboxPage : public ConfigPage
+{
+public:
+ LetterboxPage(QWidget *parent = 0);
+
+ virtual void readConfig();
+ virtual void writeConfig();
+
+private:
+ QSpinBox *m_baseSpin;
+ QSpinBox *m_extraSpin;
+};
+
+class InterfacePage : public ConfigPage
+{
+public:
+ InterfacePage(QWidget *parent = 0);
+
+ virtual void readConfig();
+ virtual void writeConfig();
+
+private:
+ QCheckBox *m_vowelFirstCheck;
+ QCheckBox *m_verboseLabelsCheck;
+ QCheckBox *m_scoreLabelsCheck;
+ QCheckBox *m_octothorpCheck;
+ QComboBox *m_britishColoringCombo;
+};
+
+#endif
diff --git a/quacker/customqsettings.h b/quacker/customqsettings.h
new file mode 100644
index 0000000..97fd256
--- /dev/null
+++ b/quacker/customqsettings.h
@@ -0,0 +1,37 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_CUSTOMQSETTINGS_H
+#define QUACKER_CUSTOMQSETTINGS_H
+
+class CustomQSettings : public QSettings
+{
+public:
+ CustomQSettings() :
+#if defined(Q_WS_WIN)
+ QSettings((QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based) ? IniFormat : NativeFormat,
+ UserScope, tr("Quackle"))
+#else
+ QSettings()
+#endif
+ {}
+};
+
+#endif
diff --git a/quacker/dashboard.cpp b/quacker/dashboard.cpp
new file mode 100644
index 0000000..b3abed4
--- /dev/null
+++ b/quacker/dashboard.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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 <QtGui>
+
+#include <game.h>
+#include <quackleio/util.h>
+
+#include "geometry.h"
+#include "dashboard.h"
+
+PlayerBrief::PlayerBrief()
+ : m_isCurrent(false), m_winnerStatus(Nonwinner)
+{
+ m_vlayout = new QVBoxLayout(this);
+ Geometry::setupFramedLayout(m_vlayout);
+
+ m_name = new QLabel;
+ m_score = new QLabel;
+
+ m_name->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+ m_score->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ m_vlayout->addWidget(m_name);
+ m_vlayout->addWidget(m_score);
+}
+
+void PlayerBrief::setPlayer(const Quackle::Player &player)
+{
+ QString nameText = (m_isCurrent? "<b>%1</b>" : "%1");
+ nameText = nameText.arg(QuackleIO::Util::uvStringToQString(player.name()));
+
+ if (m_winnerStatus == Winner || m_winnerStatus == Cowinner)
+ {
+ nameText += QString("<h3>%1</h3>").arg(m_winnerStatus == Winner? tr("Winner!") : tr("Cowinner"));
+ }
+
+ m_name->setText(nameText);
+ m_score->setText(QString("<h2>%1</h2>").arg(player.score()));
+
+ if (m_isCurrent)
+ {
+ setFrameStyle(QFrame::Panel | QFrame::Sunken);
+ setLineWidth(2);
+ }
+ else
+ {
+ setFrameStyle(QFrame::NoFrame);
+ }
+
+}
+
+////////////
+
+Dashboard::Dashboard(QWidget *parent)
+ : HistoryView(parent)
+{
+ m_hlayout = new QHBoxLayout(this);
+ Geometry::setupInnerLayout(m_hlayout);
+}
+
+Dashboard::~Dashboard()
+{
+}
+
+void Dashboard::historyChanged(const Quackle::History &history)
+{
+ const Quackle::PlayerList players(history.currentPosition().endgameAdjustedScores());
+ const int numberOfPlayers = players.size();
+ const bool gameOver = history.currentPosition().gameOver();
+
+ while (m_briefs.size() > numberOfPlayers)
+ {
+ delete m_briefs.back();
+ m_briefs.pop_back();
+ }
+
+ while (m_briefs.size() < numberOfPlayers)
+ {
+ m_briefs.push_back(new PlayerBrief);
+ m_hlayout->addWidget(m_briefs.back());
+ m_briefs.back()->show();
+ }
+
+ Quackle::PlayerList winners;
+ if (gameOver)
+ winners = history.currentPosition().leadingPlayers();
+
+ int playerCountFromZero = 0;
+ QList<PlayerBrief *>::iterator briefIt = m_briefs.begin();
+ for (Quackle::PlayerList::const_iterator playerIt = players.begin(); playerIt != players.end(); ++playerIt, ++briefIt, ++playerCountFromZero)
+ {
+ const bool isCurrentPlayer = !gameOver && *playerIt == history.currentPosition().playerOnTurn();
+
+ (*briefIt)->setCurrentPlayer(isCurrentPlayer);
+ (*briefIt)->setWinnerStatus(Nonwinner);
+
+ if (gameOver)
+ {
+ Quackle::PlayerList::const_iterator winnersIt;
+ for (winnersIt = winners.begin(); winnersIt != winners.end(); ++winnersIt)
+ {
+ if (*playerIt == *winnersIt)
+ {
+ (*briefIt)->setWinnerStatus(winners.size() > 1? Cowinner : Winner);
+ break;
+ }
+ }
+ }
+
+ (*briefIt)->setPlayer(*playerIt);
+ }
+}
+
diff --git a/quacker/dashboard.h b/quacker/dashboard.h
new file mode 100644
index 0000000..7f33d7d
--- /dev/null
+++ b/quacker/dashboard.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_DASHBOARD_H
+#define QUACKER_DASHBOARD_H
+
+#include <QList>
+
+#include <game.h>
+
+#include "view.h"
+
+class QFrame;
+class QHBoxLayout;
+class QLabel;
+class QVBoxLayout;
+
+enum WinnerStatus { Winner = 0, Cowinner, Nonwinner };
+
+class PlayerBrief : public QFrame
+{
+Q_OBJECT
+
+public:
+ PlayerBrief();
+
+ void setCurrentPlayer(bool isCurrent);
+ void setWinnerStatus(WinnerStatus isWinner);
+ void setPlayer(const Quackle::Player &player);
+
+private:
+ QVBoxLayout *m_vlayout;
+
+ bool m_isCurrent;
+ WinnerStatus m_winnerStatus;
+
+ QLabel *m_name;
+ QLabel *m_score;
+};
+
+inline void PlayerBrief::setCurrentPlayer(bool isCurrent)
+{
+ m_isCurrent = isCurrent;
+}
+
+inline void PlayerBrief::setWinnerStatus(WinnerStatus isWinner)
+{
+ m_winnerStatus = isWinner;
+}
+
+class Dashboard : public HistoryView
+{
+Q_OBJECT
+
+public:
+ Dashboard(QWidget *parent = 0);
+ virtual ~Dashboard();
+
+public slots:
+ virtual void historyChanged(const Quackle::History &history);
+
+protected:
+ QList<PlayerBrief *> m_briefs;
+
+ QHBoxLayout *m_hlayout;
+};
+
+#endif
diff --git a/quacker/geometry.cpp b/quacker/geometry.cpp
new file mode 100644
index 0000000..8d9e93c
--- /dev/null
+++ b/quacker/geometry.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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 <QtGui>
+
+#include "geometry.h"
+
+void Geometry::setupFramedLayout(QBoxLayout *layout)
+{
+}
+
+void Geometry::setupInnerLayout(QBoxLayout *layout)
+{
+ layout->setMargin(0);
+}
diff --git a/quacker/geometry.h b/quacker/geometry.h
new file mode 100644
index 0000000..53e7040
--- /dev/null
+++ b/quacker/geometry.h
@@ -0,0 +1,36 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_GEOMETRY_H
+#define QUACKER_GEOMETRY_H
+
+class QBoxLayout;
+
+class Geometry
+{
+ public:
+ // set up layout that has a frame around it
+ static void setupFramedLayout(QBoxLayout *layout);
+
+ // set up layout that has no frame around it
+ static void setupInnerLayout(QBoxLayout *layout);
+};
+
+#endif
diff --git a/quacker/graphicalboard.cpp b/quacker/graphicalboard.cpp
new file mode 100644
index 0000000..194d4c9
--- /dev/null
+++ b/quacker/graphicalboard.cpp
@@ -0,0 +1,1494 @@
+/*
+ * 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 <iostream>
+#include <math.h>
+
+#include <QtGui>
+
+#include <game.h>
+#include <move.h>
+#include <boardparameters.h>
+#include <quackleio/util.h>
+
+#include "geometry.h"
+#include "graphicalboard.h"
+#include "quackersettings.h"
+#include "settings.h"
+
+const double GraphicalBoardFrame::s_markOtherLengthMultiplier = 0.6;
+
+const double TileWidget::s_defaultLetterScale = 0.7;
+
+GraphicalBoard::GraphicalBoard(QWidget *parent)
+ : BoardWithQuickEntry(parent)
+{
+ m_boardFrame = new GraphicalBoardFrame;
+ connect(m_boardFrame, SIGNAL(localCandidateChanged(const Quackle::Move &)), this, SLOT(setLocalCandidate(const Quackle::Move &)));
+
+ m_boardWrapper = new QWidget;
+
+ QVBoxLayout *helperLayout = new QVBoxLayout(m_boardWrapper);
+ Geometry::setupInnerLayout(helperLayout);
+
+ m_vlayout->addWidget(m_boardWrapper);
+ helperLayout->addWidget(m_boardFrame);
+ m_vlayout->setStretchFactor(m_boardWrapper, 10);
+
+ m_subviews.push_back(m_boardFrame);
+ connectSubviewSignals();
+}
+
+GraphicalBoard::~GraphicalBoard()
+{
+}
+
+void GraphicalBoard::expandToFullWidth()
+{
+ m_boardFrame->expandToSize(m_boardWrapper->size());
+}
+
+void GraphicalBoard::resizeEvent(QResizeEvent * /* event */)
+{
+ QTimer::singleShot(0, this, SLOT(expandToFullWidth()));
+}
+
+///////////////////
+
+GraphicalBoardFrame::GraphicalBoardFrame(QWidget *parent)
+ : View(parent), m_ignoreRack(false), m_alwaysShowVerboseLabels(false), m_boardSize(0, 0), m_sideLength(0)
+{
+ setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
+ setLineWidth(2);
+
+ setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ setFocusPolicy(Qt::StrongFocus);
+
+ QColor color(PixmapCacher::self()->markColor);
+ QPalette customPalette;
+ customPalette.setColor(QPalette::Light, color.light(s_highlightFactor));
+ customPalette.setColor(QPalette::Mid, color);
+ customPalette.setColor(QPalette::Dark, color.dark(130));
+ setPalette(customPalette);
+
+ PixmapCacher::self()->tileFont = font();
+#if defined(Q_WS_WIN)
+ PixmapCacher::self()->tileFont.setFamily(QString("Arial"));
+#endif // Q_WS_WIN
+
+ //expandToSize(QSize(15 * 25, 15 * 25));
+
+ m_board.prepareEmptyBoard();
+ m_candidate = Quackle::Move::createNonmove();
+ resetArrow();
+}
+
+GraphicalBoardFrame::~GraphicalBoardFrame()
+{
+ PixmapCacher::cleanUp();
+}
+
+void GraphicalBoardFrame::staticDrawPosition(const Quackle::GamePosition &position, const QSize &size, QPixmap *pixmap)
+{
+ GraphicalBoardFrame doozy;
+ doozy.positionChanged(position);
+ doozy.expandToSize(size);
+ doozy.generateBoardPixmap(pixmap);
+}
+
+void GraphicalBoardFrame::positionChanged(const Quackle::GamePosition &position)
+{
+ m_board = position.board();
+ m_rack = position.currentPlayer().rack();
+ m_ignoreRack = !position.currentPlayer().racksAreKnown();
+
+ m_board.updateBritishness();
+
+ resetArrow();
+ m_candidate = position.moveMade();
+
+ prepare();
+}
+
+void GraphicalBoardFrame::prepare()
+{
+ drawBoard(m_board);
+ drawMove(m_candidate);
+
+ if (isOnBoard(m_arrowRoot))
+ drawArrow(m_arrowRoot, m_arrowDirection);
+
+ generateBoardPixmap(&m_pixmap);
+ update();
+}
+
+void GraphicalBoardFrame::expandToSize(const QSize &maxSize)
+{
+ m_maxSize = maxSize;
+
+ // if empty we delay this call
+ if (m_boardSize.isEmpty())
+ return;
+ else
+ {
+ // TODO we get here too often while resizing
+
+ // do calculations in terms of one axis, arbitrarily, width
+ QSize lastCandidate;
+ QSize candidate(0, 0);
+ do
+ {
+ lastCandidate = candidate;
+ candidate += m_boardSize;
+ }
+ while (candidate.width() < m_maxSize.width() && candidate.height() < m_maxSize.height());
+
+ const int maxWidth = lastCandidate.width();
+
+ const double numBlocksWidthwise = ((double)m_boardSize.width() + s_markOtherLengthMultiplier);
+
+ m_sideLength = (int)floor((maxWidth - frameWidth() * 2) / numBlocksWidthwise);
+
+ if (m_sideLength < 0)
+ m_sideLength = 0;
+
+ resizeWidgets(m_sideLength);
+
+ const int shorterMarkWidth = markAt(QSize(0, 0))->size().width();
+ const int shorterMarkHeight = markAt(QSize(0, 0))->size().height();
+ m_tilesOffset.setX(shorterMarkWidth);
+ m_tilesOffset.setY(shorterMarkHeight);
+
+ m_sizeForBoard.setWidth(shorterMarkWidth + m_boardSize.width() * m_sideLength);
+ m_sizeForBoard.setHeight(shorterMarkHeight + m_boardSize.height() * m_sideLength);
+ setMaximumSize(m_sizeForBoard + QSize(frameWidth() * 2, frameWidth() * 2));
+
+ prepare();
+ }
+}
+
+void GraphicalBoardFrame::generateBoardPixmap(QPixmap *pixmap)
+{
+ if (m_sizeForBoard.isEmpty())
+ {
+ *pixmap = QPixmap(0, 0);
+ return;
+ }
+
+ *pixmap = QPixmap(m_sizeForBoard);
+ QPainter painter(pixmap);
+
+ for (QSize currentTile(0, 0); currentTile.height() < m_boardSize.height(); currentTile.setHeight(currentTile.height() + 1))
+ {
+ for (currentTile.setWidth(0); currentTile.width() < m_boardSize.width(); currentTile.setWidth(currentTile.width() + 1))
+ {
+ TileWidget *tile = tileAt(currentTile);
+ if (!tile)
+ continue;
+
+ painter.drawPixmap(coordinatesOfTile(currentTile), tile->tilePixmap());
+ }
+ }
+
+ for (int row = 0; row <= m_boardSize.height(); ++row)
+ {
+ const QSize location(0, row);
+ painter.drawPixmap(coordinatesOfMark(location), markAt(location)->tilePixmap());
+ }
+
+ for (int col = 1; col <= m_boardSize.width(); ++col)
+ {
+ const QSize location(col, 0);
+ painter.drawPixmap(coordinatesOfMark(location), markAt(location)->tilePixmap());
+ }
+}
+
+void GraphicalBoardFrame::drawBoard(const Quackle::Board &board)
+{
+ QSize newBoardSize(board.width(), board.height());
+
+ if (m_boardSize != newBoardSize)
+ {
+ // if it was empty, we need to recalculate
+ // tile widths
+ bool wasEmpty = m_boardSize.isEmpty();
+
+ deleteWidgets();
+ m_boardSize = newBoardSize;
+ recreateWidgets();
+
+ if (wasEmpty)
+ expandToSize(m_maxSize);
+ }
+
+ for (QSize currentTile(0, 0); currentTile.height() < m_boardSize.height(); currentTile.setHeight(currentTile.height() + 1))
+ {
+ for (currentTile.setWidth(0); currentTile.width() < m_boardSize.width(); currentTile.setWidth(currentTile.width() + 1))
+ {
+ Quackle::Board::TileInformation info(board.tileInformation(currentTile.height(), currentTile.width()));
+
+ TileWidget *tile = tileAt(currentTile);
+ if (!tile)
+ continue;
+
+ tile->setInformation(info);
+
+ tile->setArrowDirection(NoArrow);
+ tile->setCemented(info.tileType == Quackle::Board::LetterTile);
+ tile->prepare();
+ }
+ }
+}
+
+TileWidget *GraphicalBoardFrame::tileAt(const QSize &loc)
+{
+ QSize mapEntry(loc + QSize(1, 1));
+ if (!m_tileWidgets.contains(mapEntry))
+ {
+ cerr << "error! graphical board can't find a tile" << endl;
+ return 0;
+ }
+
+ return m_tileWidgets.value(mapEntry);
+}
+
+QSize GraphicalBoardFrame::locationForPosition(const QPoint &pos)
+{
+ QPoint semiRegularized = (pos - QPoint(frameWidth(), frameWidth()) - m_tilesOffset);
+ QSize finalSize((int)floor((double)semiRegularized.x() / m_sideLength), (int)floor((double)semiRegularized.y() / m_sideLength));
+ return finalSize;
+}
+
+TileWidget *GraphicalBoardFrame::markAt(const QSize &loc)
+{
+ if (!m_tileWidgets.contains(loc))
+ {
+ cerr << "error! graphical board can't find a mark" << endl;
+ return 0;
+ }
+
+ return m_tileWidgets.value(loc);
+}
+
+void GraphicalBoardFrame::addTile(const QSize &loc, TileWidget *tile)
+{
+ m_tileWidgets.insert(loc + QSize(1, 1), tile);
+}
+
+void GraphicalBoardFrame::removeTile(const QSize &loc)
+{
+ m_tileWidgets.remove(loc + QSize(1, 1));
+}
+
+void GraphicalBoardFrame::addMark(const QSize &loc, MarkWidget *tile)
+{
+ m_tileWidgets.insert(loc, tile);
+}
+
+void GraphicalBoardFrame::removeMark(const QSize &loc)
+{
+ m_tileWidgets.remove(loc);
+}
+
+QPoint GraphicalBoardFrame::coordinatesOfTile(const QSize &loc)
+{
+ QPoint point(loc.width(), loc.height());
+ return (point * m_sideLength) + m_tilesOffset;
+}
+
+QPoint GraphicalBoardFrame::coordinatesOfMark(const QSize &loc)
+{
+ QPoint point(loc.width(), loc.height());
+
+ if (loc == QSize(0, 0))
+ return point;
+
+ QPoint vector(point.x() == 0? 0 : 1, point.y() == 0? 0 : 1);
+
+ return ((point - vector) * m_sideLength + QPoint(m_tilesOffset.x() * vector.x(), m_tilesOffset.y() * vector.y()));
+}
+
+void GraphicalBoardFrame::drawMove(const Quackle::Move &move)
+{
+ if (move.action == Quackle::Move::Place)
+ {
+ if (move.tiles().empty())
+ return;
+
+ const QSize startTile(move.startcol, move.startrow);
+
+ const Quackle::LetterString::const_iterator end(move.tiles().end());
+ int i = 0;
+ for (Quackle::LetterString::const_iterator it = move.tiles().begin(); it != end; ++it, ++i)
+ {
+ // if this is a cemented letter that we shouldn't overwrite
+ if (move.isAlreadyOnBoard(*it))
+ continue;
+
+ QSize currentTile(startTile);
+ if (move.horizontal)
+ currentTile.setWidth(currentTile.width() + i);
+ else
+ currentTile.setHeight(currentTile.height() + i);
+
+ TileWidget *tileWidget = tileAt(currentTile);
+ if (!tileWidget)
+ continue;
+
+ Quackle::Board::TileInformation info;
+ info.tileType = Quackle::Board::LetterTile;
+
+ info.isBlank = QUACKLE_ALPHABET_PARAMETERS->isBlankLetter(*it);
+ info.letter = QUACKLE_ALPHABET_PARAMETERS->clearBlankness(*it);
+
+ tileWidget->setInformation(info);
+ tileWidget->setCemented(false);
+ tileWidget->prepare();
+ }
+ }
+}
+
+void GraphicalBoardFrame::drawArrow(const QSize &location, int arrowDirection)
+{
+ TileWidget *tile = tileAt(location);
+ if (!tile)
+ return;
+
+ tile->setArrowDirection(arrowDirection);
+ tile->prepare();
+}
+
+void GraphicalBoardFrame::deleteWidgets()
+{
+ if (m_boardSize.isEmpty())
+ return;
+
+ for (QSize currentTile(0, 0); currentTile.height() < m_boardSize.height(); currentTile.setHeight(currentTile.height() + 1))
+ {
+ for (currentTile.setWidth(0); currentTile.width() < m_boardSize.width(); currentTile.setWidth(currentTile.width() + 1))
+ {
+ delete tileAt(currentTile);
+ removeTile(currentTile);
+ }
+ }
+
+ for (int row = 0; row <= m_boardSize.height(); ++row)
+ {
+ delete markAt(QSize(0, row));
+ removeMark(QSize(0, row));
+ }
+
+ for (int col = 1; col <= m_boardSize.width(); ++col)
+ {
+ delete markAt(QSize(col, 0));
+ removeMark(QSize(col, 0));
+ }
+}
+
+void GraphicalBoardFrame::recreateWidgets()
+{
+ Quackle::Board emptyBoard;
+ emptyBoard.prepareEmptyBoard();
+
+ for (QSize currentTile(0, 0); currentTile.height() < m_boardSize.height(); currentTile.setHeight(currentTile.height() + 1))
+ {
+ for (currentTile.setWidth(0); currentTile.width() < m_boardSize.width(); currentTile.setWidth(currentTile.width() + 1))
+ {
+ TileWidget *newTile = new TileWidget;
+
+ newTile->setLocation(currentTile);
+ newTile->setAlwaysShowVerboseLabels(m_alwaysShowVerboseLabels);
+ newTile->setOriginalInformation(emptyBoard.tileInformation(currentTile.height(), currentTile.width()));
+
+ addTile(currentTile, newTile);
+ }
+ }
+
+ for (int row = 0; row <= m_boardSize.height(); ++row)
+ {
+ MarkWidget *newMark = new MarkWidget;
+
+ if (row == 0)
+ newMark->setCapstone();
+ else
+ newMark->setRow(row);
+
+ addMark(QSize(0, row), newMark);
+ }
+
+ for (int col = 1; col <= m_boardSize.width(); ++col)
+ {
+ MarkWidget *newMark = new MarkWidget;
+ newMark->setCol(col);
+ addMark(QSize(col, 0), newMark);
+ }
+}
+
+void GraphicalBoardFrame::resizeWidgets(int sideLength)
+{
+ PixmapCacher::self()->invalidate();
+
+ bool firstTile = true;
+ for (QSize currentTile(0, 0); currentTile.height() < m_boardSize.height(); currentTile.setHeight(currentTile.height() + 1))
+ {
+ for (currentTile.setWidth(0); currentTile.width() < m_boardSize.width(); currentTile.setWidth(currentTile.width() + 1))
+ {
+ TileWidget *tile = tileAt(currentTile);
+ if (!tile)
+ continue;
+
+ tile->setSideLength(sideLength);
+
+ if (firstTile)
+ {
+ emit tileFontChanged(tile->actualLetterFont());
+ firstTile = false;
+ }
+ }
+ }
+
+ for (int row = 0; row <= m_boardSize.height(); ++row)
+ {
+ TileWidget *mark = markAt(QSize(0, row));
+ mark->setSideLength(sideLength);
+ }
+
+ for (int col = 1; col <= m_boardSize.width(); ++col)
+ {
+ TileWidget *mark = markAt(QSize(col, 0));
+ mark->setSideLength(sideLength);
+ }
+}
+
+void GraphicalBoardFrame::flushPixmapsAndRedraw()
+{
+ m_boardSize = QSize();
+ prepare();
+}
+
+void GraphicalBoardFrame::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this);
+ painter.drawPixmap(contentsRect().topLeft(), m_pixmap);
+ painter.end();
+
+ QFrame::paintEvent(event);
+}
+
+void GraphicalBoardFrame::mousePressEvent(QMouseEvent *event)
+{
+ setFocus();
+
+ if (!wantMousePressEvent(event))
+ {
+ event->ignore();
+ return;
+ }
+
+ const QPoint pos(event->pos());
+
+ QSize location = locationForPosition(pos);
+
+ if (location.isValid())
+ tileClicked(location, event);
+
+ event->accept();
+}
+
+void GraphicalBoardFrame::keyPressEvent(QKeyEvent *event)
+{
+ enum { Backspace = 0, Delete, Submit, Commit, Append } mode;
+
+ switch (event->key())
+ {
+ case Qt::Key_Backspace:
+ mode = Backspace;
+ break;
+
+ case Qt::Key_Delete:
+ mode = Delete;
+ break;
+
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ mode = event->modifiers() & Qt::ShiftModifier || event->modifiers() & Qt::ControlModifier || event->modifiers() & Qt::MetaModifier? Commit : Submit;
+ break;
+
+ default:
+ mode = Append;
+ break;
+ }
+
+ if (!hasCandidate())
+ {
+ event->ignore();
+ return;
+ }
+
+ switch (mode)
+ {
+
+ // these modes need a candidate
+ case Submit:
+ submitHandler();
+ break;
+
+ case Commit:
+ commitHandler();
+ break;
+
+ default:
+ if (!hasArrow())
+ {
+ event->ignore();
+ return;
+ }
+
+ switch (mode)
+ {
+
+ // these modes need an arrow
+ case Delete:
+ deleteHandler();
+ break;
+
+ case Backspace:
+ backspaceHandler();
+ break;
+
+ case Append:
+ if (event->modifiers() & Qt::AltModifier || event->modifiers() & Qt::ControlModifier || event->text().isEmpty())
+ {
+ event->ignore();
+ return;
+ }
+
+ appendHandler(event->text(), event->modifiers() & Qt::ShiftModifier);
+ break;
+
+ case Commit:
+ case Submit:
+ break;
+ }
+ break;
+ }
+
+ prepare();
+
+ event->accept();
+}
+
+bool GraphicalBoardFrame::wantMousePressEvent(const QMouseEvent *event) const
+{
+ return (event->button() == Qt::LeftButton);
+}
+
+Quackle::Move GraphicalBoardFrame::flip(const Quackle::Move &flippee)
+{
+ if (!m_board.isConnected(flippee))
+ return flippee;
+
+ Quackle::MoveList words = m_board.allWordsFormedBy(flippee);
+ Quackle::Move flipped = words.front();
+ if (flipped.tiles().length() > 1)
+ {
+ return flipped;
+ }
+ else
+ {
+ return flippee;
+ }
+}
+
+void GraphicalBoardFrame::prettifyAndSetLocalCandidate(const Quackle::Move &candidate)
+{
+ m_candidate = candidate;
+ m_candidate.setPrettyTiles(m_board.prettyTilesOfMove(m_candidate));
+
+ if (candidate.action == Quackle::Move::Place && candidate.wordTilesWithNoPlayThru().empty())
+ {
+ emit statusMessage(tr("Click again to switch arrow orientation, or type a word."));
+ return;
+ }
+
+ //UVcout << "m_candidate: " << m_candidate << endl;
+
+ if (m_candidate.wordTilesWithNoPlayThru().length() == 1)
+ {
+ emit localCandidateChanged(flip(m_candidate));
+
+ emit statusMessage(tr("Press Enter to add %1 to candidate list, or Control+Enter to commit to it immediately.").arg(QuackleIO::Util::moveToDetailedString(flip(m_candidate))));
+ }
+ else
+ {
+ emit localCandidateChanged(m_candidate);
+
+ emit statusMessage(tr("Press Enter to add %1 to candidate list, or Control+Enter to commit to it immediately.").arg(QuackleIO::Util::moveToDetailedString(m_candidate)));
+ }
+}
+
+void GraphicalBoardFrame::setLocalCandidate(const Quackle::Move &candidate)
+{
+ m_candidate = candidate;
+ resetArrow();
+ prepare();
+}
+
+void GraphicalBoardFrame::backspaceHandler()
+{
+ unsigned int hoppedTiles = 0;
+ QSize currentTile(m_arrowRoot);
+ while (true)
+ {
+ const QSize previousTile = currentTile - arrowVector();
+
+ bool stopHere;
+
+ if (!isOnBoard(previousTile))
+ stopHere = true;
+ else
+ {
+ Quackle::Board::TileInformation previousTileInformation(m_board.tileInformation(previousTile.height(), previousTile.width()));
+
+ stopHere = previousTileInformation.tileType != Quackle::Board::LetterTile;
+ }
+
+ ++hoppedTiles;
+
+ if (stopHere)
+ {
+ m_arrowRoot = previousTile;
+ break;
+ }
+
+ currentTile = previousTile;
+ }
+
+ Quackle::LetterString tiles(m_candidate.tiles());
+
+ if (hoppedTiles > tiles.length())
+ tiles = Quackle::LetterString();
+ else
+ tiles = Quackle::String::left(m_candidate.tiles(), m_candidate.tiles().length() - hoppedTiles);
+
+ if (tiles.empty())
+ {
+ Quackle::Move originalMove;
+ Quackle::LetterString hoppedLetters;
+
+ currentTile = m_arrowRoot;
+ while (true)
+ {
+ const QSize previousTile = currentTile - arrowVector();
+
+ bool stopHere;
+
+ if (!isOnBoard(previousTile))
+ stopHere = true;
+ else
+ {
+ Quackle::Board::TileInformation previousTileInformation(m_board.tileInformation(previousTile.height(), previousTile.width()));
+
+ stopHere = previousTileInformation.tileType != Quackle::Board::LetterTile;
+ }
+
+ if (stopHere)
+ {
+ const bool horizontal = m_arrowDirection == ArrowRight;
+ m_candidate = Quackle::Move::createPlaceMove(currentTile.height(), currentTile.width(), horizontal, hoppedLetters);
+ break;
+ }
+
+ hoppedLetters += QUACKLE_PLAYED_THRU_MARK;
+ currentTile = previousTile;
+ }
+ }
+ else
+ {
+ m_candidate.setTiles(tiles);
+ }
+
+ ensureCandidatePlacedProperly();
+ prettifyAndSetLocalCandidate(m_candidate);
+}
+
+void GraphicalBoardFrame::deleteHandler()
+{
+ setLocalCandidate(Quackle::Move::createNonmove());
+}
+
+void GraphicalBoardFrame::submitHandler()
+{
+ QTimer::singleShot(0, this, SLOT(setGlobalCandidate()));
+}
+
+void GraphicalBoardFrame::commitHandler()
+{
+ QTimer::singleShot(0, this, SLOT(setAndCommitGlobalCandidate()));
+}
+
+void GraphicalBoardFrame::setGlobalCandidate()
+{
+ if (m_candidate.action == Quackle::Move::Place && m_candidate.wordTilesWithNoPlayThru().empty())
+ {
+ emit statusMessage(tr("Click again to switch arrow orientation, or type a word."));
+ return;
+ }
+
+ if (m_candidate.wordTilesWithNoPlayThru().length() == 1)
+ {
+ emit setCandidateMove(flip(m_candidate));
+ }
+ else
+ {
+ emit setCandidateMove(m_candidate);
+ }
+}
+
+void GraphicalBoardFrame::setAndCommitGlobalCandidate()
+{
+ setGlobalCandidate();
+ emit commit();
+}
+
+void GraphicalBoardFrame::appendHandler(const QString &text, bool shiftPressed)
+{
+ if (!isOnBoard(m_arrowRoot))
+ return;
+
+ if (!hasCandidate())
+ return;
+
+ Quackle::LetterString appendedLetterString(QuackleIO::Util::encode(text));
+
+ if (appendedLetterString.length() == 0 || Quackle::String::front(appendedLetterString) < QUACKLE_FIRST_LETTER)
+ return;
+
+ if (shiftPressed)
+ appendedLetterString = Quackle::String::setBlankness(appendedLetterString);
+ else
+ appendedLetterString = Quackle::String::clearBlankness(appendedLetterString);
+
+ Quackle::Move newCandidate(m_candidate);
+ Quackle::LetterString newTiles(m_candidate.tiles() + appendedLetterString);
+ if (!m_ignoreRack && !m_rack.contains(Quackle::String::usedTiles(newTiles)))
+ {
+ Quackle::LetterString blankedNewTiles(m_candidate.tiles() + Quackle::String::setBlankness(appendedLetterString));
+
+ if (m_rack.contains(Quackle::String::usedTiles(blankedNewTiles)))
+ {
+ newTiles = blankedNewTiles;
+ }
+ }
+
+ newCandidate.setTiles(newTiles);
+
+ Quackle::LetterString hoppedTiles;
+
+ QSize currentTile(m_arrowRoot);
+ while (true)
+ {
+ const QSize nextTile = currentTile + arrowVector();
+
+ bool stopHere = false;
+ bool resetArrowAfter = false;
+
+ if (!isOnBoard(nextTile))
+ {
+ stopHere = true;
+ }
+ else
+ {
+ Quackle::Board::TileInformation nextTileInformation(m_board.tileInformation(nextTile.height(), nextTile.width()));
+
+ if (nextTileInformation.tileType != Quackle::Board::LetterTile)
+ stopHere = true;
+ }
+
+ if (stopHere)
+ {
+ newCandidate.setTiles(newCandidate.tiles() + hoppedTiles);
+
+ if (resetArrowAfter)
+ resetArrow();
+ else
+ m_arrowRoot = nextTile;
+
+ break;
+ }
+
+ hoppedTiles += QUACKLE_PLAYED_THRU_MARK;
+ currentTile = nextTile;
+ }
+
+ prettifyAndSetLocalCandidate(newCandidate);
+}
+
+void GraphicalBoardFrame::tileClicked(const QSize &tileLocation, const QMouseEvent * /* event */)
+{
+ Quackle::Board::TileInformation info(m_board.tileInformation(tileLocation.height(), tileLocation.width()));
+ if (info.tileType == Quackle::Board::LetterTile)
+ return;
+
+ if (m_arrowRoot == tileLocation)
+ {
+ ++m_arrowDirection;
+ if (m_arrowDirection == ArrowWorm)
+ m_arrowDirection = s_firstArrowDirection;
+ }
+ else
+ m_arrowDirection = s_firstArrowDirection;
+
+ m_arrowRoot = tileLocation;
+
+ Quackle::Move originalMove;
+ Quackle::LetterString hoppedTiles;
+
+ QSize currentTile(tileLocation);
+ while (true)
+ {
+ const QSize previousTile = currentTile - arrowVector();
+
+ bool stopHere;
+
+ if (!isOnBoard(previousTile))
+ stopHere = true;
+ else
+ {
+ Quackle::Board::TileInformation previousTileInformation(m_board.tileInformation(previousTile.height(), previousTile.width()));
+
+ stopHere = previousTileInformation.tileType != Quackle::Board::LetterTile;
+ }
+
+ if (stopHere)
+ {
+ const bool horizontal = m_arrowDirection == ArrowRight;
+ originalMove = Quackle::Move::createPlaceMove(currentTile.height(), currentTile.width(), horizontal, hoppedTiles);
+ break;
+ }
+
+ hoppedTiles += QUACKLE_PLAYED_THRU_MARK;
+ currentTile = previousTile;
+ }
+
+ prettifyAndSetLocalCandidate(originalMove);
+
+ // TODO work some cleverness so we can do this!
+ //emit setCandidateMove(Quackle::Move::createNonmove());
+
+ for (QSize currentTile(0, 0); currentTile.height() < m_boardSize.height(); currentTile.setHeight(currentTile.height() + 1))
+ for (currentTile.setWidth(0); currentTile.width() < m_boardSize.width(); currentTile.setWidth(currentTile.width() + 1))
+ if (currentTile != tileLocation)
+ {
+ TileWidget *tile = tileAt(currentTile);
+ if (!tile)
+ continue;
+
+ tile->setArrowDirection(NoArrow);
+ }
+
+ prepare();
+}
+
+void GraphicalBoardFrame::resetArrow()
+{
+ m_arrowRoot = QSize(-1, -1);
+ m_arrowDirection = NoArrow;
+}
+
+bool GraphicalBoardFrame::hasArrow() const
+{
+ return m_arrowRoot.isValid();
+}
+
+QSize GraphicalBoardFrame::arrowVector() const
+{
+ const bool horizontal = (m_arrowDirection == ArrowRight);
+ return horizontal? QSize(1, 0) : QSize(0, 1);
+}
+
+bool GraphicalBoardFrame::hasCandidate() const
+{
+ return m_candidate.isAMove();
+}
+
+void GraphicalBoardFrame::ensureCandidatePlacedProperly()
+{
+ if (m_candidate.tiles().empty())
+ {
+ m_candidate.startrow = m_arrowRoot.height();
+ m_candidate.startcol = m_arrowRoot.width();
+ }
+}
+
+bool GraphicalBoardFrame::isOnBoard(const QSize &location) const
+{
+ return (location.width() >= 0 && location.width() < m_boardSize.width() && location.height() >= 0 && location.height() < m_boardSize.height());
+}
+
+////////////////
+
+unsigned int qHash(const QColor &color)
+{
+ return color.rgb();
+}
+
+PixmapCacher *PixmapCacher::m_self = 0;
+PixmapCacher *PixmapCacher::self()
+{
+ if (m_self == 0)
+ m_self = new PixmapCacher();
+
+ return m_self;
+}
+
+void PixmapCacher::cleanUp()
+{
+ delete m_self;
+ m_self = 0;
+}
+
+PixmapCacher::PixmapCacher()
+{
+ arrowColor = QColor("black");
+ letterColor = QColor("#6e6e6e");
+ britishLetterColor = QColor("#703d3d");
+
+ // tiles on rack will be of different sizes and thus are slightly
+ // altered to fool the pixmap cacher
+ rackColor = letterColor.light(101);
+
+ DLSColor = QColor("cornflowerblue");
+ TLSColor = QColor("slateblue");
+ DWSColor = QColor("palevioletred");
+ TWSColor = QColor("firebrick");
+
+ QLSColor = QColor("blueviolet").light();
+ QWSColor = QColor("goldenrod");
+
+ nothingColor = QColor("gainsboro");
+
+ cementedLetterTextColor = QColor("#f8f8ff");
+ cementedBritishLetterTextColor = QColor("#FFC0C0");
+ uncementedLetterTextColor = QColor("khaki");
+
+ markColor = QColor("tan");
+ markTextColor = markColor.dark();
+}
+
+bool PixmapCacher::contains(const QColor &color) const
+{
+ return m_pixmaps.contains(color);
+}
+
+QPixmap PixmapCacher::get(const QColor &color) const
+{
+ return m_pixmaps.value(color);
+}
+
+void PixmapCacher::put(const QColor &color, const QPixmap &pixmap)
+{
+ m_pixmaps.insert(color, pixmap);
+}
+
+void PixmapCacher::invalidate()
+{
+ m_pixmaps.clear();
+}
+
+////////////////
+
+TileWidget::TileWidget()
+ : m_cemented(false), m_arrowDirection(GraphicalBoardFrame::NoArrow), m_alwaysShowVerboseLabels(false)
+{
+}
+
+TileWidget::~TileWidget()
+{
+}
+
+void TileWidget::setSideLength(int sideLength)
+{
+ setOurSize(sideLength, sideLength);
+
+ if (!m_pixmap.isNull())
+ prepare();
+}
+
+int TileWidget::sideLength() const
+{
+ return m_size.width();
+}
+
+QSize TileWidget::size() const
+{
+ return m_size;
+}
+
+void TileWidget::setInformation(const Quackle::Board::TileInformation &information)
+{
+ m_information = information;
+}
+
+Quackle::Board::TileInformation TileWidget::information() const
+{
+ return m_information;
+}
+
+void TileWidget::setOriginalInformation(const Quackle::Board::TileInformation &originalInformation)
+{
+ if (originalInformation.tileType == Quackle::Board::BonusSquareTile)
+ m_backgroundColor = tileColor(originalInformation);
+}
+
+void TileWidget::setLocation(const QSize &location)
+{
+ m_location = location;
+}
+
+void TileWidget::setCemented(bool cemented)
+{
+ m_cemented = cemented;
+}
+
+bool TileWidget::cemented() const
+{
+ return m_cemented;
+}
+
+void TileWidget::setArrowDirection(int arrowDirection)
+{
+ m_arrowDirection = arrowDirection;
+}
+
+GraphicalBoardFrame::ArrowDirection TileWidget::arrowDirection() const
+{
+ return (GraphicalBoardFrame::ArrowDirection)m_arrowDirection;
+}
+
+void TileWidget::prepare()
+{
+ m_pixmap = generateTilePixmap();
+}
+
+const QPixmap &TileWidget::tilePixmap()
+{
+ return m_pixmap;
+}
+
+QColor TileWidget::tileColor()
+{
+ return tileColor(m_information);
+}
+
+QColor TileWidget::tileColor(const Quackle::Board::TileInformation &information)
+{
+ QColor ret;
+
+ PixmapCacher *cache = PixmapCacher::self();
+
+ switch (information.tileType)
+ {
+ case Quackle::Board::LetterTile:
+ if (information.isOnRack)
+ ret = cache->rackColor;
+ else if (information.isBritish && QuackerSettings::self()->britishColoring == TileBritishColoring)
+ ret = cache->britishLetterColor;
+ else
+ ret = cache->letterColor;
+ break;
+
+ case Quackle::Board::BonusSquareTile:
+ switch (information.bonusSquareType)
+ {
+ case Quackle::Board::LetterBonus:
+ if (information.bonusMultiplier == 2)
+ ret = cache->DLSColor;
+ else if (information.bonusMultiplier == 3)
+ ret = cache->TLSColor;
+ else if (information.bonusMultiplier == 4)
+ ret = cache->QLSColor;
+ else
+ {
+ // TODO general case
+ }
+ break;
+
+ case Quackle::Board::WordBonus:
+ if (information.bonusMultiplier == 2)
+ ret = cache->DWSColor;
+ else if (information.bonusMultiplier == 3)
+ ret = cache->TWSColor;
+ else if (information.bonusMultiplier == 4)
+ ret = cache->QWSColor;
+ else
+ {
+ // TODO general case
+ }
+ break;
+
+ case Quackle::Board::NoBonus:
+ // urps, this won't happen
+ ret = cache->nothingColor;
+ break;
+ }
+ break;
+
+ case Quackle::Board::NothingTile:
+ ret = cache->nothingColor;
+ break;
+ }
+
+ return ret;
+}
+
+QColor TileWidget::backgroundColor()
+{
+ return m_backgroundColor;
+}
+
+QColor TileWidget::letterTextColor()
+{
+ QColor ret;
+
+ if (m_arrowDirection != GraphicalBoardFrame::NoArrow)
+ ret = PixmapCacher::self()->arrowColor;
+ else if (m_cemented)
+ {
+ if (m_information.isBritish && QuackerSettings::self()->britishColoring == TextBritishColoring)
+ ret = PixmapCacher::self()->cementedBritishLetterTextColor;
+ else
+ ret = PixmapCacher::self()->cementedLetterTextColor;
+ }
+ else if (m_information.letter == QUACKLE_NULL_MARK && m_information.isStartLocation)
+ ret = PixmapCacher::self()->arrowColor;
+ else if (m_information.letter == QUACKLE_NULL_MARK)
+ ret = tileColor().light(145);
+ else
+ ret = PixmapCacher::self()->uncementedLetterTextColor;
+
+ return ret;
+}
+
+QString TileWidget::letterText()
+{
+ int glyphIndex;
+
+ if (m_arrowDirection == GraphicalBoardFrame::ArrowRight)
+ glyphIndex = 0;
+ else if (m_arrowDirection == GraphicalBoardFrame::ArrowDown)
+ glyphIndex = 1;
+ else if (m_information.letter == QUACKLE_NULL_MARK)
+ {
+ if (m_information.isStartLocation)
+ glyphIndex = 2;
+ else
+ {
+ if (shouldShowVerboseLabels())
+ {
+ if (m_information.bonusSquareType == Quackle::Board::LetterBonus)
+ return QString("%1LS").arg(m_information.bonusMultiplier);
+ else if (m_information.bonusSquareType == Quackle::Board::WordBonus)
+ return QString("%1WS").arg(m_information.bonusMultiplier);
+ }
+
+ return QString::null;
+ }
+ }
+ else
+ return QuackleIO::Util::sanitizeUserVisibleLetterString(QuackleIO::Util::letterToQString(m_information.letter));
+
+ QChar preferredGlyphs[] = {QChar(0x21d2), QChar(0x21d3), QChar(0x2606)}; // double arrows, white star
+ QChar secondaryGlyphs[] = {QChar(0x2192), QChar(0x2193), QChar( '*')}; // single arrows, ASCII star
+ QChar asciiGlyphs[] = {QChar( '>'), QChar( 'v'), QChar( '*')}; // 7-bit
+ QFontMetrics metrics(letterFont());
+
+#ifdef FORCE_SECONDARY_ARROW_GLYPHS
+ QChar macSecondaryGlyphs[] = {QChar(0x2192), QChar(0x2193), QChar(0x2606)}; // single arrows, white star
+ return QString(macSecondaryGlyphs[glyphIndex]);
+#else
+ if (metrics.inFont(preferredGlyphs[0]) &&
+ metrics.inFont(preferredGlyphs[1]) &&
+ metrics.inFont(preferredGlyphs[2]))
+ return QString(preferredGlyphs[glyphIndex]);
+ else if (metrics.inFont(secondaryGlyphs[0]) &&
+ metrics.inFont(secondaryGlyphs[1]) &&
+ metrics.inFont(secondaryGlyphs[2]))
+ return QString(secondaryGlyphs[glyphIndex]);
+ else
+ return QString(asciiGlyphs[glyphIndex]);
+#endif
+}
+
+QFont TileWidget::letterFont()
+{
+ // Sorry for the hardcoded constants :( :(
+ if (m_information.isStartLocation && m_information.tileType != Quackle::Board::LetterTile)
+ return scaledFont(0.7);
+ else if (m_arrowDirection == GraphicalBoardFrame::NoArrow && m_information.tileType == Quackle::Board::BonusSquareTile)
+ return scaledFont(.35);
+ else
+ {
+ double initialScale = 0.7;
+ if (m_information.tileType == Quackle::Board::LetterTile)
+ {
+ QString text = letterText();
+ if (text.length() > 1)
+ {
+ initialScale = 1.0 / text.length();
+ }
+
+ }
+
+ double scale = m_information.isBlank? initialScale * 5 / 7 : initialScale;
+
+ QFont ret = scaledFont(scale);
+ ret.setBold(true);
+ return ret;
+ }
+}
+
+QFont TileWidget::actualLetterFont()
+{
+ return scaledFont(1);
+}
+
+QString TileWidget::miniText()
+{
+ if (m_information.letter != QUACKLE_NULL_MARK && QuackerSettings::self()->scoreLabels)
+ {
+ if (m_information.isBlank)
+ return QString::null;
+ else
+ return QString::number(QUACKLE_ALPHABET_PARAMETERS->score(m_information.letter));
+ }
+ else
+ return QString::null;
+}
+
+QColor TileWidget::miniTextColor()
+{
+ return tileColor().light(170);
+}
+
+QFont TileWidget::miniFont()
+{
+ return scaledFont(miniText().length() > 1? .275 : .325);
+}
+
+bool TileWidget::shouldShowVerboseLabels() const
+{
+ return m_alwaysShowVerboseLabels || QuackerSettings::self()->verboseLabels;
+}
+
+QFont TileWidget::scaledFont(float multiplier)
+{
+ const int smallerSideLength = qMin(size().width(), size().height());
+
+ if (smallerSideLength == 0)
+ return PixmapCacher::self()->tileFont;
+
+ QFont ret(PixmapCacher::self()->tileFont);
+ ret.setPixelSize((int)(smallerSideLength * multiplier));
+ return ret;
+}
+
+void TileWidget::setOurSize(const QSize &size)
+{
+ m_size = size;
+}
+
+void TileWidget::setOurSize(int width, int height)
+{
+ setOurSize(QSize(width, height));
+}
+
+QPixmap TileWidget::generateTilePixmap()
+{
+ const QSize currentSize(size());
+
+ if (currentSize.isEmpty())
+ return QPixmap();
+
+ const QRect borderRect(0, 0, currentSize.width(), currentSize.height());
+ const int maxSideLength = qMax(currentSize.width(), currentSize.height());
+ const int borderWidth = 1;
+
+ const QPointF midpoint((double)(currentSize.width() + borderWidth) / 2, (double)(currentSize.height() + borderWidth) / 2);
+ const QColor color(tileColor());
+
+ QPixmap ret(currentSize);
+
+ if (PixmapCacher::self()->contains(color))
+ ret = PixmapCacher::self()->get(color);
+ else
+ {
+ //UVcout << "cache miss for color " << color.rgb() << endl;
+ double radius = maxSideLength / 2 * 1.41 + maxSideLength / 10 + 2;
+
+ // could be used for cool effect -- the color of the bonus square we're obscuring
+ //const QColor outerColor(backgroundColor());
+
+ QRadialGradient gradient(QPointF(radius, radius), radius * 3, QPointF(radius / 3, radius / 3));
+ gradient.setColorAt(0, color.light(GraphicalBoardFrame::s_highlightFactor));
+ gradient.setColorAt(.95, color.dark(GraphicalBoardFrame::s_highlightFactor));
+
+ QPainter painter(&ret);
+ painter.setBrush(gradient);
+
+ painter.drawEllipse((int)(midpoint.x() - radius), (int)(midpoint.y() - radius), (int)(radius * 2), (int)(radius * 2));
+
+ QPalette customPalette;
+ customPalette.setColor(QPalette::Light, color.light(GraphicalBoardFrame::s_highlightFactor));
+ customPalette.setColor(QPalette::Dark, color);
+ customPalette.setColor(QPalette::Mid, color);
+
+ qDrawShadePanel(&painter, borderRect.x(), borderRect.y(), borderRect.width(), borderRect.height(), customPalette, false, borderWidth);
+
+ PixmapCacher::self()->put(color, ret);
+ }
+
+ const QString nanism = miniText();
+ const bool hasNanism = !nanism.isEmpty();
+
+ const QString text = letterText();
+ if (!text.isEmpty())
+ {
+ QPainter painter(&ret);
+ painter.setFont(letterFont());
+ QPen pen(letterTextColor());
+ painter.setPen(pen);
+ painter.setBrush(Qt::NoBrush);
+
+ const QRectF textSize(painter.boundingRect(borderRect, text));
+ const QPointF startPoint(midpoint - textSize.bottomRight() / 2);
+ const QPointF roundedStartPoint(floor(startPoint.x()), floor(startPoint.y()));
+
+ QRectF textRect(textSize);
+ textRect.moveTo(roundedStartPoint);
+
+ painter.drawText(textRect, Qt::TextDontClip, text);
+
+ if (m_information.isBlank)
+ {
+ painter.setBrush(Qt::NoBrush);
+ pen.setWidth(1);
+ painter.setPen(pen);
+
+ const int border = currentSize.width() / 5;
+ painter.drawRect(QRect(border, border, currentSize.width() - 2 * border, currentSize.height() - 2 * border));
+ }
+ }
+
+ if (hasNanism)
+ {
+ QPainter painter(&ret);
+ painter.setFont(miniFont());
+ QPen pen(miniTextColor());
+ painter.setPen(pen);
+ painter.setBrush(Qt::NoBrush);
+
+ const QRectF textSize(painter.boundingRect(borderRect, nanism));
+ const QPointF startPoint((midpoint * (nanism.length() > 1? 1.65 : 1.68)) - textSize.bottomRight() / 2);
+ const QPointF roundedStartPoint(floor(startPoint.x()), floor(startPoint.y()));
+
+ QRectF textRect(textSize);
+ textRect.moveTo(roundedStartPoint);
+
+ painter.drawText(textRect, Qt::TextDontClip, nanism);
+ }
+
+ return ret;
+}
+
+bool operator<(const QSize &firstSize, const QSize &secondSize)
+{
+ return (firstSize.height() + (QUACKLE_MAXIMUM_BOARD_SIZE + 1) * firstSize.width()) < (secondSize.height() + (QUACKLE_MAXIMUM_BOARD_SIZE + 1) * secondSize.width());
+}
+
+//////////////////
+
+MarkWidget::MarkWidget()
+ : m_horizontal(true), m_capstone(false)
+{
+ m_information.tileType = Quackle::Board::LetterTile;
+}
+
+MarkWidget::~MarkWidget()
+{
+}
+
+void MarkWidget::setRow(int row)
+{
+ m_horizontal = false;
+ m_letterText = QString::number(row);
+}
+
+void MarkWidget::setCol(int col)
+{
+ m_horizontal = true;
+ m_letterText = QChar('A' + col - 1);
+}
+
+void MarkWidget::setCapstone()
+{
+ m_capstone = true;
+ m_letterText = QString::null;
+}
+
+void MarkWidget::setSideLength(int sideLength)
+{
+ const int otherLength = (int)(sideLength * GraphicalBoardFrame::s_markOtherLengthMultiplier);
+
+ if (m_capstone)
+ setOurSize(otherLength, otherLength);
+ else if (m_horizontal)
+ setOurSize(sideLength, otherLength);
+ else
+ setOurSize(otherLength, sideLength);
+
+ prepare();
+}
+
+QColor MarkWidget::tileColor()
+{
+ // we slightly alter colors to fool the pixmap cacher!
+ if (m_capstone)
+ return PixmapCacher::self()->markColor.light(101);
+ else if (m_horizontal)
+ return PixmapCacher::self()->markColor;
+ else
+ return PixmapCacher::self()->markColor.dark(101);
+}
+
+QColor MarkWidget::letterTextColor()
+{
+ return PixmapCacher::self()->markTextColor;
+}
+
+QFont MarkWidget::letterFont()
+{
+ return scaledFont(0.7);
+}
+
+QString MarkWidget::letterText()
+{
+ return m_letterText;
+}
diff --git a/quacker/graphicalboard.h b/quacker/graphicalboard.h
new file mode 100644
index 0000000..433bafc
--- /dev/null
+++ b/quacker/graphicalboard.h
@@ -0,0 +1,330 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_GRAPHICALBOARD_H
+#define QUACKER_GRAPHICALBOARD_H
+
+#include <QColor>
+#include <QWidget>
+#include <QHash>
+#include <QMap>
+#include <QPixmap>
+#include <QSize>
+
+#include <board.h>
+
+#include "boarddisplay.h"
+
+class QFrame;
+class QGridLayout;
+
+class GraphicalBoardFrame;
+class MarkWidget;
+class TileWidget;
+
+namespace Quackle
+{
+ class Board;
+ class Move;
+}
+
+unsigned int qHash(const QColor &color);
+
+class PixmapCacher
+{
+public:
+ static PixmapCacher *self();
+
+ // deletes self and resets
+ static void cleanUp();
+
+ QColor arrowColor;
+ QColor letterColor;
+ QColor britishLetterColor;
+ QColor DLSColor;
+ QColor TLSColor;
+ QColor DWSColor;
+ QColor TWSColor;
+
+ QColor QLSColor;
+ QColor QWSColor;
+
+ QColor nothingColor;
+ QColor rackColor;
+
+ QColor cementedLetterTextColor;
+ QColor cementedBritishLetterTextColor;
+ QColor uncementedLetterTextColor;
+
+ QColor markColor;
+ QColor markTextColor;
+
+ QFont tileFont;
+
+ bool contains(const QColor &color) const;
+ QPixmap get(const QColor &color) const;
+ void put(const QColor &color, const QPixmap &pixmap);
+
+ void invalidate();
+
+protected:
+ PixmapCacher();
+ PixmapCacher(const PixmapCacher&);
+ PixmapCacher& operator=(const PixmapCacher&);
+
+private:
+ static PixmapCacher *m_self;
+ QHash<QColor, QPixmap> m_pixmaps;
+};
+
+class GraphicalBoard : public BoardWithQuickEntry
+{
+Q_OBJECT
+
+public:
+ GraphicalBoard(QWidget *parent = 0);
+ ~GraphicalBoard();
+
+ GraphicalBoardFrame *boardFrame() { return m_boardFrame; }
+
+protected slots:
+ virtual void expandToFullWidth();
+ virtual void resizeEvent(QResizeEvent *event);
+
+private:
+ GraphicalBoardFrame *m_boardFrame;
+ QWidget *m_boardWrapper;
+};
+
+class GraphicalBoardFrame : public View
+{
+Q_OBJECT
+
+public:
+ GraphicalBoardFrame(QWidget *parent = 0);
+ ~GraphicalBoardFrame();
+
+ static const double s_markOtherLengthMultiplier;
+ static const int s_highlightFactor = 125;
+
+ // ArrowRight is direction after initial click;
+ // when we increment to ArrowWorm we restart with it
+ enum ArrowDirection { NoArrow = 0, ArrowRight = 1, ArrowDown = 2, /* ... */ ArrowWorm = 3 };
+
+ static const int s_firstArrowDirection = ArrowRight;
+
+ static void staticDrawPosition(const Quackle::GamePosition &position, const QSize &size, QPixmap *pixmap);
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition &position);
+ virtual void expandToSize(const QSize &maxSize);
+
+protected slots:
+ void backspaceHandler();
+ void deleteHandler();
+ void submitHandler();
+ void commitHandler();
+ void appendHandler(const QString &text, bool shiftPressed);
+
+ void setGlobalCandidate();
+ void setAndCommitGlobalCandidate();
+
+ virtual void tileClicked(const QSize &tileLocation, const QMouseEvent * /* event */);
+ virtual void prepare();
+
+ void setLocalCandidate(const Quackle::Move &candidate);
+
+protected:
+ Quackle::Board m_board;
+ Quackle::Rack m_rack;
+ bool m_ignoreRack;
+
+ virtual void keyPressEvent(QKeyEvent *e);
+ virtual void paintEvent(QPaintEvent *event);
+ virtual void mousePressEvent(QMouseEvent *event);
+
+ virtual bool wantMousePressEvent(const QMouseEvent *event) const;
+
+ void generateBoardPixmap(QPixmap *pixmap);
+
+ // these three are misnamed - they just set up the fields of the
+ // tilewidgets
+ void drawBoard(const Quackle::Board &board);
+ void drawMove(const Quackle::Move &move);
+ void drawArrow(const QSize &location, int arrowDirection);
+
+ void deleteWidgets();
+ void recreateWidgets();
+ void resizeWidgets(int sideLength);
+ void flushPixmapsAndRedraw();
+
+ void addTile(const QSize &loc, TileWidget *tile);
+ void removeTile(const QSize &loc);
+ TileWidget *tileAt(const QSize &loc);
+
+ // returns invalid size if there no tile at point
+ // or the tile at relative-to-widget coordinates pos
+ QSize locationForPosition(const QPoint &pos);
+
+ QPoint coordinatesOfTile(const QSize &loc);
+
+ void addMark(const QSize &loc, MarkWidget *tile);
+ void removeMark(const QSize &loc);
+ TileWidget *markAt(const QSize &loc);
+ QPoint coordinatesOfMark(const QSize &loc);
+
+ Quackle::Move flip(const Quackle::Move &flippee);
+ void prettifyAndSetLocalCandidate(const Quackle::Move &candidate);
+
+ bool m_alwaysShowVerboseLabels;
+
+signals:
+ void localCandidateChanged(const Quackle::Move &candidate);
+ void tileFontChanged(const QFont &font);
+
+private:
+ QMap<QSize, TileWidget *> m_tileWidgets;
+ QSize m_boardSize;
+ QSize m_sizeForBoard;
+
+ QPixmap m_pixmap;
+
+ // when empty, user has set no arrow
+ QSize m_arrowRoot;
+ Quackle::Move m_candidate;
+ int m_arrowDirection;
+
+ void resetArrow();
+ bool hasArrow() const;
+ QSize arrowVector() const;
+ bool hasCandidate() const;
+ void ensureCandidatePlacedProperly();
+
+ bool isOnBoard(const QSize &location) const;
+
+ QSize m_maxSize;
+
+ // side length of one tile
+ int m_sideLength;
+
+ // shorter side length of a mark
+ QPoint m_tilesOffset;
+};
+
+class TileWidget
+{
+public:
+ TileWidget();
+ virtual ~TileWidget();
+
+ virtual void setSideLength(int sideLength);
+ int sideLength() const;
+
+ QSize size() const;
+
+ virtual void setInformation(const Quackle::Board::TileInformation &information);
+ Quackle::Board::TileInformation information() const;
+
+ virtual void setOriginalInformation(const Quackle::Board::TileInformation &originalInformation);
+ virtual void setLocation(const QSize &location);
+
+ virtual void setCemented(bool cemented);
+ bool cemented() const;
+
+ virtual void setArrowDirection(int arrowDirection);
+ GraphicalBoardFrame::ArrowDirection arrowDirection() const;
+
+ // to be called after the set* functions to show
+ // the correct things
+ virtual void prepare();
+
+ const QPixmap &tilePixmap();
+
+ virtual QColor tileColor();
+ virtual QColor tileColor(const Quackle::Board::TileInformation &information);
+ virtual QColor backgroundColor();
+ virtual QColor letterTextColor();
+ virtual QString letterText();
+
+ // either actual font or smaller one for bonus square message
+ virtual QFont letterFont();
+
+ // font for drawing actual letters
+ virtual QFont actualLetterFont();
+
+ virtual QString miniText();
+ virtual QColor miniTextColor();
+ virtual QFont miniFont();
+
+ void setAlwaysShowVerboseLabels(bool alwaysShowVerboseLabels);
+
+protected:
+ QPixmap generateTilePixmap();
+ QFont scaledFont(float multiplier);
+ static const double s_defaultLetterScale;
+
+ virtual void setOurSize(const QSize &size);
+ virtual void setOurSize(int width, int height);
+
+ Quackle::Board::TileInformation m_information;
+ bool m_cemented;
+ int m_arrowDirection;
+
+ QColor m_backgroundColor;
+ QSize m_location;
+
+ QSize m_size;
+ QPixmap m_pixmap;
+
+ bool shouldShowVerboseLabels() const;
+
+ bool m_alwaysShowVerboseLabels;
+};
+
+inline void TileWidget::setAlwaysShowVerboseLabels(bool alwaysShowVerboseLabels)
+{
+ m_alwaysShowVerboseLabels = alwaysShowVerboseLabels;
+}
+
+class MarkWidget : public TileWidget
+{
+public:
+ MarkWidget();
+ virtual ~MarkWidget();
+
+ virtual void setSideLength(int sideLength);
+ virtual QColor tileColor();
+ virtual QColor letterTextColor();
+ virtual QFont letterFont();
+ virtual QString letterText();
+
+ void setRow(int row);
+ void setCol(int col);
+ void setCapstone();
+
+protected:
+ bool m_horizontal;
+ bool m_capstone;
+ QString m_letterText;
+};
+
+bool operator<(const QSize &firstSize, const QSize &secondSize);
+
+#endif
diff --git a/quacker/graphicalreporter.cpp b/quacker/graphicalreporter.cpp
new file mode 100644
index 0000000..67851f4
--- /dev/null
+++ b/quacker/graphicalreporter.cpp
@@ -0,0 +1,233 @@
+/*
+ * 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 <QtGui>
+
+#include <board.h>
+#include <computerplayer.h>
+#include <game.h>
+#include <quackleio/util.h>
+
+#include "graphicalboard.h"
+#include "graphicalreporter.h"
+
+const char *kHtmlHeader =
+"<html>\n"
+"<head>\n"
+"<title>Quackle Graphical Game Report</title>\n"
+"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf8\">\n"
+"</head>\n"
+"<body bgcolor=white>\n"
+"<h1>Graphical Game Report</h1>\n"
+"<p><i>Generated by Quackle crossword game AI and analysis software</i>\n"
+"<br /><a href=\"http://quackle.org\">http://quackle.org</a></p>\n"
+"\n\n"
+;
+
+GraphicalReporter::GraphicalReporter(const QString &outputDirectory, bool generateImages)
+ : m_output(outputDirectory), m_generateImages(generateImages)
+{
+}
+
+void GraphicalReporter::reportHeader(const Quackle::Game &game)
+{
+ openIndex();
+ m_indexStream << kHtmlHeader;
+ m_indexStream << QuackleIO::Util::uvStringToQString(game.currentPosition().board().htmlKey());
+}
+
+void GraphicalReporter::reportGame(const Quackle::Game &game, Quackle::ComputerPlayer *computerPlayer)
+{
+ reportHeader(game);
+
+ for (Quackle::PositionList::const_iterator it = game.history().begin(); it != game.history().end(); ++it)
+ {
+ reportPosition(*it, computerPlayer);
+ }
+}
+
+void GraphicalReporter::reportPosition(const Quackle::GamePosition &position, Quackle::ComputerPlayer *computerPlayer)
+{
+ openIndex();
+
+ const QSize pictureSize(500, 500);
+
+ Quackle::GamePosition positionCopy = position;
+
+ {
+ QString title;
+
+ if (!position.gameOver())
+ {
+ title = GraphicalBoard::tr("<h2>%1: Turn %2</h2>").arg(QuackleIO::Util::uvStringToQString(position.currentPlayer().name())).arg(position.turnNumber());
+ }
+ else
+ {
+ title = GraphicalBoard::tr("<h2>Game over.</h2>");
+ }
+
+ if (m_generateImages)
+ {
+ QPixmap pixmap;
+ positionCopy.resetMoveMade();
+ GraphicalBoardFrame::staticDrawPosition(positionCopy, pictureSize, &pixmap);
+
+ QImage image = pixmap.toImage();
+
+ const QString filebasename = QString("%1-%2-position.png").arg(position.turnNumber()).arg(QuackleIO::Util::uvStringToQString(position.currentPlayer().name()));
+ const QString filename = makeFilename(filebasename);
+
+ if (image.save(filename, "PNG"))
+ {
+ m_indexStream << QString("<a href=\"%1\">%2</a>").arg(filebasename).arg(title) << endl;
+ }
+ else
+ {
+ QMessageBox::critical(0, GraphicalBoard::tr("Error Writing File - Quacker"), GraphicalBoard::tr("Could not write image %1.").arg(filename));
+ }
+
+ m_indexStream << "<p><img src=\"" << filebasename << "\"></p>" << endl;
+ }
+ else
+ {
+ m_indexStream << title;
+
+ const int boardTileSize = position.gameOver()? 45 : 25;
+ m_indexStream << QuackleIO::Util::sanitizeUserVisibleLetterString(QuackleIO::Util::uvStringToQString(position.board().htmlBoard(boardTileSize))) << endl;
+ }
+ }
+
+ const Quackle::PlayerList players(position.endgameAdjustedScores());
+
+ m_indexStream << "<table cellspacing=6>" << endl;
+ for (Quackle::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
+ {
+ m_indexStream << "<tr>";
+
+ m_indexStream << "<td>";
+ if ((*it) == position.currentPlayer())
+ m_indexStream << "&rarr;";
+ else
+ m_indexStream << "&nbsp;";
+ m_indexStream << "</td>";
+
+ m_indexStream
+ << "<td>" << QuackleIO::Util::uvStringToQString((*it).name()) << "</td>"
+ << "<td>" << QuackleIO::Util::sanitizeUserVisibleLetterString(QuackleIO::Util::uvStringToQString((*it).rack().toString())) << "</td>"
+ << "<td>" << (*it).score() << "</td>"
+ << "</tr>"
+ << endl;
+ }
+ m_indexStream << "</table>" << endl;
+
+ if (computerPlayer && !position.gameOver())
+ {
+ computerPlayer->setPosition(position);
+
+ if (position.committedMove().isAMove())
+ computerPlayer->considerMove(position.committedMove());
+
+ const unsigned int movesToShow = 5;
+ Quackle::MoveList moves = computerPlayer->moves(movesToShow);
+
+ if (!moves.contains(position.committedMove()))
+ {
+ if (moves.size() == movesToShow)
+ moves.pop_back();
+
+ moves.push_back(position.committedMove());
+ }
+
+ m_indexStream << "<ol>" << endl;
+ for (Quackle::MoveList::const_iterator it = moves.begin(); it != moves.end(); ++it)
+ {
+ QString item;
+ switch ((*it).action)
+ {
+ case Quackle::Move::Place:
+ {
+ if (m_generateImages)
+ {
+ QPixmap pixmap;
+
+ positionCopy.setMoveMade(*it);
+ GraphicalBoardFrame::staticDrawPosition(positionCopy, pictureSize, &pixmap);
+
+ QImage image = pixmap.toImage();
+
+ const QString filebasename = QString("%1-%2-%3-%4.png").arg(position.turnNumber()).arg(QuackleIO::Util::uvStringToQString(position.currentPlayer().name())).arg(QuackleIO::Util::letterStringToQString((*it).prettyTiles())).arg(QuackleIO::Util::uvStringToQString((*it).positionString()));
+ const QString filename = makeFilename(filebasename);
+
+ if (image.save(filename, "PNG"))
+ {
+ item = QString("<a href=\"%1\">%2</a> %3").arg(filebasename);
+ }
+ else
+ {
+ QMessageBox::critical(0, GraphicalBoard::tr("Error Writing File - Quacker"), GraphicalBoard::tr("Could not write image %1.").arg(filename));
+ }
+ }
+ else
+ {
+ item = "%1 %2";
+ }
+
+ item = item.arg(QuackleIO::Util::sanitizeUserVisibleLetterString(QuackleIO::Util::moveToDetailedString(*it))).arg((*it).score);
+ break;
+ }
+
+ case Quackle::Move::Exchange:
+ default:
+ item = QuackleIO::Util::moveToDetailedString(*it);
+ break;
+ }
+
+ if (*it == position.committedMove())
+ item += QString(" &nbsp;&larr;");
+
+ if (!item.isEmpty())
+ m_indexStream << "<li>" << item << "</li>" << endl;
+ }
+ m_indexStream << "</ol>" << endl;
+ }
+
+ m_indexStream << "\n\n";
+}
+
+QString GraphicalReporter::makeFilename(const QString &filename) const
+{
+ return QString("%1/%2").arg(m_output).arg(filename);
+}
+
+void GraphicalReporter::openIndex()
+{
+ if (!m_indexStream.device())
+ {
+ m_indexFile.setFileName(m_generateImages? makeFilename("index.html") : m_output);
+ if (!m_indexFile.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(0, GraphicalBoard::tr("Error Writing File - Quacker"), GraphicalBoard::tr("Could not open %1 for writing.").arg(m_indexFile.fileName()));
+ return;
+ }
+
+ m_indexStream.setDevice(&m_indexFile);
+ }
+}
+
diff --git a/quacker/graphicalreporter.h b/quacker/graphicalreporter.h
new file mode 100644
index 0000000..720de1a
--- /dev/null
+++ b/quacker/graphicalreporter.h
@@ -0,0 +1,65 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_GRAPHICAL_REPORTER_H
+#define QUACKER_GRAPHICAL_REPORTER_H
+
+#include <QFile>
+#include <QTextStream>
+
+namespace Quackle
+{
+ class ComputerPlayer;
+ class GamePosition;
+ class Game;
+}
+
+class GraphicalReporter
+{
+public:
+ // If generateImages is true, output must be an existing directory.
+ // If false, output is an HTML file.
+ GraphicalReporter(const QString &output, bool generateImages);
+
+ // makes header and report for all positions
+ void reportGame(const Quackle::Game &game, Quackle::ComputerPlayer *computerPlayer);
+
+ // makes header and puts into the index file
+ void reportHeader(const Quackle::Game &game);
+
+ // makes graphical displays of top n plays of position, dumps to images
+ // and puts links to them in the index file.
+ void reportPosition(const Quackle::GamePosition &position, Quackle::ComputerPlayer *computerPlayer);
+
+protected:
+ QString makeFilename(const QString &filename) const;
+
+ // opens the index file (m_output/index.html or m_output if it's a file)
+ // if it is not already open
+ void openIndex();
+
+ QString m_output;
+ QFile m_indexFile;
+ QTextStream m_indexStream;
+
+ bool m_generateImages;
+};
+
+#endif
diff --git a/quacker/history.cpp b/quacker/history.cpp
new file mode 100644
index 0000000..4bac94d
--- /dev/null
+++ b/quacker/history.cpp
@@ -0,0 +1,158 @@
+/*
+ * 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 <QtGui>
+
+#include <game.h>
+#include <quackleio/util.h>
+
+#include "history.h"
+#include "geometry.h"
+
+History::History(QWidget *parent)
+ : HistoryView(parent)
+{
+ m_vlayout = new QVBoxLayout(this);
+ Geometry::setupFramedLayout(m_vlayout);
+
+ m_tableWidget = new QTableWidget(this);
+ m_tableWidget->setHorizontalScrollMode(QTableView::ScrollPerPixel);
+ connect(m_tableWidget, SIGNAL(itemActivated(QTableWidgetItem *)), this, SLOT(itemActivated(QTableWidgetItem *)));
+
+ setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+
+ m_vlayout->addWidget(m_tableWidget);
+}
+
+void History::historyChanged(const Quackle::History &history)
+{
+ m_tableWidget->clear();
+ const Quackle::PlayerList players(history.players());
+
+ QStringList columnLabels;
+ QStringList rowLabels;
+
+ for (int i = 1; i <= history.maximumTurnNumber(); ++i)
+ rowLabels.push_back(QString::number(i));
+
+ Quackle::GamePosition lastPosition(history.lastPosition());
+ lastPosition.setCurrentPlayer(lastPosition.playerOnTurn().id());
+ const bool gameOver = lastPosition.gameOver();
+ if (gameOver)
+ rowLabels.push_back(tr("Final"));
+
+ m_tableWidget->setRowCount(rowLabels.size());
+ m_tableWidget->setColumnCount(players.size());
+
+ Quackle::PlayerList currentScores(lastPosition.endgameAdjustedScores());
+
+ QTableWidgetItem *currentItem = 0;
+ int playerCountFromZero = 0;
+ Quackle::PlayerList::const_iterator currentScoresIt = currentScores.begin();
+ for (Quackle::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it, ++currentScoresIt, ++playerCountFromZero)
+ {
+ columnLabels.push_back(QuackleIO::Util::uvStringToQString((*it).name()));
+
+ const Quackle::PositionList positions(history.positionsFacedBy((*it).id()));
+ for (Quackle::PositionList::const_iterator pit = positions.begin(); pit != positions.end(); ++pit)
+ {
+ QTableWidgetItem *item = createItem(*pit, history.currentPosition().currentPlayer());
+
+ const int row = /* make zero indexed */ (*pit).turnNumber() - 1;
+ const int column = playerCountFromZero;
+ m_tableWidget->setItem(row, column, item);
+
+ Quackle::HistoryLocation location((*it).id(), (*pit).turnNumber());
+ m_locationMap.insert(location, item);
+
+ if (location == history.currentLocation())
+ {
+ currentItem = m_tableWidget->item(row, column);
+ m_tableWidget->setCurrentItem(currentItem);
+ }
+ }
+
+ if (gameOver)
+ {
+ QString scoreString(QString::number((*currentScoresIt).score()));
+
+ QTableWidgetItem *item = createPlainItem(scoreString);
+ m_tableWidget->setItem(rowLabels.size() - 1, playerCountFromZero, item);
+
+ if (history.currentPosition().gameOver())
+ currentItem = item;
+ }
+
+ if (!positions.empty())
+ m_tableWidget->resizeColumnToContents(playerCountFromZero);
+ }
+
+ m_tableWidget->setHorizontalHeaderLabels(columnLabels);
+ m_tableWidget->setVerticalHeaderLabels(rowLabels);
+
+ m_tableWidget->scrollToItem(currentItem);
+}
+
+QTableWidgetItem *History::createItem(const Quackle::GamePosition &position, const Quackle::Player &currentPlayer)
+{
+ const Quackle::Move committedMove(position.committedMove());
+ QString contentString;
+
+ if (committedMove.action == Quackle::Move::Nonmove)
+ contentString = tr("*TO PLAY*");
+ else
+ {
+ const QString moveString = (position.currentPlayer() == currentPlayer? QuackleIO::Util::moveToDetailedString(committedMove) : QuackleIO::Util::moveToSensitiveString(committedMove));
+
+ const int score = position.currentPlayer().score() + committedMove.effectiveScore();
+
+ const QString scoreString = QString("+%2/%3").arg(committedMove.effectiveScore()).arg(score);
+
+ // Pad scoreString to 8 chars, because '+117/381' is 8 chars, and this
+ // helps the words kinda line up with each other.
+ contentString = QString("%1 %2").arg(moveString).arg(scoreString, 8);
+ }
+
+ return createPlainItem(contentString);
+}
+
+QTableWidgetItem *History::createPlainItem(const QString &contentString)
+{
+ QTableWidgetItem *ret = new QTableWidgetItem(contentString);
+ ret->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+
+ // make scores line up with each other
+ ret->setTextAlignment(Qt::AlignRight);
+
+ return ret;
+}
+
+void History::itemActivated(QTableWidgetItem *item)
+{
+ for (QMap<Quackle::HistoryLocation, QTableWidgetItem *>::iterator it = m_locationMap.begin(); it != m_locationMap.end(); ++it)
+ {
+ if (it.value() == item)
+ {
+ emit goToHistoryLocation(it.key());
+ break;
+ }
+ }
+}
+
diff --git a/quacker/history.h b/quacker/history.h
new file mode 100644
index 0000000..f1d4c8f
--- /dev/null
+++ b/quacker/history.h
@@ -0,0 +1,66 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_HISTORY_H
+#define QUACKER_HISTORY_H
+
+#include <QMap>
+
+#include <game.h>
+
+#include "view.h"
+
+class QTableWidget;
+class QTableWidgetItem;
+class QVBoxLayout;
+
+namespace Quackle
+{
+ class Game;
+ class GamePosition;
+ class History;
+}
+
+class History : public HistoryView
+{
+Q_OBJECT
+
+public:
+ History(QWidget *parent = 0);
+ virtual ~History()
+ {
+ }
+
+public slots:
+ virtual void historyChanged(const Quackle::History &history);
+
+private slots:
+ void itemActivated(QTableWidgetItem *item);
+
+protected:
+ QTableWidgetItem *createItem(const Quackle::GamePosition &position, const Quackle::Player &currentPlayer);
+ QTableWidgetItem *createPlainItem(const QString &contentString);
+ QMap<Quackle::HistoryLocation, QTableWidgetItem *> m_locationMap;
+
+ QVBoxLayout *m_vlayout;
+ QTableWidget *m_tableWidget;
+};
+
+#endif
diff --git a/quacker/letterbox.cpp b/quacker/letterbox.cpp
new file mode 100644
index 0000000..e07aa0a
--- /dev/null
+++ b/quacker/letterbox.cpp
@@ -0,0 +1,1449 @@
+/*
+ * 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 <QtGui>
+
+#include <quackleio/dictfactory.h>
+#include <quackleio/util.h>
+
+#include "letterbox.h"
+#include "customqsettings.h"
+#include "quackersettings.h"
+#include "letterboxsettings.h"
+#include "lister.h"
+
+Letterbox *Letterbox::m_self = 0;
+Letterbox *Letterbox::self()
+{
+ return m_self;
+}
+
+Letterbox::Letterbox(QWidget *parent, QAction *preferencesAction, ListerDialog *listerDialog)
+ : QMainWindow(parent), m_initializationChuu(false), m_modified(false), m_mistakeMade(false), m_listerDialog(listerDialog), m_pauseMs(0), m_keystrokes(0), m_numberIterator(0), m_preferencesAction(preferencesAction)
+{
+ m_self = this;
+
+ createWidgets();
+ createMenu();
+ loadSettings();
+
+ setCaption(tr("No List"));
+
+ QTimer::singleShot(0, this, SLOT(finishInitialization()));
+}
+
+Letterbox::~Letterbox()
+{
+ saveSettings();
+}
+
+void Letterbox::closeEvent(QCloseEvent *closeEvent)
+{
+ closeEvent->setAccepted(tryToClose());
+}
+
+bool Letterbox::tryToClose()
+{
+ pause(true);
+
+ if (m_modified)
+ {
+ switch (askToSave())
+ {
+ case 0:
+ qApp->processEvents();
+ writeFile();
+
+ // fall through
+
+ case 1:
+ return true;
+
+ case 2:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void Letterbox::finishInitialization()
+{
+ // ensure no dangling iterators
+ m_clueResultsIterator = m_clueResults.begin();
+ m_answersIterator = m_answers.begin();
+ m_queryIterator = m_list.begin();
+
+ m_timer = new QTimer(this);
+ connect(m_timer, SIGNAL(timeout()), this, SLOT(timeout()));
+
+ m_clueResults.clear();
+
+ loadFile();
+
+ if (m_filename.isEmpty())
+ {
+ statusBar()->showMessage(tr("[Paused]") + QString(" ") + tr("Enjoy your letterboxings."));
+ loadExampleList();
+ }
+ else
+ {
+ statusBar()->showMessage(tr("[Paused]") + QString(" ") + tr("Enjoy your letterboxings on %1.").arg(m_filename.right(m_filename.length() - m_filename.lastIndexOf("/") - 1)));
+ }
+}
+
+void Letterbox::open()
+{
+ pause(true);
+
+ if (m_modified)
+ {
+ switch (askToSave())
+ {
+ case 0:
+ writeFile();
+
+ case 1:
+ break;
+
+ case 2:
+ return;
+ }
+ }
+
+ QString defaultFilter = defaultStudyListFileFilter();
+ QString filename = QFileDialog::getOpenFileName(this, tr("Choose Letterbox file to open"), getInitialDirectory(), studyListFileFilters(), &defaultFilter);
+ if (!filename.isEmpty())
+ {
+ setInitialDirectory(filename);
+ m_filename = filename;
+ loadFile();
+ saveSettings();
+ }
+}
+
+void Letterbox::openParticularFile(const QString &filename)
+{
+ if (!filename.isEmpty())
+ {
+ if (m_modified)
+ {
+ switch (askToSave())
+ {
+ case 0:
+ writeFile();
+
+ case 1:
+ break;
+
+ case 2:
+ return;
+ }
+ }
+
+ m_filename = filename;
+ loadFile();
+ }
+}
+
+void Letterbox::setCaption(const QString &text)
+{
+ if (!text.isNull())
+ m_ourCaption = text;
+
+ setWindowTitle(QString("%1[*] - Quackle Letterbox").arg(m_ourCaption));
+}
+
+void Letterbox::setModified(bool modified)
+{
+ m_modified = modified;
+ setWindowModified(m_modified);
+}
+
+bool Letterbox::dictCheck()
+{
+ if (!QuackleIO::DictFactory::querier()->isLoaded())
+ {
+ QMessageBox::critical(this, tr("No Dictionary Loaded - Quackle Letterbox"), tr("Please open a dictionary with Settings->Set Dictionary."));
+ return false;
+ }
+
+ return true;
+}
+
+int Letterbox::askToSave()
+{
+ return QMessageBox::warning(this, tr("Unsaved Results - Quackle Letterbox"), tr("There are unsaved results in the current Letterbox list. Save them?"), tr("&Save"), tr("&Discard"), tr("&Cancel"), 0, 2);
+}
+
+void Letterbox::generateList()
+{
+ pause(true);
+
+ if (m_listerDialog)
+ {
+ m_listerDialog->show();
+ m_listerDialog->raise();
+ }
+}
+
+void Letterbox::loadFile()
+{
+ if (m_filename.isEmpty())
+ return;
+
+ QString filename(m_filename.right(m_filename.length() - m_filename.lastIndexOf("/") - 1));
+ statusBar()->showMessage(tr("Loading %1...").arg(filename));
+ qApp->processEvents();
+
+ m_list.clear();
+ m_answers.clear();
+ m_clueResults.clear();
+
+ QFile file(m_filename);
+ if (!file.exists())
+ {
+ QMessageBox::critical(this, tr("Error Loading Letterbox List - Quackle Letterbox"), tr("Filename %1 does not exist").arg(m_filename));
+ return;
+ }
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Loading Letterbox List - Quackle Letterbox"), tr("%1 cannot be opened.").arg(filename));
+ return;
+ }
+
+ int startAt = 0;
+
+ QTextStream stream(&file);
+ QString line;
+
+ m_initializationChuu = true;
+
+ bool firstLine = true;
+
+ while (!stream.atEnd())
+ {
+ line = stream.readLine().trimmed().toUpper();
+
+ if (firstLine)
+ {
+ if (line.left(10) == "\" RESUME: ")
+ startAt = line.right(line.length() - 10).toInt();
+
+ firstLine = false;
+ }
+
+ line.remove('#');
+
+ QString letters = line;
+ QString comment;
+
+ int quoteMarkIndex = line.indexOf("\"");
+ if (quoteMarkIndex >= 0)
+ {
+ letters = line.left(quoteMarkIndex).trimmed();
+ comment = line.right(line.length() - quoteMarkIndex - 1).trimmed();
+ }
+
+ if (letters.isEmpty())
+ continue;
+
+ m_list += letters;
+
+ m_clueResults.append(parseComment(comment));
+ m_clueResults.last().clue = clueFor(letters);
+ }
+
+ file.close();
+
+ if (!dictCheck())
+ return;
+
+ jumpTo(startAt);
+
+ statusBar()->showMessage(tr("Loaded list `%1' of length %2.").arg(filename).arg(m_clueResults.count()));
+ setCaption(filename);
+
+ m_initializationChuu = false;
+ setModified(false);
+
+ prepareQuiz();
+ pause(true);
+}
+
+void Letterbox::loadExampleList()
+{
+ // TODO; load an interesting list, like sixes in playability order
+}
+
+void Letterbox::writeFile()
+{
+ outputResults();
+
+ setModified(false);
+ statusBar()->showMessage(tr("Saved results to file."));
+}
+
+void Letterbox::jumpTo()
+{
+ pause(true);
+
+ bool ok;
+ int index = QInputDialog::getInteger(this, tr("Jump to word - Quackle Letterbox"), tr("Index to which to jump:"), m_numberIterator + 1, 1, m_clueResults.count(), 1, &ok);
+ if (ok)
+ {
+ jumpTo(index);
+ }
+
+ prepareQuiz();
+ pause(true);
+}
+
+void Letterbox::jumpTo(int index)
+{
+ if (m_numberIterator != index)
+ setModified(true);
+
+ m_numberIterator = index;
+
+ if (index >= m_clueResults.size())
+ {
+ m_answersIterator = m_answers.end();
+ m_clueResultsIterator = m_clueResults.end();
+ m_queryIterator = m_list.end();
+ return;
+ }
+
+ while (m_answers.count() <= m_numberIterator)
+ {
+ m_answers.append(answersFor(m_list.at(m_answers.count())));
+ m_clueResults[m_answers.count() - 1].setWordList(m_answers.at(m_answers.count() - 1));
+ }
+
+ m_clueResultsIterator = m_clueResults.begin();
+ m_answersIterator = m_answers.begin();
+ for (int i = 0; i < index; ++i)
+ {
+ ++m_clueResultsIterator;
+ ++m_answersIterator;
+ }
+ (*m_clueResultsIterator).resetStats();
+
+ m_queryIterator = m_list.begin();
+ for (int i = 0; i < index + 1; ++i)
+ {
+ ++m_queryIterator;
+ }
+}
+
+ClueResult Letterbox::parseComment(const QString &comment)
+{
+ if (comment.isEmpty())
+ return ClueResult();
+
+ QStringList items = comment.split(" ", QString::SkipEmptyParts);
+
+ ClueResult ret;
+
+ for (QStringList::iterator it = items.begin(); it != items.end(); ++it)
+ {
+ WordResult word;
+ word.word = *it;
+
+ ++it;
+ word.time = (*it).toInt();
+
+ ++it;
+ word.keystrokes = (*it).toInt();
+ word.missed = (word.time == 0);
+
+ ret.words.append(word);
+ }
+
+ return ret;
+}
+
+void Letterbox::increment()
+{
+ jumpTo(m_numberIterator + 1);
+
+ if (m_numberIterator >= m_clueResults.size())
+ {
+ updateViews();
+ listFinished();
+ m_timer->stop();
+ return;
+ }
+
+ prepareQuiz();
+}
+
+void Letterbox::pause(bool paused)
+{
+ timerControl(paused);
+
+ if (m_pauseAction->isChecked() != paused)
+ m_pauseAction->setChecked(paused);
+
+ if (!paused)
+ m_lineEdit->setFocus();
+
+ if (paused)
+ {
+ m_pauseTime.start();
+ statusBar()->showMessage(tr("Paused on #%1 of %2 total.").arg(m_numberIterator + 1).arg(m_clueResults.count()));
+ }
+ else
+ {
+ m_pauseMs += m_pauseTime.elapsed();
+ statusBar()->showMessage(tr("Resuming..."));
+ }
+}
+
+void Letterbox::timerControl(bool paused)
+{
+ if (paused)
+ {
+ m_timer->stop();
+ }
+ else if (isInQuiz())
+ {
+ m_timer->setSingleShot(true);
+ m_timer->start(timerLength());
+ }
+}
+
+void Letterbox::markLastAsMissed()
+{
+ ClueResultList::iterator it(m_clueResultsIterator);
+ (*(--it)).resetStats();
+
+ if (!m_pauseAction->isChecked())
+ {
+ // reset clock
+ timerControl(true);
+ timerControl(false);
+ }
+
+ statusBar()->showMessage(tr("%1 marked as missed.").arg((*it).clue.clueString));
+}
+
+void Letterbox::skip()
+{
+ for (WordResultList::iterator it = (*m_clueResultsIterator).words.begin(); it != (*m_clueResultsIterator).words.end(); ++it)
+ {
+ (*it).missed = false;
+ (*it).keystrokes = (*it).word.length();
+ (*it).time = timerLength();
+ }
+
+ m_mistakeMade = false;
+ increment();
+}
+
+void Letterbox::prepareQuiz()
+{
+ updateViews();
+
+ m_submittedAnswers.clear();
+
+ m_lineEdit->clear();
+
+ m_mistakeMade = false;
+
+ if (m_numberIterator == m_clueResults.count())
+ {
+ statusBar()->clearMessage();
+ return;
+ }
+
+ m_lineEdit->setFocus();
+ m_pauseAction->setChecked(false);
+
+ statusBar()->showMessage(tr("Word #%1 of %2 total.").arg(m_numberIterator + 1).arg(m_clueResults.count()));
+
+ timerControl(true);
+ timerControl(false);
+
+ m_time.start();
+ m_keystrokes = 0;
+ m_pauseMs = 0;
+}
+
+int Letterbox::timerLength()
+{
+ if (!isInQuiz())
+ return 0;
+
+ return LetterboxSettings::self()->msecWaitBase + LetterboxSettings::self()->msecWaitExtraPerSolution * (*m_answersIterator).count();
+}
+
+void Letterbox::listFinished()
+{
+ writeFile();
+ statusBar()->showMessage(tr("List is finished, results saved."));
+ m_timer->stop();
+}
+
+bool Letterbox::isInQuiz() const
+{
+ return m_clueResults.size() > 0 && m_numberIterator < m_clueResults.size();
+}
+
+void Letterbox::outputResults()
+{
+ QFile file(m_filename);
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Writing File - Quackle Letterbox"), tr("Could not open %1 for writing.").arg(m_filename));
+ return;
+ }
+
+ QTextStream stream(&file);
+
+ if (m_numberIterator < m_clueResults.count())
+ stream << "\" Resume: " << m_numberIterator << "\n";
+
+ ClueResultList::iterator end = m_clueResults.end();
+ QStringList::iterator listIt = m_list.begin();
+ for (ClueResultList::iterator it = m_clueResults.begin(); it != end; ++it)
+ {
+ stream << *listIt;
+
+ if ((*it).words.count() > 0)
+ {
+ stream << " \"";
+ for (WordResultList::iterator word = (*it).words.begin(); word != (*it).words.end(); ++word)
+ stream << " " << (*word).word << " " << (*word).time << " " << (*word).keystrokes;
+ }
+
+ stream << "\n";
+
+ ++listIt;
+ }
+
+ file.close();
+
+ if (LetterboxSettings::self()->newMissesFile)
+ {
+ QString missesFilename = m_filename + QString("-misses");
+ QFile missesFile(missesFilename);
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error writing misses file"), tr("Could not open %1 for writing.").arg(missesFilename));
+ return;
+ }
+
+ QTextStream stream(&missesFile);
+
+ for (ClueResultList::iterator it = m_clueResults.begin(); it != m_clueResults.end(); ++it)
+ {
+ for (WordResultList::iterator word = (*it).words.begin(); word != (*it).words.end(); ++word)
+ {
+ if ((*word).time == 0)
+ {
+ stream << (*it).clue.clueString << "\n";
+ break;
+ }
+ }
+ }
+
+ missesFile.close();
+ }
+}
+
+int Letterbox::chewScore(const QString & /*word*/, const QString & /*steal*/)
+{
+ int ret = 0;
+
+ /*
+ int wordpairs[26][26];
+ for (int i = 0; i < 26; i++)
+ for (int j = 0; j < 26; j++)
+ wordpairs[i][j] = 0;
+
+ if ((word.length() < 2) || (steal.length() < word.length()))
+ return 0;
+
+ for (unsigned int i = 0; i < word.length() - 1; i++)
+ {
+ int a = word.at(i).latin1() - 'A';
+ int b = word.at(i + 1).latin1() - 'A';
+ if ((a >= 0) && (a < 26) && (b >= 0) && (b < 26))
+ wordpairs[a][b]++;
+ }
+
+ for (unsigned int i = 0; i < steal.length() - 1; i++)
+ {
+ int a = steal.at(i).latin1() - 'A';
+ int b = steal.at(i + 1).latin1() - 'A';
+ if ((a >= 0) && (a < 26) && (b >= 0) && (b < 26))
+ {
+ if (wordpairs[a][b] == 0)
+ ret++;
+ else
+ wordpairs[a][b]--;
+ }
+ }
+
+*/
+ return ret;
+}
+
+void Letterbox::updateDuringQuery()
+{
+ update();
+}
+
+Clue Letterbox::mathClue(const QString &clue)
+{
+ Dict::WordList clueWords = QuackleIO::DictFactory::querier()->query(clue);
+ QString word = (*clueWords.begin()).word;
+
+ int bestChew = 0;
+ QString ret;
+
+ for (int i = 0; i < word.length(); i++)
+ {
+ QString query = word.left(i) + word.right(word.length() - i - 1);
+ Dict::WordList words = QuackleIO::DictFactory::querier()->query(query);
+ for (Dict::WordList::iterator it = words.begin(); it != words.end(); ++it)
+ {
+ int chew = chewScore((*it).word, word);
+ if (chew > bestChew)
+ {
+ bestChew = chew;
+ ret = (*it).word + " + " + word.at(i);
+ }
+ }
+ }
+
+ if (bestChew > 2)
+ return Clue(ret);
+
+ return Clue(arrangeLettersForUser(word));
+}
+
+QString Letterbox::alphagram(const QString &word)
+{
+ return QuackleIO::Util::alphagram(word);
+}
+
+QString Letterbox::arrangeLettersForUser(const QString &word)
+{
+ return QuackleIO::Util::arrangeLettersForUser(word);
+}
+
+Clue Letterbox::clueFor(const QString &word)
+{
+ if (word.isNull())
+ return Clue(QString::null);
+
+ if (LetterboxSettings::self()->mathMode)
+ return mathClue(word);
+
+ return Clue(arrangeLettersForUser(word));
+}
+
+Dict::WordList Letterbox::answersFor(const QString &word)
+{
+ Dict::WordList results;
+
+ if (word.isNull())
+ return results;
+
+ // no updates during initialization of lists, but with extensions
+ results = QuackleIO::DictFactory::querier()->query(word, (m_initializationChuu? Dict::Querier::None : Dict::Querier::CallUpdate) | Dict::Querier::WithExtensions);
+
+ return results;
+}
+
+void Letterbox::mistakeDetector(const QString &text)
+{
+ m_keystrokes++;
+
+ QString upperText(text.toUpper());
+
+ if (upperText.length() == 0)
+ return;
+
+ for (Dict::WordList::iterator it = (*m_answersIterator).begin(); it != (*m_answersIterator).end(); ++it)
+ {
+ if (LetterboxSettings::self()->spaceComplete)
+ {
+ if (upperText[0] == ' ')
+ {
+ if (!m_submittedAnswers.contains((*it).word))
+ {
+ processAnswer((*it).word);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if ((*it).word.startsWith(upperText) && !m_submittedAnswers.contains((*it).word))
+ {
+ if (m_mistakeMade)
+ statusBar()->clearMessage();
+
+ if (LetterboxSettings::self()->autoCompleteLength > 0)
+ if (upperText.length() == LetterboxSettings::self()->autoCompleteLength)
+ processAnswer((*it).word);
+
+ return;
+ }
+ }
+ }
+
+ m_mistakeMade = true;
+
+ statusBar()->showMessage(tr("MISTAKE DETECTED!"), /* show for 2 seconds */ 2000);
+}
+
+void Letterbox::lineEditReturnPressed()
+{
+ processAnswer(m_lineEdit->text());
+}
+
+void Letterbox::processAnswer(const QString &answer)
+{
+ if (!isInQuiz())
+ return;
+
+ QString upperAnswer(answer.toUpper());
+
+ if (m_submittedAnswers.contains(upperAnswer) > 0)
+ {
+ statusBar()->showMessage(tr("You already submitted %1.").arg(upperAnswer));
+ m_lineEdit->clear();
+ return;
+ }
+
+ for (WordResultList::iterator it = (*m_clueResultsIterator).words.begin(); it != (*m_clueResultsIterator).words.end(); ++it)
+ {
+ if ((*it).word == upperAnswer)
+ {
+ (*it).missed = false;
+ (*it).keystrokes = m_keystrokes;
+ (*it).time = m_time.elapsed() - m_pauseMs;
+ }
+ }
+
+ for (Dict::WordList::iterator it = (*m_answersIterator).begin(); it != (*m_answersIterator).end(); ++it)
+ {
+ if ((*it).word == upperAnswer)
+ {
+ m_submittedAnswers.append(upperAnswer);
+ m_lineEdit->clear();
+ m_mistakeMade = false;
+
+ if (m_submittedAnswers.count() >= (*m_answersIterator).count())
+ {
+ increment();
+ return;
+ }
+ else
+ {
+ statusBar()->showMessage(tr("%1 is correct.").arg(upperAnswer));
+ m_solutionsView->addSubmission(upperAnswer);
+ break;
+ }
+ }
+ }
+
+ m_lineEdit->clear();
+ m_keystrokes = 0;
+ m_pauseMs = 0;
+}
+
+void Letterbox::timeout()
+{
+ increment();
+}
+
+void Letterbox::updateViews()
+{
+ m_solutionsView->setWords(m_answers.begin(), m_answersIterator);
+ m_upcomingView->setWords(m_clueResultsIterator, m_clueResults.end());
+}
+
+void Letterbox::createMenu()
+{
+ QMenu *file = menuBar()->addMenu(tr("&File"));
+ QMenu *go = menuBar()->addMenu(tr("&Go"));
+ QMenu *settings = menuBar()->addMenu(tr("&Settings"));
+ QMenu *help = menuBar()->addMenu(tr("&Help"));
+
+ QAction *generateAction = new QAction(tr("&Generate new list..."), this);
+ generateAction->setShortcut(tr("Ctrl+L"));
+ file->addAction(generateAction);
+ connect(generateAction, SIGNAL(activated()), this, SLOT(generateList()));
+
+ QAction *openAction = new QAction(tr("&Open..."), this);
+ openAction->setShortcut(tr("Ctrl+O"));
+ file->addAction(openAction);
+ connect(openAction, SIGNAL(activated()), this, SLOT(open()));
+
+ QAction *saveAction = new QAction(tr("&Save"), this);
+ saveAction->setShortcut(tr("Ctrl+S"));
+ file->addAction(saveAction);
+ connect(saveAction, SIGNAL(activated()), this, SLOT(writeFile()));
+
+ file->addSeparator();
+
+ QAction *printAction = new QAction(tr("&Print"), this);
+ file->addAction(printAction);
+ connect(printAction, SIGNAL(activated()), this, SLOT(print()));
+
+ QAction *printStudyAction = new QAction(tr("&Print Study Sheet"), this);
+ file->addAction(printStudyAction);
+ connect(printStudyAction, SIGNAL(activated()), this, SLOT(printStudy()));
+
+ file->addSeparator();
+
+ QAction *quitAction = new QAction(tr("&Close"), this);
+ quitAction->setShortcut(tr("Ctrl+W"));
+ file->addAction(quitAction);
+ connect(quitAction, SIGNAL(activated()), this, SLOT(close()));
+
+ QAction *jumpAction = new QAction(tr("&Jump to..."), this);
+ jumpAction->setShortcut(tr("Ctrl+J"));
+ go->addAction(jumpAction);
+ connect(jumpAction, SIGNAL(activated()), this, SLOT(jumpTo()));
+
+ QAction *markAsMissedAction = new QAction(tr("Mark last as &missed"), this);
+ markAsMissedAction->setShortcut(tr("Ctrl+M"));
+ go->addAction(markAsMissedAction);
+ connect(markAsMissedAction, SIGNAL(activated()), this, SLOT(markLastAsMissed()));
+
+ QAction *skipAction = new QAction(tr("S&kip"), this);
+ skipAction->setShortcut(tr("Ctrl+K"));
+ go->addAction(skipAction);
+ connect(skipAction, SIGNAL(activated()), this, SLOT(skip()));
+
+ go->addSeparator();
+
+ m_pauseAction = new QAction(tr("&Pause"), this);
+ m_pauseAction->setShortcut(tr("Ctrl+P"));
+ m_pauseAction->setCheckable(true);
+ go->addAction(m_pauseAction);
+ connect(m_pauseAction, SIGNAL(toggled(bool)), this, SLOT(pause(bool)));
+
+ QAction *focusAction = new QAction(tr("&Focus entry widget"), this);
+ focusAction->setShortcut(tr("Ctrl+F"));
+ go->addAction(focusAction);
+ connect(focusAction, SIGNAL(activated()), m_lineEdit, SLOT(setFocus()));
+
+ settings->addAction(m_preferencesAction);
+
+ settings->addSeparator();
+
+ QAction *aboutAction = new QAction(tr("&About Letterbox"), this);
+ help->addAction(aboutAction);
+ connect(aboutAction, SIGNAL(activated()), this, SLOT(about()));
+}
+
+void Letterbox::createWidgets()
+{
+ QWidget *centralWidget = new QWidget(this);
+ QVBoxLayout *centralLayout = new QVBoxLayout(centralWidget);
+
+ setCentralWidget(centralWidget);
+
+ WordView *solutionsView = new WordView(centralWidget);
+ WordView *upcomingView = new WordView(centralWidget);
+
+ m_lineEdit = new QLineEdit(centralWidget);
+ m_lineEdit->setAlignment(Qt::AlignHCenter);
+ m_lineEdit->setValidator(new InputValidator(m_lineEdit));
+ connect(m_lineEdit, SIGNAL(returnPressed()), this, SLOT(lineEditReturnPressed()));
+ connect(m_lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(mistakeDetector(const QString &)));
+
+ centralLayout->addWidget(solutionsView);
+ centralLayout->addWidget(m_lineEdit);
+ centralLayout->addWidget(upcomingView);
+
+ centralLayout->setStretchFactor(solutionsView, 3);
+
+ m_solutionsView = solutionsView;
+ m_upcomingView = upcomingView;
+}
+
+void Letterbox::print()
+{
+ pause(true);
+
+ QString filename = QFileDialog::getSaveFileName(this, tr("Choose HTML file to which to save pretty word list"), m_filename + ".html", "*.html");
+
+ if (filename.isEmpty())
+ return;
+
+ QFile file(filename);
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Writing File - Quackle Letterbox"), tr("Could not open %1 for writing.").arg(filename));
+ return;
+ }
+
+ HTMLRepresentation printer;
+
+ bool wasModified = m_modified;
+ int previousNumber = m_numberIterator;
+ statusBar()->showMessage(tr("Generating HTML..."));
+ jumpTo(m_clueResults.size() - 1);
+
+ printer.setWords(m_answers.begin(), m_answers.end());
+
+ jumpTo(previousNumber);
+ setModified(wasModified);
+
+ QTextStream stream(&file);
+ stream << printer.html() << "\n";
+
+ file.close();
+
+ statusBar()->showMessage(tr("%1 written.").arg(filename));
+}
+
+void Letterbox::printStudy()
+{
+ pause(true);
+
+ QString filename = QFileDialog::getSaveFileName(this, tr("Choose file to which to save study sheet"), m_filename + "-study");
+
+ if (filename.isEmpty())
+ return;
+
+ QFile file(filename);
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Writing File - Quackle Letterbox"), tr("Could not open %1 for writing.").arg(filename));
+ return;
+ }
+
+ bool wasModified = m_modified;
+ int previousNumber = m_numberIterator;
+ statusBar()->showMessage(tr("Generating study sheet..."));
+ jumpTo(m_clueResults.size() - 1);
+
+ QTextStream stream(&file);
+ stream << generateStudySheet(m_answers.begin(), m_answers.end()) << "\n";
+
+ file.close();
+
+ jumpTo(previousNumber);
+ setModified(wasModified);
+
+ statusBar()->showMessage(tr("%1 written.").arg(filename));
+}
+
+QString Letterbox::generateStudySheet(Dict::WordListList::ConstIterator start, Dict::WordListList::ConstIterator end)
+{
+ QString ret;
+
+ int prevLengthOfExtensions = LetterboxSettings::self()->lengthOfExtensions;
+ LetterboxSettings::self()->lengthOfExtensions = 1;
+
+ for (Dict::WordListList::ConstIterator it = start; it != end; ++it)
+ {
+ int length = (*it).front().word.length();
+ QString pad = " ";
+ for (int i = 0; i < length; ++i)
+ pad += " ";
+
+ ret += arrangeLettersForUser((*it).front().word) + " ";
+ bool first = true;
+ for (Dict::WordList::ConstIterator wit = (*it).begin(); wit != (*it).end(); ++wit)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ ret += pad;
+ }
+
+ ret += HTMLRepresentation::prettyExtensionList((*wit).getFrontExtensionList(), false);
+ ret += " ";
+ ret += (*wit).word;
+ ret += " ";
+ ret += HTMLRepresentation::prettyExtensionList((*wit).getBackExtensionList(), false);
+ ret += "\n";
+ }
+ }
+
+ LetterboxSettings::self()->lengthOfExtensions = prevLengthOfExtensions;
+
+ return ret;
+}
+
+void Letterbox::about()
+{
+ QMessageBox::about(this, tr("About Quackle Letterbox"), "<p><b>Letterbox</b> is a lexical study tool, that is now part of Quackle.</p><p>Copyright 2005-2007 by<ul><li>John O'Laughlin &lt;olaughlin@gmail.com&gt;</li><li>Jason Katz-Brown &lt;jasonkatzbrown@gmail.com&gt;</li></ul>");
+}
+
+void Letterbox::saveSettings()
+{
+ LetterboxSettings::self()->writeSettings();
+
+ CustomQSettings settings;
+
+ settings.setValue("quackle/letterbox/most-recent-list", m_filename);
+ settings.setValue("quackle/letterbox/window-size", size());
+}
+
+void Letterbox::loadSettings()
+{
+ LetterboxSettings::self()->readSettings();
+
+ CustomQSettings settings;
+ m_filename = settings.value("quackle/letterbox/most-recent-list", QString("")).toString();
+ resize(settings.value("quackle/letterbox/window-size", QSize(800, 600)).toSize());
+}
+
+//////////
+
+WordResult::WordResult()
+{
+ resetStats();
+}
+
+WordResult::WordResult(QString w)
+{
+ word = w;
+ resetStats();
+}
+
+void WordResult::resetStats()
+{
+ missed = true;
+ keystrokes = 0;
+ time = 0;
+}
+
+//////////
+
+ClueResult::ClueResult()
+{
+}
+
+ClueResult::ClueResult(const QString &newClue)
+ : clue(newClue)
+{
+}
+
+void ClueResult::setWordList(Dict::WordList answers)
+{
+ for (Dict::WordList::iterator it = answers.begin(); it != answers.end(); ++it)
+ {
+ bool isAnswerInOurWords = false;
+ for (WordResultList::iterator wrIt = words.begin(); wrIt != words.end(); ++wrIt)
+ {
+ if ((*it).word == (*wrIt).word)
+ {
+ isAnswerInOurWords = true;
+ break;
+ }
+ }
+
+ if (!isAnswerInOurWords)
+ words.append(WordResult((*it).word));
+ }
+}
+
+void ClueResult::resetStats()
+{
+ for (WordResultList::iterator wrIt = words.begin(); wrIt != words.end(); ++wrIt)
+ (*wrIt).resetStats();
+}
+
+Clue::Clue()
+{
+}
+
+Clue::Clue(const QString &newClueString)
+ : clueString(newClueString)
+{
+}
+
+//////////
+
+InputValidator::InputValidator(QObject *parent) : QValidator(parent)
+{
+}
+
+InputValidator::~InputValidator()
+{
+}
+
+QValidator::State InputValidator::validate(QString & /* input */, int &) const
+{
+ return Acceptable;
+}
+
+/////////
+
+WordView::WordView(QWidget *parent)
+ : QTextEdit(parent)
+{
+ m_shownSolutions = 16;
+ m_shownClues = 30;
+ m_tablePadding = 0;
+ m_tableSpacing = 0;
+
+ setReadOnly(true);
+
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ // TODO(jasonkb) background/foreground colors
+ //setPaper(QBrush(QColor(LetterboxSettings::self()->backgroundColor)));
+ //setColor(LetterboxSettings::self()->foregroundColor);
+}
+
+WordView::~WordView()
+{
+}
+
+void WordView::htmlUpdated(ContentType type)
+{
+ setText(html());
+
+ switch (type)
+ {
+ case Content_Upcoming:
+ scrollToAnchor("top");
+ break;
+
+ case Content_Solutions:
+ QTextCursor cursor = textCursor();
+ cursor.movePosition(QTextCursor::End);
+ setTextCursor(cursor);
+ ensureCursorVisible();
+ break;
+ }
+}
+
+////////
+
+HTMLRepresentation::HTMLRepresentation()
+ : m_shownSolutions(INT_MAX), m_shownClues(INT_MAX), m_tablePadding(4), m_tableSpacing(0)
+{
+}
+
+HTMLRepresentation::~HTMLRepresentation()
+{
+}
+
+void HTMLRepresentation::htmlUpdated(ContentType /* type */ )
+{
+}
+
+void HTMLRepresentation::setHTML(const QString &text, ContentType type)
+{
+ m_html = text;
+ htmlUpdated(type);
+}
+
+QString HTMLRepresentation::html()
+{
+ return m_html;
+}
+
+void HTMLRepresentation::setWords(ClueResultList::ConstIterator start, ClueResultList::ConstIterator end, bool revers)
+{
+ QString html("<a name=top>");
+
+ int shown = 0;
+
+ html += "<center>";
+
+ if (revers)
+ {
+ ClueResultList::ConstIterator it = end;
+ while (shown < m_shownClues)
+ {
+ if (it == start)
+ break;
+
+ --it;
+
+ if (shown == 0)
+ html += "<font size=6>";
+ else
+ html += "<font size=6>";
+
+ html += htmlForPlainWord((*it).clue.clueString) + "<br>";
+
+ html += "</font>";
+
+ shown++;
+ }
+ }
+ else
+ {
+ for (ClueResultList::ConstIterator it = start; (it != end) && (shown < 30); ++it)
+ {
+ if (shown == 0)
+ html += "<font size=6>";
+ else
+ html += "<font size=6>";
+
+ html += htmlForPlainWord((*it).clue.clueString) + "<br>";
+
+ if (shown == 0)
+ html += "</font>";
+
+ shown++;
+ }
+ }
+
+ html += "</center>";
+
+ setHTML(html, Content_Upcoming);
+}
+
+void HTMLRepresentation::setWords(Dict::WordListList::ConstIterator start, Dict::WordListList::ConstIterator end, bool revers)
+{
+ Dict::WordListList::ConstIterator newStart = end;
+ for (int i = 0; i < m_shownSolutions; i++)
+ if (newStart != start)
+ newStart--;
+ start = newStart;
+
+ QString html("<a name=top>");
+
+ if (revers)
+ {
+ Dict::WordListList::ConstIterator it = end;
+ while (true)
+ {
+ if (it == start)
+ break;
+
+ --it;
+ html += htmlForWordList(*it);
+ }
+ }
+ else
+ {
+ for (Dict::WordListList::ConstIterator it = start; it != end; ++it)
+ html += htmlForWordList(*it);
+ }
+
+ setHTML(html, Content_Solutions);
+}
+
+QString HTMLRepresentation::htmlForWordList(const Dict::WordList &wordList)
+{
+ if (wordList.isEmpty())
+ return QString::null;
+
+ QString html = QString("<center><table border=1 cellspacing=%1 cellpadding=%2>").arg(m_tableSpacing).arg(m_tablePadding);
+
+ bool hasFrontExtensionColumn = false;
+ bool hasBackExtensionColumn = false;
+
+ Dict::WordList::ConstIterator end = wordList.end();
+ for (Dict::WordList::ConstIterator it = wordList.begin(); it != end; ++it)
+ {
+ if (!(tableOfExtensions((*it).getFrontExtensionList()).isEmpty()))
+ hasFrontExtensionColumn = true;
+ if (!(tableOfExtensions((*it).getBackExtensionList()).isEmpty()))
+ hasBackExtensionColumn = true;
+ }
+
+ for (Dict::WordList::ConstIterator it = wordList.begin(); it != end; ++it)
+ {
+ html += "<tr>";
+
+ if (hasFrontExtensionColumn)
+ html += "<td align=right>" + tableOfExtensions((*it).getFrontExtensionList()) + "</td>";
+
+ html += "<td align=center>" + htmlForSolution(*it) + "</td>";
+
+ if (hasBackExtensionColumn)
+ html += "<td align=left>" + tableOfExtensions((*it).getBackExtensionList()) + "</td>";
+
+ html += "</tr>";
+ }
+ html += "</table></center><font size=\"-7\"><br></font>\n";
+
+ return html;
+}
+
+QString HTMLRepresentation::htmlForPlainWord(const QString &word)
+{
+ QString fontArgs("color=\"%1\"");
+
+ QString html("<font " + fontArgs.arg(LetterboxSettings::self()->foregroundColor) + ">%1</font>");
+
+ return html.arg(word);
+}
+
+QString HTMLRepresentation::htmlForSolution(const Dict::Word &word)
+{
+ QString fontArgs("size=\"+5\" color=\"%1\"");
+
+ QString html("<b><font " + fontArgs.arg(word.british? LetterboxSettings::self()->sowpodsColor : LetterboxSettings::self()->foregroundColor) + ">%1</font></b>");
+
+ return html.arg(word.word + (word.british? "#" : ""));
+}
+
+QString HTMLRepresentation::tableOfExtensions(const Dict::ExtensionList &list)
+{
+ QString html;
+
+ bool first = true;
+
+ for (int i = 1; i <= LetterboxSettings::self()->lengthOfExtensions; ++i)
+ {
+ Dict::ExtensionList extensions(Dict::Word::extensionsByLength(i, list));
+ if (extensions.count() > 0)
+ {
+ QString extensionHtml = HTMLRepresentation::prettyExtensionList(extensions);
+
+ if (!extensionHtml.isEmpty())
+ {
+ if (!first)
+ html += "<br>";
+ else
+ first = false;
+
+ if (i == 1)
+ {
+ html += "<font size=5>";
+ }
+ else
+ {
+ html += "<font size=3>";
+ }
+
+ html += htmlForPlainWord(extensionHtml);
+
+ html += "</font>";
+ }
+ }
+ }
+
+ return html;
+}
+
+void HTMLRepresentation::addSubmission(const QString &submission)
+{
+ QString newHtml(html());
+ QString item(QString("%1").arg(submission));
+
+ if (newHtml.endsWith("</p>"))
+ newHtml.insert(newHtml.length() - 4, QString("<br>") + item);
+ else
+ newHtml += QString("<center><h2>%1</h2><p>%2</p>").arg(qApp->tr("Correct Answers")).arg(item);
+
+ setHTML(newHtml, Content_Solutions);
+}
+
+QString HTMLRepresentation::prettyExtensionList(const Dict::ExtensionList &list, bool make_html)
+{
+ QString ret;
+
+ int extensionChars = 0;
+ QString space = make_html? "&nbsp;" : " ";
+
+ for (Dict::ExtensionList::ConstIterator it = list.begin(); it != list.end(); ++it)
+ {
+ QString fontArgs("color=\"%1\"");
+
+ QString color = (*it).british? LetterboxSettings::self()->sowpodsColor : LetterboxSettings::self()->foregroundColor;
+
+ QString html;
+
+ if (make_html)
+ html += "<font " + fontArgs.arg(color) + ">%1</font>";
+ else
+ html += "%1";
+
+ QString wordHtml;
+
+ if (LetterboxSettings::self()->numExtensionChars > 0)
+ {
+ if (extensionChars >= LetterboxSettings::self()->numExtensionChars)
+ {
+ int numExtensions = 0;
+ numExtensions = list.size();
+
+ ret += QString("...%1(%2)").arg(space).arg(numExtensions);
+ return ret;
+ }
+ }
+
+ wordHtml += (*it).word;
+ extensionChars += (*it).word.length();
+
+ // this is super clumsy, working around the fact that it doesn't show leading nbsps
+ bool displayableTokensRemain = false;
+
+ for (Dict::ExtensionList::ConstIterator lookAhead = it; lookAhead != list.end(); ++lookAhead)
+ {
+ if (lookAhead == it)
+ continue;
+
+ displayableTokensRemain = true;
+ }
+
+ if (displayableTokensRemain)
+ wordHtml += space;
+
+ ret += html.arg(wordHtml);
+
+ if (!displayableTokensRemain)
+ return ret;
+ }
+
+ return ret;
+}
+
+QString Letterbox::getInitialDirectory() const
+{
+ return m_initialDirectory.isEmpty()? QDir::homePath() : m_initialDirectory;
+}
+
+void Letterbox::setInitialDirectory(const QString &filename)
+{
+ QFileInfo file(filename);
+ m_initialDirectory = file.path();
+}
+
+QString Letterbox::studyListFileFilters() const
+{
+ return tr("Letterbox files (%1);;All files (*)").arg("*.letterbox");
+}
+
+QString Letterbox::defaultStudyListFileFilter() const
+{
+ return "*.letterbox";
+}
+
diff --git a/quacker/letterbox.h b/quacker/letterbox.h
new file mode 100644
index 0000000..d7a2b34
--- /dev/null
+++ b/quacker/letterbox.h
@@ -0,0 +1,295 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_LETTERBOX_H
+#define QUACKER_LETTERBOX_H
+
+#include <QMainWindow>
+#include <QValidator>
+#include <QTextEdit>
+#include <QTime>
+
+#include <quackleio/dict.h>
+
+class QAction;
+class QLineEdit;
+class QTimer;
+class Letterbox;
+class HTMLRepresentation;
+class ListerDialog;
+
+class WordResult
+{
+public:
+ WordResult();
+ WordResult(QString w);
+ void resetStats();
+ QString word;
+ int time;
+ bool missed;
+ int keystrokes;
+};
+
+typedef QList<WordResult> WordResultList;
+
+class Clue
+{
+public:
+ Clue();
+ Clue(const QString &newClueString);
+ QString clueString;
+};
+
+class ClueResult
+{
+public:
+ ClueResult();
+ ClueResult(const QString &newClue);
+ Clue clue;
+
+ void setWordList(Dict::WordList answers);
+ WordResultList words;
+
+ void resetStats();
+};
+
+typedef QList<ClueResult> ClueResultList;
+
+class InputValidator : public QValidator
+{
+Q_OBJECT
+
+public:
+ InputValidator(QObject *parent = 0);
+ ~InputValidator();
+
+ QValidator::State validate(QString&, int&) const;
+};
+
+class Letterbox : public QMainWindow
+{
+Q_OBJECT
+
+public:
+ Letterbox(QWidget *parent, QAction *preferencesAction, ListerDialog *listerDialog);
+ ~Letterbox();
+
+ static Letterbox *self();
+
+ bool tryToClose();
+ void closeEvent(QCloseEvent *closeEvent);
+
+ // make alphagram
+ QString alphagram(const QString &word);
+
+ // make pattern of letters user wants
+ QString arrangeLettersForUser(const QString &word);
+
+ // make an anahook expression where one letter is added (if not possible, return alphagram)
+ Clue mathClue(const QString &word);
+
+ // Letterbox clue giver
+ Clue clueFor(const QString &word);
+
+ // calculate an anahook's score as an anagram steal with John Chew's system
+ int chewScore(const QString &word, const QString &steal);
+
+ void updateDuringQuery();
+
+public slots:
+ void open();
+ void openParticularFile(const QString &filename);
+ void generateList();
+ void writeFile();
+ void print();
+ void printStudy();
+ void about();
+
+ void loadFile();
+
+ // call timerControl, and tell user about it
+ void pause(bool paused);
+
+ void markLastAsMissed();
+ void skip();
+
+ void increment();
+ void prepareQuiz();
+
+ // only adjusts iterators; does not prepareQuiz()
+ void jumpTo(int index);
+
+ // asks user where to jump to
+ void jumpTo();
+
+protected slots:
+ void finishInitialization();
+ void lineEditReturnPressed();
+ void mistakeDetector(const QString &text);
+ void timeout();
+
+ void setCaption(const QString &text = QString::null);
+ void setModified(bool modified);
+
+protected:
+ void saveSettings();
+
+ // use this to control timer!
+ void timerControl(bool paused);
+
+ // also calls createWidgets()
+ void loadSettings();
+
+ // opens a word list for new users
+ void loadExampleList();
+
+ // all anagrams of letters
+ Dict::WordList answersFor(const QString &word);
+
+ // if answer correct, tell user and keep note.
+ // if user has given all correct answers, increment()
+ void processAnswer(const QString &answer);
+
+ void updateViews();
+
+ // let user know he can stick a fork in this list.
+ // perhaps later record the history of his misery?
+ void listFinished();
+
+ // returns true if there is an alphagram being shown to be answered.
+ bool isInQuiz() const;
+
+ // outputs results to the same file words were loaded from
+ void outputResults();
+
+ QString generateStudySheet(Dict::WordListList::ConstIterator start, Dict::WordListList::ConstIterator end);
+
+ // returns length of time user gets to answer
+ // based on m_msecWaitBase and m_msecWaitExtraPerSolution
+ int timerLength();
+
+ bool dictCheck();
+
+ static Letterbox *m_self;
+
+ // returns 0 for save, 1 for discard, 2 for cancel
+ int askToSave();
+
+ // used to know when to update UI when querying anagrammer
+ bool m_initializationChuu;
+
+private:
+ QString m_filename;
+ bool m_modified;
+ QString m_ourCaption;
+
+ QLineEdit *m_lineEdit;
+ bool m_mistakeMade;
+
+ ListerDialog *m_listerDialog;
+
+ QTimer *m_timer;
+ QTime m_time;
+ QTime m_pauseTime;
+ int m_pauseMs;
+ int m_keystrokes;
+
+ QStringList m_list;
+ Dict::WordListList m_answers;
+
+ Dict::WordListList::iterator m_answersIterator;
+ QStringList::iterator m_queryIterator;
+
+ int m_numberIterator;
+
+ ClueResultList m_clueResults;
+ ClueResultList::iterator m_clueResultsIterator;
+
+ // returns empty clue result if empty comment
+ ClueResult parseComment(const QString &comment);
+
+ QStringList m_submittedAnswers;
+
+ HTMLRepresentation *m_solutionsView;
+ HTMLRepresentation *m_upcomingView;
+
+ QAction *m_pauseAction;
+ QAction *m_preferencesAction;
+
+ void createMenu();
+ void createWidgets();
+
+ QString getInitialDirectory() const;
+ void setInitialDirectory(const QString &filename);
+ QString m_initialDirectory;
+ QString studyListFileFilters() const;
+ QString defaultStudyListFileFilter() const;
+};
+
+class HTMLRepresentation
+{
+public:
+ HTMLRepresentation();
+ virtual ~HTMLRepresentation();
+
+ // set solutions!
+ void setWords(Dict::WordListList::ConstIterator start, Dict::WordListList::ConstIterator end, bool revers = false);
+
+ // set upcoming!
+ void setWords(ClueResultList::ConstIterator start, ClueResultList::ConstIterator end, bool revers = false);
+
+ // add something to list at bottom!
+ void addSubmission(const QString &submission);
+
+ enum ContentType { Content_Solutions, Content_Upcoming };
+
+ virtual void htmlUpdated(ContentType type);
+
+ void setHTML(const QString &text, ContentType type);
+ QString html();
+
+ static QString prettyExtensionList(const Dict::ExtensionList &list, bool html = true);
+
+protected:
+ QString htmlForPlainWord(const QString &word);
+ QString htmlForSolution(const Dict::Word &word);
+ QString htmlForWordList(const Dict::WordList &wordList);
+ QString tableOfExtensions(const Dict::ExtensionList &list);
+
+ int m_shownSolutions;
+ int m_shownClues;
+ int m_tablePadding;
+ int m_tableSpacing;
+
+ QString m_html;
+};
+
+class WordView : public QTextEdit, public HTMLRepresentation
+{
+Q_OBJECT
+
+public:
+ WordView(QWidget *parent = 0);
+ virtual ~WordView();
+
+ virtual void htmlUpdated(ContentType type);
+};
+
+#endif
diff --git a/quacker/letterboxsettings.cpp b/quacker/letterboxsettings.cpp
new file mode 100644
index 0000000..66f052a
--- /dev/null
+++ b/quacker/letterboxsettings.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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 <QtGui>
+
+#include "customqsettings.h"
+#include "letterboxsettings.h"
+
+LetterboxSettings *LetterboxSettings::m_self = 0;
+LetterboxSettings *LetterboxSettings::self()
+{
+ return m_self;
+}
+
+LetterboxSettings::LetterboxSettings()
+ : msecWaitBase(1500), msecWaitExtraPerSolution(1500), backgroundColor("#eeeeee"), foregroundColor("#000000"), sowpodsColor("#ff0000"), lengthOfExtensions(3), autoCompleteLength(0), mathMode(false), numExtensionChars(32), spaceComplete(false), newMissesFile(false)
+{
+ m_self = this;
+}
+
+LetterboxSettings::~LetterboxSettings()
+{
+}
+
+void LetterboxSettings::readSettings()
+{
+ CustomQSettings settings;
+
+ msecWaitBase = settings.value("quackle/letterbox/msecWaitBase", msecWaitBase).toInt();
+ msecWaitExtraPerSolution = settings.value("quackle/letterbox/msecWaitExtraPerSolution", msecWaitExtraPerSolution).toInt();
+
+ backgroundColor = settings.value("quackle/letterbox/backgroundColor", backgroundColor).toString();
+ foregroundColor = settings.value("quackle/letterbox/foregroundColor", foregroundColor).toString();
+ sowpodsColor = settings.value("quackle/letterbox/sowpodsColor", sowpodsColor).toString();
+
+ dictFilename = settings.value("quackle/letterbox/dict/filename", dictFilename).toString();
+ dictGaddagFilename = settings.value("quackle/letterbox/dict/gaddagfilename", dictGaddagFilename).toString();
+
+ lengthOfExtensions = settings.value("quackle/letterbox/lengthOfExtensions", lengthOfExtensions).toInt();
+
+ mathMode = settings.value("quackle/letterbox/mathMode", mathMode).toBool();
+
+ autoCompleteLength = settings.value("quackle/letterbox/autoCompleteLength", autoCompleteLength).toInt();
+
+ numExtensionChars = settings.value("quackle/letterbox/numExtensionChars", numExtensionChars).toInt();
+
+ spaceComplete = settings.value("quackle/letterbox/spaceComplete", spaceComplete).toBool();
+ newMissesFile = settings.value("quackle/letterbox/newMissesFile", newMissesFile).toBool();
+}
+
+void LetterboxSettings::writeSettings()
+{
+ CustomQSettings settings;
+
+ settings.setValue("quackle/letterbox/msecWaitBase", msecWaitBase);
+ settings.setValue("quackle/letterbox/msecWaitExtraPerSolution", msecWaitExtraPerSolution);
+
+ settings.setValue("quackle/letterbox/backgroundColor", backgroundColor);
+ settings.setValue("quackle/letterbox/foregroundColor", foregroundColor);
+ settings.setValue("quackle/letterbox/sowpodsColor", sowpodsColor);
+
+ settings.setValue("quackle/letterbox/dict/filename", dictFilename);
+ settings.setValue("quackle/letterbox/dict/gaddagfilename", dictGaddagFilename);
+
+ settings.setValue("quackle/letterbox/lengthOfExtensions", lengthOfExtensions);
+
+ settings.setValue("quackle/letterbox/mathMode", mathMode);
+
+ settings.setValue("quackle/letterbox/autoCompleteLength", autoCompleteLength);
+ settings.setValue("quackle/letterbox/numExtensionChars", numExtensionChars);
+
+ settings.setValue("quackle/letterbox/spaceComplete", spaceComplete);
+ settings.setValue("quackle/letterbox/newMissesFile", newMissesFile);
+}
+
diff --git a/quacker/letterboxsettings.h b/quacker/letterboxsettings.h
new file mode 100644
index 0000000..a21ba26
--- /dev/null
+++ b/quacker/letterboxsettings.h
@@ -0,0 +1,63 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_LETTERBOXSETTINGS_H
+#define QUACKER_LETTERBOXSETTINGS_H
+
+#include <QString>
+
+class LetterboxSettings
+{
+public:
+ LetterboxSettings();
+ ~LetterboxSettings();
+
+ static LetterboxSettings *self();
+
+ void readSettings();
+ void writeSettings();
+
+ int msecWaitBase;
+ int msecWaitExtraPerSolution;
+
+ QString backgroundColor;
+ QString foregroundColor;
+ QString sowpodsColor;
+
+ QString dictFilename;
+ QString dictGaddagFilename;
+
+ int lengthOfExtensions;
+
+ // 0 == off
+ int autoCompleteLength;
+
+ bool mathMode;
+
+ int numExtensionChars;
+
+ bool spaceComplete;
+ bool newMissesFile;
+
+private:
+ static LetterboxSettings *m_self;
+};
+
+#endif
diff --git a/quacker/lister.cpp b/quacker/lister.cpp
new file mode 100644
index 0000000..55941a8
--- /dev/null
+++ b/quacker/lister.cpp
@@ -0,0 +1,723 @@
+/*
+ * 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 <iostream>
+using namespace std;
+
+#include <QtGui>
+
+#include <quackleio/dictfactory.h>
+
+#include "lister.h"
+#include "customqsettings.h"
+
+ListerDialog::ListerDialog(QWidget *parent, const QString &settingsGroup, const QString &appName, int flags)
+ : QDialog(parent), m_settingsGroup(settingsGroup), m_appName(appName), m_flags(flags)
+{
+ setWindowTitle(windowTitleWithAppName(tr("Lister")));
+
+ QVBoxLayout *vbox = new QVBoxLayout(this);
+
+ QHBoxLayout *mainHorizontalLayout = new QHBoxLayout;
+ vbox->addLayout(mainHorizontalLayout);
+
+ QVBoxLayout *leftSideLayout = new QVBoxLayout;
+ mainHorizontalLayout->addLayout(leftSideLayout);
+ QVBoxLayout *rightSideLayout = new QVBoxLayout;
+ mainHorizontalLayout->addLayout(rightSideLayout);
+ mainHorizontalLayout->setStretchFactor(leftSideLayout, 3);
+
+ // left side
+ QHBoxLayout *queryLayout = new QHBoxLayout;
+ leftSideLayout->addLayout(queryLayout);
+
+ m_queryEdit = new QLineEdit;
+ connect(m_queryEdit, SIGNAL(returnPressed()), this, SLOT(queryGo()));
+ queryLayout->addWidget(m_queryEdit);
+
+ m_queryButton = new QPushButton(tr("A-Z?* Query"));
+ queryLayout->addWidget(m_queryButton);
+ connect(m_queryButton, SIGNAL(clicked()), this, SLOT(queryGo()));
+
+ QHBoxLayout *smallHLayout = new QHBoxLayout;
+ leftSideLayout->addLayout(smallHLayout);
+
+ m_numResultsLabel = new QLabel;
+ smallHLayout->addWidget(m_numResultsLabel);
+ smallHLayout->addStretch();
+
+ m_buildChecker = new QCheckBox(tr("Buil&d"));
+ smallHLayout->addWidget(m_buildChecker);
+
+ m_sowpodsChecker = new QCheckBox(tr("Remove &British"));
+ connect(m_sowpodsChecker, SIGNAL(toggled(bool)), this, SLOT(setRemoveSowpods(bool)));
+
+ if (!(m_flags & IgnoreBritishness))
+ smallHLayout->addWidget(m_sowpodsChecker);
+
+ m_listBox = new QListWidget;
+ leftSideLayout->addWidget(m_listBox);
+ m_numResultsLabel->setBuddy(m_listBox);
+
+ // right side
+
+ QLabel *filtersLabel = new QLabel(tr("&Filters:"));
+ rightSideLayout->addWidget(filtersLabel);
+
+ m_filtersBox = new QListWidget;
+ rightSideLayout->addWidget(m_filtersBox);
+ connect(m_filtersBox, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(showFilter(QListWidgetItem *)));
+ filtersLabel->setBuddy(m_filtersBox);
+
+ QStringList filters;
+
+ if (m_flags & ProbabilityInsteadOfPlayability)
+ filters << "Probability";
+ else
+ filters << "Playability";
+
+ filters << "Regex";
+ filters << "Num. Anagrams";
+
+ if (!(m_flags & IgnoreBritishness))
+ filters << "Keep British";
+
+ m_filtersBox->addItems(filters);
+
+ m_filtersLayout = new QVBoxLayout;
+ rightSideLayout->addLayout(m_filtersLayout);
+ m_currentFilter = 0;
+
+ m_applyButton = new QPushButton(tr("&Apply"));
+ rightSideLayout->addWidget(m_applyButton);
+
+ // bottom
+
+ QHBoxLayout *filenameLayout = new QHBoxLayout;
+ vbox->addLayout(filenameLayout);
+ QPushButton *filenameButton = new QPushButton(tr("Pic&k filename..."));
+ filenameLayout->addWidget(filenameButton);
+ connect(filenameButton, SIGNAL(clicked()), this, SLOT(chooseFilename()));
+ m_filenameEdit = new QLineEdit;
+ filenameLayout->addWidget(m_filenameEdit);
+
+ QHBoxLayout *buttonBox = new QHBoxLayout;
+ vbox->addLayout(buttonBox);
+ QHBoxLayout *buttonBox2 = new QHBoxLayout;
+ vbox->addLayout(buttonBox2);
+
+ m_writeButton = new QPushButton(tr("&Write alphagram file"));
+ m_writeNormalButton = new QPushButton(tr("&Write normal file"));
+ m_studyThisButton = new QPushButton(tr("Write and &Study"));
+ QPushButton *openButton = new QPushButton(tr("&Open File..."));
+ QPushButton *clearButton = new QPushButton(tr("C&lear"));
+ m_closeButton = new QPushButton(tr("&Close"));
+
+ buttonBox->addWidget(m_writeButton);
+ buttonBox->addWidget(m_writeNormalButton);
+
+ if (!(m_flags & NothingToReturn))
+ buttonBox->addWidget(m_studyThisButton);
+
+ buttonBox->addWidget(openButton);
+ buttonBox2->addWidget(clearButton);
+ buttonBox2->addStretch();
+ buttonBox2->addWidget(m_closeButton);
+
+ filenameChanged(QString::null); // disable write button initially
+
+ connect(m_filenameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(filenameChanged(const QString &)));
+ connect(m_writeButton, SIGNAL(clicked()), this, SLOT(writeButtonClicked()));
+ connect(m_writeNormalButton, SIGNAL(clicked()), this, SLOT(writeNormalButtonClicked()));
+ connect(m_studyThisButton, SIGNAL(clicked()), this, SLOT(studyButtonClicked()));
+ connect(m_closeButton, SIGNAL(clicked()), this, SLOT(done()));
+ connect(clearButton, SIGNAL(clicked()), this, SLOT(clear()));
+ connect(openButton, SIGNAL(clicked()), this, SLOT(openFile()));
+
+ showFilter(filters.first());
+ //m_filtersBox->setCurrentItem(0);
+
+ clear();
+}
+
+ListerDialog::~ListerDialog()
+{
+ saveSettings();
+}
+
+void ListerDialog::resetFocus()
+{
+ // Qt has some quirks so we're thorough
+ m_closeButton->clearFocus();
+ m_queryButton->setFocus();
+ m_queryEdit->setFocus();
+}
+
+void ListerDialog::setQuery(const QString &query)
+{
+ m_queryEdit->setText(query);
+ m_queryEdit->selectAll();
+ resetFocus();
+}
+
+void ListerDialog::queryGo()
+{
+ if (m_queryEdit->text().length() >= 40)
+ {
+ QMessageBox::warning(this, tr("Overlong Query - Quackle"), QString("<html>%1</html>").arg(tr("Queries, arbitrarily, cannot exceed 40 letters.")));
+ return;
+ }
+
+ m_wordList = QuackleIO::DictFactory::querier()->query(m_queryEdit->text(), m_buildChecker->isChecked()? Dict::Querier::NoRequireAllLetters : Dict::Querier::None);
+
+ setRemoveSowpods(m_sowpodsChecker->isChecked());
+ populateListBox();
+
+ // per Robin's request
+ m_queryEdit->deselect();
+}
+
+void ListerDialog::chooseFilename()
+{
+ QString filename = QFileDialog::getSaveFileName(this, tr("Choose file to save list to"), m_filenameEdit->text());
+ if (!filename.isEmpty())
+ m_filenameEdit->setText(filename);
+}
+
+void ListerDialog::filenameChanged(const QString &filename)
+{
+ m_studyThisButton->setEnabled(!filename.isEmpty());
+ m_writeButton->setEnabled(!filename.isEmpty());
+ m_writeNormalButton->setEnabled(!filename.isEmpty());
+
+ m_filename = filename;
+}
+
+void ListerDialog::clear()
+{
+ m_wordList.clear();
+ populateListBox();
+ m_queryEdit->clear();
+
+ // do not remove this fatuous line; otherwise Close button gets
+ // mysterious focus?
+ m_queryButton->setFocus();
+
+ m_queryEdit->setFocus();
+}
+
+void ListerDialog::openFile()
+{
+ QString filename = QFileDialog::getOpenFileName(this, tr("Choose list to open"));
+ if (filename.isEmpty())
+ return;
+
+ m_wordList.clear();
+
+ QFile file(filename);
+ if (file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ QTextStream stream(&file);
+ QString line;
+ while (!stream.atEnd())
+ {
+ line = stream.readLine();
+
+ int quoteMarkIndex = line.indexOf("\"");
+ if (quoteMarkIndex >= 0)
+ line = line.left(quoteMarkIndex).trimmed();
+
+ QStringList words(line.split(" "));
+ for (QStringList::Iterator it = words.begin(); it != words.end(); ++it)
+ {
+ bool found = false;
+ Dict::WordList results(QuackleIO::DictFactory::querier()->query(*it));
+ for (Dict::WordList::Iterator resultsIt = results.begin(); resultsIt != results.end(); ++resultsIt)
+ {
+ if ((*resultsIt).word == *it)
+ {
+ m_wordList.append(*resultsIt);
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ continue;
+
+ m_wordList += results;
+ }
+ }
+
+ file.close();
+ }
+
+ setWordList(m_wordList);
+}
+
+void ListerDialog::showFilter(const QString &filterName)
+{
+ Filter *newFilter = 0;
+ if (filterName == "Playability" || filterName == "Probability")
+ newFilter = new PlayabilityFilter(this);
+ else if (filterName == "Regex")
+ newFilter = new RegexFilter(this);
+ else if (filterName == "Num. Anagrams")
+ newFilter = new NumAnagramsFilter(this);
+ else if (filterName == "Keep British")
+ newFilter = new KeepBritishFilter(this);
+
+ if (m_currentFilter)
+ saveSettings();
+ delete m_currentFilter;
+
+ m_currentFilter = newFilter;
+
+ if (!m_currentFilter)
+ {
+ m_applyButton->setEnabled(false);
+ return;
+ }
+
+ m_filtersLayout->addWidget(m_currentFilter);
+ loadSettings();
+ m_currentFilter->show();
+
+ m_applyButton->setEnabled(true);
+ connect(m_applyButton, SIGNAL(clicked()), m_currentFilter, SLOT(apply()));
+}
+
+void ListerDialog::showFilter(QListWidgetItem *item)
+{
+ showFilter(item->text());
+}
+
+void ListerDialog::done()
+{
+ saveSettings();
+ resetFocus();
+ reject();
+}
+
+QString ListerDialog::run(QWidget *parent, const QString &settingsGroup, const QString &appName, int flags)
+{
+ ListerDialog dialog(parent, settingsGroup, appName, flags);
+
+ bool accepted = (dialog.exec() == QDialog::Accepted);
+
+ if (accepted)
+ return dialog.filename();
+
+ return QString::null;
+}
+
+void ListerDialog::saveSettings()
+{
+ CustomQSettings settings;
+ settings.beginGroup(m_settingsGroup);
+
+ if (m_currentFilter)
+ m_currentFilter->saveSettings(&settings);
+
+ settings.setValue("sowpods", m_sowpodsChecker->isChecked());
+ settings.setValue("build", m_buildChecker->isChecked());
+}
+
+void ListerDialog::loadSettings()
+{
+ CustomQSettings settings;
+ settings.beginGroup(m_settingsGroup);
+
+ if (m_currentFilter)
+ m_currentFilter->loadSettings(&settings);
+
+ m_sowpodsChecker->setChecked(settings.value("sowpods", false).toBool());
+ m_buildChecker->setChecked(settings.value("build", false).toBool());
+}
+
+void ListerDialog::setRemoveSowpods(bool removeSowpods)
+{
+ if (!removeSowpods)
+ return;
+
+ Dict::WordList filteredWords;
+
+ Dict::WordList::Iterator end = m_wordList.end();
+ for (Dict::WordList::Iterator it = m_wordList.begin(); it != end; ++it)
+ {
+ if (!(*it).british)
+ filteredWords.append((*it));
+ }
+
+ m_wordList = filteredWords;
+ populateListBox();
+}
+
+void ListerDialog::writeButtonClicked()
+{
+ // alphagrams
+ writeList(true);
+}
+
+void ListerDialog::writeNormalButtonClicked()
+{
+ // not alphagrams
+ writeList(false);
+}
+
+void ListerDialog::studyButtonClicked()
+{
+ // alphagrams
+ writeList(true);
+
+ saveSettings();
+ accept();
+}
+
+void ListerDialog::populateListBox()
+{
+ QStringList words;
+
+ Dict::WordList::Iterator end = m_wordList.end();
+ for (Dict::WordList::Iterator it = m_wordList.begin(); it != end; ++it)
+ words.append((*it).word + ((*it).british? "#" : ""));
+
+ m_listBox->clear();
+ m_listBox->addItems(words);
+
+ m_numResultsLabel->setText(tr("%1 &results").arg(words.count()));
+}
+
+QMap<QString, Dict::WordList> ListerDialog::anagramMap()
+{
+ QMap<QString, Dict::WordList> anagramSets;
+
+ Dict::WordList::Iterator end = m_wordList.end();
+ for (Dict::WordList::Iterator it = m_wordList.begin(); it != end; ++it)
+ {
+ QString alpha = QuackleIO::DictFactory::querier()->alphagram((*it).word);
+ anagramSets[alpha].append(*it);
+ }
+
+ return anagramSets;
+}
+
+QString ListerDialog::writeList(bool alphagrams)
+{
+ if (m_filename.isEmpty())
+ return QString::null;
+
+ QFile file(m_filename);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, windowTitleWithAppName(tr("Error writing file")), tr("Could not open %1 for writing.").arg(m_filename));
+ return QString::null;
+ }
+
+ QTextStream stream(&file);
+
+ QMap<QString, Dict::WordList> map(anagramMap());
+
+ Dict::WordList::Iterator end = m_wordList.end();
+ for (Dict::WordList::Iterator it = m_wordList.begin(); it != end; ++it)
+ {
+ if (alphagrams)
+ {
+ QString alphagram(QuackleIO::DictFactory::querier()->alphagram((*it).word));
+ if (map.contains(alphagram))
+ {
+ stream << alphagram << "\n";
+ map.remove(alphagram);
+ }
+ }
+ else
+ stream << (*it).word << "\n";
+ }
+
+ file.close();
+
+ return m_filename;
+}
+
+Dict::WordList &ListerDialog::wordList()
+{
+ return m_wordList;
+}
+
+void ListerDialog::setWordList(Dict::WordList list)
+{
+ m_wordList = list;
+ setRemoveSowpods(m_sowpodsChecker->isChecked());
+ populateListBox();
+}
+
+QSpinBox *ListerDialog::makeSpinBox(int minimum, int maximum, int singleStep)
+{
+ QSpinBox *ret = new QSpinBox;
+
+ ret->setMinimum(minimum);
+ ret->setMaximum(maximum);
+ ret->setSingleStep(singleStep);
+
+ return ret;
+}
+
+QString ListerDialog::windowTitleWithAppName(const QString &windowTitle)
+{
+ if (m_appName.isEmpty())
+ return windowTitle;
+ else
+ return windowTitle + QString(" - %1").arg(m_appName);
+}
+
+///////////////
+
+Filter::Filter(ListerDialog *dialog)
+ : QFrame(dialog), m_dialog(dialog)
+{
+ m_vbox = new QVBoxLayout(this);
+ setFrameStyle(QFrame::Panel | QFrame::Raised);
+ setLineWidth(2);
+}
+
+void Filter::apply()
+{
+}
+
+void Filter::saveSettings(QSettings *)
+{
+}
+
+void Filter::loadSettings(QSettings *)
+{
+}
+
+///////////////
+
+PlayabilityFilter::PlayabilityFilter(ListerDialog *dialog)
+ : Filter(dialog)
+{
+ QHBoxLayout *minRankLayout = new QHBoxLayout;
+ m_vbox->addLayout(minRankLayout);
+
+ QLabel *minRankLabel = new QLabel(tr("&Minimum rank:"));
+ minRankLayout->addWidget(minRankLabel);
+ m_minRankSpinner = ListerDialog::makeSpinBox(0, 99999, 100);
+ m_minRankSpinner->setSpecialValueText(tr("none"));
+ minRankLabel->setBuddy(m_minRankSpinner);
+ minRankLayout->addWidget(m_minRankSpinner);
+
+ QHBoxLayout *maxRankLayout = new QHBoxLayout;
+ m_vbox->addLayout(maxRankLayout);
+
+ QLabel *maxRankLabel = new QLabel(tr("Ma&ximum rank:"));
+ maxRankLayout->addWidget(maxRankLabel);
+ m_maxRankSpinner = ListerDialog::makeSpinBox(0, 99999, 100);
+ m_maxRankSpinner->setSpecialValueText(tr("none"));
+ maxRankLabel->setBuddy(m_maxRankSpinner);
+ maxRankLayout->addWidget(m_maxRankSpinner);
+}
+
+void PlayabilityFilter::apply()
+{
+ int minimumRank = m_minRankSpinner->value();
+ int maximumRank = m_maxRankSpinner->value();
+ if (maximumRank == 0)
+ maximumRank = INT_MAX;
+
+ const bool useProb = m_dialog->flags() & ListerDialog::ProbabilityInsteadOfPlayability;
+
+ QMap<QString, Dict::WordList> map(m_dialog->anagramMap());
+
+ Dict::WordList intermediateList;
+
+ QMap<QString, Dict::WordList>::Iterator end = map.end();
+ for (QMap<QString, Dict::WordList>::Iterator it = map.begin(); it != end; ++it)
+ {
+ Dict::Word newEntry;
+ newEntry.word = it.key();
+
+ for (Dict::WordList::Iterator extIt = it.value().begin(); extIt != it.value().end(); ++extIt)
+ {
+ if (useProb)
+ newEntry.probability += (*extIt).probability;
+ else
+ newEntry.playability += (*extIt).playability;
+ }
+
+ intermediateList.append(newEntry);
+ }
+
+ int index = 0;
+
+ if (useProb)
+ intermediateList.setSortBy(Dict::WordList::Probability);
+ else
+ intermediateList.setSortBy(Dict::WordList::Playability);
+ qSort(intermediateList);
+
+ Dict::WordList filteredList;
+
+ Dict::WordList::Iterator intermediateEnd = intermediateList.end();
+ for (Dict::WordList::Iterator it = intermediateList.begin(); it != intermediateEnd; ++it)
+ {
+ ++index;
+
+ if (index > maximumRank)
+ break;
+
+ if (index < minimumRank)
+ continue;
+
+ filteredList += map[(*it).word];
+ }
+
+ m_dialog->setWordList(filteredList);
+}
+
+void PlayabilityFilter::saveSettings(QSettings *settings)
+{
+ settings->setValue("playabilityfilter/minRank", m_minRankSpinner->value());
+ settings->setValue("playabilityfilter/maxRank", m_maxRankSpinner->value());
+}
+
+void PlayabilityFilter::loadSettings(QSettings *settings)
+{
+ m_minRankSpinner->setValue(settings->value("playabilityfilter/minRank", 0).toInt());
+ m_maxRankSpinner->setValue(settings->value("playabilityfilter/maxRank", 0).toInt());
+}
+
+///////////////
+
+RegexFilter::RegexFilter(ListerDialog *dialog)
+ : Filter(dialog)
+{
+ m_lineEdit = new QLineEdit;
+ m_vbox->addWidget(m_lineEdit);
+ connect(m_lineEdit, SIGNAL(returnPressed()), this, SLOT(apply()));
+}
+
+void RegexFilter::apply()
+{
+ QRegExp regexp(m_lineEdit->text());
+ regexp.setCaseSensitivity(Qt::CaseInsensitive);
+
+ Dict::WordList filteredList;
+ const Dict::WordList &list = m_dialog->wordList();;
+ Dict::WordList::ConstIterator end = list.end();
+
+ for (Dict::WordList::ConstIterator it = list.begin(); it != end; ++it)
+ if (regexp.indexIn((*it).word) >= 0)
+ filteredList.append(*it);
+
+ m_dialog->setWordList(filteredList);
+}
+
+////////////////////
+
+NumAnagramsFilter::NumAnagramsFilter(ListerDialog *dialog)
+ : Filter(dialog)
+{
+ QHBoxLayout *twlAnagramsLayout = new QHBoxLayout;
+ m_vbox->addLayout(twlAnagramsLayout);
+
+ QLabel *twlAnagramsLabel = new QLabel(tr("Number of &TWL anagrams:"));
+ twlAnagramsLayout->addWidget(twlAnagramsLabel);
+ m_twlAnagramsSpinner = ListerDialog::makeSpinBox(0, 15, 1);
+ twlAnagramsLabel->setBuddy(m_twlAnagramsSpinner);
+ twlAnagramsLayout->addWidget(m_twlAnagramsSpinner);
+
+ QHBoxLayout *oswOnlyAnagramsLayout = new QHBoxLayout;
+ QLabel *oswOnlyAnagramsLabel = new QLabel(tr("Number of &OSW-only anagrams:"));
+ oswOnlyAnagramsLayout->addWidget(oswOnlyAnagramsLabel);
+ m_oswOnlyAnagramsSpinner = ListerDialog::makeSpinBox(0, 15, 1);
+ oswOnlyAnagramsLabel->setBuddy(m_oswOnlyAnagramsSpinner);
+ oswOnlyAnagramsLayout->addWidget(m_oswOnlyAnagramsSpinner);
+
+ if (!(m_dialog->flags() & ListerDialog::IgnoreBritishness))
+ m_vbox->addLayout(oswOnlyAnagramsLayout);
+}
+
+void NumAnagramsFilter::apply()
+{
+ int numTwlAnagrams = m_twlAnagramsSpinner->value();
+ int numOswOnlyAnagrams = m_oswOnlyAnagramsSpinner->value();
+
+ Dict::WordList filteredList;
+
+ QMap<QString, Dict::WordList> map(m_dialog->anagramMap());
+
+ Dict::WordList::Iterator end = m_dialog->wordList().end();
+ for (Dict::WordList::Iterator it = m_dialog->wordList().begin(); it != end; ++it)
+ {
+ 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)
+ {
+ if ((*word).british)
+ british++;
+ else
+ twl++;
+
+ playability += (*word).playability;
+ }
+
+ if ((twl == numTwlAnagrams) && (british == numOswOnlyAnagrams))
+ filteredList.append(*it);
+ }
+
+ m_dialog->setWordList(filteredList);
+}
+
+void NumAnagramsFilter::saveSettings(QSettings *settings)
+{
+ settings->setValue("numanagramsfilter/twlAnagrams", m_twlAnagramsSpinner->value());
+ settings->setValue("numanagramsfilter/oswOnlyAnagrams", m_oswOnlyAnagramsSpinner->value());
+}
+
+void NumAnagramsFilter::loadSettings(QSettings *settings)
+{
+ m_twlAnagramsSpinner->setValue(settings->value("numanagramsfilter/twlAnagrams", 1).toInt());
+ m_oswOnlyAnagramsSpinner->setValue(settings->value("numanagramsfilter/oswOnlyAnagrams", 0).toInt());
+}
+
+////////////////////
+
+KeepBritishFilter::KeepBritishFilter(ListerDialog *dialog)
+ : Filter(dialog)
+{
+}
+
+void KeepBritishFilter::apply()
+{
+ Dict::WordList filteredList;
+ const Dict::WordList &list = m_dialog->wordList();;
+ Dict::WordList::ConstIterator end = list.end();
+
+ for (Dict::WordList::ConstIterator it = list.begin(); it != end; ++it)
+ if ((*it).british)
+ filteredList.append(*it);
+
+ m_dialog->setWordList(filteredList);
+}
+
diff --git a/quacker/lister.h b/quacker/lister.h
new file mode 100644
index 0000000..60ba784
--- /dev/null
+++ b/quacker/lister.h
@@ -0,0 +1,204 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKLE_LISTER_H
+#define QUACKLE_LISTER_H
+
+#include <qdialog.h>
+#include <qframe.h>
+
+#include <quackleio/dict.h>
+
+class QCheckBox;
+class QLabel;
+class QLineEdit;
+class QListWidget;
+class QListWidgetItem;
+class QPushButton;
+class QSettings;
+class QSpinBox;
+class QVBoxLayout;
+
+class Filter;
+
+class ListerDialog : public QDialog
+{
+Q_OBJECT
+
+public:
+ enum ListerFlags { FullLister = 0x0000, IgnoreBritishness = 0x0001, ProbabilityInsteadOfPlayability = 0x0002, NothingToReturn = 0x0004 };
+
+ ListerDialog(QWidget *parent, const QString &settingsGroup, const QString &appName = QString::null, int flags = FullLister);
+ ~ListerDialog();
+
+ // use this for modal running! settingsGroup is like "letterbox"
+ static QString run(QWidget *parent, const QString &settingsGroup, const QString &appName = QString::null, int flags = FullLister);
+
+ Dict::WordList &wordList();
+ void setWordList(Dict::WordList list);
+
+ // map of alphagrams to words
+ QMap<QString, Dict::WordList> anagramMap();
+
+ QString writeList(bool alphagrams);
+ QString filename() { return m_filename; }
+
+ int flags() { return m_flags; }
+
+ static QSpinBox *makeSpinBox(int minimum, int maximum, int singleStep);
+
+public slots:
+ void setQuery(const QString &query);
+ void queryGo();
+ void setRemoveSowpods(bool on);
+
+ // makes filter widget and save/load settings exactly once
+ void showFilter(const QString &filterName);
+ void showFilter(QListWidgetItem *item);
+
+ void chooseFilename();
+ void filenameChanged(const QString &);
+
+ void clear();
+ void openFile();
+
+ void writeButtonClicked();
+ void writeNormalButtonClicked();
+ void studyButtonClicked();
+
+ QString windowTitleWithAppName(const QString &windowTitle);
+
+ void done();
+
+protected:
+ Dict::WordList m_wordList;
+ QString m_settingsGroup;
+ QString m_appName;
+ int m_flags;
+
+ void saveSettings();
+ void loadSettings();
+
+ void resetFocus();
+ void populateListBox();
+
+private:
+
+ // left side
+ QLineEdit *m_queryEdit;
+ QPushButton *m_queryButton;
+ QPushButton *m_closeButton;
+ QLabel *m_numResultsLabel;
+ QCheckBox *m_sowpodsChecker;
+ QCheckBox *m_buildChecker;
+
+ // right side
+ QListWidget *m_filtersBox;
+ QVBoxLayout *m_filtersLayout;
+ Filter *m_currentFilter;
+ QPushButton *m_applyButton;
+
+ // bottom
+ QLineEdit *m_filenameEdit;
+ QPushButton *m_writeButton;
+ QPushButton *m_writeNormalButton;
+ QPushButton *m_studyThisButton;
+ QListWidget *m_listBox;
+
+ QString m_filename;
+};
+
+class Filter : public QFrame
+{
+Q_OBJECT
+
+public:
+ Filter(ListerDialog *dialog);
+
+public slots:
+ virtual void apply();
+ virtual void saveSettings(QSettings *settings);
+ virtual void loadSettings(QSettings *settings);
+
+protected:
+ ListerDialog *m_dialog;
+ QVBoxLayout *m_vbox;
+};
+
+class RegexFilter : public Filter
+{
+Q_OBJECT
+
+public:
+ RegexFilter(ListerDialog *dialog);
+
+public slots:
+ virtual void apply();
+
+private:
+ QLineEdit *m_lineEdit;
+};
+
+class PlayabilityFilter : public Filter
+{
+Q_OBJECT
+
+public:
+ PlayabilityFilter(ListerDialog *dialog);
+
+public slots:
+ virtual void apply();
+ virtual void saveSettings(QSettings *settings);
+ virtual void loadSettings(QSettings *settings);
+
+private:
+ QSpinBox *m_minRankSpinner;
+ QSpinBox *m_maxRankSpinner;
+};
+
+class NumAnagramsFilter : public Filter
+{
+Q_OBJECT
+
+public:
+ NumAnagramsFilter(ListerDialog *dialog);
+
+public slots:
+ virtual void apply();
+ virtual void saveSettings(QSettings *settings);
+ virtual void loadSettings(QSettings *settings);
+
+private:
+ QSpinBox *m_twlAnagramsSpinner;
+ QSpinBox *m_oswOnlyAnagramsSpinner;
+};
+
+class KeepBritishFilter : public Filter
+{
+Q_OBJECT
+
+public:
+ KeepBritishFilter(ListerDialog *dialog);
+
+public slots:
+ virtual void apply();
+};
+
+#endif
diff --git a/quacker/main.cpp b/quacker/main.cpp
new file mode 100644
index 0000000..e293853
--- /dev/null
+++ b/quacker/main.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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 <QApplication>
+
+#include "quacker.h"
+
+int main(int argc, char **argv)
+{
+ QApplication a(argc, argv);
+ TopLevel topLevel;
+ topLevel.show();
+ return a.exec();
+}
+
diff --git a/quacker/movebox.cpp b/quacker/movebox.cpp
new file mode 100644
index 0000000..3e29c5d
--- /dev/null
+++ b/quacker/movebox.cpp
@@ -0,0 +1,301 @@
+/*
+ * 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 <iostream>
+
+#include <QtGui>
+
+#include <game.h>
+#include <quackleio/util.h>
+
+#include "geometry.h"
+#include "movebox.h"
+
+MoveBox::MoveBox(QWidget *parent)
+ : View(parent)
+{
+ QVBoxLayout *vlayout = new QVBoxLayout(this);
+ Geometry::setupInnerLayout(vlayout);
+ vlayout->setSpacing(0);
+
+ m_treeWidget = new QTreeWidget(this);
+
+ m_treeWidget->setColumnCount(3);
+ m_treeWidget->setSelectionMode(QTreeWidget::ExtendedSelection);
+ QStringList headers;
+ headers << tr("Move") << tr("Score") << tr("Leave") << tr("Win %") << tr("Valuation");
+ m_treeWidget->setHeaderLabels(headers);
+
+ QHBoxLayout *buttonLayout = new QHBoxLayout;
+ Geometry::setupInnerLayout(buttonLayout);
+ m_removeButton = new QPushButton(tr("Remove"));
+ connect(m_removeButton, SIGNAL(clicked()), this, SLOT(removeMove()));
+ buttonLayout->addWidget(m_removeButton);
+
+ m_commitButton = new QPushButton(tr("Commit"));
+ connect(m_commitButton, SIGNAL(clicked()), this, SIGNAL(commit()));
+ buttonLayout->addWidget(m_commitButton);
+
+ setSelectionWatchingEnabled(true);
+
+ vlayout->addWidget(m_treeWidget);
+ vlayout->addLayout(buttonLayout);
+}
+
+void MoveBox::moveActivated(QTreeWidgetItem *item)
+{
+ if (item == 0)
+ {
+ emit setCandidateMove(Quackle::Move::createNonmove());
+ return;
+ }
+
+ // do nothing if no item was clicked or if more than one item is selected
+ if (m_treeWidget->selectedItems().size() != 1)
+ return;
+
+ for (QMap<Quackle::Move, QTreeWidgetItem *>::iterator it = m_moveMap.begin(); it != m_moveMap.end(); ++it)
+ {
+ if (it.value() == item)
+ {
+ emit setCandidateMove(it.key());
+ break;
+ }
+ }
+}
+
+void MoveBox::selectionChanged()
+{
+ const bool moveSelected = !m_treeWidget->selectedItems().empty();
+ m_removeButton->setEnabled(moveSelected);
+ m_commitButton->setEnabled(moveSelected);
+}
+
+void MoveBox::removeMove()
+{
+ QList<QTreeWidgetItem *> selectedItems = m_treeWidget->selectedItems();
+ if (selectedItems.empty())
+ return;
+
+ Quackle::MoveList selectedMoves;
+
+ for (QList<QTreeWidgetItem *>::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it)
+ {
+ for (QMap<Quackle::Move, QTreeWidgetItem *>::iterator mapIt = m_moveMap.begin(); mapIt != m_moveMap.end(); ++mapIt)
+ {
+ if (mapIt.value() == *it)
+ {
+ selectedMoves.push_back(mapIt.key());
+ break;
+ }
+ }
+ }
+
+ emit removeCandidateMoves(selectedMoves);
+
+ // TODO make this code work to select the next item
+ QTreeWidgetItem *prevLastSelection = m_moveMap.value(selectedMoves.back());
+ QTreeWidgetItem *nextSelection = 0;
+ const int numItems = m_treeWidget->topLevelItemCount();
+ for (int i = 0; i < numItems; ++i)
+ {
+ if (m_treeWidget->topLevelItem(i) == prevLastSelection)
+ {
+ if (i != numItems - 1)
+ {
+ nextSelection = m_treeWidget->topLevelItem(i + 1);
+ break;
+ }
+ }
+ }
+
+ if (nextSelection)
+ {
+ for (QMap<Quackle::Move, QTreeWidgetItem *>::iterator mapIt = m_moveMap.begin(); mapIt != m_moveMap.end(); ++mapIt)
+ {
+ if (mapIt.value() == nextSelection)
+ {
+ emit setCandidateMove(mapIt.key());
+ break;
+ }
+ }
+ }
+}
+
+// This is complex as it tries to do as little as possible when
+// the move list hasn't changed and is sorted the same way,
+// so simulations can go as fast as possible.
+// Nevertheless, TODO clean this up
+void MoveBox::setMoves(const Quackle::MoveList &moves, const Quackle::Move &selectedMove)
+{
+ bool resorted = false;
+ if (m_previousMoves.size() == moves.size())
+ {
+ Quackle::MoveList::const_iterator prevIt = m_previousMoves.begin();
+ const Quackle::MoveList::const_iterator end = moves.end();
+ for (Quackle::MoveList::const_iterator it = moves.begin(); it != end; ++it, ++prevIt)
+ {
+ if (!(*prevIt == *it))
+ {
+ resorted = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ resorted = true;
+ }
+
+ bool hasOldItems = false;
+ bool hasNewItems = false;
+
+ Quackle::MoveList::const_iterator end(moves.end());
+ for (Quackle::MoveList::const_iterator it = moves.begin(); it != end; ++it)
+ {
+ QMap<Quackle::Move, QTreeWidgetItem *>::const_iterator mapEnd(m_moveMap.end());
+ for (QMap<Quackle::Move, QTreeWidgetItem *>::const_iterator mapIt = m_moveMap.begin(); mapIt != mapEnd; ++mapIt)
+ {
+ if (mapIt.key() == *it)
+ {
+ mapIt.value()->setText(WinPercentageColumn, formatWinPercentage((*it).win));
+ mapIt.value()->setText(EquityColumn, formatValuation((*it).equity));
+
+ if (resorted)
+ {
+ m_treeWidget->addTopLevelItem(m_treeWidget->takeTopLevelItem(m_treeWidget->indexOfTopLevelItem(mapIt.value())));
+ }
+
+ hasOldItems = true;
+ goto foundFirstPass;
+ }
+ }
+
+ hasNewItems = true;
+ m_moveMap.insert(*it, createItem(*it));
+
+ foundFirstPass:
+ continue;
+ }
+
+ if (resorted)
+ {
+ for (QMutableMapIterator<Quackle::Move, QTreeWidgetItem *> mapIt(m_moveMap); mapIt.hasNext(); )
+ {
+ mapIt.next();
+
+ for (Quackle::MoveList::const_iterator it = moves.begin(); it != end; ++it)
+ if (mapIt.key() == *it)
+ goto found;
+
+ delete mapIt.value();
+ mapIt.remove();
+
+ found:
+ continue;
+ }
+ }
+
+ if (moves.contains(selectedMove) && m_moveMap.contains(selectedMove))
+ {
+ m_treeWidget->setCurrentItem(m_moveMap.value(selectedMove));
+ }
+
+ selectionChanged();
+
+ if (hasNewItems)
+ QTimer::singleShot(0, this, SLOT(checkGeometry()));
+
+ m_previousMoves = moves;
+ m_previousSelection = selectedMove;
+}
+
+void MoveBox::checkGeometry()
+{
+ m_treeWidget->resizeColumnToContents(EquityColumn);
+ m_treeWidget->resizeColumnToContents(WinPercentageColumn);
+ m_treeWidget->resizeColumnToContents(LeaveColumn);
+ m_treeWidget->resizeColumnToContents(ScoreColumn);
+ m_treeWidget->resizeColumnToContents(PlayColumn);
+}
+
+void MoveBox::positionChanged(const Quackle::GamePosition &position)
+{
+ if (m_rack.tiles() != position.currentPlayer().rack().tiles())
+ {
+ for (QMap<Quackle::Move, QTreeWidgetItem *>::iterator mapIt = m_moveMap.begin(); mapIt != m_moveMap.end(); ++mapIt)
+ delete mapIt.value();
+
+ m_moveMap.clear();
+ }
+
+ m_rack = position.currentPlayer().rack();
+ setMoves(position.moves(), position.moveMade());
+}
+
+void MoveBox::movesChanged(const Quackle::MoveList &moves)
+{
+ setMoves(moves, m_previousSelection);
+}
+
+QTreeWidgetItem *MoveBox::createItem(const Quackle::Move &move)
+{
+ QTreeWidgetItem *item = new QTreeWidgetItem(m_treeWidget);
+ item->setText(PlayColumn, QuackleIO::Util::moveToDetailedString(move));
+ item->setText(ScoreColumn, QString::number(move.effectiveScore()));
+ item->setText(LeaveColumn, QuackleIO::Util::letterStringToQString(QuackleIO::Util::arrangeLettersForUser(m_rack - move)));
+ item->setText(WinPercentageColumn, formatWinPercentage(move.win));
+ item->setText(EquityColumn, formatValuation(move.equity));
+
+ return item;
+}
+
+QString MoveBox::formatValuation(double valuation)
+{
+ return QString::number(valuation, 'f', 1);
+}
+
+QString MoveBox::formatWinPercentage(double winPercentage)
+{
+ return QString::number(winPercentage * 100.0, 'f', 2);
+}
+
+void MoveBox::setSelectionWatchingEnabled(bool enabled)
+{
+ if (enabled)
+ {
+ connect(m_treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
+
+ // the former is single-click to select on all platforms,
+ // latter is always double-click to select on most platforms
+ connect(m_treeWidget, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(moveActivated(QTreeWidgetItem *)));
+ //connect(m_treeWidget, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(moveActivated(QTreeWidgetItem *)));
+
+ // to allow the arrow keys to be used to select moves
+ // connect(m_treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(moveActivated(QTreeWidgetItem *)));
+ }
+ else
+ {
+ disconnect(m_treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
+ disconnect(m_treeWidget, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(moveActivated(QTreeWidgetItem *)));
+ // disconnect(m_treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(moveActivated(QTreeWidgetItem *)));
+ }
+}
+
diff --git a/quacker/movebox.h b/quacker/movebox.h
new file mode 100644
index 0000000..ad620ad
--- /dev/null
+++ b/quacker/movebox.h
@@ -0,0 +1,74 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_MOVEBOX_H
+#define QUACKER_MOVEBOX_H
+
+#include <QMap>
+
+#include <move.h>
+#include <rack.h>
+
+#include "view.h"
+
+class QPushButton;
+class QTreeWidget;
+class QTreeWidgetItem;
+
+class MoveBox : public View
+{
+Q_OBJECT
+
+public:
+ MoveBox(QWidget *parent = 0);
+
+ // Sets the moves shown in the movebox and the current (selected) move
+ void setMoves(const Quackle::MoveList &moves, const Quackle::Move &selectedMove);
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition &position);
+ virtual void movesChanged(const Quackle::MoveList &moves);
+
+private slots:
+ void moveActivated(QTreeWidgetItem *item);
+ void selectionChanged();
+ void removeMove();
+ void checkGeometry();
+
+protected:
+ QTreeWidgetItem *createItem(const Quackle::Move &move);
+
+ QString formatWinPercentage(double winPercentage);
+ QString formatValuation(double valuation);
+
+ void setSelectionWatchingEnabled(bool enabled);
+
+ QMap<Quackle::Move, QTreeWidgetItem *> m_moveMap;
+ Quackle::MoveList m_previousMoves;
+ Quackle::Move m_previousSelection;
+ Quackle::Rack m_rack;
+
+ enum Columns { PlayColumn = 0, ScoreColumn = 1, LeaveColumn = 2, WinPercentageColumn = 3, EquityColumn = 4 };
+ QTreeWidget *m_treeWidget;
+ QPushButton *m_removeButton;
+ QPushButton *m_commitButton;
+};
+
+#endif
diff --git a/quacker/newgame.cpp b/quacker/newgame.cpp
new file mode 100644
index 0000000..97ef15d
--- /dev/null
+++ b/quacker/newgame.cpp
@@ -0,0 +1,425 @@
+/*
+ * 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 <iostream>
+
+#include <QtGui>
+
+#include <computerplayer.h>
+#include <datamanager.h>
+#include <quackleio/util.h>
+
+#include "newgame.h"
+#include "customqsettings.h"
+
+NewGameDialog::NewGameDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ m_tabs = new QTabWidget;
+
+ m_playerTab = new PlayerTab;
+ m_tabs->addTab(m_playerTab, tr("&Players"));
+
+ QPushButton *okButton = new QPushButton(tr("&OK"));
+ QPushButton *cancelButton = new QPushButton(tr("&Cancel"));
+
+ connect(okButton, SIGNAL(clicked()), this, SLOT(done()));
+ connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
+
+ QHBoxLayout *buttonLayout = new QHBoxLayout;
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(okButton);
+ buttonLayout->addWidget(cancelButton);
+
+ QVBoxLayout *topLayout = new QVBoxLayout;
+ topLayout->addWidget(m_tabs);
+ topLayout->addLayout(buttonLayout);
+ setLayout(topLayout);
+
+ loadSettings();
+
+ setWindowTitle(tr("New Game - Quackle"));
+ okButton->setDefault(true);
+}
+
+Quackle::PlayerList NewGameDialog::players() const
+{
+ return m_playerTab->players();
+}
+
+void NewGameDialog::done()
+{
+ saveSettings();
+ accept();
+}
+
+void NewGameDialog::saveSettings()
+{
+ m_playerTab->saveSettings();
+}
+
+void NewGameDialog::loadSettings()
+{
+}
+
+/////////////
+
+PlayerTab::PlayerTab(QWidget *parent)
+ : QWidget(parent), m_changingEditorManually(false)
+{
+ m_addPlayerButton = new QPushButton(tr("&Add New Player"));
+ m_removePlayerButton = new QPushButton(tr("&Remove Player"));
+
+ connect(m_addPlayerButton, SIGNAL(clicked()), this, SLOT(addPlayer()));
+ connect(m_removePlayerButton, SIGNAL(clicked()), this, SLOT(removePlayer()));
+
+ QHBoxLayout *buttonLayout = new QHBoxLayout;
+ buttonLayout->addWidget(m_addPlayerButton);
+ buttonLayout->addWidget(m_removePlayerButton);
+
+ m_playersTreeWidget = new QTreeWidget;
+
+ // probably too confusing to be able to select multiple players at oncet?
+ // all functionality for it is implemented wrt removing multiple players at oncet
+ //m_playersTreeWidget->setSelectionMode(QTreeWidget::ExtendedSelection);
+
+ connect(m_playersTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
+ QStringList headers;
+ headers << tr("Name") << tr("Controller");
+ m_playersTreeWidget->setHeaderLabels(headers);
+
+ m_editGroup = new QGroupBox(tr("Player &Information"));
+ QHBoxLayout *editLayout = new QHBoxLayout(m_editGroup);
+
+ m_nameEdit = new QLineEdit;
+ connect(m_nameEdit, SIGNAL(textEdited(const QString &)), this, SLOT(playerEdited()));
+ editLayout->addWidget(m_nameEdit);
+
+ m_playerType = new QComboBox;
+
+ QStringList playerTypes(tr("Human"));
+ playerTypes.push_back(tr("Human With Unknown Racks"));
+ for (Quackle::PlayerList::const_reverse_iterator it = QUACKLE_COMPUTER_PLAYERS.rbegin(); it != QUACKLE_COMPUTER_PLAYERS.rend(); ++it)
+ {
+ if ((*it).computerPlayer()->isUserVisible())
+ playerTypes.push_back(QuackleIO::Util::uvStringToQString((*it).name()));
+ }
+ m_playerType->addItems(playerTypes);
+
+ connect(m_playerType, SIGNAL(activated(const QString &)), this, SLOT(playerEdited()));
+ editLayout->addWidget(m_playerType);
+
+ QVBoxLayout *topLayout = new QVBoxLayout;
+ topLayout->addWidget(m_playersTreeWidget);
+ topLayout->addWidget(m_editGroup);
+ topLayout->addStretch();
+ topLayout->addLayout(buttonLayout);
+
+ setLayout(topLayout);
+
+ QTimer::singleShot(0, this, SLOT(populatePlayers()));
+}
+
+void PlayerTab::saveSettings()
+{
+ CustomQSettings settings;
+
+ QList<QVariant> playerIds;
+
+ QList<Quackle::Player> playerList(m_playerMap.keys());
+
+ for (QList<Quackle::Player>::iterator it = playerList.begin(); it != playerList.end(); ++it)
+ {
+ playerIds.push_back((*it).id());
+ settings.setValue(QString("quackle/newgame/players/%1").arg((*it).id()), QuackleIO::Util::uvStringToQString((*it).storeInformationToString()));
+ }
+
+ settings.setValue("quackle/newgame/playerIds", playerIds);
+
+ // this is a workaround for one item lists getting saved oddly
+ if (playerIds.size() == 1)
+ settings.setValue("quackle/newgame/playerId", playerIds.front().toInt());
+ else
+ settings.setValue("quackle/newgame/playerId", -1);
+}
+
+void PlayerTab::populatePlayers()
+{
+ CustomQSettings settings;
+
+ // this is a workaround for one item lists getting saved oddly
+ int playerId = settings.value("quackle/newgame/playerId", -1).toInt();
+
+ QList<QVariant> playerIds;
+ if (playerId >= 0)
+ playerIds.push_back(QVariant(playerId));
+ else
+ playerIds = settings.value("quackle/newgame/playerIds", QList<QVariant>()).toList();
+
+ for (QList<QVariant>::iterator it = playerIds.begin(); it != playerIds.end(); ++it)
+ {
+ int id = (*it).toInt();
+
+ QString infoString = settings.value(QString("quackle/newgame/players/%1").arg(id)).toString();
+ if (infoString.isNull())
+ continue;
+
+ Quackle::Player player(Quackle::Player::makePlayerFromString(QuackleIO::Util::qstringToString(infoString)));
+ if (!player.computerPlayer())
+ {
+ // this is required if the computer id in the settings is no longer
+ // offered by Quackle
+ player.setComputerPlayer(defaultComputerPlayer());
+ }
+ addPlayer(player);
+ }
+
+ if (m_playerMap.empty())
+ {
+ // one computer
+ Quackle::Player newPlayer(QuackleIO::Util::qstringToString(tr("Quackle")), Quackle::Player::ComputerPlayerType, 0);
+ newPlayer.setComputerPlayer(defaultComputerPlayer());
+ addPlayer(newPlayer);
+
+ // and a human
+ addPlayer();
+ }
+
+ selectionChanged();
+}
+
+Quackle::PlayerList PlayerTab::players() const
+{
+ // this is all silly to do but oh well
+ QList<Quackle::Player> playerList(m_playerMap.keys());
+ Quackle::PlayerList ret;
+
+ for (QList<Quackle::Player>::const_iterator it = playerList.begin(); it != playerList.end(); ++it)
+ {
+ ret.push_back(*it);
+
+ if (ret.back().name().empty())
+ ret.back().setName(QuackleIO::Util::qstringToString(tr("No Name")));
+
+ QStringList splitName = QuackleIO::Util::uvStringToQString(ret.back().name()).split(QRegExp("\\s+"));
+ ret.back().setAbbreviatedName(QuackleIO::Util::qstringToString(splitName.join("_")));
+ }
+
+ return ret;
+}
+
+void PlayerTab::addPlayer(const Quackle::Player &player)
+{
+ QTreeWidgetItem *item = new QTreeWidgetItem(m_playersTreeWidget);
+ m_playerMap.insert(player, item);
+ setItem(item, player);
+
+ m_playersTreeWidget->clearSelection();
+ m_playersTreeWidget->setItemSelected(item, true);
+}
+
+Quackle::ComputerPlayer *PlayerTab::defaultComputerPlayer() const
+{
+ for (Quackle::PlayerList::const_reverse_iterator it = QUACKLE_COMPUTER_PLAYERS.rbegin(); it != QUACKLE_COMPUTER_PLAYERS.rend(); ++it)
+ if ((*it).computerPlayer()->isUserVisible() && !(*it).computerPlayer()->isSlow())
+ return (*it).computerPlayer();
+
+ return QUACKLE_COMPUTER_PLAYERS.back().computerPlayer();
+}
+
+void PlayerTab::setItem(QTreeWidgetItem *item, const Quackle::Player &player)
+{
+ item->setText(PlayerName, QuackleIO::Util::uvStringToQString(player.name()));
+ item->setText(PlayerType, stringForPlayer(player));
+}
+
+void PlayerTab::addPlayer()
+{
+ QString name(tr("New Player %1"));
+ QString unusedName;
+ int unusedId;
+
+ for (int i = 1; ; ++i)
+ {
+ unusedName = name.arg(i);
+ UVString nameString = QuackleIO::Util::qstringToString(unusedName);
+
+ bool found = false;
+ for (QMap<Quackle::Player, QTreeWidgetItem *>::iterator it = m_playerMap.begin(); it != m_playerMap.end(); ++it)
+ {
+ if (it.key().name() == nameString)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ break;
+ }
+
+ for (unusedId = 0; ; ++unusedId)
+ {
+ bool found = false;
+ for (QMap<Quackle::Player, QTreeWidgetItem *>::iterator it = m_playerMap.begin(); it != m_playerMap.end(); ++it)
+ {
+ if (it.key().id() == unusedId)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ break;
+ }
+
+ Quackle::Player player(QuackleIO::Util::qstringToString(unusedName), Quackle::Player::HumanPlayerType, unusedId);
+ addPlayer(player);
+}
+
+void PlayerTab::removePlayer()
+{
+ QList<QTreeWidgetItem *> items = m_playersTreeWidget->selectedItems();
+ for (QList<QTreeWidgetItem *>::iterator it = items.begin(); it != items.end(); ++it)
+ {
+ delete *it;
+
+ QList<Quackle::Player> correspondingPlayers(m_playerMap.keys(*it));
+
+ if (correspondingPlayers.size() > 0)
+ m_playerMap.remove(correspondingPlayers.front());
+ }
+
+ selectionChanged();
+}
+
+void PlayerTab::playerEdited()
+{
+ if (m_changingEditorManually || !hasSelection())
+ return;
+
+ Quackle::Player lastPlayer(getLastPlayer());
+ if (lastPlayer.id() < 0)
+ return;
+
+ lastPlayer.setName(QuackleIO::Util::qstringToString(m_nameEdit->text()));
+ updatePlayerFromString(m_playerType->currentText(), lastPlayer);
+
+ // please excuse the HORRIBLE use of QMap here
+ QTreeWidgetItem *item = m_playerMap.value(lastPlayer);
+ m_playerMap.remove(lastPlayer);
+ m_playerMap.insert(lastPlayer, item);
+
+ setItem(item, lastPlayer);
+}
+
+void PlayerTab::selectionChanged()
+{
+ bool hasSel = hasSelection();
+ m_removePlayerButton->setEnabled(hasSel);
+ m_editGroup->setEnabled(hasSel);
+
+ if (!hasSel)
+ return;
+
+ Quackle::Player lastPlayer(getLastPlayer());
+
+ if (lastPlayer.id() < 0)
+ {
+ UVcout << "last player id < 0" << endl;
+ return;
+ }
+
+ m_changingEditorManually = true;
+
+ m_nameEdit->setText(QuackleIO::Util::uvStringToQString(lastPlayer.name()));
+ m_playerType->setCurrentIndex(m_playerType->findText(stringForPlayer(lastPlayer)));
+
+ m_changingEditorManually = false;
+}
+
+bool PlayerTab::hasSelection()
+{
+ return !m_playersTreeWidget->selectedItems().empty();
+}
+
+Quackle::Player PlayerTab::getLastPlayer()
+{
+ QTreeWidgetItem *lastItem = m_playersTreeWidget->selectedItems().back();
+ Quackle::Player lastPlayer;
+ for (QMap<Quackle::Player, QTreeWidgetItem *>::iterator it = m_playerMap.begin(); it != m_playerMap.end(); ++it)
+ if (it.value() == lastItem)
+ return it.key();
+
+ // urp!
+ return Quackle::Player();
+}
+
+QString PlayerTab::stringForPlayer(const Quackle::Player &player)
+{
+ switch (player.type())
+ {
+ case Quackle::Player::HumanPlayerType:
+ {
+ if (player.racksAreKnown())
+ return tr("Human");
+ else
+ return tr("Human With Unknown Racks");
+ }
+
+ case Quackle::Player::ComputerPlayerType:
+ if (player.computerPlayer())
+ return QuackleIO::Util::uvStringToQString(player.computerPlayer()->name());
+
+ default:
+ return tr("Unknown Player");
+ }
+}
+
+void PlayerTab::updatePlayerFromString(const QString &typeString, Quackle::Player &player)
+{
+ if (typeString == tr("Human"))
+ {
+ player.setType(Quackle::Player::HumanPlayerType);
+ player.setRacksAreKnown(true);
+ player.setComputerPlayer(0);
+ return;
+ }
+
+ if (typeString == tr("Human With Unknown Racks"))
+ {
+ player.setType(Quackle::Player::HumanPlayerType);
+ player.setRacksAreKnown(false);
+ player.setComputerPlayer(0);
+ return;
+ }
+
+ player.setType(Quackle::Player::ComputerPlayerType);
+
+ bool found = false;
+ const Quackle::Player &modelPlayer = QUACKLE_COMPUTER_PLAYERS.playerForName(QuackleIO::Util::qstringToString(typeString), found);
+
+ if (found)
+ player.setComputerPlayer(modelPlayer.computerPlayer());
+ else
+ player.setComputerPlayer(0);
+}
+
diff --git a/quacker/newgame.h b/quacker/newgame.h
new file mode 100644
index 0000000..9b3be5f
--- /dev/null
+++ b/quacker/newgame.h
@@ -0,0 +1,112 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_NEWGAME_H
+#define QUACKER_NEWGAME_H
+
+#include <QDialog>
+#include <QMap>
+
+#include <player.h>
+#include <playerlist.h>
+
+class QComboBox;
+class QGroupBox;
+class QLineEdit;
+class QTabWidget;
+class QTreeWidget;
+class QTreeWidgetItem;
+class QPushButton;
+class QSettings;
+class PlayerTab;
+
+namespace Quackle
+{
+ class ComputerPlayer;
+}
+
+class NewGameDialog : public QDialog
+{
+Q_OBJECT
+
+public:
+ NewGameDialog(QWidget *parent = 0);
+
+ Quackle::PlayerList players() const;
+
+public slots:
+ void done();
+
+protected:
+ void saveSettings();
+ void loadSettings();
+
+private:
+ QTabWidget *m_tabs;
+ PlayerTab *m_playerTab;
+};
+
+class PlayerTab : public QWidget
+{
+Q_OBJECT
+
+public:
+ PlayerTab(QWidget *parent = 0);
+
+ Quackle::PlayerList players() const;
+
+public slots:
+ void saveSettings();
+
+private slots:
+ void addPlayer();
+ void removePlayer();
+ void selectionChanged();
+ void playerEdited();
+ void populatePlayers();
+
+private:
+ bool hasSelection();
+
+ void addPlayer(const Quackle::Player &player);
+ void setItem(QTreeWidgetItem *item, const Quackle::Player &player);
+
+ QString stringForPlayer(const Quackle::Player &player);
+ void updatePlayerFromString(const QString &typeString, Quackle::Player &player);
+
+ Quackle::ComputerPlayer *defaultComputerPlayer() const;
+
+ enum PlayerTreeRows { PlayerName = 0, PlayerType = 1 };
+
+ Quackle::Player getLastPlayer();
+
+ QTreeWidget *m_playersTreeWidget;
+ QPushButton *m_addPlayerButton;
+ QPushButton *m_removePlayerButton;
+
+ QGroupBox *m_editGroup;
+ QLineEdit *m_nameEdit;
+ QComboBox *m_playerType;
+ bool m_changingEditorManually;
+
+ QMap<Quackle::Player, QTreeWidgetItem *> m_playerMap;
+};
+
+#endif
diff --git a/quacker/noteeditor.cpp b/quacker/noteeditor.cpp
new file mode 100644
index 0000000..e737d0f
--- /dev/null
+++ b/quacker/noteeditor.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 <QtGui>
+
+#include <game.h>
+#include <quackleio/util.h>
+
+#include "geometry.h"
+#include "noteeditor.h"
+
+NoteEditor::NoteEditor(QWidget *parent)
+ : View(parent)
+{
+ QVBoxLayout *layout = new QVBoxLayout(this);
+ Geometry::setupInnerLayout(layout);
+
+ m_textEdit = new QTextEdit;
+ m_textEdit->setMinimumSize(60, 20);
+ layout->addWidget(m_textEdit);
+
+ showNote(UVString());
+
+ connect(m_textEdit, SIGNAL(textChanged()), this, SLOT(noteEdited()));
+}
+
+NoteEditor::~NoteEditor()
+{
+}
+
+QSize NoteEditor::sizeHint() const
+{
+ //return QSize(20, 20);
+ QSize hint = QFrame::sizeHint();
+ return QSize(hint.width(), hint.height() / 2);
+}
+
+void NoteEditor::positionChanged(const Quackle::GamePosition &position)
+{
+ showNote(position.explanatoryNote());
+}
+
+void NoteEditor::showNote(const UVString &note)
+{
+ if (note.empty())
+ {
+ manuallySetNoteText(tr("Type a note here!"));
+ return;
+ }
+
+ const QString qstringNote = QuackleIO::Util::uvStringToQString(note);
+
+ manuallySetNoteText(qstringNote);
+}
+
+void NoteEditor::manuallySetNoteText(const QString &note)
+{
+ disconnect(m_textEdit, SIGNAL(textChanged()), this, SLOT(noteEdited()));
+ m_textEdit->setPlainText(note);
+ connect(m_textEdit, SIGNAL(textChanged()), this, SLOT(noteEdited()));
+}
+
+void NoteEditor::noteEdited()
+{
+ emit setNote(QuackleIO::Util::qstringToString(m_textEdit->toPlainText()));
+}
diff --git a/quacker/noteeditor.h b/quacker/noteeditor.h
new file mode 100644
index 0000000..e10d14d
--- /dev/null
+++ b/quacker/noteeditor.h
@@ -0,0 +1,51 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_NOTEEDITOR_H
+#define QUACKER_NOTEEDITOR_H
+
+#include <alphabetparameters.h>
+#include "view.h"
+
+class QTextEdit;
+
+class NoteEditor : public View
+{
+Q_OBJECT
+
+public:
+ NoteEditor(QWidget *parent = 0);
+ virtual ~NoteEditor();
+
+ virtual QSize sizeHint() const;
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition &position);
+
+protected slots:
+ virtual void showNote(const UVString &note);
+ virtual void noteEdited();
+ void manuallySetNoteText(const QString &note);
+
+private:
+ QTextEdit *m_textEdit;
+};
+
+#endif
diff --git a/quacker/oppothread.cpp b/quacker/oppothread.cpp
new file mode 100644
index 0000000..6e61ac5
--- /dev/null
+++ b/quacker/oppothread.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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 <QtGui>
+
+#include <computerplayer.h>
+#include <uv.h>
+
+#include "oppothread.h"
+
+QuackerDispatch::QuackerDispatch(QObject *parent)
+ : QObject(parent), m_shouldAbort(false)
+{
+}
+
+QuackerDispatch::~QuackerDispatch()
+{
+}
+
+void QuackerDispatch::signalFractionDone(double fraction)
+{
+ emit fractionDone(fraction);
+}
+
+OppoThread::OppoThread(QObject *parent)
+ : QThread(parent), m_player(0)
+{
+ m_dispatch = new QuackerDispatch(this);
+ connect(m_dispatch, SIGNAL(fractionDone(double)), this, SLOT(signalFractionDone(double)));
+}
+
+OppoThread::~OppoThread()
+{
+ m_dispatch->setShouldAbort(true);
+
+ wait();
+}
+
+void OppoThread::run()
+{
+ if (!m_player)
+ {
+ UVcout << "No computer player for oppo thread to use in position!" << endl;
+ return;
+ }
+
+ m_player->setPosition(m_position);
+ m_player->setDispatch(m_dispatch);
+ m_moves = m_player->moves(m_nmoves);
+}
+
+void OppoThread::signalFractionDone(double fraction)
+{
+ emit fractionDone(fraction, this);
+}
+
+void OppoThread::setPosition(const Quackle::GamePosition &position)
+{
+ if (isRunning())
+ return;
+
+ m_position = position;
+}
+
+void OppoThread::setPlayer(Quackle::ComputerPlayer *player)
+{
+ if (isRunning())
+ return;
+
+ m_player = player;
+}
+
+void OppoThread::findBestMoves(int nmoves)
+{
+ if (isRunning())
+ {
+ UVcout << "OppoThread is already running!" << endl;
+ return;
+ }
+
+ m_dispatch->setShouldAbort(false);
+ m_nmoves = nmoves;
+ start(HighPriority);
+}
+
+const Quackle::MoveList &OppoThread::moves() const
+{
+ return m_moves;
+}
+
+void OppoThread::abort()
+{
+ m_dispatch->setShouldAbort(true);
+}
+
diff --git a/quacker/oppothread.h b/quacker/oppothread.h
new file mode 100644
index 0000000..9296d87
--- /dev/null
+++ b/quacker/oppothread.h
@@ -0,0 +1,101 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_OPPOTHREAD_H
+#define QUACKER_OPPOTHREAD_H
+
+#include <QMutex>
+#include <QThread>
+
+#include <computerplayer.h>
+#include <game.h>
+
+class QuackerDispatch : public QObject, public Quackle::ComputerDispatch
+{
+Q_OBJECT
+
+public:
+ QuackerDispatch(QObject *parent = 0);
+ virtual ~QuackerDispatch();
+
+ virtual bool shouldAbort() { return m_shouldAbort; }
+ virtual void setShouldAbort(bool shouldAbort) { m_shouldAbort = shouldAbort; }
+
+ virtual void signalFractionDone(double fraction);
+
+signals:
+ void fractionDone(double fractionDone);
+
+private:
+ bool m_shouldAbort;
+};
+
+class OppoThread : public QThread
+{
+Q_OBJECT
+
+public:
+ OppoThread(QObject *parent = 0);
+ ~OppoThread();
+
+ void setPosition(const Quackle::GamePosition &position);
+ const Quackle::GamePosition &position() const;
+
+ void setPlayer(Quackle::ComputerPlayer *player);
+ Quackle::ComputerPlayer *player() const;
+
+ // starts the thread
+ void findBestMoves(int nmoves);
+
+ void abort();
+
+ const Quackle::MoveList &moves() const;
+
+protected:
+ void run();
+
+signals:
+ void fractionDone(double fraction, OppoThread *thread);
+
+private slots:
+ void signalFractionDone(double fractionDone);
+
+private:
+ Quackle::GamePosition m_position;
+ Quackle::MoveList m_moves;
+ int m_nmoves;
+ Quackle::ComputerPlayer *m_player;
+
+ QuackerDispatch *m_dispatch;
+
+ QMutex m_mutex;
+};
+
+inline const Quackle::GamePosition &OppoThread::position() const
+{
+ return m_position;
+}
+
+inline Quackle::ComputerPlayer *OppoThread::player() const
+{
+ return m_player;
+}
+
+#endif
diff --git a/quacker/oppothreadprogressbar.cpp b/quacker/oppothreadprogressbar.cpp
new file mode 100644
index 0000000..52fa4c8
--- /dev/null
+++ b/quacker/oppothreadprogressbar.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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 <QtGui>
+
+#include "geometry.h"
+#include "oppothreadprogressbar.h"
+#include "oppothread.h"
+
+OppoThreadProgressBar::OppoThreadProgressBar(OppoThread *thread)
+ : m_thread(thread)
+{
+ QHBoxLayout *layout = new QHBoxLayout(this);
+ Geometry::setupInnerLayout(layout);
+
+ m_progressBar = new QProgressBar;
+ m_progressBar->setRange(0, 100);
+ layout->addWidget(m_progressBar);
+
+ m_cancelButton = new QPushButton(tr("Finish Now"));
+ connect(m_cancelButton, SIGNAL(clicked()), this, SLOT(cancel()));
+ layout->addWidget(m_cancelButton);
+}
+
+void OppoThreadProgressBar::setValue(int value)
+{
+ m_progressBar->setValue(value);
+}
+
+void OppoThreadProgressBar::cancel()
+{
+ m_thread->abort();
+}
+
diff --git a/quacker/oppothreadprogressbar.h b/quacker/oppothreadprogressbar.h
new file mode 100644
index 0000000..ccd6797
--- /dev/null
+++ b/quacker/oppothreadprogressbar.h
@@ -0,0 +1,50 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_OPPOTHREADPROGRESSBAR_H
+#define QUACKER_OPPOTHREADPROGRESSBAR_H
+
+#include <QWidget>
+
+class OppoThread;
+
+class QProgressBar;
+class QPushButton;
+
+class OppoThreadProgressBar : public QWidget
+{
+Q_OBJECT
+
+public:
+ OppoThreadProgressBar(OppoThread *thread);
+
+ // sets value out of 100
+ void setValue(int value);
+
+protected slots:
+ void cancel();
+
+protected:
+ OppoThread *m_thread;
+ QProgressBar *m_progressBar;
+ QPushButton *m_cancelButton;
+};
+
+#endif
diff --git a/quacker/quacker.cpp b/quacker/quacker.cpp
new file mode 100644
index 0000000..20b0515
--- /dev/null
+++ b/quacker/quacker.cpp
@@ -0,0 +1,2319 @@
+/*
+ * 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 <algorithm>
+#include <iostream>
+
+using namespace std;
+
+#include <QtGui>
+
+#include <game.h>
+#include <boardparameters.h>
+#include <computerplayer.h>
+#include <gameparameters.h>
+
+#include <quackleio/froggetopt.h>
+#include <quackleio/util.h>
+#include <quackleio/logania.h>
+#include <quackleio/queenie.h>
+#include <quackleio/streamingreporter.h>
+
+#include "brb.h"
+#include "configdialog.h"
+#include "customqsettings.h"
+#include "dashboard.h"
+#include "geometry.h"
+#include "graphicalreporter.h"
+#include "history.h"
+#include "lister.h"
+#include "letterbox.h"
+#include "lexiconparameters.h"
+#include "movebox.h"
+#include "noteeditor.h"
+#include "newgame.h"
+#include "oppothreadprogressbar.h"
+#include "quacker.h"
+#include "quackersettings.h"
+#include "settings.h"
+#include "simviewer.h"
+#include "widgetfactory.h"
+#include "view.h"
+
+const int kExtraPlaysToKibitz = 15;
+
+TopLevel::TopLevel(QWidget *parent)
+ : QMainWindow(parent), m_listerDialog(0), m_letterbox(0), m_simViewer(0), m_plies(2), m_logania(0), m_modified(false)
+{
+ QCoreApplication::setOrganizationName("Quackle.org");
+ QCoreApplication::setOrganizationDomain("quackle.org");
+ QCoreApplication::setApplicationName("Quackle");
+
+ qRegisterMetaType<OppoThread*>("OppoThread*");
+
+ m_quackerSettings = new QuackerSettings;
+
+ m_settings = new Settings;
+ m_settings->preInitialize();
+ m_settings->createGUI();
+ connect(m_settings, SIGNAL(refreshViews()), this, SLOT(updateAllViews()));
+
+ m_game = new Quackle::Game;
+ m_simulator = new Quackle::Simulator;
+
+ createMenu();
+ createWidgets();
+
+ loadSettings();
+
+ setCaption(tr("Welcome"));
+ statusMessage(tr("Please wait for Quackle to load its data structures..."));
+
+ QTimer::singleShot(0, this, SLOT(finishInitialization()));
+}
+
+TopLevel::~TopLevel()
+{
+ QuackleIO::Queenie::cleanUp();
+ delete m_game;
+ delete m_simulator;
+ delete m_quackerSettings;
+}
+
+void TopLevel::closeEvent(QCloseEvent *closeEvent)
+{
+ pause(true);
+
+ if (m_letterbox && m_letterbox->isVisible())
+ {
+ if (!m_letterbox->tryToClose())
+ {
+ closeEvent->ignore();
+ return;
+ }
+
+ delete m_letterbox;
+ }
+
+ if (m_modified)
+ {
+ switch (askToSave())
+ {
+ case 0:
+ qApp->processEvents();
+ save();
+
+ // fall through
+
+ case 1:
+ closeEvent->accept();
+ break;
+
+ case 2:
+ closeEvent->ignore();
+ }
+ }
+ else
+ closeEvent->accept();
+
+ saveSettings();
+}
+
+void TopLevel::finishInitialization()
+{
+ statusMessage(tr("Initializing..."));
+ m_settings->initialize();
+ m_settings->load();
+
+ m_timer = new QTimer(this);
+ connect(m_timer, SIGNAL(timeout()), this, SLOT(timeout()));
+ m_simulationTimer = new QTimer(this);
+ connect(m_simulationTimer, SIGNAL(timeout()), this, SLOT(incrementSimulation()));
+
+ // Birthday
+ m_birthdayTimer = new QTimer(this);
+ connect(m_birthdayTimer, SIGNAL(timeout()), this, SLOT(birthdayBash()));
+
+ QTimer::singleShot(0, this, SLOT(introduceToUser()));
+}
+
+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."));
+ }
+
+ parseCommandLineOptions();
+
+ if (!CustomQSettings().contains("quackle/hasBeenRun"))
+ firstTimeRun();
+}
+
+void TopLevel::parseCommandLineOptions()
+{
+ GetOpt opts;
+ QString filename;
+ opts.addOptionalArgument("filename", &filename);
+ if (!opts.parse())
+ return;
+
+ if (!filename.isNull())
+ openFile(filename);
+}
+
+bool TopLevel::isPlayerOnTurnComputer() const
+{
+ return m_game->currentPosition().playerOnTurn().type() == Quackle::Player::ComputerPlayerType;
+}
+
+bool TopLevel::isCommitAllowed() const
+{
+ return !m_game->currentPosition().gameOver() && !(shouldOutcraftyCurrentPlayer() && isPlayerOnTurnComputer());
+}
+
+void TopLevel::commit()
+{
+ if (!m_game->hasPositions())
+ return;
+
+ if (!isCommitAllowed())
+ {
+ statusMessage(tr("%1 is currently on turn so you cannot commit. Please wait.").arg(QuackleIO::Util::uvStringToQString(m_game->currentPosition().playerOnTurn().name())));
+ return;
+ }
+
+ const bool askSillyQuestion = false;
+
+ if (askSillyQuestion)
+ {
+ if (!(m_game->currentPosition().location() == m_game->history().lastLocation()))
+ {
+ int result = QMessageBox::warning(this, tr("Previous Position Commit - Quackle"), dialogText(tr("You've asked to commit a move from a previous position. You have three options: <ol><li>Commit and resume the game from directly after this position. This throws away all later positions.</li><li>Commit and leave later moves the same. This is risky because scores and board position of all later moves might become inconsistent. You'll need to save the game and reopen it to have the scores make sense.</li><li>Cancel this commit. <b>Canceling is recommended.</b></li></ol>Which would you like to do?")), tr("&Commit and resume from after this position"), tr("&Commit and leave later moves the same"), tr("&Cancel"), 0, 2);
+
+ switch (result)
+ {
+ case 0:
+ // Commit and resume after this position.
+ // We can just pass back to the normal commit method.
+ break;
+
+ case 1:
+ // commit silently
+ m_game->currentPosition().prepareForCommit();
+ setModified(true);
+ showToHuman();
+ return;
+
+ case 2:
+ default:
+ // cancel
+ return;
+ }
+ }
+ }
+
+ if (m_game->candidate().isAMove())
+ {
+ stopEverything();
+
+ m_game->commitCandidate();
+ setModified(true);
+
+ advanceGame();
+ }
+ else
+ {
+ statusMessage(tr("No move specified."));
+ }
+}
+
+void TopLevel::pass()
+{
+ Quackle::Move pass(Quackle::Move::createPassMove());
+ setCandidateMove(pass);
+}
+
+void TopLevel::overdraw()
+{
+ bool ok = false;
+ QString letters = QInputDialog::getText(this, tr("Handle overdraw - Quackle"), tr("Please input the letters that sit on the table faceup."), QLineEdit::Normal, QString(), &ok);
+
+ if (!ok)
+ return;
+
+ if (letters.isEmpty())
+ return;
+
+ Quackle::LetterString throwbackLetterString;
+ int validityFlags = m_game->currentPosition().handleOverdraw(QuackleIO::Util::nonBlankEncode(letters), &throwbackLetterString);
+
+ if (validityFlags & Quackle::GamePosition::InvalidOverdrawNumber)
+ {
+ bool tryAgain = QMessageBox::question(this, tr("Verify Overdraw - Quackle"), dialogText(tr("Overdraw %1 does not contain at least %2 tiles, the minimum number of tiles you should turn over. Try again?").arg(letters).arg(QUACKLE_PARAMETERS->overdrawPenalty() + 1)), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes;
+ if (tryAgain)
+ overdraw(); // try again
+ return;
+ }
+
+ if (validityFlags & Quackle::GamePosition::OverdrawnTilesNotUnseen)
+ {
+ bool tryAgain = QMessageBox::question(this, tr("Verify Overdraw - Quackle"), dialogText(tr("Tiles in overdraw %1 are not in the unseen pool. Try again?").arg(letters)), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes;
+ if (tryAgain)
+ overdraw();
+ return;
+ }
+
+ QMessageBox::information(this, tr("Overdraw answer - Quackle"), dialogText(tr("Please put %1 back in the bag.")).arg(QuackleIO::Util::letterStringToQString(throwbackLetterString)));
+}
+
+void TopLevel::statusMessage(const QString &message)
+{
+ statusBar()->showMessage(message);
+}
+
+bool TopLevel::askToCarryOn(const QString &text)
+{
+ return QMessageBox::question(this, tr("Verify Play - Quackle"), dialogText(text), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes;
+}
+
+void TopLevel::setCandidateMove(const Quackle::Move &move)
+{
+ if (!m_game->hasPositions() || (move.action == Quackle::Move::Place && move.tiles().empty()))
+ return;
+
+ Quackle::Move prettiedMove(move);
+ m_game->currentPosition().ensureMoveTilesDoNotIncludePlayThru(prettiedMove);
+ m_game->currentPosition().ensureMovePrettiness(prettiedMove);
+
+ if (m_game->currentPosition().moves().contains(prettiedMove))
+ {
+ m_game->currentPosition().scoreMove(prettiedMove);
+ m_game->setCandidate(prettiedMove);
+ }
+ else
+ {
+ int validityFlags = m_game->currentPosition().validateMove(prettiedMove);
+ bool carryOn = true;
+
+ while (carryOn && validityFlags != Quackle::GamePosition::ValidMove)
+ {
+ if (validityFlags & Quackle::GamePosition::TooLateExchange)
+ {
+ carryOn = askToCarryOn(tr("Bag must contain at least %1 tiles for an exchange.").arg(Quackle::DataManager::self()->parameters()->minimumTilesForExchange()));
+ validityFlags ^= Quackle::GamePosition::TooLateExchange;
+ continue;
+ }
+
+ if (validityFlags & Quackle::GamePosition::InvalidTiles)
+ {
+ if (!m_game->currentPosition().currentPlayer().racksAreKnown())
+ {
+ bool validifiable = validifyMove(prettiedMove);
+ if (!validifiable)
+ {
+ carryOn = askToCarryOn(tr("%1 would need an impossible rack to make play %2; make play anyway?").arg(QuackleIO::Util::uvStringToQString(m_game->currentPosition().currentPlayer().name())).arg(QuackleIO::Util::moveToDetailedString(prettiedMove)));
+ }
+ else
+ {
+ carryOn = true;
+ }
+ }
+ else
+ {
+ carryOn = askToCarryOn(tr("%1's rack does not include all tiles in %2; make play anyway?").arg(QuackleIO::Util::uvStringToQString(m_game->currentPosition().currentPlayer().name())).arg(QuackleIO::Util::moveToDetailedString(prettiedMove)));
+ }
+
+ validityFlags ^= Quackle::GamePosition::InvalidTiles;
+ continue;
+ }
+
+ if (validityFlags & Quackle::GamePosition::InvalidPlace)
+ {
+ carryOn = askToCarryOn(tr("%1 does not connect to other plays on board; make play anyway?").arg(QuackleIO::Util::moveToDetailedString(prettiedMove)));
+ validityFlags ^= Quackle::GamePosition::InvalidPlace;
+ continue;
+ }
+
+ if (validityFlags & Quackle::GamePosition::InvalidOpeningPlace)
+ {
+ carryOn = askToCarryOn(tr("Opening play %1 does not cover the star; make play anyway?").arg(QuackleIO::Util::moveToDetailedString(prettiedMove)));
+ validityFlags ^= Quackle::GamePosition::InvalidOpeningPlace;
+ continue;
+ }
+
+ if (validityFlags & Quackle::GamePosition::UnacceptableWord)
+ {
+ carryOn = askToCarryOn(tr("%1 forms an unacceptable word; make play anyway?").arg(QuackleIO::Util::moveToDetailedString(prettiedMove)));
+
+ if (carryOn)
+ {
+ if (QMessageBox::question(this, tr("Challenge Decision - Quackle"), dialogText(tr("If committed, should %1 be challenged off the board?")).arg(QuackleIO::Util::moveToDetailedString(prettiedMove)), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
+ {
+ prettiedMove.setIsChallengedPhoney(true);
+ }
+ }
+
+ validityFlags ^= Quackle::GamePosition::UnacceptableWord;
+ continue;
+ }
+
+ if (validityFlags & Quackle::GamePosition::InvalidAction)
+ {
+ statusMessage(tr("Urps."));
+ carryOn = false;
+ continue;
+ }
+ }
+
+ if (!carryOn)
+ return;
+
+ m_game->currentPosition().scoreMove(prettiedMove);
+ m_game->currentPosition().addAndSetMoveMade(prettiedMove);
+ switchToTab(ChoicesTabIndex);
+ ensureUpToDateSimulatorMoveList();
+ }
+
+ if (!m_game->currentPosition().currentPlayer().racksAreKnown() && !m_game->currentPosition().currentPlayer().rack().contains(prettiedMove.usedTiles()))
+ {
+ m_game->currentPosition().setCurrentPlayerRack(Quackle::Rack(prettiedMove.usedTiles()));
+ }
+
+ updatePositionViews();
+
+ // this duplicates the job of updateMoveViews as it also does
+ // a check if we have simulation results -- but we don't want to send out
+ // a moves changed signal if we don't have results because
+ // we just sent out a position changed signal
+ if (m_simulator->hasSimulationResults())
+ updateMoveViews();
+}
+
+bool TopLevel::validifyMove(Quackle::Move &move)
+{
+ Quackle::LetterString tiles = move.usedTiles();
+ Quackle::Rack rack = m_game->currentPosition().currentPlayer().rack();
+ if (rack.contains(tiles))
+ return true;
+
+ if (move.action == Quackle::Move::Exchange)
+ {
+ if (tiles.size() > rack.size())
+ return false;
+
+ Quackle::LetterString newTiles;
+ for (Quackle::LetterString::const_iterator it = tiles.begin(); it != tiles.end(); ++it)
+ {
+ Quackle::Letter theLetterLetter = *it;
+ Quackle::LetterString theLetter;
+ theLetter.push_back(theLetterLetter);
+
+ if (!rack.contains(theLetter))
+ {
+ theLetterLetter = Quackle::String::front(rack.tiles());
+
+ theLetter.clear();
+ theLetter.push_back(theLetterLetter);
+ }
+
+ newTiles.push_back(theLetterLetter);
+ rack.unload(theLetter);
+ }
+
+ move.setTiles(newTiles);
+ }
+
+ if (move.action == Quackle::Move::Place)
+ {
+ if (!m_game->currentPosition().canSetCurrentPlayerRackWithoutBagExpansion(Quackle::Rack(tiles)))
+ return false;
+ }
+
+ return true;
+}
+
+void TopLevel::removeCandidateMoves(const Quackle::MoveList &moves)
+{
+ if (!m_game->hasPositions())
+ return;
+
+ const Quackle::MoveList::const_iterator end(moves.end());
+ for (Quackle::MoveList::const_iterator it = moves.begin(); it != end; ++it)
+ m_game->currentPosition().removeMove(*it);
+
+ ensureUpToDateSimulatorMoveList();
+ updateMoveViews();
+}
+
+void TopLevel::setRack(const Quackle::Rack &rack)
+{
+ if (!m_game->hasPositions())
+ return;
+
+ if (rack.empty())
+ return;
+
+ Quackle::Rack rackToSet = rack;
+
+ if (!m_game->currentPosition().canSetPlayerRackWithoutBagExpansion(m_game->currentPosition().currentPlayer().id(), rack))
+ {
+ if (!askToCarryOn(tr("The rack %1 contains letters that are not available without modifying the tile distribution; do you wish to expand the bag to allow this?").arg(QuackleIO::Util::letterStringToQString(rack.tiles()))))
+ {
+ rackToSet = m_game->currentPosition().currentPlayer().rack();
+ }
+ }
+
+ m_game->currentPosition().setPlayerRack(m_game->currentPosition().currentPlayer().id(), rackToSet);
+ m_simulator->currentPosition().setCurrentPlayerRack(rackToSet);
+ updatePositionViews();
+
+ statusMessage(tr("%1's rack set to %2.").arg(QuackleIO::Util::uvStringToQString(m_game->currentPosition().currentPlayer().name())).arg(QuackleIO::Util::letterStringToQString(rackToSet.tiles())));
+ setModified(true);
+}
+
+void TopLevel::setNote(const UVString &note)
+{
+ m_game->currentPosition().setExplanatoryNote(note);
+ setModified(true);
+}
+
+void TopLevel::goToHistoryLocation(const Quackle::HistoryLocation &location)
+{
+ if (!m_game->hasPositions())
+ return;
+
+ // FIXME this shouldn't be necessary once OppoThread::abort() works.
+ if (shouldOutcraftyCurrentPlayer() && isPlayerOnTurnComputer())
+ {
+ statusMessage(tr("Please wait for %1 to play before looking at the history.").arg(QuackleIO::Util::uvStringToQString(m_game->currentPosition().playerOnTurn().name())));
+ return;
+ }
+
+ stopEverything();
+ stopOutcraftyingCurrentPlayer();
+
+ m_game->setCurrentPosition(location);
+ showToHuman();
+}
+
+void TopLevel::stopEverything()
+{
+ // stop simulation if it's going
+ simulate(false);
+
+ for (QList<OppoThread *>::iterator it = m_otherOppoThreads.begin(); it != m_otherOppoThreads.end(); ++it)
+ (*it)->abort();
+ for (QList<OppoThread *>::iterator it = m_oppoThreads.begin(); it != m_oppoThreads.end(); ++it)
+ (*it)->abort();
+}
+
+OppoThreadProgressBar *TopLevel::createProgressBarForThread(OppoThread *thread)
+{
+ OppoThreadProgressBar *bar = new OppoThreadProgressBar(thread);
+ m_progressIndicators[thread] = bar;
+ statusBar()->addPermanentWidget(bar);
+ return bar;
+}
+
+void TopLevel::removeProgressIndicators()
+{
+ QMap<OppoThread *, OppoThreadProgressBar *>::const_iterator it = m_progressIndicators.constBegin();
+ while (it != m_progressIndicators.constEnd()) {
+ statusBar()->removeWidget(it.value());
+ delete it.value();
+ ++it;
+ }
+
+ m_progressIndicators.clear();
+}
+
+OppoThreadProgressBar *TopLevel::progressBarOfThread(OppoThread *thread)
+{
+ if (!m_progressIndicators.contains(thread))
+ return 0;
+
+ return m_progressIndicators[thread];
+}
+
+void TopLevel::removeProgressIndicator(OppoThread *thread)
+{
+ statusBar()->removeWidget(progressBarOfThread(thread));
+ delete progressBarOfThread(thread);
+ m_progressIndicators.remove(thread);
+}
+
+void TopLevel::updateAllViews()
+{
+ if (!m_game->hasPositions())
+ return;
+
+ updateHistoryViews();
+ updatePositionViews();
+ updateSimViews();
+
+ m_game->currentPosition().ensureBoardIsPreparedForAnalysis();
+}
+
+void TopLevel::updatePositionViews()
+{
+ emit positionChanged(m_game->currentPosition());
+
+ m_simulateAction->setEnabled(!m_game->currentPosition().moves().empty());
+ m_simulateDetailsAction->setEnabled(!m_game->currentPosition().moves().empty());
+ m_simulateClearAction->setEnabled(!m_game->currentPosition().moves().empty());
+ m_kibitzAction->setEnabled(!m_game->currentPosition().gameOver());
+ m_kibitzFiftyAction->setEnabled(!m_game->currentPosition().gameOver());
+ m_kibitzAllAction->setEnabled(!m_game->currentPosition().gameOver());
+ m_kibitzAsActions->setEnabled(!m_game->currentPosition().gameOver());
+ m_passAction->setEnabled(!m_game->currentPosition().gameOver());
+ m_overdrawAction->setEnabled(!m_game->currentPosition().gameOver());
+ m_reportAsActions->setEnabled(m_game->hasPositions());
+ m_htmlReportAction->setEnabled(m_game->hasPositions());
+ m_firstPositionAction->setEnabled(m_game->hasPositions());
+
+#ifdef ENABLE_GRAPHICAL_REPORT
+ m_graphicalReportAction->setEnabled(m_game->hasPositions());
+#endif
+
+ const bool hasAMove = m_game->currentPosition().moveMade().isAMove();
+
+ const bool commitAllowed = isCommitAllowed();
+ m_commitAction->setEnabled(commitAllowed && hasAMove);
+ m_commitTopChoiceAction->setEnabled(commitAllowed);
+
+ bool hasNextPosition;
+ m_game->history().nextPosition(&hasNextPosition);
+ m_nextPositionAction->setEnabled(hasNextPosition);
+
+ bool hasPreviousPosition;
+ m_game->history().previousPosition(&hasPreviousPosition);
+ m_previousPositionAction->setEnabled(hasPreviousPosition);
+
+ const QString assistiveText = tr("Click once or twice on the board, type, then press the Enter key.");
+
+ if (commitAllowed && hasAMove)
+ statusMessage(tr("Press a Commit button to play %1.").arg(QuackleIO::Util::moveToDetailedString(m_game->currentPosition().moveMade())));
+ else if (isPlayerOnTurnComputer())
+ statusMessage(tr("%1 to play.").arg(QuackleIO::Util::uvStringToQString(m_game->currentPosition().playerOnTurn().name())));
+ else if (m_game->currentPosition().currentPlayer().drawnLetters().empty())
+ statusMessage(tr("%1 to play. %2").arg(QuackleIO::Util::uvStringToQString(m_game->currentPosition().playerOnTurn().name())).arg(assistiveText));
+ else
+ statusMessage(tr("%1 to play after drawing %2. %3").arg(QuackleIO::Util::uvStringToQString(m_game->currentPosition().playerOnTurn().name())).arg(QuackleIO::Util::letterStringToQString(m_game->currentPosition().playerOnTurn().drawnLetters().tiles())).arg(assistiveText));
+
+ updateListerDialogWithRack();
+}
+
+void TopLevel::updateMoveViews()
+{
+ if (m_simulator->hasSimulationResults())
+ emit movesChanged(m_simulator->moves(/* prune */ true, /* sort by win */ true));
+ else
+ emit movesChanged(m_game->currentPosition().moves());
+
+ m_simulateAction->setEnabled(!m_game->currentPosition().moves().empty());
+ m_simulateDetailsAction->setEnabled(!m_game->currentPosition().moves().empty());
+ m_simulateClearAction->setEnabled(!m_game->currentPosition().moves().empty());
+}
+
+void TopLevel::updateHistoryViews()
+{
+ emit historyChanged(m_game->history());
+}
+
+void TopLevel::initializeGame(const Quackle::PlayerList &players)
+{
+ stopEverything();
+
+ m_game->reset();
+ m_filename = QString();
+ m_logania = 0;
+ setModified(false);
+
+ if (players.empty())
+ return;
+
+ Quackle::PlayerList newPlayers(players);
+
+ // shuffle so same person doesn't go first twice in a row,
+ // if there are multiple players in the game
+ if (newPlayers.size() > 1)
+ {
+ UVString prevFirst = m_firstPlayerName;
+ while (m_firstPlayerName == prevFirst || m_firstPlayerName.empty())
+ {
+ random_shuffle(newPlayers.begin(), newPlayers.end());
+ m_firstPlayerName = newPlayers.front().name();
+ }
+ }
+
+ m_game->setPlayers(newPlayers);
+ m_game->associateKnownComputerPlayers();
+
+ m_game->addPosition();
+
+ advanceGame();
+
+ setCaption(gameTitle());
+
+ const bool debugEndgame = false;
+ if (debugEndgame)
+ {
+ for (int i = 0; i < 20; ++i)
+ m_game->haveComputerPlay();
+ advanceGame();
+ }
+}
+
+void TopLevel::open()
+{
+ pause(true);
+
+ if (m_modified)
+ {
+ switch (askToSave())
+ {
+ case 0:
+ save();
+
+ case 1:
+ break;
+
+ case 2:
+ return;
+ }
+ }
+
+ // QString getOpenFileName ( QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString(), QString * selectedFilter = 0, Options options = 0 )
+ QString defaultFilter = defaultGameFileFilter();
+ QString filename = QFileDialog::getOpenFileName(this, tr("Choose game file to open"), getInitialDirectory(), gameFileFilters(), &defaultFilter);
+
+ if (!filename.isEmpty())
+ openFile(filename);
+}
+
+void TopLevel::openFile(const QString &filename)
+{
+ m_filename = filename;
+ setInitialDirectory(filename);
+ loadFile(m_filename);
+ saveSettings();
+
+ setCaption(gameTitle());
+}
+
+QString TopLevel::getInitialDirectory() const
+{
+ return m_initialDirectory.isEmpty()? QDir::homePath() : m_initialDirectory;
+}
+
+void TopLevel::setInitialDirectory(const QString &filename)
+{
+ QFileInfo file(filename);
+ m_initialDirectory = file.path();
+}
+
+QString TopLevel::gameFileFilters() const
+{
+ return tr("Game files (%1);;All files (*)").arg(QuackleIO::Queenie::self()->filters().join(" "));
+}
+
+QString TopLevel::defaultGameFileFilter() const
+{
+ return QuackleIO::Queenie::self()->filters().join(" ");
+}
+
+void TopLevel::newGame()
+{
+ if (!setupCheck())
+ return;
+
+ pause(true);
+
+ if (m_modified)
+ {
+ switch (askToSave())
+ {
+ case 0:
+ save();
+
+ case 1:
+ break;
+
+ case 2:
+ return;
+ }
+ }
+
+ NewGameDialog newGameDialog(this);
+ switch (newGameDialog.exec())
+ {
+ case QDialog::Accepted:
+ initializeGame(newGameDialog.players());
+ break;
+
+ case QDialog::Rejected:
+ break;
+ }
+
+ // Birthday
+ startBirthday();
+}
+
+void TopLevel::setCaption(const QString &text)
+{
+ if (!text.isNull())
+ m_ourCaption = text;
+
+ setWindowTitle(QString("%1[*] - Quackle").arg(m_ourCaption));
+}
+
+void TopLevel::setModified(bool modified)
+{
+ m_modified = modified;
+ setWindowModified(m_modified);
+}
+
+bool TopLevel::setupCheck()
+{
+ bool okay = QUACKLE_DATAMANAGER->isGood();
+
+ if (!okay)
+ {
+ QMessageBox::warning(this, tr("Not Ready - Quackle"), dialogText(tr("Quackle cannot load its lexicon files.")));
+ }
+
+ return okay;
+}
+
+void TopLevel::plugIntoBaseMatrix(BaseView *view)
+{
+ connect(view, SIGNAL(statusMessage(const QString &)), this, SLOT(statusMessage(const QString &)));
+}
+
+void TopLevel::plugIntoMatrix(View *view)
+{
+ plugIntoBaseMatrix(view);
+
+ connect(view, SIGNAL(setCandidateMove(const Quackle::Move &)), this, SLOT(setCandidateMove(const Quackle::Move &)));
+ connect(view, SIGNAL(removeCandidateMoves(const Quackle::MoveList &)), this, SLOT(removeCandidateMoves(const Quackle::MoveList &)));
+ connect(view, SIGNAL(commit()), this, SLOT(commit()));
+ connect(view, SIGNAL(setRack(const Quackle::Rack &)), this, SLOT(setRack(const Quackle::Rack &)));
+ connect(view, SIGNAL(setNote(const UVString &)), this, SLOT(setNote(const UVString &)));
+}
+
+void TopLevel::plugIntoPositionMatrix(View *view)
+{
+ connect(this, SIGNAL(positionChanged(const Quackle::GamePosition &)), view, SLOT(positionChanged(const Quackle::GamePosition &)));
+}
+
+void TopLevel::plugIntoMoveMatrix(View *view)
+{
+ connect(this, SIGNAL(movesChanged(const Quackle::MoveList &)), view, SLOT(movesChanged(const Quackle::MoveList &)));
+}
+
+void TopLevel::plugIntoHistoryMatrix(HistoryView *view)
+{
+ plugIntoBaseMatrix(view);
+
+ connect(view, SIGNAL(goToHistoryLocation(const Quackle::HistoryLocation &)), this, SLOT(goToHistoryLocation(const Quackle::HistoryLocation &)));
+
+ connect(this, SIGNAL(historyChanged(const Quackle::History &)), view, SLOT(historyChanged(const Quackle::History &)));
+}
+
+int TopLevel::askToSave()
+{
+ return QMessageBox::warning(this, tr("Unsaved Moves - Quackle"), dialogText(tr("There are unsaved moves in the current game. Save them?")), tr("&Save"), tr("&Discard"), tr("&Cancel"), 0, 2);
+}
+
+void TopLevel::generateList()
+{
+ if (!setupCheck())
+ return;
+
+ pause(true);
+
+ if (m_listerDialog)
+ {
+ m_listerDialog->show();
+ m_listerDialog->raise();
+ }
+
+ updateListerDialogWithRack();
+}
+
+void TopLevel::updateListerDialogWithRack()
+{
+ if (m_listerDialog && m_game->hasPositions())
+ m_listerDialog->setQuery(QuackleIO::Util::letterStringToQString(m_game->currentPosition().currentPlayer().rack().tiles()));
+}
+
+void TopLevel::letterbox()
+{
+ if (!m_letterbox)
+ {
+ m_letterbox = new Letterbox(this, m_preferencesAction, m_listerDialog);
+ }
+
+ m_letterbox->show();
+ m_letterbox->raise();
+}
+
+void TopLevel::kibitz()
+{
+ if (!m_game->hasPositions())
+ return;
+
+ const bool confuseUser = false;
+
+ if (confuseUser)
+ {
+ const int currentlyKibitzed = m_game->currentPosition().moves().size();
+ kibitz(currentlyKibitzed < kExtraPlaysToKibitz? kExtraPlaysToKibitz : currentlyKibitzed + kExtraPlaysToKibitz);
+ }
+ else
+ {
+ kibitz(kExtraPlaysToKibitz);
+ }
+}
+
+void TopLevel::kibitz(int numberOfPlays, Quackle::ComputerPlayer *computerPlayer)
+{
+ if (!m_game->hasPositions())
+ return;
+
+ if (computerPlayer)
+ {
+ OppoThread *thread = new OppoThread;
+ m_otherOppoThreads.push_back(thread);
+ connect(thread, SIGNAL(finished()), this, SLOT(kibitzThreadFinished()));
+ connect(thread, SIGNAL(fractionDone(double, OppoThread *)), this, SLOT(playerFractionDone(double, OppoThread *)));
+ thread->setPosition(m_game->currentPosition());
+
+ thread->setPlayer(computerPlayer->clone());
+ thread->findBestMoves(numberOfPlays);
+ statusMessage(tr("Asked %1 for her choices. Please allow her time to think.").arg(QuackleIO::Util::uvStringToQString(computerPlayer->name())));
+ }
+ else
+ {
+ m_game->currentPosition().kibitz(numberOfPlays);
+ kibitzFinished();
+ }
+}
+
+void TopLevel::kibitzThreadFinished()
+{
+ QString name;
+ QString rack;
+ for (QList<OppoThread *>::iterator it = m_otherOppoThreads.begin(); it != m_otherOppoThreads.end(); )
+ {
+ if ((*it)->isFinished())
+ {
+ removeProgressIndicator(*it);
+
+ m_game->currentPosition().setMoves((*it)->moves());
+ name = QuackleIO::Util::uvStringToQString((*it)->player()->name());
+ rack = QuackleIO::Util::letterStringToQString((*it)->position().currentPlayer().rack().tiles());
+
+ // player is a clone
+ delete (*it)->player();
+
+ delete (*it);
+ it = m_otherOppoThreads.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ kibitzFinished();
+
+ statusMessage(tr("Showing %1's choices from %2.").arg(name).arg(rack));
+}
+
+void TopLevel::kibitzFinished()
+{
+ updatePositionViews();
+ ensureUpToDateSimulatorMoveList();
+
+ switchToTab(ChoicesTabIndex);
+}
+
+void TopLevel::kibitzFifty()
+{
+ kibitz(50);
+}
+
+void TopLevel::kibitzAll()
+{
+ kibitz(INT_MAX);
+}
+
+void TopLevel::kibitzAs(Quackle::ComputerPlayer *computerPlayer)
+{
+ kibitz(kExtraPlaysToKibitz, computerPlayer);
+}
+
+void TopLevel::firstPosition()
+{
+ bool exists;
+ const Quackle::GamePosition &firstPozzy = m_game->history().firstPosition(&exists);
+ if (exists)
+ {
+ goToHistoryLocation(firstPozzy.location());
+ }
+}
+
+void TopLevel::nextPosition()
+{
+ bool exists;
+ const Quackle::GamePosition &nextPozzy = m_game->history().nextPosition(&exists);
+ if (exists)
+ {
+ goToHistoryLocation(nextPozzy.location());
+ }
+}
+
+void TopLevel::previousPosition()
+{
+ bool exists;
+ const Quackle::GamePosition &previousPozzy = m_game->history().previousPosition(&exists);
+ if (exists)
+ {
+ goToHistoryLocation(previousPozzy.location());
+ }
+}
+
+void TopLevel::commitTopChoice()
+{
+ if (!m_game->hasPositions())
+ return;
+
+ m_game->currentPosition().kibitz(1);
+ m_game->setCandidate(m_game->currentPosition().moves().back());
+ commit();
+}
+
+void TopLevel::ensureUpToDateSimulatorMoveList()
+{
+ m_simulator->setIncludedMoves(m_game->currentPosition().moves());
+}
+
+void TopLevel::simulate(bool startSimulation)
+{
+ m_simulateAction->setChecked(startSimulation);
+
+ // it's not so useful to have sim control show/hide
+ // like this
+ //m_simulatorWidget->setVisible(startSimulation);
+
+ if (startSimulation)
+ {
+ logfileChanged();
+ incrementSimulation();
+ }
+ else
+ m_simulationTimer->stop();
+}
+
+void TopLevel::simulateToggled(bool startSimulation)
+{
+ if (!m_game->hasPositions())
+ return;
+
+ simulate(startSimulation);
+
+ if (startSimulation)
+ {
+ switchToTab(ChoicesTabIndex);
+ statusMessage(tr("Starting simulation. To stop the simulation, uncheck the \"Simulate\" menu entry in the Move menu."));
+
+ // Make sure the simulator has the correct partial oppo rack.
+ partialOppoRackChanged();
+ }
+}
+
+void TopLevel::clearSimulationResults()
+{
+ if (!m_game->hasPositions())
+ return;
+
+ m_simulator->resetNumbers();
+
+ updateMoveViews();
+ updateSimViews();
+}
+
+void TopLevel::timeout()
+{
+ UVcout << "toplevel::timeout" << endl;
+}
+
+void TopLevel::pliesSet(const QString &plyString)
+{
+ if (plyString == tr("Many"))
+ m_plies = -1;
+ else
+ m_plies = plyString.toInt();
+}
+
+void TopLevel::ignoreOpposChanged()
+{
+ m_simulator->setIgnoreOppos(m_ignoreOpposCheck->isChecked());
+}
+
+void TopLevel::updatePliesCombo()
+{
+ int index;
+
+ if (m_plies == -1)
+ index = m_pliesToOffer;
+ else
+ index = m_plies - 1;
+
+ m_pliesCombo->setCurrentIndex(index);
+}
+
+void TopLevel::logfileEnabled(bool on)
+{
+ setLogfileEnabled(on);
+}
+
+void TopLevel::setLogfileEnabled(bool /* enabled */)
+{
+ // not needed with QGroupBox
+ //m_logfileChooser->setEnabled(enabled);
+ //m_logfileEdit->setEnabled(enabled);
+
+ logfileChanged();
+}
+
+bool TopLevel::isLogfileEnabled() const
+{
+ return m_logfileEnable->isChecked();
+}
+
+QString TopLevel::userSpecifiedLogfile() const
+{
+ return m_logfileEdit->text();
+}
+
+QString TopLevel::logfile() const
+{
+ return isLogfileEnabled()? userSpecifiedLogfile() : QString("");
+}
+
+void TopLevel::logfileChanged()
+{
+ m_simulator->setLogfile(QuackleIO::Util::qstringToStdString(logfile()), /* append */ true);
+}
+
+void TopLevel::chooseLogfile()
+{
+ QString filename;
+
+ QFileDialog *fileDialog = new QFileDialog(this, tr("Choose log file"));
+
+ fileDialog->setDirectory(userSpecifiedLogfile().isEmpty()? QDir::currentPath() : QFileInfo(userSpecifiedLogfile()).absolutePath());
+ fileDialog->setFileMode(QFileDialog::AnyFile);
+ fileDialog->setConfirmOverwrite(false);
+
+ if (fileDialog->exec())
+ {
+ QStringList files(fileDialog->selectedFiles());
+ if (!files.empty())
+ filename = files.back();
+ }
+
+ if (filename.isEmpty())
+ return;
+
+ m_logfileEdit->setText(filename);
+ logfileChanged();
+}
+
+void TopLevel::partialOppoRackEnabled(bool on)
+{
+ setPartialOppoRackEnabled(on);
+}
+
+void TopLevel::setPartialOppoRackEnabled(bool /* enabled */)
+{
+ partialOppoRackChanged();
+}
+
+bool TopLevel::isPartialOppoRackEnabled() const
+{
+ return m_partialOppoRackEnable->isChecked();
+}
+
+QString TopLevel::userSpecifiedPartialOppoRack() const
+{
+ return m_partialOppoRackEdit->text();
+}
+
+QString TopLevel::partialOppoRack() const
+{
+ return isPartialOppoRackEnabled()? userSpecifiedPartialOppoRack() : QString("");
+}
+
+void TopLevel::partialOppoRackChanged()
+{
+ Quackle::Rack rack = QuackleIO::Util::makeRack(partialOppoRack());
+ if (!m_game->hasPositions())
+ return;
+
+ Quackle::LetterString oppoTiles = m_game->currentPosition().currentPlayer().rack().tiles();
+ Quackle::LetterString doubleTiles = rack.tiles();
+ for (Quackle::LetterString::const_iterator it = oppoTiles.begin(); it != oppoTiles.end(); ++it)
+ {
+ doubleTiles.push_back(*it);
+ }
+ Quackle::Rack doubleRack(doubleTiles);
+
+ if (!m_game->currentPosition().canSetPlayerRackWithoutBagExpansion(m_game->currentPosition().currentPlayer().id(), doubleRack))
+ {
+ QMessageBox::warning(this, tr("Wrackful Rack - Quackle"), dialogText(tr("The rack %1 contains letters that are not possible for opponent to hold.").arg(QuackleIO::Util::letterStringToQString(rack.tiles()))));
+ m_partialOppoRackEdit->selectAll();
+ m_partialOppoRackEdit->setFocus();
+ return;
+ }
+
+ m_simulator->setPartialOppoRack(rack);
+}
+
+void TopLevel::showSimulationDetails()
+{
+ if (!m_simViewer)
+ m_simViewer = new SimViewer(this);
+
+ m_simViewer->setSimulator(*m_simulator);
+ m_simViewer->show();
+
+ updateSimViews();
+}
+
+void TopLevel::incrementSimulation()
+{
+ if (m_simulateAction->isChecked())
+ {
+ m_simulator->simulate(m_plies);
+ m_simulationTimer->start(0);
+
+ if (m_simulator->iterations() % 10 == 0)
+ updateMoveViews();
+
+ updateSimViews();
+ }
+}
+
+void TopLevel::updateSimViews()
+{
+ m_simulatorWidget->setTitle(m_simulator->hasSimulationResults()? tr("Simulation: %2 iterations").arg(m_simulator->iterations()) : tr("Simulation"));
+
+ if (m_simViewer && m_simViewer->isVisible())
+ m_simViewer->setSimulator(*m_simulator);
+}
+
+void TopLevel::loadFile(const QString &filename)
+{
+ if (filename.isEmpty())
+ return;
+
+ QString strippedFilename(filename.right(filename.length() - filename.lastIndexOf("/") - 1));
+ statusMessage(tr("Loading %1...").arg(strippedFilename));
+ qApp->processEvents();
+
+ QFile file(filename);
+ if (!file.exists())
+ {
+ QMessageBox::critical(this, tr("Error Loading Game File - Quackle"), dialogText(tr("Filename %1 does not exist.")).arg(filename));
+ return;
+ }
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Loading Game File - Quackle"), dialogText(tr("%1 cannot be opened.")).arg(filename));
+ return;
+ }
+
+ QuackleIO::Logania *logania = QuackleIO::Queenie::self()->loganiaForFile(filename);
+ if (logania == 0)
+ {
+ QMessageBox::critical(this, tr("Error Loading Game File - Quackle"), dialogText(tr("Sorry, %1 is in a format Quackle cannot read.")).arg(filename));
+ file.close();
+ return;
+ }
+
+ QTextStream stream(&file);
+ m_game = logania->read(stream, QuackleIO::Logania::MaintainBoardPreparation);
+
+ file.close();
+
+ m_logania = logania;
+
+ statusMessage(tr("Loaded game from `%1'.").arg(filename));
+ setModified(false);
+
+ startBirthday();
+
+ showToHuman();
+}
+
+void TopLevel::save()
+{
+ if (m_filename.isEmpty())
+ {
+ saveAs();
+ return;
+ }
+
+ writeFile(m_filename);
+}
+
+void TopLevel::saveAs()
+{
+ QString defaultFilter = defaultGameFileFilter();
+ QString filename = QFileDialog::getSaveFileName(this, tr("Choose file to which to save game"), getInitialDirectory(), gameFileFilters(), &defaultFilter);
+
+ if (!filename.isEmpty())
+ {
+ m_filename = filename;
+ setInitialDirectory(filename);
+ writeFile(m_filename);
+ saveSettings();
+ }
+}
+
+void TopLevel::reportAs(Quackle::ComputerPlayer *player)
+{
+ QString filename = QFileDialog::getSaveFileName(this, tr("Choose file to which to write report"), getInitialDirectory());
+
+ if (!filename.isEmpty())
+ {
+ setInitialDirectory(filename);
+ QFile file(filename);
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Writing File - Quacker"), dialogText(tr("Could not open %1 for writing.")).arg(filename));
+ return;
+ }
+
+ Quackle::ComputerPlayer *clone = player->clone();
+
+ QTextStream stream(&file);
+ QuackleIO::StreamingReporter::reportGame(*m_game, clone, stream);
+ delete clone;
+ }
+}
+
+void TopLevel::htmlReport()
+{
+ Quackle::ComputerPlayer *player = new Quackle::StaticPlayer();
+ QString filename = QFileDialog::getSaveFileName(this, tr("Choose HTML file to which to write report - Quackle"), getInitialDirectory(), "HTML Files (*.html)");
+
+ if (!filename.isEmpty())
+ {
+ setInitialDirectory(filename);
+
+ GraphicalReporter reporter(filename, /* don't generate images */ false);
+ reporter.reportGame(*m_game, player);
+ }
+
+ delete player;
+}
+
+void TopLevel::graphicalReport()
+{
+ Quackle::ComputerPlayer *player = new Quackle::StaticPlayer();
+ QString directory = QFileDialog::getExistingDirectory(this, tr("Choose directory to which to write report and graphics"), getInitialDirectory());
+
+ if (!directory.isEmpty())
+ {
+ setInitialDirectory(directory);
+
+ GraphicalReporter reporter(directory, /* generate images */ true);
+ reporter.reportGame(*m_game, player);
+ }
+
+ delete player;
+}
+
+void TopLevel::writeFile(const QString &filename)
+{
+ if (!m_game->hasPositions())
+ return;
+
+ QFile file(filename);
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Writing File - Quacker"), dialogText(tr("Could not open %1 for writing.")).arg(filename));
+ return;
+ }
+
+ QTextStream stream(&file);
+
+ QuackleIO::Logania *logania = m_logania? m_logania : QuackleIO::Queenie::self()->defaultLogania();
+
+ if (logania == 0)
+ {
+ file.close();
+ return;
+ }
+
+ logania->write(*m_game, stream);
+
+ file.close();
+
+ setModified(false);
+ statusMessage(tr("Saved game to `%1'.").arg(filename));
+}
+
+void TopLevel::pause(bool paused)
+{
+ timerControl(paused);
+
+ if (m_pauseAction->isChecked() != paused)
+ m_pauseAction->setChecked(paused);
+
+ if (!paused)
+ {
+ // set focus to widget that should have focus
+ m_brb->grabFocus();
+ }
+
+/*
+ if (paused)
+ statusMessage(tr("Paused."));
+ else
+ statusMessage(tr("Resuming..."));
+*/
+}
+
+void TopLevel::advanceGame()
+{
+ if (isPlayerOnTurnComputer())
+ startOppoThread();
+
+ showToHuman();
+}
+
+void TopLevel::startOppoThread()
+{
+ OppoThread *thread = new OppoThread;
+ m_oppoThreads.push_back(thread);
+ connect(thread, SIGNAL(finished()), this, SLOT(computerPlayerDone()));
+ connect(thread, SIGNAL(fractionDone(double, OppoThread *)), this, SLOT(playerFractionDone(double, OppoThread *)));
+ thread->setPosition(m_game->currentPosition());
+
+ thread->setPlayer(m_game->currentPosition().playerOnTurn().computerPlayer()->clone());
+ thread->findBestMoves(1);
+}
+
+void TopLevel::startOutcraftyingCurrentPlayer()
+{
+ if (shouldOutcraftyCurrentPlayer())
+ {
+ m_game->currentPosition().setCurrentPlayer(m_game->currentPosition().humanPlayer().id());
+ }
+}
+
+bool TopLevel::shouldOutcraftyCurrentPlayer() const
+{
+ return !m_game->currentPosition().gameOver() && m_game->hasPositions() && m_game->currentPosition().location() == m_game->history().lastLocation();
+}
+
+void TopLevel::stopOutcraftyingCurrentPlayer()
+{
+ // Magic
+ m_game->currentPosition().setCurrentPlayer(m_game->currentPosition().playerOnTurn().id());
+}
+
+void TopLevel::computerPlayerDone()
+{
+ Quackle::MoveList moves;
+
+ for (QList<OppoThread *>::iterator it = m_oppoThreads.begin(); it != m_oppoThreads.end(); )
+ {
+ if ((*it)->isFinished())
+ {
+ removeProgressIndicator(*it);
+
+ if ((*it)->position().playerOnTurn().positionallyEqual(m_game->currentPosition().playerOnTurn()))
+ {
+ moves = (*it)->moves();
+ }
+ else
+ {
+ UVcout << "Player on turn in position is not positionally equal to current player" << endl;
+ UVcout << (*it)->position().playerOnTurn() << endl;
+ UVcout << m_game->currentPosition().playerOnTurn() << endl;
+ }
+
+ // player is a clone
+ delete (*it)->player();
+
+ delete (*it);
+ it = m_oppoThreads.erase(it);
+ }
+ else
+ {
+ ++it;
+ UVcout << "computerPlayerDone but thread is not finished" << endl;
+ }
+ }
+
+ if (moves.empty())
+ {
+ UVcout << "compy moves are empty" << endl;
+ return;
+ }
+
+ stopOutcraftyingCurrentPlayer();
+ m_game->commitMove(moves.front());
+ setModified(true);
+ showToHuman();
+
+ if (!m_game->currentPosition().gameOver() && isPlayerOnTurnComputer())
+ {
+ QTimer::singleShot(0, this, SLOT(advanceGame()));
+ return;
+ }
+}
+
+void TopLevel::playerFractionDone(double fraction, OppoThread *thread)
+{
+ OppoThreadProgressBar *bar = progressBarOfThread(thread);
+ if (bar == 0)
+ bar = createProgressBarForThread(thread);
+ bar->setValue(static_cast<int>(fraction * 100));
+}
+
+void TopLevel::showToHuman()
+{
+ if (!m_game->currentPosition().gameOver())
+ {
+ startOutcraftyingCurrentPlayer();
+ }
+
+ Quackle::Rack rack(m_game->currentPosition().currentPlayer().rack());
+
+ // TODO
+ rack.setTiles(QuackleIO::Util::arrangeLettersForUser(rack));
+
+ // make sure that the internal order of rack is how user likes it
+ m_game->currentPosition().setCurrentPlayerRack(rack);
+
+ m_simulator->setPosition(m_game->currentPosition());
+
+ updateHistoryViews();
+ updatePositionViews();
+
+ ensureUpToDateSimulatorMoveList();
+ updateSimViews();
+
+ if (m_game->currentPosition().gameOver())
+ {
+ pause(true);
+
+ statusMessage(tr("Game over. Double-click an entry in the History table to perform postmortem."));
+ }
+ else
+ {
+ pause(false);
+ }
+
+ m_showAsciiAction->setEnabled(true);
+ m_saveAction->setEnabled(true);
+ m_saveAsAction->setEnabled(true);
+
+ switchToTab(HistoryTabIndex);
+ m_brb->grabFocus();
+
+ // TODO We should probably tell the user if the tile situation
+ // has become nonideal instead of just having this function
+ // spit things to stdout.
+
+ m_game->currentPosition().ensureProperBag();
+}
+
+void TopLevel::timerControl(bool paused)
+{
+ if (paused)
+ m_timer->stop();
+ else
+ {
+ // start timer
+ // eg m_timer->start(timerLength(), /* single shot */ true);
+ // except we'll want a non-single-shot job
+ }
+}
+
+QString TopLevel::gameTitle()
+{
+ QString ret;
+ const Quackle::PlayerList &players(m_game->players());
+
+ if (players.size() == 0)
+ ret = tr("No Game");
+ else if (players.size() == 1)
+ ret = tr("%1's solo game").arg(QuackleIO::Util::uvStringToQString(players.front().name()));
+ else if (players.size() == 2)
+ ret = tr("%1 versus %2").arg(QuackleIO::Util::uvStringToQString(players.front().name())).arg(QuackleIO::Util::uvStringToQString(players.at(1).name()));
+ else if (players.size() > 2)
+ ret = tr("Game between %1 and friends").arg(QuackleIO::Util::uvStringToQString(players.front().name()));
+
+ return ret;
+}
+
+void TopLevel::createMenu()
+{
+ const bool enableCommitAction = true;
+ const bool putCommitActionOnToolbar = false;
+ const bool enableLetterbox = false;
+
+ //// Game menu
+
+ m_newAction = new QAction(tr("&New game..."), this);
+ m_newAction->setShortcut(tr("Ctrl+N"));
+ connect(m_newAction, SIGNAL(triggered()), this, SLOT(newGame()));
+
+ m_openAction = new QAction(tr("&Open..."), this);
+ m_openAction->setShortcut(tr("Ctrl+O"));
+ connect(m_openAction, SIGNAL(triggered()), this, SLOT(open()));
+
+ m_saveAction = new QAction(tr("&Save"), this);
+ m_saveAction->setEnabled(false);
+ m_saveAction->setShortcut(tr("Ctrl+S"));
+ connect(m_saveAction, SIGNAL(triggered()), this, SLOT(save()));
+
+ m_saveAsAction = new QAction(tr("Save &as..."), this);
+ m_saveAsAction->setEnabled(false);
+ connect(m_saveAsAction, SIGNAL(triggered()), this, SLOT(saveAs()));
+
+ QAction *printAction = new QAction(tr("&Print..."), this);
+ connect(printAction, SIGNAL(triggered()), this, SLOT(print()));
+
+ m_showAsciiAction = new QAction(tr("Show plaintext &board"), this);
+ m_showAsciiAction->setEnabled(false);
+ connect(m_showAsciiAction, SIGNAL(triggered()), this, SLOT(showAscii()));
+
+ m_pauseAction = new QAction(tr("Pa&use"), this);
+ m_pauseAction->setShortcut(tr("Ctrl+P"));
+ m_pauseAction->setCheckable(true);
+ connect(m_pauseAction, SIGNAL(toggled(bool)), this, SLOT(pause(bool)));
+
+ QAction *quitAction = new QAction(tr("&Quit"), this);
+ quitAction->setShortcut(tr("Ctrl+Q"));
+ connect(quitAction, SIGNAL(triggered()), this, SLOT(close()));
+
+ //// Move
+
+ m_kibitzAction = new QAction(tr("&Generate choices"), this);
+ m_kibitzAction->setShortcut(tr("Ctrl+G"));
+ m_kibitzAction->setEnabled(false);
+ connect(m_kibitzAction, SIGNAL(triggered()), this, SLOT(kibitz()));
+
+ m_commitAction = new QAction(tr("Comm&it"), this);
+ m_commitAction->setShortcut(tr("Ctrl+I"));
+ m_commitAction->setEnabled(false);
+ connect(m_commitAction, SIGNAL(triggered()), this, SLOT(commit()));
+
+ m_passAction = new QAction(tr("&Pass"), this);
+ m_passAction->setEnabled(false);
+ connect(m_passAction, SIGNAL(triggered()), this, SLOT(pass()));
+
+ m_overdrawAction = new QAction(tr("&Handle opponent overdraw..."), this);
+ m_overdrawAction->setEnabled(false);
+ connect(m_overdrawAction, SIGNAL(triggered()), this, SLOT(overdraw()));
+
+ m_commitTopChoiceAction = new QAction(tr("&Commit top statically-evaluated choice"), this);
+ m_commitTopChoiceAction->setShortcut(tr("Ctrl+T"));
+ m_commitTopChoiceAction->setEnabled(false);
+ connect(m_commitTopChoiceAction, SIGNAL(triggered()), this, SLOT(commitTopChoice()));
+
+ m_kibitzFiftyAction = new QAction(tr("Generate 50 choices"), this);
+ m_kibitzFiftyAction->setEnabled(false);
+ connect(m_kibitzFiftyAction, SIGNAL(triggered()), this, SLOT(kibitzFifty()));
+
+ m_kibitzAllAction = new QAction(tr("Generate &all choices"), this);
+ m_kibitzAllAction->setEnabled(false);
+ connect(m_kibitzAllAction, SIGNAL(triggered()), this, SLOT(kibitzAll()));
+
+ //// Kibitzers and reports
+ m_kibitzAsActions = new QActionGroup(this);
+ m_reportAsActions = new QActionGroup(this);
+ for (Quackle::PlayerList::const_reverse_iterator it = QUACKLE_COMPUTER_PLAYERS.rbegin(); it != QUACKLE_COMPUTER_PLAYERS.rend(); ++it)
+ {
+ if (!(*it).computerPlayer()->isUserVisible())
+ continue;
+ KibitzerListener *listener = new KibitzerListener((*it).computerPlayer(), this);
+
+ QAction *kibitzAction = new QAction(tr("Ask %1 for choices").arg(QuackleIO::Util::uvStringToQString((*it).name())), this);
+ kibitzAction->setIconText(tr("Ask %1").arg(QuackleIO::Util::uvStringToQString((*it).name())));
+ connect(kibitzAction, SIGNAL(triggered()), listener, SLOT(kibitzTriggered()));
+
+ QAction *reportAction = new QAction(tr("Ask %1 for a full-game report").arg(QuackleIO::Util::uvStringToQString((*it).name())), this);
+ connect(reportAction, SIGNAL(triggered()), listener, SLOT(reportTriggered()));
+
+ connect(listener, SIGNAL(kibitzAs(Quackle::ComputerPlayer *)), this, SLOT(kibitzAs(Quackle::ComputerPlayer *)));
+ connect(listener, SIGNAL(reportAs(Quackle::ComputerPlayer *)), this, SLOT(reportAs(Quackle::ComputerPlayer *)));
+
+ m_kibitzAsActions->addAction(kibitzAction);
+ m_reportAsActions->addAction(reportAction);
+ }
+ m_kibitzAsActions->setEnabled(false);
+ m_reportAsActions->setEnabled(false);
+
+ // Go
+ m_firstPositionAction = new QAction(tr("&First position"), this);
+ m_firstPositionAction->setEnabled(false);
+ m_firstPositionAction->setShortcut(tr("Ctrl+Home"));
+ connect(m_firstPositionAction, SIGNAL(triggered()), this, SLOT(firstPosition()));
+
+ m_previousPositionAction = new QAction(tr("&Back to previous position"), this);
+ m_previousPositionAction->setIconText(tr("Back"));
+ m_previousPositionAction->setEnabled(false);
+ m_previousPositionAction->setShortcut(tr("Ctrl+B"));
+ connect(m_previousPositionAction, SIGNAL(triggered()), this, SLOT(previousPosition()));
+
+ m_nextPositionAction = new QAction(tr("&Forward to next position"), this);
+ m_nextPositionAction->setIconText(tr("Forward"));
+ m_nextPositionAction->setEnabled(false);
+ m_nextPositionAction->setShortcut(tr("Ctrl+F"));
+ connect(m_nextPositionAction, SIGNAL(triggered()), this, SLOT(nextPosition()));
+
+ //// Simulation
+
+ m_simulateAction = new QAction(tr("Start/Stop si&mulation"), this);
+ m_simulateAction->setIconText(tr("Simulate"));
+ m_simulateAction->setCheckable(true);
+ m_simulateAction->setEnabled(false);
+ connect(m_simulateAction, SIGNAL(toggled(bool)), this, SLOT(simulateToggled(bool)));
+
+ m_simulateDetailsAction = new QAction(tr("Show simulation &details"), this);
+ m_simulateDetailsAction->setEnabled(false);
+ connect(m_simulateDetailsAction, SIGNAL(triggered()), this, SLOT(showSimulationDetails()));
+
+ m_simulateClearAction = new QAction(tr("Clear simulation results"), this);
+ m_simulateClearAction->setEnabled(false);
+ connect(m_simulateClearAction, SIGNAL(triggered()), this, SLOT(clearSimulationResults()));
+
+ //// Study
+
+ QAction *letterboxAction = new QAction(tr("Letter&box"), this);
+ letterboxAction->setShortcut(tr("Ctrl+B"));
+ connect(letterboxAction, SIGNAL(triggered()), this, SLOT(letterbox()));
+
+ m_generateAction = new QAction(tr("Generate word &list"), this);
+ m_generateAction->setShortcut(tr("Ctrl+L"));
+ connect(m_generateAction, SIGNAL(triggered()), this, SLOT(generateList()));
+
+ //// Settings
+
+ m_preferencesAction = new QAction(tr("&Configure Quackle..."), this);
+ connect(m_preferencesAction, SIGNAL(triggered()), this, SLOT(showConfigDialog()));
+
+ //// Help
+
+ QAction *hintsAction = new QAction(tr("&Helpful Hints"), this);
+ connect(hintsAction, SIGNAL(triggered()), this, SLOT(hints()));
+
+ QAction *aboutAction = new QAction(tr("&About Quackle"), this);
+ connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
+
+ QAction *aboutQtAction = new QAction(tr("About &Qt"), this);
+ connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
+
+ //// Menus
+
+ QMenu *game = menuBar()->addMenu(tr("&Game"));
+
+ game->addAction(m_newAction);
+ game->addAction(m_openAction);
+ game->addSeparator();
+ game->addAction(m_saveAction);
+ game->addAction(m_saveAsAction);
+ game->addSeparator();
+
+ if (!enableLetterbox)
+ {
+ game->addAction(m_generateAction);
+ }
+
+ game->addAction(m_showAsciiAction);
+ game->addSeparator();
+ game->addAction(quitAction);
+
+ QMenu *move = menuBar()->addMenu(tr("Mo&ve"));
+
+ move->addAction(m_kibitzAction);
+ move->addAction(m_kibitzFiftyAction);
+ move->addAction(m_kibitzAllAction);
+
+ move->addSeparator();
+
+ QList<QAction *> kibitzAsActions = m_kibitzAsActions->actions();
+ for (QList<QAction *>::iterator it = kibitzAsActions.begin(); it != kibitzAsActions.end(); ++it)
+ move->addAction(*it);
+
+ move->addSeparator();
+
+ move->addAction(m_passAction);
+ move->addAction(m_overdrawAction);
+ move->addSeparator();
+
+ if (enableCommitAction)
+ move->addAction(m_commitAction);
+ move->addAction(m_commitTopChoiceAction);
+
+ QMenu *go = menuBar()->addMenu(tr("&Go"));
+ go->addAction(m_firstPositionAction);
+ go->addAction(m_nextPositionAction);
+ go->addAction(m_previousPositionAction);
+
+ QMenu *reports = menuBar()->addMenu(tr("Re&ports"));
+
+ QList<QAction *> reportAsActions = m_reportAsActions->actions();
+ for (QList<QAction *>::iterator it = reportAsActions.begin(); it != reportAsActions.end(); ++it)
+ reports->addAction(*it);
+
+ reports->addSeparator();
+
+ m_htmlReportAction = new QAction(tr("Quick full-game HTML report"), this);
+ m_htmlReportAction->setEnabled(false);
+ connect(m_htmlReportAction, SIGNAL(triggered()), this, SLOT(htmlReport()));
+ reports->addAction(m_htmlReportAction);
+
+#ifdef ENABLE_GRAPHICAL_REPORT
+ m_graphicalReportAction = new QAction(tr("Slow full-game HTML report with images"), this);
+ m_graphicalReportAction->setEnabled(false);
+ connect(m_graphicalReportAction, SIGNAL(triggered()), this, SLOT(graphicalReport()));
+ reports->addAction(m_graphicalReportAction);
+#endif
+
+ QMenu *simulation = menuBar()->addMenu(tr("Si&mulation"));
+ simulation->addAction(m_simulateAction);
+ simulation->addAction(m_simulateDetailsAction);
+ simulation->addAction(m_simulateClearAction);
+
+ if (enableLetterbox)
+ {
+ QMenu *study = menuBar()->addMenu(tr("Stud&y"));
+ study->addAction(m_generateAction);
+ study->addAction(letterboxAction);
+ }
+
+ QMenu *settings = menuBar()->addMenu(tr("&Settings"));
+ settings->addAction(m_preferencesAction);
+
+ menuBar()->addSeparator();
+
+ QMenu *help = menuBar()->addMenu(tr("&Help"));
+ help->addAction(hintsAction);
+ help->addSeparator();
+ help->addAction(aboutAction);
+ help->addAction(aboutQtAction);
+
+ // file toolbar
+ QToolBar *fileBar = addToolBar(tr("File"));
+ fileBar->setObjectName("file-toolbar");
+ fileBar->addAction(m_newAction);
+ fileBar->addAction(m_generateAction);
+
+ // move toolbar
+ QToolBar *moveBar = addToolBar(tr("Move"));
+ moveBar->setObjectName("move-toolbar");
+
+ if (enableCommitAction && putCommitActionOnToolbar)
+ {
+ moveBar->addAction(m_commitAction);
+ moveBar->addSeparator();
+ }
+
+ moveBar->addAction(m_kibitzAction);
+ moveBar->addAction(m_nextPositionAction);
+ moveBar->addAction(m_kibitzAsActions->actions().front());
+ moveBar->addAction(m_simulateAction);
+
+ // study toolbar
+ if (enableLetterbox)
+ {
+ QToolBar *studyBar = addToolBar(tr("Study"));
+ studyBar->setObjectName("study-toolbar");
+ studyBar->addAction(letterboxAction);
+ }
+}
+
+void TopLevel::createWidgets()
+{
+ m_splitter = new QSplitter(Qt::Horizontal, this);
+
+ setCentralWidget(m_splitter);
+
+ QWidget *leftSide = new QWidget;
+ m_leftSideLayout = new QVBoxLayout(leftSide);
+ Geometry::setupFramedLayout(m_leftSideLayout);
+
+ m_dashboard = new Dashboard;
+ plugIntoHistoryMatrix(m_dashboard);
+
+ m_choicesWidget = new QWidget;
+ QVBoxLayout *choicesLayout = new QVBoxLayout(m_choicesWidget);
+ Geometry::setupFramedLayout(choicesLayout);
+
+ m_simulatorWidget = new QGroupBox(tr("Simulation"));
+ m_simulatorWidget->setFlat(true);
+ QVBoxLayout *simulatorLayout = new QVBoxLayout(m_simulatorWidget);
+ Geometry::setupInnerLayout(simulatorLayout);
+
+ QHBoxLayout *plyLayout = new QHBoxLayout;
+ Geometry::setupInnerLayout(plyLayout);
+
+ m_pliesCombo = new QComboBox;
+ QStringList plyOptions;
+
+ for (int i = 1; i <= m_pliesToOffer; ++i)
+ plyOptions.push_back(QString::number(i));
+
+ plyOptions.push_back(tr("Many"));
+
+ m_pliesCombo->addItems(plyOptions);
+ connect(m_pliesCombo, SIGNAL(activated(const QString &)), this, SLOT(pliesSet(const QString &)));
+
+ QLabel *plyLabel = new QLabel(tr("p&lies"));
+ plyLabel->setBuddy(m_pliesCombo);
+
+ m_ignoreOpposCheck = new QCheckBox(tr("oppos pass"));
+ connect(m_ignoreOpposCheck, SIGNAL(stateChanged(int)), this, SLOT(ignoreOpposChanged()));
+
+ m_showDetailsButton = new QPushButton(tr("&Details"));
+ connect(m_showDetailsButton, SIGNAL(clicked()), this, SLOT(showSimulationDetails()));
+
+ plyLayout->addWidget(m_pliesCombo);
+ plyLayout->addWidget(plyLabel);
+ plyLayout->addWidget(m_ignoreOpposCheck);
+ plyLayout->addStretch();
+ plyLayout->addWidget(m_showDetailsButton);
+ simulatorLayout->addLayout(plyLayout);
+
+ m_partialOppoRackEnable = new QGroupBox(tr("Specify partial oppo rack"));
+ m_partialOppoRackEnable->setCheckable(true);
+ m_partialOppoRackEnable->setFlat(true);
+ connect(m_partialOppoRackEnable, SIGNAL(toggled(bool)), this, SLOT(partialOppoRackEnabled(bool)));
+
+ QHBoxLayout *partialOppoRackLayout = new QHBoxLayout(m_partialOppoRackEnable);
+ Geometry::setupInnerLayout(partialOppoRackLayout);
+
+ m_partialOppoRackEdit = new QLineEdit;
+ connect(m_partialOppoRackEdit, SIGNAL(textEdited(const QString &)), this, SLOT(partialOppoRackChanged()));
+
+ partialOppoRackLayout->addWidget(m_partialOppoRackEdit);
+ simulatorLayout->addWidget(m_partialOppoRackEnable);
+
+ m_logfileEnable = new QGroupBox(tr("Log sim to file"));
+ m_logfileEnable->setCheckable(true);
+ m_logfileEnable->setFlat(true);
+ connect(m_logfileEnable, SIGNAL(toggled(bool)), this, SLOT(logfileEnabled(bool)));
+
+ QHBoxLayout *logfileLayout = new QHBoxLayout(m_logfileEnable);
+ Geometry::setupInnerLayout(logfileLayout);
+
+ m_logfileEdit = new QLineEdit;
+ connect(m_logfileEdit, SIGNAL(editingFinished()), this, SLOT(logfileChanged()));
+
+ m_logfileChooser = new QPushButton(tr("Browse..."));
+ connect(m_logfileChooser, SIGNAL(clicked()), this, SLOT(chooseLogfile()));
+
+ logfileLayout->addWidget(m_logfileEdit);
+ logfileLayout->addWidget(m_logfileChooser);
+ simulatorLayout->addWidget(m_logfileEnable);
+
+ m_noteEditor = new NoteEditor;
+ plugIntoMatrix(m_noteEditor);
+ plugIntoPositionMatrix(m_noteEditor);
+
+ m_moveBox = new MoveBox;
+ plugIntoMatrix(m_moveBox);
+ plugIntoPositionMatrix(m_moveBox);
+ plugIntoMoveMatrix(m_moveBox);
+
+ choicesLayout->addWidget(m_moveBox, 3);
+ choicesLayout->addWidget(m_noteEditor, 1);
+ choicesLayout->addWidget(m_simulatorWidget, 1);
+
+ m_history = new History;
+ plugIntoHistoryMatrix(m_history);
+
+ m_tabWidget = new QTabWidget;
+ m_tabWidget->addTab(m_history, tr("Histor&y"));
+ m_tabWidget->addTab(m_choicesWidget, tr("&Choices"));
+ m_tabWidget->addTab(m_settings, tr("Se&ttings"));
+
+ GraphicalFactory factory;
+ m_brb = new BRB(&factory);
+ plugIntoMatrix(m_brb);
+ plugIntoPositionMatrix(m_brb);
+
+ m_leftSideLayout->addWidget(m_dashboard);
+ m_leftSideLayout->addWidget(m_tabWidget);
+
+ m_splitter->addWidget(leftSide);
+ m_splitter->addWidget(m_brb);
+
+ m_splitter->setStretchFactor(1, 4);
+
+ m_listerDialog = new ListerDialog(this, "quackle", tr("Quackle"), ListerDialog::NothingToReturn);
+}
+
+void TopLevel::switchToTab(TabIndex index)
+{
+ m_tabWidget->setCurrentIndex(index);
+}
+
+QString TopLevel::playerString() const
+{
+ QString ret;
+
+ if (!m_game->hasPositions())
+ return tr("No game");
+
+ Quackle::PlayerList players = m_game->currentPosition().endgameAdjustedScores();
+ if (players.empty())
+ return tr("No game");
+
+ bool begin = true;
+ int i = 0;
+ const int maximumIndex = players.size() - 1;
+ const Quackle::PlayerList::const_iterator end(players.end());
+ for (Quackle::PlayerList::const_iterator it = players.begin(); it != end; ++it, ++i)
+ {
+ if (!begin)
+ ret += tr(", ");
+
+ if (!begin && i == maximumIndex && i >= 2)
+ ret += tr("and ");
+
+ ret += tr("%1 (score %2)").arg(QuackleIO::Util::uvStringToQString((*it).name())).arg((*it).score());
+ begin = false;
+ }
+
+ return ret;
+}
+
+void TopLevel::showAscii()
+{
+ if (!m_game->hasPositions())
+ return;
+
+ QString text;
+
+ // This is now usurped into Reporter's output.
+ //text += tr("Players: %1").arg(playerString()) + "\n\n";
+
+ UVString report;
+ Quackle::Reporter::reportPosition(m_game->currentPosition(), 0, &report);
+ text += QuackleIO::Util::uvStringToQString(report);
+
+ int result = QMessageBox::question(this, tr("Plaintext board - Quackle"), QString("<pre>%1</pre>").arg(text), tr("&Write to file"), tr("Copy to clip&board"), tr("&OK"), 1);
+
+ switch (result)
+ {
+ case 0:
+ {
+ QString filename = QFileDialog::getSaveFileName(this, tr("Choose file to print plaintext board to - Quackle"), getInitialDirectory());
+
+ if (!filename.isEmpty())
+ {
+ setInitialDirectory(filename);
+ writeAsciiToFile(text, filename);
+ }
+ break;
+ }
+
+ case 1:
+ copyToClipboard(text);
+ break;
+
+ case 2:
+ break;
+ }
+}
+
+void TopLevel::copyToClipboard(const QString &text)
+{
+ QApplication::clipboard()->setText(text, QClipboard::Clipboard);
+ QApplication::clipboard()->setText(text, QClipboard::Selection);
+
+ statusMessage(tr("Copied to clipboard."));
+}
+
+void TopLevel::writeAsciiToFile(const QString &text, const QString &filename)
+{
+ QFile file(filename);
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Writing File - Quackle"), dialogText(tr("Could not open %1 for writing.")).arg(file.fileName()));
+ return;
+ }
+
+ QTextStream stream(&file);
+ stream << text << "\n";
+
+ file.close();
+
+ statusMessage(tr("`%1' written.").arg(filename));
+}
+
+void TopLevel::print()
+{
+ pause(true);
+
+ QString filename = QFileDialog::getSaveFileName(this, tr("Choose file to print to - Quackle"), m_filename + ".html");
+
+ if (filename.isEmpty())
+ return;
+
+ QFile file(filename);
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QMessageBox::critical(this, tr("Error Writing File - Quackle"), dialogText(tr("Could not open %1 for writing.")).arg(file.fileName()));
+ return;
+ }
+
+ QTextStream stream(&file);
+ //stream << printer.html() << "\n";
+
+ file.close();
+
+ statusMessage(tr("`%1' written.").arg(filename));
+}
+
+void TopLevel::firstTimeRun()
+{
+ switchToTab(SettingsTabIndex);
+ QMessageBox::information(this, tr("Welcome - Quackle"), dialogText(tr("Welcome to Quackle! To get started, configure a board by clicking Add Board in the Settings tab, add some bonus squares, and then start a new game. Also check out the Helpful Hints from the Help menu, and keep your eye out for the aidant messages in the status bar at the bottom of the window.")));
+}
+
+void TopLevel::about()
+{
+ QMessageBox::about(this, tr("About Quackle 0.97"), dialogText(tr(
+"<p><b>Quackle</b> 0.97 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-2012 by</p>"
+"<ul>"
+"<li>Jason Katz-Brown &lt;jasonkatzbrown@gmail.com&gt;</li>"
+"<li>John O'Laughlin &lt;olaughlin@gmail.com&gt;</li>"
+"</ul>"
+"<p>Quackle is free, open-source software licensed under the terms of the GNU General Public License Version 2. See</p>"
+"<p><tt><a href=\"http://quackle.org/LICENSE\">http://quackle.org/LICENSE</a></tt></p>"
+)));
+}
+
+void TopLevel::hints()
+{
+ QMessageBox::information(this, tr("Helpful Hints - Quackle"), dialogText(tr(
+"<ul>"
+"<li>Press Control-Enter after typing your word on the board to enter and commit your move quickly.</li>"
+"<li>Double-click at any time during a game on any item in the History table to analyze that position. If you then commit a play, you will restart the game from that point and future plays will be lost.</li>"
+"<li>To analyze a real-life game, start a two-player game with two human-controlled players. For one player, for each turn set the rack to the rack you had in the game and then analyze the position and commit the play that you made in real life. For the other player, commit your oppo's real-life plays and ignore the warnings about rack contents Quackle gives.</li>"
+"<li>Stop simulations by unchecking \"Simulate\" in the Move menu. Sims can be stopped and restarted without losing their state, and sims of different plies can be combined. Check out the sim details during a simulation by choosing \"Show simulation details\" from the Move menu!</li>"
+"</ul>"
+"<p>Have fun using Quackle. We'd love your help developing it, especially if you can code, but we like suggestions too! Please join the Quackle Yahoo! group at</p>"
+"<p><tt>http://games.groups.yahoo.com/group/quackle/</tt></p>"
+)));
+}
+
+void TopLevel::showConfigDialog()
+{
+ ConfigDialog dialog;
+ connect(&dialog, SIGNAL(refreshViews()), this, SLOT(updateAllViews()));
+ dialog.exec();
+}
+
+void TopLevel::saveSettings()
+{
+ CustomQSettings settings;
+
+ settings.setValue("quackle/initial-directory", m_initialDirectory);
+ settings.setValue("quackle/window-size", size());
+ settings.setValue("quackle/splitter-sizes", m_splitter->saveState());
+ settings.setValue("quackle/window-state", saveState(0));
+ settings.setValue("quackle/plies", m_plies);
+ settings.setValue("quackle/ignoreoppos", m_ignoreOpposCheck->isChecked());
+ settings.setValue("quackle/logfileEnabled", isLogfileEnabled());
+ settings.setValue("quackle/logfile", userSpecifiedLogfile());
+ settings.setValue("quackle/partialopporackenabled", isPartialOppoRackEnabled());
+ settings.setValue("quackle/partialopporack", userSpecifiedPartialOppoRack());
+
+ settings.setValue("quackle/hasBeenRun", true);
+
+ QuackerSettings::self()->writeSettings();
+}
+
+void TopLevel::loadSettings()
+{
+ CustomQSettings settings;
+
+ m_initialDirectory = settings.value("quackle/initial-directory", QString("")).toString();
+ resize(settings.value("quackle/window-size", QSize(800, 600)).toSize());
+
+ if (settings.contains("quackle/splitter-sizes"))
+ m_splitter->restoreState(settings.value("quackle/splitter-sizes").toByteArray());
+
+ if (settings.contains("quackle/window-state"))
+ restoreState(settings.value("quackle/window-state").toByteArray(), 0);
+
+ m_plies = settings.value("quackle/plies", 2).toInt();
+ updatePliesCombo();
+
+ m_ignoreOpposCheck->setChecked(settings.value("quackle/ignoreoppos", false).toBool());
+
+ m_logfileEdit->setText(settings.value("quackle/logfile", QString("")).toString());
+ const bool logfileEnabled = settings.value("quackle/logfileEnabled", false).toBool();
+ m_logfileEnable->setChecked(logfileEnabled);
+ setLogfileEnabled(logfileEnabled);
+
+ m_partialOppoRackEdit->setText(settings.value("quackle/partialopporack", QString("")).toString());
+ const bool partialOppoRackEnabled = settings.value("quackle/partialopporackenabled", false).toBool();
+ m_partialOppoRackEnable->setChecked(partialOppoRackEnabled);
+ setPartialOppoRackEnabled(partialOppoRackEnabled);
+
+ QuackerSettings::self()->readSettings();
+}
+
+QString TopLevel::dialogText(const QString &text)
+{
+ return QString("<html>%1</html>").arg(text);
+}
+
+void TopLevel::startBirthday()
+{
+ if (!m_game || !m_game->hasPositions())
+ {
+ return;
+ }
+
+ // Happy Birthday, Ong Suanne.
+
+ bool isBirthday = false;
+ const Quackle::PlayerList &players = m_game->currentPosition().players();
+ for (Quackle::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
+ {
+ if ((*it).name() == "zorbonauts")
+ {
+ isBirthday = true;
+ break;
+ }
+ }
+
+ if (!isBirthday)
+ {
+ m_birthdayTimer->stop();
+ return;
+ }
+
+ m_birthdayIndex = 0;
+ m_birthdayTimer->start(800);
+}
+
+void TopLevel::birthdayBash()
+{
+ birthdayGram(m_birthdayIndex, /* off */ false);
+ ++m_birthdayIndex;
+ if (m_birthdayIndex >= 11)
+ m_birthdayIndex = 0;
+ birthdayGram(m_birthdayIndex, /* on */ true);
+}
+
+void TopLevel::birthdayGram(int index, bool on)
+{
+ switch (index)
+ {
+ case 0:
+ setCaption(tr("HURRRRRRRRRRRRRRRRRRRRRRRRR"));
+ break;
+ case 1:
+ m_newAction->setIconText(on? tr("HAPPY") : tr("New game"));
+ break;
+ case 2:
+ m_generateAction->setIconText(on? tr("BIRTHDAY") : tr("Generate word list"));
+ break;
+ case 3:
+ m_kibitzAction->setIconText(on? tr("ONG") : tr("Generate choices"));
+ break;
+ case 4:
+ m_nextPositionAction->setIconText(on? tr("! ! ! ! ! ! ! ! !") : tr("Forward"));
+ break;
+ case 5:
+ m_kibitzAsActions->actions().front()->setIconText(on? tr("SUANNE") : tr("Ask Championship Player"));
+ break;
+ case 6:
+ m_simulateAction->setIconText(on? tr("! ! ! ! ! ! ! ! !") : tr("Simulate"));
+ break;
+ case 7:
+ m_tabWidget->setTabText(0, on? tr("HAVE A") : tr("Histor&y"));
+ break;
+ case 8:
+ m_tabWidget->setTabText(1, on? tr("SUPERPIMP") : tr("&Choices"));
+ break;
+ case 9:
+ m_tabWidget->setTabText(2, on? tr("YEAR") : tr("Se&ttings"));
+ break;
+ case 10:
+ statusMessage(on? tr("you're awesome don't ever change") : tr(""));
+ break;
+ default:
+ break;
+ }
+}
+
+//////////////
+
+KibitzerListener::KibitzerListener(Quackle::ComputerPlayer *computerPlayer, QObject *parent)
+ : QObject(parent), m_computerPlayer(computerPlayer), m_slowPlayerWarningTriggered(false)
+{
+}
+
+void KibitzerListener::kibitzTriggered()
+{
+ if (slownessCheck())
+ emit kibitzAs(m_computerPlayer);
+}
+
+void KibitzerListener::reportTriggered()
+{
+ if (slownessCheck())
+ emit reportAs(m_computerPlayer);
+}
+
+bool KibitzerListener::slownessCheck()
+{
+ return true;
+}
+
diff --git a/quacker/quacker.h b/quacker/quacker.h
new file mode 100644
index 0000000..338b227
--- /dev/null
+++ b/quacker/quacker.h
@@ -0,0 +1,445 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_QUACKER_H
+#define QUACKER_QUACKER_H
+
+// #define ENABLE_GRAPHICAL_REPORT
+
+#include <QMainWindow>
+#include <QMap>
+
+#include <datamanager.h>
+#include "oppothread.h"
+#include <sim.h>
+
+#include <quackleio/dictimplementation.h>
+
+class QAction;
+class QActionGroup;
+class QComboBox;
+class QCheckBox;
+class QGroupBox;
+class QLineEdit;
+class QPushButton;
+class QSplitter;
+class QTabWidget;
+class QTimer;
+class QVBoxLayout;
+
+class OppoThreadProgressBar;
+
+namespace Quackle
+{
+ class ComputerPlayer;
+ class Game;
+ class GamePosition;
+ class History;
+ class HistoryLocation;
+ class Move;
+ class Rack;
+}
+
+namespace QuackleIO
+{
+ class Logania;
+}
+
+class BaseView;
+class HistoryView;
+class Letterbox;
+class ListerDialog;
+class QuackerSettings;
+class Settings;
+class SimViewer;
+class View;
+
+class TopLevel : public QMainWindow
+{
+Q_OBJECT
+
+public:
+ TopLevel(QWidget *parent = 0);
+ ~TopLevel();
+
+ void closeEvent(QCloseEvent *closeEvent);
+
+ // returns text wrapped in an HTML tag to ensure dialog wrapping
+ static QString dialogText(const QString &text);
+
+public slots:
+ void open();
+ void newGame();
+
+ // like loadFile, but set the filename, initial directory,
+ // and other things
+ void openFile(const QString &filename);
+
+ // actually loads the file
+ void loadFile(const QString &filename);
+
+ void save();
+ void saveAs();
+ void writeFile(const QString &filename);
+ void generateList();
+ void letterbox();
+
+ void kibitz();
+ void kibitz(int numberOfPlays, Quackle::ComputerPlayer *computerPlayer = 0);
+ void kibitzFifty();
+ void kibitzAll();
+ void kibitzAs(Quackle::ComputerPlayer *computerPlayer);
+
+ void firstPosition();
+ void nextPosition();
+ void previousPosition();
+
+ void reportAs(Quackle::ComputerPlayer *computerPlayer);
+ void htmlReport();
+ void graphicalReport();
+ void commitTopChoice();
+ void simulate(bool startSimulation);
+ void simulateToggled(bool startSimulation);
+ void clearSimulationResults();
+
+ void showAscii();
+ void writeAsciiToFile(const QString &text, const QString &filename);
+ void copyToClipboard(const QString &text);
+ void print();
+ void firstTimeRun();
+ void about();
+ void hints();
+
+ void showConfigDialog();
+
+ // set up our game object based on a shuffled playerList
+ void initializeGame(const Quackle::PlayerList &players);
+
+ // call timerControl, and tell user about it
+ void pause(bool paused);
+
+protected slots:
+ // final set up of initial data structures
+ // and an example game
+ void finishInitialization();
+
+ // say hi
+ void introduceToUser();
+
+ // prepares current position and spawns computer player thread
+ // if necessary then sends orders to update the UI
+ void advanceGame();
+
+ // asks oppo thread to find best move
+ void startOppoThread();
+
+ // called by oppo thread when it's done
+ void computerPlayerDone();
+
+ // called by player thread when it's done
+ void kibitzThreadFinished();
+
+ // called by a oppo/player thread when it has a status update
+ void playerFractionDone(double fraction, OppoThread *thread);
+
+ // update both positional and history views,
+ // then wait for human to make a play
+ void showToHuman();
+
+ // use this to control timer!
+ void timerControl(bool paused);
+
+ // commit candidate play then advance the game
+ void commit();
+
+ // add a pass candidate
+ void pass();
+
+ // handle an overdraw
+ void overdraw();
+
+ void statusMessage(const QString &mesage);
+
+ // set game's candidate to move and update views
+ void setCandidateMove(const Quackle::Move &move);
+ void removeCandidateMoves(const Quackle::MoveList &moves);
+
+ // set current player's rack and update views
+ void setRack(const Quackle::Rack &rack);
+
+ // set current position's explanatory note
+ void setNote(const UVString &note);
+
+ // set history location to view
+ void goToHistoryLocation(const Quackle::HistoryLocation &location);
+
+ // stop simulation, opponenent thread, etc
+ void stopEverything();
+
+ // update all views (usually because of a settings change)
+ void updateAllViews();
+
+ // update *positional* views - emit positionChanged
+ void updatePositionViews();
+
+ // updates move views from either simulation results if available
+ // or kibitzed moves
+ void updateMoveViews();
+
+ // update history views when a new position is added
+ void updateHistoryViews();
+
+ void setCaption(const QString &text = QString::null);
+ void setModified(bool modified);
+
+ // main timer
+ void timeout();
+
+ // simulation timer
+ void incrementSimulation();
+ void updateSimViews();
+
+ // simulator settings:
+ void pliesSet(const QString &plyString);
+ void ignoreOpposChanged();
+ void updatePliesCombo();
+ void logfileEnabled(bool on);
+ void logfileChanged();
+ void chooseLogfile();
+ void partialOppoRackEnabled(bool on);
+ void partialOppoRackChanged();
+ void showSimulationDetails();
+
+ // Birthday
+ void startBirthday();
+ void birthdayBash();
+ void birthdayGram(int index, bool on);
+
+signals:
+ // emitted when views (eg board) should update based on the
+ // current position (includes board information, current candidate play
+ // that should be shown)
+ void positionChanged(const Quackle::GamePosition &position);
+
+ void movesChanged(const Quackle::MoveList &moves);
+
+ // emitted when views of history must update
+ void historyChanged(const Quackle::History &history);
+
+protected:
+ Quackle::DataManager m_dataManager;
+ Quackle::Game *m_game;
+ Quackle::Simulator *m_simulator;
+
+private:
+ void saveSettings();
+ void loadSettings();
+
+ // returns 0 for save, 1 for discard, 2 for cancel
+ int askToSave();
+
+ // returns true if user wants to make play anyway
+ bool askToCarryOn(const QString &text);
+
+ // used to know when to update UI when performing heavy calculation
+ bool m_initializationChuu;
+
+ // are dictionaries etc properly set up?
+ bool setupCheck();
+
+ // hook up signals and slots associated with a view
+ void plugIntoBaseMatrix(BaseView *view);
+ void plugIntoMatrix(View *view);
+ void plugIntoPositionMatrix(View *view);
+ void plugIntoMoveMatrix(View *view);
+ void plugIntoHistoryMatrix(HistoryView *view);
+
+ void parseCommandLineOptions();
+
+ bool isPlayerOnTurnComputer() const;
+ bool isCommitAllowed() const;
+ bool shouldOutcraftyCurrentPlayer() const;
+ void startOutcraftyingCurrentPlayer();
+ void stopOutcraftyingCurrentPlayer();
+
+ // Used to sanitize a move for a player with unknown racks.
+ // Changes exchanges to exchange tiles on the current rack.
+ // If move is a place, changes current player's rack to something
+ // that includes the used tiles.
+ // Returns true if the validization was successful.
+ bool validifyMove(Quackle::Move &move);
+
+ void kibitzFinished();
+
+ OppoThreadProgressBar *createProgressBarForThread(OppoThread *thread);
+ void removeProgressIndicators();
+ void removeProgressIndicator(OppoThread *thread);
+ OppoThreadProgressBar *progressBarOfThread(OppoThread *thread);
+
+ // generic game title
+ QString gameTitle();
+
+ UVString m_firstPlayerName;
+ QString playerString() const;
+
+ QList<OppoThread *> m_oppoThreads;
+ QList<OppoThread *> m_otherOppoThreads;
+
+ QTimer *m_timer;
+ QTimer *m_simulationTimer;
+
+ QMap<OppoThread *, OppoThreadProgressBar *> m_progressIndicators;
+
+ View *m_brb;
+
+ QSplitter *m_splitter;
+
+ QVBoxLayout *m_leftSideLayout;
+
+ enum TabIndex { HistoryTabIndex = 0, ChoicesTabIndex = 1, SettingsTabIndex = 2};
+ QTabWidget *m_tabWidget;
+
+ HistoryView *m_history;
+ HistoryView *m_dashboard;
+
+ QWidget *m_choicesWidget;
+ View *m_moveBox;
+ View *m_noteEditor;
+
+ Settings *m_settings;
+
+ ListerDialog *m_listerDialog;
+ void updateListerDialogWithRack();
+
+ Letterbox *m_letterbox;
+ QuackerSettings *m_quackerSettings;
+
+// encapsulated simulator settings widget
+ QGroupBox *m_simulatorWidget;
+ QPushButton *m_showDetailsButton;
+ QLineEdit *m_logfileEdit;
+ QGroupBox *m_logfileEnable;
+ QPushButton *m_logfileChooser;
+ QLineEdit *m_partialOppoRackEdit;
+ QGroupBox *m_partialOppoRackEnable;
+ QCheckBox *m_ignoreOpposCheck;
+
+ static const int m_pliesToOffer = 6;
+ QComboBox *m_pliesCombo;
+
+ SimViewer *m_simViewer;
+
+ void setLogfileEnabled(bool enabled);
+ bool isLogfileEnabled() const;
+
+ // the value of the file-choosing lineedit
+ QString userSpecifiedLogfile() const;
+
+ // what user specified, or empty string if logging disabled
+ QString logfile() const;
+
+ void setPartialOppoRackEnabled(bool enabled);
+ bool isPartialOppoRackEnabled() const;
+
+ // the value of the partial oppo rack-choosing lineedit
+ QString userSpecifiedPartialOppoRack() const;
+
+ // what user specified, or empty string if logging disabled
+ QString partialOppoRack() const;
+
+ int m_plies;
+// end encapsulation, hah
+
+ QString m_filename;
+ QuackleIO::Logania *m_logania;
+ bool m_modified;
+ QString m_ourCaption;
+
+ QString getInitialDirectory() const;
+ void setInitialDirectory(const QString &filename);
+ QString m_initialDirectory;
+ QString gameFileFilters() const;
+ QString defaultGameFileFilter() const;
+
+ QActionGroup *m_kibitzAsActions;
+ QActionGroup *m_reportAsActions;
+ QAction *m_htmlReportAction;
+ QAction *m_showAsciiAction;
+ QAction *m_generateAction;
+ QAction *m_newAction;
+ QAction *m_openAction;
+ QAction *m_saveAction;
+ QAction *m_saveAsAction;
+ QAction *m_pauseAction;
+ QAction *m_firstPositionAction;
+ QAction *m_nextPositionAction;
+ QAction *m_previousPositionAction;
+ QAction *m_commitAction;
+ QAction *m_passAction;
+ QAction *m_overdrawAction;
+ QAction *m_kibitzAction;
+ QAction *m_kibitzFiftyAction;
+ QAction *m_kibitzAllAction;
+ QAction *m_commitTopChoiceAction;
+ QAction *m_simulateAction;
+ QAction *m_simulateClearAction;
+ QAction *m_simulateDetailsAction;
+ QAction *m_preferencesAction;
+
+#ifdef ENABLE_GRAPHICAL_REPORT
+ QAction *m_graphicalReportAction;
+#endif
+
+ // make sure moves in our simulator match those in current position
+ void ensureUpToDateSimulatorMoveList();
+
+ void createMenu();
+ void createWidgets();
+ void switchToTab(TabIndex index);
+
+ // Birthday
+ QTimer *m_birthdayTimer;
+ int m_birthdayIndex;
+};
+
+class KibitzerListener : public QObject
+{
+Q_OBJECT
+
+public:
+ KibitzerListener(Quackle::ComputerPlayer *computerPlayer, QObject *parent);
+
+public slots:
+ void kibitzTriggered();
+ void reportTriggered();
+
+signals:
+ void kibitzAs(Quackle::ComputerPlayer *computerPlayer);
+ void reportAs(Quackle::ComputerPlayer *computerPlayer);
+
+private:
+ // returns true if the action should still be carried out despite slowness
+ bool slownessCheck();
+
+ Quackle::ComputerPlayer *m_computerPlayer;
+ bool m_slowPlayerWarningTriggered;
+};
+
+#endif
diff --git a/quacker/quacker.ico b/quacker/quacker.ico
new file mode 100644
index 0000000..c84420f
--- /dev/null
+++ b/quacker/quacker.ico
Binary files differ
diff --git a/quacker/quacker.pro b/quacker/quacker.pro
new file mode 100644
index 0000000..3fa45cc
--- /dev/null
+++ b/quacker/quacker.pro
@@ -0,0 +1,41 @@
+TEMPLATE = app
+VERSION = 0.97
+DEPENDPATH += .. ../quackleio
+INCLUDEPATH += . ..
+
+MOC_DIR = moc
+
+# enable/disable debug symbols
+#CONFIG += debug
+CONFIG += release
+
+build_pass:CONFIG(debug, debug|release) {
+ LIBS += -L../debug -L../quackleio/debug
+}
+
+build_pass:CONFIG(release, debug|release) {
+ LIBS += -L../release -L../quackleio/release
+}
+
+LIBS += -L.. -L../quackleio -lquackle -lquackleio
+
+# Input
+HEADERS += bagdisplay.h boarddisplay.h boardsetup.h boardsetupdialog.h brb.h configdialog.h configpages.h customqsettings.h dashboard.h geometry.h graphicalboard.h graphicalreporter.h history.h letterbox.h letterboxsettings.h lister.h movebox.h newgame.h noteeditor.h quacker.h quackersettings.h rackdisplay.h settings.h simviewer.h view.h widgetfactory.h oppothread.h oppothreadprogressbar.h
+SOURCES += bagdisplay.cpp boarddisplay.cpp boardsetup.cpp boardsetupdialog.cpp brb.cpp configdialog.cpp configpages.cpp dashboard.cpp geometry.cpp graphicalboard.cpp graphicalreporter.cpp history.cpp letterbox.cpp letterboxsettings.cpp lister.cpp movebox.cpp newgame.cpp noteeditor.cpp quacker.cpp quackersettings.cpp rackdisplay.cpp settings.cpp simviewer.cpp view.cpp widgetfactory.cpp oppothread.cpp oppothreadprogressbar.cpp main.cpp
+
+
+win32 {
+ RC_FILE = quacker.rc
+}
+
+win32:!win32-g++ {
+ QMAKE_CFLAGS_DEBUG ~= s/-MDd/-MTd/
+ QMAKE_CXXFLAGS_DEBUG ~= s/-MDd/-MTd/
+ QMAKE_CFLAGS_RELEASE ~= s/-MD/-MT/
+ QMAKE_CXXFLAGS_RELEASE ~= s/-MD/-MT/
+}
+
+macx {
+ DEFINES += FORCE_SECONDARY_ARROW_GLYPHS=1
+}
+
diff --git a/quacker/quacker.rc b/quacker/quacker.rc
new file mode 100644
index 0000000..75700d7
--- /dev/null
+++ b/quacker/quacker.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "quacker.ico"
diff --git a/quacker/quackersettings.cpp b/quacker/quackersettings.cpp
new file mode 100644
index 0000000..cb00045
--- /dev/null
+++ b/quacker/quackersettings.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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 <QtGui>
+
+#include "customqsettings.h"
+#include "quackersettings.h"
+
+QuackerSettings *QuackerSettings::m_self = 0;
+QuackerSettings *QuackerSettings::self()
+{
+ return m_self;
+}
+
+QuackerSettings::QuackerSettings()
+ : britishColoring(TextBritishColoring), verboseLabels(false), scoreLabels(true)
+{
+ m_self = this;
+}
+
+QuackerSettings::~QuackerSettings()
+{
+}
+
+void QuackerSettings::readSettings()
+{
+ CustomQSettings settings;
+ britishColoring = settings.value("quackle/settings/british-coloring", britishColoring).toInt();
+ verboseLabels = settings.value("quackle/settings/verbose-labels", verboseLabels).toBool();
+ scoreLabels = settings.value("quackle/settings/score-labels", scoreLabels).toBool();
+ QuackleIO::UtilSettings::self()->vowelFirst = settings.value("quackle/settings/vowel-first", QuackleIO::UtilSettings::self()->vowelFirst).toBool();
+ QuackleIO::UtilSettings::self()->octothorpBritish = settings.value("quackle/settings/octothorp-british", QuackleIO::UtilSettings::self()->octothorpBritish).toBool();
+
+ m_letterboxSettings.readSettings();
+}
+
+void QuackerSettings::writeSettings()
+{
+ CustomQSettings settings;
+ settings.setValue("quackle/settings/british-coloring", britishColoring);
+ settings.setValue("quackle/settings/verbose-labels", verboseLabels);
+ settings.setValue("quackle/settings/score-labels", scoreLabels);
+ settings.setValue("quackle/settings/vowel-first", QuackleIO::UtilSettings::self()->vowelFirst);
+ settings.setValue("quackle/settings/octothorp-british", QuackleIO::UtilSettings::self()->octothorpBritish);
+
+ m_letterboxSettings.writeSettings();
+}
+
diff --git a/quacker/quackersettings.h b/quacker/quackersettings.h
new file mode 100644
index 0000000..c498485
--- /dev/null
+++ b/quacker/quackersettings.h
@@ -0,0 +1,58 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_QUACKERSETTINGS_H
+#define QUACKER_QUACKERSETTINGS_H
+
+#include <QString>
+
+#include <quackleio/util.h>
+
+#include "letterboxsettings.h"
+
+// This singleton class keeps all Quacker settings in one place.
+// It also maintains one instance of a LetterboxSettings and QuackleIO::UtilSettings.
+
+enum BritishColoring { NoBritishColoring = 0, TextBritishColoring = 1, TileBritishColoring = 2};
+
+class QuackerSettings
+{
+public:
+ QuackerSettings();
+ ~QuackerSettings();
+
+ static QuackerSettings *self();
+
+ // reads and writes Quacker and Letterbox settings
+ void readSettings();
+ void writeSettings();
+
+ int britishColoring;
+ bool verboseLabels;
+ bool scoreLabels;
+
+private:
+ static QuackerSettings *m_self;
+
+ QuackleIO::UtilSettings m_utilSettings;
+ LetterboxSettings m_letterboxSettings;
+};
+
+#endif
diff --git a/quacker/rackdisplay.cpp b/quacker/rackdisplay.cpp
new file mode 100644
index 0000000..d178285
--- /dev/null
+++ b/quacker/rackdisplay.cpp
@@ -0,0 +1,279 @@
+/*
+ * 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 <iostream>
+
+#include <QtGui>
+
+#include <game.h>
+#include <quackleio/util.h>
+
+#include "geometry.h"
+#include "graphicalboard.h"
+#include "rackdisplay.h"
+
+QuickEntryRack::QuickEntryRack(QWidget *parent)
+ : View(parent)
+{
+ QHBoxLayout *textLayout = new QHBoxLayout();
+ Geometry::setupInnerLayout(textLayout);
+
+ m_lineEdit = new QLineEdit;
+ m_lineEdit->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ connect(m_lineEdit, SIGNAL(returnPressed()), this, SLOT(quickEditReturnPressed()));
+
+ QPushButton *setButton = new QPushButton(tr("Set Rack"));
+ connect(setButton, SIGNAL(clicked()), this, SLOT(quickEditReturnPressed()));
+
+ QPushButton *shuffleButton = new QPushButton(tr("Shu&ffle"));
+ connect(shuffleButton, SIGNAL(clicked()), this, SLOT(shuffle()));
+
+ m_label = new QLabel(tr("&Rack"));
+ m_label->setBuddy(m_lineEdit);
+ textLayout->addWidget(m_label, 1);
+ textLayout->addWidget(m_lineEdit, 4);
+ textLayout->addWidget(setButton, 1);
+ textLayout->addWidget(shuffleButton, 1);
+
+ m_tiles = new GraphicalRack;
+
+ QVBoxLayout *layout = new QVBoxLayout;
+ Geometry::setupInnerLayout(layout);
+ layout->addWidget(m_tiles);
+ layout->addLayout(textLayout);
+ setLayout(layout);
+}
+
+QuickEntryRack::~QuickEntryRack()
+{
+}
+
+void QuickEntryRack::positionChanged(const Quackle::GamePosition &position)
+{
+ if (m_rackTiles == position.currentPlayer().rack().tiles())
+ return;
+
+ m_rackTiles = position.currentPlayer().rack().tiles();
+ QString tiles = QuackleIO::Util::letterStringToQString(m_rackTiles);
+ m_lineEdit->setText(tiles);
+ m_label->setText(QString("%1's &rack:").arg(QuackleIO::Util::uvStringToQString(position.currentPlayer().name())));
+ m_tiles->setText(m_rackTiles);
+}
+
+void QuickEntryRack::grabFocus()
+{
+ m_lineEdit->setFocus();
+ m_lineEdit->selectAll();
+}
+
+void QuickEntryRack::quickEditReturnPressed()
+{
+ QString text(m_lineEdit->text());
+ m_lineEdit->clear();
+ processRack(text);
+}
+
+void QuickEntryRack::processRack(const QString &rack)
+{
+ if (rack.isEmpty())
+ {
+ emit statusMessage(tr("Useless rack."));
+ return;
+ }
+
+ emit setRack(QuackleIO::Util::makeRack(rack));
+}
+
+void QuickEntryRack::shuffle()
+{
+ Quackle::Rack rack(QuackleIO::Util::makeRack(m_lineEdit->text()));
+ rack.shuffle();
+
+ emit setRack(rack);
+}
+
+GraphicalRack::GraphicalRack(QWidget * parent)
+ : QFrame(parent)
+{
+ m_layout = new QHBoxLayout(this);
+ Geometry::setupInnerLayout(m_layout);
+ m_layout->addStretch();
+
+ setAcceptDrops(true);
+ setMinimumSize(50, 50);
+}
+
+void
+GraphicalRack::setText(const Quackle::LetterString &text)
+{
+ // clear old labels
+ while(m_layout->count()) {
+ QLabel *label = qobject_cast<QLabel*>(m_layout->itemAt(0)->widget());
+ if (!label) {
+ break;
+ }
+ m_layout->removeWidget(label);
+ label->close();
+ }
+
+ PixmapCacher::self()->invalidate();
+ for (int i = text.size() - 1; i >= 0 ; --i) {
+ QLabel *label = new QLabel;
+ label->setAttribute (Qt::WA_DeleteOnClose);
+
+ TileWidget tile;
+ Quackle::Board::TileInformation info;
+ info.isOnRack = true;
+ info.letter = text[i];
+ info.tileType = Quackle::Board::LetterTile;
+ tile.setInformation(info);
+ tile.setSideLength(50);
+ tile.prepare();
+
+ label->setPixmap(tile.tilePixmap());
+
+ m_layout->insertWidget(0, label);
+ }
+}
+
+const QString GraphicalRack::mime_type = "application/x-quackle-tile";
+
+void
+GraphicalRack::dragEnterEvent (QDragEnterEvent* event)
+{
+ if (event->mimeData()->hasFormat(mime_type))
+ {
+ if (event->source() == this) {
+ event->setDropAction (Qt::MoveAction);
+ event->accept();
+ } else {
+ event->acceptProposedAction();
+ }
+ } else {
+ event->ignore();
+ }
+}
+
+//---------------------------------------------------------------------------
+// dropEvent
+//
+//! The event handler that receives drop events.
+//
+//! @param event the drop event
+//---------------------------------------------------------------------------
+void
+GraphicalRack::dropEvent (QDropEvent* event)
+{
+ if (event->mimeData()->hasFormat (mime_type)) {
+ QByteArray itemData = event->mimeData()->data (mime_type);
+ QDataStream dataStream(&itemData, QIODevice::ReadOnly);
+
+ QPixmap pixmap;
+ QPoint sourcePos;
+ QPoint offset;
+ dataStream >> pixmap >> sourcePos >> offset;
+
+ QLabel* droppedTile = new QLabel;
+ droppedTile->setPixmap(pixmap);
+ droppedTile->setAttribute (Qt::WA_DeleteOnClose);
+
+ QPoint dropPos = event->pos() - offset;
+
+ // Move the tile an extra half tile width in the direction of the
+ // move. This allows the tile to assume a new spot if it is dragged
+ // more than halfway onto the spot.
+ int extraMove = (sourcePos.x() < dropPos.x() ? 50 / 2
+ : -50 / 2);
+
+ dropPos.setX(dropPos.x() + extraMove);
+
+ for(int i = 0; i < m_layout->count(); ++i) {
+ QLabel *label = qobject_cast<QLabel*>(m_layout->itemAt(i)->widget());
+ if (!label) { // hit the stretcher
+ m_layout->insertWidget(i, droppedTile);
+ break;
+ }
+ if (dropPos.x() > label->pos().x()) {
+ continue;
+ }
+ m_layout->insertWidget(i, droppedTile);
+ break;
+ }
+
+ if (event->source() == this) {
+ event->setDropAction (Qt::MoveAction);
+ event->accept();
+ } else {
+ event->acceptProposedAction();
+ }
+ }
+ else {
+ event->ignore();
+ }
+}
+
+//---------------------------------------------------------------------------
+// mousePressEvent
+//
+//! The event handler that receives mouse press events.
+//
+//! @param event the mouse press event
+//---------------------------------------------------------------------------
+void
+GraphicalRack::mousePressEvent (QMouseEvent* event)
+{
+ QLabel* child = qobject_cast<QLabel*>(childAt (event->pos()));
+ if (!child)
+ return;
+
+ QPixmap pixmap = *(child->pixmap());
+
+ QByteArray itemData;
+ QDataStream dataStream (&itemData, QIODevice::WriteOnly);
+ dataStream << pixmap << QPoint (event->pos())
+ << QPoint (event->pos() - child->pos());
+
+ QMimeData *mimeData = new QMimeData;
+ mimeData->setData (mime_type, itemData);
+
+ QDrag *drag = new QDrag (this);
+ drag->setMimeData (mimeData);
+ drag->setPixmap (pixmap);
+ drag->setHotSpot (event->pos() - child->pos());
+
+ QColor bgColor = palette().color(QPalette::Window);
+
+ QPixmap tempPixmap = pixmap;
+ QPainter painter;
+ painter.begin (&tempPixmap);
+ painter.fillRect(pixmap.rect(), QColor(bgColor.red(), bgColor.green(),
+ bgColor.blue(), 127));
+ painter.end();
+
+ child->setPixmap (tempPixmap);
+
+ if (drag->start (Qt::CopyAction | Qt::MoveAction) == Qt::MoveAction) {
+ child->close();
+ }
+ else {
+ child->show();
+ child->setPixmap (pixmap);
+ }
+}
diff --git a/quacker/rackdisplay.h b/quacker/rackdisplay.h
new file mode 100644
index 0000000..780b67f
--- /dev/null
+++ b/quacker/rackdisplay.h
@@ -0,0 +1,80 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_RACKDISPLAY_H
+#define QUACKER_RACKDISPLAY_H
+
+#include "view.h"
+#include "alphabetparameters.h"
+
+class QFont;
+class QLabel;
+class QLineEdit;
+class QHBoxLayout;
+class GraphicalRack;
+class RackTileWidget;
+
+class QuickEntryRack : public View
+{
+Q_OBJECT
+
+public:
+ QuickEntryRack(QWidget *parent = 0);
+ virtual ~QuickEntryRack();
+
+public slots:
+ virtual void positionChanged(const Quackle::GamePosition &position);
+ virtual void grabFocus();
+
+private slots:
+ void quickEditReturnPressed();
+ void shuffle();
+
+protected:
+ virtual void processRack(const QString &rack);
+
+private:
+ QLabel *m_label;
+ QLineEdit *m_lineEdit;
+ GraphicalRack *m_tiles;
+ Quackle::LetterString m_rackTiles;
+};
+
+class GraphicalRack : public QFrame
+{
+ Q_OBJECT
+
+public:
+ GraphicalRack(QWidget * parent = 0);
+
+public slots:
+ virtual void setText(const Quackle::LetterString &text);
+
+protected:
+ void dragEnterEvent (QDragEnterEvent* event);
+ void dropEvent (QDropEvent* event);
+ void mousePressEvent (QMouseEvent* event);
+
+private:
+ QHBoxLayout *m_layout;
+ static const QString mime_type;
+};
+
+#endif
diff --git a/quacker/settings.cpp b/quacker/settings.cpp
new file mode 100644
index 0000000..ae85709
--- /dev/null
+++ b/quacker/settings.cpp
@@ -0,0 +1,377 @@
+/*
+ * 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 <iostream>
+#include <sstream>
+
+#include <QtGui>
+#include <QMessageBox>
+
+#include <alphabetparameters.h>
+#include <board.h>
+#include <boardparameters.h>
+#include <computerplayercollection.h>
+#include <datamanager.h>
+#include <game.h>
+#include <lexiconparameters.h>
+#include <rack.h>
+#include <strategyparameters.h>
+
+#include <quackleio/flexiblealphabet.h>
+#include <quackleio/util.h>
+
+#include "settings.h"
+#include "boardsetupdialog.h"
+#include "customqsettings.h"
+#include "graphicalboard.h"
+
+Settings *Settings::m_self = 0;
+Settings *Settings::self()
+{
+ return m_self;
+}
+
+Settings::Settings(QWidget *parent)
+ : QWidget(parent), m_lexiconNameCombo(0), m_alphabetNameCombo(0)
+{
+ m_self = this;
+}
+
+void Settings::createGUI()
+{
+ if (m_lexiconNameCombo != 0)
+ return;
+
+ QVBoxLayout *vlayout = new QVBoxLayout(this);
+
+ m_lexiconNameCombo = new QComboBox;
+ connect(m_lexiconNameCombo, SIGNAL(activated(const QString &)), this, SLOT(lexiconChanged(const QString &)));
+
+ QStringList items;
+ items << "csw12" << "cswapr07" << "sowpods" << "twl06" << "twl98" << "ods5" << "korean" << "greek";
+ m_lexiconNameCombo->addItems(items);
+
+ 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_alphabetNameCombo = new QComboBox;
+ connect(m_alphabetNameCombo, SIGNAL(activated(const QString &)), this, SLOT(alphabetChanged(const QString &)));
+
+ QStringList alphabetItems;
+ //alphabetItems << "english" << "english_super" << "french" << "korean" << "greek" << "mandarin" << "zhuyin" << "pinyin";
+ alphabetItems << "english" << "english_super" << "french" << "korean" << "greek";
+ m_alphabetNameCombo->addItems(alphabetItems);
+
+ QHBoxLayout *alphabetLayout = new QHBoxLayout;
+ QLabel *alphabetNameLabel = new QLabel(tr("&Alphabet:"));
+ alphabetNameLabel->setBuddy(m_alphabetNameCombo);
+
+ alphabetLayout->addWidget(alphabetNameLabel);
+ alphabetLayout->addWidget(m_alphabetNameCombo);
+
+ 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()));
+
+ m_editBoard = new QPushButton(tr("&Edit Board"));
+ connect(m_editBoard, SIGNAL(clicked()), this, SLOT(editBoard()));
+
+ m_deleteBoard = new QPushButton(tr("&Delete Board"));
+ connect(m_deleteBoard, SIGNAL(clicked()), this, SLOT(deleteBoard()));
+
+ loadBoardNameCombo();
+
+ QGroupBox *boardGroup = new QGroupBox("Game Board Definitions");
+ QGridLayout *boardLayout = new QGridLayout(boardGroup);
+ QLabel *boardNameLabel = new QLabel(tr("&Board:"));
+ boardNameLabel->setBuddy(m_boardNameCombo);
+
+ 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->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_boardNameCombo->setCurrentIndex(m_boardNameCombo->findText(QuackleIO::Util::uvStringToQString(QUACKLE_BOARD_PARAMETERS->name())));
+}
+
+void Settings::preInitialize()
+{
+ // load computer players
+ QUACKLE_DATAMANAGER->setComputerPlayers(Quackle::ComputerPlayerCollection::fullCollection());
+}
+
+void Settings::initialize()
+{
+ CustomQSettings settings;
+
+ QUACKLE_DATAMANAGER->setBackupLexicon("twl06");
+
+ if (QFile::exists("data"))
+ QUACKLE_DATAMANAGER->setDataDirectory("data");
+ else if (QFile::exists("../data"))
+ QUACKLE_DATAMANAGER->setDataDirectory("../data");
+ else if (QFile::exists("Quackle.app/Contents/data"))
+ QUACKLE_DATAMANAGER->setDataDirectory("Quackle.app/Contents/data");
+
+ else
+ {
+ QDir directory = QFileInfo(qApp->arguments().at(0)).absoluteDir();
+ if (directory.cd("data"))
+ QUACKLE_DATAMANAGER->setDataDirectory(directory.absolutePath().toStdString());
+ else
+ 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>"));
+ }
+
+ QString lexiconName = settings.value("quackle/settings/lexicon-name", QString("twl06")).toString();
+
+ // Handle Collins update.
+ if (lexiconName == "cswfeb07")
+ lexiconName = "cswapr07";
+
+ setQuackleToUseLexiconName(QuackleIO::Util::qstringToStdString(lexiconName));
+ setQuackleToUseAlphabetName(QuackleIO::Util::qstringToStdString(settings.value("quackle/settings/alphabet-name", QString("english")).toString()));
+ setQuackleToUseBoardName(settings.value("quackle/settings/board-name", QString("")).toString());
+}
+
+void Settings::setQuackleToUseLexiconName(const string &lexiconName)
+{
+ if (QUACKLE_LEXICON_PARAMETERS->lexiconName() != lexiconName)
+ {
+ QUACKLE_LEXICON_PARAMETERS->setLexiconName(lexiconName);
+
+ 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);
+
+ string dawgFile = Quackle::LexiconParameters::findDictionaryFile(lexiconName + ".dawg");
+ if (dawgFile.empty())
+ {
+ UVcout << "Dawg for lexicon '" << lexiconName << "' does not exist." << endl;
+ QUACKLE_LEXICON_PARAMETERS->unloadDawg();
+ }
+ else
+ QUACKLE_LEXICON_PARAMETERS->loadDawg(dawgFile);
+
+ QUACKLE_STRATEGY_PARAMETERS->initialize(lexiconName);
+ }
+}
+
+void Settings::setQuackleToUseAlphabetName(const string &alphabetName)
+{
+ if (QUACKLE_ALPHABET_PARAMETERS->alphabetName() != alphabetName)
+ {
+ QString alphabetFile = QuackleIO::Util::stdStringToQString(Quackle::AlphabetParameters::findAlphabetFile(alphabetName + ".quackle_alphabet"));
+
+ QuackleIO::FlexibleAlphabetParameters *flexure = new QuackleIO::FlexibleAlphabetParameters;
+ flexure->setAlphabetName(alphabetName);
+ if (flexure->load(alphabetFile))
+ {
+ if (flexure->length() != QUACKLE_ALPHABET_PARAMETERS->length() && QUACKLE_ALPHABET_PARAMETERS->alphabetName() != "default")
+ {
+ QMessageBox::warning(this, tr("Alphabet mismatch - Quackle"), QString("<html>%1</html>").arg(tr("%1 has a different number of letters than %2, so please start a new game or else Quackle will crash or act strangely.").arg(QuackleIO::Util::stdStringToQString(flexure->alphabetName())).arg(QuackleIO::Util::stdStringToQString(QUACKLE_ALPHABET_PARAMETERS->alphabetName()))));
+ }
+
+ QUACKLE_DATAMANAGER->setAlphabetParameters(flexure);
+ }
+ else
+ {
+ UVcerr << "Couldn't load alphabet!" << endl;
+ delete flexure;
+ }
+ }
+}
+
+void Settings::setQuackleToUseBoardName(const QString &boardName)
+{
+ CustomQSettings settings;
+ settings.beginGroup("quackle/boardparameters");
+
+ if (boardName.isEmpty() || !settings.contains(boardName))
+ QUACKLE_DATAMANAGER->setBoardParameters(new Quackle::BoardParameters());
+ else
+ {
+ QByteArray boardParameterBytes = qUncompress(settings.value(boardName).toByteArray());
+ string boardParameterBuf;
+ boardParameterBuf.assign((const char *) boardParameterBytes, boardParameterBytes.size());
+ istringstream boardParameterStream(boardParameterBuf);
+
+ QUACKLE_DATAMANAGER->setBoardParameters(Quackle::BoardParameters::Deserialize(boardParameterStream));
+ }
+ QUACKLE_BOARD_PARAMETERS->setName(QuackleIO::Util::qstringToString(boardName));
+ loadBoardNameCombo();
+}
+
+void Settings::lexiconChanged(const QString &lexiconName)
+{
+ string lexiconNameString = QuackleIO::Util::qstringToStdString(lexiconName);
+ setQuackleToUseLexiconName(lexiconNameString);
+
+ CustomQSettings settings;
+ settings.setValue("quackle/settings/lexicon-name", lexiconName);
+
+ emit refreshViews();
+}
+
+void Settings::alphabetChanged(const QString &alphabetName)
+{
+ string alphabetNameString = QuackleIO::Util::qstringToStdString(alphabetName);
+ setQuackleToUseAlphabetName(alphabetNameString);
+
+ CustomQSettings settings;
+ settings.setValue("quackle/settings/alphabet-name", alphabetName);
+
+ emit refreshViews();
+}
+
+void Settings::boardChanged(const QString &boardName)
+{
+ CustomQSettings settings;
+ settings.setValue("quackle/settings/board-name", boardName);
+
+ setQuackleToUseBoardName(boardName);
+ emit refreshViews();
+}
+
+void Settings::addBoard()
+{
+ QUACKLE_DATAMANAGER->setBoardParameters(new Quackle::BoardParameters());
+ QUACKLE_BOARD_PARAMETERS->setName(MARK_UV(""));
+
+ CustomQSettings settings;
+ BoardSetupDialog dialog(this);
+ if (dialog.exec())
+ {
+ QString boardName = QuackleIO::Util::uvStringToQString(QUACKLE_BOARD_PARAMETERS->name());
+ settings.beginGroup("quackle/boardparameters");
+
+ ostringstream boardParameterStream;
+ QUACKLE_BOARD_PARAMETERS->Serialize(boardParameterStream);
+
+ QByteArray boardParameterBytes = qCompress(
+ (const uchar *)boardParameterStream.str().data(),
+ boardParameterStream.str().size());
+ settings.setValue(boardName, QVariant(boardParameterBytes));
+ boardChanged(boardName);
+ }
+ else
+ setQuackleToUseBoardName(settings.value("quackle/settings/board-name", QString("")).toString());
+
+ PixmapCacher::self()->invalidate();
+}
+
+void Settings::editBoard()
+{
+ QString oldBoardName = m_boardNameCombo->currentText();
+ QUACKLE_BOARD_PARAMETERS->setName(QuackleIO::Util::qstringToString(oldBoardName));
+
+ BoardSetupDialog dialog(this);
+ if (dialog.exec())
+ {
+ QString newBoardName = QuackleIO::Util::uvStringToQString(QUACKLE_BOARD_PARAMETERS->name());
+ CustomQSettings settings;
+ settings.beginGroup("quackle/boardparameters");
+
+ if (newBoardName != oldBoardName)
+ settings.remove(oldBoardName);
+
+ ostringstream boardParameterStream;
+ QUACKLE_BOARD_PARAMETERS->Serialize(boardParameterStream);
+
+ QByteArray boardParameterBytes = qCompress(
+ (const char *)boardParameterStream.str().data(),
+ boardParameterStream.str().size());
+ settings.setValue(newBoardName, QVariant(boardParameterBytes));
+ 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());
+ }
+}
+
+void Settings::loadBoardNameCombo()
+{
+ if (m_lexiconNameCombo == 0)
+ return;
+
+ while (m_boardNameCombo->count() > 0)
+ m_boardNameCombo->removeItem(0);
+
+ CustomQSettings settings;
+ settings.beginGroup("quackle/boardparameters");
+ QStringList boardNames = settings.childKeys();
+ boardNames.sort();
+ m_boardNameCombo->addItems(boardNames);
+ settings.endGroup();
+
+ QString currentItem = settings.value("quackle/settings/board-name", QString("")).toString();
+ m_boardNameCombo->setCurrentIndex(m_boardNameCombo->findText(currentItem));
+
+ m_editBoard->setEnabled(boardNames.count() > 0);
+ m_deleteBoard->setEnabled(boardNames.count() > 0);
+}
+
diff --git a/quacker/settings.h b/quacker/settings.h
new file mode 100644
index 0000000..d741921
--- /dev/null
+++ b/quacker/settings.h
@@ -0,0 +1,89 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_SETTINGS_H
+#define QUACKER_SETTINGS_H
+
+#include <string>
+
+#include <QWidget>
+#include <QSettings>
+
+class QComboBox;
+class QCheckBox;
+class QPushButton;
+
+using namespace std;
+
+class Settings : public QWidget
+{
+Q_OBJECT
+
+public:
+ Settings(QWidget *parent = 0);
+
+ static Settings *self();
+
+signals:
+ void refreshViews();
+
+public slots:
+ // called before anything else to initialize quackle generally
+ void preInitialize();
+
+ // called to set up libquackle data structures and our internal
+ // data structures based on stored user settings
+ void initialize();
+
+ // called to set widgets to display current settings based
+ // on libquackle data structures and our internal data structures
+ void load();
+
+ void createGUI();
+
+protected slots:
+ void lexiconChanged(const QString &lexiconName);
+ void alphabetChanged(const QString &alphabetName);
+ void boardChanged(const QString &boardName);
+
+ void addBoard();
+ void editBoard();
+ void deleteBoard();
+
+ void setQuackleToUseLexiconName(const string &lexiconName);
+ void setQuackleToUseAlphabetName(const string &alphabetName);
+ void setQuackleToUseBoardName(const QString &lexiconName);
+
+protected:
+ QComboBox *m_lexiconNameCombo;
+ QComboBox *m_alphabetNameCombo;
+ QComboBox *m_boardNameCombo;
+ QPushButton *m_addBoard;
+ QPushButton *m_editBoard;
+ QPushButton *m_deleteBoard;
+
+private:
+ // populate the popup based on what's in QSettings
+ void loadBoardNameCombo();
+
+ static Settings *m_self;
+};
+
+#endif
diff --git a/quacker/simviewer.cpp b/quacker/simviewer.cpp
new file mode 100644
index 0000000..359dc4a
--- /dev/null
+++ b/quacker/simviewer.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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 <iostream>
+
+#include <QtGui>
+
+#include <quackleio/util.h>
+
+#include "simviewer.h"
+
+SimViewer::SimViewer(QWidget *parent)
+ : QDialog(parent)
+{
+ m_tabs = new QTabWidget;
+
+ m_averagesTab = new AveragesTab;
+ m_tabs->addTab(m_averagesTab, tr("&Averages"));
+
+ QPushButton *closeButton = new QPushButton(tr("&Close"));
+ closeButton->setDefault(true);
+
+ connect(closeButton, SIGNAL(clicked()), this, SLOT(done()));
+
+ QHBoxLayout *buttonLayout = new QHBoxLayout;
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(closeButton);
+
+ QVBoxLayout *topLayout = new QVBoxLayout;
+ topLayout->addWidget(m_tabs);
+ topLayout->addLayout(buttonLayout);
+ setLayout(topLayout);
+
+ setWindowTitle(tr("Simulation Results - Quackle"));
+}
+
+void SimViewer::setSimulator(const Quackle::Simulator &simulator)
+{
+ m_averagesTab->setSimulator(simulator);
+ setWindowTitle(tr("%1 iterations of %2 - Quackle").arg(simulator.iterations()).arg(QuackleIO::Util::letterStringToQString(simulator.currentPosition().currentPlayer().rack().tiles())));
+}
+
+void SimViewer::done()
+{
+ accept();
+}
+
+/////////////
+
+AveragesTab::AveragesTab(QWidget *parent)
+ : QWidget(parent)
+{
+ QVBoxLayout *topLayout = new QVBoxLayout(this);
+
+ m_textEdit = new QTextEdit;
+ m_textEdit->setReadOnly(true);
+
+ QPushButton *explainButton = new QPushButton(tr("&Explain me!"));
+ connect(explainButton, SIGNAL(clicked()), this, SLOT(explain()));
+
+ topLayout->addWidget(m_textEdit);
+ //topLayout->addWidget(explainButton);
+}
+
+void AveragesTab::setSimulator(const Quackle::Simulator &simulator)
+{
+ QString html;
+
+ html += statisticTable(simulator);
+
+ html += "<hr />";
+
+ const Quackle::SimmedMoveList::const_iterator end(simulator.simmedMoves().end());
+ for (Quackle::SimmedMoveList::const_iterator it = simulator.simmedMoves().begin(); it != end; ++it)
+ {
+ if (!(*it).includeInSimulation())
+ continue;
+
+ QString levels;
+ for (Quackle::LevelList::const_iterator levelIt = (*it).levels.begin(); levelIt != (*it).levels.end(); ++levelIt)
+ {
+ QString plays;
+ for (Quackle::PositionStatisticsList::const_iterator valueIt = (*levelIt).statistics.begin(); valueIt != (*levelIt).statistics.end(); ++valueIt)
+ {
+ //plays += QString("(%1) ").arg((*valueIt).score.averagedValue());
+ //plays += tr("(bingos %1) ").arg((*valueIt).bingos.averagedValue());
+ }
+
+ if (!plays.isEmpty())
+ levels += QString("<li>%1</li>").arg(plays);
+ }
+
+ html += QString("<h3>%1</h3><ol>%2</ol>").arg(QuackleIO::Util::moveToDetailedString((*it).move)).arg(levels);
+
+ html += "<ul>";
+ if ((*it).residual.hasValues())
+ html += tr("<li>Rack leftover value: %1</li>").arg((*it).residual.averagedValue());
+ if ((*it).gameSpread.hasValues())
+ html += tr("<li>Spread: %1 (sd %2)</li>").arg((*it).gameSpread.averagedValue()).arg((*it).gameSpread.standardDeviation());
+ html += tr("<li>Valuation: %1</li>").arg((*it).calculateEquity());
+ html += tr("<li>Bogowin %: %1%</li>").arg((*it).calculateWinPercentage());
+ html += "</ul>";
+ }
+
+ // TODO don't scroll to top when resetting
+ m_textEdit->setHtml(html);
+}
+
+QString AveragesTab::statisticTable(const Quackle::Simulator &simulator)
+{
+ QString ret;
+ for (int levelIndex = 0; levelIndex < simulator.numLevels(); ++levelIndex)
+ {
+ for (int playerIndex = 0; playerIndex < simulator.numPlayersAtLevel(levelIndex); ++playerIndex)
+ {
+ if (levelIndex == 0 && playerIndex == 0)
+ continue;
+
+ const Quackle::SimmedMoveList::const_iterator end(simulator.simmedMoves().end());
+
+ // Little bit of fudgery so that the turn after our next turn is #2,
+ // and turn after oppo's next turn is also #2.
+ const int turnNumber = levelIndex + (playerIndex == 0? 0 : 1);
+ QString name = tr("%1 turn #%2").arg(playerIndex == 0? "Our" : "Oppo").arg(turnNumber);
+ if (playerIndex == 1 && levelIndex == 0)
+ {
+ name = tr("Oppo next turn");
+ }
+ else if (playerIndex == 0 && levelIndex == 1)
+ {
+ name = tr("Our next turn");
+ }
+ ret += QString("<h2>%1</h2>").arg(name);
+
+ ret += "<table border=0 cellspacing=4>";
+ ret += tr("<tr><th>Candidate</th><th>Score</th><th>Std. Dev.</th><th>Bingo %</th></tr>");
+ for (Quackle::SimmedMoveList::const_iterator it = simulator.simmedMoves().begin(); it != end; ++it)
+ {
+ if (!(*it).includeInSimulation())
+ continue;
+
+ ret += "<tr>";
+ ret += tr("<td>%1</td>").arg(QuackleIO::Util::moveToDetailedString((*it).move));
+
+ Quackle::AveragedValue value = (*it).getPositionStatistics(levelIndex, playerIndex).getStatistic(Quackle::PositionStatistics::StatisticScore);
+ Quackle::AveragedValue bingos = (*it).getPositionStatistics(levelIndex, playerIndex).getStatistic(Quackle::PositionStatistics::StatisticBingos);
+
+ ret += QString("<td>%1</td><td>%2</td>").arg(value.averagedValue()).arg(value.standardDeviation());
+ ret += QString("<td>%1</td>").arg(bingos.averagedValue() * 100.0);
+ ret += "</tr>";
+ }
+ ret += "</table>";
+ }
+ }
+
+ return ret;
+}
+
+void AveragesTab::explain()
+{
+ QMessageBox::information(this, tr("Simulation Details Explanation - Quackle"), tr("<p>A Quackle 2-ply simulation first puts our candidate play on the board, then has opponents make their best plays based on static evaluation, and has us make our best response based on static evaluation. So</p><ol><li>(28 [sd 0]) (39.1503 [sd 17.8229])</li><li>(45.1284 [sd 21.0234])</li></ol><p>means we're looking at a candidate play scoring 28. The average oppo response scored 39.2 with standard deviation 17.8. Our average riposte scored 45.1 with standard deviation 21.0.</p><ul><li>The residual value is the average leave value of our rack at the end of simulation minus the summed average leave value of oppo's racks.</li><li>The spread is the average differential between our score and the leading player's score at the end of an iteration.</li><li>The fake win percentage is how often we had a positive spread at the end of the iteration.</li></ul><p>Note that odd-plied simulations are fun to try occasionally. For example, a 3-ply simulation has two of our plays (including the candidate) and two of each oppo's plays. Also try the <i>Oppos pass</i> option, which has oppos pass for all of their turns in the simulation.</p>"));
+}
+
+QSize SimViewer::sizeHint() const
+{
+ return QSize(400, 400);
+}
+
diff --git a/quacker/simviewer.h b/quacker/simviewer.h
new file mode 100644
index 0000000..d419ee8
--- /dev/null
+++ b/quacker/simviewer.h
@@ -0,0 +1,70 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_SIMVIEWER_H
+#define QUACKER_SIMVIEWER_H
+
+#include <QDialog>
+
+#include <sim.h>
+
+class QPushButton;
+class QTabWidget;
+class QTextEdit;
+class AveragesTab;
+
+class SimViewer : public QDialog
+{
+Q_OBJECT
+
+public:
+ SimViewer(QWidget *parent = 0);
+
+ virtual QSize sizeHint() const;
+
+public slots:
+ void setSimulator(const Quackle::Simulator &simulator);
+
+ void done();
+
+private:
+ QTabWidget *m_tabs;
+ AveragesTab *m_averagesTab;
+};
+
+class AveragesTab : public QWidget
+{
+Q_OBJECT
+
+public:
+ AveragesTab(QWidget *parent = 0);
+
+public slots:
+ void setSimulator(const Quackle::Simulator &simulator);
+
+ QString statisticTable(const Quackle::Simulator &simulator);
+
+ void explain();
+
+private:
+ QTextEdit *m_textEdit;
+};
+
+#endif
diff --git a/quacker/uisuggestions.txt b/quacker/uisuggestions.txt
new file mode 100644
index 0000000..7e4ecc3
--- /dev/null
+++ b/quacker/uisuggestions.txt
@@ -0,0 +1,51 @@
+By Jeff Walden <jwalden@mit.edu>
+
+Pitting two "slow sucky simulating computer" players against each other effectively results in a hang; the UI can only barely be called usable.
+
+After removing plays from the list of play choices, no play is selected (and thus I have to do mad tabbing in order to remove more plays).
+
+Delete doesn't remove the currently-selected play or plays from the list of choices.
+
+The same play may be included in the list of choices twice (cf. VIM/FADEIN).
+
+Pressing space bar while focus is on a button doesn't push the button.
+
+Settings should be in a separate dialog box accessible thru menus. Most users are unlikely to change the board after initially setting it, so consequently it's not something that should be displayed in the main UI. (I have further ideas for what a board-editing interface should look like; I'll draw something up later and hopefully look into writing some code to make it if I can find the time. I really should learn how to use Qt sometime, and this seems like it might be a good way to do so.)
+
+The Enter/Commit distinction in Quackle is clumsy; I'm still working on a good (not just better) solution for this. For now, however, I think you can get rid of the Enter part by just updating the list of choices dynamically as (valid) plays are typed.
+
+The generated plaintext boards may not be amenable to use in HTML email -- insert . for a blank space?
+
+Application icon so no generic icon in Windows?
+
+Regarding premium square coloring/labeling -- probably don't offer labeling but instead offer a hideable legend; I'm unsure whether color customization is a worthwhile endeavor, but I suspect it isn't.
+
+The score for the current move needs to be displayed more prominently than within a tree in the choices list. A move's score and the leave are the prime variables in determining whether to make a play. The leave of the play is intuitive and doesn't really need to be displayed, because the player can work back to it with ease. However, calculating the score requires substantial effort from the player, and the process is fairly error-prone even for experienced players. Additionally, since the score is only displayed in the choices list, it requires the user to see Quackle's valuation of the play even if he explicitly doesn't want to see it (because, say, he's playing a serious game).
+
+Should player names really be changeable? As Quackle exists now it's just a cosmetic detail (cf. Maven ratings for a non-cosmetic use of names), and because some number of users will feel obligated to fill it out just because it's there, it'll unnecessarily slow down creation of new games.
+
+Starting a new game shouldn't require displaying a New Game dialog, in general. The current method works well in the general case of n players, each of which follows one of four different models. However, this isn't how anyone actually uses "computer crossword games". There are really two different models which cover 95% of all uses: human vs. computer and human vs. human (i.e., poor man's postmortem mode). The other possibilities are all useful at various times and in various situations, but realistically there just isn't enough demand for playing the computer against itself to make this easily accessible in the main new-game UI. Instead, the following menu items should be added to the Game menu: New Game Against Computer (Ctrl+N), New Game Against Human (Ctrl+H), and New Custom Game... (Ctrl+Shift+N). The menu item titles aren't necessarily the best titles imaginable, but the idea is to make accessing the most common new-game scenarios easy and to hide the complexity of accessing the uncommon scenarios so it doesn't make using the most useful scenarios harder.
+
+The explanation of simulation details is complete gobbledygook to anyone who doesn't already understand simulations. I'll take a go at rewriting the text sometime.
+
+The location of the start/stop simulation button isn't good because it's so far from the simulation details area. Typically the user is going to remove some nonzero number of choices from the list of choices before running a simulation, so his attention (and often mouse) will be in the area at this time. Actually running the simulation then requires an attention (and often mouse) shift which is much slower than just including the simulate button in that area.
+
+The simulate UI feels crufty overall; I'm trying to come up with a good, useful description of changes that can be made to it so that it's better. Any changes to it that I mention here are at best incremental changes, and I suspect they could be totally overridden at some point in future UI recommendations.
+
+I can type in the empty boxes in the History tab!
+
+Why does hitting Enter when a history box is selected reflow the entire history tree?
+
+Is About Qt legally required? It's totally useless to any real users, so if it's not legally required it should be removed to make accessing functionality in the Help menu a tad bit faster.
+
+A possibly hard modification: stop the simulation when all possible move combinations have been exhausted. I can simulate an endgame situation for thousands of totally repetitive iterations if the number of tiles left on racks and in the bag is, say, 14 or fewer; it's extremely obvious that nothing's happening when the number of tiles left is below 7.
+
+There's no quick way to check whether or not a letter string is a word without attempting to play it. A not-totally-fleshed-out suggestion on how to fix this: hook up Ctrl+F to display a Firefox-style find bar across the bottom of Quackle and focus a textbox where the letter string to check can go, and highlight the textbox light green when the letters in the box are a valid letter string and red when the letters aren't a valid letter string (i.e., use a validate-as-you-type model) and display VALID or INVALID after the textbox as the letter string is displayed. (Note: the colors mentioned here are just examples; I'd have to play with them to see what background and text colors actually work best.)
+
+General considerations: hide complexity as much as possible. Use menus to hide not-often-used functionality, and make that functionality available via keyboard shortcuts for those who really want to use it all the time.
+
+Alert boxes suck for communicating information. Majorly.
+
+Help documentation: at a minimum, document all the keyboard shortcuts now while you haven't forgotten them, and make that documentation accessible via the Help menu.
+
+617 319 1300
diff --git a/quacker/view.cpp b/quacker/view.cpp
new file mode 100644
index 0000000..8f4e2c9
--- /dev/null
+++ b/quacker/view.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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 "view.h"
+
+BaseView::BaseView(QWidget *parent)
+ : QFrame(parent)
+{
+}
+
+BaseView::~BaseView()
+{
+}
+
+/////////
+
+View::View(QWidget *parent)
+ : BaseView(parent)
+{
+}
+
+View::~View()
+{
+}
+
+void View::grabFocus()
+{
+}
+
+void View::positionChanged(const Quackle::GamePosition &position)
+{
+ for (QList<View *>::iterator it = m_subviews.begin(); it != m_subviews.end(); ++it)
+ (*it)->positionChanged(position);
+}
+
+void View::movesChanged(const Quackle::MoveList &moves)
+{
+ for (QList<View *>::iterator it = m_subviews.begin(); it != m_subviews.end(); ++it)
+ (*it)->movesChanged(moves);
+}
+
+void View::connectSubviewSignals()
+{
+ for (QList<View *>::iterator it = m_subviews.begin(); it != m_subviews.end(); ++it)
+ {
+ connect(*it, SIGNAL(statusMessage(const QString &)), this, SIGNAL(statusMessage(const QString &)));
+ connect(*it, SIGNAL(setCandidateMove(const Quackle::Move &)), this, SIGNAL(setCandidateMove(const Quackle::Move &)));
+ connect(*it, SIGNAL(removeCandidateMoves(const Quackle::MoveList &)), this, SIGNAL(removeCandidateMoves(const Quackle::MoveList &)));
+ connect(*it, SIGNAL(commit()), this, SIGNAL(commit()));
+ connect(*it, SIGNAL(setRack(const Quackle::Rack &)), this, SIGNAL(setRack(const Quackle::Rack &)));
+ }
+}
+
+/////////
+
+HistoryView::HistoryView(QWidget *parent)
+ : BaseView(parent)
+{
+}
+
+HistoryView::~HistoryView()
+{
+}
+
+void HistoryView::historyChanged(const Quackle::History & /* history */)
+{
+}
+
diff --git a/quacker/view.h b/quacker/view.h
new file mode 100644
index 0000000..ba43e8f
--- /dev/null
+++ b/quacker/view.h
@@ -0,0 +1,119 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_VIEW_H
+#define QUACKER_VIEW_H
+
+#include <QFrame>
+#include <QList>
+
+#include <alphabetparameters.h>
+
+namespace Quackle
+{
+ class GamePosition;
+ class History;
+ class HistoryLocation;
+ class Move;
+ class MoveList;
+ class Rack;
+}
+
+class BaseView : public QFrame
+{
+Q_OBJECT
+
+public:
+ BaseView(QWidget *parent = 0);
+ virtual ~BaseView();
+
+signals:
+ // tell user of a message, usually via status bar
+ void statusMessage(const QString &message);
+};
+
+class View : public BaseView
+{
+Q_OBJECT
+
+public:
+ View(QWidget *parent = 0);
+ virtual ~View();
+
+signals:
+ // emit to alert the rest of the application to show this
+ // as candidate move - may eventually trigger positionChanged
+ // in response
+ void setCandidateMove(const Quackle::Move &move);
+ void removeCandidateMoves(const Quackle::MoveList &moves);
+ void commit();
+
+ // emit to alert the rest of the application to reset the current
+ // player's rack - may eventually trigger positionChanged
+ // in response
+ void setRack(const Quackle::Rack &rack);
+
+ // Sets the current position's explanatory note.
+ // Does *not* alert the rest of the application.
+ void setNote(const UVString &note);
+
+public slots:
+ // called whenever game position changes; the board only changes when a
+ // move is made, but the candidate move (accessible from position.moveMade(),
+ // and the resulting board from position.boardAfterMoveMade()) change alone too.
+ // This is called in both cases.
+ //
+ // The default implementation calls positionChanged(position) for all subviews
+ // in m_subviews.
+ virtual void positionChanged(const Quackle::GamePosition &position);
+
+ // called when user starts a simulation and this move list should
+ // supercede that from the position
+ virtual void movesChanged(const Quackle::MoveList &moves);
+
+ virtual void grabFocus();
+
+protected:
+ // keep a list of View subclasses in m_subviews
+ // and call this so their signals are emitted from this object
+ void connectSubviewSignals();
+ QList<View *> m_subviews;
+};
+
+class HistoryView : public BaseView
+{
+Q_OBJECT
+
+public:
+ HistoryView(QWidget *parent = 0);
+ virtual ~HistoryView();
+
+signals:
+ // emit to alert the rest of the application to show this
+ // as candidate move - may eventually trigger positionChanged
+ // in response
+ void goToHistoryLocation(const Quackle::HistoryLocation &location);
+
+public slots:
+ // called whenever history is added to
+ virtual void historyChanged(const Quackle::History &history);
+};
+
+#endif
diff --git a/quacker/widgetfactory.cpp b/quacker/widgetfactory.cpp
new file mode 100644
index 0000000..a50636d
--- /dev/null
+++ b/quacker/widgetfactory.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "bagdisplay.h"
+#include "boarddisplay.h"
+#include "graphicalboard.h"
+#include "boardsetup.h"
+#include "rackdisplay.h"
+#include "widgetfactory.h"
+
+View *TextFactory::createBoardDisplay()
+{
+ return new TextBoard;
+}
+
+View *TextFactory::createRackDisplay()
+{
+ return new QuickEntryRack;
+}
+
+View *TextFactory::createBagDisplay()
+{
+ return new BagDisplay;
+}
+
+View *GraphicalFactory::createBoardDisplay()
+{
+ return new GraphicalBoard;
+}
+
+View *GraphicalFactory::createRackDisplay()
+{
+ return new QuickEntryRack;
+}
+
+View *GraphicalFactory::createBagDisplay()
+{
+ return new BagDisplay;
+}
+
+View *BoardSetupFactory::createBoardDisplay()
+{
+ return new BoardSetup;
+}
+
+View *BoardSetupFactory::createRackDisplay()
+{
+ return new View;
+}
+
+View *BoardSetupFactory::createBagDisplay()
+{
+ return new View;
+}
+
diff --git a/quacker/widgetfactory.h b/quacker/widgetfactory.h
new file mode 100644
index 0000000..288863b
--- /dev/null
+++ b/quacker/widgetfactory.h
@@ -0,0 +1,62 @@
+/*
+ * 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
+ */
+
+#ifndef QUACKER_WIDGETFACTORY_H
+#define QUACKER_WIDGETFACTORY_H
+
+#include "view.h"
+
+class WidgetFactory
+{
+public:
+ virtual ~WidgetFactory() { };
+ virtual View *createBoardDisplay() = 0;
+ virtual View *createRackDisplay() = 0;
+ virtual View *createBagDisplay() = 0;
+};
+
+class TextFactory : public WidgetFactory
+{
+public:
+ virtual ~TextFactory() { };
+ virtual View *createBoardDisplay();
+ virtual View *createRackDisplay();
+ virtual View *createBagDisplay();
+};
+
+class GraphicalFactory : public WidgetFactory
+{
+public:
+ virtual ~GraphicalFactory() { };
+ virtual View *createBoardDisplay();
+ virtual View *createRackDisplay();
+ virtual View *createBagDisplay();
+};
+
+class BoardSetupFactory : public WidgetFactory
+{
+public:
+ virtual ~BoardSetupFactory() { };
+ virtual View *createBoardDisplay();
+ virtual View *createRackDisplay();
+ virtual View *createBagDisplay();
+};
+
+#endif