private float Simulation(Node nodeToSimulate, POGame.POGame poGame) { float result = -1; int simulationSteps = 0; PlayerTask task = null; List <PlayerTask> taskToSimulate = new List <PlayerTask>(); if (poGame == null) { return(0.5f); } if (nodeToSimulate.task.PlayerTaskType == PlayerTaskType.END_TURN) { return(Estimator.estimateFromState(ESTIMATION_MODE, poGame)); } while (poGame.getCopy().State != SabberStoneCore.Enums.State.COMPLETE) { task = SimulationPolicies.selectSimulationPolicy(SIMULATION_POLICY, poGame, Rnd, greedyAgent, CHILDREN_CONSIDERED_SIMULATING); taskToSimulate.Add(task); if (task.PlayerTaskType != PlayerTaskType.END_TURN) { poGame = poGame.Simulate(taskToSimulate)[taskToSimulate[0]]; } taskToSimulate.Clear(); if (poGame == null) { return(0.5f); } if (task.PlayerTaskType == PlayerTaskType.END_TURN) { return(Estimator.estimateFromState(ESTIMATION_MODE, poGame)); } simulationSteps++; } if (poGame.CurrentPlayer.PlayState == SabberStoneCore.Enums.PlayState.CONCEDED || poGame.CurrentPlayer.PlayState == SabberStoneCore.Enums.PlayState.LOST) { result = 0; } else if (poGame.CurrentPlayer.PlayState == SabberStoneCore.Enums.PlayState.WON) { result = 1; } return(result); }
public POOptionNode(POOptionNode parent, POGame.POGame game, int playerId, PlayerTask playerTask, IScore scoring) { _parent = parent; _game = game.getCopy(); // create clone _playerId = playerId; PlayerTask = playerTask; Scoring = scoring; if (!IsRoot) { Execute(); } }
//public MCTSNode(int playerId, IScore scoring, POGame.POGame game, PlayerTask task, MCTSNode parent) public MCTSNode(int playerId, List <ScoreExt> scorings, POGame.POGame game, PlayerTask task, MCTSNode parent) { _parent = parent; //_scoring = scoring; _scorings = scorings; _playerId = playerId; //_game = game.Clone(); _game = game.getCopy(); _task = task; VisitCount = 1; if (Task != null) { //Game.Process(Task); Dictionary <PlayerTask, POGame.POGame> dir = Game.Simulate(new List <PlayerTask> { Task }); POGame.POGame newGame = dir[Task]; Game = newGame; // simulation has failed, maybe reduce score? if (Game == null) { //_gameState = _endTurn = 1; _endTurn = 1; } else { //if (Game.CurrentPlayer.Choice != null) //{ // Console.WriteLine("Choices are null"); //} _gameState = Game.State == State.RUNNING ? 0 : (PlayerController.PlayState == PlayState.WON ? 1 : -1); _endTurn = Game.CurrentPlayer.Id != _playerId ? 1 : 0; //Scoring.Controller = PlayerController; //_score = Scoring.Rate(); foreach (ScoreExt scoring in Scorings) { scoring.Controller = PlayerController; _score += scoring.Value * scoring.Rate(); } _score /= Scorings.Count; TotalScore += _score; } } }
public override MCTSNode simulate(POGame.POGame game) { POGame.POGame gameCopy = game.getCopy(); // initials root node var initLeafs = new List <MCTSNode>(); var root = new MCTSNode(_playerId, new List <MCTSNode.ScoreExt> { new MCTSNode.ScoreExt(1.0, _scoring) }, gameCopy, null, null); // simulate MCTSNode bestNode = simulate(_deltaTime, root, ref initLeafs); return(bestNode); }
private int Simulate() { POGame.POGame gameClone = Game.getCopy(); int initialPlayer = gameClone.CurrentPlayer.PlayerId; while (true) { if (gameClone.State == SabberStoneCore.Enums.State.COMPLETE) { Controller currPlayer = gameClone.CurrentPlayer; if (currPlayer.PlayState == SabberStoneCore.Enums.PlayState.WON && currPlayer.PlayerId == initialPlayer) { return(1); } if (currPlayer.PlayState == SabberStoneCore.Enums.PlayState.LOST && currPlayer.PlayerId == initialPlayer) { return(0); } } List <PlayerTask> options = gameClone.CurrentPlayer.Options(); int randomNumber = rand.Next(options.Count); PlayerTask action = options[randomNumber]; try { // Process fails as soon as opponent plays a card, so use simulate here Dictionary <PlayerTask, POGame.POGame> dic = gameClone.Simulate(new List <PlayerTask> { action }); gameClone = dic[action]; //更新遊戲狀態 if (gameClone == null) { Debug.WriteLine(action.FullPrint()); } } catch (Exception e) { //Debug.WriteLine("Exception during single game simulation"); //Debug.WriteLine(e.StackTrace); } } }
private Node Selection(Node root, int iterations, ref POGame.POGame poGame) { Node bestNode = new Node(); double bestScore = double.MinValue; double childScore = 0; POGame.POGame pOGameIfSimulationFail = poGame.getCopy(); foreach (Node node in root.children) { childScore = TreePolicies.selectTreePolicy(TREE_POLICY, node, iterations, EXPLORE_CONSTANT, ref poGame, SCORE_IMPORTANCE, greedyAgent); if (childScore > bestScore) { bestScore = childScore; bestNode = node; } } List <PlayerTask> taskToSimulate = new List <PlayerTask>(); taskToSimulate.Add(bestNode.task); if (bestNode.task.PlayerTaskType != PlayerTaskType.END_TURN) { poGame = poGame.Simulate(taskToSimulate)[bestNode.task]; } if (poGame == null) { root.children.Remove(bestNode); if (root.children.Count == 0) { root = root.parent; } poGame = pOGameIfSimulationFail; return(Selection(root, iterations, ref poGame)); } if (bestNode.children.Count != 0) { bestNode = Selection(bestNode, iterations, ref poGame); } return(bestNode); }
public override PlayerTask GetMove(POGame.POGame poGame) { if (poGame.CurrentPlayer.Options().Count == 1) { return(poGame.CurrentPlayer.Options()[0]); } Console.WriteLine("Turn #" + poGame.Turn + ": AlvaroAgent needs to take a decision..."); Console.WriteLine(poGame.FullPrint()); POGame.POGame initialState = poGame.getCopy(); Node root = new Node(); Node selectedNode; Node nodeToSimulate; float scoreOfSimulation; int iterations = 0; InitializeRoot(root, initialState); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (stopwatch.ElapsedMilliseconds <= MAX_TIME) { poGame = initialState; selectedNode = Selection(root, iterations, ref poGame); nodeToSimulate = Expansion(selectedNode, ref poGame); Console.WriteLine("Iteration #" + iterations + ": node to be simulated:" + selectedNode); for (int i = 0; i < NUM_SIMULATIONS; i++) { scoreOfSimulation = Simulation(nodeToSimulate, poGame); Backpropagation(nodeToSimulate, scoreOfSimulation); iterations++; } } stopwatch.Stop(); PlayerTask selectedTask = SelectAction.selectTask(SELECTION_ACTION_METHOD, root, iterations, EXPLORE_CONSTANT); Console.WriteLine("Turn #" + poGame.Turn + ", selected task:" + selectedTask); return(selectedTask); }
private Node Expansion(Node leaf, ref POGame.POGame poGame) { Node nodeToSimulate; POGame.POGame pOGameIfSimulationFail = poGame.getCopy(); if (leaf.timesVisited == 0 || leaf.depth >= TREE_MAXIMUM_DEPTH || leaf.task.PlayerTaskType == PlayerTaskType.END_TURN) { nodeToSimulate = leaf; } else { foreach (PlayerTask task in poGame.CurrentPlayer.Options()) { leaf.children.Add(new Node(task, leaf, leaf.depth + 1)); } nodeToSimulate = leaf.children[0]; List <PlayerTask> taskToSimulate = new List <PlayerTask>(); taskToSimulate.Add(nodeToSimulate.task); if (nodeToSimulate.task.PlayerTaskType != PlayerTaskType.END_TURN) { poGame = poGame.Simulate(taskToSimulate)[nodeToSimulate.task]; } while (poGame == null) { if (leaf.children.Count <= 1) { return(leaf); } poGame = pOGameIfSimulationFail; taskToSimulate.Clear(); leaf.children.Remove(leaf.children[0]); nodeToSimulate = leaf.children[0]; taskToSimulate.Add(nodeToSimulate.task); if (nodeToSimulate.task.PlayerTaskType != PlayerTaskType.END_TURN) { poGame = poGame.Simulate(taskToSimulate)[nodeToSimulate.task]; } } } return(nodeToSimulate); }
/// <summary> /// simulate the chosen move and update scorematrix with it /// </summary> /// <param name="poGame"></param> /// <param name="task"></param> /// <param name="options"></param> /// <returns>returns different Task if simulated Task produces errors</returns> PlayerTask SimulateNextTaskAndUpdateScoreMatrix(POGame.POGame poGame, PlayerTask task, List <PlayerTask> options) { POGame.POGame simulatedGame = poGame.getCopy(); try { Dictionary <PlayerTask, POGame.POGame> simulatedGames = simulatedGame.Simulate(new List <PlayerTask>() { task }); int tries = 0; while ((simulatedGames[task] == null || simulatedGames[task].State == SabberStoneCore.Enums.State.INVALID) && options.Count > 1) { if (tries > 3) { break; } task = options[Rnd.Next(options.Count)]; simulatedGames = simulatedGame.Simulate(new List <PlayerTask>() { task }); tries++; } simulatedGame.Process(task); } catch { } if (simulatedGame != null) { gameStateScoreMatrix.VisitState(GetExtensiveScoreParametersOfGame(simulatedGame)); } // check if game is ending to computer reward if (simulatedGame.CurrentPlayer.PlayState == SabberStoneCore.Enums.PlayState.WON) { Log(LogLevel.INFO, "Might be winning next round, with health: " + simulatedGame.CurrentPlayer.Hero.Health); float additionalReward = Math.Clamp(simulatedGame.CurrentPlayer.Hero.Health / 30, 0, 1) * 100f; reward = 100f + additionalReward; } return(task); }
public NodeGameState(POGame.POGame poGame, PlayerTask task = null, NodeGameState parent = null) { chdr = new List <NodeGameState>(); prt = parent; game = poGame.getCopy(); this.task = task; WasExpanded = false; PlayerId = game.CurrentPlayer.PlayerId; if (parent == null) { isEnemyNode = false; // root is always MyPlayer } else if (PlayerId != parent.PlayerId) { isEnemyNode = !parent.isEnemyNode; } else { isEnemyNode = parent.isEnemyNode; } }
public static PlayerTask GetBestAction(POGame.POGame game, int iterations) { TaskNode root = new TaskNode(null, null, game.getCopy()); for (int i = 0; i < iterations; ++i) { try { TaskNode node = root.SelectNode(); node = node.Expand(); int r = node.SimulateGames(5); node.Backpropagate(r); } catch (Exception e) { Debug.WriteLine(e.Message); Debug.WriteLine(e.StackTrace); } } TaskNode best = null; foreach (TaskNode child in root.Children) { //Console.WriteLine("visits: " + child.TotNumVisits); //Console.WriteLine("wins: " + child.Wins); if (best == null || child.TotNumVisits > best.TotNumVisits) { best = child; } } //Console.WriteLine("best visits: " + best.TotNumVisits); //Console.WriteLine("best wins: " + best.Wins); return(best.Action); }
public Tree(POGame.POGame game) { Root = new Node(new State(game.getCopy(false), null)); }
/* * public static PlayerTask GetBestAction_iteration(POGame.POGame game, int iterations) * { * TaskNode root = new TaskNode(null, null, game.getCopy()); * * for (int i = 0; i < iterations; ++i) * { * try * { * TaskNode node = root.SelectNode(); * node = node.Expand(); * int r = node.SimulateGames(10); * node.Backpropagate(r); * } * catch (Exception e) * { * Debug.WriteLine(e.Message); * Debug.WriteLine(e.StackTrace); * } * } * * TaskNode best = null; * * foreach (TaskNode child in root.Children) * { * //Console.WriteLine("visits: " + child.TotNumVisits); * //Console.WriteLine("wins: " + child.Wins); * * if (best == null || child.TotNumVisits > best.TotNumVisits) * { * best = child; * } * } * * //Console.WriteLine("best visits: " + best.TotNumVisits); * //Console.WriteLine("best wins: " + best.Wins); * * return best.Action; * } */ public static PlayerTask GetBestAction_second(POGame.POGame game, double seconds) { DateTime start = DateTime.Now; TaskNode root = new TaskNode(null, null, game.getCopy()); int i = 0; while (true) { if (TimeUp(start, seconds - 0.1)) { break; } try { TaskNode node = root.SelectNode(); if (TimeUp(start, seconds)) { break; } node = node.Expand(); if (TimeUp(start, seconds)) { break; } int r = node.SimulateGames(5); //預設5 if (TimeUp(start, seconds)) { break; } node.Backpropagate(r); } catch (Exception e) { //Debug.WriteLine(e.Message); //Debug.WriteLine(e.StackTrace); } ++i; } TaskNode best = null; //Console.WriteLine($"Iterations: {i}, Time: " + (DateTime.Now-start).TotalMilliseconds + "ms"); foreach (TaskNode child in root.Children) { //Console.WriteLine("visits: " + child.TotNumVisits); //Console.WriteLine("wins: " + child.Wins); if (best == null || child.TotNumVisits > best.TotNumVisits || (child.TotNumVisits == best.TotNumVisits && child.Wins > best.Wins)) { best = child; } } //Console.WriteLine("best visits: " + best.TotNumVisits); //Console.WriteLine("best wins: " + best.Wins); if (best == null) { //Debug.WriteLine("best == null"); return(game.CurrentPlayer.Options()[0]); } //Console.WriteLine("best wins: " + best.Wins + " best visits: " + best.TotNumVisits); return(best.Action); }
/// <summary> /// TODO: API /// </summary> /// <param name="game"></param> /// <returns></returns> public override MCTSNode simulate(POGame.POGame game) { POGame.POGame gameCopy = game.getCopy(); // initials root node var initLeafs = new List <MCTSNode>(); var root = new MCTSNode(_playerId, new List <MCTSNode.ScoreExt> { new MCTSNode.ScoreExt(1.0, _scoring) }, gameCopy, null, null); // simulate MCTSNode bestNode = simulate(_deltaTime, root, ref initLeafs); // initials opponent's history if (_oppHistory == null) { List <PlayHistoryEntry> history = gameCopy.CurrentOpponent.PlayHistory; PlayHistoryEntry[] copyHistory = new PlayHistoryEntry[history.Count]; history.CopyTo(copyHistory); _oppHistory = copyHistory.ToList(); } var simulationQueue = new Queue <KeyValuePair <POGame.POGame, List <MCTSNode> > >(); simulationQueue.Enqueue(new KeyValuePair <POGame.POGame, List <MCTSNode> >(gameCopy, initLeafs)); int i = 0; while (i < _predictionParameters.SimulationDepth && simulationQueue.Count > 0) { // calculate the lower and upper time bound of the current depth double lowerSimulationTimeBound = _deltaTime + i * (2 * _deltaTime); KeyValuePair <POGame.POGame, List <MCTSNode> > simulation = simulationQueue.Dequeue(); POGame.POGame simulationGame = simulation.Key; List <MCTSNode> leafs = simulation.Value; leafs = leafs.Where(l => l.Game != null) .OrderByDescending(l => l.Score) .Take((leafs.Count > _predictionParameters.LeafCount) ? _predictionParameters.LeafCount : leafs.Count) .ToList(); if (leafs.Count() < 0) { return(bestNode); } Controller opponent = getOpponent(simulationGame); List <Prediction> predicitionMap = getPredictionMap(simulationGame, opponent); var oldSimulations = new Dictionary <POGame.POGame, List <MCTSNode> >(); // the simulation time for one leaf double timePerLeaf = (2 * _deltaTime) / leafs.Count; // get all games from all leaf nodes for (int j = 0; j < leafs.Count; j++) { // calculate the lower time bound of the current leaf double lowerLeafTimeBound = lowerSimulationTimeBound + j * timePerLeaf; MCTSNode leafNode = leafs[j]; POGame.POGame oppGame = leafNode.Game; double leafScore; // XXX: game can be null leafScore = simulateOpponentWithPrediction(lowerLeafTimeBound, timePerLeaf, oppGame, opponent, predicitionMap, ref oldSimulations); // back-propagate score backpropagate(leafNode, leafScore); } var newSimulations = new Dictionary <POGame.POGame, List <MCTSNode> >(); oldSimulations.ToList() .OrderByDescending(s => s.Value.Sum(l => l.TotalScore)) .Take((leafs.Count > _predictionParameters.OverallLeafCount) ? _predictionParameters.OverallLeafCount : leafs.Count) .ToList() .ForEach(l => newSimulations.Add(l.Key, l.Value)); // add new simulations foreach (KeyValuePair <POGame.POGame, List <MCTSNode> > sim in oldSimulations) { simulationQueue.Enqueue(sim); } i++; } return(root.Children .OrderByDescending(c => c.TotalScore) .First()); }
/// <summary> /// TODO: API /// </summary> /// <param name="oppGame"></param> /// <param name="opponent">the controller of the opponent</param> /// <param name="predicitionMap">the map with all predictions</param> /// <param name="newSimulations"></param> /// <returns></returns> private double simulateOpponentWithPrediction(double lowerTimeBound, double timePerLeaf, POGame.POGame oppGame, Controller opponent, List <Prediction> predicitionMap, ref Dictionary <POGame.POGame, List <MCTSNode> > newSimulations) { double predictionScore = 0; if (predicitionMap?.Any() ?? false) { int denominator = predicitionMap.Count; var scorings = predicitionMap.GroupBy(p => p.Deck.Scoring) .Select(c => new MCTSNode.ScoreExt(((double)c.Count() / denominator), c.Key)) .OrderByDescending(s => s.Value).ToList(); // the simulation time for one prediction double timePerPrediction = timePerLeaf / predicitionMap.Count; // use prediction for each game for (int i = 0; i < predicitionMap.Count; i++) { Prediction prediction = predicitionMap[i]; var setasideZone = opponent.ControlledZones[Zone.SETASIDE] as SetasideZone; setasideZone = new SetasideZone(opponent); // create deck zone List <Card> deckCards = prediction.Deck.Cards; var deckZone = opponent.ControlledZones[Zone.DECK] as DeckZone; deckZone = new DeckZone(opponent); createZone(opponent, deckCards, deckZone, ref setasideZone); deckZone.Shuffle(); // create hand zone List <Card> handCards = prediction.Hand.Cards; var handZone = opponent.ControlledZones[Zone.HAND] as HandZone; handZone = new HandZone(opponent); createZone(opponent, handCards, handZone, ref setasideZone); var oppLeafNodes = new List <MCTSNode>(); IScore oppStrategy = prediction.Deck.Scoring; // forward game POGame.POGame forwardGame = oppGame.getCopy(); // upper time bound for simulation the opponent using the current prediction double oppSimulationTime = lowerTimeBound + (i + 1) * timePerPrediction / 2; // simulate opponent's moves while (forwardGame != null && forwardGame.State == State.RUNNING && forwardGame.CurrentPlayer.Id == opponent.Id) { // simulate var oppRoot = new MCTSNode(opponent.Id, scorings, forwardGame, null, null); MCTSNode bestOppNode = simulate(oppSimulationTime, oppRoot, ref oppLeafNodes); // get solution List <PlayerTask> solutions = bestOppNode.GetSolution(); for (int j = 0; j < solutions.Count && (forwardGame != null); j++) { PlayerTask oppTask = solutions[j]; Dictionary <PlayerTask, POGame.POGame> dir = forwardGame.Simulate(new List <PlayerTask> { oppTask }); forwardGame = dir[oppTask]; if (forwardGame != null && forwardGame.CurrentPlayer.Choice != null) { break; } } } // upper time bound for simulation the player using the forwarded game double simulationTime = oppSimulationTime + timePerPrediction / 2; double score = 0; var leafs = new List <MCTSNode>(); // simulate player using forwarded opponent game while (forwardGame != null && forwardGame.State == State.RUNNING && forwardGame.CurrentPlayer.Id == _playerId) { // simulate var root = new MCTSNode(_playerId, new List <MCTSNode.ScoreExt> { new MCTSNode.ScoreExt(1.0, _scoring) }, forwardGame, null, null); MCTSNode bestNode = simulate(simulationTime, root, ref leafs); // get solution List <PlayerTask> solutions = bestNode.GetSolution(); for (int j = 0; j < solutions.Count && (forwardGame != null); j++) { PlayerTask task = solutions[j]; Dictionary <PlayerTask, POGame.POGame> dir = forwardGame.Simulate(new List <PlayerTask> { task }); forwardGame = dir[task]; if (forwardGame != null && forwardGame.CurrentPlayer.Choice != null) { break; } } // TODO: maybe penalty forwardGame == null score = bestNode.TotalScore; } predictionScore += score; if (forwardGame != null) { newSimulations.Add(forwardGame, leafs); } } } return(predictionScore); }
public static int simulation_num = 5; //模擬次數,預設5 /* * public static PlayerTask GetBestAction_iteration(POGame.POGame game, int iterations) * { * TaskNode root = new TaskNode(null, null, game.getCopy()); * * for (int i = 0; i < iterations; ++i) * { * try * { * TaskNode node = root.SelectNode(); * node = node.Expand(); * int r = node.SimulateGames(simulation_num); * node.Backpropagate(r); * } * catch (Exception e) * { * Debug.WriteLine(e.Message); * Debug.WriteLine(e.StackTrace); * } * } * * TaskNode best = null; * * foreach (TaskNode child in root.Children) * { * Console.WriteLine("visits: " + child.TotNumVisits); * Console.WriteLine("wins: " + child.Wins); * * if (best == null || child.TotNumVisits > best.TotNumVisits) * { * best = child; * } * } * * //Console.WriteLine("best visits: " + best.TotNumVisits); * //Console.WriteLine("best wins: " + best.Wins); * * return best.Action; * } */ public static PlayerTask GetBestAction_second(POGame.POGame game, double seconds) { DateTime start = DateTime.Now; TaskNode root = new TaskNode(null, null, game.getCopy()); int i = 0; while (true) { if (TimeUp(start, seconds - 0.1)) { break; } //Board_Analysis(game);//場面分析函式 Controller my = game.CurrentPlayer; Controller op = game.CurrentOpponent; //分配數值 num_my_board = my.BoardZone.Count; num_op_board = op.BoardZone.Count; num_my_hand = my.HandZone.Count; num_op_hand = op.HandZone.Count; num_my_hero = my.Hero.Health; num_op_hero = op.Hero.Health; num_my_deck = my.DeckZone.Count; num_op_deck = op.DeckZone.Count; num_remaining_mana = my.RemainingMana; TreeScore.treescore.tree_score.tree_node = SabberStoneCoreAi.src.Program.main.node_string; c = Math.Round(TreeScore.treescore.tree_score.Node_Evaluation(num_my_board, num_op_board, num_my_hand, num_op_hand, num_my_hero, num_op_hero, num_my_deck, num_op_deck, num_remaining_mana), 2, MidpointRounding.AwayFromZero); //Console.WriteLine(num_my_board+" "+ num_op_board + " " + num_my_hand + " " + num_op_hand + " " + // num_my_hero + " " + num_op_hero + " " + num_my_deck + " " + num_op_deck + " " + num_remaining_mana); if (c < 0) { c = 0; } //Console.WriteLine("c=" + c); //simulation_num = (int) Math.Round(TreeScore.treescore.tree_score.Node_Evaluation(num_my_board, num_op_board, num_my_hand, num_op_hand, // num_my_hero, num_op_hero, num_my_deck, num_op_deck), 0, MidpointRounding.AwayFromZero); //if (simulation_num < 0) //{ // simulation_num = 1; //} //Console.WriteLine("模擬次數為:" + simulation_num); try { TaskNode node = root.SelectNode(); if (TimeUp(start, seconds)) { break; } node = node.Expand(); if (TimeUp(start, seconds)) { break; } int r = node.SimulateGames(simulation_num); //預設5 if (TimeUp(start, seconds)) { break; } node.Backpropagate(r); } catch (Exception e) { //Debug.WriteLine(e.Message); //Debug.WriteLine(e.StackTrace); } ++i; } TaskNode best = null; //Console.WriteLine($"Iterations: {i}, Time: " + (DateTime.Now-start).TotalMilliseconds + "ms"); foreach (TaskNode child in root.Children) { //Console.WriteLine("visits: " + child.TotNumVisits);//刪除註解 //Console.WriteLine("wins: " + child.Wins); if (best == null || child.TotNumVisits > best.TotNumVisits || (child.TotNumVisits == best.TotNumVisits && child.Wins > best.Wins)) { best = child; } } //Console.WriteLine("best visits: " + best.TotNumVisits); //Console.WriteLine("best wins: " + best.Wins); if (best == null) { //Debug.WriteLine("best == null"); return(game.CurrentPlayer.Options()[0]); } //Console.WriteLine("best wins: " + best.Wins + " best visits: " + best.TotNumVisits); return(best.Action); }