From fba3198887e4576b4c311cd253dabd404cd077df Mon Sep 17 00:00:00 2001 From: Framonti <37839292+Framonti@users.noreply.github.com> Date: Thu, 14 Sep 2023 17:53:40 +0200 Subject: [PATCH] Unit tests for Battle.kt (#10127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ add testing helper function to create the barbarian civilization * add tests for Battle class * add more tests for Battle class * ♻️ let compiler infer types * add helper test methods to create unit with uniques * use unit with unique explicitly created to loose tests from specific ruleset * add even more tests for Battle --- .../unciv/logic/battle/BattleDamageTest.kt | 3 +- .../src/com/unciv/logic/battle/BattleTest.kt | 341 ++++++++++++++++++ .../unciv/logic/battle/TargetHelperTest.kt | 9 +- .../src/com/unciv/testing/GdxTestRunner.java | 2 +- tests/src/com/unciv/testing/TestGame.kt | 27 ++ 5 files changed, 372 insertions(+), 10 deletions(-) create mode 100644 tests/src/com/unciv/logic/battle/BattleTest.kt diff --git a/tests/src/com/unciv/logic/battle/BattleDamageTest.kt b/tests/src/com/unciv/logic/battle/BattleDamageTest.kt index 9322610e79..a40c392e7f 100644 --- a/tests/src/com/unciv/logic/battle/BattleDamageTest.kt +++ b/tests/src/com/unciv/logic/battle/BattleDamageTest.kt @@ -146,8 +146,7 @@ class BattleDamageTest { // given val defenderTile = testGame.getTile(Vector2.Zero) testGame.setTileFeatures(defenderTile.position, Constants.hill) - defenderCiv.resourceStockpiles.add("Horses", 1) // no resource penalty - val defenderUnit = testGame.addUnit("Horseman", defenderCiv, defenderTile) + val defenderUnit = testGame.addDefaultMeleeUnitWithUniques(defenderCiv, defenderTile, "No defensive terrain bonus") // when val defenceModifiers = BattleDamage.getDefenceModifiers(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defenderUnit), defaultAttackerTile) diff --git a/tests/src/com/unciv/logic/battle/BattleTest.kt b/tests/src/com/unciv/logic/battle/BattleTest.kt new file mode 100644 index 0000000000..ea22304cd6 --- /dev/null +++ b/tests/src/com/unciv/logic/battle/BattleTest.kt @@ -0,0 +1,341 @@ +package com.unciv.logic.battle + +import com.badlogic.gdx.math.Vector2 +import com.unciv.logic.civilization.Civilization +import com.unciv.logic.map.mapunit.MapUnit +import com.unciv.testing.GdxTestRunner +import com.unciv.testing.TestGame +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertTrue +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(GdxTestRunner::class) +class BattleTest { + private lateinit var attackerCiv: Civilization + private lateinit var defenderCiv: Civilization + + private lateinit var defaultAttackerUnit: MapUnit + private lateinit var defaultDefenderUnit: MapUnit + + private val testGame = TestGame() + + @Before + fun setUp() { + testGame.makeHexagonalMap(4) + attackerCiv = testGame.addCiv() + defenderCiv = testGame.addCiv() + + defaultAttackerUnit = testGame.addUnit("Warrior", attackerCiv, testGame.getTile(Vector2.X)) + defaultAttackerUnit.currentMovement = 2f + defaultDefenderUnit = testGame.addUnit("Warrior", defenderCiv, testGame.getTile(Vector2.Zero)) + defaultDefenderUnit.currentMovement = 2f + } + + @Test + fun `defender should withdraw from melee attack if has the unique to do so`() { + val defenderUnit = testGame.addDefaultMeleeUnitWithUniques(attackerCiv, testGame.getTile(Vector2.Y), "May withdraw before melee ([100]%)") + defenderUnit.currentMovement = 2f + + // when + val damageDealt = Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defenderUnit)) + + // thenvi + assertEquals(0, damageDealt.attackerDealt) + assertEquals(0, damageDealt.defenderDealt) + assertFalse(defenderUnit.getTile().position == Vector2.Y) // defender moves away + assertEquals(Vector2.X, defaultAttackerUnit.getTile().position) // attacker didn't move + assertEquals(0f, defaultAttackerUnit.currentMovement) // warriors cannot move anymore after attacking + } + + @Test + fun `both defender and attacker should do damage when are melee`() { + // when + val damageDealt = Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(31, damageDealt.attackerDealt) + assertEquals(31, damageDealt.defenderDealt) + assertEquals(69, defaultAttackerUnit.health) + assertEquals(69, defaultDefenderUnit.health) + } + + @Test + fun `only attacker should do damage when he's ranged`() { + // given + val attackerUnit = testGame.addUnit("Archer", attackerCiv, testGame.getTile(Vector2.Y)) + + // when + val damageDealt = Battle.attack(MapUnitCombatant(attackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(28, damageDealt.attackerDealt) + assertEquals(0, damageDealt.defenderDealt) + } + + @Test + fun `should move to enemy position when melee killing`() { + // given + defaultDefenderUnit.health = 1 + + // when + Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(Vector2.Zero, defaultAttackerUnit.getTile().position) + assertTrue(defaultDefenderUnit.isDestroyed) + } + + @Test + fun `should stay in original position when ranged killing`() { + // given + val attackerUnit = testGame.addUnit("Archer", attackerCiv, testGame.getTile(Vector2.Y)) + defaultDefenderUnit.health = 1 + + // when + Battle.attack(MapUnitCombatant(attackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(Vector2.Y, attackerUnit.getTile().position) + assertTrue(defaultDefenderUnit.isDestroyed) + } + + @Test + fun `both attacker and defender should earn XP`() { + // when + Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(5, defaultAttackerUnit.promotions.XP) + assertEquals(4, defaultDefenderUnit.promotions.XP) + } + + @Test + fun `should earn XP when fighting barbarian and below XP cap`() { + // given + val barbarianCiv = testGame.addBarbarianCiv() + val barbarianUnit = testGame.addUnit("Brute", barbarianCiv, testGame.getTile(Vector2.Y)) + + // when + Battle.attack(MapUnitCombatant(barbarianUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(5, barbarianUnit.promotions.XP) + assertEquals(4, defaultDefenderUnit.promotions.XP) + } + + @Test + fun `should not earn XP when fighting barbarian if exceeding XP cap`() { + // given + val barbarianCiv = testGame.addBarbarianCiv() + val barbarianUnit = testGame.addUnit("Brute", barbarianCiv, testGame.getTile(Vector2.Y)) + defaultDefenderUnit.promotions.XP = 35 + + // when + Battle.attack(MapUnitCombatant(barbarianUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(5, barbarianUnit.promotions.XP) + assertEquals(35, defaultDefenderUnit.promotions.XP) + } + + @Test + fun `attacker should expend all movement points to attack without uniques`() { + // when + Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(0f, defaultAttackerUnit.currentMovement) + } + + @Test + fun `attacker should still have movement points left with 'additional attack per turn' unique`() { + // given + val attackerUnit = testGame.addDefaultMeleeUnitWithUniques(attackerCiv, testGame.getTile(Vector2.Y), "[1] additional attacks per turn") + attackerUnit.currentMovement = 2f + + // when + Battle.attack(MapUnitCombatant(attackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(1f, attackerUnit.currentMovement) + } + + @Test + fun `attacker should still have movement points left with 'can move after attacking' unique`() { + // given + val attackerUnit = testGame.addDefaultMeleeUnitWithUniques(attackerCiv, testGame.getTile(Vector2.Y), "Can move after attacking") + attackerUnit.currentMovement = 2f + + // when + Battle.attack(MapUnitCombatant(attackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(1f, attackerUnit.currentMovement) + } + + @Test + fun `should capture civilian`() { + // given + val defenderUnit = testGame.addUnit("Worker", defenderCiv, testGame.getTile(Vector2(2f, 0f))) + + // when + val attack = Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defenderUnit)) + + // then + assertEquals(0, attack.attackerDealt) + assertEquals(0, attack.defenderDealt) + assertEquals(Vector2(2f, 0f), defaultAttackerUnit.getTile().position) + assertEquals(attackerCiv, defaultAttackerUnit.getTile().civilianUnit!!.civ) // captured unit + } + + @Test + fun `should transform settler into worker upon capture`() { + // given + val defenderUnit = testGame.addUnit("Settler", defenderCiv, testGame.getTile(Vector2(2f, 0f))) + + // when + val attack = Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defenderUnit)) + + // then + assertEquals(0, attack.attackerDealt) + assertEquals(0, attack.defenderDealt) + assertEquals(Vector2(2f, 0f), defaultAttackerUnit.getTile().position) + assertEquals(attackerCiv, defaultAttackerUnit.getTile().civilianUnit!!.civ) // captured unit + assertEquals("Worker", defaultAttackerUnit.getTile().civilianUnit!!.baseUnit.name) + } + @Test + fun `should earn Great General from combat`() { + // when + Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(5, attackerCiv.greatPeople.greatGeneralPoints) + assertEquals(4, defenderCiv.greatPeople.greatGeneralPoints) + } + + @Test + fun `should not earn Great General from combat against barbarians`() { + // given + val barbarianCiv = testGame.addBarbarianCiv() + val barbarianUnit = testGame.addUnit("Brute", barbarianCiv, testGame.getTile(Vector2.Y)) + + // when + Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(barbarianUnit)) + + // then + assertEquals(0, attackerCiv.greatPeople.greatGeneralPoints) + assertEquals(0, barbarianCiv.greatPeople.greatGeneralPoints) + } + + @Test + fun `should earn more Great General Points from uniques`() { + // given + val attackerUnit = testGame.addDefaultMeleeUnitWithUniques(attackerCiv, testGame.getTile(Vector2.Y), "[Great General] is earned [100]% faster") + + // when + Battle.attack(MapUnitCombatant(attackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(10, attackerCiv.greatPeople.greatGeneralPoints) + } + + @Test + fun `should conquer city when defeated and melee attacked`() { + // given + val defenderCity = testGame.addCity(defenderCiv, testGame.getTile(Vector2.Y), initialPopulation = 1) + defenderCity.health = 1 + + testGame.gameInfo.currentPlayerCiv = testGame.addCiv() // otherwise test crashes when puppetying city + testGame.gameInfo.currentPlayer = testGame.gameInfo.currentPlayerCiv.civName + + // when + Battle.attack(MapUnitCombatant(defaultAttackerUnit), CityCombatant(defenderCity)) + + // then + assertEquals(attackerCiv, defenderCity.civ) + assertEquals(defenderCiv.civName, defenderCity.previousOwner) + assertEquals(defenderCiv.civName, defenderCity.foundingCiv) + } + + @Test + fun `should not conquer city when defeated and ranged attacked`() { + // given + val attackerUnit = testGame.addUnit("Archer", attackerCiv, testGame.getTile(Vector2(0f, 2f))) + val defenderCity = testGame.addCity(defenderCiv, testGame.getTile(Vector2.Y), initialPopulation = 1) + defenderCity.health = 1 + + // when + Battle.attack(MapUnitCombatant(attackerUnit), CityCombatant(defenderCity)) + + // then + assertEquals(defenderCiv, defenderCity.civ) + assertEquals(1, defenderCity.health) + } + + @Test + fun `should not conquer city when unit has 'unable to capture cities' unique`() { + // given + val attackerUnit = testGame.addDefaultMeleeUnitWithUniques(attackerCiv, testGame.getTile(Vector2(0f, 2f)), "Unable to capture cities") + val defenderCity = testGame.addCity(defenderCiv, testGame.getTile(Vector2.Y), initialPopulation = 1) + defenderCity.health = 1 + + // when + Battle.attack(MapUnitCombatant(attackerUnit), CityCombatant(defenderCity)) + + // then + assertEquals(defenderCiv, defenderCity.civ) + assertEquals(1, defenderCity.health) + } + + @Test + fun `should not gain XP when attacking defeated city`() { + // given + val attackerUnit = testGame.addUnit("Archer", attackerCiv, testGame.getTile(Vector2(0f, 2f))) + val defenderCity = testGame.addCity(defenderCiv, testGame.getTile(Vector2.Y), initialPopulation = 1) + defenderCity.health = 1 + + // when + Battle.attack(MapUnitCombatant(attackerUnit), CityCombatant(defenderCity)) + + // then + assertEquals(0, attackerUnit.promotions.XP) + } + + @Test + fun `should earn prizes when defeating units`() { + // given + val attackerPolicy = testGame.createPolicy( + "Earn [100]% of killed [Military] unit's [Strength] as [Culture]", + "Earn [100]% of killed [Military] unit's [Strength] as [Gold]" + ) + attackerCiv.policies.adopt(attackerPolicy, true) + + defaultDefenderUnit.health = 1 + + // when + Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(8, attackerCiv.gold) + assertEquals(8, attackerCiv.policies.storedCulture) + } + + @Test + fun `should heal when defeating units if has unique`() { + // given + val attackerUnit = testGame.addDefaultRangedUnitWithUniques(attackerCiv, testGame.getTile(Vector2.Y), "Heals [10] damage if it kills a unit") + attackerUnit.health = 90 + attackerUnit.currentMovement = 2f + + defaultDefenderUnit.health = 1 + + // when + Battle.attack(MapUnitCombatant(attackerUnit), MapUnitCombatant(defaultDefenderUnit)) + + // then + assertEquals(100, attackerUnit.health) + } +} diff --git a/tests/src/com/unciv/logic/battle/TargetHelperTest.kt b/tests/src/com/unciv/logic/battle/TargetHelperTest.kt index a4abbd5990..ef96e42a21 100644 --- a/tests/src/com/unciv/logic/battle/TargetHelperTest.kt +++ b/tests/src/com/unciv/logic/battle/TargetHelperTest.kt @@ -340,14 +340,9 @@ class TargetHelperTest { @Test fun `should get no attackable tiles when has cannot attack unique`() { // given - val attackerTile = testGame.setTileTerrain(Vector2.Zero, Constants.ocean) - val defenderTile = testGame.setTileTerrain(Vector2(1f, 1f), Constants.ocean) - - val attackerUnit = testGame.addUnit("Carrier", attackerCiv, attackerTile) + val attackerUnit = testGame.addDefaultMeleeUnitWithUniques(attackerCiv, testGame.getTile(Vector2.Zero), "Cannot attack") attackerUnit.currentMovement = 2f - testGame.addUnit("Destroyer", defenderCiv, defenderTile) - - + testGame.addUnit("Warrior", defenderCiv, testGame.getTile(Vector2.X)) // when val attackableEnemies = TargetHelper.getAttackableEnemies(attackerUnit, attackerUnit.movement.getDistanceToTiles()) diff --git a/tests/src/com/unciv/testing/GdxTestRunner.java b/tests/src/com/unciv/testing/GdxTestRunner.java index 1878dcf854..e157bd0085 100644 --- a/tests/src/com/unciv/testing/GdxTestRunner.java +++ b/tests/src/com/unciv/testing/GdxTestRunner.java @@ -33,7 +33,7 @@ import static org.mockito.Mockito.*; public class GdxTestRunner extends BlockJUnit4ClassRunner implements ApplicationListener { - private final Map invokeInRender = new HashMap(); + private final Map invokeInRender = new HashMap<>(); public GdxTestRunner(Class klass) throws InitializationError { super(klass); diff --git a/tests/src/com/unciv/testing/TestGame.kt b/tests/src/com/unciv/testing/TestGame.kt index 5bb352193c..8c11ccebdd 100644 --- a/tests/src/com/unciv/testing/TestGame.kt +++ b/tests/src/com/unciv/testing/TestGame.kt @@ -45,6 +45,7 @@ class TestGame { UncivGame.Current = UncivGame() // And the settings can be reached for the locale used in .tr() UncivGame.Current.settings = GameSettings() + UncivGame.Current.gameInfo = gameInfo // Create a new ruleset we can easily edit, and set the important variables of gameInfo RulesetCache.loadRulesets(noMods = true) @@ -144,6 +145,16 @@ class TestGame { return civInfo } + fun addBarbarianCiv() : Civilization { + val barbarianCivilization = Civilization(Constants.barbarians) + val nation = Nation() + nation.name = Constants.barbarians + barbarianCivilization.nation = nation + barbarianCivilization.gameInfo = gameInfo + gameInfo.civilizations.add(barbarianCivilization) + return barbarianCivilization + } + fun addCity( civInfo: Civilization, tile: Tile, @@ -202,6 +213,22 @@ class TestGame { return obj } + fun addDefaultMeleeUnitWithUniques(civInfo: Civilization, tile: Tile, vararg uniques: String) : MapUnit { + val createBaseUnit = createBaseUnit("Sword", *uniques) + createBaseUnit.movement = 2 + createBaseUnit.strength = 8 + return this.addUnit(createBaseUnit.name, civInfo, tile) + } + + fun addDefaultRangedUnitWithUniques(civInfo: Civilization, tile: Tile, vararg uniques: String) : MapUnit { + val createBaseUnit = createBaseUnit("Archery", *uniques) + createBaseUnit.movement = 2 + createBaseUnit.strength = 5 + createBaseUnit.rangedStrength = 7 + createBaseUnit.range = 2 + return this.addUnit(createBaseUnit.name, civInfo, tile) + } + fun createBaseUnit(unitType: String = createUnitType().name, vararg uniques: String) = createRulesetObject(ruleset.units, *uniques) { val baseUnit = BaseUnit()