Added rand= for [set_variable] which can be used in MP,

the user has to make sure it stays in sync.
This commit is contained in:
Mark de Wever 2007-12-24 22:31:51 +00:00
parent e171ebfa40
commit 06d19f9a4f
9 changed files with 229 additions and 18 deletions

View file

@ -39,6 +39,8 @@ Version 1.3.12+svn:
* updated {GENERIC_UNIT ...} macro to take advantage of random_gender
* fixed [filter_radius] to support [and][or][not] and radius=
* undoing recruits is no longer allowed
* added rand= for [set_variable] which can be used in MP, the user has to
make sure it stays in sync
* miscellaneous and bug fixes:
* various bug fixes and code cleanups
* added gzip and gunzip command line parameters

View file

@ -755,6 +755,16 @@ bool game_controller::load_game()
}
}
// Get the status of the random in the snapshot.
// For a replay we need to restore the start only, the replaying gets at
// proper location.
// For normal loading also restore the call count.
const int seed = lexical_cast_default<int>
(cfg["random_seed"], 42);
const unsigned calls = show_replay ? 0 :
lexical_cast_default<unsigned> (state_.snapshot["random_calls"]);
state_.seed_random(seed, calls);
} catch(game::error& e) {
gui::show_error_message(disp(), _("The file you have tried to load is corrupt: '") + e.message + '\'');
return false;
@ -819,6 +829,7 @@ void game_controller::set_tutorial()
state_.campaign_define = "TUTORIAL";
reset_defines_map();
defines_map_["TUTORIAL"] = preproc_define();
}
bool game_controller::new_campaign()
@ -2041,7 +2052,7 @@ static int play_game(int argc, char** argv)
return remove(input_file.c_str());
} catch(io_exception& e) {
std::cerr << "IO error: " << e.what() << "\n";
std::cerr << "IO error: " << e.what() << "\n";
}
} else if(val == "--gunzip") {

View file

@ -1080,6 +1080,12 @@ bool event_handler::handle_event_command(const queued_event& event_info,
// range (i.e. -1..-10, 0..100, -10..10, etc).
const std::string random = cfg["random"];
if(random.empty() == false) {
// random is deprecated but will be available in the 1.4 branch
// so enable the message after forking
//! @todo Enable after branching and once rand works fully in MP
//! including synchronizing.
//lg::wml_error << "Usage of 'random' is deprecated use 'rand' instead, "
// "support will be removed in 1.5.2.\n";
std::string random_value;
// If we're not replaying, create a random number
if(get_replay_source().at_end()) {
@ -1144,7 +1150,6 @@ bool event_handler::handle_event_command(const queued_event& event_info,
break;
}
}
recorder.set_random_value(random_value.c_str());
}
@ -1160,6 +1165,79 @@ bool event_handler::handle_event_command(const queued_event& event_info,
}
var = random_value;
}
// The new random generator, the logic is a copy paste of the old random.
const std::string rand = cfg["rand"];
if(rand.empty() == false) {
assert(state_of_game);
std::string random_value;
std::string word;
std::vector<std::string> words;
std::vector<std::pair<long,long> > ranges;
int num_choices = 0;
std::string::size_type pos = 0, pos2 = std::string::npos;
std::stringstream ss(std::stringstream::in|std::stringstream::out);
while (pos2 != rand.length()) {
pos = pos2+1;
pos2 = rand.find(",", pos);
if (pos2 == std::string::npos)
pos2 = rand.length();
word = rand.substr(pos, pos2-pos);
words.push_back(word);
std::string::size_type tmp = word.find("..");
if (tmp == std::string::npos) {
// Treat this element as a string
ranges.push_back(std::pair<int, int>(0,0));
num_choices += 1;
}
else {
// Treat as a numerical range
const std::string first = word.substr(0, tmp);
const std::string second = word.substr(tmp+2,
rand.length());
long low, high;
ss << first + " " + second;
ss >> low;
ss >> high;
ss.clear();
if (low > high) {
tmp = low;
low = high;
high = tmp;
}
ranges.push_back(std::pair<long, long>(low,high));
num_choices += (high - low) + 1;
}
}
int choice = state_of_game->get_random() % num_choices;
int tmp = 0;
for(size_t i = 0; i < ranges.size(); i++) {
tmp += (ranges[i].second - ranges[i].first) + 1;
if (tmp > choice) {
if (ranges[i].first == 0 && ranges[i].second == 0) {
random_value = words[i];
}
else {
tmp = (ranges[i].second - (tmp - choice)) + 1;
ss << tmp;
ss >> random_value;
}
break;
}
}
var = random_value;
}
}
// Conditional statements

View file

@ -447,30 +447,36 @@ game_state::game_state(const game_data& data, const config& cfg) :
snapshot(),
last_selected(gamemap::location::null_location),
variables(),
temporaries()
temporaries(),
random_seed_(lexical_cast<int>(cfg["random_seed"])),
random_pool_(random_seed_),
random_calls_(0)
{
log_scope("read_game");
const config* snapshot = cfg.child("snapshot");
if (snapshot != NULL){
if (snapshot != NULL){
this->snapshot = *snapshot;
const config::child_list& players = snapshot->get_children("player");
seed_random(random_seed_, lexical_cast_default<unsigned>((*snapshot)["random_calls"]));
const config::child_list& players = snapshot->get_children("player");
if(!players.empty()) {
for(config::child_list::const_iterator i = players.begin(); i != players.end(); ++i) {
std::string save_id = (**i)["save_id"];
if(!players.empty()) {
for(config::child_list::const_iterator i = players.begin(); i != players.end(); ++i) {
std::string save_id = (**i)["save_id"];
if(save_id.empty()) {
std::cerr << "Corrupted player entry: NULL save_id" << std::endl;
} else {
player_info player = read_player(data, *i);
this->players.insert(std::pair<std::string, player_info>(save_id,player));
}
}
}
}
if(save_id.empty()) {
std::cerr << "Corrupted player entry: NULL save_id" << std::endl;
} else {
player_info player = read_player(data, *i);
this->players.insert(std::pair<std::string, player_info>(save_id,player));
}
}
}
}
std::cerr << "scenario: '" << scenario << "'\n";
std::cerr << "next_scenario: '" << next_scenario << "'\n";
@ -588,6 +594,9 @@ void write_game(const game_state& gamestate, config& cfg, WRITE_GAME_MODE mode)
cfg["campaign_define"] = gamestate.campaign_define;
cfg["campaign_extra_defines"] = utils::join(gamestate.campaign_xtra_defines);
cfg["random_seed"] = lexical_cast<std::string>(gamestate.get_random_seed());
cfg["random_calls"] = lexical_cast<std::string>(gamestate.get_random_calls());
cfg.add_child("variables",gamestate.get_variables());
for(std::map<std::string, wml_menu_item *>::const_iterator j=gamestate.wml_menu_items.begin();
@ -640,7 +649,10 @@ void write_game(config_writer &out, const game_state& gamestate, WRITE_GAME_MODE
out.write_key_val("difficulty", gamestate.difficulty);
out.write_key_val("campaign_define", gamestate.campaign_define);
out.write_key_val("campaign_extra_defines", utils::join(gamestate.campaign_xtra_defines));
out.write_key_val("random_seed", lexical_cast<std::string>(gamestate.get_random_seed()));
out.write_key_val("random_calls", lexical_cast<std::string>(gamestate.get_random_calls()));
out.write_child("variables", gamestate.get_variables());
for(std::map<std::string, wml_menu_item *>::const_iterator j=gamestate.wml_menu_items.begin();
j!=gamestate.wml_menu_items.end(); ++j) {
out.open_child("menu_item");
@ -1095,6 +1107,44 @@ void game_state::clear_variable(const std::string& varname)
}
}
//! Get a new random number.
int game_state::get_random()
{
random_next();
++random_calls_;
DBG_NG << "pulled user random " << random_pool_
<< " for call " << random_calls_ << '\n';
return (static_cast<unsigned>(random_pool_ / 65536) % 32768);
}
//! Seeds the random pool.
//!
//! @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 game_state::seed_random(const int seed, const unsigned call_count)
{
random_pool_ = seed;
random_seed_ = seed;
for(random_calls_ = 0; random_calls_ < call_count; ++random_calls_) {
random_next();
}
DBG_NG << "Seeded random with " << random_seed_ << " with "
<< random_calls_ << " calls, pool is now at "
<< random_pool_ << '\n';
}
//! Sets the next random number in the pool.
void game_state::random_next()
{
// Use the simple random generator as shown in man rand(3).
// The division is done separately since we also want to
// quickly go the the wanted index in the random list.
random_pool_ = random_pool_ * 1103515245 + 12345;
}
static void clear_wmi(std::map<std::string, wml_menu_item*>& gs_wmi) {
std::map<std::string, wml_menu_item*>::iterator itor = gs_wmi.begin();
for(itor = gs_wmi.begin(); itor != gs_wmi.end(); ++itor) {
@ -1123,6 +1173,9 @@ game_state& game_state::operator=(const game_state& state)
campaign = state.campaign;
scenario = state.scenario;
completion = state.completion;
random_seed_ = state.random_seed_;
random_pool_ = state.random_pool_;
random_calls_ = state.random_calls_;
players = state.players;
scoped_variables = state.scoped_variables;

View file

@ -113,7 +113,10 @@ public:
snapshot(),
last_selected(gamemap::location::null_location),
variables(),
temporaries()
temporaries(),
random_seed_(rand()),
random_pool_(random_seed_),
random_calls_(0)
{}
game_state(const game_state& state);
@ -182,10 +185,39 @@ public:
//! the last location where a select event fired.
gamemap::location last_selected;
//! Get a new random number.
int get_random();
//! Seeds the random pool.
void seed_random(const int seed, const unsigned 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()
{ random_seed_ = random_pool_; random_calls_ = 0; }
int get_random_seed() const { return random_seed_; }
int get_random_calls() const { return random_calls_; }
private:
config variables;
mutable config temporaries; // lengths of arrays, etc.
friend struct variable_info;
//! Initial seed for the pool.
int random_seed_;
//! State for the random pool.
int random_pool_;
//! Number of time a random number is generated.
unsigned random_calls_;
//! Sets the next random number in the pool.
void random_next();
};

View file

@ -1447,6 +1447,8 @@ void connect::load_game()
level_["experience_modifier"] = state_.snapshot["experience_modifier"];
level_["observer"] = state_.snapshot["observer"];
level_.add_child("snapshot") = state_.snapshot;
level_["random_seed"] = state_.snapshot["random_seed"];
level_["random_calls"] = state_.snapshot["random_calls"];
// Adds the replay data, and the replay start, to the level,
// so clients can receive it.
@ -1481,6 +1483,7 @@ void connect::load_game()
level_["scenario"] = params_.name;
level_["experience_modifier"] = lexical_cast<std::string>(params_.xp_modifier);
level_["random_seed"] = lexical_cast<std::string>(state_.get_random_seed());
}
const std::string& era = params_.era;

View file

@ -31,6 +31,7 @@
#include "wml_separators.hpp"
#define LOG_NG LOG_STREAM(info, engine)
#define ERR_NG LOG_STREAM(err, engine)
#define ERR_CF LOG_STREAM(err, config)
#define DBG_NW LOG_STREAM(debug, network)
#define LOG_NW LOG_STREAM(info, network)
@ -94,6 +95,16 @@ void level_to_gamestate(config& level, game_state& state, bool saved_game)
}
}
//set random
const std::string seed = level["random_seed"];
if(! seed.empty()) {
const unsigned calls = lexical_cast_default<unsigned>(level["random_calls"]);
state.seed_random(lexical_cast<int>(seed), calls);
} else {
ERR_NG << "No random seed found, random "
"events will probably be out of sync.\n";
}
//adds the starting pos to the level
if(level.child("replay_start") == NULL){
level.add_child("replay_start", level);

View file

@ -449,6 +449,7 @@ LEVEL_RESULT play_game(display& disp, game_state& gamestate, const config& game_
// Switch to the next scenario.
gamestate.scenario = gamestate.next_scenario;
gamestate.rotate_random();
if(io_type == IO_CLIENT) {
if (!gamestate.next_scenario.empty()){

View file

@ -15,6 +15,26 @@
//! @file random.cpp
//! Generate random numbers.
//!
//! There are various ways to get a random number.
//! rand() This can be used for things that never are send over the
//! network e.g. generate a random map (the final result the
//! map is send, but the other players don't need to generate
//! the map.
//!
//! get_random() A random generator which is syncronized over the network
//! this only seems to work when it's used by 1 player at the
//! same time. It's syncronized after an event so if an event
//! runs at two clients at the same time it gets out of sync
//! and sets the entire game out of sync.
//!
//! game_state::get_random()
//! A random generator which is seeded by the host of an MP
//! game. This generator is (not yet) synchronized over the
//! network. It's only used by [set_variable]rand=. The map
//! designer has to make sure it stays in sync. This
//! generator can be used at the same time at multiple client
//! since the generators are always in sync.
#include "global.hpp"