refactor: 创建 board 和 rule 模块

* 将部分逻辑转移到这两个 class;
* 创建了 types.h 将部分类型转移到此文件;
* 将 millList 由 list 改为 vector, 否则容易出现段错误.
This commit is contained in:
CalciteM 2019-09-08 15:15:21 +08:00
parent ae91aa1942
commit dc90db249c
25 changed files with 1775 additions and 1590 deletions

View File

@ -121,7 +121,7 @@
#endif
#ifdef DEBUG_MODE
#define DRAW_SEAT_NUMBER
#define PLAYER_DRAW_SEAT_NUMBER
#endif
#ifndef MOBILE_APP_UI

View File

@ -24,7 +24,9 @@ INCLUDEPATH += src/ui/qt
SOURCES += \
src/ai/evaluate.cpp \
src/ai/movegen.cpp \
src/game/board.cpp \
src/game/millgame.cpp \
src/game/rule.cpp \
src/main.cpp \
src/base/thread.cpp \
src/ai/search.cpp \
@ -52,7 +54,10 @@ HEADERS += \
src/base/thread.h \
src/ai/search.h \
src/ai/zobrist.h \
src/game/board.h \
src/game/millgame.h \
src/game/rule.h \
src/game/types.h \
src/ui/qt/client.h \
src/ui/qt/gamecontroller.h \
src/ui/qt/gamescene.h \

View File

@ -451,7 +451,10 @@
<ClInclude Include="src\base\MemoryPool.h" />
<ClInclude Include="src\base\stackalloc.h" />
<QtMoc Include="src\base\thread.h" />
<ClInclude Include="src\game\board.h" />
<ClInclude Include="src\game\millgame.h" />
<ClInclude Include="src\game\rule.h" />
<ClInclude Include="src\game\types.h" />
<ClInclude Include="src\ui\qt\boarditem.h" />
<QtMoc Include="src\ui\qt\client.h" />
<QtMoc Include="src\ui\qt\gamecontroller.h" />
@ -696,7 +699,9 @@
<ClCompile Include="src\ai\movegen.cpp" />
<ClCompile Include="src\ai\search.cpp" />
<ClCompile Include="src\base\thread.cpp" />
<ClCompile Include="src\game\board.cpp" />
<ClCompile Include="src\game\millgame.cpp" />
<ClCompile Include="src\game\rule.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\ui\qt\boarditem.cpp" />
<ClCompile Include="src\ui\qt\client.cpp" />

View File

@ -102,6 +102,15 @@
<ClInclude Include="src\ai\movegen.h">
<Filter>ai</Filter>
</ClInclude>
<ClInclude Include="src\game\board.h">
<Filter>game</Filter>
</ClInclude>
<ClInclude Include="src\game\rule.h">
<Filter>game</Filter>
</ClInclude>
<ClInclude Include="src\game\types.h">
<Filter>game</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">
@ -316,6 +325,12 @@
<ClCompile Include="src\ai\movegen.cpp">
<Filter>ai</Filter>
</ClCompile>
<ClCompile Include="src\game\board.cpp">
<Filter>game</Filter>
</ClCompile>
<ClCompile Include="src\game\rule.cpp">
<Filter>game</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="millgame.rc">

View File

@ -1,6 +1,6 @@
#include "evaluate.h"
Evaluation::value_t Evaluation::getValue(MillGame &chessTemp, MillGame::ChessContext *chessContext, MillGameAi_ab::Node *node)
value_t Evaluation::getValue(MillGame &chessTemp, ChessContext *chessContext, MillGameAi_ab::Node *node)
{
// 初始评估值为0对先手有利则增大对后手有利则减小
value_t value = 0;
@ -16,10 +16,10 @@ Evaluation::value_t Evaluation::getValue(MillGame &chessTemp, MillGame::ChessCon
#endif
switch (chessContext->stage) {
case MillGame::GAME_NOTSTARTED:
case GAME_NOTSTARTED:
break;
case MillGame::GAME_PLACING:
case GAME_PLACING:
// 按手中的棋子计分不要break;
nPiecesInHandDiff = chessContext->nPiecesInHand_1 - chessContext->nPiecesInHand_2;
value += nPiecesInHandDiff * 50;
@ -36,13 +36,13 @@ Evaluation::value_t Evaluation::getValue(MillGame &chessTemp, MillGame::ChessCon
switch (chessContext->action) {
// 选子和落子使用相同的评价方法
case MillGame::ACTION_CHOOSE:
case MillGame::ACTION_PLACE:
case ACTION_CHOOSE:
case ACTION_PLACE:
break;
// 如果形成去子状态每有一个可去的子算100分
case MillGame::ACTION_CAPTURE:
nPiecesNeedRemove = (chessContext->turn == MillGame::PLAYER1) ?
case ACTION_CAPTURE:
nPiecesNeedRemove = (chessContext->turn == PLAYER1) ?
chessContext->nPiecesNeedRemove : -(chessContext->nPiecesNeedRemove);
value += nPiecesNeedRemove * 100;
#ifdef DEBUG_AB_TREE
@ -55,24 +55,24 @@ Evaluation::value_t Evaluation::getValue(MillGame &chessTemp, MillGame::ChessCon
break;
case MillGame::GAME_MOVING:
case GAME_MOVING:
// 按场上棋子计分
value += chessContext->nPiecesOnBoard_1 * 100 - chessContext->nPiecesOnBoard_2 * 100;
#ifdef EVALUATE_MOBILITY
// 按棋子活动能力计分
value += chessTemp.getMobilityDiff(false) * 10;
value += chessTemp.getMobilityDiff(chessContext->turn, chessTemp.currentRule, chessContext->nPiecesInHand_1, chessContext->nPiecesInHand_2, false) * 10;
#endif /* EVALUATE_MOBILITY */
switch (chessContext->action) {
// 选子和落子使用相同的评价方法
case MillGame::ACTION_CHOOSE:
case MillGame::ACTION_PLACE:
case ACTION_CHOOSE:
case ACTION_PLACE:
break;
// 如果形成去子状态每有一个可去的子算128分
case MillGame::ACTION_CAPTURE:
nPiecesNeedRemove = (chessContext->turn == MillGame::PLAYER1) ?
case ACTION_CAPTURE:
nPiecesNeedRemove = (chessContext->turn == PLAYER1) ?
chessContext->nPiecesNeedRemove : -(chessContext->nPiecesNeedRemove);
value += nPiecesNeedRemove * 128;
#ifdef DEBUG_AB_TREE
@ -86,10 +86,10 @@ Evaluation::value_t Evaluation::getValue(MillGame &chessTemp, MillGame::ChessCon
break;
// 终局评价最简单
case MillGame::GAME_OVER:
case GAME_OVER:
// 布局阶段闷棋判断
if (chessContext->nPiecesOnBoard_1 + chessContext->nPiecesOnBoard_2 >=
MillGame::N_SEATS * MillGame::N_RINGS) {
Board::N_SEATS * Board::N_RINGS) {
if (chessTemp.getRule()->isStartingPlayerLoseWhenBoardFull) {
// winner = PLAYER2;
value -= 10000;
@ -102,11 +102,11 @@ Evaluation::value_t Evaluation::getValue(MillGame &chessTemp, MillGame::ChessCon
}
// 走棋阶段被闷判断
if (chessContext->action == MillGame::ACTION_CHOOSE &&
chessTemp.isAllSurrounded(chessContext->turn) &&
if (chessContext->action == ACTION_CHOOSE &&
chessTemp.context.board.isAllSurrounded(chessContext->turn, chessTemp.currentRule, chessContext->nPiecesOnBoard_1, chessContext->nPiecesOnBoard_2, chessContext->turn) &&
chessTemp.getRule()->isLoseWhenNoWay) {
// 规则要求被“闷”判负,则对手获胜
if (chessContext->turn == MillGame::PLAYER1) {
if (chessContext->turn == PLAYER1) {
value -= 10000;
#ifdef DEBUG_AB_TREE
node->result = -2;

View File

@ -13,57 +13,55 @@ public:
Evaluation &operator=(const Evaluation &) = delete;
using value_t = MillGameAi_ab::value_t;
static value_t getValue(MillGame &chessTemp, MillGame::ChessContext *chessContext, MillGameAi_ab::Node *node);
static value_t getValue(MillGame &chessTemp, ChessContext *chessContext, MillGameAi_ab::Node *node);
// ÆÀ¹À×ÓÁ¦
#ifdef EVALUATE_ENABLE
#ifdef EVALUATE_MATERIAL
static value_t evaluateMaterial(Node *node)
static value_t evaluateMaterial(MillGameAi_ab::Node *node)
{
return 0;
}
#endif
#ifdef EVALUATE_SPACE
static value_t evaluateSpace(Node *node)
static value_t evaluateSpace(MillGameAi_ab::Node *node)
{
return 0;
}
#endif
#ifdef EVALUATE_MOBILITY
static value_t evaluateMobility(Node *node)
static value_t evaluateMobility(MillGameAi_ab::Node *node)
{
return 0;
}
#endif
#ifdef EVALUATE_TEMPO
static value_t evaluateTempo(Node *node)
static value_t evaluateTempo(MillGameAi_ab::Node *node)
{
return 0;
}
#endif
#ifdef EVALUATE_THREAT
static value_t evaluateThreat(Node *node)
static value_t evaluateThreat(MillGameAi_ab::Node *node)
{
return 0;
}
#endif
#ifdef EVALUATE_SHAPE
static value_t evaluateShape(Node *node)
static value_t evaluateShape(MillGameAi_ab::Node *node)
{
return 0;
}
#endif
#ifdef EVALUATE_MOTIF
static value_t MillGameAi_ab::evaluateMotif(Node *node)
static value_t MillGameAi_ab::evaluateMotif(MillGameAi_ab::Node *node)
{
return 0;
}

View File

@ -6,15 +6,15 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
MillGameAi_ab::Node *node, MillGameAi_ab::Node *rootNode,
move_t bestMove)
{
const int MOVE_PRIORITY_TABLE_SIZE = MillGame::N_RINGS * MillGame::N_SEATS;
const int MOVE_PRIORITY_TABLE_SIZE = Board::N_RINGS * Board::N_SEATS;
int pos = 0;
size_t newCapacity = 24;
// 留足余量空间避免多次重新分配,此动作本身也占用 CPU/内存 开销
switch (chessTemp.getStage()) {
case MillGame::GAME_PLACING:
if (chessTemp.getAction() == MillGame::ACTION_CAPTURE) {
if (chessTemp.whosTurn() == MillGame::PLAYER1)
case GAME_PLACING:
if (chessTemp.getAction() == ACTION_CAPTURE) {
if (chessTemp.whosTurn() == PLAYER1)
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_2());
else
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_1());
@ -22,9 +22,9 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
newCapacity = static_cast<size_t>(chessTemp.getPiecesInHandCount_1() + chessTemp.getPiecesInHandCount_2());
}
break;
case MillGame::GAME_MOVING:
if (chessTemp.getAction() == MillGame::ACTION_CAPTURE) {
if (chessTemp.whosTurn() == MillGame::PLAYER1)
case GAME_MOVING:
if (chessTemp.getAction() == ACTION_CAPTURE) {
if (chessTemp.whosTurn() == PLAYER1)
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_2());
else
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_1());
@ -32,7 +32,7 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
newCapacity = 6;
}
break;
case MillGame::GAME_NOTSTARTED:
case GAME_NOTSTARTED:
newCapacity = 24;
break;
default:
@ -48,15 +48,15 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
}
// 对手
MillGame::Player opponent = MillGame::getOpponent(chessTemp.context.turn);
Player opponent = MillGame::getOpponent(chessTemp.context.turn);
// 列出所有合法的下一招
switch (chessTemp.context.action) {
// 对于选子和落子动作
case MillGame::ACTION_CHOOSE:
case MillGame::ACTION_PLACE:
case ACTION_CHOOSE:
case ACTION_PLACE:
// 对于摆子阶段
if (chessTemp.context.stage & (MillGame::GAME_PLACING | MillGame::GAME_NOTSTARTED)) {
if (chessTemp.context.stage & (GAME_PLACING | GAME_NOTSTARTED)) {
for (int i : movePriorityTable) {
pos = i;
@ -64,7 +64,7 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
continue;
}
if (chessTemp.context.stage != MillGame::GAME_NOTSTARTED || node != rootNode) {
if (chessTemp.context.stage != GAME_NOTSTARTED || node != rootNode) {
ai_ab.addNode(node, 0, pos, bestMove, chessTemp.context.turn);
} else {
// 若为先手,则抢占星位
@ -77,7 +77,7 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
}
// 对于移子阶段
if (chessTemp.context.stage & MillGame::GAME_MOVING) {
if (chessTemp.context.stage & GAME_MOVING) {
int newPos, oldPos;
// 尽量走理论上较差的位置的棋子
@ -88,12 +88,12 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
continue;
}
if ((chessTemp.context.turn == MillGame::PLAYER1 &&
if ((chessTemp.context.turn == PLAYER1 &&
(chessTemp.context.nPiecesOnBoard_1 > chessTemp.currentRule.nPiecesAtLeast || !chessTemp.currentRule.allowFlyWhenRemainThreePieces)) ||
(chessTemp.context.turn == MillGame::PLAYER2 &&
(chessTemp.context.turn == PLAYER2 &&
(chessTemp.context.nPiecesOnBoard_2 > chessTemp.currentRule.nPiecesAtLeast || !chessTemp.currentRule.allowFlyWhenRemainThreePieces))) {
// 对于棋盘上还有3个子以上或不允许飞子的情况要求必须在着法表中
for (int moveDirection = MillGame::MOVE_DIRECTION_CLOCKWISE; moveDirection <= MillGame::MOVE_DIRECTION_OUTWARD; moveDirection++) {
for (int moveDirection = MOVE_DIRECTION_CLOCKWISE; moveDirection <= MOVE_DIRECTION_OUTWARD; moveDirection++) {
// 对于原有位置,遍历四个方向的着法,如果棋盘上为空位就加到结点列表中
newPos = moveTable[oldPos][moveDirection];
if (newPos && !chessTemp.board_[newPos]) {
@ -103,7 +103,7 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
}
} else {
// 对于棋盘上还有不到3个字但允许飞子的情况不要求在着法表中是空位就行
for (newPos = MillGame::POS_BEGIN; newPos < MillGame::POS_END; newPos++) {
for (newPos = Board::POS_BEGIN; newPos < Board::POS_END; newPos++) {
if (!chessTemp.board_[newPos]) {
int move = (oldPos << 8) + newPos;
ai_ab.addNode(node, 0, move, bestMove, chessTemp.context.turn);
@ -115,8 +115,8 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
break;
// 对于吃子动作
case MillGame::ACTION_CAPTURE:
if (chessTemp.isAllInMills(opponent)) {
case ACTION_CAPTURE:
if (chessTemp.context.board.isAllInMills(opponent)) {
// 全成三的情况
for (int i = MOVE_PRIORITY_TABLE_SIZE - 1; i >= 0; i--) {
pos = movePriorityTable[i];
@ -131,7 +131,7 @@ void MoveList::generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
for (int i = MOVE_PRIORITY_TABLE_SIZE - 1; i >= 0; i--) {
pos = movePriorityTable[i];
if (chessTemp.board_[pos] & opponent) {
if (chessTemp.getRule()->allowRemoveMill || !chessTemp.isInMills(pos)) {
if (chessTemp.getRule()->allowRemoveMill || !chessTemp.context.board.isInMills(pos)) {
ai_ab.addNode(node, 0, -pos, bestMove, chessTemp.context.turn);
}
}
@ -147,7 +147,7 @@ void MoveList::createMoveTable(MillGame &chess)
{
#ifdef CONST_MOVE_TABLE
#if 1
const int moveTable_obliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
const int moveTable_obliqueLine[Board::N_POINTS][N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},
@ -194,7 +194,7 @@ void MoveList::createMoveTable(MillGame &chess)
/* 39 */ {0, 0, 0, 0},
};
const int moveTable_noObliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
const int moveTable_noObliqueLine[Board::N_POINTS][N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},
@ -241,7 +241,7 @@ void MoveList::createMoveTable(MillGame &chess)
/* 39 */ {0, 0, 0, 0},
};
#else
const int moveTable_obliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
const int moveTable_obliqueLine[Board::N_POINTS][N_MOVE_DIRECTIONS] = {
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
@ -288,7 +288,7 @@ void MoveList::createMoveTable(MillGame &chess)
{0, 0, 0, 0}
};
const int moveTable_noObliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
const int moveTable_noObliqueLine[Board::N_POINTS][N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},

View File

@ -11,9 +11,7 @@ public:
MoveList() = delete;
MoveList &operator=(const MoveList &) = delete;
using move_t = MillGameAi_ab::move_t;
// 生成所有合法的着法并建立子节点
static void generateLegalMoves(MillGameAi_ab &ai_ab, MillGame &chessTemp,
MillGameAi_ab::Node *node, MillGameAi_ab::Node *rootNode,
@ -26,11 +24,11 @@ public:
static void shuffleMovePriorityTable(MillGame &chess);
// 着法表 // TODO: Move to private
inline static int moveTable[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = { {0} };
inline static int moveTable[Board::N_POINTS][N_MOVE_DIRECTIONS] = { {0} };
private:
// 着法顺序表, 后续会被打乱
inline static array<int, MillGame::N_RINGS *MillGame::N_SEATS> movePriorityTable {
inline static array<int, Board::N_RINGS *Board::N_SEATS> movePriorityTable {
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,

View File

@ -28,22 +28,23 @@
#include "evaluate.h"
#include "movegen.h"
#include "hashmap.h"
#include "types.h"
using namespace CTSL;
#ifdef HASH_MAP_ENABLE
static constexpr int hashsize = 0x2000000; // 8-128M:102s, 4-64M:93s 2-32M:91s 1-16M: 冲突
HashMap<MillGame::hash_t, MillGameAi_ab::HashValue> hashmap(hashsize);
HashMap<hash_t, MillGameAi_ab::HashValue> hashmap(hashsize);
#endif // HASH_MAP_ENABLE
#ifdef BOOK_LEARNING
static constexpr int bookHashsize = 0x1000000; // 16M
HashMap<MillGame::hash_t, MillGameAi_ab::HashValue> bookHashMap(bookHashsize);
vector<MillGame::hash_t> openingBook;
HashMap<hash_t, MillGameAi_ab::HashValue> bookHashMap(bookHashsize);
vector<hash_t> openingBook;
#endif // BOOK_LEARNING
#ifdef THREEFOLD_REPETITION
vector<MillGame::hash_t> positions;
vector<hash_t> positions;
#endif
MillGameAi_ab::MillGameAi_ab()
@ -57,11 +58,11 @@ MillGameAi_ab::~MillGameAi_ab()
rootNode = nullptr;
}
MillGameAi_ab::depth_t MillGameAi_ab::changeDepth(depth_t originalDepth)
depth_t MillGameAi_ab::changeDepth(depth_t originalDepth)
{
depth_t newDepth = originalDepth;
if ((chessTemp.context.stage) & (MillGame::GAME_PLACING)) {
if ((chessTemp.context.stage) & (GAME_PLACING)) {
#ifdef GAME_PLACING_DYNAMIC_DEPTH
#ifdef DEAL_WITH_HORIZON_EFFECT
#ifdef HASH_MAP_ENABLE
@ -89,7 +90,7 @@ MillGameAi_ab::depth_t MillGameAi_ab::changeDepth(depth_t originalDepth)
#ifdef GAME_MOVING_FIXED_DEPTH
// 走棋阶段将深度调整
if ((chessTemp.context.stage) & (MillGame::GAME_MOVING)) {
if ((chessTemp.context.stage) & (GAME_MOVING)) {
newDepth = GAME_MOVING_FIXED_DEPTH;
}
#endif /* GAME_MOVING_FIXED_DEPTH */
@ -101,7 +102,7 @@ MillGameAi_ab::depth_t MillGameAi_ab::changeDepth(depth_t originalDepth)
void MillGameAi_ab::buildRoot()
{
rootNode = addNode(nullptr, 0, 0, 0, MillGame::NOBODY);
rootNode = addNode(nullptr, 0, 0, 0, PLAYER_NOBODY);
}
struct MillGameAi_ab::Node *MillGameAi_ab::addNode(
@ -109,7 +110,7 @@ struct MillGameAi_ab::Node *MillGameAi_ab::addNode(
value_t value,
move_t move,
move_t bestMove,
enum MillGame::Player player
enum Player player
)
{
#ifdef MEMORY_POOL
@ -162,15 +163,15 @@ struct MillGameAi_ab::Node *MillGameAi_ab::addNode(
char cmd[32] = { 0 };
if (move < 0) {
chessTemp.pos2rs(-move, r, s);
chessTemp.context.board.pos2rs(-move, r, s);
sprintf(cmd, "-(%1u,%1u)", r, s);
} else if (move & 0x7f00) {
int r1, s1;
chessTemp.pos2rs(move >> 8, r1, s1);
chessTemp.pos2rs(move & 0x00ff, r, s);
chessTemp.context.board.pos2rs(move >> 8, r1, s1);
chessTemp.context.board.pos2rs(move & 0x00ff, r, s);
sprintf(cmd, "(%1u,%1u)->(%1u,%1u)", r1, s1, r, s);
} else {
chessTemp.pos2rs(move & 0x007f, r, s);
chessTemp.context.board.pos2rs(move & 0x007f, r, s);
sprintf(cmd, "(%1u,%1u)", r, s);
}
@ -182,7 +183,7 @@ struct MillGameAi_ab::Node *MillGameAi_ab::addNode(
if (bestMove == 0 || move != bestMove) {
#ifdef MILL_FIRST
// 优先成三
if (chessTemp.getStage() == MillGame::GAME_PLACING && move > 0 && chessTemp.isInMills(move, true)) {
if (chessTemp.getStage() == GAME_PLACING && move > 0 && chessTemp.context.board.isInMills(move, true)) {
parent->children.insert(parent->children.begin(), newNode);
} else {
parent->children.push_back(newNode);
@ -239,7 +240,7 @@ void MillGameAi_ab::sortLegalMoves(Node *node)
{
// 这个函数对效率的影响很大,排序好的话,剪枝较早,节省时间,但不能在此函数耗费太多时间
if (chessTemp.whosTurn() == MillGame::PLAYER1) {
if (chessTemp.whosTurn() == PLAYER1) {
std::stable_sort(node->children.begin(), node->children.end(), nodeGreater);
} else {
std::stable_sort(node->children.begin(), node->children.end(), nodeLess);
@ -302,8 +303,8 @@ void MillGameAi_ab::setChess(const MillGame &chess)
rootNode->pruned = false;
#endif
#ifdef DEBUG_AB_TREE
rootNode->action = MillGame::ACTION_NONE;
rootNode->stage = MillGame::GAME_NONE;
rootNode->action = ACTION_NONE;
rootNode->stage = GAME_NONE;
rootNode->root = rootNode;
#endif
}
@ -321,7 +322,7 @@ int MillGameAi_ab::alphaBetaPruning(depth_t depth)
chrono::steady_clock::time_point timeEnd;
#ifdef BOOK_LEARNING
if (chess_.getStage() == MillGame::GAME_PLACING)
if (chess_.getStage() == GAME_PLACING)
{
if (chess_.context.nPiecesInHand_1 <= 10) {
// 开局库只记录摆棋阶段最后的局面
@ -336,8 +337,8 @@ int MillGameAi_ab::alphaBetaPruning(depth_t depth)
#ifdef THREEFOLD_REPETITION
static int nRepetition = 0;
if (chess_.getStage() == MillGame::GAME_MOVING) {
MillGame::hash_t hash = chess_.getHash();
if (chess_.getStage() == GAME_MOVING) {
hash_t hash = chess_.getHash();
if (std::find(positions.begin(), positions.end(), hash) != positions.end()) {
nRepetition++;
@ -350,7 +351,7 @@ int MillGameAi_ab::alphaBetaPruning(depth_t depth)
}
}
if (chess_.getStage() == MillGame::GAME_PLACING) {
if (chess_.getStage() == GAME_PLACING) {
positions.clear();
}
#endif // THREEFOLD_REPETITION
@ -389,7 +390,7 @@ int MillGameAi_ab::alphaBetaPruning(depth_t depth)
return 0;
}
MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t alpha, value_t beta, Node *node)
value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t alpha, value_t beta, Node *node)
{
// 评价值
value_t value;
@ -412,7 +413,7 @@ MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t al
enum HashType hashf = hashfALPHA;
// 获取哈希值
MillGame::hash_t hash = chessTemp.getHash();
hash_t hash = chessTemp.getHash();
#ifdef DEBUG_AB_TREE
node->hash = hash;
#endif
@ -443,7 +444,7 @@ MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t al
#if 0
// TODO: 有必要针对深度微调 value?
if (chessContext->turn == MillGame::PLAYER1)
if (chessContext->turn == PLAYER1)
node->value += hashValue.depth - depth;
else
node->value -= hashValue.depth - depth;
@ -470,7 +471,7 @@ MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t al
#endif // DEBUG_AB_TREE
// 搜索到叶子节点(决胜局面) // TODO: 对哈希进行特殊处理
if (chessContext->stage == MillGame::GAME_OVER) {
if (chessContext->stage == GAME_OVER) {
// 局面评估
node->value = Evaluation::getValue(chessTemp, chessContext, node);
evaluatedNodeCount++;
@ -501,7 +502,7 @@ MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t al
evaluatedNodeCount++;
// 为争取速胜value 值 +- 深度 (有必要?)
if (chessContext->turn == MillGame::PLAYER1) {
if (chessContext->turn == PLAYER1) {
node->value += depth;
} else {
node->value -= depth;
@ -515,8 +516,8 @@ MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t al
#ifdef BOOK_LEARNING
// 检索开局库
if (chessContext->stage == MillGame::GAME_PLACING && findBookHash(hash, hashValue)) {
if (chessContext->turn == MillGame::PLAYER2) {
if (chessContext->stage == GAME_PLACING && findBookHash(hash, hashValue)) {
if (chessContext->turn == PLAYER2) {
// 是否需对后手扣分 // TODO: 先后手都处理
node->value += 1;
}
@ -536,7 +537,7 @@ MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t al
// 根据演算模型执行 MiniMax 检索,对先手,搜索 Max, 对后手,搜索 Min
minMax = chessTemp.whosTurn() == MillGame::PLAYER1 ? -INF_VALUE : INF_VALUE;
minMax = chessTemp.whosTurn() == PLAYER1 ? -INF_VALUE : INF_VALUE;
for (auto child : node->children) {
// 上下文入栈保存,以便后续撤销着法
@ -567,7 +568,7 @@ MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t al
chessTemp.context = contextStack.top();
contextStack.pop();
if (chessTemp.whosTurn() == MillGame::PLAYER1) {
if (chessTemp.whosTurn() == PLAYER1) {
// 为走棋一方的层, 局面对走棋的一方来说是以 α 为评价
// 取最大值
@ -698,12 +699,12 @@ const char* MillGameAi_ab::bestMove()
// 检查是否必败
MillGame::Player whosTurn = chess_.whosTurn();
Player whosTurn = chess_.whosTurn();
for (auto child : rootNode->children) {
// TODO: 使用常量代替
if (whosTurn == MillGame::PLAYER1 && child->value > -10000 ||
whosTurn == MillGame::PLAYER2 && child->value < 10000) {
if ((whosTurn == PLAYER1 && child->value > -10000) ||
(whosTurn == PLAYER2 && child->value < 10000)) {
isMostLose = false;
break;
}
@ -711,9 +712,9 @@ const char* MillGameAi_ab::bestMove()
// 自动认输
if (isMostLose) {
if (whosTurn == MillGame::PLAYER1) {
if (whosTurn == PLAYER1) {
sprintf(cmdline, "Player1 give up!");
} else if (whosTurn == MillGame::PLAYER2) {
} else if (whosTurn == PLAYER2) {
sprintf(cmdline, "Player2 give up!");
}
@ -758,15 +759,15 @@ const char *MillGameAi_ab::move2string(move_t move)
int r, s;
if (move < 0) {
chessTemp.pos2rs(-move, r, s);
chessTemp.context.board.pos2rs(-move, r, s);
sprintf(cmdline, "-(%1u,%1u)", r, s);
} else if (move & 0x7f00) {
int r1, s1;
chessTemp.pos2rs(move >> 8, r1, s1);
chessTemp.pos2rs(move & 0x00ff, r, s);
chessTemp.context.board.pos2rs(move >> 8, r1, s1);
chessTemp.context.board.pos2rs(move & 0x00ff, r, s);
sprintf(cmdline, "(%1u,%1u)->(%1u,%1u)", r1, s1, r, s);
} else {
chessTemp.pos2rs(move & 0x007f, r, s);
chessTemp.context.board.pos2rs(move & 0x007f, r, s);
sprintf(cmdline, "(%1u,%1u)", r, s);
}
@ -774,7 +775,7 @@ const char *MillGameAi_ab::move2string(move_t move)
}
#ifdef HASH_MAP_ENABLE
MillGameAi_ab::value_t MillGameAi_ab::probeHash(MillGame::hash_t hash,
value_t MillGameAi_ab::probeHash(hash_t hash,
depth_t depth, value_t alpha, value_t beta,
move_t &bestMove, HashType &type)
{
@ -810,7 +811,7 @@ out:
return valUNKNOWN;
}
bool MillGameAi_ab::findHash(MillGame::hash_t hash, HashValue &hashValue)
bool MillGameAi_ab::findHash(hash_t hash, HashValue &hashValue)
{
return hashmap.find(hash, hashValue);
@ -839,7 +840,7 @@ bool MillGameAi_ab::findHash(MillGame::hash_t hash, HashValue &hashValue)
#endif
}
int MillGameAi_ab::recordHash(value_t value, depth_t depth, HashType type, MillGame::hash_t hash, move_t bestMove)
int MillGameAi_ab::recordHash(value_t value, depth_t depth, HashType type, hash_t hash, move_t bestMove)
{
// 同样深度或更深时替换
// 注意: 每走一步以前都必须把散列表中所有的标志项置为 hashfEMPTY
@ -876,12 +877,12 @@ void MillGameAi_ab::clearHashMap()
#ifdef BOOK_LEARNING
bool MillGameAi_ab::findBookHash(MillGame::hash_t hash, HashValue &hashValue)
bool MillGameAi_ab::findBookHash(hash_t hash, HashValue &hashValue)
{
return bookHashMap.find(hash, hashValue);
}
int MillGameAi_ab::recordBookHash(MillGame::hash_t hash, const HashValue &hashValue)
int MillGameAi_ab::recordBookHash(hash_t hash, const HashValue &hashValue)
{
//hashMapMutex.lock();
bookHashMap.insert(hash, hashValue);
@ -900,7 +901,7 @@ void MillGameAi_ab::clearBookHashMap()
void MillGameAi_ab::recordOpeningBookToHashMap()
{
HashValue hashValue;
MillGame::hash_t hash = 0;
hash_t hash = 0;
for (auto iter = openingBook.begin(); iter != openingBook.end(); ++iter)
{

View File

@ -52,11 +52,6 @@ using namespace CTSL;
class MillGameAi_ab
{
public:
// 定义类型
using depth_t = uint8_t;
using value_t = int16_t;
using move_t = MillGame::move_t;
// 定义一个节点结构体
struct Node
{
@ -65,7 +60,7 @@ public:
struct Node* parent {}; // 父节点
move_t move {}; // 着法的命令行指令,图上标示为节点前的连线
value_t value {}; // 节点的值
enum MillGame::Player player; // 此着是谁下的 (目前仅调试用)
enum Player player; // 此着是谁下的 (目前仅调试用)
#ifdef SORT_CONSIDER_PRUNED
bool pruned {}; // 是否在此处剪枝
#endif
@ -80,8 +75,8 @@ public:
bool isTimeout; // 是否遍历到此结点时因为超时而被迫退出
bool isLeaf; // 是否为叶子结点, 叶子结点是决胜局面
bool visited; // 是否在遍历时访问过
MillGame::GameStage stage; // 摆棋阶段还是走棋阶段
MillGame::Action action; // 动作状态
GameStage stage; // 摆棋阶段还是走棋阶段
Action action; // 动作状态
int nPiecesOnBoardDiff; // 场上棋子个数和对手的差值
int nPiecesInHandDiff; // 手中的棋子个数和对手的差值
int nPiecesNeedRemove; // 手中有多少可去的子,如对手有可去的子则为负数
@ -91,7 +86,7 @@ public:
bool isHash; // 是否从 Hash 读取
#endif /* HASH_MAP_ENABLE */
#if ((defined HASH_MAP_ENABLE) || (defined BOOK_LEARNING) || (defined THREEFOLD_REPETITION))
MillGame::hash_t hash; // 哈希值
hash_t hash; // 哈希值
#endif
#endif /* DEBUG_AB_TREE */
};
@ -148,8 +143,8 @@ public:
static bool nodeGreater(const Node *first, const Node *second);
#ifdef BOOK_LEARNING
bool findBookHash(MillGame::hash_t hash, HashValue &hashValue);
static int recordBookHash(MillGame::hash_t hash, const HashValue &hashValue);
bool findBookHash(hash_t hash, HashValue &hashValue);
static int recordBookHash(hash_t hash, const HashValue &hashValue);
void clearBookHashMap();
static void recordOpeningBookToHashMap();
static void recordOpeningBookHashMapToFile();
@ -160,7 +155,7 @@ public: /* TODO: Move to private or protected */
// 增加新节点
struct Node *addNode(Node *parent, value_t value,
move_t move, move_t bestMove,
enum MillGame::Player player);
enum Player player);
// 定义极大值
static const value_t INF_VALUE = 0x1 << 14;
@ -202,7 +197,7 @@ protected:
#endif /* EVALUATE_ENABLE */
// Alpha-Beta剪枝算法
MillGameAi_ab::value_t alphaBetaPruning(depth_t depth, value_t alpha, value_t beta, Node *node);
value_t alphaBetaPruning(depth_t depth, value_t alpha, value_t beta, Node *node);
// 返回着法的命令行
const char *move2string(move_t move);
@ -212,11 +207,11 @@ protected:
#ifdef HASH_MAP_ENABLE
// 查找哈希表
bool findHash(MillGame::hash_t hash, HashValue &hashValue);
value_t probeHash(MillGame::hash_t hash, depth_t depth, value_t alpha, value_t beta, move_t &bestMove, HashType &type);
bool findHash(hash_t hash, HashValue &hashValue);
value_t probeHash(hash_t hash, depth_t depth, value_t alpha, value_t beta, move_t &bestMove, HashType &type);
// 插入哈希表
int recordHash(value_t value, depth_t depth, HashType type, MillGame::hash_t hash, move_t bestMove);
int recordHash(value_t value, depth_t depth, HashType type, hash_t hash, move_t bestMove);
#endif // HASH_MAP_ENABLE
private:
@ -226,7 +221,7 @@ private:
// 演算用的模型
MillGame chessTemp;
MillGame::ChessContext *chessContext {};
ChessContext *chessContext {};
// hash 计算时,各种转换用的模型
MillGame chessTempShift;
@ -256,7 +251,7 @@ private:
//#ifdef MEMORY_POOL
// StackAlloc<MillGame::ChessContext, MemoryPool<MillGame::ChessContext> > contextStack;
//#else
stack<MillGame::ChessContext> contextStack;
stack<ChessContext> contextStack;
//#endif
// 标识,用于跳出剪枝算法,立即返回
@ -268,11 +263,11 @@ private:
};
#ifdef HASH_MAP_ENABLE
extern HashMap<MillGame::hash_t, MillGameAi_ab::HashValue> hashmap;
extern HashMap<hash_t, MillGameAi_ab::HashValue> hashmap;
#endif /* #ifdef HASH_MAP_ENABLE */
#ifdef THREEFOLD_REPETITION
extern vector<MillGame::hash_t> positions;
extern vector<hash_t> positions;
#endif
#endif

View File

@ -22,7 +22,7 @@
#include "millgame.h"
static const MillGame::hash_t zobrist0[MillGame::N_POINTS][MillGame::POINT_TYPE_COUNT] = {
static const hash_t zobrist0[Board::N_POINTS][POINT_TYPE_COUNT] = {
#ifdef HASH_MAP_CUTDOWN
{0x4E421A00, 0x3962FF00, 0x6DB6EE00, 0x219AE100},
{0x1F3DE200, 0xD9AACB00, 0xD5173300, 0xD3F9EA00},

View File

@ -123,7 +123,7 @@ namespace CTSL //Concurrent Thread Safe Library
#ifdef DISABLE_HASHBUCKET
QFile file(filename);
file.open(QIODevice::WriteOnly);
file.write(static_cast<char *>(hashTable), sizeof(HashNode<K, V>) * hashSize);
file.write((char *)(hashTable), sizeof(HashNode<K, V>) * hashSize);
file.close();
#endif
}
@ -134,7 +134,7 @@ namespace CTSL //Concurrent Thread Safe Library
#ifdef DISABLE_HASHBUCKET
QFile file(filename);
file.open(QIODevice::ReadOnly);
file.read(static_cast<char *>(hashTable), sizeof(HashNode<K, V>) * hashSize);
file.read((char *)(hashTable), sizeof(HashNode<K, V>) * hashSize);
file.close();
#endif
}

View File

@ -76,7 +76,7 @@ void AiThread::setAi(const MillGame &chess)
mutex.unlock();
}
void AiThread::setAi(const MillGame &chess, MillGameAi_ab::depth_t depth, int time)
void AiThread::setAi(const MillGame &chess, depth_t depth, int time)
{
mutex.lock();
this->chess_ = &chess;

View File

@ -55,7 +55,7 @@ protected:
public:
// AI设置
void setAi(const MillGame &chess);
void setAi(const MillGame &chess, MillGameAi_ab::depth_t depth, int time);
void setAi(const MillGame &chess, depth_t depth, int time);
Server *getServer()
{
@ -68,7 +68,7 @@ public:
}
// 深度和限时
void getDepthTime(MillGameAi_ab::depth_t &depth, int &time)
void getDepthTime(depth_t &depth, int &time)
{
depth = aiDepth;
time = aiTime;
@ -113,7 +113,7 @@ private:
MillGameAi_ab ai_ab;
// AI的层数
MillGameAi_ab::depth_t aiDepth;
depth_t aiDepth;
// AI的限时
int aiTime;

992
src/game/board.cpp Normal file
View File

@ -0,0 +1,992 @@
/*****************************************************************************
* Copyright (C) 2018-2019 MillGame authors
*
* Authors: liuweilhy <liuweilhy@163.com>
* Calcitem <calcitem@outlook.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include "board.h"
#include "movegen.h"
// 名义上是个数组,实际上相当于一个判断是否在棋盘上的函数
const int Board::onBoard[N_POINTS] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 成三表
int Board::millTable[N_POINTS][N_DIRECTIONS][N_RINGS - 1] = { {{0}} };
Board::Board()
{
}
Board::~Board()
{
if (!millList.empty()) {
millList.clear();
}
}
Board &Board::operator= (const Board &other)
{
if (this == &other)
return *this;
memcpy(this->board_, other.board_, sizeof(this->board_));
if (!millList.empty()) {
millList.clear();
}
if (!other.millList.empty()) {
for (auto i : other.millList) {
millList.push_back(i);
}
}
return *this;
}
void Board::createMillTable(const Rule &currentRule)
{
#ifdef CONST_MILL_TABLE
const int millTable_noObliqueLine[Board::N_POINTS][Board::N_DIRECTIONS][2] = {
/* 0 */ {{0, 0}, {0, 0}, {0, 0}},
/* 1 */ {{0, 0}, {0, 0}, {0, 0}},
/* 2 */ {{0, 0}, {0, 0}, {0, 0}},
/* 3 */ {{0, 0}, {0, 0}, {0, 0}},
/* 4 */ {{0, 0}, {0, 0}, {0, 0}},
/* 5 */ {{0, 0}, {0, 0}, {0, 0}},
/* 6 */ {{0, 0}, {0, 0}, {0, 0}},
/* 7 */ {{0, 0}, {0, 0}, {0, 0}},
/* 8 */ {{16, 24}, {9, 15}, {0, 0}},
/* 9 */ {{0, 0}, {15, 8}, {10, 11}},
/* 10 */ {{18, 26}, {11, 9}, {0, 0}},
/* 11 */ {{0, 0}, {9, 10}, {12, 13}},
/* 12 */ {{20, 28}, {13, 11}, {0, 0}},
/* 13 */ {{0, 0}, {11, 12}, {14, 15}},
/* 14 */ {{22, 30}, {15, 13}, {0, 0}},
/* 15 */ {{0, 0}, {13, 14}, {8, 9}},
/* 16 */ {{8, 24}, {17, 23}, {0, 0}},
/* 17 */ {{0, 0}, {23, 16}, {18, 19}},
/* 18 */ {{10, 26}, {19, 17}, {0, 0}},
/* 19 */ {{0, 0}, {17, 18}, {20, 21}},
/* 20 */ {{12, 28}, {21, 19}, {0, 0}},
/* 21 */ {{0, 0}, {19, 20}, {22, 23}},
/* 22 */ {{14, 30}, {23, 21}, {0, 0}},
/* 23 */ {{0, 0}, {21, 22}, {16, 17}},
/* 24 */ {{8, 16}, {25, 31}, {0, 0}},
/* 25 */ {{0, 0}, {31, 24}, {26, 27}},
/* 26 */ {{10, 18}, {27, 25}, {0, 0}},
/* 27 */ {{0, 0}, {25, 26}, {28, 29}},
/* 28 */ {{12, 20}, {29, 27}, {0, 0}},
/* 29 */ {{0, 0}, {27, 28}, {30, 31}},
/* 30 */ {{14, 22}, {31, 29}, {0, 0}},
/* 31 */ {{0, 0}, {29, 30}, {24, 25}},
/* 32 */ {{0, 0}, {0, 0}, {0, 0}},
/* 33 */ {{0, 0}, {0, 0}, {0, 0}},
/* 34 */ {{0, 0}, {0, 0}, {0, 0}},
/* 35 */ {{0, 0}, {0, 0}, {0, 0}},
/* 36 */ {{0, 0}, {0, 0}, {0, 0}},
/* 37 */ {{0, 0}, {0, 0}, {0, 0}},
/* 38 */ {{0, 0}, {0, 0}, {0, 0}},
/* 39 */ {{0, 0}, {0, 0}, {0, 0}}
};
const int millTable_hasObliqueLines[Board::N_POINTS][Board::N_DIRECTIONS][2] = {
/* 0 */ {{0, 0}, {0, 0}, {0, 0}},
/* 1 */ {{0, 0}, {0, 0}, {0, 0}},
/* 2 */ {{0, 0}, {0, 0}, {0, 0}},
/* 3 */ {{0, 0}, {0, 0}, {0, 0}},
/* 4 */ {{0, 0}, {0, 0}, {0, 0}},
/* 5 */ {{0, 0}, {0, 0}, {0, 0}},
/* 6 */ {{0, 0}, {0, 0}, {0, 0}},
/* 7 */ {{0, 0}, {0, 0}, {0, 0}},
/* 8 */ {{16, 24}, {9, 15}, {0, 0}},
/* 9 */ {{17, 25}, {15, 8}, {10, 11}},
/* 10 */ {{18, 26}, {11, 9}, {0, 0}},
/* 11 */ {{19, 27}, {9, 10}, {12, 13}},
/* 12 */ {{20, 28}, {13, 11}, {0, 0}},
/* 13 */ {{21, 29}, {11, 12}, {14, 15}},
/* 14 */ {{22, 30}, {15, 13}, {0, 0}},
/* 15 */ {{23, 31}, {13, 14}, {8, 9}},
/* 16 */ {{8, 24}, {17, 23}, {0, 0}},
/* 17 */ {{9, 25}, {23, 16}, {18, 19}},
/* 18 */ {{10, 26}, {19, 17}, {0, 0}},
/* 19 */ {{11, 27}, {17, 18}, {20, 21}},
/* 20 */ {{12, 28}, {21, 19}, {0, 0}},
/* 21 */ {{13, 29}, {19, 20}, {22, 23}},
/* 22 */ {{14, 30}, {23, 21}, {0, 0}},
/* 23 */ {{15, 31}, {21, 22}, {16, 17}},
/* 24 */ {{8, 16}, {25, 31}, {0, 0}},
/* 25 */ {{9, 17}, {31, 24}, {26, 27}},
/* 26 */ {{10, 18}, {27, 25}, {0, 0}},
/* 27 */ {{11, 19}, {25, 26}, {28, 29}},
/* 28 */ {{12, 20}, {29, 27}, {0, 0}},
/* 29 */ {{13, 21}, {27, 28}, {30, 31}},
/* 30 */ {{14, 22}, {31, 29}, {0, 0}},
/* 31 */ {{15, 23}, {29, 30}, {24, 25}},
/* 32 */ {{0, 0}, {0, 0}, {0, 0}},
/* 33 */ {{0, 0}, {0, 0}, {0, 0}},
/* 34 */ {{0, 0}, {0, 0}, {0, 0}},
/* 35 */ {{0, 0}, {0, 0}, {0, 0}},
/* 36 */ {{0, 0}, {0, 0}, {0, 0}},
/* 37 */ {{0, 0}, {0, 0}, {0, 0}},
/* 38 */ {{0, 0}, {0, 0}, {0, 0}},
/* 39 */ {{0, 0}, {0, 0}, {0, 0}}
};
if (currentRule.hasObliqueLines) {
memcpy(millTable, millTable_hasObliqueLines, sizeof(millTable));
} else {
memcpy(millTable, millTable_noObliqueLine, sizeof(millTable));
}
#else /* CONST_MILL_TABLE */
for (int i = 0; i < N_SEATS; i++) {
// 内外方向的“成三”
// 如果是0、2、4、6位偶数位或是有斜线
if (!(i & 1) || this->currentRule.hasObliqueLines) {
millTable[1 * N_SEATS + i][0][0] = 2 * N_SEATS + i;
millTable[1 * N_SEATS + i][0][1] = 3 * N_SEATS + i;
millTable[2 * N_SEATS + i][0][0] = 1 * N_SEATS + i;
millTable[2 * N_SEATS + i][0][1] = 3 * N_SEATS + i;
millTable[3 * N_SEATS + i][0][0] = 1 * N_SEATS + i;
millTable[3 * N_SEATS + i][0][1] = 2 * N_SEATS + i;
}
// 对于无斜线情况下的1、3、5、7位奇数位
else {
// 置空该组“成三”
millTable[1 * N_SEATS + i][0][0] = 0;
millTable[1 * N_SEATS + i][0][1] = 0;
millTable[2 * N_SEATS + i][0][0] = 0;
millTable[2 * N_SEATS + i][0][1] = 0;
millTable[3 * N_SEATS + i][0][0] = 0;
millTable[3 * N_SEATS + i][0][1] = 0;
}
// 当前圈上的“成三”
// 如果是0、2、4、6位
if (!(i & 1)) {
millTable[1 * N_SEATS + i][1][0] = 1 * N_SEATS + (i + 1) % N_SEATS;
millTable[1 * N_SEATS + i][1][1] = 1 * N_SEATS + (i + N_SEATS - 1) % N_SEATS;
millTable[2 * N_SEATS + i][1][0] = 2 * N_SEATS + (i + 1) % N_SEATS;
millTable[2 * N_SEATS + i][1][1] = 2 * N_SEATS + (i + N_SEATS - 1) % N_SEATS;
millTable[3 * N_SEATS + i][1][0] = 3 * N_SEATS + (i + 1) % N_SEATS;
millTable[3 * N_SEATS + i][1][1] = 3 * N_SEATS + (i + N_SEATS - 1) % N_SEATS;
// 置空另一组“成三”
millTable[1 * N_SEATS + i][2][0] = 0;
millTable[1 * N_SEATS + i][2][1] = 0;
millTable[2 * N_SEATS + i][2][0] = 0;
millTable[2 * N_SEATS + i][2][1] = 0;
millTable[3 * N_SEATS + i][2][0] = 0;
millTable[3 * N_SEATS + i][2][1] = 0;
}
// 对于1、3、5、7位奇数位
else {
// 当前圈上逆时针的“成三”
millTable[1 * N_SEATS + i][1][0] = 1 * N_SEATS + (i + N_SEATS - 2) % N_SEATS;
millTable[1 * N_SEATS + i][1][1] = 1 * N_SEATS + (i + N_SEATS - 1) % N_SEATS;
millTable[2 * N_SEATS + i][1][0] = 2 * N_SEATS + (i + N_SEATS - 2) % N_SEATS;
millTable[2 * N_SEATS + i][1][1] = 2 * N_SEATS + (i + N_SEATS - 1) % N_SEATS;
millTable[3 * N_SEATS + i][1][0] = 3 * N_SEATS + (i + N_SEATS - 2) % N_SEATS;
millTable[3 * N_SEATS + i][1][1] = 3 * N_SEATS + (i + N_SEATS - 1) % N_SEATS;
// 当前圈上顺时针的“成三”
millTable[1 * N_SEATS + i][2][0] = 1 * N_SEATS + (i + 1) % N_SEATS;
millTable[1 * N_SEATS + i][2][1] = 1 * N_SEATS + (i + 2) % N_SEATS;
millTable[2 * N_SEATS + i][2][0] = 2 * N_SEATS + (i + 1) % N_SEATS;
millTable[2 * N_SEATS + i][2][1] = 2 * N_SEATS + (i + 2) % N_SEATS;
millTable[3 * N_SEATS + i][2][0] = 3 * N_SEATS + (i + 1) % N_SEATS;
millTable[3 * N_SEATS + i][2][1] = 3 * N_SEATS + (i + 2) % N_SEATS;
}
}
#endif /* CONST_MILL_TABLE */
#if 0
for (int i = 0; i < N_POINTS; i++) {
printf("/* %d */ {", i);
for (int j = 0; j < N_DIRECTIONS; j++) {
printf("{");
for (int k = 0; k < 2; k++) {
if (k == 0) {
printf("%d, ", millTable[i][j][k]);
} else {
printf("%d", millTable[i][j][k]);
}
}
if (j == 2)
printf("}");
else
printf("}, ");
}
printf("},\n");
}
printf("======== millTable End =========\n");
#endif
}
void Board::pos2rs(const int pos, int &r, int &s)
{
//r = pos / N_SEATS;
//s = pos % N_SEATS + 1;
r = pos >> 3;
s = (pos & 0x07) + 1;
}
int Board::rs2Pos(int r, int s)
{
if (r < 1 || r > N_RINGS || s < 1 || s > N_SEATS)
return 0;
return r * N_SEATS + s - 1;
}
int Board::isInMills(int pos, bool test)
{
int n = 0;
int pos1, pos2;
int m = test ? INT32_MAX : board_[pos] & '\x30';
for (int i = 0; i < 3; i++) {
pos1 = millTable[pos][i][0];
pos2 = millTable[pos][i][1];
if (m & board_[pos1] & board_[pos2])
n++;
}
return n;
}
int Board::addMills(const Rule &currentRule, int pos)
{
// 成三用一个64位整数了规则如下
// 0x 00 00 00 00 00 00 00 00
// unused unused piece1 pos1 piece2 pos2 piece3 pos3
// piece1、piece2、piece3按照序号从小到大顺序排放
uint64_t mill = 0;
int n = 0;
int p[3], min, temp;
char m = board_[pos] & '\x30';
for (int i = 0; i < 3; i++) {
p[0] = pos;
p[1] = millTable[pos][i][0];
p[2] = millTable[pos][i][1];
// 如果没有成三
if (!(m & board_[p[1]] & board_[p[2]])) {
continue;
}
// 如果成三
// 排序
for (int j = 0; j < 2; j++) {
min = j;
for (int k = j + 1; k < 3; k++) {
if (p[min] > p[k])
min = k;
}
if (min == j) {
continue;
}
temp = p[min];
p[min] = p[j];
p[j] = temp;
}
// 成三
mill = (static_cast<uint64_t>(board_[p[0]]) << 40)
+ (static_cast<uint64_t>(p[0]) << 32)
+ (static_cast<uint64_t>(board_[p[1]]) << 24)
+ (static_cast<uint64_t>(p[1]) << 16)
+ (static_cast<uint64_t>(board_[p[2]]) << 8)
+ static_cast<uint64_t>(p[2]);
// 如果允许相同三连反复去子
if (currentRule.allowRemovePiecesRepeatedly) {
n++;
continue;
}
// 如果不允许相同三连反复去子
// 迭代器
auto iter = millList.begin();
// 遍历
for (iter = millList.begin(); iter != millList.end(); iter++) {
if (mill == *iter)
break;
}
// 如果没找到历史项
if (iter == millList.end()) {
n++;
millList.push_back(mill);
}
}
return n;
}
bool Board::isAllInMills(char ch)
{
for (int i = POS_BEGIN; i < POS_END; i++) {
if (board_[i] & ch) {
if (!isInMills(i)) {
return false;
}
}
}
return true;
}
bool Board::isAllInMills(enum Player player)
{
char ch = 0x00;
if (player == PLAYER1)
ch = 0x10;
else if (player == PLAYER2)
ch = 0x20;
else
return true;
return isAllInMills(ch);
}
// 判断玩家的棋子周围有几个空位
int Board::getSurroundedEmptyPosCount(enum Player turn, const Rule &currentRule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, int pos, bool includeFobidden)
{
int count = 0;
if ((turn == PLAYER1 &&
(nPiecesOnBoard_1 > currentRule.nPiecesAtLeast || !currentRule.allowFlyWhenRemainThreePieces)) ||
(turn == PLAYER2 &&
(nPiecesOnBoard_2 > currentRule.nPiecesAtLeast || !currentRule.allowFlyWhenRemainThreePieces))) {
int d, movePos;
for (d = 0; d < N_MOVE_DIRECTIONS; d++) {
movePos = MoveList::moveTable[pos][d];
if (movePos) {
if (board_[movePos] == 0x00 ||
(includeFobidden && board_[movePos] == 0x0F)) {
count++;
}
}
}
}
return count;
}
// 判断玩家的棋子是否被围
bool Board::isSurrounded(enum Player turn, const Rule &currentRule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, int pos)
{
// 判断pos处的棋子是否被“闷”
if ((turn == PLAYER1 &&
(nPiecesOnBoard_1 > currentRule.nPiecesAtLeast || !currentRule.allowFlyWhenRemainThreePieces)) ||
(turn == PLAYER2 &&
(nPiecesOnBoard_2 > currentRule.nPiecesAtLeast || !currentRule.allowFlyWhenRemainThreePieces))) {
int i, movePos;
for (i = 0; i < 4; i++) {
movePos = MoveList::moveTable[pos][i];
if (movePos && !board_[movePos])
break;
}
// 被围住
if (i == 4)
return true;
}
// 没被围住
return false;
}
bool Board::isAllSurrounded(enum Player turn, const Rule &currentRule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, char ch)
{
// 如果摆满
if (nPiecesOnBoard_1 + nPiecesOnBoard_2 >= N_SEATS * N_RINGS)
return true;
// 判断是否可以飞子
if ((turn == PLAYER1 &&
(nPiecesOnBoard_1 <= currentRule.nPiecesAtLeast && currentRule.allowFlyWhenRemainThreePieces)) ||
(turn == PLAYER2 &&
(nPiecesOnBoard_2 <= currentRule.nPiecesAtLeast && currentRule.allowFlyWhenRemainThreePieces))) {
return false;
}
// 查询整个棋盘
int movePos;
for (int i = 1; i < N_SEATS * (N_RINGS + 1); i++) {
if (!(ch & board_[i])) {
continue;
}
for (int d = 0; d < N_MOVE_DIRECTIONS; d++) {
movePos = MoveList::moveTable[i][d];
if (movePos && !board_[movePos])
return false;
}
}
return true;
}
// 判断玩家的棋子是否全部被围
bool Board::isAllSurrounded(enum Player turn, const Rule &currentRule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, enum Player ply)
{
char t = '\x30';
if (ply == PLAYER1)
t &= '\x10';
else if (ply == PLAYER2)
t &= '\x20';
return isAllSurrounded(turn, currentRule, nPiecesOnBoard_1, nPiecesOnBoard_2, t);
}
enum Player Board::getWhosPiece(int r, int s)
{
int pos = rs2Pos(r, s);
if (board_[pos] & '\x10')
return PLAYER1;
if (board_[pos] & '\x20')
return PLAYER2;
return PLAYER_NOBODY;
}
// Unused
bool Board::getPieceRS(const Player &player, const int &number, int &r, int &s, struct Rule &currentRule)
{
int piece;
if (player == PLAYER1) {
piece = 0x10;
} else if (player == PLAYER2) {
piece = 0x20;
} else {
return false;
}
if (number > 0 && number <= currentRule.nTotalPiecesEachSide)
piece &= number;
else
return false;
for (int i = POS_BEGIN; i < POS_END; i++) {
if (board_[i] == piece) {
pos2rs(i, r, s);
return true;
}
}
return false;
}
// 获取当前棋子
bool Board::getCurrentPiece(Player &player, int &number, int currentPos)
{
if (!onBoard[currentPos])
return false;
int p = board_[currentPos];
if (p & 0x10) {
player = PLAYER1;
number = p - 0x10;
} else if (p & 0x20) {
player = PLAYER2;
number = p - 0x20;
} else {
return false;
}
return true;
}
void Board::mirror(list <string> &cmdlist, char* cmdline, int32_t move_, struct Rule &currentRule, int currentPos, bool cmdChange /*= true*/)
{
int ch;
int r, s;
int i;
for (r = 1; r <= N_RINGS; r++) {
for (s = 1; s < N_SEATS / 2; s++) {
ch = board_[r * N_SEATS + s];
board_[r * N_SEATS + s] = board_[(r + 1) * N_SEATS - s];
//updateHash(i * N_SEATS + j);
board_[(r + 1) * N_SEATS - s] = ch;
//updateHash((i + 1) * N_SEATS - j);
}
}
uint64_t llp[3] = { 0 };
if (move_ < 0) {
r = (-move_) / N_SEATS;
s = (-move_) % N_SEATS;
s = (N_SEATS - s) % N_SEATS;
move_ = -(r * N_SEATS + s);
} else {
llp[0] = static_cast<uint64_t>(move_ >> 8);
llp[1] = move_ & 0x00ff;
for (i = 0; i < 2; i++) {
r = static_cast<int>(llp[i]) / N_SEATS;
s = static_cast<int>(llp[i]) % N_SEATS;
s = (N_SEATS - s) % N_SEATS;
llp[i] = (static_cast<uint64_t>(r) * N_SEATS + s);
}
move_ = static_cast<int16_t>(((llp[0] << 8) | llp[1]));
}
if (currentPos != 0) {
r = currentPos / N_SEATS;
s = currentPos % N_SEATS;
s = (N_SEATS - s) % N_SEATS;
currentPos = r * N_SEATS + s;
}
if (currentRule.allowRemovePiecesRepeatedly) {
for (auto &mill : millList) {
llp[0] = (mill & 0x000000ff00000000) >> 32;
llp[1] = (mill & 0x0000000000ff0000) >> 16;
llp[2] = (mill & 0x00000000000000ff);
for (i = 0; i < 3; i++) {
r = static_cast<int>(llp[i]) / N_SEATS;
s = static_cast<int>(llp[i]) % N_SEATS;
s = (N_SEATS - s) % N_SEATS;
llp[i] = static_cast<uint64_t>(r * N_SEATS + s);
}
mill &= 0xffffff00ff00ff00;
mill |= (llp[0] << 32) | (llp[1] << 16) | llp[2];
}
}
// 命令行解析
if (cmdChange) {
int r1, s1, r2, s2;
int args = 0;
int mm = 0, ss = 0;
args = sscanf(cmdline, "(%1u,%1u)->(%1u,%1u) %2u:%2u", &r1, &s1, &r2, &s2, &mm, &ss);
if (args >= 4) {
s1 = (N_SEATS - s1 + 1) % N_SEATS;
s2 = (N_SEATS - s2 + 1) % N_SEATS;
cmdline[3] = '1' + static_cast<char>(s1);
cmdline[10] = '1' + static_cast<char>(s2);
} else {
args = sscanf(cmdline, "-(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
s1 = (N_SEATS - s1 + 1) % N_SEATS;
cmdline[4] = '1' + static_cast<char>(s1);
} else {
args = sscanf(cmdline, "(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
s1 = (N_SEATS - s1 + 1) % N_SEATS;
cmdline[3] = '1' + static_cast<char>(s1);
}
}
}
for (auto &iter : cmdlist) {
args = sscanf(iter.c_str(), "(%1u,%1u)->(%1u,%1u) %2u:%2u", &r1, &s1, &r2, &s2, &mm, &ss);
if (args >= 4) {
s1 = (N_SEATS - s1 + 1) % N_SEATS;
s2 = (N_SEATS - s2 + 1) % N_SEATS;
iter[3] = '1' + static_cast<char>(s1);
iter[10] = '1' + static_cast<char>(s2);
} else {
args = sscanf(iter.c_str(), "-(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
s1 = (N_SEATS - s1 + 1) % N_SEATS;
iter[4] = '1' + static_cast<char>(s1);
} else {
args = sscanf(iter.c_str(), "(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
s1 = (N_SEATS - s1 + 1) % N_SEATS;
iter[3] = '1' + static_cast<char>(s1);
}
}
}
}
}
}
void Board::turn(list <string> &cmdlist, char *cmdline, int32_t move_, const Rule &currentRule, int currentPos, bool cmdChange /*= true*/)
{
int ch;
int r, s;
int i;
for (s = 0; s < N_SEATS; s++) {
ch = board_[N_SEATS + s];
board_[N_SEATS + s] = board_[N_SEATS * N_RINGS + s];
//updateHash(N_SEATS + s);
board_[N_SEATS * N_RINGS + s] = ch;
//updateHash(N_SEATS * N_RINGS + s);
}
uint64_t llp[3] = { 0 };
if (move_ < 0) {
r = (-move_) / N_SEATS;
s = (-move_) % N_SEATS;
if (r == 1)
r = N_RINGS;
else if (r == N_RINGS)
r = 1;
move_ = -(r * N_SEATS + s);
} else {
llp[0] = static_cast<uint64_t>(move_ >> 8);
llp[1] = move_ & 0x00ff;
for (i = 0; i < 2; i++) {
r = static_cast<int>(llp[i]) / N_SEATS;
s = static_cast<int>(llp[i]) % N_SEATS;
if (r == 1)
r = N_RINGS;
else if (r == N_RINGS)
r = 1;
llp[i] = static_cast<uint64_t>(r * N_SEATS + s);
}
move_ = static_cast<int16_t>(((llp[0] << 8) | llp[1]));
}
if (currentPos != 0) {
r = currentPos / N_SEATS;
s = currentPos % N_SEATS;
if (r == 1)
r = N_RINGS;
else if (r == N_RINGS)
r = 1;
currentPos = r * N_SEATS + s;
}
if (currentRule.allowRemovePiecesRepeatedly) {
for (auto &mill : millList) {
llp[0] = (mill & 0x000000ff00000000) >> 32;
llp[1] = (mill & 0x0000000000ff0000) >> 16;
llp[2] = (mill & 0x00000000000000ff);
for (i = 0; i < 3; i++) {
r = static_cast<int>(llp[i]) / N_SEATS;
s = static_cast<int>(llp[i]) % N_SEATS;
if (r == 1)
r = N_RINGS;
else if (r == N_RINGS)
r = 1;
llp[i] = static_cast<uint64_t>(r * N_SEATS + s);
}
mill &= 0xffffff00ff00ff00;
mill |= (llp[0] << 32) | (llp[1] << 16) | llp[2];
}
}
// 命令行解析
if (cmdChange) {
int r1, s1, r2, s2;
int args = 0;
int mm = 0, ss = 0;
args = sscanf(cmdline, "(%1u,%1u)->(%1u,%1u) %2u:%2u",
&r1, &s1, &r2, &s2, &mm, &ss);
if (args >= 4) {
if (r1 == 1)
r1 = N_RINGS;
else if (r1 == N_RINGS)
r1 = 1;
if (r2 == 1)
r2 = N_RINGS;
else if (r2 == N_RINGS)
r2 = 1;
cmdline[1] = '0' + static_cast<char>(r1);
cmdline[8] = '0' + static_cast<char>(r2);
} else {
args = sscanf(cmdline, "-(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
if (r1 == 1)
r1 = N_RINGS;
else if (r1 == N_RINGS)
r1 = 1;
cmdline[2] = '0' + static_cast<char>(r1);
} else {
args = sscanf(cmdline, "(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
if (r1 == 1)
r1 = N_RINGS;
else if (r1 == N_RINGS)
r1 = 1;
cmdline[1] = '0' + static_cast<char>(r1);
}
}
}
for (auto &iter : cmdlist) {
args = sscanf(iter.c_str(),
"(%1u,%1u)->(%1u,%1u) %2u:%2u",
&r1, &s1, &r2, &s2, &mm, &ss);
if (args >= 4) {
if (r1 == 1)
r1 = N_RINGS;
else if (r1 == N_RINGS)
r1 = 1;
if (r2 == 1)
r2 = N_RINGS;
else if (r2 == N_RINGS)
r2 = 1;
iter[1] = '0' + static_cast<char>(r1);
iter[8] = '0' + static_cast<char>(r2);
} else {
args = sscanf(iter.c_str(), "-(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
if (r1 == 1)
r1 = N_RINGS;
else if (r1 == N_RINGS)
r1 = 1;
iter[2] = '0' + static_cast<char>(r1);
} else {
args = sscanf(iter.c_str(), "(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
if (r1 == 1)
r1 = N_RINGS;
else if (r1 == N_RINGS)
r1 = 1;
iter[1] = '0' + static_cast<char>(r1);
}
}
}
}
}
}
void Board::rotate(int degrees, list <string> &cmdlist, char *cmdline, int32_t move_, const Rule &currentRule, int currentPos, bool cmdChange /*= true*/)
{
// 将degrees转化为0~359之间的数
degrees = degrees % 360;
if (degrees < 0)
degrees += 360;
if (degrees == 0 || degrees % 90)
return;
degrees /= 45;
int ch1, ch2;
int r, s;
int i;
if (degrees == 2) {
for (r = 1; r <= N_RINGS; r++) {
ch1 = board_[r * N_SEATS];
ch2 = board_[r * N_SEATS + 1];
for (s = 0; s < N_SEATS - 2; s++) {
board_[r * N_SEATS + s] = board_[r * N_SEATS + s + 2];
}
board_[r * N_SEATS + 6] = ch1;
//updateHash(i * N_SEATS + 6);
board_[r * N_SEATS + 7] = ch2;
//updateHash(i * N_SEATS + 7);
}
} else if (degrees == 6) {
for (r = 1; r <= N_RINGS; r++) {
ch1 = board_[r * N_SEATS + 7];
ch2 = board_[r * N_SEATS + 6];
for (s = N_SEATS - 1; s >= 2; s--) {
board_[r * N_SEATS + s] = board_[r * N_SEATS + s - 2];
//updateHash(i * N_SEATS + j);
}
board_[r * N_SEATS + 1] = ch1;
//updateHash(i * N_SEATS + 1);
board_[r * N_SEATS] = ch2;
//updateHash(i * N_SEATS);
}
} else if (degrees == 4) {
for (r = 1; r <= N_RINGS; r++) {
for (s = 0; s < N_SEATS / 2; s++) {
ch1 = board_[r * N_SEATS + s];
board_[r * N_SEATS + s] = board_[r * N_SEATS + s + 4];
//updateHash(i * N_SEATS + j);
board_[r * N_SEATS + s + 4] = ch1;
//updateHash(i * N_SEATS + j + 4);
}
}
} else {
return;
}
uint64_t llp[3] = { 0 };
if (move_ < 0) {
r = (-move_) / N_SEATS;
s = (-move_) % N_SEATS;
s = (s + N_SEATS - degrees) % N_SEATS;
move_ = -(r * N_SEATS + s);
} else {
llp[0] = static_cast<uint64_t>(move_ >> 8);
llp[1] = move_ & 0x00ff;
r = static_cast<int>(llp[0]) / N_SEATS;
s = static_cast<int>(llp[0]) % N_SEATS;
s = (s + N_SEATS - degrees) % N_SEATS;
llp[0] = static_cast<uint64_t>(r * N_SEATS + s);
r = static_cast<int>(llp[1]) / N_SEATS;
s = static_cast<int>(llp[1]) % N_SEATS;
s = (s + N_SEATS - degrees) % N_SEATS;
llp[1] = static_cast<uint64_t>(r * N_SEATS + s);
move_ = static_cast<int16_t>(((llp[0] << 8) | llp[1]));
}
if (currentPos != 0) {
r = currentPos / N_SEATS;
s = currentPos % N_SEATS;
s = (s + N_SEATS - degrees) % N_SEATS;
currentPos = r * N_SEATS + s;
}
if (currentRule.allowRemovePiecesRepeatedly) {
for (auto &mill : millList) {
llp[0] = (mill & 0x000000ff00000000) >> 32;
llp[1] = (mill & 0x0000000000ff0000) >> 16;
llp[2] = (mill & 0x00000000000000ff);
for (i = 0; i < 3; i++) {
r = static_cast<int>(llp[i]) / N_SEATS;
s = static_cast<int>(llp[i]) % N_SEATS;
s = (s + N_SEATS - degrees) % N_SEATS;
llp[i] = static_cast<uint64_t>(r * N_SEATS + s);
}
mill &= 0xffffff00ff00ff00;
mill |= (llp[0] << 32) | (llp[1] << 16) | llp[2];
}
}
// 命令行解析
if (cmdChange) {
int r1, s1, r2, s2;
int args = 0;
int mm = 0, ss = 0;
args = sscanf(cmdline, "(%1u,%1u)->(%1u,%1u) %2u:%2u", &r1, &s1, &r2, &s2, &mm, &ss);
if (args >= 4) {
s1 = (s1 - 1 + N_SEATS - degrees) % N_SEATS;
s2 = (s2 - 1 + N_SEATS - degrees) % N_SEATS;
cmdline[3] = '1' + static_cast<char>(s1);
cmdline[10] = '1' + static_cast<char>(s2);
} else {
args = sscanf(cmdline, "-(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
s1 = (s1 - 1 + N_SEATS - degrees) % N_SEATS;
cmdline[4] = '1' + static_cast<char>(s1);
} else {
args = sscanf(cmdline, "(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
s1 = (s1 - 1 + N_SEATS - degrees) % N_SEATS;
cmdline[3] = '1' + static_cast<char>(s1);
}
}
}
for (auto &iter : cmdlist) {
args = sscanf(iter.c_str(), "(%1u,%1u)->(%1u,%1u) %2u:%2u", &r1, &s1, &r2, &s2, &mm, &ss);
if (args >= 4) {
s1 = (s1 - 1 + N_SEATS - degrees) % N_SEATS;
s2 = (s2 - 1 + N_SEATS - degrees) % N_SEATS;
iter[3] = '1' + static_cast<char>(s1);
iter[10] = '1' + static_cast<char>(s2);
} else {
args = sscanf(iter.c_str(), "-(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
s1 = (s1 - 1 + N_SEATS - degrees) % N_SEATS;
iter[4] = '1' + static_cast<char>(s1);
} else {
args = sscanf(iter.c_str(), "(%1u,%1u) %2u:%2u", &r1, &s1, &mm, &ss);
if (args >= 2) {
s1 = (s1 - 1 + N_SEATS - degrees) % N_SEATS;
iter[3] = '1' + static_cast<char>(s1);
}
}
}
}
}
}

145
src/game/board.h Normal file
View File

@ -0,0 +1,145 @@
/*****************************************************************************
* Copyright (C) 2018-2019 MillGame authors
*
* Authors: liuweilhy <liuweilhy@163.com>
* Calcitem <calcitem@outlook.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef BOARD_H
#define BOARD_H
#include <vector>
#include "config.h"
#include "rule.h"
#include "types.h"
using namespace std;
class Board
{
public:
Board();
~Board();
Board & operator=(const Board &);
// 静态成员常量
// 3圈禁止修改!
static const int N_RINGS = 3;
// 8位禁止修改!
static const int N_SEATS = 8;
// 横直斜3个方向禁止修改!
static const int N_DIRECTIONS = 3;
// 棋盘点的个数40
static const int N_POINTS = (Board::N_RINGS + 2) * Board::N_SEATS;
// 遍历棋盘点所用的起始位置,即 [8, 32)
static const int POS_BEGIN = N_SEATS;
static const int POS_END = ((N_RINGS + 1) * N_SEATS);
// 空棋盘点位,用于判断一个棋子位置是否在棋盘上
static const int onBoard[N_POINTS];
// 成三表,表示棋盘上各个位置有成三关系的对应位置表
// 这个表跟规则有关,一旦规则改变需要重新修改
static int millTable[N_POINTS][N_DIRECTIONS][N_RINGS - 1];
// 生成成三表
void createMillTable(const Rule &currentRule);
// 局面左右镜像
void mirror(list <string> &cmdlist, char *cmdline, int32_t move_, struct Rule &currentRule, int currentPos, bool cmdChange = true);
// 局面内外翻转
void turn(list <string> &cmdlist, char *cmdline, int32_t move_, const Rule &currentRule, int currentPos, bool cmdChange = true);
// 局面逆时针旋转
void rotate(int degrees, list <string> &cmdlist, char *cmdline, int32_t move_, const Rule &currentRule, int currentPos, bool cmdChange = true);
// 判断棋盘pos处的棋子处于几个“三连”中
int isInMills(int pos, bool test = false);
// 判断玩家的所有棋子是否都处于“三连”状态
bool isAllInMills(char ch);
bool isAllInMills(enum Player);
// 判断玩家的棋子周围有几个空位
int getSurroundedEmptyPosCount(enum Player turn, const Rule &currentRule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, int pos, bool includeFobidden);
// 判断玩家的棋子是否被围
bool isSurrounded(enum Player turn, const Rule &currentRule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, int pos);
// 判断玩家的棋子是否全部被围
bool isAllSurrounded(enum Player turn, const Rule &currentRule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, char ch);
bool isAllSurrounded(enum Player turn, const Rule &currentRule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, enum Player ply);
// 三连加入列表
int addMills(const Rule &currentRule, int pos);
// 获取位置点棋子的归属人
enum Player getWhosPiece(int r, int s);
bool getPieceRS(const Player &player, const int &number, int &r, int &s, struct Rule &currentRule);
// 获取当前棋子
bool getCurrentPiece(Player &player, int &number, int currentPos);
// 将棋盘下标形式转化为第r圈第s位r和s下标都从1开始
void pos2rs(int pos, int &r, int &s);
// 将第c圈第p位转化为棋盘下标形式r和s下标都从1开始
int rs2Pos(int r, int s);
//private:
// 棋局,抽象为一个 5*8 的数组,上下两行留空
/*
0x00
0x0F
0x11~0x1C 1~12
0x21~0x2C 1~12
(board[i] & 0x10)
(board[i] & 0x20)
*/
int board_[N_POINTS]{};
/*
struct Mill {
char piece1; // “三连”中最小的棋子
char pos1; // 最小棋子的位置
char piece2; // 次小的棋子
char pos2; // 次小棋子的位置
char piece3; // 最大的棋子
char pos3; // 最大棋子的位置
};
64
0x 00 00 00 00 00 00 00 00
unused unused piece1 pos1 piece2 pos2 piece3 pos3
*/
// 三连列表
vector<uint64_t> millList;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -27,9 +27,54 @@
#include <list>
#include "config.h"
#include "types.h"
#include "rule.h"
#include "board.h"
using std::string;
using std::list;
using namespace std;
// 棋局结构体,算法相关,包含当前棋盘数据
// 单独分离出来供AI判断局面用生成置换表时使用
class ChessContext
{
public:
Board board;
#if ((defined HASH_MAP_ENABLE) || (defined BOOK_LEARNING) || (defined THREEFOLD_REPETITION))
// 局面的哈希值
hash_t hash{};
// Zobrist 数组
hash_t zobrist[Board::N_POINTS][POINT_TYPE_COUNT]{};
#endif /* HASH_MAP_ENABLE */
// 局面阶段标识
enum GameStage stage;
// 轮流状态标识
enum Player turn;
// 动作状态标识
enum Action action
{
};
// 玩家1剩余未放置子数
int nPiecesInHand_1{};
// 玩家2剩余未放置子数
int nPiecesInHand_2{};
// 玩家1盘面剩余子数
int nPiecesOnBoard_1{};
// 玩家1盘面剩余子数
int nPiecesOnBoard_2{};
// 尚待去除的子数
int nPiecesNeedRemove{};
};
// 棋类(在数据模型内,玩家只分先后手,不分黑白)
// 注意MillGame类不是线程安全的
@ -40,118 +85,11 @@ class MillGame
friend class MillGameAi_ab;
public:
// 静态成员常量
// 3圈禁止修改
static const int N_RINGS = 3;
// 8位禁止修改
static const int N_SEATS = 8;
// 横直斜3个方向禁止修改
static const int N_DIRECTIONS = 3;
// 棋盘点的个数40
static const int N_POINTS = (MillGame::N_RINGS + 2) * MillGame::N_SEATS;
// 移动方向包括顺时针、逆时针、向内、向外4个方向
enum MoveDirection
{
MOVE_DIRECTION_CLOCKWISE = 0, // 顺时针
MOVE_DIRECTION_ANTICLOCKWISE = 1, // 逆时针
MOVE_DIRECTION_INWARD = 2, // 向内
MOVE_DIRECTION_OUTWARD = 3, // 向外
MOVE_DIRECTION_FLY = 4, // 飞子
N_MOVE_DIRECTIONS = 4 // 移动方向数
};
// 遍历棋盘点所用的起始位置,即 [8, 32)
static const int POS_BEGIN = N_SEATS;
static const int POS_END = ((N_RINGS + 1) * N_SEATS);
// 预定义的规则数目
static const int N_RULES = 4;
// 定义类型
using move_t = int32_t;
using step_t = uint16_t;
#ifdef HASH_MAP_CUTDOWN
using hash_t = uint32_t;
#else
using hash_t = uint64_t;
#endif /* HASH_MAP_CUTDOWN */
// 位置迭代器
// typedef typename std::vector<move_t>::iterator posIterator;
// typedef typename std::vector<move_t>::const_iterator constPosIterator;
// 赢盘数
int score_1 {};
int score_2 {};
int score_draw {};
// 嵌套的规则结构体
struct Rule
{
// 规则名称
const char *name;
// 规则介绍
const char *description;
// 任一方子数各9子或各12子
int nTotalPiecesEachSide;
// 赛点子数,少于则判负
int nPiecesAtLeast;
// 是否有斜线
bool hasObliqueLines;
// 是否有禁点(摆棋阶段被提子的点不能再摆子)
bool hasForbiddenPoint;
// 是否后摆棋者先行棋
bool isDefenderMoveFirst;
// 相同顺序和位置的重复“三连”是否可反复提子
bool allowRemovePiecesRepeatedly;
// 多个“三连”能否多提子
bool allowRemoveMultiPieces;
// 能否提“三连”的子
bool allowRemoveMill;
// 摆棋满子闷棋只有12子棋才出现是否算先手负false为和棋
bool isStartingPlayerLoseWhenBoardFull;
// 走棋阶段不能行动被“闷”是否算负false则轮空由对手走棋
bool isLoseWhenNoWay;
// 剩三子时是否可以飞棋
bool allowFlyWhenRemainThreePieces;
// 最大步数,超出判和
step_t maxStepsLedToDraw;
// 包干最长时间超出判负为0则不计时
int maxTimeLedToLose;
};
// 预定义的规则
static const struct Rule RULES[N_RULES];
// 局面阶段标识
enum GameStage : uint16_t
{
GAME_NONE = 0x0000,
GAME_NOTSTARTED = 0x0001, // 未开局
GAME_PLACING = 0x0002, // 开局(摆棋)
GAME_MOVING = 0x0004, // 中局(走棋)
GAME_OVER = 0x0008 // 结局
};
uint64_t rand64() {
return static_cast<uint64_t>(rand()) ^
(static_cast<uint64_t>(rand()) << 15) ^
@ -165,130 +103,11 @@ public:
return rand64() << 8;
}
// 玩家标识, 轮流状态, 胜负标识
enum Player : uint8_t
{
PLAYER1 = 0x10, // 玩家1
PLAYER2 = 0x20, // 玩家2
DRAW = 0x40, // 双方和棋
NOBODY = 0x80 // 胜负未分
};
static int playerToId(enum Player player);
static Player getOpponent(enum Player player);
// 动作状态标识
enum Action : uint16_t
{
ACTION_NONE = 0x0000,
ACTION_CHOOSE = 0x0100, // 选子
ACTION_PLACE = 0x0200, // 落子
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
{
// 棋局抽象为一个5×8的数组上下两行留空
/*
0x00
0x0F
0x110x1C 112
0x210x2C 112
(board[i] & 0x10)
(board[i] & 0x20)
*/
int board[N_POINTS] {};
#if ((defined HASH_MAP_ENABLE) || (defined BOOK_LEARNING) || (defined THREEFOLD_REPETITION))
// 局面的哈希值
hash_t hash {};
// 标记处于走子阶段的哈希
hash_t gameMovingHash {};
// 吃子动作的哈希
hash_t actionCaptureHash {};
// 标记轮到玩家2行棋的哈希
hash_t player2sTurnHash {};
// Zobrist 数组
hash_t zobrist[N_POINTS][POINT_TYPE_COUNT] {};
#endif /* HASH_MAP_ENABLE */
// 局面阶段标识
enum MillGame::GameStage stage;
// 轮流状态标识
enum MillGame::Player turn;
// 动作状态标识
enum MillGame::Action action {};
// 玩家1剩余未放置子数
int nPiecesInHand_1 {};
// 玩家2剩余未放置子数
int nPiecesInHand_2 {};
// 玩家1盘面剩余子数
int nPiecesOnBoard_1 {};
// 玩家1盘面剩余子数
int nPiecesOnBoard_2 {};
// 尚待去除的子数
int nPiecesNeedRemove {};
#if 0
struct Mill {
char piece1; // “三连”中最小的棋子
char pos1; // 最小棋子的位置
char piece2; // 次小的棋子
char pos2; // 次小棋子的位置
char piece3; // 最大的棋子
char pos3; // 最大棋子的位置
};
64
0x 00 00 00 00 00 00 00 00
unused unused piece1 pos1 piece2 pos2 piece3 pos3
#endif
// “三连列表”
list <uint64_t> millList;
};
private:
// 空棋盘点位,用于判断一个棋子位置是否在棋盘上
static const int onBoard[N_POINTS];
// 着法表每个位置有最多4种走法顺时针、逆时针、向内、向外
// 这个表跟规则有关,一旦规则改变需要重新修改
static int moveTable[N_POINTS][N_MOVE_DIRECTIONS];
// 成三表,表示棋盘上各个位置有成三关系的对应位置表
// 这个表跟规则有关,一旦规则改变需要重新修改
static int millTable[N_POINTS][N_DIRECTIONS][N_RINGS - 1];
// 生成着法表
void createMoveTable();
// 生成成三表
void createMillTable();
// 创建哈希值
void constructHash();
@ -331,15 +150,9 @@ public:
// 获取棋盘数据
const int *getBoard() const
{
return context.board;
return context.board.board_;
}
// 获取棋子位置(r, s)
bool getPieceRS(const Player &player, const int &number, int &r, int &s);
// 获取当前棋子
bool getCurrentPiece(Player &player, int &number);
// 获取当前棋子位置点
int getCurrentPos() const
{
@ -409,9 +222,6 @@ public:
return tips;
}
// 获取位置点棋子的归属人
enum Player getWhosPiece(int r, int s);
// 获取当前着法
const char *getCmdLine() const
{
@ -467,7 +277,7 @@ public:
}
// 计算玩家1和玩家2的棋子活动能力之差
int getMobilityDiff(bool includeFobidden);
int getMobilityDiff(enum Player turn, const Rule &rule, int nPiecesOnBoard_1, int nPiecesOnBoard_2, bool includeFobidden);
// 游戏重置
bool reset();
@ -490,42 +300,6 @@ public:
// 命令行解析函数
bool command(const char *cmd);
// 局面左右镜像
void mirror(bool cmdChange = true);
// 局面内外翻转
void turn(bool cmdChange = true);
// 局面逆时针旋转
void rotate(int degrees, bool cmdChange = true);
// 判断棋盘pos处的棋子处于几个“三连”中
int isInMills(int pos, bool test = false);
// 判断玩家的所有棋子是否都处于“三连”状态
bool isAllInMills(char ch);
bool isAllInMills(enum Player);
// 判断玩家的棋子周围有几个空位
int getSurroundedEmptyPosCount(int pos, bool includeFobidden);
// 判断玩家的棋子是否被围
bool isSurrounded(int pos);
// 判断玩家的棋子是否全部被围
bool isAllSurrounded(char ch);
bool isAllSurrounded(enum Player);
// 三连加入列表
int addMills(int pos);
// 将棋盘下标形式转化为第r圈第s位r和s下标都从1开始
void pos2rs(int pos, int &r, int &s);
// 将第c圈第p位转化为棋盘下标形式r和s下标都从1开始
int rs2Pos(int r, int s);
// 更新时间和状态,用内联函数以提高效率
inline int update(int time_p = -1);
@ -537,7 +311,7 @@ public:
void cleanForbiddenPoints();
// 改变轮流
enum MillGame::Player changeTurn();
enum Player changeTurn();
// 设置提示
void setTips();
@ -558,7 +332,7 @@ public:
public: /* TODO: move to private */
// 棋局上下文
struct ChessContext context;
ChessContext context;
// 当前使用的规则
struct Rule currentRule
@ -568,13 +342,41 @@ public: /* TODO: move to private */
// 棋局上下文中的棋盘数据,单独提出来
int *board_;
// 棋谱
list <string> cmdlist;
// 着法命令行用于棋谱的显示和解析, 当前着法的命令行指令,即一招棋谱
char cmdline[64]{};
/*
AI会用到
0x 00 00
pos1 pos2
0x00????
0x__??__为移动前的位置??
0xFF????
31 ----- 24 ----- 25
| \ | / |
| 23 -- 16 -- 17 |
| | \ | / | |
| | 15 08 09 | |
30-22-14 10-18-26
| | 13 12 11 | |
| | / | \ | |
| 21 -- 20 -- 19 |
| / | \ |
29 ----- 28 ----- 27
*/
int32_t move_{};
// 选中的棋子在board中的位置
int currentPos{};
private:
// 棋局哈希值
// uint64_t hash;
// 选中的棋子在board中的位置
int currentPos {};
// 胜负标识
enum Player winner;
@ -602,35 +404,6 @@ private:
// 玩家2用时
time_t elapsedSeconds_2 {};
/* 当前着法AI会用到如下表示
0x 00 00
pos1 pos2
0x00????
0x__??__为移动前的位置??
0xFF????
*/
/*
31 ----- 24 ----- 25
| \ | / |
| 23 -- 16 -- 17 |
| | \ | / | |
| | 15 08 09 | |
30-22-14 10-18-26
| | 13 12 11 | |
| | / | \ | |
| 21 -- 20 -- 19 |
| / | \ |
29 ----- 28 ----- 27
*/
int32_t move_ {};
// 着法命令行用于棋谱的显示和解析
// 当前着法的命令行指令,即一招棋谱
char cmdline[64] {};
// 棋谱
list <string> cmdlist;
// 当前棋局的字符提示
string tips;
};

114
src/game/rule.cpp Normal file
View File

@ -0,0 +1,114 @@
/*****************************************************************************
* Copyright (C) 2018-2019 MillGame authors
*
* Authors: liuweilhy <liuweilhy@163.com>
* Calcitem <calcitem@outlook.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include "rule.h"
#include "types.h"
// 对静态常量数组的定义要放在类外,不要放在头文件
// 预定义的4套规则
const struct Rule RULES[N_RULES] = {
{
"成三棋", // 成三棋
// 规则说明
"1. 双方各9颗子开局依次摆子\n"
"2. 凡出现三子相连,就提掉对手一子;\n"
"3. 不能提对手的“三连”子,除非无子可提;\n"
"4. 同时出现两个“三连”只能提一子;\n"
"5. 摆完后依次走子,每次只能往相邻位置走一步;\n"
"6. 把对手棋子提到少于3颗时胜利\n"
"7. 走棋阶段不能行动(被“闷”)算负。",
9, // 双方各9子
3, // 赛点子数为3
false, // 没有斜线
false, // 没有禁点,摆棋阶段被提子的点可以再摆子
false, // 先摆棋者先行棋
true, // 可以重复成三
false, // 多个“三连”只能提一子
false, // 不能提对手的“三连”子,除非无子可提;
true, // 摆棋满子闷棋只有12子棋才出现算先手负
true, // 走棋阶段不能行动(被“闷”)算负
false, // 剩三子时不可以飞棋
0, // 不计步数
0 // 不计时
},
{
"打三棋(12连棋)", // 打三棋
// 规则说明
"1. 双方各12颗子棋盘有斜线\n"
"2. 摆棋阶段被提子的位置不能再摆子,直到走棋阶段;\n"
"3. 摆棋阶段,摆满棋盘算先手负;\n"
"4. 走棋阶段,后摆棋的一方先走;\n"
"5. 同时出现两个“三连”只能提一子;\n"
"6. 其它规则与成三棋基本相同。",
12, // 双方各12子
3, // 赛点子数为3
true, // 有斜线
true, // 有禁点,摆棋阶段被提子的点不能再摆子
true, // 后摆棋者先行棋
true, // 可以重复成三
false, // 多个“三连”只能提一子
true, // 可以提对手的“三连”子
true, // 摆棋满子闷棋只有12子棋才出现算先手负
true, // 走棋阶段不能行动(被“闷”)算负
false, // 剩三子时不可以飞棋
50, // 不计步数
0 // 不计时
},
{
"九连棋", // 九连棋
// 规则说明
"1. 规则与成三棋基本相同,只是它的棋子有序号,\n"
"2. 相同序号、位置的“三连”不能重复提子;\n"
"3. 走棋阶段不能行动(被“闷”),则由对手继续走棋;\n"
"4. 一步出现几个“三连”就可以提几个子。",
9, // 双方各9子
3, // 赛点子数为3
false, // 没有斜线
false, // 没有禁点,摆棋阶段被提子的点可以再摆子
false, // 先摆棋者先行棋
false, // 不可以重复成三
true, // 出现几个“三连”就可以提几个子
false, // 不能提对手的“三连”子,除非无子可提;
true, // 摆棋满子闷棋只有12子棋才出现算先手负
false, // 走棋阶段不能行动(被“闷”),则由对手继续走棋
false, // 剩三子时不可以飞棋
0, // 不计步数
0 // 不计时
},
{
"莫里斯九子棋", // 莫里斯九子棋
// 规则说明
"规则与成三棋基本相同只是在走子阶段当一方仅剩3子时他可以飞子到任意空位。",
9, // 双方各9子
3, // 赛点子数为3
false, // 没有斜线
false, // 没有禁点,摆棋阶段被提子的点可以再摆子
false, // 先摆棋者先行棋
true, // 可以重复成三
false, // 多个“三连”只能提一子
false, // 不能提对手的“三连”子,除非无子可提;
true, // 摆棋满子闷棋只有12子棋才出现算先手负
true, // 走棋阶段不能行动(被“闷”)算负
true, // 剩三子时可以飞棋
0, // 不计步数
0 // 不计时
}
};

82
src/game/rule.h Normal file
View File

@ -0,0 +1,82 @@
/*****************************************************************************
* Copyright (C) 2018-2019 MillGame authors
*
* Authors: liuweilhy <liuweilhy@163.com>
* Calcitem <calcitem@outlook.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef RULE_H
#define RULE_H
#include "types.h"
struct Rule
{
// 规则名称
const char *name;
// 规则介绍
const char *description;
// 任一方子数各9子或各12子
int nTotalPiecesEachSide;
// 赛点子数,少于则判负
int nPiecesAtLeast;
// 是否有斜线
bool hasObliqueLines;
// 是否有禁点(摆棋阶段被提子的点不能再摆子)
bool hasForbiddenPoint;
// 是否后摆棋者先行棋
bool isDefenderMoveFirst;
// 相同顺序和位置的重复“三连”是否可反复提子
bool allowRemovePiecesRepeatedly;
// 多个“三连”能否多提子
bool allowRemoveMultiPieces;
// 能否提“三连”的子
bool allowRemoveMill;
// 摆棋满子闷棋只有12子棋才出现是否算先手负false为和棋
bool isStartingPlayerLoseWhenBoardFull;
// 走棋阶段不能行动被“闷”是否算负false则轮空由对手走棋
bool isLoseWhenNoWay;
// 剩三子时是否可以飞棋
bool allowFlyWhenRemainThreePieces;
// 最大步数,超出判和
step_t maxStepsLedToDraw;
// 包干最长时间超出判负为0则不计时
int maxTimeLedToLose;
};
// 预定义的规则数目
static const int N_RULES = 4;
// 预定义的规则
extern const struct Rule RULES[N_RULES];
#endif /* RULE_H */

87
src/game/types.h Normal file
View File

@ -0,0 +1,87 @@
/*****************************************************************************
* Copyright (C) 2018-2019 MillGame authors
*
* Authors: liuweilhy <liuweilhy@163.com>
* Calcitem <calcitem@outlook.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef TYPES_H
#define TYPES_H
#include "config.h"
using move_t = int32_t;
using step_t = uint16_t;
using value_t = int16_t;
using depth_t = uint8_t;
#ifdef HASH_MAP_CUTDOWN
using hash_t = uint32_t;
#else
using hash_t = uint64_t;
#endif /* HASH_MAP_CUTDOWN */
// 移动方向包括顺时针、逆时针、向内、向外4个方向
enum MoveDirection
{
MOVE_DIRECTION_CLOCKWISE = 0, // 顺时针
MOVE_DIRECTION_ANTICLOCKWISE = 1, // 逆时针
MOVE_DIRECTION_INWARD = 2, // 向内
MOVE_DIRECTION_OUTWARD = 3, // 向外
MOVE_DIRECTION_FLY = 4, // 飞子
N_MOVE_DIRECTIONS = 4 // 移动方向数
};
// 棋盘点上棋子的类型
enum PointType : uint16_t
{
POINT_TYPE_EMPTY = 0, // 没有棋子
POINT_TYPE_PLAYER1 = 1, // 先手的子
POINT_TYPE_PLAYER2 = 2, // 后手的子
POINT_TYPE_FORBIDDEN = 3, // 禁点
POINT_TYPE_COUNT = 4
};
// 玩家标识, 轮流状态, 胜负标识
enum Player : uint8_t
{
PLAYER1 = 0x10, // 玩家1
PLAYER2 = 0x20, // 玩家2
PLAYER_DRAW = 0x40, // 双方和棋
PLAYER_NOBODY = 0x80 // 胜负未分
};
// 局面阶段标识
enum GameStage : uint16_t
{
GAME_NONE = 0x0000,
GAME_NOTSTARTED = 0x0001, // 未开局
GAME_PLACING = 0x0002, // 开局(摆棋)
GAME_MOVING = 0x0004, // 中局(走棋)
GAME_OVER = 0x0008 // 结局
};
// 动作状态标识
enum Action : uint16_t
{
ACTION_NONE = 0x0000,
ACTION_CHOOSE = 0x0100, // 选子
ACTION_PLACE = 0x0200, // 落子
ACTION_CAPTURE = 0x0400 // 提子
};
#endif /* TYPES_H */

View File

@ -136,7 +136,7 @@ void BoardItem::paint(QPainter *painter,
}
}
#ifdef DRAW_SEAT_NUMBER
#ifdef PLAYER_DRAW_SEAT_NUMBER
// 画 Seat 编号
QPen fontPen(QBrush(Qt::white), LINE_WEIGHT, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);
painter->setPen(fontPen);
@ -151,7 +151,7 @@ void BoardItem::paint(QPainter *painter,
QString strSeat(cSeat);
painter->drawText(position[(N_RINGS - 1) * N_SEATS + i], strSeat);
}
#endif // DRAW_SEAT_NUMBER
#endif // PLAYER_DRAW_SEAT_NUMBER
}
QPointF BoardItem::nearestPosition(QPointF const pos)

View File

@ -104,11 +104,11 @@ const QMap<int, QStringList> GameController::getActions()
// 之所以不用信号和槽的模式,是因为发信号的时候槽还来不及关联
QMap<int, QStringList> actions;
for (int i = 0; i < MillGame::N_RULES; i++) {
for (int i = 0; i < N_RULES; i++) {
// QMap的key存放int索引值value存放规则名称和规则提示
QStringList strlist;
strlist.append(tr(MillGame::RULES[i].name));
strlist.append(tr(MillGame::RULES[i].description));
strlist.append(tr(RULES[i].name));
strlist.append(tr(RULES[i].description));
actions.insert(i, strlist);
}
@ -137,8 +137,8 @@ void GameController::gameReset()
timeID = 0;
// 棋未下完,则算对手得分
if (chess_.getStage() == MillGame::GAME_MOVING &&
chess_.whoWin() == MillGame::NOBODY) {
if (chess_.getStage() == GAME_MOVING &&
chess_.whoWin() == PLAYER_NOBODY) {
giveUp();
}
@ -262,10 +262,10 @@ void GameController::setInvert(bool arg)
}
}
void GameController::setRule(int ruleNo, MillGame::step_t stepLimited /*= -1*/, int timeLimited /*= -1*/)
void GameController::setRule(int ruleNo, step_t stepLimited /*= -1*/, int timeLimited /*= -1*/)
{
// 更新规则,原限时和限步不变
if (ruleNo < 0 || ruleNo >= MillGame::N_RULES)
if (ruleNo < 0 || ruleNo >= N_RULES)
return;
this->ruleNo_ = ruleNo;
@ -275,7 +275,7 @@ void GameController::setRule(int ruleNo, MillGame::step_t stepLimited /*= -1*/,
}
// 设置模型规则,重置游戏
chess_.setContext(&MillGame::RULES[ruleNo], stepsLimit, timeLimit);
chess_.setContext(&RULES[ruleNo], stepsLimit, timeLimit);
chessTemp = chess_;
// 重置游戏
@ -314,7 +314,7 @@ void GameController::setEngine2(bool arg)
}
}
void GameController::setAiDepthTime(MillGameAi_ab::depth_t depth1, int time1, MillGameAi_ab::depth_t depth2, int time2)
void GameController::setAiDepthTime(depth_t depth1, int time1, depth_t depth2, int time2)
{
if (isAiPlayer1) {
ai1.stop();
@ -336,7 +336,7 @@ void GameController::setAiDepthTime(MillGameAi_ab::depth_t depth1, int time1, Mi
}
}
void GameController::getAiDepthTime(MillGameAi_ab::depth_t &depth1, int &time1, MillGameAi_ab::depth_t &depth2, int &time2)
void GameController::getAiDepthTime(depth_t &depth1, int &time1, depth_t &depth2, int &time2)
{
ai1.getDepthTime(depth1, time1);
ai2.getDepthTime(depth2, time2);
@ -398,8 +398,8 @@ void GameController::flip()
ai2.wait();
}
chess_.mirror();
chess_.rotate(180);
chess_.context.board.mirror(chess_.cmdlist, chess_.cmdline, chess_.move_, chess_.currentRule, chess_.currentPos);
chess_.context.board.rotate(180, chess_.cmdlist, chess_.cmdline, chess_.move_, chess_.currentRule, chess_.currentPos);
chessTemp = chess_;
// 更新棋谱
@ -438,7 +438,7 @@ void GameController::mirror()
ai2.wait();
}
chess_.mirror();
chess_.context.board.mirror(chess_.cmdlist, chess_.cmdline, chess_.move_, chess_.currentRule, chess_.currentPos);
chessTemp = chess_;
// 更新棋谱
@ -480,7 +480,7 @@ void GameController::turnRight()
ai2.wait();
}
chess_.rotate(-90);
chess_.context.board.rotate(-90, chess_.cmdlist, chess_.cmdline, chess_.move_, chess_.currentRule, chess_.currentPos);
chessTemp = chess_;
// 更新棋谱
@ -520,7 +520,7 @@ void GameController::turnLeft()
ai2.wait();
}
chess_.rotate(90);
chess_.context.board.rotate(90, chess_.cmdlist, chess_.cmdline, chess_.move_, chess_.currentRule, chess_.currentPos);
chessTemp = chess_;
// 更新棋谱
@ -564,7 +564,7 @@ void GameController::timerEvent(QTimerEvent *event)
emit time2Changed(qt2.toString("hh:mm:ss"));
// 如果胜负已分
if (chess_.whoWin() != MillGame::NOBODY) {
if (chess_.whoWin() != PLAYER_NOBODY) {
// 停止计时
killTimer(timeID);
@ -609,8 +609,8 @@ void GameController::timerEvent(QTimerEvent *event)
bool GameController::isAIsTurn()
{
return ((chess_.whosTurn() == MillGame::PLAYER1 && isAiPlayer1) ||
(chess_.whosTurn() == MillGame::PLAYER2 && isAiPlayer2));
return ((chess_.whosTurn() == PLAYER1 && isAiPlayer1) ||
(chess_.whosTurn() == PLAYER2 && isAiPlayer2));
}
// 关键槽函数根据QGraphicsScene的信号和状态来执行选子、落子或去子
@ -647,7 +647,7 @@ bool GameController::actionPiece(QPointF pos)
manualListModel.removeRows(currentRow + 1, manualListModel.rowCount() - currentRow - 1);
// 如果再决出胜负后悔棋,则重新启动计时
if (chess_.whoWin() == MillGame::NOBODY) {
if (chess_.whoWin() == PLAYER_NOBODY) {
// 重新启动计时
timeID = startTimer(100);
@ -664,7 +664,7 @@ bool GameController::actionPiece(QPointF pos)
}
// 如果未开局则开局
if (chess_.getStage() == MillGame::GAME_NOTSTARTED)
if (chess_.getStage() == GAME_NOTSTARTED)
gameStart();
// 判断执行选子、落子或去子
@ -673,9 +673,9 @@ bool GameController::actionPiece(QPointF pos)
QGraphicsItem *item = scene.itemAt(pos, QTransform());
switch (chess_.getAction()) {
case MillGame::ACTION_PLACE:
case ACTION_PLACE:
if (chess_._place(r, s)) {
if (chess_.getAction() == MillGame::ACTION_CAPTURE) {
if (chess_.getAction() == ACTION_CAPTURE) {
// 播放成三音效
playSound(":/sound/resources/sound/capture.wav");
} else {
@ -689,7 +689,7 @@ bool GameController::actionPiece(QPointF pos)
// 如果移子不成功尝试重新选子这里不break
[[fallthrough]];
case MillGame::ACTION_CHOOSE:
case ACTION_CHOOSE:
piece = qgraphicsitem_cast<PieceItem *>(item);
if (!piece)
break;
@ -703,7 +703,7 @@ bool GameController::actionPiece(QPointF pos)
}
break;
case MillGame::ACTION_CAPTURE:
case ACTION_CAPTURE:
if (chess_._capture(r, s)) {
// 播放音效
playSound(":/sound/resources/sound/remove.wav");
@ -739,7 +739,7 @@ bool GameController::actionPiece(QPointF pos)
// 播放胜利或失败音效
#ifndef DONOT_PLAY_WIN_SOUND
if (chess_.whoWin() != MillGame::NOBODY &&
if (chess_.whoWin() != PLAYER_NOBODY &&
(manualListModel.data(manualListModel.index(currentRow - 1))).toString().contains("Time over."))
playSound(":/sound/resources/sound/win.wav");
#endif
@ -747,8 +747,8 @@ bool GameController::actionPiece(QPointF pos)
// AI设置
if (&chess_ == &(this->chess_)) {
// 如果还未决出胜负
if (chess_.whoWin() == MillGame::NOBODY) {
if (chess_.whosTurn() == MillGame::PLAYER1) {
if (chess_.whoWin() == PLAYER_NOBODY) {
if (chess_.whosTurn() == PLAYER1) {
if (isAiPlayer1) {
ai1.resume();
}
@ -782,11 +782,11 @@ bool GameController::giveUp()
{
bool result = false;
if (chess_.whosTurn() == MillGame::PLAYER1) {
result = chess_.giveup(MillGame::PLAYER1);
if (chess_.whosTurn() == PLAYER1) {
result = chess_.giveup(PLAYER1);
}
else if (chess_.whosTurn() == MillGame::PLAYER2) {
result = chess_.giveup(MillGame::PLAYER2);
else if (chess_.whosTurn() == PLAYER2) {
result = chess_.giveup(PLAYER2);
}
if (result) {
@ -802,7 +802,7 @@ bool GameController::giveUp()
manualListModel.insertRow(++currentRow);
manualListModel.setData(manualListModel.index(currentRow), i.c_str());
}
if (chess_.whoWin() != MillGame::NOBODY)
if (chess_.whoWin() != PLAYER_NOBODY)
playSound(":/sound/resources/sound/loss.wav");
}
@ -825,11 +825,11 @@ bool GameController::command(const QString &cmd, bool update /* = true */)
QString sound;
switch (chess_.getAction()) {
case MillGame::ACTION_CHOOSE:
case MillGame::ACTION_PLACE:
case ACTION_CHOOSE:
case ACTION_PLACE:
sound = ":/sound/resources/sound/drog.wav";
break;
case MillGame::ACTION_CAPTURE:
case ACTION_CAPTURE:
sound = ":/sound/resources/sound/remove.wav";
break;
default:
@ -837,14 +837,14 @@ bool GameController::command(const QString &cmd, bool update /* = true */)
}
// 如果未开局则开局
if (chess_.getStage() == MillGame::GAME_NOTSTARTED) {
if (chess_.getStage() == GAME_NOTSTARTED) {
gameStart();
}
if (!chess_.command(cmd.toStdString().c_str()))
return false;
if (sound == ":/sound/resources/sound/drog.wav" && chess_.getAction() == MillGame::ACTION_CAPTURE) {
if (sound == ":/sound/resources/sound/drog.wav" && chess_.getAction() == ACTION_CAPTURE) {
sound = ":/sound/resources/sound/capture.wav";
}
@ -882,7 +882,7 @@ bool GameController::command(const QString &cmd, bool update /* = true */)
// 播放胜利或失败音效
#ifndef DONOT_PLAY_WIN_SOUND
if (chess_.whoWin() != MillGame::NOBODY &&
if (chess_.whoWin() != PLAYER_NOBODY &&
(manualListModel.data(manualListModel.index(currentRow - 1))).toString().contains("Time over.")) {
playSound(":/sound/resources/sound/win.wav");
}
@ -891,8 +891,8 @@ bool GameController::command(const QString &cmd, bool update /* = true */)
// AI设置
if (&chess_ == &(this->chess_)) {
// 如果还未决出胜负
if (chess_.whoWin() == MillGame::NOBODY) {
if (chess_.whosTurn() == MillGame::PLAYER1) {
if (chess_.whoWin() == PLAYER_NOBODY) {
if (chess_.whosTurn() == PLAYER1) {
if (isAiPlayer1) {
ai1.resume();
}
@ -1004,9 +1004,9 @@ bool GameController::updateScence(MillGame &chess)
int j;
// 遍历棋盘,查找并放置棋盘上的棋子
for (j = MillGame::POS_BEGIN; j < MillGame::POS_END; j++) {
for (j = Board::POS_BEGIN; j < Board::POS_END; j++) {
if (board[j] == key) {
pos = scene.rs2pos(j / MillGame::N_SEATS, j % MillGame::N_SEATS + 1);
pos = scene.rs2pos(j / Board::N_SEATS, j % Board::N_SEATS + 1);
if (piece->pos() != pos) {
// 让移动的棋子位于顶层
@ -1028,7 +1028,7 @@ bool GameController::updateScence(MillGame &chess)
}
// 如果没有找到,放置棋盘外的棋子
if (j == (MillGame::N_SEATS) * (MillGame::N_RINGS + 1)) {
if (j == (Board::N_SEATS) * (Board::N_RINGS + 1)) {
// 判断是被吃掉的子,还是未安放的子
if (key & 0x10) {
pos = (key - 0x11 < nTotalPieces / 2 - chess.getPiecesInHandCount_1()) ?
@ -1043,7 +1043,7 @@ bool GameController::updateScence(MillGame &chess)
deletedPiece = piece;
#ifdef GAME_PLACING_SHOW_CAPTURED_PIECES
if (chess.getStage() == MillGame::GAME_MOVING) {
if (chess.getStage() == GAME_MOVING) {
#endif
QPropertyAnimation *animation = new QPropertyAnimation(piece, "pos");
animation->setDuration(durationTime);
@ -1061,10 +1061,10 @@ bool GameController::updateScence(MillGame &chess)
}
// 添加摆棋阶段禁子点
if (chess.getRule()->hasForbiddenPoint && chess.getStage() == MillGame::GAME_PLACING) {
for (int j = MillGame::POS_BEGIN; j < MillGame::POS_END; j++) {
if (chess.getRule()->hasForbiddenPoint && chess.getStage() == GAME_PLACING) {
for (int j = Board::POS_BEGIN; j < Board::POS_END; j++) {
if (board[j] == 0x0F) {
pos = scene.rs2pos(j / MillGame::N_SEATS, j % MillGame::N_SEATS + 1);
pos = scene.rs2pos(j / Board::N_SEATS, j % Board::N_SEATS + 1);
if (nTotalPieces < pieceList.size()) {
pieceList.at(nTotalPieces++)->setPos(pos);
} else {
@ -1080,7 +1080,7 @@ bool GameController::updateScence(MillGame &chess)
}
// 走棋阶段清除禁子点
if (chess.getRule()->hasForbiddenPoint && chess.getStage() != MillGame::GAME_PLACING) {
if (chess.getRule()->hasForbiddenPoint && chess.getStage() != GAME_PLACING) {
while (nTotalPieces < pieceList.size()) {
delete pieceList.at(nTotalPieces);
pieceList.removeAt(nTotalPieces);

View File

@ -100,8 +100,8 @@ public:
return &manualListModel;
}
void setAiDepthTime(MillGameAi_ab::depth_t depth1, int time1, MillGameAi_ab::depth_t depth2, int time2);
void getAiDepthTime(MillGameAi_ab::depth_t &depth1, int &time1, MillGameAi_ab::depth_t &depth2, int &time2);
void setAiDepthTime(depth_t depth1, int time1, depth_t depth2, int time2);
void getAiDepthTime(depth_t &depth1, int &time1, depth_t &depth2, int &time2);
signals:
@ -126,7 +126,7 @@ signals:
public slots:
// 设置规则
void setRule(int ruleNo, MillGame::step_t stepLimited = UINT16_MAX, int timeLimited = -1);
void setRule(int ruleNo, step_t stepLimited = UINT16_MAX, int timeLimited = -1);
// 游戏开始
void gameStart();
@ -267,7 +267,7 @@ private:
int timeLimit;
// 规则限步数
MillGame::step_t stepsLimit;
step_t stepsLimit;
// 玩家1剩余时间
time_t remainingTime1;

View File

@ -383,14 +383,14 @@ void MillGameWindow::ruleInfo()
ui.labelRule->setText(tl + sl);
// 规则提示
ui.labelInfo->setToolTip(QString(MillGame::RULES[ruleNo].name) + "\n" +
MillGame::RULES[ruleNo].description);
ui.labelInfo->setToolTip(QString(RULES[ruleNo].name) + "\n" +
RULES[ruleNo].description);
ui.labelRule->setToolTip(ui.labelInfo->toolTip());
#if 0
QString tip_Rule = QString("%1\n%2").arg(tr(MillGame::RULES[ruleNo].name))
.arg(tr(MillGame::RULES[ruleNo].info));
QString tip_Rule = QString("%1\n%2").arg(tr(RULES[ruleNo].name))
.arg(tr(RULES[ruleNo].info));
#endif
}
@ -466,7 +466,7 @@ void MillGameWindow::on_actionLimited_T_triggered()
int dTime = comboBox_time->currentData().toInt();
if (gStep != dStep || gTime != dTime) {
// 重置游戏规则
game->setRule(ruleNo, static_cast<MillGame::step_t>(dStep), dTime);
game->setRule(ruleNo, static_cast<step_t>(dStep), dTime);
}
}
@ -922,7 +922,7 @@ void MillGameWindow::on_actionEngine_E_triggered()
connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
// 目前数据
MillGameAi_ab::depth_t depth1, depth2;
depth_t depth1, depth2;
int time1, time2;
game->getAiDepthTime(depth1, time1, depth2, time2);
spinBox_depth1->setValue(depth1);
@ -932,11 +932,11 @@ void MillGameWindow::on_actionEngine_E_triggered()
// 新设数据
if (dialog->exec() == QDialog::Accepted) {
MillGameAi_ab::depth_t depth1_new, depth2_new;
depth_t depth1_new, depth2_new;
int time1_new, time2_new;
depth1_new = static_cast<MillGameAi_ab::depth_t>(spinBox_depth1->value());
depth2_new = static_cast<MillGameAi_ab::depth_t>(spinBox_depth2->value());
depth1_new = static_cast<depth_t>(spinBox_depth1->value());
depth2_new = static_cast<depth_t>(spinBox_depth2->value());
time1_new = spinBox_time1->value();
time2_new = spinBox_time2->value();