-- Maze module: maze generator and renderer for Enigma
-- 
-- Copyright (c) 2002 Nat Pryce
-- License: GPL v2.0 or above

function remove_random_element( table )
    return tremove( table, random( 1, getn(table) ) )
end

function random_element( table )
    return table[random( 1, getn(table) )]
end


-------------------------------------------------------------------------------
-- Maze objects


-- These constants conflict with those defined in init.lua; commented
-- them out for now (dh).

-- NORTH = 1
-- SOUTH = 2
-- WEST = 3
-- EAST = 4

function maze_coords_to_cell( maze, x, y )
    return y*maze.width + x
end

function maze_can_go_south( maze, x, y )
    return y < (maze.height-1) and maze.linky[maze_coords_to_cell(maze,x,y)]
end

function maze_can_go_north( maze, x, y )
    return y > 0 and maze_can_go_south(maze,x,y-1)
end

function maze_can_go_east( maze, x, y )
    return x < (maze.width-1) and maze.linkx[maze_coords_to_cell(maze,x,y)]
end

function maze_can_go_west( maze, x, y )
    return x > 0 and maze_can_go_east( maze, x-1, y )
end

function maze_link_south( maze, x, y )
    assert( maze_contains_cell( maze, x, y+1 ) )
    maze.linky[maze_coords_to_cell(maze,x,y)] = 1
end

function maze_link_north( maze, x, y )
    assert( maze_contains_cell( maze, x, y-1 ) )
    maze_link_south( maze, x, y-1 )
end

function maze_link_east( maze, x, y )
    assert( maze_contains_cell( maze, x+1, y ) )
    maze.linkx[maze_coords_to_cell(maze,x,y)] = 1
end

function maze_link_west( maze, x, y )
    assert( maze_contains_cell( maze, x-1, y ) )
    maze_link_east( maze, x-1, y )
end

function maze_contains_cell( maze, x, y )
    return x >= 0 and x < maze.width and y >= 0 and y < maze.height
end

function new_maze( width, height )
    local maze = {}
    
    maze.width = width
    maze.height = height
    maze.linkx = {}
    maze.linky = {}
    
    maze.contains_cell = maze_contains_cell
    maze.coords_to_cell = maze_coords_to_cell
    maze.can_go_north = maze_can_go_north
    maze.can_go_south = maze_can_go_south
    maze.can_go_west = maze_can_go_west
    maze.can_go_east = maze_can_go_east
    maze.link_north = maze_link_north
    maze.link_south = maze_link_south
    maze.link_west = maze_link_west
    maze.link_east = maze_link_east
    
    return maze
end


-------------------------------------------------------------------------------
-- Maze generator based on Kruskal's minimum spanning tree algorithm

function new_kruskal_maze( width, height )
    local maze = new_maze(width,height)
    local walls = maze_walls( maze )
    local zones = {}
    local zone_count = width*height
    
    randomseed( enigma.GetTicks() )
    
    while zone_count > 1 do
        wall = remove_random_element( walls )
        local x1 = wall.cellx
        local y1 = wall.celly
        local x2, y2
        
        if wall.side == SOUTH then
            x2 = wall.cellx
            y2 = wall.celly+1
        else -- wall.side == EAST
            x2 = wall.cellx+1
            y2 = wall.celly
        end
        
        cell1 = maze:coords_to_cell(x1,y1)
        cell2 = maze:coords_to_cell(x2,y2)
        zone1 = find_zone( zones, cell1 )
        zone2 = find_zone( zones, cell2 )
        
        if zone1 ~= zone2 then
            if wall.side == SOUTH then
                maze:link_south( x1, y1 )
            else -- wall.side == EAST
                maze:link_east( x1, y1 )
            end
            
            union_zones( zones, cell1, cell2 )
            zone_count = zone_count - 1
        end
    end
    
    return maze
end


function maze_walls( maze )
    walls = {}
    for y = 0, maze.height-1 do
        for x = 0, maze.width-1 do
            if y < maze.height-1 then
                tinsert( walls, {cellx=x,celly=y,side=SOUTH} )
            end
            if x < maze.width-1 then
                tinsert( walls, {cellx=x,celly=y,side=EAST} )
            end
        end
    end
    
    return walls
end



-- Union-find algorithm for merging "zones" of the maze.  Each zone is
-- a tree of cells identified by the cell at the root of the tree.  A root
-- cell is represented by a nil reference in the zones table indexed by the
-- cell number.

function find_zone( zones, cell )
    if zones[cell] == nil then
        return cell
    else
        zones[cell] = find_zone( zones, zones[cell] )
        return zones[cell]
    end
end

function union_zones( zones, cell1, cell2 )
    zones[find_zone(zones,cell2)] = find_zone(zones,cell1)
end



-------------------------------------------------------------------------------
-- Maze renderers


function render_maze( maze, cell_renderer )
    for cellx = 0, maze.width-1 do
        for celly = 0, maze.height-1 do
            cell_renderer( maze, cellx, celly )
        end
    end
end

function tight_maze( maze, maze_floor, wall_floor, wall_stone )
    local originx = 1
    local originy = 1
    
    function cell_to_level( cellx, celly )
        return %originx + cellx * 2, %originy + celly * 2
    end
    
    function draw_maze_border( maze, stone )
        width = maze.width*2
        height = maze.height*2
        
        draw_stones( stone, {0,0}, {1,0}, width )
        draw_stones( stone, {0,height-1},{1,0}, width )
        draw_stones( stone, {0,0}, {0,1}, height )
        draw_stones( stone, {width-1,0},{0,1}, height )
    end
    
    function render_cell( maze, cellx, celly )
        local x, y = cell_to_level( cellx, celly )
        
        if %wall_stone then 
            set_stone( %wall_stone, x+1, y+1 )
        end
        if %maze_floor then
            set_floor( %maze_floor, x, y )
        end
        if maze:can_go_south(cellx,celly) then
            if %maze_floor then
                set_floor( %maze_floor, x, y+1 )
            end
        else
            if %wall_stone then
                set_stone( %wall_stone, x, y+1 )
            end
        end
        if maze:can_go_east(cellx,celly) then
            if %maze_floor then
                set_floor( %maze_floor, x+1, y )
            end
        else
            if %wall_stone then
                set_stone( %wall_stone, x+1, y )
            end
        end
    end
    
    create_world( maze.width*2 + 1, maze.height*2 + 1 )
    if wall_stone then
        draw_border( wall_stone )
    end
    if wall_floor then
        fill_floor( wall_floor, 0, 0, level_width, level_height )
    else
        fill_floor( maze_floor, 0, 0, level_width, level_height )
    end
    render_maze( maze, render_cell )
    
    oxyd(1,0)
    oxyd(2*maze.width-1,2*maze.height)
    oxyd(1,2*maze.height)
    oxyd(2*maze.width-1,0)

    local actorx, actory = cell_to_level( random(maze.width)-1, 
                                          random(maze.height)-1 )
    set_actor( "ac-blackball", actorx + 0.5, actory + 0.5,
               { player=0 } )
end

