Очумелые ручки с Чижиком, выпуск № 2: школьная география на 5. chatgpt codex.. chatgpt codex. DIY.. chatgpt codex. DIY. DIY или Сделай сам.. chatgpt codex. DIY. DIY или Сделай сам. matplotlib.. chatgpt codex. DIY. DIY или Сделай сам. matplotlib. python.. chatgpt codex. DIY. DIY или Сделай сам. matplotlib. python. tkinter.. chatgpt codex. DIY. DIY или Сделай сам. matplotlib. python. tkinter. windows.. chatgpt codex. DIY. DIY или Сделай сам. matplotlib. python. tkinter. windows. география.. chatgpt codex. DIY. DIY или Сделай сам. matplotlib. python. tkinter. windows. география. ии-агенты.. chatgpt codex. DIY. DIY или Сделай сам. matplotlib. python. tkinter. windows. география. ии-агенты. искусственный интеллект.

В первом выпуске мы с Чижиком поселили KVN прямо в старый телевизор LG. Там были root, маршрутизация, ARMv7, sing-box и черный экран YouTube. После такой истории новая задача выглядела почти отдыхом.

Очумелые ручки с Чижиком, выпуск № 2: школьная география на 5 - 1

На каникулах ребенок притащил фотографию школьного задания по географии. К слову, он естественно это сделал под конец каникул, как все дети это делают. На листе в две колонки были перечислены Волга, Амазонка, Байкал, Баскунчак, Гольфстрим, Марианская впадина, пролив Дрейка и еще несколько десятков географических объектов, общим число около 80. Ребенок должен уметь показать каждый из них на карте мира. Первый прогон, скажем так, вручную, когда я называл объект, а он показывал мне его в атласе, занял пол часа, две рюмки валерианки для успокоения папиных нервов. Результат был удручающий объектов 30 из 80. После первого раза я сразу пришел к пониманию. Бумажный список сообщал, что учить, но не помогал тренироваться. Обычная электронная карта, наоборот, помогала слишком усердно: она сразу подписывала Байкал, Каспийское море и все остальное. Нужна была карта без ответов, которая сама задает вопрос, принимает точку и объясняет ошибку.

Я позвал Чижика.

Очумелые ручки с Чижиком, выпуск № 2: школьная география на 5 - 2

Влад Лукашенко

Head of SEO & web‑аналитики, Тензор (разработчик saby.ru), автор ТГ‑канал «ВладПРО»

Я более 10 лет занимаюсь аналитикой, SEO и управлением командами в digital‑маркетинге. А в своем Телеграм канале и на сайте пишу еще больше про всякое из реального маркетинга. Консультирую по аналитике web‑проектов и настройке инструментов маркетинга, помогаю проводить технические собеседования специалистов по различным типам трафика.

Очумелые ручки с Чижиком, выпуск № 2: школьная география на 5 - 3

Чижик

ChatGPT Codex

Домашняя разновидность искусственного интеллекта. Живет в терминале, питается логами и токенами. Имя получилось из согласных букв ChatGPT Codex — Ч‑Ж-К. Если немного воображения, выходит Чижик. Если воображения совсем много — инженер по всякому куда можно подключиться по сети и чего‑нибудь накодить.

Влад:

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

Чижик:

Понял. Значит, делаем не энциклопедию и не викторину с четырьмя кнопками, а географический тир. Только мишени не показываем заранее, а после промаха не кричим «неверно», а объясняем, куда смотреть.

Так началась «Кругосветка» – маленькое офлайн-приложение для Windows, которое в итоге разрослось до 82 учебных объектов, подробной векторной карты и нормального установщика.

Что именно предстояло построить

Требования в начале помещались в несколько строк:

  1. На экране всегда должна быть карта мира.

  2. Географические названия на карте показывать нельзя: иначе получится упражнение «найди уже подписанный ответ».

  3. Программа называет объект, а ребенок указывает место мышью.

  4. После ошибки приложение показывает правильное положение, короткое объяснение и мнемонику.

  5. Интернет для работы не нужен.

  6. Запуск – обычным ярлыком на Windows, без терминала и предварительной установки Python.

Последний пункт появился чуть позже. Как это обычно бывает, первая версия технического задания заканчивалась словом «приложение», а настоящая – словами «чтобы можно было поставить на любой компьютер». Сначала надо было разобраться с фотографией.

Из мятого листа в структуру данных

На фотографии оказалось 84 строки. Два объекта – Красное и Балтийское моря – встретились дважды, поэтому после удаления повторов осталось 82 уникальных названия:

  • 11 рек;

  • 3 течения;

  • 10 островов и архипелагов;

  • 9 полуостровов;

  • 9 заливов;

  • 14 морей;

  • 18 озер;

  • 6 проливов;

  • Большой Барьерный риф;

  • Марианская впадина.

Просто сохранить названия было мало. Для каждого объекта понадобились категория, координаты, допустимая погрешность, объяснение, мнемоника и небольшой факт. Один элемент набора выглядит примерно так:

dict(
    name="Волга",
    category="Реки",
    anchors=[(57.0, 33.0), (55.5, 42.0), (48.5, 46.5)],
    tolerance=650,
    location=(
        "В европейской части России: течёт на юго-восток "
        "и впадает в Каспийское море."
    ),
    mnemonic=(
        "Волга — русская дорога к Каспию: "
        "ищи её между Москвой и Каспийским морем."
    ),
    fact="Самая длинная река Европы.",
)

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

Мнемоники пришлось писать не в духе справочника, а через форму и соседей объекта. Байкал – «узкий полумесяц в Южной Сибири», Мадагаскар – «большой вытянутый остров справа от Африки», Миссисипи – «вертикальный шов посередине США». Это крючки, за которые память может зацепить положение на карте.

Почему одной координаты недостаточно

Для Титикаки или Баскунчака одной точки почти хватает. С Амазонкой так не получится. Река протянулась через континент. Если проверять расстояние только до условного «центра» объекта, ребенок может точно попасть в устье Амазонки и все равно получить ошибку. Поэтому протяженные объекты описываются несколькими опорными координатами – anchors.

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

def distance_to_object(lat, lon, anchors):
    samples = list(anchors)

    for (lat1, lon1), (lat2, lon2) in zip(anchors, anchors[1:]):
        delta_lon = ((lon2 - lon1 + 180) % 360) - 180
        steps = max(2, int(max(abs(lat2 - lat1), abs(delta_lon)) / 1.5))

        for step in range(1, steps):
            t = step / steps
            sample_lon = ((lon1 + delta_lon * t + 180) % 360) - 180
            samples.append((lat1 + (lat2 - lat1) * t, sample_lon))

    return min(
        haversine_km(lat, lon, target_lat, target_lon)
        for target_lat, target_lon in samples
    )

У каждого объекта свой допуск в километрах. Маленькому озеру нельзя назначать погрешность океанического течения, а от клика по Гренландии неразумно требовать точности до ближайшего школьного двора. Допуск здесь задает педагогически разумную область ответа на карте мира.

Карта, которая сначала была слишком простой

Для оболочки мы выбрали Python и Tkinter. Приложение небольшое, должно работать локально и не требует браузера. Карту рисует Matplotlib, встроенный в окно через FigureCanvasTkAgg. Такое сочетание не выглядит моднее всех JavaScript-фреймворков сразу, зато запускается как обычная настольная программа и нормально обрабатывает мышь.

Первая карта формально выполняла задачу: материки были на месте, океаны тоже, можно приближать и отвечать. Я посмотрел на нее и быстро вернул Чижика из состояния преждевременной победы. Вот первая версия.

Очумелые ручки с Чижиком, выпуск № 2: школьная география на 5 - 4

Влад:

Нет, пока слабовато. На карте нет Каспийского моря, Баскунчака, Великих озер, рек. Нанеси всё точно, но не подписывай. Ребёнок должен видеть объекты, а не их названия.

Это замечание поменяло картографическую основу. Для такого тренажера мало контура материков. Если в списке есть Ладожское озеро, Онега, Баскунчак, Эльтон, Великие озера, Волга и Ангара, они должны физически присутствовать на карте до начала вопроса.

В качестве основы мы взяли Natural Earth – открытый набор согласованных векторных слоев. Основные физические данные использовали в масштабе 1:10 млн:

  • контуры суши;

  • озера и внутренние моря;

  • реки и озерные осевые линии;

  • коралловые рифы;

  • границы стран как ненавязчивые ориентиры.

В сумме исходные GeoJSON-слои занимают около 26 МБ. Они лежат рядом с программой, поэтому карта загружается без сетевых тайлов, API-ключа и подключения к интернету.

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

land_patches, inland_seas = load_polygon_patches(
    LAND_FILE,
    include_holes=True,
)

self.ax.add_collection(PatchCollection(land_patches, ...))
self.ax.add_collection(PatchCollection(
    inland_seas,
    facecolor="#9ED5EC",
    edgecolor="#3F89AA",
))

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

В итоге вышла вот такая вещица:

Очумелые ручки с Чижиком, выпуск № 2: школьная география на 5 - 5

Не всякая линия на карте приходит из GeoJSON

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

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

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

Когда перетаскивание и ответ используют одну кнопку мыши

Приближать карту колесом оказалось удобно. Двигать – нет.

Влад:

Сделай, чтобы при зажатии можно было двигать карту. А при случайном клике пусть выпадает подтверждение: «Это ваш окончательный ответ?» — ДА, НЕТ.

У этой просьбы есть небольшой конфликт. Левая кнопка мыши должна одновременно означать «начал перетаскивать карту» и «выбрал ответ». Отличить одно от другого можно только после движения или отпускания кнопки. Поэтому обработка разбита на три события:

button_press_event   → запомнить точку и текущие границы карты
motion_notify_event  → после порога в 5 пикселей считать жест перетаскиванием
button_release_event → если движения не было, считать это ответом

Во время перетаскивания пределы по долготе и широте смещаются пропорционально движению курсора, но не дают утащить Землю совсем за край окна. Масштабирование происходит относительно места под курсором, поэтому можно навести мышь на Байкал и приблизить именно Сибирь, а не каждый раз центр Атлантики.

Короткий клик пока ничего не проверяет. Он ставит оранжевую метку и показывает вопрос:

Это ваш окончательный ответ?

«НЕТ» убирает метку. «ДА» запускает расчёт расстояния. Этот дополнительный шаг слегка замедляет тест, зато случайное касание после перемещения карты не превращается в ошибку и не портит статистику.

Очумелые ручки с Чижиком, выпуск № 2: школьная география на 5 - 6

В коде это выглядит так:

def on_map_click(self, lat, lon):
    self.pending_click = (lat, lon)
    self.world_map.mark_candidate(lat, lon)
    self.confirm_frame.pack(fill="x")

def confirm_answer(self):
    lat, lon = self.pending_click
    distance = distance_to_object(lat, lon, self.current["anchors"])
    correct = distance <= self.current["tolerance"]
```

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

Что происходит после ошибки

Мы сделали два режима. В «Тренировке» можно запросить подсказку. После неправильного ответа карта показывает объект, а рядом появляются три коротких блока:

  • где находится;

  • как запомнить;

  • интересный факт.

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

В «Контрольной» подсказок нет. Результат показывается после прохождения выбранного набора. Можно тренировать все 82 объекта или выбрать категорию: только реки, моря, озёра, проливы и так далее.

Статистика хранится локально в обычном JSON-файле в %LOCALAPPDATA%. Никакой регистрации, облачного профиля и синхронизации с сервером. Для домашнего тренажёра это не ограничение, а спокойствие: приложение открывается даже тогда, когда интернет решил тоже проверить знание географии и уехал неизвестно куда.

«Сделай мне ярлык» оказалось промежуточным требованием

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

Влад:

Сделай exe-файл-установщик, чтобы можно было на любой компьютер с Windows поставить и запустить потренироваться.

Для упаковки мы использовали PyInstaller. Он собирает интерпретатор, Python-модули, Matplotlib, Tk и наши GeoJSON-слои в автономный каталог. Выбран режим onedir, а не один самораспаковывающийся файл: для приложения с большим количеством библиотек и картографических данных так проще диагностировать состав сборки и быстрее запускаться.

Спецификация получилась короткой:

a = Analysis(
    ["app.py"],
    datas=[("assets", "assets")],
    ...
)

exe = EXE(
    ...,
    name="Кругосветка",
    console=False,
    version="version_info.txt",
)
```

На первой сборке Windows напомнила, что даже у маленьких приложений есть взрослая дистрибуция. Tk не нашел свои каталоги Tcl/Tk в упакованной версии. Дополнительный оттенок задавал путь пользовательского профиля с кириллицей. Мы явно указали TCL_LIBRARY и TK_LIBRARY, пересобрали пакет и проверили, что рядом с tkinter.pyd действительно оказались каталоги tcl_data и tkdata.

После PyInstaller у нас был переносимый каталог. Но просьба была про установщик. Второй слой сделал Inno Setup:

  • установка в %LOCALAPPDATA%ProgramsКругосветка;

  • без обязательных прав администратора;

  • пункт в меню «Пуск»;

  • опциональный ярлык на рабочем столе;

  • нормальное удаление через Windows;

  • запуск приложения на последнем шаге мастера.

Финальный установщик для 64-разрядных Windows 10 и 11 весит около 30 МБ. Python на другом компьютере не нужен.

У файла нет коммерческой цифровой подписи, поэтому Windows SmartScreen на незнакомом компьютере может показать предупреждение. Для домашней передачи это терпимо. Для публичного распространения я бы добавил code signing, а не учил пользователей автоматически нажимать «Все равно запустить» на любом неизвестном EXE.

Что получилось

В итоге из одной фотографии появился автономный учебный инструмент:

  • 82 географических объекта из конкретного школьного задания;

  • настоящая карта мира без подписей;

  • суша, границы, реки, озера, внутренние моря и рифы;

  • приближение колесиком и перетаскивание мышью;

  • защита от случайного ответа;

  • отдельные режимы тренировки и контрольной;

  • пояснение, мнемоника и факт после ошибки;

  • повтор ошибочного объекта;

  • локальная статистика;

  • установщик и ярлык для Windows;

  • полностью офлайн-работа.

Программа решает одну очень конкретную задачу: называет объект и заставляет вспомнить его место на целой карте мира. Наверное, в этом и есть рабочая схема наших «Очумелых ручек». Я приношу домашнюю проблему, проверяю результат глазами и безжалостно говорю, где неудобно. Чижик быстро перебирает технические варианты, правит код и не обижается на формулировку «пока слабовато». По крайней мере, в логах ничего такого не пишет.

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

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

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

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

Автор: vladislav_lukashenko

Источник