// Check for equality public override bool Equals(object other) { CheckerState otherCheckerState = other as CheckerState; if (other == null) { return(false); } if (other == this) { return(true); } // If there are any points of difference, return false for (int rowIndex = 0; rowIndex < board.Length; ++rowIndex) { for (int colIndex = 0; colIndex < board[rowIndex].Length; ++colIndex) { if (board[rowIndex][colIndex] != otherCheckerState.board[rowIndex][colIndex]) { return(false); } } } // Otherwise, return true return(true); }
// Connect playerOne and playerTwo the the event signals from CheckerState public static void WireEvents(CheckerState checkerState, Agent playerOne, Agent playerTwo) { // Wire up player behaviors to state events CheckerState.WhiteWins += playerOne.Victory; CheckerState.WhiteWins += playerTwo.Defeat; CheckerState.BlackWins += playerOne.Defeat; CheckerState.BlackWins += playerTwo.Victory; }
// Copy constructor public CheckerState(CheckerState src) { board = new Piece[8][]; for (int rowIndex = 0; rowIndex < 8; ++rowIndex) { // Create a new row board[rowIndex] = new Piece[8]; // Copy src members into new row for (int colIndex = 0; colIndex < 8; ++colIndex) { board[rowIndex][colIndex] = src.board[rowIndex][colIndex]; } } }
// Modify, clone, and restore the current state to produce a successor state private CheckerState makeMove(int rowSrc, int colSrc, int rowDest, int colDest, bool isJump) { // The piece to move Piece curPiece = board[rowSrc][colSrc]; Piece midPiece = board[(rowSrc + rowDest) / 2][(colSrc + colDest) / 2]; // Modify board[rowSrc][colSrc] = Piece.Blank; board[rowDest][colDest] = curPiece; if (isJump) { board[(rowSrc + rowDest) / 2][(colSrc + colDest) / 2] = Piece.Blank; } // Convert any men to kings if (board[rowDest][colDest] == Piece.White && rowDest == 0) { board[rowDest][colDest] = Piece.WhiteKing; } if (board[rowDest][colDest] == Piece.Black && rowDest == 7) { board[rowDest][colDest] = Piece.BlackKing; } // Clone CheckerState modState = new CheckerState(this); // Restore if (isJump) { board[(rowSrc + rowDest) / 2][(colSrc + colDest) / 2] = midPiece; } board[rowDest][colDest] = Piece.Blank; board[rowSrc][colSrc] = curPiece; // Return new state return(modState); }
// Recursively generate all states that could result from a jump private List <IState> getJumps(int row, int col, Move move, CheckerState state) { // Ending states List <IState> successors = new List <IState>(); // Current player's piece Piece curPiece = board[row][col]; bool isKing = (curPiece == Piece.WhiteKing || curPiece == Piece.BlackKing) ? true : false; // Enemy piece types Piece otherPiece; Piece otherPieceKing; // What direction in which to move int advance; if (move == Move.White) { otherPiece = Piece.Black; otherPieceKing = Piece.BlackKing; advance = -1; } else { otherPiece = Piece.White; otherPieceKing = Piece.WhiteKing; advance = 1; } // If there is a forward-left enemy piece if (inBounds(row + advance, col - 1) && (board[row + advance][col - 1] == otherPiece || board[row + advance][col - 1] == otherPieceKing)) { // If there is a blank cell to bridge to if (inBounds(row + (advance * 2), col - 2) && (board[row + (advance * 2)][col - 2] == Piece.Blank)) { // Add any possible successors CheckerState modState = state.makeMove(row, col, row + (advance * 2), col - 2, true); // If current piece becomes a king if (isKing == false && (modState.board[row + (advance * 2)][col - 2] == Piece.WhiteKing || modState.board[row + (advance * 2)][col - 2] == Piece.BlackKing)) { // End all successive jumps successors.Add(modState); } else { // Otherwise, find all possible branching possibilities successors.AddRange(getJumps(row + (advance * 2), col - 2, move, modState)); } } } // If there is a forward-right enemy piece if (inBounds(row + advance, col + 1) && (board[row + advance][col + 1] == otherPiece || board[row + advance][col + 1] == otherPieceKing)) { // If there is a blank cell to bridge to if (inBounds(row + (advance * 2), col + 2) && (board[row + (advance * 2)][col + 2] == Piece.Blank)) { // Add any possible successors CheckerState modState = state.makeMove(row, col, row + (advance * 2), col + 2, true); // If current piece becomes a king if (isKing == false && (modState.board[row + (advance * 2)][col + 2] == Piece.WhiteKing || modState.board[row + (advance * 2)][col + 2] == Piece.BlackKing)) { // End all successive jumps successors.Add(modState); } else { // Otherwise, find all possible branching possibilities successors.AddRange(getJumps(row + (advance * 2), col + 2, move, modState)); } } } // If the current piece is already a king if (isKing) { // If there is a back-left enemy piece if (inBounds(row - advance, col - 1) && (board[row - advance][col - 1] == otherPiece || board[row - advance][col - 1] == otherPieceKing)) { // If there is a blank cell to bridge to if (inBounds(row - (advance * 2), col - 2) && (board[row - (advance * 2)][col - 2] == Piece.Blank)) { // Add any possible successors CheckerState modState = state.makeMove(row, col, row - (advance * 2), col - 2, true); successors.AddRange(getJumps(row - (advance * 2), col - 2, move, modState)); } } // If there is a back-right enemy piece if (inBounds(row - advance, col + 1) && (board[row - advance][col + 1] == otherPiece || board[row - advance][col + 1] == otherPieceKing)) { // If there is a blank cell to bridge to if (inBounds(row - (advance * 2), col + 2) && (board[row - (advance * 2)][col + 2] == Piece.Blank)) { // Add any possible successors CheckerState modState = state.makeMove(row, col, row - (advance * 2), col + 2, true); successors.AddRange(getJumps(row - (advance * 2), col + 2, move, modState)); } } } // If no further jumps were found, this is a terminal state (base case) if (successors.Count == 0) { successors.Add(state); } // Return all viable successor jump states found return(successors); }
// Train two agents to compete in an adversarial game public static void TrainZeroSum(long practiceGames, bool showOutput, Agent agentOne, Agent agentTwo) { // Report the number of practice games if (showOutput) { Console.WriteLine($"Number of Practice Games: {practiceGames}"); } // Create starting state IState state = new CheckerState(); CheckerState checkerState = state as CheckerState; // Put agents in training mode agentOne.TrainingMode(1.0); agentTwo.TrainingMode(1.0); // Use repeated wins as a benchmark bool enoughTraining = false; long games = 0; int ticks = 0; bool dotPrinted = false; // Reset dotPrinted after it has moved on Action <object, EventArgs> resetDotPrinted = (object sender, EventArgs e) => dotPrinted = false; CheckerState.WhiteWins += resetDotPrinted; CheckerState.BlackWins += resetDotPrinted; // Begin training progress bar if (showOutput) { Console.Write("[ "); } // Watch each agent evolve while (!enoughTraining) { // Train the first agent checkerState = agentOne.Act(checkerState.SuccessorsBlack) as CheckerState; checkerState.GoalTest(); games = agentOne.Victories + agentTwo.Victories + agentOne.Draws; // Account for every 1% of progress if (games % (practiceGames / 100) == 0 && !dotPrinted) { // Update competitiveness at quarters if (ticks % 25 == 0) { // Display quarter in progress bar if (showOutput) { Console.Write($"[{ticks}]"); } // Make each agent more competitive agentOne.TrainingMode((100.0 - ticks) / 100.0); agentTwo.TrainingMode((100.0 - ticks) / 100.0); } // Update ticks ticks += 1; // Don't include hundredth tick if (ticks < 100 && showOutput) { Console.Write("."); } dotPrinted = true; } // Check if total number of practices have been met if (games >= practiceGames) { enoughTraining = true; } // Train the second agent, assuming they are not sufficiently trained if (!enoughTraining) { checkerState = agentTwo.Act(checkerState.SuccessorsWhite) as CheckerState; checkerState.GoalTest(); games = agentOne.Victories + agentTwo.Victories + agentOne.Draws; // Account for every 1% of progress if (games % (practiceGames / 100) == 0 && !dotPrinted) { // Update competitiveness at quarters if (ticks % 25 == 0) { // Display quarter in progress bar if (showOutput) { Console.Write($"[{ticks}]"); } // Make each agent more competitive agentOne.TrainingMode((100.0 - ticks) / 100.0); agentTwo.TrainingMode((100.0 - ticks) / 100.0); } // Update ticks ticks += 1; // Don't include hundredth tick if (ticks < 100 && showOutput) { Console.Write("."); } dotPrinted = true; } // Check if total number of practices have been met if (games >= practiceGames) { enoughTraining = true; } } } // End progress bar if (showOutput) { Console.WriteLine(" ]"); Console.WriteLine("\n"); } // Put agents in competitive mode agentOne.CompeteMode(); agentTwo.CompeteMode(); }
public void Run() { if (!quiet) { Console.WriteLine("RL Checkers"); } IState state = new CheckerState(); CheckerState checkerState = state as CheckerState; Agent playerOne = new Agent(); Agent playerTwo = new Agent(); // Wire player behaviors to state events CheckerState.WireEvents(checkerState, playerOne, playerTwo); // Train agents if (!useInFile) { CheckerState.TrainZeroSum(training, !quiet, playerOne, playerTwo); } // Handle Serialization if (useInFile) { Utilities.ReadInFile(inFile, playerOne, playerTwo); } if (useOutFile) { Utilities.WriteOutFile(outFile, playerOne); } // Determine victory, defeat, and cat's game events bool playerTwoVictory = false; bool playerTwoDefeat = false; Action <object, EventArgs> declareVictory = (object sender, EventArgs e) => playerTwoVictory = true; Action <object, EventArgs> declareDefeat = (object sender, EventArgs e) => playerTwoDefeat = true; CheckerState.WhiteWins += declareVictory; CheckerState.BlackWins += declareDefeat; // Player competes with computer while (!playerTwoVictory && !playerTwoDefeat) { checkerState = playerOne.Act(checkerState.SuccessorsBlack) as CheckerState; checkerState.GoalTest(); if (!playerTwoVictory && !playerTwoDefeat) { // Display move options List <IState> options = checkerState.SuccessorsWhite; int moveIndex = 0; bool validMove = false; // Prompt the user while (!validMove) { try { // Receive user input CheckerState.PrintOptions(options); Console.Write("Please select a move: "); moveIndex = Int32.Parse(Console.ReadLine()) - 1; // If move is not possible, throw exception if (moveIndex < 0 || moveIndex >= options.Count) { throw new InvalidMoveException(moveIndex + 1); } // If no error was raised, mark the move as valid validMove = true; } catch (InvalidMoveException e) { Console.Write(e.Message); Console.WriteLine("; please select from the options listed."); } } // Assign the state checkerState = options[moveIndex] as CheckerState; checkerState.GoalTest(); } // Select outcome if (playerTwoVictory) { Console.WriteLine("Human player wins!"); } if (playerTwoDefeat) { Console.WriteLine("Computer wins!"); } } }