/// <summary> /// Determine similarity between all archetype decks and all cards played by the opponent yet. /// Then update the cardlist displayed in the overlay with the highest matching archetype deck after removing all opponent cards. /// </summary> internal async void UpdateCardList() { // Small delay to guarantee opponents cards list is up to date (should be 300+ ms in debug mode or with attached debugger, otherwise restarting HDT could lead to an incomplete opponent decklist!) #if DEBUG await Task.Delay(1000); #else await Task.Delay(100); #endif // Only continue if in valid game mode or game format if (!IsValidGameMode || !IsValidGameFormat) { return; } // Get opponent's cards list (all yet revealed cards) //var opponentCardlist = Core.Game.Opponent.RevealedCards; IList <Card> opponentCardlist = Core.Game.Opponent.OpponentCardList.Where(x => !x.IsCreated).ToList(); // If opponent's class is unknown yet or we have no imported archetype decks in the database, return empty card list //if (!opponentCardlist.Any() || !ArchetypeDecks.Any()) if (CoreAPI.Game.Opponent.Class == "" || !ArchetypeDecks.Any()) { currentArchetypeDeckGuid = Guid.Empty; _advisorOverlay.Update(new List <Card>(), true); } else { // Create archetype dictionary IDictionary <Deck, float> dict = new Dictionary <Deck, float>(); // Calculate similarities between all opponent's class archetype decks and all yet known opponent cards. Exclude wild decks in standard format using NAND expression. OR expression should also work: (!d.IsWildDeck || CoreAPI.Game.CurrentFormat == Format.Wild) foreach (var archetypeDeck in ArchetypeDecks.Where(d => d.Class == CoreAPI.Game.Opponent.Class && !(d.IsWildDeck && CoreAPI.Game.CurrentFormat == Format.Standard))) { // Insert deck with calculated value into dictionary and prevent exception by inserting duplicate decks if (!dict.ContainsKey(archetypeDeck)) { dict.Add(archetypeDeck, archetypeDeck.Similarity(opponentCardlist)); } } // Get highest similarity value var maxSim = dict.Values.DefaultIfEmpty(0).Max(); // Some unreproducable bug threw an exception here. System.InvalidOperationException: Sequence contains no elements @ IEnumerable.Max() => should be fixed by DefaultIfEmpty() now! // If any archetype deck matches more than MinimumSimilarity (as percentage) show the deck with the highest similarity (important: we need at least 1 deck in dict, otherwise we can't show any results.) if (dict.Count > 0 && maxSim >= Settings.Default.MinimumSimilarity * 0.01) { // Select top decks with highest similarity value var topSimDecks = (from d in dict where Math.Abs(d.Value - maxSim) < 0.001 select d).ToList(); // Select top decks with most played games var maxGames = topSimDecks.Max(x => x.Key.GetPlayedGames()); // If class was something like "Groddo the Bogwarden" in monster hunt, we got an InvalidOperationException at Max() because dict was empty. Now we check for dict > 0 above to prevent this. var topGamesDecks = (from t in topSimDecks where t.Key.GetPlayedGames() == maxGames select t).ToList(); // Select best matched deck with both highest similarity value and most played games var matchedDeck = topGamesDecks.First(); // Show matched deck name and similarity value or number of matching cards and number of all played cards if (Settings.Default.ShowAbsoluteSimilarity) { // Count how many cards from opponent deck are in matched deck int matchingCards = matchedDeck.Key.CountMatchingCards(opponentCardlist); _advisorOverlay.LblArchetype.Text = String.Format("{0} ({1}/{2})", matchedDeck.Key.Name, matchingCards, opponentCardlist.Sum(x => x.Count)); } else { _advisorOverlay.LblArchetype.Text = String.Format("{0} ({1}%)", matchedDeck.Key.Name, Math.Round(matchedDeck.Value * 100, 2)); } _advisorOverlay.LblStats.Text = String.Format("{0}", matchedDeck.Key.Note); Deck deck = DeckList.Instance.Decks.Where(d => d.TagList.ToLowerInvariant().Contains("archetype")).First(d => d.Name == matchedDeck.Key.Name); if (deck != null) { var predictedCards = ((Deck)deck.Clone()).Cards.ToList(); // Remove already played opponent cards from predicted archetype deck. But don't remove revealed jousted cards, because they were only seen and not played yet. foreach (var card in opponentCardlist.Where(x => !x.Jousted)) { if (predictedCards.Contains(card)) { var item = predictedCards.Find(x => x.Id == card.Id); item.Count -= card.Count; } } //var sortedPredictedCards = predictedCards.OrderBy(x => x.Cost).ThenBy(y => y.Name).ToList(); bool isNewArchetypeDeck = currentArchetypeDeckGuid != matchedDeck.Key.DeckId; // Update overlay cards _advisorOverlay.Update(predictedCards, isNewArchetypeDeck); // Remember current archetype deck guid with highest similarity to opponent's played cards currentArchetypeDeckGuid = matchedDeck.Key.DeckId; } } else { // If no archetype deck matches more than MinimumSimilarity clear the list and show the best match percentage _advisorOverlay.LblArchetype.Text = String.Format("Best match: {0}%", Math.Round(maxSim * 100, 2)); _advisorOverlay.LblStats.Text = ""; _advisorOverlay.Update(new List <Card>(), currentArchetypeDeckGuid != Guid.Empty); currentArchetypeDeckGuid = Guid.Empty; } } }
/// <summary> /// Determine similarity between all archetype decks and all cards played by the opponent yet. /// Then update the cardlist displayed in the overlay with the highest matching archetype deck after removing all opponent cards. /// </summary> internal async void UpdateCardList() { // Only continue if in valid game mode or game format if (!IsValidGameMode || !IsValidGameFormat) { return; } // Small delay to guarantee opponents cards list is up to date (should be 300+ ms in debug mode or with attached debugger, otherwise restarting HDT could lead to an incomplete opponent decklist!) #if DEBUG await Task.Delay(1000); #else await Task.Delay(100); #endif // Get opponent's cards list (all yet revealed cards) //var opponentCardlist = Core.Game.Opponent.RevealedCards; IList <Card> opponentCardlist = Core.Game.Opponent.OpponentCardList.Where(x => !x.IsCreated).ToList(); // If no opponent's cards were revealed yet or we have no imported archetype decks in the database, return empty card list if (!opponentCardlist.Any() || !ArchetypeDecks.Any()) { currentArchetypeDeckGuid = Guid.Empty; _advisorOverlay.Update(new List <Card>(), true); } else { // Create archetype dictionary IDictionary <Deck, float> dict = new Dictionary <Deck, float>(); // Calculate similarities between all opponent's class archetype decks and all yet known opponent cards. Exclude wild decks in standard format using NAND expression. foreach (var archetypeDeck in ArchetypeDecks.Where(d => d.Class == CoreAPI.Game.Opponent.Class && !(d.IsWildDeck && CoreAPI.Game.CurrentFormat == Format.Standard))) { // Insert deck with calculated value into dictionary and prevent exception by inserting duplicate decks if (!dict.ContainsKey(archetypeDeck)) { dict.Add(archetypeDeck, archetypeDeck.Similarity(opponentCardlist)); } } // Get highest similarity value var maxSim = dict.Values.Max(); // TODO: Some unreproducable bug threw an exception here. System.InvalidOperationException: Sequence contains no elements @ IEnumerable.Max() // If any archetype deck matches more than MinimumSimilarity show the deck with the highest similarity if (maxSim >= Settings.Default.MinimumSimilarity) { // Select top decks with highest similarity value var topSimDecks = (from d in dict where Math.Abs(d.Value - maxSim) < 0.001 select d).ToList(); // Select top decks with most played games var maxGames = topSimDecks.Max(x => x.Key.GetPlayedGames()); var topGamesDecks = (from t in topSimDecks where t.Key.GetPlayedGames() == maxGames select t).ToList(); // Select best matched deck with both highest similarity value and most played games var matchedDeck = topGamesDecks.First(); // Show matched deck name and similarity value or number of matching cards and number of all played cards if (Settings.Default.ShowAbsoluteSimilarity) { // Count how many cards from opponent deck are in matched deck int matchingCards = matchedDeck.Key.CountMatchingCards(opponentCardlist); _advisorOverlay.LblArchetype.Text = String.Format("{0} ({1}/{2})", matchedDeck.Key.Name, matchingCards, matchedDeck.Key.CountUnion(opponentCardlist)); } else { _advisorOverlay.LblArchetype.Text = String.Format("{0} ({1}%)", matchedDeck.Key.Name, Math.Round(matchedDeck.Value * 100, 2)); } _advisorOverlay.LblStats.Text = String.Format("{0}", matchedDeck.Key.Note); Deck deck = DeckList.Instance.Decks.Where(d => d.TagList.ToLowerInvariant().Contains("archetype")).First(d => d.Name == matchedDeck.Key.Name); if (deck != null) { var predictedCards = ((Deck)deck.Clone()).Cards.ToList(); foreach (var card in opponentCardlist) { // Remove already played opponent cards from predicted archetype deck. But don't remove revealed jousted cards, because they were only seen and not played yet. if (predictedCards.Contains(card)) { var item = predictedCards.Find(x => x.Id == card.Id); if (!card.Jousted) { item.Count -= card.Count; } // highlight jousted cards in green // also highlight when deck has 2 of a card and we have matched one if (item.Count > 0) { item.HighlightInHand = true; item.InHandCount += card.Count; } else { item.HighlightInHand = false; item.InHandCount = 0; } } if (Settings.Default.ShowNonMatchingCards) { // Show known cards that don't match the archetype deck, in red if (!predictedCards.Contains(card)) { var item = (Card)card.Clone(); item.HighlightInHand = false; item.WasDiscarded = true; if (!item.Jousted) { item.Count = 0; } predictedCards.Add(item); } } } //var sortedPredictedCards = predictedCards.OrderBy(x => x.Cost).ThenBy(y => y.Name).ToList(); bool isNewArchetypeDeck = currentArchetypeDeckGuid != matchedDeck.Key.DeckId; // Update overlay cards _advisorOverlay.Update(predictedCards.ToSortedCardList(), isNewArchetypeDeck); // Remember current archetype deck guid with highest similarity to opponent's played cards currentArchetypeDeckGuid = matchedDeck.Key.DeckId; } } else { _advisorOverlay.LblArchetype.Text = String.Format("Best match: {0}%", Math.Round(maxSim * 100, 2)); _advisorOverlay.LblStats.Text = ""; _advisorOverlay.Update(Settings.Default.ShowNonMatchingCards ? opponentCardlist.ToList() : new List <Card>(), currentArchetypeDeckGuid != Guid.Empty); currentArchetypeDeckGuid = Guid.Empty; } } }