diff --git a/README.md b/README.md index e7d6320..420b632 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,56 @@ # Final Reality -Final Reality is a simplified clone of the renowned game, Final Fantasy. Its main purpose is to +>Final Reality is a simplified clone of the renowned game, Final Fantasy. Its main purpose is to serve as an educational tool, teaching foundational programming concepts. -This README is yours to complete it. Take this opportunity to describe your contributions, the -design decisions you've made, and any other information you deem necessary. +*This README is yours to complete it. Take this opportunity to describe your contributions, the +design decisions you've made, and any other information you deem necessary.* + +## 1° Assigment +### Partial Assigment 1 +- Made sure to not use words "Unit", "Class", "def" for naming, to avoid problems with existing +fields, instead used "Units", "Profession", and "defense". +- Packages/folders for Weapons, Professions, and Units which correspond to Characters and Enemies. +- Units trait for common stats between enemies and player characters. +- ICharacter trait adds profession field and Enemy trait adds damage. +- Abstract class for empty held weapon and isAlive method, common in all characters. +- Party class, with default dummy characters, can ask members if they are alive to determine if whole party +is alive, and can add members up to three. +- Profession trait with name implemented by each profession extending from abstract class +constructor, which will be easier to compare later. +- Weapon trait for common attributes, and two abstract classes for common and magic weapons with magic damage. +- Tests with munit.FunSuite, mainly of constructors, making sure to have good coverage. +### Partial Assigment 2 +- Programmer class in charge to manage actionbar changes and determining which enemy or character can get a turn +- Programmer can add and remove units (max 3 party members, max 5 enemies per battle) +- Programmer for now has an arbitrary amount k to increase the actionbar each step +- They ideal combat manager will step the programmer, then ask if any member can perform a turn, and if true, +perform an action with returned unit, otherwise keep stepping to increase actionbars +- Maybe one programmer object can be used for each different battle, making sure to destroy the leftover object +### Final Assigment 1 +- Started the privatization of values and methods, making sure that var type values where at least protected, while +val types can be public. And added methods to view some now private or protected values. +- Decided to add a maxLife value for units, it could be useful in the future for not healing over the maximum. +- Used require to avoid using illegal values in constructors. +- Added methods for character attacking enemies, enemies attacking characters, character taking damage by enemies, +and enemies taking damage by characters. +- Added more tests for more code coverage. + +## 2° Assigment +### Partial Assigment 3 +- Added two exceptions for invalid weapon for profession and weapon already being used by someone else. +- Added methods to characters and weapons using double dispatch to ask the weapon itself for equipping and even +throwing exceptions if required. The idea for the future is to check if weapons can be equipped, then equip +the weapon later, but if trying to force equip something that should not be equipped, exceptions get thrown +for equipping something null, for weapons already being used by someone else, and by invalid weapons for a +certain profession. +- Added methods for un-equipping weapons and made sure to update the max action bar after equipping and +un-equipping weapons. +- Also added method to weapons to get the owner for comparing. +### Partial Assigment 4 +- I already had implemented un-equipping weapons, max of 3 party members, and enemies can only attack players, +and players can only attack enemies. But still added a bit of more code to make it look better and even added +more tests. This project is licensed under the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/). \ No newline at end of file diff --git a/src/main/scala/combatsystem/Programmer.scala b/src/main/scala/combatsystem/Programmer.scala new file mode 100644 index 0000000..de15ef4 --- /dev/null +++ b/src/main/scala/combatsystem/Programmer.scala @@ -0,0 +1,112 @@ +package combatsystem +import unit.{Enemy, ICharacter, IParty, Units} + +import scala.collection.mutable.ListBuffer + +/** Class representing a programmer of turns. + * + * Uses a [[unit.Party]] and a ListBuffer type [[unit.Enemy]] to up to 5 enemies, has many methods to ask and change the actionbar of the units, and then check and retrieve who could perform an action + * + * @param party A Party of Characters + * @param enemies A ListBuffer of enemies + * + * @constructor Creates a new programmer for turns checking. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.1 + */ +class Programmer(private var party: IParty, private val enemies: ListBuffer[Enemy]) { + /**An arbitrary amount to increase the actionbar*/ + protected var k: Int = 3 + /**A ListBuffer to save the units that have completed their actionbars and can perform a turn, initialized empty*/ + private var readyList: ListBuffer[Units] = ListBuffer() //required to be var because otherwise cant be sorted + //When the constructors builds, make sure to set all units actionbar to zero in case they already had some from another battle + party.step(0) + for (n <- enemies) n.setActionBar(0) + + /** Adds a Character to the programmer, who then makes sure add to the party and set its actionbar to zero + * + * @param who The character to add to the programmer + */ + def add(who: ICharacter): Unit = { + party.add(who) + who.setActionBar(0) + } + + /** Adds an enemy to the programmer, who then makes sure add to the ListBuffer and set its actionbar to zero. + * Can only add an enemy if there is space (max 5 enemies) + * @param who The enemy to add to the programmer + */ + def add(who: Enemy): Unit = { + if (enemies.length < 5) { + enemies.addOne(who) + who.setActionBar(0) + } + } + + /** Removes a specific character from the programmer, who makes sure to remove it from the party too + * + * @param who The character to remove from the programmer + */ + def remove(who: ICharacter): Unit = { + party.remove(who) + } + + /** Removes a specific enemy from the programmer + * + * @param who The enemy to remove from the programmer + */ + def remove(who: Enemy): Unit = { + enemies -= who + } + + /** Method to make progress in the actionbar on each party member and enemy in the ListBuffer*/ + def step(): Unit = { + party.step(k) + for (n <- enemies) n.setActionBar(k) + } + + /** Checks all the units actionbars, and if they completed it, gets added to readyList ListBuffer + * + * @return True if any unit can perform a turn + */ + def anyTurn: Boolean = { + readyList.clear() //First clears the list to avoid duplicates + readyList = party.anyTurnForProgrammer(readyList) + for (n <- enemies) { + if (n.getActionBar >= 0) readyList.addOne(n) //Check getActionBar in EnemyClass for more details + } + //If at least one unit in list, someone can perform a turn, then returns true + if (readyList.nonEmpty) true + else false + } + + /**Sorts the readyList by actionbar, then extracts the one unit with more excess and resets its actionbar to zero + * + * @return The character or enemy that can perform an action, prioritizing the one with more excess actionbar + */ + def getTurn: Units = { + readyList = readyList.sortWith(_.getActionBar < _.getActionBar) //this way, the last one in list is the one with priority to have a turn + val result: Units = readyList.last //character or enemy to return + readyList.last.setActionBar(0) + readyList.dropRightInPlace(1) + result + } + + /** Gets the stored party + * + * @return The Programmer's Party + */ + def getParty: IParty = { + party + } + + /** Gets the stored enemies + * + * @return The Programmer's Enemies List + */ + def getEnemies: ListBuffer[Enemy] = { + enemies + } +} \ No newline at end of file diff --git a/src/main/scala/profession/AbstractProfession.scala b/src/main/scala/profession/AbstractProfession.scala new file mode 100644 index 0000000..ae7937f --- /dev/null +++ b/src/main/scala/profession/AbstractProfession.scala @@ -0,0 +1,16 @@ +package profession + +/** Abstract class representing professions. + * + * Professions name must be provided by the subclasses. + * + * Used by [[profession.Warrior]], [[profession.Paladin]], [[profession.Ninja]], [[profession.BlackMage]], and [[profession.WhiteMage]] + * + * @param name The name of the profession. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class AbstractProfession(val name:String) extends Profession { +} diff --git a/src/main/scala/profession/BlackMage.scala b/src/main/scala/profession/BlackMage.scala new file mode 100644 index 0000000..42f2e6f --- /dev/null +++ b/src/main/scala/profession/BlackMage.scala @@ -0,0 +1,15 @@ +package profession + +/** Class representing Black Mage Profession. + * + * Sends "Black Mage" for name to [[profession.AbstractProfession]] superclass + * + * @constructor Creates a new Black Mage Profession. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class BlackMage extends AbstractProfession("Black Mage") { + +} diff --git a/src/main/scala/profession/Ninja.scala b/src/main/scala/profession/Ninja.scala new file mode 100644 index 0000000..c09f6d6 --- /dev/null +++ b/src/main/scala/profession/Ninja.scala @@ -0,0 +1,15 @@ +package profession + +/** Class representing Ninja Profession. + * + * Sends "Ninja" for name to [[profession.AbstractProfession]] superclass + * + * @constructor Creates a new Ninja Profession. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class Ninja extends AbstractProfession("Ninja") { + +} diff --git a/src/main/scala/profession/Paladin.scala b/src/main/scala/profession/Paladin.scala new file mode 100644 index 0000000..f0c1d00 --- /dev/null +++ b/src/main/scala/profession/Paladin.scala @@ -0,0 +1,15 @@ +package profession + +/** Class representing Paladin Profession. + * + * Sends "Paladin" for name to [[profession.AbstractProfession]] superclass + * + * @constructor Creates a new Paladin Profession. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class Paladin extends AbstractProfession("Paladin") { + +} diff --git a/src/main/scala/profession/Profession.scala b/src/main/scala/profession/Profession.scala new file mode 100644 index 0000000..0778cf1 --- /dev/null +++ b/src/main/scala/profession/Profession.scala @@ -0,0 +1,9 @@ +package profession + +/** Trait mainly used for type in constructors. + * Also provides a name for the profession. + * Used by [[profession.AbstractProfession]] + */ +trait Profession { + val name: String +} diff --git a/src/main/scala/profession/Warrior.scala b/src/main/scala/profession/Warrior.scala new file mode 100644 index 0000000..e3f1777 --- /dev/null +++ b/src/main/scala/profession/Warrior.scala @@ -0,0 +1,14 @@ +package profession + +/** Class representing Warrior Profession. + * + * Sends "Warrior" for name to [[profession.AbstractProfession]] superclass + * + * @constructor Creates a new Warrior Profession. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class Warrior extends AbstractProfession("Warrior") { +} diff --git a/src/main/scala/profession/WhiteMage.scala b/src/main/scala/profession/WhiteMage.scala new file mode 100644 index 0000000..87abfe1 --- /dev/null +++ b/src/main/scala/profession/WhiteMage.scala @@ -0,0 +1,15 @@ +package profession + +/** Class representing White Mage Profession. + * + * Sends "White Mage" for name to [[profession.AbstractProfession]] superclass + * + * @constructor Creates a new White Mage Profession. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class WhiteMage extends AbstractProfession("White Mage") { + +} diff --git a/src/main/scala/unit/AbstractCharacter.scala b/src/main/scala/unit/AbstractCharacter.scala new file mode 100644 index 0000000..dce9790 --- /dev/null +++ b/src/main/scala/unit/AbstractCharacter.scala @@ -0,0 +1,64 @@ +package unit + +import weapon.{InvalidWeaponException, UsedWeaponException, Weapon} + +/** Abstract class representing Characters. + * + * Used by [[unit.Character]], and [[unit.MagicCharacter]] + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.3 + */ +abstract class AbstractCharacter extends ICharacter { + /** The weapon this character is holding. + * + * Starts by default empty, meaning no held weapon. + */ + protected var heldweapon: Weapon = null + /* + /** Placeholder method to equip a weapon */ + def placeholderEquipWeapon(weapon:Weapon) : Unit ={ + heldweapon = weapon + }*/ + /** Returns the equipped weapon */ + def getHeldWeapon : Weapon = heldweapon + + def isAlive: Boolean = { + if (life > 0) true + else false + } + + def getLife: Int = { + life + } + def getDefense: Int ={ + defense + } + + def attackAnEnemy(who:Enemy) : Unit = { + if(heldweapon != null) who.hurtByCharacter(this) //nothing happens if no weapon is held + } + def hurtByEnemy(who:Enemy) : Unit ={ + var howMuchWillItHurt : Int = who.getDamage - defense + if(howMuchWillItHurt < 0) howMuchWillItHurt = 0 + life -= howMuchWillItHurt + if(life < 0) life = 0 + } + + def canEquipWeapon(weapon:Weapon) : Boolean = { + if(weapon != null) weapon.canEquipTo(this) + else false + } + def removeWeapon(): Unit = { + if(heldweapon != null) { heldweapon.removeOwner() } + heldweapon = null + updateMaxActionbar() + } + def equipWeapon(weapon:Weapon) : Unit = { + removeWeapon() + weapon.equipTo(this) + heldweapon = weapon + updateMaxActionbar() + } +} diff --git a/src/main/scala/unit/Character.scala b/src/main/scala/unit/Character.scala new file mode 100644 index 0000000..ca885ac --- /dev/null +++ b/src/main/scala/unit/Character.scala @@ -0,0 +1,66 @@ +package unit + +import exceptions.Require +import profession.Profession + +/** Class representing a Character. + * + * When initializing, one can skip the defense value, alternative constructor defaults it to 0. + * + * @param name The name of the character. + * @param life The life and maxLife of the character. + * @param defense The defense of the character. + * @param weight The weight of the character. + * @param profession The profession of the character. + * + * @constructor Creates a new Character. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.4 + */ +class Character(val name: String = "Unknown", + protected var life : Int = 0, + protected var defense: Int = 0, + val weight: Double = 0.1, + val profession:Profession) extends AbstractCharacter { + /** Alternative constructor if one where to skip defense, defaulting it to 0 */ + def this(x: String, l: Int, w: Double, p: Profession) = { + this(x, l, 0, w, p) + } + /** Max life value, initialized to the constructor life value */ + val maxLife : Int = life + Require.Stat(life,"life") atLeast 0 + Require.Stat(defense,"defense") atLeast 0 + require(weight>0,"number must be greater than zero") + /**The actionbar of the character, starts at zero*/ + private var actionbar : Double = 0 + /**The threshold to complete to consider a complete actionbar and be able to get a turn*/ + private var maxActionbar: Double = { + if (heldweapon != null) weight + (heldweapon.weight / 2) + else weight + } + + def updateMaxActionbar() : Unit = { //make sure when you could equip a weapon, to update maxActionBar + if (heldweapon != null) maxActionbar = weight + (heldweapon.weight / 2) + else maxActionbar = weight + } + + /** Method that compares actionbar with maxActionbar + * + * @return The difference between actionbar and maxActionbar, the excess must be greater or equal to zero + */ + def getActionBar: Double = { + actionbar - maxActionbar + } + + /** Method that adds k to character action bar. + * In case k = 0, actionbar goes back to zero. + * + * @param k Amount to change actionbar + */ + def setActionBar(k: Int): Unit = { + if(k != 0) actionbar += k + else actionbar = 0 + } +} diff --git a/src/main/scala/unit/DummyCharacter.scala b/src/main/scala/unit/DummyCharacter.scala new file mode 100644 index 0000000..f528c21 --- /dev/null +++ b/src/main/scala/unit/DummyCharacter.scala @@ -0,0 +1,24 @@ +package unit + +/** Class representing a special case Character + * + * This Dummy is used for empty party members, and also initializing weapons to have no owner. + * Methods implemented here should make it easier by not checking if there is a real character present. + * + * Extends from [[unit.Character]], with null profession to check empty members in party. + * + * @example isAlive method always returns false, ideal for checking party status with any empty party members. + * @constructor Creates a new Dummy. + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.1 + */ +class DummyCharacter extends Character(profession = null) { + life = 0 + + override def getActionBar: Double = { //so that it never can get a turn + -1 + } + + override def setActionBar(k: Int): Unit = { } //does nothing for no turn progress +} diff --git a/src/main/scala/unit/Enemy.scala b/src/main/scala/unit/Enemy.scala new file mode 100644 index 0000000..befbfeb --- /dev/null +++ b/src/main/scala/unit/Enemy.scala @@ -0,0 +1,19 @@ +package unit + +/** Trait mainly used for type in constructors. + * Also provides a damage value to enemies. + * Used by [[unit.EnemyClass]] + */ +trait Enemy extends Units{ + protected var damage: Int + /** Returns the damage value of the enemy */ + def getDamage: Int + /** Method that calls hurtByEnemy on target character + * @param who The character to attack + * */ + def attackACharacter(who:ICharacter) : Unit + /** Method that handles taking damage from character attack + * @param who The Character attacking the enemy + * */ + def hurtByCharacter(who:ICharacter) : Unit +} diff --git a/src/main/scala/unit/EnemyClass.scala b/src/main/scala/unit/EnemyClass.scala new file mode 100644 index 0000000..a8c8563 --- /dev/null +++ b/src/main/scala/unit/EnemyClass.scala @@ -0,0 +1,67 @@ +package unit + +import exceptions.Require + +/** Class representing an Enemy. + * + * @param name The name of the enemy. + * @param life The life and maxLife of the enemy. + * @param damage The damage of the enemy. + * @param defense The defense of the enemy. + * @param weight The weight of the enemy. + * @constructor Creates a new Enemy. + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.4 + */ +class EnemyClass(val name: String, + protected var life:Int, + protected var damage:Int, + protected var defense:Int, + val weight:Double) extends Enemy{ + def isAlive: Boolean = { + if (life > 0) true + else false + } + /** Max life value, initialized to the constructor life value */ + val maxLife : Int = life + Require.Stat(life,"life") atLeast 0 + Require.Stat(defense,"defense") atLeast 0 + Require.Stat(damage,"damage") atLeast 0 + require(weight>0,"number must be greater than zero") + /**The actionbar of the enemy, starts at zero*/ + private var actionbar : Double = 0 + + /** Method that compares actionbar with weight + * + * @return The difference between actionbar and weight; the excess must be greater or equal to zero + */ + def getActionBar: Double = { + actionbar - weight + } + + /** Method that adds k to enemy action bar. + * In case k = 0, actionbar goes back to zero. + * + * @param k Amount to change actionbar + */ + def setActionBar(k: Int): Unit = { + if(k != 0) actionbar += k + else actionbar = 0 + } + + def getLife: Int = life + def getDamage: Int = damage + def getDefense: Int = defense + + def attackACharacter(who:ICharacter) : Unit = { + who.hurtByEnemy(this) + } + def hurtByCharacter(who:ICharacter) : Unit ={ + val howMuchDamage : Int = who.getHeldWeapon.damage + var howMuchWillItHurt : Int = howMuchDamage - defense + if(howMuchWillItHurt < 0) howMuchWillItHurt = 0 + life -= howMuchWillItHurt + if(life < 0) life = 0 + } +} diff --git a/src/main/scala/unit/FullPartyException.scala b/src/main/scala/unit/FullPartyException.scala new file mode 100644 index 0000000..534de2c --- /dev/null +++ b/src/main/scala/unit/FullPartyException.scala @@ -0,0 +1,11 @@ +package unit + +/** Exception when trying to add a member to a full party. (max of 3 members) + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class FullPartyException(string : String) extends Exception(string){ + +} diff --git a/src/main/scala/unit/ICharacter.scala b/src/main/scala/unit/ICharacter.scala new file mode 100644 index 0000000..467f7bd --- /dev/null +++ b/src/main/scala/unit/ICharacter.scala @@ -0,0 +1,39 @@ +package unit +import profession.Profession +import weapon.{InvalidWeaponException, UsedWeaponException, Weapon} + +/** Trait mainly used for type in constructors. + * Also provides a profession to the character. + * Used by [[unit.AbstractCharacter]] + */ +trait ICharacter extends Units{ + val profession:Profession + + /** Updates the maxActionbar to the correct value */ + def updateMaxActionbar() : Unit + def getHeldWeapon : Weapon + /** Method that calls hurtByCharacter on target enemy; + * Can't attack and enemy if no weapon is held + * @param who The enemy to attack + * */ + def attackAnEnemy(who:Enemy) : Unit + /** Method that handles taking damage from enemy attack + * @param who The Enemy attacking the character + * */ + def hurtByEnemy(who:Enemy) : Unit + + /** Asks the weapon if trying to equip this weapon is valid + * @param weapon The weapon to equip + * @return Bool if its valid to equip the weapon */ + def canEquipWeapon(weapon:Weapon) : Boolean + /** Removes the held weapon from the Character. + * + * Does nothing if no weapon is held. */ + def removeWeapon() : Unit + /** Tries to equip the weapon to the character + * @param weapon The weapon to equip + * @throws InvalidWeaponException If trying to equip an invalid weapon for the corresponding profession + * @throws NullPointerException If trying to equip a null weapon + * @throws UsedWeaponException If the weapon is already being used by someone else */ + def equipWeapon(weapon:Weapon) : Unit +} diff --git a/src/main/scala/unit/IParty.scala b/src/main/scala/unit/IParty.scala new file mode 100644 index 0000000..7a2b199 --- /dev/null +++ b/src/main/scala/unit/IParty.scala @@ -0,0 +1,17 @@ +package unit + +import scala.collection.mutable.ListBuffer + +/** Trait mainly used for type checking. + * Used by [[unit.Party]] + */ +trait IParty { + protected var member1 : ICharacter + protected var member2 : ICharacter + protected var member3 : ICharacter + def add(who:ICharacter): Unit + def step(k : Int): Unit + def remove(who:ICharacter):Unit + def anyTurnForProgrammer(inlist:ListBuffer[Units]): ListBuffer[Units] + def getMembers : ListBuffer[ICharacter] +} diff --git a/src/main/scala/unit/MagicCharacter.scala b/src/main/scala/unit/MagicCharacter.scala new file mode 100644 index 0000000..cb6ea30 --- /dev/null +++ b/src/main/scala/unit/MagicCharacter.scala @@ -0,0 +1,76 @@ +package unit + +import exceptions.Require +import profession.Profession + +/** Class representing a Magic Character. + * + * When initializing, one can skip the defense value, alternative constructor defaults it to 0. + * + * @param name The name of the magic character. + * @param life The life and maxLife of the magic character. + * @param defense The defense of the magic character. + * @param weight The weight of the magic character. + * @param profession The profession of the magic character. + * @param mana The mana of the magic character. + * + * @constructor Creates a new Magic Character. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.4 + */ +class MagicCharacter(val name: String = "Unknown", + protected var life: Int = 0, + protected var defense: Int = 0, + val weight: Double = 0.1, + val profession: Profession, + private var mana:Int=1) extends AbstractCharacter { + /** Alternative constructor if one where to skip defense, defaulting it to 0 */ + def this(x: String, l: Int, w: Double, p: Profession, m: Int) = { + this(x, l, 0, w, p, m) + } + /** Max life value, initialized to the constructor life value */ + val maxLife : Int = life + /** Max mana value, initialized to the constructor mana value */ + val maxMana : Int = mana + Require.Stat(life,"life") atLeast 0 + Require.Stat(defense,"defense") atLeast 0 + require(weight>0,"number must be greater than zero") + Require.Stat(mana,"mana") atLeast 0 + /**The actionbar of the character, starts at zero*/ + private var actionbar : Double = 0 + /**The threshold to complete to consider a complete actionbar and be able to get a turn*/ + private var maxActionbar: Double = { + if (heldweapon != null) weight + (heldweapon.weight / 2) + else weight + } + + def updateMaxActionbar() : Unit = { //make sure when you could equip a weapon, to update maxActionBar + if (heldweapon != null) maxActionbar = weight + (heldweapon.weight / 2) + else maxActionbar = weight + } + + /** Method that compares actionbar with maxActionbar + * + * @return The difference between actionbar and maxActionbar, the excess must be greater or equal to zero + */ + def getActionBar: Double = { + actionbar - maxActionbar + } + + /** Method that adds k to character action bar. + * In case k = 0, actionbar goes back to zero. + * + * @param k Amount to change actionbar + */ + def setActionBar(k: Int): Unit = { + if(k != 0) actionbar += k + else actionbar = 0 + } + + /** Returns the mana of the character */ + def getMana: Int = { + mana + } +} \ No newline at end of file diff --git a/src/main/scala/unit/Party.scala b/src/main/scala/unit/Party.scala new file mode 100644 index 0000000..b68dc81 --- /dev/null +++ b/src/main/scala/unit/Party.scala @@ -0,0 +1,86 @@ +package unit + +import scala.collection.mutable.ListBuffer + +/** Class representing a Party. + * + * When initializing, the slots get filled with dummy characters if no character is inputted. + * + * @param member1 The first member in party. + * @param member2 The second member in party. + * @param member3 The third member in party. + * @constructor Creates a new Party. + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.2 + */ +class Party(protected var member1: ICharacter = new DummyCharacter, + protected var member2: ICharacter = new DummyCharacter, + protected var member3: ICharacter = new DummyCharacter) extends IParty { + + /** Adds a member to one of the party slots + * + * Check member slots from 1 to 3, and if a null profession is found, it means it has a Dummy to replace with a real character. + * + * @param who The character to be added to the party. + * @throws FullPartyException When trying to add someone when party is full. (max 3 members) + */ + def add(who: ICharacter): Unit = { + if (member1.profession == null) member1 = who + else if (member2.profession == null) member2 = who + else if (member3.profession == null) member3 = who + else throw new FullPartyException("Party is already full") + } + + /** Determines if the party is alive. + * + * Calls each member isAlive method, returns true if anyone returns true. + * + * @return If the party is alive + */ + def isAlive: Boolean = { + member1.isAlive || member2.isAlive || member3.isAlive + } + + /** Changes the actionbar of each party member by a k amount + * + * @param k Amount to change the actionbar + */ + def step(k: Int): Unit = { + member1.setActionBar(k) + member2.setActionBar(k) + member3.setActionBar(k) + } + + /** Removes a specific member from the party + * + * @param who The Character to remove from the party + */ + def remove(who:ICharacter):Unit={ + if(who==member3) member3 = new DummyCharacter + if(who==member2) member2 = new DummyCharacter + if(who==member1) member1 = new DummyCharacter + } + + /** Using a private list in the programmer, this checks and delivers an updated list with party members with + * filled action bars, by checking excess if it is positive (check getActionBar in Character or MagicCharacter for more details) + * + * @param inlist A ListBuffer with units delivered by the programmer + * @return + */ + def anyTurnForProgrammer(inlist:ListBuffer[Units]): ListBuffer[Units] ={ + if (member1.getActionBar >= 0) inlist.addOne(member1) + if (member2.getActionBar >= 0) inlist.addOne(member2) + if (member3.getActionBar >= 0) inlist.addOne(member3) + inlist + } + + /** Returns a ListBuffer with the Party Members */ + def getMembers : ListBuffer[ICharacter] = { + val memberList = new ListBuffer[ICharacter] + memberList += member1 + memberList += member2 + memberList += member3 + memberList + } +} diff --git a/src/main/scala/unit/Units.scala b/src/main/scala/unit/Units.scala new file mode 100644 index 0000000..8f6096b --- /dev/null +++ b/src/main/scala/unit/Units.scala @@ -0,0 +1,28 @@ +package unit + +/** Trait represents common attributes shared by playable characters and enemies. + * + * Used by [[unit.ICharacter]] and [[unit.Enemy]] + */ +trait Units { + val name: String + protected var life: Int + protected var defense: Int + val weight: Double + val maxLife: Int + + /** Determines if the current entity is alive. + * + * Checks if current life is above 0, returns true if it is, otherwise returns false. + * + * @return If the entity is alive + */ + def isAlive: Boolean + def getActionBar: Double + def setActionBar(k : Int) : Unit + /** Returns the life of the unit */ + def getLife : Int + /** Returns the defense of the unit */ + def getDefense : Int + +} diff --git a/src/main/scala/weapon/AbstractCommonWeapon.scala b/src/main/scala/weapon/AbstractCommonWeapon.scala new file mode 100644 index 0000000..a00dc76 --- /dev/null +++ b/src/main/scala/weapon/AbstractCommonWeapon.scala @@ -0,0 +1,28 @@ +package weapon + +import exceptions.Require +import unit.{DummyCharacter, ICharacter} + +/** Abstract class representing non magic weapons. + * + * Used by [[weapon.Axe]], [[weapon.Bow]], and [[weapon.Sword]] + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.1 + */ +abstract class AbstractCommonWeapon extends Weapon{ + Require.Stat(damage,"damage") atLeast 0 + require(weight>0,"number must be greater than zero") + + def getOwner : ICharacter = { + if(owner.profession == null) null + else owner + } + def removeOwner(): Unit = { owner = new DummyCharacter } + def equipTo(who: ICharacter): Unit = { + if(owner.profession != null) throw new UsedWeaponException(s"$name is already being used by someone else") + else if(!canEquipTo(who)) throw new InvalidWeaponException(s"Can not equip ${this.getClass.getName} to ${who.profession.name}") + else { owner = who } + } +} diff --git a/src/main/scala/weapon/AbstractMagicWeapon.scala b/src/main/scala/weapon/AbstractMagicWeapon.scala new file mode 100644 index 0000000..91c86b9 --- /dev/null +++ b/src/main/scala/weapon/AbstractMagicWeapon.scala @@ -0,0 +1,28 @@ +package weapon + +import exceptions.Require +import unit.{DummyCharacter, ICharacter} + +/** Abstract class representing magic weapons. + * + * Used by [[weapon.Staff]], and [[weapon.Wand]] + * + * @param magic_damage The magic damage of the weapon. + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.1 + */ +abstract class AbstractMagicWeapon(val magic_damage: Int) extends Weapon{ +Require.Stat(magic_damage,"Magic damage") atLeast 0 + + def getOwner : ICharacter = { + if(owner.profession == null) null + else owner + } + def removeOwner(): Unit = { owner = new DummyCharacter } + def equipTo(who: ICharacter): Unit = { + if(owner.profession != null) throw new UsedWeaponException(s"$name is already being used by someone else") + else if(!canEquipTo(who)) throw new InvalidWeaponException(s"Can not equip magic ${this.getClass.getName} to ${who.profession.name}") + else { owner = who } + } +} diff --git a/src/main/scala/weapon/Axe.scala b/src/main/scala/weapon/Axe.scala new file mode 100644 index 0000000..33edaad --- /dev/null +++ b/src/main/scala/weapon/Axe.scala @@ -0,0 +1,33 @@ +package weapon + +import exceptions.Require +import unit.{DummyCharacter, ICharacter} + +/** Class representing an Axe. + * + * When initializing, owner can be a [[unit.DummyCharacter]] + * + * @param name The name of the weapon. + * @param damage The damage of the weapon. + * @param weight The weight of the weapon. + * @param owner The owner of the weapon. + * + * @constructor Creates a new Axe. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.3 + */ +class Axe(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter) extends AbstractCommonWeapon { + + + def canEquipTo(who: ICharacter): Boolean = { + if(owner.profession != null) false + else if(who.profession.name == "Paladin" || who.profession.name == "Warrior") true + else false + } + +} diff --git a/src/main/scala/weapon/Bow.scala b/src/main/scala/weapon/Bow.scala new file mode 100644 index 0000000..892e8e7 --- /dev/null +++ b/src/main/scala/weapon/Bow.scala @@ -0,0 +1,32 @@ +package weapon + +import exceptions.Require +import unit.{DummyCharacter, ICharacter} + +/** Class representing a Bow. + * + * When initializing, owner can be a [[unit.DummyCharacter]] + * + * @param name The name of the weapon. + * @param damage The damage of the weapon. + * @param weight The weight of the weapon. + * @param owner The owner of the weapon. + * + * @constructor Creates a new Bow. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.3 + */ +class Bow(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter) extends AbstractCommonWeapon { + + + def canEquipTo(who: ICharacter): Boolean = { + if(owner.profession != null) false + else if(who.profession.name == "Warrior" || who.profession.name == "Ninja" || who.profession.name == "White Mage") true + else false + } +} diff --git a/src/main/scala/weapon/InvalidWeaponException.scala b/src/main/scala/weapon/InvalidWeaponException.scala new file mode 100644 index 0000000..68fc45f --- /dev/null +++ b/src/main/scala/weapon/InvalidWeaponException.scala @@ -0,0 +1,22 @@ +package weapon + +/** Exception when trying to equip an invalid weapon to a character. + * + * In this case the corresponding professions can equip: + * + * - Paladin: Sword and Axe + * + * - Warrior: Sword, Axe and Bow + * + * - Ninja: Sword, Bow and Wand + * + * - Black Mage: Sword, Wand and Staff + * + * - White Mage: Bow, Wand and Staff + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class InvalidWeaponException(string : String) extends Exception(string) { + +} diff --git a/src/main/scala/weapon/Staff.scala b/src/main/scala/weapon/Staff.scala new file mode 100644 index 0000000..a255f14 --- /dev/null +++ b/src/main/scala/weapon/Staff.scala @@ -0,0 +1,33 @@ +package weapon + +import unit.{DummyCharacter, ICharacter} + +/** Class representing a Staff. + * + * When initializing, owner can be a [[unit.DummyCharacter]] + * + * @param name The name of the weapon. + * @param damage The damage of the weapon. + * @param weight The weight of the weapon. + * @param owner The owner of the weapon. + * @param magic_damage The magic damage of the weapon. + * + * @constructor Creates a new Staff. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.3 + */ +class Staff(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter, + magic_damage:Int) extends AbstractMagicWeapon(magic_damage) { + + + def canEquipTo(who: ICharacter): Boolean = { + if(owner.profession != null) false + else if(who.profession.name == "Black Mage" || who.profession.name == "White Mage") true + else false + } +} diff --git a/src/main/scala/weapon/Sword.scala b/src/main/scala/weapon/Sword.scala new file mode 100644 index 0000000..20923c5 --- /dev/null +++ b/src/main/scala/weapon/Sword.scala @@ -0,0 +1,31 @@ +package weapon + +import unit.{DummyCharacter, ICharacter} + +/** Class representing a Sword. + * + * When initializing, owner can be a [[unit.DummyCharacter]] + * + * @param name The name of the weapon. + * @param damage The damage of the weapon. + * @param weight The weight of the weapon. + * @param owner The owner of the weapon. + * + * @constructor Creates a new Sword. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.3 + */ +class Sword(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter) extends AbstractCommonWeapon { + + + def canEquipTo(who: ICharacter): Boolean = { + if(owner.profession != null) false + else if(who.profession.name == "Paladin" || who.profession.name == "Warrior" || who.profession.name == "Ninja" || who.profession.name == "Black Mage") true + else false + } +} diff --git a/src/main/scala/weapon/UsedWeaponException.scala b/src/main/scala/weapon/UsedWeaponException.scala new file mode 100644 index 0000000..8c93cae --- /dev/null +++ b/src/main/scala/weapon/UsedWeaponException.scala @@ -0,0 +1,10 @@ +package weapon + +/** Exception when trying to equip a weapon, when it's already being used by someone else + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.0 + */ +class UsedWeaponException(string : String) extends Exception(string){ + +} diff --git a/src/main/scala/weapon/Wand.scala b/src/main/scala/weapon/Wand.scala new file mode 100644 index 0000000..259fc87 --- /dev/null +++ b/src/main/scala/weapon/Wand.scala @@ -0,0 +1,33 @@ +package weapon + +import unit.{DummyCharacter, ICharacter} + +/** Class representing a Wand. + * + * When initializing, owner can be a [[unit.DummyCharacter]] + * + * @param name The name of the weapon. + * @param damage The damage of the weapon. + * @param weight The weight of the weapon. + * @param owner The owner of the weapon. + * @param magic_damage The magic damage of the weapon. + * + * @constructor Creates a new Wand. + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.3 + */ +class Wand(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter, + magic_damage:Int) extends AbstractMagicWeapon(magic_damage) { + + + def canEquipTo(who: ICharacter): Boolean = { + if(owner.profession != null) false + else if(who.profession.name == "Ninja" || who.profession.name == "Black Mage" || who.profession.name == "White Mage") true + else false + } +} diff --git a/src/main/scala/weapon/Weapon.scala b/src/main/scala/weapon/Weapon.scala new file mode 100644 index 0000000..fc9482f --- /dev/null +++ b/src/main/scala/weapon/Weapon.scala @@ -0,0 +1,30 @@ +package weapon + +import unit.ICharacter + +/** Trait represents common attributes shared by all weapons. + * + * Used by [[weapon.AbstractCommonWeapon]] and [[weapon.AbstractMagicWeapon]] + */ +trait Weapon { + val name: String + val damage: Int + val weight: Double + protected var owner: ICharacter + + /** Returns the owner of the weapon; returns null if no owner (DummyCharacter) */ + def getOwner : ICharacter + + /** Checks if its valid to equip this weapon, by checking if its not being used by other + * character and profession restrictions + * @param who The character asking if equipping this weapon is valid + * @return Bool if its possible or not to equip this weapon */ + def canEquipTo(who : ICharacter) : Boolean + /** Removes the owner of the weapon; this is called after removing a weapon from a character */ + def removeOwner() : Unit + /** Assigns this weapon owner to the corresponding character + * @param who The character equipping this weapon + * @throws InvalidWeaponException If trying to equip an invalid weapon for the corresponding profession + * @throws UsedWeaponException If the weapon is already being used by someone else*/ + def equipTo(who: ICharacter) : Unit +} diff --git a/src/test/scala/combatsystem/CombatTest.scala b/src/test/scala/combatsystem/CombatTest.scala new file mode 100644 index 0000000..3c633b7 --- /dev/null +++ b/src/test/scala/combatsystem/CombatTest.scala @@ -0,0 +1,82 @@ +package combatsystem +import profession.{BlackMage, Warrior} +import unit.{Character, DummyCharacter, Enemy, EnemyClass, MagicCharacter, Party} +import weapon.{Sword, Wand} + +import scala.collection.mutable.ListBuffer + +class CombatTest extends munit.FunSuite { + var member1 : Character = _ + var member2 : MagicCharacter = _ + var member3 : Character = _ + var enemy1 : EnemyClass = _ + var enemy2 : EnemyClass = _ + var enemy3 : EnemyClass = _ + var enemy4 : EnemyClass = _ + var enemy5 : EnemyClass = _ + var enemy6 : EnemyClass = _ + var SomeParty : Party = _ + var TestTurns : Programmer = _ + + override def beforeEach(context: BeforeEach): Unit = { + member1 = new Character("Dude",100,5,50.0,new Warrior) + member1.equipWeapon(new Sword("Test",50,12.5,new DummyCharacter)) + member2 = new MagicCharacter("Another Dude",100,1,40.25,new BlackMage, 500) + member2.equipWeapon(new Wand("Test2",10,12.5,new DummyCharacter, 50)) + member3 = new Character("Dude with no weapon",100,5,50.0,new Warrior) + enemy1 = new EnemyClass("Foo",20,10,3,10.0) + enemy2 = new EnemyClass("Another Foo",30,10,5,10.5) + enemy3 = new EnemyClass("Foo III",45,12,30,12.5) + enemy4 = new EnemyClass("Foo Foo",75,15,15,90.5) + enemy5 = new EnemyClass("Oof",100,25,10,120) + enemy6 = new EnemyClass("FOO",250,100,50,250) + SomeParty = new Party(member1) + TestTurns = new Programmer(SomeParty,ListBuffer[Enemy](enemy1,enemy2,enemy3)) + } + + test("Programmer Adding and removing Tests"){ + TestTurns.add(member2) + SomeParty.add(member2) + assertEquals(TestTurns.getParty,SomeParty) + TestTurns.add(enemy4) + assertEquals(TestTurns.getEnemies(3),enemy4) + TestTurns.add(enemy5) + TestTurns.add(enemy6) + assertEquals(TestTurns.getEnemies.last,enemy5) + TestTurns.remove(member1) + assertEquals(TestTurns.getParty.getMembers.head.profession,null) //Dummy Character has no profession + TestTurns.remove(enemy3) + assertEquals(TestTurns.getEnemies,ListBuffer[Enemy](enemy1,enemy2,enemy4,enemy5)) + } + test("Programmer Turns Tests"){ + TestTurns.add(member2) + TestTurns.step() + if(TestTurns.anyTurn) TestTurns.getTurn + assertEquals(TestTurns.anyTurn,false) + for(i <- 0 to 50) TestTurns.step() + assertEquals(TestTurns.anyTurn,true) + if(TestTurns.anyTurn) assertEquals(TestTurns.getTurn,enemy1) + if(TestTurns.anyTurn) assertEquals(TestTurns.getTurn,enemy2) + if(TestTurns.anyTurn) assertEquals(TestTurns.getTurn,enemy3) + if(TestTurns.anyTurn) assertEquals(TestTurns.getTurn,member2) + if(TestTurns.anyTurn) assertEquals(TestTurns.getTurn,member1) + assertEquals(TestTurns.anyTurn,false) + } + test("Attack Tests"){ // characters cant attack enemies, and enemies cant attack characters + member1.attackAnEnemy(enemy1) + assertEquals(enemy1.getLife,0) + member1.attackAnEnemy(enemy3) + assertEquals(enemy3.getLife,25) + member2.attackAnEnemy(enemy5) + assertEquals(enemy5.getLife,100) + member3.attackAnEnemy(enemy2) + assertEquals(enemy2.getLife,30) + + enemy4.attackACharacter(member1) + assertEquals(member1.getLife,90) + enemy6.attackACharacter(member2) + assertEquals(member2.getLife,1) + enemy6.attackACharacter(member2) + assertEquals(member2.getLife,0) + } +} diff --git a/src/test/scala/unit/CharacterTest.scala b/src/test/scala/unit/CharacterTest.scala new file mode 100644 index 0000000..e97bb10 --- /dev/null +++ b/src/test/scala/unit/CharacterTest.scala @@ -0,0 +1,78 @@ +package unit + +import exceptions.InvalidStatException +import profession.{BlackMage, Ninja, Paladin, Warrior, WhiteMage} + +class CharacterTest extends munit.FunSuite { + var trainer: Character = _ + var trainer2: Character = _ + var magic_trainer: MagicCharacter = _ + var magic_trainer2: MagicCharacter = _ + var weak_character: Character = _ + var weak_character2: MagicCharacter = _ + var weak_character3: MagicCharacter = _ + var AliveParty: Party = _ + var DeathParty: Party = _ + var some_enemy: Enemy = _ + var some_death_enemy: Enemy = _ + + override def beforeEach(context: BeforeEach): Unit = { + trainer = new Character("TrainerOne", 100, 50, 32.5, new Paladin) + trainer2 = new Character("TrainerTwo", 100, 50, 50, new Warrior) + magic_trainer = new MagicCharacter("magic", 20, 5, 25, new BlackMage, 1000) + magic_trainer2 = new MagicCharacter(profession = new BlackMage,mana=1) + weak_character = new Character("Weak characterOne", 100, 30, new Ninja) + weak_character2 = new MagicCharacter("Weak characterTwo", 100, 32.5, new WhiteMage,1) + weak_character3 = new MagicCharacter("Weak characterThree", 100, 10, new WhiteMage,1) + AliveParty = new Party(trainer,trainer2) + AliveParty.add(magic_trainer) + DeathParty = new Party(new Character(profession = new Warrior),new Character(profession = new Warrior)) + some_enemy = new EnemyClass("Bob",5,50,3,100.5) + some_death_enemy = new EnemyClass("Bob",0,50,3,100.5) + } + test("Character constructor checks"){ + assertEquals(trainer.name,"TrainerOne") + assertEquals(trainer.getLife,100) + assertEquals(trainer.getDefense,50) + assertEquals(trainer.weight,32.5) + assertEquals(trainer.maxLife,100) + assertEquals(trainer.profession.name,"Paladin") + assertEquals(trainer2.weight,50.0) + assertEquals(trainer2.getHeldWeapon,null) + assertEquals(magic_trainer.name,"magic") + assertEquals(magic_trainer.getLife,20) + assertEquals(magic_trainer.maxLife,20) + assertEquals(magic_trainer.getDefense,5) + assertEquals(magic_trainer.weight,25.0) + assertEquals(magic_trainer.profession.name,"Black Mage") + assertEquals(magic_trainer.getMana,1000) + assertEquals(magic_trainer2.getMana,1) + assertEquals(magic_trainer2.maxMana,1) + assertEquals(magic_trainer2.getHeldWeapon,null) + assertEquals(weak_character.weight,30.0) + assertEquals(weak_character.getDefense,0) + assertEquals(weak_character2.weight,32.5) + assertEquals(weak_character2.getDefense,0) + assertEquals(weak_character3.weight,10.0) + assertEquals(weak_character3.getDefense,0) + } + test("Party Status"){ + assertEquals(AliveParty.isAlive,true) + assertEquals(DeathParty.isAlive,false) + } + test("Full party exception"){ + interceptMessage[FullPartyException]("Party is already full"){AliveParty.add(weak_character)} + } + test("Enemies"){ + assertEquals(some_enemy.getDefense,3) + assertEquals(some_enemy.maxLife,5) + assertEquals(some_enemy.isAlive,true) + assertEquals(some_death_enemy.isAlive,false) + } + test("Require Exception"){ + intercept[InvalidStatException](new Character("Life Bug", -1, 50, 32.5, new Paladin)) + intercept[InvalidStatException](new Character("Defense Bug", 10, -10, 32.5, new Paladin)) + intercept[IllegalArgumentException](new Character("Weight Bug", 10, 100, 0, new Paladin)) + intercept[InvalidStatException](new MagicCharacter("Magic Bug", 20, 5, 25, new BlackMage, -1)) + } +} diff --git a/src/test/scala/weapon/WeaponTest.scala b/src/test/scala/weapon/WeaponTest.scala new file mode 100644 index 0000000..d6cfe71 --- /dev/null +++ b/src/test/scala/weapon/WeaponTest.scala @@ -0,0 +1,84 @@ +package weapon + +import profession._ +import unit.{Character, DummyCharacter, MagicCharacter} + +class WeaponTest extends munit.FunSuite { + var TestSword: Sword = _ + var TestAxe: Axe = _ + var TestBow: Bow = _ + var TestWand: Wand = _ + var TestStaff: Staff = _ + + var dude1 : Character = _ + var dude2 : Character = _ + var dude3 : Character = _ + var magic_dude1 : MagicCharacter = _ + var magic_dude2 : MagicCharacter = _ + override def beforeEach(context: BeforeEach): Unit = { + TestSword = new Sword("Starter Sword",5,10,new DummyCharacter) + TestAxe = new Axe("Starter Axe",15,30.5,new DummyCharacter) + TestBow = new Bow("Starter Bow",10,8.25,new DummyCharacter) + TestWand = new Wand("Starter Wand",5,3,new DummyCharacter,20) + TestStaff = new Staff("Starter Staff",10,17.5,new DummyCharacter,15) + + dude1 = new Character("Dude 1", 100, 50, 32.5, new Paladin) + dude2 = new Character("Dude 2", 100, 50, 32.5, new Warrior) + dude3 = new Character("Dude 3", 100, 50, 32.5, new Ninja) + magic_dude1 = new MagicCharacter("Magic Dude 1", 20, 5, 25, new BlackMage, 1000) + magic_dude2 = new MagicCharacter("Magic Dude 2", 20, 5, 25, new WhiteMage, 1000) + } + test("Weapons Constructors Tests"){ + assertEquals(TestSword.weight,10.0) + assertEquals(TestStaff.magic_damage,15) + } + test("Equipping weapons Tests"){ + assertEquals(dude1.getHeldWeapon,null) //No held weapons + assertEquals(dude2.getHeldWeapon,null) + assertEquals(dude3.getHeldWeapon,null) + assertEquals(magic_dude1.getHeldWeapon,null) + assertEquals(magic_dude2.getHeldWeapon,null) + assertEquals(TestSword.getOwner,null) //DummyCharacters or rather no owners + assertEquals(TestAxe.getOwner,null) + assertEquals(TestBow.getOwner,null) + assertEquals(TestWand.getOwner,null) + assertEquals(TestStaff.getOwner,null) + + assertEquals(dude1.canEquipWeapon(TestSword),true) + assertEquals(dude1.canEquipWeapon(TestBow),false) + dude1.equipWeapon(TestSword) + dude2.equipWeapon(TestAxe) + dude3.equipWeapon(TestBow) + magic_dude1.equipWeapon(TestWand) + magic_dude2.equipWeapon(TestStaff) + + assertEquals(TestSword.getOwner,dude1) + assertEquals(TestAxe.getOwner,dude2) + assertEquals(TestBow.getOwner,dude3) + assertEquals(TestWand.getOwner,magic_dude1) + assertEquals(TestStaff.getOwner,magic_dude2) + assertEquals(dude1.getHeldWeapon,TestSword) + assertEquals(dude2.getHeldWeapon,TestAxe) + assertEquals(dude3.getHeldWeapon,TestBow) + assertEquals(magic_dude1.getHeldWeapon,TestWand) + assertEquals(magic_dude2.getHeldWeapon,TestStaff) + + assertEquals(dude1.canEquipWeapon(TestSword),false) //weapon is now being used + assertEquals(dude1.canEquipWeapon(null),false) + } + test("Equipping weapons exceptions"){ + interceptMessage[InvalidWeaponException]("Can not equip magic weapon.Staff to Paladin"){dude1.equipWeapon(TestStaff)} + dude1.equipWeapon(TestAxe) + interceptMessage[UsedWeaponException]("Starter Axe is already being used by someone else"){dude2.equipWeapon(TestAxe)} // trying to equip a weapon being held by someone else + intercept[NullPointerException](dude1.equipWeapon(null)) + } + + test("More Coverage"){ + magic_dude1.equipWeapon(TestWand) + magic_dude1.removeWeapon() // un-equipping the weapon + assertEquals(magic_dude1.getHeldWeapon,null) + assertEquals(dude1.canEquipWeapon(TestWand),false) + assertEquals(magic_dude1.canEquipWeapon(TestAxe),false) + assertEquals(magic_dude2.canEquipWeapon(TestSword),false) + } +}