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

Данная статья написана "по мотивам" этой публикации и обсуждений в комментариях к ней. Я никак не планирую использовать нейросети в своей деятельности, но мне стало интересно, насколько удобоваримый результат можно получить с помощью бесплатной нейросети.

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

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

Итак, я обратился к чатужпт с вопросом, какую сеть он посоветует. Посоветовал себя, я и согласился. Среди перечисленных им языков были Питон и разные виды Си. Но Си я не знаю и на нем не пишу. Я уже больше 30 лет пишу на Бейсике разных видов (от qb4 до VBNET), но Бейсика в списке не было. Зато был Питон. Это интересно как раз тем, что Питон я мельком изучал и программы на нем писал в качестве тренировки. Даже две статьи про свое обучение написал, а тексты программ есть там же в разделе "Исходники".

Но считать себя знатоком Питона никак не могу и тут как раз подходит вайб-кодинг: язык я знаю плохо и типа мне нужна помощь. Но все же код могу понять. Понятно, что мои программы на Питоне дилетантские и неоптимальные. Тем интереснее было посмотреть, что выдаст нейронка и кто из нас в итоге победит! :)

Ну и нейросети, как я понял, как раз заточены на Питон и по идее результат должен превзойти мой на порядок. Да и для Виндовс кроме Питона и разных версий Бейсика у меня примеров и нет. Так что выбор был предрешен. Я решал три задачи. Расскажу поочередно.

1. "Напиши программу на Питоне, которая печает на консоли свой код".

Ну, ЖПТ обрадовался, что это "известная задача под названием quine" и на Питоне она имеет очень короткое решение в виде

s = 's = %r; print(s%%s)'; print(s%s)

Тут я почувствовал себя дураком, потому что мой код (формальным переводом с языка sb1) был таким:

K=chr(34)
T=['']*9
T[0]="K=chr(34)"
T[1]="T=['']*9"
T[2]="for J in range(0,2):"
T[3]="    print(T[J])"
T[4]="for J in range(0,9):"
T[5]="    print(chr(84)+chr(91)+str(J)+chr(93)+chr(61)+K+T[J]+K)"
T[6]="for J in range(2,9):"
T[7]="    print(T[J])"
T[8]="input(chr(13))"
for J in range(0,2):
    print(T[J])
for J in range(0,9):
    print(chr(84)+chr(91)+str(J)+chr(93)+chr(61)+K+T[J]+K)
for J in range(2,9):
    print(T[J])
input(chr(13))

Признав свое поражение, я приступил ко второй задаче.

2. "Напиши программу, которая выводит окно размером 200*100 с белым фоном, где черным цветом выводится текущее время. Размер окна можно менять как угодно и вывод времени должен вписываться в окно в любом случае путем изменения размера шрифта".

Я не уверен, что дословно промпт был точно таким, но задача такая.

Как ни удивительно, но ЖПТ сделал такую программу, долго расхваливая как плавно она работает. Должен признать, что чисто визуально она действительно работает плавнее моей, но я просто не стал с этим заморачиваться, когда писал свою: в принципе, вполне мог бы. Однако, она тоже имеет свои минусы, в т.ч. слишком большие поля. Ну да ладно.

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

Приведу здесь код ИИ и свой код тоже для сравнения.

# ЧАСЫ, код нейронки
import tkinter as tk
from tkinter import font
import time

PADDING = 10

class Clock:
    def __init__(self, root):
        self.root = root
        root.title("Часы")
        root.geometry("200x100")
        root.configure(bg="white")
        self.label = tk.Label(root, text="", bg="white", fg="black")
        self.label.place(relx=0.5, rely=0.5, anchor="center")
        self.current_time = ""
        root.bind("<Configure>", self.on_resize)
        self.update_time()

    def fit_font(self):
        w = max(1, self.root.winfo_width() - 2 * PADDING)
        h = max(1, self.root.winfo_height() - 2 * PADDING)
        low = 1; high = 500; best = 1
        while low <= high:
            mid = (low + high) // 2
            f = font.Font(family="Arial", size=mid, weight="bold")
            text_w = f.measure(self.current_time)
            text_h = f.metrics("linespace")
            if text_w <= w and text_h <= h:
                best = mid; low = mid + 1
            else:
                high = mid - 1
        self.label.configure(font=("Arial", best, "bold"))

    def update_time(self):
        self.current_time = time.strftime("%H:%M:%S")
        self.label.config(text=self.current_time)
        self.fit_font()
        self.root.after(1000, self.update_time)

    def on_resize(self, event):
        self.fit_font()

root = tk.Tk()
Clock(root)
root.mainloop()
# ЧАСЫ, код мой из "Исходников"
import datetime
import threading
import sys
import math
from tkinter import *
frmHASY=Tk()
frmHASY.title("Текущее время")
frmHASY.geometry('300x60')
frmHASY.configure(bg='#FFFFFF')
METKA=Label(frmHASY, font=('Courier New', 40, 'bold'), background='#FFFFFF', foreground='#000000')
METKA.place(relx=0.5, rely=0.5, anchor=CENTER)

def OnTimer():
  try:
    W=frmHASY.winfo_width()/7
    H=frmHASY.winfo_height()
    M=min(W,H)
    RAZ=math.floor(M)
    METKA.config(font=('Courier New', RAZ, 'bold'))
    STRO=datetime.datetime.now().time().strftime("%H:%M:%S")
    METKA.config(text=STRO)
    VREM=threading.Timer(1.0, OnTimer).start()
  except:
    sys.exit()
    
OnTimer()
frmHASY.mainloop()

Как видно, количество строк у меня вдвое меньше, при том, что я модернизировал код ИИ, убрав лишние пустые строки и разместив некоторые операторы через ";" в одну строку. Просто для компактности представления.

Сравнить коды мне сложно (помимо размера) и эту функцию я отдаю профессионалам в Питоне. :) Просто замечу, что я свой код тоже "переводил" с Бейсика, что видно даже по именам переменных. Понимаю, что в целом такой путь порочен, но я только знакомился с языком и этот путь был самым простым. И получилось же!

А вот с третьей задачей даже не знаю как быть в плане описания... Будет длинно!

3. Разумеется, тут я выбрал уже достаточно сложную задачу, к тому же имеющую некие "подводные камни", о чем далее.

Поначалу я обратился к чатужпт даже без авторизации (учтите, что чисто хронологически эту задачу я решал ДО перечисленных выше). Задачу эту я тоже выбрал из своего раздела "Исходники", там разные задачи на разных языках реализованные.

Какая задача выбрана? Я, конечно, не смогу тут привести мой достаточно длинный диалог с чатом, но отмечу основные моменты.

Задача была такая: Окно 640*480, синий фон. Вращаются два желтых кольца справа налево и сверху вниз, синхронно. Это один режим. Переключение на второй - клавишей "Enter".

Второй режим. Черный фон окна и из центра растут круги стандартных цветов: красный, желтый, зеленый, голубой, синий и фиолетовый. Потом снова красный и т.д. Этот режим имеет два варианта (переключение клавишей "Space"):

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

На самом деле, я упростил задачу для нейросети, поскольку изначально скорость разных цветов предполагалась разная. Но уж ладно... Изначально ЖПТ сказал, что нет проблем, он подключит то, и то, и вообще сделает 3D-эффекты. Я сказал, что можно использовать только стандартные библиотеки Питона и никаких 3Д не нужно. Что меня удивило - так это сразу замечание ЖПТ о тех подводных камнях, через которые прошел я сам при написании программы: я привык, что можно просто рисовать окружности нужным цветом и потом поверх рисовать фоном. И так "двигать" кольца. Но на Питоне это все объекты и ресурсы улетают.

ЖПТ предложил заранее создать кольца и таки просто двигать их (я к этому варианту пришел, но далеко не сразу). Вообще он по ходу обсуждения много чего предлагал: иногда лишнего, иногда полезного, но в целом задачу понял вроде бы правильно, что меня тоже удивило, ибо задача мне казалась непривычной.

Поскольку я писал эту программу по ходу изучения Питона, то много раз переделывал свой код как из-за прямых ошибок, так и из-за выбора неоптимальных методов (например, в движении колец). Но мне простительно, я этот язык не знал! А вот ЖПТ, выдавая код по частям в силу ограничений на размер сообщения, по ходу выдачи менял прошлый текст и просил "заменить вот этот фрагмент на вот этот", ибо "так лучше" или "тот вообще не работает". Но это странно, ведь по идее ЖПТ Питон знает! Или как?

В общем, в итоге я получил код раза в три больше своего и на мой взгляд, сложнее. Но! Мой код "некошерный", т.е. это "лобовой" перевод кода с Бейсика на Питон с изменением соответствующих частей в силу разности организации (пример - работа с кольцами). Тем не менее, мой код работает верно, а код ЖПТ - нет. О чем я ему злорадно и сообщил, отметив, что человек, даже будучи дилетантом, сделал лучше.

ИИ признал ошибки, отметив, что "сделал их по невнимательности". Но позвольте! Роботы для того и нужны, чтобы быть полностью внимательными! Допускать ляпы (типа "да, я забыл вначале указать цвет окна, поэтому оно белое") - это удел человека. А тут... Но главное - неверный выбор алгоритма кругов ("да, ты прав, надо было сделать 256 кругов по 1 пикселю") и сбой в циклах цветов. Но это я даже не пытался анализировать и просить исправить, ибо эксперимент и так показал главное: человек лучше! И рассчитывать на ИИ нельзя.

Вообще мой эксперимент включал не только проверку возможности нейросети в программировании, но и проверку уровня сложности донесения до ИИ требуемой задачи. Но сами понимаете, повторить тут весь диалог (довольно долгий!) невозможно. Приведу тут код, полученный от ИИ (он очень длинный, поэтому загнал в спойлер):

Скрытый текст
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
первый вариант без авторизации

Rings & Waves
Демонстрационная программа на tkinter.

Enter  - переключение между режимами.
Space  - переключение варианта волн.
Esc    - выход.

Только стандартная библиотека Python.
"""

import math
import time
import tkinter as tk

# ============================================================
# Настраиваемые параметры
# ============================================================

WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480

CENTER_X = WINDOW_WIDTH // 2
CENTER_Y = WINDOW_HEIGHT // 2

FPS = 25
FRAME_TIME = int(1000 / FPS)

BACKGROUND_BLUE = "#0030C0"
BACKGROUND_BLACK = "#000000"

RING_COLOR = "#FFD800"
RING_WIDTH = 4
RING_RADIUS = 120

ROTATION_PERIOD = 2.0

RAINBOW = (
    "#ff0000",
    "#ffff00",
    "#00ff00",
    "#00ffff",
    "#0000ff",
    "#a000ff",
)

MAX_RADIUS = int(math.hypot(CENTER_X, CENTER_Y)) + 20

WAVE_SPEED = 180.0          # пикселей в секунду
WAVE_INTERVAL = 0.6         # секунд между волнами
MAX_WAVES = 12

GRADIENT_STEPS = 16

# ============================================================
# Вспомогательные функции
# ============================================================

def clamp(v, a, b):
    if v < a:
        return a
    if v > b:
        return b
    return v

def darken(color, k):
    """
    Затемнение цвета.
    k = 1.0 -> исходный цвет
    k = 0.0 -> чёрный
    """

    k = clamp(k, 0.0, 1.0)

    r = int(color[1:3], 16)
    g = int(color[3:5], 16)
    b = int(color[5:7], 16)

    r = int(r * k)
    g = int(g * k)
    b = int(b * k)

    return f"#{r:02x}{g:02x}{b:02x}"

# ============================================================
# Волна
# ============================================================

class Wave:

    def __init__(self, color):
        self.color = color
        self.radius = 0.0
        self.alive = True
        self.items = []

    def update(self, dt):
        self.radius += WAVE_SPEED * dt
        if self.radius > MAX_RADIUS:
            self.alive = False

    def draw(self, canvas):
        if not self.items:
            for item in self.items:
                canvas.itemconfigure(item, state="normal")
            r = self.radius
            for i in range(GRADIENT_STEPS):
                k = (GRADIENT_STEPS - i) / GRADIENT_STEPS
                color = darken(self.color, k * k)
                item = canvas.create_oval(
                    CENTER_X - r,
                    CENTER_Y - r,
                    CENTER_X + r,
                    CENTER_Y + r,
                    outline="",
                    fill=color,
                )
                self.items.append(item)
                r -= self.radius / GRADIENT_STEPS
        else:
            r = self.radius
            step = self.radius / GRADIENT_STEPS
            for i, item in enumerate(self.items):
                k = (GRADIENT_STEPS - i) / GRADIENT_STEPS
                color = darken(self.color, k * k)
                canvas.coords(
                    item,
                    CENTER_X - r,
                    CENTER_Y - r,
                    CENTER_X + r,
                    CENTER_Y + r,
                )
                canvas.itemconfigure(item, fill=color)
                r -= step
                if r <= 0:
                    break

    def destroy(self, canvas):
        for item in self.items:
            canvas.itemconfigure(item, state="hidden")
        self.items.clear()

# ============================================================
# Основной класс
# ============================================================

class App:

    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Rings & Waves")
        self.root.resizable(False, False)
        self.canvas = tk.Canvas(
            self.root,
            width=WINDOW_WIDTH,
            height=WINDOW_HEIGHT,
            highlightthickness=0,
        )
        self.canvas.pack()
        self.mode = 0          # 0 - кольца; 1 - волны
        self.wave_mode = 0     # 0 - одна волна; 1 - поток
        self.phase = 0.0
        self.last_time = time.perf_counter()
        self.last_wave = self.last_time
        self.waves = []
        self.root.bind("<Return>", self.on_enter)
        self.root.bind("<space>", self.on_space)
        self.root.bind("<Escape>", lambda e: self.root.destroy())

    # Создаём два кольца один раз

        self.ring_h = self.canvas.create_oval(
            0, 0, 0, 0,
            outline=RING_COLOR,
            width=RING_WIDTH
        )
        self.ring_v = self.canvas.create_oval(
            0, 0, 0, 0,
            outline=RING_COLOR,
            width=RING_WIDTH
        )
        self.animate()
        self.root.mainloop()

    def clear_waves(self):
        for wave in self.waves:
            wave.destroy(self.canvas)
        self.waves.clear()

    def on_enter(self, event=None):
        self.mode ^= 1
        if self.mode == 0:
            self.canvas.configure(bg=BACKGROUND_BLUE)
            self.canvas.itemconfigure(self.ring_h, state="normal")
            self.canvas.itemconfigure(self.ring_v, state="normal")
            self.clear_waves()
        else:
            self.canvas.configure(bg=BACKGROUND_BLACK)
            self.canvas.itemconfigure(self.ring_h, state="hidden")
            self.canvas.itemconfigure(self.ring_v, state="hidden")
            self.clear_waves()
            self.last_wave = time.perf_counter()

    def on_space(self, event=None):
        self.wave_mode ^= 1
        self.clear_waves()

    def draw_rings(self):
        t = time.perf_counter()
        angle = (t % ROTATION_PERIOD) / ROTATION_PERIOD
        angle *= 2.0 * math.pi
        k = abs(math.cos(angle))
        # Чтобы кольцо никогда полностью не исчезало
        k = max(k, 0.03)
        rx = RING_RADIUS
        ry = int(RING_RADIUS * k)
        self.canvas.coords(
            self.ring_h,
            CENTER_X - rx,
            CENTER_Y - ry,
            CENTER_X + rx,
            CENTER_Y + ry
        )
        rx = int(RING_RADIUS * k)
        ry = RING_RADIUS
        self.canvas.coords(
            self.ring_v,
            CENTER_X - rx,
            CENTER_Y - ry,
            CENTER_X + rx,
            CENTER_Y + ry
        )

    def spawn_wave(self):
        color = RAINBOW[len(self.waves) % len(RAINBOW)]
        self.waves.append(Wave(color))

    def update_waves(self, dt):
        now = time.perf_counter()
        if self.wave_mode == 0:
            if not self.waves:
                self.spawn_wave()
            elif not self.waves[0].alive:
                self.clear_waves()
                self.spawn_wave()
        else:
            if (now - self.last_wave) >= WAVE_INTERVAL:
                self.last_wave = now
                self.spawn_wave()
                if len(self.waves) > MAX_WAVES:
                    old = self.waves.pop(0)
                    old.destroy(self.canvas)
        alive = []
        for wave in self.waves:
            wave.update(dt)
            wave.draw(self.canvas)
            if wave.alive:
                alive.append(wave)
            else:
                wave.destroy(self.canvas)
        self.waves = alive

    def animate(self):
        now = time.perf_counter()
        dt = now - self.last_time
        self.last_time = now
        if self.mode == 0:
            self.draw_rings()
        else:
            self.update_waves(dt)
        self.root.after(FRAME_TIME, self.animate)

if __name__ == "__main__":
    App()

Но это не все…

Вечером с другого компьютера(!) я авторизовался(!) в чате и после выполнения задач в пунктах 1 и 2 выше, я решил вернуться к данной теме.

И вот тут что интересно: по идее, ЖПТ не должен был знать, что “я тот” и “я этот” - одно лицо, как и хранить информацию о диалоге без авторизации. Но на деле все оказалось, видимо, не так.

Нет, он на сказал “привет, я помню”. Но задачу он усвоил значительно быстрее! Однако, он создал несколько вариантов последовательно, опять же, на ходу “придумывая” улучшения. Поскольку я предупредил о длине статьи, я приведу тут два его варианта, а в конце - свой, из “Исходников”. Кстати, тут я ему все же сказал, что скорость должна зависеть от цвета круга.

Вариант первый (с ошибками!).

Скрытый текст
# вариант с ошибками
import tkinter as tk
import math
import time

# Настройки

WIDTH = 640
HEIGHT = 480
FPS = 60
FRAME = int(1000 / FPS)
RING_PERIOD = 4.0
BACKGROUND_BLUE = "#0000C0"

COLORS = [
    (255,   0,   0),   # красный
    (255, 255,   0),   # желтый
    (0,   255,   0),   # зеленый
    (0,   255, 255),   # голубой
    (0,     0, 255),   # синий
    (255,   0, 255),   # фиолетовый
]

COLOR_TIME = [3.0, 3.5, 4.0, 4.5, 5.0, 5.5,]

class Demo:

    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Demo")
        self.root.geometry(f"{WIDTH}x{HEIGHT}")
        self.canvas = tk.Canvas(self.root, width=WIDTH, height=HEIGHT, bg=BACKGROUND_BLUE, highlightthickness=0)
        self.canvas.pack(fill="both", expand=True)
        self.root.bind("<Return>", self.enter_pressed)
        self.root.bind("<space>", self.space_pressed)
        self.canvas.bind("<Configure>", self.resize)
        self.mode = 0
        # 0 - кольца
        # 1 - круги с радиальным градиентом
        # 2 - круги с временным затуханием
        self.start_time = time.perf_counter()
        self.cx = WIDTH / 2; self.cy = HEIGHT / 2
        self.radius = min(WIDTH, HEIGHT) * 0.35
        self.ring1 = self.canvas.create_oval(0, 0, 0, 0, outline="yellow", width=2)
        self.ring2 = self.canvas.create_oval(0, 0, 0, 0, outline="yellow", width=2)
        self.color_index = 0
        self.circle_radius = 0
        self.circle_items = []
        self.last_radius = -1
        self.mode_start = time.perf_counter()
        self.animate()
        self.root.mainloop()

    def resize(self, event):
        self.cx = event.width / 2;  self.cy = event.height / 2
        self.radius = min(event.width, event.height) * 0.35

    def enter_pressed(self, event):
        if self.mode == 0:
            self.mode = 1
            self.mode_start = time.perf_counter()
            self.color_index = 0
            self.prepare_circle()
        else:
            self.mode = 0
            self.canvas.delete("all")
            self.canvas.configure(bg=BACKGROUND_BLUE)
            self.ring1 = self.canvas.create_oval(0,0,0,0, outline="yellow", width=2)
            self.ring2 = self.canvas.create_oval(0,0,0,0, outline="yellow", width=2)

    def space_pressed(self, event):
        if self.mode == 0:
            return
        if self.mode == 1:
            self.mode = 2
        else:
            self.mode = 1
        self.mode_start = time.perf_counter()
        self.color_index = 0
        self.prepare_circle()

    def animate(self):
        if self.mode == 0:
            self.animate_rings()
        else:
            self.animate_circles()
        self.root.after(FRAME, self.animate)

    def animate_rings(self):
        t = time.perf_counter() - self.start_time
        phase = 2 * math.pi * t / RING_PERIOD
        c = abs(math.cos(phase))
        rx1 = self.radius; ry1 = self.radius * c
        rx2 = self.radius * c; ry2 = self.radius
        self.canvas.coords(self.ring1, self.cx - rx1, self.cy - ry1, self.cx + rx1, self.cy + ry1)
        self.canvas.coords(self.ring2, self.cx - rx2, self.cy - ry2, self.cx + rx2, self.cy + ry2)

    def rgb(self, r, g, b):
        return "#{:02x}{:02x}{:02x}".format(
            max(0, min(255, int(r))),
            max(0, min(255, int(g))),
            max(0, min(255, int(b)))
        )

    def prepare_circle(self):
        self.canvas.delete("all")
        self.canvas.configure(bg="black")
        self.circle_items.clear()
        self.last_radius = -1
        self.mode_start = time.perf_counter()

    def next_color(self):
        self.color_index += 1
        if self.color_index >= len(COLORS):
            self.color_index = 0
        self.prepare_circle()

    def animate_circles(self):
        elapsed = time.perf_counter() - self.mode_start
        duration = COLOR_TIME[self.color_index]
        progress = elapsed / duration
        if progress >= 1.0:
            self.next_color(); return
        max_radius = self.radius
        radius = int(progress * max_radius)
        if radius <= self.last_radius:
            return
        base = COLORS[self.color_index]
        while self.last_radius < radius:
            self.last_radius += 1
            r = self.last_radius
            if self.mode == 1:
                # Радиальный градиент
                k = 1.0 - r / max_radius
            else:
                # Временное затухание
                k = 1.0 - progress
            if k < 0:
                k = 0
            color = self.rgb(base[0] * k, base[1] * k, base[2] * k)
            item = self.canvas.create_oval(
                self.cx - r,
                self.cy - r,
                self.cx + r,
                self.cy + r,
                outline=color,
                width=2
            )
            self.circle_items.append(item)

Demo()

Во-первых, тут нет выхода по эскейпу. Во-вторых, варианты 1 и 2 кругов ничем визуально не отличаются и работают неверно. Я не буду разбирать код и искать ошибки (я же дилетант по условию задачи!). Но я указал ИИ, что код не работает как надо. Получил массу извинений и предложеений по улучшению. В итоге ИИ дал другой вариант, гораздо более компактный и как ни странно, в целом верный.

Мне пришлось добавить (уже самому) выход по эскейпу, а также убрать строку, которую он все же оставил. Про эскейп я не говорил, а вот лишняя строка - это все же ошибка! В коде программы есть комментарий, поймете.

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

Код исправленной программы:

Скрытый текст
# hind by chatgpt
import tkinter as tk
import math,time

WIDTH,HEIGHT=640,480
FPS=60
FRAME=int(1000/FPS)
RING_PERIOD=4.0
COLORS=[(255,0,0),(255,255,0),(0,255,0),(0,255,255),(0,0,255),(255,0,255)]
TIMES=[3, 3.5, 4, 4.5, 5, 5.5]

def rgb(r,g,b):
    return f"#{int(max(0,min(255,r))):02x}{int(max(0,min(255,g))):02x}{int(max(0,min(255,b))):02x}"

class Demo:
    def __init__(self):
        self.root=tk.Tk()
        self.root.geometry(f"{WIDTH}x{HEIGHT}")
        self.cv=tk.Canvas(self.root,bg="#0040c0",highlightthickness=0)
        self.cv.pack(fill="both",expand=True)
        self.cx,self.cy=WIDTH/2,HEIGHT/2
        self.R=min(WIDTH,HEIGHT)*0.35
        self.r1=self.cv.create_oval(0,0,0,0,outline="yellow",width=2)
        self.r2=self.cv.create_oval(0,0,0,0,outline="yellow",width=2)
        self.mode=0
        self.var=0
        self.start=time.perf_counter()
        self.mode_start=self.start
        self.items=[]
        self.last=-1
        self.color=0
        self.root.bind("<Configure>",self.resize)
        self.root.bind("<Return>",self.enter)
        self.root.bind("<space>",self.space)
        self.root.bind("<Escape>",lambda e: self.root.destroy()) # ДОБАВЛЕНО МНОЙ
        self.loop()
        self.root.mainloop()

    def resize(self,e):
        self.cx=e.width/2; self.cy=e.height/2; self.R=min(e.width,e.height)*0.35

    def enter(self,e):
        if self.mode==0:
            self.mode=1; self.cv.delete("all"); self.cv.configure(bg="black")
            self.items=[]; self.last=-1; self.color=0; self.mode_start=time.perf_counter()
        else:
            self.mode=0; self.cv.delete("all"); self.cv.configure(bg="#0040c0")
            self.r1=self.cv.create_oval(0,0,0,0,outline="yellow",width=2)
            self.r2=self.cv.create_oval(0,0,0,0,outline="yellow",width=2)

    def space(self,e):
        if self.mode:
            self.var=1-self.var
            self.cv.delete("all"); self.items=[]; self.last=-1; self.color=0; self.mode_start=time.perf_counter()

    def loop(self):
        if self.mode==0:self.rings()
        else:self.circles()
        self.root.after(FRAME,self.loop)

    def rings(self):
        t=(time.perf_counter()-self.start)/RING_PERIOD*2*math.pi
        c=abs(math.cos(t))
        self.cv.coords(self.r1,self.cx-self.R,self.cy-self.R*c,self.cx+self.R,self.cy+self.R*c)
        self.cv.coords(self.r2,self.cx-self.R*c,self.cy-self.R,self.cx+self.R*c,self.cy+self.R)

    def circles(self):
        dur=TIMES[self.color]
        p=(time.perf_counter()-self.mode_start)/dur
        if p>=1:
            self.color=(self.color+1)%len(COLORS)
#            self.cv.delete("all"); ОШИБКА! - убрано мной
            self.items=[]; self.last=-1; self.mode_start=time.perf_counter();
            return
        r=int(self.R*p)
        base=COLORS[self.color]
        if self.var==1:
            k=1-p
            col=rgb(base[0]*k,base[1]*k,base[2]*k)
            for it in self.items:
                self.cv.itemconfig(it,outline=col)
        while self.last<r:
            self.last+=1
            rr=self.last
            if self.var==0:
                k=1-rr/self.R
                col=rgb(base[0]*k,base[1]*k,base[2]*k)
            it=self.cv.create_oval(self.cx-rr,self.cy-rr,self.cx+rr,self.cy+rr,outline=col,width=2)
            self.items.append(it)

Demo()

Этот вариант (с моими дополнениями) уже работает как надо! И я хочу для сравнения привести свой код тоже. Хотя он, как и предыдущие, получен переводом с Бейсика.

Мой код:

Скрытый текст
# мой код из "Исходников"
import time
import sys
from tkinter import *

WIR=640; VYS=480
OKNO=Tk()
OKNO.title("HIND (Press <Enter> (and <Space>) or <Esc>)")
OKNO.geometry(f'{WIR}x{VYS}')
OKNO.configure(bg='#000000')
X=int(WIR/2); Y=int(VYS/2)
W=int(OKNO.winfo_screenwidth()/2); H=int(OKNO.winfo_screenheight()/2)
W=W-X; H=H-Y
OKNO.geometry(f'+{W}+{H}')
OKNO.resizable(False, False)
POLE=Canvas(OKNO, width=WIR, height=VYS, bg="#000080")
POLE.pack()
PRIZ=-1; ZAL=0; R=min(X,Y)-6; KOF=X/Y
OVAL1=[0]*128
OVAL2=[0]*128
OVAL3=[0]*128

def TISTOP():
    POLE.delete('all')
    OKNO.destroy()
    sys.exit()

def RGB(r,g,b):
    CC=f'#{r:02x}{g:02x}{b:02x}'
    return CC

def WREM(TTT):
    VREM = time.time()
    while time.time()-VREM<TTT:
        continue

def ROTOR():
    global PRIZ,R,X,Y
    I=1; Z=1; B=R
    POLE.delete('all')
    YG0=Y-B; YG1=Y+B
    XV0=X-B; XV1=X+B
    coordG=X-R,YG0,X+R,YG1
    coordV=XV0,Y-R,XV1,Y+R
    POLE.create_rectangle(0,0,WIR-1,VYS-1,fill="#000080",outline="#000080")
    OVALG=POLE.create_oval(coordG,outline="#FFFF00")
    OVALV=POLE.create_oval(coordV,outline="#FFFF00")
    while PRIZ<0:
        S=Z*I/9; I=I+S
        if S>0 and I>R or S<0 and I<1: Z = -Z
        if I<1: I = 1
        B=R/I
        YG0=Y-B; YG1=Y+B
        XV0=X-B; XV1=X+B
        coordG=X-R,YG0,X+R,YG1
        coordV=XV0,Y-R,XV1,Y+R
        POLE.coords(OVALG,coordG)
        POLE.coords(OVALV,coordV)
        TT=int(100/(1+I/B))
        WREM(TT/1000)
        POLE.update()

def SFERA():
    global KOF,X,Y,ZAL,PRIZ,OVAL1,OVAL2,OVAL3
    if PRIZ==0: return
    POLE.delete('all')
    QQ="#000000"
    POLE.create_rectangle(0,0,WIR-1,VYS-1,fill=QQ,outline=QQ)
    for I in range(1,128):
        RAD=I*2; X3=X-I; Y3=Y-I
        OVAL1[I]=POLE.create_oval(X3,Y3,X3+RAD,Y3+RAD,outline=QQ)
        OVAL2[I]=POLE.create_oval(X3+1,Y3,X3+RAD-1,Y3+RAD,outline=QQ)
        OVAL3[I]=POLE.create_oval(X3,Y3+1,X3+RAD,Y3+RAD-1,outline=QQ)
    while PRIZ>0:
        for J in range(1,7):
            TIM=0.01*pow(KOF,J)
            for I in range(1,128):
                Q = (128 - I) *2
                match J:
                    case 1: QQ=RGB(Q,0,0)
                    case 2: QQ=RGB(Q,Q,0)
                    case 3: QQ=RGB(0,Q,0)
                    case 4: QQ=RGB(0,Q,Q)
                    case 5: QQ=RGB(0,0,Q)
                    case 6: QQ=RGB(Q,0,Q)
                RAD=I*2; X3=X-I; Y3=Y-I
                if ZAL!=0:
                    for K in range(1,I+1):
                        POLE.itemconfig(OVAL1[K],outline=QQ)
                        POLE.itemconfig(OVAL2[K],outline=QQ)
                        POLE.itemconfig(OVAL3[K],outline=QQ)
                else:
                    POLE.itemconfig(OVAL1[I],outline=QQ)
                    POLE.itemconfig(OVAL2[I],outline=QQ)
                    POLE.itemconfig(OVAL3[I],outline=QQ)
                WREM(TIM/1000)
                POLE.update()
                if PRIZ<=0: break
            if PRIZ<=0: break

def KLAW(e):
    global PRIZ,ZAL
    KEY=e.keysym
    if KEY=="space" and PRIZ>0:
        ZAL=1-ZAL
    elif KEY=="Return":
        PRIZ=-PRIZ
        if PRIZ<0:
            ROTOR()
        elif PRIZ>0:
            SFERA()
    elif KEY=="Escape":
        PRIZ=0; TISTOP()

OKNO.bind("<Key>", KLAW)
OKNO.after(10,ROTOR)
OKNO.mainloop()

Как я понял, ИИ писал программу по другой логике, чем я. Причем все три его варианта достаточно отличаются друг от друга.

Мне сложно оценивать качество результата (фактически это только третья программа ИИ, ибо два прошлых варианта работают неправильно). Конечно, я писал программу дольше, чем в сумме составляют все мои переговоры с ЖПТ. Правда, сколько писал - не помню (дело довольно давнее), а главное - я писал в ходе обучения! Читал документацию, спрашивал на форумах и т.д. Если бы я свободно владел Питоном, вполне вероятно, что написал бы программу быстрее, чем разговаривал с ИИ. Но достаточно свободно владею я только Бейсиком...

Так же понятно, что скорость генерации кода у ИИ в целом больше как минимум за счет скорости "набора текста" и прочих подобных моментов. Но в итоге эксперимента я все же могу утверждать, что "хоронить" программистов как профессию рано. :)

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

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

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

P.S. Но вот чего я не понял, так это списка языков тут на Хабре. В списке для выбора кода есть крайне экзотические варианты, но нет ни Бейсика (в любом варианте!), ни даже Пролога! Нет Паскаля, хотя Дельфи есть, но все же это не синонимы. Я понимаю, что нет моего языка "Ellochka", но все остальные где?

Комментарии (32)


  1. varvaravorobeva
    02.07.2026 18:34

    Смешно с того как программисты с опытом начинают относиться к ИИ как к быстрому джуниору)


    1. hauserich Автор
      02.07.2026 18:34

      Мне сложно говорить про отношение других программистов, но лично я просто проводил эксперимент. :) Без всяких планов на будущее. :)


  1. valera_efremov
    02.07.2026 18:34

    Вы пошли не совсем верной дорогой, использовав ChatGPT - он для "домохозяек".

    Попробуйте в Claude Code или Cursor те же задачи с разными моделями. Удивитесь результату.

    Я несколько лет скептически относился к нейросетям. Но год назад попробовав, теперь смотрю на себя прошлого как на пещерного человека.

    Пользуюсь Cursor. Он и по SSH подключится, посмотрит что на сервере случилось, гигабайтные логи почитает, в проекте нужный файл найдет с нужной по смыслу мне строкой. И даже при подключении MCP браузера может сам открывать сайт и тестировать.

    Я превратился из раба в рабовладельца. Из наборщика кода на клавиатуре в архитектора.

    Когнитивные способности не снизились, я могу и сейчас писать код сам.


    1. hauserich Автор
      02.07.2026 18:34

      Да, я понимаю, что ЖПТ не для программирования. Но я хотел бесплатные сети, а вроде бы и Курсор, и Клауде - платные, разве нет?
      Насчет того, чтобы давать роботу доступ к сайту своему - нет, не решусь. :) Нет доверия. Спросить - да, я спрашивал один раз уж не помню что, когда саппорт хостинга был завален работой и долго отвечали. Кажется, как раз ЖПТ и помог (я кроме него только Джемени иногда применял). Так что я не отрицаю пользу нейросетей, я просто не согласен с наблюдаемым фанатизмом и разговорами про "все перевести на ИИ" даже в рамках государства. Не знаю как лет через 10, но пока точно нет. Да и потом стремно...


      1. JerryI
        02.07.2026 18:34

        Они могут работать бесплатно, но хотят забугорный номер. Попробуйте Ollama cloud и сцепите его с каким нибудь vscode


        1. hauserich Автор
          02.07.2026 18:34

          Номер? Там регистрация по телефону? Ради тестирования нет смысла...


          1. JerryI
            02.07.2026 18:34

            Там хорошие лимиты и Sonnet Max очень хороша даже в каких-то забытых языках программирования

            Даже в режиме чата она вам создаст реальные файлы и позволит их скачать и запустить


            1. hauserich Автор
              02.07.2026 18:34

              Спасибо, буду иметь в виду!


    1. Void-Cowboy
      02.07.2026 18:34

      лолшто? Нейросети это инстрмент, даже с голым чатом можно разное творить, главное что бы контекст влазило, а уж codex и вовсе держит пальму первества после того как антропики нахрен сломали нормально работающий опус 4.6 (и 4.7-4.8 не сильно улучшили ситуацию)

      К слову текущий мифос как раз дает ощущение того самого, "зимнего" клода на 4.6. Так что возможно что даунгрейд, что был в начале года, был только для того что бы новая модель казалась революционной.

      А Cursor как по мне не дотягивает ни до чата ни но антропиков.


      1. hauserich Автор
        02.07.2026 18:34

        Я не так глубоко в теме разных ИИ, ибо на практике пользуюсь своим интеллектом, а не искусственным. :) Но тенденция такая активная, что захотелось вообще проверить - а насколько В ПРИНЦИПЕ это возможно. Понятно, что чатжпт тут не самый лучший образец. Но многие пользуются им...


        1. nidalee
          02.07.2026 18:34

          Смотрите, вам человек пишет чуть-чуть неочевидную вещь: ChatGPT ChatGPT рознь. Есть ChatGPT в браузере, а есть ChatGPT в Codex - десктопном приложении. Так вот, писать код в чате - это было очень плохо еще два года назад, когда альтернатив в виде приложений с агентами не было. Сейчас никто в здравом уме не будет вайбкодить в чате, это делается в Codex или Claude Code и аналогах. И лучший результат достигается, если сначала через plan mode описать, что у тебя есть, и что хочешь получить. Потом ручками подредактировать план, где не согласен, прогнать "ask me questions" и уже после всего этого, по вылизанному плану поставить прогон "вайбкодинга". Тогда да, что-то может и получится.

          Можете мне в ЛС или сюда прислать задачку из тех, что вы скармливали чату - я вам пришлю, что получилось БЕЗ ручных изменений.


          1. hauserich Автор
            02.07.2026 18:34

            Вы правы, я прекрасно понимаю, что взялся тестировать далеко не максимально продвинутую платформу. Но с другой стороны, я предлагал достаточно простые задачи. И в этом плане мне нечего писать в личку, потому что все описано в статье, ничего иного я чату не предлагал. :)
            И да - мне было бы интересно увидеть вариант использования других ИИ (более профессиональных) с такими же входными данными (задачами). Просто у меня там нет аккаунтов, нет этих приложений и т.д., поскольку я не планирую их использовать, так и ставить смысла не вижу. Но у кого есть - да, мне было бы интересно посмотреть!


            1. nidalee
              02.07.2026 18:34

              Проблема тут в том, что во всех примерах кроме первого (где у ChatGPT получилось), я не вижу реального промпта, который вы использовали. Только воспоминания и ощущения. Ткните меня, или киньте в меня реальным промптом, с которым не получилось - я попробую на Opus или Fable. Чтобы сравнение было прямым и честным.


              1. hauserich Автор
                02.07.2026 18:34

                Ну, промпт в часах был примерно таким как указано. И в целом задачу ИИ решил, хотя чисто эстетически не все идеально, но это я и не оговаривал.
                А вот с третьей задачей сложнее. Я ведь тестировал не только сам ИИ, но и то, насколько сложно объяснить нейросети, что именно мне нужно! Чисто практически это не менее важно: если промпт по сложности и объему равен программе, то зачем нужен ИИ? :)

                Поэтому я не писал четкий промпт, а именно своими словами объяснил задачу примерно так, как и описано. Тут ведь многое потом в диалоге с ЖПТ еще уточнялось... Я сейчас не могу авторизоваться там, чтобы восстановить весь диалог (а первая попытка была вовсе без авторизации и там не сохранилось ничего).
                Если запустите мой вариант (как эталон), то я как раз буду рад посмотреть, какой промпт Вы используете для Fable или Opus. Возможно, я действительно просто не умею формулировать задачи, ибо не имею опыта с ИИ. Но изначально и предполагалось, что я - дилетант и хочу подставить работу ИИ в качестве якобы своей. Иначе зачем мне ИИ? Сам напишу... :)


                1. nidalee
                  02.07.2026 18:34

                  Чисто практически это не менее важно: если промпт по сложности и объему равен программе, то зачем нужен ИИ? :)

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

                  Если запустите мой вариант (как эталон), то я как раз буду рад посмотреть, какой промпт Вы используете для Fable или Opus.

                  Попробую.


                  1. hauserich Автор
                    02.07.2026 18:34

                    Вы только пишете чего хотите достичь, а как - это уже не ваша забота в идеале.

                    Так это же тоже промпт по сути! И от того, как я его напишу (насколько подробно и однозначно) будет зависеть и результат!
                    Кстати, ЖПТ пару раз задавал уточняющие вопросы именно в плане моих формулировок. То, что мне казалось однозначно понятным, он трактовал по-разному и требовал конкретики. В частности, про вращение колец в двух плоскостях. Мою формулировку он не понял, хотя вроде она очевидна...


                    1. nidalee
                      02.07.2026 18:34

                      Вот код, что получился:
                      # -*- coding: utf-8 -*-
                      """
                      Графическая демка на чистой стандартной библиотеке Python (tkinter).
                      
                      Окно 640x480.
                      
                      Режим 1 (синий фон):
                          Два жёлтых кольца вращаются синхронно — одно "справа налево"
                          (вокруг вертикальной оси), другое "сверху вниз" (вокруг горизонтальной).
                      
                      Режим 2 (чёрный фон):
                          Из центра растут круги стандартных цветов по кругу:
                          красный, жёлтый, зелёный, голубой, синий, фиолетовый, снова красный ...
                          Два варианта (переключаются пробелом):
                              A — новые круги поверх старых, яркость падает от центра к периферии;
                              B — всё изображение постепенно темнеет до чёрного, поэтому каждый
                                  цвет как будто рисуется на чёрном фоне.
                      
                      Управление:
                          Enter  — переключение между режимом 1 и режимом 2
                          Space  — в режиме 2 переключение варианта A / B
                          Esc    — выход
                      """
                      
                      import math
                      import tkinter as tk
                      
                      # --------------------------------------------------------------------------- #
                      #  Константы
                      # --------------------------------------------------------------------------- #
                      W, H = 640, 480                 # размер окна
                      CX, CY = W // 2, H // 2         # центр
                      FRAME_MS = 20                   # ~50 кадров в секунду
                      
                      BG_BLUE = "#0000a0"             # синий фон режима 1
                      BG_BLACK = "#000000"           # чёрный фон режима 2
                      YELLOW = "#ffff00"
                      
                      # Режим 1 — кольца
                      RING_R = 150                    # радиус колец
                      RING_WIDTH = 5                  # толщина линии кольца
                      SPIN = 0.045                    # прирост угла за кадр (скорость вращения)
                      
                      # Стандартные цвета (по кругу цветового спектра)
                      STD_COLORS = [
                          (255, 0, 0),     # красный
                          (255, 255, 0),   # жёлтый
                          (0, 255, 0),     # зелёный
                          (0, 255, 255),   # голубой
                          (0, 0, 255),     # синий
                          (255, 0, 255),   # фиолетовый
                      ]
                      NCOL = len(STD_COLORS)
                      
                      # Режим 2 — общие
                      R_MAX = 430                     # радиус, покрывающий углы окна (диагональ/2 ~ 400)
                      
                      # Вариант A (яркость по радиусу)
                      BAND_STEP = 5                   # шаг вложенных окружностей (px)
                      BAND_W = 44                     # ширина одной цветовой полосы (px)
                      SPEED_A = 1.3                   # скорость "выползания" полос из центра
                      
                      # Вариант B (затухание в чёрный)
                      SPEED_B = 3.0                   # скорость роста радиуса кольца (px/кадр)
                      RING_W_B = 4                    # толщина растущего кольца
                      SPAWN_EVERY = 3                 # каждые N кадров рождается новое кольцо
                      FADE = SPEED_B / R_MAX          # за сколько кадров кольцо гаснет до чёрного
                      
                      
                      def dim_hex(rgb, k):
                          """Цвет rgb, приглушённый коэффициентом k (0..1), в виде #rrggbb."""
                          if k < 0:
                              k = 0.0
                          elif k > 1:
                              k = 1.0
                          r, g, b = rgb
                          return "#%02x%02x%02x" % (int(r * k), int(g * k), int(b * k))
                      
                      
                      class Demo:
                          def __init__(self, root):
                              self.root = root
                              self.canvas = tk.Canvas(root, width=W, height=H,
                                                      highlightthickness=0, bg=BG_BLUE)
                              self.canvas.pack()
                      
                              # общее состояние
                              self.mode = 1            # 1 — кольца, 2 — круги
                              self.variant = "A"       # A / B (только для режима 2)
                              self.theta = 0.0         # угол вращения колец
                              self.g = 0.0             # фаза роста кругов
                              self.frame = 0           # счётчик кадров (для рождения колец в B)
                      
                              # идентификаторы графических объектов
                              self.ring1 = None
                              self.ring2 = None
                              self.bands = []          # вложенные окружности варианта A
                              self.rings = []          # активные растущие кольца варианта B: {id, r, col, bright}
                      
                              # горячие клавиши
                              root.bind("<Return>", self.toggle_mode)
                              root.bind("<KP_Enter>", self.toggle_mode)
                              root.bind("<space>", self.toggle_variant)
                              root.bind("<Escape>", lambda e: root.destroy())
                      
                              self.build()
                              self.tick()
                      
                          # ------------------------------------------------------------------ #
                          #  Переключение режимов
                          # ------------------------------------------------------------------ #
                          def toggle_mode(self, event=None):
                              self.mode = 2 if self.mode == 1 else 1
                              self.build()
                      
                          def toggle_variant(self, event=None):
                              if self.mode == 2:
                                  self.variant = "B" if self.variant == "A" else "A"
                                  self.build()
                      
                          # ------------------------------------------------------------------ #
                          #  Пересборка сцены под текущий режим
                          # ------------------------------------------------------------------ #
                          def build(self):
                              self.canvas.delete("all")
                              self.bands = []
                              self.rings = []
                              self.g = 0.0
                              self.frame = 0
                      
                              if self.mode == 1:
                                  self.root.title("Режим 1: два вращающихся кольца   |   Enter — сменить режим,  Esc — выход")
                                  self.canvas.config(bg=BG_BLUE)
                                  # два кольца — контур (fill=""), обновляются в кадре
                                  self.ring1 = self.canvas.create_oval(0, 0, 0, 0, outline=YELLOW,
                                                                       width=RING_WIDTH, fill="")
                                  self.ring2 = self.canvas.create_oval(0, 0, 0, 0, outline=YELLOW,
                                                                       width=RING_WIDTH, fill="")
                              else:
                                  title_v = "A: ярче в центре" if self.variant == "A" else "B: затухание в чёрный"
                                  self.root.title("Режим 2 [%s]   |   Enter — сменить режим,  Space — вариант A/B,  Esc — выход"
                                                  % title_v)
                                  self.canvas.config(bg=BG_BLACK)
                                  if self.variant == "A":
                                      # набор вложенных закрашенных окружностей: внешние снизу, внутренние сверху
                                      r = R_MAX
                                      while r > 0:
                                          oid = self.canvas.create_oval(CX - r, CY - r, CX + r, CY + r,
                                                                        outline="", fill=BG_BLACK)
                                          self.bands.append((oid, r))
                                          r -= BAND_STEP
                                      # сдвигаем фазу так, чтобы в центре цикл стартовал с красного
                                      if self.bands:
                                          self.g = float(self.bands[-1][1])
                                  # вариант B создаёт кольца динамически в tick()
                      
                          # ------------------------------------------------------------------ #
                          #  Один кадр анимации
                          # ------------------------------------------------------------------ #
                          def tick(self):
                              if self.mode == 1:
                                  self.draw_rings()
                              elif self.variant == "A":
                                  self.draw_circles_a()
                              else:
                                  self.draw_circles_b()
                      
                              self.root.after(FRAME_MS, self.tick)
                      
                          # --- Режим 1: вращающиеся кольца ---------------------------------- #
                          def draw_rings(self):
                              self.theta += SPIN
                              c = abs(math.cos(self.theta))   # фаза кольца 1
                              s = abs(math.sin(self.theta))   # фаза кольца 2 — сдвиг на 90°, чтобы оба кольца
                                                              # были видны всегда (не сливались в одно) и вращались
                                                              # синхронно, каждое в своей плоскости
                      
                              # кольцо 1: вращение вокруг вертикальной оси -> меняется ширина (справа налево)
                              a = max(1, RING_R * c)
                              b = RING_R
                              self.canvas.coords(self.ring1, CX - a, CY - b, CX + a, CY + b)
                      
                              # кольцо 2: вращение вокруг горизонтальной оси -> меняется высота (сверху вниз)
                              a = RING_R
                              b = max(1, RING_R * s)
                              self.canvas.coords(self.ring2, CX - a, CY - b, CX + a, CY + b)
                      
                          # --- Режим 2, вариант A: яркость по радиусу ----------------------- #
                          def draw_circles_a(self):
                              self.g += SPEED_A
                              for oid, r in self.bands:
                                  idx = int((self.g - r) // BAND_W) % NCOL
                                  bright = 1.0 - r / R_MAX          # ярко в центре, темно по краям
                                  self.canvas.itemconfig(oid, fill=dim_hex(STD_COLORS[idx], bright))
                      
                          # --- Режим 2, вариант B: затухание в чёрный ----------------------- #
                          def draw_circles_b(self):
                              self.frame += 1
                      
                              # рождение нового кольца из центра (первый круг — красный)
                              if self.frame % SPAWN_EVERY == 0:
                                  idx = (self.frame // SPAWN_EVERY - 1) % NCOL
                                  oid = self.canvas.create_oval(CX, CY, CX, CY,
                                                                outline=YELLOW, width=RING_W_B)
                                  self.rings.append({"id": oid, "r": 1.0,
                                                     "col": STD_COLORS[idx], "bright": 1.0})
                      
                              # рост + затухание существующих колец
                              alive = []
                              for ring in self.rings:
                                  ring["r"] += SPEED_B
                                  ring["bright"] -= FADE
                                  r = ring["r"]
                                  if r > R_MAX or ring["bright"] <= 0:
                                      self.canvas.delete(ring["id"])
                                      continue
                                  self.canvas.coords(ring["id"], CX - r, CY - r, CX + r, CY + r)
                                  self.canvas.itemconfig(ring["id"],
                                                         outline=dim_hex(ring["col"], ring["bright"]))
                                  alive.append(ring)
                              self.rings = alive
                      
                      
                      def main():
                          root = tk.Tk()
                          root.resizable(False, False)
                          Demo(root)
                          root.mainloop()
                      
                      
                      if __name__ == "__main__":
                          main()
                      
                      Вот история чата:

                      Окно 640*480, синий фон. Вращаются два желтых кольца справа налево и сверху вниз, синхронно. Это один режим. Переключение на второй - клавишей “Enter”.

                      Второй режим. Черный фон окна и из центра растут круги стандартных цветов: красный, желтый, зеленый, голубой, синий и фиолетовый. Потом снова красный и т.д. Этот режим имеет два варианта (переключение клавишей “Space”):

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

                      Отличная задача — классическая графическая демка. Прежде чем писать, мне нужно уточнить одну вещь, которая полностью определяет результат: на чём делать. Остальное я реализую по спецификации (интерпретацию колец опишу ниже и вы поправите, если что).

                      ● User answered Claude’s questions: · На какой платформе/языке сделать программу с окном 640×480, обработкой Enter/Space и анимацией? → HTML5 Canvas + JS Interrupted · What should Claude do instead?

                      Я передумал - хочу на Python

                      Понял — делаю на Python (pygame). Сначала проверю, что установлено на машине.

                      Ran 1 shell command Остальное я реализую по спецификации (интерпретацию колец опишу ниже и вы поправите, если что).

                      Ran 1 shell command

                      Python 3.12.9 есть, а pygame нет. Ставлю pygame:

                      Ran 1 shell command Interrupted · What should Claude do instead?

                      Давай только стандартные библиотеки

                      Понял — только стандартная библиотека, без pygame. Тогда GUI на tkinter (входит в стандартную поставку Python), рисование на Canvas. Проверю, что tkinter доступен:

                      Ran 1 shell command

                      tkinter на месте (Tk 8.6). Пишу программу целиком на стандартной библиотеке — окно на tkinter, рисование на Canvas, анимация через after().

                      ● Write(demo.py) Wrote 227 lines to demo.py -- coding: utf-8 -- “” рафическая демка на чистой стандартной библиотеке Python (tkinter).

                      Окно 640x480.

                      Режим 1 (синий фон): Два жёлтых кольца вращаются синхронно — одно “справа налево” (вокруг вертикальной оси), другое “сверху вниз” (вок … +217 lines

                      Как закончишь, перепроверь ADVERSARIAL агентом

                      Программа готова. Теперь прогоню собственный «дымовой» тест (без всплывающего окна — withdraw()), чтобы убедиться, что все три состояния рисуются без исключений и жизненный цикл колец в варианте B корректен.

                      ● Write(C:\Users\mistd\AppData\Local\Temp\claude\D–habr\141d40eb-0ceb-4b0a-a95d-d449fbf8ab07\scratchpad\smoke.py) Wrote 74 lines

                      Ran 1 shell command

                      Собственный дымовой тест зелёный:

                      • Режим 1 — 402 кадра без ошибок, кольца схлопываются в линии на θ=90°.

                      • Вариант A — 86 вложенных окружностей, центр яркий (#00fc00), край тёмный (#000000) → градиент «ярко в центре, темно по краям». ✓

                      • Вариант B — колец рождается и гаснет стабильно (пик 48, утечки нет), все цвета валидны. ✓

                      Теперь, как вы просили, запускаю адверсариального агента — пусть целенаправленно ищет расхождения со спецификацией, краши и краевые случаи (с правом запускать код).

                      ● Agent(Adversarial review of demo.py) Done (5 tool uses · 48.0k tokens · 5m 40s)

                      Allowed by auto mode classifier

                      Адверсариальный агент отработал: крашей, утечек и невалидных цветов нет (прогнал 200k/100k/5k кадров, фаззинг dim_hex, переключения — всё чисто). Нашёл 3 реальных замечания, которые стоит поправить:

                      1. Вариант B начинается с жёлтого, а не красного — off-by-one: спека явно требует «красный, жёлтый, …», а первый круг выходит жёлтым.

                      2. Режим 1: кольца при одинаковой фазе дважды за цикл идеально совпадают → второе кольцо «пропадает» (сливается в одно). Читается как пульсация, а не как две разнонаправленные оси.

                      3. Вариант A стартует с magenta в центре (та же несогласованность фазы) + мёртвая строка self.g += SPEED_B в B.

                      Исправляю все три.

                      ● Update(demo.py) Added 5 lines, removed 2 lines # — Режим 1: вращающиеся кольца ---------------------------------- # def draw_rings(self): self.theta += SPIN c = abs(math.cos(self.theta)) c = abs(math.cos(self.theta)) # фаза кольца 1 s = abs(math.sin(self.theta)) # фаза кольцакольца # были видны и вращались # синхронно, каждое в своей плоскости

                          # кольцо 1: вращение вокруг вертикальной оси -> меняется ширина (справа налево)    a = max(1, RING_R * c)    # кольцо 2: вращение вокруг горизонтальной оси -> меняется высота (сверху вниз)    a = RING_R    b = max(1, RING_R * c)    b = max(1, RING_R * s)    self.canvas.coords(self.ring2, CX - a, CY - b, CX + a, CY + b)
                      # --- Режим 2, вариант A: яркость по радиусу ----------------------- #
                      

                      ● Update(demo.py) Added 3 lines outline=“”, fill=BG_BLACK) self.bands.append((oid, r)) r -= BAND_STEP # сдвигаем фазу так, чтобы в центре цикл стартовал с красного if self.bands: self.g = float(self.bands[-1][1]) # вариант B создаёт кольца динамически в tick()

                      # ------------------------------------------------------------------ #
                      

                      ● Update(demo.py) Added 2 lines, removed 3 lines

                      # --- Режим 2, вариант B: затухание в чёрный ----------------------- #
                      def draw_circles_b(self):    self.g += SPEED_B    self.frame += 1    # рождение нового кольца из центра    # рождение нового кольца из центра (первый кр    if self.frame % SPAWN_EVERY == 0:        idx = (self.frame // SPAWN_EVERY) % NCOL        idx = (self.frame // SPAWN_EVERY - 1) % NCOL        oid = self.canvas.create_oval(CX, CY, CX, CY,                                      outline=YELLOW, width=RING_W_B)        self.rings.append({"id": oid, "r": 1.0,
                      

                      Обновляю тест-харнесс под новую семантику режима 1 и проверяю, что фиксы сработали (B и A стартуют с красного, кольца больше не совпадают, утечек по-прежнему нет).

                      ● Write(C:\Users\mistd\AppData\Local\Temp\claude\D–habr\141d40eb-0ceb-4b0a-a95d-d449fbf8ab07\scratchpad\smoke2.py) Wrote 71 lines

                      Ran 1 shell command

                      Ассерт упал из-за бага в тесте, а не в коде: draw_rings инкрементирует theta в начале, поэтому после d.theta=0 фактически рисуется θ=0.045 (кольцо 2 почти схлопнуто, высота 13.5, а не <5). Код корректен. Правлю тест (выставляю θ с поправкой на инкремент):

                      ● Update(C:\Users\mistd\AppData\Local\Temp\claude\D–habr\141d40eb-0ceb-4b0a-a95d-d449fbf8ab07\scratchpad\smoke2.py) Added 6 lines, removed 2 lines

                      Ran 1 shell command

                      Опять баг теста, не кода: я мерил суммарную «светимость» цвета, а она зависит ещё и от оттенка (magenta ярче green по сумме каналов). Спека говорит про коэффициент яркости k = 1 − r/R_MAX, который монотонен по постением: каждый band должен равняться dim_hex(цвет, k).

                      ● Update(C:\Users\mistd\AppData\Local\Temp\claude\D–habr\141d40eb-0ceb-4b0a-a95d-d449fbf8ab07\scratchpad\smoke2.py) Added 21 lines, removed 13 lines

                      Готово

                      Файл: D:\habr\demo.py — чистая стандартная библиотека (tkinter, math), никаких зависимостей.

                      Запуск: python D:\habr\demo.py

                      Управление: Enter — режим 1 ↔ режим 2 · Space — вариант A ↔ B (в режиме 2) · Esc — выход. Текущее состояние дублируется в заголовке окна.

                      Что делает:

                      • Режим 1 — синий фон, два жёлтых кольца вращаются синхронно: одно вокруг вертикальной оси («справа налево»), второе вокруг горизонтальной («сверху вниз»).

                      • Режим 2 — чёрный фон, из центра растут круги по цветовому кругу (красный → жёлтый → зелёный → голубой → синий → фиолетовый → снова красный):

                        • A — новые поверх старых, яркость падает от центра к краю (сплошной градиент);

                        • B — каждый круг по мере роста гаснет до чёрного, поэтому новый цвет появляется как бы на чёрном фоне (тонкие расходящиеся кольца).

                      Что нашёл и исправил адверсариальный агент

                      Крашей, утечек памяти и невалидных цветов не найдено (прогнал 200k/100k/5k кадров, фаззинг dim_hex, сотни переключений). Три реальных замечания — исправлены:

                      1. Вариант B стартовал с жёлтого → теперь с красного, как в спеке.

                      2. В режиме 1 при одинаковой фазе кольца дважды за оборот сливторого кольца на 90° (эффект «гироскопа»), оба видны всегда.Остаётся синхронным (одна скорость).

                      3. Вариант A стартовал с magenta в центре → теперь с красного; заодно убрал мёртвую строку.

                      Два места, где я выбрал интерпретацию — скажите, если нужно иначе

                      • Кольца (режим 1): сделал сдвиг фазы 90° (гироскоп). Если хотите строго одинаковую фазу (оба схлопываются одновременно в «плюс») — в draw_rings замените s = abs(math.sin(…)) на abs(math.cos(…)).

                      • Вариант B: каждое кольцо гаснет индивидуально, центр всегда яркий (полотно ушло бы в чёрный, только если перестать плодить кольца). Это соответствует «каждый цвет на чёрном фоне». Если нужен буквально глобальный fade всего кадра — это другая механика, могу переделать.

                      Хотите — подправлю скорость/радиус/толщину/цвета или запущу у вас для визуальной проверки.

                      А это комментарий по статье:

                      Прочитал. Это оказалась статья «Мой первый вайб‑кодинг» (hauserich): автор сравнивает свой код с кодом ChatGPT на трёх задачах, и Задача 3 — ровно наша (кольца + растущие круги, два варианта по Space). Причём и ChatGPT, и автор, и я независимо сошлись на Python + tkinter, create_oval, предсоздании объектов и правке через coords()/itemconfig(), одинаковом списке из 6 цветов и формуле яркости k = 1 − r/R. Так что выбор платформы подтвердился с трёх сторон.

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

                      Что бы я пересмотрел у себя

                      1. Главное: «растущие круги» — последовательно, а не рейнбоу‑полосами

                      И у ChatGPT, и у автора круги растут по одному цвету за раз: красный вырастает из центра до края → потом жёлтый из центра → потом зелёный, и по кругу. У ChatGPT это массив длительностей TIMES=[3,3.5,4,4.5,5,5.5] (один цвет = один «прогресс» p), у автора — внешний цикл for J in range(1,7) в SFERA().

                      У меня же в обоих вариантах на экране одновременно много цветов — концентрический радужный набор полос (idx = (g − r)//BAND_W). Это валидное прочтение слов «растут круги стандартных цветов… потом снова красный», но каноничное (то, что реально имел в виду автор задачи, переводя со своего BASIC) — последовательное: один цвет вырос — сменился следующим. Строка спеки «красный, жёлтый… потом снова красный» читается именно как временна́я последовательность.

                      Это не «баг», но если цель — соответствовать первоисточнику, я бы переключил режим 2 на «один цвет растёт из центра до края, затем следующий». Моя версия визуально богаче, их — ближе к заданию.

                      1. Вариант B: у них темнеет всё изображение, у меня — каждое кольцо по отдельности

                      Спека варианта «Второй»: «в ходе вывода все изображение темнеет вплоть до черного». Обе версии из статьи делают это буквально: растущий диск одного цвета целиком гаснет до чёрного по мере роста —

                      • ChatGPT: k = 1 − p, и itemconfig перекрашивает все текущие овалы в одну яркость;

                      • автор (ZAL!=0): for K in range(1,I+1): itemconfig(…) — перекрашивает весь диск 1…I в одну убывающую яркость Q=(128−I)*2.

                      То есть перед сменой цвета экран реально уходит в чёрный, и новый цвет стартует «на чёрном фоне».

                      У меня в B центр всегда яркий — гаснут только отдельные кольца по мере ухода на периферию. Это тоже читается как «каждый цвет на чёрном», но глобального затемнения нет (мой же адверсариальный агент это отметил как «plausible concern»). После статьи я склонен признать: буквальному тексту точнее соответствует их механика, и я бы привёл B к ней.

                      1. Кольца: одинаковая фаза — это и есть канон, а мой «гироскоп» — отклонение

                      Тут поучительно. Мой адверсариальный агент счёл, что кольца, идеально совпадающие в одно дважды за оборот, — дефект, и я сдвинул фазу на 90°. Но в статье обе версии используют одну фазу для обоих эллипсов:

                      • ChatGPT: один и тот же c=abs(cos(t)) для r1 и r2;

                      • автор: одно и то же B=R/I для OVALG и OVALV.

                      Значит совпадение в круг (при c=1) и «плюс» из двух линий (при c=0) — это задуманный пульсирующий вид, а не баг. Мой сдвиг фазы «чинит» то, что чинить не просили. Хорошо, что я оставил ревёрт в одну строку (sin → cos в draw_rings) — им и восстанавливается каноничный вид.

                      Мелочь в плюс мне: моя (и ChatGPT‑овская) математика R·|cos| — это физически точная ортопроекция равномерно вращающегося кольца (естественно «залипает» анфас, быстро проскакивает ребром). У автора B=R/I с нелинейным I и переменной задержкой WREM — стилизованный ручной easing, не косинус.

                      1. Разная скорость по цветам (по желанию)

                      У них каждый цвет растёт со своей скоростью (TIMES у ChatGPT; TIM=0.01·KOF^J, KOF=1.333 у автора — каждый следующий цвет быстрее). У меня скорость единая. В моей спеке этого не было, так что это не упущение, но приятная деталь — могу добавить.

                      Где мой код держится хорошо (или лучше)

                      • Событийный цикл. У автора анимация крутится в while‑циклах с busy‑wait WREM() (спинит ядро на 100%, блокирует event loop, требует ручного POLE.update()), а ROTOR/SFERA вызываются прямо из обработчика клавиш — это классический tkinter‑footgun: update() внутри цикла может снова диспатчить KLAW, отсюда риск ре‑энтрантности/RecursionError при частом нажатии. И я, и ChatGPT используем идиоматичный root.after(…) — обе «ИИ‑версии» тут аккуратнее человеческой.

                      • Нет утечки объектов. У ChatGPT в circles() при смене цвета делается self.items=[] без удаления овалов с холста — старые объекты копятся на канве бесконечно (≈128–168 овалов за цвет, чистятся только при delete(“all”) на смене режима). Забавно: это ровно «видит локальный момент, но не общую картину» из тезиса статьи. У меня A — фиксированные 86 овалов с перекраской, B — кольца удаляются по гашению (проверено: пик 48, роста нет). У автора тоже без утечки — предвыделенные 384 овала.

                      • Старт с красного. Обе версии из статьи стартуют красным (self.color=0 / J=1). У меня в первой редакции был off‑by‑one (B начинался с жёлтого) — тот самый, что я уже поправил.

                      И честно — одно место, где ChatGPT лучше меня: он считает время по стенным часам (time.perf_counter()), поэтому скорость анимации не зависит от частоты кадров; у меня приращения на кадр, и при просадке ниже 50 fps демка замедлится. Для такой задачи неважно, но подход ChatGPT робастнее.

                      Комментарий к самим листингам

                      Версия ChatGPT. Компактно и грамотно: after()‑цикл, perf_counter, аккуратный rgb() с клампом max(0,min(255,…)), для ресайза. Слабости: (1) описанная утечка овалов; (2) одинаковая фаза колец — «второе кольцо исчезает» в круг дважды за оборот; (3) круги заполняют лишь центральный радиус 0.35·min = 168 px, а не всё окно. Крепкий «локальный» код с одной неочевидной «глобальной» дырой — прямо иллюстрация к выводу статьи.

                      Версия автора. Это фактически эталон задачи, и спеку она реализует полностью и вдумчиво: предсоздание овалов против мерцания; тройной овал (OVAL1/2/3 со сдвигом на 1 px) — хак, чтобы соседние 1‑пиксельные контуры не оставляли щелей; ZAL чисто разводит оба варианта (radial gradient ↔ равномерное затемнение всего диска); радиальный градиент Q=(128−I)·2; переменный easing вращения. Минусы — «бейсиковые» привычки: busy‑wait WREM, управление анимацией из while внутри обработчиков (ре‑энтрантность), from tkinter import *, глобальные переменные повсюду, транслит‑имена (OKNO/POLE/PRIZ/ROTOR/SFERA/KLAW), O(n²) перекраска в ветке ZAL!=0. Работает и точно по заданию, но по структуре — самый «олдскульный» из трёх.

                      Итог

                      Ничего в моём коде не «сломано», но статья вскрыла три места, где я разошёлся с первоисточником: (1) я показываю все цвета сразу, а канон — по одному растущему цвету; (2) мой B гасит кольца поштучно, а канон — затемняет весь кадр до чёрного; (3) мой сдвиг фазы колец — красивое, но отклонение от задуманной пульсации. По инженерии (event loop, утечки) моя версия и версия ChatGPT аккуратнее человеческой, но у ChatGPT — утечка овалов, а у меня был off‑by‑one с первым цветом (уже поправлен).

                      Если захочешь «привести к первоисточнику» — скажи, и я (а) переключу режим 2 на последовательный рост одного цвета, (б) сделаю вариант B глобальным затемнением всего диска, (в) верну кольцам одну фазу. Каждое — маленькая правка, но менять пока ничего не буду, как договорились.


                      1. hauserich Автор
                        02.07.2026 18:34

                        Я несколько в шоке. :) Если честно, такой объем текста читать в виде комментария не совсем удобно. Но вообще - это какая нейросеть? Потому что такое впечатление, что куски ее диалога - это куски моей беседы с ЖПТ, хотя по идее где она могла их взять?
                        Насчет разбора - я понял, что в целом написал все верно, а насчет "транслита имен" - а что тут плохого? Зато наглядно. :)
                        В плане организации - да, я переводил "в лоб" с бейсика, понимаю, что формально так не принято на питоне.

                        Но вообще я думал сделать не анализ моей статьи на качество вариантов, а отдельно предложить другой нейросети сделать свой вариант программы, НЕ ГЛЯДЯ на уже созданное...


                      1. nidalee
                        02.07.2026 18:34

                        Я несколько в шоке. :) Если честно, такой объем текста читать в виде комментария не совсем удобно.

                        Понимаю. Могу куда-нибудь закинуть.

                        Но вообще - это какая нейросеть?

                        Это Claude Opus 4.8 на xhigh effort с ultracode (которым она не воспользовалась).

                        Потому что такое впечатление, что куски ее диалога - это куски моей беседы с ЖПТ, хотя по идее где она могла их взять?

                        Я копипастил ТЗ как его увидел из статьи. Потом еще две правки внес, когда прочитал развитие истории (с HTML на Python поменял, потом отказался от pygame).


                      1. hauserich Автор
                        02.07.2026 18:34

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


                      1. hauserich Автор
                        02.07.2026 18:34

                        Дико извиняюсь, но я запустил код, который идет первым. Он работает абсолютно неправильно!


                      1. nidalee
                        02.07.2026 18:34

                        Не за что извиняться :)

                        Я думаю, что по большей части проблема в формулировке ТЗ. Оно действительно открыто к интерпретации, посмотрите анализ (третий спойлер). Я лично не увидел особых расхождений со скопированным ТЗ, я думаю тут вопрос кто как понял.

                        Вот это кстати промпт, по которому adverserial агент пытался оспорить написанное:

                        Обратите внимание на "The author's INTERPRETATION (which you should also scrutinise, not just accept):"

                        You are an ADVERSARIAL code reviewer. Your job is to BREAK and DISPROVE, not to praise. Assume the code is subtly wrong until proven otherwise. Be skeptical and concrete.

                        The file to review is D:\habr\demo.py — a graphics demo written in pure Python standard library (tkinter Canvas). You may READ it and you may RUN Python to test it (Windows, python 3.12, tkinter 8.6 available). To test without popping a window, create the Tk root and call root.withdraw(), then drive the drawing methods directly and call root.update_idletasks() — do NOT call root.mainloop() (it blocks). There is an existing headless harness at C:\Users\mistd\AppData\Local\Temp\claude\D--habr\141d40eb-0ceb-4b0a-a95d-d449fbf8ab07\scratchpad\smoke.py you can read/adapt.

                        THE ORIGINAL SPEC (Russian, must be satisfied faithfully): “Окно 640*480, синий фон. Вращаются два желтых кольца справа налево и сверху вниз, синхронно. Это один режим. Переключение на второй - клавишей Enter. Второй режим. Черный фон окна и из центра растут круги стандартных цветов: красный, желтый, зеленый, голубой, синий и фиолетовый. Потом снова красный и т.д. Этот режим имеет два варианта (переключение клавишей Space): Один - новые круги рисуются поверх старых, яркость кругов уменьшается от центра к периферии. Второй - в ходе вывода кругов все изображение темнеет вплоть до черного, поэтому каждый цвет рисуется как бы на черном фоне.”

                        The author’s INTERPRETATION (which you should also scrutinise, not just accept):

                        • Two yellow rings = two concentric ellipses sharing one angle theta; ring1 varies horizontal semi-axis (spins about vertical axis => “right-to-left”), ring2 varies vertical semi-axis (spins about horizontal axis => “top-to-bottom”). “Synchronous” = same theta/phase.

                        • Mode 2 colors cycle: red, yellow, green, cyan, blue, magenta, repeat.

                        • Variant A: nested filled ovals recolored per-frame; brightness = 1 - r/R_MAX (bright center, dark edge); inner drawn on top of outer.

                        • Variant B: expanding ring outlines spawned from center, each fading toward black over time via subtractive dimming; whole picture would go black if spawning stopped.

                        ADVERSARIAL CHECKLIST — investigate each and try to find a real defect:

                        1. CRASHES / EXCEPTIONS: any input (long runtime, integer overflow of self.g/self.frame after millions of frames, division, empty lists, tkinter color-string format like negative or >255 channel values, coords with x1<x0) that throws. Actually reproduce by running many iterations.

                        2. RESOURCE LEAK: in variant B, do canvas items accumulate without being deleted over a long run? Prove with a count over thousands of frames. Also: switching modes repeatedly — are old items deleted (canvas.delete(‘all’))? Any orphaned after timers stacking up if you toggle modes fast?

                        3. SPEC CONFORMANCE:

                          • Mode 1 background actually blue? rings actually yellow and actually reading as “rotating”? Do BOTH directions (right-to-left AND top-to-bottom) actually appear, or do both rings collapse identically (same phase) making it a pulsing plus-sign rather than two distinct rotations? Is that a faithful reading of “справа налево и сверху вниз”? Argue it.

                          • Mode 2 background black? Colors exactly the 6 named standard colors and in the right cycle order?

                          • Variant A: is brightness truly decreasing from center to periphery at all times? Any radius where it inverts or bands read wrong because of the (g - r)//BAND_W sign when g is small (g starts at 0, so g - r is negative early — does int(negative)//positive behave as intended, and does modulo give the right color)? Check the very first frames.

                          • Variant B: does it truly “darken to black”? With SPAWN_EVERY spawning and FADE = SPEED_B/R_MAX, do rings actually reach black before deletion, or get deleted by radius first while still bright (leaving a visible bright ring popping out of existence at the edge)? Compute the numbers.

                          • Are A and B VISUALLY DISTINCT enough to be recognisably “two variants”?

                        4. KEY HANDLING: Enter (Return AND KP_Enter) toggles mode; Space toggles variant only in mode 2; Esc quits. Any dead keys, or Space doing something wrong in mode 1?

                        5. Off-by-one / color math in dim_hex: does it ever produce an invalid Tk color (e.g. ‘#100’ or channel 256)? Feed extreme k.

                        6. Anything that would look visibly wrong or janky that a user would immediately complain about.

                        Deliverables: Return a concise, ranked list of CONFIRMED defects (state how you reproduced each — command/observation), plus PLAUSIBLE concerns you couldn’t fully confirm. If something is fine, say so briefly. Do NOT fix anything — only report. Be specific with file line numbers where possible. Prefer running code to prove claims over speculation.

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


                      1. hauserich Автор
                        02.07.2026 18:34

                        Да, если агент сначала переводит на английский, а потом исполняет - может быть что угодно. Я английский не знаю, поэтому оценить качество перевода не могу. Но в этом плане ЖПТ прежде чем создавать код задавал кучу вопросов, в ходе обсуждения которых все нечеткости устранялись. а то, что его коды работали неверно - это, я думаю, как раз проявление тезиса из моей статьи насчет глобального видения.


                      1. hauserich Автор
                        02.07.2026 18:34

                        А Вы запускали мой код? видели как должно быть по идее?


                      1. nidalee
                        02.07.2026 18:34

                        Нет, я специально использовал только ваш промпт. Потому что - без наездов и без претензий - если вы неправильно по своему коду сформулировали ТЗ, то по нему будет неправильный (отличающийся) результат.

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

                        Что касается ТЗ, то я выше (изначально) уже писал:

                        И лучший результат достигается, если сначала через plan mode описать, что у тебя есть, и что хочешь получить. Потом ручками подредактировать план, где не согласен, прогнать "ask me questions" и уже после всего этого, по вылизанному плану поставить прогон "вайбкодинга". Тогда да, что-то может и получится.

                        Правильно поставленная и вылизанная задача - бОльшая часть работы. Поэтому я третий день пишу план вместо кода для своего проекта :)


                      1. hauserich Автор
                        02.07.2026 18:34

                        Понятно, но получается, что ЖПТ лучше? Ведь он в конечном итоге сделал правильный вариант, задавая мне правильные вопросы?


  1. Wesha
    02.07.2026 18:34

    1. hauserich Автор
      02.07.2026 18:34

      Звучит логично. :)


      1. Wesha
        02.07.2026 18:34

        Именно поэтому я веду свой проект — потому что не разбираться в одностраничных комиксах крайне сложно (хотя некоторым удаётся).


        1. hauserich Автор
          02.07.2026 18:34

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


  1. Domestomag
    02.07.2026 18:34

    Проверь себя и ии на leetcode.


    1. hauserich Автор
      02.07.2026 18:34

      Я не себя проверял, а нейросеть. :) Меня чего проверять - со мной все ясно. :) А вот ее проверить было как раз интересно...