1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
|
<?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>
|