AI Maze

3
core modules
5
AI components
15
face samples needed
System architecture
launch.pyEntry point
face_auth.pyFace login
final_mazeT.pyGame engine
ai_maze_stats_*.jsonPer-user data
AI components in this project
Haar cascade face detection LBP texture recognition ORB feature matching A* pathfinding (enemies) Adaptive difficulty director
AI Maze Escape
Mechanism
mechanism of my face recognition system

The system has three stages: detection, feature extraction, and matching.

1. Detection — OpenCV's Haar cascade classifier (haarcascade_frontalface_default.xml) scans the grayscale webcam frame using a sliding window. It looks for patterns of light/dark rectangles characteristic of frontal faces. If nothing is found, it retries with a looser scale factor.

2. Feature extraction — The cropped face is resized to 160×160 and two descriptors are computed: LBP (Local Binary Patterns), which encodes each pixel as a binary code based on its 8 neighbours, giving a texture fingerprint; and a multi-region intensity histogram that captures the overall brightness distribution across a 3×3 grid of regions.

3. Matching — At login, the live features are compared to every stored sample using cosine similarity. The formula is: 0.45×LBP_sim + 0.45×hist_sim + 0.10×ORB_sim. If the best score exceeds a threshold of 0.32, the user is recognised.

Algorithm
What is LBP and why did I choose it?

Local Binary Patterns work by comparing each pixel to its 8 surrounding neighbours. If a neighbour is brighter or equal, it gets a 1, otherwise 0. The 8 bits form a code (0–255). These codes are then histogrammed across a 4×4 grid of face regions — giving a 512-dimensional feature vector.

LBP is chosen because it is illumination-invariant (it measures relative brightness, not absolute), it works without deep learning (no GPU, no heavy libraries), and it runs fast enough for real-time use — on a basic laptop camera at 30fps.

Algorithm
What is ORB and how is it used here?

ORB (Oriented FAST and Rotated BRIEF) is a keypoint detector and binary descriptor. It finds distinctive corner-like points on the face image and encodes them as 256-bit binary strings. Two face images are compared by matching these keypoints using a brute-force Hamming distance matcher.

In this system ORB contributes only 10% weight in the final similarity score, because it can be noisy on small faces. It's a supplemental check on top of the more reliable LBP and histogram comparisons. The Lowe ratio test (0.75) is applied to filter out weak matches.

Registration
How does the registration process work?

The user types their name and clicks Capture Face. The system collects 15 sample frames at 0.32-second intervals while the camera detects a face. Each frame is processed through the full feature pipeline (LBP + histogram + ORB). All 15 feature sets are stored together in a users.pkl pickle file under face_data/.

At login time, the live features are compared against all stored samples for each user. The top half of similarity scores are averaged, which makes recognition robust to a few bad samples (e.g. a blink or partial occlusion during registration).

Low light
How does the system handle poor lighting?

Two mechanisms handle low light. First, CLAHE (Contrast Limited Adaptive Histogram Equalization) is applied to every face crop — it enhances local contrast without over-brightening. Second, if mean pixel brightness falls below 45, a gamma correction LUT (gamma=0.45) brightens dark pixels non-linearly before detection.

There's also a "Torch" button in the UI that fills the screen with warm white, acting as a physical screen-light for very dark environments.

Algorithm
How is the maze generated?

The maze uses a recursive backtracker (DFS-based) algorithm. The grid starts as all walls. A carve() function starts at (1,1), randomly shuffles the 4 directions, and carves paths by setting both the step cell and intermediate wall cell to 0 (open). This continues recursively until all reachable cells are visited.

This guarantees a perfect maze — exactly one path between any two points, no loops. A 22% loop factor is then applied: a random fraction of wall cells are opened, creating shortcuts and making the maze more interesting to explore.

Algorithm
How does A* pathfinding work in your game?

A* (A-star) is used for enemy navigation. It maintains an open set of cells. Each candidate gets a score f = g + h, where g is the actual distance from the start and h is the Manhattan distance heuristic to the goal. The cell with the lowest f-score is expanded first.

A* always finds the shortest path through the maze grid. Enemies call it each time they need to move. The result is a list of cells — the enemy takes the first step each movement tick.

The "blocker" enemy is special: instead of targeting the player directly, it runs A* toward a point along the player's path to the exit — trying to cut off the escape route.

AI
Explain the enemy FSM (Finite State Machine)

Each enemy is a Finite State Machine with four states: WANDER, PATROL, CHASE, and ATTACK.

Transitions are determined by Manhattan distance to the player, scaled by the current difficulty multiplier. A hunter switches to CHASE if the player is within 9 × difficulty cells, and ATTACK within 2 cells. A blocker patrols near the exit and only chases if the player is within 14 cells.

In WANDER, enemies pathfind to a random distant cell. In PATROL, they follow a multi-point route. In CHASE/ATTACK, A* is called toward the target. A stun timer from player attacks forces the WANDER state temporarily.

Visual feedback: CHASE blends the enemy colour toward red, PATROL toward yellow, WANDER darkens it — so the player can read enemy intent at a glance.

Modes
What are the three game modes and how do they differ?

Lost in a Maze — Navigate from (1,1) to the exit at the bottom-right. The maze uses the recursive backtracker with 22% loop factor. Efficiency and time are tracked per level.

Survival — An open field with sparse obstacles. Enemies spawn in waves; the player must survive as long as possible. The Adaptive Director continuously adjusts enemy count and speed.

Night Out — Same as maze mode but with a circular spotlight effect: a dark overlay with an alpha-punched hole near the player. Only a small radius is visible, dramatically increasing difficulty. The spotlight is created by layering two SRCALPHA surfaces with BLEND_RGBA_MIN to produce a smooth gradient fade.

Scoring
How is the score and efficiency calculated?

Efficiency = optimal_path_length / (optimal_path_length + wrong_moves). The optimal path is computed once via A* at level start. Every move not on the optimal path increments wrong. A perfect run gives efficiency = 1.0.

Score = base points scaled by time and efficiency. Faster completions and fewer wrong turns give higher scores. The per-level result is stored in the user's JSON stats file, including mode, level, time, wrong count, efficiency, and score.

Core system
Explain the adaptive difficulty system

There are two layers of adaptation: a per-session Adaptive Director and a cross-session Player Profile.

The AdaptiveDirector maintains a skill value (0–100) and a difficulty multiplier (0.40–1.60). Deaths reduce skill (penalty = 12 + death_streak × 6). Clearing a wave increases it (bonus = 8 + wave_streak × 4). The difficulty multiplier is lerp-interpolated toward a target with factor 0.35, so it changes smoothly.

The Player Profile uses historical stats across all sessions to classify the player into one of five types: Beginner, Balanced, SpeedRunner, LogicalPlanner, or Explorer — each getting different starting maze sizes, fog settings, and enemy counts.

Profiles
How are player profiles determined?

After at least 2 completed levels, compute_profile() classifies the player based on running averages:

SpeedRunner: efficiency > 80% AND average time < 20s — gets larger mazes, faster enemies, more enemies.

LogicalPlanner: wrong moves < 3 AND efficiency > 70% — gets fog of war enabled for challenging navigation.

Explorer: more than 12 average wrong moves — gets very large mazes with fog, rewarding exploration playstyle.

Balanced / Beginner get progressively growing mazes with moderate enemies. Profiles re-evaluate after every level, so the game adapts as the player improves.

Data
How is per-user data stored and loaded?

Each registered user gets a dedicated JSON file named ai_maze_stats_{username}.json. The filename is derived from the face-auth username — special characters are replaced with underscores and the name lowercased. Guests use ai_maze_stats_guest.json.

The file stores a history array of level results, each with: mode, level, time_s, wrong, efficiency, and score. Stats are loaded at game start and used to compute the player profile. After each level the new result is appended and saved. Each person's adaptive difficulty is therefore persistent across sessions.

Architecture
How does launch.py connect face auth to the game without modifying the game file?

launch.py uses Python's importlib to load final_mazeT.py as a module at runtime rather than running it as a script. This means the game's if __name__ == "__main__" block is never executed — instead, the launcher calls Game().run() directly.

Before executing the module, it patches SAVE_FILE directly on the module object, so the game saves to the logged-in user's file. It also monkey-patches Game.build_menu and Game.draw_menu to show a player name badge in the top-right corner — all without touching the original game file.

Design choice
Why not just use dlib or face_recognition libraries?

The design constraint was to avoid heavy dependencies. dlib requires compiled C++ binaries and a model download (~100MB). The face_recognition library depends on dlib. Both require specific OS-level build tools to install.

This system only needs OpenCV and NumPy — both standard pip installs with no compilation needed. The LBP + histogram approach achieves reliable recognition for a small, known user base (a classroom or team), which is the intended use case. The trade-off is that it would not scale to millions of users or handle large pose/lighting variations as well as deep learning methods.

Sound
How is sound generated without any audio files?

All sound is procedurally generated at runtime using NumPy. Each sound effect is a mathematical formula applied to a time array — for example, the coin sound is a sine wave with rising frequency: sin(2π × (600 + 500t) × t) with an exponential decay envelope.

Background music is also generated per-mode by layering sine waves at different frequencies. Sounds are generated once, cached in a dictionary, and played via pygame.mixer. The entire game is self-contained in Python — zero asset files needed.

Opening line suggestion: "This project combines two AI areas — computer vision for face recognition, and game AI for adaptive difficulty — in a single playable Python application."

If asked "what was hardest": The face recognition threshold tuning (0.32) — too low and strangers log in, too high and the real user gets rejected. Also the spotlight effect in Night Out mode, which required compositing two alpha surfaces with a special blend flag (BLEND_RGBA_MIN).

If asked "what would you improve": Replace LBP matching with a lightweight CNN (e.g. MobileNet) for better accuracy at varied angles. Add online learning so the model updates with new face samples over time. Also add a difficulty graph on the stats screen so players can visualise how their skill score changed over time.

If asked about limitations: Face recognition only works reliably for frontal faces in reasonable lighting. The system stores raw feature vectors in a pickle file — not suitable for production (no encryption). LBP is also sensitive to large changes in appearance between registration and login (e.g. new glasses or hairstyle).

Key terms to use confidently: cosine similarity, Haar cascade, Local Binary Patterns, CLAHE histogram equalization, gamma correction, A* heuristic, Finite State Machine, adaptive difficulty, player profiling, monkey-patching, procedural audio.

For a live demo: Show registration first (so the face data is fresh), then login, then play one level of Night Out — it's the most visually impressive mode and immediately shows the spotlight effect. The colour-changing enemies also demonstrate the FSM visually without needing to explain it.