summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.MacOS4
-rw-r--r--README.Windows7
-rw-r--r--bag.cpp9
-rw-r--r--bag.h3
-rw-r--r--bindings/Makefile56
-rw-r--r--bindings/go/quackle.go6
-rw-r--r--bindings/lua/test_selfplay.lua90
-rw-r--r--bindings/python/test1_position.py78
-rw-r--r--bindings/python/test2_selfplay.py84
-rw-r--r--bindings/quackle.i111
-rw-r--r--data/lexica/copyrights.txt1
-rw-r--r--data/lexica/osps.dawgbin2570571 -> 2581267 bytes
-rw-r--r--game.cpp255
-rw-r--r--game.h24
-rw-r--r--installer.iss4
-rw-r--r--move.cpp28
-rw-r--r--move.h4
-rw-r--r--preendgame.cpp2
-rw-r--r--quacker/Info.plist2
-rw-r--r--quacker/boarddisplay.cpp28
-rw-r--r--quacker/boarddisplay.h17
-rw-r--r--quacker/configpages.cpp4
-rw-r--r--quacker/configpages.h1
-rw-r--r--quacker/graphicalboard.cpp2
-rw-r--r--quacker/graphicalreporter.cpp2
-rw-r--r--quacker/letterbox.cpp9
-rw-r--r--quacker/lister.cpp2
-rw-r--r--quacker/quacker.cpp71
-rw-r--r--quacker/quacker.plist4
-rw-r--r--quacker/quacker.pro2
-rw-r--r--quacker/quackersettings.cpp2
-rw-r--r--quackleio/gcgio.cpp50
-rw-r--r--quackleio/gcgio.h1
-rw-r--r--quackleio/util.cpp13
-rw-r--r--quackleio/util.h1
-rw-r--r--rack.cpp9
-rw-r--r--rack.h4
-rw-r--r--reporter.cpp2
-rw-r--r--sim.cpp2
39 files changed, 825 insertions, 169 deletions
diff --git a/README.MacOS b/README.MacOS
index ff13cba..9a78654 100644
--- a/README.MacOS
+++ b/README.MacOS
@@ -26,8 +26,8 @@ Creating the DMG:
* Set up Xcode with your Mac Developer account, and make sure you
have a Developer ID Application signing certificate installed into
your Keychain.
-* In the Xcode settings, set the deployment target to 10.7 (or some appropriate
- early-ish version).
+* In the Xcode settings, set the deployment target to 10.9 (earlier versions
+ don't appear to work with C++11).
* Create an Archive build using XCode.
* From the Archive interface, click Validate... and validate for a Developer
ID-signed Application. If you have a signing profile, this validation
diff --git a/README.Windows b/README.Windows
index 80951fd..2bfe8f4 100644
--- a/README.Windows
+++ b/README.Windows
@@ -66,6 +66,13 @@ working executable...
+ Look for any copies of quacker.res
+ Right-click on each one and choose Exclude From Project
+* Recent versions of Visual Studio no longer build XP-compatible binaries
+ by default. To change this in the project file, bring up the
+ Quackle Properties page, and under the General configuration,
+ change the "Platform Toolset" to one which indicates Windows XP
+ compatibility. Do this for the Quackle, quackleio, and libquackle
+ projects (the minimum projects which are part of a standard Quackle install).
+
Additional things to know:
--------------------------
diff --git a/bag.cpp b/bag.cpp
index b9d95ad..e4ebef9 100644
--- a/bag.cpp
+++ b/bag.cpp
@@ -55,6 +55,15 @@ void Bag::prepareFullBag()
m_tiles.push_back(letter);
}
+int Bag::fullBagTileCount()
+{
+ int tileCount = 0;
+ // we start at 0 because we want to include blanks etcetera
+ for (Letter letter = 0; letter <= QUACKLE_ALPHABET_PARAMETERS->lastLetter(); ++letter)
+ tileCount += QUACKLE_ALPHABET_PARAMETERS->count(letter);
+ return tileCount;
+}
+
void Bag::toss(const LetterString &letters)
{
const LetterString::const_iterator end(letters.end());
diff --git a/bag.h b/bag.h
index 5444076..e7e3f22 100644
--- a/bag.h
+++ b/bag.h
@@ -72,6 +72,9 @@ public:
// use this to start out your bag for use
void prepareFullBag();
+ // Assuming a full bag, how many tiles would that be?
+ int fullBagTileCount();
+
// whether there are no tiles left in the bag
bool empty() const;
diff --git a/bindings/Makefile b/bindings/Makefile
new file mode 100644
index 0000000..9cdb6a3
--- /dev/null
+++ b/bindings/Makefile
@@ -0,0 +1,56 @@
+CC=g++
+
+QTFLAGS := $(shell pkg-config QtCore --cflags)
+QTLIBS := $(shell pkg-config QtCore --libs)
+PYTHONFLAGS := $(shell pkg-config python2 --cflags)
+PHPFLAGS := $(shell php-config --includes)
+PHPLIBS := $(shell php-config --libs)
+LUAFLAGS := $(shell pkg-config lua5.1 --cflags)
+
+INCLUDES=-I..
+QUACKLELIBS=../lib/release/libquackle.a ../quackleio/lib/release/libquackleio.a
+
+php/quackle_wrap.cxx:
+ @test -d php || mkdir php
+ swig -c++ -o $@ $(INCLUDES) $(QTFLAGS) -php quackle.i
+
+php/quackle_wrap.o: php/quackle_wrap.cxx
+ $(CC) -std=c++11 -fPIC $(QTFLAGS) $(PHPFLAGS) $(PHPLIBS) $(INCLUDES) -c $< -o $@
+
+php: php/quackle_wrap.o
+ $(CC) -std=c++11 -shared -Wl,--whole-archive $(QUACKLELIBS) -Wl,--no-whole-archive $(QTLIBS) $< -o php/quackle.so
+
+python/quackle_wrap.cxx:
+ @test -d python || mkdir python
+ swig -c++ -o $@ $(INCLUDES) $(QTFLAGS) -python quackle.i
+
+python/quackle_wrap.o: python/quackle_wrap.cxx
+ $(CC) -std=c++11 -fPIC $(QTFLAGS) $(PYTHONFLAGS) $(INCLUDES) -c $< -o $@
+
+python: python/quackle_wrap.o
+ $(CC) -std=c++11 -shared -Wl,--whole-archive $(QUACKLELIBS) -Wl,--no-whole-archive $(QTLIBS) $< -o python/_quackle.so
+
+go:
+ ln -sf ../quackle.i go/quackle.swigcxx
+ go build ./go/...
+
+lua/quackle_wrap.cxx:
+ @test -d lua || mkdir lua
+ swig -c++ -o $@ $(INCLUDES) $(QTFLAGS) -lua quackle.i
+
+lua/quackle_wrap.o: lua/quackle_wrap.cxx
+ $(CC) -std=c++11 -fPIC $(LUAFLAGS) $(QTFLAGS) $(INCLUDES) -c $< -o $@
+
+lua: lua/quackle_wrap.o
+ $(CC) -std=c++11 -shared $(LUAFLAGS) -Wl,--whole-archive $(QUACKLELIBS) -Wl,--no-whole-archive $(QTLIBS) $< -o lua/quackle.so
+
+.PHONY: clean go
+
+clean:
+ -rm -rf python/quackle.py
+ -rm -rf php/*php*
+ -rm -rf */*_wrap.cxx
+ -rm -rf */*.o
+ -rm -rf */*.so
+ -rm -rf */*.pyc
+ -rm -rf go/quackle.swigcxx
diff --git a/bindings/go/quackle.go b/bindings/go/quackle.go
new file mode 100644
index 0000000..6c40a84
--- /dev/null
+++ b/bindings/go/quackle.go
@@ -0,0 +1,6 @@
+package quackle
+
+// #cgo CXXFLAGS: -I../..
+// #cgo pkg-config: QtCore
+// #cgo LDFLAGS: -L${SRCDIR}/../../lib/release -L${SRCDIR}/../../quackleio/lib/release -lquackle -lquackleio -lQtCore
+import "C"
diff --git a/bindings/lua/test_selfplay.lua b/bindings/lua/test_selfplay.lua
new file mode 100644
index 0000000..9209f52
--- /dev/null
+++ b/bindings/lua/test_selfplay.lua
@@ -0,0 +1,90 @@
+require 'quackle'
+
+function startUp(lexicon, alphabet, datadir)
+
+ local lexicon = lexicon or 'twl06'
+ local alphabet = alphabet or 'english'
+ local datadir = datadir or '../../data'
+
+ -- Set up the data manager
+ local dm = quackle.DataManager()
+ dm:setComputerPlayers(quackle.ComputerPlayerCollection.fullCollection())
+ dm:setBackupLexicon(lexicon)
+ dm:setAppDataDirectory(datadir)
+
+ -- Set up the alphabet
+ local abc = quackle.AlphabetParameters.findAlphabetFile(alphabet)
+ local abc2 = quackle.Util.stdStringToQString(abc) --convert to qstring
+ local fa = quackle.FlexibleAlphabetParameters()
+
+ assert(fa:load(abc2))
+ dm:setAlphabetParameters(fa)
+
+ -- Set up the board
+ local board = quackle.BoardParameters()
+ dm:setBoardParameters(board)
+
+ -- Find the lexicon
+ local dawg = quackle.LexiconParameters.findDictionaryFile(lexicon .. '.dawg')
+ local gaddag = quackle.LexiconParameters.findDictionaryFile(lexicon .. '.gaddag')
+ dm:lexiconParameters():loadDawg(dawg)
+ dm:lexiconParameters():loadGaddag(gaddag)
+
+ dm:strategyParameters():initialize(lexicon)
+ return dm
+
+end
+
+
+function getComputerPlayer(dm, name)
+
+ local name = name or 'Speedy Player'
+
+ local player, found = dm:computerPlayers():playerForName(name)
+ assert(found)
+ player = player:computerPlayer()
+
+ return player
+
+end
+
+dm = startUp()
+
+p1 = getComputerPlayer(dm)
+p2 = getComputerPlayer(dm)
+
+-- Create computer players
+player1 = quackle.Player('Compy1', quackle.Player.ComputerPlayerType, 0)
+player1:setComputerPlayer(p1)
+print(player1:name())
+
+player2 = quackle.Player('Compy2', quackle.Player.ComputerPlayerType, 1)
+player2:setComputerPlayer(p2)
+print(player2:name())
+
+dm:seedRandomNumbers(42)
+
+game = quackle.Game()
+players = quackle.PlayerList()
+
+players:push_back(player1)
+players:push_back(player2)
+
+game:setPlayers(players)
+game:associateKnownComputerPlayers()
+game:addPosition()
+
+for i=1, 50 do
+ if game:currentPosition():gameOver() then
+ print "GAME OVER"
+ break
+ end
+
+ player = game:currentPosition():currentPlayer()
+ move = game:haveComputerPlay()
+ --print("Player: " .. player:name())
+ print("Rack : " .. player:rack():toString())
+ print('Move: ' .. move:toString())
+ print('Board: \n' .. game:currentPosition():board():toString())
+
+end
diff --git a/bindings/python/test1_position.py b/bindings/python/test1_position.py
new file mode 100644
index 0000000..e04081a
--- /dev/null
+++ b/bindings/python/test1_position.py
@@ -0,0 +1,78 @@
+# coding: utf-8
+
+import quackle
+
+def startUp(lexicon='twl06',
+ alphabet='english',
+ datadir='../../data'):
+
+ # Set up the data manager
+ dm = quackle.DataManager()
+ dm.setComputerPlayers(quackle.ComputerPlayerCollection.fullCollection())
+ dm.setBackupLexicon(lexicon)
+ dm.setAppDataDirectory(datadir)
+
+ # Set up the alphabet
+ abc = quackle.AlphabetParameters.findAlphabetFile(alphabet)
+ abc2 = quackle.Util.stdStringToQString(abc) #convert to qstring
+ fa = quackle.FlexibleAlphabetParameters()
+
+ assert fa.load(abc2)
+ dm.setAlphabetParameters(fa)
+
+ # Set up the board
+ board = quackle.BoardParameters()
+ dm.setBoardParameters(board)
+
+ # Find the lexicon
+ dawg = quackle.LexiconParameters.findDictionaryFile(lexicon + '.dawg')
+ gaddag = quackle.LexiconParameters.findDictionaryFile(lexicon + '.gaddag')
+ dm.lexiconParameters().loadDawg(dawg)
+ dm.lexiconParameters().loadGaddag(gaddag)
+
+ dm.strategyParameters().initialize(lexicon)
+ return dm
+
+
+def getComputerPlayer(dm, name='Speedy Player'):
+ player, found = dm.computerPlayers().playerForName(name)
+ assert found
+ player = player.computerPlayer()
+ return player
+
+
+dm = startUp()
+
+# Create a computer player
+player1 = getComputerPlayer(dm)
+print player1.name()
+
+# Create the Game file (.gcg) reader
+gamereader = quackle.GCGIO()
+gamePath = quackle.Util.stdStringToQString('../../test/positions/short_game_with_bad_moves.gcg')
+game = gamereader.read(gamePath, quackle.Logania.MaintainBoardPreparation)
+
+# Get the current position
+position = game.currentPosition()
+
+player1.setPosition(position)
+
+racks = quackle.ProbableRackList()
+unseenbag = position.unseenBag()
+if unseenbag.size() <= dm.parameters().rackSize() + 3:
+ enum = quackle.Enumerator(unseenbag)
+ enum.enumerate(racks)
+ for rack in racks:
+ print rack
+
+movesToShow = 10
+
+print "Board state: \n%s" % position.board().toString()
+print "Move made: %s" % position.moveMade().toString()
+print "Current player: %s" % position.currentPlayer().storeInformationToString()
+print "Turn number: %i" % position.turnNumber()
+
+movelist = player1.moves(10)
+
+# Show 10 moves suggested by computer player
+for move in movelist: print move.toString()
diff --git a/bindings/python/test2_selfplay.py b/bindings/python/test2_selfplay.py
new file mode 100644
index 0000000..bb6e889
--- /dev/null
+++ b/bindings/python/test2_selfplay.py
@@ -0,0 +1,84 @@
+# coding: utf-8
+
+import time
+
+import quackle
+
+def startUp(lexicon='twl06',
+ alphabet='english',
+ datadir='../../data'):
+
+ # Set up the data manager
+ dm = quackle.DataManager()
+ dm.setComputerPlayers(quackle.ComputerPlayerCollection.fullCollection())
+ dm.setBackupLexicon(lexicon)
+ dm.setAppDataDirectory(datadir)
+
+ # Set up the alphabet
+ abc = quackle.AlphabetParameters.findAlphabetFile(alphabet)
+ abc2 = quackle.Util.stdStringToQString(abc) #convert to qstring
+ fa = quackle.FlexibleAlphabetParameters()
+
+ assert fa.load(abc2)
+ dm.setAlphabetParameters(fa)
+
+ # Set up the board
+ board = quackle.BoardParameters()
+ dm.setBoardParameters(board)
+
+ # Find the lexicon
+ dawg = quackle.LexiconParameters.findDictionaryFile(lexicon + '.dawg')
+ gaddag = quackle.LexiconParameters.findDictionaryFile(lexicon + '.gaddag')
+ dm.lexiconParameters().loadDawg(dawg)
+ dm.lexiconParameters().loadGaddag(gaddag)
+
+ dm.strategyParameters().initialize(lexicon)
+ return dm
+
+
+def getComputerPlayer(dm, name='Speedy Player'):
+ player, found = dm.computerPlayers().playerForName(name)
+ assert found
+ player = player.computerPlayer()
+ return player
+
+
+dm = startUp()
+
+p1 = getComputerPlayer(dm)
+p2 = getComputerPlayer(dm)
+
+# Create computer players
+player1 = quackle.Player('Compy1', quackle.Player.ComputerPlayerType, 0)
+player1.setComputerPlayer(p1)
+print player1.name()
+
+player2 = quackle.Player('Compy2', quackle.Player.ComputerPlayerType, 1)
+player2.setComputerPlayer(p2)
+print player2.name()
+
+dm.seedRandomNumbers(42)
+
+game = quackle.Game()
+players = quackle.PlayerList()
+
+players.append(player1)
+players.append(player2)
+
+game.setPlayers(players)
+game.associateKnownComputerPlayers()
+game.addPosition()
+
+for i in range(50):
+ if game.currentPosition().gameOver():
+ print "GAME OVER"
+ break
+
+ player = game.currentPosition().currentPlayer()
+ move = game.haveComputerPlay()
+ #print "Player: " + player.name()
+ print "Rack : " + player.rack().toString()
+ print 'Move: ' + move.toString()
+ print 'Board: \n' + game.currentPosition().board().toString()
+
+ time.sleep(1)
diff --git a/bindings/quackle.i b/bindings/quackle.i
new file mode 100644
index 0000000..e45eb88
--- /dev/null
+++ b/bindings/quackle.i
@@ -0,0 +1,111 @@
+%module quackle
+%{
+#include "fixedstring.h"
+#include "uv.h"
+#include "alphabetparameters.h"
+#include "move.h"
+#include "rack.h"
+#include "bag.h"
+#include "board.h"
+#include "boardparameters.h"
+#include "evaluator.h"
+#include "catchall.h"
+#include "player.h"
+#include "game.h"
+#include "gameparameters.h"
+#include "sim.h"
+#include "computerplayer.h"
+#include "computerplayercollection.h"
+#include "datamanager.h"
+#include "endgame.h"
+#include "endgameplayer.h"
+#include "enumerator.h"
+#include "bogowinplayer.h"
+#include "clock.h"
+#include "generator.h"
+#include "gaddag.h"
+#include "lexiconparameters.h"
+#include "preendgame.h"
+#include "reporter.h"
+#include "resolvent.h"
+#include "strategyparameters.h"
+
+#include <QString>
+#include "quackleio/flexiblealphabet.h"
+#include "quackleio/util.h"
+#include "quackleio/logania.h"
+#include "quackleio/gcgio.h"
+%}
+
+
+%include "std_string.i"
+%include "std_vector.i"
+%include "typemaps.i"
+
+%include "fixedstring.h"
+%include "uv.h"
+%include "alphabetparameters.h"
+
+/*Needed to generate proper iterable types */
+%template(MoveVector) std::vector<Quackle::Move>;
+%template(PlayerVector) std::vector<Quackle::Player>;
+%template(ProbableRackList) std::vector<Quackle::ProbableRack>;
+%template(PositionList) std::vector<Quackle::GamePosition>;
+%template(LetterParameterVector) std::vector<Quackle::LetterParameter>;
+%template(LetterStringVector) std::vector<Quackle::LetterString>;
+
+%include "move.h"
+%include "rack.h"
+%include "bag.h"
+%include "board.h"
+%include "boardparameters.h"
+%include "evaluator.h"
+%include "catchall.h"
+%include "player.h"
+
+using namespace std;
+namespace Quackle
+{
+ class PlayerList : public vector<Player>
+ {
+ public:
+ PlayerList();
+
+ const Player &playerForId(int id, bool &OUTPUT) const;
+ const Player &playerForName(const UVString &name, bool &OUTPUT) const;
+ };
+}
+
+%include "game.h"
+%include "gameparameters.h"
+%include "sim.h"
+%include "computerplayer.h"
+%include "computerplayercollection.h"
+
+%apply SWIGTYPE *DISOWN {Quackle::AlphabetParameters *alphabetParameters};
+%apply SWIGTYPE *DISOWN {Quackle::BoardParameters *boardParameters};
+%apply SWIGTYPE *DISOWN {Quackle::StrategyParameters *lexiconParameters};
+%apply SWIGTYPE *DISOWN {Quackle::LexiconParameters *lexiconParameters};
+%apply SWIGTYPE *DISOWN {Quackle::Evaluator *evaluator};
+%apply SWIGTYPE *DISOWN {Quackle::GameParameters *parameters};
+%apply SWIGTYPE *DISOWN {const Quackle::PlayerList &playerList};
+
+%include "datamanager.h"
+%include "endgame.h"
+%include "endgameplayer.h"
+%include "enumerator.h"
+%include "bogowinplayer.h"
+%include "clock.h"
+%include "generator.h"
+%include "gaddag.h"
+%include "lexiconparameters.h"
+%include "preendgame.h"
+%include "reporter.h"
+%include "resolvent.h"
+%include "strategyparameters.h"
+
+%include <QString>
+%include "quackleio/flexiblealphabet.h"
+%include "quackleio/util.h"
+%include "quackleio/logania.h"
+%include "quackleio/gcgio.h"
diff --git a/data/lexica/copyrights.txt b/data/lexica/copyrights.txt
index 980b9ae..94f6474 100644
--- a/data/lexica/copyrights.txt
+++ b/data/lexica/copyrights.txt
@@ -1,4 +1,5 @@
eea8dfe5:Collins Scrabble™ Words 2012, ©HarperCollins Publishers Ltd 2015
48dea2c8:Collins Scrabble™ Words 2015, ©HarperCollins Publishers Ltd 2015
0109ce12:Official Tournament and Club Word List 2014 Edition (OTCWL2014) Copyright © 2014 Hasbro, Inc. Published under license with Merriam-Webster, Incorporated.
+9ea8d964:Official Tournament and Club Word List 2016 Edition (OTCWL2016) Copyright © 2016 Hasbro, Inc. Published under license with Merriam-Webster, Incorporated.
d5aeccff:ODS7
diff --git a/data/lexica/osps.dawg b/data/lexica/osps.dawg
index 6d81d3d..98b6d48 100644
--- a/data/lexica/osps.dawg
+++ b/data/lexica/osps.dawg
Binary files differ
diff --git a/game.cpp b/game.cpp
index 845e864..7e0f438 100644
--- a/game.cpp
+++ b/game.cpp
@@ -70,7 +70,7 @@ void Game::setPlayers(const PlayerList &list)
void Game::addPosition()
{
addClonePosition();
- m_positions.lastPosition().incrementTurn();
+ m_positions.lastPosition().incrementTurn(&history());
m_positions.setCurrentLocation(m_positions.lastLocation());
@@ -106,15 +106,14 @@ void Game::associateComputerPlayer(int playerId, ComputerPlayer *computerPlayer)
void Game::associateKnownComputerPlayers()
{
- const PlayerList::const_iterator end(m_positions.players().end());
- for (PlayerList::const_iterator it = m_positions.players().begin(); it != end; ++it)
+ for (const auto &it : m_positions.players())
{
- if ((*it).type() == Player::ComputerPlayerType)
+ if (it.type() == Player::ComputerPlayerType)
{
- ComputerPlayer *computerPlayer = (*it).computerPlayer();
+ ComputerPlayer *computerPlayer = it.computerPlayer();
if (computerPlayer)
- associateComputerPlayer((*it).id(), computerPlayer);
+ associateComputerPlayer(it.id(), computerPlayer);
}
}
}
@@ -186,15 +185,16 @@ void Game::commitMove(const Move &move)
///////////
GamePosition::GamePosition(const PlayerList &players)
- : m_players(players), m_currentPlayer(m_players.end()), m_playerOnTurn(m_players.end()), m_turnNumber(0), m_nestedness(0), m_scorelessTurnsInARow(0), m_gameOver(false)
+ : m_players(players), m_currentPlayer(m_players.end()), m_playerOnTurn(m_players.end()), m_turnNumber(0), m_nestedness(0), m_scorelessTurnsInARow(0), m_gameOver(false), m_tilesOnRack(QUACKLE_PARAMETERS->rackSize())
{
setEmptyBoard();
resetMoveMade();
resetBag();
+ m_tilesInBag = m_bag.fullBagTileCount() - (QUACKLE_PARAMETERS->rackSize() * m_players.size());
}
GamePosition::GamePosition(const GamePosition &position)
- : m_players(position.m_players), m_moves(position.m_moves), m_moveMade(position.m_moveMade), m_committedMove(position.m_committedMove), m_turnNumber(position.m_turnNumber), m_nestedness(position.m_nestedness), m_scorelessTurnsInARow(position.m_scorelessTurnsInARow), m_gameOver(position.m_gameOver), m_board(position.m_board), m_bag(position.m_bag), m_drawingOrder(position.m_drawingOrder), m_explanatoryNote(position.m_explanatoryNote)
+ : m_players(position.m_players), m_moves(position.m_moves), m_moveMade(position.m_moveMade), m_committedMove(position.m_committedMove), m_turnNumber(position.m_turnNumber), m_nestedness(position.m_nestedness), m_scorelessTurnsInARow(position.m_scorelessTurnsInARow), m_gameOver(position.m_gameOver), m_tilesInBag(position.m_tilesInBag), m_tilesOnRack(position.m_tilesOnRack), m_board(position.m_board), m_bag(position.m_bag), m_drawingOrder(position.m_drawingOrder), m_explanatoryNote(position.m_explanatoryNote)
{
// reset iterator
if (position.turnNumber() == 0)
@@ -219,6 +219,8 @@ const GamePosition &GamePosition::operator=(const GamePosition &position)
m_nestedness = position.m_nestedness;
m_scorelessTurnsInARow = position.m_scorelessTurnsInARow;
m_gameOver = position.m_gameOver;
+ m_tilesInBag = position.m_tilesInBag;
+ m_tilesOnRack = position.m_tilesOnRack;
m_board = position.m_board;
m_bag = position.m_bag;
m_drawingOrder = position.m_drawingOrder;
@@ -253,9 +255,8 @@ void GamePosition::kibitz(int nmoves)
m_moves = generator.kibitzList();
- const MoveList::iterator end(m_moves.end());
- for (MoveList::iterator it = m_moves.begin(); it != end; ++it)
- ensureMovePrettiness(*it);
+ for (auto &it : m_moves)
+ ensureMovePrettiness(it);
}
const Move &GamePosition::staticBestMove()
@@ -308,9 +309,9 @@ void GamePosition::addMove(const Move &move)
void GamePosition::makeSureMoveListContainsMoves(const MoveList &moves)
{
- for (MoveList::const_iterator it = moves.begin(); it != moves.end(); ++it)
- if (!m_moves.contains(*it))
- addMove(*it);
+ for (const auto &it : moves)
+ if (!m_moves.contains(it))
+ addMove(it);
}
void GamePosition::kibitzAs(ComputerPlayer *computerPlayer, int nmoves)
@@ -357,7 +358,13 @@ int GamePosition::validateMove(const Move &move) const
}
break;
+ case Move::BlindExchange:
+ if (!exchangeAllowed())
+ ret |= TooLateExchange;
+ break;
+
case Move::UnusedTilesBonus:
+ case Move::UnusedTilesBonusError:
case Move::TimePenalty:
ret = InvalidAction;
break;
@@ -379,9 +386,9 @@ bool GamePosition::formsAcceptableWords(const Move &move) const
{
vector<Move> words = m_board.allWordsFormedBy(move);
- for (vector<Move>::const_iterator it = words.begin(); it != words.end(); ++it)
+ for (const auto &it : words)
{
- if (!isAcceptableWord((*it).wordTiles()))
+ if (!isAcceptableWord(it.wordTiles()))
return false;
}
@@ -419,15 +426,15 @@ int GamePosition::handleOverdraw(const LetterString &letters, LetterString *thro
double minimumLeaveValue = 99999;
- for (ProbableRackList::const_iterator it = racks.begin(); it != racks.end(); ++it)
+ for (const auto &it : racks)
{
- double value = leaveValue((*it).rack.tiles());
+ double value = leaveValue(it.rack.tiles());
if (value < minimumLeaveValue)
{
Rack rack(letters);
- rack.unload((*it).rack.tiles());
- *throwback = (Rack(letters) - (*it).rack).tiles();
+ rack.unload(it.rack.tiles());
+ *throwback = (Rack(letters) - it.rack).tiles();
minimumLeaveValue = value;
}
}
@@ -498,12 +505,11 @@ Bag GamePosition::unseenBagFromPlayerPerspective(const Player &player) const
// other way:
Bag ret(m_bag);
- const PlayerList::const_iterator end(m_players.end());
- for (PlayerList::const_iterator it = m_players.begin(); it != end; ++it)
+ for (const auto &it : m_players)
{
- if (!((*it) == player))
+ if (!(it == player))
{
- ret.toss((*it).rack());
+ ret.toss(it.rack());
}
}
@@ -520,11 +526,10 @@ void GamePosition::ensureProperBag() const
Bag racks;
racks.clear();
- const PlayerList::const_iterator end(m_players.end());
- for (PlayerList::const_iterator it = m_players.begin(); it != end; ++it)
+ for (const auto &it : m_players)
{
- allTiles.toss((*it).rack());
- racks.toss((*it).rack());
+ allTiles.toss(it.rack());
+ racks.toss(it.rack());
}
allTiles.toss(m_bag.shuffledTiles());
@@ -617,13 +622,12 @@ UVString GamePosition::nestednessIndentation() const
void GamePosition::setOppRack(const Rack &rack, bool adjustBag)
{
int oppID;
- const PlayerList::iterator end(m_players.end());
- for (PlayerList::iterator it = m_players.begin(); it != end; ++it)
- if ((*it).id() != currentPlayer().id())
+ for (auto &it : m_players)
+ if (it.id() != currentPlayer().id())
{
- m_bag.toss((*it).rack());
- (*it).setRack(Rack(""));
- oppID = (*it).id();
+ m_bag.toss(it.rack());
+ it.setRack(Rack(""));
+ oppID = it.id();
setPlayerRack(oppID, rack, adjustBag);
return;
}
@@ -634,15 +638,14 @@ Rack GamePosition::oppRack()
{
UVcout << "currentPlayer(): " << currentPlayer() << endl;
- const PlayerList::iterator end(m_players.end());
- for (PlayerList::iterator it = m_players.begin(); it != end; ++it)
- UVcout << "rack " << *it << " " << (*it).rack() << endl;
+ for (const auto &it : m_players)
+ UVcout << "rack " << it << " " << it.rack() << endl;
- for (PlayerList::iterator it = m_players.begin(); it != end; ++it)
+ for (const auto &it : m_players)
{
- if ((*it).id() != currentPlayer().id())
+ if (it.id() != currentPlayer().id())
{
- return (*it).rack();
+ return it.rack();
}
}
@@ -651,20 +654,19 @@ Rack GamePosition::oppRack()
void GamePosition::setPlayerRack(int playerID, const Rack &rack, bool adjustBag)
{
- const PlayerList::iterator end(m_players.end());
- for (PlayerList::iterator it = m_players.begin(); it != end; ++it)
+ for (auto &it : m_players)
{
- if ((*it).id() == playerID)
+ if (it.id() == playerID)
{
// restore bag
if (adjustBag)
{
// please be correct code
- m_bag.toss((*it).rack() - rack);
- removeLetters((rack - (*it).rack()).tiles());
+ m_bag.toss(it.rack() - rack);
+ removeLetters((rack - it.rack()).tiles());
}
- (*it).setRack(rack);
+ it.setRack(rack);
}
}
}
@@ -679,9 +681,8 @@ bool GamePosition::canSetPlayerRackWithoutBagExpansion(int playerID, const Rack
(void) playerID;
Bag someTiles(m_bag);
- const PlayerList::const_iterator end(m_players.end());
- for (PlayerList::const_iterator it = m_players.begin(); it != end; ++it)
- someTiles.toss((*it).rack());
+ for (const auto &it : m_players)
+ someTiles.toss(it.rack());
// Now we have a bag with all tiles not on the board
return someTiles.removeLetters(rack.tiles());
@@ -719,6 +720,32 @@ bool GamePosition::setPlayerOnTurn(int playerID)
return false;
}
+void GamePosition::setTileBonus(const UVString &player, const LetterString &allegedTiles, int allegedTileBonus)
+{
+ bool found;
+ Quackle::Move tileBonusMove = Quackle::Move::createUnusedTilesBonus(allegedTiles, allegedTileBonus);
+ PlayerList::const_iterator currentPlayer = playerWithAbbreviatedName(player, found);
+ if (currentPlayer == m_currentPlayer && m_gameOver)
+ {
+ if (allegedTileBonus == m_committedMove.effectiveScore())
+ return; // this has already been computed
+
+ // We computed this, but it differs from the actual tile bonus we're requested to add
+ tileBonusMove.action = Quackle::Move::UnusedTilesBonusError;
+ }
+ else if (allegedTileBonus > 0)
+ {
+ ++m_turnNumber;
+ tileBonusMove.action = Quackle::Move::UnusedTilesBonusError; // we didn't empty a rack, but somebody's claiming we did
+ }
+
+ setCurrentPlayer(currentPlayer->id());
+ setMoveMade(tileBonusMove);
+ setCommittedMove(tileBonusMove);
+ m_gameOver = true;
+ m_explanatoryNote = UVString("Quackle says: Bag wasn't empty or tiles drawn out of order");
+}
+
void GamePosition::prepareForCommit()
{
setCommittedMove(moveMade());
@@ -742,7 +769,7 @@ void GamePosition::resetBag()
m_bag.prepareFullBag();
}
-bool GamePosition::incrementTurn()
+bool GamePosition::incrementTurn(const History* history)
{
if (gameOver() || m_players.empty())
return false;
@@ -762,6 +789,51 @@ bool GamePosition::incrementTurn()
// now moveTiles is the tiles that are in play but not on rack
removeLetters(moveTiles.tiles());
+ if (history)
+ {
+ PlayerList::iterator nextCurrentPlayer(m_currentPlayer);
+ nextCurrentPlayer++;
+ if (nextCurrentPlayer == m_players.end())
+ nextCurrentPlayer = m_players.begin();
+ const Quackle::PositionList positions(history->positionsFacedBy((*nextCurrentPlayer).id()));
+ if (positions.size() > 0)
+ m_tilesOnRack = positions.back().m_tilesOnRack;
+ else if (m_turnNumber > 1)
+ {
+ // this can happen inside of a simming player engine
+ // which doesn't have a full history list
+ m_tilesOnRack = currentPlayer().rack().tiles().size();
+ }
+ if (m_moveMade.action == Move::Place && !m_moveMade.isChallengedPhoney())
+ m_tilesOnRack -= m_moveMade.usedTiles().size();
+ if (m_tilesInBag == 0)
+ {
+ // We can get off on our counting with unknown racks.
+ // Shift tiles around if that happens.
+ Rack otherPlayerRack = nextCurrentPlayer->rack();
+ if (m_tilesOnRack == 0 && !remainingRack.empty())
+ {
+ otherPlayerRack.load(remainingRack.tiles());
+ remainingRack = remainingRack - remainingRack;
+ }
+ else if (m_tilesOnRack != 0 && remainingRack.empty())
+ {
+ int tilesToMove = m_tilesOnRack;
+ while (otherPlayerRack.tiles().size() > 0 && tilesToMove-- > 0)
+ {
+ LetterString oneAtATime = otherPlayerRack.tiles().substr(0, 1);
+ otherPlayerRack.unload(oneAtATime);
+ remainingRack.load(oneAtATime);
+ }
+ }
+ nextCurrentPlayer->setRack(otherPlayerRack);
+ }
+ while (m_tilesInBag > 0 && m_tilesOnRack < QUACKLE_PARAMETERS->rackSize() && m_moveMade.action == Move::Place)
+ {
+ m_tilesInBag--;
+ m_tilesOnRack++;
+ }
+ }
// update our current player's score before possibly
// adding endgame bonuses
@@ -783,7 +855,7 @@ bool GamePosition::incrementTurn()
}
}
- if ((m_moveMade.action == Move::Place && m_moveMade.effectiveScore() == 0) || m_moveMade.action == Move::Exchange || m_moveMade.action == Move::Pass)
+ if ((m_moveMade.action == Move::Place && m_moveMade.effectiveScore() == 0) || m_moveMade.action == Move::Exchange || m_moveMade.action == Move::BlindExchange || m_moveMade.action == Move::Pass)
++m_scorelessTurnsInARow;
else
m_scorelessTurnsInARow = 0;
@@ -823,10 +895,9 @@ bool GamePosition::incrementTurn()
// refill rack of any player whose rack is empty (again for
// beginning of game)
- const PlayerList::iterator end(m_players.end());
- for (PlayerList::iterator it = m_players.begin(); it != end; ++it)
- if ((*it).rack().empty())
- replenishAndSetRack((*it).rack(), *it);
+ for (auto &it : m_players)
+ if (it.rack().empty())
+ replenishAndSetRack(it.rack(), it);
// freeze current player as last person who played out
if (gameOver())
@@ -838,7 +909,8 @@ bool GamePosition::incrementTurn()
m_drawingOrder = LetterString();
// reset note to a clean slate
- m_explanatoryNote = UVString();
+ if (m_moveMade.action != Quackle::Move::UnusedTilesBonusError)
+ m_explanatoryNote = UVString();
return ret;
}
@@ -847,14 +919,13 @@ bool GamePosition::removeLetters(const LetterString &letters)
{
bool ret = true;
- const LetterString::const_iterator end(letters.end());
- for (LetterString::const_iterator it = letters.begin(); it != end; ++it)
+ for (const auto &it : letters)
{
#ifdef VERBOSE_DEBUG_BAG
UVcout << "removeLetters processing " << QUACKLE_ALPHABET_PARAMETERS->userVisible(*it) << endl;
#endif
- const bool removedFromBag = m_bag.removeLetter(*it);
+ const bool removedFromBag = m_bag.removeLetter(it);
if (removedFromBag)
{
#ifdef VERBOSE_DEBUG_BAG
@@ -864,20 +935,19 @@ bool GamePosition::removeLetters(const LetterString &letters)
else
{
bool removedFromPlayer = false;
- const PlayerList::iterator playersEnd(m_players.end());
- for (PlayerList::iterator playerIt = m_players.begin(); playerIt != playersEnd; ++playerIt)
+ for (auto &playerIt : m_players)
{
- if (*playerIt == currentPlayer())
+ if (playerIt == currentPlayer())
continue;
LetterString letterString;
- letterString += (*it);
+ letterString += it;
- Rack newRack((*playerIt).rack());
+ Rack newRack(playerIt.rack());
removedFromPlayer = newRack.unload(letterString);
if (removedFromPlayer)
{
- replenishAndSetRack(newRack, *playerIt);
+ replenishAndSetRack(newRack, playerIt);
break;
}
}
@@ -911,7 +981,7 @@ PlayerList::const_iterator GamePosition::nextPlayer() const
return ret;
}
-PlayerList::const_iterator GamePosition::playerWithId(int id, bool &found) const
+PlayerList::const_iterator GamePosition::playerWithAbbreviatedName(const UVString &abbreviatedName, bool &found) const
{
PlayerList::const_iterator it = m_currentPlayer;
for (++it; ; ++it)
@@ -919,7 +989,7 @@ PlayerList::const_iterator GamePosition::playerWithId(int id, bool &found) const
if (it == m_players.end())
it = m_players.begin();
- if ((*it).id() == id)
+ if ((*it).abbreviatedName() == abbreviatedName)
{
found = true;
return it;
@@ -961,10 +1031,9 @@ PlayerList::const_iterator GamePosition::nextPlayerOfType(Player::PlayerType typ
PlayerList GamePosition::endgameAdjustedScores() const
{
PlayerList ret;
- const PlayerList::const_iterator end(m_players.end());
- for (PlayerList::const_iterator it = m_players.begin(); it != end; ++it)
+ for (const auto &it : m_players)
{
- Player adjustedPlayer(*it);
+ Player adjustedPlayer(it);
if (gameOver() && adjustedPlayer == currentPlayer())
{
@@ -982,13 +1051,13 @@ PlayerList GamePosition::leadingPlayers() const
PlayerList ret;
PlayerList players(endgameAdjustedScores());
- for (PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
+ for (const auto &it : players)
{
- if (!ret.empty() && (*it).score() > ret.back().score())
+ if (!ret.empty() && it.score() > ret.back().score())
ret.clear();
- if (ret.empty() || (*it).score() >= ret.back().score())
- ret.push_back(*it);
+ if (ret.empty() || it.score() >= ret.back().score())
+ ret.push_back(it);
}
return ret;
@@ -1000,16 +1069,16 @@ int GamePosition::spread(int playerID) const
int currentPlayerScore = 0;
PlayerList players(endgameAdjustedScores());
- for (PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
+ for (const auto &it : players)
{
- if ((*it).id() == playerID)
+ if (it.id() == playerID)
{
- currentPlayerScore = (*it).score();
+ currentPlayerScore = it.score();
continue;
}
- if ((*it).score() > nextBest)
- nextBest = (*it).score();
+ if (it.score() > nextBest)
+ nextBest = it.score();
}
return currentPlayerScore - nextBest;
@@ -1017,17 +1086,16 @@ int GamePosition::spread(int playerID) const
void GamePosition::adjustScoresToFinishPassedOutGame()
{
- const PlayerList::iterator end(m_players.end());
- for (PlayerList::iterator it = m_players.begin(); it != end; ++it)
+ for (auto &it : m_players)
{
- if ((*it) == currentPlayer())
+ if (it == currentPlayer())
{
- m_moveMade = Move::createUnusedTilesBonus((*it).rack().tiles(), -(*it).rack().score());
+ m_moveMade = Move::createUnusedTilesBonus(it.rack().tiles(), -it.rack().score());
m_committedMove = m_moveMade;
}
else
{
- (*it).addToScore(-(*it).rack().score());
+ it.addToScore(-it.rack().score());
}
}
}
@@ -1048,14 +1116,13 @@ int GamePosition::deadwood(LetterString *tiles) const
int addand = 0;
tiles->clear();
- const PlayerList::const_iterator end(m_players.end());
- for (PlayerList::const_iterator it = m_players.begin(); it != end; ++it)
+ for (const auto &it : m_players)
{
- if ((*it) == currentPlayer())
+ if (it == currentPlayer())
continue;
- addand += (*it).rack().score();
- *tiles += (*it).rack().tiles();
+ addand += it.rack().score();
+ *tiles += it.rack().tiles();
}
return addand * 2;
@@ -1074,9 +1141,8 @@ UVOStream& operator<<(UVOStream& o, const Quackle::GamePosition &position)
o << "Move made is " << position.moveMade() << endl;
o << "All Players: " << endl;
PlayerList players(position.players());
- const PlayerList::const_iterator end(players.end());
- for (PlayerList::const_iterator it = players.begin(); it != end; ++it)
- o << *it << endl;
+ for (const auto &it : players)
+ o << it << endl;
o << position.bag() << endl;
if (position.gameOver())
o << "Game over." << endl;
@@ -1086,9 +1152,8 @@ UVOStream& operator<<(UVOStream& o, const Quackle::GamePosition &position)
UVOStream& operator<<(UVOStream& o, const Quackle::PositionList &positions)
{
- const Quackle::PositionList::const_iterator end(positions.end());
- for (Quackle::PositionList::const_iterator it = positions.begin(); it != end; ++it)
- o << *it << endl;
+ for (const auto &it : positions)
+ o << it << endl;
return o;
}
diff --git a/game.h b/game.h
index 40d7600..c022848 100644
--- a/game.h
+++ b/game.h
@@ -34,6 +34,7 @@ namespace Quackle
{
class ComputerPlayer;
+class History;
class HistoryLocation
{
@@ -269,9 +270,9 @@ public:
// and found is set to false.
PlayerList::const_iterator nextPlayerOfType(Player::PlayerType type, bool &found) const;
- // Returns the player with id specified, or the current player
+ // Returns the player with the abbreviated name specified, or the current player
// if not found.
- PlayerList::const_iterator playerWithId(int id, bool &found) const;
+ PlayerList::const_iterator playerWithAbbreviatedName(const UVString &abbreviatedName, bool &found) const;
const PlayerList &players() const;
@@ -295,7 +296,7 @@ public:
bool gameOver() const;
// the move made will
- // be set to the proper UnusedTileBonus move.
+ // be set to the proper UnusedTilesBonus move.
// The score of the bonus is *not* added to score of current player,
// therefor this method is quite misnamed.
void adjustScoresToFinishGame();
@@ -355,6 +356,8 @@ public:
void setCommittedMove(const Move &move);
const Move &committedMove() const;
+ void setTileBonus(const UVString &player, const LetterString &allegedTiles, int allegedTileBonus);
+
// saves the current candidate as the committedMove.
void prepareForCommit();
@@ -380,7 +383,7 @@ public:
// at start of game.)
// If applicable, this player's score is also incremented by score of move
// made.
- bool incrementTurn();
+ bool incrementTurn(const History* history = NULL);
// Turn numbers in games start from 1.
// A turn number of zero indicates a position that is pregame.
@@ -426,6 +429,8 @@ protected:
unsigned int m_nestedness;
int m_scorelessTurnsInARow;
bool m_gameOver;
+ int m_tilesInBag;
+ int m_tilesOnRack;
Quackle::Board m_board;
@@ -533,6 +538,17 @@ inline bool GamePosition::gameOver() const
inline void GamePosition::setMoveMade(const Move &move)
{
m_moveMade = move;
+ if (m_gameOver && move.action != Quackle::Move::UnusedTilesBonus && move.action != Quackle::Move::UnusedTilesBonusError)
+ {
+ m_gameOver = false; // apparently the game isn't over...somebody's force-feeding us bad plays
+ --m_turnNumber;
+ m_moveMade.action = Quackle::Move::PlaceError;
+ m_explanatoryNote = "Quackle says: Tiles were drawn out of order, leading to extra turns";
+ if (++m_currentPlayer == m_players.end())
+ {
+ m_currentPlayer = m_players.begin();
+ }
+ }
}
inline const Move &GamePosition::moveMade() const
diff --git a/installer.iss b/installer.iss
index a450985..96a987f 100644
--- a/installer.iss
+++ b/installer.iss
@@ -3,14 +3,14 @@
[Setup]
AppName=Quackle
-AppVerName=Quackle 1.0
+AppVerName=Quackle 1.0.3
DefaultDirName={pf}\Quackle
DefaultGroupName=Quackle
ChangesAssociations=yes
UninstallDisplayIcon={app}\Quackle.exe
[Icons]
-Name: "{group}\Quackle 1.0"; Filename: "{app}\Quackle.exe"; WorkingDir: "{app}"
+Name: "{group}\Quackle 1.0.3"; Filename: "{app}\Quackle.exe"; WorkingDir: "{app}"
[Registry]
Root: HKCU; Subkey: "Software\Quackle.org"
diff --git a/move.cpp b/move.cpp
index eaaf8b6..2d0a9c4 100644
--- a/move.cpp
+++ b/move.cpp
@@ -40,14 +40,20 @@ bool operator==(const Move &move1, const Move &move2)
switch (move1.action)
{
case Quackle::Move::Place:
+ case Quackle::Move::PlaceError:
ret = (move1.horizontal == move2.horizontal && move1.startrow == move2.startrow && move1.startcol == move2.startcol && move1.tiles() == move2.tiles() && move1.isChallengedPhoney() == move2.isChallengedPhoney());
break;
case Quackle::Move::UnusedTilesBonus:
+ case Quackle::Move::UnusedTilesBonusError:
case Quackle::Move::Exchange:
ret = (Quackle::String::alphabetize(move1.tiles()) == Quackle::String::alphabetize(move2.tiles()));
break;
+ case Quackle::Move::BlindExchange:
+ ret = (move1.tiles().length() == move2.tiles().length());
+ break;
+
case Quackle::Move::Pass:
case Quackle::Move::Nonmove:
case Quackle::Move::TimePenalty:
@@ -81,7 +87,7 @@ bool Quackle::operator<(const Move &move1, const Move &move2)
LetterString Move::usedTiles() const
{
- return m_isChallengedPhoney? LetterString() : String::usedTiles(m_tiles);
+ return (m_isChallengedPhoney || action == BlindExchange) ? LetterString() : String::usedTiles(m_tiles);
}
LetterString Move::wordTiles() const
@@ -124,6 +130,7 @@ UVString Move::xml() const
break;
case Exchange:
+ case BlindExchange:
actionString = MARK_UV("exchange");
includeTiles = true;
break;
@@ -137,6 +144,7 @@ UVString Move::xml() const
includeScore = true;
break;
+ case UnusedTilesBonusError:
case UnusedTilesBonus:
actionString = MARK_UV("unusedtilesbonus");
includeTiles = true;
@@ -180,17 +188,19 @@ UVString Move::toString() const
{
UVOStringStream ss;
- if (action == Quackle::Move::Pass)
- ss << "- ";
- else if (action == Quackle::Move::Exchange)
- ss << "-" << QUACKLE_ALPHABET_PARAMETERS->userVisible(m_tiles);
+ if (action == Quackle::Move::Pass)
+ ss << "- ";
+ else if (action == Quackle::Move::Exchange)
+ ss << "-" << QUACKLE_ALPHABET_PARAMETERS->userVisible(m_tiles);
+ else if (action == Quackle::Move::BlindExchange)
+ ss << "-" << m_tiles.length();
else if (action == Quackle::Move::Nonmove)
ss << "nonmove";
else if (action == Quackle::Move::TimePenalty)
ss << "timepenalty " << score;
- else if (action == Quackle::Move::UnusedTilesBonus)
+ else if (action == Quackle::Move::UnusedTilesBonus || action == Quackle::Move::UnusedTilesBonusError)
ss << "(" << QUACKLE_ALPHABET_PARAMETERS->userVisible(m_tiles) << ")";
- else if (action == Quackle::Move::Place)
+ else if (action == Quackle::Move::Place || action == Quackle::Move::PlaceError)
{
ss << positionString();
ss << " " << QUACKLE_ALPHABET_PARAMETERS->userVisible(m_tiles);
@@ -290,11 +300,11 @@ Move Move::createChallengedPhoney(int zeroIndexedRow, int zeroIndexedColumn, boo
return move;
}
-Move Move::createExchangeMove(LetterString tilesToExchange)
+Move Move::createExchangeMove(LetterString tilesToExchange, bool isBlind)
{
Move move;
- move.action = Move::Exchange;
+ move.action = isBlind ? Move::BlindExchange : Move::Exchange;
move.setTiles(tilesToExchange);
move.score = 0;
diff --git a/move.h b/move.h
index f4d1827..3e0d396 100644
--- a/move.h
+++ b/move.h
@@ -43,7 +43,7 @@ namespace Quackle
class Move
{
public:
- enum Action { Place = 0, Exchange, Pass, UnusedTilesBonus, TimePenalty, Nonmove };
+ enum Action { Place = 0, PlaceError, Exchange, BlindExchange, Pass, UnusedTilesBonus, UnusedTilesBonusError, TimePenalty, Nonmove };
// creates a pass move with 0 equity;
// tiles is "", score and equity are zero
@@ -124,7 +124,7 @@ public:
static Move createChallengedPhoney(UVString placeString, LetterString word);
static Move createChallengedPhoney(int zeroIndexedRow, int zeroIndexedColumn, bool horizontal, LetterString word);
- static Move createExchangeMove(LetterString tilesToExchange);
+ static Move createExchangeMove(LetterString tilesToExchange, bool isBlind);
static Move createUnusedTilesBonus(LetterString unusedTiles, int bonus);
static Move createTimePenalty(int penalty);
static Move createPassMove();
diff --git a/preendgame.cpp b/preendgame.cpp
index 227598f..fe9a356 100644
--- a/preendgame.cpp
+++ b/preendgame.cpp
@@ -167,7 +167,7 @@ MoveList Preendgame::moves(int nmoves)
tempPosition.setOppRack((*it).rack);
tempPosition.setMoveMade(*moveIt);
- tempPosition.incrementTurn();
+ tempPosition.incrementTurn(NULL);
tempPosition.makeMove(*moveIt);
//tempPosition.incrementNestedness();
diff --git a/quacker/Info.plist b/quacker/Info.plist
index 854c8a9..29cb164 100644
--- a/quacker/Info.plist
+++ b/quacker/Info.plist
@@ -26,7 +26,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>1.0</string>
+ <string>1.0.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
diff --git a/quacker/boarddisplay.cpp b/quacker/boarddisplay.cpp
index e2d7056..d8b2a8d 100644
--- a/quacker/boarddisplay.cpp
+++ b/quacker/boarddisplay.cpp
@@ -39,8 +39,9 @@ BoardWithQuickEntry::BoardWithQuickEntry(QWidget *parent)
m_vlayout = new QVBoxLayout(this);
Geometry::setupInnerLayout(m_vlayout);
- m_lineEdit = new QLineEdit;
+ m_lineEdit = new QLineEditWithShiftReturn;
connect(m_lineEdit, SIGNAL(returnPressed()), this, SLOT(quickEditReturnPressed()));
+ connect(m_lineEdit, SIGNAL(shiftReturnPressed()), this, SLOT(quickEditShiftReturnPressed()));
QLabel *placeLabel = new QLabel(tr("Move: '<position> <word>' or 'exchange <tiles|number>'"));
placeLabel->setBuddy(m_lineEdit);
@@ -103,6 +104,13 @@ void BoardWithQuickEntry::quickEditReturnPressed()
m_lineEdit->clear();
}
+void BoardWithQuickEntry::quickEditShiftReturnPressed()
+{
+ quickEditReturnPressed();
+ performCommit();
+ m_lineEdit->setFocus();
+}
+
void BoardWithQuickEntry::plusFive()
{
m_localCandidateMove.setScoreAddition(m_localCandidateMove.scoreAddition() + 5);
@@ -177,7 +185,7 @@ void BoardWithQuickEntry::processCommand(const QString &command)
if (isPass)
move = Quackle::Move::createPassMove();
else
- move = Quackle::Move::createExchangeMove(encodedLetters);
+ move = Quackle::Move::createExchangeMove(encodedLetters, isIntConvertable);
}
else
{
@@ -224,3 +232,19 @@ void TextBoard::positionChanged(const Quackle::GamePosition &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())));
}
+
+///////////
+
+void QLineEditWithShiftReturn::keyPressEvent(QKeyEvent * e)
+{
+ if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter)
+ {
+ if (e->modifiers() & Qt::ShiftModifier)
+ {
+ emit shiftReturnPressed();
+ return;
+ }
+ }
+ QLineEdit::keyPressEvent(e);
+}
+
diff --git a/quacker/boarddisplay.h b/quacker/boarddisplay.h
index 35ebcb7..fbb71c1 100644
--- a/quacker/boarddisplay.h
+++ b/quacker/boarddisplay.h
@@ -22,8 +22,10 @@
#include <move.h>
#include "view.h"
+#include <QLineEdit>
class QLineEdit;
+class QLineEditWithShiftReturn;
class QPushButton;
class QTextEdit;
class QVBoxLayout;
@@ -49,6 +51,7 @@ protected slots:
private slots:
void quickEditReturnPressed();
+ void quickEditShiftReturnPressed();
void plusFive();
void performCommit();
void reset();
@@ -60,7 +63,7 @@ protected:
QVBoxLayout *m_vlayout;
private:
- QLineEdit *m_lineEdit;
+ QLineEditWithShiftReturn *m_lineEdit;
QPushButton *m_commitButton;
Quackle::Move m_localCandidateMove;
};
@@ -79,4 +82,16 @@ private:
QTextEdit *m_textEdit;
};
+class QLineEditWithShiftReturn : public QLineEdit
+{
+Q_OBJECT
+
+signals:
+ void shiftReturnPressed();
+
+public:
+ virtual void keyPressEvent(QKeyEvent * e);
+};
+
+
#endif
diff --git a/quacker/configpages.cpp b/quacker/configpages.cpp
index 7cd0083..a424c15 100644
--- a/quacker/configpages.cpp
+++ b/quacker/configpages.cpp
@@ -103,10 +103,12 @@ InterfacePage::InterfacePage(QWidget *parent)
QGroupBox *miscellanyGroup = new QGroupBox(tr("Miscellany"));
m_vowelFirstCheck = new QCheckBox(tr("&Vowel-first alphabetizing"));
m_octothorpCheck = new QCheckBox(tr("&Octothorp British words"));
+ m_scoreInvalidAsZero = new QCheckBox(tr("&Score 0 for plays with illegal words"));
QGridLayout *miscellanyLayout = new QGridLayout;
miscellanyLayout->addWidget(m_vowelFirstCheck, 0, 0);
miscellanyLayout->addWidget(m_octothorpCheck, 1, 0);
+ miscellanyLayout->addWidget(m_scoreInvalidAsZero, 2, 0);
miscellanyGroup->setLayout(miscellanyLayout);
QVBoxLayout *mainLayout = new QVBoxLayout;
@@ -123,6 +125,7 @@ void InterfacePage::readConfig()
m_verboseLabelsCheck->setChecked(QuackerSettings::self()->verboseLabels);
m_scoreLabelsCheck->setChecked(QuackerSettings::self()->scoreLabels);
m_octothorpCheck->setChecked(QuackleIO::UtilSettings::self()->octothorpBritish);
+ m_scoreInvalidAsZero->setChecked(QuackleIO::UtilSettings::self()->scoreInvalidAsZero);
}
void InterfacePage::writeConfig()
@@ -132,5 +135,6 @@ void InterfacePage::writeConfig()
QuackerSettings::self()->verboseLabels = m_verboseLabelsCheck->isChecked();
QuackerSettings::self()->scoreLabels = m_scoreLabelsCheck->isChecked();
QuackleIO::UtilSettings::self()->octothorpBritish = m_octothorpCheck->isChecked();
+ QuackleIO::UtilSettings::self()->scoreInvalidAsZero = m_scoreInvalidAsZero->isChecked();
}
diff --git a/quacker/configpages.h b/quacker/configpages.h
index 6d02ec0..6b61375 100644
--- a/quacker/configpages.h
+++ b/quacker/configpages.h
@@ -52,6 +52,7 @@ private:
QCheckBox *m_verboseLabelsCheck;
QCheckBox *m_scoreLabelsCheck;
QCheckBox *m_octothorpCheck;
+ QCheckBox *m_scoreInvalidAsZero;
QComboBox *m_britishColoringCombo;
};
diff --git a/quacker/graphicalboard.cpp b/quacker/graphicalboard.cpp
index 71a29c6..37db686 100644
--- a/quacker/graphicalboard.cpp
+++ b/quacker/graphicalboard.cpp
@@ -327,7 +327,7 @@ QPoint GraphicalBoardFrame::coordinatesOfMark(const QSize &loc)
void GraphicalBoardFrame::drawMove(const Quackle::Move &move)
{
- if (move.action == Quackle::Move::Place)
+ if (move.action == Quackle::Move::Place || move.action == Quackle::Move::PlaceError)
{
if (move.tiles().empty())
return;
diff --git a/quacker/graphicalreporter.cpp b/quacker/graphicalreporter.cpp
index 8b95536..39e74ef 100644
--- a/quacker/graphicalreporter.cpp
+++ b/quacker/graphicalreporter.cpp
@@ -161,6 +161,7 @@ void GraphicalReporter::reportPosition(const Quackle::GamePosition &position, Qu
switch ((*it).action)
{
case Quackle::Move::Place:
+ case Quackle::Move::PlaceError:
{
if (m_generateImages)
{
@@ -193,6 +194,7 @@ void GraphicalReporter::reportPosition(const Quackle::GamePosition &position, Qu
}
case Quackle::Move::Exchange:
+ case Quackle::Move::BlindExchange:
default:
item = QuackleIO::Util::moveToDetailedString(*it);
break;
diff --git a/quacker/letterbox.cpp b/quacker/letterbox.cpp
index 8834b61..492f24d 100644
--- a/quacker/letterbox.cpp
+++ b/quacker/letterbox.cpp
@@ -241,6 +241,7 @@ void Letterbox::loadFile()
QTextStream stream(&file);
QString line;
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
m_initializationChuu = true;
@@ -526,6 +527,7 @@ void Letterbox::outputResults()
}
QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
if (m_numberIterator < m_clueResults.count())
stream << "\" Resume: " << m_numberIterator << "\n";
@@ -562,6 +564,7 @@ void Letterbox::outputResults()
}
QTextStream stream(&missesFile);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
for (ClueResultList::iterator it = m_clueResults.begin(); it != m_clueResults.end(); ++it)
{
@@ -927,6 +930,7 @@ void Letterbox::print()
setModified(wasModified);
QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream << printer.html() << "\n";
file.close();
@@ -957,6 +961,7 @@ void Letterbox::printStudy()
jumpTo(m_clueResults.size() - 1);
QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream << generateStudySheet(m_answers.begin(), m_answers.end()) << "\n";
file.close();
@@ -1178,7 +1183,9 @@ void HTMLRepresentation::setHTML(const QString &text, ContentType type)
QString HTMLRepresentation::html()
{
- return m_html;
+ return QString("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>") +
+ m_html +
+ QString("</body></html>");
}
void HTMLRepresentation::setWords(ClueResultList::ConstIterator start, ClueResultList::ConstIterator end, bool revers)
diff --git a/quacker/lister.cpp b/quacker/lister.cpp
index 87ee9a6..316ac49 100644
--- a/quacker/lister.cpp
+++ b/quacker/lister.cpp
@@ -242,6 +242,7 @@ void ListerDialog::openFile()
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
QString line;
while (!stream.atEnd())
{
@@ -439,6 +440,7 @@ QString ListerDialog::writeList(bool alphagrams)
}
QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
QMap<QString, Dict::WordList> map(anagramMap());
diff --git a/quacker/quacker.cpp b/quacker/quacker.cpp
index d979256..f7cc336 100644
--- a/quacker/quacker.cpp
+++ b/quacker/quacker.cpp
@@ -215,36 +215,6 @@ void TopLevel::commit()
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();
@@ -325,6 +295,7 @@ void TopLevel::setCandidateMove(const Quackle::Move &move)
}
else
{
+ bool playHasIllegalWords = false;
int validityFlags = m_game->currentPosition().validateMove(prettiedMove);
bool carryOn = true;
@@ -353,7 +324,16 @@ void TopLevel::setCandidateMove(const Quackle::Move &move)
}
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)));
+ QMessageBox mb(QMessageBox::Question, tr("Verify Play"),
+ 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)));
+ QPushButton* mb_yes = mb.addButton(QMessageBox::Yes);
+ mb.addButton(QMessageBox::No);
+ QPushButton* mb_unknownRacks = mb.addButton(tr("Assume unknown racks for this game"), QMessageBox::ApplyRole);
+ mb.exec();
+ if (mb.clickedButton() == mb_yes || mb.clickedButton() == mb_unknownRacks)
+ carryOn = true;
+ if (mb.clickedButton() == mb_unknownRacks)
+ m_game->currentPosition().currentPlayer().setRacksAreKnown(false);
}
validityFlags ^= Quackle::GamePosition::InvalidTiles;
@@ -384,6 +364,8 @@ void TopLevel::setCandidateMove(const Quackle::Move &move)
{
prettiedMove.setIsChallengedPhoney(true);
}
+ else
+ playHasIllegalWords = true;
}
validityFlags ^= Quackle::GamePosition::UnacceptableWord;
@@ -401,13 +383,18 @@ void TopLevel::setCandidateMove(const Quackle::Move &move)
if (!carryOn)
return;
- m_game->currentPosition().scoreMove(prettiedMove);
+ if (playHasIllegalWords && QuackleIO::UtilSettings::self()->scoreInvalidAsZero)
+ prettiedMove.score = 0;
+ else
+ 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()))
+ if (!m_game->currentPosition().currentPlayer().racksAreKnown() &&
+ !m_game->currentPosition().currentPlayer().rack().contains(prettiedMove.usedTiles()) &&
+ prettiedMove.action != Quackle::Move::BlindExchange)
{
m_game->currentPosition().setCurrentPlayerRack(Quackle::Rack(prettiedMove.usedTiles()));
}
@@ -797,7 +784,14 @@ void TopLevel::setCaption(const QString &text)
if (!text.isNull())
m_ourCaption = text;
- setWindowTitle(QString("%1[*] - Quackle").arg(m_ourCaption));
+ if (m_filename.isEmpty())
+ setWindowTitle(QString("%1 - Quackle").arg(m_ourCaption));
+ else
+ {
+ QString filename = QDir::fromNativeSeparators(m_filename);
+ filename = filename.mid(filename.lastIndexOf('/') + 1);
+ setWindowTitle(QString("%1[*] - %2 - Quackle").arg(filename, m_ourCaption));
+ }
}
void TopLevel::setModified(bool modified)
@@ -1336,6 +1330,7 @@ void TopLevel::reportAs(Quackle::ComputerPlayer *player)
Quackle::ComputerPlayer *clone = player->clone();
QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
QuackleIO::StreamingReporter::reportGame(*m_game, clone, stream);
delete clone;
}
@@ -2094,6 +2089,7 @@ void TopLevel::writeAsciiToFile(const QString &text, const QString &filename)
}
QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream << text << "\n";
file.close();
@@ -2119,6 +2115,7 @@ void TopLevel::print()
}
QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
//stream << printer.html() << "\n";
file.close();
@@ -2135,9 +2132,9 @@ void TopLevel::firstTimeRun()
void TopLevel::about()
{
QString aboutText = tr(
-"<p><b>Quackle</b> 1.0.1 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><b>Quackle</b> 1.0.3 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-2015 by</p>"
+"<p>Copyright 2005-2016 by</p>"
"<ul>"
"<li>Jason Katz-Brown &lt;jasonkatzbrown@gmail.com&gt;</li>"
"<li>John O'Laughlin &lt;olaughlin@gmail.com&gt;</li>"
@@ -2163,7 +2160,7 @@ void TopLevel::about()
fclose(file);
aboutText += "</ul>";
}
- QMessageBox::about(this, tr("About Quackle 1.0.1"), dialogText(aboutText));
+ QMessageBox::about(this, tr("About Quackle 1.0.3"), dialogText(aboutText));
}
void TopLevel::hints()
diff --git a/quacker/quacker.plist b/quacker/quacker.plist
index eef0ac9..3969d54 100644
--- a/quacker/quacker.plist
+++ b/quacker/quacker.plist
@@ -13,7 +13,7 @@
<key>CFBundleName</key>
<string>Quackle</string>
<key>CFBundleShortVersionString</key>
- <string>1.0.0</string>
+ <string>1.0.3</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSApplicationCategoryType</key>
@@ -21,6 +21,6 @@
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHumanReadableCopyright</key>
- <string>Copyright &copy; 2005-2015 by Jason Katz-Brown & John O'Laughlin</string>
+ <string>Copyright &copy; 2005-2016 by Jason Katz-Brown & John O'Laughlin</string>
</dict>
</plist>
diff --git a/quacker/quacker.pro b/quacker/quacker.pro
index dcf2954..7bc5a6d 100644
--- a/quacker/quacker.pro
+++ b/quacker/quacker.pro
@@ -1,5 +1,5 @@
TEMPLATE = app
-VERSION = 1.0.1
+VERSION = 1.0.3
TARGET = Quackle
DEPENDPATH += .. ../quackleio
INCLUDEPATH += . ..
diff --git a/quacker/quackersettings.cpp b/quacker/quackersettings.cpp
index 1889848..510e43a 100644
--- a/quacker/quackersettings.cpp
+++ b/quacker/quackersettings.cpp
@@ -45,6 +45,7 @@ void QuackerSettings::readSettings()
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();
+ QuackleIO::UtilSettings::self()->scoreInvalidAsZero = settings.value("quackle/settings/score-invalid-as-zero", QuackleIO::UtilSettings::self()->scoreInvalidAsZero).toBool();
m_letterboxSettings.readSettings();
}
@@ -57,6 +58,7 @@ void QuackerSettings::writeSettings()
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);
+ settings.setValue("quackle/settings/score-invalid-as-zero", QuackleIO::UtilSettings::self()->scoreInvalidAsZero);
m_letterboxSettings.writeSettings();
}
diff --git a/quackleio/gcgio.cpp b/quackleio/gcgio.cpp
index cafbf49..b60d57c 100644
--- a/quackleio/gcgio.cpp
+++ b/quackleio/gcgio.cpp
@@ -30,6 +30,23 @@ GCGIO::GCGIO()
{
}
+Quackle::Game *GCGIO::read(const QString &filename, int flags)
+{
+ QFile file(filename);
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ UVcerr << "Could not open gcg " << QuackleIO::Util::qstringToString(filename) << endl;
+ return new Quackle::Game;
+ }
+
+ QTextStream in(&file);
+ Quackle::Game *ret = read(in, flags);
+ file.close();
+
+ return ret;
+}
+
Quackle::Game *GCGIO::read(QTextStream &stream, int flags)
{
Quackle::Game *ret = new Quackle::Game;
@@ -43,6 +60,7 @@ Quackle::Game *GCGIO::read(QTextStream &stream, int flags)
bool gameStarted = false;
QString line;
+ stream.setCodec(QTextCodec::codecForName("ISO-8859-1"));
while (!stream.atEnd())
{
line = stream.readLine();
@@ -111,6 +129,11 @@ Quackle::Game *GCGIO::read(QTextStream &stream, int flags)
incompleteRack = Util::encode(rackString);
hasIncompleteRack = true;
}
+ else if (line.startsWith("#character-encoding"))
+ {
+ QString encoding{line.right(line.length() - 20).trimmed()};
+ stream.setCodec(QTextCodec::codecForName(encoding.toAscii()));
+ }
}
else if (line.startsWith(">"))
{
@@ -120,6 +143,7 @@ Quackle::Game *GCGIO::read(QTextStream &stream, int flags)
gameStarted = true;
}
+ UVString currentPlayer = Util::qstringToString(strings.front().mid(1, strings.front().size() - 2));
strings.pop_front();
if (strings.isEmpty())
@@ -135,10 +159,11 @@ Quackle::Game *GCGIO::read(QTextStream &stream, int flags)
if (rackString.startsWith("(") && rackString.endsWith(")"))
{
// end the game
- if (ret->hasPositions())
+ if (ret->hasPositions() && !ret->currentPosition().gameOver())
ret->commitCandidate(canMaintainCrosses);
else
ret->addPosition();
+ ret->currentPosition().setTileBonus(currentPlayer, Util::encode(rackString.mid(1, rackString.size() - 2)), strings.front().toInt());
continue;
}
@@ -162,10 +187,21 @@ Quackle::Game *GCGIO::read(QTextStream &stream, int flags)
else if (firstMoveBite.startsWith("-"))
{
const QString exchangedLetters = firstMoveBite.right(firstMoveBite.length() - 1);
- if (exchangedLetters.isEmpty())
+ bool isLetterCount = false;
+ uint letterCount = exchangedLetters.toUInt(&isLetterCount);
+
+ if (exchangedLetters.isEmpty() || (isLetterCount && letterCount == 0))
move = Quackle::Move::createPassMove();
+ else if (isLetterCount)
+ {
+ Quackle::LetterString encodedLetters;
+
+ for (uint i = 0; i < letterCount; ++i)
+ encodedLetters.push_back(QUACKLE_BLANK_MARK);
+ move = Quackle::Move::createExchangeMove(encodedLetters, true);
+ }
else
- move = Quackle::Move::createExchangeMove(Util::encode(exchangedLetters));
+ move = Quackle::Move::createExchangeMove(Util::encode(exchangedLetters), false);
}
else if (firstMoveBite.startsWith("(time)"))
{
@@ -293,6 +329,7 @@ bool GCGIO::canRead(QTextStream &stream) const
void GCGIO::write(const Quackle::Game &game, QTextStream &stream)
{
Quackle::PlayerList players = game.players();
+ stream.setCodec(QTextCodec::codecForName("ISO-8859-1"));
for (Quackle::PlayerList::iterator it = players.begin(); it != players.end(); ++it)
{
stream << "#player" << (*it).id() + 1 << " " << Util::uvStringToQString((*it).abbreviatedName()) << " " << Util::uvStringToQString((*it).name()) << endl;
@@ -324,11 +361,14 @@ void GCGIO::write(const Quackle::Game &game, QTextStream &stream)
outputScoreAddition = 0;
}
- stream << ">" << Util::uvStringToQString((*it).currentPlayer().abbreviatedName()) << ": " << Util::letterStringToQString((*it).currentPlayer().rack().alphaTiles()) << " " << Util::uvStringToQString(move.toString()) << " +" << outputScore << " " << outputScore + (*it).currentPlayer().score() << endl;
+ QString rackString = Util::letterStringToQString((*it).currentPlayer().rack().alphaTiles());
+ if (move.action == Quackle::Move::UnusedTilesBonusError)
+ rackString = QString();
+ stream << ">" << Util::uvStringToQString((*it).currentPlayer().abbreviatedName()) << ": " << rackString << " " << Util::uvStringToQString(move.toString()) << " +" << outputScore << " " << outputScore + (*it).currentPlayer().score() << endl;
if (move.isChallengedPhoney())
{
- stream << ">" << Util::uvStringToQString((*it).currentPlayer().abbreviatedName()) << ": " << Util::letterStringToQString((*it).currentPlayer().rack().alphaTiles()) << " -- -" << outputScore << " " << move.effectiveScore() + (*it).currentPlayer().score() << endl;
+ stream << ">" << Util::uvStringToQString((*it).currentPlayer().abbreviatedName()) << ": " << rackString << " -- -" << outputScore << " " << move.effectiveScore() + (*it).currentPlayer().score() << endl;
}
if (outputScoreAddition != 0)
diff --git a/quackleio/gcgio.h b/quackleio/gcgio.h
index 477f84f..a219c9f 100644
--- a/quackleio/gcgio.h
+++ b/quackleio/gcgio.h
@@ -30,6 +30,7 @@ public:
GCGIO();
~GCGIO() {};
+ virtual Quackle::Game *read(const QString &filename, int flags);
virtual Quackle::Game *read(QTextStream &stream, int flags);
virtual bool canRead(QTextStream &stream) const;
virtual void write(const Quackle::Game &game, QTextStream &stream);
diff --git a/quackleio/util.cpp b/quackleio/util.cpp
index cfd8672..2fca766 100644
--- a/quackleio/util.cpp
+++ b/quackleio/util.cpp
@@ -34,7 +34,7 @@ UtilSettings *UtilSettings::self()
}
UtilSettings::UtilSettings()
- : octothorpBritish(true), vowelFirst(false)
+ : octothorpBritish(true), vowelFirst(false), scoreInvalidAsZero(false)
{
m_self = this;
}
@@ -55,8 +55,15 @@ QString Util::moveToDetailedString(const Quackle::Move &move)
ret = QObject::tr("Exch. %1").arg(prettyTiles);
break;
+ case Quackle::Move::BlindExchange:
+ ret = QObject::tr("Exch. %1").arg(move.tiles().length());
+ break;
+
+ case Quackle::Move::UnusedTilesBonusError:
case Quackle::Move::UnusedTilesBonus:
ret = QObject::tr("2*(%1)").arg(letterStringToQString(Util::alphagram(move.usedTiles())));
+ if (move.action == Quackle::Move::UnusedTilesBonusError)
+ ret += " [Endgame Error]";
break;
case Quackle::Move::TimePenalty:
@@ -68,6 +75,7 @@ QString Util::moveToDetailedString(const Quackle::Move &move)
break;
case Quackle::Move::Place:
+ case Quackle::Move::PlaceError:
ret = uvStringToQString(move.positionString()) + " ";
ret += prettyTiles;
@@ -77,6 +85,9 @@ QString Util::moveToDetailedString(const Quackle::Move &move)
if (move.isChallengedPhoney())
ret = QObject::tr("%1 [Challenged Off]").arg(ret);
+ if (move.action == Quackle::Move::PlaceError)
+ ret += " [Endgame Misdraw]";
+
break;
}
diff --git a/quackleio/util.h b/quackleio/util.h
index 10a57a2..62545bb 100644
--- a/quackleio/util.h
+++ b/quackleio/util.h
@@ -42,6 +42,7 @@ public:
bool octothorpBritish;
bool vowelFirst;
+ bool scoreInvalidAsZero;
private:
static UtilSettings *m_self;
diff --git a/rack.cpp b/rack.cpp
index 900763f..967514c 100644
--- a/rack.cpp
+++ b/rack.cpp
@@ -76,6 +76,15 @@ bool Rack::unload(const LetterString &used)
return ret;
}
+void Rack::load(const LetterString &tiles)
+{
+ for (LetterString::const_iterator it = tiles.begin(); it != tiles.end(); ++it)
+ {
+ if (it != QUACKLE_NULL_MARK)
+ m_tiles += *it;
+ }
+}
+
bool Rack::contains(const LetterString &used) const
{
return Rack(*this).unload(used);
diff --git a/rack.h b/rack.h
index 7bc7df8..a175c29 100644
--- a/rack.h
+++ b/rack.h
@@ -57,6 +57,8 @@ public:
// in this rack and unloaded
bool unload(const LetterString &used);
+ void load(const LetterString &tiles);
+
// same as above but nonmutating
bool contains(const LetterString &used) const;
@@ -99,7 +101,7 @@ inline bool Rack::empty() const
}
const Quackle::Rack operator-(const Quackle::Rack &rack, const Quackle::Move &move);
-const Quackle::Rack operator-(const Quackle::Rack &rack1, const Quackle::Rack &rack);
+const Quackle::Rack operator-(const Quackle::Rack &rack1, const Quackle::Rack &rack2);
UVOStream &operator<<(UVOStream &o, const Quackle::Rack &rack);
diff --git a/reporter.cpp b/reporter.cpp
index c844afa..602dd8b 100644
--- a/reporter.cpp
+++ b/reporter.cpp
@@ -156,6 +156,7 @@ void Reporter::reportPosition(const GamePosition &position, ComputerPlayer *comp
break;
case Move::Exchange:
+ case Move::BlindExchange:
s << MARK_UV("xch");
break;
@@ -163,6 +164,7 @@ void Reporter::reportPosition(const GamePosition &position, ComputerPlayer *comp
s << MARK_UV("pas");
break;
case Move::UnusedTilesBonus:
+ case Move::UnusedTilesBonusError:
case Move::TimePenalty:
case Move::Nonmove:
break;
diff --git a/sim.cpp b/sim.cpp
index f9e36a7..bd8a904 100644
--- a/sim.cpp
+++ b/sim.cpp
@@ -315,7 +315,7 @@ void Simulator::simulate(int plies)
{
LetterString deadwood;
deadwoodScore = m_simulatedGame.currentPosition().deadwood(&deadwood);
- // accont for deadwood in this move rather than a separate
+ // account for deadwood in this move rather than a separate
// UnusedTilesBonus move.
move.score += deadwoodScore;
}