Rule based placement
This approach will probably need revising once you start having more different building types and rules to their placement, but the task is assumed to be exactly as specified and should be able to expand somewhat as needed:
List of fixed building types as input.
Each building type comes with a set of rules for placement.
Buildings are laid out on a regular grid.
I will now add a fourth constraint that is not explicitly specified:
- Buildings are placed in a specific order.
This is needed because some buildings depend on other buildings to be placed first.
In your sample, churches should be placed first, then the houses, then the factories.
So we adjust your array to use the new order - not to complicate with yet another data point to store:
{churches: 3, houses: 20, factories: 7}
Then, we iterate over this array.
For each element, we do the following:
Pick a location
Iterate over all possible locations - either one by one, down and across (a standard x,y
loop), or by picking a random unoccupied location - this is probably the better choice to avoid bias, although there are probably even better methods that I am not aware of.
Rule function
Once a location is picked, you run a rule function that is unique per building type. This function either returns success or failure. On success, the building is placed and the building count is decreased by one. If the building count becomes 0 or less, move onto next building type. On failure, new location is picked and the rule function runs again - see bottom regarding infinite loops.
For a church, for example, the rule function is a random chance that becomes bigger towards the centre and smaller towards the edges - this will probably work better with the random location method, otherwise your chance would have to be highly nonlinear - nearly 1 in the centre, nearly 0 at the edges but maybe only 0.2 or something halfway through. Even then, there will likely be a bias towards whichever corner is your 0,0
on the map.
It may also be useful to store an additional grid that has a kind of a distance map to each placed church - initialised to a very large value like 9999, and once a church is placed, its corresponding grid position is set to 0, its neighbours to 1 and so on - for performance reasons maybe only going to 5 or 6 which should be enough anyway. Make sure never to overwrite a lower value with the higher one - after all it's the distance to the closest, not to this specific church that is being used.
The rule function then would make use of this value to make sure churches don't get placed too close to each other.
For a house the rule function would determine chance based on a church being nearby - nearly 1 next to one, lesser far away. It would also benefit from the "church proximity map" we created earlier.
Finally, factories can be placed at the edges with a rule function that only returns success if the location is an edge.
Further thoughts and improvements
It may be a good idea to keep some kind of a "failure counter" to prevent infinite loops - after every failure of a rule function, it is incremented, and if it passes a high value like 1000 (this will probably need tuning and scaling based on map size), we give up on placing these buildings: the building counter is exhausted and we move on.
Some way of checking value validity - make sure total building count is not bigger than the map can fit - the above counter would prevent the worst but it's still a good idea to keep some value sanity.