Skip to content
The Lumenary

Math laboratory

Loops that close, loops that stop, and one that never stops

Runs turtle loops 'repeat n times: forward 1, turn right a degrees' for squares, triangles, hexagons and a star, reporting the distance from the start (floating point) and the total turning. Then runs three 'repeat until' loops, one of which never meets its stopping condition and is cut off after 1000 steps.

Back to the labNumerical approximationExact computation
Loops that close, loops that stop, and one that never stopsPythonNumerical approximationExact computationComputationally tested
"""Loops that close, loops that stop, and a loop that never stops.

A turtle starts at a point facing up and runs the loop 'repeat n times:
forward 1, turn right by some angle'. The program reports how far from the
start it finishes and how much it has turned altogether: the path closes
up when the total turning is a whole number of full turns (360 degrees).
Then it runs three 'repeat until' loops: counting up by 3 until the number
is bigger than 20, counting down by 2 from 10 until it is exactly 0, and
counting down by 2 from 9 until it is exactly 0 - a loop whose stopping
condition is never true, so the program has to give up after 1000 steps.
"""

import math
import textwrap

def turtle_loop(repeats, angle):
    """Run 'repeat: forward 1, turn right angle'. Return (end, total turn)."""
    x, y, heading = 0.0, 0.0, 90.0  # heading in degrees; 90 means 'up'
    for _ in range(repeats):
        x += math.cos(math.radians(heading))
        y += math.sin(math.radians(heading))
        heading -= angle  # turning right is clockwise
    return (x, y), repeats * angle

def repeat_until(start, step, stop, limit=1000):
    """Repeat 'number = step(number)' until stop(number) is true.

    Returns (final number, steps taken, stopped?) - gives up after limit."""
    number = start
    for steps in range(limit):
        if stop(number):
            return number, steps, True
        number = step(number)
    return number, limit, stop(number)

def main():
    print("repeat n times: forward 1, turn right by a degrees")
    print("   n     a  distance from start  total turning")
    for repeats, angle in [(4, 90), (3, 120), (6, 60), (3, 60), (5, 144),
                           (8, 45)]:
        (x, y), turned = turtle_loop(repeats, angle)
        distance = math.hypot(x, y)
        print(f"{repeats:>4}  {angle:>4}  {distance:>19.3f}  {turned:>9} deg")
    print("Here the path closes exactly when the total turning is a whole")
    print("number of full turns: 360, 720, ... degrees.")
    print()
    print("repeat until:")
    loops = [
        ("start 0, add 3 until bigger than 20", 0, lambda n: n + 3,
         lambda n: n > 20),
        ("start 10, take 2 until exactly 0", 10, lambda n: n - 2,
         lambda n: n == 0),
        ("start 9, take 2 until exactly 0", 9, lambda n: n - 2,
         lambda n: n == 0),
    ]
    for text, start, step, stop in loops:
        number, steps, stopped = repeat_until(start, step, stop)
        if stopped:
            print(f"  {text}: stops after {steps} steps at {number}")
        else:
            print(f"  {text}: gave up after {steps} steps (at {number})")
    print()
    print(textwrap.fill(
        "Epistemic status: the turtle distances are floating-point "
        "calculations, rounded to 3 decimal places, so '0.000' means 'zero "
        "up to rounding' - the lesson explains with angles why these paths "
        "close exactly. The counting loops use exact whole numbers. Giving "
        "up after 1000 steps does not prove that a loop never stops; the "
        "odd-number argument in the lesson does.",
        width=78,
    ))

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Used in