public override string ToString() { string rarityStr = "*"; for (ETriadCardRarity Idx = ETriadCardRarity.Common; Idx < Card.Rarity; Idx++) { rarityStr += " *"; } return(Card.Name + " (" + rarityStr + ")"); }
public TriadCard(int id, string name, string iconPath, ETriadCardRarity rarity, ETriadCardType type, int numUp, int numDown, int numLeft, int numRight, int sortKey) { Id = id; Name = name; IconPath = iconPath; Rarity = rarity; Type = type; Sides = new int[4] { numUp, numLeft, numDown, numRight }; SameNumberId = -1; SortKey = sortKey; }
private bool TryParseCardRarity(string desc, out ETriadCardRarity foundRarity) { if (!string.IsNullOrEmpty(desc)) { foreach (ETriadCardRarity testRarity in Enum.GetValues(typeof(ETriadCardRarity))) { if (testRarity.ToString().Equals(desc, StringComparison.InvariantCultureIgnoreCase)) { foundRarity = testRarity; return(true); } } } foundRarity = ETriadCardRarity.Common; return(false); }
public TriadCard(int id, string iconPath, ETriadCardRarity rarity, ETriadCardType type, int numUp, int numDown, int numLeft, int numRight, int sortOrder, int group) { Id = id; Name = LocalizationDB.Get().FindOrAddLocString(ELocStringType.CardName, id); IconPath = iconPath; Rarity = rarity; Type = type; Sides = new int[4] { numUp, numLeft, numDown, numRight }; SameNumberId = -1; SortOrder = sortOrder; Group = group; if (group != 0 && SortOrder < 1000) { SortOrder += 1000; } }
public TriadCard(int id, string name, string iconPath, ETriadCardRarity rarity, ETriadCardType type, int numUp, int numDown, int numLeft, int numRight, int sortOrder, int group) { Id = id; Name = name; IconPath = iconPath; Rarity = rarity; Type = type; Sides = new int[4] { numUp, numLeft, numDown, numRight }; SameNumberId = -1; SortOrder = sortOrder; Group = group; if (group != 0 && SortOrder < 1000) { SortOrder += 1000; } }
private void UpdatePossibleDeckCount(int numRare, int numCommon, List <TriadCard> lockedCards, bool bIsOrderImportant) { int numLocked = 0; foreach (TriadCard card in lockedCards) { if (card != null) { numLocked++; } } if (numLocked > 0) { ETriadCardRarity rareThreshold = GetRareThreshold(PlayerSettingsDB.Get().ownedCards); ECardSlotState[] cardSlots = BuildCardSlots(lockedCards, numRare, numCommon, rareThreshold, bIsOrderImportant); UpdatePossibleDeckCount(numRare, numCommon, cardSlots, bIsOrderImportant); } else { // num possible decks: numRare * (num 4 element combinations from numCommon set) // num 4 elem: numCommon! / (4! * (numCommon - 4)!) numPossibleDecks = numRare; for (int Idx = 0; Idx < 4; Idx++) { numPossibleDecks *= (numCommon - Idx); } int Fact4 = (4 * 3 * 2 * 1); if (!bIsOrderImportant || !bAllowPermutationChecks) { numPossibleDecks /= Fact4; } } }
private ECardSlotState[] BuildCardSlots(List <TriadCard> lockedCards, int numRare, int numCommon, ETriadCardRarity rareThreshold, bool bIsOrderImportant) { ECardSlotState[] slots = new ECardSlotState[lockedCards.Count]; // assign locked slots int numAssignedRare = 0; for (int Idx = 0; Idx < lockedCards.Count; Idx++) { if (lockedCards[Idx] != null) { if (lockedCards[Idx].Rarity >= rareThreshold) { slots[Idx] = ECardSlotState.LockedRare; numAssignedRare++; } else { slots[Idx] = ECardSlotState.LockedCommon; } } else { slots[Idx] = ECardSlotState.Common; } } // assign rare slot (1st available or 2nd for order rule) if (numAssignedRare == 0 && numRare > 0) { int FirstFreeIdx = 0; int SecondFreeIdx = 0; for (int Idx = 0; Idx < slots.Length; Idx++) { if (slots[Idx] == ECardSlotState.Common) { if (FirstFreeIdx <= 0) { FirstFreeIdx = Idx; } else if (SecondFreeIdx <= 0) { SecondFreeIdx = Idx; } } } int UseRareIdx = (bIsOrderImportant && SecondFreeIdx <= 3) ? SecondFreeIdx : FirstFreeIdx; slots[UseRareIdx] = ECardSlotState.Rare; } return(slots); }
private void FindDecksScored(TriadGameModifier[] regionMods, List <TriadCard> lockedCards) { PlayerSettingsDB playerDB = PlayerSettingsDB.Get(); //TriadCardDB playerDB = TriadCardDB.Get(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); TriadGameSession solver = new TriadGameSession(); solver.modifiers.AddRange(npc.Rules); solver.modifiers.AddRange(regionMods); solver.UpdateSpecialRules(); bool bIsOrderImportant = false; foreach (TriadGameModifier mod in solver.modifiers) { bIsOrderImportant = bIsOrderImportant || mod.IsDeckOrderImportant(); } List <TriadCard> rareList = new List <TriadCard>(); List <TriadCard> commonList = new List <TriadCard>(); FindCardsToUse(playerDB.ownedCards, solver.modifiers, rareList, commonList); ETriadCardRarity rareThreshold = GetRareThreshold(playerDB.ownedCards); ECardSlotState[] cardSlots = BuildCardSlots(lockedCards, rareList.Count, commonList.Count, rareThreshold, bIsOrderImportant); object lockOb = new object(); int bestScore = 0; TriadDeck bestDeck = new TriadDeck(PlayerSettingsDB.Get().starterCards); List <TriadCard>[] slotLists = new List <TriadCard> [cardSlots.Length]; int numLocked = 0; int numRareSlots = 0; for (int Idx = 0; Idx < slotLists.Length; Idx++) { switch (cardSlots[Idx]) { case ECardSlotState.Common: slotLists[Idx] = commonList; break; case ECardSlotState.Rare: slotLists[Idx] = rareList; numRareSlots++; break; default: slotLists[Idx] = new List <TriadCard>() { lockedCards[Idx] }; numLocked++; break; } } if (numLocked > 0) { // slower when ran for all 5 slots UpdatePossibleDeckCount(rareList.Count, commonList.Count, cardSlots, bIsOrderImportant); //for (int IdxS0 = 0; IdxS0 < slotLists[0].Count; IdxS0++) Parallel.For(0, slotLists[0].Count, IdxS0 => { if (!bAbort) { //for (int IdxS1 = 0; IdxS1 < slotLists[1].Count; IdxS1++) Parallel.For(0, slotLists[1].Count, IdxS1 => { if (!bAbort) { //for (int IdxS2 = 0; IdxS2 < slotLists[2].Count; IdxS2++) Parallel.For(0, slotLists[2].Count, IdxS2 => { if (!bAbort) { //for (int IdxS3 = 0; IdxS3 < slotLists[3].Count; IdxS3++) Parallel.For(0, slotLists[3].Count, IdxS3 => { if (!bAbort) { //for (int IdxS4 = 0; IdxS4 < slotLists[4].Count; IdxS4++) Parallel.For(0, slotLists[4].Count, IdxS4 => { if (!bAbort) { TriadCard[] testDeckCards = new TriadCard[] { slotLists[0][IdxS0], slotLists[1][IdxS1], slotLists[2][IdxS2], slotLists[3][IdxS3], slotLists[4][IdxS4] }; if (testDeckCards[0] != testDeckCards[1] && testDeckCards[0] != testDeckCards[2] && testDeckCards[0] != testDeckCards[3] && testDeckCards[0] != testDeckCards[4] && testDeckCards[1] != testDeckCards[2] && testDeckCards[1] != testDeckCards[3] && testDeckCards[1] != testDeckCards[4] && testDeckCards[2] != testDeckCards[3] && testDeckCards[2] != testDeckCards[4] && testDeckCards[3] != testDeckCards[4]) { Random randomGen = GetRandomStream(IdxS0, IdxS1, IdxS2, IdxS3, IdxS4); // TODO: custom permutation lookup { TriadDeck testDeck = new TriadDeck(testDeckCards); int testScore = GetDeckScore(solver, testDeck, randomGen, 1); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = testDeck; OnFoundDeck.Invoke(testDeck); } } } } lock (lockOb) { numTestedDecks++; } } }); } }); } }); } }); } }); } else { // faster loops when nothing is locked // A: single rare slot, most common case if (numRareSlots == 1) { UpdatePossibleDeckCount(rareList.Count, commonList.Count, lockedCards, bIsOrderImportant); Parallel.For(0, rareList.Count, IdxR0 => { if (!bAbort) { Parallel.For(0, commonList.Count, IdxC1 => { if (!bAbort) { for (int IdxC2 = IdxC1 + 1; IdxC2 < commonList.Count; IdxC2++) { for (int IdxC3 = IdxC2 + 1; IdxC3 < commonList.Count; IdxC3++) { for (int IdxC4 = IdxC3 + 1; IdxC4 < commonList.Count; IdxC4++) { TriadCard[] testDeckCards = new TriadCard[] { rareList[IdxR0], commonList[IdxC1], commonList[IdxC2], commonList[IdxC3], commonList[IdxC4] }; Random randomGen = GetRandomStream(IdxR0, IdxC1, IdxC2, IdxC3, IdxC4); if (bIsOrderImportant) { if (bAllowPermutationChecks) { for (int IdxP = 0; IdxP < permutationList.Length; IdxP++) { int[] UseOrder = permutationList[IdxP]; TriadCard[] permDeckCards = new TriadCard[] { testDeckCards[UseOrder[0]], testDeckCards[UseOrder[1]], testDeckCards[UseOrder[2]], testDeckCards[UseOrder[3]], testDeckCards[UseOrder[4]] }; TriadDeck permDeck = new TriadDeck(permDeckCards); int testScore = GetDeckScore(solver, permDeck, randomGen, 10); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = permDeck; OnFoundDeck.Invoke(permDeck); } } } } else { testDeckCards = new TriadCard[] { commonList[IdxC1], rareList[IdxR0], commonList[IdxC2], commonList[IdxC3], commonList[IdxC4] }; } } { TriadDeck testDeck = new TriadDeck(testDeckCards); int testScore = GetDeckScore(solver, testDeck, randomGen, 1); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = testDeck; OnFoundDeck.Invoke(testDeck); } } } lock (lockOb) { numTestedDecks++; if (bAbort) { IdxC2 = commonList.Count; IdxC3 = commonList.Count; IdxC4 = commonList.Count; } } } } } } }); } }); } else if (numRareSlots == 0) { // remove part of common list for faster procesing, normally it would use 1 rare slot (smaller pool) // randomly for all thta matters... int maxCommonToUse = commonList.Count * 80 / 100; Random pruneRng = new Random(); while (commonList.Count > maxCommonToUse) { int idxToRemove = pruneRng.Next(0, commonList.Count); commonList.RemoveAt(idxToRemove); } // call simpler version of max possible combinations, 1 list in use UpdatePossibleDeckCount(maxCommonToUse, bIsOrderImportant); OnUpdateMaxSearchDecks?.Invoke(numPossibleDecks.ToString()); Parallel.For(0, commonList.Count, IdxC1 => { if (!bAbort) { for (int IdxC2 = IdxC1 + 1; IdxC2 < commonList.Count; IdxC2++) { for (int IdxC3 = IdxC2 + 1; IdxC3 < commonList.Count; IdxC3++) { for (int IdxC4 = IdxC3 + 1; IdxC4 < commonList.Count; IdxC4++) { for (int IdxC5 = IdxC4 + 1; IdxC5 < commonList.Count; IdxC5++) { TriadCard[] testDeckCards = new TriadCard[] { commonList[IdxC1], commonList[IdxC2], commonList[IdxC3], commonList[IdxC4], commonList[IdxC5] }; Random randomGen = GetRandomStream(IdxC1, IdxC2, IdxC3, IdxC4, IdxC5); if (bIsOrderImportant && bAllowPermutationChecks) { for (int IdxP = 0; IdxP < permutationList.Length; IdxP++) { int[] UseOrder = permutationList[IdxP]; TriadCard[] permDeckCards = new TriadCard[] { testDeckCards[UseOrder[0]], testDeckCards[UseOrder[1]], testDeckCards[UseOrder[2]], testDeckCards[UseOrder[3]], testDeckCards[UseOrder[4]] }; TriadDeck permDeck = new TriadDeck(permDeckCards); int testScore = GetDeckScore(solver, permDeck, randomGen, 10); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = permDeck; OnFoundDeck.Invoke(permDeck); } } } } { TriadDeck testDeck = new TriadDeck(testDeckCards); int testScore = GetDeckScore(solver, testDeck, randomGen, 1); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = testDeck; OnFoundDeck.Invoke(testDeck); } } } lock (lockOb) { numTestedDecks++; if (bAbort) { IdxC2 = commonList.Count; IdxC3 = commonList.Count; IdxC4 = commonList.Count; IdxC5 = commonList.Count; } } } } } } } }); } else { Logger.WriteLine("Unexpected slot setup: " + string.Join(", ", cardSlots) + ", bailing out"); } } stopwatch.Stop(); Logger.WriteLine("Building list of decks: " + stopwatch.ElapsedMilliseconds + "ms, num:" + numPossibleDecks); optimizedDeck = bestDeck; }
private void FindDecks(TriadGameModifier[] regionMods) { PlayerSettingsDB playerDB = PlayerSettingsDB.Get(); //TriadCardDB playerDB = TriadCardDB.Get(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); ETriadCardRarity RarityLimitThr = (playerDB.ownedCards.Count < 30) ? ETriadCardRarity.Uncommon : (playerDB.ownedCards.Count < 60) ? ETriadCardRarity.Rare : ETriadCardRarity.Epic; TriadGameSession solver = new TriadGameSession(); solver.modifiers.AddRange(npc.Rules); solver.modifiers.AddRange(regionMods); solver.UpdateSpecialRules(); bool bIsOrderImportant = false; foreach (TriadGameModifier mod in solver.modifiers) { bIsOrderImportant = bIsOrderImportant || mod.IsDeckOrderImportant(); } object lockOb = new object(); int bestScore = 0; TriadDeck bestDeck = new TriadDeck(PlayerSettingsDB.Get().starterCards); Parallel.For(1, playerDB.ownedCards.Count, Idx1 => { Parallel.For(Idx1 + 1, playerDB.ownedCards.Count, Idx2 => { int rareCounterLv2 = ((playerDB.ownedCards[Idx1].Rarity >= RarityLimitThr) ? 1 : 0) + ((playerDB.ownedCards[Idx2].Rarity >= RarityLimitThr) ? 1 : 0); if (rareCounterLv2 <= 1) { Parallel.For(Idx2 + 1, playerDB.ownedCards.Count, Idx3 => { int rareCounterLv3 = ((playerDB.ownedCards[Idx1].Rarity >= RarityLimitThr) ? 1 : 0) + ((playerDB.ownedCards[Idx2].Rarity >= RarityLimitThr) ? 1 : 0) + ((playerDB.ownedCards[Idx3].Rarity >= RarityLimitThr) ? 1 : 0); if (rareCounterLv3 <= 1) { Parallel.For(Idx3 + 1, playerDB.ownedCards.Count, Idx4 => { int rareCounterLv4 = ((playerDB.ownedCards[Idx1].Rarity >= RarityLimitThr) ? 1 : 0) + ((playerDB.ownedCards[Idx2].Rarity >= RarityLimitThr) ? 1 : 0) + ((playerDB.ownedCards[Idx3].Rarity >= RarityLimitThr) ? 1 : 0) + ((playerDB.ownedCards[Idx4].Rarity >= RarityLimitThr) ? 1 : 0); if (rareCounterLv4 <= 1) { for (int Idx5 = Idx4 + 1; Idx5 < playerDB.ownedCards.Count; Idx5++) { int rareCounterLv5 = rareCounterLv4 + ((playerDB.ownedCards[Idx5].Rarity >= RarityLimitThr) ? 1 : 0); if (rareCounterLv5 <= 1) { TriadCard[] testDeckCards = new TriadCard[] { playerDB.ownedCards[Idx1], playerDB.ownedCards[Idx2], playerDB.ownedCards[Idx3], playerDB.ownedCards[Idx4], playerDB.ownedCards[Idx5] }; Random randomGen = GetRandomStream(Idx1, Idx2, Idx3, Idx4, Idx5); if (bIsOrderImportant) { if (bAllowPermutationChecks) { for (int IdxP = 0; IdxP < permutationList.Length; IdxP++) { int[] UseOrder = permutationList[IdxP]; TriadDeck permDeck = new TriadDeck(new TriadCard[] { testDeckCards[UseOrder[0]], testDeckCards[UseOrder[1]], testDeckCards[UseOrder[2]], testDeckCards[UseOrder[3]], testDeckCards[UseOrder[4]] }); int testScore = GetDeckScore(solver, permDeck, randomGen, 10); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = permDeck; } } } } else { int fixedRareSlot = 2; for (int TestSlotIdx = 0; TestSlotIdx < testDeckCards.Length; TestSlotIdx++) { if (testDeckCards[TestSlotIdx].Rarity > testDeckCards[fixedRareSlot].Rarity) { TriadCard swapOb = testDeckCards[TestSlotIdx]; testDeckCards[TestSlotIdx] = testDeckCards[fixedRareSlot]; testDeckCards[fixedRareSlot] = swapOb; } } } } { TriadDeck testDeck = new TriadDeck(testDeckCards); int testScore = GetDeckScore(solver, testDeck, randomGen, 1); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = testDeck; } } } } numTestedDecks++; } } else { numTestedDecks += playerDB.ownedCards.Count - Idx4; } }); } else { numTestedDecks += (playerDB.ownedCards.Count - Idx3) * (playerDB.ownedCards.Count - Idx3 - 1); } }); } else { numTestedDecks += (playerDB.ownedCards.Count - Idx2) * (playerDB.ownedCards.Count - Idx2 - 1) * (playerDB.ownedCards.Count - Idx2 - 2); } }); }); stopwatch.Stop(); Logger.WriteLine("Building list of decks: " + stopwatch.ElapsedMilliseconds + "ms, num:" + numPossibleDecks); optimizedDeck = bestDeck; }
private void FindCardsToUse(List <TriadCard> allCards, List <TriadGameModifier> modifiers, List <TriadCard> rareList, List <TriadCard> commonList) { ETriadCardRarity limitedRarity = GetRareThreshold(allCards); List <CardScoreData> rareScoredList = new List <CardScoreData>(); List <CardScoreData> commonScoredList = new List <CardScoreData>(); foreach (TriadCard card in allCards) { if (card == null || !card.IsValid()) { continue; } // try to guess how good card will perform // - avg of sides // - std of sides // - rarity (should be reflected by sides already) // - corners with same number // - max corner number int numberSum = card.Sides[0] + card.Sides[1] + card.Sides[2] + card.Sides[3]; float numberAvg = numberSum / 4.0f; float numberMeanSqDiff = ((card.Sides[0] - numberAvg) * (card.Sides[0] - numberAvg)) + ((card.Sides[1] - numberAvg) * (card.Sides[1] - numberAvg)) + ((card.Sides[2] - numberAvg) * (card.Sides[2] - numberAvg)) + ((card.Sides[3] - numberAvg) * (card.Sides[3] - numberAvg)); float numberStd = (float)Math.Sqrt(numberMeanSqDiff / 4); int cornerNum = 0; int numCorners = 0; if (card.Sides[0] == card.Sides[1]) { numCorners++; cornerNum = Math.Max(cornerNum, card.Sides[0]); } if (card.Sides[1] == card.Sides[2]) { numCorners++; cornerNum = Math.Max(cornerNum, card.Sides[1]); } if (card.Sides[2] == card.Sides[3]) { numCorners++; cornerNum = Math.Max(cornerNum, card.Sides[2]); } if (card.Sides[3] == card.Sides[0]) { numCorners++; cornerNum = Math.Max(cornerNum, card.Sides[3]); } float cardScore = (numberAvg * scoreAvgSides) + (numberStd * scoreStdSides) + (numCorners * scoreSameCorners) + (cornerNum * scoreMaxCorner) + ((int)card.Rarity * scoreRarity); foreach (TriadGameModifier mod in modifiers) { mod.OnScoreCard(card, ref cardScore); } if (card.Rarity >= limitedRarity) { rareScoredList.Add(new CardScoreData(card, cardScore)); } else { commonScoredList.Add(new CardScoreData(card, cardScore)); } } // special case: don't include any rare slots with reverse rule if there's enough cards in common list bool shouldRemoveRares = false; foreach (TriadGameModifier mod in modifiers) { if (mod.GetType() == typeof(TriadGameModifierReverse)) { shouldRemoveRares = (commonScoredList.Count >= 5); break; } } commonScoredList.Sort(); rareScoredList.Sort(); int maxCommonToCopy = Math.Min(shouldRemoveRares ? (numCommonToBuild + numRareToBuild) : numCommonToBuild, commonScoredList.Count); for (int Idx = 0; Idx < maxCommonToCopy; Idx++) { commonList.Add(commonScoredList[Idx].card); } int maxRareToCopy = Math.Min(shouldRemoveRares ? 0 : numRareToBuild, rareScoredList.Count); for (int Idx = 0; Idx < maxRareToCopy; Idx++) { rareList.Add(rareScoredList[Idx].card); } }
public bool Load() { List <TriadCard> loadedCards = new List <TriadCard>(); int maxLoadedId = 0; try { XmlDocument xdoc = new XmlDocument(); Stream dataStream = AssetManager.Get().GetAsset(DBPath); xdoc.Load(dataStream); foreach (XmlNode cardNode in xdoc.DocumentElement.ChildNodes) { XmlElement cardElem = (XmlElement)cardNode; if (cardElem != null && cardElem.Name == "card") { try { ETriadCardRarity cardRarity = ETriadCardRarity.Common; ETriadCardType cardType = ETriadCardType.None; bool bHasRarity = TryParseCardRarity(cardElem.GetAttribute("rarity"), out cardRarity); bool bHasType = TryParseCardType(cardElem.GetAttribute("type"), out cardType); if (bHasType && bHasRarity) { TriadCard newCard = new TriadCard( int.Parse(cardElem.GetAttribute("id")), WebUtility.HtmlDecode(cardElem.GetAttribute("name")), cardElem.GetAttribute("icon"), cardRarity, cardType, ParseCardSideNum(cardElem.GetAttribute("up")), ParseCardSideNum(cardElem.GetAttribute("dn")), ParseCardSideNum(cardElem.GetAttribute("lt")), ParseCardSideNum(cardElem.GetAttribute("rt")), int.Parse(cardElem.GetAttribute("sort"))); if (newCard.IsValid()) { loadedCards.Add(newCard); maxLoadedId = Math.Max(maxLoadedId, newCard.Id); } else { Logger.WriteLine("Loading failed! ob[" + newCard + "], xml:" + cardElem.OuterXml); } } else { Logger.WriteLine("Loading failed! xml:" + cardElem.OuterXml); } } catch (Exception ex) { Logger.WriteLine("Loading failed! Exception:" + ex); } } } } catch (Exception ex) { Logger.WriteLine("Loading failed! Exception:" + ex); } if (loadedCards.Count > 0) { while (cards.Count < (maxLoadedId + 1)) { cards.Add(null); } foreach (TriadCard card in loadedCards) { cards[card.Id] = card; } } sameNumberMap.Clear(); int sameNumberId = 0; for (int Idx1 = 0; Idx1 < cards.Count; Idx1++) { TriadCard card1 = cards[Idx1]; if (card1 != null && card1.SameNumberId < 0) { bool bHasSameNumberCards = false; for (int Idx2 = (Idx1 + 1); Idx2 < cards.Count; Idx2++) { TriadCard card2 = cards[Idx2]; if (card2 != null && card2.SameNumberId < 0) { bool bHasSameNumbers = (card1.Sides[0] == card2.Sides[0]) && (card1.Sides[1] == card2.Sides[1]) && (card1.Sides[2] == card2.Sides[2]) && (card1.Sides[3] == card2.Sides[3]); bHasSameNumberCards = bHasSameNumberCards || bHasSameNumbers; if (bHasSameNumbers) { if (!sameNumberMap.ContainsKey(sameNumberId)) { sameNumberMap.Add(sameNumberId, new List <TriadCard>()); sameNumberMap[sameNumberId].Add(card1); card1.SameNumberId = sameNumberId; } sameNumberMap[sameNumberId].Add(card2); card2.SameNumberId = sameNumberId; } } } if (bHasSameNumberCards) { sameNumberId++; } } } Logger.WriteLine("Loaded cards: " + loadedCards.Count + ", same sides: " + sameNumberMap.Count); return(loadedCards.Count > 0); }
public TriadDeckOptimizer() { numGamesToPlay = 2000; numPriorityToBuild = 10; numCommonToBuild = 20; numCommonPctToDropPerPriSlot = 10; maxSlotsPerRarity = new Dictionary <ETriadCardRarity, int>(); maxSlotsPerRarity.Add(ETriadCardRarity.Legendary, 1); maxSlotsPerRarity.Add(ETriadCardRarity.Epic, 2); commonRarity = ETriadCardRarity.Rare; debugMode = false; bAbort = false; #if DEBUG debugMode = true; #endif // DEBUG scoreAvgSides = 1.0f; scoreStdSides = 0.0f; scoreMaxSides = 0.1f; scoreSameCorners = 0.0f; scoreMaxCorner = 0.0f; scoreRarity = 1.0f; // generate lookup for permutations used when deck order is important // num entries = 5! = 120 permutationList = new int[120][]; int ListIdx = 0; for (int IdxP0 = 0; IdxP0 < 5; IdxP0++) { for (int IdxP1 = 0; IdxP1 < 5; IdxP1++) { if (IdxP1 == IdxP0) { continue; } for (int IdxP2 = 0; IdxP2 < 5; IdxP2++) { if (IdxP2 == IdxP0 || IdxP2 == IdxP1) { continue; } for (int IdxP3 = 0; IdxP3 < 5; IdxP3++) { if (IdxP3 == IdxP0 || IdxP3 == IdxP1 || IdxP3 == IdxP2) { continue; } for (int IdxP4 = 0; IdxP4 < 5; IdxP4++) { if (IdxP4 == IdxP0 || IdxP4 == IdxP1 || IdxP4 == IdxP2 || IdxP4 == IdxP3) { continue; } permutationList[ListIdx] = new int[5] { IdxP0, IdxP1, IdxP2, IdxP3, IdxP4 }; ListIdx++; } } } } } // temporary, set initial rule set OnLanguageChanged(); }
private bool FindCardPool(List <TriadCard> allCards, List <TriadGameModifier> modifiers, List <TriadCard> lockedCards) { currentPool = new CardPool(); int maxRarityNum = Enum.GetValues(typeof(ETriadCardRarity)).Length; int priRarityNum = (int)commonRarity + 1; int[] mapAvailRarity = new int[maxRarityNum]; // special case: don't include any rare slots with reverse rule if there's enough cards in common list bool hasReverseMod = false; bool hasAscensionMod = false; foreach (TriadGameModifier mod in modifiers) { if (mod.GetType() == typeof(TriadGameModifierReverse)) { hasReverseMod = true; } else if (mod.GetType() == typeof(TriadGameModifierAscention)) { hasAscensionMod = true; } } // find number of priority lists based on unique rarity limits List <ETriadCardRarity> priRarityThr = new List <ETriadCardRarity>(); for (int idxR = priRarityNum; idxR < maxRarityNum; idxR++) { ETriadCardRarity testRarity = (ETriadCardRarity)idxR; if (!hasReverseMod && maxSlotsPerRarity.ContainsKey(testRarity) && maxSlotsPerRarity[testRarity] > 0) { mapAvailRarity[idxR] = maxSlotsPerRarity[testRarity]; mapAvailRarity[idxR - 1] -= maxSlotsPerRarity[testRarity]; priRarityThr.Add(testRarity); } } if (debugMode) { Logger.WriteLine("FindCardPool> priRarityThr:{0}, maxAvail:[{1},{2},{3},{4},{5}], reverse:{6}, ascention:{7}", priRarityThr.Count, mapAvailRarity[0], mapAvailRarity[1], mapAvailRarity[2], mapAvailRarity[3], mapAvailRarity[4], hasReverseMod, hasAscensionMod); } // check rarity of locked cards, eliminate pri list when threshold is matched // when multiple pri rarities are locked, start eliminating from pool above // e.g. 2x 4 star locked => 4 star out, 5 star out currentPool.deckSlotTypes = new int[lockedCards.Count]; int numLockedCards = 0; for (int idx = 0; idx < lockedCards.Count; idx++) { TriadCard card = lockedCards[idx]; if (card != null) { if (card.Rarity > commonRarity) { for (int testR = (int)card.Rarity; testR <= maxRarityNum; testR++) { if (mapAvailRarity[testR] > 0) { mapAvailRarity[testR]--; break; } } } currentPool.deckSlotTypes[idx] = DeckSlotLocked; numLockedCards++; } else { currentPool.deckSlotTypes[idx] = DeckSlotCommon; } } if (debugMode) { Logger.WriteLine(">> adjusted for locking, numLocked:{0}, maxAvail:[{1},{2},{3},{4},{5}]", numLockedCards, mapAvailRarity[0], mapAvailRarity[1], mapAvailRarity[2], mapAvailRarity[3], mapAvailRarity[4]); } if (numLockedCards == lockedCards.Count) { return(false); } List <CardScoreData> commonScoredList = new List <CardScoreData>(); List <List <CardScoreData> > priScoredList = new List <List <CardScoreData> >(); for (int idxP = 0; idxP < priRarityThr.Count; idxP++) { priScoredList.Add(new List <CardScoreData>()); } // reverse priority thresholds, idx:0 becomes strongest card priRarityThr.Reverse(); // assign each owned card to scored lists foreach (TriadCard card in allCards) { if (card == null || !card.IsValid()) { continue; } // try to guess how good card will perform // - avg of sides // - std of sides // - max of sides // - rarity (should be reflected by sides already) // - corners with same number // - max corner number int numberMax = Math.Max(Math.Max(card.Sides[0], card.Sides[1]), Math.Max(card.Sides[2], card.Sides[3])); int numberSum = card.Sides[0] + card.Sides[1] + card.Sides[2] + card.Sides[3]; float numberAvg = numberSum / 4.0f; float numberMeanSqDiff = ((card.Sides[0] - numberAvg) * (card.Sides[0] - numberAvg)) + ((card.Sides[1] - numberAvg) * (card.Sides[1] - numberAvg)) + ((card.Sides[2] - numberAvg) * (card.Sides[2] - numberAvg)) + ((card.Sides[3] - numberAvg) * (card.Sides[3] - numberAvg)); float numberStd = (float)Math.Sqrt(numberMeanSqDiff / 4); int cornerNum = 0; int numCorners = 0; if (card.Sides[0] == card.Sides[1]) { numCorners++; cornerNum = Math.Max(cornerNum, card.Sides[0]); } if (card.Sides[1] == card.Sides[2]) { numCorners++; cornerNum = Math.Max(cornerNum, card.Sides[1]); } if (card.Sides[2] == card.Sides[3]) { numCorners++; cornerNum = Math.Max(cornerNum, card.Sides[2]); } if (card.Sides[3] == card.Sides[0]) { numCorners++; cornerNum = Math.Max(cornerNum, card.Sides[3]); } CardScoreData scoredCard = new CardScoreData() { card = card }; scoredCard.score = (numberAvg * scoreAvgSides) + (numberStd * scoreStdSides) + (numberMax * scoreMaxSides) + (numCorners * scoreSameCorners) + (cornerNum * scoreMaxCorner) + ((int)card.Rarity * scoreRarity); foreach (TriadGameModifier mod in modifiers) { mod.OnScoreCard(card, ref scoredCard.score); } for (int idxP = 0; idxP < priRarityThr.Count; idxP++) { if (card.Rarity <= priRarityThr[idxP]) { priScoredList[idxP].Add(scoredCard); } } if (card.Rarity <= commonRarity) { commonScoredList.Add(scoredCard); } } if (debugMode) { Logger.WriteLine(">> card lists sorted, common:{0}", commonScoredList.Count); } bool isPoolValid = (commonScoredList.Count > 0); if (isPoolValid) { int numPriLists = 0; int deckSlotIdx = isOrderImportant ? 1 : 0; for (int idx = 0; idx < priScoredList.Count; idx++) { int numAvail = mapAvailRarity[(int)priRarityThr[idx]]; if (debugMode) { Logger.WriteLine(" pri list[{0}]:{1}, rarity:{2}, avail:{3}", idx, priScoredList[idx].Count, priRarityThr[idx], numAvail); } if ((numAvail > 0) && (priScoredList[idx].Count > 0)) { // initial deckSlotIdx should be already past only available spot (e.g. all slots but [0] are locked), make sure to wrap around // find fist available Common slot to overwrite with priority list, repeat numAvail times for (int idxAvail = 0; idxAvail < numAvail; idxAvail++) { for (int idxD = 0; idxD < currentPool.deckSlotTypes.Length; idxD++) { if (currentPool.deckSlotTypes[deckSlotIdx] == DeckSlotCommon) { break; } deckSlotIdx++; } currentPool.deckSlotTypes[deckSlotIdx] = numPriLists; } numPriLists++; } else { priScoredList[idx].Clear(); } } // ascension modifier special case: same type across all pools is best // aply after priority lists were trimmed if (hasAscensionMod) { ApplyAscentionFilter(commonScoredList, priScoredList); } if (numPriLists > 0) { currentPool.priorityLists = new TriadCard[numPriLists][]; if (debugMode) { Logger.WriteLine(">> num priority lists:{0}", numPriLists); } int idxP = 0; for (int idxL = 0; idxL < priScoredList.Count; idxL++) { int maxPriorityToUse = Math.Min(numPriorityToBuild, priScoredList[idxL].Count); if (maxPriorityToUse > 0) { currentPool.priorityLists[idxP] = new TriadCard[maxPriorityToUse]; priScoredList[idxL].Sort(); for (int idxC = 0; idxC < maxPriorityToUse; idxC++) { currentPool.priorityLists[idxP][idxC] = priScoredList[idxL][idxC].card; } idxP++; } } } // adjust pool of common cards based on avail common slots // - all common: use requested size // - scale down 20% per every priority list slot int numPriSlots = 0; for (int idx = 0; idx < currentPool.deckSlotTypes.Length; idx++) { numPriSlots += (currentPool.deckSlotTypes[idx] >= 0) ? 1 : 0; } int maxCommonToUse = Math.Min(numCommonToBuild - (numCommonToBuild * numPriSlots * numCommonPctToDropPerPriSlot / 100), commonScoredList.Count); if (debugMode) { Logger.WriteLine(">> adjusting common pool based on priSlots:{0} and drop:{1}% => {2}", numPriSlots, numCommonPctToDropPerPriSlot, maxCommonToUse); } currentPool.commonList = new TriadCard[maxCommonToUse]; commonScoredList.Sort(); for (int idx = 0; idx < currentPool.commonList.Length; idx++) { currentPool.commonList[idx] = commonScoredList[idx].card; } } if (debugMode) { Logger.WriteLine(">> deck slot types:[{0}, {1}, {2}, {3}, {4}]", currentPool.deckSlotTypes[0], currentPool.deckSlotTypes[1], currentPool.deckSlotTypes[2], currentPool.deckSlotTypes[3], currentPool.deckSlotTypes[4]); } return(isPoolValid); }
private void FindDecksScored(TriadGameModifier[] regionMods, List <TriadCard> lockedCards) { PlayerSettingsDB playerDB = PlayerSettingsDB.Get(); //TriadCardDB playerDB = TriadCardDB.Get(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); TriadGameSession solver = new TriadGameSession(); solver.modifiers.AddRange(npc.Rules); solver.modifiers.AddRange(regionMods); solver.UpdateSpecialRules(); bool bIsOrderImportant = false; foreach (TriadGameModifier mod in solver.modifiers) { bIsOrderImportant = bIsOrderImportant || mod.IsDeckOrderImportant(); } List <TriadCard> rareList = new List <TriadCard>(); List <TriadCard> commonList = new List <TriadCard>(); FindCardsToUse(playerDB.ownedCards, solver.modifiers, rareList, commonList); ETriadCardRarity rareThreshold = GetRareThreshold(playerDB.ownedCards); ECardSlotState[] cardSlots = BuildCardSlots(lockedCards, rareList.Count, commonList.Count, rareThreshold, bIsOrderImportant); object lockOb = new object(); int bestScore = 0; TriadDeck bestDeck = new TriadDeck(PlayerSettingsDB.Get().starterCards); List <TriadCard>[] slotLists = new List <TriadCard> [cardSlots.Length]; int numLocked = 0; for (int Idx = 0; Idx < slotLists.Length; Idx++) { switch (cardSlots[Idx]) { case ECardSlotState.Common: slotLists[Idx] = commonList; break; case ECardSlotState.Rare: slotLists[Idx] = rareList; break; default: slotLists[Idx] = new List <TriadCard>() { lockedCards[Idx] }; numLocked++; break; } } if (numLocked > 0) { // slower when ran for all 5 slots UpdatePossibleDeckCount(rareList.Count, commonList.Count, cardSlots, bIsOrderImportant); //for (int IdxS0 = 0; IdxS0 < slotLists[0].Count; IdxS0++) Parallel.For(0, slotLists[0].Count, IdxS0 => { if (!bAbort) { //for (int IdxS1 = 0; IdxS1 < slotLists[1].Count; IdxS1++) Parallel.For(0, slotLists[1].Count, IdxS1 => { if (!bAbort) { //for (int IdxS2 = 0; IdxS2 < slotLists[2].Count; IdxS2++) Parallel.For(0, slotLists[2].Count, IdxS2 => { if (!bAbort) { //for (int IdxS3 = 0; IdxS3 < slotLists[3].Count; IdxS3++) Parallel.For(0, slotLists[3].Count, IdxS3 => { if (!bAbort) { //for (int IdxS4 = 0; IdxS4 < slotLists[4].Count; IdxS4++) Parallel.For(0, slotLists[4].Count, IdxS4 => { if (!bAbort) { TriadCard[] testDeckCards = new TriadCard[] { slotLists[0][IdxS0], slotLists[1][IdxS1], slotLists[2][IdxS2], slotLists[3][IdxS3], slotLists[4][IdxS4] }; if (testDeckCards[0] != testDeckCards[1] && testDeckCards[0] != testDeckCards[2] && testDeckCards[0] != testDeckCards[3] && testDeckCards[0] != testDeckCards[4] && testDeckCards[1] != testDeckCards[2] && testDeckCards[1] != testDeckCards[3] && testDeckCards[1] != testDeckCards[4] && testDeckCards[2] != testDeckCards[3] && testDeckCards[2] != testDeckCards[4] && testDeckCards[3] != testDeckCards[4]) { Random randomGen = GetRandomStream(IdxS0, IdxS1, IdxS2, IdxS3, IdxS4); // TODO: custom permutation lookup { TriadDeck testDeck = new TriadDeck(testDeckCards); int testScore = GetDeckScore(solver, testDeck, randomGen, 1); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = testDeck; OnFoundDeck.Invoke(testDeck); } } } } lock (lockOb) { numTestedDecks++; } } }); } }); } }); } }); } }); } else { // faster loops when nothing is locked UpdatePossibleDeckCount(rareList.Count, commonList.Count, lockedCards, bIsOrderImportant); Parallel.For(0, rareList.Count, IdxR0 => { if (!bAbort) { Parallel.For(0, commonList.Count, IdxC1 => { if (!bAbort) { for (int IdxC2 = IdxC1 + 1; IdxC2 < commonList.Count; IdxC2++) { for (int IdxC3 = IdxC2 + 1; IdxC3 < commonList.Count; IdxC3++) { for (int IdxC4 = IdxC3 + 1; IdxC4 < commonList.Count; IdxC4++) { TriadCard[] testDeckCards = new TriadCard[] { rareList[IdxR0], commonList[IdxC1], commonList[IdxC2], commonList[IdxC3], commonList[IdxC4] }; Random randomGen = GetRandomStream(IdxR0, IdxC1, IdxC2, IdxC3, IdxC4); if (bIsOrderImportant) { if (bAllowPermutationChecks) { for (int IdxP = 0; IdxP < permutationList.Length; IdxP++) { int[] UseOrder = permutationList[IdxP]; TriadCard[] permDeckCards = new TriadCard[] { testDeckCards[UseOrder[0]], testDeckCards[UseOrder[1]], testDeckCards[UseOrder[2]], testDeckCards[UseOrder[3]], testDeckCards[UseOrder[4]] }; TriadDeck permDeck = new TriadDeck(permDeckCards); int testScore = GetDeckScore(solver, permDeck, randomGen, 10); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = permDeck; OnFoundDeck.Invoke(permDeck); } } } } else { testDeckCards = new TriadCard[] { commonList[IdxC1], rareList[IdxR0], commonList[IdxC2], commonList[IdxC3], commonList[IdxC4] }; } } { TriadDeck testDeck = new TriadDeck(testDeckCards); int testScore = GetDeckScore(solver, testDeck, randomGen, 1); if (testScore > bestScore) { lock (lockOb) { bestScore = testScore; bestDeck = testDeck; OnFoundDeck.Invoke(testDeck); } } } lock (lockOb) { numTestedDecks++; if (bAbort) { IdxC2 = commonList.Count; IdxC3 = commonList.Count; IdxC4 = commonList.Count; } } } } } } }); } }); } stopwatch.Stop(); Logger.WriteLine("Building list of decks: " + stopwatch.ElapsedMilliseconds + "ms, num:" + numPossibleDecks); optimizedDeck = bestDeck; }
public bool Load() { try { XmlDocument xdoc = new XmlDocument(); Stream dataStream = AssetManager.Get().GetAsset(DBPath); xdoc.Load(dataStream); foreach (XmlNode cardNode in xdoc.DocumentElement.ChildNodes) { XmlElement cardElem = (XmlElement)cardNode; if (cardElem != null && cardElem.Name == "card") { try { ETriadCardRarity cardRarity = (ETriadCardRarity)int.Parse(cardElem.GetAttribute("rarity")); ETriadCardType cardType = (ETriadCardType)int.Parse(cardElem.GetAttribute("type")); int sortOrder = int.Parse(cardElem.GetAttribute("sort")); int cardGroup = int.Parse(cardElem.GetAttribute("group")); TriadCard newCard = new TriadCard( int.Parse(cardElem.GetAttribute("id")), cardElem.GetAttribute("icon"), cardRarity, cardType, ParseCardSideNum(cardElem.GetAttribute("up")), ParseCardSideNum(cardElem.GetAttribute("dn")), ParseCardSideNum(cardElem.GetAttribute("lt")), ParseCardSideNum(cardElem.GetAttribute("rt")), sortOrder, cardGroup); while (cards.Count <= newCard.Id) { cards.Add(null); } cards[newCard.Id] = newCard; } catch (Exception ex) { Logger.WriteLine("Loading failed! Exception:" + ex); } } } } catch (Exception ex) { Logger.WriteLine("Loading failed! Exception:" + ex); } sameNumberMap.Clear(); int sameNumberId = 0; for (int Idx1 = 0; Idx1 < cards.Count; Idx1++) { TriadCard card1 = cards[Idx1]; if (card1 != null && card1.SameNumberId < 0) { bool bHasSameNumberCards = false; for (int Idx2 = (Idx1 + 1); Idx2 < cards.Count; Idx2++) { TriadCard card2 = cards[Idx2]; if (card2 != null && card2.SameNumberId < 0) { bool bHasSameNumbers = (card1.Sides[0] == card2.Sides[0]) && (card1.Sides[1] == card2.Sides[1]) && (card1.Sides[2] == card2.Sides[2]) && (card1.Sides[3] == card2.Sides[3]); bHasSameNumberCards = bHasSameNumberCards || bHasSameNumbers; if (bHasSameNumbers) { if (!sameNumberMap.ContainsKey(sameNumberId)) { sameNumberMap.Add(sameNumberId, new List <TriadCard>()); sameNumberMap[sameNumberId].Add(card1); card1.SameNumberId = sameNumberId; } sameNumberMap[sameNumberId].Add(card2); card2.SameNumberId = sameNumberId; } } } if (bHasSameNumberCards) { sameNumberId++; } } } Logger.WriteLine("Loaded cards: " + cards.Count + ", same sides: " + sameNumberMap.Count); return(cards.Count > 0); }