/// RootMove::extract_pv_from_tt() builds a PV by adding moves from the TT table. /// We consider also failing high nodes and not only BOUND_EXACT nodes so to /// allow to always have a ponder move even when we fail high at root, and a /// long PV to print that is important for position analysis. internal void extract_pv_from_tt(Position pos) { StateInfoArray sia = StateInfoArrayBroker.GetObject(); int stPos = 0; TTEntry tte; int ply = 1; Move m = pv[0]; Debug.Assert(m != MoveC.MOVE_NONE && pos.is_pseudo_legal(m)); pv.Clear(); pv.Add(m); pos.do_move(m, sia.state[stPos++]); UInt32 ttePos = 0; while (TT.probe(pos.key(), ref ttePos, out tte) && (m = tte.move()) != MoveC.MOVE_NONE // Local copy, TT entry could change && pos.is_pseudo_legal(m) && pos.pl_move_is_legal(m, pos.pinned_pieces()) && ply < Constants.MAX_PLY && (!pos.is_draw(false) || ply < 2)) { pv.Add(m); pos.do_move(m, sia.state[stPos++]); ply++; } pv.Add(MoveC.MOVE_NONE); do pos.undo_move(pv[--ply]); while (ply != 0); StateInfoArrayBroker.Free(); }
internal ScaleFactor scale_factor_BLACK(Position pos) { if (scalingFunctionBLACK == null) return (factorBLACK); ScaleFactor sf = scalingFunctionBLACK(ColorC.BLACK, pos); return sf == ScaleFactorC.SCALE_FACTOR_NONE ? factorBLACK : sf; }
internal ScaleFactor scale_factor_WHITE(Position pos) { if (scalingFunctionWHITE == null) return factorWHITE; ScaleFactor sf = scalingFunctionWHITE(ColorC.WHITE, pos); return sf == ScaleFactorC.SCALE_FACTOR_NONE ? factorWHITE : sf; }
private static void generate_castle( int Side, bool Checks, Position pos, MoveStack[] ms, ref int mpos, int us) { if (pos.castle_impeded(us, Side) || (pos.can_castle_CR(Utils.make_castle_right(us, Side)) == 0)) { return; } // After castling, the rook and king final positions are the same in Chess960 // as they would be in standard chess. var kfrom = pos.king_square(us); var rfrom = pos.castle_rook_square(us, Side); var kto = Utils.relative_square(us, Side == CastlingSideC.KING_SIDE ? SquareC.SQ_G1 : SquareC.SQ_C1); var enemies = pos.pieces_C(us ^ 1); Debug.Assert(!pos.in_check()); int K = pos.chess960 ? kto > kfrom ? -1 : 1 : Side == CastlingSideC.KING_SIDE ? -1 : 1; for (Square s = kto; s != kfrom; s += (Square)K) { if ((pos.attackers_to(s) & enemies) != 0) { return; } } // 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 (pos.chess960 && ((pos.attackers_to(kto, Utils.xor_bit(pos.occupied_squares, rfrom)) & enemies) != 0)) { return; } var m = Utils.make(kfrom, rfrom, MoveTypeC.CASTLING); if (Checks) { var ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); var givesCheck = pos.move_gives_check(m, ci); CheckInfoBroker.Free(); if (!givesCheck) { return; } } ms[mpos++].move = m; }
internal int scale_factor_BLACK(Position pos) { if (this.scalingFunctionBLACK == null) { return (this.factorBLACK); } var sf = this.scalingFunctionBLACK(ColorC.BLACK, pos); return sf == ScaleFactorC.SCALE_FACTOR_NONE ? this.factorBLACK : sf; }
internal int scale_factor_WHITE(Position pos) { if (this.scalingFunctionWHITE == null) { return this.factorWHITE; } var sf = this.scalingFunctionWHITE(ColorC.WHITE, pos); return sf == ScaleFactorC.SCALE_FACTOR_NONE ? this.factorWHITE : sf; }
public void Run(object arguments) { string[] args = (string[])arguments; Plug.Write(Utils.engine_info()); Plug.Write(Constants.endl); CheckInfoBroker.init(); EvalInfoBroker.init(); SwapListBroker.init(); MovesSearchedBroker.init(); PositionBroker.init(); StateInfoArrayBroker.init(); MListBroker.init(); LoopStackBroker.init(); MovePickerBroker.init(); StateInfoBroker.init(); Utils.init(); #if WINDOWS_RT #else Book.init(); #endif Position.init(); KPKPosition.init(); Endgame.init(); Search.init(); Evaluate.init(); Threads.init(); // .Net warmup sequence Plug.IsWarmup = true; Position pos = new Position(Uci.StartFEN, false, Threads.main_thread()); Stack<string> stack = Utils.CreateStack("go depth 7"); Uci.go(pos, stack); Threads.wait_for_search_finished(); Plug.IsWarmup = false; StringBuilder sb = new StringBuilder(); for (int i = 1; i < args.Length; i++) { sb.Append(args[i]).Append(" "); } Uci.uci_loop(sb.ToString()); Threads.exit(); }
internal void CreateCheckInfo(Position pos) { Color them = pos.sideToMove ^ 1; ksq = pos.pieceList[them][PieceTypeC.KING][0]; pinned = pos.pinned_pieces(); dcCandidates = pos.discovered_check_candidates(); checkSq[PieceTypeC.PAWN] = Utils.StepAttacksBB[((them << 3) | PieceTypeC.PAWN)][ksq]; checkSq[PieceTypeC.KNIGHT] = Utils.StepAttacksBB_KNIGHT[ksq]; #if X64 checkSq[PieceTypeC.BISHOP] = Utils.BAttacks[ksq][(((pos.occupied_squares & Utils.BMasks[ksq]) * Utils.BMagics[ksq]) >> Utils.BShifts[ksq])]; checkSq[PieceTypeC.ROOK] = Utils.RAttacks[ksq][(((pos.occupied_squares & Utils.RMasks[ksq]) * Utils.RMagics[ksq]) >> Utils.RShifts[ksq])]; #else checkSq[PieceTypeC.BISHOP] = pos.attacks_from_BISHOP(ksq); checkSq[PieceTypeC.ROOK] = pos.attacks_from_ROOK(ksq); #endif checkSq[PieceTypeC.QUEEN] = checkSq[PieceTypeC.BISHOP] | checkSq[PieceTypeC.ROOK]; checkSq[PieceTypeC.KING] = 0; }
private static void generate_castle(CastlingSide Side, bool OnlyChecks, Position pos, MoveStack[] ms, ref int mpos, Color us) { if (pos.castle_impeded(us, Side) || (pos.can_castle_CR(Utils.make_castle_right(us, Side))==0) ) return; // After castling, the rook and king final positions are the same in Chess960 // as they would be in standard chess. Square kfrom = pos.king_square(us); Square rfrom = pos.castle_rook_square(us, Side); Square kto = Utils.relative_square(us, Side == CastlingSideC.KING_SIDE ? SquareC.SQ_G1 : SquareC.SQ_C1); Bitboard enemies = pos.pieces_C(us ^ 1); Debug.Assert(!pos.in_check()); for (Square s = Math.Min(kfrom, kto), e = Math.Max(kfrom, kto); s <= e; s++) if (s != kfrom // We are not in check && ((pos.attackers_to(s) & enemies) != 0)) return; // 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 (pos.chess960 && ((pos.attackers_to(kto, Utils.xor_bit(pos.occupied_squares, rfrom)) & enemies) != 0)) return; Move m = Utils.make_castle(kfrom, rfrom); if (OnlyChecks) { CheckInfo ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); bool givesCheck = pos.move_gives_check(m, ci); CheckInfoBroker.Free(); if (!givesCheck) return; } ms[mpos++].move = m; }
internal static int SetupStatePos = 0; // *SetupState = StateRingBuf; #endregion Fields #region Methods // go() is called when engine receives the "go" UCI command. The function sets // the thinking time and other parameters from the input string, and then starts // the main searching thread. internal static void go(Position pos, Stack<string> stack) { string token = string.Empty; LimitsType limits = new LimitsType(); List<Move> searchMoves = new List<Phase>(); while (stack.Count > 0) { token = stack.Pop(); if (token == "wtime") limits.time[ColorC.WHITE] = int.Parse(stack.Pop()); else if (token == "btime") limits.time[ColorC.BLACK] = int.Parse(stack.Pop()); else if (token == "winc") limits.inc[ColorC.WHITE] = int.Parse(stack.Pop()); else if (token == "binc") limits.inc[ColorC.BLACK] = int.Parse(stack.Pop()); else if (token == "movestogo") limits.movesToGo = int.Parse(stack.Pop()); else if (token == "depth") limits.depth = int.Parse(stack.Pop()); else if (token == "nodes") limits.nodes = int.Parse(stack.Pop()); else if (token == "movetime") limits.movetime = int.Parse(stack.Pop()); else if (token == "infinite") limits.infinite = 1; else if (token == "ponder") limits.ponder = true; else if (token == "searchmoves") while ((token = stack.Pop()) != null) { searchMoves.Add(Utils.move_from_uci(pos, token)); } } Threads.start_searching(pos, limits, searchMoves); }
/// PawnTable::evaluate_pawns() evaluates each pawn of the given color internal static Score evaluate_pawns(Color Us, Position pos, Bitboard ourPawns, Bitboard theirPawns, PawnEntry e) { Color Them = (Us == ColorC.WHITE ? ColorC.BLACK : ColorC.WHITE); Bitboard b; Square s; File f; Rank r; bool passed, isolated, doubled, opposed, chain, backward, candidate; Score value = ScoreC.SCORE_ZERO; Square[] pl = pos.pieceList[Us][PieceTypeC.PAWN]; int plPos = 0; // Loop through all pawns of the current color and score each pawn while ((s = pl[plPos++]) != SquareC.SQ_NONE) { Debug.Assert(pos.piece_on(s) == Utils.make_piece(Us, PieceTypeC.PAWN)); f = (s & 7); r = (s >> 3); // This file cannot be half open if (Us == ColorC.WHITE) { e.halfOpenFilesWHITE &= ~(1 << f); } else { e.halfOpenFilesBLACK &= ~(1 << f); } // Our rank plus previous one. Used for chain detection b = Utils.RankBB[r] | Utils.RankBB[Us == ColorC.WHITE ? r - 1 : r + 1]; // Flag the pawn as passed, isolated, doubled or member of a pawn // chain (but not the backward one). chain = (ourPawns & Utils.AdjacentFilesBB[f] & b) != 0; isolated = (ourPawns & Utils.AdjacentFilesBB[f]) == 0; doubled = (ourPawns & Utils.ForwardBB[Us][s]) != 0; opposed = (theirPawns & Utils.ForwardBB[Us][s]) != 0; passed = (theirPawns & Utils.PassedPawnMask[Us][s]) == 0; // Test for backward pawn backward = false; // If the pawn is passed, isolated, or member of a pawn chain it cannot // be backward. If there are friendly pawns behind on adjacent files // or if can capture an enemy pawn it cannot be backward either. if (!(passed | isolated | chain) && (((ourPawns & Utils.AttackSpanMask[Them][s])) == 0) && ((((Utils.StepAttacksBB[((Us << 3) | PieceTypeC.PAWN)][s]) & theirPawns)) == 0)) { // We now know that there are no friendly pawns beside or behind this // pawn on adjacent files. We now check whether the pawn is // backward by looking in the forward direction on the adjacent // files, and seeing whether we meet a friendly or an enemy pawn first. b = Utils.StepAttacksBB[((Us << 3) | PieceTypeC.PAWN)][s]; // Note that we are sure to find something because pawn is not passed // nor isolated, so loop is potentially infinite, but it isn't. while ((b & (ourPawns | theirPawns)) == 0) { if (Us == ColorC.WHITE) { b <<= 8; } else { b >>= 8; } } // The friendly pawn needs to be at least two ranks closer than the // enemy pawn in order to help the potentially backward pawn advance. backward = (((b | (Us == ColorC.WHITE ? b << 8 : b >> 8)) & theirPawns) != 0); } Debug.Assert(opposed | passed | (((Utils.AttackSpanMask[Us][s] & theirPawns)) != 0)); // A not passed pawn is a candidate to become passed if it is free to // advance and if the number of friendly pawns beside or behind this // pawn on adjacent files is higher or equal than the number of // enemy pawns in the forward direction on the adjacent files. candidate = !(opposed | passed | backward | isolated) && (b = Utils.AttackSpanMask[Them][s + (Us == ColorC.WHITE ? SquareC.DELTA_N : SquareC.DELTA_S)] & ourPawns) != 0 && Bitcount.popcount_1s_Max15(b) >= Bitcount.popcount_1s_Max15(Utils.AttackSpanMask[Us][s] & theirPawns); // Passed pawns will be properly scored in evaluation because we need // full attack info to evaluate passed pawns. Only the frontmost passed // pawn on each file is considered a true passed pawn. if (passed && !doubled) { if (Us == ColorC.WHITE) { e.passedPawnsWHITE |= Utils.SquareBB[s]; } else { e.passedPawnsBLACK |= Utils.SquareBB[s]; } } // Score this pawn if (isolated) value -= IsolatedPawnPenalty[opposed ? 1 : 0][f]; if (doubled) value -= DoubledPawnPenalty[opposed ? 1 : 0][f]; if (backward) value -= BackwardPawnPenalty[opposed ? 1 : 0][f]; if (chain) value += ChainBonus[f]; if (candidate) value += CandidateBonus[((s >> 3) ^ (Us * 7))]; } return value; }
/// PawnTable::pawn_info() takes a position object as input, computes /// a PawnInfo object, and returns a pointer to it. The result is also stored /// in an hash table, so we don't have to recompute everything when the same /// pawn structure occurs again. internal void probe(Position pos, out PawnEntry e) { Key key = pos.pawn_key(); e = entries[((UInt32)key) & Constants.PawnTableMask]; // If pi.key matches the position's pawn hash key, it means that we // have analysed this pawn structure before, and we can simply return // the information we found the last time instead of recomputing it. if (e.key == key) return; // Initialize PawnInfo entry e.key = key; e.passedPawnsWHITE = e.passedPawnsBLACK = 0; e.kingSquaresWHITE = e.kingSquaresBLACK = SquareC.SQ_NONE; e.halfOpenFilesWHITE = e.halfOpenFilesBLACK = 0xFF; // Calculate pawn attacks Bitboard wPawns = pos.pieces_PTC(PieceTypeC.PAWN, ColorC.WHITE); Bitboard bPawns = pos.pieces_PTC(PieceTypeC.PAWN, ColorC.BLACK); e.pawnAttacksWHITE = ((wPawns & ~Constants.FileHBB) << 9) | ((wPawns & ~Constants.FileABB) << 7); e.pawnAttacksBLACK = ((bPawns & ~Constants.FileHBB) >> 7) | ((bPawns & ~Constants.FileABB) >> 9); // Evaluate pawns for both colors and weight the result e.value = evaluate_pawns(ColorC.WHITE, pos, wPawns, bPawns, e) - evaluate_pawns(ColorC.BLACK, pos, bPawns, wPawns, e); e.value = Utils.apply_weight(e.value, PawnStructureWeight); return; }
internal Score update_safety_BLACK(Position pos, Square ksq) { kingSquaresBLACK = ksq; castleRightsBLACK = pos.st.castleRights & CastleRightC.BLACK_ANY; if (((ksq >> 3) ^ (ColorC.BLACK * 7)) > RankC.RANK_4) return kingSafetyBLACK = ScoreC.SCORE_ZERO; Value bonus = shelter_storm(ColorC.BLACK, pos, ksq); // If we can castle use the bonus after the castle if is bigger if ((pos.st.castleRights & CastleRightC.BLACK_OO) != 0) bonus = Math.Max(bonus, shelter_storm(ColorC.BLACK, pos, (SquareC.SQ_G1 ^ (ColorC.BLACK * 56)))); if ((pos.st.castleRights & CastleRightC.BLACK_OOO) != 0) bonus = Math.Max(bonus, shelter_storm(ColorC.BLACK, pos, (SquareC.SQ_C1 ^ (ColorC.BLACK * 56)))); return kingSafetyBLACK = (bonus << 16); }
/// PawnEntry::shelter_storm() calculates shelter and storm penalties for the file /// the king is on, as well as the two adjacent files. internal static Value shelter_storm(Color Us, Position pos, Square ksq) { Color Them = (Us == ColorC.WHITE ? ColorC.BLACK : ColorC.WHITE); Value safety = PawnTable.MaxSafetyBonus; Bitboard b = pos.byTypeBB[PieceTypeC.PAWN] & (Utils.InFrontBB[Us][ksq >> 3] | Utils.RankBB[ksq >> 3]); Bitboard ourPawns = b & pos.byColorBB[Us] & ~Utils.RankBB[ksq >> 3]; Bitboard theirPawns = b & pos.byColorBB[Them]; Rank rkUs, rkThem; File kf = ksq & 7; kf = (kf == FileC.FILE_A) ? kf + 1 : ((kf == FileC.FILE_H) ? kf - 1 : kf); for (int f = kf - 1; f <= kf + 1; f++) { // Shelter penalty is higher for the pawn in front of the king b = ourPawns & Utils.FileBB[f]; rkUs = (b != 0) ? ((Us == ColorC.WHITE ? Utils.first_1(b) : (Utils.last_1(b) ^ 56)) >> 3) : RankC.RANK_1; safety -= PawnTable.ShelterWeakness[f != kf ? 1 : 0][rkUs]; // Storm danger is smaller if enemy pawn is blocked b = theirPawns & Utils.FileBB[f]; rkThem = (b != 0) ? ((Us == ColorC.WHITE ? Utils.first_1(b) : (Utils.last_1(b) ^ 56)) >> 3) : RankC.RANK_1; safety -= PawnTable.StormDanger[rkThem == rkUs + 1 ? 1 : 0][rkThem]; } return safety; }
/// move_to_san() takes a position and a legal Move as input and returns its /// short algebraic notation representation. internal static string move_to_san(Position pos, int m) { if (m == MoveC.MOVE_NONE) { return "(none)"; } if (m == MoveC.MOVE_NULL) { return "(null)"; } Debug.Assert(pos.move_is_legal(m)); Bitboard others, b; Color us = pos.sideToMove; var san = new StringBuilder(); Square from = from_sq(m); Square to = to_sq(m); Piece pc = pos.piece_on(from); PieceType pt = type_of(pc); if (type_of_move(m) == MoveTypeC.CASTLING) { san.Append(to > from ? "O-O" : "O-O-O"); } else { if (pt != PieceTypeC.PAWN) { san.Append(PieceToChar[ColorC.WHITE][pt]); // Upper case // Disambiguation if we have more then one piece of type 'pt' that can // reach 'to' with a legal move. others = b = (pos.attacks_from_PS(pc, to) & pos.pieces_PTC(pt, us)) ^ (ulong)from; while (others != 0) { Move move = make_move(pop_lsb(ref b), to); if (!pos.pl_move_is_legal(move, pos.pinned_pieces())) { others ^= (ulong)from_sq(move); } } if (others != 0) { if ((others & file_bb_S(from)) == 0) { san.Append(file_to_char(file_of(from))); } else if ((others & rank_bb_S(from)) == 0) { san.Append(rank_to_char(rank_of(from))); } else { san.Append(square_to_string(from)); } } } else if (pos.is_capture(m)) { san.Append(file_to_char(file_of(from))); } if (pos.is_capture(m)) { san.Append('x'); } san.Append(square_to_string(to)); if (type_of_move(m) == MoveTypeC.PROMOTION) { san.Append('='); san.Append(PieceToChar[ColorC.WHITE][promotion_type(m)]); } } var ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); if (pos.move_gives_check(m, ci)) { var st = new StateInfo(); pos.do_move(m, st); var mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); san.Append(mlist.pos > 0 ? "+" : "#"); MListBroker.Free(); pos.undo_move(m); } CheckInfoBroker.Free(); return san.ToString(); }
/// move_from_uci() takes a position and a string representing a move in /// simple coordinate notation and returns an equivalent legal Move if any. internal static int move_from_uci(Position pos, string str) { var strLowerPromotion = (str.Length == 5 ? str.Substring(0, 4) + str.Substring(4).ToLowerInvariant() : str); var mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); for (var i = 0; i < mlist.pos; i++) { if (strLowerPromotion == move_to_uci(mlist.moves[i].move, pos.chess960)) { var retval = mlist.moves[i].move; MListBroker.Free(); return retval; } } MListBroker.Free(); return MoveC.MOVE_NONE; }
internal static bool is_KXK(int Us, Position pos) { var Them = (Us == ColorC.WHITE ? ColorC.BLACK : ColorC.WHITE); return pos.non_pawn_material(Them) == ValueC.VALUE_ZERO && pos.piece_count(Them, PieceTypeC.PAWN) == 0 && pos.non_pawn_material(Us) >= Constants.RookValueMidgame; }
internal static Position GetObject() { int slotID = System.Threading.Thread.CurrentThread.ManagedThreadId & Constants.BROKER_SLOT_MASK; if (_cnt[slotID] == _pool[slotID].Length) { int poolLength = _pool[slotID].Length; Position[] temp = new Position[poolLength + Constants.BrokerCapacity]; Array.Copy(_pool[slotID], temp, poolLength); for (int i = 0; i < Constants.BrokerCapacity; i++) { temp[poolLength + i] = new Position(); } _pool[slotID] = temp; } return _pool[slotID][_cnt[slotID]++]; }
/// Book::probe() tries to find a book move for the given position. If no move /// is found returns MOVE_NONE. If pickBest is true returns always the highest /// rated move, otherwise randomly chooses one, based on the move score. internal static Move probe(Position pos, string filename, bool pickBest) { #if PORTABLE #region DLL book if (bookNotExists) { return MoveC.MOVE_NONE; } BookEntry e = new BookEntry(); UInt16 best = 0; uint sum = 0; Move move = MoveC.MOVE_NONE; UInt64 key = book_key(pos); try { System.Reflection.Assembly bookAssembly = System.Reflection.Assembly.Load("PortfishBook"); using (Stream fs = bookAssembly.GetManifestResourceStream("PortfishBook.book.bin")) { UInt64 size = (UInt64)(fs.Length / SIZE_OF_BOOKENTRY); using (BinaryReader br = new BinaryReader(fs)) { binary_search(key, size, br); while (Read(ref e, br) && (e.key == key)) { best = Math.Max(best, e.count); sum += e.count; // Choose book move according to its score. If a move has a very // high score it has higher probability to be choosen than a move // with lower score. Note that first entry is always chosen. if ((RKiss.rand() % sum < e.count) || (pickBest && e.count == best)) { move = e.move; } } } } } catch(System.IO.FileNotFoundException) { bookNotExists = true; return MoveC.MOVE_NONE; } #endregion #else #region File system read if (!System.IO.File.Exists(filename)) return MoveC.MOVE_NONE; BookEntry e = new BookEntry(); UInt16 best = 0; uint sum = 0; Move move = MoveC.MOVE_NONE; UInt64 key = book_key(pos); using (FileStream fs = new FileStream(filename, FileMode.Open)) { UInt64 size = (UInt64)(fs.Length / SIZE_OF_BOOKENTRY); using (BinaryReader br = new BinaryReader(fs)) { binary_search(key, size, br); while (Read(ref e, br) && (e.key == key)) { best = Math.Max(best, e.count); sum += e.count; // Choose book move according to its score. If a move has a very // high score it has higher probability to be choosen than a move // with lower score. Note that first entry is always chosen. if (((sum!=0) && (RKiss.rand() % sum < e.count)) || (pickBest && e.count == best)) { move = e.move; } } br.Close(); } fs.Close(); } #endregion #endif if (move != 0) { // A PolyGlot book move is encoded as follows: // // bit 0- 5: destination square (from 0 to 63) // bit 6-11: origin square (from 0 to 63) // bit 12-14: promotion piece (from KNIGHT == 1 to QUEEN == 4) // // Castling moves follow "king captures rook" representation. So in case book // move is a promotion we have to convert to our representation, in all the // other cases we can directly compare with a Move after having masked out // the special Move's flags (bit 14-15) that are not supported by PolyGlot. int pt = (move >> 12) & 7; if (pt != 0) { move = Utils.make_promotion(Utils.from_sq(move), Utils.to_sq(move), (pt + 1)); } // Add 'special move' flags and verify it is legal MList mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); for (int i = 0; i < mlist.pos; i++) { if (move == (mlist.moves[i].move & 0x3FFF)) { Move retval = mlist.moves[i].move; MListBroker.Free(); return retval; } } MListBroker.Free(); } return MoveC.MOVE_NONE; }
// split() does the actual work of distributing the work at a node between // several available threads. If it does not succeed in splitting the node // (because no idle threads are available, or because we have no unused split // point objects), the function immediately returns. If splitting is possible, a // SplitPoint object is initialized with all the data that must be copied to the // helper threads and then helper threads are told that they have been assigned // work. This will cause them to instantly leave their idle loops and call // search(). When all threads have returned from search() then split() returns. internal static Value split(bool Fake, Position pos, Stack[] ss, int ssPos, Value alpha, Value beta, Value bestValue, ref Move bestMove, Depth depth, Move threatMove, int moveCount, MovePicker mp, int nodeType) { Debug.Assert(pos.pos_is_ok()); Debug.Assert(bestValue > -ValueC.VALUE_INFINITE); Debug.Assert(bestValue <= alpha); Debug.Assert(alpha < beta); Debug.Assert(beta <= ValueC.VALUE_INFINITE); Debug.Assert(depth > DepthC.DEPTH_ZERO); Thread master = pos.this_thread(); if (master.splitPointsCnt >= Constants.MAX_SPLITPOINTS_PER_THREAD) return bestValue; // Pick the next available split point from the split point stack SplitPoint sp = master.splitPoints[master.splitPointsCnt]; sp.parent = master.curSplitPoint; sp.master = master; sp.cutoff = false; sp.slavesMask = 1UL << master.idx; #if ACTIVE_REPARENT sp.allSlavesRunning = true; #endif sp.depth = depth; sp.bestMove = bestMove; sp.threatMove = threatMove; sp.alpha = alpha; sp.beta = beta; sp.nodeType = nodeType; sp.bestValue = bestValue; sp.mp = mp; sp.moveCount = moveCount; sp.pos = pos; sp.nodes = 0; sp.ss = ss; sp.ssPos = ssPos; Debug.Assert(master.is_searching); master.curSplitPoint = sp; int slavesCnt = 0; ThreadHelper.lock_grab(sp.Lock); ThreadHelper.lock_grab(splitLock); for (int i = 0; i < size() && !Fake; ++i) if (threads[i].is_available_to(master)) { sp.slavesMask |= 1UL << i; threads[i].curSplitPoint = sp; threads[i].is_searching = true; // Slave leaves idle_loop() if (useSleepingThreads) threads[i].wake_up(); if (++slavesCnt + 1 >= maxThreadsPerSplitPoint) // Master is always included break; } master.splitPointsCnt++; ThreadHelper.lock_release(splitLock); ThreadHelper.lock_release(sp.Lock); // Everything is set up. The master thread enters the idle loop, from which // it will instantly launch a search, because its is_searching flag is set. // We pass the split point as a parameter to the idle loop, which means that // the thread will return from the idle loop when all slaves have finished // their work at this split point. if (slavesCnt != 0 || Fake) { master.idle_loop(sp, null); // In helpful master concept a master can help only a sub-tree of its split // point, and because here is all finished is not possible master is booked. Debug.Assert(!master.is_searching); } // We have returned from the idle loop, which means that all threads are // finished. Note that setting is_searching and decreasing activeSplitPoints is // done under lock protection to avoid a race with Thread::is_available_to(). ThreadHelper.lock_grab(sp.Lock); // To protect sp->nodes ThreadHelper.lock_grab(splitLock); master.is_searching = true; master.splitPointsCnt--; master.curSplitPoint = sp.parent; pos.nodes += sp.nodes; bestMove = sp.bestMove; ThreadHelper.lock_release(splitLock); ThreadHelper.lock_release(sp.Lock); return sp.bestValue; }
/// MaterialTable::material_info() takes a position object as input, /// computes or looks up a MaterialInfo object, and returns a pointer to it. /// If the material configuration is not already present in the table, it /// is stored there, so we don't have to recompute everything when the /// same material configuration occurs again. internal void probe(Position pos, out MaterialEntry e) { var key = pos.material_key(); e = this.entries[((uint)key) & Constants.MaterialTableMask]; // If mi->key matches the position's material hash key, it means that we // have analysed this material configuration before, and we can simply // return the information we found the last time instead of recomputing it. if (e.key == key) { return; } // Initialize MaterialInfo entry var npm = pos.non_pawn_material(ColorC.WHITE) + pos.non_pawn_material(ColorC.BLACK); e.value = 0; e.scalingFunctionWHITE = null; e.scalingFunctionBLACK = null; e.spaceWeight = 0; e.key = key; e.factorWHITE = e.factorBLACK = ScaleFactorC.SCALE_FACTOR_NORMAL; e.gamePhase = npm >= MidgameLimit ? PhaseC.PHASE_MIDGAME : npm <= EndgameLimit ? PhaseC.PHASE_ENDGAME : (((npm - EndgameLimit) * 128) / (MidgameLimit - EndgameLimit)); // Let's look if we have a specialized evaluation function for this // particular material configuration. First we look for a fixed // configuration one, then a generic one if previous search failed. if ((e.evaluationFunction = Endgame.probeValue(key, out e.evaluationFunctionColor)) != null) { return; } if (is_KXK(ColorC.WHITE, pos)) { e.evaluationFunction = Endgame.Endgame_KXK; e.evaluationFunctionColor = ColorC.WHITE; return; } if (is_KXK(ColorC.BLACK, pos)) { e.evaluationFunction = Endgame.Endgame_KXK; e.evaluationFunctionColor = ColorC.BLACK; return; } if ((pos.pieces_PT(PieceTypeC.PAWN) == 0) && (pos.pieces_PT(PieceTypeC.ROOK) == 0) && (pos.pieces_PT(PieceTypeC.QUEEN) == 0)) { // Minor piece endgame with at least one minor piece per side and // no pawns. Note that the case KmmK is already handled by KXK. Debug.Assert( (pos.pieces_PTC(PieceTypeC.KNIGHT, ColorC.WHITE) | pos.pieces_PTC(PieceTypeC.BISHOP, ColorC.WHITE)) != 0); Debug.Assert( (pos.pieces_PTC(PieceTypeC.KNIGHT, ColorC.BLACK) | pos.pieces_PTC(PieceTypeC.BISHOP, ColorC.BLACK)) != 0); if (pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP) + pos.piece_count(ColorC.WHITE, PieceTypeC.KNIGHT) <= 2 && pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP) + pos.piece_count(ColorC.BLACK, PieceTypeC.KNIGHT) <= 2) { e.evaluationFunction = Endgame.Endgame_KmmKm; e.evaluationFunctionColor = pos.sideToMove; return; } } // OK, we didn't find any special evaluation function for the current // material configuration. Is there a suitable scaling function? // // We face problems when there are several conflicting applicable // scaling functions and we need to decide which one to use. EndgameScaleFactor sf; int c; if ((sf = Endgame.probeScaleFactor(key, out c)) != null) { if (c == ColorC.WHITE) { e.scalingFunctionWHITE = sf; } else { e.scalingFunctionBLACK = sf; } return; } // Generic scaling functions that refer to more then one material // distribution. Should be probed after the specialized ones. // Note that these ones don't return after setting the function. if (is_KBPsKs(ColorC.WHITE, pos)) { e.scalingFunctionWHITE = Endgame.Endgame_KBPsK; } if (is_KBPsKs(ColorC.BLACK, pos)) { e.scalingFunctionBLACK = Endgame.Endgame_KBPsK; } if (is_KQKRPs(ColorC.WHITE, pos)) { e.scalingFunctionWHITE = Endgame.Endgame_KQKRPs; } else if (is_KQKRPs(ColorC.BLACK, pos)) { e.scalingFunctionBLACK = Endgame.Endgame_KQKRPs; } var npm_w = pos.non_pawn_material(ColorC.WHITE); var npm_b = pos.non_pawn_material(ColorC.BLACK); if (npm_w + npm_b == ValueC.VALUE_ZERO) { if (pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN) == 0) { Debug.Assert(pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN) >= 2); e.scalingFunctionWHITE = Endgame.Endgame_KPsK; } else if (pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN) == 0) { Debug.Assert(pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN) >= 2); e.scalingFunctionBLACK = Endgame.Endgame_KPsK; } else if (pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN) == 1 && pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN) == 1) { // This is a special case because we set scaling functions // for both colors instead of only one. e.scalingFunctionWHITE = Endgame.Endgame_KPKP; e.scalingFunctionBLACK = Endgame.Endgame_KPKP; } } // No pawns makes it difficult to win, even with a material advantage if (pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN) == 0 && npm_w - npm_b <= Constants.BishopValueMidgame) { e.factorWHITE = (byte) (npm_w == npm_b || npm_w < Constants.RookValueMidgame ? 0 : NoPawnsSF[Math.Min(pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP), 2)]); } if (pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN) == 0 && npm_b - npm_w <= Constants.BishopValueMidgame) { e.factorBLACK = (byte) (npm_w == npm_b || npm_b < Constants.RookValueMidgame ? 0 : NoPawnsSF[Math.Min(pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP), 2)]); } // Compute the space weight if (npm_w + npm_b >= 2 * Constants.QueenValueMidgame + 4 * Constants.RookValueMidgame + 2 * Constants.KnightValueMidgame) { var minorPieceCount = pos.piece_count(ColorC.WHITE, PieceTypeC.KNIGHT) + pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP) + pos.piece_count(ColorC.BLACK, PieceTypeC.KNIGHT) + pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP); e.spaceWeight = minorPieceCount * minorPieceCount; } // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder // for the bishop pair "extended piece", this allow us to be more flexible // in defining bishop pair bonuses. this.pieceCount[0][0] = pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP) > 1 ? 1 : 0; this.pieceCount[0][1] = pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN); this.pieceCount[0][2] = pos.piece_count(ColorC.WHITE, PieceTypeC.KNIGHT); this.pieceCount[0][3] = pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP); this.pieceCount[0][4] = pos.piece_count(ColorC.WHITE, PieceTypeC.ROOK); this.pieceCount[0][5] = pos.piece_count(ColorC.WHITE, PieceTypeC.QUEEN); this.pieceCount[1][0] = pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP) > 1 ? 1 : 0; this.pieceCount[1][1] = pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN); this.pieceCount[1][2] = pos.piece_count(ColorC.BLACK, PieceTypeC.KNIGHT); this.pieceCount[1][3] = pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP); this.pieceCount[1][4] = pos.piece_count(ColorC.BLACK, PieceTypeC.ROOK); this.pieceCount[1][5] = pos.piece_count(ColorC.BLACK, PieceTypeC.QUEEN); e.value = (short)((this.imbalance(ColorC.WHITE) - this.imbalance(ColorC.BLACK)) / 16); }
internal static bool is_KQKRPs(int Us, Position pos) { var Them = (Us == ColorC.WHITE ? ColorC.BLACK : ColorC.WHITE); return pos.piece_count(Us, PieceTypeC.PAWN) == 0 && pos.non_pawn_material(Us) == Constants.QueenValueMidgame && pos.piece_count(Us, PieceTypeC.QUEEN) == 1 && pos.piece_count(Them, PieceTypeC.ROOK) == 1 && pos.piece_count(Them, PieceTypeC.PAWN) >= 1; }
internal static bool is_KBPsKs(int Us, Position pos) { return pos.non_pawn_material(Us) == Constants.BishopValueMidgame && pos.piece_count(Us, PieceTypeC.BISHOP) == 1 && pos.piece_count(Us, PieceTypeC.PAWN) >= 1; }
internal Score king_safety(Color Us, Position pos, Square ksq) { if (Us == ColorC.WHITE) { return kingSquaresWHITE == ksq && castleRightsWHITE == (pos.st.castleRights & (CastleRightC.WHITE_ANY)) ? kingSafetyWHITE : update_safety_WHITE(pos, ksq); } else { return kingSquaresBLACK == ksq && castleRightsBLACK == (pos.st.castleRights & (CastleRightC.WHITE_ANY << 2)) ? kingSafetyBLACK : update_safety_BLACK(pos, ksq); } }
/// benchmark() runs a simple benchmark by letting Stockfish analyze a set /// of positions for a given limit each. There are five parameters; the /// transposition table size, the number of search threads that should /// be used, the limit value spent for each position (optional, default is /// depth 12), an optional file name where to look for positions in fen /// format (defaults are the positions defined above) and the type of the /// limit value: depth (default), time in secs or number of nodes. internal static void benchmark(Position current, Stack<string> stack) { List<string> fens = new List<string>(); LimitsType limits = new LimitsType(); Int64 nodes = 0; Int64 nodesAll = 0; long e = 0; long eAll = 0; // Assign default values to missing arguments string ttSize = (stack.Count > 0) ? (stack.Pop()) : "128"; string threads = (stack.Count > 0) ? (stack.Pop()) : "1"; string limit = (stack.Count > 0) ? (stack.Pop()) : "12"; string fenFile = (stack.Count > 0) ? (stack.Pop()) : "default"; string limitType = (stack.Count > 0) ? (stack.Pop()) : "depth"; OptionMap.Instance["Hash"].v = ttSize; OptionMap.Instance["Threads"].v = threads; TT.clear(); if (limitType == "time") limits.movetime = 1000 * int.Parse(limit); // maxTime is in ms else if (limitType == "nodes") limits.nodes = int.Parse(limit); else limits.depth = int.Parse(limit); if (fenFile == "default") { fens.AddRange(Defaults); } else if (fenFile == "current") { fens.Add(current.to_fen()); } else { #if PORTABLE throw new Exception("File cannot be read."); #else System.IO.StreamReader sr = new System.IO.StreamReader(fenFile, true); string fensFromFile = sr.ReadToEnd(); sr.Close(); sr.Dispose(); string[] split = fensFromFile.Replace("\r", "").Split('\n'); foreach (string fen in split) { if (fen.Trim().Length > 0) { fens.Add(fen.Trim()); } } #endif } Stopwatch time = new Stopwatch(); long[] res = new long[fens.Count]; for (int i = 0; i < fens.Count; i++) { time.Reset(); time.Start(); Position pos = new Position(fens[i], bool.Parse(OptionMap.Instance["UCI_Chess960"].v), Threads.main_thread()); Plug.Write("\nPosition: "); Plug.Write((i + 1).ToString()); Plug.Write("/"); Plug.Write(fens.Count.ToString()); Plug.Write(Constants.endl); if (limitType == "perft") { Int64 cnt = Search.perft(pos, limits.depth * DepthC.ONE_PLY); Plug.Write("\nPerft "); Plug.Write(limits.depth.ToString()); Plug.Write(" leaf nodes: "); Plug.Write(cnt.ToString()); Plug.Write(Constants.endl); nodes = cnt; } else { Threads.start_searching(pos, limits, new List<Move>()); Threads.wait_for_search_finished(); nodes = Search.RootPosition.nodes; res[i] = nodes; } e = time.ElapsedMilliseconds; nodesAll += nodes; eAll += e; Plug.Write("\n==========================="); Plug.Write("\nTotal time (ms) : "); Plug.Write(e.ToString()); Plug.Write("\nNodes searched : "); Plug.Write(nodes.ToString()); Plug.Write("\nNodes/second : "); Plug.Write(((int)(nodes / (e / 1000.0))).ToString()); Plug.Write(Constants.endl); } Plug.Write("\n==========================="); Plug.Write("\nTotal time (ms) : "); Plug.Write(eAll.ToString()); Plug.Write("\nNodes searched : "); Plug.Write(nodesAll.ToString()); Plug.Write("\nNodes/second : "); Plug.Write(((int)(nodesAll / (eAll / 1000.0))).ToString()); Plug.Write(Constants.endl); //for (int i = 0; i < res.Length; i++) //{ // Plug.Write(string.Format("{0}: {1}", i, res[i])); // Plug.Write(Constants.endl); //} }
// ThreadsManager::start_searching() wakes up the main thread sleeping in // main_loop() so to start a new search, then returns immediately. internal static void start_searching(Position pos, LimitsType limits, List<Move> searchMoves) { wait_for_search_finished(); Search.SearchTime.Reset(); Search.SearchTime.Start(); // As early as possible Search.SignalsStopOnPonderhit = Search.SignalsFirstRootMove = false; Search.SignalsStop = Search.SignalsFailedLowAtRoot = false; Search.RootPosition.copy(pos); Search.Limits = limits; Search.RootMoves.Clear(); MList mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); for (int i = 0; i < mlist.pos; i++) { Move move = mlist.moves[i].move; if ((searchMoves.Count == 0) || Utils.existSearchMove(searchMoves, move)) { Search.RootMoves.Add(new RootMove(move)); } } MListBroker.Free(); main_thread().do_sleep = false; main_thread().wake_up(); }
internal static void init() { for (int i = 0; i < Constants.BROKER_SLOTS; i++) { _pool[i] = new Position[0]; } }
/// Wait for a command from the user, parse this text string as an UCI command, /// and call the appropriate functions. Also intercepts EOF from stdin to ensure /// that we exit gracefully if the GUI dies unexpectedly. In addition to the UCI /// commands, the function also supports a few debug commands. internal static void uci_loop(string args) { for (int i = 0; i < 102; i++) { StateRingBuf[i] = new StateInfo(); } Position pos = new Position(StartFEN, false, Threads.main_thread()); // The root position string cmd, token = string.Empty; while (token != "quit") { if (args.Length > 0) { cmd = args; } else if (String.IsNullOrEmpty(cmd = Plug.ReadLine())) // Block here waiting for input { cmd = "quit"; } Stack<string> stack = Utils.CreateStack(cmd); token = stack.Pop(); if (token == "quit" || token == "stop") { Search.SignalsStop = true; Threads.wait_for_search_finished(); // Cannot quit while threads are running } else if (token == "ponderhit") { // The opponent has played the expected move. GUI sends "ponderhit" if // we were told to ponder on the same move the opponent has played. We // should continue searching but switching from pondering to normal search. Search.Limits.ponder = false; if (Search.SignalsStopOnPonderhit) { Search.SignalsStop = true; Threads.main_thread().wake_up(); // Could be sleeping } } else if (token == "go") { go(pos, stack); } else if (token == "ucinewgame") { /* Avoid returning "Unknown command" */ } else if (token == "isready") { Plug.Write("readyok"); Plug.Write(Constants.endl); } else if (token == "position") { set_position(pos, stack); } else if (token == "setoption") { set_option(stack); } else if (token == "validmoves") { Search.validmoves(pos, stack); } else if (token == "d") { pos.print(0); } else if (token == "flip") { pos.flip(); } else if (token == "eval") { Plug.Write(Evaluate.trace(pos)); Plug.Write(Constants.endl); } else if (token == "bench") { Benchmark.benchmark(pos, stack); } else if (token == "key") { Plug.Write("key: "); Plug.Write(String.Format("{0:X}", pos.key())); Plug.Write("\nmaterial key: "); Plug.Write(pos.material_key().ToString()); Plug.Write("\npawn key: "); Plug.Write(pos.pawn_key().ToString()); Plug.Write(Constants.endl); } else if (token == "uci") { Plug.Write("id name "); Plug.Write(Utils.engine_info(true)); Plug.Write("\n"); Plug.Write(OptionMap.Instance.ToString()); Plug.Write("\nuciok"); Plug.Write(Constants.endl); } else if (token == "perft") { token = stack.Pop(); // Read depth Stack<string> ss = Utils.CreateStack( string.Format("{0} {1} {2} current perft", OptionMap.Instance["Hash"].v, OptionMap.Instance["Threads"].v, token) ); Benchmark.benchmark(pos, ss); } else { Plug.Write("Unknown command: "); Plug.Write(cmd); Plug.Write(Constants.endl); } if (args.Length > 0) // Command line arguments have one-shot behaviour { Threads.wait_for_search_finished(); break; } } }
// book_key() returns the PolyGlot hash key of the given position private static UInt64 book_key(Position pos) { UInt64 key = 0; Bitboard b = pos.occupied_squares; while (b != 0) { // Piece offset is at 64 * polyPiece where polyPiece is defined as: // BP = 0, WP = 1, BN = 2, WN = 3, ... BK = 10, WK = 11 Square s = Utils.pop_1st_bit(ref b); Piece p = pos.piece_on(s); int polyPiece = 2 * (Utils.type_of(p) - 1) + (Utils.color_of(p) == ColorC.WHITE ? 1 : 0); key ^= PolyGlotRandoms[ZobPieceOffset + (64 * polyPiece + s)]; } b = (ulong)pos.can_castle_CR(CastleRightC.ALL_CASTLES); while (b != 0) key ^= PolyGlotRandoms[ZobCastleOffset + Utils.pop_1st_bit(ref b)]; if (pos.st.epSquare != SquareC.SQ_NONE) key ^= PolyGlotRandoms[ZobEnPassantOffset + Utils.file_of(pos.st.epSquare)]; if (pos.sideToMove == ColorC.WHITE) key ^= PolyGlotRandoms[ZobTurnOffset + 0]; return key; }
// set_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 following move list ("moves"). internal static void set_position(Position pos, Stack<string> stack) { Move m; string token, fen = string.Empty; token = stack.Pop(); if (token == "startpos") { fen = StartFEN; if (stack.Count > 0) { token = stack.Pop(); } // Consume "moves" token if any } else if (token == "fen") while ((stack.Count > 0) && (token = stack.Pop()) != "moves") fen += token + " "; else return; pos.from_fen(fen, bool.Parse(OptionMap.Instance["UCI_Chess960"].v), Threads.main_thread()); // Parse move list (if any) while ((stack.Count > 0) && (m = Utils.move_from_uci(pos, token = stack.Pop())) != MoveC.MOVE_NONE) { pos.do_move(m, StateRingBuf[SetupStatePos]); // Increment pointer to StateRingBuf circular buffer SetupStatePos = (SetupStatePos + 1) % 102; } }