130 Widgets

Semi-random thoughts and tales of tinkering

Lesson 6: A World of Blocks

Minecraft's world is made of blocks arranged in a grid. In this lesson, you'll learn about arrays — one of the most powerful tools in programming — and use them to build a real block world! 🧱

Arrays: Lists of Things

An array is an ordered list. Instead of creating separate variables for each item, you store them all in one place:

// Without arrays (messy!):
let block0 = 'grass';
let block1 = 'dirt';
let block2 = 'stone';

// With an array (clean!):
let blocks = ['grass', 'dirt', 'stone'];

// Access items by their position (called "index")
// IMPORTANT: counting starts at 0, not 1!
blocks[0]  // → 'grass'  (first item)
blocks[1]  // → 'dirt'   (second item)
blocks[2]  // → 'stone'  (third item)
Think of an array like a row of lockers. Each locker has a number (starting from 0), and you can put something inside each one. You can check any locker by its number.

2D Arrays: A Grid

A Minecraft world is a grid — it has rows and columns. We can make a grid by putting arrays inside an array! This is called a 2D array (two-dimensional):

// Each number represents a block type:
// 0 = air (empty), 1 = grass, 2 = dirt, 3 = stone

const world = [
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  // Row 0 (sky)
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  // Row 1
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  // Row 2
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  // Row 3
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  // Row 4
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  // Row 5
  [1, 1, 1, 0, 0, 1, 1, 1, 1, 1],  // Row 6 (grass surface)
  [2, 2, 2, 0, 0, 2, 2, 2, 2, 2],  // Row 7 (dirt)
  [3, 3, 3, 3, 3, 3, 3, 3, 3, 3],  // Row 8 (stone)
  [3, 3, 3, 3, 3, 3, 3, 3, 3, 3],  // Row 9 (stone)
];

// Access a specific block:
world[6][0]  // → 1 (grass) — row 6, column 0
world[0][5]  // → 0 (air) — row 0, column 5

📊 Reading the Grid

To get a block at position (column, row), use: world[row][column]

Think of it as: which row first, then which column in that row.

Block Types

We use numbers to represent different block types. Here's our block type chart:

NumberBlockColor
0Air (empty)Nothing — we skip it
1Grass#4a8f29
2Dirt#8b6914
3Stone#808080
4Wood#6b4226
5Leaves#2d7a2d

Drawing the World with Loops

We use a for loop inside another for loop to go through every block in the grid. The outer loop goes through each row, and the inner loop goes through each column:

const BLOCK_SIZE = 32;

// Colors for each block type
const COLORS = ['', '#4a8f29', '#8b6914', '#808080', '#6b4226', '#2d7a2d'];

function drawWorld() {
  for (let row = 0; row < world.length; row++) {
    for (let col = 0; col < world[row].length; col++) {
      const block = world[row][col];

      if (block === 0) continue;  // Skip air blocks

      // Calculate pixel position
      const x = col * BLOCK_SIZE;
      const y = row * BLOCK_SIZE;

      // Draw the block
      ctx.fillStyle = COLORS[block];
      ctx.fillRect(x, y, BLOCK_SIZE, BLOCK_SIZE);

      // Draw outline
      ctx.strokeStyle = 'rgba(0,0,0,0.2)';
      ctx.strokeRect(x, y, BLOCK_SIZE, BLOCK_SIZE);
    }
  }
}

🔁 Nested Loops

A for loop repeats code a certain number of times. When we put one loop inside another, it's called a nested loop.

The outer loop (rows) runs 10 times. For each of those 10 runs, the inner loop (columns) runs 10 times. So the block-drawing code runs 10 × 10 = 100 times! That's how we draw the entire grid.

continue means "skip this one and move to the next" — we use it to skip air blocks.

See It In Action!

Here's a world built from a 2D array. Notice the gap in the middle — those are air blocks!

A block world created from a 2D array! Each colored square is one block.

Helper Functions: getBlock and setBlock

To make our code cleaner, let's create two helper functions:

function getBlock(col, row) {
  // Return air if outside the world boundaries
  if (col < 0 || col >= WORLD_WIDTH) return 0;
  if (row < 0 || row >= WORLD_HEIGHT) return 0;
  return world[row][col];
}

function setBlock(col, row, blockType) {
  if (col < 0 || col >= WORLD_WIDTH) return;
  if (row < 0 || row >= WORLD_HEIGHT) return;
  world[row][col] = blockType;
}
🛡️ Boundary Checking: We check if the position is inside the world before reading or writing. Without this check, trying to access world[-1][5] would crash our game! Always protect your code from going out of bounds.

🏆 Challenges — Try These!

  • Edit the world array to make a different landscape — try making hills!
  • Add a new block type (number 4 = wood, 5 = leaves) and add a tree
  • Make the world bigger — add more rows and columns
  • Try setBlock(5, 3, 1) to place a grass block in the air — what does it look like?
  • Can you make a cave by putting 0s (air) inside the stone area?

📝 What We Learned

  • Arrays are ordered lists: [item0, item1, item2]
  • 2D arrays are arrays of arrays — perfect for grids!
  • Array indexes start at 0, not 1
  • For loops repeat code a set number of times
  • Nested loops let us visit every cell in a grid
  • We use numbers to represent block types (0 = air, 1 = grass, etc.)