Skip to content

Bmogul/java_maze

Repository files navigation

Pseudo-3D Maze Explorer

A first-person maze navigation game built in Java (2019-2020) that simulates 3D perspective using 2D polygon rendering techniques. This project demonstrates raycasting-inspired principles similar to early first-person games like Wolfenstein 3D, but implemented using pure Java AWT/Swing without any 3D graphics libraries.

Overview

This maze game presents a dual-view interface:

  • Top-down map view: Shows the entire maze layout with your position
  • First-person 3D view: Renders a pseudo-3D hallway perspective that updates based on your position and facing direction

The player navigates through a maze loaded from a text file, avoiding traps and death tiles, using teleporters, and trying to reach the exit with the minimum number of moves.

Project Structure

Core Classes

MazeProgram.java (Main Controller)

The main entry point and game controller that extends JPanel and implements KeyListener and MouseListener.

Key Responsibilities:

  • Manages the JFrame window and rendering loop
  • Handles keyboard input (arrow keys for movement/rotation)
  • Coordinates game state (finished, death, teleportation)
  • Loads maze from file (maze.txt)
  • Renders both 2D overhead map and 3D first-person view
  • Implements game mechanics (traps, teleportation, death tiles)

Important Methods:

  • setBoard() (lines 136-215): Parses maze file and initializes game state
  • paintComponent() (lines 38-135): Main rendering method
  • keyPressed()/keyReleased() (lines 222-351): Input handling
  • checkTrap(), checkTeleport(), checkDeath() (lines 247-301): Game mechanics

Maze.java (3D Renderer)

Contains the pseudo-3D rendering logic that creates the illusion of a first-person hallway view.

Key Features:

  • Defines 13 polygon shapes representing hallway segments at different depths
  • Implements drawSurrounding() (lines 33-412): The core 3D rendering engine
  • Handles all four cardinal directions (ZERO=East, NINETY=North, ONEEIGHTY=West, TWOSEVENTY=South)
  • Uses perspective scaling and depth-based shading

Polygon Structure:

  • ceiling1, ceiling2, ceiling3: Three depth levels of ceiling
  • floor1, floor2, floor3: Three depth levels of floor
  • left1, left2, left3: Three depth levels of left walls
  • right1, right2, right3: Three depth levels of right walls
  • wall1, wall2, wall3: Three depth levels of front-facing walls

Explorer.java (Player)

Represents the player character navigating the maze.

Key Features:

  • Tracks position (Location loc) and facing direction
  • Manages turn counter (score system)
  • Implements movement and rotation logic
  • Uses constants for facing directions (ZERO=1, NINETY=2, ONEEIGHTY=3, TWOSEVENTY=4)

Important Methods:

  • move() (lines 35-60): Executes actual movement after validation
  • potentialMove() (lines 61-80): Calculates proposed movement for collision detection
  • turnLeft(), turnRight() (lines 81-116): Rotation logic

Wall.java (Tile)

Represents a single tile in the maze grid (despite the name, it can be walls, paths, traps, etc.).

Properties:

  • isWall: Solid wall (impassable)
  • trap: Penalty tile (adds 20 moves)
  • teleport: Portal to another teleport tile
  • death: Instant game over

Location.java (Position)

Simple coordinate wrapper for x,y positions in pixel space.

Note: Uses relative modification methods (setX(int i) adds to x, doesn't set it absolutely)

Game Mechanics

Maze File Format (maze.txt)

The maze is defined in a text file using ASCII characters:

  • # = Wall (impassable)
  • (space) = Path (walkable)
  • * = Trap (adds 20 moves penalty)
  • T = Teleporter (transports to another T tile)
  • X = Death tile (instant game over)
  • First space character = Starting position
  • Last row space = Exit goal

Controls

  • UP Arrow: Move forward (in the direction you're facing)
  • LEFT Arrow: Turn left (rotate 90° counterclockwise)
  • RIGHT Arrow: Turn right (rotate 90° clockwise)

Scoring System

Your score is the number of moves taken to complete the maze:

  • ≤ 110 moves: IMPOSSIBLE
  • 111-130 moves: GREAT JOB
  • 131-209 moves: BETTER LUCK NEXT TIME
  • ≥ 210 moves: Death (ran out of moves)

Visual Color Coding

Top-down map:

  • Gray: Walls
  • Light Gray: Paths
  • Yellow: Traps
  • Blue: Teleporters
  • Red: Death tiles
  • Red outline: Your position

3D view:

  • Uses depth-based shading (.darker() for distance)
  • Red outlines on wall edges for definition

Pseudo-3D Rendering Theory

The Challenge

How do you create a 3D first-person view using only 2D polygons? This project solves that problem using a technique inspired by raycasting but implemented with pre-defined polygon shapes.

The Solution: Perspective Projection

The key insight is that 3D perspective can be simulated by drawing 2D polygons that shrink toward a vanishing point at the screen center.

Depth Layers

The renderer creates three "depth layers" representing:

  1. Layer 1: Immediate surroundings (1 tile away)
  2. Layer 2: Mid-distance (2 tiles away)
  3. Layer 3: Far distance (3 tiles away)

Each layer uses progressively smaller polygons positioned closer to the center of the screen.

Polygon Geometry (Maze.java:35-93)

Floor Example:
floor1: Large trapezoid at bottom (close)
  [250,650,750,150] x [525,525,600,600]

floor2: Medium trapezoid (middle distance)
  [300,600,650,250] x [487.5,487.5,525,525]

floor3: Small trapezoid (far distance)
  [325,575,600,300] x [468.75,468.75,487.5,487.5]

Notice how:

  • Width narrows: 500px → 350px → 250px
  • Height compresses: 75px → 37.5px → 18.75px
  • Converges toward center (450px horizontal, ~350px vertical)

The Rendering Algorithm

For each depth level (1-3 tiles ahead), the drawSurrounding() method:

  1. Checks bounds to prevent array out of bounds
  2. Queries the maze at that position in the facing direction
  3. Conditionally renders based on what's there:
    • If there's a wall ahead → draw front wall polygon
    • If path continues → draw floor and check sides
    • If walls to left/right → draw side wall polygons

Example (Facing East - Case 1, lines 100-172):

// Can the player see 1 tile ahead?
if(!walls[p.getR()][p.getC()+1].isWall()) {
    g.fillPolygon(floor2);  // Draw mid-distance floor

    // Are there walls to the sides at this depth?
    if(walls[p.getR()+1][p.getC()+1].isWall())
        g.fillPolygon(right2);  // Draw right wall
    if(walls[p.getR()-1][p.getC()+1].isWall())
        g.fillPolygon(left2);   // Draw left wall

    // Can player see 2 tiles ahead? (recurse deeper)
    if(!walls[p.getR()][p.getC()+2].isWall()) {
        // ... check for walls and draw floor3/left3/right3
    }
}

Depth Cueing (Atmospheric Perspective)

To enhance depth perception, farther objects are rendered darker:

g.setColor(Color.GRAY);                     // Close (layer 1)
g.setColor(Color.GRAY.darker());            // Mid (layer 2)
g.setColor(Color.GRAY.darker().darker());   // Far (layer 3)

This simulates atmospheric fog and helps the player judge distance.

Why This Approach?

Pros:

  • No complex 3D math or matrices required
  • Runs efficiently on any Java-capable system
  • Educational: clearly shows perspective principles
  • Retro aesthetic similar to 1990s dungeon crawlers

Cons:

  • Limited to rectilinear (grid-based) mazes
  • Only renders 3 tiles deep
  • Fixed viewing angles (90° rotations only)
  • Manual polygon definition for each direction

Comparison to True Raycasting

Games like Wolfenstein 3D use raycasting, where rays are cast from the player's position to detect walls at arbitrary angles and distances. This project uses a simplified approach:

  • Raycasting: Cast rays → measure distance → calculate column height
  • This project: Pre-defined polygons → query maze at fixed depths → conditionally render

This is more like portal rendering or BSP-based techniques, where you pre-compute viewable regions.

Technical Implementation Details

Coordinate Systems

The project uses two coordinate systems:

  1. Pixel Coordinates: Screen positions (0-1000 width, 0-800 height)
  2. Grid Coordinates: Maze array indices (row, column)

Conversion: Each grid cell is 5×5 pixels

  • gridRow = pixelY / 5
  • gridCol = pixelX / 5

Movement Validation (MazeProgram.java:226-245)

The game uses a two-step movement system:

  1. Potential Move: Calculate where player wants to go (Explorer.potentialMove())
  2. Validation: Check if that position is valid (Maze.canMove())
  3. Execute: If valid, commit the move (Explorer.move())

This prevents walking through walls and allows for collision detection.

Direction System

The Explorer class uses numeric constants for cardinal directions:

     NINETY (2)
         ↑
         |
ONEEIGHTY (3) ← + → ZERO (1)
         |
         ↓
    TWOSEVENTY (4)

This maps to array coordinates:

  • ZERO (1) = +X (right)
  • NINETY (2) = -Y (up)
  • ONEEIGHTY (3) = -X (left)
  • TWOSEVENTY (4) = +Y (down)

Teleporter Logic (MazeProgram.java:264-290)

Teleporters work by:

  1. Detecting when player enters a teleport tile
  2. Storing the current teleport location
  3. Scanning maze for the OTHER teleport tile
  4. Moving player to that location
  5. Using a teleported flag to prevent instant re-teleportation

Arrow Direction Indicator

The program displays a rotated arrow image showing which direction you're facing (lines 314-345). The image file is swapped based on the facing direction after rotation.

Running the Program

Prerequisites

  • Java Development Kit (JDK) 8 or higher

Compilation

javac MazeProgram.java

This will automatically compile all dependent classes.

Execution

java MazeProgram

The program will:

  1. Load maze.txt from the current directory
  2. Open a 1000×800 window
  3. Start with the 3D view enabled
  4. Display arrow images from arrow.png, arrowUp.png, arrowDown.png, arrowBack.png

Educational Value

This project demonstrates several important computer science concepts:

1. Graphics Programming

  • Polygon rendering with java.awt.Polygon
  • Double buffering (implicit in Swing)
  • Event-driven GUI programming

2. Game Development

  • Game loop architecture
  • Input handling
  • State management (game states, flags)
  • Collision detection

3. Algorithms & Data Structures

  • 2D array representation of grid-based worlds
  • Coordinate transformations
  • Conditional rendering based on view direction

4. Mathematics

  • Perspective projection (vanishing point geometry)
  • Grid-to-pixel coordinate mapping
  • Rotation matrices (simplified to 90° increments)

5. Computer Graphics Theory

  • Depth cueing (atmospheric perspective)
  • Painter's algorithm (drawing back-to-front)
  • Portal/BSP rendering concepts

Historical Context

This project is a modern educational implementation of techniques used in early 3D games:

  • Wolfenstein 3D (1992): Raycasting for pseudo-3D
  • Doom (1993): BSP trees and portal rendering
  • Duke Nukem 3D (1996): Build engine portal-based rendering

While modern games use true 3D graphics with GPUs, understanding these foundational techniques helps programmers appreciate:

  • How to create compelling visuals with limited resources
  • The difference between 2.5D and true 3D
  • The evolution of graphics programming

Possible Extensions

Ideas for enhancing this project:

  1. Textured walls: Use images instead of solid colors
  2. Mini-map toggle: Hide/show the overhead view
  3. Multiple maze levels: Load different maze files
  4. Enemy AI: Add moving obstacles
  5. Collectibles: Items to gather before exit
  6. Diagonal movement: Allow 45° angles
  7. Variable tile sizes: Support different perspectives
  8. Lighting effects: Flashlight cone, darkness
  9. Sound effects: Footsteps, door sounds, ambient audio
  10. Level editor: GUI tool to create maze.txt files

Reflections (2019-2020 Senior Year Project)

This project represents a significant achievement in understanding how classic 3D games worked before modern GPU acceleration. The hand-coded polygon definitions in Maze.java show careful geometric planning to create convincing perspective.

The code demonstrates:

  • Strong object-oriented design (separation of concerns)
  • Practical problem-solving (collision detection, state management)
  • Mathematical thinking (coordinate systems, perspective)
  • Persistence (manually defining 52 polygon coordinates!)

While the code could be refactored for cleaner architecture (the switch statements in drawSurrounding() are quite repetitive), it works effectively and shows clear logic flow.

License

Educational project - feel free to learn from and modify.


Created as a high school senior project (2019-2020) exploring data structures and algorithms.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages