Merge pull request #322 from cbeck88/boost_rng_squash

use boost mersenne twister rng, add boost::random dependency
This commit is contained in:
Chris Beck 2014-11-03 18:22:51 -05:00
commit 55078504a9
29 changed files with 812 additions and 57 deletions

View file

@ -32,7 +32,7 @@ before_install:
install:
- sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu/ saucy main universe"
- time sudo apt-get update -qq
- time sudo apt-get install -qq libboost-filesystem-dev libboost-iostreams-dev libboost-program-options-dev libboost-regex-dev libboost-system-dev libboost-test-dev libboost-locale-dev libcairo2-dev libfribidi-dev libpango1.0-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-net1.2-dev libsdl-ttf2.0-dev gdb
- time sudo apt-get install -qq libboost-filesystem-dev libboost-iostreams-dev libboost-random-dev libboost-program-options-dev libboost-regex-dev libboost-system-dev libboost-test-dev libboost-locale-dev libcairo2-dev libfribidi-dev libpango1.0-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-net1.2-dev libsdl-ttf2.0-dev gdb
- if [ "$CXX" = "g++" ]; then time sudo apt-get -qq install g++-4.8; fi
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8"; fi
- if [ "$CHECK_UTF8" = true ]; then time sudo apt-get install -qq moreutils; fi

View file

@ -22,6 +22,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
find_package(SDL 1.2.7 REQUIRED)
find_package(Boost 1.36 REQUIRED COMPONENTS iostreams program_options regex system)
find_package(Boost 1.40 REQUIRED COMPONENTS random)
# no, gettext executables are not required when NLS is deactivated
find_package(Gettext)

View file

@ -376,6 +376,7 @@ if env["prereqs"]:
conf.CheckBoost("iostreams", require_version = "1.34.1") & \
conf.CheckBoostIostreamsGZip() & \
conf.CheckBoostIostreamsBZip2() & \
conf.CheckBoost("random",require_version = "1.40.0") & \
conf.CheckBoost("smart_ptr", header_only = True) & \
conf.CheckBoost("system") & \
((not env["boostfilesystem"]) or

View file

@ -229,6 +229,8 @@ set(libwesnoth-core_STAT_SRC
hash.cpp
log.cpp
md5.cpp
mt_rng.cpp
seed_rng.cpp
thread.cpp
tstring.cpp
util.cpp
@ -1380,8 +1382,9 @@ if(ENABLE_TESTS)
tests/test_lexical_cast.cpp
tests/test_map_location.cpp
tests/test_make_enum.cpp
tests/test_mp_connect.cpp
tests/test_network_worker.cpp
tests/test_rng.cpp
tests/test_mp_connect.cpp
tests/test_sdl_utils.cpp
tests/test_serialization.cpp
tests/test_team.cpp

View file

@ -21,6 +21,8 @@ libwesnoth_core_sources = Split("""
map.cpp
map_location.cpp
md5.cpp
mt_rng.cpp
seed_rng.cpp
network.cpp
thread.cpp
tstring.cpp
@ -701,6 +703,7 @@ test_sources = Split("""
tests/test_lexical_cast.cpp
tests/test_map_location.cpp
tests/test_make_enum.cpp
tests/test_rng.cpp
tests/test_mp_connect.cpp
tests/test_network_worker.cpp
tests/test_sdl_utils.cpp

View file

@ -890,8 +890,8 @@ namespace {
int &abs_n = *(attacker_turn ? &abs_n_attack_ : &abs_n_defend_);
bool &update_fog = *(attacker_turn ? &update_def_fog_ : &update_att_fog_);
int ran_num = random_new::generator->next_random();
bool hits = (ran_num % 100) < attacker.cth_;
int ran_num = random_new::generator->get_random_int(0,99);
bool hits = (ran_num < attacker.cth_);
int damage = 0;
if (hits) {
@ -1421,7 +1421,7 @@ namespace
//we are in the situation, that the unit is owned by a human, but he's not allowed to do this decision.
//becasue it's a mp game and it's not his turn.
//note that it doens't matter wether we call random_new::generator->next_random() or rand().
res = random_new::generator->next_random() % nb_options_;
res = random_new::generator->get_random_int(0, nb_options_-1);
}
LOG_NG << "unit at position " << loc_ << "choose advancement number " << res << "\n";
config retv;

View file

@ -269,7 +269,7 @@ void carryover_info::transfer_to(config& level){
config::attribute_value & seed_value = level["random_seed"];
if ( seed_value.empty() ) {
seed_value = rng_.get_random_seed();
seed_value = rng_.get_random_seed_str();
level["random_calls"] = rng_.get_random_calls();
}
@ -293,7 +293,7 @@ const config carryover_info::to_config()
config& end_level = cfg.add_child("end_level_data");
end_level_.write(end_level);
cfg["random_seed"] = rng_.get_random_seed();
cfg["random_seed"] = rng_.get_random_seed_str();
cfg["random_calls"] = rng_.get_random_calls();
cfg.add_child("variables", variables_);

View file

@ -11,7 +11,7 @@ class game_data;
#include "config.hpp"
#include "game_end_exceptions.hpp"
#include "simple_rng.hpp"
#include "mt_rng.hpp"
#include "game_events/wmi_container.hpp"
class carryover{
@ -87,8 +87,8 @@ public:
game_events::wmi_container& get_wml_menu_items() { return wml_menu_items_; }
const rand_rng::simple_rng& rng() const { return rng_; }
rand_rng::simple_rng& rng() { return rng_; }
const rand_rng::mt_rng& rng() const { return rng_; }
rand_rng::mt_rng& rng() { return rng_; }
const end_level_data& get_end_level() const;
@ -101,7 +101,7 @@ private:
std::vector<carryover> carryover_sides_;
end_level_data end_level_;
config variables_;
rand_rng::simple_rng rng_;
rand_rng::mt_rng rng_;
game_events::wmi_container wml_menu_items_;
std::string next_scenario_; /**< the scenario coming next (for campaigns) */
int next_underlying_unit_id_;

View file

@ -179,7 +179,7 @@ void game_data::write_snapshot(config& cfg) const {
cfg["can_end_turn"] = can_end_turn_;
cfg["random_seed"] = rng_.get_random_seed();
cfg["random_seed"] = rng_.get_random_seed_str();
cfg["random_calls"] = rng_.get_random_calls();
cfg.add_child("variables", variables_);
@ -191,7 +191,7 @@ void game_data::write_config(config_writer& out){
out.write_key_val("scenario", scenario_);
out.write_key_val("next_scenario", next_scenario_);
out.write_key_val("random_seed", lexical_cast<std::string>(rng_.get_random_seed()));
out.write_key_val("random_seed", rng_.get_random_seed_str());
out.write_key_val("random_calls", lexical_cast<std::string>(rng_.get_random_calls()));
out.write_child("variables", variables_);

View file

@ -21,7 +21,7 @@
#include "game_end_exceptions.hpp"
#include "game_events/wmi_container.hpp"
#include "map_location.hpp"
#include "simple_rng.hpp"
#include "mt_rng.hpp"
#include "variable_info.hpp"
#include <boost/shared_ptr.hpp>
@ -81,8 +81,8 @@ public:
game_events::wmi_container& get_wml_menu_items() { return wml_menu_items_; }
const rand_rng::simple_rng& rng() const { return rng_; }
rand_rng::simple_rng& rng() { return rng_; }
const rand_rng::mt_rng& rng() const { return rng_; }
rand_rng::mt_rng& rng() { return rng_; }
enum PHASE {
INITIAL,
@ -130,7 +130,7 @@ private:
}
game_events::wmi_container wml_menu_items_;
rand_rng::simple_rng rng_;
rand_rng::mt_rng rng_;
config variables_;
PHASE phase_;
bool can_end_turn_;

View file

@ -77,6 +77,7 @@
#include <boost/scoped_ptr.hpp>
#include <boost/assign/list_of.hpp>
static lg::log_domain log_engine("engine");
#define DBG_NG LOG_STREAM(debug, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
@ -2012,13 +2013,13 @@ WML_HANDLER_FUNCTION(set_variable, /*event_info*/, cfg)
<< 0x3fffffff
<< ".\n";
}
long choice = random_new::generator->next_random();// gameinfo->rng().get_next_random();
uint32_t choice = random_new::generator->next_random();// gameinfo->rng().get_next_random();
if(num_choices >= 32768) {
choice <<= 15;
choice += random_new::generator->next_random();//gameinfo->rng().get_next_random();
}
choice %= num_choices;
long tmp = 0;
uint32_t tmp = 0;
for(size_t i = 0; i < ranges.size(); ++i) {
tmp += (ranges[i].second - ranges[i].first) + 1;
if (tmp > choice) {

100
src/mt_rng.cpp Normal file
View file

@ -0,0 +1,100 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
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.
*/
#include "mt_rng.hpp"
#include "seed_rng.hpp"
#include "config.hpp"
#include "log.hpp"
#include <sstream>
#include <iomanip>
static lg::log_domain log_random("random");
#define DBG_RND LOG_STREAM(debug, log_random)
#define LOG_RND LOG_STREAM(info, log_random)
#define WRN_RND LOG_STREAM(warn, log_random)
#define ERR_RND LOG_STREAM(err, log_random)
namespace rand_rng
{
mt_rng::mt_rng() :
random_seed_(seed_rng::next_seed()),
mt_(random_seed_),
random_calls_(0)
{
}
mt_rng::mt_rng(const config& cfg) :
random_seed_(42),
mt_(random_seed_), //we don't have the seed at construction time, we have to seed after construction in this case. Constructing an mt19937 is somewhat expensive, apparently has about 2kb of private memory.
random_calls_(0)
{
config::attribute_value seed = cfg["random_seed"];
seed_random(seed.str(), cfg["random_calls"].to_int(0));
}
bool mt_rng::operator== (const mt_rng & other) const {
return random_seed_ == other.random_seed_
&& random_calls_ == other.random_calls_
&& mt_ == other.mt_;
}
uint32_t mt_rng::get_next_random()
{
uint32_t result = mt_();
++random_calls_;
DBG_RND << "pulled user random " << result
<< " for call " << random_calls_
<< " with seed " << std::hex << random_seed_ << '\n';
return result;
}
void mt_rng::rotate_random()
{
seed_random(mt_(),0);
}
void mt_rng::seed_random(const uint32_t seed, const unsigned int call_count)
{
random_seed_ = seed;
mt_.seed(random_seed_);
discard(call_count); //mt_.discard(call_count);
DBG_RND << "Seeded random with " << std::hex << random_seed_ << " with "
<< random_calls_ << " calls." << std::endl;
}
void mt_rng::seed_random(const std::string & seed_str, const unsigned int call_count)
{
uint32_t new_seed;
std::istringstream s(seed_str);
s >> std::hex >> new_seed;
seed_random(new_seed, call_count);
}
std::string mt_rng::get_random_seed_str() const {
std::stringstream stream;
stream << std::setfill('0') << std::setw(sizeof(uint32_t)*2) << std::hex << random_seed_;
return stream.str();
}
void mt_rng::discard(const unsigned int call_count)
{
for(unsigned int i = 0; i < call_count; ++i) {
get_next_random();
}
}
} // ends rand_rng namespace

96
src/mt_rng.hpp Normal file
View file

@ -0,0 +1,96 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
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 MT_RNG_HPP_INCLUDED
#define MT_RNG_HPP_INCLUDED
#include <boost/cstdint.hpp>
#include <boost/random/mersenne_twister.hpp>
using boost::uint32_t;
class config;
namespace rand_rng
{
/*
This class provides an interface, similar to simple_rng, to the
boost mt19937 generator.
*/
class mt_rng
{
public:
mt_rng();
mt_rng(const config& cfg);
/** Get a new random number. */
uint32_t get_next_random();
/**
* Same as uint32_t version, but uses a stringstream to convert given
* hex string.
* @param seed A hex string. Should not have 0x leading.
* @param call_count Value to set internal call counter to after seeding.
*/
void seed_random(const std::string & seed, const unsigned int call_count = 0);
/**
* Resets the random to the 0 calls and the seed to the random
* this way we stay in the same sequence but don't have a lot
* calls. Used when moving to the next scenario.
*/
void rotate_random();
uint32_t get_random_seed() const { return random_seed_; }
std::string get_random_seed_str() const;
unsigned int get_random_calls() const { return random_calls_; }
//Comparisons, mainly used for testing
bool operator== (const mt_rng &other) const;
bool operator!= (const mt_rng &other) const
{ return !operator==(other); }
private:
/** Initial seed for the pool. */
uint32_t random_seed_;
/** State for the random pool (boost mersenne twister random generator). */
boost::mt19937 mt_;
/** Number of time a random number is generated. */
unsigned int random_calls_;
/** On my local version of boost::random, I can use mt_.discard to discard a number of rng results.
In older versions this seems to be unavailable. I'm implementing as a private method of mt_rng,
following description here: http://www.boost.org/doc/libs/1_51_0/doc/html/boost/random/mersenne_twister_engine.html#id1408119-bb
*/
void discard(const unsigned int call_count);
/**
* Seeds the random pool. This is the old version, I would like to mark this private.
*
* @param seed The initial value for the random engine.
* @param call_count Upon loading we need to restore the state at saving
* so set the number of times a random number is
* generated for replays the orginal value is
* required.
*/
void seed_random(const uint32_t seed, const unsigned int call_count = 0);
};
} // ends rand_rng namespace
#endif

View file

@ -16,8 +16,9 @@
#include "log.hpp"
#include <cassert>
#include <stdlib.h>
static lg::log_domain log_random("random");
#define DBG_RND LOG_STREAM(debug, log_random)
#define LOG_RND LOG_STREAM(info, log_random)
@ -44,19 +45,39 @@ namespace random_new
return random_calls_;
}
int rng::next_random()
uint32_t rng::next_random()
{
random_calls_++;
return next_random_impl();
}
int rng::next_random_impl()
/**
* This code is based on the boost implementation of uniform_smallint.
* http://www.boost.org/doc/libs/1_55_0/boost/random/uniform_smallint.hpp
* Using that code would be ideal, except that boost, and C++11, do not
* guarantee that it will work the same way on all platforms, or that the
* results may not be different in future versions of the library.
* The simplified version I have written should work the same on all
* platforms, which is the most important thing for us.
* The existence of "modulo bias" seems less important when we have moved
* to boost::mt19937, since it guarantees that there are no "bad bits"
* and has a very large range.
*
* If a standard cross platform version becomes available then this should
* be replaced.
*/
int rng::get_random_int_in_range_zero_to(int max)
{
assert(max >= 0);
return static_cast<int> (next_random() % (static_cast<uint32_t>(max)+1));
}
uint32_t rng::next_random_impl()
{
//getting here means random was called form outsiude a synced context.
int retv = rand();
uint32_t retv = rand();
LOG_RND << "random_new::rng::next_random returned " << retv;
return retv;
}
}

View file

@ -14,6 +14,11 @@
#ifndef RANDOM_NEW_H_INCLUDED
#define RANDOM_NEW_H_INCLUDED
#include <cstdlib> //needed for RAND_MAX
#include <boost/cstdint.hpp>
using boost::uint32_t;
namespace random_new
{
/**
@ -23,12 +28,38 @@ namespace random_new
{
public:
rng();
int next_random();
/**
* Provides the next random draw. This is raw PRG output.
*/
uint32_t next_random();
virtual ~rng();
/**
* Provides the number of random calls to the rng in this context.
* Note that this may be different from the number of random calls to
* the underlying rng, and to the random_calls number in save files!
*/
unsigned int get_random_calls();
/**
* This helper method provides a random int from the underlying generator,
* using results of next_random in a manner guaranteed to be cross platform.
* The result will be random in range [min,max] inclusive.
* @param min The minimum value produced.
* @param max The maximum value produced.
*/
int get_random_int(int min, int max)
{ return min + get_random_int_in_range_zero_to(max - min); }
protected:
virtual int next_random_impl();
virtual uint32_t next_random_impl();
unsigned int random_calls_;
private:
/** Does the hard work of get_random_int.
* The result will be random in range [0,max] inclusive.
* @param max The maximum value produced.
*/
int get_random_int_in_range_zero_to(int max);
};
/**

View file

@ -17,7 +17,7 @@
namespace random_new
{
rng_deterministic::rng_deterministic(rand_rng::simple_rng& gen)
rng_deterministic::rng_deterministic(rand_rng::mt_rng& gen)
: generator_(gen)
{
@ -28,13 +28,13 @@ namespace random_new
}
int rng_deterministic::next_random_impl()
uint32_t rng_deterministic::next_random_impl()
{
return generator_.get_next_random();
}
set_random_determinstic::set_random_determinstic(rand_rng::simple_rng& rng)
set_random_determinstic::set_random_determinstic(rand_rng::mt_rng& rng)
: old_rng_(generator), new_rng_(rng)
{
generator = &new_rng_;

View file

@ -14,7 +14,7 @@
#ifndef RANDOM_NEW_DETERMINISTIC_H_INCLUDED
#define RANDOM_NEW_DETERMINISTIC_H_INCLUDED
#include "random_new.hpp"
#include "simple_rng.hpp"
#include "mt_rng.hpp"
namespace random_new
{
@ -27,12 +27,13 @@ namespace random_new
class rng_deterministic : public random_new::rng
{
public:
rng_deterministic(rand_rng::simple_rng& gen);
rng_deterministic(rand_rng::mt_rng& gen);
virtual ~rng_deterministic();
protected:
virtual int next_random_impl();
virtual uint32_t next_random_impl();
private:
rand_rng::simple_rng& generator_;
rand_rng::mt_rng& generator_;
};
/**
@ -41,7 +42,7 @@ namespace random_new
class set_random_determinstic
{
public:
set_random_determinstic(rand_rng::simple_rng& rng);
set_random_determinstic(rand_rng::mt_rng& rng);
~set_random_determinstic();
private :
rng* old_rng_;

View file

@ -22,18 +22,18 @@ static lg::log_domain log_random("random");
namespace random_new
{
synced_rng::synced_rng(boost::function0<int> seed_generator)
synced_rng::synced_rng(boost::function0<std::string> seed_generator)
: has_valid_seed_(false), seed_generator_(seed_generator), gen_()
{
}
int synced_rng::next_random_impl()
uint32_t synced_rng::next_random_impl()
{
if(!has_valid_seed_)
{
initialize();
}
//getting here means random was called form inside a synced context.
int retv = gen_.get_next_random();
uint32_t retv = gen_.get_next_random();
LOG_RND << "random_new::rng::next_random_impl returned " << retv;
return retv;
@ -41,7 +41,7 @@ namespace random_new
void synced_rng::initialize()
{
int new_seed = seed_generator_();
std::string new_seed = seed_generator_();
gen_.seed_random(new_seed, 0);
has_valid_seed_ = true;
}

View file

@ -18,7 +18,7 @@
#include "random_new.hpp"
#include "simple_rng.hpp"
#include "mt_rng.hpp"
#include "utils/boost_function_guarded.hpp"
@ -31,16 +31,16 @@ namespace random_new
class synced_rng : public random_new::rng
{
public:
synced_rng(boost::function0<int> seed_generator);
synced_rng(boost::function0<std::string> seed_generator);
virtual ~synced_rng();
protected:
virtual int next_random_impl();
virtual uint32_t next_random_impl();
private:
void initialize();
bool has_valid_seed_;
boost::function0<int> seed_generator_;
//TODO: replayce this by boost::random::mt19937 or similar
rand_rng::simple_rng gen_;
boost::function0<std::string> seed_generator_;
rand_rng::mt_rng gen_;
};
}
#endif

View file

@ -334,7 +334,8 @@ void replay_controller::reset_replay()
gui_->labels().read(level_);
resources::gamedata->rng().seed_random(level_["random_seed"], level_["random_calls"]);
config::attribute_value random_seed = level_["random_seed"];
resources::gamedata->rng().seed_random(random_seed.str(), level_["random_calls"]);
statistics::fresh_stats();
set_victory_when_enemies_defeated(level_["victory_when_enemies_defeated"].to_bool(true));

70
src/seed_rng.cpp Normal file
View file

@ -0,0 +1,70 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
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.
*/
/* This file selects a seed source -- "nondeterministic" random number
generator in boost documentation. It should be a wrapper for
boost::random_device on platforms where this is available, otherwise
it should most likely be the system time.
*/
#include "seed_rng.hpp"
/*****
Use preprocessor tests to decide whether to try to include and
use boost random device, or fallback to system time.
*****/
#define SEED_RNG_USE_BOOST_RANDOM_DEVICE
//Boost does not support random device on windows before v 1.43.0
//http://www.boost.org/users/history/version_1_43_0.html
#if (defined(_WIN32) && (BOOST_VERSION < 104300))
#undef SEED_RNG_USE_BOOST_RANDOM_DEVICE
#endif
/*****
End preprocessor checks
*****/
#ifdef SEED_RNG_USE_BOOST_RANDOM_DEVICE
#include <boost/nondet_random.hpp>
#else
#include <ctime>
#endif
#include <sstream>
#include <iomanip>
namespace seed_rng {
#ifdef SEED_RNG_USE_BOOST_RANDOM_DEVICE
uint32_t next_seed() {
static boost::random_device rnd_;
return rnd_();
}
#else
uint32_t next_seed() {
return static_cast<uint32_t> (std::time(0));
}
#endif
std::string next_seed_str() {
uint32_t random_seed_ = next_seed();
std::stringstream stream;
stream << std::setfill('0') << std::setw(sizeof(uint32_t)*2) << std::hex << random_seed_;
return stream.str();
}
} //ends seed_rng namespace

38
src/seed_rng.hpp Normal file
View file

@ -0,0 +1,38 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
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.
*/
/*
This file provides a name space to store a source for seeds for
prgs. It should be boost::random_device on platforms that provide
this with our version of boost random, and otherwise should be the
system time I suppose.
The seed_rng::next_seed function provided probably shouldn't be used
anywhere except for default constructors of prg classes, or similar.
*/
#include <boost/cstdint.hpp>
#include <string>
#ifndef SEED_RNG_HPP_INCLUDED
#define SEED_RNG_HPP_INCLUDED
namespace seed_rng {
uint32_t next_seed();
std::string next_seed_str();
} // ends seed_rng namespace
#endif

View file

@ -24,6 +24,8 @@
#include "serialization/string_utils.hpp"
#include "util.hpp"
#include <sstream>
#include <iomanip>
#include <boost/bind.hpp>
static lg::log_domain log_server("server");
@ -67,7 +69,8 @@ game::game(player_map& players, const network::connection host,
termination_(),
save_replays_(save_replays),
replay_save_path_(replay_save_path),
global_wait_side_(0)
global_wait_side_(0),
rng_()
{
assert(owner_);
players_.push_back(owner_);
@ -1019,13 +1022,17 @@ bool game::process_turn(simple_wml::document& data, const player_map::const_iter
void game::require_random(const simple_wml::document &/*data*/, const player_map::iterator /*user*/)
{
// note, that during end turn events, it's side=1 for the server but side= side_count() on the clients.
uint32_t seed = rng_.get_next_random();
std::stringstream stream;
stream << std::setfill('0') << std::setw(sizeof(uint32_t)*2) << std::hex << seed;
int seed = rand() & 0x7FFFFFFF;
simple_wml::document* mdata = new simple_wml::document;
simple_wml::node& turn = mdata->root().add_child("turn");
simple_wml::node& command = turn.add_child("command");
simple_wml::node& random_seed = command.add_child("random_seed");
random_seed.set_attr_int("new_seed",seed);
random_seed.set_attr_dup("new_seed",stream.str().c_str());
command.set_attr("from_side", "server");
command.set_attr("dependent", "yes");

View file

@ -23,6 +23,8 @@
#include <map>
#include <vector>
#include "../mt_rng.hpp"
//class player;
namespace wesnothd {
@ -392,6 +394,9 @@ private:
/** The side from which global variable data is expected*/
int global_wait_side_;
/** A wrapper for mersenne twister rng which generates randomness for this game */
rand_rng::mt_rng rng_;
};
struct game_is_member {

View file

@ -33,12 +33,15 @@
#include "play_controller.hpp"
#include "actions/undo.hpp"
#include "game_end_exceptions.hpp"
#include "seed_rng.hpp"
#include "syncmp_handler.hpp"
#include <boost/lexical_cast.hpp>
#include <cassert>
#include <stdlib.h>
#include <sstream>
#include <iomanip>
static lg::log_domain log_replay("replay");
#define DBG_REPLAY LOG_STREAM(debug, log_replay)
#define LOG_REPLAY LOG_STREAM(info, log_replay)
@ -150,11 +153,13 @@ void synced_context::set_synced_state(synced_state newstate)
state_ = newstate;
}
int synced_context::generate_random_seed()
std::string synced_context::generate_random_seed()
{
random_seed_choice cho;
config retv_c = synced_context::ask_server("random_seed", cho);
return retv_c["new_seed"];
config::attribute_value seed_val = retv_c["new_seed"];
return seed_val.str();
}
bool synced_context::is_simultaneously()
@ -475,7 +480,8 @@ config random_seed_choice::query_user(int /*side*/) const
config retv;
retv["new_seed"] = rand();
retv["new_seed"] = seed_rng::next_seed_str();
return retv;
}
config random_seed_choice::random_choice(int /*side*/) const
@ -485,6 +491,6 @@ config random_seed_choice::random_choice(int /*side*/) const
assert(false && "random_seed_choice::random_choice called");
config retv;
retv["new_seed"] = 0;
retv["new_seed"] = "deadbeef";
return retv;
}

View file

@ -72,7 +72,7 @@ public:
/*
Generates a new seed for a synced event, by asking the 'server'
*/
static int generate_random_seed();
static std::string generate_random_seed();
/**
called from get_user_choice while waiting for a remove user choice.
*/

View file

@ -111,6 +111,46 @@ BOOST_AUTO_TEST_CASE( test_send_client )
}
void try_send_random_seed ( const std::string seed_str, const unsigned int random_calls)
{
config cfg_send;
config& child = cfg_send.add_child("command");
child["random_seed"] = seed_str;
child["random_calls"] = random_calls;
network::send_data(cfg_send, client_client1);
network::connection receive_from;
config received;
receive_from = receive(received);
BOOST_CHECK_MESSAGE( receive_from == server_client1, "Received data is not from test client 1" );
BOOST_CHECK_EQUAL(cfg_send, received);
config rec_command = received.child("command");
std::string rec_seed_str = rec_command["random_seed"].str();
unsigned int rec_calls = rec_command["random_calls"];
BOOST_CHECK_EQUAL(seed_str, rec_seed_str);
BOOST_CHECK_EQUAL(random_calls, rec_calls);
}
BOOST_AUTO_TEST_CASE( test_send_random_seed )
{
try_send_random_seed("0000badd",0);
try_send_random_seed("00001234",1);
try_send_random_seed("deadbeef",2);
try_send_random_seed("12345678",3);
try_send_random_seed("00009999",4);
try_send_random_seed("ffffaaaa",5);
try_send_random_seed("11110000",6);
try_send_random_seed("10101010",7);
try_send_random_seed("aaaa0000",8);
}
class connect_aborter : public threading::waiter
{

331
src/tests/test_rng.cpp Normal file
View file

@ -0,0 +1,331 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
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.
*/
#define GETTEXT_DOMAIN "wesnoth-test"
#include <boost/test/unit_test.hpp>
#include "random_new_synced.hpp"
#include "random_new_deterministic.hpp"
#include "config.hpp"
#include <sstream>
#include <iomanip>
BOOST_AUTO_TEST_SUITE( rng )
/* this test adapted from validation routine at
http://www.boost.org/doc/libs/1_38_0/libs/random/random_test.cpp
*/
BOOST_AUTO_TEST_CASE( validate_mt19937 )
{
boost::mt19937 rng;
for (int i = 0; i < 9999 ; i++) {
rng();
}
unsigned long val = rng();
BOOST_CHECK_EQUAL( val , 4123659995U );
}
/* this test checks the soundness of mt_rng string manipulations */
BOOST_AUTO_TEST_CASE( test_mt_rng_seed_manip )
{
uint32_t seed = 42;
std::stringstream stream;
stream << std::setfill('0') << std::setw(sizeof(uint32_t)*2) << std::hex << seed;
std::string seed_str = stream.str();
rand_rng::mt_rng rng;
rng.seed_random(seed_str);
BOOST_CHECK (rng.get_random_seed() == seed);
BOOST_CHECK (rng.get_random_seed_str() == seed_str);
std::string seed_str2 = rng.get_random_seed_str();
rng.seed_random(seed_str2);
BOOST_CHECK (rng.get_random_seed() == seed);
BOOST_CHECK (rng.get_random_seed_str() == seed_str);
uint32_t seed3 = 1123581321; //try the same with a different number
std::stringstream stream2;
stream2 << std::setfill('0') << std::setw(sizeof(uint32_t)*2) << std::hex << seed3;
std::string seed_str3 = stream2.str();
rng.seed_random(seed_str3);
BOOST_CHECK (rng.get_random_seed() == seed3);
BOOST_CHECK (rng.get_random_seed_str() == seed_str3);
std::string seed_str4 = rng.get_random_seed_str();
rng.seed_random(seed_str4);
BOOST_CHECK (rng.get_random_seed() == seed3);
BOOST_CHECK (rng.get_random_seed_str() == seed_str3);
//now check that the results that shouldn't match don't
BOOST_CHECK (seed != seed3);
BOOST_CHECK (seed_str != seed_str3);
}
BOOST_AUTO_TEST_CASE( test_mt_rng_config_seed_manip )
{
uint32_t seed = 42;
std::stringstream stream;
stream << std::setfill('0') << std::setw(sizeof(uint32_t)*2) << std::hex << seed;
std::string seed_str = stream.str();
config cfg;
cfg["random_seed"] = seed_str;
cfg["random_calls"] = 0;
rand_rng::mt_rng rng(cfg);
BOOST_CHECK (rng.get_random_seed() == seed);
BOOST_CHECK (rng.get_random_seed_str() == seed_str);
std::string seed_str2 = rng.get_random_seed_str();
rng.seed_random(seed_str2);
BOOST_CHECK (rng.get_random_seed() == seed);
BOOST_CHECK (rng.get_random_seed_str() == seed_str);
uint32_t seed3 = 1123581321; //try the same with a different number
std::stringstream stream2;
stream2 << std::setfill('0') << std::setw(sizeof(uint32_t)*2) << std::hex << seed3;
std::string seed_str3 = stream2.str();
config cfg2;
cfg2["random_seed"] = seed_str3;
cfg2["random_calls"] = 0;
rand_rng::mt_rng rng2(cfg2);
BOOST_CHECK (rng2.get_random_seed() == seed3);
BOOST_CHECK (rng2.get_random_seed_str() == seed_str3);
std::string seed_str4 = rng2.get_random_seed_str();
rng2.seed_random(seed_str4);
BOOST_CHECK (rng2.get_random_seed() == seed3);
BOOST_CHECK (rng2.get_random_seed_str() == seed_str3);
//now check that the results that shouldn't match don't
BOOST_CHECK (seed != seed3);
BOOST_CHECK (seed_str != seed_str3);
}
BOOST_AUTO_TEST_CASE( test_mt_rng_reproducibility )
{
config cfg;
cfg["random_seed"] = "5eedf00d";
cfg["random_calls"] = 0;
rand_rng::mt_rng rng1(cfg);
rand_rng::mt_rng rng2(cfg);
BOOST_CHECK(rng1 == rng2);
for (int i = 0; i < 10 ; i++) {
BOOST_CHECK(rng1.get_next_random() == rng2.get_next_random());
}
}
BOOST_AUTO_TEST_CASE( test_mt_rng_reproducibility2 )
{
config cfg;
cfg["random_seed"] = "18da5eed";
cfg["random_calls"] = 9999;
rand_rng::mt_rng rng1(cfg);
rand_rng::mt_rng rng2(cfg);
BOOST_CHECK(rng1 == rng2);
for (int i = 0; i < 10 ; i++) {
BOOST_CHECK(rng1.get_next_random() == rng2.get_next_random());
}
}
BOOST_AUTO_TEST_CASE( test_mt_rng_reproducibility3 )
{
rand_rng::mt_rng rng1;
config cfg;
cfg["random_seed"] = rng1.get_random_seed_str();
cfg["random_calls"] = rng1.get_random_calls();
rand_rng::mt_rng rng2(cfg);
BOOST_CHECK(rng1 == rng2);
for (int i = 0; i < 10 ; i++) {
BOOST_CHECK(rng1.get_next_random() == rng2.get_next_random());
}
}
BOOST_AUTO_TEST_CASE( test_mt_rng_reproducibility4 )
{
rand_rng::mt_rng rng1;
for (int i = 0; i < 5; i++) {
rng1.get_next_random();
}
config cfg;
cfg["random_seed"] = rng1.get_random_seed_str();
cfg["random_calls"] = rng1.get_random_calls();
rand_rng::mt_rng rng2(cfg);
BOOST_CHECK(rng1 == rng2);
BOOST_CHECK(rng1.get_next_random() == rng2.get_next_random());
}
BOOST_AUTO_TEST_CASE( test_mt_rng_reproducibility5 )
{
config cfg;
cfg["random_seed"] = "5eedc0de";
cfg["random_calls"] = 0;
rand_rng::mt_rng rng(cfg);
for (int i = 0; i < 9999 ; i++) {
rng.get_next_random();
}
config cfg2;
cfg2["random_seed"] = rng.get_random_seed_str();
cfg2["random_calls"] = rng.get_random_calls();
rand_rng::mt_rng rng2(cfg2);
uint32_t result1 = rng.get_next_random();
uint32_t result2 = rng2.get_next_random();
BOOST_CHECK (rng == rng2);
BOOST_CHECK (rng.get_random_seed_str() == rng2.get_random_seed_str());
BOOST_CHECK (rng.get_random_calls() == rng2.get_random_calls());
BOOST_CHECK (result1 == result2);
config cfg_save;
cfg_save["random_seed"] = rng.get_random_seed_str();
cfg_save["random_calls"] = rng.get_random_calls();
uint32_t result3 = rng.get_next_random();
rand_rng::mt_rng rng3(cfg_save);
uint32_t result4 = rng3.get_next_random();
BOOST_CHECK (rng == rng3);
BOOST_CHECK (rng.get_random_seed_str() == rng3.get_random_seed_str());
BOOST_CHECK (rng.get_random_calls() == rng3.get_random_calls());
BOOST_CHECK (result3 == result4);
}
namespace {
void validate_seed_string(std::string seed_str)
{
config cfg;
cfg["random_seed"] = seed_str;
cfg["random_calls"] = 0;
rand_rng::mt_rng rng1(cfg);
for (int i = 0; i < 9999 ; i++) {
rng1.get_next_random();
}
config cfg2;
cfg2["random_seed"] = rng1.get_random_seed_str();
cfg2["random_calls"] = rng1.get_random_calls();
rand_rng::mt_rng rng2(cfg2);
for (int i = 0; i < 9999 ; i++) {
rng1.get_next_random();
rng2.get_next_random();
}
BOOST_CHECK(rng1 == rng2);
BOOST_CHECK(rng1.get_next_random() == rng2.get_next_random());
}
}
BOOST_AUTO_TEST_CASE( test_mt_rng_reproducibility_coverage )
{
validate_seed_string("0000badd");
validate_seed_string("00001234");
validate_seed_string("deadbeef");
validate_seed_string("12345678");
validate_seed_string("00009999");
validate_seed_string("ffffaaaa");
validate_seed_string("11110000");
validate_seed_string("10101010");
validate_seed_string("aaaa0000");
}
namespace {
std::string validate_get_random_int_seed_generator()
{
return "dada5eed";
}
}
#define validation_get_random_int_num_draws 19999
#define validation_get_random_int_max 32000
#define validation_get_random_int_correct_answer 10885
/**
* This test and the next validate that we are getting the correct values
* from the get_random_int function, in the class random_new.
* We test both subclasses of random_new.
* If these tests fail but the seed manipulation tests all pass,
* and validate_mt19937 passes, then it suggests that the implementation
* of get_random_int may not be working properly on your platform.
*/
BOOST_AUTO_TEST_CASE( validate_get_random_int )
{
config cfg;
cfg["random_seed"] = validate_get_random_int_seed_generator();
cfg["random_calls"] = validation_get_random_int_num_draws;
rand_rng::mt_rng mt_(cfg);
boost::shared_ptr<random_new::rng> gen_ (new random_new::rng_deterministic(mt_));
int val = gen_->get_random_int(0, validation_get_random_int_max);
BOOST_CHECK_EQUAL ( val , validation_get_random_int_correct_answer );
}
BOOST_AUTO_TEST_CASE( validate_get_random_int2 )
{
boost::shared_ptr<random_new::rng> gen_ (new random_new::synced_rng(validate_get_random_int_seed_generator));
for (int i = 0; i < validation_get_random_int_num_draws; i++) {
gen_->next_random();
}
int val = gen_->get_random_int(0,validation_get_random_int_max);
BOOST_CHECK_EQUAL ( val , validation_get_random_int_correct_answer );
}
BOOST_AUTO_TEST_SUITE_END();

View file

@ -148,8 +148,7 @@ static unit_race::GENDER generate_gender(const unit_type & type, bool random_gen
if ( random_gender == false || genders.size() == 1 ) {
return genders.front();
} else {
int random = random_new::generator->next_random();
return genders[random % genders.size()];
return genders[random_new::generator->get_random_int(0,genders.size()-1)];
// Note: genders is guaranteed to be non-empty, so this is not a
// potential division by zero.
// Note: Whoever wrote this code, you should have used an assertion, to save others hours of work...
@ -758,7 +757,7 @@ void unit::generate_traits(bool musthaveonly)
int max_traits = u_type.num_traits();
for (; nb_traits < max_traits && !candidate_traits.empty(); ++nb_traits)
{
int num = random_new::generator->next_random() % candidate_traits.size();
int num = random_new::generator->get_random_int(0,candidate_traits.size()-1);
modifications_.add_child("trait", candidate_traits[num]);
candidate_traits.erase(candidate_traits.begin() + num);
}