From cafbe8e8e8c26594dd7040788e6f72bc4bc8cfd9 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 23:13:18 +0100 Subject: [PATCH] Change the Move enum to a class This changes the Move enum to a class, this way all move related functions can be moved into the class and be more self contained. closes https://github.com/official-stockfish/Stockfish/pull/4958 No functional change --- src/movegen.cpp | 28 ++++----- src/movegen.h | 8 +-- src/movepick.cpp | 32 +++++----- src/movepick.h | 2 +- src/position.cpp | 86 +++++++++++++-------------- src/position.h | 10 ++-- src/search.cpp | 148 ++++++++++++++++++++++++----------------------- src/thread.cpp | 10 ++-- src/tt.cpp | 2 +- src/tt.h | 2 +- src/types.h | 117 ++++++++++++++++++++++--------------- src/uci.cpp | 18 +++--- 12 files changed, 241 insertions(+), 222 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 750a07e8..e6923067 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -34,13 +34,13 @@ ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS; if constexpr (Type == CAPTURES || all) - *moveList++ = make(to - D, to, QUEEN); + *moveList++ = Move::make(to - D, to, QUEEN); if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all) { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); + *moveList++ = Move::make(to - D, to, ROOK); + *moveList++ = Move::make(to - D, to, BISHOP); + *moveList++ = Move::make(to - D, to, KNIGHT); } return moveList; @@ -89,13 +89,13 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta while (b1) { Square to = pop_lsb(b1); - *moveList++ = make_move(to - Up, to); + *moveList++ = Move(to - Up, to); } while (b2) { Square to = pop_lsb(b2); - *moveList++ = make_move(to - Up - Up, to); + *moveList++ = Move(to - Up - Up, to); } } @@ -128,13 +128,13 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta while (b1) { Square to = pop_lsb(b1); - *moveList++ = make_move(to - UpRight, to); + *moveList++ = Move(to - UpRight, to); } while (b2) { Square to = pop_lsb(b2); - *moveList++ = make_move(to - UpLeft, to); + *moveList++ = Move(to - UpLeft, to); } if (pos.ep_square() != SQ_NONE) @@ -150,7 +150,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta assert(b1); while (b1) - *moveList++ = make(pop_lsb(b1), pos.ep_square()); + *moveList++ = Move::make(pop_lsb(b1), pos.ep_square()); } } @@ -175,7 +175,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) b &= pos.check_squares(Pt); while (b) - *moveList++ = make_move(from, pop_lsb(b)); + *moveList++ = Move(from, pop_lsb(b)); } return moveList; @@ -213,12 +213,12 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { b &= ~attacks_bb(pos.square(~Us)); while (b) - *moveList++ = make_move(ksq, pop_lsb(b)); + *moveList++ = Move(ksq, pop_lsb(b)); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) - *moveList++ = make(ksq, pos.castling_rook_square(cr)); + *moveList++ = Move::make(ksq, pos.castling_rook_square(cr)); } return moveList; @@ -268,9 +268,9 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) - if (((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) + if (((pinned & cur->from_sq()) || cur->from_sq() == ksq || cur->type_of() == EN_PASSANT) && !pos.legal(*cur)) - *cur = (--moveList)->move; + *cur = *(--moveList); else ++cur; diff --git a/src/movegen.h b/src/movegen.h index 3ae84c4c..5f650d2e 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -37,12 +37,10 @@ enum GenType { LEGAL }; -struct ExtMove { - Move move; - int value; +struct ExtMove: public Move { + int value; - operator Move() const { return move; } - void operator=(Move m) { move = m; } + void operator=(Move m) { data = m.raw(); } // Inhibit unwanted implicit conversions to Move // with an ambiguity that yields to a compile error. diff --git a/src/movepick.cpp b/src/movepick.cpp index f33839cd..cae01891 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -166,19 +166,19 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = - (7 * int(PieceValue[pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) + (7 * int(PieceValue[pos.piece_on(m.to_sq())]) + + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]) / 16; else if constexpr (Type == QUIETS) { Piece pc = pos.moved_piece(m); PieceType pt = type_of(pos.moved_piece(m)); - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); // histories - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()]; m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; @@ -211,12 +211,12 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + m.value = PieceValue[pos.piece_on(m.to_sq())] - Value(type_of(pos.moved_piece(m))) + (1 << 28); else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][to_sq(m)]; + m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] + + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][m.to_sq()]; } } @@ -235,7 +235,7 @@ Move MovePicker::select(Pred filter) { cur++; } - return MOVE_NONE; + return Move::none(); } // Most important method of the MovePicker class. It @@ -278,8 +278,7 @@ top: endMoves = std::end(refutations); // If the countermove is the same as a killer, skip it - if (refutations[0].move == refutations[2].move - || refutations[1].move == refutations[2].move) + if (refutations[0] == refutations[2] || refutations[1] == refutations[2]) --endMoves; ++stage; @@ -287,7 +286,7 @@ top: case REFUTATION : if (select([&]() { - return *cur != MOVE_NONE && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); + return *cur != Move::none() && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); })) return *(cur - 1); ++stage; @@ -308,8 +307,7 @@ top: case QUIET : if (!skipQuiets && select([&]() { - return *cur != refutations[0].move && *cur != refutations[1].move - && *cur != refutations[2].move; + return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) return *(cur - 1); @@ -343,7 +341,7 @@ top: // If we did not find any move and we do not try checks, we have finished if (depth != DEPTH_QS_CHECKS) - return MOVE_NONE; + return Move::none(); ++stage; [[fallthrough]]; @@ -360,7 +358,7 @@ top: } assert(false); - return MOVE_NONE; // Silence warning + return Move::none(); // Silence warning } } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 24252433..ad4be8e9 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -142,7 +142,7 @@ using CorrectionHistory = // MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which returns a // new pseudo-legal move each time it is called, until there are no moves left, -// when MOVE_NONE is returned. In order to improve the efficiency of the +// when Move::none() is returned. In order to improve the efficiency of the // alpha-beta algorithm, MovePicker attempts to return the moves which are most // likely to get a cut-off first. class MovePicker { diff --git a/src/position.cpp b/src/position.cpp index 32823bd0..810bba57 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -140,14 +140,14 @@ void Position::init() { for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) { - Move move = make_move(s1, s2); + Move move = Move(s1, s2); Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; int i = H1(key); while (true) { std::swap(cuckoo[i], key); std::swap(cuckooMove[i], move); - if (move == MOVE_NONE) // Arrived at empty slot? + if (move == Move::none()) // Arrived at empty slot? break; i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot } @@ -487,11 +487,11 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { // Tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { - assert(is_ok(m)); + assert(m.is_ok()); Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); assert(color_of(moved_piece(m)) == us); assert(piece_on(square(us)) == make_piece(us, KING)); @@ -499,7 +499,7 @@ bool Position::legal(Move m) const { // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { Square ksq = square(us); Square capsq = to - pawn_push(us); @@ -516,7 +516,7 @@ bool Position::legal(Move m) const { // Castling moves generation does not check if the castling path is clear of // enemy attacks, it is delayed at a later time: now! - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { // After castling, the rook and king final positions are the same in // Chess960 as they would be in standard chess. @@ -529,7 +529,7 @@ bool Position::legal(Move m) const { // In case of Chess960, verify if the Rook blocks some checks. // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 || !(blockers_for_king(us) & to_sq(m)); + return !chess960 || !(blockers_for_king(us) & m.to_sq()); } // If the moving piece is a king, check whether the destination square is @@ -549,18 +549,18 @@ bool Position::legal(Move m) const { bool Position::pseudo_legal(const Move m) const { Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = moved_piece(m); // Use a slower but simpler function for uncommon cases // yet we skip the legality check of MoveList(). - if (type_of(m) != NORMAL) + if (m.type_of() != NORMAL) return checkers() ? MoveList(*this).contains(m) : MoveList(*this).contains(m); // Is not a promotion, so the promotion piece must be empty - assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); + assert(m.promotion_type() - KNIGHT == NO_PIECE_TYPE); // If the 'from' square is not occupied by a piece belonging to the side to // move, the move is obviously not legal. @@ -615,11 +615,11 @@ bool Position::pseudo_legal(const Move m) const { // Tests whether a pseudo-legal move gives a check bool Position::gives_check(Move m) const { - assert(is_ok(m)); + assert(m.is_ok()); assert(color_of(moved_piece(m)) == sideToMove); - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); // Is there a direct check? if (check_squares(type_of(piece_on(from))) & to) @@ -627,15 +627,15 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || type_of(m) == CASTLING; + return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; - switch (type_of(m)) + switch (m.type_of()) { case NORMAL : return false; case PROMOTION : - return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle @@ -664,7 +664,7 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { - assert(is_ok(m)); + assert(m.is_ok()); assert(&newSt != st); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); @@ -691,16 +691,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Color us = sideToMove; Color them = ~us; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(from); - Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); assert(color_of(pc) == us); - assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); + assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us)); assert(type_of(captured) != KING); - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { assert(pc == make_piece(us, KING)); assert(captured == make_piece(us, ROOK)); @@ -720,7 +720,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // update non-pawn material. if (type_of(captured) == PAWN) { - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { capsq -= pawn_push(us); @@ -771,7 +771,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) + if (m.type_of() != CASTLING) { dp.piece[0] = pc; dp.from[0] = from; @@ -791,9 +791,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { k ^= Zobrist::enpassant[file_of(st->epSquare)]; } - else if (type_of(m) == PROMOTION) + else if (m.type_of() == PROMOTION) { - Piece promotion = make_piece(us, promotion_type(m)); + Piece promotion = make_piece(us, m.promotion_type()); assert(relative_rank(us, to) == RANK_8); assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); @@ -866,22 +866,22 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // be restored to exactly the same state as before the move was made. void Position::undo_move(Move m) { - assert(is_ok(m)); + assert(m.is_ok()); sideToMove = ~sideToMove; Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(to); - assert(empty(from) || type_of(m) == CASTLING); + assert(empty(from) || m.type_of() == CASTLING); assert(type_of(st->capturedPiece) != KING); - if (type_of(m) == PROMOTION) + if (m.type_of() == PROMOTION) { assert(relative_rank(us, to) == RANK_8); - assert(type_of(pc) == promotion_type(m)); + assert(type_of(pc) == m.promotion_type()); assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); remove_piece(to); @@ -889,7 +889,7 @@ void Position::undo_move(Move m) { put_piece(pc, to); } - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { Square rfrom, rto; do_castling(us, from, to, rfrom, rto); @@ -902,7 +902,7 @@ void Position::undo_move(Move m) { { Square capsq = to; - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { capsq -= pawn_push(us); @@ -1011,8 +1011,8 @@ void Position::undo_null_move() { // en passant and promotions. Key Position::key_after(Move m) const { - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(from); Piece captured = piece_on(to); Key k = st->key ^ Zobrist::side; @@ -1031,13 +1031,13 @@ Key Position::key_after(Move m) const { // algorithm similar to alpha-beta pruning with a null window. bool Position::see_ge(Move m, Value threshold) const { - assert(is_ok(m)); + assert(m.is_ok()); // Only deal with normal moves, assume others pass a simple SEE - if (type_of(m) != NORMAL) + if (m.type_of() != NORMAL) return VALUE_ZERO >= threshold; - Square from = from_sq(m), to = to_sq(m); + Square from = m.from_sq(), to = m.to_sq(); int swap = PieceValue[piece_on(to)] - threshold; if (swap < 0) @@ -1182,8 +1182,8 @@ bool Position::has_game_cycle(int ply) const { if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) { Move move = cuckooMove[j]; - Square s1 = from_sq(move); - Square s2 = to_sq(move); + Square s1 = move.from_sq(); + Square s2 = move.to_sq(); if (!((between_bb(s1, s2) ^ s2) & pieces())) { diff --git a/src/position.h b/src/position.h index 46956afc..3e932759 100644 --- a/src/position.h +++ b/src/position.h @@ -210,7 +210,7 @@ inline Piece Position::piece_on(Square s) const { inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } -inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); } +inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } @@ -312,16 +312,16 @@ inline int Position::rule50_count() const { return st->rule50; } inline bool Position::is_chess960() const { return chess960; } inline bool Position::capture(Move m) const { - assert(is_ok(m)); - return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; + assert(m.is_ok()); + return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT; } // Returns true if a move is generated from the capture stage, having also // queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { - assert(is_ok(m)); - return capture(m) || promotion_type(m) == QUEEN; + assert(m.is_ok()); + return capture(m) || m.promotion_type() == QUEEN; } inline Piece Position::captured_piece() const { return st->capturedPiece; } diff --git a/src/search.cpp b/src/search.cpp index aae3625a..0d41f48d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -130,7 +130,7 @@ struct Skill { Move pick_best(size_t multiPV); double level; - Move best = MOVE_NONE; + Move best = Move::none(); }; template @@ -226,7 +226,7 @@ void MainThread::search() { if (rootMoves.empty()) { - rootMoves.emplace_back(MOVE_NONE); + rootMoves.emplace_back(Move::none()); sync_cout << "info depth 0 score " << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; } @@ -262,7 +262,7 @@ void MainThread::search() { Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); if (int(Options["MultiPV"]) == 1 && !Limits.depth && !skill.enabled() - && rootMoves[0].pv[0] != MOVE_NONE) + && rootMoves[0].pv[0] != Move::none()) bestThread = Threads.get_best_thread(); bestPreviousScore = bestThread->rootMoves[0].score; @@ -293,7 +293,7 @@ void Thread::search() { Stack stack[MAX_PLY + 10], *ss = stack + 7; Move pv[MAX_PLY + 1]; Value alpha, beta, delta; - Move lastBestMove = MOVE_NONE; + Move lastBestMove = Move::none(); Depth lastBestMoveDepth = 0; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); double timeReduction = 1, totBestMoveChanges = 0; @@ -604,11 +604,11 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss + 1)->excludedMove = bestMove = MOVE_NONE; - (ss + 2)->killers[0] = (ss + 2)->killers[1] = MOVE_NONE; + (ss + 1)->excludedMove = bestMove = Move::none(); + (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; ss->doubleExtensions = (ss - 1)->doubleExtensions; - Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; // Step 4. Transposition table lookup. @@ -618,7 +618,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() - : MOVE_NONE; + : Move::none(); ttCapture = ttMove && pos.capture_stage(ttMove); // At this point, if excluded, skip straight to step 6, static eval. However, @@ -650,8 +650,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else if (!ttCapture) { int penalty = -stat_malus(depth); - thisThread->mainHistory[us][from_to(ttMove)] << penalty; - update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); + thisThread->mainHistory[us][ttMove.from_to()] << penalty; + update_continuation_histories(ss, pos.moved_piece(ttMove), ttMove.to_sq(), penalty); } } @@ -699,7 +699,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), MOVE_NONE, VALUE_NONE); + std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE); return value; } @@ -764,17 +764,17 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->staticEval = eval = to_static_eval(newEval); // Static evaluation is saved as it was before adjustment by correction history - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), unadjustedStaticEval); } // Use static evaluation difference to improve quiet move ordering (~9 Elo) - if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); bonus = bonus > 0 ? 2 * bonus : bonus / 2; - thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; - if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; + if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; } @@ -810,9 +810,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17496 && eval >= beta - && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 && !excludedMove - && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17496 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 + && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); @@ -820,7 +820,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Null move dynamic reduction based on depth and eval Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; - ss->currentMove = MOVE_NULL; + ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; pos.do_null_move(st); @@ -883,7 +883,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != Move::none()) if (move != excludedMove && pos.legal(move)) { assert(pos.capture_stage(move)); @@ -894,7 +894,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->currentMove = move; ss->continuationHistory = &thisThread - ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][to_sq(move)]; + ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; pos.do_move(move, st); @@ -938,7 +938,7 @@ moves_loop: // When in check, search starts here (ss - 6)->continuationHistory}; Move countermove = - prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; + prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none(); MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, &thisThread->pawnHistory, countermove, ss->killers); @@ -953,9 +953,9 @@ moves_loop: // When in check, search starts here // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) + while ((move = mp.next_move(moveCountPruning)) != Move::none()) { - assert(is_ok(move)); + assert(move.is_ok()); if (move == excludedMove) continue; @@ -1009,10 +1009,10 @@ moves_loop: // When in check, search starts here // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Piece capturedPiece = pos.piece_on(to_sq(move)); + Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] - + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; + + captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) continue; } @@ -1024,15 +1024,16 @@ moves_loop: // When in check, search starts here else { int history = - (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][to_sq(move)]; + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) if (lmrDepth < 6 && history < -3752 * depth) continue; - history += 2 * thisThread->mainHistory[us][from_to(move)]; + history += 2 * thisThread->mainHistory[us][move.from_to()]; lmrDepth += history / 7838; lmrDepth = std::max(lmrDepth, -1); @@ -1077,7 +1078,7 @@ moves_loop: // When in check, search starts here ss->excludedMove = move; value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - ss->excludedMove = MOVE_NONE; + ss->excludedMove = Move::none(); if (value < singularBeta) { @@ -1125,12 +1126,13 @@ moves_loop: // When in check, search starts here // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 4325) + && (*contHist[0])[movedPiece][move.to_sq()] >= 4325) extension = 1; // Recapture extensions (~1 Elo) - else if (PvNode && move == ttMove && to_sq(move) == prevSq - && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] + else if (PvNode && move == ttMove && move.to_sq() == prevSq + && captureHistory[movedPiece][move.to_sq()] + [type_of(pos.piece_on(move.to_sq()))] > 4146) extension = 1; } @@ -1145,7 +1147,7 @@ moves_loop: // When in check, search starts here // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = - &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][to_sq(move)]; + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; // Step 16. Make the move pos.do_move(move, st, givesCheck); @@ -1187,10 +1189,10 @@ moves_loop: // When in check, search starts here else if (move == ttMove) r = 0; - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - 3817; + ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] - 3817; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) r -= ss->statScore / 14767; @@ -1229,7 +1231,7 @@ moves_loop: // When in check, search starts here : value >= beta ? stat_bonus(newDepth) : 0; - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); + update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } @@ -1249,7 +1251,7 @@ moves_loop: // When in check, search starts here if (PvNode && (moveCount == 1 || value > alpha)) { (ss + 1)->pv = pv; - (ss + 1)->pv[0] = MOVE_NONE; + (ss + 1)->pv[0] = Move::none(); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); } @@ -1296,7 +1298,7 @@ moves_loop: // When in check, search starts here assert((ss + 1)->pv); - for (Move* m = (ss + 1)->pv; *m != MOVE_NONE; ++m) + for (Move* m = (ss + 1)->pv; *m != Move::none(); ++m) rm.pv.push_back(*m); // We record how often the best move has been changed in each iteration. @@ -1375,7 +1377,7 @@ moves_loop: // When in check, search starts here + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << stat_bonus(depth) * bonus / 2; } @@ -1451,11 +1453,11 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (PvNode) { (ss + 1)->pv = pv; - ss->pv[0] = MOVE_NONE; + ss->pv[0] = Move::none(); } Thread* thisThread = pos.this_thread(); - bestMove = MOVE_NONE; + bestMove = Move::none(); ss->inCheck = pos.checkers(); moveCount = 0; @@ -1476,7 +1478,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { posKey = pos.key(); tte = TT.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = ss->ttHit ? tte->move() : MOVE_NONE; + ttMove = ss->ttHit ? tte->move() : Move::none(); pvHit = ss->ttHit && tte->is_pv(); // At non-PV nodes we check for an early TT cutoff @@ -1513,7 +1515,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; + (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; Value newEval = ss->staticEval @@ -1527,7 +1529,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - MOVE_NONE, unadjustedStaticEval); + Move::none(), unadjustedStaticEval); return bestValue; } @@ -1545,7 +1547,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); @@ -1553,9 +1555,9 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != Move::none()) { - assert(is_ok(move)); + assert(move.is_ok()); // Check for legality if (!pos.legal(move)) @@ -1570,13 +1572,13 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) - if (!givesCheck && to_sq(move) != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY - && type_of(move) != PROMOTION) + if (!givesCheck && move.to_sq() != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY + && move.type_of() != PROMOTION) { if (moveCount > 2) continue; - futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; + futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())]; // If static eval + value of piece we are going to capture is much lower // than alpha we can prune this move. @@ -1610,8 +1612,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { break; // Continuation history based pruning (~3 Elo) - if (!capture && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) + if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] < 0 + && (*contHist[1])[pos.moved_piece(move)][move.to_sq()] < 0) continue; // Do not search moves with bad enough SEE values (~5 Elo) @@ -1626,7 +1628,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->currentMove = move; ss->continuationHistory = &thisThread - ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][to_sq(move)]; + ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; quietCheckEvasions += !capture && ss->inCheck; @@ -1738,9 +1740,9 @@ Value value_from_tt(Value v, int ply, int r50c) { // Adds current move and appends child pv[] void update_pv(Move* pv, Move move, const Move* childPv) { - for (*pv++ = move; childPv && *childPv != MOVE_NONE;) + for (*pv++ = move; childPv && *childPv != Move::none();) *pv++ = *childPv++; - *pv = MOVE_NONE; + *pv = Move::none(); } @@ -1775,25 +1777,25 @@ void update_all_stats(const Position& pos, update_quiet_stats(pos, ss, bestMove, bestMoveBonus); int pIndex = pawn_structure_index(pos); - thisThread->pawnHistory[pIndex][moved_piece][to_sq(bestMove)] << quietMoveBonus; + thisThread->pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread - ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][to_sq(quietsSearched[i])] + ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] << -quietMoveMalus; - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus; + thisThread->mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - to_sq(quietsSearched[i]), -quietMoveMalus); + quietsSearched[i].to_sq(), -quietMoveMalus); } } else { // Increase stats for the best move in case it was a capture move - captured = type_of(pos.piece_on(to_sq(bestMove))); - captureHistory[moved_piece][to_sq(bestMove)][captured] << quietMoveBonus; + captured = type_of(pos.piece_on(bestMove.to_sq())); + captureHistory[moved_piece][bestMove.to_sq()][captured] << quietMoveBonus; } // Extra penalty for a quiet early move that was not a TT move or @@ -1808,8 +1810,8 @@ void update_all_stats(const Position& pos, for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); - captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveMalus; + captured = type_of(pos.piece_on(capturesSearched[i].to_sq())); + captureHistory[moved_piece][capturesSearched[i].to_sq()][captured] << -quietMoveMalus; } } @@ -1823,7 +1825,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; - if (is_ok((ss - i)->currentMove)) + if (((ss - i)->currentMove).is_ok()) (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); } } @@ -1841,13 +1843,13 @@ void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { Color us = pos.side_to_move(); Thread* thisThread = pos.this_thread(); - thisThread->mainHistory[us][from_to(move)] << bonus; - update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); + thisThread->mainHistory[us][move.from_to()] << bonus; + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); // Update countermove history - if (is_ok((ss - 1)->currentMove)) + if (((ss - 1)->currentMove).is_ok()) { - Square prevSq = to_sq((ss - 1)->currentMove); + Square prevSq = ((ss - 1)->currentMove).to_sq(); thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } } @@ -1987,7 +1989,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { assert(pv.size() == 1); - if (pv[0] == MOVE_NONE) + if (pv[0] == Move::none()) return false; pos.do_move(pv[0], st); diff --git a/src/thread.cpp b/src/thread.cpp index e900a9ac..01ccd4fc 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -66,7 +66,7 @@ Thread::~Thread() { // Reset histories, usually before a new game void Thread::clear() { - counterMoves.fill(MOVE_NONE); + counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(0); pawnHistory.fill(0); @@ -220,9 +220,9 @@ void ThreadPool::start_thinking(Position& pos, Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = threads.front(); - std::map votes; - Value minScore = VALUE_NONE; + Thread* bestThread = threads.front(); + std::unordered_map votes; + Value minScore = VALUE_NONE; // Find the minimum score of all threads for (Thread* th : threads) diff --git a/src/tt.cpp b/src/tt.cpp index 5c4e6d53..2e3f7d32 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -39,7 +39,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) // Preserve any existing move for the same position if (m || uint16_t(k) != key16) - move16 = uint16_t(m); + move16 = m; // Overwrite less valuable entries (cheapest checks first) if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) diff --git a/src/tt.h b/src/tt.h index 82a66863..61e854c1 100644 --- a/src/tt.h +++ b/src/tt.h @@ -53,7 +53,7 @@ struct TTEntry { uint16_t key16; uint8_t depth8; uint8_t genBound8; - uint16_t move16; + Move move16; int16_t value16; int16_t eval16; }; diff --git a/src/types.h b/src/types.h index dde1a52c..2970d1e0 100644 --- a/src/types.h +++ b/src/types.h @@ -108,30 +108,6 @@ using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; -// A move needs 16 bits to be stored -// -// bit 0- 5: destination square (from 0 to 63) -// bit 6-11: origin square (from 0 to 63) -// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) -// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) -// NOTE: en passant bit is set only when a pawn can be captured -// -// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in -// any normal move destination square is always different from origin square -// while MOVE_NONE and MOVE_NULL have the same origin and destination square. - -enum Move : int { - MOVE_NONE, - MOVE_NULL = 65 -}; - -enum MoveType { - NORMAL, - PROMOTION = 1 << 14, - EN_PASSANT = 2 << 14, - CASTLING = 3 << 14 -}; - enum Color { WHITE, BLACK, @@ -353,8 +329,6 @@ inline Color color_of(Piece pc) { return Color(pc >> 3); } -constexpr bool is_ok(Move m) { return m != MOVE_NONE && m != MOVE_NULL; } - constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } constexpr File file_of(Square s) { return File(s & 7); } @@ -369,34 +343,81 @@ constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_o constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } -constexpr Square from_sq(Move m) { - assert(is_ok(m)); - return Square((m >> 6) & 0x3F); -} - -constexpr Square to_sq(Move m) { - assert(is_ok(m)); - return Square(m & 0x3F); -} - -constexpr int from_to(Move m) { return m & 0xFFF; } - -constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); } - -constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); } - -constexpr Move make_move(Square from, Square to) { return Move((from << 6) + to); } - -template -constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { - return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); -} // Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } + +enum MoveType { + NORMAL, + PROMOTION = 1 << 14, + EN_PASSANT = 2 << 14, + CASTLING = 3 << 14 +}; + +// A move needs 16 bits to be stored +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +// NOTE: en passant bit is set only when a pawn can be captured +// +// Special cases are Move::none() and Move::null(). We can sneak these in because in +// any normal move destination square is always different from origin square +// while Move::none() and Move::null() have the same origin and destination square. +class Move { + public: + Move() = default; + constexpr explicit Move(std::uint16_t d) : + data(d) {} + + constexpr Move(Square from, Square to) : + data((from << 6) + to) {} + + template + static constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); + } + + constexpr Square from_sq() const { + assert(is_ok()); + return Square((data >> 6) & 0x3F); + } + + constexpr Square to_sq() const { + assert(is_ok()); + return Square(data & 0x3F); + } + + constexpr int from_to() const { return data & 0xFFF; } + + constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } + + constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } + + constexpr bool is_ok() const { return none().data != data && null().data != data; } + + static constexpr Move null() { return Move(65); } + static constexpr Move none() { return Move(0); } + + constexpr bool operator==(const Move& m) const { return data == m.data; } + constexpr bool operator!=(const Move& m) const { return data != m.data; } + + constexpr explicit operator bool() const { return data != 0; } + + constexpr std::uint16_t raw() const { return data; } + + struct MoveHash { + std::size_t operator()(const Move& m) const { return m.data; } + }; + + protected: + std::uint16_t data; +}; + } // namespace Stockfish #endif // #ifndef TYPES_H_INCLUDED diff --git a/src/uci.cpp b/src/uci.cpp index 5dc9b2b0..8e93eee6 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -75,7 +75,7 @@ void position(Position& pos, std::istringstream& is, StateListPtr& states) { pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse the move list, if any - while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) + while (is >> token && (m = UCI::to_move(pos, token)) != Move::none()) { states->emplace_back(); pos.do_move(m, states->back()); @@ -395,22 +395,22 @@ std::string UCI::square(Square s) { // Internally, all castling moves are always encoded as 'king captures rook'. std::string UCI::move(Move m, bool chess960) { - if (m == MOVE_NONE) + if (m == Move::none()) return "(none)"; - if (m == MOVE_NULL) + if (m == Move::null()) return "0000"; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); - if (type_of(m) == CASTLING && !chess960) + if (m.type_of() == CASTLING && !chess960) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); std::string move = UCI::square(from) + UCI::square(to); - if (type_of(m) == PROMOTION) - move += " pnbrqk"[promotion_type(m)]; + if (m.type_of() == PROMOTION) + move += " pnbrqk"[m.promotion_type()]; return move; } @@ -427,7 +427,7 @@ Move UCI::to_move(const Position& pos, std::string& str) { if (str == UCI::move(m, pos.is_chess960())) return m; - return MOVE_NONE; + return Move::none(); } } // namespace Stockfish