Example: This is an example of an example.
The examples are often taken from hypothetical games that are not, have never been and will never be implemented for Widelands, but are useful to illustrate some issue.There are notes that inform about the actual implementation that currently exists Widelands. Such notes look like this.
Knowledge about Widelands or similar game engines help to understand this document.This document is still very incomplete. There may be old parts that should be updated. Many issues are not enough described, such as interactions between players.
An important question is how to define what it means that a transport system is good. It can be good in different aspects. Let us first consider the pure game logic aspect. A transport system that is good in this aspect is one that helps the player to achieve well in games by making rational decisions. It can be measured by letting a player (human or computer controlled) use different transport systems in games against other players and observing how the choice of transport system affects the outcome of the games. If a player performs well with a transport system, it means that that transport system is good for that player. If a player performs bad with a transport system, it means that that transport sytem is bad for that player. But it does not necessarly mean that that transport system is generally bad. It could also be caused by the player's inability to operate it. So care must be taken to teach the players to use their transport systems before making such measurements. In practice, such measurements could be carried out by letting computer controlled players using different transport systems play a lot of games on different randomly generated maps, against each other or against human players on public game servers.
Another aspect of transport systems is usability. It should be easy for the players to configure the transport sytem so that it works well to make it operate well. Providing good default values is an important part of aspect. A good interface (GUI for human players, API for computer players) is another part. A thirs aspect of transport systems is computational efficiency. The transport system should not use more than a fair share of the computational resources on the kind computer that users buy nowadays to play games. There should be enough resources left for game logic, computer players, graphics and sound.
Widelands does not yet support immovable objects on triangles.
Enumeration of all possible destinations (or origins if searching backwards), followed by a guided search (A*) for each of them, and then choosing the shortest path that was found. Both algorithms give an optimal result, but they take different time.
Uniform-cost is good when there are many possible destinations (and they are evenly distributed).
Uniform-cost is bad when there are many nodes and few possible destinations. If there is no possible destination, the search will consider every node. This is the worst case.
The location-enumeration with A* approach is bad when there are many possible destinations, because it has to consider every possible destination. But it is possible to reduce the time by not making a search for every considered destination. If a destination is such that no path to (or from, if searching backwards) it can be cheaper than the currently best found path, then that location can be omitted. This omitting of locations needs to use the same heuristic as the A*-algorithm itself uses in each search. Such a heuristic would consider the distance, height difference and the movement cost rules of the game, including movement cost modifiers of terrain (if any). (Of course the heuristic has to be admissible.) Therefore the efficiency of this optimization depends heavily on the movement rules. It works best when movement cost is closely tied to distance, and it does not work at all when there are cases when movement is free (for example the railroads in Freeciv). The efficiency of the optimization also depends on the order in which the locations are considered. They should be sorted according to the heuristic.
The location-enumeration with A* approach is good when there are few possible destinations. It does not have to consider any nodes at all when there is no possible destination. This is the best case.
Note that the uniformed-cost search has its best case when the location-enumeration with A* approach has its worst case, and the other way around. Therefore it is tempting to try to combine them. This can be done by letting them work independently side by side. When one of them finishes, the other is stopped. If they get equal execution time, the worst case of the combined search would be 2 * min(worst_case_1, worst_case_2). If there would have been more than 2 alternatives, the worst case would be N * min(worst_case_1, worst_case_2, ..., worst_case_N). Parallell searches could probably be implemented without threads, although it could be complex, because the 2 parallell tasks are different. An impementation with threads would make it possible to reduce the worst case to ceiling(N / number_of_processors) * min(worst_case_1, worst_case_2, ..., worst_case_N) , which means that the worst case of 2 on a dual processor system would only be min(worst_case_1, worst_case_2).
Another approach is to chose one of the 2 approaches based on the size of the search space and the number of possible destinations.
Note that if it would be acceptable to only use the uniform-cost alternative, there would be one less reason use the economy concept, because the search would not need the list of destinations (which the economy provides). If there is a reachable destination, it will be found. Not needing the economy concept would make the impelementation of the transport system a lot easier, because managing ecomomies is complex, for example the handling of splits and merges.
Widelands uses A* for pathfinding between_flags (Economy::find_route). It uses a specialized queue implementation (FlagQueue), which has the specialized procedure boost. The heuristic is Map::calc_cost_estimate. IS THIS HEURISTIC REALLY ADMISSIBLE? If not, the search may return a suboptimal result.
Widelands uses location-enumeration with A* for finding the nearest supply when a ware is requested. It is implemented in Economy::find_best_supply. The optimization that nodes with higher estimated cost than the lowest cost found so far are dropped is implemented. But the supply locations are not sorted by estimated cost, so the optimization is probably not as efficient as it could be.
Example: Log is a type of ware.
Example: Woodcutter is a type of worker.
Wares and workers can be produced by buildings. This can require some inputs in form of wares, workers, map objects, natural resources, experience or time. Those inputs may or may not be consumed (if time is an input, it is of course consumed).
Example: Grain is a type of ware. It is produced by a building of type farm. This requires a worker input of type farmer (not consumed), a map object input of type grown-up grainfield (consumed) and time input when the worker walks to the field, harvests, walks back and rests.
Example: Farmer is a type of worker. It is produced at a building of type warehouse. This requires a ware input of type scythe (consumed) and a worker input of type carrier (consumed).
Example: Iron ore is a type of ware. It is produced at a building of type mine. This requires a ware input of type food (consumed), a worker input of type miner (not consumed), a natural resouce input of type iron (consumed) and time input.
Example: Master-brewer is a type of worker. It is produced at a building of type brewery. This requires a worker input of type brewer (consumed) and experience input (consumed).
In Widelands, carriers are workers. They are produced at a building of type warehouse. This requires no other input than a time of up to 2500 milliseconds. This is a special case, see the comment for the implementation of the procedure void Warehouse::act(Game*, uint).
Wares and workers can be transported through transport zones. For some combinations of ware/worker type and transport zone type, this requires a carrier. For other combinations, no carrier is needed. Certain combinations may be disabled, which means that the transport is not possible, with or without the help of a carrier.
Example: When a ware of type iron ore is transported through a transport zone of type land, a carrier is required.
Example: When ware of type log is transported through a transport zone of type river, no carrier is needed.
Example: When a worker of type baker is transported through a transport zone of type road, no carrier is needed.
Example: When a worker of type blacksmith is transported through a transport zone of type ocean, a carrier is needed.
Example: The transport of workers of type ferry through transport zones of type land, is disabled.
The restriction mentioned above that certain combinations of ware/worker type and transport zone type are disabled could be seen as an incompatibility between cargo type and carrier type. But seeing it that way may make transfer planning unnecessary difficult, because it has the consequence that not all carriers in a transport zone have equal capabilities as far as cargo types are concerned.
There seems to be some differences between wares and workers:
Since all of those differences are things that workers have, but wares do not have, it suggests that worker concept is an extension of the ware concept. So a worker is a ware with a little extra functionality.
Example: The set of provider types for the ware type meat consists of hunter and pig farm.
Sometimes there is reason to use on-demand production. This is how it works: when a provider has all the prerequisites for producing a particular type of wareware, it registers an on-demand capability for that ware type. The economy can then order the production when there is a demand.
Example: The set of recipient types for the ware type grain consists of bakery, pig farm, donkey breeder and brewery.
When the recipient requests the ware from the economy, it immediately answers in 1 of 2 ways:
A transfer has been arranged and the ware is expected to arrive at timestamp <n>.
In this case the recipient can make another request for the same ware type.
No such ware is available. The request has been noted and a transfer will be arranged when a ware becomes available for the request.
In this case the recipient is not allowed to make another request for the same ware type (until a transfer has been arranged for the first request).
| Example: A tool factory uses 1 ware of type wood and 1 ware of type metal to produce 1 ware of type tool. The internal storage can hold up to 4 wood, 3 metal and 2 tool. A production cycle takes 10 time units. Suppose that a new tool factory begins to operate at time 1000. Then it will first request a wood. Since it has no metal, and does not know when it will get any, set the time for when the wood is needed to infinity. The economy will reply, and so on: |
Game time: 1000
|
||||||||||||
|
Factory: I want a wood at time <inf>. Economy: Request #01: a wood is estimated to arrive at time 1014. |
Game time: 1000
|
||||||||||||
|
Factory: I want a metal at time 1014. Economy: Request #02: A metal is estimated to arrive at time 1018. |
Game time: 1000
|
||||||||||||
|
Factory: I want order #01 to arrive a time 1018. |
Game time: 1000
|
||||||||||||
|
Factory: I want a wood at time <inf>. Economy: Request #03: A wood is estimated to arrive at time 1016. |
Game time: 1000
|
||||||||||||
|
Factory: I want a metal at time 1028. Economy: Request #04: No metal available. |
Game time: 1000
|
||||||||||||
|
Factory: I want a wood at time <inf>. Economy: Request #05: A wood is estimated to arrive at time 1018. |
Game time: 1000
|
||||||||||||
|
Factory: I want a wood at time <inf>. Economy:Request #06: No wood available. |
Game time: 1000
|
||||||||||||
|
Now the factory has made all requests it can, and it expects to begin working on its first tool at time 1018. Suppose that the time advances to 1012 and then the economy gets a metal to transfer to the factory. Then they will have this conversation: Economy: A metal became available for request #4 and is estimated to arrive at time 1041. |
Game time: 1012
|
||||||||||||
| Factory: I want order #03 to arrive at time 1041. |
Game time: 1012
|
||||||||||||
|
Factory: I want a metal at time 1051.
Economy: Request #07: No metal available. |
Game time: 1012
|
||||||||||||
|
The the conversation has ended for this time. The time advances to
1014 and the first wood arrives (on time). The factory now wants the
first metal as soon as possible.
Economy: Request #01: delivered. Factory: I want order #02 to arrive at time 0. |
Game time: 1014
|
||||||||||||
|
The time advances to 1016 and the metal has been delayed for some reason. So the factory has to replan the prodution schedule. Economy: Request #02: new estimated arrival time is 1023. Factory: I want order #04 to arrive at time 1033. |
Game time: 1016
|
||||||||||||
|
The time advances to 1023 and the metal arrives (on time).
Economy: Request #02: delivered. |
Game time: 1023
|
||||||||||||
|
The factory now sees that it has all needed materials. Now it checks
if there is a free output storage for the tool and reserves it. Since
nothing can fail at this point (except for things like war), the
factory can now announce that a tool will be produced. Depending on
how intelligent the economy is, it may already
use that information. It can then give the factory an estimated pickup
time, so that the factory can plan for the usage of the storage space.
If not, it replies with infinity.
Factory: I consume a wood. Factory: I consume a metal. Factory: A tool will be produced at time 1043. |
Game time: 1023
|
The reason to divide a player's land into economies is that it it would be in vain to search for ware supply points or requests in nodes that are not connected to the node where the search begins. And within an economy, every node is connected to every other node, so if there is a matching pair of supply and request, it will be possible to deliver. (Unless of course there is some kind of transport deadlock.)
Note: the following paragraph is newly written and needs to be developed further.An important limitation of the economy concept as described here is that it is actually per ware type. Suppose that two nodes are connected so that most wares can be transported between them through a network of transport zones and exchange places. But what does it mean if some of the transport zones are of a type that can not transport a particular type of ware? Suppose that a ware of type catapult can not be transported betweent the two nodes. This means that the two nodes are not in the same economy as far as the ware type catapult is concerned. This means that one of the following design choices has to be made:
| Ware class | Can move on | Least passage requirement | Includes for example |
| walking | land | narrow | persons, pack-animals |
| cart | land | medium | carts, small sleighs |
| waggon | land | wide | waggons, big sleighs, catapults |
| boat | calm waters and rivers | narrow | boats |
| ferry | calm waters | medium | ferries |
| floatable | rivers | wide | trunks |
| Ware class | Least carrier requirement | Includes for example | |
| transportable_tiny | tiny | valuables (gold, gems, weapons, tools) | |
| tranportable_small | small | bulk wares such as beer (in barrels), ore | |
| waggon | land | wide | waggons, big sleighs, catapults |
| boat | calm waters and rivers | narrow | boats |
| ferry | calm waters | medium | ferries |
| floatable | rivers | wide | trunks |
| transportable_tiny | |||
Example: In the roadbased model, a flag is an exchange place. It has a set of roads that are connected to it. Those roads are transport zones of the type road, which are compatible with the carrier type road carrier.
Example: In the roadless model, a contiuous passable land area controlled by a player is a transport zone of type land. It is compatible with the carrier type land carrier.Example: A port is an exchange place. It is connected the ocean, which is a transport zone of type ocean. The transport zone type ocean is compatible with the carrier type ship. In the roadless model, the port is also connected to a transport zone of type land. In the roadbased model, the port is connected to a transport zone of type road.
There can be different types of exchange places, that enable exchanges between different types of transport zones.Example: An exchange place of type port enables exchanges between transport zones of types ocean and road.
Example: An exchange place of type airport enables exchanges between transport zones of types air and road.
Example: An exchange place of type floatway station enables exchanges between transport zones of types floatway and land.
When finding a path in a network of transport zones and exchange-places, the search node type is a combination of map node and transport zone. The search space can be thought of as a 3-dimensional graph, where each transport zone is a horizontal 2-dimensional graph, with a unique z-coordinate. Exchange places are vertical connections between the horizontal transport zones.
First I explain the basic idea of how searching is done. This is not completely correct, so I will explain some modifications afterwards. The search function has a queue of searchnodes to be expanded. When the search begins, each combination of the begin map node and the transport systems that it belongs to are added to the queue. The cost is the carrier-wait-time + the carrier-load-time. After that, the main loop of the search function begins. It runs until the queue is empty (or the end location is reached). The loop takes the first (lowest cost) searchnode from the queue. If the map node of that searchnode is the end location, the search ends successfully. Otherwise successors are generated and added to the queue. But that is just standard searching. The special thing here is the generation of successors. There are two kinds of successors.Generating successors in different map nodes and same transport zone: For each neighbouring map node, check if it belongs to the same transport zone. If it does, the combination of that map node and the current transport zone is a successor. The cost of the successor is the cost of the current searchnode + the cost to move from the current map node to that neighbouring map node.
Generating successors in the same map node and different transport zone: Since there must be an exchange place to move between different transport zones, this is first tested. If there is, it is checked if the exchange place's type allows exchanges from the current transport zone's type. If that is allowed, iterate over all transport zones that the current map node belongs to. For each of them, check if its type is a target of the exchange place's type. If so, the combination of the current map node and the new transport zone is a successor. The cost of the successor is the cost of the current searchnode + the carrier-unload-time of the current transport zone + the carrier-wait-time of the interface + the carrier-load-time of the new transport zone.This is what is done when a successor has been generated:
The second modification is needed to preserve a property of the current road implementation in Widelands. As transport zones are defined so far, it is assumed that transport directly between 2 adjacent map nodes that belong to the same transport zone is always possible. But in Widelands, roads can be built from node A to the neighbour of A via other nodes. The question is if this makes sense. If not, the road building interface could straighten the roads that the user builds. When the user clicks on the neighbour of A, the road is made directly from A to the neighbour, and the detour is removed. But I can actually think of one case where such detours make sense; mountain roads. As seen in reality, detours are built instead of straight very steep roads.
A good solution to this is to straighten roads unless the detour is actually cheaper than the direct route. Then the pathfinding will choose the detour, so it will follow the actual road and be correct.
Straightening roads works as follows: Before a road is finished (and preferrably after every enhancement during the building process), a new search is made. This search finds an optimal path from the road's starting point to the end point, but only through nodes that have been allocated to the road.But there is another solution that is not so difficult and allows keeping the current behaviour, wether it makes sense or not. Each searchnode can have a vector of 6 bits that store wether a direction is allowed. This can be stored in a byte together with the 2 bits used for load ability and unload ability.
Such a vector of allowed directions may be necessary anyway when floating is implemented, because floating should only work downstream.Another idea is to implement a specialization/optimization of the transport zone concept; let us call it single-ended transport zone type. This is when the transport zone has only one node where the ware can be unloaded. The transport zone type road as currently implemented in Widelands is a single-ended transport zone type, because the only allowed destination of a ware passing the road is the flag at the other end. This would allow caching of the road's cost instead of recalculating it step by step each time it is considered during pathfinding. As a side effect of this, the search algorithm would be able to return optimal results even for irrational roads without using a vector of allowed directions.
Widelands implements a roadbased model. One limitation is that there must never be 2 flags that are at adjacent nodes. This implies that each road has at least 2 sections. A second limitation is that each road has exactly 2 endpoints. This implies that a road can not have crossings (forks). (But of course the transport system as a whole can have crossings. They are at the exchange points.) A third limitation is that roads may not overlap except at exchange points. A fourth limitation is that a road can have at most 1 carrier. There is an important special rule that carriers leave the road that they work on and walk into buildings, when the carriers deliver wares to buildings.
But even in the roadless model, roads can have a prominent role, both logcally and visually. Idle diggers (the workers with the showels that prepare constructionsites) could spend their time building roads on the most walked sections. Sections with roads could be faster to move on.
To determine the roadbuilding priority of the sections, they could have counters for how many wares have passed there (maybe even weighted by ware prioritypriority). A decay function should be used to make recently trafficed sections more important than sections that were just trafficed a lot in the past.Sections that have been walked a lot, but which the diggers did not yet have time to construct road on, could be marked visually as dirt paths, where the vegetation on the ground is worn off by the traffic.
This role of roads in the game would most likely be at least as entertaining as a roadbased model for a significant fraction of the potential userbase.This kind of roads would cause an interesting behaviour that also exists in reality. Since the pathfinding would give paths that are optimal with roads instead of what would be optimal without roads, old roads are kept in use even if a hypothetical new road along a shorter path would have been more efficient, but that new road never gets a chance to compete, because the carriers use the old road, so the diggers never see the demand for the new road. Note that this is exactly what often happens in the real world. A solution to this (if it is considered a problem) could be to compute the 2 paths (the optimal considering roads and the optimal not considering roads) and mark the sections in the path that the ware would have taken instead of the sections in the actually taken path.
Because of the cost reduction of roads, they help to guide the pathfinding search. And since recipients are probably more likely to be close to than far from roads, this would make the patfhinding more efficient. But this is only a hypothesis that has yet to be tested.Cost can not be negative because cost is travel time and time can not go backwards.
A store has a capacity. It can be infinite or limited.
Example: A storage area has the size of 6 map locations and each map location can store up to 8 wares of the same type. (Mixing wares of different types on a location is not allowed.)
Storage has a boolean setting for accepting each kind of ware. This can be set per storage or per economy (or both).
Example: An economy is configured to store gold and plank but not water.
Example: Wares of type trunk can only be produced at productionsites of type woodcutter's hut. The construction of a woodcutter's hut requires 3 trunks. There are less than 3 trunks in the economy. Thus a production deadlock has occured.
A production deadlock for an important ware type can make the game impossible to finish. This is of course very frustrating for the user. When designing games, it is important to avoid such production deadlocks. But there may be games where the handling of production deadlocks is intentionally a part of the challenge. This is the case when a user gets an initial supply of a ware type and can not produce any more of it. The same applies to games with finite resources. Every ware that can only be produced from finite resources is in fact deadlocked. A production deadlock that makes the game impossible to finish is called a fatal production deadlock.A game designer may include elements intended to break fatal production deadlocks.
Example: There may be a way for players to generate some kind of magic points. Those could be used to cast a spell which will cause a few wares to magically appear. The types of wares that will appear is chosen by the game with a combination of need estimation (based on open requests) and an element of chance. There could also be spells to generate natural resources so that they can be harvested, or there could be spells to remove covers (like glaciers) to uncover natural resources.Petrinet theory, especially reachability testing may be used to investigate the existence of production deadlocking.
It is important to classify ware types by how they can be transformed. Suppose that we have the ware type stone. It is deadlocked because it is produced from the finite resource rock (I know that this is not a very realistic example). So stone has to be used with care. There are several recipient types for stone, including many types of construction sites. One recipient type is the gravel plant, which crushes stones to gravel. Gravel is sometimes needed, and it is necessary to crush stones to get gravel, but it should not be done more than necessary, because that could cause a fatal production deadlock. Now the question becomes how can this be avoided? The obvious way is to use on-demand production, which in this case would mean that stone is only crushed to gravel when gravel is requested. This works well when the transformation is fast, but what happens if suddenly a large quantity of gravel is needed and the crushing takes a lot of time? The delay could give a disadvantage to the player. It may even be enough to loose the game, if the margins are small. To fix this, a reasonable quantity of gravel could be held in supply. Let us call this quantity the target quantity. There would be a per-economy configuration setting for this; a natural number per ware type. The game designer should provide good default values for this setting. If having such a setting is considered to provide too much control of details, it could be done more implicit. There could just be a setting to not transport the gravel to storage . Then the gravel plant would stop the production when the output pile is full. (Of course there may be other reasons to have such no-store options for ware types, like limited storage capacity and limited transport capacity).
When using configured target quantities, there are 2 types of events where the setting is used:This priority can be set per provider or per economy. This priority needs to be considered when a ware becomes available, for example produced at a provider.
In general, every productionsite that uses the commands findobject or findspace should use the observer design pattern. Another use is transport. A transfer could observe the transport zones and exchange places that it has to cross.
The object being watched is called the subject. There are two kinds of notifications that an observer can get:Example: The construction of a sawmill is finished. The sawmil is taken into production so the log storage needs to be filled. 8 logs are requested.
Example: A sawmill takes a log from the internal storage to use it. 1 log is requested to compensate the reduction in the storage.When a ware becomes available, it must be decided if and what should be done with it. If there are requests for that ware type in the economy, one of them has to be chosen. There are some simple approaches for this:
It could be the case that none of the simple approaches is good enough. Perhaps some recipient types are more important than others.
Example: It might be a good idea to give construction sites that are building woodcutter huts highest priority for trunks, since once they are completed, they will help to provide trunks.Then priorities could be used. The set of all recipeints of a ware type could be ordered to form a priority array.
But this may not be good enough either. It could cause starvation, which means that a recipient type with low priority will never get any deliveries. To solve this, the player may have to change the priorities often. To avoid that, it may be a good idea to let some recipient types have equal priority, and distribute wares among them according to some other rule.So the priority array should contain sets of recipient types. All recipient types in the first set in the array have highest priority, and so on.
But how should wares be distributed among the recipient types with the same priority? One of the simple approaches above may be used, or it could be done based on weights. Suppose that weight-based distribution is wanted. Then each recipient type should get a weight. Suppose that we want to store other data than a weight in the priority data structure. Thus we create a record with the weight and the other data. Each recipient type should be associated with such a record. This is easiest done with a map data structure.Each recipient type that can receive the ware type in question must be in exactly 1 recipient record in the list.
The special recipient type storage must also be in the list. If it is, wares of the type in question will be stored if no recipeint with higher priority than the storage exists. If a recipient type is below the storage in the list, the wares of the type in question will be stored instead of delivered to the recipient with low priority. And the recipient will not get any wares from storage.There is also a special recipient type called do-not-transport. If there are no requests with higher priority than do-not-transport, the ware will not be transported. Thus recipient types with lower priority than do-not-transport will not get any deliveries. This special recipient type is useful for turning off deliveries to certain recipient types. It is especially useful for turning off deliveries to storage, for example to avoid filling storage or transport capacity with wares of an abundant type.
Both special recipient types, storage and do-not-transport, must be alone in their priority groups.Example:
(The sourcecode of the image is here.)
An economy's priorities for the ware type trunk. When a trunk becomes available it is handled like this:When economies are merged, the settings the new economy will inherit the priority settings from one of the old economies. The question is which of them. This can be chosen as the one
When an economy is split, all new economies inherit the priority settings from the old economy.