每走一步棋更新 Hash 值以提升效率

已知问题:
棋力明显下滑,待调试
This commit is contained in:
CalciteM Team 2019-07-07 01:04:43 +08:00
parent ae247bad52
commit d38200dce5
5 changed files with 190 additions and 47 deletions

View File

@ -1,13 +1,13 @@
#ifndef CONFIG_H
#define CONFIG_H
//#define DEBUG
#define DEBUG
#define DEAL_WITH_HORIZON_EFFECT
//#define DEAL_WITH_HORIZON_EFFECT
//#define RANDOM_BEST_MOVE
//#define HASH_MAP_ENABLE
#define HASH_MAP_ENABLE
#ifdef DEBUG
#define DONOT_PLAY_SOUND

View File

@ -112,15 +112,20 @@ const int NineChess::onBoard[(N_RINGS + 2) * N_SEATS] = {
};
// 着法表
int NineChess::moveTable[(N_RINGS + 2) * N_SEATS][N_MOVE_DIRECTIONS] = { 0 };
int NineChess::moveTable[N_POINTS][N_MOVE_DIRECTIONS] = { 0 };
// 成三表
int NineChess::millTable[(N_RINGS + 2) * N_SEATS][N_DIRECTIONS][N_RINGS - 1] = { 0 };
int NineChess::millTable[N_POINTS][N_DIRECTIONS][N_RINGS - 1] = { 0 };
NineChess::NineChess()
{
// 单独提出 board,免得每次都写 context.board;
// 单独提出 board 等数据,免得每次都写 context.board;
board_ = context.board;
//hash_ = &context.hash;
//zobrist_ = &context.zobrist;
// 创建哈希数据
constructHash();
// 默认选择第1号规则即“打三棋”
setContext(&RULES[1]);
@ -171,6 +176,24 @@ NineChess::~NineChess()
{
}
void NineChess::constructHash()
{
context.hash = 0;
#if 0
gameMovingHash = rand64();
player2sTurnHash = rand64();
uint64_t zobrist[N_POINTS][POINT_TYPE_COUNT];
for (int p = 0; p < N_POINTS; p++) {
for (int t = NineChess::POINT_TYPE_EMPTY; t <= NineChess::POINT_TYPE_FORBIDDEN; t++) {
zobrist[p][t] = rand64();
}
}
#endif
}
NineChess::Player NineChess::getOpponent(NineChess::Player player)
{
switch (player)
@ -301,7 +324,7 @@ void NineChess::createMillTable()
// 设置棋局状态和棋盘数据,用于初始化
bool NineChess::setContext(const struct Rule *rule, int maxStepsLedToDraw, int maxTimeLedToLose,
int initialStep, int flags, const char *board,
int nPiecesInHand_1, int nPiecesInHand_2, int nPiecesNeedRemove)
int nPiecesInHand_1, int nPiecesInHand_2, int nPiecesNeedRemove, uint64_t hash)
{
// 有效性判断
if (maxStepsLedToDraw < 0 || maxTimeLedToLose < 0 || initialStep < 0 ||
@ -320,24 +343,39 @@ bool NineChess::setContext(const struct Rule *rule, int maxStepsLedToDraw, int m
this->currentStep = initialStep;
// 局面阶段标识
if (flags & GAME_NOTSTARTED)
if (flags & GAME_NOTSTARTED) {
context.stage = GAME_NOTSTARTED;
else if (flags & GAME_PLACING)
}
else if (flags & GAME_PLACING) {
context.stage = GAME_PLACING;
else if (flags & GAME_MOVING)
}
else if (flags & GAME_MOVING) {
context.stage = GAME_MOVING;
else if (flags & GAME_OVER)
//context.hash ^= // TODO
}
else if (flags & GAME_OVER) {
context.stage = GAME_OVER;
else
}
else {
return false;
}
// 轮流状态标识
if (flags & PLAYER1)
if (flags & PLAYER1) {
// if (context.turn == PLAYER2) {
// context.hash ^= player2sTurnHash;
// }
context.turn = PLAYER1;
else if (flags & PLAYER2)
}
else if (flags & PLAYER2) {
// if (context.turn == PLAYER1) {
// context.hash ^= player2sTurnHash;
// }
context.turn = PLAYER2;
else
}
else {
return false;
}
// 动作状态标识
if (flags & ACTION_CHOOSE)
@ -352,19 +390,37 @@ bool NineChess::setContext(const struct Rule *rule, int maxStepsLedToDraw, int m
// 当前棋局3×8
if (board == nullptr) {
memset(context.board, 0, sizeof(context.board));
context.hash = 0;
} else {
memcpy(context.board, board, sizeof(context.board));
context.hash = hash;
}
// 计算盘面子数
// 棋局抽象为一个5×8的数组上下两行留空
/*
0x00
0x0F
0x110x1C 112
0x210x2C 112
(board[i] & 0x10)
(board[i] & 0x20)
*/
context.nPiecesOnBoard_1 = context.nPiecesOnBoard_2 = 0;
for (int r = 1; r < N_RINGS + 2; r++) {
for (int s = 0; s < N_SEATS; s++) {
if (context.board[r * N_SEATS + s] & '\x10')
int pos = r * N_SEATS + s;
if (context.board[pos] & '\x10') {
context.nPiecesOnBoard_1++;
else if (context.board[r * N_SEATS + s] & '\x20') {
}
else if (context.board[pos] & '\x20') {
context.nPiecesOnBoard_2++;
}
else if (context.board[pos] & '\x0F') {
// 不计算盘面子数
}
//updateHash(pos);
}
}
@ -434,7 +490,8 @@ bool NineChess::setContext(const struct Rule *rule, int maxStepsLedToDraw, int m
}
void NineChess::getContext(struct Rule &rule, int &step, int &flags,
int *&board, int &nPiecesInHand_1, int &nPiecesInHand_2, int &num_NeedRemove)
int *&board, int &nPiecesInHand_1, int &nPiecesInHand_2, int &num_NeedRemove,
uint64_t &hash)
{
rule = this->currentRule;
step = this->currentStep;
@ -443,6 +500,7 @@ void NineChess::getContext(struct Rule &rule, int &step, int &flags,
nPiecesInHand_1 = context.nPiecesInHand_1;
nPiecesInHand_2 = context.nPiecesInHand_2;
num_NeedRemove = context.nPiecesNeedRemove;
hash = context.hash;
}
bool NineChess::reset()
@ -486,6 +544,9 @@ bool NineChess::reset()
// 用时置零
elapsedMS_1 = elapsedMS_2 = 0;
// 哈希归零
context.hash = 0;
// 提示
setTips();
@ -548,7 +609,7 @@ bool NineChess::getPieceCP(const Player &player, const int &number, int &c, int
else
return false;
for (int i = N_SEATS; i < N_SEATS * (N_RINGS + 1); i++) {
for (int i = POS_BEGIN; i < POS_END; i++) {
if (board_[i] == piece) {
pos2cp(i, c, p);
return true;
@ -578,7 +639,7 @@ bool NineChess::getCurrentPiece(Player &player, int &number)
bool NineChess::pos2cp(const int pos, int &c, int &p)
{
if (pos < N_SEATS || pos >= N_SEATS * (N_RINGS + 1))
if (pos < POS_BEGIN || POS_END <= pos)
return false;
c = pos / N_SEATS;
@ -609,9 +670,10 @@ bool NineChess::place(int c, int p, long time_p /* = -1*/)
if (context.action != ACTION_PLACE)
return false;
// 如果落子位置在棋盘外、已有子点或禁点返回false
// 转换为 pos
int pos = cp2pos(c, p);
// 如果落子位置在棋盘外、已有子点或禁点返回false
if (!onBoard[pos] || board_[pos])
return false;
@ -637,6 +699,7 @@ bool NineChess::place(int c, int p, long time_p /* = -1*/)
}
board_[pos] = piece;
updateHash(pos);
move_ = pos;
player_ms = update(time_p);
sprintf(cmdline, "(%1u,%1u) %02u:%02u.%03u",
@ -725,7 +788,9 @@ bool NineChess::place(int c, int p, long time_p /* = -1*/)
c, p, player_ms / 60000, (player_ms % 60000) / 1000, player_ms % 1000);
cmdlist.push_back(string(cmdline));
board_[pos] = board_[currentPos];
updateHash(pos);
board_[currentPos] = '\x00';
updateHash(currentPos);
currentPos = pos;
currentStep++;
n = addMills(currentPos);
@ -811,6 +876,7 @@ bool NineChess::capture(int c, int p, long time_p /* = -1*/)
currentPos = 0;
context.nPiecesNeedRemove--;
currentStep++;
updateHash(pos);
// 去子完成
// 如果决出胜负
@ -937,6 +1003,7 @@ bool NineChess::place(int pos)
// 如非“落子”状态返回false
if (context.action != ACTION_PLACE)
return false;
// 如果落子位置在棋盘外、已有子点或禁点返回false
if (!onBoard[pos] || board_[pos])
return false;
@ -959,6 +1026,7 @@ bool NineChess::place(int pos)
}
board_[pos] = piece;
updateHash(pos);
move_ = pos;
currentPos = pos;
//step++;
@ -1034,7 +1102,9 @@ bool NineChess::place(int pos)
// 移子
move_ = (currentPos << 8) + pos;
board_[pos] = board_[currentPos];
updateHash(pos);
board_[currentPos] = '\x00';
updateHash(currentPos);
currentPos = pos;
//step++;
n = addMills(currentPos);
@ -1111,6 +1181,7 @@ bool NineChess::capture(int pos)
move_ = -pos;
currentPos = 0;
context.nPiecesNeedRemove--;
updateHash(pos);
//step++;
// 去子完成
@ -1215,36 +1286,47 @@ bool NineChess::choose(int pos)
return false;
}
uint64_t NineChess::getHash()
{
return context.hash;
}
// hash函数对应可重复去子的规则
uint64_t NineChess::chessHash()
uint64_t NineChess::updateHash(int pos)
{
/*
* hash各数据位详解hash
* 57-640
* 5601
* 5501
* 7-544822448
* 56-630
* 5501
* 5401
* 6-534822448
* 0b000b010b100b11
* 5-6232
* 1-4player1的手棋数player2的
* 4-5232
* 0-3player1的手棋数player2的
*/
#if 0
uint64_t hash = 0ull;
for (int i = POS_BEGIN; i < POS_END; i++) {
hash |= board_[i] & 0x30;
hash <<= 2;
}
#endif
uint64_t temp = board_[pos] & 0x30 >> 4;
context.hash |= (temp) << ((pos - 8) * 2 + 6);
if (context.turn == PLAYER2)
hash |= 1ull << 55;
context.hash |= 1ull << 55;
if (context.action == ACTION_CAPTURE)
hash |= 1ull << 54;
context.hash |= 1ull << 54;
hash |= (uint64_t)context.nPiecesNeedRemove << 4;
hash |= context.nPiecesInHand_1;
context.hash |= (uint64_t)context.nPiecesNeedRemove << 4;
context.hash |= context.nPiecesInHand_1;
return hash;
return context.hash;
}
bool NineChess::giveup(Player loser)
@ -1673,10 +1755,15 @@ bool NineChess::isAllSurrounded(enum Player ply)
void NineChess::cleanForbiddenPoints()
{
int pos = 0;
for (int i = 1; i <= N_RINGS; i++) {
for (int j = 0; j < N_SEATS; j++) {
if (board_[i * N_SEATS + j] == '\x0f')
board_[i * N_SEATS + j] = '\x00';
pos = i * N_SEATS + j;
if (board_[pos] == '\x0f') {
board_[pos] = '\x00';
updateHash(pos);
}
}
}
}
@ -1774,7 +1861,9 @@ void NineChess::mirror(bool cmdChange /*= true*/)
for (j = 1; j < N_SEATS / 2; j++) {
ch = board_[i * N_SEATS + j];
board_[i * N_SEATS + j] = board_[(i + 1) * N_SEATS - j];
//updateHash(i * N_SEATS + j);
board_[(i + 1) * N_SEATS - j] = ch;
//updateHash((i + 1) * N_SEATS - j);
}
}
@ -1891,7 +1980,9 @@ void NineChess::turn(bool cmdChange /*= true*/)
for (i = 0; i < N_SEATS; i++) {
ch = board_[N_SEATS + i];
board_[N_SEATS + i] = board_[N_SEATS * N_RINGS + i];
//updateHash(N_SEATS + i);
board_[N_SEATS * N_RINGS + i] = ch;
//updateHash(N_SEATS * N_RINGS + i);
}
uint64_t llp1, llp2, llp3;
@ -2080,9 +2171,12 @@ void NineChess::rotate(int degrees, bool cmdChange /*= true*/)
ch2 = board_[i * N_SEATS + 1];
for (j = 0; j < N_SEATS - 2; j++) {
board_[i * N_SEATS + j] = board_[i * N_SEATS + j + 2];
//updateHash(i * N_SEATS + j);
}
board_[i * N_SEATS + 6] = ch1;
//updateHash(i * N_SEATS + 6);
board_[i * N_SEATS + 7] = ch2;
//updateHash(i * N_SEATS + 7);
}
} else if (degrees == 6) {
for (i = 1; i <= N_RINGS; i++) {
@ -2090,16 +2184,21 @@ void NineChess::rotate(int degrees, bool cmdChange /*= true*/)
ch2 = board_[i * N_SEATS + 6];
for (j = N_SEATS - 1; j >= 2; j--) {
board_[i * N_SEATS + j] = board_[i * N_SEATS + j - 2];
//updateHash(i * N_SEATS + j);
}
board_[i * N_SEATS + 1] = ch1;
//updateHash(i * N_SEATS + 1);
board_[i * N_SEATS] = ch2;
//updateHash(i * N_SEATS);
}
} else if (degrees == 4) {
for (i = 1; i <= N_RINGS; i++) {
for (j = 0; j < N_SEATS / 2; j++) {
ch1 = board_[i * N_SEATS + j];
board_[i * N_SEATS + j] = board_[i * N_SEATS + j + 4];
//updateHash(i * N_SEATS + j);
board_[i * N_SEATS + j + 4] = ch1;
//updateHash(i * N_SEATS + j + 4);
}
}
} else

View File

@ -36,6 +36,9 @@ public:
// 横直斜3个方向禁止修改
static const int N_DIRECTIONS = 3;
// 棋盘点的个数40
static const int N_POINTS = (NineChess::N_RINGS + 2) * NineChess::N_SEATS;
// 移动方向包括顺时针、逆时针、向内、向外4个方向
enum MoveDirection
{
@ -123,6 +126,14 @@ public:
GAME_OVER = 0x0008 // 结局
};
uint64_t rand64(void) {
return rand() ^
((uint64_t)rand() << 15) ^
((uint64_t)rand() << 30) ^
((uint64_t)rand() << 45) ^
((uint64_t)rand() << 60);
}
// 玩家标识, 轮流状态, 胜负标识
enum Player : uint16_t
{
@ -143,6 +154,16 @@ public:
ACTION_CAPTURE = 0x0400 // 提子
};
// 棋盘点上棋子的类型
enum PointType : uint16_t
{
POINT_TYPE_EMPTY = 0, // 没有棋子
POINT_TYPE_PLAYER1 = 1, // 先手的子
POINT_TYPE_PLAYER2 = 2, // 后手的子
POINT_TYPE_FORBIDDEN = 3, // 禁点
POINT_TYPE_COUNT = 4
};
// 棋局结构体,算法相关,包含当前棋盘数据
// 单独分离出来供AI判断局面用生成置换表时使用
struct ChessContext
@ -156,7 +177,13 @@ public:
(board[i] & 0x10)
(board[i] & 0x20)
*/
int board[(NineChess::N_RINGS + 2) * NineChess::N_SEATS];
int board[N_POINTS];
// 局面哈希值
uint64_t hash;
// Zobrist 数组
//uint64_t zobrist[N_POINTS][POINT_TYPE_COUNT];
// 局面阶段标识
enum NineChess::GameStage stage;
@ -219,6 +246,9 @@ private:
// 生成成三表
void createMillTable();
// 创建哈希值
void constructHash();
public:
explicit NineChess();
virtual ~NineChess();
@ -238,12 +268,14 @@ public:
const char *board = nullptr, // 默认空棋盘
int nPiecesInHand_1 = 12, // 玩家1剩余未放置子数
int nPiecesInHand_2 = 12, // 玩家2剩余未放置子数
int nPiecesNeedRemove = 0 // 尚待去除的子数
int nPiecesNeedRemove = 0, // 尚待去除的子数
uint64_t hash = 0ull // Hash 为0
);
// 获取棋局状态和棋盘上下文
void getContext(struct Rule &rule, int &step, int &flags, int *&board,
int &nPiecesInHand_1, int &p2_nPiecesInHand_2InHand, int &nPiecesNeedRemove);
int &nPiecesInHand_1, int &p2_nPiecesInHand_2InHand, int &nPiecesNeedRemove,
uint64_t &hash);
// 获取当前规则
const struct Rule *getRule() const
@ -448,7 +480,8 @@ protected:
bool capture(int pos);
// hash函数
uint64_t chessHash();
uint64_t getHash();
uint64_t updateHash(int pos);
private:
// 当前使用的规则
@ -457,9 +490,12 @@ private:
// 棋局上下文
struct ChessContext context;
// 棋局数据中的棋盘数据,单独提出来
// 棋局上下文中的棋盘数据,单独提出来
int *board_;
// 棋局哈希值
// uint64_t hash;
// 选中的棋子在board中的位置
int currentPos;

View File

@ -447,8 +447,8 @@ int NineChessAi_ab::changeDepth(int originalDepth)
//int depthTable[] = { 2, 11, 11, 11, 11, 10, 9, 8, 8, 8, 7, 7, 1 };
int depthTable[] = { 2, 12, 12, 12, 12, 11, 10, 9, 9, 9, 8, 7, 1 };
#else
//int depthTable[] = { 2, 12, 12, 12, 12, 11, 10, 9, 8, 8, 8, 7, 1 };
int depthTable[] = { 2, 12, 12, 12, 12, 11, 10, 9, 9, 9, 8, 7, 1 };
int depthTable[] = { 2, 12, 12, 12, 12, 11, 10, 9, 8, 8, 8, 7, 1 };
//int depthTable[] = { 2, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 1 };
#endif // DEAL_WITH_HORIZON_EFFECT
newDepth = depthTable[chessTemp.getPiecesInHandCount_1()];
#elif defined GAME_PLACING_FIXED_DEPTH
@ -516,6 +516,7 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node)
// 初始化
node->isLeaf = false;
node->isTimeout = false;
node->isHash = false;
#endif
// 搜索到叶子节点(决胜局面)
@ -557,7 +558,7 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node)
#ifdef HASH_MAP_ENABLE
// 检索 hashmap
uint64_t hash = chessTemp.chessHash();
uint64_t hash = chessTemp.getHash();
node->hash = hash;
hashMapMutex.lock();
@ -567,6 +568,11 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node)
if (node != rootNode &&
iter != hashmap.end() &&
iter->second.depth >= depth) {
#ifdef DEBUG_AB_TREE
node->isHash = true;
#endif
// TODO: 处理 Alpha/Beta 确切值
node->value = iter->second.value;
if (chessContext->turn == NineChess::PLAYER1)
@ -791,6 +797,7 @@ const char *NineChessAi_ab::move2string(int move)
unordered_map<uint64_t, NineChessAi_ab::HashValue>::iterator NineChessAi_ab::findHash(uint64_t hash)
{
auto iter = hashmap.find(hash);
if (iter != hashmap.end())
return iter;
@ -805,7 +812,7 @@ unordered_map<uint64_t, NineChessAi_ab::HashValue>::iterator NineChessAi_ab::fin
chessTempShift.turn(false);
for (int k = 0; k < 4; k++) {
chessTempShift.rotate(k * 90, false);
iter = hashmap.find(chessTempShift.chessHash());
iter = hashmap.find(chessTempShift.getHash());
if (iter != hashmap.end())
return iter;
}

View File

@ -48,12 +48,13 @@ public:
#ifdef DEBUG_AB_TREE
string cmd;
enum NineChess::Player player; // 此招是谁下的
int depth;
int depth; // 深度
bool evaluated; // 是否评估过局面
int alpha; // 当前搜索结点走棋方搜索到的最好值,任何比它小的值对当前结点的走棋方都没有意义。当函数递归时 Alpha 和 Beta 不但取负数而且要交换位置
int beta; // 表示对手目前的劣势这是对手所能承受的最坏结果Beta 值越大,表示对手劣势越明显,如果当前结点返回 Beta 或比 Beta 更好的值,作为父结点的对方就绝对不会选择这种策略
bool isTimeout; // 是否遍历到此结点时因为超时而被迫退出
bool isLeaf; // 是否为叶子结点, 叶子结点是决胜局面
bool isHash; // 是否从 Hash 读取
NineChess::GameStage stage; // 摆棋阶段还是走棋阶段
NineChess::Action action; // 动作状态
int nPiecesOnBoardDiff; // 场上棋子个数和对手的差值