Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
498870f
[CI] Introduce GitHub CI that builds the project on Windows, Linux an…
KvanTTT Nov 13, 2025
b551dd4
First draft of Dots game implementation
KvanTTT Nov 7, 2025
f006fb4
Remove `inline` modifier to fix linking issues
KvanTTT Dec 13, 2025
57b465b
Split `MAX_LEN` into `MAX_LEN_X` and `MAX_LEN_Y`
KvanTTT Nov 7, 2025
611b13e
Use dedicated `visited_data` for processing visited locs instead of s…
KvanTTT Aug 29, 2025
ca35e32
Implement iterative grounding detection and score calculating in case…
KvanTTT Dec 13, 2025
91bd12d
Refine `Board` and `BoardHistory` constructors
KvanTTT Nov 7, 2025
a54fb85
Refine grounding utils in `BoardHistory`
KvanTTT Sep 7, 2025
40ac07e
Extract Dots stress tests to a separated file
KvanTTT Sep 7, 2025
5e2a4e7
Don't consider PASS (Grounding) move in case it doesn't win the game
KvanTTT Sep 7, 2025
6a5efd5
Split `playMoveAssumeLegalDots` that is used much more often than `pl…
KvanTTT Sep 7, 2025
aa692ec
Introduce `BoardHistory.isPassAliveFinished`
KvanTTT Nov 7, 2025
6c8363a
Initial support of handicap games (#1)
KvanTTT Sep 17, 2025
ea1abdd
Don't use `doEndGameIfAllPassAlive` in the main game loop because the…
KvanTTT Sep 21, 2025
5b82bbc
Implement randomization of start pos, fix #2
KvanTTT Sep 21, 2025
fb78da9
Tidy up code by using default parameters for some functions in the ga…
KvanTTT Nov 7, 2025
b8b798e
Refine `tryRecognizeStartPos` to ignore handicap and to detect remain…
KvanTTT Nov 7, 2025
acc39a5
Introduce `dataBoardLenX`, `dataBoardLenY` config keys
KvanTTT Sep 29, 2025
320c719
[Python] Initial support for `pos_len_x`, `pos_len_y` in advance to `…
KvanTTT Sep 30, 2025
81c3f62
[Python] Fix `read_array_header` for different versions (1, 2)
KvanTTT Sep 30, 2025
afc7bbd
Fix calculating of empty items that can be captured in `makeMoveAndCa…
KvanTTT Oct 2, 2025
25d25e0
Add an option to `printBoard` that allows to get rid of hash printing
KvanTTT Oct 2, 2025
785572a
Use `winOrEffectiveDrawByGrounding` instead of `doesGroundingWinGame`
KvanTTT Oct 2, 2025
63ad75c
Introduce Global::FLOAT_EPS for more accurate float comparisons
KvanTTT Oct 4, 2025
3d60f5e
Refine Dots NN inputs and scoring
KvanTTT Oct 4, 2025
c420e73
Introduce `endGameIfNoLegalMoves`
KvanTTT Nov 7, 2025
43477ad
[GTP Support for basic GTP commands for Dots game
KvanTTT Nov 7, 2025
892cf0d
[Python] Refine Dots NN inputs and scoring
KvanTTT Oct 5, 2025
ca4074a
Move stress tests down for convenience (because they are quite slow)
KvanTTT Oct 5, 2025
44457e4
[GTP] Support multiple moves for `play` command
KvanTTT Oct 11, 2025
20f237d
[GTP] Introduce `get_moves` and `get_position` commands
KvanTTT Nov 7, 2025
689849c
[GTP] Introduce `get_boardsize` command, `undo` supports multiple moves
KvanTTT Oct 13, 2025
58cc24f
Fix the compilation error with a localpattern
KvanTTT Oct 13, 2025
647d523
Add configs and fix scripts to make it robust for seltraining loop
KvanTTT Oct 13, 2025
7c923c1
Support for `RESIGN_LOC`
KvanTTT Nov 7, 2025
82d9bc3
[GTP] Support for number of moves in `undo`
KvanTTT Nov 7, 2025
29f9edd
Revert "[Python] Refine Dots NN inputs and scoring"
KvanTTT Nov 13, 2025
f93c19b
Make number of spatial and global features identical to KataGo last m…
KvanTTT Nov 15, 2025
dbe65c1
Refactor `getString` code in config_parser
KvanTTT Nov 16, 2025
86258e5
Fix compilation errors on Linux
KvanTTT Nov 16, 2025
190f367
Fix compilation warnings from Linux and macOS compilers
KvanTTT Nov 16, 2025
5b85fb8
~ [CI] Pack necessary .dll in resulting Windows artifact
KvanTTT Dec 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Build and Test

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:

jobs:
build-linux:
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake build-essential zlib1g-dev libzip-dev opencl-headers ocl-icd-opencl-dev

- name: Cache CMake build
uses: actions/cache@v4
with:
path: |
cpp/CMakeCache.txt
cpp/CMakeFiles
key: ${{ runner.os }}-cmake-${{ hashFiles('**/CMakeLists.txt') }}
restore-keys: |
${{ runner.os }}-cmake-

- name: Configure CMake
working-directory: cpp
# Use '-DCMAKE_CXX_FLAGS_RELEASE="-s"' to strip debug symbols. Otherwise, the executable is too big
run: |
cmake . -DUSE_BACKEND=OPENCL -DNO_GIT_REVISION=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS_RELEASE="-s"

- name: Build
working-directory: cpp
run: |
make -j$(nproc)

- name: Run tests
run: |
cpp/katago runtests

- name: Upload artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v4
with:
name: katago-linux-opencl
path: cpp/katago

build-macos:
runs-on: macos-latest
permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install dependencies
run: |
brew install zlib libzip opencl-headers
- name: Cache CMake build
uses: actions/cache@v4
with:
path: |
cpp/CMakeCache.txt
cpp/CMakeFiles
cpp/build.ninja
cpp/.ninja_deps
cpp/.ninja_log
key: ${{ runner.os }}-cmake-${{ hashFiles('**/CMakeLists.txt') }}
restore-keys: |
${{ runner.os }}-cmake-
- name: Configure CMake
working-directory: cpp
run: |
cmake . -G Ninja -DUSE_BACKEND=OPENCL -DNO_GIT_REVISION=1 -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: cpp
run: |
ninja
- name: Run tests
run: |
cpp/katago runtests
- name: Upload artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v4
with:
name: katago-macos-opencl
path: cpp/katago

build-windows:
runs-on: windows-latest
permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup MSVC
uses: microsoft/setup-msbuild@v2

- name: Cache vcpkg packages
uses: actions/cache@v4
with:
path: |
${{ env.VCPKG_INSTALLATION_ROOT }}/installed
${{ env.VCPKG_INSTALLATION_ROOT }}/packages
key: ${{ runner.os }}-vcpkg-${{ hashFiles('**/vcpkg.json') }}-opencl
restore-keys: |
${{ runner.os }}-vcpkg-

- name: Install vcpkg dependencies
run: |
vcpkg install zlib:x64-windows libzip:x64-windows opencl:x64-windows

- name: Configure CMake
working-directory: cpp
run: |
cmake . -G "Visual Studio 17 2022" -A x64 `
-DUSE_BACKEND=OPENCL `
-DNO_GIT_REVISION=1 `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"

- name: Build
working-directory: cpp
run: |
cmake --build . --config Release -j 4

- name: Copy required DLLs
working-directory: cpp
run: |
$vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT
Copy-Item "$vcpkgRoot/installed/x64-windows/bin/*.dll" -Destination "Release/" -ErrorAction SilentlyContinue

- name: Run tests
run: |
cpp/Release/katago.exe runtests

- name: Upload artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v4
with:
name: katago-windows-opencl
path: cpp/Release/
23 changes: 20 additions & 3 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ set(USE_TCMALLOC 0 CACHE BOOL "Use TCMalloc")
set(NO_GIT_REVISION 0 CACHE BOOL "Disable embedding the git revision into the compiled exe")
set(USE_AVX2 0 CACHE BOOL "Compile with AVX2")
set(USE_BIGGER_BOARDS_EXPENSIVE 0 CACHE BOOL "Allow boards up to size 50. Compiling with this will use more memory and slow down KataGo, even when playing on boards of size 19.")
set(DOTS_GAME 0 CACHE BOOL "Configure KataGo to compile for Dots Game with possibility to run Go as well. It configures more optimal field sizes")

set(USE_CACHE_TENSORRT_PLAN 0 CACHE BOOL "Use TENSORRT plan cache. May use a lot of disk space. Only applies when USE_BACKEND is TENSORRT.")
mark_as_advanced(USE_CACHE_TENSORRT_PLAN)
Expand Down Expand Up @@ -231,8 +232,10 @@ add_executable(katago
core/threadtest.cpp
core/timer.cpp
game/board.cpp
game/dotsfield.cpp
game/rules.cpp
game/boardhistory.cpp
game/dotsboardhistory.cpp
game/graphhash.cpp
dataio/sgf.cpp
dataio/numpywrite.cpp
Expand All @@ -242,6 +245,7 @@ add_executable(katago
dataio/homedata.cpp
dataio/files.cpp
neuralnet/nninputs.cpp
neuralnet/nninputsdots.cpp
neuralnet/sgfmetadata.cpp
neuralnet/modelversion.cpp
neuralnet/nneval.cpp
Expand Down Expand Up @@ -280,6 +284,11 @@ add_executable(katago
${GIT_HEADER_FILE_ALWAYS_UPDATED}
tests/testboardarea.cpp
tests/testboardbasic.cpp
tests/testdotsutils.cpp
tests/testdotsbasic.cpp
tests/testdotsstartposes.cpp
tests/testdotsstress.cpp
tests/testdotsextra.cpp
tests/testbook.cpp
tests/testcommon.cpp
tests/testconfig.cpp
Expand Down Expand Up @@ -447,9 +456,17 @@ elseif(USE_BACKEND STREQUAL "EIGEN")
endif()
endif()

if(USE_BIGGER_BOARDS_EXPENSIVE)
target_compile_definitions(katago PRIVATE COMPILE_MAX_BOARD_LEN=50)
endif()
if(DOTS_GAME)
target_compile_definitions(katago PRIVATE COMPILE_MAX_BOARD_LEN_X=39)
target_compile_definitions(katago PRIVATE COMPILE_MAX_BOARD_LEN_Y=39)
if(USE_BIGGER_BOARDS_EXPENSIVE)
message(SEND_ERROR "USE_BIGGER_BOARDS_EXPENSIVE is not yet supported for Dots Game")
endif()
else()
if(USE_BIGGER_BOARDS_EXPENSIVE)
target_compile_definitions(katago PRIVATE COMPILE_MAX_BOARD_LEN=50)
endif()
endif(DOTS_GAME)

if(NO_GIT_REVISION AND (NOT BUILD_DISTRIBUTED))
target_compile_definitions(katago PRIVATE NO_GIT_REVISION)
Expand Down
22 changes: 11 additions & 11 deletions cpp/book/book.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,15 @@ static Hash128 getExtraPosHash(const Board& board) {
for(int y = 0; y<board.y_size; y++) {
for(int x = 0; x<board.x_size; x++) {
Loc loc = Location::getLoc(x,y,board.x_size);
hash ^= Board::ZOBRIST_BOARD_HASH2[loc][board.colors[loc]];
hash ^= Board::ZOBRIST_BOARD_HASH2[loc][board.getColor(loc)];
}
}
return hash;
}

void BookHash::getHashAndSymmetry(const BoardHistory& hist, int repBound, BookHash& hashRet, int& symmetryToAlignRet, vector<int>& symmetriesRet, int bookVersion) {
Board boardsBySym[SymmetryHelpers::NUM_SYMMETRIES];
BoardHistory histsBySym[SymmetryHelpers::NUM_SYMMETRIES];
vector<Board> boardsBySym;
vector<BoardHistory> histsBySym;
Hash128 accums[SymmetryHelpers::NUM_SYMMETRIES];

// Make sure the book all matches orientation for rectangular boards.
Expand All @@ -143,8 +143,8 @@ void BookHash::getHashAndSymmetry(const BoardHistory& hist, int repBound, BookHa
SymmetryHelpers::NUM_SYMMETRIES_WITHOUT_TRANSPOSE : SymmetryHelpers::NUM_SYMMETRIES;

for(int symmetry = 0; symmetry < numSymmetries; symmetry++) {
boardsBySym[symmetry] = SymmetryHelpers::getSymBoard(hist.initialBoard,symmetry);
histsBySym[symmetry] = BoardHistory(boardsBySym[symmetry], hist.initialPla, hist.rules, hist.initialEncorePhase);
boardsBySym.emplace_back(SymmetryHelpers::getSymBoard(hist.initialBoard,symmetry));
histsBySym.emplace_back(boardsBySym[symmetry], hist.initialPla, hist.rules, hist.initialEncorePhase);
accums[symmetry] = Hash128();
}

Expand Down Expand Up @@ -2621,7 +2621,7 @@ int64_t Book::exportToHtmlDir(
const int symmetry = 0;
SymBookNode symNode(node, symmetry);

BoardHistory hist;
BoardHistory hist(Rules::DEFAULT_GO);
vector<Loc> moveHistory;
bool suc = symNode.getBoardHistoryReachingHere(hist,moveHistory);
if(!suc) {
Expand Down Expand Up @@ -2664,7 +2664,7 @@ int64_t Book::exportToHtmlDir(
for(int y = 0; y<board.y_size; y++) {
for(int x = 0; x<board.x_size; x++) {
Loc loc = Location::getLoc(x,y,board.x_size);
dataVarsStr += Global::intToString(board.colors[loc]) + ",";
dataVarsStr += Global::intToString(board.getColor(loc)) + ",";
}
}
dataVarsStr += "];\n";
Expand Down Expand Up @@ -2869,7 +2869,7 @@ void Book::saveToFile(const string& fileName) const {
paramsDump["version"] = bookVersion;
paramsDump["initialBoard"] = Board::toJson(initialBoard);
paramsDump["initialRules"] = initialRules.toJson();
paramsDump["initialPla"] = PlayerIO::playerToString(initialPla);
paramsDump["initialPla"] = PlayerIO::playerToString(initialPla, initialRules.isDots);
paramsDump["repBound"] = repBound;
paramsDump["errorFactor"] = params.errorFactor;
paramsDump["costPerMove"] = params.costPerMove;
Expand Down Expand Up @@ -2922,7 +2922,7 @@ void Book::saveToFile(const string& fileName) const {
json nodeData = json::object();
if(bookVersion >= 2) {
nodeData["id"] = nodeIdx;
nodeData["pla"] = PlayerIO::playerToStringShort(node->pla);
nodeData["pla"] = PlayerIO::playerToStringShort(node->pla, initialRules.isDots);
nodeData["syms"] = node->symmetries;
nodeData["wl"] = roundDouble(node->thisValuesNotInBook.winLossValue, 100000000);
nodeData["sM"] = roundDouble(node->thisValuesNotInBook.scoreMean, 1000000);
Expand All @@ -2939,7 +2939,7 @@ void Book::saveToFile(const string& fileName) const {
}
else {
nodeData["hash"] = node->hash.toString();
nodeData["pla"] = PlayerIO::playerToString(node->pla);
nodeData["pla"] = PlayerIO::playerToString(node->pla, initialRules.isDots);
nodeData["symmetries"] = node->symmetries;
nodeData["winLossValue"] = node->thisValuesNotInBook.winLossValue;
nodeData["scoreMean"] = node->thisValuesNotInBook.scoreMean;
Expand Down Expand Up @@ -3030,7 +3030,7 @@ Book* Book::loadFromFile(const std::string& fileName, int numThreadsForRecompute
assertContains(params,"initialBoard");
Board initialBoard = Board::ofJson(params["initialBoard"]);
assertContains(params,"initialRules");
Rules initialRules = Rules::parseRules(params["initialRules"].dump());
Rules initialRules = Rules::parseRules(params["initialRules"].dump(), params.value("dots", false));
Player initialPla = PlayerIO::parsePlayer(params["initialPla"].get<string>());
int repBound = params["repBound"].get<int>();

Expand Down
25 changes: 13 additions & 12 deletions cpp/command/analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,13 @@ int MainCmds::analysis(const vector<string>& args) {
const string expectedSha256 = "";
nnEval = Setup::initializeNNEvaluator(
modelFile,modelFile,expectedSha256,cfg,logger,seedRand,expectedConcurrentEvals,
NNPos::MAX_BOARD_LEN,NNPos::MAX_BOARD_LEN,defaultMaxBatchSize,defaultRequireExactNNLen,disableFP16,
NNPos::MAX_BOARD_LEN_X,NNPos::MAX_BOARD_LEN_Y,defaultMaxBatchSize,defaultRequireExactNNLen,disableFP16,
Setup::SETUP_FOR_ANALYSIS
);
if(humanModelFile != "") {
humanEval = Setup::initializeNNEvaluator(
humanModelFile,humanModelFile,expectedSha256,cfg,logger,seedRand,expectedConcurrentEvals,
NNPos::MAX_BOARD_LEN,NNPos::MAX_BOARD_LEN,defaultMaxBatchSize,defaultRequireExactNNLen,disableFP16,
NNPos::MAX_BOARD_LEN_X,NNPos::MAX_BOARD_LEN_Y,defaultMaxBatchSize,defaultRequireExactNNLen,disableFP16,
Setup::SETUP_FOR_ANALYSIS
);
if(!humanEval->requiresSGFMetadata()) {
Expand Down Expand Up @@ -430,7 +430,8 @@ int MainCmds::analysis(const vector<string>& args) {
vector<AsyncBot*> bots;
for(int threadIdx = 0; threadIdx<numAnalysisThreads; threadIdx++) {
string searchRandSeed = Global::uint64ToHexString(seedRand.nextUInt64()) + Global::uint64ToHexString(seedRand.nextUInt64());
AsyncBot* bot = new AsyncBot(defaultParams, nnEval, humanEval, &logger, searchRandSeed);
// TODO: Fix for Dots game
AsyncBot* bot = new AsyncBot(defaultParams, nnEval, humanEval, &logger, searchRandSeed, Rules::DEFAULT_GO);
bot->setCopyOfExternalPatternBonusTable(patternBonusTable);
bot->setExternalEvalCache(evalCache);
threads.push_back(std::thread(analysisLoopProtected,bot,threadIdx));
Expand Down Expand Up @@ -702,19 +703,20 @@ int MainCmds::analysis(const vector<string>& args) {
{
int64_t xBuf;
int64_t yBuf;
static const string boardSizeError = string("Must provide an integer from 2 to ") + Global::intToString(Board::MAX_LEN);
static const string boardSizeXError = string("Must provide an integer from 2 to ") + Global::intToString(Board::MAX_LEN_X);
static const string boardSizeYError = string("Must provide an integer from 2 to ") + Global::intToString(Board::MAX_LEN_Y);
if(input.find("boardXSize") == input.end()) {
reportErrorForId(rbase.id, "boardXSize", boardSizeError.c_str());
reportErrorForId(rbase.id, "boardXSize", boardSizeXError);
continue;
}
if(input.find("boardYSize") == input.end()) {
reportErrorForId(rbase.id, "boardYSize", boardSizeError.c_str());
reportErrorForId(rbase.id, "boardYSize", boardSizeYError);
continue;
}
if(!parseInteger(input, "boardXSize", xBuf, 2, Board::MAX_LEN, boardSizeError.c_str())) {
if(!parseInteger(input, "boardXSize", xBuf, 2, Board::MAX_LEN_X, boardSizeXError.c_str())) {
continue;
}
if(!parseInteger(input, "boardYSize", yBuf, 2, Board::MAX_LEN, boardSizeError.c_str())) {
if(!parseInteger(input, "boardYSize", yBuf, 2, Board::MAX_LEN_Y, boardSizeYError.c_str())) {
continue;
}
boardXSize = (int)xBuf;
Expand Down Expand Up @@ -882,14 +884,14 @@ int MainCmds::analysis(const vector<string>& args) {
if(input.find("rules") != input.end()) {
if(input["rules"].is_string()) {
string s = input["rules"].get<string>();
if(!Rules::tryParseRules(s,rules)) {
if(!Rules::tryParseRules(s, rules, input.value("dots", false))) {
reportErrorForId(rbase.id, "rules", "Could not parse rules: " + s);
continue;
}
}
else if(input["rules"].is_object()) {
string s = input["rules"].dump();
if(!Rules::tryParseRules(s,rules)) {
if(!Rules::tryParseRules(s, rules, input.value("dots", false))) {
reportErrorForId(rbase.id, "rules", "Could not parse rules: " + s);
continue;
}
Expand Down Expand Up @@ -1110,8 +1112,7 @@ int MainCmds::analysis(const vector<string>& args) {
continue;
}


Board board(boardXSize,boardYSize);
Board board(boardXSize,boardYSize,rules);
for(int i = 0; i<placements.size(); i++) {
board.setStone(placements[i].loc,placements[i].pla);
}
Expand Down
7 changes: 4 additions & 3 deletions cpp/command/benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,10 @@ int MainCmds::benchmark(const vector<string>& args) {
}

static void warmStartNNEval(const CompactSgf& sgf, Logger& logger, const SearchParams& params, NNEvaluator* nnEval, Rand& seedRand) {
Board board(sgf.xSize,sgf.ySize);
const Rules rules = Rules(sgf.isDots);
Board board(sgf.xSize,sgf.ySize,rules);
Player nextPla = P_BLACK;
BoardHistory hist(board,nextPla,Rules(),0);
BoardHistory hist(board,nextPla,rules,0);
SearchParams thisParams = params;
thisParams.numThreads = 1;
thisParams.maxVisits = 5;
Expand Down Expand Up @@ -643,7 +644,7 @@ int MainCmds::genconfig(const vector<string>& args, const string& firstCommand)
string prompt =
"What rules should KataGo use by default for play and analysis?\n"
"(chinese, japanese, korean, tromp-taylor, aga, chinese-ogs, new-zealand, bga, stone-scoring, aga-button):\n";
promptAndParseInput(prompt, [&](const string& line) { configRules = Rules::parseRules(line); });
promptAndParseInput(prompt, [&](const string& line) { configRules = Rules::parseRules(line, sgf->isDots); }); // TODO: probably incorrect for Dots game?
}

cout << endl;
Expand Down
Loading
Loading