import pygame, sys, pymunk, time import math # Pygame and Pymunk setup pygame.init() info = pygame.display.Info() SCREEN_WIDTH = info.current_w SCREEN_HEIGHT = info.current_h screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN | pygame.SCALED) clock = pygame.time.Clock() space = pymunk.Space() space.gravity = (0, 980) apples = [] def create_apple(space, position=(SCREEN_WIDTH // 2, 0), radius=80): body = pymunk.Body(1, 100, body_type=pymunk.Body.DYNAMIC) body.position = position shape = pymunk.Circle(body, radius) shape.elasticity = 1 shape.collision_type = 1 space.add(body, shape) return shape def draw_apples(apples): for apple in apples: pos = (int(apple.body.position.x), int(apple.body.position.y)) pygame.draw.circle(screen, (0, 0, 0), pos, int(apple.radius)) def static_ball(space): body = pymunk.Body(body_type=pymunk.Body.STATIC) body.position = (SCREEN_WIDTH // 2, SCREEN_HEIGHT - 150) shape = pymunk.Circle(body, 50) shape.elasticity = 0.8 space.add(body, shape) shape.original_radius = shape.radius # Save the original radius shape.deform_time = 0 # Track the time for deformation return shape def draw_static_ball(balls): for ball in balls: pos = (int(ball.body.position.x), int(ball.body.position.y)) # Deform the ball when it's colliding if ball.deform_time > pygame.time.get_ticks(): radius = ball.original_radius * 0.6 # Make it squished else: radius = ball.original_radius # Restore to original size pygame.draw.circle(screen, (0, 0, 0), pos, int(radius)) def create_walls(space): static_lines = [ pymunk.Segment(space.static_body, (0, 0), (SCREEN_WIDTH, 0), 5), pymunk.Segment(space.static_body, (0, SCREEN_HEIGHT), (SCREEN_WIDTH, SCREEN_HEIGHT), 5), pymunk.Segment(space.static_body, (0, 0), (0, SCREEN_HEIGHT), 5), pymunk.Segment(space.static_body, (SCREEN_WIDTH, 0), (SCREEN_WIDTH, SCREEN_HEIGHT), 5), ] for line in static_lines: line.elasticity = 0.9 line.friction = 0.5 space.add(*static_lines) # Cast ray with reflection def cast_rays(space, origin, num_rays=60, ray_length=1000, max_bounces=3): angle_step = 360 / num_rays for i in range(num_rays): angle = i * angle_step radians = angle * (3.14159265 / 180) dx = ray_length * pymunk.Vec2d(1, 0).rotated(radians) end = origin + dx current_origin = origin current_direction = dx remaining_bounces = max_bounces color_intensity = 255 while remaining_bounces > 0: hit = space.segment_query_first(current_origin, current_origin + current_direction, 0, pymunk.ShapeFilter()) if hit: # Calculate normal and reflection normal = hit.normal # Reflect the vector using the formula R = V - 2 * (V . N) * N reflection = current_direction - 2 * current_direction.dot(normal) * normal # Draw the ray pygame.draw.line(screen, (255, 255 - color_intensity, 255 - color_intensity), current_origin, hit.point, 2) # Update the origin and direction for the next bounce current_origin = hit.point current_direction = reflection remaining_bounces -= 1 color_intensity = max(0, color_intensity - 85) # Darken with each bounce else: # No more collisions, draw the ray till the end point pygame.draw.line(screen, (200, 200, 200), current_origin, current_origin + current_direction, 1) break # Collision handler def on_apple_collision(arbiter, space, data): shape_a, shape_b = arbiter.shapes # Only allow duplication if apples are the same size if shape_a.radius != shape_b.radius: return True # Allow the normal collision behavior for different-sized apples # Remove original apples from space and apples list apples_to_remove = [shape_a, shape_b] for shape in apples_to_remove: if shape in apples: space.remove(shape, shape.body) apples.remove(shape) # Determine new radius (half of the smaller original) radius = shape_a.radius / 2 if radius >= 10: # Prevent apples from getting too small mid_point = (shape_a.body.position + shape_b.body.position) / 2 offset = pymunk.Vec2d(30, 0) # Create new apples with the smaller radius new_apple1 = create_apple(space, position=mid_point + offset, radius=radius) new_apple2 = create_apple(space, position=mid_point - offset, radius=radius) apples.append(new_apple1) apples.append(new_apple2) return False # Skip default physics response # Game setup apples.append(create_apple(space)) balls = [static_ball(space)] create_walls(space) # Collision handler setup handler = space.add_collision_handler(1, 1) handler.begin = on_apple_collision # Apple spawn control APPLE_COOLDOWN = 0.5 last_apple_time = 0 # Control spawning with L key spawn_enabled = False # Whether the L key has been pressed last_spawn_time = 0 # Time tracker for spawn interval SPAWN_INTERVAL = 1000 # 1 second interval (1000 ms) last_L_press_time = 0 # Track last time the L key was pressed to avoid repeated toggles # Main loop while True: dt = clock.tick(120) / 1000.0 for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): pygame.quit() sys.exit() keys = pygame.key.get_pressed() # Toggle spawning on L key press if keys[pygame.K_l] and pygame.time.get_ticks() - last_L_press_time > 300: # Prevent continuous toggling spawn_enabled = not spawn_enabled # Toggle spawn state last_L_press_time = pygame.time.get_ticks() # Update last press time # If spawning is enabled and enough time has passed, spawn a new ball if spawn_enabled and pygame.time.get_ticks() - last_spawn_time >= SPAWN_INTERVAL: new_ball = static_ball(space) balls.append(new_ball) last_spawn_time = pygame.time.get_ticks() # Reset spawn timer if keys[pygame.K_SPACE] and (time.time() - last_apple_time > APPLE_COOLDOWN): new_apple = create_apple(space) apples.append(new_apple) last_apple_time = time.time() if keys[pygame.K_a]: for apple in apples: apple.body.apply_force_at_local_point((-325, 0)) if keys[pygame.K_d]: for apple in apples: apple.body.apply_force_at_local_point((325, 0)) if keys[pygame.K_w]: for apple in apples: apple.body.apply_force_at_local_point((0, -2050)) screen.fill((217, 217, 217)) for apple in apples: cast_rays(space, apple.body.position, num_rays=60, ray_length=800) draw_apples(apples) draw_static_ball(balls) space.step(1 / 50) pygame.display.update()