/// <summary> /// Run GA against itself till evolution <paramref name="recursionDepth"/> times /// </summary> /// <param name="recursionDepth"></param> public static void RunRecursiveGeneticAlgorithm(int recursionDepth = 3) { //double[] priorGenes = LoadChromosome(@"E:\Source\SamOthellop\SamOthellop\Model\Agents\Genetic\bestchromosome5-10.dat"); FloatingPointChromosome chromosome = new FloatingPointChromosome( new double[] { -100, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, new double[] { 100, 100, 100, 100, 100, 100, 60, 60, 60, 60, 60, 60, 3, 3, 3, 3, 3, 3 }, new int[] { 64, 64, 64, 64, 64, 64, 8, 8, 8, 8, 8, 8, 64, 64, 64, 64, 64, 64 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3 } ); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < recursionDepth; i++) { if (i == 0) { IOthelloAgent randAgent = new RandomAgent(); EvolveGeneticAlgorithm(chromosome, randAgent, "test" + i.ToString()); } else { double[] genes = LoadChromosome(); IOthelloAgent heurAgent = new HeuristicAgent(LoadChromosome(@"E:\Source\SamOthellop\SamOthellop\Model\Agents\Genetic\test" + (i - 1).ToString())); EvolveGeneticAlgorithm(chromosome, heurAgent, "test" + i.ToString()); } } stopwatch.Stop(); Console.WriteLine("Sucessfully recursively geneticially trained. Training cost {0} time with {1} recursions", stopwatch.Elapsed, recursionDepth); }
public double Evaluate(IChromosome chromosome) {//Play n games vs a random (to be Neural Net), % win is Fitness FloatingPointChromosome myChromosome = (FloatingPointChromosome)chromosome; double[] genes = myChromosome.ToFloatingPoints(); double fitness = 0; double wonCount = 0; object wonCountLock = new object(); for (int index = 0; index < TEST_COUNT; index++) { //Parallel.For(0, TEST_COUNT, new ParallelOptions() { MaxDegreeOfParallelism = 2}, // (index) => { BoardStates player = (index % 2 == 0) ? BoardStates.black : BoardStates.white; OthelloGame othelloGame = new OthelloGame(); IEvaluationAgent heurAgent = new HeuristicAgent(genes); while (!othelloGame.GameComplete) { if (othelloGame.WhosTurn == player) { othelloGame.MakeMove(heurAgent.MakeMove(othelloGame, player)); } else { othelloGame.MakeMove(opposingAgent.MakeMove(othelloGame, ~player)); } } if (othelloGame.GameComplete)//just gotta check { if (othelloGame.FinalWinner == player) { lock (wonCountLock) { wonCount++; } } else if (othelloGame.FinalWinner == BoardStates.empty) { lock (wonCountLock) { wonCount += .5; } } } else { throw new Exception("EvaluationFitness didn't complete a game"); } // }); } fitness = (double)wonCount / TEST_COUNT; //System.Diagnostics.Debug.WriteLine("Fitness: " + fitness); return(fitness); }
/// <summary> /// Returns the value of the argument state with respect to the argument node. /// </summary> /// <param name="context">The context of the search.</param> /// <param name="node">The node that provides the context to evaluate the state.</param> /// <param name="state">The state that should be evaluated.</param> /// <returns>Double representing the value of the state with respect to the node.</returns> public double Evaluate(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, TreeSearchNode <SabberStoneState, SabberStoneAction> node, SabberStoneState state) { // Check if we can and want to use the HeuristicBot's evaluation if (UseHeuristicBotEvaluation) { // This scoring function is actually used to score the effect of tasks, but we are using it here to score the effect of the transition from our Source state to the state from which we are currently evaluating. // TODO using the HeuristicBot's evaluation function could be improved var heuristicEvaluation = HeuristicAgent.EvaluateStateTransition(context.Source, state); // Colour the evaluation depending on who the active player is in the state var isRootPlayer = state.CurrentPlayer() == context.Source.CurrentPlayer(); heuristicEvaluation = isRootPlayer ? heuristicEvaluation : heuristicEvaluation * -1; // Normalise the value between -1 and 1. The min and max values have been empirically set and equal the min and max possible evaluations that are returned by the HeuristicBot's function. var norm = 2 * Util.Normalise(heuristicEvaluation, -50, 50) - 1; // Note: this is a transformation from [0,1] to [-1,1] return(norm); } var rootPlayerId = context.Source.CurrentPlayer(); var rootPlayer = state.Player1.Id == rootPlayerId ? state.Player1 : state.Player2; var opponent = rootPlayer.Opponent; // Check for a win/loss if (state.PlayerWon != State.DRAW) { return(state.PlayerWon == rootPlayerId ? 1 : -1); } // Gather stats that we need // TODO gather stats from cards in hand // Opponent HP var oH = opponent.Hero.Health; // Opponent's Taunt Minions HP var opponentMinions = opponent.BoardZone.GetAll(); var oMtH = opponentMinions.Where(i => i.HasTaunt).Sum(j => j.Health); // Opponent's Unknown HP in Hand var oUHh = 0; // Opponent's Unknown Direct Damage in Hand var oDdh = 0; // Opponent's Minion Power var oMP = opponentMinions.Where(i => i.CanAttack).Sum(j => j.AttackDamage); // Opponent's Unknown Minion Power from Hand var oUMPh = 0; // Opponent's Weapon Damage var oWD = opponent.Hero.Weapon?.AttackDamage ?? 0; // Opponent's Fatigue Damage var oFD = opponent.DeckZone.IsEmpty ? opponent.Hero.Fatigue + 1 : 0; // Root Player HP var rH = rootPlayer.Hero.Health; // Root Player's Taunt Minions HP var rootPlayerMinions = rootPlayer.BoardZone.GetAll(); var rMtH = rootPlayerMinions.Where(i => i.HasTaunt).Sum(j => j.Health); // Root Player's HP in Hand var rHh = 0; // Root Player's Direct Damage in Hand var rDdh = 0; // Root Player's Minion Power var rMP = rootPlayerMinions.Where(i => i.CanAttack).Sum(j => j.AttackDamage); // Root Player's Minion Power from Hand var rMPh = 0; // Root Player's Weapon Damage var rWD = rootPlayer.Hero.Weapon?.AttackDamage ?? 0; // Root Player's Fatigue Damage var rFD = rootPlayer.DeckZone.IsEmpty ? rootPlayer.Hero.Fatigue + 1 : 0; // Calculate the approximate turns before the opponent dies var opponentHealth = oH + oMtH + oUHh - oFD - rDdh; var rootPlayerDamage = rMP + rMPh + rWD; var oTD = rootPlayerDamage > 0 ? opponentHealth / (rootPlayerDamage * 1.0) : int.MaxValue; // Calculate the approximate turns before the root player dies var rootPlayerHealth = rH + rMtH + rHh - rFD - oDdh; var opponentDamage = oMP + oUMPh + oWD; var rTD = opponentDamage > 0 ? rootPlayerHealth / (opponentDamage * 1.0) : int.MaxValue; // Check some situations var canKillOpponentThisTurn = (int)Math.Ceiling(oTD) == 1; var canBeKilledByOpponentThisTurn = (int)Math.Ceiling(rTD) == 1; var notARaceSituation = oTD >= 4 && rTD >= 4; // If the root player can kill the opponent, evaluation is 1 if (canKillOpponentThisTurn) { return(1); } // If opponent can't be killed, but they can kill root player next turn, evaluation is -1 if (canBeKilledByOpponentThisTurn) { return(-1); } // If this is not a racing situation (yet), return a cautious number if (notARaceSituation) { // Two aspects here (to keep it simple) // -> root player's HP vs opponent's HP // -> root player's #creatures vs opponent's #creatures // Having more HP ánd more creatures is quite good if (rH > oH && rootPlayerMinions.Length > opponentMinions.Length) { return(0.75); } if (rH > oH && rootPlayerMinions.Length == opponentMinions.Length) { return(0.25); } if (rH > oH && rootPlayerMinions.Length < opponentMinions.Length) { return(0.1); } if (rH == oH && rootPlayerMinions.Length > opponentMinions.Length) { return(0.33); } if (rH == oH && rootPlayerMinions.Length == opponentMinions.Length) { return(0); } if (rH == oH && rootPlayerMinions.Length < opponentMinions.Length) { return(-0.33); } if (rH < oH && rootPlayerMinions.Length > opponentMinions.Length) { return(-0.1); } if (rH < oH && rootPlayerMinions.Length == opponentMinions.Length) { return(-0.25); } // Having less HP ánd less creatures is quite bad if (rH < oH && rootPlayerMinions.Length < opponentMinions.Length) { return(-0.75); } } // If none of the above applies, look at the difference between when the opponent dies and when the root player dies var difference = oTD - rTD; // If the difference is between -1 and 1, it is too close to tell if (difference >= -1 && difference <= 1) { return(0); } // If the difference is negative, it means the root player would die later than the opponent, so the root player would be slightly ahead if (difference < -1) { return(0.5); } // If the difference is positive, it means the opponent would die later than the root player, so the root player would be losing slightly if (difference > 1) { return(-0.5); } throw new ArgumentOutOfRangeException($"Evaluation values do not fall into the expected range: oTD={oTD:F3} | rTD={rTD:F3}"); }