diff options
author | Jason Katz-Brown <jason@airbnb.com> | 2013-08-25 02:17:13 -0700 |
---|---|---|
committer | Jason Katz-Brown <jason@airbnb.com> | 2013-08-25 02:17:13 -0700 |
commit | 9306cb60c32082c5403931de0823a9fd5daa196c (patch) | |
tree | ca1b6eb695fdf3f0c2294e92416b272164bae642 /quacker/movebox.cpp | |
parent | 8fb2c681cecc01b46b0f4ba02d5cc177c4747b1c (diff) |
Initial git commit.
Diffstat (limited to 'quacker/movebox.cpp')
-rw-r--r-- | quacker/movebox.cpp | 301 |
1 files changed, 301 insertions, 0 deletions
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 *))); + } +} + |