Smiley - Python code

 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