I am trying to create a parent class for cars and subclasses from it. Each one has separate methods and store them in an array then if the class are subclass try to call the method on it.
Parent class
public class car {
public String name ;
public double price ;
public car (String name , int price) {
this.name = name ;
this.price = price;
}
public String toString() {
return "car name : "+this.name
+" Price : " +this.price ;
}
}
Sub class
public class CarMotors extends car {
public float MotorsCapacity ;
public CarMotors( String name, int price , float MotorsCapacity) {
super(name, price);
this.MotorsCapacity = MotorsCapacity ;
}
public float getMotorsCapacity() {
return this.MotorsCapacity;
}
}
Main class
public class Test {
public static void main(String[] args) {
car [] cars = new car[2] ;
cars[0] = new car("M3" , 78000);
cars[1] = new CarMotors("M4" , 98000 , 3.0f);
for(int i=0 ;i<2;i++){
if(cars[i] instanceof CarMotors) {
System.out.println(cars[i].getMotorsCapacity()); // error here
}else {
System.out.println(cars[i].toString());
}
}
}
}
As you can see, I can't print the getMotorsCapacity(). I am new to Java. I think there is a cast that need to happen, but I don't now how.
Being short... a class only can see what its yours behaviors.
In your example CarMotors is a Car, that's fine.
But the behavior getMotorsCapacity() is created in CarMotors and it wasn't in Car.
That error occurs because, it's OK in a variable Car you are able to put an instance of CarMotors because CarMotors is a Car. So, any method that is in Car is also in CarMotors, yes, you can call. Look at cars[i].toString() there's no problem here.
You need explicitly say to compiler:
"- oh, right, originally this variable is a Car, but I know that is a CarMotors inside that. I will make a cast here, OK compiler? Thanks."
System.out.println(((CarMotors) cars[i]).getMotorsCapacity());
Or, to be more clear:
CarMotors carMotors = ((CarMotors) cars[i]);
System.out.println(carMotors.getMotorsCapacity());
I'm new to Java programming, sorry if this is a dumb question.
I find it hard to word this question properly, but I have an assignment to create a aircraft class that can make aircraft land, takeoff etc. And need to test it using Testclass. When the new object are entered it automatically assigns a unique ID to the aircraft in the constructor.
I can do this using a instance method fine as it has a return value which is returned to to Testclass. The question wants me to do this in the constructor itself, however, the constructor never returns anything. So the variable never gets sent to the Testclass. I clearly am not understanding OOP properly. Even when I try to just use a getter method to get the ID created in the constructor it gives me the initialized variable before the the constructor has worked on this. This is the code I have so far and its completely wrong I know but if someone could point me in the right direction or tell me how to word this question better it would be a massive help.
// I need to enter 3 aircraft into the system in the testclass
public class Aircraft {
private int aircraftID;
private static int lastID;
private String airportcode;
private int ID = 100;
private int count;
public Aircraft(int a, int b, int c){
// Constructor
// Assign ID
this.ID = a;
lastID = ID;
ID++;
this.ID =b;
lastID = ID;
ID++;
}
}
OK, you want to create an Aircraft that has an automatically-assigned unique identifier, and can take off and land. That implies you need a field for tracking the identifier, a field for tracking whether it's in the air (or not), and methods for the take off and land operations. You also need a static field for generating the unique identifiers. (Note that this implementation isn't thread safe.)
private class Aircraft {
private static int staticId = 0;
private int uniqueId = 0;
private boolean onGround = true; // Aircraft start on the ground in this implementation
public Aircraft(){
this.uniqueId = staticId; // putting this line first makes uniqueId zero-indexed in effect
staticId++;
}
public void land(){
onGround = true;
}
public void takeoff(){
onGround = false;
}
public boolean isFlying(){
return !onGround; // If it's not on the ground, it's flying
}
public int getUniqueId(){
return uniqueId;
}
}
Unit tests checks all of the methods and expected functionality of the class in question:
import org.junit.Test;
import static org.junit.Assert.*;
import Aircraft;
class Testclass {
private final Aircraft aircraft = new Aircraft();
#Test
public void hasId(){
aircraft.getUniqueId() >= 0;
}
#Test
public void canLand(){
assertTrue(aircraft.land());
}
#Test
public void canTakeOff(){
assertTrue(aircraft.takeOff());
}
#Test
public void checkFlightOperationsAreTrackedCorrectly(){
aircraft.land();
assertFalse(aircraft.isFlying());
aircraft.takeOff();
assertTrue(aircraft.isFlying());
}
}
As pointed out a constructor does not return anything (the simplified version is that with new it returns an object instance). I am kinda guessing at what you are trying to acomplish, but I'll have a go anyways. It seems to me that you are trying to cram the construction of 3 objects into one constructor - which is why your constructor has 3 parameters. Also you are playing havoc with the IDs.
I have removed all the variables that I didnt quite understand, leaving only ID that increments with each instantiated Aircraft. The #Override is mainly just for show.
public class Aircraft {
private int aircraftID;
private static int lastID = 0;
#Override
public String toString(){
return "Aircraft_" + this.aircraftID;
}
public Aircraft() {
lastID++;
this.aircraftID = lastID;
}
}
I took the liberty and wrote the TestClass just to see if we have the same thing in mind. Again the printAircraft() method is for show.
public class TestClass {
private List<Aircraft> aircrafts;
public TestClass(){
aircrafts = new ArrayList<>();
}
public void addAircraft(Aircraft a){
aircrafts.add(a);
}
public void printAircraft(){
Iterator<Aircraft> it = aircrafts.iterator();
while(it.hasNext()){
System.out.println(it.next().toString());
}
}
}
and to test it, we create and instance of TestClass add 3 Aircraft instances and print out the contents
public static void main(String[] args) {
TestClass tc = new TestClass();
tc.addAircraft(new Aircraft());
tc.addAircraft(new Aircraft());
tc.addAircraft(new Aircraft());
tc.printAircraft();
}
This would be the case if you are to write the TestClass. If that is given, it would help to know what it looks like - maybe that would help us understand better.
The pet store program should start with the user being able to choose to adopt a pet or give a pet the to the shop. If the user wants to adopt a pet, they should be able to see either all available pets, unless they say they know what type of pet they want, then show only available pets of that type.
The 4 methods that will need to be created for this program should:
add new pets
get a pet adopted
show pets by type
show pets available for adoption
Object Class: Pets.java
import java.util.*;
public class Pets {
public static void main(String[] args){
private double age; // age of the animal (e.g. for 6 months the age would be .5)
private String petName; // name of the animal
private String aType; // the type of the pet (e.g. "bird", "dog", "cat", "fish", etc)
private int collarID; // id number for the pets
private boolean isAdopted = false; // truth of if the pet has been adopted or not
private String newOwner;
private Date adoptionDate;
public double getAge() {
return age;
}
public void setAge(double age) {
this.age = age;
}
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public String getaType() {
return aType;
}
public void setaType(String aType) {
this.aType = aType;
}
public int getCollarId() {
return collarID;
}
public void setCollarId(int collarId) {
this.collarID = collarId;
}
public boolean isAdoptated() {
return isAdopted;
}
public void setAdoptated(boolean isAdoptated) {
this.isAdopted = isAdoptated;
}
public Date getAdoptionDate() {
return adoptionDate;
}
public void setAdoptionDate(Date adoptionDate) {
this.adoptionDate = adoptionDate;
}
#Override
public String toString() {
return "Pets [age=" + age + ", petName=" + petName + ", aType=" + aType + ", collarId=" + collarID
+ ", isAdoptated=" + isAdopted + ", adoptionDate=" + adoptionDate + "]";
}
}
}
You should define the data fields and methods inside the class, but not inside the main()-method. The main()-method is the entry point of your java application and could be used to create an instance of your Pets class.
e.g.:
public static void main(String[] args) {
Pets pet = new Pets();
}
This code is not compiling for 2 main reasons:
You are specifying access modifiers on variables inside a method (in this case main), which is forbidden;
You are writing methods (e.g. getAge) inside another method (main) and trying to return a variable (e.g. age) that is out of that scope, in fact the variable age is not known inside the getAge method, because it's declared in the main method.
You should move the variable declaration to class level, and then have all methods separated using those variables. I'll give you a sketch, not the complete solution:
import java.util.*;
public class Pets {
/* Insert all variable declarations here */
private double age;
/* Constructor if you need it */
public Pets(/* parameters you think you need */) {
// Set attributes when you declare a new Pets()
}
/* Insert all methods you need here */
public double getAge() {
return this.age;
}
The positioning of the main method - for what I've understoon from your description - should be placed outside this class, in another class where the whole application will start to run. The Pet class should serve only for anything concerning pets (the four methods you will need to implement and all getters/setters for retrieving private class variables).
You’ve happened to put about everything — private fields and public methods — inside you main method. That doesn’t make sense. Everything that is in your main, move it outside, right under the line public class Pets {. That should fix your compiler error.
I've been trying to figure this out for a while now, I've searched through various questions but I don't think I'm geting any closer.
I have a superclass Vehicle that gives various details of a vehicle. I then have my Car class which inherits from Vehicle.
I am trying to print the toString from my Car class, which overides the Vehicle class.
Basically I want to print out the information about each car.
Here is my main
public static void main(String []args)
{
Car Ford = new Car(Red, 4, true);
//Ford = Ford.; Tried various things here to no avail, kept getting nullfalse in output
Car Honda = new Car(Blue, 4, false);
System.out.println(Ford.vehicleDetails);
System.out.println("Honda");
}
}
This is my Vehicle class
public class Vehicle
{
// Variables
private String vehicleColour;
private int numberOfWheels;
String vehicleDetails;
// Constructor
public Vehicle(String vehicleColour, int numberOfWheels)
{
}
// Getters
public String getVehicleColour()
{
return vehicleColour;
}
public int getNumberOfWheels()
{
return numberOfWheels;
}
// Setters
public void setVehicleColour(String vehicleColour)
{
this.vehicleColour = vehicleColour;
}
public void setNumberOfWheels(int numberOfWheels)
{
this.numberOfWheels = numberOfWheels;
}
// Constructor
public Vehicle ()
{
}
// toString method super
public String toString() {
String vehicleDetails = (getVehicleColour() + "" + getNumberOfWheels());
return vehicleDetails;
}
}
And this is my Car class
public class Car extends Vehicle
{
// Variables
private boolean convertible;
Car vehicleDetails;
// Getter
public boolean getConvertible()
{
return convertible;
}
// Setter
public void setConvertible(boolean convertible)
{
this.convertible = convertible;
}
// Constructor
public Car(String vehicleColour, int numberOfWheels, boolean convertible) {
}
// toString method override
#Override
public String toString() {
return super.toString() + convertible;
}
}
I want my output to be something like "The Ford is red, has 4 wheels and is a convertible", with bold text coming from Vehicle and Car classes respectively.
How do I get the bold text to show up in output? Right now I just get default values such as null, 0 and false.
I appreciate any input, I know I am probably doing something really stupid that is really obvious but I just can't see it.
Thanks.
I want my output to be something like "The Ford is red, has 4 wheels and is a convertible"
For your above requirement simple solution:
System.out.println("The Ford is "+ Ford.toString() );
If you intend to get the whole output from Ford.toString then while creating Ford object you need to pass the car name too! Like shown below and make necessary changes in constructor for it!
Car Ford = new Car("Ford","Red", 4, true);
However constructors in Car and Vehicle class are made but no value is assigned to the local variables from them! Thus .toString() was not getting anything to show!
Required changes are:
In Main Class
System.out.println("The Ford is "+ Ford.toString() );
In Car Class
//change in the constructor
public Car(String vehicleColour, int numberOfWheels, boolean convertible) {
setNumberOfWheels(numberOfWheels);
setVehicleColour(vehicleColour);
setConvertible(convertible);
}
I am trying to print the toString from my Car class, which overides the Vehicle class.
//change in toString()
#Override
public String toString() {
if(convertible){
return super.toString() +"and is a convertible" ;
}else{
return super.toString() +"and is not a convertible" ;
}
}
In Vehicle Class
//change the constructor
public Vehicle(String vehicleColour, int numberOfWheels)
{
setNumberOfWheels(this.numberOfWheels);
setVehicleColour(this.vehicleColour);
}
Basically I want to print out the information about each car.
//change the toString()
public String toString() {
String vehicleDetails = (getVehicleColour() + ", has " +getNumberOfWheels()+" wheels");
return vehicleDetails;
}
I want my output to be something like "The Ford is red, has 4 wheels
and is a convertible", with bold text coming from Vehicle and Car
classes respectively.
If you want to include those words, then you have to tell it to include those words in your toString().
For example, if you want it to print "has 4 wheels", then you can't just do getNumberOfWheels(). Otherwise you just get a 4. You have to do something like "has " + getNumberOfWheels() + " wheels". If you don't tell it to include all the words, it won't know to include them.
I work on my first Java project, which is a basic roleplaying game. Now I work on spells, and I need some OOD guidance.
I have Character, which is an abstract class. Character has some subclasses (like mage, fighter, rogue, cleric).
Mage and cleric(as for now, cleric doesn't have mana, but it might change) are both spell-casters.
I also have a Spell class, with some info (like spell name, mana cost etc). MageSpellsList and ClericSpellsList are another classes and both have lists of class Spell. and I also have Effects class(casting a spell should use it).
What would be a good object oriented design for dealing with spells (the solution shouldn't include Effects class, I can deal with that later) ?
Maybe using a "SpellCaster" interface with some methods like castSpell and showSpellbook, so Mage and Cleric will implement the interface? .
Maybe MageSpellsList and ClericSpellsList should be a subclass of Spell ? My goal is to use castSpell("spell name here") and let castSpell do the job, by using a good OOD, rather than writing a specific method for each spell (and without duplicate code between mage and Cleric)
Mage.java:
public class Mage extends Character {
private List<Spell> spellBook;
private int mana;
private int CurrentMana;
public Mage(String name) {
super(name);
setName(name);
setCharacterClass("Mage");
setLevel(1);
setHitDice(4);
setStrength(10);
setConstitution(10);
setDexterity(14);
setIntelligence(16);
setWisdom(14);
setCharisma(10);
setHp((int) (4 + getModifier(getConstitution())));
setCurrentHp(getHp());
setArmorClass(10 + getModifier(getDexterity()));
setBaseAttackBonus(0);
setMana(20 + 2 * getModifier(getIntelligence()));
setCurrentMana(getMana());
spellBook = new ArrayList<Spell>();
}
public int getMana() {
return mana;
}
public int getCurrentMana() {
return CurrentMana;
}
protected void setMana(int mna) {
mana = mna;
}
protected void setCurrentMana(int CurrMana) {
CurrentMana = CurrMana;
}
public void showSpellBook() {
for (Iterator<Spell> iter = spellBook.iterator(); iter.hasNext(); ) {
Spell spell = iter.next();
System.out.println("Spell name: " + spell.getSpellName());
System.out.println("Spell effect: " + spell.getEffect());
}
}
public void addToSpellBook(String spellName) {
Spell newSpell;
newSpell = MageSpellsList.getSpell(spellName);
spellBook.add(newSpell);
System.out.println(newSpell.getSpellName() + " has been added to the spellbook");
}
public void chooseSpells() {
System.out.println();
}
void castSpell(String spellName, Character hero, Character target) {
try {
Spell spell = MageSpellsList.getSpell(spellName);
System.out.println("You casted: " + spellName);
System.out.println("Spell effect: " + spell.getEffect());
} catch (Exception e) {
System.out.println("No such spell");
}
}
}
Spell.java:
public class Spell {
private String name;
private int spellLevel;
private String effect;
private int manaCost;
private int duration;
Spell(String name, int spellLevel, String effect, int manaCost, int duration) {
this.name = name;
this.spellLevel = spellLevel;
this.effect = effect;
this.manaCost = manaCost;
this.duration= duration;
}
String getSpellName() { return name; }
int getSpellLevel() { return spellLevel; }
String getEffect() { return effect; }
int getManaCost() {
return manaCost;
}
int getDuration() { return duration; }
}
MageSpellsList.java:
public class MageSpellsList {
static List<Spell> MageSpellsList = new ArrayList<Spell>();
static {
MageSpellsList.add(new Spell("Magic Missiles", 1, "damage", 2, 0));
MageSpellsList.add(new Spell("Magic Armor", 1, "changeStat", 2, 0));
MageSpellsList.add(new Spell("Scorching Ray ", 2, "damage", 4, 0));
MageSpellsList.add(new Spell("Fireball", 3, "damage", 5,0 ));
MageSpellsList.add(new Spell("Ice Storm", 4, "damage", 8, 0));
}
static void showSpellsOfLevel(int spellLevel) {
try {
for (Iterator<Spell> iter = MageSpellsList.iterator(); iter.hasNext(); ) {
Spell spell = iter.next();
if (spellLevel == spell.getSpellLevel()) {
System.out.println("Spell name: " + spell.getSpellName());
System.out.println("Spell effect: " + spell.getEffect());
}
}
} catch (Exception e){
System.out.println("Epells of level " + spellLevel + " haven't been found in spells-list");
}
}
static Spell getSpell(String spellName) {
try {
for (Iterator<Spell> iter = MageSpellsList.iterator(); iter.hasNext(); ) {
Spell spell = iter.next();
if (spellName.equals(spell.getSpellName())) {
return spell;
}
}
} catch (Exception e){
System.out.println(spellName + " haven't been found in spells-list");
return null;
}
return null;
}
}
Effects.java:
public class Effects {
public void damage(int dice, Character attacker, Character target){
int damage = DiceRoller.roll(dice);
System.out.println(attacker.getName() + " dealt " + damage + " damage to " + target.getName());
target.setCurrentHp(target.getCurrentHp() - damage);
}
public static void damage(int n, int dice, int bonus, Character target) {
int damage = DiceRoller.roll(n,dice,bonus);
System.out.println("You dealt" + damage + "damage to " + target.getName());
target.setCurrentHp(target.getCurrentHp() - damage);
}
public static void heal(int n, int dice, int bonus, Character target) {
int heal = DiceRoller.roll(n,dice,bonus);
if (heal + target.getCurrentHp() >= target.getHp()) {
target.setCurrentHp(target.getHp());
} else {
target.setCurrentHp(target.getCurrentHp() + heal);
}
System.out.println("You healed" + heal + " hit points!");
}
public static void changeStat(String stat, int mod, Character target){
System.out.println(stat + " + " + mod);
switch (stat) {
case "strength":
target.setStrength(target.getStrength() + mod);
break;
case "constitution":
target.setConstitution(target.getConstitution() + mod);
break;
case "dexterity":
target.setDexterity(target.getDexterity() + mod);
break;
case "intelligence":
target.setIntelligence(target.getIntelligence() + mod);
break;
case "wisdom":
target.setWisdom(target.getWisdom() + mod);
break;
case "charisma":
target.setCharisma(target.getCharisma() + mod);
break;
case "armorClass":
target.setArmorClass(target.getArmorClass() + mod);
break;
}
}
}
Preamble
I try to generalise the classes as much as possible, so I do not end up with lots of specific classes that just represent different data, instead of a different structure. Also, I try to separate data structures from game mechanics. In particular, I try to keep the combat mechanics all in one place, instead of splitting them across different classes, and I try not to hard-code any data. In this answer, we will cover the characters, their abilities/spells, the effects of the abilities, and the combat mechanics.
Characters
Consider, for instance, a PlayableCharacter, that represents your characters. This is a standard data class. It provides methods for increasing or decreasing health and mana, and a collection of available abilities.
class PlayableCharacter {
private final int maxHealth;
private int health;
private final int maxResource; // mana, energy and so on
private int resource;
private final Collection<Ability> abilities;
// getters and setters
}
Abilities
Abilities are equally data classes. They represent mana costs, triggered effects, and so on. I often represent this as a normal class, and then read the individual abilities from external data files. Here we can skip that and declare them with enumerations.
enum Ability {
FIREBALL("Fireball", 3, 5, new Effect[] {
new Effect(Mechanic.DAMAGE, 10, 0),
new Effect(Mechanic.BURN, 2, 3)
});
private final String name;
private final int level;
private final int cost;
private final List<Effect> effects;
}
Effects
Finally the effects tell what an ability does. How much damage, how long it lasts, how it affects a character. Again, this is all data, no game logic.
class Effect {
private final Mechanic effect;
private final int value;
private final int duration;
}
The mechanics are just an enumeration.
enum Mechanic {
DAMAGE, BURN;
}
Mechanics
Now it is time to make things work properly. This is the class that your game loop will be interacting with, and you must feed it the game state (which characters are battling, for instance).
class BattleEngine {
void useAbility(PlayableCharacter source, PlayableCharacter target, Ability ability) {
// ...
}
}
How you implement each mechanic is up to you. It can range from an infernal switch or if/else for each Mechanic, or you can move the code to the Mechanic enum, or to private nested classes and use an EnumMap to retrieve each handler.
Example Mechanic
interface MechanicHandler {
void apply(PlayableCharacter source, PlayableCharacter target, Effect effect);
}
class BattleEngine {
private final Map<Mechanic, MechanicHandler> mechanics;
void useAbility(PlayableCharacter source, PlayableCharacter target, Ability ability) {
source.decreaseResource(ability.getCost());
for (Effect effect: ability.getEffects()) {
MechanicHandler mh = mechanics.get(e.getMechanic());
mh.apply(source, target, effect);
}
}
private static final class DicePerLevel implements MechanicHandler {
#Override
public void apply(PlayableCharacter source, PlayableCharacter target, Effect effect) {
int levels = Math.min(effect.getValue(), source.getLevel());
int damage = 0;
for (int i = 0; i < levels; ++i) {
int roll; // roll a d6 die
damage += roll;
}
target.decreaseHealth(damage);
}
}
}
The SpellCaster spellbook should be a Map<String, Spell> so you can look it up by name when it is cast. The Spell class should define an abstract method for applying the effects to a Character. I don't see the point of a "SpellCaster" interface because the implementation of the castSpell() method is always the same (the behavior is delegated to the Spell itself).
Here is a sample scenario:
Mage fireMage = new Mage("Red Niminim");
fireMage.addSpell(new Fireball());
fireMage.addAttribute(Attribute.RESIST_FIRE);
fireMage.addAttribute(Attribute.WEAK_TO_COLD);
Mage iceMage = new Mage("Blue Niminim");
fireMage.addSpell(new Icestorm());
fireMage.addAttribute(Attribute.RESIST_COLD);
fireMage.addAttribute(Attribute.WEAK_TO_FIRE);
Cleric cleric = new Cleric("Friar Joe");
cleric.addSpell(new Heal());
// battle!
fireMage.castSpell("Fireball", cleric); // 15 damage
fireMage.castSpell("Fireball", iceMage); // 30 damage
fireMage.castSpell("Fireball", fireMage); // 0 damage
iceMage.castSpell("Icestorm", cleric); // 15 damage
iceMage.castSpell("Icestorm", fireMage); // 30 damage
iceMage.castSpell("Icestorm", iceMage); // 0 damage
cleric.castSpell("Heal", cleric); // 15 healed
Attribute.java
public enum Attribute {
RESIST_FIRE, WEAK_TO_FIRE, RESIST_COLD, WEAK_TO_COLD;
}
Spell.java
public abstract class Spell {
private String name;
private int manaCost;
public Spell(String name, int manaCost) {
this.name = name;
this.manaCost = manaCost;
}
public abstract void apply(Character character);
public String getName() {
return name;
}
public int getManaCost() {
return manaCost;
}
}
SpellCaster.java (snippet)
public void castSpell(String name, Character character) {
getSpellBook().get(name).apply(character);
}
public void addSpell(Spell spell) {
getSpellBook().put(spell.getName(), spell);
}
Fireball.java
public class Fireball extends Spell {
private static final String NAME = "Fireball";
private static final int MANA_COST = 8;
private static final int DAMAGE_AMOUNT = 15;
public Fireball() {
super(NAME, MANA_COST);
}
#Override
public void apply(Character character) {
int damage = DAMAGE_AMOUNT;
if (character.getAttributes().contains(Attribute.RESIST_FIRE)) {
damage = 0;
}
else if (character.getAttributes().contains(Attribute.WEAK_TO_FIRE)) {
damage = damage * 2;
}
character.setCurrentHp(character.getCurrentHp() - damage);
}
}
Icestorm.java
public class Icestorm extends Spell {
private static final String NAME = "Icestorm";
private static final int MANA_COST = 8;
private static final int DAMAGE_AMOUNT = 15;
public Icestorm() {
super(NAME, MANA_COST);
}
#Override
public void apply(Character character) {
int damage = DAMAGE_AMOUNT;
if (character.getAttributes().contains(Attribute.RESIST_COLD)) {
damage = 0;
}
else if (character.getAttributes().contains(Attribute.WEAK_TO_COLD)) {
damage = damage * 2;
}
character.setCurrentHp(character.getCurrentHp() - damage);
}
}
Heal.java
public class Heal extends Spell {
private static final String NAME = "Heal";
private static final int MANA_COST = 10;
private static final int HEAL_AMOUNT = 15;
public Heal() {
super(NAME, MANA_COST);
}
#Override
public void apply(Character character) {
character.setCurrentHp(character.getCurrentHp() + HEAL_AMOUNT);
}
}
Here is an example of how you can use enum instead of strings in your Effects class. I took the liberty of renaming your Character class to PlayerCharacter to avoid collision with java.lang.Character.
Effects.java:
public class Effects {
...
public static void changeStat(Stat status, int mod, PlayerCharacter target) {
System.out.println(status + " + " + mod);
status.effect(mod).accept(target);
}
}
A little bit cleaner, isn't it? How it works? The magic is all in the enum:
Stat.java:
import java.util.function.Consumer;
import java.util.function.IntUnaryOperator;
import java.util.function.ObjIntConsumer;
import java.util.function.ToIntFunction;
public enum Stat {
STRENGTH(PlayerCharacter::getStrength, PlayerCharacter::setStrength),
CONSTITUTION(PlayerCharacter::getConstitution, PlayerCharacter::setStrength),
DEXTERITY(PlayerCharacter::getDexterity, PlayerCharacter::setDexterity),
INTELLIGENCE(PlayerCharacter::getIntelligence, PlayerCharacter::setIntelligence),
WISDOM(PlayerCharacter::getWisdom, PlayerCharacter::setWisdom),
CHARISMA(PlayerCharacter::getCharisma, PlayerCharacter::setCharisma),
ARMORCLASS(PlayerCharacter::getArmorClass, PlayerCharacter::setArmorClass);
Stat(ToIntFunction<PlayerCharacter> findcurrentvalue, ObjIntConsumer<PlayerCharacter> setnewvalue) {
this.findcurrentvalue = findcurrentvalue;
this.setnewvalue = setnewvalue;
}
private ToIntFunction<PlayerCharacter> findcurrentvalue;
private ObjIntConsumer<PlayerCharacter> setnewvalue;
Consumer<PlayerCharacter> effect(int mod) {
return target -> {
setnewvalue.accept(target, findcurrentvalue.applyAsInt(target) + mod);
};
}
}
The two mysterious types ToIntFunction and ObjIntConsumer are functional interfaces:
ToIntFunction takes some kind of object as input (here: a PlayerCharacter) and returns an int.
ObjIntConsumer takes some kind of object (here: a PlayerCharacter) and an int as input, and returns nothing.
You could also create your own functional interface if you like, like so:
Effect.java:
#FunctionalInterface
public interface Effect<T extends PlayerCharacter> {
void affect(T t);
}
Stat.java:
...
Effect<PlayerCharacter> effect(IntUnaryOperator calculator) {
return target -> {
setnewvalue.accept(target, calculator.applyAsInt(findcurrentvalue.applyAsInt(target)));
};
}
...
Then you can do this in changeStat:
public class Effects {
...
public static void changeStat(Stat status, int mod, PlayerCharacter target) {
System.out.println(status + " + " + mod);
status.effect(x -> x + mod).affect(target);
}
}
This way you can decide in the Effects class what will happen. Well, I don't imagine the character stats to change much from spells, but a similar mechanic can be used for HP and such :)
The x -> x + mod bit could come from the spell itself too. It's a function that takes an int and returns an int, which is called an IntUnaryOperator in Java:
Effects.java:
...
public static void boost(int dice, PlayerCharacter target) {
int value = DiceRoller.roll(dice);
changeStat(Stat.STRENGTH, x -> x + value, target);
}
public static void changeStat(Stat status, IntUnaryOperator change, PlayerCharacter target) {
status.effect(change).affect(target);
}
...
Here the spell (boost in this case, which I just invented!) will increase the player's strength (the STRENGTH constant) by a dice roll. It accomplishes this by calling the changeStat with three parameters:
STRENGTH → tells the method what status to change.
A "formula" for changing the value (note that you don't actually need to know the value here, just the formula!).
The target to affect.
As you can see, there is no need here to know how to find the strength value, or how to set it to something else. That is all handled by the enum, so you can keep your spell code clean.
You could even inline the changeStat method directly in the spell method this way, since there isn't really any "real" code in it anymore – that logic is hidden in the enum.
Clean and neat :)
I think your idea of having a SpellCaster interface (which includes the castSpell()) is a good one. This defines the behavior or ability of the character.
I would include the list of available spells as an instance field in the Mage or Cleric classes. Come to think of it, maybe it would be a good idea to create an abstract class called SpellCaster which extends Character. The SpellCaster class can declare the list of spells and subclasses (Mage and Cleric) can add specific spells to it.
I'm going to discard the Effects class for now. Each spell can take care of its own behavior. So for example, when calling castSpell("spellName", hero, target) you can pass the required parameters to the spell object and it can take care of dealing the damage or changing stats.
In addition, there could be multiple Spell subclasses. For example, DamageSpell, Buff, Debuff. The superclass Spell has a method apply() and each subclass can implement it with it's own behavior. When calling castSpell() then you delegate the control to a specific subclass of a Spell which has encapsulated the behavior and knows exactly if it should deal damage or change stats. That's essentially the Strategy Pattern.
Why treat spells different than abilities? A fighter class might not have spells as magic spells, but it should be able to perform class specific moves like a whirlwind.
Class PlayableCharacter: abstract class, defines the abstract methods for handling resources(regen rate, max, effects on character), abilities, gear. And implements all the basics.
Class ManaCharacter: extends PlayableCharacter handles it resource as mana.
Class Mage extends ManaCharacter: Will just implement the methods to define what kind of gear it can use, the special abilities it can perform, etc.