Lua API for the name generators

This commit is contained in:
Celtic Minstrel 2016-04-15 14:12:29 -04:00
parent a9dbbe9c05
commit 1649930113
19 changed files with 474 additions and 188 deletions

View file

@ -139,6 +139,9 @@ Version 1.13.4+dev:
sources. The [sound_source] and [remove_sound_source] now use these.
* New wesnoth.log function for printing log messages. The [wml_message]
and [deprecated_message] tags now use this.
* New wesnoth.name_generator function builds a name generator and returns
it as a callable userdata. Both the original Markov chain generator
and the new context free gramamr generator are supported
* WML tables defined in Lua now accept string keys with array values
(where "array" is a table whose keys are all integers). This joins
the elements of the array with commas and produces a single string

View file

@ -135,6 +135,7 @@
916719061CADABEA00B055A9 /* game_data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC387E68195AFB1F00FC0342 /* game_data.cpp */; };
916719071CADAC0D00B055A9 /* libboost_randomw.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC64D75C1A085C990092EF75 /* libboost_randomw.dylib */; };
916719081CADAC2800B055A9 /* apple_notification.mm in Sources */ = {isa = PBXBuildFile; fileRef = F40A13BB1A3A88BA00C4D071 /* apple_notification.mm */; };
916B7E941CC151FA00811097 /* race.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55999C00EC62181008DD061 /* race.cpp */; };
919B37F81BAF789E00E0094C /* synced_user_choice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919B37F71BAF789D00E0094C /* synced_user_choice.cpp */; };
919B37FC1BAF7A9D00E0094C /* synced_choice_wait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919B37FA1BAF7A9D00E0094C /* synced_choice_wait.cpp */; };
91A214E51CAD666B00927AEA /* arrow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B52EE8A1121359A600CFBDAB /* arrow.cpp */; };
@ -448,6 +449,7 @@
91B622221B76C0F400B00E0F /* libboost_regexw.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F4EF0D5313AD4E35003C701D /* libboost_regexw.dylib */; };
91B622231B76C0F400B00E0F /* libboost_systemw.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F4EF0D5413AD4E35003C701D /* libboost_systemw.dylib */; };
91B622241B76C0F400B00E0F /* libboost_threadw.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F4EF0D5B13AD4E6D003C701D /* libboost_threadw.dylib */; };
91C55DA41CC078820040012E /* context_free_grammar_generator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91C55DA21CC078820040012E /* context_free_grammar_generator.cpp */; };
91DCA6891C9066CC0030F8D0 /* unit_preview_pane.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91DCA6871C9066CC0030F8D0 /* unit_preview_pane.cpp */; };
91DCA68D1C9066EC0030F8D0 /* unit_recruit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91DCA68B1C9066EC0030F8D0 /* unit_recruit.cpp */; };
91E355631CACA1CE00774252 /* libboost_unit_test_frameworkw.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 91E355621CACA1CE00774252 /* libboost_unit_test_frameworkw.dylib */; };
@ -718,6 +720,7 @@
91F462881C7115C50050A9C9 /* combobox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91F462861C7115C50050A9C9 /* combobox.cpp */; };
91F462941C7117400050A9C9 /* drop_down_list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91F462921C7117400050A9C9 /* drop_down_list.cpp */; };
91FAC70A1C7FBC3400DAB2C3 /* lua_formula_bridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */; };
91FBBADB1CB6D1B700470BFE /* markov_generator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FBBAD91CB6D1B700470BFE /* markov_generator.cpp */; };
B504B94C1284C06B00261FE9 /* tips.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B504B94A1284C06B00261FE9 /* tips.cpp */; };
B508D13F10013BF900B12852 /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B508D13E10013BF900B12852 /* Growl.framework */; };
B508D14B10013E4700B12852 /* Growl Registration Ticket.growlRegDict in Resources */ = {isa = PBXBuildFile; fileRef = B508D14A10013E4700B12852 /* Growl Registration Ticket.growlRegDict */; };
@ -841,7 +844,6 @@
B5599B0B0EC62181008DD061 /* reports.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55999B70EC62181008DD061 /* reports.cpp */; };
B5599B0C0EC62181008DD061 /* replay_controller.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55999B90EC62181008DD061 /* replay_controller.cpp */; };
B5599B0D0EC62181008DD061 /* replay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55999BB0EC62181008DD061 /* replay.cpp */; };
B5599B0F0EC62181008DD061 /* race.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55999C00EC62181008DD061 /* race.cpp */; };
B5599B100EC62181008DD061 /* preferences_display.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55999C20EC62181008DD061 /* preferences_display.cpp */; };
B5599B110EC62181008DD061 /* preferences.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55999C40EC62181008DD061 /* preferences.cpp */; };
B5599B130EC62181008DD061 /* playturn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55999C80EC62181008DD061 /* playturn.cpp */; };
@ -1671,6 +1673,9 @@
91B621F51B76BCB000B00E0F /* unicode_cast.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = unicode_cast.hpp; sourceTree = "<group>"; };
91B621F61B76BCB000B00E0F /* unicode_types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = unicode_types.hpp; sourceTree = "<group>"; };
91B621F71B76BD4600B00E0F /* multimenu.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = multimenu.hpp; sourceTree = "<group>"; };
91C55DA11CC078780040012E /* name_generator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = name_generator.hpp; sourceTree = "<group>"; };
91C55DA21CC078820040012E /* context_free_grammar_generator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = context_free_grammar_generator.cpp; sourceTree = "<group>"; };
91C55DA31CC078820040012E /* context_free_grammar_generator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = context_free_grammar_generator.hpp; sourceTree = "<group>"; };
91DCA6871C9066CC0030F8D0 /* unit_preview_pane.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unit_preview_pane.cpp; sourceTree = "<group>"; };
91DCA6881C9066CC0030F8D0 /* unit_preview_pane.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_preview_pane.hpp; sourceTree = "<group>"; };
91DCA68B1C9066EC0030F8D0 /* unit_recruit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unit_recruit.cpp; sourceTree = "<group>"; };
@ -1716,6 +1721,8 @@
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>"; };
91FAC70B1C80168600DAB2C3 /* group.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = group.hpp; sourceTree = "<group>"; };
91FBBAD91CB6D1B700470BFE /* markov_generator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = markov_generator.cpp; sourceTree = "<group>"; };
91FBBADA1CB6D1B700470BFE /* markov_generator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = markov_generator.hpp; sourceTree = "<group>"; };
B504B94A1284C06B00261FE9 /* tips.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tips.cpp; sourceTree = "<group>"; };
B504B94B1284C06B00261FE9 /* tips.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tips.hpp; sourceTree = "<group>"; };
B508D13E10013BF900B12852 /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = lib/Growl.framework; sourceTree = "<group>"; };
@ -3001,8 +3008,6 @@
B55999C10EC62181008DD061 /* preferences_display.hpp */,
ECC84C1B1B973C5900A5F451 /* quit_confirmation.cpp */,
ECC84C1C1B973C5900A5F451 /* quit_confirmation.hpp */,
B55999C00EC62181008DD061 /* race.cpp */,
B55999BF0EC62181008DD061 /* race.hpp */,
EC84245B18F30D9100FC1EEF /* random_new.cpp */,
EC84245C18F30D9100FC1EEF /* random_new.hpp */,
EC84245718F30D9000FC1EEF /* random_new_deterministic.cpp */,
@ -3565,11 +3570,16 @@
isa = PBXGroup;
children = (
91EF6BFC1C9E22E400E2A733 /* const_clone.hpp */,
91C55DA21CC078820040012E /* context_free_grammar_generator.cpp */,
91C55DA31CC078820040012E /* context_free_grammar_generator.hpp */,
918056BE1CB1E4C0001A7F35 /* functional.hpp */,
911F471B1CAE5A7E00F47035 /* iterable_pair.hpp */,
91EF6BFF1C9E22E400E2A733 /* iterator.hpp */,
EC53B04D1B23BB0E002F758F /* make_enum.cpp */,
91B621931B76720B00B00E0F /* make_enum.hpp */,
91FBBAD91CB6D1B700470BFE /* markov_generator.cpp */,
91FBBADA1CB6D1B700470BFE /* markov_generator.hpp */,
91C55DA11CC078780040012E /* name_generator.hpp */,
91EF6C001C9E22E400E2A733 /* reference_counter.hpp */,
91EF6C011C9E22E400E2A733 /* sha1.cpp */,
91EF6C021C9E22E400E2A733 /* sha1.hpp */,
@ -3648,6 +3658,8 @@
B55999730EC62181008DD061 /* map.cpp */,
B55999720EC62181008DD061 /* map.hpp */,
91B6219C1B76735200B00E0F /* ptr.hpp */,
B55999C00EC62181008DD061 /* race.cpp */,
B55999BF0EC62181008DD061 /* race.hpp */,
B55999710EC62181008DD061 /* types.cpp */,
B55999700EC62181008DD061 /* types.hpp */,
B55999790EC62181008DD061 /* udisplay.cpp */,
@ -5016,7 +5028,6 @@
B52EE89E1213590500CFBDAB /* progress_bar.cpp in Sources */,
B5599ADC0EC62181008DD061 /* progressbar.cpp in Sources */,
62CC8E7817B9063E00C16B75 /* pump.cpp in Sources */,
B5599B0F0EC62181008DD061 /* race.cpp in Sources */,
EC84246718F30D9100FC1EEF /* random_new_deterministic.cpp in Sources */,
EC84246818F30D9100FC1EEF /* random_new_synced.cpp in Sources */,
EC84246918F30D9100FC1EEF /* random_new.cpp in Sources */,
@ -5186,6 +5197,9 @@
91DCA68D1C9066EC0030F8D0 /* unit_recruit.cpp in Sources */,
9122417C1CAAB7B7008B347F /* loadscreen.cpp in Sources */,
91FAC70A1C7FBC3400DAB2C3 /* lua_formula_bridge.cpp in Sources */,
91FBBADB1CB6D1B700470BFE /* markov_generator.cpp in Sources */,
91C55DA41CC078820040012E /* context_free_grammar_generator.cpp in Sources */,
916B7E941CC151FA00811097 /* race.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -980,9 +980,11 @@ set(wesnoth-main_SRC
units/helper.cpp
units/id.cpp
units/map.cpp
units/race.cpp
units/types.cpp
utils/sha1.cpp
utils/context_free_grammar_generator.cpp
utils/markov_generator.cpp
variable.cpp
variable_info.cpp
whiteboard/action.cpp
@ -1039,7 +1041,6 @@ set(libwesnoth-game_STAT_SRC
pathutils.cpp
preferences.cpp
preferences_display.cpp
race.cpp
quit_confirmation.cpp
reports.cpp
show_dialog.cpp

View file

@ -120,7 +120,6 @@ libwesnoth_sources = Split("""
pathutils.cpp
preferences.cpp
quit_confirmation.cpp
race.cpp
reports.cpp
show_dialog.cpp
sound_music_track.cpp
@ -552,11 +551,13 @@ wesnoth_sources = Split("""
units/helper.cpp
units/id.cpp
units/map.cpp
units/race.cpp
units/types.cpp
units/udisplay.cpp
units/unit.cpp
utils/sha1.cpp
utils/context_free_grammar_generator.cpp
utils/markov_generator.cpp
variable_info.cpp
variable.cpp
whiteboard/action.cpp

View file

@ -27,10 +27,11 @@
#include "default_map_generator_job.hpp"
#include "pathfind/pathfind.hpp"
#include "pathutils.hpp"
#include "race.hpp"
#include "util.hpp"
#include "wml_exception.hpp"
#include "formula/string_utils.hpp"
#include "utils/context_free_grammar_generator.hpp"
#include "utils/markov_generator.hpp"
#include <SDL.h>
#include "seed_rng.hpp"
@ -601,14 +602,14 @@ static map_location place_village(const t_translation::t_map& map,
return best_loc;
}
std::string default_map_generator_job::generate_name(const unit_race& name_generator, const std::string& id,
std::string default_map_generator_job::generate_name(boost::shared_ptr<name_generator>& name_generator, const std::string& id,
std::string* base_name, utils::string_map* additional_symbols)
{
const std::vector<std::string>& options = utils::split(string_table[id].str());
if(options.empty() == false) {
const size_t choice = rng_()%options.size();
LOG_NG << "calling name generator...\n";
const std::string& name = name_generator.generate_name(unit_race::MALE);
const std::string& name = name_generator->generate();
LOG_NG << "name generator returned '" << name << "'\n";
if(base_name != nullptr) {
*base_name = name;
@ -783,12 +784,19 @@ std::string default_map_generator_job::default_generate_map(size_t width, size_t
config naming = cfg.child_or_empty("naming");
// If the [naming] child is empty, we cannot provide good names.
std::map<map_location,std::string>* misc_labels = naming.empty() ? nullptr : labels;
// HACK: dummy names to satisfy unit_race requirements
naming["id"] = "village_naming";
naming["plural_name"] = "villages";
// Make a dummy race for generating names
const unit_race name_generator(naming);
boost::shared_ptr<name_generator> name_generator;
if(naming.has_attribute("name_generator")) {
name_generator.reset(new context_free_grammar_generator(naming["name_generator"]));
if(!name_generator->is_valid()) {
name_generator.reset();
}
}
config::attribute_value markov_list = naming.get_old_attribute("names", "male_names",
"[naming]male_names is deprecated, use names instead");
if(!markov_list.blank()) {
name_generator.reset(new markov_generator(utils::split(markov_list), naming["markov_chain_size"], 12));
}
std::vector<terrain_height_mapper> height_conversion;
@ -1279,11 +1287,20 @@ std::string default_map_generator_job::default_generate_map(size_t width, size_t
config naming_cfg = cfg.child_or_empty("village_naming");
// If the [village_naming] child is empty, we cannot provide good names.
std::map<map_location,std::string>* village_labels = naming_cfg.empty() ? nullptr : labels;
// HACK: dummy names to satisfy unit_race requirements
naming_cfg["id"] = "village_naming";
naming_cfg["plural_name"] = "villages";
const unit_race village_names_generator(naming_cfg);
// Specify "class" here because we also have a local variable with the same name
boost::shared_ptr<class name_generator> village_names_generator;
if(naming_cfg.has_attribute("name_generator")) {
village_names_generator.reset(new context_free_grammar_generator(naming["name_generator"]));
if(!village_names_generator->is_valid()) {
village_names_generator.reset();
}
}
config::attribute_value markov_list = naming_cfg.get_old_attribute("names", "male_names",
"[village_naming]male_names is deprecated, use names instead");
if(!markov_list.blank()) {
village_names_generator.reset(new markov_generator(utils::split(markov_list), naming["markov_chain_size"], 12));
}
// First we work out the size of the x and y distance between villages
const size_t tiles_per_village = ((width*height)/9)/nvillages;

View file

@ -24,12 +24,13 @@ class unit_race;
#include "map/location.hpp"
#include "terrain/translation.hpp"
#include "serialization/string_utils.hpp"
#include "utils/name_generator.hpp"
#include <boost/random.hpp>
#include <boost/cstdint.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <map>
class default_map_generator_job
{
public:
@ -59,7 +60,7 @@ private:
bool generate_lake(t_translation::t_map& terrain, int x, int y, int lake_fall_off, std::set<map_location>& locs_touched);
map_location random_point_at_side(size_t width, size_t height);
std::string generate_name(const unit_race& name_generator, const std::string& id,
std::string generate_name(boost::shared_ptr<name_generator>& name_generator, const std::string& id,
std::string* base_name=nullptr,
utils::string_map* additional_symbols=nullptr);

View file

@ -17,7 +17,7 @@
#include "gui/dialogs/dialog.hpp"
#include "gui/widgets/group.hpp"
#include "race.hpp"
#include "units/race.hpp"
#include <string>
#include <vector>

View file

@ -28,7 +28,7 @@
#include "utils/make_enum.hpp" // for operator<<
#include "map/map.hpp" // for gamemap
#include "marked-up_text.hpp" // for is_cjk_char, word_wrap_text
#include "race.hpp" // for unit_race, etc
#include "units/race.hpp" // for unit_race, etc
#include "resources.hpp" // for tod_manager, config_manager
#include "sdl/utils.hpp" // for surface
#include "serialization/string_utils.hpp" // for split, quoted_split, etc

View file

@ -20,7 +20,7 @@
#include "language.hpp" // for string_table, symbol_table
#include "log.hpp" // for LOG_STREAM, logger, etc
#include "movetype.hpp" // for movetype, movetype::effects, etc
#include "race.hpp" // for unit_race, etc
#include "units/race.hpp" // for unit_race, etc
#include "terrain/terrain.hpp" // for terrain_type
#include "terrain/translation.hpp" // for operator==, t_list, etc
#include "terrain/type_data.hpp" // for terrain_type_data, etc

View file

@ -104,6 +104,8 @@
#include "units/ptr.hpp" // for unit_const_ptr, unit_ptr
#include "units/types.hpp" // for unit_type_data, unit_types, etc
#include "util.hpp" // for lexical_cast
#include "utils/markov_generator.hpp"
#include "utils/context_free_grammar_generator.hpp"
#include "variable.hpp" // for vconfig, etc
#include "variable_info.hpp"
#include "wml_exception.hpp"
@ -4368,6 +4370,69 @@ int game_lua_kernel::intf_toggle_fog(lua_State *L, const bool clear)
return 0;
}
static int intf_name_generator(lua_State *L)
{
std::string type = luaL_checkstring(L, 1);
name_generator* gen = nullptr;
if(type == "markov" || type == "markov_chain") {
std::vector<std::string> input;
if(lua_istable(L, 2)) {
input = lua_check<std::vector<std::string>>(L, 2);
} else {
input = utils::parenthetical_split(luaL_checkstring(L, 2));
}
int chain_sz = luaL_optinteger(L, 3, 2);
int max_len = luaL_optinteger(L, 4, 12);
gen = new(lua_newuserdata(L, sizeof(markov_generator)))
markov_generator(input, chain_sz, max_len);
// Ensure the pointer didn't change when cast
assert(static_cast<void*>(gen) == dynamic_cast<markov_generator*>(gen));
} else if(type == "context_free" || type == "cfg" || type == "CFG") {
void* buf = lua_newuserdata(L, sizeof(context_free_grammar_generator));
if(lua_istable(L, 2)) {
std::map<std::string, std::vector<std::string>> data;
for(lua_pushnil(L); lua_next(L, 2); lua_pop(L, 1)) {
if(!lua_isstring(L, -2)) {
lua_pushstring(L, "CFG generator: invalid nonterminal name (must be a string)");
return lua_error(L);
}
if(lua_isstring(L, -1)) {
data[lua_tostring(L,-2)] = utils::split(lua_tostring(L,-1),'|');
} else if(lua_istable(L, -1)) {
data[lua_tostring(L,-2)] = lua_check<std::vector<std::string>>(L, -1);
} else {
lua_pushstring(L, "CFG generator: invalid noterminal value (must be a string or list of strings)");
return lua_error(L);
}
}
if(!data.empty()) {
gen = new(buf) context_free_grammar_generator(data);
}
} else {
gen = new(buf) context_free_grammar_generator(luaL_checkstring(L, 2));
}
if(gen) {
assert(static_cast<void*>(gen) == dynamic_cast<context_free_grammar_generator*>(gen));
}
} else {
return luaL_argerror(L, 1, "should be either 'markov_chain' or 'context_free'");
}
static const char*const generic_err = "error initializing name generator";
if(!gen) {
lua_pushstring(L, generic_err);
return lua_error(L);
}
// We set the metatable now, even if the generator is invalid, so that it
// will be properly collected if it was invalid.
luaL_getmetatable(L, "name generator");
lua_setmetatable(L, -2);
if(!gen->is_valid()) {
lua_pushstring(L, generic_err);
return lua_error(L);
}
return 1;
}
// END CALLBACK IMPLEMENTATION
game_board & game_lua_kernel::board() {
@ -4450,6 +4515,7 @@ game_lua_kernel::game_lua_kernel(CVideo * video, game_state & gs, play_controlle
{ "get_traits", &intf_get_traits },
{ "get_viewing_side", &intf_get_viewing_side },
{ "modify_ai", &intf_modify_ai },
{ "name_generator", &intf_name_generator },
{ "set_music", &intf_set_music },
{ "transform_unit", &intf_transform_unit },
{ "unit_ability", &intf_unit_ability },

View file

@ -14,9 +14,10 @@
#include "scripting/lua_race.hpp"
#include "race.hpp"
#include "units/race.hpp"
#include "scripting/lua_common.hpp"
#include "units/types.hpp"
#include "utils/name_generator.hpp"
#include <string>
@ -30,6 +31,7 @@
// Registry key
static const char * Race = "race";
static const char * Gen = "name generator";
/**
* Gets some data on a race (__index metamethod).
@ -71,10 +73,38 @@ static int impl_race_get(lua_State* L)
}
return 1;
}
if (strcmp(m, "male_name_gen") == 0) {
new(lua_newuserdata(L, sizeof(proxy_name_generator)))
proxy_name_generator(race.generator(unit_race::MALE));
luaL_getmetatable(L, Gen);
lua_setmetatable(L, -2);
return 1;
}
if (strcmp(m, "female_name_gen") == 0) {
new(lua_newuserdata(L, sizeof(proxy_name_generator)))
proxy_name_generator(race.generator(unit_race::FEMALE));
luaL_getmetatable(L, Gen);
lua_setmetatable(L, -2);
return 1;
}
return 0;
}
static int impl_name_generator_call(lua_State *L)
{
name_generator* gen = static_cast<name_generator*>(lua_touserdata(L, 1));
lua_pushstring(L, gen->generate().c_str());
return 1;
}
static int impl_name_generator_collect(lua_State *L)
{
name_generator* gen = static_cast<name_generator*>(lua_touserdata(L, 1));
gen->~name_generator();
return 0;
}
namespace lua_race {
std::string register_metatable(lua_State * L)
@ -89,6 +119,14 @@ namespace lua_race {
lua_pushstring(L, "race");
lua_setfield(L, -2, "__metatable");
luaL_newmetatable(L, Gen);
static luaL_Reg const generator[] = {
{ "__call", &impl_name_generator_call},
{ "__gc", &impl_name_generator_collect},
{ nullptr, nullptr}
};
luaL_setfuncs(L, generator, 0);
return "Adding getrace metatable...\n";
}

168
src/units/race.cpp Normal file
View file

@ -0,0 +1,168 @@
/*
Copyright (C) 2003 - 2016 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://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.
*/
/**
* @file
* Generate race-specific unit-names.
*/
#include "global.hpp"
#include "units/race.hpp"
#include "log.hpp"
#include "serialization/string_utils.hpp"
#include "serialization/unicode_cast.hpp"
#include "utils/markov_generator.hpp"
#include "utils/context_free_grammar_generator.hpp"
/// Dummy race used when a race is not yet known.
const unit_race unit_race::null_race;
/// Standard string id (not translatable) for FEMALE
const std::string unit_race::s_female("female");
/// Standard string id (not translatable) for MALE
const std::string unit_race::s_male("male");
static const config &empty_traits() {
static config cfg;
return cfg;
}
static const config &empty_topics() {
static config cfg;
return cfg;
}
unit_race::unit_race() :
cfg_(),
id_(),
plural_name_(),
description_(),
ntraits_(0),
traits_(empty_traits().child_range("trait")),
topics_(empty_topics().child_range("topic")),
global_traits_(true),
undead_variation_()
{
name_[MALE] = "";
name_[FEMALE] = "";
}
unit_race::unit_race(const config& cfg) :
cfg_(cfg),
id_(cfg["id"]),
plural_name_(cfg["plural_name"].t_str()),
description_(cfg["description"].t_str()),
ntraits_(cfg["num_traits"]),
traits_(cfg.child_range("trait")),
topics_(cfg.child_range("topic")),
global_traits_(!cfg["ignore_global_traits"].to_bool()),
undead_variation_(cfg["undead_variation"])
{
if (id_.empty()) {
lg::wml_error() << "[race] '" << cfg["name"] << "' is missing an id field.";
}
if (plural_name_.empty()) {
lg::wml_error() << "[race] '" << cfg["name"] << "' is missing a plural_name field.";
plural_name_ = (cfg["name"]);
}
// use "name" if "male_name" or "female_name" aren't available
name_[MALE] = cfg["male_name"];
if(name_[MALE].empty()) {
name_[MALE] = (cfg["name"]);
}
name_[FEMALE] = cfg["female_name"];
if(name_[FEMALE].empty()) {
name_[FEMALE] = (cfg["name"]);
}
config::attribute_value male_generator = cfg["male_name_generator"];
config::attribute_value female_generator = cfg["female_name_generator"];
if(male_generator.blank()) {
male_generator = cfg["name_generator"];
}
if(female_generator.blank()) {
female_generator = cfg["name_generator"];
}
if(!male_generator.blank()) {
name_generator_[MALE].reset(new context_free_grammar_generator(male_generator));
if(!name_generator_[MALE]->is_valid()) {
name_generator_[MALE].reset();
}
}
if(!female_generator.blank()) {
name_generator_[FEMALE].reset(new context_free_grammar_generator(female_generator));
if(!name_generator_[FEMALE]->is_valid()) {
name_generator_[FEMALE].reset();
}
}
int chain_size = cfg["markov_chain_size"].to_int(2);
if(!name_generator_[MALE]) {
name_generator_[MALE].reset(new markov_generator(utils::split(cfg["male_names"]), chain_size, 12));
}
if(!name_generator_[FEMALE]) {
name_generator_[FEMALE].reset(new markov_generator(utils::split(cfg["female_names"]), chain_size, 12));
}
}
std::string unit_race::generate_name(unit_race::GENDER gender) const
{
return name_generator_[gender]->generate();
}
const name_generator& unit_race::generator(unit_race::GENDER gender) const
{
return *name_generator_[gender];
}
bool unit_race::uses_global_traits() const
{
return global_traits_;
}
const config::const_child_itors &unit_race::additional_traits() const
{
return traits_;
}
const config::const_child_itors &unit_race::additional_topics() const
{
return topics_;
}
unsigned int unit_race::num_traits() const { return ntraits_; }
std::string const& gender_string(unit_race::GENDER gender) {
switch(gender) {
case unit_race::FEMALE:
return unit_race::s_female;
default:
case unit_race::MALE:
return unit_race::s_male;
}
}
unit_race::GENDER string_gender(const std::string& str, unit_race::GENDER def) {
if ( str == unit_race::s_male ) {
return unit_race::MALE;
} else if ( str == unit_race::s_female ) {
return unit_race::FEMALE;
}
return def;
}

View file

@ -16,10 +16,8 @@
#define RACE_HPP_INCLUDED
#include "config.hpp"
#include "serialization/unicode_types.hpp"
#include "utils/context_free_grammar_generator.hpp"
typedef std::map<ucs4::string, ucs4::string > markov_prefix_map;
#include "utils/name_generator.hpp"
#include <boost/smart_ptr/shared_ptr.hpp>
class unit_race
{
@ -38,6 +36,7 @@ public:
const t_string& description() const { return description_; }
std::string generate_name(GENDER gender) const;
const name_generator& generator(GENDER gender) const;
bool uses_global_traits() const;
@ -60,9 +59,7 @@ private:
t_string plural_name_;
t_string description_;
unsigned int ntraits_;
markov_prefix_map next_[NUM_GENDERS];
int chain_size_;
context_free_grammar_generator name_generator_[NUM_GENDERS];
boost::shared_ptr<name_generator> name_generator_[NUM_GENDERS];
config::const_child_itors traits_;
config::const_child_itors topics_;

View file

@ -18,7 +18,7 @@
#include "utils/make_enum.hpp"
#include "map/location.hpp"
#include "movetype.hpp"
#include "race.hpp"
#include "units/race.hpp"
#include "units/attack_type.hpp"
#include "util.hpp"

View file

@ -19,21 +19,20 @@
*/
#include "context_free_grammar_generator.hpp"
#include "../log.hpp"
#include "../random_new.hpp"
context_free_grammar_generator::context_free_grammar_generator() :
initialized_(false)
{
#include "log.hpp"
#include "random_new.hpp"
#include "serialization/string_utils.hpp"
}
#include <algorithm>
context_free_grammar_generator::~context_free_grammar_generator()
{
}
bool context_free_grammar_generator::constructFromString(const std::string &source) {
context_free_grammar_generator::context_free_grammar_generator(const std::string& source) :
initialized_(false)
{
const char* reading = source.c_str();
nonterminal* current = nullptr;
std::vector<std::string>* filled = nullptr;
@ -53,7 +52,7 @@ bool context_free_grammar_generator::constructFromString(const std::string &sour
} else if (*reading == '|') {
if (!filled || !current) {
lg::wml_error() << "[context_free_grammar_generator] Parsing error: misplaced | symbol";
return false;
return;
}
filled->push_back(buf);
current->possibilities_.push_back(std::vector<std::string>());
@ -69,7 +68,7 @@ bool context_free_grammar_generator::constructFromString(const std::string &sour
if (*reading == '{') {
if (!filled) {
lg::wml_error() << "[context_free_grammar_generator] Parsing error: misplaced { symbol";
return false;
return;
}
filled->push_back(buf);
buf.clear();
@ -77,7 +76,7 @@ bool context_free_grammar_generator::constructFromString(const std::string &sour
if (*reading == '}') {
if (!filled) {
lg::wml_error() << "[context_free_grammar_generator] Parsing error: misplaced } symbol";
return false;
return;
}
filled->push_back(buf);
buf.clear();
@ -88,7 +87,43 @@ bool context_free_grammar_generator::constructFromString(const std::string &sour
if (filled) filled->push_back(buf);
initialized_ = true;
return true;
}
context_free_grammar_generator::context_free_grammar_generator(const std::map<std::string, std::vector<std::string>>& source) :
initialized_(false)
{
for(auto rule : source) {
std::string key = rule.first; // Need to do this because utils::strip is mutating
key = utils::strip(key);
std::string buf;
for(std::string str : rule.second) {
nonterminals_[key].possibilities_.emplace_back();
std::vector<std::string>* filled = &nonterminals_[key].possibilities_.back();
// A little code duplication here...
for(char c : str) {
if (c == '{') {
if (!filled) {
lg::wml_error() << "[context_free_grammar_generator] Parsing error: misplaced { symbol";
return;
}
filled->push_back(buf);
buf.clear();
}
else if (c == '}') {
if (!filled) {
lg::wml_error() << "[context_free_grammar_generator] Parsing error: misplaced } symbol";
return;
}
filled->push_back('{' + utils::strip(buf));
buf.clear();
} else buf.push_back(c);
}
if(!buf.empty()) {
filled->push_back(buf);
}
}
}
initialized_ = true;
}
std::string context_free_grammar_generator::print_nonterminal(const std::string& name, uint32_t* seed, short seed_pos) const {

View file

@ -15,12 +15,14 @@
#ifndef CONTEXT_FREE_GRAMMAR_GENERATOR_INCLUDED
#define CONTEXT_FREE_GRAMMAR_GENERATOR_INCLUDED
#include "utils/name_generator.hpp"
#include <string>
#include <map>
#include <list>
#include <vector>
class context_free_grammar_generator
class context_free_grammar_generator : public name_generator
{
private:
@ -36,26 +38,27 @@ private:
static const short unsigned int seed_size = 20;
public:
/** Default constructor */
context_free_grammar_generator();
/** Initialisation
* @param source the definition of the context-free grammar to use
* @returns if the operation was successful
*/
bool constructFromString(const std::string& source);
context_free_grammar_generator(const std::string& source);
/** Initialisation
* @param source A map of nonterminals to lists of possibilities
*/
context_free_grammar_generator(const std::map<std::string, std::vector<std::string>>& source);
/** Generates a possible word in the grammar set before
* @returns the word
* @return the word
*/
std::string generate() const;
std::string generate() const override;
~context_free_grammar_generator();
/** Checks if the object is initialized
* @returns if it is initialized
* @return if it is initialized
*/
bool is_initialized() const {return initialized_; }
bool is_valid() const override {return initialized_; }
};
#endif

View file

@ -17,33 +17,11 @@
* Generate race-specific unit-names.
*/
#include "global.hpp"
#include "markov_generator.hpp"
#include "race.hpp"
#include "log.hpp"
#include "serialization/string_utils.hpp"
#include "serialization/unicode_cast.hpp"
#include "random_new.hpp"
/// Dummy race used when a race is not yet known.
const unit_race unit_race::null_race;
/// Standard string id (not translatable) for FEMALE
const std::string unit_race::s_female("female");
/// Standard string id (not translatable) for MALE
const std::string unit_race::s_male("male");
static const config &empty_traits() {
static config cfg;
return cfg;
}
static const config &empty_topics() {
static config cfg;
return cfg;
}
static void add_prefixes(const ucs4::string& str, size_t length, markov_prefix_map& res)
{
for(size_t i = 0; i <= str.size(); ++i) {
@ -145,118 +123,15 @@ static ucs4::string markov_generate_name(const markov_prefix_map& prefixes,
return originalRes;
}
unit_race::unit_race() :
cfg_(),
id_(),
plural_name_(),
description_(),
ntraits_(0),
chain_size_(0),
traits_(empty_traits().child_range("trait")),
topics_(empty_topics().child_range("topic")),
global_traits_(true),
undead_variation_()
markov_generator::markov_generator(const std::vector<std::string>& items, size_t chain_size, size_t max_len)
: prefixes_(markov_prefixes(items, chain_size))
, chain_size_(chain_size)
, max_len_(max_len)
{
name_[MALE] = "";
name_[FEMALE] = "";
}
unit_race::unit_race(const config& cfg) :
cfg_(cfg),
id_(cfg["id"]),
plural_name_(cfg["plural_name"].t_str()),
description_(cfg["description"].t_str()),
ntraits_(cfg["num_traits"]),
chain_size_(cfg["markov_chain_size"]),
traits_(cfg.child_range("trait")),
topics_(cfg.child_range("topic")),
global_traits_(!cfg["ignore_global_traits"].to_bool()),
undead_variation_(cfg["undead_variation"])
std::string markov_generator::generate() const
{
if (id_.empty()) {
lg::wml_error() << "[race] '" << cfg["name"] << "' is missing an id field.";
}
if (plural_name_.empty()) {
lg::wml_error() << "[race] '" << cfg["name"] << "' is missing a plural_name field.";
plural_name_ = (cfg["name"]);
}
// use "name" if "male_name" or "female_name" aren't available
name_[MALE] = cfg["male_name"];
if(name_[MALE].empty()) {
name_[MALE] = (cfg["name"]);
}
name_[FEMALE] = cfg["female_name"];
if(name_[FEMALE].empty()) {
name_[FEMALE] = (cfg["name"]);
}
if (cfg.has_attribute("male_name_generator")) {
name_generator_[MALE].constructFromString(cfg["male_name_generator"]);
}
if (cfg.has_attribute("female_name_generator")) {
name_generator_[FEMALE].constructFromString(cfg["female_name_generator"]);
}
if (cfg.has_attribute("name_generator")) {
if (!name_generator_[MALE].is_initialized()) {
name_generator_[MALE].constructFromString(cfg["name_generator"]);
}
if (!name_generator_[FEMALE].is_initialized()) {
name_generator_[FEMALE].constructFromString(cfg["name_generator"]);
}
}
if(chain_size_ <= 0)
chain_size_ = 2;
//std::vector<std::string> names = ;
next_[MALE] = markov_prefixes(utils::split(cfg["male_names"]), chain_size_);
next_[FEMALE] = markov_prefixes(utils::split(cfg["female_names"]), chain_size_);
}
std::string unit_race::generate_name(
unit_race::GENDER gender) const
{
if (name_generator_[gender].is_initialized()) {
return name_generator_[gender].generate();
}
return unicode_cast<utf8::string>(
markov_generate_name(next_[gender], chain_size_, 12));
}
bool unit_race::uses_global_traits() const
{
return global_traits_;
}
const config::const_child_itors &unit_race::additional_traits() const
{
return traits_;
}
const config::const_child_itors &unit_race::additional_topics() const
{
return topics_;
}
unsigned int unit_race::num_traits() const { return ntraits_; }
std::string const& gender_string(unit_race::GENDER gender) {
switch(gender) {
case unit_race::FEMALE:
return unit_race::s_female;
default:
case unit_race::MALE:
return unit_race::s_male;
}
}
unit_race::GENDER string_gender(const std::string& str, unit_race::GENDER def) {
if ( str == unit_race::s_male ) {
return unit_race::MALE;
} else if ( str == unit_race::s_female ) {
return unit_race::FEMALE;
}
return def;
ucs4::string name = markov_generate_name(prefixes_, chain_size_, max_len_);
return unicode_cast<utf8::string>(name);
}

View file

@ -0,0 +1,32 @@
/*
Copyright (C) 2003 - 2016 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://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.
*/
#ifndef MARKOV_GENERATOR_HPP_INCLUDED
#define MARKOV_GENERATOR_HPP_INCLUDED
#include "serialization/unicode_types.hpp"
#include "utils/name_generator.hpp"
#include <map>
typedef std::map<ucs4::string, ucs4::string> markov_prefix_map;
class markov_generator : public name_generator {
markov_prefix_map prefixes_;
size_t chain_size_, max_len_;
public:
markov_generator(const std::vector<std::string>& items, size_t chain_size, size_t max_len);
std::string generate() const override;
};
#endif

View file

@ -0,0 +1,35 @@
/*
Copyright (C) 2003 - 2016 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://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.
*/
#ifndef NAME_GENERATOR_HPP_INCLUDED
#define NAME_GENERATOR_HPP_INCLUDED
#include <string>
class name_generator {
public:
virtual std::string generate() const = 0;
virtual bool is_valid() const {return true;}
virtual ~name_generator() {}
};
class proxy_name_generator : public name_generator {
const name_generator& base;
public:
proxy_name_generator(const name_generator& b) : base(b) {}
std::string generate() const override {return base.generate();}
bool is_valid() const override {return base.is_valid();}
};
#endif