Skip to content

Commit

Permalink
Merge pull request #644 from Dugy/master
Browse files Browse the repository at this point in the history
Unit name generator using context-free grammar
  • Loading branch information
CelticMinstrel committed Apr 11, 2016
2 parents 37deb40 + 9e35e4d commit a9dbbe9
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 3 deletions.
3 changes: 3 additions & 0 deletions changelog
Expand Up @@ -32,6 +32,9 @@ Version 1.13.4+dev:
uses full weapon filter.
* lua_function=var.member now works in SUF; however, 'var' still needs to
be a global variable.
* Added new keys name_generator, male_name_generator and female_name_generator
for the [race] tag to declare a context-free grammar to describe how names
are derived
* AiWML:
* Simplified aspect syntax which works for all aspects, present and future:
* All aspects with simple values can be specified as key=value
Expand Down
3 changes: 3 additions & 0 deletions data/core/about.cfg
Expand Up @@ -1058,6 +1058,9 @@
name = "Doug Rosvick (dlr365)"
wikiuser = "dlr365"
[/entry]
[entry]
name= "Dugi"
[/entry]
[entry]
name = "Duthlet"
[/entry]
Expand Down
166 changes: 165 additions & 1 deletion data/core/macros/names.cfg

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/CMakeLists.txt
Expand Up @@ -982,6 +982,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
Expand Down
1 change: 1 addition & 0 deletions src/SConscript
Expand Up @@ -556,6 +556,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
Expand Down
18 changes: 18 additions & 0 deletions src/race.cpp
Expand Up @@ -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;

Expand All @@ -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));
}
Expand Down
4 changes: 2 additions & 2 deletions src/race.hpp
Expand Up @@ -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;

Expand Down Expand Up @@ -63,6 +62,7 @@ class unit_race
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_;
Expand Down
123 changes: 123 additions & 0 deletions src/utils/context_free_grammar_generator.cpp
@@ -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);
}
61 changes: 61 additions & 0 deletions src/utils/context_free_grammar_generator.hpp
@@ -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

0 comments on commit a9dbbe9

Please sign in to comment.