race.cpp

Go to the documentation of this file.
00001 /* $Id: race.cpp 25481 2008-04-02 22:10:35Z jhinrichs $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 //! @file race.cpp
00016 //! Generate race-specific unit-names.
00017 
00018 #include "global.hpp"
00019 
00020 #include "race.hpp"
00021 #include "random.hpp"
00022 #include "serialization/string_utils.hpp"
00023 
00024 #include <cstdlib>
00025 
00026 namespace {
00027 
00028 config::child_list empty_traits;
00029 
00030 }
00031 
00032 static void add_prefixes(const wide_string& str, size_t length, markov_prefix_map& res)
00033 {
00034     for(size_t i = 0; i <= str.size(); ++i) {
00035         const size_t start = i > length ? i - length : 0;
00036         const wide_string key(str.begin() + start, str.begin() + i);
00037         const wchar_t c = i != str.size() ? str[i] : 0;
00038         res[key].push_back(c);
00039     }
00040 }
00041 
00042 static markov_prefix_map markov_prefixes(const std::vector<std::string>& items, size_t length)
00043 {
00044     markov_prefix_map res;
00045 
00046     for(std::vector<std::string>::const_iterator i = items.begin(); i != items.end(); ++i) {
00047         add_prefixes(utils::string_to_wstring(*i),length,res);
00048     }
00049 
00050     return res;
00051 }
00052 
00053 static wide_string markov_generate_name(const markov_prefix_map& prefixes,
00054     size_t chain_size, size_t max_len, simple_rng* rng)
00055 {
00056     if(chain_size == 0)
00057         return wide_string();
00058 
00059     wide_string prefix, res;
00060 
00061     // Since this function is called in the name description in a MP game it
00062     // uses the local locale. The locale between players can be different and
00063     // thus the markov_prefix_map can be different. This resulted in
00064     // get_random() getting called a different number of times for the
00065     // generation in different locales (due to the bail out at 'if(c == 0)').
00066     //
00067     // This causes a problem since the random state is no longer in sync. The
00068     // following calls to get_random() return different results, which caused
00069     // traits to be different. To avoid that problem we call get_random()
00070     // the maximum number of times and store the result in a lookup table.
00071     std::vector<int> random(max_len);
00072     size_t j = 0;
00073     for(; j < max_len; ++j) {
00074         random[j] = rng ? rng->get_random() : get_random();
00075     }
00076 
00077     j = 0;
00078     while(res.size() < max_len) {
00079         const markov_prefix_map::const_iterator i = prefixes.find(prefix);
00080         if(i == prefixes.end() || i->second.empty()) {
00081             return res;
00082         }
00083 
00084         const wchar_t c = i->second[random[j++]%i->second.size()];
00085         if(c == 0) {
00086             return res;
00087         }
00088 
00089         res.resize(res.size()+1);
00090         res[res.size()-1] = c;
00091         prefix.resize(prefix.size()+1);
00092         prefix[prefix.size()-1] = c;
00093         while(prefix.size() > chain_size) {
00094             prefix.erase(prefix.begin());
00095         }
00096     }
00097 
00098     // Getting here means that the maximum length was reached when
00099     // generating the name, hence the ending of the name has to be
00100     // made valid. Otherwise weird names like UnĂ¡rierini- and
00101     // Thramboril-G may occur.
00102 
00103     // Strip characters from the end until the last prefix of the
00104     // name has end-of-string as a possible next character in the
00105     // markov prefix map. If no valid ending is found, use the
00106     // originally generated name.
00107     wide_string originalRes = res;
00108     int prefixLen;
00109     while (res.size() > 0) {
00110         prefixLen = chain_size < res.size() ? chain_size : res.size();
00111         prefix = wide_string(res.end() - prefixLen, res.end());
00112 
00113         const markov_prefix_map::const_iterator i = prefixes.find(prefix);
00114         if (i == prefixes.end() || i->second.empty()) {
00115             return res;
00116         }
00117         if (std::find(i->second.begin(), i->second.end(), static_cast<wchar_t>(0))
00118                 != i->second.end()) {
00119             // This ending is valid.
00120             return res;
00121         }
00122         // The current ending is invalid, remove the last character
00123         // and retry.
00124         res.pop_back();
00125     }
00126     // No valid ending at all could be found. This generally should
00127     // not happen, unless the chain length is very long or the
00128     // maximum length is very small. Return the originally generated
00129     // name, it's not much we can do about it.
00130     return originalRes;
00131 }
00132 
00133 unit_race::unit_race() :
00134         id_(),
00135         plural_name_(),
00136         description_(),
00137         ntraits_(0),
00138         chain_size_(0),
00139         traits_(&empty_traits),
00140         global_traits_(true)
00141 {
00142         name_[MALE] = "";
00143         name_[FEMALE] = "";
00144 }
00145 
00146 unit_race::unit_race(const config& cfg) :
00147         id_(cfg["id"]),
00148         plural_name_(cfg["plural_name"]),
00149         description_(cfg["description"]),
00150         ntraits_(atoi(cfg["num_traits"].c_str())),
00151         chain_size_(atoi(cfg["markov_chain_size"].c_str())),
00152         traits_(&cfg.get_children("trait")),
00153         global_traits_(!utils::string_bool(cfg["ignore_global_traits"]))
00154 
00155 {
00156     //! @todo FIXME remove support after branching 1.4.
00157     //! 2 versions with lg::wml_error
00158     //! VALIDATE after that
00159     if(id_.empty()) {
00160         // This code is only for compatibility with old race defs.
00161         id_ = (cfg["name"]);
00162     }
00163     if(plural_name_.empty()) {
00164         // This code is only for compatibility with old race defs.
00165         plural_name_ = (cfg["name"]);
00166     }
00167     // use "name" if "male_name" or "female_name" aren't available
00168     name_[MALE] = cfg["male_name"];
00169     if(name_[MALE].empty()) {
00170         name_[MALE] = (cfg["name"]);
00171     }
00172     name_[FEMALE] = cfg["female_name"];
00173     if(name_[FEMALE].empty()) {
00174         name_[FEMALE] = (cfg["name"]);
00175     }
00176 
00177     if(chain_size_ <= 0)
00178         chain_size_ = 2;
00179 
00180     //std::vector<std::string> names = ;
00181     next_[MALE] = markov_prefixes(utils::split(cfg["male_names"]), chain_size_);
00182     next_[FEMALE] = markov_prefixes(utils::split(cfg["female_names"]), chain_size_);
00183 }
00184 
00185 std::string unit_race::generate_name(
00186         unit_race::GENDER gender, simple_rng* rng) const
00187 {
00188     return utils::wstring_to_string(
00189         markov_generate_name(next_[gender], chain_size_, 12, rng));
00190 }
00191 
00192 bool unit_race::uses_global_traits() const
00193 {
00194     return global_traits_;
00195 }
00196 
00197 const config::child_list& unit_race::additional_traits() const
00198 {
00199     return *traits_;
00200 }
00201 
00202 unsigned int unit_race::num_traits() const { return ntraits_; }
00203 
00204 std::string const& gender_string(unit_race::GENDER gender) {
00205     static const std::string female_string = "female";
00206     static const std::string male_string = "male";
00207     switch(gender) {
00208     case unit_race::FEMALE:
00209         return female_string;
00210     default:
00211     case unit_race::MALE:
00212         return male_string;
00213     }
00214 }
00215 
00216 unit_race::GENDER string_gender(const std::string& str, unit_race::GENDER def) {
00217     if(str == gender_string(unit_race::MALE)) {
00218         return unit_race::MALE;
00219     } else if(str == gender_string(unit_race::FEMALE)) {
00220         return unit_race::FEMALE;
00221     }
00222     return def;
00223 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs