Об игре в шляпу
2026-02-14
Итак, я захотел организовать шляпу
В лагере нужно проводить всякие игрушки для детей. И одна из этих игрушек – шляпа. Есть простой способ проводить ее — куча приложений есть, которые дают возможность поиграть сходу когда захочется. И это очень хорошо, возможность что-то сделать спонтанно — это огромный плюс. Однако возникло желание организовать самодельную игру в шляпу. Преимущественно — из интереса, чтобы посмотреть, насколько сложно решить возникающие по пути проблемы. Но у этого есть и косвенные преимущества перед уже готовыми приложениями. Основные два — это возможность контролирования словарного состава шляпы и гранулярный контроль за правилами (приложения не позволяют реализовать, например, механику откладывания слова, а она, как мне кажется, повышает робастность игры в шляпу, если смотреть на шляпу как на оценку скорости объяснения слов из фиксированной категории человеком; разумеется, это вкусовщина). Можно придумывать еще всякие реальные и мнимые плюсы и минусы, но это все неважно. Итак, задача — реализовать физическую версию шляпы с созданием и печатью карточек со словами и контролем набора слов. Собственно, это две основные проблемы, которые пришлось решить на пути к цели.
Создание карточек
К созданию карточек было реально два требования — они должны легко печататься и вырезаться и они должны легко создаваться по списку слов (хочется верить, что он будет генерироваться автоматически, но даже если он генерируется вручную, вбиваться они будут в максимально простом формате для гибкости). Собственно, есть четыре три вида документов с разумной печатью: презентации powerpoint (см. On the Turing completeness of PowerPoint), таблицы Excel, таблицы Word, pdf-ки (например, генерируемые латехом).
Во-первых, Excel.
Сам по себе Excel — это документ с непростым бинарным форматом, слишком сложным для нашей задачи. В целом это не критическая проблема — несложно один раз постараться и написать преобразовать простой список -> таблица в excel, но для удобной печати нужно будет еще настраивать границы полей, границы печати, словом — нужно ощутимо запариваться с деталями формата (я даже не уверен, что границы печати можно хорошо задать через API (читать — через команды, доступные из библиотеки языка вроде питона), хотя скорее всего и можно).
Есть формат csv (Comma Separated Values), в котором таблица хранится в формате, разделенным запятыми (duh). Это простой и приятный формат, но для печати по нему таблиц нужно решать те же проблемы с оформлением, что и с excel-ем, так что мимо.
Во-вторых, Word.
Как и Excel, это нетривиальный формат, который представляет из себя сжатую XML-ку, но если надо — разово все сделается.
В нем даже можно делать таблицы и печатать их, но тут проблема в том, что таблицы в ворде — не основные жители, поэтому возможности с ними будут ограничеными, а нужно детально контролировать размер карточек, их количество, и, собственно, преобразовывать список слов в этот формат.
Как и в случае с Excel-ем, можно, но это довольно трудоемкая работа.
В третьих, собственно, latex, тот самый, с помощью которого все серьезные люди пишут статьи, серии задач для технических кружков (кроме, возможно, некоторых физиков в 239 школе) и тому подобное. Сходу есть приятный бонус — известный пакет geometry для латеха позволяет сделать любые разумные размеры листа. Тогда в голове виднеется план действий:
- подогнать размер карточки, чтобы на лист A4 вмещалось ровно M x N карточек
- подогнать шрифт, чтобы шрифт разумно выглядел в пределах карточки
- подогнать поля так, чтобы карточка была примерно посередине листа (тот же пакет позволяет точечно указать поля изображения)
- напечатать получившиеся карточки в количестве MxN на листе с границами карточек (благо, любая разумная pdf-читалка такое умеет делать) После небольшого шаманства получилось это — ровно то, что нужно. Список слов нужно совсем легко обработать — по сути приписать справа от каждого слова какой-то технический префикс вроде \newpage.
Генерация слов
Собственно, содержательная часть работы — откуда-то найти много слов и сгенерировать их. Хотелось бы соблюдать хотя бы примерную стабильность в сложности слов (независимо ни от чего придется процеживать руками слишком выбивающиеся слова, с этим ничего не поделаешь). Откуда брать слова примерно одной сложности?
Нулевую мысль мне подсказал Ваня (для друзей – Иван Викторович) — приложения для шляпы ведь откуда-то уже взяли слова? Можно ровно эти слова и подтянуть. Эти слова можно перебивать, играя в мнимую игру в приложении, но это довольно тяготно и требует внимания и человека, который быстро печатает — такого может не оказаться. Дело бы упростилось, если бы набор слова можно было бы легко выкачать из приложения. И в целом так можно делать, но для этого нужно подтягивать целую среду разработки для просмотра исходного кода андроид приложения (еще, скорее всего, пришлось бы еще немного reverse engineering-ом заниматься, так мы читаем не исходный код, а декомпилированный, хотя я тут совсем не знаю деталей, так как не трогал содержательно мобильную разработку), что явно перебор, а еще и в некотором смысле некрасиво (ведь эти слова тоже не с воздуха взялись, их кто-то придумал и вставил в приложение; если бы разработчик хотел ими поделиться, он бы сделал это явно).
Моей первой идеей было взять корпус русского языка и вытащить из него какую-то информацию. К счастью, корпус русского языка публичный и имеет разумный сайт. К несчастью, информацию с него программно не представляется возможным считать. Все-таки корпус предназначен для другой задачи — не создания списка слов (хотя это тоже можно по желанию), а для анализа слов и лемм, в них встречающихся.
Вторым мне пришло в голову посмотреть на списки слов для изучающих русский как иностранный. Есть популярное деление уровней знания языка: A1, A2, B1, B2, C1, C2, где A1 — базовый уровень знания языка, а C2 — уровень знаний у носителя. Не все языки и соответствующие языковые экзамены придерживаются такой системы (например, в японском деление на уровни — N5, N4, …, N1), но у русского ровно такая система. Тогда достаточно было найти слова по уровням — и в идеальном случае они будут в как раз сгруппированы по примерной сложности. Воодушевишись я загуглил список слов. Я вначале порадовался, так как я сходу нашел разумно обрабатывающийся список слов для изучающих русский как иностранный. Но потом пришло осознание — в нем довольно мало слов на каждый уровень (все-таки иностранцы изучают очень ограниченного подмножество языка, особенно вначале). Также огорчило, что хоть список слов и разумно обрабатывается (то есть не нужно мучаться с выкачиванием данных из стремного формата типа pdf), он обрабатывается не автоматически. Например, в списке были также слова других частей речи, которые не были никак помечены. Чтобы их убрать, пришлось идти на хитрости — например, чтобы выкинуть глаголы, пришлось выкинуть все слова, заканчивающиеся на “ть”. Но так были выкинуты еще слова вроде “нить”, из без того малого списка слов, но также остались невыкинутыми глаголы вроде “умываться”, которые нужно руками находить и отфильтровывать, и так по всем частям речи. Работать можно, но сложно. Можно было бы поискать более приемлемые в этом плане списки слов, но это практически лучшее, что я нашел — в других местах список был еще более грустным в том или ином плане (не составляют люди разумные списки так).
Возникло острое желание все-таки найти какой-то словарь русского языка с данными о словах. Например, частотный словарь — звучит разумно, ведь сложность слова явно кореллирует с частотой его использования. Это оказалось сложнее, чем я думал, ибо таким занималось внезапно мало людей, а также есть много дохлых ссылок и неактуальных постов на форумах. Однако в итоге я нашел идеал — частотный словарь с 50_000(!) словами (из которых аж 22к существительных), в очень приятном формате (тот самый csv) и с задокументированными полями таблицы (!!!). Словом, я наткнулся на клад.
В частности, среди полей словаря был, кроме, собственно, частотности, коэффициент Жуйана (Juilland), который показывает, насколько слово является специфическим или универсальным. Может показаться, что эту характеристику нельзя разумно описать числом. Трюк здесь следующий: если слово универсально, то оно при фиксированной частоте будет примерно равномерно размазано по текстам, на основании которых составлялся словарь. Если слово специфично для какой-то области, то мы найдем его в текстах плотными группами. А свойство “размазанности” можно разумно описать — чем более слова размазанны, тем больше среднее растояния между всеми парами инстанций данного слова в тексте. Более точное описание я здесь приводить не буду, оставлю только общую интуицию. Что важно — это, как мне кажется, лучше, чем частотность, отображает сложность слова (это, однако, только ощущения, реально я не сравнивал их способности оценивать сложность объяснения слова). Теперь осталось только сделать генерацию игрушки:
- из всего списка слов оставить только существительные
- их отсортировать по метрике (например, по коэффициенту Жуйана)
- разбить отсортированный список на крупные блоки, где крупный блок отображает по смыслу сложность
- интересующий нас блок случайно перемешать
- из перемешанного блока выбрать нужное число слов для игры + запас
- руками отфильтровать слова, выбивающиеся по сложности из средней видимой сложности
Таким образом, обе возникшие проблемы в итоге решились, и турнир по шляпе с физическими карточками состоялся. Зачем что-то делать 30 минут, если это можно автоматизировать за день-два :)
Кодовая часть писалась на питоне вначале (потому что у меня стоял Pycharm, поддерживающий питоновские ноутбуки), но после выхода из суеты лагеря переписалась на человеческий язык (Kotlin), где операции вида тех, что перечисленны выше, можно выполнять через точку (в отличие от некоторых других языков, в которых методы вроде map, filter и подобные вызываются через префиксную нотацию; не будем показывать пальцем на эти языки). Репозиторий вот (там есть и котлиновский итоговый код, и черновый питоновский), все открыто для копирования, редактирования и просто вдохновления :)