refactor: 清理代码

包括暂时删除 MCTS 大部分代码。
后续再加回来。
This commit is contained in:
Calcitem 2020-05-09 00:17:27 +08:00
parent 08b07c0de8
commit a334e7ee8d
14 changed files with 33 additions and 4522 deletions

View File

@ -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

View File

@ -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 \

View File

@ -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" />

View File

@ -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>

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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,31 +554,9 @@ 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
@ -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

View File

@ -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();
#ifdef EVALUATE_ENABLE
// 评价函数
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 };

View File

@ -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(
{

File diff suppressed because it is too large Load Diff

View File

@ -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 };

View File

@ -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);