Firstly I technically have solved this problem, but I’m not keen on my solution and want to start over.
Basically I have a grid map, each cell of which has four variables, Heat, Humidity, Fertility and Radiation. I want to select a random plant to grow on each cell based on those four factors.
My solution was to create a plant class, containing each plants ideal conditions, then I added all the classes to a list.
I run a for statement the length of the list, inside the statement I compare each of the cells variables to each of the plants preferences in turn. Depending on how close the two number are, the plants name gets added to a new separate list a set number of times.
This makes a long list full of plant names, the closer the the ideal conditions the more times the name appears on the list.
I then generate a random number between zero and the length of this new list. Then pull name at that index from the list and that is the plant the grows there.
I does work, but it feels like a convoluted solution.
I am curious how more experienced coders would have tackled this problem.
Compare those 4 values and create a single combined weight/bias value based upon how close they are to the ideal conditions for each plant, maybe make it non-linear. This gives you a temporary list of single floats, one for each plant where a larger value means closer to ideal and a smaller value means further away from ideal.
You can then simply perform a weighted random choice from the list to choose a plant with the most suited more likely or just iterate it and choose the closest one always. The index of the item in the list should be the same as the index of the plant in question because you formed the list by iterating the list of plants so it’s in the same order.
Thought it interesting to throw together some code to demonstrate both weighted random and closest match, maybe it’ll help you and potentially others with a similar question. If not, it was fun throwing it together.
NOTE: Might have bugs.
using System;
using System.Collections.Generic;
using UnityEngine;
public class WeightedTest : MonoBehaviour
{
[Serializable]
public struct Conditions
{
[Range(0f, 1f)] public float Heat;
[Range(0f, 1f)] public float Humidity;
[Range(0f, 1f)] public float Fertility;
[Range(0f, 1f)] public float Radiation;
private const float WeightScale = 2f;
public float GetConditionsWeight(ref Conditions other)
{
return
Mathf.Pow(
(4f -
(Mathf.Abs(Heat - other.Heat) +
Mathf.Abs(Humidity - other.Humidity) +
Mathf.Abs(Fertility - other.Fertility) +
Mathf.Abs(Radiation - other.Radiation))) / 4f,
WeightScale);
}
}
[Serializable]
public class Plant
{
public string Name;
public Conditions IdealConditions;
}
public Conditions CurrentConditions;
public List<Plant> Plants = new();
private readonly List<float> m_Weights = new();
private void OnGUI()
{
if (GUI.Button(new Rect(10f, 10f, 150f, 100f), "Choose Plant"))
Debug.Log(ChoosePlant().Name);
}
private Plant ChoosePlant()
{
var totalSum = 0f;
m_Weights.Clear();
foreach (var plant in Plants)
{
var weight = CurrentConditions.GetConditionsWeight(ref plant.IdealConditions);
m_Weights.Add(weight);
totalSum += weight;
}
// Set to false to have weighted random.
#if true
// Largest weight.
var bestWeight = float.MinValue;
var bestPlantIndex = 0;
for (var i = 0; i < m_Weights.Count; ++i)
{
var weight = m_Weights[i];
if (weight <= bestWeight)
continue;
bestWeight = weight;
bestPlantIndex = i;
}
return Plants[bestPlantIndex];
#else
// Weighted random.
var value = UnityEngine.Random.Range(0f, totalSum);
for (var i = 0; i < m_Weights.Count; ++i)
{
var weight = m_Weights[i];
if (value <= weight)
return Plants[i];
value -= weight;
}
throw new InvalidOperationException("We should always choose a plant!");
#endif
}
}
I would also suggest to check out Gaussian distribution, first calculating weights for the grid as suggested but then with Gaussian you can get more natural distribution because it is biased distribution so it will have peaks in ideal conditions and falloff. If it feels to uniform you could after apply some general randomness to calculated distribution.
Sorry it took me so long to spot this post. I’m on my third prototype now, and switching up from a simple square board to a hex grid that expands outwards from a single central point. That was fun to get my head around,
Now I just need to remake my plants spawner to match the new grid system and I can start comparing them again.
As soon as I have some rudimentary UI I’ll post a semi playable prototype.
I’d say it will take lots of play-testing to make it fun. I’d guess the player is going to assume it also depends on distance, as in plants try to grow in adjacent squares, or at least nearby as seeds drift, so plant spawning will have to take that into account. I also wonder about situations where no plant really wants to grow – there should be a possible “no plant” result. I also wonder about them being close in rating. As a player I’d expect that I’ll get mostly the “best” plant, even if it’s only a little better. In other words, if plantA is rated 7.8 for a space and plantB is 7.5, I’ll see 75% A’s. But if I can adjust conditions to flip that I’ll suddenly get 75% B’s. That makes it worthwhile and obvious when the player solves it.
Basically, I think in play-testing it’s going to look “too random”, and you’re also going to see fun patterns that aren’t in your programming but can be added.
You hit the nail on the head regarding the random feeling during my first basic play testing. Why I revisited my plant generation first.
To answer a couple of those questions. There is a state for dead ground, when no plant wants to grow, in fact everything starts off that way. Small simple plants grow and die leading to larger more complex one, Growing fertility and reducing radiation.
Plants are not constant, there is a random element to what grows where, but they can thrive or remain small and stunted and eventually die. Also all plants will have their own life span, varying massively according to the specific plant.
I have also considered plants being more likely to spread to adjacent cells, and not only in a productive way, having plants that are at first appear helpful but quickly become rampant.