import pygame
import random
import math
import time
# --- Configuration ---
WIDTH, HEIGHT = 800, 600
RADIUS = 7
NUM_CIRCLES = 130
RECORD_TIME = 5.0 # seconds
FPS = 60
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
sleeptime=.02
# --- Helper functions ---
def make_smiley_positions(n):
positions = []
cx, cy = WIDTH // 2, HEIGHT // 2
# eyes
for _ in range(n // 8):
x = cx - 80
y = cy - 60
positions.append((x, y))
for _ in range(n // 8):
x = cx + 80
y = cy - 60
positions.append((x, y))
# mouth (arc)
for i in range(n // 2):
angle = math.pi / 2 + (i / (n // 2)) * math.pi
x = cx + 120 * math.sin(angle) + 10
y = cy - 80 * math.cos(angle)
positions.append((x, y))
# fill rest randomly around face outline
while len(positions) < n:
angle += .2
r = 200
x = cx + r * math.cos(angle)
y = cy + r * math.sin(angle)
positions.append((x, y))
return positions
def random_velocity():
angle = random.random() * 2 * math.pi
speed = random.uniform(3, 10)
return speed * math.cos(angle), speed * math.sin(angle)
def is_valid_coord(x):
return isinstance(x, (int, float)) and math.isfinite(x)
# --- Circle setup ---
positions = make_smiley_positions(NUM_CIRCLES)
velocities = [random_velocity() for _ in range(NUM_CIRCLES)]
recorded_states = []
# --- Main simulation ---
running = True
reverse_mode = False
start_time = time.time()
# helper to nudge overlapping circles slightly apart
def nudge_apart(i, j):
# small random displacement
dx = (random.random() - 0.5) * 0.5
dy = (random.random() - 0.5) * 0.5
xi, yi = positions[i]
xj, yj = positions[j]
positions[i] = (xi + dx, yi + dy)
positions[j] = (xj - dx, yj - dy)
while running:
dt = clock.tick(FPS) / 1000.0
screen.fill((30, 30, 30))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# record current state (for reversal later)
if not reverse_mode:
# store a snapshot of positions and velocities
recorded_states.append([(x, y, vx, vy) for (x, y), (vx, vy) in zip(positions, velocities)])
if time.time() - start_time > RECORD_TIME:
reverse_mode = True
recorded_states = recorded_states[::-1]
step = 0
continue
if reverse_mode:
# play back recorded states in reverse
if step < len(recorded_states):
frame = recorded_states[step]
# replace positions and velocities from frame (keeps lengths aligned)
positions = [(x, y) for x, y, _, _ in frame]
velocities = [(vx, vy) for _, _, vx, vy in frame]
step += 1
else:
running = False
else:
# --- Move circles ---
for i in range(NUM_CIRCLES):
x, y = positions[i]
vx, vy = velocities[i]
# ensure we have numbers (fallback if something went wrong)
if not (is_valid_coord(x) and is_valid_coord(y)):
# reset to center-ish if invalid
x, y = WIDTH // 2 + random.uniform(-5, 5), HEIGHT // 2 + random.uniform(-5, 5)
positions[i] = (x, y)
x += vx
y += vy
# bounce off walls
if x - RADIUS < 0:
x = RADIUS
vx *= -1
if x + RADIUS > WIDTH:
x = WIDTH - RADIUS
vx *= -1
if y - RADIUS < 0:
y = RADIUS
vy *= -1
if y + RADIUS > HEIGHT:
y = HEIGHT - RADIUS
vy *= -1
positions[i] = (x, y)
velocities[i] = (vx, vy)
# --- Bounce between circles (safe) ---
for i in range(NUM_CIRCLES):
for j in range(i + 1, NUM_CIRCLES):
dx = positions[j][0] - positions[i][0]
dy = positions[j][1] - positions[i][1]
dist = math.hypot(dx, dy)
# if exactly overlapping, nudge and skip heavy computation
if dist <= 1e-8:
nudge_apart(i, j)
continue
if dist < 2 * RADIUS:
# compute normal
nx, ny = dx / dist, dy / dist
# relative velocity along normal
vi = velocities[i][0] * nx + velocities[i][1] * ny
vj = velocities[j][0] * nx + velocities[j][1] * ny
# exchange normal components (simple elastic)
vi, vj = vj, vi
# update velocities by adding change along normal
velocities[i] = (
velocities[i][0] + (vi - (velocities[i][0] * nx + velocities[i][1] * ny)) * nx,
velocities[i][1] + (vi - (velocities[i][0] * nx + velocities[i][1] * ny)) * ny,
)
velocities[j] = (
velocities[j][0] + (vj - (velocities[j][0] * nx + velocities[j][1] * ny)) * nx,
velocities[j][1] + (vj - (velocities[j][0] * nx + velocities[j][1] * ny)) * ny,
)
# --- Draw circles safely ---
for idx, p in enumerate(positions):
# robust unpacking
try:
x, y = p
except Exception:
# log invalid element, skip drawing but keep positions list structure intact
print(f"[WARN] positions[{idx}] is invalid (not a pair):", p)
continue
# validate numeric coordinates
if not (is_valid_coord(x) and is_valid_coord(y)):
print(f"[WARN] positions[{idx}] has non-finite coords:", x, y)
continue
# final safe draw
px, py = int(round(x)), int(round(y))
# clamp so pygame won't complain about absurd coords (optional)
px = max(0, min(WIDTH, px))
py = max(0, min(HEIGHT, py))
if reverse_mode:
if idx!=110:
pygame.draw.circle(screen, (200,200, 50), (px, py), max(1, int(RADIUS)))
else:
pygame.draw.circle(screen, (255,100, 100), (px, py), max(1, int(RADIUS)))
if reverse_mode:
time.sleep(sleeptime)
sleeptime+=.00015
pygame.display.flip()
time.sleep(6)
pygame.quit()
No comments:
Post a Comment