Pixels2GenAI
Path i Foundations
M 04 · 4.1.2 · hybrid

4.1.2 Dragon Curve

Generate the Heighway dragon — a recursive L/R fold sequence rendered with turtle graphics. One self-similar rule, 2,047 turns, an unmistakable silhouette.

Duration20–25 min
Levelbeginner-intermediate
Load3 core concepts
Prereqs4.1.1 (recursion basics), Python string manipulation

Overview

The Heighway dragon curve is what you get when you fold a paper strip in half repeatedly, always in the same direction, then unfold each crease to exactly 90°. NASA physicist John Heighway first investigated it in 1966, and Martin Gardner popularised it in his Scientific American column the next year [1]. What makes it irresistible: the entire curve, no matter how complex, is encoded as a string of Ls and Rs built by one short recursive rule. Render the string with turtle graphics — move forward, turn left or right — and a self-similar dragon emerges that never crosses itself and tiles the plane.

This lesson sits at the conceptual end of Module 04. Where 4.1.1 used recursion to place pixels, 4.1.2 uses recursion to construct a sequence of instructions and only then renders it. That separation — generation rule vs. interpretation — is the same architectural move you will see again in Mandelbrot iteration (4.1.3) and L-systems (4.3.1).

Learning objectives

  1. Apply the recursive rule “$D_n$ = previous sequence + $R$ + reversed-and-inverted previous sequence” to build the dragon sequence.
  2. Render an L/R turn sequence on a NumPy canvas using a turtle-graphics interpreter.
  3. Predict the sequence length for any depth using the formula 2 to the power (n+1), minus 1.
  4. Recognise the dragon curve as a separation of generation (the recursive string) from interpretation (the turtle walk) — the same pattern that drives L-systems.

Quick start — see the dragon emerge

python · quick_start.py
import numpy as np
from PIL import Image

LEFT, UP, RIGHT, DOWN = range(4)

def turn_left(d):  return (d + 3) % 4
def turn_right(d): return (d + 1) % 4

def generate_dragon_sequence(initial_turn, depth):
    if depth == 0:
        return initial_turn
    previous = generate_dragon_sequence(initial_turn, depth - 1)
    inverted = ''.join('L' if c == 'R' else 'R' for c in previous[::-1])
    return previous + 'R' + inverted

# Depth 10 → 2047 turn instructions
sequence = generate_dragon_sequence('R', 10)
print(f"Sequence length: {len(sequence)}")
The Heighway dragon curve rendered at depth 10 — a light-blue, intricate, self-similar shape that resembles a curled dragon
Fig. 1 Depth 10 — 2,047 line segments produced from one recursive rule applied to a single `R`.

Core concepts

Concept 1 — Paper-folding as recursion

Fold a paper strip in half once. Unfold it so the crease opens to 90°: one right turn. Fold it twice in the same direction, then unfold each crease to 90°: the sequence is R R L — two right turns then one left turn [2]. The number of creases doubles with every fold, and the pattern is exactly the dragon curve at that depth.

The construction rule that captures this is recursive: take the previous depth’s sequence, append a single R (the brand-new middle fold), then append the reversed and inverted previous sequence. “Inverted” means swap every L with R and vice versa — this captures the fact that, after the new fold, the second half of the paper now points in the opposite direction:

$D_0 = R$, and at depth $n$, $D_n$ is the previous depth’s sequence, then R, then the previous depth’s sequence reversed and inverted.

Concept 2 — Generating the sequence

The recursive construction translates directly to two short Python functions:

python · sequence.py
def invert_sequence(s):
    """Swap every L for R and vice versa."""
    return ''.join('L' if c == 'R' else 'R' for c in s)

def generate_dragon_sequence(initial_turn, depth):
    if depth == 0:
        return initial_turn
    previous = generate_dragon_sequence(initial_turn, depth - 1)
    second_half = invert_sequence(previous[::-1])
    return previous + 'R' + second_half
Diagram showing the recursive growth of the dragon curve sequence through depths zero, one, two, three, and four, highlighting the central R fold and the reversed-inverted second half
Fig. 2 The recursive sequence: each row is the previous row, then `R`, then the previous row reversed-and-inverted.

Trace the first few iterations by hand:

DepthSequenceLength
0R1
1R R L3
2RRL R RLL7
3RRLRRLL R RRLRLLL15
4RRLRRLLRRRLRLLL R RRLRRLRRRLLRLLL31

The bolded middle R is the brand-new fold introduced at each depth — every other character was either inherited from the previous row or is a reversed-inverted copy of it.

Concept 3 — Turtle interpretation

The sequence tells you how to turn. To actually draw anything, an imaginary “turtle” walks across the canvas:

  • Forward — move one step in the current direction, drawing pixels along the way.
  • L — turn 90° counter-clockwise.
  • R — turn 90° clockwise.

Directions are integers 0..3 so rotation is a (d ± 1) % 4 operation. Forward motion is a slice assignment along the matching axis — canvas[y, x:x+s] for a rightward step, and so on:

python · turtle.py
def move_forward(canvas, position, step_size, direction, color):
    x, y = position
    if direction == RIGHT:
        canvas[y, x:x + step_size] = color
        return (x + step_size, y)
    elif direction == LEFT:
        canvas[y, x - step_size:x] = color
        return (x - step_size, y)
    elif direction == UP:
        canvas[y - step_size:y, x] = color
        return (x, y - step_size)
    elif direction == DOWN:
        canvas[y:y + step_size, x] = color
        return (x, y + step_size)

def draw_dragon(canvas, sequence, start, step, direction, color):
    pos = start
    for turn in sequence:
        pos = move_forward(canvas, pos, step, direction, color)
        direction = turn_right(direction) if turn == 'R' else turn_left(direction)
    move_forward(canvas, pos, step, direction, color)
Diagram showing the four cardinal directions arranged in a circle and how left and right turns map to direction index changes
Fig. 3 The turtle's direction code: integers `0..3` form a cycle that left/right turns step around modulo 4.

Exercises

Three exercises in Execute → Modify → Create order: run the depth-10 dragon, sweep parameters, then re-implement the recursive sequence generator from scratch.

EXECUTE I.

Run the dragon

Download and run dragon_curve.py. The full script generates the depth-10 sequence and walks the turtle across an 800×600 canvas.

dragon_curve.py — full reference implementation

Reflection questions

  • Why does the depth-10 sequence have exactly 2,047 entries?
  • Look for the “spine” of bilateral symmetry. Where on the curve does the very first R of the sequence fall — at the start, the middle, or the end?
  • What is the visible difference between depth 6 and depth 10? Where does the new detail live?
MODIFY II.

Sweep depth, colour, and direction

Edit your copy of dragon_curve.py to produce three variants. Only change the marked parameters.

Goals

  1. Depth comparison. Render the same dragon at depths 4, 7, and 10 side-by-side. Keep step_size = 3 constant so you can see the silhouette converging.
  2. Fire dragon. Replace the light-blue colour (100, 180, 255) with a warm orange-red such as (255, 110, 40).
  3. Rotated dragon. Change start_direction from UP to RIGHT. Predict what happens, then verify.
CREATE III.

Re-code the recursive generator

Implement the dragon sequence generator from scratch. The turtle/render code is provided; you fill in the recursion.

python · exercise3_starter.py
def invert_sequence(sequence):
    """Swap all L's and R's."""
    # TODO 1: return a string with every L replaced by R and every R by L
    pass

def generate_dragon_sequence(initial_turn, depth):
    """Build the dragon curve sequence recursively."""
    if depth == 0:
        # TODO 2: return the base case
        pass
    # TODO 3: get the previous depth's sequence (recursive call)
    previous = None
    # TODO 4: build the second half — reverse, then invert
    second_half = None
    # TODO 5: combine: previous + 'R' (the new fold) + second_half
    return None

# Self-test
test = generate_dragon_sequence('R', 3)
assert test == 'RRLRRLLRRRLRLLL', f"Got {test!r}"
print("All good — depth 3 sequence matches the expected dragon string.")

Make it your own

  • Twin dragons. Draw two dragons from the same start position, one facing UP and one facing DOWN, each a different colour. They interlock without overlapping — the twin-dragon tiling Knuth analysed [2].
An animation of twin dragon curves in blue and orange interlocking on a black background
Fig. 6 Twin dragons growing through depths 1–10 — one blue and rising, one orange and descending.
  • Animated depth sweep. Loop over depths 1..12 and save each as a frame. Pillow’s Image.save(..., save_all=True, append_images=...) writes the result as an animated GIF.
An animation of the dragon curve evolving from depth one to depth twelve, with each frame adding more recursive detail
Fig. 7 One frame per depth — the dragon's silhouette settles by depth 6 and the rest is boundary detail.
  • Vector output. Replace move_forward with a Pillow ImageDraw.line() call (or SVG output) for a smooth, resolution-independent dragon you can print or laser-cut.

Downloads

dragon_curve.py — Exercise 1 reference exercise3_solution.py — Exercise 3 reference dragon.py — minimal alternative implementation

Summary

Common pitfalls to avoid

  • Forgetting the base case. Without if depth == 0: return initial_turn, the recursion never terminates and Python raises RecursionError.
  • Reversing without inverting (or vice versa). Both operations are needed on the second half. Drop either one and the curve loses its self-similar shape and starts crossing itself.
  • Off-canvas drawing. Depth 12 at step_size = 3 exceeds an 800-pixel canvas. Either reduce step_size, increase the canvas, or tune start_position.
  • Confusing axis conventions. move_forward uses (x, y) but NumPy indexes canvas[y, x]. Mixing these up swaps your dragon’s orientation.

References

  1. [1] Gardner, M. (1967). Mathematical games: The dragon curve and other animals. Scientific American, 216(3), 124–129.
  2. [2] Davis, C., & Knuth, D. E. (1970). Number representations and dragon curves — I, II. Journal of Recreational Mathematics, 3(2), 66–81; 3(3), 133–149.
  3. [3] Crichton, M. (1990). Jurassic Park. Knopf. ISBN 978-0-394-58816-2.
  4. [4] Mandelbrot, B. B. (1982). The Fractal Geometry of Nature. W. H. Freeman. ISBN 978-0-7167-1186-5.
  5. [5] Prusinkiewicz, P., & Lindenmayer, A. (1990). The Algorithmic Beauty of Plants. Springer-Verlag. ISBN 978-0-387-97297-8.
  6. [6] Edgar, G. (2008). Measure, Topology, and Fractal Geometry (2nd ed.). Springer. ISBN 978-0-387-74748-4.
  7. [7] Harris, C. R., Millman, K. J., van der Walt, S. J., et al. (2020). Array programming with NumPy. Nature, 585, 357–362. doi:10.1038/s41586-020-2649-2