Semi-random thoughts and tales of tinkering
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! 🧱
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)
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
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.
We use numbers to represent different block types. Here's our block type chart:
| Number | Block | Color |
|---|---|---|
| 0 | Air (empty) | Nothing — we skip it |
| 1 | Grass | #4a8f29 |
| 2 | Dirt | #8b6914 |
| 3 | Stone | #808080 |
| 4 | Wood | #6b4226 |
| 5 | Leaves | #2d7a2d |
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);
}
}
}
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.
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.
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;
}
world[-1][5]
would crash our game! Always protect your code from going out of bounds.
world array to make a different landscape — try making hills!setBlock(5, 3, 1) to place a grass block in the air — what does it look like?[item0, item1, item2]