Your world gets dangerous. Add a Monster class, roaming enemies, and a full turn-based battle system, attack, defend, flee, plus XP and level-ups when you win.
๐ถ Ages 11+โฑ๏ธ ~2 Hoursโ Javaโ Free
๐น Monster class๐ฒ Random encountersโ๏ธ Turn-based combat๐ก๏ธ Defend actionโจ XP & level-ups๐ฌ Battle state
Reuse the Hero pattern for goblins, wolves and slimes
Active
๐ฏ
Goal for this step
Create a Monster class and a factory method that builds different enemy types.
1Continue in your Part 1 project, everything builds on it.
2Monster looks like a mini Hero: name, hp, maxHp, attack, plus xpReward and goldReward.
3Add a static Monster create(String type) factory: "slime" is weak, "wolf" is medium, "goblin" hits hard.
4Notice how similar Monster and Hero are, spotting shared patterns is a real developer skill (Part 3 will exploit it).
Monster.java
public class Monster {
String name;
int hp, maxHp, attack, xpReward, goldReward;
public Monster(String name, int hp, int attack, int xp, int gold) {
this.name = name; this.maxHp = hp; this.hp = hp;
this.attack = attack; this.xpReward = xp; this.goldReward = gold;
}
public static Monster create(String type) {
switch (type) {
case "slime": return new Monster("Slime", 12, 3, 8, 5);
case "wolf": return new Monster("Wolf", 20, 5, 15, 12);
case "goblin": return new Monster("Goblin", 28, 7, 25, 20);
}
return create("slime");
}
public void takeDamage(int amount) { hp = Math.max(0, hp - amount); }
public boolean isAlive() { return hp > 0; }
}
โ๏ธ
Fill in the Blanks
+15 XP
Monster.create("wolf") is a factory method, we call it on the class itself without using outside. Each monster carries an xpReward and a for when it is defeated.
๐ง
Knowledge Check
+15 XP
What is handy about the create(type) factory pattern?
AIt is the only way to make objects
BAll monster stats live in ONE place, balancing the game means editing one method
CStatic methods run faster
2
๐ฒ
Random Encounters
Each step in the wild might start a battle
Locked
๐ฏ
Goal for this step
Trigger battles as the hero explores, with smart odds that feel fair.
1Add game state to GamePanel: String state = "EXPLORE"; and a Monster foe; field.
2After every successful step onto grass, roll the dice: Math.random() < 0.15 โ 15% encounter chance.
3On an encounter: pick a random monster type (slimes common, goblins rare), set state = "BATTLE".
4While state is "BATTLE", arrow keys must NOT move the hero, the state check does that.
5Fairness trick: guarantee 3 safe steps after each battle with a small safeSteps counter.
GamePanel.java
String state = "EXPLORE";
Monster foe;
int safeSteps = 0;
// after a successful move in keyPressed:
if (state.equals("EXPLORE") && map[heroRow][heroCol] == 0) {
if (safeSteps > 0) safeSteps--;
else if (Math.random() < 0.15) {
double roll = Math.random();
String type = roll < 0.5 ? "slime"
: roll < 0.85 ? "wolf" : "goblin";
foe = Monster.create(type);
state = "BATTLE";
}
}
โจ๏ธ
Code Challenge
+20 XP
Set the encounter odds: 50% slime, 35% wolf, 15% goblin.
GamePanel.java
String type = roll < ? "slime"
: roll < ? "wolf" : "goblin";
๐ก Hint: roll is between 0 and 1. Slime claims the first half; wolf claims up to 0.85 (0.5 + 0.35); goblin gets the rest.
๐ง
Knowledge Check
+15 XP
Why guarantee a few safe steps after each battle?
AMonsters need time to respawn
BBack-to-back random battles feel unfair and exhausting, pacing is part of game design
CJava random needs to reset
3
โ๏ธ
The Battle Screen
Draw the combat UI: two fighters, HP bars, action menu
Locked
๐ฏ
Goal for this step
When a battle starts, replace the map with a proper battle screen.
1In paintComponent: if state is "BATTLE", draw the battle screen INSTEAD of the map, dark backdrop, hero bottom-left, monster top-right.
2Both fighters get name + HP bar (you wrote that bar code in Part 1, reuse it!).
3Action menu along the bottom: [A]ttack [D]efend [P]otion [F]lee.
4Add a String battleLog, one line describing what just happened ("Wolf bites you for 5!"). Draw it centre-screen.
5No logic yet, just the visuals, next step brings it to life.
GamePanel.java (paintComponent)
if (state.equals("BATTLE")) {
g.setColor(new Color(20, 12, 28));
g.fillRect(0, 0, 700, 560);
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.BOLD, 22));
g.drawString(hero.getName(), 60, 380);
g.drawString(foe.name, 470, 140);
drawBar(g, 60, 392, hero.getHp(), hero.getMaxHp());
drawBar(g, 470, 152, foe.hp, foe.maxHp);
g.drawString(battleLog, 60, 280);
g.drawString("[A]ttack [D]efend [P]otion [F]lee", 60, 500);
return; // skip drawing the map
}
void drawBar(Graphics g, int x, int y, int val, int max) {
g.setColor(Color.RED); g.fillRect(x, y, 150, 14);
g.setColor(new Color(6,214,160)); g.fillRect(x, y, 150 * val / max, 14);
}
โ๏ธ
Fill in the Blanks
+15 XP
We reused the HP bar by pulling it into a method called . The early stops the map being drawn under the battle screen.
๐ง
Knowledge Check
+15 XP
The battle screen and map are both drawn by paintComponent. What chooses between them?
AThe Timer speed
BThe state variable, one screen per state, checked at the top
CThe window size
4
๐ก๏ธ
Turn Logic: Attack, Defend, Flee
You act, then the monster acts, the full turn cycle
Locked
๐ฏ
Goal for this step
Implement the complete turn: player action, then the monster hits back.
1In keyPressed, when state is "BATTLE": A attacks (damage = hero attack ยฑ a little randomness), D defends, P uses a potion, F tries to flee (60% chance).
2Damage variance keeps it spicy: hero.getAttack() + (int)(Math.random()*4) - 1 โ attack-1 up to attack+2.
3After YOUR action, if the foe still lives, it strikes back, defending halves that hit.
4Update battleLog after both actions so the player can follow the fight.
5If hero HP hits 0 โ state = "GAME_OVER". Fleeing successfully โ back to "EXPLORE" with safeSteps = 3.
GamePanel.java
boolean defending = false;
void battleAction(char key) {
defending = false;
if (key == 'a') {
int dmg = hero.getAttack() + (int)(Math.random()*4) - 1;
foe.takeDamage(dmg);
battleLog = "You hit " + foe.name + " for " + dmg + "!";
} else if (key == 'd') {
defending = true;
battleLog = "You brace behind your shield.";
} else if (key == 'p') {
battleLog = hero.useItem("Potion") ? "Glug! Potion heals 12." : "No potions left!";
} else if (key == 'f') {
if (Math.random() < 0.6) { state = "EXPLORE"; safeSteps = 3;
battleLog = ""; return; }
battleLog = "Could not escape!";
} else return;
if (foe.isAlive()) {
int dmg = foe.attack + (int)(Math.random()*3);
if (defending) dmg /= 2;
hero.takeDamage(dmg);
battleLog += " " + foe.name + " hits for " + dmg + "!";
if (!hero.isAlive()) state = "GAME_OVER";
}
}
โจ๏ธ
Code Challenge
+20 XP
Make defending halve the monsterโs counter-attack:
GamePanel.java
int dmg = foe.attack + (int)(Math.random()*3);
if () dmg 2;
hero.takeDamage(dmg);
๐ก Hint: Check the boolean set by the D action; integer divide-and-assign halves the value.
๐ง
Knowledge Check
+15 XP
Why does the monster only counter-attack if (foe.isAlive())?
ADead monsters hitting you back would be absurd, always check state after dealing damage
BIt saves memory
CisAlive() heals the monster
5
โจ
Victory, XP & Level-Ups
Win the fight, claim rewards, grow stronger
Locked
๐ฏ
Goal for this step
Reward victory with XP and gold, and level the hero up at thresholds.
1After your attack, if !foe.isAlive(): victory! Add foe.xpReward and foe.goldReward to the hero, log it, return to EXPLORE.
2Give Hero a gainXp(int) method: add XP, and while XP โฅ level*25, subtract that cost, level++, maxHp += 6, attack += 2, and refill HP.
3A full heal on level-up feels GREAT, reward the grind.
4Show level and XP progress in the HUD: Lv 3 ยท XP 12/75.
5Balance check: a slime should take ~3 hits at level 1 and 1-2 at level 3. Feel the power curve!
Hero.java
public void gainXp(int amount) {
xp += amount;
while (xp >= level * 25) {
xp -= level * 25;
level++;
maxHp += 6;
attack += 2;
hp = maxHp; // level-up full heal!
}
}
public int getLevel() { return level; }
public void addGold(int g) { gold += g; }
โ๏ธ
Fill in the Blanks
+15 XP
Levelling costs level ร XP. On level-up the hero gains 6 max HP, attack, and is healed to .
๐ง
Knowledge Check
+15 XP
Why is gainXp a while loop, not an if?
AWhile loops are safer
BA huge XP reward (like a boss) might grant several level-ups at once
Cif statements cannot change fields
6
๐
Defeat, Respawn & Balance Pass
Game over screen, respawning, and tuning the numbers
Locked
๐ฏ
Goal for this step
Handle losing gracefully and tune combat until it feels fair but tense.
1GAME_OVER state: dark screen, "YOU FELLโฆ", stats reached, and [R] Try Again.
2On R: respawn at the start tile with half max HP and half gold (a sting, not a wipe) and state = "EXPLORE".
3Balance testing is a real job, check: Can you survive 3 slimes in a row at level 1? Does a goblin feel scary but beatable at level 2?
4Tune: encounter rate (0.15), potion power (12), defend halving, flee odds (0.6). Small numbers, huge feel changes.
5Part 2 done! Your world is alive and dangerous. Part 3: NPCs, a quest, and the Dragon boss finale.
GamePanel.java
if (state.equals("GAME_OVER") && e.getKeyChar() == 'r') {
heroRow = 1; heroCol = 1;
hero.heal(hero.getMaxHp() / 2); // half HP respawn
state = "EXPLORE";
safeSteps = 5;
}
๐ก
Losing should cost something, but never everything. Players quit games that wipe them, and quit games with no danger. Sit in between.
โจ๏ธ
Code Challenge
+20 XP
Give the fallen hero a fair respawn:
GamePanel.java
heroRow = 1; heroCol = ; // back to the start
hero.heal(hero.getMaxHp() / ); // half health
safeSteps = ; // breathing room
๐ก Hint: Start tile was (1,1). Respawn at half of max HP, with five guaranteed-safe steps.
๐ง
Knowledge Check
+15 XP
Which is the best-designed death penalty for this game?
ADelete the save file
BRespawn at start with half HP/gold, losing stings but you keep your levels and continue
CNothing happens at all
๐๐๐ฎโจ๐
Workshop Complete!
Turn-based combat, XP and level-ups, the beating heart of every RPG, built by you. Part 3 finishes the quest with bosses and the final goal!