void MoveToNext(ref CardList <CardInPlay>[] current, CardList <CardInPlay> next, double timestamp, bool isInitialDraw, bool isLocalPlayer) { CardList <CardInPlay> stationaryResult = new CardList <CardInPlay>(); CardList <CardInPlay> movedResult = new CardList <CardInPlay>(); // For each card in 'next', look for a card in 'current' that's in the same zone // If found, remove from both and add to stationaryCards set for (int i = 0; i < NumZones; i++) { stationaryResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[i], (x, y) => { // Skip values in next that are incorrect zone if ((int)x.CurrentZone != i) { return(-1); } int z = x.TheCard.Cost - y.TheCard.Cost; if (z == 0) { z = x.TheCard.Name.CompareTo(y.TheCard.Name); } return(z); }, (x, y) => { y.BoundingBox = x.BoundingBox; y.NormalizedBoundingBox = x.NormalizedBoundingBox; y.NormalizedCenter = x.NormalizedCenter; return(y); })); } // For each card in next, look for a card in 'current' Ether zone that may have returned to the same zone movedResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[(int)PlayZone.Ether], (x, y) => { // Skip values in next that are incorrect zone if (y.CurrentZone != PlayZone.Ether) { return(-1); } int z = x.TheCard.Cost - y.TheCard.Cost; if (z == 0) { z = x.TheCard.Name.CompareTo(y.TheCard.Name); } // Skip values in current that are incorrect zone if (z == 0 && x.CurrentZone != y.LastNonEtherZone) { z = 1; } return(z); }, (x, y) => { y.BoundingBox = x.BoundingBox; y.NormalizedBoundingBox = x.NormalizedBoundingBox; y.NormalizedCenter = x.NormalizedCenter; y.MoveToZone(x.CurrentZone, timestamp); return(y); })); // For each card in next, look for a card in 'current' Ether zone that may has an approved transition movedResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[(int)PlayZone.Ether], (x, y) => { // Skip values in next that are incorrect zone if (y.CurrentZone != PlayZone.Ether) { return(-1); } int z = x.TheCard.Cost - y.TheCard.Cost; if (z == 0) { z = x.TheCard.Name.CompareTo(y.TheCard.Name); } // Skip values in current that are incorrect zone if (z == 0 && GameBoard.TransitionResult.Proceed != GameBoard.TransitionAllowed(y.LastNonEtherZone, PlayZone.Unknown, x.CurrentZone, isInitialDraw, isLocalPlayer)) { z = -1; } return(z); }, (x, y) => { x.LastNonEtherZone = y.LastNonEtherZone; x.LastZone = PlayZone.Ether; x.IsFromDeck = y.IsFromDeck; x.ChampionCode = y.ChampionCode; return(x); })); // For each card in next, look for approved transitions, skipping current in deck for (int i = 0; i < NumZones; i++) { if (i == (int)PlayZone.Deck) { continue; } movedResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[i], (x, y) => { // Skip values in next that are incorrect zone int z = x.TheCard.Cost - y.TheCard.Cost; if (z == 0) { z = x.TheCard.Name.CompareTo(y.TheCard.Name); } if (z == 0 && GameBoard.TransitionResult.Proceed != GameBoard.TransitionAllowed(y.CurrentZone, y.LastNonEtherZone, x.CurrentZone, isInitialDraw, isLocalPlayer)) { z = -1; } return(z); }, (x, y) => { x.SetLastZone(y.CurrentZone); x.IsFromDeck = y.IsFromDeck; x.ChampionCode = y.ChampionCode; return(x); })); } // For each card in next, look for declined transitions for (int i = 0; i < NumZones; i++) { stationaryResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[i], (x, y) => { // Skip values in next that are incorrect zone int z = x.TheCard.Cost - y.TheCard.Cost; if (z == 0) { z = x.TheCard.Name.CompareTo(y.TheCard.Name); } if (z == 0 && GameBoard.TransitionResult.Stay != GameBoard.TransitionAllowed(y.CurrentZone, y.LastNonEtherZone, x.CurrentZone, isInitialDraw, isLocalPlayer)) { z = -1; } return(z); }, (x, y) => { y.BoundingBox = x.BoundingBox; y.NormalizedBoundingBox = x.NormalizedBoundingBox; y.NormalizedCenter = x.NormalizedCenter; return(y); })); } // For each card in 'hand', look for champion transformations stationaryResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[(int)PlayZone.Hand], (x, y) => { // Skip values in next that are incorrect zone if ((int)x.CurrentZone != (int)PlayZone.Hand) { return(-1); } if (y.ChampionCode.Length == 0) { return(-1); } if (y.TheCard.SuperType == "Champion") { var spellCode = y.TheCard.AssociatedCardCodes.Last(); var spellCard = CardLibrary.GetCard(spellCode); if (spellCard.FlavorText == x.TheCard.FlavorText) { return(0); } } else if (x.TheCard.SuperType == "Champion") { if (x.CardCode == y.ChampionCode) { return(0); } } return(-1); }, (x, y) => { x.LastZone = y.CurrentZone; x.IsFromDeck = y.IsFromDeck; x.ChampionCode = y.ChampionCode; return(x); })); // For each card in next, look for approved transitions from deck movedResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[(int)PlayZone.Deck], (x, y) => { // Skip values in next that are incorrect zone int z = x.TheCard.Cost - y.TheCard.Cost; if (z == 0) { z = x.TheCard.Name.CompareTo(y.TheCard.Name); } if (z == 0 && GameBoard.TransitionResult.Proceed != GameBoard.TransitionAllowed(y.CurrentZone, y.LastNonEtherZone, x.CurrentZone, isInitialDraw, isLocalPlayer)) { z = -1; } return(z); }, (x, y) => { x.SetLastZone(y.CurrentZone); x.IsFromDeck = y.IsFromDeck; x.ChampionCode = y.ChampionCode; return(x); })); // Move not in deck to ether for (int i = 0; i < NumZones; i++) { PlayZone newZone = PlayZone.Ether; if (i == (int)PlayZone.Deck || i == (int)PlayZone.Graveyard || i == (int)PlayZone.Ether) { continue; } if (i == (int)PlayZone.Stage && isInitialDraw) { newZone = PlayZone.Deck; } for (int j = 0; j < current[i].Count; j++) { current[i][j].MoveToZone(newZone, timestamp); } movedResult.AddRange(current[i]); current[i].Clear(); } // Remove cards in next that are in a zone that does not accept from Unknown next = next.GetSubset(x => GameBoard.TransitionResult.Proceed == GameBoard.TransitionAllowed(x.LastNonEtherZone, PlayZone.Unknown, x.CurrentZone, isInitialDraw, isLocalPlayer)); // Add remaining cards to the moved set movedResult.AddRange(next); // Log all moves foreach (var card in movedResult) { LogMove(card, card.CurrentZone != PlayZone.Ether); } // Re-add all the cars to the zones for (int i = 0; i < NumZones; i++) { current[i].AddRange(stationaryResult.GetSubset(x => (int)x.CurrentZone == i)); current[i].AddRange(movedResult.GetSubset(x => (int)x.CurrentZone == i)); } }
/// <summary> /// Process next rectangle layout /// </summary> /// <param name="overlay"></param> /// <param name="timestamp"></param> public void ProcessNext(Dictionary <string, JsonElement> overlay, double timestamp) { TimeCounter++; if (!TestMode && PlayerCards[(int)PlayZone.Deck].Count == 0 && PlayerCards[(int)PlayZone.Graveyard].Count == 0) { // Have not received the deck yet return; } CardList <CardInPlay> cardsInPlay = new CardList <CardInPlay>(); bool isChampionUpgrading = false; if (overlay != null) { var screen = overlay["Screen"].ToObject <Dictionary <string, JsonElement> >(); ScreenWidth = screen["ScreenWidth"].GetInt32(); ScreenHeight = screen["ScreenHeight"].GetInt32(); // We normalize elements' bounding box based on screen height. However, if screen ratio becomes // too high, screen expands height-wise. To make sure we have same behavior as before, // We adjust the height accordingly. int normalizedScreenHeight = (int)(0.5 + GameBoard.ComputeNormalizedScreenHeight(ScreenWidth, ScreenHeight)); Point correctionOffset = new Point(0, 0); var rectangles = overlay["Rectangles"].ToObject <Dictionary <string, JsonElement>[]>(); foreach (var dict in rectangles) { string cardCode = dict["CardCode"].GetString(); if (cardCode == "face") { if (dict["LocalPlayer"].GetBoolean()) { int x = dict["TopLeftX"].GetInt32(); int y = dict["TopLeftY"].GetInt32(); if (IsInitialDraw) { LocalPlayerFace = new Point(x, y); } else { correctionOffset.X = LocalPlayerFace.X - x; correctionOffset.Y = LocalPlayerFace.Y - y; } } // We don't process face continue; } Card card = CardLibrary.GetCard(cardCode); // Also ignore abilities if (card.Type != "Ability") { CardInPlay c = new CardInPlay(dict, ScreenWidth, ScreenHeight, correctionOffset, normalizedScreenHeight); cardsInPlay.Add(card.Cost, card.Name, c); if (c.CurrentZone == PlayZone.Hand && c.NormalizedCenter.Y > 1.3f) { // Champion is upgrading isChampionUpgrading = true; } } } } // Split next elements between owners. Also, disregard cards with unknown zone CardList <CardInPlay> nextPlayerCards = new CardList <CardInPlay>(); CardList <CardInPlay> nextOpponentCards = new CardList <CardInPlay>(); cardsInPlay.Split(ref nextPlayerCards, ref nextOpponentCards, x => x.Owner == PlayerType.LocalPlayer && x.CurrentZone != PlayZone.Unknown); Callback.OnElementsUpdate(nextPlayerCards, nextOpponentCards, ScreenWidth, ScreenHeight); if (isChampionUpgrading) { // Bail, champion is upgrading return; } if (nextPlayerCards.FindIndex(x => x.CurrentZone == PlayZone.Stage) >= 0) { CardsAreOnStage = true; } else if (CardsAreOnStage) { // Transition, reset ether timers for (int i = 0; i < PlayerCards[(int)PlayZone.Ether].Count; i++) { PlayerCards[(int)PlayZone.Ether][i].EtherStartTime = timestamp; } CardsAreOnStage = false; } // Mark all opponent cards as "from deck" for now // This is because we cannot reliably know which ones are not from deck yet for (int i = 0; i < nextOpponentCards.Count; i++) { nextOpponentCards[i].IsFromDeck = nextOpponentCards[i].TheCard.IsCollectible; } MoveToNext(ref PlayerCards, nextPlayerCards, timestamp, IsInitialDraw, true); if (IsInitialDraw) { // Initial draw until we add some cards to hand IsInitialDraw = (PlayerCards[(int)PlayZone.Hand].Count() == 0); } MoveToNext(ref OpponentCards, nextOpponentCards, timestamp, false, false); // Purge Ether of spells that have been cast bool thoroughCleanUp = false; int handCardIndex = PlayerCards[(int)PlayZone.Hand].FindIndex(x => x.CurrentZone == PlayZone.Hand); if (PlayerCards[(int)PlayZone.Hand].Count() > 0 && PlayerCards[(int)PlayZone.Hand][0].NormalizedBoundingBox.Height < 0.235f) { thoroughCleanUp = true; } // Clean up ether -- generally move expired cards to graveyard, but also send cast champion spells back to deck if (!CardsAreOnStage) { // Find all the expiring CardList <CardInPlay> cardsGoingToGraveyard = CleanUpEther(ref PlayerCards[(int)PlayZone.Ether], timestamp, thoroughCleanUp); for (int i = 0; i < cardsGoingToGraveyard.Count; i++) { if (cardsGoingToGraveyard[i].ChampionCode.Length > 0 && cardsGoingToGraveyard[i].TheCard.Type == "Spell") { var championCard = CardLibrary.GetCard(cardsGoingToGraveyard[i].ChampionCode); var championCardInPlay = new CardInPlay(championCard); championCardInPlay.LastZone = PlayZone.Ether; championCardInPlay.LastNonEtherZone = cardsGoingToGraveyard[i].LastNonEtherZone; championCardInPlay.CurrentZone = PlayZone.Deck; PlayerCards[(int)PlayZone.Deck].Add(championCard.Cost, championCard.Name, championCardInPlay); cardsGoingToGraveyard[i].IsFromDeck = false; Log.WriteLine(LogType.Player, "[{0}{1}] Shuffled back: {2}", championCardInPlay.LastNonEtherZone.ToString()[0], championCardInPlay.CurrentZone.ToString()[0], championCard.Name); } } PlayerCards[(int)PlayZone.Graveyard].AddRange(cardsGoingToGraveyard); } OpponentCards[(int)PlayZone.Graveyard].AddRange(CleanUpEther(ref OpponentCards[(int)PlayZone.Ether], timestamp, true)); NotifyCardSetUpdates(); // Update attacking player int numPlayerAttackers = PlayerCards[(int)PlayZone.Battle].Count + PlayerCards[(int)PlayZone.Windup].Count + PlayerCards[(int)PlayZone.Attack].Count; int numOpponentAttackers = OpponentCards[(int)PlayZone.Battle].Count + OpponentCards[(int)PlayZone.Windup].Count + OpponentCards[(int)PlayZone.Attack].Count; if (AttackingPlayer == PlayerType.None) { if (numPlayerAttackers != numOpponentAttackers) { AttackingPlayer = numPlayerAttackers > numOpponentAttackers ? PlayerType.LocalPlayer : PlayerType.Opponent; } } else if (numPlayerAttackers == 0 && numOpponentAttackers == 0) { AttackingPlayer = PlayerType.None; } // Here we try to identify opponent's cards that did not start in deck // If a card appears in hand and we can see it, it was likely generated // Also, if a card appears in the field without going through stage first // it was likely generated by spell or another unit // Mark these cards as not from deck // Unfortunately, we have no way of tracking nabbed cards, so those will show up // as from deck for (int i = 0; i < OpponentCards[(int)PlayZone.Field].Count; i++) { if (OpponentCards[(int)PlayZone.Field][i].LastNonEtherZone == PlayZone.Unknown) { OpponentCards[(int)PlayZone.Field][i].IsFromDeck = false; } } for (int i = 0; i < OpponentCards[(int)PlayZone.Hand].Count; i++) { if (OpponentCards[(int)PlayZone.Hand][i].LastNonEtherZone == PlayZone.Unknown) { OpponentCards[(int)PlayZone.Hand][i].IsFromDeck = false; } } }