/*
* Quackle -- Crossword game artificial intelligence and analysis tool
* Copyright (C) 2005-2019 Jason Katz-Brown, John O'Laughlin, and John Fultz.
*
* 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 3 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, see .
*/
#ifndef QUACKLE_SIM_H
#define QUACKLE_SIM_H
#include
#include
#include
#include
#include
#include
#include
#include "alphabetparameters.h"
#include "game.h"
using std::vector;
namespace Quackle
{
class ComputerDispatch;
struct AveragedValue
{
// new zeroed value
AveragedValue()
: m_valueSum(0), m_squaredValueSum(0), m_incorporatedValues(0)
{
}
void incorporateValue(double newValue);
// zero everything
void clear();
long double valueSum() const;
long double squaredValueSum() const;
long int incorporatedValues() const;
// whether or not incorporatedValues is greater than zero
bool hasValues() const;
// valueSum() / incorporatedValues() or zero
// if there have been no incorporated values
double averagedValue() const;
// sqrt((incorporatedValues() * squaredValueSum() - valueSum()^2) / (incorporatedValues() * (incorporatedValues() - 1)))
double standardDeviation() const;
private:
long double m_valueSum;
long double m_squaredValueSum;
long int m_incorporatedValues;
};
inline void AveragedValue::incorporateValue(double newValue)
{
m_valueSum += newValue;
m_squaredValueSum += newValue * newValue;
++m_incorporatedValues;
}
inline long double AveragedValue::valueSum() const
{
return m_valueSum;
}
inline long double AveragedValue::squaredValueSum() const
{
return m_squaredValueSum;
}
inline double AveragedValue::averagedValue() const
{
return m_incorporatedValues == 0? 0 : (m_valueSum / m_incorporatedValues);
}
inline long int AveragedValue::incorporatedValues() const
{
return m_incorporatedValues;
}
inline bool AveragedValue::hasValues() const
{
return m_incorporatedValues > 0;
}
struct PositionStatistics
{
enum StatisticType { StatisticScore, StatisticBingos };
AveragedValue getStatistic(StatisticType type) const;
AveragedValue score;
AveragedValue bingos;
};
typedef vector PositionStatisticsList;
struct Level
{
// expand the scores list to be at least number long
void setNumberScores(unsigned int number);
PositionStatisticsList statistics;
};
class LevelList : public vector
{
public:
// expand the levels list to be at least number long
void setNumberLevels(unsigned int number);
};
struct SimmedMove
{
SimmedMove(const Move &_move) :
move(_move),
m_id(++objectIdCounter),
m_includeInSimulation(true) { }
// + our scores - their scores + residual, except if we have no levels,
// in which case returns move.equity
double calculateEquity() const;
// average wins value * 100, except if we have no win percentage data,
// in which case returns move.win
double calculateWinPercentage() const;
// clear all level values
void clear();
bool includeInSimulation() const;
void setIncludeInSimulation(bool includeInSimulation);
long id() const { return m_id; };
Move move;
LevelList levels;
AveragedValue residual;
AveragedValue gameSpread;
AveragedValue wins;
PositionStatistics getPositionStatistics(int level, int playerIndex) const;
private:
long m_id;
bool m_includeInSimulation;
static std::atomic_long objectIdCounter;
};
inline bool SimmedMove::includeInSimulation() const
{
return m_includeInSimulation;
}
inline void SimmedMove::setIncludeInSimulation(bool includeInSimulation)
{
m_includeInSimulation = includeInSimulation;
}
typedef vector SimmedMoveList;
class SimmedMoveMessage
{
public:
long id;
Move move;
LevelList levels;
vector score;
vector bingos;
double residual;
double gameSpread;
double wins;
bool bogowin;
std::ostringstream logStream;
UVString xmlIndent;
};
class SimmedMoveConstants
{
public:
Game game;
int startPlayerId;
int playerCount;
int decimalTurns;
int levelCount;
bool ignoreOppos;
bool isLogging;
};
class SimmedMoveMessageQueue
{
public:
SimmedMoveMessageQueue() = default;
SimmedMoveMessageQueue(SimmedMoveMessageQueue&) = delete;
SimmedMoveMessageQueue(SimmedMoveMessageQueue&&) = delete;
void push(SimmedMoveMessage& msg);
SimmedMoveMessage pop();
std::pair pop_or_terminate();
void send_terminate_all();
void send_terminate_one(const std::thread::id& id);
const SimmedMoveConstants& constants() { return m_constants; };
void setConstants(const SimmedMoveConstants& constants) { m_constants = constants; };
private:
SimmedMoveConstants m_constants;
std::queue m_queue;
std::condition_variable m_condition;
std::mutex m_queueMutex;
bool m_terminateAll = false;
std::thread::id m_terminateOne;
};
class Simulator
{
public:
// constructs a new simulator
Simulator();
Simulator(const Simulator&) = delete;
Simulator(Simulator&&) = delete;
~Simulator();
// Simulate moves on this position. Also initializes the
// move list, rack, resets numbers, and closes logfile.
void setPosition(const GamePosition &position);
// get access to the position that starts each playahead of the
// simulation; use to rechange rack or scores etcetera
GamePosition ¤tPosition();
const GamePosition ¤tPosition() const;
const History &history() const;
// If logfile is an empty string, logging is disabled.
// If logfile is the same logfile as currently set, nothing
// happens. If it is different, old logfile is closed if it
// was open. If append is false, this destroys file contents
// already in logfile.
void setLogfile(const string &logfile, bool append = true);
string logfile() const;
// Will honor dispatch->shouldAbort() but won't signal
// any doneness for now.
void setDispatch(ComputerDispatch *dispatch);
ComputerDispatch *dispatch() const;
// append message to logfile if one is open
void logMessage(const UVString &message);
bool isLogging() const;
void closeLogfile();
// Set moves to include in simulation. Moves that
// are in the simmed list now that are not in this given
// list still live in the simmed list but are not iterated thru
// during simulation. Moves that were not in the simmed list are added
// to front of simmed move list and level values zeroed.
void setIncludedMoves(const MoveList &moves);
void makeSureConsideredMovesAreIncluded();
void moveConsideredMovesToBeginning(MoveList &moves) const;
// moves that are immune from pruning
void setConsideredMoves(const MoveList &moves);
const MoveList &consideredMoves() const;
void addConsideredMove(const Move &move);
bool isConsideredMove(const Move &move) const;
// include only currently included moves that are within
// equityThreshold points below the best play and cap at
// maxNumberOfMoves
void pruneTo(double equityThreshold, int maxNumberOfMoves);
// if ignore is true, oppos will always pass in simulation
void setIgnoreOppos(bool ignore);
bool ignoreOppos() const;
static void simThreadFunc(SimmedMoveMessageQueue& incoming, SimmedMoveMessageQueue& outgoing);
void setThreadCount(size_t count);
// set values for all levels of all moves to zero
void resetNumbers();
// Run a chunk of the simulation.
// If plies is negative, simulation runs to end of game.
// Iterations is how many iterations to run before returning;
// more iterations can be computed and incorporated by recalling
// simulate().
void simulate(int plies, int iterations);
// simulate one iteration
void simulate(int plies);
static void simulateOnePosition(SimmedMoveMessage &message, const SimmedMoveConstants &constants);
// Incorporate the results of a single simulation into the
// cumulative results
void incorporateMessage(const SimmedMoveMessage &message);
// Set oppo's rack to some partially-known tiles.
// Set this to an empty rack if no tiles are known, so
// that all tiles are chosen randomly each iteration.
void setPartialOppoRack(const Rack &rack);
const Rack &partialOppoRack() const;
// Set oppo's racks to something random, including
// tiles specified by setPartialOppoRack above.
// Possibly inference-aided randomness.
void randomizeOppoRacks();
// set drawing order for the first refill
void randomizeDrawingOrder();
// returns maximal number of iterations over all moves since
// resetting numbers
int iterations() const;
// returns true if any iterations have been run
bool hasSimulationResults() const;
// full simulation information
const SimmedMoveList &simmedMoves() const;
// Return the moves sorted by simulated equity.
// If prune is true, does not include plays that aren't included
// in simulation anymore.
MoveList moves(bool prune = false, bool byWin = false) const;
const SimmedMove &simmedMoveForMove(const Move &move) const;
int numLevels() const;
int numPlayersAtLevel(int levelIndex) const;
protected:
void writeLogHeader();
void writeLogFooter();
UVOFStream m_logfileStream;
string m_logfile;
bool m_logfileIsOpen;
bool m_hasHeader;
UVString m_xmlIndent;
Rack m_partialOppoRack;
Game m_originalGame;
ComputerDispatch *m_dispatch;
SimmedMoveList m_simmedMoves;
// moves that are immune from pruning
MoveList m_consideredMoves;
int m_iterations;
bool m_ignoreOppos;
// Pair of thread and bool requesting to terminate
std::vector m_threadPool;
SimmedMoveMessageQueue m_sendQueue;
SimmedMoveMessageQueue m_receiveQueue;
};
inline GamePosition &Simulator::currentPosition()
{
return m_originalGame.currentPosition();
}
inline const GamePosition &Simulator::currentPosition() const
{
return m_originalGame.currentPosition();
}
inline const History &Simulator::history() const
{
return m_originalGame.history();
}
inline ComputerDispatch *Simulator::dispatch() const
{
return m_dispatch;
}
inline string Simulator::logfile() const
{
return m_logfile;
}
inline bool Simulator::isLogging() const
{
return m_logfileIsOpen;
}
inline const Rack &Simulator::partialOppoRack() const
{
return m_partialOppoRack;
}
inline void Simulator::setConsideredMoves(const MoveList &moves)
{
m_consideredMoves = moves;
}
inline const MoveList &Simulator::consideredMoves() const
{
return m_consideredMoves;
}
inline void Simulator::setIgnoreOppos(bool ignore)
{
m_ignoreOppos = ignore;
}
inline bool Simulator::ignoreOppos() const
{
return m_ignoreOppos;
}
inline int Simulator::iterations() const
{
return m_iterations;
}
inline bool Simulator::hasSimulationResults() const
{
return m_iterations > 0;
}
inline const SimmedMoveList &Simulator::simmedMoves() const
{
return m_simmedMoves;
}
}
UVOStream& operator<<(UVOStream& o, const Quackle::AveragedValue &value);
UVOStream& operator<<(UVOStream& o, const Quackle::Level &level);
UVOStream& operator<<(UVOStream& o, const Quackle::SimmedMove &move);
UVOStream& operator<<(UVOStream& o, const Quackle::SimmedMoveList &move);
#endif