import pygame
import random
import math

# Initialize Pygame
pygame.init()

# Set up display
dock_height = 150
WIDTH = 600
HEIGHT = WIDTH + dock_height  # 600 + 150 = 750
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Monte Carlo Pi Estimation")

# Fonts and colors
FONT = pygame.font.SysFont(None, 28)
SMALL_FONT = pygame.font.SysFont(None, 24)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (220, 20, 60)
BLUE = (65, 105, 225)
GRAY = (200, 200, 200)
DARK_GRAY = (50, 50, 50)

# Simulation variables
running = True
calculating = False
num_points = 0
points_generated = 0
inside_circle = 0
pi_estimate = 0
points_per_frame = 1000
points_per_second = 30000  # Default value
use_points_per_second = False  # Toggle between per frame and per second

# Input boxes and buttons
input_boxes = {
    'num_points': pygame.Rect(0, 0, 140, 32),
    'points_rate': pygame.Rect(0, 0, 140, 32),
}
inputs_active = {'num_points': False, 'points_rate': False}
user_texts = {'num_points': '', 'points_rate': ''}

# Pre-fill "Points per Frame/Second" input box with default value
user_texts['points_rate'] = str(points_per_frame if not use_points_per_second else points_per_second)

# Buttons
toggle_button = pygame.Rect(0, 0, 160, 32)
stop_button = pygame.Rect(0, 0, 80, 32)

# Surface for drawing points
point_surface = pygame.Surface((WIDTH, WIDTH))  # Visualization area
point_surface.fill(BLACK)

# Draw square and circle on the surface
pygame.draw.rect(point_surface, WHITE, (0, 0, WIDTH, WIDTH), 2)
pygame.draw.circle(point_surface, WHITE, (WIDTH // 2, WIDTH // 2), WIDTH // 2, 2)

clock = pygame.time.Clock()

# Positions and layout
x_label = 20
x_input = 200
elem_spacing = 40  # Vertical spacing between elements
start_y = WIDTH + 10  # 610

labels = {
    'num_points': 'Number of Points:',
    'points_rate': f"Points per {'Second' if use_points_per_second else 'Frame'}:",
}

y_positions = {
    'num_points': start_y,
    'points_rate': start_y + elem_spacing,
}

# Set positions for input boxes
for key in input_boxes:
    input_boxes[key].x = x_input
    input_boxes[key].y = y_positions[key]
    input_boxes[key].w = 140
    input_boxes[key].h = 32

# Main loop
while running:
    dt = clock.tick(60) / 1000  # delta time in seconds

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        # Input box events
        if event.type == pygame.MOUSEBUTTONDOWN:
            # Check for input box clicks
            for key in inputs_active:
                if input_boxes[key].collidepoint(event.pos):
                    inputs_active[key] = True
                else:
                    inputs_active[key] = False
            # Toggle button
            if toggle_button.collidepoint(event.pos):
                use_points_per_second = not use_points_per_second
                # Update label
                labels['points_rate'] = f"Points per {'Second' if use_points_per_second else 'Frame'}:"
                # Update displayed rate value
                current_rate = points_per_second if use_points_per_second else points_per_frame
                user_texts['points_rate'] = str(current_rate)
            # Stop button
            if stop_button.collidepoint(event.pos):
                calculating = False

        if event.type == pygame.KEYDOWN:
            for key, active in inputs_active.items():
                if active:
                    if event.key == pygame.K_RETURN:
                        if key == 'num_points' and user_texts['num_points'].isdigit():
                            num_points = int(user_texts['num_points'])
                            user_texts['num_points'] = ''
                            inputs_active[key] = False  # Deselect input box
                            calculating = True
                            points_generated = 0
                            inside_circle = 0
                            pi_estimate = 0
                            point_surface.fill(BLACK)
                            # Redraw the square and circle
                            pygame.draw.rect(point_surface, WHITE, (0, 0, WIDTH, WIDTH), 2)
                            pygame.draw.circle(point_surface, WHITE, (WIDTH // 2, WIDTH // 2), WIDTH // 2, 2)
                        elif key == 'points_rate' and user_texts['points_rate'].isdigit():
                            rate = int(user_texts['points_rate'])
                            if rate > 0:
                                if use_points_per_second:
                                    points_per_second = rate
                                else:
                                    points_per_frame = rate
                            inputs_active[key] = False  # Deselect input box
                            # Note: We do NOT clear user_texts['points_rate'], so it retains the entered value
                        else:
                            # Invalid input; deselect input box
                            inputs_active[key] = False
                    elif event.key == pygame.K_BACKSPACE:
                        user_texts[key] = user_texts[key][:-1]
                    else:
                        if event.unicode.isdigit():
                            user_texts[key] += event.unicode

    # Draw background
    window.fill(DARK_GRAY)
    # Draw point surface
    window.blit(point_surface, (0, 0))

    # Simulation calculation and drawing
    if calculating:
        if use_points_per_second:
            pts_this_frame = int(points_per_second * dt)
        else:
            pts_this_frame = points_per_frame

        for _ in range(min(pts_this_frame, num_points - points_generated)):
            x = random.uniform(-1, 1)
            y = random.uniform(-1, 1)
            distance = x ** 2 + y ** 2
            if distance <= 1:
                inside_circle += 1
                color = BLUE
            else:
                color = RED

            # Convert coordinate system to fit the window
            pixel_x = int((x + 1) * (WIDTH / 2))
            pixel_y = int((y + 1) * (WIDTH / 2))
            pygame.draw.circle(point_surface, color, (pixel_x, pixel_y), 2)
            points_generated += 1

        # Estimate pi
        pi_estimate = 4 * inside_circle / points_generated

        if points_generated >= num_points:
            calculating = False

    # Draw dock at the bottom
    dock_rect = pygame.Rect(0, WIDTH, WIDTH, dock_height)
    pygame.draw.rect(window, BLACK, dock_rect)

    # Input fields and labels
    for key in input_boxes:
        # Label
        label = FONT.render(labels[key], True, WHITE)
        window.blit(label, (x_label, y_positions[key] + 5))
        # Input box
        txt_surface = FONT.render(user_texts[key], True, BLACK)
        input_boxes[key].w = max(140, txt_surface.get_width() + 10)
        pygame.draw.rect(window, WHITE if inputs_active[key] else GRAY, input_boxes[key])
        window.blit(txt_surface, (input_boxes[key].x + 5, input_boxes[key].y + 5))
        pygame.draw.rect(window, WHITE, input_boxes[key], 2)

    # Positions for buttons (after rendering text)
    buttons_y = start_y + 2 * elem_spacing
    # Toggle button text and dimensions
    toggle_text = f"Use Points per {'Second' if not use_points_per_second else 'Frame'}"
    toggle_label = FONT.render(toggle_text, True, BLACK)
    toggle_button.w = max(160, toggle_label.get_width() + 10)
    toggle_button.h = 32
    toggle_button.x = x_input
    toggle_button.y = buttons_y

    # Stop button dimensions
    stop_text = FONT.render("Stop", True, BLACK)
    stop_button.w = max(80, stop_text.get_width() + 20)
    stop_button.h = 32
    # Place stop button to the right of toggle button with proper spacing
    stop_button.x = toggle_button.x + toggle_button.w + 20
    stop_button.y = buttons_y

    # Draw Toggle button
    pygame.draw.rect(window, GRAY, toggle_button)
    window.blit(toggle_label, (toggle_button.x + 5, toggle_button.y + 5))
    pygame.draw.rect(window, WHITE, toggle_button, 2)

    # Draw Stop button
    pygame.draw.rect(window, GRAY, stop_button)
    window.blit(stop_text, (stop_button.x + 10, stop_button.y + 5))
    pygame.draw.rect(window, WHITE, stop_button, 2)

    # Display π estimation and stats
    if pi_estimate:
        pi_text = FONT.render(f"Estimated Pi: {pi_estimate:.6f}", True, WHITE)
        window.blit(pi_text, (x_label, buttons_y + 40))
    if num_points:
        progress_text = FONT.render(f"Points: {points_generated}/{num_points}", True, WHITE)
        window.blit(progress_text, (x_label, buttons_y + 70))

    pygame.display.flip()

pygame.quit()


This is a monte-carlo pi estimation visualized with pygame. Can you try to recreate it in HTML + JS?