Start a Lua mathx module to hold round, shuffle, random, and a few other things

This commit is contained in:
Celtic Minstrel 2020-09-27 22:42:24 -04:00
parent c4d9590e71
commit a09f88c1c9
7 changed files with 211 additions and 140 deletions

98
data/lua/core/mathx.lua Normal file
View file

@ -0,0 +1,98 @@
--[========[Additional mathematical functions]========]
print("Loading mathx module...")
function mathx.random_choice(possible_values, random_func)
random_func = random_func or mathx.random
assert(type(possible_values) == "table" or type(possible_values) == "string",
string.format("mathx.random_choice expects a string or table as parameter, got %s instead",
type(possible_values)))
local items = {}
local num_choices = 0
if type(possible_values) == "string" then
-- split on commas
for _,word in ipairs(possible_values:quoted_split()) do
-- does the word contain two dots? If yes, that's a range
local dots_start, dots_end = word:find("%.%.")
if dots_start then
-- split on the dots if so and cast to numbers
local low = tonumber(word:sub(1, dots_start-1))
local high = tonumber(word:sub(dots_end+1))
-- perhaps someone passed a string as part of the range, intercept the issue
if not (low and high) then
wesnoth.message("Malformed range: " .. word)
table.insert(items, word)
num_choices = num_choices + 1
else
if low > high then
-- low is greater than high, swap them
low, high = high, low
end
-- if both ends represent the same number, then just use that number
if low == high then
table.insert(items, low)
num_choices = num_choices + 1
else
-- insert a table representing the range
table.insert(items, {low, high})
-- how many items does the range contain? Increase difference by 1 because we include both ends
num_choices = num_choices + (high - low) + 1
end
end
else
-- handle as a string
table.insert(items, word)
num_choices = num_choices + 1
end
end
else
num_choices = #possible_values
items = possible_values
-- We need to parse ranges separately anyway
for i, val in ipairs(possible_values) do
if type(val) == "table" then
assert(#val == 2 and type(val[1]) == "number" and type(val[2]) == "number", "Malformed range for helper.rand")
if val[1] > val[2] then
val = {val[2], val[1]}
end
num_choices = num_choices + (val[2] - val[1])
end
end
end
local idx = random_func(1, num_choices)
for i, item in ipairs(items) do
if type(item) == "table" then -- that's a range
local elems = item[2] - item[1] + 1 -- amount of elements in the range, both ends included
if elems >= idx then
return item[1] + elems - idx
else
idx = idx - elems
end
else -- that's a single element
idx = idx - 1
if idx == 0 then
return item
end
end
end
return nil
end
function mathx.shuffle(t, random_func)
random_func = random_func or mathx.random
-- since tables are passed by reference, this is an in-place shuffle
-- it uses the Fisher-Yates algorithm, also known as Knuth shuffle
assert(type(t) == "table", string.format("mathx.shuffle expects a table as parameter, got %s instead", type(t)))
local length = #t
for index = length, 2, -1 do
local random = random_func(1, index)
t[index], t[random] = t[random], t[index]
end
end
wesnoth.random = wesnoth.deprecate_api('wesnoth.random', 'mathx.random', 1, nil, mathx.random)

View file

@ -86,114 +86,6 @@ function helper.adjacent_tiles(x, y, with_borders)
end
end
function helper.rand (possible_values, random_func)
random_func = random_func or wesnoth.random
assert(type(possible_values) == "table" or type(possible_values) == "string",
string.format("helper.rand expects a string or table as parameter, got %s instead",
type(possible_values)))
local items = {}
local num_choices = 0
if type(possible_values) == "string" then
-- split on commas
for word in possible_values:gmatch("[^,]+") do
-- does the word contain two dots? If yes, that's a range
local dots_start, dots_end = word:find("%.%.")
if dots_start then
-- split on the dots if so and cast as numbers
local low = tonumber(word:sub(1, dots_start-1))
local high = tonumber(word:sub(dots_end+1))
-- perhaps someone passed a string as part of the range, intercept the issue
if not (low and high) then
wesnoth.message("Malformed range: " .. possible_values)
table.insert(items, word)
num_choices = num_choices + 1
else
if low > high then
-- low is greater than high, swap them
low, high = high, low
end
-- if both ends represent the same number, then just use that number
if low == high then
table.insert(items, low)
num_choices = num_choices + 1
else
-- insert a table representing the range
table.insert(items, {low, high})
-- how many items does the range contain? Increase difference by 1 because we include both ends
num_choices = num_choices + (high - low) + 1
end
end
else
-- handle as a string
table.insert(items, word)
num_choices = num_choices + 1
end
end
else
num_choices = #possible_values
items = possible_values
-- We need to parse ranges separately anyway
for i, val in ipairs(possible_values) do
if type(val) == "table" then
assert(#val == 2 and type(val[1]) == "number" and type(val[2]) == "number", "Malformed range for helper.rand")
if val[1] > val[2] then
val = {val[2], val[1]}
end
num_choices = num_choices + (val[2] - val[1])
end
end
end
local idx = random_func(1, num_choices)
for i, item in ipairs(items) do
if type(item) == "table" then -- that's a range
local elems = item[2] - item[1] + 1 -- amount of elements in the range, both ends included
if elems >= idx then
return item[1] + elems - idx
else
idx = idx - elems
end
else -- that's a single element
idx = idx - 1
if idx == 0 then
return item
end
end
end
return nil
end
function helper.round( number )
-- code converted from util.hpp, round_portable function
-- round half away from zero method
if number >= 0 then
number = math.floor( number + 0.5 )
else
number = math.ceil ( number - 0.5 )
end
return number
end
function helper.shuffle( t, random_func )
random_func = random_func or wesnoth.random
-- since tables are passed by reference, this is an in-place shuffle
-- it uses the Fisher-Yates algorithm, also known as Knuth shuffle
assert(
type( t ) == "table",
string.format( "helper.shuffle expects a table as parameter, got %s instead", type( t ) ) )
local length = #t
for index = length, 2, -1 do
local random = random_func( 1, index )
t[index], t[random] = t[random], t[index]
end
end
-- Compatibility and deprecations
helper.distance_between = wesnoth.deprecate_api('helper.distance_between', 'wesnoth.map.distance_between', 1, nil, wesnoth.map.distance_between)
helper.get_child = wesnoth.deprecate_api('helper.get_child', 'wml.get_child', 1, nil, wml.get_child)
@ -217,5 +109,8 @@ helper.shallow_parsed = wesnoth.deprecate_api('helper.shallow_parsed', 'wml.shal
helper.set_wml_var_metatable = wesnoth.deprecate_api('helper.set_wml_var_metatable', 'wml.variable.proxy', 2, nil, helper.set_wml_var_metatable)
helper.set_wml_tag_metatable = wesnoth.deprecate_api('helper.set_wml_tag_metatable', 'wml.tag', 2, nil, helper.set_wml_tag_metatable)
helper.get_user_choice = wesnoth.deprecate_api('helper.get_user_choice', 'gui.get_user_choice', 1, nil, gui.get_user_choice)
helper.rand = wesnoth.deprecate_api('helper.rand', 'mathx.random_choice', 1, nil, mathx.random_choice)
helper.round = wesnoth.deprecate_api('helper.round', 'mathx.round', 1, nil, mathx.round)
helper.shuffle = wesnoth.deprecate_api('helper.shuffle', 'mathx.shuffle', 1, nil, mathx.shuffle)
return helper

View file

@ -1039,6 +1039,7 @@
91E3570A1CACC9B200774252 /* playcampaign.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC2F600B1A048E210018C9D6 /* playcampaign.cpp */; };
91E3570B1CACC9B200774252 /* singleplayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC2F600C1A048E220018C9D6 /* singleplayer.cpp */; };
91ECD5D21BA11A5200B25CF1 /* unit_creator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91ECD5D01BA11A5200B25CF1 /* unit_creator.cpp */; };
91F8E12E260A25E2002312BA /* lua_mathx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91F8E12D260A25E1002312BA /* lua_mathx.cpp */; };
91FAC70A1C7FBC3400DAB2C3 /* lua_formula_bridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */; };
91FBBAD81CB6BC3F00470BFE /* filesystem_sdl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FBBAD71CB6BC3F00470BFE /* filesystem_sdl.cpp */; };
91FBBADB1CB6D1B700470BFE /* markov_generator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FBBAD91CB6D1B700470BFE /* markov_generator.cpp */; };
@ -2232,6 +2233,8 @@
91ECD5D11BA11A5200B25CF1 /* unit_creator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_creator.hpp; sourceTree = "<group>"; };
91EF6BFC1C9E22E400E2A733 /* const_clone.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = const_clone.hpp; sourceTree = "<group>"; };
91EF6C001C9E22E400E2A733 /* reference_counter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = reference_counter.hpp; sourceTree = "<group>"; };
91F8E12C260A25E1002312BA /* lua_mathx.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = lua_mathx.hpp; sourceTree = "<group>"; };
91F8E12D260A25E1002312BA /* lua_mathx.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lua_mathx.cpp; sourceTree = "<group>"; };
91FAC7081C7F931900DAB2C3 /* lua_formula_bridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lua_formula_bridge.hpp; sourceTree = "<group>"; };
91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lua_formula_bridge.cpp; sourceTree = "<group>"; };
91FBBAD71CB6BC3F00470BFE /* filesystem_sdl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = filesystem_sdl.cpp; sourceTree = "<group>"; };
@ -4681,6 +4684,8 @@
91B621E91B76BB1500B00E0F /* lua_kernel_base.hpp */,
ECA4A6791A1EC319006BCCF2 /* lua_map_location_ops.cpp */,
91B621EA1B76BB1800B00E0F /* lua_map_location_ops.hpp */,
91F8E12D260A25E1002312BA /* lua_mathx.cpp */,
91F8E12C260A25E1002312BA /* lua_mathx.hpp */,
9190B73A1CA0554900B0EF66 /* lua_pathfind_cost_calculator.hpp */,
ECFB61831DA0A0C50055D3F8 /* lua_preferences.cpp */,
ECFB61841DA0A0C50055D3F8 /* lua_preferences.hpp */,
@ -5344,6 +5349,7 @@
46685C9D219D518B0009CFFE /* schema_validator.cpp in Sources */,
ECF0F80123A09929004A2011 /* lua_stringx.cpp in Sources */,
46F92DE72174F6A400602C1C /* game_delete.cpp in Sources */,
91F8E12E260A25E2002312BA /* lua_mathx.cpp in Sources */,
6295C3C4150FC9750077D8C5 /* map_fragment.cpp in Sources */,
EC4E3B1D19B2D7AD0049CBD7 /* map_generator.cpp in Sources */,
B5599B2C0EC62181008DD061 /* label.cpp in Sources */,

View file

@ -321,6 +321,7 @@ scripting/lua_formula_bridge.cpp
scripting/lua_gui2.cpp
scripting/lua_wml.cpp
scripting/lua_stringx.cpp
scripting/lua_mathx.cpp
scripting/lua_kernel_base.cpp
scripting/lua_map_location_ops.cpp
scripting/lua_preferences.cpp

View file

@ -19,7 +19,6 @@
#include "gui/core/gui_definition.hpp" // for remove_single_widget_definition
#include "log.hpp"
#include "lua_jailbreak_exception.hpp" // for lua_jailbreak_exception
#include "random.hpp"
#include "seed_rng.hpp"
#include "deprecation.hpp"
#include "language.hpp" // for get_language
@ -37,6 +36,7 @@
#include "scripting/lua_wml.hpp"
#include "scripting/lua_stringx.hpp"
#include "scripting/lua_map_location_ops.hpp"
#include "scripting/lua_mathx.hpp"
#include "scripting/lua_rng.hpp"
#include "scripting/lua_widget.hpp"
#include "scripting/push_check.hpp"
@ -281,36 +281,6 @@ static int intf_name_generator(lua_State *L)
return 1;
}
/**
* Returns a random numer, same interface as math.random.
*/
static int intf_random(lua_State *L)
{
if (lua_isnoneornil(L, 1)) {
double r = static_cast<double>(randomness::generator->next_random());
double r_max = static_cast<double>(std::numeric_limits<uint32_t>::max());
lua_push(L, r / (r_max + 1));
return 1;
}
else {
int32_t min;
int32_t max;
if (lua_isnumber(L, 2)) {
min = lua_check<int32_t>(L, 1);
max = lua_check<int32_t>(L, 2);
}
else {
min = 1;
max = lua_check<int32_t>(L, 1);
}
if (min > max) {
return luaL_argerror(L, 1, "min > max");
}
lua_push(L, randomness::generator->get_random_int(min, max));
return 1;
}
}
/**
* Logs a message
* Arg 1: (optional) Logger
@ -431,6 +401,7 @@ lua_kernel_base::lua_kernel_base()
{ "utf8", luaopen_utf8 }, // added in Lua 5.3
// Wesnoth libraries
{ "stringx",lua_stringx::luaW_open },
{ "mathx", lua_mathx::luaW_open },
{ "wml", lua_wml::luaW_open },
{ "gui", lua_gui2::luaW_open },
{ nullptr, nullptr }
@ -483,7 +454,6 @@ lua_kernel_base::lua_kernel_base()
{ "compile_formula", &lua_formula_bridge::intf_compile_formula},
{ "eval_formula", &lua_formula_bridge::intf_eval_formula},
{ "name_generator", &intf_name_generator },
{ "random", &intf_random },
{ "log", &intf_log },
{ "get_image_size", &intf_get_image_size },
{ "get_time_stamp", &intf_get_time_stamp },

View file

@ -0,0 +1,81 @@
/*
Copyright (C) 2014 - 2020 by the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "scripting/lua_mathx.hpp"
#include "scripting/lua_kernel_base.hpp"
#include "scripting/lua_common.hpp"
#include "scripting/push_check.hpp"
#include "random.hpp"
#include "SDL2/SDL_timer.h" // for SDL_GetTicks
#include "lua/lauxlib.h"
#include "lua/lua.h"
#include "lua/lualib.h"
namespace lua_mathx {
/**
* Returns a random number, same interface as math.random.
*/
static int intf_random(lua_State* L)
{
if (lua_isnoneornil(L, 1)) {
double r = static_cast<double>(randomness::generator->next_random());
double r_max = static_cast<double>(std::numeric_limits<uint32_t>::max());
lua_push(L, r / (r_max + 1));
return 1;
}
else {
int32_t min;
int32_t max;
if (lua_isnumber(L, 2)) {
min = lua_check<int32_t>(L, 1);
max = lua_check<int32_t>(L, 2);
}
else {
min = 1;
max = lua_check<int32_t>(L, 1);
}
if (min > max) {
return luaL_argerror(L, 1, "min > max");
}
lua_push(L, randomness::generator->get_random_int(min, max));
return 1;
}
}
static int intf_round(lua_State* L) {
double n = lua_tonumber(L, 1);
lua_pushinteger(L, std::round(n));
return 1;
}
int luaW_open(lua_State* L) {
auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
lk.add_log("Adding mathx module...\n");
static luaL_Reg const math_callbacks[] = {
{ "random", &intf_random },
{ "round", &intf_round },
{ nullptr, nullptr },
};
lua_newtable(L);
luaL_setfuncs(L, math_callbacks, 0);
// Set the mathx metatable to index the math module
lua_createtable(L, 0, 1);
lua_getglobal(L, "math");
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
return 1;
}
}

View file

@ -0,0 +1,20 @@
/*
Copyright (C) 2014 - 2020 by the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
struct lua_State;
namespace lua_mathx {
int luaW_open(lua_State* L);
}