Created
April 9, 2025 10:15
-
-
Save shricodev/6631aa77a360a43452528d9778d636da to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import math | |
| import sys | |
| import numpy as np | |
| import pygame | |
| from pygame.locals import * | |
| # Initialize pygame | |
| pygame.init() | |
| # Constants | |
| WIDTH, HEIGHT = 800, 600 | |
| FPS = 60 | |
| WHITE = (255, 255, 255) | |
| RED = (255, 0, 0) | |
| BLACK = (0, 0, 0) | |
| GRAVITY = 0.5 | |
| FRICTION = 0.99 | |
| RESTITUTION = 0.8 # Coefficient of restitution (bounciness) | |
| HEX_RADIUS = 200 # Radius of the hexagon | |
| # Set up the display | |
| screen = pygame.display.set_mode((WIDTH, HEIGHT)) | |
| pygame.display.set_caption("Bouncing Ball in Spinning Hexagon") | |
| clock = pygame.time.Clock() | |
| class Ball: | |
| def __init__(self, x, y, radius=15): | |
| self.x = x | |
| self.y = y | |
| self.radius = radius | |
| self.vx = 0 | |
| self.vy = 0 | |
| def update(self): | |
| # Apply gravity | |
| self.vy += GRAVITY | |
| # Apply friction | |
| self.vx *= FRICTION | |
| self.vy *= FRICTION | |
| # Update position | |
| self.x += self.vx | |
| self.y += self.vy | |
| def draw(self): | |
| pygame.draw.circle(screen, RED, (int(self.x), int(self.y)), self.radius) | |
| def get_hexagon_points(center_x, center_y, radius, angle=0): | |
| points = [] | |
| for i in range(6): | |
| # Calculate the angle for each vertex (60 degrees apart) | |
| theta = ( | |
| angle + math.pi / 3 * i + math.pi / 6 | |
| ) # Adding π/6 to rotate the hexagon to make it rest on a side | |
| x = center_x + radius * math.cos(theta) | |
| y = center_y + radius * math.sin(theta) | |
| points.append((x, y)) | |
| return points | |
| def line_intersection(line1, line2): | |
| """Find the intersection point of two lines""" | |
| (x1, y1), (x2, y2) = line1 | |
| (x3, y3), (x4, y4) = line2 | |
| denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1) | |
| if denom == 0: # lines are parallel | |
| return None | |
| ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom | |
| ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom | |
| # If ua and ub are between 0-1, lines intersect | |
| if 0 <= ua <= 1 and 0 <= ub <= 1: | |
| x = x1 + ua * (x2 - x1) | |
| y = y1 + ua * (y2 - y1) | |
| return (x, y) | |
| return None | |
| def point_segment_distance(point, segment): | |
| """Calculate distance from point to line segment""" | |
| (px, py), ((x1, y1), (x2, y2)) = point, segment | |
| # Vector from p to line endpoints | |
| p1_p = px - x1 | |
| p1_p2 = x2 - x1 | |
| p2_p = px - x2 | |
| p1_p_y = py - y1 | |
| p1_p2_y = y2 - y1 | |
| p2_p_y = py - y2 | |
| # Dot product to determine projection | |
| dot = p1_p * p1_p2 + p1_p_y * p1_p2_y | |
| len_sq = p1_p2 * p1_p2 + p1_p2_y * p1_p2_y | |
| # Parametric position of projection | |
| param = dot / len_sq if len_sq != 0 else -1 | |
| # Determine nearest point on segment | |
| if param < 0: | |
| xx, yy = x1, y1 | |
| elif param > 1: | |
| xx, yy = x2, y2 | |
| else: | |
| xx = x1 + param * p1_p2 | |
| yy = y1 + param * p1_p2_y | |
| dx = px - xx | |
| dy = py - yy | |
| distance = math.sqrt(dx * dx + dy * dy) | |
| return distance, (xx, yy) | |
| def reflect_velocity(normal, velocity, restitution): | |
| """Calculate reflected velocity after bouncing off a surface""" | |
| normal = np.array(normal) | |
| normal = normal / np.linalg.norm(normal) # Normalize | |
| velocity = np.array(velocity) | |
| dot_product = np.dot(velocity, normal) | |
| if dot_product < 0: # Only reflect if ball is moving toward the wall | |
| reflected_velocity = velocity - (1 + restitution) * dot_product * normal | |
| return reflected_velocity.tolist() | |
| return velocity.tolist() | |
| # Create ball | |
| ball = Ball(WIDTH // 2, HEIGHT // 2 - 100) | |
| rotation_angle = 0 | |
| rotation_speed = 0.03 # radians per frame | |
| def main(): | |
| global rotation_angle, rotation_speed | |
| while True: | |
| for event in pygame.event.get(): | |
| if event.type == QUIT: | |
| pygame.quit() | |
| sys.exit() | |
| elif event.type == KEYDOWN: | |
| if event.key == K_LEFT: | |
| rotation_speed = min(0.1, rotation_speed + 0.01) | |
| elif event.key == K_RIGHT: | |
| rotation_speed = max(-0.1, rotation_speed - 0.01) | |
| elif event.key == K_r: # Reset ball position | |
| ball.x, ball.y = WIDTH // 2, HEIGHT // 2 - 100 | |
| ball.vx, ball.vy = 0, 0 | |
| # Update rotation | |
| rotation_angle += rotation_speed | |
| # Get current hexagon points | |
| hex_points = get_hexagon_points( | |
| WIDTH // 2, HEIGHT // 2, HEX_RADIUS, int(rotation_angle) | |
| ) | |
| # Check for collisions with hexagon walls | |
| for i in range(len(hex_points)): | |
| wall_start = hex_points[i] | |
| wall_end = hex_points[(i + 1) % len(hex_points)] | |
| # Check distance from ball to wall | |
| distance, closest_point = point_segment_distance( | |
| (ball.x, ball.y), (wall_start, wall_end) | |
| ) | |
| if distance < ball.radius: | |
| # Collision detected - calculate normal vector | |
| wall_vec = (wall_end[0] - wall_start[0], wall_end[1] - wall_start[1]) | |
| normal = (-wall_vec[1], wall_vec[0]) # Perpendicular vector | |
| # Reflect velocity | |
| reflected_v = reflect_velocity(normal, (ball.vx, ball.vy), RESTITUTION) | |
| ball.vx, ball.vy = reflected_v | |
| # Move ball outside the wall to prevent sticking | |
| penetration = ball.radius - distance | |
| normal_x = normal[0] / math.sqrt(normal[0] ** 2 + normal[1] ** 2) | |
| normal_y = normal[1] / math.sqrt(normal[0] ** 2 + normal[1] ** 2) | |
| ball.x += normal_x * penetration | |
| ball.y += normal_y * penetration | |
| # Update ball physics | |
| ball.update() | |
| # Draw everything | |
| screen.fill(BLACK) | |
| # Draw hexagon | |
| pygame.draw.polygon(screen, WHITE, hex_points, 2) | |
| # Draw ball | |
| ball.draw() | |
| # Display controls | |
| font = pygame.font.SysFont(None, 24) | |
| text = font.render("LEFT/RIGHT: Rotate Hexagon | R: Reset Ball", True, WHITE) | |
| screen.blit(text, (10, 10)) | |
| pygame.display.flip() | |
| clock.tick(FPS) | |
| if __name__ == "__main__": | |
| main() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output: https://youtu.be/3-yklc-AUnA