private float MinValue(DejarikChessDuD[] state, int turnDepth, bool secondMove, float alpha, float beta) { if (DejTree.CutOffTest(state, turnDepth)) { return(DejTree.Eval(state)); } else { float v = 420f; for (int i = 0; i < 25; i++) { if (state[i] != null && state[i].Owner == Enemy_Turn) { for (int j = 0; j < 25; j++) { if (state[i].AllPossibleMoves()[j]) { if (state[j] == null) { //We are "Moving" the piece to an empty sector. DejarikChessDuD[] newState = Utilities.FakeMoveChessPiece(state, state[i], j); if (secondMove) { //This is the last move of the turn, next move is going to be ai v = Utilities.Min(v, MaxValue(newState, turnDepth + 1, false, alpha, beta)); } else { //next move is still the real person v = Utilities.Min(v, MinValue(newState, turnDepth, true, alpha, beta)); } } else { //We are "Attacking" another piece int nextTurn = Enemy_Turn; if (secondMove) { nextTurn = Player_Turn; } DejarikChessDuD[] dudState = Utilities.Clone(state); v = Utilities.Max(v, ChanceValue(dudState, i, j, turnDepth, secondMove, alpha, beta, nextTurn)); } if (v <= alpha) { return(v); } if (v < beta) { beta = v; } } } } } return(v); } }
private float MaxValue(DejarikChessDuD[] state, int turnDepth, bool secondMove, float alpha, float beta) { float v = 0f; if (DejTree.CutOffTest(state, turnDepth)) //should be false until last move or a certain depth { v = DejTree.Eval(state); } else { v = -420f; for (int i = 0; i < 25; i++) { if (state[i] != null && state[i].Owner == Player_Turn && state[i].AllPossibleMoves() != null) { for (int j = 0; j < 25; j++) { if (state[i].AllPossibleMoves()[j]) { if (state[j] == null) { DejarikChessDuD[] newState = Utilities.FakeMoveChessPiece(state, state[i], j); //We are "Moving" the piece to an empty sector. if (secondMove) { //This is the last move of the turn, next move is going to be enemy v = Utilities.Max(v, MinValue(newState, turnDepth + 1, false, alpha, beta)); } else { //next move is still ai, but is the last (second) v = Utilities.Max(v, MaxValue(newState, turnDepth, true, alpha, beta)); } } else { //We are "Attacking" another piece //This is the last move of the turn, next move is going to be enemy v = Utilities.Max(v, ChanceValue(state, i, j, turnDepth, secondMove, alpha, beta, Player_Turn)); } if (v >= beta) { return(v); } if (v > alpha) { alpha = v; } } } } } } return(v); }
void ThreadedWork() { try { _threadRunning = true; bool workDone = false; for (int i = 0; i < 25; i++) { int[] adiacents = BoardManager.AdiacentSectors(i); for (int j = 0; j < adiacents.Length; j++) { if (adiacents[j] < 0 || adiacents[j] > 24) { Debug.Log("AdiacentSectors fail: sector " + i + " has adiacent " + adiacents[j]); } } } // This pattern lets us interrupt the work at a safe point if neeeded. int turnDepth = 0; float oldv = -1000f; float v = -1000f; float alpha = -1000f; float beta = 1000f; if (waitingForPush) { int[] possiblePushSectors = BoardManager.AdiacentSectors(pushedPiece.CurrentSector); fromSector = pushedPiece.CurrentSector; int found = 0; int sectorFound = -1; for (int i = 0; i < possiblePushSectors.Length; i++) { if (realState[possiblePushSectors[i]] == null) { found++; sectorFound = possiblePushSectors[i]; } } if (found == 1) { toSector = sectorFound; } else { for (int i = 0; _threadRunning && i < possiblePushSectors.Length; i++) { if (realState[possiblePushSectors[i]] == null) { //we can move the piece into sector "i" DejarikChessDuD[] newState = Utilities.FakeMoveChessPiece(realState, realState[pushedPiece.CurrentSector], possiblePushSectors[i]); if (originalTurn == Player_Turn) { //next action is taken by AI v = Utilities.Max(v, MaxValue(newState, turnDepth, secondMove, alpha, beta)); } else { //next action is taken by a Real Person (tm) v = Utilities.Max(v, MinValue(newState, turnDepth, secondMove, alpha, beta)); } if (oldv != v) { toSector = possiblePushSectors[i]; } if (v >= beta) { workDone = true; break; } if (v > alpha) { alpha = v; } oldv = v; } } } workDone = true; } else { if (DejTree.CutOffTest(realState, turnDepth)) //should be false until last move or a certain depth { v = DejTree.Eval(realState); } else { int i = 0; for (i = 0; _threadRunning && i < 25; i++) { if (realState[i] != null && realState[i].Owner == Player_Turn) { //Debug.Log("Starting to evaluate actions available for piece" + state[i].Name); if (realState[i].AllPossibleMoves() == null) { // Debug.Log("Possible moves for " + state[i].Name + " are null"); continue; } int j = 0; for (j = 0; _threadRunning && j < 25; j++) { bool possibleMove = realState[i].AllPossibleMoves()[j]; if (possibleMove) { if (realState[j] == null) { //We are "Moving" the piece to an empty sector. DejarikChessDuD[] newState = Utilities.FakeMoveChessPiece(realState, realState[i], j); if (secondMove) { //This is the last move of the turn, next move is going to be enemy v = Utilities.Max(v, MinValue(newState, turnDepth + 1, false, alpha, beta)); } else { //next move is still ai, but is the last (second) v = Utilities.Max(v, MaxValue(newState, turnDepth, true, alpha, beta)); } } else { //We are "Attacking" another piece //This is the last move of the turn, next move is going to be enemy int nextTurn = Player_Turn; if (secondMove) { nextTurn = Enemy_Turn; } DejarikChessDuD[] dudState = Utilities.Clone(realState); v = Utilities.Max(v, ChanceValue(dudState, i, j, turnDepth, secondMove, alpha, beta, nextTurn)); } if (oldv != v) { fromSector = i; toSector = j; } if (v >= beta) { //return v; //break out of both cycles and move on workDone = true; break; } if (v > alpha) { alpha = v; } oldv = v; } // Debug.Log("Finished to evaluate action " + j + " for piece" + state[i].Name); } //Debug.Log("Finished to evaluate actions available for piece" + state[i].Name); if (workDone) { break; } } } } } workDone = true; _threadRunning = false; } catch (System.Exception e) { System.Console.WriteLine(e.Message); } }
//This is the main Coroutine, executing every "coroutineInterval" instead of every frame private IEnumerator MainCoroutine() { //Build a minmax tree for imperfect, adversarial, stochastic alpha-beta search. //Every max/min node is separated by a chance node. //Some chance nodes have only 1 child, with probability==1 (deterministic movements) //Every turn contains two moves. //Wait for board to initialize while (!board.initialized) { yield return(new WaitForSeconds(2 * coroutineInterval)); } DejarikChessPiece[] currentRealState = new DejarikChessPiece[25]; DejarikChessDuD[] state = new DejarikChessDuD[25]; float v = -2000f; float oldv = v; int fromSector = -1; int toSector = -1; while (!board.gameEnded) { //version 1: we wait for our turn to calculate everything. while (board.currentTurn != this.Player_Turn) { yield return(new WaitForSeconds(5 * coroutineInterval)); } Debug.Log("Starting search"); //We get the current state of the game for (int i = 0; i < 25; i++) { currentRealState[i] = board.getPieceInSector(i); if (board.getPieceInSector(i) != null) { state[i] = new DejarikChessDuD(currentRealState[i].Name, currentRealState[i].Attack, currentRealState[i].Defense, currentRealState[i].Movement, currentRealState[i].CurrentSector, currentRealState[i].Owner, currentRealState[i].pieceType, currentRealState[i].AllPossibleMoves()); } else { state[i] = null; } } //Simulating first move at level 0 float alpha = -1000f; float beta = 1000f; int turnDepth = 0; bool vFound = false; bool secondMove = (board.actionsLeft == 1); if (board.waitingForPush) { int[] possiblePushSectors = BoardManager.AdiacentSectors(board.pushed.CurrentSector); fromSector = board.pushed.CurrentSector; for (int i = 0; i < possiblePushSectors.Length; i++) { if (state[i] == null) { //we can move the piece into sector "i" DejarikChessDuD[] newState = Utilities.FakeMoveChessPiece(state, state[board.pushed.CurrentSector], i); if ((board.originalTurn == Player_Turn && !secondMove) || (board.originalTurn == Enemy_Turn && secondMove)) { //next action is taken by AI v = Utilities.Max(v, MaxValue(newState, turnDepth, secondMove, alpha, beta)); } else { //next action is taken by a Real Person (tm) v = Utilities.Max(v, MinValue(newState, turnDepth, secondMove, alpha, beta)); } if (oldv != v) { toSector = i; } if (v >= beta) { break; } if (v > alpha) { alpha = v; } oldv = v; } } } else { if (DejTree.CutOffTest(state, turnDepth)) //should be false until last move or a certain depth { v = DejTree.Eval(state); } else { v = -420f; int i = 0; for (i = 0; i < 25; i++) { if (state[i] != null && state[i].Owner == Player_Turn) { Debug.Log("Starting to evaluate actions available for piece" + state[i].Name); if (state[i].AllPossibleMoves() == null) { Debug.Log("Possible moves for " + state[i].Name + " are null"); continue; } int j = 0; for (j = 0; j < 25; j++) { if (state[i].AllPossibleMoves()[j]) { if (state[j] == null) { //We are "Moving" the piece to an empty sector. DejarikChessDuD[] newState = Utilities.FakeMoveChessPiece(state, state[i], j); if (secondMove) { //This is the last move of the turn, next move is going to be enemy v = Utilities.Max(v, MinValue(newState, turnDepth + 1, false, alpha, beta)); } else { //next move is still ai, but is the last (second) v = Utilities.Max(v, MaxValue(newState, turnDepth, true, alpha, beta)); } } else { //We are "Attacking" another piece //This is the last move of the turn, next move is going to be enemy v = Utilities.Max(v, ChanceValue(state, i, j, turnDepth, secondMove, alpha, beta, Player_Turn)); } if (oldv != v) { fromSector = i; toSector = j; } if (v >= beta) { //return v; //break out of both cycles and move on vFound = true; break; } if (v > alpha) { alpha = v; } oldv = v; Debug.Log("Finished to evaluate action " + j + " for piece" + state[i].Name); } yield return(new WaitForSeconds(coroutineInterval)); } //I need to return control to Unity Debug.Log("Finished to evaluate actions available for piece" + state[i].Name); yield return(new WaitForSeconds(coroutineInterval)); if (vFound) { break; } } } } } // the action of the node with value v selectedChessPiece = currentRealState[fromSector]; if (currentRealState[toSector] == null) { if (board.waitingForPush) { PushChessPiece(toSector); } else { MoveChessPiece(toSector); } } else { //attack AttackChessPiece(toSector); } } }