/// <summary> /// We fill in values bottom down. So sometimes when you set a new value you should check parents too. /// Also if we are a choice and just added a worse value, don't recalculate parent. /// </summary> private void SetValue(NodeValue v) { var oldValue = Value; Value = v; switch (GetChoiceType()) { case NodeType.Choice: if (v > oldValue || object.ReferenceEquals(oldValue, null)) { Parent?.CalcValue(); } break; case NodeType.Random: //TODO this is optimizable. Parent?.CalcValue(); break; case NodeType.Leaf: Parent.CalcValue(); break; } }
public static void TestCultistMC() { var turnNumber = 0; var initialDeck = new List <string>() { "Strike", "Strike", "Strike", "Strike", "Strike", "Defend", "Defend", "Defend", "Defend", "Bash" }; //initialHand.AddRange(new List<string>() {"Pummel", "Exhume"}); var hand = gsl(); var drawPile = initialDeck; var exhaustPile = gsl(); var discardPile = gsl(); var enemyHp = 51; var playerHp = 1; var firstDraw = new List <string>() { "Strike", "Strike", "Defend", "Defend", "Bash" }; var statuses = new List <StatusInstance>() { }; if (false) { turnNumber = 1; discardPile = firstDraw; drawPile = gsl("Strike", "Strike", "Defend", "Defend", "Strike"); firstDraw = gsl("Strike", "Strike", "Defend", "Defend", "Strike"); statuses = new List <StatusInstance>() { GS(new Feather(), 3), GS(new Vulnerable(), 1) }; } var enemy = new Cultist(hp: enemyHp); enemy.StatusInstances = statuses; var player = new Player(hp: playerHp); var deck = new Deck(drawPile, hand, discardPile, exhaustPile); var mc = new MonteCarlo(deck, enemy, player, turnNumber, firstDraw); var bestValue = new NodeValue(-100, 0, null); for (var ii = 0; ii < 100000; ii++) { var leaf = mc.MC(mc.Root.Randoms.First()); var value = leaf.GetValue(); if (value > bestValue) { bestValue = value; SaveLeaf(leaf, ii); ShowInitialValues(mc.Root.Randoms.First()); } } System.IO.File.AppendAllText(Output, $"==========Final First move best lines"); ShowInitialValues(mc.Root.Randoms.First()); SaveAllLeaves(mc.Root); }
private void CalcValue() { ValueCalculated = true; switch (GetChoiceType()) { case NodeType.TooLong: var tooLongVal = Fight._Player.HP - Fight._Enemies[0].HP; SetValue(new NodeValue(tooLongVal, 0, null)); break; case NodeType.Leaf: var val = 0; var tt = this; switch (Fight.Status) { case FightStatus.Ongoing: var hh = AALeafHistory(); throw new Exception("Leaves can't have ongoing fights."); //this happens when we get toolong //A //BC // B is toolong, C ended. // we backgrack to A but can't calc b. B is a leaf. //TODO this is obviously wrong. //no point in calculating them as we go. //val = Fight._Player.HP - Fight._Enemies[0].HP; return; case FightStatus.Won: val = Fight._Player.HP; break; case FightStatus.Lost: val = -1 * Fight._Enemies[0].HP; break; } var cards = 0; if (Fight.FightAction.FightActionType == FightActionEnum.PlayCard) { cards++; } SetValue(new NodeValue(val, cards, null)); break; case NodeType.Choice: if (FightAction == null) { throw new Exception("This should not happen"); } NodeValue value = null; FightNode bc = null; foreach (var c in Choices) { var cval = c.GetValue(); if (value == null || cval > value) { value = cval; bc = c; } } int cards2 = 0; //Todo prefer playing cards to playing potions! if (FightAction.FightActionType == FightActionEnum.PlayCard || FightAction.FightActionType == FightActionEnum.Potion) { cards2++; } var myVal = new NodeValue(value.Value, value.Cards + cards2, bc); SetValue(myVal); break; case NodeType.Random: var rsum = 0.0d; var rc = 0; foreach (var r in Randoms) { rsum += r.GetValue().Value *r.Weight; rc += r.Weight; } SetValue(new NodeValue(rsum * 1.0 / rc, 0, null)); //TODO okay to not assign bestchild? it doesn't make sense over random. break; } }