<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<el:level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://enigma-game.org/schema/level/1 level.xsd" xmlns:el="http://enigma-game.org/schema/level/1">
  <el:protected>
    <el:info el:type="library">
      <el:identity el:title="" el:id="lib/libmath"/>
      <el:version el:score="1" el:release="1" el:revision="7" el:status="released"/>
      <el:author  el:name="Enigma Team" el:email="" el:homepage=""/>
      <el:copyright>Copyright © 2007, 2008 Enigma Team</el:copyright>
      <el:license el:type="GPL v2.0 or above" el:open="true"/>
      <el:compatibility el:enigma="1.10">
        <el:dependency el:path="lib/liblua" el:id="lib/liblua" el:release="1" el:preload="true"/>
      </el:compatibility>
      <el:modes el:easy="false" el:single="false" el:network="false"/>
      <el:comments>
      </el:comments>
      <el:score el:easy="-" el:difficult="-"/>
    </el:info>
    <el:luamain><![CDATA[

---------------------------------------------------------------------
-- libmath includes mathematical and functions and algorithms
-- of various origins.
---------------------------------------------------------------------
--
-- libmath provides the following functions:
--   lib.math.manhattan_distance(x1, y1, x2, y2)
--   lib.math.digits(number, base)
--   lib.math.combinations(depth, digits)
--   lib.math.cubic_polynomial(a, x, y)
--   lib.math.permutation(n)
--   lib.math.cyclic_permutation(n)  
--   lib.math.random_vector(number, ...)
--   lib.math.mark_components(group, attribute_name, neighborhood, exclusive_attribute)
-- Additional mathematically relevant functions are part of liblua and api2init:
--   lib.lua.mod(value, modul)
--   cond(condition, iftrue, iffalse)
--
-- libmath depends on liblua, as lib.math.combinations makes
-- use of lib.lua.deep_copy.
--

lib.math = {}
setmetatable(lib.math, getmetatable(lib))

---------------------------------------------------------------------
--  ADVANCED  POSITION  HANDLING  AND  CALCULATIONS
---------------------------------------------------------------------

-- manhattan_distance calculates the Manhattan-distance between
-- (X1,Y1) and (X2, Y2), which is |X1 - X2| + |Y1 - Y2|.
-- If X2 and Y2 are nil, X1 and Y1 are assumed to be positions
-- instead of coordinates.
function lib.math.manhattan_distance(x1, y1, x2, y2)
    if x1 and y1 and x2 and y2 then
        -- x1, y1, x2, y2 are coordinates
        return math.abs(x1 - x2) + math.abs(y1 - y2)
    end
    if x1 and y1 then
        -- x1 and y1 are positions, possibly tables.
        local p1 = x1
        local p2 = y1
        if type(p1) == "table" then
            p1 = po(p1)
        end
        if type(p2) == "table" then
            p2 = po(p2)
        end
        return math.abs(p1.x - p2.x) + math.abs(p1.y - p2.y)
    end
    error("manhattan_distance: Too less arguments.", 2)
end

---------------------------------------------------------------------
--  MATHEMATICAL  FUNCTIONS
---------------------------------------------------------------------

-- digits returns a table whose elements are the digits of NUMBER
-- in base BASE. BASE can be a number (e.g. 3 to get ternary)
-- as well as a table (then the table entries with numerical
-- keys will be used as digits).
-- Examples:
--   lib.math.digits(13, 2) = {1, 0, 1, 1}
--   lib.math.digits(15, 16) = {15}
--   lib.math.digits(17, 3) = {2, 2, 1}
--   lib.math.digits(17, {2, "b", 5}) = {5, 5, "b"}
-- Hexadecimal would be:
--   lib.math.digits(x, {0,1,2,3,4,5,6,7,8,9,"A","B","C","D","E","F"})
-- NUMBER is supposed to be a non-negative integer.
-- If NUMBER is zero, lib.math.digits returns the zero-digit as
-- result, unless ZERO_IS_EMPTY is true - in this case, it returns
-- an empty table.
function lib.math.digits(number, base, zero_is_empty)
    -- Check arguments and calculate fullbase and exponent
    assert_type(number, "lib.math.digits first argument", 2, "natural")
    assert_type(base, "lib.math.digits second argument", 2, "integer", "table")
    assert_type(zero_is_empty, "lib.math.digits third argument", 2, "boolean", "nil")
    local fullbase = {}
    local exponent = 0
    if type(base) == "number" then
        if base < 2 then
            error("lib.math.digits: Second argument out of range, is "..base..", must be at least 2.", 2)
        end
        for j = 1, base do
            table.insert(fullbase, j - 1)
        end
    else -- type(base) == "table"
        if table.getn(base) < 2 then
            error("lib.math.digits: Second argument has not enough elements, must be at least 2.", 2)
        end
        fullbase = base
    end
    exponent = table.getn(fullbase)
    -- Handle zero
    if number == 0 then
        return cond(zero_is_empty, {}, {fullbase[1]})
    end
    -- Decompose NUMBER
    local remains = number
    local result = {}
    while remains > 0 do
        local d = remains % exponent
        table.insert(result, fullbase[d + 1])
        remains = (remains - d) / exponent
        if remains ~= math.ceil(remains) then
            error("lib.math.digits: Internal error during calculation (remains = "..remains..").")
        end
    end
    return result
end

-- Return a table of all combinations of DEPTH entries,
-- each of which is chosen from DIGITS.
-- Example: lib.math.combinations(3, {7, 8, "a"}) will return
--   { {7,7,7}, {7,7,8}, {7,7,"a"}, {7,8,7}, {7,8,8}, {7,8,"a"},
--     {7,"a",7}, {7,"a",8}, {7,"a","a"}, {8,7,7}, ... }
-- Mathematically, it builds the leafs of an #DIGITS-ary tree
-- of depth DEPTH. Note that liblua has to be loaded to use
-- lib.math.combinations.
function lib.math.combinations(depth, digits)
    local all_combinations = {{}}
    local digs = digits
    assert_type(depth, "lib.math.combinations first argument (depth)", 2, "positive integer")
    assert_type(digits, "lib.math.combinations second argument (digits)", 2, "positive integer", "table")
    if type(digits) == "number" then
        digs = {}
        for j = 1, digits do
            digs[j] = j
        end
    end
    for _ = 1, depth do
        local next_step = {}
        for _, old_combination in pairs(all_combinations) do
            for _, new_digit in pairs(digs) do
                local new_combination = lib.lua.deep_copy(old_combination)
                table.insert(new_combination, new_digit)
                table.insert(next_step, new_combination)
            end
        end
        all_combinations = next_step
    end
    return all_combinations
end

-- cubic_polynomial returns the result of the
-- following polynomial with coefficients in A:
-- a[10]*y*y*y + a[9]*x*y*y + a[8]*x*x*y + a[7]*x*x*x
--   + a[6]*y*y + a[5]*x*y + a[4]*x*x + a[3]*y + a[2]*x + a[1]
-- You can use lib.math.random_vector(10, ...) and
-- a modulo operation to easily form a random
-- pattern of a floor, or choose the coefficients
-- to your own liking. Entries in A which are not
-- numbers are considered zero.
function lib.math.cubic_polynomial(a, x, y)
    assert_type(a, "lib.math.cubic_polynomial first argument", 2, "table")
    assert_type(x, "lib.math.cubic_polynomial second argument", 2, "number")
    assert_type(y, "lib.math.cubic_polynomial third argument", 2, "number")
    return   (a[10] or 0)*y*y*y + (a[9] or 0)*x*y*y + (a[8] or 0)*x*x*y
           + (a[7] or 0)*x*x*x + (a[6] or 0)*y*y + (a[5] or 0)*x*y
           + (a[4] or 0)*x*x + (a[3] or 0)*y + (a[2] or 0)*x + (a[1] or 0)
end

-- steps takes DISCRIMINATOR and returns a value dependend on where
-- in the table STEPS of integers the discriminator is located.
-- Example: lib.math.steps(x, {4, 8, 13}) returns:
--     0: If        x < 4
--     1: If   4 <= x < 8
--     2: If   8 <= x < 13
--     3: If  13 <= x
-- Negative and non-integer values are allowed.
function lib.math.steps(discriminator, steps)
    assert_type(discriminator, "lib.math.steps first argument", 2, "number")
    assert_type(steps, "lib.math.steps second argument", 2, "table")
    local result = 0
    for _, height in ipairs(steps) do
        assert_type(height, "lib.math.steps entry in second argument (height)", 2, "number")
        if discriminator < height then
            return result
        else
            result = result + 1
        end
    end
    return result
end

---------------------------------------------------------------------
--  PERMUTATIONS  AND  RANDOM  NUMBERS
---------------------------------------------------------------------

-- Return a random permutation of n elements.
-- This function outputs a table with integer entries between
-- 1 and n at positions 1 to n.
function lib.math.permutation(n)
    assert_type(n, "lib.math.permutation first argument", 2, "positive integer")
    if n == 1 then
        return {1}
    end
    local sequence = {}
    for j = 1, n do
        table.insert(sequence, j)
    end
    for n = table.getn(sequence), 2, -1 do
        local m = math.random(n)
        sequence[n], sequence[m] = sequence[m], sequence[n]
    end
    return sequence
end

-- Return a random cyclic permutation (i.e. with only one cycle) of n elements.
function lib.math.cyclic_permutation(n)
    assert_type(n, "lib.math.cyclic_permutation first argument", 2, "positive integer")
    local sequence1 = lib.math.permutation(n)
    local sequence2 = {}
    for j = 1, n - 1 do
        sequence2[sequence1[j]] = sequence1[j+1]
    end
    sequence2[sequence1[n]] = sequence1[1]
    return sequence2
end

-- Return a table with NUMBER random entries.
-- Additional arguments like with math.random.
function lib.math.random_vector(number, ...)
    assert_type(number, "lib.math.random_vector first argument", 2, "natural")
    local result = {}
    for j = 1, number do
        result[j] = math.random(...)
    end
    return result
end

-- Analyze the connected components of a group GROUP of floors.
-- Two floors are directly connected if their difference is in the polist
-- NEIGHBORHOOD, which has to be symmetric. A table is returned holding a
-- list of groups, each group is a complete connected component of GROUP.
-- In turn, each object in GROUP is attached an attribute with title
-- ATTRIBUTE_NAME, which holds the number of its component.
-- Note that any prior information in this attribute is lost.
-- If the attribute is not used by any other floor of the level, you may
-- set EXCLUSIVE_ATTRIBUTE to "true", which will result in a faster
-- algorithm.
-- Default NEIGHBORHOOD is NEIGHBORS_4.
function lib.math.mark_components(group, attribute_name, neighborhood, exclusive_attribute)
    assert_type(group, "lib.math.mark_components first argument (object group)", 2, "group")
    assert_type(attribute_name, "lib.math.mark_components second argument (attribute name)", 2, "non-empty string")
    assert_type(neighborhood, "lib.math.mark_components third argument (neighborhood)", 2, "polist", "nil")
    assert_type(exclusive_attribute, "lib.math.mark_components fourth argument (attribute name is exclusive)",
                2, "boolean", "nil")
    -- Check that GROUP is a group of floors.
    for obj in group do
        if not obj:is("fl") then
            error("lib.math.mark_components first argument must be a group of "
             .. "floors, but contains " .. obj:kind() .. ".", 2)
        end
    end
    -- Check that NEIGHBORHOOD is symmetric.
    if neighborhood and (neighborhood ~= NEIGHBORS_8) and (neighborhood ~= NEIGHBORS_CHESS) then
        for j = 1, #neighborhood do
            local found = false
            for k = 1, #neighborhood do
                found = found or (neighborhood[j] == -neighborhood[k])
            end
            if not found then
                error("lib.math.mark_components second argument must be a "
                      .. "symmetric polist, i.e. each negative of each position must be in the "
                      .. "polist as well.", 2)
            end
        end
    end
    local neigh = neighborhood or NEIGHBORS_4
    -- Start analyzing the components.
    local component = {}
    group[attribute_name] = 0
    -- Default recursive function.
    local function mark_component(obj, current)
        if (obj[attribute_name] or -1) == 0 then
            obj[attribute_name] = current
            component[current] = component[current] + obj
            for next_obj in fl(neigh + po(obj)) * group do
                mark_component(next_obj, current)
            end
        end
    end
    -- If we know that the attribute ATTRIBUTE_NAME is not used by any
    -- other floor, we might choose another, faster algorithm.
    local function mark_component_use_exclusiveness(obj, current)
        obj[attribute_name] = current
        component[current] = component[current] + obj
        for next_obj in fl(neigh + po(obj)) do
            if (next_obj[attribute_name] or -1) == 0 then
                mark_component_use_exclusiveness(next_obj, current)
            end
        end
    end
    -- Now loop over all floors of the group.
    for obj in group do
        if obj[attribute_name] == 0 then
            component[1 + #component] = grp()
            if exclusive_attribute then
                mark_component_use_exclusiveness(obj, #component)
            else
                mark_component(obj, #component)
            end
        end    
    end
    return component    
end

    ]]></el:luamain>
    <el:i18n>
    </el:i18n>
  </el:protected>
</el:level>

