public bool CanCast(ManaPool floating) { if (UnpayableManaCost) { return(false); } ManaPool subtractedPool = new ManaPool(floating); bool coloredOk = true; foreach (ColorsEnum color in Enum.GetValues(typeof(ColorsEnum))) { coloredOk &= (subtractedPool[color] -= this[color]) >= 0; } if (!coloredOk) { return(false); } subtractedPool.Generic += subtractedPool.W + subtractedPool.U + subtractedPool.B + subtractedPool.R + subtractedPool.G; return(subtractedPool.Generic >= GenericMana); }
/// <summary> /// Copy constructor for the <see cref="mlaSharp.ManaPool"/> class. /// </summary> /// <param name='mp'> /// ManaPool to copy /// </param> public ManaPool(ManaPool mp) { this.W = mp.W; this.U = mp.U; this.B = mp.B; this.R = mp.R; this.G = mp.G; this.Generic = mp.Generic; }
public bool Equals(ManaPool other) { return((W == other.W) && (U == other.U) && (B == other.B) && (R == other.R) && (G == other.G) && (Generic == other.Generic)); }
private void RemoveManaFromPool(ManaPool pool, ManaCost cost) { // pay colored costs var ColorsEnumValues = Enum.GetValues(typeof(ColorsEnum)); foreach (ColorsEnum color in ColorsEnumValues) { pool[color] -= cost[color]; } // pay generic costs int genericCost = cost.GenericMana; // if can pay with only generic mana, do so if (pool.Generic - genericCost >= 0) { pool.Generic -= genericCost; return; } // otherwise, pay for generic costs by looking through each color genericCost -= pool.Generic; pool.Generic = 0; foreach (ColorsEnum color in ColorsEnumValues) { // if there's enough mana in the current color to pay the rest of the cost, // pay it and break // otherwise, subtract what we can if (pool[color] - genericCost >= 0) { pool[color] -= genericCost; genericCost = 0; break; } genericCost -= pool[color]; pool[color] -= pool[color]; } }
/// <summary> /// Copy constructor for the <see cref="mlaSharp.State"/> class. /// </summary> /// <param name='s'> /// State s to copy. /// </param> public State(State s) { CurrStep = s.CurrStep; PlayersTurn = s.PlayersTurn; PlayerWithPriority = s.PlayerWithPriority; Battlefield = new List <Card>(s.Battlefield); Stack = new List <StackObject>(s.Stack); ManaPools = new Dictionary <Player, ManaPool>(); LifeTotals = new Dictionary <Player, int>(); Hands = new Dictionary <Player, List <Card> >(); Graveyards = new Dictionary <Player, List <Card> >(); HostGameEngine = s.HostGameEngine; // deep copy dictionary entries foreach (Player p in s.HostGameEngine.Players) { ManaPools[p] = new ManaPool(s.ManaPools[p]); LifeTotals[p] = s.LifeTotals[p]; Hands[p] = new List <Card>(s.Hands[p]); Graveyards[p] = new List <Card>(s.Graveyards[p]); } }
/// <summary> /// Enumerate all possible actions from CurrState for a given player. /// </summary> /// <returns> /// The list of actions. /// </returns> /// <param name='p'> /// The player for whom to enumerate the actions. /// </param> public List <ActionDescriptionTuple> EnumActions(Player p) { var actions = new List <ActionDescriptionTuple>(); // if the stack is empty, and that player has priority if (p == CurrState.PlayersTurn && p == CurrState.PlayerWithPriority && CurrState.Stack.Count == 0) { #region Attacks // declareAtk if (p == CurrState.PlayersTurn && CurrState.CurrStep == Steps.declareAtk && !CurrState.AttackersDeclared) { var possibleAttackers = (from c in CurrState.Battlefield where c.Controller == CurrState.PlayersTurn where c.Type.Contains("Creature") where c.ControlTimestamp < CurrState.TurnNumber // TODO: represent summoning sickness correctly where (c.Status & Card.StatusEnum.Tapped) == 0 // i.e. untapped select c as CreatureCard) .ToList(); if (possibleAttackers.Count() > 0) { AttackersToBlockersDictionary = new Dictionary <CreatureCard, IList <CreatureCard> >(); DefendingPlayer = Players[(currentPlayerIndex + 1) % Players.Count]; // TODO: make choosing attackers actually consistent, fix this hack // present the list of actions as comprising only of choosing attackers // warning - number of actions is exponential in number of attackers if (true || p is MctsPlayer) { var allPossibleAttacks = possibleAttackers.PowerSet(); StringBuilder descSb = new StringBuilder(); foreach (var atkSubset in allPossibleAttacks) { var atkSubsetClosureVariable = atkSubset; // needed so the closures point to distinct subsets instead of just the last descSb.Clear(); descSb.Append("Attack with "); foreach (CreatureCard c in atkSubset) { descSb.Append(c.Name); descSb.Append(", "); } if (atkSubset.Count == 0) { descSb.Append("nothing"); // complete description for attacking with nothing } else { descSb.Remove(descSb.Length - 2, 2); // remove trailing ", " } var possibleAttack = new ActionDescriptionTuple() { GameAction = (Player player, State s) => { foreach (CreatureCard c in atkSubsetClosureVariable) { AttackersToBlockersDictionary[c] = null; c.Status |= Card.StatusEnum.Tapped; // TODO: allow for vigilance } // TODO: allow "fast" effects to be played, but for now, skip priority pass // Give "priority" to defending player so they can assign blocks CurrState.PlayerWithPriority = DefendingPlayer; s.MoveToNextStep(); }, ActionDescription = descSb.ToString() }; actions.Add(possibleAttack); } CurrState.AttackersDeclared = true; return(actions); } // TODO: change implementation to allow attacking planeswalkers, multiplayer, etc var chosenAttackers = p.ChooseAttackers(possibleAttackers); // create attackers to blockers dictionary that will be passed to defending player string attackersMsg = "Player " + p.Name + " attacks with "; foreach (var creature in chosenAttackers) { AttackersToBlockersDictionary[creature] = new List <CreatureCard>(); attackersMsg += creature.Name + ", "; } if (chosenAttackers.Count > 0) { logger.Debug(attackersMsg); } } } #endregion // move to the next step var moveToNextStep = new ActionDescriptionTuple() { GameAction = (Player player, State s) => { s.MoveToNextStep(); }, ActionDescription = "Move to the next step." }; actions.Add(moveToNextStep); // for convience, an end turn action is available var endTurn = new ActionDescriptionTuple() { GameAction = (Player player, State s) => { do { s.MoveToNextStep(); }while(s.CurrStep != Steps.main1); }, ActionDescription = "Move to the next turn" }; actions.Add(endTurn); // if it's a main phase, sorcery speed effects can be played if ((CurrState.CurrStep == Steps.main1 || CurrState.CurrStep == Steps.main2)) { // AP can play lands if (CurrState.LandsLeftToPlayThisTurn > 0) { var lands = from c in CurrState.Hands[CurrState.PlayerWithPriority] where c.Type.Contains("Land") select c; foreach (Card land in lands) { var actionDescription = new ActionDescriptionTuple() { GameAction = (Player player, State s) => { s.Battlefield.Add(land); s.Hands[player].Remove(land); land.ControlTimestamp = s.TurnNumber; land.Controller = player; s.LandsLeftToPlayThisTurn--; }, ActionDescription = "Play a " + land.Name + " (land)" }; actions.Add(actionDescription); } } // Cast sorceries and creatures // TODO: make this work for sorceries ManaPool floating = CurrState.ManaPools[CurrState.PlayerWithPriority]; var sorcerySpeedSpells = from c in CurrState.Hands[CurrState.PlayerWithPriority] where c.Type.Contains("Creature") where c.Cost.CanCast(floating) select c; foreach (Card spell in sorcerySpeedSpells) { var spellClosureVar = spell; // needed so the closure refers to this spell, not just the last one. // this doesn't work for sorceries StackObject so = new StackObject(StackObject.StackObjectType.Card, spell.Type, spell.Text, spell.Owner, spell.Owner, spell.Colors, (Player Player, State s) => { s.Battlefield.Add(spellClosureVar); spellClosureVar.ControlTimestamp = s.TurnNumber; spellClosureVar.Controller = Player; }); var actionDescription = new ActionDescriptionTuple() { GameAction = (Player player, State s) => { s.Stack.Add(so); s.Hands[player].Remove(spellClosureVar); RemoveManaFromPool(s.ManaPools[p], spellClosureVar.Cost); }, ActionDescription = "Cast " + spellClosureVar.Name + " (creature)" }; actions.Add(actionDescription); } } } #region Blocks if (CurrState.AttackersDeclared && p == DefendingPlayer && CurrState.CurrStep == Steps.declareBlk && !CurrState.BlockersDeclared) { var possibleBlockers = (from c in CurrState.Battlefield where c.Controller == DefendingPlayer where c.Type.Contains("Creature") where (c.Status & Card.StatusEnum.Tapped) == 0 select c as CreatureCard) .ToList(); if (AttackersToBlockersDictionary == null) { throw new NullReferenceException("AttackersToBlockersDictionary was unexpectedly null"); } if (possibleBlockers.Count() == 0) // give "priority" back to active player { actions.Add(new ActionDescriptionTuple() { GameAction = (Player player, State s) => { CurrState.BlockersDeclared = true; CurrState.PlayerWithPriority = CurrState.PlayersTurn; // TODO: while there are no "fast" effects, skip priority pass CurrState.MoveToNextStep(); }, ActionDescription = "No blocks" }); } else { // TODO: make choosing blockers actually consistent, fix this hack // present the list of actions as comprising only of choosing blocks // warning - number of actions is exponential in the number of attackers AND blockers if (true || p is MctsPlayer) { var attackers = AttackersToBlockersDictionary.Keys.ToList(); var possibleBlocks = GetPossibleBlocks(attackers, possibleBlockers); StringBuilder descSb = new StringBuilder(); foreach (var block in possibleBlocks) { var blockClosureVariable = block; // needed so the closures point to distinct subsets instead of just the last descSb.Clear(); GameActionDelegate ga = (Player player, State s) => { foreach (var t in blockClosureVariable) { AttackersToBlockersDictionary[t.Attacker] = t.Blockers; } // TODO: allow "fast" effects to be played, but for now, skip priority pass // give priority back to active player CurrState.PlayerWithPriority = CurrState.PlayersTurn; s.MoveToNextStep(); }; foreach (var atbTuple in block) { AttackersToBlockersDictionary[atbTuple.Attacker] = atbTuple.Blockers; descSb.Append("Block "); descSb.Append(atbTuple.Attacker.ToString()); descSb.Append(" with "); if (atbTuple.Blockers.Count == 0) { descSb.Append("nothing; "); } else { foreach (var blocker in atbTuple.Blockers) { descSb.Append(blocker.ToString()); descSb.Append(" & "); } descSb.Remove(descSb.Length - 3, 3); // remove trailing " & " descSb.Append("; "); } } //descSb.Remove(descSb.Length-2,2); // remove trailing "; " var possibleBlockDescription = new ActionDescriptionTuple() { GameAction = ga, ActionDescription = descSb.ToString() }; actions.Add(possibleBlockDescription); } CurrState.BlockersDeclared = true; return(actions); } List <CreatureCard> blockersCopy; IDictionary <CreatureCard, IList <CreatureCard> > atbCopy; // query player for legal blocks do { // copy the data structures in case defending player generates invalid blocks blockersCopy = possibleBlockers.ToList(); atbCopy = AttackersToBlockersDictionary.DeepCopy(); // query player for blocks DefendingPlayer.ChooseBlockers(atbCopy, blockersCopy); } while (!LegalBlocks(atbCopy)); AttackersToBlockersDictionary = atbCopy; p.OrderBlockers(AttackersToBlockersDictionary); // display blocks string blockersMsg = "Player " + DefendingPlayer.Name + " blocks:\n"; foreach (var attacker in AttackersToBlockersDictionary.Keys) { // only display attackers that are blocked if (AttackersToBlockersDictionary[attacker].Count > 0) { blockersMsg += attacker.Name + " is blocked by "; foreach (var blockers in AttackersToBlockersDictionary[attacker]) { blockersMsg += blockers.Name + ", "; } blockersMsg += "\n"; } } logger.Debug(blockersMsg); } } #endregion // "instant" speed effects, such as activated abilities, instants if (p == CurrState.PlayerWithPriority) { // check for activated abilities // note, this where clause assumes that only the controllers of cards can activate their abilities // not true for Oona's Prowler, Mindslaver, etc var abilitiesAvailable = from card in CurrState.Battlefield where card.Controller == CurrState.PlayerWithPriority from ab in card.ActivatedAbilities where ab.AbilityAvailable(p, CurrState) select Tuple.Create(card, ab); foreach (var tuple in abilitiesAvailable) { var abilityAction = new ActionDescriptionTuple() { GameAction = tuple.Item2.AbilityAction, ActionDescription = tuple.Item1.Name + "'s activated ability" }; actions.Add(abilityAction); } // TODO: allow casting instants } return(actions); }
public bool Equals(ManaPool other) { return (W == other.W) && (U == other.U) && (B == other.B) && (R == other.R) && (G == other.G) && (Generic == other.Generic); }