diff --git a/NineChess/ninechess.pro.user b/NineChess/ninechess.pro.user index da6cd138..07833b77 100644 --- a/NineChess/ninechess.pro.user +++ b/NineChess/ninechess.pro.user @@ -1,10 +1,10 @@ - + EnvironmentId - {9a8c5f9d-1814-40d9-ab01-983c45f229c5} + {fba36f69-c7a3-4c3f-91d4-7c71ad79549c} ProjectExplorer.Project.ActiveTarget @@ -54,22 +54,19 @@ ProjectExplorer.Project.PluginSettings - - - true - + ProjectExplorer.Project.Target.0 - Desktop Qt 5.9.7 GCC 64bit - Desktop Qt 5.9.7 GCC 64bit - qt.qt5.597.gcc_64_kit + Desktop Qt 5.11.0 MSVC2017 64bit + Desktop Qt 5.11.0 MSVC2017 64bit + qt.qt5.5110.win64_msvc2017_64_kit 0 0 0 - /media/sniper/Work/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_9_7_GCC_64bit-Debug + E:/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_11_0_MSVC2017_64bit-Debug true @@ -87,10 +84,7 @@ Make Qt4ProjectManager.MakeStep - - -w - -r - + false @@ -106,10 +100,7 @@ Make Qt4ProjectManager.MakeStep - - -w - -r - + true clean @@ -129,7 +120,7 @@ true - /media/sniper/Work/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_9_7_GCC_64bit-Release + E:/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_11_0_MSVC2017_64bit-Release true @@ -147,10 +138,7 @@ Make Qt4ProjectManager.MakeStep - - -w - -r - + false @@ -166,10 +154,7 @@ Make Qt4ProjectManager.MakeStep - - -w - -r - + true clean @@ -189,7 +174,7 @@ true - /media/sniper/Work/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_9_7_GCC_64bit-Profile + E:/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_11_0_MSVC2017_64bit-Profile true @@ -207,10 +192,7 @@ Make Qt4ProjectManager.MakeStep - - -w - -r - + false @@ -226,10 +208,7 @@ Make Qt4ProjectManager.MakeStep - - -w - -r - + true clean @@ -257,7 +236,7 @@ ProjectExplorer.BuildSteps.Deploy 1 - Deploy Configuration + 部署设置 ProjectExplorer.DefaultDeployConfiguration @@ -307,12 +286,13 @@ ninechess - Qt4ProjectManager.Qt4RunConfiguration:/media/sniper/Work/My program/QT/NineChess/NineChess/ninechess.pro + Qt4ProjectManager.Qt4RunConfiguration:E:/My program/QT/NineChess/NineChess/ninechess.pro true ninechess.pro + false - /media/sniper/Work/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_9_7_GCC_64bit-Debug + E:/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_11_0_MSVC2017_64bit-Debug 3768 false true diff --git a/NineChess/src/aithread.cpp b/NineChess/src/aithread.cpp index 2078b34c..20dfebcf 100644 --- a/NineChess/src/aithread.cpp +++ b/NineChess/src/aithread.cpp @@ -16,8 +16,7 @@ AiThread::~AiThread() void AiThread::setAi(const NineChess &chess) { mutex.lock(); - this->chess = chess; - ai_ab.setChess(chess); + this->chess = &chess; mutex.unlock(); } @@ -26,7 +25,7 @@ void AiThread::run() // 测试用数据 int iTemp = 0; - while (true) { + forever{ if (isInterruptionRequested()) return; mutex.lock(); @@ -34,6 +33,12 @@ void AiThread::run() pauseCondition.wait(&mutex); mutex.unlock(); + ai_ab.setChess(*chess); + ai_ab.alphaBetaPruning(1); + const char * str = ai_ab.bestMove(); + qDebug() << str; + emit command(str); + // 测试用 qDebug() << "thread running " << iTemp << "ms"; msleep(250); diff --git a/NineChess/src/aithread.h b/NineChess/src/aithread.h index 48f581b6..db972cbf 100644 --- a/NineChess/src/aithread.h +++ b/NineChess/src/aithread.h @@ -36,8 +36,8 @@ private: // 等待条件,这里没用到,留着以后扩展用 QWaitCondition pauseCondition; - // 棋类 - NineChess chess; + // 主线程棋对象的引用 + const NineChess *chess; // Alpha-Beta剪枝算法类 NineChessAi_ab ai_ab; }; diff --git a/NineChess/src/gamecontroller.cpp b/NineChess/src/gamecontroller.cpp index 7e821cc7..2c4a45c8 100644 --- a/NineChess/src/gamecontroller.cpp +++ b/NineChess/src/gamecontroller.cpp @@ -149,7 +149,6 @@ void GameController::gameReset() currentRow = 0; manualListModel.insertRow(0); manualListModel.setData(manualListModel.index(0), chess.getCmdLine()); - // 发出信号通知主窗口更新LCD显示 QTime qtime = QTime(0, 0, 0, 0).addMSecs(time1); emit time1Changed(qtime.toString("mm:ss.zzz")); @@ -409,12 +408,9 @@ bool GameController::actionPiece(QPointF pos) if (chess.getAction() == NineChess::ACTION_PLACE) { result = placePiece(pos); }// 去子 - else if (chess.getAction() == NineChess::ACTION_REMOVE) { - result = removePiece(pos); + else if (chess.getAction() == NineChess::ACTION_CAPTURE) { + result = capturePiece(pos); } - // 如果完成后进入中局,则删除禁点 - //if (chess.getPhase() == NineChess::GAME_MID && chess.getRule()->hasForbidden) - // cleanForbidden(); break; case NineChess::GAME_MID: @@ -427,8 +423,8 @@ bool GameController::actionPiece(QPointF pos) // 如果移子不成功,尝试重新选子 result = movePiece(pos); }// 去子 - else if (chess.getAction() == NineChess::ACTION_REMOVE) { - result = removePiece(pos); + else if (chess.getAction() == NineChess::ACTION_CAPTURE) { + result = capturePiece(pos); } break; @@ -502,8 +498,12 @@ bool GameController::placePiece(QPointF pos) // 发信号更新状态栏 message = QString::fromStdString(chess.getTip()); emit statusBarChanged(message); - // 播放音效 - playSound(":/sound/resources/sound/drog.wav"); + // 播放成三音效 + if (chess.getAction() == NineChess::ACTION_CAPTURE) + playSound(":/sound/resources/sound/capture.wav"); + // 播放落下棋子音效 + else + playSound(":/sound/resources/sound/drog.wav"); return true; } @@ -523,8 +523,12 @@ bool GameController::movePiece(QPointF pos) // 发信号更新状态栏 message = QString::fromStdString(chess.getTip()); emit statusBarChanged(message); - // 播放音效 - playSound(":/sound/resources/sound/move.wav"); + // 播放成三音效 + if (chess.getAction() == NineChess::ACTION_CAPTURE) + playSound(":/sound/resources/sound/capture.wav"); + // 播放移动棋子音效 + else + playSound(":/sound/resources/sound/move.wav"); return true; } // 如果移子不成功,尝试重新选子 @@ -535,13 +539,13 @@ bool GameController::movePiece(QPointF pos) } // 去子 -bool GameController::removePiece(QPointF pos) +bool GameController::capturePiece(QPointF pos) { int c, p; if (!scene.pos2cp(pos, c, p)) { return false; } - if (!chess.remove(c, p)) { + if (!chess.capture(c, p)) { // 播放禁止音效 playSound(":/sound/resources/sound/forbidden.wav"); return false; @@ -699,5 +703,25 @@ bool GameController::updateScence(NineChess &chess) animationGroup->start(QAbstractAnimation::DeleteWhenStopped); + // AI设置 + if (&chess == &(this->chess)) { + // 如果还未决出胜负 + if (chess.whoWin() == NineChess::NOBODY) { + if (chess.whosTurn() == NineChess::PLAYER1) { + ai1.resume(); + ai2.pause(); + } + else { + ai1.pause(); + ai2.resume(); + } + } + // 如果已经决出胜负 + else { + ai1.pause(); + ai1.pause(); + } + } + return true; } diff --git a/NineChess/src/gamecontroller.h b/NineChess/src/gamecontroller.h index 3bdc748d..e3281d68 100644 --- a/NineChess/src/gamecontroller.h +++ b/NineChess/src/gamecontroller.h @@ -90,7 +90,7 @@ protected: // 移动旧子 bool movePiece(QPointF pos); // 去子 - bool removePiece(QPointF pos); + bool capturePiece(QPointF pos); private: // 棋对象的数据模型 diff --git a/NineChess/src/ninechess.cpp b/NineChess/src/ninechess.cpp index 3bb8bb3b..24133835 100644 --- a/NineChess/src/ninechess.cpp +++ b/NineChess/src/ninechess.cpp @@ -3,6 +3,7 @@ ** Mail: liuweilhy@163.com ** This file is part of the NineChess game. ****************************************************************************/ + #if _MSC_VER >= 1600 #pragma execution_character_set("utf-8") #endif @@ -200,8 +201,8 @@ bool NineChess::setData(const struct Rule *rule, int s, int t, int step, int fla data.action = ACTION_CHOOSE; else if (flags & ACTION_PLACE) data.action = ACTION_PLACE; - else if (flags & ACTION_REMOVE) - data.action = ACTION_REMOVE; + else if (flags & ACTION_CAPTURE) + data.action = ACTION_CAPTURE; else return false; @@ -236,7 +237,7 @@ bool NineChess::setData(const struct Rule *rule, int s, int t, int step, int fla data.player2_InHand = p2_InHand < data.player2_InHand ? p2_InHand : data.player2_InHand; // 设置去子状态时的剩余尚待去除子数 - if (flags & ACTION_REMOVE) { + if (flags & ACTION_CAPTURE) { if (num_NeedRemove >= 0 && num_NeedRemove < 3) data.num_NeedRemove = num_NeedRemove; } @@ -398,7 +399,7 @@ bool NineChess::reset() winner = NOBODY; // 当前棋局(3×8) - memset(board, 0, sizeof(board)); + memset(board, 0, sizeof(data.board)); // 盘面子数归零 data.player1_Remain = data.player2_Remain = 0; @@ -611,7 +612,7 @@ bool NineChess::place(int c, int p, long time_p /* = -1*/) // 设置去子数目 data.num_NeedRemove = rule.removeMore ? n : 1; // 进入去子状态 - data.action = ACTION_REMOVE; + data.action = ACTION_CAPTURE; } setTip(); return true; @@ -660,7 +661,7 @@ bool NineChess::place(int c, int p, long time_p /* = -1*/) // 设置去子数目 data.num_NeedRemove = rule.removeMore ? n : 1; // 进入去子状态 - data.action = ACTION_REMOVE; + data.action = ACTION_CAPTURE; setTip(); } setTip(); @@ -670,13 +671,13 @@ bool NineChess::place(int c, int p, long time_p /* = -1*/) return false; } -bool NineChess::remove(int c, int p, long time_p /* = -1*/) +bool NineChess::capture(int c, int p, long time_p /* = -1*/) { // 如果局面为"未开局"或“结局”,返回false if (data.phase == GAME_NOTSTARTED || data.phase == GAME_OVER) return false; // 如非“去子”状态,返回false - if (data.action != ACTION_REMOVE) + if (data.action != ACTION_CAPTURE) return false; // 如果去子完成,返回false if (data.num_NeedRemove <= 0) @@ -685,11 +686,9 @@ bool NineChess::remove(int c, int p, long time_p /* = -1*/) long player_ms = -1; int pos = cp2pos(c, p); // 对手 - enum Players opponent = PLAYER2; - if (data.turn == PLAYER2) - opponent = PLAYER1; - // 判断去子不是对手棋 - if (getWhosPiece(c, p) != opponent) + char opponent = data.turn == PLAYER1 ? 0x20 : 0x10; + // 判断去子是不是对手棋 + if (!(opponent & board[pos])) return false; // 如果当前子是否处于“三连”之中,且对方还未全部处于“三连”之中 @@ -810,6 +809,267 @@ bool NineChess::choose(int c, int p) return false; } +bool NineChess::place(int pos) +{ + // 如果局面为“结局”,返回false + if (data.phase == GAME_OVER) + return false; + // 如果局面为“未开局”,则开具 + if (data.phase == GAME_NOTSTARTED) + start(); + + // 如非“落子”状态,返回false + if (data.action != ACTION_PLACE) + return false; + // 如果落子位置在棋盘外、已有子点或禁点,返回false + if (!inBoard[pos] || board[pos]) + return false; + + // 对于开局落子 + char piece = '\x00'; + int n = 0; + if (data.phase == GAME_OPENING) { + // 先手下 + if (data.turn == PLAYER1) + { + piece = '\x11' + rule.numOfChess - data.player1_InHand; + data.player1_InHand--; + data.player1_Remain++; + } + // 后手下 + else + { + piece = '\x21' + rule.numOfChess - data.player2_InHand; + data.player2_InHand--; + data.player2_Remain++; + } + board[pos] = piece; + move_ = pos; + currentPos = pos; + data.step++; + // 如果决出胜负 + if (win()) { + setTip(); + return true; + } + + n = addMills(currentPos); + // 开局阶段未成三 + if (n == 0) { + // 如果双方都无未放置的棋子 + if (data.player1_InHand == 0 && data.player2_InHand == 0) { + // 进入中局阶段 + data.phase = GAME_MID; + // 进入选子状态 + data.action = ACTION_CHOOSE; + // 清除禁点 + cleanForbidden(); + // 设置轮到谁走 + if (rule.isDefensiveMoveFirst) { + data.turn = PLAYER2; + } + else { + data.turn = PLAYER1; + } + + // 再决胜负 + if (win()) { + setTip(); + return true; + } + } + // 如果双方还有子 + else { + // 设置轮到谁走 + changeTurn(); + } + } + // 如果成三 + else { + // 设置去子数目 + data.num_NeedRemove = rule.removeMore ? n : 1; + // 进入去子状态 + data.action = ACTION_CAPTURE; + } + setTip(); + return true; + } + + // 对于中局落子 + else if (data.phase == GAME_MID) { + // 如果落子不合法 + if ((data.turn == PLAYER1 && (data.player1_Remain > rule.numAtLest || !rule.canFly)) || + (data.turn == PLAYER2 && (data.player2_Remain > rule.numAtLest || !rule.canFly))) { + int i; + for (i = 0; i < 4; i++) { + if (pos == moveTable[currentPos][i]) + break; + } + // 不在招法表中 + if (i == 4) + return false; + } + // 移子 + move_ = currentPos << 8 + pos; + board[pos] = board[currentPos]; + board[currentPos] = '\x00'; + currentPos = pos; + data.step++; + n = addMills(currentPos); + + // 中局阶段未成三 + if (n == 0) { + // 进入选子状态 + data.action = ACTION_CHOOSE; + // 设置轮到谁走 + changeTurn(); + // 如果决出胜负 + if (win()) { + setTip(); + return true; + } + } + // 中局阶段成三 + else { + // 设置去子数目 + data.num_NeedRemove = rule.removeMore ? n : 1; + // 进入去子状态 + data.action = ACTION_CAPTURE; + setTip(); + } + setTip(); + return true; + } + + return false; +} + +bool NineChess::capture(int pos) +{ + // 如果局面为"未开局"或“结局”,返回false + if (data.phase == GAME_NOTSTARTED || data.phase == GAME_OVER) + return false; + // 如非“去子”状态,返回false + if (data.action != ACTION_CAPTURE) + return false; + // 如果去子完成,返回false + if (data.num_NeedRemove <= 0) + return false; + // 对手 + char opponent = data.turn == PLAYER1 ? 0x20 : 0x10; + // 判断去子是不是对手棋 + if (!(opponent & board[pos])) + return false; + + // 如果当前子是否处于“三连”之中,且对方还未全部处于“三连”之中 + if (isInMills(pos) && !isAllInMills(opponent)) { + return false; + } + + // 去子(设置禁点) + if (rule.hasForbidden && data.phase == GAME_OPENING) + board[pos] = '\x0f'; + else // 去子 + board[pos] = '\x00'; + if (data.turn == PLAYER1) + data.player2_Remain--; + else if (data.turn == PLAYER2) + data.player1_Remain--; + move_ = -pos; + currentPos = 0; + data.num_NeedRemove--; + data.step++; + // 去子完成 + + // 如果决出胜负 + if (win()) { + setTip(); + return true; + } + // 还有其余的子要去吗 + if (data.num_NeedRemove > 0) { + // 继续去子 + return true; + } + // 所有去子都完成了 + else { + // 开局阶段 + if (data.phase == GAME_OPENING) { + // 如果双方都无未放置的棋子 + if (data.player1_InHand == 0 && data.player2_InHand == 0) { + // 进入中局阶段 + data.phase = GAME_MID; + // 进入选子状态 + data.action = ACTION_CHOOSE; + // 清除禁点 + cleanForbidden(); + // 设置轮到谁走 + if (rule.isDefensiveMoveFirst) { + data.turn = PLAYER2; + } + else { + data.turn = PLAYER1; + } + // 再决胜负 + if (win()) { + setTip(); + return true; + } + } + // 如果双方还有子 + else { + // 进入落子状态 + data.action = ACTION_PLACE; + // 设置轮到谁走 + changeTurn(); + // 如果决出胜负 + if (win()) { + setTip(); + return true; + } + } + } + // 中局阶段 + else { + // 进入选子状态 + data.action = ACTION_CHOOSE; + // 设置轮到谁走 + changeTurn(); + // 如果决出胜负 + if (win()) { + setTip(); + return true; + } + } + } + setTip(); + return true; +} + +bool NineChess::choose(int pos) +{ + // 如果局面不是"中局”,返回false + if (data.phase != GAME_MID) + return false; + // 如非“选子”或“落子”状态,返回false + if (data.action != ACTION_CHOOSE && data.action != ACTION_PLACE) + return false; + char t = data.turn == PLAYER1 ? 0x10 : 0x20; + // 判断选子是否可选 + if (board[pos] & t) { + // 判断pos处的棋子是否被“闷” + if (isSurrounded(pos)) { + return false; + } + // 选子 + currentPos = pos; + // 选子完成,进入落子状态 + data.action = ACTION_PLACE; + return true; + } + return false; +} + bool NineChess::giveup(Players loser) { if (data.phase == GAME_MID || data.phase == GAME_OPENING) @@ -872,7 +1132,7 @@ bool NineChess::command(const char *cmd) if (mm >= 0 && ss >= 0 && mss >= 0) tm = mm * 60000 + ss * 1000 + mss; } - return remove(c1, p1, tm); + return capture(c1, p1, tm); } // 落子 @@ -900,6 +1160,21 @@ bool NineChess::command(const char *cmd) return false; } +bool NineChess::command(int16_t move) +{ + if (move < 0) { + return capture(-move); + } + else if (move & 0x00ff) { + return place(move & 0x00ff); + } + else { + if (choose(move >> 8)) + return place(move & 0x00ff); + } + return false; +} + inline long NineChess::update(long time_p /*= -1*/) { long ret = -1; @@ -1079,7 +1354,7 @@ int NineChess::addMills(int pos) //0x 00 00 00 00 00 00 00 00 // unused unused piece1 pos1 piece2 pos2 piece3 pos3 //piece1、piece2、piece3按照序号从小到大顺序排放 - long long mill = 0; + uint64_t mill = 0; int n = 0; int p[3], min, temp; char m = board[pos] & '\x30'; @@ -1104,12 +1379,12 @@ int NineChess::addMills(int pos) } } // 成三 - mill = (((long long)board[p[0]]) << 40) - + (((long long)p[0]) << 32) - + (((long long)board[p[1]]) << 24) - + (((long long)p[1]) << 16) - + (((long long)board[p[2]]) << 8) - + (long long)p[2]; + mill = (((uint64_t)board[p[0]]) << 40) + + (((uint64_t)p[0]) << 32) + + (((uint64_t)board[p[1]]) << 24) + + (((uint64_t)p[1]) << 16) + + (((uint64_t)board[p[2]]) << 8) + + (uint64_t)p[2]; // 如果允许相同三连反复去子 if (rule.canRepeated) { @@ -1119,7 +1394,7 @@ int NineChess::addMills(int pos) else { // 迭代器 - list::iterator itor; + list::iterator itor; // 遍历 for (itor = data.millList.begin(); itor != data.millList.end(); itor++) { @@ -1137,6 +1412,17 @@ int NineChess::addMills(int pos) return n; } +bool NineChess::isAllInMills(char ch) +{ + for (int i = SEAT; i < SEAT * (RING+1); i++) + if (board[i] & ch) { + if (!isInMills(i)) { + return false; + } + } + return true; +} + bool NineChess::isAllInMills(enum Players player) { char ch = '\x00'; @@ -1146,15 +1432,7 @@ bool NineChess::isAllInMills(enum Players player) ch = '\x20'; else return true; - for (int i = 1; i <= RING; i++) - for (int j = 0; j < SEAT; j++) { - if (board[i*SEAT + j] & ch) { - if (!isInMills(i*SEAT + j)) { - return false; - } - } - } - return true; + return isAllInMills(ch); } // 判断玩家的棋子是否被围 @@ -1178,14 +1456,8 @@ bool NineChess::isSurrounded(int pos) return false; } -// 判断玩家的棋子是否全部被围 -bool NineChess::isAllSurrounded(enum Players ply) +bool NineChess::isAllSurrounded(char ch) { - char t = '\x30'; - if (ply == PLAYER1) - t &= '\x10'; - else if (ply == PLAYER2) - t &= '\x20'; // 如果摆满 if (data.player1_Remain + data.player2_Remain >= SEAT * RING) return true; @@ -1196,23 +1468,30 @@ bool NineChess::isAllSurrounded(enum Players ply) return false; } // 查询整个棋盘 - for (int i = 1; i <= RING; i++) - { - for (int j = 0; j < SEAT; j++) - { - int movePos; - if (t & board[i*SEAT + j]) { - for (int k = 0; k < 4; k++) { - movePos = moveTable[i*SEAT + j][k]; - if (movePos && !board[movePos]) - return false; - } + char movePos; + for (int i = 1; i < SEAT * (RING + 1); i++) { + if (ch & board[i]) { + for (int k = 0; k < 4; k++) { + movePos = moveTable[i][k]; + if (movePos && !board[movePos]) + return false; } } } return true; } +// 判断玩家的棋子是否全部被围 +bool NineChess::isAllSurrounded(enum Players ply) +{ + char t = '\x30'; + if (ply == PLAYER1) + t &= '\x10'; + else if (ply == PLAYER2) + t &= '\x20'; + return isAllSurrounded(t); +} + void NineChess::cleanForbidden() { for (int i = 1; i <= RING; i++) @@ -1245,7 +1524,7 @@ void NineChess::setTip() tip = "轮到玩家2落子,剩余" + std::to_string(data.player2_InHand) + "子"; } } - else if (data.action == ACTION_REMOVE) { + else if (data.action == ACTION_CAPTURE) { if (data.turn == PLAYER1) { tip = "轮到玩家1去子,需去" + std::to_string(data.num_NeedRemove) + "子"; } @@ -1263,7 +1542,7 @@ void NineChess::setTip() tip = "轮到玩家2选子移动"; } } - else if (data.action == ACTION_REMOVE) { + else if (data.action == ACTION_CAPTURE) { if (data.turn == PLAYER1) { tip = "轮到玩家1去子,需去" + std::to_string(data.num_NeedRemove) + "子"; } diff --git a/NineChess/src/ninechess.h b/NineChess/src/ninechess.h index 4a9bd8ce..42886a99 100644 --- a/NineChess/src/ninechess.h +++ b/NineChess/src/ninechess.h @@ -20,6 +20,8 @@ using std::list; // 所以不能跨线程修改NineChess类的静态成员变量,切记! class NineChess { + // AI友元类 + friend class NineChessAi_ab; public: // 5个静态成员常量 // 3圈,禁止修改! @@ -65,7 +67,7 @@ public: static const struct Rule RULES[RULENUM]; // 局面阶段标识 - enum Phases { + enum Phases : uint16_t { GAME_NOTSTARTED = 0x0001, // 未开局 GAME_OPENING = 0x0002, // 开局(摆棋) GAME_MID = 0x0004, // 中局(走棋) @@ -73,7 +75,7 @@ public: }; // 玩家标识,轮流状态,胜负标识 - enum Players { + enum Players : uint16_t { PLAYER1 = 0x0010, // 玩家1 PLAYER2 = 0x0020, // 玩家2 DRAW = 0x0040, // 双方和棋 @@ -81,10 +83,10 @@ public: }; // 动作状态标识 - enum Actions { + enum Actions : uint16_t { ACTION_CHOOSE = 0x0100, // 选子 ACTION_PLACE = 0x0200, // 落子 - ACTION_REMOVE = 0x0400 // 提子 + ACTION_CAPTURE = 0x0400 // 提子 }; // 棋局结构体,包含当前棋盘数据 @@ -109,15 +111,15 @@ public: enum NineChess::Actions action; // 玩家1剩余未放置子数 - char player1_InHand; + int8_t player1_InHand; // 玩家2剩余未放置子数 - char player2_InHand; + int8_t player2_InHand; // 玩家1盘面剩余子数 - char player1_Remain; + int8_t player1_Remain; // 玩家1盘面剩余子数 - char player2_Remain; + int8_t player2_Remain; // 尚待去除的子数 - char num_NeedRemove; + int8_t num_NeedRemove; /* 本打算用如下的结构体来表示“三连” struct Mill { @@ -133,7 +135,7 @@ public: unused unused piece1 pos1 piece2 pos2 piece3 pos3 */ // “三连列表” - list millList; + list millList; }; private: @@ -225,20 +227,27 @@ public: // 落子,在第c圈第p个位置,为迎合日常,c和p下标都从1开始 bool place(int c, int p, long time_p = -1); // 去子,在第c圈第p个位置,为迎合日常,c和p下标都从1开始 - bool remove(int c, int p, long time_p = -1); - // 认输 + bool capture(int c, int p, long time_p = -1); + // 下面3个函数没有算法无关判断和无关操作 + bool choose(int pos); + bool place(int pos); + bool capture(int pos); + // 认输 bool giveup(Players loser); // 命令行解析函数 + bool command(int16_t move); bool command(const char *cmd); protected: // 判断棋盘pos处的棋子处于几个“三连”中 int isInMills(int pos); // 判断玩家的所有棋子是否都处于“三连”状态 + bool isAllInMills(char ch); bool isAllInMills(enum Players); // 判断玩家的棋子是否被围 bool isSurrounded(int pos); // 判断玩家的棋子是否全部被围 + bool isAllSurrounded(char ch); bool isAllSurrounded(enum Players); // 三连加入列表 int addMills(int pos); @@ -285,7 +294,7 @@ private: 移子:0x__??,__为移动前的位置,??为移动后的位置 去子:0xFF??,??取位置补码,即为负数 */ - short move_; + int16_t move_; // 招法命令行用于棋谱的显示和解析 // 当前招法的命令行指令,即一招棋谱 diff --git a/NineChess/src/ninechessai_ab.cpp b/NineChess/src/ninechessai_ab.cpp index f7ca8e46..ed03e137 100644 --- a/NineChess/src/ninechessai_ab.cpp +++ b/NineChess/src/ninechessai_ab.cpp @@ -1,4 +1,10 @@ -#include "ninechessai_ab.h" +/**************************************************************************** +** by liuweilhy, 2018.11.29 +** Mail: liuweilhy@163.com +** This file is part of the NineChess game. +****************************************************************************/ + +#include "ninechessai_ab.h" #include #include @@ -9,7 +15,7 @@ depth(3) // 默认3层深度 { rootNode = new Node; rootNode->value = 0; - rootNode->move_ = 0; + rootNode->move = 0; rootNode->parent = nullptr; } @@ -20,8 +26,89 @@ NineChessAi_ab::~NineChessAi_ab() void NineChessAi_ab::buildChildren(Node *node) { + char opponent; // 列出所有合法的下一招 - ; + switch (chessTemp.data.action) + { + case NineChess::ACTION_CHOOSE: + case NineChess::ACTION_PLACE: + // 对于开局落子 + if ((chessTemp.data.phase) & (NineChess::GAME_OPENING | NineChess::GAME_NOTSTARTED)) { + for (int i = NineChess::SEAT; i < (NineChess::RING + 1)*NineChess::SEAT; i++) { + if (!chessTemp.board[i]) { + Node * newNode = new Node; + newNode->parent = node; + newNode->value = 0; + newNode->move = i; + node->children.push_back(newNode); + } + } + } + // 对于中局移子 + else { + char newPos; + for (int i = NineChess::SEAT; i < (NineChess::RING + 1)*NineChess::SEAT; i++) { + if (!chessTemp.choose(i)) + break; + if ((chessTemp.data.turn == NineChess::PLAYER1 && (chessTemp.data.player1_Remain > chessTemp.rule.numAtLest || !chessTemp.rule.canFly)) || + (chessTemp.data.turn == NineChess::PLAYER2 && (chessTemp.data.player2_Remain > chessTemp.rule.numAtLest || !chessTemp.rule.canFly))) { + for (int j = 0; j < 4; j++) { + if (newPos == chessTemp.moveTable[i][j]) { + Node * newNode = new Node; + newNode->parent = node; + newNode->value = 0; + newNode->move = i << 8 + newPos; + node->children.push_back(newNode); + } + } + } + else { + for (int j = NineChess::SEAT; j < (NineChess::RING + 1)*NineChess::SEAT; j++) { + if (newPos & chessTemp.board[j]) + break; + Node * newNode = new Node; + newNode->parent = node; + newNode->value = 0; + newNode->move = i << 8 + j; + node->children.push_back(newNode); + } + } + } + } + break; + + case NineChess::ACTION_CAPTURE: + opponent = chessTemp.data.turn == NineChess::PLAYER1 ? 0x20 : 0x10; + // 全成三的情况 + if (chessTemp.isAllInMills(opponent)) { + for (int i = NineChess::SEAT; i < (NineChess::RING + 1)*NineChess::SEAT; i++) { + if (chessTemp.board[i] & opponent) { + Node * newNode = new Node; + newNode->parent = node; + newNode->value = 0; + newNode->move = -i; + node->children.push_back(newNode); + } + } + } + else { + for (int i = NineChess::SEAT; i < (NineChess::RING + 1)*NineChess::SEAT; i++) { + if (chessTemp.board[i] & opponent) { + if (chessTemp.isInMills(i)) + break; + Node * newNode = new Node; + newNode->parent = node; + newNode->value = 0; + newNode->move = -i; + node->children.push_back(newNode); + } + } + } + break; + + default: + break; + } } void NineChessAi_ab::sortChildren(Node *node) @@ -51,27 +138,95 @@ void NineChessAi_ab::setChess(const NineChess &chess) { this->chess = chess; chessTemp = chess; + chessData = &(chessTemp.data); requiredQuit = false; + // 生成棋子价值表 + for (int j = 0; j < NineChess::SEAT; j++) + { + // 对于0、2、4、6位(偶数位) + if (!(j & 1)) { + boardScore[1 * NineChess::SEAT + j] = 80; + boardScore[2 * NineChess::SEAT + j] = 100; + boardScore[3 * NineChess::SEAT + j] = 80; + } + // 对于有斜线情况下的1、3、5、7位(奇数位) + else if(chessTemp.rule.hasObliqueLine) { + boardScore[1 * NineChess::SEAT + j] = 70; + boardScore[2 * NineChess::SEAT + j] = 90; + boardScore[3 * NineChess::SEAT + j] = 70; + } + // 对于无斜线情况下的1、3、5、7位(奇数位) + else { + boardScore[1 * NineChess::SEAT + j] = 60; + boardScore[2 * NineChess::SEAT + j] = 80; + boardScore[3 * NineChess::SEAT + j] = 60; + } + } } int NineChessAi_ab::evaluate(Node *node) { // 初始评估值为0,对先手有利则增大,对后手有利则减小 int value = 0; + switch (chessData->phase) + { + case NineChess::GAME_NOTSTARTED: + break; + // 开局和中局阶段用同样的评价方法 + case NineChess::GAME_OPENING: + case NineChess::GAME_MID: + // 按手棋数目计分,每子50分 + value += (chessData->player1_InHand) * 50 - (chessData->player2_InHand) * 50; + // 按场上棋子计分 + for (int i = 1*NineChess::SEAT; i < NineChess::SEAT*(NineChess::RING+1); i++) { + if (chessData->board[i] & 0x10) + value += boardScore[i]; + else if (chessData->board[i] & 0x20) + value -= boardScore[i]; + } + switch (chessData->action) + { + // 选子和落子使用相同的评价方法 + case NineChess::ACTION_CHOOSE: + case NineChess::ACTION_PLACE: + break; + // 如果形成去子状态,每有一个可去的子,算100分 + case NineChess::ACTION_CAPTURE: + value += (chessData->turn == NineChess::PLAYER1) ? chessData->num_NeedRemove * 100 : -chessData->num_NeedRemove * 100; + break; + default: + break; + } + break; + // 终局评价最简单 + case NineChess::GAME_OVER: + if (chessData->player1_Remain < chessTemp.rule.numAtLest) + value = -infinity; + else if (chessData->player2_Remain < chessTemp.rule.numAtLest) + value = infinity; + break; + default: + break; + } // 赋值返回 node->value = value; return value; } +int NineChessAi_ab::alphaBetaPruning(int depth) +{ + return alphaBetaPruning(depth, -infinity, infinity, rootNode); +} + int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node) { // 评价值 int value; - if (!depth || !(node->children.size())) { + if (!depth || chessTemp.data.phase == NineChess::GAME_OVER) { node->value = evaluate(node); return node->value; } @@ -85,7 +240,11 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node) // 对先手,搜索Max if (chessTemp.whosTurn() == NineChess::PLAYER1) { for (auto child : node->children) { + hashTable.push_back({chessTemp.data, 0, depth}); + chessTemp.command(child->move); value = alphaBetaPruning(depth - 1, alpha, beta, child); + chessTemp.data = hashTable.back().data; + hashTable.pop_back(); // 取最大值 if (value > alpha) alpha = value; @@ -100,7 +259,11 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node) // 对后手,搜索Min else { for (auto child : node->children) { + hashTable.push_back({ chessTemp.data, 0, depth }); + chessTemp.command(child->move); value = alphaBetaPruning(depth - 1, alpha, beta, child); + chessTemp.data = hashTable.back().data; + hashTable.pop_back(); // 取最小值 if (value < beta) beta = value; @@ -116,6 +279,45 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node) return node->value; } +const char *NineChessAi_ab::bestMove() +{ + if ((rootNode->children).size() == 0) + return "error!"; + // 在最好的招法中随机挑一个 + int16_t moves[12] = {0}; + int n = 0; + for (auto child : rootNode->children) { + if (child->value == rootNode->value) + moves[n++] = child->move; + if (n >= 12) + break; + } + srand((unsigned)time(0)); + int i = rand() % 12; + return move2string(moves[i]); +} + +const char *NineChessAi_ab::move2string(int16_t move) +{ + int c, p; + if (move < 0) { + chessTemp.pos2cp(-move, c, p); + sprintf(cmdline, "-(%1u,%1u)", c, p); + } + else if (move & 0x00ff) { + chessTemp.pos2cp(move & 0x00ff, c, p); + sprintf(cmdline, "(%1u,%1u)", c, p); + } + else { + int c1, p1; + chessTemp.pos2cp(move >> 8, c1, p1); + chessTemp.pos2cp(move & 0x00ff, c, p); + sprintf(cmdline, "(%1u,%1u)->(%1u,%1u)", c1, p1, c, p); + } + return cmdline; +} + + void NineChessAi_ab::reverse(const NineChess *node1, NineChess *node2, int i) { @@ -131,12 +333,13 @@ void NineChessAi_ab::rotate(const NineChess *node1, NineChess *node2, int i) } -bool NineChessAi_ab::isInCache(Node * node, int &value) +bool NineChessAi_ab::isInHash(const Node *node) { -/* NineChess tempData; + /* + NineChess tempData; for (int i = 0; i < 2; i++) { reverse(node, &tempData, i); - for (int j = 0; j < 6; j++) { + for (int j = 0; j < 2; j++) { turn(node, &tempData, j); int n = chess.rule.hasX ? 8 : 4; for (int k = 0; k < n; k++) { diff --git a/NineChess/src/ninechessai_ab.h b/NineChess/src/ninechessai_ab.h index 397266e3..4921be08 100644 --- a/NineChess/src/ninechessai_ab.h +++ b/NineChess/src/ninechessai_ab.h @@ -1,4 +1,10 @@ -#ifndef NINECHESSAI_AB +/**************************************************************************** +** by liuweilhy, 2018.11.29 +** Mail: liuweilhy@163.com +** This file is part of the NineChess game. +****************************************************************************/ + +#ifndef NINECHESSAI_AB #define NINECHESSAI_AB #include "ninechess.h" @@ -6,15 +12,24 @@ // 注意:NineChess类不是线程安全的! // 所以不能在ai类中修改NineChess类的静态成员变量,切记! - +// 另外,AI类是NineChess类的友元类,可以访问其私有变量 +// 尽量不要使用NineChess的操作函数,因为有参数安全性检测和不必要的赋值,影响效率 class NineChessAi_ab { public: + // 定义哈希表 + struct Hash{ + NineChess::ChessData data; + uint64_t value; + int depth; + }; + // 定义一个节点结构体 - struct Node{ + struct Node { + uint64_t hash; // 当前局面的哈希值 int value; // 节点的值 - short move_; // 招法的命令行指令,图上标示为节点前的连线 + int16_t move; // 招法的命令行指令,图上标示为节点前的连线 struct Node * parent; // 父节点 list children; // 子节点列表 }; @@ -26,6 +41,10 @@ public: void setChess(const NineChess &chess); void setDepth(int depth) { this->depth = depth; } void quit() { requiredQuit = true; } + // Alpha-Beta剪枝算法 + int alphaBetaPruning(int depth); + // 返回最佳走法的命令行 + const char *bestMove(); protected: // 建立子节点 @@ -38,6 +57,8 @@ protected: int evaluate(Node *node); // Alpha-Beta剪枝算法 int alphaBetaPruning(int depth, int alpha, int beta, Node *node); + // 返回招法的命令行 + const char *move2string(int16_t move); // 局面逆序 void reverse(const NineChess *node1, NineChess *node2, int i); @@ -45,28 +66,36 @@ protected: void turn(const NineChess *node1, NineChess *node2, int i); // 局面旋转 void rotate(const NineChess *node1, NineChess *node2, int i); - // 判断是否在缓存中 - bool isInCache(Node * node, int &value); + + // 判断是否在哈希表中 + bool isInHash(const Node *node); private: // 原始模型 NineChess chess; // 演算用的模型 NineChess chessTemp; + NineChess::ChessData *chessData; // 根节点 Node * rootNode; - // 局面数据缓存区 - list dataCache; - // 局面数据缓存区最大大小 - size_t cacheMaxSize; + // 局面数据哈希表 + list hashTable; + // 哈希表最大大小 + size_t hashTableMaxSize; // 标识,用于跳出剪枝算法,立即返回 bool requiredQuit; // 剪枝算法的层深 int depth; // 定义极大值,等于32位有符号整形数字的最大值 - static const int infinity = 0x7fffffff; + static const int infinity = INT32_MAX; + +private: + // 棋子价值表 + char boardScore[(NineChess::RING + 2)*NineChess::SEAT]; + // 命令行 + char cmdline[32]; }; #endif diff --git a/范例棋谱.txt b/范例棋谱.txt index 28889a89..75c58279 100644 --- a/范例棋谱.txt +++ b/范例棋谱.txt @@ -1,50 +1,69 @@ r3 s000 t00 (3,1) 00:00.000 -(2,1) 00:01.390 -(2,3) 00:01.701 -(2,4) 00:03.027 -(3,2) 00:04.404 -(2,2) 00:04.111 -(3,8) 00:06.017 --(2,1) 00:08.355 -(2,8) 00:09.284 -(1,1) 00:10.028 -(2,1) 00:10.625 --(1,1) 00:11.837 -(1,4) 00:14.877 -(1,3) 00:18.100 -(3,3) 00:17.331 -(3,4) 00:19.044 -(3,5) 00:19.068 -(2,5) 00:21.019 -(1,5) 00:20.360 -(2,7) 00:24.118 -(1,5)->(1,6) 00:22.927 -(2,5)->(2,6) 00:25.459 --(3,5) 00:28.499 -(1,6)->(1,5) 00:25.352 -(2,6)->(2,5) 00:30.155 -(1,5)->(1,6) 00:26.503 -(2,7)->(2,6) 00:31.620 --(1,6) 00:32.290 -(1,4)->(1,5) 00:28.604 -(2,8)->(2,7) 00:34.556 -(3,8)->(3,7) 00:30.648 -(2,1)->(2,8) 00:36.458 --(3,1) 00:39.850 -(3,2)->(3,1) 00:34.675 -(2,5)->(3,5) 00:46.946 -(1,5)->(1,4) 00:36.227 -(2,6)->(2,5) 00:48.337 -(3,7)->(3,8) 00:38.581 -(2,7)->(2,6) 00:50.010 --(3,3) 00:51.016 -(2,3)->(3,3) 00:43.433 -(1,3)->(2,3) 00:52.296 --(3,3) 00:52.822 -(1,4)->(1,3) 00:45.444 -(2,5)->(1,5) 00:56.716 -(1,3)->(1,2) 00:46.681 -(2,4)->(2,5) 00:59.099 --(1,2) 01:60.821 +(2,2) 00:00.862 +(3,8) 00:00.933 +(2,1) 00:01.718 +(3,2) 00:01.902 +-(2,2) 00:02.860 +(2,2) 00:02.931 +(2,3) 00:03.797 +(2,8) 00:04.163 +-(2,3) 00:08.181 +(3,3) 00:05.117 +(1,2) 00:08.978 +(3,4) 00:06.070 +-(1,2) 00:09.135 +(2,4) 00:11.820 +(1,3) 00:12.683 +(2,3) 00:12.638 +-(1,3) 00:13.435 +(1,2) 00:15.614 +(1,3) 00:14.292 +(1,1) 00:18.783 +(1,7) 00:16.909 +(1,1)->(1,8) 00:25.186 +(1,7)->(1,6) 00:18.661 +(1,8)->(1,1) 00:27.009 +(1,3)->(1,4) 00:21.829 +(1,2)->(1,3) 00:29.741 +(2,4)->(2,5) 00:23.310 +(1,3)->(1,2) 00:31.160 +(2,5)->(1,5) 00:25.351 +-(1,1) 00:26.217 +(1,2)->(1,1) 00:34.764 +(1,5)->(2,5) 00:28.847 +(1,1)->(1,2) 00:36.665 +(2,5)->(3,5) 00:30.555 +(1,2)->(1,3) 00:43.333 +(1,6)->(1,5) 00:31.964 +(1,3)->(1,2) 00:45.544 +(1,5)->(2,5) 00:33.201 +(1,2)->(1,3) 00:48.734 +(1,4)->(1,5) 00:34.476 +-(1,3) 00:35.306 +(3,8)->(3,7) 00:56.347 +(2,5)->(2,6) 00:37.406 +(3,7)->(3,8) 00:58.193 +(3,5)->(2,5) 00:39.694 +(3,8)->(3,7) 01:61.938 +(1,5)->(1,4) 00:41.361 +(3,7)->(3,8) 01:63.645 +(2,3)->(2,4) 00:43.490 +-(3,2) 00:46.140 +(3,3)->(3,2) 01:70.759 +-(1,4) 01:72.136 +(2,2)->(2,3) 00:47.966 +(3,2)->(3,3) 01:73.383 +(2,3)->(2,2) 00:50.108 +(3,1)->(3,2) 01:75.424 +-(2,2) 01:76.448 +(2,4)->(2,3) 00:55.468 +(3,8)->(3,1) 01:78.611 +(2,3)->(2,2) 00:57.257 +-(3,1) 00:58.015 +(3,3)->(2,3) 01:84.964 +(2,8)->(2,7) 00:59.896 +(2,3)->(3,3) 01:86.208 +(2,1)->(2,8) 01:61.493 +-(3,2) 01:62.658 Player2 win!