@@ -40,14 +40,61 @@ namespace {
4040
4141 inline Variant::PotionType potion_type_from_piece (const Variant* var, PieceType pt) {
4242 if (!var || !var->potions )
43- return static_cast <Variant::PotionType>(Variant::POTION_TYPE_NB);
43+ return static_cast <Variant::PotionType>(Variant::POTION_TYPE_NB);
4444 if (pt == var->potionPiece [Variant::POTION_FREEZE])
4545 return Variant::POTION_FREEZE;
4646 if (pt == var->potionPiece [Variant::POTION_JUMP])
4747 return Variant::POTION_JUMP;
4848 return static_cast <Variant::PotionType>(Variant::POTION_TYPE_NB);
4949 }
5050
51+ inline Square potion_zone_center (const Position& pos, Variant::PotionType potion,
52+ Bitboard zone) {
53+ if (zone == Bitboard (0 ))
54+ return SQ_NONE;
55+ if (potion == Variant::POTION_JUMP)
56+ return lsb (zone);
57+ Bitboard candidates = zone;
58+ while (candidates)
59+ {
60+ Square s = pop_lsb (candidates);
61+ if (pos.freeze_zone_from_square (s) == zone)
62+ return s;
63+ }
64+ return lsb (zone);
65+ }
66+
67+ inline Bitboard potion_zone_from_center (const Position& pos, Variant::PotionType potion,
68+ Square s) {
69+ if (s == SQ_NONE)
70+ return Bitboard (0 );
71+ if (potion == Variant::POTION_FREEZE)
72+ return pos.freeze_zone_from_square (s);
73+ return square_bb (s);
74+ }
75+
76+ inline Square parse_fen_square (const Position& pos, const std::string& token) {
77+ if (token.size () < 2 )
78+ return SQ_NONE;
79+ char fileChar = char (tolower (token[0 ]));
80+ if (fileChar < ' a' || fileChar > char (' a' + pos.max_file ()))
81+ return SQ_NONE;
82+ int file = fileChar - ' a' ;
83+ int rank = 0 ;
84+ for (size_t i = 1 ; i < token.size (); ++i)
85+ {
86+ if (!isdigit (token[i]))
87+ return SQ_NONE;
88+ rank = rank * 10 + (token[i] - ' 0' );
89+ }
90+ if (rank <= 0 )
91+ return SQ_NONE;
92+ int rankIndex = rank - 1 ;
93+ if (rankIndex > pos.max_rank ())
94+ return SQ_NONE;
95+ return make_square (File (file), Rank (rankIndex));
96+ }
97+
5198 struct SpellContextScope {
5299 Position& pos;
53100 bool active;
@@ -402,7 +449,87 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
402449 add_to_hand (Piece (idx));
403450 }
404451
452+ if (!sfen && potions_enabled ())
453+ {
454+ std::string potionState;
455+ ss >> std::ws;
456+ if (ss.peek () == ' {' )
457+ {
458+ ss >> token;
459+ while (ss >> token)
460+ {
461+ if (token == ' }' )
462+ break ;
463+ if (!isspace (token))
464+ potionState.push_back (char (token));
465+ }
466+ }
467+
468+ auto parse_potion_state = [&](const std::string& state) {
469+ size_t start = 0 ;
470+ const int maxCooldown = (1u << POTION_COOLDOWN_BITS) - 1 ;
471+ while (start < state.size ())
472+ {
473+ size_t end = state.find (' ,' , start);
474+ std::string entry = state.substr (start, end == std::string::npos ? end : end - start);
475+ if (entry.size () >= 4 )
476+ {
477+ char pieceChar = entry[0 ];
478+ Color c = islower (pieceChar) ? BLACK : WHITE;
479+ Variant::PotionType potion = static_cast <Variant::PotionType>(Variant::POTION_TYPE_NB);
480+ for (int pt = 0 ; pt < Variant::POTION_TYPE_NB; ++pt)
481+ {
482+ Variant::PotionType ptEnum = static_cast <Variant::PotionType>(pt);
483+ PieceType potionPiece = potion_piece (ptEnum);
484+ if (potionPiece == NO_PIECE_TYPE)
485+ continue ;
486+ char expected = piece_to_char ()[make_piece (WHITE, potionPiece)];
487+ if (tolower (expected) == tolower (pieceChar))
488+ {
489+ potion = ptEnum;
490+ break ;
491+ }
492+ }
493+
494+ size_t at = entry.find (' @' , 1 );
495+ size_t colon = entry.find (' :' , at == std::string::npos ? 0 : at + 1 );
496+ if (potion != Variant::POTION_TYPE_NB && at != std::string::npos && colon != std::string::npos)
497+ {
498+ std::string squareToken = entry.substr (at + 1 , colon - at - 1 );
499+ std::string cooldownToken = entry.substr (colon + 1 );
500+ Square center = SQ_NONE;
501+ if (!squareToken.empty () && squareToken != " -" )
502+ center = parse_fen_square (*this , squareToken);
503+
504+ int cooldown = 0 ;
505+ for (char ch : cooldownToken)
506+ {
507+ if (!isdigit (ch))
508+ {
509+ cooldown = 0 ;
510+ break ;
511+ }
512+ cooldown = cooldown * 10 + (ch - ' 0' );
513+ }
514+ if (cooldown > maxCooldown)
515+ cooldown = maxCooldown;
516+
517+ st->potionCooldown [c][potion] = cooldown;
518+ st->potionZones [c][potion] = potion_zone_from_center (*this , potion, center);
519+ }
520+ }
521+ if (end == std::string::npos)
522+ break ;
523+ start = end + 1 ;
524+ }
525+ };
526+
527+ if (!potionState.empty ())
528+ parse_potion_state (potionState);
529+ }
530+
405531 // 2. Active color
532+ ss >> std::ws;
406533 ss >> token;
407534 sideToMove = (token != (sfen ? ' w' : ' b' ) ? WHITE : BLACK); // Invert colors for SFEN
408535 ss >> token;
@@ -836,6 +963,38 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
836963 ss << ' ]' ;
837964 }
838965
966+ if (potions_enabled ())
967+ {
968+ std::string potionState;
969+ const int maxCooldown = (1u << POTION_COOLDOWN_BITS) - 1 ;
970+ for (Color c : {WHITE, BLACK})
971+ for (int pt = 0 ; pt < Variant::POTION_TYPE_NB; ++pt)
972+ {
973+ Variant::PotionType potion = static_cast <Variant::PotionType>(pt);
974+ PieceType potionPiece = potion_piece (potion);
975+ if (potionPiece == NO_PIECE_TYPE)
976+ continue ;
977+ if (!potionState.empty ())
978+ potionState += " ," ;
979+ potionState += piece_to_char ()[make_piece (c, potionPiece)];
980+ potionState += " @" ;
981+ Bitboard zone = potion_zone (c, potion);
982+ if (zone)
983+ potionState += UCI::square (*this , potion_zone_center (*this , potion, zone));
984+ else
985+ potionState += " -" ;
986+ potionState += " :" ;
987+ int cooldown = potion_cooldown (c, potion);
988+ if (cooldown < 0 )
989+ cooldown = 0 ;
990+ if (cooldown > maxCooldown)
991+ cooldown = maxCooldown;
992+ potionState += std::to_string (cooldown);
993+ }
994+ if (!potionState.empty ())
995+ ss << " {" << potionState << " }" ;
996+ }
997+
839998 ss << (sideToMove == WHITE ? " w " : " b " );
840999
8411000 // Disambiguation for chess960 "king" square
@@ -1352,8 +1511,8 @@ bool Position::legal(Move m) const {
13521511 if (freeze_squares () & to_sq (m))
13531512 return false ;
13541513
1355- // Non-royal pieces can not be impeded from castling
1356- if (type_of (piece_on (from)) != KING )
1514+ // Only the castling king piece is subject to attack checks
1515+ if (type_of (piece_on (from)) != castling_king_piece (us) )
13571516 return true ;
13581517
13591518 for (Square s = to; s != from; s += step)
0 commit comments