From: Ada Joule Date: Fri, 9 Sep 2022 11:01:04 +0000 (+0700) Subject: Implement Ouk Chaktrang's counting rules X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=6c0ca3a3d9c3b50fb22f4215e184da86cef9dd75;p=fairystockfish.git Implement Ouk Chaktrang's counting rules According to Article 5, Item 5 of this document. https://docs.google.com/document/d/1adppJ66vonM27UYwC-KyldXl7oZ_5Pb0/edit?usp=sharing&ouid=116281580550740302191&rtpof=true&sd=true To summarise the differences from Makruk: 1. The game ends in a draw immediately when the counting limit is reached, instead of exceeded. This effectively reduces the counting limit by one move. 2. The condition for a player to start board's honour counting is having three pieces or less, regardless of the number of unpromoted pawns on the board. 3. When the condition for piece's honour counting is met, the counting player may choose not to go into it, and continue the board's honour counting instead. This is be implemented by automatically choosing the method that can reach the counting limit in less number of moves. --- diff --git a/src/parser.cpp b/src/parser.cpp index 3a93664..eda6346 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -90,6 +90,7 @@ namespace { template <> bool set(const std::string& value, CountingRule& target) { target = value == "makruk" ? MAKRUK_COUNTING + : value == "cambodian" ? CAMBODIAN_COUNTING : value == "asean" ? ASEAN_COUNTING : NO_COUNTING; return value == "makruk" || value == "asean" || value == "none"; diff --git a/src/position.cpp b/src/position.cpp index 2082461..966766e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -753,7 +753,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string // Counting limit or ep-square if (st->countingLimit) - ss << " " << st->countingLimit << " "; + ss << " " << counting_limit(countStarted) << " "; else ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(*this, ep_square()) + " "); @@ -1847,12 +1847,19 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { sideToMove = ~sideToMove; - if ( counting_rule() - && (!st->countingLimit || (captured && count(sideToMove) == 1)) - && counting_limit()) + if (counting_rule()) { - st->countingLimit = 2 * counting_limit(); - st->countingPly = counting_rule() == MAKRUK_COUNTING && count(sideToMove) == 1 ? 2 * count() : 0; + if (counting_rule() != ASEAN_COUNTING && type_of(captured) == PAWN && count(~sideToMove) == 1 && !count() && count_limit(~sideToMove)) + { + st->countingLimit = 2 * count_limit(~sideToMove); + st->countingPly = 2 * count() - 1; + } + + if ((!st->countingLimit || ((captured || type_of(m) == PROMOTION) && count(sideToMove) == 1)) && count_limit(sideToMove)) + { + st->countingLimit = 2 * count_limit(sideToMove); + st->countingPly = counting_rule() == ASEAN_COUNTING || count(sideToMove) > 1 ? 0 : 2 * count(); + } } // Update king attacks used for fast check detection @@ -2435,7 +2442,7 @@ bool Position::is_optional_game_end(Value& result, int ply, int countStarted) co // counting rules if ( counting_rule() && st->countingLimit - && counting_ply(countStarted) > st->countingLimit + && counting_ply(countStarted) > counting_limit(countStarted) && (!checkers() || MoveList(*this).size())) { result = VALUE_DRAW; @@ -2756,9 +2763,9 @@ bool Position::has_game_cycle(int ply) const { } -/// Position::counting_limit() returns the counting limit in full moves. +/// Position::count_limit() returns the counting limit in full moves. -int Position::counting_limit() const { +int Position::count_limit(Color sideToCount) const { assert(counting_rule()); @@ -2766,33 +2773,56 @@ int Position::counting_limit() const { { case MAKRUK_COUNTING: // No counting for side to move - if (count() || count(~sideToMove) == 1) + if (count() || count(~sideToCount) == 1) return 0; // Board's honor rule - if (count(sideToMove) > 1) + if (count(sideToCount) > 1) return 64; // Pieces' honor rule - if (count(~sideToMove) > 1) + if (count(~sideToCount) > 1) return 8; - if (count(~sideToMove) == 1) + if (count(~sideToCount) == 1) return 16; - if (count(~sideToMove) > 1) + if (count(~sideToCount) > 1) return 22; - if (count(~sideToMove) > 1) + if (count(~sideToCount) > 1) return 32; - if (count(~sideToMove) == 1) + if (count(~sideToCount) == 1) return 44; return 64; + case CAMBODIAN_COUNTING: + // No counting for side to move + if (count(sideToCount) > 3 || count(~sideToCount) == 1) + return 0; + // Board's honor rule + if (count(sideToCount) > 1) + return 63; + // Pieces' honor rule + if (count()) + return 0; + if (count(~sideToCount) > 1) + return 7; + if (count(~sideToCount) == 1) + return 15; + if (count(~sideToCount) > 1) + return 21; + if (count(~sideToCount) > 1) + return 31; + if (count(~sideToCount) == 1) + return 43; + + return 63; + case ASEAN_COUNTING: - if (count() || count(sideToMove) > 1) + if (count() || count(sideToCount) > 1) return 0; - if (count(~sideToMove)) + if (count(~sideToCount)) return 16; - if (count(~sideToMove)) + if (count(~sideToCount)) return 44; - if (count(~sideToMove)) + if (count(~sideToCount)) return 64; return 0; diff --git a/src/position.h b/src/position.h index f06566c..68275f5 100644 --- a/src/position.h +++ b/src/position.h @@ -301,7 +301,10 @@ public: bool has_game_cycle(int ply) const; bool has_repeated() const; Bitboard chased() const; - int counting_limit() const; + int count_limit(Color sideToCount) const; + int board_honor_counting_ply(int countStarted) const; + bool board_honor_counting_shorter(int countStarted) const; + int counting_limit(int countStarted) const; int counting_ply(int countStarted) const; int rule50_count() const; Score psq_score() const; @@ -1180,8 +1183,24 @@ inline int Position::game_ply() const { return gamePly; } +inline int Position::board_honor_counting_ply(int countStarted) const { + return countStarted == 0 ? + st->countingPly : + countStarted < 0 ? 0 : std::max(1 + gamePly - countStarted, 0); +} + +inline bool Position::board_honor_counting_shorter(int countStarted) const { + return counting_rule() == CAMBODIAN_COUNTING && 126 - board_honor_counting_ply(countStarted) < st->countingLimit - st->countingPly; +} + +inline int Position::counting_limit(int countStarted) const { + return board_honor_counting_shorter(countStarted) ? 126 : st->countingLimit; +} + inline int Position::counting_ply(int countStarted) const { - return countStarted == 0 || (count(WHITE) <= 1 || count(BLACK) <= 1) ? st->countingPly : countStarted < 0 ? 0 : std::min(st->countingPly, std::max(1 + gamePly - countStarted, 0)); + return !count() && (count(WHITE) <= 1 || count(BLACK) <= 1) && !board_honor_counting_shorter(countStarted) ? + st->countingPly : + board_honor_counting_ply(countStarted); } inline int Position::rule50_count() const { diff --git a/src/types.h b/src/types.h index 084b492..95b0516 100644 --- a/src/types.h +++ b/src/types.h @@ -293,7 +293,7 @@ enum MaterialCounting { }; enum CountingRule { - NO_COUNTING, MAKRUK_COUNTING, ASEAN_COUNTING + NO_COUNTING, MAKRUK_COUNTING, CAMBODIAN_COUNTING, ASEAN_COUNTING }; enum ChasingRule { diff --git a/src/variant.cpp b/src/variant.cpp index dfd8573..3af35f9 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -121,6 +121,7 @@ namespace { v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde - 0 1"; v->gating = true; v->cambodianMoves = true; + v->countingRule = CAMBODIAN_COUNTING; v->nnueAlias = "makruk"; return v; } diff --git a/src/variants.ini b/src/variants.ini index 4e8cb7e..a842c81 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -126,7 +126,7 @@ # [Bitboard]: list of squares [e.g., d4 e4 d5 e5]. * can be used as wildcard for files (e.g., *1 is the first rank) # [Value]: game result for the side to move [win, loss, draw] # [MaterialCounting]: material couting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] -# [CountingRule]: makruk or ASEAN counting rules [makruk, asean, none] +# [CountingRule]: makruk, cambodian, or ASEAN counting rules [makruk, cambodian, asean, none] # [ChasingRule]: xiangqi chasing rules [axf, none] # [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, none] diff --git a/test.py b/test.py index ade9e97..8ea4620 100644 --- a/test.py +++ b/test.py @@ -436,6 +436,66 @@ class TestPyffish(unittest.TestCase): result = sf.get_fen("makruk", fen, moves, False, False, True, 58) self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 8 33") + # ouk piece honor counting + fen = "8/3k4/8/2K1S1P1/8/8/8/8 w - - 0 1" + moves = ["g5g6m"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "8/3k4/6M~1/2K1S3/8/8/8/8 b - 86 8 1") + + fen = "8/2K3k1/5m2/4S1S1/8/8/8/8 w - 128 97 1" + moves = ["e5f6"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "8/2K3k1/5S2/6S1/8/8/8/8 b - 42 8 1") + + # adjust to board honor counting if it's faster + fen = "8/3k4/8/2K1S1P1/8/8/8/8 w - - 0 1" + moves = ["g5g6m"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, -1) + self.assertEqual(result, "8/3k4/6M~1/2K1S3/8/8/8/8 b - 86 8 1") + + fen = "8/2K3k1/5m2/4S1S1/8/8/8/8 w - 126 101 80" + moves = ["e5f6"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, 58) + self.assertEqual(result, "8/2K3k1/5S2/6S1/8/8/8/8 b - 126 102 80") + + # pawn promotion triggers piece honor counting + fen = "8/8/4k3/5P2/8/2RMK3/8/8 w - 126 41 50" + moves = ["f5f6m"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, 58) + self.assertEqual(result, "8/8/4kM~2/8/8/2RMK3/8/8 b - 30 10 50") + + # king capturing the last unpromoted pawn triggers piece honor counting + fen = "8/8/4k3/5P2/8/2RMK3/8/8 b - 126 42 50" + moves = ["e6f5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, 58) + self.assertEqual(result, "8/8/8/5k2/8/2RMK3/8/8 w - 30 7 51") + + # ouk board honor counting + fen = "3k4/2m5/8/4MP2/3KS3/8/8/8 w - - 0 1" + moves = ["f5f6m"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/4M3/3KS3/8/8/8 b - 126 0 1") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 126 0 33" + moves = ["d4d5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 126 1 33") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 126 36 1" + moves = ["d4d5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 126 37 1") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 126 0 33" + moves = ["d4d5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, -1) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 126 0 33") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 126 7 33" + moves = ["d4d5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, 58) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 126 8 33") + # asean counting fen = "4k3/3r4/2K5/8/3R4/8/8/8 w - - 0 1" moves = ["d4d7"]