添加未完成的Hash代码但暂时关闭宏
This commit is contained in:
parent
702a562bbd
commit
527fec2856
|
@ -34,7 +34,9 @@ void AiThread::setAi(const NineChess &chess)
|
|||
this->chess_ = &chess;
|
||||
ai_ab.setChess(*(this->chess_));
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
ai_ab.clearHashMap();
|
||||
#endif
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#define DEBUG
|
||||
//#define DEBUG
|
||||
|
||||
//#define RANDOM_MOVE
|
||||
#define RANDOM_MOVE
|
||||
|
||||
//#define DEAL_WITH_HORIZON_EFFECT
|
||||
#define DEAL_WITH_HORIZON_EFFECT
|
||||
|
||||
//#define RANDOM_BEST_MOVE
|
||||
|
||||
#define HASH_MAP_ENABLE
|
||||
//#define HASH_MAP_ENABLE
|
||||
|
||||
//#define DONOT_DELETE_TREE
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
|||
//#define DONOT_PLAY_SOUND
|
||||
|
||||
#ifdef DEBUG
|
||||
#define GAME_PLACING_FIXED_DEPTH 3
|
||||
#define GAME_PLACING_FIXED_DEPTH 4
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
|
@ -48,7 +48,7 @@
|
|||
#define DRAW_SEAT_NUMBER
|
||||
#endif
|
||||
|
||||
//#define IDS_SUPPORT
|
||||
#define IDS_SUPPORT
|
||||
|
||||
#define SAVE_CHESSBOOK_WHEN_ACTION_NEW_TRIGGERED
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ template <typename T>
|
|||
T& HashMap<T>::at(uint64_t i)
|
||||
{
|
||||
if (i >= capacity) {
|
||||
qDebug() << "索引超过最大值";
|
||||
qDebug() << "Error";
|
||||
return pool[0];
|
||||
}
|
||||
return pool[i];
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#ifndef HASHMAP_H
|
||||
#define HASHMAP_H
|
||||
|
||||
#include <limits>
|
||||
#include <qDebug>
|
||||
|
@ -8,7 +9,7 @@ class HashMap
|
|||
{
|
||||
public:
|
||||
HashMap();
|
||||
HashMap(size_t capacity);
|
||||
HashMap(size_t capacity);
|
||||
~HashMap();
|
||||
|
||||
enum FindResult
|
||||
|
@ -25,6 +26,8 @@ public:
|
|||
return pool[addr];
|
||||
}
|
||||
|
||||
uint64_t hashToAddr(uint64_t hash);
|
||||
|
||||
T &find(uint64_t hash)
|
||||
{
|
||||
uint64_t addr = hashToAddr(hash);
|
||||
|
@ -39,17 +42,15 @@ public:
|
|||
|
||||
void insert(uint64_t hash, const T &hashValue);
|
||||
|
||||
protected:
|
||||
bool construct();
|
||||
|
||||
private:
|
||||
size_t capacity;
|
||||
size_t size;
|
||||
|
||||
T *pool;
|
||||
T *pool;
|
||||
|
||||
bool construct();
|
||||
|
||||
uint64_t hashToAddr(uint64_t hash);
|
||||
};
|
||||
|
||||
|
||||
#endif // HASHMAP_H
|
||||
#endif // HASHMAP_H
|
||||
|
|
|
@ -121,11 +121,14 @@ NineChess::NineChess()
|
|||
{
|
||||
// 单独提出 board 等数据,免得每次都写 context.board;
|
||||
board_ = context.board;
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
//hash_ = &context.hash;
|
||||
//zobrist_ = &context.zobrist;
|
||||
|
||||
// 创建哈希数据
|
||||
constructHash();
|
||||
#endif
|
||||
|
||||
// 默认选择第1号规则,即“打三棋”
|
||||
setContext(&RULES[1]);
|
||||
|
@ -176,6 +179,7 @@ NineChess::~NineChess()
|
|||
{
|
||||
}
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
void NineChess::constructHash()
|
||||
{
|
||||
context.hash = 0ull;
|
||||
|
@ -193,6 +197,7 @@ void NineChess::constructHash()
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif /* HASH_MAP_ENABLE */
|
||||
|
||||
NineChess::Player NineChess::getOpponent(NineChess::Player player)
|
||||
{
|
||||
|
@ -324,8 +329,7 @@ void NineChess::createMillTable()
|
|||
// 设置棋局状态和棋盘数据,用于初始化
|
||||
bool NineChess::setContext(const struct Rule *rule, int maxStepsLedToDraw, int maxTimeLedToLose,
|
||||
int initialStep, int flags, const char *board,
|
||||
int nPiecesInHand_1, int nPiecesInHand_2, int nPiecesNeedRemove,
|
||||
uint64_t hash, uint64_t hashCheckCode)
|
||||
int nPiecesInHand_1, int nPiecesInHand_2, int nPiecesNeedRemove)
|
||||
{
|
||||
// 有效性判断
|
||||
if (maxStepsLedToDraw < 0 || maxTimeLedToLose < 0 || initialStep < 0 ||
|
||||
|
@ -391,12 +395,16 @@ bool NineChess::setContext(const struct Rule *rule, int maxStepsLedToDraw, int m
|
|||
// 当前棋局(3×8)
|
||||
if (board == nullptr) {
|
||||
memset(context.board, 0, sizeof(context.board));
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
context.hash = 0ull;
|
||||
context.hashCheckCode = 0ull;
|
||||
#endif
|
||||
} else {
|
||||
memcpy(context.board, board, sizeof(context.board));
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
context.hash = hash;
|
||||
context.hashCheckCode = hashCheckCode;
|
||||
#endif
|
||||
}
|
||||
|
||||
// 计算盘面子数
|
||||
|
@ -493,8 +501,7 @@ bool NineChess::setContext(const struct Rule *rule, int maxStepsLedToDraw, int m
|
|||
}
|
||||
|
||||
void NineChess::getContext(struct Rule &rule, int &step, int &flags,
|
||||
int *&board, int &nPiecesInHand_1, int &nPiecesInHand_2, int &num_NeedRemove,
|
||||
uint64_t &hash, uint64_t &hashCheckCode)
|
||||
int *&board, int &nPiecesInHand_1, int &nPiecesInHand_2, int &num_NeedRemove)
|
||||
{
|
||||
rule = this->currentRule;
|
||||
step = this->currentStep;
|
||||
|
@ -503,7 +510,9 @@ void NineChess::getContext(struct Rule &rule, int &step, int &flags,
|
|||
nPiecesInHand_1 = context.nPiecesInHand_1;
|
||||
nPiecesInHand_2 = context.nPiecesInHand_2;
|
||||
num_NeedRemove = context.nPiecesNeedRemove;
|
||||
hashCheckCode = context.hashCheckCode;
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
hash = context.hash;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool NineChess::reset()
|
||||
|
@ -547,9 +556,11 @@ bool NineChess::reset()
|
|||
// 用时置零
|
||||
elapsedMS_1 = elapsedMS_2 = 0;
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 哈希以及哈希校验码归零
|
||||
context.hash = 0;
|
||||
context.hashCheckCode = 0;
|
||||
#endif
|
||||
|
||||
// 提示
|
||||
setTips();
|
||||
|
@ -703,7 +714,9 @@ bool NineChess::place(int c, int p, long time_p /* = -1*/)
|
|||
}
|
||||
|
||||
board_[pos] = piece;
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(pos);
|
||||
#endif
|
||||
move_ = pos;
|
||||
player_ms = update(time_p);
|
||||
sprintf(cmdline, "(%1u,%1u) %02u:%02u.%03u",
|
||||
|
@ -792,9 +805,13 @@ bool NineChess::place(int c, int p, long time_p /* = -1*/)
|
|||
c, p, player_ms / 60000, (player_ms % 60000) / 1000, player_ms % 1000);
|
||||
cmdlist.push_back(string(cmdline));
|
||||
board_[pos] = board_[currentPos];
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(pos);
|
||||
#endif
|
||||
board_[currentPos] = '\x00';
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(currentPos);
|
||||
#endif
|
||||
currentPos = pos;
|
||||
currentStep++;
|
||||
n = addMills(currentPos);
|
||||
|
@ -880,7 +897,9 @@ bool NineChess::capture(int c, int p, long time_p /* = -1*/)
|
|||
currentPos = 0;
|
||||
context.nPiecesNeedRemove--;
|
||||
currentStep++;
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(pos);
|
||||
#endif
|
||||
// 去子完成
|
||||
|
||||
// 如果决出胜负
|
||||
|
@ -1030,7 +1049,9 @@ bool NineChess::place(int pos)
|
|||
}
|
||||
|
||||
board_[pos] = piece;
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(pos);
|
||||
#endif
|
||||
move_ = pos;
|
||||
currentPos = pos;
|
||||
//step++;
|
||||
|
@ -1106,9 +1127,13 @@ bool NineChess::place(int pos)
|
|||
// 移子
|
||||
move_ = (currentPos << 8) + pos;
|
||||
board_[pos] = board_[currentPos];
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(pos);
|
||||
#endif
|
||||
board_[currentPos] = '\x00';
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(currentPos);
|
||||
#endif
|
||||
currentPos = pos;
|
||||
//step++;
|
||||
n = addMills(currentPos);
|
||||
|
@ -1185,7 +1210,9 @@ bool NineChess::capture(int pos)
|
|||
move_ = -pos;
|
||||
currentPos = 0;
|
||||
context.nPiecesNeedRemove--;
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(pos);
|
||||
#endif
|
||||
//step++;
|
||||
// 去子完成
|
||||
|
||||
|
@ -1290,6 +1317,8 @@ bool NineChess::choose(int pos)
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
|
||||
uint64_t NineChess::getHash()
|
||||
{
|
||||
return context.hash;
|
||||
|
@ -1341,6 +1370,7 @@ uint64_t NineChess::updateHash(int pos)
|
|||
|
||||
return context.hashCheckCode; // TODO: 返回什么
|
||||
}
|
||||
#endif /* HASH_MAP_ENABLE */
|
||||
|
||||
bool NineChess::giveup(Player loser)
|
||||
{
|
||||
|
@ -1775,7 +1805,9 @@ void NineChess::cleanForbiddenPoints()
|
|||
pos = i * N_SEATS + j;
|
||||
if (board_[pos] == '\x0f') {
|
||||
board_[pos] = '\x00';
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
updateHash(pos);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,7 +179,8 @@ public:
|
|||
*/
|
||||
int board[N_POINTS];
|
||||
|
||||
// 局面哈希的校验码,校验码相同 才能认为是同一局面
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 局面哈希的校验码(过时)
|
||||
uint64_t hashCheckCode;
|
||||
|
||||
// 局面的哈希值
|
||||
|
@ -189,16 +190,17 @@ public:
|
|||
uint16_t hashAddr;
|
||||
|
||||
// 标记处于走子阶段的哈希
|
||||
uint16_t gameMovingHash;
|
||||
uint64_t gameMovingHash;
|
||||
|
||||
// 吃子动作的哈希
|
||||
uint16_t actionCaptureHash;
|
||||
uint64_t actionCaptureHash;
|
||||
|
||||
// 标记轮到玩家2行棋的哈希
|
||||
uint16_t player2sTurnHash;
|
||||
uint64_t player2sTurnHash;
|
||||
|
||||
// Zobrist 数组
|
||||
uint64_t zobrist[N_POINTS][POINT_TYPE_COUNT];
|
||||
#endif /* HASH_MAP_ENABLE */
|
||||
|
||||
// 局面阶段标识
|
||||
enum NineChess::GameStage stage;
|
||||
|
@ -283,15 +285,12 @@ public:
|
|||
const char *board = nullptr, // 默认空棋盘
|
||||
int nPiecesInHand_1 = 12, // 玩家1剩余未放置子数
|
||||
int nPiecesInHand_2 = 12, // 玩家2剩余未放置子数
|
||||
int nPiecesNeedRemove = 0, // 尚待去除的子数
|
||||
uint64_t hash = 0ull, // 哈希值
|
||||
uint64_t hashCheckCode = 0ull // 哈希校验码
|
||||
int nPiecesNeedRemove = 0 // 尚待去除的子数
|
||||
);
|
||||
|
||||
// 获取棋局状态和棋盘上下文
|
||||
void getContext(struct Rule &rule, int &step, int &flags, int *&board,
|
||||
int &nPiecesInHand_1, int &p2_nPiecesInHand_2InHand, int &nPiecesNeedRemove,
|
||||
uint64_t &hash, uint64_t &hashCheckCode);
|
||||
int &nPiecesInHand_1, int &p2_nPiecesInHand_2InHand, int &nPiecesNeedRemove);
|
||||
|
||||
// 获取当前规则
|
||||
const struct Rule *getRule() const
|
||||
|
@ -495,10 +494,12 @@ protected:
|
|||
bool place(int pos);
|
||||
bool capture(int pos);
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// hash相关
|
||||
uint64_t getHash();
|
||||
uint64_t getHashCheckCode();
|
||||
uint64_t updateHash(int pos);
|
||||
#endif /* HASH_MAP_ENABLE */
|
||||
|
||||
private:
|
||||
// 当前使用的规则
|
||||
|
|
|
@ -22,6 +22,10 @@ NineChessAi_ab::NineChessAi_ab() :
|
|||
hashHitCount(0)
|
||||
{
|
||||
buildRoot();
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
hashMap.construct(); // TODO
|
||||
#endif
|
||||
}
|
||||
|
||||
NineChessAi_ab::~NineChessAi_ab()
|
||||
|
@ -61,7 +65,9 @@ struct NineChessAi_ab::Node *NineChessAi_ab::addNode(Node *parent, int value, in
|
|||
newNode->alpha = -INF_VALUE;
|
||||
newNode->beta = INF_VALUE;
|
||||
newNode->result = 0;
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
newNode->isHash = false;
|
||||
#endif
|
||||
newNode->visited = false;
|
||||
|
||||
int c, p;
|
||||
|
@ -94,8 +100,7 @@ struct NineChessAi_ab::Node *NineChessAi_ab::addNode(Node *parent, int value, in
|
|||
//mutex NineChessAi_ab::hashMapMutex;
|
||||
//HashMap<NineChessAi_ab::HashValue> NineChessAi_ab::hashmap;
|
||||
|
||||
mutex hashMapMutex;
|
||||
HashMap<NineChessAi_ab::HashValue> hashmap;
|
||||
|
||||
|
||||
#ifdef MOVE_PRIORITY_TABLE_SUPPORT
|
||||
#ifdef RANDOM_MOVE
|
||||
|
@ -359,10 +364,12 @@ void NineChessAi_ab::deleteTree(Node *node)
|
|||
|
||||
void NineChessAi_ab::setChess(const NineChess &chess)
|
||||
{
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 如果规则改变,重建hashmap
|
||||
if (strcmp(this->chess_.currentRule.name, chess.currentRule.name)) {
|
||||
clearHashMap();
|
||||
}
|
||||
#endif
|
||||
|
||||
this->chess_ = chess;
|
||||
chessTemp = chess;
|
||||
|
@ -602,8 +609,10 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node)
|
|||
// 临时增加的深度,克服水平线效应用
|
||||
int epsilon = 0;
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 哈希类型
|
||||
enum HashType hashf = hashfALPHA;
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_AB_TREE
|
||||
node->depth = depth;
|
||||
|
@ -612,10 +621,12 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node)
|
|||
// 初始化
|
||||
node->isLeaf = false;
|
||||
node->isTimeout = false;
|
||||
node->isHash = false;
|
||||
node->visited = true;
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
node->isHash = false;
|
||||
node->hash = 0;
|
||||
#endif
|
||||
#endif // HASH_MAP_ENABLE
|
||||
#endif // DEBUG_AB_TREE
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 检索 hashmap
|
||||
|
@ -758,8 +769,9 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node)
|
|||
// α 为走棋一方搜索到的最好值,任何比它小的值对当前结点的走棋方都没有意义
|
||||
// 如果某个着法的结果小于或等于 α,那么它就是很差的着法,因此可以抛弃
|
||||
alpha = std::max(value, alpha);
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
hashf = hashfALPHA; // ????
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -776,8 +788,9 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node)
|
|||
// 如果某个着法的结果大于或等于 β,那么整个结点就作废了,因为对手不希望走到这个局面,而它有别的着法可以避免到达这个局面。
|
||||
// 因此如果我们找到的评价大于或等于β,就证明了这个结点是不会发生的,因此剩下的合理着法没有必要再搜索。
|
||||
beta = std::min(value, beta);
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
hashf = hashfBETA; // ????
|
||||
#endif
|
||||
}
|
||||
|
||||
// 如果某个着法的结果大于 α 但小于β,那么这个着法就是走棋一方可以考虑走的
|
||||
|
@ -834,16 +847,18 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node)
|
|||
return node->value;
|
||||
}
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
int NineChessAi_ab::recordHash(const HashValue &hashValue)
|
||||
{
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
hashMapMutex.lock();
|
||||
hashmap.insert(hashValue.hash, hashValue);
|
||||
hashMap.insert(hashValue.hash, hashValue);
|
||||
hashMapMutex.unlock();
|
||||
#endif // HASH_MAP_ENABLE
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* NineChessAi_ab::bestMove()
|
||||
{
|
||||
|
@ -940,9 +955,10 @@ const char *NineChessAi_ab::move2string(int move)
|
|||
return cmdline;
|
||||
}
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
NineChessAi_ab::HashValue NineChessAi_ab::findHash(uint64_t hash)
|
||||
{
|
||||
NineChessAi_ab::HashValue hashValue = hashmap.find(hash);
|
||||
NineChessAi_ab::HashValue hashValue = hashMap.find(hash);
|
||||
|
||||
// TODO: 变换局面
|
||||
#if 0
|
||||
|
@ -974,6 +990,7 @@ NineChessAi_ab::HashValue NineChessAi_ab::findHash(uint64_t hash)
|
|||
void NineChessAi_ab::clearHashMap()
|
||||
{
|
||||
hashMapMutex.lock();
|
||||
hashmap.clear();
|
||||
hashMap.clear();
|
||||
hashMapMutex.unlock();
|
||||
}
|
||||
#endif /* HASH_MAP_ENABLE */
|
|
@ -27,6 +27,7 @@ using namespace std;
|
|||
class NineChessAi_ab
|
||||
{
|
||||
public:
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 定义哈希值的类型
|
||||
enum HashType : int16_t
|
||||
{
|
||||
|
@ -46,6 +47,7 @@ public:
|
|||
uint64_t hash;
|
||||
enum HashType type;
|
||||
};
|
||||
#endif /* HASH_MAP_ENABLE */
|
||||
|
||||
// 定义一个节点结构体
|
||||
struct Node
|
||||
|
@ -57,9 +59,11 @@ public:
|
|||
struct Node* parent; // 父节点
|
||||
size_t id; // 结点编号
|
||||
int rand; // 随机数,对于 value 一致的结点随机排序用
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
uint64_t hash;
|
||||
uint64_t hashCheckCode;
|
||||
bool isHash; // 是否从 Hash 读取
|
||||
#endif /* HASH_MAP_ENABLE */
|
||||
bool pruned; // 是否在此处剪枝
|
||||
#ifdef DEBUG_AB_TREE
|
||||
string cmd;
|
||||
|
@ -116,13 +120,20 @@ public:
|
|||
// 返回最佳走法的命令行
|
||||
const char *bestMove();
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 清空哈希表
|
||||
void clearHashMap();
|
||||
#endif
|
||||
|
||||
// 比较函数
|
||||
static bool nodeLess(const Node *first, const Node *second);
|
||||
static bool nodeGreater(const Node *first, const Node *second);
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
static std::mutex hashMapMutex;
|
||||
static HashMap<HashValue> hashMap;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// 生成所有合法的着法并建立子节点
|
||||
void generateLegalMoves(Node *node);
|
||||
|
@ -139,8 +150,10 @@ protected:
|
|||
// 增加新节点
|
||||
struct Node *addNode(Node *parent, int value, NineChess::move_t move, enum NineChess::Player player);
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 插入哈希表
|
||||
int recordHash(const HashValue &hashValue);
|
||||
#endif
|
||||
|
||||
// 评价函数
|
||||
int evaluate(Node *node);
|
||||
|
@ -161,8 +174,10 @@ protected:
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HASH_MAP_ENABLE
|
||||
// 查找哈希表
|
||||
HashValue findHash(uint64_t hash);
|
||||
#endif
|
||||
|
||||
private:
|
||||
// 原始模型
|
||||
|
@ -212,7 +227,4 @@ private:
|
|||
char cmdline[32];
|
||||
};
|
||||
|
||||
extern mutex hashMapMutex;
|
||||
extern HashMap<NineChessAi_ab::HashValue> hashmap;
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue