diff --git a/src/main/java/at/nanopenguin/mtcg/Pair.java b/src/main/java/at/nanopenguin/mtcg/Pair.java index da8a8a7..8317553 100644 --- a/src/main/java/at/nanopenguin/mtcg/Pair.java +++ b/src/main/java/at/nanopenguin/mtcg/Pair.java @@ -15,4 +15,13 @@ public class Pair { public T left() { return this.left; }; public U right() { return this.right; }; + + @Override + public boolean equals(Object o) { + if (o instanceof Pair) { + return left.equals(((Pair) o).left) && right.equals(((Pair) o).right); + } + + return super.equals(o); + } } diff --git a/src/main/java/at/nanopenguin/mtcg/application/Battle.java b/src/main/java/at/nanopenguin/mtcg/application/Battle.java index 8874fd0..bc6604f 100644 --- a/src/main/java/at/nanopenguin/mtcg/application/Battle.java +++ b/src/main/java/at/nanopenguin/mtcg/application/Battle.java @@ -12,15 +12,15 @@ import java.util.List; public class Battle { @RequiredArgsConstructor - private enum ElementMod { + enum ElementMod { HALF(0.5), NONE(1), DOUBLE(2); public final double percentMod; } - private record FightDTO(Combatant player, Card card) {}; - private record RoundResult(FightDTO winner, FightDTO loser, boolean draw, ElementMod winnerMod, ElementMod loserMod) {}; + record FightDTO(Combatant player, Card card) {}; + record RoundResult(FightDTO winner, FightDTO loser, boolean draw, ElementMod winnerMod, ElementMod loserMod) {}; private Pair combatants; @Getter private volatile List log = new ArrayList<>(); @@ -50,7 +50,7 @@ public class Battle { this.combatants.right().updateStats(!leftWins); } - private void playRound(int round) { + void playRound(int round) { RoundResult result = this.fight( new FightDTO( this.combatants.left(), @@ -74,7 +74,7 @@ public class Battle { result.loser().card()); } - private boolean isImmune(Card defend, Card attack) { + static boolean isImmune(Card defend, Card attack) { if (defend.name().equals("Dragon") && attack.name().endsWith("Goblin")) return true; if (defend.name().equals("Wizzard") && attack.name().equals("Ork")) return true; if (defend.name().equals("WaterSpell") && attack.name().equals("Knight")) return true; @@ -84,7 +84,7 @@ public class Battle { return false; } - private enum Element { + enum Element { NORMAL, FIRE, WATER; @@ -105,7 +105,7 @@ public class Battle { } } - private Pair getElementMod(Card left, Card right) { + static Pair getElementMod(Card left, Card right) { Pair returnMods = new Pair<>(ElementMod.NONE, ElementMod.NONE); if (!left.name().endsWith("Spell") && !right.name().endsWith("Spell")) return returnMods; @@ -130,13 +130,13 @@ public class Battle { return returnMods; } - private RoundResult fight(FightDTO left, FightDTO right) { - if (this.isImmune(left.card(), right.card())) return new RoundResult(left, right, false, ElementMod.NONE, ElementMod.NONE); - if (this.isImmune(right.card(), left.card())) return new RoundResult(right, left, false, ElementMod.NONE, ElementMod.NONE); + static RoundResult fight(FightDTO left, FightDTO right) { + if (isImmune(left.card(), right.card())) return new RoundResult(left, right, false, ElementMod.NONE, ElementMod.NONE); + if (isImmune(right.card(), left.card())) return new RoundResult(right, left, false, ElementMod.NONE, ElementMod.NONE); Pair dmgMods = getElementMod(left.card(), right.card()); - boolean leftWins = left.card().damage()*dmgMods.left().percentMod > right.card().damage()*dmgMods.right().percentMod; + boolean leftWins = left.card().damage()*dmgMods.left().percentMod >= right.card().damage()*dmgMods.right().percentMod; return new RoundResult( leftWins ? left : right, leftWins ? right : left, @@ -145,7 +145,7 @@ public class Battle { leftWins ? dmgMods.right() : dmgMods.left()); } - private String createCombatString(int round, RoundResult result) { + static String createCombatString(int round, RoundResult result) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder .append(round) diff --git a/src/test/java/at/nanopenguin/mtcg/application/BattleTest.java b/src/test/java/at/nanopenguin/mtcg/application/BattleTest.java new file mode 100644 index 0000000..72cf13c --- /dev/null +++ b/src/test/java/at/nanopenguin/mtcg/application/BattleTest.java @@ -0,0 +1,117 @@ +package at.nanopenguin.mtcg.application; + +import at.nanopenguin.mtcg.Pair; +import at.nanopenguin.mtcg.application.service.schemas.Card; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mockito; + +public class BattleTest { + private static Card cardWithName(String name) { + Card cardMock = Mockito.mock(Card.class); + Mockito.when(cardMock.name()).thenReturn(name); + return cardMock; + } + + private static Card cardWithNameAndDmg(String name, float dmg) { + Card cardMock = Mockito.mock(Card.class); + Mockito.when(cardMock.name()).thenReturn(name); + Mockito.when(cardMock.damage()).thenReturn(dmg); + return cardMock; + } + + @ParameterizedTest + @CsvSource({ + "WaterGoblin, FireSpell, DOUBLE, HALF", // generic + "FireGoblin, NormalSpell, DOUBLE, HALF", + "NormalGoblin, WaterSpell, DOUBLE, HALF", + "FireGoblin, WaterSpell, HALF, DOUBLE", // generic reverse + "NormalGoblin, FireSpell, HALF, DOUBLE", + "WaterGoblin, NormalSpell, HALF, DOUBLE", + "WaterGoblin, FireTroll, NONE, NONE", // no spell + "WaterSpell, FireSpell, DOUBLE, HALF", // two spells + "FireGoblin, FireSpell, NONE, NONE", // same element + "FireSpell, Dragon, DOUBLE, HALF" // non elemental + }) + void elementMods(String name1, String name2, Battle.ElementMod expectedMod1, Battle.ElementMod expectedMod2){ + Card card1 = cardWithName(name1); + Card card2 = cardWithName(name2); + + Assertions.assertEquals(new Pair<>(expectedMod1, expectedMod2), Battle.getElementMod(card1, card2)); + } + + @ParameterizedTest + @CsvSource({ + "Dragon, FireGoblin, true", + "Dragon, WaterGoblin, true", + "Dragon, NormalGoblin, true", + "WaterGoblin, Dragon, false", + "Wizzard, Ork, true", + "Ork, Wizzard, false", + "WaterSpell, Knight, true", + "FireSpell, Knight, false", + "Knight, WaterSpell, false", + "Kraken, WaterSpell, true", + "Kraken, FireSpell, true", + "FireElf, Dragon, true", + "NormalElf, Dragon, false" + }) + void immunityTest(String name1, String name2, boolean expected) { + Card card1 = cardWithName(name1); + Card card2 = cardWithName(name2); + + Assertions.assertEquals(expected, Battle.isImmune(card1, card2)); + } + + private static Battle.FightDTO getFightDTO(String cardName, float dmg) { + Battle.FightDTO fightDTOmock = Mockito.mock(Battle.FightDTO.class); + Card cardMock = cardWithNameAndDmg(cardName, dmg); + Mockito.when(fightDTOmock.card()).thenReturn(cardMock); + return fightDTOmock; + } + + enum fightOutcome { + LEFT_WINS, + DRAW, + RIGHT_WINS + } + + @ParameterizedTest + @CsvSource({ + "WaterGoblin, 10, FireTroll, 15, RIGHT_WINS, NONE, NONE", + "FireTroll, 15, WaterGoblin, 10, LEFT_WINS, NONE, NONE", + "FireSpell, 10, WaterSpell, 20, RIGHT_WINS, DOUBLE, HALF", + "FireSpell, 20, WaterSpell, 5, DRAW, HALF, DOUBLE", + "FireSpell, 90, WaterSpell, 5, LEFT_WINS, HALF, DOUBLE", + "FireSpell, 10, WaterGoblin, 10, RIGHT_WINS, DOUBLE, HALF", + "WaterSpell, 10, WaterGoblin, 10, DRAW, NONE, NONE", + "RegularSpell, 10, WaterGoblin, 10, LEFT_WINS, DOUBLE, HALF", + "RegularSpell, 10, Knight, 15, RIGHT_WINS, NONE, NONE", + }) + void fightTest(String card1, float dmg1, String card2, float dmg2, fightOutcome outcome, Battle.ElementMod expectedMod1, Battle.ElementMod expectedMod2) { + Battle.FightDTO fightDTO1 = getFightDTO(card1, dmg1); + Battle.FightDTO fightDTO2 = getFightDTO(card2, dmg2); + + Battle.RoundResult result = Battle.fight(fightDTO1, fightDTO2); + + switch (outcome) { + case LEFT_WINS -> { + Assertions.assertEquals(fightDTO1, result.winner()); + Assertions.assertFalse(result.draw()); + } + case DRAW -> { + Assertions.assertTrue(result.draw()); + } + case RIGHT_WINS -> { + Assertions.assertEquals(fightDTO2, result.winner()); + Assertions.assertFalse(result.draw()); + } + } + + Assertions.assertEquals(expectedMod1, result.winnerMod()); + Assertions.assertEquals(expectedMod2, result.loserMod()); + } + + +}