If you want pseudo-random or random content you can use a sequence for the creation.
You can use creators as a framework that offers great prototyping flexibility.
You can define a source interface for values that you use for decisions:
public interface StreamSource {
int consumeInt();
long consumeLong();
char consumeChar();
byte consumeByte();
float consumeFloat();
double consumeDouble();
}
Then use another interface that creates objects using the value source:
public interface ObjectCreator<OBJ> {
OBJ create(StreamSource streamSource);
}
Now you can write data containers (e.g. the classes Wave and Enemy) and creator implementations that create actual content.
There are dependencies between Waves and Enemies ... a Wave creator will use an Enemy creator to create enemies that are right for the level of the wave instance, for example.
The way I accomplish that is with Context interfaces (obviously this is just to show the basic idea ... no null checks, do default behaviour etc.):
public interface EnemyContext {
int getMinStrength();
int getMaxStrength();
}
public class WaveCreator implements ObjectCreator<Wave>, EnemyContext {
private final EnemyCreator enemyCreator = new EnemyCreator();
private WaveContext context = null;
public WaveCreator() {
enemyCreator.setContext(this);
}
public void setContext(WaveContext context) {
this.context = context;
}
public int getMinStrength() {
return getMinStrengthByWaveLevel(context.getWaveLevel());
}
public int getMaxStrength() {
return getMaxStrengthByWaveLevel(context.getWaveLevel());
}
public int getMinStrengthByWaveLevel(int waveLevel) {.
...
}
...
public Wave create(StreamSource streamSource) {
Wave wave = new Wave();
int numberOfEnemies = 10;
numberOfEnemies += streamSource.consumeChar() % 10;
for (int i=0; i<numberOfEnemies; i++) {
wave.registerEnemy(enemyCreator.create(streamSource));
}
return wave;
}
}
The game itself or a module would register itself as the wave context and increment the level. That change would create different waves.
Notice how the context influences the object creation in a controllable way while the streamSource is responsible for (pseudo) random decisions.
This is Java code, but I hope the concept translates well ... you have a lot of freedom in the create functions so you can play with different approaches and add complexity pretty easily.
I can attach a Java implementation of a random stream source and a fixed stream source (fibonacci sequence - for reproduceable content with a random character) if you are interested ... if you do not see how it all would come together feel free to ask ...
Guess for what you have in mind the streamSource is irrelevant, though.
You would just ask the context for the current wave level and play with implementations of create until you find a strategy that produces waves that you are happy with.
Rough fibonacci implementation without a random character:
public Wave create(StreamSource streamSource) {
Wave wave = new Wave();
for (int tierNumber=1; tierNumber<=5; tierNumber++) {
setCurrentTier(tierNumber);
int numberOfEnemies = getNumberOfEnemies();
for (int i=0; i<numberOfEnemies; i++) {
wave.registerEnemy(enemyCreator.create(streamSource));
}
}
return wave;
}
private int getNumberOfEnemies() {
return (3 + getFibonacci(context.getWaveLevel())) / getCurrentTier();
}
private int getFibonacci(int n) {
if (n == 0 || n == 1) return n;
else
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Obviously in this case the enemy context must contain the method getCurrentTier(), so that the EnemyCreator knows what tier it has to create an enemy for.