public T Remove(IEntity Entity, bool ClearZone = true) { if (Entity.Zone.Type != Zone.INVALID) { #if _ZONE_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Removing " + Entity.ShortDescription + " from " + Entity.Controller.ShortDescription + "'s " + Type + " zone at position " + Entity.ZonePosition); #endif bool removed = (Entity is T ? asList.Remove((T)Entity) : true); if (removed) { if (Entity is T) { updateZonePositions(); } if (ClearZone) { Entity[GameTag.ZONE_POSITION] = 0; Entity[GameTag.ZONE] = (int)Zone.INVALID; } } else { return(default(T)); } } return(Entity is T ? (T)Entity : default(T)); }
public void EnqueueDeferred(IEntity source, QueueAction a) { if (a == null) { return; } #if _QUEUE_DEBUG DebugLog.WriteLine("Queue (Game " + Game.GameId + "): Queueing action " + a + " for " + source.ShortDescription + " at depth " + Depth); #endif var e = initializeAction(source, a); if (OnQueueing != null) { OnQueueing(this, e); // TODO: Count the number of arguments the cancelled action would take and remove those too if (!e.Cancel) { Queue.AddBack(e); } } else { Queue.AddBack(e); } OnQueued?.Invoke(this, e); }
public void Queue(TriggerType type, IEntity source) { // Check for self-death (deathrattle) if (type == OnDeath && source is ICharacter && ((ICharacter)source).MortallyWounded && source.Card.Behaviour.Deathrattle != null) { #if _TRIGGER_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Queueing deathrattle for " + source.ShortDescription); #endif Game.QueueActionBlock(BlockType.TRIGGER, source, source.Card.Behaviour.Deathrattle); QueuedTriggersCount++; } if (!Triggers.ContainsKey(type)) { return; } #if _TRIGGER_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Checking triggers for " + type + " initiated by " + source.ShortDescription); #endif foreach (var entityId in Triggers[type]) { var owningEntity = Game.Entities[entityId]; // OnPlay triggers should be ignored for Self when entering Play zone if (type == OnPlay && owningEntity == source) { continue; } // Ignore entity if not in an active zone Trigger trigger; owningEntity.Card.Behaviour.TriggersByZone[owningEntity.Zone.Type].TryGetValue(type, out trigger); if (trigger != null) { #if _TRIGGER_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Checking trigger conditions for " + owningEntity.ShortDescription); #endif // Test trigger condition if (trigger.Condition?.Eval(owningEntity, source) ?? true) { #if _TRIGGER_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Firing trigger for " + owningEntity.ShortDescription + " with actions: " + string.Join(" ", trigger.Action.Select(a => a.ToString()))); #endif Game.QueueActionBlock(BlockType.TRIGGER, owningEntity, trigger.Action, // Trigger index: 0 for normal entities; -1 for Game; specific index for player if specified, otherwise -1 Index: TriggerIndices.ContainsKey(type) ? TriggerIndices[type] : source == Game ? -1 : source is Player ? -1 : 0); QueuedTriggersCount++; } } #if _TRIGGER_DEBUG else { DebugLog.WriteLine("Game " + Game.GameId + ": Ignoring triggers for " + owningEntity.ShortDescription + " because it wasn't in an active zone"); } #endif } }
public void ForceRun(IEntity owningEntity, ActionGraph g, IEntity target = null) { var actions = g.Unravel(); #if _TRIGGER_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Running forced trigger for " + owningEntity.ShortDescription + " with actions: " + string.Join(" ", actions.Select(a => a.ToString()))); #endif QueuedTriggersCount++; Game.RunActionBlock(BlockType.TRIGGER, owningEntity, actions, target); }
public void StartBlock(IEntity source, List<QueueAction> qa, BlockStart gameBlock = null) { if (qa == null) { Game.OnBlockEmpty(gameBlock); return; } #if _QUEUE_DEBUG DebugLog.WriteLine("Queue (Game " + Game.GameId + "): Spawning new queue at depth " + (Depth + 1) + " for " + source.ShortDescription + " with actions: " + string.Join(" ", qa.Select(a => a.ToString())) + " for action block: " + (gameBlock?.ToString() ?? "none")); #endif QueueStack.Push(Queue); Queue = new Deque<QueueActionEventArgs>(); BlockStack.Push(gameBlock); EnqueueDeferred(source, qa); }
public void Shuffle() { #if _DECK_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Shuffling " + Controller.ShortDescription + "'s deck"); #endif var possiblePositions = Enumerable.Range(1, Count).ToList(); foreach (var c in this) { int selIndex = RNG.Between(0, possiblePositions.Count - 1); // Quicker to just set zone positions than do a ton of same-zone moves c[GameTag.ZONE_POSITION] = possiblePositions[selIndex]; possiblePositions.RemoveAt(selIndex); } SetDirty(); }
private void EndBlock() { if (Depth > 0) { #if _QUEUE_DEBUG System.Diagnostics.Debug.Assert(IsBlockEmpty); DebugLog.WriteLine("Queue (Game " + Game.GameId + "): Destroying queue at depth " + Depth); #endif Queue = QueueStack.Pop(); var gameBlock = BlockStack.Pop(); if (gameBlock != null) Game.OnBlockEmpty(gameBlock); } // When the queue is empty, notify the game - it may refill it with new actions if (IsEmpty) Game.OnQueueEmpty(); }
private void Add(IEntity entity, TriggerType type, Trigger trigger) { #if _TRIGGER_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Associating trigger " + type + " for entity " + entity.ShortDescription + " with game " + Game.GameId); #endif if (Triggers.ContainsKey(type)) { Triggers[type].Add(entity.Id); } else { Triggers = Triggers.Add(type, new List <int> { entity.Id }); } }
public Task<ActionResult> ProcessBlockAsync(object UserData = null) { #if _QUEUE_DEBUG DebugLog.WriteLine("Queue (Game " + Game.GameId + "): Start processing current block"); var depth = Depth; // Block might not be finished if queue was cancelled var result = ProcessAllAsync(UserData, Depth).ContinueWith(x => { if (!LastActionCancelled) DebugLog.WriteLine("Queue (Game " + Game.GameId + "): End processing current block"); return x.Result; }); #else var result = ProcessAllAsync(UserData, Depth); #endif return result; }
public void Add(List <Card> cards) { int nextPos = Count + 1; foreach (var card in cards) { var tags = new Dictionary <GameTag, int> { { GameTag.ZONE, (int)Zone.DECK }, { GameTag.ZONE_POSITION, nextPos++ } }; #if _DECK_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Adding " + card.Name + " to " + Controller.ShortDescription + "'s deck"); #endif Game.Add(Entity.FromCard(card, tags), (Player)Controller); } // Force deck zone contents to update SetDirty(); }
public void Fill() { // TODO: Add filter/availableCards arguments later var deck = Entities.Select(x => x.Card).ToList(); var cardsAlreadyInDeck = deck.Count; var cardsToAdd = StartingCards - cardsAlreadyInDeck; var availableCards = Cards.Wild[HeroClass]; #if _DECK_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Adding " + cardsToAdd + " random cards to " + Controller.ShortDescription + "'s deck"); #endif while (cardsToAdd > 0) { var card = RNG <Card> .Choose(availableCards); if (deck.Count(c => c == card) < card.MaxAllowedInDeck) { deck.Add(card); cardsToAdd--; } } Add(deck.Skip(cardsAlreadyInDeck).ToList()); }
public T Add(IEntity Entity, int ZonePosition = -1) { // Update ownership if (Entity.Game == null) { Game.Add(Entity, (Player)Controller); } if (Type == Zone.INVALID) { return((T)Entity); } if (ZonePosition == -1) { if (Entity is Minion) { ZonePosition = Count + 1; } else if (Entity is Spell) { if (Type == Zone.PLAY) { ZonePosition = 0; } else { ZonePosition = Count + 1; } } else if (Entity is Hero || Entity is HeroPower) { ZonePosition = 0; } } if (Type == Zone.SETASIDE || Type == Zone.GRAVEYARD || (Type == Zone.PLAY && Controller is Game) || Entity is Player) { ZonePosition = 0; } if (ZonePosition != 0) { if (Entity is T) { if (IsFull) { throw new ZoneException("Zone size exceeded when trying to move " + Entity.ShortDescription + " to zone " + Type); } asList.Insert(ZonePosition - 1, (T)Entity); } } Entity[GameTag.ZONE] = (int)Type; if (Type == Zone.GRAVEYARD && Entity is Minion && Entity[GameTag.ZONE] == (int)Zone.PLAY) { Entity.Controller.NumFriendlyMinionsThatDiedThisTurn++; Entity.Controller.NumFriendlyMinionsThatDiedThisGame++; } if (ZonePosition == 0) { Entity[GameTag.ZONE_POSITION] = 0; } else { updateZonePositions(); } #if _ZONE_DEBUG DebugLog.WriteLine("Game " + Game.GameId + ": Adding " + Entity.ShortDescription + " to " + Entity.Controller.ShortDescription + "'s " + Type + " zone at position " + ZonePosition); #endif if (Entity is T) { return((T)Entity); } return(default(T)); }
public async Task <ActionResult> ProcessAllAsync(object UserData = null, int MaxUnwindDepth = 0, bool one = false) { LastActionCancelled = false; bool DoneOne = false; while (!Paused && !IsBlockEmpty && !LastActionCancelled && !DoneOne) { if (one) { DoneOne = true; } // Get next action and make sure it's up to date if cloned from another game var action = Queue.RemoveFront(); action.Game = Game; if (action.Source.Game.GameId != Game.GameId) { action.Source = Game.Entities[action.Source.Id]; } #if _QUEUE_DEBUG DebugLog.WriteLine("Queue (Game " + Game.GameId + "): Dequeued action " + action + " for " + action.Source.ShortDescription + " at depth " + Depth); #endif // Get needed arguments for action from stack action.Args = new ActionResult[action.Action.Args.Count]; for (int i = action.Action.Args.Count - 1; i >= 0; i--) { // Prefer PreCompiledQueueAction items as arguments var arg = action.Action.CompiledArgs[i]; if (!arg.HasResult) { // Otherwise prefer EagerQueueAction items as arguments if (action.Action.EagerArgs[i] != null) { arg = action.Action.EagerArgs[i].Run(Game, action.Source, null); } else { // Otherwise use the ResultStack to get regular QueueAction items as arguments // In this round, only pop arguments for which ActionGraph parameters were supplied // to the QueueAction as these will be at the top of the stack if (action.Action.Args[i] != null) { arg = StackPop(); List <IEntity> eList = arg; if (eList != null && eList.Count > 0 && eList[0].Game != Game) { arg = new List <IEntity>(eList.Select(e => Game.Entities[e.Id])); } } } } action.Args[i] = arg; } // Now go through all of the arguments that weren't supplied as ActionGraphs and pop them off the stack for (int i = action.Action.Args.Count - 1; i >= 0; i--) { if (action.Action.Args[i] == null) { var arg = StackPop(); List <IEntity> eList = arg; if (eList != null && eList.Count > 0 && eList[0].Game != Game) { arg = new List <IEntity>(eList.Select(e => Game.Entities[e.Id])); } action.Args[i] = arg; } } // Replace current UserData with new UserData if supplied if (UserData != null) { this.UserData = UserData; } action.UserData = this.UserData; if (OnActionStarting != null) { OnActionStarting(this, action); if (action.Cancel) { LastActionCancelled = true; continue; } } var actionType = action.Action.GetType(); if (ReplacedActions.ContainsKey(actionType)) { await ReplacedActions[actionType](this, action); // action.Cancel implied when action is replaced LastActionCancelled = true; continue; } if (HasHistory) { AddItem(action); } // Run action and push results onto stack #if _QUEUE_DEBUG DebugLog.WriteLine("Queue (Game " + Game.GameId + "): Running action " + action + " for " + action.Source.ShortDescription + " at depth " + Depth); #endif var @async = action.Action as QueueActionAsync; var result = (@async != null ? await @async.RunAsync(action.Game, action.Source, action.Args) : action.Action.Run(action.Game, action.Source, action.Args)); if (result.HasResult) { StackPush(result); } // The >= allows the current block to unwind for ProcessBlock() while (IsBlockEmpty && Depth >= MaxUnwindDepth) { EndBlock(); if (IsEmpty) { break; } } #if _QUEUE_DEBUG DebugLog.WriteLine("Queue (Game " + Game.GameId + "): Finished action " + action + " for " + action.Source.ShortDescription + " at depth " + Depth); #endif OnAction?.Invoke(this, action); // Propagate cancellation up the chain by only changing it if not already set LastActionCancelled = action.Cancel; } // Return whatever is left on the stack if (LastActionCancelled) { return(ActionResult.None); } var finalResult = ResultStack.Reverse().FirstOrDefault(); if (!Paused && IsEmpty) { StackClear(); } return(finalResult); }