Fix FEN parsing for S-Chess
authorFabian Fichter <ianfab@users.noreply.github.com>
Thu, 9 Jan 2025 18:49:43 +0000 (19:49 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Thu, 9 Jan 2025 19:23:31 +0000 (20:23 +0100)
Closes #855.

src/position.cpp
test.py

index 40fa6e3..5026a6f 100644 (file)
@@ -384,17 +384,16 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
                                          : make_square(castling_king_file(), castling_rank(c));
               // Skip invalid castling rights
               if (!(castlingKings & st->castlingKingSquare[c]))
-              {
                   st->castlingKingSquare[c] = SQ_NONE;
-                  continue;
-              }
           }
 
           // Set gates (and skip castling rights)
           if (gating())
           {
-              st->gatesBB[c] |= rsq;
-              if (token == 'K' || token == 'Q')
+              // Only add gates for occupied squares
+              if (pieces(c) & rsq)
+                  st->gatesBB[c] |= rsq;
+              if ((token == 'K' || token == 'Q') && st->castlingKingSquare[c] != SQ_NONE)
                   st->gatesBB[c] |= st->castlingKingSquare[c];
               // Do not set castling rights for gates unless there are no pieces in hand,
               // which means that the file is referring to a chess960 castling right.
@@ -402,7 +401,10 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
                   continue;
           }
 
-          if (castling_enabled() && (castling_rook_pieces(c) & type_of(piece_on(rsq))) && color_of(piece_on(rsq)) == c)
+          // Only add castling right if both king and rook are on expected squares
+          if (   castling_enabled()
+              && st->castlingKingSquare[c] != SQ_NONE
+              && (castling_rook_pieces(c) & type_of(piece_on(rsq))) && color_of(piece_on(rsq)) == c)
               set_castling_right(c, rsq);
       }
 
diff --git a/test.py b/test.py
index 9198914..f235748 100644 (file)
--- a/test.py
+++ b/test.py
@@ -337,6 +337,11 @@ class TestPyffish(unittest.TestCase):
         result = sf.legal_moves("shogun", SHOGUN, ["c2c4", "b8c6", "b2b4", "b7b5", "c4b5", "c6b8"])
         self.assertIn("b5b6+", result)
 
+        # Seirawan gating but no castling
+        fen = "rnbq3r/pp2bkpp/8/2p1p2K/2p1P3/8/PPPP1PPP/RNB4R[EHeh] b ABCHabcdh - 0 10"
+        result = sf.legal_moves("seirawan", fen, [])
+        self.assertIn("c8g4h", result)
+
         # In Cannon Shogi the FGC and FSC can also move one square diagonally and, besides,
         # move or capture two squares diagonally, by leaping an adjacent piece. 
         fen = "lnsg1gsnl/1rc1kuab1/p1+A1p1p1p/3P5/6i2/6P2/P1P1P3P/1B1U1ICR1/LNSGKGSNL[] w - - 1 3"
@@ -460,6 +465,12 @@ class TestPyffish(unittest.TestCase):
         result = sf.get_fen("seirawan", fen0, ["e8g8"])
         self.assertEqual(result, fen1)
 
+        # handle invalid castling/gating flags
+        fen0 = "rnbq3r/pp2bkpp/8/2p1p2K/2p1P3/8/PPPP1PPP/RNB4R[EHeh] b QBCEHabcdk - 0 10"
+        fen1 = "rnbq3r/pp2bkpp/8/2p1p2K/2p1P3/8/PPPP1PPP/RNB4R[EHeh] b ABCHabcdh - 0 10"
+        result = sf.get_fen("seirawan", fen0, [])
+        self.assertEqual(result, fen1)
+
         result = sf.get_fen("chess", CHESS, [], True, False, False)
         self.assertEqual(result, CHESS960)