Added a new way to generate unit names, using rules of a context-free grammar.

This commit is contained in:
Dugy 2016-04-07 00:34:09 +02:00
parent 83dd965f9e
commit f84e8f4795
7 changed files with 371 additions and 3 deletions

File diff suppressed because one or more lines are too long

View file

@ -980,6 +980,7 @@ set(wesnoth-main_SRC
units/map.cpp
units/types.cpp
utils/sha1.cpp
utils/context_free_grammar_generator.cpp
variable.cpp
variable_info.cpp
whiteboard/action.cpp

View file

@ -555,6 +555,7 @@ wesnoth_sources = Split("""
units/udisplay.cpp
units/unit.cpp
utils/sha1.cpp
utils/context_free_grammar_generator.cpp
variable_info.cpp
variable.cpp
whiteboard/action.cpp

View file

@ -191,6 +191,21 @@ unit_race::unit_race(const config& cfg) :
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;
@ -202,6 +217,9 @@ unit_race::unit_race(const config& cfg) :
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));
}

View file

@ -17,8 +17,7 @@
#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;
@ -63,6 +62,7 @@ private:
unsigned int ntraits_;
markov_prefix_map next_[NUM_GENDERS];
int chain_size_;
context_free_grammar_generator name_generator_[NUM_GENDERS];
config::const_child_itors traits_;
config::const_child_itors topics_;

View file

@ -0,0 +1,123 @@
/*
Copyright (C) 2016 by Ján Dugáček
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
* Algorithm to generate names using a context-free grammar, which allows more control
* than the usual Markov chain generator
*/
#include "context_free_grammar_generator.hpp"
#include "../log.hpp"
#include "../random_new.hpp"
context_free_grammar_generator::context_free_grammar_generator() :
initialized_(false)
{
}
context_free_grammar_generator::~context_free_grammar_generator()
{
}
bool context_free_grammar_generator::constructFromString(const std::string &source) {
const char* reading = source.c_str();
nonterminal* current = nullptr;
std::vector<std::string>* filled = nullptr;
std::string buf;
while (*reading != 0) {
if (*reading == '=') {
current = &nonterminals_[buf];
current->possibilities_.push_back(std::vector<std::string>());
filled = &current->possibilities_.back();
buf.clear();
} else if (*reading == '\n') {
if (filled) filled->push_back(buf);
filled = nullptr;
current = nullptr;
buf.clear();
} else if (*reading == '|') {
if (!filled || !current) {
lg::wml_error() << "[context_free_grammar_generator] Parsing error: misplaced | symbol";
return false;
}
filled->push_back(buf);
current->possibilities_.push_back(std::vector<std::string>());
filled = &current->possibilities_.back();
buf.clear();
} else if (*reading == '\\' && reading[1] == 'n') {
reading++;
buf.push_back('\n');
} else if (*reading == '\\' && reading[1] == 't') {
reading++;
buf.push_back('\t');
} else {
if (*reading == '{') {
if (!filled) {
lg::wml_error() << "[context_free_grammar_generator] Parsing error: misplaced { symbol";
return false;
}
filled->push_back(buf);
buf.clear();
}
if (*reading == '}') {
if (!filled) {
lg::wml_error() << "[context_free_grammar_generator] Parsing error: misplaced } symbol";
return false;
}
filled->push_back(buf);
buf.clear();
} else buf.push_back(*reading);
}
reading++;
}
if (filled) filled->push_back(buf);
initialized_ = true;
return true;
}
std::string context_free_grammar_generator::print_nonterminal(const std::string& name, uint32_t* seed, short seed_pos) const {
std::string result;
std::map<std::string, nonterminal>::const_iterator found = nonterminals_.find(name);
if (found == nonterminals_.end()) {
lg::wml_error() << "[context_free_grammar_generator] Warning: needed nonterminal " << name << " not defined";
return "!" + name;
}
const context_free_grammar_generator::nonterminal& got = found->second;
unsigned int picked = seed[seed_pos++] % got.possibilities_.size();
if (seed_pos >= seed_size) seed_pos = 0;
if (picked == got.last_) {
picked = seed[seed_pos++] % got.possibilities_.size();
if (seed_pos >= seed_size) seed_pos = 0;
}
const_cast<unsigned int&>(got.last_) = picked; /* The variable last_ can change, the rest must stay const */
const std::vector<std::string>& used = got.possibilities_[picked];
for (unsigned int i = 0; i < used.size(); i++) {
if (used[i][0] == '{') result += print_nonterminal(used[i].substr(1), seed, seed_pos);
else result += used[i];
}
return result;
}
std::string context_free_grammar_generator::generate() const {
uint32_t seed[seed_size];
for (unsigned short int i = 0; i < seed_size; i++) {
seed[i] = random_new::generator->next_random();
}
return print_nonterminal("main", seed, 0);
}

View file

@ -0,0 +1,61 @@
/*
Copyright (C) 2016 by Ján Dugáček
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 CONTEXT_FREE_GRAMMAR_GENERATOR_INCLUDED
#define CONTEXT_FREE_GRAMMAR_GENERATOR_INCLUDED
#include <string>
#include <map>
#include <list>
#include <vector>
class context_free_grammar_generator
{
private:
struct nonterminal {
nonterminal() : last_(1) {}
std::vector<std::vector<std::string> > possibilities_;
unsigned int last_;
};
std::map<std::string, nonterminal> nonterminals_;
bool initialized_;
std::string print_nonterminal(const std::string& name, uint32_t* seed, short int seed_pos) const;
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);
/** Generates a possible word in the grammar set before
* @returns the word
*/
std::string generate() const;
~context_free_grammar_generator();
/** Checks if the object is initialized
* @returns if it is initialized
*/
bool is_initialized() const {return initialized_; }
};
#endif