kingdoms_game/mods/utils/arrayutil.lua

387 lines
12 KiB
Lua
Raw Normal View History

2017-09-18 07:19:27 -05:00
--[[
Copyright (c) 2014, Robert 'Bobby' Zenz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--]]
--- Various utility functions for working with arrays. An array is sub-form of
-- a array, the array is simply indexed with numbers like this:
--
-- local array = { 1 = "a", 2 = "b", 3 = "c" }
-- local array = { "a", "b", "c" }
arrayutil = {}
--- Gets if the given array contains the given item.
--
-- @param array The array to search in.
-- @param item The item to search for, can either be an item or another array.
-- @param equals Optional. The function to determine if items equal each other,
-- defaults to tableutil.equals.
-- @param offset_step Optional. If the given item is an array, this determines
-- how much of the array is skipped before it is tried to
-- match.
-- @return true if the array contains the given item.
function arrayutil.contains(array, item, equals, offset_step)
return arrayutil.index(array, item, equals, offset_step) >= 0
end
--- Creates a 2D array with the given bounds and sets it to the given default
-- value.
--
-- @param start_x The start of the first dimension, inclusive.
-- @param start_y The start of the second dimension, inclusive.
-- @param end_x The end of the first dimension, inclusive.
-- @param end_y The end of the second dimension, inclusive.
-- @param default_value The default value that will be set, it will be cloned
-- for every entry.
-- @return The created 2D array.
function arrayutil.create2d(start_x, start_y, end_x, end_y, default_value)
local array = {}
for x = start_x, end_x, 1 do
array[x] = {}
for y = start_y, end_y, 1 do
array[x][y] = tableutil.clone(default_value)
end
end
return array
end
--- Creates a 3D array with the given bounds and sets it to the given default
-- value.
--
-- @param start_x The start of the first dimension, inclusive.
-- @param start_y The start of the second dimension, inclusive.
-- @param start_z The start of the third dimension, inclusive.
-- @param end_x The end of the first dimension, inclusive.
-- @param end_y The end of the second dimension, inclusive.
-- @param end_z The end of the third dimension, inclusive.
-- @param default_value The default value that will be set, it will be cloned
-- for every entry.
-- @return The created 3D array.
function arrayutil.create3d(start_x, start_y, start_z, end_x, end_y, end_z, default_value)
local array = {}
for x = start_x, end_x, 1 do
array[x] = {}
for y = start_y, end_y, 1 do
array[x][y] = {}
for z = start_z, end_z, 1 do
array[x][y][z] = tableutil.clone(default_value)
end
end
end
return array
end
--- Gets the index of the item in the given array.
--
-- @param array The array to search in.
-- @param item The item to search for, can either be an item or another array.
-- @param equals Optional. The function to determine if items equal each other,
-- defaults to tableutil.equals.
-- @param offset_step Optional. If the given item is an array, this determines
-- how much of the array is skipped before it is tried to
-- match.
-- @return The index of the given item or array, -1 if it was not found.
function arrayutil.index(array, item, equals, offset_step)
equals = equals or tableutil.equals
offset_step = offset_step or 1
if #array == 0 then
return -1
end
local item_is_array = type(item) == "table"
if item_is_array then
if #array < #item then
return -1
end
end
if item_is_array then
for offset = 1, #array - 1, offset_step do
local match = true
for index = 1, #item, 1 do
local array_index = index + offset - 1
if array_index > #array then
array_index = array_index - #array
end
if not equals(array[array_index], item[index]) then
match = false
-- Ugly way to break a loop, I know.
index = #item + 1
end
end
if match then
return offset
end
end
else
for index = 1, #array, 1 do
if equals(array[index], item) then
return index
end
end
end
return -1
end
--- Finds the next matching column.
--
-- @param array The 2D array to search.
-- @param start_index Optional. The index at which to start. Defaults to 1, or
-- if the direction is reversed, the number of columns in
-- the array.
-- @param matcher Optional. The function that is used to determine if the column
-- matches or not. Is expected to take one argument, the item,
-- and return a boolean. The column matches if any of its items
-- matches this condition. Defaults to not nil and not empty
-- string.
-- @param reverse Optional. If the array should be serched backwards. Defaults
-- to false.
-- @return The index of the matching column. -1 if none was found.
function arrayutil.next_matching_column(array, start_index, matcher, reverse)
matcher = matcher or function(item)
return item ~= nil and item ~= ""
end
local current_column = 0
if reverse and start_index == nil then
for row_index = 1, #array, 1 do
local row = array[row_index]
current_column = math.max(current_column, #row)
end
else
current_column = start_index or 1
end
local had_column = true
while had_column do
had_column = false
for row_index = 1, #array, 1 do
local row = array[row_index]
if current_column >= 1 and current_column <= #row then
had_column = true
if matcher(row[current_column]) then
return current_column
end
end
end
if reverse then
current_column = current_column - 1
else
current_column = current_column + 1
end
end
return -1
end
--- Finds the next matching row.
--
-- @param array The 2D array to search.
-- @param start_index Optional. The index at which to start. Defaults to 1, or
-- if the direction is reversed, the number of rows in
-- the array.
-- @param matcher Optional. The function that is used to determine if the row
-- matches or not. Is expected to take one argument, the item,
-- and return a boolean. The row matches if any of its items
-- matches this condition. Defaults to not nil and not empty
-- string.
-- @param reverse Optional. If the array should be serched backwards. Defaults
-- to false.
-- @return The index of the matching row. -1 if none was found.
function arrayutil.next_matching_row(array, start_index, matcher, reverse)
matcher = matcher or function(item)
return item ~= nil and item ~= ""
end
local to = #array
local step = 1
if reverse then
start_index = start_index or #array
to = 1
step = -1
else
start_index = start_index or 1
end
for row_index = start_index, to, step do
local row = array[row_index]
for column_index = 1, #row, 1 do
if matcher(row[column_index]) then
return row_index
end
end
end
return -1
end
--- Finds the previous matching column.
--
-- @param array The 2D array to search.
-- @param start_index Optional. The index at which to start. Defaults to
-- the number columns in the array.
-- @param matcher Optional. The function that is used to determine if the column
-- matches or not. Is expected to take one argument, the item,
-- and return a boolean. The column matches if any of its items
-- matches this condition. Defaults to not nil and not empty
-- string.
-- @return The index of the matching column. -1 if none was found.
function arrayutil.previous_matching_column(array, start_index, matcher)
return arrayutil.next_matching_column(array, start_index, matcher, true)
end
--- Finds the previous matching row.
--
-- @param array The 2D array to search.
-- @param start_index Optional. The index at which to start. Defaults to
-- the number rows in the array.
-- @param matcher Optional. The function that is used to determine if the row
-- matches or not. Is expected to take one argument, the item,
-- and return a boolean. The row matches if any of its items
-- matches this condition. Defaults to not nil and not empty
-- string.
-- @return The index of the matching row. -1 if none was found.
function arrayutil.previous_matching_row(array, start_index, matcher)
return arrayutil.next_matching_row(array, start_index, matcher, true)
end
--- Removes empty rows and columns at the beginning and the end of the given
-- array.
--
-- @param array The array.
-- @param is_empty Optional. The function used for determining if the item is
-- empty. By default nil and an empty string is considered
-- empty. Expected is a function that takes one item and returns
-- a boolean.
function arrayutil.reduce2d(array, is_empty)
local first_row = arrayutil.next_matching_row(array, nil, is_empty)
local last_row = arrayutil.previous_matching_row(array, nil, is_empty)
local first_column = arrayutil.next_matching_column(array, nil, is_empty)
local last_column = arrayutil.previous_matching_column(array, nil, is_empty)
if last_row == -1 then
last_row = first_row
end
if last_column == -1 then
last_column = first_column
end
local reduced = {}
if first_row ~= -1 and first_column ~= -1 then
for row_index = first_row, last_row, 1 do
local row = array[row_index]
local reduced_row = {}
for column_index = first_column, last_column, 1 do
reduced_row[column_index - first_column + 1] = row[column_index]
end
reduced[row_index - first_row + 1] = reduced_row
end
end
return reduced
end
--- Reindexes the given 2D array, swapping the two dimensions.
--
-- @param data The array to reindex.
-- @param new_x The new startpoint for the first dimension.
-- @param new_y The new startpoint for the second dimension.
-- @return The reindexed array.
function arrayutil.swapped_reindex2d(data, new_x, new_y)
local reindexed_data = {}
for old_x = 1, constants.block_size, 1 do
local index_x = new_x + old_x - 1
reindexed_data[index_x] = {}
for old_y = 1, constants.block_size, 1 do
local index_y = new_y + old_y - 1
reindexed_data[index_x][index_y] = data[old_y][old_x]
end
end
return reindexed_data
end
--- Reindexes the given 3d array, swapping the two dimensions.
--
-- @param data The array to reindex.
-- @param new_x The new startpoint for the first dimension.
-- @param new_y The new startpoint for the second dimension.
-- @param new_z The new startpoint for the third dimension.
-- @return The reindexed array.
function arrayutil.swapped_reindex3d(data, new_x, new_y, new_z)
local reindexed_data = {}
for old_x = 1, constants.block_size, 1 do
local index_x = new_x + old_x - 1
reindexed_data[index_x] = {}
for old_z = 1, constants.block_size, 1 do
local index_z = new_z + old_z - 1
reindexed_data[index_x][index_z] = {}
for old_y = 1, constants.block_size, 1 do
local index_y = new_y + old_y - 1
reindexed_data[index_x][index_z][index_y] = data[old_z][old_y][old_x]
end
end
end
return reindexed_data
end