← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/ship_scheduling into lp:widelands

 

TiborB has proposed merging lp:~widelands-dev/widelands/ship_scheduling into lp:widelands.

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/ship_scheduling/+merge/266626

This is rework of ship transportation - with goal to make it more effective (f.e. eliminate unexplained moves of ships and so on).

But I had to modify also regression tests, because in some situation ships behave differently, f.e. in current code in some situation first ship in m_ships is sent, or ship is sent to first port in m_ports, not considering distance or so.

I think there will have to be some discussion about this design :)

Oh, also debug window for port was improved, but still debug window for ship is missing, or rather not accessible in normal way... Once I had a ship on coast and was able to open debug window of ship. Obviously land and sea has bit different logic for opening debug wndows...
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/ship_scheduling into lp:widelands.
=== modified file 'src/economy/fleet.cc'
--- src/economy/fleet.cc	2015-02-05 12:11:20 +0000
+++ src/economy/fleet.cc	2015-07-31 20:49:05 +0000
@@ -337,6 +337,29 @@
 	return true;
 }
 
+uint32_t Fleet::count_ships(){
+	return m_ships.size();
+}
+
+uint32_t Fleet::count_ships_heading_here(EditorGameBase & egbase, PortDock * port){
+	uint32_t ships_on_way = 0;
+	//if (upcast(Game, game, &owner().egbase())) { //NOCOM is upcast needed?
+		for (uint16_t s = 0; s < m_ships.size(); s += 1){
+			if (m_ships[s]->get_destination(egbase) == port){
+				ships_on_way += 1;
+			}
+		}
+	//}
+	return ships_on_way;
+}
+
+uint32_t Fleet::count_ports(){
+	return m_ports.size();
+}
+bool Fleet::get_act_pending(){
+	return m_act_pending;
+}
+
 void Fleet::add_neighbours(PortDock & pd, std::vector<RoutingNodeNeighbour> & neighbours)
 {
 	uint32_t idx = std::find(m_ports.begin(), m_ports.end(), &pd) - m_ports.begin();
@@ -576,6 +599,25 @@
 }
 
 /**
+ * Search among the docks of the fleet for the one that has matches given coordinates.
+ * Intended for a ship querying in what portdock it is now.
+ *
+ * @return the dock, or 0 if not found.
+ */
+PortDock * Fleet::get_dock(EditorGameBase & egbase, Coords field_coords) const
+{
+	for (PortDock * temp_port : m_ports) {
+		for (Coords tmp_coords :  temp_port->get_positions(egbase)) {
+			if (tmp_coords == field_coords){
+				return temp_port;
+			}
+		}
+	}
+
+	return nullptr;
+}
+
+/**
  * @return an arbitrary dock of the fleet, or 0 if the fleet has no docks
  */
 PortDock * Fleet::get_arbitrary_dock() const
@@ -590,8 +632,9 @@
  */
 void Fleet::update(EditorGameBase & egbase)
 {
-	if (m_act_pending)
+	if (m_act_pending){
 		return;
+	}
 
 	if (upcast(Game, game, &egbase)) {
 		schedule_act(*game, 100);
@@ -608,6 +651,7 @@
 void Fleet::act(Game & game, uint32_t /* data */)
 {
 	m_act_pending = false;
+
 	if (!active()) {
 		// If we are here, most likely act() was called by a port with waiting wares or an expedition ready
 		// although there are still no ships. We can't handle it now, so we reschedule the act()
@@ -618,63 +662,225 @@
 
 	molog("Fleet::act\n");
 
-	for (Ship * temp_ship : m_ships) {
-		Ship & ship = *temp_ship;
-		if (ship.get_nritems() > 0 && !ship.get_destination(game)) {
-			molog("Ship %u has items\n", ship.serial());
-			bool found_dst = false;
-			for (ShippingItem& temp_item : ship.m_items) {
-				PortDock * dst = temp_item.get_destination(game);
-				if (dst) {
-					molog("... sending to portdock %u\n", dst->serial());
-					ship.set_destination(game, *dst);
-					found_dst = true;
-					break;
-				}
-			}
-			// If we end here, we just send the ship to the first port - maybe the old port got destroyed
-			if (!found_dst) {
-				assert(!m_ports.empty());
-				ship.set_destination(game, *m_ports[0]);
-			}
-		}
-	}
-
-	for (uint32_t i = 0; i < m_ports.size(); ++i) {
-		PortDock & pd = *m_ports[i];
-
-		if (pd.get_need_ship()) {
-			molog("Port %u needs ship\n", pd.serial());
-
-			bool success = false;
-			for (Ship * temp_ship : m_ships) {
-				Ship & ship = *temp_ship;
-				// Check whether ship is in TRANSPORT state
-				if (ship.get_ship_state() != Ship::TRANSPORT)
-					continue;
-
-				PortDock * dst = ship.get_destination(game);
-				// Check if ship has currently a different destination
-				if (dst && dst != &pd)
-					continue;
-				if (ship.get_nritems() >= ship.descr().get_capacity())
-					continue;
-
-				molog("... ship %u takes care of it\n", ship.serial());
-
-				if (!dst)
-					ship.set_destination(game, pd);
-
-				success = true;
-				break;
-			}
-
-			if (!success) {
-				schedule_act(game, 5000); // retry in the next time
-				m_act_pending = true;
-				break;
-			}
-		}
+	// we need to calculate what ship is to be send to which port
+	// for this we will have temporary data structure with format
+	// <<ship,port>,score>
+	// where ship and port are not objects but positions in m_ports and m_ships
+	// this is to allow native hashing
+	std::map<std::pair<uint16_t, uint16_t>, uint16_t> scores;
+
+	// so we will identify all pairs: idle ship : ports, and score all such
+	// pairs. We consider
+	// - count of wares onboard, first ware (oldest) is counted as 8 (prioritization)
+	//   (counting wares for particular port only)
+	// - count wares waiting at the port/3
+	// - distance between ship and a port (0-10 points, the closer the more points)
+	// - is another ship heading there right now?
+
+	// at the end we must know if requrests of all ports asking for ship were addressed
+	// if any unsatisfied, we must schedule new run of this function
+	// when we send a ship there, the port is removed from list
+	std::list<uint16_t> waiting_ports;
+
+	// this is just helper - first member of scores map
+	std::pair<uint16_t, uint16_t> mapping; //ship number, port number
+
+	// first we go over ships - idle ones (=without destination)
+	// then over wares on these ships and create first ship-port
+	// pairs with score
+	for (uint16_t s = 0; s < m_ships.size(); s += 1){
+		if (m_ships[s]->get_destination(game)) {
+			continue;
+		}
+		if (m_ships[s]->get_ship_state() != Ship::TRANSPORT) {
+			continue; // in expedition obviously
+		}
+
+		for (uint16_t i = 0; i < m_ships[s]->get_nritems(); i += 1){
+			PortDock * dst = m_ships[s]->m_items[i].get_destination(game);
+			if (!dst) {
+				// if wares without destination on ship without destination
+				// such ship can be send to any port, and should be sent
+				// to some port, so we add 1 point to score for each port
+				for (uint16_t p = 0; p < m_ports.size(); p += 1){
+					mapping.first = s;
+					mapping.second = p;
+					scores[mapping] += 1;
+				}
+				continue;
+			}
+
+			bool destination_found = false; //just a functional check
+			for (uint16_t p = 0; p < m_ports.size(); p += 1){
+				if (m_ports[p] ==  m_ships[s]->m_items[i].get_destination(game)){
+					mapping.first = s;
+					mapping.second = p;
+					scores[mapping] += (i == 0)?8:1;
+					destination_found = true;
+				}
+			}
+			if (!destination_found){
+				// Perhaps the throw here is too strong
+				// we can still remove it before stable release if it proves too much
+				// during my testing this situation never happened
+				throw wexception("A ware with destination that does not match any of player's"
+				" ports, ship %u, ware's destination: %u",
+				m_ships[s]->serial(),
+				m_ships[s]->m_items[i].get_destination(game)->serial());
+			}
+		}
+	}
+
+	// now opposite aproach - we go over ports to find out those that have wares
+	// waiting for ship then find candidate ships to satisfy the requests
+	for (uint16_t p = 0; p < m_ports.size(); p += 1){
+		PortDock & pd = *m_ports[p];
+		if (!pd.get_need_ship()){
+			continue;
+		}
+
+		// general stategy is "one ship for port is enough", but sometimes
+		// amount of ware waiting for ship is too high
+		if (count_ships_heading_here(game, &pd) * 25 > pd.count_waiting()) {
+			continue;
+		}
+
+		waiting_ports.push_back(p);
+
+		// scoring and entering the pair into scores (or increasing existing
+		// score if the pair is already there)
+		// following is to prohibit sending more then one empty ship to
+		// this port (no big harm, but regression tests does not like it) change the comment NOCOM
+		//uint16_t empty_ships_sent_here = 0;
+		for (uint16_t s = 0; s < m_ships.size(); s += 1){
+
+			if (m_ships[s]->get_destination(game)) {
+				continue; // already has destination
+			}
+
+			if (m_ships[s]->get_ship_state() != Ship::TRANSPORT) {
+				continue; // in expedition obviously
+			}
+
+			mapping.first = s;
+			mapping.second = p;
+			// folowing aproximately considers free capacity of a ship
+			scores[mapping] += ((m_ships[s]->get_nritems() > 15)?1:3)
+			+
+			std::min(
+				m_ships[s]->descr().get_capacity() - m_ships[s]->get_nritems(),
+				m_ports[p]->count_waiting()) / 3;
+		}
+	}
+
+	//now adding score for distance
+	for (std::pair<std::pair<uint16_t, uint16_t>, uint16_t> ship_port_relation : scores) {
+
+		// here we get distance ship->port
+		// possibilities are:
+		// - we are in port and it is the same as target port
+		// - we are in other port, then we use get_dock() function to fetch precalculated path
+		// - if above fails, we calculate path "manually"
+		int16_t route_length = -1;
+
+		PortDock * current_portdock = get_dock(game, m_ships[ship_port_relation.first.first]->get_position());
+
+		if (current_portdock) { // we try to use precalculated paths of game
+
+			// we are in the same portdock
+			if (current_portdock == m_ports[ship_port_relation.first.second]) {
+				route_length = 0;
+			} else { // it is different portdock then
+				Path tmp_path;
+				if (get_path(*current_portdock, *m_ports[ship_port_relation.first.second], tmp_path)) {
+					route_length = tmp_path.get_nsteps();
+				}
+			}
+		}
+
+		// most probably the ship is not in a portdock (should not happen frequently)
+		if (route_length == -1) {
+			route_length = m_ships[ship_port_relation.first.first]->calculate_sea_route
+			(game, *m_ports[ship_port_relation.first.second]);
+		}
+
+		// now we have length of route, so we need to calculate score
+		int16_t score_for_distance = 0;
+		if (route_length < 3) {
+			score_for_distance = 10;
+		} else {
+			score_for_distance = 8 - route_length / 50;
+		}
+		// must not be negative
+		score_for_distance = (score_for_distance < 0)?0:score_for_distance;
+
+		scores[ship_port_relation.first] += score_for_distance;
+	}
+
+	// looking for best scores and sending ships accordingly
+	uint16_t best_ship = 0;
+	uint16_t best_port = 0;
+	uint16_t best_score;
+
+	// after sending a ship we will remove one or more items from scores
+	while (!scores.empty()){
+		best_score = 0;
+
+		// searching for combination with highest score
+		for (std::pair<std::pair<uint16_t, uint16_t>, uint16_t> combination : scores) {
+			if (combination.second > best_score){
+				best_score = combination.second;
+				best_ship = combination.first.first;
+				best_port = combination.first.second;
+			}
+		}
+		if (best_score == 0){
+			// this is check of correctnes of this algorithm, this should not happen
+			throw wexception("Fleet::act(): No port-destination pair selected or its score is zero");
+		}
+
+		// making sure the winner has no destination set
+		assert(!m_ships[best_ship]->get_destination(game));
+
+		// now actual setting destination for "best ship"
+		m_ships[best_ship]->set_destination(game, *m_ports[best_port]);
+		molog("... ship %u sent to port %u, wares onboard: %2d, the port is asking for a ship: %s\n",
+		m_ships[best_ship]->serial(),
+		m_ports[best_port]->serial(),
+		m_ships[best_ship]->get_nritems(),
+		(m_ports[best_port]->get_need_ship())?"yes":"no");
+
+		// pruning the scores table
+		// the ship that was just sent somewhere cannot be send elsewhere :)
+		for (auto it = scores.cbegin(); it != scores.cend();){
+
+			// decreasing score for target port as there was a ship just sent there
+			if (it->first.second == best_port) {
+				mapping.first = it->first.first;
+				mapping.second = it->first.second;
+				scores[mapping] /= 2;
+				// just make sure it is nonzero
+				scores[mapping] = (scores[mapping] == 0)?1:scores[mapping];
+			}
+
+			// but removing all pairs where best ship is participating as it is not available anymore
+			// (because it was sent to "best port")
+			if (it->first.first == best_ship) {
+				scores.erase(it++);
+			} else {
+				++it;
+			}
+		}
+
+		// also removing the port from waiting_ports
+		waiting_ports.remove(best_port);
+	}
+
+	if (!waiting_ports.empty()) {
+		molog("... there are %" PRIuS " ports requesting ship(s) we cannot satisfy yet\n",
+		waiting_ports.size());
+		schedule_act(game, 5000); // retry next time
+		m_act_pending = true;
 	}
 }
 
@@ -682,8 +888,7 @@
 {
 	MapObject::log_general_info(egbase);
 
-	molog
-		("%" PRIuS " ships and %" PRIuS " ports\n",  m_ships.size(), m_ports.size());
+	molog ("%" PRIuS " ships and %" PRIuS " ports\n",  m_ships.size(), m_ports.size());
 }
 
 #define FLEET_SAVEGAME_VERSION 4

=== modified file 'src/economy/fleet.h'
--- src/economy/fleet.h	2014-09-10 08:55:04 +0000
+++ src/economy/fleet.h	2015-07-31 20:49:05 +0000
@@ -24,6 +24,7 @@
 
 #include "base/macros.h"
 #include "logic/instances.h"
+#include "logic/widelands_geometry.h"
 
 namespace Widelands {
 
@@ -79,6 +80,7 @@
 	Player & owner() const {return m_owner;}
 
 	PortDock * get_dock(Flag & flag) const;
+	PortDock * get_dock(EditorGameBase &, Coords) const;
 	PortDock * get_arbitrary_dock() const;
 	void set_economy(Economy * e);
 
@@ -98,6 +100,11 @@
 	bool get_path(PortDock & start, PortDock & end, Path & path);
 	void add_neighbours(PortDock & pd, std::vector<RoutingNodeNeighbour> & neighbours);
 
+	uint32_t count_ships();
+	uint32_t count_ships_heading_here(EditorGameBase & egbase, PortDock * port);
+	uint32_t count_ports();
+	bool get_act_pending();
+
 protected:
 	void act(Game &, uint32_t data) override;
 

=== modified file 'src/economy/portdock.cc'
--- src/economy/portdock.cc	2015-05-05 20:00:21 +0000
+++ src/economy/portdock.cc	2015-07-31 20:49:05 +0000
@@ -364,8 +364,9 @@
 			m_waiting.pop_back();
 		}
 
-		if (m_waiting.empty())
+		if (m_waiting.empty()){
 			set_need_ship(game, false);
+		}
 	}
 
 	m_fleet->update(game);
@@ -408,6 +409,13 @@
 	return count;
 }
 
+/**
+ * Return the number of wares or workers waiting at the dock.
+ */
+uint32_t PortDock::count_waiting() {
+	return m_waiting.size();
+}
+
 /// \returns whether an expedition was started or is even ready
 bool PortDock::expedition_started() {
 	return (m_expedition_bootstrap.get() != nullptr) || m_expedition_ready;

=== modified file 'src/economy/portdock.h'
--- src/economy/portdock.h	2014-09-10 10:18:46 +0000
+++ src/economy/portdock.h	2015-07-31 20:49:05 +0000
@@ -113,6 +113,7 @@
 	void log_general_info(const EditorGameBase &) override;
 
 	uint32_t count_waiting(WareWorker waretype, WareIndex wareindex);
+	uint32_t count_waiting();
 
 	// Returns true if a expedition is started or ready to be send out.
 	bool expedition_started();

=== modified file 'src/logic/ship.cc'
--- src/logic/ship.cc	2015-04-07 20:56:02 +0000
+++ src/logic/ship.cc	2015-07-31 20:49:05 +0000
@@ -242,18 +242,8 @@
 
 	PortDock* dst = get_destination(game);
 	if (!dst) {
-		molog("ship_update: No destination anymore.\n");
-		if (m_items.empty())
-			return false;
-		molog("but it has wares....\n");
-		pop_task(game);
-		PortDock* other_dock = m_fleet->get_arbitrary_dock();
-		// TODO(sirver): What happens if there is no port anymore?
-		if (other_dock) {
-			set_destination(game, *other_dock);
-		} else {
-			start_task_idle(game, descr().main_animation(), 2000);
-		}
+		//here we just do nothing, this is usually OK
+		start_task_idle(game, descr().main_animation(), 10000);
 		return true;
 	}
 
@@ -729,7 +719,7 @@
  * @note This is supposed to be called only from the scheduling code of @ref Fleet.
  */
 void Ship::set_destination(Game& game, PortDock& pd) {
-	molog("set_destination to %u (currently %" PRIuS " items)\n", pd.serial(), m_items.size());
+	molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd.serial(), m_items.size());
 	m_destination = &pd;
 	send_signal(game, "wakeup");
 }
@@ -755,9 +745,9 @@
 }
 
 /**
- * Find a path to the dock @p pd and follow it without using precomputed paths.
+ * Find a path to the dock @p pd, returns its length, and the path optionally.
  */
-void Ship::start_task_movetodock(Game& game, PortDock& pd) {
+uint32_t Ship::calculate_sea_route(Game& game, PortDock& pd, Path* finalpath){
 	Map& map = game.map();
 	StepEvalAStar se(pd.get_warehouse()->get_position());
 	se.m_swim = true;
@@ -772,15 +762,47 @@
 	FCoords cur;
 	while (astar.step(cur, cost)) {
 		if (cur.field->get_immovable() == &pd) {
-			Path path;
-			astar.pathto(cur, path);
-			start_task_movepath(game, path, descr().get_sail_anims());
-			return;
+			if (finalpath){
+				astar.pathto(cur, *finalpath);
+				return finalpath->get_nsteps();
+			} else {
+				Path path;
+				astar.pathto(cur, path);
+				return path.get_nsteps();
+			}
 		}
 	}
 
-	molog("start_task_movedock: Failed to find path!\n");
-	start_task_idle(game, descr().main_animation(), 5000);
+	molog("   calculate_sea_distance: Failed to find path!\n");
+	return std::numeric_limits<uint32_t>::max();
+
+}
+
+/**
+ * Find a path to the dock @p pd and follow it without using precomputed paths.
+ */
+void Ship::start_task_movetodock(Game& game, PortDock& pd) {
+	Path path;
+
+	uint32_t const distance = calculate_sea_route(game, pd, &path);
+
+	// if we get a meaningfull result
+	if (distance < std::numeric_limits<uint32_t>::max()) {
+		start_task_movepath(game, path, descr().get_sail_anims());
+		return;
+	} else {
+		log("start_task_movedock: Failed to find a path: ship at %3dx%3d to port at: %3dx%3d\n",
+		get_position().x,
+		get_position().y,
+		pd.get_positions(game)[0].x,
+		pd.get_positions(game)[0].y);
+		//this should not happen, but in theory there could be some inconstinency
+		//I (tiborb) failed to invoke this situation when testing so
+		//I am not sure if following line behaves allright
+		get_fleet()->update(game);
+		start_task_idle(game, descr().main_animation(), 5000);
+	}
+
 }
 
 /// Prepare everything for the coming exploration

=== modified file 'src/logic/ship.h'
--- src/logic/ship.h	2015-06-10 06:46:40 +0000
+++ src/logic/ship.h	2015-07-31 20:49:05 +0000
@@ -115,6 +115,8 @@
 	void start_task_movetodock(Game &, PortDock &);
 	void start_task_expedition(Game &);
 
+	uint32_t calculate_sea_route(Game& game, PortDock& pd, Path* finalpath = nullptr);
+
 	void log_general_info(const EditorGameBase &) override;
 
 	uint32_t get_nritems() const {return m_items.size();}

=== modified file 'src/logic/warehouse.cc'
--- src/logic/warehouse.cc	2015-02-13 22:39:34 +0000
+++ src/logic/warehouse.cc	2015-07-31 20:49:05 +0000
@@ -28,6 +28,7 @@
 #include "base/wexception.h"
 #include "economy/economy.h"
 #include "economy/flag.h"
+#include "economy/fleet.h"
 #include "economy/portdock.h"
 #include "economy/request.h"
 #include "economy/ware_instance.h"
@@ -1427,8 +1428,25 @@
 {
 	Building::log_general_info(egbase);
 
-	if (descr().get_isport())
-		molog("Port dock: %u\n", m_portdock ? m_portdock->serial() : 0);
+	if (descr().get_isport()){
+		PortDock* pd_tmp = m_portdock;
+		if (pd_tmp){
+			molog("Port dock: %u\n", pd_tmp->serial());
+			molog("port needs ship: %s\n", (pd_tmp->get_need_ship())?"true":"false");
+			molog("wares and workers waiting: %u\n", pd_tmp->count_waiting());
+			molog("exped. in progr.: %s\n", (pd_tmp->expedition_started())?"true":"false");
+			Fleet* fleet = pd_tmp->get_fleet();
+			if (fleet) {
+				molog("* fleet: %u\n", fleet->serial());
+				molog("  ships: %u, ports: %u\n", fleet->count_ships(), fleet->count_ports());
+				molog("  m_act_pending: %s\n", (fleet->get_act_pending())?"true":"false");
+			} else {
+				molog("No fleet?!\n");
+			}
+		} else {
+			molog ("No port dock!?\n");
+		}
+	}
 }
 
 

=== modified file 'test/maps/expedition.wmf/scripting/init.lua'
--- test/maps/expedition.wmf/scripting/init.lua	2015-06-29 13:49:35 +0000
+++ test/maps/expedition.wmf/scripting/init.lua	2015-07-31 20:49:05 +0000
@@ -103,6 +103,36 @@
    sleep(100)
 end
 
+--function cancel_expedition_or_sink_in_shipwindow()
+    --if second_ship then
+	--ship_to_click=second_ship
+	--elseif first_ship then
+	--ship_to_click=first_ship
+	--else
+		--assert(false)
+		--end
+		
+   --click_on_ship(ship_to_click)
+   --if click_button("cancel_expedition") then
+		--sleep(100)
+		--assert_true(click_button("ok"))
+		--sleep(100)
+		--close_windows()
+		--sleep(100)
+		--print (" DEBUG expedition cancelled")
+   --else
+		--click_on_ship(ship_to_click)
+		--assert_true(click_button("sink"))
+		--sleep(100)
+		--assert_true(click_button("ok"))
+		--sleep(100)
+		--close_windows()
+		--sleep(100) 
+		--print (" DEBUG ship sunk")
+   --end   
+--end
+
+
 function dismantle_hardener()
    assert_true(click_building(p1, "hardener"))
    assert_true(click_button("dismantle"))
@@ -151,18 +181,28 @@
    first_ship = p1:place_bob("ship", map:get_field(10, 10))
 end
 
+function create_second_ship()
+   second_ship = p1:place_bob("ship", map:get_field(14, 10))
+end
+
 function create_two_ships()
    create_one_ship()
-   second_ship = p1:place_bob("ship", map:get_field(14, 10))
+   create_second_ship()
 end
 
-function test_cancel_started_expedition_on_ship()
+function test_cancel_started_expedition_on_ship(needs_second_ship)
    sleep(100)
    game.desired_speed = 10 * 1000
 
    -- Start a new expedition.
    port:start_expedition()
    wait_for_message("Expedition Ready")
+   
+   --if current test requires second ship...
+   if needs_second_ship then
+      create_second_ship()
+   end
+   
    game.desired_speed = 10 * 1000
    sleep(10000)
 
@@ -197,15 +237,23 @@
    game.desired_speed = 10 * 1000
    sleep(10000)
 
-   first_ship.island_explore_direction="ccw"
+   if first_ship.state=="exp_waiting" then
+      expedition_ship=first_ship
+   elseif second_ship.state=="exp_waiting" then
+      expedition_ship=second_ship
+   else
+      assert(false)
+   end
+
+   expedition_ship.island_explore_direction="ccw"
    sleep(2000)
-   assert_equal("ccw",first_ship.island_explore_direction)
+   assert_equal("ccw",expedition_ship.island_explore_direction)
    sleep(6000)
 
    stable_save("sailing")
    assert_equal(1, p1:get_workers("builder"))
 
-   cancel_expedition_in_shipwindow(first_ship)
+   cancel_expedition_in_shipwindow(expedition_ship)
    sleep(20000)
    assert_equal(1, p1:get_workers("builder"))
    check_wares_in_port_are_all_there()
@@ -218,6 +266,7 @@
    wl.ui.MapView():close()
 end
 
+--NOCOM needed?
 function test_cancel_when_port_space_was_reached()
    sleep(100)
    game.desired_speed = 10 * 1000
@@ -263,11 +312,20 @@
 
    port:start_expedition()
    wait_for_message("Expedition Ready")
-   first_ship.island_explore_direction="ccw"
+   
+   if first_ship.state=="exp_waiting" then
+      expedition_ship=first_ship
+   elseif second_ship.state=="exp_waiting" then
+      expedition_ship=second_ship
+   else
+      assert(false)
+   end   
+   
+   expedition_ship.island_explore_direction="ccw"
    sleep(2000)
-   assert_equal("ccw",first_ship.island_explore_direction)
+   assert_equal("ccw",expedition_ship.island_explore_direction)
    wait_for_message("Port Space Found")
-   first_ship:build_colonization_port()
+   expedition_ship:build_colonization_port()
    sleep(500)
    assert_equal(1, p1:get_workers("builder"))
    wait_for_message("Port")

=== modified file 'test/maps/expedition.wmf/scripting/test_cancel_started_expedition_on_ship_one_ship.lua'
--- test/maps/expedition.wmf/scripting/test_cancel_started_expedition_on_ship_one_ship.lua	2013-10-29 20:22:08 +0000
+++ test/maps/expedition.wmf/scripting/test_cancel_started_expedition_on_ship_one_ship.lua	2015-07-31 20:49:05 +0000
@@ -1,5 +1,6 @@
 run(function()
    create_one_ship()
 
-   test_cancel_started_expedition_on_ship()
+   --false indicates that second ship is not to be created
+   test_cancel_started_expedition_on_ship(false)
 end)

=== modified file 'test/maps/expedition.wmf/scripting/test_cancel_started_expedition_on_ship_two_ships.lua'
--- test/maps/expedition.wmf/scripting/test_cancel_started_expedition_on_ship_two_ships.lua	2013-10-29 20:22:08 +0000
+++ test/maps/expedition.wmf/scripting/test_cancel_started_expedition_on_ship_two_ships.lua	2015-07-31 20:49:05 +0000
@@ -1,5 +1,6 @@
 run(function()
-   create_two_ships()
+   create_one_ship()
 
-   test_cancel_started_expedition_on_ship()
+   --true indicates that second ship is to be created
+   test_cancel_started_expedition_on_ship(true)
 end)

=== modified file 'test/maps/expedition.wmf/scripting/test_cancel_when_port_space_was_reached_two_ships.lua'
--- test/maps/expedition.wmf/scripting/test_cancel_when_port_space_was_reached_two_ships.lua	2013-10-29 20:22:08 +0000
+++ test/maps/expedition.wmf/scripting/test_cancel_when_port_space_was_reached_two_ships.lua	2015-07-31 20:49:05 +0000
@@ -1,5 +1,47 @@
 run(function()
    create_two_ships()
 
-   test_cancel_when_port_space_was_reached()
+
+   sleep(100)
+   game.desired_speed = 10 * 1000
+
+   -- Send expedition to port space.
+   port:start_expedition()
+   wait_for_message("Expedition Ready")
+   assert_equal(1, p1:get_workers("builder"))
+   sleep(500)
+
+
+   if first_ship.state=="exp_waiting" then
+      expedition_ship=first_ship
+   elseif second_ship.state=="exp_waiting" then
+      expedition_ship=second_ship
+   else
+      assert(false)
+   end
+
+   expedition_ship.island_explore_direction="ccw"
+   sleep(2000)
+   assert_equal("ccw",expedition_ship.island_explore_direction)
+   wait_for_message("Port Space Found")
+   sleep(500)
+   assert_equal(1, p1:get_workers("builder"))
+
+   stable_save("reached_port_space")
+   assert_equal(1, p1:get_workers("builder"))
+
+   cancel_expedition_in_shipwindow(expedition_ship)
+   sleep(20000)
+   assert_equal(1, p1:get_workers("builder"))
+   check_wares_in_port_are_all_there()
+
+   -- Dismantle the hardener to make sure that the builder is able to do his work.
+   game.desired_speed = 50 * 1000
+   dismantle_hardener()
+
+   print("# All Tests passed.")
+   wl.ui.MapView():close()
+
+
+
 end)


Follow ups