private MoveMgr.Move BestMove(int playerNum, List <MoveMgr.Move> availableMoves) { Grid2D grid = _Board.CloneGrid(); MoveMgr.Move bestMove = null; bestMove = GetWinMove(availableMoves, grid); if (null != bestMove) { return(bestMove); } bestMove = GetBlockOpponentWinMove(availableMoves, GetOpponents(playerNum), grid); if (null != bestMove) { return(bestMove); } bestMove = Get3CornerMove(availableMoves, grid); if (null != bestMove) { return(bestMove); } return(RandomMove(playerNum, availableMoves)); }
/// <summary> /// Checks for diagonal win Falling from left to right. /// </summary> /// <param name="move">Last move made</param> /// <returns>true if move wins</returns> /// <remarks>Works with any size grid</remarks> public bool HasDiagWinFromCell_Fall(MoveMgr.Move move) { int counter = 1; // Up for (int r = move.Row - 1, c = move.Col - 1; r >= 0 && c >= 0; r--, c--) { if (move.PlayerNum != _grid[r, c]) { break; } ++counter; } // Down for (int r = move.Row + 1, c = move.Col + 1; r < NumRows && c < NumCols; r++, c++) { if (move.PlayerNum != _grid[r, c]) { break; } ++counter; } return(counter >= NumConnectionsToWin); }
private MoveMgr.Move BestMove_MCTS(int playerNum, List <MoveMgr.Move> availableMoves) { Grid2D grid = _Board.CloneGrid(); MoveMgr.Move bestMove = _MCTS.GetMove(grid, playerNum); return(bestMove); }
private float GetProbability(int playerNum, MoveMgr.Move move, List <MoveMgr.Move> availableMoves, Grid2D grid) { // TODO design a "Grid2D" class that allows board to be manipulated without side effects (notifing others and yada) if (grid.DoesMoveWin(move)) { return(Win); } if (availableMoves.Count == 0) { return(Draw); } // Apply move to local grid copy. New grid class should have grid.Add(move) and grid.Remove(move) grid.AddMove(move); // TODO: apply opponent moves from available moves // Recurse GetProbabablity() for each opponent move float probablity = 0f; for (int i = 0; i < availableMoves.Count - 1; i++) { List <MoveMgr.Move> nextMoves = availableMoves.GetRange(0, availableMoves.Count); nextMoves.Remove(move); // probablity += GetOpponetProbability(opponentNum, availableMoves[i], nextMoves, grid); } return(probablity / (float)(availableMoves.Count - 1)); }
public Node AddChild(MoveMgr.Move move, IGameState state) { Node n = new Node(state, move, this); _untriedMoves.Remove(move); _children.Add(n); return(n); }
/// <summary> /// Undo a move and rollback state /// </summary> /// <param name="move">Only check row,col and sets to empty</param> public void ClearCell(MoveMgr.Move move) { _grid[move.Row, move.Col] = 0; --NumMovesMade; GameState = PlayState.InProgress; LastPlayerTurn = LastPlayerTurn <= 1 ? NumPlayers : LastPlayerTurn - 1; }
/// <summary> /// Check if move is in a corner. /// </summary> /// <param name="move">A move to check</param> /// <returns>true if move is in any corner</returns> public bool IsCorner(MoveMgr.Move move) { return ((move.Row == 0 && move.Col == 0) || (move.Row == 0 && move.Col == (NumCols - 1)) || (move.Row == (NumRows - 1) && move.Col == 0) || (move.Row == (NumRows - 1) && move.Col == (NumCols - 1))); }
public float GetResult(MoveMgr.Move move, int playerNumPerspective) { Debug.Assert(IsGameOver); if (IsDraw) { return(0.75f); } return(WinningPlayer == playerNumPerspective ? 1.0f : 0.0f); }
/// <summary> /// Try to eliminate BestMoves from availableMoves. If only good moves exist, return the lesser good. /// </summary> /// <param name="playerNum"></param> /// <param name="availableMoves"></param> /// <returns></returns> private MoveMgr.Move WorstMove(int playerNum, List <MoveMgr.Move> availableMoves) { Grid2D grid = _Board.CloneGrid(); MoveMgr.Move bestMove = null; // Skip all winning moves do { if (availableMoves.Count == 1) { return(availableMoves[0]); } else if (null != bestMove) { availableMoves.Remove(bestMove); bestMove.Dispose(); } bestMove = GetWinMove(availableMoves, grid); }while (null != bestMove); // Skip all blocking moves List <int> opponents = GetOpponents(playerNum); do { if (availableMoves.Count == 1) { return(availableMoves[0]); } else if (null != bestMove) { availableMoves.Remove(bestMove); bestMove.Dispose(); } bestMove = GetBlockOpponentWinMove(availableMoves, opponents, grid); }while (null != bestMove); // Skip all 3 corner do { if (availableMoves.Count == 1) { return(availableMoves[0]); } else if (null != bestMove) { availableMoves.Remove(bestMove); bestMove.Dispose(); } bestMove = Get3CornerMove(availableMoves, grid); }while (null != bestMove); return(RandomMove(playerNum, availableMoves)); }
/// <summary> /// Records a move if valid. /// </summary> /// <param name="move">1-NumPlayers</param> /// <remarks>Use CanMove to determine if MakeMove will record the move</remarks> public override void MakeMove(MoveMgr.Move move) { if (!CanMove(move)) { Debug.LogWarning("Invalid move, not recording move."); return; } _LiveGrid.AddMove(move); base.MakeMove(move); }
private void ExecuteLastAIMove() { if (null == NextAIMove) { return; } if (Time.fixedTime > (AiStartMoveTime + AiTimeDelay)) { MoveMgr.Move tmpMove = NextAIMove; NextAIMove = null; _BoardState.MakeMove(tmpMove); } }
/// <summary> /// Checks if move is valid in current state. /// </summary> /// <param name="playerNum">1-NumPlayers</param> /// <param name="row">0-(NumRows-1)</param> /// <param name="col">0-(NumCols-1)</param> /// <returns>true if move is valid</returns> public bool CanMove(MoveMgr.Move move) { if (PlayerTurn != move.PlayerNum) { Debug.LogError("player: " + move.PlayerNum + " tried to move but it's #" + PlayerTurn + "'s turn."); return(false); } if (!IsCellOpen(move.Row, move.Col)) { Debug.LogError("Tried to play in an occupied cell (" + move.Row + "," + move.Col + ")."); return(false); } return(true); }
private void AIMoved(MoveMgr.Move move, List <MoveMgr.Move> availableMoves, AIMoveCallback callback) { try { Debug.Assert(null != move); availableMoves.Remove(move); callback(move); } finally { // Release to pool availableMoves.ForEach(delegate(MoveMgr.Move m) { m.Dispose(); }); } }
/// <summary> /// Check if opponent about to win, and block their win /// </summary> /// <param name="availableMoves">List of available moves</param> /// <param name="opponentNums">List of oppenent numbers</param> /// <param name="grid">The grid to test against</param> /// <returns>A blocking move or null if no wins possible in next play</returns> private MoveMgr.Move GetBlockOpponentWinMove(List <MoveMgr.Move> availableMoves, List <int> opponentNums, Grid2D grid) { foreach (int oppNum in opponentNums) { foreach (MoveMgr.Move move in availableMoves) { using (MoveMgr.Move oppMove = MoveMgr.Move.Fetch(oppNum, move)) { if (grid.DoesMoveWin(oppMove)) { return(move); } } } } return(null); }
public IEnumerator GetMove(int playerNum, GameState.PlayerType playerType, AIMoveCallback callback) { MoveMgr.Move bestMove = null; List <MoveMgr.Move> availableMoves = GetPlayableCells(playerNum); yield return(null); switch (playerType) { case GameState.PlayerType.AI_Easy: { bestMove = WorstMove(playerNum, availableMoves); break; } case GameState.PlayerType.AI_Medium: { bestMove = RandomMove(playerNum, availableMoves); break; } case GameState.PlayerType.AI_Hard: { bestMove = BestMove(playerNum, availableMoves); break; } case GameState.PlayerType.AI_Hard_MCTS: { bestMove = BestMove_MCTS(playerNum, availableMoves); break; } default: { Debug.LogAssertion("Unhandled AI type: " + playerType.ToString()); break; } } AIMoved(bestMove, availableMoves, callback); }
float _probablity; // TODO: This number highly coupled to a given player. Consider storing as an array for each player. public Node(IGameState state, MoveMgr.Move move, Node parent) { ++NodeCount; Parent = parent; Move = move; PlayerNum = move.PlayerNum; if (state.IsDraw) { Winner = DRAW; } else { Winner = state.WinningPlayer; // will return 0 if no current winner. } _untriedMoves = state.GetAvailableMoves(state.PlayerTurn); _children = new List <Node>(_untriedMoves.Count); _visits = 0; _probablity = 0f; }
public bool DoesMoveWin(MoveMgr.Move move) { bool wasTmpValuePushed = false; if (IsCellOpen(move)) { AddMove(move); wasTmpValuePushed = true; } bool isWin = HasHorzWin(move) || HasVertWin(move) || HasDiagWinFromCell(move); if (wasTmpValuePushed) { ClearCell(move); } return(isWin); }
/// <summary> /// Callback from mouse click on a cell in the Tic-Tac-Toe Grid2D /// </summary> /// <param name="rowcol">Comma delimited string with row,col of cell clicked</param> public void OnClickSpot(string rowcol) { if (!IsHumanTurn) { Debug.LogWarning("Not a Human's turn"); return; } if (null != _Hint) { BoardView.ClearHint(_Hint.Row, _Hint.Col); _Hint = null; } // Parse row,col clicked string[] rc = rowcol.Split(','); int row = System.Convert.ToInt32(rc[0]); int col = System.Convert.ToInt32(rc[1]); _BoardState.MakeMove(MoveMgr.Move.Fetch(_BoardState.PlayerTurn, row, col)); }
void IPlayerMoveObserver.Update(MoveMgr.Move move) { // Visual Update BoardView.SetCell(move.Row, move.Col, GetPlayerSymbol(move.PlayerNum)); // Check for win if (_BoardState.IsWinningMove(move)) { GameOver(move.PlayerNum); return; } // Check for Draw if (_BoardState.IsBoardFull) { GameOver(0); return; } UpdatePlayerTurn(); }
public bool HasVertWin(MoveMgr.Move move) { int maxConnected = 0; int counter = 0; for (int row = 0; row < NumRows; row++) { if (move.PlayerNum == _grid[row, move.Col]) { ++counter; if (counter > maxConnected) { maxConnected = counter; } } else { counter = 0; } } return(maxConnected >= NumConnectionsToWin); }
public bool HasHorzWin(MoveMgr.Move move) { int maxConnected = 0; int counter = 0; for (int col = 0; col < NumCols; col++) { if (move.PlayerNum == _grid[move.Row, col]) { ++counter; if (counter > maxConnected) { maxConnected = counter; } } else { counter = 0; } } return(maxConnected >= NumConnectionsToWin); }
/// <summary> /// Only available on Human turn in a Human vs AI game. /// Removes last A.I. Move, then last Human move. /// </summary> public void UndoMove() { if (!IsHumanVsAIGame()) { Debug.LogError("Only Human vs AI games allow undo."); return; } if (!IsHumanTurn) { Debug.LogError("Can only undo on a Human turn."); return; } for (int p = 0; p < NumPlayers; p++) { MoveMgr.Move move = _BoardState.Undo(); if (null == move) { return; } BoardView.SetCell(move.Row, move.Col, string.Empty); } }
public void AddMove(MoveMgr.Move move) { if (move.PlayerNum == 0) { ClearCell(move); return; } _grid[move.Row, move.Col] = move.PlayerNum; LastPlayerTurn = move.PlayerNum; ++NumMovesMade; if (DoesMoveWin(move)) { GameState = (PlayState)move.PlayerNum; } else if (!HasMoves) { GameState = PlayState.Draw; } return; }
public bool HasDiagWinFromCell(MoveMgr.Move move) { return(HasDiagWinFromCell_Rise(move) || HasDiagWinFromCell_Fall(move)); }
public bool IsCellOpen(MoveMgr.Move move) { return(_grid[move.Row, move.Col] == 0); }
public MoveMgr.Move GetMove(IGameState rootState, int playerNum, int maxIterations = 5000) { // Tree reuse /* * if (null == _root) * { * _root = new Node(rootState, MoveMgr.Move.Fetch(playerNum, 0, 0), null); * } else * { * _root = _root.AdvanceToCurrentState(rootState); * } */ // Fresh tree each time (clears visits and probablities) ClearState(); _root = new Node(rootState, MoveMgr.Move.Fetch(playerNum, 0, 0), null); for (int i = 0; i < maxIterations; ++i) { Node node = _root; //TODO: may need to recreate tree each time until probablites array and visits array implemented. Need to save state for each player. IGameState state = rootState.CloneState(); // Select while (node.HasUntriedMoves == false && node.HasChildren) // Fully expanded and non-terminal { node = node.UCTSelectChild(); state.AddMove(node.Move); } // Expand if (node.HasUntriedMoves) { MoveMgr.Move move = node.GetRandomUntriedMove(); state.AddMove(move); node = node.AddChild(move, state); // Add child and descend } // Simulate (Rollout - not adding nodes to Terminal state ) while (!state.IsGameOver) { MoveMgr.Move move = state.GetRandomMove(state.PlayerTurn); state.AddMove(move); } // Prioritize if a win or lose, blacklist/whitelist if (node.IsTerminal && !node.IsDraw) { if (node.Winner != playerNum) { // must blacklist parent which is 'this' players move. //This player cannot lose on their turn, but can play a move that will make them lose on opponents turn. if (null != node.Parent) { node.BlackListParent(); } } else { node.WhiteList(); } } // Backpropagate while (null != node) { node.Update(state.GetResult(node.Move, playerNum)); node = node.Parent; } } _root = _root.GetMostVisitedChild(); _root.TrimTreeTop(); return(_root.Move); }
private void HintCallback(MoveMgr.Move move) { _Hint = move; BoardView.SetHint(_Hint.Row, _Hint.Col); }
private void AIMovedCallback(MoveMgr.Move move) { NextAIMove = move; }
/// <summary> /// Searches for 3 corner win move. If that doesn't exist, random any corner. /// </summary> /// <param name="availableMoves">Available moves for the current player</param> /// <param name="grid">The grid to test against</param> /// <returns>A valid corner move or null if no corner available</returns> private MoveMgr.Move Get3CornerMove(List <MoveMgr.Move> availableMoves, Grid2D grid) { List <MoveMgr.Move> cornerMoves = new List <MoveMgr.Move>(4); foreach (MoveMgr.Move m in availableMoves) { if (grid.IsCorner(m)) { cornerMoves.Add(m); } } if (cornerMoves.Count == 0) { return(null); } else if (cornerMoves.Count == 1) { return(cornerMoves[0]); } else if (cornerMoves.Count < 4) { // 2nd move, counter via center move if (availableMoves.Count == (grid.NumCells - 1)) { MoveMgr.Move bestMove = GetCenterMove(availableMoves); if (null != bestMove) { return(bestMove); } } // 4th move, counter if (availableMoves.Count == (grid.NumCells - 1)) { List <int> opponents = GetOpponents(availableMoves[1].PlayerNum); if (opponents.Count == 1) { if (DoesPlayerHaveOpposingCorners(opponents[0], grid)) { MoveMgr.Move bestMove = GetAnySideCenterMove(availableMoves); if (null != bestMove) { return(bestMove); } } } } // Find opposing corner move which: // 1. Prevents 3 corner win by opponent. // 2. Setup for 3 corner win. foreach (MoveMgr.Move m in cornerMoves) { int row = m.Row == 0 ? grid.MaxRowIndex : 0; int col = m.Col == 0 ? grid.MaxColIndex : 0; if (grid[row, col] != 0) { return(m); } } } // Any random corner. Random to prevent same play each game int index = UnityEngine.Random.Range(0, cornerMoves.Count); return(cornerMoves[index]); }
public bool WasMoveMade(MoveMgr.Move move) { return(_grid[move.Row, move.Col] == move.PlayerNum); }