Derive new magics from existing sliders dynamically
authorH.G.Muller <hgm@hgm-xboard.(none)>
Sat, 3 Jan 2026 20:39:15 +0000 (21:39 +0100)
committerH.G.Muller <hgm@hgm-xboard.(none)>
Sun, 4 Jan 2026 08:03:14 +0000 (09:03 +0100)
The infra-structure is provided for adding new magic attack getters from
those for Rook and Bishop. These use the same attacks tables as the one
they are copied from, but mask away some squares from teh original masks.
  This is controlled by the 16 high-order bits of the range code passed
from the Betza parser to the bitboard-creating routines, which then do
for the masks what the 16 low-order bits do for the pseudoAttacks:
remove those squares from the slider path for which the corresponding bit
was set.
  This is a memory-efficient way for creating new types of rides in the 8
principal directions, such as Dabbaba- or Alfilrider, or even those with
still larger strides, such as the Threeleaperrider (HH). It can in principle
also be used for defining (non-lame) ski-sliders or a Panda.

src/bitboard.cpp
src/types.h

index da2f98d..970fe3b 100644 (file)
@@ -54,11 +54,16 @@ Magic NightriderMagics[SQUARE_NB];
 Magic GrasshopperMagicsH[SQUARE_NB];
 Magic GrasshopperMagicsV[SQUARE_NB];
 Magic GrasshopperMagicsD[SQUARE_NB];
+Magic CustomMagics[MAX_RIDE - CUSTOM_RIDES][SQUARE_NB];
 
-Magic* magics[] = {BishopMagics, RookMagicsH, RookMagicsV, CannonMagicsH, CannonMagicsV,
+Magic* magics[MAX_RIDE] = {BishopMagics, RookMagicsH, RookMagicsV, CannonMagicsH, CannonMagicsV,
                    LameDabbabaMagics, HorseMagics, ElephantMagics, JanggiElephantMagics, CannonDiagMagics, NightriderMagics,
                    GrasshopperMagicsH, GrasshopperMagicsV, GrasshopperMagicsD};
 
+int nrOfRides;
+int riderBaseType[MAX_RIDE];
+int riderRangeMask[MAX_RIDE];
+
 namespace {
 
 // Some magics need to be split in order to reduce memory consumption.
@@ -112,6 +117,8 @@ namespace {
   const std::map<DirectionCode, int> GrasshopperDirectionsD { {step(1, 1), 0xFFFE}, {step(-1, 1), 0xFFFE},
                                                               {step(-1, -1), 0xFFFE}, {step(1, -1), 0xFFFE} };
 
+  const std::map<DirectionCode, int> allDirections[] { BishopDirections, RookDirectionsH, RookDirectionsV };
+
   enum MovementType { RIDER, HOPPER, LAME_LEAPER, HOPPER_RANGE };
 
   template <MovementType MT>
@@ -242,6 +249,40 @@ std::string Bitboards::pretty(Bitboard b) {
   return s;
 }
 
+/// Derive a new RiderType from a sliding one, (if it did not exist yet)
+/// by removing squares to be skipped from the masks of the latter
+
+RiderType assign_magic(RiderType r, int limit) {
+
+  int n = lsb(r);
+  int i;
+  limit >>= 16;
+
+  for(i = 0; i < nrOfRides; i++)
+      if(riderBaseType[i] == n && riderRangeMask[i] == limit) return RiderType(1 << i);
+
+  if(nrOfRides == MAX_RIDE) return r; // use standard mask if capacity exceeded
+
+  i = nrOfRides++; // allocate new magic
+  riderBaseType[i] = n;
+  riderRangeMask[i] = limit;
+  Magic* m = CustomMagics[i - CUSTOM_RIDES];
+  magics[i] = m;
+
+  for(Square s = SQ_A1; s < SQUARE_NB; ++s)
+  {
+      Bitboard b = 0;
+      m[s] = magics[n][s];
+
+      for (auto const& [v, limit2] : allDirections[n]) {
+          b |= one_ride<RIDER>(v, limit, s, 0, WHITE);
+      }
+      m[s].mask &= b; // restrict mask according to high bits of limit
+  }
+
+  return RiderType(1 << i);
+}
+
 /// Bitboards::init_pieces() initializes piece move/attack bitboards and rider types
 
 void Bitboards::init_pieces() {
@@ -274,11 +315,11 @@ void Bitboards::init_pieces() {
               for (auto const& [d, limit] : pi->slider[initial][modality])
               {
                   if (BishopDirections.find(d) != BishopDirections.end())
-                      riderTypes |= RIDER_BISHOP;
+                      riderTypes |= assign_magic(RIDER_BISHOP, limit);
                   if (RookDirectionsH.find(d) != RookDirectionsH.end())
-                      riderTypes |= RIDER_ROOK_H;
+                      riderTypes |= assign_magic(RIDER_ROOK_H, limit);
                   if (RookDirectionsV.find(d) != RookDirectionsV.end())
-                      riderTypes |= RIDER_ROOK_V;
+                      riderTypes |= assign_magic(RIDER_ROOK_V, limit);
                   if (HorseDirections.find(d) != HorseDirections.end())
                       riderTypes |= RIDER_NIGHTRIDER;
               }
@@ -377,6 +418,9 @@ void Bitboards::init() {
   init_magics<HOPPER>(GrasshopperTableD, GrasshopperMagicsD, GrasshopperDirectionsD);
 #endif
 
+  nrOfRides = CUSTOM_RIDES;
+  for(int i = 0; i < CUSTOM_RIDES; i++) riderBaseType[i] = i;
+
   init_pieces();
 
   for (Square s1 = SQ_A1; s1 <= SQ_MAX; ++s1)
index e271faf..aac1085 100644 (file)
@@ -472,6 +472,8 @@ enum RiderType : int {
   ASYMMETRICAL_RIDERS =  RIDER_HORSE | RIDER_JANGGI_ELEPHANT
                        | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D,
   NON_SLIDING_RIDERS = HOPPING_RIDERS | LAME_LEAPERS | RIDER_NIGHTRIDER,
+  CUSTOM_RIDES = 20,
+  MAX_RIDE = 32
 };
 
 extern Value PieceValue[PHASE_NB][PIECE_NB];