import math
import random
import tkinter as tk
from tkinter import ttk, messagebox

class Molecule:
    __slots__ = ('x', 'y', 'vx', 'vy', 'radius', 'item')
    def __init__(self, x, y, vx, vy, radius, item):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.radius = radius
        self.item = item

class GasApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Молекулы идеального газа")
        self.geometry("900x700")
        self.molecules = []
        self.running = False
        self.paused = False
        self.after_id = None
        self.diameter = 5
        self.radius = self.diameter / 2
        self._create_widgets()
        self.protocol("WM_DELETE_WINDOW", self._on_close)

    def _create_widgets(self):
        control_frame = ttk.Frame(self, padding=5)
        control_frame.pack(side=tk.TOP, fill=tk.X)
        ttk.Label(control_frame, text="Количество:").pack(side=tk.LEFT, padx=(0, 5))
        self.count_var = tk.IntVar(value=100)
        count_spin = ttk.Spinbox(control_frame, from_=1, to=500, textvariable=self.count_var, width=8)
        count_spin.pack(side=tk.LEFT, padx=(0, 15))
        ttk.Label(control_frame, text="Скорость:").pack(side=tk.LEFT, padx=(0, 5))
        self.speed_var = tk.DoubleVar(value=3.0)
        speed_spin = ttk.Spinbox(control_frame, from_=0.5, to=50.0, increment=0.5, textvariable=self.speed_var, width=8)
        speed_spin.pack(side=tk.LEFT, padx=(0, 15))
        self.start_button = ttk.Button(control_frame, text="Запустить", command=self.start_simulation)
        self.start_button.pack(side=tk.LEFT, padx=5)
        self.pause_button = ttk.Button(control_frame, text="Пауза", command=self.toggle_pause, state=tk.DISABLED)
        self.pause_button.pack(side=tk.LEFT, padx=5)
        self.clear_button = ttk.Button(control_frame, text="Очистить", command=self.clear_simulation)
        self.clear_button.pack(side=tk.LEFT, padx=5)
        self.canvas = tk.Canvas(self, bg='white', bd=2, relief=tk.SUNKEN)
        self.canvas.pack(fill=tk.BOTH, expand=True)

    def start_simulation(self):
        if self.running:
            self.stop_animation()
            self.clear_canvas()
        try:
            count = int(self.count_var.get())
            speed = float(self.speed_var.get())
        except (ValueError, tk.TclError):
            messagebox.showerror("Ошибка", "Введите корректные числовые значения.")
            return
        if count < 1 or count > 500:
            messagebox.showerror("Ошибка", "Количество должно быть от 1 до 500.")
            return
        if speed <= 0:
            messagebox.showerror("Ошибка", "Скорость должна быть положительной.")
            return
        self.canvas.update_idletasks()
        width = self.canvas.winfo_width()
        height = self.canvas.winfo_height()
        if width < 20 or height < 20:
            width, height = 800, 600
        self._create_molecules(count, speed, width, height)
        self.running = True
        self.paused = False
        self.pause_button.config(text="Пауза", state=tk.NORMAL)
        self.start_button.config(text="Перезапустить")
        if self.after_id is None:
            self.after_id = self.after(16, self._animate)

    def _create_molecules(self, count, speed, width, height):
        self.clear_canvas()
        self.molecules = []
        cols = max(1, int(math.ceil(math.sqrt(count * width / height))))
        rows = max(1, int(math.ceil(count / cols)))
        cell_w = width / cols
        cell_h = height / rows
        spacing = self.diameter + 2
        placed = 0
        for r in range(rows):
            for c in range(cols):
                if placed >= count:
                    break
                cx = c * cell_w + cell_w / 2
                cy = r * cell_h + cell_h / 2
                offset_x = random.uniform(-min(cell_w, spacing) / 4, min(cell_w, spacing) / 4)
                offset_y = random.uniform(-min(cell_h, spacing) / 4, min(cell_h, spacing) / 4)
                x = max(self.radius, min(width - self.radius, cx + offset_x))
                y = max(self.radius, min(height - self.radius, cy + offset_y))
                angle = random.uniform(0, 2 * math.pi)
                vx = speed * math.cos(angle)
                vy = speed * math.sin(angle)
                item = self.canvas.create_oval(x - self.radius, y - self.radius,
                                               x + self.radius, y + self.radius,
                                               fill='blue', outline='')
                self.molecules.append(Molecule(x, y, vx, vy, self.radius, item))
                placed += 1
            if placed >= count:
                break

    def toggle_pause(self):
        if not self.running:
            return
        self.paused = not self.paused
        self.pause_button.config(text="Продолжить" if self.paused else "Пауза")

    def clear_simulation(self):
        self.stop_animation()
        self.clear_canvas()
        self.molecules = []
        self.start_button.config(text="Запустить")
        self.pause_button.config(text="Пауза", state=tk.DISABLED)

    def stop_animation(self):
        self.running = False
        self.paused = False

    def clear_canvas(self):
        self.canvas.delete("all")

    def _animate(self):
        if self.running and not self.paused:
            self._update_simulation()
        self.after_id = self.after(16, self._animate)

    def _update_simulation(self):
        width = self.canvas.winfo_width()
        height = self.canvas.winfo_height()
        if width < 20 or height < 20:
            return
        for mol in self.molecules:
            mol.x += mol.vx
            mol.y += mol.vy
            if mol.x - mol.radius < 0:
                mol.x = mol.radius
                mol.vx = -mol.vx
            elif mol.x + mol.radius > width:
                mol.x = width - mol.radius
                mol.vx = -mol.vx
            if mol.y - mol.radius < 0:
                mol.y = mol.radius
                mol.vy = -mol.vy
            elif mol.y + mol.radius > height:
                mol.y = height - mol.radius
                mol.vy = -mol.vy
        n = len(self.molecules)
        for i in range(n):
            mi = self.molecules[i]
            for j in range(i + 1, n):
                mj = self.molecules[j]
                dx = mi.x - mj.x
                dy = mi.y - mj.y
                dist_sq = dx * dx + dy * dy
                min_dist = mi.radius + mj.radius
                if dist_sq < min_dist * min_dist:
                    if dist_sq < 1e-12:
                        angle = random.uniform(0, 2 * math.pi)
                        dx = math.cos(angle) * min_dist
                        dy = math.sin(angle) * min_dist
                        dist_sq = dx * dx + dy * dy
                    dist = math.sqrt(dist_sq)
                    nx = dx / dist
                    ny = dy / dist
                    dvx = mi.vx - mj.vx
                    dvy = mi.vy - mj.vy
                    dvn = dvx * nx + dvy * ny
                    if dvn > 0:
                        mi.vx -= dvn * nx
                        mi.vy -= dvn * ny
                        mj.vx += dvn * nx
                        mj.vy += dvn * ny
                    overlap = min_dist - dist
                    mi.x += nx * overlap / 2
                    mi.y += ny * overlap / 2
                    mj.x -= nx * overlap / 2
                    mj.y -= ny * overlap / 2
                    if mi.x - mi.radius < 0:
                        mi.x = mi.radius
                        mi.vx = abs(mi.vx)
                    elif mi.x + mi.radius > width:
                        mi.x = width - mi.radius
                        mi.vx = -abs(mi.vx)
                    if mi.y - mi.radius < 0:
                        mi.y = mi.radius
                        mi.vy = abs(mi.vy)
                    elif mi.y + mi.radius > height:
                        mi.y = height - mi.radius
                        mi.vy = -abs(mi.vy)
                    if mj.x - mj.radius < 0:
                        mj.x = mj.radius
                        mj.vx = abs(mj.vx)
                    elif mj.x + mj.radius > width:
                        mj.x = width - mj.radius
                        mj.vx = -abs(mj.vx)
                    if mj.y - mj.radius < 0:
                        mj.y = mj.radius
                        mj.vy = abs(mj.vy)
                    elif mj.y + mj.radius > height:
                        mj.y = height - mj.radius
                        mj.vy = -abs(mj.vy)
        for mol in self.molecules:
            self.canvas.coords(mol.item, mol.x - mol.radius, mol.y - mol.radius,
                               mol.x + mol.radius, mol.y + mol.radius)

    def _on_close(self):
        self.stop_animation()
        if self.after_id is not None:
            self.after_cancel(self.after_id)
        self.destroy()

if __name__ == "__main__":
    app = GasApp()
    app.mainloop()