public void EnemyMove(FightAction enemyAction = null) { if (enemyAction == null) { enemyAction = _Enemies[0].GetAction(TurnNumber); } var history = new List <string>(); StartEnemyTurn(history); if (enemyAction == null) { throw new Exception("No enemy action?"); } if (enemyAction.CardInstance != null) { _Attack(enemyAction.CardInstance, history); enemyAction.AddHistory(history); AssignLastAction(enemyAction); } else { throw new Exception("Noop"); } EnemyDone = true; }
public MonteCarlo(Deck deck, Enemy enemy, Player player, int turnNumber = 0, List <string> firstHand = null) { _Deck = deck ?? throw new ArgumentNullException(nameof(deck)); _Enemy = enemy ?? throw new ArgumentNullException(); _Player = player ?? throw new ArgumentNullException(); _TurnNumber = turnNumber; if (firstHand == null) { //by default shuffle the deck at fight start. _Deck.Reshuffle(new EffectSet(), new List <string>()); } else { var cards = _Deck.FindSetOfCards(_Deck.GetDrawPile, firstHand); var firstAction = new FightAction(FightActionEnum.StartTurn, cardTargets: cards); _FirstAction = firstAction; } var fight = new Fight(_Deck, _Player, _Enemy); fight.TurnNumber = _TurnNumber; var root = new FightNode(fight); fight.FightNode = root; root.StartFight(); Root = root; if (_FirstAction != null) { root.ApplyAction(_FirstAction); } }
internal FightNode EnemyMove(FightAction action) { var c = GetNode(); c.Fight.EnemyMove(action); AddChild(c); return(c); }
/// <summary> /// Call various high level actions on nodes and they'll eventually return ienumerable<FightNode> /// </summary> internal FightNode PlayCard(FightAction action) { var c = GetNode(); c.Fight.PlayCard(action); AddChild(c); return(c); }
internal FightNode DrinkPotion(FightAction action) { var c = GetNode(); var rnd = c.Fight.DrinkPotion(action.Potion, (Enemy)action.Target); AddChild(c); return(c); }
private void GenerateKeyIfNecessary(FightAction action) { if (action.Keys != null) { action.Key = Rnd.Next(action.Keys.Count); //note down which randomness we chose for this so that we can identify duplicates later. } }
internal FightNode StartTurn(FightAction action = null) { var c = GetNode(); c.Fight.StartTurn(action); AddChild(c); return(c); }
public void AssignLastAction(FightAction a) { //unless a fight is part of a fightnode, don't assign history. e.g. tests. if (FightAction != null && FightNode != null) { throw new Exception("protection"); } FightAction = a; }
public bool StartTurn(FightAction action = null) { if (PlayerTurn) { throw new Exception("Turn Already Started"); } if (!EnemyDone) { throw new Exception("Enemy not done"); } PlayerTurn = true; EnemyDone = false; var history = new List <string>(); TurnNumber++; var ef = new EffectSet(); var drawCount = _Player.GetDrawAmount(); List <CardInstance> initialHand = action?.CardTargets.ToList(); if (action?.CardTargets == null) { initialHand = _Deck.DrawCards(drawCount, ef, history); } else { _Deck.ForceDrawCards(_Player, initialHand, ef, history); } history.Add($"Drew: {Helpers.SJ(' ', initialHand.OrderBy(el => el.ToString()))}"); _Player.Energy = _Player.MaxEnergy(); _Player.Block = 0; foreach (var status in _Player.StatusInstances) { status.StartTurn(_Player, ef.PlayerEffect, ef.EnemyEffect); } foreach (var relic in _Player.Relics) { relic.StartTurn(_Player, _Enemies[0], ef); } ApplyEffectSet(ef, _Player, _Enemies[0], history); if (action == null) { action = new FightAction(FightActionEnum.StartTurn, cardTargets: initialHand, history: history); } else { action.History = history; } AssignLastAction(action); return(true); }
public void EnemyMove(int amount, int count) { if (PlayerTurn) { throw new Exception("Not your turn"); } var ea = new FightAction(FightActionEnum.EnemyMove, card: new CardInstance(new EnemyCard(targetType: TargetType.Player, amount, count), 0), hadRandomEffects: true); EnemyMove(ea); }
public override FightAction GetAction(int turn) { if (turn == 1) { var res = new FightAction(FightActionEnum.EnemyMove, hadRandomEffects: true, card: new CardInstance(new EnemyCard(targetType: TargetType.Enemy, buffs: new List <StatusInstance>() { new StatusInstance(new Feather(), 3) }), 0), key: 1); return(res); } else { var res = new FightAction(FightActionEnum.EnemyMove, hadRandomEffects: true, card: new CardInstance(new EnemyCard(targetType: TargetType.Player, 6, 1), 0), key: 1); return(res); } }
public FightNode ApplyAction(FightAction action) { //check if a child already has this action; if so just return that one. //would be a lot better to just MC the child directly rather than MCing the parent and then redoing this traversal. GenerateKeyIfNecessary(action); if (action.Random && action.Key == null) { throw new Exception("Guard"); } var dup = FindDuplicate(action); if (dup != null) { dup.Weight++; return(dup); } switch (action.FightActionType) { case FightActionEnum.PlayCard: var child = PlayCard(action); //non-rnd return(child); case FightActionEnum.Potion: var child2 = DrinkPotion(action); //non-rnd return(child2); case FightActionEnum.EndTurn: var child3 = EndTurn(); //non-rnd return(child3); case FightActionEnum.EnemyMove: var child4 = EnemyMove(action); //rnd return(child4); case FightActionEnum.StartTurn: var child5 = StartTurn(action); return(child5); default: throw new Exception("Invalid action"); } }
/// <summary> /// All playable cards, potions, or endturn. /// Complication: Some cards generate multiple actions (when they have random effects.) /// </summary> public IList <FightAction> GetNormalActions(bool includeUnplayable) { var res = new List <FightAction>(); var hand = _Deck.GetHand; var consideredCis = new List <string>(); var en = _Player.Energy; var includeDuplicates = includeUnplayable ? true : false; foreach (var ci in hand) { if (!includeUnplayable) // { if (consideredCis.Contains(ci.ToString())) { continue; } } var playable = ci.EnergyCost() <= en && ci.Playable(hand); if (includeUnplayable || playable) { var action = new FightAction(fightActionType: FightActionEnum.PlayCard, card: ci, playable: playable); res.Add(action); consideredCis.Add(ci.ToString()); } } var seenPots = new HashSet <string>(); foreach (var pot in _Player.Potions) { var key = pot.ToString(); if (seenPots.Contains(key)) { continue; } seenPots.Add(key); var sa = new FightAction(FightActionEnum.Potion, potion: pot, target: _Enemies[0]); res.Add(sa); } res.Add(new FightAction(FightActionEnum.EndTurn)); return(res); }
/// <summary> /// When we generate an action, we check to see if it's already a duplicate (in randoms or choices). This is how we compare them. /// </summary> public bool IsEqual(FightAction other) { if (FightActionType != other.FightActionType) { return(false); } if (CardInstance?.ToString() != other.CardInstance?.ToString()) { return(false); } if (Potion?.Name != other.Potion?.Name) { return(false); } if (Target?.Name != other.Target?.Name) { return(false); } if (CardTargets != null && other.CardTargets == null) { return(false); } if (CardTargets == null && other.CardTargets != null) { return(false); } if (!Helpers.CompareHands(CardTargets, other.CardTargets, out var msg2)) { return(false); } //if (Key != other.Key) return false; //we do not compare key here because this is at the "choice" stage. if (Random != other.Random) { return(false); } return(true); }
/// <summary> /// Also start the first turn. /// </summary> internal void StartFight() { FightAction = new FightAction(FightActionEnum.StartFight, hadRandomEffects: true); }
/// <summary> /// Add node to the proper place and return it for further MCing /// </summary> public FightNode AddChild(FightNode child) { //interesting, although enemymoves are also randoms, there is no point in having an intermediate copy node. //i.e. Cendturn => RenemyMove => [Rvarious enemy moves] is kind of of pointless //unlike CstartTurn => Rwildstrike => [Rvarious wildstrikes] // => Rother random card => [ROther random card random outputs] child.FightAction = child.Fight.FightAction; if (child.FightAction.FightActionType == FightActionEnum.EnemyMove) { child.Parent = this; child.FightAction = child.Fight.FightAction; Randoms.Add(child); } else if (child.FightAction.Random) { //two cases: //the intermediate node is already created: FightNode intermediateNode = null; foreach (var c in Choices) { if (c.FightAction.IsEqual(child.FightAction)) { intermediateNode = c; break; } } if (intermediateNode == null) { //if we have a random, then consider it a c with this as a random child. intermediateNode = new FightNode(child.Fight.Copy()); intermediateNode.Parent = this; var src = child.Fight.FightAction; //we convert the specced out action into a generic one again. var nonspecifiecAction = new FightAction(fightActionType: src.FightActionType, card: src.CardInstance, cardTargets: src.CardTargets, potion: src.Potion, target: src.Target, hadRandomEffects: src.Random); intermediateNode.FightAction = nonspecifiecAction; //zero these out since at this stage they represent the action pre-randomization. if (intermediateNode.Fight.FightNode != null) { intermediateNode.Fight.FightNode.FightAction.Key = null; } intermediateNode.FightAction.Key = null; Choices.Add(intermediateNode); //there is not a random with this key already since this is the first time we played this card with random children. } //we also have to check for identity with the child nodes. foreach (var other in intermediateNode.Randoms) { if (other.FightAction.IsEqual(child.Fight.FightAction) && other.FightAction.Key == child.Fight.FightAction.Key) { //I should just compare the order of the draw pile actually. other.Weight++; return(other); } else { var ae = 4; } } child.Parent = intermediateNode; child.FightAction = child.Fight.FightAction; //child.fightaction still has its key intermediateNode.Randoms.Add(child); } else { child.Parent = this; child.FightAction = child.Fight.FightAction; Choices.Add(child); } //Fight is over. Calc values. if (child.Fight.Status != FightStatus.Ongoing) { child.CalcValue(); } return(child); }
/// <summary> /// when trying to add a child node, we check if there already is one for the same action. /// complexity: for random actions, we first add a choice then the child randoms. /// This searches for both the child + the child random and if not returns nothing. /// If the intermediate node *does* exist, but the appropriate random child isn't found, /// that will be detected in AddChild /// </summary> private FightNode FindDuplicate(FightAction action) { if (action.FightActionType == FightActionEnum.EnemyMove) { foreach (var r in Randoms) { if (r.FightAction.IsEqual(action)) { return(r); } } } else if (Randoms.Count > 0) { throw new Exception("Should not happen"); } else { if (action.FightActionType == FightActionEnum.PlayCard) { if (Choices.Count > 0) { if (action.CardInstance.Card.Name == nameof(PommelStrike)) { var ae = 4; } } } //there should only be one cardplay of this. var c = Choices.SingleOrDefault(el => el.FightAction.IsEqual(action)); if (c == null) { return(null); } if (c.FightAction.IsEqual(action)) { if (!action.Random) //the nonrandom case; you simply found a normal node that represents this state already. { return(c); } //e.g. we have played wildstrike before so we found it in the choices. else { foreach (var r in c.Randoms) { if (r.FightAction.Key == action.Key) { return(r); } if (r.FightAction.Key == null) { throw new Exception("Should not happen"); } } //there is a node for playing this card, but no appropriate rchild. return(null); } } } return(null); }
/// <summary> /// From monster POV, player is the enemy. /// /// TODO: why not send a cardDescriptor, and have this method just find a matching card? would make external combinatorics easier. /// </summary> public void PlayCard(FightAction action, bool forceExhaust = false, bool newCard = false, IList <CardInstance> source = null) { var cardInstance = action.CardInstance; var cardTargets = action.CardTargets; if (!PlayerTurn) { throw new Exception("Not your turn"); } //get a copy since action was generated from an earlier version. if (source == null) { source = _Deck.GetHand; } cardInstance = Helpers.FindIdenticalCardInSource(source, cardInstance); var history = new List <string>(); if (forceExhaust) { cardInstance.OverrideExhaust = true; } if (newCard) { //don't do checks; definitely playable, etc. } else { if (!cardInstance.Playable(_Deck.GetHand)) { throw new Exception("Trying to play unplayable card."); } if (cardInstance.EnergyCost() > _Player.Energy) { throw new Exception("Trying to play too expensive card"); } var ec = cardInstance.EnergyCost(); _Player.Energy -= ec.En; _Deck.BeforePlayingCard(cardInstance); } IEntity target; switch (cardInstance.Card.TargetType) { case TargetType.Player: target = _Player; break; case TargetType.Enemy: target = _Enemies[0]; break; default: throw new NotImplementedException(); } //set the initial effect, or status. var ef = new EffectSet(); cardInstance.Play(ef, _Player, _Enemies[0], cardTargets, _Deck, action.Key); //generate an effect containing all the changes that will happen. foreach (var si in _Player.StatusInstances) { var statusIsTargeted = _Player == target; si.Apply(cardInstance.Card, ef.PlayerEffect, ef.EnemyEffect, statusIsTargeted, true); } foreach (var si in _Enemies[0].StatusInstances) { var statusIsTargeted = _Enemies[0] == target; si.Apply(cardInstance.Card, ef.PlayerEffect, ef.EnemyEffect, statusIsTargeted, true); } foreach (var relic in _Player.Relics) { relic.CardPlayed(cardInstance.Card, ef, player: _Player, enemy: _Enemies[0]); } //relic effects apply first. //make sure to apply card effects before putting the just played card into discard, so it can't be drawn again by its own action. _Deck.AfterPlayingCard(cardInstance, ef, history); ApplyEffectSet(ef, _Player, _Enemies[0], history: history, ci: cardInstance); //reuse the same action. action.History = history; if (ef.HadRandomness) { action.Random = true; action.Key = ef.Key; } AssignLastAction(action); _Deck.CardPlayCleanup(); }
public void PlayCard(CardInstance ci, List <CardInstance> cardTargets = null, bool forceExhaust = false, bool newCard = false, IList <CardInstance> source = null) { var action = new FightAction(FightActionEnum.PlayCard, card: ci, cardTargets: cardTargets, hadRandomEffects: ci.Card.RandomEffects); PlayCard(action, forceExhaust: forceExhaust, newCard: newCard, source: source); }