← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~7010622-q/widelands/topple-seafaring-2 into lp:widelands

 

toptopple has proposed merging lp:~7010622-q/widelands/topple-seafaring-2 into lp:widelands.

Commit message:
further improvements of sea exploration AI (seafaring)
max. expedition time made adapting to map size (60-210 min)

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~7010622-q/widelands/topple-seafaring-2/+merge/312111

changed "visited spots" list to player-global list
made expedition-max-time dependent on the map size (60 to 210 min)
stabilised island circle movement (against random direction choice)
improved spot scoring "other-region" evaluation to radius 10
reaction to "no open sea" is again ship-explore-island"
removed blocking of map region
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~7010622-q/widelands/topple-seafaring-2 into lp:widelands.
=== modified file 'src/ai/ai_help_structs.h'
--- src/ai/ai_help_structs.h	2016-08-04 15:49:05 +0000
+++ src/ai/ai_help_structs.h	2016-11-30 04:53:43 +0000
@@ -431,15 +431,13 @@
 
 struct ShipObserver {
 	Widelands::Ship* ship;
-	Widelands::Coords expedition_start_point;
-	std::unordered_set<uint32_t> visited_spots;
+	bool waiting_for_command_ = false;
+	uint32_t last_command_time = 0;
 
-	// a ship circumvents all islands in the same direction, the value
-	// is assigned only once
+	// direction by which the ship circumvents an island
+	// this is the last circle-island command's direction
 	Widelands::IslandExploreDirection island_circ_direction =
 	   Widelands::IslandExploreDirection::kClockwise;
-	bool waiting_for_command_ = false;
-	uint32_t last_command_time = 0;
 };
 
 struct WareObserver {

=== modified file 'src/ai/defaultai.cc'
--- src/ai/defaultai.cc	2016-11-24 19:45:32 +0000
+++ src/ai/defaultai.cc	2016-11-30 04:53:43 +0000
@@ -870,8 +870,21 @@
 		throw wexception("Corrupted AI data");
 	}
 
+	// initialise max duration of single ship's expedition
+	uint32_t ar1 = uint32_t(map.get_height()) * map.get_width();
+	uint32_t rt1 = round(sqrt(ar1));
+	printf("--- EXPEDITION MAP AREA ROOT == %u\n", rt1);
+	int scope = 320 - 64;
+	int off = rt1 - 64;
+	if (off < 0) off = 0;
+	if (off > scope) off = scope;
+	expedition_max_duration = kExpeditionMinDuration +
+			double(off) * (kExpeditionMaxDuration - kExpeditionMinDuration) / scope;
+	printf("--- EXPEDITION MAX DURATION == %u\n", expedition_max_duration/1000);
+	assert(expedition_max_duration >= kExpeditionMinDuration && expedition_max_duration > 0);
+
 	// Sometimes there can be a ship in expedition, but expedition start time is not given
-	// f.e. human player played this player before
+	// e.g. human player played this player before
 	if (expedition_ship_ != kNoShip && persistent_data->expedition_start_time == kNoExpedition) {
 		// Current gametime is better then 'kNoExpedition'
 		persistent_data->expedition_start_time = gametime;
@@ -4131,7 +4144,7 @@
 		}
 }
 
-// walk and search for territory controlled by other player
+// walk and search for territory controlled by some player type
 // usually scanning radius is enough but sometimes we must walk to
 // verify that an enemy territory is really accessible by land
 bool DefaultAI::other_player_accessible(const uint32_t max_distance,
@@ -4179,7 +4192,7 @@
 				return true;
 			}
 
-			// if anybody but not me
+			// if somebody but not me
 			if (type == WalkSearch::kOtherPlayers && f->get_owned_by() != pn) {
 				*tested_fields = done.size();
 				return true;

=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h	2016-11-24 19:53:52 +0000
+++ src/ai/defaultai.h	2016-11-30 04:53:43 +0000
@@ -162,7 +162,7 @@
 	void update_mineable_field(Widelands::MineableField&);
 	void update_productionsite_stats();
 
-	// for productionsites
+	// for production sites
 	Widelands::BuildingNecessity
 	check_building_necessity(Widelands::BuildingObserver& bo, PerfEvaluation purpose, uint32_t);
 
@@ -227,6 +227,7 @@
 	template <typename T> void check_range(const T, const T, const char*);
 
 	// Functions used for seafaring / defaultai_seafaring.cc
+	Widelands::IslandExploreDirection randomExploreDirection();
 	void gain_ship(Widelands::Ship&, NewShip);
 	void check_ship_in_expedition(Widelands::ShipObserver&, uint32_t);
 	void expedition_management(Widelands::ShipObserver&);
@@ -337,12 +338,15 @@
 	enum { kReprioritize, kStopShipyard, kStapShipyard };
 	bool seafaring_economy;  // false by default, until first port space is found
 	uint32_t expedition_ship_;
+	uint32_t expedition_max_duration;
 	std::vector<int16_t> marine_task_queue;
+	std::unordered_set<uint32_t> expedition_visited_spots;
 
 	// common for defaultai.cc and defaultai_seafaring.cc
 	static constexpr int kColonyScanStartArea = 35;
 	static constexpr int kColonyScanMinArea = 10;
-	static constexpr int kExpeditionMaxDuration = 120 * 60 * 1000;
+	static constexpr int kExpeditionMinDuration = 60 * 60 * 1000;
+	static constexpr int kExpeditionMaxDuration = 210 * 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;

=== modified file 'src/ai/defaultai_seafaring.cc'
--- src/ai/defaultai_seafaring.cc	2016-11-23 21:37:22 +0000
+++ src/ai/defaultai_seafaring.cc	2016-11-30 04:53:43 +0000
@@ -29,10 +29,9 @@
 	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
+	// abort if any player - including self - is too near to the spot (radius 10)
 	if (other_player_accessible(
-	       8, &tested_fields, &mineable_fields_count, candidate_spot, WalkSearch::kAnyPlayer)) {
+	       10, &tested_fields, &mineable_fields_count, candidate_spot, WalkSearch::kAnyPlayer)) {
 		return 0;
 	}
 
@@ -226,9 +225,9 @@
 
 	if (!allships.empty()) {
 		// iterating over ships and doing what is needed
-		for (std::list<ShipObserver>::iterator i = allships.begin(); i != allships.end(); ++i) {
+		for (std::list<ShipObserver>::iterator so = allships.begin(); so != allships.end(); ++so) {
 
-			const Widelands::Ship::ShipStates ship_state = i->ship->get_ship_state();
+			const Widelands::Ship::ShipStates ship_state = so->ship->get_ship_state();
 
 			// Here we manage duration of expedition and related variables
 			if (ship_state == Widelands::Ship::ShipStates::kExpeditionWaiting ||
@@ -240,42 +239,41 @@
 				// - expedition_start_time
 				// - expected_colony_scan
 				// - no_more_expeditions_
-				check_ship_in_expedition(*i, gametime);
+				check_ship_in_expedition(*so, gametime);
 
 				// We are not in expedition mode (or perhaps building a colonisation port)
 				// so resetting start time
-			} else if (expedition_ship_ == i->ship->serial()) {
+			} else if (expedition_ship_ == so->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;
+			if ((so->ship->get_ship_state() == Widelands::Ship::ShipStates::kExpeditionWaiting ||
+			     so->ship->get_ship_state() == Widelands::Ship::ShipStates::kExpeditionPortspaceFound) &&
+			    !so->waiting_for_command_) {
+				if (gametime - so->last_command_time > 180 * 1000) {
+					so->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);
+					    player_number(), so->ship->get_position().x, so->ship->get_position().y,
+					    (gametime - so->last_command_time) / 1000);
 				}
 			}
 			// if ships is waiting for command
-			if (i->waiting_for_command_) {
-				expedition_management(*i);
+			if (so->waiting_for_command_) {
+				expedition_management(*so);
 				action_taken = true;
 			}
 
 			// Checking utilization
-			if (i->ship->get_ship_state() == Widelands::Ship::ShipStates::kTransport) {
+			if (so->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;
+				   (so->ship->get_nritems() > 10) ? 10000 : so->ship->get_nritems() * 1000;
 				// This number is kind of average
 				persistent_data->ships_utilization =
 				   persistent_data->ships_utilization * 19 / 20 + tmp_util / 20;
@@ -323,49 +321,47 @@
 }
 
 /**
- * This is part of check_ships() function separated due to readibility purpuses
+ * This is part of check_ships() function separated for readability
  */
-void DefaultAI::check_ship_in_expedition(ShipObserver& so, uint32_t const gametime) {
+ void DefaultAI::check_ship_in_expedition(ShipObserver& so, uint32_t const gametime) {
 	// consistency check
 	assert(expedition_ship_ == so.ship->serial() || expedition_ship_ == kNoShip);
+	uint32_t expedition_time = gametime - persistent_data->expedition_start_time;
 
-	// This is obviously new expedition
+	// obviously a 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) {
+	// expedition is overdue, setting no_more_expeditions = true
+	// also we attempt to cancel expedition (the code for cancellation may not work properly)
+	// TODO(unknown): - test expedition cancellation deeply (may need to be fixed)
+	} else if (expedition_time >= expedition_max_duration) {
+		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);
+
+	// for known and running expedition
+	} else {
+		// decrease persistent_data->colony_scan_area based on lapsed time
 		assert(persistent_data->expedition_start_time > kNoExpedition);
-		// remaining_time is a percent so in range 0-100
+		assert(expedition_time < expedition_max_duration);
+
+		// calculate percentage of remaining expedition time (range 0-100)
 		const uint32_t remaining_time = 100 - ((gametime - persistent_data->expedition_start_time) /
-		                                       (kExpeditionMaxDuration / 100));
+		                                       (expedition_max_duration / 100));
 		assert(remaining_time <= 100);
 
-		// We calculate expected value and actual value (persistent_data->colony_scan_area
-		// is changed only when needed)
+		// calculate a new persistent_data->colony_scan_area (value is unchanged or decreased)
 		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);
 	}
 }
 
@@ -374,11 +370,7 @@
 
 	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;
-	}
+	allships.back().island_circ_direction = randomExploreDirection();
 
 	if (type == NewShip::kBuilt) {
 		marine_task_queue.push_back(kStopShipyard);
@@ -397,8 +389,14 @@
 	}
 }
 
+Widelands::IslandExploreDirection DefaultAI::randomExploreDirection () {
+	return game().logic_rand() % 20 < 10 ?
+	     Widelands::IslandExploreDirection::kClockwise :
+	     Widelands::IslandExploreDirection::kCounterClockwise;
+}
+
 // this is called whenever ship received a notification that requires
-// navigation decisions (these notifiation are processes not in 'real time')
+// navigation decisions (these notification are processes not in 'real time')
 void DefaultAI::expedition_management(ShipObserver& so) {
 
 	Map& map = game().map();
@@ -406,53 +404,55 @@
 	// 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;
+	// second we put current spot into expedition visited_spots
+	bool first_time_here = expedition_visited_spots.count(so.ship->get_position().hash()) == 0;
 	if (first_time_here) {
-		so.visited_spots.insert(so.ship->get_position().hash());
+		expedition_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
+	// if we have a port-space we can build a Port or continue exploring
+	// 1. examine to build a port (colony founding)
+	if (!so.ship->exp_port_spaces().empty()) {
 
 		// 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
+		// we make a decision based on the score value and random
+		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) {
+    // we were not here before
+	// OR we might randomly repeat island exploration
+	if (first_time_here || game().logic_rand() % 100 < repeat_island_prob) {
+		if (first_time_here) {
+			printf("EXPLORE- first time here\n");
+		} else {
+			printf("EXPLORE- here before\n");
+		}
+
+		// determine direction of island circle movement
+		// Note: if the ship doesn't own an island-explore-direction it is in inter-island exploration
+		// in this case we create a new direction at random, otherwise continue circle movement
+		if (!so.ship->is_island_circling()) {
+			so.island_circ_direction = randomExploreDirection();
+			printf("EXPLORE- set new SAIL direction: %u\n", so.island_circ_direction);
+		} else {
+			printf("EXPLORE- continue ISLAND CIRCLE, dir=%u\n", so.island_circ_direction);
+		}
+
+		// send the ship to circle island
 		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
+    // we head for open sea again
 	} else {
 		// determine swimmable directions
 		std::vector<Direction> possible_directions;
@@ -475,19 +475,17 @@
 		// 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);
+			// TODO(unknown): we should implement a 'rescue' procedure like 'sail for x fields and wait-state'
+			game().send_player_ship_explore_island(*so.ship, so.island_circ_direction);
+			printf("EXPLORE- jamming spot, cont CIRCLE, dir=%u\n", 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));
+			const Direction direction =
+			      possible_directions.at(game().logic_rand() % possible_directions.size());
+			game().send_player_ship_scouting_direction(*so.ship, static_cast<WalkingDir>(direction));
+
+			printf("EXPLORE- break free FOR SEA, dir=%u\n", direction);
 		}
 	}
 
@@ -495,3 +493,4 @@
 	so.waiting_for_command_ = false;
 	return;
 }
+

=== modified file 'src/logic/map_objects/tribes/ship.h'
--- src/logic/map_objects/tribes/ship.h	2016-11-03 07:20:57 +0000
+++ src/logic/map_objects/tribes/ship.h	2016-11-30 04:53:43 +0000
@@ -207,6 +207,11 @@
 		return expedition_->swimmable[dir - 1];
 	}
 
+	// whether the ship's expedition is in state "island-exploration" (circular movement)
+	bool is_island_circling () {
+		return expedition_->island_exploration;
+	}
+
 	/// \returns whether the expedition ship is close to the coast
 	bool exp_close_to_coast() const {
 		if (!expedition_)


Follow ups