diff --git a/README.md b/README.md index e7d6320..2ae8c8c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,40 @@ # 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 +- 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. 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..dab7a0b --- /dev/null +++ b/src/main/scala/unit/AbstractCharacter.scala @@ -0,0 +1,49 @@ +package unit + +import weapon.Weapon + +/** Abstract class representing Characters. + * + * Professions name must be provided by the subclasses. + * + * Used by [[unit.Character]], and [[unit.MagicCharacter]] + * + * @author Javier Torres + * @since 1.0.0 + * @version 1.0.2 + */ +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 + } +} 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/ICharacter.scala b/src/main/scala/unit/ICharacter.scala new file mode 100644 index 0000000..7523ee5 --- /dev/null +++ b/src/main/scala/unit/ICharacter.scala @@ -0,0 +1,24 @@ +package unit +import profession.Profession +import weapon.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 +} 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..f7c56bc --- /dev/null +++ b/src/main/scala/unit/Party.scala @@ -0,0 +1,85 @@ +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. + * Otherwise does nothing. + * + * @param who The character to be added to the party. + */ + 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 + } + + /** 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..2355862 --- /dev/null +++ b/src/main/scala/weapon/AbstractCommonWeapon.scala @@ -0,0 +1,16 @@ +package weapon + +import exceptions.Require + +/** 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.0 + */ +abstract class AbstractCommonWeapon extends Weapon{ + Require.Stat(damage,"damage") atLeast 0 + require(weight>0,"number must be greater than zero") +} diff --git a/src/main/scala/weapon/AbstractMagicWeapon.scala b/src/main/scala/weapon/AbstractMagicWeapon.scala new file mode 100644 index 0000000..7f37eeb --- /dev/null +++ b/src/main/scala/weapon/AbstractMagicWeapon.scala @@ -0,0 +1,16 @@ +package weapon + +import exceptions.Require + +/** 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.0 + */ +abstract class AbstractMagicWeapon(val magic_damage: Int) extends Weapon{ +Require.Stat(magic_damage,"Magic damage") atLeast 0 +} diff --git a/src/main/scala/weapon/Axe.scala b/src/main/scala/weapon/Axe.scala new file mode 100644 index 0000000..c8eed1f --- /dev/null +++ b/src/main/scala/weapon/Axe.scala @@ -0,0 +1,26 @@ +package weapon + +import exceptions.Require +import unit.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.2 + */ +class Axe(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter) extends AbstractCommonWeapon { + +} diff --git a/src/main/scala/weapon/Bow.scala b/src/main/scala/weapon/Bow.scala new file mode 100644 index 0000000..2910368 --- /dev/null +++ b/src/main/scala/weapon/Bow.scala @@ -0,0 +1,26 @@ +package weapon + +import exceptions.Require +import unit.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.2 + */ +class Bow(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter) extends AbstractCommonWeapon { + +} diff --git a/src/main/scala/weapon/Staff.scala b/src/main/scala/weapon/Staff.scala new file mode 100644 index 0000000..25e3d91 --- /dev/null +++ b/src/main/scala/weapon/Staff.scala @@ -0,0 +1,27 @@ +package weapon + +import unit.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.1 + */ +class Staff(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter, + magic_damage:Int) extends AbstractMagicWeapon(magic_damage) { + +} diff --git a/src/main/scala/weapon/Sword.scala b/src/main/scala/weapon/Sword.scala new file mode 100644 index 0000000..5e0ca06 --- /dev/null +++ b/src/main/scala/weapon/Sword.scala @@ -0,0 +1,25 @@ +package weapon + +import unit.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.1 + */ +class Sword(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter) extends AbstractCommonWeapon { + +} diff --git a/src/main/scala/weapon/Wand.scala b/src/main/scala/weapon/Wand.scala new file mode 100644 index 0000000..37668b2 --- /dev/null +++ b/src/main/scala/weapon/Wand.scala @@ -0,0 +1,27 @@ +package weapon + +import unit.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.1 + */ +class Wand(val name:String, + val damage:Int, + val weight:Double, + protected var owner:ICharacter, + magic_damage:Int) extends AbstractMagicWeapon(magic_damage) { + +} diff --git a/src/main/scala/weapon/Weapon.scala b/src/main/scala/weapon/Weapon.scala new file mode 100644 index 0000000..71bf765 --- /dev/null +++ b/src/main/scala/weapon/Weapon.scala @@ -0,0 +1,14 @@ +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 +} diff --git a/src/test/scala/combatsystem/CombatTest.scala b/src/test/scala/combatsystem/CombatTest.scala new file mode 100644 index 0000000..ef6ba35 --- /dev/null +++ b/src/test/scala/combatsystem/CombatTest.scala @@ -0,0 +1,89 @@ +package combatsystem +import profession.{BlackMage, Warrior} +import unit.{Character, 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.placeholderEquipWeapon(new Sword("Test",50,12.5,member1)) + member2 = new MagicCharacter("Another Dude",100,1,40.25,new BlackMage, 500) + member2.placeholderEquipWeapon(new Wand("Test",10,12.5,member2, 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"){ + 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) + } + test("Placeholder update actionbar after equipping weapon tests"){ + member1.updateMaxActionbar() + member2.updateMaxActionbar() + member3.updateMaxActionbar() + member2.placeholderEquipWeapon(null) + member2.updateMaxActionbar() + } +} diff --git a/src/test/scala/unit/CharacterTest.scala b/src/test/scala/unit/CharacterTest.scala new file mode 100644 index 0000000..e981207 --- /dev/null +++ b/src/test/scala/unit/CharacterTest.scala @@ -0,0 +1,75 @@ +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("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..fa881aa --- /dev/null +++ b/src/test/scala/weapon/WeaponTest.scala @@ -0,0 +1,16 @@ +package weapon + +import unit.DummyCharacter + +class WeaponTest extends munit.FunSuite { + var TestSword: Sword = new Sword("Starter Sword",5,10,new DummyCharacter) + var TestAxe: Axe = new Axe("Starter Axe",15,30.5,new DummyCharacter) + var TestBow: Bow = new Bow("Starter Bow",10,8.25,new DummyCharacter) + var TestWand: Wand = new Wand("Starter Wand",5,3,new DummyCharacter,20) + var TestStaff: Staff = new Staff("Starter Staff",10,17.5,new DummyCharacter,15) + + test("Weapons Constructors Tests"){ + assertEquals(TestSword.weight,10.0) + assertEquals(TestStaff.magic_damage,15) + } +}