Как часто хочется себе представить как движения планет зависят от звезды? Сегодня для этого не нужен телескоп! Представить движения планет в зависимости от объекта очень большой массы можно и на компьютере.

Идея для данной программы зародилась в момент, когда я захотел наглядно представить себе то, как движутся планеты нашей солнечной системы, и как бы менялась траектория движения планет относительно солнца при различных начальных условиях.

Долго тянуть не буду и сразу перейду к краткому описанию программы. Решил писать на питоне. Суть заключается в том, что мы можем создавать планеты в любой точке плоскости, с любым радиусом, массой, цветом и начальной скоростью и моделировать их взаимодействие.Чтобы было не очень сумбурно, решил в куски с кодом вставлять комментарии.

Сначала продемонстрирую пример работы: создаю Солнце и 6 планет, левым придаю скорость вверх, а правым - вниз, равную по модулю. Несмотря на то, что в начальный момент скорость каждой из планет одинакова, свой полный оборот вокруг Солнца они завершают в разное время. Это происходит из-за разницы в начальных расстояниях.

Первый блок: просто импортируем необходимые библиотеки

import pygame
import time

Второй блок: создаём необходимые переменные

fps = 120  # просто fps
G = 6.67 / 10 ** 11  # сохраняем константу гравитационной постоянной
k = 1000000  # метров в одном пикселе
mouse_x, mouse_y = 0, 0  # начальное положение мышки (начальные значения не очень влияют)
time_speed = 10000  # коэффициент ускорения программы
# При коэффициенте ускорения больше 10 ^ 5, появляется очень большая погрешность

Третий блок: запускаем окно, задаём ширину, высоту и переменные для отображения fps и ускорения

pygame.init()
screen = pygame.display.set_mode((1500, 800))
pygame.display.set_caption("Planetary acceleration")
style = pygame.font.SysFont("arial", 36)
render_fps = style.render('fps ' + str(fps), True, 'blue')
render_time_speed = style.render('acceleration: ' + str(time_speed), True, 'blue')

Четвёртый блок: для удобства создадим класс, который будет отвечать за одну планету

class Planet:
    def __init__(self, x, y, r, m, color):  # задаём начальные параметры
        self.x = x  # координата по оси Ох
        self.y = y  # координата по оси Оу
        self.r = r  # радиус планеты
        self.m = m  # масса планеты
        self.color = color  # цвет планеты
        self.speed = [0, 0]  # скорость которую имеет планета 
        self.f = [0, 0]  # сила, которые действуют на планету
        self.status = True  # существует планета или уже нет
        self.trace_count = 0  # переменная, чтобы "не очень часто" выводить траекторию планеты, иначе сильно упало бы fps
        self.trace = []  # точки траектории нашей планеты

    def update_coordinates(self):  # обновляем скорость, координату и траекторию
        self.speed[0] += (self.f[0] / self.m) * time_speed ** 2 / fps ** 2 
        self.speed[1] += (self.f[1] / self.m) * time_speed ** 2 / fps ** 2

        self.x += self.speed[0]
        self.y += self.speed[1]

        self.trace_count += (self.speed[0] ** 2 + self.speed[1] ** 2) ** 0.5 
        if self.trace_count / k >= 7:  # чем меньше число после знака, тем более сплошной будет траектория при выводе
            self.trace_count = 0
            self.trace.append((self.x, self.y))
        if len(self.trace) > 1000:  # Также, чтобы не проседало fps, когда много точек, начинаем удалять с конца
            self.trace.pop(0)

    def draw(self):  # отображаем все планеты и их траектории
        pygame.draw.circle(screen, self.color, ((self.x - mouse_x) / k, (self.y - mouse_y) / k), self.r / k)
        for i in self.trace:
            pygame.draw.circle(screen, self.color, ((i[0] - mouse_x) / k, (i[1] - mouse_y) / k), 1)

Пятый блок: нужен для определения всех сил, действующих на каждую планету

def update_forces(planets, collides):
    for i in range(len(planets)):
        for j in range(i + 1, len(planets)):
            dif_x = planets[j].x - planets[i].x  # разница между двумя планетами по х
            dif_y = planets[j].y - planets[i].y  # разница между двумя планетами по y
            d = (dif_x ** 2 + dif_y ** 2) ** 0.5  # пифагорово расстояние между планетами
            f = G * planets[i].m * planets[j].m / d ** 2  # Сила между двумя планетами

            planets[i].f[0] += dif_x * f / d  # обновляем действующие силы на тело
            planets[i].f[1] += dif_y * f / d

            planets[j].f[0] -= dif_x * f / d
            planets[j].f[1] -= dif_y * f / d

            if planets[i].r + planets[j].r > d:  # Находим какие две планеты сталкиваются друг с другом
                collides.append((i, j))

Шестой блок: обрабатываем все столкновения планет

def remove_collides(planets, collides):
    for i in collides:
        p1 = planets[i[0]]
        p2 = planets[i[1]]
        if p1.status and p2.status:  # если обе планеты ещё существуют
            if p1.m > p2.m:  # какая планета больше по массе, та и выиграла
                new_planet = Planet(p1.x, p1.y, p1.r + p2.r, p1.m + p2.m, p1.color)
            else:
                new_planet = Planet(p2.x, p2.y, p1.r + p2.r, p1.m + p2.m, p2.color)

            new_planet.speed = [(p1.m * p1.speed[0] + p2.m * p2.speed[0]) / (p1.m + p2.m),
                                (p1.m * p1.speed[1] + p2.m * p2.speed[1]) / (p1.m + p2.m)]

            planets.append(new_planet)
            p1.status = p2.status = 0

Седьмой блок: для примера, создаём солнце и несколько планет

planets = []

# Солнце
p = Planet(0, 0, 696_000_000, 1.9891 * 10 ** 30, 'orange')
planets.append(p)

for i in range(14, 20):
    p = Planet((-1) ** i * (i * 10 ** 9 + 10 ** 9), 0, 1, 1, 'green')
    planets.append(p)
    planets[i - 13].speed[1] += (-1) ** i * 29273.6 * time_speed / fps

Восьмой блок: запускаем основную программу, обрабатываем все команды, которые могут поступать от пользователя и обновляем массив, в котором содержаться ещё "живые" планеты

tick = 0
tm = time.time()
running = True  # стандартная переменная для запуска цикла 
while running:
    tick += 1
    if tick == 100:  # чтобы fps в левом углу не сходил с ума и не происходило деление на ноль 
        tick = 0
        render_fps = style.render("fps:" + str(int(100 / (time.time() - tm))), True, "blue")
        tm = time.time()

    for event in pygame.event.get():  # начинаем обрабатывать команды
        if event.type == pygame.QUIT:  # выход
            running = False

        if event.type == pygame.MOUSEBUTTONDOWN:  # нажатие любой кнопки мыши
            x = event.pos[0]  # определяем где находится мышка у пользователя
            y = event.pos[1]
            new_x = mouse_x + x * k  # считаем коэффициенты для в нашей "размерности"
            new_y = mouse_y + y * k

            if event.button == 4:  # колёсико мышки вперёд - приближение космической карты
                k *= 0.85
                mouse_x = new_x - x * k
                mouse_y = new_y - y * k

            if event.button == 5:  # колёсико мышки назад - отдаление космической карты
                k /= 0.85
                mouse_x = new_x - x * k
                mouse_y = new_y - y * k

            if event.button == 3:  # правая кнопка мыши - добавляем планету в точку, куда указывает мышка
                planets.append(Planet(new_x, new_y, 6371000, 5.9722 * 10 ** 24, 'blue'))

            if event.button == 2:  # нажатие колёсика - ускорение работы программы
                if time_speed >= 100000:
                    time_speed = 1
                    for i in planets:
                        i.speed[0] /= 100000  # уменьшение скоростей планет
                        i.speed[1] /= 100000
                else:
                    time_speed *= 10
                    for i in planets:
                        i.speed[0] *= 10
                        i.speed[1] *= 10

                render_time_speed = style.render('acceleration: ' + str(time_speed), True, 'blue')  # обновляем отображение ускорения 

        if event.type == pygame.MOUSEMOTION:  # для перемещения по космической карте с помощью удержания левой кнопки мыши
            if pygame.mouse.get_pressed()[0]:
                mouse_x -= event.rel[0] * k
                mouse_y -= event.rel[1] * k

    collides = []  # массив для определения кортежей столкновений 
    update_forces(planets, collides)  # находим все столкновения 

    remove_collides(planets, collides)  # обрабатываем все столкновения

    screen.fill("black")  # обновляем чёрный фон

    new_planets = []
    for planet in planets:  
        if planet.status:  # выбираем только те планеты, которые ещё не столкнулись
            planet.update_coordinates()  # обновляем их значения
            planet.f = [0, 0]  # сбрасываем действующие силы
            new_planets.append(planet)  # сохраняем "живые" планеты
            planet.draw()  # отображаем всё
    planets = new_planets  # обновляем массив

    screen.blit(render_fps, (10, 10))  # выводим fps
    screen.blit(render_time_speed, (10, 50))  # выводим ускорение 
    pygame.display.update()
pygame.quit()  # заканчиваем 

Всё. Это вся программа, которая необходима для данной задачи.

Под конец, давайте полностью смоделируем работу Солнечной системы!

Добавим код для создания Солнца и всех планет

# Земля:
p = Planet(-152 * 10 ** 9, 0, 6371000, 5.9722 * 10 ** 24, 'green')
planets.append(p)
planets[0].speed[1] += 29273.6 * time_speed / fps # скорость вокруг солнца

# Луна:
p = Planet(-363104000 - 152 * 10 ** 9, 0, 1737100, 7.35 * 10 ** 22, 'grey')
planets.append(p)
planets[1].speed[1] = 1023 * time_speed / fps # скорость вокруг земли
planets[1].speed[1] += 29273.6 * time_speed / fps # скорость вокруг солнца

# Меркурий
p = Planet(-69.8 * 10 ** 9, 0, 2439700, 3.33022 * 10 ** 23, "yellow")
planets.append(p)
planets[2].speed[1] += 38000 * time_speed / fps # Скорость вокруг солнца

# Венера
p = Planet(-109 * 10 ** 9, 0, 6051800, 4.8675 * 10 ** 24, "white")
planets.append(p)
planets[3].speed[1] += 34000 * time_speed / fps

# Марс
p = Planet(-249.2 * 10 ** 9, 0, 3390000, 6.4171 * 10 ** 23, "red")
planets.append(p)
planets[4].speed[1] += 23000 * time_speed / fps

# Юпитер
p = Planet(-816.521 * 10 ** 9, 0, 71492000, 1.8986 * 10 ** 27, "brown")
planets.append(p)
planets[5].speed[1] += 12500 * time_speed / fps

# Сатурн
p = Planet(-1.51 * 10 ** 12, 0, 60268000, 5.6846 * 10 ** 26, "beige")
planets.append(p)
planets[6].speed[1] += 9100 * time_speed / fps

# Уран
p = Planet(-3 * 10 ** 12, 0, 	25362000, 8.6813 * 10 ** 25, "pink")
planets.append(p)
planets[7].speed[1] += 6300 * time_speed / fps

# Нептун
p = Planet(-4.5 * 10 ** 12, 0, 24622000, 	1.02409 * 10 ** 26, "blue")
planets.append(p)
planets[8].speed[1] += 4950 * time_speed / fps

# Солнце
p = Planet(0, 0, 696_000_000, 1.9891 * 10 ** 30, 'orange')
planets.append(p)

В связи с тем, что расстояние между планетами очень большое, сначала я решил показать симуляцию движения планет земного типа

Меркурий, Венера, Земля с Луной и Марс. На Марсе новый год ещё не наступил :(
Меркурий, Венера, Земля с Луной и Марс. На Марсе новый год ещё не наступил :(

Теперь немного отдалимся от Солнца и посмотрим на оставшиеся планеты

Это всё, что я хотел рассказать и показать вам сегодня. Смело копируйте программу и проводите свои собственные эксперименты на космической карте!