Tutorial 03: Control Flow
Difficulty: Beginner
Time: ~30 minutes
Prerequisites: Tutorial 02
Goal
Use conditions and loops to decide what runs and how often.
if and else
The simplest branch:
if (score > 10) {
say("High score!")
}With an else:
if (lives > 0) {
say("Still alive")
} else {
say("Game over")
}You can chain conditions:
if (wave == 1) {
say("Warmup")
} else if (wave < 5) {
say("Main phase")
} else {
say("Final phase")
}while
while repeats until the condition becomes false.
let i: int = 0
while (i < 3) {
say(f"Loop: {i}")
i = i + 1
}Use it when the stop condition depends on changing state.
for
A range-based for is cleaner when you know the number of iterations:
for i in 0..5 {
say(f"Round {i}")
}The upper bound is exclusive, so 0..5 runs 0, 1, 2, 3, 4.
You can also loop over array values directly:
let rewards: int[] = [5, 10, 20]
for reward in rewards {
say(f"Reward: {reward}")
}Use a range when you need the index, and an array loop when you only need the value.
foreach over Minecraft selectors
Use foreach when the source is a Minecraft selector, not a RedScript array:
foreach (player in @a[tag=joined]) {
tell(player, "Round starting")
}
foreach (zombie in @e[type=zombie,distance=..12]) {
kill(zombie)
}Inside a selector loop, the loop variable is an entity target. This is different from for reward in rewards, where the variable is a normal RedScript value.
break
Exit the nearest loop immediately:
let i: int = 0
while (i < 10) {
if (i == 4) {
break
}
say(f"i = {i}")
i = i + 1
}continue
Skip the rest of the current iteration:
for i in 0..6 {
if (i == 2) {
continue
}
say(f"Processed {i}")
}match
match is useful when a value can be one of several cases.
enum Phase { Lobby, Playing, Finished }
let phase: Phase = Phase::Lobby
fn show_phase() {
match phase {
Phase::Lobby => { say("Waiting for players") },
Phase::Playing => { say("Match running") },
Phase::Finished => { say("Results screen") },
}
}This reads better than a long chain of if statements when you are handling named states.
match can also use integer ranges and a wildcard arm:
let score: int = 86
match score {
90..100 => { say("A") },
80..89 => { say("B") },
_ => { say("Keep practicing") },
}Range patterns in match are inclusive on both ends.
if let Some(...)
Option<T> values are handled with if let Some(name) = value:
let maybe_kills: Option<int> = Some(3)
if let Some(kills) = maybe_kills {
say(f"Kills: {kills}")
} else {
say("No kill count yet")
}Use this when a helper may or may not return a value. The variable inside Some(...) is only available in that branch.
Putting It Together
namespace tutorial03
enum GameState { Idle, CountingDown, Running }
let state: GameState = GameState::Idle
let timer: int = 5
@throttle(ticks=20)
fn game_loop() {
match state {
GameState::Idle => {
actionbar(@a, "Waiting...")
},
GameState::CountingDown => {
if (timer > 0) {
title(@a, f"{timer}")
timer = timer - 1
} else {
state = GameState::Running
say("Go!")
}
},
GameState::Running => {
actionbar(@a, "Game is running")
},
}
}
@on_trigger("start")
fn start_game() {
state = GameState::CountingDown
timer = 5
}This tutorial uses:
matchfor the high-level modeiffor the timer condition@throttle(ticks=20)to step once per second
When to Use Which Form
- use
iffor one-off boolean decisions - use
matchfor named modes or enum cases - use
forfor known iteration counts - use
whilewhen the exit condition is dynamic
Practice
- Add a
Pausedstate and handle it withmatch. - Write a
forloop that gives a player 5 apples. - Write a
whileloop that counts down from 10 to 1.
Next: Tutorial 04: Functions