/** * determines if the given GridCell is a valid move for the given player */ private bool isValidMove(ReversiGraph graph, ReversiGraph.GridCell given, Player player) { if (given.State != ReversiGraph.CellState.EMPTY) { return(false); } ReversiGraph.CellState seeking = GetSeekingState(player); //loop through each possible direction to determine if a move is possible foreach (Direction direction in (Direction[])Enum.GetValues(typeof(Direction))) { if (given.Edges.ContainsKey(direction)) { //traverse the current direction, a move is valid only if we find another same state cell AT LEAST two cells away. int depth = graph.Traverse(given, seeking, direction, null, 0); if (depth > 1) { return(true); } } } return(false); }
/** * uses the AI to decide a move for the given player */ private void AsyncronousAIMove(ReversiGraph graph, Player player) { //verify it is this player's turn if (currentTurn != player || IsGameFinished()) { return; } //evaluate the moves and determine the best one //Func<ReversiGraph, Player, int, int, int, Move> searcher = ( ReversiGraph g, Player p, int a, int b, int diff ) => negamax( graph, player, Int32.MinValue, Int32.MaxValue, aiDifficulty ) //make sure to toggle the states properly so that we don't accidentally start another async task aiMoveReady = false; aiThinking = true; //set up our delegate instance currentSearchingTask = ((ReversiGraph g, Player p, int a, int b, int ai) => negamax(g, p, a, b, ai)); //set our AI start time aiStartTime = Time.time; //invoke the delegate asyncronously and store the reference to the AsyncResult used to retrieve the result bestMove = currentSearchingTask.BeginInvoke(graph, player, Int32.MinValue, Int32.MaxValue, aiDifficulty, null, null); //Move bestMove = negamax( graph, player, Int32.MinValue, Int32.MaxValue, aiDifficulty ); //performMove( graph, graph.Cells[bestMove.Cell], player ); }
/** * initialize the graph */ public void Start() { gameGraph = new ReversiGraph(); gamePiece = (GameObject)Resources.Load("piece_model"); currentTurn = Player.WHITE; aiDifficulty = 3; aiMoveReady = false; aiVSai = false; gameover = false; aiThinking = false; }
private ReversiGraph(ReversiGraph other) { //for a copy constructor, we create a fresh instance of the Cells map this.Cells = new Dictionary <string, GridCell>(); this.dirty = true; //add a clone of each cell to our map foreach (GridCell cell in other.Cells.Values) { Cells.Add(cell.Name, cell.Clone()); } }
/** * retrieves a set of all possible moves for the given player */ private HashSet <ReversiGraph.GridCell> GetAllPossibleMoves(ReversiGraph graph, Player player) { HashSet <ReversiGraph.GridCell> allMoves = new HashSet <ReversiGraph.GridCell>(); foreach (ReversiGraph.GridCell cell in graph.Cells.Values) { if (isValidMove(graph, cell, player)) { allMoves.Add(cell); } } return(allMoves); }
/** * performs a move for the given player */ private void performMove(ReversiGraph graph, ReversiGraph.GridCell given, Player player) { if (given.State != ReversiGraph.CellState.EMPTY) { return; } //if ( currentTurn != player ) return; //instantiate a new hashset which will be used to keep track of any grid cells which need to be flipped as a result of this move HashSet <ReversiGraph.GridCell> flipsNeeded = new HashSet <ReversiGraph.GridCell>(); //determine which color we are seeking ReversiGraph.CellState seeking = GetSeekingState(player); //loop through each possible direction and flip any necessary pieces foreach (Direction direction in (Direction[])Enum.GetValues(typeof(Direction))) { //traverse the given direction if needed if (given.Edges.ContainsKey(direction)) { //traverse the direction graph.Traverse(given, seeking, direction, flipsNeeded, 0); } } //we don't need to flip the current cell (although the code is structured such that this would not cause an issue) flipsNeeded.Remove(given); //go through and flip each of the cells which need to be flipped foreach (ReversiGraph.GridCell cell in flipsNeeded) { cell.Flip(graph == gameGraph); } //place a piece at the current position, only if we are working with the actual game board if (gameGraph == graph) { Quaternion rotation = (player == Player.WHITE ? Quaternion.identity : new Quaternion(0f, 0f, 3.1415f, 0f)); GameObject newPiece = (GameObject)Instantiate(gamePiece, given.SpawnPoint, rotation); given.State = seeking; given.Model = newPiece; } }
/** * performs the negamax algorithm using alpha/beta pruning to determine the best possible move */ private Move negamax(ReversiGraph graph, Player player, int alpha, int beta, int depth) { if (graph == null) { return(null); } if (depth <= 0 || graph.IsFinished()) { return(new Move(null, graph.DetermineAdvantageFor(player))); } //if we haven't reached the bottom of the traversal tree and the graph has possible moves, we need to check them further. HashSet <ReversiGraph.GridCell> moves = GetAllPossibleMoves(graph, player); Move bestMove = new Move(null, alpha); //we have moves available! that other guy is going down if (moves.Count > 0) { //to add an element of randomness, I'm going to alter this algorithm to give the AI a way to randomly choose between equal moves ArrayList bestMoves = new ArrayList(); foreach (ReversiGraph.GridCell cell in moves) { //we need to duplicate our graph for the recursive call, so that our current board isn't maniplulated. luckily this is efficient //because we don't copy all the edges in every graph clone, we use a lookup table for that. ReversiGraph cloned = graph.Clone(); //perform the move on the cloned graph using the current player performMove(cloned, cloned.Cells[cell.Name], player); //use recursion to determine the opponents next best move Player oppPlayer = GetOppositePlayer(player); //extra comment for debugging issue (monodevelop sucks) Move opponentsBestMove = negamax(cloned, oppPlayer, -beta, -alpha, depth - 1); //we flip our opponents score, to determine how much we like it. if the move is better for us, we save it. int score = -opponentsBestMove.Score; if (alpha < score) { alpha = score; bestMove = new Move(cell.Name, score); //storing a list of equal moves will give us an element of randomness. we still store bestMove though in the event of pruning bestMoves.Clear(); bestMoves.Add(bestMove); } else if (alpha == score) { bestMoves.Add(new Move(cell.Name, score)); } //this is the alpha beta pruning. If this statement evaluates to true, it means we cannot possibly find a more optimum move down the recursive tree in this direction if (alpha >= beta) { return(bestMove); } } if (bestMoves.Count > 1) { //bestMove = (Move) bestMoves[UnityEngine.Random.Range( 0, bestMoves.Count - 1 )]; bestMove = (Move)bestMoves[new System.Random().Next(bestMoves.Count)]; } } //if we have no moves available and given that the depth is above 0, it is the other player's turn else { //check for the opposite player moves = GetAllPossibleMoves(graph, GetOppositePlayer(player)); //our opponent is able to move if (moves.Count > 0) { //we don't need to clone the graph, since we made no changes to it, so recursively call negamax as the opposite player Move opponentsBestMove = negamax(graph, GetOppositePlayer(player), -beta, -alpha, depth - 1); //we need to invert the returned move, since we want the opposite outcome as our opponent bestMove = new Move(opponentsBestMove.Cell, -opponentsBestMove.Score); } //if no moves are available for him either, the game is over and we determine the winner else { int difference = graph.DetermineAdvantageFor(player); //if the advantage is positive, we win! if (difference > 0) { bestMove = new Move(null, Int32.MaxValue); } //tie game? else if (difference == 0) { bestMove = new Move(null, 0); } //if we get a negative number, we will lose this game. else { bestMove = new Move(null, Int32.MinValue); } } } return(bestMove); }