Merge pull request #385 from cbeck88/random_faction_mode

Random faction mode
This commit is contained in:
Chris Beck 2015-03-13 16:23:40 -04:00
commit 4f7099895d
13 changed files with 146 additions and 24 deletions

View file

@ -1,6 +1,7 @@
#include "configure_engine.hpp"
#include "formula_string_utils.hpp"
#include "game_config_manager.hpp"
#include "mp_game_settings.hpp"
#include "settings.hpp"
#include <boost/foreach.hpp>
@ -54,6 +55,7 @@ void configure_engine::set_default_values() {
set_random_start_time(random_start_time_default());
set_fog_game(fog_game_default());
set_shroud_game(shroud_game_default());
set_random_faction_mode(random_faction_mode_default());
}
bool configure_engine::force_lock_settings() const {
@ -77,6 +79,7 @@ bool configure_engine::fog_game() const { return parameters_.fog_game; }
bool configure_engine::shroud_game() const { return parameters_.shroud_game; }
bool configure_engine::allow_observers() const { return parameters_.allow_observers; }
bool configure_engine::shuffle_sides() const { return parameters_.shuffle_sides; }
mp_game_settings::RANDOM_FACTION_MODE configure_engine::random_faction_mode() const { return parameters_.random_faction_mode; }
const config& configure_engine::options() const { return parameters_.options; }
void configure_engine::set_game_name(std::string val) { parameters_.name = val; }
@ -96,6 +99,7 @@ void configure_engine::set_shroud_game(bool val) { parameters_.shroud_game = val
void configure_engine::set_allow_observers(bool val) { parameters_.allow_observers = val; }
void configure_engine::set_oos_debug(bool val) { state_.classification().oos_debug = val; }
void configure_engine::set_shuffle_sides(bool val) { parameters_.shuffle_sides = val; }
void configure_engine::set_random_faction_mode(mp_game_settings::RANDOM_FACTION_MODE val) { parameters_.random_faction_mode = val;}
void configure_engine::set_options(const config& cfg) { parameters_.options = cfg; }
void configure_engine::set_scenario(size_t scenario_num) {
@ -180,6 +184,10 @@ bool configure_engine::allow_observers_default() const {
bool configure_engine::shuffle_sides_default() const {
return preferences::shuffle_sides();
}
mp_game_settings::RANDOM_FACTION_MODE configure_engine::random_faction_mode_default() const {
return mp_game_settings::string_to_RANDOM_FACTION_MODE_default(preferences::random_faction_mode(), mp_game_settings::DEFAULT);
}
const config& configure_engine::options_default() const {
return preferences::options();
}

View file

@ -17,6 +17,7 @@
#include "gettext.hpp"
#include "game_preferences.hpp"
#include "mp_game_settings.hpp"
#include "saved_game.hpp"
namespace ng {
@ -55,6 +56,7 @@ public:
bool shroud_game() const;
bool allow_observers() const;
bool shuffle_sides() const;
mp_game_settings::RANDOM_FACTION_MODE random_faction_mode() const;
const config& options() const;
// setter methods
@ -75,6 +77,7 @@ public:
void set_allow_observers(bool val);
void set_oos_debug(bool val);
void set_shuffle_sides(bool val);
void set_random_faction_mode(mp_game_settings::RANDOM_FACTION_MODE val);
void set_options(const config& cfg);
void set_scenario(size_t scenario_num);
@ -97,6 +100,7 @@ public:
bool shroud_game_default() const;
bool allow_observers_default() const;
bool shuffle_sides_default() const;
mp_game_settings::RANDOM_FACTION_MODE random_faction_mode_default() const;
const config& options_default() const;
// parameters_ accessor

View file

@ -21,6 +21,7 @@
#include "map.hpp"
#include "multiplayer_ui.hpp"
#include "mp_game_utils.hpp"
#include "mt_rng.hpp"
#include "tod_manager.hpp"
#include <boost/foreach.hpp>
@ -391,10 +392,32 @@ void connect_engine::start_game(LOAD_USERS load_users)
{
DBG_MP << "starting a new game" << std::endl;
// Resolves the "random faction", "random gender" and "random message"
// Must be done before shuffle sides, or some cases will cause errors
// Resolves the "random faction", "random gender" and "random message"
// Must be done before shuffle sides, or some cases will cause errors
rand_rng::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
side->resolve_random();
std::vector<std::string> avoid_faction_ids;
// If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
if (params_.random_faction_mode != mp_game_settings::DEFAULT) {
BOOST_FOREACH(side_engine_ptr side2, side_engines_) {
if (!side2->flg().is_random_faction()) {
switch(params_.random_faction_mode) {
case mp_game_settings::NO_MIRROR:
avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
break;
case mp_game_settings::NO_ALLY_MIRROR:
if (side2->team() == side->team()) {// TODO: When the connect engines are fixed to allow multiple teams, this should be changed to "if side1 and side2 are allied, i.e. their list of teams has nonempty intersection"
avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
}
break;
default:
break; // assert(false);
}
}
}
}
side->resolve_random(rng, avoid_faction_ids);
}
// Shuffle sides (check settings and if it is a re-loaded game).
@ -413,7 +436,7 @@ void connect_engine::start_game(LOAD_USERS load_users)
// Fisher-Yates shuffle.
for (int i = playable_sides.size(); i > 1; i--)
{
int j_side = playable_sides[rand() % i];
int j_side = playable_sides[rng.get_next_random() % i];
int i_side = playable_sides[i - 1];
if (i_side == j_side) continue; //nothing to swap
@ -464,6 +487,8 @@ void connect_engine::start_game_commandline(
typedef boost::tuple<unsigned int, std::string> mp_option;
rand_rng::mt_rng rng;
unsigned num = 0;
BOOST_FOREACH(side_engine_ptr side, side_engines_) {
num++;
@ -514,7 +539,7 @@ void connect_engine::start_game_commandline(
// Finally, resolve "random faction",
// "random gender" and "random message", if any remains unresolved.
side->resolve_random();
side->resolve_random(rng);
} // end top-level loop
update_and_send_diff(true);
@ -1168,7 +1193,7 @@ bool side_engine::swap_sides_on_drop_target(const unsigned drop_target) {
return true;
}
void side_engine::resolve_random()
void side_engine::resolve_random(rand_rng::mt_rng & rng, const std::vector<std::string> & avoid_faction_ids)
{
if (parent_.params_.saved_game) {
return;
@ -1176,7 +1201,7 @@ void side_engine::resolve_random()
chose_random_ = flg_.is_random_faction();
flg_.resolve_random();
flg_.resolve_random(rng, avoid_faction_ids);
LOG_MP << "side " << (index_ + 1) << ": faction=" <<
(flg_.current_faction())["name"] << ", leader=" <<

View file

@ -22,6 +22,8 @@
#include "saved_game.hpp"
#include <boost/scoped_ptr.hpp>
namespace rand_rng { class mt_rng; }
namespace ng {
enum controller {
@ -157,7 +159,7 @@ public:
bool swap_sides_on_drop_target(const unsigned drop_target);
void resolve_random();
void resolve_random( rand_rng::mt_rng & rng, const std::vector<std::string> & avoid_faction_ids = std::vector<std::string>());
// Resets this side to its default state.
void reset();

View file

@ -16,6 +16,7 @@
#include "config.hpp"
#include "formula_string_utils.hpp"
#include "gettext.hpp"
#include "mt_rng.hpp"
#include "unit_types.hpp"
#include "wml_separators.hpp"
@ -206,7 +207,16 @@ bool flg_manager::is_random_faction()
return (*current_faction_)["random_faction"].to_bool();
}
void flg_manager::resolve_random() {
// When we use a random mode like "no mirror", "no ally mirror", the list of
// faction ids to avoid is past as an argument.
// It may be that for some scenario configuration, a strict no mirror
// assignment is not possible, because there are too many sides, or some users
// have forced their faction choices to be matching, etc.
// In that case we gracefully continue by ignoring the no mirror rule and
// assigning as we would have if it were off.
// If there is still no options we throw a config error because it means the
// era is misconfigured.
void flg_manager::resolve_random(rand_rng::mt_rng & rng, const std::vector<std::string> & avoid) {
if (is_random_faction()) {
std::vector<std::string> faction_choices, faction_excepts;
@ -223,6 +233,7 @@ void flg_manager::resolve_random() {
// Builds the list of factions eligible for choice
// (non-random factions).
std::vector<int> nonrandom_sides;
std::vector<int> fallback_nonrandom_sides;
int num = -1;
BOOST_FOREACH(const config* i, available_factions_) {
++num;
@ -241,16 +252,28 @@ void flg_manager::resolve_random() {
continue;
}
nonrandom_sides.push_back(num);
fallback_nonrandom_sides.push_back(num); //This side is consistent with this random faction, remember as a fallback.
if (!avoid.empty() &&
std::find(avoid.begin(), avoid.end(),
faction_id) != avoid.end()) {
continue;
}
nonrandom_sides.push_back(num); //This side is consistent with this random faction, and the avoid factions argument.
}
}
if (nonrandom_sides.empty()) {
nonrandom_sides = fallback_nonrandom_sides; // There was no way to succeed consistently with the avoid factions argument, so ignore it as a fallback.
}
if (nonrandom_sides.empty()) {
throw config::error(_("Only random sides in the current era."));
}
const int faction_index =
nonrandom_sides[rand() % nonrandom_sides.size()];
nonrandom_sides[rng.get_next_random() % nonrandom_sides.size()];
current_faction_ = available_factions_[faction_index];
update_available_leaders();
@ -275,7 +298,7 @@ void flg_manager::resolve_random() {
"Unable to find a leader type for faction $faction",
i18n_symbols));
} else {
const int lchoice = rand() % nonrandom_leaders.size();
const int lchoice = rng.get_next_random() % nonrandom_leaders.size();
current_leader_ = nonrandom_leaders[lchoice];
update_available_genders();
@ -294,7 +317,7 @@ void flg_manager::resolve_random() {
}
}
const int gchoice = rand() % nonrandom_genders.size();
const int gchoice = rng.get_next_random() % nonrandom_genders.size();
current_gender_ = nonrandom_genders[gchoice];
} else {
utils::string_map i18n_symbols;

View file

@ -20,6 +20,7 @@
#include <vector>
class config;
namespace rand_rng { class mt_rng; }
namespace ng {
@ -55,7 +56,7 @@ public:
void reset_gender_combo(gui::combo& combo_gender) const;
bool is_random_faction();
void resolve_random();
void resolve_random(rand_rng::mt_rng & rng, const std::vector<std::string> & avoid); //Second Argument is a list of faction ids we don't want to match, used to implement random faction modes. If it is not possible to resolve then we just proceed anyways rather than give an error.
// Picks the first faction with the greater amount of data
// matching the criteria.

View file

@ -26,6 +26,7 @@
#include "gui/dialogs/mp_create_game_set_password.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "minimap.hpp"
#include "mp_game_settings.hpp"
#include "multiplayer_configure.hpp"
#include "filesystem.hpp"
#include "log.hpp"
@ -35,6 +36,7 @@
#include "wml_separators.hpp"
#include "formula_string_utils.hpp"
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
@ -82,6 +84,8 @@ configure::configure(game_display& disp, const config &cfg, chat& c, config& gam
observers_game_(disp.video(), _("Observers"), gui::button::TYPE_CHECK),
oos_debug_(disp.video(), _("Debug OOS"), gui::button::TYPE_CHECK),
shuffle_sides_(disp.video(), _("Shuffle sides"), gui::button::TYPE_CHECK),
random_faction_mode_label_(disp.video(), _("Random Factions:"), font::SIZE_SMALL, font::LOBBY_COLOR),
random_faction_mode_(disp, std::vector<std::string>()),
cancel_game_(disp.video(), _("Back")),
launch_game_(disp.video(), _("OK")),
password_button_(disp.video(), _("Set Password...")),
@ -183,6 +187,17 @@ configure::configure(game_display& disp, const config &cfg, chat& c, config& gam
shuffle_sides_.set_check(engine_.shuffle_sides_default());
shuffle_sides_.set_help_string(_("Assign sides to players at random"));
random_faction_mode_label_.set_help_string(_("Allow for mirror matchups when random factions are chosen"));
std::vector<std::string> translated_modes;
for(size_t i = 0; i < mp_game_settings::RANDOM_FACTION_MODE_COUNT; ++i) {
std::string mode_str = mp_game_settings::RANDOM_FACTION_MODE_to_string(static_cast<mp_game_settings::RANDOM_FACTION_MODE> (i));
translated_modes.push_back(translation::gettext(mode_str.c_str()));
}
random_faction_mode_.set_items(translated_modes);
random_faction_mode_.set_selected(engine_.random_faction_mode());
random_faction_mode_.set_help_string(_("Independent: Random factions assigned independently\nNo Mirror: No two players will get the same faction\nNo Ally Mirror: No two allied players will get the same faction"));
#if 0
// The possible vision settings
std::vector<std::string> vision_types;
@ -229,6 +244,7 @@ configure::~configure()
// Save values for next game
DBG_MP << "storing parameter values in preferences" << std::endl;
preferences::set_shuffle_sides(engine_.shuffle_sides());
preferences::set_random_faction_mode(mp_game_settings::RANDOM_FACTION_MODE_to_string(engine_.random_faction_mode()));
preferences::set_use_map_settings(engine_.use_map_settings());
preferences::set_countdown(engine_.mp_countdown());
preferences::set_countdown_init_time(engine_.mp_countdown_init_time());
@ -292,6 +308,7 @@ const mp_game_settings& configure::get_parameters()
engine_.set_allow_observers(observers_game_.checked());
engine_.set_oos_debug(oos_debug_.checked());
engine_.set_shuffle_sides(shuffle_sides_.checked());
engine_.set_random_faction_mode(static_cast<mp_game_settings::RANDOM_FACTION_MODE>(random_faction_mode_.selected()));
engine_.set_options(options_manager_.get_values());
@ -483,6 +500,8 @@ void configure::hide_children(bool hide)
observers_game_.hide(hide);
oos_debug_.hide(hide);
shuffle_sides_.hide(hide);
random_faction_mode_label_.hide(hide);
random_faction_mode_.hide(hide);
cancel_game_.hide(hide);
launch_game_.hide(hide);
@ -534,24 +553,31 @@ void configure::layout_children(const SDL_Rect& rect)
int slider_width = options_pane_left_.width() - 40;
int xpos_left = 0;
int ypos_left = 0;
unsigned int xpos_left = 0;
unsigned int ypos_left = 0;
ypos_left += 2 * border_size;
options_pane_left_.add_widget(&observers_game_, xpos_left, ypos_left);
options_pane_left_.add_widget(&shuffle_sides_,
options_pane_left_.add_widget(&shuffle_sides_, xpos_left, ypos_left);
options_pane_left_.add_widget(&observers_game_,
xpos_left + (options_pane_left_.width() - xpos_left) / 2 + border_size, ypos_left);
ypos_left += shuffle_sides_.height() + border_size;
options_pane_left_.add_widget(&countdown_game_, xpos_left, ypos_left);
options_pane_left_.add_widget(&random_faction_mode_label_, xpos_left, ypos_left);
xpos_left += random_faction_mode_label_.width() + border_size;
options_pane_left_.add_widget(&random_faction_mode_, xpos_left, ypos_left);
xpos_left += random_faction_mode_.width() + border_size;
if(!local_players_only_) {
options_pane_left_.add_widget(&password_button_,
(ca.x + first_column_width / 2) - 40, ypos_left);
std::max(xpos_left + 2* border_size, (options_pane_left_.width() / 2) + border_size), ypos_left);
} else {
password_button_.hide(true);
}
xpos_left = 0;
ypos_left += random_faction_mode_.height() + border_size;
options_pane_left_.add_widget(&countdown_game_, xpos_left, ypos_left);
ypos_left += countdown_game_.height() + border_size;
options_pane_left_.add_widget(&countdown_init_time_label_, xpos_left, ypos_left );

View file

@ -79,6 +79,9 @@ private:
gui::button observers_game_;
gui::button oos_debug_;
gui::button shuffle_sides_;
gui::label random_faction_mode_label_;
gui::combo random_faction_mode_;
gui::button cancel_game_;
gui::button launch_game_;
gui::button password_button_;

View file

@ -568,6 +568,14 @@ void set_shuffle_sides(bool value)
preferences::set("shuffle_sides", value);
}
std::string random_faction_mode(){
return preferences::get("random_faction_mode");
}
void set_random_faction_mode(const std::string & value) {
preferences::set("random_faction_mode", value);
}
bool use_map_settings()
{
return preferences::get("mp_use_map_settings", true);

View file

@ -125,6 +125,9 @@ class acquaintance;
bool shuffle_sides();
void set_shuffle_sides(bool value);
std::string random_faction_mode();
void set_random_faction_mode(const std::string & value);
bool use_map_settings();
void set_use_map_settings(bool value);

View file

@ -51,6 +51,7 @@ mp_game_settings::mp_game_settings() :
allow_observers(false),
shuffle_sides(false),
saved_game(false),
random_faction_mode(DEFAULT),
options()
{}
@ -84,6 +85,7 @@ mp_game_settings::mp_game_settings(const config& cfg)
, allow_observers(cfg["observer"].to_bool())
, shuffle_sides(cfg["shuffle_sides"].to_bool())
, saved_game(cfg["savegame"].to_bool())
, random_faction_mode(string_to_RANDOM_FACTION_MODE_default(cfg["random_faction_mode"].str(), DEFAULT))
, options(cfg.child_or_empty("options"))
{
}
@ -118,6 +120,7 @@ config mp_game_settings::to_config() const
cfg["mp_random_start_time"] = random_start_time;
cfg["observer"] = allow_observers;
cfg["shuffle_sides"] = shuffle_sides;
cfg["random_faction_mode"] = RANDOM_FACTION_MODE_to_string (random_faction_mode);
cfg["savegame"] = saved_game;
cfg.add_child("options", options);

View file

@ -18,6 +18,8 @@
#define MP_GAME_SETTINGS_HPP_INCLUDED
#include "config.hpp"
#include "gettext.hpp"
#include "make_enum.hpp"
#include "savegame_config.hpp"
struct mp_game_settings : public savegame::savegame_config
@ -61,7 +63,17 @@ struct mp_game_settings : public savegame::savegame_config
bool saved_game;
MAKE_ENUM(RANDOM_FACTION_MODE,
(DEFAULT, N_("Independent"))
(NO_MIRROR, N_("No Mirror"))
(NO_ALLY_MIRROR, N_("No Ally Mirror"))
)
RANDOM_FACTION_MODE random_faction_mode;
config options;
};
MAKE_ENUM_STREAM_OPS2(mp_game_settings, RANDOM_FACTION_MODE)
#endif

View file

@ -21,6 +21,7 @@
#include "game_initialization/multiplayer_connect.hpp"
#include "game_initialization/multiplayer_ui.hpp"
#include "hotkey/hotkey_manager.hpp"
#include "mt_rng.hpp"
#include "saved_game.hpp"
#include <boost/foreach.hpp>
@ -53,6 +54,7 @@ namespace {
boost::scoped_ptr<game_display> disp;
boost::scoped_ptr<saved_game> state;
boost::scoped_ptr<rand_rng::mt_rng> rng;
}
@ -87,6 +89,8 @@ struct mp_connect_fixture {
game_config().find_child(lexical_cast<std::string>(game_classification::MULTIPLAYER), "id", state->mp_settings().name));
state->mp_settings().num_turns = state->get_starting_pos()["turns"];
rng.reset(new rand_rng::mt_rng());
}
~mp_connect_fixture()
{
@ -299,7 +303,7 @@ BOOST_AUTO_TEST_CASE( flg_map_settings )
side.clear();
side["faction"] = "Random";
side_engine.reset(create_side_engine(side, connect_engine.get()));
side_engine->resolve_random();
side_engine->resolve_random(*rng);
BOOST_CHECK( side_engine->flg().current_faction()["id"] != "Random" );
BOOST_CHECK( side_engine->flg().current_leader() != "random" &&
side_engine->flg().current_leader() != "null");
@ -311,7 +315,7 @@ BOOST_AUTO_TEST_CASE( flg_map_settings )
side["faction"] = "Random";
side["type"] = "Troll";
side_engine.reset(create_side_engine(side, connect_engine.get()));
side_engine->resolve_random();
side_engine->resolve_random(*rng);
BOOST_CHECK( side_engine->flg().current_faction()["id"] != "Random" );
BOOST_CHECK_EQUAL( side_engine->flg().current_leader(), "Troll" );
BOOST_CHECK( side_engine->flg().current_gender() != "random" &&
@ -323,7 +327,7 @@ BOOST_AUTO_TEST_CASE( flg_map_settings )
side["type"] = "White Mage";
side["gender"] = "male";
side_engine.reset(create_side_engine(side, connect_engine.get()));
side_engine->resolve_random();
side_engine->resolve_random(*rng);
BOOST_CHECK( side_engine->flg().current_faction()["id"] != "Random" );
BOOST_CHECK_EQUAL( side_engine->flg().current_leader(), "White Mage" );
BOOST_CHECK_EQUAL( side_engine->flg().current_gender(), "male" );
@ -332,7 +336,7 @@ BOOST_AUTO_TEST_CASE( flg_map_settings )
side.clear();
side["type"] = "random";
side_engine.reset(create_side_engine(side, connect_engine.get()));
side_engine->resolve_random();
side_engine->resolve_random(*rng);
BOOST_CHECK( side_engine->flg().current_leader() != "random" );
}