From 4ad7b50f39d0b9eafa232e5c48a020f3fdc08ad7 Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Sun, 27 Sep 2020 22:42:24 -0400 Subject: [PATCH] Start a Lua mathx module to hold round, shuffle, random, and a few other things --- data/lua/core/mathx.lua | 99 +++++++++++++++++++ data/lua/helper.lua | 111 +--------------------- projectfiles/VC16/wesnoth.vcxproj | 8 ++ projectfiles/VC16/wesnoth.vcxproj.filters | 6 ++ source_lists/wesnoth | 1 + src/scripting/lua_kernel_base.cpp | 44 +-------- src/scripting/lua_mathx.cpp | 91 ++++++++++++++++++ src/scripting/lua_mathx.hpp | 20 ++++ 8 files changed, 230 insertions(+), 150 deletions(-) create mode 100644 data/lua/core/mathx.lua create mode 100644 src/scripting/lua_mathx.cpp create mode 100644 src/scripting/lua_mathx.hpp diff --git a/data/lua/core/mathx.lua b/data/lua/core/mathx.lua new file mode 100644 index 000000000000..8d4315214393 --- /dev/null +++ b/data/lua/core/mathx.lua @@ -0,0 +1,99 @@ +--[========[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 possible_values: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) +wesnoth.get_time_stamp = wesnoth.deprecate_api('wesnoth.get_time_stamp', 'mathx.current_timestamp', 1, nil, mathx.current_timestamp) \ No newline at end of file diff --git a/data/lua/helper.lua b/data/lua/helper.lua index 65ab83a03452..4bf7426e7b19 100644 --- a/data/lua/helper.lua +++ b/data/lua/helper.lua @@ -93,114 +93,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) @@ -224,5 +116,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 diff --git a/projectfiles/VC16/wesnoth.vcxproj b/projectfiles/VC16/wesnoth.vcxproj index 5bebab6c122f..422f9f2821d9 100644 --- a/projectfiles/VC16/wesnoth.vcxproj +++ b/projectfiles/VC16/wesnoth.vcxproj @@ -2702,6 +2702,13 @@ $(IntDir)Scripting\ $(IntDir)Scripting\ + + $(IntDir)Scripting\ + $(IntDir)Scripting\ + $(IntDir)Scripting\ + $(IntDir)Scripting\ + $(IntDir)Scripting\ + $(IntDir)Scripting\ $(IntDir)Scripting\ @@ -4039,6 +4046,7 @@ + diff --git a/projectfiles/VC16/wesnoth.vcxproj.filters b/projectfiles/VC16/wesnoth.vcxproj.filters index 918391fd392f..e6ed8c55df5c 100644 --- a/projectfiles/VC16/wesnoth.vcxproj.filters +++ b/projectfiles/VC16/wesnoth.vcxproj.filters @@ -1572,6 +1572,9 @@ Scripting + + Scripting + Scripting @@ -3075,6 +3078,9 @@ Scripting + + Scripting + Scripting diff --git a/source_lists/wesnoth b/source_lists/wesnoth index b3462ba41c6a..9565a0b22e0f 100644 --- a/source_lists/wesnoth +++ b/source_lists/wesnoth @@ -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 diff --git a/src/scripting/lua_kernel_base.cpp b/src/scripting/lua_kernel_base.cpp index 19781ada1489..0cd8960ea96c 100644 --- a/src/scripting/lua_kernel_base.cpp +++ b/src/scripting/lua_kernel_base.cpp @@ -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(randomness::generator->next_random()); - double r_max = static_cast(std::numeric_limits::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(L, 1); - max = lua_check(L, 2); - } - else { - min = 1; - max = lua_check(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 @@ -375,15 +345,6 @@ static int intf_get_image_size(lua_State *L) { return 2; } -/** -* Returns the time stamp, exactly as [set_variable] time=stamp does. -* - Ret 1: integer -*/ -static int intf_get_time_stamp(lua_State *L) { - lua_pushinteger(L, SDL_GetTicks()); - return 1; -} - static int intf_get_language(lua_State* L) { lua_push(L, get_language().localename); @@ -431,6 +392,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,10 +445,8 @@ 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 }, { "get_language", &intf_get_language }, { nullptr, nullptr } }; diff --git a/src/scripting/lua_mathx.cpp b/src/scripting/lua_mathx.cpp new file mode 100644 index 000000000000..e92610324c5b --- /dev/null +++ b/src/scripting/lua_mathx.cpp @@ -0,0 +1,91 @@ +/* + 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(randomness::generator->next_random()); + double r_max = static_cast(std::numeric_limits::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(L, 1); + max = lua_check(L, 2); + } + else { + min = 1; + max = lua_check(L, 1); + } + if (min > max) { + return luaL_argerror(L, 1, "min > max"); + } + lua_push(L, randomness::generator->get_random_int(min, max)); + return 1; + } +} + +/** +* Returns the time stamp, exactly as [set_variable] time=stamp does. +* - Ret 1: integer +*/ +static int intf_get_time_stamp(lua_State* L) { + lua_pushinteger(L, SDL_GetTicks()); + 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(L); + lk.add_log("Adding mathx module...\n"); + static luaL_Reg const math_callbacks[] = { + { "current_timestamp", &intf_get_time_stamp }, + { "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; +} + +} diff --git a/src/scripting/lua_mathx.hpp b/src/scripting/lua_mathx.hpp new file mode 100644 index 000000000000..ac262cf7b3bc --- /dev/null +++ b/src/scripting/lua_mathx.hpp @@ -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); +}