⚔️ Java RPG Game
A text-based role‑playing game built in Java, demonstrating core Object‑Oriented Programming principles such as inheritance, polymorphism, interfaces, abstraction, and encapsulation.
The player chooses a hero (Knight, Wizard, or Assassin), equips weapons and armour, and battles various enemies (Skeleton, Vampire, Goblin) in a simple turn‑based loop. Locations and consumable items are also partially implemented.
📁 Project Structure
🎮 Gameplay Overview
- Character Selection – Enter your name, pick a class (Assassin, Wizard, Knight), choose a weapon and armour.
- Combat Loop – You face random enemies from a list. Each turn you can:
- Light Attack
- Heavy Attack (normal attack)
- Defend
- Heal
- Use Special Ability
- Enemy AI – Enemies randomly choose between light and heavy attacks. Some enemies (e.g. Goblin) have special behaviours.
- Progression – After defeating an enemy, the player's maximum HP and MP increase, and both player and enemy stats are refreshed.
- Relocation – You can choose to move to another location (currently selecting a random enemy from the list).
🧱 Class Hierarchy & Interfaces
Interface Entity
Defines the contract for all living beings in the game.
| Method | Description |
|---|---|
| void attack(Entity target) | Basic attack on a target. |
| void LightAttack(Entity target, int base_damage) | A weaker attack with fixed damage. |
| void defend(Entity me, int damage) | Defensive action (confusing signature, see Player). |
| String getName() | Returns the entity's name. |
| void heal(int health) | Restores health points. |
| void fillMana(int mana) | Restores mana points. |
| void takeDamage(int damage) | Applies damage after armour reduction. |
| int getMaxHP() | Returns maximum health. |
| int getMaxMP() | Returns maximum mana. |
| boolean ISLive() | Checks if the entity is alive (HP > 0). |
Interface Item
Defines usable items.
| Method | Description |
|---|---|
| void use(Entity target) | Apply the item's effect to the target. |
| String getName() | Returns the item's name. |
🧙 Abstract Classes
Player (extends Entity)
Common base for all player characters.
Fields:
- name, weapon, armor, hp, maxHP, mp, maxMP, baseDamage (unused)
Constructors:
- Player(String name, int hp, int mp, Weapon weapon, Armor armor, int baseDamage)
- Player(String name, int hp, int mp, Weapon weapon, Armor armor) – sets maxHP/maxMP equal to initial hp/mp.
Methods:
- void attack(Entity target) – Deals weapon damage.
- void defend(Entity me, int damage) – Heals the entity itself (unusual).
- void takeDamage(int damage) – Reduces damage by armour defence.
- void heal(int health) – Adds health, capped at maxHP.
- void fillMana(int mana) – Adds mana, capped at maxMP.
- abstract void specialAttack(Entity target) – Each subclass defines its own special move.
- boolean IsLive() – Returns true if HP > 0.
- void LightAttack(Entity target, int base_damage) – Deals flat damage.
- void UpGradeMaxHP() / UpGradeMaxMP() – Increases max stats by 20% (used after battle).
- void freshFactors() – Fully restores HP and MP.
Enemy (extends Entity)
Base for all enemy types.
Fields:
- weapon, hp, mp, maxHp, maxMp
Constructor:
- Enemy(int hp, int mp, Weapon weapon) – Initialises stats and weapon.
Methods:
- int getHp(), int getMp(), Weapon getWeapon(), void setNewHp(int newhp)
- void takeDamage(int damage) – Direct HP reduction.
- abstract void attack(Entity target)
- abstract void check_hp() – Intended for death/recovery checks.
- abstract void defend() – Normal defend.
- abstract String getName()
- void heal(int health), void fillMana(int mana) – Standard recovery.
- boolean IsLive() – HP > 0.
- void LightAttack(Entity target, int base_damage) – Flat damage.
- int getMaxHP(), int getMaxMp()
- void freshFactors() – Resets HP/MP to max.
Consumable (implements Item)
Base for consumable items.
- Consumable(String name) – Sets the item name.
- String getName()
Weapon (abstract)
Base for all weapons.
Fields:
- int damage, int manaCost
Constructor: Weapon(int damage, int manaCost)
Methods:
- int getDamage(), int getManaCost()
- (No use method – not implementing Item)
Armor (abstract)
Base for all armour pieces. Fields:
- defense, maxDefense, durability, maxDurability, isBroke (private)
Constructors:
- Armor(int defense, int durability) – Main constructor (does not initialise max values correctly).
- Armor(int maxDefense, int maxDurability, boolean isBroke) – Unused overload.
Methods:
- void checkBreak() – If durability ≤ 0, sets isBroke = true and defense = 0.
- void repair() – Restores defence/durability to max values.
- int getDefense(), int getDurability(), boolean isBroke()
- void setMaxDefense(int max)
- void newDurability() – Decrements durability (does not auto‑call checkBreak).
🧝♂️ Concrete Player Classes
Knight
- Base stats: HP=40, MP=20
- specialAttack(Entity target) – Deals 5 damage.
Wizard
- Base stats: HP=40, MP=20
- specialAttack(Entity target) – Deals 5 damage and heals self for 10 HP (capped at max).
Assassin
- Base stats: HP=20, MP=40
- specialAttack(Entity target) – Deals weapon damage.
All three override ISLive() to always return false (bug – should use IsLive() from Player).
👹 Concrete Enemy Classes
Skeleton
- HP=10, MP=2
- attack(Entity target) – Weapon damage.
- check_hp() – If HP ≤ 0 and not yet revived, sets HP to Max_hp/2 (5). (Bug: ISLive() returns false, so this may not work as intended.)
- getName() returns "Skeleton".
Vampire
- HP=12, MP=5
- attack(Entity target) – Weapon damage, heals self by damage/3, capped at Max_hp.
- getName() returns "Vampire".
Goblin
- HP=6, MP=18
- attack(Entity target) – Weapon damage.
- getName() returns "Gobblin".
Dragon
- Extends Enemy but currently all methods are empty/return default values – not fully implemented.
⚔️ Weapons
Sword
- damage=3, manaCost=4 (constants, but constructor accepts parameters that are ignored)
- Copy constructor: Sword(Weapon other) copies damage and manaCost from another weapon.
- uniqueAbility(ArrayList targets) – deals weapon damage to all targets.
SwordPro
- damage=4, manaCost=3
- Same copy constructor and unique ability.
SwordProMax
- damage=5, manaCost=2
- Same pattern.
🛡️ Armors
KnightArmor
- defense=3, durability=2 (constructor ignores parameters, always uses static constants)
- No copy constructor.
ArmorNormal
- defense=1, durability=2
ArmorPro
- defense=2, durability=2
ArmorProMax
- defense=1, durability=5
All armours use the same pattern: a static final value passed to super().
🧪 Consumables
Flask (extends Consumable)
- Flask(String name) – calls super.
- use(Entity target) – Heals target by 10% of its max HP.
Currently unused in gameplay; the Main loop does not reference flasks.
📍 Location
- Fields: name, enemies (ArrayList), locations (ArrayList)
- Constructor takes locations and enemies, but does not assign name (remains null).
- Not integrated into the main game loop yet.
🎯 Main Class (Game Loop)
The main method contains:
- Setup: Creates weapon instances, enemy list (Skeleton, Vampire, Goblin), scanner, random.
- Player creation: Asks for name, class (1‑3), weapon (1‑3), armour (1‑4). Uses copy constructors for weapons.
- Game loop: While player is alive (checks player.ISLive() – currently buggy, always false so loop never enters). Inside:
- Displays current enemy name.
- Offers two options: Fight (1) or Move (2). Moving picks a random enemy from the list.
- When fight is selected, calls playerMove() (player attacks), then if enemy lives, enemyMove() (enemy attacks).
- After enemy dies, player’s max HP/MP increase, both are refreshed.
- Helper methods:
- playerMove(Player, Enemy) – Displays a menu of actions (Light Attack, Heavy Attack, Defend, Heal, Special) and calls the corresponding method.
- enemyMove(Player, Enemy, int) – Randomly chooses Light Attack or normal Attack for the enemy.
- showDetails(Player, Enemy) – Prints current stats.
- pause(double seconds) – Sleeps the program for a given time.
🐞 Known Issues / Quirks
- Player.ISLive() always returns false because overridden incorrectly in subclasses; the loop condition is never satisfied.
- Armor constructors do not properly initialise maxDefense/maxDurability.
- Entity interface contains defend(Entity me, int damage) which doesn’t match the usual defend() signature.
- Many ISLive() overrides just return false, while the correct logic is in IsLive() (non‑interface method). The game uses ISLive() (interface method) which clashes with the intended behaviour.
- Location is not used; its name field is never set.
🚀 How to Run
- Compile all .java files inside the org/project package.
- Run org.project.Main.
- Follow the console prompts to play.
📚 OOP Concepts Demonstrated
- Inheritance: Player and Enemy extend Entity; weapons extend Weapon; armours extend Armor.
- Polymorphism: Entity references used for both players and enemies in combat.
- Encapsulation: Private fields with getters/setters (though some fields could be better protected).
- Abstraction: Abstract classes Player, Enemy, Weapon, Armor define common behaviour; subclasses fill in specifics.
Enjoy the game! Contributions and fixes are welcome.