mirror of
git://git.savannah.nongnu.org/eliot.git
synced 2025-01-17 06:11:49 +01:00
Sorting the search results happens to be quite slow, often much slower than the search itself.
But we don't need to sort them all the time, and in general we don't even need to keep all the rounds. This commit greatly improves the search performance by filtering the results in 3 different ways, depending on the context: - A limit to the number of results can be given (useful for the training mode). The kept results are the best ones, not the first ones found by the search. - When only the best round is needed (when the AI is playing with level 100, or when preparing the rack for an explosive game), we don't need to keep rounds with a lower score - When the AI has a level lower than 100, it is still possible to skip many rounds The search limit in training mode is configurable (defaulting to 100) and can be deactivated.
This commit is contained in:
parent
771ba0c35e
commit
87e1d4795b
12 changed files with 399 additions and 77 deletions
|
@ -29,29 +29,39 @@
|
||||||
|
|
||||||
|
|
||||||
AIPercent::AIPercent(float iPercent)
|
AIPercent::AIPercent(float iPercent)
|
||||||
: m_percent(iPercent)
|
|
||||||
{
|
{
|
||||||
// Ensure the decimal value of the percentage is between 0 and 1
|
if (iPercent < 0)
|
||||||
if (m_percent < 0)
|
iPercent = 0;
|
||||||
m_percent = 0;
|
if (iPercent > 1)
|
||||||
if (m_percent > 1)
|
iPercent = 1;
|
||||||
m_percent = 1;
|
|
||||||
|
// Use BestResults to be slightly faster when the percentage is 100%
|
||||||
|
if (iPercent == 1)
|
||||||
|
m_results = new BestResults;
|
||||||
|
else
|
||||||
|
m_results = new PercentResults(iPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AIPercent::~AIPercent()
|
||||||
|
{
|
||||||
|
delete m_results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AIPercent::compute(const Dictionary &iDic, const Board &iBoard, bool iFirstWord)
|
void AIPercent::compute(const Dictionary &iDic, const Board &iBoard, bool iFirstWord)
|
||||||
{
|
{
|
||||||
m_results.clear();
|
m_results->clear();
|
||||||
|
|
||||||
Rack rack;
|
Rack rack;
|
||||||
getCurrentRack().getRack(rack);
|
getCurrentRack().getRack(rack);
|
||||||
m_results.search(iDic, iBoard, rack, iFirstWord);
|
m_results->search(iDic, iBoard, rack, iFirstWord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Move AIPercent::getMove() const
|
Move AIPercent::getMove() const
|
||||||
{
|
{
|
||||||
if (m_results.size() == 0)
|
if (m_results->size() == 0)
|
||||||
{
|
{
|
||||||
// If there is no result, pass the turn.
|
// If there is no result, pass the turn.
|
||||||
// XXX: it is forbidden in duplicate mode (even passing is forbidden),
|
// XXX: it is forbidden in duplicate mode (even passing is forbidden),
|
||||||
|
@ -60,25 +70,7 @@ Move AIPercent::getMove() const
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If there are results, apply the algorithm
|
return Move(m_results->get(0));
|
||||||
double wantedScore = m_percent * m_results.get(0).getPoints();
|
|
||||||
// Look for the first round giving at least 'wantedScore' points
|
|
||||||
// Browse the results 10 by 10 (a dichotomy would be better, but this
|
|
||||||
// is not performance critical)
|
|
||||||
unsigned int index = 0;
|
|
||||||
while (index < m_results.size() &&
|
|
||||||
m_results.get(index).getPoints() > wantedScore)
|
|
||||||
{
|
|
||||||
index += 10;
|
|
||||||
}
|
|
||||||
// Now the wanted round is in the last 10 indices
|
|
||||||
if (index >= m_results.size())
|
|
||||||
index = m_results.size() - 1;
|
|
||||||
while (m_results.get(index).getPoints() < wantedScore)
|
|
||||||
{
|
|
||||||
--index;
|
|
||||||
}
|
|
||||||
return Move(m_results.get(index));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ class AIPercent: public AIPlayer
|
||||||
public:
|
public:
|
||||||
/// Constructor, taking the percentage (0.0 <= iPercent <= 1.0)
|
/// Constructor, taking the percentage (0.0 <= iPercent <= 1.0)
|
||||||
AIPercent(float iPercent);
|
AIPercent(float iPercent);
|
||||||
virtual ~AIPercent() {}
|
virtual ~AIPercent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method does the actual computation. It will be called before any
|
* This method does the actual computation. It will be called before any
|
||||||
|
@ -52,11 +52,8 @@ public:
|
||||||
virtual Move getMove() const;
|
virtual Move getMove() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Percentage used for this player
|
|
||||||
float m_percent;
|
|
||||||
|
|
||||||
/// Container for all the found solutions
|
/// Container for all the found solutions
|
||||||
Results m_results;
|
Results *m_results;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -331,7 +331,7 @@ PlayedRack Game::helperSetRackRandom(const PlayedRack &iPld,
|
||||||
Rack rack;
|
Rack rack;
|
||||||
pld.getRack(rack);
|
pld.getRack(rack);
|
||||||
|
|
||||||
Results res;
|
BestResults res;
|
||||||
res.search(getDic(), getBoard(), rack, getHistory().beforeFirstRound());
|
res.search(getDic(), getBoard(), rack, getHistory().beforeFirstRound());
|
||||||
if (res.size())
|
if (res.size())
|
||||||
{
|
{
|
||||||
|
|
217
game/results.cpp
217
game/results.cpp
|
@ -19,9 +19,11 @@
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include <boost/foreach.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cwctype>
|
#include <cwctype>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "tile.h"
|
#include "tile.h"
|
||||||
#include "round.h"
|
#include "round.h"
|
||||||
|
@ -98,27 +100,204 @@ const Round & Results::get(unsigned int i) const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Results::search(const Dictionary &iDic, const Board &iBoard,
|
void Results::sort()
|
||||||
const Rack &iRack, bool iFirstWord)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
|
|
||||||
if (iFirstWord)
|
|
||||||
{
|
|
||||||
iBoard.searchFirst(iDic, iRack, *this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
iBoard.search(iDic, iRack, *this);
|
|
||||||
}
|
|
||||||
|
|
||||||
sortByPoints();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Results::sortByPoints()
|
|
||||||
{
|
{
|
||||||
less_points lp;
|
less_points lp;
|
||||||
std::sort(m_rounds.begin(), m_rounds.end(), lp);
|
std::sort(m_rounds.begin(), m_rounds.end(), lp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BestResults::BestResults()
|
||||||
|
: m_bestScore(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BestResults::search(const Dictionary &iDic, const Board &iBoard,
|
||||||
|
const Rack &iRack, bool iFirstWord)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
|
||||||
|
if (iFirstWord)
|
||||||
|
iBoard.searchFirst(iDic, iRack, *this);
|
||||||
|
else
|
||||||
|
iBoard.search(iDic, iRack, *this);
|
||||||
|
|
||||||
|
sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BestResults::add(const Round &iRound)
|
||||||
|
{
|
||||||
|
// Ignore too low scores
|
||||||
|
if (m_bestScore > iRound.getPoints())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_bestScore < iRound.getPoints())
|
||||||
|
{
|
||||||
|
// New best score: clear the stored results
|
||||||
|
m_bestScore = iRound.getPoints();
|
||||||
|
m_rounds.clear();
|
||||||
|
}
|
||||||
|
m_rounds.push_back(iRound);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BestResults::clear()
|
||||||
|
{
|
||||||
|
m_rounds.clear();
|
||||||
|
m_bestScore = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PercentResults::PercentResults(float iPercent)
|
||||||
|
: m_percent(iPercent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class Predicate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Predicate(int iPoints) : m_chosenPoints(iPoints) {}
|
||||||
|
bool operator()(const Round &iRound) const
|
||||||
|
{
|
||||||
|
return iRound.getPoints() != m_chosenPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int m_chosenPoints;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void PercentResults::search(const Dictionary &iDic, const Board &iBoard,
|
||||||
|
const Rack &iRack, bool iFirstWord)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
|
||||||
|
if (iFirstWord)
|
||||||
|
iBoard.searchFirst(iDic, iRack, *this);
|
||||||
|
else
|
||||||
|
iBoard.search(iDic, iRack, *this);
|
||||||
|
|
||||||
|
if (m_rounds.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// At this point, add() has been called, so the best score is valid
|
||||||
|
|
||||||
|
// Find the lowest score at least equal to the min_score
|
||||||
|
int chosenPoints = m_bestScore;
|
||||||
|
BOOST_FOREACH(const Round &iRound, m_rounds)
|
||||||
|
{
|
||||||
|
int points = iRound.getPoints();
|
||||||
|
if (points >= m_minScore && points < chosenPoints)
|
||||||
|
{
|
||||||
|
chosenPoints = points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep only the rounds with the "chosenPoints" score
|
||||||
|
std::remove_if(m_rounds.begin(), m_rounds.end(), Predicate(chosenPoints));
|
||||||
|
ASSERT(!m_rounds.empty(), "Bug in PercentResults");
|
||||||
|
|
||||||
|
// Sort the remaining rounds
|
||||||
|
sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PercentResults::add(const Round &iRound)
|
||||||
|
{
|
||||||
|
// Ignore too low scores
|
||||||
|
if (m_minScore > iRound.getPoints())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_bestScore < iRound.getPoints())
|
||||||
|
{
|
||||||
|
m_bestScore = iRound.getPoints();
|
||||||
|
m_minScore = (int)ceil(m_bestScore * m_percent);
|
||||||
|
}
|
||||||
|
m_rounds.push_back(iRound);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PercentResults::clear()
|
||||||
|
{
|
||||||
|
m_rounds.clear();
|
||||||
|
m_bestScore = 0;
|
||||||
|
m_minScore = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
LimitResults::LimitResults(int iLimit)
|
||||||
|
: m_limit(iLimit), m_total(0), m_minScore(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LimitResults::search(const Dictionary &iDic, const Board &iBoard,
|
||||||
|
const Rack &iRack, bool iFirstWord)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
|
||||||
|
if (iFirstWord)
|
||||||
|
iBoard.searchFirst(iDic, iRack, *this);
|
||||||
|
else
|
||||||
|
iBoard.search(iDic, iRack, *this);
|
||||||
|
|
||||||
|
if (m_rounds.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Sort the rounds
|
||||||
|
sort();
|
||||||
|
|
||||||
|
// Truncate the results to respect the limit
|
||||||
|
if (m_limit != 0 && m_rounds.size() > (unsigned int) m_limit)
|
||||||
|
m_rounds.resize(m_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LimitResults::add(const Round &iRound)
|
||||||
|
{
|
||||||
|
// If we ignore the limit, simply add the round
|
||||||
|
if (m_limit == 0)
|
||||||
|
{
|
||||||
|
m_rounds.push_back(iRound);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore too low scores
|
||||||
|
if (m_minScore >= iRound.getPoints())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add the round
|
||||||
|
m_rounds.push_back(iRound);
|
||||||
|
++m_total;
|
||||||
|
++m_scoresCount[iRound.getPoints()];
|
||||||
|
|
||||||
|
// Can we increase the minimum score required?
|
||||||
|
if (m_total - m_scoresCount[m_minScore] >= m_limit)
|
||||||
|
{
|
||||||
|
// Yes! "Forget" the rounds of score m_minScore
|
||||||
|
// They are still present in m_rounds, but they will be removed
|
||||||
|
// for real later in the search() method
|
||||||
|
m_total -= m_scoresCount[m_minScore];
|
||||||
|
m_scoresCount.erase(m_minScore);
|
||||||
|
|
||||||
|
// Find the new min score
|
||||||
|
map<int, int>::const_iterator it =
|
||||||
|
m_scoresCount.lower_bound(m_minScore);
|
||||||
|
ASSERT(it != m_scoresCount.end(), "Bug in LimitResults::add())");
|
||||||
|
m_minScore = it->first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LimitResults::clear()
|
||||||
|
{
|
||||||
|
m_rounds.clear();
|
||||||
|
m_scoresCount.clear();
|
||||||
|
m_minScore = -1;
|
||||||
|
m_total = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
104
game/results.h
104
game/results.h
|
@ -23,6 +23,7 @@
|
||||||
#define _RESULTS_H_
|
#define _RESULTS_H_
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
#include "round.h"
|
#include "round.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -33,31 +34,104 @@ class Rack;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class allows to perform a search on the board for a given rack,
|
* This abstract class defines the interface to perform a search on the board
|
||||||
* and it offers accessors to the resulting rounds.
|
* for a given rack, and it offers accessors to the resulting rounds.
|
||||||
* The rounds are sorted by decreasing number of points, then by alphabetical
|
* Not all the rounds found by the search are necessarily kept, it depends
|
||||||
* order (case insensitive), then by coordinates, then by alphabetical orderi
|
* on the implementation (see below in the file for the various
|
||||||
* again (case sensitive this time).
|
* implementations).
|
||||||
|
*
|
||||||
|
* After the search, the rounds are sorted by decreasing number of points,
|
||||||
|
* then by alphabetical order (case insensitive), then by coordinates,
|
||||||
|
* then by alphabetical order again (case sensitive this time).
|
||||||
*/
|
*/
|
||||||
class Results
|
class Results
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
unsigned int size() const { return m_rounds.size(); }
|
unsigned int size() const { return m_rounds.size(); }
|
||||||
void clear() { m_rounds.clear(); }
|
|
||||||
const Round & get(unsigned int) const;
|
const Round & get(unsigned int) const;
|
||||||
|
|
||||||
/// Perform a search on the board
|
/**
|
||||||
void search(const Dictionary &iDic, const Board &iBoard,
|
* Perform a search on the board. Every time a word is found,
|
||||||
const Rack &iRack, bool iFirstWord);
|
* the add() method will be called. At the end of the search,
|
||||||
|
* results are sorted.
|
||||||
|
*/
|
||||||
|
virtual void search(const Dictionary &iDic, const Board &iBoard,
|
||||||
|
const Rack &iRack, bool iFirstWord) = 0;
|
||||||
|
|
||||||
// FIXME: This method is used to fill the container with the rounds,
|
/** Add a round */
|
||||||
// but it should not be part of the public interface
|
virtual void add(const Round &iRound) = 0;
|
||||||
void add(const Round &iRound) { m_rounds.push_back(iRound); }
|
|
||||||
|
/** Clear the stored rounds, and get ready for a new search */
|
||||||
|
virtual void clear() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
vector<Round> m_rounds;
|
||||||
|
void sort();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This implementation keeps only the rounds corresponding to the best score.
|
||||||
|
* If there are several rounds with the same score, they are all kept.
|
||||||
|
* All other rounds are ignored.
|
||||||
|
*/
|
||||||
|
class BestResults: public Results
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BestResults();
|
||||||
|
virtual void search(const Dictionary &iDic, const Board &iBoard,
|
||||||
|
const Rack &iRack, bool iFirstWord);
|
||||||
|
virtual void clear();
|
||||||
|
virtual void add(const Round &iRound);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
vector<Round> m_rounds;
|
int m_bestScore;
|
||||||
|
};
|
||||||
|
|
||||||
void sortByPoints();
|
/**
|
||||||
|
* This implementation finds the best score possible, and keeps only
|
||||||
|
* the rounds whose score is closest to (but not lower than) the given
|
||||||
|
* percentage of the best score.
|
||||||
|
* All the rounds with this closest score are kept, rounds with a different
|
||||||
|
* score are ignored.
|
||||||
|
*/
|
||||||
|
class PercentResults: public Results
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** The percentage is given as a float between 0 (0%) and 1 (100%) */
|
||||||
|
PercentResults(float iPercent);
|
||||||
|
virtual void search(const Dictionary &iDic, const Board &iBoard,
|
||||||
|
const Rack &iRack, bool iFirstWord);
|
||||||
|
virtual void clear();
|
||||||
|
virtual void add(const Round &iRound);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const float m_percent;
|
||||||
|
int m_bestScore;
|
||||||
|
int m_minScore;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This implementation keeps the N best rounds, N being the given limit.
|
||||||
|
* All other rounds are ignored.
|
||||||
|
* In the special case where the limit is 0, all rounds are kept (but you can
|
||||||
|
* expect the sorting of the rounds to be much slower...)
|
||||||
|
*/
|
||||||
|
class LimitResults: public Results
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LimitResults(int iLimit);
|
||||||
|
virtual void search(const Dictionary &iDic, const Board &iBoard,
|
||||||
|
const Rack &iRack, bool iFirstWord);
|
||||||
|
virtual void clear();
|
||||||
|
virtual void add(const Round &iRound);
|
||||||
|
|
||||||
|
void setLimit(int iNewLimit) { m_limit = iNewLimit; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_limit;
|
||||||
|
map<int, int> m_scoresCount;
|
||||||
|
int m_total;
|
||||||
|
int m_minScore;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -119,6 +119,18 @@ namespace
|
||||||
#endif
|
#endif
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void copySetting(const Config &srcConf, Config &dstConf, const char *path)
|
||||||
|
{
|
||||||
|
if (srcConf.exists(path))
|
||||||
|
{
|
||||||
|
T t;
|
||||||
|
srcConf.lookupValue(path, t);
|
||||||
|
dstConf.lookup(path) = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,6 +143,10 @@ Settings::Settings()
|
||||||
// ============== General options ==============
|
// ============== General options ==============
|
||||||
|
|
||||||
// ============== Training mode options ==============
|
// ============== Training mode options ==============
|
||||||
|
Setting &training = m_conf->getRoot().add("training", Setting::TypeGroup);
|
||||||
|
|
||||||
|
// Number of search results kept in a search
|
||||||
|
training.add("search-limit", Setting::TypeInt) = 100;
|
||||||
|
|
||||||
// ============== Duplicate mode options ==============
|
// ============== Duplicate mode options ==============
|
||||||
Setting &dupli = m_conf->getRoot().add("duplicate", Setting::TypeGroup);
|
Setting &dupli = m_conf->getRoot().add("duplicate", Setting::TypeGroup);
|
||||||
|
@ -159,7 +175,16 @@ Settings::Settings()
|
||||||
// Try to read the values from the configuration file
|
// Try to read the values from the configuration file
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
m_conf->readFile(m_fileName.c_str());
|
// We cannot call readFile() on m_conf, as it removes the previous
|
||||||
|
// settings. So we create a temporary config, and copy the settings
|
||||||
|
// one by one...
|
||||||
|
Config tmpConf;
|
||||||
|
tmpConf.readFile(m_fileName.c_str());
|
||||||
|
copySetting<int>(tmpConf, *m_conf, "training.search-limit");
|
||||||
|
copySetting<int>(tmpConf, *m_conf, "duplicate.solo-players");
|
||||||
|
copySetting<int>(tmpConf, *m_conf, "duplicate.solo-value");
|
||||||
|
copySetting<bool>(tmpConf, *m_conf, "duplicate.reject-invalid");
|
||||||
|
copySetting<bool>(tmpConf, *m_conf, "freegame.reject-invalid");
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
|
@ -236,9 +261,11 @@ int Settings::getInt(const string &iName) const
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Dummy implementation
|
// Dummy implementation
|
||||||
if (iName == "duplicate.solo-players")
|
if (iName == "training.search-limit")
|
||||||
|
return 100;
|
||||||
|
else if (iName == "duplicate.solo-players")
|
||||||
return 16;
|
return 16;
|
||||||
else if (iName == "duplicate.solo-bonus")
|
else if (iName == "duplicate.solo-value")
|
||||||
return 10;
|
return 10;
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -28,8 +28,10 @@
|
||||||
# define _(String) String
|
# define _(String) String
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "training.h"
|
||||||
#include "dic.h"
|
#include "dic.h"
|
||||||
#include "tile.h"
|
#include "tile.h"
|
||||||
|
#include "settings.h"
|
||||||
#include "rack.h"
|
#include "rack.h"
|
||||||
#include "round.h"
|
#include "round.h"
|
||||||
#include "move.h"
|
#include "move.h"
|
||||||
|
@ -38,14 +40,13 @@
|
||||||
#include "player_move_cmd.h"
|
#include "player_move_cmd.h"
|
||||||
#include "player_rack_cmd.h"
|
#include "player_rack_cmd.h"
|
||||||
#include "game_move_cmd.h"
|
#include "game_move_cmd.h"
|
||||||
#include "training.h"
|
|
||||||
#include "encoding.h"
|
#include "encoding.h"
|
||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
|
||||||
|
|
||||||
Training::Training(const Dictionary &iDic)
|
Training::Training(const Dictionary &iDic)
|
||||||
: Game(iDic)
|
: Game(iDic), m_results(1000)
|
||||||
{
|
{
|
||||||
// Training mode implicitly uses 1 human player
|
// Training mode implicitly uses 1 human player
|
||||||
Game::addPlayer(new HumanPlayer);
|
Game::addPlayer(new HumanPlayer);
|
||||||
|
@ -147,6 +148,8 @@ void Training::search()
|
||||||
// Search for the current player
|
// Search for the current player
|
||||||
Rack r;
|
Rack r;
|
||||||
m_players[m_currPlayer]->getCurrentRack().getRack(r);
|
m_players[m_currPlayer]->getCurrentRack().getRack(r);
|
||||||
|
int limit = Settings::Instance().getInt("training.search-limit");
|
||||||
|
m_results.setLimit(limit);
|
||||||
m_results.search(getDic(), getBoard(), r, getHistory().beforeFirstRound());
|
m_results.search(getDic(), getBoard(), r, getHistory().beforeFirstRound());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,8 +97,8 @@ private:
|
||||||
|
|
||||||
void endTurn();
|
void endTurn();
|
||||||
|
|
||||||
/// Search results, with all the possible rounds
|
/// Search results, with all the possible rounds up to a predefined limit
|
||||||
Results m_results;
|
LimitResults m_results;
|
||||||
|
|
||||||
/// Round corresponding to the last test play (if any)
|
/// Round corresponding to the last test play (if any)
|
||||||
Round m_testRound;
|
Round m_testRound;
|
||||||
|
|
|
@ -64,7 +64,7 @@ PrefsDialog::PrefsDialog(QWidget *iParent)
|
||||||
checkBoxFreeRefuseInvalid->setChecked(Settings::Instance().getBool("freegame.reject-invalid"));
|
checkBoxFreeRefuseInvalid->setChecked(Settings::Instance().getBool("freegame.reject-invalid"));
|
||||||
|
|
||||||
// Training settings
|
// Training settings
|
||||||
|
spinBoxTrainSearchLimit->setValue(Settings::Instance().getInt("training.search-limit"));
|
||||||
}
|
}
|
||||||
catch (GameException &e)
|
catch (GameException &e)
|
||||||
{
|
{
|
||||||
|
@ -133,7 +133,8 @@ void PrefsDialog::updateSettings()
|
||||||
checkBoxFreeRefuseInvalid->isChecked());
|
checkBoxFreeRefuseInvalid->isChecked());
|
||||||
|
|
||||||
// Training settings
|
// Training settings
|
||||||
|
Settings::Instance().setInt("training.search-limit",
|
||||||
|
spinBoxTrainSearchLimit->value());
|
||||||
}
|
}
|
||||||
catch (GameException &e)
|
catch (GameException &e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
<ui version="4.0" >
|
<ui version="4.0" >
|
||||||
<class>PrefsDialog</class>
|
<class>PrefsDialog</class>
|
||||||
<widget class="QDialog" name="PrefsDialog" >
|
<widget class="QDialog" name="PrefsDialog" >
|
||||||
|
<property name="geometry" >
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>414</width>
|
||||||
|
<height>526</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
<property name="windowTitle" >
|
<property name="windowTitle" >
|
||||||
<string>_("Preferences")</string>
|
<string>_("Preferences")</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -194,6 +202,45 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout" >
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>_("Search results limit:")</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBoxTrainSearchLimit" >
|
||||||
|
<property name="toolTip" >
|
||||||
|
<string>_("Maximum number of results returned by a search. The returned
|
||||||
|
results will always be the best ones. Use 0 to disable the limit (warning:
|
||||||
|
searches yielding many results can be extremely slow in this case!).")</string>
|
||||||
|
</property>
|
||||||
|
<property name="maximum" >
|
||||||
|
<number>100000</number>
|
||||||
|
</property>
|
||||||
|
<property name="value" >
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2" >
|
||||||
|
<property name="orientation" >
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0" >
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
s i training.search-limit 1000
|
||||||
e
|
e
|
||||||
t QpiNZ?s
|
t QpiNZ?s
|
||||||
a t
|
a t
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
Using seed: 0
|
Using seed: 0
|
||||||
[?] pour l'aide
|
[?] pour l'aide
|
||||||
|
commande> s i training.search-limit 1000
|
||||||
commande> e
|
commande> e
|
||||||
mode entraînement
|
mode entraînement
|
||||||
[?] pour l'aide
|
[?] pour l'aide
|
||||||
|
|
Loading…
Reference in a new issue