Новые модели выходят практически каждый день. За последние два месяца мы увидели релизы от Qwen, OpenAI, Anthropic и Google. Очень ждали DeepSeek, но его пока не слышно и не видно.

В своих прошлых материалах я сравнивал последние версии моделей крупных компаний в конкретных сферах: программирование и генерация текста.

Сегодняшняя статья продолжит этот цикл. По-хорошему, этот материал должен был выйти до сравнений в узких сферах, потому что сегодня я планирую сравнить Gemini 3.1 Pro, ChatGPT 5.4 и Claude Opus 4.6 сразу в нескольких дисциплинах. Сделать, так сказать, общее сравнение.

Сразу предупрежу: никаких межгалактических заданий вы тут не увидите. Моя цель куда приземленнее – проверить модели в рутинных задачах, с которыми они сталкиваются каждый день. Мне это нужно, чтобы понять, какой модели сейчас стоит отдать предпочтение. В расчет пойдет качество выполнения (оцененное мной), а также затраты в денежном эквиваленте.

На этом с предисловием можно закончить. Принимайте стратегически удобное положение, а я начинаю.


Немного об участниках сравнения

ChatGPT 5.4

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

Для начала модель гораздо лучше ищет информацию в интернете и лучше справляется с ответами на объединенные вопросы из множества источников. Не будем забывать, что контекстное окно было увеличено до 1 миллиона токенов. 

Компания проработала и улучшила базовые способности модели, но и сделала акцент на агентный режим. GPT-5.4 может анализировать скриншоты, пользоваться браузером, выполнять действия мышью и клавиатурой, вызывать API и инструменты. Понятное дело, не напрямую, через специальное ПО.

Claude Opus 4.6

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

Одной из центральных инноваций в новой версии стала технология внутреннего планирования. В отличие от прошлых итераций, Opus теперь не просто генерирует слово за словом, а выстраивает предварительную карту рассуждений. В задачах модель сначала проведет внутреннюю верификацию каждого этапа. 

Если рассматривать опыт использования модели в разработке программного обеспечения, то здесь обновленный Opus делает серьезный шаг вперед. Благодаря контекстному окну объемом полтора миллиона токенов, вы можете оперировать не отдельными фрагментами, а целыми модулями системы. Это позволяет загрузить в память нейросети практически всю кодовую базу небольшого проекта.

Gemini 3.1 Pro

Google не стал мелочиться и назвал Gemini 3.1 Pro своей самой интеллектуальной моделью для сложных задач. Звучит достаточно гордо.

Главная фишка апдейта – это улучшенное базовое мышление. Если прошлые обновления Gemini 3 Pro были больше про расширение возможностей, то здесь инженеры Google DeepMind занимались прокачкой внутреннего процессора модели.

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

Контекстное окно у модели, все так же 1 миллион токенов на входе. На выходе модель выдает до 64 тысяч токенов. То есть она способна сгенерировать целую небольшую повесть или очень объемный кусок кода.

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


Небольшое отступление

Все модели для сравнения были взяты у агрегатора нейросетей BotHub. Список нейросетей там обширен – от работы с текстом до транскрибации и генерации видео. По специальной ссылке для регистрации можно получить 300 000 капсов для собственных экспериментов.


Условия сравнения

Модели будут сравниваться в четыре этапа: работа с текстом (генерация/выжимка), решение математических задач и написание кода. Как я и упоминал выше, достаточно рутинные поручения, которые поступают тысячами от людей в мире каждый день.

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

Первый этап – генерация текста

Скрытый текст

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

У рассказа должна быть классическая структура с завязкой, развитием и обязательным логическим концом, финал должен быть смешным и завершенным, не обрывай повествование на полуслове. Юмор должен быть умным и ситуационным, избегай глупых шуток ниже пояса, абсурдного сюрреализма и тупого юмора. Сделай акцент на иронии, нелепых магических неудачах, комичных персонажах и пародии на штампы фэнтези. Главный герой или герои должны столкнуться с магической проблемой, которая решается самым неожиданным и смешным способом.

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

Второй этап – выжимка из текста

Скрытый текст

Ради интереса, я решил скормить моделям практическую работу по передачи информации в системах реального времени. Формат будет не просто текстом, а PDF-файлом. Цель – сжать и не потерять ключевую информацию. В идеале результат должен будет иметь все необходимые данные для выполнения самой практической работы.

Выполни сжатие текста из прикрепленного файла. Главная задача – сократить объем текста, но при этом сохранить всю ключевую информацию, необходимую для выполнения практической работы и ответа на контрольные вопросы. Проанализируй содержание. Четко выдели и сохрани элементы для выполнения практической работы. Результат должен представлять собой связный, логичный и понятный текст, готовый к практическому использованию по назначению.

Третий этап – математические задачи

Модель получит скриншоты 4-х задач из разных тем по математике: одну уровня ЕГЭ и три из высшей.

Четвертый этап – программирование

Скрытый текст

Ты – профессиональный программист на Python. Твоя задача – разработать полностью проработанное десктопное приложение Инженерного калькулятора с графическим интерфейсом. Код должен быть готов к запуску, чистым и содержать комментарии для пояснения ключевых моментов.

В функционал калькулятора необходимо включить базовые арифметические операции: сложение, вычитание, умножение и деление. Также добавь расширенные математические возможности: возведение в степень, извлечение квадратного корня, вычисление факториала, модуля числа и обязательную поддержку скобок для задания приоритета операций. Из тригонометрических функций потребуются синус, косинус, тангенс, а также обратные функции арксинус, арккосинус и арктангенс с возможностью переключения между градусами и радианами. Добавь натуральный и десятичный логарифмы, функцию возведения экспоненты в степень, а также математические константы – число пи и число e с высокой точностью. Из дополнительных функций пригодятся вычисление процентов, смена знака числа и кнопки очистки ввода. Интерфейс должен быть продуманным с логичным расположением кнопок, а поле ввода должно наглядно отображать введенное выражение и полученный результат. Для реализации графического интерфейса можно использовать любую удобную библиотеку, например tkinter или PyQt. 

Отдельное требование – в интерфейсе калькулятора должна присутствовать кнопка с названием "Игра" или "Змейка". При нажатии на эту кнопку должно открываться новое окно с классической игрой "Змейка". Реализуй простую рабочую версию игры: змейка управляется стрелками клавиатуры, еда появляется в случайном месте игрового поля, при столкновении с границами экрана или собственным хвостом игра завершается, а текущий счет должен отображаться на экране.


Сравнение

Первый этап

ChatGPT 5.4

Скрытый текст

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

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

Меня часто в комментариях просят указывать цены в таких тестах. Собственно, я решил наконец это начать делать. ChatGPT 5.4 выполнял это задание за 25 рублей.

Gemini 3.1 Pro

Вариант от Gemini 3.1 Pro мне понравился больше, чем ChatGPT. Текст не забит водой, юмор есть, попытки вставить клишированность тоже. Сам рассказ читается опять же просто. Максимальный балл в копилку модели.

Стоимость этого задания для Gemini 3.1 Pro составляет 20 рублей.

Claude Opus 4.6

Скрытый текст

Для начала скажу, что Claude поистине расписался, что сильно отличает его от Gemini, который зачастую отдает предпочтение краткости.

В остальном, нареканий нет. Рассказ читается достаточно просто, есть юмор. Даже пародия на очередь в каких-то сервисах. Максимальный балл в копилку модели.

Стоимость запроса – 68 рублей. Что, пожалуй, главный минус. С другой стороны Claude не скупилась на буквы и насыпала их прямо гору.

Второй этап

ChatGPT 5.4

Скрытый текст

Самый приятный результат. Ничего не потеряно, но при этом вся ключевая информация осталась. Максимальный балл.

Стоимость – 24 рубля.

Самый приятный результат. Ничего не потеряно, но при этом вся ключевая информация осталась. Максимальный балл.

Стоимость – 24 рубля.

Gemini 3.1 Pro

По мне в задании на сжатие текста без потери ключевой информации – краткость не всегда подходит. Собственно тут так и есть. Если ChatGPT убрал воду, но оставил всю другую информацию, то Gemini по большей части вообще оставил только самое основное. Из-за чего есть трудности в некоторых моментах. Два балла.

Стоимость запроса – 16 рублей. 

Claude Opus 4.6

Скрытый текст

Ответу от Claude я тоже поставлю два балла. С одной стороны, выглядит по объему плюс-минус, как у ChatGPT. С другой стороны все же некоторые моменты были упущены, хотя сама модель дополнительно ответила на контрольные вопросы.

Цена запроса – 38 рублей.

Третий этап

ChatGPT 5.4

Gemini 3.1 Pro

Claude Opus 4.6

Все модели спокойно справились с этим заданием. Поэтому по сути, все они получают максимальный балл. Единственное, что можно тут сравнить это их стоимость. ChatGPT потратил 15 рублей на этот запрос, Gemini – 22 рубля, ну а Claude 29 рублей.

Четвертый этап

ChatGPT 5.4

Хорошо сделанный калькулятор по функционалу, аналогично и со змейкой. В обоих случаях есть просадка по дизайну. Максимальный балл. 

Стоимость самого запроса – 48 рублей.

Опять же, когда-то упоминали, что я не прикрепляю код. Поэтому в этот раз мы это исправим.

Скрытый текст
import ast
import math
import random
import tkinter as tk
from tkinter import ttk


# ============================================================
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ БЕЗОПАСНОГО ВЫЧИСЛЕНИЯ ВЫРАЖЕНИЙ
# ============================================================

def factorial_checked(value: float) -> int:
    """
    Безопасный факториал:
    разрешаем только неотрицательные целые числа.
    """
    if value < 0:
        raise ValueError("Факториал определен только для неотрицательных чисел.")
    if not float(value).is_integer():
        raise ValueError("Факториал определен только для целых чисел.")
    return math.factorial(int(value))


def percent(value: float) -> float:
    """Перевод числа в проценты: 50% -> 0.5"""
    return value / 100.0


def find_operand_start(expression: str, end_index: int) -> int:
    """
    Находит начало последнего операнда слева от end_index.
    Нужно для корректной обработки postfix-операторов:
    - факториал: 5!
    - процент: (20+30)%
    """
    i = end_index

    while i >= 0 and expression[i].isspace():
        i -= 1

    if i < 0:
        return 0

    # Число: 123.45
    if expression[i].isdigit() or expression[i] == ".":
        while i >= 0 and (expression[i].isdigit() or expression[i] == "."):
            i -= 1
        return i + 1

    # Имя: pi, e, abs, sin и т.д.
    if expression[i].isalpha() or expression[i] == "_":
        while i >= 0 and (expression[i].isalnum() or expression[i] == "_"):
            i -= 1
        return i + 1

    # Выражение в скобках или вызов функции: (2+3), sin(30)
    if expression[i] == ")":
        depth = 1
        i -= 1

        while i >= 0:
            if expression[i] == ")":
                depth += 1
            elif expression[i] == "(":
                depth -= 1
                if depth == 0:
                    break
            i -= 1

        if i < 0:
            raise ValueError("Ошибка: несогласованные скобки.")

        start = i

        # Если перед "(" стоит имя функции, включаем и его: sin(...)
        j = start - 1
        while j >= 0 and expression[j].isspace():
            j -= 1

        k = j
        while k >= 0 and (expression[k].isalnum() or expression[k] == "_"):
            k -= 1

        if k != j:
            start = k + 1

        return start

    return i + 1


def replace_postfix_operator(expression: str, symbol: str, function_name: str) -> str:
    """
    Преобразует postfix-операторы:
    - 5!  -> factorial(5)
    - (2+3)! -> factorial((2+3))
    """
    while symbol in expression:
        index = expression.find(symbol)
        start = find_operand_start(expression, index - 1)
        operand = expression[start:index].strip()

        if not operand:
            raise ValueError(f"Некорректное использование оператора {symbol}")

        expression = (
            expression[:start]
            + f"{function_name}({operand})"
            + expression[index + 1:]
        )

    return expression


def preprocess_expression(expression: str) -> str:
    """
    Подготавливает пользовательское выражение к вычислению:
    - × -> *
    - ÷ -> /
    - π -> pi
    - ^ -> **
    - postfix ! и %
    - запятая -> точка
    """
    expr = expression.strip()

    expr = expr.replace("×", "*")
    expr = expr.replace("÷", "/")
    expr = expr.replace("π", "pi")
    expr = expr.replace(",", ".")
    expr = expr.replace("^", "**")

    # Постфиксные операции
    expr = replace_postfix_operator(expr, "!", "factorial")
    expr = replace_postfix_operator(expr, "%", "percent")

    return expr


class SafeEvaluator(ast.NodeVisitor):
    """
    Безопасный вычислитель выражений через AST.
    Разрешаем только:
    - числа
    - + - * / **
    - унарный плюс/минус
    - вызовы разрешенных функций
    - разрешенные константы
    """

    def __init__(self, names: dict):
        self.names = names

    def visit(self, node):
        return super().visit(node)

    def generic_visit(self, node):
        raise ValueError(f"Недопустимая конструкция: {type(node).__name__}")

    def visit_Expression(self, node: ast.Expression):
        return self.visit(node.body)

    def visit_Constant(self, node: ast.Constant):
        if isinstance(node.value, bool):
            raise ValueError("Булевы значения не поддерживаются.")
        if not isinstance(node.value, (int, float)):
            raise ValueError("Поддерживаются только числа.")
        return node.value

    def visit_Name(self, node: ast.Name):
        if node.id not in self.names:
            raise ValueError(f"Неизвестное имя: {node.id}")
        return self.names[node.id]

    def visit_UnaryOp(self, node: ast.UnaryOp):
        operand = self.visit(node.operand)

        if isinstance(node.op, ast.UAdd):
            return +operand
        if isinstance(node.op, ast.USub):
            return -operand

        raise ValueError("Недопустимая унарная операция.")

    def visit_BinOp(self, node: ast.BinOp):
        left = self.visit(node.left)
        right = self.visit(node.right)

        if isinstance(node.op, ast.Add):
            return left + right
        if isinstance(node.op, ast.Sub):
            return left - right
        if isinstance(node.op, ast.Mult):
            return left * right
        if isinstance(node.op, ast.Div):
            return left / right
        if isinstance(node.op, ast.Pow):
            return left ** right

        raise ValueError("Недопустимая бинарная операция.")

    def visit_Call(self, node: ast.Call):
        if not isinstance(node.func, ast.Name):
            raise ValueError("Разрешены только прямые вызовы функций.")

        func_name = node.func.id

        if func_name not in self.names:
            raise ValueError(f"Функция {func_name} не разрешена.")

        func = self.names[func_name]

        if node.keywords:
            raise ValueError("Именованные аргументы не поддерживаются.")

        args = [self.visit(arg) for arg in node.args]
        return func(*args)


# ==========================================
# ОТДЕЛЬНОЕ ОКНО ИГРЫ "ЗМЕЙКА"
# ==========================================

class SnakeGame:
    CELL_SIZE = 20
    GRID_WIDTH = 25
    GRID_HEIGHT = 20
    UPDATE_DELAY = 120  # миллисекунды

    def __init__(self, parent):
        self.window = tk.Toplevel(parent)
        self.window.title("Змейка")
        self.window.resizable(False, False)

        self.score_var = tk.StringVar(value="Счет: 0")

        top_frame = ttk.Frame(self.window, padding=10)
        top_frame.pack(fill="x")

        ttk.Label(
            top_frame,
            textvariable=self.score_var,
            font=("Segoe UI", 12, "bold")
        ).pack(side="left")

        ttk.Button(
            top_frame,
            text="Новая игра",
            command=self.reset_game
        ).pack(side="right")

        canvas_width = self.GRID_WIDTH * self.CELL_SIZE
        canvas_height = self.GRID_HEIGHT * self.CELL_SIZE

        self.canvas = tk.Canvas(
            self.window,
            width=canvas_width,
            height=canvas_height,
            bg="#111111",
            highlightthickness=1,
            highlightbackground="#444444"
        )
        self.canvas.pack(padx=10, pady=(0, 10))

        self.after_id = None
        self.game_over = False

        self.window.bind("<Up>", lambda event: self.change_direction((0, -1)))
        self.window.bind("<Down>", lambda event: self.change_direction((0, 1)))
        self.window.bind("<Left>", lambda event: self.change_direction((-1, 0)))
        self.window.bind("<Right>", lambda event: self.change_direction((1, 0)))

        self.window.protocol("WM_DELETE_WINDOW", self.on_close)

        self.reset_game()
        self.window.focus_force()

    def reset_game(self):
        if self.after_id is not None:
            self.window.after_cancel(self.after_id)
            self.after_id = None

        center_x = self.GRID_WIDTH // 2
        center_y = self.GRID_HEIGHT // 2

        self.snake = [
            (center_x, center_y),
            (center_x - 1, center_y),
            (center_x - 2, center_y),
        ]
        self.direction = (1, 0)
        self.next_direction = (1, 0)
        self.food = self.spawn_food()
        self.score = 0
        self.game_over = False

        self.score_var.set("Счет: 0")
        self.draw()
        self.game_loop()

    def on_close(self):
        if self.after_id is not None:
            self.window.after_cancel(self.after_id)
        self.window.destroy()

    def spawn_food(self):
        while True:
            position = (
                random.randint(0, self.GRID_WIDTH - 1),
                random.randint(0, self.GRID_HEIGHT - 1)
            )
            if position not in self.snake:
                return position

    def change_direction(self, new_direction):
        if self.game_over:
            return

        # Запрещаем разворот на 180 градусов
        if (
            new_direction[0] == -self.direction[0]
            and new_direction[1] == -self.direction[1]
        ):
            return

        self.next_direction = new_direction

    def game_loop(self):
        if self.game_over:
            return

        self.direction = self.next_direction
        head_x, head_y = self.snake[0]
        dx, dy = self.direction

        new_head = (head_x + dx, head_y + dy)

        # Столкновение со стеной
        if (
            new_head[0] < 0 or new_head[0] >= self.GRID_WIDTH
            or new_head[1] < 0 or new_head[1] >= self.GRID_HEIGHT
        ):
            self.finish_game()
            return

        # Столкновение с собой
        if new_head in self.snake:
            self.finish_game()
            return

        self.snake.insert(0, new_head)

        if new_head == self.food:
            self.score += 1
            self.score_var.set(f"Счет: {self.score}")
            self.food = self.spawn_food()
        else:
            self.snake.pop()

        self.draw()
        self.after_id = self.window.after(self.UPDATE_DELAY, self.game_loop)

    def finish_game(self):
        self.game_over = True
        self.draw()

        self.canvas.create_text(
            self.GRID_WIDTH * self.CELL_SIZE // 2,
            self.GRID_HEIGHT * self.CELL_SIZE // 2,
            text=f"Игра окончена\nСчет: {self.score}",
            fill="white",
            font=("Segoe UI", 20, "bold"),
            justify="center"
        )

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

        # Еда
        fx, fy = self.food
        self.draw_cell(fx, fy, "#ff4d4d")

        # Змейка
        for index, (x, y) in enumerate(self.snake):
            color = "#55ff55" if index == 0 else "#2ecc71"
            self.draw_cell(x, y, color)

    def draw_cell(self, x, y, color):
        x1 = x * self.CELL_SIZE
        y1 = y * self.CELL_SIZE
        x2 = x1 + self.CELL_SIZE
        y2 = y1 + self.CELL_SIZE

        self.canvas.create_rectangle(
            x1, y1, x2, y2,
            fill=color,
            outline="#1f1f1f"
        )


# ==========================================
# ОСНОВНОЕ ПРИЛОЖЕНИЕ КАЛЬКУЛЯТОРА
# ==========================================

class ScientificCalculatorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Инженерный калькулятор")
        self.root.geometry("560x760")
        self.root.minsize(540, 720)

        self.expression_var = tk.StringVar()
        self.result_var = tk.StringVar(value="Результат: ")
        self.angle_mode_var = tk.StringVar(value="deg")  # deg / rad

        self.create_widgets()

        self.root.bind("<Return>", lambda event: self.evaluate_expression())
        self.root.bind("<KP_Enter>", lambda event: self.evaluate_expression())

    # --------------------------
    # Создание интерфейса
    # --------------------------
    def create_widgets(self):
        main = ttk.Frame(self.root, padding=12)
        main.pack(fill="both", expand=True)

        # Верхняя часть: ввод выражения и результат
        display_frame = ttk.LabelFrame(main, text="Ввод и результат", padding=10)
        display_frame.pack(fill="x", pady=(0, 10))

        self.entry = ttk.Entry(
            display_frame,
            textvariable=self.expression_var,
            font=("Segoe UI", 20),
            justify="right"
        )
        self.entry.pack(fill="x", padx=4, pady=(4, 8))
        self.entry.focus_set()

        result_label = ttk.Label(
            display_frame,
            textvariable=self.result_var,
            font=("Segoe UI", 14, "bold"),
            foreground="#0b7a29"
        )
        result_label.pack(anchor="e", padx=4)

        # Переключение градусов/радиан
        mode_frame = ttk.LabelFrame(main, text="Режим углов", padding=10)
        mode_frame.pack(fill="x", pady=(0, 10))

        ttk.Radiobutton(
            mode_frame,
            text="Градусы",
            variable=self.angle_mode_var,
            value="deg"
        ).pack(side="left", padx=5)

        ttk.Radiobutton(
            mode_frame,
            text="Радианы",
            variable=self.angle_mode_var,
            value="rad"
        ).pack(side="left", padx=5)

        # Кнопки калькулятора
        buttons_frame = ttk.Frame(main)
        buttons_frame.pack(fill="both", expand=True)

        for col in range(5):
            buttons_frame.columnconfigure(col, weight=1)

        button_layout = [
            ["(", ")", "C", "⌫", "Игра"],
            ["sin", "cos", "tan", "asin", "acos"],
            ["atan", "ln", "log", "exp", "√"],
            ["x²", "xʸ", "x!", "abs", "%"],
            ["7", "8", "9", "÷", "π"],
            ["4", "5", "6", "×", "e"],
            ["1", "2", "3", "-", "±"],
            ["0", ".", "+", "=", ""],
        ]

        for row_index, row in enumerate(button_layout):
            buttons_frame.rowconfigure(row_index, weight=1)

            for col_index, text in enumerate(row):
                if not text:
                    continue

                btn = tk.Button(
                    buttons_frame,
                    text=text,
                    font=("Segoe UI", 14, "bold"),
                    relief="raised",
                    bd=1,
                    padx=8,
                    pady=12,
                    command=lambda t=text: self.on_button_click(t)
                )
                btn.grid(
                    row=row_index,
                    column=col_index,
                    sticky="nsew",
                    padx=4,
                    pady=4
                )

                # Небольшая цветовая логика для удобства
                if text in {"=", "Игра"}:
                    btn.configure(bg="#d9ecff")
                elif text in {"C", "⌫"}:
                    btn.configure(bg="#ffd9d9")
                elif text in {"÷", "×", "-", "+", "xʸ", "x²", "x!", "%"}:
                    btn.configure(bg="#f2f2f2")

    # --------------------------
    # Работа с кнопками
    # --------------------------
    def on_button_click(self, text: str):
        if text == "=":
            self.evaluate_expression()
        elif text == "C":
            self.clear_all()
        elif text == "⌫":
            self.backspace()
        elif text == "Игра":
            self.open_snake()
        elif text == "π":
            self.insert_token("pi", auto_multiply=True)
        elif text == "√":
            self.insert_token("sqrt(", auto_multiply=True)
        elif text == "sin":
            self.insert_token("sin(", auto_multiply=True)
        elif text == "cos":
            self.insert_token("cos(", auto_multiply=True)
        elif text == "tan":
            self.insert_token("tan(", auto_multiply=True)
        elif text == "asin":
            self.insert_token("asin(", auto_multiply=True)
        elif text == "acos":
            self.insert_token("acos(", auto_multiply=True)
        elif text == "atan":
            self.insert_token("atan(", auto_multiply=True)
        elif text == "ln":
            self.insert_token("ln(", auto_multiply=True)
        elif text == "log":
            self.insert_token("log(", auto_multiply=True)
        elif text == "exp":
            self.insert_token("exp(", auto_multiply=True)
        elif text == "abs":
            self.insert_token("abs(", auto_multiply=True)
        elif text == "xʸ":
            self.insert_token("^")
        elif text == "x!":
            self.append_postfix("!")
        elif text == "x²":
            self.square_last_operand()
        elif text == "%":
            self.append_postfix("%")
        elif text == "±":
            self.toggle_sign()
        else:
            self.insert_token(text)

    def insert_token(self, token: str, auto_multiply: bool = False):
        """
        Вставка текста в конец выражения.
        Если auto_multiply=True, то при необходимости автоматически добавляется '*'.
        Например:
        2 -> sin(  => 2*sin(
        5 -> pi    => 5*pi
        """
        expr = self.expression_var.get()

        if auto_multiply and expr.strip():
            stripped = expr.rstrip()
            if stripped:
                last_char = stripped[-1]
                if last_char.isdigit() or last_char in ").!%":
                    token = "*" + token
                elif stripped.endswith("pi") or stripped.endswith("e"):
                    token = "*" + token

        self.expression_var.set(expr + token)

    def append_postfix(self, symbol: str):
        expr = self.expression_var.get().rstrip()

        if not expr:
            return

        if expr[-1] in "+-×÷*/^(":
            return

        self.expression_var.set(expr + symbol)

    def square_last_operand(self):
        """
        Кнопка x² возводит последний операнд в квадрат.
        Например:
        5      -> (5)^2
        sin(2) -> (sin(2))^2
        """
        expr = self.expression_var.get().rstrip()
        if not expr:
            return

        if expr[-1] in "+-×÷*/^(":
            return

        start = find_operand_start(expr, len(expr) - 1)
        operand = expr[start:].strip()
        prefix = expr[:start]

        self.expression_var.set(prefix + f"({operand})^2")

    def toggle_sign(self):
        """
        Смена знака последнего операнда.
        Например:
        5+3 -> 5+(-(3))
        """
        expr = self.expression_var.get().rstrip()

        if not expr:
            self.expression_var.set("-")
            return

        if expr[-1] in "+-×÷*/^(":
            self.expression_var.set(expr + "-")
            return

        start = find_operand_start(expr, len(expr) - 1)
        operand = expr[start:].strip()
        prefix = expr[:start]

        self.expression_var.set(prefix + f"(-({operand}))")

    def backspace(self):
        expr = self.expression_var.get()
        if expr:
            self.expression_var.set(expr[:-1])

    def clear_all(self):
        self.expression_var.set("")
        self.result_var.set("Результат: ")

    # --------------------------
    # Математические функции
    # --------------------------
    def current_mode_is_degrees(self) -> bool:
        return self.angle_mode_var.get() == "deg"

    def sin_func(self, x):
        return math.sin(math.radians(x)) if self.current_mode_is_degrees() else math.sin(x)

    def cos_func(self, x):
        return math.cos(math.radians(x)) if self.current_mode_is_degrees() else math.cos(x)

    def tan_func(self, x):
        return math.tan(math.radians(x)) if self.current_mode_is_degrees() else math.tan(x)

    def asin_func(self, x):
        result = math.asin(x)
        return math.degrees(result) if self.current_mode_is_degrees() else result

    def acos_func(self, x):
        result = math.acos(x)
        return math.degrees(result) if self.current_mode_is_degrees() else result

    def atan_func(self, x):
        result = math.atan(x)
        return math.degrees(result) if self.current_mode_is_degrees() else result

    def allowed_names(self) -> dict:
        """
        Набор разрешенных имен для безопасного вычисления.
        """
        return {
            "pi": math.pi,
            "e": math.e,
            "sin": self.sin_func,
            "cos": self.cos_func,
            "tan": self.tan_func,
            "asin": self.asin_func,
            "acos": self.acos_func,
            "atan": self.atan_func,
            "sqrt": math.sqrt,
            "factorial": factorial_checked,
            "abs": abs,
            "ln": math.log,
            "log": math.log10,
            "exp": math.exp,
            "percent": percent,
        }

    def format_result(self, value):
        """
        Красивое форматирование результата.
        """
        if isinstance(value, float):
            if value.is_integer():
                return str(int(value))
            return f"{value:.15g}"
        return str(value)

    def evaluate_expression(self):
        expr = self.expression_var.get().strip()

        if not expr:
            return

        try:
            prepared_expr = preprocess_expression(expr)
            tree = ast.parse(prepared_expr, mode="eval")
            evaluator = SafeEvaluator(self.allowed_names())
            result = evaluator.visit(tree)

            self.result_var.set(f"Результат: {self.format_result(result)}")

        except ZeroDivisionError:
            self.result_var.set("Результат: Ошибка — деление на ноль")
        except ValueError as error:
            self.result_var.set(f"Результат: Ошибка — {error}")
        except SyntaxError:
            self.result_var.set("Результат: Ошибка синтаксиса")
        except Exception as error:
            self.result_var.set(f"Результат: Ошибка — {error}")

    # --------------------------
    # Запуск игры
    # --------------------------
    def open_snake(self):
        SnakeGame(self.root)


# ==========================================
# ТОЧКА ВХОДА
# ==========================================

def main():
    root = tk.Tk()

    # Небольшая настройка стандартной темы ttk
    style = ttk.Style(root)
    try:
        style.theme_use("clam")
    except tk.TclError:
        pass

    app = ScientificCalculatorApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()

Gemini 3.1 Pro

Gemini больше постарался над дизайном. Все функции работают и придраться к ним я, увы, не смог.

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

Скрытый текст
import tkinter as tk
from tkinter import messagebox
import math
import random
import re

class SnakeGame(tk.Toplevel):
    """Класс, реализующий игру 'Змейка' в отдельном окне."""
    def __init__(self, parent):
        super().__init__(parent)
        self.title("Игра: Змейка")
        # Настраиваем размер и запрещаем изменение размера окна
        self.geometry("400x450")
        self.resizable(False, False)
        self.configure(bg="#1e1e1e")
        
        self.width = 400
        self.height = 400
        self.cell_size = 20
        self.is_running = True # Флаг для остановки цикла при закрытии окна
        
        # Перехват закрытия окна для корректной остановки цикла
        self.protocol("WM_DELETE_WINDOW", self.on_close)

        # Интерфейс игры
        self.score_label = tk.Label(self, text="Счет: 0", font=("Consolas", 14, "bold"), bg="#1e1e1e", fg="#4caf50")
        self.score_label.pack(pady=5)
        
        self.canvas = tk.Canvas(self, bg="black", width=self.width, height=self.height, highlightthickness=0)
        self.canvas.pack()
        
        # Управление стрелками
        self.bind("<KeyPress>", self.on_key_press)
        
        self.start_game()

    def start_game(self):
        """Инициализация стартовых параметров игры."""
        # Змейка начинается с 3 сегментов
        self.snake = [(100, 100), (80, 100), (60, 100)]
        self.dx = self.cell_size
        self.dy = 0
        self.next_dx = self.cell_size
        self.next_dy = 0
        self.score = 0
        self.score_label.config(text=f"Счет: {self.score}")
        self.game_over = False
        
        self.spawn_food()
        self.update_game()

    def spawn_food(self):
        """Создание еды в случайном месте, где нет змейки."""
        while True:
            x = random.randint(0, (self.width // self.cell_size) - 1) * self.cell_size
            y = random.randint(0, (self.height // self.cell_size) - 1) * self.cell_size
            self.food = (x, y)
            if self.food not in self.snake:
                break

    def on_key_press(self, event):
        """Обработка нажатий клавиш для управления."""
        key = event.keysym
        # Запрещаем движение в противоположном направлении (разворот на 180)
        if key == "Up" and self.dy == 0:
            self.next_dx, self.next_dy = 0, -self.cell_size
        elif key == "Down" and self.dy == 0:
            self.next_dx, self.next_dy = 0, self.cell_size
        elif key == "Left" and self.dx == 0:
            self.next_dx, self.next_dy = -self.cell_size, 0
        elif key == "Right" and self.dx == 0:
            self.next_dx, self.next_dy = self.cell_size, 0
        elif key == "space" and self.game_over:
            self.start_game()

    def update_game(self):
        """Основной игровой цикл."""
        if not self.is_running:
            return # Остановка, если окно закрыто

        if self.game_over:
            self.canvas.create_text(self.width/2, self.height/2 - 20, text="ИГРА ОКОНЧЕНА", fill="#f44336", font=("Consolas", 24, "bold"))
            self.canvas.create_text(self.width/2, self.height/2 + 20, text="Нажмите ПРОБЕЛ для рестарта", fill="white", font=("Consolas", 12))
            return

        # Применяем новое направление
        self.dx, self.dy = self.next_dx, self.next_dy
        head_x, head_y = self.snake[0]
        new_head = (head_x + self.dx, head_y + self.dy)

        # Проверка столкновений со стенами
        if (new_head[0] < 0 or new_head[0] >= self.width or
            new_head[1] < 0 or new_head[1] >= self.height or
            new_head in self.snake):
            self.game_over = True
            self.after(100, self.update_game)
            return

        self.snake.insert(0, new_head)

        # Проверка поедания еды
        if new_head == self.food:
            self.score += 10
            self.score_label.config(text=f"Счет: {self.score}")
            self.spawn_food()
        else:
            self.snake.pop() # Если еда не съедена, удаляем хвост

        # Отрисовка кадра
        self.canvas.delete("all")
        
        # Рисуем еду (красный круг)
        self.canvas.create_oval(self.food[0], self.food[1], 
                                self.food[0] + self.cell_size, self.food[1] + self.cell_size, fill="#ff5252", outline="")
        
        # Рисуем змейку (зеленые квадраты)
        for i, segment in enumerate(self.snake):
            color = "#4caf50" if i == 0 else "#81c784" # Голова чуть темнее
            self.canvas.create_rectangle(segment[0], segment[1], 
                                         segment[0] + self.cell_size, segment[1] + self.cell_size, fill=color, outline="#1b5e20")

        # Планируем следующий кадр (скорость игры)
        self.after(100, self.update_game)

    def on_close(self):
        """Метод вызывается при закрытии окна крестиком."""
        self.is_running = False
        self.destroy()


class Calculator(tk.Tk):
    """Главный класс инженерного калькулятора."""
    def __init__(self):
        super().__init__()
        self.title("Инженерный Калькулятор")
        self.geometry("500x650")
        self.resizable(False, False)
        self.configure(bg="#202020")
        
        self.is_degrees = True # Режим углов: Градусы (True) или Радианы (False)
        self.create_widgets()

    def create_widgets(self):
        """Создание и размещение элементов интерфейса."""
        # Поле ввода/вывода
        self.display_var = tk.StringVar()
        self.display = tk.Entry(self, textvariable=self.display_var, font=("Consolas", 28), 
                                bg="#333333", fg="#ffffff", bd=0, justify="right", insertbackground="white")
        self.display.grid(row=0, column=0, columnspan=5, ipadx=10, ipady=20, pady=10, padx=10, sticky="we")

        # Цветовые схемы для кнопок
        styles = {
            'num': {'bg': '#3b3b3b', 'fg': '#ffffff', 'activebackground': '#555555', 'activeforeground': '#ffffff'},
            'op': {'bg': '#ff9800', 'fg': '#ffffff', 'activebackground': '#f57c00', 'activeforeground': '#ffffff'},
            'func': {'bg': '#2c2c2c', 'fg': '#eeeeee', 'activebackground': '#444444', 'activeforeground': '#ffffff'},
            'sys': {'bg': '#f44336', 'fg': '#ffffff', 'activebackground': '#d32f2f', 'activeforeground': '#ffffff'},
            'game': {'bg': '#4caf50', 'fg': '#ffffff', 'activebackground': '#388e3c', 'activeforeground': '#ffffff'}
        }
        
        btn_font = ("Segoe UI", 14, "bold")

        # Матрица кнопок (Текст, строка, колонка, стиль, функция)
        buttons = [
            ("Deg", 1, 0, 'sys', self.toggle_angle), ("(", 1, 1, 'func', lambda: self.insert("(")), (")", 1, 2, 'func', lambda: self.insert(")")), ("C", 1, 3, 'sys', self.backspace), ("AC", 1, 4, 'sys', self.clear_all),
            ("sin", 2, 0, 'func', lambda: self.insert("sin(")), ("cos", 2, 1, 'func', lambda: self.insert("cos(")), ("tan", 2, 2, 'func', lambda: self.insert("tan(")), ("abs", 2, 3, 'func', lambda: self.insert("abs(")), ("/", 2, 4, 'op', lambda: self.insert("/")),
            ("asin", 3, 0, 'func', lambda: self.insert("asin(")), ("acos", 3, 1, 'func', lambda: self.insert("acos(")), ("atan", 3, 2, 'func', lambda: self.insert("atan(")), ("√", 3, 3, 'func', lambda: self.insert("sqrt(")), ("*", 3, 4, 'op', lambda: self.insert("*")),
            ("ln", 4, 0, 'func', lambda: self.insert("ln(")), ("log", 4, 1, 'func', lambda: self.insert("log(")), ("exp", 4, 2, 'func', lambda: self.insert("exp(")), ("x!", 4, 3, 'func', lambda: self.insert("fact(")), ("-", 4, 4, 'op', lambda: self.insert("-")),
            ("π", 5, 0, 'func', lambda: self.insert("π")), ("e", 5, 1, 'func', lambda: self.insert("e")), ("^", 5, 2, 'func', lambda: self.insert("^")), ("%", 5, 3, 'func', lambda: self.insert("%")), ("+", 5, 4, 'op', lambda: self.insert("+")),
            ("7", 6, 0, 'num', lambda: self.insert("7")), ("8", 6, 1, 'num', lambda: self.insert("8")), ("9", 6, 2, 'num', lambda: self.insert("9")), ("+/-", 6, 3, 'num', self.change_sign), ("=", 6, 4, 'op', self.calculate),
            ("4", 7, 0, 'num', lambda: self.insert("4")), ("5", 7, 1, 'num', lambda: self.insert("5")), ("6", 7, 2, 'num', lambda: self.insert("6")), ("Змейка", 7, 3, 'game', self.open_snake),
            ("1", 8, 0, 'num', lambda: self.insert("1")), ("2", 8, 1, 'num', lambda: self.insert("2")), ("3", 8, 2, 'num', lambda: self.insert("3")),
            ("0", 9, 0, 'num', lambda: self.insert("0")), (".", 9, 2, 'num', lambda: self.insert("."))
        ]

        self.btn_objects = {}
        for (text, r, c, style, func) in buttons:
            btn = tk.Button(self, text=text, font=btn_font, command=func, relief="flat", bd=0, **styles[style])
            
            # Настройка объединения ячеек для красивой сетки
            if text == "=":
                btn.grid(row=r, column=c, rowspan=4, sticky="nsew", padx=3, pady=3)
            elif text == "Змейка":
                btn.grid(row=r, column=c, rowspan=3, sticky="nsew", padx=3, pady=3)
            elif text == "0":
                btn.grid(row=r, column=c, columnspan=2, sticky="nsew", padx=3, pady=3)
            else:
                btn.grid(row=r, column=c, sticky="nsew", padx=3, pady=3)
                
            self.btn_objects[text] = btn

        # Настройка весов для равномерного растягивания кнопок
        for i in range(5):
            self.grid_columnconfigure(i, weight=1)
        for i in range(1, 10):
            self.grid_rowconfigure(i, weight=1)

    # --- Функции управления вводом ---

    def insert(self, char):
        self.display.insert(tk.END, char)

    def clear_all(self):
        self.display_var.set("")

    def backspace(self):
        current = self.display_var.get()
        self.display_var.set(current[:-1])

    def change_sign(self):
        current = self.display_var.get()
        if not current:
            return
        if current.startswith("-("):
            self.display_var.set(current[2:-1])
        else:
            self.display_var.set(f"-({current})")

    def toggle_angle(self):
        self.is_degrees = not self.is_degrees
        self.btn_objects["Deg"].config(text="Deg" if self.is_degrees else "Rad")

    def open_snake(self):
        SnakeGame(self)

    # --- Математические обертки (с учетом градусов/радиан) ---

    def _to_rad(self, x):
        return math.radians(x) if self.is_degrees else x

    def _to_deg(self, x):
        return math.degrees(x) if self.is_degrees else x

    def safe_sin(self, x): return math.sin(self._to_rad(x))
    def safe_cos(self, x): return math.cos(self._to_rad(x))
    def safe_tan(self, x): return math.tan(self._to_rad(x))
    def safe_asin(self, x): return self._to_deg(math.asin(x))
    def safe_acos(self, x): return self._to_deg(math.acos(x))
    def safe_atan(self, x): return self._to_deg(math.atan(x))

    # --- Парсинг и вычисление ---

    def calculate(self):
        expr = self.display_var.get()
        if not expr:
            return

        # Замена символов на синтаксис Python
        expr = expr.replace('π', str(math.pi))
        expr = expr.replace('e', str(math.e))
        expr = expr.replace('^', '**')
        # Обработка процентов (например: 50% -> 50/100)
        expr = re.sub(r'(\d+(?:\.\d+)?)%', r'(\1/100)', expr)

        # Окружение разрешенных функций
        safe_env = {
            'sin': self.safe_sin, 'cos': self.safe_cos, 'tan': self.safe_tan,
            'asin': self.safe_asin, 'acos': self.safe_acos, 'atan': self.safe_atan,
            'sqrt': math.sqrt, 'log': math.log10, 'ln': math.log,
            'exp': math.exp, 'fact': math.factorial, 'abs': abs
        }

        try:
            # Безопасное вычисление (запрет встроенных функций Python)
            result = eval(expr, {"__builtins__": None}, safe_env)
            
            # Убираем `.0` у целых чисел и ограничиваем длину дроби
            if isinstance(result, float):
                if result.is_integer():
                    result = int(result)
                else:
                    result = round(result, 12) # Защита от 0.1+0.2=0.30000000000000004
                    
            self.display_var.set(str(result))
            
        except ZeroDivisionError:
            messagebox.showerror("Ошибка", "Деление на ноль!")
            self.display_var.set("")
        except ValueError:
            messagebox.showerror("Ошибка", "Недопустимое значение (например, корень из отрицательного числа)!")
            self.display_var.set("")
        except Exception:
            messagebox.showerror("Ошибка", "Синтаксическая ошибка! Проверьте правильность выражения.")
            self.display_var.set("")

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

Claude Opus 4.6

Claude Opus меня удивил. Функции работают, базовые действия вроде бы тоже. Ключевое тут – вроде бы. Деление, которое сопутствует появлению десятичных значений приводит к ошибке. По мне, достаточно фатальный промах.

Со змейкой же никаких нареканий нет. Дизайн и работоспособность на высшем уровне. В совокупности из-за промаха с калькулятором модель лишается одного балла.

Стоимость ответа – 73 рубля.

Скрытый текст
"""
===============================================================================
  ИНЖЕНЕРНЫЙ КАЛЬКУЛЯТОР С ИГРОЙ «ЗМЕЙКА»
  Автор: AI Assistant
  Библиотека GUI: tkinter (входит в стандартную поставку Python)
  Python 3.8+
===============================================================================
  Функционал:
    • Базовая арифметика: +, −, ×, ÷
    • Степень, корень, факториал, модуль, скобки
    • Тригонометрия: sin, cos, tan, asin, acos, atan (DEG / RAD)
    • Логарифмы: ln, log₁₀, exp(x)
    • Константы: π, e
    • Процент, смена знака, очистка
    • Встроенная игра «Змейка»
===============================================================================
"""

import tkinter as tk
from tkinter import messagebox
import math
import random
import re

# ─────────────────────────── ЦВЕТОВАЯ СХЕМА ──────────────────────────────────

COLORS = {
    "bg_main":       "#1e1e2e",   # фон главного окна
    "bg_display":    "#11111b",   # фон дисплея
    "fg_expr":       "#a6adc8",   # цвет текста выражения
    "fg_result":     "#cdd6f4",   # цвет текста результата
    "btn_number":    "#313244",   # кнопки цифр
    "btn_number_fg": "#cdd6f4",
    "btn_operator":  "#45475a",   # кнопки операторов
    "btn_operator_fg":"#f5c2e7",
    "btn_function":  "#1e1e2e",   # кнопки функций
    "btn_function_fg":"#94e2d5",
    "btn_equal":     "#a6e3a1",   # кнопка «=»
    "btn_equal_fg":  "#1e1e2e",
    "btn_clear":     "#f38ba8",   # кнопки очистки
    "btn_clear_fg":  "#1e1e2e",
    "btn_game":      "#fab387",   # кнопка «Змейка»
    "btn_game_fg":   "#1e1e2e",
    "btn_hover":     "#585b70",   # подсветка при наведении
}

# ═══════════════════════════════════════════════════════════════════════════════
#                        КЛАСС ИНЖЕНЕРНОГО КАЛЬКУЛЯТОРА
# ═══════════════════════════════════════════════════════════════════════════════

class EngineeringCalculator:
    """Главное окно приложения — инженерный калькулятор."""

    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("Инженерный калькулятор")
        self.root.configure(bg=COLORS["bg_main"])
        self.root.resizable(False, False)

        # ── Переменные состояния ──
        self.expression = ""          # текущее выражение (отображается)
        self.angle_mode = "DEG"       # режим углов: DEG или RAD
        self.last_answer = 0          # последний результат (Ans)
        self.history_expr = ""        # строка-история (прошлое выражение)

        # ── Построение интерфейса ──
        self._build_display()
        self._build_mode_bar()
        self._build_buttons()

        # ── Привязка клавиатуры ──
        self.root.bind("<Key>", self._on_key_press)

        # Центрирование окна
        self.root.update_idletasks()
        self._center_window(self.root)

    # ─────────────────── Центрирование окна на экране ─────────────────────────

    @staticmethod
    def _center_window(win):
        """Размещает окно по центру экрана."""
        win.update_idletasks()
        w = win.winfo_width()
        h = win.winfo_height()
        x = (win.winfo_screenwidth() // 2) - (w // 2)
        y = (win.winfo_screenheight() // 2) - (h // 2)
        win.geometry(f"+{x}+{y}")

    # ─────────────────── ДИСПЛЕЙ (два поля: выражение + результат) ─────────────

    def _build_display(self):
        """Создаёт область дисплея калькулятора."""
        frame = tk.Frame(self.root, bg=COLORS["bg_display"], pady=8, padx=12)
        frame.pack(fill="x", padx=6, pady=(6, 0))

        # Верхняя строка — введённое выражение / история
        self.lbl_expression = tk.Label(
            frame, text="", font=("Consolas", 14),
            anchor="e", bg=COLORS["bg_display"], fg=COLORS["fg_expr"]
        )
        self.lbl_expression.pack(fill="x")

        # Нижняя строка — текущий ввод / результат
        self.lbl_result = tk.Label(
            frame, text="0", font=("Consolas", 28, "bold"),
            anchor="e", bg=COLORS["bg_display"], fg=COLORS["fg_result"]
        )
        self.lbl_result.pack(fill="x")

    # ─────────────────── ПАНЕЛЬ РЕЖИМА (DEG / RAD + кнопка «Змейка») ──────────

    def _build_mode_bar(self):
        """Строит панель переключения DEG/RAD и кнопку игры."""
        bar = tk.Frame(self.root, bg=COLORS["bg_main"])
        bar.pack(fill="x", padx=6, pady=(4, 0))

        # Кнопка переключения режима углов
        self.btn_mode = tk.Button(
            bar, text="DEG", font=("Consolas", 11, "bold"), width=6,
            bg="#89b4fa", fg=COLORS["bg_main"], bd=0, relief="flat",
            activebackground="#74c7ec", cursor="hand2",
            command=self._toggle_angle_mode
        )
        self.btn_mode.pack(side="left", padx=(0, 4))

        # Кнопка «Змейка 🐍»
        btn_snake = tk.Button(
            bar, text="🐍 Змейка", font=("Consolas", 11, "bold"), width=12,
            bg=COLORS["btn_game"], fg=COLORS["btn_game_fg"], bd=0,
            relief="flat", activebackground="#e0a070", cursor="hand2",
            command=self._open_snake_game
        )
        btn_snake.pack(side="right")

    def _toggle_angle_mode(self):
        """Переключает режим углов DEG ↔ RAD."""
        if self.angle_mode == "DEG":
            self.angle_mode = "RAD"
            self.btn_mode.config(text="RAD", bg="#f9e2af")
        else:
            self.angle_mode = "DEG"
            self.btn_mode.config(text="DEG", bg="#89b4fa")

    # ─────────────────── СЕТКА КНОПОК ─────────────────────────────────────────

    def _build_buttons(self):
        """
        Создаёт все кнопки калькулятора в виде сетки.
        Каждая кнопка описывается кортежем:
            (текст, категория, действие_или_значение, colspan)
        """
        frame = tk.Frame(self.root, bg=COLORS["bg_main"])
        frame.pack(fill="both", expand=True, padx=6, pady=6)

        # Описание раскладки кнопок (построчно)
        layout = [
            # Ряд 0 — научные функции (верхний)
            [("sin",  "func", "sin("),  ("cos",  "func", "cos("),
             ("tan",  "func", "tan("),  ("π",    "func", "π"),
             ("e",    "func", "e")],
            # Ряд 1
            [("asin", "func", "asin("), ("acos", "func", "acos("),
             ("atan", "func", "atan("), ("ln",   "func", "ln("),
             ("log",  "func", "log(")],
            # Ряд 2
            [("x²",  "func", "²"),      ("√",    "func", "√("),
             ("xⁿ",  "func", "^("),     ("n!",   "func", "!"),
             ("exp",  "func", "exp(")],
            # Ряд 3
            [("(",   "op",   "("),      (")",    "op",   ")"),
             ("|x|", "func", "abs("),   ("%",    "op",   "%"),
             ("±",   "func", "±")],
            # Ряд 4 — цифры и операторы
            [("7",   "num",  "7"),      ("8",    "num",  "8"),
             ("9",   "num",  "9"),      ("÷",    "op",   "÷"),
             ("C",   "clear","C")],
            # Ряд 5
            [("4",   "num",  "4"),      ("5",    "num",  "5"),
             ("6",   "num",  "6"),      ("×",    "op",   "×"),
             ("⌫",   "clear","⌫")],
            # Ряд 6
            [("1",   "num",  "1"),      ("2",    "num",  "2"),
             ("3",   "num",  "3"),      ("−",    "op",   "−"),
             ("Ans",  "func", "Ans")],
            # Ряд 7
            [("0",   "num",  "0"),      (".",    "num",  "."),
             ("×10ⁿ","func", "×10^("), ("+",    "op",   "+"),
             ("=",   "equal","=")],
        ]

        for r, row in enumerate(layout):
            for c, (text, cat, value) in enumerate(row):
                self._make_button(frame, text, cat, value, r, c)

        # Равномерное растяжение колонок
        for c in range(5):
            frame.columnconfigure(c, weight=1)
        for r in range(len(layout)):
            frame.rowconfigure(r, weight=1)

    def _make_button(self, parent, text, category, value, row, col, colspan=1):
        """Создаёт одну стилизованную кнопку и помещает в сетку."""
        # Выбор цветов по категории
        style_map = {
            "num":   (COLORS["btn_number"],   COLORS["btn_number_fg"]),
            "op":    (COLORS["btn_operator"], COLORS["btn_operator_fg"]),
            "func":  (COLORS["btn_function"], COLORS["btn_function_fg"]),
            "equal": (COLORS["btn_equal"],    COLORS["btn_equal_fg"]),
            "clear": (COLORS["btn_clear"],    COLORS["btn_clear_fg"]),
        }
        bg, fg = style_map.get(category, (COLORS["btn_number"], COLORS["btn_number_fg"]))

        btn = tk.Button(
            parent, text=text, font=("Consolas", 13, "bold"),
            bg=bg, fg=fg, bd=0, relief="flat",
            activebackground=COLORS["btn_hover"], activeforeground=fg,
            cursor="hand2",
            command=lambda v=value: self._on_button_click(v)
        )
        btn.grid(row=row, column=col, columnspan=colspan,
                 sticky="nsew", padx=2, pady=2, ipady=10)

        # Эффект наведения мыши
        btn.bind("<Enter>", lambda e, b=btn, c=bg: b.config(bg=COLORS["btn_hover"]))
        btn.bind("<Leave>", lambda e, b=btn, c=bg: b.config(bg=c))

    # ─────────────────── ОБРАБОТКА НАЖАТИЙ ────────────────────────────────────

    def _on_button_click(self, value: str):
        """Обрабатывает нажатие любой кнопки."""
        if value == "C":
            # Полная очистка
            self.expression = ""
            self.history_expr = ""
            self._update_display("0")
        elif value == "⌫":
            # Удаление последнего символа / токена
            self.expression = self._backspace(self.expression)
            self._update_display(self.expression if self.expression else "0")
        elif value == "=":
            self._evaluate()
        elif value == "±":
            self._negate()
        elif value == "Ans":
            self.expression += str(self.last_answer)
            self._update_display(self.expression)
        else:
            self.expression += value
            self._update_display(self.expression)

    def _backspace(self, expr: str) -> str:
        """
        Умное удаление: если выражение заканчивается на имя функции
        (например, 'sin('), удаляем целиком; иначе — один символ.
        """
        functions = ["sin(", "cos(", "tan(", "asin(", "acos(", "atan(",
                     "ln(", "log(", "exp(", "abs(", "√(", "^(", "×10^("]
        for fn in sorted(functions, key=len, reverse=True):
            if expr.endswith(fn):
                return expr[:-len(fn)]
        return expr[:-1] if expr else ""

    # ─────────────────── СМЕНА ЗНАКА ──────────────────────────────────────────

    def _negate(self):
        """
        Меняет знак последнего числа в выражении.
        Если выражение пустое или заканчивается оператором — добавляет '(-'.
        """
        if not self.expression:
            self.expression = "(-"
            self._update_display(self.expression)
            return

        # Пытаемся найти последнее число
        match = re.search(r'(\d+\.?\d*)$', self.expression)
        if match:
            start = match.start()
            # Проверяем, есть ли перед числом «(-»
            if start >= 2 and self.expression[start - 2:start] == "(-":
                self.expression = self.expression[:start - 2] + self.expression[start:]
            else:
                self.expression = self.expression[:start] + "(-" + self.expression[start:]
        else:
            self.expression += "(-"
        self._update_display(self.expression)

    # ─────────────────── ВЫЧИСЛЕНИЕ ВЫРАЖЕНИЯ ─────────────────────────────────

    def _evaluate(self):
        """
        Преобразует пользовательское выражение в вычисляемое Python-выражение
        и безопасно его считает.
        """
        expr = self.expression
        if not expr.strip():
            return

        display_expr = expr  # сохраняем для истории

        try:
            result = self._safe_eval(expr)
            # Форматирование результата
            if isinstance(result, float):
                if result == int(result) and abs(result) < 1e15:
                    result_str = str(int(result))
                else:
                    result_str = f"{result:.10g}"
            else:
                result_str = str(result)

            self.last_answer = result
            self.history_expr = display_expr + " ="
            self.expression = result_str
            self.lbl_expression.config(text=self.history_expr)
            self.lbl_result.config(text=result_str)

        except ZeroDivisionError:
            self._show_error("Деление на ноль")
        except ValueError as ve:
            self._show_error(f"Ошибка значения: {ve}")
        except OverflowError:
            self._show_error("Переполнение")
        except Exception as ex:
            self._show_error(f"Ошибка: {ex}")

    def _safe_eval(self, expr: str):
        """
        Преобразует строку калькулятора в безопасное Python-выражение.
        Используем ограниченный набор имён (без доступа к builtins).
        """
        # ── Замены символов на Python-операторы ──
        expr = expr.replace("÷", "/")
        expr = expr.replace("×", "*")
        expr = expr.replace("−", "-")
        expr = expr.replace("π", "(math.pi)")
        expr = expr.replace("e", "(math.e)")
        expr = expr.replace("²", "**2")
        expr = expr.replace("^", "**")
        expr = expr.replace("×10**(", "*10**(")    # научная нотация

        # ── Обработка процента: число% → (число/100) ──
        expr = re.sub(r'(\d+\.?\d*)%', r'(\1/100)', expr)

        # ── Обработка факториала: число! → math.factorial(число) ──
        expr = re.sub(r'(\d+)!', r'math.factorial(\1)', expr)

        # ── Тригонометрические и другие функции ──
        # В зависимости от режима (DEG/RAD) оборачиваем аргумент
        if self.angle_mode == "DEG":
            # Прямые триг. функции: sin(x) → math.sin(math.radians(x))
            for fn in ["sin", "cos", "tan"]:
                expr = expr.replace(f"{fn}(", f"math.{fn}(math.radians(")
                # Нужно добавить закрывающую скобку для radians
                # Делаем это после всех замен — через подсчёт скобок
            # Обратные триг.: asin(x) → math.degrees(math.asin(x))
            for fn in ["asin", "acos", "atan"]:
                expr = expr.replace(f"{fn}(", f"math.d(math.{fn}(")
        else:
            # RAD — просто заменяем на math.*
            for fn in ["sin", "cos", "tan", "asin", "acos", "atan"]:
                expr = expr.replace(f"{fn}(", f"math.{fn}(")

        expr = expr.replace("ln(", "math.log(")
        expr = expr.replace("log(", "math.log10(")
        expr = expr.replace("√(", "math.sqrt(")
        expr = expr.replace("abs(", "abs(")
        expr = expr.replace("(math.e)xp(", "math.exp(")  # fix 'e' replacement in 'exp'

        # ── Автозакрытие скобок ──
        open_count = expr.count("(")
        close_count = expr.count(")")
        expr += ")" * (open_count - close_count)

        # ── Для DEG-режима — добавляем закрывающие скобки для radians/degrees ──
        if self.angle_mode == "DEG":
            # Каждый math.sin(math.radians(... нуждается в дополнительной ')' перед закрытием sin
            # Используем промежуточную функцию-обёртку
            expr = expr.replace("math.d(", "__deg__(")

        # ── Неявное умножение: 2π → 2*π,  )(  →  )*( ──
        expr = re.sub(r'(\d)$$', r'\1*(', expr)
        expr = re.sub(r'$$\(', r')*(', expr)

        # ── Безопасный eval с ограниченным пространством имён ──
        allowed = {
            "math": math,
            "abs": abs,
            "__deg__": lambda x: math.degrees(x),
            "__builtins__": {},
        }

        return eval(expr, allowed)

    def _show_error(self, msg: str):
        """Отображает ошибку на дисплее."""
        self.lbl_expression.config(text=self.expression)
        self.lbl_result.config(text=msg)
        self.expression = ""

    # ─────────────────── ОБНОВЛЕНИЕ ДИСПЛЕЯ ───────────────────────────────────

    def _update_display(self, text: str):
        """Обновляет нижнюю строку дисплея."""
        self.lbl_result.config(text=text)

    # ─────────────────── ВВОД С КЛАВИАТУРЫ ────────────────────────────────────

    def _on_key_press(self, event):
        """Обработчик клавиатурного ввода."""
        key = event.char
        keysym = event.keysym

        if key in "0123456789.()+-":
            self._on_button_click(key)
        elif key == "*":
            self._on_button_click("×")
        elif key == "/":
            self._on_button_click("÷")
        elif key == "^":
            self._on_button_click("^(")
        elif key == "%":
            self._on_button_click("%")
        elif keysym == "Return" or keysym == "KP_Enter":
            self._on_button_click("=")
        elif keysym == "BackSpace":
            self._on_button_click("⌫")
        elif keysym == "Escape":
            self._on_button_click("C")

    # ─────────────────── ЗАПУСК ИГРЫ «ЗМЕЙКА» ────────────────────────────────

    def _open_snake_game(self):
        """Открывает новое окно с игрой Змейка."""
        SnakeGame(self.root)


# ═══════════════════════════════════════════════════════════════════════════════
#                          КЛАСС ИГРЫ «ЗМЕЙКА»
# ═══════════════════════════════════════════════════════════════════════════════

class SnakeGame:
    """
    Классическая игра «Змейка» в отдельном окне.
    Управление: стрелки клавиатуры.
    Столкновение со стеной или своим хвостом — конец игры.
    """

    # Настройки игрового поля
    CELL_SIZE = 20          # размер одной клетки (пикселей)
    COLS = 25               # количество колонок
    ROWS = 20               # количество строк
    SPEED_INITIAL = 120     # начальная скорость (мс между кадрами)
    SPEED_MIN = 60          # максимальная скорость
    SPEED_STEP = 2          # ускорение за каждое яблоко

    # Цвета
    COLOR_BG = "#0f0f1a"
    COLOR_GRID = "#1a1a2e"
    COLOR_SNAKE_HEAD = "#a6e3a1"
    COLOR_SNAKE_BODY = "#74c7a4"
    COLOR_FOOD = "#f38ba8"
    COLOR_FOOD_GLOW = "#f5c2e7"
    COLOR_TEXT = "#cdd6f4"

    def __init__(self, parent):
        self.window = tk.Toplevel(parent)
        self.window.title("🐍 Змейка")
        self.window.resizable(False, False)
        self.window.configure(bg=self.COLOR_BG)

        # Ширина и высота канваса
        self.width = self.COLS * self.CELL_SIZE
        self.height = self.ROWS * self.CELL_SIZE

        # ── Верхняя панель: счёт + рекорд ──
        top = tk.Frame(self.window, bg=self.COLOR_BG)
        top.pack(fill="x", padx=10, pady=(10, 0))

        self.lbl_score = tk.Label(
            top, text="Счёт: 0", font=("Consolas", 14, "bold"),
            bg=self.COLOR_BG, fg=self.COLOR_TEXT
        )
        self.lbl_score.pack(side="left")

        self.lbl_best = tk.Label(
            top, text="Рекорд: 0", font=("Consolas", 14),
            bg=self.COLOR_BG, fg="#89b4fa"
        )
        self.lbl_best.pack(side="right")

        # ── Игровое поле (Canvas) ──
        self.canvas = tk.Canvas(
            self.window, width=self.width, height=self.height,
            bg=self.COLOR_BG, highlightthickness=1, highlightbackground="#313244"
        )
        self.canvas.pack(padx=10, pady=10)

        # ── Кнопки управления ──
        bottom = tk.Frame(self.window, bg=self.COLOR_BG)
        bottom.pack(fill="x", padx=10, pady=(0, 10))

        self.btn_start = tk.Button(
            bottom, text="▶  Новая игра", font=("Consolas", 12, "bold"),
            bg="#a6e3a1", fg="#1e1e2e", bd=0, cursor="hand2", width=16,
            command=self._start_game
        )
        self.btn_start.pack(side="left", padx=(0, 5))

        btn_close = tk.Button(
            bottom, text="✕  Закрыть", font=("Consolas", 12, "bold"),
            bg="#f38ba8", fg="#1e1e2e", bd=0, cursor="hand2", width=16,
            command=self.window.destroy
        )
        btn_close.pack(side="right")

        # ── Привязка клавиш ──
        self.window.bind("<Key>", self._on_key)

        # ── Состояние ──
        self.snake = []
        self.direction = "Right"
        self.next_direction = "Right"
        self.food = None
        self.score = 0
        self.best_score = 0
        self.running = False
        self.speed = self.SPEED_INITIAL
        self._after_id = None  # id таймера для корректной отмены

        # Рисуем приветственный экран
        self._draw_welcome()

        # Фокус для приёма клавиш
        self.window.focus_force()

        # Центрирование
        self.window.update_idletasks()
        EngineeringCalculator._center_window(self.window)

    # ─────────────────── ПРИВЕТСТВЕННЫЙ ЭКРАН ─────────────────────────────────

    def _draw_welcome(self):
        """Начальный экран с подсказкой."""
        self.canvas.delete("all")
        self._draw_grid()
        cx, cy = self.width // 2, self.height // 2
        self.canvas.create_text(
            cx, cy - 30, text="🐍 ЗМЕЙКА", font=("Consolas", 28, "bold"),
            fill=self.COLOR_SNAKE_HEAD
        )
        self.canvas.create_text(
            cx, cy + 20, text="Нажмите «Новая игра» или Enter",
            font=("Consolas", 13), fill=self.COLOR_TEXT
        )
        self.canvas.create_text(
            cx, cy + 50, text="Управление: ← ↑ → ↓",
            font=("Consolas", 12), fill="#6c7086"
        )

    # ─────────────────── ИНИЦИАЛИЗАЦИЯ НОВОЙ ИГРЫ ─────────────────────────────

    def _start_game(self):
        """Запускает (или перезапускает) игру."""
        # Отмена предыдущего таймера
        if self._after_id:
            self.window.after_cancel(self._after_id)
            self._after_id = None

        # Начальное положение змейки (центр поля, 3 сегмента)
        mid_x = self.COLS // 2
        mid_y = self.ROWS // 2
        self.snake = [(mid_x, mid_y), (mid_x - 1, mid_y), (mid_x - 2, mid_y)]
        self.direction = "Right"
        self.next_direction = "Right"
        self.score = 0
        self.speed = self.SPEED_INITIAL
        self.running = True

        self.lbl_score.config(text="Счёт: 0")
        self.btn_start.config(text="⟳  Заново")

        self._place_food()
        self._game_loop()

    # ─────────────────── ОСНОВНОЙ ИГРОВОЙ ЦИКЛ ───────────────────────────────

    def _game_loop(self):
        """Один шаг игры: движение → проверка → отрисовка → рекурсивный вызов."""
        if not self.running:
            return

        self._move_snake()

        if self._check_collision():
            self._game_over()
            return

        # Проверяем, съела ли змейка еду
        if self.snake[0] == self.food:
            self.score += 1
            self.lbl_score.config(text=f"Счёт: {self.score}")
            self._place_food()
            # Ускорение
            self.speed = max(self.SPEED_MIN, self.speed - self.SPEED_STEP)
        else:
            self.snake.pop()  # убираем хвост (змейка не растёт)

        self._draw()

        # Планируем следующий кадр
        self._after_id = self.window.after(self.speed, self._game_loop)

    # ─────────────────── ДВИЖЕНИЕ ─────────────────────────────────────────────

    def _move_snake(self):
        """Двигает змейку на один шаг в текущем направлении."""
        self.direction = self.next_direction
        hx, hy = self.snake[0]
        moves = {
            "Up":    (0, -1),
            "Down":  (0,  1),
            "Left":  (-1, 0),
            "Right": (1,  0),
        }
        dx, dy = moves[self.direction]
        new_head = (hx + dx, hy + dy)
        self.snake.insert(0, new_head)

    # ─────────────────── КОЛЛИЗИИ ─────────────────────────────────────────────

    def _check_collision(self) -> bool:
        """Проверяет столкновение со стеной или собственным хвостом."""
        hx, hy = self.snake[0]
        # Стена
        if hx < 0 or hx >= self.COLS or hy < 0 or hy >= self.ROWS:
            return True
        # Хвост (голова уже в списке — проверяем дубликат)
        if self.snake[0] in self.snake[1:]:
            return True
        return False

    # ─────────────────── РАЗМЕЩЕНИЕ ЕДЫ ──────────────────────────────────────

    def _place_food(self):
        """Помещает еду в случайную свободную клетку."""
        while True:
            pos = (random.randint(0, self.COLS - 1),
                   random.randint(0, self.ROWS - 1))
            if pos not in self.snake:
                self.food = pos
                return

    # ─────────────────── ОТРИСОВКА ────────────────────────────────────────────

    def _draw(self):
        """Полная перерисовка игрового поля."""
        self.canvas.delete("all")
        self._draw_grid()
        self._draw_food()
        self._draw_snake()

    def _draw_grid(self):
        """Рисует лёгкую сетку."""
        for x in range(0, self.width, self.CELL_SIZE):
            self.canvas.create_line(x, 0, x, self.height, fill=self.COLOR_GRID)
        for y in range(0, self.height, self.CELL_SIZE):
            self.canvas.create_line(0, y, self.width, y, fill=self.COLOR_GRID)

    def _draw_snake(self):
        """Рисует змейку (голова — ярче, тело — чуть темнее)."""
        cs = self.CELL_SIZE
        for i, (x, y) in enumerate(self.snake):
            color = self.COLOR_SNAKE_HEAD if i == 0 else self.COLOR_SNAKE_BODY
            pad = 1 if i == 0 else 2  # голова чуть крупнее
            self.canvas.create_rectangle(
                x * cs + pad, y * cs + pad,
                (x + 1) * cs - pad, (y + 1) * cs - pad,
                fill=color, outline=""
            )
        # Глаза на голове
        hx, hy = self.snake[0]
        cx = hx * cs + cs // 2
        cy = hy * cs + cs // 2
        offsets = {
            "Up":    [(-3, -3), (3, -3)],
            "Down":  [(-3,  3), (3,  3)],
            "Left":  [(-3, -3), (-3, 3)],
            "Right": [(3, -3),  (3,  3)],
        }
        for ox, oy in offsets.get(self.direction, [(0, 0)]):
            self.canvas.create_oval(
                cx + ox - 2, cy + oy - 2, cx + ox + 2, cy + oy + 2,
                fill="#1e1e2e", outline=""
            )

    def _draw_food(self):
        """Рисует еду с эффектом «свечения»."""
        if not self.food:
            return
        cs = self.CELL_SIZE
        fx, fy = self.food
        # Свечение (полупрозрачный круг побольше)
        self.canvas.create_oval(
            fx * cs - 2, fy * cs - 2,
            (fx + 1) * cs + 2, (fy + 1) * cs + 2,
            fill=self.COLOR_FOOD_GLOW, outline=""
        )
        # Само «яблоко»
        self.canvas.create_oval(
            fx * cs + 3, fy * cs + 3,
            (fx + 1) * cs - 3, (fy + 1) * cs - 3,
            fill=self.COLOR_FOOD, outline=""
        )

    # ─────────────────── КОНЕЦ ИГРЫ ───────────────────────────────────────────

    def _game_over(self):
        """Обработка конца игры."""
        self.running = False
        if self.score > self.best_score:
            self.best_score = self.score
            self.lbl_best.config(text=f"Рекорд: {self.best_score}")

        # Рисуем экран Game Over
        self._draw()
        cx, cy = self.width // 2, self.height // 2
        # Затемнение
        self.canvas.create_rectangle(
            0, cy - 50, self.width, cy + 55,
            fill="#11111b", outline="", stipple="gray50"
        )
        self.canvas.create_text(
            cx, cy - 20, text="GAME OVER",
            font=("Consolas", 26, "bold"), fill="#f38ba8"
        )
        self.canvas.create_text(
            cx, cy + 15, text=f"Счёт: {self.score}",
            font=("Consolas", 16), fill=self.COLOR_TEXT
        )
        self.canvas.create_text(
            cx, cy + 40, text="Нажмите Enter для новой игры",
            font=("Consolas", 11), fill="#6c7086"
        )

    # ─────────────────── УПРАВЛЕНИЕ КЛАВИШАМИ ─────────────────────────────────

    def _on_key(self, event):
        """Обработка нажатий клавиш в окне игры."""
        keysym = event.keysym

        # Запуск/рестарт по Enter
        if keysym in ("Return", "KP_Enter"):
            self._start_game()
            return

        # Запрещаем разворот на 180°
        opposites = {"Up": "Down", "Down": "Up", "Left": "Right", "Right": "Left"}
        if keysym in opposites and self.running:
            if opposites[keysym] != self.direction:
                self.next_direction = keysym

        # WASD-поддержка
        wasd = {"w": "Up", "s": "Down", "a": "Left", "d": "Right"}
        if keysym in wasd and self.running:
            mapped = wasd[keysym]
            if opposites[mapped] != self.direction:
                self.next_direction = mapped


# ═══════════════════════════════════════════════════════════════════════════════
#                            ТОЧКА ВХОДА
# ═══════════════════════════════════════════════════════════════════════════════

def main():
    root = tk.Tk()

    # Иконка окна (текстовый fallback для кросс-платформенности)
    try:
        root.iconname("Calc")
    except Exception:
        pass

    app = EngineeringCalculator(root)
    root.mainloop()


if __name__ == "__main__":
    main()

Итог

Первый этап

Второй этап

Третий этап

Четвертый этап

Сумма

ChatGPT 5.4

2,5 балла / 25 рублей

3 балла / 24 рублей 

3 балла / 15 рублей

3 балла / 48 рублей

11,5 баллов / 112 рублей

Gemini 3.1 Pro

3 балла / 20 рублей

2 балла / 16 рублей

3 балла / 22 рубля

2,5 балла / 29 рублей

10,5 баллов / 87 рублей 

Claude Opus 4.6

3 балла / 68 рублей

2 балла / 38 рублей

3 балла / 29 рублей

2 балла / 73 рубля

10 баллов / 208 рублей

Итог прозрачен – победил ChatGPT 5.4. Что немного меня удивляет. В предыдущих столкновениях он был на втором-третьем месте, а тут выбился в лидеры.

Цена, важный параметр. Тут уже лидирует Gemini 3.1 Pro. Хоть модель и допустила промашку в разделе с программированием, в остальном все придирки – мое личное мнение. 

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

Спасибо за прочтение!