I've written a program that simulates a fight between the player and an enemy to gain experience in the programming of such role-playing games. Above all, it was important to me to have good, object-oriented design. Criticism in this regard is expressly desired.
Explanation of the skills:
Fireball (needs 1 mana) - this is a skill that causes damage on an enemy. Nothing special.
Healing (needs 3 mana) - this skill heals the caster.
Knock Out (needs 2 mana) - this skill knocks the enemy out. He cant cast skills in this or in the next round. He also gets damage.
Poisoning (needs 3 mana) - the enemy gets damage and is poisoned in the next rounds. So he gets damage in the next rounds.
Sorry for my bad english. I will improve it :)
The program can be tested here
Here are my source files
Game.java
public class Game {
private Player player;
private NPC enemy;
private int score;
public Game() {
player = new Player("Player", 20, 5, 5);
enemy = new NPC("Enemy", 20, 5, 5);
}
public boolean fight() {
UF.println(player.getName() + " fights against " + enemy.getName(), 1000);
int rounds = 1;
while(player.getLife() > 0 && enemy.getLife() > 0) {
UF.println(" --- Round " + rounds + " --- \n", 1000);
UF.println(player.getName() + " Stats", 0);
UF.println("Life: " + UF.displayGraphical(player.getLife(), player.getLifeMax(), "#"), 0);
UF.println("Mana: " + UF.displayGraphical(player.getMana(), player.getManaMax(), "@") + "\n", 1000);
UF.println(enemy.getName() + " Stats", 0);
UF.println("Life: " + UF.displayGraphical(enemy.getLife(), enemy.getLifeMax(), "#"), 0);
UF.println("Mana: " + UF.displayGraphical(enemy.getMana(), enemy.getManaMax(), "@") + "\n", 1000);
player.isPoisoned();
if(player.getLife() <= 0) break;
player.chooseSkill(enemy);
if(enemy.getLife() <= 0 || player.getLife() <= 0) break;
enemy.isPoisoned();
if(enemy.getLife() <= 0) break;
enemy.chooseSkill(player);
rounds++;
}
if(player.getLife() > 0 && enemy.getLife() <= 0) {
UF.println("YOU HAVE WON! YOU ARE NOW STRONGER", 2000);
UF.println("PREPARE FOR THE NEXT FIGHT!\n", 2000);
score++;
enemy.getStronger(1.2);
player.getStronger();
return true;
} else {
UF.println("Game over!", 0);
UF.println("You reached " + score + " Points. Gratulation!", 2000);
return false;
}
}
public void mainLoop() {
while(fight());
}
public static void main(String[] args) {
Game game = new Game();
game.mainLoop();
}
}
UF.java
// name stands for "useful functions"
public class UF {
public static void println(String text, int time) {
System.out.println(text);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static String displayGraphical(int value1, int value2, String symbol) {
if(value2 < value1) {
return "error";
}
String text = "[";
int numberOfSymbols = 0;
int numberOfWhiteSpace = 20;
float filledOut = (float)value1 / (float)value2;
numberOfSymbols = (int)(filledOut * 20);
for(int i = 0; i < numberOfSymbols; i++) {
text += symbol;
}
numberOfWhiteSpace -= numberOfSymbols;
for(int i = 0; i < numberOfWhiteSpace; i++) {
text += " ";
}
text += "] (" + value1 + " / " + value2 + ")";
return text;
}
}
Being.java
import java.util.Scanner;
public class Being {
// basic values
private String name;
protected int lifeMax;
protected int manaMax;
protected int powerMax;
protected int lifeActual;
protected int manaActual;
protected int powerActual;
// skills
protected Skill fireball;
protected Skill knockOut;
protected Skill healing;
protected Skill poisoning;
// state values
protected boolean alive;
protected boolean isKnockedOut;
protected int[] isPoisoned;
public Being(String name, int lifeMax, int manaMax, int powerMax) {
this.name = name;
this.lifeMax = lifeMax;
this.manaMax = manaMax;
this.powerMax = powerMax;
this.lifeActual = lifeMax;
this.manaActual = manaMax;
this.powerActual = powerMax;
// skills
fireball = new Skill(this, "Fireball", 1, 1, 1);
knockOut = new Skill(this, "Knock Out", 1, 0.5, 2);
healing = new Skill(this, "Healing", 1, 2, 3);
poisoning = new Skill(this, "Poisoning", 1, 0.2, 3);
// state values
alive = true;
isKnockedOut = false;
// how many rounds, how big is damage
isPoisoned = new int[2];
}
@Override
public String toString() {
String text = "";
text += name + "\n";
text += "Life: " + lifeActual + " / " + lifeMax + "\n";
text += "Mana: " + manaActual + " / " + manaMax + "\n";
text += "Power: " + powerMax + " / " + powerMax + "\n";
return text;
}
public String getName() {
return name;
}
public int getLife() {
return lifeActual;
}
public int getLifeMax() {
return lifeMax;
}
public int getMana() {
return manaActual;
}
public int getManaMax() {
return manaMax;
}
public int getPower() {
return powerActual;
}
public boolean isKnockedOut() {
if(isKnockedOut) {
UF.println(name + " is knocked out and can't cast skills this round\n", 1000);
isKnockedOut = false;
return true;
} else {
return false;
}
}
public void isPoisoned() {
if(isPoisoned[0] > 0) {
UF.println(name + " is poisoned", 1000);
sustainDamage(isPoisoned[1]);
isPoisoned[0]--;
}
}
public boolean isAlive() {
return alive;
}
public void sustainDamage(int value) {
if(value > 0) {
lifeActual -= value;
UF.println(name + " has gotten " + value + " damage\n", 1000);
}
if (lifeActual <= 0) {
alive = false;
}
}
public boolean useMana(int value) {
if(value <= manaActual) {
manaActual -= value;
UF.println(name + " has used " + value + " mana.", 1000);
return true;
} else {
UF.println(name + " has not enough mana.", 1000);
return false;
}
}
public void cureLife(int value) {
int difference = lifeMax - lifeActual;
if(value >= difference) {
lifeActual = lifeMax;
UF.println(name + " is fully healed.", 1000);
} else {
lifeActual += value;
UF.println(name + " has healed " + value + " life points.", 1000);
}
}
public void castFireball(Being enemy) {
UF.println(name + " casts " + fireball.getName() + " on " + enemy.getName() + ".", 1000);
if(useMana(fireball.getManaRequirement())) {
enemy.sustainDamage(fireball.getValue());
}
}
public void castHealing() {
UF.println(name + " casts " + healing.getName() + ".", 1000);
if(useMana(healing.getManaRequirement())) {
cureLife(healing.getValue());
}
}
public void castKnockOut(Being enemy) {
UF.println(name + " casts " + knockOut.getName() + " on " + enemy.getName() + ".", 1000);
if(useMana(knockOut.getManaRequirement())) {
enemy.sustainDamage(knockOut.getValue());
enemy.isKnockedOut = true;
UF.println(enemy.getName() + " is knocked out.", 1000);
}
}
public void castPoisoning(Being enemy) {
UF.println(name + " casts " + poisoning.getName() + " on " + enemy.getName() + ".", 1000);
if(useMana(poisoning.getManaRequirement())) {
enemy.sustainDamage(powerActual / 2);
enemy.isPoisoned[0] = poisoning.getLevel() + 2;
enemy.isPoisoned[1] = poisoning.getValue();
}
}
}
Player.java
import java.util.Scanner;
public class Player extends Being {
private static Scanner input = new Scanner(System.in);
public Player(String name, int lifeMax, int manaMax, int powerMax) {
super(name, lifeMax, manaMax, powerMax);
}
public void chooseSkill(Being enemy) {
if(!isKnockedOut()) {
System.out.println("[1] Fireball");
System.out.println("[2] Healing");
System.out.println("[3] Knock Out");
System.out.println("[4] Poisoning");
System.out.println("[5] Give up");
System.out.println("[*] Do nothing");
System.out.print("Input: ");
String command = input.next();
if(command.equals("1")) {
castFireball(enemy);
} else if (command.equals("2")) {
castHealing();
} else if (command.equals("3")) {
castKnockOut(enemy);
} else if (command.equals("4")) {
castPoisoning(enemy);
} else if (command.equals("5")) {
lifeActual = 0;
}
}
}
public void getStronger() {
lifeMax += 5;
manaMax += 2;
powerMax += 1;
lifeActual = lifeMax;
manaActual = manaMax;
powerActual = powerMax;
isKnockedOut = false;
isPoisoned[0] = 0;
isPoisoned[1] = 0;
}
}
NPC.java
import java.util.concurrent.ThreadLocalRandom;
public class NPC extends Being {
public NPC(String name, int lifeMax, int manaMax, int powerMax) {
super(name, lifeMax, manaMax, powerMax);
}
// this is actually my first KI, i am so proud
public void chooseSkill(Being enemy) {
if(isKnockedOut()) {
return;
}
if (manaActual == 0) {
lifeActual = 0;
UF.println(getName() + " gives up because he has no mana.", 1000);
return;
}
// decide for a skill
int decision;
while(true) {
decision = ThreadLocalRandom.current().nextInt(0, 4);
// i hope this is an accepted style, but i suspect it isnt
if((decision == 1 && healing.getManaRequirement() > manaActual)
|| (decision == 1 && lifeMax == lifeActual)
|| (decision == 2 && knockOut.getManaRequirement() > manaActual)
|| (decision == 3 && poisoning.getManaRequirement() > manaActual)) {
continue;
} else {
break;
}
}
// cast the skill
if(decision == 0) {
castFireball(enemy);
} else if (decision == 1) {
castHealing();
} else if (decision == 2) {
castKnockOut(enemy);
} else if (decision == 3) {
castPoisoning(enemy);
}
}
public void getStronger(double factor) {
lifeMax *= factor;
manaMax *= factor;
manaMax += 3;
powerMax += factor;
lifeActual = lifeMax;
manaActual = manaMax;
powerActual = powerMax;
isKnockedOut = false;
isPoisoned[0] = 0;
isPoisoned[1] = 0;
}
}
Skill.java
public class Skill {
private Being caster;
private String name;
private int level;
private double value;
private int manaRequirement;
public Skill(Being caster, String name, int level, double value, int manaRequirement) {
this.caster = caster;
this.name = name;
this.level = level;
// means strength of the skill
// multiplicated with level
this.value = value;
// also multiplicated with level
this.manaRequirement = manaRequirement;
}
public String getName() {
return name;
}
public int getValue() {
return (int)(caster.getPower() * value * level);
}
public int getLevel() {
return level;
}
public int getManaRequirement() {
return manaRequirement * level;
}
}
EDIT
I have two more questions that I would like to have answered.
Different languages
Let's say I want to publish my program in different languages. Accordingly, all output statements would have to be adjusted. What methodologies exist for such a project?
Graphic surface
Suppose I want to plot all data graphically. I would have to rewrite all classes. Is there an effective way to separate the processing of the data from the output of the data?