diff --git a/Rapfi/command/gomocup.cpp b/Rapfi/command/gomocup.cpp index 0b202d69..7754f55e 100644 --- a/Rapfi/command/gomocup.cpp +++ b/Rapfi/command/gomocup.cpp @@ -60,6 +60,36 @@ std::filesystem::path readPathFromInput(std::istream &in = std::cin) return pathFromConsoleString(path); } +bool checkLastMoveIsNotPass(Board &board) +{ + if (board.passMoveCount() > 0 && board.getLastMove() == Pos::PASS) { + ERRORL("Consecutive pass is not supported. " + "There must be one non-pass move between two pass moves."); + return true; + } + return false; +} + +/// Parse a coord in form 'x,y' from in stream and check if the pos is legal. +/// Legal means the pos must be an empty cell or an non-consecutive pass move. +std::optional parseLegalCoord(std::istream &is, Board &board) +{ + int x = -2, y = -2; + char comma; + is >> x >> comma >> y; + + Pos pos = inputCoordConvert(x, y, board.size()); + if (board.isLegal(pos)) { + if (checkLastMoveIsNotPass(board)) + return std::nullopt; + return pos; + } + else { + ERRORL("Coord is not valid or empty."); + return std::nullopt; + } +} + } // namespace namespace Command::GomocupProtocol { @@ -578,22 +608,13 @@ void begin() void turn() { - int x, y; - char comma; - std::cin >> x >> comma >> y; + auto pos = parseLegalCoord(std::cin, *board); + if (!pos.has_value()) + return; options.multiPV = 1; options.balanceMode = Search::SearchOptions::BALANCE_NONE; - Pos pos = inputCoordConvert(x, y, board->size()); - if (pos == Pos::PASS) - board->doPassMove(); - else if (pos.valid() && board->isEmpty(pos)) - board->move(options.rule, pos); - else { - ERRORL("Coord is not valid or empty."); - return; - } - + board->move(options.rule, *pos); think(*board); } @@ -606,42 +627,31 @@ void getPosition(bool startThink) // Read position sequence enum SideFlag { SELF = 1, OPPO = 2, WALL = 3 }; std::vector> position; - bool lastMoveIsPass = false; + while (true) { std::string coordStr; std::cin >> coordStr; upperInplace(coordStr); - if (coordStr == "DONE") + if (coordStr == "DONE" || std::cin.eof()) break; - int x = -1, y = -1, color = -1; + std::optional pos; + int color = -1; { - char comma; std::stringstream ss; + char comma; ss << coordStr; - ss >> x >> comma >> y >> comma >> color; + pos = parseLegalCoord(ss, *board); + ss >> comma >> color; } - Pos pos = inputCoordConvert(x, y, board->size()); - SideFlag side = (SideFlag)color; - - if ((pos == Pos::PASS && (side == SELF || side == OPPO)) - || (pos.valid() && board->isEmpty(pos) - && (side == SELF || side == OPPO || side == WALL))) { - if (pos == Pos::PASS && lastMoveIsPass) { - ERRORL("Consecutive pass is not supported. " - "There must be one non-pass move between two passes."); - continue; - } - - position.emplace_back(pos, side); - - if (side != WALL) - lastMoveIsPass = pos == Pos::PASS; + if (pos.has_value()) { + if (color == SELF || color == OPPO || color == WALL && *pos != Pos::NONE) + position.emplace_back(*pos, static_cast(color)); + else + ERRORL("Color is not a valid value, must be one of [1, 2, 3]."); } - else - ERRORL("Coord is not valid or empty, or color is not valid."); } // The first move (either real move or pass) is always considered as BLACK @@ -659,20 +669,20 @@ void getPosition(bool startThink) continue; // Make sure current side to move correspond to the input side - // This will not record pass move in board history + // If not, we add an extra PASS move to flip the side if (side == SELF && board->sideToMove() != selfColor - || side == OPPO && board->sideToMove() != ~selfColor) - board->flipSide(); + || side == OPPO && board->sideToMove() != ~selfColor) { + if (checkLastMoveIsNotPass(*board)) + return; - if (pos == Pos::PASS) - board->doPassMove(); - else - board->move(options.rule, pos); - } + board->move(options.rule, Pos::PASS); + } - // Make sure the side to move is correct - if (board->sideToMove() != selfColor) - board->flipSide(); + if (pos == Pos::PASS && checkLastMoveIsNotPass(*board)) + return; + + board->move(options.rule, pos); + } // Start thinking if needed if (startThink) @@ -681,7 +691,7 @@ void getPosition(bool startThink) void getBlock(bool remove = false) { - int x, y, color; + int x, y; char comma; std::string coordStr; std::stringstream ss; @@ -695,10 +705,10 @@ void getBlock(bool remove = false) ss.clear(); ss << coordStr; - ss >> x >> comma >> y >> comma >> color; + ss >> x >> comma >> y; Pos pos = inputCoordConvert(x, y, board->size()); - if (!pos.valid() || pos == Pos::PASS) + if (!board->isInBoard(pos) || pos == Pos::PASS) ERRORL("Block coord is a pass or invalid."); bool alreadyInList = std::count(options.blockMoves.begin(), options.blockMoves.end(), pos); @@ -762,19 +772,12 @@ void getDatabasePosition() if (coordStr == "DONE") break; - int x = -1, y = -1; - { - char comma; - std::stringstream ss; - ss << coordStr; - ss >> x >> comma >> y; - } + std::stringstream ss; + ss << coordStr; + auto pos = parseLegalCoord(ss, *board); - Pos pos = inputCoordConvert(x, y, board->size()); - if (pos.valid() && board->isEmpty(pos)) - board->move(options.rule, pos); - else - ERRORL("Coord is not valid or empty."); + if (pos.has_value()) + board->move(options.rule, *pos); } } @@ -931,25 +934,15 @@ void editDatabaseBoardLabel() std::getline(std::cin, newText); getDatabasePosition(); - // Parse coord into pos - Pos pos = Pos::NONE; - { - int x = -1, y = -1; - char comma; - std::stringstream ss; - ss << coordStr; - ss >> x >> comma >> y; - pos = inputCoordConvert(x, y, board->size()); - } - - if (!pos.valid() || !board->isEmpty(pos)) { - ERRORL("Coord is not valid or empty."); + std::stringstream ss; + ss << coordStr; + auto pos = parseLegalCoord(ss, *board); + if (!pos.has_value()) return; - } if (Search::Threads.dbStorage() && !Config::DatabaseReadonlyMode) { DBClient dbClient(*Search::Threads.dbStorage(), RECORD_MASK_TEXT); - dbClient.setBoardText(*board, options.rule, pos, ConsoleCPToUTF8(newText)); + dbClient.setBoardText(*board, options.rule, *pos, ConsoleCPToUTF8(newText)); } } diff --git a/Rapfi/core/iohelper.cpp b/Rapfi/core/iohelper.cpp index 0a6afc44..7b153d39 100644 --- a/Rapfi/core/iohelper.cpp +++ b/Rapfi/core/iohelper.cpp @@ -31,7 +31,7 @@ #include #endif -constexpr int PASS_COORD_X = 0; +constexpr int PASS_COORD_X = -1; constexpr int PASS_COORD_Y = -1; std::ostream &operator<<(std::ostream &os, SyncFlag f) diff --git a/Rapfi/database/dbclient.cpp b/Rapfi/database/dbclient.cpp index 32210f49..a9fc5bff 100644 --- a/Rapfi/database/dbclient.cpp +++ b/Rapfi/database/dbclient.cpp @@ -609,7 +609,7 @@ std::vector> DBClient::queryBoardTexts(const Board & void DBClient::setBoardText(const Board &board, Rule rule, Pos pos, std::string text) { - if (!board.isEmpty(pos)) + if (!board.isLegal(pos)) return; DBRecord record, childRecord; diff --git a/Rapfi/database/dbutils.cpp b/Rapfi/database/dbutils.cpp index cb1f5dbd..ae9e8c28 100644 --- a/Rapfi/database/dbutils.cpp +++ b/Rapfi/database/dbutils.cpp @@ -41,10 +41,7 @@ void copyDatabasePathToRoot(DBStorage &dbSrc, DBStorage &dbDst, Board &board, Ru while (board.ply() > 0) { Pos move = board.getLastMove(); path.push_back(move); - if (move == Pos::PASS) - board.undoPassMove(); - else - board.undo(rule); + board.undo(rule); } DBRecord record, tempRecord; @@ -53,11 +50,7 @@ void copyDatabasePathToRoot(DBStorage &dbSrc, DBStorage &dbDst, Board &board, Ru Pos pos = path.back(); path.pop_back(); - if (board.getLastMove() == Pos::PASS) - board.doPassMove(); - else - board.move(rule, pos); - + board.move(rule, pos); key = constructDBKey(board, rule); if (dbSrc.get(key, record, RECORD_MASK_ALL) @@ -458,7 +451,7 @@ size_t RenlibReader::traverse(int boardSize, Rule rule, std::function(boardSize); @@ -519,7 +512,7 @@ RenlibReader::LibNode RenlibReader::readNode() uint8_t move = popByte(); uint8_t flag = popByte(); LibNode node { - move ? Pos {(move & 0x0f) - 1, (move & 0xf0) >> 4} : Pos::NONE, + move ? Pos {(move & 0x0f) - 1, (move & 0xf0) >> 4} : Pos::PASS, NodeFlag(flag), }; @@ -571,10 +564,10 @@ size_t RenlibReader::processNode(Board &board, do { bool ignoreChildren = false; - if (node->move == Pos::NONE) { + if (node->move == Pos::PASS) { if (board.passMoveCount() >= board.cellCount()) throw std::runtime_error("too many pass move"); - board.doPassMove(); + board.move(rule, Pos::PASS); } else if (board.isInBoard(node->move)) { if (board.isEmpty(node->move)) { @@ -613,12 +606,8 @@ size_t RenlibReader::processNode(Board &board, } // Undo the move - if (!ignoreChildren) { - if (board.getLastMove() == Pos::PASS) - board.undoPassMove(); - else - board.undo(rule); - } + if (!ignoreChildren) + board.undo(rule); // Process next sibling node if (node->hasSibling()) { diff --git a/Rapfi/eval/evaluator.cpp b/Rapfi/eval/evaluator.cpp index 57649f8c..fbe683e6 100644 --- a/Rapfi/eval/evaluator.cpp +++ b/Rapfi/eval/evaluator.cpp @@ -126,13 +126,11 @@ void Evaluator::syncWithBoard(const Board &board) for (int i = 0; i < board.ply(); i++) { Pos pos = board.getHistoryMove(i); - if (pos == Pos::PASS) - boardClone.doPassMove(); + if (pos == Pos::PASS) { + boardClone.move(rule, Pos::PASS); + afterPass(boardClone); + } else { - // Ensure side to move is the same as the stone on the original board - if (boardClone.sideToMove() != board.get(pos)) - boardClone.flipSide(); - beforeMove(boardClone, pos); boardClone.move(rule, pos); afterMove(boardClone, pos); diff --git a/Rapfi/eval/evaluator.h b/Rapfi/eval/evaluator.h index 838e53fb..e3e19a34 100644 --- a/Rapfi/eval/evaluator.h +++ b/Rapfi/eval/evaluator.h @@ -124,14 +124,18 @@ class Evaluator /// Resets the evaluator state to empty board. virtual void initEmptyBoard() = 0; - /// Update hook called before board.move(). + /// Update hook called before board.move(). Pos is empty and not a pass. virtual void beforeMove(const Board &board, Pos pos) {}; - /// Update hook called after board.move(). + /// Update hook called after board.move(). Pos is empty and not a pass. virtual void afterMove(const Board &board, Pos pos) {}; - /// Update hook called before board.undo(). + /// Update hook called before board.undo(). Pos is empty and not a pass. virtual void beforeUndo(const Board &board, Pos pos) {}; - /// Update hook called after board.undo(). + /// Update hook called after board.undo(). Pos is empty and not a pass. virtual void afterUndo(const Board &board, Pos pos) {}; + /// Update hook called after board.move(Pos::PASS). + virtual void afterPass(const Board &board) {}; + /// Update hook called after board.undo() and the last move is a pass. + virtual void afterUndoPass(const Board &board) {}; /// @brief Sync evaluator state with the given board state. /// This is implemented as initEmptyBoard() as well as a sequence of beforeMove() diff --git a/Rapfi/game/board.cpp b/Rapfi/game/board.cpp index 8780ed0d..adce11b1 100644 --- a/Rapfi/game/board.cpp +++ b/Rapfi/game/board.cpp @@ -203,6 +203,23 @@ template void Board::newGame(); template void Board::move(Pos pos) { + // handle the case when the pos is a PASS move + if (UNLIKELY(pos == Pos::PASS)) { + assert(passMoveCount() < cellCount()); + + StateInfo &st = stateInfos[++moveCount]; + st = stateInfos[moveCount - 1]; + st.lastMove = Pos::PASS; + + passCount[currentSide]++; + currentSide = ~currentSide; + + // after move evaluator update + if (MT == MoveType::NORMAL && evaluator_) + evaluator_->afterPass(*this); + return; + } + assert(pos.valid()); assert(isEmpty(pos)); @@ -321,6 +338,19 @@ void Board::undo() assert(moveCount > 0); Pos lastPos = getLastMove(); + // handle the case when the last move is a PASS + if (UNLIKELY(lastPos == Pos::PASS)) { + currentSide = ~currentSide; + assert(passCount[currentSide] > 0); + passCount[currentSide]--; + moveCount--; + + // after undo evaluator update + if (MT == MoveType::NORMAL && evaluator_) + evaluator_->afterUndoPass(*this); + return; + } + // before undo evaluator update if (MT == MoveType::NORMAL && evaluator_) evaluator_->beforeUndo(*this, lastPos); @@ -390,26 +420,6 @@ template void Board::undo(); template void Board::undo(); template void Board::undo(); -void Board::doPassMove() -{ - assert(passMoveCount() < cellCount()); - - StateInfo &st = stateInfos[++moveCount]; - st = stateInfos[moveCount - 1]; - st.lastMove = Pos::PASS; - - passCount[currentSide]++; - currentSide = ~currentSide; -} - -void Board::undoPassMove() -{ - currentSide = ~currentSide; - assert(passCount[currentSide] > 0); - passCount[currentSide]--; - moveCount--; -} - bool Board::checkForbiddenPoint(Pos pos) const { const Cell &fpCell = cell(pos); diff --git a/Rapfi/game/board.h b/Rapfi/game/board.h index 05c2422b..a448702c 100644 --- a/Rapfi/game/board.h +++ b/Rapfi/game/board.h @@ -173,13 +173,16 @@ class Board void newGame(); /// Make move and incremental update the board state. - /// @param pos Pos to put the next stone. + /// @param pos Pos to put the next stone. A Pass move is allowed. /// @tparam R Game rule to use. /// @tparam MT Type of this move. There are four types of move: /// 1. NORMAL: Updates cell, pattern, score, eval and external evaluator. /// 2. NO_EVALUATOR: Updates cell, pattern, score, eval. /// 3. NO_EVAL: Updates cell, pattern, score. /// 4. NO_EVAL_MULTI: Updates cell, pattern, score. Side to move is not swapped. + /// @note Recursive pass move is allowed, but the total number of null moves + /// must be not greater than MAX_PASS_MOVES. As long as consecutive pass + /// moves are not allowed, this condition should be met. template void move(Pos pos); @@ -197,23 +200,12 @@ class Board void undo(Rule rule); // ------------------------------------------------------------------------ - // pass move & multi move + // special helper function /// Flip current side to move without recording in state info. - void flipSide() - { - currentSide = ~currentSide; - currentZobristKey = ~currentZobristKey; // Invert zobrist key in case of tt conflict - } - - /// Make a pass move. - /// @note Recursive pass move is allowed, but the total number of null moves - /// must be not greater than MAX_PASS_MOVES. As long as consecutive pass - /// moves are not allowed, this condition should be met. - void doPassMove(); - - /// Undo a pass move. - void undoPassMove(); + /// This is only served for some special board checking proecess. It must be + /// used in pair locally. If a pass is desired, use move(Pos::PASS) instead. + void flipSide() { currentSide = ~currentSide; } // ------------------------------------------------------------------------ // pos-specific info queries @@ -232,12 +224,16 @@ class Board return cells[pos].piece; } - /// Check if the pos is in side the board. + /// Check if the pos is in the region of current board size. bool isInBoard(Pos pos) const { return pos.isInBoard(boardSize, boardSize); } - /// Check if the pos is an empty cell. + /// Check if the pos is on an empty cell. + /// @pos The pos to query, which is assumed to meet 'pos.valid() == true'. bool isEmpty(Pos pos) const { return get(pos) == EMPTY; } + /// Check if the pos is legal (on an empty cell or is a pass move). + bool isLegal(Pos pos) const { return pos.valid() && (isEmpty(pos) || pos == Pos::PASS); } + /// Check if a pos on board is forbidden point in Renju rule. /// @param pos Pos to check forbidden. Should be an empty cell. /// @return Whether this pos is a real forbidden point. diff --git a/Rapfi/game/movegen.cpp b/Rapfi/game/movegen.cpp index 8e47c890..062fce20 100644 --- a/Rapfi/game/movegen.cpp +++ b/Rapfi/game/movegen.cpp @@ -121,7 +121,7 @@ Pos findFirstPattern4Pos(const Board &board, Color side, Pattern4 p4) /// Find all pseudo defence pos of FOUR pattern4. /// @param side Color of side with a FOUR pattern4. -Move *findAllPseudoFourDefendPos(const Board &board, Color side, Move *moveList) +ScoredMove *findAllPseudoFourDefendPos(const Board &board, Color side, ScoredMove *moveList) { FOR_EVERY_CAND_POS(&board, pos) { @@ -148,16 +148,16 @@ Move *findAllPseudoFourDefendPos(const Board &board, Color side, Move *moveList) /// Find all exact defence pos of opponent FOUR pattern4. template -Move *findFourDefence(const Board &board, Move *const moveList) +ScoredMove *findFourDefence(const Board &board, ScoredMove *const moveList) { - Color oppo = ~board.sideToMove(); - Move *last = moveList; + Color oppo = ~board.sideToMove(); + ScoredMove *last = moveList; assert(board.p4Count(oppo, A_FIVE) == 0); assert(board.p4Count(oppo, B_FLEX4) > 0); // Find all defend pos for F3 attack line pattern (_*OOO*_, X*OOO**X, _*O*OO*_, _O*O*O*O_) - auto findF3LineDefence = [=, &board](Pos f4Pos, int dir, Move *const list) { + auto findF3LineDefence = [=, &board](Pos f4Pos, int dir, ScoredMove *const list) { const Cell &f4Cell = board.cell(f4Pos); assert(f4Cell.pattern(oppo, dir) == F4); @@ -199,7 +199,7 @@ Move *findFourDefence(const Board &board, Move *const moveList) }; // Find all defend pos for double B3 attack line pattern (XOOO**_ + XOOO**_) - auto findB3Defence = [=, &board](Pos f4Pos, int dir, Move *list) { + auto findB3Defence = [=, &board](Pos f4Pos, int dir, ScoredMove *list) { const Cell &f4Cell = board.cell(f4Pos); assert(f4Cell.pattern(oppo, dir) == B4); @@ -351,7 +351,7 @@ Move *findFourDefence(const Board &board, Move *const moveList) /// Find all exact defence pos of opponent B4F3 pattern4. /// @note If no direct defence is needed, empty move list is returned. template -Move *findB4F3Defence(const Board &board, Move *const moveList) +ScoredMove *findB4F3Defence(const Board &board, ScoredMove *const moveList) { Color oppo = ~board.sideToMove(); @@ -359,7 +359,7 @@ Move *findB4F3Defence(const Board &board, Move *const moveList) assert(board.p4Count(oppo, C_BLOCK4_FLEX3) > 0); // Find all valid defence move for a F3 line through a LUT - auto findF3LineDefence = [=, &board](Pos f3Pos, int dir, Move *list) { + auto findF3LineDefence = [=, &board](Pos f3Pos, int dir, ScoredMove *list) { assert(board.cell(f3Pos).pattern(oppo, dir) == F3 || board.cell(f3Pos).pattern(oppo, dir) == F3S); @@ -485,7 +485,7 @@ Move *findB4F3Defence(const Board &board, Move *const moveList) return Pos::NONE; }; - auto findAllB3CounterDefence = [=, &board](Pos b4Pos, int dir, Move *list) { + auto findAllB3CounterDefence = [=, &board](Pos b4Pos, int dir, ScoredMove *list) { Color self = ~oppo; const bool isPseudoForbiddenB4 = R == RENJU && self == BLACK && board.cell(b4Pos).pattern4[self] == FORBID; @@ -532,8 +532,8 @@ Move *findB4F3Defence(const Board &board, Move *const moveList) assert(B4F3Cell.piece == EMPTY); assert(B4F3Cell.pattern4[oppo] == C_BLOCK4_FLEX3); - Move *last = moveList; - *last++ = B4F3Pos; + ScoredMove *last = moveList; + *last++ = B4F3Pos; // Iterate all directions to find F3 pattern line and B4 pattern line. for (int dir = 0; dir < 4; dir++) { @@ -565,15 +565,15 @@ Move *findB4F3Defence(const Board &board, Move *const moveList) /// by other generator. /// @note Board state must satisfy `board.p4Count(oppo, B_FLEX4) > 0`. template -Move *generateFourDefence(const Board &board, Move *moveList) +ScoredMove *generateFourDefence(const Board &board, ScoredMove *moveList) { assert(board.p4Count(~board.sideToMove(), B_FLEX4)); - Move *last = findFourDefence(board, moveList); + ScoredMove *last = findFourDefence(board, moveList); - std::sort(moveList, last, [](Move m, Move n) { return m.pos < n.pos; }); - last = std::unique(moveList, last, [](Move m, Move n) { return m.pos == n.pos; }); + std::sort(moveList, last, [](ScoredMove m, ScoredMove n) { return m.pos < n.pos; }); + last = std::unique(moveList, last, [](ScoredMove m, ScoredMove n) { return m.pos == n.pos; }); - return std::remove_if(moveList, last, [&](Move move) { + return std::remove_if(moveList, last, [&](ScoredMove move) { assert(board.isEmpty(move)); assert(board.cell(move).pattern4[~board.sideToMove()] >= E_BLOCK4 || board.cell(move).pattern4[~board.sideToMove()] == FORBID); @@ -589,19 +589,19 @@ Move *generateFourDefence(const Board &board, Move *moveList) /// have some B4 counter defence move, empty move list is returned. /// @note Board state must satisfy `board.p4Count(oppo, C_BLOCK4_FLEX3) > 0`. template -Move *generateB4F3Defence(const Board &board, Move *moveList) +ScoredMove *generateB4F3Defence(const Board &board, ScoredMove *moveList) { assert(board.p4Count(~board.sideToMove(), C_BLOCK4_FLEX3)); - Move *last = findB4F3Defence(board, moveList); + ScoredMove *last = findB4F3Defence(board, moveList); // If direct defence is not needed, we simply return empty move list. if (last == moveList) return moveList; - std::sort(moveList, last, [](Move m, Move n) { return m.pos < n.pos; }); - last = std::unique(moveList, last, [](Move m, Move n) { return m.pos == n.pos; }); + std::sort(moveList, last, [](ScoredMove m, ScoredMove n) { return m.pos < n.pos; }); + last = std::unique(moveList, last, [](ScoredMove m, ScoredMove n) { return m.pos == n.pos; }); - return std::remove_if(moveList, last, [&](Move move) { + return std::remove_if(moveList, last, [&](ScoredMove move) { assert(board.isEmpty(move)); return board.cell(move).pattern4[board.sideToMove()] >= E_BLOCK4; }); @@ -610,7 +610,7 @@ Move *generateB4F3Defence(const Board &board, Move *moveList) } // namespace template -Move *generate(const Board &board, Move *moveList) +ScoredMove *generate(const Board &board, ScoredMove *moveList) { Color self = board.sideToMove(); @@ -624,16 +624,16 @@ Move *generate(const Board &board, Move *moveList) return moveList; } -template Move *generate(const Board &, Move *); -template Move *generate(const Board &, Move *); -template Move *generate(const Board &, Move *); +template ScoredMove *generate(const Board &, ScoredMove *); +template ScoredMove *generate(const Board &, ScoredMove *); +template ScoredMove *generate(const Board &, ScoredMove *); template -Move *generateNeighbors(const Board &board, - Move *moveList, - Pos center, - const Direction *neighbors, - size_t numNeighbors) +ScoredMove *generateNeighbors(const Board &board, + ScoredMove *moveList, + Pos center, + const Direction *neighbors, + size_t numNeighbors) { Color self = board.sideToMove(); @@ -651,14 +651,16 @@ Move *generateNeighbors(const Board &board, return moveList; } -template Move *generateNeighbors(const Board &, Move *, Pos, const Direction *, size_t); -template Move *generateNeighbors(const Board &, Move *, Pos, const Direction *, size_t); +template ScoredMove * +generateNeighbors(const Board &, ScoredMove *, Pos, const Direction *, size_t); +template ScoredMove * +generateNeighbors(const Board &, ScoredMove *, Pos, const Direction *, size_t); /// Generate direct winning moves for current side to move. /// @return The first found winning pos. /// @note Board state must satisfy `p4Count(self, A_FIVE) + p4Count(self, B_FLEX4) > 0`. template <> -Move *generate(const Board &board, Move *moveList) +ScoredMove *generate(const Board &board, ScoredMove *moveList) { Color self = board.sideToMove(); @@ -679,7 +681,7 @@ Move *generate(const Board &board, Move *moveList) /// @return The first found FIVE pattern4 pos. /// @note Board state must satisfy `board.p4Count(oppo, A_FIVE) > 0`. template <> -Move *generate(const Board &board, Move *moveList) +ScoredMove *generate(const Board &board, ScoredMove *moveList) { Color oppo = ~board.sideToMove(); assert(board.p4Count(oppo, A_FIVE) > 0); @@ -706,7 +708,7 @@ Move *generate(const Board &board, Move *moveList) /// by other generator. /// @note Board state must satisfy `board.p4Count(oppo, B_FLEX4) > 0`. template <> -Move *generate(const Board &board, Move *moveList) +ScoredMove *generate(const Board &board, ScoredMove *moveList) { return generateFourDefence(board, moveList); } @@ -714,25 +716,25 @@ Move *generate(const Board &board, Move *moveList) /// Generates defence moves for opponent B_FLEX4 pattern4. /// This version also generates all losing moves. template <> -Move *generate(const Board &board, Move *moveList) +ScoredMove *generate(const Board &board, ScoredMove *moveList) { return generateFourDefence(board, moveList); } template <> -Move *generate(const Board &board, Move *moveList) +ScoredMove *generate(const Board &board, ScoredMove *moveList) { return generateB4F3Defence(board, moveList); } template <> -Move *generate(const Board &board, Move *moveList) +ScoredMove *generate(const Board &board, ScoredMove *moveList) { return generateB4F3Defence(board, moveList); } template <> -Move *generate(const Board &board, Move *moveList) +ScoredMove *generate(const Board &board, ScoredMove *moveList) { return generateB4F3Defence(board, moveList); } diff --git a/Rapfi/game/movegen.h b/Rapfi/game/movegen.h index 74ff40d2..d834e0b5 100644 --- a/Rapfi/game/movegen.h +++ b/Rapfi/game/movegen.h @@ -53,28 +53,28 @@ constexpr GenType operator|(GenType a, GenType b) return GenType(int(a) | int(b)); } -/// Move struct contains a pos and its score, used for move sorting. -struct Move +/// ScoredMove struct contains a pos and its score, used for move sorting. +struct ScoredMove { Pos pos; Score score; /// Score with history and other heruistics that is used for sorting Score rawScore; /// Raw score from score table or evaluator - operator Pos() const { return pos; } - operator float() const = delete; // Inhibit unwanted implicit conversions - void operator =(Pos p) { pos = p; } - bool operator<(const Move &o) const { return score < o.score; } - bool operator>(const Move &o) const { return score > o.score; } + operator Pos() const { return pos; } + operator float() const = delete; // Inhibit unwanted implicit conversions + void operator=(Pos p) { pos = p; } + bool operator<(const ScoredMove &o) const { return score < o.score; } + bool operator>(const ScoredMove &o) const { return score > o.score; } }; /// Generate moves satisfying the given GenType, and stores them /// in the move list. Scores of generated moves are all set to zero. /// @param board The current board state to generate moves. /// @param moveList The begin cursor of an empty move list. -/// @tparam Type Move generation type. +/// @tparam Type ScoredMove generation type. /// @return The end cursor of move list (next of the last element). template -Move *generate(const Board &board, Move *moveList); +ScoredMove *generate(const Board &board, ScoredMove *moveList); /// Generate moves satisfying the given GenType, while only generate /// neighbor moves around the last move of the current side. @@ -83,14 +83,14 @@ Move *generate(const Board &board, Move *moveList); /// @param center The center pos on board to generate moves. /// @param neighbors A pointer to an array of pos offsets. /// @param numNeighbors The length of pos offsets array. -/// @tparam Type Move generation type. +/// @tparam Type ScoredMove generation type. /// @return The end cursor of move list (next of the last element). template -Move *generateNeighbors(const Board &board, - Move *moveList, - Pos center, - const Direction *neighbors, - size_t numNeighbors); +ScoredMove *generateNeighbors(const Board &board, + ScoredMove *moveList, + Pos center, + const Direction *neighbors, + size_t numNeighbors); /// Validate opponent C_BLOCK4_FLEX3 type move is real threat in Renju. /// @return True if threat is real, otherwise false. diff --git a/Rapfi/search/ab/history.cpp b/Rapfi/search/ab/history.cpp index c8bde90a..86d31ff0 100644 --- a/Rapfi/search/ab/history.cpp +++ b/Rapfi/search/ab/history.cpp @@ -83,21 +83,22 @@ void HistoryTracker::updateBestmoveStats(Depth depth, Pos bestMove, Value bestVa void HistoryTracker::updateTTMoveStats(Depth depth, Pos ttMove, Value ttValue, Value beta) { // Validate ttMove first - if (ttMove.valid() && board.isEmpty(ttMove)) { - Color self = board.sideToMove(), oppo = ~self; - bool oppo5 = board.p4Count(oppo, A_FIVE); - bool oppo4 = oppo5 || board.p4Count(oppo, B_FLEX4); - Pattern4 selfP4 = board.cell(ttMove).pattern4[self]; - int bonus = statBonus(depth); - - if (!oppo4 && selfP4 < H_FLEX3) { - // Bonus for a quiet ttMove that fails high - if (ttValue >= beta) - updateQuietStats(ttMove, bonus); - // Penalty for a quiet ttMove that fails low - else - searchData->mainHistory[self][ttMove][HIST_QUIET] << -bonus; - } + if (!board.isLegal(ttMove)) + return; + + Color self = board.sideToMove(), oppo = ~self; + bool oppo5 = board.p4Count(oppo, A_FIVE); + bool oppo4 = oppo5 || board.p4Count(oppo, B_FLEX4); + Pattern4 selfP4 = board.cell(ttMove).pattern4[self]; + int bonus = statBonus(depth); + + if (!oppo4 && selfP4 < H_FLEX3) { + // Bonus for a quiet ttMove that fails high + if (ttValue >= beta) + updateQuietStats(ttMove, bonus); + // Penalty for a quiet ttMove that fails low + else + searchData->mainHistory[self][ttMove][HIST_QUIET] << -bonus; } } diff --git a/Rapfi/search/ab/search.cpp b/Rapfi/search/ab/search.cpp index 24841e6b..ed684fbe 100644 --- a/Rapfi/search/ab/search.cpp +++ b/Rapfi/search/ab/search.cpp @@ -865,10 +865,10 @@ Value search(Board &board, SearchStack *ss, Value alpha, Value beta, Depth depth ss->currentMove = Pos::PASS; (ss + 1)->numNullMoves++; - board.doPassMove(); + board.move(Pos::PASS); TT.prefetch(board.zobristKey()); value = -search(board, ss + 1, -beta, -beta + 1, depth - r, !cutNode); - board.undoPassMove(); + board.undo(); (ss + 1)->numNullMoves--; if (value >= beta) { @@ -944,7 +944,7 @@ Value search(Board &board, SearchStack *ss, Value alpha, Value beta, Depth depth // Step 11. Loop through all legal moves until no moves remain // or a beta cutoff occurs. while (Pos move = mp()) { - assert(board.isEmpty(move)); + assert(board.isLegal(move)); // Skip excluded move when in Singular extension search if (!RootNode && move == skipMove) @@ -1615,7 +1615,7 @@ Value vcfsearch(Board &board, SearchStack *ss, Value alpha, Value beta, Depth de {(ss - 2)->moveP4[self], (ss - 4)->moveP4[self]}}); while (Pos move = mp()) { - assert(board.isEmpty(move)); + assert(board.isLegal(move)); ss->currentMove = move; ss->moveCount = ++moveCount; diff --git a/Rapfi/search/hashtable.cpp b/Rapfi/search/hashtable.cpp index 0916af34..4a44f449 100644 --- a/Rapfi/search/hashtable.cpp +++ b/Rapfi/search/hashtable.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . -*/ + */ #include "hashtable.h" @@ -143,7 +143,7 @@ bool HashTable::probe(HashKey hashKey, ttEval = Value(tte.eval16); ttIsPv = bool(tte.pvBoundBest16 >> 15); ttBound = Bound((tte.pvBoundBest16 >> 13) & 0x3); - ttMove = Pos(tte.pvBoundBest16 & 0x3ff); + ttMove = Pos((tte.pvBoundBest16 & 0x3ff) - 1); ttDepth = int(tte.depth8) + (int)DEPTH_LOWER_BOUND; return true; @@ -192,13 +192,14 @@ void HashTable::store(HashKey hashKey, // Use previous stored best move if we do not have a best move this time if (move == Pos::NONE && newKey32 == oldKey32) - move = Pos(replace->pvBoundBest16 & 0x3ff); + move = Pos((replace->pvBoundBest16 & 0x3ff) - 1); // Construct the new tt entry on stack first, then copy it to shared memory + // Note that best move is stored by adding 1 to offset the Pos::PASS. TTEntry newEntry; newEntry.value16 = int16_t(searchValueToStoredValue(value, ply)); newEntry.eval16 = int16_t(eval); - newEntry.pvBoundBest16 = uint16_t(isPv) << 15 | uint16_t(bound) << 13 | uint16_t(move); + newEntry.pvBoundBest16 = uint16_t(isPv) << 15 | uint16_t(bound) << 13 | uint16_t((int)move + 1); newEntry.depth8 = uint8_t(depth - (int)DEPTH_LOWER_BOUND); newEntry.generation8 = uint8_t(generation); newEntry.key32 = newKey32 ^ newEntry.data[0] ^ newEntry.data[1]; diff --git a/Rapfi/search/movepick.cpp b/Rapfi/search/movepick.cpp index 89e08218..d492376f 100644 --- a/Rapfi/search/movepick.cpp +++ b/Rapfi/search/movepick.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . -*/ + */ #include "movepick.h" @@ -45,7 +45,7 @@ enum Stages { /// Partial sort the move list up to the score limit. It dynamiclly decides /// which sorting algorithm to use based on how many moves are in the list. -void fastPartialSort(Move *begin, Move *end, Score limit) +void fastPartialSort(ScoredMove *begin, ScoredMove *end, Score limit) { // heruistic values constexpr size_t InsertionSortLimit = MAX_MOVES / 4; @@ -55,10 +55,10 @@ void fastPartialSort(Move *begin, Move *end, Score limit) if (nMoves <= InsertionSortLimit) { // Sorts moves in descending order up to and including a given limit. // The order of moves smaller than the limit is left unspecified. - for (Move *sortedEnd = begin, *p = begin + 1; p < end; ++p) + for (ScoredMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) if (p->score >= limit) { - Move tmp = *p, *q; - *p = *++sortedEnd; + ScoredMove tmp = *p, *q; + *p = *++sortedEnd; for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) *q = *(q - 1); *q = tmp; @@ -155,7 +155,7 @@ MovePicker::MovePicker(Rule rule, const Board &board, ExtraArgs -void MovePicker::scoreMoves() +void MovePicker::scoreAllMoves() { using Evaluation::Evaluator; using Evaluation::PolicyBuffer; @@ -298,7 +298,7 @@ Pos MovePicker::operator()() curMove = moves; endMove = generate(board, curMove); - scoreMoves(); + scoreAllMoves(); fastPartialSort(curMove, endMove, 0); stage = ALLMOVES; @@ -320,7 +320,7 @@ Pos MovePicker::operator()() endMove = generate(board, curMove); endMove = (rule == RENJU ? generate : generate)(board, endMove); - scoreMoves(); + scoreAllMoves(); fastPartialSort(curMove, endMove, 0); stage = ALLMOVES; @@ -344,7 +344,7 @@ Pos MovePicker::operator()() endMove = (rule == RENJU ? generate : generate)(board, endMove); - scoreMoves(); + scoreAllMoves(); fastPartialSort(curMove, endMove, 0); stage = ALLMOVES; @@ -363,7 +363,7 @@ Pos MovePicker::operator()() arraySize(RANGE_SQUARE2_LINE4)); } - scoreMoves(); + scoreAllMoves(); fastPartialSort(curMove, endMove, 0); stage = ALLMOVES; diff --git a/Rapfi/search/movepick.h b/Rapfi/search/movepick.h index 97aac2dd..ac68048a 100644 --- a/Rapfi/search/movepick.h +++ b/Rapfi/search/movepick.h @@ -38,7 +38,7 @@ class MovePicker /// generation phase to set and provide information for move ordering. template MovePicker(Rule rule, const Board &board, ExtraArgs args); - MovePicker(const MovePicker &) = delete; + MovePicker(const MovePicker &) = delete; MovePicker &operator=(const MovePicker &) = delete; [[nodiscard]] Pos operator()(); @@ -63,9 +63,9 @@ class MovePicker template Pos pickNextMove(Pred); template - void scoreMoves(); - Move *begin() { return curMove; } - Move *end() { return endMove; } + void scoreAllMoves(); + ScoredMove *begin() { return curMove; } + ScoredMove *end() { return endMove; } const Board &board; const MainHistory *mainHistory; @@ -76,8 +76,8 @@ class MovePicker bool allowPlainB4InVCF; bool hasPolicy; Score curScore, curPolicyScore, maxPolicyScore; - Move *curMove, *endMove; - Move moves[MAX_MOVES]; + ScoredMove *curMove, *endMove; + ScoredMove moves[MAX_MOVES]; }; template <>