The trick is to have an a algorithm that produce something that looks good, while depending on random value from a PRNG (pseudo-random number generator, like srand and rand). Then, just seed the generator with the level number and be sure to not use your random number generator for anything else than the terrain.
Also, be deterministic: don't depend on timing or user input and you'll be fine.
For example, you could separate your terrain into n sections, pick a random point x,y, making sure each is in the i'th of the n section. Then linearly interpolate between each 2 consecutive points in between, in k sub points. Offset each such sub-point by some limited distance.
Then, in a loop, to smooth, randomly pick 3 consecutive points, and move the middle one a bit closer to the center between the two others. This will remove any hard edges.
By adjusting the various settings, number of sections, number of sub point, offset percentage, etc..., you'll get different looks.
From there, you need to tweak for your use-case. Moon-lander ? Add flat spots. Star-Wars, add cave for huge worms, you get the picture...
Also remember that if generating the whole terrain is expensive, you can do it in parts. The level seed can be used to generated sub-seed for each level section. So, in level 4, you use seed 4 to get, say 100 sub-seed. In each 100 parts of level 4, you use one seed to generate the terrain locally. You can nest as deep as you want.
See code and sample results below.
using namespace std;
const float WIDTH = 10000;
const float HEIGHT = 1000;
const float SHIFT = 200;
const int SECTIONS = 40;
const int SUB = 5;
const int SMOOTH_STEPS = 4000;
int levelNumber = 42; //for example
xPos.resize(SECTIONS * SUB);
yPos.resize(SECTIONS * SUB);
// random points, in a semi-regular fashion
for(size_t i = 0; i < SECTIONS; i++)
xPos[i * SUB] = (rand() / (float)RAND_MAX) * (WIDTH / SECTIONS) + i * (WIDTH / SECTIONS);
yPos[i * SUB] = (rand() / (float)RAND_MAX) * (HEIGHT);
// add points in between, letting them deviate only partly
for(size_t i = 0; i < SECTIONS - 1; i++)
for(size_t j = 1; j < SUB; j++)
float x = xPos[i * SUB] * (1 - (float)j/SUB) + xPos[(i + 1) * SUB] * ((float)j/SUB); // interpolate
float y = yPos[i * SUB] * (1 - (float)j/SUB) + yPos[(i + 1) * SUB] * ((float)j/SUB);
x += (rand() / (float)RAND_MAX) * SHIFT - SHIFT / 2;
y += (rand() / (float)RAND_MAX) * SHIFT - SHIFT / 2;
xPos[i * SUB + j] = x;
yPos[i * SUB + j] = y;
// make smoother
for(size_t i = 0; i < SMOOTH_STEPS; i++)
// pick a point not at an extremity
size_t index = rand() % (SECTIONS * SUB - 2) + 1;
float x0 = xPos[index - 1];
float y0 = yPos[index - 1];
float x1 = xPos[index];
float y1 = yPos[index];
float x2 = xPos[index + 1];
float y2 = yPos[index + 1];
xPos[index] = x0 * 0.05 + x2 * 0.05 + x1 * 0.90;
yPos[index] = y0 * 0.05 + y2 * 0.05 + y1 * 0.90;
for(size_t i = 0 ; i < SECTIONS * SUB; i++)
cout << xPos[i] << " " << yPos[i] << endl;