refactor: 着法生成相关逻辑转到 movegen.cpp

顺带将 client.cpp 改为 UTF-8 with BOM, 以通过 Qt 编译.
This commit is contained in:
CalciteM 2019-09-08 12:57:30 +08:00
parent 8fd1303cc3
commit ae91aa1942
11 changed files with 710 additions and 666 deletions

View File

@ -23,6 +23,7 @@ INCLUDEPATH += src/ui/qt
SOURCES += \
src/ai/evaluate.cpp \
src/ai/movegen.cpp \
src/game/millgame.cpp \
src/main.cpp \
src/base/thread.cpp \
@ -41,6 +42,7 @@ HEADERS += \
include/version.h \
include/version.h.template \
src/ai/evaluate.h \
src/ai/movegen.h \
src/base/HashNode.h \
src/base/debug.h \
src/base/hashMap.h \

View File

@ -442,6 +442,7 @@
</AdditionalInputs>
</CustomBuild>
<ClInclude Include="src\ai\evaluate.h" />
<ClInclude Include="src\ai\movegen.h" />
<ClInclude Include="src\ai\search.h" />
<ClInclude Include="src\ai\zobrist.h" />
<ClInclude Include="src\base\debug.h" />
@ -692,6 +693,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\ai\evaluate.cpp" />
<ClCompile Include="src\ai\movegen.cpp" />
<ClCompile Include="src\ai\search.cpp" />
<ClCompile Include="src\base\thread.cpp" />
<ClCompile Include="src\game\millgame.cpp" />

View File

@ -99,6 +99,9 @@
<ClInclude Include="src\ai\evaluate.h">
<Filter>ai</Filter>
</ClInclude>
<ClInclude Include="src\ai\movegen.h">
<Filter>ai</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">
@ -310,6 +313,9 @@
<ClCompile Include="src\ai\evaluate.cpp">
<Filter>ai</Filter>
</ClCompile>
<ClCompile Include="src\ai\movegen.cpp">
<Filter>ai</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="millgame.rc">

430
src/ai/movegen.cpp Normal file
View File

@ -0,0 +1,430 @@
#include <random>
#include "movegen.h"
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;
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)
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_2());
else
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_1());
} else {
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)
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_2());
else
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_1());
} else {
newCapacity = 6;
}
break;
case MillGame::GAME_NOTSTARTED:
newCapacity = 24;
break;
default:
newCapacity = 24;
break;
};
node->children.reserve(newCapacity + 2 /* TODO: 未细调故再多留余量2 */);
// 如果有子节点,则返回,避免重复建立
if (!node->children.empty()) {
return;
}
// 对手
MillGame::Player opponent = MillGame::getOpponent(chessTemp.context.turn);
// 列出所有合法的下一招
switch (chessTemp.context.action) {
// 对于选子和落子动作
case MillGame::ACTION_CHOOSE:
case MillGame::ACTION_PLACE:
// 对于摆子阶段
if (chessTemp.context.stage & (MillGame::GAME_PLACING | MillGame::GAME_NOTSTARTED)) {
for (int i : movePriorityTable) {
pos = i;
if (chessTemp.board_[pos]) {
continue;
}
if (chessTemp.context.stage != MillGame::GAME_NOTSTARTED || node != rootNode) {
ai_ab.addNode(node, 0, pos, bestMove, chessTemp.context.turn);
} else {
// 若为先手,则抢占星位
if (MillGame::isStarPoint(pos)) {
ai_ab.addNode(node, MillGameAi_ab::INF_VALUE, pos, bestMove, chessTemp.context.turn);
}
}
}
break;
}
// 对于移子阶段
if (chessTemp.context.stage & MillGame::GAME_MOVING) {
int newPos, oldPos;
// 尽量走理论上较差的位置的棋子
for (int i = MOVE_PRIORITY_TABLE_SIZE - 1; i >= 0; i--) {
oldPos = movePriorityTable[i];
if (!chessTemp.choose(oldPos)) {
continue;
}
if ((chessTemp.context.turn == MillGame::PLAYER1 &&
(chessTemp.context.nPiecesOnBoard_1 > chessTemp.currentRule.nPiecesAtLeast || !chessTemp.currentRule.allowFlyWhenRemainThreePieces)) ||
(chessTemp.context.turn == MillGame::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++) {
// 对于原有位置,遍历四个方向的着法,如果棋盘上为空位就加到结点列表中
newPos = moveTable[oldPos][moveDirection];
if (newPos && !chessTemp.board_[newPos]) {
int move = (oldPos << 8) + newPos;
ai_ab.addNode(node, 0, move, bestMove, chessTemp.context.turn); // (12%)
}
}
} else {
// 对于棋盘上还有不到3个字但允许飞子的情况不要求在着法表中是空位就行
for (newPos = MillGame::POS_BEGIN; newPos < MillGame::POS_END; newPos++) {
if (!chessTemp.board_[newPos]) {
int move = (oldPos << 8) + newPos;
ai_ab.addNode(node, 0, move, bestMove, chessTemp.context.turn);
}
}
}
}
}
break;
// 对于吃子动作
case MillGame::ACTION_CAPTURE:
if (chessTemp.isAllInMills(opponent)) {
// 全成三的情况
for (int i = MOVE_PRIORITY_TABLE_SIZE - 1; i >= 0; i--) {
pos = movePriorityTable[i];
if (chessTemp.board_[pos] & opponent) {
ai_ab.addNode(node, 0, -pos, bestMove, chessTemp.context.turn);
}
}
break;
}
// 不是全成三的情况
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)) {
ai_ab.addNode(node, 0, -pos, bestMove, chessTemp.context.turn);
}
}
}
break;
default:
break;
}
}
void MoveList::createMoveTable(MillGame &chess)
{
#ifdef CONST_MOVE_TABLE
#if 1
const int moveTable_obliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},
/* 3 */ {0, 0, 0, 0},
/* 4 */ {0, 0, 0, 0},
/* 5 */ {0, 0, 0, 0},
/* 6 */ {0, 0, 0, 0},
/* 7 */ {0, 0, 0, 0},
/* 8 */ {9, 15, 16, 0},
/* 9 */ {17, 8, 10, 0},
/* 10 */ {9, 11, 18, 0},
/* 11 */ {19, 10, 12, 0},
/* 12 */ {11, 13, 20, 0},
/* 13 */ {21, 12, 14, 0},
/* 14 */ {13, 15, 22, 0},
/* 15 */ {23, 8, 14, 0},
/* 16 */ {17, 23, 8, 24},
/* 17 */ {9, 25, 16, 18},
/* 18 */ {17, 19, 10, 26},
/* 19 */ {11, 27, 18, 20},
/* 20 */ {19, 21, 12, 28},
/* 21 */ {13, 29, 20, 22},
/* 22 */ {21, 23, 14, 30},
/* 23 */ {15, 31, 16, 22},
/* 24 */ {25, 31, 16, 0},
/* 25 */ {17, 24, 26, 0},
/* 26 */ {25, 27, 18, 0},
/* 27 */ {19, 26, 28, 0},
/* 28 */ {27, 29, 20, 0},
/* 29 */ {21, 28, 30, 0},
/* 30 */ {29, 31, 22, 0},
/* 31 */ {23, 24, 30, 0},
/* 32 */ {0, 0, 0, 0},
/* 33 */ {0, 0, 0, 0},
/* 34 */ {0, 0, 0, 0},
/* 35 */ {0, 0, 0, 0},
/* 36 */ {0, 0, 0, 0},
/* 37 */ {0, 0, 0, 0},
/* 38 */ {0, 0, 0, 0},
/* 39 */ {0, 0, 0, 0},
};
const int moveTable_noObliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},
/* 3 */ {0, 0, 0, 0},
/* 4 */ {0, 0, 0, 0},
/* 5 */ {0, 0, 0, 0},
/* 6 */ {0, 0, 0, 0},
/* 7 */ {0, 0, 0, 0},
/* 8 */ {16, 9, 15, 0},
/* 9 */ {10, 8, 0, 0},
/* 10 */ {18, 11, 9, 0},
/* 11 */ {12, 10, 0, 0},
/* 12 */ {20, 13, 11, 0},
/* 13 */ {14, 12, 0, 0},
/* 14 */ {22, 15, 13, 0},
/* 15 */ {8, 14, 0, 0},
/* 16 */ {8, 24, 17, 23},
/* 17 */ {18, 16, 0, 0},
/* 18 */ {10, 26, 19, 17},
/* 19 */ {20, 18, 0, 0},
/* 20 */ {12, 28, 21, 19},
/* 21 */ {22, 20, 0, 0},
/* 22 */ {14, 30, 23, 21},
/* 23 */ {16, 22, 0, 0},
/* 24 */ {16, 25, 31, 0},
/* 25 */ {26, 24, 0, 0},
/* 26 */ {18, 27, 25, 0},
/* 27 */ {28, 26, 0, 0},
/* 28 */ {20, 29, 27, 0},
/* 29 */ {30, 28, 0, 0},
/* 30 */ {22, 31, 29, 0},
/* 31 */ {24, 30, 0, 0},
/* 32 */ {0, 0, 0, 0},
/* 33 */ {0, 0, 0, 0},
/* 34 */ {0, 0, 0, 0},
/* 35 */ {0, 0, 0, 0},
/* 36 */ {0, 0, 0, 0},
/* 37 */ {0, 0, 0, 0},
/* 38 */ {0, 0, 0, 0},
/* 39 */ {0, 0, 0, 0},
};
#else
const int moveTable_obliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{9, 15, 0, 16},
{10, 8, 0, 17},
{11, 9, 0, 18},
{12, 10, 0, 19},
{13, 11, 0, 20},
{14, 12, 0, 21},
{15, 13, 0, 22},
{8, 14, 0, 23},
{17, 23, 8, 24},
{18, 16, 9, 25},
{19, 17, 10, 26},
{20, 18, 11, 27},
{21, 19, 12, 28},
{22, 20, 13, 29},
{23, 21, 14, 30},
{16, 22, 15, 31},
{25, 31, 16, 0},
{26, 24, 17, 0},
{27, 25, 18, 0},
{28, 26, 19, 0},
{29, 27, 20, 0},
{30, 28, 21, 0},
{31, 29, 22, 0},
{24, 30, 23, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
};
const int moveTable_noObliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},
/* 3 */ {0, 0, 0, 0},
/* 4 */ {0, 0, 0, 0},
/* 5 */ {0, 0, 0, 0},
/* 6 */ {0, 0, 0, 0},
/* 7 */ {0, 0, 0, 0},
/* 8 */ {9, 15, 0, 16},
/* 9 */ {10, 8, 0, 0},
/* 10 */ {11, 9, 0, 18},
/* 11 */ {12, 10, 0, 0},
/* 12 */ {13, 11, 0, 20},
/* 13 */ {14, 12, 0, 0},
/* 14 */ {15, 13, 0, 22},
/* 15 */ {8, 14, 0, 0},
/* 16 */ {17, 23, 8, 24},
/* 17 */ {18, 16, 0, 0},
/* 18 */ {19, 17, 10, 26},
/* 19 */ {20, 18, 0, 0},
/* 20 */ {21, 19, 12, 28},
/* 21 */ {22, 20, 0, 0},
/* 22 */ {23, 21, 14, 30},
/* 23 */ {16, 22, 0, 0},
/* 24 */ {25, 31, 16, 0},
/* 25 */ {26, 24, 0, 0},
/* 26 */ {27, 25, 18, 0},
/* 27 */ {28, 26, 0, 0},
/* 28 */ {29, 27, 20, 0},
/* 29 */ {30, 28, 0, 0},
/* 30 */ {31, 29, 22, 0},
/* 31 */ {24, 30, 0, 0},
/* 32 */ {0, 0, 0, 0},
/* 33 */ {0, 0, 0, 0},
/* 34 */ {0, 0, 0, 0},
/* 35 */ {0, 0, 0, 0},
/* 36 */ {0, 0, 0, 0},
/* 37 */ {0, 0, 0, 0},
/* 38 */ {0, 0, 0, 0},
/* 39 */ {0, 0, 0, 0},
};
#endif
if (chess.currentRule.hasObliqueLines) {
memcpy(moveTable, moveTable_obliqueLine, sizeof(moveTable));
} else {
memcpy(moveTable, moveTable_noObliqueLine, sizeof(moveTable));
}
#else /* CONST_MOVE_TABLE */
for (int r = 1; r <= N_RINGS; r++) {
for (int s = 0; s < N_SEATS; s++) {
int p = r * N_SEATS + s;
// 顺时针走一步的位置
moveTable[p][MOVE_DIRECTION_CLOCKWISE] = r * N_SEATS + (s + 1) % N_SEATS;
// 逆时针走一步的位置
moveTable[p][MOVE_DIRECTION_ANTICLOCKWISE] = r * N_SEATS + (s + N_SEATS - 1) % N_SEATS;
// 如果是 0、2、4、6位偶数位或是有斜线
if (!(s & 1) || this->currentRule.hasObliqueLines) {
if (r > 1) {
// 向内走一步的位置
moveTable[p][MOVE_DIRECTION_INWARD] = (r - 1) * N_SEATS + s;
}
if (r < N_RINGS) {
// 向外走一步的位置
moveTable[p][MOVE_DIRECTION_OUTWARD] = (r + 1) * N_SEATS + s;
}
}
#if 0
// 对于无斜线情况下的1、3、5、7位奇数位则都设为棋盘外点默认'\x00'
else {
// 向内走一步的位置设为随便棋盘外一点
moveTable[i * SEAT + j][2] = '\x00';
// 向外走一步的位置设为随便棋盘外一点
moveTable[i * SEAT + j][3] = '\x00';
}
#endif
}
}
#endif /* CONST_MOVE_TABLE */
#if 0
int sum = 0;
for (int i = 0; i < N_POINTS; i++) {
loggerDebug("/* %d */ {", i);
for (int j = 0; j < N_MOVE_DIRECTIONS; j++) {
if (j == N_MOVE_DIRECTIONS - 1)
loggerDebug("%d", moveTable[i][j]);
else
loggerDebug("%d, ", moveTable[i][j]);
sum += moveTable[i][j];
}
loggerDebug("},\n");
}
loggerDebug("sum = %d\n");
#endif
}
void MoveList::shuffleMovePriorityTable(MillGame & chess)
{
array<int, 4> movePriorityTable0 = { 17, 19, 21, 23 }; // 中圈四个顶点 (星位)
array<int, 8> movePriorityTable1 = { 25, 27, 29, 31, 9, 11, 13, 15 }; // 外圈和内圈四个顶点
array<int, 4> movePriorityTable2 = { 16, 18, 20, 22 }; // 中圈十字架
array<int, 8> movePriorityTable3 = { 24, 26, 28, 30, 8, 10, 12, 14 }; // 外内圈十字架
if (chess.getRandomMove() == true) {
uint32_t seed = static_cast<uint32_t>(std::chrono::system_clock::now().time_since_epoch().count());
std::shuffle(movePriorityTable0.begin(), movePriorityTable0.end(), std::default_random_engine(seed));
std::shuffle(movePriorityTable1.begin(), movePriorityTable1.end(), std::default_random_engine(seed));
std::shuffle(movePriorityTable2.begin(), movePriorityTable2.end(), std::default_random_engine(seed));
std::shuffle(movePriorityTable3.begin(), movePriorityTable3.end(), std::default_random_engine(seed));
}
for (size_t i = 0; i < 4; i++) {
movePriorityTable[i + 0] = movePriorityTable0[i];
}
for (size_t i = 0; i < 8; i++) {
movePriorityTable[i + 4] = movePriorityTable1[i];
}
for (size_t i = 0; i < 4; i++) {
movePriorityTable[i + 12] = movePriorityTable2[i];
}
for (size_t i = 0; i < 8; i++) {
movePriorityTable[i + 16] = movePriorityTable3[i];
}
}

40
src/ai/movegen.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef MOVEGEN_H
#define MOVEGEN_H
#include "config.h"
#include "millgame.h"
#include "search.h"
class MoveList
{
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,
move_t bestMove);
// 生成着法表
static void createMoveTable(MillGame &chess);
// 随机打乱着法搜索顺序
static void shuffleMovePriorityTable(MillGame &chess);
// 着法表 // TODO: Move to private
inline static int moveTable[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = { {0} };
private:
// 着法顺序表, 后续会被打乱
inline static array<int, MillGame::N_RINGS *MillGame::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,
};
};
#endif /* MOVEGEN_H */

View File

@ -21,12 +21,12 @@
#include <cmath>
#include <array>
#include <random>
#include <chrono>
#include <algorithm>
#include "search.h"
#include "evaluate.h"
#include "movegen.h"
#include "hashmap.h"
using namespace CTSL;
@ -199,178 +199,6 @@ struct MillGameAi_ab::Node *MillGameAi_ab::addNode(
return newNode;
}
void MillGameAi_ab::shuffleMovePriorityTable()
{
array<int, 4> movePriorityTable0 = { 17, 19, 21, 23 }; // 中圈四个顶点 (星位)
array<int, 8> movePriorityTable1 = { 25, 27, 29, 31, 9, 11, 13, 15 }; // 外圈和内圈四个顶点
array<int, 4> movePriorityTable2 = { 16, 18, 20, 22 }; // 中圈十字架
array<int, 8> movePriorityTable3 = { 24, 26, 28, 30, 8, 10, 12, 14 }; // 外内圈十字架
if (chess_.getRandomMove() == true) {
uint32_t seed = static_cast<uint32_t>(std::chrono::system_clock::now().time_since_epoch().count());
std::shuffle(movePriorityTable0.begin(), movePriorityTable0.end(), std::default_random_engine(seed));
std::shuffle(movePriorityTable1.begin(), movePriorityTable1.end(), std::default_random_engine(seed));
std::shuffle(movePriorityTable2.begin(), movePriorityTable2.end(), std::default_random_engine(seed));
std::shuffle(movePriorityTable3.begin(), movePriorityTable3.end(), std::default_random_engine(seed));
}
for (size_t i = 0; i < 4; i++) {
movePriorityTable[i + 0] = movePriorityTable0[i];
}
for (size_t i = 0; i < 8; i++) {
movePriorityTable[i + 4] = movePriorityTable1[i];
}
for (size_t i = 0; i < 4; i++) {
movePriorityTable[i + 12] = movePriorityTable2[i];
}
for (size_t i = 0; i < 8; i++) {
movePriorityTable[i + 16] = movePriorityTable3[i];
}
}
void MillGameAi_ab::generateLegalMoves(Node *node, move_t bestMove)
{
const int MOVE_PRIORITY_TABLE_SIZE = MillGame::N_RINGS * MillGame::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)
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_2());
else
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_1());
} else {
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)
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_2());
else
newCapacity = static_cast<size_t>(chessTemp.getPiecesOnBoardCount_1());
} else {
newCapacity = 6;
}
break;
case MillGame::GAME_NOTSTARTED:
newCapacity = 24;
break;
default:
newCapacity = 24;
break;
};
node->children.reserve(newCapacity + 2 /* TODO: 未细调故再多留余量2 */);
// 如果有子节点,则返回,避免重复建立
if (!node->children.empty()) {
return;
}
// 对手
MillGame::Player opponent = MillGame::getOpponent(chessTemp.context.turn);
// 列出所有合法的下一招
switch (chessTemp.context.action) {
// 对于选子和落子动作
case MillGame::ACTION_CHOOSE:
case MillGame::ACTION_PLACE:
// 对于摆子阶段
if (chessTemp.context.stage & (MillGame::GAME_PLACING | MillGame::GAME_NOTSTARTED)) {
for (int i : movePriorityTable) {
pos = i;
if (chessTemp.board_[pos]) {
continue;
}
if (chessTemp.context.stage != MillGame::GAME_NOTSTARTED || node != rootNode) {
addNode(node, 0, pos, bestMove, chessTemp.context.turn);
} else {
// 若为先手,则抢占星位
if (MillGame::isStarPoint(pos)) {
addNode(node, INF_VALUE, pos, bestMove, chessTemp.context.turn);
}
}
}
break;
}
// 对于移子阶段
if (chessTemp.context.stage & MillGame::GAME_MOVING) {
int newPos, oldPos;
// 尽量走理论上较差的位置的棋子
for (int i = MOVE_PRIORITY_TABLE_SIZE - 1; i >= 0; i--) {
oldPos = movePriorityTable[i];
if (!chessTemp.choose(oldPos)) {
continue;
}
if ((chessTemp.context.turn == MillGame::PLAYER1 &&
(chessTemp.context.nPiecesOnBoard_1 > chessTemp.currentRule.nPiecesAtLeast || !chessTemp.currentRule.allowFlyWhenRemainThreePieces)) ||
(chessTemp.context.turn == MillGame::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++) {
// 对于原有位置,遍历四个方向的着法,如果棋盘上为空位就加到结点列表中
newPos = MillGame::moveTable[oldPos][moveDirection];
if (newPos && !chessTemp.board_[newPos]) {
int move = (oldPos << 8) + newPos;
addNode(node, 0, move, bestMove, chessTemp.context.turn); // (12%)
}
}
} else {
// 对于棋盘上还有不到3个字但允许飞子的情况不要求在着法表中是空位就行
for (newPos = MillGame::POS_BEGIN; newPos < MillGame::POS_END; newPos++) {
if (!chessTemp.board_[newPos]) {
int move = (oldPos << 8) + newPos;
addNode(node, 0, move, bestMove, chessTemp.context.turn);
}
}
}
}
}
break;
// 对于吃子动作
case MillGame::ACTION_CAPTURE:
if (chessTemp.isAllInMills(opponent)) {
// 全成三的情况
for (int i = MOVE_PRIORITY_TABLE_SIZE - 1; i >= 0; i--) {
pos = movePriorityTable[i];
if (chessTemp.board_[pos] & opponent) {
addNode(node, 0, -pos, bestMove, chessTemp.context.turn);
}
}
break;
}
// 不是全成三的情况
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)) {
addNode(node, 0, -pos, bestMove, chessTemp.context.turn);
}
}
}
break;
default:
break;
}
}
bool MillGameAi_ab::nodeLess(const Node *first, const Node *second)
{
#ifdef SORT_CONSIDER_PRUNED
@ -528,7 +356,7 @@ int MillGameAi_ab::alphaBetaPruning(depth_t depth)
#endif // THREEFOLD_REPETITION
// 随机打乱着法顺序
shuffleMovePriorityTable();
MoveList::shuffleMovePriorityTable(chess_);
#ifdef IDS_SUPPORT
// 深化迭代
@ -704,7 +532,7 @@ MillGameAi_ab::value_t MillGameAi_ab::alphaBetaPruning(depth_t depth, value_t al
}
// 生成子节点树,即生成每个合理的着法
generateLegalMoves(node, bestMove);
MoveList::generateLegalMoves(*this, chessTemp, node, rootNode, bestMove);
// 根据演算模型执行 MiniMax 检索,对先手,搜索 Max, 对后手,搜索 Min

View File

@ -156,10 +156,16 @@ public:
static void loadOpeningBookFileToHashMap();
#endif // BOOK_LEARNING
protected:
// 生成所有合法的着法并建立子节点
void generateLegalMoves(Node *node, move_t bestMove);
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);
// 定义极大值
static const value_t INF_VALUE = 0x1 << 14;
protected:
// 对合法的着法降序排序
void sortLegalMoves(Node *node);
@ -169,11 +175,6 @@ protected:
// 构造根节点
void buildRoot();
// 增加新节点
struct Node *addNode(Node *parent, value_t value,
move_t move, move_t bestMove,
enum MillGame::Player player);
// 评价函数
value_t evaluate(Node *node);
#ifdef EVALUATE_ENABLE
@ -208,10 +209,7 @@ protected:
// 篡改深度
depth_t changeDepth(depth_t originalDepth);
// 随机打乱着法搜索顺序
void shuffleMovePriorityTable();
#ifdef HASH_MAP_ENABLE
// 查找哈希表
bool findHash(MillGame::hash_t hash, HashValue &hashValue);
@ -264,16 +262,6 @@ private:
// 标识,用于跳出剪枝算法,立即返回
bool requiredQuit {false};
// 着法顺序表, 后续会被打乱
array<int, MillGame::N_RINGS *MillGame::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,
};
// 定义极大值
static const value_t INF_VALUE = 0x1 << 14;
private:
// 命令行
char cmdline[64] {};

View File

@ -22,6 +22,7 @@
#include <algorithm>
#include "millgame.h"
#include "search.h"
#include "movegen.h"
// 对静态常量数组的定义要放在类外,不要放在头文件
// 预定义的4套规则
@ -123,9 +124,6 @@ const int MillGame::onBoard[N_POINTS] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 着法表
int MillGame::moveTable[N_POINTS][N_MOVE_DIRECTIONS] = {{0}};
// 成三表
int MillGame::millTable[N_POINTS][N_DIRECTIONS][N_RINGS - 1] = {{{0}}};
@ -209,259 +207,6 @@ MillGame::Player MillGame::getOpponent(MillGame::Player player)
return NOBODY;
}
void MillGame::createMoveTable()
{
#ifdef CONST_MOVE_TABLE
#if 1
const int moveTable_obliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},
/* 3 */ {0, 0, 0, 0},
/* 4 */ {0, 0, 0, 0},
/* 5 */ {0, 0, 0, 0},
/* 6 */ {0, 0, 0, 0},
/* 7 */ {0, 0, 0, 0},
/* 8 */ {9, 15, 16, 0},
/* 9 */ {17, 8, 10, 0},
/* 10 */ {9, 11, 18, 0},
/* 11 */ {19, 10, 12, 0},
/* 12 */ {11, 13, 20, 0},
/* 13 */ {21, 12, 14, 0},
/* 14 */ {13, 15, 22, 0},
/* 15 */ {23, 8, 14, 0},
/* 16 */ {17, 23, 8, 24},
/* 17 */ {9, 25, 16, 18},
/* 18 */ {17, 19, 10, 26},
/* 19 */ {11, 27, 18, 20},
/* 20 */ {19, 21, 12, 28},
/* 21 */ {13, 29, 20, 22},
/* 22 */ {21, 23, 14, 30},
/* 23 */ {15, 31, 16, 22},
/* 24 */ {25, 31, 16, 0},
/* 25 */ {17, 24, 26, 0},
/* 26 */ {25, 27, 18, 0},
/* 27 */ {19, 26, 28, 0},
/* 28 */ {27, 29, 20, 0},
/* 29 */ {21, 28, 30, 0},
/* 30 */ {29, 31, 22, 0},
/* 31 */ {23, 24, 30, 0},
/* 32 */ {0, 0, 0, 0},
/* 33 */ {0, 0, 0, 0},
/* 34 */ {0, 0, 0, 0},
/* 35 */ {0, 0, 0, 0},
/* 36 */ {0, 0, 0, 0},
/* 37 */ {0, 0, 0, 0},
/* 38 */ {0, 0, 0, 0},
/* 39 */ {0, 0, 0, 0},
};
const int moveTable_noObliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},
/* 3 */ {0, 0, 0, 0},
/* 4 */ {0, 0, 0, 0},
/* 5 */ {0, 0, 0, 0},
/* 6 */ {0, 0, 0, 0},
/* 7 */ {0, 0, 0, 0},
/* 8 */ {16, 9, 15, 0},
/* 9 */ {10, 8, 0, 0},
/* 10 */ {18, 11, 9, 0},
/* 11 */ {12, 10, 0, 0},
/* 12 */ {20, 13, 11, 0},
/* 13 */ {14, 12, 0, 0},
/* 14 */ {22, 15, 13, 0},
/* 15 */ {8, 14, 0, 0},
/* 16 */ {8, 24, 17, 23},
/* 17 */ {18, 16, 0, 0},
/* 18 */ {10, 26, 19, 17},
/* 19 */ {20, 18, 0, 0},
/* 20 */ {12, 28, 21, 19},
/* 21 */ {22, 20, 0, 0},
/* 22 */ {14, 30, 23, 21},
/* 23 */ {16, 22, 0, 0},
/* 24 */ {16, 25, 31, 0},
/* 25 */ {26, 24, 0, 0},
/* 26 */ {18, 27, 25, 0},
/* 27 */ {28, 26, 0, 0},
/* 28 */ {20, 29, 27, 0},
/* 29 */ {30, 28, 0, 0},
/* 30 */ {22, 31, 29, 0},
/* 31 */ {24, 30, 0, 0},
/* 32 */ {0, 0, 0, 0},
/* 33 */ {0, 0, 0, 0},
/* 34 */ {0, 0, 0, 0},
/* 35 */ {0, 0, 0, 0},
/* 36 */ {0, 0, 0, 0},
/* 37 */ {0, 0, 0, 0},
/* 38 */ {0, 0, 0, 0},
/* 39 */ {0, 0, 0, 0},
};
#else
const int moveTable_obliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{9, 15, 0, 16},
{10, 8, 0, 17},
{11, 9, 0, 18},
{12, 10, 0, 19},
{13, 11, 0, 20},
{14, 12, 0, 21},
{15, 13, 0, 22},
{8, 14, 0, 23},
{17, 23, 8, 24},
{18, 16, 9, 25},
{19, 17, 10, 26},
{20, 18, 11, 27},
{21, 19, 12, 28},
{22, 20, 13, 29},
{23, 21, 14, 30},
{16, 22, 15, 31},
{25, 31, 16, 0},
{26, 24, 17, 0},
{27, 25, 18, 0},
{28, 26, 19, 0},
{29, 27, 20, 0},
{30, 28, 21, 0},
{31, 29, 22, 0},
{24, 30, 23, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
};
const int moveTable_noObliqueLine[MillGame::N_POINTS][MillGame::N_MOVE_DIRECTIONS] = {
/* 0 */ {0, 0, 0, 0},
/* 1 */ {0, 0, 0, 0},
/* 2 */ {0, 0, 0, 0},
/* 3 */ {0, 0, 0, 0},
/* 4 */ {0, 0, 0, 0},
/* 5 */ {0, 0, 0, 0},
/* 6 */ {0, 0, 0, 0},
/* 7 */ {0, 0, 0, 0},
/* 8 */ {9, 15, 0, 16},
/* 9 */ {10, 8, 0, 0},
/* 10 */ {11, 9, 0, 18},
/* 11 */ {12, 10, 0, 0},
/* 12 */ {13, 11, 0, 20},
/* 13 */ {14, 12, 0, 0},
/* 14 */ {15, 13, 0, 22},
/* 15 */ {8, 14, 0, 0},
/* 16 */ {17, 23, 8, 24},
/* 17 */ {18, 16, 0, 0},
/* 18 */ {19, 17, 10, 26},
/* 19 */ {20, 18, 0, 0},
/* 20 */ {21, 19, 12, 28},
/* 21 */ {22, 20, 0, 0},
/* 22 */ {23, 21, 14, 30},
/* 23 */ {16, 22, 0, 0},
/* 24 */ {25, 31, 16, 0},
/* 25 */ {26, 24, 0, 0},
/* 26 */ {27, 25, 18, 0},
/* 27 */ {28, 26, 0, 0},
/* 28 */ {29, 27, 20, 0},
/* 29 */ {30, 28, 0, 0},
/* 30 */ {31, 29, 22, 0},
/* 31 */ {24, 30, 0, 0},
/* 32 */ {0, 0, 0, 0},
/* 33 */ {0, 0, 0, 0},
/* 34 */ {0, 0, 0, 0},
/* 35 */ {0, 0, 0, 0},
/* 36 */ {0, 0, 0, 0},
/* 37 */ {0, 0, 0, 0},
/* 38 */ {0, 0, 0, 0},
/* 39 */ {0, 0, 0, 0},
};
#endif
if (currentRule.hasObliqueLines) {
memcpy(moveTable, moveTable_obliqueLine, sizeof(moveTable));
} else {
memcpy(moveTable, moveTable_noObliqueLine, sizeof(moveTable));
}
#else /* CONST_MOVE_TABLE */
for (int r = 1; r <= N_RINGS; r++) {
for (int s = 0; s < N_SEATS; s++) {
int p = r * N_SEATS + s;
// 顺时针走一步的位置
moveTable[p][MOVE_DIRECTION_CLOCKWISE] = r * N_SEATS + (s + 1) % N_SEATS;
// 逆时针走一步的位置
moveTable[p][MOVE_DIRECTION_ANTICLOCKWISE] = r * N_SEATS + (s + N_SEATS - 1) % N_SEATS;
// 如果是 0、2、4、6位偶数位或是有斜线
if (!(s & 1) || this->currentRule.hasObliqueLines) {
if (r > 1) {
// 向内走一步的位置
moveTable[p][MOVE_DIRECTION_INWARD] = (r - 1) * N_SEATS + s;
}
if (r < N_RINGS) {
// 向外走一步的位置
moveTable[p][MOVE_DIRECTION_OUTWARD] = (r + 1) * N_SEATS + s;
}
}
#if 0
// 对于无斜线情况下的1、3、5、7位奇数位则都设为棋盘外点默认'\x00'
else {
// 向内走一步的位置设为随便棋盘外一点
moveTable[i * SEAT + j][2] = '\x00';
// 向外走一步的位置设为随便棋盘外一点
moveTable[i * SEAT + j][3] = '\x00';
}
#endif
}
}
#endif /* CONST_MOVE_TABLE */
#if 0
int sum = 0;
for (int i = 0; i < N_POINTS; i++) {
loggerDebug("/* %d */ {", i);
for (int j = 0; j < N_MOVE_DIRECTIONS; j++) {
if (j == N_MOVE_DIRECTIONS - 1)
loggerDebug("%d", moveTable[i][j]);
else
loggerDebug("%d, ", moveTable[i][j]);
sum += moveTable[i][j];
}
loggerDebug("},\n");
}
loggerDebug("sum = %d\n");
#endif
}
void MillGame::createMillTable()
{
#ifdef CONST_MILL_TABLE
@ -795,7 +540,7 @@ bool MillGame::setContext(const struct Rule *rule, step_t maxStepsLedToDraw, int
winner = NOBODY;
// 生成着法表
createMoveTable();
MoveList::createMoveTable(*this);
// 生成成三表
createMillTable();
@ -1121,7 +866,7 @@ bool MillGame::place(int pos, int time_p, int8_t rs)
int i;
for (i = 0; i < 4; i++) {
if (pos == moveTable[currentPos][i])
if (pos == MoveList::moveTable[currentPos][i])
break;
}
@ -1810,7 +1555,7 @@ int MillGame::getSurroundedEmptyPosCount(int pos, bool includeFobidden)
(context.nPiecesOnBoard_2 > currentRule.nPiecesAtLeast || !currentRule.allowFlyWhenRemainThreePieces))) {
int d, movePos;
for (d = 0; d < N_MOVE_DIRECTIONS; d++) {
movePos = moveTable[pos][d];
movePos = MoveList::moveTable[pos][d];
if (movePos) {
if (board_[movePos] == 0x00 ||
(includeFobidden && board_[movePos] == 0x0F)) {
@ -1857,7 +1602,7 @@ bool MillGame::isSurrounded(int pos)
(context.nPiecesOnBoard_2 > currentRule.nPiecesAtLeast || !currentRule.allowFlyWhenRemainThreePieces))) {
int i, movePos;
for (i = 0; i < 4; i++) {
movePos = moveTable[pos][i];
movePos = MoveList::moveTable[pos][i];
if (movePos && !board_[movePos])
break;
}
@ -1891,7 +1636,7 @@ bool MillGame::isAllSurrounded(char ch)
}
for (int d = 0; d < N_MOVE_DIRECTIONS; d++) {
movePos = moveTable[i][d];
movePos = MoveList::moveTable[i][d];
if (movePos && !board_[movePos])
return false;
}

View File

@ -556,16 +556,19 @@ public:
hash_t updateHashMisc();
#endif
private:
// 当前使用的规则
struct Rule currentRule {};
public: /* TODO: move to private */
// 棋局上下文
struct ChessContext context;
// 当前使用的规则
struct Rule currentRule
{
};
// 棋局上下文中的棋盘数据,单独提出来
int *board_;
private:
// 棋局哈希值
// uint64_t hash;

View File

@ -1,203 +1,203 @@
/*****************************************************************************
* Copyright (C) 2019 MillGame authors
*
* Authors: 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 <QtWidgets>
#include <QtNetwork>
#include "client.h"
#include "thread.h"
Client::Client(QWidget *parent, uint16_t port)
: QDialog(parent)
, hostCombo(new QComboBox)
, portLineEdit(new QLineEdit)
, getActionButton(new QPushButton(tr("Connect")))
, tcpSocket(new QTcpSocket(this))
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
hostCombo->setEditable(true);
hostCombo->addItem(QString("localhost"));
portLineEdit->setValidator(new QIntValidator(1, 65535, this));
auto hostLabel = new QLabel(tr("&Server name:"));
hostLabel->setBuddy(hostCombo);
auto portLabel = new QLabel(tr("S&erver port:"));
portLabel->setBuddy(portLineEdit);
statusLabel = new QLabel(tr("This Client requires that you run the "
"Server as well."));
getActionButton->setDefault(true);
getActionButton->setEnabled(false);
auto quitButton = new QPushButton(tr("Close"));
auto buttonBox = new QDialogButtonBox;
buttonBox->addButton(getActionButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
in.setDevice(tcpSocket);
in.setVersion(QDataStream::Qt_4_0);
connect(hostCombo, &QComboBox::editTextChanged,
this, &Client::enableGetActionButton);
connect(portLineEdit, &QLineEdit::textChanged,
this, &Client::enableGetActionButton);
connect(getActionButton, &QAbstractButton::clicked,
this, &Client::requestNewAction);
connect(quitButton, &QAbstractButton::clicked, this, &QWidget::close);
connect(tcpSocket, &QIODevice::readyRead, this, &Client::readAction);
connect(tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
this, &Client::displayError);
QGridLayout *mainLayout = nullptr;
if (QGuiApplication::styleHints()->showIsFullScreen() || QGuiApplication::styleHints()->showIsMaximized()) {
auto outerVerticalLayout = new QVBoxLayout(this);
outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
auto outerHorizontalLayout = new QHBoxLayout;
outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
auto groupBox = new QGroupBox(QGuiApplication::applicationDisplayName());
mainLayout = new QGridLayout(groupBox);
outerHorizontalLayout->addWidget(groupBox);
outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
outerVerticalLayout->addLayout(outerHorizontalLayout);
outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
} else {
mainLayout = new QGridLayout(this);
}
mainLayout->addWidget(hostLabel, 0, 0);
mainLayout->addWidget(hostCombo, 0, 1);
mainLayout->addWidget(portLabel, 1, 0);
mainLayout->addWidget(portLineEdit, 1, 1);
mainLayout->addWidget(statusLabel, 2, 0, 1, 2);
mainLayout->addWidget(buttonBox, 3, 0, 1, 2);
setWindowTitle(QGuiApplication::applicationDisplayName());
portLineEdit->setFocus();
portLineEdit->setText(QString::number(port));
QNetworkConfigurationManager manager;
if (manager.capabilities() & QNetworkConfigurationManager::NetworkSessionRequired) {
// Get saved network configuration
QSettings settings(QSettings::UserScope, QLatin1String("QtProject"));
settings.beginGroup(QLatin1String("QtNetwork"));
const QString id = settings.value(QLatin1String("DefaultNetworkConfiguration")).toString();
settings.endGroup();
// If the saved network configuration is not currently discovered use the system default
QNetworkConfiguration config = manager.configurationFromIdentifier(id);
if ((config.state() & QNetworkConfiguration::Discovered) !=
QNetworkConfiguration::Discovered) {
config = manager.defaultConfiguration();
}
networkSession = new QNetworkSession(config, this);
connect(networkSession, &QNetworkSession::opened, this, &Client::sessionOpened);
getActionButton->setEnabled(false);
statusLabel->setText(tr("Opening network session."));
networkSession->open();
}
}
void Client::requestNewAction()
{
getActionButton->setEnabled(false);
tcpSocket->abort();
tcpSocket->connectToHost(hostCombo->currentText(),
portLineEdit->text().toUShort());
}
void Client::readAction()
{
QString nextAction;
in >> nextAction;
if (nextAction == currentAction) {
QTimer::singleShot(0, this, &Client::requestNewAction);
return;
}
currentAction = nextAction;
statusLabel->setText(currentAction);
emit command(currentAction);
getActionButton->setEnabled(true);
QTimer::singleShot(10, this, &Client::requestNewAction);
}
void Client::displayError(QAbstractSocket::SocketError socketError)
{
switch (socketError) {
case QAbstractSocket::RemoteHostClosedError:
break;
case QAbstractSocket::HostNotFoundError:
QMessageBox::information(this, tr("Client"),
tr("The host was not found. Please check the "
"host name and port settings."));
break;
case QAbstractSocket::ConnectionRefusedError:
QMessageBox::information(this, tr("Client"),
tr("The connection was refused by the peer. "
"Make sure the server is running, "
"and check that the host name and port "
"settings are correct."));
break;
default:
QMessageBox::information(this, tr("Client"),
tr("The following error occurred: %1.")
.arg(tcpSocket->errorString()));
}
getActionButton->setEnabled(true);
}
void Client::enableGetActionButton()
{
getActionButton->setEnabled((!networkSession || networkSession->isOpen()) &&
!hostCombo->currentText().isEmpty() &&
!portLineEdit->text().isEmpty());
}
void Client::sessionOpened()
{
// Save the used configuration
QNetworkConfiguration config = networkSession->configuration();
QString id;
if (config.type() == QNetworkConfiguration::UserChoice)
id = networkSession->sessionProperty(QLatin1String("UserChoiceConfiguration")).toString();
else
id = config.identifier();
QSettings settings(QSettings::UserScope, QLatin1String("QtProject"));
settings.beginGroup(QLatin1String("QtNetwork"));
settings.setValue(QLatin1String("DefaultNetworkConfiguration"), id);
settings.endGroup();
statusLabel->setText(tr("This Client requires that you run the "
"Server as well."));
enableGetActionButton();
}
/*****************************************************************************
* Copyright (C) 2019 MillGame authors
*
* Authors: 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 <QtWidgets>
#include <QtNetwork>
#include "client.h"
#include "thread.h"
Client::Client(QWidget *parent, uint16_t port)
: QDialog(parent)
, hostCombo(new QComboBox)
, portLineEdit(new QLineEdit)
, getActionButton(new QPushButton(tr("Connect")))
, tcpSocket(new QTcpSocket(this))
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
hostCombo->setEditable(true);
hostCombo->addItem(QString("localhost"));
portLineEdit->setValidator(new QIntValidator(1, 65535, this));
auto hostLabel = new QLabel(tr("&Server name:"));
hostLabel->setBuddy(hostCombo);
auto portLabel = new QLabel(tr("S&erver port:"));
portLabel->setBuddy(portLineEdit);
statusLabel = new QLabel(tr("This Client requires that you run the "
"Server as well."));
getActionButton->setDefault(true);
getActionButton->setEnabled(false);
auto quitButton = new QPushButton(tr("Close"));
auto buttonBox = new QDialogButtonBox;
buttonBox->addButton(getActionButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
in.setDevice(tcpSocket);
in.setVersion(QDataStream::Qt_4_0);
connect(hostCombo, &QComboBox::editTextChanged,
this, &Client::enableGetActionButton);
connect(portLineEdit, &QLineEdit::textChanged,
this, &Client::enableGetActionButton);
connect(getActionButton, &QAbstractButton::clicked,
this, &Client::requestNewAction);
connect(quitButton, &QAbstractButton::clicked, this, &QWidget::close);
connect(tcpSocket, &QIODevice::readyRead, this, &Client::readAction);
connect(tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
this, &Client::displayError);
QGridLayout *mainLayout = nullptr;
if (QGuiApplication::styleHints()->showIsFullScreen() || QGuiApplication::styleHints()->showIsMaximized()) {
auto outerVerticalLayout = new QVBoxLayout(this);
outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
auto outerHorizontalLayout = new QHBoxLayout;
outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
auto groupBox = new QGroupBox(QGuiApplication::applicationDisplayName());
mainLayout = new QGridLayout(groupBox);
outerHorizontalLayout->addWidget(groupBox);
outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
outerVerticalLayout->addLayout(outerHorizontalLayout);
outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
} else {
mainLayout = new QGridLayout(this);
}
mainLayout->addWidget(hostLabel, 0, 0);
mainLayout->addWidget(hostCombo, 0, 1);
mainLayout->addWidget(portLabel, 1, 0);
mainLayout->addWidget(portLineEdit, 1, 1);
mainLayout->addWidget(statusLabel, 2, 0, 1, 2);
mainLayout->addWidget(buttonBox, 3, 0, 1, 2);
setWindowTitle(QGuiApplication::applicationDisplayName());
portLineEdit->setFocus();
portLineEdit->setText(QString::number(port));
QNetworkConfigurationManager manager;
if (manager.capabilities() & QNetworkConfigurationManager::NetworkSessionRequired) {
// Get saved network configuration
QSettings settings(QSettings::UserScope, QLatin1String("QtProject"));
settings.beginGroup(QLatin1String("QtNetwork"));
const QString id = settings.value(QLatin1String("DefaultNetworkConfiguration")).toString();
settings.endGroup();
// If the saved network configuration is not currently discovered use the system default
QNetworkConfiguration config = manager.configurationFromIdentifier(id);
if ((config.state() & QNetworkConfiguration::Discovered) !=
QNetworkConfiguration::Discovered) {
config = manager.defaultConfiguration();
}
networkSession = new QNetworkSession(config, this);
connect(networkSession, &QNetworkSession::opened, this, &Client::sessionOpened);
getActionButton->setEnabled(false);
statusLabel->setText(tr("Opening network session."));
networkSession->open();
}
}
void Client::requestNewAction()
{
getActionButton->setEnabled(false);
tcpSocket->abort();
tcpSocket->connectToHost(hostCombo->currentText(),
portLineEdit->text().toUShort());
}
void Client::readAction()
{
QString nextAction;
in >> nextAction;
if (nextAction == currentAction) {
QTimer::singleShot(0, this, &Client::requestNewAction);
return;
}
currentAction = nextAction;
statusLabel->setText(currentAction);
emit command(currentAction);
getActionButton->setEnabled(true);
QTimer::singleShot(10, this, &Client::requestNewAction);
}
void Client::displayError(QAbstractSocket::SocketError socketError)
{
switch (socketError) {
case QAbstractSocket::RemoteHostClosedError:
break;
case QAbstractSocket::HostNotFoundError:
QMessageBox::information(this, tr("Client"),
tr("The host was not found. Please check the "
"host name and port settings."));
break;
case QAbstractSocket::ConnectionRefusedError:
QMessageBox::information(this, tr("Client"),
tr("The connection was refused by the peer. "
"Make sure the server is running, "
"and check that the host name and port "
"settings are correct."));
break;
default:
QMessageBox::information(this, tr("Client"),
tr("The following error occurred: %1.")
.arg(tcpSocket->errorString()));
}
getActionButton->setEnabled(true);
}
void Client::enableGetActionButton()
{
getActionButton->setEnabled((!networkSession || networkSession->isOpen()) &&
!hostCombo->currentText().isEmpty() &&
!portLineEdit->text().isEmpty());
}
void Client::sessionOpened()
{
// Save the used configuration
QNetworkConfiguration config = networkSession->configuration();
QString id;
if (config.type() == QNetworkConfiguration::UserChoice)
id = networkSession->sessionProperty(QLatin1String("UserChoiceConfiguration")).toString();
else
id = config.identifier();
QSettings settings(QSettings::UserScope, QLatin1String("QtProject"));
settings.beginGroup(QLatin1String("QtNetwork"));
settings.setValue(QLatin1String("DefaultNetworkConfiguration"), id);
settings.endGroup();
statusLabel->setText(tr("This Client requires that you run the "
"Server as well."));
enableGetActionButton();
}

View File

@ -1,4 +1,4 @@
/*****************************************************************************
/*****************************************************************************
* Copyright (C) 2019 MillGame authors
*
* Authors: Calcitem <calcitem@outlook.com>