Allow encoding of non-contiguous slider ranges
authorH.G.Muller <hgm@hgm-xboard.(none)>
Sat, 3 Jan 2026 19:07:19 +0000 (20:07 +0100)
committerH.G.Muller <hgm@hgm-xboard.(none)>
Sat, 3 Jan 2026 20:16:04 +0000 (21:16 +0100)
The range of sliders as encoded in the steps/slidrs/hoppers maps
that are passed from the Betza parser to the attack bitboard generators
are no longer encoded as a number that indicates the range, but as the set
of squares along the ray. Each of the lower 16 bits in this parameter
corresponds to such a square, and setting the bit would deny access to that
by keeping it out of the pseudoMoves/Attacks bitboards. So a range-3
slider would need code 0xFFF8, range 4 0xFFF0 etc.
  This opens the possibility to encode lame ski-sliders by passing a 1,
excluding the first square on the ray as destination. (But keeping it
as a potential blocking square of the entire slide. Or lame riders like
the Dabbabarider (0x5555) or lame Panda (0xAAAAA), in combination with
the Rook magics.
  This does not affect leapers, which keep using a 1 code for encoding
lameness.
  Note that although non-contiguous destinations can currently break
FSF's check detection, leading to crashes. This occurs with sliders that
can deliver check by moving radially away from the King, such as lame
ski-sliders. Lame elemntary riders (DD, AA, HH) should be OK.

src/bitboard.cpp
src/piece.cpp
src/psqt.cpp

index b590265..da2f98d 100644 (file)
@@ -122,27 +122,24 @@ namespace {
 #endif
 
   template <MovementType MT>
-  Bitboard sliding_attack(std::map<DirectionCode, int> directions, Square sq, Bitboard occupied, Color c = WHITE) {
-    assert(MT != LAME_LEAPER);
-
-    Bitboard attack = 0;
+  Bitboard one_ride(DirectionCode v, int limit, Square sq, Bitboard occupied, Color c) {
 
-    for (auto const& [v, limit] : directions)
-    {
-        int count = 0;
         bool hurdle = false;
         Direction d = board_step(v);
-        for (Square s = sq + (c == WHITE ? d : -d);
-             is_ok(s) && distance(s, s - (c == WHITE ? d : -d)) <= 2;
-             s += (c == WHITE ? d : -d))
+        int lim = limit;
+        Bitboard attack = 0;
+
+        if(c != WHITE) d = -d;
+        if(MT == HOPPER_RANGE && limit == 0xFFFE) lim = 0; // give grasshopper unlimited total range
+
+        for (Square s = sq + d;
+             is_ok(s) && distance(s, s - d) <= 2;
+             s += d)
         {
             if (MT != HOPPER || hurdle)
             {
-                attack |= s;
-                // 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;
+                if(!(lim & 1)) attack |= s;
+                lim >>= 1;
             }
 
             if (occupied & s)
@@ -153,8 +150,20 @@ namespace {
                     break;
             }
         }
-    }
 
+        return attack;
+  }
+
+  template <MovementType MT>
+  Bitboard sliding_attack(std::map<DirectionCode, int> directions, Square sq, Bitboard occupied, Color c = WHITE) {
+    assert(MT != LAME_LEAPER);
+
+    Bitboard attack = 0;
+
+    for (auto const& [v, limit] : directions)
+    {
+        attack |= one_ride<MT>(v, limit & 0xFFFF, sq, occupied, c);
+    }
     return attack;
   }
 
index cd6660e..f6ad675 100644 (file)
@@ -77,7 +77,7 @@ namespace {
               hopper = true;
               // Grasshopper
               if (c == 'g')
-                  distance = 1;
+                  distance = 0xFFFE; // range 1
           }
           // Lame leaper
           else if (c == 'n')
@@ -123,7 +123,7 @@ namespace {
                       if (isdigit(betza[i])) {
                           int range = betza[i] - '0';
                           if(range)
-                              distance = range;
+                              distance |= (0xFFFF << range & 0xFFFF);
                       }
                   } else i--;
                }
index 0732c4d..827bf9c 100644 (file)
@@ -157,7 +157,8 @@ constexpr Score PBonus[RANK_NB][FILE_NB] =
 int slider_fraction(std::map<DirectionCode, int> slider) {
     int s = 0;
     for (auto const& [_, limit] : slider) {
-        s += limit == 0 ? 100 : 200 * std::min(limit + 1, 8) / 16;
+        int targets = popcount(~limit & 0xFFFF); // in reality non-reachable squares should be weighted by distance
+        s += limit == 0 ? 100 : 200 * std::min(targets + 1, 8) / 16;
     }
     return s;
 }