def g56_shove(rng):
"""SHOVE -- particles dropped at random points repel each other by a short-range
force until they settle into an uneven equilibrium spacing; each cell's glyph
weight comes from its rank in the settled pressure field, so tight knots where
particles jammed together read dark and the gaps they shoved open read light."""
N = rng.choice([24, 32, 40, 50])
c = Canvas(N, "GLYPH", "glyph", "shove", tiles=False)
NX, NY = c.cols, c.rows
n = rng.choice([12, 16, 22, 28, 34])
pts = [[rng.uniform(0.0, NX), rng.uniform(0.0, NY)] for _ in range(n)]
reach = rng.uniform(3.5, 8.0)
strength = rng.uniform(0.6, 1.3)
iters = rng.choice([25, 35, 50])
for _ in range(iters):
fx = [0.0] * n
fy = [0.0] * n
for a in range(n):
ax, ay = pts[a]
for b in range(a + 1, n):
dx = ax - pts[b][0]
dy = ay - pts[b][1]
d = math.sqrt(dx * dx + dy * dy) + 1e-6
if d < reach:
push = strength * (reach - d) / reach
ux, uy = dx / d, dy / d
fx[a] += ux * push
fy[a] += uy * push
fx[b] -= ux * push
fy[b] -= uy * push
for i in range(n):
pts[i][0] = clamp(pts[i][0] + fx[i], 0.0, NX - 1e-6)
pts[i][1] = clamp(pts[i][1] + fy[i], 0.0, NY - 1e-6)
sigma = rng.uniform(1.3, 2.6)
twosig2 = 2.0 * sigma * sigma
field = [[0.0] * NY for _ in range(NX)]
for i in range(NX):
cx = i + 0.5
for j in range(NY):
cy = j + 0.5
v = 0.0
for (px, py) in pts:
dx = cx - px
dy = cy - py
v += math.exp(-(dx * dx + dy * dy) / twosig2)
field[i][j] = v
flat = [(field[i][j], i, j) for i in range(NX) for j in range(NY)]
flat.sort(key=lambda t: t[0])
total = len(flat)
chars = ramp([" ", ".", ":", "+", "x", "o", "#", "▒", "▓", "█"])
k = len(chars)
gamma = rng.uniform(0.55, 1.35)
for idx, (v, i, j) in enumerate(flat):
p = (idx + 0.5) / total
p = p ** gamma
bucket = min(k - 1, int(p * k))
ch = chars[bucket]
if ch != " ":
c.glyph(i, j, ch)
return c