widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #08828
[Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
TiborB has proposed merging lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands.
Requested reviews:
Widelands Developers (widelands-dev)
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/ai_seafaring_split/+merge/311544
This moves couple of seafaring functions from defaultai.cc to defaultai_seafaring.cc, mainly because of toptopple's request. Defaultaicc is too big anyway and this should help administrating DefaultAI struct and give toptopple some space to modify seafaring functionality.
However I dont know how standard-compliant such design is - but it works.
@toptopple - is this what you asked for?
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands.
=== modified file 'src/ai/CMakeLists.txt'
--- src/ai/CMakeLists.txt 2016-01-28 05:24:34 +0000
+++ src/ai/CMakeLists.txt 2016-11-22 21:08:29 +0000
@@ -6,6 +6,7 @@
ai_hints.h
computer_player.cc
computer_player.h
+ defaultai_seafaring.cc
defaultai.cc
defaultai.h
DEPENDS
=== modified file 'src/ai/defaultai.cc'
--- src/ai/defaultai.cc 2016-11-19 18:25:49 +0000
+++ src/ai/defaultai.cc 2016-11-22 21:08:29 +0000
@@ -36,7 +36,6 @@
#include "economy/flag.h"
#include "economy/portdock.h"
#include "economy/road.h"
-#include "economy/wares_queue.h"
#include "logic/findbob.h"
#include "logic/findimmovable.h"
#include "logic/findnode.h"
@@ -63,18 +62,9 @@
constexpr int kBuildingMinInterval = 25 * 1000;
constexpr int kMinBFCheckInterval = 5 * 1000;
constexpr int kMinMFCheckInterval = 19 * 1000;
-constexpr int kShipCheckInterval = 5 * 1000;
constexpr int kMarineDecisionInterval = 20 * 1000;
constexpr int kTrainingSitesCheckInterval = 15 * 1000;
-// handfull of constants used for expeditions/colonization
-constexpr int kColonyScanStartArea = 35;
-constexpr int kColonyScanMinArea = 10;
-constexpr int kExpeditionMaxDuration = 120 * 60 * 1000;
-constexpr uint32_t kNoShip = std::numeric_limits<uint32_t>::max();
-constexpr uint32_t kNever = std::numeric_limits<uint32_t>::max();
-constexpr uint32_t kNoExpedition = 0;
-
// following two are used for roads management, for creating shortcuts and dismantling dispensable
// roads
constexpr int32_t kSpotsTooLittle = 15;
@@ -83,10 +73,6 @@
// this is intended for map developers, by default should be off
constexpr bool kPrintStats = false;
-constexpr int8_t kUncalculated = -1;
-constexpr uint8_t kFalse = 0;
-constexpr uint8_t kTrue = 1;
-
// duration of military campaign
constexpr int kCampaignDuration = 15 * 60 * 1000;
@@ -3490,283 +3476,6 @@
return false;
}
-// This function scans current situation with shipyards, ports, ships, ongoing expeditions
-// and makes two decisions:
-// - build a ship
-// - start preparation for expedition
-bool DefaultAI::marine_main_decisions() {
-
- if (!seafaring_economy) {
- set_taskpool_task_time(kNever, SchedulerTaskId::KMarineDecisions);
- return false;
- }
-
- // getting some base statistics
- player_ = game().get_player(player_number());
- uint16_t ports_count = 0;
- uint16_t shipyards_count = 0;
- uint16_t expeditions_in_prep = 0;
- uint16_t expeditions_in_progress = 0;
- bool idle_shipyard_stocked = false;
-
- // goes over all warehouses (these includes ports)
- for (const WarehouseSiteObserver& wh_obs : warehousesites) {
- if (wh_obs.bo->is_port) {
- ports_count += 1;
- if (Widelands::PortDock* pd = wh_obs.site->get_portdock()) {
- if (pd->expedition_started()) {
- expeditions_in_prep += 1;
- }
- }
- }
- }
-
- // goes over productionsites and gets status of shipyards
- for (const ProductionSiteObserver& ps_obs : productionsites) {
- if (ps_obs.bo->is_shipyard) {
- shipyards_count += 1;
-
- // counting stocks
- uint8_t stocked_wares = 0;
- std::vector<WaresQueue*> const warequeues = ps_obs.site->warequeues();
- size_t const nr_warequeues = warequeues.size();
- for (size_t i = 0; i < nr_warequeues; ++i) {
- stocked_wares += warequeues[i]->get_filled();
- }
- if (stocked_wares == 16 && ps_obs.site->is_stopped() && ps_obs.site->can_start_working()) {
- idle_shipyard_stocked = true;
- }
- }
- }
-
- // and now over ships
- for (std::list<ShipObserver>::iterator sp_iter = allships.begin(); sp_iter != allships.end();
- ++sp_iter) {
- if (sp_iter->ship->state_is_expedition()) {
- expeditions_in_progress += 1;
- }
- }
-
- assert(allships.size() >= expeditions_in_progress);
- bool ship_free = allships.size() - expeditions_in_progress > 0;
-
- enum class FleetStatus : uint8_t { kNeedShip = 0, kEnoughShips = 1, kDoNothing = 2 };
-
- // now we decide whether we have enough ships or need to build another
- // three values: kDoNothing, kNeedShip, kEnoughShips
- FleetStatus enough_ships = FleetStatus::kDoNothing;
- if (ports_count > 0 && shipyards_count > 0 && idle_shipyard_stocked) {
-
- // we always need at least one ship in transport mode
- if (!ship_free) {
- enough_ships = FleetStatus::kNeedShip;
-
- // we want at least as many free ships as we have ports
- } else if (int(allships.size()) - ports_count - expeditions_in_progress < 0) {
- enough_ships = FleetStatus::kNeedShip;
-
- // if ships utilization is too high
- } else if (persistent_data->ships_utilization > 5000) {
- enough_ships = FleetStatus::kNeedShip;
-
- } else {
- enough_ships = FleetStatus::kEnoughShips;
- }
- }
-
- // building a ship? if yes, find a shipyard and order it to build a ship
- if (enough_ships == FleetStatus::kNeedShip) {
-
- for (const ProductionSiteObserver& ps_obs : productionsites) {
- if (ps_obs.bo->is_shipyard && ps_obs.site->can_start_working() &&
- ps_obs.site->is_stopped()) {
- // make sure it is fully stocked
- // counting stocks
- uint8_t stocked_wares = 0;
- std::vector<WaresQueue*> const warequeues = ps_obs.site->warequeues();
- size_t const nr_warequeues = warequeues.size();
- for (size_t i = 0; i < nr_warequeues; ++i) {
- stocked_wares += warequeues[i]->get_filled();
- }
- if (stocked_wares < 16) {
- continue;
- }
-
- game().send_player_start_stop_building(*ps_obs.site);
- return true;
- }
- }
- }
-
- // starting an expedition? if yes, find a port and order it to start an expedition
- if (ports_count > 0 && expeditions_in_progress == 0 && expeditions_in_prep == 0 &&
- persistent_data->no_more_expeditions == kFalse && ship_free) {
-
- // we need to find a port
- for (const WarehouseSiteObserver& wh_obs : warehousesites) {
- if (wh_obs.bo->is_port) {
- game().send_player_start_or_cancel_expedition(*wh_obs.site);
- return true;
- }
- }
- }
- return true;
-}
-
-// This identifies ships that are waiting for command
-bool DefaultAI::check_ships(uint32_t const gametime) {
-
- if (!seafaring_economy) {
- set_taskpool_task_time(std::numeric_limits<int32_t>::max(), SchedulerTaskId::kCheckShips);
- return false;
- }
-
- bool action_taken = false;
-
- if (!allships.empty()) {
- // iterating over ships and doing what is needed
- for (std::list<ShipObserver>::iterator i = allships.begin(); i != allships.end(); ++i) {
-
- const Widelands::Ship::ShipStates ship_state = i->ship->get_ship_state();
-
- // Here we manage duration of expedition and related variables
- if (ship_state == Widelands::Ship::ShipStates::kExpeditionWaiting ||
- ship_state == Widelands::Ship::ShipStates::kExpeditionScouting ||
- ship_state == Widelands::Ship::ShipStates::kExpeditionPortspaceFound) {
-
- // the function below will take care of variables like
- // - expedition_ship_
- // - expedition_start_time
- // - expected_colony_scan
- // - no_more_expeditions_
- check_ship_in_expedition(*i, gametime);
-
- // We are not in expedition mode (or perhaps building a colonisation port)
- // so resetting start time
- } else if (expedition_ship_ == i->ship->serial()) {
- // Obviously expedition just ended
- persistent_data->expedition_start_time = kNoExpedition;
- expedition_ship_ = kNoShip;
- }
-
- // only two states need an attention
- if ((i->ship->get_ship_state() == Widelands::Ship::ShipStates::kExpeditionWaiting ||
- i->ship->get_ship_state() ==
- Widelands::Ship::ShipStates::kExpeditionPortspaceFound) &&
- !i->waiting_for_command_) {
- if (gametime - i->last_command_time > 180 * 1000) {
- i->waiting_for_command_ = true;
- log(" %1d: last command for ship at %3dx%3d was %3d seconds ago, something wrong "
- "here?...\n",
- player_number(), i->ship->get_position().x, i->ship->get_position().y,
- (gametime - i->last_command_time) / 1000);
- }
- }
- // if ships is waiting for command
- if (i->waiting_for_command_) {
- expedition_management(*i);
- action_taken = true;
- }
-
- // Checking utilization
- if (i->ship->get_ship_state() == Widelands::Ship::ShipStates::kTransport) {
- // Good utilization is 10 pieces of ware onboard, to track utilization we use range
- // 0-10000
- // to avoid float or rounding errors if integers in range 0-100
- const int16_t tmp_util =
- (i->ship->get_nritems() > 10) ? 10000 : i->ship->get_nritems() * 1000;
- // This number is kind of average
- persistent_data->ships_utilization =
- persistent_data->ships_utilization * 19 / 20 + tmp_util / 20;
-
- // Arithmetics check
- assert(persistent_data->ships_utilization >= 0 &&
- persistent_data->ships_utilization <= 10000);
- }
- }
- }
-
- // processing marine_task_queue
- while (!marine_task_queue.empty()) {
- if (marine_task_queue.back() == kStopShipyard) {
- // iterate over all production sites searching for shipyard
- for (std::list<ProductionSiteObserver>::iterator site = productionsites.begin();
- site != productionsites.end(); ++site) {
- if (site->bo->is_shipyard) {
- if (!site->site->is_stopped()) {
- game().send_player_start_stop_building(*site->site);
- }
- }
- }
- }
-
- if (marine_task_queue.back() == kReprioritize) {
- for (std::list<ProductionSiteObserver>::iterator site = productionsites.begin();
- site != productionsites.end(); ++site) {
- if (site->bo->is_shipyard) {
- for (uint32_t k = 0; k < site->bo->inputs.size(); ++k) {
- game().send_player_set_ware_priority(
- *site->site, wwWARE, site->bo->inputs.at(k), HIGH_PRIORITY);
- }
- }
- }
- }
-
- marine_task_queue.pop_back();
- }
-
- if (action_taken) {
- set_taskpool_task_time(gametime + kShipCheckInterval, SchedulerTaskId::kCheckShips);
- }
- return true;
-}
-
-/**
- * This is part of check_ships() function separated due to readibility purpuses
- */
- void DefaultAI::check_ship_in_expedition(ShipObserver& so, uint32_t const gametime) {
- // consistency check
- assert(expedition_ship_ == so.ship->serial() || expedition_ship_ == kNoShip);
-
- // This is obviously new expedition
- if (expedition_ship_ == kNoShip) {
- assert(persistent_data->expedition_start_time == kNoExpedition);
- persistent_data->expedition_start_time = gametime;
- expedition_ship_ = so.ship->serial();
-
- // Already known expedition, all we do now, is decreasing persistent_data->colony_scan_area
- // based on lapsed time
- } else if (gametime - persistent_data->expedition_start_time < kExpeditionMaxDuration) {
- assert(persistent_data->expedition_start_time > kNoExpedition);
- // remaining_time is a percent so in range 0-100
- const uint32_t remaining_time = 100 - ((gametime - persistent_data->expedition_start_time) /
- (kExpeditionMaxDuration / 100));
- assert(remaining_time <= 100);
-
- // We calculate expected value and actual value (persistent_data->colony_scan_area
- // is changed only when needed)
- const uint32_t expected_colony_scan =
- kColonyScanMinArea + (kColonyScanStartArea - kColonyScanMinArea) * remaining_time / 100;
- assert(expected_colony_scan >= kColonyScanMinArea &&
- expected_colony_scan <= kColonyScanStartArea);
-
- // So changing it if needed
- if (expected_colony_scan < persistent_data->colony_scan_area) {
- persistent_data->colony_scan_area = expected_colony_scan;
- }
-
- // Expedition overdue. Setting no_more_expeditions = true
- // But we do not cancel it, the code for cancellation does not work properly now
- // TODO(unknown): - expedition code for cancellation needs to be fixed and afterwareds
- // AI can be changed to cancel overdue expedition
- } else if (gametime - persistent_data->expedition_start_time >= kExpeditionMaxDuration) {
- assert(persistent_data->expedition_start_time > 0);
- persistent_data->colony_scan_area = kColonyScanMinArea;
- persistent_data->no_more_expeditions = kTrue;
-
- game().send_player_cancel_expedition_ship(*so.ship);
- }
-}
/**
* checks the first mine in list, takes care if it runs out of
@@ -4695,34 +4404,6 @@
}
}
-// this is called whenever we gain ownership of a Ship
-void DefaultAI::gain_ship(Ship& ship, NewShip type) {
-
- allships.push_back(ShipObserver());
- allships.back().ship = &ship;
- if (game().logic_rand() % 20 < 10) {
- allships.back().island_circ_direction = IslandExploreDirection::kClockwise;
- } else {
- allships.back().island_circ_direction = IslandExploreDirection::kCounterClockwise;
- }
-
- if (type == NewShip::kBuilt) {
- marine_task_queue.push_back(kStopShipyard);
- } else {
- seafaring_economy = true;
- if (ship.state_is_expedition()) {
- if (expedition_ship_ == kNoShip) {
- // OK, this ship is in expedition
- expedition_ship_ = ship.serial();
- } else {
- // What? Another ship in expedition? AI is not able to manage two expedition ships...
- log(" %d: AI will not control ship %s, as there is already another one in expedition\n",
- player_number(), ship.get_shipname().c_str());
- }
- }
- }
-}
-
// this is called whenever we lose ownership of a PlayerImmovable
void DefaultAI::lose_immovable(const PlayerImmovable& pi) {
if (upcast(Building const, building, &pi)) {
@@ -4875,175 +4556,6 @@
return false; // no players found
}
-// this scores spot for potential colony
-uint8_t DefaultAI::spot_scoring(Widelands::Coords candidate_spot) {
-
- Map& map = game().map();
- uint8_t score = 0;
- uint16_t mineable_fields_count = 0;
- uint32_t tested_fields = 0;
-
- // abort if any player - including self - is too near to the spot (radius 8)
- // TODO(toptopple): extract function for player distance only
- if (other_player_accessible(8, &tested_fields, &mineable_fields_count,
- candidate_spot, WalkSearch::kAnyPlayer)) {
- return 0;
- }
-
- // now check with variant radius (depending on expedition time)
- // on the beginning we search for completely deserted area,
- // later we will accept also area nearer to friendly territory
- WalkSearch search_type = WalkSearch::kAnyPlayer;
- if (persistent_data->colony_scan_area < 25) {
- search_type = WalkSearch::kEnemy;
- }
-
- // abort if we run into other player
- if (other_player_accessible(persistent_data->colony_scan_area, &tested_fields,
- &mineable_fields_count, candidate_spot, search_type)) {
- return 0;
- }
-
- // abort if available area (island) is too small...
- // colony_scan_area is a radius (distance) and has no direct relevance to the size of area,
- // but it seems a good measurement
- if (tested_fields < persistent_data->colony_scan_area) {
- return 0;
- }
-
- // if we are here we put score
- score = 1;
- if (mineable_fields_count > 0) {
- score += 1;
- }
-
- // here we check for surface rocks + trees
- std::vector<ImmovableFound> immovables;
- // Search in a radius of range
- map.find_immovables(Area<FCoords>(map.get_fcoords(candidate_spot), 10), &immovables);
-
- int32_t const rocks_attr = MapObjectDescr::get_attribute_id("rocks");
- uint16_t rocks = 0;
- int32_t const tree_attr = MapObjectDescr::get_attribute_id("tree");
- uint16_t trees = 0;
-
- for (uint32_t j = 0; j < immovables.size(); ++j) {
- if (immovables.at(j).object->has_attribute(rocks_attr)) {
- ++rocks;
- }
- if (immovables.at(j).object->has_attribute(tree_attr)) {
- ++trees;
- }
- }
- if (rocks > 1) {
- score += 1;
- }
- if (trees > 1) {
- score += 1;
- }
-
- return score;
-}
-
-
-// this is called whenever ship received a notification that requires
-// navigation decisions (these notifiation are processes not in 'real time')
-void DefaultAI::expedition_management(ShipObserver& so) {
-
- Map& map = game().map();
- const int32_t gametime = game().get_gametime();
- // probability for island exploration repetition
- const int repeat_island_prob = 20;
-
- // second we put current spot into visited_spots
- bool first_time_here = so.visited_spots.count(so.ship->get_position().hash()) == 0;
- if (first_time_here) {
- so.visited_spots.insert(so.ship->get_position().hash());
- }
-
- // if we have portspace following options are avaiable:
- // 1. Build a port there
- if (!so.ship->exp_port_spaces().empty()) { // making sure we have possible portspaces
-
- // we score the place (value max == 8)
- const uint8_t spot_score = spot_scoring(so.ship->exp_port_spaces().front()) *2;
-
- if (game().logic_rand() % 8 < spot_score) { // we build a port here
- game().send_player_ship_construct_port(*so.ship, so.ship->exp_port_spaces().front());
- so.last_command_time = gametime;
- so.waiting_for_command_ = false;
- // blocking the area for some time to save AI from idle attempts to built there
- // buildings
- // TODO(TiborB): how long it takes to build a port?
- // I used 5 minutes
- MapRegion<Area<FCoords>> mr(
- map, Area<FCoords>(map.get_fcoords(so.ship->exp_port_spaces().front()), 8));
- do {
- blocked_fields.add(mr.location(), game().get_gametime() + 5 * 60 * 1000);
- } while (mr.advance(map));
-
- return;
- }
- }
-
- // 2. Go on with expedition
-
- // we were not here before
- if (first_time_here) {
- game().send_player_ship_explore_island(*so.ship, so.island_circ_direction);
-
- // we were here before but we might randomly repeat island exploration
- } else if (game().logic_rand() % 100 < repeat_island_prob) {
- // let's add randomness to the Brown's molecule movement of ship
- // TODO(toptopple): make the following decision dependent on ship state != circle-island
- Widelands::IslandExploreDirection dir = game().logic_rand() % 10 < 5 ?
- Widelands::IslandExploreDirection::kClockwise :
- Widelands::IslandExploreDirection::kCounterClockwise;
- game().send_player_ship_explore_island(*so.ship, dir);
-
- // we head for open sea again
- } else {
- // determine swimmable directions
- std::vector<Direction> possible_directions;
- for (Direction dir = FIRST_DIRECTION; dir <= LAST_DIRECTION; ++dir) {
- // testing distance of 8 fields
- // this would say there is an 'open sea' there
- Widelands::FCoords tmp_fcoords = map.get_fcoords(so.ship->get_position());
- for (int8_t i = 0; i < 8; ++i) {
- tmp_fcoords = map.get_neighbour(tmp_fcoords, dir);
- if (tmp_fcoords.field->nodecaps() & MOVECAPS_SWIM) {
- if (i == 7) {
- possible_directions.push_back(dir);
- }
- } else {
- break;
- }
- }
- }
-
- // we test if there is open sea
- if (possible_directions.empty()) {
- // 2.A No there is no open sea
- // ## test-out of cancel-expedition method
- // ## circle island has danger of endless repetition
- // TODO(unknown): we should implement a 'rescue' procedure like 'sail for x fields and rethink'
- game().send_player_cancel_expedition_ship(*so.ship);
- // game().send_player_ship_explore_island(*so.ship, so.island_circ_direction);
-
- } else {
- // 2.B Yes, pick one of available directions
- const Direction final_direction =
- possible_directions.at(game().logic_rand() % possible_directions.size());
- game().send_player_ship_scouting_direction(
- *so.ship, static_cast<WalkingDir>(final_direction));
- }
- }
-
- so.last_command_time = gametime;
- so.waiting_for_command_ = false;
- return;
-}
-
// this is called whenever we gain a new building
void DefaultAI::gain_building(Building& b, const bool found_on_load) {
=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h 2016-08-04 15:49:05 +0000
+++ src/ai/defaultai.h 2016-11-22 21:08:29 +0000
@@ -28,6 +28,7 @@
#include "ai/ai_help_structs.h"
#include "ai/computer_player.h"
#include "base/i18n.h"
+#include "economy/wares_queue.h"
#include "logic/map_objects/immovable.h"
#include "logic/map_objects/tribes/ship.h"
#include "logic/map_objects/tribes/soldier.h"
@@ -234,6 +235,19 @@
// Variables of default AI
DefaultAI::Type type_;
+ // handfull of constants used for expeditions/colonization
+ static constexpr int kColonyScanStartArea = 35;
+ static constexpr int kColonyScanMinArea = 10;
+ static constexpr int kExpeditionMaxDuration = 120 * 60 * 1000;
+ static constexpr uint32_t kNoShip = std::numeric_limits<uint32_t>::max();
+ static constexpr uint32_t kNever = std::numeric_limits<uint32_t>::max();
+ static constexpr uint32_t kNoExpedition = 0;
+ static constexpr int kShipCheckInterval = 5 * 1000;
+
+ static constexpr int8_t kUncalculated = -1;
+ static constexpr uint8_t kFalse = 0;
+ static constexpr uint8_t kTrue = 1;
+
// collect statistics on how many times which job was run
uint32_t sched_stat_[20] = {0};
Follow ups
-
[Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: noreply, 2016-11-28
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: GunChleoc, 2016-11-28
-
[Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: bunnybot, 2016-11-27
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: TiborB, 2016-11-26
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: GunChleoc, 2016-11-26
-
[Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: bunnybot, 2016-11-25
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: TiborB, 2016-11-24
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: SirVer, 2016-11-24
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: TiborB, 2016-11-23
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: toptopple, 2016-11-23
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: TiborB, 2016-11-23
-
Re: [Merge] lp:~widelands-dev/widelands/ai_seafaring_split into lp:widelands
From: toptopple, 2016-11-23