Labyrinth.
2D maze game in Java with MVC architecture — OOP course project.

A 2D Labyrinth game in Java. The player has to find the exit of a maze while avoiding stalkers that chase them; an invisibility potion freezes the stalkers for three rounds. Built for the 2nd-semester Object-Oriented Programming course where the actual grading criterion was the architecture — MVC discipline, observer pattern, clean inheritance — not the gameplay.
What it solves.
Game projects are a classic OOP teaching tool because they force you to model real interactions between entities (player ↔ stalker ↔ environment) with shared behaviors. The trap is to start ad-hoc, end up with one God-class, and fail the design review. The challenge was twofold: (a) get the architecture right under the course's grading criteria, and (b) come back after submission to fix the things I'd missed.
How it's solved.
- 01
Model — Level holds dynamic state (player, stalkers, powerups, effects). Map loads the static grid from an ASCII text file. Tile is abstract; Wall and Corridor inherit. Entity is abstract for anything that moves; Player and Stalker inherit. Powerups/effects abstracted through Powerup and Effect interfaces.
- 02
View — View is an interface; concrete views (ConsoleView, GraphicView) register on the level and get notified when state changes (observer pattern). GraphicView renders the board with Swing sprites + toolbar + status bar; ConsoleView exists for debugging.
- 03
Controller — Listens for key events and toolbar actions, calls matching methods on Level. Knows nothing about drawing — that flows model → views via observer updates. Entry point wires everything on Swing's Event Dispatch Thread.
- 04
Levels as plain text files in resources/map/ — each character is one tile (+ wall, . corridor, s start, f finish, e stalker, i potion). Loading validates structure and throws IOException with row/column on the first invalid character.
- 05
Stalker AI: simple greedy rule — try the axis with larger distance first, fall back to the other axis if blocked. Not optimal (they get stuck in dead ends sometimes), but for hand-designed level sizes it works.
What makes it interesting.
MVC discipline as the actual deliverable
The interesting deliverable wasn't the game — it was the MVC separation. Controller doesn't draw; View doesn't mutate state; Model fires observer updates. Each piece is testable in isolation.
Came back after submission to refactor
Post-submission cleanup pass: fixed an equals()-without-hashCode() bug that broke HashSet usage, switched resource loading from filesystem paths to classpath (so the JAR actually works when launched from anywhere), and tightened the map parser to fail loudly on unknown characters — which surfaced two typos in shipped level files that the old silent fallback had hidden.
Validating input loudly is worth the noise
Old parser silently treated unknown characters as corridors. New version throws IOException with exact row and column. Caught a stray comma in level1.txt and stray space in level2.txt that had been invisibly buggy since submission.

Tech.
- Language
- Java 11+
- GUI / rendering
- Java SwingAWT
- Pattern
- MVCObserver (view updates)
- Build
- Maven
- Resources
- ASCII map files32×32 PNG sprites
- Tooling
- IntelliJ IDEAGit