From 5480ea8f7caf368479fa30b8ba930012d2501984 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 24 Aug 2025 04:34:37 +0800 Subject: [PATCH 001/124] Add basic Mighty chain implementation --- .../util/LocalizationExtensions.kt | 1 + app/src/main/res/values-b+zh+CN/localized.xml | 1 + app/src/main/res/values-b+zh+TW/localized.xml | 1 + app/src/main/res/values-ja/localized.xml | 1 + app/src/main/res/values-ko/localized.xml | 1 + app/src/main/res/values-vi/localized.xml | 1 + app/src/main/res/values/localized.xml | 1 + .../scripts/enums/BraveChainEnum.kt | 1 + .../scripts/modules/ApplyBraveChains.kt | 47 +++++++++++++++++++ 9 files changed, 55 insertions(+) diff --git a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt index 22dda16b5..ff2cfc450 100644 --- a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt +++ b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt @@ -25,6 +25,7 @@ val BraveChainEnum.stringRes get() = when (this) { BraveChainEnum.None -> R.string.p_brave_chains_don_t_care BraveChainEnum.WithNP -> R.string.p_brave_chains_with_np + BraveChainEnum.WithNPMighty -> R.string.p_brave_chains_with_np_mighty BraveChainEnum.Avoid -> R.string.p_brave_chains_avoid } diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index 93f856fa4..ad732eb1e 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -186,6 +186,7 @@ Order Change" "圣晶石" "无偏好" "宝具施放后" + "宝具施放后(Mighty)" "避免追击连携" "关闭" "跳过" diff --git a/app/src/main/res/values-b+zh+TW/localized.xml b/app/src/main/res/values-b+zh+TW/localized.xml index 4e0120085..0273860b8 100644 --- a/app/src/main/res/values-b+zh+TW/localized.xml +++ b/app/src/main/res/values-b+zh+TW/localized.xml @@ -184,6 +184,7 @@ Order Change" "聖晶石" "無偏好" "寶具施放後" + "寶具施放後(Mighty)" "避免Brave Chain" "關閉" "跳過" diff --git a/app/src/main/res/values-ja/localized.xml b/app/src/main/res/values-ja/localized.xml index f0af57857..48ee50d51 100644 --- a/app/src/main/res/values-ja/localized.xml +++ b/app/src/main/res/values-ja/localized.xml @@ -188,6 +188,7 @@ "聖晶石" "考慮しない" "宝具と同時に" + "宝具と同時に(マイティ)" "使用しない" "使用確認OFF" "使用しない" diff --git a/app/src/main/res/values-ko/localized.xml b/app/src/main/res/values-ko/localized.xml index 9460afab4..bc95a062b 100644 --- a/app/src/main/res/values-ko/localized.xml +++ b/app/src/main/res/values-ko/localized.xml @@ -183,6 +183,7 @@ "성정석" "상관 없음" "보구랑 같이" + "보구랑 같이 (무적 체인)" "자제" "비활성화" "스킵" diff --git a/app/src/main/res/values-vi/localized.xml b/app/src/main/res/values-vi/localized.xml index ca11704d0..fd65fba45 100644 --- a/app/src/main/res/values-vi/localized.xml +++ b/app/src/main/res/values-vi/localized.xml @@ -184,6 +184,7 @@ Sao chép hoặc đổi tên nó nếu bạn muốn giữ nó." "SQ" "không quan tâm" "Với NP" + "Với NP (Mighty)" "Tránh" "Tắt" "Bỏ qua" diff --git a/app/src/main/res/values/localized.xml b/app/src/main/res/values/localized.xml index fac49e869..66755e690 100644 --- a/app/src/main/res/values/localized.xml +++ b/app/src/main/res/values/localized.xml @@ -186,6 +186,7 @@ Copy or rename it if you want to keep it." "SQ" "Don't care" "With NP" + "With NP (Mighty)" "Avoid" "Disabled" "Skip" diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt index 4b378bb3a..686a3b699 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt @@ -3,5 +3,6 @@ package io.github.fate_grand_automata.scripts.enums enum class BraveChainEnum { None, WithNP, + WithNPMighty, Avoid } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 963416306..116798c4e 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -75,6 +75,47 @@ class ApplyBraveChains @Inject constructor() { ) } + private fun withNpMighty( + cards: List, + rearrange: Boolean, + npUsage: NPUsage + ): List { + // Get default NP sort, because we are using the default priority as our baseline + val justRearranged by lazy { + withNpMighty( + cards = cards.take(3), + rearrange = rearrange, + npUsage = npUsage + ) + } + + // If 2 or 3 are NP, ignore. Because FGA cannot detect NP types at the moment + if (npUsage.nps.size >= 2) return justRearranged + + // if cannot get first card, return default + val firstCard = cards.firstOrNull() ?: return justRearranged + val firstCardType = firstCard.type + + val cardsWithDifferentTypesFromFirst = cards + .filter { it.type != firstCardType } + .toMutableList() + + // If all are the same, just return default + val secondCard = cardsWithDifferentTypesFromFirst.firstOrNull() ?: return justRearranged + val secondCardType = secondCard.type + + val cardsWithDifferentTypesFromSecond = cardsWithDifferentTypesFromFirst + .filter { it.type != secondCardType } + .toMutableList() + + val thirdCard = cardsWithDifferentTypesFromSecond.firstOrNull() + val remainder = cards - firstCard - secondCard - thirdCard + + // Return the result + val newList = listOf(firstCard, secondCard) + thirdCard + remainder + return newList.filterNotNull() + } + private fun avoid( cards: List, rearrange: Boolean @@ -172,6 +213,12 @@ class ApplyBraveChains @Inject constructor() { npUsage = npUsage ) + BraveChainEnum.WithNPMighty -> withNpMighty( + cards = cards, + rearrange = rearrange, + npUsage = npUsage + ) + BraveChainEnum.Avoid -> avoid( cards = cards, rearrange = rearrange From c359a884a3d82e6dcfe6043bb64ab1cce676ef22 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 24 Aug 2025 06:03:27 +0800 Subject: [PATCH 002/124] Use the rearranged cards as the baseline --- .../scripts/modules/ApplyBraveChains.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 116798c4e..0ed660541 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -96,7 +96,7 @@ class ApplyBraveChains @Inject constructor() { val firstCard = cards.firstOrNull() ?: return justRearranged val firstCardType = firstCard.type - val cardsWithDifferentTypesFromFirst = cards + val cardsWithDifferentTypesFromFirst = justRearranged .filter { it.type != firstCardType } .toMutableList() @@ -109,10 +109,11 @@ class ApplyBraveChains @Inject constructor() { .toMutableList() val thirdCard = cardsWithDifferentTypesFromSecond.firstOrNull() - val remainder = cards - firstCard - secondCard - thirdCard + val newSet = listOf(firstCard, secondCard, thirdCard) + val remainder = cards - newSet // Return the result - val newList = listOf(firstCard, secondCard) + thirdCard + remainder + val newList = newSet + remainder return newList.filterNotNull() } From fb9e316953f0b79007e94991e571c9ec49e91429 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 24 Aug 2025 06:07:04 +0800 Subject: [PATCH 003/124] Minor refactor to simplify it --- .../scripts/modules/ApplyBraveChains.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 0ed660541..e010ce933 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -109,12 +109,11 @@ class ApplyBraveChains @Inject constructor() { .toMutableList() val thirdCard = cardsWithDifferentTypesFromSecond.firstOrNull() - val newSet = listOf(firstCard, secondCard, thirdCard) - val remainder = cards - newSet + val newSet = listOfNotNull(firstCard, secondCard, thirdCard) + val remainder = justRearranged - newSet // Return the result - val newList = newSet + remainder - return newList.filterNotNull() + return newSet + remainder } private fun avoid( From 7a8fc72613e94a5812dc5285775ef7605894c4a6 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 25 Aug 2025 01:01:48 +0800 Subject: [PATCH 004/124] Fix bug where justRearranged was not calling the correct function (causing a crash because infinite loop). Also fix using cards instead of justRearranged because it does not provide the full 5 cards. --- .../scripts/modules/ApplyBraveChains.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index e010ce933..4a81e099b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -82,8 +82,8 @@ class ApplyBraveChains @Inject constructor() { ): List { // Get default NP sort, because we are using the default priority as our baseline val justRearranged by lazy { - withNpMighty( - cards = cards.take(3), + withNp( + cards = cards, rearrange = rearrange, npUsage = npUsage ) @@ -92,15 +92,24 @@ class ApplyBraveChains @Inject constructor() { // If 2 or 3 are NP, ignore. Because FGA cannot detect NP types at the moment if (npUsage.nps.size >= 2) return justRearranged + // Get np if there is one + val firstNp = npUsage.nps.firstOrNull() + val firstFieldSlot = firstNp?.toFieldSlot() + // if cannot get first card, return default val firstCard = cards.firstOrNull() ?: return justRearranged val firstCardType = firstCard.type - val cardsWithDifferentTypesFromFirst = justRearranged - .filter { it.type != firstCardType } + val cardsWithDifferentTypesFromFirst = cards + .filter { + // Different card + it.type != firstCardType + // and either match with np or there is no np + && (firstFieldSlot == null || it.fieldSlot == firstFieldSlot) + } .toMutableList() - // If all are the same, just return default + // If all are the same (or none matching the NP), just return default val secondCard = cardsWithDifferentTypesFromFirst.firstOrNull() ?: return justRearranged val secondCardType = secondCard.type @@ -110,7 +119,7 @@ class ApplyBraveChains @Inject constructor() { val thirdCard = cardsWithDifferentTypesFromSecond.firstOrNull() val newSet = listOfNotNull(firstCard, secondCard, thirdCard) - val remainder = justRearranged - newSet + val remainder = cards - newSet // Return the result return newSet + remainder From e88f2152e63937a1f4215672117eff0668d7c1a6 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 26 Aug 2025 02:42:04 +0800 Subject: [PATCH 005/124] Fix not checking for NP in the first card check. Add unit tests --- .../scripts/modules/ApplyBraveChains.kt | 11 +- .../scripts/BraveChainsTest.kt | 110 ++++++++++++++++++ .../scripts/MightyChainTest.kt | 54 +++++++++ 3 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 4a81e099b..fe505bfa8 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -97,15 +97,18 @@ class ApplyBraveChains @Inject constructor() { val firstFieldSlot = firstNp?.toFieldSlot() // if cannot get first card, return default - val firstCard = cards.firstOrNull() ?: return justRearranged + val filteredCards = cards + .filter { + // and either match with np or there is no np + firstFieldSlot == null || it.fieldSlot == firstFieldSlot + } + val firstCard = filteredCards.firstOrNull() ?: return justRearranged val firstCardType = firstCard.type - val cardsWithDifferentTypesFromFirst = cards + val cardsWithDifferentTypesFromFirst = filteredCards .filter { // Different card it.type != firstCardType - // and either match with np or there is no np - && (firstFieldSlot == null || it.fieldSlot == firstFieldSlot) } .toMutableList() diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt index 8173e5b5b..7cc3fa466 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt @@ -157,4 +157,114 @@ class BraveChainsTest { assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D) } + + @Test + fun mightyChain_lineup1() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_lineup2() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotA() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + ).map { it.card } + + // Unable to Brave Chain. Will ignore and return result of withNp + assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotB() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + ).map { it.card } + + // Attempt to Brave chain with Scathach cards + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotC() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0) + ).map { it.card } + + // Unable to Mighty Chain. Will ignore and return result of withNp + assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) + } + + @Test + fun mightyChain_fullCards_lineup1() { + val braveChains = ApplyBraveChains() + + val cards = MightyChainTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_fullCards_lineup2() { + val braveChains = ApplyBraveChains() + + val cards = MightyChainTest.lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) + } + + @Test + fun mightyChain_fullCards_lineup3() { + val braveChains = ApplyBraveChains() + + val cards = MightyChainTest.lineup3 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) + } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt new file mode 100644 index 000000000..38efcfc7a --- /dev/null +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt @@ -0,0 +1,54 @@ +package io.github.fate_grand_automata.scripts + +import io.github.fate_grand_automata.scripts.enums.CardAffinityEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.TeamSlot + +class MightyChainTest { + companion object { + val scathach1WB = ParsedCard( + card = CommandCard.Face.A, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val scathach2WQ = ParsedCard( + card = CommandCard.Face.B, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Weak + ) + val scathach3WA = ParsedCard( + card = CommandCard.Face.C, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Weak + ) + val scathach4WB = ParsedCard( + card = CommandCard.Face.D, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val scathach5WQ = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Weak + ) + + val lineup1 = listOf(scathach1WB, scathach2WQ, scathach3WA, scathach4WB, scathach5WQ) + + val lineup2 = listOf(scathach1WB, scathach4WB, scathach5WQ, scathach2WQ, scathach3WA) + + val lineup3 = listOf(scathach5WQ, scathach2WQ, scathach3WA, scathach1WB, scathach4WB) + } +} \ No newline at end of file From 05f15240cc3dc96bdbbffdd141a55b2041cca3ba Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 26 Aug 2025 10:47:34 +0800 Subject: [PATCH 006/124] Fix rearrange for mighty chain and add tests. Add handling for rearrange. Moves mighty chain tests to MightyChainTest and add more tests. --- .../scripts/modules/ApplyBraveChains.kt | 13 +- .../scripts/BraveChainsTest.kt | 110 -------- .../scripts/MightyChainTest.kt | 234 ++++++++++++++++++ 3 files changed, 246 insertions(+), 111 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index fe505bfa8..c33b6db81 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -123,9 +123,20 @@ class ApplyBraveChains @Inject constructor() { val thirdCard = cardsWithDifferentTypesFromSecond.firstOrNull() val newSet = listOfNotNull(firstCard, secondCard, thirdCard) val remainder = cards - newSet + val combinedCards = newSet + remainder + + if (rearrange + && listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } + ) { + Collections.swap(combinedCards, 0, 1) + } // Return the result - return newSet + remainder + return rearrange( + cards = combinedCards, + rearrange = rearrange, + npUsage = npUsage + ) } private fun avoid( diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt index 7cc3fa466..8173e5b5b 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt @@ -157,114 +157,4 @@ class BraveChainsTest { assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D) } - - @Test - fun mightyChain_lineup1() { - val braveChains = ApplyBraveChains() - - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } - - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) - } - - @Test - fun mightyChain_lineup2() { - val braveChains = ApplyBraveChains() - - val cards = FaceCardPriorityTest.lineup2 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } - - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) - } - - @Test - fun mightyChain_lineup1_withNp_fieldSlotA() { - val braveChains = ApplyBraveChains() - - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0) - ).map { it.card } - - // Unable to Brave Chain. Will ignore and return result of withNp - assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) - } - - @Test - fun mightyChain_lineup1_withNp_fieldSlotB() { - val braveChains = ApplyBraveChains() - - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0) - ).map { it.card } - - // Attempt to Brave chain with Scathach cards - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) - } - - @Test - fun mightyChain_lineup1_withNp_fieldSlotC() { - val braveChains = ApplyBraveChains() - - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0) - ).map { it.card } - - // Unable to Mighty Chain. Will ignore and return result of withNp - assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) - } - - @Test - fun mightyChain_fullCards_lineup1() { - val braveChains = ApplyBraveChains() - - val cards = MightyChainTest.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } - - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) - } - - @Test - fun mightyChain_fullCards_lineup2() { - val braveChains = ApplyBraveChains() - - val cards = MightyChainTest.lineup2 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } - - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) - } - - @Test - fun mightyChain_fullCards_lineup3() { - val braveChains = ApplyBraveChains() - - val cards = MightyChainTest.lineup3 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } - - assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) - } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt index 38efcfc7a..6df7fd570 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt @@ -1,11 +1,17 @@ package io.github.fate_grand_automata.scripts +import assertk.assertThat +import assertk.assertions.containsExactly +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardAffinityEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.TeamSlot +import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains +import kotlin.test.Test class MightyChainTest { companion object { @@ -51,4 +57,232 @@ class MightyChainTest { val lineup3 = listOf(scathach5WQ, scathach2WQ, scathach3WA, scathach1WB, scathach4WB) } + + @Test + fun mightyChain_lineup1() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_lineup1_rearrange() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_lineup2() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) + } + + @Test + fun mightyChain_lineup2_rearrange() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.D) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotA() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + ).map { it.card } + + // Unable to Brave Chain. Will ignore and return result of withNp + assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotA_rearrange() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + rearrange = true + ).map { it.card } + + // Unable to Brave Chain. Will ignore and return result of withNp + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotB() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + ).map { it.card } + + // Attempt to Brave chain with Scathach cards + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotB_rearrange() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + rearrange = true + ).map { it.card } + + // Attempt to Brave chain with Scathach cards + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotC() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0) + ).map { it.card } + + // Unable to Mighty Chain. Will ignore and return result of withNp + assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) + } + + @Test + fun mightyChain_lineup1_withNp_fieldSlotC_rearrange() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + rearrange = true + ).map { it.card } + + // Unable to Mighty Chain. Will ignore and return result of withNp + assertThat(picked).containsExactly(CommandCard.Face.D, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) + } + + @Test + fun mightyChain_fullCards_lineup1() { + val braveChains = ApplyBraveChains() + + val cards = lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_fullCards_lineup1_rearrange() { + val braveChains = ApplyBraveChains() + + val cards = lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun mightyChain_fullCards_lineup2() { + val braveChains = ApplyBraveChains() + + val cards = lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) + } + + @Test + fun mightyChain_fullCards_lineup2_rearrange() { + val braveChains = ApplyBraveChains() + + val cards = lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.E, CommandCard.Face.D, CommandCard.Face.B) + } + + @Test + fun mightyChain_fullCards_lineup3() { + val braveChains = ApplyBraveChains() + + val cards = lineup3 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) + } + + @Test + fun mightyChain_fullCards_lineup3_rearrange() { + val braveChains = ApplyBraveChains() + + val cards = lineup3 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) + } } \ No newline at end of file From 56885bcbb40551589da3b49e1f6d941fa3a8a5f0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 26 Aug 2025 21:22:55 +0800 Subject: [PATCH 007/124] Add handling for if third card is invalid and it is not an NP use (it should default to the fallback scenario) --- .../scripts/modules/ApplyBraveChains.kt | 5 ++- .../scripts/MightyChainTest.kt | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index c33b6db81..c907eba21 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -96,12 +96,12 @@ class ApplyBraveChains @Inject constructor() { val firstNp = npUsage.nps.firstOrNull() val firstFieldSlot = firstNp?.toFieldSlot() - // if cannot get first card, return default val filteredCards = cards .filter { // and either match with np or there is no np firstFieldSlot == null || it.fieldSlot == firstFieldSlot } + // if cannot get first card, return default val firstCard = filteredCards.firstOrNull() ?: return justRearranged val firstCardType = firstCard.type @@ -121,6 +121,9 @@ class ApplyBraveChains @Inject constructor() { .toMutableList() val thirdCard = cardsWithDifferentTypesFromSecond.firstOrNull() + // Return default if we do not have a mighty chain + if (thirdCard == null && npUsage.nps.isEmpty()) return justRearranged + val newSet = listOfNotNull(firstCard, secondCard, thirdCard) val remainder = cards - newSet val combinedCards = newSet + remainder diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt index 6df7fd570..446240a02 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt @@ -36,6 +36,13 @@ class MightyChainTest { type = CardTypeEnum.Arts, affinity = CardAffinityEnum.Weak ) + val scathach3WAltQ = ParsedCard( + card = CommandCard.Face.C, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Weak + ) val scathach4WB = ParsedCard( card = CommandCard.Face.D, servant = TeamSlot.B, @@ -56,6 +63,11 @@ class MightyChainTest { val lineup2 = listOf(scathach1WB, scathach4WB, scathach5WQ, scathach2WQ, scathach3WA) val lineup3 = listOf(scathach5WQ, scathach2WQ, scathach3WA, scathach1WB, scathach4WB) + + // 3 Quick, 2 Buster; Buster starting card + val lineup4 = listOf(scathach1WB, scathach2WQ, scathach3WAltQ, scathach4WB, scathach5WQ) + // 3 Quick, 2 Buster; Quick starting card + val lineup5 = listOf(scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB) } @Test @@ -285,4 +297,32 @@ class MightyChainTest { assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) } + + // Scenario for when 2 card types are found but not the 3rd + @Test + fun mightyChain_fullCards_lineup4() { + val braveChains = ApplyBraveChains() + + val cards = lineup4 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + // Scenario for when 2 card types are found but not the 3rd + @Test + fun mightyChain_fullCards_lineup5() { + val braveChains = ApplyBraveChains() + + val cards = lineup5 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.D) + } } \ No newline at end of file From fb2bef64b1b8d1e7437133f1fc4690699a964820 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 26 Aug 2025 21:26:49 +0800 Subject: [PATCH 008/124] Add recommended fix by copilot for easier readability --- .../scripts/modules/ApplyBraveChains.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index c907eba21..3cbd97d70 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -58,13 +58,13 @@ class ApplyBraveChains @Inject constructor() { .toMutableList() val nonMatchingCards = cards - matchingCards val combinedCards = matchingCards + nonMatchingCards + /* When rearrange is active and there is 1 NP and 1 Card before NP, we want the best or matching face-card after NP. */ - if (rearrange - && listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } - ) { + val shouldSwapForNpUsageScenario = listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } + if (rearrange && shouldSwapForNpUsageScenario) { Collections.swap(combinedCards, 0, 1) } @@ -128,9 +128,12 @@ class ApplyBraveChains @Inject constructor() { val remainder = cards - newSet val combinedCards = newSet + remainder - if (rearrange - && listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } - ) { + /* + When rearrange is active and there is 1 NP and 1 Card before NP, + we want the best or matching face-card after NP. + */ + val shouldSwapForNpUsageScenario = listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } + if (rearrange && shouldSwapForNpUsageScenario) { Collections.swap(combinedCards, 0, 1) } From 4f158c955debaac5b584c11de109851d0c2653a0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 27 Aug 2025 01:48:29 +0800 Subject: [PATCH 009/124] Rename and add more comments test names to make them clearer --- .../scripts/MightyChainTest.kt | 66 ++++++++++++------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt index 446240a02..9b00be492 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt @@ -71,7 +71,7 @@ class MightyChainTest { } @Test - fun mightyChain_lineup1() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -80,11 +80,12 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty ).map { it.card } + // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun mightyChain_lineup1_rearrange() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ), rearrange=true`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -94,11 +95,12 @@ class MightyChainTest { rearrange = true ).map { it.card } + // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun mightyChain_lineup2() { + fun `Mixed cards - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup2 @@ -107,11 +109,12 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty ).map { it.card } + // Expect 1SB,2KQ,3NA,5SQ,4NA - 15324 - AECBD assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) } @Test - fun mightyChain_lineup2_rearrange() { + fun `Mixed cards - lineup2 (1SB,5SQ,2SQ,3NA,4NA), rearrange=true`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup2 @@ -121,11 +124,12 @@ class MightyChainTest { rearrange = true ).map { it.card } + // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.D) } @Test - fun mightyChain_lineup1_withNp_fieldSlotA() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -135,12 +139,13 @@ class MightyChainTest { npUsage = NPUsage(setOf(CommandCard.NP.A), 0) ).map { it.card } - // Unable to Brave Chain. Will ignore and return result of withNp + // Unable to Brave Chain with Kama. Will ignore and return result of withNp + // Expect 2KQ,1SB,3NA,4NA,5SQ - 21345 - BACDE assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun mightyChain_lineup1_withNp_fieldSlotA_rearrange() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, rearrange=true`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -151,12 +156,12 @@ class MightyChainTest { rearrange = true ).map { it.card } - // Unable to Brave Chain. Will ignore and return result of withNp + // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun mightyChain_lineup1_withNp_fieldSlotB() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -166,12 +171,13 @@ class MightyChainTest { npUsage = NPUsage(setOf(CommandCard.NP.B), 0) ).map { it.card } - // Attempt to Brave chain with Scathach cards + // Attempt to Brave chain with Scathach NP + // Expect 1SB,5SQ,2KQ,3NA,4NA - 15234 - AEBCD assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) } @Test - fun mightyChain_lineup1_withNp_fieldSlotB_rearrange() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, rearrange=true`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -182,12 +188,12 @@ class MightyChainTest { rearrange = true ).map { it.card } - // Attempt to Brave chain with Scathach cards + // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) } @Test - fun mightyChain_lineup1_withNp_fieldSlotC() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -197,12 +203,13 @@ class MightyChainTest { npUsage = NPUsage(setOf(CommandCard.NP.C), 0) ).map { it.card } - // Unable to Mighty Chain. Will ignore and return result of withNp + // Attempt to Brave chain with Nero NP + // Expect 3NA,4NA,1SB,2KQ,5SQ - 34125 - CDABE assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) } @Test - fun mightyChain_lineup1_withNp_fieldSlotC_rearrange() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, rearranged=true`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -213,12 +220,12 @@ class MightyChainTest { rearrange = true ).map { it.card } - // Unable to Mighty Chain. Will ignore and return result of withNp + // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.D, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) } @Test - fun mightyChain_fullCards_lineup1() { + fun `Full cards - lineup1 (BQABQ)`() { val braveChains = ApplyBraveChains() val cards = lineup1 @@ -227,11 +234,13 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty ).map { it.card } + // Expect same result as input + // Expect BQABQ / 12345 / ABCDE assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun mightyChain_fullCards_lineup1_rearrange() { + fun `Full cards - lineup1 (BQABQ), rearranged=true`() { val braveChains = ApplyBraveChains() val cards = lineup1 @@ -241,11 +250,12 @@ class MightyChainTest { rearrange = true ).map { it.card } + // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun mightyChain_fullCards_lineup2() { + fun `Full cards - lineup2 (BBQQA)`() { val braveChains = ApplyBraveChains() val cards = lineup2 @@ -254,11 +264,12 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty ).map { it.card } + // Expect 1SB,5SQ,3NA,4NA,2KQ / 15342 / AECDB assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) } @Test - fun mightyChain_fullCards_lineup2_rearrange() { + fun `Full cards - lineup2 (BBQQA), rearranged=true`() { val braveChains = ApplyBraveChains() val cards = lineup2 @@ -268,11 +279,12 @@ class MightyChainTest { rearrange = true ).map { it.card } + // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.E, CommandCard.Face.D, CommandCard.Face.B) } @Test - fun mightyChain_fullCards_lineup3() { + fun `Full cards - lineup3 (QQABB)`() { val braveChains = ApplyBraveChains() val cards = lineup3 @@ -281,11 +293,12 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty ).map { it.card } + // Expect QABQB / 53124 / ECABD assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) } @Test - fun mightyChain_fullCards_lineup3_rearrange() { + fun `Full cards - lineup3 (QQABB), rearranged=true`() { val braveChains = ApplyBraveChains() val cards = lineup3 @@ -295,12 +308,13 @@ class MightyChainTest { rearrange = true ).map { it.card } + // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) } // Scenario for when 2 card types are found but not the 3rd @Test - fun mightyChain_fullCards_lineup4() { + fun `Full cards - lineup4 (BQQBQ) - No mighty chain`() { val braveChains = ApplyBraveChains() val cards = lineup4 @@ -309,12 +323,14 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty ).map { it.card } + // Expect same result as input + // Expect BQQBQ / 12345 / ABCDE assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } // Scenario for when 2 card types are found but not the 3rd @Test - fun mightyChain_fullCards_lineup5() { + fun `Full cards - lineup5 (QQQBB) - No mighty chain`() { val braveChains = ApplyBraveChains() val cards = lineup5 @@ -323,6 +339,8 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty ).map { it.card } + // Expect same result as input + // Expect QQQBB / 52314 / EBCAD assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.D) } } \ No newline at end of file From 553985c98105dfe57ca2ca483eef544aebca25ba Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 28 Aug 2025 22:16:08 +0800 Subject: [PATCH 010/124] Add np matcher test code --- app/src/main/assets/En/art_splash.png | Bin 0 -> 26219 bytes app/src/main/assets/En/buster_splash.png | Bin 0 -> 25459 bytes app/src/main/assets/En/quick_splash.png | Bin 0 -> 18961 bytes .../io/github/lib_automata/AutomataApi.kt | 5 ++ .../fate_grand_automata/scripts/Images.kt | 3 ++ .../locations/BattleScreenLocations.kt | 7 ++- .../scripts/modules/ApplyBraveChains.kt | 14 +++-- .../scripts/modules/Card.kt | 14 ++++- .../scripts/modules/CardParser.kt | 5 ++ .../scripts/modules/ServantTracker.kt | 50 ++++++++++++++++-- 10 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 app/src/main/assets/En/art_splash.png create mode 100644 app/src/main/assets/En/buster_splash.png create mode 100644 app/src/main/assets/En/quick_splash.png diff --git a/app/src/main/assets/En/art_splash.png b/app/src/main/assets/En/art_splash.png new file mode 100644 index 0000000000000000000000000000000000000000..1f7e48c37b1d204321c5dad0e89a5b56c41a7aa6 GIT binary patch literal 26219 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%0AeL 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 LNkvXXu0mjf-)(3b literal 0 HcmV?d00001 diff --git a/app/src/main/assets/En/quick_splash.png b/app/src/main/assets/En/quick_splash.png new file mode 100644 index 0000000000000000000000000000000000000000..6bd6d84a6a7a7c5335b30466b64b1400875d4007 GIT binary patch literal 18961 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 Region(-685, 410, 110, 60).xFromCenter() else -> Region(-685, 330, 110, 60).xFromCenter() } + val servantNpCardTypeRegion = when (gameServer) { + is GameServer.Jp, GameServer.Cn -> 410 + else -> 330 + }.let { y -> Region(-825, y + 150, 400, 250) }.xFromCenter() + val battleSafeMiddleOfScreenClick = Location(0, 550).xFromCenter() } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 3cbd97d70..8ba0f2880 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -1,6 +1,8 @@ package io.github.fate_grand_automata.scripts.modules import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot @@ -78,7 +80,8 @@ class ApplyBraveChains @Inject constructor() { private fun withNpMighty( cards: List, rearrange: Boolean, - npUsage: NPUsage + npUsage: NPUsage, + npTypes: Map = emptyMap() ): List { // Get default NP sort, because we are using the default priority as our baseline val justRearranged by lazy { @@ -95,6 +98,9 @@ class ApplyBraveChains @Inject constructor() { // Get np if there is one val firstNp = npUsage.nps.firstOrNull() val firstFieldSlot = firstNp?.toFieldSlot() + val firstNpType = firstFieldSlot + ?.let { npTypes.getOrElse(firstFieldSlot) { CardTypeEnum.Unknown } } + ?: CardTypeEnum.Unknown val filteredCards = cards .filter { @@ -227,7 +233,8 @@ class ApplyBraveChains @Inject constructor() { cards: List, braveChains: BraveChainEnum, rearrange: Boolean = false, - npUsage: NPUsage = NPUsage.none + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap() ): List { val picked = when (braveChains) { BraveChainEnum.None -> rearrange( @@ -245,7 +252,8 @@ class ApplyBraveChains @Inject constructor() { BraveChainEnum.WithNPMighty -> withNpMighty( cards = cards, rearrange = rearrange, - npUsage = npUsage + npUsage = npUsage, + npTypes ) BraveChainEnum.Avoid -> avoid( diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index 6296f0bae..908f11416 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -3,12 +3,15 @@ package io.github.fate_grand_automata.scripts.modules import io.github.fate_grand_automata.scripts.IFgoAutomataApi import io.github.fate_grand_automata.scripts.ScriptLog import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.SpamConfigPerTeamSlot +import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.scripts.models.battle.BattleState +import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.fate_grand_automata.scripts.prefs.IBattleConfig import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @@ -56,12 +59,14 @@ class Card @Inject constructor( val braveChainsPerWave = battleConfig.braveChains val rearrangeCardsPerWave = battleConfig.rearrangeCards + val npTypes = npUsage.nps.associate { it.toFieldSlot() to it.type() } return braveChains.pick( cards = cardsOrderedByPriority, npUsage = npUsage, braveChains = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), - rearrange = rearrangeCardsPerWave.inCurrentWave(false) + rearrange = rearrangeCardsPerWave.inCurrentWave(false), + npTypes = npTypes ).map { it.card } } @@ -92,4 +97,11 @@ class Card @Inject constructor( .also { messages.log(ScriptLog.ClickingCards(it)) } .forEach { caster.use(it) } } + + fun CommandCard.NP.type(): CardTypeEnum { + val fieldSlot = this.toFieldSlot() + val teamSlot = servantTracker.deployed[fieldSlot] ?: return CardTypeEnum.Unknown + if (teamSlot is TeamSlot.Unknown) return CardTypeEnum.Unknown + return servantTracker.getNpCardType(teamSlot) + } } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/CardParser.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/CardParser.kt index 18323f8ca..73e48a6c1 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/CardParser.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/CardParser.kt @@ -6,10 +6,15 @@ import io.github.fate_grand_automata.scripts.ScriptNotify import io.github.fate_grand_automata.scripts.enums.CardAffinityEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.TeamSlot +import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.mapValues @ScriptScope class CardParser @Inject constructor( diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt index 5e9d48ff3..604a4a86b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt @@ -3,6 +3,7 @@ package io.github.fate_grand_automata.scripts.modules import io.github.fate_grand_automata.scripts.IFgoAutomataApi import io.github.fate_grand_automata.scripts.Images import io.github.fate_grand_automata.scripts.ScriptLog +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.OrderChangeMember @@ -11,6 +12,9 @@ import io.github.fate_grand_automata.scripts.models.skills import io.github.lib_automata.Pattern import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.mapValues import kotlin.time.Duration.Companion.milliseconds @ScriptScope @@ -57,9 +61,26 @@ class ServantTracker @Inject constructor( private val faceCardImages = mutableMapOf>() + /** + * NP card type handling + */ + private val npCardImages = mutableMapOf>() + private val npSplashImages = mapOf( + CardTypeEnum.Buster to images[Images.SplashBuster], + CardTypeEnum.Arts to images[Images.SplashArts], + CardTypeEnum.Quick to images[Images.SplashQuick], + ) + val npCardTypes = mutableMapOf() + + fun getNpCardType(teamSlot: TeamSlot): CardTypeEnum { + return npCardTypes.getOrElse(teamSlot) { CardTypeEnum.Unknown } + } + + override fun close() { checkImages.values.forEach { it.close() } faceCardImages.values.flatten().forEach { it.close() } + npCardImages.values.flatten().forEach { it.close() } checkImages.clear() } @@ -92,9 +113,28 @@ class ServantTracker @Inject constructor( if (supportSlot == null && isSupport) { supportSlot = teamSlot - } else if (!isSupport) { - // Don't useSameSnapIn here, since we open a dialog - initFaceCard(teamSlot, slot) + } + + // We now always want to init the face card, so that we can check the npType + // Don't useSameSnapIn here, since we open a dialog + initFaceCard(teamSlot, slot) + + // After initFaceCard, we need to check npType for servant here + if (npCardImages.contains(teamSlot)) { + val patternList = npCardImages[teamSlot] + // Get type by comparing the patterns we have with the splash arts + val type = npSplashImages + .mapValues { + (_, image) -> patternList?.maxOf { pattern -> + pattern.find(image)?.score ?: 0.0 + } ?: 0.0 + } + .filterValues { it > 0.0 } + .maxByOrNull { it.value } + ?.key + if (type != null) { + npCardTypes.put(teamSlot, type) + } } } @@ -110,11 +150,15 @@ class ServantTracker @Inject constructor( val image = locations.battle.servantDetailsFaceCardRegion.getPattern("Face $teamSlot") + val npImage = locations.battle.servantNpCardTypeRegion.getPattern("NP type $teamSlot") + // Close dialog locations.battle.extraInfoWindowCloseClick.click() faceCardImages.getOrPut(teamSlot) { mutableListOf() } .add(image) + npCardImages.getOrPut(teamSlot) { mutableListOf() } + .add(npImage) 250.milliseconds.wait() } From 9aa0036e929a71eff3449dd2793fa48063213eee Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 28 Aug 2025 23:14:36 +0800 Subject: [PATCH 011/124] Update images used for checking. --- app/src/main/assets/En/art_splash.png | Bin 26219 -> 0 bytes app/src/main/assets/En/buster_splash.png | Bin 25459 -> 0 bytes app/src/main/assets/En/np_art.png | Bin 0 -> 1717 bytes app/src/main/assets/En/np_buster.png | Bin 0 -> 1696 bytes app/src/main/assets/En/np_quick.png | Bin 0 -> 1678 bytes app/src/main/assets/En/quick_splash.png | Bin 18961 -> 0 bytes .../fate_grand_automata/scripts/Images.kt | 6 +++--- .../scripts/locations/BattleScreenLocations.kt | 2 +- .../scripts/modules/ServantTracker.kt | 17 +++++++++-------- 9 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 app/src/main/assets/En/art_splash.png delete mode 100644 app/src/main/assets/En/buster_splash.png create mode 100644 app/src/main/assets/En/np_art.png create mode 100644 app/src/main/assets/En/np_buster.png create mode 100644 app/src/main/assets/En/np_quick.png delete mode 100644 app/src/main/assets/En/quick_splash.png diff --git a/app/src/main/assets/En/art_splash.png b/app/src/main/assets/En/art_splash.png deleted file mode 100644 index 1f7e48c37b1d204321c5dad0e89a5b56c41a7aa6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26219 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%0AeL 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 LNkvXXu0mjf-)(3b diff --git a/app/src/main/assets/En/np_art.png b/app/src/main/assets/En/np_art.png new file mode 100644 index 0000000000000000000000000000000000000000..fda684fea1cfb78a45799f2f0413b8df08a637b0 GIT binary patch literal 1717 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 RNNW`Xcu>7vE1knL{{-!#_YVL7 literal 0 HcmV?d00001 diff --git a/app/src/main/assets/En/quick_splash.png b/app/src/main/assets/En/quick_splash.png deleted file mode 100644 index 6bd6d84a6a7a7c5335b30466b64b1400875d4007..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18961 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 410 else -> 330 - }.let { y -> Region(-825, y + 150, 400, 250) }.xFromCenter() + }.let { y -> Region(-735, y + 350, 100, 50) }.xFromCenter() val battleSafeMiddleOfScreenClick = Location(0, 550).xFromCenter() } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt index 604a4a86b..cebc5c492 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt @@ -66,9 +66,9 @@ class ServantTracker @Inject constructor( */ private val npCardImages = mutableMapOf>() private val npSplashImages = mapOf( - CardTypeEnum.Buster to images[Images.SplashBuster], - CardTypeEnum.Arts to images[Images.SplashArts], - CardTypeEnum.Quick to images[Images.SplashQuick], + CardTypeEnum.Buster to images[Images.NpBuster], + CardTypeEnum.Arts to images[Images.NpArts], + CardTypeEnum.Quick to images[Images.NpQuick], ) val npCardTypes = mutableMapOf() @@ -122,16 +122,18 @@ class ServantTracker @Inject constructor( // After initFaceCard, we need to check npType for servant here if (npCardImages.contains(teamSlot)) { val patternList = npCardImages[teamSlot] - // Get type by comparing the patterns we have with the splash arts - val type = npSplashImages + val scores = npSplashImages .mapValues { - (_, image) -> patternList?.maxOf { pattern -> - pattern.find(image)?.score ?: 0.0 + (_, image) -> patternList?.maxOf { + pattern -> pattern.find(image)?.score ?: 0.0 } ?: 0.0 } + // Get type by comparing the patterns we have with the splash arts + val type = scores .filterValues { it > 0.0 } .maxByOrNull { it.value } ?.key + if (type != null) { npCardTypes.put(teamSlot, type) } @@ -149,7 +151,6 @@ class ServantTracker @Inject constructor( 250.milliseconds.wait() val image = locations.battle.servantDetailsFaceCardRegion.getPattern("Face $teamSlot") - val npImage = locations.battle.servantNpCardTypeRegion.getPattern("NP type $teamSlot") // Close dialog From 44125fb50122e67abca6b2c4877618c29093994b Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 29 Aug 2025 00:42:20 +0800 Subject: [PATCH 012/124] Refactor Mighty Chain logic to different file --- .../scripts/modules/ApplyBraveChains.kt | 56 +++------- .../scripts/modules/ApplyMightyChains.kt | 102 ++++++++++++++++++ 2 files changed, 114 insertions(+), 44 deletions(-) create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyMightyChains.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 8ba0f2880..1d1246169 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -12,6 +12,8 @@ import javax.inject.Inject @ScriptScope class ApplyBraveChains @Inject constructor() { + private val applyMightyChains = ApplyMightyChains() + private fun rearrange( cards: List, rearrange: Boolean, @@ -83,7 +85,7 @@ class ApplyBraveChains @Inject constructor() { npUsage: NPUsage, npTypes: Map = emptyMap() ): List { - // Get default NP sort, because we are using the default priority as our baseline + // Get default NP sort, because using the default priority as the baseline val justRearranged by lazy { withNp( cards = cards, @@ -92,47 +94,13 @@ class ApplyBraveChains @Inject constructor() { ) } - // If 2 or 3 are NP, ignore. Because FGA cannot detect NP types at the moment - if (npUsage.nps.size >= 2) return justRearranged - - // Get np if there is one - val firstNp = npUsage.nps.firstOrNull() - val firstFieldSlot = firstNp?.toFieldSlot() - val firstNpType = firstFieldSlot - ?.let { npTypes.getOrElse(firstFieldSlot) { CardTypeEnum.Unknown } } - ?: CardTypeEnum.Unknown - - val filteredCards = cards - .filter { - // and either match with np or there is no np - firstFieldSlot == null || it.fieldSlot == firstFieldSlot - } - // if cannot get first card, return default - val firstCard = filteredCards.firstOrNull() ?: return justRearranged - val firstCardType = firstCard.type - - val cardsWithDifferentTypesFromFirst = filteredCards - .filter { - // Different card - it.type != firstCardType - } - .toMutableList() - - // If all are the same (or none matching the NP), just return default - val secondCard = cardsWithDifferentTypesFromFirst.firstOrNull() ?: return justRearranged - val secondCardType = secondCard.type - - val cardsWithDifferentTypesFromSecond = cardsWithDifferentTypesFromFirst - .filter { it.type != secondCardType } - .toMutableList() - - val thirdCard = cardsWithDifferentTypesFromSecond.firstOrNull() - // Return default if we do not have a mighty chain - if (thirdCard == null && npUsage.nps.isEmpty()) return justRearranged - - val newSet = listOfNotNull(firstCard, secondCard, thirdCard) - val remainder = cards - newSet - val combinedCards = newSet + remainder + // Returns empty if there are no valid targets. If empty, return the default + val mightyChainList = applyMightyChains.getMightyChain( + cards, + npUsage, + npTypes + )?.toMutableList() + if (mightyChainList == null || mightyChainList.isEmpty()) return justRearranged /* When rearrange is active and there is 1 NP and 1 Card before NP, @@ -140,12 +108,12 @@ class ApplyBraveChains @Inject constructor() { */ val shouldSwapForNpUsageScenario = listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } if (rearrange && shouldSwapForNpUsageScenario) { - Collections.swap(combinedCards, 0, 1) + Collections.swap(mightyChainList, 0, 1) } // Return the result return rearrange( - cards = combinedCards, + cards = mightyChainList, rearrange = rearrange, npUsage = npUsage ) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyMightyChains.kt new file mode 100644 index 000000000..5f3dd1085 --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyMightyChains.kt @@ -0,0 +1,102 @@ +package io.github.fate_grand_automata.scripts.modules + +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.toFieldSlot +import io.github.lib_automata.dagger.ScriptScope +import javax.inject.Inject + +@ScriptScope +class ApplyMightyChains @Inject constructor() { + // We want 3 unique types, the magic number + val totalUniqueCardTypesPermitted = 3 + + fun getMightyChain ( + cards: List, + npUsage: NPUsage, + npTypes: Map + ): List? { + val uniqueCardTypesFromNp = npTypes.values.toSet() + if (!isMightyChainAllowed(npUsage, uniqueCardTypesFromNp, npTypes)) return null + + // Get np if there is only 1 (since we want to try for Brave Chain) + val firstNp = if (npUsage.nps.size > 1) npUsage.nps.firstOrNull() else null + val firstFieldSlot = firstNp?.toFieldSlot() + + return tryToGetMightyChain( + cards, + uniqueCardTypesFromNp, + firstFieldSlot + ) + } + + // Returns null if uniqueCards cannot be found + fun tryToGetMightyChain( + cards: List, + // Decreases number of cards to get + // e.g. usually based on npSize + uniqueCardTypesAlreadyFilled: Set, + // In case of a single NP, we want to know what slot it is + singleNpFieldSlot: FieldSlot? + ): List? { + val cardsToFind = totalUniqueCardTypesPermitted + val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() + + val newList = mutableListOf() + var filteredCards = cards + while (uniqueCardTypes.size < cardsToFind) { + filteredCards = filteredCards.filter { + // Always look for a different card type + it.type !in uniqueCardTypes + } + // If there is a single NP, we want to try for a Brave Mighty Chain + if (singleNpFieldSlot != null) { + // Attempt to find one matching the fieldSlot + val fieldSlotList = filteredCards.filter { + it.fieldSlot == singleNpFieldSlot + } + if (fieldSlotList.isNotEmpty()) filteredCards = fieldSlotList + } + val filteredCard = filteredCards.firstOrNull() + if (filteredCard == null) break; // If cannot find, we leave + uniqueCardTypes.add(filteredCard.type) + newList.add(filteredCard) + } + + // If there isn't a valid list of cards, we reject and have empty be returned + if (uniqueCardTypes.size < cardsToFind) return null + + // Otherwise, we return the expected output + val remainder = cards - newList + val combinedCards = newList + remainder + + return combinedCards + } + + fun isMightyChainAllowed ( + npUsage: NPUsage, + uniqueCardTypesFromNp: Set, + npTypes: Map = emptyMap() + ): Boolean { + val npUsageSize = npUsage.nps.size + + // if there are more than 1 NP and we don't know their types, + // just do the default since we can't guarantee a mighty chain anyway + if (npUsageSize > 0 && npTypes.size != npUsageSize) return false + + // We stop if + return ( + // number of nps is 1 + npUsageSize == 1 + // or number of nps do not match with unique size + // scenarios: + // 1 NP = no problem, we proceed + // 3 NP = Even if it doesn't match, it doesn't matter to us since all are filled + // 2 NP (different) = 2 unique == 2 nps (clear to proceed) + // 2 NP (same) = 2 clash already. No mighty chain here + || uniqueCardTypesFromNp.size == npUsageSize + ) + } +} \ No newline at end of file From 690d56d5cf975e0506e1555fab81f7d3ee9f3470 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 29 Aug 2025 01:03:38 +0800 Subject: [PATCH 013/124] Add more unit tests --- .../scripts/MightyChainTest.kt | 240 +++++++++++++++++- 1 file changed, 230 insertions(+), 10 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt index 9b00be492..2b7c40c5d 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt @@ -1,7 +1,9 @@ package io.github.fate_grand_automata.scripts import assertk.assertThat +import assertk.assertions.contains import assertk.assertions.containsExactly +import assertk.assertions.isEqualTo import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardAffinityEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum @@ -11,6 +13,7 @@ import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains +import kotlin.collections.mapOf import kotlin.test.Test class MightyChainTest { @@ -85,7 +88,7 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ), rearrange=true`() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -114,7 +117,7 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup2 (1SB,5SQ,2SQ,3NA,4NA), rearrange=true`() { + fun `Mixed cards - lineup2 (1SB,5SQ,2SQ,3NA,4NA), with rearrange`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup2 @@ -138,14 +141,18 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + ).map { it.card } // Unable to Brave Chain with Kama. Will ignore and return result of withNp - // Expect 2KQ,1SB,3NA,4NA,5SQ - 21345 - BACDE - assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).isEqualTo(pickedDefault) } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, rearrange=true`() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -155,9 +162,52 @@ class MightyChainTest { npUsage = NPUsage(setOf(CommandCard.NP.A), 0), rearrange = true ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + rearrange = true + ).map { it.card } // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + ).map { it.card } + + // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange & npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + rearrange = true + ).map { it.card } + + // Same as above but swap position 1 and 2) // because NP + assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) } @Test @@ -170,14 +220,18 @@ class MightyChainTest { braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + ).map { it.card } - // Attempt to Brave chain with Scathach NP - // Expect 1SB,5SQ,2KQ,3NA,4NA - 15234 - AEBCD - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + // Unable to Brave Chain with Scathach. Will ignore and return result of withNp + assertThat(picked).isEqualTo(pickedDefault) } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, rearrange=true`() { + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with rearrange`() { val braveChains = ApplyBraveChains() val cards = FaceCardPriorityTest.lineup1 @@ -192,6 +246,24 @@ class MightyChainTest { assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) } + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ) + ).map { it.card } + + // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + @Test fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { val braveChains = ApplyBraveChains() @@ -224,6 +296,43 @@ class MightyChainTest { assertThat(picked).containsExactly(CommandCard.Face.D, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) } + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ) + ).map { it.card } + + // Expect 1SB,2KQ,3NA,4NA,5SQ - 12345 - ABCDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearrange & npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + rearrange = true + ).map { it.card } + + // Expect 2KQ,1SB,3NA,4NA,5SQ - 21345 - BACDE + assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + @Test fun `Full cards - lineup1 (BQABQ)`() { val braveChains = ApplyBraveChains() @@ -343,4 +452,115 @@ class MightyChainTest { // Expect QQQBB / 52314 / EBCAD assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.D) } + + /** + * Two NP scenario + */ + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero)`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) + ).map { it.card } + + // Should fall back to default behaviour + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts + ) + ).map { it.card } + + // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach)`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) + ).map { it.card } + + // Should fall back to default behaviour + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) + ).map { it.card } + + // Should fall back to default behaviour + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = FaceCardPriorityTest.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts, + ) + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0) + ).map { it.card } + + // Should fall back to default behaviour + assertThat(picked).isEqualTo(pickedDefault) + } } \ No newline at end of file From 6826203131e443b9ce9654708bab4ff95ec71137 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 29 Aug 2025 02:02:09 +0800 Subject: [PATCH 014/124] Remove unnecessary additions --- .../github/fate_grand_automata/scripts/modules/CardParser.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/CardParser.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/CardParser.kt index 73e48a6c1..18323f8ca 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/CardParser.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/CardParser.kt @@ -6,15 +6,10 @@ import io.github.fate_grand_automata.scripts.ScriptNotify import io.github.fate_grand_automata.scripts.enums.CardAffinityEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard -import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.TeamSlot -import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.mapValues @ScriptScope class CardParser @Inject constructor( From f83166e8ddcd2eec930a06f6df31eb4d34cf48b3 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 29 Aug 2025 02:28:46 +0800 Subject: [PATCH 015/124] Update minimum similarity score to 0.5 (since 0.9 is the matching) --- .../fate_grand_automata/scripts/modules/ServantTracker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt index cebc5c492..078dd7f6f 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt @@ -125,7 +125,7 @@ class ServantTracker @Inject constructor( val scores = npSplashImages .mapValues { (_, image) -> patternList?.maxOf { - pattern -> pattern.find(image)?.score ?: 0.0 + pattern -> pattern.find(image, 0.5)?.score ?: 0.0 } ?: 0.0 } // Get type by comparing the patterns we have with the splash arts From 120f774b04c382e822823a7550ecac1aefff927e Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 00:10:33 +0800 Subject: [PATCH 016/124] Refactor: Move ApplyMightyChains to `attack` package --- .../fate_grand_automata/scripts/modules/ApplyBraveChains.kt | 1 + .../scripts/modules/{ => attack}/ApplyMightyChains.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/{ => attack}/ApplyMightyChains.kt (98%) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 1d1246169..cf584f75c 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -6,6 +6,7 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot +import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains import io.github.lib_automata.dagger.ScriptScope import java.util.Collections import javax.inject.Inject diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt similarity index 98% rename from scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyMightyChains.kt rename to scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt index 5f3dd1085..5771783c2 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyMightyChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt @@ -1,4 +1,4 @@ -package io.github.fate_grand_automata.scripts.modules +package io.github.fate_grand_automata.scripts.modules.attack import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot From 322970c34bd3645a944a87e875c8efbce4ed7150 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 00:10:36 +0800 Subject: [PATCH 017/124] Test: Update `MightyChainTest` to use `AttackLineUps` and clarify test names --- .../scripts/MightyChainTest.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt index 2b7c40c5d..0fbeb816e 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt @@ -334,10 +334,10 @@ class MightyChainTest { } @Test - fun `Full cards - lineup1 (BQABQ)`() { + fun `ScenarioFullCards - lineup1 (BQABQ)`() { val braveChains = ApplyBraveChains() - val cards = lineup1 + val cards = AttackLineUps.ScenarioFullCards.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -349,10 +349,10 @@ class MightyChainTest { } @Test - fun `Full cards - lineup1 (BQABQ), rearranged=true`() { + fun `ScenarioFullCards - lineup1 (BQABQ), rearranged=true`() { val braveChains = ApplyBraveChains() - val cards = lineup1 + val cards = AttackLineUps.ScenarioFullCards.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -364,10 +364,10 @@ class MightyChainTest { } @Test - fun `Full cards - lineup2 (BBQQA)`() { + fun `ScenarioFullCards - lineup2 (BBQQA)`() { val braveChains = ApplyBraveChains() - val cards = lineup2 + val cards = AttackLineUps.ScenarioFullCards.lineup2 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -378,10 +378,10 @@ class MightyChainTest { } @Test - fun `Full cards - lineup2 (BBQQA), rearranged=true`() { + fun `ScenarioFullCards - lineup2 (BBQQA), rearranged=true`() { val braveChains = ApplyBraveChains() - val cards = lineup2 + val cards = AttackLineUps.ScenarioFullCards.lineup2 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -393,10 +393,10 @@ class MightyChainTest { } @Test - fun `Full cards - lineup3 (QQABB)`() { + fun `ScenarioFullCards - lineup3 (QQABB)`() { val braveChains = ApplyBraveChains() - val cards = lineup3 + val cards = AttackLineUps.ScenarioFullCards.lineup3 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -407,10 +407,10 @@ class MightyChainTest { } @Test - fun `Full cards - lineup3 (QQABB), rearranged=true`() { + fun `ScenarioFullCards - lineup3 (QQABB), rearranged=true`() { val braveChains = ApplyBraveChains() - val cards = lineup3 + val cards = AttackLineUps.ScenarioFullCards.lineup3 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -423,10 +423,10 @@ class MightyChainTest { // Scenario for when 2 card types are found but not the 3rd @Test - fun `Full cards - lineup4 (BQQBQ) - No mighty chain`() { + fun `ScenarioFullCards - lineup4 (BQQBQ) - No mighty chain`() { val braveChains = ApplyBraveChains() - val cards = lineup4 + val cards = AttackLineUps.ScenarioFullCards.lineup4 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -439,10 +439,10 @@ class MightyChainTest { // Scenario for when 2 card types are found but not the 3rd @Test - fun `Full cards - lineup5 (QQQBB) - No mighty chain`() { + fun `ScenarioFullCards - lineup5 (QQQBB) - No mighty chain`() { val braveChains = ApplyBraveChains() - val cards = lineup5 + val cards = AttackLineUps.ScenarioFullCards.lineup5 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty From 9ca62de8c9850b105a63664815b8c34d0e36c002 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 00:10:35 +0800 Subject: [PATCH 018/124] Test: Extract card lineups for attack tests --- .../scripts/AttackLineUps.kt | 66 +++++++++++++++++++ .../scripts/MightyChainTest.kt | 55 ---------------- 2 files changed, 66 insertions(+), 55 deletions(-) create mode 100644 scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt new file mode 100644 index 000000000..2301edc8a --- /dev/null +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt @@ -0,0 +1,66 @@ +package io.github.fate_grand_automata.scripts + +import io.github.fate_grand_automata.scripts.enums.CardAffinityEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.TeamSlot + +class AttackLineUps { + data object ScenarioFullCards { + val scathach1WB = ParsedCard( + card = CommandCard.Face.A, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val scathach2WQ = ParsedCard( + card = CommandCard.Face.B, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Weak + ) + val scathach3WA = ParsedCard( + card = CommandCard.Face.C, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Weak + ) + val scathach3WAltQ = ParsedCard( + card = CommandCard.Face.C, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Weak + ) + val scathach4WB = ParsedCard( + card = CommandCard.Face.D, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val scathach5WQ = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Weak + ) + + val lineup1 = listOf(scathach1WB, scathach2WQ, scathach3WA, scathach4WB, scathach5WQ) + + val lineup2 = listOf(scathach1WB, scathach4WB, scathach5WQ, scathach2WQ, scathach3WA) + + val lineup3 = listOf(scathach5WQ, scathach2WQ, scathach3WA, scathach1WB, scathach4WB) + + // 3 Quick, 2 Buster; Buster starting card + val lineup4 = listOf(scathach1WB, scathach2WQ, scathach3WAltQ, scathach4WB, scathach5WQ) + // 3 Quick, 2 Buster; Quick starting card + val lineup5 = listOf(scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB) + } +} \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt index 0fbeb816e..7e4940da2 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt @@ -17,61 +17,6 @@ import kotlin.collections.mapOf import kotlin.test.Test class MightyChainTest { - companion object { - val scathach1WB = ParsedCard( - card = CommandCard.Face.A, - servant = TeamSlot.B, - fieldSlot = FieldSlot.B, - type = CardTypeEnum.Buster, - affinity = CardAffinityEnum.Weak - ) - val scathach2WQ = ParsedCard( - card = CommandCard.Face.B, - servant = TeamSlot.B, - fieldSlot = FieldSlot.B, - type = CardTypeEnum.Quick, - affinity = CardAffinityEnum.Weak - ) - val scathach3WA = ParsedCard( - card = CommandCard.Face.C, - servant = TeamSlot.B, - fieldSlot = FieldSlot.B, - type = CardTypeEnum.Arts, - affinity = CardAffinityEnum.Weak - ) - val scathach3WAltQ = ParsedCard( - card = CommandCard.Face.C, - servant = TeamSlot.B, - fieldSlot = FieldSlot.B, - type = CardTypeEnum.Quick, - affinity = CardAffinityEnum.Weak - ) - val scathach4WB = ParsedCard( - card = CommandCard.Face.D, - servant = TeamSlot.B, - fieldSlot = FieldSlot.B, - type = CardTypeEnum.Buster, - affinity = CardAffinityEnum.Weak - ) - val scathach5WQ = ParsedCard( - card = CommandCard.Face.E, - servant = TeamSlot.B, - fieldSlot = FieldSlot.B, - type = CardTypeEnum.Quick, - affinity = CardAffinityEnum.Weak - ) - - val lineup1 = listOf(scathach1WB, scathach2WQ, scathach3WA, scathach4WB, scathach5WQ) - - val lineup2 = listOf(scathach1WB, scathach4WB, scathach5WQ, scathach2WQ, scathach3WA) - - val lineup3 = listOf(scathach5WQ, scathach2WQ, scathach3WA, scathach1WB, scathach4WB) - - // 3 Quick, 2 Buster; Buster starting card - val lineup4 = listOf(scathach1WB, scathach2WQ, scathach3WAltQ, scathach4WB, scathach5WQ) - // 3 Quick, 2 Buster; Quick starting card - val lineup5 = listOf(scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB) - } @Test fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { From 728252d0b9e0e9ae57632377c814b315df8f59a2 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 00:51:28 +0800 Subject: [PATCH 019/124] Make MightyChainTest use AttackLineUps for all test cases --- .../scripts/AttackLineUps.kt | 126 +++++++++++++++++- .../scripts/MightyChainTest.kt | 116 ++++++++-------- 2 files changed, 181 insertions(+), 61 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt index 2301edc8a..5bc576d7c 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt @@ -8,7 +8,131 @@ import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.TeamSlot class AttackLineUps { - data object ScenarioFullCards { + data object Standard { + val scathach1WB = ParsedCard( + card = CommandCard.Face.A, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val kama2Q = ParsedCard( + card = CommandCard.Face.B, + servant = TeamSlot.A, + fieldSlot = FieldSlot.A, + type = CardTypeEnum.Quick + ) + val nero3RA = ParsedCard( + card = CommandCard.Face.C, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Resist + ) + val nero4RA = ParsedCard( + card = CommandCard.Face.D, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Resist + ) + val scathach5WQ = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Weak + ) + + /** + * [Kama, Scathach, Nero] + * [Scathach WB] [Kama Q] [Nero RA] [Nero RA] [Scathach WQ] + */ + val lineup1 = listOf(scathach1WB, kama2Q, nero3RA, nero4RA, scathach5WQ) + + val lineup2 = listOf(scathach1WB, scathach5WQ, kama2Q, nero3RA, nero4RA) + } + + data object BusterFocus { + val kiyohime1B = ParsedCard( + card = CommandCard.Face.A, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val kiyohime2B = ParsedCard( + card = CommandCard.Face.A, + servant = TeamSlot.A, + fieldSlot = FieldSlot.A, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val nero3RA = ParsedCard( + card = CommandCard.Face.C, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Resist + ) + val nero4RA = ParsedCard( + card = CommandCard.Face.D, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Resist + ) + val kiyohime5B = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val nero3QAlt = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Resist + ) + val nero4BAlt = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Resist + ) + val nero5BAlt = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Resist + ) + + // Buster brave chain + val lineup01 = listOf(kiyohime1B, kiyohime2B, nero3RA, nero4RA, kiyohime5B) + val lineup02 = listOf(kiyohime1B, kiyohime5B, kiyohime2B, nero3RA, nero4RA) + + // Buster chain only + val lineup03 = listOf(kiyohime1B, kiyohime2B, nero3RA, nero4RA, nero5BAlt) + val lineup04 = listOf(kiyohime1B, nero5BAlt, kiyohime2B, nero3RA, nero4RA) + + // Buster chain with either Brave or non-Brave + val lineup05 = listOf(kiyohime1B, kiyohime2B, nero3RA, nero4BAlt, kiyohime5B) + val lineup06 = listOf(kiyohime1B, kiyohime5B, kiyohime2B, nero3RA, nero4BAlt) + + // Buster brave chain or Mighty chain + val lineup07 = listOf(kiyohime1B, kiyohime2B, nero3QAlt, nero4RA, kiyohime5B) + val lineup08 = listOf(kiyohime1B, kiyohime5B, kiyohime2B, nero3QAlt, nero4RA) + + // Buster chain or Mighty brave chain + val lineup09 = listOf(kiyohime1B, kiyohime2B, nero3QAlt, nero4RA, nero5BAlt) + val lineup10 = listOf(kiyohime1B, nero5BAlt, kiyohime2B, nero3QAlt, nero4RA) + } + + data object SingleServantOnly { val scathach1WB = ParsedCard( card = CommandCard.Face.A, servant = TeamSlot.B, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt index 7e4940da2..b2bcc9c8d 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt @@ -1,17 +1,13 @@ package io.github.fate_grand_automata.scripts import assertk.assertThat -import assertk.assertions.contains import assertk.assertions.containsExactly import assertk.assertions.isEqualTo import io.github.fate_grand_automata.scripts.enums.BraveChainEnum -import io.github.fate_grand_automata.scripts.enums.CardAffinityEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage -import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains import kotlin.collections.mapOf import kotlin.test.Test @@ -19,10 +15,10 @@ import kotlin.test.Test class MightyChainTest { @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -33,10 +29,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -48,10 +44,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup2 + val cards = AttackLineUps.Standard.lineup2 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -62,10 +58,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup2 (1SB,5SQ,2SQ,3NA,4NA), with rearrange`() { + fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA), with rearrange`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup2 + val cards = AttackLineUps.Standard.lineup2 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -77,10 +73,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -97,10 +93,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -119,10 +115,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -137,10 +133,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange & npTypes`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange & npTypes`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -156,10 +152,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -176,10 +172,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with rearrange`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with rearrange`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -192,10 +188,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -210,10 +206,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -226,10 +222,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, rearranged=true`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, rearranged=true`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -242,10 +238,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -260,10 +256,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearrange & npTypes`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearrange & npTypes`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -279,10 +275,10 @@ class MightyChainTest { } @Test - fun `ScenarioFullCards - lineup1 (BQABQ)`() { + fun `SingleServantOnly - lineup1 (BQABQ)`() { val braveChains = ApplyBraveChains() - val cards = AttackLineUps.ScenarioFullCards.lineup1 + val cards = AttackLineUps.SingleServantOnly.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -294,10 +290,10 @@ class MightyChainTest { } @Test - fun `ScenarioFullCards - lineup1 (BQABQ), rearranged=true`() { + fun `SingleServantOnly - lineup1 (BQABQ), rearranged=true`() { val braveChains = ApplyBraveChains() - val cards = AttackLineUps.ScenarioFullCards.lineup1 + val cards = AttackLineUps.SingleServantOnly.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -309,10 +305,10 @@ class MightyChainTest { } @Test - fun `ScenarioFullCards - lineup2 (BBQQA)`() { + fun `SingleServantOnly - lineup2 (BBQQA)`() { val braveChains = ApplyBraveChains() - val cards = AttackLineUps.ScenarioFullCards.lineup2 + val cards = AttackLineUps.SingleServantOnly.lineup2 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -323,10 +319,10 @@ class MightyChainTest { } @Test - fun `ScenarioFullCards - lineup2 (BBQQA), rearranged=true`() { + fun `SingleServantOnly - lineup2 (BBQQA), rearranged=true`() { val braveChains = ApplyBraveChains() - val cards = AttackLineUps.ScenarioFullCards.lineup2 + val cards = AttackLineUps.SingleServantOnly.lineup2 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -338,10 +334,10 @@ class MightyChainTest { } @Test - fun `ScenarioFullCards - lineup3 (QQABB)`() { + fun `SingleServantOnly - lineup3 (QQABB)`() { val braveChains = ApplyBraveChains() - val cards = AttackLineUps.ScenarioFullCards.lineup3 + val cards = AttackLineUps.SingleServantOnly.lineup3 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -352,10 +348,10 @@ class MightyChainTest { } @Test - fun `ScenarioFullCards - lineup3 (QQABB), rearranged=true`() { + fun `SingleServantOnly - lineup3 (QQABB), rearranged=true`() { val braveChains = ApplyBraveChains() - val cards = AttackLineUps.ScenarioFullCards.lineup3 + val cards = AttackLineUps.SingleServantOnly.lineup3 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -368,10 +364,10 @@ class MightyChainTest { // Scenario for when 2 card types are found but not the 3rd @Test - fun `ScenarioFullCards - lineup4 (BQQBQ) - No mighty chain`() { + fun `SingleServantOnly - lineup4 (BQQBQ) - No mighty chain`() { val braveChains = ApplyBraveChains() - val cards = AttackLineUps.ScenarioFullCards.lineup4 + val cards = AttackLineUps.SingleServantOnly.lineup4 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -384,10 +380,10 @@ class MightyChainTest { // Scenario for when 2 card types are found but not the 3rd @Test - fun `ScenarioFullCards - lineup5 (QQQBB) - No mighty chain`() { + fun `SingleServantOnly - lineup5 (QQQBB) - No mighty chain`() { val braveChains = ApplyBraveChains() - val cards = AttackLineUps.ScenarioFullCards.lineup5 + val cards = AttackLineUps.SingleServantOnly.lineup5 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty @@ -402,10 +398,10 @@ class MightyChainTest { * Two NP scenario */ @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero)`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero)`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -422,10 +418,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -441,10 +437,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach)`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach)`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -461,10 +457,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, @@ -485,10 +481,10 @@ class MightyChainTest { } @Test - fun `Mixed cards - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 + val cards = AttackLineUps.Standard.lineup1 val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, From b69203e1553f1a1dc05c50241f271af19b10199d Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 01:04:45 +0800 Subject: [PATCH 020/124] Refactor test file location --- .../fate_grand_automata/scripts/{ => attack}/AttackLineUps.kt | 2 +- .../scripts/{ => attack}/MightyChainTest.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename scripts/src/test/java/io/github/fate_grand_automata/scripts/{ => attack}/AttackLineUps.kt (99%) rename scripts/src/test/java/io/github/fate_grand_automata/scripts/{ => attack}/MightyChainTest.kt (99%) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt similarity index 99% rename from scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt rename to scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index 5bc576d7c..08f616778 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -1,4 +1,4 @@ -package io.github.fate_grand_automata.scripts +package io.github.fate_grand_automata.scripts.attack import io.github.fate_grand_automata.scripts.enums.CardAffinityEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt similarity index 99% rename from scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt rename to scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index b2bcc9c8d..b59ae25b3 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -1,15 +1,15 @@ -package io.github.fate_grand_automata.scripts +package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEqualTo +import io.github.fate_grand_automata.scripts.attack.AttackLineUps import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains -import kotlin.collections.mapOf import kotlin.test.Test class MightyChainTest { From e5eaea6b6ff28ac1662054e72c23ab206c54a8f0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 03:14:33 +0800 Subject: [PATCH 021/124] Add chain priority toggle --- .../di/script/PreferencesModule.kt | 6 +++ .../io/github/fate_grand_automata/ui/Theme.kt | 4 ++ .../ui/card_priority/CardPriorityListItem.kt | 2 + .../ui/card_priority/CardPriorityScreen.kt | 3 +- .../ui/card_priority/CardPriorityView.kt | 13 +++++- .../ui/card_priority/CardPriorityViewModel.kt | 10 +++++ .../card_priority/CardPriorityWaveSelector.kt | 1 + .../ui/card_priority/Render.kt | 12 ++++-- app/src/main/res/values-b+zh+CN/localized.xml | 1 + app/src/main/res/values-b+zh+TW/localized.xml | 1 + app/src/main/res/values-ja/localized.xml | 1 + app/src/main/res/values-ko/localized.xml | 1 + app/src/main/res/values-vi/localized.xml | 1 + app/src/main/res/values/localized.xml | 1 + .../fate_grand_automata/prefs/BattleConfig.kt | 3 ++ .../prefs/core/BattleConfigCore.kt | 14 +++++++ .../scripts/enums/ChainTypeEnum.kt | 10 +++++ .../models/battle/ChainPriorityPerWave.kt | 40 +++++++++++++++++++ .../scripts/prefs/IBattleConfig.kt | 3 ++ 19 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt diff --git a/app/src/main/java/io/github/fate_grand_automata/di/script/PreferencesModule.kt b/app/src/main/java/io/github/fate_grand_automata/di/script/PreferencesModule.kt index cef7ac31f..ae6e981d1 100644 --- a/app/src/main/java/io/github/fate_grand_automata/di/script/PreferencesModule.kt +++ b/app/src/main/java/io/github/fate_grand_automata/di/script/PreferencesModule.kt @@ -7,6 +7,7 @@ import io.github.fate_grand_automata.scripts.models.AutoSkillCommand import io.github.fate_grand_automata.scripts.models.CardPriorityPerWave import io.github.fate_grand_automata.scripts.models.ServantPriorityPerWave import io.github.fate_grand_automata.scripts.models.SpamConfigPerTeamSlot +import io.github.fate_grand_automata.scripts.models.battle.ChainPriorityPerWave import io.github.fate_grand_automata.scripts.prefs.IBattleConfig import io.github.fate_grand_automata.scripts.prefs.IPreferences import io.github.fate_grand_automata.scripts.prefs.ISupportPreferences @@ -50,4 +51,9 @@ class PreferencesModule { @Provides fun provideServantPriority(battleConfig: IBattleConfig): ServantPriorityPerWave? = if (battleConfig.useServantPriority) battleConfig.servantPriority else null + + @ScriptScope + @Provides + fun provideChainPriority(battleConfig: IBattleConfig): ChainPriorityPerWave? = + if (battleConfig.useChainPriority) battleConfig.chainPriority else null } \ No newline at end of file diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/Theme.kt b/app/src/main/java/io/github/fate_grand_automata/ui/Theme.kt index 67f307a38..af27aa2ab 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/Theme.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/Theme.kt @@ -159,6 +159,10 @@ private val DarkColors = darkColorScheme( fun FGAListItemColors() = ListItemDefaults.colors( containerColor = MaterialTheme.colorScheme.surfaceVariant ) +@Composable +fun FGAListItemColorsDisabled() = ListItemDefaults.colors( + containerColor = Color.Gray +) @Composable fun FGATheme( diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityListItem.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityListItem.kt index 137e3cd41..b0c142258 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityListItem.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityListItem.kt @@ -2,12 +2,14 @@ package io.github.fate_grand_automata.ui.card_priority import androidx.compose.runtime.MutableState import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.CardScore import io.github.fate_grand_automata.scripts.models.TeamSlot data class CardPriorityListItem( val scores: MutableList, val servantPriority: MutableList, + val chainPriority: MutableList, var rearrangeCards: MutableState, var braveChains: MutableState ) \ No newline at end of file diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityScreen.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityScreen.kt index 44eb608cb..7c1c67337 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityScreen.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityScreen.kt @@ -10,7 +10,8 @@ fun CardPriorityScreen( ) { CardPriorityView( items = vm.cardPriorityItems, - useServantPriority = vm.useServantPriority + useServantPriority = vm.useServantPriority, + useChainPriority = vm.useChainPriority ) DisposableEffect(vm) { diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityView.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityView.kt index a013599c9..e7db033b2 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityView.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityView.kt @@ -31,7 +31,8 @@ import kotlinx.coroutines.launch @Composable fun CardPriorityView( items: SnapshotStateList, - useServantPriority: Pref + useServantPriority: Pref, + useChainPriority: Pref ) { val pagerState = rememberPagerState(pageCount = {items.size},) val scope = rememberCoroutineScope() @@ -50,6 +51,13 @@ fun CardPriorityView( val servantPriority by useServantPriority.remember() useServantPriority.SwitchPreference( title = stringResource(R.string.p_battle_config_use_servant_priority), + modifier = Modifier + .padding(bottom = 4.dp) + ) + + val chainPriority by useChainPriority.remember() + useChainPriority.SwitchPreference( + title = stringResource(R.string.p_battle_config_use_chain_priority), modifier = Modifier .padding(bottom = 16.dp) ) @@ -81,7 +89,8 @@ fun CardPriorityView( } items.getOrNull(it)?.Render( - useServantPriority = servantPriority + useServantPriority = servantPriority, + useChainPriority = chainPriority ) } } diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityViewModel.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityViewModel.kt index fcc987278..8bd3c1d6d 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityViewModel.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityViewModel.kt @@ -11,6 +11,7 @@ import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.models.CardPriority import io.github.fate_grand_automata.scripts.models.CardPriorityPerWave import io.github.fate_grand_automata.scripts.models.ServantPriorityPerWave +import io.github.fate_grand_automata.scripts.models.battle.ChainPriorityPerWave import javax.inject.Inject @HiltViewModel @@ -21,6 +22,7 @@ class CardPriorityViewModel @Inject constructor( val cardPriorityItems: SnapshotStateList by lazy { val cardPriority = battleConfig.cardPriority.get() val servantPriority = battleConfig.servantPriority.get() + val chainPriority = battleConfig.chainPriority.get() val rearrangeCards = battleConfig.rearrangeCards.get() val braveChains = battleConfig.braveChains.get() @@ -33,6 +35,7 @@ class CardPriorityViewModel @Inject constructor( CardPriorityListItem( it.value, servantPriority.atWave(it.index).toMutableList(), + chainPriority.atWave(it.index).toMutableList(), mutableStateOf(rearrangeCards.getOrElse(it.index) { false }), mutableStateOf(braveChains.getOrElse(it.index) { BraveChainEnum.None }) ) @@ -41,6 +44,7 @@ class CardPriorityViewModel @Inject constructor( } val useServantPriority = battleConfig.useServantPriority + val useChainPriority = battleConfig.useChainPriority fun save() { battleConfig.cardPriority.set( @@ -55,6 +59,12 @@ class CardPriorityViewModel @Inject constructor( ) ) + battleConfig.chainPriority.set( + ChainPriorityPerWave.from( + cardPriorityItems.map { it.chainPriority } + ) + ) + battleConfig.rearrangeCards.set(cardPriorityItems.map { it.rearrangeCards.value }) battleConfig.braveChains.set(cardPriorityItems.map { it.braveChains.value }) } diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityWaveSelector.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityWaveSelector.kt index afad96631..e0320f4c9 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityWaveSelector.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/CardPriorityWaveSelector.kt @@ -108,6 +108,7 @@ fun CardPriorityWaveSelector( CardPriorityListItem( items[0].scores.toMutableList(), items[0].servantPriority.toMutableList(), + items[0].chainPriority.toMutableList(), mutableStateOf(false), mutableStateOf(BraveChainEnum.None) ) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index 99e596029..07c515f47 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -23,6 +23,7 @@ import io.github.fate_grand_automata.R import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.ui.FGAListItemColors +import io.github.fate_grand_automata.ui.FGAListItemColorsDisabled import io.github.fate_grand_automata.ui.drag_sort.DragSort import io.github.fate_grand_automata.ui.drag_sort.DragSortAdapter import io.github.fate_grand_automata.ui.prefs.listDialog @@ -30,7 +31,8 @@ import io.github.fate_grand_automata.util.stringRes @Composable fun CardPriorityListItem.Render( - useServantPriority: Boolean + useServantPriority: Boolean, + useChainPriority: Boolean ) { Column( horizontalAlignment = Alignment.CenterHorizontally @@ -58,13 +60,17 @@ fun CardPriorityListItem.Render( title = stringResource(R.string.p_brave_chains) ) + val braveChainListItemColor = if (useChainPriority) FGAListItemColorsDisabled() else FGAListItemColors() ListItem( modifier = Modifier .weight(1f) - .clickable { braveChainDialog.show() }, + .clickable { + if (!useChainPriority) + braveChainDialog.show() + }, headlineContent = { Text(stringResource(R.string.p_brave_chains)) }, supportingContent = { Text(stringResource(braveChains.stringRes)) }, - colors = FGAListItemColors() + colors = braveChainListItemColor ) var rearrange by rearrangeCards diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index ad732eb1e..cf4c6b879 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -401,6 +401,7 @@ Order Change" "金种火最大领取数(枚数)" "下载成功。5秒后将重启APP。" "使用从者优先级" + "使用追击连携先级" "全部重置" "重置为默认值" "选择语言" diff --git a/app/src/main/res/values-b+zh+TW/localized.xml b/app/src/main/res/values-b+zh+TW/localized.xml index 0273860b8..7db088c10 100644 --- a/app/src/main/res/values-b+zh+TW/localized.xml +++ b/app/src/main/res/values-b+zh+TW/localized.xml @@ -397,4 +397,5 @@ Order Change" "金種火最大領取數(總枚數)" "更新已下載完成,於 5 秒鐘後重啟" "偏好從者" + "偏好Chain" diff --git a/app/src/main/res/values-ja/localized.xml b/app/src/main/res/values-ja/localized.xml index 48ee50d51..1205d500d 100644 --- a/app/src/main/res/values-ja/localized.xml +++ b/app/src/main/res/values-ja/localized.xml @@ -413,5 +413,6 @@ "10個消費する" "サーヴァントの優先順位を設定する" + "チェインの優先順位を設定する" \ No newline at end of file diff --git a/app/src/main/res/values-ko/localized.xml b/app/src/main/res/values-ko/localized.xml index bc95a062b..e8cfd02ea 100644 --- a/app/src/main/res/values-ko/localized.xml +++ b/app/src/main/res/values-ko/localized.xml @@ -394,6 +394,7 @@ "종화 획득 총량" "다운로드가 완료되었습니다. 5초 내로 재시작됩니다." "서번트 우선순위 사용" + "체인 우선순위 사용" "모두 초기화" "기본값으로 초기화" "사용할 언어를 선택하세요" diff --git a/app/src/main/res/values-vi/localized.xml b/app/src/main/res/values-vi/localized.xml index fd65fba45..af8d98c3d 100644 --- a/app/src/main/res/values-vi/localized.xml +++ b/app/src/main/res/values-vi/localized.xml @@ -395,4 +395,5 @@ Sau khi nhấn nút, hãy chuyển bộ lọc ứng dụng từ \"Không đượ "Tổng số lượng than hồng vàng tối đa" "Tải xuống thành công. Khởi động lại ứng dụng sau 5 giây." "Sử dụng ưu tiên Servant" + "Sử dụng ưu tiên Chain" \ No newline at end of file diff --git a/app/src/main/res/values/localized.xml b/app/src/main/res/values/localized.xml index 66755e690..1e4419666 100644 --- a/app/src/main/res/values/localized.xml +++ b/app/src/main/res/values/localized.xml @@ -96,6 +96,7 @@ Skills" "Server" "Card Priority" "Use Servant Priority" + "Use Chain Priority" "Support" "Selection Mode" "Fallback" diff --git a/prefs/src/main/java/io/github/fate_grand_automata/prefs/BattleConfig.kt b/prefs/src/main/java/io/github/fate_grand_automata/prefs/BattleConfig.kt index ab8195c14..602c0d86a 100644 --- a/prefs/src/main/java/io/github/fate_grand_automata/prefs/BattleConfig.kt +++ b/prefs/src/main/java/io/github/fate_grand_automata/prefs/BattleConfig.kt @@ -21,6 +21,9 @@ internal class BattleConfig( override val servantPriority by prefs.servantPriority override val useServantPriority by prefs.useServantPriority + override val chainPriority by prefs.chainPriority + override val useChainPriority by prefs.useChainPriority + override val party by prefs.party override val materials by prefs.materials diff --git a/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/BattleConfigCore.kt b/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/BattleConfigCore.kt index 77e1cc960..c63569981 100644 --- a/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/BattleConfigCore.kt +++ b/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/BattleConfigCore.kt @@ -14,6 +14,7 @@ import io.github.fate_grand_automata.scripts.enums.ShuffleCardsEnum import io.github.fate_grand_automata.scripts.models.CardPriorityPerWave import io.github.fate_grand_automata.scripts.models.ServantPriorityPerWave import io.github.fate_grand_automata.scripts.models.ServantSpamConfig +import io.github.fate_grand_automata.scripts.models.battle.ChainPriorityPerWave class BattleConfigCore( val id: String, @@ -111,6 +112,19 @@ class BattleConfigCore( default = ServantPriorityPerWave.default ) + val useChainPriority = maker.bool("use_chain_priority") + val chainPriority = maker.serialized( + "chain_priority", + serializer = object : Serializer { + override fun deserialize(serialized: String): ChainPriorityPerWave = + ChainPriorityPerWave.of(serialized) + + override fun serialize(value: ChainPriorityPerWave) = + value.toString() + }, + default = ChainPriorityPerWave.default + ) + private val gson = Gson() private val defaultSpamConfig = (1..6).map { ServantSpamConfig() } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt new file mode 100644 index 000000000..a6a21cfef --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt @@ -0,0 +1,10 @@ +package io.github.fate_grand_automata.scripts.enums + +enum class ChainTypeEnum { + Brave, + Mighty, + Quick, + Arts, + Buster, + Avoid, +} \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt new file mode 100644 index 000000000..91bfd51a8 --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt @@ -0,0 +1,40 @@ +package io.github.fate_grand_automata.scripts.models.battle + +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum +import io.github.fate_grand_automata.scripts.models.TeamSlot + +class ChainPriorityPerWave private constructor( + private val chainsPerWave: List> +) : List> by chainsPerWave { + fun atWave(wave: Int) = + chainsPerWave[wave.coerceIn(chainsPerWave.indices)] + + override fun toString() = + chainsPerWave.joinToString(STAGE_SEPARATOR) + + companion object { + val default = from(listOf(ChainTypeEnum.entries)) + private const val STAGE_SEPARATOR = "\n" + private const val SEPARATOR = "," + + fun from(chainsPerWave: List>) = + ChainPriorityPerWave(chainsPerWave) + + fun of(priority: String): ChainPriorityPerWave = + if (priority.isBlank()) { + default + } else { + ChainPriorityPerWave( + priority + .split(STAGE_SEPARATOR) + .map { priorityOnAWave -> + priorityOnAWave + .split(SEPARATOR) + .map { + ChainTypeEnum.valueOf(it) + } + } + ) + } + } +} \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/IBattleConfig.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/IBattleConfig.kt index 8d1a88d15..779a0f556 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/IBattleConfig.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/IBattleConfig.kt @@ -7,6 +7,7 @@ import io.github.fate_grand_automata.scripts.enums.ShuffleCardsEnum import io.github.fate_grand_automata.scripts.models.CardPriorityPerWave import io.github.fate_grand_automata.scripts.models.ServantPriorityPerWave import io.github.fate_grand_automata.scripts.models.ServantSpamConfig +import io.github.fate_grand_automata.scripts.models.battle.ChainPriorityPerWave interface IBattleConfig { val id: String @@ -15,6 +16,8 @@ interface IBattleConfig { var cardPriority: CardPriorityPerWave val useServantPriority: Boolean val servantPriority: ServantPriorityPerWave + val useChainPriority: Boolean + val chainPriority: ChainPriorityPerWave val rearrangeCards: List val braveChains: List val party: Int From 0f7587633ac700ff2a083ee6f3b8591ec8703efe Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 04:38:19 +0800 Subject: [PATCH 022/124] Add rendering for Chain Priority --- .../ui/card_priority/Render.kt | 40 ++++++++++++++++++- app/src/main/res/values-b+zh+CN/localized.xml | 2 + app/src/main/res/values-b+zh+TW/localized.xml | 2 + app/src/main/res/values-ja/localized.xml | 2 + app/src/main/res/values-ko/localized.xml | 2 + app/src/main/res/values-vi/localized.xml | 2 + app/src/main/res/values/localized.xml | 2 + .../scripts/enums/ChainTypeEnum.kt | 4 +- .../models/battle/ChainPriorityPerWave.kt | 6 ++- 9 files changed, 57 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index 07c515f47..bb6ca76d1 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.github.fate_grand_automata.R import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.ui.FGAListItemColors import io.github.fate_grand_automata.ui.FGAListItemColorsDisabled @@ -91,6 +92,12 @@ fun CardPriorityListItem.Render( } } + if (useChainPriority) { + ChainPriority( + priorities = chainPriority + ) + } + if (useServantPriority) { ServantPriority( priorities = servantPriority @@ -104,7 +111,7 @@ private fun ServantPriority( priorities: MutableList ) { Text( - "Servant Priority".uppercase(), + stringResource(R.string.p_servant_priority).uppercase(), modifier = Modifier .padding(bottom = 5.dp, top = 16.dp) ) @@ -128,4 +135,35 @@ private fun ServantPriority( ) } ) +} + +@Composable +private fun ChainPriority( + priorities: MutableList +) { + Text( + stringResource(R.string.p_chain_priority).uppercase(), + modifier = Modifier + .padding(bottom = 10.dp, top = 10.dp) + ) + + val context = LocalContext.current + + DragSort( + items = priorities, + viewConfigGrabber = { + DragSortAdapter.ItemViewConfig( + foregroundColor = Color.WHITE, + backgroundColor = when (it) { + ChainTypeEnum.Arts -> R.color.colorArts + ChainTypeEnum.Quick -> R.color.colorQuick + ChainTypeEnum.Buster -> R.color.colorBuster + ChainTypeEnum.Brave -> R.color.colorQuickResist + ChainTypeEnum.Mighty -> R.color.colorArtsResist + ChainTypeEnum.Avoid -> R.color.colorBusterResist + }.let { res -> context.getColor(res) }, + text = it.name + ) + } + ) } \ No newline at end of file diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index cf4c6b879..11425bfef 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -125,6 +125,8 @@ Order Change" "Save unmodified screenshots" "Save unmodified full-screen screenshots instead of cropped and rescaled screenshots" "加速道具" + "从者优先级" + "追击连携先级" "追击连携" "调换顺序 (优先级高的卡后出)" diff --git a/app/src/main/res/values-b+zh+TW/localized.xml b/app/src/main/res/values-b+zh+TW/localized.xml index 7db088c10..f5738482f 100644 --- a/app/src/main/res/values-b+zh+TW/localized.xml +++ b/app/src/main/res/values-b+zh+TW/localized.xml @@ -125,6 +125,8 @@ Order Change" "儲存原始截圖" "儲存截圖時,保留原始截圖大小而不進行影像縮放" "加速道具" + "偏好從者" + "偏好Chain" "Brave Chain" "調換順序 (優先性高的卡後出)" diff --git a/app/src/main/res/values-ja/localized.xml b/app/src/main/res/values-ja/localized.xml index 1205d500d..575a16e97 100644 --- a/app/src/main/res/values-ja/localized.xml +++ b/app/src/main/res/values-ja/localized.xml @@ -128,6 +128,8 @@ "未編集のスクリーンショットを保存" "トリミングや拡縮されたスクリーンショットの代わりに、未編集でフルサイズのスクリーンショットを保存します" "ブーストアイテム" + "サーヴァントの優先順位" + "チェインの優先順位" "Brave Chains" "優先順位を逆転する" "サーヴァント画像チェックをスキップ" diff --git a/app/src/main/res/values-ko/localized.xml b/app/src/main/res/values-ko/localized.xml index e8cfd02ea..8eb4abb7d 100644 --- a/app/src/main/res/values-ko/localized.xml +++ b/app/src/main/res/values-ko/localized.xml @@ -125,6 +125,8 @@ "스크린샷을 수정하지 않고 저장" "스크린샷을 저장할때 배율조정과 잘라내기가 적용되지 않은 전체화면 스크린샷을 사용" "부스트 아이템" + "서번트 우선순위" + "체인 우선순위" "브레이브 체인" "카드 재배열" "서번트 카드 아이콘 확인 생략" diff --git a/app/src/main/res/values-vi/localized.xml b/app/src/main/res/values-vi/localized.xml index af8d98c3d..f9b13dc4f 100644 --- a/app/src/main/res/values-vi/localized.xml +++ b/app/src/main/res/values-vi/localized.xml @@ -124,6 +124,8 @@ Master" "Lưu ảnh chụp màn hình chưa sửa đổi" "Lưu ảnh chụp màn hình toàn màn hình chưa sửa đổi thay vì ảnh chụp màn hình bị cắt và thay đổi kích thước" "Vật phẩm tăng cường" + "Ưu tiên Servant" + "Ưu tiên Chain" "Brave Chains" "Sắp xếp lại thẻ" "Bỏ qua việc kiểm tra thẻ Servant" diff --git a/app/src/main/res/values/localized.xml b/app/src/main/res/values/localized.xml index 1e4419666..f90cac056 100644 --- a/app/src/main/res/values/localized.xml +++ b/app/src/main/res/values/localized.xml @@ -129,6 +129,8 @@ Skills" "Save unmodified screenshots" "Save unmodified full-screen screenshots instead of cropped and rescaled screenshots" "Boost Item" + "Servant Priority" + "Chain Priority" "Brave Chains" "Rearrange Cards" "Skip servant face-card checks" diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt index a6a21cfef..77d4067df 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt @@ -3,8 +3,8 @@ package io.github.fate_grand_automata.scripts.enums enum class ChainTypeEnum { Brave, Mighty, - Quick, - Arts, Buster, + Arts, + Quick, Avoid, } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt index 91bfd51a8..88f697d0c 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt @@ -1,7 +1,6 @@ package io.github.fate_grand_automata.scripts.models.battle import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum -import io.github.fate_grand_automata.scripts.models.TeamSlot class ChainPriorityPerWave private constructor( private val chainsPerWave: List> @@ -10,7 +9,10 @@ class ChainPriorityPerWave private constructor( chainsPerWave[wave.coerceIn(chainsPerWave.indices)] override fun toString() = - chainsPerWave.joinToString(STAGE_SEPARATOR) + chainsPerWave + .joinToString(STAGE_SEPARATOR) { + it.joinToString(SEPARATOR) { c -> c.name } + } companion object { val default = from(listOf(ChainTypeEnum.entries)) From 29e0f2808cbf60fba59792b34e26bf2f89684b6e Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 04:48:29 +0800 Subject: [PATCH 023/124] Change Brave Chain text to reflect when it is being used as Chain Priority --- .../fate_grand_automata/ui/card_priority/Render.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index bb6ca76d1..6b29ca586 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -61,7 +61,16 @@ fun CardPriorityListItem.Render( title = stringResource(R.string.p_brave_chains) ) - val braveChainListItemColor = if (useChainPriority) FGAListItemColorsDisabled() else FGAListItemColors() + val braveChainListItemColor = + if (useChainPriority) + FGAListItemColorsDisabled() + else + FGAListItemColors() + val supportingHeader = + if (useChainPriority) + R.string.p_chain_priority + else + braveChains.stringRes ListItem( modifier = Modifier .weight(1f) @@ -70,7 +79,7 @@ fun CardPriorityListItem.Render( braveChainDialog.show() }, headlineContent = { Text(stringResource(R.string.p_brave_chains)) }, - supportingContent = { Text(stringResource(braveChains.stringRes)) }, + supportingContent = { Text(stringResource(supportingHeader)) }, colors = braveChainListItemColor ) From 5581ad6084a3ded22c8a5eb62e6e5bf18778c1bc Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 05:16:55 +0800 Subject: [PATCH 024/124] Add ChainPrioritySelector --- .../scripts/modules/Card.kt | 16 ++++ .../modules/attack/ChainPrioritySelector.kt | 77 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ChainPrioritySelector.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index 908f11416..b514f7f94 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -4,6 +4,7 @@ import io.github.fate_grand_automata.scripts.IFgoAutomataApi import io.github.fate_grand_automata.scripts.ScriptLog import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage @@ -11,7 +12,9 @@ import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.SpamConfigPerTeamSlot import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.scripts.models.battle.BattleState +import io.github.fate_grand_automata.scripts.models.battle.ChainPriorityPerWave import io.github.fate_grand_automata.scripts.models.toFieldSlot +import io.github.fate_grand_automata.scripts.modules.attack.ChainPrioritySelector import io.github.fate_grand_automata.scripts.prefs.IBattleConfig import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @@ -26,6 +29,7 @@ class Card @Inject constructor( private val parser: CardParser, private val priority: FaceCardPriority, private val braveChains: ApplyBraveChains, + private val chainPrioritySelector: ChainPrioritySelector, private val battleConfig: IBattleConfig ) : IFgoAutomataApi by api { @@ -61,6 +65,18 @@ class Card @Inject constructor( val rearrangeCardsPerWave = battleConfig.rearrangeCards val npTypes = npUsage.nps.associate { it.toFieldSlot() to it.type() } + val useChainPriority = battleConfig.useChainPriority + if (useChainPriority) { + val chainPriority = battleConfig.chainPriority.atWave(state.stage) + chainPrioritySelector.pick( + cards = cardsOrderedByPriority, + npUsage = npUsage, + chainPriority = chainPriority, + rearrange = rearrangeCardsPerWave.inCurrentWave(false), + npTypes = npTypes + ).map { it.card } + } + return braveChains.pick( cards = cardsOrderedByPriority, npUsage = npUsage, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ChainPrioritySelector.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ChainPrioritySelector.kt new file mode 100644 index 000000000..552422a02 --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ChainPrioritySelector.kt @@ -0,0 +1,77 @@ +package io.github.fate_grand_automata.scripts.modules.attack + +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains +import io.github.lib_automata.dagger.ScriptScope +import java.util.Collections +import javax.inject.Inject + +@ScriptScope +class ChainPrioritySelector @Inject constructor( + private val applyMightyChains: ApplyMightyChains, +) { + private fun rearrange( + cards: List, + rearrange: Boolean, + npUsage: NPUsage + ): List { + /* + When rearrange is active and there is 1 NP and 1 Card before NP, + we want the best or matching face-card after NP. + */ + val shouldSwapForNpUsageScenario = listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } + if (shouldSwapForNpUsageScenario) { + return cards.toMutableList().also { + Collections.swap(it, 0, 1) + } + } + + if (rearrange + // If there are cards before NP, at max there's only 1 card after NP + && npUsage.cardsBeforeNP == 0 + // If there are more than 1 NPs, only 1 card after NPs at max + && npUsage.nps.size <= 1 + ) { + val cardsToRearrange = List(cards.size) { index -> index } + .take((3 - npUsage.nps.size).coerceAtLeast(0)) + .reversed() + + // When clicking 3 cards, move the card with 2nd highest priority to last position to amplify its effect + // Do the same when clicking 2 cards unless they're used before NPs. + if (cardsToRearrange.size in 2..3) { + return cards.toMutableList().also { + Collections.swap(it, cardsToRearrange[1], cardsToRearrange[0]) + } + } + } + + return cards + } + + fun pick( + cards: List, + chainPriority: List, + rearrange: Boolean = false, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap() + ): List { + var newCardOrder: List? = null + for (chain in chainPriority) { + newCardOrder = when (chain) { + ChainTypeEnum.Mighty -> applyMightyChains.getMightyChain(cards, npUsage, npTypes) + else -> null + } + if (newCardOrder != null) break; + } + + return rearrange( + cards = newCardOrder ?: cards, + rearrange = rearrange, + npUsage = npUsage + ) + } +} \ No newline at end of file From d507a5211038077aaa2a161f01ab4d4bc16add6c Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 30 Aug 2025 07:12:03 +0800 Subject: [PATCH 025/124] Add chainPriority to functions --- .../scripts/modules/attack/ApplyMightyChains.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt index 5771783c2..052bcd9fb 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt @@ -1,6 +1,7 @@ package io.github.fate_grand_automata.scripts.modules.attack import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard @@ -16,7 +17,8 @@ class ApplyMightyChains @Inject constructor() { fun getMightyChain ( cards: List, npUsage: NPUsage, - npTypes: Map + npTypes: Map, + chainPriority: List? = null ): List? { val uniqueCardTypesFromNp = npTypes.values.toSet() if (!isMightyChainAllowed(npUsage, uniqueCardTypesFromNp, npTypes)) return null @@ -28,7 +30,8 @@ class ApplyMightyChains @Inject constructor() { return tryToGetMightyChain( cards, uniqueCardTypesFromNp, - firstFieldSlot + firstFieldSlot, + chainPriority ) } @@ -39,7 +42,8 @@ class ApplyMightyChains @Inject constructor() { // e.g. usually based on npSize uniqueCardTypesAlreadyFilled: Set, // In case of a single NP, we want to know what slot it is - singleNpFieldSlot: FieldSlot? + singleNpFieldSlot: FieldSlot? = null, + chainPriority: List? = null ): List? { val cardsToFind = totalUniqueCardTypesPermitted val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() From 357e3f0aa99f800d7b12dcacbd6818b77dc52acd Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 31 Aug 2025 01:11:23 +0800 Subject: [PATCH 026/124] Go back to using Brave Chain to determine if Brave Chains should be used instead --- .../ui/card_priority/Render.kt | 21 ++++--------------- .../scripts/enums/ChainTypeEnum.kt | 2 -- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index 6b29ca586..627e6cb5c 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -61,26 +61,15 @@ fun CardPriorityListItem.Render( title = stringResource(R.string.p_brave_chains) ) - val braveChainListItemColor = - if (useChainPriority) - FGAListItemColorsDisabled() - else - FGAListItemColors() - val supportingHeader = - if (useChainPriority) - R.string.p_chain_priority - else - braveChains.stringRes ListItem( modifier = Modifier .weight(1f) .clickable { - if (!useChainPriority) - braveChainDialog.show() + braveChainDialog.show() }, headlineContent = { Text(stringResource(R.string.p_brave_chains)) }, - supportingContent = { Text(stringResource(supportingHeader)) }, - colors = braveChainListItemColor + supportingContent = { Text(stringResource(braveChains.stringRes)) }, + colors = FGAListItemColors() ) var rearrange by rearrangeCards @@ -167,9 +156,7 @@ private fun ChainPriority( ChainTypeEnum.Arts -> R.color.colorArts ChainTypeEnum.Quick -> R.color.colorQuick ChainTypeEnum.Buster -> R.color.colorBuster - ChainTypeEnum.Brave -> R.color.colorQuickResist - ChainTypeEnum.Mighty -> R.color.colorArtsResist - ChainTypeEnum.Avoid -> R.color.colorBusterResist + ChainTypeEnum.Mighty -> R.color.colorMasterSkill }.let { res -> context.getColor(res) }, text = it.name ) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt index 77d4067df..222215df7 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt @@ -1,10 +1,8 @@ package io.github.fate_grand_automata.scripts.enums enum class ChainTypeEnum { - Brave, Mighty, Buster, Arts, Quick, - Avoid, } \ No newline at end of file From cb5b70e5c7addc4966734672e0777a677b89cc58 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 31 Aug 2025 01:29:37 +0800 Subject: [PATCH 027/124] Make changes/fixes recommended by Copilot Additionally fix bug in code dealing with single NP Mighty Brave Chain. --- .../scripts/modules/attack/ApplyMightyChains.kt | 7 +++---- .../fate_grand_automata/scripts/attack/MightyChainTest.kt | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt index 5771783c2..297698acb 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt @@ -22,7 +22,7 @@ class ApplyMightyChains @Inject constructor() { if (!isMightyChainAllowed(npUsage, uniqueCardTypesFromNp, npTypes)) return null // Get np if there is only 1 (since we want to try for Brave Chain) - val firstNp = if (npUsage.nps.size > 1) npUsage.nps.firstOrNull() else null + val firstNp = if (npUsage.nps.size == 1) npUsage.nps.first() else null val firstFieldSlot = firstNp?.toFieldSlot() return tryToGetMightyChain( @@ -45,9 +45,8 @@ class ApplyMightyChains @Inject constructor() { val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() val newList = mutableListOf() - var filteredCards = cards while (uniqueCardTypes.size < cardsToFind) { - filteredCards = filteredCards.filter { + var filteredCards = cards.filter { // Always look for a different card type it.type !in uniqueCardTypes } @@ -60,7 +59,7 @@ class ApplyMightyChains @Inject constructor() { if (fieldSlotList.isNotEmpty()) filteredCards = fieldSlotList } val filteredCard = filteredCards.firstOrNull() - if (filteredCard == null) break; // If cannot find, we leave + if (filteredCard == null) break // If cannot find, we leave uniqueCardTypes.add(filteredCard.type) newList.add(filteredCard) } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index b59ae25b3..1a1bddd8f 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -167,7 +167,7 @@ class MightyChainTest { npUsage = NPUsage(setOf(CommandCard.NP.B), 0) ).map { it.card } - // Unable to Brave Chain with Scathach. Will ignore and return result of withNp + // Unable to Mighty Chain with Scathach. Will ignore and return result of withNp assertThat(picked).isEqualTo(pickedDefault) } @@ -245,7 +245,7 @@ class MightyChainTest { val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts ) @@ -263,7 +263,7 @@ class MightyChainTest { val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts ), @@ -425,7 +425,7 @@ class MightyChainTest { val picked = braveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0), + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, FieldSlot.C to CardTypeEnum.Arts From 6342e7e38d15913c6764ec3c6460ee539b865713 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 31 Aug 2025 01:53:46 +0800 Subject: [PATCH 028/124] Apply the NP rearrange logic to the generic rearrange function instead of withNP function. --- .../scripts/modules/ApplyBraveChains.kt | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index cf584f75c..d6db4709f 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -20,9 +20,22 @@ class ApplyBraveChains @Inject constructor() { rearrange: Boolean, npUsage: NPUsage ): List { - if (rearrange + // Get this out of the way asap + if (!rearrange) return cards + + /* + If there is 1 NP and 1 Card before NP, + we want the best or matching face-card after NP immediately + */ + if (listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 }) { + return cards.toMutableList().also { + Collections.swap(it, 0, 1) + } + } + + if ( // If there are cards before NP, at max there's only 1 card after NP - && npUsage.cardsBeforeNP == 0 + npUsage.cardsBeforeNP == 0 // If there are more than 1 NPs, only 1 card after NPs at max && npUsage.nps.size <= 1 ) { @@ -64,15 +77,6 @@ class ApplyBraveChains @Inject constructor() { val nonMatchingCards = cards - matchingCards val combinedCards = matchingCards + nonMatchingCards - /* - When rearrange is active and there is 1 NP and 1 Card before NP, - we want the best or matching face-card after NP. - */ - val shouldSwapForNpUsageScenario = listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } - if (rearrange && shouldSwapForNpUsageScenario) { - Collections.swap(combinedCards, 0, 1) - } - return rearrange( cards = combinedCards, rearrange = rearrange, @@ -103,15 +107,6 @@ class ApplyBraveChains @Inject constructor() { )?.toMutableList() if (mightyChainList == null || mightyChainList.isEmpty()) return justRearranged - /* - When rearrange is active and there is 1 NP and 1 Card before NP, - we want the best or matching face-card after NP. - */ - val shouldSwapForNpUsageScenario = listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } - if (rearrange && shouldSwapForNpUsageScenario) { - Collections.swap(mightyChainList, 0, 1) - } - // Return the result return rearrange( cards = mightyChainList, From 48eeb94cf3cabfe3b9b26968d97f2316e255c924 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 31 Aug 2025 02:27:42 +0800 Subject: [PATCH 029/124] Split tests so that MightyChainTest can have its own proper unit test independent of BraveChain behaviour. --- .../modules/attack/ApplyMightyChains.kt | 4 +- .../attack/MightyChainIntegrationTest.kt | 511 ++++++++++++++++++ .../scripts/attack/MightyChainTest.kt | 401 +++----------- 3 files changed, 583 insertions(+), 333 deletions(-) create mode 100644 scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt index 297698acb..5648838de 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt @@ -15,8 +15,8 @@ class ApplyMightyChains @Inject constructor() { fun getMightyChain ( cards: List, - npUsage: NPUsage, - npTypes: Map + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap() ): List? { val uniqueCardTypesFromNp = npTypes.values.toSet() if (!isMightyChainAllowed(npUsage, uniqueCardTypesFromNp, npTypes)) return null diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt new file mode 100644 index 000000000..cf898e550 --- /dev/null +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt @@ -0,0 +1,511 @@ +package io.github.fate_grand_automata.scripts.attack + +import assertk.assertThat +import assertk.assertions.containsExactly +import assertk.assertions.isEqualTo +import io.github.fate_grand_automata.scripts.attack.AttackLineUps +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains +import kotlin.test.Test + +class MightyChainIntegrationTest { + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + // Expect 1SB,2KQ,3NA,5SQ,4NA - 15324 - AECBD + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA), with rearrange`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.D) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + ).map { it.card } + + // Unable to Brave Chain with Kama. Will ignore and return result of withNp + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + rearrange = true + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + ).map { it.card } + + // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange & npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + rearrange = true + ).map { it.card } + + // Same as above but swap position 1 and 2) // because NP + assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + ).map { it.card } + + // Unable to Mighty Chain with Scathach. Will ignore and return result of withNp + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with rearrange`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ) + ).map { it.card } + + // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0) + ).map { it.card } + + // Attempt to Brave chain with Nero NP + // Expect 3NA,4NA,1SB,2KQ,5SQ - 34125 - CDABE + assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearranged`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).containsExactly(CommandCard.Face.D, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ) + ).map { it.card } + + // Expect 1SB,2KQ,3NA,4NA,5SQ - 12345 - ABCDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearrange & npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + rearrange = true + ).map { it.card } + + // Expect 2KQ,1SB,3NA,4NA,5SQ - 21345 - BACDE + assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `SingleServantOnly - lineup1 (BQABQ)`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.SingleServantOnly.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + // Expect same result as input + // Expect BQABQ / 12345 / ABCDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `SingleServantOnly - lineup1 (BQABQ), with rearranged`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.SingleServantOnly.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `SingleServantOnly - lineup2 (BBQQA)`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.SingleServantOnly.lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + // Expect 1SB,5SQ,3NA,4NA,2KQ / 15342 / AECDB + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) + } + + @Test + fun `SingleServantOnly - lineup2 (BBQQA), with rearranged`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.SingleServantOnly.lineup2 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.E, CommandCard.Face.D, CommandCard.Face.B) + } + + @Test + fun `SingleServantOnly - lineup3 (QQABB)`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.SingleServantOnly.lineup3 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + // Expect QABQB / 53124 / ECABD + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) + } + + @Test + fun `SingleServantOnly - lineup3 (QQABB), with rearranged`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.SingleServantOnly.lineup3 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) + } + + // Scenario for when 2 card types are found but not the 3rd + @Test + fun `SingleServantOnly - lineup4 (BQQBQ) - No mighty chain`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.SingleServantOnly.lineup4 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).isEqualTo(pickedDefault) + } + + // Scenario for when 2 card types are found but not the 3rd + @Test + fun `SingleServantOnly - lineup5 (QQQBB) - No mighty chain`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.SingleServantOnly.lineup5 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty + ).map { it.card } + + assertThat(picked).isEqualTo(pickedDefault) + } + + /** + * Two NP scenario + */ + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero) - No mighty chain`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) + ).map { it.card } + + // Should fall back to default behaviour + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts + ) + ).map { it.card } + + // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) + ).map { it.card } + + // Should fall back to default behaviour + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) + ).map { it.card } + + // Should fall back to default behaviour + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { + val braveChains = ApplyBraveChains() + + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNPMighty, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts, + ) + ).map { it.card } + val pickedDefault = braveChains.pick( + cards = cards, + braveChains = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0) + ).map { it.card } + + // Should fall back to default behaviour + assertThat(picked).isEqualTo(pickedDefault) + } +} \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 1a1bddd8f..f2068c861 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -2,506 +2,245 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly -import assertk.assertions.isEqualTo -import io.github.fate_grand_automata.scripts.attack.AttackLineUps -import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import assertk.assertions.isNull import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage -import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains +import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains import kotlin.test.Test class MightyChainTest { + val mightyChain = ApplyMightyChains() @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } + val picked = mightyChain.getMightyChain( + cards = cards + )?.map { it.card } // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) - } - - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - rearrange = true - ).map { it.card } - - // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup2 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } + val picked = mightyChain.getMightyChain( + cards = cards + )?.map { it.card } // Expect 1SB,2KQ,3NA,5SQ,4NA - 15324 - AECBD - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) - } - - @Test - fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA), with rearrange`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.Standard.lineup2 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - rearrange = true - ).map { it.card } - - // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.D) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) - ).map { it.card } - val pickedDefault = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0) - ).map { it.card } + )?.map { it.card } // Unable to Brave Chain with Kama. Will ignore and return result of withNp - assertThat(picked).isEqualTo(pickedDefault) - } - - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - rearrange = true - ).map { it.card } - val pickedDefault = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - rearrange = true - ).map { it.card } - - // Same as above but swap position 2 and 3) - assertThat(picked).isEqualTo(pickedDefault) + assertThat(picked).isNull() } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick ) - ).map { it.card } + )?.map { it.card } // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange & npTypes`() { - val braveChains = ApplyBraveChains() - + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - rearrange = true - ).map { it.card } - - // Same as above but swap position 1 and 2) // because NP - assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) - } - - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) - ).map { it.card } - val pickedDefault = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0) - ).map { it.card } - - // Unable to Mighty Chain with Scathach. Will ignore and return result of withNp - assertThat(picked).isEqualTo(pickedDefault) - } - - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with rearrange`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - rearrange = true - ).map { it.card } + )?.map { it.card } - // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + // Unable to Mighty Chain with Scathach NP since type is unknown. + assertThat(picked).isNull() } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.B to CardTypeEnum.Quick ) - ).map { it.card } + )?.map { it.card } // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { - val braveChains = ApplyBraveChains() - + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) - ).map { it.card } + )?.map { it.card } - // Attempt to Brave chain with Nero NP - // Expect 3NA,4NA,1SB,2KQ,5SQ - 34125 - CDABE - assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) - } - - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, rearranged=true`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - rearrange = true - ).map { it.card } - - // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.D, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) + // Unable to Mighty Chain with Nero NP since type is unknown. + assertThat(picked).isNull() } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts ) - ).map { it.card } + )?.map { it.card } // Expect 1SB,2KQ,3NA,4NA,5SQ - 12345 - ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) - } - - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearrange & npTypes`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ), - rearrange = true - ).map { it.card } - - // Expect 2KQ,1SB,3NA,4NA,5SQ - 21345 - BACDE - assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test fun `SingleServantOnly - lineup1 (BQABQ)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } + )?.map { it.card } // Expect same result as input // Expect BQABQ / 12345 / ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) - } - - @Test - fun `SingleServantOnly - lineup1 (BQABQ), rearranged=true`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - rearrange = true - ).map { it.card } - - // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test fun `SingleServantOnly - lineup2 (BBQQA)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } + val picked = mightyChain.getMightyChain( + cards = cards + )?.map { it.card } // Expect 1SB,5SQ,3NA,4NA,2KQ / 15342 / AECDB - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) - } - - @Test - fun `SingleServantOnly - lineup2 (BBQQA), rearranged=true`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - rearrange = true - ).map { it.card } - - // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.E, CommandCard.Face.D, CommandCard.Face.B) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) } @Test fun `SingleServantOnly - lineup3 (QQABB)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } + )?.map { it.card } // Expect QABQB / 53124 / ECABD - assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) - } - - @Test - fun `SingleServantOnly - lineup3 (QQABB), rearranged=true`() { - val braveChains = ApplyBraveChains() - - val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - rearrange = true - ).map { it.card } - - // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) } // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup4 (BQQBQ) - No mighty chain`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } + val picked = mightyChain.getMightyChain( + cards = cards + )?.map { it.card } - // Expect same result as input - // Expect BQQBQ / 12345 / ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).isNull() } // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup5 (QQQBB) - No mighty chain`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty - ).map { it.card } + val picked = mightyChain.getMightyChain( + cards = cards + )?.map { it.card } - // Expect same result as input - // Expect QQQBB / 52314 / EBCAD - assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.D) + assertThat(picked).isNull() } /** * Two NP scenario */ @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero)`() { - val braveChains = ApplyBraveChains() - + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) - ).map { it.card } - val pickedDefault = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) - ).map { it.card } + )?.map { it.card } - // Should fall back to default behaviour - assertThat(picked).isEqualTo(pickedDefault) + assertThat(picked).isNull() } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, FieldSlot.C to CardTypeEnum.Arts ) - ).map { it.card } + )?.map { it.card } // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach)`() { - val braveChains = ApplyBraveChains() - + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - ).map { it.card } - val pickedDefault = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) - ).map { it.card } + )?.map { it.card } - // Should fall back to default behaviour - assertThat(picked).isEqualTo(pickedDefault) + assertThat(picked).isNull() } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { - val braveChains = ApplyBraveChains() - + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, FieldSlot.B to CardTypeEnum.Quick ) - ).map { it.card } - val pickedDefault = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) - ).map { it.card } + )?.map { it.card } - // Should fall back to default behaviour - assertThat(picked).isEqualTo(pickedDefault) + assertThat(picked).isNull() } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { - val braveChains = ApplyBraveChains() - + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = mightyChain.getMightyChain( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, FieldSlot.B to CardTypeEnum.Quick, FieldSlot.C to CardTypeEnum.Arts, ) - ).map { it.card } - val pickedDefault = braveChains.pick( - cards = cards, - braveChains = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0) - ).map { it.card } + )?.map { it.card } - // Should fall back to default behaviour - assertThat(picked).isEqualTo(pickedDefault) + assertThat(picked).isNull() } } \ No newline at end of file From 9da4321b476be22cc92a74fffd51e5124d01bbf1 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 31 Aug 2025 06:05:24 +0800 Subject: [PATCH 030/124] Add 'Avoid' in ChainType. Add automatic 'disabling' of Chain types when past 'Avoid'. Refactor many of the edited files to match coding standard. --- .../ui/card_priority/Render.kt | 34 ++++++++++++++----- .../ui/drag_sort/DragSort.kt | 11 ++++-- .../ui/drag_sort/DragSortAdapter.kt | 24 +++++++++---- .../util/ItemTouchHelperCallback.kt | 15 +++++--- .../util/ItemTouchHelpers.kt | 4 ++- app/src/main/res/values/colors.xml | 3 ++ .../scripts/enums/ChainTypeEnum.kt | 1 + 7 files changed, 69 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index 627e6cb5c..d7360cc2b 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -12,6 +12,7 @@ import androidx.compose.material3.ListItem import androidx.compose.material3.Text import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -20,6 +21,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.github.fate_grand_automata.R +import io.github.fate_grand_automata.prefs.core.Pref import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.TeamSlot @@ -147,19 +149,33 @@ private fun ChainPriority( val context = LocalContext.current + val getBackgroundColor = fun (enum: ChainTypeEnum): Int { + return (if (priorities.isAfterAvoid(enum)) R.color.colorDisabled + else when (enum) { + ChainTypeEnum.Arts -> R.color.colorArts + ChainTypeEnum.Quick -> R.color.colorQuick + ChainTypeEnum.Buster -> R.color.colorBuster + ChainTypeEnum.Mighty -> R.color.colorPrimaryDark + ChainTypeEnum.Avoid -> R.color.colorAvoid + } + ).let { res -> context.getColor(res) } + } DragSort( items = priorities, viewConfigGrabber = { DragSortAdapter.ItemViewConfig( foregroundColor = Color.WHITE, - backgroundColor = when (it) { - ChainTypeEnum.Arts -> R.color.colorArts - ChainTypeEnum.Quick -> R.color.colorQuick - ChainTypeEnum.Buster -> R.color.colorBuster - ChainTypeEnum.Mighty -> R.color.colorMasterSkill - }.let { res -> context.getColor(res) }, - text = it.name + backgroundColor = getBackgroundColor(it), + text = it.name, ) - } + }, + updateBackgroundColorOnMove = true ) -} \ No newline at end of file +} + +fun MutableList.isAfterAvoid(chainTypeEnum: ChainTypeEnum, indexOfAvoid: Int? = null): Boolean { + val avoidIndex = indexOfAvoid ?: this.indexOf(ChainTypeEnum.Avoid) + val enumIndex = this.indexOf(chainTypeEnum) + if (avoidIndex < 0) return false + return enumIndex > avoidIndex +} diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSort.kt b/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSort.kt index 71b9da235..53b69b27c 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSort.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSort.kt @@ -1,5 +1,6 @@ package io.github.fate_grand_automata.ui.drag_sort +import android.view.View import androidx.compose.runtime.Composable import androidx.compose.ui.viewinterop.AndroidView import androidx.recyclerview.widget.ItemTouchHelper @@ -10,7 +11,8 @@ import io.github.fate_grand_automata.util.ItemTouchHelperCallback @Composable fun DragSort( items: MutableList, - viewConfigGrabber: (T) -> DragSortAdapter.ItemViewConfig + viewConfigGrabber: (T) -> DragSortAdapter.ItemViewConfig, + updateBackgroundColorOnMove: Boolean = false ) { AndroidView( factory = { context -> @@ -23,9 +25,12 @@ fun DragSort( update = { it.adapter = DragSortAdapter( items, - viewConfigGrabber + viewConfigGrabber, + updateBackgroundColorOnMove ).also { adapter -> - val callback = ItemTouchHelperCallback(adapter) + val callback = ItemTouchHelperCallback( + adapter = adapter + ) val itemTouchHelper = ItemTouchHelper(callback) itemTouchHelper.attachToRecyclerView(it) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSortAdapter.kt b/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSortAdapter.kt index f8cd8f055..b90988f7d 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSortAdapter.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSortAdapter.kt @@ -15,7 +15,8 @@ import io.github.fate_grand_automata.util.IItemTouchHelperViewHolder class DragSortAdapter( private val items: MutableList, - private val viewConfigGrabber: (T) -> ItemViewConfig + private val viewConfigGrabber: (T) -> ItemViewConfig, + val updateBackgroundColorOnMove: Boolean = false, ) : RecyclerView.Adapter(), IItemTouchHelperAdapter { class ItemViewConfig( @ColorInt val foregroundColor: Int, @@ -59,11 +60,22 @@ class DragSortAdapter( holder.textView.setTextColor(viewConfig.foregroundColor) } - override fun onItemMove(From: Int, To: Int) { - val temp = items[From] - items[From] = items[To] - items[To] = temp + override fun onItemMove(from: Int, to: Int, origin: View?, target: View?) { + val temp = items[from] + items[from] = items[to] + items[to] = temp - notifyItemMoved(From, To) + // Swap views since the items also swapped + val views = listOf(target, origin) + for ((index, pos) in listOf(from, to).withIndex()) { + val currentItem = items[pos] + val viewConfig = viewConfigGrabber(currentItem) + val view = views[index] + if (updateBackgroundColorOnMove && view != null) { + view.setBackgroundColor(viewConfig.backgroundColor) + } + } + + notifyItemMoved(from, to) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/fate_grand_automata/util/ItemTouchHelperCallback.kt b/app/src/main/java/io/github/fate_grand_automata/util/ItemTouchHelperCallback.kt index c3ca77089..3b9a1c183 100644 --- a/app/src/main/java/io/github/fate_grand_automata/util/ItemTouchHelperCallback.kt +++ b/app/src/main/java/io/github/fate_grand_automata/util/ItemTouchHelperCallback.kt @@ -3,11 +3,13 @@ package io.github.fate_grand_automata.util import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -class ItemTouchHelperCallback(private val Adapter: IItemTouchHelperAdapter, - private val DragFlags: Int = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) : ItemTouchHelper.Callback() { +class ItemTouchHelperCallback( + private val adapter: IItemTouchHelperAdapter, + private val dragFlags: Int = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, +) : ItemTouchHelper.Callback() { override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { - return makeMovementFlags(DragFlags, 0) + return makeMovementFlags(dragFlags, 0) } override fun isLongPressDragEnabled() = true @@ -15,7 +17,12 @@ class ItemTouchHelperCallback(private val Adapter: IItemTouchHelperAdapter, override fun isItemViewSwipeEnabled() = false override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { - Adapter.onItemMove(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + adapter.onItemMove( + viewHolder.bindingAdapterPosition, + target.bindingAdapterPosition, + viewHolder.itemView, + target.itemView + ) return true } diff --git a/app/src/main/java/io/github/fate_grand_automata/util/ItemTouchHelpers.kt b/app/src/main/java/io/github/fate_grand_automata/util/ItemTouchHelpers.kt index c8690a900..46dceb19c 100644 --- a/app/src/main/java/io/github/fate_grand_automata/util/ItemTouchHelpers.kt +++ b/app/src/main/java/io/github/fate_grand_automata/util/ItemTouchHelpers.kt @@ -1,7 +1,9 @@ package io.github.fate_grand_automata.util +import android.view.View + interface IItemTouchHelperAdapter { - fun onItemMove(From: Int, To: Int) + fun onItemMove(from: Int, to: Int, origin: View? = null, target: View? = null) } interface IItemTouchHelperViewHolder { diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 7c509bb40..c70ffcdb3 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,6 +2,7 @@ #1B3147 #3498db + #ebebe4 #2e7d32 #006064 @@ -22,6 +23,8 @@ #2e7d32 #7cb342 + #808080 + #B3FFFFFF #30000000 diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt index 222215df7..98740fec9 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt @@ -5,4 +5,5 @@ enum class ChainTypeEnum { Buster, Arts, Quick, + Avoid, } \ No newline at end of file From 02a50c8dfb7bb074728f4a69396521fe88713e66 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 1 Sep 2025 03:14:51 +0800 Subject: [PATCH 031/124] Remove MightyChain from BraveChain and add new Attack handling system Remove MightyChain from BraveChain Rename ChainPrioritySelector to AttackPriorityHandler Create Interface for attack chains Add CardChainPriorityHandler as a wrapper for individual chain processing Add BraveChainHandler with a different BraveChain implementation Change MightChainIntegrationTest to use the AttackPriorityHandler and BraveChainHandler for output and tests instead --- .../scripts/enums/AttackPriorityEnum.kt | 12 + .../scripts/enums/ChainTypeEnum.kt | 6 +- .../interfaces/AttackChainInterface.kt | 48 +++ .../models/battle/ChainPriorityPerWave.kt | 2 +- .../scripts/modules/ApplyBraveChains.kt | 43 +-- .../scripts/modules/Card.kt | 13 +- .../modules/attack/ApplyMightyChains.kt | 75 +++-- ...tySelector.kt => AttackPriorityHandler.kt} | 50 ++- .../modules/attack/BraveChainHandler.kt | 100 ++++++ .../attack/CardChainPriorityHandler.kt | 41 +++ .../attack/MightyChainIntegrationTest.kt | 298 +++++++++--------- .../scripts/attack/MightyChainTest.kt | 51 +-- 12 files changed, 487 insertions(+), 252 deletions(-) create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/AttackPriorityEnum.kt create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt rename scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/{ChainPrioritySelector.kt => AttackPriorityHandler.kt} (54%) create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/AttackPriorityEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/AttackPriorityEnum.kt new file mode 100644 index 000000000..3f34d6149 --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/AttackPriorityEnum.kt @@ -0,0 +1,12 @@ +package io.github.fate_grand_automata.scripts.enums + +enum class AttackPriorityEnum { + BraveChainPriority, + CardChainPriority, + ServantPriority, + CardColorPriority; + + companion object { + val defaultOrder = listOf(BraveChainPriority, CardChainPriority, ServantPriority, CardColorPriority) + } +} \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt index 98740fec9..4ead6c18b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt @@ -5,5 +5,9 @@ enum class ChainTypeEnum { Buster, Arts, Quick, - Avoid, + Avoid; + + companion object { + val defaultOrder = listOf(Mighty, Buster, Arts, Quick, Avoid) + } } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt new file mode 100644 index 000000000..7e2b3c6a5 --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt @@ -0,0 +1,48 @@ +package io.github.fate_grand_automata.scripts.interfaces + +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard + +interface AttackChainInterface { + /** + * Checks if the currently selected Chain is valid + * + * @param cards Cards available for this attack + * @param npUsage NPs in use for this attack + * @param npTypes Card Types of the NP in use for this attack + * @param braveChainEnum Brave chain settings for this attack + * @param hasServantPriority Is Servant Priority in use for this attack + * @param forceBraveChain Allow only Brave Chains to succeed + */ + fun isAttackChainAllowed( + cards: List, + npUsage: NPUsage = NPUsage.Companion.none, + npTypes: Map = emptyMap(), + braveChainEnum: BraveChainEnum = BraveChainEnum.None, + hasServantPriority: Boolean = false, + forceBraveChain: Boolean = false, + ): Boolean + + /** + * Runs the selection of the cards using this Chain. + * Expects to return null if Chain cannot be performed. + * + * @param cards Cards available for this attack + * @param npUsage NPs in use for this attack + * @param npTypes Card Types of the NP in use for this attack + * @param braveChainEnum Brave chain settings for this attack + * @param hasServantPriority Is Servant Priority in use for this attack + * @param forceBraveChain Allow only Brave Chains to succeed + */ + fun pick( + cards: List, + npUsage: NPUsage = NPUsage.Companion.none, + npTypes: Map = emptyMap(), + braveChainEnum: BraveChainEnum = BraveChainEnum.None, + hasServantPriority: Boolean = false, + forceBraveChain: Boolean = false, + ): List? +} \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt index 88f697d0c..22ebec7a1 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt @@ -15,7 +15,7 @@ class ChainPriorityPerWave private constructor( } companion object { - val default = from(listOf(ChainTypeEnum.entries)) + val default = from(listOf(ChainTypeEnum.defaultOrder)) private const val STAGE_SEPARATOR = "\n" private const val SEPARATOR = "," diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index d6db4709f..a5c3ca4d4 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -6,15 +6,12 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot -import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains import io.github.lib_automata.dagger.ScriptScope import java.util.Collections import javax.inject.Inject @ScriptScope class ApplyBraveChains @Inject constructor() { - private val applyMightyChains = ApplyMightyChains() - private fun rearrange( cards: List, rearrange: Boolean, @@ -84,37 +81,6 @@ class ApplyBraveChains @Inject constructor() { ) } - private fun withNpMighty( - cards: List, - rearrange: Boolean, - npUsage: NPUsage, - npTypes: Map = emptyMap() - ): List { - // Get default NP sort, because using the default priority as the baseline - val justRearranged by lazy { - withNp( - cards = cards, - rearrange = rearrange, - npUsage = npUsage - ) - } - - // Returns empty if there are no valid targets. If empty, return the default - val mightyChainList = applyMightyChains.getMightyChain( - cards, - npUsage, - npTypes - )?.toMutableList() - if (mightyChainList == null || mightyChainList.isEmpty()) return justRearranged - - // Return the result - return rearrange( - cards = mightyChainList, - rearrange = rearrange, - npUsage = npUsage - ) - } - private fun avoid( cards: List, rearrange: Boolean @@ -213,14 +179,7 @@ class ApplyBraveChains @Inject constructor() { npUsage = npUsage ) - BraveChainEnum.WithNPMighty -> withNpMighty( - cards = cards, - rearrange = rearrange, - npUsage = npUsage, - npTypes - ) - - BraveChainEnum.Avoid -> avoid( + else -> avoid( cards = cards, rearrange = rearrange ) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index b514f7f94..5f4e3b3d4 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -4,7 +4,6 @@ import io.github.fate_grand_automata.scripts.IFgoAutomataApi import io.github.fate_grand_automata.scripts.ScriptLog import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum -import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage @@ -12,9 +11,8 @@ import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.SpamConfigPerTeamSlot import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.scripts.models.battle.BattleState -import io.github.fate_grand_automata.scripts.models.battle.ChainPriorityPerWave import io.github.fate_grand_automata.scripts.models.toFieldSlot -import io.github.fate_grand_automata.scripts.modules.attack.ChainPrioritySelector +import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandler import io.github.fate_grand_automata.scripts.prefs.IBattleConfig import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @@ -29,7 +27,7 @@ class Card @Inject constructor( private val parser: CardParser, private val priority: FaceCardPriority, private val braveChains: ApplyBraveChains, - private val chainPrioritySelector: ChainPrioritySelector, + private val attackPriorityHandler: AttackPriorityHandler, private val battleConfig: IBattleConfig ) : IFgoAutomataApi by api { @@ -68,12 +66,14 @@ class Card @Inject constructor( val useChainPriority = battleConfig.useChainPriority if (useChainPriority) { val chainPriority = battleConfig.chainPriority.atWave(state.stage) - chainPrioritySelector.pick( + attackPriorityHandler.pick( cards = cardsOrderedByPriority, npUsage = npUsage, + braveChainEnum = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), chainPriority = chainPriority, rearrange = rearrangeCardsPerWave.inCurrentWave(false), - npTypes = npTypes + npTypes = npTypes, + hasServantPriority = battleConfig.useServantPriority ).map { it.card } } @@ -82,7 +82,6 @@ class Card @Inject constructor( npUsage = npUsage, braveChains = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), rearrange = rearrangeCardsPerWave.inCurrentWave(false), - npTypes = npTypes ).map { it.card } } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt index 05fd4bc3c..6ef496905 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt @@ -1,49 +1,68 @@ package io.github.fate_grand_automata.scripts.modules.attack +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum -import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.interfaces.AttackChainInterface import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @ScriptScope -class ApplyMightyChains @Inject constructor() { +class ApplyMightyChains @Inject constructor( + private val braveChainHandler: BraveChainHandler +): AttackChainInterface { // We want 3 unique types, the magic number val totalUniqueCardTypesPermitted = 3 - fun getMightyChain ( + // Returns null if uniqueCards cannot be found + override fun pick ( cards: List, - npUsage: NPUsage = NPUsage.none, - npTypes: Map = emptyMap(), - chainPriority: List? = null + npUsage: NPUsage, + npTypes: Map, + braveChainEnum: BraveChainEnum, + hasServantPriority: Boolean, + forceBraveChain: Boolean, ): List? { val uniqueCardTypesFromNp = npTypes.values.toSet() if (!isMightyChainAllowed(npUsage, uniqueCardTypesFromNp, npTypes)) return null - // Get np if there is only 1 (since we want to try for Brave Chain) - val firstNp = if (npUsage.nps.size == 1) npUsage.nps.first() else null - val firstFieldSlot = firstNp?.toFieldSlot() + // Check for Brave Chain + val braveChainFieldSlot = + if (braveChainEnum == BraveChainEnum.Avoid) null + else if (npUsage.nps.size == 1) { + // Get np if there is only 1 (since we want to try for Brave Chain) + val firstNp = npUsage.nps.firstOrNull() + firstNp?.toFieldSlot() + } else { + val braveChainCapableFieldSlots = braveChainHandler.getFieldSlotsWithValidBraveChain(cards, npUsage) + val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } + // Return the first valid one, since it is already the highest priority + braveChainPriorityCard?.fieldSlot + } - return tryToGetMightyChain( - cards, - uniqueCardTypesFromNp, - firstFieldSlot, - chainPriority + return pick( + cards = cards, + uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, + fieldSlotForBraveChain = braveChainFieldSlot, + braveChainEnum = braveChainEnum, + forceBraveChain = forceBraveChain, ) } // Returns null if uniqueCards cannot be found - fun tryToGetMightyChain( + private fun pick( cards: List, // Decreases number of cards to get // e.g. usually based on npSize uniqueCardTypesAlreadyFilled: Set, - // In case of a single NP, we want to know what slot it is - singleNpFieldSlot: FieldSlot? = null, - chainPriority: List? = null + // In case of a Brave chain, we want to know what slot it is + fieldSlotForBraveChain: FieldSlot? = null, + braveChainEnum: BraveChainEnum = BraveChainEnum.None, + hasServantPriority: Boolean = false, + forceBraveChain: Boolean = false, ): List? { val cardsToFind = totalUniqueCardTypesPermitted val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() @@ -55,12 +74,14 @@ class ApplyMightyChains @Inject constructor() { it.type !in uniqueCardTypes } // If there is a single NP, we want to try for a Brave Mighty Chain - if (singleNpFieldSlot != null) { + if (fieldSlotForBraveChain != null) { // Attempt to find one matching the fieldSlot val fieldSlotList = filteredCards.filter { - it.fieldSlot == singleNpFieldSlot + it.fieldSlot == fieldSlotForBraveChain } - if (fieldSlotList.isNotEmpty()) filteredCards = fieldSlotList + // Even if it is empty, if forceBraveChain is on, + // it only accepts Brave Mighty Chains and not normal Mighty Chains + if (fieldSlotList.isNotEmpty() || forceBraveChain) filteredCards = fieldSlotList } val filteredCard = filteredCards.firstOrNull() if (filteredCard == null) break // If cannot find, we leave @@ -78,6 +99,18 @@ class ApplyMightyChains @Inject constructor() { return combinedCards } + override fun isAttackChainAllowed ( + cards: List, + npUsage: NPUsage, + npTypes: Map, + braveChainEnum: BraveChainEnum, + hasServantPriority: Boolean, + forceBraveChain: Boolean, + ): Boolean { + val uniqueCardTypesFromNp = npTypes.values.toSet() + return isMightyChainAllowed(npUsage, uniqueCardTypesFromNp, npTypes) + } + fun isMightyChainAllowed ( npUsage: NPUsage, uniqueCardTypesFromNp: Set, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ChainPrioritySelector.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt similarity index 54% rename from scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ChainPrioritySelector.kt rename to scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 552422a02..269814b1a 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ChainPrioritySelector.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -1,20 +1,22 @@ package io.github.fate_grand_automata.scripts.modules.attack +import io.github.fate_grand_automata.scripts.enums.AttackPriorityEnum +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains import io.github.lib_automata.dagger.ScriptScope import java.util.Collections import javax.inject.Inject @ScriptScope -class ChainPrioritySelector @Inject constructor( - private val applyMightyChains: ApplyMightyChains, +class AttackPriorityHandler @Inject constructor( + private val cardChainPriorityHandler: CardChainPriorityHandler, + private val braveChainHandler: BraveChainHandler, ) { - private fun rearrange( + fun rearrange( cards: List, rearrange: Boolean, npUsage: NPUsage @@ -54,22 +56,44 @@ class ChainPrioritySelector @Inject constructor( fun pick( cards: List, - chainPriority: List, - rearrange: Boolean = false, + attackPriorityOrder: List = AttackPriorityEnum.defaultOrder, + chainPriority: List = ChainTypeEnum.defaultOrder, + braveChainEnum: BraveChainEnum = BraveChainEnum.None, npUsage: NPUsage = NPUsage.none, - npTypes: Map = emptyMap() + npTypes: Map = emptyMap(), + rearrange: Boolean = false, + hasServantPriority: Boolean = false, ): List { var newCardOrder: List? = null - for (chain in chainPriority) { - newCardOrder = when (chain) { - ChainTypeEnum.Mighty -> applyMightyChains.getMightyChain(cards, npUsage, npTypes) - else -> null + var braveChainFallback: List? = null + for (attackPriority in attackPriorityOrder) { + when (attackPriority) { + AttackPriorityEnum.BraveChainPriority -> { + braveChainFallback = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = npUsage, + ) + } + AttackPriorityEnum.CardChainPriority -> { + if (newCardOrder != null) continue + newCardOrder = cardChainPriorityHandler.pick( + cards = cards, + chainPriority = chainPriority, + braveChainEnum = braveChainEnum, + npUsage = npUsage, + npTypes = npTypes, + hasServantPriority = hasServantPriority, + // BraveChain is higher priority than color chain + forceBraveChain = braveChainFallback != null + ) + } + else -> continue } - if (newCardOrder != null) break; } return rearrange( - cards = newCardOrder ?: cards, + cards = newCardOrder ?: braveChainFallback ?: cards, rearrange = rearrange, npUsage = npUsage ) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt new file mode 100644 index 000000000..1926d4e47 --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -0,0 +1,100 @@ +package io.github.fate_grand_automata.scripts.modules.attack + +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.toFieldSlot +import io.github.lib_automata.dagger.ScriptScope +import javax.inject.Inject + +@ScriptScope +class BraveChainHandler @Inject constructor() { + fun isBraveChainAllowed ( + braveChainEnum: BraveChainEnum, + cards: List, + npUsage: NPUsage + ): Boolean { + val cardsPerFieldSlotMap = getCardsPerFieldSlotMap(cards, npUsage) + return isBraveChainAllowed(braveChainEnum, cardsPerFieldSlotMap) + } + + fun pick( + cards: List, + braveChainEnum: BraveChainEnum, + npUsage: NPUsage = NPUsage.none, + ): List? { + val cardsPerFieldSlotMap = getCardsPerFieldSlotMap(cards, npUsage) + if (!isBraveChainAllowed(braveChainEnum, cardsPerFieldSlotMap)) return null + + val braveChainCapableFieldSlots = getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) + + // Use the first valid one, since it is already the highest priority + val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } + val priorityFieldSlot = braveChainPriorityCard?.fieldSlot + + // Safety check + if (priorityFieldSlot == null) return null + val selectedCards = cards + .filter { it.fieldSlot == priorityFieldSlot } + .take((3 - npUsage.nps.size).coerceAtLeast(0)) + val remainder = cards - selectedCards + return selectedCards + remainder + } + + private fun isBraveChainAllowed ( + braveChainEnum: BraveChainEnum, + cardsPerFieldSlotMap: Map + ): Boolean { + if ( + braveChainEnum == BraveChainEnum.Avoid + || cardsPerFieldSlotMap.values.isEmpty() + ) return false + + // Sort by most number of cards + val highestTotalCardCount = cardsPerFieldSlotMap.values.sorted().reversed().first() + + return highestTotalCardCount >= 3 + } + + private fun getCardsPerFieldSlotMap ( + cards: List, + npUsage: NPUsage + ): Map { + // Card list check + val cardsPerFieldSlotMap: MutableMap = mutableMapOf() + for (card in cards) { + val fieldSlot = card.fieldSlot + if (fieldSlot == null) continue + val currentValue = cardsPerFieldSlotMap.getOrElse(fieldSlot) { 0 } + cardsPerFieldSlotMap[fieldSlot] = currentValue + 1 + } + // NPs check + // Any more than 1 NP means that it is impossible to Brave Chain + // 0 NPs does not need handling + if (npUsage.nps.size == 1) { + val fieldSlot = npUsage.nps.first().toFieldSlot() + val currentValue = cardsPerFieldSlotMap.getOrElse(fieldSlot) { 0 } + cardsPerFieldSlotMap[fieldSlot] = currentValue + 1 + } + return cardsPerFieldSlotMap.toMap() + } + + fun getFieldSlotsWithValidBraveChain ( + cards: List, + npUsage: NPUsage + ): List { + val cardsPerFieldSlotMap = getCardsPerFieldSlotMap(cards, npUsage) + return getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) + } + + private fun getFieldSlotsWithValidBraveChain ( + cardsPerFieldSlotMap: Map + ): List { + return cardsPerFieldSlotMap.keys + .filter { cardsPerFieldSlotMap.getOrElse(it) { 0 } >= 3 } + .map { it } + } + +} \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt new file mode 100644 index 000000000..52d8e3d60 --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -0,0 +1,41 @@ +package io.github.fate_grand_automata.scripts.modules.attack + +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.lib_automata.dagger.ScriptScope +import javax.inject.Inject + +@ScriptScope +class CardChainPriorityHandler @Inject constructor( + private val applyMightyChains: ApplyMightyChains, +) { + fun pick( + cards: List, + chainPriority: List = ChainTypeEnum.defaultOrder, + braveChainEnum: BraveChainEnum = BraveChainEnum.None, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + hasServantPriority: Boolean = false, + forceBraveChain: Boolean = false, + ): List? { + var newCardOrder: List? = null + for (chain in chainPriority) { + if (newCardOrder != null) continue + newCardOrder = when (chain) { + ChainTypeEnum.Mighty -> applyMightyChains.pick( + cards = cards, + npUsage = npUsage, + npTypes = npTypes, + forceBraveChain = forceBraveChain + ) + else -> null + } + } + + return newCardOrder + } +} \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt index cf898e550..776a4f1de 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt @@ -3,25 +3,60 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEqualTo -import io.github.fate_grand_automata.scripts.attack.AttackLineUps import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage -import io.github.fate_grand_automata.scripts.modules.ApplyBraveChains +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains +import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandler +import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler +import io.github.fate_grand_automata.scripts.modules.attack.CardChainPriorityHandler +import kotlin.test.BeforeTest import kotlin.test.Test class MightyChainIntegrationTest { + lateinit var braveChainHandler: BraveChainHandler + lateinit var attackPriorityHandler: AttackPriorityHandler + + @BeforeTest + fun init() { + braveChainHandler = BraveChainHandler() + attackPriorityHandler = AttackPriorityHandler( + cardChainPriorityHandler = CardChainPriorityHandler( + applyMightyChains = ApplyMightyChains( + braveChainHandler = braveChainHandler + ) + ), + braveChainHandler = braveChainHandler + ) + } + + fun getDefaultBraveChainResult ( + cards: List, + braveChainEnum: BraveChainEnum, + rearrange: Boolean = false, + npUsage: NPUsage = NPUsage.none, + ): List { + val braveChainCards = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = npUsage, + ) ?: cards + return attackPriorityHandler.rearrange( + cards = braveChainCards, + rearrange = rearrange, + npUsage = npUsage, + ) + } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE @@ -30,12 +65,10 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, rearrange = true ).map { it.card } @@ -45,12 +78,10 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup2 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } // Expect 1SB,2KQ,3NA,5SQ,4NA - 15324 - AECBD @@ -59,12 +90,10 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA), with rearrange`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup2 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, rearrange = true ).map { it.card } @@ -74,17 +103,15 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNP, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) ).map { it.card } @@ -94,34 +121,30 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), rearrange = true ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNP, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), rearrange = true ).map { it.card } - // Same as above but swap position 2 and 3) + // Same as above but swap position 1 and 2 because of NP assertThat(picked).isEqualTo(pickedDefault) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick @@ -134,12 +157,10 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange & npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick @@ -147,72 +168,87 @@ class MightyChainIntegrationTest { rearrange = true ).map { it.card } - // Same as above but swap position 1 and 2) // because NP + // Same as above but swap position 1 and 2 because of NP assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), ).map { it.card } - // Unable to Mighty Chain with Scathach. Will ignore and return result of withNp + // No mighty chain. Expect NP Brave Chain output assertThat(picked).isEqualTo(pickedDefault) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with rearrange`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + rearrange = true + ).map { it.card } + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), rearrange = true ).map { it.card } - // Same as above but swap position 2 and 3) - assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + assertThat(picked).isEqualTo(pickedDefault) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { - val braveChains = ApplyBraveChains() - + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes - No Mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.B to CardTypeEnum.Quick ) ).map { it.card } - // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + // No mighty chain. Expect NP Brave Chain output + // Expect 1SB,5SQ,2KQ,3NA,4NA - 15234 - AEBCD + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { - val braveChains = ApplyBraveChains() + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes & rearrange`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = attackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + } + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) ).map { it.card } @@ -223,46 +259,40 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearranged`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), rearrange = true ).map { it.card } - // Same as above but swap position 2 and 3) + // Same as above but swap position 1 and 2 because of NP assertThat(picked).containsExactly(CommandCard.Face.D, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts ) ).map { it.card } - // Expect 1SB,2KQ,3NA,4NA,5SQ - 12345 - ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + // Expect 3NA,4NA,1SB,2KQ,5SQ - 34125 - CDABE + assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearrange & npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts @@ -270,18 +300,16 @@ class MightyChainIntegrationTest { rearrange = true ).map { it.card } - // Expect 2KQ,1SB,3NA,4NA,5SQ - 21345 - BACDE - assertThat(picked).containsExactly(CommandCard.Face.B, CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + // Same as above but swap position 1 and 2 because of NP + assertThat(picked).containsExactly(CommandCard.Face.D, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) } @Test fun `SingleServantOnly - lineup1 (BQABQ)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } // Expect same result as input @@ -291,12 +319,10 @@ class MightyChainIntegrationTest { @Test fun `SingleServantOnly - lineup1 (BQABQ), with rearranged`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, rearrange = true ).map { it.card } @@ -306,12 +332,10 @@ class MightyChainIntegrationTest { @Test fun `SingleServantOnly - lineup2 (BBQQA)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } // Expect 1SB,5SQ,3NA,4NA,2KQ / 15342 / AECDB @@ -320,12 +344,10 @@ class MightyChainIntegrationTest { @Test fun `SingleServantOnly - lineup2 (BBQQA), with rearranged`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, rearrange = true ).map { it.card } @@ -335,12 +357,10 @@ class MightyChainIntegrationTest { @Test fun `SingleServantOnly - lineup3 (QQABB)`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } // Expect QABQB / 53124 / ECABD @@ -349,12 +369,10 @@ class MightyChainIntegrationTest { @Test fun `SingleServantOnly - lineup3 (QQABB), with rearranged`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, rearrange = true ).map { it.card } @@ -365,16 +383,14 @@ class MightyChainIntegrationTest { // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup4 (BQQBQ) - No mighty chain`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } assertThat(picked).isEqualTo(pickedDefault) @@ -383,16 +399,14 @@ class MightyChainIntegrationTest { // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup5 (QQQBB) - No mighty chain`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNPMighty + braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } assertThat(picked).isEqualTo(pickedDefault) @@ -403,17 +417,15 @@ class MightyChainIntegrationTest { */ @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero) - No mighty chain`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNP, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) ).map { it.card } @@ -423,12 +435,10 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, @@ -442,17 +452,15 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNP, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) ).map { it.card } @@ -462,21 +470,19 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, FieldSlot.B to CardTypeEnum.Quick ) ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNP, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) ).map { it.card } @@ -486,12 +492,10 @@ class MightyChainIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { - val braveChains = ApplyBraveChains() - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChains.pick( + val picked = attackPriorityHandler.pick( cards = cards, - braveChains = BraveChainEnum.WithNPMighty, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, @@ -499,9 +503,9 @@ class MightyChainIntegrationTest { FieldSlot.C to CardTypeEnum.Arts, ) ).map { it.card } - val pickedDefault = braveChains.pick( + val pickedDefault = getDefaultBraveChainResult( cards = cards, - braveChains = BraveChainEnum.WithNP, + braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0) ).map { it.card } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index f2068c861..42df13bd6 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -3,21 +3,32 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isNull +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains +import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler +import kotlin.test.BeforeTest import kotlin.test.Test class MightyChainTest { - val mightyChain = ApplyMightyChains() + lateinit var mightyChain: ApplyMightyChains + + @BeforeTest + fun init() { + mightyChain = ApplyMightyChains( + braveChainHandler = BraveChainHandler() + ) + } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( - cards = cards + val picked = mightyChain.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP )?.map { it.card } // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE @@ -27,7 +38,7 @@ class MightyChainTest { @Test fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards )?.map { it.card } @@ -38,7 +49,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) )?.map { it.card } @@ -50,7 +61,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -65,7 +76,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) )?.map { it.card } @@ -77,7 +88,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( @@ -92,7 +103,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) )?.map { it.card } @@ -104,7 +115,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( @@ -119,7 +130,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup1 (BQABQ)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, )?.map { it.card } @@ -131,7 +142,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup2 (BBQQA)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards )?.map { it.card } @@ -142,7 +153,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup3 (QQABB)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, )?.map { it.card } @@ -154,7 +165,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup4 (BQQBQ) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards )?.map { it.card } @@ -165,7 +176,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup5 (QQQBB) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards )?.map { it.card } @@ -178,7 +189,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) )?.map { it.card } @@ -189,7 +200,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), npTypes = mapOf( @@ -205,7 +216,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), )?.map { it.card } @@ -216,7 +227,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( @@ -231,7 +242,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.getMightyChain( + val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( From 9f81a22f8905652890f922527efde3f06ebe64a5 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 1 Sep 2025 03:17:12 +0800 Subject: [PATCH 032/124] Remove WithNPMighty option from BraveChainEnum --- .../github/fate_grand_automata/util/LocalizationExtensions.kt | 1 - app/src/main/res/values-b+zh+CN/localized.xml | 1 - app/src/main/res/values-b+zh+TW/localized.xml | 1 - app/src/main/res/values-ja/localized.xml | 1 - app/src/main/res/values-ko/localized.xml | 1 - app/src/main/res/values-vi/localized.xml | 1 - app/src/main/res/values/localized.xml | 1 - .../github/fate_grand_automata/scripts/enums/BraveChainEnum.kt | 1 - .../fate_grand_automata/scripts/modules/ApplyBraveChains.kt | 2 +- 9 files changed, 1 insertion(+), 9 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt index ff2cfc450..22dda16b5 100644 --- a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt +++ b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt @@ -25,7 +25,6 @@ val BraveChainEnum.stringRes get() = when (this) { BraveChainEnum.None -> R.string.p_brave_chains_don_t_care BraveChainEnum.WithNP -> R.string.p_brave_chains_with_np - BraveChainEnum.WithNPMighty -> R.string.p_brave_chains_with_np_mighty BraveChainEnum.Avoid -> R.string.p_brave_chains_avoid } diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index 11425bfef..07e8e23a2 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -188,7 +188,6 @@ Order Change" "圣晶石" "无偏好" "宝具施放后" - "宝具施放后(Mighty)" "避免追击连携" "关闭" "跳过" diff --git a/app/src/main/res/values-b+zh+TW/localized.xml b/app/src/main/res/values-b+zh+TW/localized.xml index f5738482f..5cfde2f59 100644 --- a/app/src/main/res/values-b+zh+TW/localized.xml +++ b/app/src/main/res/values-b+zh+TW/localized.xml @@ -186,7 +186,6 @@ Order Change" "聖晶石" "無偏好" "寶具施放後" - "寶具施放後(Mighty)" "避免Brave Chain" "關閉" "跳過" diff --git a/app/src/main/res/values-ja/localized.xml b/app/src/main/res/values-ja/localized.xml index 575a16e97..26e3476fe 100644 --- a/app/src/main/res/values-ja/localized.xml +++ b/app/src/main/res/values-ja/localized.xml @@ -190,7 +190,6 @@ "聖晶石" "考慮しない" "宝具と同時に" - "宝具と同時に(マイティ)" "使用しない" "使用確認OFF" "使用しない" diff --git a/app/src/main/res/values-ko/localized.xml b/app/src/main/res/values-ko/localized.xml index 8eb4abb7d..04bc19c00 100644 --- a/app/src/main/res/values-ko/localized.xml +++ b/app/src/main/res/values-ko/localized.xml @@ -185,7 +185,6 @@ "성정석" "상관 없음" "보구랑 같이" - "보구랑 같이 (무적 체인)" "자제" "비활성화" "스킵" diff --git a/app/src/main/res/values-vi/localized.xml b/app/src/main/res/values-vi/localized.xml index f9b13dc4f..0546c7445 100644 --- a/app/src/main/res/values-vi/localized.xml +++ b/app/src/main/res/values-vi/localized.xml @@ -186,7 +186,6 @@ Sao chép hoặc đổi tên nó nếu bạn muốn giữ nó." "SQ" "không quan tâm" "Với NP" - "Với NP (Mighty)" "Tránh" "Tắt" "Bỏ qua" diff --git a/app/src/main/res/values/localized.xml b/app/src/main/res/values/localized.xml index f90cac056..0bb11dac7 100644 --- a/app/src/main/res/values/localized.xml +++ b/app/src/main/res/values/localized.xml @@ -189,7 +189,6 @@ Copy or rename it if you want to keep it." "SQ" "Don't care" "With NP" - "With NP (Mighty)" "Avoid" "Disabled" "Skip" diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt index 686a3b699..4b378bb3a 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt @@ -3,6 +3,5 @@ package io.github.fate_grand_automata.scripts.enums enum class BraveChainEnum { None, WithNP, - WithNPMighty, Avoid } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index a5c3ca4d4..1d17d59a9 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -179,7 +179,7 @@ class ApplyBraveChains @Inject constructor() { npUsage = npUsage ) - else -> avoid( + BraveChainEnum.Avoid -> avoid( cards = cards, rearrange = rearrange ) From 0cd8dc120ac2d8301dd9471c9392209f0e183df7 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 1 Sep 2025 03:49:26 +0800 Subject: [PATCH 033/124] Add localization for ChainTypeEnums --- .../fate_grand_automata/ui/card_priority/Render.kt | 6 +++++- .../fate_grand_automata/util/LocalizationExtensions.kt | 10 ++++++++++ app/src/main/res/values-b+zh+CN/localized.xml | 5 +++++ app/src/main/res/values-b+zh+TW/localized.xml | 5 +++++ app/src/main/res/values-ja/localized.xml | 5 +++++ app/src/main/res/values-ko/localized.xml | 5 +++++ app/src/main/res/values-vi/localized.xml | 5 +++++ app/src/main/res/values/localized.xml | 5 +++++ 8 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index d7360cc2b..9ab14adda 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -160,13 +160,17 @@ private fun ChainPriority( } ).let { res -> context.getColor(res) } } + val localizedStringMap: MutableMap = mutableMapOf() + priorities.forEach { + localizedStringMap[it] = stringResource(it.stringRes) + } DragSort( items = priorities, viewConfigGrabber = { DragSortAdapter.ItemViewConfig( foregroundColor = Color.WHITE, backgroundColor = getBackgroundColor(it), - text = it.name, + text = localizedStringMap.getOrElse(it) { it.name }, ) }, updateBackgroundColorOnMove = true diff --git a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt index 22dda16b5..d9813a63b 100644 --- a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt +++ b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt @@ -4,6 +4,7 @@ import io.github.fate_grand_automata.R import io.github.fate_grand_automata.prefs.core.GameAreaMode import io.github.fate_grand_automata.scripts.enums.BondCEEffectEnum import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.enums.GameServer import io.github.fate_grand_automata.scripts.enums.MaterialEnum import io.github.fate_grand_automata.scripts.enums.RefillResourceEnum @@ -28,6 +29,15 @@ val BraveChainEnum.stringRes BraveChainEnum.Avoid -> R.string.p_brave_chains_avoid } +val ChainTypeEnum.stringRes + get() = when (this) { + ChainTypeEnum.Arts -> R.string.p_chain_type_arts + ChainTypeEnum.Quick -> R.string.p_chain_type_quick + ChainTypeEnum.Buster -> R.string.p_chain_type_buster + ChainTypeEnum.Mighty -> R.string.p_chain_type_mighty + ChainTypeEnum.Avoid -> R.string.p_chain_type_avoid + } + val SpamEnum.stringRes get() = when (this) { SpamEnum.None -> R.string.p_spam_none diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index 07e8e23a2..4021df7cc 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -189,6 +189,11 @@ Order Change" "无偏好" "宝具施放后" "避免追击连携" + "迅击" + "技击" + "力击" + "Mighty" + "避免" "关闭" "跳过" "瞎打模式" diff --git a/app/src/main/res/values-b+zh+TW/localized.xml b/app/src/main/res/values-b+zh+TW/localized.xml index 5cfde2f59..368f9d724 100644 --- a/app/src/main/res/values-b+zh+TW/localized.xml +++ b/app/src/main/res/values-b+zh+TW/localized.xml @@ -187,6 +187,11 @@ Order Change" "無偏好" "寶具施放後" "避免Brave Chain" + "Quick" + "Arts" + "Buster" + "Mighty" + "避免" "關閉" "跳過" "自動模式" diff --git a/app/src/main/res/values-ja/localized.xml b/app/src/main/res/values-ja/localized.xml index 26e3476fe..0ec12896b 100644 --- a/app/src/main/res/values-ja/localized.xml +++ b/app/src/main/res/values-ja/localized.xml @@ -191,6 +191,11 @@ "考慮しない" "宝具と同時に" "使用しない" + "Quick" + "Arts" + "Buster" + "マイティ" + "おわり" "使用確認OFF" "使用しない" "リピートモード" diff --git a/app/src/main/res/values-ko/localized.xml b/app/src/main/res/values-ko/localized.xml index 04bc19c00..a1993d710 100644 --- a/app/src/main/res/values-ko/localized.xml +++ b/app/src/main/res/values-ko/localized.xml @@ -186,6 +186,11 @@ "상관 없음" "보구랑 같이" "자제" + "Quick" + "Arts" + "Buster" + "Mighty" + "자제" "비활성화" "스킵" "반복 모드" diff --git a/app/src/main/res/values-vi/localized.xml b/app/src/main/res/values-vi/localized.xml index 0546c7445..5d5ada829 100644 --- a/app/src/main/res/values-vi/localized.xml +++ b/app/src/main/res/values-vi/localized.xml @@ -187,6 +187,11 @@ Sao chép hoặc đổi tên nó nếu bạn muốn giữ nó." "không quan tâm" "Với NP" "Tránh" + "Quick" + "Arts" + "Buster" + "Mighty" + "Tránh" "Tắt" "Bỏ qua" "Chế độ spam" diff --git a/app/src/main/res/values/localized.xml b/app/src/main/res/values/localized.xml index 0bb11dac7..dc5fcfe7b 100644 --- a/app/src/main/res/values/localized.xml +++ b/app/src/main/res/values/localized.xml @@ -190,6 +190,11 @@ Copy or rename it if you want to keep it." "Don't care" "With NP" "Avoid" + "Quick" + "Arts" + "Buster" + "Mighty" + "Avoid" "Disabled" "Skip" "Spam mode" From 22b59527fce85a3bfb977ced16cb38fd28eb9d56 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 1 Sep 2025 10:08:14 +0800 Subject: [PATCH 034/124] Remove unnecessary imports from Render --- .../io/github/fate_grand_automata/ui/card_priority/Render.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index 9ab14adda..9d49243dc 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -12,7 +12,6 @@ import androidx.compose.material3.ListItem import androidx.compose.material3.Text import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -21,12 +20,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.github.fate_grand_automata.R -import io.github.fate_grand_automata.prefs.core.Pref import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.ui.FGAListItemColors -import io.github.fate_grand_automata.ui.FGAListItemColorsDisabled import io.github.fate_grand_automata.ui.drag_sort.DragSort import io.github.fate_grand_automata.ui.drag_sort.DragSortAdapter import io.github.fate_grand_automata.ui.prefs.listDialog From 3c8191ed5fe435e41fe028857c6a275ac2da07f8 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 2 Sep 2025 00:26:23 +0800 Subject: [PATCH 035/124] Refactor MightyChainTest to have more consistent asserts --- .../scripts/attack/MightyChainTest.kt | 73 ++++++++++--------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 42df13bd6..01780e4f5 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -2,6 +2,7 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly +import assertk.assertions.isEmpty import assertk.assertions.isNull import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum @@ -29,10 +30,10 @@ class MightyChainTest { val picked = mightyChain.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test @@ -40,10 +41,10 @@ class MightyChainTest { val cards = AttackLineUps.Standard.lineup2 val picked = mightyChain.pick( cards = cards - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect 1SB,2KQ,3NA,5SQ,4NA - 15324 - AECBD - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) } @Test @@ -52,10 +53,10 @@ class MightyChainTest { val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Unable to Brave Chain with Kama. Will ignore and return result of withNp - assertThat(picked).isNull() + assertThat(picked).isEmpty() } @Test @@ -67,10 +68,10 @@ class MightyChainTest { npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick ) - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) } @Test @@ -79,10 +80,10 @@ class MightyChainTest { val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Unable to Mighty Chain with Scathach NP since type is unknown. - assertThat(picked).isNull() + assertThat(picked).isEmpty() } @Test @@ -94,10 +95,10 @@ class MightyChainTest { npTypes = mapOf( FieldSlot.B to CardTypeEnum.Quick ) - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) } @Test @@ -106,10 +107,10 @@ class MightyChainTest { val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Unable to Mighty Chain with Nero NP since type is unknown. - assertThat(picked).isNull() + assertThat(picked).isEmpty() } @Test @@ -121,10 +122,10 @@ class MightyChainTest { npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts ) - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect 1SB,2KQ,3NA,4NA,5SQ - 12345 - ABCDE - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test @@ -132,11 +133,11 @@ class MightyChainTest { val cards = AttackLineUps.SingleServantOnly.lineup1 val picked = mightyChain.pick( cards = cards, - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect same result as input // Expect BQABQ / 12345 / ABCDE - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test @@ -144,10 +145,10 @@ class MightyChainTest { val cards = AttackLineUps.SingleServantOnly.lineup2 val picked = mightyChain.pick( cards = cards - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect 1SB,5SQ,3NA,4NA,2KQ / 15342 / AECDB - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) } @Test @@ -155,10 +156,10 @@ class MightyChainTest { val cards = AttackLineUps.SingleServantOnly.lineup3 val picked = mightyChain.pick( cards = cards, - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect QABQB / 53124 / ECABD - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) + assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) } // Scenario for when 2 card types are found but not the 3rd @@ -167,9 +168,9 @@ class MightyChainTest { val cards = AttackLineUps.SingleServantOnly.lineup4 val picked = mightyChain.pick( cards = cards - )?.map { it.card } + )?.map { it.card } ?: emptyList() - assertThat(picked).isNull() + assertThat(picked).isEmpty() } // Scenario for when 2 card types are found but not the 3rd @@ -178,9 +179,9 @@ class MightyChainTest { val cards = AttackLineUps.SingleServantOnly.lineup5 val picked = mightyChain.pick( cards = cards - )?.map { it.card } + )?.map { it.card } ?: emptyList() - assertThat(picked).isNull() + assertThat(picked).isEmpty() } /** @@ -192,9 +193,9 @@ class MightyChainTest { val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) - )?.map { it.card } + )?.map { it.card } ?: emptyList() - assertThat(picked).isNull() + assertThat(picked).isEmpty() } @Test @@ -207,10 +208,10 @@ class MightyChainTest { FieldSlot.A to CardTypeEnum.Quick, FieldSlot.C to CardTypeEnum.Arts ) - )?.map { it.card } + )?.map { it.card } ?: emptyList() // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE - assertThat(picked ?: emptyList()).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } @Test @@ -219,9 +220,9 @@ class MightyChainTest { val picked = mightyChain.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - )?.map { it.card } + )?.map { it.card } ?: emptyList() - assertThat(picked).isNull() + assertThat(picked).isEmpty() } @Test @@ -234,9 +235,9 @@ class MightyChainTest { FieldSlot.A to CardTypeEnum.Quick, FieldSlot.B to CardTypeEnum.Quick ) - )?.map { it.card } + )?.map { it.card } ?: emptyList() - assertThat(picked).isNull() + assertThat(picked).isEmpty() } @Test @@ -250,8 +251,8 @@ class MightyChainTest { FieldSlot.B to CardTypeEnum.Quick, FieldSlot.C to CardTypeEnum.Arts, ) - )?.map { it.card } + )?.map { it.card } ?: emptyList() - assertThat(picked).isNull() + assertThat(picked).isEmpty() } } \ No newline at end of file From 19a92e8e4edde56fcd55e58057e74b4f32f4a6ef Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 2 Sep 2025 01:23:36 +0800 Subject: [PATCH 036/124] Move some functions out of BraveChainHandler into Utils. Write tests for BraveChainHandler and Utils --- .../interfaces/AttackChainInterface.kt | 8 +- .../modules/attack/ApplyMightyChains.kt | 18 ++- .../modules/attack/BraveChainHandler.kt | 51 +------- .../scripts/modules/attack/Utils.kt | 50 +++++++ .../scripts/attack/AttackLineUps.kt | 4 +- .../scripts/attack/BraveChainHandlerTest.kt | 92 +++++++++++++ .../attack/MightyChainIntegrationTest.kt | 14 +- .../scripts/attack/MightyChainTest.kt | 7 +- .../scripts/attack/UtilsTest.kt | 122 ++++++++++++++++++ 9 files changed, 299 insertions(+), 67 deletions(-) create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt create mode 100644 scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainHandlerTest.kt create mode 100644 scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt index 7e2b3c6a5..0fb244518 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt @@ -14,7 +14,7 @@ interface AttackChainInterface { * @param npUsage NPs in use for this attack * @param npTypes Card Types of the NP in use for this attack * @param braveChainEnum Brave chain settings for this attack - * @param hasServantPriority Is Servant Priority in use for this attack + * @param forceServantPriority Is Servant Priority in use for this attack * @param forceBraveChain Allow only Brave Chains to succeed */ fun isAttackChainAllowed( @@ -22,7 +22,7 @@ interface AttackChainInterface { npUsage: NPUsage = NPUsage.Companion.none, npTypes: Map = emptyMap(), braveChainEnum: BraveChainEnum = BraveChainEnum.None, - hasServantPriority: Boolean = false, + forceServantPriority: Boolean = false, forceBraveChain: Boolean = false, ): Boolean @@ -34,7 +34,7 @@ interface AttackChainInterface { * @param npUsage NPs in use for this attack * @param npTypes Card Types of the NP in use for this attack * @param braveChainEnum Brave chain settings for this attack - * @param hasServantPriority Is Servant Priority in use for this attack + * @param forceServantPriority Is Servant Priority in use for this attack * @param forceBraveChain Allow only Brave Chains to succeed */ fun pick( @@ -42,7 +42,7 @@ interface AttackChainInterface { npUsage: NPUsage = NPUsage.Companion.none, npTypes: Map = emptyMap(), braveChainEnum: BraveChainEnum = BraveChainEnum.None, - hasServantPriority: Boolean = false, + forceServantPriority: Boolean = false, forceBraveChain: Boolean = false, ): List? } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt index 6ef496905..471062866 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt @@ -12,7 +12,7 @@ import javax.inject.Inject @ScriptScope class ApplyMightyChains @Inject constructor( - private val braveChainHandler: BraveChainHandler + private val utils: Utils ): AttackChainInterface { // We want 3 unique types, the magic number val totalUniqueCardTypesPermitted = 3 @@ -23,7 +23,7 @@ class ApplyMightyChains @Inject constructor( npUsage: NPUsage, npTypes: Map, braveChainEnum: BraveChainEnum, - hasServantPriority: Boolean, + forceServantPriority: Boolean, forceBraveChain: Boolean, ): List? { val uniqueCardTypesFromNp = npTypes.values.toSet() @@ -37,7 +37,14 @@ class ApplyMightyChains @Inject constructor( val firstNp = npUsage.nps.firstOrNull() firstNp?.toFieldSlot() } else { - val braveChainCapableFieldSlots = braveChainHandler.getFieldSlotsWithValidBraveChain(cards, npUsage) + val braveChainCapableFieldSlots = utils.getFieldSlotsWithValidBraveChain(cards, npUsage) + // If there is Servant Priority, the first card is most important. + // Meaning if first card cannot Brave Chain, we ignore Brave Chain + if (forceServantPriority) { + val firstCardFieldSlot = cards.firstOrNull()?.fieldSlot + if (braveChainCapableFieldSlots.contains(firstCardFieldSlot)) firstCardFieldSlot + else null + } val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } // Return the first valid one, since it is already the highest priority braveChainPriorityCard?.fieldSlot @@ -48,6 +55,7 @@ class ApplyMightyChains @Inject constructor( uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, fieldSlotForBraveChain = braveChainFieldSlot, braveChainEnum = braveChainEnum, + forceServantPriority = forceServantPriority, forceBraveChain = forceBraveChain, ) } @@ -61,7 +69,7 @@ class ApplyMightyChains @Inject constructor( // In case of a Brave chain, we want to know what slot it is fieldSlotForBraveChain: FieldSlot? = null, braveChainEnum: BraveChainEnum = BraveChainEnum.None, - hasServantPriority: Boolean = false, + forceServantPriority: Boolean = false, forceBraveChain: Boolean = false, ): List? { val cardsToFind = totalUniqueCardTypesPermitted @@ -104,7 +112,7 @@ class ApplyMightyChains @Inject constructor( npUsage: NPUsage, npTypes: Map, braveChainEnum: BraveChainEnum, - hasServantPriority: Boolean, + forceServantPriority: Boolean, forceBraveChain: Boolean, ): Boolean { val uniqueCardTypesFromNp = npTypes.values.toSet() diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 1926d4e47..89935561a 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -1,22 +1,22 @@ package io.github.fate_grand_automata.scripts.modules.attack import io.github.fate_grand_automata.scripts.enums.BraveChainEnum -import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @ScriptScope -class BraveChainHandler @Inject constructor() { +class BraveChainHandler @Inject constructor( + private val utils: Utils +) { fun isBraveChainAllowed ( braveChainEnum: BraveChainEnum, cards: List, npUsage: NPUsage ): Boolean { - val cardsPerFieldSlotMap = getCardsPerFieldSlotMap(cards, npUsage) + val cardsPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) return isBraveChainAllowed(braveChainEnum, cardsPerFieldSlotMap) } @@ -25,10 +25,10 @@ class BraveChainHandler @Inject constructor() { braveChainEnum: BraveChainEnum, npUsage: NPUsage = NPUsage.none, ): List? { - val cardsPerFieldSlotMap = getCardsPerFieldSlotMap(cards, npUsage) + val cardsPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) if (!isBraveChainAllowed(braveChainEnum, cardsPerFieldSlotMap)) return null - val braveChainCapableFieldSlots = getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) + val braveChainCapableFieldSlots = utils.getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) // Use the first valid one, since it is already the highest priority val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } @@ -58,43 +58,4 @@ class BraveChainHandler @Inject constructor() { return highestTotalCardCount >= 3 } - private fun getCardsPerFieldSlotMap ( - cards: List, - npUsage: NPUsage - ): Map { - // Card list check - val cardsPerFieldSlotMap: MutableMap = mutableMapOf() - for (card in cards) { - val fieldSlot = card.fieldSlot - if (fieldSlot == null) continue - val currentValue = cardsPerFieldSlotMap.getOrElse(fieldSlot) { 0 } - cardsPerFieldSlotMap[fieldSlot] = currentValue + 1 - } - // NPs check - // Any more than 1 NP means that it is impossible to Brave Chain - // 0 NPs does not need handling - if (npUsage.nps.size == 1) { - val fieldSlot = npUsage.nps.first().toFieldSlot() - val currentValue = cardsPerFieldSlotMap.getOrElse(fieldSlot) { 0 } - cardsPerFieldSlotMap[fieldSlot] = currentValue + 1 - } - return cardsPerFieldSlotMap.toMap() - } - - fun getFieldSlotsWithValidBraveChain ( - cards: List, - npUsage: NPUsage - ): List { - val cardsPerFieldSlotMap = getCardsPerFieldSlotMap(cards, npUsage) - return getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) - } - - private fun getFieldSlotsWithValidBraveChain ( - cardsPerFieldSlotMap: Map - ): List { - return cardsPerFieldSlotMap.keys - .filter { cardsPerFieldSlotMap.getOrElse(it) { 0 } >= 3 } - .map { it } - } - } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt new file mode 100644 index 000000000..6bb23964c --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt @@ -0,0 +1,50 @@ +package io.github.fate_grand_automata.scripts.modules.attack + +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.toFieldSlot +import io.github.lib_automata.dagger.ScriptScope +import javax.inject.Inject + +@ScriptScope +class Utils @Inject constructor() { + fun getCardsPerFieldSlotMap ( + cards: List, + npUsage: NPUsage = NPUsage.none + ): Map { + // Card list check + val cardsPerFieldSlotMap: MutableMap = mutableMapOf() + for (card in cards) { + val fieldSlot = card.fieldSlot + if (fieldSlot == null) continue + val currentValue = cardsPerFieldSlotMap.getOrElse(fieldSlot) { 0 } + cardsPerFieldSlotMap[fieldSlot] = currentValue + 1 + } + // NPs check + // Any more than 1 NP means that it is impossible to Brave Chain + // 0 NPs does not need handling + if (npUsage.nps.size == 1) { + val fieldSlot = npUsage.nps.first().toFieldSlot() + val currentValue = cardsPerFieldSlotMap.getOrElse(fieldSlot) { 0 } + cardsPerFieldSlotMap[fieldSlot] = currentValue + 1 + } + return cardsPerFieldSlotMap.toMap() + } + + fun getFieldSlotsWithValidBraveChain ( + cards: List, + npUsage: NPUsage = NPUsage.none + ): List { + val cardsPerFieldSlotMap = getCardsPerFieldSlotMap(cards, npUsage) + return getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) + } + + fun getFieldSlotsWithValidBraveChain ( + cardsPerFieldSlotMap: Map + ): List { + return cardsPerFieldSlotMap.keys + .filter { cardsPerFieldSlotMap.getOrElse(it) { 0 } >= 3 } + .map { it } + } +} \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index 08f616778..c8374aec4 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -63,8 +63,8 @@ class AttackLineUps { ) val kiyohime2B = ParsedCard( card = CommandCard.Face.A, - servant = TeamSlot.A, - fieldSlot = FieldSlot.A, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, type = CardTypeEnum.Buster, affinity = CardAffinityEnum.Weak ) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainHandlerTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainHandlerTest.kt new file mode 100644 index 000000000..7a06ceaad --- /dev/null +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainHandlerTest.kt @@ -0,0 +1,92 @@ +package io.github.fate_grand_automata.scripts.attack + +import assertk.assertThat +import assertk.assertions.containsExactly +import assertk.assertions.isEmpty +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.modules.attack.Utils +import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler +import kotlin.test.BeforeTest +import kotlin.test.Test + +class BraveChainHandlerTest { + lateinit var braveChainHandler: BraveChainHandler + + @BeforeTest + fun init() { + braveChainHandler = BraveChainHandler( + utils = Utils() + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), None - No Brave chain`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.None + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), WithNP - No Brave chain`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, WithNP - No Brave chain` () { + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, WithNP`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + )?.map { it.card } ?: emptyList() + + // Expect 1SB,2KQ,3NA,5SQ,4NA - 15324 - AECBD + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), Avoid`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.Avoid + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } + + @Test + fun avoidBraveChainsWith2DiffCards() { + val cards = AttackLineUps.Standard.lineup2 + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.Avoid + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } +} \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt index 776a4f1de..86c546351 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt @@ -11,6 +11,7 @@ import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandler +import io.github.fate_grand_automata.scripts.modules.attack.Utils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import io.github.fate_grand_automata.scripts.modules.attack.CardChainPriorityHandler import kotlin.test.BeforeTest @@ -22,14 +23,13 @@ class MightyChainIntegrationTest { @BeforeTest fun init() { - braveChainHandler = BraveChainHandler() + val utils = Utils() + braveChainHandler = BraveChainHandler(utils) attackPriorityHandler = AttackPriorityHandler( + braveChainHandler = braveChainHandler, cardChainPriorityHandler = CardChainPriorityHandler( - applyMightyChains = ApplyMightyChains( - braveChainHandler = braveChainHandler - ) + applyMightyChains = ApplyMightyChains(utils) ), - braveChainHandler = braveChainHandler ) } @@ -77,7 +77,7 @@ class MightyChainIntegrationTest { } @Test - fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 val picked = attackPriorityHandler.pick( cards = cards, @@ -89,7 +89,7 @@ class MightyChainIntegrationTest { } @Test - fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA), with rearrange`() { + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA), with rearrange`() { val cards = AttackLineUps.Standard.lineup2 val picked = attackPriorityHandler.pick( cards = cards, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 01780e4f5..3566ff3ac 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -3,14 +3,13 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty -import assertk.assertions.isNull import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains -import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler +import io.github.fate_grand_automata.scripts.modules.attack.Utils import kotlin.test.BeforeTest import kotlin.test.Test @@ -20,7 +19,7 @@ class MightyChainTest { @BeforeTest fun init() { mightyChain = ApplyMightyChains( - braveChainHandler = BraveChainHandler() + utils = Utils() ) } @@ -37,7 +36,7 @@ class MightyChainTest { } @Test - fun `Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 val picked = mightyChain.pick( cards = cards diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt new file mode 100644 index 000000000..565e777b3 --- /dev/null +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt @@ -0,0 +1,122 @@ +package io.github.fate_grand_automata.scripts.attack + +import assertk.assertThat +import assertk.assertions.isEqualTo +import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.modules.attack.Utils +import kotlin.test.Test + +class UtilsTest { + val utils = Utils() + + @Test + fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerFieldSlotMap( + cards = cards, + ) + + assertThat(result.size, "Result.size").isEqualTo(3) + assertThat(result[FieldSlot.A], "FieldSlot.A").isEqualTo(1) + assertThat(result[FieldSlot.B], "FieldSlot.B").isEqualTo(2) + assertThat(result[FieldSlot.C], "FieldSlot.C").isEqualTo(2) + } + + @Test + fun `getCardsPerFieldSlotMap, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getCardsPerFieldSlotMap( + cards = cards, + ) + + assertThat(result.size, "Result.size").isEqualTo(3) + assertThat(result[FieldSlot.A], "FieldSlot.A").isEqualTo(1) + assertThat(result[FieldSlot.B], "FieldSlot.B").isEqualTo(2) + assertThat(result[FieldSlot.C], "FieldSlot.C").isEqualTo(2) + } + + @Test + fun `getCardsPerFieldSlotMap, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + val result = utils.getCardsPerFieldSlotMap( + cards = cards, + ) + + assertThat(result.size, "Result.size").isEqualTo(2) + assertThat(result[FieldSlot.B], "FieldSlot.B").isEqualTo(3) + assertThat(result[FieldSlot.C], "FieldSlot.C").isEqualTo(2) + } + + @Test + fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerFieldSlotMap( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + ) + + assertThat(result.size, "Result.size").isEqualTo(3) + assertThat(result[FieldSlot.A], "FieldSlot.A").isEqualTo(2) + assertThat(result[FieldSlot.B], "FieldSlot.B").isEqualTo(2) + assertThat(result[FieldSlot.C], "FieldSlot.C").isEqualTo(2) + } + + @Test + fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerFieldSlotMap( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + ) + + assertThat(result.size, "Result.size").isEqualTo(3) + assertThat(result[FieldSlot.A], "FieldSlot.A").isEqualTo(1) + assertThat(result[FieldSlot.B], "FieldSlot.B").isEqualTo(3) + assertThat(result[FieldSlot.C], "FieldSlot.C").isEqualTo(2) + } + + @Test + fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getFieldSlotsWithValidBraveChain( + cards = cards, + ) + + assertThat(result.size).isEqualTo(0) + } + + @Test + fun `getFieldSlotsWithValidBraveChain, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getFieldSlotsWithValidBraveChain( + cards = cards, + ) + + assertThat(result.size).isEqualTo(0) + } + + @Test + fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getFieldSlotsWithValidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + ) + + assertThat(result.size).isEqualTo(0) + } + + @Test + fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getFieldSlotsWithValidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + ) + + assertThat(result.size, "Result.size").isEqualTo(1) + assertThat(result[0], "FieldSlotB").isEqualTo(FieldSlot.B) + } +} \ No newline at end of file From 2bff158f9ee72de1053ce11c5e8f7089bf6f4fcb Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 2 Sep 2025 02:01:36 +0800 Subject: [PATCH 037/124] Add CardType mapping function in Utils --- .../scripts/modules/attack/Utils.kt | 19 ++++ .../scripts/attack/UtilsTest.kt | 105 ++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt index 6bb23964c..b37092253 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt @@ -1,5 +1,6 @@ package io.github.fate_grand_automata.scripts.modules.attack +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard @@ -31,6 +32,24 @@ class Utils @Inject constructor() { } return cardsPerFieldSlotMap.toMap() } + fun getCardsPerCardTypeMap ( + cards: List, + npTypes: Map = emptyMap(), + ): Map { + // Card list check + val cardsPerCardTypeMap: MutableMap = mutableMapOf() + for (card in cards) { + val cardType = card.type + val currentValue = cardsPerCardTypeMap.getOrElse(cardType) { 0 } + cardsPerCardTypeMap[cardType] = currentValue + 1 + } + // NPs check + for (npType in npTypes.values) { + val currentValue = cardsPerCardTypeMap.getOrElse(npType) { 0 } + cardsPerCardTypeMap[npType] = currentValue + 1 + } + return cardsPerCardTypeMap.toMap() + } fun getFieldSlotsWithValidBraveChain ( cards: List, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt index 565e777b3..2092576c8 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt @@ -2,6 +2,7 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.isEqualTo +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage @@ -119,4 +120,108 @@ class UtilsTest { assertThat(result.size, "Result.size").isEqualTo(1) assertThat(result[0], "FieldSlotB").isEqualTo(FieldSlot.B) } + + @Test + fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerCardTypeMap( + cards = cards, + ) + + assertThat(result.size).isEqualTo(3) + assertThat(result[CardTypeEnum.Arts], "Arts").isEqualTo(2) + assertThat(result[CardTypeEnum.Quick], "Quick").isEqualTo(2) + assertThat(result[CardTypeEnum.Buster], "Buster").isEqualTo(1) + } + + @Test + fun `getCardsPerCardTypeMap, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getCardsPerCardTypeMap( + cards = cards, + ) + + assertThat(result.size).isEqualTo(3) + assertThat(result[CardTypeEnum.Arts], "Arts").isEqualTo(2) + assertThat(result[CardTypeEnum.Quick], "Quick").isEqualTo(2) + assertThat(result[CardTypeEnum.Buster], "Buster").isEqualTo(1) + } + + @Test + fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerCardTypeMap( + cards = cards, + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + ) + + assertThat(result.size).isEqualTo(3) + assertThat(result[CardTypeEnum.Arts], "Arts").isEqualTo(2) + assertThat(result[CardTypeEnum.Quick], "Quick").isEqualTo(3) + assertThat(result[CardTypeEnum.Buster], "Buster").isEqualTo(1) + } + + @Test + fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerCardTypeMap( + cards = cards, + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ) + ) + + assertThat(result.size, "Result.size").isEqualTo(3) + assertThat(result[CardTypeEnum.Arts], "Arts").isEqualTo(2) + assertThat(result[CardTypeEnum.Quick], "Quick").isEqualTo(3) + assertThat(result[CardTypeEnum.Buster], "Buster").isEqualTo(1) + } + + @Test + fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerCardTypeMap( + cards = cards, + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ) + ) + + assertThat(result.size, "Result.size").isEqualTo(3) + assertThat(result[CardTypeEnum.Arts], "Arts").isEqualTo(3) + assertThat(result[CardTypeEnum.Quick], "Quick").isEqualTo(2) + assertThat(result[CardTypeEnum.Buster], "Buster").isEqualTo(1) + } + + @Test + fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerCardTypeMap( + cards = cards, + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + ) + + assertThat(result.size, "Result.size").isEqualTo(3) + assertThat(result[CardTypeEnum.Arts], "Arts").isEqualTo(2) + assertThat(result[CardTypeEnum.Quick], "Quick").isEqualTo(4) + assertThat(result[CardTypeEnum.Buster], "Buster").isEqualTo(1) + } + + @Test + fun `getCardsPerCardTypeMap, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + val result = utils.getCardsPerCardTypeMap( + cards = cards, + ) + + assertThat(result.size, "Result.size").isEqualTo(2) + assertThat(result[CardTypeEnum.Arts], "Arts").isEqualTo(2) + assertThat(result[CardTypeEnum.Quick], "Quick").isEqualTo(null) + assertThat(result[CardTypeEnum.Buster], "Buster").isEqualTo(3) + } } \ No newline at end of file From 1399922bdf0d67f92de2aff6c396a7b38885013d Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 2 Sep 2025 03:16:19 +0800 Subject: [PATCH 038/124] Add class and tests for avoiding chains. --- .../scripts/modules/attack/ApplyAvoidChain.kt | 98 +++++++ .../modules/attack/ApplyMightyChains.kt | 8 +- .../scripts/attack/AvoidChainTest.kt | 272 ++++++++++++++++++ 3 files changed, 374 insertions(+), 4 deletions(-) create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyAvoidChain.kt create mode 100644 scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyAvoidChain.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyAvoidChain.kt new file mode 100644 index 000000000..13dffa54d --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyAvoidChain.kt @@ -0,0 +1,98 @@ +package io.github.fate_grand_automata.scripts.modules.attack + +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.toFieldSlot +import io.github.lib_automata.dagger.ScriptScope +import javax.inject.Inject +import kotlin.collections.set + +@ScriptScope +class ApplyAvoidChain @Inject constructor( + private val utils: Utils +) { + fun pick( + cards: List, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + avoidBraveChains: Boolean = true, + avoidCardChains: Boolean = true, + ): List? { + // If there are already 3 NPs, there is nothing to avoid + if (npUsage.nps.size == 3) return null + // No matter what is returned, it will be valid for sure + if (npUsage.nps.size == 2) { + // No matter what is returned, it will be valid for sure + if (!avoidCardChains) return cards + // Otherwise, need to check if they have the same NP type + } + + // For tracking + val selectedFieldSlots = mutableMapOf() + val selectedCardTypes = mutableMapOf() + + if (avoidBraveChains) { + for (np in npUsage.nps) { + val fieldSlot = np.toFieldSlot() + val currentValue = selectedFieldSlots.getOrElse(fieldSlot) { 0 } + selectedFieldSlots[fieldSlot] = currentValue + 1 + } + } + if (avoidCardChains) { + for (npType in npTypes.values) { + val currentValue = selectedCardTypes.getOrElse(npType) { 0 } + selectedCardTypes[npType] = currentValue + 1 + } + } + + val cardsPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) + val cardsPerCardType = utils.getCardsPerCardTypeMap(cards, npTypes) + + // Otherwise, calculate + val cardsNeeded = 3 - npUsage.nps.size + val selectedCards = mutableListOf() + for (card in cards) { + val fieldSlot = card.fieldSlot + if (fieldSlot == null) continue + val fieldSlotCount = selectedFieldSlots.getOrElse(fieldSlot) { 0 } + if (avoidBraveChains + && ( + // If there are already 2 selected cards with the same card slot + fieldSlotCount >= 2 + // and there are still valid targets, skip + && cardsPerFieldSlotMap.size > 1 + ) + ) continue + + val cardType = card.type + val cardTypeCount = selectedCardTypes.getOrElse(cardType) { 0 } + if (avoidCardChains + // There are at least 2 cards to choose from + && cardsPerCardType.size > 1 + && ( + cardTypeCount >= 2 + // Or if it would make a Mighty Chain + || (cardTypeCount == 0 && selectedCardTypes.size >= 2) + ) + ) continue + + // If reach here, card can be added + selectedCards.add(card) + selectedFieldSlots[fieldSlot] = fieldSlotCount + 1 + selectedCardTypes[cardType] = cardTypeCount + 1 + + // If goal has been reached, end the loop + if (selectedCards.size >= cardsNeeded) break + } + + // Return empty if cannot fulfill + if (selectedCards.size < cardsNeeded) return null + + val remainder = cards - selectedCards + val combinedCards = selectedCards + remainder + + return combinedCards + } +} \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt index 471062866..9052a2bb8 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt @@ -75,7 +75,7 @@ class ApplyMightyChains @Inject constructor( val cardsToFind = totalUniqueCardTypesPermitted val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() - val newList = mutableListOf() + val selectedCards = mutableListOf() while (uniqueCardTypes.size < cardsToFind) { var filteredCards = cards.filter { // Always look for a different card type @@ -94,15 +94,15 @@ class ApplyMightyChains @Inject constructor( val filteredCard = filteredCards.firstOrNull() if (filteredCard == null) break // If cannot find, we leave uniqueCardTypes.add(filteredCard.type) - newList.add(filteredCard) + selectedCards.add(filteredCard) } // If there isn't a valid list of cards, we reject and have empty be returned if (uniqueCardTypes.size < cardsToFind) return null // Otherwise, we return the expected output - val remainder = cards - newList - val combinedCards = newList + remainder + val remainder = cards - selectedCards + val combinedCards = selectedCards + remainder return combinedCards } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt new file mode 100644 index 000000000..eb1215a96 --- /dev/null +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -0,0 +1,272 @@ +package io.github.fate_grand_automata.scripts.attack + +import assertk.assertThat +import assertk.assertions.containsExactly +import assertk.assertions.isEmpty +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import kotlin.test.Test +import io.github.fate_grand_automata.scripts.modules.attack.ApplyAvoidChain +import io.github.fate_grand_automata.scripts.modules.attack.Utils + +class AvoidChainTest { + val avoidChain = ApplyAvoidChain(utils = Utils()) + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + val picked = avoidChain.pick( + cards = cards + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { + val cards = AttackLineUps.SingleServantOnly.lineup1 + val picked = avoidChain.pick( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.C, + CommandCard.Face.E, + ) + } + + @Test + fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { + val cards = AttackLineUps.SingleServantOnly.lineup2 + val picked = avoidChain.pick( + cards = cards + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } + + @Test + fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { + val cards = AttackLineUps.SingleServantOnly.lineup3 + val picked = avoidChain.pick( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.D, + ) + } + + // Scenario for when 2 card types are found but not the 3rd + @Test + fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { + val cards = AttackLineUps.SingleServantOnly.lineup4 + val picked = avoidChain.pick( + cards = cards + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + // Scenario for when 2 card types are found but not the 3rd + @Test + fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { + val cards = AttackLineUps.SingleServantOnly.lineup5 + val picked = avoidChain.pick( + cards = cards + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + /** + * Two NP scenario + */ + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1B, 2B), with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster, + FieldSlot.B to CardTypeEnum.Buster + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts, + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } +} \ No newline at end of file From 3acaab70cdeac99ec880447684173488832894f8 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 2 Sep 2025 03:16:43 +0800 Subject: [PATCH 039/124] Filter out unknown cards, since they will be generally unable to be read or stunned. --- .../scripts/modules/attack/AttackPriorityHandler.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 269814b1a..d93ebe432 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -64,13 +64,19 @@ class AttackPriorityHandler @Inject constructor( rearrange: Boolean = false, hasServantPriority: Boolean = false, ): List { + // All stunned cards are categorized as Unknown. + // Filter all of them since those cards are to be avoided if possible + // and the system does not deal with CardTypeEnum.Unknown + val filteredCards = cards.filter { it.type != CardTypeEnum.Unknown } + val finalFallback = filteredCards + (cards - filteredCards) + var newCardOrder: List? = null var braveChainFallback: List? = null for (attackPriority in attackPriorityOrder) { when (attackPriority) { AttackPriorityEnum.BraveChainPriority -> { braveChainFallback = braveChainHandler.pick( - cards = cards, + cards = filteredCards, braveChainEnum = braveChainEnum, npUsage = npUsage, ) @@ -78,7 +84,7 @@ class AttackPriorityHandler @Inject constructor( AttackPriorityEnum.CardChainPriority -> { if (newCardOrder != null) continue newCardOrder = cardChainPriorityHandler.pick( - cards = cards, + cards = filteredCards, chainPriority = chainPriority, braveChainEnum = braveChainEnum, npUsage = npUsage, @@ -93,7 +99,7 @@ class AttackPriorityHandler @Inject constructor( } return rearrange( - cards = newCardOrder ?: braveChainFallback ?: cards, + cards = newCardOrder ?: braveChainFallback ?: finalFallback, rearrange = rearrange, npUsage = npUsage ) From ba6fc7a75e9f1d197e7a44ba30ce31dd1a9a900a Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 2 Sep 2025 03:33:49 +0800 Subject: [PATCH 040/124] Refactor names of classes --- ...pplyAvoidChain.kt => AvoidChainHandler.kt} | 2 +- .../attack/CardChainPriorityHandler.kt | 4 +- ...yMightyChains.kt => MightyChainHandler.kt} | 2 +- .../scripts/attack/AvoidChainTest.kt | 4 +- ...eChainHandlerTest.kt => BraveChainTest.kt} | 2 +- .../attack/MightyChainIntegrationTest.kt | 4 +- .../scripts/attack/MightyChainTest.kt | 42 +++++++++---------- 7 files changed, 30 insertions(+), 30 deletions(-) rename scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/{ApplyAvoidChain.kt => AvoidChainHandler.kt} (98%) rename scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/{ApplyMightyChains.kt => MightyChainHandler.kt} (99%) rename scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/{BraveChainHandlerTest.kt => BraveChainTest.kt} (99%) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyAvoidChain.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt similarity index 98% rename from scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyAvoidChain.kt rename to scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index 13dffa54d..24680513d 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyAvoidChain.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -10,7 +10,7 @@ import javax.inject.Inject import kotlin.collections.set @ScriptScope -class ApplyAvoidChain @Inject constructor( +class AvoidChainHandler @Inject constructor( private val utils: Utils ) { fun pick( diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 52d8e3d60..c762feb18 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -11,7 +11,7 @@ import javax.inject.Inject @ScriptScope class CardChainPriorityHandler @Inject constructor( - private val applyMightyChains: ApplyMightyChains, + private val mightyChainHandler: MightyChainHandler, ) { fun pick( cards: List, @@ -26,7 +26,7 @@ class CardChainPriorityHandler @Inject constructor( for (chain in chainPriority) { if (newCardOrder != null) continue newCardOrder = when (chain) { - ChainTypeEnum.Mighty -> applyMightyChains.pick( + ChainTypeEnum.Mighty -> mightyChainHandler.pick( cards = cards, npUsage = npUsage, npTypes = npTypes, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt similarity index 99% rename from scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt rename to scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 9052a2bb8..1ac9e25e3 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ApplyMightyChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -11,7 +11,7 @@ import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @ScriptScope -class ApplyMightyChains @Inject constructor( +class MightyChainHandler @Inject constructor( private val utils: Utils ): AttackChainInterface { // We want 3 unique types, the magic number diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index eb1215a96..dc4e7c91e 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -8,11 +8,11 @@ import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import kotlin.test.Test -import io.github.fate_grand_automata.scripts.modules.attack.ApplyAvoidChain +import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.Utils class AvoidChainTest { - val avoidChain = ApplyAvoidChain(utils = Utils()) + val avoidChain = AvoidChainHandler(utils = Utils()) @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainHandlerTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt similarity index 99% rename from scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainHandlerTest.kt rename to scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 7a06ceaad..61b228927 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainHandlerTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -11,7 +11,7 @@ import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import kotlin.test.BeforeTest import kotlin.test.Test -class BraveChainHandlerTest { +class BraveChainTest { lateinit var braveChainHandler: BraveChainHandler @BeforeTest diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt index 86c546351..7955ce03e 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt @@ -9,7 +9,7 @@ import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains +import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandler import io.github.fate_grand_automata.scripts.modules.attack.Utils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler @@ -28,7 +28,7 @@ class MightyChainIntegrationTest { attackPriorityHandler = AttackPriorityHandler( braveChainHandler = braveChainHandler, cardChainPriorityHandler = CardChainPriorityHandler( - applyMightyChains = ApplyMightyChains(utils) + mightyChainHandler = MightyChainHandler(utils) ), ) } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 3566ff3ac..5c365bbd4 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -8,17 +8,17 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage -import io.github.fate_grand_automata.scripts.modules.attack.ApplyMightyChains +import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler import io.github.fate_grand_automata.scripts.modules.attack.Utils import kotlin.test.BeforeTest import kotlin.test.Test class MightyChainTest { - lateinit var mightyChain: ApplyMightyChains + lateinit var mightyChainHandler: MightyChainHandler @BeforeTest fun init() { - mightyChain = ApplyMightyChains( + mightyChainHandler = MightyChainHandler( utils = Utils() ) } @@ -26,7 +26,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP )?.map { it.card } ?: emptyList() @@ -38,7 +38,7 @@ class MightyChainTest { @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -49,7 +49,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) )?.map { it.card } ?: emptyList() @@ -61,7 +61,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -76,7 +76,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) )?.map { it.card } ?: emptyList() @@ -88,7 +88,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( @@ -103,7 +103,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) )?.map { it.card } ?: emptyList() @@ -115,7 +115,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( @@ -130,7 +130,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup1 (BQABQ)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -142,7 +142,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup2 (BBQQA)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -153,7 +153,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup3 (QQABB)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -165,7 +165,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup4 (BQQBQ) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -176,7 +176,7 @@ class MightyChainTest { @Test fun `SingleServantOnly - lineup5 (QQQBB) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -189,7 +189,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) )?.map { it.card } ?: emptyList() @@ -200,7 +200,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), npTypes = mapOf( @@ -216,7 +216,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), )?.map { it.card } ?: emptyList() @@ -227,7 +227,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( @@ -242,7 +242,7 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChain.pick( + val picked = mightyChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( From 29771a424cecfb11699964e6bc19e038f0d579f9 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 2 Sep 2025 22:33:37 +0800 Subject: [PATCH 041/124] Remove AttackChainInterface since all chains have really specific requirements --- .../interfaces/AttackChainInterface.kt | 48 ------------------- .../modules/attack/MightyChainHandler.kt | 19 +++----- 2 files changed, 7 insertions(+), 60 deletions(-) delete mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt deleted file mode 100644 index 0fb244518..000000000 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/interfaces/AttackChainInterface.kt +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.fate_grand_automata.scripts.interfaces - -import io.github.fate_grand_automata.scripts.enums.BraveChainEnum -import io.github.fate_grand_automata.scripts.enums.CardTypeEnum -import io.github.fate_grand_automata.scripts.models.FieldSlot -import io.github.fate_grand_automata.scripts.models.NPUsage -import io.github.fate_grand_automata.scripts.models.ParsedCard - -interface AttackChainInterface { - /** - * Checks if the currently selected Chain is valid - * - * @param cards Cards available for this attack - * @param npUsage NPs in use for this attack - * @param npTypes Card Types of the NP in use for this attack - * @param braveChainEnum Brave chain settings for this attack - * @param forceServantPriority Is Servant Priority in use for this attack - * @param forceBraveChain Allow only Brave Chains to succeed - */ - fun isAttackChainAllowed( - cards: List, - npUsage: NPUsage = NPUsage.Companion.none, - npTypes: Map = emptyMap(), - braveChainEnum: BraveChainEnum = BraveChainEnum.None, - forceServantPriority: Boolean = false, - forceBraveChain: Boolean = false, - ): Boolean - - /** - * Runs the selection of the cards using this Chain. - * Expects to return null if Chain cannot be performed. - * - * @param cards Cards available for this attack - * @param npUsage NPs in use for this attack - * @param npTypes Card Types of the NP in use for this attack - * @param braveChainEnum Brave chain settings for this attack - * @param forceServantPriority Is Servant Priority in use for this attack - * @param forceBraveChain Allow only Brave Chains to succeed - */ - fun pick( - cards: List, - npUsage: NPUsage = NPUsage.Companion.none, - npTypes: Map = emptyMap(), - braveChainEnum: BraveChainEnum = BraveChainEnum.None, - forceServantPriority: Boolean = false, - forceBraveChain: Boolean = false, - ): List? -} \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 1ac9e25e3..156fcbcb8 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -5,7 +5,6 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.fate_grand_automata.scripts.interfaces.AttackChainInterface import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @@ -13,12 +12,12 @@ import javax.inject.Inject @ScriptScope class MightyChainHandler @Inject constructor( private val utils: Utils -): AttackChainInterface { +) { // We want 3 unique types, the magic number val totalUniqueCardTypesPermitted = 3 // Returns null if uniqueCards cannot be found - override fun pick ( + fun pick ( cards: List, npUsage: NPUsage, npTypes: Map, @@ -36,18 +35,14 @@ class MightyChainHandler @Inject constructor( // Get np if there is only 1 (since we want to try for Brave Chain) val firstNp = npUsage.nps.firstOrNull() firstNp?.toFieldSlot() - } else { + } else if (braveChainEnum == BraveChainEnum.Always) { + // Force brave chain only if it always wants a Brave Chain val braveChainCapableFieldSlots = utils.getFieldSlotsWithValidBraveChain(cards, npUsage) - // If there is Servant Priority, the first card is most important. - // Meaning if first card cannot Brave Chain, we ignore Brave Chain - if (forceServantPriority) { - val firstCardFieldSlot = cards.firstOrNull()?.fieldSlot - if (braveChainCapableFieldSlots.contains(firstCardFieldSlot)) firstCardFieldSlot - else null - } val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } // Return the first valid one, since it is already the highest priority braveChainPriorityCard?.fieldSlot + } else { + null } return pick( @@ -107,7 +102,7 @@ class MightyChainHandler @Inject constructor( return combinedCards } - override fun isAttackChainAllowed ( + fun isMightyChainAllowed ( cards: List, npUsage: NPUsage, npTypes: Map, From ba12b90cfe5b023138db33867b8d9ab4799a3947 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 2 Sep 2025 23:11:48 +0800 Subject: [PATCH 042/124] Add Always for Brave Chain. Refactor to pass the heavier utility mapping data from parent. --- .../util/LocalizationExtensions.kt | 1 + app/src/main/res/values-b+zh+CN/localized.xml | 1 + app/src/main/res/values-b+zh+TW/localized.xml | 1 + app/src/main/res/values-ja/localized.xml | 1 + app/src/main/res/values-ko/localized.xml | 1 + app/src/main/res/values-vi/localized.xml | 1 + app/src/main/res/values/localized.xml | 1 + .../scripts/enums/BraveChainEnum.kt | 1 + .../scripts/modules/ApplyBraveChains.kt | 1 + .../modules/attack/AttackPriorityHandler.kt | 6 +++ .../modules/attack/BraveChainHandler.kt | 13 ++--- .../attack/CardChainPriorityHandler.kt | 6 ++- .../modules/attack/MightyChainHandler.kt | 47 ++++++++++--------- .../attack/MightyChainIntegrationTest.kt | 1 + 14 files changed, 48 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt index d9813a63b..f7a9aaf95 100644 --- a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt +++ b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt @@ -26,6 +26,7 @@ val BraveChainEnum.stringRes get() = when (this) { BraveChainEnum.None -> R.string.p_brave_chains_don_t_care BraveChainEnum.WithNP -> R.string.p_brave_chains_with_np + BraveChainEnum.Always -> R.string.p_brave_chains_don_t_care BraveChainEnum.Avoid -> R.string.p_brave_chains_avoid } diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index 4021df7cc..b2aa9a3b5 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -188,6 +188,7 @@ Order Change" "圣晶石" "无偏好" "宝具施放后" + "始终追求链接" "避免追击连携" "迅击" "技击" diff --git a/app/src/main/res/values-b+zh+TW/localized.xml b/app/src/main/res/values-b+zh+TW/localized.xml index 368f9d724..2ccbaeb5d 100644 --- a/app/src/main/res/values-b+zh+TW/localized.xml +++ b/app/src/main/res/values-b+zh+TW/localized.xml @@ -186,6 +186,7 @@ Order Change" "聖晶石" "無偏好" "寶具施放後" + "始终Brave Chain" "避免Brave Chain" "Quick" "Arts" diff --git a/app/src/main/res/values-ja/localized.xml b/app/src/main/res/values-ja/localized.xml index 0ec12896b..a642fd6a8 100644 --- a/app/src/main/res/values-ja/localized.xml +++ b/app/src/main/res/values-ja/localized.xml @@ -190,6 +190,7 @@ "聖晶石" "考慮しない" "宝具と同時に" + "いつも使用" "使用しない" "Quick" "Arts" diff --git a/app/src/main/res/values-ko/localized.xml b/app/src/main/res/values-ko/localized.xml index a1993d710..f18f30447 100644 --- a/app/src/main/res/values-ko/localized.xml +++ b/app/src/main/res/values-ko/localized.xml @@ -185,6 +185,7 @@ "성정석" "상관 없음" "보구랑 같이" + "항상" "자제" "Quick" "Arts" diff --git a/app/src/main/res/values-vi/localized.xml b/app/src/main/res/values-vi/localized.xml index 5d5ada829..6ebd34707 100644 --- a/app/src/main/res/values-vi/localized.xml +++ b/app/src/main/res/values-vi/localized.xml @@ -186,6 +186,7 @@ Sao chép hoặc đổi tên nó nếu bạn muốn giữ nó." "SQ" "không quan tâm" "Với NP" + "Luôn luôn" "Tránh" "Quick" "Arts" diff --git a/app/src/main/res/values/localized.xml b/app/src/main/res/values/localized.xml index dc5fcfe7b..181798fe7 100644 --- a/app/src/main/res/values/localized.xml +++ b/app/src/main/res/values/localized.xml @@ -189,6 +189,7 @@ Copy or rename it if you want to keep it." "SQ" "Don't care" "With NP" + "Always" "Avoid" "Quick" "Arts" diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt index 4b378bb3a..7f6ed749e 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/BraveChainEnum.kt @@ -3,5 +3,6 @@ package io.github.fate_grand_automata.scripts.enums enum class BraveChainEnum { None, WithNP, + Always, Avoid } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 1d17d59a9..d55bc4521 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -173,6 +173,7 @@ class ApplyBraveChains @Inject constructor() { npUsage = npUsage ) + BraveChainEnum.Always, BraveChainEnum.WithNP -> withNp( cards = cards, rearrange = rearrange, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index d93ebe432..8380d5bbe 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -15,6 +15,7 @@ import javax.inject.Inject class AttackPriorityHandler @Inject constructor( private val cardChainPriorityHandler: CardChainPriorityHandler, private val braveChainHandler: BraveChainHandler, + private val utils: Utils, ) { fun rearrange( cards: List, @@ -70,6 +71,10 @@ class AttackPriorityHandler @Inject constructor( val filteredCards = cards.filter { it.type != CardTypeEnum.Unknown } val finalFallback = filteredCards + (cards - filteredCards) + // Get all the supplementary data + val cardCountPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) + val cardCountPerCardTypeMap = utils.getCardsPerCardTypeMap(cards, npTypes) + var newCardOrder: List? = null var braveChainFallback: List? = null for (attackPriority in attackPriorityOrder) { @@ -79,6 +84,7 @@ class AttackPriorityHandler @Inject constructor( cards = filteredCards, braveChainEnum = braveChainEnum, npUsage = npUsage, + cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) } AttackPriorityEnum.CardChainPriority -> { diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 89935561a..94a20f40d 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -1,6 +1,7 @@ package io.github.fate_grand_automata.scripts.modules.attack import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard @@ -11,21 +12,13 @@ import javax.inject.Inject class BraveChainHandler @Inject constructor( private val utils: Utils ) { - fun isBraveChainAllowed ( - braveChainEnum: BraveChainEnum, - cards: List, - npUsage: NPUsage - ): Boolean { - val cardsPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) - return isBraveChainAllowed(braveChainEnum, cardsPerFieldSlotMap) - } - fun pick( cards: List, braveChainEnum: BraveChainEnum, npUsage: NPUsage = NPUsage.none, + cardCountPerFieldSlotMap: Map? = null, ): List? { - val cardsPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) + val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) if (!isBraveChainAllowed(braveChainEnum, cardsPerFieldSlotMap)) return null val braveChainCapableFieldSlots = utils.getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index c762feb18..98fc05c07 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -21,6 +21,8 @@ class CardChainPriorityHandler @Inject constructor( npTypes: Map = emptyMap(), hasServantPriority: Boolean = false, forceBraveChain: Boolean = false, + cardCountPerFieldSlotMap: Map? = null, + cardCountPerCardTypeMap: Map? = null, ): List? { var newCardOrder: List? = null for (chain in chainPriority) { @@ -30,7 +32,9 @@ class CardChainPriorityHandler @Inject constructor( cards = cards, npUsage = npUsage, npTypes = npTypes, - forceBraveChain = forceBraveChain + braveChainEnum = braveChainEnum, + forceBraveChain = forceBraveChain, + cardCountPerCardTypeMap = cardCountPerCardTypeMap, ) else -> null } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 156fcbcb8..f3e9c2e78 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -8,6 +8,7 @@ import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject +import kotlin.collections.Map @ScriptScope class MightyChainHandler @Inject constructor( @@ -19,14 +20,21 @@ class MightyChainHandler @Inject constructor( // Returns null if uniqueCards cannot be found fun pick ( cards: List, - npUsage: NPUsage, - npTypes: Map, - braveChainEnum: BraveChainEnum, - forceServantPriority: Boolean, - forceBraveChain: Boolean, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + braveChainEnum: BraveChainEnum = BraveChainEnum.None, + cardCountPerCardTypeMap: Map? = null, + forceBraveChain: Boolean = false, ): List? { val uniqueCardTypesFromNp = npTypes.values.toSet() - if (!isMightyChainAllowed(npUsage, uniqueCardTypesFromNp, npTypes)) return null + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + if (!isMightyChainAllowed( + cards = cards, + npUsage = npUsage, + uniqueCardTypesFromNp = uniqueCardTypesFromNp, + npTypes = npTypes, + cardCountPerCardTypeMap = cardCountPerCardTypeMap + )) return null // Check for Brave Chain val braveChainFieldSlot = @@ -50,7 +58,6 @@ class MightyChainHandler @Inject constructor( uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, fieldSlotForBraveChain = braveChainFieldSlot, braveChainEnum = braveChainEnum, - forceServantPriority = forceServantPriority, forceBraveChain = forceBraveChain, ) } @@ -64,7 +71,6 @@ class MightyChainHandler @Inject constructor( // In case of a Brave chain, we want to know what slot it is fieldSlotForBraveChain: FieldSlot? = null, braveChainEnum: BraveChainEnum = BraveChainEnum.None, - forceServantPriority: Boolean = false, forceBraveChain: Boolean = false, ): List? { val cardsToFind = totalUniqueCardTypesPermitted @@ -77,7 +83,7 @@ class MightyChainHandler @Inject constructor( it.type !in uniqueCardTypes } // If there is a single NP, we want to try for a Brave Mighty Chain - if (fieldSlotForBraveChain != null) { + if (fieldSlotForBraveChain != null && braveChainEnum != BraveChainEnum.Avoid) { // Attempt to find one matching the fieldSlot val fieldSlotList = filteredCards.filter { it.fieldSlot == fieldSlotForBraveChain @@ -105,27 +111,22 @@ class MightyChainHandler @Inject constructor( fun isMightyChainAllowed ( cards: List, npUsage: NPUsage, - npTypes: Map, - braveChainEnum: BraveChainEnum, - forceServantPriority: Boolean, - forceBraveChain: Boolean, - ): Boolean { - val uniqueCardTypesFromNp = npTypes.values.toSet() - return isMightyChainAllowed(npUsage, uniqueCardTypesFromNp, npTypes) - } - - fun isMightyChainAllowed ( - npUsage: NPUsage, - uniqueCardTypesFromNp: Set, - npTypes: Map = emptyMap() + npTypes: Map = emptyMap(), + uniqueCardTypesFromNp: Set? = null, + cardCountPerCardTypeMap: Map? = null, ): Boolean { + val uniqueCardTypesFromNp = uniqueCardTypesFromNp ?: npTypes.values.toSet() + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) val npUsageSize = npUsage.nps.size // if there are more than 1 NP and we don't know their types, // just do the default since we can't guarantee a mighty chain anyway if (npUsageSize > 0 && npTypes.size != npUsageSize) return false - // We stop if + // If we do not have 3 unique cards at least, it is impossible to Mighty Chain + if (cardCountPerCardTypeMap.size < 3) return false + + // Success if return ( // number of nps is 1 npUsageSize == 1 diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt index 7955ce03e..130eb4245 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt @@ -27,6 +27,7 @@ class MightyChainIntegrationTest { braveChainHandler = BraveChainHandler(utils) attackPriorityHandler = AttackPriorityHandler( braveChainHandler = braveChainHandler, + utils = utils, cardChainPriorityHandler = CardChainPriorityHandler( mightyChainHandler = MightyChainHandler(utils) ), From 05a177b9d98236200df9ab8e5816c5f59a1b715b Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 01:19:40 +0800 Subject: [PATCH 043/124] Add ColorChain handling. Make sure logic falls to BraveChain if Avoid is not the first option. --- .../scripts/modules/Card.kt | 1 - .../modules/attack/AttackPriorityHandler.kt | 13 +- .../modules/attack/AvoidChainHandler.kt | 10 +- .../attack/CardChainPriorityHandler.kt | 33 +- .../modules/attack/ColorChainHandler.kt | 76 +++ .../modules/attack/MightyChainHandler.kt | 30 +- .../scripts/modules/attack/Utils.kt | 31 + .../scripts/attack/AttackLineUps.kt | 2 +- ...kt => CardChainPriorityIntegrationTest.kt} | 203 +++++- .../scripts/attack/ColorChainTest.kt | 607 ++++++++++++++++++ 10 files changed, 962 insertions(+), 44 deletions(-) create mode 100644 scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt rename scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/{MightyChainIntegrationTest.kt => CardChainPriorityIntegrationTest.kt} (75%) create mode 100644 scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index 5f4e3b3d4..b5a4c7709 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -73,7 +73,6 @@ class Card @Inject constructor( chainPriority = chainPriority, rearrange = rearrangeCardsPerWave.inCurrentWave(false), npTypes = npTypes, - hasServantPriority = battleConfig.useServantPriority ).map { it.card } } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 8380d5bbe..3ab0473d6 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -63,7 +63,6 @@ class AttackPriorityHandler @Inject constructor( npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), rearrange: Boolean = false, - hasServantPriority: Boolean = false, ): List { // All stunned cards are categorized as Unknown. // Filter all of them since those cards are to be avoided if possible @@ -75,6 +74,11 @@ class AttackPriorityHandler @Inject constructor( val cardCountPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) val cardCountPerCardTypeMap = utils.getCardsPerCardTypeMap(cards, npTypes) + // Determine the type of chain priority + val indexOfAvoid = chainPriority.indexOf(ChainTypeEnum.Avoid) + val filteredChainPriority = if (indexOfAvoid == 0) listOf(ChainTypeEnum.Avoid) else chainPriority.subList(0, indexOfAvoid) + + // Start var newCardOrder: List? = null var braveChainFallback: List? = null for (attackPriority in attackPriorityOrder) { @@ -91,13 +95,14 @@ class AttackPriorityHandler @Inject constructor( if (newCardOrder != null) continue newCardOrder = cardChainPriorityHandler.pick( cards = filteredCards, - chainPriority = chainPriority, + chainPriority = filteredChainPriority, braveChainEnum = braveChainEnum, npUsage = npUsage, npTypes = npTypes, - hasServantPriority = hasServantPriority, // BraveChain is higher priority than color chain - forceBraveChain = braveChainFallback != null + forceBraveChain = braveChainFallback != null, + cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, + cardCountPerCardTypeMap = cardCountPerCardTypeMap, ) } else -> continue diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index 24680513d..f3bc4f276 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -19,6 +19,8 @@ class AvoidChainHandler @Inject constructor( npTypes: Map = emptyMap(), avoidBraveChains: Boolean = true, avoidCardChains: Boolean = true, + cardCountPerFieldSlotMap: Map? = null, + cardCountPerCardTypeMap: Map? = null, ): List? { // If there are already 3 NPs, there is nothing to avoid if (npUsage.nps.size == 3) return null @@ -47,8 +49,8 @@ class AvoidChainHandler @Inject constructor( } } - val cardsPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) - val cardsPerCardType = utils.getCardsPerCardTypeMap(cards, npTypes) + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) // Otherwise, calculate val cardsNeeded = 3 - npUsage.nps.size @@ -62,7 +64,7 @@ class AvoidChainHandler @Inject constructor( // If there are already 2 selected cards with the same card slot fieldSlotCount >= 2 // and there are still valid targets, skip - && cardsPerFieldSlotMap.size > 1 + && cardCountPerFieldSlotMap.size > 1 ) ) continue @@ -70,7 +72,7 @@ class AvoidChainHandler @Inject constructor( val cardTypeCount = selectedCardTypes.getOrElse(cardType) { 0 } if (avoidCardChains // There are at least 2 cards to choose from - && cardsPerCardType.size > 1 + && cardCountPerCardTypeMap.size > 1 && ( cardTypeCount >= 2 // Or if it would make a Mighty Chain diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 98fc05c07..6877b7ab0 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -12,6 +12,9 @@ import javax.inject.Inject @ScriptScope class CardChainPriorityHandler @Inject constructor( private val mightyChainHandler: MightyChainHandler, + private val colorChainHandler: ColorChainHandler, + private val avoidChainHandler: AvoidChainHandler, + private val utils: Utils ) { fun pick( cards: List, @@ -19,12 +22,14 @@ class CardChainPriorityHandler @Inject constructor( braveChainEnum: BraveChainEnum = BraveChainEnum.None, npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), - hasServantPriority: Boolean = false, forceBraveChain: Boolean = false, cardCountPerFieldSlotMap: Map? = null, cardCountPerCardTypeMap: Map? = null, ): List? { var newCardOrder: List? = null + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + for (chain in chainPriority) { if (newCardOrder != null) continue newCardOrder = when (chain) { @@ -36,7 +41,31 @@ class CardChainPriorityHandler @Inject constructor( forceBraveChain = forceBraveChain, cardCountPerCardTypeMap = cardCountPerCardTypeMap, ) - else -> null + ChainTypeEnum.Arts, + ChainTypeEnum.Buster, + ChainTypeEnum.Quick -> colorChainHandler.pick( + cardType = when (chain) { + ChainTypeEnum.Buster -> CardTypeEnum.Buster + ChainTypeEnum.Arts -> CardTypeEnum.Arts + ChainTypeEnum.Quick -> CardTypeEnum.Quick + else -> CardTypeEnum.Unknown + }, + cards = cards, + npUsage = npUsage, + npTypes = npTypes, + braveChainEnum = braveChainEnum, + forceBraveChain = forceBraveChain, + cardCountPerCardTypeMap = cardCountPerCardTypeMap, + ) + ChainTypeEnum.Avoid -> avoidChainHandler.pick( + cards = cards, + npUsage = npUsage, + npTypes = npTypes, + avoidBraveChains = braveChainEnum == BraveChainEnum.Avoid, + avoidCardChains = true, + cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, + cardCountPerCardTypeMap = cardCountPerCardTypeMap, + ) } } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt new file mode 100644 index 000000000..60d792ec7 --- /dev/null +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -0,0 +1,76 @@ +package io.github.fate_grand_automata.scripts.modules.attack + +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.lib_automata.dagger.ScriptScope +import javax.inject.Inject + +@ScriptScope +open class ColorChainHandler @Inject constructor( + private val utils: Utils +) { + // Returns null if uniqueCards cannot be found + fun pick ( + cardType: CardTypeEnum, + cards: List, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + braveChainEnum: BraveChainEnum = BraveChainEnum.None, + forceBraveChain: Boolean = false, + cardCountPerCardTypeMap: Map? = null + ): List? { + // NEVER want to make a chain of Unknown cards + if (cardType == CardTypeEnum.Unknown) return null + + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + if (!isColorChainAllowed(cardType, cards, npTypes, cardCountPerCardTypeMap)) return null + + // Check for Brave Chain + val braveChainFieldSlot = utils.getBraveChainFieldSlot( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = npUsage, + ) + + // if it passes the above, the actual implementation is very easy + val cardsNeeded = 3 - npUsage.nps.size + var selectedCards = cards.filter { it.type == cardType } + + // If there is a single NP, we want to try for a Brave Mighty Chain + if (braveChainFieldSlot != null && braveChainEnum != BraveChainEnum.Avoid) { + // Attempt to find one matching the fieldSlot + val fieldSlotList = selectedCards.filter { + it.fieldSlot == braveChainFieldSlot + } + // If there is a valid number of cards for the fieldSlot, attempt to Brave Chain + // Even if it is not valid, if forceBraveChain is on, + // it only accepts Brave Color Chains and not normal Color Chains + if (fieldSlotList.size >= cardsNeeded || forceBraveChain) selectedCards = fieldSlotList + } + + if (selectedCards.size < cardsNeeded) return null + val combinedCards = selectedCards + (cards - selectedCards) + return combinedCards + } + + fun isColorChainAllowed ( + cardType: CardTypeEnum, + cards: List, + npTypes: Map = emptyMap(), + cardCountPerCardTypeMap: Map? = null + ): Boolean { + val uniqueCardTypesFromNp = npTypes.values.toSet() + // Impossible if the unique number of card types are not 1 + if (uniqueCardTypesFromNp.size > 1) return false + // Impossible if the nps are not of the chain type + if (uniqueCardTypesFromNp.size == 1 && uniqueCardTypesFromNp.firstOrNull() != cardType) return false + + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + + // Ensure there are at least 3 cards of the type + return cardCountPerCardTypeMap.getOrElse(cardType) { 0 } >= 3 + } +} \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index f3e9c2e78..4c7127e4c 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -5,7 +5,6 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject import kotlin.collections.Map @@ -37,26 +36,17 @@ class MightyChainHandler @Inject constructor( )) return null // Check for Brave Chain - val braveChainFieldSlot = - if (braveChainEnum == BraveChainEnum.Avoid) null - else if (npUsage.nps.size == 1) { - // Get np if there is only 1 (since we want to try for Brave Chain) - val firstNp = npUsage.nps.firstOrNull() - firstNp?.toFieldSlot() - } else if (braveChainEnum == BraveChainEnum.Always) { - // Force brave chain only if it always wants a Brave Chain - val braveChainCapableFieldSlots = utils.getFieldSlotsWithValidBraveChain(cards, npUsage) - val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } - // Return the first valid one, since it is already the highest priority - braveChainPriorityCard?.fieldSlot - } else { - null - } + val braveChainFieldSlot = utils.getBraveChainFieldSlot( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = npUsage, + forceBraveChain = forceBraveChain, + ) return pick( cards = cards, uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, - fieldSlotForBraveChain = braveChainFieldSlot, + braveChainFieldSlot = braveChainFieldSlot, braveChainEnum = braveChainEnum, forceBraveChain = forceBraveChain, ) @@ -69,7 +59,7 @@ class MightyChainHandler @Inject constructor( // e.g. usually based on npSize uniqueCardTypesAlreadyFilled: Set, // In case of a Brave chain, we want to know what slot it is - fieldSlotForBraveChain: FieldSlot? = null, + braveChainFieldSlot: FieldSlot? = null, braveChainEnum: BraveChainEnum = BraveChainEnum.None, forceBraveChain: Boolean = false, ): List? { @@ -83,10 +73,10 @@ class MightyChainHandler @Inject constructor( it.type !in uniqueCardTypes } // If there is a single NP, we want to try for a Brave Mighty Chain - if (fieldSlotForBraveChain != null && braveChainEnum != BraveChainEnum.Avoid) { + if (braveChainFieldSlot != null && braveChainEnum != BraveChainEnum.Avoid) { // Attempt to find one matching the fieldSlot val fieldSlotList = filteredCards.filter { - it.fieldSlot == fieldSlotForBraveChain + it.fieldSlot == braveChainFieldSlot } // Even if it is empty, if forceBraveChain is on, // it only accepts Brave Mighty Chains and not normal Mighty Chains diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt index b37092253..ea1e5af78 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt @@ -1,6 +1,8 @@ package io.github.fate_grand_automata.scripts.modules.attack +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard @@ -66,4 +68,33 @@ class Utils @Inject constructor() { .filter { cardsPerFieldSlotMap.getOrElse(it) { 0 } >= 3 } .map { it } } + + fun getBraveChainFieldSlot ( + cards: List, + braveChainEnum: BraveChainEnum = BraveChainEnum.None, + npUsage: NPUsage = NPUsage.none, + forceBraveChain: Boolean = false, + ): FieldSlot? { + return if (braveChainEnum == BraveChainEnum.Avoid) null + else if (npUsage.nps.size == 1) { + // Get np if there is only 1 (since we want to try for Brave Chain) + val firstNp = npUsage.nps.firstOrNull() + firstNp?.toFieldSlot() + } else if (braveChainEnum == BraveChainEnum.Always || forceBraveChain) { + // Force brave chain only if it always wants a Brave Chain + val braveChainCapableFieldSlots = getFieldSlotsWithValidBraveChain(cards, npUsage) + val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } + // Return the first valid one, since it is already the highest priority + braveChainPriorityCard?.fieldSlot + } else { + null + } + } + + fun List.isAfterAvoid(chainTypeEnum: ChainTypeEnum, indexOfAvoid: Int? = null): Boolean { + val avoidIndex = indexOfAvoid ?: this.indexOf(ChainTypeEnum.Avoid) + val enumIndex = this.indexOf(chainTypeEnum) + if (avoidIndex < 0) return false + return enumIndex > avoidIndex + } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index c8374aec4..c709fc0ab 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -62,7 +62,7 @@ class AttackLineUps { affinity = CardAffinityEnum.Weak ) val kiyohime2B = ParsedCard( - card = CommandCard.Face.A, + card = CommandCard.Face.B, servant = TeamSlot.B, fieldSlot = FieldSlot.B, type = CardTypeEnum.Buster, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt similarity index 75% rename from scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt rename to scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 130eb4245..a03496e79 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -11,25 +11,38 @@ import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandler +import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.Utils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import io.github.fate_grand_automata.scripts.modules.attack.CardChainPriorityHandler +import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler import kotlin.test.BeforeTest import kotlin.test.Test -class MightyChainIntegrationTest { - lateinit var braveChainHandler: BraveChainHandler +class CardChainPriorityIntegrationTest { lateinit var attackPriorityHandler: AttackPriorityHandler + lateinit var braveChainHandler: BraveChainHandler + lateinit var mightyChainHandler: MightyChainHandler + lateinit var colorChainHandler: ColorChainHandler + lateinit var avoidChainHandler: AvoidChainHandler + @BeforeTest fun init() { val utils = Utils() braveChainHandler = BraveChainHandler(utils) + mightyChainHandler = MightyChainHandler(utils) + colorChainHandler = ColorChainHandler(utils) + avoidChainHandler = AvoidChainHandler(utils) + attackPriorityHandler = AttackPriorityHandler( braveChainHandler = braveChainHandler, utils = utils, cardChainPriorityHandler = CardChainPriorityHandler( - mightyChainHandler = MightyChainHandler(utils) + mightyChainHandler = mightyChainHandler, + colorChainHandler = colorChainHandler, + avoidChainHandler = avoidChainHandler, + utils = utils, ), ) } @@ -40,13 +53,129 @@ class MightyChainIntegrationTest { rearrange: Boolean = false, npUsage: NPUsage = NPUsage.none, ): List { - val braveChainCards = braveChainHandler.pick( + val results = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = npUsage, + ) ?: cards + return attackPriorityHandler.rearrange( + cards = results, + rearrange = rearrange, + npUsage = npUsage, + ) + } + + fun getDefaultMightyChainResult ( + cards: List, + braveChainEnum: BraveChainEnum, + rearrange: Boolean = false, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + ): List { + val results = mightyChainHandler.pick( + cards = cards, + npUsage = npUsage, + npTypes = npTypes, + braveChainEnum = braveChainEnum, + forceBraveChain = braveChainEnum == BraveChainEnum.Always, + ) ?: cards + return attackPriorityHandler.rearrange( + cards = results, + rearrange = rearrange, + npUsage = npUsage, + ) + } + + fun getDefaultColorChainResult ( + cardType: CardTypeEnum, + cards: List, + braveChainEnum: BraveChainEnum, + rearrange: Boolean = false, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + ): List { + val results = colorChainHandler.pick( + cardType = cardType, + cards = cards, + npUsage = npUsage, + npTypes = npTypes, + braveChainEnum = braveChainEnum, + forceBraveChain = braveChainEnum == BraveChainEnum.Always, + ) ?: cards + return attackPriorityHandler.rearrange( + cards = results, + rearrange = rearrange, + npUsage = npUsage, + ) + } + + fun getDefaultBusterChainResult ( + cards: List, + braveChainEnum: BraveChainEnum, + rearrange: Boolean = false, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + ): List { + return getDefaultColorChainResult( + cardType = CardTypeEnum.Buster, + cards = cards, + npUsage = npUsage, + npTypes = npTypes, + braveChainEnum = braveChainEnum, + rearrange = rearrange, + ) + } + + fun getDefaultArtsChainResult ( + cards: List, + braveChainEnum: BraveChainEnum, + rearrange: Boolean = false, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + ): List { + return getDefaultColorChainResult( + cardType = CardTypeEnum.Arts, + cards = cards, + npUsage = npUsage, + npTypes = npTypes, + braveChainEnum = braveChainEnum, + rearrange = rearrange, + ) + } + + fun getDefaultQuickChainResult ( + cards: List, + braveChainEnum: BraveChainEnum, + rearrange: Boolean = false, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + ): List { + return getDefaultColorChainResult( + cardType = CardTypeEnum.Quick, cards = cards, + npUsage = npUsage, + npTypes = npTypes, braveChainEnum = braveChainEnum, + rearrange = rearrange, + ) + } + + fun getDefaultAvoidChainResult ( + cards: List, + braveChainEnum: BraveChainEnum, + rearrange: Boolean = false, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + ): List { + val results = avoidChainHandler.pick( + cards = cards, npUsage = npUsage, + npTypes = npTypes, + avoidBraveChains = braveChainEnum == BraveChainEnum.Avoid, + avoidCardChains = true, ) ?: cards return attackPriorityHandler.rearrange( - cards = braveChainCards, + cards = results, rearrange = rearrange, npUsage = npUsage, ) @@ -211,7 +340,28 @@ class MightyChainIntegrationTest { } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes - No Mighty chain`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = attackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ) + ).map { it.card } + val pickedDefault = getDefaultBraveChainResult( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + ).map { it.card } + + // No mighty chain. Expect NP Brave Chain output + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes, raw`() { val cards = AttackLineUps.Standard.lineup1 val picked = attackPriorityHandler.pick( cards = cards, @@ -239,6 +389,29 @@ class MightyChainIntegrationTest { ), rearrange = true ).map { it.card } + val pickedDefault = getDefaultBraveChainResult( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + rearrange = true + ).map { it.card } + + // Same as above but swap position 2 and 3) + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes & rearrange, raw`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = attackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + rearrange = true + ).map { it.card } // Same as above but swap position 2 and 3) assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) @@ -383,13 +556,13 @@ class MightyChainIntegrationTest { // Scenario for when 2 card types are found but not the 3rd @Test - fun `SingleServantOnly - lineup4 (BQQBQ) - No mighty chain`() { + fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup4 val picked = attackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } - val pickedDefault = getDefaultBraveChainResult( + val pickedDefault = getDefaultQuickChainResult( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -399,13 +572,13 @@ class MightyChainIntegrationTest { // Scenario for when 2 card types are found but not the 3rd @Test - fun `SingleServantOnly - lineup5 (QQQBB) - No mighty chain`() { + fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup5 val picked = attackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } - val pickedDefault = getDefaultBraveChainResult( + val pickedDefault = getDefaultQuickChainResult( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -481,10 +654,16 @@ class MightyChainIntegrationTest { FieldSlot.B to CardTypeEnum.Quick ) ).map { it.card } - val pickedDefault = getDefaultBraveChainResult( + + // Fall back to Quick chain + val pickedDefault = getDefaultQuickChainResult( cards = cards, braveChainEnum = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) ).map { it.card } // Should fall back to default behaviour diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt new file mode 100644 index 000000000..2d392285e --- /dev/null +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -0,0 +1,607 @@ +package io.github.fate_grand_automata.scripts.attack + +import assertk.assertThat +import assertk.assertions.containsExactly +import assertk.assertions.isEmpty +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot +import io.github.fate_grand_automata.scripts.models.NPUsage +import kotlin.test.Test +import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler +import io.github.fate_grand_automata.scripts.modules.attack.Utils + +class ColorChainTest { + val colorChain = ColorChainHandler(utils = Utils()) + val cardTypeList = CardTypeEnum.entries + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes & forceBraveChain`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + forceBraveChain = true + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes & forceBraveChain`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { + val cards = AttackLineUps.SingleServantOnly.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { + val cards = AttackLineUps.SingleServantOnly.lineup2 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { + val cards = AttackLineUps.SingleServantOnly.lineup3 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { + val cards = AttackLineUps.SingleServantOnly.lineup4 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.D, + ) + } + + // Scenario for when 2 card types are found but not the 3rd + @Test + fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { + val cards = AttackLineUps.SingleServantOnly.lineup5 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.D, + ) + } + + /** + * Two NP scenario + */ + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1B, 2B), with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster, + FieldSlot.B to CardTypeEnum.Buster + ) + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts, + ) + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (2Kiyohime), with npTypes`() { + val cards = AttackLineUps.BusterFocus.lineup01 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Buster, + ) + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } + + @Test + fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (3Nero), with npTypes`() { + val cards = AttackLineUps.BusterFocus.lineup01 + val values = cardTypeList.map { type -> + colorChain.pick( + cards = cards, + cardType = type, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts, + ) + )?.map { it.card } ?: emptyList() + } + val results = cardTypeList.zip(values).toMap() + + assertThat( + results.getOrElse(CardTypeEnum.Buster) { emptyList() }, + "Buster" + ).isEmpty() + assertThat( + results.getOrElse(CardTypeEnum.Arts) { emptyList() }, + "Arts" + ).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + assertThat( + results.getOrElse(CardTypeEnum.Quick) { emptyList() }, + "Quick" + ).isEmpty() + } +} \ No newline at end of file From 8e3ee9c4549372c431a8a00485715b1927d71e15 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 01:36:55 +0800 Subject: [PATCH 044/124] Add handling for how to treat 'Avoid' --- .../modules/attack/AttackPriorityHandler.kt | 16 ++++++---- .../attack/CardChainPriorityHandler.kt | 2 +- .../CardChainPriorityIntegrationTest.kt | 30 +++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 3ab0473d6..c5fb77acd 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -74,10 +74,6 @@ class AttackPriorityHandler @Inject constructor( val cardCountPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) val cardCountPerCardTypeMap = utils.getCardsPerCardTypeMap(cards, npTypes) - // Determine the type of chain priority - val indexOfAvoid = chainPriority.indexOf(ChainTypeEnum.Avoid) - val filteredChainPriority = if (indexOfAvoid == 0) listOf(ChainTypeEnum.Avoid) else chainPriority.subList(0, indexOfAvoid) - // Start var newCardOrder: List? = null var braveChainFallback: List? = null @@ -93,16 +89,24 @@ class AttackPriorityHandler @Inject constructor( } AttackPriorityEnum.CardChainPriority -> { if (newCardOrder != null) continue + + // Determine the allowed chain priority + val indexOfAvoid = chainPriority.indexOf(ChainTypeEnum.Avoid) + // If braveChainFallback does not exist, + // 'Avoid' is to be included, since it is treated as cardPriorityHandler's own fallback method + val allowAvoid = if (braveChainFallback == null) 1 else 0 + val filteredChainPriority = if (indexOfAvoid == 0) listOf(ChainTypeEnum.Avoid) else chainPriority.subList(0, indexOfAvoid + allowAvoid) + newCardOrder = cardChainPriorityHandler.pick( cards = filteredCards, chainPriority = filteredChainPriority, braveChainEnum = braveChainEnum, npUsage = npUsage, npTypes = npTypes, - // BraveChain is higher priority than color chain - forceBraveChain = braveChainFallback != null, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, cardCountPerCardTypeMap = cardCountPerCardTypeMap, + // BraveChain is higher priority than color chain + forceBraveChain = braveChainFallback != null ) } else -> continue diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 6877b7ab0..cb01d164d 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -22,9 +22,9 @@ class CardChainPriorityHandler @Inject constructor( braveChainEnum: BraveChainEnum = BraveChainEnum.None, npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), - forceBraveChain: Boolean = false, cardCountPerFieldSlotMap: Map? = null, cardCountPerCardTypeMap: Map? = null, + forceBraveChain: Boolean = false, ): List? { var newCardOrder: List? = null val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index a03496e79..77b511500 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -5,6 +5,7 @@ import assertk.assertions.containsExactly import assertk.assertions.isEqualTo import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum +import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage @@ -193,6 +194,35 @@ class CardChainPriorityIntegrationTest { assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) } + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), No Mighty in chainPriority allowed`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = attackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + chainPriority = ChainTypeEnum.defaultOrder.filter { it != ChainTypeEnum.Mighty }, + ).map { it.card } + val pickedDefault = getDefaultAvoidChainResult( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + ).map { it.card } + + assertThat(picked).isEqualTo(pickedDefault) + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ),No Mighty in chainPriority allowed, raw`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = attackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + chainPriority = ChainTypeEnum.defaultOrder.filter { it != ChainTypeEnum.Mighty }, + ).map { it.card } + + // Expect avoid SB,KQ,SQ,NA,NA - 12534 - ABECD + assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D) + } + @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { val cards = AttackLineUps.Standard.lineup1 From cf30d3d54532e76cb67183ad1bd9a948398a1c9c Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 02:12:03 +0800 Subject: [PATCH 045/124] Account for Avoid index being 0 just in case --- .../modules/attack/AttackPriorityHandler.kt | 6 +++- .../CardChainPriorityIntegrationTest.kt | 30 ++++++++++++++++--- .../scripts/attack/MightyChainTest.kt | 2 +- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index c5fb77acd..c732a170f 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -95,7 +95,11 @@ class AttackPriorityHandler @Inject constructor( // If braveChainFallback does not exist, // 'Avoid' is to be included, since it is treated as cardPriorityHandler's own fallback method val allowAvoid = if (braveChainFallback == null) 1 else 0 - val filteredChainPriority = if (indexOfAvoid == 0) listOf(ChainTypeEnum.Avoid) else chainPriority.subList(0, indexOfAvoid + allowAvoid) + val filteredChainPriority = when (indexOfAvoid) { + -1 -> chainPriority + 0 -> listOf(ChainTypeEnum.Avoid) + else -> chainPriority.subList(0, indexOfAvoid + allowAvoid) + } newCardOrder = cardChainPriorityHandler.pick( cards = filteredCards, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 77b511500..e478240d9 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -195,7 +195,7 @@ class CardChainPriorityIntegrationTest { } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), No Mighty in chainPriority allowed`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), No Mighty allowed`() { val cards = AttackLineUps.Standard.lineup1 val picked = attackPriorityHandler.pick( cards = cards, @@ -211,7 +211,7 @@ class CardChainPriorityIntegrationTest { } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ),No Mighty in chainPriority allowed, raw`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), No Mighty allowed, raw`() { val cards = AttackLineUps.Standard.lineup1 val picked = attackPriorityHandler.pick( cards = cards, @@ -560,7 +560,7 @@ class CardChainPriorityIntegrationTest { } @Test - fun `SingleServantOnly - lineup3 (QQABB)`() { + fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 val picked = attackPriorityHandler.pick( cards = cards, @@ -572,7 +572,29 @@ class CardChainPriorityIntegrationTest { } @Test - fun `SingleServantOnly - lineup3 (QQABB), with rearranged`() { + fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B) - Buster priority`() { + val cards = AttackLineUps.SingleServantOnly.lineup3 + val picked = attackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + chainPriority = listOf(ChainTypeEnum.Buster, ChainTypeEnum.Mighty), + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Buster + ), + ).map { it.card } + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } + + @Test + fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B), with rearranged`() { val cards = AttackLineUps.SingleServantOnly.lineup3 val picked = attackPriorityHandler.pick( cards = cards, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 5c365bbd4..1e290f3d2 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -151,7 +151,7 @@ class MightyChainTest { } @Test - fun `SingleServantOnly - lineup3 (QQABB)`() { + fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 val picked = mightyChainHandler.pick( cards = cards, From 79f1955d4ffdf084ca689f1e0b83014c80514d9a Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 02:12:43 +0800 Subject: [PATCH 046/124] Return the result of the new cards --- .../java/io/github/fate_grand_automata/scripts/modules/Card.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index b5a4c7709..eaf1f5da8 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -66,7 +66,7 @@ class Card @Inject constructor( val useChainPriority = battleConfig.useChainPriority if (useChainPriority) { val chainPriority = battleConfig.chainPriority.atWave(state.stage) - attackPriorityHandler.pick( + return attackPriorityHandler.pick( cards = cardsOrderedByPriority, npUsage = npUsage, braveChainEnum = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), From 41a67a99fc98519fe1f8de52dcaf27055cd5adbe Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 03:00:12 +0800 Subject: [PATCH 047/124] Fix bug in BraveChain where if there was 2 Brave Chains and one of them had an NP, it would not pick the NP one if it was not the first card --- .../modules/attack/BraveChainHandler.kt | 22 ++++++++++++++++--- .../scripts/attack/BraveChainTest.kt | 22 ++++++++++++++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 94a20f40d..21ac9d67b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -5,6 +5,7 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @@ -19,12 +20,18 @@ class BraveChainHandler @Inject constructor( cardCountPerFieldSlotMap: Map? = null, ): List? { val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) - if (!isBraveChainAllowed(braveChainEnum, cardsPerFieldSlotMap)) return null + if (!isBraveChainAllowed(braveChainEnum, npUsage, cardsPerFieldSlotMap)) return null val braveChainCapableFieldSlots = utils.getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) - // Use the first valid one, since it is already the highest priority - val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } + val npFieldSlot = if (npUsage.nps.size == 1) npUsage.nps.firstOrNull()?.toFieldSlot() else null + + val braveChainPriorityCard = cards.firstOrNull { + // Use the first valid one, since it is already the highest priority + braveChainCapableFieldSlots.contains(it.fieldSlot) && + // And if there is an NP, it must match the NP + (npFieldSlot == null || npFieldSlot == it.fieldSlot) + } val priorityFieldSlot = braveChainPriorityCard?.fieldSlot // Safety check @@ -38,6 +45,7 @@ class BraveChainHandler @Inject constructor( private fun isBraveChainAllowed ( braveChainEnum: BraveChainEnum, + npUsage: NPUsage = NPUsage.none, cardsPerFieldSlotMap: Map ): Boolean { if ( @@ -45,6 +53,14 @@ class BraveChainHandler @Inject constructor( || cardsPerFieldSlotMap.values.isEmpty() ) return false + if (npUsage.nps.size == 1) { + // Permit brave chain with NP only + val npFieldSlot = npUsage.nps.firstOrNull()?.toFieldSlot() + if (npFieldSlot != null) { + return cardsPerFieldSlotMap.getOrElse(npFieldSlot) { 0 } >= 3 + } + } + // Sort by most number of cards val highestTotalCardCount = cardsPerFieldSlotMap.values.sorted().reversed().first() diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 61b228927..6643affbc 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -4,7 +4,9 @@ import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.modules.attack.Utils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler @@ -68,6 +70,24 @@ class BraveChainTest { assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) } + @Test + fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (3Nero)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + } + @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), Avoid`() { val cards = AttackLineUps.Standard.lineup1 @@ -80,7 +100,7 @@ class BraveChainTest { } @Test - fun avoidBraveChainsWith2DiffCards() { + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA), Avoid`() { val cards = AttackLineUps.Standard.lineup2 val picked = braveChainHandler.pick( cards = cards, From 48f9d2ea3d60c61f34d98570f7eb46681609b8fd Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 09:18:25 +0800 Subject: [PATCH 048/124] Clean up unused variables. Make sure that Always uses the correct localization --- .../io/github/fate_grand_automata/ui/drag_sort/DragSort.kt | 1 - .../github/fate_grand_automata/util/LocalizationExtensions.kt | 2 +- .../fate_grand_automata/scripts/modules/ApplyBraveChains.kt | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSort.kt b/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSort.kt index 53b69b27c..f9aee1b7f 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSort.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSort.kt @@ -1,6 +1,5 @@ package io.github.fate_grand_automata.ui.drag_sort -import android.view.View import androidx.compose.runtime.Composable import androidx.compose.ui.viewinterop.AndroidView import androidx.recyclerview.widget.ItemTouchHelper diff --git a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt index f7a9aaf95..be1e64867 100644 --- a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt +++ b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt @@ -26,7 +26,7 @@ val BraveChainEnum.stringRes get() = when (this) { BraveChainEnum.None -> R.string.p_brave_chains_don_t_care BraveChainEnum.WithNP -> R.string.p_brave_chains_with_np - BraveChainEnum.Always -> R.string.p_brave_chains_don_t_care + BraveChainEnum.Always -> R.string.p_brave_chains_always BraveChainEnum.Avoid -> R.string.p_brave_chains_avoid } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index d55bc4521..491efbcf9 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -163,8 +163,7 @@ class ApplyBraveChains @Inject constructor() { cards: List, braveChains: BraveChainEnum, rearrange: Boolean = false, - npUsage: NPUsage = NPUsage.none, - npTypes: Map = emptyMap() + npUsage: NPUsage = NPUsage.none ): List { val picked = when (braveChains) { BraveChainEnum.None -> rearrange( From 0ec0101a7f738e1bcc768be4f4f585a41d5e4fe5 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 09:28:49 +0800 Subject: [PATCH 049/124] Hide 'Always' unless Chain Priority is on --- .../io/github/fate_grand_automata/ui/card_priority/Render.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index 9d49243dc..c3c7f06f8 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -55,7 +55,9 @@ fun CardPriorityListItem.Render( val braveChainDialog = listDialog( selected = braveChains, onSelectedChange = { braveChains = it }, - entries = BraveChainEnum.entries + entries = BraveChainEnum.entries.filter { + useChainPriority || it != BraveChainEnum.Always + } .associateWith { stringResource(it.stringRes) }, title = stringResource(R.string.p_brave_chains) ) From 390fa423d4577dce133a1e542b9b9a2f87dcdd79 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 23:43:05 +0800 Subject: [PATCH 050/124] Add caching for MightChain checks --- .../scripts/modules/attack/MightyChainHandler.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 4c7127e4c..b93eff068 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -67,11 +67,15 @@ class MightyChainHandler @Inject constructor( val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() val selectedCards = mutableListOf() + // Cache the filteredList the first time, to make the next check faster + var cachedFilteredCards: List = cards while (uniqueCardTypes.size < cardsToFind) { - var filteredCards = cards.filter { + var filteredCards = cachedFilteredCards.filter { // Always look for a different card type it.type !in uniqueCardTypes } + cachedFilteredCards = filteredCards + // If there is a single NP, we want to try for a Brave Mighty Chain if (braveChainFieldSlot != null && braveChainEnum != BraveChainEnum.Avoid) { // Attempt to find one matching the fieldSlot @@ -83,12 +87,13 @@ class MightyChainHandler @Inject constructor( if (fieldSlotList.isNotEmpty() || forceBraveChain) filteredCards = fieldSlotList } val filteredCard = filteredCards.firstOrNull() - if (filteredCard == null) break // If cannot find, we leave + if (filteredCard == null) break // If cannot find, leave uniqueCardTypes.add(filteredCard.type) selectedCards.add(filteredCard) + cachedFilteredCards = cachedFilteredCards - filteredCard } - // If there isn't a valid list of cards, we reject and have empty be returned + // If there isn't a valid list of cards, reject and return null if (uniqueCardTypes.size < cardsToFind) return null // Otherwise, we return the expected output From 50a1ab0cebf7cf3d7d2e431a5ac4d4435c14ba54 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 3 Sep 2025 23:43:51 +0800 Subject: [PATCH 051/124] Change logic of AvoidChain to attempt to make the consecutive cards from different servants. --- .../modules/attack/AvoidChainHandler.kt | 80 ++++++++++++------- .../modules/attack/MightyChainHandler.kt | 4 +- .../scripts/attack/AvoidChainTest.kt | 2 +- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index f3bc4f276..beddaa333 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -52,41 +52,63 @@ class AvoidChainHandler @Inject constructor( val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + var cachedFilteredCards = cards + var previousFieldSlot: FieldSlot? = null + // Otherwise, calculate val cardsNeeded = 3 - npUsage.nps.size val selectedCards = mutableListOf() - for (card in cards) { - val fieldSlot = card.fieldSlot - if (fieldSlot == null) continue - val fieldSlotCount = selectedFieldSlots.getOrElse(fieldSlot) { 0 } - if (avoidBraveChains - && ( - // If there are already 2 selected cards with the same card slot - fieldSlotCount >= 2 - // and there are still valid targets, skip - && cardCountPerFieldSlotMap.size > 1 - ) - ) continue + while (selectedCards.size < cardsNeeded) { + // Loop through all the cards at least one time so that the next pass is easier + var filteredCards = mutableListOf() + for (card in cachedFilteredCards) { + if (card in selectedCards) continue + val fieldSlot = card.fieldSlot + if (fieldSlot == null) continue + val fieldSlotCount = selectedFieldSlots.getOrElse(fieldSlot) { 0 } + if (avoidBraveChains + && ( + // If there are already 2 selected cards with the same card slot + fieldSlotCount >= 2 + // and there are still valid targets, skip + && cardCountPerFieldSlotMap.size > 1 + ) + ) continue - val cardType = card.type - val cardTypeCount = selectedCardTypes.getOrElse(cardType) { 0 } - if (avoidCardChains - // There are at least 2 cards to choose from - && cardCountPerCardTypeMap.size > 1 - && ( - cardTypeCount >= 2 - // Or if it would make a Mighty Chain - || (cardTypeCount == 0 && selectedCardTypes.size >= 2) - ) - ) continue + val cardType = card.type + val cardTypeCount = selectedCardTypes.getOrElse(cardType) { 0 } + if (avoidCardChains + // There are at least 2 cards to choose from + && cardCountPerCardTypeMap.size > 1 + && ( + cardTypeCount >= 2 + // Or if it would make a Mighty Chain + || (cardTypeCount == 0 && selectedCardTypes.size >= 2) + ) + ) continue + // If reach here, add to filteredList + filteredCards.add(card) + } - // If reach here, card can be added - selectedCards.add(card) - selectedFieldSlots[fieldSlot] = fieldSlotCount + 1 - selectedCardTypes[cardType] = cardTypeCount + 1 + // Cache the filteredCards to make the next check faster + cachedFilteredCards = filteredCards - // If goal has been reached, end the loop - if (selectedCards.size >= cardsNeeded) break + // Try to make the next card different from the previous card's fieldSlot, if possible + if (previousFieldSlot != null) { + val fieldSlotFilter = filteredCards.filter { + it.fieldSlot != previousFieldSlot + } + if (fieldSlotFilter.isNotEmpty()) filteredCards = fieldSlotFilter.toMutableList() + } + + val filteredCard = filteredCards.firstOrNull() + if (filteredCard == null || filteredCard.fieldSlot == null) break // If cannot find, leave + + // If reach here, card can be added + selectedCards.add(filteredCard) + selectedFieldSlots[filteredCard.fieldSlot] = selectedFieldSlots.getOrElse(filteredCard.fieldSlot) { 0 } + 1 + selectedCardTypes[filteredCard.type] = selectedCardTypes.getOrElse(filteredCard.type) { 0 } + 1 + previousFieldSlot = filteredCard.fieldSlot } // Return empty if cannot fulfill diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index b93eff068..95c479087 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -71,8 +71,9 @@ class MightyChainHandler @Inject constructor( var cachedFilteredCards: List = cards while (uniqueCardTypes.size < cardsToFind) { var filteredCards = cachedFilteredCards.filter { + it !in selectedCards // Always look for a different card type - it.type !in uniqueCardTypes + && it.type !in uniqueCardTypes } cachedFilteredCards = filteredCards @@ -90,7 +91,6 @@ class MightyChainHandler @Inject constructor( if (filteredCard == null) break // If cannot find, leave uniqueCardTypes.add(filteredCard.type) selectedCards.add(filteredCard) - cachedFilteredCards = cachedFilteredCards - filteredCard } // If there isn't a valid list of cards, reject and return null diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index dc4e7c91e..bdd591abc 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -39,8 +39,8 @@ class AvoidChainTest { assertThat(picked).containsExactly( CommandCard.Face.A, - CommandCard.Face.E, CommandCard.Face.B, + CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, ) From 9a46c3447d43a90df7313feccfb4cb9937278bdc Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 00:14:02 +0800 Subject: [PATCH 052/124] Add handling for Avoid in ColorChainHandler --- .../modules/attack/ColorChainHandler.kt | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index 60d792ec7..13b429d92 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -5,6 +5,7 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @@ -39,8 +40,37 @@ open class ColorChainHandler @Inject constructor( val cardsNeeded = 3 - npUsage.nps.size var selectedCards = cards.filter { it.type == cardType } - // If there is a single NP, we want to try for a Brave Mighty Chain - if (braveChainFieldSlot != null && braveChainEnum != BraveChainEnum.Avoid) { + + // Do not have any BraveChains or it is invalid + if (braveChainEnum == BraveChainEnum.Avoid) { + val npFieldSlots = npUsage.nps.map { it.toFieldSlot() }.toSet() + // If there is 2 or more NP, it will never be a BraveChain + if (npFieldSlots.size <= 1) { + val selectedCardFieldSlotSet = selectedCards.map { it.fieldSlot }.toSet() + if ( + // If there is only 1 unique field slot in the selected list + selectedCardFieldSlotSet.size == 1 && ( + // no NPs to cancel out + npFieldSlots.isEmpty() + // Or there is exactly 1 NP and it is the same + || npFieldSlots.first() == selectedCardFieldSlotSet.first() + ) + ) return null + + // Otherwise, it is valid, but the correct cards must be selected + val newSelection = selectedCards.take(3).toMutableList() + val newSelectFirstFieldSlot = newSelection.first().fieldSlot + if (newSelection.all { it.fieldSlot == newSelectFirstFieldSlot }) { + val differentSlot = selectedCards.firstOrNull() { it.fieldSlot != newSelectFirstFieldSlot } + if (differentSlot == null) return null + // Just add new card to index 1, aka 2nd card + newSelection.add(1, differentSlot) + } + selectedCards = newSelection + (selectedCards - newSelection) + } + } + // If there is a single NP, try for a Brave Mighty Chain + else if (braveChainFieldSlot != null) { // Attempt to find one matching the fieldSlot val fieldSlotList = selectedCards.filter { it.fieldSlot == braveChainFieldSlot From 0b65233a4895707c71f510bf47bbc785dd912cf2 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 00:32:55 +0800 Subject: [PATCH 053/124] Extract AvoidBraveChain logic to Utils file --- .../modules/attack/ColorChainHandler.kt | 32 +++------------- .../scripts/modules/attack/Utils.kt | 38 ++++++++++++++++--- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index 13b429d92..06567818f 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -40,34 +40,14 @@ open class ColorChainHandler @Inject constructor( val cardsNeeded = 3 - npUsage.nps.size var selectedCards = cards.filter { it.type == cardType } - // Do not have any BraveChains or it is invalid if (braveChainEnum == BraveChainEnum.Avoid) { - val npFieldSlots = npUsage.nps.map { it.toFieldSlot() }.toSet() - // If there is 2 or more NP, it will never be a BraveChain - if (npFieldSlots.size <= 1) { - val selectedCardFieldSlotSet = selectedCards.map { it.fieldSlot }.toSet() - if ( - // If there is only 1 unique field slot in the selected list - selectedCardFieldSlotSet.size == 1 && ( - // no NPs to cancel out - npFieldSlots.isEmpty() - // Or there is exactly 1 NP and it is the same - || npFieldSlots.first() == selectedCardFieldSlotSet.first() - ) - ) return null - - // Otherwise, it is valid, but the correct cards must be selected - val newSelection = selectedCards.take(3).toMutableList() - val newSelectFirstFieldSlot = newSelection.first().fieldSlot - if (newSelection.all { it.fieldSlot == newSelectFirstFieldSlot }) { - val differentSlot = selectedCards.firstOrNull() { it.fieldSlot != newSelectFirstFieldSlot } - if (differentSlot == null) return null - // Just add new card to index 1, aka 2nd card - newSelection.add(1, differentSlot) - } - selectedCards = newSelection + (selectedCards - newSelection) - } + val newSelection = utils.getCardsForAvoidBraveChain( + cards = selectedCards, + npUsage = npUsage, + ) + if (newSelection == null) return null + selectedCards = newSelection } // If there is a single NP, try for a Brave Mighty Chain else if (braveChainFieldSlot != null) { diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt index ea1e5af78..67310577b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt @@ -2,7 +2,6 @@ package io.github.fate_grand_automata.scripts.modules.attack import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum -import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard @@ -91,10 +90,37 @@ class Utils @Inject constructor() { } } - fun List.isAfterAvoid(chainTypeEnum: ChainTypeEnum, indexOfAvoid: Int? = null): Boolean { - val avoidIndex = indexOfAvoid ?: this.indexOf(ChainTypeEnum.Avoid) - val enumIndex = this.indexOf(chainTypeEnum) - if (avoidIndex < 0) return false - return enumIndex > avoidIndex + fun getCardsForAvoidBraveChain ( + cards: List, + npUsage: NPUsage = NPUsage.none, + ): List? { + val npFieldSlots = npUsage.nps.map { it.toFieldSlot() }.toSet() + // If there is 2 or more NP, it will never be a BraveChain + if (npFieldSlots.size <= 1) { + val fieldSlotSet = cards.map { it.fieldSlot }.toSet() + if ( + // If there is only 1 unique field slot in the list + fieldSlotSet.size == 1 + && ( + // no NPs to cancel out + npFieldSlots.isEmpty() + // Or there is exactly 1 NP and it is the same + || npFieldSlots.first() == fieldSlotSet.first() + ) + ) return null + + // Otherwise, it is valid, but the correct cards must be selected + val filteredCards = cards.take(3).toMutableList() + val firstFieldSlot = filteredCards.first().fieldSlot + if (filteredCards.all { it.fieldSlot == firstFieldSlot }) { + val differentSlot = cards.firstOrNull() { it.fieldSlot != firstFieldSlot } + if (differentSlot == null) return null + // Just add new card to index 1, aka 2nd card + filteredCards.add(1, differentSlot) + } + return filteredCards + (cards - filteredCards) + } + + return cards } } \ No newline at end of file From 106f5f6b7aaeb9b4b1cc8820e10d65b719ea40bf Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 00:33:06 +0800 Subject: [PATCH 054/124] Add AvoidBraveChain to MightyChainHandler --- .../modules/attack/MightyChainHandler.kt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 95c479087..0e6ccd710 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -45,6 +45,7 @@ class MightyChainHandler @Inject constructor( return pick( cards = cards, + npUsage = npUsage, uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, braveChainFieldSlot = braveChainFieldSlot, braveChainEnum = braveChainEnum, @@ -55,6 +56,7 @@ class MightyChainHandler @Inject constructor( // Returns null if uniqueCards cannot be found private fun pick( cards: List, + npUsage: NPUsage = NPUsage.none, // Decreases number of cards to get // e.g. usually based on npSize uniqueCardTypesAlreadyFilled: Set, @@ -66,14 +68,15 @@ class MightyChainHandler @Inject constructor( val cardsToFind = totalUniqueCardTypesPermitted val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() - val selectedCards = mutableListOf() + var selectedCards = mutableListOf() // Cache the filteredList the first time, to make the next check faster var cachedFilteredCards: List = cards while (uniqueCardTypes.size < cardsToFind) { var filteredCards = cachedFilteredCards.filter { - it !in selectedCards // Always look for a different card type - && it.type !in uniqueCardTypes + it.type !in uniqueCardTypes + // It should never reach here due to needing uniqueCardTypes, but just in case + && it !in selectedCards } cachedFilteredCards = filteredCards @@ -96,6 +99,16 @@ class MightyChainHandler @Inject constructor( // If there isn't a valid list of cards, reject and return null if (uniqueCardTypes.size < cardsToFind) return null + // Do not have any BraveChains or it is invalid + if (braveChainEnum == BraveChainEnum.Avoid) { + val newSelection = utils.getCardsForAvoidBraveChain( + cards = selectedCards + cachedFilteredCards, + npUsage = npUsage, + ) + if (newSelection == null) return null + selectedCards = newSelection.toMutableList() + } + // Otherwise, we return the expected output val remainder = cards - selectedCards val combinedCards = selectedCards + remainder From 812960b439477c3ce766d8f8bfdcceb9ce622310 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 00:45:25 +0800 Subject: [PATCH 055/124] Prevent the forcing of BraveChains if BraveChainEnum is set to None --- .../scripts/modules/attack/AttackPriorityHandler.kt | 2 ++ .../github/fate_grand_automata/scripts/modules/attack/Utils.kt | 1 + .../fate_grand_automata/scripts/attack/ColorChainTest.kt | 3 +++ 3 files changed, 6 insertions(+) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index c732a170f..d1fb6b4f0 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -111,6 +111,8 @@ class AttackPriorityHandler @Inject constructor( cardCountPerCardTypeMap = cardCountPerCardTypeMap, // BraveChain is higher priority than color chain forceBraveChain = braveChainFallback != null + && braveChainEnum != BraveChainEnum.Avoid + && braveChainEnum != BraveChainEnum.None ) } else -> continue diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt index 67310577b..63967a45f 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt @@ -75,6 +75,7 @@ class Utils @Inject constructor() { forceBraveChain: Boolean = false, ): FieldSlot? { return if (braveChainEnum == BraveChainEnum.Avoid) null + else if (braveChainEnum == BraveChainEnum.None && !forceBraveChain) return null else if (npUsage.nps.size == 1) { // Get np if there is only 1 (since we want to try for Brave Chain) val firstNp = npUsage.nps.firstOrNull() diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 2d392285e..b1cdfee20 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -3,6 +3,7 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot @@ -111,6 +112,7 @@ class ColorChainTest { npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick ), + braveChainEnum = BraveChainEnum.WithNP, forceBraveChain = true )?.map { it.card } ?: emptyList() } @@ -176,6 +178,7 @@ class ColorChainTest { npTypes = mapOf( FieldSlot.B to CardTypeEnum.Quick ), + braveChainEnum = BraveChainEnum.WithNP, forceBraveChain = true, )?.map { it.card } ?: emptyList() } From 1d746a7c8baecd2b60800c1dcd6fc2ef5248a185 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 01:13:50 +0800 Subject: [PATCH 056/124] Change the behavior when dealing with forcedBraveChain and BraveChainEnum.Always. --- .../modules/attack/ColorChainHandler.kt | 9 ++- .../modules/attack/MightyChainHandler.kt | 9 ++- .../scripts/modules/attack/Utils.kt | 24 +++++-- .../scripts/attack/BraveChainTest.kt | 2 - .../scripts/attack/ColorChainTest.kt | 10 ++- .../scripts/attack/UtilsTest.kt | 62 +++++++++++++++++++ 6 files changed, 99 insertions(+), 17 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index 06567818f..63e774cea 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -31,10 +31,12 @@ open class ColorChainHandler @Inject constructor( // Check for Brave Chain val braveChainFieldSlot = utils.getBraveChainFieldSlot( - cards = cards, braveChainEnum = braveChainEnum, + cards = cards, npUsage = npUsage, + forceBraveChain = forceBraveChain, ) + val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum // if it passes the above, the actual implementation is very easy val cardsNeeded = 3 - npUsage.nps.size @@ -49,7 +51,7 @@ open class ColorChainHandler @Inject constructor( if (newSelection == null) return null selectedCards = newSelection } - // If there is a single NP, try for a Brave Mighty Chain + // If there is a braveChainFieldSlot, try for a Brave Color Chain else if (braveChainFieldSlot != null) { // Attempt to find one matching the fieldSlot val fieldSlotList = selectedCards.filter { @@ -58,7 +60,8 @@ open class ColorChainHandler @Inject constructor( // If there is a valid number of cards for the fieldSlot, attempt to Brave Chain // Even if it is not valid, if forceBraveChain is on, // it only accepts Brave Color Chains and not normal Color Chains - if (fieldSlotList.size >= cardsNeeded || forceBraveChain) selectedCards = fieldSlotList + if (fieldSlotList.size >= cardsNeeded || braveChainEnum == BraveChainEnum.Always) + selectedCards = fieldSlotList } if (selectedCards.size < cardsNeeded) return null diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 0e6ccd710..066471145 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -37,11 +37,12 @@ class MightyChainHandler @Inject constructor( // Check for Brave Chain val braveChainFieldSlot = utils.getBraveChainFieldSlot( - cards = cards, braveChainEnum = braveChainEnum, + cards = cards, npUsage = npUsage, forceBraveChain = forceBraveChain, ) + val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum return pick( cards = cards, @@ -49,7 +50,6 @@ class MightyChainHandler @Inject constructor( uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, braveChainFieldSlot = braveChainFieldSlot, braveChainEnum = braveChainEnum, - forceBraveChain = forceBraveChain, ) } @@ -63,7 +63,6 @@ class MightyChainHandler @Inject constructor( // In case of a Brave chain, we want to know what slot it is braveChainFieldSlot: FieldSlot? = null, braveChainEnum: BraveChainEnum = BraveChainEnum.None, - forceBraveChain: Boolean = false, ): List? { val cardsToFind = totalUniqueCardTypesPermitted val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() @@ -80,7 +79,7 @@ class MightyChainHandler @Inject constructor( } cachedFilteredCards = filteredCards - // If there is a single NP, we want to try for a Brave Mighty Chain + // If there is a braveChainFieldSlot, we want to try for a Brave Mighty Chain if (braveChainFieldSlot != null && braveChainEnum != BraveChainEnum.Avoid) { // Attempt to find one matching the fieldSlot val fieldSlotList = filteredCards.filter { @@ -88,7 +87,7 @@ class MightyChainHandler @Inject constructor( } // Even if it is empty, if forceBraveChain is on, // it only accepts Brave Mighty Chains and not normal Mighty Chains - if (fieldSlotList.isNotEmpty() || forceBraveChain) filteredCards = fieldSlotList + if (fieldSlotList.isNotEmpty() || braveChainEnum == BraveChainEnum.Always) filteredCards = fieldSlotList } val filteredCard = filteredCards.firstOrNull() if (filteredCard == null) break // If cannot find, leave diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt index 63967a45f..1763ed5c2 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt @@ -68,19 +68,35 @@ class Utils @Inject constructor() { .map { it } } + /** + * Returns a field slot for a BraveChain if there is one available + * @param braveChainEnum BraveChainEnum that determines the behavior of this function. + * BraveChainEnum.None -> Does not return a fieldSlot, since BraveChains are not forced. + * BraveChainEnum.Avoid -> Does not return a fieldSlot, since BraveChains are avoided. + * BraveChainEnum.WithNP -> Returns a field slot if there is 1 NP with a valid BraveChain. + * BraveChainEnum.Always -> Always returns a fieldSlot if there is a valid one. + * @param cards The list of cards available to choose from + * @param npUsage NPs that have been clicked + * @param forceBraveChain Treats braveChainEnum as BraveChainEnum.Always + * @returns A valid FieldSlot for a BraveChain or null if there is none + */ fun getBraveChainFieldSlot ( - cards: List, braveChainEnum: BraveChainEnum = BraveChainEnum.None, + cards: List, npUsage: NPUsage = NPUsage.none, forceBraveChain: Boolean = false, ): FieldSlot? { + val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum return if (braveChainEnum == BraveChainEnum.Avoid) null - else if (braveChainEnum == BraveChainEnum.None && !forceBraveChain) return null + else if (braveChainEnum == BraveChainEnum.None) return null else if (npUsage.nps.size == 1) { // Get np if there is only 1 (since we want to try for Brave Chain) val firstNp = npUsage.nps.firstOrNull() - firstNp?.toFieldSlot() - } else if (braveChainEnum == BraveChainEnum.Always || forceBraveChain) { + val fieldSlot = firstNp?.toFieldSlot() + // Only return the field slot if it is valid for a Brave Chain + if (cards.filter { it.fieldSlot == fieldSlot }.size > 1) fieldSlot + else null + } else if (braveChainEnum == BraveChainEnum.Always) { // Force brave chain only if it always wants a Brave Chain val braveChainCapableFieldSlots = getFieldSlotsWithValidBraveChain(cards, npUsage) val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 6643affbc..4fd035745 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -4,9 +4,7 @@ import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty import io.github.fate_grand_automata.scripts.enums.BraveChainEnum -import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard -import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.modules.attack.Utils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index b1cdfee20..84150d722 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -112,7 +112,6 @@ class ColorChainTest { npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick ), - braveChainEnum = BraveChainEnum.WithNP, forceBraveChain = true )?.map { it.card } ?: emptyList() } @@ -129,7 +128,13 @@ class ColorChainTest { assertThat( results.getOrElse(CardTypeEnum.Quick) { emptyList() }, "Quick" - ).isEmpty() + ).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) } @Test @@ -178,7 +183,6 @@ class ColorChainTest { npTypes = mapOf( FieldSlot.B to CardTypeEnum.Quick ), - braveChainEnum = BraveChainEnum.WithNP, forceBraveChain = true, )?.map { it.card } ?: emptyList() } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt index 2092576c8..485ec2a6a 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt @@ -2,6 +2,8 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.isEqualTo +import assertk.assertions.isNull +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot @@ -78,6 +80,20 @@ class UtilsTest { assertThat(result[FieldSlot.C], "FieldSlot.C").isEqualTo(2) } + @Test + fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsPerFieldSlotMap( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0) + ) + + assertThat(result.size, "Result.size").isEqualTo(3) + assertThat(result[FieldSlot.A], "FieldSlot.A").isEqualTo(1) + assertThat(result[FieldSlot.B], "FieldSlot.B").isEqualTo(2) + assertThat(result[FieldSlot.C], "FieldSlot.C").isEqualTo(3) + } + @Test fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 @@ -121,6 +137,18 @@ class UtilsTest { assertThat(result[0], "FieldSlotB").isEqualTo(FieldSlot.B) } + @Test + fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getFieldSlotsWithValidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0) + ) + + assertThat(result.size, "Result.size").isEqualTo(1) + assertThat(result[0], "FieldSlotC").isEqualTo(FieldSlot.C) + } + @Test fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 @@ -224,4 +252,38 @@ class UtilsTest { assertThat(result[CardTypeEnum.Quick], "Quick").isEqualTo(null) assertThat(result[CardTypeEnum.Buster], "Buster").isEqualTo(3) } + + @Test + fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getBraveChainFieldSlot( + cards = cards, + braveChainEnum = BraveChainEnum.Always, + ) + + assertThat(result).isNull() + } + + @Test + fun `getBraveChainFieldSlot, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getBraveChainFieldSlot( + cards = cards, + braveChainEnum = BraveChainEnum.Always, + ) + + assertThat(result).isNull() + } + + @Test + fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getBraveChainFieldSlot( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + braveChainEnum = BraveChainEnum.Always, + ) + + assertThat(result).isNull() + } } \ No newline at end of file From 20bf2d39aa7553174a65167b729231f59d8c9a17 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 01:49:50 +0800 Subject: [PATCH 057/124] Add new tests. Fix bug in getBraveChainFieldSlot --- .../scripts/modules/attack/Utils.kt | 15 +- .../scripts/attack/AttackLineUps.kt | 23 ++- .../scripts/attack/UtilsTest.kt | 150 +++++++++++++++--- 3 files changed, 160 insertions(+), 28 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt index 1763ed5c2..967fb0a7e 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt @@ -87,24 +87,25 @@ class Utils @Inject constructor() { forceBraveChain: Boolean = false, ): FieldSlot? { val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum - return if (braveChainEnum == BraveChainEnum.Avoid) null - else if (braveChainEnum == BraveChainEnum.None) return null + if ( + braveChainEnum == BraveChainEnum.Avoid + || braveChainEnum == BraveChainEnum.None + // Since it is impossible to make a BraveChain + || npUsage.nps.size > 1) return null else if (npUsage.nps.size == 1) { // Get np if there is only 1 (since we want to try for Brave Chain) val firstNp = npUsage.nps.firstOrNull() val fieldSlot = firstNp?.toFieldSlot() // Only return the field slot if it is valid for a Brave Chain - if (cards.filter { it.fieldSlot == fieldSlot }.size > 1) fieldSlot - else null + if (cards.filter { it.fieldSlot == fieldSlot }.size > 1) return fieldSlot } else if (braveChainEnum == BraveChainEnum.Always) { // Force brave chain only if it always wants a Brave Chain val braveChainCapableFieldSlots = getFieldSlotsWithValidBraveChain(cards, npUsage) val braveChainPriorityCard = cards.firstOrNull { braveChainCapableFieldSlots.contains(it.fieldSlot) } // Return the first valid one, since it is already the highest priority - braveChainPriorityCard?.fieldSlot - } else { - null + return braveChainPriorityCard?.fieldSlot } + return null } fun getCardsForAvoidBraveChain ( diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index c709fc0ab..b85aae241 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -45,11 +45,15 @@ class AttackLineUps { ) /** - * [Kama, Scathach, Nero] - * [Scathach WB] [Kama Q] [Nero RA] [Nero RA] [Scathach WQ] + * Kama, Scathach, Nero; + * Scathach 1WB, Kama 2Q, Nero 3RA, Nero 4RA, Scathach 5WQ */ val lineup1 = listOf(scathach1WB, kama2Q, nero3RA, nero4RA, scathach5WQ) + /** + * Kama, Scathach, Nero; + * Scathach 1WB, Scathach 5WQ, Kama 2Q, Nero 3RA, Nero 4RA + */ val lineup2 = listOf(scathach1WB, scathach5WQ, kama2Q, nero3RA, nero4RA) } @@ -112,23 +116,33 @@ class AttackLineUps { ) // Buster brave chain + /** kiyohime1B, kiyohime2B, nero3RA, nero4RA, kiyohime5B */ val lineup01 = listOf(kiyohime1B, kiyohime2B, nero3RA, nero4RA, kiyohime5B) + /** kiyohime1B, kiyohime5B, kiyohime2B, nero3RA, nero4RA */ val lineup02 = listOf(kiyohime1B, kiyohime5B, kiyohime2B, nero3RA, nero4RA) // Buster chain only + /** kiyohime1B, kiyohime2B, nero3RA, nero4RA, nero5BAlt */ val lineup03 = listOf(kiyohime1B, kiyohime2B, nero3RA, nero4RA, nero5BAlt) + /** kiyohime1B, nero5BAlt, kiyohime2B, nero3RA, nero4RA */ val lineup04 = listOf(kiyohime1B, nero5BAlt, kiyohime2B, nero3RA, nero4RA) // Buster chain with either Brave or non-Brave + /** kiyohime1B, kiyohime2B, nero3RA, nero4BAlt, kiyohime5B */ val lineup05 = listOf(kiyohime1B, kiyohime2B, nero3RA, nero4BAlt, kiyohime5B) + /** kiyohime1B, kiyohime5B, kiyohime2B, nero3RA, nero4BAlt */ val lineup06 = listOf(kiyohime1B, kiyohime5B, kiyohime2B, nero3RA, nero4BAlt) // Buster brave chain or Mighty chain + /** kiyohime1B, kiyohime2B, nero3QAlt, nero4RA, kiyohime5B */ val lineup07 = listOf(kiyohime1B, kiyohime2B, nero3QAlt, nero4RA, kiyohime5B) + /** kiyohime1B, kiyohime5B, kiyohime2B, nero3QAlt, nero4RA */ val lineup08 = listOf(kiyohime1B, kiyohime5B, kiyohime2B, nero3QAlt, nero4RA) // Buster chain or Mighty brave chain + /** kiyohime1B, kiyohime2B, nero3QAlt, nero4RA, nero5BAlt */ val lineup09 = listOf(kiyohime1B, kiyohime2B, nero3QAlt, nero4RA, nero5BAlt) + /** kiyohime1B, nero5BAlt, kiyohime2B, nero3QAlt, nero4RA */ val lineup10 = listOf(kiyohime1B, nero5BAlt, kiyohime2B, nero3QAlt, nero4RA) } @@ -176,15 +190,20 @@ class AttackLineUps { affinity = CardAffinityEnum.Weak ) + /** scathach1WB, scathach2WQ, scathach3WA, scathach4WB, scathach5WQ */ val lineup1 = listOf(scathach1WB, scathach2WQ, scathach3WA, scathach4WB, scathach5WQ) + /** scathach1WB, scathach4WB, scathach5WQ, scathach2WQ, scathach3WA */ val lineup2 = listOf(scathach1WB, scathach4WB, scathach5WQ, scathach2WQ, scathach3WA) + /** scathach5WQ, scathach2WQ, scathach3WA, scathach1WB, scathach4WB */ val lineup3 = listOf(scathach5WQ, scathach2WQ, scathach3WA, scathach1WB, scathach4WB) // 3 Quick, 2 Buster; Buster starting card + /** scathach1WB, scathach2WQ, scathach3WAltQ, scathach4WB, scathach5WQ */ val lineup4 = listOf(scathach1WB, scathach2WQ, scathach3WAltQ, scathach4WB, scathach5WQ) // 3 Quick, 2 Buster; Quick starting card + /** scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB */ val lineup5 = listOf(scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB) } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt index 485ec2a6a..c3a637e5d 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt @@ -13,6 +13,7 @@ import kotlin.test.Test class UtilsTest { val utils = Utils() + val BRAVE_CHAIN_ENUM_LIST = BraveChainEnum.entries @Test fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { @@ -149,6 +150,28 @@ class UtilsTest { assertThat(result[0], "FieldSlotC").isEqualTo(FieldSlot.C) } + @Test + fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getFieldSlotsWithValidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) + ) + + assertThat(result.size, "Result.size").isEqualTo(0) + } + + @Test + fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + All 3 NP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getFieldSlotsWithValidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0) + ) + + assertThat(result.size, "Result.size").isEqualTo(0) + } + @Test fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 @@ -255,35 +278,124 @@ class UtilsTest { @Test fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { - val cards = AttackLineUps.Standard.lineup1 - val result = utils.getBraveChainFieldSlot( - cards = cards, - braveChainEnum = BraveChainEnum.Always, - ) + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getBraveChainFieldSlot( + cards = cards, + braveChainEnum = braveChainEnum, + ) - assertThat(result).isNull() + assertThat(result, braveChainEnum.toString()).isNull() + } } @Test fun `getBraveChainFieldSlot, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { - val cards = AttackLineUps.Standard.lineup2 - val result = utils.getBraveChainFieldSlot( - cards = cards, - braveChainEnum = BraveChainEnum.Always, - ) + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getBraveChainFieldSlot( + cards = cards, + braveChainEnum = braveChainEnum, + ) - assertThat(result).isNull() + assertThat(result, braveChainEnum.toString()).isNull() + } } @Test fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { - val cards = AttackLineUps.Standard.lineup2 - val result = utils.getBraveChainFieldSlot( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - braveChainEnum = BraveChainEnum.Always, - ) + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getBraveChainFieldSlot( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + braveChainEnum = braveChainEnum, + ) + + assertThat(result, braveChainEnum.toString()).isNull() + } + } + + @Test + fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getBraveChainFieldSlot( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + braveChainEnum = braveChainEnum, + ) + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> assertThat(result, braveChainEnum.toString()) + .isEqualTo(FieldSlot.B) + else -> assertThat(result, braveChainEnum.toString()).isNull() + } + } + } + + @Test + fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getBraveChainFieldSlot( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + braveChainEnum = braveChainEnum, + ) + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> assertThat(result, braveChainEnum.toString()) + .isEqualTo(FieldSlot.C) + else -> assertThat(result, braveChainEnum.toString()).isNull() + } + } + } + + @Test + fun `getBraveChainFieldSlot, Standard -lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getBraveChainFieldSlot( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + braveChainEnum = braveChainEnum, + ) + + assertThat(result, braveChainEnum.toString()).isNull() + } + } + + @Test + fun `getBraveChainFieldSlot, Standard -lineup1 (1SB,2KQ,3NA,4NA,5SQ) + All 3 NP`() { + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getBraveChainFieldSlot( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + braveChainEnum = braveChainEnum, + ) + + assertThat(result, braveChainEnum.toString()).isNull() + } + } + + @Test + fun `getBraveChainFieldSlot, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.BusterFocus.lineup01 + val result = utils.getBraveChainFieldSlot( + cards = cards, + braveChainEnum = braveChainEnum, + ) - assertThat(result).isNull() + when (braveChainEnum) { + BraveChainEnum.Always -> assertThat(result, braveChainEnum.toString()) + .isEqualTo(FieldSlot.B) + else -> assertThat(result, braveChainEnum.toString()).isNull() + } + } } } \ No newline at end of file From c515a9d07fb2c0662c953be56f201a725fbf3b15 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 02:02:06 +0800 Subject: [PATCH 058/124] Add more test cases for Utils --- .../scripts/attack/UtilsTest.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt index c3a637e5d..9e63bc126 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt @@ -1,6 +1,7 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat +import assertk.assertions.containsExactly import assertk.assertions.isEqualTo import assertk.assertions.isNull import io.github.fate_grand_automata.scripts.enums.BraveChainEnum @@ -398,4 +399,59 @@ class UtilsTest { } } } + + @Test + fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB)`() { + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.BusterFocus.lineup03 + val result = utils.getBraveChainFieldSlot( + cards = cards, + braveChainEnum = braveChainEnum, + ) + + when (braveChainEnum) { + BraveChainEnum.Always -> assertThat(result, braveChainEnum.toString()) + .isEqualTo(FieldSlot.C) + else -> assertThat(result, braveChainEnum.toString()).isNull() + } + } + } + + @Test + fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB) - 2Kiyo-NP`() { + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.BusterFocus.lineup03 + val result = utils.getBraveChainFieldSlot( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + braveChainEnum = braveChainEnum, + ) + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> assertThat(result, braveChainEnum.toString()) + .isEqualTo(FieldSlot.B) + else -> assertThat(result, braveChainEnum.toString()).isNull() + } + } + } + + @Test + fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB) - 3Nero-NP`() { + for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + val cards = AttackLineUps.BusterFocus.lineup03 + val result = utils.getBraveChainFieldSlot( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + braveChainEnum = braveChainEnum, + ) + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> assertThat(result, braveChainEnum.toString()) + .isEqualTo(FieldSlot.C) + else -> assertThat(result, braveChainEnum.toString()).isNull() + } + } + } } \ No newline at end of file From 9a60da93e1ee256bfa8f2e8b6454112ea666ac60 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 02:16:40 +0800 Subject: [PATCH 059/124] Fix edge case for getCardsForAvoidBraveChain to maintain card order. Add more tests for Utils --- .../scripts/modules/attack/Utils.kt | 16 +- .../scripts/attack/UtilsTest.kt | 140 +++++++++++++++++- 2 files changed, 150 insertions(+), 6 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt index 967fb0a7e..fbba62496 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt @@ -127,10 +127,22 @@ class Utils @Inject constructor() { ) ) return null + val npFieldSlot = npFieldSlots.firstOrNull() + // Otherwise, it is valid, but the correct cards must be selected - val filteredCards = cards.take(3).toMutableList() + val cardsNeeded = 3 - npUsage.nps.size + val filteredCards = cards.take(cardsNeeded).toMutableList() val firstFieldSlot = filteredCards.first().fieldSlot - if (filteredCards.all { it.fieldSlot == firstFieldSlot }) { + if ( + // If all cards are the same field slot + filteredCards.all { it.fieldSlot == firstFieldSlot } + && ( + // and it matches the NP + npFieldSlot == firstFieldSlot + // or the NP is null + || npFieldSlot == null + ) + ) { val differentSlot = cards.firstOrNull() { it.fieldSlot != firstFieldSlot } if (differentSlot == null) return null // Just add new card to index 1, aka 2nd card diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt index 9e63bc126..84c66392e 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt @@ -30,7 +30,7 @@ class UtilsTest { } @Test - fun `getCardsPerFieldSlotMap, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + fun `getCardsPerFieldSlotMap, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 val result = utils.getCardsPerFieldSlotMap( cards = cards, @@ -107,7 +107,7 @@ class UtilsTest { } @Test - fun `getFieldSlotsWithValidBraveChain, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + fun `getFieldSlotsWithValidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 val result = utils.getFieldSlotsWithValidBraveChain( cards = cards, @@ -187,7 +187,7 @@ class UtilsTest { } @Test - fun `getCardsPerCardTypeMap, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + fun `getCardsPerCardTypeMap, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 val result = utils.getCardsPerCardTypeMap( cards = cards, @@ -291,7 +291,7 @@ class UtilsTest { } @Test - fun `getBraveChainFieldSlot, Standard - lineup2 (1SB,5SQ,2SQ,3NA,4NA)`() { + fun `getBraveChainFieldSlot, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { val cards = AttackLineUps.Standard.lineup2 val result = utils.getBraveChainFieldSlot( @@ -454,4 +454,136 @@ class UtilsTest { } } } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsForAvoidBraveChain( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(result).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getCardsForAvoidBraveChain( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(result).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsForAvoidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + assertThat(result).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getCardsForAvoidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + assertThat(result).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 1-KamaNP`() { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getCardsForAvoidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + assertThat(result).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2-ScathachNP`() { + val cards = AttackLineUps.Standard.lineup2 + val result = utils.getCardsForAvoidBraveChain( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + assertThat(result).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + + @Test + fun `getCardsForAvoidBraveChain, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + val result = utils.getCardsForAvoidBraveChain( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(result).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `getCardsForAvoidBraveChain, BusterFocus - lineup02 (1KB,5KB,2KB,3NA,4NA)`() { + val cards = AttackLineUps.BusterFocus.lineup02 + val result = utils.getCardsForAvoidBraveChain( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(result).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.D, + ) + } } \ No newline at end of file From f4b5c7ff1820515f14736a23622c2c8e267161ec Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 20:02:40 +0800 Subject: [PATCH 060/124] Rename Utils to AttackUtils --- .../scripts/modules/attack/AttackPriorityHandler.kt | 2 +- .../scripts/modules/attack/{Utils.kt => AttackUtils.kt} | 2 +- .../scripts/modules/attack/AvoidChainHandler.kt | 2 +- .../scripts/modules/attack/BraveChainHandler.kt | 3 +-- .../scripts/modules/attack/CardChainPriorityHandler.kt | 2 +- .../scripts/modules/attack/ColorChainHandler.kt | 3 +-- .../scripts/modules/attack/MightyChainHandler.kt | 2 +- .../scripts/attack/{UtilsTest.kt => AttackUtilsTest.kt} | 6 +++--- .../fate_grand_automata/scripts/attack/AvoidChainTest.kt | 4 ++-- .../fate_grand_automata/scripts/attack/BraveChainTest.kt | 4 ++-- .../scripts/attack/CardChainPriorityIntegrationTest.kt | 4 ++-- .../fate_grand_automata/scripts/attack/ColorChainTest.kt | 5 ++--- .../fate_grand_automata/scripts/attack/MightyChainTest.kt | 4 ++-- 13 files changed, 20 insertions(+), 23 deletions(-) rename scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/{Utils.kt => AttackUtils.kt} (99%) rename scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/{UtilsTest.kt => AttackUtilsTest.kt} (99%) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index d1fb6b4f0..8a26d1a0f 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -15,7 +15,7 @@ import javax.inject.Inject class AttackPriorityHandler @Inject constructor( private val cardChainPriorityHandler: CardChainPriorityHandler, private val braveChainHandler: BraveChainHandler, - private val utils: Utils, + private val utils: AttackUtils, ) { fun rearrange( cards: List, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt similarity index 99% rename from scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt rename to scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index fbba62496..df99e7cab 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/Utils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -10,7 +10,7 @@ import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @ScriptScope -class Utils @Inject constructor() { +class AttackUtils @Inject constructor() { fun getCardsPerFieldSlotMap ( cards: List, npUsage: NPUsage = NPUsage.none diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index beddaa333..8028084ee 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -11,7 +11,7 @@ import kotlin.collections.set @ScriptScope class AvoidChainHandler @Inject constructor( - private val utils: Utils + private val utils: AttackUtils ) { fun pick( cards: List, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 21ac9d67b..001498af6 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -1,7 +1,6 @@ package io.github.fate_grand_automata.scripts.modules.attack import io.github.fate_grand_automata.scripts.enums.BraveChainEnum -import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard @@ -11,7 +10,7 @@ import javax.inject.Inject @ScriptScope class BraveChainHandler @Inject constructor( - private val utils: Utils + private val utils: AttackUtils ) { fun pick( cards: List, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index cb01d164d..9d6b08705 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -14,7 +14,7 @@ class CardChainPriorityHandler @Inject constructor( private val mightyChainHandler: MightyChainHandler, private val colorChainHandler: ColorChainHandler, private val avoidChainHandler: AvoidChainHandler, - private val utils: Utils + private val utils: AttackUtils ) { fun pick( cards: List, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index 63e774cea..985c96a95 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -5,13 +5,12 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @ScriptScope open class ColorChainHandler @Inject constructor( - private val utils: Utils + private val utils: AttackUtils ) { // Returns null if uniqueCards cannot be found fun pick ( diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 066471145..a8f41ec86 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -11,7 +11,7 @@ import kotlin.collections.Map @ScriptScope class MightyChainHandler @Inject constructor( - private val utils: Utils + private val utils: AttackUtils ) { // We want 3 unique types, the magic number val totalUniqueCardTypesPermitted = 3 diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt similarity index 99% rename from scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt rename to scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt index 84c66392e..59f4fabca 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/UtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt @@ -9,11 +9,11 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage -import io.github.fate_grand_automata.scripts.modules.attack.Utils +import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import kotlin.test.Test -class UtilsTest { - val utils = Utils() +class AttackUtilsTest { + val utils = AttackUtils() val BRAVE_CHAIN_ENUM_LIST = BraveChainEnum.entries @Test diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index bdd591abc..d52e9425c 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -9,10 +9,10 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import kotlin.test.Test import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.Utils +import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class AvoidChainTest { - val avoidChain = AvoidChainHandler(utils = Utils()) + val avoidChain = AvoidChainHandler(utils = AttackUtils()) @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 4fd035745..4f73a2040 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -6,7 +6,7 @@ import assertk.assertions.isEmpty import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.NPUsage -import io.github.fate_grand_automata.scripts.modules.attack.Utils +import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import kotlin.test.BeforeTest import kotlin.test.Test @@ -17,7 +17,7 @@ class BraveChainTest { @BeforeTest fun init() { braveChainHandler = BraveChainHandler( - utils = Utils() + utils = AttackUtils() ) } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index e478240d9..af5b09a08 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -13,7 +13,7 @@ import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandler import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.Utils +import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import io.github.fate_grand_automata.scripts.modules.attack.CardChainPriorityHandler import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler @@ -30,7 +30,7 @@ class CardChainPriorityIntegrationTest { @BeforeTest fun init() { - val utils = Utils() + val utils = AttackUtils() braveChainHandler = BraveChainHandler(utils) mightyChainHandler = MightyChainHandler(utils) colorChainHandler = ColorChainHandler(utils) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 84150d722..55a307ce5 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -3,17 +3,16 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty -import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import kotlin.test.Test import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.Utils +import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class ColorChainTest { - val colorChain = ColorChainHandler(utils = Utils()) + val colorChain = ColorChainHandler(utils = AttackUtils()) val cardTypeList = CardTypeEnum.entries @Test diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 1e290f3d2..4a60758a8 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -9,7 +9,7 @@ import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.Utils +import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import kotlin.test.BeforeTest import kotlin.test.Test @@ -19,7 +19,7 @@ class MightyChainTest { @BeforeTest fun init() { mightyChainHandler = MightyChainHandler( - utils = Utils() + utils = AttackUtils() ) } From 53bcc605243e0a258c63ec84a69b45edf3265274 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 20:18:30 +0800 Subject: [PATCH 061/124] Remove the unnecessary inject by converting it to a normal class constructor. --- .../fate_grand_automata/scripts/modules/ApplyBraveChains.kt | 2 +- .../fate_grand_automata/scripts/modules/attack/AttackUtils.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 491efbcf9..f58ef961c 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -11,7 +11,7 @@ import java.util.Collections import javax.inject.Inject @ScriptScope -class ApplyBraveChains @Inject constructor() { +class ApplyBraveChains () { private fun rearrange( cards: List, rearrange: Boolean, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index df99e7cab..df34638e4 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -10,7 +10,7 @@ import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @ScriptScope -class AttackUtils @Inject constructor() { +class AttackUtils () { fun getCardsPerFieldSlotMap ( cards: List, npUsage: NPUsage = NPUsage.none From c16e1a7501267f6e55a499177f1d64cd7b169df2 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 22:59:36 +0800 Subject: [PATCH 062/124] Add more tests. Refactor some tests --- .../scripts/attack/AttackLineUps.kt | 4 +- .../scripts/attack/AttackUtilsTest.kt | 24 +-- .../scripts/attack/MightyChainTest.kt | 142 +++++++++++++++--- 3 files changed, 135 insertions(+), 35 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index b85aae241..cd51b51ed 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -200,10 +200,10 @@ class AttackLineUps { val lineup3 = listOf(scathach5WQ, scathach2WQ, scathach3WA, scathach1WB, scathach4WB) // 3 Quick, 2 Buster; Buster starting card - /** scathach1WB, scathach2WQ, scathach3WAltQ, scathach4WB, scathach5WQ */ + /** servant1WB, servant2WQ, servant3WAltQ, servant4WB, servant5WQ */ val lineup4 = listOf(scathach1WB, scathach2WQ, scathach3WAltQ, scathach4WB, scathach5WQ) // 3 Quick, 2 Buster; Quick starting card - /** scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB */ + /** servant5WQ, servant2WQ, servant3WAltQ, servant1WB, servant4WB */ val lineup5 = listOf(scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB) } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt index 59f4fabca..02f97b7cf 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt @@ -14,7 +14,7 @@ import kotlin.test.Test class AttackUtilsTest { val utils = AttackUtils() - val BRAVE_CHAIN_ENUM_LIST = BraveChainEnum.entries + val braveChainEnums = BraveChainEnum.entries @Test fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { @@ -279,7 +279,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -292,7 +292,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup2 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -305,7 +305,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup2 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -319,7 +319,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -338,7 +338,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -357,7 +357,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, Standard -lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -371,7 +371,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, Standard -lineup1 (1SB,2KQ,3NA,4NA,5SQ) + All 3 NP`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -385,7 +385,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.BusterFocus.lineup01 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -402,7 +402,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB)`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.BusterFocus.lineup03 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -419,7 +419,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB) - 2Kiyo-NP`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.BusterFocus.lineup03 val result = utils.getBraveChainFieldSlot( cards = cards, @@ -438,7 +438,7 @@ class AttackUtilsTest { @Test fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB) - 3Nero-NP`() { - for (braveChainEnum in BRAVE_CHAIN_ENUM_LIST) { + for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.BusterFocus.lineup03 val result = utils.getBraveChainFieldSlot( cards = cards, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 4a60758a8..28b3630c8 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -31,8 +31,13 @@ class MightyChainTest { braveChainEnum = BraveChainEnum.WithNP )?.map { it.card } ?: emptyList() - // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) } @Test @@ -42,8 +47,13 @@ class MightyChainTest { cards = cards )?.map { it.card } ?: emptyList() - // Expect 1SB,2KQ,3NA,5SQ,4NA - 15324 - AECBD - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D) + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D + ) } @Test @@ -54,7 +64,7 @@ class MightyChainTest { npUsage = NPUsage(setOf(CommandCard.NP.A), 0) )?.map { it.card } ?: emptyList() - // Unable to Brave Chain with Kama. Will ignore and return result of withNp + // Cannot detect NP type, so unable to Mighty Chain assertThat(picked).isEmpty() } @@ -69,8 +79,13 @@ class MightyChainTest { ) )?.map { it.card } ?: emptyList() - // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + ) } @Test @@ -96,8 +111,13 @@ class MightyChainTest { ) )?.map { it.card } ?: emptyList() - // Expect 1SB,3NA,2KQ,4NA,5SQ - 13245 - ACBDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.C, CommandCard.Face.B, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + ) } @Test @@ -123,31 +143,106 @@ class MightyChainTest { ) )?.map { it.card } ?: emptyList() - // Expect 1SB,2KQ,3NA,4NA,5SQ - 12345 - ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 1Kama-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup2 + val picked = mightyChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.D, + ) + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2Scathach-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup2 + val picked = mightyChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.D, + ) + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 3Nero-NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup2 + val picked = mightyChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) } @Test - fun `SingleServantOnly - lineup1 (BQABQ)`() { + fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 val picked = mightyChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() // Expect same result as input - // Expect BQABQ / 12345 / ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) } @Test - fun `SingleServantOnly - lineup2 (BBQQA)`() { + fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 val picked = mightyChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() - // Expect 1SB,5SQ,3NA,4NA,2KQ / 15342 / AECDB - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.B) + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.B, + ) } @Test @@ -157,13 +252,18 @@ class MightyChainTest { cards = cards, )?.map { it.card } ?: emptyList() - // Expect QABQB / 53124 / ECABD - assertThat(picked).containsExactly(CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.D) + assertThat(picked).containsExactly( + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.D + ) } // Scenario for when 2 card types are found but not the 3rd @Test - fun `SingleServantOnly - lineup4 (BQQBQ) - No mighty chain`() { + fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup4 val picked = mightyChainHandler.pick( cards = cards @@ -174,7 +274,7 @@ class MightyChainTest { // Scenario for when 2 card types are found but not the 3rd @Test - fun `SingleServantOnly - lineup5 (QQQBB) - No mighty chain`() { + fun `SingleServantOnly - lineup5 (1Q,2Q,3Q,4B,5B) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup5 val picked = mightyChainHandler.pick( cards = cards From dd14ba7a8867733037cdab346bdf37a9d94c2a22 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 4 Sep 2025 23:47:19 +0800 Subject: [PATCH 063/124] Make BraveChainHandler use util function for getting BraveChains. Add more tests to BraveChainTest. --- .../scripts/modules/attack/AttackUtils.kt | 2 +- .../modules/attack/BraveChainHandler.kt | 20 +- .../scripts/attack/AttackLineUps.kt | 2 +- .../scripts/attack/BraveChainTest.kt | 317 ++++++++++++++---- 4 files changed, 270 insertions(+), 71 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index df34638e4..9fd2c3f82 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -97,7 +97,7 @@ class AttackUtils () { val firstNp = npUsage.nps.firstOrNull() val fieldSlot = firstNp?.toFieldSlot() // Only return the field slot if it is valid for a Brave Chain - if (cards.filter { it.fieldSlot == fieldSlot }.size > 1) return fieldSlot + if (cards.filter { it.fieldSlot == fieldSlot }.size >= 2) return fieldSlot } else if (braveChainEnum == BraveChainEnum.Always) { // Force brave chain only if it always wants a Brave Chain val braveChainCapableFieldSlots = getFieldSlotsWithValidBraveChain(cards, npUsage) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 001498af6..997655e95 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -21,20 +21,14 @@ class BraveChainHandler @Inject constructor( val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) if (!isBraveChainAllowed(braveChainEnum, npUsage, cardsPerFieldSlotMap)) return null - val braveChainCapableFieldSlots = utils.getFieldSlotsWithValidBraveChain(cardsPerFieldSlotMap) - - val npFieldSlot = if (npUsage.nps.size == 1) npUsage.nps.firstOrNull()?.toFieldSlot() else null - - val braveChainPriorityCard = cards.firstOrNull { - // Use the first valid one, since it is already the highest priority - braveChainCapableFieldSlots.contains(it.fieldSlot) && - // And if there is an NP, it must match the NP - (npFieldSlot == null || npFieldSlot == it.fieldSlot) - } - val priorityFieldSlot = braveChainPriorityCard?.fieldSlot - - // Safety check + // Always returns a valid field slot if there is one for BraveChain + val priorityFieldSlot = utils.getBraveChainFieldSlot( + braveChainEnum = braveChainEnum, + cards = cards, + npUsage = npUsage, + ) if (priorityFieldSlot == null) return null + val selectedCards = cards .filter { it.fieldSlot == priorityFieldSlot } .take((3 - npUsage.nps.size).coerceAtLeast(0)) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index cd51b51ed..d4345ea76 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -101,7 +101,7 @@ class AttackLineUps { affinity = CardAffinityEnum.Resist ) val nero4BAlt = ParsedCard( - card = CommandCard.Face.E, + card = CommandCard.Face.D, servant = TeamSlot.C, fieldSlot = FieldSlot.C, type = CardTypeEnum.Buster, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 4f73a2040..7d4097d73 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -3,8 +3,11 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty +import assertk.assertions.isEqualTo +import assertk.assertions.isNull import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.models.CommandCard +import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler @@ -14,6 +17,8 @@ import kotlin.test.Test class BraveChainTest { lateinit var braveChainHandler: BraveChainHandler + val braveChainEnums = BraveChainEnum.entries + @BeforeTest fun init() { braveChainHandler = BraveChainHandler( @@ -22,89 +27,289 @@ class BraveChainTest { } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), None - No Brave chain`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChainHandler.pick( - cards = cards, - braveChainEnum = BraveChainEnum.None - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum + )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), WithNP - No Brave chain`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChainHandler.pick( - cards = cards, - braveChainEnum = BraveChainEnum.WithNP - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, WithNP - No Brave chain` () { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChainHandler.pick( - cards = cards, - braveChainEnum = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0) - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, WithNP`() { + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = braveChainHandler.pick( - cards = cards, - braveChainEnum = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0) - )?.map { it.card } ?: emptyList() - - // Expect 1SB,2KQ,3NA,5SQ,4NA - 15324 - AECBD - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.E, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D) + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } } @Test - fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (3Nero)`() { - val cards = AttackLineUps.BusterFocus.lineup01 - val picked = braveChainHandler.pick( - cards = cards, - braveChainEnum = BraveChainEnum.WithNP, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - ) + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama + 2Scathach)`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), Avoid`() { - val cards = AttackLineUps.Standard.lineup1 - val picked = braveChainHandler.pick( - cards = cards, - braveChainEnum = BraveChainEnum.Avoid - )?.map { it.card } ?: emptyList() + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum + )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test - fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA), Avoid`() { + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup2 - val picked = braveChainHandler.pick( - cards = cards, - braveChainEnum = BraveChainEnum.Avoid - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2Scathach-NP`() { + val cards = AttackLineUps.Standard.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 3Nero-NP`() { + val cards = AttackLineUps.Standard.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB) + 1 NP (2Kiyohime)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB) + 1 NP (3Nero)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `BusterFocus - lineup02 (1KB,5KB,2KB,3NA,4NA)`() { + val cards = AttackLineUps.BusterFocus.lineup02 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `BusterFocus - lineup05 (1KB,2KB,3NA,4NB,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup05 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + when (braveChainEnum) { + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } } } \ No newline at end of file From 29957fbcfaa620d4369238e886954cdf9da9ac2c Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 5 Sep 2025 00:07:02 +0800 Subject: [PATCH 064/124] Add active selection of cards for BraveChainEnum.Avoid in BraveChainHandler --- .../modules/attack/BraveChainHandler.kt | 13 +- .../scripts/attack/AvoidChainTest.kt | 17 +++ .../scripts/attack/BraveChainTest.kt | 133 ++++++++++++++++-- .../CardChainPriorityIntegrationTest.kt | 5 +- 4 files changed, 156 insertions(+), 12 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 997655e95..abfc43ce6 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -10,7 +10,8 @@ import javax.inject.Inject @ScriptScope class BraveChainHandler @Inject constructor( - private val utils: AttackUtils + private val utils: AttackUtils, + private val avoidChainHandler: AvoidChainHandler, ) { fun pick( cards: List, @@ -19,6 +20,16 @@ class BraveChainHandler @Inject constructor( cardCountPerFieldSlotMap: Map? = null, ): List? { val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) + + if (braveChainEnum == BraveChainEnum.Avoid) { + return avoidChainHandler.pick( + cards = cards, + npUsage = npUsage, + avoidBraveChains = true, + avoidCardChains = false, + ) + } + if (!isBraveChainAllowed(braveChainEnum, npUsage, cardsPerFieldSlotMap)) return null // Always returns a valid field slot if there is one for BraveChain diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index d52e9425c..21b910668 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -46,6 +46,23 @@ class AvoidChainTest { ) } + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChain.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 7d4097d73..3de4ff500 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -9,33 +9,64 @@ import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils +import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import kotlin.test.BeforeTest import kotlin.test.Test class BraveChainTest { lateinit var braveChainHandler: BraveChainHandler + lateinit var avoidChainHandler: AvoidChainHandler val braveChainEnums = BraveChainEnum.entries @BeforeTest fun init() { + val utils = AttackUtils() + avoidChainHandler = AvoidChainHandler( + utils = utils + ) braveChainHandler = BraveChainHandler( - utils = AttackUtils() + utils = AttackUtils(), + avoidChainHandler = avoidChainHandler, ) } + fun assertDefaultAvoidChain ( + cards: List, + picked: List, + braveChainEnum: BraveChainEnum, + npUsage: NPUsage = NPUsage.none, + ) { + if (braveChainEnum != BraveChainEnum.Avoid) return + val defaultAvoid = avoidChainHandler.pick( + cards = cards, + npUsage = npUsage, + avoidBraveChains = true, + avoidCardChains = false, + )?.map { it.card } ?: emptyList() + assertThat(picked, BraveChainEnum.Avoid.toString()).isEqualTo(defaultAvoid) + } + @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { val picked = braveChainHandler.pick( cards = cards, - braveChainEnum = braveChainEnum + braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).isEmpty() + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } } } @@ -46,10 +77,18 @@ class BraveChainTest { val picked = braveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).isEmpty() + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } } } @@ -64,6 +103,12 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + picked = picked, + ) BraveChainEnum.WithNP, BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -89,6 +134,12 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + picked = picked, + ) BraveChainEnum.WithNP, BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -110,10 +161,18 @@ class BraveChainTest { val picked = braveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).isEmpty() + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } } } @@ -126,7 +185,14 @@ class BraveChainTest { braveChainEnum = braveChainEnum )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).isEmpty() + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } } } @@ -137,10 +203,18 @@ class BraveChainTest { val picked = braveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0) + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).isEmpty() + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } } } @@ -155,6 +229,12 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + picked = picked, + ) BraveChainEnum.WithNP, BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -180,6 +260,12 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + picked = picked, + ) BraveChainEnum.WithNP, BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -204,6 +290,11 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( CommandCard.Face.A, @@ -228,6 +319,12 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + picked = picked, + ) BraveChainEnum.WithNP, BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -253,6 +350,12 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + picked = picked, + ) BraveChainEnum.WithNP, BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -277,6 +380,11 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( CommandCard.Face.A, @@ -300,6 +408,11 @@ class BraveChainTest { )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) BraveChainEnum.Always, -> assertThat(picked, braveChainEnum.toString()).containsExactly( CommandCard.Face.A, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index af5b09a08..1321a466e 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -31,10 +31,13 @@ class CardChainPriorityIntegrationTest { @BeforeTest fun init() { val utils = AttackUtils() - braveChainHandler = BraveChainHandler(utils) mightyChainHandler = MightyChainHandler(utils) colorChainHandler = ColorChainHandler(utils) avoidChainHandler = AvoidChainHandler(utils) + braveChainHandler = BraveChainHandler( + utils = utils, + avoidChainHandler = avoidChainHandler + ) attackPriorityHandler = AttackPriorityHandler( braveChainHandler = braveChainHandler, From e31204448e0692b46ba7a5d10458552eb1e0fdfd Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 5 Sep 2025 00:07:39 +0800 Subject: [PATCH 065/124] Give variable more appropriate name --- .../scripts/attack/AvoidChainTest.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index 21b910668..366d65c92 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -12,12 +12,12 @@ import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class AvoidChainTest { - val avoidChain = AvoidChainHandler(utils = AttackUtils()) + val avoidChainHandler = AvoidChainHandler(utils = AttackUtils()) @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -33,7 +33,7 @@ class AvoidChainTest { @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -49,7 +49,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() @@ -66,7 +66,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -86,7 +86,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( @@ -106,7 +106,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( @@ -126,7 +126,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -142,7 +142,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -158,7 +158,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -175,7 +175,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -192,7 +192,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -211,7 +211,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), npTypes = mapOf( @@ -232,7 +232,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( @@ -253,7 +253,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1B, 2B), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( @@ -274,7 +274,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChain.pick( + val picked = avoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( From 766f49d5e72fd1050a42ea4aeddf7177f4de27e2 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 5 Sep 2025 00:17:24 +0800 Subject: [PATCH 066/124] Refactor to make more easy to read --- .../scripts/modules/attack/AttackUtils.kt | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index 9fd2c3f82..534ac25e8 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -114,43 +114,41 @@ class AttackUtils () { ): List? { val npFieldSlots = npUsage.nps.map { it.toFieldSlot() }.toSet() // If there is 2 or more NP, it will never be a BraveChain - if (npFieldSlots.size <= 1) { - val fieldSlotSet = cards.map { it.fieldSlot }.toSet() - if ( - // If there is only 1 unique field slot in the list - fieldSlotSet.size == 1 - && ( - // no NPs to cancel out - npFieldSlots.isEmpty() - // Or there is exactly 1 NP and it is the same - || npFieldSlots.first() == fieldSlotSet.first() - ) - ) return null + if (npFieldSlots.size >= 2) return cards - val npFieldSlot = npFieldSlots.firstOrNull() + val fieldSlotSet = cards.map { it.fieldSlot }.toSet() + if ( + // If there is only 1 unique field slot in the list + fieldSlotSet.size == 1 + && ( + // no NPs to cancel out + npFieldSlots.isEmpty() + // Or there is exactly 1 NP and it is the same + || npFieldSlots.first() == fieldSlotSet.first() + ) + ) return null - // Otherwise, it is valid, but the correct cards must be selected - val cardsNeeded = 3 - npUsage.nps.size - val filteredCards = cards.take(cardsNeeded).toMutableList() - val firstFieldSlot = filteredCards.first().fieldSlot - if ( - // If all cards are the same field slot - filteredCards.all { it.fieldSlot == firstFieldSlot } - && ( - // and it matches the NP - npFieldSlot == firstFieldSlot - // or the NP is null - || npFieldSlot == null - ) - ) { - val differentSlot = cards.firstOrNull() { it.fieldSlot != firstFieldSlot } - if (differentSlot == null) return null - // Just add new card to index 1, aka 2nd card - filteredCards.add(1, differentSlot) - } - return filteredCards + (cards - filteredCards) - } + val npFieldSlot = npFieldSlots.firstOrNull() - return cards + // Otherwise, it is valid, but the correct cards must be selected + val cardsNeeded = 3 - npUsage.nps.size + val filteredCards = cards.take(cardsNeeded).toMutableList() + val firstFieldSlot = filteredCards.first().fieldSlot + if ( + // If all cards are the same field slot + filteredCards.all { it.fieldSlot == firstFieldSlot } + && ( + // and it matches the NP + npFieldSlot == firstFieldSlot + // or the NP is null + || npFieldSlot == null + ) + ) { + val differentSlot = cards.firstOrNull() { it.fieldSlot != firstFieldSlot } + if (differentSlot == null) return null + // Just add new card to index 1, aka 2nd card + filteredCards.add(1, differentSlot) + } + return filteredCards + (cards - filteredCards) } } \ No newline at end of file From 9452750add3f14f40527eb41c064c13a11dd6877 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 5 Sep 2025 01:36:16 +0800 Subject: [PATCH 067/124] Add special function for handling AvoidBraveChain with MightyChain. Add tests for edge cases. --- .../scripts/modules/attack/AttackUtils.kt | 4 +- .../modules/attack/MightyChainHandler.kt | 54 +- .../scripts/attack/AttackLineUps.kt | 54 ++ .../scripts/attack/MightyChainTest.kt | 801 +++++++++++++----- 4 files changed, 696 insertions(+), 217 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index 534ac25e8..37359e6e1 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -114,11 +114,11 @@ class AttackUtils () { ): List? { val npFieldSlots = npUsage.nps.map { it.toFieldSlot() }.toSet() // If there is 2 or more NP, it will never be a BraveChain - if (npFieldSlots.size >= 2) return cards + if (npFieldSlots.size >= 2) return null val fieldSlotSet = cards.map { it.fieldSlot }.toSet() if ( - // If there is only 1 unique field slot in the list + // If there is only 1 unique field slot in the list fieldSlotSet.size == 1 && ( // no NPs to cancel out diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index a8f41ec86..30bd05cf2 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -5,7 +5,9 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope +import kotlinx.coroutines.selects.select import javax.inject.Inject import kotlin.collections.Map @@ -98,13 +100,14 @@ class MightyChainHandler @Inject constructor( // If there isn't a valid list of cards, reject and return null if (uniqueCardTypes.size < cardsToFind) return null - // Do not have any BraveChains or it is invalid if (braveChainEnum == BraveChainEnum.Avoid) { - val newSelection = utils.getCardsForAvoidBraveChain( - cards = selectedCards + cachedFilteredCards, + val newSelection = getMightyChainWithoutBraveChain( + cards = cards, npUsage = npUsage, + selectedCards = selectedCards.toList() ) if (newSelection == null) return null + // if it is not null, there is a valid non-Brave Chain option selectedCards = newSelection.toMutableList() } @@ -115,6 +118,51 @@ class MightyChainHandler @Inject constructor( return combinedCards } + fun getMightyChainWithoutBraveChain ( + cards: List, + selectedCards: List, + npUsage: NPUsage = NPUsage.none, + ): List? { + val cardsPerFieldSlot = utils.getCardsPerFieldSlotMap(cards, npUsage) + // If there is only 1 unique field slot throughout, this is a valid entry (since it is impossible to avoid Brave Chain) + if (cardsPerFieldSlot.size == 1) return selectedCards + + val allFieldSlots = selectedCards.mapNotNull { it.fieldSlot } + npUsage.nps.map { it.toFieldSlot() } + val uniqueFieldSlotsSet = allFieldSlots.toSet() + // If there is more than 1 unique fieldSlot, returning is fine + if (uniqueFieldSlotsSet.size > 1) return selectedCards + + // Otherwise, attempt to substitute cards + val newSelection = mutableListOf() + var isNewSelectionValid = false + for (card in selectedCards) { + // if already valid, add the card and continue + if (isNewSelectionValid) { + newSelection.add(card) + continue + } + val validReplacements = cards.filter{ + it.type == card.type + && it.fieldSlot != card.fieldSlot + } + if (validReplacements.isEmpty()) { + newSelection.add(card) + } else { + // Found a suitable replacement + newSelection.add(validReplacements.first()) + isNewSelectionValid = true + } + } + + // If a suitable replacement was found, return is allowed + if (isNewSelectionValid) { + return newSelection + } + + // Otherwise, return null + return null + } + fun isMightyChainAllowed ( cards: List, npUsage: NPUsage, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index d4345ea76..60619e5ba 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -57,6 +57,60 @@ class AttackLineUps { val lineup2 = listOf(scathach1WB, scathach5WQ, kama2Q, nero3RA, nero4RA) } + data object MightyOutlier { + val scathach1WB = ParsedCard( + card = CommandCard.Face.A, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Weak + ) + val scathach2WA = ParsedCard( + card = CommandCard.Face.B, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Weak + ) + val nero3RA = ParsedCard( + card = CommandCard.Face.C, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Resist + ) + val nero4RA = ParsedCard( + card = CommandCard.Face.D, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Resist + ) + val scathach5WQ = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Quick, + affinity = CardAffinityEnum.Weak + ) + + /** + * Scathach, Nero; + * Scathach 1WB, Scathach 2Q, Nero 3RA, Nero 4RA, Scathach 5WQ + */ + val lineup1 = listOf(scathach1WB, scathach2WA, nero3RA, nero4RA, scathach5WQ) + val lineup1_DefaultMightyChain = listOf(scathach1WB, scathach2WA, scathach5WQ) + val lineup1_DefaultMightyChain_WithQuickNP = listOf(scathach1WB, scathach2WA) + val lineup1_DefaultMightyChain_WithNonScathachBusterNP = listOf(scathach2WA, scathach5WQ) + val lineup1_DefaultMightyChain_WithNonScathachArtsNP = listOf(scathach1WB, scathach5WQ) + + /** + * Scathach, Nero; + * Scathach 1WB, Scathach 5WQ, Scathach 2Q, Nero 3RA, Nero 4RA + */ + val lineup2 = listOf(scathach1WB, scathach5WQ, scathach2WA, nero3RA, nero4RA) + } + data object BusterFocus { val kiyohime1B = ParsedCard( card = CommandCard.Face.A, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 28b3630c8..781f7057e 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -3,11 +3,13 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty +import assertk.assertions.isEqualTo import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import kotlin.test.BeforeTest @@ -15,272 +17,454 @@ import kotlin.test.Test class MightyChainTest { lateinit var mightyChainHandler: MightyChainHandler + lateinit var utils: AttackUtils + + val braveChainEnums = BraveChainEnum.entries @BeforeTest fun init() { + utils = AttackUtils() mightyChainHandler = MightyChainHandler( - utils = AttackUtils() + utils = utils ) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( - cards = cards, - braveChainEnum = BraveChainEnum.WithNP - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } } @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val picked = mightyChainHandler.pick( - cards = cards - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.B, - CommandCard.Face.D - ) + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + ) + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0) - )?.map { it.card } ?: emptyList() - - // Cannot detect NP type, so unable to Mighty Chain - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + // Cannot detect NP type, so unable to Mighty Chain + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, ) - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.B, - CommandCard.Face.D, - CommandCard.Face.E, - ) + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0) - )?.map { it.card } ?: emptyList() - - // Unable to Mighty Chain with Scathach NP since type is unknown. - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0) + )?.map { it.card } ?: emptyList() + + // Unable to Mighty Chain with Scathach NP since type is unknown. + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.B to CardTypeEnum.Quick - ) - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } + } - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.B, - CommandCard.Face.D, - CommandCard.Face.E, - ) + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with Arts as npType`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.WithNP, + BraveChainEnum.Always -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0) - )?.map { it.card } ?: emptyList() - - // Unable to Mighty Chain with Nero NP since type is unknown. - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0) + )?.map { it.card } ?: emptyList() + + // Unable to Mighty Chain with Nero NP since type is unknown. + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ) - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } + } - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes & forcedBraveChain`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + forceBraveChain = true + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup2 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.D, - ) + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.D, + ) + } } @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup2 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.B to CardTypeEnum.Quick - ), - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.D, + ) + } + } + } - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.D, - ) + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2Scathach-NP, with Arts as npType`() { + val cards = AttackLineUps.Standard.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.None, + BraveChainEnum.WithNP, + BraveChainEnum.Always -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + } } @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup2 - val picked = mightyChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ), - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - ) + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + } } @Test - fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { + fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = mightyChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } - // Expect same result as input - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + @Test + fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q) + Buster NP, with npTypes`() { + val cards = AttackLineUps.SingleServantOnly.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Buster + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } } @Test fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = mightyChainHandler.pick( - cards = cards - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.B, - ) + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.B, + ) + } } @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = mightyChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.D - ) + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.D + ) + } } // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = mightyChainHandler.pick( - cards = cards - )?.map { it.card } ?: emptyList() - - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup5 (1Q,2Q,3Q,4B,5B) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = mightyChainHandler.pick( - cards = cards - )?.map { it.card } ?: emptyList() - - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } /** @@ -289,69 +473,262 @@ class MightyChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes - No mighty chain`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts, + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + /** + * Special edge cases + */ + @Test + fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.MightyOutlier.lineup1 + val picked = mightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) + selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.E, + ) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { - val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( + fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + ScathachNP`() { + val cards = AttackLineUps.MightyOutlier.lineup1 + val picked = mightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.C to CardTypeEnum.Arts - ) + selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain_WithQuickNP, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), )?.map { it.card } ?: emptyList() - // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE - assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + ) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { - val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( + fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + Non-Scathach Buster NP`() { + val cards = AttackLineUps.MightyOutlier.lineup1 + val picked = mightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain_WithNonScathachBusterNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + assertThat(picked).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + ) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { - val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( + fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + Non-Scathach Arts NP`() { + val cards = AttackLineUps.MightyOutlier.lineup1 + val picked = mightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick - ) + selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain_WithNonScathachArtsNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + ) } @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes - No mighty chain`() { - val cards = AttackLineUps.Standard.lineup1 - val picked = mightyChainHandler.pick( + fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + Non-Scathach Quick NP`() { + val cards = AttackLineUps.MightyOutlier.lineup1 + val picked = mightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick, - FieldSlot.C to CardTypeEnum.Arts, - ) + selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain_WithQuickNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() - assertThat(picked).isEmpty() + assertThat(picked).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + ) + } + + @Test + fun `MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.MightyOutlier.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.D, + ) + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + } + } + + @Test + fun `MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + 1 NP (Scathach)`() { + val cards = AttackLineUps.MightyOutlier.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + ) + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + ) + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } + } + + + @Test + fun `MightyOutlier - lineup2 (1SB,5SQ,2SA,3NA,4NA)`() { + val cards = AttackLineUps.MightyOutlier.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + ) + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + } } } \ No newline at end of file From 6ba7f471724f8071861d6346d0de4b867bb9d891 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 5 Sep 2025 02:23:30 +0800 Subject: [PATCH 068/124] Move function to ColorChainHandler. Move tests. Redo getCardsForAvoidBraveChain function --- .../scripts/modules/attack/AttackUtils.kt | 45 ---- .../attack/CardChainPriorityHandler.kt | 2 + .../modules/attack/ColorChainHandler.kt | 70 +++++- .../modules/attack/MightyChainHandler.kt | 14 +- .../scripts/attack/AttackUtilsTest.kt | 132 ------------ .../scripts/attack/ColorChainTest.kt | 200 ++++++++++++++++++ 6 files changed, 277 insertions(+), 186 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index 37359e6e1..b9e77e914 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -7,7 +7,6 @@ import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope -import javax.inject.Inject @ScriptScope class AttackUtils () { @@ -107,48 +106,4 @@ class AttackUtils () { } return null } - - fun getCardsForAvoidBraveChain ( - cards: List, - npUsage: NPUsage = NPUsage.none, - ): List? { - val npFieldSlots = npUsage.nps.map { it.toFieldSlot() }.toSet() - // If there is 2 or more NP, it will never be a BraveChain - if (npFieldSlots.size >= 2) return null - - val fieldSlotSet = cards.map { it.fieldSlot }.toSet() - if ( - // If there is only 1 unique field slot in the list - fieldSlotSet.size == 1 - && ( - // no NPs to cancel out - npFieldSlots.isEmpty() - // Or there is exactly 1 NP and it is the same - || npFieldSlots.first() == fieldSlotSet.first() - ) - ) return null - - val npFieldSlot = npFieldSlots.firstOrNull() - - // Otherwise, it is valid, but the correct cards must be selected - val cardsNeeded = 3 - npUsage.nps.size - val filteredCards = cards.take(cardsNeeded).toMutableList() - val firstFieldSlot = filteredCards.first().fieldSlot - if ( - // If all cards are the same field slot - filteredCards.all { it.fieldSlot == firstFieldSlot } - && ( - // and it matches the NP - npFieldSlot == firstFieldSlot - // or the NP is null - || npFieldSlot == null - ) - ) { - val differentSlot = cards.firstOrNull() { it.fieldSlot != firstFieldSlot } - if (differentSlot == null) return null - // Just add new card to index 1, aka 2nd card - filteredCards.add(1, differentSlot) - } - return filteredCards + (cards - filteredCards) - } } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 9d6b08705..078440587 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -40,6 +40,7 @@ class CardChainPriorityHandler @Inject constructor( braveChainEnum = braveChainEnum, forceBraveChain = forceBraveChain, cardCountPerCardTypeMap = cardCountPerCardTypeMap, + cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) ChainTypeEnum.Arts, ChainTypeEnum.Buster, @@ -56,6 +57,7 @@ class CardChainPriorityHandler @Inject constructor( braveChainEnum = braveChainEnum, forceBraveChain = forceBraveChain, cardCountPerCardTypeMap = cardCountPerCardTypeMap, + cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) ChainTypeEnum.Avoid -> avoidChainHandler.pick( cards = cards, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index 985c96a95..f4cda2c32 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -5,8 +5,10 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard +import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject +import kotlin.collections.Map @ScriptScope open class ColorChainHandler @Inject constructor( @@ -20,7 +22,8 @@ open class ColorChainHandler @Inject constructor( npTypes: Map = emptyMap(), braveChainEnum: BraveChainEnum = BraveChainEnum.None, forceBraveChain: Boolean = false, - cardCountPerCardTypeMap: Map? = null + cardCountPerFieldSlotMap: Map? = null, + cardCountPerCardTypeMap: Map? = null, ): List? { // NEVER want to make a chain of Unknown cards if (cardType == CardTypeEnum.Unknown) return null @@ -43,12 +46,15 @@ open class ColorChainHandler @Inject constructor( // Do not have any BraveChains or it is invalid if (braveChainEnum == BraveChainEnum.Avoid) { - val newSelection = utils.getCardsForAvoidBraveChain( - cards = selectedCards, + val newSelection = getCardsForAvoidBraveChain( + cards = cards, + selectedCards = selectedCards, npUsage = npUsage, + cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) - if (newSelection == null) return null - selectedCards = newSelection + // if it is null, it means that it is impossible to get a list that avoids a BraveChain + // if it is not null, there is a valid non-Brave Chain option + if (newSelection != null) selectedCards = newSelection.toMutableList() } // If there is a braveChainFieldSlot, try for a Brave Color Chain else if (braveChainFieldSlot != null) { @@ -85,4 +91,58 @@ open class ColorChainHandler @Inject constructor( // Ensure there are at least 3 cards of the type return cardCountPerCardTypeMap.getOrElse(cardType) { 0 } >= 3 } + + /** + * Tries to get a list of valid cards for BraveChainEnum.Avoid while fulfilling the color requirement of this Chain. + * + * @param cards The list of cards available to choose from + * @param npUsage NPs that have been clicked + * @param selectedCards The list of cards that fulfil the color chain + * @param cardCountPerFieldSlotMap Optional passing of map from parent to reuse. Otherwise, this function will generate its own. + * @return null if there is no valid selectable options, otherwise a valid list of ParsedCards. + */ + fun getCardsForAvoidBraveChain ( + cards: List, + selectedCards: List, + npUsage: NPUsage = NPUsage.none, + cardCountPerFieldSlotMap: Map? = null, + ): List? { + val cardsNeeded = 3 - npUsage.nps.size + if (selectedCards.size < cardsNeeded) return null + + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) + // If there is only 1 unique field slot throughout, this is a valid entry (since it is impossible to avoid Brave Chain) + if (cardCountPerFieldSlotMap.size == 1) return selectedCards + + val npFieldSlots = npUsage.nps.map { it.toFieldSlot() }.toSet() + // If there is 2 or more NP, it will never be a BraveChain, so any cards are valid + if (npFieldSlots.size >= 2) return selectedCards + + val selectedCardsFieldSlotSet = selectedCards.mapNotNull { it.fieldSlot }.toSet() + // If the cards available are not 1 fieldSlot, but the selected cards are 1 fieldSlot only + if (selectedCardsFieldSlotSet.size == 1) return null // invalid + + val npFieldSlot = npFieldSlots.firstOrNull() + + // Otherwise, a valid list of cards exist, but the correct cards must be selected + val filteredCards = selectedCards.take(cardsNeeded).toMutableList() + val filteredCardsSet = filteredCards.mapNotNull { it.fieldSlot }.toSet() + if ( + // If all cards are the same field slot + filteredCardsSet.size == 1 + // it is the same slot as the NP + && (npFieldSlot == filteredCardsSet.first() + // or the NP is null + || npFieldSlot == null + ) + ) { + // Attempt to fetch a different field slot + val differentCard = cards.firstOrNull() { it.fieldSlot != filteredCardsSet.first() } + // If there is no different card (even though there should, by this stage), reutrn null + if (differentCard == null) return null + // Just add new card to index 1, aka 2nd card + filteredCards.add(1, differentCard) + } + return filteredCards + (cards - filteredCards) + } } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 30bd05cf2..e67c0e1ac 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -7,7 +7,6 @@ import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope -import kotlinx.coroutines.selects.select import javax.inject.Inject import kotlin.collections.Map @@ -24,6 +23,7 @@ class MightyChainHandler @Inject constructor( npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), braveChainEnum: BraveChainEnum = BraveChainEnum.None, + cardCountPerFieldSlotMap: Map? = null, cardCountPerCardTypeMap: Map? = null, forceBraveChain: Boolean = false, ): List? { @@ -52,6 +52,8 @@ class MightyChainHandler @Inject constructor( uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, braveChainFieldSlot = braveChainFieldSlot, braveChainEnum = braveChainEnum, + + cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) } @@ -65,6 +67,8 @@ class MightyChainHandler @Inject constructor( // In case of a Brave chain, we want to know what slot it is braveChainFieldSlot: FieldSlot? = null, braveChainEnum: BraveChainEnum = BraveChainEnum.None, + + cardCountPerFieldSlotMap: Map? = null, ): List? { val cardsToFind = totalUniqueCardTypesPermitted val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() @@ -104,7 +108,8 @@ class MightyChainHandler @Inject constructor( val newSelection = getMightyChainWithoutBraveChain( cards = cards, npUsage = npUsage, - selectedCards = selectedCards.toList() + selectedCards = selectedCards.toList(), + cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) if (newSelection == null) return null // if it is not null, there is a valid non-Brave Chain option @@ -122,10 +127,11 @@ class MightyChainHandler @Inject constructor( cards: List, selectedCards: List, npUsage: NPUsage = NPUsage.none, + cardCountPerFieldSlotMap: Map? = null, ): List? { - val cardsPerFieldSlot = utils.getCardsPerFieldSlotMap(cards, npUsage) + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) // If there is only 1 unique field slot throughout, this is a valid entry (since it is impossible to avoid Brave Chain) - if (cardsPerFieldSlot.size == 1) return selectedCards + if (cardCountPerFieldSlotMap.size == 1) return selectedCards val allFieldSlots = selectedCards.mapNotNull { it.fieldSlot } + npUsage.nps.map { it.toFieldSlot() } val uniqueFieldSlotsSet = allFieldSlots.toSet() diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt index 02f97b7cf..60caaba50 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt @@ -454,136 +454,4 @@ class AttackUtilsTest { } } } - - @Test - fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { - val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsForAvoidBraveChain( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(result).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) - } - - @Test - fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { - val cards = AttackLineUps.Standard.lineup2 - val result = utils.getCardsForAvoidBraveChain( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(result).containsExactly( - CommandCard.Face.A, - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - ) - } - - @Test - fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { - val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsForAvoidBraveChain( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - )?.map { it.card } ?: emptyList() - - assertThat(result).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) - } - - @Test - fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { - val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsForAvoidBraveChain( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - )?.map { it.card } ?: emptyList() - - assertThat(result).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) - } - - @Test - fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 1-KamaNP`() { - val cards = AttackLineUps.Standard.lineup2 - val result = utils.getCardsForAvoidBraveChain( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - )?.map { it.card } ?: emptyList() - - assertThat(result).containsExactly( - CommandCard.Face.A, - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - ) - } - - @Test - fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2-ScathachNP`() { - val cards = AttackLineUps.Standard.lineup2 - val result = utils.getCardsForAvoidBraveChain( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - )?.map { it.card } ?: emptyList() - - assertThat(result).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - ) - } - - @Test - fun `getCardsForAvoidBraveChain, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { - val cards = AttackLineUps.BusterFocus.lineup01 - val result = utils.getCardsForAvoidBraveChain( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(result).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) - } - - @Test - fun `getCardsForAvoidBraveChain, BusterFocus - lineup02 (1KB,5KB,2KB,3NA,4NA)`() { - val cards = AttackLineUps.BusterFocus.lineup02 - val result = utils.getCardsForAvoidBraveChain( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(result).containsExactly( - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.D, - ) - } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 55a307ce5..6f3945d38 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -3,6 +3,7 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot @@ -610,4 +611,203 @@ class ColorChainTest { "Quick" ).isEmpty() } + + /** + * Special cases + */ + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { + val cards = AttackLineUps.Standard.lineup2 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + Slot 1 NP`() { + val cards = AttackLineUps.Standard.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + Slot 2 NP`() { + val cards = AttackLineUps.Standard.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + Slot 3 NP`() { + val cards = AttackLineUps.Standard.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + Slot 1 NP`() { + val cards = AttackLineUps.Standard.lineup2 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + Slot 2 NP`() { + val cards = AttackLineUps.Standard.lineup2 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + Slot 3 NP`() { + val cards = AttackLineUps.Standard.lineup2 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `getCardsForAvoidBraveChain, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `getCardsForAvoidBraveChain, BusterFocus - lineup02 (1KB,5KB,2KB,3NA,4NA)`() { + val cards = AttackLineUps.BusterFocus.lineup02 + for (cardType in cardTypeList) { + val result = colorChain.getCardsForAvoidBraveChain( + cards = cards, + selectedCards = cards.filter { it.type == cardType }, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } } \ No newline at end of file From bed2d25f2583f5718307521e2d236e6473c6fd64 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 5 Sep 2025 02:27:55 +0800 Subject: [PATCH 069/124] Revert "Remove the unnecessary inject by converting it to a normal class constructor." This reverts commit 53bcc605243e0a258c63ec84a69b45edf3265274. --- .../fate_grand_automata/scripts/modules/ApplyBraveChains.kt | 2 +- .../fate_grand_automata/scripts/modules/attack/AttackUtils.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index f58ef961c..491efbcf9 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -11,7 +11,7 @@ import java.util.Collections import javax.inject.Inject @ScriptScope -class ApplyBraveChains () { +class ApplyBraveChains @Inject constructor() { private fun rearrange( cards: List, rearrange: Boolean, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index b9e77e914..48058064b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -9,7 +9,7 @@ import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope @ScriptScope -class AttackUtils () { +class AttackUtils @Inject constructor() { fun getCardsPerFieldSlotMap ( cards: List, npUsage: NPUsage = NPUsage.none From 51a7824e673f5cf7885f98464596d6c1d4775d82 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 5 Sep 2025 02:32:11 +0800 Subject: [PATCH 070/124] Fix bug with build. Clean up unnecessary imports --- .../fate_grand_automata/scripts/modules/ApplyBraveChains.kt | 2 -- .../fate_grand_automata/scripts/modules/attack/AttackUtils.kt | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 491efbcf9..1c8976671 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -1,8 +1,6 @@ package io.github.fate_grand_automata.scripts.modules import io.github.fate_grand_automata.scripts.enums.BraveChainEnum -import io.github.fate_grand_automata.scripts.enums.CardTypeEnum -import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index 48058064b..0694f0837 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -7,6 +7,7 @@ import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope +import javax.inject.Inject @ScriptScope class AttackUtils @Inject constructor() { From fb114c316bd628fb57901e50519d9f63ce15837a Mon Sep 17 00:00:00 2001 From: Vylantze Date: Fri, 5 Sep 2025 02:41:45 +0800 Subject: [PATCH 071/124] Refactor ColorChainTests to make them more compact. --- .../scripts/attack/ColorChainTest.kt | 634 +++++++----------- 1 file changed, 234 insertions(+), 400 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 6f3945d38..93867ac39 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -19,360 +19,258 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick - ) + ), )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes & forceBraveChain`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick ), - forceBraveChain = true + forceBraveChain = true, )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.B to CardTypeEnum.Quick - ) + ), )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes & forceBraveChain`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.B to CardTypeEnum.Quick ), forceBraveChain = true, )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts ), )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes & forceBraveChain`() { + val cards = AttackLineUps.Standard.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).containsExactly( - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - ) - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).containsExactly( - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.D, - ) } // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).containsExactly( - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.A, - CommandCard.Face.D, - ) } /** @@ -381,235 +279,171 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, FieldSlot.C to CardTypeEnum.Arts - ) + ), )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick - ) + FieldSlot.B to CardTypeEnum.Quick, + ), )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1B, 2B), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Buster, - FieldSlot.B to CardTypeEnum.Buster - ) + FieldSlot.B to CardTypeEnum.Buster, + ), )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, FieldSlot.B to CardTypeEnum.Quick, FieldSlot.C to CardTypeEnum.Arts, - ) + ), )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB)`() { val cards = AttackLineUps.BusterFocus.lineup01 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - ) - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (2Kiyohime), with npTypes`() { val cards = AttackLineUps.BusterFocus.lineup01 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( FieldSlot.B to CardTypeEnum.Buster, - ) + ), )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - ) - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } @Test fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (3Nero), with npTypes`() { val cards = AttackLineUps.BusterFocus.lineup01 - val values = cardTypeList.map { type -> - colorChain.pick( + for (cardType in cardTypeList) { + val result = colorChain.pick( cards = cards, - cardType = type, + cardType = cardType, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts, - ) + ), )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } } - val results = cardTypeList.zip(values).toMap() - - assertThat( - results.getOrElse(CardTypeEnum.Buster) { emptyList() }, - "Buster" - ).isEmpty() - assertThat( - results.getOrElse(CardTypeEnum.Arts) { emptyList() }, - "Arts" - ).containsExactly( - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - ) - assertThat( - results.getOrElse(CardTypeEnum.Quick) { emptyList() }, - "Quick" - ).isEmpty() } /** From 140818c7dc766be8b9b3be9dd4c25b46a8f7bdb4 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 7 Sep 2025 02:14:06 +0800 Subject: [PATCH 072/124] Add more handling for Unknown cards --- .../scripts/modules/attack/AttackUtils.kt | 23 ++++++++++++++++ .../modules/attack/AvoidChainHandler.kt | 14 +++++++--- .../modules/attack/BraveChainHandler.kt | 22 ++++++++++----- .../attack/CardChainPriorityHandler.kt | 11 ++++++++ .../modules/attack/ColorChainHandler.kt | 16 ++++++++++- .../modules/attack/MightyChainHandler.kt | 27 ++++++++++++++++--- .../scripts/attack/BraveChainTest.kt | 2 -- 7 files changed, 99 insertions(+), 16 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index 0694f0837..99959855b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -107,4 +107,27 @@ class AttackUtils @Inject constructor() { } return null } + + /** + * @returns A list of non-Unknown type cards that can be used to make a Chain. + */ + fun getValidNonUnknownCards ( + cards: List + ): List { + // Try to ensure unknown is handled + return cards.filter { it.type != CardTypeEnum.Unknown } + } + + /** + * @returns Are there enough cards to make a chain? + */ + fun isChainable ( + cards: List, + npUsage: NPUsage = NPUsage.none, + npTypes: Map = emptyMap(), + ): Boolean { + if (npTypes.values.contains(CardTypeEnum.Unknown)) return false + val cardsNeeded = 3 - npUsage.nps.size + return getValidNonUnknownCards(cards).size >= cardsNeeded + } } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index 8028084ee..6bc68af5a 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -24,10 +24,18 @@ class AvoidChainHandler @Inject constructor( ): List? { // If there are already 3 NPs, there is nothing to avoid if (npUsage.nps.size == 3) return null - // No matter what is returned, it will be valid for sure + + // Try to ensure unknown is handled + val filteredCards = utils.getValidNonUnknownCards(cards) + if (!utils.isChainable( + cards = filteredCards, + npUsage = npUsage, + npTypes = npTypes, + )) return null + if (npUsage.nps.size == 2) { // No matter what is returned, it will be valid for sure - if (!avoidCardChains) return cards + if (!avoidCardChains) return filteredCards + (cards - filteredCards) // Otherwise, need to check if they have the same NP type } @@ -52,7 +60,7 @@ class AvoidChainHandler @Inject constructor( val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) - var cachedFilteredCards = cards + var cachedFilteredCards: List = filteredCards var previousFieldSlot: FieldSlot? = null // Otherwise, calculate diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index abfc43ce6..cc6f92a2c 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -1,6 +1,7 @@ package io.github.fate_grand_automata.scripts.modules.attack import io.github.fate_grand_automata.scripts.enums.BraveChainEnum +import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard @@ -14,16 +15,25 @@ class BraveChainHandler @Inject constructor( private val avoidChainHandler: AvoidChainHandler, ) { fun pick( + npUsage: NPUsage = NPUsage.none, cards: List, braveChainEnum: BraveChainEnum, - npUsage: NPUsage = NPUsage.none, cardCountPerFieldSlotMap: Map? = null, ): List? { - val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) + // Try to ensure unknown is handled + val filteredCards = utils.getValidNonUnknownCards(cards) + if (!utils.isChainable( + cards = filteredCards, + npUsage = npUsage, + )) { + return null + } + + val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(filteredCards, npUsage) if (braveChainEnum == BraveChainEnum.Avoid) { return avoidChainHandler.pick( - cards = cards, + cards = filteredCards, npUsage = npUsage, avoidBraveChains = true, avoidCardChains = false, @@ -35,15 +45,15 @@ class BraveChainHandler @Inject constructor( // Always returns a valid field slot if there is one for BraveChain val priorityFieldSlot = utils.getBraveChainFieldSlot( braveChainEnum = braveChainEnum, - cards = cards, + cards = filteredCards, npUsage = npUsage, ) if (priorityFieldSlot == null) return null - val selectedCards = cards + val selectedCards = filteredCards .filter { it.fieldSlot == priorityFieldSlot } .take((3 - npUsage.nps.size).coerceAtLeast(0)) - val remainder = cards - selectedCards + val remainder = filteredCards - selectedCards return selectedCards + remainder } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 078440587..a18a84267 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -8,6 +8,7 @@ import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject +import kotlin.collections.plus @ScriptScope class CardChainPriorityHandler @Inject constructor( @@ -26,6 +27,16 @@ class CardChainPriorityHandler @Inject constructor( cardCountPerCardTypeMap: Map? = null, forceBraveChain: Boolean = false, ): List? { + // Try to ensure unknown is handled + val filteredCards = utils.getValidNonUnknownCards(cards) + if (!utils.isChainable( + cards = filteredCards, + npUsage = npUsage, + npTypes = npTypes, + )) { + return filteredCards + (cards - filteredCards) + } + var newCardOrder: List? = null val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index f4cda2c32..3cd932034 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -44,6 +44,15 @@ open class ColorChainHandler @Inject constructor( val cardsNeeded = 3 - npUsage.nps.size var selectedCards = cards.filter { it.type == cardType } + // Do a pre-emptive check + if (!utils.isChainable( + cards = selectedCards, + npUsage = npUsage, + npTypes = npTypes, + )) { + return null + } + // Do not have any BraveChains or it is invalid if (braveChainEnum == BraveChainEnum.Avoid) { val newSelection = getCardsForAvoidBraveChain( @@ -69,6 +78,7 @@ open class ColorChainHandler @Inject constructor( selectedCards = fieldSlotList } + // Secondary check if (selectedCards.size < cardsNeeded) return null val combinedCards = selectedCards + (cards - selectedCards) return combinedCards @@ -80,6 +90,9 @@ open class ColorChainHandler @Inject constructor( npTypes: Map = emptyMap(), cardCountPerCardTypeMap: Map? = null ): Boolean { + // NEVER want to make a chain of Unknown cards + if (cardType == CardTypeEnum.Unknown) return false + val uniqueCardTypesFromNp = npTypes.values.toSet() // Impossible if the unique number of card types are not 1 if (uniqueCardTypesFromNp.size > 1) return false @@ -89,6 +102,7 @@ open class ColorChainHandler @Inject constructor( val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) // Ensure there are at least 3 cards of the type + // This innately handles Unknown for Color chain return cardCountPerCardTypeMap.getOrElse(cardType) { 0 } >= 3 } @@ -138,7 +152,7 @@ open class ColorChainHandler @Inject constructor( ) { // Attempt to fetch a different field slot val differentCard = cards.firstOrNull() { it.fieldSlot != filteredCardsSet.first() } - // If there is no different card (even though there should, by this stage), reutrn null + // If there is no different card (even though there should, by this stage), return null if (differentCard == null) return null // Just add new card to index 1, aka 2nd card filteredCards.add(1, differentCard) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index e67c0e1ac..fa8fec265 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -27,10 +27,20 @@ class MightyChainHandler @Inject constructor( cardCountPerCardTypeMap: Map? = null, forceBraveChain: Boolean = false, ): List? { + // Try to ensure unknown is handled + val filteredCards = utils.getValidNonUnknownCards(cards) + if (!utils.isChainable( + cards = filteredCards, + npUsage = npUsage, + npTypes = npTypes, + )) { + return null + } + val uniqueCardTypesFromNp = npTypes.values.toSet() - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(filteredCards, npTypes) if (!isMightyChainAllowed( - cards = cards, + cards = filteredCards, npUsage = npUsage, uniqueCardTypesFromNp = uniqueCardTypesFromNp, npTypes = npTypes, @@ -40,14 +50,14 @@ class MightyChainHandler @Inject constructor( // Check for Brave Chain val braveChainFieldSlot = utils.getBraveChainFieldSlot( braveChainEnum = braveChainEnum, - cards = cards, + cards = filteredCards, npUsage = npUsage, forceBraveChain = forceBraveChain, ) val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum return pick( - cards = cards, + cards = filteredCards, npUsage = npUsage, uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, braveChainFieldSlot = braveChainFieldSlot, @@ -180,10 +190,19 @@ class MightyChainHandler @Inject constructor( val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) val npUsageSize = npUsage.nps.size + // Unable to make a Mighty Chain with Unknown cards in NP + if (npTypes.values.contains(CardTypeEnum.Unknown)) return false + // if there are more than 1 NP and we don't know their types, // just do the default since we can't guarantee a mighty chain anyway if (npUsageSize > 0 && npTypes.size != npUsageSize) return false + // Unable to make a Mighty Chain with Unknown cards in NP + if (npTypes.values.contains(CardTypeEnum.Unknown)) return false + + // Do not accept Unknown cards for Mighty Chain + if (cardCountPerCardTypeMap.getOrElse(CardTypeEnum.Unknown) { 0 } > 0) return false; + // If we do not have 3 unique cards at least, it is impossible to Mighty Chain if (cardCountPerCardTypeMap.size < 3) return false diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 3de4ff500..86a421e99 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -4,10 +4,8 @@ import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty import assertk.assertions.isEqualTo -import assertk.assertions.isNull import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.models.CommandCard -import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils From 8b4a3acdc6aedab192a77210ef62bdf2bfa4ba9c Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 7 Sep 2025 02:54:19 +0800 Subject: [PATCH 073/124] Add CardTypeEnum.Unknown tests --- .../scripts/attack/AttackLineUps.kt | 81 +++++ .../scripts/attack/AttackUtilsTest.kt | 302 +++++++++++++++++- 2 files changed, 380 insertions(+), 3 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index 60619e5ba..461b6e9e0 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -260,4 +260,85 @@ class AttackLineUps { /** servant5WQ, servant2WQ, servant3WAltQ, servant1WB, servant4WB */ val lineup5 = listOf(scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB) } + + data object Unknown { + val scathach1Unknown = ParsedCard( + card = CommandCard.Face.A, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Unknown, + affinity = CardAffinityEnum.Weak + ) + val kama2Unknown = ParsedCard( + card = CommandCard.Face.B, + servant = TeamSlot.A, + fieldSlot = FieldSlot.A, + type = CardTypeEnum.Unknown + ) + val nero3Unknown = ParsedCard( + card = CommandCard.Face.C, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Unknown, + affinity = CardAffinityEnum.Resist + ) + val nero4Unknown = ParsedCard( + card = CommandCard.Face.D, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Unknown, + affinity = CardAffinityEnum.Resist + ) + val scathach5Unknown = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Unknown, + affinity = CardAffinityEnum.Weak + ) + + /** + * Standard.lineup1 with 1st card Unknown + */ + val lineup1 = listOf( + scathach1Unknown, + Standard.kama2Q, + Standard.nero3RA, + Standard.nero4RA, + Standard.scathach5WQ, + ) + + /** + * Standard.lineup1 with 2 cards Unknown + */ + val lineup2 = listOf( + scathach1Unknown, + kama2Unknown, + Standard.nero3RA, + Standard.nero4RA, + Standard.scathach5WQ, + ) + + /** + * Standard.lineup1 with 3 cards Unknown + */ + val lineup3 = listOf( + scathach1Unknown, + kama2Unknown, + nero3Unknown, + Standard.nero4RA, + Standard.scathach5WQ, + ) + + /** + * All cards Unknown + */ + val lineup4 = listOf( + scathach1Unknown, + kama2Unknown, + nero3Unknown, + nero4Unknown, + scathach5Unknown, + ) + } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt index 60caaba50..3452dd735 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt @@ -1,7 +1,6 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat -import assertk.assertions.containsExactly import assertk.assertions.isEqualTo import assertk.assertions.isNull import io.github.fate_grand_automata.scripts.enums.BraveChainEnum @@ -356,7 +355,7 @@ class AttackUtilsTest { } @Test - fun `getBraveChainFieldSlot, Standard -lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { + fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 val result = utils.getBraveChainFieldSlot( @@ -370,7 +369,7 @@ class AttackUtilsTest { } @Test - fun `getBraveChainFieldSlot, Standard -lineup1 (1SB,2KQ,3NA,4NA,5SQ) + All 3 NP`() { + fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + All 3 NP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 val result = utils.getBraveChainFieldSlot( @@ -454,4 +453,301 @@ class AttackUtilsTest { } } } + + @Test + fun `getValidNonUnknownCards, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.getValidNonUnknownCards( + cards = cards, + ) + + assertThat(result.size).isEqualTo(5) + } + + @Test + fun `getValidNonUnknownCards, Unknown - lineup1 (1 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup1 + val result = utils.getValidNonUnknownCards( + cards = cards, + ) + + assertThat(result.size).isEqualTo(4) + } + + @Test + fun `getValidNonUnknownCards, Unknown - lineup2 (2 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup2 + val result = utils.getValidNonUnknownCards( + cards = cards, + ) + + assertThat(result.size).isEqualTo(3) + } + + @Test + fun `getValidNonUnknownCards, Unknown - lineup3 (3 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup3 + val result = utils.getValidNonUnknownCards( + cards = cards, + ) + + assertThat(result.size).isEqualTo(2) + } + + @Test + fun `getValidNonUnknownCards, Unknown - lineup4 (5 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.getValidNonUnknownCards( + cards = cards, + ) + + assertThat(result.size).isEqualTo(0) + } + + @Test + fun `isChainable, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.isChainable( + cards = cards, + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Valid NP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Valid NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Unknown NP`() { + val cards = AttackLineUps.Standard.lineup1 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Unknown + ), + ) + + assertThat(result).isEqualTo(false) + } + + @Test + fun `isChainable, Unknown - lineup1 (1 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup1 + val result = utils.isChainable( + cards = cards, + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Unknown - lineup1 (1 Unknown) + 1 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup1 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Unknown - lineup1 (1 Unknown) + 1 Valid NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup1 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Unknown - lineup1 (1 Unknown) + 1 Unknown NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup1 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Unknown + ), + ) + + assertThat(result).isEqualTo(false) + } + + @Test + fun `isChainable, Unknown - lineup2 (2 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup2 + val result = utils.isChainable( + cards = cards, + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Unknown - lineup3 (3 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup3 + val result = utils.isChainable( + cards = cards, + ) + + assertThat(result).isEqualTo(false) + } + + @Test + fun `isChainable, Unknown - lineup3 (3 Unknown) + Valid NP`() { + val cards = AttackLineUps.Unknown.lineup3 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Unknown - lineup3 (3 Unknown) + Valid NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup3 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Unknown - lineup4 (5 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.isChainable( + cards = cards, + ) + + assertThat(result).isEqualTo(false) + } + + @Test + fun `isChainable, Unknown - lineup4 (5 Unknown) + 1 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + ) + + assertThat(result).isEqualTo(false) + } + + @Test + fun `isChainable, Unknown - lineup4 (5 Unknown) + 1 Valid NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + ) + + assertThat(result).isEqualTo(false) + } + + @Test + fun `isChainable, Unknown - lineup4 (5 Unknown) + 2 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + ) + + assertThat(result).isEqualTo(false) + } + + @Test + fun `isChainable, Unknown - lineup4 (5 Unknown) + 2 Valid NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + ), + ) + + assertThat(result).isEqualTo(false) + } + + @Test + fun `isChainable, Unknown - lineup4 (5 Unknown) + 3 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Unknown - lineup4 (5 Unknown) + 3 Valid NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Arts, + ), + ) + + assertThat(result).isEqualTo(true) + } + + @Test + fun `isChainable, Unknown - lineup4 (5 Unknown) + 2 Valid NP + 1 Unknown NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup4 + val result = utils.isChainable( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Unknown, + ), + ) + + assertThat(result).isEqualTo(false) + } } \ No newline at end of file From 0fcc419b6e9fc27b42b7f6f8b2e8dac53f232438 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 7 Sep 2025 03:08:55 +0800 Subject: [PATCH 074/124] Use more appropriate name for unknown filtered cards --- .../modules/attack/AttackPriorityHandler.kt | 8 ++++---- .../scripts/modules/attack/AvoidChainHandler.kt | 8 ++++---- .../scripts/modules/attack/BraveChainHandler.kt | 15 +++++++-------- .../modules/attack/CardChainPriorityHandler.kt | 16 ++++++++-------- .../scripts/modules/attack/MightyChainHandler.kt | 12 ++++++------ 5 files changed, 29 insertions(+), 30 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 8a26d1a0f..73c142e3a 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -67,8 +67,8 @@ class AttackPriorityHandler @Inject constructor( // All stunned cards are categorized as Unknown. // Filter all of them since those cards are to be avoided if possible // and the system does not deal with CardTypeEnum.Unknown - val filteredCards = cards.filter { it.type != CardTypeEnum.Unknown } - val finalFallback = filteredCards + (cards - filteredCards) + val nonUnknownCards = cards.filter { it.type != CardTypeEnum.Unknown } + val finalFallback = nonUnknownCards + (cards - nonUnknownCards) // Get all the supplementary data val cardCountPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) @@ -81,7 +81,7 @@ class AttackPriorityHandler @Inject constructor( when (attackPriority) { AttackPriorityEnum.BraveChainPriority -> { braveChainFallback = braveChainHandler.pick( - cards = filteredCards, + cards = nonUnknownCards, braveChainEnum = braveChainEnum, npUsage = npUsage, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, @@ -102,7 +102,7 @@ class AttackPriorityHandler @Inject constructor( } newCardOrder = cardChainPriorityHandler.pick( - cards = filteredCards, + cards = nonUnknownCards, chainPriority = filteredChainPriority, braveChainEnum = braveChainEnum, npUsage = npUsage, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index 6bc68af5a..f7bb39672 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -26,16 +26,16 @@ class AvoidChainHandler @Inject constructor( if (npUsage.nps.size == 3) return null // Try to ensure unknown is handled - val filteredCards = utils.getValidNonUnknownCards(cards) + val nonUnknownCards = utils.getValidNonUnknownCards(cards) if (!utils.isChainable( - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, )) return null if (npUsage.nps.size == 2) { // No matter what is returned, it will be valid for sure - if (!avoidCardChains) return filteredCards + (cards - filteredCards) + if (!avoidCardChains) return nonUnknownCards + (cards - nonUnknownCards) // Otherwise, need to check if they have the same NP type } @@ -60,7 +60,7 @@ class AvoidChainHandler @Inject constructor( val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) - var cachedFilteredCards: List = filteredCards + var cachedFilteredCards: List = nonUnknownCards var previousFieldSlot: FieldSlot? = null // Otherwise, calculate diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index cc6f92a2c..5b57fa67b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -1,7 +1,6 @@ package io.github.fate_grand_automata.scripts.modules.attack import io.github.fate_grand_automata.scripts.enums.BraveChainEnum -import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard @@ -21,19 +20,19 @@ class BraveChainHandler @Inject constructor( cardCountPerFieldSlotMap: Map? = null, ): List? { // Try to ensure unknown is handled - val filteredCards = utils.getValidNonUnknownCards(cards) + val nonUnknownCards = utils.getValidNonUnknownCards(cards) if (!utils.isChainable( - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, )) { return null } - val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(filteredCards, npUsage) + val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) if (braveChainEnum == BraveChainEnum.Avoid) { return avoidChainHandler.pick( - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, avoidBraveChains = true, avoidCardChains = false, @@ -45,15 +44,15 @@ class BraveChainHandler @Inject constructor( // Always returns a valid field slot if there is one for BraveChain val priorityFieldSlot = utils.getBraveChainFieldSlot( braveChainEnum = braveChainEnum, - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, ) if (priorityFieldSlot == null) return null - val selectedCards = filteredCards + val selectedCards = nonUnknownCards .filter { it.fieldSlot == priorityFieldSlot } .take((3 - npUsage.nps.size).coerceAtLeast(0)) - val remainder = filteredCards - selectedCards + val remainder = nonUnknownCards - selectedCards return selectedCards + remainder } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index a18a84267..2204f7d8e 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -28,24 +28,24 @@ class CardChainPriorityHandler @Inject constructor( forceBraveChain: Boolean = false, ): List? { // Try to ensure unknown is handled - val filteredCards = utils.getValidNonUnknownCards(cards) + val nonUnknownCards = utils.getValidNonUnknownCards(cards) if (!utils.isChainable( - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, )) { - return filteredCards + (cards - filteredCards) + return nonUnknownCards + (nonUnknownCards - nonUnknownCards) } var newCardOrder: List? = null - val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) for (chain in chainPriority) { if (newCardOrder != null) continue newCardOrder = when (chain) { ChainTypeEnum.Mighty -> mightyChainHandler.pick( - cards = cards, + cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, braveChainEnum = braveChainEnum, @@ -62,7 +62,7 @@ class CardChainPriorityHandler @Inject constructor( ChainTypeEnum.Quick -> CardTypeEnum.Quick else -> CardTypeEnum.Unknown }, - cards = cards, + cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, braveChainEnum = braveChainEnum, @@ -71,7 +71,7 @@ class CardChainPriorityHandler @Inject constructor( cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) ChainTypeEnum.Avoid -> avoidChainHandler.pick( - cards = cards, + cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, avoidBraveChains = braveChainEnum == BraveChainEnum.Avoid, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index fa8fec265..9255803da 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -28,9 +28,9 @@ class MightyChainHandler @Inject constructor( forceBraveChain: Boolean = false, ): List? { // Try to ensure unknown is handled - val filteredCards = utils.getValidNonUnknownCards(cards) + val nonUnknownCards = utils.getValidNonUnknownCards(cards) if (!utils.isChainable( - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, )) { @@ -38,9 +38,9 @@ class MightyChainHandler @Inject constructor( } val uniqueCardTypesFromNp = npTypes.values.toSet() - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(filteredCards, npTypes) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) if (!isMightyChainAllowed( - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, uniqueCardTypesFromNp = uniqueCardTypesFromNp, npTypes = npTypes, @@ -50,14 +50,14 @@ class MightyChainHandler @Inject constructor( // Check for Brave Chain val braveChainFieldSlot = utils.getBraveChainFieldSlot( braveChainEnum = braveChainEnum, - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, forceBraveChain = forceBraveChain, ) val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum return pick( - cards = filteredCards, + cards = nonUnknownCards, npUsage = npUsage, uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, braveChainFieldSlot = braveChainFieldSlot, From 153e008ed8ad7346cfa91460617b38eed45c97e0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 7 Sep 2025 03:14:44 +0800 Subject: [PATCH 075/124] Ensure that 5 cards are always returned (even if there is Unknown) --- .../modules/attack/AttackPriorityHandler.kt | 7 +++++++ .../modules/attack/AvoidChainHandler.kt | 6 +++--- .../modules/attack/BraveChainHandler.kt | 21 +++++++++---------- .../attack/CardChainPriorityHandler.kt | 2 +- .../modules/attack/MightyChainHandler.kt | 6 +++++- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 73c142e3a..e1a1257e5 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -69,6 +69,13 @@ class AttackPriorityHandler @Inject constructor( // and the system does not deal with CardTypeEnum.Unknown val nonUnknownCards = cards.filter { it.type != CardTypeEnum.Unknown } val finalFallback = nonUnknownCards + (cards - nonUnknownCards) + if (!utils.isChainable( + cards = nonUnknownCards, + npUsage = npUsage, + npTypes = npTypes, + )) { + return finalFallback + } // Get all the supplementary data val cardCountPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index f7bb39672..1a590d8b9 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -57,8 +57,8 @@ class AvoidChainHandler @Inject constructor( } } - val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) var cachedFilteredCards: List = nonUnknownCards var previousFieldSlot: FieldSlot? = null @@ -122,7 +122,7 @@ class AvoidChainHandler @Inject constructor( // Return empty if cannot fulfill if (selectedCards.size < cardsNeeded) return null - val remainder = cards - selectedCards + val remainder = (nonUnknownCards - selectedCards) + (cards - nonUnknownCards) val combinedCards = selectedCards + remainder return combinedCards diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 5b57fa67b..cb546bee0 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -19,6 +19,15 @@ class BraveChainHandler @Inject constructor( braveChainEnum: BraveChainEnum, cardCountPerFieldSlotMap: Map? = null, ): List? { + if (braveChainEnum == BraveChainEnum.Avoid) { + return avoidChainHandler.pick( + cards = cards, + npUsage = npUsage, + avoidBraveChains = true, + avoidCardChains = false, + ) + } + // Try to ensure unknown is handled val nonUnknownCards = utils.getValidNonUnknownCards(cards) if (!utils.isChainable( @@ -29,16 +38,6 @@ class BraveChainHandler @Inject constructor( } val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) - - if (braveChainEnum == BraveChainEnum.Avoid) { - return avoidChainHandler.pick( - cards = nonUnknownCards, - npUsage = npUsage, - avoidBraveChains = true, - avoidCardChains = false, - ) - } - if (!isBraveChainAllowed(braveChainEnum, npUsage, cardsPerFieldSlotMap)) return null // Always returns a valid field slot if there is one for BraveChain @@ -52,7 +51,7 @@ class BraveChainHandler @Inject constructor( val selectedCards = nonUnknownCards .filter { it.fieldSlot == priorityFieldSlot } .take((3 - npUsage.nps.size).coerceAtLeast(0)) - val remainder = nonUnknownCards - selectedCards + val remainder = (nonUnknownCards - selectedCards) + (cards - nonUnknownCards) return selectedCards + remainder } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 2204f7d8e..2e98dda21 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -34,7 +34,7 @@ class CardChainPriorityHandler @Inject constructor( npUsage = npUsage, npTypes = npTypes, )) { - return nonUnknownCards + (nonUnknownCards - nonUnknownCards) + return nonUnknownCards + (cards - nonUnknownCards) } var newCardOrder: List? = null diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 9255803da..5bde5077a 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -56,7 +56,7 @@ class MightyChainHandler @Inject constructor( ) val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum - return pick( + val selectedCards = pick( cards = nonUnknownCards, npUsage = npUsage, uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, @@ -65,6 +65,10 @@ class MightyChainHandler @Inject constructor( cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) + + if (selectedCards == null) return null + // Handle for CardType.Unknown and ensure that 5 cards are always returned + return selectedCards + (cards - selectedCards) } // Returns null if uniqueCards cannot be found From 728f839f65cd5f0315e23c0f44f8cc8fa0bc6544 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 7 Sep 2025 04:32:30 +0800 Subject: [PATCH 076/124] Add tests for AvoidChain and BraveChain --- .../scripts/attack/AttackLineUps.kt | 21 ++ .../scripts/attack/AvoidChainTest.kt | 155 ++++++++++++ .../scripts/attack/BraveChainTest.kt | 232 ++++++++++++++++++ 3 files changed, 408 insertions(+) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index 461b6e9e0..c4486ba0c 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -340,5 +340,26 @@ class AttackLineUps { nero4Unknown, scathach5Unknown, ) + + /** + * Special option for Buster/Brave Chain. 1 Unknown + */ + val lineup5 = listOf( + BusterFocus.kiyohime1B, + BusterFocus.kiyohime2B, + nero3Unknown, + BusterFocus.nero4RA, + BusterFocus.kiyohime5B, + ) + /** + * Special option for Buster/Brave Chain. 2 Unknown, only valid BraveChain with NP. + */ + val lineup6 = listOf( + scathach1Unknown, + BusterFocus.kiyohime2B, + nero3Unknown, + BusterFocus.nero4RA, + BusterFocus.kiyohime5B, + ) } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index 366d65c92..e09894a1a 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -3,6 +3,7 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty +import assertk.assertions.isEqualTo import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot @@ -286,4 +287,158 @@ class AvoidChainTest { assertThat(picked).isEmpty() } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Unknown NP`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = avoidChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Unknown + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } + + @Test + fun `Unknown - lineup1 (1 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup1 + val picked = avoidChainHandler.pick( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.D, + CommandCard.Face.A, + ) + } + + @Test + fun `Unknown - lineup1 (1 Unknown) + 1 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup1 + val picked = avoidChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + ) + } + + @Test + fun `Unknown - lineup1 (1 Unknown) + 1 Valid NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup1 + val picked = avoidChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + ) + } + + @Test + fun `Unknown - lineup1 (1 Unknown) + 1 Unknown NP`() { + val cards = AttackLineUps.Unknown.lineup1 + val picked = avoidChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Unknown + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } + + @Test + fun `Unknown - lineup2 (2 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup2 + val picked = avoidChainHandler.pick( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + ) + } + + @Test + fun `Unknown - lineup3 (3 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup3 + val picked = avoidChainHandler.pick( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } + + @Test + fun `Unknown - lineup3 (3 Unknown) + 1 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup3 + val picked = avoidChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } + + @Test + fun `Unknown - lineup3 (3 Unknown) + 1 Valid NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup3 + val picked = avoidChainHandler.pick( + cards = cards, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked).containsExactly( + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } + + @Test + fun `Unknown - lineup4 (5 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup4 + val picked = avoidChainHandler.pick( + cards = cards, + )?.map { it.card } ?: emptyList() + + assertThat(picked).isEmpty() + } } \ No newline at end of file diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 86a421e99..342e1d5cd 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -423,4 +423,236 @@ class BraveChainTest { } } } + + @Test + fun `Unknown - lineup1 (1 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup1 (1 Unknown) + 1 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + ) + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup2 (2 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup2 (2 Unknown) + 1 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + ) + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.B, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup3 (3 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup3 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup5 (1 Unknown, Brave Chain available)`() { + val cards = AttackLineUps.Unknown.lineup5 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) + BraveChainEnum.Always -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.D, + CommandCard.Face.C, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup5 (1 Unknown) + 1 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup5 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + ) + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.C, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup6 (2 Unknown, no Brave Chain available)`() { + val cards = AttackLineUps.Unknown.lineup6 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup6 (2 Unknown) + 1 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup6 + for (braveChainEnum in braveChainEnums) { + val picked = braveChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Avoid -> assertDefaultAvoidChain( + cards = cards, + braveChainEnum = braveChainEnum, + picked = picked, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + ) + BraveChainEnum.WithNP, + BraveChainEnum.Always, -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.C, + ) + else -> assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + } } \ No newline at end of file From fd9e89dd7b1021a161540781ac288ed9ffcd11e0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 7 Sep 2025 05:15:35 +0800 Subject: [PATCH 077/124] Add tests for ColorChain and MightyChain for Unknown handling --- .../scripts/attack/ColorChainTest.kt | 290 ++++++++++++++++++ .../scripts/attack/MightyChainTest.kt | 196 ++++++++++++ 2 files changed, 486 insertions(+) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 93867ac39..4f355d93b 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -3,6 +3,8 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty +import assertk.assertions.isNotNull +import assertk.assertions.isNull import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard @@ -446,6 +448,294 @@ class ColorChainTest { } } + /** + * Unknown handling + */ + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Unknown NP, with npTypes`() { + val cards = AttackLineUps.Standard.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Unknown + ), + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup1 (1 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup1 (1 Unknown) + 1Kama-NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup1 (1 Unknown) + 3Nero-NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup1 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup2 (2 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup2 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup2 (2 Unknown) + 1Kama-NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup2 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup2 (2 Unknown) + 3Nero-NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup2 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup4 (5 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup4 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup4 (5 Unknown) + 2 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup4 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup4 (5 Unknown) + 3 Valid NP`() { + val cards = AttackLineUps.Unknown.lineup4 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Quick + ) + ) + + when (cardType) { + CardTypeEnum.Quick -> assertThat(result, cardType.toString()).isNotNull() + else -> assertThat(result, cardType.toString()).isNull() + } + } + } + + @Test + fun `Unknown - lineup5 (1 Unknown, Buster Chain)`() { + val cards = AttackLineUps.Unknown.lineup5 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup5 (1 Unknown) + 2 Valid NP, Arts Chain`() { + val cards = AttackLineUps.Unknown.lineup5 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Arts, + FieldSlot.B to CardTypeEnum.Arts, + ) + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.E, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + + @Test + fun `Unknown - lineup6 (2 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup6 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + )?.map { it.card } ?: emptyList() + + assertThat(result, cardType.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup6 (1 Unknown) + 1 Valid NP, Buster Chain`() { + val cards = AttackLineUps.Unknown.lineup6 + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster, + ) + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, cardType.toString()).isEmpty() + } + } + } + /** * Special cases */ diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 781f7057e..42cef2ddd 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -559,6 +559,202 @@ class MightyChainTest { } } + /** + * Unknown handling + */ + @Test + fun `Unknown - lineup1 (1 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup1 (1 Unknown) + 1 Valid Buster NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + ) + } + } + + @Test + fun `Unknown - lineup2 (2 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup2 (2 Unknown) + 1 Valid Buster NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + ) + } + } + + @Test + fun `Unknown - lineup3 (3 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup3 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup3 (3 Unknown) + 1 Valid Buster NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup3 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } + } + + @Test + fun `Unknown - lineup4 (5 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup4 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup5 (1 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup5 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup5 (1 Unknown) + 1 Valid Quick NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup5 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.D, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + ) + } + } + + @Test + fun `Unknown - lineup6 (1 Unknown)`() { + val cards = AttackLineUps.Unknown.lineup6 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } + } + + @Test + fun `Unknown - lineup6 (1 Unknown) + 1 Valid Quick NP, with npTypes`() { + val cards = AttackLineUps.Unknown.lineup6 + for (braveChainEnum in braveChainEnums) { + val picked = mightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + ) + } + } + /** * Special edge cases */ From 7dfb83a3a17bffb77952ba6aeeb9847bc423f9fa Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 7 Sep 2025 06:18:06 +0800 Subject: [PATCH 078/124] Add tests for all BraveChainEnums in ColorChainTest. --- .../scripts/attack/ColorChainTest.kt | 1090 +++++++++-------- 1 file changed, 606 insertions(+), 484 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 4f355d93b..037727b25 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -18,55 +18,67 @@ class ColorChainTest { val colorChain = ColorChainHandler(utils = AttackUtils()) val cardTypeList = CardTypeEnum.entries + val braveChainEnums = BraveChainEnum.entries + @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Quick -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -74,27 +86,31 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes & forceBraveChain`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - forceBraveChain = true, - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Quick -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -102,26 +118,31 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.B to CardTypeEnum.Quick - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Quick -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + when (cardType to braveChainEnum) { + CardTypeEnum.Quick to BraveChainEnum.None, + CardTypeEnum.Quick to BraveChainEnum.WithNP, + CardTypeEnum.Quick to BraveChainEnum.Avoid, -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -129,44 +150,51 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes & forceBraveChain`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.B to CardTypeEnum.Quick - ), - forceBraveChain = true, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Arts -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -174,79 +202,95 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes & forceBraveChain`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ), - forceBraveChain = true, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Quick -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -255,22 +299,26 @@ class ColorChainTest { @Test fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Quick -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.A, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -281,45 +329,52 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.C to CardTypeEnum.Arts - ), - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick, - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Quick -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -327,27 +382,31 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1B, 2B), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Buster, - FieldSlot.B to CardTypeEnum.Buster, - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Buster -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster, + FieldSlot.B to CardTypeEnum.Buster, + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -355,41 +414,48 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick, - FieldSlot.C to CardTypeEnum.Arts, - ), - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts, + ), + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB)`() { val cards = AttackLineUps.BusterFocus.lineup01 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Buster -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -397,26 +463,30 @@ class ColorChainTest { @Test fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (2Kiyohime), with npTypes`() { val cards = AttackLineUps.BusterFocus.lineup01 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.B to CardTypeEnum.Buster, - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Buster -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Buster, + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -424,26 +494,30 @@ class ColorChainTest { @Test fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (3Nero), with npTypes`() { val cards = AttackLineUps.BusterFocus.lineup01 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts, - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Arts -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts, + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -454,56 +528,66 @@ class ColorChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Unknown NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Unknown - ), - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Unknown + ), + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Unknown - lineup1 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Unknown - lineup1 (1 Unknown) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Quick -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Quick -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -511,26 +595,30 @@ class ColorChainTest { @Test fun `Unknown - lineup1 (1 Unknown) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup1 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Arts -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -538,56 +626,66 @@ class ColorChainTest { @Test fun `Unknown - lineup2 (2 Unknown)`() { val cards = AttackLineUps.Unknown.lineup2 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Unknown - lineup2 (2 Unknown) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup2 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Unknown - lineup2 (2 Unknown) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup2 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ), - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Arts -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -595,52 +693,61 @@ class ColorChainTest { @Test fun `Unknown - lineup4 (5 Unknown)`() { val cards = AttackLineUps.Unknown.lineup4 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Unknown - lineup4 (5 Unknown) + 2 Valid NP`() { val cards = AttackLineUps.Unknown.lineup4 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick - ) - )?.map { it.card } ?: emptyList() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() - assertThat(result, cardType.toString()).isEmpty() + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Unknown - lineup4 (5 Unknown) + 3 Valid NP`() { val cards = AttackLineUps.Unknown.lineup4 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick, - FieldSlot.C to CardTypeEnum.Quick + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Quick + ) ) - ) - when (cardType) { - CardTypeEnum.Quick -> assertThat(result, cardType.toString()).isNotNull() - else -> assertThat(result, cardType.toString()).isNull() + when (cardType) { + CardTypeEnum.Quick -> assertThat(result, cardType.toString()).isNotNull() + else -> assertThat(result, cardType.toString()).isNull() + } } } } @@ -648,22 +755,26 @@ class ColorChainTest { @Test fun `Unknown - lineup5 (1 Unknown, Buster Chain)`() { val cards = AttackLineUps.Unknown.lineup5 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Buster -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - ) - else -> assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -671,27 +782,31 @@ class ColorChainTest { @Test fun `Unknown - lineup5 (1 Unknown) + 2 Valid NP, Arts Chain`() { val cards = AttackLineUps.Unknown.lineup5 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Arts, - FieldSlot.B to CardTypeEnum.Arts, - ) - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Arts -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.E, + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Arts, + FieldSlot.B to CardTypeEnum.Arts, ) - else -> assertThat(result, cardType.toString()).isEmpty() + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Arts -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.E, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } @@ -699,39 +814,46 @@ class ColorChainTest { @Test fun `Unknown - lineup6 (2 Unknown)`() { val cards = AttackLineUps.Unknown.lineup6 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - )?.map { it.card } ?: emptyList() - - assertThat(result, cardType.toString()).isEmpty() + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } @Test fun `Unknown - lineup6 (1 Unknown) + 1 Valid NP, Buster Chain`() { val cards = AttackLineUps.Unknown.lineup6 - for (cardType in cardTypeList) { - val result = colorChain.pick( - cards = cards, - cardType = cardType, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Buster, - ) - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Buster -> - assertThat(result, cardType.toString()).containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = colorChain.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster, ) - else -> assertThat(result, cardType.toString()).isEmpty() + )?.map { it.card } ?: emptyList() + + when (cardType) { + CardTypeEnum.Buster -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } } } } From 86e4706faa96fe36d641d10fd016cf31df99ee25 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sun, 7 Sep 2025 06:23:55 +0800 Subject: [PATCH 079/124] Fix bug where ColorChain was not handling Avoid properly --- .../modules/attack/ColorChainHandler.kt | 3 +- .../scripts/attack/ColorChainTest.kt | 48 ++++++++++++------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index 3cd932034..a7905ea3d 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -63,7 +63,8 @@ open class ColorChainHandler @Inject constructor( ) // if it is null, it means that it is impossible to get a list that avoids a BraveChain // if it is not null, there is a valid non-Brave Chain option - if (newSelection != null) selectedCards = newSelection.toMutableList() + if (newSelection == null) return null + selectedCards = newSelection.toMutableList() } // If there is a braveChainFieldSlot, try for a Brave Color Chain else if (braveChainFieldSlot != null) { diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 037727b25..572ac30b1 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -183,8 +183,10 @@ class ColorChainTest { ), )?.map { it.card } ?: emptyList() - when (cardType) { - CardTypeEnum.Arts -> + when (cardType to braveChainEnum) { + CardTypeEnum.Arts to BraveChainEnum.None, + CardTypeEnum.Arts to BraveChainEnum.Always, + CardTypeEnum.Arts to BraveChainEnum.WithNP, -> assertThat(result, cardType.toString()).containsExactly( CommandCard.Face.C, CommandCard.Face.D, @@ -444,8 +446,10 @@ class ColorChainTest { braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() - when (cardType) { - CardTypeEnum.Buster -> + when (cardType to braveChainEnum) { + CardTypeEnum.Buster to BraveChainEnum.None, + CardTypeEnum.Buster to BraveChainEnum.Always, + CardTypeEnum.Buster to BraveChainEnum.WithNP, -> assertThat(result, cardType.toString()).containsExactly( CommandCard.Face.A, CommandCard.Face.B, @@ -475,8 +479,10 @@ class ColorChainTest { ), )?.map { it.card } ?: emptyList() - when (cardType) { - CardTypeEnum.Buster -> + when (cardType to braveChainEnum) { + CardTypeEnum.Buster to BraveChainEnum.None, + CardTypeEnum.Buster to BraveChainEnum.Always, + CardTypeEnum.Buster to BraveChainEnum.WithNP, -> assertThat(result, "$braveChainEnum, $cardType").containsExactly( CommandCard.Face.A, CommandCard.Face.B, @@ -506,8 +512,10 @@ class ColorChainTest { ), )?.map { it.card } ?: emptyList() - when (cardType) { - CardTypeEnum.Arts -> + when (cardType to braveChainEnum) { + CardTypeEnum.Arts to BraveChainEnum.None, + CardTypeEnum.Arts to BraveChainEnum.Always, + CardTypeEnum.Arts to BraveChainEnum.WithNP, -> assertThat(result, "$braveChainEnum, $cardType").containsExactly( CommandCard.Face.C, CommandCard.Face.D, @@ -607,8 +615,10 @@ class ColorChainTest { ), )?.map { it.card } ?: emptyList() - when (cardType) { - CardTypeEnum.Arts -> + when (cardType to braveChainEnum) { + CardTypeEnum.Arts to BraveChainEnum.None, + CardTypeEnum.Arts to BraveChainEnum.Always, + CardTypeEnum.Arts to BraveChainEnum.WithNP, -> assertThat(result, "$braveChainEnum, $cardType").containsExactly( CommandCard.Face.C, CommandCard.Face.D, @@ -674,8 +684,10 @@ class ColorChainTest { ), )?.map { it.card } ?: emptyList() - when (cardType) { - CardTypeEnum.Arts -> + when (cardType to braveChainEnum) { + CardTypeEnum.Arts to BraveChainEnum.None, + CardTypeEnum.Arts to BraveChainEnum.Always, + CardTypeEnum.Arts to BraveChainEnum.WithNP, -> assertThat(result, "$braveChainEnum, $cardType").containsExactly( CommandCard.Face.C, CommandCard.Face.D, @@ -763,8 +775,10 @@ class ColorChainTest { braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() - when (cardType) { - CardTypeEnum.Buster -> + when (cardType to braveChainEnum) { + CardTypeEnum.Buster to BraveChainEnum.None, + CardTypeEnum.Buster to BraveChainEnum.Always, + CardTypeEnum.Buster to BraveChainEnum.WithNP, -> assertThat(result, "$braveChainEnum, $cardType").containsExactly( CommandCard.Face.A, CommandCard.Face.B, @@ -842,8 +856,10 @@ class ColorChainTest { ) )?.map { it.card } ?: emptyList() - when (cardType) { - CardTypeEnum.Buster -> + when (cardType to braveChainEnum) { + CardTypeEnum.Buster to BraveChainEnum.None, + CardTypeEnum.Buster to BraveChainEnum.Always, + CardTypeEnum.Buster to BraveChainEnum.WithNP, -> assertThat(result, "$braveChainEnum, $cardType").containsExactly( CommandCard.Face.B, CommandCard.Face.E, From 8832a50654aa522ec577a266e3e2bd4c91f60df0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:28:56 +0800 Subject: [PATCH 080/124] Change AttackUtils to object --- .../modules/attack/AttackPriorityHandler.kt | 7 +- .../scripts/modules/attack/AttackUtils.kt | 3 +- .../modules/attack/AvoidChainHandler.kt | 12 +- .../modules/attack/BraveChainHandler.kt | 9 +- .../attack/CardChainPriorityHandler.kt | 9 +- .../modules/attack/ColorChainHandler.kt | 14 +-- .../modules/attack/MightyChainHandler.kt | 16 ++- .../scripts/attack/AttackUtilsTest.kt | 113 +++++++++--------- .../scripts/attack/AvoidChainTest.kt | 2 +- .../scripts/attack/BraveChainTest.kt | 6 +- .../CardChainPriorityIntegrationTest.kt | 10 +- .../scripts/attack/ColorChainTest.kt | 2 +- .../scripts/attack/MightyChainTest.kt | 5 +- 13 files changed, 93 insertions(+), 115 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index e1a1257e5..58c6120f4 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -15,7 +15,6 @@ import javax.inject.Inject class AttackPriorityHandler @Inject constructor( private val cardChainPriorityHandler: CardChainPriorityHandler, private val braveChainHandler: BraveChainHandler, - private val utils: AttackUtils, ) { fun rearrange( cards: List, @@ -69,7 +68,7 @@ class AttackPriorityHandler @Inject constructor( // and the system does not deal with CardTypeEnum.Unknown val nonUnknownCards = cards.filter { it.type != CardTypeEnum.Unknown } val finalFallback = nonUnknownCards + (cards - nonUnknownCards) - if (!utils.isChainable( + if (!AttackUtils.isChainable( cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, @@ -78,8 +77,8 @@ class AttackPriorityHandler @Inject constructor( } // Get all the supplementary data - val cardCountPerFieldSlotMap = utils.getCardsPerFieldSlotMap(cards, npUsage) - val cardCountPerCardTypeMap = utils.getCardsPerCardTypeMap(cards, npTypes) + val cardCountPerFieldSlotMap = AttackUtils.getCardsPerFieldSlotMap(cards, npUsage) + val cardCountPerCardTypeMap = AttackUtils.getCardsPerCardTypeMap(cards, npTypes) // Start var newCardOrder: List? = null diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index 99959855b..1f3bb7463 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -9,8 +9,7 @@ import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject -@ScriptScope -class AttackUtils @Inject constructor() { +object AttackUtils { fun getCardsPerFieldSlotMap ( cards: List, npUsage: NPUsage = NPUsage.none diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index 1a590d8b9..834511cd7 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -10,9 +10,7 @@ import javax.inject.Inject import kotlin.collections.set @ScriptScope -class AvoidChainHandler @Inject constructor( - private val utils: AttackUtils -) { +class AvoidChainHandler @Inject constructor() { fun pick( cards: List, npUsage: NPUsage = NPUsage.none, @@ -26,8 +24,8 @@ class AvoidChainHandler @Inject constructor( if (npUsage.nps.size == 3) return null // Try to ensure unknown is handled - val nonUnknownCards = utils.getValidNonUnknownCards(cards) - if (!utils.isChainable( + val nonUnknownCards = AttackUtils.getValidNonUnknownCards(cards) + if (!AttackUtils.isChainable( cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, @@ -57,8 +55,8 @@ class AvoidChainHandler @Inject constructor( } } - val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) var cachedFilteredCards: List = nonUnknownCards var previousFieldSlot: FieldSlot? = null diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index cb546bee0..0acc4696e 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -10,7 +10,6 @@ import javax.inject.Inject @ScriptScope class BraveChainHandler @Inject constructor( - private val utils: AttackUtils, private val avoidChainHandler: AvoidChainHandler, ) { fun pick( @@ -29,19 +28,19 @@ class BraveChainHandler @Inject constructor( } // Try to ensure unknown is handled - val nonUnknownCards = utils.getValidNonUnknownCards(cards) - if (!utils.isChainable( + val nonUnknownCards = AttackUtils.getValidNonUnknownCards(cards) + if (!AttackUtils.isChainable( cards = nonUnknownCards, npUsage = npUsage, )) { return null } - val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) + val cardsPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) if (!isBraveChainAllowed(braveChainEnum, npUsage, cardsPerFieldSlotMap)) return null // Always returns a valid field slot if there is one for BraveChain - val priorityFieldSlot = utils.getBraveChainFieldSlot( + val priorityFieldSlot = AttackUtils.getBraveChainFieldSlot( braveChainEnum = braveChainEnum, cards = nonUnknownCards, npUsage = npUsage, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 2e98dda21..fb207de3a 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -15,7 +15,6 @@ class CardChainPriorityHandler @Inject constructor( private val mightyChainHandler: MightyChainHandler, private val colorChainHandler: ColorChainHandler, private val avoidChainHandler: AvoidChainHandler, - private val utils: AttackUtils ) { fun pick( cards: List, @@ -28,8 +27,8 @@ class CardChainPriorityHandler @Inject constructor( forceBraveChain: Boolean = false, ): List? { // Try to ensure unknown is handled - val nonUnknownCards = utils.getValidNonUnknownCards(cards) - if (!utils.isChainable( + val nonUnknownCards = AttackUtils.getValidNonUnknownCards(cards) + if (!AttackUtils.isChainable( cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, @@ -38,8 +37,8 @@ class CardChainPriorityHandler @Inject constructor( } var newCardOrder: List? = null - val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) for (chain in chainPriority) { if (newCardOrder != null) continue diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index a7905ea3d..a23412b68 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -11,9 +11,7 @@ import javax.inject.Inject import kotlin.collections.Map @ScriptScope -open class ColorChainHandler @Inject constructor( - private val utils: AttackUtils -) { +open class ColorChainHandler @Inject constructor() { // Returns null if uniqueCards cannot be found fun pick ( cardType: CardTypeEnum, @@ -28,11 +26,11 @@ open class ColorChainHandler @Inject constructor( // NEVER want to make a chain of Unknown cards if (cardType == CardTypeEnum.Unknown) return null - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(cards, npTypes) if (!isColorChainAllowed(cardType, cards, npTypes, cardCountPerCardTypeMap)) return null // Check for Brave Chain - val braveChainFieldSlot = utils.getBraveChainFieldSlot( + val braveChainFieldSlot = AttackUtils.getBraveChainFieldSlot( braveChainEnum = braveChainEnum, cards = cards, npUsage = npUsage, @@ -45,7 +43,7 @@ open class ColorChainHandler @Inject constructor( var selectedCards = cards.filter { it.type == cardType } // Do a pre-emptive check - if (!utils.isChainable( + if (!AttackUtils.isChainable( cards = selectedCards, npUsage = npUsage, npTypes = npTypes, @@ -100,7 +98,7 @@ open class ColorChainHandler @Inject constructor( // Impossible if the nps are not of the chain type if (uniqueCardTypesFromNp.size == 1 && uniqueCardTypesFromNp.firstOrNull() != cardType) return false - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(cards, npTypes) // Ensure there are at least 3 cards of the type // This innately handles Unknown for Color chain @@ -125,7 +123,7 @@ open class ColorChainHandler @Inject constructor( val cardsNeeded = 3 - npUsage.nps.size if (selectedCards.size < cardsNeeded) return null - val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(cards, npUsage) // If there is only 1 unique field slot throughout, this is a valid entry (since it is impossible to avoid Brave Chain) if (cardCountPerFieldSlotMap.size == 1) return selectedCards diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 5bde5077a..ba3ca86d9 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -11,9 +11,7 @@ import javax.inject.Inject import kotlin.collections.Map @ScriptScope -class MightyChainHandler @Inject constructor( - private val utils: AttackUtils -) { +class MightyChainHandler @Inject constructor() { // We want 3 unique types, the magic number val totalUniqueCardTypesPermitted = 3 @@ -28,8 +26,8 @@ class MightyChainHandler @Inject constructor( forceBraveChain: Boolean = false, ): List? { // Try to ensure unknown is handled - val nonUnknownCards = utils.getValidNonUnknownCards(cards) - if (!utils.isChainable( + val nonUnknownCards = AttackUtils.getValidNonUnknownCards(cards) + if (!AttackUtils.isChainable( cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, @@ -38,7 +36,7 @@ class MightyChainHandler @Inject constructor( } val uniqueCardTypesFromNp = npTypes.values.toSet() - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) if (!isMightyChainAllowed( cards = nonUnknownCards, npUsage = npUsage, @@ -48,7 +46,7 @@ class MightyChainHandler @Inject constructor( )) return null // Check for Brave Chain - val braveChainFieldSlot = utils.getBraveChainFieldSlot( + val braveChainFieldSlot = AttackUtils.getBraveChainFieldSlot( braveChainEnum = braveChainEnum, cards = nonUnknownCards, npUsage = npUsage, @@ -143,7 +141,7 @@ class MightyChainHandler @Inject constructor( npUsage: NPUsage = NPUsage.none, cardCountPerFieldSlotMap: Map? = null, ): List? { - val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: utils.getCardsPerFieldSlotMap(cards, npUsage) + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(cards, npUsage) // If there is only 1 unique field slot throughout, this is a valid entry (since it is impossible to avoid Brave Chain) if (cardCountPerFieldSlotMap.size == 1) return selectedCards @@ -191,7 +189,7 @@ class MightyChainHandler @Inject constructor( cardCountPerCardTypeMap: Map? = null, ): Boolean { val uniqueCardTypesFromNp = uniqueCardTypesFromNp ?: npTypes.values.toSet() - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: utils.getCardsPerCardTypeMap(cards, npTypes) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(cards, npTypes) val npUsageSize = npUsage.nps.size // Unable to make a Mighty Chain with Unknown cards in NP diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt index 3452dd735..fa6e51f32 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackUtilsTest.kt @@ -12,13 +12,12 @@ import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import kotlin.test.Test class AttackUtilsTest { - val utils = AttackUtils() val braveChainEnums = BraveChainEnum.entries @Test fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerFieldSlotMap( + val result = AttackUtils.getCardsPerFieldSlotMap( cards = cards, ) @@ -31,7 +30,7 @@ class AttackUtilsTest { @Test fun `getCardsPerFieldSlotMap, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val result = utils.getCardsPerFieldSlotMap( + val result = AttackUtils.getCardsPerFieldSlotMap( cards = cards, ) @@ -44,7 +43,7 @@ class AttackUtilsTest { @Test fun `getCardsPerFieldSlotMap, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { val cards = AttackLineUps.BusterFocus.lineup01 - val result = utils.getCardsPerFieldSlotMap( + val result = AttackUtils.getCardsPerFieldSlotMap( cards = cards, ) @@ -56,7 +55,7 @@ class AttackUtilsTest { @Test fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerFieldSlotMap( + val result = AttackUtils.getCardsPerFieldSlotMap( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) ) @@ -70,7 +69,7 @@ class AttackUtilsTest { @Test fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerFieldSlotMap( + val result = AttackUtils.getCardsPerFieldSlotMap( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) ) @@ -84,7 +83,7 @@ class AttackUtilsTest { @Test fun `getCardsPerFieldSlotMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerFieldSlotMap( + val result = AttackUtils.getCardsPerFieldSlotMap( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) ) @@ -98,7 +97,7 @@ class AttackUtilsTest { @Test fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getFieldSlotsWithValidBraveChain( + val result = AttackUtils.getFieldSlotsWithValidBraveChain( cards = cards, ) @@ -108,7 +107,7 @@ class AttackUtilsTest { @Test fun `getFieldSlotsWithValidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val result = utils.getFieldSlotsWithValidBraveChain( + val result = AttackUtils.getFieldSlotsWithValidBraveChain( cards = cards, ) @@ -118,7 +117,7 @@ class AttackUtilsTest { @Test fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getFieldSlotsWithValidBraveChain( + val result = AttackUtils.getFieldSlotsWithValidBraveChain( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) ) @@ -129,7 +128,7 @@ class AttackUtilsTest { @Test fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getFieldSlotsWithValidBraveChain( + val result = AttackUtils.getFieldSlotsWithValidBraveChain( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) ) @@ -141,7 +140,7 @@ class AttackUtilsTest { @Test fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getFieldSlotsWithValidBraveChain( + val result = AttackUtils.getFieldSlotsWithValidBraveChain( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) ) @@ -153,7 +152,7 @@ class AttackUtilsTest { @Test fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getFieldSlotsWithValidBraveChain( + val result = AttackUtils.getFieldSlotsWithValidBraveChain( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0) ) @@ -164,7 +163,7 @@ class AttackUtilsTest { @Test fun `getFieldSlotsWithValidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + All 3 NP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getFieldSlotsWithValidBraveChain( + val result = AttackUtils.getFieldSlotsWithValidBraveChain( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0) ) @@ -175,7 +174,7 @@ class AttackUtilsTest { @Test fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerCardTypeMap( + val result = AttackUtils.getCardsPerCardTypeMap( cards = cards, ) @@ -188,7 +187,7 @@ class AttackUtilsTest { @Test fun `getCardsPerCardTypeMap, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val result = utils.getCardsPerCardTypeMap( + val result = AttackUtils.getCardsPerCardTypeMap( cards = cards, ) @@ -201,7 +200,7 @@ class AttackUtilsTest { @Test fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerCardTypeMap( + val result = AttackUtils.getCardsPerCardTypeMap( cards = cards, npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick @@ -217,7 +216,7 @@ class AttackUtilsTest { @Test fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerCardTypeMap( + val result = AttackUtils.getCardsPerCardTypeMap( cards = cards, npTypes = mapOf( FieldSlot.B to CardTypeEnum.Quick @@ -233,7 +232,7 @@ class AttackUtilsTest { @Test fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerCardTypeMap( + val result = AttackUtils.getCardsPerCardTypeMap( cards = cards, npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts @@ -249,7 +248,7 @@ class AttackUtilsTest { @Test fun `getCardsPerCardTypeMap, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getCardsPerCardTypeMap( + val result = AttackUtils.getCardsPerCardTypeMap( cards = cards, npTypes = mapOf( FieldSlot.A to CardTypeEnum.Quick, @@ -266,7 +265,7 @@ class AttackUtilsTest { @Test fun `getCardsPerCardTypeMap, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { val cards = AttackLineUps.BusterFocus.lineup01 - val result = utils.getCardsPerCardTypeMap( + val result = AttackUtils.getCardsPerCardTypeMap( cards = cards, ) @@ -280,7 +279,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, braveChainEnum = braveChainEnum, ) @@ -293,7 +292,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup2 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, braveChainEnum = braveChainEnum, ) @@ -306,7 +305,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup2 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), braveChainEnum = braveChainEnum, @@ -320,7 +319,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2-ScathachNP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), braveChainEnum = braveChainEnum, @@ -339,7 +338,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3-NeroNP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), braveChainEnum = braveChainEnum, @@ -358,7 +357,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1-KamaNP + 2-ScathachNP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), braveChainEnum = braveChainEnum, @@ -372,7 +371,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + All 3 NP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), braveChainEnum = braveChainEnum, @@ -386,7 +385,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.BusterFocus.lineup01 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, braveChainEnum = braveChainEnum, ) @@ -403,7 +402,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB)`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.BusterFocus.lineup03 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, braveChainEnum = braveChainEnum, ) @@ -420,7 +419,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB) - 2Kiyo-NP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.BusterFocus.lineup03 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), braveChainEnum = braveChainEnum, @@ -439,7 +438,7 @@ class AttackUtilsTest { fun `getBraveChainFieldSlot, BusterFocus - lineup03 (1KB,2KB,3NA,4NA,5NB) - 3Nero-NP`() { for (braveChainEnum in braveChainEnums) { val cards = AttackLineUps.BusterFocus.lineup03 - val result = utils.getBraveChainFieldSlot( + val result = AttackUtils.getBraveChainFieldSlot( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), braveChainEnum = braveChainEnum, @@ -457,7 +456,7 @@ class AttackUtilsTest { @Test fun `getValidNonUnknownCards, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.getValidNonUnknownCards( + val result = AttackUtils.getValidNonUnknownCards( cards = cards, ) @@ -467,7 +466,7 @@ class AttackUtilsTest { @Test fun `getValidNonUnknownCards, Unknown - lineup1 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup1 - val result = utils.getValidNonUnknownCards( + val result = AttackUtils.getValidNonUnknownCards( cards = cards, ) @@ -477,7 +476,7 @@ class AttackUtilsTest { @Test fun `getValidNonUnknownCards, Unknown - lineup2 (2 Unknown)`() { val cards = AttackLineUps.Unknown.lineup2 - val result = utils.getValidNonUnknownCards( + val result = AttackUtils.getValidNonUnknownCards( cards = cards, ) @@ -487,7 +486,7 @@ class AttackUtilsTest { @Test fun `getValidNonUnknownCards, Unknown - lineup3 (3 Unknown)`() { val cards = AttackLineUps.Unknown.lineup3 - val result = utils.getValidNonUnknownCards( + val result = AttackUtils.getValidNonUnknownCards( cards = cards, ) @@ -497,7 +496,7 @@ class AttackUtilsTest { @Test fun `getValidNonUnknownCards, Unknown - lineup4 (5 Unknown)`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.getValidNonUnknownCards( + val result = AttackUtils.getValidNonUnknownCards( cards = cards, ) @@ -507,7 +506,7 @@ class AttackUtilsTest { @Test fun `isChainable, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, ) @@ -517,7 +516,7 @@ class AttackUtilsTest { @Test fun `isChainable, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Valid NP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), ) @@ -528,7 +527,7 @@ class AttackUtilsTest { @Test fun `isChainable, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Valid NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -542,7 +541,7 @@ class AttackUtilsTest { @Test fun `isChainable, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Unknown NP`() { val cards = AttackLineUps.Standard.lineup1 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -556,7 +555,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup1 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup1 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, ) @@ -566,7 +565,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup1 (1 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup1 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), ) @@ -577,7 +576,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup1 (1 Unknown) + 1 Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup1 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -591,7 +590,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup1 (1 Unknown) + 1 Unknown NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup1 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -605,7 +604,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup2 (2 Unknown)`() { val cards = AttackLineUps.Unknown.lineup2 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, ) @@ -615,7 +614,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup3 (3 Unknown)`() { val cards = AttackLineUps.Unknown.lineup3 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, ) @@ -625,7 +624,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup3 (3 Unknown) + Valid NP`() { val cards = AttackLineUps.Unknown.lineup3 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), ) @@ -636,7 +635,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup3 (3 Unknown) + Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup3 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -650,7 +649,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup4 (5 Unknown)`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, ) @@ -660,7 +659,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup4 (5 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), ) @@ -671,7 +670,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup4 (5 Unknown) + 1 Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -685,7 +684,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup4 (5 Unknown) + 2 Valid NP`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), ) @@ -696,7 +695,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup4 (5 Unknown) + 2 Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( @@ -711,7 +710,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup4 (5 Unknown) + 3 Valid NP`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), ) @@ -722,7 +721,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup4 (5 Unknown) + 3 Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( @@ -738,7 +737,7 @@ class AttackUtilsTest { @Test fun `isChainable, Unknown - lineup4 (5 Unknown) + 2 Valid NP + 1 Unknown NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup4 - val result = utils.isChainable( + val result = AttackUtils.isChainable( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index e09894a1a..59f4e82bc 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -13,7 +13,7 @@ import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class AvoidChainTest { - val avoidChainHandler = AvoidChainHandler(utils = AttackUtils()) + val avoidChainHandler = AvoidChainHandler() @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 342e1d5cd..91b22d4b5 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -22,12 +22,8 @@ class BraveChainTest { @BeforeTest fun init() { - val utils = AttackUtils() - avoidChainHandler = AvoidChainHandler( - utils = utils - ) + avoidChainHandler = AvoidChainHandler() braveChainHandler = BraveChainHandler( - utils = AttackUtils(), avoidChainHandler = avoidChainHandler, ) } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 1321a466e..00f989e18 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -30,23 +30,19 @@ class CardChainPriorityIntegrationTest { @BeforeTest fun init() { - val utils = AttackUtils() - mightyChainHandler = MightyChainHandler(utils) - colorChainHandler = ColorChainHandler(utils) - avoidChainHandler = AvoidChainHandler(utils) + mightyChainHandler = MightyChainHandler() + colorChainHandler = ColorChainHandler() + avoidChainHandler = AvoidChainHandler() braveChainHandler = BraveChainHandler( - utils = utils, avoidChainHandler = avoidChainHandler ) attackPriorityHandler = AttackPriorityHandler( braveChainHandler = braveChainHandler, - utils = utils, cardChainPriorityHandler = CardChainPriorityHandler( mightyChainHandler = mightyChainHandler, colorChainHandler = colorChainHandler, avoidChainHandler = avoidChainHandler, - utils = utils, ), ) } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 572ac30b1..95ce29f73 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -15,7 +15,7 @@ import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class ColorChainTest { - val colorChain = ColorChainHandler(utils = AttackUtils()) + val colorChain = ColorChainHandler() val cardTypeList = CardTypeEnum.entries val braveChainEnums = BraveChainEnum.entries diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 42cef2ddd..c006cd4d9 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -23,10 +23,7 @@ class MightyChainTest { @BeforeTest fun init() { - utils = AttackUtils() - mightyChainHandler = MightyChainHandler( - utils = utils - ) + mightyChainHandler = MightyChainHandler() } @Test From 28f4a5af0056e826a6d24bf38553f5fba5b2a1db Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:31:47 +0800 Subject: [PATCH 081/124] Change AvoidChainHandler to object --- .../modules/attack/AvoidChainHandler.kt | 3 +- .../modules/attack/BraveChainHandler.kt | 6 +-- .../attack/CardChainPriorityHandler.kt | 3 +- .../scripts/attack/AvoidChainTest.kt | 52 +++++++++---------- .../scripts/attack/BraveChainTest.kt | 9 +--- .../CardChainPriorityIntegrationTest.kt | 9 +--- 6 files changed, 33 insertions(+), 49 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index 834511cd7..b8d235137 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -9,8 +9,7 @@ import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject import kotlin.collections.set -@ScriptScope -class AvoidChainHandler @Inject constructor() { +object AvoidChainHandler { fun pick( cards: List, npUsage: NPUsage = NPUsage.none, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 0acc4696e..5204155fc 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -9,9 +9,7 @@ import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @ScriptScope -class BraveChainHandler @Inject constructor( - private val avoidChainHandler: AvoidChainHandler, -) { +class BraveChainHandler @Inject constructor() { fun pick( npUsage: NPUsage = NPUsage.none, cards: List, @@ -19,7 +17,7 @@ class BraveChainHandler @Inject constructor( cardCountPerFieldSlotMap: Map? = null, ): List? { if (braveChainEnum == BraveChainEnum.Avoid) { - return avoidChainHandler.pick( + return AvoidChainHandler.pick( cards = cards, npUsage = npUsage, avoidBraveChains = true, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index fb207de3a..62d6fa1ef 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -14,7 +14,6 @@ import kotlin.collections.plus class CardChainPriorityHandler @Inject constructor( private val mightyChainHandler: MightyChainHandler, private val colorChainHandler: ColorChainHandler, - private val avoidChainHandler: AvoidChainHandler, ) { fun pick( cards: List, @@ -69,7 +68,7 @@ class CardChainPriorityHandler @Inject constructor( cardCountPerCardTypeMap = cardCountPerCardTypeMap, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) - ChainTypeEnum.Avoid -> avoidChainHandler.pick( + ChainTypeEnum.Avoid -> AvoidChainHandler.pick( cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index 59f4e82bc..446949aff 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -13,12 +13,10 @@ import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class AvoidChainTest { - val avoidChainHandler = AvoidChainHandler() - @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -34,7 +32,7 @@ class AvoidChainTest { @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -50,7 +48,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() @@ -67,7 +65,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -87,7 +85,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), npTypes = mapOf( @@ -107,7 +105,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( @@ -127,7 +125,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -143,7 +141,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -159,7 +157,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -176,7 +174,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -193,7 +191,7 @@ class AvoidChainTest { @Test fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards )?.map { it.card } ?: emptyList() @@ -212,7 +210,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), npTypes = mapOf( @@ -233,7 +231,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( @@ -254,7 +252,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1B, 2B), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), npTypes = mapOf( @@ -275,7 +273,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), npTypes = mapOf( @@ -291,7 +289,7 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Unknown NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -305,7 +303,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup1 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -321,7 +319,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup1 (1 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() @@ -338,7 +336,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup1 (1 Unknown) + 1 Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -358,7 +356,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup1 (1 Unknown) + 1 Unknown NP`() { val cards = AttackLineUps.Unknown.lineup1 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -372,7 +370,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup2 (2 Unknown)`() { val cards = AttackLineUps.Unknown.lineup2 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -388,7 +386,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup3 (3 Unknown)`() { val cards = AttackLineUps.Unknown.lineup3 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() @@ -398,7 +396,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup3 (3 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup3 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), )?.map { it.card } ?: emptyList() @@ -415,7 +413,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup3 (3 Unknown) + 1 Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup3 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), npTypes = mapOf( @@ -435,7 +433,7 @@ class AvoidChainTest { @Test fun `Unknown - lineup4 (5 Unknown)`() { val cards = AttackLineUps.Unknown.lineup4 - val picked = avoidChainHandler.pick( + val picked = AvoidChainHandler.pick( cards = cards, )?.map { it.card } ?: emptyList() diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 91b22d4b5..0062ad6c9 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -16,16 +16,11 @@ import kotlin.test.Test class BraveChainTest { lateinit var braveChainHandler: BraveChainHandler - lateinit var avoidChainHandler: AvoidChainHandler - val braveChainEnums = BraveChainEnum.entries @BeforeTest fun init() { - avoidChainHandler = AvoidChainHandler() - braveChainHandler = BraveChainHandler( - avoidChainHandler = avoidChainHandler, - ) + braveChainHandler = BraveChainHandler() } fun assertDefaultAvoidChain ( @@ -35,7 +30,7 @@ class BraveChainTest { npUsage: NPUsage = NPUsage.none, ) { if (braveChainEnum != BraveChainEnum.Avoid) return - val defaultAvoid = avoidChainHandler.pick( + val defaultAvoid = AvoidChainHandler.pick( cards = cards, npUsage = npUsage, avoidBraveChains = true, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 00f989e18..f9c6b55c7 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -26,23 +26,18 @@ class CardChainPriorityIntegrationTest { lateinit var braveChainHandler: BraveChainHandler lateinit var mightyChainHandler: MightyChainHandler lateinit var colorChainHandler: ColorChainHandler - lateinit var avoidChainHandler: AvoidChainHandler @BeforeTest fun init() { mightyChainHandler = MightyChainHandler() colorChainHandler = ColorChainHandler() - avoidChainHandler = AvoidChainHandler() - braveChainHandler = BraveChainHandler( - avoidChainHandler = avoidChainHandler - ) + braveChainHandler = BraveChainHandler() attackPriorityHandler = AttackPriorityHandler( braveChainHandler = braveChainHandler, cardChainPriorityHandler = CardChainPriorityHandler( mightyChainHandler = mightyChainHandler, colorChainHandler = colorChainHandler, - avoidChainHandler = avoidChainHandler, ), ) } @@ -167,7 +162,7 @@ class CardChainPriorityIntegrationTest { npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), ): List { - val results = avoidChainHandler.pick( + val results = AvoidChainHandler.pick( cards = cards, npUsage = npUsage, npTypes = npTypes, From 6c5033bc0e8e277dcd3eecd58a4e99173ee0a477 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:33:48 +0800 Subject: [PATCH 082/124] Change BraveChainHandler to object --- .../modules/attack/AttackPriorityHandler.kt | 3 +- .../modules/attack/BraveChainHandler.kt | 2 +- .../scripts/attack/BraveChainTest.kt | 52 ++++++++----------- .../CardChainPriorityIntegrationTest.kt | 5 +- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 58c6120f4..731ad2019 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -14,7 +14,6 @@ import javax.inject.Inject @ScriptScope class AttackPriorityHandler @Inject constructor( private val cardChainPriorityHandler: CardChainPriorityHandler, - private val braveChainHandler: BraveChainHandler, ) { fun rearrange( cards: List, @@ -86,7 +85,7 @@ class AttackPriorityHandler @Inject constructor( for (attackPriority in attackPriorityOrder) { when (attackPriority) { AttackPriorityEnum.BraveChainPriority -> { - braveChainFallback = braveChainHandler.pick( + braveChainFallback = BraveChainHandler.pick( cards = nonUnknownCards, braveChainEnum = braveChainEnum, npUsage = npUsage, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 5204155fc..bd59c85a7 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -9,7 +9,7 @@ import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @ScriptScope -class BraveChainHandler @Inject constructor() { +object BraveChainHandler { fun pick( npUsage: NPUsage = NPUsage.none, cards: List, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 0062ad6c9..34a133198 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -15,14 +15,8 @@ import kotlin.test.BeforeTest import kotlin.test.Test class BraveChainTest { - lateinit var braveChainHandler: BraveChainHandler val braveChainEnums = BraveChainEnum.entries - @BeforeTest - fun init() { - braveChainHandler = BraveChainHandler() - } - fun assertDefaultAvoidChain ( cards: List, picked: List, @@ -43,7 +37,7 @@ class BraveChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -63,7 +57,7 @@ class BraveChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -85,7 +79,7 @@ class BraveChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -116,7 +110,7 @@ class BraveChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -147,7 +141,7 @@ class BraveChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama + 2Scathach)`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), @@ -169,7 +163,7 @@ class BraveChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum )?.map { it.card } ?: emptyList() @@ -189,7 +183,7 @@ class BraveChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -211,7 +205,7 @@ class BraveChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2Scathach-NP`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -242,7 +236,7 @@ class BraveChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 3Nero-NP`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -273,7 +267,7 @@ class BraveChainTest { fun `BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { val cards = AttackLineUps.BusterFocus.lineup01 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -301,7 +295,7 @@ class BraveChainTest { fun `BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB) + 1 NP (2Kiyohime)`() { val cards = AttackLineUps.BusterFocus.lineup01 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -332,7 +326,7 @@ class BraveChainTest { fun `BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB) + 1 NP (3Nero)`() { val cards = AttackLineUps.BusterFocus.lineup01 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -363,7 +357,7 @@ class BraveChainTest { fun `BusterFocus - lineup02 (1KB,5KB,2KB,3NA,4NA)`() { val cards = AttackLineUps.BusterFocus.lineup02 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -391,7 +385,7 @@ class BraveChainTest { fun `BusterFocus - lineup05 (1KB,2KB,3NA,4NB,5KB)`() { val cards = AttackLineUps.BusterFocus.lineup05 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -419,7 +413,7 @@ class BraveChainTest { fun `Unknown - lineup1 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -439,7 +433,7 @@ class BraveChainTest { fun `Unknown - lineup1 (1 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -470,7 +464,7 @@ class BraveChainTest { fun `Unknown - lineup2 (2 Unknown)`() { val cards = AttackLineUps.Unknown.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -490,7 +484,7 @@ class BraveChainTest { fun `Unknown - lineup2 (2 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -521,7 +515,7 @@ class BraveChainTest { fun `Unknown - lineup3 (3 Unknown)`() { val cards = AttackLineUps.Unknown.lineup3 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -541,7 +535,7 @@ class BraveChainTest { fun `Unknown - lineup5 (1 Unknown, Brave Chain available)`() { val cards = AttackLineUps.Unknown.lineup5 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -569,7 +563,7 @@ class BraveChainTest { fun `Unknown - lineup5 (1 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup5 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -600,7 +594,7 @@ class BraveChainTest { fun `Unknown - lineup6 (2 Unknown, no Brave Chain available)`() { val cards = AttackLineUps.Unknown.lineup6 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -620,7 +614,7 @@ class BraveChainTest { fun `Unknown - lineup6 (2 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup6 for (braveChainEnum in braveChainEnums) { - val picked = braveChainHandler.pick( + val picked = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index f9c6b55c7..d5b28a2ac 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -23,7 +23,6 @@ import kotlin.test.Test class CardChainPriorityIntegrationTest { lateinit var attackPriorityHandler: AttackPriorityHandler - lateinit var braveChainHandler: BraveChainHandler lateinit var mightyChainHandler: MightyChainHandler lateinit var colorChainHandler: ColorChainHandler @@ -31,10 +30,8 @@ class CardChainPriorityIntegrationTest { fun init() { mightyChainHandler = MightyChainHandler() colorChainHandler = ColorChainHandler() - braveChainHandler = BraveChainHandler() attackPriorityHandler = AttackPriorityHandler( - braveChainHandler = braveChainHandler, cardChainPriorityHandler = CardChainPriorityHandler( mightyChainHandler = mightyChainHandler, colorChainHandler = colorChainHandler, @@ -48,7 +45,7 @@ class CardChainPriorityIntegrationTest { rearrange: Boolean = false, npUsage: NPUsage = NPUsage.none, ): List { - val results = braveChainHandler.pick( + val results = BraveChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = npUsage, From 12e056224d1cc652c0e73f5b5a0309d44981fb4a Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:35:34 +0800 Subject: [PATCH 083/124] Change ColorChainHandler to object --- .../modules/attack/BraveChainHandler.kt | 1 - .../attack/CardChainPriorityHandler.kt | 3 +- .../modules/attack/ColorChainHandler.kt | 3 +- .../CardChainPriorityIntegrationTest.kt | 5 +- .../scripts/attack/ColorChainTest.kt | 89 +++++++++---------- 5 files changed, 47 insertions(+), 54 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index bd59c85a7..3f9ed0897 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -8,7 +8,6 @@ import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject -@ScriptScope object BraveChainHandler { fun pick( npUsage: NPUsage = NPUsage.none, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 62d6fa1ef..913513086 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -13,7 +13,6 @@ import kotlin.collections.plus @ScriptScope class CardChainPriorityHandler @Inject constructor( private val mightyChainHandler: MightyChainHandler, - private val colorChainHandler: ColorChainHandler, ) { fun pick( cards: List, @@ -53,7 +52,7 @@ class CardChainPriorityHandler @Inject constructor( ) ChainTypeEnum.Arts, ChainTypeEnum.Buster, - ChainTypeEnum.Quick -> colorChainHandler.pick( + ChainTypeEnum.Quick -> ColorChainHandler.pick( cardType = when (chain) { ChainTypeEnum.Buster -> CardTypeEnum.Buster ChainTypeEnum.Arts -> CardTypeEnum.Arts diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index a23412b68..8001c7e94 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -10,8 +10,7 @@ import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject import kotlin.collections.Map -@ScriptScope -open class ColorChainHandler @Inject constructor() { +object ColorChainHandler { // Returns null if uniqueCards cannot be found fun pick ( cardType: CardTypeEnum, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index d5b28a2ac..f9201d3f8 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -24,17 +24,14 @@ class CardChainPriorityIntegrationTest { lateinit var attackPriorityHandler: AttackPriorityHandler lateinit var mightyChainHandler: MightyChainHandler - lateinit var colorChainHandler: ColorChainHandler @BeforeTest fun init() { mightyChainHandler = MightyChainHandler() - colorChainHandler = ColorChainHandler() attackPriorityHandler = AttackPriorityHandler( cardChainPriorityHandler = CardChainPriorityHandler( mightyChainHandler = mightyChainHandler, - colorChainHandler = colorChainHandler, ), ) } @@ -86,7 +83,7 @@ class CardChainPriorityIntegrationTest { npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), ): List { - val results = colorChainHandler.pick( + val results = ColorChainHandler.pick( cardType = cardType, cards = cards, npUsage = npUsage, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 95ce29f73..88d1e6198 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -15,7 +15,6 @@ import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class ColorChainTest { - val colorChain = ColorChainHandler() val cardTypeList = CardTypeEnum.entries val braveChainEnums = BraveChainEnum.entries @@ -25,7 +24,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -41,7 +40,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -57,7 +56,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -88,7 +87,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -120,7 +119,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -152,7 +151,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -173,7 +172,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -206,7 +205,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -227,7 +226,7 @@ class ColorChainTest { val cards = AttackLineUps.SingleServantOnly.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -243,7 +242,7 @@ class ColorChainTest { val cards = AttackLineUps.SingleServantOnly.lineup2 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -259,7 +258,7 @@ class ColorChainTest { val cards = AttackLineUps.SingleServantOnly.lineup3 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -275,7 +274,7 @@ class ColorChainTest { val cards = AttackLineUps.SingleServantOnly.lineup4 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -303,7 +302,7 @@ class ColorChainTest { val cards = AttackLineUps.SingleServantOnly.lineup5 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -333,7 +332,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -354,7 +353,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -386,7 +385,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -418,7 +417,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -440,7 +439,7 @@ class ColorChainTest { val cards = AttackLineUps.BusterFocus.lineup01 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -469,7 +468,7 @@ class ColorChainTest { val cards = AttackLineUps.BusterFocus.lineup01 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -502,7 +501,7 @@ class ColorChainTest { val cards = AttackLineUps.BusterFocus.lineup01 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -538,7 +537,7 @@ class ColorChainTest { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -558,7 +557,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -574,7 +573,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -605,7 +604,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup1 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -638,7 +637,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup2 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -654,7 +653,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup2 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -674,7 +673,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup2 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -707,7 +706,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup4 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -723,7 +722,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup4 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -744,7 +743,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup4 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -769,7 +768,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup5 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -798,7 +797,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup5 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -830,7 +829,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup6 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -846,7 +845,7 @@ class ColorChainTest { val cards = AttackLineUps.Unknown.lineup6 for (braveChainEnum in braveChainEnums) { for (cardType in cardTypeList) { - val result = colorChain.pick( + val result = ColorChainHandler.pick( cards = cards, cardType = cardType, braveChainEnum = braveChainEnum, @@ -881,7 +880,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, )?.map { it.card } ?: emptyList() @@ -894,7 +893,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, )?.map { it.card } ?: emptyList() @@ -907,7 +906,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + Slot 1 NP`() { val cards = AttackLineUps.Standard.lineup1 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -931,7 +930,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + Slot 2 NP`() { val cards = AttackLineUps.Standard.lineup1 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -955,7 +954,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + Slot 3 NP`() { val cards = AttackLineUps.Standard.lineup1 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -979,7 +978,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + Slot 1 NP`() { val cards = AttackLineUps.Standard.lineup2 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -1003,7 +1002,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + Slot 2 NP`() { val cards = AttackLineUps.Standard.lineup2 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -1027,7 +1026,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + Slot 3 NP`() { val cards = AttackLineUps.Standard.lineup2 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -1051,7 +1050,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { val cards = AttackLineUps.BusterFocus.lineup01 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, )?.map { it.card } ?: emptyList() @@ -1064,7 +1063,7 @@ class ColorChainTest { fun `getCardsForAvoidBraveChain, BusterFocus - lineup02 (1KB,5KB,2KB,3NA,4NA)`() { val cards = AttackLineUps.BusterFocus.lineup02 for (cardType in cardTypeList) { - val result = colorChain.getCardsForAvoidBraveChain( + val result = ColorChainHandler.getCardsForAvoidBraveChain( cards = cards, selectedCards = cards.filter { it.type == cardType }, )?.map { it.card } ?: emptyList() From 886ff8df36807fc3b4db3c02de30dc620a06640b Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:38:34 +0800 Subject: [PATCH 084/124] Change MightyChainHandler to object --- .../attack/CardChainPriorityHandler.kt | 6 +- .../modules/attack/MightyChainHandler.kt | 5 +- .../CardChainPriorityIntegrationTest.kt | 10 +- .../scripts/attack/MightyChainTest.kt | 96 +++++++++---------- 4 files changed, 50 insertions(+), 67 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 913513086..68bcc53e6 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -11,9 +11,7 @@ import javax.inject.Inject import kotlin.collections.plus @ScriptScope -class CardChainPriorityHandler @Inject constructor( - private val mightyChainHandler: MightyChainHandler, -) { +class CardChainPriorityHandler @Inject constructor() { fun pick( cards: List, chainPriority: List = ChainTypeEnum.defaultOrder, @@ -41,7 +39,7 @@ class CardChainPriorityHandler @Inject constructor( for (chain in chainPriority) { if (newCardOrder != null) continue newCardOrder = when (chain) { - ChainTypeEnum.Mighty -> mightyChainHandler.pick( + ChainTypeEnum.Mighty -> MightyChainHandler.pick( cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index ba3ca86d9..5c3cb54ee 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -10,10 +10,9 @@ import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject import kotlin.collections.Map -@ScriptScope -class MightyChainHandler @Inject constructor() { +object MightyChainHandler { // We want 3 unique types, the magic number - val totalUniqueCardTypesPermitted = 3 + const val totalUniqueCardTypesPermitted = 3 // Returns null if uniqueCards cannot be found fun pick ( diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index f9201d3f8..ec0671d0e 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -23,16 +23,10 @@ import kotlin.test.Test class CardChainPriorityIntegrationTest { lateinit var attackPriorityHandler: AttackPriorityHandler - lateinit var mightyChainHandler: MightyChainHandler - @BeforeTest fun init() { - mightyChainHandler = MightyChainHandler() - attackPriorityHandler = AttackPriorityHandler( - cardChainPriorityHandler = CardChainPriorityHandler( - mightyChainHandler = mightyChainHandler, - ), + cardChainPriorityHandler = CardChainPriorityHandler(), ) } @@ -61,7 +55,7 @@ class CardChainPriorityIntegrationTest { npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), ): List { - val results = mightyChainHandler.pick( + val results = MightyChainHandler.pick( cards = cards, npUsage = npUsage, npTypes = npTypes, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index c006cd4d9..c52f7fc4f 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -16,21 +16,13 @@ import kotlin.test.BeforeTest import kotlin.test.Test class MightyChainTest { - lateinit var mightyChainHandler: MightyChainHandler - lateinit var utils: AttackUtils - val braveChainEnums = BraveChainEnum.entries - @BeforeTest - fun init() { - mightyChainHandler = MightyChainHandler() - } - @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -49,7 +41,7 @@ class MightyChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -68,7 +60,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -83,7 +75,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -106,7 +98,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) @@ -121,7 +113,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -148,7 +140,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with Arts as npType`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -183,7 +175,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) @@ -198,7 +190,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -225,7 +217,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes & forcedBraveChain`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -243,7 +235,7 @@ class MightyChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -266,7 +258,7 @@ class MightyChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -293,7 +285,7 @@ class MightyChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2Scathach-NP, with Arts as npType`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -329,7 +321,7 @@ class MightyChainTest { fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -356,7 +348,7 @@ class MightyChainTest { fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.SingleServantOnly.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -379,7 +371,7 @@ class MightyChainTest { fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q) + Buster NP, with npTypes`() { val cards = AttackLineUps.SingleServantOnly.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -402,7 +394,7 @@ class MightyChainTest { fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -421,7 +413,7 @@ class MightyChainTest { fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -441,7 +433,7 @@ class MightyChainTest { fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup4 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -455,7 +447,7 @@ class MightyChainTest { fun `SingleServantOnly - lineup5 (1Q,2Q,3Q,4B,5B) - No mighty chain`() { val cards = AttackLineUps.SingleServantOnly.lineup5 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -471,7 +463,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) @@ -485,7 +477,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), @@ -509,7 +501,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), @@ -523,7 +515,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), @@ -541,7 +533,7 @@ class MightyChainTest { fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), @@ -563,7 +555,7 @@ class MightyChainTest { fun `Unknown - lineup1 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -576,7 +568,7 @@ class MightyChainTest { fun `Unknown - lineup1 (1 Unknown) + 1 Valid Buster NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -599,7 +591,7 @@ class MightyChainTest { fun `Unknown - lineup2 (2 Unknown)`() { val cards = AttackLineUps.Unknown.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -612,7 +604,7 @@ class MightyChainTest { fun `Unknown - lineup2 (2 Unknown) + 1 Valid Buster NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -635,7 +627,7 @@ class MightyChainTest { fun `Unknown - lineup3 (3 Unknown)`() { val cards = AttackLineUps.Unknown.lineup3 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -648,7 +640,7 @@ class MightyChainTest { fun `Unknown - lineup3 (3 Unknown) + 1 Valid Buster NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup3 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -671,7 +663,7 @@ class MightyChainTest { fun `Unknown - lineup4 (5 Unknown)`() { val cards = AttackLineUps.Unknown.lineup4 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -684,7 +676,7 @@ class MightyChainTest { fun `Unknown - lineup5 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup5 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -697,7 +689,7 @@ class MightyChainTest { fun `Unknown - lineup5 (1 Unknown) + 1 Valid Quick NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup5 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -720,7 +712,7 @@ class MightyChainTest { fun `Unknown - lineup6 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup6 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -733,7 +725,7 @@ class MightyChainTest { fun `Unknown - lineup6 (1 Unknown) + 1 Valid Quick NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup6 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -758,7 +750,7 @@ class MightyChainTest { @Test fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ)`() { val cards = AttackLineUps.MightyOutlier.lineup1 - val picked = mightyChainHandler.getMightyChainWithoutBraveChain( + val picked = MightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain )?.map { it.card } ?: emptyList() @@ -773,7 +765,7 @@ class MightyChainTest { @Test fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + ScathachNP`() { val cards = AttackLineUps.MightyOutlier.lineup1 - val picked = mightyChainHandler.getMightyChainWithoutBraveChain( + val picked = MightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain_WithQuickNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -788,7 +780,7 @@ class MightyChainTest { @Test fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + Non-Scathach Buster NP`() { val cards = AttackLineUps.MightyOutlier.lineup1 - val picked = mightyChainHandler.getMightyChainWithoutBraveChain( + val picked = MightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain_WithNonScathachBusterNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -803,7 +795,7 @@ class MightyChainTest { @Test fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + Non-Scathach Arts NP`() { val cards = AttackLineUps.MightyOutlier.lineup1 - val picked = mightyChainHandler.getMightyChainWithoutBraveChain( + val picked = MightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain_WithNonScathachArtsNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -818,7 +810,7 @@ class MightyChainTest { @Test fun `getMightyChainWithoutBraveChain, MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + Non-Scathach Quick NP`() { val cards = AttackLineUps.MightyOutlier.lineup1 - val picked = mightyChainHandler.getMightyChainWithoutBraveChain( + val picked = MightyChainHandler.getMightyChainWithoutBraveChain( cards = cards, selectedCards = AttackLineUps.MightyOutlier.lineup1_DefaultMightyChain_WithQuickNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -834,7 +826,7 @@ class MightyChainTest { fun `MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ)`() { val cards = AttackLineUps.MightyOutlier.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() @@ -864,7 +856,7 @@ class MightyChainTest { fun `MightyOutlier - lineup1 (1SB,2SA,3NA,4NA,5SQ) + 1 NP (Scathach)`() { val cards = AttackLineUps.MightyOutlier.lineup1 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -899,7 +891,7 @@ class MightyChainTest { fun `MightyOutlier - lineup2 (1SB,5SQ,2SA,3NA,4NA)`() { val cards = AttackLineUps.MightyOutlier.lineup2 for (braveChainEnum in braveChainEnums) { - val picked = mightyChainHandler.pick( + val picked = MightyChainHandler.pick( cards = cards, braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() From fcbb00ddc162e59950fb70135a5c365948a68f86 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:40:55 +0800 Subject: [PATCH 085/124] Clean up unused imports and styling warnings --- .../scripts/modules/attack/AttackUtils.kt | 2 -- .../scripts/modules/attack/AvoidChainHandler.kt | 2 -- .../scripts/modules/attack/BraveChainHandler.kt | 2 -- .../scripts/modules/attack/ColorChainHandler.kt | 4 +--- .../scripts/modules/attack/MightyChainHandler.kt | 8 +++----- .../scripts/attack/CardChainPriorityIntegrationTest.kt | 1 - 6 files changed, 4 insertions(+), 15 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index 1f3bb7463..6be04725e 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -6,8 +6,6 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot -import io.github.lib_automata.dagger.ScriptScope -import javax.inject.Inject object AttackUtils { fun getCardsPerFieldSlotMap ( diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index b8d235137..95dc277a7 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -5,8 +5,6 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot -import io.github.lib_automata.dagger.ScriptScope -import javax.inject.Inject import kotlin.collections.set object AvoidChainHandler { diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 3f9ed0897..2bd694378 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -5,8 +5,6 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot -import io.github.lib_automata.dagger.ScriptScope -import javax.inject.Inject object BraveChainHandler { fun pick( diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index 8001c7e94..d6d1e2117 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -6,8 +6,6 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot -import io.github.lib_automata.dagger.ScriptScope -import javax.inject.Inject import kotlin.collections.Map object ColorChainHandler { @@ -149,7 +147,7 @@ object ColorChainHandler { ) ) { // Attempt to fetch a different field slot - val differentCard = cards.firstOrNull() { it.fieldSlot != filteredCardsSet.first() } + val differentCard = cards.firstOrNull { it.fieldSlot != filteredCardsSet.first() } // If there is no different card (even though there should, by this stage), return null if (differentCard == null) return null // Just add new card to index 1, aka 2nd card diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 5c3cb54ee..ed9274bf1 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -6,13 +6,11 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot -import io.github.lib_automata.dagger.ScriptScope -import javax.inject.Inject import kotlin.collections.Map object MightyChainHandler { // We want 3 unique types, the magic number - const val totalUniqueCardTypesPermitted = 3 + const val TOTAL_UNIQUE_CARD_TYPES_PERMITTED = 3 // Returns null if uniqueCards cannot be found fun pick ( @@ -81,7 +79,7 @@ object MightyChainHandler { cardCountPerFieldSlotMap: Map? = null, ): List? { - val cardsToFind = totalUniqueCardTypesPermitted + val cardsToFind = TOTAL_UNIQUE_CARD_TYPES_PERMITTED val uniqueCardTypes = uniqueCardTypesAlreadyFilled.toMutableSet() var selectedCards = mutableListOf() @@ -202,7 +200,7 @@ object MightyChainHandler { if (npTypes.values.contains(CardTypeEnum.Unknown)) return false // Do not accept Unknown cards for Mighty Chain - if (cardCountPerCardTypeMap.getOrElse(CardTypeEnum.Unknown) { 0 } > 0) return false; + if (cardCountPerCardTypeMap.getOrElse(CardTypeEnum.Unknown) { 0 } > 0) return false // If we do not have 3 unique cards at least, it is impossible to Mighty Chain if (cardCountPerCardTypeMap.size < 3) return false diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index ec0671d0e..969740bb6 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -13,7 +13,6 @@ import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandler import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import io.github.fate_grand_automata.scripts.modules.attack.CardChainPriorityHandler import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler From 15995dc3ae958f309bd1d4cc5fc1cb1ac06c1825 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:42:58 +0800 Subject: [PATCH 086/124] Change CardChainPriorityHandler to an object as well --- .../scripts/modules/attack/AttackPriorityHandler.kt | 6 ++---- .../scripts/modules/attack/CardChainPriorityHandler.kt | 5 +---- .../scripts/attack/CardChainPriorityIntegrationTest.kt | 5 +---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 731ad2019..623264e44 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -12,9 +12,7 @@ import java.util.Collections import javax.inject.Inject @ScriptScope -class AttackPriorityHandler @Inject constructor( - private val cardChainPriorityHandler: CardChainPriorityHandler, -) { +class AttackPriorityHandler @Inject constructor() { fun rearrange( cards: List, rearrange: Boolean, @@ -106,7 +104,7 @@ class AttackPriorityHandler @Inject constructor( else -> chainPriority.subList(0, indexOfAvoid + allowAvoid) } - newCardOrder = cardChainPriorityHandler.pick( + newCardOrder = CardChainPriorityHandler.pick( cards = nonUnknownCards, chainPriority = filteredChainPriority, braveChainEnum = braveChainEnum, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 68bcc53e6..09a4d9b62 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -6,12 +6,9 @@ import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.lib_automata.dagger.ScriptScope -import javax.inject.Inject import kotlin.collections.plus -@ScriptScope -class CardChainPriorityHandler @Inject constructor() { +object CardChainPriorityHandler { fun pick( cards: List, chainPriority: List = ChainTypeEnum.defaultOrder, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 969740bb6..9ac84041c 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -14,7 +14,6 @@ import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandler import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.CardChainPriorityHandler import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler import kotlin.test.BeforeTest import kotlin.test.Test @@ -24,9 +23,7 @@ class CardChainPriorityIntegrationTest { @BeforeTest fun init() { - attackPriorityHandler = AttackPriorityHandler( - cardChainPriorityHandler = CardChainPriorityHandler(), - ) + attackPriorityHandler = AttackPriorityHandler() } fun getDefaultBraveChainResult ( From 4db66bb0651b611029bb7c6fb1c7c7bf3b734a52 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:45:25 +0800 Subject: [PATCH 087/124] Change AttackPriorityHandler to object --- .../scripts/modules/Card.kt | 3 +- .../modules/attack/AttackPriorityHandler.kt | 3 +- .../CardChainPriorityIntegrationTest.kt | 84 +++++++++---------- 3 files changed, 40 insertions(+), 50 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index eaf1f5da8..523d724aa 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -27,7 +27,6 @@ class Card @Inject constructor( private val parser: CardParser, private val priority: FaceCardPriority, private val braveChains: ApplyBraveChains, - private val attackPriorityHandler: AttackPriorityHandler, private val battleConfig: IBattleConfig ) : IFgoAutomataApi by api { @@ -66,7 +65,7 @@ class Card @Inject constructor( val useChainPriority = battleConfig.useChainPriority if (useChainPriority) { val chainPriority = battleConfig.chainPriority.atWave(state.stage) - return attackPriorityHandler.pick( + return AttackPriorityHandler.pick( cards = cardsOrderedByPriority, npUsage = npUsage, braveChainEnum = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 623264e44..cd94d85b2 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -11,8 +11,7 @@ import io.github.lib_automata.dagger.ScriptScope import java.util.Collections import javax.inject.Inject -@ScriptScope -class AttackPriorityHandler @Inject constructor() { +object AttackPriorityHandler { fun rearrange( cards: List, rearrange: Boolean, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 9ac84041c..2ca31467b 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -15,17 +15,9 @@ import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandle import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler -import kotlin.test.BeforeTest import kotlin.test.Test class CardChainPriorityIntegrationTest { - lateinit var attackPriorityHandler: AttackPriorityHandler - - @BeforeTest - fun init() { - attackPriorityHandler = AttackPriorityHandler() - } - fun getDefaultBraveChainResult ( cards: List, braveChainEnum: BraveChainEnum, @@ -37,7 +29,7 @@ class CardChainPriorityIntegrationTest { braveChainEnum = braveChainEnum, npUsage = npUsage, ) ?: cards - return attackPriorityHandler.rearrange( + return AttackPriorityHandler.rearrange( cards = results, rearrange = rearrange, npUsage = npUsage, @@ -58,7 +50,7 @@ class CardChainPriorityIntegrationTest { braveChainEnum = braveChainEnum, forceBraveChain = braveChainEnum == BraveChainEnum.Always, ) ?: cards - return attackPriorityHandler.rearrange( + return AttackPriorityHandler.rearrange( cards = results, rearrange = rearrange, npUsage = npUsage, @@ -81,7 +73,7 @@ class CardChainPriorityIntegrationTest { braveChainEnum = braveChainEnum, forceBraveChain = braveChainEnum == BraveChainEnum.Always, ) ?: cards - return attackPriorityHandler.rearrange( + return AttackPriorityHandler.rearrange( cards = results, rearrange = rearrange, npUsage = npUsage, @@ -153,7 +145,7 @@ class CardChainPriorityIntegrationTest { avoidBraveChains = braveChainEnum == BraveChainEnum.Avoid, avoidCardChains = true, ) ?: cards - return attackPriorityHandler.rearrange( + return AttackPriorityHandler.rearrange( cards = results, rearrange = rearrange, npUsage = npUsage, @@ -163,7 +155,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -175,7 +167,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), No Mighty allowed`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, chainPriority = ChainTypeEnum.defaultOrder.filter { it != ChainTypeEnum.Mighty }, @@ -191,7 +183,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), No Mighty allowed, raw`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, chainPriority = ChainTypeEnum.defaultOrder.filter { it != ChainTypeEnum.Mighty }, @@ -204,7 +196,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, rearrange = true @@ -217,7 +209,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -229,7 +221,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA), with rearrange`() { val cards = AttackLineUps.Standard.lineup2 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, rearrange = true @@ -242,7 +234,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) @@ -260,7 +252,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -280,7 +272,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -296,7 +288,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with rearrange & npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0), @@ -313,7 +305,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -331,7 +323,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with rearrange`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -350,7 +342,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -371,7 +363,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes, raw`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -388,7 +380,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes & rearrange`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -411,7 +403,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes & rearrange, raw`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0), @@ -428,7 +420,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.C), 0) @@ -442,7 +434,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearranged`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -456,7 +448,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -472,7 +464,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearrange & npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.C), 0), @@ -489,7 +481,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup1 (BQABQ)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -502,7 +494,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup1 (BQABQ), with rearranged`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, rearrange = true @@ -515,7 +507,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup2 (BBQQA)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -527,7 +519,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup2 (BBQQA), with rearranged`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, rearrange = true @@ -540,7 +532,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -552,7 +544,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B) - Buster priority`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, chainPriority = listOf(ChainTypeEnum.Buster, ChainTypeEnum.Mighty), @@ -574,7 +566,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B), with rearranged`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, rearrange = true @@ -588,7 +580,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -604,7 +596,7 @@ class CardChainPriorityIntegrationTest { @Test fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, ).map { it.card } @@ -622,7 +614,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (2Kama, 3Nero) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B, CommandCard.NP.C), 0) @@ -640,7 +632,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), @@ -657,7 +649,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach) - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), @@ -675,7 +667,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes - No mighty chain`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), @@ -703,7 +695,7 @@ class CardChainPriorityIntegrationTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = attackPriorityHandler.pick( + val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), From d79125fcbb158ae003684b4cb76297ccc10a7ed9 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:46:38 +0800 Subject: [PATCH 088/124] Clean up unused imports --- .../scripts/modules/attack/AttackPriorityHandler.kt | 2 -- .../fate_grand_automata/scripts/attack/AvoidChainTest.kt | 2 -- .../fate_grand_automata/scripts/attack/BraveChainTest.kt | 8 +++----- .../fate_grand_automata/scripts/attack/ColorChainTest.kt | 1 - .../fate_grand_automata/scripts/attack/MightyChainTest.kt | 4 ---- 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index cd94d85b2..15474a2c2 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -7,9 +7,7 @@ import io.github.fate_grand_automata.scripts.enums.ChainTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.lib_automata.dagger.ScriptScope import java.util.Collections -import javax.inject.Inject object AttackPriorityHandler { fun rearrange( diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index 446949aff..182be28f0 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -3,14 +3,12 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty -import assertk.assertions.isEqualTo import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import kotlin.test.Test import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class AvoidChainTest { @Test diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 34a133198..5778a3616 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -8,10 +8,8 @@ import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard -import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler -import kotlin.test.BeforeTest import kotlin.test.Test class BraveChainTest { @@ -278,7 +276,7 @@ class BraveChainTest { braveChainEnum = braveChainEnum, picked = picked, ) - BraveChainEnum.Always, -> + BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).containsExactly( CommandCard.Face.A, CommandCard.Face.B, @@ -368,7 +366,7 @@ class BraveChainTest { braveChainEnum = braveChainEnum, picked = picked, ) - BraveChainEnum.Always, -> + BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).containsExactly( CommandCard.Face.A, CommandCard.Face.E, @@ -396,7 +394,7 @@ class BraveChainTest { braveChainEnum = braveChainEnum, picked = picked, ) - BraveChainEnum.Always, -> + BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).containsExactly( CommandCard.Face.A, CommandCard.Face.B, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 88d1e6198..95cc54cfc 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -12,7 +12,6 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage import kotlin.test.Test import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils class ColorChainTest { val cardTypeList = CardTypeEnum.entries diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index c52f7fc4f..df5d4bc45 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -3,16 +3,12 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty -import assertk.assertions.isEqualTo import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage -import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.modules.attack.MightyChainHandler -import io.github.fate_grand_automata.scripts.modules.attack.AttackUtils -import kotlin.test.BeforeTest import kotlin.test.Test class MightyChainTest { From d7c39bee0a7450ad132c410f59c385767c326ce4 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 00:48:54 +0800 Subject: [PATCH 089/124] Change ApplyBraveChains to object --- .../scripts/modules/ApplyBraveChains.kt | 5 +-- .../scripts/modules/Card.kt | 3 +- .../scripts/BraveChainsTest.kt | 36 +++++-------------- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt index 1c8976671..c433d5379 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ApplyBraveChains.kt @@ -4,12 +4,9 @@ import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.models.NPUsage import io.github.fate_grand_automata.scripts.models.ParsedCard import io.github.fate_grand_automata.scripts.models.toFieldSlot -import io.github.lib_automata.dagger.ScriptScope import java.util.Collections -import javax.inject.Inject -@ScriptScope -class ApplyBraveChains @Inject constructor() { +object ApplyBraveChains { private fun rearrange( cards: List, rearrange: Boolean, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index 523d724aa..c700043d5 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -26,7 +26,6 @@ class Card @Inject constructor( private val caster: Caster, private val parser: CardParser, private val priority: FaceCardPriority, - private val braveChains: ApplyBraveChains, private val battleConfig: IBattleConfig ) : IFgoAutomataApi by api { @@ -75,7 +74,7 @@ class Card @Inject constructor( ).map { it.card } } - return braveChains.pick( + return ApplyBraveChains.pick( cards = cardsOrderedByPriority, npUsage = npUsage, braveChains = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt index 8173e5b5b..c498ee3cf 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt @@ -11,10 +11,8 @@ import kotlin.test.Test class BraveChainsTest { private fun shouldReturnSame(mode: BraveChainEnum) { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = mode ) @@ -28,10 +26,8 @@ class BraveChainsTest { } private fun justRearrange(mode: BraveChainEnum) { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = mode, rearrange = true @@ -57,10 +53,8 @@ class BraveChainsTest { @Test fun braveChainsWith1MatchingCard() { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.A), 0) @@ -71,10 +65,8 @@ class BraveChainsTest { @Test fun braveChainsRearrangeWith1MatchingCard() { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNP, rearrange = true, @@ -86,10 +78,8 @@ class BraveChainsTest { @Test fun braveChainsWith2MatchingCards() { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNP, npUsage = NPUsage(setOf(CommandCard.NP.B), 0) @@ -100,10 +90,8 @@ class BraveChainsTest { @Test fun braveChainsRearrangeWith2MatchingCards() { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = BraveChainEnum.WithNP, rearrange = true, @@ -115,10 +103,8 @@ class BraveChainsTest { @Test fun avoidBraveChains3DiffCards() { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup1 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = BraveChainEnum.Avoid ).map { it.card } @@ -133,10 +119,8 @@ class BraveChainsTest { @Test fun avoidBraveChainsWith2DiffCards() { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup2 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = BraveChainEnum.Avoid ).map { it.card } @@ -146,10 +130,8 @@ class BraveChainsTest { @Test fun avoidBraveChainsRearrangeWith2DiffCards() { - val braveChains = ApplyBraveChains() - val cards = FaceCardPriorityTest.lineup2 - val picked = braveChains.pick( + val picked = ApplyBraveChains.pick( cards = cards, braveChains = BraveChainEnum.Avoid, rearrange = true From 11b03db2db750e22a9c09bb3ac923af4f889d4de Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 03:12:45 +0800 Subject: [PATCH 090/124] Add NP detection system using faces --- .../locations/AttackScreenLocations.kt | 6 +++ .../scripts/modules/Card.kt | 11 ++++- .../scripts/modules/ServantTracker.kt | 40 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/locations/AttackScreenLocations.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/locations/AttackScreenLocations.kt index fbd51528b..3f3d29616 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/locations/AttackScreenLocations.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/locations/AttackScreenLocations.kt @@ -53,6 +53,12 @@ class AttackScreenLocations @Inject constructor( CommandCard.Face.E -> 880 }.let { x -> Region(x - 100, 700, 500, 400) + faceCardDeltaY }.xFromCenter() + fun servantNPMatchRegion(card: CommandCard.NP) = when (card) { + CommandCard.NP.A -> -620 + CommandCard.NP.B -> -150 + CommandCard.NP.C -> 324 + }.let { x -> Region(x - 50, 100, 400, 400) + faceCardDeltaY }.xFromCenter() + fun supportCheckRegion(card: CommandCard.Face) = affinityRegion(card) + Location(-50, 100) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index c700043d5..f7a53f035 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -63,10 +63,11 @@ class Card @Inject constructor( val useChainPriority = battleConfig.useChainPriority if (useChainPriority) { + val detectedNPs = npUsage.detected() val chainPriority = battleConfig.chainPriority.atWave(state.stage) return AttackPriorityHandler.pick( cards = cardsOrderedByPriority, - npUsage = npUsage, + npUsage = detectedNPs, braveChainEnum = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), chainPriority = chainPriority, rearrange = rearrangeCardsPerWave.inCurrentWave(false), @@ -116,4 +117,12 @@ class Card @Inject constructor( if (teamSlot is TeamSlot.Unknown) return CardTypeEnum.Unknown return servantTracker.getNpCardType(teamSlot) } + + fun NPUsage.detected (): NPUsage { + val npCardsDetected = servantTracker.npCardsDetected() + val validNPs = (this.nps + spamNps) + .filter { it in npCardsDetected } + .toSet() + return NPUsage(validNPs, this.cardsBeforeNP) + } } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt index 078dd7f6f..1b05c7e36 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt @@ -9,6 +9,7 @@ import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.OrderChangeMember import io.github.fate_grand_automata.scripts.models.TeamSlot import io.github.fate_grand_automata.scripts.models.skills +import io.github.fate_grand_automata.scripts.models.toFieldSlot import io.github.lib_automata.Pattern import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject @@ -286,6 +287,38 @@ class ServantTracker @Inject constructor( return result } + val npCardDetectionScoreCutoff = 0.8 + fun npCardsDetected(): Set { + if (prefs.skipServantFaceCardCheck) { + return emptySet() + } + + val npCards = CommandCard.NP.list.toSet() + val deployedServants = faceCardImages + .filterKeys { it in deployed.values } + + val scores = npCards + .map { npCard -> + npCard to deployedServants + .filterKeys { it.toFieldSlot() == npCard.toFieldSlot() } + .mapValues { (_, images) -> + images.maxOf { image -> + locations.attack.servantNPMatchRegion(npCard) + .find(image, 0.4)?.score ?: 0.0 + } + } + .values + } + val result = scores + .filter { (_, values) -> + values.filter { it > npCardDetectionScoreCutoff }.maxByOrNull { it } != null + } + .map { it.first } + .toSet() + + return result + } + /** * Adds the 3rd Ascension Melusine image to the existing 1st/2nd Ascension * image so both are detected as the same Servant. @@ -308,4 +341,11 @@ class ServantTracker @Inject constructor( */ private fun isSupport(slot: FieldSlot) = !prefs.treatSupportLikeOwnServant && images[Images.ServantCheckSupport] in locations.battle.servantChangeSupportCheckRegion(slot) + + fun TeamSlot.toFieldSlot(): FieldSlot? { + return deployed + .entries + .firstOrNull { (_, teamSlot) -> teamSlot == this } + ?.key + } } From a9992fa1bef80294cbff548d335662065bd77e90 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 22:36:09 +0800 Subject: [PATCH 091/124] Add None as an option in ChainTypeEnum, to replace Avoid's role as a cutoff option. --- .../ui/card_priority/Render.kt | 13 +++++++------ .../util/LocalizationExtensions.kt | 1 + .../scripts/enums/ChainTypeEnum.kt | 5 ++++- .../modules/attack/AttackPriorityHandler.kt | 16 ++-------------- .../modules/attack/CardChainPriorityHandler.kt | 1 + .../attack/CardChainPriorityIntegrationTest.kt | 4 ++-- 6 files changed, 17 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index c3c7f06f8..7e1e9651c 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -149,25 +149,26 @@ private fun ChainPriority( val context = LocalContext.current val getBackgroundColor = fun (enum: ChainTypeEnum): Int { - return (if (priorities.isAfterAvoid(enum)) R.color.colorDisabled + return (if (priorities.isAfterCutoff(enum)) R.color.colorDisabled else when (enum) { ChainTypeEnum.Arts -> R.color.colorArts ChainTypeEnum.Quick -> R.color.colorQuick ChainTypeEnum.Buster -> R.color.colorBuster ChainTypeEnum.Mighty -> R.color.colorPrimaryDark ChainTypeEnum.Avoid -> R.color.colorAvoid + ChainTypeEnum.None -> R.color.colorDisabled } ).let { res -> context.getColor(res) } } val localizedStringMap: MutableMap = mutableMapOf() - priorities.forEach { - localizedStringMap[it] = stringResource(it.stringRes) + ChainTypeEnum.entries.forEach { + localizedStringMap[it] = if (it == ChainTypeEnum.None) "│" else stringResource(it.stringRes) } DragSort( items = priorities, viewConfigGrabber = { DragSortAdapter.ItemViewConfig( - foregroundColor = Color.WHITE, + foregroundColor = if (it == ChainTypeEnum.None) Color.BLACK else Color.WHITE, backgroundColor = getBackgroundColor(it), text = localizedStringMap.getOrElse(it) { it.name }, ) @@ -176,8 +177,8 @@ private fun ChainPriority( ) } -fun MutableList.isAfterAvoid(chainTypeEnum: ChainTypeEnum, indexOfAvoid: Int? = null): Boolean { - val avoidIndex = indexOfAvoid ?: this.indexOf(ChainTypeEnum.Avoid) +fun MutableList.isAfterCutoff(chainTypeEnum: ChainTypeEnum, indexOfAvoid: Int? = null): Boolean { + val avoidIndex = indexOfAvoid ?: this.indexOf(ChainTypeEnum.Cutoff) val enumIndex = this.indexOf(chainTypeEnum) if (avoidIndex < 0) return false return enumIndex > avoidIndex diff --git a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt index be1e64867..dd5f2658b 100644 --- a/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt +++ b/app/src/main/java/io/github/fate_grand_automata/util/LocalizationExtensions.kt @@ -37,6 +37,7 @@ val ChainTypeEnum.stringRes ChainTypeEnum.Buster -> R.string.p_chain_type_buster ChainTypeEnum.Mighty -> R.string.p_chain_type_mighty ChainTypeEnum.Avoid -> R.string.p_chain_type_avoid + ChainTypeEnum.None -> R.string.p_spam_none } val SpamEnum.stringRes diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt index 4ead6c18b..5997a5f64 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/ChainTypeEnum.kt @@ -5,9 +5,12 @@ enum class ChainTypeEnum { Buster, Arts, Quick, + None, Avoid; companion object { - val defaultOrder = listOf(Mighty, Buster, Arts, Quick, Avoid) + val Cutoff = None + val defaultOrder = listOf(Mighty, Buster, Arts, Quick, None, Avoid) + val allPermitted = listOf(Mighty, Buster, Arts, Quick, Avoid) } } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index 15474a2c2..a30cdd152 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -90,20 +90,9 @@ object AttackPriorityHandler { AttackPriorityEnum.CardChainPriority -> { if (newCardOrder != null) continue - // Determine the allowed chain priority - val indexOfAvoid = chainPriority.indexOf(ChainTypeEnum.Avoid) - // If braveChainFallback does not exist, - // 'Avoid' is to be included, since it is treated as cardPriorityHandler's own fallback method - val allowAvoid = if (braveChainFallback == null) 1 else 0 - val filteredChainPriority = when (indexOfAvoid) { - -1 -> chainPriority - 0 -> listOf(ChainTypeEnum.Avoid) - else -> chainPriority.subList(0, indexOfAvoid + allowAvoid) - } - newCardOrder = CardChainPriorityHandler.pick( cards = nonUnknownCards, - chainPriority = filteredChainPriority, + chainPriority = chainPriority, braveChainEnum = braveChainEnum, npUsage = npUsage, npTypes = npTypes, @@ -111,8 +100,7 @@ object AttackPriorityHandler { cardCountPerCardTypeMap = cardCountPerCardTypeMap, // BraveChain is higher priority than color chain forceBraveChain = braveChainFallback != null - && braveChainEnum != BraveChainEnum.Avoid - && braveChainEnum != BraveChainEnum.None + && (braveChainEnum == BraveChainEnum.WithNP || braveChainEnum == BraveChainEnum.Always) ) } else -> continue diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 09a4d9b62..a73dee1a4 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -36,6 +36,7 @@ object CardChainPriorityHandler { for (chain in chainPriority) { if (newCardOrder != null) continue newCardOrder = when (chain) { + ChainTypeEnum.None -> break; ChainTypeEnum.Mighty -> MightyChainHandler.pick( cards = nonUnknownCards, npUsage = npUsage, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 2ca31467b..7ed27048f 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -170,7 +170,7 @@ class CardChainPriorityIntegrationTest { val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, - chainPriority = ChainTypeEnum.defaultOrder.filter { it != ChainTypeEnum.Mighty }, + chainPriority = ChainTypeEnum.allPermitted.filter { it != ChainTypeEnum.Mighty }, ).map { it.card } val pickedDefault = getDefaultAvoidChainResult( cards = cards, @@ -186,7 +186,7 @@ class CardChainPriorityIntegrationTest { val picked = AttackPriorityHandler.pick( cards = cards, braveChainEnum = BraveChainEnum.WithNP, - chainPriority = ChainTypeEnum.defaultOrder.filter { it != ChainTypeEnum.Mighty }, + chainPriority = ChainTypeEnum.allPermitted.filter { it != ChainTypeEnum.Mighty }, ).map { it.card } // Expect avoid SB,KQ,SQ,NA,NA - 12534 - ABECD From 8748533e801c860f0b053f3ad263d7cb7c06f374 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 8 Sep 2025 22:46:28 +0800 Subject: [PATCH 092/124] Add fix for old builds that do not have the type --- .../scripts/models/battle/ChainPriorityPerWave.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt index 22ebec7a1..554406f46 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/models/battle/ChainPriorityPerWave.kt @@ -35,6 +35,9 @@ class ChainPriorityPerWave private constructor( .map { ChainTypeEnum.valueOf(it) } + .toSet() + .plusElement(ChainTypeEnum.None) + .toList() } ) } From 99a397a02c61f9133b5b823709fdf4d3aa7e47f5 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 9 Sep 2025 04:24:49 +0800 Subject: [PATCH 093/124] Change color of disabled text --- .../fate_grand_automata/ui/card_priority/Render.kt | 13 ++++++++++--- .../ui/drag_sort/DragSortAdapter.kt | 2 ++ app/src/main/res/values/colors.xml | 3 ++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index 7e1e9651c..14e8d3fb2 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -148,15 +148,22 @@ private fun ChainPriority( val context = LocalContext.current + val getForegroundColor = fun (enum: ChainTypeEnum): Int { + return (if (priorities.isAfterCutoff(enum)) R.color.colorDisabledText + else when (enum) { + ChainTypeEnum.None -> Color.BLACK + else -> Color.WHITE + }) + } val getBackgroundColor = fun (enum: ChainTypeEnum): Int { - return (if (priorities.isAfterCutoff(enum)) R.color.colorDisabled + return (if (priorities.isAfterCutoff(enum)) R.color.colorDisabledBackground else when (enum) { ChainTypeEnum.Arts -> R.color.colorArts ChainTypeEnum.Quick -> R.color.colorQuick ChainTypeEnum.Buster -> R.color.colorBuster ChainTypeEnum.Mighty -> R.color.colorPrimaryDark ChainTypeEnum.Avoid -> R.color.colorAvoid - ChainTypeEnum.None -> R.color.colorDisabled + ChainTypeEnum.None -> R.color.colorDisabledBackground } ).let { res -> context.getColor(res) } } @@ -168,7 +175,7 @@ private fun ChainPriority( items = priorities, viewConfigGrabber = { DragSortAdapter.ItemViewConfig( - foregroundColor = if (it == ChainTypeEnum.None) Color.BLACK else Color.WHITE, + foregroundColor = getForegroundColor(it), backgroundColor = getBackgroundColor(it), text = localizedStringMap.getOrElse(it) { it.name }, ) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSortAdapter.kt b/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSortAdapter.kt index b90988f7d..2285d5a96 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSortAdapter.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/drag_sort/DragSortAdapter.kt @@ -73,6 +73,8 @@ class DragSortAdapter( val view = views[index] if (updateBackgroundColorOnMove && view != null) { view.setBackgroundColor(viewConfig.backgroundColor) + val textView: TextView = view.findViewById(R.id.drag_sort_text) + textView.setTextColor(viewConfig.foregroundColor) } } diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index c70ffcdb3..632ddcb2c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,7 +2,8 @@ #1B3147 #3498db - #ebebe4 + #efefef + #1010104d #2e7d32 #006064 From bfeffd8ba1f690b6c2ae4b752c8a0c73bcfa19e0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 9 Sep 2025 04:35:35 +0800 Subject: [PATCH 094/124] Fix bug with text color. Set better text color for disabled --- .../io/github/fate_grand_automata/ui/card_priority/Render.kt | 2 +- app/src/main/res/values/colors.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt index 14e8d3fb2..f06ec2c01 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/card_priority/Render.kt @@ -149,7 +149,7 @@ private fun ChainPriority( val context = LocalContext.current val getForegroundColor = fun (enum: ChainTypeEnum): Int { - return (if (priorities.isAfterCutoff(enum)) R.color.colorDisabledText + return (if (priorities.isAfterCutoff(enum)) context.getColor(R.color.colorDisabledText) else when (enum) { ChainTypeEnum.None -> Color.BLACK else -> Color.WHITE diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 632ddcb2c..74ddc70cd 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,7 +3,7 @@ #1B3147 #3498db #efefef - #1010104d + #c0c0c0 #2e7d32 #006064 From 87c0222b7e70fdc901b9fde1ba521e8167803950 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 9 Sep 2025 10:26:21 +0800 Subject: [PATCH 095/124] Make the disabled text color more visible --- app/src/main/res/values/colors.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 74ddc70cd..312d6a4c2 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,7 +3,7 @@ #1B3147 #3498db #efefef - #c0c0c0 + #909090 #2e7d32 #006064 From 5f935e52df08157ca8a1fc8692dd1dbe6ee0602b Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 9 Sep 2025 10:55:20 +0800 Subject: [PATCH 096/124] Split detection function in preparation for future feature --- .../io/github/fate_grand_automata/scripts/modules/Card.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index f7a53f035..4a80d6215 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -119,10 +119,14 @@ class Card @Inject constructor( } fun NPUsage.detected (): NPUsage { + val validNPs = (this.nps + spamNps).detected() + return NPUsage(validNPs, this.cardsBeforeNP) + } + + fun Set.detected (): Set { val npCardsDetected = servantTracker.npCardsDetected() - val validNPs = (this.nps + spamNps) + return this .filter { it in npCardsDetected } .toSet() - return NPUsage(validNPs, this.cardsBeforeNP) } } \ No newline at end of file From 11fcdb9f478edd8451eee282be885f373677d250 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 9 Sep 2025 21:17:24 +0800 Subject: [PATCH 097/124] If ChainPriority is on, use the NP automatic detection for clicking as well. --- .../io/github/fate_grand_automata/scripts/modules/Card.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index 4a80d6215..ec71d0706 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -99,8 +99,11 @@ class Card @Inject constructor( val nps = npUsage.nps + spamNps - if (nps.isNotEmpty()) { - nps + val useChainPriority = battleConfig.useChainPriority + val npsToUse = if (useChainPriority) nps.detected() else nps + + if (npsToUse.isNotEmpty()) { + npsToUse .also { messages.log(ScriptLog.ClickingNPs(it)) } .forEach { caster.use(it) } } From 3c69e9257d3dc9d1ce0d527dfd589e1fc5fe39ca Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 10 Sep 2025 00:01:03 +0800 Subject: [PATCH 098/124] Fix bug where npTypes were not detected for spam NPs --- .../io/github/fate_grand_automata/scripts/modules/Card.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index ec71d0706..ca3c86fff 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -59,15 +59,15 @@ class Card @Inject constructor( val braveChainsPerWave = battleConfig.braveChains val rearrangeCardsPerWave = battleConfig.rearrangeCards - val npTypes = npUsage.nps.associate { it.toFieldSlot() to it.type() } val useChainPriority = battleConfig.useChainPriority if (useChainPriority) { - val detectedNPs = npUsage.detected() + val detectedNPUsage = npUsage.detected() + val npTypes = detectedNPUsage.nps.associate { it.toFieldSlot() to it.type() } val chainPriority = battleConfig.chainPriority.atWave(state.stage) return AttackPriorityHandler.pick( cards = cardsOrderedByPriority, - npUsage = detectedNPs, + npUsage = detectedNPUsage, braveChainEnum = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), chainPriority = chainPriority, rearrange = rearrangeCardsPerWave.inCurrentWave(false), From 42977d437c33218392a8dfa185bd7f07ab6ed88c Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 10 Sep 2025 00:46:26 +0800 Subject: [PATCH 099/124] Revert "If ChainPriority is on, use the NP automatic detection for clicking as well." This reverts commit 11fcdb9f478edd8451eee282be885f373677d250. --- .../io/github/fate_grand_automata/scripts/modules/Card.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index ca3c86fff..372d928d8 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -99,11 +99,8 @@ class Card @Inject constructor( val nps = npUsage.nps + spamNps - val useChainPriority = battleConfig.useChainPriority - val npsToUse = if (useChainPriority) nps.detected() else nps - - if (npsToUse.isNotEmpty()) { - npsToUse + if (nps.isNotEmpty()) { + nps .also { messages.log(ScriptLog.ClickingNPs(it)) } .forEach { caster.use(it) } } From 677dfc636e7d92d2c7213239bf5d61605d0c69cb Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 10 Sep 2025 02:02:57 +0800 Subject: [PATCH 100/124] Add additional checks so that it won't immediately fail if it cannot detect the expected NPs.. --- .../scripts/modules/Card.kt | 18 ++++++++++++++++-- .../scripts/modules/ServantTracker.kt | 3 ++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index 372d928d8..adab56c51 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -16,6 +16,7 @@ import io.github.fate_grand_automata.scripts.modules.attack.AttackPriorityHandle import io.github.fate_grand_automata.scripts.prefs.IBattleConfig import io.github.lib_automata.dagger.ScriptScope import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds @ScriptScope class Card @Inject constructor( @@ -119,12 +120,25 @@ class Card @Inject constructor( } fun NPUsage.detected (): NPUsage { - val validNPs = (this.nps + spamNps).detected() + var validNPs: Set = emptySet() + var counter = 0 + do { + if (counter > 0) { + // We want to wait for the cards to settle a bit more before we try again + 100.milliseconds.wait() + } + val newSet = (this.nps + spamNps).detected() + if (newSet.size > validNPs.size) { + validNPs = newSet + } + counter++ + } while (counter < 3 && !validNPs.containsAll(this.nps)) + return NPUsage(validNPs, this.cardsBeforeNP) } fun Set.detected (): Set { - val npCardsDetected = servantTracker.npCardsDetected() + val npCardsDetected = servantTracker.npCardsDetectedUsingServantFaces() return this .filter { it in npCardsDetected } .toSet() diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt index 1b05c7e36..1be9110ef 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt @@ -288,7 +288,8 @@ class ServantTracker @Inject constructor( } val npCardDetectionScoreCutoff = 0.8 - fun npCardsDetected(): Set { + + fun npCardsDetectedUsingServantFaces(): Set { if (prefs.skipServantFaceCardCheck) { return emptySet() } From be28269c779354b0a84494d62cb4314207af1079 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 10 Sep 2025 11:53:54 +0800 Subject: [PATCH 101/124] Only use detected exclusively for spamNPs. --- .../io/github/fate_grand_automata/scripts/modules/Card.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index adab56c51..fb4bf2844 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -63,12 +63,13 @@ class Card @Inject constructor( val useChainPriority = battleConfig.useChainPriority if (useChainPriority) { - val detectedNPUsage = npUsage.detected() - val npTypes = detectedNPUsage.nps.associate { it.toFieldSlot() to it.type() } + val npUsageToUse = NPUsage(npUsage.nps + spamNps.detected(), npUsage.cardsBeforeNP) + + val npTypes = npUsageToUse.nps.associate { it.toFieldSlot() to it.type() } val chainPriority = battleConfig.chainPriority.atWave(state.stage) return AttackPriorityHandler.pick( cards = cardsOrderedByPriority, - npUsage = detectedNPUsage, + npUsage = npUsageToUse, braveChainEnum = braveChainsPerWave.inCurrentWave(BraveChainEnum.None), chainPriority = chainPriority, rearrange = rearrangeCardsPerWave.inCurrentWave(false), From aece22b3e45b4118439d4ea85e039de085232d84 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Wed, 10 Sep 2025 11:56:44 +0800 Subject: [PATCH 102/124] Revert the additional checks --- .../fate_grand_automata/scripts/modules/Card.kt | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt index fb4bf2844..79c17a14e 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Card.kt @@ -121,20 +121,7 @@ class Card @Inject constructor( } fun NPUsage.detected (): NPUsage { - var validNPs: Set = emptySet() - var counter = 0 - do { - if (counter > 0) { - // We want to wait for the cards to settle a bit more before we try again - 100.milliseconds.wait() - } - val newSet = (this.nps + spamNps).detected() - if (newSet.size > validNPs.size) { - validNPs = newSet - } - counter++ - } while (counter < 3 && !validNPs.containsAll(this.nps)) - + val validNPs = (this.nps + spamNps).detected() return NPUsage(validNPs, this.cardsBeforeNP) } From 49795ef7ae4db76877022b95abafb32ad033ed0a Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 13 Sep 2025 00:17:26 +0800 Subject: [PATCH 103/124] Remove forceBraveChain option from subhandlers. Put the forceBraveChain check on the highest handler instead. --- .../scripts/modules/attack/AttackUtils.kt | 3 - .../attack/CardChainPriorityHandler.kt | 5 +- .../modules/attack/ColorChainHandler.kt | 5 +- .../modules/attack/MightyChainHandler.kt | 5 +- .../CardChainPriorityIntegrationTest.kt | 14 +++- .../scripts/attack/ColorChainTest.kt | 74 ------------------- .../scripts/attack/MightyChainTest.kt | 18 ----- 7 files changed, 17 insertions(+), 107 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt index 6be04725e..8bca45087 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackUtils.kt @@ -74,16 +74,13 @@ object AttackUtils { * BraveChainEnum.Always -> Always returns a fieldSlot if there is a valid one. * @param cards The list of cards available to choose from * @param npUsage NPs that have been clicked - * @param forceBraveChain Treats braveChainEnum as BraveChainEnum.Always * @returns A valid FieldSlot for a BraveChain or null if there is none */ fun getBraveChainFieldSlot ( braveChainEnum: BraveChainEnum = BraveChainEnum.None, cards: List, npUsage: NPUsage = NPUsage.none, - forceBraveChain: Boolean = false, ): FieldSlot? { - val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum if ( braveChainEnum == BraveChainEnum.Avoid || braveChainEnum == BraveChainEnum.None diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index a73dee1a4..a1b969d5c 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -33,6 +33,9 @@ object CardChainPriorityHandler { val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) + // If forceBraveChain, set the braveChainEnum directly here + val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum + for (chain in chainPriority) { if (newCardOrder != null) continue newCardOrder = when (chain) { @@ -42,7 +45,6 @@ object CardChainPriorityHandler { npUsage = npUsage, npTypes = npTypes, braveChainEnum = braveChainEnum, - forceBraveChain = forceBraveChain, cardCountPerCardTypeMap = cardCountPerCardTypeMap, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) @@ -59,7 +61,6 @@ object CardChainPriorityHandler { npUsage = npUsage, npTypes = npTypes, braveChainEnum = braveChainEnum, - forceBraveChain = forceBraveChain, cardCountPerCardTypeMap = cardCountPerCardTypeMap, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index d6d1e2117..4ec968a8b 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -16,7 +16,6 @@ object ColorChainHandler { npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), braveChainEnum: BraveChainEnum = BraveChainEnum.None, - forceBraveChain: Boolean = false, cardCountPerFieldSlotMap: Map? = null, cardCountPerCardTypeMap: Map? = null, ): List? { @@ -31,9 +30,7 @@ object ColorChainHandler { braveChainEnum = braveChainEnum, cards = cards, npUsage = npUsage, - forceBraveChain = forceBraveChain, ) - val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum // if it passes the above, the actual implementation is very easy val cardsNeeded = 3 - npUsage.nps.size @@ -68,7 +65,7 @@ object ColorChainHandler { it.fieldSlot == braveChainFieldSlot } // If there is a valid number of cards for the fieldSlot, attempt to Brave Chain - // Even if it is not valid, if forceBraveChain is on, + // Even if it is not valid, if BraveChainEnum.Always, // it only accepts Brave Color Chains and not normal Color Chains if (fieldSlotList.size >= cardsNeeded || braveChainEnum == BraveChainEnum.Always) selectedCards = fieldSlotList diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index ed9274bf1..80441f358 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -20,7 +20,6 @@ object MightyChainHandler { braveChainEnum: BraveChainEnum = BraveChainEnum.None, cardCountPerFieldSlotMap: Map? = null, cardCountPerCardTypeMap: Map? = null, - forceBraveChain: Boolean = false, ): List? { // Try to ensure unknown is handled val nonUnknownCards = AttackUtils.getValidNonUnknownCards(cards) @@ -47,9 +46,7 @@ object MightyChainHandler { braveChainEnum = braveChainEnum, cards = nonUnknownCards, npUsage = npUsage, - forceBraveChain = forceBraveChain, ) - val braveChainEnum = if (forceBraveChain) BraveChainEnum.Always else braveChainEnum val selectedCards = pick( cards = nonUnknownCards, @@ -100,7 +97,7 @@ object MightyChainHandler { val fieldSlotList = filteredCards.filter { it.fieldSlot == braveChainFieldSlot } - // Even if it is empty, if forceBraveChain is on, + // Even if it is empty, if BraveChainEnum.Always, // it only accepts Brave Mighty Chains and not normal Mighty Chains if (fieldSlotList.isNotEmpty() || braveChainEnum == BraveChainEnum.Always) filteredCards = fieldSlotList } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 7ed27048f..a0aa9de3d 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -48,7 +48,6 @@ class CardChainPriorityIntegrationTest { npUsage = npUsage, npTypes = npTypes, braveChainEnum = braveChainEnum, - forceBraveChain = braveChainEnum == BraveChainEnum.Always, ) ?: cards return AttackPriorityHandler.rearrange( cards = results, @@ -71,7 +70,6 @@ class CardChainPriorityIntegrationTest { npUsage = npUsage, npTypes = npTypes, braveChainEnum = braveChainEnum, - forceBraveChain = braveChainEnum == BraveChainEnum.Always, ) ?: cards return AttackPriorityHandler.rearrange( cards = results, @@ -193,6 +191,18 @@ class CardChainPriorityIntegrationTest { assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D) } + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), No Mighty allowed, end before Avoid`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = AttackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + chainPriority = ChainTypeEnum.defaultOrder.filter { it != ChainTypeEnum.Mighty }, + ).map { it.card } + + assertThat(picked).isEqualTo(cards.map{ it.card }) + } + @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ), with rearrange`() { val cards = AttackLineUps.Standard.lineup1 diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 95cc54cfc..09c416060 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -81,38 +81,6 @@ class ColorChainTest { } } - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes & forceBraveChain`() { - val cards = AttackLineUps.Standard.lineup1 - for (braveChainEnum in braveChainEnums) { - for (cardType in cardTypeList) { - val result = ColorChainHandler.pick( - cards = cards, - cardType = cardType, - braveChainEnum = braveChainEnum, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - forceBraveChain = true, - )?.map { it.card } ?: emptyList() - - when (cardType) { - CardTypeEnum.Quick -> - assertThat(result, "$braveChainEnum, $cardType").containsExactly( - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) - - else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() - } - } - } - } - @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 @@ -145,27 +113,6 @@ class ColorChainTest { } } - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes & forceBraveChain`() { - val cards = AttackLineUps.Standard.lineup1 - for (braveChainEnum in braveChainEnums) { - for (cardType in cardTypeList) { - val result = ColorChainHandler.pick( - cards = cards, - cardType = cardType, - braveChainEnum = braveChainEnum, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.B to CardTypeEnum.Quick - ), - forceBraveChain = true, - )?.map { it.card } ?: emptyList() - - assertThat(result, "$braveChainEnum, $cardType").isEmpty() - } - } - } - @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 @@ -199,27 +146,6 @@ class ColorChainTest { } } - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes & forceBraveChain`() { - val cards = AttackLineUps.Standard.lineup1 - for (braveChainEnum in braveChainEnums) { - for (cardType in cardTypeList) { - val result = ColorChainHandler.pick( - cards = cards, - cardType = cardType, - braveChainEnum = braveChainEnum, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ), - forceBraveChain = true, - )?.map { it.card } ?: emptyList() - - assertThat(result, "$braveChainEnum, $cardType").isEmpty() - } - } - } - @Test fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index df5d4bc45..206ab49f8 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -209,24 +209,6 @@ class MightyChainTest { } } - @Test - fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes & forcedBraveChain`() { - val cards = AttackLineUps.Standard.lineup1 - for (braveChainEnum in braveChainEnums) { - val picked = MightyChainHandler.pick( - cards = cards, - braveChainEnum = braveChainEnum, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ), - forceBraveChain = true - )?.map { it.card } ?: emptyList() - - assertThat(picked, braveChainEnum.toString()).isEmpty() - } - } - @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup2 From 47c017e6ffac5d8ad097ecfdb4f6f50e6e2092dd Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 13 Sep 2025 00:53:37 +0800 Subject: [PATCH 104/124] Change AvoidChainHandler's behaviour since it is now included in the chain (and has to handle BraveChainEnum) --- .../modules/attack/AvoidChainHandler.kt | 22 ++++++++++++++----- .../modules/attack/BraveChainHandler.kt | 2 +- .../attack/CardChainPriorityHandler.kt | 2 +- .../scripts/attack/BraveChainTest.kt | 3 +-- .../CardChainPriorityIntegrationTest.kt | 2 +- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index 95dc277a7..439eb6642 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -1,5 +1,6 @@ package io.github.fate_grand_automata.scripts.modules.attack +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage @@ -10,9 +11,9 @@ import kotlin.collections.set object AvoidChainHandler { fun pick( cards: List, + braveChainEnum: BraveChainEnum = BraveChainEnum.Avoid, npUsage: NPUsage = NPUsage.none, npTypes: Map = emptyMap(), - avoidBraveChains: Boolean = true, avoidCardChains: Boolean = true, cardCountPerFieldSlotMap: Map? = null, cardCountPerCardTypeMap: Map? = null, @@ -34,6 +35,14 @@ object AvoidChainHandler { // Otherwise, need to check if they have the same NP type } + // Handle brave chains + val avoidBraveChains = braveChainEnum == BraveChainEnum.Avoid + val braveChainFieldSlot = AttackUtils.getBraveChainFieldSlot( + braveChainEnum = braveChainEnum, + cards = nonUnknownCards, + npUsage = npUsage, + ) + // For tracking val selectedFieldSlots = mutableMapOf() val selectedCardTypes = mutableMapOf() @@ -52,10 +61,13 @@ object AvoidChainHandler { } } - val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(nonUnknownCards, npUsage) - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(nonUnknownCards, npTypes) + // Filter BraveChain-able only cards or skip filter (if there is no BraveChain required) + val cardsToUse = nonUnknownCards.filter { braveChainFieldSlot == null || it.fieldSlot == braveChainFieldSlot } + + val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(cardsToUse, npUsage) + val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(cardsToUse, npTypes) - var cachedFilteredCards: List = nonUnknownCards + var cachedFilteredCards: List = cardsToUse var previousFieldSlot: FieldSlot? = null // Otherwise, calculate @@ -117,7 +129,7 @@ object AvoidChainHandler { // Return empty if cannot fulfill if (selectedCards.size < cardsNeeded) return null - val remainder = (nonUnknownCards - selectedCards) + (cards - nonUnknownCards) + val remainder = (cardsToUse - selectedCards) + (cards - cardsToUse) val combinedCards = selectedCards + remainder return combinedCards diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt index 2bd694378..d7e707341 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/BraveChainHandler.kt @@ -17,7 +17,7 @@ object BraveChainHandler { return AvoidChainHandler.pick( cards = cards, npUsage = npUsage, - avoidBraveChains = true, + braveChainEnum = braveChainEnum, avoidCardChains = false, ) } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index a1b969d5c..4242540b1 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -68,7 +68,7 @@ object CardChainPriorityHandler { cards = nonUnknownCards, npUsage = npUsage, npTypes = npTypes, - avoidBraveChains = braveChainEnum == BraveChainEnum.Avoid, + braveChainEnum = braveChainEnum, avoidCardChains = true, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, cardCountPerCardTypeMap = cardCountPerCardTypeMap, diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt index 5778a3616..413c14da4 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/BraveChainTest.kt @@ -21,11 +21,10 @@ class BraveChainTest { braveChainEnum: BraveChainEnum, npUsage: NPUsage = NPUsage.none, ) { - if (braveChainEnum != BraveChainEnum.Avoid) return val defaultAvoid = AvoidChainHandler.pick( cards = cards, + braveChainEnum = braveChainEnum, npUsage = npUsage, - avoidBraveChains = true, avoidCardChains = false, )?.map { it.card } ?: emptyList() assertThat(picked, BraveChainEnum.Avoid.toString()).isEqualTo(defaultAvoid) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index a0aa9de3d..599384294 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -138,9 +138,9 @@ class CardChainPriorityIntegrationTest { ): List { val results = AvoidChainHandler.pick( cards = cards, + braveChainEnum = braveChainEnum, npUsage = npUsage, npTypes = npTypes, - avoidBraveChains = braveChainEnum == BraveChainEnum.Avoid, avoidCardChains = true, ) ?: cards return AttackPriorityHandler.rearrange( From 51971a6a98b781f0a6ff95363b404082bce52e84 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 13 Sep 2025 01:14:39 +0800 Subject: [PATCH 105/124] Add tests for all BraveChainEnums for AvoidChainHandler. --- .../scripts/attack/AvoidChainTest.kt | 689 ++++++++++-------- 1 file changed, 396 insertions(+), 293 deletions(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index 182be28f0..9f077dfe4 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -3,6 +3,7 @@ package io.github.fate_grand_automata.scripts.attack import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty +import io.github.fate_grand_automata.scripts.enums.BraveChainEnum import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot @@ -11,195 +12,255 @@ import kotlin.test.Test import io.github.fate_grand_automata.scripts.modules.attack.AvoidChainHandler class AvoidChainTest { + val braveChainEnums = BraveChainEnum.entries + @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ)`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } } @Test fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA)`() { val cards = AttackLineUps.Standard.lineup2 - val picked = AvoidChainHandler.pick( - cards = cards - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.E, - CommandCard.Face.C, - CommandCard.Face.D, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1Kama-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, ) - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.B to CardTypeEnum.Quick - ) - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Always, + BraveChainEnum.WithNP -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.C to CardTypeEnum.Arts - ) - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.B, - CommandCard.Face.D, - CommandCard.Face.E, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + + when (braveChainEnum) { + BraveChainEnum.Always, + BraveChainEnum.WithNP -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + ) + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + } } @Test fun `SingleServantOnly - lineup1 (1B,2Q,3A,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.D, - CommandCard.Face.C, - CommandCard.Face.E, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.C, + CommandCard.Face.E, + ) + } } @Test fun `SingleServantOnly - lineup2 (1B,4B,5Q,2Q,3A)`() { val cards = AttackLineUps.SingleServantOnly.lineup2 - val picked = AvoidChainHandler.pick( - cards = cards - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.D, - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } } @Test fun `SingleServantOnly - lineup3 (5Q,2Q,3A,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup3 - val picked = AvoidChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.A, - CommandCard.Face.D, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.D, + ) + } } // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup4 (1B,2Q,3Q,4B,5Q)`() { val cards = AttackLineUps.SingleServantOnly.lineup4 - val picked = AvoidChainHandler.pick( - cards = cards - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } } // Scenario for when 2 card types are found but not the 3rd @Test fun `SingleServantOnly - lineup5 (5Q,2Q,3Q,1B,4B)`() { val cards = AttackLineUps.SingleServantOnly.lineup5 - val picked = AvoidChainHandler.pick( - cards = cards - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } } /** @@ -208,233 +269,275 @@ class AvoidChainTest { @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 3Nero), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.C to CardTypeEnum.Arts + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, ) - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.B, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1Kama, 2Scathach), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, ) - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2 NP (1B, 2B), with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Buster, - FieldSlot.B to CardTypeEnum.Buster + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Buster, + FieldSlot.B to CardTypeEnum.Buster + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, ) - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.B, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3 NP, with npTypes`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick, - FieldSlot.B to CardTypeEnum.Quick, - FieldSlot.C to CardTypeEnum.Arts, - ) - )?.map { it.card } ?: emptyList() - - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B, CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick, + FieldSlot.C to CardTypeEnum.Arts, + ) + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 1 Unknown NP`() { val cards = AttackLineUps.Standard.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Unknown - ), - )?.map { it.card } ?: emptyList() - - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Unknown + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test fun `Unknown - lineup1 (1 Unknown)`() { val cards = AttackLineUps.Unknown.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.E, - CommandCard.Face.D, - CommandCard.Face.A, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.D, + CommandCard.Face.A, + ) + } } @Test fun `Unknown - lineup1 (1 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - CommandCard.Face.A, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + ) + } } @Test fun `Unknown - lineup1 (1 Unknown) + 1 Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - CommandCard.Face.A, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + ) + } } @Test fun `Unknown - lineup1 (1 Unknown) + 1 Unknown NP`() { val cards = AttackLineUps.Unknown.lineup1 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Unknown - ), - )?.map { it.card } ?: emptyList() - - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Unknown + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test fun `Unknown - lineup2 (2 Unknown)`() { val cards = AttackLineUps.Unknown.lineup2 - val picked = AvoidChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.C, - CommandCard.Face.E, - CommandCard.Face.D, - CommandCard.Face.A, - CommandCard.Face.B, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.D, + CommandCard.Face.A, + CommandCard.Face.B, + ) + } } @Test fun `Unknown - lineup3 (3 Unknown)`() { val cards = AttackLineUps.Unknown.lineup3 - val picked = AvoidChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } @Test fun `Unknown - lineup3 (3 Unknown) + 1 Valid NP`() { val cards = AttackLineUps.Unknown.lineup3 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.D, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } } @Test fun `Unknown - lineup3 (3 Unknown) + 1 Valid NP, with npTypes`() { val cards = AttackLineUps.Unknown.lineup3 - val picked = AvoidChainHandler.pick( - cards = cards, - npUsage = NPUsage(setOf(CommandCard.NP.A), 0), - npTypes = mapOf( - FieldSlot.A to CardTypeEnum.Quick - ), - )?.map { it.card } ?: emptyList() - - assertThat(picked).containsExactly( - CommandCard.Face.D, - CommandCard.Face.E, - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - ) + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.A), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } } @Test fun `Unknown - lineup4 (5 Unknown)`() { val cards = AttackLineUps.Unknown.lineup4 - val picked = AvoidChainHandler.pick( - cards = cards, - )?.map { it.card } ?: emptyList() - - assertThat(picked).isEmpty() + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() + } } } \ No newline at end of file From 3779ec0c57ca8410cf9694363dabf3caf8fad473 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 13 Sep 2025 02:01:17 +0800 Subject: [PATCH 106/124] Add new checks and new tests for AvoidChainHandler. --- .../modules/attack/AvoidChainHandler.kt | 28 ++- .../attack/CardChainPriorityHandler.kt | 2 - .../scripts/attack/AttackLineUps.kt | 11 ++ .../scripts/attack/AvoidChainTest.kt | 161 ++++++++++++++---- 4 files changed, 156 insertions(+), 46 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt index 439eb6642..30645d845 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AvoidChainHandler.kt @@ -36,6 +36,8 @@ object AvoidChainHandler { } // Handle brave chains + // If there is a valid braveChainFieldSlot, it means that a Brave Chain must be made while avoiding + // Color + Mighty Chains while avoidCardChains = true val avoidBraveChains = braveChainEnum == BraveChainEnum.Avoid val braveChainFieldSlot = AttackUtils.getBraveChainFieldSlot( braveChainEnum = braveChainEnum, @@ -43,6 +45,21 @@ object AvoidChainHandler { npUsage = npUsage, ) + // This is just a normal BraveChain requirement. Go to BraveChainHandler for this + if (braveChainFieldSlot != null && !avoidCardChains) return null + + // Filter BraveChain-able only cards or skip filter (if there is no BraveChain required) + val cardsToUse = nonUnknownCards.filter { braveChainFieldSlot == null || it.fieldSlot == braveChainFieldSlot } + + val cardCountPerFieldSlotMap = AttackUtils.getCardsPerFieldSlotMap(cardsToUse, npUsage) + val cardCountPerCardTypeMap = AttackUtils.getCardsPerCardTypeMap(cardsToUse, npTypes) + + // If only a BraveChain is possible and it is avoidBraveChains + if (avoidBraveChains && cardCountPerFieldSlotMap.size == 1) return null + + // If only a ColorChain is possible + if (avoidCardChains && cardCountPerCardTypeMap.size == 1) return null + // For tracking val selectedFieldSlots = mutableMapOf() val selectedCardTypes = mutableMapOf() @@ -61,12 +78,6 @@ object AvoidChainHandler { } } - // Filter BraveChain-able only cards or skip filter (if there is no BraveChain required) - val cardsToUse = nonUnknownCards.filter { braveChainFieldSlot == null || it.fieldSlot == braveChainFieldSlot } - - val cardCountPerFieldSlotMap = cardCountPerFieldSlotMap ?: AttackUtils.getCardsPerFieldSlotMap(cardsToUse, npUsage) - val cardCountPerCardTypeMap = cardCountPerCardTypeMap ?: AttackUtils.getCardsPerCardTypeMap(cardsToUse, npTypes) - var cachedFilteredCards: List = cardsToUse var previousFieldSlot: FieldSlot? = null @@ -93,8 +104,6 @@ object AvoidChainHandler { val cardType = card.type val cardTypeCount = selectedCardTypes.getOrElse(cardType) { 0 } if (avoidCardChains - // There are at least 2 cards to choose from - && cardCountPerCardTypeMap.size > 1 && ( cardTypeCount >= 2 // Or if it would make a Mighty Chain @@ -109,7 +118,8 @@ object AvoidChainHandler { cachedFilteredCards = filteredCards // Try to make the next card different from the previous card's fieldSlot, if possible - if (previousFieldSlot != null) { + // and there must not be a valid BraveChain for WithNP and Always + if (previousFieldSlot != null && braveChainFieldSlot == null) { val fieldSlotFilter = filteredCards.filter { it.fieldSlot != previousFieldSlot } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 4242540b1..74f2ffcb9 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -70,8 +70,6 @@ object CardChainPriorityHandler { npTypes = npTypes, braveChainEnum = braveChainEnum, avoidCardChains = true, - cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, - cardCountPerCardTypeMap = cardCountPerCardTypeMap, ) } } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index c4486ba0c..d62eb795f 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -147,6 +147,13 @@ class AttackLineUps { type = CardTypeEnum.Buster, affinity = CardAffinityEnum.Weak ) + val nero3BAlt = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.C, + fieldSlot = FieldSlot.C, + type = CardTypeEnum.Buster, + affinity = CardAffinityEnum.Resist + ) val nero3QAlt = ParsedCard( card = CommandCard.Face.E, servant = TeamSlot.C, @@ -198,6 +205,10 @@ class AttackLineUps { val lineup09 = listOf(kiyohime1B, kiyohime2B, nero3QAlt, nero4RA, nero5BAlt) /** kiyohime1B, nero5BAlt, kiyohime2B, nero3QAlt, nero4RA */ val lineup10 = listOf(kiyohime1B, nero5BAlt, kiyohime2B, nero3QAlt, nero4RA) + + // Avoid chain + /** kiyohime1B, kiyohime2B, nero3BAlt, nero4BAlt, kiyohime5B */ + val lineup11 = listOf(kiyohime1B, kiyohime2B, nero3BAlt, nero4BAlt, kiyohime5B) } data object SingleServantOnly { diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt index 9f077dfe4..d871ba7ce 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AvoidChainTest.kt @@ -146,13 +146,63 @@ class AvoidChainTest { when (braveChainEnum) { BraveChainEnum.Always, BraveChainEnum.WithNP -> + assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, CommandCard.Face.C, + CommandCard.Face.B, CommandCard.Face.D, + CommandCard.Face.E, + ) + } + + } + } + + @Test + fun `BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + + when (braveChainEnum) { + BraveChainEnum.Always -> + assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( CommandCard.Face.A, + CommandCard.Face.C, CommandCard.Face.B, + CommandCard.Face.D, CommandCard.Face.E, ) + } + } + } + + @Test + fun `BusterFocus - lineup01 (1KB,2KB,3NA,4NA,5KB) + 1 NP (2Kiyohime)`() { + val cards = AttackLineUps.BusterFocus.lineup01 + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Buster + ), + )?.map { it.card } ?: emptyList() + + + when (braveChainEnum) { + BraveChainEnum.Always, + BraveChainEnum.WithNP -> + assertThat(picked, braveChainEnum.toString()).isEmpty() else -> assertThat(picked, braveChainEnum.toString()).containsExactly( CommandCard.Face.A, @@ -162,7 +212,23 @@ class AvoidChainTest { CommandCard.Face.E, ) } + } + } + @Test + fun `BusterFocus - lineup11 (1KB,2KB,3NB,4NB,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup11 + for (braveChainEnum in braveChainEnums) { + val picked = AvoidChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Buster + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).isEmpty() } } @@ -175,13 +241,18 @@ class AvoidChainTest { braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.D, - CommandCard.Face.C, - CommandCard.Face.E, - ) + when (braveChainEnum) { + BraveChainEnum.Avoid -> + assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.C, + CommandCard.Face.E, + ) + } } } @@ -194,13 +265,18 @@ class AvoidChainTest { braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).containsExactly( - CommandCard.Face.A, - CommandCard.Face.D, - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - ) + when (braveChainEnum) { + BraveChainEnum.Avoid -> + assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + ) + } } } @@ -213,13 +289,18 @@ class AvoidChainTest { braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).containsExactly( - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.A, - CommandCard.Face.D, - ) + when (braveChainEnum) { + BraveChainEnum.Avoid -> + assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.D, + ) + } } } @@ -233,13 +314,18 @@ class AvoidChainTest { braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).containsExactly( - CommandCard.Face.A, - CommandCard.Face.B, - CommandCard.Face.C, - CommandCard.Face.D, - CommandCard.Face.E, - ) + when (braveChainEnum) { + BraveChainEnum.Avoid -> + assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } } } @@ -253,13 +339,18 @@ class AvoidChainTest { braveChainEnum = braveChainEnum, )?.map { it.card } ?: emptyList() - assertThat(picked, braveChainEnum.toString()).containsExactly( - CommandCard.Face.E, - CommandCard.Face.B, - CommandCard.Face.A, - CommandCard.Face.C, - CommandCard.Face.D, - ) + when (braveChainEnum) { + BraveChainEnum.Avoid -> + assertThat(picked, braveChainEnum.toString()).isEmpty() + else -> + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } } } From 8b8ea2f2ef5d868b8928ea648eb34b7b22a835f0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 18 Sep 2025 01:52:42 +0800 Subject: [PATCH 107/124] Update documentation (part 1) --- wiki/img/attack/attack-priority-flowchart.png | Bin 0 -> 252169 bytes wiki/img/attack/brave-chain-options.png | Bin 0 -> 13580 bytes .../attack/chain-priority-options-arts.png | Bin 0 -> 6976 bytes .../attack/chain-priority-options-avoid.png | Bin 0 -> 6784 bytes .../attack/chain-priority-options-buster.png | Bin 0 -> 7684 bytes .../attack/chain-priority-options-default.png | Bin 0 -> 8410 bytes .../attack/chain-priority-options-none.png | Bin 0 -> 6471 bytes wiki/img/attack/chain-priority-toggle.png | Bin 0 -> 5035 bytes wiki/scripts/Battle.md | 89 +++++++++++++++--- 9 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 wiki/img/attack/attack-priority-flowchart.png create mode 100644 wiki/img/attack/brave-chain-options.png create mode 100644 wiki/img/attack/chain-priority-options-arts.png create mode 100644 wiki/img/attack/chain-priority-options-avoid.png create mode 100644 wiki/img/attack/chain-priority-options-buster.png create mode 100644 wiki/img/attack/chain-priority-options-default.png create mode 100644 wiki/img/attack/chain-priority-options-none.png create mode 100644 wiki/img/attack/chain-priority-toggle.png diff --git a/wiki/img/attack/attack-priority-flowchart.png b/wiki/img/attack/attack-priority-flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..17bc4b3ac11409fca3e49e2dc3a4e5b387e7561c GIT binary patch literal 252169 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%0DcAsrS&r8yevRA3R($3kB}slwjWs&l<3o03 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

hZ8gn(!Ze&JusD4V-%97W>_53-ywe4H1M@tEz` zeDoJ!l-oxiFQ|WQ)wAXmKIns+Y^OnBS6^@gJ3rh}xOq-B?0FeCO8*sh~_rzS+E4dVX?)q9&5aP#9Cep!>|1yNIkW&s6W(sq|GQ@-USHK1EUa9N6~4= z{Ys8Qs4JO5Os_Q{%Uxw)9Pbd7r=Tq=R_<}op*_+VK%q}_xVUM*qkrkRwF-!CT7p`L zV{z|-X_bcw;9cEjv~k{OSlgYfVR-ryP(f~*YFthElEY_heA+E4P7q^mB&K)wRLX~= zs+RY@7)GSlu)IJNNA~W%+#YA5RbE=CsJ?*Q7YmB-TKc48r?M6LhYG=@ls5y5qT53J zb7a$zv{~tLQ>U}&YU=`)=jOSOE*+u>9rY0&Qwr{$oNIgH+fRKE17$Ga->cMdoMTJ z&;Y6Zl#q;s)4+BM0eUF;DWs{(%gAHgeZzdT_`mL=(+P#5f^j>f`?pzN0IaPAv}#+1 zw)Rn>A})~RzT0|P3yJU|<0#NqhwOCZSHC?DTn&>Vv8_9Jv;&pcftc|TzvHS(ihOPS zmg(9^(fidHK4W2|yvleYm6mgzmdAl1AlfGoE_#Au7N5yziS@?LVEAGUa2zQAyj!ufSX=miqxvw>Tl#cP{tmDXog4`HR7rC4QTWsDS8=Y~H3CN&wB2=mFB9j7Gs}#GF-c`vPgXlNT~k&8s!cbL zKM}1yRsb*TxR~rOtg68Z>%sp{ET80y0X38%A6!BUfJ@;riGJpP`12C>S<-ud(Fx`e z=iCON>Cr+?I1bRTdv4qWiulHO49-cCvSg>p{tP1_hQQ&0tCZyV;4jU!s=W!m z{BeTsdrSSS2@`!JnJ5A$iJnVR&1s;pBox1>Rc2=x)VWs#WQ#}yr?5moKaW_ALAde5 zD{`D`UJ@W>T~(Ly=vWs*s9`^W=%<-j)Fpi3E(8~x?-J&rfm&VyhjLuwHO$h6;6kb9Dly977j!72;wu=WQ)9N9LLZ)4qnht#;= zD0t0Ky~^?;!K~ICT_eXj=Vy#2+;ru<__VJeSM)j{Dl?3ktFiw}cX#FCO_yK*l=Xy8 zAz1~Q#4fqszz8<;nAIg9xEaOgkab%}+US8D{wH!C-$e0tAmM(l!^IA>HhK9&6jlyu z`9U6lR}oa}3~WBfTzJg>u&Gx%=Mg3d`4F8tt0ve_BKI6#PbSE<0Ho#eqAOQZ^fbf4&d;XNH>)4MV)GlX;UwZ-B|&7sut9oDKl!^vpvw{uVE9%6G*NL(r6l74 ztO+KtO(f=6{Ad-0bJVstO%NSG!73ax)D|HC0i^ZEft-K9-`C_gpF?Wn61(%(yDCNt z8`6j29;bLE18zd@df7(*L@ldqpXBGaIu~LF`D#L=J6_!P7C2G3YGijp&wS&T1VXoWSkavNyPWx~gtn^nGn~ z)ZWCh^_l&5kmO?OAzfR8FW@_Mb)0a!zSm~Plo-X&Xb=ZO_PJa zY@`?Mt9Eg{^GC@H54;%tKIQ&{JX+iz>s+ybg(}0m6HRIdsTwTY`IWkAz{lDV#4-iB zr_gx61R$p0H29YdWM1Hxrlu%+?vO7I<)w&_w#KDr0hCv(C}-+ifIXQ8dn!rW&l@lR z12dXDn%D~}xqnr)%eRKnWPgf>JbvU#U@d3K*i2ON^cq`KjPCP&XX0rcLFDv_O}xb+ zFuNCVF(^b|C)we!Y>wrsUX7o!nFPS-~~;HW|o^7|h#zl>&jn1qE-rdU45 zS~4`xDlkecGojOfp?Tq>bCLD;Nk8>qqf2a^UuPl%*yyJ9h)fMAz zqdjzm;hH@%_`~%I^lHln4cdHWIImMqK)!GdwY$?Q_gN@Z6Kcw>yjxPZ1jFCgLLke< zw%aouYh#R6ZJzW*P+hR*y}fDHLAAgxOqm36C7a@$TE#8J_VHQsev@Fcb93qUtwJ6f zKEO%62grZdq|J2Gz>Wm01$ zUW5^Zk%XTJAHZWE^^evrnKyaPk!{bTi^c$MHHEB_RifDaz>e8hC`PnMnHA@TXVkYC z5vN~oduxQI3p7k`WmW-kI1C`?R9KUU0^PLx#u^bzn=}|v%9-cE7IQ`xUii!bwIEno ziQ=8v_a`>+gsOOlb6E=20BwV+Bqhg|;iWBoAT=bF3d=Lh3eNFbpdAeMU6pLjYu z5Ma#d5)zmW3E@&M8#Gh=VP#aHyHA;@3z#Gwow;wSw12Z)6RV{3*>62wQt(`kYLw)+ zcJ#A+FaH?-6&((T%VzHh>2M3&!$MSo+X3Q?r+m#d1fm=XF5>7DwuAT30X0N(d-R(T z;ldn}R8$9~!%9jyr1Q*`hMoNG#E_yvI4M6gJGuvczw|wCf){}Yy@>6)wBE|02JjNz zm?ZLr1I-708S(1J!O^xKw?5hiZm~bZ=>D7KKFh-$t0lcr$M?+fBJ+(JDc(Z0M5`GN z96Gjsl_GkWQ@Gy|OuER92LcbyT8~m8@(sA{5EqGwaCSzxSowpU?gI{{AcXopY}1 zdSCDNYdl|%r%Hz~%UsOt2rG|SZM~U}nFk{)|?Gl`2^Bapu>4r@q zOO}u{-@eh&h|@F8fzw}4_eD3}SoUeJU7@KS27RG$UOO9&3p%=K8Meq;Y8HuB-@>@i z;HKNs!hO-{m+28aSCLoG!^3a3t|TsrFGU4@_zzDZA%C^ccVlq6=vlbh$4$NFM*Iys z>+)Lzw{~x_J`PMf`XAq1wj)x601pv?97jJp^+*E9@ISx2Y$4%vcrTqtZ;vbdMew{- zO%lB43DgFvx=)D`+i%pn7bJp%cP>!PG7tFagiLWUJ``bqvpTSU31^iSdmSUmX8?Z~ zP90asnV`+z3!r1v@_X|l26@#pkK;)=b`g^!p=2_@ngSS^Ixrftg+O^T`90?{3)0af z`tw>fG4<7ox>$alA`#zN4KVn4I7-1#iuRrBejg#3wd*X3=-tDb&z|g?t=iw7i}2m< zDFDpOQA~cLtp(BF83AdxJ^{fiDpRs@^xet)6bb`z;X}nAf*DhqUg>61NzV`@=;o(! zL<--iQ>Lv=L1YPo#_>|yebPJ?SNw-fB84DILf$)8iHpUi{rRGK2OWmJ z6Y~xGmo~V!FaFF4KEeRJc_LsB-V*5Fl=R(ImVw5g0v)6)RR55YT23@*RW_bHI3c;g zmLhv!BIe7Zj8D4Gpuj^P6BEk_H_kH7GcGZ%PDBPB_u3q3f589c90E&crTQVc8bmyI zEwNXcV)%3~)nVz#(iY7-Qayr?=NX6#GUC!8P5XCL>eJnVOb_HNXgD zaj-qhd8?GWfpzX^~T7`hjxX>w$YUdCqEdUs1nz-tV>+|+;yr~sTzSp zT?`^x@txXhJt(EVsBH4o{?CpoaEa#}KcY!u7{p?1B1Y?j+Q7jE%&XfL&0hia<6xD4 zXeQAG;?IH)_j}7P{AH`ifJ2~3=CptuiPtw^Lwv=9>LBi-7}vXF13*2cI(AxqppkH0 zL{wtY=+0wjfW!Ermf2uC9ynP$37=BZo>VwwWS#EIS{Dtle9okGTimdPK5ev>Ncpz7 zT@xpFOn^L$Vg1R{md)eS$0zeNWI)lYi5=FE?1+zB4q;J^6I`Y~&g2rKe*1kD8Yzf; zVY*9<^ck%o#%?Fsmo*EQPS8GoTe(SjpMCyvu2<+dD`ntJ+DmNorwHT6PQv%}kyk0< ze?VH*dze#HYJl7u#g@?}bn^|(Nbz%9mm=K)s-@2B?jH`{Cj5{6*+{xsSnW6iZr3I~D&>K(= z7}@#tg6xjoMK1?C+83ZEz-Gf@Sk~+Ub)!B_zpgKDy`OYK* zd9_yhd2WQyC;TO9)6&71eOh$&ZrTpfB3X%u0Pd3VA(!p)%+ttL8nj`6;L2Gt0Hf}I zVbW+af4sk0VPRC_M=&NGWhQ4~Y~f>(eCFxERp*W%K-E!Xc)$HynFvDa)W|E|Npynf za~J$?8waOzRkvNvmK^r^AY@ojA417A7FP&q?1YRYq zh9LmtWvS5|y6DWdeaJsU!L2R?{JFZ3eSAU#8nBs^Awrh>Ix>1z3Q`IHV(wQ(V}9kB zy?eP8-9lI5E4seBTz$-up{C8ea^Z-oBy8&WX7iA%Bnd8bjHbzs140?qC`z76bmBbx zt>98$j+sO}c>Cr`W?B8|e@&2MB=J3@R;PJKZGx9>@C65 zaBkJ`yedF>lW?8sAoaRv+Xpcnhwo5}(1~;V$9#!@a}(ner?(1LdGA zVG@6oXNCkQ1s7H+b`m;cek1^#DR}*Q9YqoQLOHkQ9&I= z#FdPe@u>uT^qE2G-b3T(EQUkgC9awX7HH=;{Sgb8Z;yA&A3QWXoY~++o;_roi5@>u zYkP=T6M3N7+(ZQ&)E){g?E~M{6X5? zMVusK0yIS?jt@>Jz{#zqEm8hLhzfrASr5mbMO^>OIE)J_q8&ORqUKo1+NcDbQGZdE zQix5W<4lW|MUoCf1At61b!_dA`feRa>|}&(0`Yz+zQ4}JFiG*B!45=7J(I$jURm{zw){=y(9Unib5S1Q%I_|CLus}S|@kVyxs$Cx$l zY8f9Nlr>?brU;k;74r?_u|c*IO;3amGtyo#PceSX4Zbz2YhK$BzjP{lHa=3}z)$_p zwZb^f7LBeUjw4IxE?x{K2j_bG()H6f)()X;cfoS{9Lnz1glrNsdHp*LGzvmRz_$7V zN7VRm{4=14W)wU+`eA`GcyRNCZOM;!kCw|@1kQf2Oj-w7nQQo=0ic_$z~TpfdbYq7 z4uXyABZug+qYj!PExHfMd54v7CFo||hc6-MX%m4m1weMfRPB_iIq`~0_z-|+;6ZDc z#W{)7(1REj6tQi(PG}*dOjspBX;bvcv!frz$pT8|bYzOu(Nn$31_vj*YwxnD&vI}} zswrev3MkmA9%JM`weu8^@J_MwpJi8L4jtQ3@L+5J5gd0PPC%Tfy$o6#hm_Pmjb)57 z;v_$LD=pq?mvRFNb31}}uI5rAAbw~hccZxNc>u4bzFDHPR*S}u_?SCpUp-ZDIM)X~ zE=ri#xTO7n4bXDB+4*U00cTFbgZ;htz~OEk=-v$h9Qfuz<<8xdKaz{I+A!pR zm0fFoN_|oZn)>$654>3g*3yU)C7hZ1vhUSPyZZ*!(y{ zKJX#Rr(KQ3`EB|hZ#p?>M33>?PXM`WVK#*iRH;T z;%Xk!Q4U|CE;+bdnC_bccI?M7ltS}0fDFc$q0rTY!#(}iZ_Ob=c=+M{4cPfrQYM}tOJI1S3|bncq=cn= zS3-Z%Tqx&?&*l=;7f_%-**3riRG-8Hvs_(B; zN&ai~HY_)|=BmN4e1^*D3{|KFzy;P6Q%&yGAK9F(WN^@ZF)=E#O>_wg*agj*!B7NP zU~_$|nWQw)Ek~Qfzb>T!GqT=q|6!C^yFKvbwt5Y*G1tPIa7Z(TMuO8Fb@I%WCJ<*i z@8^AaUp#adh}J$r{ebB$sS&_dyzO%_I=~lbcT(mZ|IV34=G=v9baIZdQw+K`a zLh;mPHEIFAov*bHO?P8>HEf%rIqlVx{*F+m4E*o>G+~@&cr)Z>v&sRO0U&?;2qi!Q z|NcM58HZnsS81eNR8&_UNhp`B4W7 zz@J&h(kldMY=9ON{K{;YK4cGpu4oT~Hp=|t@*2aN*;{;C&rh}toHOf+1UDvF858rb z+xgF5aKF?r?AOmht8I1kaq2Q+4%+Y#&c*CWM1N}mWBHLpaxKHz#--nT?1##Gp>3E& z$`L&X$=#0jy2F?2Dhe)k?Jx$LMG6|_*FplooA;+vsEnn(=Svp)m{B%|5veW$L7yxF z&-y4BL%9X&q5Ytf1qPA-EBdmX=}1>4fSn#x4N{C=R>tMWEP+pZbpeC;-w#FNUVx@n zP0;#cD94ElMGru56@4Clz6GrE2sW>{B=srhx0RN1i1Bz4Q+s*u_3$UU!+GXG`PpE& zC`7wEAu~d0UG832CnTEvpEnjDSHJUsbKpI9N4}lWTu0f*h-B$Ar|2!elZCFHyW|t+ zdzsJe@DBWa_upAM$32yvO0a+$IYq9s-xJP2u0_ucLjT^YXhnF;->v+g>%dH)jxa4^ zaitF>Bh7Zl^iA^b`|X4YrFh3{C3+Xjm)PjZC9Z>wHtZxExUGH^@!x6zCadA3arsC5 z-(wZAL2{tLO7#A+Vbj%vw)>S9B9)sjC_0Ayb{15(x{E8VAX{FllV_{(Z>}C#7XSDD zad_eQ>!|27@URFfEef)gXf^3$DkEpeqn{=l_hXN34|{`dEt`W9}G z^fgQv&ckNv`t3pyfUz?>=?Gfw087H&nQt$-6Q8oyjL7A^p{Iape+NF=9Vy|-$tLaB z^lwh~?N%23^AwIAJ?^fu87No(n7SOi!@UxW@uaaS@3)t5t)7(_3z?lF{$2QS*rlBm zz$-xPCi{B%DnIrr4O(;ym@hh*nLLe+VMumx%(`pAzt0o#oBRfgnu^I`xc>m7j*6!J z^D<|7A{wGNOjk}KiNFt-xg0O2DY*CS@ASutl%wX*R{*n}48mNw-ute&JO9D`i>O-T zcFWCCZ=9a7?5BZ}i&ZEhoZ~IAou>l)yl14^X-+FWENI${J@`hf6jA2&=cKr)g)+13%jKeg_uQSNP5V z!*~@A@L|qf(4|G~Z(XIQkXASJ_KUyi{_V!f1+0P6QP@UL0we#!ImXc=Q6)0KM|lCv z=iS4s{TF@5t|NyHTr}9v6$oWMLxT;CCKVf9`a&Mn$S*rZrF8r~j3R)TCd~S zKl}N{N^qBe;s3c6xG}D3-!?FTJ3r*T^wFwcfp8I%I`InZLPp@=dweh86$S3m1x$z< z4Gcazir=+P^SxaNp=qELIa#w?Dv{3i)>{l>a{*9#F%z*d6zs`M01HNf#b1V<8~_Dm z=&+eHxK^EiEU=m?2#cEWxJE>pZFPwp&X$V~Qy|^7FQw3gPPf0?3-|*U95Dhm@q57T z-Sq~9+~wf|lbM|)^AMnZs%uo!OKc$s?>D5Rda6dGFMe14$RCpJ1I zybup=vWMe7DOtyXiUktl@dD zWx4l(M^nNtcK(~g(0M)VQ~+=%L#%ee7*$}RE%NS-A~&XphCxoMN-Ia=-%k?%oF>`< z{7B^_EYQG5@X5!--hzWR*`*%Pqs!LMH4z?n1zS;($MUV|3&8jI=8iglie2-vbO1q59E z*I5Vt5={jMY6nmSe16_+7W55iUTySx0?eTET*pe^r}pR5Yxm-_W?qUOf9K}Z&*S|y`Xkxn$#jurk8QY0yoDpaVG9`@uRpZ5w!Z zhFjU~#z?ppG{^zm)ik-!x5FV9D=y6Sogx0UV!#d@FBoWc&lHk+m8sX{lS#GI9d18NC`E-1a;$WIQaPHlT&B+fcbN`s>@aab% z2*x8X>@kD}Pzs-ip8*dM&?8%YsYwUrFwEy{q9L0o_jdUWC^?^p#t75obvi(#6mK$F>w%S$3P54WAAKwnA<@nwV}IQuZ_k zY4;tw1_Cnrmb2^iY|eiX)#V_4=TUYUSG|ilG$Pe!2B7vggiS1iq_=(Kk?@xh zd#;lJ5AcwRe%ZNmIj%s_*7~)V%NoCarRKt;%rhSq+Nk_1>-%5$LaYDpVy;K&X7C`x zITR~-yk4n*J?u9(;aS;;=O;VZN@Kk6$BMqf{2Vsf8{>oSAJU1ni`_VU%#4cc?aXG> zL7QseAsut!9S}j{zH>T$Ih9#5;w^Pn0nF2lzc1?4Q5?(xMjqq+XMGeyJSYGP@aM`@ zKRJK08MLW33<1%U{U>0+#R6Ie6@Y(epQhL}4kMClWV@gI{*AYXnyl#O(Ynz7?`6wy zg+5SGuWfh+-fX4?!lw}UUT-?andh|%(vjBrx1-qVLjO+#i&nA-4NIl3Z$<+*a?735 zQ|}LXN}FQ>;?4`Z+!E%z@@9c@+(=1MZ{4NAk<-z;zWY2boOX{6 zcl^DP5u|OGKA4~oF^C=V1;Oy1+HUcC-vi&``XA-*`1`_GUVmC|+tbNun!IyM`z;rO z7OktUB`B`eN|%(>C2vL%SY3hr~QH_q9BP9FW&`h_`U+utU~K38>+xO#gR|9o!S2uV` zO)+<MbpGI{EC5^M$Veu=1TcX+~rz(ugj_p#NXZL$>Eugr|D74&YL zn<3;Pc?;+ij>%zl9rVk;uJW&tm4ojYH6CsODwtR5XoXzm*(BVkF&{~gsE$v4t!`C z0Ck!xVw|dBWIa%noGR>8>+;*F_IrD?RvwLl4Sw>}iQFfft-jm9CBe(&z+-DN{%e&I zZ3yo(6B7z^wo~)dRImxopDR!u5qOK)`BXow=HKi17Ac=9{46CkWI?o%wXVTKjFeh| z?x||g@bp?7u=Q*H2GL`he1-u$Vc_05vw4{!&>%fK`i$Aq_MSUS>XiAJ@{u|PHaX)_ zbyfra$up)8EBu0nM~&7w^db3%to+OK057!Q8{ zv15||1%V;$Tsjj=Ge$9KMk)Rd60Otzc{J?1Y8;I$sVXFW9{yvqa*CTHEYmXUpF4T` zHr#JTHR3Et7nAijLsRzUrs~W=%eB}W86>xg4*L0?z(!dO_hcn@3>Mz^!TP-&e^e=I z(NJ>l#QvSm*S+s@D}S{~&IhKsGeiaae-e#VBx~opczJ!sOr-z(GwSl3Y>E*!pKi>` z!o|B))2R*LCA&C`ev>g~W%{p`xt)6hP8(ewL|85g#Xm)R9XoF|Smq{to5_B|N$r+s z{X`hmHyy_`gOh8U-*axtT6jL+bg$CUBr~jhAig56m0o-7#RwYy3 z_;N2fTM0TgN((i@Yo;k%z=Z7Cfcf-YVK`U~7xHP+xUjT?3|w)d@G%=f3w#x=Cfz0W z-!`OZ+3*}9oT!-pu(mzyF;2zZLm|<$QR(Q@Go4NZyX|=Erm!m~_ce95-4Tr`>S>B; zMrsldmw-@o2mhH(EQiq&_0BTM4j7DG7&0w(Z<=8s=vOcYG|D>Z`fa&HY zMhB+0tLb{PLx+6DGm-TtASrd&B}XO4?;1GzIuE4xgL>P|s(-4whw+MKXf>dfFSMBM z7dS1^Pg(kdY)EvwZXqWJip@d3D*wst!<{;jViW6wY9Z4Po6q#aCvD4IXUr_xIq2_M zdnU~ZSJZE{$7MJ?UtP4gzq`#j=iS7aBflGZenGN(uzd~C*n~pqNLAf9qlw}5Hjsm& z!SAzNVNaHmwASVJ3VBc0-@O=@L0twi-nPB z+$-DVr>_g7;}W>fu%d(ZZZVKdKMuBiGC^AiMu#hIVMm^|8f#~JxFt(j8u80|l3#3G z$vc8m;cPTP$U|e#XNno z#;G`VLWj$LOg*2eUtU|Tt7h7x%ywO#j{qL2+~!yxI;>rKoOn7}tvQEcVzV~CWxd49 z#O@sn%=15i8$C6SLb#-w+**5Hd=kiCLYFcR!^t&tb?D8sKVui|HWstY$69*wlC|DK z?r45BU1=wOM`N#76I2`xDZa6n|ViT$d zbrkr7QCBvs(0f+_>!yb4AjtXP>1Hwg_ud+1mcXB8$ErjqT?x(ugl9wwEd@L>I(UkB z%SI7RI@4)$Vh5+Qs2Aduia&ixwQ^u4rb-f6uO*`#u`4N!Gvj?`y3+&W1Iu1w(!W zG%f6wtU}1=?nWM`hDK8x?!^n)l*~myJe^OIG*O(Ql%fzv4XRwlR9=>O!Kn&k0j(5a zq2H$`JISaD3tv)y%3>^=*R{dLFj66;$UVQSxXa@l;JM|bN~qX*Gm{_Hm*Y;Ew{-G} zMZKYWIyH;JxdnD%b<;@N70z`yJI9P3m)Cjk@vELpwE6VZEWMY9Q~+DYW{jpttQ9=X z^0v2;yv_?DoL=CU9%}g-Q=RxP+B)OK3M1Q)-#X5Eo%~T})bXN8ff>FY%2adzg8bk~ zJp(=x+iC%y@aPui1-S>D9y-wqvDKah3vLy?r;CbEACcF~@(BG$vbcGzfr$DS5hYU&b$<{FDS22j&dCIef347}UFV40zpM z+p&yCg~`C{g@e#}Xk@sihuGM|aTFO>96e&q*GFHp_bpGx@BH6^D;Y|!kF63^rXQWj zapdRBbsC|?T?mX%jxHYd`?M@$jY6yga72_ai^izEnI@FFd~l5^bZY z#*K`E&dyDtLWJ?R3nNV4%PGFwaw)^x0g_&&3C$(TzOzM5p`_tR_!Yo9^*3vEs^*K- z5ZhK+*sPtCM7*h?Pm-Qu4T9PdC*neuu{PaPTph_@v^}2Sve7mkLsB^PeSD~`i_d5} zCR00Q;GHt%BXxtz5{)~gujRt38Bf-2`w&vxO_2>N9dKhQed%23q1iTzz`3T8WL&C@ z(I(wZ#$|9EQBK<_G%}?8yHK9UT|KZ|OCe%QxFO>Z`8~CiUN4RWcIrJj!i&T6)t>wE z*mJ`9EBv6ZB9_4jdC5`HpgCjb@0*5wz}7fawPyi`WTr5;Z~8eeMtn-6+b@goALFO2 zsZ+^LwoJZiTesKIsXaO|&a=AL@m(7$Au|(1dZX`D6FvnFUWO}Dy>~KoGb5lsH^y6f zeIphLZoO^UF||;`_sfLcw?nCsdCj&$8|2M8H4^QNRTt|Y`um`3Yxtak9mJl1rrCZ;NU>L}}5iI4XyDy#`Q?wYSNae_1Xq?C3 z+w{uAvjfbZIfR1KFW-v83-Z!XIJQ@KWo&0Aa}zD^c5Jt!RvAmWu(`OFXT_0*PY0vV zZ?0r{50|qN0#b#Xq1v#(ad^z3-UkK$@faiJZDP3c!#jD>(2C1(XluH^aBJi0>*Wgt z%%DSdaf}!2P&gzf=pA?-4=DtNTU{)YpbFFY8NIwXeBupy1r$jCQ?LLM3tDv3k!9F< zf0?JuM6((}hcgjjFCzsd&>;7R z+|5KCdX)nARxQ9P&d^J0u%I)8NU3J)GT!*T%*6jOr)P-3pt}4~LdduNEgK;h3(Z&% zKb9O&T{{Nf__M;Jj>CEL9t5neb!PT+=a%0kOZJQ-30=)j{9~-33opq~0=Fp)>X@q> zf6)4$8v<^3mKw$%6odtY;N@&tzk_$;u6`WV?Gsn;djP6wc~4u8w9d`B%@ErdlKyBQ8flGm?ENujkaoqlzBv}l$o$UJs!^=b0ekX($R7gnW4blbVyS#3@}A=813KHO)q`c=_TdI6NL}i(Y{`&ns;5)jBky0^Rmfx9{(I8ldD$Rk}0*iTd zWmK2qM)p|9|b+d|)uQVoKS<<087SFzd!5Fcdt*sGVIcV>Gl80`l} zhz9o`F`NmnmMsJguygRO9PUMG=TlB!FZ2s}IHqPE&+yw*+kU&kpvm@AFmjIZ#gDBQ zF6f^w0mJWK?0vbE_rJI9iCTc|7UZhf+Z^XxO!rx_KpokIhUZr8dnx65MmNw(g42^o z3N(XAT@?(dL7p5}xMumC15_P^M_D~PcJ^;MfYP~0QBBODsO!I;4(1Lj`UH3w zhtKRsBoVgc(8%`Vj>rKtADFjPxx;rZHp6a)F#M=wz3*P{U{18W&WSyDwtnWOS5R}$ zsC%kpJn{noX4}r4+E=aF8nY0D+Uk}e0a^cdB|rxtJ*;4&Iga{_RoXhpvPmvrY9C;( zBcliRfeN^v#i4w3vrS>g_GL+moxg!>>e_Vif_<^{ey)pJVbkp8 z;Q6k-Wu^_cA1@97>SYF{f&1UPH>r$ye$+DIfoyt#W}|mH&EG!Tn(7*8j1MX>m8^EY zh8RAcH0Td0WVb84)|@rk^Hit|R}eEmI(`q-@biZ%y!#2GbrF>+KZWNyIox~9yFuQ^ z0;~hiT;HGuY7_V8_oxG_*}dmQt=fK$zBqtZ74C})=K6mz7ybrV4=@+dhvG&I2X0eG zVXMA(_nR6=FN40Zx$Jyi(6=S#H8muU62^}ALcmDbLH7Wt>6;5)Q&!!3d+ptT^Eq44 zIk>gr3v@Cc!6chR-V+H+j4WtP8-O?Vikv@j1J|N}OJ&%|rNaNc8A^B*@?yxJpVG7A zpvku3gZ=v~GOjknqmbDUs4p}B<8BJHc2*Q^WY|4@*C0$kI!l|v7z>I+zj%BD z8ly^#vz--xcBZT~5?i#XoTf9%P1$>5bq}~txN$f(#p$mu#W6@mvNVdsj}w_HOa?QW zC{nC%gFIFh9*SU=rve(QDa4OG&&|UFkEML8D&1Qt`1dq9U_C_geXMJms5Wll_wPU(gzx{m` zxR3PYu!o0TN_qq=E)p8YTL8dAVT_Ci!OW<{F})?5160|eZT%y_>Q#U){$*(PwKGZm z^&V7Cww%2}|I{^aRtJgOS*jZLwaNG0S(muKX#BHoOUkU8$b0!S<9lF3FX6Rnv*BnB zx=;!m z-u={jjjKRGgX3j zJl~|ktF+Y<9)noSFaN_JvybV47-Y`a0MFA*E8xf}1a;@GI6=d)N^gu3(r^mGaLR*0 zQVAdy-w2on!+37gk4lV1fK&;vXg=rOLU!dxmgW-e3$nQNgpUs*0U2~b^zP( zi;3^rwTB00z4Wd5`U~ zKjhTbg#y!yve27ktd0RdH}@gu#{e-hM|wU{-n(e@@tnXc#~&Y}Tp`?bN@lfVXvBypFvPeDD$ zstp9MFS-DnCBk7REiPX`T0SI#*^5mjTDXJozzHIw+9%CZ_s&AhJaBe9b(c6*@18r@8Jp%n>u zj;w24)Yty!gXQwJEc;suc1Pz~ml(X&Gzfk6Kq9#@aCjS?r6V)Ssjy(Ht~!d9-`l+{ z9);HhLlTiz+Fe@Zs3oihE1ZHcb(> z8H|yG3o@>Nq^Tr(>-zf}pYJ!Wlea1#!iR%3Pnu*z(Ad61u_^*UYpZpovI{6lOB!%Z zgO}%>7Koa(2E12i@*_IKS*8IT>g)FEtVIO#`9~l5-<>5SgDKke$ELfrgdqIR`JcuX zk^_!G-g%f|;JPt@xo>cytc1pM;)&WkP=tw^xKVGs6aJ^2rNRLVjJ2eo=1>?Yk>-4y zYIP1-Zy_%!JP-iHQey5OYMdnoSiey#!hEJA5Z6t?9Q<=YIJki@_#Top1%lg9`GM8j z%;;%VS>!K==qFgNNBT{_H7ir^CY`#;#WWfUTXkVOXV>@iZbPu9AAH4})PBnI40_zME^XQ>v z8DG%#Yw@Ir4BcwAV-z$nE9Y(8O{g_y+M2;p-za*jxHqelU%Z?^A+G#B^k|xwriMZ(5$8_Rqsl??fH$&BDH*5D4s@t?=rd>)z zZptg94wfO%(64~V-=fT7A|Q8v~A_P zUi>HL)FnrvnqEDyE(TfF^NT=&&+HL)wRK-Oq z$?i41PLr6(LgENoIcha`6}&> zx!)n9{L#%q)w4+ur%2U(e*+AW05zS;wW#Pp3GZwp^R%XAC7l~Qy~Pbc-~l6bX^|5s4i{hi5FY-+KDW?tE z-Cpfv7jCL1wN2-c*%auy6U6?h+<5&BH=}GxE*^B#ICbA3uRb2s+qt<7I>JJowW3W= zue&CWKR^fz?a1x&+vtp)rd~;>U}X1N ziFgjOuAwZtY|{eEhiqOm=?zPwRlPjrZD67>e|h}|h-f3Mn9_N>RasKxvaEYqTnkKF zp{4rm+5B5Db0C{r94+7eSu^q6otQx|*V!O*UCIkgLYGA~uzQT)R5|-oCAMox3=yBV zP-mP$o54Fl*pSWF2hX6ec}kAFRfSZ>!z^H-=VBftH+I5pTW)t99WLlw$R^gK3 zNpQ{xp2uTh502rEso;UURJaZ3G=>#^J7g~tB?)>AS#r;4cce-f^XUDk@>KJolz|h# zwzQK9l74#27(+lv+)4coWpr}7)10yny~{;Vy`*o|C-xy;!RNCeSV?p@n{(iZ{ROz; zv~jXr22ek4Rh?=@HtZ<;s*I8;Z^B2QrFB;DR$x#7(bD`1)DeaBaO~h>z%x3LWMk2? z&l_)iYt+v+J*{@G!8Sc(Q>H8*r^E*6rY4qe|7;XTbr@V0S9QcW!?^)lwI8azydcD< zr-{~5!f(@{#glE99jB!2$T)oRjBe)`BMC`g9g0mWLu9I^hu9Aqm>g2{HWm=GcPunm zeCdB7X7>8+=C4FQwDk~3jZ(11N7N?S5jrE~bf&k`Gjfm4u|K;y$R!TH0s7CZ!3#OFU<2;lmQCyy3|-sCU_W>1_%k4 zUi>>74x@+rv_4upPhtDC{>NE(@)aKeArKf0ZAr0c@VpLszyd0xI%-L8Is7KTHnQ!@ zQFggGY>AtwMmb}1-FYE&f&6ESRn+P+++8&ctmUp^?QfBAqG4_bsW4BRT^Ut;F@&1K z9x@8sPE=H#E&kSTsAWVl{chIg3nQoQ4`h?{KMM}c4&!*h6xRu@6l8#{)X0r3JCkdeF$QjE&lD6hd_vNbYSe zzfnR+=^Na87L}7Pe8vhu7rH;)Uy3Q`G8Nn#J#Y1nkk`x$K35C_zB47}H2K4)^enJ$ zZ4Tgd3DV@7R9BbrzDFvRbC6nRC(@jlYk{5~2;nJ?Wj^?Kc@Os+oo8N03J#YPDt5V^ z6T-$e2u(7m3|Am$*@T({O3j;2pBbhUxzfH|vcwjH>LC<8rDW4-OV!+NOP&qP4WgSF zxouw`M$-^l4wdr?-d~;#(@djmW`-%eDso@AFV;;%`v+qOVL&f_&9hJSo zOtdbl%eu2P|?G3JD9n87* zm2i^x*8$&-&Ng=-$i5nT!o$95&s}yTfVk)-j*+R~?bV$TzS&goMJI_{ItG5HeVglV zv51XmslHtQ&Yfq~azYlE1sbQD9HU6kWq+z|bW{u21zEUY@afqmI83Juq^skNw?98( zs63S^aQC1&wWaX^vM zcr8Ng{-+rJ0K<-So$LPURJGD3t7=vb$vU&t&pLpKv~?ifE#23I7&`9%X6TfB<1WFz zes-@T?XB|2{?3vld>Pp9*p0B@wm@mBOu}EW_RO+eo-`=t7J0j+;>D-=r|EOsS8kKl z+~vnFwu4-6nw^qnGq*otuU$p4zPzGlEx^yxB{ssR`>85Fb>6Nr|I9c=?r-lM4!_OO zh53zDbJ^woD!l-cB}s?eO?OvCAkbUH#>YRoOYs^aXh9>;^0pcm-a+N3-)aHFqB(m4 z2*H&NRoX8PF}yQV#hdz7+s~dv<0^KGfR9A~dIO8YN$SY0ir+4n?ZmOx`M)?Sn+A5J*J(?pN*H z)K4G+$@95U;sDKgFOV2TkfUR(G-obcNkJ0u7wI0(up7b0<-%nFe1A~Je#@W_jFKgR zlz0K;*G!MGiualpKt{R_(JoPU`?G6+!BLQpo(uFJ z=+Ol!nw4E&;i)X%hpy0&7s(G!b3;yD=dr7jzXmSu>@c%;MMq<3zl}#J4pnQ@!aY6( zVTfwZ!Y2_!@_MJ)-fYV$QipX!*|r(>m42Ikj^W>VGr=1E;SdDE6MoiiRd>2m|6^Ymenaa@i{}wt=lY9UwZN?*y%%lSHd804L?$8G&BI2?- zmp=4*-mN?Hz&hE9^kz;p71oT3*`{~;<1i$)GCWD>mR-?hUI7rduDMaR@?!&y>oz_zc2HfSzi1Jd>= zD-~?g9rf2i6O5Jw*}911iPz`fUFC=G4)L#qkGB^{R;IZ`CV5x2$GOB1DY%&?OmE8= z`D^P%iM(0$?o-+AX*f@VSd$znzd1HLUi@%#{7z3)=G<^qmA6YUo^;r25))v)z#PR2N5+cOF65h zUA~_GX#monK^E%FRIj&M31@4N&emMZN3|3auUTFT0wyk=Tyf#0T2v886ndpHcSV73 zy!EU^RYnSa2h2cNBXqaS`oYe38=+bSBquCl(K?@otK!Wpqc^O5`KXny&C=51sOjmY@mdz^ znsdjdmd|O|)N_(y2NJZj!BSU4c6?K^0Sgw4u1J;&DV_tnQ{=n4Iq^B^Vf*rBRmmwD z%jw2rY&}XG)Ti05%uw%U>rqlsKit%NTr8=Q^Ebz)N5TTh!%GE6X%ILK)Bo*PP;#WMNS;GC`D(aVaMc@8t2yQNBQhPs5^@ zxY2FCQNz~#S$f>@5o&|+3{1is@!D1K@ir|LEDHkxE-qgqpN?PRwx_rmPji$;AS6DQ0se1&uU?fF}U2kXwmqH=;yzF$I|M3HSkh>L*I~HqoJmfD`QS7s+S9m@ymsVeymwc=dQY zInXjk5uNI7nYkcL{1r+^)s0V_^Q8~mw$yW}tJiW5zxXT=U44EFcukk-*P|{HQaG4@ zAzBRS5?pY#K`ad@a8=YMYax;0-a3tyA`TU6aWp9GaUVpRujy`FCV^<8W%ED98Ds&KP>@p=H0(;r`AwWS2`<$x=IHf zk7b%uN~MP(p+;w&ewTnzV`Cz45i{G|35}E3E;sJG1|FVRd+D3WWJ2l_^0qpTDXL_R z0al1URFP66{bi5NH#Jpgg<(jJuc@6%D)BC2XjbFe_gS6KEQ^3}Q)jo;ReC((9q>u) zd82)UdDEMcL8E}yLj50zcww_5) zyKKPlz{+aVPDRS;8to-O@p7W3edX(zx`jALKSfi2PhE+3YHFH&Us4(gS3dq5zY%PZ zpUdC(o25ObHah}J8AI?t)4~*zik>6eh>@{u@F~mZ;xya+!~3c$?!jwP>HRqtM#Q-D zfusHIboNr4u4hHZNBF!^L{v4pMa`GQd=c)3uR|v9=2ULq<*rkB5^lNs(B?-RI}HA+ zoDIec^9uYa$f%5ZR%A@!pn;<2N4LmKl#gO`*?bYo%{iR0sVb94(k^Tf;0R*&s#mmU-KtdJ5*+Q^*+4vG(+ENBy@hVq4DyP8Y52wJG* zJ%Y35AM+Z z5dW4I79}xdHqc?Azcf@4@pEGNrLJU;(E=SBSc64%~w9l$uBR<8dfaOFdyClITp$RWSF%9|KAs@?IqwTF3-C zNB)487|AA{ox~BDL>0d@A=ozPr~S+e=GC#Wh~~}G8DtEsK*{2f*d~LU-)OVA zR3cQ_ray4U;_LZ&ozV zfFPX~Dk$9@4qehAAu%9BcYo(Lp2z2X|9^e!TMJ#IOK0x6?{m)H``XvOF8t$*Z-FSm zuQDHWO@PF~tYwpPpz5^s%VZsg8{V?=_YQ^|lw}XABCutH%w+7hF7%5w1~^a4(QK!oG}XTG9}N1Zu+5ebe4N8A{3Gi@EyF7Yq}Aa}sV*Z&HxL z7BMMbJI;~0PUHQ`za}eml5!!CyTRu@hCM@mW1o26#rDczR+ zg&N}WKKP&ZHmRjNwG>&Xt1X9w_42PhS6zgIM!i-Fb6lQ|(5${`T8>M>yS)-Dm-D6z zgk8=W6m>S~Sh8qR3zEYc7Weu9ykI62e0Hy9Esg;Vn#0L{b4iFUUEiagZeK1unT7Tz zI#>Q|kDt$&`iZ|f=;yKszzDlG<@nNH#ADy3!J&SFb@|Nq^HnkcBqJ~M7z=+R_g!~O z6Bq$iM+4?iKJ5JD9o4fQ&Gz4OyfFD86zt~Ak`sC}q(z{`P`uFEq*?rx$h&FyDaEEW zgX?!UFfN(b%;eSKV@Xrtt=0)cW1s1fJw0+}ckJ>lXpmuO+40cQCz1X_8F@t{J}Ck%mhIV+=lu|AD)2Oa zsVgn!7Z`qk;HiNUI>-o#bbelkIF`;s1|uvjsBj4%=O?Or*##@Tbs>ZcDkqay+|B~f z(;!UqE>W4Ybn82A9!*)p=dAV4OhxcA8;MIyE4{14Xaeua{Vw-$0`o}FiTTv{&?G0Pd7F1DQ@`Q98#ej8XLCOYY?Mq~Zv1q7dG;_Y``s)e@@VPF0) zI`gZSzsx3wl~y`+gv&R<#lA96F&$B;=+mU~eez-;jXqej9K23KuRE|mY zC>RCiwkT&9ykW_AEnucco}AhBK(BjZmC7inn_wnGBjDzGN{;YDu8E@dT;FfIyG2e~I!b+|I#J&ox5kO|)=x#t2gEf7G83-HDG2lHuW zR;QXX7G*Lsl}JkfKboG1nma&~`hU!9a>~hFgr?1>7|#2Kk`8uSw?@3+iGxPZKjI+4 z?X&TU)$2};q>hf(n5HHyL71h5N)L*#wR8zlkE~g-8D1$4!crTAFL2l3An=c}xDe%74yfF1^JETog{acfkPhoOuP+&pWJ|+g z(c4RqAGM^xHZk%RBtLR~)G{%~UPDQeGvz+Ba*|7YxU0tq7iU|RLX`>y2xNQYrQ;;I z$#)dY_0PpAH<;sW4NTo;V-|n7_x`U5s-LR~LU37OclhXVK%Y=lXuQ=95&+E4;R$*S z;O*US9)0J7z`UawXgRr#wmbvlXgw6-!Rd%IlZ2I^ii)@@Dr5gr=Q;3PwiuZO%YFD0 zEDumVE-4Ep*Ga2=EZ`R}(c0tUX#YrQ*^Q^Gb9Sl>ZqpiMD!tDRhiwntf!{e5Wheq5 zF+N|f5)-oaWII=MFGRKocX#iZOwY$e`^B=XV_IU(eN;Y$SKguwbONRBt+Cj;%BhW=(jMwf?vj^Si0<$j%KsF>d^FDH=hP|( zrFy?agrKhuv~2B9mWVl5ea|q2Gfg7 zS}pj4iRQ4&-0NYnxN#pPShI+7p9WnPwUAJ9E-Ec91t~_K{zJeWTt?Kj?)K)r1Yv z?6HOsaJFR0m=GM?d#$C+jhD=`2J~hof>YPB!4%u)e5bQj7eY*2UP;9iC~zqP5x8q| z8TZ&u*XmA3g>SeOtge)VEgS4!`F!u05Io!$Q^$lkC-b=I+i5Zy6UHQ4nrgskQ6FEE zf{eweM1^@nE(YW>rx~tZMS;cAnTgAJww?k$zcj6Pn4T&8Jy^~1kuZwxYSc4^+c>N@ zi-ipIW}(w(HI$DAUsg^mJ<{dYAR75fHOgTIw74-kyQNw2+_91S7=`95@WAISlY;}bkh{} z5||mF6Mkm(fapb^V>@3d9b`h@;!_P6F-lq!1f!bL>x%5d-9i%mnM>a)ODWF!fu%V7 z?X%x_K(qBf81`qPib*Bpyk z*dT!isM#uS$w_tV3}dTH_D|Vau${r-Y6l~npZWnOm(6VhiE@nN5CjgS92V+BH=t=Hg#@! z{Rrq1I47pED?h)2^Bp!t*wguopG>6_U8E7mgW!F?No(kN9qTfEI?#=KZ^$(RS=dIq zO5W4dS+JoG2k4C`pS=+M1Bj~b1#1aMo1b_1QrD?h*C-Ou1{O#uDuSUh=OObRU@7<* z$BB{;l}!jUUWzilz*#jfO}`Gw&PUAIRUMlijt+Sbm^U`+;_Sn=h%kLBa_2jyIK9#y zyCc!i)Lo z+aIg_(v%=zvn>*9=m>HUY>U0Ha)XI!A;14jd)b;$(BghKs-uAI>yKilM{<_IODZ%qIK%S;;McBHlF%Z=lpyyoA#Y zF|?-EgE*ax&?T~fEU~hgK_vrNS-}K&Ikc;8lt)O!OJgFvehygMbGc4@bRv8Lv^Rb> zf&k+~a}a-d%h}}~cXFy^kbHoy@HBC0qcTH~FFxgHLxCP~m`G044d$Y=nvwiz=XVx* z+2b-J$?oy|G;W#uuyT5`dWypA7MU;=U4%v%6mN0CAbPsxV@9VIR;6|Nb;}1m8nW+t zHAJOc+dwbZ%+jfj>S0Kv=$T{CImz^TDaad^+zlANbOltsuftl&W{6z3GI0~nPU6ct zwW_mMHtl*E&~oyEQ^1_$k+!Zjb$0{bfC)&SSo30jAl_TJ_g@UNd*p`&*k68pxQxoyAE62fazW*>kQI8d4|4OK_h-qFK!cH5BY}em zc}@{9G05pc4R6vldK9uSoe9_mt}~fxyg|=dAhwzhI}<+NanswQ-(H&@&n4#R;yt^M zxqYU;uX1}5V;h`tUAeqS+!wsGaKlPBLC2L9P7Qk?D$b0+s=C-%5n|W49QuQVt#vS zlYX^9$DkN)CaGNvAv^c7fwj(wCMbqW1?)SxnJ0a1fB0m9G�I)D(Mmk5(4^&aTVW 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 literal 0 HcmV?d00001 diff --git a/wiki/img/attack/brave-chain-options.png b/wiki/img/attack/brave-chain-options.png new file mode 100644 index 0000000000000000000000000000000000000000..1e6caadfeafd7e680ec23313cf1a6ae6e2dcfb1d GIT binary patch literal 13580 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 Ck`rP8 literal 0 HcmV?d00001 diff --git a/wiki/img/attack/chain-priority-options-arts.png b/wiki/img/attack/chain-priority-options-arts.png new file mode 100644 index 0000000000000000000000000000000000000000..07374b12b2f2c632b7620f1d2ed9977c16e9f028 GIT binary patch literal 6976 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 literal 0 HcmV?d00001 diff --git a/wiki/img/attack/chain-priority-options-avoid.png b/wiki/img/attack/chain-priority-options-avoid.png new file mode 100644 index 0000000000000000000000000000000000000000..474ad30b2e52d5d774ab024f115f419dd1a04afd GIT binary patch literal 6784 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 literal 0 HcmV?d00001 diff --git a/wiki/img/attack/chain-priority-options-buster.png b/wiki/img/attack/chain-priority-options-buster.png new file mode 100644 index 0000000000000000000000000000000000000000..95dee47c8865fc0dd81661b9be93fc96879782b6 GIT binary patch literal 7684 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 D_hG`g literal 0 HcmV?d00001 diff --git a/wiki/img/attack/chain-priority-options-default.png b/wiki/img/attack/chain-priority-options-default.png new file mode 100644 index 0000000000000000000000000000000000000000..75b609d24c765ea1528294754ab65b8aecc7ac71 GIT binary patch literal 8410 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 Z(5!3S4yTkn{;U8{R#aE0lCyaGe*iaE_?`d& literal 0 HcmV?d00001 diff --git a/wiki/img/attack/chain-priority-options-none.png b/wiki/img/attack/chain-priority-options-none.png new file mode 100644 index 0000000000000000000000000000000000000000..3b867277a94afeda4ff5c748adbea01c4cf751a1 GIT binary patch literal 6471 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 F`X6a`%nkqm literal 0 HcmV?d00001 diff --git a/wiki/scripts/Battle.md b/wiki/scripts/Battle.md index 122f3b887..2af6f77f1 100644 --- a/wiki/scripts/Battle.md +++ b/wiki/scripts/Battle.md @@ -38,6 +38,13 @@ - [Card Priority explanation](#card-priority-explanation) - [Symbols used in Card Priority](#symbols-used-in-card-priority) - [Servant Priority](#servant-priority) + - [Chain Priority](#chain-priority) + - [Interaction with other priorities](#interaction-with-other-priorities) + - [Mighty Chain](#mighty-chain) + - [Buster Chain](#buster-chain) + - [Arts Chain](#arts-chain) + - [Quick Chain](#quick-chain) + - [Avoid Chain](#avoid-chain) - [Rearrange Cards](#rearrange-cards) - [Brave Chain](#brave-chain) - [Support Selection](#support-selection) @@ -390,15 +397,15 @@ With cards in the high will have priority usage over cards on the low end #### Symbols used in Card Priority -- WB = Weak Buster -- WA = Weak Art -- WQ = Weak Quick -- B - Buster -- A - Art -- Q - Quick -- RB = Resist Buster -- RA = Resist Art -- RQ = Resist Quick +- $${\color{red}WB = Weak Buster}$$ +- $${\color{blue}WA = Weak Art}$$ +- $${\color{green}WQ = Weak Quick}$$ +- $${\color{red}B - Buster}$$ +- $${\color{blue}A - Art}$$ +- $${\color{green}Q - Quick}$$ +- $${\color{red}RB = Resist Buster}$$ +- $${\color{blue}RA = Resist Art}$$ +- $${\color{green}RQ = Resist Quick}$$ Cards can be changed around for different use case eg: @@ -437,13 +444,71 @@ Then you can now select the servant you want to prioritize. Follow the positioning on the [Party Mapping](#party-mapping) +___ + +### Chain Priority + +![Chain priority on](../img/attack/chain-priority-toggle.png) + +Chain Priority is a new feature that allows for attempts at $${\color{black}Mighty}$$, $${\color{red}Buster}$$, $${\color{blue}Arts}$$ or $${\color{green}Quick}$$ Chains, based on the order that the chains are chosen in. There is an additional option called `Avoid`, which when used attempts to avoid any of the other chains. + +| Sample orders | Name | Description | +|:------------------------------------------------------------------------------------|:--------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ![Chain priority options default](../img/attack/chain-priority-options-default.png) | Default order | The default order used when Chain Priority is turned on. This disables Avoid Chain. | +| ![Chain priority options buster](../img/attack/chain-priority-options-buster.png) | $${\color{red}Buster}$$ focused | This order disables all chains except $${\color{red}Buster}$$ (highest priority) and $${\color{black}Mighty}$$ (second priority). Failure to execute any of these chains will result in no other chain being (actively) used. | +| ![Chain priority options arts](../img/attack/chain-priority-options-arts.png) | $${\color{blue}Arts}$$ only | Only $${\color{blue}Arts}$$ chains will be attempted. All order chains are ignored. | +| ![Chain priority options avoid](../img/attack/chain-priority-options-avoid.png) | `Avoid` only | All chains will be avoided, unless it is absolutely impossible (e.g. all cards are $${\color{green}Quick}$$ cards). | +| ![Chain priority options none](../img/attack/chain-priority-options-none.png) | None | This completely skips all checks. It is almost the same as turning Chain Priority off. Why? | + +| Flow chart of entire system | +|:-------------------------------------------------------------------------:| +| ![Attack Priority flowchart](../img/attack/attack-priority-flowchart.png) | + +#### Interaction with other priorities + +In general, the priority of card handling is in this order: + +``` +Brave Chains > Chain Priority > Servant Priority (if on) > Card Priority +``` + +This means that the [Brave Chain](#brave-chain) option is of the highest importance. This interaction with Chain Priority will be elaborated in the corresponding sections. + +#### Mighty Chain + +TBA + +#### Buster Chain + +TBA + +#### Arts Chain + +TBA + +#### Quick Chain + +TBA + +#### Avoid Chain + +TBA + ___ ### Brave Chain -There are only 3 options for brace chains unless you use Servant Priority. +There are only 4 options for brave chains. + +![Battle Configs - Card Priority | Brave Chain options](../img/attack/brave-chain-options.png "Battle Configs - Card Priority | Brave Chain options") + +| Option | Description | +|:-----------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Don't care | Will not attempt to form nor avoid any Brave Chains and will simply use whatever order other priorities have sorted in. Thus, there can be the occasional Brave Chain that occur. | +| With NP | Will only attempt to use Brave Chains if a single NP is being used. If no NPs are used, Brave Chains are ignored. | +| Always | Will always attempt to make a Brave Chain if one is available, regardless of the Servant. | +| Avoid | Will actively avoid making Brave Chains if one is available, unless it is absolutely impossible (e.g. all cards belong to a single Servant) | -![Battle Configs - Card Priority | Brave Chain options](https://i.imgur.com/nhPPIM0.jpeg "Battle Configs - Card Priority | Brave Chain options") ___ ### Rearrange Cards @@ -456,7 +521,7 @@ Buster card is picked first, all face cards get a damage boost. The Rearrange Cards feature orders the 3 strongest cards so the 2nd strongest is used last. -![Face Cards](img/face-cards.jpg) +![Face Cards](../img/face-cards.jpg) If default card priority is used, FGA will pick these 3 cards: - Jeanne Archer Buster (strongest card) From 093d7536b917f6573ec833e858cc8e41a38aee5b Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 18 Sep 2025 01:58:12 +0800 Subject: [PATCH 108/124] Update images with transparency --- .../attack/chain-priority-options-arts.png | Bin 6976 -> 6924 bytes .../attack/chain-priority-options-avoid.png | Bin 6784 -> 6744 bytes .../attack/chain-priority-options-buster.png | Bin 7684 -> 7638 bytes .../attack/chain-priority-options-default.png | Bin 8410 -> 8378 bytes .../attack/chain-priority-options-none.png | Bin 6471 -> 6406 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/wiki/img/attack/chain-priority-options-arts.png b/wiki/img/attack/chain-priority-options-arts.png index 07374b12b2f2c632b7620f1d2ed9977c16e9f028..203853ec35eaa9b8774780b129f92496a24d06f0 100644 GIT binary patch literal 6924 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 O0%&RIs8_1mME)0Li~i*R literal 6976 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 diff --git a/wiki/img/attack/chain-priority-options-avoid.png b/wiki/img/attack/chain-priority-options-avoid.png index 474ad30b2e52d5d774ab024f115f419dd1a04afd..a5b5d8820e7d3c67d5966d575060eeb19bde3b77 100644 GIT binary patch literal 6744 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 literal 6784 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 diff --git a/wiki/img/attack/chain-priority-options-buster.png b/wiki/img/attack/chain-priority-options-buster.png index 95dee47c8865fc0dd81661b9be93fc96879782b6..4a99fa314b2e738677e6bdbb731be673a8712c8e 100644 GIT binary patch literal 7638 zcmb_>XEA=~$^4x3?ne9`s_%w_L*D-P!tHV{w7|i68mb5Z!@Q?=(g~AjwHrcCo$CC?LK=tW z8~t>_E&^FZ;aNm1)b2gtDhpZ&z9LIGHwkq@7IZL~c|FZ-&?Skv9iMGIZK!PTXIjeU z=4M95Y@Mmp&?UdI2dBG-%lyUA`Orotk-#l`a2$RuUQlO3kU~e(YsQY^r;VMKw_+^J zJhFOyW>d*qElW)mt?z4F3xh562TyNgYgef?Uv{*_T6Y{bp3WtNhrWrsJPe@yt-iat zs)~`fxpRUJVo|mRI`)mEuYl|MacX{aENGf#QS*8GH;e9@L zQr8wOP>3MVYuxM@#kmBwdu1-f@y;Vtba0_k4kVzi5NEZ@IBLqpP0Lf7)vw3i-i4 z?es7;jjeC4?(~~p)R4n`R}mMs(`2Xdo^`oMz(n^MaM|FKg;itopzyP}NEKDOt6POI zJQIA)?O=Oz+o^S}{aEXa#H+q)zDEJNc9W3V4}ZF|@A>@^RxZCZ9)Ik_RkNH}-t9^z zdivhbDSyaLfA@6Uj--23yFOh(t=m?_^Fe`o_V_AT{!#Rc1T z&I^au(IHkx8aUv}2MTno)iX`$&H zbDp3}be{Wq%}ux9gI)Exx*Ce_2z@b@j_;@9C*5`tV(k04?{@hU=eY^#O990fIp1f< zRu=5N_S}5Zu{%?IeQ_1q*c~rbA_B$U2N0Up@~f26Qo9-_zH%En-i)g~LS)!r5E7Ls zbsBSa%ry>?)(hr%0oExT`&Rh&G(+6j28r{XXHDWAqV|Fo1)y`<7?r>MqQbObRis0%uMTd15Et1xaYp8h(92vd3f!F;+^(7 z3=fO%;4}4>JDYby561)U=4O`GHF3AU{-CrvoK4GkvD%KKAXD(J6ntW%I;&retBJ$& zso2J6@?Z09KAV@$tDvwe<8iEIvg|sqCOQ}aoL4-}Hth}D%;i&<)`6L-UVzK@^5eOP z!7}_(W1KVPvLSC6a+mnR0Jmqpj#P!+!(V`}%_8ubGZOki1JvzHye?}GKNde;svfn5#x;Rd7 zw5+#Gtrd$0A4*JU+fPhecBuVb?KmX!VpYyXBt5vHxNw;7V>jVI5a(kKRj?(v76NXFI5=@?uWIj~R)#PXF|+n?`S4G2K`HcX4{q zMvJdD#=9KlUYqy6+HcDm>TLdnF@Lz?YywxgI{4TNVj$`Pb4WQRt7YS(Ixte>rL3v9 z!%qxi5Re5x?^TV`@jc?IW;W+I{Yp!uA(+kXQo)oNxWPL`p!jmKMrQ_1K)rI(-nHTj z*?rCo6@UNrqOO%Ye5fFS&!|N+a+ksIh_vauzR9uk45GT675e zv{BbfNp_ng5#r%a6*rXY*o8WWaoKHY)@io89 zGFeNvqLFST@a6hyRST;^y#!Vvo2u$bPE}mL_~HtCx1=eY70b$11H>oB*mBXh*tN1A zO@zXh#iZHzZz-rrV!U~Rx>PaobRnL!@|e)~n_u>}seyrY)E32SKm;n9Y$MGor)-qYekm)4#fw z83~%r%hawvrl-kdhmLu`5Ri`I!CcQ7)tn`j9@}f0;s?u#?$qA-lda#_#wanFOGoS9%{|m{9&Y*R zV}8Zxb`Xr1L+^AyRKt8YiI}{Q!@75A^&>ryb8iBE42-_X?V?x0JbHEk0QTePw_ti>Yi|CPlR}}oYz4$`DO2QeMm3^9J6*USZAp|nt zXze?b^R$L{d~bK`>v#Klq`ZMpafr0~%xP(+8oU07MCOiuU+INr}2TfI=lL;3=tA531!vCf>{ zEUOpnS#4Osm1@^F1-XZnMcH{x$E<%`d?MAD&-xKruyJ&ugC;P$7&6UWDaSScqE(Yc$Xsh?k%SjGHP~HQymM@ zR~IkCQQW{`V3PFHNNZhmz7$IjR>w3E1yjo0g$*L+yT<>vN_l5bhA10A%pCmxRWa0* z7SOR9-#lJ8I^e>aYWBl9uKk(o3MfbfbhbfSN|CP7SlfN^*dp6*{ zF)A0wXPQArcxU175O4v+_D9Gq z~ba4Hj{bJgjuQYIxm!^Gdt^7MGPx17jkury;t#AAItRS0gOucQYw#y8qIupOTcG zo09Y)FRj~Jzd(Y-hqKS!UHY|7>9Y&_%UuCSh}lN33K!G429J`TQ31<8$b1|geDz(z zXE?Sr?D81Mr0=<$_6&wgO4J7;34HO*D$^{gl!}!qMAPOX$~3ep~^1H`~(Q+=!WrZj^%c{ zYYeK;`YaY#a6f!8L{$%*Pr zUdB8twp014!^{VgDayiimeH;FhE9ZGW4iSPi}8$_-nwoiC^^t;rDiipq)71q{yk>| zz2tP818ww%?6yy0Bdb-{WBivR{4;VJ!XgY4A*#M9-M&{@{rKnne=0*G)qYBvpuNx2 zYfi4?(Zro@Q%gWiRk}8POueR#xr9iZ~jz1y(=?4>C z0}>q(W>*DAd~P=G(3y6at<%UjMA5Jxl!*t5^xVhdW*)8$q^iyUrtr3|uC5|1#k_W3 zm1lpw8ZXhcl;STZEvM*BmkOA_%f=Qh_Ln+c8DBXj9K%1Q#5gas@SvF_IBzB0)*@t+ zv$6m_EhRc-uIJ5fuhYf6p5!<~u^%XyHJbx&TgFc6GApk)$4eYjP1grft+(;6^P-p} z4xGRtryq^YP3tcMD?5KAPLDA3*9rq+!OHZS?8stIv+z5pfNp%0sHa3o+WR=olPMvPL3Gc(`6vZ_g}{ z@yQczE@qg!oyg4(&~q9nWmlUh%x0$6IT0>f96&+*)Y9WAfH4hO&r*Z8j&fZKemd^F zv9+}Y^Y21GRoxo?X_J^2grpviAQg~q+B8^dXruvN_)}fK6R9~4v0$H-6;_y4_}!>o zMmuq-W~{X(8>9539WUt|mvnDjBFG-)r)(^=g-6b$yd4v)njvoc#hu%@0Veqc9P`Z{ zpFzqiFO*^CjkTs!^cOfvd2-~4WobQN=d%i!Aers}YI<@lO*OjxLW1Nc%V=EC)^UE< zSfhlKxg0Q57ct~jl?mg;uB5x#1hrJWGfc-v4N;x@kWx+8p?UY@1b?x`(+a$ zHYUE8aHQ+%mr#CDHdPA?i?CucF;qDAN}L{e5{W(fSYjCy)$4ujd+=o0yBvS2&Mn}b zNFHIfOkDhCO;0WAxISgT5q$v$2y>EE8Aw#6Ista{N7--|w+BoIVCmw|r6Jb6I{;;7Gy=HU5(F=R)YcgXfN} z8gS2$hgt`|7k&3V=Z;yAV|Xv7hOQ6xD74t*EBgiLKJzj4lY0gX&aMLQUzqp9kgn}5 z>70psVy*@)t&0IzR`AJVkWx$QDSvm-6dgmsF8+DD__M_8SUd&QlR^sDz#+E!4_ouB zrNL>S((H$UG6d{Ww!=gRJERS9a-yqyzlbop-=m)z;WDIm={f!y z{7htCw$wX{@O~o_r+f0T;|%e^_Ea@*Y{)R^*K36Gbc!M+VvRHt+v&nb|{pIyPA996t5z0nVT57I$=l%m+mn>&vS$wmnTp~mu&w~OJ z=I%e;Hs>vyjp-J_^o)ss%Z-B=H>X}#iTnPn3kyD!zoG%w+@sZziKSC(?hcUXU3?LX zpR@&zHM`@j?al@Lr%;Saqd!}s0;Ka%UkEybCDC!pElE+7=^qlp0oWQFcgafsHPWi5 zezU}l9ZE;__6#4W|70pliXL2i^Qtk-^tDFu?+I=wU3_lQv|%}WUZ~%<>54*p`H5hccM4OsRK=~tkOG0ww6W))H%D3QR< z>`l{3>Jcp3JQU0fHmh>a`IwF zGNEs?Wx+6v0wuocn)~gaUzAb!8`Um}AEcb7bMBFj@`jI7u27JQQ?FE42*vh*L+R~n zf4@kSAQ>IHZbn~oKqk%YYH8-I%_F;lbE`!llx2I?G0%uW7a|?bg-x_0t-#yF-gA z=d`%zcG6gqyjQy%OiKj4s)Q-Noh=`lxvbWs`dT*1iVGaR%lzf527j@!v{_d{TRoLl z@_?I)FGtK+4ysSZX;v!BF6wQ%phm~dZ)9e!rnm?g;<=#1dwu)9_xj8v0tu$Fyd%>a zhB<J1I#`8k=D5PKZ0bqtz1uOPlk9}~)QcWRed?TyOI1?_&84-CC| z?>HVIWMk3^xA1?ffq>LX`y*hd+fkYrmoX`9s={mARpL=TUCc9Kin2QY=}&;CIb)uD zkEocboyDmvM%U_n-Tow;XG9hpnS)b4YBg&-=bjL5;GXWz#nRz&(!zv2IYyGXVwNcLv_h^I2|{pWM4;NbX~TKuscx?>05HqqvIg{bO$jAd>QVU*c4!>)$7@ny5!4vu=- zC*-G8ZJZJWh~{Tq_acL@#OcxO+u})tMSR%is3ELJ{A5u@0@F2&-H>+p3>rB>?jv99 zHo1;H!Moz#GQ10ZEACDK>F;8~3lJT?O^7cC6SlwjP3>)os0V(W;vwWbJ<)Ybes2;= zMhZ#q&L~d9p*eSJC8M>xuV@B_VqeU(sSbB`Z(d*ox*R)f@e{wF*>wRyj9o7;jO2 zr102c;&CVOigW+3G(hU?&kvGYC;T*?7y5hzy!!#?_%5nDvZ1L>zO6PbJd+ef7jzg2 zbt;)pOYlEz>Su~)anRD}7`SfKU`SqaW_WW)=R&GuMj0yW=hY?F_7oD49uOIkvFh87Q=5~*oU_;bSb|tiz zt(CY=TePXz{g4L80?;LvEPg?)ky4M)>Ul^RXWXf4P#DHYtt+edzHAs+8p89-S1(r_ zc4Qa&@$n%Fl*nf)*pZnDpc*LrO-PmP0ciG_j4VOjJK34h&GJ!rW4_JWBKvC>tHhsW zO14!wpST%#@AC7TB+Y6Du=m<=9rEW2$r>@95!su2>6Or6}j z5F4<_y}xD89Mp}DjJq|L!F@1MO9JwR3B5ZwO#5jeqA#r)CMo^&RvtbwZFVcLaMCU2 zuh|5h=I7$u+%`507nY46eo)1&Ljy)MQrwGF#4p_T;zVmr^pU_b^jB*oFUB@+;8`D2ZUNH&H*{^hlG%2g&+aKs=7O@Q9@3`xOGSE&u7CB}FW<$MZ|hZ(kfJp;-Rp?S32_Dj zru+S;aUMBvT|~z)!||NCn8&jvT!MA&d<2BXPDd|`H9HCtvfvY4cOZsLwtZqXwV|zb z@^rKHhKdUmv=^SV-z=BI_HvumY8#1OZER&5g%52?6~)uCBQbB!6ufI&h4)OuiDD^A z$4{QJe&k{&c%O8yA`LoS1GP{<_8lk1vm1xYwEHzL3h&I-UmqCp`&V%jOnq)=*Yw0* z?+TZ->&cdG-HziIc&wM7^?Ljsd&#Ge&y`@v&^ye8$( zDft96y-^pAb%@;xiA-Lj-JfrQn+^`sJZ0o7=&W`sp^zJ(8X9*1gMD#;$n2*XSF_Y9 zyXrcYATlE z7djdvxqOH^Xp zv1g4+?Eitvm57W477syxC^D?~*}TaDjG-%5`Q)9-m+c zI1B2zYFm*pJAG;Jn-5m;eGs|%B7W=ZSXWVgUPF3BIszz%yG*Iw5y(--or*x6{*`!^ z!Yju*N%IA>;-rDEB552)zYb? zW-n?~-R@o1b6L=EB?Mwq-Fc#yTMSy8A4xezPr`raZC@(OAL%s{6DTS@r}{W@r;Wbo z{s7k6Y@%aabr4*8=wG~9QZIE5Byv8)7r{jz56nuP~&EnJ4rQ%r!}sbPW>% z{w_2<;dsFxhv!x)xFx6#^V`F_c?)9xs|_jIs?Xl)4+Ga#XxwOI{>n<;AOD-u6sYlk zFJN^?sbYxPy@)ldW<`Ca9mKq2;y`M>0pB}TXrt2-z(o6?n`fC0KN6+X9@yXgsKrr~ LQ-zevyhZ#M&R?v8 literal 7684 zcmcI}Wk6J2*Y?cNjfkYcfFLm_l8V$IjYxMlNO#BFf^-asNFyN~GQiL+Fp7Y5cPRr5 zAzklqKhOK?d;fkv&e`j%eXZDg?Y+-g>qM)oDv%K~5CZ@JG9^V>O#lFh!qUcs_}Ke4 zD+dX-A#zhR@B{$Jd+sh^ziWv#0KkM)l9kr>o88SONYj>UjyQE`3>=GS8D4A&)RT69 z!`02k^)3q6!~@Ptk9SWxISNAZE}Vla+0zy!D!85{NGd2p7eOEKU39B?t6b3g#X@j% zaWSXx-l^2^sfw>o_OIgM@xYsoo3fke&m+p;T_Pi*og))s@X#XB!YwB;5TLKDhb>Y@ zEG4^bh4bkdUgNx(lShWr7A^G-!o))!IUTxP(dhmhWz+Ke8+B6$j37^BW6EXe!^TMT zch!bL)FADa%VP5GD8kbuSFLNScB+Z7VZEl?a&`S?eqnF55^(QqIZsh#7$I(|uhQz| zduq`*<{3c81Ubo-grqvnKAzk?dGHCxCAKh*w|MtpVJ5f#Jo9GO_W=5h{foZK2Y4#? z+(N7G5?7C%2xjf?1Y@_}W!2}}DJCgL70ojD%2CflV^g~$w|Bjsv5Ug zrX(|Tmz&*_g6Q3cH0)BB`oMBJdA8wZrb77G3MB6 zu>_ncvfK@L+TqC%4=enb*_CgSnufKRl>TYR&HTXXRJATG zYtqOUe3Y^`BLhw8tUV-Je^|z}H)e~^Jkqm@QOqmuzNp|NQk8XW>1#5T=f~r#+czqTehYQE}_=g96dsGFc@AAnLqQK|8gO zF|k_2C3L)iU9B;C%UT-RMgypF`LxhWmE+BwYkLxwg>!RwxREht_KS?OU~5A<@QQPO ztMVGn=v?j65|B)>6hq|XHW5l309;x$WqwBEfkEbbH>dN zxE3!y7ySLXBlBqwbT~M2&LFSS@>bDpZxGn=s;_^0PR=rFwjJ#aX1 z(~!May}*aXQa>Kt+Awokiis_z$#bNIZv?PFX z_Q{8AAI*Rw?8@905Z@vKyUw@pA(_N^%%m=TaFT2pZh789O6y6Edi!S?KOTjgz3;4sHXo$N!k+y0Rj)+*yIq&j{!t*LAiwZQyAF z5)gkXjl9$Y4AiOV!HnCzTY{cRPe06YcnhSOyk)!=R5L$< zVOg~SZM>Ov<~6UO@_n2V_=QCJ#PEIn3IFl8QwEUGr8EzZtJMdYM_gpkGRJpo7L4rN zO&J|Z1=y`t{ng@rF>1)MmfNC`3--+m_%qL|hzW1a7^}6(qh3`W>|4^7hCiMQZbn6&$rjH^=#*Ener$>7fHxJu#2@ z=!1<61>3Rv=xx9T-&HYoO6oX!zP2*Hta|Az*5dTs`pTM21c$7;pb_@BgJo629?1nu zVe*u%%gP6V&7+0Y`oOQZyYhyYC?p`O!qVNR}?Sm4g`%c#lK*{pZQTrjM-{Id8N818^jfxEGjyJv^DEi_D$-3#$pvpo z_soOXCHY>BpiamFs4Z|5nhNK1gOwV#wuL=jqzXd2RZW|!synTGb8y+QLmg3x=>0O` z73(YJeg9H5i-y(}FXp;;M=3ERK-}QzH%}!jR?EWD)ou%jdzuE#hcMSj?|nM&EI)E~g)j9_d1Uo2QFjR!nyT zh#D;!5+o@+AV4MTxh-UunwwC@4f;%L%|GPJ^=e^XDF@}z2}EZauJ!2&kU0{#2t zkn->i_nDI5e`faS&TMv|N4iMGyHytgMF=AfX<}9j`_p;SuUFZscL5)<7h;INi^2b| z=5DZ93;$;W!GlITzm$^xShYD3sw1w`#;?<-tf=^zE9OuimmR1C)ufiY2fw2In5cKu)2OkO^LhBcTCfK1%>S>#|2l_#d^-v`o{d(nH>7rC@%-dUt)ON` z%_K=L4b?QQo}Sjf_uv7?>e|}tprw(Kk=|LK{sgAg<*u-+oOyK>6_upf)!DtdhUzOo zI@3DmVe+a|3L`By0%JjSS&oq7+hm)O|)y{u#R!0gHI6UHHBFVpBo3;f^6zo1?V_QENNPTj2 zfu^UTVjF%iRV$tldb(%gfAuC)q-lbgLe{>+ZKeAV$J)g!ZHMb<6TvXY*Ow= z)`5CclW*jXrbW+`=V~>rO<%I*OI2ML0IbKVaNZ;S#;F|qMZYmdhMo*=vWap`Ocw>` z^2ImPZr4q=CyOVgr*o7EZ)+F9=6%=e{=SSww^6~X-VYf9R}MD@dO@T)oq3#Ob^CNv z)nRzpeJh_O|*T#UYlLmn~WBdLqa?LOE5l&PH%Xr>3SFt3fz816OCy zT^CdO`?SkB$>B9-^|h6b2X%^b9jcLbsz^fDbFcI+F0pAd=I6FJVdp`fgEXYb!QNWS9!x zi)$URdHl!JI<;iEJGRwtPz^~E8->8NR*HiC9a3GnxxPeb>9+;c?;ji_G0jRbf@Nf6 z;@!#w0nCy?`bdKY7n7zO^aqe)c$s?+p&Fk7LiT62*e4k8Q}Bu~DB)|A$VX-z-sNPJ z+G`qjGhesq$7??&4QQp#wK5NXw|mREOiLu?rn4T_*4B0WVS7ENX8ma@PDC`%cOAK% zc1~Y<7<`yqgIwO*Pe!$RY47z+PYrsKZzX#lRfObNHAnAs4zNiix!n zV$VX~_*!31F?iMCuCI#@*aYk9BE!#iKU4nsGRvYyZz^*<8^k;5y2NKzJ-3P8H{fZT zdByIKOS@uem-`7;H-TNuHpOm#Mmgy5d(drxr-sOdTDwG2(fp%1giUOkpk2bw6@V_K z+>O0(v0#j!Q`%gdFq^|Xe78~@iJ06R%-j&o757K{8oDpQIkWt7*>kbkdjR{{68L#& zX=#aTt}U>^oosi`ti|U|0?u0V^2O(c_awB~*AcU7>QfV*25I}+Rt)5Ua(nP4@`cY% zT2S!GjzOjf1&d_LR*UT*L-7*HHfFxbYAqP2c{7kO$3a~k-gDBJTXQ{I@A#yrnuLb; z*~vfdqXZPpe$T-L0d0AwQT|PAtvbE^U=nh8uY18uF{cmy=V=wM&s;uh;Hy!B!#2cQ zA^o&R$*bOkMeH#p?`ryfLi+21FBzHKU1Xzza$k6+5!Ja4$g=qxx+hVk_(#`V1Uj3o zJb{SgpD&;<_yxvTFoHZ4^8k7(8rF;WG_YB&c<2OT7jZ0ReZBJq-CKGz#S2XTkO>)p zOa_sn+I#v3!kP6gJz^T&Ce&^Q2M0cUm;PEt#so}HsYvu*oLUYZ#2~{3dBp|#?9sG- zYPj=cB2_C)WS{-aTIAG5f|pC)os1-;BL+lJ&A|$4V_{x`gEq7=arGC^@K4%wOVeS@P zmF%9bjM1E43f<0_m0~ab#xD*_XZgBsx**t?+SottT^rij6&9_N{Hs)}I8L!2dkAgF zFoKEv$-o~r6R8f?0?`?xW`%pkkMH{kwMG@bFK|Y3^ME~jSbp0qQBbW6=8m>O2OqtW zs?(s5$eBU=L_Np>^UiFEiK_Ap2>TVYEEZzWXFdebRnKohBbu^xfu^!S`^HT`0bX(C z*1tZCI6{0KNB_KOA<5u0r=OuAdBU)SZ{hs)! z$0qo68!%=VvAS;66!I4(+OytJkwQStaj8T`D)VGTgC7w*m)H&lFZ_yUd~O{H8=4N` zC08380z?;kSQB zr~{ya?ApbZRN*_oXh)>`n*5_HG+CbW20uu)jMVL0+dXDx7ts^P6GYz8*6@Hw?ejRo zh#$(rOTK=M-4fB)JyuUh;jrgFR*uWj?*nNU+ok}cqjjo$Hb>%-&N0M_Omz(V_(FAL zEhp7{qbJo5zB{)2lEB>`iT&{WLDY0jmw*?uqdy~LYWN)=u0$dq*h|L_%R@+8m?dPo zjJcdOjM<#u6xgj_W&(W|K-QQ%tM$vkE()gZD%1;aJQNGf*$bxJhe8fzMpp3HASCK= zw_U;cM=hdOE@!h#H>CIp@J|HNd2aKUXp{1w9!xf>yu=FbU~~y*MRtu7pyvO4{Gx5& z_*K1n`{$q=ga}$j$@i+_ zBennH{4qqvda0c!S?@|ltPkRXBXEtfT}hA&(eK5FyITeN;enHc!cNl&=$VQWyF~17 zqj}3urCEq1*$=h3_CYe;-3!>Xa8u&&I-LG>45?x{uPzQ;ir0e#KHNTH{FtE2sA3H9 zi8pXxUF!5y?3fe0K)Jb68vYPA$SceoQ5*t+&VFjaQEZj+SdZf04DTRp$@|It&E|<$ z)C*4b_(q4~KK;RLM|bRo*d%>eh0q`D7GiR3sve)_PHx}9K*HrUXelYpYsp%zj=VJ& zHxJhOnKp%*GoI$Z+PKSG$__ls`CT!?mhY#K@~60P=o&XT-a4E0jT~ZFJIVXH4yC3x z^1*hMP44FwriB2tHh1hfl8}Vh3@nbZzqG5ywRQr6^KQ)EjNjK!_)Wx%!hav1+dgd{ zrlRW$XmS{HIS*O=`DFv7XrDDngG4VA5vjyk+1hMT2P&haNWg=hEGFL{A|d49!5mx! zeGMNwnoEDyH^YY8kK_Ft+vl7qDb~pepu(1H1U-D8O~)gl_N%YKYyYq+YYblZWly~h zWD(pNbBVg@%m+kU{L>96-J4(3hP9s2KRWMCt4|c)*dAjK2N_fnV*O z%F(k(!3i&XujP|DE;YcJ`&vSXu6Fbq^Vja)O_IIFlY_VMGt(-wFa%zho7w50aXt+}DG}84Z zMZQIa5z1yEUxi^f=wz*Vd{G14x{Hzf_<8dIEG~CHDQZVkM{ml(!!mSxv#BqjA}!0+&tvY` z&2X}kL?BT6-PheCHusyQ_@U_?=^x*3Ju>>C_eQqq+k(+{vWJI6c+Be$=AP|-T)WUt zu(|KGT<^Ed|98(=uqc8qmek(aQfS(;mHODhK-`sp9rjV{neV;Hs$sW80MP<5X|j!D z!|sQUlYbC;=muIzqmvXmaFRBzqyDq(o{**%HInYW!^K~%7M>s~;GcQNu&t<}qOW+3 zIxkzdy-0A_#ldz&l04Kze1r2{rRyYsA7yJ0NNEBirJk0)TO}ox?ih`m#7>*I*I$R2 z_ZdggB6?NE{GDdlitZp6hJZQmS~lRQj?s<;Z)d5aLlK>j+BdHd6bUakgLH+52v_#s zv};pPPerx*?rb_!0dF>A&`(r8KDL!JD;?-Se}vQYAL; zv8;YmyWE!<{q!!p=I~1{MX?YsAj(s=`!B0n90Acn8;b)ko4J`DH5B<;Lb?TguY|n)8I*SKfhx}(xet)+-|A7az@m8)Us_m`mDfg4uk_fpyMf|f={mehW9&z7`K zNOr|=*o%frFf?4W0+hlM*DJPVR!6Ua1+sN;ak$4lxSmacK9e(tQmYB?Ee-&8`d$KVkKXMDlo2 zni4M>4NeJi8h<{Dh=e8di65A}>5~`T9%;(!5p3Xg?Fy7ti1!S0t0Fc|D}O7Dj7ea& zN^)TABvY47W0)Gqg5knG|J?ouut?<(mKC*$OK#NGdBn88gvieO!v|#*Fo=~LqYnd>GP|ieQs}aMS3<90)4wdRxVO4$8Trn_q@J4ebp|iYf z?~j8H%vNKsvt4=9;fPYo6fr%7j@(C3| zaaf1B|D}>)gjISIRAsAPnZNrV D_hG`g diff --git a/wiki/img/attack/chain-priority-options-default.png b/wiki/img/attack/chain-priority-options-default.png index 75b609d24c765ea1528294754ab65b8aecc7ac71..ee465b1e2232849397be0e13b54f51f5a7b1f090 100644 GIT binary patch literal 8378 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 A3jhEB literal 8410 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 Z(5!3S4yTkn{;U8{R#aE0lCyaGe*iaE_?`d& diff --git a/wiki/img/attack/chain-priority-options-none.png b/wiki/img/attack/chain-priority-options-none.png index 3b867277a94afeda4ff5c748adbea01c4cf751a1..fdc3b66579282b72b2f77da6bc51aa4c254c12ce 100644 GIT binary patch literal 6406 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 literal 6471 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 Date: Thu, 18 Sep 2025 02:36:32 +0800 Subject: [PATCH 109/124] Update documentation (part 2) --- wiki/img/attack/avoid-chain-flowchart.png | Bin 0 -> 38533 bytes wiki/img/attack/brave-chain-flowchart.png | Bin 0 -> 41331 bytes wiki/img/attack/color-chain-flowchart.png | Bin 0 -> 29044 bytes wiki/img/attack/mighty-chain-flowchart.png | Bin 0 -> 35061 bytes wiki/scripts/Battle.md | 63 ++++++++++++++------- 5 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 wiki/img/attack/avoid-chain-flowchart.png create mode 100644 wiki/img/attack/brave-chain-flowchart.png create mode 100644 wiki/img/attack/color-chain-flowchart.png create mode 100644 wiki/img/attack/mighty-chain-flowchart.png diff --git a/wiki/img/attack/avoid-chain-flowchart.png b/wiki/img/attack/avoid-chain-flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..d14675c78a10aff24e407ab4dd93533e100cfb04 GIT binary patch literal 38533 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 UtuxzC@C=ELhJkvmnq9>I0~bL@C;$Ke literal 0 HcmV?d00001 diff --git a/wiki/img/attack/brave-chain-flowchart.png b/wiki/img/attack/brave-chain-flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..549d0e6bbde8ae507e09c4f51158f185cc0e10cd GIT binary patch literal 41331 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%0Ar=b{Q8$5J>TKIqq9%&HH?>B? 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 B!jAv| literal 0 HcmV?d00001 diff --git a/wiki/img/attack/mighty-chain-flowchart.png b/wiki/img/attack/mighty-chain-flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..c5d993a4038aeffeda8266f3f46738b9b88c5454 GIT binary patch literal 35061 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 literal 0 HcmV?d00001 diff --git a/wiki/scripts/Battle.md b/wiki/scripts/Battle.md index 2af6f77f1..b8186f7bd 100644 --- a/wiki/scripts/Battle.md +++ b/wiki/scripts/Battle.md @@ -41,9 +41,7 @@ - [Chain Priority](#chain-priority) - [Interaction with other priorities](#interaction-with-other-priorities) - [Mighty Chain](#mighty-chain) - - [Buster Chain](#buster-chain) - - [Arts Chain](#arts-chain) - - [Quick Chain](#quick-chain) + - [Buster / Arts / Quick Chain](#buster--arts--quick-chain) - [Avoid Chain](#avoid-chain) - [Rearrange Cards](#rearrange-cards) - [Brave Chain](#brave-chain) @@ -458,9 +456,11 @@ Chain Priority is a new feature that allows for attempts at $${\color{black}Migh | ![Chain priority options buster](../img/attack/chain-priority-options-buster.png) | $${\color{red}Buster}$$ focused | This order disables all chains except $${\color{red}Buster}$$ (highest priority) and $${\color{black}Mighty}$$ (second priority). Failure to execute any of these chains will result in no other chain being (actively) used. | | ![Chain priority options arts](../img/attack/chain-priority-options-arts.png) | $${\color{blue}Arts}$$ only | Only $${\color{blue}Arts}$$ chains will be attempted. All order chains are ignored. | | ![Chain priority options avoid](../img/attack/chain-priority-options-avoid.png) | `Avoid` only | All chains will be avoided, unless it is absolutely impossible (e.g. all cards are $${\color{green}Quick}$$ cards). | -| ![Chain priority options none](../img/attack/chain-priority-options-none.png) | None | This completely skips all checks. It is almost the same as turning Chain Priority off. Why? | +| ![Chain priority options none](../img/attack/chain-priority-options-none.png) | None | This completely skips all checks. It is almost the same as turning Chain Priority off, except that it uses Chain Priority's Brave Chain handling instead of the default. | -| Flow chart of entire system | +The way Chain Priority works is that it checks for Chains in the order that is shown (from left to right). If it finds a chain, then it will use that chain immediately. If Chain Priority is on and is using the `Default order`, it will attempt to check for a Mighty Chain first. If there is no valid Mighty Chain, then it will attempt to check for a Buster Chain. If there is no valid Buster Chain, it will attempt to check for an Arts Chain and so on and so forth. If, however, an Arts Chain was found, it will skip checking for a Quick Chain using the cards and will immediately use the Arts Chain. + +| Flowchart of entire system | |:-------------------------------------------------------------------------:| | ![Attack Priority flowchart](../img/attack/attack-priority-flowchart.png) | @@ -472,42 +472,67 @@ In general, the priority of card handling is in this order: Brave Chains > Chain Priority > Servant Priority (if on) > Card Priority ``` -This means that the [Brave Chain](#brave-chain) option is of the highest importance. This interaction with Chain Priority will be elaborated in the corresponding sections. +This means that the [Brave Chain](#brave-chain) option is of the highest importance. This interaction with Chain Priority will be elaborated on in the corresponding sections. + +| Simplified Brave Chain flowchart / decision tree | +|:-----------------------------------------------------------------:| +| ![Brave Chain flowchart](../img/attack/brave-chain-flowchart.png) | + +Please refer to the [Brave Chain](#brave-chain) section for more information on what each Brave Chain option entails. #### Mighty Chain -TBA +| Simplified Mighty Chain flowchart | +|:-------------------------------------------------------------------:| +| ![Mighty Chain flowchart](../img/attack/mighty-chain-flowchart.png) | -#### Buster Chain +Mighty Chains, by definition, require all 3 card types in a single chain to be executed. Mighty Chains can be done with multiple Servants or with a single Servant (thus forming a Brave Chain). The order of Mighty Chain cards is currently determined using the [Card Priority](#card-priority-optional). Thus, the default order is BAQ (Buster, Arts, Quick). -TBA +

Advanced note -#### Arts Chain +Since the default order is determined using Card Priority, it also takes into account weaknesses. Thus, if a weak card happens to be an Arts card and there is no weak Buster, since Weak Arts is before a neutral Buster, the Mighty Chain will be ABQ. -TBA +This slightly strange interaction is something to note when preparing the configuration, though it should rarely occur. + +
+ +| Brave Chains options | Interactions | +|:---------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Don't care | The default output of Mighty Chains will be used, regardless of whether a Brave Chain is used or not. A Mighty Chain will be used if there is one available, otherwise it will skip to the next Chain option. | +| With NP | Will always attempt to make a Mighty Brave Chain only when a single NP is being used.
If there is both a Brave Chain and a Mighty Chain separately available, the Brave Chain will be used over the Mighty Chain. | +| Always | Will always attempt to make a Mighty Brave Chain if one is available, regardless of the Servant.
If there is both a Brave Chain and a Mighty Chain separately available, the Brave Chain will be used over the Mighty Chain. | +| Avoid | Will actively avoid making Brave Chains. If the only Mighty Chain is a Mighty Brave Chain, it will be skipped. | -#### Quick Chain +#### Buster / Arts / Quick Chain + +| Simplified Buster / Arts / Quick Chain flowchart | +|:-----------------------------------------------------------------:| +| ![Color Chain flowchart](../img/attack/color-chain-flowchart.png) | TBA #### Avoid Chain +| Simplified Avoid Chain flowchart | +|:-----------------------------------------------------------------:| +| ![Avoid Chain flowchart](../img/attack/avoid-chain-flowchart.png) | + TBA ___ ### Brave Chain -There are only 4 options for brave chains. +There are only 3 options for brave chains, unless [Chain Priority](#chain-priority) is on. ![Battle Configs - Card Priority | Brave Chain options](../img/attack/brave-chain-options.png "Battle Configs - Card Priority | Brave Chain options") -| Option | Description | -|:-----------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Don't care | Will not attempt to form nor avoid any Brave Chains and will simply use whatever order other priorities have sorted in. Thus, there can be the occasional Brave Chain that occur. | -| With NP | Will only attempt to use Brave Chains if a single NP is being used. If no NPs are used, Brave Chains are ignored. | -| Always | Will always attempt to make a Brave Chain if one is available, regardless of the Servant. | -| Avoid | Will actively avoid making Brave Chains if one is available, unless it is absolutely impossible (e.g. all cards belong to a single Servant) | +| Option | Description | +|:----------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Don't care | Will not attempt to form nor avoid any Brave Chains and will simply use whatever order other priorities have sorted in. Thus, there can be the occasional Brave Chain that occur. | +| With NP | Will always attempt to make a Brave Chain when a single NP is being used. If no NPs are used, Brave Chain checking is ignored. | +| Always (only if ([Chain Priority](#chain-priority) is on) | Will always attempt to make a Brave Chain when one is available, regardless of the Servant. | +| Avoid | Will actively avoid making Brave Chains if one is available, unless it is absolutely impossible (e.g. all cards belong to a single Servant) | ___ From 08fe2fd203c15d046eaf91ddef4ff8c24a649994 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 18 Sep 2025 03:04:59 +0800 Subject: [PATCH 110/124] Readd forceBraveChain so that in the case where there is no Brave Chain available, 'Always' will return a valid chain --- .../attack/CardChainPriorityHandler.kt | 2 + .../modules/attack/ColorChainHandler.kt | 3 +- .../modules/attack/MightyChainHandler.kt | 5 +- .../CardChainPriorityIntegrationTest.kt | 20 ++++ .../scripts/attack/ColorChainTest.kt | 108 ++++++++++++++++++ .../scripts/attack/MightyChainTest.kt | 102 ++++++++++++++++- 6 files changed, 237 insertions(+), 3 deletions(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt index 74f2ffcb9..623adfee2 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/CardChainPriorityHandler.kt @@ -47,6 +47,7 @@ object CardChainPriorityHandler { braveChainEnum = braveChainEnum, cardCountPerCardTypeMap = cardCountPerCardTypeMap, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, + forceBraveChain = forceBraveChain, ) ChainTypeEnum.Arts, ChainTypeEnum.Buster, @@ -63,6 +64,7 @@ object CardChainPriorityHandler { braveChainEnum = braveChainEnum, cardCountPerCardTypeMap = cardCountPerCardTypeMap, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, + forceBraveChain = forceBraveChain, ) ChainTypeEnum.Avoid -> AvoidChainHandler.pick( cards = nonUnknownCards, diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt index 4ec968a8b..a12245646 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/ColorChainHandler.kt @@ -18,6 +18,7 @@ object ColorChainHandler { braveChainEnum: BraveChainEnum = BraveChainEnum.None, cardCountPerFieldSlotMap: Map? = null, cardCountPerCardTypeMap: Map? = null, + forceBraveChain: Boolean = false, ): List? { // NEVER want to make a chain of Unknown cards if (cardType == CardTypeEnum.Unknown) return null @@ -67,7 +68,7 @@ object ColorChainHandler { // If there is a valid number of cards for the fieldSlot, attempt to Brave Chain // Even if it is not valid, if BraveChainEnum.Always, // it only accepts Brave Color Chains and not normal Color Chains - if (fieldSlotList.size >= cardsNeeded || braveChainEnum == BraveChainEnum.Always) + if (fieldSlotList.size >= cardsNeeded || forceBraveChain) selectedCards = fieldSlotList } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt index 80441f358..9ac27a769 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/MightyChainHandler.kt @@ -20,6 +20,7 @@ object MightyChainHandler { braveChainEnum: BraveChainEnum = BraveChainEnum.None, cardCountPerFieldSlotMap: Map? = null, cardCountPerCardTypeMap: Map? = null, + forceBraveChain: Boolean = false, ): List? { // Try to ensure unknown is handled val nonUnknownCards = AttackUtils.getValidNonUnknownCards(cards) @@ -54,6 +55,7 @@ object MightyChainHandler { uniqueCardTypesAlreadyFilled = uniqueCardTypesFromNp, braveChainFieldSlot = braveChainFieldSlot, braveChainEnum = braveChainEnum, + forceBraveChain = forceBraveChain, cardCountPerFieldSlotMap = cardCountPerFieldSlotMap, ) @@ -73,6 +75,7 @@ object MightyChainHandler { // In case of a Brave chain, we want to know what slot it is braveChainFieldSlot: FieldSlot? = null, braveChainEnum: BraveChainEnum = BraveChainEnum.None, + forceBraveChain: Boolean = false, cardCountPerFieldSlotMap: Map? = null, ): List? { @@ -99,7 +102,7 @@ object MightyChainHandler { } // Even if it is empty, if BraveChainEnum.Always, // it only accepts Brave Mighty Chains and not normal Mighty Chains - if (fieldSlotList.isNotEmpty() || braveChainEnum == BraveChainEnum.Always) filteredCards = fieldSlotList + if (fieldSlotList.isNotEmpty() || forceBraveChain) filteredCards = fieldSlotList } val filteredCard = filteredCards.firstOrNull() if (filteredCard == null) break // If cannot find, leave diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 599384294..fd15cad83 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -471,6 +471,26 @@ class CardChainPriorityIntegrationTest { assertThat(picked).containsExactly(CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E) } + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes using default chain`() { + val cards = AttackLineUps.Standard.lineup1 + val picked = AttackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + ).map { it.card } + val pickedDefault = getDefaultBraveChainResult( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + ).map { it.card } + + assertThat(picked).isEqualTo(pickedDefault) + } + @Test fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with rearrange & npTypes`() { val cards = AttackLineUps.Standard.lineup1 diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt index 09c416060..be459f0c7 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/ColorChainTest.kt @@ -10,6 +10,7 @@ import io.github.fate_grand_automata.scripts.enums.CardTypeEnum import io.github.fate_grand_automata.scripts.models.CommandCard import io.github.fate_grand_automata.scripts.models.FieldSlot import io.github.fate_grand_automata.scripts.models.NPUsage +import io.github.fate_grand_automata.scripts.modules.attack.BraveChainHandler import kotlin.test.Test import io.github.fate_grand_automata.scripts.modules.attack.ColorChainHandler @@ -99,6 +100,39 @@ class ColorChainTest { when (cardType to braveChainEnum) { CardTypeEnum.Quick to BraveChainEnum.None, CardTypeEnum.Quick to BraveChainEnum.WithNP, + CardTypeEnum.Quick to BraveChainEnum.Always, + CardTypeEnum.Quick to BraveChainEnum.Avoid, -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.D, + ) + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } + } + } + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes + forceBraveChain`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = ColorChainHandler.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + + when (cardType to braveChainEnum) { + CardTypeEnum.Quick to BraveChainEnum.None, CardTypeEnum.Quick to BraveChainEnum.Avoid, -> assertThat(result, "$braveChainEnum, $cardType").containsExactly( CommandCard.Face.B, @@ -388,6 +422,36 @@ class ColorChainTest { } } + @Test + fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + forceBraveChain`() { + val cards = AttackLineUps.BusterFocus.lineup01 + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = ColorChainHandler.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + + when (cardType to braveChainEnum) { + CardTypeEnum.Buster to BraveChainEnum.None, + CardTypeEnum.Buster to BraveChainEnum.Always, + CardTypeEnum.Buster to BraveChainEnum.WithNP, -> + assertThat(result, cardType.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } + } + } + } + @Test fun `BusterFocus - lineup1 (1KB,2KB,3NA,4NA,5KB) + 1 NP (2Kiyohime), with npTypes`() { val cards = AttackLineUps.BusterFocus.lineup01 @@ -454,6 +518,50 @@ class ColorChainTest { } } + @Test + fun `BusterFocus - lineup05 (1KB,2KB,3NA,4NB,5KB)`() { + val cards = AttackLineUps.BusterFocus.lineup05 + for (braveChainEnum in braveChainEnums) { + for (cardType in cardTypeList) { + val result = ColorChainHandler.pick( + cards = cards, + cardType = cardType, + braveChainEnum = braveChainEnum, + )?.map { it.card } ?: emptyList() + + when (cardType to braveChainEnum) { + CardTypeEnum.Buster to BraveChainEnum.None, + CardTypeEnum.Buster to BraveChainEnum.WithNP, -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + CommandCard.Face.C, + ) + CardTypeEnum.Buster to BraveChainEnum.Always,-> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.E, + CommandCard.Face.C, + CommandCard.Face.D, + ) + CardTypeEnum.Buster to BraveChainEnum.Avoid, -> + assertThat(result, "$braveChainEnum, $cardType").containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.C, + CommandCard.Face.E, + ) + + else -> assertThat(result, "$braveChainEnum, $cardType").isEmpty() + } + } + } + } + /** * Unknown handling */ diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt index 206ab49f8..df434537f 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/MightyChainTest.kt @@ -118,7 +118,32 @@ class MightyChainTest { ) )?.map { it.card } ?: emptyList() + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.B, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 2Scathach-NP, with npTypes + forceBraveChain`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = MightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + when (braveChainEnum) { + BraveChainEnum.WithNP, BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).isEmpty() else -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -192,10 +217,35 @@ class MightyChainTest { npUsage = NPUsage(setOf(CommandCard.NP.C), 0), npTypes = mapOf( FieldSlot.C to CardTypeEnum.Arts - ) + ), + )?.map { it.card } ?: emptyList() + + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + CommandCard.Face.E, + ) + } + } + + @Test + fun `Standard - lineup1 (1SB,2KQ,3NA,4NA,5SQ) + 3Nero-NP, with npTypes + forceBraveChain`() { + val cards = AttackLineUps.Standard.lineup1 + for (braveChainEnum in braveChainEnums) { + val picked = MightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + forceBraveChain = true, )?.map { it.card } ?: emptyList() when (braveChainEnum) { + BraveChainEnum.WithNP, BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).isEmpty() else -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -245,7 +295,32 @@ class MightyChainTest { ), )?.map { it.card } ?: emptyList() + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.C, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.D, + ) + } + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 2Scathach-NP, with npTypes + forceBraveChain`() { + val cards = AttackLineUps.Standard.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = MightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.B to CardTypeEnum.Quick + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + when (braveChainEnum) { + BraveChainEnum.WithNP, BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).isEmpty() else -> assertThat(picked, braveChainEnum.toString()).containsExactly( @@ -308,7 +383,32 @@ class MightyChainTest { ), )?.map { it.card } ?: emptyList() + assertThat(picked, braveChainEnum.toString()).containsExactly( + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.B, + CommandCard.Face.C, + CommandCard.Face.D, + ) + } + } + + @Test + fun `Standard - lineup2 (1SB,5SQ,2KQ,3NA,4NA) + 3Nero-NP, with npTypes + forceBraveChain`() { + val cards = AttackLineUps.Standard.lineup2 + for (braveChainEnum in braveChainEnums) { + val picked = MightyChainHandler.pick( + cards = cards, + braveChainEnum = braveChainEnum, + npUsage = NPUsage(setOf(CommandCard.NP.C), 0), + npTypes = mapOf( + FieldSlot.C to CardTypeEnum.Arts + ), + forceBraveChain = true, + )?.map { it.card } ?: emptyList() + when (braveChainEnum) { + BraveChainEnum.WithNP, BraveChainEnum.Always -> assertThat(picked, braveChainEnum.toString()).isEmpty() else -> assertThat(picked, braveChainEnum.toString()).containsExactly( From 0cd49f0d9ddb4ec4b58b4fc6af0291c4d3cf0b62 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 18 Sep 2025 03:08:52 +0800 Subject: [PATCH 111/124] Add encasing box around advanced note --- wiki/scripts/Battle.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wiki/scripts/Battle.md b/wiki/scripts/Battle.md index b8186f7bd..2dcc4a3c8 100644 --- a/wiki/scripts/Battle.md +++ b/wiki/scripts/Battle.md @@ -490,9 +490,11 @@ Mighty Chains, by definition, require all 3 card types in a single chain to be e
Advanced note -Since the default order is determined using Card Priority, it also takes into account weaknesses. Thus, if a weak card happens to be an Arts card and there is no weak Buster, since Weak Arts is before a neutral Buster, the Mighty Chain will be ABQ. +``` +Since the default order is determined using Card Priority, it also takes into account Weak and Resist. Thus, if a weak card happens to be an Arts card and there is no weak Buster, since weak Arts is before a neutral Buster, the Mighty Chain will be ABQ. This slightly strange interaction is something to note when preparing the configuration, though it should rarely occur. +```
From 81ac38c5f085969331491faacb51983188d38ab0 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 18 Sep 2025 03:10:23 +0800 Subject: [PATCH 112/124] Update advanced note --- wiki/scripts/Battle.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/wiki/scripts/Battle.md b/wiki/scripts/Battle.md index 2dcc4a3c8..9f9171042 100644 --- a/wiki/scripts/Battle.md +++ b/wiki/scripts/Battle.md @@ -490,11 +490,8 @@ Mighty Chains, by definition, require all 3 card types in a single chain to be e
Advanced note -``` -Since the default order is determined using Card Priority, it also takes into account Weak and Resist. Thus, if a weak card happens to be an Arts card and there is no weak Buster, since weak Arts is before a neutral Buster, the Mighty Chain will be ABQ. - -This slightly strange interaction is something to note when preparing the configuration, though it should rarely occur. -``` +> Since the default order is determined using Card Priority, it also takes into account Weak and Resist. Thus, if a weak card happens to be an Arts card and there is no weak Buster, since weak Arts is before a neutral Buster, the Mighty Chain will be ABQ. +> This slightly strange interaction is something to note when preparing the configuration, though it should rarely occur.
From b1a03027b91ece0cd5fcb08b835bd552bf64ea15 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 18 Sep 2025 23:50:58 +0800 Subject: [PATCH 113/124] Update documentation part 3 --- wiki/img/attack/attack-priority-flowchart.png | Bin 252169 -> 105839 bytes wiki/img/attack/brave-chain-menu.png | Bin 0 -> 7759 bytes wiki/scripts/Battle.md | 54 ++++++++++++------ 3 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 wiki/img/attack/brave-chain-menu.png diff --git a/wiki/img/attack/attack-priority-flowchart.png b/wiki/img/attack/attack-priority-flowchart.png index 17bc4b3ac11409fca3e49e2dc3a4e5b387e7561c..653891385d4553da578cf901c8bcb79bd7ce718a 100644 GIT binary patch literal 105839 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

7X;OeUx~_E&F4geG!Tl#)Y`{KM*W!}I#f5*TS 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%0DcAsrS&r8yevRA3R($3kB}slwjWs&l<3o03 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

hZ8gn(!Ze&JusD4V-%97W>_53-ywe4H1M@tEz` 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�I)D(Mmk5(4^&aTVW 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 diff --git a/wiki/img/attack/brave-chain-menu.png b/wiki/img/attack/brave-chain-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..a9fe86660a8a63d94b0319403d5eac45b016390a GIT binary patch literal 7759 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 literal 0 HcmV?d00001 diff --git a/wiki/scripts/Battle.md b/wiki/scripts/Battle.md index 9f9171042..8f9db3096 100644 --- a/wiki/scripts/Battle.md +++ b/wiki/scripts/Battle.md @@ -450,17 +450,19 @@ ___ Chain Priority is a new feature that allows for attempts at $${\color{black}Mighty}$$, $${\color{red}Buster}$$, $${\color{blue}Arts}$$ or $${\color{green}Quick}$$ Chains, based on the order that the chains are chosen in. There is an additional option called `Avoid`, which when used attempts to avoid any of the other chains. -| Sample orders | Name | Description | -|:------------------------------------------------------------------------------------|:--------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ![Chain priority options default](../img/attack/chain-priority-options-default.png) | Default order | The default order used when Chain Priority is turned on. This disables Avoid Chain. | -| ![Chain priority options buster](../img/attack/chain-priority-options-buster.png) | $${\color{red}Buster}$$ focused | This order disables all chains except $${\color{red}Buster}$$ (highest priority) and $${\color{black}Mighty}$$ (second priority). Failure to execute any of these chains will result in no other chain being (actively) used. | -| ![Chain priority options arts](../img/attack/chain-priority-options-arts.png) | $${\color{blue}Arts}$$ only | Only $${\color{blue}Arts}$$ chains will be attempted. All order chains are ignored. | -| ![Chain priority options avoid](../img/attack/chain-priority-options-avoid.png) | `Avoid` only | All chains will be avoided, unless it is absolutely impossible (e.g. all cards are $${\color{green}Quick}$$ cards). | -| ![Chain priority options none](../img/attack/chain-priority-options-none.png) | None | This completely skips all checks. It is almost the same as turning Chain Priority off, except that it uses Chain Priority's Brave Chain handling instead of the default. | +| Sample orders | Name | Description | +|:------------------------------------------------------------------------------------|:--------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ![Chain priority options default](../img/attack/chain-priority-options-default.png) | Default order | The default order used when Chain Priority is turned on. This disables Avoid Chain. | +| ![Chain priority options buster](../img/attack/chain-priority-options-buster.png) | $${\color{red}Buster}$$ focused | This order disables all chains except $${\color{red}Buster}$$ (highest priority) and $${\color{black}Mighty}$$ (second priority). Failure to execute any of these chains will result in no other chain being (actively) used. | +| ![Chain priority options arts](../img/attack/chain-priority-options-arts.png) | $${\color{blue}Arts}$$ only | Only $${\color{blue}Arts}$$ chains will be attempted. All other chain checks are skipped. | +| ![Chain priority options avoid](../img/attack/chain-priority-options-avoid.png) | `Avoid` only | All chains will be avoided, unless it is absolutely impossible (e.g. all cards are $${\color{green}Quick}$$ cards). | +| ![Chain priority options none](../img/attack/chain-priority-options-none.png) | None | This completely skips all checks. It is almost the same as turning Chain Priority off, except that it uses Chain Priority's [Brave Chain](#brave-chain) handling instead of the default (which allows usage of the 'Always' option). | -The way Chain Priority works is that it checks for Chains in the order that is shown (from left to right). If it finds a chain, then it will use that chain immediately. If Chain Priority is on and is using the `Default order`, it will attempt to check for a Mighty Chain first. If there is no valid Mighty Chain, then it will attempt to check for a Buster Chain. If there is no valid Buster Chain, it will attempt to check for an Arts Chain and so on and so forth. If, however, an Arts Chain was found, it will skip checking for a Quick Chain using the cards and will immediately use the Arts Chain. +The way Chain Priority works is that it checks for Chains in the order that is shown (from left to right). If it finds a chain, then it will use that chain immediately. -| Flowchart of entire system | +E.g. If Chain Priority is on and `Default order` is used, it will attempt to check for a $${\color{black}Mighty}$$ Chain first. If there is no valid $${\color{black}Mighty}$$ Chain, then it will attempt to check for a $${\color{red}Buster}$$ Chain. If there is no valid $${\color{red}Buster}$$ Chain, it will attempt to check for an $${\color{blue}Arts}$$ Chain and so on and so forth. If an $${\color{blue}Arts}$$ Chain was found, it will skip checking for a $${\color{green}Quick}$$ Chain using the cards and will immediately use the $${\color{blue}Arts}$$ Chain. However, if all options fail to find any usable Chain, it will fallback to Servant Priority + Card Priority to determine the cards used. + +| Flowchart of entire system | |:-------------------------------------------------------------------------:| | ![Attack Priority flowchart](../img/attack/attack-priority-flowchart.png) | @@ -508,7 +510,14 @@ Mighty Chains, by definition, require all 3 card types in a single chain to be e |:-----------------------------------------------------------------:| | ![Color Chain flowchart](../img/attack/color-chain-flowchart.png) | -TBA +A $${\color{red}Buster}$$ / $${\color{blue}Arts}$$ / $${\color{green}Quick}$$ Chain is made by choosing 3 cards of the same type. This is referred to a Color Chain for this system. + +| Brave Chains options | Interactions | +|:---------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Don't care | The default output of Color Chains will be used, regardless of whether a Brave Chain is used or not. A Color Chain will be used if there is one available, otherwise it will skip to the next Chain option. | +| With NP | Will always attempt to make a Color Brave Chain only when a single NP is being used.
If there is both a Brave Chain and a Color Chain separately available, the Brave Chain will be used over the Color Chain. | +| Always | Will always attempt to make a Color Brave Chain if one is available, regardless of the Servant.
If there is both a Brave Chain and a Color Chain separately available, the Brave Chain will be used over the Color Chain. | +| Avoid | Will actively avoid making Brave Chains. If the only Color Chain is a Color Brave Chain, it will be skipped. | #### Avoid Chain @@ -516,7 +525,16 @@ TBA |:-----------------------------------------------------------------:| | ![Avoid Chain flowchart](../img/attack/avoid-chain-flowchart.png) | -TBA +The default behaviour of Avoid Chain in the system is to only avoid Mighty Chain and Color Chain as much as possible. This is the option, that, when combined with 'Avoid Brave Chains', tries its best to completely avoid any kind of chain possible. When it is not possible to avoid a Chain, it will be skipped over. + +The default implementation of this system attempts to stagger Servant cards if there are at least 2 Servants available that can make a non-Chain. This is due to the expected use case being to use this option only when face cards are to be used for efficient farming of simple and easy maps like Fuyuki. + +| Brave Chains options | Interactions | +|:---------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Don't care | The default only avoids Mighty and Color Chains. Thus, Brave Chains will occasionally occur with this option picked. | +| With NP | Attempts to make a Brave Chain (if one is available) only when a single NP is used, while avoiding Mighty and Color Chains.
| +| Always | Will attempt a Brave Chain while still avoiding a Mighty or Color Chain.
While the use-case of this option is near zero, it is still something that can be picked. | +| Avoid | Will actively avoid making Brave Chains. The most common use case. | ___ @@ -524,14 +542,16 @@ ___ There are only 3 options for brave chains, unless [Chain Priority](#chain-priority) is on. +![Battle Configs - Card Priority | Brave Chain menu](../img/attack/brave-chain-menu.png "Battle Configs - Card Priority | Brave Chain menu") + ![Battle Configs - Card Priority | Brave Chain options](../img/attack/brave-chain-options.png "Battle Configs - Card Priority | Brave Chain options") -| Option | Description | -|:----------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Don't care | Will not attempt to form nor avoid any Brave Chains and will simply use whatever order other priorities have sorted in. Thus, there can be the occasional Brave Chain that occur. | -| With NP | Will always attempt to make a Brave Chain when a single NP is being used. If no NPs are used, Brave Chain checking is ignored. | -| Always (only if ([Chain Priority](#chain-priority) is on) | Will always attempt to make a Brave Chain when one is available, regardless of the Servant. | -| Avoid | Will actively avoid making Brave Chains if one is available, unless it is absolutely impossible (e.g. all cards belong to a single Servant) | +| Option | Description | +|:----------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Don't care | Will not attempt to form nor avoid any Brave Chains and will simply use whatever order other priorities have sorted in. Thus, there can be the occasional Brave Chains that occur. | +| With NP | Will always attempt to make a Brave Chain when a single NP is being used. If no NPs are used, Brave Chain checking is ignored. | +| Always (only if ([Chain Priority](#chain-priority) is on) | Will always attempt to make a Brave Chain when one is available, regardless of the Servant. | +| Avoid | Will actively avoid making Brave Chains if one is available, unless it is absolutely impossible (e.g. all cards belong to a single Servant) | ___ From e417a8e19b9c7c6726fc0ec9579382ece075870f Mon Sep 17 00:00:00 2001 From: Vylantze Date: Thu, 18 Sep 2025 23:55:04 +0800 Subject: [PATCH 114/124] Update documentation part 4 --- wiki/scripts/Battle.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wiki/scripts/Battle.md b/wiki/scripts/Battle.md index 8f9db3096..16ccbe41f 100644 --- a/wiki/scripts/Battle.md +++ b/wiki/scripts/Battle.md @@ -462,9 +462,9 @@ The way Chain Priority works is that it checks for Chains in the order that is s E.g. If Chain Priority is on and `Default order` is used, it will attempt to check for a $${\color{black}Mighty}$$ Chain first. If there is no valid $${\color{black}Mighty}$$ Chain, then it will attempt to check for a $${\color{red}Buster}$$ Chain. If there is no valid $${\color{red}Buster}$$ Chain, it will attempt to check for an $${\color{blue}Arts}$$ Chain and so on and so forth. If an $${\color{blue}Arts}$$ Chain was found, it will skip checking for a $${\color{green}Quick}$$ Chain using the cards and will immediately use the $${\color{blue}Arts}$$ Chain. However, if all options fail to find any usable Chain, it will fallback to Servant Priority + Card Priority to determine the cards used. -| Flowchart of entire system | -|:-------------------------------------------------------------------------:| -| ![Attack Priority flowchart](../img/attack/attack-priority-flowchart.png) | +| Flowchart of entire system | +|:--------------------------------------------------------------------------:| +| ![Attack Priority flow chart](../img/attack/attack-priority-flowchart.png) | #### Interaction with other priorities From d1852b8c48d40f01f38b9dfa5343efdab7565070 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 22 Sep 2025 00:31:55 +0800 Subject: [PATCH 115/124] Fix bug with single card before NP + 1 NP usage. Add test case for it. --- .../modules/attack/AttackPriorityHandler.kt | 2 +- .../scripts/attack/AttackLineUps.kt | 10 ++++++ .../CardChainPriorityIntegrationTest.kt | 33 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt index a30cdd152..fe72d3f92 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/attack/AttackPriorityHandler.kt @@ -20,7 +20,7 @@ object AttackPriorityHandler { we want the best or matching face-card after NP. */ val shouldSwapForNpUsageScenario = listOf(npUsage.nps.size, npUsage.cardsBeforeNP).all { it == 1 } - if (shouldSwapForNpUsageScenario) { + if (rearrange && shouldSwapForNpUsageScenario) { return cards.toMutableList().also { Collections.swap(it, 0, 1) } diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt index d62eb795f..d26e1ddaf 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/AttackLineUps.kt @@ -254,6 +254,13 @@ class AttackLineUps { type = CardTypeEnum.Quick, affinity = CardAffinityEnum.Weak ) + val scathach5WAAlt = ParsedCard( + card = CommandCard.Face.E, + servant = TeamSlot.B, + fieldSlot = FieldSlot.B, + type = CardTypeEnum.Arts, + affinity = CardAffinityEnum.Weak + ) /** scathach1WB, scathach2WQ, scathach3WA, scathach4WB, scathach5WQ */ val lineup1 = listOf(scathach1WB, scathach2WQ, scathach3WA, scathach4WB, scathach5WQ) @@ -270,6 +277,9 @@ class AttackLineUps { // 3 Quick, 2 Buster; Quick starting card /** servant5WQ, servant2WQ, servant3WAltQ, servant1WB, servant4WB */ val lineup5 = listOf(scathach5WQ, scathach2WQ, scathach3WAltQ, scathach1WB, scathach4WB) + + /** scathach3WA, scathach5WAAlt, scathach1WB, scathach4WB, scathach2WQ */ + val lineup6_special = listOf(scathach3WA, scathach5WAAlt, scathach1WB, scathach4WB, scathach2WQ) } data object Unknown { diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index fd15cad83..6fb6235ea 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -744,4 +744,37 @@ class CardChainPriorityIntegrationTest { // Should fall back to default behaviour assertThat(picked).isEqualTo(pickedDefault) } + + // Test case for issue found with BB Worm farming map + // Scenario: All cards are BB, all are weak against enemy. + // Expected to be a Mighty Chain of A/NP/B, but becomes B/NP/A instead. + @Test + fun `SingleServantOnly - lineup6 (3A,5A,1B,4B,2Q)`() { + val cards = AttackLineUps.SingleServantOnly.lineup6_special + val picked = AttackPriorityHandler.pick( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 1), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + ) + ).map { it.card } + val pickedDefault = getDefaultMightyChainResult( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A), 1), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + ) + ).map { it.card } + + assertThat(picked).isEqualTo(pickedDefault) + assertThat(picked).containsExactly( + CommandCard.Face.C, + CommandCard.Face.A, + CommandCard.Face.E, + CommandCard.Face.D, + CommandCard.Face.B, + ) + } } \ No newline at end of file From b3a7dcc142c60968e37669154b04e88e4b398053 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 22 Sep 2025 00:45:30 +0800 Subject: [PATCH 116/124] Add more tests. --- .../attack/CardChainPriorityIntegrationTest.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt index 6fb6235ea..e4845ab25 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/attack/CardChainPriorityIntegrationTest.kt @@ -672,8 +672,21 @@ class CardChainPriorityIntegrationTest { ) ).map { it.card } + // Fall back to Mighty chain + val pickedDefault = getDefaultMightyChainResult( + cards = cards, + braveChainEnum = BraveChainEnum.WithNP, + npUsage = NPUsage(setOf(CommandCard.NP.A, CommandCard.NP.B), 0), + npTypes = mapOf( + FieldSlot.A to CardTypeEnum.Quick, + FieldSlot.B to CardTypeEnum.Quick + ) + ).map { it.card } + // Expect SB,KQ,NA,NA,SQ - 12345 - ABCDE assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.C, CommandCard.Face.D, CommandCard.Face.E) + // Default behaviour check + assertThat(picked).isEqualTo(pickedDefault) } @Test @@ -759,6 +772,8 @@ class CardChainPriorityIntegrationTest { FieldSlot.A to CardTypeEnum.Quick, ) ).map { it.card } + + // Uses Mighty Chain val pickedDefault = getDefaultMightyChainResult( cards = cards, braveChainEnum = BraveChainEnum.WithNP, From 485deae495b514e5a49eadb9c43b3374e520736c Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 6 Oct 2025 10:13:11 +0800 Subject: [PATCH 117/124] Add handling for when support Servant cannot be detected due to too many buffs. --- .../fate_grand_automata/scripts/modules/ServantTracker.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt index 1be9110ef..a971a1be7 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt @@ -254,8 +254,12 @@ class ServantTracker @Inject constructor( } } + // Fallback for if the support servant is not found + val currentSupportSlot = supportSlot + val isSupportFound = if (currentSupportSlot != null) (result.getOrElse(currentSupportSlot) { emptySet() }).isNotEmpty() else false + val ownedServants = faceCardImages - .filterKeys { it != supportSlot && it in deployed.values } + .filterKeys { (isSupportFound || it != supportSlot) && it in deployed.values } cardsRemaining .groupBy { card -> // find the best matching Servant which isn't the support From 329e295f5b1a8e4d0bfff310cf21457ee2e9e867 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 6 Oct 2025 22:25:55 +0800 Subject: [PATCH 118/124] Remove black color for Mighty Chain documentation --- wiki/scripts/Battle.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wiki/scripts/Battle.md b/wiki/scripts/Battle.md index 16ccbe41f..7a18f6a92 100644 --- a/wiki/scripts/Battle.md +++ b/wiki/scripts/Battle.md @@ -448,19 +448,19 @@ ___ ![Chain priority on](../img/attack/chain-priority-toggle.png) -Chain Priority is a new feature that allows for attempts at $${\color{black}Mighty}$$, $${\color{red}Buster}$$, $${\color{blue}Arts}$$ or $${\color{green}Quick}$$ Chains, based on the order that the chains are chosen in. There is an additional option called `Avoid`, which when used attempts to avoid any of the other chains. +Chain Priority is a new feature that allows for attempts at `Mighty`, $${\color{red}Buster}$$, $${\color{blue}Arts}$$ or $${\color{green}Quick}$$ Chains, based on the order that the chains are chosen in. There is an additional option called "Avoid", which when used attempts to avoid any of the other chains. | Sample orders | Name | Description | |:------------------------------------------------------------------------------------|:--------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ![Chain priority options default](../img/attack/chain-priority-options-default.png) | Default order | The default order used when Chain Priority is turned on. This disables Avoid Chain. | -| ![Chain priority options buster](../img/attack/chain-priority-options-buster.png) | $${\color{red}Buster}$$ focused | This order disables all chains except $${\color{red}Buster}$$ (highest priority) and $${\color{black}Mighty}$$ (second priority). Failure to execute any of these chains will result in no other chain being (actively) used. | +| ![Chain priority options buster](../img/attack/chain-priority-options-buster.png) | $${\color{red}Buster}$$ focused | This order disables all chains except $${\color{red}Buster}$$ (highest priority) and `Mighty` (second priority). Failure to execute any of these chains will result in no other chain being (actively) used. | | ![Chain priority options arts](../img/attack/chain-priority-options-arts.png) | $${\color{blue}Arts}$$ only | Only $${\color{blue}Arts}$$ chains will be attempted. All other chain checks are skipped. | -| ![Chain priority options avoid](../img/attack/chain-priority-options-avoid.png) | `Avoid` only | All chains will be avoided, unless it is absolutely impossible (e.g. all cards are $${\color{green}Quick}$$ cards). | +| ![Chain priority options avoid](../img/attack/chain-priority-options-avoid.png) | "Avoid" only | All chains will be avoided, unless it is absolutely impossible (e.g. all cards are $${\color{green}Quick}$$ cards). | | ![Chain priority options none](../img/attack/chain-priority-options-none.png) | None | This completely skips all checks. It is almost the same as turning Chain Priority off, except that it uses Chain Priority's [Brave Chain](#brave-chain) handling instead of the default (which allows usage of the 'Always' option). | The way Chain Priority works is that it checks for Chains in the order that is shown (from left to right). If it finds a chain, then it will use that chain immediately. -E.g. If Chain Priority is on and `Default order` is used, it will attempt to check for a $${\color{black}Mighty}$$ Chain first. If there is no valid $${\color{black}Mighty}$$ Chain, then it will attempt to check for a $${\color{red}Buster}$$ Chain. If there is no valid $${\color{red}Buster}$$ Chain, it will attempt to check for an $${\color{blue}Arts}$$ Chain and so on and so forth. If an $${\color{blue}Arts}$$ Chain was found, it will skip checking for a $${\color{green}Quick}$$ Chain using the cards and will immediately use the $${\color{blue}Arts}$$ Chain. However, if all options fail to find any usable Chain, it will fallback to Servant Priority + Card Priority to determine the cards used. +E.g. If Chain Priority is on and `Default order` is used, it will attempt to check for a `Mighty` Chain first. If there is no valid `Mighty` Chain, then it will attempt to check for a $${\color{red}Buster}$$ Chain. If there is no valid $${\color{red}Buster}$$ Chain, it will attempt to check for an $${\color{blue}Arts}$$ Chain and so on and so forth. If an $${\color{blue}Arts}$$ Chain was found, it will skip checking for a $${\color{green}Quick}$$ Chain using the cards and will immediately use the $${\color{blue}Arts}$$ Chain. However, if all options fail to find any usable Chain, it will fallback to Servant Priority + Card Priority to determine the cards used. | Flowchart of entire system | |:--------------------------------------------------------------------------:| From a60d5f42aa79f0f1c82a5e386cbea61ea721e161 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 6 Oct 2025 23:20:56 +0800 Subject: [PATCH 119/124] Add the waitBeforeTurn duration after a orderChange action to attempt to fix a detection bug caused by lag. --- .../ui/fine_tune/FineTuneSettingsViewModel.kt | 2 +- .../io/github/fate_grand_automata/scripts/modules/Caster.kt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/fine_tune/FineTuneSettingsViewModel.kt b/app/src/main/java/io/github/fate_grand_automata/ui/fine_tune/FineTuneSettingsViewModel.kt index c68e2b722..5fc98b6e3 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/fine_tune/FineTuneSettingsViewModel.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/fine_tune/FineTuneSettingsViewModel.kt @@ -135,7 +135,7 @@ class FineTuneSettingsViewModel @Inject constructor( pref = prefs.waitBeforeTurn, name = R.string.p_fine_tune_wait_before_turn, icon = icon(R.drawable.ic_time), - valueRange = 0..2000, + valueRange = 0..5000, valueRepresentation = { "${it}ms" }, hint = "Delay before the skill sequence starts after Battle screen is detected. Slower devices might need longer delay." ), diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Caster.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Caster.kt index a5544f3be..9063afe60 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Caster.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Caster.kt @@ -164,7 +164,9 @@ class Caster @Inject constructor( waitForAnimationToFinish(15.seconds) // Extra wait for the lag introduced by Order change - 1.seconds.wait() + 0.5.seconds.wait() + // Dynamic wait (default will be the same amount of time) + prefs.waitBeforeTurn.wait() servantTracker.orderChanged(action.starting, action.sub) } From a41b7f62f0b0b283b9711d576d51b4e51ff60f40 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Mon, 6 Oct 2025 23:25:44 +0800 Subject: [PATCH 120/124] Update app/src/main/res/values-b+zh+CN/localized.xml Copilot suggestion Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/src/main/res/values-b+zh+CN/localized.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index b2aa9a3b5..024161d90 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -126,7 +126,7 @@ Order Change" "Save unmodified full-screen screenshots instead of cropped and rescaled screenshots" "加速道具" "从者优先级" - "追击连携先级" + "追击连携优先级" "追击连携" "调换顺序 (优先级高的卡后出)" From d9c5876d3f5421095d2c6dff52b033f812e1864c Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 7 Oct 2025 00:12:55 +0800 Subject: [PATCH 121/124] Add new variables for Fine Tune. --- .../ui/fine_tune/FineTuneSettingsViewModel.kt | 26 ++++++++++++++++++- app/src/main/res/values-b+zh+CN/localized.xml | 3 +++ app/src/main/res/values-b+zh+TW/localized.xml | 3 +++ app/src/main/res/values-ja/localized.xml | 3 +++ app/src/main/res/values-ko/localized.xml | 3 +++ app/src/main/res/values-vi/localized.xml | 3 +++ app/src/main/res/values/localized.xml | 3 +++ .../fate_grand_automata/prefs/Preferences.kt | 6 +++++ .../prefs/core/PrefsCore.kt | 3 +++ .../scripts/modules/Caster.kt | 4 +-- .../scripts/modules/ServantTracker.kt | 7 +++-- .../scripts/prefs/IPreferences.kt | 4 +++ 12 files changed, 60 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/fine_tune/FineTuneSettingsViewModel.kt b/app/src/main/java/io/github/fate_grand_automata/ui/fine_tune/FineTuneSettingsViewModel.kt index 5fc98b6e3..74efeec6e 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/fine_tune/FineTuneSettingsViewModel.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/fine_tune/FineTuneSettingsViewModel.kt @@ -59,7 +59,23 @@ class FineTuneSettingsViewModel @Inject constructor( valueRange = 50..100, valueRepresentation = { "$it%" }, hint = "Similarity threshold for detecting wave change. If your skill commands are used in the wrong wave, tweaking this might help." - ) + ), + FineTuneItem( + pref = prefs.npCardTypeSimilarity, + name = R.string.p_fine_tune_np_card_type_similarity, + icon = icon(Icons.Default.Star), + valueRange = 30..100, + valueRepresentation = { "$it%" }, + hint = "Similarity threshold used for the NP card type detection (Buster, Arts, Quick). Only used when Chain Priority is on." + ), + FineTuneItem( + pref = prefs.npSpamCardDetectionSimilarity, + name = R.string.p_fine_tune_np_spam_card_detection_similarity, + icon = icon(Icons.Default.Star), + valueRange = 30..100, + valueRepresentation = { "$it%" }, + hint = "Similarity threshold used for automatically detecting NPs in Spam mode. Only used when Chain Priority is on." + ), ) ), FineTuneGroup( @@ -139,6 +155,14 @@ class FineTuneSettingsViewModel @Inject constructor( valueRepresentation = { "${it}ms" }, hint = "Delay before the skill sequence starts after Battle screen is detected. Slower devices might need longer delay." ), + FineTuneItem( + pref = prefs.waitAfterOrderChange, + name = R.string.p_fine_tune_wait_after_order_change, + icon = icon(R.drawable.ic_time), + valueRange = 0..5000, + valueRepresentation = { "${it}ms" }, + hint = "Delay before the skill sequence starts after Order Change has been used. Slower devices might need longer delay." + ), FineTuneItem( pref = prefs.waitBeforeCards, name = R.string.p_fine_tune_wait_before_cards, diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index 024161d90..af73923cb 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -163,6 +163,8 @@ Order Change" "全局识别阈值(%)" "满破星识别阈值(%)" "战斗场次识别阈值(%)" + "宝具类型识别阈值(%)" + "宝具瞎打模式识别阈值(%)" "点击" "点击后暂停(ms)" "点击时间(ms)" @@ -174,6 +176,7 @@ Order Change" "等待时间" "施放技能后等待时间(ms)" "回合间等待时间(ms)" + "换人(Order Change)后等待时间(ms)" "选择指令卡前等待时间(ms)" "等待时间加乘(%)" "FGA有问题请先点这(GitHub链接)" diff --git a/app/src/main/res/values-b+zh+TW/localized.xml b/app/src/main/res/values-b+zh+TW/localized.xml index 2ccbaeb5d..353d2fd2b 100644 --- a/app/src/main/res/values-b+zh+TW/localized.xml +++ b/app/src/main/res/values-b+zh+TW/localized.xml @@ -161,6 +161,8 @@ Order Change" "最低相似度(%)" "滿破相似度(%)" "關卡計數相似度(%)" + "Buster/Arts/Quick寶具施相似度(%)" + "自動模式寶具施相似度(%)" "點選" "點選後暫停(ms)" "點選時間(ms)" @@ -172,6 +174,7 @@ Order Change" "等待時間" "施放技能後等待時間(ms)" "回合間等待時間(ms)" + "换人(Order Change)后等待时间(ms)" "選擇指令卡前等待時間(ms)" "等待時間加乘(%)" "若 FGA 有問題請先參考此處(GitHub連結)" diff --git a/app/src/main/res/values-ja/localized.xml b/app/src/main/res/values-ja/localized.xml index a642fd6a8..43dd507f9 100644 --- a/app/src/main/res/values-ja/localized.xml +++ b/app/src/main/res/values-ja/localized.xml @@ -165,6 +165,8 @@ "最小の類似度" "MLBの類似度" "ウェーブカウンターの類似度" + "宝具Buster/Arts/Quickの類似度" + "リピートモード宝具の類似度" "クリック" "クリック後の待機秒数" "クリックの継続時間" @@ -176,6 +178,7 @@ "待機" "スキル使用後の待機時間" "ターン経過後の待機時間" + "オーダーチェンジの後の待機時間" "カード選択後の待機時間" "待機時間倍率" "Troubleshooting" diff --git a/app/src/main/res/values-ko/localized.xml b/app/src/main/res/values-ko/localized.xml index f18f30447..8e2187ce9 100644 --- a/app/src/main/res/values-ko/localized.xml +++ b/app/src/main/res/values-ko/localized.xml @@ -162,6 +162,8 @@ "최소 유사도 (%)" "최대해방 / 풀돌 유사도 (%)" "스테이지 카운터 유사도 (%)" + "보구랑 Buster/Arts/Quick 유사도 (%)" + "반복 모드 보구랑 유사도 (%)" "터치" "터치 후 딜레이 (ms)" "터치 지속 시간 (ms)" @@ -173,6 +175,7 @@ "딜레이" "스킬 딜레이 (ms)" "턴 시작 전 딜레이 (ms)" + "오더 체인지 후 딜레이 (ms)" "카드 뜨기 전 딜레이 (ms)" "딜레이 배율기 (%)" "문제 해결" diff --git a/app/src/main/res/values-vi/localized.xml b/app/src/main/res/values-vi/localized.xml index 6ebd34707..475ee2011 100644 --- a/app/src/main/res/values-vi/localized.xml +++ b/app/src/main/res/values-vi/localized.xml @@ -161,6 +161,8 @@ Sao chép hoặc đổi tên nó nếu bạn muốn giữ nó." "Độ tương tự tối thiểu" "MLB giống nhau" "Sự tương đồng của bộ đếm giai đoạn" + "NP Buster/Arts/Quick tương tự" + "Sự tương đồng trong phát hiện thư rác NP" "Số nhấp chuột" "Chờ sau khi nhấp chuột" "Thời lượng nhấp chuột" @@ -172,6 +174,7 @@ Sao chép hoặc đổi tên nó nếu bạn muốn giữ nó." "Chờ đợi" "Độ trễ kỹ năng" "Đợi trước khi rẽ" + "Đợi sau khi thay đổi thứ tự" "Chờ trước Thẻ" "Chờ nhân" "Xử lý sự cố" diff --git a/app/src/main/res/values/localized.xml b/app/src/main/res/values/localized.xml index 181798fe7..b46a42bbb 100644 --- a/app/src/main/res/values/localized.xml +++ b/app/src/main/res/values/localized.xml @@ -166,6 +166,8 @@ Copy or rename it if you want to keep it." "Min Similarity" "MLB Similarity" "Stage Counter Similarity" + "NP Card Type Similarity" + "NP Spam Detection Similarity" "Clicks" "Wait after clicking" "Click duration" @@ -177,6 +179,7 @@ Copy or rename it if you want to keep it." "Wait" "Skill Delay" "Wait before Turn" + "Wait after Order Change" "Wait before Cards" "Wait Multiplier" "Troubleshooting" diff --git a/prefs/src/main/java/io/github/fate_grand_automata/prefs/Preferences.kt b/prefs/src/main/java/io/github/fate_grand_automata/prefs/Preferences.kt index bcee9eecd..7a39b02a1 100644 --- a/prefs/src/main/java/io/github/fate_grand_automata/prefs/Preferences.kt +++ b/prefs/src/main/java/io/github/fate_grand_automata/prefs/Preferences.kt @@ -97,6 +97,12 @@ class PreferencesImpl @Inject constructor( override val waitBeforeCards by prefs.waitBeforeCards.map { it.milliseconds } + override val waitAfterOrderChange by prefs.waitAfterOrderChange.map { it.milliseconds } + + override val npSpamCardDetectionSimilarity by prefs.npSpamCardDetectionSimilarity.map { it / 100.0 } + + override val npCardTypeSimilarity by prefs.npCardTypeSimilarity.map { it / 100.0 } + override var maxGoldEmberStackSize by prefs.maxGoldEmberSetSize override var maxGoldEmberTotalCount by prefs.maxGoldEmberTotalCount diff --git a/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/PrefsCore.kt b/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/PrefsCore.kt index 98be008d1..5bb5d8e0b 100644 --- a/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/PrefsCore.kt +++ b/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/PrefsCore.kt @@ -61,12 +61,15 @@ class PrefsCore @Inject constructor( val minSimilarity = maker.int("min_similarity", 80) val mlbSimilarity = maker.int("mlb_similarity", 70) val stageCounterSimilarity = maker.int("stage_counter_similarity", 85) + val npCardTypeSimilarity = maker.int("card_type_similarity", 50) + val npSpamCardDetectionSimilarity = maker.int("np_card_detection_similarity", 80) val stageCounterNew = maker.bool("stage_counter_new") val skillDelay = maker.int("skill_delay", 500) val waitMultiplier = maker.int("wait_multiplier", 100) val waitBeforeTurn = maker.int("wait_before_turn", 500) val waitBeforeCards = maker.int("wait_before_cards", 2000) + val waitAfterOrderChange = maker.int("wait_after_order_change", 1000) val clickWaitTime = maker.int("click_wait_time", 300) val clickDuration = maker.int("click_duration", 50) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Caster.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Caster.kt index 9063afe60..69562661e 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Caster.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/Caster.kt @@ -164,9 +164,7 @@ class Caster @Inject constructor( waitForAnimationToFinish(15.seconds) // Extra wait for the lag introduced by Order change - 0.5.seconds.wait() - // Dynamic wait (default will be the same amount of time) - prefs.waitBeforeTurn.wait() + prefs.waitAfterOrderChange.wait() servantTracker.orderChanged(action.starting, action.sub) } diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt index a971a1be7..53610b65c 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/modules/ServantTracker.kt @@ -85,6 +85,7 @@ class ServantTracker @Inject constructor( checkImages.clear() } + private fun init(teamSlot: TeamSlot, slot: FieldSlot) { messages.log( ScriptLog.ServantEnteredSlot( @@ -126,7 +127,7 @@ class ServantTracker @Inject constructor( val scores = npSplashImages .mapValues { (_, image) -> patternList?.maxOf { - pattern -> pattern.find(image, 0.5)?.score ?: 0.0 + pattern -> pattern.find(image, prefs.npCardTypeSimilarity)?.score ?: 0.0 } ?: 0.0 } // Get type by comparing the patterns we have with the splash arts @@ -291,8 +292,6 @@ class ServantTracker @Inject constructor( return result } - val npCardDetectionScoreCutoff = 0.8 - fun npCardsDetectedUsingServantFaces(): Set { if (prefs.skipServantFaceCardCheck) { return emptySet() @@ -316,7 +315,7 @@ class ServantTracker @Inject constructor( } val result = scores .filter { (_, values) -> - values.filter { it > npCardDetectionScoreCutoff }.maxByOrNull { it } != null + values.filter { it > prefs.npSpamCardDetectionSimilarity }.maxByOrNull { it } != null } .map { it.first } .toSet() diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/IPreferences.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/IPreferences.kt index 46df78165..a21a62e25 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/IPreferences.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/IPreferences.kt @@ -41,6 +41,10 @@ interface IPreferences { val stageCounterNew: Boolean val waitBeforeTurn: Duration val waitBeforeCards: Duration + val waitAfterOrderChange: Duration + + val npSpamCardDetectionSimilarity: Double + val npCardTypeSimilarity: Double val support: ISupportPreferencesCommon val platformPrefs: PlatformPrefs From 160b720421f86dd98453377497de793bcfbe49b8 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 7 Oct 2025 18:09:33 +0800 Subject: [PATCH 122/124] Update with a comment on the redundancy of the defaultOrder. --- .../fate_grand_automata/scripts/enums/AttackPriorityEnum.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/AttackPriorityEnum.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/AttackPriorityEnum.kt index 3f34d6149..23e05a681 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/AttackPriorityEnum.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/enums/AttackPriorityEnum.kt @@ -7,6 +7,9 @@ enum class AttackPriorityEnum { CardColorPriority; companion object { + // It is defined separately here instead of using the order of the enum definition + // because some parts might be removed or changed in future when the actual AttackPriority implementation is done + // and this will prevent the app from breaking when serializing or deserializing content from this enum. val defaultOrder = listOf(BraveChainPriority, CardChainPriority, ServantPriority, CardColorPriority) } } \ No newline at end of file From 8c3d185c32f73bdaadce82d2561d21022e30e32d Mon Sep 17 00:00:00 2001 From: Vylantze Date: Sat, 6 Dec 2025 05:28:41 +0800 Subject: [PATCH 123/124] Force rebuild --- .../io/github/fate_grand_automata/scripts/BraveChainsTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt b/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt index c498ee3cf..23f517c11 100644 --- a/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt +++ b/scripts/src/test/java/io/github/fate_grand_automata/scripts/BraveChainsTest.kt @@ -139,4 +139,4 @@ class BraveChainsTest { assertThat(picked).containsExactly(CommandCard.Face.A, CommandCard.Face.B, CommandCard.Face.E, CommandCard.Face.C, CommandCard.Face.D) } -} \ No newline at end of file +} From e4f7a7a7d05eaf0b97d42fe4a510df6077da1292 Mon Sep 17 00:00:00 2001 From: Vylantze Date: Tue, 23 Dec 2025 07:52:02 +0800 Subject: [PATCH 124/124] Fix merge conflicts --- app/src/main/res/values-b+zh+CN/localized.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/res/values-b+zh+CN/localized.xml b/app/src/main/res/values-b+zh+CN/localized.xml index 671de3890..b34ffaed4 100644 --- a/app/src/main/res/values-b+zh+CN/localized.xml +++ b/app/src/main/res/values-b+zh+CN/localized.xml @@ -96,6 +96,7 @@ "区服" "指令卡优先级" "使用从者优先级" + "使用追击连携先级" "助战从者" "助战从者选取方式" "备用选取方式" @@ -405,8 +406,6 @@ "正在提取助战图片" "金种火最大领取数(枚数)" "下载成功。5秒后将重启APP。" - "使用从者优先级" - "使用追击连携先级" "静希草十郎" "三选项"