Remove squares that do not block any moves from magic masks
authorH.G.Muller <hgm@hgm-xboard.(none)>
Fri, 9 Jan 2026 10:59:42 +0000 (11:59 +0100)
committerH.G.Muller <hgm@hgm-xboard.(none)>
Fri, 9 Jan 2026 10:59:42 +0000 (11:59 +0100)
The old code was removing edge squares from the Magic masks, but that is
only sufficient for sliders. Pieces like Nightriders can already fall off
the board when they are two files / ranks away from the edge. The proper
procedure is to remove the fore-last square of any ride from the mask.
The routine sliding_attack has been adapted to do that, when an additional
argument asks it to do so.
  As a result, the attacks table for Nightriders can now be much smaller:
it shrinks from more than 7MB to only 840KB.

src/bitboard.cpp

index 9eeb16c..d1a9628 100644 (file)
@@ -84,7 +84,7 @@ namespace {
   Bitboard ElephantTable[0x400];  // To store elephant attacks
   Bitboard JanggiElephantTable[0x1C000];  // To store janggi elephant attacks
   Bitboard CannonDiagTable[0x33C00]; // To store diagonal cannon attacks
-  Bitboard NightriderTable[0x70200]; // To store nightrider attacks
+  Bitboard NightriderTable[0xD200]; // To store nightrider attacks
   Bitboard GrasshopperTableH[0x11800];  // To store horizontal grasshopper attacks
   Bitboard GrasshopperTableV[0x4800];  // To store vertical grasshopper attacks
   Bitboard GrasshopperTableD[0x33C00]; // To store diagonal grasshopper attacks
@@ -99,7 +99,7 @@ namespace {
   Bitboard ElephantTable[0x1A0];  // To store elephant attacks
   Bitboard JanggiElephantTable[0x5C00];  // To store janggi elephant attacks
   Bitboard CannonDiagTable[0x1480]; // To store diagonal cannon attacks
-  Bitboard NightriderTable[0x1840]; // To store nightrider attacks
+  Bitboard NightriderTable[0x500]; // To store nightrider attacks
   Bitboard GrasshopperTableH[0xA00];  // To store horizontal grasshopper attacks
   Bitboard GrasshopperTableV[0xA00];  // To store vertical grasshopper attacks
   Bitboard GrasshopperTableD[0x1480]; // To store diagonal grasshopper attacks
@@ -138,7 +138,7 @@ namespace {
 #endif
 
   template <MovementType MT>
-  Bitboard one_ride(DirectionCode v, int limit, Square sq, Bitboard occupied, Color c) {
+  Bitboard one_ride(DirectionCode v, int limit, Square sq, Bitboard occupied, Color c, bool mask) {
 
         bool hurdle = false;
         Direction d = board_step(v);
@@ -162,6 +162,8 @@ namespace {
              is_ok(s) && distance(s, s - d) <= 2;
              s += d)
         {
+            if(mask && (!is_ok(s + d) || distance(s, s + d) > 2)) break; // forelast square is not blocker
+
             if (MT != HOPPER || hurdle)
             {
                 if(!(lim & 1)) attack |= s;
@@ -181,14 +183,14 @@ namespace {
   }
 
   template <MovementType MT>
-  Bitboard sliding_attack(std::map<DirectionCode, int> directions, Square sq, Bitboard occupied, Color c = WHITE) {
+  Bitboard sliding_attack(std::map<DirectionCode, int> directions, Square sq, Bitboard occupied, Color c = WHITE, bool mask = false) {
     assert(MT != LAME_LEAPER);
 
     Bitboard attack = 0;
 
     for (auto const& [v, limit] : directions)
     {
-        attack |= one_ride<MT>(v, limit & 0xFFFF, sq, occupied, c);
+        attack |= one_ride<MT>(v, limit & 0xFFFF, sq, occupied, c, mask);
     }
     return attack;
   }
@@ -532,9 +534,6 @@ Bitboard puzzle(Bitboard key, Bitboard maskBitsToMap, Bitboard magic, int keySta
 
     for (Square s = SQ_A1; s <= SQ_MAX; ++s)
     {
-        // Board edges are not considered in the relevant occupancies
-        edges = ((Rank1BB | rank_bb(RANK_MAX)) & ~rank_bb(s)) | ((FileABB | file_bb(FILE_MAX)) & ~file_bb(s));
-
         // Given a square 's', the mask is the bitboard of sliding attacks from
         // 's' computed on an empty board. The index must be big enough to contain
         // all the attacks for each possible subset of the mask and so is 2 power
@@ -542,7 +541,7 @@ Bitboard puzzle(Bitboard key, Bitboard maskBitsToMap, Bitboard magic, int keySta
         // 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 ? HOPPER_RANGE : 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, WHITE, true));
 #ifdef LARGEBOARDS
         m.shift = 128 - popcount(m.mask);
 #else