diff --git a/src/cavegen.cpp b/src/cavegen.cpp new file mode 100644 index 00000000000..49c1115bc54 --- /dev/null +++ b/src/cavegen.cpp @@ -0,0 +1,291 @@ +#include "cavegen.hpp" +#include "pathfind.hpp" +#include "util.hpp" + +cave_map_generator::cave_map_generator(const config& cfg) : wall_('W'), clear_('u'), village_('D'), castle_('C'), + cfg_(NULL), width_(50), height_(50) +{ + cfg_ = cfg.find_child("map_generator","name",name()); + + if(cfg_ == NULL) { + static const config default_cfg; + cfg_ = &default_cfg; + } + + width_ = atoi((*cfg_)["map_width"].c_str()); + height_ = atoi((*cfg_)["map_height"].c_str()); +} + +bool cave_map_generator::allow_user_config() const { return true; } + +void cave_map_generator::user_config(display& disp) { return; } + +std::string cave_map_generator::name() const { return "cave"; } + +std::string cave_map_generator::create_map(const std::vector& args) +{ + const config res = create_scenario(args); + return res["map_data"]; +} + +config cave_map_generator::create_scenario(const std::vector& args) +{ + map_ = std::vector >(width_,std::vector(height_,wall_)); + chambers_.clear(); + passages_.clear(); + res_.clear(); + + std::cerr << "creating scenario....\n"; + generate_chambers(); + + std::cerr << "placing chambers...\n"; + for(std::vector::const_iterator c = chambers_.begin(); c != chambers_.end(); ++c) { + place_chamber(*c); + } + + std::cerr << "placing passages...\n"; + + for(std::vector::const_iterator p = passages_.begin(); p != passages_.end(); ++p) { + place_passage(*p); + } + + std::cerr << "outputting map....\n"; + std::stringstream out; + for(size_t y = 0; y != height_; ++y) { + for(size_t x = 0; x != width_; ++x) { + out << map_[x][y]; + } + + out << "\n"; + } + + res_["map_data"] = out.str(); + + std::cerr << "returning result...\n"; + + return res_; +} + +void cave_map_generator::build_chamber(gamemap::location loc, std::set& locs, size_t size, size_t jagged) +{ + if(size == 0 || locs.count(loc) != 0 || !on_board(loc)) + return; + + locs.insert(loc); + + gamemap::location adj[6]; + get_adjacent_tiles(loc,adj); + for(size_t n = 0; n != 6; ++n) { + if((rand()%100) < (100-jagged)) { + build_chamber(adj[n],locs,size-1,jagged); + } + } +} + +void cave_map_generator::generate_chambers() +{ + const config::child_list& chambers = cfg_->get_children("chamber"); + for(config::child_list::const_iterator i = chambers.begin(); i != chambers.end(); ++i) { + //if there is only a chance of the chamber appearing, deal with that here. + const std::string& chance = (**i)["chance"]; + if(chance != "" && (rand()%100) < atoi(chance.c_str())) { + continue; + } + + const std::string& xpos = (**i)["x"]; + const std::string& ypos = (**i)["y"]; + + size_t min_xpos = 0, min_ypos = 0, max_xpos = width_, max_ypos = height_; + + if(xpos != "") { + const std::vector& items = config::split(xpos,'-'); + if(items.empty() == false) { + min_xpos = atoi(items.front().c_str()) - 1; + max_xpos = atoi(items.back().c_str()); + } + } + + if(ypos != "") { + const std::vector& items = config::split(ypos,'-'); + if(items.empty() == false) { + min_ypos = atoi(items.front().c_str()) - 1; + max_ypos = atoi(items.back().c_str()); + } + } + + const size_t x = min_xpos + (rand()%(max_xpos-min_xpos)); + const size_t y = min_ypos + (rand()%(max_ypos-min_ypos)); + + const std::string& size = (**i)["size"]; + size_t chamber_size = 3; + if(size != "") { + chamber_size = atoi(size.c_str()); + } + + const std::string& jagged = (**i)["jagged"]; + size_t jagged_edges = 0; + if(jagged != "") { + jagged_edges = atoi(jagged.c_str()); + } + + chamber new_chamber; + new_chamber.center = gamemap::location(x,y); + build_chamber(new_chamber.center,new_chamber.locs,chamber_size,jagged_edges); + + new_chamber.items = (**i).child("items"); + + const std::string& id = (**i)["id"]; + if(id != "") { + chamber_ids_[id] = chambers_.size(); + } + + chambers_.push_back(new_chamber); + + const config::child_list& passages = (**i).get_children("passage"); + for(config::child_list::const_iterator p = passages.begin(); p != passages.end(); ++p) { + const std::string& dst = (**p)["destination"]; + + //find the destination of this passage + const std::map::const_iterator itor = chamber_ids_.find(dst); + if(itor == chamber_ids_.end()) + continue; + + assert(itor->second < chambers_.size()); + + passages_.push_back(passage(new_chamber.center,chambers_[itor->second].center,**p)); + } + } +} + +void cave_map_generator::place_chamber(const chamber& c) +{ + for(std::set::const_iterator i = c.locs.begin(); i != c.locs.end(); ++i) { + set_terrain(*i,clear_); + } + + if(c.items != NULL) { + place_items(c,c.items->ordered_begin(),c.items->ordered_end()); + } +} + +void cave_map_generator::place_items(const chamber& c, config::all_children_iterator i1, config::all_children_iterator i2) +{ + if(c.locs.empty()) { + return; + } + + size_t index = 0; + while(i1 != i2) { + const std::string& key = *(*i1).first; + config cfg = *(*i1).second; + if(cfg["same_location_as_previous"] != "yes") { + index = rand()%c.locs.size(); + } + + std::set::const_iterator loc = c.locs.begin(); + std::advance(loc,index); + + char buf[50]; + sprintf(buf,"%d",loc->x+1); + cfg.values["x"] = buf; + + sprintf(buf,"%d",loc->y+1); + cfg.values["y"] = buf; + + //if this is a side, place a castle for the side + if(key == "side" && cfg["no_castle"] != "yes") { + place_castle(cfg["side"],*loc); + } + + res_.add_child(key,cfg); + + ++i1; + } +} + +struct passage_path_calculator +{ + passage_path_calculator(const std::vector >& mapdata, gamemap::TERRAIN wall, double laziness, size_t windiness) + : map_(mapdata), wall_(wall), laziness_(laziness), windiness_(windiness) + {} + + double cost(const gamemap::location& loc, double so_far) const; +private: + const std::vector >& map_; + gamemap::TERRAIN wall_; + double laziness_; + size_t windiness_; +}; + +double passage_path_calculator::cost(const gamemap::location& loc, double so_far) const +{ + if(loc.x < 0 || loc.y < 0 || size_t(loc.x) >= map_.size() || map_.empty() || size_t(loc.y) >= map_.front().size()) { + return 100000.0; + } + + double res = 1.0; + if(map_[loc.x][loc.y] == wall_) { + res = laziness_; + } + + if(windiness_ > 1) { + res *= double(rand()%windiness_); + } + + return res; +} + +void cave_map_generator::place_passage(const passage& p) +{ + const std::string& chance = p.cfg["chance"]; + if(chance != "" && (rand()%100) < atoi(chance.c_str())) { + return; + } + + + const size_t windiness = atoi(p.cfg["windiness"].c_str()); + const double laziness = maximum(1.0,atof(p.cfg["laziness"].c_str())); + + passage_path_calculator calc(map_,wall_,laziness,windiness); + + const paths::route rt = a_star_search(p.src,p.dst,10000.0,calc); + + const size_t width = maximum(1,atoi(p.cfg["width"].c_str())); + const size_t jagged = atoi(p.cfg["jagged"].c_str()); + + for(std::vector::const_iterator i = rt.steps.begin(); i != rt.steps.end(); ++i) { + std::set locs; + build_chamber(*i,locs,width,jagged); + for(std::set::const_iterator j = locs.begin(); j != locs.end(); ++j) { + set_terrain(*j,clear_); + } + } +} + +bool cave_map_generator::on_board(const gamemap::location& loc) const +{ + return loc.x >= 0 && loc.y >= 0 && loc.x < width_ && loc.y < height_; +} + +void cave_map_generator::set_terrain(gamemap::location loc, gamemap::TERRAIN t) +{ + if(on_board(loc)) { + gamemap::TERRAIN& c = map_[loc.x][loc.y]; + if(c == clear_ || c == wall_) { + c = t; + } + } +} + +void cave_map_generator::place_castle(const std::string& side, gamemap::location loc) +{ + if(side != "") { + set_terrain(loc,side[0]); + } + + gamemap::location adj[6]; + get_adjacent_tiles(loc,adj); + for(size_t n = 0; n != 6; ++n) { + set_terrain(adj[n],castle_); + } +} \ No newline at end of file diff --git a/src/cavegen.hpp b/src/cavegen.hpp new file mode 100644 index 00000000000..09cb0a44157 --- /dev/null +++ b/src/cavegen.hpp @@ -0,0 +1,59 @@ +#ifndef CAVEGEN_HPP_INCLUDED +#define CAVEGEN_HPP_INCLUDED + +#include "mapgen.hpp" + +class cave_map_generator : public map_generator +{ +public: + cave_map_generator(const config& game_config); + + bool allow_user_config() const; + void user_config(display& disp); + + std::string name() const; + + std::string create_map(const std::vector& args); + config create_scenario(const std::vector& args); + +private: + + struct chamber { + gamemap::location center; + std::set locs; + config* items; + }; + + struct passage { + passage(gamemap::location s, gamemap::location d, const config& c) + : src(s), dst(d), cfg(c) + {} + gamemap::location src, dst; + config cfg; + }; + + void generate_chambers(); + void build_chamber(gamemap::location loc, std::set& locs, size_t size, size_t jagged); + + void place_chamber(const chamber& c); + void place_items(const chamber& c, config::all_children_iterator i1, config::all_children_iterator i2); + + void place_passage(const passage& p); + + bool on_board(const gamemap::location& loc) const; + void set_terrain(gamemap::location loc, gamemap::TERRAIN t); + void place_castle(const std::string& side, gamemap::location loc); + + gamemap::TERRAIN wall_, clear_, village_, castle_; + std::vector > map_; + + std::map chamber_ids_; + std::vector chambers_; + std::vector passages_; + + config res_; + const config* cfg_; + size_t width_, height_; +}; + +#endif \ No newline at end of file diff --git a/src/statistics.cpp b/src/statistics.cpp new file mode 100644 index 00000000000..aafcf5a038f --- /dev/null +++ b/src/statistics.cpp @@ -0,0 +1,182 @@ +#include "statistics.hpp" + +namespace { + +typedef statistics::stats stats; + +struct scenario_stats +{ + scenario_stats(const std::string& name) : scenario_name(name) + {} + + std::vector team_stats; + std::string scenario_name; +}; + +std::vector master_stats; + +stats& get_stats(int team) +{ + if(master_stats.empty()) { + master_stats.push_back(scenario_stats("")); + } + + std::vector& team_stats = master_stats.back().team_stats; + const size_t index = size_t(team-1); + if(index >= team_stats.size()) { + team_stats.resize(index+1); + } + + return team_stats[index]; +} + +void merge_str_int_map(stats::str_int_map& a, const stats::str_int_map& b) +{ + for(stats::str_int_map::const_iterator i = b.begin(); i != b.end(); ++i) { + a[i->first] += i->second; + } +} + +void merge_battle_result_maps(stats::battle_result_map& a, const stats::battle_result_map& b) +{ + for(stats::battle_result_map::const_iterator i = b.begin(); i != b.end(); ++i) { + merge_str_int_map(a[i->first],i->second); + } +} + +void merge_stats(stats& a, const stats& b) +{ + merge_str_int_map(a.recruits,b.recruits); + merge_str_int_map(a.recalls,b.recalls); + merge_str_int_map(a.advanced_to,b.advanced_to); + merge_str_int_map(a.deaths,b.deaths); + merge_str_int_map(a.killed,b.killed); + + merge_battle_result_maps(a.attacks,b.attacks); + merge_battle_result_maps(a.defends,b.defends); + + a.recruit_cost += b.recruit_cost; + a.recall_cost += b.recall_cost; + a.damage_inflicted += b.damage_inflicted; + a.damage_taken += b.damage_taken; +} + +} + +namespace statistics +{ + +stats::stats() : recruit_cost(0), recall_cost(0), damage_inflicted(0), damage_taken(0) +{} + +scenario_context::scenario_context(const std::string& name) +{ + master_stats.push_back(scenario_stats(name)); +} + +scenario_context::~scenario_context() +{ +} + +attack_context::attack_context(const unit& a, const unit& d, const battle_stats& stats) + : attacker_type(a.type().name()), defender_type(d.type().name()), + bat_stats(stats), attacker_side(a.side()), defender_side(d.side()) +{ +} + +attack_context::~attack_context() +{ + attacker_stats().attacks[bat_stats.chance_to_hit_defender][attacker_res]++; + defender_stats().defends[bat_stats.chance_to_hit_attacker][defender_res]++; +} + +stats& attack_context::attacker_stats() +{ + return get_stats(attacker_side); +} + +stats& attack_context::defender_stats() +{ + return get_stats(defender_side); +} + +void attack_context::attack_result(attack_context::ATTACK_RESULT res) +{ + attacker_res.resize(attacker_res.size()+1); + attacker_res[attacker_res.size()-1] = (res == MISSES ? '0' : '1'); + + attacker_stats().damage_inflicted += bat_stats.damage_defender_takes; + defender_stats().damage_taken += bat_stats.damage_defender_takes; + + if(res == KILLS) { + attacker_stats().killed[defender_type]++; + defender_stats().deaths[attacker_type]++; + } +} + +void attack_context::defend_result(attack_context::ATTACK_RESULT res) +{ + defender_res.resize(defender_res.size()+1); + defender_res[defender_res.size()-1] = (res == MISSES ? '0' : '1'); + + attacker_stats().damage_taken += bat_stats.damage_attacker_takes; + defender_stats().damage_inflicted += bat_stats.damage_defender_takes; + + if(res == KILLS) { + attacker_stats().deaths[defender_type]++; + defender_stats().killed[attacker_type]++; + } +} + +void recruit_unit(const unit& u) +{ + stats& s = get_stats(u.side()); + s.recruits[u.type().name()]++; + s.recruit_cost += u.type().cost(); +} + +void recall_unit(const unit& u) +{ + stats& s = get_stats(u.side()); + s.recalls[u.type().name()]++; + s.recall_cost += u.type().cost(); +} + +void advance_unit(const unit& u) +{ + stats& s = get_stats(u.side()); + s.advanced_to[u.type().name()]++; +} + +std::vector get_categories() +{ + std::vector res; + res.push_back("all_statistics"); + for(std::vector::const_iterator i = master_stats.begin(); i != master_stats.end(); ++i) { + res.push_back(i->scenario_name); + } + + return res; +} + +stats calculate_stats(int category, int side) +{ + if(category == 0) { + stats res; + for(int i = 1; i <= int(master_stats.size()); ++i) { + merge_stats(res,calculate_stats(i,side)); + } + + return res; + } else { + const size_t index = master_stats.size() - size_t(side); + const size_t side_index = size_t(side) - 1; + if(index < master_stats.size() && side_index < master_stats[index].team_stats.size()) { + return master_stats[index].team_stats[side_index]; + } else { + return stats(); + } + } +} + +} \ No newline at end of file diff --git a/src/statistics.hpp b/src/statistics.hpp new file mode 100644 index 00000000000..ce10c12b9c0 --- /dev/null +++ b/src/statistics.hpp @@ -0,0 +1,68 @@ +#ifndef STATISTICS_HPP_INCLUDED +#define STATISTICS_HPP_INCLUDED + +#include "actions.hpp" +#include "unit.hpp" + +namespace statistics +{ + struct stats + { + stats(); + + typedef std::map str_int_map; + str_int_map recruits, recalls, advanced_to, deaths, killed; + int recruit_cost, recall_cost; + + //a type that will map a string of hit/miss to the number of times + //that sequence has occurred + typedef str_int_map battle_sequence_frequency_map; + + //a type that will map different % chances to hit to different results + typedef std::map battle_result_map; + + battle_result_map attacks, defends; + + int damage_inflicted, damage_taken; + }; + + struct scenario_context + { + scenario_context(const std::string& name); + ~scenario_context(); + }; + + struct attack_context + { + attack_context(const unit& a, const unit& d, const battle_stats& stats); + ~attack_context(); + + enum ATTACK_RESULT { MISSES, HITS, KILLS }; + + void attack_result(ATTACK_RESULT res); + void defend_result(ATTACK_RESULT res); + + private: + + std::string attacker_type, defender_type; + battle_stats bat_stats; + int attacker_side, defender_side; + std::string attacker_res, defender_res; + + stats& attacker_stats(); + stats& defender_stats(); + }; + + void recruit_unit(const unit& u); + void recall_unit(const unit& u); + + void advance_unit(const unit& u); + + config write_stats(int team); + void read_stats(int team, const config& cfg); + + std::vector get_categories(); + stats calculate_stats(int category, int side); +} + +#endif \ No newline at end of file