130 Widgets

Semi-random thoughts and tales of tinkering

Lesson 11: World Generation

Until now, we've been hand-building our worlds. But real Minecraft generates entire worlds automatically using math! In this lesson, you'll learn about noise functions — the magical math that creates realistic terrain. 🌍✨

Random vs. Smooth Random

JavaScript has Math.random(), which gives a random number between 0 and 1. But if we used pure randomness for terrain, we'd get something ugly — blocks jumping wildly up and down with no smooth hills.

Pure random terrain — way too chaotic! 😵

What we want is smooth randomness — random values that change gradually, creating natural-looking hills and valleys. This is called noise.

Smooth noise terrain — now THAT looks like Minecraft! 🏔️

How Noise Works (Simplified)

A noise function takes a position and returns a smooth random value between -1 and 1. Nearby positions get similar values, creating smooth transitions:

// Simple noise using sin waves at different scales
function simpleNoise(x, seed) {
  return Math.sin(x * 0.02 + seed) * 0.5
       + Math.sin(x * 0.05 + seed * 2) * 0.3
       + Math.sin(x * 0.1 + seed * 3) * 0.2;
}
Imagine ocean waves. Big waves create the overall shape (hills), medium waves add some bumps, and tiny ripples add fine detail. Noise works the same way — we add waves at different sizes together!

Seeds: Repeatable Randomness

A seed is a starting number that determines the entire world. The same seed always creates the same world — that's how Minecraft lets you share world seeds with friends!

const SEED = Math.floor(Math.random() * 999999);

// This seed will always create the same terrain:
// Seed 12345 → always the same hills, caves, trees
// Seed 67890 → completely different, but also always the same

Building Terrain with Noise

We use noise to calculate a height map — the surface height at each column:

function generateWorld() {
  for (let col = 0; col < WORLD_WIDTH; col++) {
    // Use noise to get surface height
    const surfaceRow = Math.floor(
      60 + simpleNoise(col, SEED) * 15
    );

    for (let row = 0; row < WORLD_HEIGHT; row++) {
      if (row < surfaceRow) {
        setBlock(col, row, 0);         // Air above surface
      } else if (row === surfaceRow) {
        setBlock(col, row, 1);         // Grass at surface
      } else if (row < surfaceRow + 4) {
        setBlock(col, row, 2);         // Dirt layer
      } else {
        setBlock(col, row, 3);         // Stone deep down
      }
    }
  }
}

Adding Trees

// After generating terrain, add trees
for (let col = 3; col < WORLD_WIDTH - 3; col++) {
  // Use noise to decide where trees go (not every column!)
  if (simpleNoise(col * 5, SEED + 100) > 0.3) {
    const surfaceRow = findSurface(col);  // Find the grass block
    if (getBlock(col, surfaceRow) === 1) {  // Only on grass
      // Trunk (3-5 blocks tall)
      const height = 3 + Math.floor(Math.abs(simpleNoise(col * 7, SEED)) * 3);
      for (let h = 1; h <= height; h++) {
        setBlock(col, surfaceRow - h, 4);  // Wood
      }
      // Leaves canopy
      for (let lx = -2; lx <= 2; lx++) {
        for (let ly = -2; ly <= 0; ly++) {
          if (Math.abs(lx) + Math.abs(ly) <= 2) {
            setBlock(col + lx, surfaceRow - height + ly, 5);  // Leaves
          }
        }
      }
      col += 5;  // Space between trees
    }
  }
}

Adding Ores

Ores appear deep underground. Rarer ores appear deeper:

// Coal: appears below surface + 5
// Iron: appears below surface + 15
// Diamond: appears below surface + 30

if (row > surfaceRow + 5 && noise(col * 0.5 + row * 0.7) > 0.7) {
  setBlock(col, row, COAL_ORE);
}
if (row > surfaceRow + 15 && noise(col * 0.5 + row * 0.7) > 0.85) {
  setBlock(col, row, IRON_ORE);
}
if (row > surfaceRow + 30 && noise(col * 0.5 + row * 0.7) > 1.0) {
  setBlock(col, row, DIAMOND_ORE);
}

Interactive Demo — Generate Worlds!

Click the button to generate a new world with a random seed:

Seed:

🧮 Why Math Makes Good Worlds

The key insight is layering:

  1. Big waves (low frequency) create the overall shape — major hills and valleys
  2. Medium waves add smaller bumps and features
  3. Small waves (high frequency) add fine detail and roughness

Each layer is called an octave. Real Minecraft uses 4-8 octaves of noise!

🏆 Challenges — Try These!

  • Generate multiple worlds and compare them — notice how each seed creates different terrain
  • Change the * 15 multiplier to make terrain flatter or more extreme
  • Add sand blocks near the surface in low-lying areas (simulate beaches!)
  • Make caves by checking if noise value is above a threshold underground
  • Try using the same seed twice — do you get the same world? (You should!)

📝 What We Learned

  • Noise is smooth randomness — nearby inputs give similar outputs
  • We create noise by adding sine waves at different scales
  • A seed determines the world — same seed = same world
  • Height maps define where the surface is at each column
  • Ores, trees, and caves are placed using noise and rules
  • Layering octaves creates realistic terrain