parent
08b07c0de8
commit
a334e7ee8d
|
@ -81,10 +81,6 @@
|
|||
#define SORT_MOVE_WITH_HUMAN_KNOWLEDGES
|
||||
|
||||
//#define IDS_WINDOW
|
||||
//#define IDS_DEBUG
|
||||
//#define IDS_ADD_VALUE
|
||||
|
||||
//#define DEEPER_IF_ONLY_ONE_LEGAL_MOVE
|
||||
|
||||
#define TRANSPOSITION_TABLE_ENABLE
|
||||
|
||||
|
@ -96,6 +92,7 @@
|
|||
//#define TRANSPOSITION_TABLE_DEBUG
|
||||
#endif
|
||||
|
||||
//WIP
|
||||
//#define HOSTORY_HEURISTIC
|
||||
|
||||
#ifdef HOSTORY_HEURISTIC
|
||||
|
@ -115,10 +112,6 @@
|
|||
|
||||
#define PREFETCH_SUPPORT
|
||||
|
||||
//#define USE_STD_STACK
|
||||
|
||||
//#define RAPID_GAME
|
||||
|
||||
// WIP, Debugging only
|
||||
//#define OPENING_BOOK
|
||||
|
||||
|
@ -128,13 +121,10 @@
|
|||
|
||||
#define THREEFOLD_REPETITION
|
||||
|
||||
//#define DONOT_DELETE_TREE
|
||||
|
||||
//#define MESSAGEBOX_ENABLE
|
||||
|
||||
#ifdef DEBUG_MODE
|
||||
#define DONOT_PLAY_SOUND
|
||||
#define DEBUG_AB_TREE
|
||||
#endif
|
||||
|
||||
//#define DONOT_PLAY_SOUND
|
||||
|
@ -147,7 +137,7 @@
|
|||
#define SAVE_GAMEBOOK_WHEN_ACTION_NEW_TRIGGERED
|
||||
#endif
|
||||
|
||||
// #define DONOT_PLAY_WIN_SOUND
|
||||
//#define DONOT_PLAY_WIN_SOUND
|
||||
|
||||
// 摆棋阶段在叉下面显示被吃的子
|
||||
//#define GAME_PLACING_SHOW_CAPTURED_PIECES
|
||||
|
|
|
@ -25,7 +25,6 @@ INCLUDEPATH += src/ui/qt
|
|||
SOURCES += \
|
||||
src/ai/endgame.cpp \
|
||||
src/ai/evaluate.cpp \
|
||||
src/ai/mcts.cpp \
|
||||
src/ai/movegen.cpp \
|
||||
src/ai/movepick.cpp \
|
||||
src/ai/trainer.cpp \
|
||||
|
@ -58,7 +57,6 @@ HEADERS += \
|
|||
include/version.h.template \
|
||||
src/ai/endgame.h \
|
||||
src/ai/evaluate.h \
|
||||
src/ai/mcts.h \
|
||||
src/ai/movegen.h \
|
||||
src/ai/movepick.h \
|
||||
src/ai/trainer.h \
|
||||
|
@ -69,7 +67,6 @@ HEADERS += \
|
|||
src/base/memmgr.h \
|
||||
src/base/misc.h \
|
||||
src/base/prefetch.h \
|
||||
src/base/sort.h \
|
||||
src/base/stack.h \
|
||||
src/base/stopwatch.h \
|
||||
src/base/aithread.h \
|
||||
|
|
|
@ -453,7 +453,6 @@
|
|||
</CustomBuild>
|
||||
<ClInclude Include="src\ai\endgame.h" />
|
||||
<ClInclude Include="src\ai\evaluate.h" />
|
||||
<ClInclude Include="src\ai\mcts.h" />
|
||||
<ClInclude Include="src\ai\movegen.h" />
|
||||
<ClInclude Include="src\ai\movepick.h" />
|
||||
<ClInclude Include="src\ai\search.h" />
|
||||
|
@ -465,7 +464,6 @@
|
|||
<ClInclude Include="src\base\memmgr.h" />
|
||||
<ClInclude Include="src\base\misc.h" />
|
||||
<ClInclude Include="src\base\prefetch.h" />
|
||||
<ClInclude Include="src\base\sort.h" />
|
||||
<ClInclude Include="src\base\stack.h" />
|
||||
<QtMoc Include="src\base\aithread.h" />
|
||||
<ClInclude Include="src\base\stopwatch.h" />
|
||||
|
@ -715,7 +713,6 @@
|
|||
<ItemGroup>
|
||||
<ClCompile Include="src\ai\endgame.cpp" />
|
||||
<ClCompile Include="src\ai\evaluate.cpp" />
|
||||
<ClCompile Include="src\ai\mcts.cpp" />
|
||||
<ClCompile Include="src\ai\movegen.cpp" />
|
||||
<ClCompile Include="src\ai\movepick.cpp" />
|
||||
<ClCompile Include="src\ai\search.cpp" />
|
||||
|
|
|
@ -129,9 +129,6 @@
|
|||
<ClInclude Include="src\base\memmgr.h">
|
||||
<Filter>base</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\base\sort.h">
|
||||
<Filter>base</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\ai\trainer.h">
|
||||
<Filter>ai</Filter>
|
||||
</ClInclude>
|
||||
|
@ -144,9 +141,6 @@
|
|||
<ClInclude Include="src\base\thread_win32_osx.h">
|
||||
<Filter>base</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\ai\mcts.h">
|
||||
<Filter>ai</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\base\prefetch.h">
|
||||
<Filter>base</Filter>
|
||||
</ClInclude>
|
||||
|
@ -392,9 +386,6 @@
|
|||
<ClCompile Include="src\test\test.cpp">
|
||||
<Filter>test</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\ai\mcts.cpp">
|
||||
<Filter>ai</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\ai\movepick.cpp">
|
||||
<Filter>ai</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
@ -38,49 +38,49 @@ public:
|
|||
#ifdef EVALUATE_ENABLE
|
||||
|
||||
#ifdef EVALUATE_MATERIAL
|
||||
static value_t evaluateMaterial(Node *node)
|
||||
static value_t evaluateMaterial()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EVALUATE_SPACE
|
||||
static value_t evaluateSpace(Node *node)
|
||||
static value_t evaluateSpace()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EVALUATE_MOBILITY
|
||||
static value_t evaluateMobility(Node *node)
|
||||
static value_t evaluateMobility()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EVALUATE_TEMPO
|
||||
static value_t evaluateTempo(Node *node)
|
||||
static value_t evaluateTempo()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EVALUATE_THREAT
|
||||
static value_t evaluateThreat(Node *node)
|
||||
static value_t evaluateThreat()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EVALUATE_SHAPE
|
||||
static value_t evaluateShape(Node *node)
|
||||
static value_t evaluateShape()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EVALUATE_MOTIF
|
||||
static value_t AIAlgorithm::evaluateMotif(Node *node)
|
||||
static value_t AIAlgorithm::evaluateMotif()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
455
src/ai/mcts.cpp
455
src/ai/mcts.cpp
|
@ -1,455 +0,0 @@
|
|||
//
|
||||
// Petter Strandmark 2013
|
||||
// petter.strandmark@gmail.com
|
||||
// calcitem@outlook.com
|
||||
//
|
||||
// Monte Carlo Tree Search for finite games.
|
||||
//
|
||||
// Originally based on Python code at
|
||||
// http://mcts.ai/code/python.html
|
||||
//
|
||||
|
||||
#include "mcts.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
#include "movegen.h"
|
||||
|
||||
#ifdef MCTS_AI
|
||||
|
||||
void Position::doRandomMove(Node *node, mt19937_64 *engine)
|
||||
{
|
||||
assert(hasMoves());
|
||||
checkInvariant();
|
||||
|
||||
generateMoves(moves);
|
||||
|
||||
int movesSize = moves.size();
|
||||
|
||||
#ifdef MCTS_PLD
|
||||
int i = 0;
|
||||
if (movesSize > 1)
|
||||
{
|
||||
std::vector<int> v{ 0, movesSize - 1 }; // Sample values
|
||||
std::vector<int> w{ movesSize - 1, 0 }; // Weights for the samples
|
||||
std::piecewise_linear_distribution<> index{ std::begin(v), std::end(v), std::begin(w) };
|
||||
|
||||
i = (int)index(*engine);
|
||||
}
|
||||
#else
|
||||
uniform_int_distribution<int> index(0, movesSize - 1);
|
||||
auto i = index(*engine);
|
||||
#endif // MCTS_PLD
|
||||
move_t m = moves[i];
|
||||
doMove(m);
|
||||
}
|
||||
|
||||
bool Position::hasMoves() const
|
||||
{
|
||||
checkInvariant();
|
||||
|
||||
player_t winner = getWinner();
|
||||
if (winner != PLAYER_NOBODY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
double Position::getResult(player_t currentSideToMove) const
|
||||
{
|
||||
assert(!hasMoves());
|
||||
checkInvariant();
|
||||
|
||||
auto winner = getWinner();
|
||||
|
||||
if (winner == PLAYER_NOBODY) {
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
if (winner == currentSideToMove) {
|
||||
return 0.0;
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
void Position::checkInvariant() const
|
||||
{
|
||||
assert(sideToMove == PLAYER_BLACK || sideToMove == PLAYER_WHITE);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Node::Node(Position &position) :
|
||||
sideToMove(position->sideToMove)
|
||||
{
|
||||
position->generateMoves(moves);
|
||||
}
|
||||
|
||||
Node::Node(StateInfo &state, const move_t &m, Node *p) :
|
||||
move(m),
|
||||
parent(p),
|
||||
sideToMove(state.position->sideToMove)
|
||||
{
|
||||
state.position->generateMoves(moves);
|
||||
}
|
||||
|
||||
void deleteChild(Node *node)
|
||||
{
|
||||
for (int i = 0; i < node->childrenSize; i++) {
|
||||
deleteChild(node->children[i]);
|
||||
}
|
||||
|
||||
node->childrenSize = 0;
|
||||
|
||||
delete node;
|
||||
node = nullptr;
|
||||
}
|
||||
|
||||
Node::~Node()
|
||||
{
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
deleteChild(children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool Node::hasUntriedMoves() const
|
||||
{
|
||||
return !moves.empty();
|
||||
}
|
||||
|
||||
template<typename RandomEngine>
|
||||
move_t Node::getUntriedMove(RandomEngine *engine) const
|
||||
{
|
||||
assert(!moves.empty());
|
||||
|
||||
#ifdef MCTS_PLD
|
||||
int i = 0;
|
||||
int movesSize = moves.size();
|
||||
if (movesSize > 1) {
|
||||
std::vector<int> v{ 0, movesSize - 1 }; // Sample values
|
||||
std::vector<int> w{ movesSize - 1, 0 }; // Weights for the samples
|
||||
std::piecewise_linear_distribution<> index{ std::begin(v), std::end(v), std::begin(w) };
|
||||
|
||||
i = (int)index(*engine);
|
||||
}
|
||||
|
||||
return moves[i];
|
||||
#else
|
||||
uniform_int_distribution<size_t> movesDistribution(0, moves.size() - 1);
|
||||
|
||||
return moves[movesDistribution(*engine)];
|
||||
#endif // #ifdef MCTS_PLD
|
||||
}
|
||||
|
||||
bool Node::hasChildren() const
|
||||
{
|
||||
return (childrenSize != 0);
|
||||
}
|
||||
|
||||
Node *Node::bestChildren() const
|
||||
{
|
||||
assert(moves.empty());
|
||||
assert(childrenSize > 0);
|
||||
|
||||
int visitsMax = numeric_limits<int>::min();
|
||||
Node *nodeMax = nullptr;
|
||||
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
if (children[i]->visits > visitsMax) {
|
||||
visitsMax = children[i]->visits;
|
||||
nodeMax = children[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nodeMax;
|
||||
}
|
||||
|
||||
Node *Node::selectChild() const
|
||||
{
|
||||
assert(childrenSize > 0);
|
||||
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
children[i]->score = double(children[i]->wins) / double(children[i]->visits) +
|
||||
sqrt(2.0 * log(double(this->visits)) / children[i]->visits);
|
||||
}
|
||||
|
||||
double scoreMax = numeric_limits<double>::min();
|
||||
Node *nodeMax = nullptr;
|
||||
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
if (children[i]->score > scoreMax) {
|
||||
scoreMax = children[i]->score;
|
||||
nodeMax = children[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nodeMax;
|
||||
}
|
||||
|
||||
Node *Node::addChild(const move_t &move, Position &position)
|
||||
{
|
||||
auto node = new Node(position, move, this); // TODO: memmgr_alloc
|
||||
|
||||
//children.push_back(node);
|
||||
children[childrenSize] = node;
|
||||
childrenSize++;
|
||||
|
||||
assert(childrenSize > 0);
|
||||
|
||||
int iter = 0;
|
||||
for (; &moves[iter] != moves.end() && moves[iter] != move; ++iter);
|
||||
|
||||
assert(&moves[iter] != moves.end());
|
||||
|
||||
moves.erase(iter);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void Node::update(double result)
|
||||
{
|
||||
visits++;
|
||||
|
||||
wins += result;
|
||||
//double my_wins = wins.load();
|
||||
//while ( ! wins.compare_exchange_strong(my_wins, my_wins + result));
|
||||
}
|
||||
|
||||
string Node::toString()
|
||||
{
|
||||
stringstream sout;
|
||||
|
||||
sout << "["
|
||||
<< "P" << 3 - sideToMove << " "
|
||||
<< "M:" << move << " "
|
||||
<< "W/V: " << wins << "/" << visits << " "
|
||||
<< "U: " << moves.size() << "]\n";
|
||||
|
||||
return sout.str();
|
||||
}
|
||||
|
||||
#if 0
|
||||
string Node::treeToString(int maxDepth, int indent) const
|
||||
{
|
||||
if (indent >= maxDepth) {
|
||||
return "";
|
||||
}
|
||||
|
||||
string s = indentString(indent) + toString();
|
||||
|
||||
for (auto child : children) {
|
||||
s += child->treeToString(maxDepth, indent + 1);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
string Node::indentString(int indent) const
|
||||
{
|
||||
string s = "";
|
||||
|
||||
for (int i = 1; i <= indent; ++i) {
|
||||
s += "| ";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
Node *AIAlgorithm::computeTree(Position position,
|
||||
const MCTSOptions options,
|
||||
mt19937_64::result_type initialSeed)
|
||||
{
|
||||
mt19937_64 random_engine(initialSeed);
|
||||
|
||||
assert(options.maxIterations >= 0 || options.maxTime >= 0);
|
||||
|
||||
if (options.maxTime >= 0) {
|
||||
#ifndef USE_OPENMP
|
||||
throw runtime_error("ComputeOptions::maxTime requires OpenMP.");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Will support more players later.
|
||||
assert(position->sideToMove == PLAYER_BLACK || position->sideToMove == PLAYER_WHITE);
|
||||
|
||||
Node *root = new Node(state);
|
||||
|
||||
#ifdef USE_OPENMP
|
||||
double start_time = ::omp_get_wtime();
|
||||
double print_time = start_time;
|
||||
#endif
|
||||
|
||||
for (int iter = 1; iter <= options.maxIterations || options.maxIterations < 0; ++iter) {
|
||||
//auto node = root.get();
|
||||
Node *node = root;
|
||||
|
||||
Position pos = position;
|
||||
|
||||
// Select a path through the tree to a leaf node.
|
||||
while (!node->hasUntriedMoves() && node->hasChildren()) {
|
||||
node = node->selectChild();
|
||||
pos.doMove(node->move);
|
||||
}
|
||||
|
||||
// If we are not already at the final game, expand the
|
||||
// tree with a new node and move there.
|
||||
if (node->hasUntriedMoves()) {
|
||||
auto move = node->getUntriedMove(&random_engine);
|
||||
st.doMove(move);
|
||||
node = node->addChild(move, pos);
|
||||
}
|
||||
|
||||
// We now play randomly until the game ends.
|
||||
while (st.hasMoves()) {
|
||||
pos.doRandomMove(root, &random_engine);
|
||||
}
|
||||
|
||||
// We have now reached a final game. Backpropagate the result
|
||||
// up the tree to the root node.
|
||||
while (node != nullptr) {
|
||||
node->update(pos.getResult(node->sideToMove));
|
||||
node = node->parent;
|
||||
}
|
||||
|
||||
#ifdef USE_OPENMP
|
||||
if (options.verbose || options.maxTime >= 0) {
|
||||
double time = ::omp_get_wtime();
|
||||
if (options.verbose && (time - print_time >= 1.0 || iter == options.maxIterations)) {
|
||||
cerr << iter << " games played (" << double(iter) / (time - start_time) << " / second)." << endl;
|
||||
print_time = time;
|
||||
}
|
||||
|
||||
if (time - start_time >= options.maxTime) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
move_t AIAlgorithm::computeMove(Position position,
|
||||
const MCTSOptions options)
|
||||
{
|
||||
// Will support more players later.
|
||||
assert(position->sideToMove == PLAYER_BLACK || position->sideToMove == PLAYER_WHITE);
|
||||
|
||||
// 分段随机打乱着法表
|
||||
MoveList::shuffle();
|
||||
|
||||
Stack<move_t, MAX_MOVES> moves;
|
||||
position->generateMoves(moves);
|
||||
assert(moves.size() > 0);
|
||||
if (moves.size() == 1) {
|
||||
return moves[0];
|
||||
}
|
||||
|
||||
#ifdef USE_OPENMP
|
||||
double start_time = ::omp_get_wtime();
|
||||
#endif
|
||||
|
||||
// Start all jobs to compute trees.
|
||||
future<Node *> rootFutures[THREADS_COUNT];
|
||||
MCTSOptions jobOptions = options;
|
||||
|
||||
jobOptions.verbose = true;
|
||||
|
||||
for (int t = 0; t < options.nThreads; ++t) {
|
||||
auto func = [t, &state, &jobOptions, this]() -> Node* {
|
||||
return computeTree(state, jobOptions, 1012411 * t + 12515);
|
||||
};
|
||||
|
||||
rootFutures[t] = async(launch::async, func);
|
||||
}
|
||||
|
||||
// Collect the results.
|
||||
Node *roots[THREADS_COUNT] = { nullptr };
|
||||
|
||||
for (int t = 0; t < options.nThreads; ++t) {
|
||||
roots[t] = move(rootFutures[t].get());
|
||||
}
|
||||
|
||||
// Merge the children of all root nodes.
|
||||
map<move_t, int> visits;
|
||||
map<move_t, double> wins;
|
||||
long long gamesPlayed = 0;
|
||||
|
||||
for (int t = 0; t < options.nThreads; ++t) {
|
||||
Node *root = roots[t];
|
||||
gamesPlayed += root->visits;
|
||||
|
||||
#if 0
|
||||
for (auto child = root->children.cbegin(); child != root->children.cend(); ++child) {
|
||||
visits[(*child)->move] += (*child)->visits;
|
||||
wins[(*child)->move] += (*child)->wins;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < root->childrenSize; i++) {
|
||||
visits[root->children[i]->move] += root->children[i]->visits;
|
||||
wins[root->children[i]->move] += root->children[i]->wins;
|
||||
}
|
||||
|
||||
deleteChild(root);
|
||||
root = nullptr;
|
||||
}
|
||||
|
||||
// Find the node with the highest score.
|
||||
double bestScore = -1;
|
||||
move_t ttMove = move_t();
|
||||
|
||||
for (auto iter : visits) {
|
||||
auto move = iter.first;
|
||||
double v = iter.second;
|
||||
double w = wins[move];
|
||||
// Expected success rate assuming a uniform prior (Beta(1, 1)).
|
||||
// https://en.wikipedia.org/wiki/Beta_distribution
|
||||
double expectedSuccessRate = (w + 1) / (v + 2);
|
||||
if (expectedSuccessRate > bestScore) {
|
||||
ttMove = move;
|
||||
bestScore = expectedSuccessRate;
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
cerr << "Move: " << iter.first
|
||||
<< " (" << setw(2) << right << int(100.0 * v / double(gamesPlayed) + 0.5) << "% visits)"
|
||||
<< " (" << setw(2) << right << int(100.0 * w / v + 0.5) << "% wins)" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
auto best_wins = wins[ttMove];
|
||||
auto best_visits = visits[ttMove];
|
||||
cerr << "----" << endl;
|
||||
cerr << "Best: " << ttMove
|
||||
<< " (" << 100.0 * best_visits / double(gamesPlayed) << "% visits)"
|
||||
<< " (" << 100.0 * best_wins / best_visits << "% wins)" << endl;
|
||||
}
|
||||
|
||||
#ifdef USE_OPENMP
|
||||
if (options.verbose) {
|
||||
double time = ::omp_get_wtime();
|
||||
cerr << gamesPlayed << " games played in " << double(time - start_time) << " s. "
|
||||
<< "(" << double(gamesPlayed) / (time - start_time) << " / second, "
|
||||
<< options.nThreads << " parallel jobs)." << endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ttMove;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ostream &operator << (ostream &out, Position &pos)
|
||||
{
|
||||
//state.position->print(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif // MCTS_AI
|
|
@ -1,88 +0,0 @@
|
|||
#ifndef MCTS_HEADER_PETTER
|
||||
#define MCTS_HEADER_PETTER
|
||||
//
|
||||
// Petter Strandmark 2013
|
||||
// petter.strandmark@gmail.com
|
||||
//
|
||||
// Monte Carlo Tree Search for finite games.
|
||||
//
|
||||
// Originally based on Python code at
|
||||
// http://mcts.ai/code/python.html
|
||||
//
|
||||
// Uses the "root parallelization" technique [1].
|
||||
//
|
||||
// This game engine can play any game defined by a game like this:
|
||||
/*
|
||||
|
||||
class GameState
|
||||
{
|
||||
public:
|
||||
typedef int move_t;
|
||||
static const move_t noMove = ...
|
||||
|
||||
void doMove(Move move);
|
||||
template<typename RandomEngine>
|
||||
void doRandomMove(*engine);
|
||||
bool hasMoves() const;
|
||||
vector<move_t> MoveList_Generate() const;
|
||||
|
||||
// Returns a value in {0, 0.5, 1}.
|
||||
// This should not be an evaluation function, because it will only be
|
||||
// called for finished games. Return 0.5 to indicate a draw.
|
||||
double getResult(int currentSideToMove) const;
|
||||
|
||||
int sideToMove;
|
||||
|
||||
// ...
|
||||
private:
|
||||
// ...
|
||||
};
|
||||
|
||||
*/
|
||||
//
|
||||
// See the examples for more details. Given a suitable State, the
|
||||
// following function (tries to) compute the best move for the
|
||||
// player to move.
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include "stack.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define USE_OPENMP
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPENMP
|
||||
#include <omp.h>
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const int THREADS_COUNT = 2;
|
||||
|
||||
class MCTSOptions
|
||||
{
|
||||
public:
|
||||
int nThreads { THREADS_COUNT };
|
||||
int maxIterations { 10000 }; // 40G: 40000000
|
||||
double maxTime { 6000 };
|
||||
bool verbose { true };
|
||||
};
|
||||
|
||||
#endif // MCTS_HEADER_PETTER
|
|
@ -27,39 +27,6 @@
|
|||
#include "search.h"
|
||||
#include "position.h"
|
||||
|
||||
void Position::generateChildren(const Stack<move_t, MAX_MOVES> &moves,
|
||||
AIAlgorithm *ai,
|
||||
Node *node
|
||||
#ifdef TT_MOVE_ENABLE
|
||||
, move_t ttMove
|
||||
#endif // TT_MOVE_ENABLE
|
||||
)
|
||||
{
|
||||
int size = moves.size();
|
||||
|
||||
assert(size != 0);
|
||||
|
||||
if (node->childrenSize > 0) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
ai->deleteTree(node->children[i]);
|
||||
}
|
||||
node->childrenSize = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
node->addChild(moves[i], ai, this
|
||||
#ifdef TT_MOVE_ENABLE
|
||||
, ttMove
|
||||
#endif // TT_MOVE_ENABLE
|
||||
);
|
||||
}
|
||||
|
||||
// 赋值
|
||||
node->sideToMove = sideToMove;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int Position::generateMoves(Stack<move_t, MAX_MOVES> &moves)
|
||||
{
|
||||
square_t square;
|
||||
|
|
|
@ -33,16 +33,8 @@
|
|||
#include "misc.h"
|
||||
#include "movepick.h"
|
||||
|
||||
#define SORT_NAME nodep
|
||||
#define SORT_TYPE Node*
|
||||
#ifdef ALPHABETA_AI
|
||||
#define SORT_CMP(x, y) (AIAlgorithm::nodeCompare((x), (y)))
|
||||
#endif
|
||||
|
||||
player_t gSideToMove;
|
||||
|
||||
#include "sort.h"
|
||||
|
||||
using namespace CTSL;
|
||||
|
||||
// 用于检测重复局面 (Position)
|
||||
|
@ -53,19 +45,10 @@ AIAlgorithm::AIAlgorithm()
|
|||
state = new StateInfo();
|
||||
st = new StateInfo();
|
||||
//movePicker = new MovePicker();
|
||||
|
||||
memmgr.memmgr_init();
|
||||
|
||||
buildRoot();
|
||||
}
|
||||
|
||||
AIAlgorithm::~AIAlgorithm()
|
||||
{
|
||||
deleteTree(root);
|
||||
root = nullptr;
|
||||
|
||||
memmgr.memmgr_exit();
|
||||
|
||||
delete st;
|
||||
//delete state;
|
||||
}
|
||||
|
@ -165,340 +148,6 @@ depth_t AIAlgorithm::changeDepth(depth_t origDepth)
|
|||
return d;
|
||||
}
|
||||
|
||||
void AIAlgorithm::buildRoot()
|
||||
{
|
||||
root = (Node *)memmgr.memmgr_alloc(sizeof(Node));
|
||||
|
||||
assert(root != nullptr);
|
||||
|
||||
root->parent = nullptr;
|
||||
root->move = MOVE_NONE;
|
||||
|
||||
#ifdef ALPHABETA_AI
|
||||
root->value = VALUE_ZERO;
|
||||
root->rating = RATING_ZERO;
|
||||
#endif // ALPHABETA_AI
|
||||
|
||||
root->sideToMove = PLAYER_NOBODY;
|
||||
}
|
||||
|
||||
Node *Node::addChild(
|
||||
const move_t &m,
|
||||
AIAlgorithm *ai,
|
||||
Position *position
|
||||
#ifdef TT_MOVE_ENABLE
|
||||
, const move_t &ttMove
|
||||
#endif // TT_MOVE_ENABLE
|
||||
)
|
||||
{
|
||||
Node *newNode = (Node *)ai->memmgr.memmgr_alloc(sizeof(Node));
|
||||
|
||||
if (unlikely(newNode == nullptr)) {
|
||||
ai->memmgr.memmgr_print_stats();
|
||||
loggerDebug("Memory Manager Alloc failed\n");
|
||||
// TODO: Deal with alloc failed
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
newNode->move = m;
|
||||
|
||||
#ifdef HOSTORY_HEURISTIC
|
||||
newNode->score = ai->movePicker->getHistoryScore(m);
|
||||
#endif
|
||||
|
||||
#ifdef ALPHABETA_AI
|
||||
newNode->value = VALUE_ZERO;
|
||||
newNode->rating = RATING_ZERO;
|
||||
#endif // ALPHABETA_AI
|
||||
|
||||
newNode->childrenSize = 0; // Important
|
||||
newNode->parent = this;
|
||||
|
||||
ai->nodeCount++;
|
||||
|
||||
children[childrenSize] = newNode;
|
||||
childrenSize++;
|
||||
|
||||
#ifdef TT_MOVE_ENABLE
|
||||
// 如果启用了置换表并且不是叶子结点
|
||||
if (move == ttMove && move != 0) {
|
||||
newNode->rating += RATING_TT;
|
||||
return newNode;
|
||||
}
|
||||
#endif // TT_MOVE_ENABLE
|
||||
|
||||
// 若没有启用置换表,或启用了但为叶子节点,则 nextMove 为0
|
||||
square_t sq = SQ_0;
|
||||
square_t sqsrc = SQ_0;
|
||||
|
||||
if (m > 0) {
|
||||
if (m & 0x1f00) {
|
||||
// 走子
|
||||
sqsrc = static_cast<square_t>(m >> 8);
|
||||
}
|
||||
|
||||
// 摆子或走子
|
||||
sq = static_cast<square_t>(m & 0x00ff);
|
||||
} else {
|
||||
// 吃子
|
||||
sq = static_cast<square_t>((-m) & 0x00ff);
|
||||
}
|
||||
|
||||
// 若为走子之前的统计故走棋阶段可能会从 @-0-@ 走成 0-@-@, 并未成三,所以需要传值 sqsrc 进行判断
|
||||
int nMills = position->board.inHowManyMills(sq, position->sideToMove, sqsrc);
|
||||
int nopponentMills = 0;
|
||||
|
||||
#ifdef SORT_MOVE_WITH_HUMAN_KNOWLEDGES
|
||||
// TODO: rule.allowRemoveMultiPieces 以及 适配打三棋之外的其他规则
|
||||
if (m > 0) {
|
||||
// 在任何阶段, 都检测落子点是否能使得本方成三
|
||||
if (nMills > 0) {
|
||||
#ifdef ALPHABETA_AI
|
||||
newNode->rating += static_cast<rating_t>(RATING_ONE_MILL * nMills);
|
||||
#endif
|
||||
} else if (position->getPhase() == PHASE_PLACING) {
|
||||
// 在摆棋阶段, 检测落子点是否能阻止对方成三
|
||||
nopponentMills = position->board.inHowManyMills(sq, position->opponent);
|
||||
#ifdef ALPHABETA_AI
|
||||
newNode->rating += static_cast<rating_t>(RATING_BLOCK_ONE_MILL * nopponentMills);
|
||||
#endif
|
||||
}
|
||||
#if 1
|
||||
else if (position->getPhase() == PHASE_MOVING) {
|
||||
// 在走棋阶段, 检测落子点是否能阻止对方成三
|
||||
nopponentMills = position->board.inHowManyMills(sq, position->opponent);
|
||||
|
||||
if (nopponentMills) {
|
||||
int nPlayerPiece = 0;
|
||||
int nOpponentPiece = 0;
|
||||
int nForbidden = 0;
|
||||
int nEmpty = 0;
|
||||
|
||||
position->board.getSurroundedPieceCount(sq, position->sideId,
|
||||
nPlayerPiece, nOpponentPiece, nForbidden, nEmpty);
|
||||
|
||||
#ifdef ALPHABETA_AI
|
||||
if (sq % 2 == 0 && nOpponentPiece == 3) {
|
||||
newNode->rating += static_cast<rating_t>(RATING_BLOCK_ONE_MILL * nopponentMills);
|
||||
} else if (sq % 2 == 1 && nOpponentPiece == 2 && rule.nTotalPiecesEachSide == 12) {
|
||||
newNode->rating += static_cast<rating_t>(RATING_BLOCK_ONE_MILL * nopponentMills);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//newNode->rating += static_cast<rating_t>(nForbidden); // 摆子阶段尽量往禁点旁边落子
|
||||
|
||||
// 对于12子棋, 白方第2着走星点的重要性和成三一样重要 (TODO)
|
||||
#ifdef ALPHABETA_AI
|
||||
if (rule.nTotalPiecesEachSide == 12 &&
|
||||
position->getPiecesOnBoardCount(2) < 2 && // patch: 仅当白方第2着时
|
||||
Board::isStar(static_cast<square_t>(m))) {
|
||||
newNode->rating += RATING_STAR_SQUARE;
|
||||
}
|
||||
#endif
|
||||
} else if (m < 0) {
|
||||
int nPlayerPiece = 0;
|
||||
int nOpponentPiece = 0;
|
||||
int nForbidden = 0;
|
||||
int nEmpty = 0;
|
||||
|
||||
position->board.getSurroundedPieceCount(sq, position->sideId,
|
||||
nPlayerPiece, nOpponentPiece, nForbidden, nEmpty);
|
||||
|
||||
#ifdef ALPHABETA_AI
|
||||
if (nMills > 0) {
|
||||
// 吃子点处于我方的三连中
|
||||
//newNode->rating += static_cast<rating_t>(RATING_CAPTURE_ONE_MILL * nMills);
|
||||
|
||||
if (nOpponentPiece == 0) {
|
||||
// 吃子点旁边没有对方棋子则优先考虑
|
||||
newNode->rating += static_cast<rating_t>(1);
|
||||
if (nPlayerPiece > 0) {
|
||||
// 且吃子点旁边有我方棋子则更优先考虑
|
||||
newNode->rating += static_cast<rating_t>(nPlayerPiece);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 吃子点处于对方的三连中
|
||||
nopponentMills = position->board.inHowManyMills(sq, position->opponent);
|
||||
if (nopponentMills) {
|
||||
if (nOpponentPiece >= 2) {
|
||||
// 旁边对方的子较多, 则倾向不吃
|
||||
newNode->rating -= static_cast<rating_t>(nOpponentPiece);
|
||||
|
||||
if (nPlayerPiece == 0) {
|
||||
// 如果旁边无我方棋子, 则更倾向不吃
|
||||
newNode->rating -= static_cast<rating_t>(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 优先吃活动力强的棋子
|
||||
newNode->rating += static_cast<rating_t>(nEmpty);
|
||||
#endif
|
||||
}
|
||||
#endif // SORT_MOVE_WITH_HUMAN_KNOWLEDGES
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
#ifdef ALPHABETA_AI
|
||||
int AIAlgorithm::nodeCompare(const Node *first, const Node *second)
|
||||
{
|
||||
#ifdef COMPARE_RATING_1ST_VALUE_2ND
|
||||
if (first->rating == second->rating) {
|
||||
if (first->value == second->value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (first->value < second->value ? 1 : -1);
|
||||
}
|
||||
|
||||
return (first->rating < second->rating ? 1 : -1);
|
||||
#endif
|
||||
|
||||
#ifdef COMPARE_VALUE_1ST_RATING_2ND
|
||||
if (first->value == second->value) {
|
||||
if (first->rating == second->rating) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (first->rating < second->rating ? 1 : -1);
|
||||
}
|
||||
|
||||
return (first->value < second->value ? 1 : -1);
|
||||
#endif
|
||||
|
||||
#ifdef COMPARE_RATING_ONLY
|
||||
return second->rating - first->rating;
|
||||
#endif
|
||||
|
||||
#ifdef COMPARE_SCORE_ONLY
|
||||
return second->score - first->score;
|
||||
#endif
|
||||
|
||||
#ifdef COMPARE_SCORE_PREFERRED
|
||||
if (first->score == second->score) {
|
||||
if (first->rating == second->rating) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (first->rating < second->rating ? 1 : -1);
|
||||
}
|
||||
|
||||
return (first->score < second->score ? 1 : -1);
|
||||
#endif
|
||||
|
||||
#ifdef COMPARE_RATING_PREFERRED
|
||||
if (first->rating == second->rating) {
|
||||
if (first->score == second->score) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (first->score < second->score ? 1 : -1);
|
||||
}
|
||||
|
||||
return (first->rating < second->rating ? 1 : -1);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void AIAlgorithm::sortMoves(Node *node)
|
||||
{
|
||||
// 这个函数对效率的影响很大,排序好的话,剪枝较早,节省时间,但不能在此函数耗费太多时间
|
||||
assert(node->childrenSize != 0);
|
||||
|
||||
//#define DEBUG_SORT
|
||||
#ifdef DEBUG_SORT
|
||||
for (int moveIndex = 0; moveIndex < node->childrenSize; moveIndex++) {
|
||||
loggerDebug("* [%d] %p: %d = %d %d (%d)\n",
|
||||
moveIndex, &(node->children[moveIndex]), node->children[moveIndex]->move, node->children[moveIndex]->value, node->children[moveIndex]->rating, !node->children[moveIndex]->pruned);
|
||||
}
|
||||
loggerDebug("\n");
|
||||
#endif
|
||||
|
||||
#define NODE_PTR_SORT_FUN(x) nodep_##x
|
||||
|
||||
gSideToMove = st->position->sideToMove; // TODO: 暂时用全局变量
|
||||
|
||||
// 此处选用排序算法, 各算法耗时统计如下:
|
||||
/*
|
||||
* sqrt_sort_sort_ins: 100% (4272)
|
||||
* bubble_sort: 115% (4920)
|
||||
* binary_insertion_sort: 122% (5209)
|
||||
* merge_sort: 131% (5612)
|
||||
* grail_lazy_stable_sort: 175% (7471)
|
||||
* tim_sort: 185% (7885)
|
||||
* selection_sort: 202% (8642)
|
||||
* rec_stable_sort: 226% (9646)
|
||||
* sqrt_sort: 275% (11729)
|
||||
*/
|
||||
#ifdef TIME_STAT
|
||||
auto timeStart = now();
|
||||
#endif
|
||||
#ifdef CYCLE_STAT
|
||||
auto cycleStart = stopwatch::rdtscp_clock::now();
|
||||
#endif
|
||||
|
||||
NODE_PTR_SORT_FUN(sqrt_sort_sort_ins)(node->children, node->childrenSize);
|
||||
|
||||
#ifdef TIME_STAT
|
||||
auto timeEnd = now();
|
||||
sortTime += (timeEnd - timeStart);
|
||||
#endif
|
||||
#ifdef CYCLE_STAT
|
||||
auto cycleEnd = stopwatch::rdtscp_clock::now();
|
||||
sortCycle += (cycleEnd - cycleStart);
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_SORT
|
||||
if (st->position.sideToMove == PLAYER_BLACK) {
|
||||
for (int moveIndex = 0; moveIndex < node->childrenSize; moveIndex++) {
|
||||
loggerDebug("+ [%d] %p: %d = %d %d (%d)\n",
|
||||
moveIndex, &(node->children[moveIndex]), node->children[moveIndex]->move, node->children[moveIndex]->value, node->children[moveIndex]->rating, !node->children[moveIndex]->pruned);
|
||||
}
|
||||
} else {
|
||||
for (int moveIndex = 0; moveIndex < node->childrenSize; moveIndex++) {
|
||||
loggerDebug("- [%d] %p: %d = %d %d (%d)\n",
|
||||
moveIndex, &(node->children[moveIndex]), node->children[moveIndex]->move, node->children[moveIndex]->value, node->children[moveIndex]->rating, !node->children[moveIndex]->pruned);
|
||||
}
|
||||
}
|
||||
loggerDebug("\n----------------------------------------\n");
|
||||
#endif
|
||||
|
||||
assert(node->childrenSize != 0);
|
||||
}
|
||||
|
||||
void AIAlgorithm::deleteTree(Node *node)
|
||||
{
|
||||
int nchild = node->childrenSize;
|
||||
|
||||
for (int i = 0; i < nchild; i++) {
|
||||
deleteTree(node->children[i]);
|
||||
}
|
||||
|
||||
node->childrenSize = 0;
|
||||
|
||||
memmgr.memmgr_free(node);
|
||||
}
|
||||
|
||||
void AIAlgorithm::deleteSubTree(Node *node)
|
||||
{
|
||||
int cs = node->childrenSize;
|
||||
|
||||
for (int i = 0; i < cs; i++) {
|
||||
Node *c = node->children[i];
|
||||
int size = c->childrenSize;
|
||||
for (int j = 0; j < size; j++) {
|
||||
deleteTree(c->children[j]);
|
||||
}
|
||||
c->childrenSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AIAlgorithm::setState(const StateInfo &g)
|
||||
{
|
||||
// 如果规则改变,重建hashmap
|
||||
|
@ -524,23 +173,11 @@ void AIAlgorithm::setState(const StateInfo &g)
|
|||
|
||||
position = st->position;
|
||||
requiredQuit = false;
|
||||
deleteTree(root);
|
||||
|
||||
root = (Node *)memmgr.memmgr_alloc(sizeof(Node));
|
||||
assert(root != nullptr);
|
||||
|
||||
memset(root, 0, sizeof(Node));
|
||||
|
||||
#ifdef DEBUG_AB_TREE
|
||||
root->root = root;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ALPHABETA_AI
|
||||
int AIAlgorithm::search(depth_t depth)
|
||||
{
|
||||
assert(root != nullptr);
|
||||
|
||||
value_t value = VALUE_ZERO;
|
||||
|
||||
depth_t d = changeDepth(depth);
|
||||
|
@ -612,45 +249,8 @@ int AIAlgorithm::search(depth_t depth)
|
|||
|
||||
loggerDebug("%d(%d) ", value, value - lastValue);
|
||||
|
||||
#ifdef IDS_DEBUG
|
||||
loggerDebug(": --------------- depth = %d/%d ---------------\n", moveIndex, d);
|
||||
int k = 0;
|
||||
int cs = root->childrenSize;
|
||||
for (int i = 0; i < cs; i++) {
|
||||
if (root->children[i]->value == root->value) {
|
||||
loggerDebug("[%.2d] %d\t%s\t%d\t%d *\n", k,
|
||||
root->children[i]->move,
|
||||
moveToCommand(root->children[i]->move),
|
||||
root->children[i]->value,
|
||||
root->children[i]->rating);
|
||||
} else {
|
||||
loggerDebug("[%.2d] %d\t%s\t%d\t%d\n", k,
|
||||
root->children[i]->move,
|
||||
moveToCommand(root->children[i]->move),
|
||||
root->children[i]->value,
|
||||
root->children[i]->rating);
|
||||
}
|
||||
|
||||
k++;
|
||||
}
|
||||
loggerDebug("\n");
|
||||
#endif // IDS_DEBUG
|
||||
|
||||
lastValue = value;
|
||||
|
||||
#if 0
|
||||
if (value <= alpha) {
|
||||
alpha = -VALUE_INFINITE;
|
||||
beta = value + 1; // X
|
||||
continue;
|
||||
}
|
||||
if (value >= beta) {
|
||||
beta = VALUE_INFINITE;
|
||||
alpha = value - 1;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef IDS_WINDOW
|
||||
alpha = value - VALUE_IDS_WINDOW;
|
||||
beta = value + VALUE_IDS_WINDOW;
|
||||
|
@ -671,7 +271,7 @@ int AIAlgorithm::search(depth_t depth)
|
|||
|
||||
if (gameOptions.getIDSEnabled()) {
|
||||
#ifdef IDS_WINDOW
|
||||
value_t window = state->position.getPhase() == PHASE_PLACING ? VALUE_PLACING_WINDOW : VALUE_MOVING_WINDOW;
|
||||
value_t window = state->position->getPhase() == PHASE_PLACING ? VALUE_PLACING_WINDOW : VALUE_MOVING_WINDOW;
|
||||
alpha = value - window;
|
||||
beta = value + window;
|
||||
#else
|
||||
|
@ -727,8 +327,8 @@ value_t AIAlgorithm::MTDF(value_t firstguess, depth_t depth)
|
|||
value_t AIAlgorithm::search(depth_t depth, value_t alpha, value_t beta)
|
||||
{
|
||||
// 评价值
|
||||
value_t value, bestValue;
|
||||
bestValue = -VALUE_INFINITE;
|
||||
value_t value;
|
||||
value_t bestValue = -VALUE_INFINITE;
|
||||
|
||||
// 临时增加的深度,克服水平线效应用
|
||||
depth_t epsilon;
|
||||
|
@ -767,9 +367,6 @@ value_t AIAlgorithm::search(depth_t depth, value_t alpha, value_t beta)
|
|||
#endif /* ENDGAME_LEARNING */
|
||||
|
||||
#ifdef TRANSPOSITION_TABLE_ENABLE
|
||||
// 哈希类型
|
||||
enum bound_t hashf = BOUND_UPPER;
|
||||
|
||||
bound_t type = BOUND_NONE;
|
||||
|
||||
value_t probeVal = TT::probeHash(posKey, depth, alpha, beta, type
|
||||
|
@ -779,15 +376,10 @@ value_t AIAlgorithm::search(depth_t depth, value_t alpha, value_t beta)
|
|||
);
|
||||
|
||||
if (probeVal != VALUE_UNKNOWN) {
|
||||
#ifdef DEBUG_MODE
|
||||
assert(node != root);
|
||||
#endif
|
||||
#ifdef TRANSPOSITION_TABLE_DEBUG
|
||||
hashHitCount++;
|
||||
#endif
|
||||
#ifdef DEBUG_AB_TREE
|
||||
node->isHash = true;
|
||||
#endif
|
||||
|
||||
bestValue = probeVal;
|
||||
|
||||
#if 0
|
||||
|
@ -834,14 +426,6 @@ value_t AIAlgorithm::search(depth_t depth, value_t alpha, value_t beta)
|
|||
bestValue -= depth;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_AB_TREE
|
||||
if (requiredQuit) {
|
||||
node->isTimeout = true;
|
||||
} else {
|
||||
node->isLeaf = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef NULL_MOVE
|
||||
if (depth % 2 == 1)
|
||||
{
|
||||
|
@ -970,33 +554,11 @@ value_t AIAlgorithm::search(depth_t depth, value_t alpha, value_t beta)
|
|||
bestMove = move;
|
||||
}
|
||||
|
||||
//alpha = value;
|
||||
|
||||
break;
|
||||
}
|
||||
#if 0
|
||||
if (value >= beta) {
|
||||
bestValue = beta;
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
#ifdef DEBUG_AB_TREE
|
||||
node->alpha = alpha;
|
||||
node->beta = beta;
|
||||
#endif
|
||||
|
||||
if (gameOptions.getIDSEnabled()) {
|
||||
#ifdef IDS_ADD_VALUE
|
||||
node->children[0]->value += 1;
|
||||
bestValue += 1;
|
||||
#endif /* IDS_ADD_VALUE */
|
||||
}
|
||||
|
||||
#ifdef TRANSPOSITION_TABLE_ENABLE
|
||||
TT::recordHash(bestValue,
|
||||
depth,
|
||||
|
@ -1056,12 +618,9 @@ const char* AIAlgorithm::nextMove()
|
|||
{
|
||||
return moveToCommand(bestMove);
|
||||
|
||||
#if 0
|
||||
char charChoose = '*';
|
||||
|
||||
if (!root->childrenSize) {
|
||||
return "error!";
|
||||
}
|
||||
|
||||
Board::printBoard();
|
||||
|
||||
int moveIndex = 0;
|
||||
|
@ -1096,11 +655,11 @@ const char* AIAlgorithm::nextMove()
|
|||
#ifdef ENDGAME_LEARNING
|
||||
// 检查是否明显劣势
|
||||
if (gameOptions.getLearnEndgameEnabled()) {
|
||||
if (root->value <= -VALUE_STRONG) {
|
||||
if (bestValue <= -VALUE_STRONG) {
|
||||
Endgame endgame;
|
||||
endgame.type = state->position->sideToMove == PLAYER_BLACK ?
|
||||
ENDGAME_PLAYER_WHITE_WIN : ENDGAME_PLAYER_BLACK_WIN;
|
||||
hash_t endgameHash = this->state->getPosKey(); // TODO: 减少重复计算哈希
|
||||
hash_t endgameHash = state->position->getPosKey(); // TODO: 减少重复计算哈希
|
||||
recordEndgameHash(endgameHash, endgame);
|
||||
}
|
||||
}
|
||||
|
@ -1115,8 +674,6 @@ const char* AIAlgorithm::nextMove()
|
|||
}
|
||||
}
|
||||
|
||||
memmgr.memmgr_print_stats();
|
||||
|
||||
nodeCount = 0;
|
||||
|
||||
#ifdef TRANSPOSITION_TABLE_ENABLE
|
||||
|
@ -1135,6 +692,7 @@ const char* AIAlgorithm::nextMove()
|
|||
}
|
||||
|
||||
return moveToCommand(bestMove);
|
||||
#endif
|
||||
}
|
||||
#endif // ALPHABETA_AI
|
||||
|
||||
|
|
161
src/ai/search.h
161
src/ai/search.h
|
@ -22,17 +22,11 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef USE_STD_STACK
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#else
|
||||
#include "stack.h"
|
||||
#endif // USE_STD_STACK
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
#include "stack.h"
|
||||
#include "position.h"
|
||||
#include "tt.h"
|
||||
#include "hashmap.h"
|
||||
|
@ -41,19 +35,17 @@
|
|||
#include "memmgr.h"
|
||||
#include "misc.h"
|
||||
#include "movepick.h"
|
||||
#include "movegen.h"
|
||||
#ifdef CYCLE_STAT
|
||||
#include "stopwatch.h"
|
||||
#endif
|
||||
|
||||
#ifdef MCTS_AI
|
||||
#include "mcts.h"
|
||||
#endif
|
||||
|
||||
class AIAlgorithm;
|
||||
class StateInfo;
|
||||
class Node;
|
||||
class Position;
|
||||
class MovePicker;
|
||||
class ExtMove;
|
||||
|
||||
using namespace std;
|
||||
using namespace CTSL;
|
||||
|
@ -63,98 +55,9 @@ using namespace CTSL;
|
|||
// 另外,AI类是 Position 类的友元类,可以访问其私有变量
|
||||
// 尽量不要使用 Position 的操作函数,因为有参数安全性检测和不必要的赋值,影响效率
|
||||
|
||||
class Node
|
||||
{
|
||||
public:
|
||||
Node();
|
||||
~Node();
|
||||
|
||||
#ifdef MCTS_AI
|
||||
Node(Position &position);
|
||||
Node(Position &position, const move_t &move, Node *parent);
|
||||
#endif // MCTS_AI
|
||||
|
||||
bool hasChildren() const;
|
||||
|
||||
Node *addChild(
|
||||
const move_t &move,
|
||||
AIAlgorithm *ai,
|
||||
Position *position
|
||||
#ifdef TT_MOVE_ENABLE
|
||||
, const move_t &ttMove
|
||||
#endif // TT_MOVE_ENABLE
|
||||
);
|
||||
|
||||
#ifdef MCTS_AI
|
||||
bool hasUntriedMoves() const;
|
||||
template<typename RandomEngine>
|
||||
move_t getUntriedMove(RandomEngine *engine) const;
|
||||
Node *bestChildren() const;
|
||||
Node *selectChild() const;
|
||||
Node *addChild(const move_t &move, Position &position);
|
||||
void update(double result);
|
||||
string toString();
|
||||
string treeToString(int max_depth = 1000000, int indent = 0) const;
|
||||
string indentString(int indent) const;
|
||||
#endif // MCTS_AI
|
||||
|
||||
static const int NODE_CHILDREN_SIZE = MAX_MOVES;
|
||||
|
||||
Node *children[NODE_CHILDREN_SIZE];
|
||||
Node *parent { nullptr };
|
||||
|
||||
#ifdef MCTS_AI
|
||||
//atomic<double> wins;
|
||||
//atomic<int> visits;
|
||||
double wins { 0 };
|
||||
double score { 0 };
|
||||
Stack<move_t, NODE_CHILDREN_SIZE> moves;
|
||||
int visits { 0 };
|
||||
#else
|
||||
move_t moves[NODE_CHILDREN_SIZE];
|
||||
#endif // MCTS_AI
|
||||
|
||||
move_t move { MOVE_NONE };
|
||||
int childrenSize { 0 };
|
||||
|
||||
#ifdef ALPHABETA_AI
|
||||
value_t value { VALUE_UNKNOWN };
|
||||
rating_t rating { RATING_ZERO };
|
||||
#ifdef HOSTORY_HEURISTIC
|
||||
score_t score { 0 };
|
||||
#endif
|
||||
#endif // ALPHABETA_AI
|
||||
|
||||
player_t sideToMove { PLAYER_NOBODY };
|
||||
|
||||
#ifdef DEBUG_AB_TREE
|
||||
size_t id; // 结点编号
|
||||
char cmd[32];
|
||||
int depth; // 深度
|
||||
bool evaluated; // 是否评估过局面
|
||||
int alpha; // 当前搜索结点走棋方搜索到的最好值,任何比它小的值对当前结点的走棋方都没有意义。当函数递归时 Alpha 和 Beta 不但取负数而且要交换位置
|
||||
int beta; // 表示对手目前的劣势,这是对手所能承受的最坏结果,Beta 值越大,表示对手劣势越明显,如果当前结点返回 Beta 或比 Beta 更好的值,作为父结点的对方就绝对不会选择这种策略
|
||||
bool isTimeout; // 是否遍历到此结点时因为超时而被迫退出
|
||||
bool isLeaf; // 是否为叶子结点, 叶子结点是决胜局面
|
||||
bool visited; // 是否在遍历时访问过
|
||||
phase_t phase; // 摆棋阶段还是走棋阶段
|
||||
action_t action; // 动作状态
|
||||
int nPiecesOnBoardDiff; // 场上棋子个数和对手的差值
|
||||
int nPiecesInHandDiff; // 手中的棋子个数和对手的差值
|
||||
int nPiecesNeedRemove; // 手中有多少可去的子,如对手有可去的子则为负数
|
||||
struct Node *root; // 根节点
|
||||
#ifdef TRANSPOSITION_TABLE_ENABLE
|
||||
bool isHash; // 是否从 Hash 读取
|
||||
#endif /* TRANSPOSITION_TABLE_ENABLE */
|
||||
hash_t hash; // 哈希值
|
||||
#endif /* DEBUG_AB_TREE */
|
||||
};
|
||||
|
||||
class AIAlgorithm
|
||||
{
|
||||
public:
|
||||
MemoryManager memmgr;
|
||||
|
||||
#ifdef TIME_STAT
|
||||
// 排序算法耗时 (ms)
|
||||
TimePoint sortTime { 0 };
|
||||
|
@ -206,20 +109,6 @@ public:
|
|||
void clearTT();
|
||||
#endif
|
||||
|
||||
#ifdef ALPHABETA_AI
|
||||
// 比较函数
|
||||
static int nodeCompare(const Node *first, const Node *second);
|
||||
#endif // ALPHABETA_AI
|
||||
|
||||
#ifdef MCTS_AI
|
||||
// TODO: 分离到 MCTS 算法类
|
||||
Node *computeTree(Position position,
|
||||
const MCTSOptions options,
|
||||
mt19937_64::result_type initialSeed);
|
||||
move_t AIAlgorithm::computeMove(Position position,
|
||||
const MCTSOptions options);
|
||||
#endif
|
||||
|
||||
#ifdef ENDGAME_LEARNING
|
||||
bool findEndgameHash(hash_t hash, Endgame &endgame);
|
||||
static int recordEndgameHash(hash_t hash, const Endgame &endgame);
|
||||
|
@ -228,45 +117,33 @@ public:
|
|||
static void loadEndgameFileToHashMap();
|
||||
#endif // ENDGAME_LEARNING
|
||||
|
||||
|
||||
public: /* TODO: Move to private or protected */
|
||||
|
||||
// 结点个数;
|
||||
size_t nodeCount { 0 };
|
||||
|
||||
// 对合法的着法降序排序
|
||||
void sortMoves(Node *node);
|
||||
|
||||
// 清空节点树
|
||||
void deleteTree(Node *node);
|
||||
void deleteSubTree(Node *node);
|
||||
|
||||
// 构造根节点
|
||||
void buildRoot();
|
||||
|
||||
// 评价函数
|
||||
value_t evaluate(Node *node);
|
||||
#ifdef EVALUATE_ENABLE
|
||||
|
||||
// 评价函数
|
||||
value_t evaluate();
|
||||
|
||||
#ifdef EVALUATE_MATERIAL
|
||||
value_t evaluateMaterial(Node *node);
|
||||
value_t evaluateMaterial();
|
||||
#endif
|
||||
#ifdef EVALUATE_SPACE
|
||||
value_t evaluateSpace(Node *node);
|
||||
value_t evaluateSpace();
|
||||
#endif
|
||||
#ifdef EVALUATE_MOBILITY
|
||||
value_t evaluateMobility(Node *node);
|
||||
value_t evaluateMobility();
|
||||
#endif
|
||||
#ifdef EVALUATE_TEMPO
|
||||
value_t evaluateTempo(Node *node);
|
||||
value_t evaluateTempo();
|
||||
#endif
|
||||
#ifdef EVALUATE_THREAT
|
||||
value_t evaluateThreat(Node *node);
|
||||
value_t evaluateThreat();
|
||||
#endif
|
||||
#ifdef EVALUATE_SHAPE
|
||||
value_t evaluateShape(Node *node);
|
||||
value_t evaluateShape();
|
||||
#endif
|
||||
#ifdef EVALUATE_MOTIF
|
||||
value_t evaluateMotif(Node *node);
|
||||
value_t evaluateMotif();
|
||||
#endif
|
||||
#endif /* EVALUATE_ENABLE */
|
||||
|
||||
|
@ -296,22 +173,14 @@ private:
|
|||
|
||||
Position *position { nullptr };
|
||||
|
||||
// 根节点
|
||||
Node *root {nullptr};
|
||||
|
||||
// 局面数据栈
|
||||
#ifdef USE_STD_STACK
|
||||
stack<Position, vector<Position> > positionStack;
|
||||
#else
|
||||
Stack<Position> positionStack;
|
||||
#endif /* USE_STD_STACK */
|
||||
|
||||
Stack<move_t, MAX_MOVES> moves;
|
||||
|
||||
// 标识,用于跳出剪枝算法,立即返回
|
||||
bool requiredQuit {false};
|
||||
|
||||
move_t bestMove { MOVE_NONE };
|
||||
//value_t bestvalue { VALUE_ZERO };
|
||||
|
||||
depth_t originDepth { 0 };
|
||||
|
||||
|
|
|
@ -97,11 +97,6 @@ void AiThread::emitCommand()
|
|||
emit command(strCommand);
|
||||
}
|
||||
|
||||
#ifdef MCTS_AI
|
||||
move_t computeMove(Position position,
|
||||
const MCTSOptions options);
|
||||
#endif // MCTS_AI
|
||||
|
||||
#ifdef OPENING_BOOK
|
||||
deque<int> openingBookDeque(
|
||||
{
|
||||
|
|
3284
src/base/sort.h
3284
src/base/sort.h
File diff suppressed because it is too large
Load Diff
|
@ -29,10 +29,6 @@
|
|||
#include "board.h"
|
||||
#include "search.h"
|
||||
|
||||
#ifdef MCTS_AI
|
||||
#include "mcts.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
class AIAlgorithm;
|
||||
|
@ -231,15 +227,6 @@ public:
|
|||
// 设置提示
|
||||
void setTips();
|
||||
|
||||
// 着法生成
|
||||
void generateChildren(const Stack<move_t, MAX_MOVES> &moves,
|
||||
AIAlgorithm *ai,
|
||||
Node *node
|
||||
#ifdef TT_MOVE_ENABLE
|
||||
, move_t ttMove
|
||||
#endif // TT_MOVE_ENABLE
|
||||
);
|
||||
|
||||
// 着法生成
|
||||
int generateMoves(Stack<move_t, MAX_MOVES> &moves);
|
||||
int generateNullMove(Stack<move_t, MAX_MOVES> &moves);
|
||||
|
@ -263,19 +250,6 @@ public:
|
|||
hash_t updateHashMisc();
|
||||
hash_t getNextMainHash(move_t m);
|
||||
|
||||
#ifdef MCTS_AI
|
||||
// MCTS 相关
|
||||
Stack<move_t, MAX_MOVES> moves;
|
||||
|
||||
//template<typename RandomEngine>
|
||||
//void doRandomMove(RandomEngine *engine);
|
||||
void doRandomMove(Node *node, mt19937_64 *engine);
|
||||
|
||||
bool hasMoves() const;
|
||||
double getResult(player_t currentSideToMove) const;
|
||||
void checkInvariant() const;
|
||||
#endif // MCTS_AI
|
||||
|
||||
// 赢盘数
|
||||
int score[COLOR_COUNT] = { 0 };
|
||||
int score_draw { 0 };
|
||||
|
|
|
@ -1204,7 +1204,7 @@ bool GameController::updateScence(StateInfo &g)
|
|||
deletedPiece = piece;
|
||||
|
||||
#ifdef GAME_PLACING_SHOW_CAPTURED_PIECES
|
||||
if (state.position->getPhase() == GAME_MOVING) {
|
||||
if (state.position->getPhase() == PHASE_MOVING) {
|
||||
#endif
|
||||
QPropertyAnimation *animation = new QPropertyAnimation(piece, "pos");
|
||||
animation->setDuration(durationTime);
|
||||
|
|
Loading…
Reference in New Issue