Tour: Turtle Graphics
Drive a turtle around a 2D plane. Each step adds one new idea, and by step 5 whole shapes execute as data. Showpieces: enums as data, pattern matching, and executing data as a program.
Step 1 — Values & functions
module Main
import std.io.stdio.println
func report(at x: Int, y: Int, facing heading: Int) {
println("turtle at (\(x), \(y)) facing \(heading)°");
}
@main
func main() {
report(at: 0, 0, facing: 0);
}
at x and facing heading are labeled (at and facing are the labels; x and heading are the bind names). y: Int between them is positional — bare name, no label — so the call site is report(at: 0, 0, facing: 0).
Step 2 — Structs & methods
<!-- sample: continue -->struct Turtle {
var x: Int
var y: Int
var heading: Int
}
extend Turtle {
mutating func forward(distance: Int) {
match self.heading {
0 => self.x = self.x + distance,
90 => self.y = self.y + distance,
180 => self.x = self.x - distance,
270 => self.y = self.y - distance,
_ => {}
}
}
mutating func turn(degrees: Int) {
self.heading = (self.heading + degrees) % 360;
}
}
Both methods take a positional Int. Calls look like t.forward(10) and t.turn(90). var fields plus mutating func and the turtle has a body — note that match works on integers here, not just enums.
Replace main to try it:
@main
func main() {
var t = Turtle(x: 0, y: 0, heading: 0);
t.forward(10);
t.turn(90);
t.forward(5);
report(at: t.x, t.y, facing: t.heading);
}
Step 3 — Enums & pattern matching
A program for the turtle is a list of commands — one enum variant per instruction.
<!-- sample: continue -->enum Command {
case Move(distance: Int)
case Turn(degrees: Int)
case PenUp
case PenDown
}
Each command is a simple value. A program is a [Command] — a list of instructions the turtle executes in order.
Step 4 — Collections
Now an interpreter. Walk the array, execute each Command.
func run(program: [Command], mutating on turtle: Turtle) {
for command in program {
match command {
.Move(distance) => turtle.forward(distance),
.Turn(degrees) => turtle.turn(degrees),
.PenUp => {},
.PenDown => {}
}
}
}
run takes program: [Command] positionally and mutating on turtle: Turtle with a label — mutating comes before the label. The turtle accumulates all the moves as the loop runs.
Add to main:
let program: [Command] = [.Move(distance: 10), .Turn(degrees: 90), .Move(distance: 5)];
run(program, on: t);
report(at: t.x, t.y, facing: t.heading);
Step 5 — Protocols
A Shape is anything that produces a [Command]. With a protocol, you can define Square, Triangle, and Spiral as types — and feed them to run interchangeably.
protocol Shape {
func commands() -> [Command]
}
struct Square {
let size: Int
}
extend Square: Shape {
public func commands() -> [Command] {
[
.Move(distance: self.size), .Turn(degrees: 90),
.Move(distance: self.size), .Turn(degrees: 90),
.Move(distance: self.size), .Turn(degrees: 90),
.Move(distance: self.size), .Turn(degrees: 90)
]
}
}
struct Spiral {
let turns: Int
}
extend Spiral: Shape {
public func commands() -> [Command] {
var result: [Command] = [];
var i = 1;
while i <= self.turns {
result.append(.Move(distance: i * 5));
result.append(.Turn(degrees: 90));
i = i + 1;
}
result
}
}
A Shape produces commands; run consumes commands. The two halves never meet directly — the protocol decouples them. That's the same pattern you'll see throughout Kestrel libraries.
Add to main:
run(Square(size: 10).commands(), on: t);
report(at: t.x, t.y, facing: t.heading); // a square returns the turtle to where it started
run(Spiral(turns: 3).commands(), on: t);
report(at: t.x, t.y, facing: t.heading);
What you saw
| Step | Feature |
|---|---|
| 1 | Functions, positional vs labeled parameters |
| 2 | var fields, mutating func, match on integers |
| 3 | Enums with payloads |
| 4 | for loop, interpreter, mutating parameters |
| 5 | Protocols, multiple conforming types |
The takeaway: programs are data. A [Command] is just a list, but the moment you walk it with match, it's executable. That same trick — represent behavior as data, interpret it later — is how Kestrel handles parsers, query builders, animation timelines, and most things that look like "small languages embedded in code."