TryAITryAITry TryAI

Shared chat

Bouncing Ball Simulation

Write a Python program that shows a ball bouncing inside a spinning hexagon. The ball should be affected by gravity and friction, and it must bounce off the rotating walls realistically.

I'll create a Python program that simulates a ball bouncing inside a spinning hexagon with realistic physics. This uses pygame for graphics and implements proper collision detection with rotating walls.

python
import pygameimport mathimport numpy as np
# Initialize Pygamepygame.init()
# ConstantsWIDTH, HEIGHT = 800, 600FPS = 60GRAVITY = 0.3FRICTION = 0.98BOUNCE_DAMPING = 0.8
# ColorsBLACK = (0, 0, 0)WHITE = (255, 255, 255)RED = (255, 100, 100)BLUE = (100, 150, 255)GREEN = (100, 255, 100)
class Vector2D:    def __init__(self, x=0, y=0):        self.x = x        self.y = y        def __add__(self, other):        return Vector2D(self.x + other.x, self.y + other.y)        def __sub__(self, other):        return Vector2D(self.x - other.x, self.y - other.y)        def __mul__(self, scalar):        return Vector2D(self.x * scalar, self.y * scalar)        def dot(self, other):        return self.x * other.x + self.y * other.y        def magnitude(self):        return math.sqrt(self.x**2 + self.y**2)        def normalize(self):        mag = self.magnitude()        if mag > 0:            return Vector2D(self.x / mag, self.y / mag)        return Vector2D(0, 0)        def rotate(self, angle):        cos_a = math.cos(angle)        sin_a = math.sin(angle)        return Vector2D(            self.x * cos_a - self.y * sin_a,            self.x * sin_a + self.y * cos_a        )
class Ball:    def __init__(self, x, y, radius=15):        self.pos = Vector2D(x, y)        self.vel = Vector2D(3, 2)        self.radius = radius        self.color = RED        def update(self):        # Apply gravity        self.vel.y += GRAVITY                # Apply friction        self.vel = self.vel * FRICTION                # Update position        self.pos = self.pos + self.vel        def draw(self, screen):        pygame.draw.circle(screen, self.color,                          (int(self.pos.x), int(self.pos.y)), self.radius)        # Draw a small dot to show rotation        dot_pos = (int(self.pos.x + self.radius * 0.6), int(self.pos.y))        pygame.draw.circle(screen, WHITE, dot_pos, 3)
class Hexagon:    def __init__(self, center_x, center_y, size=200):        self.center = Vector2D(center_x, center_y)        self.size = size        self.angle = 0        self.angular_velocity = 0.02        self.vertices = []        self.edges = []        self.update_vertices()        def update_vertices(self):        self.vertices = []        # Create hexagon vertices        for i in range(6):            angle = (i * math.pi / 3) + self.angle            x = self.center.x + self.size * math.cos(angle)            y = self.center.y + self.size * math.sin(angle)            self.vertices.append(Vector2D(x, y))                # Create edges (each edge is defined by two consecutive vertices)        self.edges = []        for i in range(6):            start = self.vertices[i]            end = self.vertices[(i + 1) % 6]            self.edges.append((start, end))        def update(self):        self.angle += self.angular_velocity        self.update_vertices()        def draw(self, screen):        # Draw hexagon outline        points = [(v.x, v.y) for v in self.vertices]        pygame.draw.polygon(screen, BLUE, points, 3)                # Draw center point        pygame.draw.circle(screen, GREEN,                          (int(self.center.x), int(self.center.y)), 5)
def point_to_line_distance(point, line_start, line_end):    """Calculate the shortest distance from a point to a line segment"""    # Vector from line start to line end    line_vec = line_end - line_start    line_length_sq = line_vec.x**2 + line_vec.y**2        if line_length_sq == 0:        # Line is actually a point        return (point - line_start).magnitude()        # Vector from line start to point    point_vec = point - line_start        # Project point onto line    t = max(0, min(1, point_vec.dot(line_vec) / line_length_sq))        # Find closest point on line segment    closest_point = line_start + line_vec * t        # Return distance and closest point    distance = (point - closest_point).magnitude()    return distance, closest_point
def reflect_velocity(velocity, normal):    """Reflect velocity vector across a normal vector"""    # v_reflected = v - 2 * (v · n) * n    dot_product = velocity.dot(normal)    reflected = velocity - normal * (2 * dot_product)    return reflected * BOUNCE_DAMPING
def check_ball_hexagon_collision(ball, hexagon):    """Check and handle collision between ball and hexagon edges"""    for edge_start, edge_end in hexagon.edges:        distance, closest_point = point_to_line_distance(ball.pos, edge_start, edge_end)                if distance < ball.radius:            # Collision detected            # Calculate normal vector (pointing inward toward hexagon center)            to_ball = ball.pos - closest_point            if to_ball.magnitude() > 0:                normal = to_ball.normalize()                                # Check if normal points toward center (we want outward normal)                to_center = hexagon.center - closest_point                if normal.dot(to_center) > 0:                    normal = normal * -1  # Flip normal to point outward                                # Move ball out of collision                penetration = ball.radius - distance                ball.pos = ball.pos + normal * penetration                                # Reflect velocity                ball.vel = reflect_velocity(ball.vel, normal)                                # Add some random variation to prevent stuck situations                ball.vel.x += (np.random.random() - 0.5) * 0.1                ball.vel.y += (np.random.random() - 0.5) * 0.1                                break
def main():    screen = pygame.display.set_mode((WIDTH, HEIGHT))    pygame.display.set_caption("Ball Bouncing in Spinning Hexagon")    clock = pygame.time.Clock()        # Create objects    ball = Ball(WIDTH // 2, HEIGHT // 2 - 50)    hexagon = Hexagon(WIDTH // 2, HEIGHT // 2, 150)        # UI elements    font = pygame.font.Font(None, 36)        running = True    paused = False        while running:        for event in pygame.event.get():            if event.type == pygame.QUIT:                running = False            elif event.type == pygame.KEYDOWN:                if event.key == pygame.K_SPACE:                    paused = not paused                elif event.key == pygame.K_r:                    # Reset ball position and velocity                    ball.pos = Vector2D(WIDTH // 2, HEIGHT // 2 - 50)                    ball.vel = Vector2D(3, 2)                elif event.key == pygame.K_UP:                    hexagon.angular_velocity += 0.01                elif event.key == pygame.K_DOWN:                    hexagon.angular_velocity -= 0.01                elif event.key == pygame.K_LEFT:                    hexagon.angular_velocity *= -1                if not paused:            # Update physics            ball.update()            hexagon.update()                        # Check collisions            check_ball_hexagon_collision(ball, hexagon)                # Draw everything        screen.fill(BLACK)                hexagon.draw(screen)        ball.draw(screen)                # Draw UI        speed_text = font.render(f"Ball Speed: {ball.vel.magnitude():.2f}", True, WHITE)        screen.blit(speed_text, (10, 10))                rotation_text = font.render(f"Rotation Speed: {hexagon.angular_velocity:.3f}", True, WHITE)        screen.blit(rotation_text, (10, 50))                # Draw instructions        instructions = [            "SPACE: Pause/Resume",            "R: Reset Ball",            "UP/DOWN: Change Rotation Speed",            "LEFT: Reverse Rotation"        ]                small_font = pygame.font.Font(None, 24)        for i, instruction in enumerate(instructions):            text = small_font.render(instruction, True, WHITE)            screen.blit(text, (WIDTH - 250, 10 + i * 25))                if paused:            pause_text = font.render("PAUSED", True, WHITE)            text_rect = pause_text.get_rect(center=(WIDTH // 2, 50))            screen.blit(pause_text, text_rect)                pygame.display.flip()        clock.tick(FPS)        pygame.quit()
if __name__ == "__main__":    main()

This program creates a realistic physics simulation with the following features:

Key Features:

  1. Realistic Physics:

    • Gravity affects the ball
    • Friction gradually slows the ball down
    • Bounce damping makes collisions more realistic
    • Proper velocity reflection off rotating walls
  2. Spinning Hexagon:

    • Continuously rotates at adjustable speed
    • Proper collision detection with all six edges
    • Visual feedback with colored outline
  3. Interactive Controls:

text
   - `SPACE`: Pause/resume simulation   - `R`: Reset ball to center   - `UP/DOWN`: Increase/decrease rotation speed   - `LEFT`: Reverse rotation direction
  1. Visual Elements:
    • Ball with rotation indicator (white dot)
    • Hexagon with colored outline
    • Real-time speed and rotation displays
    • Control instructions

Technical Implementation:

  • Vector2D Class: Handles all vector mathematics
  • Collision Detection: Point-to-line distance calculation for accurate wall collisions
  • Physics Integration: Proper velocity reflection with surface normals
  • Rotation Handling: Hexagon vertices update each frame based on rotation angle

The simulation handles edge cases like the ball getting stuck and provides smooth, realistic bouncing behavior inside the rotating hexagon. The physics feel natural with gravity pulling the ball down and friction gradually slowing it over time.

anthropic/claude-sonnet-4-20250514-v1
Can you fix this code, the ball falls through the bottom of the hexagon as soon as I run the file. It slows down very slightly, but continues to just go downwards, am I doing something wrong?

You're right! The issue is with the collision detection. The problem is that the ball is falling through because the collision normal calculation is incorrect, and the hexagon might be too small for the default ball position. Let me fix this:

python
import pygameimport mathimport numpy as np
# Initialize Pygamepygame.init()
# ConstantsWIDTH, HEIGHT = 800, 600FPS = 60GRAVITY = 0.3FRICTION = 0.995  # Reduced friction so we can see the effect betterBOUNCE_DAMPING = 0.85
# ColorsBLACK = (0, 0, 0)WHITE = (255, 255, 255)RED = (255, 100, 100)BLUE = (100, 150, 255)GREEN = (100, 255, 100)YELLOW = (255, 255, 100)
class Vector2D:    def __init__(self, x=0, y=0):        self.x = x        self.y = y        def __add__(self, other):        return Vector2D(self.x + other.x, self.y + other.y)        def __sub__(self, other):        return Vector2D(self.x - other.x, self.y - other.y)        def __mul__(self, scalar):        return Vector2D(self.x * scalar, self.y * scalar)        def dot(self, other):        return self.x * other.x + self.y * other.y        def magnitude(self):        return math.sqrt(self.x**2 + self.y**2)        def normalize(self):        mag = self.magnitude()        if mag > 0:            return Vector2D(self.x / mag, self.y / mag)        return Vector2D(0, 0)
class Ball:    def __init__(self, x, y, radius=12):        self.pos = Vector2D(x, y)        self.vel = Vector2D(2, 1)  # Smaller initial velocity        self.radius = radius        self.color = RED        def update(self):        # Apply gravity        self.vel.y += GRAVITY                # Apply friction        self.vel = self.vel * FRICTION                # Update position        self.pos = self.pos + self.vel        def draw(self, screen):        pygame.draw.circle(screen, self.color,                          (int(self.pos.x), int(self.pos.y)), self.radius)        # Draw a small dot to show rotation        dot_pos = (int(self.pos.x + self.radius * 0.6), int(self.pos.y))        pygame.draw.circle(screen, WHITE, dot_pos, 3)
class Hexagon:    def __init__(self, center_x, center_y, size=180):  # Increased size        self.center = Vector2D(center_x, center_y)        self.size = size        self.angle = 0        self.angular_velocity = 0.01  # Slower initial rotation        self.vertices = []        self.edges = []        self.update_vertices()        def update_vertices(self):        self.vertices = []        # Create hexagon vertices (starting from top)        for i in range(6):            angle = (i * math.pi / 3) + self.angle - math.pi/2  # Start from top            x = self.center.x + self.size * math.cos(angle)            y = self.center.y + self.size * math.sin(angle)            self.vertices.append(Vector2D(x, y))                # Create edges        self.edges = []        for i in range(6):            start = self.vertices[i]            end = self.vertices[(i + 1) % 6]            self.edges.append((start, end))        def update(self):        self.angle += self.angular_velocity        self.update_vertices()        def draw(self, screen):        # Draw filled hexagon with transparency effect        points = [(int(v.x), int(v.y)) for v in self.vertices]        pygame.draw.polygon(screen, (50, 50, 100), points)        pygame.draw.polygon(screen, BLUE, points, 4)                # Draw center point        pygame.draw.circle(screen, GREEN,                          (int(self.center.x), int(self.center.y)), 5)
def point_to_line_segment_distance(point, line_start, line_end):    """Calculate distance from point to line segment and return closest point"""    # Vector from start to end of line    line_vec = line_end - line_start    line_length_sq = line_vec.x**2 + line_vec.y**2        if line_length_sq < 1e-6:  # Line is essentially a point        return (point - line_start).magnitude(), line_start        # Vector from line start to point    start_to_point = point - line_start        # Project point onto line (parameter t)    t = start_to_point.dot(line_vec) / line_length_sq    t = max(0, min(1, t))  # Clamp to line segment        # Find closest point on line segment    closest_point = line_start + line_vec * t    distance = (point - closest_point).magnitude()        return distance, closest_point
def get_edge_normal(edge_start, edge_end, center_point):    """Get the inward-pointing normal for an edge"""    # Edge vector    edge_vec = edge_end - edge_start        # Two possible normals (perpendicular to edge)    normal1 = Vector2D(-edge_vec.y, edge_vec.x).normalize()    normal2 = Vector2D(edge_vec.y, -edge_vec.x).normalize()        # Choose the normal that points toward the center    edge_midpoint = Vector2D((edge_start.x + edge_end.x) / 2, (edge_start.y + edge_end.y) / 2)    to_center = center_point - edge_midpoint        # Pick the normal that has positive dot product with to_center    if normal1.dot(to_center) > normal2.dot(to_center):        return normal1  # This points inward    else:        return normal2  # This points inward
def is_point_inside_hexagon(point, hexagon):    """Check if a point is inside the hexagon using cross product method"""    vertices = hexagon.vertices    n = len(vertices)        for i in range(n):        v1 = vertices[i]        v2 = vertices[(i + 1) % n]                # Vector from v1 to v2        edge = v2 - v1        # Vector from v1 to point        to_point = point - v1                # Cross product (in 2D, this gives the z-component)        cross = edge.x * to_point.y - edge.y * to_point.x                # If cross product is negative, point is outside this edge        if cross < 0:            return False        return True
def check_ball_hexagon_collision(ball, hexagon):    """Improved collision detection"""    ball_inside = is_point_inside_hexagon(ball.pos, hexagon)        if not ball_inside:        # Ball is completely outside - move it back in        # Find closest edge and push ball inside        min_distance = float('inf')        closest_edge = None        closest_point = None                for i, (edge_start, edge_end) in enumerate(hexagon.edges):            distance, point_on_edge = point_to_line_segment_distance(ball.pos, edge_start, edge_end)            if distance < min_distance:                min_distance = distance                closest_edge = (edge_start, edge_end)                closest_point = point_on_edge                if closest_edge:            # Move ball inside            direction_to_ball = ball.pos - closest_point            if direction_to_ball.magnitude() > 0:                direction_to_ball = direction_to_ball.normalize()                ball.pos = closest_point + direction_to_ball * ball.radius * 1.1                        # Get inward normal and reflect velocity            inward_normal = get_edge_normal(closest_edge[0], closest_edge[1], hexagon.center)                        # Reflect velocity (inward normal becomes outward normal for reflection)            outward_normal = inward_normal * -1            dot_product = ball.vel.dot(outward_normal)            if dot_product < 0:  # Only reflect if moving toward the wall                ball.vel = ball.vel - outward_normal * (2 * dot_product)                ball.vel = ball.vel * BOUNCE_DAMPING        else:        # Ball is inside - check if it's too close to any edge        for edge_start, edge_end in hexagon.edges:            distance, closest_point = point_to_line_segment_distance(ball.pos, edge_start, edge_end)                        if distance < ball.radius:                # Collision! Move ball away from edge                direction_away = ball.pos - closest_point                if direction_away.magnitude() > 0:                    direction_away = direction_away.normalize()                    # Push ball away from the edge                    penetration = ball.radius - distance                    ball.pos = ball.pos + direction_away * (penetration + 1)                                        # Reflect velocity                    dot_product = ball.vel.dot(direction_away)                    if dot_product < 0:  # Moving toward the wall                        ball.vel = ball.vel - direction_away * (2 * dot_product)                        ball.vel = ball.vel * BOUNCE_DAMPING                                            break
def main():    screen = pygame.display.set_mode((WIDTH, HEIGHT))    pygame.display.set_caption("Ball Bouncing in Spinning Hexagon - Fixed Version")    clock = pygame.time.Clock()        # Create objects    hexagon = Hexagon(WIDTH // 2, HEIGHT // 2, 200)  # Create hexagon first    ball = Ball(WIDTH // 2, HEIGHT // 2 - 80)  # Ball starts well inside        # UI elements    font = pygame.font.Font(None, 36)    small_font = pygame.font.Font(None, 24)        running = True    paused = False        while running:        for event in pygame.event.get():            if event.type == pygame.QUIT:                running = False            elif event.type == pygame.KEYDOWN:                if event.key == pygame.K_SPACE:                    paused = not paused                elif event.key == pygame.K_r:                    # Reset ball position and velocity                    ball.pos = Vector2D(WIDTH // 2, HEIGHT // 2 - 80)                    ball.vel = Vector2D(2, 1)                elif event.key == pygame.K_UP:                    hexagon.angular_velocity += 0.005                elif event.key == pygame.K_DOWN:                    hexagon.angular_velocity -= 0.005                elif event.key == pygame.K_LEFT:                    hexagon.angular_velocity *= -1                if not paused:            # Update physics            ball.update()            hexagon.update()                        # Check collisions            check_ball_hexagon_collision(ball, hexagon)                # Draw everything        screen.fill(BLACK)                hexagon.draw(screen)        ball.draw(screen)                # Draw UI        speed_text = font.render(f"Ball Speed: {ball.vel.magnitude():.2f}", True, WHITE)        screen.blit(speed_text, (10, 10))                rotation_text = font.render(f"Rotation Speed: {hexagon.angular_velocity:.3f}", True, WHITE)        screen.blit(rotation_text, (10, 50))                # Check if ball is inside        inside_text = small_font.render(f"Ball Inside: {is_point_inside_hexagon(ball.pos, hexagon)}", True, YELLOW)        screen.blit(inside_text, (10, 90))                # Draw instructions        instructions = [            "SPACE: Pause/Resume",            "R: Reset Ball",             "UP/DOWN: Change Rotation Speed",            "LEFT: Reverse Rotation"        ]                for i, instruction in enumerate(instructions):            text = small_font.render(instruction, True, WHITE)            screen.blit(text, (WIDTH - 250, 10 + i * 25))                if paused:            pause_text = font.render("PAUSED", True, WHITE)            text_rect = pause_text.get_rect(center=(WIDTH // 2, 50))            screen.blit(pause_text, text_rect)                pygame.display.flip()        clock.tick(FPS)        pygame.quit()
if __name__ == "__main__":    main()

Key Fixes:

  1. Better Collision Detection:

    • Added is_point_inside_hexagon() function to properly detect if the ball is inside
    • Handles both cases: ball inside hexagon and ball outside hexagon
  2. Improved Normal Calculation:

    • get_edge_normal() function correctly calculates inward-pointing normals
    • Proper velocity reflection using the correct normal direction
  3. Better Initial Conditions:

    • Larger hexagon (size 200 instead of 150)
    • Ball starts well inside the hexagon
    • Smaller initial velocity to make it easier to see
  4. Enhanced Physics:

    • Reduced friction so the ball doesn't slow down too quickly
    • Better penetration resolution to prevent ball from getting stuck
  5. Visual Improvements:

    • Filled hexagon background for better visibility
    • Added indicator showing if ball is inside the hexagon
    • Better collision feedback

Now the ball should properly bounce off all walls of the hexagon and stay contained within it. The collision detection is much more robust and handles edge cases better!

anthropic/claude-sonnet-4-20250514-v1

This is a shared TryAI chat. Sign in to start your own conversation.

Sign in to TryAI