Back to Minecraft Lessons
Lesson 5

Patterns, Functions & the Castle

Use the loop counter as a coordinate, add math for round shapes, give your functions parameters — and combine them into a whole castle.

ESSENTIAL QUESTION

How can the loop counter become part of what you build, and how can small functions combine into something as complex as a castle?

By the end of this lesson, I will...

  • Use the iteration variable i (and j in nested loops) inside the loop body to position blocks.
  • Use modulo (%) to make alternating patterns like checker boards.
  • Use abs(), max(), and the Pythagorean theorem to build pyramids and round structures.
  • Write a function that takes parameters — so you can call it at any position and size.
  • Combine several parameterized functions into one bigger build (a castle).
Every Lesson

Start Here First — Every Single Time

Every Minecraft lesson begins with a brand-new world. Do not skip this. Open Lesson 0, follow the setup steps, then come back here and continue with Step 1.

Open Lesson 0 — World Setup

What you already know

Lesson 5 is the final lesson of the unit — it pulls together everything from Lessons 1–4 and pushes it one step further.

From Lesson 2for i in range(N): repeats N times.
From Lesson 3The Builder, relative coords (~), trace_path, mark/line.
From Lesson 4blocks.fill(), opposite corners, mobs.spawn(), randpos().
New todayUse i inside the loop. Use math for round shapes. Give functions parameters. Compose them into one big build.

STEP 1 Warm-up: The Loop Variable Was There All Along

Look back at every for loop you've ever written in Lessons 2, 3, and 4. They all have a name right after for — usually i, side, or step. That name is called the iteration variable. It counts up by 1 every time the loop repeats.

Here's the catch: you've almost never used it. Look at this Lesson 2 code:

for i in range(5):
    agent.move(FORWARD, 1)
    agent.place(DOWN)

The variable i took the values 0, 1, 2, 3, 4 — and we ignored every single one of them. The loop just acted as a "do this 5 times" counter.

Today's big idea: the iteration variable i can become part of what you build. If i goes 0, 1, 2, ..., 9, you can use it as a coordinate, a height, a color index, or a piece of math. Every iteration looks different.

Today's vocabulary

Iteration variableThe i in for i in range(N). Counts 0, 1, ..., N-1.
Modulo (%)The remainder after division. 7 % 2 = 1. Turns numbers into repeating patterns.
Distance from centerHow far a block is from a chosen point — measured with abs() or the Pythagorean theorem.
ParameterA value a function accepts when it's called. def tower(cx, cz, h): takes 3 parameters.
CompositionCalling functions from inside other functions to build something bigger.

STEP 2 Open MakeCode (Python Only)

You should already be in your fresh flat world from Lesson 0. If not, go back and complete Lesson 0 first.

  1. In Minecraft, press C to open Code Builder.
  2. Choose MakeCode.
  3. Click New Project and name it Lesson 5 - Castle.
  4. Click Code options, then select Python Only, then click Create.

STEP 3 Use i in a Position — Build a Staircase

The simplest way to use i is to put it directly inside pos(...). Each iteration places a block at a different spot.

def stairs():
    for i in range(10):
        blocks.fill(STONE,
                    pos(i, 0, 0),
                    pos(i, i, 0),
                    FillOperation.REPLACE)
player.on_chat("stairs", stairs)

What's happening

  • Each pass through the loop has a different value of i.
  • pos(i, 0, 0) — the bottom of the column. The i means the column slides one block east each iteration.
  • pos(i, i, 0) — the top of the column. Because Y is i, the column gets taller as the loop goes.
  • Result: 10 columns side-by-side. The first is 1 block tall, the last is 10 blocks tall — a staircase.
Predict: how many total stone blocks does this program place?

The columns are 1, 2, 3, ..., 10 blocks tall. Add them up: 1+2+3+…+10 = 55 blocks. The same loop placed dramatically different numbers of blocks per iteration — because i changed each time.

Step back first. Press T, type /tp ~ ~ ~10, and press Enter to jump 10 blocks south. The stairs will rise to the east in front of you. Then chat stairs.

STEP 4 Two Loops, Two Variables: i AND j

Nested loops were introduced in Lesson 2. Until now you ignored both variables. Today both of them do something.

def steps():
    for i in range(6):
        for j in range(6):
            height = i + j + 1
            blocks.fill(STONE_BRICKS,
                        pos(i * 2, 0, j * 2),
                        pos(i * 2, height, j * 2),
                        FillOperation.REPLACE)
player.on_chat("steps", steps)

How the variables work together

  • i controls one axis (east/west) — i * 2 spaces the pillars 2 blocks apart.
  • j controls another axis (north/south) — same spacing.
  • height = i + j + 1 — the pillar at i=0, j=0 is 1 block tall. The pillar at i=5, j=5 is 11 blocks tall.
  • Result: a 6×6 field of pillars rising diagonally from one corner to the opposite. It looks like a stadium — or a video-game victory podium.
Predict: which pillar is tallest? Which is shortest?

Shortest: i=0, j=00 + 0 + 1 = 1 block.

Tallest: i=5, j=55 + 5 + 1 = 11 blocks. The pillars rise diagonally because the height depends on both variables.

Step back first. T, /tp ~ ~ ~10, Enter. Then chat steps.

STEP 5 Patterns with Modulo %

The % operator is called modulo. It gives the remainder after dividing.

ExpressionResultWhat it's useful for
0 % 20Even numbers give 0.
1 % 21Odd numbers give 1.
5 % 21Still odd → still 1.
(i + j) % 20 or 1Toggles every block — checkerboard.
i % 40, 1, 2, 3, 0, 1, …Cycles through 4 values — stripes of 4 colors.

Use modulo to pick a color from a list:

def checker():
    colors = [WOOL, OBSIDIAN, STONE, REDSTONE_BLOCK]
    for i in range(16):
        for j in range(16):
            color = colors[(i % 2) * 2 + (j % 2)]
            blocks.fill(color,
                        pos(i, 0, j),
                        pos(i, 0, j),
                        FillOperation.REPLACE)
player.on_chat("checker", checker)

Reading the color picker

  • colors is a list — an ordered collection of values. Index 0 is WOOL, index 1 is OBSIDIAN, etc.
  • (i % 2) * 2 + (j % 2) — combines the two modulo results into a single number 0–3.
  • That number indexes the list — so each cell gets one of four colors based on whether i and j are even or odd.
Why lists beat nested if/else. Without lists you'd need four if/elif/else branches to choose a color. With a list and modulo arithmetic, the whole choice is one short line. Lists shine when you want to index into a fixed set of options.
Predict: what happens if you change colors to just two entries like [WOOL, OBSIDIAN] and change the index to (i + j) % 2?

You get a classic two-color checkerboard. (i + j) % 2 is 0 when both are even or both are odd, and 1 otherwise — exactly the checker pattern. Try it!

STEP 6 Distance Math: Pyramids and Cones

So far i and j have been straight coordinates. Now let's measure something more interesting: distance from the center of the grid. Two ways to measure distance give two completely different shapes.

Diamond pyramid — abs() and max()

The function abs(x) gives the absolute value — how far x is from zero. The function max(a, b) gives whichever number is bigger.

def pyramid_demo():
    SIZE = 32
    CENTER = SIZE // 2
    for i in range(SIZE):
        for j in range(SIZE):
            height = CENTER - max(abs(i - CENTER), abs(j - CENTER))
            if height > 0:
                blocks.fill(SANDSTONE,
                            pos(i, 0, j),
                            pos(i, height, j),
                            FillOperation.REPLACE)
player.on_chat("pyramid_demo", pyramid_demo)
  • abs(i - CENTER) — how far this column is from center along the X axis.
  • max(abs(i - CENTER), abs(j - CENTER)) — the larger of the two distances. Pillars on the edge of the grid have a big value; pillars near the center have a small value.
  • height = CENTER - max(…)flips it. Center pillars are tall; edge pillars are short or zero.
  • Result: a step pyramid — tallest at the middle, shortest at the edges, with a diamond outline.

Round volcano — the Pythagorean theorem

For a round shape instead of a diamond, measure the real Euclidean distance using the Pythagorean theorem: distance = sqrt(dx² + dz²).

def volcano_demo():
    SIZE = 32
    CENTER = SIZE // 2
    for i in range(SIZE):
        for j in range(SIZE):
            dx = i - CENTER
            dz = j - CENTER
            dist = (dx * dx + dz * dz) ** 0.5
            height = int(CENTER - dist)
            if height > 0:
                blocks.fill(STONE,
                            pos(i, 0, j),
                            pos(i, height, j),
                            FillOperation.REPLACE)
player.on_chat("volcano_demo", volcano_demo)
  • dx * dx + dz * dz — the sum of squares (no built-in square function needed; just multiply).
  • ** 0.5 — raise to the half power, which is the same as square root.
  • int(…) — convert the decimal distance to a whole-number height (you can't place half a block).
  • Result: a smooth round cone. Same idea as the pyramid, but using true distance instead of max-of-two distances gives a circle instead of a diamond.
The same loop builds two completely different shapes. The only difference is how you measure distance. Diamond uses max(abs, abs); circle uses sqrt(dx² + dz²). Pick the math — pick the shape.

Conditional placement — only build inside a circle

Distance math doesn't have to control how tall a column is. It can also decide whether to build at all. Here's a flat round platform: build only where the distance is less than a chosen radius.

def platform():
    SIZE = 32
    CENTER = SIZE // 2
    RADIUS = 12
    for i in range(SIZE):
        for j in range(SIZE):
            dx = i - CENTER
            dz = j - CENTER
            if dx * dx + dz * dz <= RADIUS * RADIUS:
                blocks.place(GOLD_BLOCK, pos(i, 0, j))
player.on_chat("platform", platform)
Skipping the square root. Notice we compare dx*dx + dz*dz against RADIUS*RADIUS — we never actually compute the square root. Squaring both sides of distance < RADIUS gives the same answer, faster. Small optimization, but a real one.
Step back first. Each of these builds is 32 blocks wide. T, /tp ~ ~ ~10, Enter before running each chat command.

STEP 7 Functions with Parameters

Look at the three programs in Step 6. They're almost identical — same SIZE, same CENTER, same outer loop. The only differences are the math inside and the block type. That's a strong hint that the code wants to be one function you can call many different ways.

This is the most important new idea in this lesson: a function can take parameters — values you pass in when you call it.

Before Hard-coded

def round_tower_v1():
    CENTER = 12
    RADIUS = 5
    HEIGHT = 14
    for di in range(-RADIUS, RADIUS + 1):
        for dj in range(-RADIUS, RADIUS + 1):
            d2 = di * di + dj * dj
            if d2 <= RADIUS * RADIUS:
                blocks.fill(STONE_BRICKS,
                  pos(CENTER + di, 0, CENTER + dj),
                  pos(CENTER + di, HEIGHT, CENTER + dj),
                  FillOperation.REPLACE)

Always builds at the same spot, same size. Useful once, then useless.

After Parameterized

def round_tower(cx, cz, radius, height):
    for di in range(-radius, radius + 1):
        for dj in range(-radius, radius + 1):
            d2 = di * di + dj * dj
            if d2 <= radius * radius:
                blocks.fill(STONE_BRICKS,
                  pos(cx + di, 0, cz + dj),
                  pos(cx + di, height, cz + dj),
                  FillOperation.REPLACE)

Works anywhere at any size. Reusable forever.

What changed

  • The constants CENTER, RADIUS, HEIGHT became parameters: cx, cz, radius, height.
  • Inside the loop, pos(CENTER + di, ...) became pos(cx + di, ...). The function uses whatever cx the caller passed.
  • To call it, you supply values: round_tower(20, 20, 4, 10) — tower at (20, 20), radius 4, height 10.

Two towers from one function

Now you can call the same function twice, with different parameters, and get two different towers:

def round_tower(cx, cz, radius, height):
    for di in range(-radius, radius + 1):
        for dj in range(-radius, radius + 1):
            d2 = di * di + dj * dj
            if d2 <= radius * radius:
                blocks.fill(STONE_BRICKS,
                            pos(cx + di, 0, cz + dj),
                            pos(cx + di, height, cz + dj),
                            FillOperation.REPLACE)

def two_towers():
    round_tower(10, 10, 4, 8)    # short, fat
    round_tower(30, 10, 3, 14)   # tall, skinny
player.on_chat("two_towers", two_towers)
One function, many builds. Without parameters, every variation needed its own copy-pasted function. With parameters, one function definition handles every tower in your world — small, big, near, far. This is the most important programming skill in today's lesson.

About the wrapper trick

You may have noticed that player.on_chat(...) expects a function that takes no arguments. But our new round_tower takes four. So we make a tiny wrapper functiontwo_towers above — that takes no arguments, but calls the real function with the values we want. The wrapper is the chat handler; the real work happens in round_tower.

STEP 8 Build a Castle by Composing Functions

Once your functions take a center (cx, cz), something magical happens: you can call several of them at the same center, and they line up automatically. That's how this castle is built — four small functions, one shared center.

Here are the four helpers. Read them carefully — each one is just a familiar pattern with parameters.

Helper 1 — the moat (water ring)

def moat(cx, cz, inner, outer):
    for di in range(-outer, outer + 1):
        for dj in range(-outer, outer + 1):
            d2 = di * di + dj * dj
            if inner * inner <= d2 <= outer * outer:
                blocks.fill(WATER,
                            pos(cx + di, -1, cz + dj),
                            pos(cx + di, -1, cz + dj),
                            FillOperation.REPLACE)

Two circle tests at once: inside the outer circle AND outside the inner one. The water sits 1 block below the surface.

Helper 2 — the castle wall with crenellations

def castle_wall(cx, cz, half_size, height):
    for di in range(-half_size, half_size + 1):
        for dj in range(-half_size, half_size + 1):
            on_edge = (di == -half_size or di == half_size or
                       dj == -half_size or dj == half_size)
            if on_edge:
                if (di + dj) % 2 == 0:
                    h = height
                else:
                    h = height - 2
                blocks.fill(STONE_BRICKS,
                            pos(cx + di, 0, cz + dj),
                            pos(cx + di, h, cz + dj),
                            FillOperation.REPLACE)

Only builds on the perimeter (the on_edge test). Modulo (% 2) alternates between full-height pillars and shorter ones — the medieval pattern called crenellations.

Helper 3 — the hollow round tower (keep)

def round_tower(cx, cz, radius, height, material, hollow):
    inner_r = radius - 1
    for di in range(-radius, radius + 1):
        for dj in range(-radius, radius + 1):
            d2 = di * di + dj * dj
            in_outer = d2 <= radius * radius
            on_ring  = d2 >= inner_r * inner_r
            if in_outer and (not hollow or on_ring):
                blocks.fill(material,
                            pos(cx + di, 0, cz + dj),
                            pos(cx + di, height, cz + dj),
                            FillOperation.REPLACE)

Two circle tests again: inside the outer circle and (if hollow) outside the inner one. That leaves only the outer ring — a tower you can walk inside.

Helper 4 — the dome roof

def dome(cx, cz, radius, base_y, material):
    for di in range(-radius, radius + 1):
        for dj in range(-radius, radius + 1):
            d2 = di * di + dj * dj
            if d2 <= radius * radius:
                height = int((radius * radius - d2) ** 0.5)
                blocks.fill(material,
                            pos(cx + di, base_y, cz + dj),
                            pos(cx + di, base_y + height, cz + dj),
                            FillOperation.REPLACE)

The Pythagorean theorem in 3D — the height of each column is what's left after subtracting dx² + dz² from radius². Building from base_y up gives a hemisphere sitting at any height. We'll start it at the top of the keep.

Putting it all together: assemble the castle

Now look at the payoff. The whole castle is just four function calls — all at the same (CX, CZ).

def castle():
    CX = 30
    CZ = 0
    # Moat - 4-block-wide water ring, just outside the wall corners
    moat(CX, CZ, 18, 22)
    # Outer wall - 24x24 square, 6 tall, crenellated
    castle_wall(CX, CZ, 12, 6)
    # Central keep - hollow stone tower, 14 tall
    round_tower(CX, CZ, 5, 14, STONE_BRICKS, True)
    # Quartz dome roof sitting on top of the keep
    dome(CX, CZ, 5, 14, WOOL)
    player.say("CASTLE COMPLETE :)")
player.on_chat("castle", castle)
This is composition. Each helper is small and does one thing. The castle function calls four of them at one shared center, and a coordinated build appears. To move the entire castle to a different spot, change only CX and CZ. To make it bigger, change the radii together. The "castle" is just a recipe of helper calls.
Predict: what changes if you swap the moat call for moat(CX, CZ, 22, 26)?

The moat moves further out from the castle. The inner edge goes from radius 18 to radius 22, and the outer edge from 22 to 26. There's now a 4-block strip of dry ground between the wall and the water — like a courtyard outside the walls.

Step back first. The castle is centered 30 blocks east of you. T, /tp ~ ~ ~10, Enter, then chat castle. Walk around it. Climb the wall. Stand inside the keep and look up at the dome.

CHALLENGE Compose Your Own Build

Make an original program that uses at least two parameterized helpers from today and calls them together at a shared center. Pick an option below or invent your own.

Option A — Twin-Tower Fortress

Build a castle_wall with a round_tower at each of the four corners. Each corner tower should be taller than the walls. Bonus: top each tower with a dome.

Option B — Volcano Island

Use moat as the ocean around your "island," then put a volcano in the middle. Bonus: add the lava crater from the advanced examples file.

Option C — Temple Complex

A central pyramid, four round_tower obelisks around it, and a circular path or moat surrounding the whole thing. Vary the sandstone, gold, and stone materials.

Option D — Custom Castle

Start from the castle in Step 8 and change it: bigger walls, different dome material, multiple keeps, a second concentric wall, mobs spawned inside… whatever you can dream up.

Minimum requirements

  • Your program starts with a chat command (player.on_chat).
  • You define at least two helper functions with parameters.
  • You call each helper at least once. At least two helpers share a center.
  • At least one helper uses i or j as a coordinate or in a height calculation.
  • At least one helper uses modulo (%) or distance math (abs/max or Pythagorean).
  • Your build covers at least a 20×20 area or is 10+ blocks tall.

Stretch goals

  • Use a list indexed by modulo to vary block materials across the build.
  • Use a for loop in your chat command to call a helper multiple times at evenly spaced positions.
  • Add mobs.spawn() inside your structure (Lesson 4 callback).
  • Combine three or more helpers at one center.
  • Add # comments above each helper explaining what it does and what its parameters mean.
Don't write code from scratch — remix. Copy the helpers from Step 8 into your program. Tweak the numbers. Add a new helper of your own. The point of parameterized functions is that you don't rewrite the same shape twice; you call it twice with different arguments.

SUBMIT Turn In Your Work

This lesson's reflection is longer than usual. Submit three items to the Schoology assignment:

  1. Screenshot of your code. The screenshot must include the def line of at least two of your helper functions and the chat-command function that calls them.
  2. Screenshot of the result in Minecraft — the full build, ideally with you standing in/on it for scale.
  3. Written reflection. Answer all five in 1–2 sentences each:
    • What did your program build, and which helpers did you compose?
    • Name one place in your code where the iteration variable (i or j) was used as a coordinate or in math — not just as a counter.
    • Did you use modulo, distance math, or both? What pattern or shape did the math create?
    • Pick one of your helper functions and explain its parameters — what does each one control?
    • If a classmate wanted to move your whole build to a new spot, what would they have to change? (Hopefully a small answer.)
Note: This is the lesson where the reflection matters most. You're not just building a thing — you're showing that you understand why parameterized helpers let you build big things from small pieces.

When You're Done

  • Trade computers and run a classmate's program. Try changing only the CX, CZ values — did the whole build move cleanly?
  • Open the advanced examples file your teacher shared — lesson5_advanced.py — for more helpers (volcano_lava, spiral staircase using trig, village of houses, sponge 3D pattern).
  • Add a spawn_castle_guards() helper that uses mobs.spawn() inside a parameterized region (Lesson 4 callback).
  • Look up blocks.fill with FillOperation.OUTLINE — can you rewrite castle_wall in two lines using one big fill?
Back to Minecraft Lessons