public Tuple <byte, byte> AIFullTurn(byte givenPiece, byte boardPosition) { MCTSNode_QAI temp = null; byte pieceToGive; byte positionToPlace = 255; if (this.root != null && this.root.flag[qflags.ENDBOARD]) { return(new Tuple <byte, byte>(255, 255)); } if (AITurnUpkeep(givenPiece, boardPosition) == null) { temp = AITurnPart(2000); positionToPlace = this.root.nodeValue; temp = AITurnPart(2000); pieceToGive = this.root.nodeValue; time.Stop(); return(new Tuple <byte, byte>(positionToPlace, pieceToGive)); } else { time.Stop(); return(new Tuple <byte, byte>(positionToPlace, this.root.nodeValue)); } }
//Finds most promising node of currNodes children and returns a reference to it. private MCTSNode_QAI selectMostPromisingNodeForSimulation(MCTSNode_QAI currNode) { MCTSNode_QAI result = null; for (int index = 0; index < currNode.children.Count; index++) { if (!currNode.children[index].flag[qflags.CHILDHASLOSINGBOARD] && !currNode.children[index].flag[qflags.ALLCHILDRENSIMULATED] && !currNode.children[index].flag[qflags.ENDBOARD]) { if (result == null) { result = currNode.children[index]; } else if (currNode.children[index].nodeSimScore(root.score.Item2) > result.nodeSimScore(root.score.Item2)) { result = currNode.children[index]; } } } //possibly move this statement to simulatebranch at pos A.0 if (result == null) { currNode.flag[qflags.ALLCHILDRENSIMULATED] = true; } return(result); }
/* ***METHODS*** */ private Tuple <long, long> simulateBranch(MCTSNode_QAI currNode) { //checks to see if current node is in an simulation end state if (currNode.flag[qflags.ENDBOARD] || currNode.flag[qflags.ALLCHILDRENSIMULATED]) { //currNode.flag[qflags.ENDBOARD] = true; return(new Tuple <long, long>(0, 0)); } if (currNode.possibleChildren.Count != 0) { int newChildDataIndex = random.Next(0, currNode.possibleChildren.Count); MCTSNode_QAI newChild = currNode.addChild(currNode.possibleChildren[newChildDataIndex]); //possibly simplified currNode.possibleChildren.RemoveAt(newChildDataIndex); if (newChild.isNodeScored()) { if (newChild.isLosingNode()) { currNode.flag[qflags.CHILDHASLOSINGBOARD] = true; currNode.flag[qflags.ALLCHILDRENSIMULATED] = true; } else if (newChild.isWinState()) { currNode.flag[qflags.CHILDHASWINNINGBOARD] = true; currNode.flag[qflags.ALLCHILDRENSIMULATED] = true; } return(currNode.addResultToScore(newChild.score));; } else { Tuple <long, long> tempScore = simulateBranch(newChild); return(currNode.addResultToScore(tempScore));; } } else { MCTSNode_QAI nextChild = selectMostPromisingNodeForSimulation(currNode); if (nextChild == null) { //pos A.0 return(new Tuple <long, long>(0, 0)); } else { Tuple <long, long> tempScore = simulateBranch(nextChild); //currNode.addResultToScore(tempScore); return(currNode.addResultToScore(tempScore)); } } }
/* ***METHODS*** */ //adds a new childto the list of the current node public MCTSNode_QAI addChild(byte givenValue) { MCTSNode_QAI newChild = new MCTSNode_QAI(this, givenValue); if (!newChild.flag[qflags.TYPE]) { newChild.boardLogic.playPiece(this.nodeValue, givenValue); newChild.possibleChildren.Remove(nodeValue); } //check to see if child is losingBoard this.children.Add(newChild); return(newChild); }
//simulates tree & return pointer to node containing best next move public MCTSNode_QAI AITurnPart(int endSimulationTime = 2000) { time.Restart(); while (time.ElapsedMilliseconds < endSimulationTime) { simulateBranch(this.root); } root = selectBestMove(this.root); return(root); }
//returns child of root containing search value, null if child not found private MCTSNode_QAI findChildWithVal(byte searchValue) { bool found = false; MCTSNode_QAI newRoot = null; for (int index = 0; index < root.children.Count && !found; index++) { if (root.children[index].nodeValue == searchValue) { newRoot = root.children[index]; found = true; } } return(newRoot); }
//returns null unless it was first turn public Tuple <byte, byte> AITurnUpkeep(byte givenPiece, byte boardPosition, int firstTurnSimulationTime = 4500) { //navigate tree to new state if (this.root == null && givenPiece == 255) //first turn of game { root = new MCTSNode_QAI(); root.flag[qflags.OWNER] = true; root.flag[qflags.TYPE] = true; root.nodeValue = (byte)(random.Next(1, 16) * 15); root.possibleChildren = new List <byte>(root.boardLogic.legalMoves); //simulate tree from seed root time.Restart(); while (time.ElapsedMilliseconds < firstTurnSimulationTime) { simulateBranch(root); } time.Stop(); return(new Tuple <byte, byte>(255, root.nodeValue)); } else if (this.root == null && givenPiece != 255) //second turn of game { root = new MCTSNode_QAI(); root.flag[qflags.OWNER] = false; root.flag[qflags.TYPE] = true; root.nodeValue = givenPiece; root.possibleChildren = new List <byte>(root.boardLogic.legalMoves); //below is a bandage for an issue where the AI would try to give back the first piece it was given //root.boardLogic.unplayedPieces.Remove(givenPiece); } else { MCTSNode_QAI tempRoot = findChildWithVal(boardPosition); if (tempRoot == null) { tempRoot = root.addChild(boardPosition); } root = tempRoot; tempRoot = findChildWithVal(givenPiece); if (tempRoot == null) { tempRoot = root.addChild(givenPiece); } root = tempRoot; } return(null); }
//returns true if AI will win private bool simulatePerfectGame(MCTSNode_QAI currNode) { MCTSNode_QAI bestMove = null; if (currNode.flag[qflags.CHILDHASLOSINGBOARD]) { return(false); } else if (currNode.flag[qflags.CHILDHASWINNINGBOARD]) { return(true); } else if (currNode.flag[qflags.ENDBOARD]) { if (currNode.flag[qflags.OWNER]) { return(currNode.isWinState()); } else { return(currNode.isTie()); } } for (int index = 0; index < currNode.children.Count; index++) { if (currNode.children[index].flag[qflags.OWNER] && (bestMove == null || bestMove.nodePickScore() < currNode.children[index].nodePickScore())) { bestMove = currNode.children[index]; } else if (!currNode.children[index].flag[qflags.OWNER] && (bestMove == null || bestMove.nodePickScore() > currNode.children[index].nodePickScore())) { bestMove = currNode.children[index]; } } return(simulatePerfectGame(bestMove)); }
public MCTSNode_QAI(MCTSNode_QAI parent, byte givenValue) { this.nodeValue = givenValue; this.flag = new bool[6]; parent.flag.CopyTo(this.flag, 0); this.flag[qflags.TYPE] = !this.flag[qflags.TYPE]; if (!this.flag[qflags.TYPE]) { this.flag[qflags.OWNER] = !this.flag[qflags.OWNER]; } if (this.flag[qflags.TYPE]) { this.possibleChildren = new List <byte>(parent.boardLogic.legalMoves); } else { this.possibleChildren = new List <byte>(parent.boardLogic.unplayedPieces); } this.children = new List <MCTSNode_QAI>(); boardLogic = new QBoard(parent.boardLogic); this.score = new Tuple <long, long>(0, 0); }
public Tuple <byte, byte> AIFullTurn_debug(byte givenPiece, byte boardPosition) { MCTSNode_QAI temp = null; byte pieceToGive; byte positionToPlace = 255; if (this.root != null && this.root.flag[qflags.ENDBOARD]) { return(new Tuple <byte, byte>(255, 255)); } if (AITurnUpkeep(givenPiece, boardPosition) == null) { temp = AITurnPart(2000); /*DEBUG*/ Console.WriteLine("\n\nSummary:"); /*DEBUG*/ Console.WriteLine("Counter: "); /*DEBUG*/ Console.WriteLine("Root: " + root.nodeValue + " " + root.score.Item1 + " " + root.score.Item2 + " " + counter); /*DEBUG*/ for (int i = 0; i < root.children.Count; i++) /*DEBUG*/ { /*DEBUG*/ Console.WriteLine("Child" + i + ": " + root.children[i].nodeValue + " " + root.children[i].score.Item1 + " " + root.children[i].score.Item2 + " " + Math.Round((float)root.children[i].nodePickScore(), 4) + " " + Math.Round((float)root.children[i].nodeSimScore(root.score.Item2), 4)); /*DEBUG*/ } this.root = temp; positionToPlace = this.root.nodeValue; temp = AITurnPart(2000); /*DEBUG*/ Console.WriteLine("\n\nSummary:"); /*DEBUG*/ Console.WriteLine("Counter: "); /*DEBUG*/ Console.WriteLine("Root: " + root.nodeValue + " " + root.score.Item1 + " " + root.score.Item2 + " " + counter); /*DEBUG*/ for (int i = 0; i < root.children.Count; i++) /*DEBUG*/ { /*DEBUG*/ Console.WriteLine("Child" + i + ": " + root.children[i].nodeValue + " " + root.children[i].score.Item1 + " " + root.children[i].score.Item2 + " " + Math.Round((float)root.children[i].nodePickScore(), 4) + " " + Math.Round((float)root.children[i].nodeSimScore(root.score.Item2), 4)); /*DEBUG*/ } if (temp != null) { this.root = temp; pieceToGive = this.root.nodeValue; /*DEBUG*/ Console.WriteLine("Selected: " + root.nodeValue + " with score " + root.nodePickScore()); } else { pieceToGive = 0; } time.Stop(); return(new Tuple <byte, byte>(positionToPlace, pieceToGive)); } else { time.Stop(); return(new Tuple <byte, byte>(positionToPlace, this.root.nodeValue)); } }
//Selects best move and returns reference to its noded private MCTSNode_QAI selectBestMove(MCTSNode_QAI currNode) { MCTSNode_QAI selectedMove = null; bool gameWin = false; if (this.difficulty == diff.hard) { bool willWinPerfectGame = false; for (int index = 0; index < currNode.children.Count && !gameWin; index++) { if (!willWinPerfectGame) { if (selectedMove == null) { selectedMove = currNode.children[index]; willWinPerfectGame = simulatePerfectGame(currNode.children[index]); } else { willWinPerfectGame = simulatePerfectGame(currNode.children[index]); if (willWinPerfectGame || selectedMove.nodePickScore() < currNode.children[index].nodePickScore()) { selectedMove = currNode.children[index]; } } } else { if (currNode.children[index].flag[qflags.ENDBOARD]) { selectedMove = currNode.children[index]; gameWin = true; } else if (selectedMove.nodePickScore() < currNode.children[index].nodePickScore()) { if (simulatePerfectGame(currNode.children[index])) { selectedMove = currNode.children[index]; } } } } } else { for (int index = 0; index < currNode.children.Count && !gameWin; index++) { if (compareMove()) { if (selectedMove == null) { selectedMove = currNode.children[index]; } if (currNode.children[index].flag[qflags.ENDBOARD]) { selectedMove = currNode.children[index]; gameWin = true; } else if (selectedMove.nodePickScore() < currNode.children[index].nodePickScore()) { selectedMove = currNode.children[index]; } } } } if (selectedMove == null && currNode.children.Count > 0) { selectedMove = currNode.children[random.Next(0, currNode.children.Count)]; } return(selectedMove); }