Need Help With Pac-Man Style Maze Generator

What I Want to Achieve

I’m creating a procedural Pac-Man style maze generator that creates a 25x25 grid maze with a central ghost house. The maze should have proper Pac-Man aesthetics (blue walls, black floor) and a ghost house with the characteristic pink gate.

The Issues

While my current script works, there are several problems that need to be addressed:

Ghost House Problems

  1. The ghost house floor isn’t level with the maze floor - there’s a noticeable height difference
  2. Sometimes maze walls are generated in front of the ghost house gate, blocking it
  3. The ghost house position can be slightly offset from the grid alignment

Performance Issues

  1. The maze generation can be slow on larger grids
  2. Wall and corner piece deletion might be inefficient
  3. There’s no pellet generation system yet
  4. Need better error handling

Here’s what the maze currently looks like

What I’ve Tried

I’ve implemented the following:

  • Used a modified depth-first search algorithm with noise-based randomisation
  • Implemented corner piece management
  • Created a central ghost house system
  • Added proper wall removal logic
  • Attempted to fix ghost house elevation by adjusting the base position, but still having issues
  • Tried to prevent walls from spawning in front of the gate by clearing the area, but walls still sometimes generate there

Here’s my current code:

-- Original procedural generated maze by GibbleDev
--[[
    Pac-Man Style Maze Generator
    Purpose: Generates a procedural Pac-Man-style maze with a central ghost house
    
    Key Features:
    - 25x25 grid maze generation
    - Central ghost house with pink gate
    - Perlin noise-based path randomization
    - Depth-first search maze algorithm
    
    Dependencies:
    - Requires a Segment model in the workspace
    - Segment model must contain: WallL, WallR, WallT, WallB, Floor parts
    
    Known Issues:
    - Performance could be improved with batched wall deletions
    - No pellet/power pellet generation
    - No spawn points for Pac-Man/ghosts
--]]

local Segment = script.Segment
local Workspace = game:GetService("Workspace")

-- Configuration
-- All major maze parameters and colors are defined here for easy modification
local Config = {
	SEED = 1,                                    -- Random seed for maze generation
	SIZE_X = 25,                                 -- Maze width in cells
	SIZE_Y = 25,                                 -- Maze height in cells
	CONTROL = math.pi,                           -- Noise control parameter
	COLORS = {
		WALL = Color3.fromRGB(33, 33, 255),      -- Pac-Man blue walls
		FLOOR = Color3.fromRGB(0, 0, 0),         -- Black floor
		CORNER = Color3.fromRGB(33, 33, 255),    -- Same as walls
		GHOST_GATE = Color3.fromRGB(255, 192, 203) -- Pink gate
	},
	GHOST_HOUSE = {
		WIDTH = 5,                               -- Width in cells
		HEIGHT = 3,                              -- Height in cells
		GATE_WIDTH = 2                           -- Width of the pink gate in cells
	}
}

-- Cache frequently used values for performance
local scale = Segment.WallL.Size.Y
local halfScaleX = Config.SIZE_X * scale / 2
local halfScaleY = Config.SIZE_Y * scale / 2
local wallThickness = Segment.WallL.Size.X
local wallHeight = scale

--[[
    Creates a corner piece for maze cells
    Returns: Instance (Part)
    - Configured with proper size, material, and color
    - Automatically anchored
--]]
local function createCornerPiece()
	local corner = Instance.new("Part")
	corner.Size = Vector3.new(wallThickness, scale, wallThickness)
	corner.Anchored = true
	corner.Material = Enum.Material.SmoothPlastic
	corner.Color = Config.COLORS.CORNER
	corner.TopSurface = Enum.SurfaceType.Smooth
	corner.BottomSurface = Enum.SurfaceType.Smooth
	return corner
end

--[[
    Applies Pac-Man visual style to a maze segment
    Parameters:
    - segment: Model (The maze segment to style)
    Applies proper colors and materials to walls and floor
--]]
local function applyPacManStyle(segment)
	-- Color the walls
	segment.WallL.Color = Config.COLORS.WALL
	segment.WallR.Color = Config.COLORS.WALL
	segment.WallT.Color = Config.COLORS.WALL
	segment.WallB.Color = Config.COLORS.WALL

	-- Set wall material
	segment.WallL.Material = Enum.Material.SmoothPlastic
	segment.WallR.Material = Enum.Material.SmoothPlastic
	segment.WallT.Material = Enum.Material.SmoothPlastic
	segment.WallB.Material = Enum.Material.SmoothPlastic

	-- Color the floor
	segment.Floor.Color = Config.COLORS.FLOOR
	segment.Floor.Material = Enum.Material.SmoothPlastic
end

--[[
    Removes grid cells where the ghost house will be placed
    Parameters:
    - grid: table (2D array of maze segments)
    Clears the central area for ghost house placement
--]]
local function clearGhostHouseInterior(grid)
	local centerX = math.floor(Config.SIZE_X / 2)
	local centerY = math.floor(Config.SIZE_Y / 2)

	local startX = centerX - math.floor(Config.GHOST_HOUSE.WIDTH / 2)
	local startY = centerY - math.floor(Config.GHOST_HOUSE.HEIGHT / 2)

	for y = startY, startY + Config.GHOST_HOUSE.HEIGHT - 1 do
		for x = startX, startX + Config.GHOST_HOUSE.WIDTH - 1 do
			if grid[y] and grid[y][x] then
				grid[y][x]:Destroy()
				grid[y][x] = nil
			end
		end
	end
end

--[[
    Creates the ghost house in the center of the maze
    Parameters:
    - mazeModel: Model (Parent model for the ghost house)
    Returns: Model (The created ghost house)
    Creates base, walls, and pink gate for ghost house
--]]
local function createGhostHouse(mazeModel)
	local ghostHouse = Instance.new("Model")
	ghostHouse.Name = "GhostHouse"
	ghostHouse.Parent = mazeModel

	local centerX = math.floor(Config.SIZE_X / 2)
	local centerY = math.floor(Config.SIZE_Y / 2)

	-- Create the base
	local base = Instance.new("Part")
	base.Name = "Base"
	base.Size = Vector3.new(
		Config.GHOST_HOUSE.WIDTH * scale,
		wallThickness,
		Config.GHOST_HOUSE.HEIGHT * scale
	)
	base.CFrame = CFrame.new(
		(centerX * scale - halfScaleX) - scale/2,
		0,  -- Ground level
		(centerY * scale - halfScaleY) - scale/2
	)
	base.Color = Config.COLORS.FLOOR
	base.Material = Enum.Material.SmoothPlastic
	base.Anchored = true
	base.Parent = ghostHouse

	-- Wall configuration for ghost house
	local walls = {
		-- Left wall
		{
			size = Vector3.new(wallThickness, wallHeight, Config.GHOST_HOUSE.HEIGHT * scale),
			position = CFrame.new(-Config.GHOST_HOUSE.WIDTH/2 * scale + wallThickness/2, wallHeight/2, 0)
		},
		-- Right wall
		{
			size = Vector3.new(wallThickness, wallHeight, Config.GHOST_HOUSE.HEIGHT * scale),
			position = CFrame.new(Config.GHOST_HOUSE.WIDTH/2 * scale - wallThickness/2, wallHeight/2, 0)
		},
		-- Back wall
		{
			size = Vector3.new(Config.GHOST_HOUSE.WIDTH * scale, wallHeight, wallThickness),
			position = CFrame.new(0, wallHeight/2, Config.GHOST_HOUSE.HEIGHT/2 * scale - wallThickness/2)
		}
	}

	-- Create blue walls
	for _, wallConfig in ipairs(walls) do
		local wall = Instance.new("Part")
		wall.Size = wallConfig.size
		wall.CFrame = base.CFrame * wallConfig.position
		wall.Color = Config.COLORS.WALL
		wall.Material = Enum.Material.SmoothPlastic
		wall.Anchored = true
		wall.Parent = ghostHouse
	end

	-- Create pink gate at the top
	local gateWidth = Config.GHOST_HOUSE.GATE_WIDTH * scale
	local gate = Instance.new("Part")
	gate.Size = Vector3.new(gateWidth, wallHeight/2, wallThickness)  -- Half height for gate
	gate.CFrame = base.CFrame * CFrame.new(0, wallHeight * 0.75, -Config.GHOST_HOUSE.HEIGHT/2 * scale + wallThickness/2)
	gate.Color = Config.COLORS.GHOST_GATE
	gate.Material = Enum.Material.SmoothPlastic
	gate.Anchored = true
	gate.Parent = ghostHouse

	-- Add side pieces for the gate (in blue)
	local leftGateSide = Instance.new("Part")
	leftGateSide.Size = Vector3.new((Config.GHOST_HOUSE.WIDTH * scale - gateWidth)/2, wallHeight, wallThickness)
	leftGateSide.CFrame = base.CFrame * CFrame.new(-gateWidth/2 - leftGateSide.Size.X/2, wallHeight/2, -Config.GHOST_HOUSE.HEIGHT/2 * scale + wallThickness/2)
	leftGateSide.Color = Config.COLORS.WALL
	leftGateSide.Material = Enum.Material.SmoothPlastic
	leftGateSide.Anchored = true
	leftGateSide.Parent = ghostHouse

	local rightGateSide = Instance.new("Part")
	rightGateSide.Size = Vector3.new((Config.GHOST_HOUSE.WIDTH * scale - gateWidth)/2, wallHeight, wallThickness)
	rightGateSide.CFrame = base.CFrame * CFrame.new(gateWidth/2 + rightGateSide.Size.X/2, wallHeight/2, -Config.GHOST_HOUSE.HEIGHT/2 * scale + wallThickness/2)
	rightGateSide.Color = Config.COLORS.WALL
	rightGateSide.Material = Enum.Material.SmoothPlastic
	rightGateSide.Anchored = true
	rightGateSide.Parent = ghostHouse

	return ghostHouse
end

-- Initialize maze container
local mazeModel = Instance.new("Folder")
mazeModel.Name = "Grid"
mazeModel.Parent = Workspace

-- Remove baseplate if it exists
if Workspace:FindFirstChild("Baseplate") then
	Workspace.Baseplate:Destroy()
end

--[[
    Grid Initialization
    Creates the initial maze grid with all walls intact
    Each cell contains:
    - Four walls (WallL, WallR, WallT, WallB)
    - Floor
    - Four corner pieces
--]]
local grid = {}
for y = 1, Config.SIZE_Y do
	grid[y] = {}
	for x = 1, Config.SIZE_X do
		local segment = Segment:Clone()
		segment:SetPrimaryPartCFrame(
			CFrame.new(
				x * scale - halfScaleX - scale/2,
				0,  -- Ground level
				y * scale - halfScaleY - scale/2
			)
		)

		-- Apply Pac-Man styling
		applyPacManStyle(segment)

		-- Add corner pieces
		local cornerTL = createCornerPiece()
		local cornerTR = createCornerPiece()
		local cornerBL = createCornerPiece()
		local cornerBR = createCornerPiece()

		-- Position corners
		local basePos = segment:GetPrimaryPartCFrame()
		local halfScale = scale/2

		cornerTL.CFrame = basePos * CFrame.new(-halfScale, 0, -halfScale)
		cornerTR.CFrame = basePos * CFrame.new(halfScale, 0, -halfScale)
		cornerBL.CFrame = basePos * CFrame.new(-halfScale, 0, halfScale)
		cornerBR.CFrame = basePos * CFrame.new(halfScale, 0, halfScale)

		cornerTL.Name = "CornerTL"
		cornerTR.Name = "CornerTR"
		cornerBL.Name = "CornerBL"
		cornerBR.Name = "CornerBR"

		cornerTL.Parent = segment
		cornerTR.Parent = segment
		cornerBL.Parent = segment
		cornerBR.Parent = segment

		segment.Name = y .. "," .. x
		segment.Parent = mazeModel
		grid[y][x] = segment
	end
end

--[[
    Direction Configuration
    Defines movement and wall relationships for each direction
    Each direction contains:
    - dx, dy: Movement deltas
    - wall1, wall2: Walls to remove when moving
    - move: Movement code for backtracking
    - corners: Corner pieces to remove
--]]
local DIRECTIONS = {
	LEFT = {dx = -1, dy = 0, wall1 = "WallL", wall2 = "WallR", move = "B", corners = {"CornerTL", "CornerBL"}},
	RIGHT = {dx = 1, dy = 0, wall1 = "WallR", wall2 = "WallL", move = "T", corners = {"CornerTR", "CornerBR"}},
	UP = {dx = 0, dy = 1, wall1 = "WallB", wall2 = "WallT", move = "R", corners = {"CornerBL", "CornerBR"}},
	DOWN = {dx = 0, dy = -1, wall1 = "WallT", wall2 = "WallB", move = "L", corners = {"CornerTL", "CornerTR"}}
}

-- Maze generation state tracking
local state = {
	currentX = 1,
	currentY = 1,
	moves = {},
	completed = 0
}

--[[
    Validates if a cell position is within the maze bounds
    Parameters:
    - x, y: number (Cell coordinates)
    Returns: boolean
--]]
local function isValidCell(x, y)
	return x >= 1 and x <= Config.SIZE_X and y >= 1 and y <= Config.SIZE_Y and grid[y] and grid[y][x] ~= nil
end

--[[
    Gets all unvisited neighboring cells
    Returns: table (Array of valid neighbors with their directions)
--]]
local function getUnvisitedNeighbors()
	local neighbors = {}

	for _, dir in pairs(DIRECTIONS) do
		local newX = state.currentX + dir.dx
		local newY = state.currentY + dir.dy

		if isValidCell(newX, newY) and not grid[newY][newX].GoneInto.Value then
			table.insert(neighbors, {
				cell = grid[newY][newX],
				direction = dir
			})
		end
	end

	return neighbors
end

--[[
    Removes walls between two cells when creating a path
    Parameters:
    - currentCell: Model (Current maze segment)
    - chosenCell: Model (Target maze segment)
    - direction: table (Direction configuration)
--]]
local function deleteWall(currentCell, chosenCell, direction)
	-- Delete walls
	currentCell[direction.wall2]:Destroy()
	chosenCell[direction.wall1]:Destroy()

	-- Delete corresponding corner pieces
	for _, cornerName in ipairs(direction.corners) do
		if currentCell:FindFirstChild(cornerName) then
			currentCell[cornerName]:Destroy()
		end
		if chosenCell:FindFirstChild(cornerName) then
			chosenCell[cornerName]:Destroy()
		end
	end

	state.currentX = state.currentX + direction.dx
	state.currentY = state.currentY + direction.dy

	table.insert(state.moves, direction.move)
end

--[[
    Implements backtracking when reaching dead ends
    Returns: boolean (true if backtracking successful)
--]]
local function backtrack()
	while #state.moves > 0 do
		local lastMove = table.remove(state.moves)

		for _, dir in pairs(DIRECTIONS) do
			if dir.move == lastMove then
				state.currentX = state.currentX - dir.dx
				state.currentY = state.currentY - dir.dy
				break
			end
		end

		if #getUnvisitedNeighbors() > 0 then
			return true
		end
	end
	return false
end

--[[
    Main Generation Loop
    Implements a modified depth-first search algorithm with noise-based randomization
    Key steps:
    1. Check and mark current cell
    2. Find unvisited neighbors
    3. Choose direction using perlin noise
    4. Create path by removing walls
    5. Backtrack when needed
    6. Create ghost house when complete
--]]
while true do
	local currentCell = grid[state.currentY] and grid[state.currentY][state.currentX]
	if not currentCell then
		if not backtrack() then
			break
		end
		continue
	end

	-- Mark cell as visited
	if not currentCell.GoneInto.Value then
		state.completed = state.completed + 1
		currentCell.GoneInto.Value = true
	end

	-- Get available neighbors
	local neighbors = getUnvisitedNeighbors()

	if #neighbors > 0 then
		-- Use perlin noise for direction selection
		local noise = math.noise(state.currentX, Config.SEED/Config.CONTROL, state.currentY)
		if noise == 0 then
			noise = math.noise(state.currentX/Config.CONTROL, Config.SEED/Config.CONTROL, state.currentY/Config.CONTROL)
		end

		-- Calculate direction based on noise
		local percentage = math.floor((noise - math.floor(noise)) * 1000)
		local index = math.floor(percentage / (1000/#neighbors)) + 1
		local chosen = neighbors[math.min(index, #neighbors)]

		-- Create path in chosen direction
		deleteWall(currentCell, chosen.cell, chosen.direction)
	else
		-- Check if maze is complete
		local validCells = 0
		for y = 1, Config.SIZE_Y do
			for x = 1, Config.SIZE_X do
				if grid[y] and grid[y][x] then
					validCells = validCells + 1
				end
			end
		end

		-- Finish generation if all cells visited
		if state.completed >= validCells then
			clearGhostHouseInterior(grid)
			local ghostHouse = createGhostHouse(mazeModel)
			break
		else
			-- Backtrack if dead end reached
			if not backtrack() then
				break
			end
		end
	end

	task.wait() -- Yield to prevent script timeout
end

Specific Questions

  1. How can I ensure the ghost house floor is level with the maze floor?
  2. What’s the best way to prevent walls from generating in front of the ghost house gate?
  3. How can I improve the ghost house positioning to align perfectly with the grid?
  4. How can I optimise the wall deletion process? Currently, I’m deleting walls one at a time.
  5. What’s the best way to implement pellet generation after the maze is created?
  6. Is there a more efficient way to handle corner pieces?
  7. How can I implement proper error handling for missing dependencies?

Additional Information

  • The script uses a Segment model with predefined wall and floor parts
  • Currently using task.wait() during generation to prevent script timeout
  • Using perlin noise for randomisation
  • Ghost house is created after maze generation but before path creation

Any help or suggestions would be greatly appreciated! I’m particularly interested in fixing the ghost house issues and improving overall maze generation performance.

Note: All code is provided and I’m looking for optimisation help, not asking anyone to write the system for me.

4 Likes

Why don’t you generate the ghost house as part of the maze so that the house is part of the maze and they generate at the same time

I tried it before but it kept coming up with this

Let me see the script that generates the map.

If there’s another script that handles with ghost room, please send that too.

I think this would’ve been it

--[[
    Pac-Man Style Maze Generator
    Purpose: Generates a procedural Pac-Man-style maze with a central ghost house

    Key Features:
    - 25x25 grid maze generation
    - Central ghost house with pink gate
    - Perlin noise-based path randomization
    - Depth-first search maze algorithm

    Dependencies:
    - Requires a Segment model in the workspace
    - Segment model must contain: WallL, WallR, WallT, WallB, Floor parts

    Known Issues:
    - Performance could be improved with batched wall deletions
    - No pellet/power pellet generation
    - No spawn points for Pac-Man/ghosts
--]]

local Segment = script.Segment
local Workspace = game:GetService("Workspace")

-- Configuration
-- All major maze parameters and colors are defined here for easy modification
local Config = {
    SEED = 1,                                    -- Random seed for maze generation
    SIZE_X = 25,                                 -- Maze width in cells
    SIZE_Y = 25,                                 -- Maze height in cells
    CONTROL = math.pi,                           -- Noise control parameter
    COLORS = {
        WALL = Color3.fromRGB(33, 33, 255),      -- Pac-Man blue walls
        FLOOR = Color3.fromRGB(0, 0, 0),         -- Black floor
        CORNER = Color3.fromRGB(33, 33, 255),    -- Same as walls
        GHOST_GATE = Color3.fromRGB(255, 192, 203) -- Pink gate
    },
    GHOST_HOUSE = {
        WIDTH = 5,                               -- Width in cells
        HEIGHT = 3,                              -- Height in cells
        GATE_WIDTH = 2                           -- Width of the pink gate in cells
    }
}

-- Cache frequently used values for performance
local scale = Segment.WallL.Size.Y
local halfScaleX = Config.SIZE_X * scale / 2
local halfScaleY = Config.SIZE_Y * scale / 2
local wallThickness = Segment.WallL.Size.X
local wallHeight = scale

--[[
    Creates a corner piece for maze cells
    Returns: Instance (Part)
    - Configured with proper size, material, and color
    - Automatically anchored
--]]
local function createCornerPiece()
    local corner = Instance.new("Part")
    corner.Size = Vector3.new(wallThickness, scale, wallThickness)
    corner.Anchored = true
    corner.Material = Enum.Material.SmoothPlastic
    corner.Color = Config.COLORS.CORNER
    corner.TopSurface = Enum.SurfaceType.Smooth
    corner.BottomSurface = Enum.SurfaceType.Smooth
    return corner
end

--[[
    Applies Pac-Man visual style to a maze segment
    Parameters:
    - segment: Model (The maze segment to style)
    Applies proper colors and materials to walls and floor
--]]
local function applyPacManStyle(segment)
    -- Color the walls
    segment.WallL.Color = Config.COLORS.WALL
    segment.WallR.Color = Config.COLORS.WALL
    segment.WallT.Color = Config.COLORS.WALL
    segment.WallB.Color = Config.COLORS.WALL

    -- Set wall material
    segment.WallL.Material = Enum.Material.SmoothPlastic
    segment.WallR.Material = Enum.Material.SmoothPlastic
    segment.WallT.Material = Enum.Material.SmoothPlastic
    segment.WallB.Material = Enum.Material.SmoothPlastic

    -- Color the floor
    segment.Floor.Color = Config.COLORS.FLOOR
    segment.Floor.Material = Enum.Material.SmoothPlastic
end

--[[
    Removes grid cells where the ghost house will be placed
    Parameters:
    - grid: table (2D array of maze segments)
    Clears the central area for ghost house placement
--]]
local function clearGhostHouseInterior(grid)
    local centerX = math.floor(Config.SIZE_X / 2)
    local centerY = math.floor(Config.SIZE_Y / 2)

    local startX = centerX - math.floor(Config.GHOST_HOUSE.WIDTH / 2)
    local startY = centerY - math.floor(Config.GHOST_HOUSE.HEIGHT / 2)

    for y = startY, startY + Config.GHOST_HOUSE.HEIGHT - 1 do
        for x = startX, startX + Config.GHOST_HOUSE.WIDTH - 1 do
            if grid[y] and grid[y][x] then
                grid[y][x]:Destroy()
                grid[y][x] = nil
            end
        end
    end
end

--[[
    Creates the ghost house in the center of the maze
    Parameters:
    - mazeModel: Model (Parent model for the ghost house)
    Returns: Model (The created ghost house)
    Creates base, walls, and pink gate for ghost house
--]]
local function createGhostHouse(mazeModel)
    local ghostHouse = Instance.new("Model")
    ghostHouse.Name = "GhostHouse"
    ghostHouse.Parent = mazeModel

    -- Ghost house creation logic here (detailed above)
    return ghostHouse
end

-- Initialize maze container
local mazeModel = Instance.new("Folder")
mazeModel.Name = "Grid"
mazeModel.Parent = Workspace

-- Remove baseplate if it exists
if Workspace:FindFirstChild("Baseplate") then
    Workspace.Baseplate:Destroy()
end

--[[
    Grid Initialization
    Creates the initial maze grid with all walls intact
    Each cell contains:
    - Four walls (WallL, WallR, WallT, WallB)
    - Floor
    - Four corner pieces
--]]
local grid = {}
for y = 1, Config.SIZE_Y do
    grid[y] = {}
    for x = 1, Config.SIZE_X do
        local segment = Segment:Clone()
        segment:SetPrimaryPartCFrame(
            CFrame.new(
                x * scale - halfScaleX - scale/2,
                0,  -- Ground level
                y * scale - halfScaleY - scale/2
            )
        )

        -- Apply Pac-Man styling
        applyPacManStyle(segment)

        -- Add corner pieces
        local cornerTL = createCornerPiece()
        local cornerTR = createCornerPiece()
        local cornerBL = createCornerPiece()
        local cornerBR = createCornerPiece()

        -- Position corners
        local basePos = segment:GetPrimaryPartCFrame()
        local halfScale = scale/2

        cornerTL.CFrame = basePos * CFrame.new(-halfScale, 0, -halfScale)
        cornerTR.CFrame = basePos * CFrame.new(halfScale, 0, -halfScale)
        cornerBL.CFrame = basePos * CFrame.new(-halfScale, 0, halfScale)
        cornerBR.CFrame = basePos * CFrame.new(halfScale, 0, halfScale)

        cornerTL.Name = "CornerTL"
        cornerTR.Name = "CornerTR"
        cornerBL.Name = "CornerBL"
        cornerBR.Name = "CornerBR"

        cornerTL.Parent = segment
        cornerTR.Parent = segment
        cornerBL.Parent = segment
        cornerBR.Parent = segment

        segment.Name = y .. "," .. x
        segment.Parent = mazeModel
        grid[y][x] = segment
    end
end

--[[
    Direction Configuration
    Defines movement and wall relationships for each direction
    Each direction contains:
    - dx, dy: Movement deltas
    - wall1, wall2: Walls to remove when moving
    - move: Movement code for backtracking
    - corners: Corner pieces to remove
--]]
local DIRECTIONS = {
    LEFT = {dx = -1, dy = 0, wall1 = "WallL", wall2 = "WallR", move = "B", corners = {"CornerTL", "CornerBL"}},
    RIGHT = {dx = 1, dy = 0, wall1 = "WallR", wall2 = "WallL", move = "T", corners = {"CornerTR", "CornerBR"}},
    UP = {dx = 0, dy = 1, wall1 = "WallB", wall2 = "WallT", move = "R", corners = {"CornerBL", "CornerBR"}},
    DOWN = {dx = 0, dy = -1, wall1 = "WallT", wall2 = "WallB", move = "L", corners = {"CornerTL", "CornerTR"}}
}

--[[
    State Tracking
    Tracks the current maze generation state, including:
    - currentX, currentY: Current cell coordinates
    - moves: Stack of movements for backtracking
    - completed: Number of visited cells
--]]
local state = {
    currentX = 1,
    currentY = 1,
    moves = {},
    completed = 0
}

--[[
    Validates if a cell position is within the maze bounds
    Parameters:
    - x, y: number (Cell coordinates)
    Returns: boolean
--]]
local function isValidCell(x, y)
    return x >= 1 and x <= Config.SIZE_X and y >= 1 and y <= Config.SIZE_Y and grid[y] and grid[y][x] ~= nil
end

--[[
    Gets all unvisited neighboring cells
    Returns: table (Array of valid neighbors with their directions)
--]]
local function getUnvisitedNeighbors()
    local neighbors = {}

    for _, dir in pairs(DIRECTIONS) do
        local newX = state.currentX + dir.dx
        local newY = state.currentY + dir.dy

        if isValidCell(newX, newY) and not grid[newY][newX].GoneInto.Value then
            table.insert(neighbors, {
                cell = grid[newY][newX],
                direction = dir
            })
        end
    end

    return neighbors
end

--[[
    Removes walls between two cells when creating a path
    Parameters:
    - currentCell: Model (Current maze segment)
    - chosenCell: Model (Target maze segment)
    - direction: table (Direction configuration)
--]]
local function deleteWall(currentCell, chosenCell, direction)
    -- Delete walls
    currentCell[direction.wall2]:Destroy()
    chosenCell[direction.wall1]:Destroy()

    -- Delete corresponding corner pieces
    for _, cornerName in ipairs(direction.corners) do
        if currentCell:FindFirstChild(cornerName) then
            currentCell[cornerName]:Destroy()
        end
        if chosenCell:FindFirstChild(cornerName) then
            chosenCell[cornerName]:Destroy()
        end
    end

    state.currentX = state.currentX + direction.dx
    state.currentY = state.currentY + direction.dy

    table.insert(state.moves, direction.move)
end

--[[
    Implements backtracking when reaching dead ends
    Returns: boolean (true if backtracking successful)
--]]
local function backtrack()
    while #state.moves > 0 do
        local lastMove = table.remove(state.moves)

        for _, dir in pairs(DIRECTIONS) do
            if dir.move == lastMove then
                state.currentX = state.currentX - dir.dx
                state.currentY = state.currentY - dir.dy
                break
            end
        end

        if #getUnvisitedNeighbors() > 0 then
            return true
        end
    end
    return false
end

--[[
    Main Generation Loop
    Implements a modified depth-first search algorithm with noise-based randomization
    Key steps:
    1. Check and mark current cell
    2. Find unvisited neighbors
    3. Choose direction using perlin noise
    4. Create path by removing walls
    5. Backtrack when needed
    6. Create ghost house when complete
--]]
while true do
    local currentCell = grid[state.currentY] and grid[state.currentY][state.currentX]
    if not currentCell then
        if not backtrack() then
            break
        end
        continue
    end

    -- Mark cell as visited
    if not currentCell.GoneInto.Value then
        state.completed = state.completed + 1
        currentCell.GoneInto.Value = true
    end

    -- Get available neighbors
    local neighbors = getUnvisitedNeighbors()

    if #neighbors > 0 then
        -- Use perlin noise for direction selection
        local noise = math.noise(state.currentX, Config.SEED/Config.CONTROL, state.currentY)
        if noise == 0 then
            noise = math.noise(state.currentX/Config.CONTROL, Config.SEED/Config.CONTROL, state.currentY/Config.CONTROL)
        end

        -- Calculate direction based on noise
        local percentage = math.floor((noise - math.floor(noise)) * 1000)
        local index = math.floor(percentage / (1000/#neighbors)) + 1
        local chosen = neighbors[math.min(index, #neighbors)]

        -- Create path in chosen direction
        deleteWall(currentCell, chosen.cell, chosen.direction)
    else
        -- Check if maze is complete
        if state.completed >= Config.SIZE_X * Config.SIZE_Y then
            -- Finalize the maze
            clearGhostHouseInterior(grid)
            createGhostHouse(mazeModel)
            break
        else
            -- Backtrack if dead end reached
            if not backtrack() then
                break
            end
        end
    end

    task.wait() -- Yield to prevent script timeout
end