widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #17641
[Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
TiborB has proposed merging lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands.
Commit message:
Not ready for merge
Requested reviews:
Widelands Developers (widelands-dev)
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/ai_flag_warehouse_distance/+merge/368544
This is just to trigger the build process
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands.
=== modified file 'src/ai/CMakeLists.txt'
--- src/ai/CMakeLists.txt 2017-11-22 19:00:09 +0000
+++ src/ai/CMakeLists.txt 2019-06-07 13:22:42 +0000
@@ -24,3 +24,4 @@
logic_map_objects
scripting_lua_table
)
+add_subdirectory(test)
=== modified file 'src/ai/ai_help_structs.cc'
--- src/ai/ai_help_structs.cc 2019-04-09 16:43:49 +0000
+++ src/ai/ai_help_structs.cc 2019-06-07 13:22:42 +0000
@@ -974,120 +974,6 @@
return (blocked_fields_.count(coords.hash()) != 0);
}
-// As a policy, we just set some default value, that will be updated later on
-FlagsForRoads::Candidate::Candidate(uint32_t coords, int32_t distance, bool different_economy)
- : coords_hash(coords), air_distance(distance) {
- new_road_possible = false;
- // Just custom values
- new_road_length = kRoadPossiblyBuildable;
- current_road_length = (different_economy) ? kNotConnectedByRoads : kConnectedByRoads;
-}
-
-// Used when sorting cadidate flags from best one
-bool FlagsForRoads::Candidate::operator<(const Candidate& other) const {
- const int32_t other_rs = other.reduction_score();
- const int32_t this_rs = reduction_score();
- return std::tie(other.new_road_possible, other_rs) < std::tie(new_road_possible, this_rs);
-}
-
-bool FlagsForRoads::Candidate::operator==(const Candidate& other) const {
- return coords_hash == other.coords_hash;
-}
-
-int32_t FlagsForRoads::Candidate::reduction_score() const {
- return current_road_length - new_road_length - (new_road_length - air_distance) / 3;
-}
-
-void FlagsForRoads::print() { // this is for debugging and development purposes
- for (auto& candidate_flag : flags_queue) {
- log(" %starget: %3dx%3d, saving: %5d (%3d), air distance: %3d, new road: %6d, score: %5d "
- "%s\n",
- (candidate_flag.reduction_score() >= min_reduction && candidate_flag.new_road_possible) ?
- "+" :
- " ",
- Coords::unhash(candidate_flag.coords_hash).x,
- Coords::unhash(candidate_flag.coords_hash).y,
- candidate_flag.current_road_length - candidate_flag.new_road_length, min_reduction,
- candidate_flag.air_distance, candidate_flag.new_road_length,
- candidate_flag.reduction_score(),
- (candidate_flag.new_road_possible) ? ", new road possible" : " ");
- }
-}
-
-// Queue is ordered but some target flags are only estimations so we take such a candidate_flag
-// first
-bool FlagsForRoads::get_best_uncalculated(uint32_t* winner) {
- for (auto& candidate_flag : flags_queue) {
- if (!candidate_flag.new_road_possible) {
- *winner = candidate_flag.coords_hash;
- return true;
- }
- }
- return false;
-}
-
-// Road from starting flag to this flag can be built
-void FlagsForRoads::road_possible(Widelands::Coords coords, const uint32_t new_road) {
- for (auto& candidate_flag : flags_queue) {
- if (candidate_flag.coords_hash == coords.hash()) {
- candidate_flag.new_road_length = new_road;
- candidate_flag.new_road_possible = true;
- candidate_flag.reduction_score();
- return;
- }
- }
- NEVER_HERE();
-}
-
-// find_reachable_fields returns duplicates so we deal with them
-bool FlagsForRoads::has_candidate(const uint32_t hash) {
- for (auto& candidate_flag : flags_queue) {
- if (candidate_flag.coords_hash == hash) {
- return true;
- }
- }
- return false;
-}
-
-// Updating walking distance into flags_queue
-void FlagsForRoads::set_cur_road_distance(Widelands::Coords coords, int32_t cur_distance) {
- for (auto& candidate_flag : flags_queue) {
- if (candidate_flag.coords_hash == coords.hash()) {
- candidate_flag.current_road_length = cur_distance;
- candidate_flag.reduction_score();
- return;
- }
- }
-}
-
-// Returns mostly best candidate, as a result of sorting
-bool FlagsForRoads::get_winner(uint32_t* winner_hash) {
- // If AI can ask for 2nd position, but there is only one viable candidate
- // we return the first one of course
- bool has_winner = false;
- for (auto candidate_flag : flags_queue) {
- if (candidate_flag.reduction_score() < min_reduction || !candidate_flag.new_road_possible ||
- candidate_flag.new_road_length * 2 > candidate_flag.current_road_length) {
- continue;
- }
- assert(candidate_flag.air_distance > 0);
- assert(candidate_flag.reduction_score() >= min_reduction);
- assert(candidate_flag.new_road_possible);
- *winner_hash = candidate_flag.coords_hash;
- has_winner = true;
-
- if (std::rand() % 4 > 0) {
- // with probability of 3/4 we accept this flag
- return true;
- }
- }
-
- if (has_winner) {
- return true;
- }
- return false;
-}
-
PlayersStrengths::PlayersStrengths() : update_time(0) {
}
@@ -1417,4 +1303,190 @@
return update_time;
}
+FlagWarehouseDistances::FlagInfo::FlagInfo(const uint32_t gametime,
+ const uint16_t dist,
+ const uint32_t wh) {
+ expiry_time = gametime + kFlagDistanceExpirationPeriod;
+ soft_expiry_time = gametime + kFlagDistanceExpirationPeriod / 2;
+ distance = dist;
+ nearest_warehouse = wh;
+ new_road_prohibited_till = 0;
+}
+FlagWarehouseDistances::FlagInfo::FlagInfo() {
+ expiry_time = 0;
+ distance = 1000;
+ new_road_prohibited_till = 0;
+}
+
+// We are updating the distance info, but not all the time.
+// Always if after soft expiration period, but
+// if below expiration period only when the new value is lower than current on
+// In both cases new expiration times are calculated
+bool FlagWarehouseDistances::FlagInfo::update(const uint32_t gametime,
+ const uint16_t new_distance,
+ const uint32_t nearest_wh) {
+ const uint32_t new_expiry_time = gametime + kFlagDistanceExpirationPeriod;
+
+ if (gametime > soft_expiry_time) {
+ distance = new_distance;
+ expiry_time = new_expiry_time;
+ soft_expiry_time = gametime + kFlagDistanceExpirationPeriod / 2;
+ nearest_warehouse = nearest_wh;
+ return true;
+ } else if (new_distance < distance || (new_distance == distance &&
+ expiry_time < gametime + kFlagDistanceExpirationPeriod)) {
+ distance = new_distance;
+ expiry_time = gametime + kFlagDistanceExpirationPeriod;
+ nearest_warehouse = nearest_wh;
+ return true;
+ }
+ return false;
+}
+
+uint16_t FlagWarehouseDistances::FlagInfo::get(const uint32_t gametime, uint32_t* nw) const {
+ *nw = nearest_warehouse;
+ if (gametime <= expiry_time) {
+ return distance;
+ }
+ return 1000;
+}
+
+void FlagWarehouseDistances::FlagInfo::road_built(const uint32_t gametime) {
+ new_road_prohibited_till = gametime + 60 * 1000;
+}
+
+bool FlagWarehouseDistances::FlagInfo::road_prohibited(const uint32_t gametime) const {
+ return new_road_prohibited_till > gametime;
+}
+
+bool FlagWarehouseDistances::set_distance(const uint32_t flag_coords,
+ const uint16_t distance,
+ uint32_t const gametime,
+ uint32_t const nearest_warehouse) {
+ if (flags_map.count(flag_coords) == 0) {
+ flags_map[flag_coords] =
+ FlagWarehouseDistances::FlagInfo(gametime, distance, nearest_warehouse);
+ return true;
+ }
+ return flags_map[flag_coords].update(gametime, distance, nearest_warehouse);
+}
+
+uint16_t FlagWarehouseDistances::count() const {
+ return flags_map.size();
+}
+
+int16_t
+FlagWarehouseDistances::get_distance(const uint32_t flag_coords, uint32_t gametime, uint32_t* nw) {
+ if (flags_map.count(flag_coords) == 0) {
+ *nw = 0;
+ return 1000; // this is to discourage to build second road from brand new flag...
+ } else {
+ return flags_map[flag_coords].get(gametime, nw);
+ }
+}
+
+void FlagWarehouseDistances::set_road_built(const uint32_t coords_hash, const uint32_t gametime) {
+ if (flags_map.count(coords_hash) == 1) {
+ flags_map[coords_hash].road_built(gametime);
+ }
+}
+
+bool FlagWarehouseDistances::get_road_prohibited(const uint32_t coords_hash,
+ const uint32_t gametime) {
+ if (flags_map.count(coords_hash) == 1) {
+ return flags_map[coords_hash].road_prohibited(gametime);
+ }
+ return false;
+}
+
+bool FlagWarehouseDistances::remove_old_flag(uint32_t gametime) {
+ for (std::map<uint32_t, FlagWarehouseDistances::FlagInfo>::iterator it = flags_map.begin();
+ it != flags_map.end(); it++) {
+ if (it->second.expiry_time + kOldFlagRemoveTime < gametime) {
+ it = flags_map.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+// Returns pointer to winning road, providing the treshold is exceeded
+FlagCandidates::Candidate* FlagCandidates::get_winner(const int16_t treshold) {
+ if (flags_.empty()) {
+ return nullptr;
+ }
+ sort();
+ if (flags_[0].score() < treshold) {
+ return nullptr;
+ }
+ if (!flags_[0].is_buildable()) {
+ return nullptr;
+ }
+ return &flags_[0];
+}
+
+FlagCandidates::Candidate::Candidate(
+ const uint32_t ch, bool de, uint16_t start_f_dist, uint16_t act_dist_to_wh, uint16_t air_dst) {
+ coords_hash = ch;
+ different_economy = de;
+ start_flag_dist_to_wh = start_f_dist;
+ flag_to_flag_road_distance = 0;
+ possible_road_distance = 1000;
+ cand_flag_distance_to_wh = act_dist_to_wh;
+ air_distance = air_dst;
+}
+
+int16_t FlagCandidates::Candidate::score() const { // NOCOM
+ return different_economy * 2000 + (start_flag_dist_to_wh - cand_flag_distance_to_wh) +
+ (flag_to_flag_road_distance - 2 * possible_road_distance);
+}
+
+bool FlagCandidates::set_road_possible(const uint32_t coords_hash, const uint16_t steps) {
+ for (auto& item : flags_) {
+ if (item.coords_hash == coords_hash) {
+ item.possible_road_distance = steps;
+ return true;
+ }
+ }
+ printf("ERROR [set_road_possible] - no such flag\n"); // NOCOM
+ return false;
+}
+
+bool FlagCandidates::set_cur_road_distance(const uint32_t coords, uint16_t dist) {
+ for (auto& item : flags_) {
+ if (item.coords_hash == coords) {
+ item.flag_to_flag_road_distance = dist;
+ return true;
+ }
+ }
+ return false;
+}
+void FlagCandidates::sort() {
+ std::sort(flags_.begin(), flags_.end());
+}
+
+void FlagCandidates::sort_by_air_distance() {
+ std::sort(flags_.begin(), flags_.end(),
+ [](const FlagCandidates::Candidate& lf, const FlagCandidates::Candidate& rf) {
+ return lf.air_distance < rf.air_distance;
+ });
+}
+
+void FlagCandidates::add_flag(const uint32_t coords,
+ const bool different_economy,
+ const uint16_t act_dist_to_wh,
+ const uint16_t air_distance) {
+ flags_.push_back(
+ Candidate(coords, different_economy, start_flag_dist_to_wh, act_dist_to_wh, air_distance));
+}
+
+bool FlagCandidates::has_candidate(const uint32_t coords_hash) {
+ for (auto& item : flags_) {
+ if (item.coords_hash == coords_hash) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace Widelands
=== modified file 'src/ai/ai_help_structs.h'
--- src/ai/ai_help_structs.h 2019-05-25 08:20:22 +0000
+++ src/ai/ai_help_structs.h 2019-06-07 13:22:42 +0000
@@ -107,6 +107,7 @@
kCheckEnemySites,
kManagementUpdate,
kUpdateStats,
+ kWarehouseFlagDist,
kUnset
};
@@ -122,6 +123,11 @@
// TODO(tiborb): this should be replaced by command line switch
constexpr int kFNeuronBitSize = 32;
constexpr int kMutationRatePosition = 42;
+// This is expiration time for distance from a flag to neares warehouse
+constexpr int kFlagDistanceExpirationPeriod = 120 * 1000;
+// If the distance of flag-warehouse was not updated for this time, we presume that the flag
+// does not exist anymore and remove it
+constexpr int kOldFlagRemoveTime = 5 * 60 * 1000;
constexpr uint32_t kNever = std::numeric_limits<uint32_t>::max();
@@ -527,8 +533,10 @@
};
struct WarehouseSiteObserver {
+
Widelands::Warehouse* site;
BuildingObserver* bo;
+ uint32_t flag_distances_last_update;
};
struct ShipObserver {
@@ -766,53 +774,6 @@
std::map<uint32_t, uint32_t> blocked_fields_;
};
-// list of candidate flags to build roads, with some additional logic
-struct FlagsForRoads {
-
- explicit FlagsForRoads(int32_t mr) : min_reduction(mr) {
- }
-
- struct Candidate {
- Candidate();
- Candidate(uint32_t coords, int32_t distance, bool different_economy);
-
- uint32_t coords_hash;
- int32_t new_road_length;
- int32_t current_road_length;
- int32_t air_distance;
-
- bool new_road_possible;
-
- bool operator<(const Candidate& other) const;
- bool operator==(const Candidate& other) const;
- int32_t reduction_score() const;
- };
-
- int32_t min_reduction;
- // This is the core of this object - candidate flags to be ordered by air_distance
- std::deque<Candidate> flags_queue;
-
- void add_flag(Widelands::Coords coords, int32_t air_dist, bool different_economy) {
- flags_queue.push_back(Candidate(coords.hash(), air_dist, different_economy));
- }
-
- uint32_t count() {
- return flags_queue.size();
- }
- bool has_candidate(uint32_t hash);
-
- // This is for debugging and development purposes
- void print();
- // during processing we need to pick first one uprocessed flag (with best score so far)
- bool get_best_uncalculated(uint32_t* winner);
- // When we test candidate flag if road can be built to it, there are two possible outcomes:
- void road_possible(Widelands::Coords coords, uint32_t new_road);
- // Updating walking distance over existing roads
- void set_cur_road_distance(Widelands::Coords coords, int32_t cur_distance);
- // Finally we query the flag that we will build a road to
- bool get_winner(uint32_t* winner_hash);
-};
-
// This is a struct that stores strength of players, info on teams and provides some outputs from
// these data
struct PlayersStrengths {
@@ -891,6 +852,104 @@
PlayerNumber this_player_number;
PlayerNumber this_player_team;
};
+
+// This is a wrapper around map of <Flag coords hash:distance from flag to nearest warehouse>
+// Flags does not have observers so this stuct server like "lazy" substitution for this, where
+// keys are hash of flags positions
+// This "lives" during entire "session", and is not saved, but is easily recalulated
+struct FlagWarehouseDistances {
+private:
+ struct FlagInfo {
+ FlagInfo();
+ FlagInfo(uint32_t, uint16_t, uint32_t);
+ // Distance to a nearest warehouse expires, but in two stages
+ // This is complete expiration and such flag is treated as without distance to WH
+ uint32_t expiry_time;
+ // When recalculating new distance, if the current information is younger than
+ // soft_expiry_time it is only decreased if new value is smaller After soft_expiry_time is
+ // updated in any case
+ uint32_t soft_expiry_time;
+ uint16_t distance;
+ // This is coords hash of nearest warehouse, not used by now
+ uint32_t nearest_warehouse;
+ // after building of new road, the distance information is updated not immediately so
+ // we prohibit current flag from another road building...
+ uint32_t new_road_prohibited_till;
+
+ bool update(uint32_t, uint16_t, uint32_t);
+ // Saying the road was built and when
+ void road_built(uint32_t);
+ // Asking if road can be built from this flag (providing current gametime)
+ bool road_prohibited(uint32_t) const;
+ // get current distance (providing current gametime)
+ uint16_t get(uint32_t, uint32_t*) const;
+ };
+ std::map<uint32_t, FlagInfo> flags_map;
+
+public:
+ // All these function uses lookup in flags_map so first argument is usually flag coords hash
+ bool set_distance(uint32_t, uint16_t, uint32_t, uint32_t);
+ int16_t get_distance(uint32_t, uint32_t, uint32_t*);
+ void set_road_built(uint32_t, uint32_t);
+ bool get_road_prohibited(uint32_t, uint32_t);
+ uint16_t count() const;
+ bool remove_old_flag(uint32_t); // NOCOM add to tests
+};
+
+// This is one-time structure - initiated and filled up when investigating possible roads to be
+// build to a flag At the end the flags are scored based on gained info), ordered and if treshold is
+// achieved the road is to be built
+struct FlagCandidates {
+
+ explicit FlagCandidates(uint16_t wd) : start_flag_dist_to_wh(wd) {
+ }
+
+ struct Candidate {
+ Candidate() = delete;
+ Candidate(uint32_t, bool, uint16_t, uint16_t, uint16_t);
+ uint16_t air_distance;
+ uint32_t coords_hash;
+ bool different_economy;
+ uint16_t start_flag_dist_to_wh;
+ uint16_t flag_to_flag_road_distance;
+ uint16_t possible_road_distance;
+ uint16_t cand_flag_distance_to_wh;
+ // Scoring is considering multiple things
+ int16_t score() const;
+ bool is_buildable() {
+ return possible_road_distance > 0;
+ }
+ bool operator<(const Candidate& other) const {
+ return score() > other.score();
+ }
+ void print() { // NOCOM - remove
+ printf(" Candidate flag at %3dx%3d: dist. to wh %4d, cur. road length: %2d, poss. road "
+ "length: %3d, air dist.: %2d, different ec: %s, score: %4d, \n",
+ Coords::unhash(coords_hash).x, Coords::unhash(coords_hash).y,
+ cand_flag_distance_to_wh, flag_to_flag_road_distance, possible_road_distance,
+ air_distance, (different_economy) ? "Y" : "N", score());
+ }
+ };
+
+private:
+ std::vector<Candidate> flags_;
+ uint16_t start_flag_dist_to_wh;
+
+public:
+ const std::vector<Candidate>& flags() const {
+ return flags_;
+ }
+ bool has_candidate(uint32_t);
+ void add_flag(uint32_t, bool, uint16_t, uint16_t);
+ bool set_cur_road_distance(uint32_t, uint16_t);
+ bool set_road_possible(uint32_t, uint16_t);
+ void sort();
+ void sort_by_air_distance();
+ uint32_t count() {
+ return flags_.size();
+ }
+ FlagCandidates::Candidate* get_winner(const int16_t = 0);
+};
} // namespace Widelands
#endif // end of include guard: WL_AI_AI_HELP_STRUCTS_H
=== modified file 'src/ai/defaultai.cc'
--- src/ai/defaultai.cc 2019-05-26 11:39:41 +0000
+++ src/ai/defaultai.cc 2019-06-07 13:22:42 +0000
@@ -331,7 +331,7 @@
set_taskpool_task_time(gametime + 1000, SchedulerTaskId::kRoadCheck);
// testing 5 roads
{
- const int32_t roads_to_check = (roads.size() < 20) ? 1 : 3;
+ const int32_t roads_to_check = roads.size() / 30 + 1;
for (int j = 0; j < roads_to_check; j += 1) {
// improve_roads function will test one road and rotate roads vector
if (improve_roads(gametime)) {
@@ -486,6 +486,10 @@
update_player_stat(gametime);
set_taskpool_task_time(gametime + kStatUpdateInterval, SchedulerTaskId::kUpdateStats);
break;
+ case SchedulerTaskId::kWarehouseFlagDist:
+ check_flag_distances(gametime);
+ set_taskpool_task_time(gametime + kFlagWarehouseUpdInterval, SchedulerTaskId::kWarehouseFlagDist);
+ break;
case SchedulerTaskId::kUnset:
NEVER_HERE();
}
@@ -958,6 +962,9 @@
taskPool.push_back(SchedulerTask(
std::max<uint32_t>(gametime, 10 * 1000), SchedulerTaskId::kUpdateStats, 15, "review"));
+ taskPool.push_back(SchedulerTask(
+ std::max<uint32_t>(gametime, 10 * 1000), SchedulerTaskId::kWarehouseFlagDist, 5, "Flag-Warehouse Update"));
+
const Map& map = game().map();
// here we scan entire map for own ships
@@ -3422,6 +3429,76 @@
return true;
}
+
+// Re-calculating warehouse to flag distances
+void DefaultAI::check_flag_distances(const uint32_t gametime) {
+ for (WarehouseSiteObserver& wh_obs : warehousesites) {
+ // wh_obs.flag_distances_last_update = gametime; NOCOM
+ uint16_t checked_flags = 0;
+ const uint32_t this_wh_hash = wh_obs.site->get_position().hash();
+ uint32_t highest_distance_set = 0;
+
+ std::queue<Widelands::Flag*>
+ remaining_flags; // only used to collect flags reachable walk over roads
+ remaining_flags.push(&wh_obs.site->base_flag());
+ // const bool distance_set =
+ flag_warehouse_distance.set_distance(
+ wh_obs.site->base_flag().get_position().hash(), 0, gametime, this_wh_hash);
+ uint32_t tmp_wh;
+ assert(flag_warehouse_distance.get_distance(
+ wh_obs.site->base_flag().get_position().hash(), gametime, &tmp_wh) == 0);
+
+ // Algorithm to walk on roads
+ // All nodes are marked as to_be_checked == true first and once the node is checked it is
+ // changed to false. Under some conditions, the same node can be checked twice, the
+ // to_be_checked can be set back to true. Because less hoops (fewer flag-to-flag roads) does
+ // not always mean shortest road.
+ while (!remaining_flags.empty()) {
+ checked_flags += 1;
+ // looking for a node with shortest existing road distance from starting flag and one that
+ // has to be checked Now going over roads leading from this flag
+ const uint16_t current_flag_distance = flag_warehouse_distance.get_distance(
+ remaining_flags.front()->get_position().hash(), gametime, &tmp_wh);
+ for (uint8_t i = 1; i <= 6; ++i) {
+ Road* const road = remaining_flags.front()->get_road(i);
+
+ if (!road) {
+ continue;
+ }
+
+ Flag* endflag = &road->get_flag(Road::FlagStart);
+
+ if (endflag == remaining_flags.front()) {
+ endflag = &road->get_flag(Road::FlagEnd);
+ }
+ const uint16_t steps_count = road->get_path().get_nsteps();
+
+ // Calculated distance can be used or ignored if f.e. longer than via other route
+ bool const updated = flag_warehouse_distance.set_distance(
+ endflag->get_position().hash(), current_flag_distance + steps_count, gametime,
+ this_wh_hash);
+
+ if (highest_distance_set < current_flag_distance + steps_count) {
+ highest_distance_set = current_flag_distance + steps_count;
+ }
+
+ if (updated) {
+ remaining_flags.push(endflag);
+ }
+ }
+ remaining_flags.pop();
+ }
+
+ printf(
+ "%d: Checked flags: %3d, highest distance: %5d, warehouses: %lu, time: %d s\n", // NOCOM
+ player_number(), checked_flags, highest_distance_set, warehousesites.size(),
+ gametime / 1000);
+ }
+
+ // Now let do some lazy prunning - remove the flags that were not updated for long
+ flag_warehouse_distance.remove_old_flag(gametime);
+}
+
// Here we pick about 25 roads and investigate them. If it is a dead end we dismantle it
bool DefaultAI::dismantle_dead_ends() {
bool road_dismantled = false;
@@ -3538,50 +3615,36 @@
}
bool is_warehouse = false;
- bool has_building = false;
if (Building* b = flag.get_building()) {
- has_building = true;
BuildingObserver& bo = get_building_observer(b->descr().name().c_str());
if (bo.type == BuildingObserver::Type::kWarehouse) {
is_warehouse = true;
}
}
+ const uint32_t flag_coords_hash = flag.get_position().hash();
- // is connected to a warehouse?
+ if (flag_warehouse_distance.get_road_prohibited(flag_coords_hash, gametime)) {return false;}
const bool needs_warehouse = flag.get_economy()->warehouses().empty();
- if (!has_building && flag.nr_of_roads() == 1) {
- return false;
- } else if (is_warehouse && flag.nr_of_roads() <= 3) {
- // Do nothing
- } else if (needs_warehouse) {
- // Do nothing
- } else if (flag.current_wares() > 5) {
- // Do nothing
- } else if (has_building && std::rand() % 3 == 0) {
- // Do nothing
- } else if (std::rand() % 10 == 0) {
- // Do nothing
- } else {
- return false;
- }
-
- int16_t expected_shortcut = 27;
- if (has_building) {
- expected_shortcut -= 3;
- }
- expected_shortcut -= flag.current_wares() * 3;
- if (is_warehouse) {
- expected_shortcut -= 10;
- }
- if (std::rand() % 5 == 0) {
- expected_shortcut -= 10;
- }
- expected_shortcut -= 2 * flag.nr_of_roads();
-
- create_shortcut_road(flag, 14, expected_shortcut, gametime);
-
- return true;
+ uint32_t tmp_wh; // NOCOM
+
+ // when deciding if we attempt to build a road from here we use probability NOCOM
+ uint16_t probability_score = 0;
+ if (flag.nr_of_roads() == 1) {probability_score += 20;}
+ if (is_warehouse && flag.nr_of_roads() <= 3) {probability_score += 20;}
+ probability_score += flag.current_wares() * 5;
+ if (needs_warehouse) {probability_score += 500;}
+ if (std::rand() % 10 == 0)
+ probability_score += flag_warehouse_distance.get_distance(flag_coords_hash, gametime, &tmp_wh);
+
+ if (std::rand() % 200 < probability_score) {
+ const bool action_taken = create_shortcut_road(flag, 14, gametime);
+ if (!action_taken) {printf ("failed to build a road\n");}
+ return true;
+ }
+ printf ("not trying to build a road\n");
+ return false;
+
}
// This function takes a road (road is smallest section of roads with two flags on the ends)
@@ -3756,318 +3819,343 @@
// connection is not known
bool DefaultAI::create_shortcut_road(const Flag& flag,
uint16_t checkradius,
- int16_t min_reduction,
uint32_t gametime) {
- // Increasing the failed_connection_tries counter
- // At the same time it indicates a time an economy is without a warehouse
- EconomyObserver* eco = get_economy_observer(flag.economy());
- // if we passed grace time this will be last attempt and if it fails
- // building is destroyes
- bool last_attempt_ = false;
-
- // this should not happen, but if the economy has a warehouse and a dismantle
- // grace time set, we must 'zero' the dismantle grace time
- if (!flag.get_economy()->warehouses().empty() &&
- eco->dismantle_grace_time != std::numeric_limits<uint32_t>::max()) {
- eco->dismantle_grace_time = std::numeric_limits<uint32_t>::max();
- }
-
- // first we deal with situations when this is economy with no warehouses
- // and this is a flag belonging to a building/constructionsite
- // such economy must get dismantle grace time (if not set yet)
- // end sometimes extended checkradius
- if (flag.get_economy()->warehouses().empty() && flag.get_building()) {
-
- // occupied military buildings get special treatment
- // (extended grace time + longer radius)
- bool occupied_military_ = false;
- Building* b = flag.get_building();
- if (upcast(MilitarySite, militb, b)) {
- if (militb->soldier_control()->stationed_soldiers().size() > 0) {
- occupied_military_ = true;
- }
- }
-
- // check if we are within grace time, if not or gracetime unset we need to do something
- // if we are within gracetime we do nothing (this loop is skipped)
-
- // if grace time is not set, this is probably first time without a warehouse and we must set
- // it
- if (eco->dismantle_grace_time == std::numeric_limits<uint32_t>::max()) {
-
- // constructionsites
- if (upcast(ConstructionSite const, constructionsite, flag.get_building())) {
- BuildingObserver& bo =
- get_building_observer(constructionsite->building().name().c_str());
- // first very special case - a port (in the phase of constructionsite)
- // this might be a new colonization port
- if (bo.is(BuildingAttribute::kPort)) {
- eco->dismantle_grace_time = gametime + 60 * 60 * 1000; // one hour should be enough
- } else { // other constructionsites, usually new (standalone) constructionsites
- eco->dismantle_grace_time =
- gametime + 30 * 1000 + // very shot time is enough
- (eco->flags.size() * 30 * 1000); // + 30 seconds for every flag in economy
- }
-
- // buildings
- } else {
-
- if (occupied_military_) {
- eco->dismantle_grace_time =
- (gametime + 90 * 60 * 1000) + (eco->flags.size() * 20 * 1000);
-
- } else { // for other normal buildings
- eco->dismantle_grace_time =
- gametime + (45 * 60 * 1000) + (eco->flags.size() * 20 * 1000);
- }
- }
-
- // we have passed grace_time - it is time to dismantle
- } else if (eco->dismantle_grace_time <= gametime) {
- last_attempt_ = true;
- // we increase a check radius in last attempt
- checkradius += 2;
- }
-
- // and bonus for occupied military buildings:
- if (occupied_military_) {
- checkradius += 4;
- }
-
- // and generally increase radius for unconnected buildings
- checkradius += 2;
- }
-
- // Now own roadfinding stuff
- const Map& map = game().map();
-
- // initializing new object of FlagsForRoads, we will push there all candidate flags
- // First we dont even know if a road can be built there (from current flag)
- Widelands::FlagsForRoads RoadCandidates(min_reduction);
-
- FindNodeWithFlagOrRoad functor;
- CheckStepRoadAI check(player_, MOVECAPS_WALK, true);
-
- // get all flags within radius
- std::vector<Coords> reachable;
- map.find_reachable_fields(
- Area<FCoords>(map.get_fcoords(flag.get_position()), checkradius), &reachable, check, functor);
-
- for (const Coords& reachable_coords : reachable) {
-
- // ignore starting flag, of course
- if (reachable_coords == flag.get_position()) {
- continue;
- }
-
- // first make sure there is an immovable (should be, but still)
- if (upcast(PlayerImmovable const, player_immovable, map[reachable_coords].get_immovable())) {
-
- // if it is the road, make a flag there
- if (dynamic_cast<const Road*>(map[reachable_coords].get_immovable())) {
- game().send_player_build_flag(player_number(), reachable_coords);
- }
-
- // do not go on if it is not a flag
- if (!dynamic_cast<const Flag*>(map[reachable_coords].get_immovable())) {
- continue;
- }
-
- // testing if a flag/road's economy has a warehouse, if not we are not
- // interested to connect to it
- if (player_immovable->economy().warehouses().size() == 0) {
- continue;
- }
-
- // This is a candidate, sending all necessary info to RoadCandidates
- const bool different_economy = (player_immovable->get_economy() != flag.get_economy());
- const int32_t air_distance = map.calc_distance(flag.get_position(), reachable_coords);
- if (!RoadCandidates.has_candidate(reachable_coords.hash())) {
- RoadCandidates.add_flag(reachable_coords, air_distance, different_economy);
- }
- }
- }
-
- // now we walk over roads and if field is reachable by roads, we change the distance assigned
- // above
- std::map<uint32_t, NearFlag> nearflags; // only used to collect flags reachable walk over roads
- nearflags[flag.get_position().hash()] = NearFlag(&flag, 0);
-
- // Algorithm to walk on roads
- // All nodes are marked as to_be_checked == true first and once the node is checked it is changed
- // to false. Under some conditions, the same node can be checked twice, the to_be_checked can
- // be set back to true. Because less hoops (fewer flag-to-flag roads) does not always mean
- // shortest
- // road.
- for (;;) {
- // looking for a node with shortest existing road distance from starting flag and one that has
- // to be checked
- uint32_t start_field = std::numeric_limits<uint32_t>::max();
- uint32_t nearest_distance = 10000;
- for (auto item : nearflags) {
- if (item.second.current_road_distance < nearest_distance && item.second.to_be_checked) {
- nearest_distance = item.second.current_road_distance;
- start_field = item.first;
- }
- }
- // OK, we failed to find a NearFlag where to_be_checked == true, so quitting the loop now
- if (start_field == std::numeric_limits<uint32_t>::max()) {
- break;
- }
-
- nearflags[start_field].to_be_checked = false;
-
- // Now going over roads leading from this flag
- for (uint8_t i = 1; i <= 6; ++i) {
- Road* const road = nearflags[start_field].flag->get_road(i);
-
- if (!road) {
- continue;
- }
-
- Flag* endflag = &road->get_flag(Road::FlagStart);
-
- if (endflag == nearflags[start_field].flag) {
- endflag = &road->get_flag(Road::FlagEnd);
- }
-
- const uint32_t endflag_hash = endflag->get_position().hash();
-
- const int32_t dist = map.calc_distance(flag.get_position(), endflag->get_position());
-
- if (dist > checkradius + 2) { // Testing bigger vicinity then checkradius....
- continue;
- }
-
- // There is few scenarios for this neighbour flag
- if (nearflags.count(endflag_hash) == 0) {
- // This is brand new flag
- nearflags[endflag_hash] =
- NearFlag(endflag, nearflags[start_field].current_road_distance +
- road->get_path().get_nsteps());
- } else {
- // We know about this flag already
- if (nearflags[endflag_hash].current_road_distance >
- nearflags[start_field].current_road_distance + road->get_path().get_nsteps()) {
- // ..but this current connection is shorter than one found before
- nearflags[endflag_hash].current_road_distance =
- nearflags[start_field].current_road_distance + road->get_path().get_nsteps();
- // So let re-check neighbours once more
- nearflags[endflag_hash].to_be_checked = true;
- }
- }
- }
- }
-
- // Sending calculated walking costs from nearflags to RoadCandidates to update info on
- // Candidate flags/roads
- for (auto& nf_walk : nearflags) {
- // NearFlag contains also flags beyond check radius, these are not relevent for us
- if (RoadCandidates.has_candidate(nf_walk.second.flag->get_position().hash())) {
- RoadCandidates.set_cur_road_distance(
- nf_walk.second.flag->get_position(), nf_walk.second.current_road_distance);
- }
- }
-
- // Here we must consider how much are buildable fields lacking
- // the number will be transformed to a weight passed to findpath function
- int32_t fields_necessity = 0;
- if (spots_ < kSpotsTooLittle) {
- fields_necessity += 10;
- }
- if (map_allows_seafaring_ && num_ports == 0) {
- fields_necessity += 10;
- }
- if (num_ports < 4) {
- fields_necessity += 5;
- }
- if (spots_ < kSpotsEnough) {
- fields_necessity += 5;
- }
- fields_necessity *= std::abs(management_data.get_military_number_at(64)) * 5;
-
- // We need to sort these flags somehow, because we are not going to investigate all of them
- // so sorting first by current road length (that might contain fake values for flags that were
- // not reached over roads) and secondary by air_distance (nearer flags first)
- std::sort(std::begin(RoadCandidates.flags_queue), std::end(RoadCandidates.flags_queue),
- [](const FlagsForRoads::Candidate& a, const FlagsForRoads::Candidate& b) {
- // Here we are doing a kind of bucketing
- const int32_t a_length = a.current_road_length / 50;
- const int32_t b_length = b.current_road_length / 50;
- return std::tie(b_length, a.air_distance) < std::tie(a_length, b.air_distance);
- });
-
- // Now we calculate roads from here to few best looking RoadCandidates....
- uint32_t possible_roads_count = 0;
- uint32_t current = 0; // hash of flag that we are going to calculate in the iteration
- while (possible_roads_count < 5 && RoadCandidates.get_best_uncalculated(¤t)) {
- const Widelands::Coords coords = Coords::unhash(current);
- Path path;
-
- // value of pathcost is not important, it just indicates, that the path can be built
- // We send this information to RoadCandidates, with length of possible road if applicable
- const int32_t pathcost =
- map.findpath(flag.get_position(), coords, 0, path, check, 0, fields_necessity);
- if (pathcost >= 0) {
- RoadCandidates.road_possible(coords, path.get_nsteps());
- possible_roads_count += 1;
- }
- }
-
- // re-sorting again, now by reduction score (custom operator specified in .h file)
- std::sort(std::begin(RoadCandidates.flags_queue), std::end(RoadCandidates.flags_queue));
-
- // Well and finally building the winning road (if any)
- uint32_t winner_hash = 0;
- if (RoadCandidates.get_winner(&winner_hash)) {
- const Widelands::Coords target_coords = Coords::unhash(winner_hash);
- Path& path = *new Path();
+ // Increasing the failed_connection_tries counter
+ // At the same time it indicates a time an economy is without a warehouse
+ EconomyObserver* eco = get_economy_observer(flag.economy());
+ // if we passed grace time this will be last attempt and if it fails
+ // building is destroyes
+ bool last_attempt_ = false;
+
+ // this should not happen, but if the economy has a warehouse and a dismantle
+ // grace time set, we must 'zero' the dismantle grace time
+ if (!flag.get_economy()->warehouses().empty() &&
+ eco->dismantle_grace_time != std::numeric_limits<uint32_t>::max()) {
+ eco->dismantle_grace_time = std::numeric_limits<uint32_t>::max();
+ }
+
+ // first we deal with situations when this is economy with no warehouses
+ // and this is a flag belonging to a building/constructionsite
+ // such economy must get dismantle grace time (if not set yet)
+ // end sometimes extended checkradius
+ if (flag.get_economy()->warehouses().empty() && flag.get_building()) {
+
+ // occupied military buildings get special treatment
+ // (extended grace time + longer radius)
+ bool occupied_military_ = false;
+ Building* b = flag.get_building();
+ if (upcast(MilitarySite, militb, b)) {
+ if (militb->soldier_control()->stationed_soldiers().size() > 0) {
+ occupied_military_ = true;
+ }
+ }
+
+ // check if we are within grace time, if not or gracetime unset we need to do something
+ // if we are within gracetime we do nothing (this loop is skipped)
+
+ // if grace time is not set, this is probably first time without a warehouse and we must set
+ // it
+ if (eco->dismantle_grace_time == std::numeric_limits<uint32_t>::max()) {
+
+ // constructionsites
+ if (upcast(ConstructionSite const, constructionsite, flag.get_building())) {
+ BuildingObserver& bo =
+ get_building_observer(constructionsite->building().name().c_str());
+ // first very special case - a port (in the phase of constructionsite)
+ // this might be a new colonization port
+ if (bo.is(BuildingAttribute::kPort)) {
+ eco->dismantle_grace_time = gametime + 60 * 60 * 1000; // one hour should be enough
+ } else { // other constructionsites, usually new (standalone) constructionsites
+ eco->dismantle_grace_time =
+ gametime + 30 * 1000 + // very shot time is enough
+ (eco->flags.size() * 30 * 1000); // + 30 seconds for every flag in economy
+ }
+
+ // buildings
+ } else {
+
+ if (occupied_military_) {
+ eco->dismantle_grace_time =
+ (gametime + 90 * 60 * 1000) + (eco->flags.size() * 20 * 1000);
+
+ } else { // for other normal buildings
+ eco->dismantle_grace_time =
+ gametime + (45 * 60 * 1000) + (eco->flags.size() * 20 * 1000);
+ }
+ }
+
+ // we have passed grace_time - it is time to dismantle
+ } else if (eco->dismantle_grace_time <= gametime) {
+ last_attempt_ = true;
+ // we increase a check radius in last attempt
+ checkradius += 2;
+ }
+
+ // and bonus for occupied military buildings:
+ if (occupied_military_) {
+ checkradius += 4;
+ }
+
+ // and generally increase radius for unconnected buildings
+ checkradius += 2;
+ }
+
+ // Now own roadfinding stuff
+ const Map& map = game().map();
+
+ // Initializing new object of FlagsForRoads, we will push there all candidate flags
+ // First we dont even know if a road can be built there (from current flag)
+ // Adding also distance of this flag to nearest wh
+ uint32_t tmp_wh; // This information is not used, but we need it
+ const uint32_t current_flag_dist_to_wh = flag_warehouse_distance.
+ get_distance(flag.get_position().hash(), gametime, &tmp_wh);
+
+ FlagCandidates flag_candidates(current_flag_dist_to_wh);
+
+ FindNodeWithFlagOrRoad functor;
+ CheckStepRoadAI check(player_, MOVECAPS_WALK, true);
+
+ // get all flags within radius
+ std::vector<Coords> reachable;
+ map.find_reachable_fields(
+ Area<FCoords>(map.get_fcoords(flag.get_position()), checkradius), &reachable, check, functor);
+
+ for (const Coords& reachable_coords : reachable) {
+
+ // ignore starting flag, of course
+ if (reachable_coords == flag.get_position()) {
+ continue;
+ }
+
+ const uint32_t reachable_coords_hash = reachable_coords.hash();
+
+ // first make sure there is an immovable (should be, but still)
+ if (upcast(PlayerImmovable const, player_immovable, map[reachable_coords].get_immovable())) {
+
+ // if it is the road, make a flag there
+ if (dynamic_cast<const Road*>(map[reachable_coords].get_immovable())) {
+ game().send_player_build_flag(player_number(), reachable_coords);
+ }
+
+ // do not go on if it is not a flag
+ if (!dynamic_cast<const Flag*>(map[reachable_coords].get_immovable())) {
+ continue;
+ }
+
+ // testing if a flag/road's economy has a warehouse, if not we are not
+ // interested to connect to it
+ if (player_immovable->economy().warehouses().size() == 0) {
+ continue;
+ }
+
+ // This is a candidate, sending all necessary info to RoadCandidates
+ const bool different_economy = (player_immovable->get_economy() != flag.get_economy());
+ const uint16_t air_distance = map.calc_distance(flag.get_position(), reachable_coords);
+
+ if (!flag_candidates.has_candidate(reachable_coords_hash) && !flag_warehouse_distance.get_road_prohibited(reachable_coords_hash, gametime)) {
+ flag_candidates.add_flag(reachable_coords_hash, different_economy,
+ flag_warehouse_distance.get_distance(reachable_coords_hash, gametime, &tmp_wh), air_distance);
+ }
+ }
+ }
+
+ // now we walk over roads and if field is reachable by roads, we change the distance assigned
+ // above
+ std::map<uint32_t, NearFlag> nearflags; // only used to collect flags reachable walk over roads
+ nearflags[flag.get_position().hash()] = NearFlag(&flag, 0);
+
+ collect_nearflags(nearflags, flag, checkradius);
+
+
+
+ // Sending calculated walking costs from nearflags to RoadCandidates to update info on
+ // Candidate flags/roads
+ for (auto& nf_walk : nearflags) {
+ const uint32_t nf_hash = nf_walk.second.flag->get_position().hash();
+ // NearFlag contains also flags beyond check radius, these are not relevent for us
+ if (flag_candidates.has_candidate(nf_hash)) {
+ flag_candidates.set_cur_road_distance(
+ nf_hash, nf_walk.second.current_road_distance);
+ }
+ }
+
+ // Here we must consider how much are buildable fields lacking
+ // the number will be transformed to a weight passed to findpath function
+ int32_t fields_necessity = 0;
+ if (spots_ < kSpotsTooLittle) {
+ fields_necessity += 10;
+ }
+ if (map_allows_seafaring_ && num_ports == 0) {
+ fields_necessity += 10;
+ }
+ if (num_ports < 4) {
+ fields_necessity += 5;
+ }
+ if (spots_ < kSpotsEnough) {
+ fields_necessity += 5;
+ }
+
+ fields_necessity *= std::abs(management_data.get_military_number_at(64)) * 5;
+
+ // Now we calculate roads from here to few best looking RoadCandidates....
+ flag_candidates.sort_by_air_distance();
+ uint32_t possible_roads_count = 0;
+ for (const auto &flag_candidate : flag_candidates.flags()){
+ if (possible_roads_count > 10) {break;}
+ const Widelands::Coords coords = Coords::unhash(flag_candidate.coords_hash);
+ Path path;
+
+ // value of pathcost is not important, it just indicates, that the path can be built
+ // We send this information to RoadCandidates, with length of possible road if applicable
+ const int32_t pathcost =
+ map.findpath(flag.get_position(), coords, 0, path, check, 0, fields_necessity);
+ if (pathcost >= 0) {
+ flag_candidates.set_road_possible(flag_candidate.coords_hash, path.get_nsteps());
+ possible_roads_count += 1;
+ }
+ }
+
+ // re-sorting again now by default by a score
+ flag_candidates.sort();
+
+
+ // Well and finally building the winning road (if any)
+ const int32_t winner_min_score = (spots_ < kSpotsTooLittle) ? 50 : 25;
+
+ FlagCandidates::Candidate* winner = flag_candidates.get_winner(winner_min_score);
+ if (winner) {
+ const Widelands::Coords target_coords = Coords::unhash(winner->coords_hash);
+ printf("Winner to connect to %3dx%3d: ",flag.get_position(). x,flag.get_position().y); //without new line
+ winner->print();
+
+ // This is to prohibit the flag for some time but with exemption of warehouse
+ if (flag_warehouse_distance.get_distance(winner->coords_hash, gametime, &tmp_wh) > 0) {
+ flag_warehouse_distance.set_road_built(winner->coords_hash, gametime);
+ }
+ // and we stright away set distance of future flag
+ flag_warehouse_distance.set_distance(flag.get_position().hash(), winner->start_flag_dist_to_wh + winner->possible_road_distance,
+ gametime, 0); // faking the warehouse
+
+ Path& path = *new Path();
#ifndef NDEBUG
- const int32_t pathcost =
- map.findpath(flag.get_position(), target_coords, 0, path, check, 0, fields_necessity);
- assert(pathcost >= 0);
+ const int32_t pathcost =
+ map.findpath(flag.get_position(), target_coords, 0, path, check, 0, fields_necessity);
+ assert(pathcost >= 0);
#else
- map.findpath(flag.get_position(), target_coords, 0, path, check, 0, fields_necessity);
+ map.findpath(flag.get_position(), target_coords, 0, path, check, 0, fields_necessity);
#endif
- game().send_player_build_road(player_number(), path);
- return true;
- }
- // We can't build a road so let's block the vicinity as an indication this area is not
- // connectible
- // Usually we block for 2 minutes, but if it is a last attempt we block for 10 minutes
- // Note: we block the vicinity only if this economy (usually a sole flag with a building) is not
- // connected to a warehouse
- if (flag.get_economy()->warehouses().empty()) {
-
- // blocking only if latest block was less then 60 seconds ago or it is last attempt
- if (eco->fields_block_last_time + 60000 < gametime || last_attempt_) {
- eco->fields_block_last_time = gametime;
-
- const uint32_t block_time = last_attempt_ ? 10 * 60 * 1000 : 2 * 60 * 1000;
-
- FindNodeAcceptAll buildable_functor;
- CheckStepOwnTerritory check_own(player_, MOVECAPS_WALK, true);
-
- // get all flags within radius
- std::vector<Coords> reachable_to_block;
- map.find_reachable_fields(Area<FCoords>(map.get_fcoords(flag.get_position()), checkradius),
- &reachable_to_block, check_own, buildable_functor);
-
- for (auto coords : reachable_to_block) {
- blocked_fields.add(coords, game().get_gametime() + block_time);
- }
- }
-
- // If it last attempt we also destroy the flag (with a building if any attached)
- if (last_attempt_) {
- remove_from_dqueue<Widelands::Flag>(eco->flags, &flag);
- game().send_player_bulldoze(*const_cast<Flag*>(&flag));
- dead_ends_check_ = true;
- return true;
- }
- }
- return false;
+ game().send_player_build_road(player_number(), path);
+ return true;
+ }
+ // We can't build a road so let's block the vicinity as an indication this area is not
+ // connectible
+ // Usually we block for 2 minutes, but if it is a last attempt we block for 10 minutes
+ // Note: we block the vicinity only if this economy (usually a sole flag with a building) is not
+ // connected to a warehouse
+ if (flag.get_economy()->warehouses().empty()) {
+
+ // blocking only if latest block was less then 60 seconds ago or it is last attempt
+ if (eco->fields_block_last_time + 60000 < gametime || last_attempt_) {
+ eco->fields_block_last_time = gametime;
+
+ const uint32_t block_time = last_attempt_ ? 10 * 60 * 1000 : 2 * 60 * 1000;
+
+ FindNodeAcceptAll buildable_functor;
+ CheckStepOwnTerritory check_own(player_, MOVECAPS_WALK, true);
+
+ // get all flags within radius
+ std::vector<Coords> reachable_to_block;
+ map.find_reachable_fields(Area<FCoords>(map.get_fcoords(flag.get_position()), checkradius),
+ &reachable_to_block, check_own, buildable_functor);
+
+ for (auto coords : reachable_to_block) {
+ blocked_fields.add(coords, game().get_gametime() + block_time);
+ }
+ }
+
+ // If it last attempt we also destroy the flag (with a building if any attached)
+ if (last_attempt_) {
+ remove_from_dqueue<Widelands::Flag>(eco->flags, &flag);
+ game().send_player_bulldoze(*const_cast<Flag*>(&flag));
+ dead_ends_check_ = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+void DefaultAI::collect_nearflags(std::map<uint32_t, NearFlag> &nearflags, const Flag& flag, const uint16_t checkradius) {
+ // Algorithm to walk on roads
+ // All nodes are marked as to_be_checked == true first and once the node is checked it is changed
+ // to false. Under some conditions, the same node can be checked twice, the to_be_checked can
+ // be set back to true. Because less hoops (fewer flag-to-flag roads) does not always mean
+ // shortest
+ // road.
+
+ const Map& map = game().map();
+
+ for (;;) {
+ // looking for a node with shortest existing road distance from starting flag and one that has
+ // to be checked
+ uint32_t start_field = std::numeric_limits<uint32_t>::max();
+ uint32_t nearest_distance = 10000;
+ for (auto item : nearflags) {
+ if (item.second.current_road_distance < nearest_distance && item.second.to_be_checked) {
+ nearest_distance = item.second.current_road_distance;
+ start_field = item.first;
+ }
+ }
+ // OK, we failed to find a NearFlag where to_be_checked == true, so quitting the loop now
+ if (start_field == std::numeric_limits<uint32_t>::max()) {
+ break;
+ }
+
+ nearflags[start_field].to_be_checked = false;
+
+ // Now going over roads leading from this flag
+ for (uint8_t i = 1; i <= 6; ++i) {
+ Road* const road = nearflags[start_field].flag->get_road(i);
+
+ if (!road) {
+ continue;
+ }
+
+ Flag* endflag = &road->get_flag(Road::FlagStart);
+
+ if (endflag == nearflags[start_field].flag) {
+ endflag = &road->get_flag(Road::FlagEnd);
+ }
+
+ const uint32_t endflag_hash = endflag->get_position().hash();
+
+ const int32_t dist = map.calc_distance(flag.get_position(), endflag->get_position());
+
+ if (dist > checkradius + 2) { // Testing bigger vicinity then checkradius....
+ continue;
+ }
+
+ // There is few scenarios for this neighbour flag
+ if (nearflags.count(endflag_hash) == 0) {
+ // This is brand new flag
+ // calculating diff how much closer we will get to the flag
+ nearflags[endflag_hash] =
+ NearFlag(endflag, nearflags[start_field].current_road_distance +
+ road->get_path().get_nsteps());
+ } else {
+ // We know about this flag already
+ if (nearflags[endflag_hash].current_road_distance >
+ nearflags[start_field].current_road_distance + road->get_path().get_nsteps()) {
+ // ..but this current connection is shorter than one found before
+ nearflags[endflag_hash].current_road_distance =
+ nearflags[start_field].current_road_distance + road->get_path().get_nsteps();
+ // So let re-check neighbours once more
+ nearflags[endflag_hash].to_be_checked = true;
+ }
+ }
+ }
+ }
+
}
/**
=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h 2019-05-15 06:29:24 +0000
+++ src/ai/defaultai.h 2019-06-07 13:22:42 +0000
@@ -148,6 +148,7 @@
static constexpr int32_t kSpotsTooLittle = 15;
static constexpr int kManagementUpdateInterval = 10 * 60 * 1000;
static constexpr int kStatUpdateInterval = 60 * 1000;
+ static constexpr int kFlagWarehouseUpdInterval = 15 * 1000;
// For vision and scheduling
static constexpr uint32_t kNever = std::numeric_limits<uint32_t>::max();
@@ -198,11 +199,14 @@
bool improve_roads(uint32_t);
bool create_shortcut_road(const Widelands::Flag&,
uint16_t maxcheckradius,
- int16_t minReduction,
const uint32_t gametime);
// trying to identify roads that might be removed
bool dispensable_road_test(const Widelands::Road&);
bool dismantle_dead_ends();
+ void collect_nearflags(std::map<uint32_t, Widelands::NearFlag> &, const Widelands::Flag&, const uint16_t);
+ // calculating distances from local warehouse to flags
+ void check_flag_distances(uint32_t);
+ Widelands::FlagWarehouseDistances flag_warehouse_distance;
bool check_economies();
bool check_productionsites(uint32_t);
@@ -272,6 +276,7 @@
bool check_ships(uint32_t);
bool attempt_escape(Widelands::ShipObserver& so);
+
// finding and owner
Widelands::PlayerNumber get_land_owner(const Widelands::Map&, uint32_t);
=== added directory 'src/ai/test'
=== added file 'src/ai/test/CMakeLists.txt'
--- src/ai/test/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ src/ai/test/CMakeLists.txt 2019-06-07 13:22:42 +0000
@@ -0,0 +1,10 @@
+wl_test(test_ai
+ SRCS
+ ai_test_main.cc
+ test_ai.cc
+ DEPENDS
+ base_log
+ base_macros
+ ai
+ logic_widelands_geometry
+)
=== added file 'src/ai/test/ai_test_main.cc'
--- src/ai/test/ai_test_main.cc 1970-01-01 00:00:00 +0000
+++ src/ai/test/ai_test_main.cc 2019-06-07 13:22:42 +0000
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2007-2019 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#define BOOST_TEST_MODULE AI
+#include <boost/test/unit_test.hpp>
=== added file 'src/ai/test/test_ai.cc'
--- src/ai/test/test_ai.cc 1970-01-01 00:00:00 +0000
+++ src/ai/test/test_ai.cc 2019-06-07 13:22:42 +0000
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2007-2019 by the Widelands Development Team
+ *
+ * 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; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <exception>
+
+#include <boost/test/unit_test.hpp>
+
+#ifdef _WIN32
+#include "base/log.h"
+#endif
+#include "ai/ai_help_structs.h"
+
+// Triggered by BOOST_AUTO_TEST_CASE
+CLANG_DIAG_OFF("-Wdisabled-macro-expansion")
+
+namespace Widelands { // Needed?
+class World;
+} // namespace Widelands
+
+using namespace Widelands;
+
+BOOST_AUTO_TEST_SUITE(ai)
+
+BOOST_AUTO_TEST_CASE(flag_distance_soft_expiry) {
+ FlagWarehouseDistances fw;
+ uint32_t tmp_wh;
+ BOOST_CHECK_EQUAL(fw.get_distance(0, 0, &tmp_wh), 1000);
+ // set_distance(const uint32_t flag_coords, const uint16_t distance,
+ // uint32_t const gametime, uint32_t const nearest_warehouse)
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 2, 0, 3), true);
+ // get_distance(const uint32_t flag_coords, uint32_t gametime, uint32_t* nw)
+ BOOST_CHECK_EQUAL(fw.get_distance(1, 2, &tmp_wh), 2); // distance now 2
+ BOOST_CHECK_EQUAL(tmp_wh, 3);
+
+ // setting longer distance below soft_expiry time
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 3, kFlagDistanceExpirationPeriod / 3, 4), false);
+ // distance to 3 not updated
+ BOOST_CHECK_EQUAL(fw.get_distance(1, kFlagDistanceExpirationPeriod / 3, &tmp_wh), 2);
+ BOOST_CHECK_EQUAL(tmp_wh, 3);
+
+ // now setting after soft expiry
+ BOOST_CHECK_EQUAL(
+ fw.set_distance(1, 1, kFlagDistanceExpirationPeriod / 3, 6), true); // distance set to 1
+ BOOST_CHECK_EQUAL(fw.get_distance(1, kFlagDistanceExpirationPeriod / 3, &tmp_wh), 1);
+ BOOST_CHECK_EQUAL(tmp_wh, 6);
+}
+BOOST_AUTO_TEST_CASE(flag_distance_below_expiry)
+/* Compare with void free_test_function() */
+{
+ FlagWarehouseDistances fw;
+ uint32_t tmp_wh;
+ // set_distance(const uint32_t flag_coords, const uint16_t distance,
+ // uint32_t const gametime, uint32_t const nearest_warehouse)
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 2, 0, 3), true);
+ // get_distance(const uint32_t flag_coords, uint32_t gametime, uint32_t* nw)
+
+ // setting longer distance after soft but below expiry time
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 3, kFlagDistanceExpirationPeriod * 2 / 3, 5), true);
+ BOOST_CHECK_EQUAL(fw.get_distance(1, kFlagDistanceExpirationPeriod * 2 / 3, &tmp_wh), 3);
+ BOOST_CHECK_EQUAL(tmp_wh, 5);
+}
+
+BOOST_AUTO_TEST_CASE(flag_distance_after_expiry)
+/* Compare with void free_test_function() */
+{
+ FlagWarehouseDistances fw;
+ uint32_t tmp_wh;
+ // set_distance(const uint32_t flag_coords, const uint16_t distance,
+ // uint32_t const gametime, uint32_t const nearest_warehouse)
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 2, 0, 3), true);
+ // get_distance(const uint32_t flag_coords, uint32_t gametime, uint32_t* nw)
+
+ // setting longer distance below expiry time
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 3, 2 * kFlagDistanceExpirationPeriod, 5), true);
+ BOOST_CHECK_EQUAL(fw.get_distance(1, 3, &tmp_wh), 3);
+ BOOST_CHECK_EQUAL(tmp_wh, 5);
+}
+
+BOOST_AUTO_TEST_CASE(flag_distance_expiration_extension)
+/* setting the same distance restart the expiry_period */
+{
+ FlagWarehouseDistances fw;
+ uint32_t tmp_wh;
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 2, 0, 3), true);
+ BOOST_CHECK_EQUAL(
+ fw.set_distance(1, 2, 0, 3), false); // cannot reset the same distance in the same time
+ // get_distance(const uint32_t flag_coords, uint32_t gametime, uint32_t* nw)
+
+ // Now we are after expiration time
+ BOOST_CHECK_EQUAL(fw.get_distance(1, kFlagDistanceExpirationPeriod + 3, &tmp_wh), 1000);
+
+ // setting distance 2 time shortly one after another
+ // set_distance(const uint32_t flag_coords, const uint16_t distance,
+ // uint32_t const gametime, uint32_t const nearest_warehouse)
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 2, kFlagDistanceExpirationPeriod + 3, 5), true);
+ BOOST_CHECK_EQUAL(fw.set_distance(1, 2, kFlagDistanceExpirationPeriod + 10, 5), true);
+ // current expiry_time should be 2*kFlagDistanceExpirationPeriod + 10
+ BOOST_CHECK_EQUAL(fw.get_distance(1, 2 * kFlagDistanceExpirationPeriod, &tmp_wh), 2);
+ BOOST_CHECK_EQUAL(tmp_wh, 5);
+ BOOST_CHECK_EQUAL(fw.get_distance(1, 2 * kFlagDistanceExpirationPeriod + 15, &tmp_wh), 1000);
+}
+
+BOOST_AUTO_TEST_CASE(flag_distance_road_builtexpiration_extension)
+/* setting the same distance restart the expiry_period */
+{
+ FlagWarehouseDistances fw;
+ // uint32_t tmp_wh;
+ // No road built on fresh flag
+ BOOST_CHECK_EQUAL(fw.get_road_prohibited(1, 1), false);
+ // get_distance(const uint32_t flag_coords, uint32_t gametime, uint32_t* nw)
+
+ // setting road we dont know about
+ fw.set_road_built(1, 0);
+ BOOST_CHECK_EQUAL(fw.get_road_prohibited(1, 1), false);
+
+ // let fw knows about it
+ fw.set_distance(1, 2, 0, 3);
+ fw.set_road_built(1, 0);
+ BOOST_CHECK_EQUAL(fw.get_road_prohibited(1, 1), true);
+ BOOST_CHECK_EQUAL(fw.get_road_prohibited(1, 59999), true);
+ BOOST_CHECK_EQUAL(fw.get_road_prohibited(1, 60001), false);
+
+ BOOST_CHECK_EQUAL(fw.get_road_prohibited(2, 60001), false);
+}
+
+BOOST_AUTO_TEST_CASE(flag_distance_old_removal)
+/* setting the same distance restart the expiry_period */
+{
+ FlagWarehouseDistances fw;
+ fw.set_distance(1, 2, 0, 3);
+ BOOST_CHECK_EQUAL(fw.count(), 1);
+ BOOST_CHECK_EQUAL(fw.remove_old_flag(kOldFlagRemoveTime + kFlagDistanceExpirationPeriod), false);
+ BOOST_CHECK_EQUAL(fw.count(), 1);
+ BOOST_CHECK_EQUAL(
+ fw.remove_old_flag(kOldFlagRemoveTime + kFlagDistanceExpirationPeriod + 2), true);
+ BOOST_CHECK_EQUAL(fw.count(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(new_flag_road_not_prohibited)
+/* setting the same distance restart the expiry_period */
+{
+ FlagWarehouseDistances fw;
+ // let fw knows about it
+ BOOST_CHECK_EQUAL(fw.count(), 0);
+ fw.set_distance(1, 2, 0, 3);
+ BOOST_CHECK_EQUAL(fw.count(), 1);
+ BOOST_CHECK_EQUAL(fw.get_road_prohibited(1, 1), false);
+}
+
+BOOST_AUTO_TEST_CASE(flag_candidate_init)
+/* setting the same distance restart the expiry_period */
+{
+ FlagCandidates fc = FlagCandidates(10);
+ BOOST_CHECK_EQUAL(fc.count(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(flag_candidate_winner_score) {
+ const uint16_t kCurFlDistToWh = 3;
+ const uint16_t kStartFlagToWh = 10;
+
+ const uint16_t kPosRoadDist = 5;
+ const uint16_t kCurRoadDistFlToFl = 17;
+
+ const uint32_t kTestedCoords = 11;
+
+ FlagCandidates fc = FlagCandidates(kStartFlagToWh);
+
+ // coord, different economy, distance to wh
+ fc.add_flag(kTestedCoords, false, kCurFlDistToWh, 1);
+ // setting coords, dist
+ BOOST_CHECK_EQUAL(fc.set_cur_road_distance(kTestedCoords, kCurRoadDistFlToFl), true);
+ BOOST_CHECK_EQUAL(
+ fc.set_cur_road_distance(1, 5), false); // we cannot set distance to unknown flag
+ BOOST_CHECK_EQUAL(fc.get_winner(), nullptr); // road not possible
+ // set length of possible road
+ BOOST_CHECK_EQUAL(fc.set_road_possible(kTestedCoords, kPosRoadDist), true);
+ BOOST_VERIFY(fc.get_winner());
+ BOOST_CHECK_EQUAL(fc.get_winner()->start_flag_dist_to_wh, kStartFlagToWh);
+ BOOST_CHECK_EQUAL(fc.get_winner()->cand_flag_distance_to_wh, kCurFlDistToWh);
+ BOOST_CHECK_EQUAL(fc.get_winner()->flag_to_flag_road_distance, kCurRoadDistFlToFl);
+ BOOST_CHECK_EQUAL(fc.get_winner()->possible_road_distance, kPosRoadDist);
+ BOOST_CHECK_EQUAL(fc.get_winner()->coords_hash, kTestedCoords);
+ BOOST_CHECK_EQUAL(fc.get_winner()->different_economy, false);
+
+ BOOST_CHECK_EQUAL(fc.get_winner()->score(),
+ +(kStartFlagToWh - kCurFlDistToWh) + (kCurRoadDistFlToFl - 2 * kPosRoadDist));
+}
+BOOST_AUTO_TEST_CASE(flag_candidates_sorting) {
+ FlagCandidates fc = FlagCandidates(10);
+
+ fc.add_flag(0, false, 10, 1);
+ fc.add_flag(1, false, 10, 1);
+ fc.add_flag(2, false, 10, 1);
+ BOOST_CHECK_EQUAL(fc.set_cur_road_distance(0, 5), true);
+ BOOST_CHECK_EQUAL(fc.set_cur_road_distance(1, 5), true);
+ BOOST_CHECK_EQUAL(fc.set_cur_road_distance(2, 5), true);
+ BOOST_CHECK_EQUAL(fc.set_road_possible(0, 4), true);
+ BOOST_CHECK_EQUAL(fc.set_road_possible(1, 2), true);
+ BOOST_CHECK_EQUAL(fc.set_road_possible(2, 3), true);
+ BOOST_CHECK_EQUAL(fc.get_winner()->coords_hash, 1); // sorted done automatically
+ BOOST_CHECK_EQUAL(fc.count(), 3);
+}
+
+BOOST_AUTO_TEST_CASE(flag_sort_by_air_distance) {
+ FlagCandidates fc = FlagCandidates(10);
+
+ fc.add_flag(0, false, 10, 4);
+ fc.add_flag(1, false, 10, 1);
+ fc.add_flag(2, false, 10, 2);
+ fc.sort_by_air_distance();
+ BOOST_CHECK_EQUAL(fc.flags()[0].air_distance, 1);
+}
+
+BOOST_AUTO_TEST_CASE(flag_has_candidate) {
+ FlagCandidates fc = FlagCandidates(10);
+
+ fc.add_flag(0, false, 10, 4);
+ fc.add_flag(1, false, 10, 1);
+ fc.add_flag(2, false, 10, 2);
+ BOOST_CHECK_EQUAL(fc.has_candidate(1), true);
+ BOOST_CHECK_EQUAL(fc.has_candidate(3), false);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
Follow ups
-
[Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: noreply, 2019-08-02
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: hessenfarmer, 2019-08-02
-
[Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: bunnybot, 2019-08-02
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: GunChleoc, 2019-08-02
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: TiborB, 2019-08-01
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: TiborB, 2019-07-31
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: GunChleoc, 2019-07-31
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: TiborB, 2019-07-29
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: hessenfarmer, 2019-07-29
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: TiborB, 2019-07-25
-
[Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: bunnybot, 2019-07-25
-
[Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: bunnybot, 2019-07-23
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: TiborB, 2019-07-11
-
[Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: bunnybot, 2019-07-10
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: GunChleoc, 2019-07-09
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: hessenfarmer, 2019-07-07
-
[Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: bunnybot, 2019-06-12
-
[Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: bunnybot, 2019-06-12
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: hessenfarmer, 2019-06-11
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: TiborB, 2019-06-10
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: hessenfarmer, 2019-06-10
-
Re: [Merge] lp:~widelands-dev/widelands/ai_flag_warehouse_distance into lp:widelands
From: TiborB, 2019-06-09