Skip to content

Commit

Permalink
first-class support for PASS
Browse files Browse the repository at this point in the history
bench 6e18e82c
bench (msvc clang) 6fb684cd
  • Loading branch information
dhbloo committed Jul 30, 2024
1 parent eb5ef75 commit dfe01a0
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 241 deletions.
147 changes: 70 additions & 77 deletions Rapfi/command/gomocup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pos> 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 {
Expand Down Expand Up @@ -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);
}

Expand All @@ -606,42 +627,31 @@ void getPosition(bool startThink)
// Read position sequence
enum SideFlag { SELF = 1, OPPO = 2, WALL = 3 };
std::vector<std::pair<Pos, SideFlag>> 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> 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<SideFlag>(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
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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));
}
}

Expand Down
2 changes: 1 addition & 1 deletion Rapfi/core/iohelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
#include <zip.h>
#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)
Expand Down
2 changes: 1 addition & 1 deletion Rapfi/database/dbclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ std::vector<std::pair<Pos, std::string>> 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;
Expand Down
27 changes: 8 additions & 19 deletions Rapfi/database/dbutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -458,7 +451,7 @@ size_t RenlibReader::traverse(int boardSize, Rule rule, std::function<CallbackFu
nodeCount++;

// remove "ROOT" move for old Renlib format
if (rootNode.move == Pos::NONE)
if (rootNode.move == Pos::PASS)
rootNode = readNode();

auto board = std::make_unique<Board>(boardSize);
Expand Down Expand Up @@ -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),
};

Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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()) {
Expand Down
10 changes: 4 additions & 6 deletions Rapfi/eval/evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 8 additions & 4 deletions Rapfi/eval/evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading

0 comments on commit dfe01a0

Please sign in to comment.