ai勉强能动,但不能正确使用,临时存一下,不要拉取使用!

This commit is contained in:
liuweilhy 2018-12-03 00:07:52 +08:00
parent a99577d089
commit 41917c1d74
10 changed files with 736 additions and 188 deletions

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.7.2, 2018-12-01T00:15:14. -->
<!-- Written by QtCreator 4.6.1, 2018-12-02T12:38:12. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{9a8c5f9d-1814-40d9-ab01-983c45f229c5}</value>
<value type="QByteArray">{fba36f69-c7a3-4c3f-91d4-7c71ad79549c}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
@ -54,22 +54,19 @@
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
</valuemap>
<valuemap type="QVariantMap"/>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.9.7 GCC 64bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.9.7 GCC 64bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.597.gcc_64_kit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.11.0 MSVC2017 64bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.11.0 MSVC2017 64bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.5110.win64_msvc2017_64_kit</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/media/sniper/Work/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_9_7_GCC_64bit-Debug</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">E:/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_11_0_MSVC2017_64bit-Debug</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@ -87,10 +84,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments">
<value type="QString">-w</value>
<value type="QString">-r</value>
</valuelist>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
@ -106,10 +100,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments">
<value type="QString">-w</value>
<value type="QString">-r</value>
</valuelist>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
@ -129,7 +120,7 @@
<value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/media/sniper/Work/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_9_7_GCC_64bit-Release</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">E:/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_11_0_MSVC2017_64bit-Release</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@ -147,10 +138,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments">
<value type="QString">-w</value>
<value type="QString">-r</value>
</valuelist>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
@ -166,10 +154,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments">
<value type="QString">-w</value>
<value type="QString">-r</value>
</valuelist>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
@ -189,7 +174,7 @@
<value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/media/sniper/Work/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_9_7_GCC_64bit-Profile</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">E:/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_11_0_MSVC2017_64bit-Profile</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@ -207,10 +192,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments">
<value type="QString">-w</value>
<value type="QString">-r</value>
</valuelist>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
@ -226,10 +208,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments">
<value type="QString">-w</value>
<value type="QString">-r</value>
</valuelist>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
@ -257,7 +236,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy Configuration</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">部署设置</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
@ -307,12 +286,13 @@
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">ninechess</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:/media/sniper/Work/My program/QT/NineChess/NineChess/ninechess.pro</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:E:/My program/QT/NineChess/NineChess/ninechess.pro</value>
<value type="bool" key="QmakeProjectManager.QmakeRunConfiguration.UseLibrarySearchPath">true</value>
<value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.CommandLineArguments"></value>
<value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.ProFile">ninechess.pro</value>
<value type="bool" key="Qt4ProjectManager.Qt4RunConfiguration.UseDyldImageSuffix">false</value>
<value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.UserWorkingDirectory"></value>
<value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.UserWorkingDirectory.default">/media/sniper/Work/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_9_7_GCC_64bit-Debug</value>
<value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.UserWorkingDirectory.default">E:/My program/QT/NineChess/build-ninechess-Desktop_Qt_5_11_0_MSVC2017_64bit-Debug</value>
<value type="uint" key="RunConfiguration.QmlDebugServerPort">3768</value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>

View File

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

View File

@ -36,8 +36,8 @@ private:
// 等待条件,这里没用到,留着以后扩展用
QWaitCondition pauseCondition;
// 棋类
NineChess chess;
// 主线程棋对象的引用
const NineChess *chess;
// Alpha-Beta剪枝算法类
NineChessAi_ab ai_ab;
};

View File

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

View File

@ -90,7 +90,7 @@ protected:
// 移动旧子
bool movePiece(QPointF pos);
// 去子
bool removePiece(QPointF pos);
bool capturePiece(QPointF pos);
private:
// 棋对象的数据模型

View File

@ -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<long long>::iterator itor;
list<uint64_t>::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) + "";
}

View File

@ -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 <long long> millList;
list <uint64_t> 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_;
// 招法命令行用于棋谱的显示和解析
// 当前招法的命令行指令,即一招棋谱

View File

@ -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 <cmath>
#include <time.h>
@ -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++) {

View File

@ -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<struct Node *> 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<NineChess> dataCache;
// 局面数据缓存区最大大小
size_t cacheMaxSize;
// 局面数据哈希表
list<struct Hash> 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

View File

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