The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
markov_generator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
3 by David White <dave@whitevine.net>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16/**
17 * @file
18 * Generate race-specific unit-names.
19 */
20
22
24#include "random.hpp"
25#include "utils/general.hpp"
26
27static void add_prefixes(const std::u32string& str, std::size_t length, markov_prefix_map& res)
28{
29 for(std::size_t i = 0; i <= str.size(); ++i) {
30 const std::size_t start = i > length ? i - length : 0;
31 const std::u32string key(str.begin() + start, str.begin() + i);
32 const char32_t c = i != str.size() ? str[i] : 0;
33 res[key].push_back(c);
34 }
35}
36
37static markov_prefix_map markov_prefixes(const std::vector<std::string>& items, std::size_t length)
38{
40
41 for(std::vector<std::string>::const_iterator i = items.begin(); i != items.end(); ++i) {
42 add_prefixes(unicode_cast<std::u32string>(*i),length,res);
43 }
44
45 return res;
46}
47
48static std::u32string markov_generate_name(const markov_prefix_map& prefixes,
49 std::size_t chain_size, std::size_t max_len)
50{
51 if(prefixes.empty() || chain_size == 0) {
52 return std::u32string();
53 }
54
55 std::u32string prefix, res;
56
57 // Since this function is called in the name description in a MP game it
58 // uses the local locale. The locale between players can be different and
59 // thus the markov_prefix_map can be different. This resulted in
60 // get_random() getting called a different number of times for the
61 // generation in different locales (due to the bail out at 'if(c == 0)').
62 //
63 // This causes a problem since the random state is no longer in sync. The
64 // following calls to get_random() return different results, which caused
65 // traits to be different. To avoid that problem we call get_random()
66 // the maximum number of times and store the result in a lookup table.
67 std::vector<int> random(max_len);
68 std::size_t j = 0;
69 for(; j < max_len; ++j) {
71 }
72
73 j = 0;
74 while(res.size() < max_len) {
75 const markov_prefix_map::const_iterator i = prefixes.find(prefix);
76 if(i == prefixes.end() || i->second.empty()) {
77 return res;
78 }
79
80 const char32_t c = i->second[random[j++]%i->second.size()];
81 if(c == 0) {
82 return res;
83 }
84
85 res.resize(res.size()+1);
86 res.back() = c;
87 prefix.resize(prefix.size()+1);
88 prefix.back() = c;
89 while(prefix.size() > chain_size) {
90 prefix.erase(prefix.begin());
91 }
92 }
93
94 // Getting here means that the maximum length was reached when
95 // generating the name, hence the ending of the name has to be
96 // made valid. Otherwise weird names like UnĂ¡rierini- and
97 // Thramboril-G may occur.
98
99 // Strip characters from the end until the last prefix of the
100 // name has end-of-string as a possible next character in the
101 // markov prefix map. If no valid ending is found, use the
102 // originally generated name.
103 std::u32string originalRes = res;
104 while(!res.empty()) {
105 const int prefixLen = chain_size < res.size() ? chain_size : res.size();
106 prefix = std::u32string(res.end() - prefixLen, res.end());
107
108 const markov_prefix_map::const_iterator i = prefixes.find(prefix);
109 if (i == prefixes.end() || i->second.empty()) {
110 return res;
111 }
112 if (utils::contains(i->second, static_cast<char32_t>(0))) {
113 // This ending is valid.
114 return res;
115 }
116 // The current ending is invalid, remove the last character
117 // and retry.
118 res.pop_back();
119 }
120 // No valid ending at all could be found. This generally should
121 // not happen, unless the chain length is very long or the
122 // maximum length is very small. Return the originally generated
123 // name, it's not much we can do about it.
124 return originalRes;
125}
126
127markov_generator::markov_generator(const std::vector<std::string>& items, std::size_t chain_size, std::size_t max_len)
128 : prefixes_(markov_prefixes(items, chain_size))
129 , chain_size_(chain_size)
130 , max_len_(max_len)
131{
132}
133
134std::string markov_generator::generate() const
135{
136 std::u32string name = markov_generate_name(prefixes_, chain_size_, max_len_);
137 return unicode_cast<std::string>(name);
138}
markov_generator(const std::vector< std::string > &items, std::size_t chain_size, std::size_t max_len)
markov_prefix_map prefixes_
std::string generate() const override
uint32_t next_random()
Provides the next random draw.
Definition random.cpp:84
static markov_prefix_map markov_prefixes(const std::vector< std::string > &items, std::size_t length)
static void add_prefixes(const std::u32string &str, std::size_t length, markov_prefix_map &res)
static std::u32string markov_generate_name(const markov_prefix_map &prefixes, std::size_t chain_size, std::size_t max_len)
std::map< std::u32string, std::u32string > markov_prefix_map
rng * generator
This generator is automatically synced during synced context.
Definition random.cpp:60
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
#define i
#define c