Python Code
Here is a Python script to verify that for every level-n small triangle in the Sierpinski gasket "chaos game" challenge, there exists a sequence of exactly n+2 moves that places an initial point starting at one of the vertices into its interior.
"""
Sierpinski Gasket - Level-n Construction with Chaos Game Points
===================================================
Draws the level-n Sierpinski gasket (n = 2..5) and plots all chaos game
points from (2+n)-tuples of moves {1,2,3} that land inside the interior of a small triangle
after starting from a vertex V_k.
Barycentric coordinates:
V_1 = (1, 0, 0) [top]
V_2 = (0, 1, 0) [lower left]
V_3 = (0, 0, 1) [lower right]
Chaos game rule: each move goes halfway toward the chosen vertex.
After (2+n) moves starting from V_k, the denominator of the
barycentric coordinates is 2^(n+2) = D.
P = V_k/D + sum_{i=1}^{n+2} V_{M[i]} / 2^(n+3-i)
A point (with coords expressed as integers over D) is in the interior of a
small triangle iff none of its barycentric coordinates is a multiple
of 1/2^n, i.e iff no numerator is divisible by D/2^n = 4.
"""
# ── imports ─────────────────────────────────────────
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from fractions import Fraction
from itertools import product as iproduct
# ── user input ────────────────────────────────────────
while True:
try:
n = int(input("Enter level n (2 to 5): "))
if 2 <= n <= 5:
break
print(" Please enter an integer between 2 and 5.")
except ValueError:
print(" Invalid input – please enter an integer.")
while True:
try:
initV = int(input("Enter initial vertex (1,2,or 3): "))
if 1 <= initV <= 3:
break
print(" Please enter an integer between 1 and 3.")
except ValueError:
print(" Invalid input – please enter an integer.")
# ── geometry helpers ────────────────────────────────────
def bary_to_cart(lam, V):
"""Barycentric (3,) → Cartesian (2,) using triangle vertices V (3×2)."""
return lam[0]*V[0] + lam[1]*V[1] + lam[2]*V[2]
# Equilateral triangle vertices (Cartesian)
V_cart = np.array([
[0.5, np.sqrt(3)/2], # V1 – top
[0.0, 0.0 ], # V2 – lower left
[1.0, 0.0 ], # V3 – lower right
])
# ── build all triangles by recursive subdivision ────────────────────
# Barycentric vertices as exact Fractions
V1b = (Fraction(1), Fraction(0), Fraction(0))
V2b = (Fraction(0), Fraction(1), Fraction(0))
V3b = (Fraction(0), Fraction(0), Fraction(1))
def midpoint(A, B):
return tuple((a + b) / 2 for a, b in zip(A, B))
def subdivide(tri):
"""Split triangle into 3 corner sub-triangles + 1 removed centre."""
A, B, C = tri
AB, BC, CA = midpoint(A, B), midpoint(B, C), midpoint(C, A)
return [(A, AB, CA), (AB, B, BC), (CA, BC, C)], (AB, BC, CA)
def build_gasket(n):
"""Return (solid_list, removed_list) of triangles (barycentric) at level n."""
solid = [(V1b, V2b, V3b)]
removed = []
for _ in range(n):
next_solid = []
for tri in solid:
corners, centre = subdivide(tri)
next_solid.extend(corners)
removed.append(centre)
solid = next_solid
return solid, removed # 3^n solid, (3^n - 1)/2 removed
solid_triangles, removed_triangles = build_gasket(n)
# ── draw the gasket ─────────────────────────────────────
fig, ax = plt.subplots(figsize=(7,6.5))
ax.set_aspect('equal')
ax.axis('off')
ax.set_xlim(-0.06, 1.06)
ax.set_ylim(-0.10, 0.99)
LIGHT_GRAY = '#c8c8c8'
WHITE = '#ffffff'
EDGE_COLOR = '#444444'
POINT_COLOR = '#c0392b'
def draw_tri(tri_bary, fc, ec=EDGE_COLOR, lw=0.6, zorder=1):
pts = np.array([
bary_to_cart(np.array([float(v) for v in vert]), V_cart)
for vert in tri_bary
])
ax.add_patch(mpatches.Polygon(
pts, closed=True, facecolor=fc, edgecolor=ec, linewidth=lw, zorder=zorder
))
# Outer boundary
draw_tri((V1b, V2b, V3b), fc=LIGHT_GRAY, lw=1.2, zorder=0)
# White removed regions (paint over the gray background)
for tri in removed_triangles:
draw_tri(tri, fc=WHITE, ec=WHITE, lw=0, zorder=2)
# Solid small triangles with edges
for tri in solid_triangles:
draw_tri(tri, fc=LIGHT_GRAY, ec=EDGE_COLOR, lw=max(0.3, 0.7 - 0.1*n), zorder=3)
# ── chaos game: all 3^(n+2) tuples of (n+2) moves ────────────────
num_moves = n + 2
D = 2 ** (n + 2) # denominator
V_bary = {
1: (Fraction(1), Fraction(0), Fraction(0)),
2: (Fraction(0), Fraction(1), Fraction(0)),
3: (Fraction(0), Fraction(0), Fraction(1)),
}
plotted_points = [] # Cartesian coords for plotting
bary_records = [] # (moves, (a,b,c) integer numerators over D)
count = 0
for moves in iproduct([1, 2, 3], repeat=num_moves):
# Starting contribution: initV / D
coords = [Fraction(0), Fraction(0), Fraction(0)]
coords[initV-1] = Fraction(1,D)
# Move i (1-indexed): contribute V_{M[i]} / 2^(n+3-i)
for i, m in enumerate(moves, start=1):
factor = Fraction(1, 2 ** (n + 3 - i))
vb = V_bary[m]
for k in range(3):
coords[k] += vb[k] * factor
# Express as integer numerators over D
int_nums = [int(c * D) for c in coords]
assert sum(int_nums) == D, f"Coords don't sum to 1: {int_nums}"
# Plot iff NO numerator is divisible by 4
if all(x % 4 != 0 for x in int_nums):
cart = bary_to_cart(np.array([float(c) for c in coords]), V_cart)
plotted_points.append(cart)
bary_records.append((moves, tuple(int_nums)))
count += 1
# ── plot the points ────────────────────────────────────────
if plotted_points:
pts = np.array(plotted_points)
pt_size = 60 - 10 * n
ax.scatter(pts[:, 0], pts[:, 1],
s=pt_size, color=POINT_COLOR, zorder=5,
linewidths=0, alpha=0.85)
# Vertex labels
for label, vc, ha in [('$V_1$', V_cart[0], 'center'),
('$V_2$', V_cart[1], 'right'),
('$V_3$', V_cart[2], 'left')]:
dx = 0 if ha == 'center' else (-0.01 if ha == 'right' else 0.01)
dy = 0.035 if ha == 'center' else -0.01
ax.text(vc[0]+dx, vc[1]+dy, label, ha=ha, va='center', fontsize=14)
total_tuples = 3 ** num_moves
minMoves = 2+n
ax.set_title(
f'Level-{n} Sierpinski Gasket with {minMoves} moves starting at $V_{initV}$\n'
f'{count} interior points out of {total_tuples} possible points',
fontsize=11, pad=10
)
plt.tight_layout()
filename = f'sierpinski_level{n}-{initV}.png'
plt.savefig(filename, dpi=150, bbox_inches='tight')
plt.show()
print(f"\nLevel n : {n}")
print(f"Number of moves : {num_moves} (= n + 2)")
print(f"Total tuples (3^{num_moves}) : {total_tuples:,}")
print(f"Solid triangles : {len(solid_triangles):,} (= 3^n)")
print(f"Points plotted : {count:,}")
print(f"Saved to : {filename}")
Credit: This Python code was developed with the assistance of Anthropic Claude.
