// 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; }
// Construction public jcOpeningBookEntry() { theLock = 0; WhiteMove = new jcMove(); WhiteMove.MoveType = NO_MOVE; BlackMove = new jcMove(); BlackMove.MoveType = NO_MOVE; }
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 ApplyMove // Change the jcBoard's internal representation to reflect the move // received as a parameter public bool ApplyMove(jcMove theMove) { // If the move includes a pawn promotion, an extra step will be required // at the end bool isPromotion = (theMove.MoveType >= jcMove.MOVE_PROMOTION_KNIGHT); int moveWithoutPromotion = (theMove.MoveType & jcMove.NO_PROMOTION_MASK); int side = theMove.MovingPiece % 2; // For now, ignore pawn promotions switch(moveWithoutPromotion) { case jcMove.MOVE_NORMAL: // The simple case RemovePiece(theMove.SourceSquare, theMove.MovingPiece); AddPiece(theMove.DestinationSquare, theMove.MovingPiece); break; case jcMove.MOVE_CAPTURE_ORDINARY: // Don't forget to remove the captured piece! RemovePiece(theMove.SourceSquare, theMove.MovingPiece); RemovePiece(theMove.DestinationSquare, theMove.CapturedPiece); AddPiece(theMove.DestinationSquare, theMove.MovingPiece); break; case jcMove.MOVE_CAPTURE_EN_PASSANT: // Here, we can use our knowledge of the board to make a small // optimization, since the pawn to be captured is always // "behind" the moving pawn's destination square, we can compute its // position on the fly RemovePiece(theMove.SourceSquare, theMove.MovingPiece); AddPiece(theMove.DestinationSquare, theMove.MovingPiece); if((theMove.MovingPiece % 2) == jcPlayer.SIDE_WHITE) RemovePiece(theMove.DestinationSquare + 8, theMove.CapturedPiece); else RemovePiece(theMove.DestinationSquare - 8, theMove.CapturedPiece); break; case jcMove.MOVE_CASTLING_QUEENSIDE: // Again, we can compute the rook's source and destination squares // because of our knowledge of the board's structure RemovePiece(theMove.SourceSquare, theMove.MovingPiece); AddPiece(theMove.DestinationSquare, theMove.MovingPiece); int theRook = ROOK + (theMove.MovingPiece % 2); RemovePiece(theMove.SourceSquare - 4, theRook); AddPiece(theMove.SourceSquare - 1, theRook); // We must now mark some squares as containing "phantom kings" so that // the castling can be cancelled by the next opponent's move, if he // can move to one of them if(side == jcPlayer.SIDE_WHITE) { SetExtraKings(side, EXTRAKINGS_WHITE_QUEENSIDE); } else { SetExtraKings(side, EXTRAKINGS_BLACK_QUEENSIDE); } HasCastled[side] = true; break; case jcMove.MOVE_CASTLING_KINGSIDE: // Again, we can compute the rook's source and destination squares // because of our knowledge of the board's structure RemovePiece(theMove.SourceSquare, theMove.MovingPiece); AddPiece(theMove.DestinationSquare, theMove.MovingPiece); theRook = ROOK + (theMove.MovingPiece % 2); RemovePiece(theMove.SourceSquare + 3, theRook); AddPiece(theMove.SourceSquare + 1, theRook); // We must now mark some squares as containing "phantom kings" so that // the castling can be cancelled by the next opponent's move, if he // can move to one of them if(side == jcPlayer.SIDE_WHITE) { SetExtraKings(side, EXTRAKINGS_WHITE_KINGSIDE); } else { SetExtraKings(side, EXTRAKINGS_BLACK_KINGSIDE); } HasCastled[side] = true; break; case jcMove.MOVE_RESIGN: // FDL Later, ask the AI player who resigned to print the continuation break; case jcMove.MOVE_STALEMATE: Console.WriteLine("Stalemate - Game is a draw."); break; } // And now, apply the promotion if(isPromotion) { int promotionType = (theMove.MoveType & jcMove.PROMOTION_MASK); int color = (theMove.MovingPiece % 2); switch(promotionType) { case jcMove.MOVE_PROMOTION_KNIGHT: RemovePiece(theMove.DestinationSquare, theMove.MovingPiece); AddPiece(theMove.DestinationSquare, KNIGHT + color); break; case jcMove.MOVE_PROMOTION_BISHOP: RemovePiece(theMove.DestinationSquare, theMove.MovingPiece); AddPiece(theMove.DestinationSquare, BISHOP + color); break; case jcMove.MOVE_PROMOTION_ROOK: RemovePiece(theMove.DestinationSquare, theMove.MovingPiece); AddPiece(theMove.DestinationSquare, ROOK + color); break; case jcMove.MOVE_PROMOTION_QUEEN: RemovePiece(theMove.DestinationSquare, theMove.MovingPiece); AddPiece(theMove.DestinationSquare, QUEEN + color); break; } } // If this was a 2-step pawn move, we now have a valid en passant // capture possibility. Otherwise, no. if((theMove.MovingPiece == jcBoard.WHITE_PAWN) && (theMove.SourceSquare - theMove.DestinationSquare == 16)) SetEnPassantPawn(theMove.DestinationSquare + 8); else if((theMove.MovingPiece == jcBoard.BLACK_PAWN) && (theMove.DestinationSquare - theMove.SourceSquare == 16)) SetEnPassantPawn(theMove.SourceSquare + 8); else ClearEnPassantPawn(); // And now, maintain castling status // If a king moves, castling becomes impossible for that side, for the // rest of the game switch(theMove.MovingPiece) { case WHITE_KING: SetCastlingStatus(CASTLE_KINGSIDE + jcPlayer.SIDE_WHITE, false); SetCastlingStatus(CASTLE_QUEENSIDE + jcPlayer.SIDE_WHITE, false); break; case BLACK_KING: SetCastlingStatus(CASTLE_KINGSIDE + jcPlayer.SIDE_BLACK, false); SetCastlingStatus(CASTLE_QUEENSIDE + jcPlayer.SIDE_BLACK, false); break; default: break; } // Or, if ANYTHING moves from a corner, castling becomes impossible on // that side (either because it's the rook that is moving, or because // it has been captured by whatever moves, or because it is already gone) switch(theMove.SourceSquare) { case 0: SetCastlingStatus(CASTLE_QUEENSIDE + jcPlayer.SIDE_BLACK, false); break; case 7: SetCastlingStatus(CASTLE_KINGSIDE + jcPlayer.SIDE_BLACK, false); break; case 56: SetCastlingStatus(CASTLE_QUEENSIDE + jcPlayer.SIDE_WHITE, false); break; case 63: SetCastlingStatus(CASTLE_KINGSIDE + jcPlayer.SIDE_WHITE, false); break; default: break; } // All that remains to do is switch sides SetCurrentPlayer((GetCurrentPlayer() + 1) % 2); 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; }