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.printf("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; }
// To get all valid moves in one text-string public static string AllMovesTostring(Position pos, bool ulongForm) { string ret = ""; MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); for (int i = 0; i < moves.size; i++) { ret += moveTostring(pos, moves.m[i], ulongForm, moves) + ";"; } return ret; }
/** Extract the PV starting from pos, using hash entries, both exact scores and bounds. */ public string extractPV(Position pos) { string ret = ""; pos = new Position(pos); // To avoid modifying the input parameter 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; }
/** * Get the current state of the game. */ public static GameState getGameState() { MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); if (moves.size == 0) { if (MoveGen.inCheck(pos)) { return pos.whiteMove ? GameState.BLACK_MATE : GameState.WHITE_MATE; } else { return pos.whiteMove ? GameState.WHITE_STALEMATE : GameState.BLACK_STALEMATE; } } if (insufficientMaterial()) { return GameState.DRAW_NO_MATE; } if (resignState != GameState.ALIVE) { return resignState; } return drawState; }
/** Return a random book move for a position, or null if out of book. */ public Move getBookMove(Position pos) { long t0 = SystemHelper.currentTimeMillis(); Random rndGen = new Random((int)t0); ulong key = pos.zobristHash(); bool iskey = bookMap.ContainsKey(key); List<BookEntry> bookMoves = (iskey ? bookMap[key] : null); if (bookMoves == null) { return null; } MoveGen.MoveList legalMoves = new MoveGen().pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, legalMoves); int sum = 0; for (int i = 0; i < bookMoves.Count; i++) { BookEntry be = bookMoves[i]; bool contains = false; for (int mi = 0; mi < legalMoves.size; mi++) if (legalMoves.m[mi].equals(be.move)) { contains = true; break; } if (!contains) { // If an illegal move was found, it means there was a hash collision. return null; } sum += getWeight(bookMoves[i].count); } if (sum <= 0) { return null; } int rnd = rndGen.Next(sum); sum = 0; for (int i = 0; i < bookMoves.Count; i++) { sum += getWeight(bookMoves[i].count); if (rnd < sum) { return bookMoves[i].move; } } // Should never get here throw new RuntimeException(); }
/** Remove pseudo-legal EP square if it is not legal, ie would leave king in check. */ public static void fixupEPSquare(Position pos) { int epSquare = pos.getEpSquare(); if (epSquare >= 0) { MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); bool epValid = false; for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; if (m.to == epSquare) { if (pos.getPiece(m.from) == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) { epValid = true; break; } } } if (!epValid) { pos.setEpSquare(-1); } } }
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) { ret += ((char)(x1 + 'a')); // Only file info needed } else if (numSameRow < 2) { ret += ((char)(y1 + '1')); // Only row info needed } else { ret += ((char)(x1 + 'a')); // File and row info needed 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; }
/** * 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. */ 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) { return null; // More than one match, not ok } else { move = m; } } } if (move != null) return move; } return move; }
/** * Convert a chess move to human readable form. * @param pos The chess position. * @param move The executed move. * @param ulongForm If true, use ulong notation, eg Ng1-f3. * Otherwise, use short notation, eg Nf3 */ public static string moveTostring(Position pos, Move move, bool ulongForm) { MoveGen MG = new MoveGen(); MoveGen.MoveList moves = MG.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); return moveTostring(pos, move, ulongForm, moves); }
/** * Extract a list of PV moves, starting from "rootPos" and first move "m". */ public List<Move> extractPVMoves(Position rootPos, Move m) { Position pos = new Position(rootPos); m = new Move(m); List<Move> ret = new List<Move>(); UndoInfo ui = new UndoInfo(); List<ulong> hashHistory = new List<ulong>(); MoveGen moveGen = new MoveGen(); while (true) { ret.Add(m); pos.makeMove(m, ui); if (hashHistory.Contains(pos.zobristHash())) { break; } hashHistory.Add(pos.zobristHash()); TTEntry ent = probe(pos.historyHash()); if (ent.type == TTEntry.T_EMPTY) { break; } m = new Move(0,0,0); ent.getMove(m); MoveGen.MoveList moves = moveGen.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; } return ret; }
private Move findSemiRandomMove(Search sc, MoveGen.MoveList moves) { sc.timeLimit(minTimeMillis, maxTimeMillis); Move bestM = sc.iterativeDeepening(moves, 1, maxNodes, verbose); int bestScore = bestM.score; long t0 = SystemHelper.currentTimeMillis(); Random rndGen = new Random((int)t0); int sum = 0; for (int mi = 0; mi < moves.size; mi++) { sum += moveProbWeight(moves.m[mi].score, bestScore); } int rnd = rndGen.Next(sum); for (int mi = 0; mi < moves.size; mi++) { int weight = moveProbWeight(moves.m[mi].score, bestScore); if (rnd < weight) { return moves.m[mi]; } rnd -= weight; } SystemHelper.println("Assert error. Should never get here!"); return null; }
/** Search a position and return the best move and score. Used for test suite processing. */ public TwoReturnValues<Move, string> searchPosition(Position pos, int maxTimeMillis) { // Create a search object ulong[] posHashList = new ulong[200]; tt.nextGeneration(); Search sc = new Search(pos, posHashList, 0, tt); // Determine all legal moves MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); sc.scoreMoveList(moves, 0); // Find best move using iterative deepening sc.timeLimit(maxTimeMillis, maxTimeMillis); Move bestM = sc.iterativeDeepening(moves, -1, -1, false); // Extract PV string PV = TextIO.moveTostring(pos, bestM, false) + " "; UndoInfo ui = new UndoInfo(); pos.makeMove(bestM, ui); PV += tt.extractPV(pos); pos.unMakeMove(bestM, ui); // tt.printStats(); // Return best move and PV return new TwoReturnValues<Move, string>(bestM, PV); }
static ulong perfT(MoveGen moveGen, Position pos, int depth) { if (depth == 0) return 1; ulong nodes = 0; MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); MoveGen.RemoveIllegal(pos, moves); if (depth == 1) { int ret = moves.size; moveGen.returnMoveList(moves); return (ulong)ret; } UndoInfo ui = new UndoInfo(); for (int mi = 0; mi < moves.size; mi++) { Move m = moves.m[mi]; pos.makeMove(m, ui); nodes += perfT(moveGen, pos, depth - 1); pos.unMakeMove(m, ui); } moveGen.returnMoveList(moves); return nodes; }
/** * 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 (ChessParseError ex) { 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 (ChessParseError ex) { SystemHelper.printf("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 (NumberFormatException nfe) { SystemHelper.printf("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.printf("perft(" + depth.ToString() + ") = " + nodes.ToString() + " t=" + ((t1 - t0)/1000).ToString() + "s" ); } catch (NumberFormatException nfe) { SystemHelper.printf("Number format exception: " + moveStr); return false; } return true; } else { return false; } }