// Constructor public jcPlayerHuman(int which) { this.SetSide(which); linebuf = new char[10]; Pseudos = new jcMoveListGenerator(); Successor = new jcBoard(); }
// public jcMove Query // Querying the table for a ready-made move to play. Return null if there // is none public jcMove Query(jcBoard theBoard) { // First, look for a match in the table int key = Math.Abs(theBoard.HashKey() % TABLE_SIZE); int hashLock = theBoard.HashLock(); // If the hash lock doesn't match the one for our position, get out if (Table[key].theLock != hashLock) return null; // If there is an entry for this board in the table, verify that it // contains a move for the current side if (theBoard.GetCurrentPlayer() == jcPlayer.SIDE_BLACK) { if (Table[key].BlackMove.MoveType != jcOpeningBookEntry.NO_MOVE) return Table[key].BlackMove; } else { if (Table[key].WhiteMove.MoveType != jcOpeningBookEntry.NO_MOVE) return Table[key].WhiteMove; } // If we haven't found anything useful, quit return null; }
/**************************************************************************** * PUBLIC METHODS ***************************************************************************/ // Move selection: An iterative-deepening paradigm calling MTD(f) repeatedly public override jcMove PickBestMove(jcBoard theBoard) { // First things first: look in the Opening Book, and if it contains a // move for this position, don't search anything MoveCounter++; jcMove Mov = null; Mov = Openings.Query(theBoard); if(Mov != null) return Mov; // Store the identity of the moving side, so that we can tell Evaluator // from whose perspective we need to evaluate positions FromWhosePerspective = theBoard.GetCurrentPlayer(); // Should we erase the history table? if((Rnd.Next() % 6) == 2) HistoryTable.Forget(); // Begin search. The search's maximum depth is determined on the fly, // according to how much effort has been spent; if it's possible to search // to depth 8 in 5 seconds, then by all means, do it! int bestGuess = 0; int iterdepth = 1; while(true) { // Searching to depth 1 is not very effective, so we begin at 2 iterdepth++; // Compute efficiency statistics NumRegularNodes = 0; NumQuiescenceNodes = 0; NumRegularTTHits = 0; NumQuiescenceTTHits = 0; NumRegularCutoffs = 0; NumQuiescenceCutoffs = 0; // Look for a move at the current depth Mov = MTDF(theBoard, bestGuess, iterdepth); bestGuess = Mov.MoveEvaluation; // Feedback! Console.Write("Iteration of depth " + iterdepth + "; best move = "); Mov.Print(); Console.Write(" --> Transposition Table hits for regular nodes: "); Console.WriteLine(NumRegularTTHits + " of " + NumRegularNodes); Console.Write(" --> Transposition Table hits for quiescence nodes: "); Console.WriteLine(NumQuiescenceTTHits + " of " + NumQuiescenceNodes); Console.WriteLine(" --> Number of cutoffs for regular nodes: " + NumRegularCutoffs); Console.WriteLine(" --> Number of cutoffs in quiescence search: " + NumQuiescenceCutoffs); // Get out if we have searched deep enough if((NumRegularNodes + NumQuiescenceNodes) > MaxSearchSize) break; if(iterdepth >= 15) break; } return Mov; }
// Loading the table from a file public bool Load(string fileName) { // Open the file as a Java tokenizer using (var sr = new StreamReader(fileName)) { // Create a game board on which to "play" the opening sequences stored in // the book, so that we know which position to associate with which move jcBoard board = new jcBoard(); jcMove mov = new jcMove(); jcMoveListGenerator successors = new jcMoveListGenerator(); // How many lines of play do we have in the book? var lines = File.ReadAllLines(fileName); int numLines = lines.Length; for (int wak = 0; wak < numLines; wak++) { // Begin the line of play with a clean board board.StartingBoard(); // Load the continuation var lineNums = lines[wak].Split(' '); for (int i = 0; i < lineNums.Length; i++) { if (lineNums[i] == "END") break; successors.ComputeLegalMoves(board); var source = Convert.ToInt32(lineNums[i]); var destination = Convert.ToInt32(lineNums[i + 1]); i++; mov = successors.FindMoveForSquares(source, destination); StoreMove(board, mov); board.ApplyMove(mov); } } } return true; }
private bool StoreMove(jcBoard theBoard, jcMove theMove) { // Where should we store this data? int key = Math.Abs(theBoard.HashKey() % TABLE_SIZE); int hashLock = theBoard.HashLock(); // Is there already an entry for a different board position where we // want to put this? If so, mark it deleted if (Table[key].theLock != hashLock) { Table[key].BlackMove.MoveType = jcOpeningBookEntry.NO_MOVE; Table[key].WhiteMove.MoveType = jcOpeningBookEntry.NO_MOVE; } // And store the new move Table[key].theLock = hashLock; if (theBoard.GetCurrentPlayer() == jcPlayer.SIDE_BLACK) { Table[key].BlackMove.Copy(theMove); } else { Table[key].WhiteMove.Copy(theMove); } return true; }
// public jcMove GetMove( theBoard ) // Getting a move from the human player. Sorry, but this is very, very // primitive: you need to enter square numbers instead of piece ID's, and // both square numbers must be entered with two digits. Ex.: 04 00 public override jcMove GetMove(jcBoard theBoard) { // Read the move from the command line bool ok = false; jcMove Mov = new jcMove(); do { Console.WriteLine("Your move, " + PlayerStrings[this.GetSide()] + "?"); // Get data from the command line string line = null; do { try { line = Console.ReadLine(); } catch(Exception) { } } while(line.Length < 3); if(line.ToUpper().Equals("RESIG")) { Mov.MoveType = jcMove.MOVE_RESIGN; return (Mov); } // Extract the source and destination squares from the line buffer Mov.SourceSquare = Convert.ToInt32(line.Substring(0, 2)); Mov.DestinationSquare = Convert.ToInt32(line.Substring(3, 2)); if((Mov.SourceSquare < 0) || (Mov.SourceSquare > 63)) { Console.WriteLine("Sorry, illegal source square " + Mov.SourceSquare); continue; } if((Mov.DestinationSquare < 0) || (Mov.DestinationSquare > 63)) { Console.WriteLine("Sorry, illegal destination square " + Mov.DestinationSquare); continue; } // Time to try to figure out what the move means! if(theBoard.GetCurrentPlayer() == jcPlayer.SIDE_WHITE) { // Is there a piece (of the moving player) on SourceSquare? // If not, abort Mov.MovingPiece = theBoard.FindWhitePiece(Mov.SourceSquare); if(Mov.MovingPiece == jcBoard.EMPTY_SQUARE) { Console.WriteLine("Sorry, You don't have a piece at square " + Mov.SourceSquare); continue; } // Three cases: there is a piece on the destination square (a capture), // the destination square allows an en passant capture, or it is a // simple non-capture move. If the destination contains a piece of the // moving side, abort if(theBoard.FindWhitePiece(Mov.DestinationSquare) != jcBoard.EMPTY_SQUARE) { Console.WriteLine("Sorry, can't capture your own piece!"); continue; } Mov.CapturedPiece = theBoard.FindBlackPiece(Mov.DestinationSquare); if(Mov.CapturedPiece != jcBoard.EMPTY_SQUARE) Mov.MoveType = jcMove.MOVE_CAPTURE_ORDINARY; else if((theBoard.GetEnPassantPawn() == (1 << Mov.DestinationSquare)) && (Mov.MovingPiece == jcBoard.WHITE_PAWN)) { Mov.CapturedPiece = jcBoard.BLACK_PAWN; Mov.MoveType = jcMove.MOVE_CAPTURE_EN_PASSANT; } // If the move isn't a capture, it may be a castling attempt else if((Mov.MovingPiece == jcBoard.WHITE_KING) && ((Mov.SourceSquare - Mov.DestinationSquare) == 2)) Mov.MoveType = jcMove.MOVE_CASTLING_KINGSIDE; else if((Mov.MovingPiece == jcBoard.WHITE_KING) && ((Mov.SourceSquare - Mov.DestinationSquare) == -2)) Mov.MoveType = jcMove.MOVE_CASTLING_QUEENSIDE; else Mov.MoveType = jcMove.MOVE_NORMAL; } else { Mov.MovingPiece = theBoard.FindBlackPiece(Mov.SourceSquare); if(Mov.MovingPiece == jcBoard.EMPTY_SQUARE) { Console.WriteLine("Sorry, you don't have a piece in square " + Mov.SourceSquare); continue; } if(theBoard.FindBlackPiece(Mov.DestinationSquare) != jcBoard.EMPTY_SQUARE) { Console.WriteLine("Sorry, you can't capture your own piece in square " + Mov.DestinationSquare); continue; } Mov.CapturedPiece = theBoard.FindWhitePiece(Mov.DestinationSquare); if(Mov.CapturedPiece != jcBoard.EMPTY_SQUARE) Mov.MoveType = jcMove.MOVE_CAPTURE_ORDINARY; else if((theBoard.GetEnPassantPawn() == (1 << Mov.DestinationSquare)) && (Mov.MovingPiece == jcBoard.BLACK_PAWN)) { Mov.CapturedPiece = jcBoard.WHITE_PAWN; Mov.MoveType = jcMove.MOVE_CAPTURE_EN_PASSANT; } else if((Mov.MovingPiece == jcBoard.BLACK_KING) && ((Mov.SourceSquare - Mov.DestinationSquare) == 2)) Mov.MoveType = jcMove.MOVE_CASTLING_KINGSIDE; else if((Mov.MovingPiece == jcBoard.BLACK_KING) && ((Mov.SourceSquare - Mov.DestinationSquare) == -2)) Mov.MoveType = jcMove.MOVE_CASTLING_QUEENSIDE; else Mov.MoveType = jcMove.MOVE_NORMAL; } // Now, if the move results in a pawn promotion, we must ask the user // for the type of promotion! if(((Mov.MovingPiece == jcBoard.WHITE_PAWN) && (Mov.DestinationSquare < 8)) || ((Mov.MovingPiece == jcBoard.BLACK_PAWN) && (Mov.DestinationSquare > 55))) { int car = -1; Console.WriteLine("Promote the pawn to [K]night, [R]ook, [B]ishop, [Q]ueen?"); do { try { car = Console.Read(); } catch(Exception) { } } while((car != 'K') && (car != 'k') && (car != 'b') && (car != 'B') && (car != 'R') && (car != 'r') && (car != 'Q') && (car != 'q')); if((car == 'K') || (car == 'k')) Mov.MoveType += jcMove.MOVE_PROMOTION_KNIGHT; else if((car == 'B') || (car == 'b')) Mov.MoveType += jcMove.MOVE_PROMOTION_BISHOP; else if((car == 'R') || (car == 'r')) Mov.MoveType += jcMove.MOVE_PROMOTION_ROOK; else Mov.MoveType += jcMove.MOVE_PROMOTION_QUEEN; } // OK, now let's see if the move is actually legal! First step: a check // for pseudo-legality, i.e., is it a valid successor to the current // board? Pseudos.ComputeLegalMoves(theBoard); if(!Pseudos.Find(Mov)) { Console.Write("Sorry, this move is not in the pseudo-legal list: "); Mov.Print(); Pseudos.Print(); continue; } // If pseudo-legal, then verify whether it leaves the king in check Successor.Clone(theBoard); Successor.ApplyMove(Mov); if(!Pseudos.ComputeLegalMoves(Successor)) { Console.Write("Sorry, this move leaves your king in check: "); Mov.Print(); continue; } // If we have made it here, we have a valid move to play! Console.WriteLine("Move is accepted..."); ok = true; } while(!ok); return (Mov); }
// public bool Clone // Make a deep copy of a jcBoard object; assumes that memory has already // been allocated for the new object, which is always true since we // "allocate" jcBoards from a permanent array public bool Clone(jcBoard target) { EnPassantPawn = target.EnPassantPawn; for(int i = 0 ; i < 4 ; i++) { CastlingStatus[i] = target.CastlingStatus[i]; } for(int i = 0 ; i < ALL_BITBOARDS ; i++) { BitBoards[i] = target.BitBoards[i]; } MaterialValue[0] = target.MaterialValue[0]; MaterialValue[1] = target.MaterialValue[1]; NumPawns[0] = target.NumPawns[0]; NumPawns[1] = target.NumPawns[1]; ExtraKings[0] = target.ExtraKings[0]; ExtraKings[1] = target.ExtraKings[1]; HasCastled[0] = target.HasCastled[0]; HasCastled[1] = target.HasCastled[1]; CurrentPlayer = target.CurrentPlayer; return true; }
// private jcMove UnrolledAlphabeta // The standard alphabeta, with the top level "unrolled" so that it can // return a jcMove structure instead of a mere minimax value // See jcAISearchAgent.Alphabeta for detailed comments on this code private jcMove UnrolledAlphabeta(jcBoard theBoard, int depth, int alpha, int beta) { jcMove BestMov = new jcMove(); jcMoveListGenerator movegen = new jcMoveListGenerator(); movegen.ComputeLegalMoves(theBoard); HistoryTable.SortMoveList(movegen, theBoard.GetCurrentPlayer()); jcBoard newBoard = new jcBoard(); int bestSoFar; bestSoFar = ALPHABETA_MINVAL; int currentAlpha = alpha; jcMove mov; // Loop on the successors while((mov = movegen.Next()) != null) { // Compute a board position resulting from the current successor newBoard.Clone(theBoard); newBoard.ApplyMove(mov); // And search it in turn int movScore = AlphaBeta(MINNODE, newBoard, depth - 1, currentAlpha, beta); // Ignore illegal moves in the alphabeta evaluation if(movScore == ALPHABETA_ILLEGAL) continue; currentAlpha = Math.Max(currentAlpha, movScore); // Is the current successor better than the previous best? if(movScore > bestSoFar) { BestMov.Copy(mov); bestSoFar = movScore; BestMov.MoveEvaluation = bestSoFar; // Can we cutoff now? if(bestSoFar >= beta) { TransTable.StoreBoard(theBoard, bestSoFar, jcMove.EVALTYPE_UPPERBOUND, depth, MoveCounter); // Add this move's efficiency in the HistoryTable HistoryTable.AddCount(theBoard.GetCurrentPlayer(), mov); return BestMov; } } } // Test for checkmate or stalemate if(bestSoFar <= ALPHABETA_GIVEUP) { newBoard.Clone(theBoard); jcMoveListGenerator secondary = new jcMoveListGenerator(); newBoard.SwitchSides(); if(secondary.ComputeLegalMoves(newBoard)) { // Then, we are not in check and may continue our efforts. HistoryTable.SortMoveList(movegen, newBoard.GetCurrentPlayer()); movegen.ResetIterator(); BestMov.MoveType = jcMove.MOVE_STALEMATE; BestMov.MovingPiece = jcBoard.KING + theBoard.GetCurrentPlayer(); while((mov = movegen.Next()) != null) { newBoard.Clone(theBoard); newBoard.ApplyMove(mov); if(secondary.ComputeLegalMoves(newBoard)) { BestMov.MoveType = jcMove.MOVE_RESIGN; } } } else { // We're in check and our best hope is GIVEUP or worse, so either we are // already checkmated or will be soon, without hope of escape BestMov.MovingPiece = jcBoard.KING + theBoard.GetCurrentPlayer(); BestMov.MoveType = jcMove.MOVE_RESIGN; } } // If we haven't returned yet, we have found an accurate minimax score // for a position which is neither a checkmate nor a stalemate TransTable.StoreBoard(theBoard, bestSoFar, jcMove.EVALTYPE_ACCURATE, depth, MoveCounter); return BestMov; }
/*************************************************************************** * PRIVATE METHODS **************************************************************************/ // private jcMove MTDF // Use the MTDF algorithm to find a good move. MTDF repeatedly calls // alphabeta with a zero-width search window, which creates very many quick // cutoffs. If alphabeta fails low, the next call will place the search // window lower; in a sense, MTDF is a sort of binary search mechanism into // the minimax space. private jcMove MTDF(jcBoard theBoard, int target, int depth) { int beta; jcMove Mov; int currentEstimate = target; int upperbound = ALPHABETA_MAXVAL; int lowerbound = ALPHABETA_MINVAL; // This is the trick: make repeated calls to alphabeta, zeroing in on the // actual minimax value of theBoard by narrowing the bounds do { if(currentEstimate == lowerbound) beta = currentEstimate + 1; else beta = currentEstimate; Mov = UnrolledAlphabeta(theBoard, depth, beta - 1, beta); currentEstimate = Mov.MoveEvaluation; if(currentEstimate < beta) upperbound = currentEstimate; else lowerbound = currentEstimate; } while(lowerbound < upperbound); return Mov; }