Commit e8f1d3c3 authored by Carl Johan Gützkow's avatar Carl Johan Gützkow 🎮
Browse files

Merge branch 'feat/terrain' into 'master'

feat: add terrain and bonuses to units

Closes #17

See merge request !23
parents 746434a8 5680bc3f
Pipeline #173278 passed with stages
in 38 seconds
package edu.ntnu.idatt2001.carljgu;
/**
* En enum that represents the different terrain
* that the battles can occur on.
*
* @author Carl Gützkow
* @version 1.1 09.04.2022
*/
public enum Terrain {
HILL,
PLAINS,
FOREST;
}
package edu.ntnu.idatt2001.carljgu;
package edu.ntnu.idatt2001.carljgu.battle;
import edu.ntnu.idatt2001.carljgu.Terrain;
import edu.ntnu.idatt2001.carljgu.units.Army;
import edu.ntnu.idatt2001.carljgu.units.Unit;
import java.util.ArrayList;
......@@ -25,6 +26,7 @@ public class Battle {
private final Army armyOne;
private final Army armyTwo;
private Army winner = null;
private final Terrain terrain;
private ArrayList<String> attackLog;
/**
......@@ -35,12 +37,13 @@ public class Battle {
* @throws IllegalArgumentException thrown if one of the armies are empty.
* @throws NullPointerException thrown if one of the armies are null object
*/
public Battle(Army armyOne, Army armyTwo) throws IllegalArgumentException {
public Battle(Army armyOne, Army armyTwo, Terrain terrain) throws IllegalArgumentException {
if (armyOne == null || armyTwo == null) throw new NullPointerException("Army is not defined");
if (!armyOne.hasUnits() || !armyTwo.hasUnits()) throw new IllegalArgumentException("Army can not be empty");
if (Objects.equals(armyOne, armyTwo)) throw new IllegalArgumentException("Armies can not be the same");
this.armyOne = armyOne;
this.armyTwo = armyTwo;
this.terrain = terrain;
this.attackLog = new ArrayList<>();
}
......@@ -65,6 +68,16 @@ public class Battle {
return attackLog;
}
/**
* Gets the terrain where
* the attack occurs.
*
* @return terrain - Terrain - the specified terrain.
*/
public Terrain getTerrain() {
return terrain;
}
/**
* Simulate a battle.
* The attacking army is chosen at random.
......@@ -100,7 +113,7 @@ public class Battle {
Unit attacker = attackingArmy.getRandom();
Unit defender = defendingArmy.getRandom();
attackLog.add(new SimulationAttack(attackingArmy, attacker, defendingArmy, defender).toString());
attackLog.add(new SimulationAttack(attackingArmy, attacker, defendingArmy, defender, terrain).toString());
}
winner = (armyOne.hasUnits()) ? armyOne : armyTwo;
......
package edu.ntnu.idatt2001.carljgu;
package edu.ntnu.idatt2001.carljgu.battle;
import edu.ntnu.idatt2001.carljgu.Terrain;
import edu.ntnu.idatt2001.carljgu.units.Army;
import edu.ntnu.idatt2001.carljgu.units.Unit;
......@@ -9,7 +10,7 @@ import edu.ntnu.idatt2001.carljgu.units.Unit;
* logging out this information.
*
* @author Carl Gützkow
* @version 1.1 28.03.2022
* @version 1.2 09.04.2022
*/
public class SimulationAttack {
......@@ -17,6 +18,7 @@ public class SimulationAttack {
private final Army defendingArmy;
private final Unit attacker;
private final Unit defender;
private final Terrain terrain;
private final int damageDealt;
/**
......@@ -33,9 +35,10 @@ public class SimulationAttack {
* @param attacker Unit - the attacker performing its attack on the defender
* @param defendingArmy Army - the army with the unit defending from the attacker
* @param defender Unit - the unit defending from the attackers attack
* @param terrain Terrain - the terrain where the attack occurs.
* @throws IllegalArgumentException thrown if the attacker is not part of the attacking army or the defender is not part of the defending army.
*/
public SimulationAttack(Army attackingArmy, Unit attacker, Army defendingArmy, Unit defender)
public SimulationAttack(Army attackingArmy, Unit attacker, Army defendingArmy, Unit defender, Terrain terrain)
throws IllegalArgumentException {
if (!attackingArmy.getAllUnits().contains(attacker) || !defendingArmy.getAllUnits().contains(defender))
throw new IllegalArgumentException("Unit is not part of the corresponding army");
......@@ -43,7 +46,8 @@ public class SimulationAttack {
this.defendingArmy = defendingArmy;
this.attacker = attacker;
this.defender = defender;
this.damageDealt = attacker.attack(defender);
this.terrain = terrain;
this.damageDealt = attacker.attack(defender, terrain);
if (defender.getHealth() <= 0) defendingArmy.remove(defender);
}
......@@ -99,6 +103,16 @@ public class SimulationAttack {
return damageDealt;
}
/**
* Gets the terrain where
* the attack occurs.
*
* @return terrain - Terrain - the specified terrain.
*/
public Terrain getTerrain() {
return terrain;
}
/**
* Overrides the toString() method
* and returns an output used in the
......@@ -106,7 +120,7 @@ public class SimulationAttack {
* name of the attacking and defending army,
* unit information like name and class
* as well as how much damage was outputted
* from the attack. In addition the
* from the attack. In addition, the
* defender's health after the attack is shown
*
* @return
......
package edu.ntnu.idatt2001.carljgu.client;
import edu.ntnu.idatt2001.carljgu.Battle;
import edu.ntnu.idatt2001.carljgu.Terrain;
import edu.ntnu.idatt2001.carljgu.battle.Battle;
import edu.ntnu.idatt2001.carljgu.FileExtensionException;
import edu.ntnu.idatt2001.carljgu.ArmyFileHandler;
import edu.ntnu.idatt2001.carljgu.units.Army;
......@@ -121,7 +122,7 @@ public class BattleController implements Initializable {
armyOne = new Army(armies[0].getName(), unitsOne);
armyTwo = new Army(armies[1].getName(), unitsTwo);
battle = new Battle(armyOne, armyTwo);
battle = new Battle(armyOne, armyTwo, Terrain.PLAINS);
attackList.getItems().clear();
......
package edu.ntnu.idatt2001.carljgu.units;
import edu.ntnu.idatt2001.carljgu.Terrain;
/**
* The abstract class Unit.
* A unit object should not be created because all units
......@@ -7,7 +9,7 @@ package edu.ntnu.idatt2001.carljgu.units;
* Since it is abstract a Unit object can not be created.
*
* @author Carl Gützkow
* @version 1.3 01.04.2022
* @version 1.4 09.04.2022
*/
public abstract class Unit {
......@@ -53,8 +55,8 @@ public abstract class Unit {
* @param unit Unit - enemy of any unit subclass.
* @return totalDamage - int - the amount of damage the attacker inflicted
*/
public int attack(Unit unit) {
int totalDamage = this.getAttack() + this.getAttackBonus() - unit.getArmor() - unit.getResistBonus();
public int attack(Unit unit, Terrain terrain) {
int totalDamage = this.getAttack() + this.getAttackBonus(terrain) - unit.getArmor() - unit.getResistBonus(terrain);
if (totalDamage > 0) {
unit.setHealth(unit.getHealth() - totalDamage);
return totalDamage;
......@@ -109,6 +111,7 @@ public abstract class Unit {
/**
* Overrides toString() method from Object.
*
* @return String - string representation of object
*/
@Override
......@@ -122,20 +125,25 @@ public abstract class Unit {
/**
* Gets attack bonus.
* Abstract method that is to be defined in subclasses.
*
* @param terrain Terrain - the terrain of the attack
* @return the attack bonus
*/
public abstract int getAttackBonus();
public abstract int getAttackBonus(Terrain terrain);
/**
* Gets resist bonus.
* Abstract method that is to be defined in subclasses.
*
* @param terrain Terrain - the terrain of the attack
* @return the resist bonus
*/
public abstract int getResistBonus();
public abstract int getResistBonus(Terrain terrain);
/**
* Returns a deep copy of
* the object.
* Abstract method that is to defined in subclasses.
*
* @return unit - Unit - a deep copy of the unit
*/
......
package edu.ntnu.idatt2001.carljgu.units.specialized;
import edu.ntnu.idatt2001.carljgu.Terrain;
import edu.ntnu.idatt2001.carljgu.units.Unit;
/**
......@@ -8,12 +9,12 @@ import edu.ntnu.idatt2001.carljgu.units.Unit;
* the superclass.
*
* @author Carl Gützkow
* @version 1.2 04.02.2022
* @version 1.3 09.04.2022
*/
public class CavalryUnit extends Unit {
/*
Constants are defined here and given an integer in constructor
Constants are defined here and given an integer
for structure and simplification if they are to be changed.
Constant meleeAttackBonus is used to calculate attack bonus.
Constant resistBonus is used to return resist bonus.
......@@ -22,9 +23,11 @@ public class CavalryUnit extends Unit {
this variable changes the first time it is run and therefore
the variable can not be a constant.
*/
private int usedAttackBonus;
private final int meleeAttackBonus;
private final int resistBonus;
private int usedAttackBonus = 6;
private final int MELEE_ATTACK_BONUS = 2;
private final int RESIST_BONUS = 1;
private final int PLAINS_BONUS = 5;
private final int FOREST_BONUS = 0;
/**
* Instantiates a new Cavalry unit.
......@@ -37,9 +40,6 @@ public class CavalryUnit extends Unit {
*/
public CavalryUnit(String name, int health, int attack, int armor) {
super(name, health, attack, armor);
this.usedAttackBonus = 6;
this.meleeAttackBonus = 2;
this.resistBonus = 1;
}
/**
......@@ -61,25 +61,32 @@ public class CavalryUnit extends Unit {
* is used to simulate the first charge. After that the
* attack bonus for melee is used instead throughout
* the CavalryUnit's lifetime.
* The bonus is also increased if the terrain is plains.
*
* @param terrain Terrain - the terrain where the attack occurs.
* @return attackBonus - int - value of the attack bonus
* used when calculating an enemy unit's health in an attack.
*/
@Override
public int getAttackBonus() {
public int getAttackBonus(Terrain terrain) {
int attackBonus = usedAttackBonus;
usedAttackBonus = meleeAttackBonus;
usedAttackBonus = MELEE_ATTACK_BONUS;
attackBonus += (terrain == Terrain.PLAINS) ? PLAINS_BONUS : 0;
return attackBonus;
}
/**
* Overrides the abstract method getResistBonus from Unit.
* The resist bonus is constant.
* The resist bonus is increased if the terrain is forest.
*
* @param terrain Terrain - the terrain where the attack occurs.
* @return resistBonus - int - value of the resist bonus
* used when calculating this object's health in an attack.
*/
@Override
public int getResistBonus() {
return resistBonus;
public int getResistBonus(Terrain terrain) {
return (terrain == Terrain.FOREST) ? FOREST_BONUS : RESIST_BONUS;
}
/**
......
......@@ -8,7 +8,7 @@ import edu.ntnu.idatt2001.carljgu.units.Unit;
* the superclass Unit as they are defined in CavalryUnit.
*
* @author Carl Gützkow
* @version 1.2 01.04.2022
* @version 1.3 09.04.2022
*/
public class CommanderUnit extends CavalryUnit {
......
package edu.ntnu.idatt2001.carljgu.units.specialized;
import edu.ntnu.idatt2001.carljgu.Terrain;
import edu.ntnu.idatt2001.carljgu.units.Unit;
/**
......@@ -8,18 +9,19 @@ import edu.ntnu.idatt2001.carljgu.units.Unit;
* the superclass.
*
* @author Carl Gützkow
* @version 1.2 01.04.2022
* @version 1.3 09.04.2022
*/
public class InfantryUnit extends Unit {
/*
Constants are defined here and given an integer in constructor
Constants are defined here
for structure and simplification if they are to be changed.
Constant attackBonus is the attack bonus for the object's lifetime.
Constant resistBonus is the resistance bonus for the object's lifetime.
*/
private final int attackBonus;
private final int resistBonus;
private final int ATTACK_BONUS = 2;
private final int RESIST_BONUS = 1;
private final int FOREST_BONUS = 3;
/**
* Instantiates a new Infantry unit.
......@@ -32,8 +34,6 @@ public class InfantryUnit extends Unit {
*/
public InfantryUnit(String name, int health, int attack, int armor) {
super(name, health, attack, armor);
this.attackBonus = 2;
this.resistBonus = 1;
}
/**
......@@ -51,25 +51,28 @@ public class InfantryUnit extends Unit {
/**
* Overrides the abstract method getAttackBonus from Unit.
* The attack bonus is constant.
* The attack bonus is increased if the terrain is forest.
*
* @param terrain Terrain - the terrain where the attack occurs.
* @return attackBonus - int - value of the attack bonus
* used when calculating an enemy unit's health in an attack.
*/
@Override
public int getAttackBonus() {
return attackBonus;
public int getAttackBonus(Terrain terrain) {
return (terrain == Terrain.FOREST) ? ATTACK_BONUS + FOREST_BONUS : ATTACK_BONUS;
}
/**
* Overrides the abstract method getResistBonus from Unit.
* The resistance bonus is constant.
* The resistance bonus is increased if the terrain is forest.
*
* @param terrain Terrain - the terrain where the attack occurs.
* @return resistBonus - int - value of the resist bonus
* used when calculating this object's health in an attack.
*/
@Override
public int getResistBonus() {
return resistBonus;
public int getResistBonus(Terrain terrain) {
return (terrain == Terrain.FOREST) ? RESIST_BONUS + FOREST_BONUS : ATTACK_BONUS;
}
/**
......
package edu.ntnu.idatt2001.carljgu.units.specialized;
import edu.ntnu.idatt2001.carljgu.Terrain;
import edu.ntnu.idatt2001.carljgu.units.Unit;
/**
......@@ -8,25 +9,27 @@ import edu.ntnu.idatt2001.carljgu.units.Unit;
* the superclass.
*
* @author Carl Gützkow
* @version 1.2 01.04.2022
* @version 1.3 09.04.2022
*/
public class RangedUnit extends Unit {
/*
Constants are defined here and given an integer in constructor
for structure and simplification if they are to be changed.
Constant attackBonus is the attack bonus for the object's lifetime.
Constant initialResistBonus is the first resistance bonus.
Constant finalResistBonus is the default resistance bonus after a number
of times resisted.
Integer timesResisted is used to calculate the resistance bonus.
In the method getResistBonus this variable increases for each time the
method is called. Therefore, it can not be a constant.
*/
private final int attackBonus;
private final int initialResistBonus;
private final int finalResistBonus;
private int timesResisted;
Constants are defined here
for structure and simplification if they are to be changed.
Constant attackBonus is the attack bonus for the object's lifetime.
Constant initialResistBonus is the first resistance bonus.
Constant finalResistBonus is the default resistance bonus after a number
of times resisted.
Integer timesResisted is used to calculate the resistance bonus.
In the method getResistBonus this variable increases for each time the
method is called. Therefore, it can not be a constant.
*/
private final int ATTACK_BONUS = 3;
private final int INITIAL_RESIST_BONUS = 6;
private final int FINAL_RESIST_BONUS = 2;
private final int HILL_BONUS = 3;
private final int FOREST_PENALTY = 2;
private int timesResisted = 0;
/**
* Instantiates a new Ranged unit.
......@@ -39,10 +42,6 @@ public class RangedUnit extends Unit {
*/
public RangedUnit(String name, int health, int attack, int armor) {
super(name, health, attack, armor);
this.attackBonus = 3;
this.initialResistBonus = 6;
this.finalResistBonus = 2;
this.timesResisted = 0;
}
/**
......@@ -60,13 +59,18 @@ public class RangedUnit extends Unit {
/**
* Overrides the abstract method getAttackBonus from Unit.
* The attack bonus is constant.
* The attack bonus is decreased if the terrain is forest
* and increased if the terrain is hills.
*
* @param terrain Terrain - the terrain where the attack occurs.
* @return attackBonus - int - value of the attack bonus
* used when calculating an enemy unit's health in an attack.
*/
@Override
public int getAttackBonus() {
public int getAttackBonus(Terrain terrain) {
int attackBonus = ATTACK_BONUS;
if (terrain == Terrain.HILL) attackBonus += HILL_BONUS;
if (terrain == Terrain.FOREST) attackBonus -= FOREST_PENALTY;
return attackBonus;
}
......@@ -80,12 +84,13 @@ public class RangedUnit extends Unit {
* The third time and all other times this method is,
* the default finalResistBonus is used.
*
* @param terrain Terrain - the terrain where the attack occurs.
* @return resistBonus - int - value of the resist bonus
* used when calculating this object's health in an attack.
*/
@Override
public int getResistBonus() {
int resistBonus = timesResisted >= 2 ? finalResistBonus : initialResistBonus - 2 * timesResisted;
public int getResistBonus(Terrain terrain) {
int resistBonus = timesResisted >= 2 ? FINAL_RESIST_BONUS : INITIAL_RESIST_BONUS - 2 * timesResisted;
timesResisted++;
return resistBonus;
}
......
......@@ -7,4 +7,5 @@ module edu.ntnu.idatt2001.carljgu.client {
exports edu.ntnu.idatt2001.carljgu.client ;
exports edu.ntnu.idatt2001.carljgu.units ;
exports edu.ntnu.idatt2001.carljgu ;
exports edu.ntnu.idatt2001.carljgu.battle;
}
......@@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import edu.ntnu.idatt2001.carljgu.battle.Battle;
import edu.ntnu.idatt2001.carljgu.units.*;
import edu.ntnu.idatt2001.carljgu.units.specialized.InfantryUnit;
import java.util.ArrayList;
......@@ -35,7 +36,7 @@ public class BattleTest {
public void exception_if_any_of_the_armies_are_empty() {
Army emptyArmy = new Army("empty army");
try {
new Battle(armyWithUnit, emptyArmy);
new Battle(armyWithUnit, emptyArmy, Terrain.PLAINS);
fail();
} catch (IllegalArgumentException exception) {
assertEquals("Army can not be empty", exception.getMessage());
......@@ -45,7 +46,7 @@ public class BattleTest {
@Test
public void exception_if_armies_are_equal() {
try {
Battle battle = new Battle(armyWithUnit, armyWithUnit);
Battle battle = new Battle(armyWithUnit, armyWithUnit, Terrain.PLAINS);
fail();
} catch (IllegalArgumentException exception) {
assertEquals("Armies can not be the same", exception.getMessage());
......@@ -61,7 +62,7 @@ public class BattleTest {
Army secondArmy = new Army("second army");
secondArmy.addUnit(new InfantryUnit("infantry", 10));
Battle battle = new Battle(armyWithUnit, secondArmy);
Battle battle = new Battle(armyWithUnit, secondArmy, Terrain.PLAINS);
Army winningArmy = battle.simulate();
assertTrue(winningArmy.hasUnits());
......@@ -75,7 +76,7 @@ public class BattleTest {
Army secondArmy = new Army("second army");
secondArmy.addUnit(new InfantryUnit("infantry", 10));
Battle battle = new Battle(armyWithUnit, secondArmy);
Battle battle = new Battle(armyWithUnit, secondArmy, Terrain.PLAINS);
battle.simulate();
assertThrows(UnsupportedOperationException.class, () -> battle.simulate());
......
......@@ -80,12 +80,12 @@ public class WarUnitTest {
attacker =
new Unit("Unit", 10, attackerArmor, 0) {
@Override
public int getAttackBonus() {
public int getAttackBonus(Terrain terrain) {
return 0;
}
@Override
public int getResistBonus() {
public int getResistBonus(Terrain terrain) {
return 0;
}
......@@ -98,12 +98,12 @@ public class WarUnitTest {
defender =
new Unit("Unit", 10, 0, defenderArmor) {
@Override
public int getAttackBonus() {
public int getAttackBonus(Terrain terrain) {
return 0;
}
@Override
public int getResistBonus() {
public int getResistBonus(Terrain terrain) {
return 0;
}
......@@ -119,7 +119,7 @@ public class WarUnitTest {
instantiateAttackerAndDefender(10, 0);
int startHealth = defender.getHealth();
attacker.attack(defender);
attacker.attack(defender, Terrain.PLAINS);
int endHealth = defender.getHealth();
assertEquals(10, startHealth - endHealth);
......@@ -130,7 +130,7 @@ public class WarUnitTest {
instantiateAttackerAndDefender(5, 10);
int startHealth = defender.getHealth();
attacker.attack(defender);
attacker.attack(defender, Terrain.PLAINS);
int endHealth = defender.getHealth();
assertEquals(startHealth, endHealth);
......
......@@ -3,29 +3,55 @@ package edu.ntnu.idatt2001.carljgu.specialized;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.ntnu.idatt2001.carljgu.Terrain;
import edu.ntnu.idatt2001.carljgu.units.specialized.CavalryUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;