Level 4 – Layered Bugs (≈ 10 minutes, stretch)
Concepts: layered bugs, unexpected control flow, combining debugger tools, fixing bugs without breaking other behavior.
This Level uses a small “level loader” that:
- Reads a level number from a (fake) string.
- Loads some enemy counts.
- Computes total difficulty.
The code has:
- A runtime bug (exception).
- A logic bug.
- A bug where stepping into another file is useful.
1. Setup the files
We’ll add two more files:
LevelLoader.javaInputParser.java
InputParser.java
public class InputParser {
// Parses level from an input string like "level:3"
public static int parseLevel(String input) {
// BUG 1: assumes the string always has "level:" and a valid number
String[] parts = input.split(":");
String numberPart = parts[1]; // can throw if input is wrong
// BUG 2: doesn't trim spaces, can cause NumberFormatException
return Integer.parseInt(numberPart);
}
}
LevelLoader.java
public class LevelLoader {
// Returns an array of enemy counts for the given level.
public static int[] loadEnemiesForLevel(int level) {
if (level == 1) {
return new int[] {2, 3};
} else if (level == 2) {
return new int[] {4, 4, 5};
} else if (level == 3) {
return new int[] {10};
} else {
// BUG 3: unexpected control flow – returns null instead of empty array
return null;
}
}
// Computes a "difficulty" score from enemy counts.
public static int computeDifficulty(int[] enemies) {
int difficulty = 0;
// BUG 4: off‑by‑one and assumes enemies is never null
for (int i = 0; i <= enemies.length; i++) {
difficulty += enemies[i] * (i + 1);
}
return difficulty;
}
}
Update GameApp.java
Add this method to the bottom of GameApp (inside the class, after printPlayerStats):
private static void demoLevels() {
System.out.println("=== Level Loader Demo ===");
String input = "level: 4"; // note the space before 4
System.out.println("Parsing input: \"" + input + "\"");
int level = InputParser.parseLevel(input);
System.out.println("Parsed level: " + level);
int[] enemies = LevelLoader.loadEnemiesForLevel(level);
int difficulty = LevelLoader.computeDifficulty(enemies);
System.out.println("Total difficulty for level " + level + ": " + difficulty);
}
Then, in main, after the mystery player block, add:
demoLevels();
Make sure GameApp.java now:
- Uses
Player,ScoreUtils,LevelLoader, andInputParser.
2. Run and observe
- Open
GameApp.java. - Click Run.
Expected symptoms:
- The scoreboard section should behave as in Level 3 (assuming you fixed it).
- The Level Loader demo will likely:
- Throw a
NumberFormatExceptionorNullPointerException. - Or print a strange difficulty.
- Throw a
Look at:
- The exception type.
- The stack trace: which method and line caused it?
3. Step through the layered flow
Use the debugger to trace the whole path:
- Set a breakpoint at the first line of
demoLevels. - Click Debug.
- When paused:
- Step Over the print statements.
- Use Step Into on
InputParser.parseLevel(input).
Inside parseLevel:
- Inspect
input,parts, andnumberPart. - Step line‑by‑line and watch for:
ArrayIndexOutOfBoundsExceptiononparts[1].NumberFormatExceptiononInteger.parseInt.
Once you understand the problem, stop debugging and fix parseLevel to:
- Validate the input format.
- Trim whitespace.
- Return a reasonable default (for example
1) or throw a clearer exception.
Run again with the debugger to confirm the parsing step works.
4. Follow the null / off‑by‑one bugs
Next, set a breakpoint on:
int[] enemies = LevelLoader.loadEnemiesForLevel(level);
- Debug again and Step Into
LevelLoader.loadEnemiesForLevel. - For the given
level, see which branch runs and what it returns. - Step back to
demoLevelsand then Step IntocomputeDifficulty.
Inside computeDifficulty:
- Watch the values of
i,enemies.length, anddifficulty. - See what happens when
iequalsenemies.length. - If
enemiesis null, notice how that changes things.
Fix the bugs by:
- Making
loadEnemiesForLevelreturn a non‑null array, even for unknown levels (for example, an emptyint[]). - Adjusting the
forloop incomputeDifficultyso it:- Does not go out of bounds.
- Handles
enemiesbeing null or empty safely.
Re‑run and confirm:
- No exceptions occur.
- The printed difficulty matches your expectations.
5. Avoid introducing new bugs
After each fix, re‑run the whole program, not just the Level Loader demo:
- Check that:
- The scoreboard output (from Level 3) still looks correct.
- No new exceptions appear.
This is a realistic part of debugging:
- Fix one bug.
- Run the full program.
- Make sure you didn’t break something else.
6. Reflection (Level 4)
Short prompts:
- What kinds of bugs did you see in this Level (compile‑time, runtime, logic)?
- How did stepping into helper methods (
parseLevel,loadEnemiesForLevel,computeDifficulty) help? - What habits can help avoid layered bugs (e.g., validating inputs, returning empty collections instead of null)?
You can compare your fixes to the suggested ones in Solutions & Fixes.