Initial development for fairy chess variants
authorianfab <ianfab@users.noreply.github.com>
Fri, 22 Jun 2018 19:16:16 +0000 (21:16 +0200)
committerianfab <ianfab@users.noreply.github.com>
Fri, 22 Jun 2018 19:16:16 +0000 (21:16 +0200)
Generalize code to be able to easily configure new variants
with different pieces and rules. Add a few initial variants:
- Chess
- Makruk
- ASEAN
- Ai-Wok
- Shatranj
- Amazon
- Hoppel-Poppel

22 files changed:
.gitignore [new file with mode: 0644]
src/Makefile
src/bitbase.cpp
src/bitboard.cpp
src/bitboard.h
src/endgame.cpp
src/evaluate.cpp
src/main.cpp
src/movegen.cpp
src/pawns.cpp
src/position.cpp
src/position.h
src/psqt.cpp
src/search.cpp
src/syzygy/tbprobe.cpp
src/thread.cpp
src/types.h
src/uci.cpp
src/uci.h
src/ucioption.cpp
src/variant.cpp [new file with mode: 0644]
src/variant.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..417fc40
--- /dev/null
@@ -0,0 +1,3 @@
+stockfish
+*.o
+.depend
index 34264e7..862bdb5 100644 (file)
@@ -38,7 +38,7 @@ PGOBENCH = ./$(EXE) bench
 ### Object files
 OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \
        material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \
-       search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o
+       search.o thread.o timeman.o tt.o uci.o ucioption.o variant.o syzygy/tbprobe.o
 
 ### Establish the operating system name
 KERNEL = $(shell uname -s)
index 6004828..5ebf444 100644 (file)
@@ -117,7 +117,7 @@ namespace {
     if (   distance(ksq[WHITE], ksq[BLACK]) <= 1
         || ksq[WHITE] == psq
         || ksq[BLACK] == psq
-        || (us == WHITE && (PawnAttacks[WHITE][psq] & ksq[BLACK])))
+        || (us == WHITE && (PseudoAttacks[WHITE][PAWN][psq] & ksq[BLACK])))
         result = INVALID;
 
     // Immediate win if a pawn can be promoted without getting captured
@@ -125,13 +125,13 @@ namespace {
              && rank_of(psq) == RANK_7
              && ksq[us] != psq + NORTH
              && (    distance(ksq[~us], psq + NORTH) > 1
-                 || (PseudoAttacks[KING][ksq[us]] & (psq + NORTH))))
+                 || (PseudoAttacks[us][KING][ksq[us]] & (psq + NORTH))))
         result = WIN;
 
     // Immediate draw if it is a stalemate or a king captures undefended pawn
     else if (   us == BLACK
-             && (  !(PseudoAttacks[KING][ksq[us]] & ~(PseudoAttacks[KING][ksq[~us]] | PawnAttacks[~us][psq]))
-                 || (PseudoAttacks[KING][ksq[us]] & psq & ~PseudoAttacks[KING][ksq[~us]])))
+             && (  !(PseudoAttacks[us][KING][ksq[us]] & ~(PseudoAttacks[us][KING][ksq[~us]] | PseudoAttacks[~us][PAWN][psq]))
+                 || (PseudoAttacks[us][KING][ksq[us]] & psq & ~PseudoAttacks[us][KING][ksq[~us]])))
         result = DRAW;
 
     // Position will be classified later
@@ -157,7 +157,7 @@ namespace {
     constexpr Result Bad  = (Us == WHITE ? DRAW  : WIN);
 
     Result r = INVALID;
-    Bitboard b = PseudoAttacks[KING][ksq[Us]];
+    Bitboard b = PseudoAttacks[Us][KING][ksq[Us]];
 
     while (b)
         r |= Us == WHITE ? db[index(Them, ksq[Them]  , pop_lsb(&b), psq)]
index b19d401..ae617c8 100644 (file)
@@ -37,8 +37,10 @@ Bitboard DistanceRingBB[SQUARE_NB][8];
 Bitboard ForwardFileBB[COLOR_NB][SQUARE_NB];
 Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB];
 Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB];
-Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
-Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
+Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
 
 Magic RookMagics[SQUARE_NB];
 Magic BishopMagics[SQUARE_NB];
@@ -58,6 +60,24 @@ namespace {
     u = ((u >> 4) + u) & 0x0F0FU;
     return (u * 0x0101U) >> 8;
   }
+
+  Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied, int maxDist = 7) {
+
+    Bitboard attack = 0;
+
+    for (int i = 0; directions[i]; ++i)
+        for (Square s = sq + directions[i];
+             is_ok(s) && distance(s, s - directions[i]) == 1 && distance(s, sq) <= maxDist;
+             s += directions[i])
+        {
+            attack |= s;
+
+            if (occupied & s)
+                break;
+        }
+
+    return attack;
+  }
 }
 
 
@@ -119,43 +139,148 @@ void Bitboards::init() {
               DistanceRingBB[s1][SquareDistance[s1][s2] - 1] |= s2;
           }
 
-  int steps[][5] = { {}, { 7, 9 }, { 6, 10, 15, 17 }, {}, {}, {}, { 1, 7, 8, 9 } };
+  // Piece moves
+  Direction RookDirections[5] = { NORTH,  EAST,  SOUTH,  WEST };
+  Direction BishopDirections[5] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST };
+
+  init_magics(RookTable, RookMagics, RookDirections);
+  init_magics(BishopTable, BishopMagics, BishopDirections);
+
+  int stepsCapture[][13] = {
+      {}, // NO_PIECE_TYPE
+      { 7, 9 }, // pawn
+      { -17, -15, -10, -6, 6, 10, 15, 17 }, // knight
+      {}, // bishop
+      {}, // rook
+      {}, // queen
+      { -9, -7, 7, 9 }, // fers/met
+      { -18, -14, 14, 18 }, // alfil
+      { -9, -7, 7, 8, 9 }, // silver/khon
+      { -17, -15, -10, -9, -7, -6, 6, 7, 9, 10, 15, 17 }, // aiwok
+      { -17, -15, -10, -6, 6, 10, 15, 17 }, // amazon
+      {}, // knibis
+      { -17, -15, -10, -6, 6, 10, 15, 17 }, // biskni
+      { -9, -8, -7, -1, 1, 7, 8, 9 } // king
+  };
+  int stepsQuiet[][13] = {
+      {}, // NO_PIECE_TYPE
+      { 8 }, // pawn
+      { -17, -15, -10, -6, 6, 10, 15, 17 }, // knight
+      {}, // bishop
+      {}, // rook
+      {}, // queen
+      { -9, -7, 7, 9 }, // fers/met
+      { -18, -14, 14, 18 }, // alfil
+      { -9, -7, 7, 8, 9 }, // silver/khon
+      { -17, -15, -10, -9, -7, -6, 6, 7, 9, 10, 15, 17 }, // aiwok
+      { -17, -15, -10, -6, 6, 10, 15, 17 }, // amazon
+      { -17, -15, -10, -6, 6, 10, 15, 17 }, // knibis
+      {}, // biskni
+      { -9, -8, -7, -1, 1, 7, 8, 9 } // king
+  };
+  Direction sliderCapture[][9] = {
+    {}, // NO_PIECE_TYPE
+    {}, // pawn
+    {}, // knight
+    { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // bishop
+    { NORTH,  EAST,  SOUTH,  WEST }, // rook
+    { NORTH,  EAST,  SOUTH,  WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // queen
+    {}, // fers/met
+    {}, // alfil
+    {}, // silver/khon
+    { NORTH,  EAST,  SOUTH,  WEST }, // aiwok
+    { NORTH,  EAST,  SOUTH,  WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // amazon
+    { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // knibis
+    {}, // biskni
+    {} // king
+  };
+  Direction sliderQuiet[][9] = {
+    {}, // NO_PIECE_TYPE
+    {}, // pawn
+    {}, // knight
+    { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // bishop
+    { NORTH,  EAST,  SOUTH,  WEST }, // rook
+    { NORTH,  EAST,  SOUTH,  WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // queen
+    {}, // fers/met
+    {}, // alfil
+    {}, // silver/khon
+    { NORTH,  EAST,  SOUTH,  WEST }, // aiwok
+    { NORTH,  EAST,  SOUTH,  WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // amazon
+    {}, // knibis
+    { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // biskni
+    {} // king
+  };
+  int sliderDistCapture[] = {
+    0, // NO_PIECE_TYPE
+    0, // pawn
+    0, // knight
+    7, // bishop
+    7, // rook
+    7, // queen
+    0, // fers/met
+    0, // alfil
+    0, // silver/khon
+    7, // aiwok
+    7, // amazon
+    7, // knibis
+    0, // biskni
+    0  // king
+  };
+  int sliderDistQuiet[] = {
+    0, // NO_PIECE_TYPE
+    0, // pawn
+    0, // knight
+    7, // bishop
+    7, // rook
+    7, // queen
+    0, // fers/met
+    0, // alfil
+    0, // silver/khon
+    7, // aiwok
+    7, // amazon
+    0, // knibis
+    7, // biskni
+    0  // king
+  };
 
   for (Color c = WHITE; c <= BLACK; ++c)
-      for (PieceType pt : { PAWN, KNIGHT, KING })
+      for (PieceType pt = PAWN; pt <= KING; ++pt)
           for (Square s = SQ_A1; s <= SQ_H8; ++s)
-              for (int i = 0; steps[pt][i]; ++i)
+          {
+              for (int i = 0; stepsCapture[pt][i]; ++i)
               {
-                  Square to = s + Direction(c == WHITE ? steps[pt][i] : -steps[pt][i]);
+                  Square to = s + Direction(c == WHITE ? stepsCapture[pt][i] : -stepsCapture[pt][i]);
 
-                  if (is_ok(to) && distance(s, to) < 3)
+                  if (is_ok(to) && distance(s, to) < 4)
                   {
-                      if (pt == PAWN)
-                          PawnAttacks[c][s] |= to;
-                      else
-                          PseudoAttacks[pt][s] |= to;
+                      PseudoAttacks[c][pt][s] |= to;
+                      LeaperAttacks[c][pt][s] |= to;
                   }
               }
+              for (int i = 0; stepsQuiet[pt][i]; ++i)
+              {
+                  Square to = s + Direction(c == WHITE ? stepsQuiet[pt][i] : -stepsQuiet[pt][i]);
 
-  Direction RookDirections[] = { NORTH,  EAST,  SOUTH,  WEST };
-  Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST };
-
-  init_magics(RookTable, RookMagics, RookDirections);
-  init_magics(BishopTable, BishopMagics, BishopDirections);
+                  if (is_ok(to) && distance(s, to) < 4)
+                  {
+                      PseudoMoves[c][pt][s] |= to;
+                      LeaperMoves[c][pt][s] |= to;
+                  }
+              }
+              PseudoAttacks[c][pt][s] |= sliding_attack(sliderCapture[pt], s, 0, sliderDistCapture[pt]);
+              PseudoMoves[c][pt][s] |= sliding_attack(sliderQuiet[pt], s, 0, sliderDistQuiet[pt]);
+          }
 
   for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
   {
-      PseudoAttacks[QUEEN][s1]  = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
-      PseudoAttacks[QUEEN][s1] |= PseudoAttacks[  ROOK][s1] = attacks_bb<  ROOK>(s1, 0);
-
       for (PieceType pt : { BISHOP, ROOK })
           for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
           {
-              if (!(PseudoAttacks[pt][s1] & s2))
+              if (!(PseudoAttacks[WHITE][pt][s1] & s2))
                   continue;
 
-              LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
-              BetweenBB[s1][s2] = attacks_bb(pt, s1, SquareBB[s2]) & attacks_bb(pt, s2, SquareBB[s1]);
+              LineBB[s1][s2] = (attacks_bb(WHITE, pt, s1, 0) & attacks_bb(WHITE, pt, s2, 0)) | s1 | s2;
+              BetweenBB[s1][s2] = attacks_bb(WHITE, pt, s1, SquareBB[s2]) & attacks_bb(WHITE, pt, s2, SquareBB[s1]);
           }
   }
 }
@@ -163,25 +288,6 @@ void Bitboards::init() {
 
 namespace {
 
-  Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) {
-
-    Bitboard attack = 0;
-
-    for (int i = 0; i < 4; ++i)
-        for (Square s = sq + directions[i];
-             is_ok(s) && distance(s, s - directions[i]) == 1;
-             s += directions[i])
-        {
-            attack |= s;
-
-            if (occupied & s)
-                break;
-        }
-
-    return attack;
-  }
-
-
   // init_magics() computes all rook and bishop attacks at startup. Magic
   // bitboards are used to look up attacks of sliding pieces. As a reference see
   // chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we
index 7ae1eff..0691e54 100644 (file)
@@ -73,8 +73,10 @@ extern Bitboard DistanceRingBB[SQUARE_NB][8];
 extern Bitboard ForwardFileBB[COLOR_NB][SQUARE_NB];
 extern Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB];
 extern Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB];
-extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
-extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
+extern Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+extern Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+extern Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+extern Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
 
 
 /// Magic holds all magic bitboards relevant data for a single square
@@ -267,21 +269,17 @@ template<> inline int distance<Rank>(Square x, Square y) { return distance(rank_
 template<PieceType Pt>
 inline Bitboard attacks_bb(Square s, Bitboard occupied) {
 
+  assert(Pt == BISHOP || Pt == ROOK);
   const Magic& m = Pt == ROOK ? RookMagics[s] : BishopMagics[s];
   return m.attacks[m.index(occupied)];
 }
 
-inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
-
-  assert(pt != PAWN);
+inline Bitboard attacks_bb(Color c, PieceType pt, Square s, Bitboard occupied) {
+  return LeaperAttacks[c][pt][s] | (PseudoAttacks[c][pt][s] & (attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied)));
+}
 
-  switch (pt)
-  {
-  case BISHOP: return attacks_bb<BISHOP>(s, occupied);
-  case ROOK  : return attacks_bb<  ROOK>(s, occupied);
-  case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
-  default    : return PseudoAttacks[pt][s];
-  }
+inline Bitboard moves_bb(Color c, PieceType pt, Square s, Bitboard occupied) {
+  return LeaperMoves[c][pt][s] | (PseudoMoves[c][pt][s] & (attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied)));
 }
 
 
index 3e01259..3cd5e5b 100644 (file)
@@ -403,8 +403,8 @@ ScaleFactor Endgame<KQKRPs>::operator()(const Position& pos) const {
       &&  relative_rank(weakSide, pos.square<KING>(strongSide)) >= RANK_4
       &&  relative_rank(weakSide, rsq) == RANK_3
       && (  pos.pieces(weakSide, PAWN)
-          & pos.attacks_from<KING>(kingSq)
-          & pos.attacks_from<PAWN>(rsq, strongSide)))
+          & pos.attacks_from<KING>(weakSide, kingSq)
+          & pos.attacks_from<PAWN>(strongSide, rsq)))
           return SCALE_FACTOR_DRAW;
 
   return SCALE_FACTOR_NONE;
@@ -547,7 +547,7 @@ ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
       // the corner
       if (   rk == RANK_6
           && distance(psq + 2 * push, ksq) <= 1
-          && (PseudoAttacks[BISHOP][bsq] & (psq + push))
+          && (PseudoAttacks[strongSide][BISHOP][bsq] & (psq + push))
           && distance<File>(bsq, psq) >= 2)
           return ScaleFactor(8);
   }
@@ -687,14 +687,14 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
     if (   ksq == blockSq1
         && opposite_colors(ksq, wbsq)
         && (   bbsq == blockSq2
-            || (pos.attacks_from<BISHOP>(blockSq2) & pos.pieces(weakSide, BISHOP))
+            || (pos.attacks_from<BISHOP>(weakSide, blockSq2) & pos.pieces(weakSide, BISHOP))
             || distance(r1, r2) >= 2))
         return SCALE_FACTOR_DRAW;
 
     else if (   ksq == blockSq2
              && opposite_colors(ksq, wbsq)
              && (   bbsq == blockSq1
-                 || (pos.attacks_from<BISHOP>(blockSq1) & pos.pieces(weakSide, BISHOP))))
+                 || (pos.attacks_from<BISHOP>(weakSide, blockSq1) & pos.pieces(weakSide, BISHOP))))
         return SCALE_FACTOR_DRAW;
     else
         return SCALE_FACTOR_NONE;
@@ -759,7 +759,7 @@ ScaleFactor Endgame<KNPKB>::operator()(const Position& pos) const {
 
   // King needs to get close to promoting pawn to prevent knight from blocking.
   // Rules for this are very tricky, so just approximate.
-  if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from<BISHOP>(bishopSq))
+  if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from<BISHOP>(weakSide, bishopSq))
       return ScaleFactor(distance(weakKingSq, pawnSq));
 
   return SCALE_FACTOR_NONE;
index 01b5aa5..09e51f4 100644 (file)
@@ -263,7 +263,7 @@ namespace {
     mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pe->pawn_attacks(Them));
 
     // Initialise attackedBy bitboards for kings and pawns
-    attackedBy[Us][KING] = pos.attacks_from<KING>(pos.square<KING>(Us));
+    attackedBy[Us][KING] = pos.attacks_from<KING>(Us, pos.square<KING>(Us));
     attackedBy[Us][PAWN] = pe->pawn_attacks(Us);
     attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN];
     attackedBy2[Us]            = attackedBy[Us][KING] & attackedBy[Us][PAWN];
@@ -310,7 +310,8 @@ namespace {
         // Find attacked squares, including x-ray attacks for bishops and rooks
         b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
           : Pt ==   ROOK ? attacks_bb<  ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
-                         : pos.attacks_from<Pt>(s);
+                         : (  (pos.attacks_from(Us, Pt, s) & pos.pieces())
+                            | (pos.moves_from(Us, Pt, s) & ~pos.pieces()));
 
         if (pos.blockers_for_king(Us) & s)
             b &= LineBB[pos.square<KING>(Us)][s];
@@ -381,7 +382,7 @@ namespace {
         {
             // Bonus for aligning rook with enemy pawns on the same rank/file
             if (relative_rank(Us, s) >= RANK_5)
-                score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]);
+                score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[Us][ROOK][s]);
 
             // Bonus for rook on an open or semi-open file
             if (pe->semiopen_file(Us, file_of(s)))
@@ -463,7 +464,7 @@ namespace {
             unsafeChecks |= b2;
 
         // Enemy knights checks
-        b = pos.attacks_from<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
+        b = pos.attacks_from<KNIGHT>(Us, ksq) & attackedBy[Them][KNIGHT];
         if (b & safe)
             kingDanger += KnightSafeCheck;
         else
@@ -603,12 +604,12 @@ namespace {
         Square s = pos.square<QUEEN>(Them);
         safeThreats = mobilityArea[Us] & ~stronglyProtected;
 
-        b = attackedBy[Us][KNIGHT] & pos.attacks_from<KNIGHT>(s);
+        b = attackedBy[Us][KNIGHT] & pos.attacks_from<KNIGHT>(Us, s);
 
         score += KnightOnQueen * popcount(b & safeThreats);
 
-        b =  (attackedBy[Us][BISHOP] & pos.attacks_from<BISHOP>(s))
-           | (attackedBy[Us][ROOK  ] & pos.attacks_from<ROOK  >(s));
+        b =  (attackedBy[Us][BISHOP] & pos.attacks_from<BISHOP>(Us, s))
+           | (attackedBy[Us][ROOK  ] & pos.attacks_from<ROOK  >(Us, s));
 
         score += SliderOnQueen * popcount(b & safeThreats & attackedBy2[Us]);
     }
@@ -675,7 +676,7 @@ namespace {
                 // in the pawn's path attacked or occupied by the enemy.
                 defendedSquares = unsafeSquares = squaresToQueen = forward_file_bb(Us, s);
 
-                bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from<ROOK>(s);
+                bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from<ROOK>(Us, s);
 
                 if (!(pos.pieces(Us) & bb))
                     defendedSquares &= attackedBy[Us][ALL_PIECES];
index c5bf325..e4a5cbc 100644 (file)
@@ -26,6 +26,7 @@
 #include "thread.h"
 #include "tt.h"
 #include "uci.h"
+#include "variant.h"
 #include "syzygy/tbprobe.h"
 
 namespace PSQT {
@@ -36,6 +37,7 @@ int main(int argc, char* argv[]) {
 
   std::cout << engine_info() << std::endl;
 
+  variants.init();
   UCI::init(Options);
   PSQT::init();
   Bitboards::init();
@@ -50,5 +52,6 @@ int main(int argc, char* argv[]) {
   UCI::loop(argc, argv);
 
   Threads.set(0);
+  variants.clear_all();
   return 0;
 }
index 35ca6e7..c68eddf 100644 (file)
@@ -52,7 +52,7 @@ namespace {
     // Because we generate only legal castling moves we need to verify that
     // when moving the castling rook we do not discover some hidden checker.
     // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
-    if (Chess960 && (attacks_bb<ROOK>(kto, pos.pieces() ^ rfrom) & pos.pieces(~us, ROOK, QUEEN)))
+    if (Chess960 && (pos.attackers_to(kto, pos.pieces() ^ rfrom) & pos.pieces(~us)))
         return moveList;
 
     Move m = make<CASTLING>(kfrom, rfrom);
@@ -65,25 +65,16 @@ namespace {
   }
 
 
-  template<GenType Type, Direction D>
-  ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) {
+  template<Color c, GenType Type, Direction D>
+  ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) {
 
-    if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
-        *moveList++ = make<PROMOTION>(to - D, to, QUEEN);
+    const MoveType T =  (D == NORTH_WEST || D == SOUTH_WEST) ? PROMOTION_LEFT
+                      : (D == NORTH_EAST || D == SOUTH_EAST) ? PROMOTION_RIGHT
+                                                             : PROMOTION_STRAIGHT;
 
-    if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
-    {
-        *moveList++ = make<PROMOTION>(to - D, to, ROOK);
-        *moveList++ = make<PROMOTION>(to - D, to, BISHOP);
-        *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
-    }
-
-    // Knight promotion is the only promotion that can give a direct check
-    // that's not already included in the queen promotion.
-    if (Type == QUIET_CHECKS && (PseudoAttacks[KNIGHT][to] & ksq))
-        *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
-    else
-        (void)ksq; // Silence a warning under MSVC
+    if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+        for (PieceType pt : pos.promotion_piece_types())
+            *moveList++ = make<T>(to - D, to, pt);
 
     return moveList;
   }
@@ -95,15 +86,16 @@ namespace {
     // Compute our parametrized parameters at compile time, named according to
     // the point of view of white side.
     constexpr Color     Them     = (Us == WHITE ? BLACK      : WHITE);
-    constexpr Bitboard  TRank8BB = (Us == WHITE ? Rank8BB    : Rank1BB);
-    constexpr Bitboard  TRank7BB = (Us == WHITE ? Rank7BB    : Rank2BB);
     constexpr Bitboard  TRank3BB = (Us == WHITE ? Rank3BB    : Rank6BB);
     constexpr Direction Up       = (Us == WHITE ? NORTH      : SOUTH);
+    constexpr Direction Down     = (Us == WHITE ? SOUTH      : NORTH);
     constexpr Direction UpRight  = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
     constexpr Direction UpLeft   = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
 
     Bitboard emptySquares;
 
+    Bitboard TRank8BB = rank_bb(Us == WHITE ? pos.promotion_rank() : ~pos.promotion_rank());
+    Bitboard TRank7BB = shift<Down>(TRank8BB);
     Bitboard pawnsOn7    = pos.pieces(Us, PAWN) &  TRank7BB;
     Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
 
@@ -116,7 +108,7 @@ namespace {
         emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces());
 
         Bitboard b1 = shift<Up>(pawnsNotOn7)   & emptySquares;
-        Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
+        Bitboard b2 = pos.double_step_enabled() ? shift<Up>(b1 & TRank3BB) & emptySquares : 0;
 
         if (Type == EVASIONS) // Consider only blocking squares
         {
@@ -128,8 +120,8 @@ namespace {
         {
             Square ksq = pos.square<KING>(Them);
 
-            b1 &= pos.attacks_from<PAWN>(ksq, Them);
-            b2 &= pos.attacks_from<PAWN>(ksq, Them);
+            b1 &= pos.attacks_from<PAWN>(Them, ksq);
+            b2 &= pos.attacks_from<PAWN>(Them, ksq);
 
             // Add pawn pushes which give discovered check. This is possible only
             // if the pawn is not on the same file as the enemy king, because we
@@ -172,16 +164,14 @@ namespace {
         Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
         Bitboard b3 = shift<Up     >(pawnsOn7) & emptySquares;
 
-        Square ksq = pos.square<KING>(Them);
-
         while (b1)
-            moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(&b1), ksq);
+            moveList = make_promotions<Us, Type, UpRight>(pos, moveList, pop_lsb(&b1));
 
         while (b2)
-            moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(&b2), ksq);
+            moveList = make_promotions<Us, Type, UpLeft >(pos, moveList, pop_lsb(&b2));
 
         while (b3)
-            moveList = make_promotions<Type, Up     >(moveList, pop_lsb(&b3), ksq);
+            moveList = make_promotions<Us, Type, Up     >(pos, moveList, pop_lsb(&b3));
     }
 
     // Standard and en-passant captures
@@ -212,7 +202,7 @@ namespace {
             if (Type == EVASIONS && !(target & (pos.ep_square() - Up)))
                 return moveList;
 
-            b1 = pawnsNotOn7 & pos.attacks_from<PAWN>(pos.ep_square(), Them);
+            b1 = pawnsNotOn7 & pos.attacks_from<PAWN>(Them, pos.ep_square());
 
             assert(b1);
 
@@ -225,30 +215,25 @@ namespace {
   }
 
 
-  template<PieceType Pt, bool Checks>
-  ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Color us,
+  template<bool Checks>
+  ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Color us, PieceType pt,
                           Bitboard target) {
 
-    assert(Pt != KING && Pt != PAWN);
+    assert(pt != KING && pt != PAWN);
 
-    const Square* pl = pos.squares<Pt>(us);
+    const Square* pl = pos.squares(us, pt);
 
     for (Square from = *pl; from != SQ_NONE; from = *++pl)
     {
-        if (Checks)
-        {
-            if (    (Pt == BISHOP || Pt == ROOK || Pt == QUEEN)
-                && !(PseudoAttacks[Pt][from] & target & pos.check_squares(Pt)))
-                continue;
-
-            if (pos.blockers_for_king(~us) & from)
-                continue;
-        }
+        // Avoid generating discovered checks twice
+        if (Checks && (pos.blockers_for_king(~us) & from))
+            continue;
 
-        Bitboard b = pos.attacks_from<Pt>(from) & target;
+        Bitboard b = (  (pos.attacks_from(us, pt, from) & pos.pieces())
+                      | (pos.moves_from(us, pt, from) & ~pos.pieces())) & target;
 
         if (Checks)
-            b &= pos.check_squares(Pt);
+            b &= pos.check_squares(pt);
 
         while (b)
             *moveList++ = make_move(from, pop_lsb(&b));
@@ -264,20 +249,18 @@ namespace {
     constexpr bool Checks = Type == QUIET_CHECKS;
 
     moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
-    moveList = generate_moves<KNIGHT, Checks>(pos, moveList, Us, target);
-    moveList = generate_moves<BISHOP, Checks>(pos, moveList, Us, target);
-    moveList = generate_moves<  ROOK, Checks>(pos, moveList, Us, target);
-    moveList = generate_moves< QUEEN, Checks>(pos, moveList, Us, target);
+    for (PieceType pt = PieceType(PAWN + 1); pt < KING; ++pt)
+        moveList = generate_moves<Checks>(pos, moveList, Us, pt, target);
 
     if (Type != QUIET_CHECKS && Type != EVASIONS)
     {
         Square ksq = pos.square<KING>(Us);
-        Bitboard b = pos.attacks_from<KING>(ksq) & target;
+        Bitboard b = pos.attacks_from<KING>(Us, ksq) & target;
         while (b)
             *moveList++ = make_move(ksq, pop_lsb(&b));
     }
 
-    if (Type != CAPTURES && Type != EVASIONS && pos.can_castle(Us))
+    if (pos.castling_enabled() && Type != CAPTURES && Type != EVASIONS && pos.can_castle(Us))
     {
         if (pos.is_chess960())
         {
@@ -346,10 +329,10 @@ ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
      if (pt == PAWN)
          continue; // Will be generated together with direct checks
 
-     Bitboard b = pos.attacks_from(pt, from) & ~pos.pieces();
+     Bitboard b = pos.moves_from(us, pt, from) & ~pos.pieces();
 
      if (pt == KING)
-         b &= ~PseudoAttacks[QUEEN][pos.square<KING>(~us)];
+         b &= ~PseudoAttacks[~us][QUEEN][pos.square<KING>(~us)];
 
      while (b)
          *moveList++ = make_move(from, pop_lsb(&b));
@@ -370,7 +353,7 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
   Color us = pos.side_to_move();
   Square ksq = pos.square<KING>(us);
   Bitboard sliderAttacks = 0;
-  Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN);
+  Bitboard sliders = pos.checkers();
 
   // Find all the squares attacked by slider checkers. We will remove them from
   // the king evasions in order to skip known illegal moves, which avoids any
@@ -378,11 +361,11 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
   while (sliders)
   {
       Square checksq = pop_lsb(&sliders);
-      sliderAttacks |= LineBB[checksq][ksq] ^ checksq;
+      sliderAttacks |=  attacks_bb(~us, type_of(pos.piece_on(checksq)), checksq, pos.pieces() ^ ksq);
   }
 
   // Generate evasions for king, capture and non capture moves
-  Bitboard b = pos.attacks_from<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
+  Bitboard b = pos.attacks_from<KING>(us, ksq) & ~pos.pieces(us) & ~sliderAttacks;
   while (b)
       *moveList++ = make_move(ksq, pop_lsb(&b));
 
@@ -392,6 +375,9 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
   // Generate blocking evasions or captures of the checking piece
   Square checksq = lsb(pos.checkers());
   Bitboard target = between_bb(checksq, ksq) | checksq;
+  // Leaper attacks can not be blocked
+  if (LeaperAttacks[~us][type_of(pos.piece_on(checksq))][checksq] & ksq)
+      target = SquareBB[checksq];
 
   return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList, target)
                      : generate_all<BLACK, EVASIONS>(pos, moveList, target);
index 6901d7b..8c5c1dc 100644 (file)
@@ -101,8 +101,8 @@ namespace {
         // Flag the pawn
         opposed    = theirPawns & forward_file_bb(Us, s);
         stoppers   = theirPawns & passed_pawn_mask(Us, s);
-        lever      = theirPawns & PawnAttacks[Us][s];
-        leverPush  = theirPawns & PawnAttacks[Us][s + Up];
+        lever      = theirPawns & PseudoAttacks[Us][PAWN][s];
+        leverPush  = theirPawns & PseudoAttacks[Us][PAWN][s + Up];
         doubled    = ourPawns   & (s - Up);
         neighbours = ourPawns   & adjacent_files_bb(f);
         phalanx    = neighbours & rank_bb(s);
@@ -128,7 +128,7 @@ namespace {
         {
             b = shift<Up>(supported) & ~theirPawns;
             while (b)
-                if (!more_than_one(theirPawns & PawnAttacks[Us][pop_lsb(&b)]))
+                if (!more_than_one(theirPawns & PseudoAttacks[Us][PAWN][pop_lsb(&b)]))
                     e->passedPawns[Us] |= s;
         }
 
index 2573fe8..9ebe3a9 100644 (file)
@@ -50,11 +50,6 @@ namespace Zobrist {
 
 namespace {
 
-const string PieceToChar(" PNBRQK  pnbrqk");
-
-constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
-                             B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING };
-
 // min_attacker() is a helper function used by see_ge() to locate the least
 // valuable attacker for the side to move, remove the attacker we just found
 // from the bitboards and scan for new X-ray attacks behind it.
@@ -101,7 +96,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
   for (Rank r = RANK_8; r >= RANK_1; --r)
   {
       for (File f = FILE_A; f <= FILE_H; ++f)
-          os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
+          os << " | " << pos.piece_to_char()[pos.piece_on(make_square(f, r))];
 
       os << " |\n +---+---+---+---+---+---+---+---+\n";
   }
@@ -118,7 +113,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
   {
       StateInfo st;
       Position p;
-      p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread());
+      p.set(pos.variant(), pos.fen(), pos.is_chess960(), &st, pos.this_thread());
       Tablebases::ProbeState s1, s2;
       Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1);
       int dtz = Tablebases::probe_dtz(p, &s2);
@@ -150,9 +145,10 @@ void Position::init() {
 
   PRNG rng(1070372);
 
-  for (Piece pc : Pieces)
-      for (Square s = SQ_A1; s <= SQ_H8; ++s)
-          Zobrist::psq[pc][s] = rng.rand<Key>();
+  for (Color c = WHITE; c <= BLACK; ++c)
+      for (PieceType pt = PAWN; pt <= KING; ++pt)
+          for (Square s = SQ_A1; s <= SQ_H8; ++s)
+              Zobrist::psq[make_piece(c, pt)][s] = rng.rand<Key>();
 
   for (File f = FILE_A; f <= FILE_H; ++f)
       Zobrist::enpassant[f] = rng.rand<Key>();
@@ -173,10 +169,13 @@ void Position::init() {
 
   // Prepare the cuckoo tables
   int count = 0;
-  for (Piece pc : Pieces)
+  for (Color c = WHITE; c <= BLACK; ++c)
+      for (PieceType pt = KNIGHT; pt <= QUEEN || pt == KING; pt != QUEEN ? ++pt : pt = KING)
+      {
+      Piece pc = make_piece(c, pt);
       for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
           for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
-              if (PseudoAttacks[type_of(pc)][s1] & s2)
+              if (PseudoAttacks[WHITE][type_of(pc)][s1] & s2)
               {
                   Move move = make_move(s1, s2);
                   Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side;
@@ -191,6 +190,7 @@ void Position::init() {
                   }
                   count++;
              }
+      }
   assert(count == 3668);
 }
 
@@ -199,7 +199,7 @@ void Position::init() {
 /// This function is not very robust - make sure that input FENs are correct,
 /// this is assumed to be the responsibility of the GUI.
 
-Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
+Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
 /*
    A FEN string defines a particular position using only the ASCII character set.
 
@@ -243,6 +243,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
   std::memset(this, 0, sizeof(Position));
   std::memset(si, 0, sizeof(StateInfo));
   std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
+  var = v;
   st = si;
 
   ss >> std::noskipws;
@@ -256,7 +257,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
       else if (token == '/')
           sq += 2 * SOUTH;
 
-      else if ((idx = PieceToChar.find(token)) != string::npos)
+      else if ((idx = piece_to_char().find(token)) != string::npos)
       {
           put_piece(Piece(idx), sq);
           ++sq;
@@ -268,6 +269,10 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
   sideToMove = (token == 'w' ? WHITE : BLACK);
   ss >> token;
 
+  // 3-4. Skip parsing castling and en passant flags if not present
+  st->epSquare = SQ_NONE;
+  if (!isdigit(ss.peek()))
+  {
   // 3. Castling availability. Compatible with 3 standards: Normal FEN standard,
   // Shredder-FEN that uses the letters of the columns on which the rooks began
   // the game instead of KQkq and also X-FEN standard that, in case of Chess960,
@@ -306,8 +311,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
           || !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))))
           st->epSquare = SQ_NONE;
   }
-  else
-      st->epSquare = SQ_NONE;
+  }
 
   // 5-6. Halfmove clock and fullmove number
   ss >> std::skipws >> st->rule50 >> gamePly;
@@ -362,11 +366,8 @@ void Position::set_check_info(StateInfo* si) const {
 
   Square ksq = square<KING>(~sideToMove);
 
-  si->checkSquares[PAWN]   = attacks_from<PAWN>(ksq, ~sideToMove);
-  si->checkSquares[KNIGHT] = attacks_from<KNIGHT>(ksq);
-  si->checkSquares[BISHOP] = attacks_from<BISHOP>(ksq);
-  si->checkSquares[ROOK]   = attacks_from<ROOK>(ksq);
-  si->checkSquares[QUEEN]  = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
+  for (PieceType pt = PAWN; pt < KING; ++pt)
+      si->checkSquares[pt] = attacks_from(~sideToMove, pt, ksq);
   si->checkSquares[KING]   = 0;
 }
 
@@ -408,14 +409,16 @@ void Position::set_state(StateInfo* si) const {
       si->pawnKey ^= Zobrist::psq[piece_on(s)][s];
   }
 
-  for (Piece pc : Pieces)
-  {
-      if (type_of(pc) != PAWN && type_of(pc) != KING)
-          si->nonPawnMaterial[color_of(pc)] += pieceCount[pc] * PieceValue[MG][pc];
+  for (Color c = WHITE; c <= BLACK; ++c)
+      for (PieceType pt = PAWN; pt <= KING; ++pt)
+      {
+          Piece pc = make_piece(c, pt);
+          if (pt != PAWN && pt != KING)
+              si->nonPawnMaterial[c] += pieceCount[pc] * PieceValue[MG][pc];
 
-      for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
-          si->materialKey ^= Zobrist::psq[pc][cnt];
-  }
+          for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
+              si->materialKey ^= Zobrist::psq[pc][cnt];
+      }
 }
 
 
@@ -436,7 +439,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) {
   string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/"
                        + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
 
-  return set(fenStr, false, si, nullptr);
+  return set(variants.find("chess")->second, fenStr, false, si, nullptr);
 }
 
 
@@ -459,7 +462,7 @@ const string Position::fen() const {
               ss << emptyCnt;
 
           if (f <= FILE_H)
-              ss << PieceToChar[piece_on(make_square(f, r))];
+              ss << piece_to_char()[piece_on(make_square(f, r))];
       }
 
       if (r > RANK_1)
@@ -503,8 +506,9 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners
   pinners = 0;
 
   // Snipers are sliders that attack 's' when a piece is removed
-  Bitboard snipers = (  (PseudoAttacks[  ROOK][s] & pieces(QUEEN, ROOK))
-                      | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders;
+  Bitboard snipers =   sliders
+                    &  attackers_to(s, 0)
+                    & ~attackers_to(s);
 
   while (snipers)
   {
@@ -527,12 +531,11 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners
 
 Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
 
-  return  (attacks_from<PAWN>(s, BLACK)    & pieces(WHITE, PAWN))
-        | (attacks_from<PAWN>(s, WHITE)    & pieces(BLACK, PAWN))
-        | (attacks_from<KNIGHT>(s)         & pieces(KNIGHT))
-        | (attacks_bb<  ROOK>(s, occupied) & pieces(  ROOK, QUEEN))
-        | (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
-        | (attacks_from<KING>(s)           & pieces(KING));
+  Bitboard b = 0;
+  for (Color c = WHITE; c <= BLACK; ++c)
+      for (PieceType pt = PAWN; pt <= KING; ++pt)
+          b |= attacks_bb(~c, pt, s, occupied) & pieces(c, pt);
+  return b;
 }
 
 
@@ -544,6 +547,8 @@ bool Position::legal(Move m) const {
 
   Color us = sideToMove;
   Square from = from_sq(m);
+  Square to = to_sq(m);
+  Square ksq = square<KING>(us);
 
   assert(color_of(moved_piece(m)) == us);
   assert(piece_on(square<KING>(us)) == make_piece(us, KING));
@@ -553,8 +558,6 @@ bool Position::legal(Move m) const {
   // the move is made.
   if (type_of(m) == ENPASSANT)
   {
-      Square ksq = square<KING>(us);
-      Square to = to_sq(m);
       Square capsq = to - pawn_push(us);
       Bitboard occupied = (pieces() ^ from ^ capsq) | to;
 
@@ -563,20 +566,17 @@ bool Position::legal(Move m) const {
       assert(piece_on(capsq) == make_piece(~us, PAWN));
       assert(piece_on(to) == NO_PIECE);
 
-      return   !(attacks_bb<  ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
-            && !(attacks_bb<BISHOP>(ksq, occupied) & pieces(~us, QUEEN, BISHOP));
+      return !(attackers_to(ksq, occupied) & pieces(~us) & occupied);
   }
 
   // If the moving piece is a king, check whether the destination
   // square is attacked by the opponent. Castling moves are checked
   // for legality during move generation.
   if (type_of(piece_on(from)) == KING)
-      return type_of(m) == CASTLING || !(attackers_to(to_sq(m)) & pieces(~us));
+      return type_of(m) == CASTLING || !(attackers_to(to) & pieces(~us));
 
-  // A non-king move is legal if and only if it is not pinned or it
-  // is moving along the ray towards or away from the king.
-  return   !(blockers_for_king(us) & from)
-        ||  aligned(from, to_sq(m), square<KING>(us));
+  // A non-king move is legal if the king is not under attack after the move.
+  return !(attackers_to(ksq, (pieces() ^ from) | to) & pieces(~us) & ~SquareBB[to]);
 }
 
 
@@ -596,7 +596,7 @@ bool Position::pseudo_legal(const Move m) const {
       return MoveList<LEGAL>(*this).contains(m);
 
   // Is not a promotion, so promotion piece must be empty
-  if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE)
+  if (promotion_type(m) != NO_PIECE_TYPE)
       return false;
 
   // If the 'from' square is not occupied by a piece belonging to the side to
@@ -613,10 +613,10 @@ bool Position::pseudo_legal(const Move m) const {
   {
       // We have already handled promotion moves, so destination
       // cannot be on the 8th/1st rank.
-      if (rank_of(to) == relative_rank(us, RANK_8))
+      if (rank_of(to) == relative_rank(us, promotion_rank()))
           return false;
 
-      if (   !(attacks_from<PAWN>(from, us) & pieces(~us) & to) // Not a capture
+      if (   !(attacks_from<PAWN>(us, from) & pieces(~us) & to) // Not a capture
           && !((from + pawn_push(us) == to) && empty(to))       // Not a single push
           && !(   (from + 2 * pawn_push(us) == to)              // Not a double push
                && (rank_of(from) == relative_rank(us, RANK_2))
@@ -624,7 +624,7 @@ bool Position::pseudo_legal(const Move m) const {
                && empty(to - pawn_push(us))))
           return false;
   }
-  else if (!(attacks_from(type_of(pc), from) & to))
+  else if (!(attacks_from(us, type_of(pc), from) & to))
       return false;
 
   // Evasions generator already takes care to avoid some kind of illegal moves
@@ -639,7 +639,9 @@ bool Position::pseudo_legal(const Move m) const {
               return false;
 
           // Our move must be a blocking evasion or a capture of the checking piece
-          if (!((between_bb(lsb(checkers()), square<KING>(us)) | checkers()) & to))
+          Square checksq = lsb(checkers());
+          if (  !((between_bb(checksq, square<KING>(us)) | checkers()) & to)
+              || (LeaperAttacks[~us][type_of(piece_on(checksq))][checksq] & square<KING>(us)))
               return false;
       }
       // In case of king moves under check we have to remove king so as to catch
@@ -668,7 +670,8 @@ bool Position::gives_check(Move m) const {
 
   // Is there a discovered check?
   if (   (st->blockersForKing[~sideToMove] & from)
-      && !aligned(from, to, square<KING>(~sideToMove)))
+      && (  !aligned(from, to, square<KING>(~sideToMove))
+          || (attackers_to(square<KING>(~sideToMove), (pieces() ^ from) | to) & pieces(sideToMove))))
       return true;
 
   switch (type_of(m))
@@ -677,7 +680,7 @@ bool Position::gives_check(Move m) const {
       return false;
 
   case PROMOTION:
-      return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove);
+      return attacks_bb(sideToMove, promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove);
 
   // En passant capture with check? We have already handled the case
   // of direct checks and ordinary discovered check, so the only case we
@@ -688,8 +691,7 @@ bool Position::gives_check(Move m) const {
       Square capsq = make_square(file_of(to), rank_of(from));
       Bitboard b = (pieces() ^ from ^ capsq) | to;
 
-      return  (attacks_bb<  ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
-            | (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
+      return attackers_to(square<KING>(~sideToMove), b) & pieces(sideToMove) & b;
   }
   case CASTLING:
   {
@@ -698,7 +700,7 @@ bool Position::gives_check(Move m) const {
       Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1);
       Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1);
 
-      return   (PseudoAttacks[ROOK][rto] & square<KING>(~sideToMove))
+      return   (PseudoAttacks[sideToMove][ROOK][rto] & square<KING>(~sideToMove))
             && (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
   }
   default:
@@ -825,7 +827,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   {
       // Set en-passant square if the moved pawn can be captured
       if (   (int(to) ^ int(from)) == 16
-          && (attacks_from<PAWN>(to - pawn_push(us), us) & pieces(them, PAWN)))
+          && (attacks_from<PAWN>(us, to - pawn_push(us)) & pieces(them, PAWN)))
       {
           st->epSquare = to - pawn_push(us);
           k ^= Zobrist::enpassant[file_of(st->epSquare)];
@@ -835,8 +837,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       {
           Piece promotion = make_piece(us, promotion_type(m));
 
-          assert(relative_rank(us, to) == RANK_8);
-          assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
+          assert(relative_rank(us, to) == promotion_rank());
+          assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING);
 
           remove_piece(pc, to);
           put_piece(promotion, to);
@@ -902,9 +904,9 @@ void Position::undo_move(Move m) {
 
   if (type_of(m) == PROMOTION)
   {
-      assert(relative_rank(us, to) == RANK_8);
+      assert(relative_rank(us, to) == promotion_rank());
       assert(type_of(pc) == promotion_type(m));
-      assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
+      assert(type_of(pc) >= KNIGHT && type_of(pc) < KING);
 
       remove_piece(pc, to);
       pc = make_piece(us, PAWN);
@@ -1250,7 +1252,7 @@ void Position::flip() {
   std::getline(ss, token); // Half and full moves
   f += token;
 
-  set(f, is_chess960(), st, this_thread());
+  set(variant(), f, is_chess960(), st, this_thread());
 
   assert(pos_is_ok());
 }
@@ -1265,8 +1267,8 @@ bool Position::pos_is_ok() const {
   constexpr bool Fast = true; // Quick (default) or full check?
 
   if (   (sideToMove != WHITE && sideToMove != BLACK)
-      || piece_on(square<KING>(WHITE)) != W_KING
-      || piece_on(square<KING>(BLACK)) != B_KING
+      || piece_on(square<KING>(WHITE)) != make_piece(WHITE, KING)
+      || piece_on(square<KING>(BLACK)) != make_piece(BLACK, KING)
       || (   ep_square() != SQ_NONE
           && relative_rank(sideToMove, ep_square()) != RANK_6))
       assert(0 && "pos_is_ok: Default");
@@ -1274,14 +1276,14 @@ bool Position::pos_is_ok() const {
   if (Fast)
       return true;
 
-  if (   pieceCount[W_KING] != 1
-      || pieceCount[B_KING] != 1
+  if (   pieceCount[make_piece(WHITE, KING)] != 1
+      || pieceCount[make_piece(BLACK, KING)] != 1
       || attackers_to(square<KING>(~sideToMove)) & pieces(sideToMove))
       assert(0 && "pos_is_ok: Kings");
 
   if (   (pieces(PAWN) & (Rank1BB | Rank8BB))
-      || pieceCount[W_PAWN] > 8
-      || pieceCount[B_PAWN] > 8)
+      || pieceCount[make_piece(WHITE, PAWN)] > 8
+      || pieceCount[make_piece(BLACK, PAWN)] > 8)
       assert(0 && "pos_is_ok: Pawns");
 
   if (   (pieces(WHITE) & pieces(BLACK))
@@ -1300,16 +1302,18 @@ bool Position::pos_is_ok() const {
   if (std::memcmp(&si, st, sizeof(StateInfo)))
       assert(0 && "pos_is_ok: State");
 
-  for (Piece pc : Pieces)
-  {
-      if (   pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
-          || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
-          assert(0 && "pos_is_ok: Pieces");
-
-      for (int i = 0; i < pieceCount[pc]; ++i)
-          if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i)
-              assert(0 && "pos_is_ok: Index");
-  }
+  for (Color c = WHITE; c <= BLACK; ++c)
+      for (PieceType pt = PAWN; pt <= KING; ++pt)
+      {
+          Piece pc = make_piece(c, pt);
+          if (   pieceCount[pc] != popcount(pieces(c, pt))
+              || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
+              assert(0 && "pos_is_ok: Pieces");
+
+          for (int i = 0; i < pieceCount[pc]; ++i)
+              if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i)
+                  assert(0 && "pos_is_ok: Index");
+      }
 
   for (Color c = WHITE; c <= BLACK; ++c)
       for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1))
index a40687e..64b4bb5 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "bitboard.h"
 #include "types.h"
+#include "variant.h"
 
 
 /// StateInfo struct stores information needed to restore a Position object to
@@ -78,10 +79,18 @@ public:
   Position& operator=(const Position&) = delete;
 
   // FEN string input/output
-  Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
+  Position& set(const Variant* v, const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
   Position& set(const std::string& code, Color c, StateInfo* si);
   const std::string fen() const;
 
+  // Variant rule properties
+  const Variant* variant() const;
+  const std::string piece_to_char() const;
+  Rank promotion_rank() const;
+  std::vector<PieceType> promotion_piece_types() const;
+  bool double_step_enabled() const;
+  bool castling_enabled() const;
+
   // Position representation
   Bitboard pieces() const;
   Bitboard pieces(PieceType pt) const;
@@ -95,6 +104,7 @@ public:
   template<PieceType Pt> int count(Color c) const;
   template<PieceType Pt> int count() const;
   template<PieceType Pt> const Square* squares(Color c) const;
+  const Square* squares(Color c, PieceType pt) const;
   template<PieceType Pt> Square square(Color c) const;
 
   // Castling
@@ -111,9 +121,9 @@ public:
   // Attacks to/from a given square
   Bitboard attackers_to(Square s) const;
   Bitboard attackers_to(Square s, Bitboard occupied) const;
-  Bitboard attacks_from(PieceType pt, Square s) const;
-  template<PieceType> Bitboard attacks_from(Square s) const;
-  template<PieceType> Bitboard attacks_from(Square s, Color c) const;
+  Bitboard attacks_from(Color c, PieceType pt, Square s) const;
+  template<PieceType> Bitboard attacks_from(Color c, Square s) const;
+  Bitboard moves_from(Color c, PieceType pt, Square s) const;
   Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
 
   // Properties of moves
@@ -190,11 +200,42 @@ private:
   Color sideToMove;
   Thread* thisThread;
   StateInfo* st;
+  const Variant* var;
   bool chess960;
 };
 
 extern std::ostream& operator<<(std::ostream& os, const Position& pos);
 
+inline const Variant* Position::variant() const {
+  assert(var != nullptr);
+  return var;
+}
+
+inline const std::string Position::piece_to_char() const {
+  assert(var != nullptr);
+  return var->pieceToChar;
+}
+
+inline Rank Position::promotion_rank() const {
+  assert(var != nullptr);
+  return var->promotionRank;
+}
+
+inline std::vector<PieceType> Position::promotion_piece_types() const {
+  assert(var != nullptr);
+  return var->promotionPieceTypes;
+}
+
+inline bool Position::double_step_enabled() const {
+  assert(var != nullptr);
+  return var->doubleStep;
+}
+
+inline bool Position::castling_enabled() const {
+  assert(var != nullptr);
+  return var->castling;
+}
+
 inline Color Position::side_to_move() const {
   return sideToMove;
 }
@@ -247,6 +288,10 @@ template<PieceType Pt> inline const Square* Position::squares(Color c) const {
   return pieceList[make_piece(c, Pt)];
 }
 
+inline const Square* Position::squares(Color c, PieceType pt) const {
+  return pieceList[make_piece(c, pt)];
+}
+
 template<PieceType Pt> inline Square Position::square(Color c) const {
   assert(pieceCount[make_piece(c, Pt)] == 1);
   return pieceList[make_piece(c, Pt)][0];
@@ -273,20 +318,16 @@ inline Square Position::castling_rook_square(CastlingRight cr) const {
 }
 
 template<PieceType Pt>
-inline Bitboard Position::attacks_from(Square s) const {
-  assert(Pt != PAWN);
-  return  Pt == BISHOP || Pt == ROOK ? attacks_bb<Pt>(s, byTypeBB[ALL_PIECES])
-        : Pt == QUEEN  ? attacks_from<ROOK>(s) | attacks_from<BISHOP>(s)
-        : PseudoAttacks[Pt][s];
+inline Bitboard Position::attacks_from(Color c, Square s) const {
+  return attacks_bb(c, Pt, s, byTypeBB[ALL_PIECES]);
 }
 
-template<>
-inline Bitboard Position::attacks_from<PAWN>(Square s, Color c) const {
-  return PawnAttacks[c][s];
+inline Bitboard Position::attacks_from(Color c, PieceType pt, Square s) const {
+  return attacks_bb(c, pt, s, byTypeBB[ALL_PIECES]);
 }
 
-inline Bitboard Position::attacks_from(PieceType pt, Square s) const {
-  return attacks_bb(pt, s, byTypeBB[ALL_PIECES]);
+inline Bitboard Position::moves_from(Color c, PieceType pt, Square s) const {
+  return moves_bb(c, pt, s, byTypeBB[ALL_PIECES]);
 }
 
 inline Bitboard Position::attackers_to(Square s) const {
@@ -347,8 +388,8 @@ inline int Position::rule50_count() const {
 }
 
 inline bool Position::opposite_bishops() const {
-  return   pieceCount[W_BISHOP] == 1
-        && pieceCount[B_BISHOP] == 1
+  return   pieceCount[make_piece(WHITE, BISHOP)] == 1
+        && pieceCount[make_piece(BLACK, BISHOP)] == 1
         && opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
 }
 
index e0f15c0..86593c1 100644 (file)
 #include "types.h"
 
 Value PieceValue[PHASE_NB][PIECE_NB] = {
-  { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg },
-  { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg }
+  { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg,
+    FersValueMg, AlfilValueMg, SilverValueMg, AiwokValueMg, AmazonValueMg, KnibisValueMg, BiskniValueMg },
+  { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg,
+    FersValueEg, AlfilValueEg, SilverValueEg, AiwokValueEg, AmazonValueEg, KnibisValueMg, BiskniValueMg }
 };
 
 namespace PSQT {
@@ -35,7 +37,7 @@ namespace PSQT {
 // type on a given square a (middlegame, endgame) score pair is assigned. Table
 // is defined for files A..D and white side: it is symmetric for black side and
 // second half of the files.
-constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
+constexpr Score Bonus[PIECE_TYPE_NB][RANK_NB][int(FILE_NB) / 2] = {
   { },
   { // Pawn
    { S(  0, 0), S(  0, 0), S(  0, 0), S( 0, 0) },
@@ -107,8 +109,10 @@ Score psq[PIECE_NB][SQUARE_NB];
 // tables are initialized by flipping and changing the sign of the white scores.
 void init() {
 
-  for (Piece pc = W_PAWN; pc <= W_KING; ++pc)
+  for (PieceType pt = PAWN; pt <= KING; ++pt)
   {
+      Piece pc = make_piece(WHITE, pt);
+
       PieceValue[MG][~pc] = PieceValue[MG][pc];
       PieceValue[EG][~pc] = PieceValue[EG][pc];
 
index fb5dfd0..8fb4ae4 100644 (file)
@@ -137,7 +137,7 @@ namespace {
             pos.undo_move(m);
         }
         if (Root)
-            sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
+            sync_cout << UCI::move(pos, m) << ": " << cnt << sync_endl;
     }
     return nodes;
   }
@@ -264,10 +264,10 @@ void MainThread::search() {
   if (bestThread != this)
       sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl;
 
-  sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
+  sync_cout << "bestmove " << UCI::move(rootPos, bestThread->rootMoves[0].pv[0]);
 
   if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
-      std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
+      std::cout << " ponder " << UCI::move(rootPos, bestThread->rootMoves[0].pv[1]);
 
   std::cout << sync_endl;
 }
@@ -870,7 +870,7 @@ moves_loop: // When in check, search starts from here
 
       if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
           sync_cout << "info depth " << depth / ONE_PLY
-                    << " currmove " << UCI::move(move, pos.is_chess960())
+                    << " currmove " << UCI::move(pos, move)
                     << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl;
       if (PvNode)
           (ss+1)->pv = nullptr;
@@ -1607,7 +1607,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
          << " pv";
 
       for (Move m : rootMoves[i].pv)
-          ss << " " << UCI::move(m, pos.is_chess960());
+          ss << " " << UCI::move(pos, m);
   }
 
   return ss.str();
index da6dc49..3951140 100644 (file)
@@ -1242,7 +1242,7 @@ void Tablebases::init(const std::string& paths) {
             if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0
             {
                 for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
-                    if ((PseudoAttacks[KING][s1] | s1) & s2)
+                    if ((PseudoAttacks[WHITE][KING][s1] | s1) & s2)
                         continue; // Illegal position
 
                     else if (!off_A1H8(s1) && off_A1H8(s2) > 0)
index f980927..d0a6336 100644 (file)
@@ -194,7 +194,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
       th->nodes = th->tbHits = th->nmpMinPly = 0;
       th->rootDepth = th->completedDepth = DEPTH_ZERO;
       th->rootMoves = rootMoves;
-      th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
+      th->rootPos.set(pos.variant(), pos.fen(), pos.is_chess960(), &setupStates->back(), th);
   }
 
   setupStates->back() = tmp;
index ab5e1b8..6b0f033 100644 (file)
@@ -122,9 +122,12 @@ enum Move : int {
 
 enum MoveType {
   NORMAL,
-  PROMOTION = 1 << 14,
-  ENPASSANT = 2 << 14,
-  CASTLING  = 3 << 14
+  ENPASSANT          = 1 << 12,
+  CASTLING           = 2 << 12,
+  PROMOTION          = 3 << 12,
+  PROMOTION_STRAIGHT = PROMOTION,
+  PROMOTION_LEFT     = 4 << 12,
+  PROMOTION_RIGHT    = 5 << 12,
 };
 
 enum Color {
@@ -187,21 +190,33 @@ enum Value : int {
   BishopValueMg = 826,   BishopValueEg = 891,
   RookValueMg   = 1282,  RookValueEg   = 1373,
   QueenValueMg  = 2500,  QueenValueEg  = 2670,
+  FersValueMg   = 400,   FersValueEg   = 400,
+  AlfilValueMg  = 300,   AlfilValueEg  = 300,
+  SilverValueMg = 600,   SilverValueEg = 600,
+  AiwokValueMg  = 2500,  AiwokValueEg  = 2500,
+  AmazonValueMg = 3000,  AmazonValueEg = 3000,
+  KnibisValueMg = 800,   KnibisValueEg = 800,
+  BiskniValueMg = 800,   BiskniValueEg = 800,
 
   MidgameLimit  = 15258, EndgameLimit  = 3915
 };
 
+const int PIECE_TYPE_BITS = 6; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS)
+
 enum PieceType {
-  NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
+  NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK,
+  QUEEN, FERS, MET = FERS, ALFIL, SILVER, KHON = SILVER, AIWOK,
+  AMAZON, KNIBIS, BISKNI, KING,
   ALL_PIECES = 0,
-  PIECE_TYPE_NB = 8
+
+  PIECE_TYPE_NB = 1 << PIECE_TYPE_BITS
 };
+static_assert(PIECE_TYPE_BITS <= 6, "PIECE_TYPE uses more than 6 bit");
+static_assert(!(PIECE_TYPE_NB & (PIECE_TYPE_NB - 1)), "PIECE_TYPE_NB is not a power of 2");
 
 enum Piece {
   NO_PIECE,
-  W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
-  B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
-  PIECE_NB = 16
+  PIECE_NB = 2 * PIECE_TYPE_NB
 };
 
 extern Value PieceValue[PHASE_NB][PIECE_NB];
@@ -362,8 +377,12 @@ constexpr File operator~(File f) {
   return File(f ^ FILE_H); // Horizontal flip FILE_A -> FILE_H
 }
 
+constexpr Rank operator~(Rank r) {
+  return Rank(r ^ RANK_8); // Vertical flip Rank_1 -> Rank_8
+}
+
 constexpr Piece operator~(Piece pc) {
-  return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT
+  return Piece(pc ^ PIECE_TYPE_NB); // Swap color of piece BLACK KNIGHT -> WHITE KNIGHT
 }
 
 constexpr CastlingRight operator|(Color c, CastlingSide s) {
@@ -383,16 +402,16 @@ constexpr Square make_square(File f, Rank r) {
 }
 
 constexpr Piece make_piece(Color c, PieceType pt) {
-  return Piece((c << 3) + pt);
+  return Piece((c << PIECE_TYPE_BITS) + pt);
 }
 
 constexpr PieceType type_of(Piece pc) {
-  return PieceType(pc & 7);
+  return PieceType(pc & (PIECE_TYPE_NB - 1));
 }
 
 inline Color color_of(Piece pc) {
   assert(pc != NO_PIECE);
-  return Color(pc >> 3);
+  return Color(pc >> PIECE_TYPE_BITS);
 }
 
 constexpr bool is_ok(Square s) {
@@ -428,24 +447,41 @@ constexpr Direction pawn_push(Color c) {
   return c == WHITE ? NORTH : SOUTH;
 }
 
-constexpr Square from_sq(Move m) {
-  return Square((m >> 6) & 0x3F);
+inline MoveType type_of(Move m) {
+  MoveType t = MoveType(m & (15 << 12));
+  if (t == PROMOTION_STRAIGHT || t == PROMOTION_LEFT || t == PROMOTION_RIGHT)
+      return PROMOTION;
+  return t;
 }
 
 constexpr Square to_sq(Move m) {
   return Square(m & 0x3F);
 }
 
-constexpr int from_to(Move m) {
- return m & 0xFFF;
+inline Square from_sq(Move m) {
+  if (type_of(m) == PROMOTION)
+  {
+      Square to = to_sq(m);
+      MoveType t = MoveType(m & (15 << 12));
+      // Assume here that promotion occur only for relative ranks >= RANK_5.
+      Direction up = (to & 32) ? NORTH : SOUTH;
+      if (t == PROMOTION_STRAIGHT)
+          return to - up;
+      if (t == PROMOTION_LEFT)
+          return to - up - WEST;
+      if (t == PROMOTION_RIGHT)
+          return to - up - EAST;
+      assert(false);
+  }
+  return Square((m >> 6) & 0x3F);
 }
 
-constexpr MoveType type_of(Move m) {
-  return MoveType(m & (3 << 14));
+inline int from_to(Move m) {
+ return to_sq(m) + (from_sq(m) << 6);
 }
 
-constexpr PieceType promotion_type(Move m) {
-  return PieceType(((m >> 12) & 3) + KNIGHT);
+inline PieceType promotion_type(Move m) {
+  return type_of(m) == PROMOTION ? PieceType((m >> 6) & 63) : NO_PIECE_TYPE;
 }
 
 inline Move make_move(Square from, Square to) {
@@ -453,11 +489,13 @@ inline Move make_move(Square from, Square to) {
 }
 
 template<MoveType T>
-constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {
-  return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);
+inline Move make(Square from, Square to, PieceType pt = NO_PIECE_TYPE) {
+  if (T == PROMOTION_STRAIGHT || T == PROMOTION_LEFT || T == PROMOTION_RIGHT)
+      return Move(T + (pt << 6) + to);
+  return Move(T + (from << 6) + to);
 }
 
-constexpr bool is_ok(Move m) {
+inline bool is_ok(Move m) {
   return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE
 }
 
index 72eda4a..39e539f 100644 (file)
@@ -39,10 +39,6 @@ extern vector<string> setup_bench(const Position&, istream&);
 
 namespace {
 
-  // FEN string of the initial position, normal chess
-  const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
-
-
   // position() is called when engine receives the "position" UCI command.
   // The function sets up the position described in the given FEN string ("fen")
   // or the starting position ("startpos") and then makes the moves given in the
@@ -57,7 +53,7 @@ namespace {
 
     if (token == "startpos")
     {
-        fen = StartFEN;
+        fen = variants.find(Options["UCI_Variant"])->second->startFen;
         is >> token; // Consume "moves" token if any
     }
     else if (token == "fen")
@@ -67,7 +63,7 @@ namespace {
         return;
 
     states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
-    pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main());
+    pos.set(variants.find(Options["UCI_Variant"])->second, fen, Options["UCI_Chess960"], &states->back(), Threads.main());
 
     // Parse move list (if any)
     while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
@@ -193,7 +189,8 @@ void UCI::loop(int argc, char* argv[]) {
   StateListPtr states(new std::deque<StateInfo>(1));
   auto uiThread = std::make_shared<Thread>(0);
 
-  pos.set(StartFEN, false, &states->back(), uiThread.get());
+  assert(variants.find(Options["UCI_Variant"])->second != nullptr);
+  pos.set(variants.find(Options["UCI_Variant"])->second, variants.find(Options["UCI_Variant"])->second->startFen, false, &states->back(), uiThread.get());
 
   for (int i = 1; i < argc; ++i)
       cmd += std::string(argv[i]) + " ";
@@ -277,7 +274,7 @@ std::string UCI::square(Square s) {
 /// normal chess mode, and in e1h1 notation in chess960 mode. Internally all
 /// castling moves are always encoded as 'king captures rook'.
 
-string UCI::move(Move m, bool chess960) {
+string UCI::move(const Position& pos, Move m) {
 
   Square from = from_sq(m);
   Square to = to_sq(m);
@@ -288,13 +285,13 @@ string UCI::move(Move m, bool chess960) {
   if (m == MOVE_NULL)
       return "0000";
 
-  if (type_of(m) == CASTLING && !chess960)
+  if (type_of(m) == CASTLING && !pos.is_chess960())
       to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
 
   string move = UCI::square(from) + UCI::square(to);
 
   if (type_of(m) == PROMOTION)
-      move += " pnbrqk"[promotion_type(m)];
+      move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))];
 
   return move;
 }
@@ -309,7 +306,7 @@ Move UCI::to_move(const Position& pos, string& str) {
       str[4] = char(tolower(str[4]));
 
   for (const auto& m : MoveList<LEGAL>(pos))
-      if (str == UCI::move(m, pos.is_chess960()))
+      if (str == UCI::move(pos, m))
           return m;
 
   return MOVE_NONE;
index 3ad3a30..2355717 100644 (file)
--- a/src/uci.h
+++ b/src/uci.h
@@ -23,6 +23,7 @@
 
 #include <map>
 #include <string>
+#include <vector>
 
 #include "types.h"
 
@@ -49,6 +50,7 @@ public:
   Option(OnChange = nullptr);
   Option(bool v, OnChange = nullptr);
   Option(const char* v, OnChange = nullptr);
+  Option(const char* v, const std::vector<std::string>& variants, OnChange = nullptr);
   Option(double v, int minv, int maxv, OnChange = nullptr);
   Option(const char* v, const char *cur, OnChange = nullptr);
 
@@ -63,6 +65,7 @@ private:
 
   std::string defaultValue, currentValue, type;
   int min, max;
+  std::vector<std::string> comboValues;
   size_t idx;
   OnChange on_change;
 };
@@ -71,7 +74,7 @@ void init(OptionsMap&);
 void loop(int argc, char* argv[]);
 std::string value(Value v);
 std::string square(Square s);
-std::string move(Move m, bool chess960);
+std::string move(const Position& pos, Move m);
 std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
 Move to_move(const Position& pos, std::string& str);
 
index 5c89142..0374548 100644 (file)
 
 #include <algorithm>
 #include <cassert>
-#include <ostream>
+#include <iostream>
 
 #include "misc.h"
 #include "search.h"
 #include "thread.h"
 #include "tt.h"
 #include "uci.h"
+#include "variant.h"
 #include "syzygy/tbprobe.h"
 
 using std::string;
@@ -41,6 +42,9 @@ void on_hash_size(const Option& o) { TT.resize(o); }
 void on_logger(const Option& o) { start_logger(o); }
 void on_threads(const Option& o) { Threads.set(o); }
 void on_tb_path(const Option& o) { Tablebases::init(o); }
+void on_variant_change(const Option &o) {
+    sync_cout << "info string variant " << (std::string)o << " startpos " << variants.find(o)->second->startFen << sync_endl;
+}
 
 
 /// Our case insensitive less() function as required by UCI protocol
@@ -72,6 +76,7 @@ void init(OptionsMap& o) {
   o["Slow Mover"]            << Option(84, 10, 1000);
   o["nodestime"]             << Option(0, 0, 10000);
   o["UCI_Chess960"]          << Option(false);
+  o["UCI_Variant"]           << Option("chess", variants.get_keys(), on_variant_change);
   o["UCI_AnalyseMode"]       << Option(false);
   o["SyzygyPath"]            << Option("<empty>", on_tb_path);
   o["SyzygyProbeDepth"]      << Option(1, 1, 100);
@@ -95,6 +100,10 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
               if (o.type == "string" || o.type == "check" || o.type == "combo")
                   os << " default " << o.defaultValue;
 
+              if (o.type == "combo")
+                  for (string value : o.comboValues)
+                      os << " var " << value;
+
               if (o.type == "spin")
                   os << " default " << int(stof(o.defaultValue))
                      << " min "     << o.min
@@ -112,6 +121,9 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
 Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f)
 { defaultValue = currentValue = v; }
 
+Option::Option(const char* v, const std::vector<std::string>& variants, OnChange f) : type("combo"), min(0), max(0), comboValues(variants), on_change(f)
+{ defaultValue = currentValue = v; }
+
 Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f)
 { defaultValue = currentValue = (v ? "true" : "false"); }
 
@@ -130,7 +142,7 @@ Option::operator double() const {
 }
 
 Option::operator std::string() const {
-  assert(type == "string");
+  assert(type == "string" || type == "combo");
   return currentValue;
 }
 
@@ -162,6 +174,7 @@ Option& Option::operator=(const string& v) {
 
   if (   (type != "button" && v.empty())
       || (type == "check" && v != "true" && v != "false")
+      || (type == "combo" && (std::find(comboValues.begin(), comboValues.end(), v) == comboValues.end()))
       || (type == "spin" && (stof(v) < min || stof(v) > max)))
       return *this;
 
diff --git a/src/variant.cpp b/src/variant.cpp
new file mode 100644 (file)
index 0000000..b0467f4
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
+  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
+  Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+
+#include "variant.h"
+
+using std::string;
+
+VariantMap variants; // Global object
+
+void VariantMap::init() {
+    const Variant* chess = [&]{
+        Variant* v = new Variant();
+        return v;
+    } ();
+    const Variant* makruk = [&]{
+        Variant* v = new Variant();
+        v->reset_pieces();
+        v->set_piece(PAWN, 'p');
+        v->set_piece(KNIGHT, 'n');
+        v->set_piece(KHON, 's');
+        v->set_piece(ROOK, 'r');
+        v->set_piece(MET, 'm');
+        v->set_piece(KING, 'k');
+        v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1";
+        v->promotionRank = RANK_6;
+        v->promotionPieceTypes = {MET};
+        v->doubleStep = false;
+        v->castling = false;
+        return v;
+    } ();
+    const Variant* asean = [&]{
+        Variant* v = new Variant();
+        v->reset_pieces();
+        v->set_piece(PAWN, 'p');
+        v->set_piece(KNIGHT, 'n');
+        v->set_piece(KHON, 'b');
+        v->set_piece(ROOK, 'r');
+        v->set_piece(MET, 'q');
+        v->set_piece(KING, 'k');
+        v->startFen = "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w - - 0 1";
+        v->promotionPieceTypes = {ROOK, KNIGHT, KHON, MET};
+        v->doubleStep = false;
+        v->castling = false;
+        return v;
+    } ();
+    const Variant* aiwok = [&]{
+        Variant* v = new Variant();
+        v->reset_pieces();
+        v->set_piece(PAWN, 'p');
+        v->set_piece(KNIGHT, 'n');
+        v->set_piece(KHON, 's');
+        v->set_piece(ROOK, 'r');
+        v->set_piece(AIWOK, 'a');
+        v->set_piece(KING, 'k');
+        v->startFen = "rnsaksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKASNR w - - 0 1";
+        v->promotionRank = RANK_6;
+        v->promotionPieceTypes = {AIWOK};
+        v->doubleStep = false;
+        v->castling = false;
+        return v;
+    } ();
+    const Variant* shatranj = [&]{
+        Variant* v = new Variant();
+        v->reset_pieces();
+        v->set_piece(PAWN, 'p');
+        v->set_piece(KNIGHT, 'n');
+        v->set_piece(ALFIL, 'b');
+        v->set_piece(ROOK, 'r');
+        v->set_piece(FERS, 'q');
+        v->set_piece(KING, 'k');
+        v->startFen = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w - - 0 1";
+        v->promotionPieceTypes = {FERS};
+        v->doubleStep = false;
+        v->castling = false;
+        // TODO: bare king, stalemate
+        return v;
+    } ();
+    const Variant* amazon = [&]{
+        Variant* v = new Variant();
+        v->set_piece(QUEEN, ' ');
+        v->set_piece(AMAZON, 'a');
+        v->startFen = "rnbakbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBAKBNR w KQkq - 0 1";
+        v->promotionPieceTypes = {AMAZON, ROOK, BISHOP, KNIGHT};
+        return v;
+    } ();
+    const Variant* hoppelpoppel = [&]{
+        Variant* v = new Variant();
+        v->set_piece(KNIGHT, ' ');
+        v->set_piece(BISHOP, ' ');
+        v->set_piece(KNIBIS, 'n');
+        v->set_piece(BISKNI, 'b');
+        v->promotionPieceTypes = {QUEEN, ROOK, BISKNI, KNIBIS};
+        return v;
+    } ();
+    insert(std::pair<std::string, const Variant*>(std::string("chess"), chess));
+    insert(std::pair<std::string, const Variant*>(std::string("makruk"), makruk));
+    insert(std::pair<std::string, const Variant*>(std::string("asean"), asean));
+    insert(std::pair<std::string, const Variant*>(std::string("ai-wok"), aiwok));
+    insert(std::pair<std::string, const Variant*>(std::string("shatranj"), shatranj));
+    insert(std::pair<std::string, const Variant*>(std::string("amazon"), amazon));
+    insert(std::pair<std::string, const Variant*>(std::string("hoppelpoppel"), hoppelpoppel));
+}
+
+void VariantMap::clear_all() {
+  for (auto const& element : *this) {
+      delete element.second;
+  }
+  clear();
+}
+
+std::vector<std::string> VariantMap::get_keys() {
+  std::vector<std::string> keys;
+  for (auto const& element : *this) {
+      keys.push_back(element.first);
+  }
+  return keys;
+}
diff --git a/src/variant.h b/src/variant.h
new file mode 100644 (file)
index 0000000..9b2d925
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
+  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
+  Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef VARIANT_H_INCLUDED
+#define VARIANT_H_INCLUDED
+
+#include <map>
+#include <vector>
+#include <string>
+
+#include "types.h"
+
+
+/// Variant struct stores information needed to determine the rules of a variant.
+
+struct Variant {
+  std::string pieceToChar =  " PNBRQ" + std::string(KING - QUEEN - 1, ' ') + "K" + std::string(PIECE_TYPE_NB - KING - 1, ' ')
+                           + " pnbrq" + std::string(KING - QUEEN - 1, ' ') + "k" + std::string(PIECE_TYPE_NB - KING - 1, ' ');
+  std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
+  Rank promotionRank = RANK_8;
+  std::vector<PieceType> promotionPieceTypes = {QUEEN, ROOK, BISHOP, KNIGHT};
+  bool doubleStep = true;
+  bool castling = true;
+
+  void set_piece(PieceType pt, char c) {
+      pieceToChar[make_piece(WHITE, pt)] = toupper(c);
+      pieceToChar[make_piece(BLACK, pt)] = tolower(c);
+  }
+
+  void reset_pieces() {
+      pieceToChar = std::string(PIECE_NB, ' ');
+  }
+};
+
+struct VariantMap : public std::map<std::string, const Variant*> {
+  void init();
+  void clear_all();
+  std::vector<std::string> get_keys();
+};
+
+extern VariantMap variants;
+
+#endif // #ifndef VARIANT_H_INCLUDED