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.
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(andjin 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).
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.
for i in range(N): repeats N times.~), trace_path, mark/line.blocks.fill(), opposite corners, mobs.spawn(), randpos().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.
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
i in for i in range(N). Counts 0, 1, ..., N-1.%)The remainder after division. 7 % 2 = 1. Turns numbers into repeating patterns.abs() or the Pythagorean theorem.def tower(cx, cz, h): takes 3 parameters.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.
- In Minecraft, press C to open Code Builder.
- Choose MakeCode.
- Click New Project and name it
Lesson 5 - Castle. - 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. Theimeans the column slides one block east each iteration.pos(i, i, 0)— the top of the column. Because Y isi, 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.
/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
icontrols one axis (east/west) —i * 2spaces the pillars 2 blocks apart.jcontrols another axis (north/south) — same spacing.height = i + j + 1— the pillar ati=0, j=0is 1 block tall. The pillar ati=5, j=5is 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=0 → 0 + 0 + 1 = 1 block.
Tallest: i=5, j=5 → 5 + 5 + 1 = 11 blocks. The pillars rise diagonally because the height depends on both variables.
/tp ~ ~ ~10, Enter. Then chat steps.
STEP 5 Patterns with Modulo %
The % operator is called modulo. It gives the remainder after dividing.
| Expression | Result | What it's useful for |
|---|---|---|
0 % 2 | 0 | Even numbers give 0. |
1 % 2 | 1 | Odd numbers give 1. |
5 % 2 | 1 | Still odd → still 1. |
(i + j) % 2 | 0 or 1 | Toggles every block — checkerboard. |
i % 4 | 0, 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
colorsis a list — an ordered collection of values. Index 0 isWOOL, index 1 isOBSIDIAN, 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
iandjare even or odd.
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.
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)
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.
/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,HEIGHTbecame parameters:cx, cz, radius, height. - Inside the loop,
pos(CENTER + di, ...)becamepos(cx + di, ...). The function uses whatevercxthe 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)
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 function — two_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)
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.
/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.
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
iorjas a coordinate or in a height calculation. - At least one helper uses modulo (
%) or distance math (abs/maxor 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
forloop 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.
SUBMIT Turn In Your Work
This lesson's reflection is longer than usual. Submit three items to the Schoology assignment:
- Screenshot of your code. The screenshot must include the
defline of at least two of your helper functions and the chat-command function that calls them. - Screenshot of the result in Minecraft — the full build, ideally with you standing in/on it for scale.
- 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 (
iorj) 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.)
When You're Done
- Trade computers and run a classmate's program. Try changing only the
CX, CZvalues — did the whole build move cleanly? - Open the advanced examples file your teacher shared —
lesson5_advanced.py— for more helpers (volcano_lava,spiralstaircase using trig,villageof houses,sponge3D pattern). - Add a
spawn_castle_guards()helper that usesmobs.spawn()inside a parameterized region (Lesson 4 callback). - Look up
blocks.fillwithFillOperation.OUTLINE— can you rewritecastle_wallin two lines using one big fill?