public void Init(MoveGen.Move move, Sprite sprite) { transform.localScale = Vector3.one; GetComponent<Image>().sprite = sprite; GetComponent<RectTransform>().anchoredPosition = new Vector2(200, 0); _move = move; _move.Used += _move_Used; }
private void NewMoveGenerated(MoveGen.Move move) { var arrow = Instantiate(Prefab); arrow.transform.SetParent(transform, true); arrow.Init(move, ArrowsSprites[((int)move.Direction)]); }
private int AlphaBetaMin(int alpha, int beta, int depthLeft, int initialDepth) { if (depthLeft == 0) { int score = QuiescenceMin(alpha, beta, 0); return(score); } numNodes++; MoveGen.numNodes++; ulong currentKey = board.key; //draw by repetition if (initialDepth != depthLeft && AddPositionW(currentKey)) { return(0); } //null move pruning if (depthLeft == initialDepth - 1 && !board.InCheck && !board.endGame) { //try null move board.MakeMove(-3, 0); int score = AlphaBetaMax(alpha, beta, 0, initialDepth); board.ReverseMove(-3, 0); //if null move causes cutoff, its assumed another move will also if (score <= alpha) { RemPositionW(currentKey); return(alpha); } if (score < beta) { beta = score; } } //set up PV int PV = 0; int nodeBestMove = 0; int nodeBestScore = 0; if (ttpvb.ContainsKey(currentKey)) { PV = ttpvb[currentKey]; } foreach (int move in MoveGen.GenerateMove(board, false, PV)) { //no PV if (move == 0) { continue; } int captured = board.MakeMove(move, 0); int score = AlphaBetaMax(alpha, beta, depthLeft - 1, initialDepth); board.ReverseMove(move, captured); if (nodeBestMove == 0 || nodeBestScore > score) { nodeBestMove = move; nodeBestScore = score; } if (score <= alpha) { if (initialDepth == depthLeft) { bestMove = move; } else { RemPositionW(currentKey); } UpdatePVB(currentKey, nodeBestMove); if (score == int.MinValue) { return(score); } return(alpha); } if (score < beta) { if (initialDepth == depthLeft) { inWindow = true; bestMove = move; } beta = score; } } if (initialDepth != depthLeft) { RemPositionW(currentKey); } UpdatePVB(currentKey, nodeBestMove); //no legal moves if (nodeBestMove == 0) { if (board.InCheck) { return(int.MaxValue); } else { return(0); } } if (nodeBestScore == int.MaxValue) { return(int.MaxValue); } return(beta); }
// Use this for initialization void Start() { _input = GetComponent<PlayerInput>(); animator = GetComponent<Animator>(); _generator = GetComponent<MoveGen>(); }
private static string moveTostring(Position pos, Move move, bool ulongForm, MoveGen.MoveList moves) { string ret = ""; int wKingOrigPos = Position.getSquare(4, 0); int bKingOrigPos = Position.getSquare(4, 7); if (move.from == wKingOrigPos && pos.getPiece(wKingOrigPos) == Piece.WKING) { // Check white castle if (move.to == Position.getSquare(6, 0)) { ret += ("O-O"); } else if (move.to == Position.getSquare(2, 0)) { ret += ("O-O-O"); } } else if (move.from == bKingOrigPos && pos.getPiece(bKingOrigPos) == Piece.BKING) { // Check white castle if (move.to == Position.getSquare(6, 7)) { ret += ("O-O"); } else if (move.to == Position.getSquare(2, 7)) { ret += ("O-O-O"); } } if (ret.Length == 0) { int p = pos.getPiece(move.from); ret += pieceToChar(p); int x1 = Position.getX(move.from); int y1 = Position.getY(move.from); int x2 = Position.getX(move.to); int y2 = Position.getY(move.to); if (ulongForm) { ret += ((char)(x1 + 'a')); ret += ((char)(y1 + '1')); ret += (isCapture(pos, move) ? 'x' : '-'); } else { if (p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) { if (isCapture(pos, move)) { ret += ((char)(x1 + 'a')); } } else { int numSameTarget = 0; int numSameFile = 0; int numSameRow = 0; for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; if (m == null) { break; } if ((pos.getPiece(m.from) == p) && (m.to == move.to)) { numSameTarget++; if (Position.getX(m.from) == x1) { numSameFile++; } if (Position.getY(m.from) == y1) { numSameRow++; } } } if (numSameTarget < 2) { // No file/row info needed } else if (numSameFile < 2) { // Only file info needed ret += ((char)(x1 + 'a')); } else if (numSameRow < 2) { // Only row info needed ret += ((char)(y1 + '1')); } else { // File and row info needed ret += ((char)(x1 + 'a')); ret += ((char)(y1 + '1')); } } if (isCapture(pos, move)) { ret += ('x'); } } ret += ((char)(x2 + 'a')); ret += ((char)(y2 + '1')); if (move.promoteTo != Piece.EMPTY) { ret += (pieceToChar(move.promoteTo)); } } UndoInfo ui = new UndoInfo(); if (MoveGen.givesCheck(pos, move)) { pos.makeMove(move, ui); MoveGen MG = new MoveGen(); MoveGen.MoveList nextMoves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, nextMoves); if (nextMoves.size == 0) { ret += ('#'); } else { ret += ('+'); } pos.unMakeMove(move, ui); } return(ret); }
// executes quiescence search public static int QSearch(int alpha, int beta, int player) { // initialize variables int value = SMALL_NUM, bestValue = SMALL_NUM; List <ChessMove> moves = new List <ChessMove>(0); if (player != AI.board.myColor) { bestValue = LARGE_NUM; } // check to see if we have time left if (AI.timer.ElapsedMilliseconds > TIME_PER_MOVE) { AI.TIME_EXPIRED = true; return(Score.TIME_EXPIRED_SCORE); } // check for draw by insufficient material if (Score.IsDrawByInsufficientMaterial()) { return(Score.DRAW_SCORE); } // update evaluated node count AI.nodes++; // generate all capturing and promoting moves (all unquiet moves) moves = MoveGen.GenerateMoves(player, true); // iterate through generated moves for (int i = 0; i < moves.Count; i++) { // make current move moves[i].DoMove(MAKE); // if current move is illegal, unmake move; continue otherwise if (!MoveGen.IsKingAttacked(player)) { // get value of next quiescence search recursion value = QSearch(alpha, beta, AI.GetOtherPlayer(player)); // check to see if time has expired if (value == Score.TIME_EXPIRED_SCORE) { return(Score.TIME_EXPIRED_SCORE); } // unmake current move moves[i].DoMove(UNMAKE); // evaluate quiescence search value if (player == AI.board.myColor) // maximize { if (value >= beta) // fail-high, prune { return(value + 1); } if (value > alpha) // set new alpha { alpha = value; } if (value > bestValue) // set new best action, best value { bestValue = value; } } else if (player == AI.board.oppColor) // minimize { if (value <= alpha) // fail-low, prune { return(value - 1); } if (value < beta) // set new beta { beta = value; } if (value < bestValue) // set new best action, best value { bestValue = value; } } } else { moves[i].DoMove(UNMAKE); } } // if no capturing or promoting moves for this state, set best value to node's heuristic value if (value == SMALL_NUM) { bestValue = Score.Evaluate(AI.board.myColor); } // return best value from the current depth return(bestValue); }
/// <summary> /// Search all capture positions, to help avoid the Horizon effect. /// </summary> private int Quiescence(int alpha, int beta, ref S_SearchInfo info) { Debug.Assert(BoardOperations.CheckBoard(board)); if ((info.Nodes & 2047) == 0) { CheckUp(ref info); } info.Nodes++; // If position is a draw. if ((IsRepetition() || board.FiftyMoves >= 100) && board.Ply != 0) { return(0); } int score = Evaluate.Position(board); // Stand_pat. if (board.Ply > Variables.MAX_DEPTH - 1) { return(score); } if (score >= beta) { return(beta); } if (score > alpha) { alpha = score; } MoveList list = new MoveList(); MoveGen.GenerateAllMoves(board, list, true); // Only capture moves int oldAlpha = alpha; score = -infinite; int legal = 0; // Will increment when we find a legal move. int bestMove = Variables.NO_MOVE; int PvMove = PvTable.Probe(board); for (int i = 0; i < list.Count; ++i) { PickNextMove(i, list); var move = list.Moves[i].Move; if (!MakeMove.Make_Move(board, move)) { continue; } legal++; score = -Quiescence(-beta, -alpha, ref info); MakeMove.TakeMove(board); // Take back the made move. if (info.Stopped) { return(0); } // We have a new alpha or beta cutoff. if (score > alpha) { bool isCaptureMove = (move & MoveOperations.MoveFlagCapture) != 0; // beta cutoff? if (score >= beta) { if (legal == 1) { info.Fhf++; // We searched the best move first. } info.Fh++; return(beta); } // Alpha cutoff alpha = score; bestMove = move; } } if (alpha != oldAlpha) { PvTable.StoreMove(board, bestMove); } return(alpha); }
/** * Handle a special command. * @param moveStr The command to handle * @return True if command handled, false otherwise. */ public bool handleCommand(string moveStr) { if (moveStr == "new") { moveList = new List <Move>(); uiInfoList = new List <UndoInfo>(); drawOfferList = new List <bool>(); currentMove = 0; pendingDrawOffer = false; drawState = GameState.ALIVE; resignState = GameState.ALIVE; try { pos = TextIO.readFEN(TextIO.startPosFEN); } catch { throw new RuntimeException(); } whitePlayer.clearTT(); blackPlayer.clearTT(); activateHumanPlayer(); return(true); } else if (moveStr == "undo") { if (currentMove > 0) { pos.unMakeMove(moveList[currentMove - 1], uiInfoList[currentMove - 1]); currentMove--; pendingDrawOffer = false; drawState = GameState.ALIVE; resignState = GameState.ALIVE; return(handleCommand("swap")); } else { SystemHelper.println("Nothing to undo"); } return(true); } else if (moveStr == "redo") { if (currentMove < moveList.Count) { pos.makeMove(moveList[currentMove], uiInfoList[currentMove]); currentMove++; pendingDrawOffer = false; return(handleCommand("swap")); } else { SystemHelper.println("Nothing to redo"); } return(true); } else if ((moveStr == "swap") || (moveStr == "go")) { Player tmp = whitePlayer; whitePlayer = blackPlayer; blackPlayer = tmp; return(true); } else if (moveStr == "list") { listMoves(); return(true); } else if (moveStr.StartsWith("setpos ")) { string fen = moveStr.Substring(moveStr.IndexOf(" ") + 1); Position newPos = null; try { newPos = TextIO.readFEN(fen); } catch { SystemHelper.println("Invalid FEN: " + fen); } if (newPos != null) { handleCommand("new"); pos = newPos; activateHumanPlayer(); } return(true); } else if (moveStr == "getpos") { string fen = TextIO.toFEN(pos); SystemHelper.println(fen); return(true); } else if (moveStr.StartsWith("draw ")) { if (getGameState() == GameState.ALIVE) { string drawCmd = moveStr.Substring(moveStr.IndexOf(" ") + 1); return(handleDrawCmd(drawCmd)); } else { return(true); } } else if (moveStr == "resign") { if (getGameState() == GameState.ALIVE) { resignState = pos.whiteMove ? GameState.RESIGN_WHITE : GameState.RESIGN_BLACK; return(true); } else { return(true); } } else if (moveStr.StartsWith("book")) { string bookCmd = moveStr.Substring(moveStr.IndexOf(" ") + 1); return(handleBookCmd(bookCmd)); } else if (moveStr.StartsWith("time")) { try { string timeStr = moveStr.Substring(moveStr.IndexOf(" ") + 1); int timeLimit = int.Parse(timeStr); whitePlayer.timeLimit(timeLimit, timeLimit, false); blackPlayer.timeLimit(timeLimit, timeLimit, false); return(true); } catch { SystemHelper.println("Number format exception: " + moveStr); return(false); } } else if (moveStr.StartsWith("perft ")) { try { string depthStr = moveStr.Substring(moveStr.IndexOf(" ") + 1); int depth = int.Parse(depthStr); MoveGen moveGen = new MoveGen(); long t0 = SystemHelper.currentTimeMillis(); ulong nodes = perfT(moveGen, pos, depth); long t1 = SystemHelper.currentTimeMillis(); SystemHelper.println("perft(" + depth.ToString() + ") = " + nodes.ToString() + " t=" + ((t1 - t0) / 1000).ToString() + "s"); } catch { SystemHelper.println("Number format exception: " + moveStr); return(false); } return(true); } else { return(false); } }
/// <summary> /// Negamax alpha beta recursion. /// </summary> public int AlphaBeta(int alpha, int beta, int depth) { if (depth == 0) { return(Quiescence(alpha, beta, 63)); //Try to find calm position } NodesEvaluated++; //If there is repetition if (board.IsRepetition() || board.FiftyMove >= 100) { return(0); } //Check time CheckTime(); //Have we run of time yet? if (RunOutOfTime) { return(0); } int OldAlpha = alpha; int BestMove = 0; int Score = -5000000; int MadeALegalMove = 0; int add = depth * Defs.MaxMoves; int num = MoveGen.GenerateMoves(board, Moves, add, depth) + add; int move; //Score the move from previous search with the highest score - will be searched first int PvMove = board.ProbePVTable(); if (PvMove != 0) { for (int i = add; i < num; i++) { if (Moves[i].move == PvMove) { Moves[i].score = 2000000; } } } for (int i = add; i < num; i++) { PickBestMove(i, num); move = Moves[i].move; board.MakeMove(move); if (!board.MoveWasIllegal()) { MadeALegalMove++; Score = -AlphaBeta(-beta, -alpha, depth - 1); board.UndoMove(); if (Score > alpha) { if (Score >= beta) { if (MadeALegalMove == 1) { FailHighFirst++; } FailHigh++; if (!move.IsCapture()) { //Not a capturing move board.SearchKillers[1][depth] = board.SearchKillers[0][depth]; board.SearchKillers[0][depth] = move; } return(beta); } if (!move.IsCapture()) { //Not a capturing move board.SearchHistory[board.pieces[move.GetFrom()]][move.GetTo()] += depth; } alpha = Score; BestMove = move; } } else { board.UndoMove(); } } //Check if mate if (MadeALegalMove == 0) { if (board.IsAttacked(board.BKing, 1)) { //Mate if ((int)board.SideToPlay == 0) //Black { return(-32767 - depth); } return(32767 + depth); } else if (board.IsAttacked(board.WKing, 0)) { //Mate if ((int)board.SideToPlay == 0) //Black { return(32767 + depth); } return(-32767 - depth); } else { //Stale mate return(0); } } //Store move if (alpha != OldAlpha) { board.StorePVMove(BestMove); } return(alpha); }
static string TestRandomGames(State state) { // run random game, check FEN var r = new Random(1234); // make repeatable for (int game = 0; game < 100; ++game) { state.Reset(); for (int moveCount = 1; moveCount < 300; ++moveCount) { var moves1 = MoveGen.GenMoves(state); if (!moves1.Any()) { break; } // check can do/undo all var fs = state.ToFEN(); foreach (var m in moves1) { state.DoMove(m); state.UndoMove(); var fe = state.ToFEN(); if (fe != fs) { return($"FEN mismatch trying move {m} \n{fs} != \n{fe}"); } } var move = moves1[r.Next(moves1.Count)]; System.Console.WriteLine($"{(moveCount + 1) / 2}: {move:5}"); // move do/undo checks var f1 = state.ToFEN(); state.DoMove(move); var f2 = state.ToFEN(); System.Console.WriteLine(f2); state.UndoMove(); var f3 = state.ToFEN(); state.DoMove(move); var f4 = state.ToFEN(); if (f1 != f3) { return($"FEN mismatch move {moveCount} : {move} \n{f1} != \n{f3}"); } if (f2 != f4) { return($"FEN mismatch move {moveCount} : {move} \n{f2} != \n{f4}"); } #if true // test FEN parsing State s2; if (!State.TryParseFEN(f1, out s2)) { return($"FEN parse failed {f1}"); } var f5 = s2.ToFEN(); if (f1 != f5) { return($"FEN parse mismatch \n{f1} != \n{f5}"); } var moves2 = MoveGen.GenMoves(s2); var mt1 = MoveGen.MoveListToText(moves1); var mt2 = MoveGen.MoveListToText(moves2); if (mt1 != mt2) { return($"Move compare failed \n{mt1} != \n{mt2}\n{f1}==\n{f5}"); } #endif if (move.checkmate) { Chess.Draw(state); } } } return(""); }
// test perf counts from various positions public static bool PerfTests() { #if false // for chasing down bugs. Compare to stockfish command line is useful State state2; State.TryParseFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", out state2); State.TryParseFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/5Q1p/PPPBBPPP/RN2K2R b KQkq - 0 1", out state2); State.TryParseFEN("r3k2r/p1ppqpb1/1n2pnp1/1b1PN3/1p2P3/5Q1p/PPPBBPPP/RN2K2R w KQkq - 0 1", out state2); //var mm = MoveGen.GenMoves(state2); MoveGen.Perft(state2, 2, true); return(true); #endif var regex = new Regex(@"^D(\d+) (\d+)"); var(errors, successes) = (0, 0); var filename = "data/perft.txt"; foreach (var line1 in File.ReadLines(filename)) { var line = line1; if (string.IsNullOrEmpty(line)) { continue; } var index = line.IndexOf('#'); if (index != -1) { line = line.Substring(0, index); } if (string.IsNullOrEmpty(line)) { continue; } var words = line.Split(';', Int32.MaxValue, StringSplitOptions.RemoveEmptyEntries); if (words.Length > 1 && State.TryParseFEN(words[0], out var state)) { for (var i = 1; i < words.Length; ++i) { var m = regex.Match(words[i].Trim()); if ( m.Success && Int32.TryParse(m.Groups[1].Value, out var depth) && UInt64.TryParse(m.Groups[2].Value, out var count) ) { if (depth > 5) { break; // todo - allow } var pinfo = PerftCount(state, depth); var tested = pinfo.count; if (tested != count) { Console.WriteLine($"ERROR: Me {tested} != Truth {count} depth {depth} FEN {words[0]}"); ++errors; } else { Console.WriteLine($"OK: {tested} == {count} depth {depth} FEN {words[0]}"); ++successes; } State.TryParseFEN(words[0], out state); } } } } Console.WriteLine($"Perft testing {successes} successes, {errors} errors"); return(errors == 0); }
public static void Test1() { Piece p = Piece.GOLD; Console.WriteLine(p.Pretty()); Console.WriteLine(p.ToUsi()); Piece p2 = Shogi.Core.Util.MakePiecePromote(Color.WHITE, p); Console.WriteLine(p2.ToUsi()); #if false // Squareのテスト Square sq = Square.SQ_56; //Console.WriteLine(sq.ToFile().ToUSI() + sq.ToRank().ToUSI()); Console.WriteLine(sq.ToUsi()); Console.WriteLine(sq.Pretty()); #endif Move m = Shogi.Core.Util.MakeMove(Square.SQ_56, Square.SQ_45); Console.WriteLine(m.ToUsi()); Move m2 = Shogi.Core.Util.MakeMoveDrop(Piece.SILVER, Square.SQ_45); Console.WriteLine(m2.ToUsi()); Move m3 = Shogi.Core.Util.MakeMovePromote(Square.SQ_84, Square.SQ_83); Console.WriteLine(m3.ToUsi()); Move m4 = Shogi.Core.Util.FromUsiMove("8h2b+"); Console.WriteLine(m4.Pretty()); Move m5 = Shogi.Core.Util.FromUsiMove("G*3b"); Console.WriteLine(m5.Pretty()); Move m6 = Shogi.Core.Util.FromUsiMove("7g7f"); Hand h = Hand.ZERO; h.Add(Piece.PAWN, 5); h.Add(Piece.KNIGHT, 1); Console.WriteLine(h.Pretty()); Console.WriteLine(h.ToUsi(Color.BLACK)); Console.WriteLine(h.ToUsi(Color.WHITE)); var pos = new Position(); //pos.UsiPositionCmd("startpos moves 7g7f 3c3d 8h3c+"); pos.InitBoard(BoardType.NoHandicap); MoveGen.GenTest(pos); pos.UsiPositionCmd("sfen lnsgkgsnl/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves 5a6b 7g7f 3a3b"); Console.WriteLine(pos.Pretty()); #if false // UndoMove()のテスト pos.UndoMove(); Console.WriteLine(pos.Pretty()); pos.UndoMove(); Console.WriteLine(pos.Pretty()); pos.UndoMove(); Console.WriteLine(pos.Pretty()); Console.WriteLine(pos.PrettyPieceNo()); #endif // 駒番号のテスト //Console.WriteLine(pos.PrettyPieceNo()); // Console.WriteLine(pos.Pieces().Pretty()); // Console.WriteLine(pos.Pieces(Color.BLACK).Pretty()); #if false // Bitboard(Square)のテスト for (Square sq = Square.ZERO; sq < Square.NB; ++sq) { Console.WriteLine("sq = " + sq.Pretty()); Bitboard b = new Bitboard(sq); Console.WriteLine(b.Pretty()); } #endif #if false // 角・馬の利きのテスト Bitboard occupied = new Bitboard(Square.SQ_33); Console.WriteLine(occupied.Pretty()); Bitboard bb = Bitboard.BishopEffect(Square.SQ_55, occupied); Console.WriteLine(bb.Pretty()); Bitboard bb2 = Bitboard.BishopStepEffect(Square.SQ_56); Console.WriteLine(bb2.Pretty()); Bitboard bb3 = Bitboard.BishopEffect(Square.SQ_56, Bitboard.AllBB()); Console.WriteLine(bb3.Pretty()); Bitboard bb4 = Bitboard.HorseEffect(Square.SQ_55, occupied); Console.WriteLine(bb4.Pretty()); #endif #if false // 飛車・龍の利きのテスト Bitboard occupied = new Bitboard(Square.SQ_53); Console.WriteLine(occupied.Pretty()); Bitboard bb = Bitboard.RookEffect(Square.SQ_55, occupied); Console.WriteLine(bb.Pretty()); Bitboard bb2 = Bitboard.RookStepEffect(Square.SQ_56); Console.WriteLine(bb2.Pretty()); Bitboard bb3 = Bitboard.RookEffect(Square.SQ_56, Bitboard.AllBB()); Console.WriteLine(bb3.Pretty()); Bitboard bb4 = Bitboard.DragonEffect(Square.SQ_55, occupied); Console.WriteLine(bb4.Pretty()); #endif #if false // 香りの利きのテスト Bitboard occupied = new Bitboard(Square.SQ_53); Bitboard bb = Bitboard.LanceEffect(Color.BLACK, Square.SQ_55, occupied); Console.WriteLine(bb.Pretty()); Bitboard bb3 = Bitboard.LanceStepEffect(Color.BLACK, Square.SQ_56); Console.WriteLine(bb3.Pretty()); #endif #if false // 歩、桂、銀、金、玉の利きのテスト Bitboard bb = Bitboard.PawnEffect(Color.BLACK, Square.SQ_55); Console.WriteLine(bb.Pretty()); Bitboard bb2 = Bitboard.KnightEffect(Color.BLACK, Square.SQ_55); Console.WriteLine(bb2.Pretty()); Bitboard bb3 = Bitboard.SilverEffect(Color.BLACK, Square.SQ_55); Console.WriteLine(bb3.Pretty()); Bitboard bb4 = Bitboard.GoldEffect(Color.BLACK, Square.SQ_55); Console.WriteLine(bb4.Pretty()); Bitboard bb5 = Bitboard.KingEffect(Square.SQ_55); Console.WriteLine(bb5.Pretty()); #endif #if false // EffectsFrom()のテスト var bb = Bitboard.EffectsFrom(Piece.W_DRAGON, Square.SQ_54, pos.Pieces()); Console.WriteLine(bb.Pretty()); var bb2 = Bitboard.EffectsFrom(Piece.W_GOLD, Square.SQ_54, pos.Pieces()); Console.WriteLine(bb2.Pretty()); #endif #if false // BitboardのPop()のテスト for (Square sq = Square.ZERO; sq < Square.NB; ++sq) { Console.Write("sq = " + sq.Pretty() + " "); Bitboard b = new Bitboard(sq); Square r = b.Pop(); Console.WriteLine(r.Pretty()); } #endif #if false // 駒落ちの局面のテスト pos.InitBoard(BoardType.Handicap2); // 2枚落ち Console.WriteLine(pos.Pretty()); pos.InitBoard(BoardType.Handicap10); // 10枚落ち Console.WriteLine(pos.Pretty()); #endif #if false pos.SetSfen(Position.SFEN_HIRATE); Console.WriteLine(pos.ToSfen()); Console.WriteLine(pos.Pretty()); pos.DoMove(m6); Console.WriteLine(pos.Pretty()); #endif #if false // sfen化して、setしてhash keyが変わらないかのテスト //pos.SetSfen(pos.ToSfen()); //Console.WriteLine(pos.Pretty()); #endif #if false // 指し手生成祭りの局面 pos.SetSfen("l6nl/5+P1gk/2np1S3/p1p4Pp/3P2Sp1/1PPb2P1P/P5GS1/R8/LN4bKL w RGgsn5p 1"); Console.WriteLine(pos.ToSfen()); Console.WriteLine(pos.Pretty()); #endif #if false // 駒番号のデバッグ Console.WriteLine(pos.PrettyPieceNo()); // Console.WriteLine(pos.Pieces().Pretty()); // Console.WriteLine(pos.Pieces(Color.BLACK).Pretty()); #endif #if false // BitweenBB()のテスト var bb = Bitboard.BetweenBB(Square.SQ_77, Square.SQ_33); Console.WriteLine(bb.Pretty()); var bb2 = Bitboard.BetweenBB(Square.SQ_58, Square.SQ_52); Console.WriteLine(bb2.Pretty()); #endif #if false // 乱数テスト var rand = new PRNG(1234); Console.WriteLine(rand.Rand()); Console.WriteLine(rand.Rand()); Console.WriteLine(rand.Rand()); var key_side = Zobrist.Side; Console.WriteLine(key_side.ToString()); #endif #if false // serialization test var csa = new Model.CsaConnectData(); var serializer = new DataContractJsonSerializer(typeof(Model.CsaConnectData)); var ms = new MemoryStream(); serializer.WriteObject(ms, csa); var json = Encoding.UTF8.GetString(ms.ToArray()); MessageBox.Show(json); #endif #if false // UsiPositionCmdのテスト。非合法手が混じっているパターン var pos2 = new Position(); try { pos2.UsiPositionCmd("startpos moves 7g7f 3c4d 2g2f "); // 2手目が非合法手 } catch (Exception e) { Console.WriteLine(e.Message); } #endif #if false { // 千日手の引き分けのテスト var pos2 = new Position(); pos.UsiPositionCmd("startpos moves"); var moves = new[] { Util.MakeMove(Square.SQ_59, Square.SQ_58), Util.MakeMove(Square.SQ_51, Square.SQ_52), Util.MakeMove(Square.SQ_58, Square.SQ_59), Util.MakeMove(Square.SQ_52, Square.SQ_51), }; int ply = 0; for (int j = 0; j < 5; ++j) { for (int i = 0; i < moves.Length; ++i) { pos.DoMove(moves[i]); var rep = pos.IsRepetition(); ply++; Console.WriteLine(string.Format("ply = {0} , rep = {1} ", ply, rep.ToString())); // 16手目を指した局面(17手目)が先手番で千日手引き分けの局面になるはず } } } #endif #if false { // 連続王手の千日手の負けのテスト var pos2 = new Position(); pos.UsiPositionCmd("startpos moves 7g7f 5c5d 8h3c 5a6b"); var moves = new[] { Util.MakeMove(Square.SQ_33, Square.SQ_44), Util.MakeMove(Square.SQ_62, Square.SQ_51), Util.MakeMove(Square.SQ_44, Square.SQ_33), Util.MakeMove(Square.SQ_51, Square.SQ_62), }; int ply = 4; for (int j = 0; j < 5; ++j) { for (int i = 0; i < moves.Length; ++i) { pos.DoMove(moves[i]); //Console.WriteLine(pos.Pretty()); //Console.WriteLine(pos.State().checkersBB.Pretty()); var rep = pos.IsRepetition(); ply++; Console.WriteLine(string.Format("ply = {0} , rep = {1} ", ply, rep.ToString())); // 19手目の局面(を指した直後の局面=20手目,後手番)で、ここで後手勝ちになるはず。 } } } #endif #if false // UndoMove()でcapture,promoteが戻るかのテスト var pos2 = new Position(); pos2.UsiPositionCmd("startpos moves 7g7f 3c3d 8h2b+"); Console.WriteLine(pos2.Pretty()); pos2.UndoMove(); Console.WriteLine(pos2.Pretty()); pos2.UndoMove(); Console.WriteLine(pos2.Pretty()); pos2.UndoMove(); Console.WriteLine(pos2.Pretty()); Console.WriteLine(pos2.PrettyPieceNo()); #endif #if true { // -- 局面の合法性のチェック // 平手 pos.SetSfen("lnsgkgsnl/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); // 先手2歩 pos.SetSfen("lnsgkgsnl/9/9/P8/9/9/P8/1B5R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); // 後手2歩 pos.SetSfen("lnsgkgsnl/9/8p/8p/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); // 玉なし pos.SetSfen("lnsg1gsnl/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSG1GSNL b - 1"); Console.WriteLine(pos.IsValid()); // 片玉 pos.SetSfen("lnsg1gsnl/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); // 片玉(詰将棋時) これは合法 pos.SetSfen("lnsg1gsnl/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1"); Console.WriteLine("詰将棋片玉 : " + pos.IsValid(true)); // 先手玉が2枚 pos.SetSfen("lnsgKgsnl/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); // 玉3枚 pos.SetSfen("lnsgkksnl/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); // 非手番側に王手 pos.SetSfen("lnsgkgsnl/9/ppppppBpp/9/9/9/PPPPPPPPP/25R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); // 行き場の無い駒 pos.SetSfen("Lnsgkgsnl/9/pppppp1pp/9/9/9/PPPPPPPPP/25R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); pos.SetSfen("l1sgkgsnl/N8/pppppp1pp/9/9/9/PPPPPPPPP/25R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); pos.SetSfen("P1sgkgsnl/9/pppppp1pp/9/9/9/9/25R1/LNSGKGSNL b - 1"); Console.WriteLine(pos.IsValid()); pos.SetSfen("2sgkgsnl/9/9/9/9/9/9/25R1/LNSGKGSNp b - 1"); Console.WriteLine(pos.IsValid()); } #endif }
//This function is called each time it is your turn //Return true to end your turn, return false to ask the server for updated information public override bool run() { // Print out the current board state Console.WriteLine("+---+---+---+---+---+---+---+---+"); for (int rank = 8; rank > 0; rank--) { Console.Write("|"); for (int file = 1; file <= 8; file++) { bool found = false; // Loops through all of the pieces for (int p = 0; !found && p < pieces.Length; p++) { // determines if that piece is at the current rank and file if (pieces[p].getRank() == rank && pieces[p].getFile() == file) { found = true; // Checks if the piece is black if (pieces[p].getOwner() == 1) { Console.Write("*"); } else { Console.Write(" "); } // prints the piece's type Console.Write((char)pieces[p].getType() + " "); } } if (!found) { Console.Write(" "); } Console.Write("|"); } Console.WriteLine("\n+---+---+---+---+---+---+---+---+"); } // Looks through information about the players for (int p = 0; p < players.Length; p++) { Console.Write(players[p].getPlayerName()); // if playerID is 0, you're white, if its 1, you're black if (players[p].getId() == myID) { Console.Write(" (ME)"); // update timeRemaining timeRemaining = players[p].getTime(); } Console.WriteLine(" time remaining: " + players[p].getTime()); } // if there has been a move, print the most recent move if (moves.Length > 0) { Console.Write("Last Move Was: "); Console.WriteLine(files[moves[0].getFromFile() - 1] + "" + moves[0].getFromRank() + "-" + files[moves[0].getToFile() - 1] + "" + moves[0].getToRank()); } ///////////////////////////////////// // <-- END OF STOCK AI.cs CODE --> // ///////////////////////////////////// // print current move number Console.WriteLine("\nMove " + turnNumber().ToString("D3") + "\n========\n"); // add to GameState List and update ChessBoard if (moves.Length <= 1) { board = new ChessBoard(ref pieces, myID); states.Add(new GameState(null, null)); } else { ChessMove lastMove = ChessMove.GetChessMove(moves[0].getFromFile(), moves[0].getToFile(), moves[0].getFromRank(), moves[0].getToRank(), moves[0].getPromoteType(), states[states.Count - 1].enPassant); board = new ChessBoard(ref pieces, myID); states.Add(new GameState(states[states.Count - 1], lastMove)); } // display current score information for player Console.Write("Score for "); if (myID == ChessBoard.WHITE) { Console.WriteLine("WHITE:\n"); } else if (myID == ChessBoard.BLACK) { Console.WriteLine("BLACK:\n"); } int material = Score.GetMaterialScore(myID); int position = Score.GetPositionScore(myID); // int mobility = Score.GetPositionScore(myID); int pawn_structure = Score.GetPawnStructureScore(myID); int king_safety = Score.GetKingSafetyScore(myID); Console.WriteLine("Net Material = " + material); Console.WriteLine("Net Position = " + position); //Console.WriteLine("Net Mobility = " + mobility); Console.WriteLine("Net Pawn Structure = " + pawn_structure); Console.WriteLine("Net King Safety = " + king_safety + "\n"); Console.WriteLine("Overall Score = " + (material + position + /*mobility +*/ pawn_structure + king_safety) + "\n"); // if playing as human, get move from console prompt while (HUMAN_PLAYER) { // get legal moves for this position List <ChessMove> legalMoves = MoveGen.GenerateMoves(myID, false); // prompt user for move Console.Write("Enter a move ([from] [to] <promotion type>): "); string[] humanMove = Console.ReadLine().Split(' '); // get origin square int humanFromFile = 0, humanFromRank = 0; for (int i = 0; i < 8; i++) { if (humanMove[0][0] == files[i]) { humanFromFile = i + 1; break; } } humanFromRank = (int)Char.GetNumericValue(humanMove[0][1]); // get destination square int humanToFile = 0, humanToRank = 0; for (int i = 0; i < 8; i++) { if (humanMove[1][0] == files[i]) { humanToFile = i + 1; break; } } humanToRank = (int)Char.GetNumericValue(humanMove[1][1]); // if promotion type is specified, get the promotion piece from move int humanPromote = 0; if (humanMove.Length > 2) { humanPromote = (int)humanMove[2][0]; } // check for legality of human move bool isLegal = false; for (int i = 0; i < legalMoves.Count; i++) { ChessMove m = legalMoves[i]; if ((ChessMove.GetFile(m.GetFromSq()) + 1) == (uint)humanFromFile && (ChessMove.GetRank(m.GetFromSq()) + 1) == (uint)humanFromRank && (ChessMove.GetFile(m.GetToSq()) + 1) == (uint)humanToFile && (ChessMove.GetRank(m.GetToSq()) + 1) == (uint)humanToRank) { isLegal = true; break; } } // if move is legal, make move if (isLegal) { // get Piece associated with move Piece humanPiece = pieces[FindPiece(humanFromFile, humanFromRank)]; // make move humanPiece.move(humanToFile, humanToRank, humanPromote); return(true); } else if (!isLegal) { Console.WriteLine("ILLEGAL MOVE. Please input a legal move.\n"); } } // reset TIME_EXPIRED and timer TIME_EXPIRED = false; timer.Reset(); // reset history table history = new HistoryTable(); // run ABMiniMax int moveScore = 0, n = 0, toFile = -1, toRank = -1; uint fromSq = 0, toSq = 0, thePiece = 0; depth = 0; List <ChessMove> completeBestMoves = new List <ChessMove>(0); Search.UpdateTimePerMove(moves.Length); timer.Start(); while (!TIME_EXPIRED) { depth += 1; nodes = 0; Search.MAX_DEPTH = depth; Search.NULLMOVE_ALLOWED = true; Search.FOLLOW_PV = true; Search.PV = new List <ChessMove>(0); int score = Search.PVABMiniMax(0, Search.SMALL_NUM, Search.LARGE_NUM); if (score != Score.TIME_EXPIRED_SCORE) { moveScore = score; completeBestMoves = new List <ChessMove>(Search.PV); } // select random move from bestMoves List if (completeBestMoves.Count > 0) { n = generator.Next(0, completeBestMoves.Count - 1); // get bestMove info fromSq = completeBestMoves[n].GetFromSq(); thePiece = completeBestMoves[n].GetPiece(); toSq = completeBestMoves[n].GetToSq(); toFile = (int)((toSq % 8) + 1); toRank = (int)((toSq / 8) + 1); // print bestMove info Console.WriteLine("Best Move: " + completeBestMoves[n].GetMoveString() + ", Score: " + moveScore + ", Depth: " + depth + " (t = " + (timer.ElapsedMilliseconds / 1000.0).ToString("F3") + "s, nodes = " + nodes + ")"); } // if checkmate is found, stop searching if (score == Score.CHECKMATE_WIN_SCORE) { break; } } timer.Stop(); // output number of best moves Console.WriteLine("completeBestMoves = " + completeBestMoves.Count); // make bestMove pieces[FindPiece(fromSq, thePiece)].move(toFile, toRank, completeBestMoves[n].GetPromoteType()); // update ChessBoard and GameState List completeBestMoves[n].DoMove(Search.MAKE); return(true); }
/// <summary> /// Convert a chess move string to a Move object. Any prefix of the string representation of a valid move counts as a legal move string, as ulong as the string only matches one valid move. /// </summary> /// <param name="pos"></param> /// <param name="strMove"></param> /// <returns></returns> public static Move stringToMove(Position pos, string strMove) { strMove = strMove.Replace("=", ""); Move move = null; if (strMove.Length == 0) { return(move); } MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); { char lastChar = strMove[strMove.Length - 1]; if ((lastChar == '#') || (lastChar == '+')) { MoveGen.MoveList subMoves = new MoveGen.MoveList(); int len = 0; for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; string str1 = TextIO.moveTostring(pos, m, true, moves); if (str1[str1.Length - 1] == lastChar) { subMoves.m[len++] = m; } } subMoves.size = len; moves = subMoves; strMove = normalizeMovestring(strMove); } } for (int i = 0; i < 2; i++) { // Search for full match for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; string str1 = normalizeMovestring(TextIO.moveTostring(pos, m, true, moves)); string str2 = normalizeMovestring(TextIO.moveTostring(pos, m, false, moves)); if (i == 0) { if ((str1 == strMove) || (str2 == strMove)) { return(m); } } else { if ((strMove.ToLower() == str1.ToLower()) || (strMove.ToLower() == str2.ToLower())) { return(m); } } } } for (int i = 0; i < 2; i++) { // Search for unique substring match for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; string str1 = normalizeMovestring(TextIO.moveTostring(pos, m, true)); string str2 = normalizeMovestring(TextIO.moveTostring(pos, m, false)); bool match; if (i == 0) { match = (str1.StartsWith(strMove) || str2.StartsWith(strMove)); } else { match = (str1.ToLower().StartsWith(strMove.ToLower()) || str2.ToLower().StartsWith(strMove.ToLower())); } if (match) { if (move != null) { // More than one match, not ok return(null); } else { move = m; } } } if (move != null) { return(move); } } return(move); }
// returns the position score for the specified color public static int GetPositionScore(int player) { // initialize variables int white_position = 0, black_position = 0, net_position = 0; uint white_king_sq = MoveGen.GetNextSquare(AI.board.white_king); uint black_king_sq = MoveGen.GetNextSquare(AI.board.black_king); // loop through game AI.board squares and calculate position/distance scores for each piece for (int i = 0; i < 64; i++) { if (AI.board.theBoard[i] == ChessBoard.EMPTY) { continue; // square is empty } if (ChessBoard.GetPieceColor(AI.board.theBoard[i]) == ChessBoard.WHITE) { switch (AI.board.theBoard[i]) { case ChessBoard.W_PAWN: white_position += PAWN_POSITION[i]; white_position += PAWN_OPPONENT_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(black_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(black_king_sq)))]; white_position += PAWN_OWN_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(white_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(white_king_sq)))]; break; case ChessBoard.W_ROOK: white_position += ROOK_POSITION[i]; white_position += ROOK_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(black_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(black_king_sq)))]; break; case ChessBoard.W_KNIGHT: white_position += KNIGHT_POSITION[i]; white_position += KNIGHT_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(black_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(black_king_sq)))]; break; case ChessBoard.W_BISHOP: white_position += BISHOP_POSITION[i]; white_position += BISHOP_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(black_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(black_king_sq)))]; break; case ChessBoard.W_QUEEN: white_position += QUEEN_POSITION[i]; white_position += QUEEN_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(black_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(black_king_sq)))]; break; case ChessBoard.W_KING: if (IsEndGame(GetMaterialScore(ChessBoard.WHITE), GetMaterialScore(ChessBoard.BLACK))) { white_position += KING_ENDGAME_POSITION[i]; } else { white_position += KING_POSITION[i]; } break; } } else if (ChessBoard.GetPieceColor(AI.board.theBoard[i]) == ChessBoard.BLACK) { switch (AI.board.theBoard[i]) { case ChessBoard.B_PAWN: black_position += PAWN_POSITION[MIRROR[i]]; black_position += PAWN_OPPONENT_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(white_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(white_king_sq)))]; black_position += PAWN_OWN_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(black_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(black_king_sq)))]; break; case ChessBoard.B_ROOK: black_position += ROOK_POSITION[MIRROR[i]]; black_position += ROOK_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(white_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(white_king_sq)))]; break; case ChessBoard.B_KNIGHT: black_position += KNIGHT_POSITION[MIRROR[i]]; black_position += KNIGHT_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(white_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(white_king_sq)))]; break; case ChessBoard.B_BISHOP: black_position += BISHOP_POSITION[MIRROR[i]]; black_position += BISHOP_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(white_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(white_king_sq)))]; break; case ChessBoard.B_QUEEN: black_position += QUEEN_POSITION[MIRROR[i]]; black_position += QUEEN_DISTANCE[Math.Min(Math.Abs((int)ChessMove.GetFile((uint)i) - (int)ChessMove.GetFile(white_king_sq)), Math.Abs((int)ChessMove.GetRank((uint)i) - (int)ChessMove.GetRank(white_king_sq)))]; break; case ChessBoard.B_KING: if (IsEndGame(GetMaterialScore(ChessBoard.WHITE), GetMaterialScore(ChessBoard.BLACK))) { black_position += KING_ENDGAME_POSITION[MIRROR[i]]; } else { black_position += KING_POSITION[MIRROR[i]]; } break; } } } // calculate net position score if (player == ChessBoard.WHITE) { net_position += white_position - black_position; } else if (player == ChessBoard.BLACK) { net_position += black_position - white_position; } // return position score return(net_position); }
/// <summary> /// Searches only capturing moves. /// </summary> public int Quiescence(int alpha, int beta, int depth) { int Score = (Evaluation.Evaluate(board) * ((board.SideToPlay * 2) - 1)); QNodes++; CheckTime(); if (RunOutOfTime) { return(0); } if (Score >= beta) { return(beta); } if (Score > alpha) { alpha = Score; } if (depth <= 21) //Should not happen, but it's here anyway { return(Score); } int MadeALegalMove = 0; Score = -5000000; int add = depth * Defs.MaxMoves; int num = MoveGen.GenerateCapturingMoves(board, Moves, add) + add; //Generates only capturing moves int move; for (int i = add; i < num; i++) { PickBestMove(i, num); move = Moves[i].move; board.MakeMove(move); if (!board.MoveWasIllegal()) { MadeALegalMove++; Score = -Quiescence(-beta, -alpha, depth - 1); if (Score > alpha) { if (Score >= beta) { if (MadeALegalMove == 1) { FailHighFirst++; } FailHigh++; board.UndoMove(); return(beta); } alpha = Score; } } board.UndoMove(); } return(alpha); }
// returns the pawn structure score for the specified color public static int GetPawnStructureScore(int player) { // initialize variables int white_pawn_structure = 0, black_pawn_structure = 0, net_pawn_structure = 0; UInt64 white_pawns = AI.board.white_pawn, black_pawns = AI.board.black_pawn; // calculate white pawn structure score while (white_pawns != ChessBoard.EMPTY_BOARD) { // get square of next pawn uint square = MoveGen.GetNextSquare(white_pawns); // check to see if pawn is a passed pawn if ((W_PASSED_PAWN[square] & AI.board.black_pawn) == ChessBoard.EMPTY_BOARD) { white_pawn_structure += BONUS_PASSED_PAWN; // check to see if rook is behind a passed pawn if ((MoveGen.GetRookMoves(square, AI.board.occupied_squares, AI.board.occupied_squares) ^ AI.board.white_rook) != ChessBoard.EMPTY_BOARD) { white_pawn_structure += BONUS_ROOK_BEHIND_PASSED_PAWN; } } // check to see if pawn is a doubled pawn if (((ChessBoard.UP_MOVES[square] | ChessBoard.DOWN_MOVES[square]) ^ ChessBoard.SQUARES[square]) != 0) { white_pawn_structure += PENALTY_DOUBLED_PAWN; } // check to see if pawn is an isolated pawn (if not isolated, check to see if pawn is a backward pawn) if ((W_ISOLATED_PAWN[square] & AI.board.white_pawn) == ChessBoard.EMPTY_BOARD) { white_pawn_structure += PENALTY_ISOLATED_PAWN; } else { if (((ChessBoard.PAWN_CAPTURES[square + 8] & AI.board.black_pawn) != ChessBoard.EMPTY_BOARD) && (W_BACKWARD_PAWN[square] & AI.board.white_pawn) == ChessBoard.EMPTY_BOARD) { white_pawn_structure += PENALTY_BACKWARD_PAWN; } } // remove current pawn from white pawns BitBoard white_pawns ^= ChessBoard.SQUARES[square]; } // calculate black pawn structure score while (black_pawns != ChessBoard.EMPTY_BOARD) { // get square of next pawn uint square = MoveGen.GetNextSquare(black_pawns); // check to see if pawn is a passed pawn if ((B_PASSED_PAWN[square] & AI.board.white_pawn) == ChessBoard.EMPTY_BOARD) { black_pawn_structure += BONUS_PASSED_PAWN; // check to see if rook is behind a passed pawn if ((MoveGen.GetRookMoves(square, AI.board.occupied_squares, AI.board.occupied_squares) ^ AI.board.black_rook) != ChessBoard.EMPTY_BOARD) { black_pawn_structure += BONUS_ROOK_BEHIND_PASSED_PAWN; } } // check to see if pawn is a doubled pawn if (((ChessBoard.UP_MOVES[square] | ChessBoard.DOWN_MOVES[square]) ^ ChessBoard.SQUARES[square]) != 0) { black_pawn_structure += PENALTY_DOUBLED_PAWN; } // check to see if pawn is an isolated pawn (if not isolated, check to see if pawn is a backward pawn) if ((B_ISOLATED_PAWN[square] & AI.board.black_pawn) == ChessBoard.EMPTY_BOARD) { black_pawn_structure += PENALTY_ISOLATED_PAWN; } else { if (((ChessBoard.PAWN_CAPTURES[square - 8] & AI.board.white_pawn) != ChessBoard.EMPTY_BOARD) && (B_BACKWARD_PAWN[square] & AI.board.black_pawn) == ChessBoard.EMPTY_BOARD) { black_pawn_structure += PENALTY_BACKWARD_PAWN; } } // remove current pawn from black pawns BitBoard black_pawns ^= ChessBoard.SQUARES[square]; } // calculate net pawn structure score if (player == ChessBoard.WHITE) { net_pawn_structure += white_pawn_structure - black_pawn_structure; } else if (player == ChessBoard.BLACK) { net_pawn_structure += black_pawn_structure - white_pawn_structure; } // return pawn structure score return(net_pawn_structure); }
public string getCommand(Position pos, bool drawOffer, List <Position> history) { // Create a search object ulong[] posHashList = new ulong[200 + history.Count]; int posHashListSize = 0; for (int i = 0; i < history.Count; i++) { Position p = history[i]; posHashList[posHashListSize++] = p.zobristHash(); } tt.nextGeneration(); Search sc = new Search(pos, posHashList, posHashListSize, tt); // Determine all legal moves MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); sc.scoreMoveList(moves, 0); // Test for "game over" if (moves.size == 0) { // Switch sides so that the human can decide what to do next. return("swap"); } if (bookEnabled) { Move bookMove = book.getBookMove(pos); if (bookMove != null) { SystemHelper.println("Book moves: " + book.getAllBookMoves(pos)); return(TextIO.moveTostring(pos, bookMove, true)); } } // Find best move using iterative deepening currentSearch = sc; sc.setListener(listener); Move bestM; if ((moves.size == 1) && (canClaimDraw(pos, posHashList, posHashListSize, moves.m[0]) == "")) { bestM = moves.m[0]; bestM.score = 0; } else if (randomMode) { bestM = findSemiRandomMove(sc, moves); } else { sc.timeLimit(minTimeMillis, maxTimeMillis); bestM = sc.iterativeDeepening(moves, maxDepth, maxNodes, verbose); } currentSearch = null; // tt.printStats(); string strMove = TextIO.moveTostring(pos, bestM, true); bestmv = bestM; // Claim draw if appropriate if (bestM.score <= 0) { string drawClaim = canClaimDraw(pos, posHashList, posHashListSize, bestM); if (drawClaim != "") { strMove = drawClaim; } } return(strMove); }
public int AlphaBeta(int alpha, int beta, int depth, ref S_SearchInfo info, bool DoNull) { Debug.Assert(BoardOperations.CheckBoard(board)); if (depth == 0) { return(Quiescence(alpha, beta, ref info)); } if ((info.Nodes & 2047) == 0) { CheckUp(ref info); } info.Nodes++; // If position is a draw. if ((IsRepetition() || board.FiftyMoves >= 100) && board.Ply != 0) { return(0); } if (board.Ply > Variables.MAX_DEPTH - 1) { return(Evaluate.Position(board)); } bool kingInCheck = Attack.IsSqAttacked(board.KingSq[board.Side], board.Side ^ 1, board); // If king is in check, search deeper to get out of check. if (kingInCheck) { depth++; // The two following lines are possibly ERROR. long timeInc = (info.StopTime - info.StartTime) * (1 / 2); info.StopTime += timeInc; } MoveList list = new MoveList(); MoveGen.GenerateAllMoves(board, list, false); int oldAlpha = alpha; int score = -infinite; int legal = 0; // Will increment when we find a legal move. int bestMove = Variables.NO_MOVE; int PvMove = PvTable.Probe(board); // Prioritize Principle Variation move if it's found. if (PvMove != Variables.NO_MOVE) { for (int i = 0; i < list.Count; ++i) { var move = list.Moves[i].Move; if (move == PvMove) { list.Moves[i].Score = 2000000; break; } } } for (int i = 0; i < list.Count; ++i) { PickNextMove(i, list); var move = list.Moves[i].Move; if (!MakeMove.Make_Move(board, move)) { continue; } legal++; score = -AlphaBeta(-beta, -alpha, depth - 1, ref info, true); MakeMove.TakeMove(board); // Take back the made move. if (info.Stopped) { return(0); // Back up to the root if times up. } // We have a new alpha or beta cutoff. if (score > alpha) { bool isCaptureMove = (move & MoveOperations.MoveFlagCapture) != 0; // beta cutoff? if (score >= beta) { if (legal == 1) { info.Fhf++; // We searched the best move first. } info.Fh++; // If beta cutoff, but no capture move. if (!isCaptureMove) { board.SearchKillers[1, board.Ply] = board.SearchKillers[0, board.Ply]; board.SearchKillers[0, board.Ply] = move; } return(beta); } // Alpha cutoff alpha = score; bestMove = move; if (!isCaptureMove) { int from = MoveOperations.FromSq(move); int to = MoveOperations.ToSq(move); board.SearchHistory[board[from], to] += depth; // Prioritizes move near the root of the tree. } } } // If we haven't had any legal moves. if (legal == 0) { // If in check with no legal moves checkmate. if (kingInCheck) { return(-mate + board.Ply); // Return the amount of moves it takes to mate. // Returning in this way, allows the method to "prefer" the fastest checkmate combination. } else { return(0); // Stalemate. } } if (alpha != oldAlpha) { PvTable.StoreMove(board, bestMove); } return(alpha); }
/// <summary> /// Extract the PV starting from pos, using hash entries, both exact scores and bounds. /// </summary> /// <param name="pos"></param> /// <returns></returns> public string extractPV(Position pos) { pos = new Position(pos); string ret = ""; bool first = true; TTEntry ent = probe(pos.historyHash()); UndoInfo ui = new UndoInfo(); List <ulong> hashHistory = new List <ulong>(); bool repetition = false; while (ent.type != TTEntry.T_EMPTY) { string type = ""; if (ent.type == TTEntry.T_LE) { type = "<"; } else if (ent.type == TTEntry.T_GE) { type = ">"; } Move m = new Move(0, 0, 0); ent.getMove(m); MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); bool contains = false; for (int mi = 0; mi < moves.size; mi++) { if (moves.m[mi].equals(m)) { contains = true; break; } } if (!contains) { break; } string moveStr = TextIO.moveTostring(pos, m, false); if (repetition) { break; } if (!first) { ret += " "; } ret += type + moveStr; pos.makeMove(m, ui); if (hashHistory.Contains(pos.zobristHash())) { repetition = true; } hashHistory.Add(pos.zobristHash()); ent = probe(pos.historyHash()); first = false; } return(ret); }
// executes time-limited alpha/beta pruning depth-limited MiniMax search public static int PVABMiniMax(int depth, int alpha, int beta) { // initialize principal variant flag bool pvfound = false; // update evaluated node count AI.nodes++; // check to see if we have time left if (AI.timer.ElapsedMilliseconds > TIME_PER_MOVE) { AI.TIME_EXPIRED = true; return(Score.TIME_EXPIRED_SCORE); } // initialize variables List <ChessMove> moves = new List <ChessMove>(0); ChessMove bestAction = new ChessMove(); int value = SMALL_NUM, bestValue = SMALL_NUM, color = -1; if (depth % 2 != 0) { bestValue = LARGE_NUM; } // check for draws if (Score.IsDrawByHundredMoves()) // 100 move draw; update history table { return(Score.DRAW_SCORE); } if (Score.IsDrawByRepetition()) // repitition draw { return(Score.DRAW_SCORE); } if (Score.IsDrawByInsufficientMaterial()) // insufficient material draw { return(Score.DRAW_SCORE); } // set color of current player if (depth % 2 == 0) { color = AI.board.myColor; } else { color = AI.board.oppColor; } // if depth limit has been reached, return quiescence search value for this node if (depth >= MAX_DEPTH) { // set follow pv flag FOLLOW_PV = false; // go one node deeper if player to move is currently in check if (MoveGen.IsKingAttacked(color)) { MAX_DEPTH++; value = PVABMiniMax(depth, alpha, beta); MAX_DEPTH--; return(value); } // return quiescence search value return(QSearch(alpha, beta, color)); } // if allowed, try a null move to get an early prune if (NULLMOVE_ALLOWED && !FOLLOW_PV) { // if game could be in zugzwang for player to move or player to move is in check, do not attempt null move if (((color == ChessBoard.WHITE && Score.GetMaterialScore(color) > Score.NULLMOVE_LIMIT_SCORE) || (color == ChessBoard.BLACK && Score.GetMaterialScore(color) > Score.NULLMOVE_LIMIT_SCORE)) && !MoveGen.IsKingAttacked(color)) { // set allow null move flag NULLMOVE_ALLOWED = false; // if we are next player to move, run zero-window search for alpha; else use beta if (color == AI.board.oppColor) { value = PVABMiniMax(depth + NULLMOVE_DEPTH_GAP, alpha, alpha + 1); } else if (color == AI.board.myColor) { value = PVABMiniMax(depth + NULLMOVE_DEPTH_GAP, beta, beta + 1); } // reset allow null move flag NULLMOVE_ALLOWED = true; // if alpha/beta was not improved, prune if (color == AI.board.myColor && value >= beta) { return(value); } else if (color == AI.board.oppColor && value <= alpha) { return(value); } } } NULLMOVE_ALLOWED = true; // generate moves for player based on depth moves = MoveGen.GenerateMoves(color, false); // iterate through generated moves for (int i = 0; i < moves.Count; i++) { // order remaining moves according to history table AI.history.OrderMoves(ref moves, color, i); // make current move moves[i].DoMove(MAKE); // if move is illegal, unmake move; continue otherwise if (!MoveGen.IsKingAttacked(color)) { // if principal variant flag is set, run zero-window search; else, run normal search if (pvfound) { // if we are next player to move, use alpha for zero-window search; else, use beta if (depth % 2 != 0) { value = PVABMiniMax(depth + 1, alpha, alpha + 1); } else if (depth % 2 == 0) { value = PVABMiniMax(depth + 1, beta, beta + 1); } // if value returned falls within alpha/beta window, run normal search with normal alpha/beta window if (value > alpha && value < beta) { value = PVABMiniMax(depth + 1, alpha, beta); } } else { value = PVABMiniMax(depth + 1, alpha, beta); } // unmake current move moves[i].DoMove(UNMAKE); // check to see if search found checkmate if (value == Score.CHECKMATE_WIN_SCORE && depth == 0) { // update PV PV.Clear(); PV.Add(moves[i]); // return checkmate score return(Score.CHECKMATE_WIN_SCORE); } // check to see if time has expired if (value == Score.TIME_EXPIRED_SCORE) { return(Score.TIME_EXPIRED_SCORE); } // evaluate minimax search value if (depth % 2 == 0) // maximize { if (value >= beta) // fail-high, prune; update history table { if (color == ChessBoard.WHITE) { AI.history.whiteValue[moves[i].GetFromSq()][moves[i].GetToSq()] += (MAX_DEPTH - depth) * (MAX_DEPTH - depth); } else if (color == ChessBoard.BLACK) { AI.history.blackValue[moves[i].GetFromSq()][moves[i].GetToSq()] += (MAX_DEPTH - depth) * (MAX_DEPTH - depth); } return(value + 1); } if (value > alpha) // set new alpha, set principal variant flag { alpha = value; pvfound = true; } if (value >= bestValue) // set new best action, best value { // if alpha improves at root, update PV if (depth == 0 && value > bestValue) { PV.Clear(); PV.Add(moves[i]); } else if (depth == 0 && value == bestValue) { PV.Add(moves[i]); } bestAction = moves[i]; bestValue = value; } } else if (depth % 2 == 1) // minimize { if (value <= alpha) // fail-low, prune; update history table { if (color == ChessBoard.WHITE) { AI.history.whiteValue[moves[i].GetFromSq()][moves[i].GetToSq()] += (MAX_DEPTH - depth) * (MAX_DEPTH - depth); } else if (color == ChessBoard.BLACK) { AI.history.blackValue[moves[i].GetFromSq()][moves[i].GetToSq()] += (MAX_DEPTH - depth) * (MAX_DEPTH - depth); } return(value - 1); } if (value < beta) // set new beta, set principal variant flag { beta = value; pvfound = true; } if (value < bestValue) // set new best action, best value { bestAction = moves[i]; bestValue = value; } } } else { moves[i].DoMove(UNMAKE); } } // no legal moves for this state if (value == SMALL_NUM) { // if in check, checkmate; else stalemate if (MoveGen.IsKingAttacked(color)) { if (color == AI.board.myColor) // we are in checkmate { return(Score.CHECKMATE_LOSE_SCORE); } else if (color == AI.board.oppColor) // opp is in checkmate { return(Score.CHECKMATE_WIN_SCORE); } } else { return(Score.DRAW_SCORE); } } // return best value from current depth; update history table if (color == ChessBoard.WHITE) { AI.history.whiteValue[bestAction.GetFromSq()][bestAction.GetToSq()] += ((MAX_DEPTH - depth) * (MAX_DEPTH - depth) + 1); } else if (color == ChessBoard.BLACK) { AI.history.blackValue[bestAction.GetFromSq()][bestAction.GetToSq()] += ((MAX_DEPTH - depth) * (MAX_DEPTH - depth) + 1); } return(bestValue); }
/// <summary> /// Translates a move to an integer /// </summary> /// <param name="board"></param> /// <param name="ptrChar"></param> /// <returns></returns> public static int ParseMove(Board board, char[] ptrChar) { if (ptrChar[1] > '8' || ptrChar[1] < '1') { return(Variables.NO_MOVE); } if (ptrChar[3] > '8' || ptrChar[3] < '1') { return(Variables.NO_MOVE); } if (ptrChar[0] > 'h' || ptrChar[0] < 'a') { return(Variables.NO_MOVE); } if (ptrChar[2] > 'h' || ptrChar[2] < 'a') { return(Variables.NO_MOVE); } // Use ASCII values as integers. int from = Conversion.FR2SQ(ptrChar[0] - 'a', ptrChar[1] - '1'); int to = Conversion.FR2SQ(ptrChar[2] - 'a', ptrChar[3] - '1'); Debug.Assert(Validators.SqOnBoard(from), String.Format("The from {0} sq is invalid", Io.SqToString(from))); Debug.Assert(Validators.SqOnBoard(to), String.Format("The to {0} sq is invalid", Io.SqToString(to))); MoveList moveList = new MoveList(); MoveGen.GenerateAllMoves(board, moveList, false); for (int i = 0; i < moveList.Count; ++i) { int move = moveList.Moves[i].Move; // If the move has been located in the move list, it means it's a valid move. if (MoveOperations.FromSq(move) == from && MoveOperations.ToSq(move) == to) { int promotedPce = MoveOperations.Promoted(move); if (promotedPce != (int)Piece.EMPTY) { if (Data.IsPieceRookQueen(promotedPce) && !Data.IsPieceBishopQueen(promotedPce) && ptrChar[4] == 'r') { return(move); } else if (!Data.IsPieceRookQueen(promotedPce) && Data.IsPieceBishopQueen(promotedPce) && ptrChar[4] == 'b') { return(move); } else if (Data.IsPieceKnight(promotedPce) && ptrChar[4] == 'k') { return(move); } else if (Data.IsPieceBishopQueen(promotedPce) && Data.IsPieceRookQueen(promotedPce) && ptrChar[4] == 'q') { return(move); } continue; } return(move); } } return(Variables.NO_MOVE); }
/// <summary> /// sqの駒を掴む /// sqの駒が自駒であることは確定している。 /// 行き先の候補の升情報を更新する。 /// </summary> /// <param name="sq"></param> public void pick_up(SquareHand sq) { if (!(gameServer.CanUserMove && !gameServer.EngineInitializing)) { return; } var pos = gameServer.Position; // この駒をユーザーが掴んで動かそうとしていることを示す viewState.picked_from = sq; viewState.picked_to = SquareHand.NB; viewState.state = GameScreenControlViewStateEnum.PiecePickedUp; // デバッグ用に出力する。 //Console.WriteLine("pick up : " + sq.Pretty() ); // 簡単に利きを表示する。 // ここで連続王手による千日手などを除外すると // 「ユーザーが駒が動かせない、バグだ」をみたいなことを言い出しかねない。 // 移動後に連続王手の千日手を回避していないという警告を出すようにしなくてはならない。 // 合法手を生成して、そこに含まれるものだけにする。 // この生成、局面が変わったときに1回でいいような気はするが.. // 何回もクリックしまくらないはずなのでまあいいや。 int n = MoveGen.LegalAll(pos, moves_buf, 0); var is_drop = sq.IsHandPiece(); var pt = pos.PieceOn(sq).PieceType(); Bitboard bb = Bitboard.ZeroBB(); // 生成されたすべての合法手に対して移動元の升が合致する指し手の移動先の升を // Bitboardに反映させていく。 for (int i = 0; i < n; ++i) { var m = moves_buf[i]; if (is_drop) { // 駒の打てる先。これは合法手でなければならない。 // 二歩とか打ち歩詰めなどがあるので合法手のみにしておく。 // (打ち歩詰めなので打てませんの警告ダイアログ、用意してないので…) // 合法手には自分の手番の駒しか含まれないのでこれでうまくいくはず if (m.IsDrop() && m.DroppedPiece() == pt) { bb |= m.To(); } } else { // 駒の移動できる先 if (!m.IsDrop() && m.From() == (Square)sq) { bb |= m.To(); } } } viewState.picked_piece_legalmovesto = bb; viewState.state = GameScreenControlViewStateEnum.PiecePickedUp; // この値が変わったことで画面の状態が変わるので、次回、OnDraw()が呼び出されなくてはならない。 Dirty = true; }
/// <summary> /// Parse a FEN string and return a chess Position object. /// </summary> /// <param name="fen"></param> /// <returns></returns> public static Position readFEN(string fen) { Position pos = new Position(); string[] words = fen.Split(' '); if (words.Length < 2) { throw new ChessParseError("Too few pieces"); } // Piece placement int row = 7; int col = 0; for (int i = 0; i < words[0].Length; i++) { char c = words[0][i]; switch (c) { case '1': col += 1; break; case '2': col += 2; break; case '3': col += 3; break; case '4': col += 4; break; case '5': col += 5; break; case '6': col += 6; break; case '7': col += 7; break; case '8': col += 8; break; case '/': row--; col = 0; break; case 'P': safeSetPiece(pos, col, row, Piece.WPAWN); col++; break; case 'N': safeSetPiece(pos, col, row, Piece.WKNIGHT); col++; break; case 'B': safeSetPiece(pos, col, row, Piece.WBISHOP); col++; break; case 'R': safeSetPiece(pos, col, row, Piece.WROOK); col++; break; case 'Q': safeSetPiece(pos, col, row, Piece.WQUEEN); col++; break; case 'K': safeSetPiece(pos, col, row, Piece.WKING); col++; break; case 'p': safeSetPiece(pos, col, row, Piece.BPAWN); col++; break; case 'n': safeSetPiece(pos, col, row, Piece.BKNIGHT); col++; break; case 'b': safeSetPiece(pos, col, row, Piece.BBISHOP); col++; break; case 'r': safeSetPiece(pos, col, row, Piece.BROOK); col++; break; case 'q': safeSetPiece(pos, col, row, Piece.BQUEEN); col++; break; case 'k': safeSetPiece(pos, col, row, Piece.BKING); col++; break; default: throw new ChessParseError("Invalid piece"); } } if (words[1].Length == 0) { throw new ChessParseError("Invalid side"); } pos.setWhiteMove(words[1][0] == 'w'); // Castling rights int castleMask = 0; if (words.Length > 2) { for (int i = 0; i < words[2].Length; i++) { char c = words[2][i]; switch (c) { case 'K': castleMask |= (1 << Position.H1_CASTLE); break; case 'Q': castleMask |= (1 << Position.A1_CASTLE); break; case 'k': castleMask |= (1 << Position.H8_CASTLE); break; case 'q': castleMask |= (1 << Position.A8_CASTLE); break; case '-': break; default: throw new ChessParseError("Invalid castling flags"); } } } pos.setCastleMask(castleMask); if (words.Length > 3) { // En passant target square string epstring = words[3]; if (epstring != "-") { if (epstring.Length < 2) { throw new ChessParseError("Invalid en passant square"); } pos.setEpSquare(getSquare(epstring)); } } try { if (words.Length > 4) { pos.halfMoveClock = int.Parse(words[4]); } if (words.Length > 5) { pos.fullMoveCounter = int.Parse(words[5]); } } catch { // Ignore errors here, since the fields are optional } // Each side must have exactly one king int wKings = 0; int bKings = 0; for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { int p = pos.getPiece(Position.getSquare(x, y)); if (p == Piece.WKING) { wKings++; } else if (p == Piece.BKING) { bKings++; } } } if (wKings != 1) { throw new ChessParseError("White must have exactly one king"); } if (bKings != 1) { throw new ChessParseError("Black must have exactly one king"); } // Make sure king can not be captured Position pos2 = new Position(pos); pos2.setWhiteMove(!pos.whiteMove); if (MoveGen.inCheck(pos2)) { throw new ChessParseError("King capture possible"); } fixupEPSquare(pos); return(pos); }