Fix cannonshogi (#757)
authorFabian Fichter <ianfab@users.noreply.github.com>
Fri, 16 Feb 2024 18:42:26 +0000 (19:42 +0100)
committerGitHub <noreply@github.com>
Fri, 16 Feb 2024 18:42:26 +0000 (19:42 +0100)
src/bitboard.cpp
src/variants.ini
test.py

index 3939296..4f00575 100644 (file)
@@ -111,7 +111,7 @@ namespace {
   const std::map<Direction, int> GrasshopperDirectionsH { {EAST, 1}, {WEST, 1} };
   const std::map<Direction, int> GrasshopperDirectionsD { {NORTH_EAST, 1}, {SOUTH_EAST, 1}, {SOUTH_WEST, 1}, {NORTH_WEST, 1} };
 
-  enum MovementType { RIDER, HOPPER, LAME_LEAPER, UNLIMITED_RIDER };
+  enum MovementType { RIDER, HOPPER, LAME_LEAPER, HOPPER_RANGE };
 
   template <MovementType MT>
 #ifdef PRECOMPUTED_MAGICS
@@ -137,7 +137,9 @@ namespace {
             if (MT != HOPPER || hurdle)
             {
                 attack |= s;
-                if (limit && MT != UNLIMITED_RIDER && ++count >= limit)
+                // For hoppers we consider limit == 1 as a grasshopper,
+                // but limit > 1 as a limited distance hopper
+                if (limit && !(MT == HOPPER_RANGE && limit == 1) && ++count >= limit)
                     break;
             }
 
@@ -300,7 +302,7 @@ void Bitboards::init_pieces() {
                               leaper |= safe_destination(s, c == WHITE ? d : -d);
                       }
                       pseudo |= sliding_attack<RIDER>(pi->slider[initial][modality], s, 0, c);
-                      pseudo |= sliding_attack<UNLIMITED_RIDER>(pi->hopper[initial][modality], s, 0, c);
+                      pseudo |= sliding_attack<HOPPER_RANGE>(pi->hopper[initial][modality], s, 0, c);
                   }
               }
           }
@@ -420,7 +422,7 @@ namespace {
         // apply to the 64 or 32 bits word to get the index.
         Magic& m = magics[s];
         // The mask for hoppers is unlimited distance, even if the hopper is limited distance (e.g., grasshopper)
-        m.mask  = (MT == LAME_LEAPER ? lame_leaper_path(directions, s) : sliding_attack<MT == HOPPER ? UNLIMITED_RIDER : MT>(directions, s, 0)) & ~edges;
+        m.mask  = (MT == LAME_LEAPER ? lame_leaper_path(directions, s) : sliding_attack<MT == HOPPER ? HOPPER_RANGE : MT>(directions, s, 0)) & ~edges;
 #ifdef LARGEBOARDS
         m.shift = 128 - popcount(m.mask);
 #else
index aaf4081..09be0e4 100644 (file)
@@ -1662,8 +1662,8 @@ cannon = u
 customPiece1 = a:pR
 customPiece2 = c:mBcpB
 customPiece3 = i:pB
-customPiece4 = w:mRpRFAcpR
-customPiece5 = f:mBpBWDcpB
+customPiece4 = w:mRpRmFpB2
+customPiece5 = f:mBpBmWpR2
 promotedPieceType = u:w a:w c:f i:f
 startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1
 
diff --git a/test.py b/test.py
index 9cf4c53..7a5aa45 100644 (file)
--- a/test.py
+++ b/test.py
@@ -100,6 +100,19 @@ customPiece2 = b:rhN
 customPiece3 = c:hlN
 customPiece4 = d:hrN
 startFen = 7/7/7/3A3/7/7/7 w - - 0 1
+
+[cannonshogi:shogi]
+dropNoDoubled = -
+shogiPawnDropMateIllegal = false
+soldier = p
+cannon = u
+customPiece1 = a:pR
+customPiece2 = c:mBcpB
+customPiece3 = i:pB
+customPiece4 = w:mRpRmFpB2
+customPiece5 = f:mBpBmWpR2
+promotedPieceType = u:w a:w c:f i:f
+startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1
 """
 
 sf.load_variant_config(ini_text)
@@ -317,6 +330,24 @@ class TestPyffish(unittest.TestCase):
         result = sf.legal_moves("shogun", SHOGUN, ["c2c4", "b8c6", "b2b4", "b7b5", "c4b5", "c6b8"])
         self.assertIn("b5b6+", 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"
+        result = sf.legal_moves("cannonshogi", fen, [])
+        # mF
+        self.assertIn("c7b6", result)
+        self.assertIn("c7d8", result)
+        self.assertNotIn("c7d6", result)
+        self.assertNotIn("c7b8", result)
+        # pB2
+        self.assertIn("c7a9", result)
+        self.assertIn("c7e5", result)
+        self.assertNotIn("c7a5", result)
+        self.assertNotIn("c7e9", result)
+        # verify distance limited to 2
+        self.assertNotIn("c7f4", result)
+        self.assertNotIn("c7g3", result)
+
         # Cambodian queen cannot capture with its leap
         # Cambodian king cannot leap to escape check
         result = sf.legal_moves("cambodian", CAMBODIAN, ["b1d2", "g8e7", "d2e4", "d6d5", "e4d6"])