6 Commits

Author SHA1 Message Date
s3rius 0795dff811 Small updates.
/ docker_build (push) Successful in 1m48s
/ helm_deploy (push) Successful in 45s
Testing zangramru / lint (push) Successful in 1m8s
Testing zangramru / pytest (push) Successful in 40s
Signed-off-by: Pavel Kirilin <s3riussan@gmail.com>
2026-07-03 23:31:10 +02:00
s3rius eb2c2771c4 Fixed lints.
Testing zangramru / lint (push) Successful in 1m5s
Testing zangramru / pytest (push) Successful in 1m5s
Signed-off-by: Pavel Kirilin <s3riussan@gmail.com>
2026-07-03 10:24:45 +02:00
s3rius 9036816e97 Fixed docker ignore.
Testing zangramru / lint (push) Failing after 1m5s
Testing zangramru / pytest (push) Successful in 1m6s
Signed-off-by: Pavel Kirilin <s3riussan@gmail.com>
2026-07-03 04:46:15 +02:00
s3rius ab7fb24b01 Added dict generators.
Testing zangramru / lint (push) Failing after 1m3s
Testing zangramru / pytest (push) Successful in 1m44s
Signed-off-by: Pavel Kirilin <s3riussan@gmail.com>
2026-07-03 04:05:55 +02:00
s3rius a2f96e247a More dicts.
/ docker_build (push) Successful in 1m20s
/ helm_deploy (push) Successful in 33s
Testing zangramru / pytest (push) Successful in 18s
Testing zangramru / lint (push) Successful in 31s
Signed-off-by: Pavel Kirilin <s3riussan@gmail.com>
2026-07-03 03:24:19 +02:00
s3rius cf98f5ccbd Dict update.
/ docker_build (push) Successful in 5m56s
/ helm_deploy (push) Successful in 48s
Testing zangramru / lint (push) Successful in 2m53s
Testing zangramru / pytest (push) Successful in 3m22s
Signed-off-by: Pavel Kirilin <s3riussan@gmail.com>
2026-07-03 01:07:08 +02:00
16 changed files with 25174 additions and 532 deletions
+1
View File
@@ -1,4 +1,5 @@
### Python template
scripts/
deploy/
.idea/
+2
View File
@@ -1,2 +1,4 @@
**/*.csv filter=lfs diff=lfs merge=lfs -text
**/*.png filter=lfs diff=lfs merge=lfs -text
scripts/dicts/data/*.txt filter=lfs diff=lfs merge=lfs -text
**/*.gz filter=lfs diff=lfs merge=lfs -text
-115
View File
@@ -1,115 +0,0 @@
digraph model_graph {
fontname = "Roboto"
fontsize = 8
splines = true
rankdir = "LR";
node [
fontname = "Roboto"
fontsize = 8
shape = "plaintext"
]
edge [
fontname = "Roboto"
fontsize = 8
]
// Tables
TABLE_Puzzle [label=<
<TABLE BGCOLOR="white" BORDER="1" CELLBORDER="0" CELLSPACING="0">
<TR>
<TD COLSPAN="2" CELLPADDING="5" ALIGN="CENTER" BGCOLOR="#4C89C8">
<FONT FACE="Roboto" COLOR="white" POINT-SIZE="10">
<B>Puzzle</B>
</FONT>
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" BORDER="0">
<FONT FACE="Roboto">
<B>id</B>
</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT FACE="Roboto">
<B>UUID</B>
</FONT>
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" BORDER="0">
<FONT FACE="Roboto">
<B>board</B>
</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT FACE="Roboto">
<B>Varchar</B>
</FONT>
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" BORDER="0">
<FONT FACE="Roboto">
<B>solutions</B>
</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT FACE="Roboto">
<B>JSON</B>
</FONT>
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" BORDER="0">
<FONT FACE="Roboto">
<B>extra</B>
</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT FACE="Roboto">
<B>JSON</B>
</FONT>
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" BORDER="0">
<FONT FACE="Roboto">
<B>definitions</B>
</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT FACE="Roboto">
<B>JSON</B>
</FONT>
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" BORDER="0">
<FONT FACE="Roboto">
<B>date</B>
</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT FACE="Roboto">
<B>Date</B>
</FONT>
</TD>
</TR>
</TABLE>
>]
// Relations
}
+92
View File
@@ -0,0 +1,92 @@
import csv
import gzip
import json
import logging
from collections import defaultdict
from pathlib import Path
from zangramru.utils.trie import TrieNode
CWD = Path(__file__).parent
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
def build_trie(dict_path: Path) -> TrieNode:
"""Build trie for wikidict definitions."""
trie = TrieNode()
total_words = 0
with gzip.open(dict_path) as f_in:
for line in f_in:
word_info = json.loads(line)
# If it starts wit capital.
if word_info.get("lang_code") != "ru":
continue
if word_info.get("pos") != "noun":
continue
if word_info["word"][0].isupper():
continue
senses = word_info.get("senses", [])
multi_def = []
for sense in senses:
glosses = sense.get("glosses")
if glosses:
multi_def.extend(glosses)
total_words += 1
trie.add(word_info["word"], definitions=multi_def)
logger.info(f"Trie is ready. Total words: {total_words}")
return trie
def get_defs(
dict_file: Path,
defs: TrieNode,
stats: dict[str, int],
) -> dict[str, list[str]]:
"""Find all word definitions for all our dicts."""
word_def = {}
with dict_file.open("r") as f_in:
for line in f_in:
if len(line.strip()) < 4:
stats["too_short"] += 1
continue
node = defs.find_node(line)
if node is None:
stats["not_found"] += 1
continue
if not node.valid_word:
stats["invalid"] += 1
continue
word_def[node.value] = node.definitions
return word_def
def main() -> None:
"""Update dictionaries."""
trie = build_trie(Path(CWD / "wikidict.jsonl.gz"))
with Path(CWD.parent.parent / "zangramru/services/dict.csv").open("w") as f_out:
writer = csv.writer(f_out, delimiter="|")
writer.writerow(["word", "def"])
stats = defaultdict(int)
words: dict[str, list[str]] = {}
for file in Path(CWD / "data").iterdir():
logger.info(f"Processing {file}")
words.update(get_defs(file, trie, stats))
logger.info("Done updating")
logger.info(stats)
for word, defs in words.items():
all_defs = ""
if len(defs) == 1:
all_defs = defs[0]
else:
all_defs = "\n".join(f"{i + 1}: {defn}" for i, defn in enumerate(defs))
writer.writerow([word, all_defs])
if __name__ == "__main__":
main()
+100
View File
@@ -0,0 +1,100 @@
и
в
не
на
я
быть
он
с
что
а
по
это
она
этот
к
но
они
мы
как
из
у
который
то
за
свой
что
весь
год
от
так
о
для
ты
же
все
тот
мочь
вы
человек
такой
его
сказать
только
или
ещё
бы
себя
один
как
уже
до
время
если
сам
когда
другой
вот
говорить
наш
мой
знать
стать
при
чтобы
дело
жизнь
кто
первый
очень
два
день
её
новый
рука
даже
во
со
раз
где
там
под
можно
ну
какой
после
их
работа
без
самый
потом
надо
хотеть
ли
слово
идти
большой
должен
место
иметь
ничто
+900
View File
@@ -0,0 +1,900 @@
то
сейчас
тут
лицо
каждый
друг
нет
теперь
ни
глаз
тоже
тогда
видеть
вопрос
через
да
здесь
дом
да
потому
сторона
какой-то
думать
сделать
страна
жить
чем
мир
об
последний
случай
голова
более
делать
что-то
смотреть
ребенок
просто
конечно
сила
российский
конец
перед
несколько
вид
система
всегда
работать
между
три
нет
понять
пойти
часть
спросить
город
дать
также
никто
понимать
получить
отношение
лишь
второй
именно
ваш
хотя
ни
сидеть
над
женщина
оказаться
русский
один
взять
прийти
являться
деньги
почему
вдруг
любить
стоить
почти
земля
общий
ведь
машина
однако
сразу
хорошо
вода
отец
высокий
остаться
выйти
много
проблема
начать
хороший
час
это
сегодня
право
совсем
нога
считать
главный
решение
увидеть
дверь
казаться
образ
писать
история
лучший
власть
закон
все
война
бог
голос
найти
поэтому
стоять
вообще
тысяча
больше
вместе
маленький
книга
некоторый
решить
любой
возможность
результат
ночь
стол
никогда
имя
область
молодой
пройти
например
статья
оно
число
компания
про
государственный
полный
принять
народ
никакой
советский
жена
настоящий
всякий
группа
развитие
процесс
суд
давать
ответить
старый
условие
твой
пока
средство
помнить
начало
ждать
свет
пора
путь
душа
куда
нужно
разный
нужный
уровень
иной
форма
связь
уж
минута
кроме
находиться
опять
многий
белый
собственный
улица
черный
написать
вечер
снова
основной
качество
мысль
дорога
мать
действие
месяц
оставаться
государство
язык
любовь
взгляд
мама
играть
далекий
лежать
нельзя
век
школа
подумать
уйти
цель
среди
общество
посмотреть
деятельность
организация
кто-то
вернуться
президент
комната
порядок
момент
театр
следовать
читать
письмо
подобный
следующий
утро
особенно
помощь
ситуация
роль
бывать
ходить
рубль
начинать
появиться
смысл
состояние
называть
рядом
квартира
назад
равный
из-за
орган
внимание
тело
труд
прийтись
хотеться
сын
мера
пять
смерть
живой
рынок
программа
задача
предприятие
известный
окно
вести
совершенно
военный
разговор
показать
правительство
важный
семья
великий
производство
простой
значит
третий
сколько
огромный
давно
политический
информация
действительно
положение
поставить
бояться
наконец
центр
происходить
ответ
муж
автор
все-таки
стена
существовать
даже
интерес
становиться
федерация
правило
оба
часто
московский
управление
слышать
быстро
смочь
заметить
как-то
мужчина
долго
правда
идея
партия
иногда
использовать
пытаться
готовый
чуть
зачем
представить
чувствовать
создать
совет
счет
сердце
движение
вещь
материал
неделя
чувство
затем
данный
заниматься
продолжать
красный
глава
ко
слушать
наука
узнать
ряд
газета
причина
против
плечо
современный
цена
план
приехать
речь
четыре
отвечать
точка
основа
товарищ
культура
слишком
рассказывать
вполне
далее
рассказать
данные
представлять
мнение
социальный
около
документ
институт
ход
брать
забыть
проект
ранний
встреча
особый
целый
директор
провести
спать
плохой
может
впрочем
сильный
наверное
скорый
ведь
срок
палец
опыт
помочь
больше
приходить
служба
крупный
внутренний
просить
вспомнить
открыть
привести
судьба
пока
девушка
поскольку
очередь
лес
пусть
экономический
оставить
правый
состав
словно
федеральный
спрашивать
принимать
член
искать
близкий
количество
похожий
событие
объект
зал
создание
войти
различный
значение
назвать
достаточно
период
хоть
шаг
необходимый
успеть
произойти
брат
искусство
единственный
легкий
структура
выходить
номер
предложить
пример
пить
исследование
гражданин
глядеть
человеческий
игра
начальник
сей
рост
ехать
международный
тема
принцип
дорогой
попасть
десять
начаться
верить
метод
тип
фильм
небольшой
держать
либо
позволять
край
местный
менее
гость
купить
уходить
собираться
воздух
туда
относиться
бывший
требовать
характер
борьба
использование
кстати
подойти
размер
удаться
образование
получать
мальчик
кровь
район
небо
американский
армия
класс
представитель
участие
девочка
политика
сначала
герой
картина
широкий
доллар
спина
территория
мировой
пол
тяжелый
довольно
поле
ж
изменение
умереть
более
направление
рисунок
течение
возможный
церковь
банк
отдельный
средний
красивый
сцена
население
большинство
сесть
двадцать
случиться
музыка
короткий
правда
проходить
составлять
свобода
память
приходиться
причем
команда
установить
союз
будто
поднять
врач
серьезный
договор
стараться
уметь
встать
дерево
интересный
факт
добрый
всего
хозяин
национальный
однажды
длинный
природа
домой
страшный
прошлый
будто
общественный
угол
чтоб
телефон
позиция
проводить
скоро
наиболее
двор
обычно
бросить
разве
писатель
самолет
объем
далеко
род
солнце
вера
берег
спектакль
фирма
способ
завод
цвет
трудно
журнал
руководитель
специалист
возможно
детский
точно
объяснить
оценка
единый
снять
определенный
низкий
нравиться
услышать
регион
связать
песня
процент
родитель
позволить
чужой
море
странный
требование
чистый
весьма
какой-нибудь
основание
половина
поехать
положить
входить
легко
поздний
роман
круг
анализ
стихи
автомобиль
специальный
экономика
литература
бумага
вместо
впервые
видно
научный
оказываться
поэт
показывать
степень
вызвать
касаться
господин
надежда
сложный
вокруг
предмет
отметить
заявить
вариант
министр
откуда
реальный
граница
действовать
дух
модель
операция
пара
сон
немного
название
ум
повод
старик
способный
мало
миллион
малый
старший
успех
практически
получиться
личный
счастье
необходимо
свободный
ребята
обычный
кабинет
прекрасный
высший
кричать
прежде
магазин
пространство
выход
остановиться
удар
база
знание
текст
сюда
темный
защита
предлагать
руководство
вовсе
площадь
сознание
гражданский
убить
возраст
молчать
согласиться
участник
участок
рано
пункт
несмотря
сильно
столь
сообщить
линия
бежать
желание
папа
кажется
петь
доктор
губа
известно
дома
вызывать
дочь
показаться
среда
председатель
представление
солдат
художник
принести
волос
оружие
выглядеть
соответствие
никак
ветер
внешний
парень
служить
зрение
попросить
генерал
состоять
огонь
отдать
боевой
понятие
строительство
ухо
выступать
грудь
нос
ставить
завтра
возникать
когда
страх
услуга
рабочий
что-нибудь
глубокий
содержание
радость
безопасность
надеяться
продукт
видимо
комплекс
бизнес
подняться
вспоминать
мало
сад
долгий
одновременно
называться
сотрудник
лето
тихо
зато
прямой
курс
помогать
предложение
финансовый
открытый
почему-то
значить
возникнуть
рот
где-то
технология
знакомый
недавно
реформа
отсутствие
нынешний
собака
камень
будущее
звать
рассказ
контроль
позвонить
река
хватать
продукция
сумма
техника
исторический
вновь
народный
прямо
ибо
выпить
здание
сфера
знаменитый
иначе
потерять
необходимость
больший
фонд
иметься
вперед
подготовка
вчера
лист
пустой
очередной
республика
хозяйство
полностью
получаться
учиться
плохо
воля
судебный
бюджет
возвращаться
расти
снег
деревня
обнаружить
мужик
постоянно
зеленый
элемент
обстоятельство
почувствовать
немец
многое
победа
источник
немецкий
золотой
передать
технический
нормальный
едва
желать
ожидать
некий
звезда
городской
выбор
соответствующий
масса
составить
итог
сестра
что
шесть
ясно
практика
сто
нести
определить
проведение
карман
любимый
родной
западный
обязательно
слава
кухня
определение
пользоваться
быстрый
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
фрак
тина
зонт
некроз
норка
порка
фигня
понт
пони
рокот
топот
потоп
жакет
гопак
колпак
сено
навес
воск
хакер
рокер
срам
меркурий
венера
марс
юпитер
сатурн
уран
нептун
плутон
резка
резак
пилон
File diff suppressed because it is too large Load Diff
Binary file not shown.
+9706 -361
View File
File diff suppressed because it is too large Load Diff
+2 -55
View File
@@ -10,6 +10,8 @@ from typing import Annotated
from fastapi import Depends, FastAPI, Request
from zangramru.utils.trie import TrieNode
CWD = Path(__file__).parent
# Weighted toward common Russian letters
@@ -42,61 +44,6 @@ class Puzzle:
print(data) # noqa: T201
class TrieNode:
"""Prefix tree for optimized solution search."""
def __init__(self, value: str = "") -> None:
self.value = value
self.children: dict[str, TrieNode] = {}
self.valid_word = False
def get_next(self, symbol: str) -> "TrieNode | None":
"""
Get next possible node in from a given symobl.
Simple lookup in children of this prefix.
"""
return self.children.get(symbol)
def add(self, word: str) -> None:
"""Add a word to the trie."""
word = word.lower()
node = self
for i in range(len(word)):
if word[i] not in node.children:
node.children[word[i]] = TrieNode(word[: i + 1])
node = node.children[word[i]]
node.valid_word = True
def find_node(self, word: str) -> "TrieNode | None":
"""
Find a node in the trie.
The node will only be returned if
the word is in the trie, or
is a prefix of a longer word.
"""
word = word.lower()
node = self
for i in range(len(word)):
if word[i] not in node.children:
return None
node = node.children[word[i]]
return node
def __str__(self) -> str:
ret = "Node"
if self.value:
ret += f"(val={self.value})"
ret += "\n"
for prefix in self.children:
ret += f" {prefix}:\n"
child = str(self.children[prefix])
for line in child.splitlines():
ret += f" {line}\n"
return ret
class PuzzleGenerator:
"""Helper for generating puzzles."""
+1 -1
View File
@@ -1060,7 +1060,7 @@
let text;
if (puzzleNumber === null) {
const compressedBoard = board.flat().join('');
const url = window.location.origin + window.location.pathname + '#' + encodeURIComponent(compressedBoard);
const url = window.location.origin + window.location.pathname + '#' + compressedBoard;
text = 'Занграммы!\n🎉 Мне потребовалось всего лишь ' + formatTime(elapsed) + '\n\n' + url;
} else {
text = 'Занграммы #' + puzzleNumber +
View File
+56
View File
@@ -0,0 +1,56 @@
class TrieNode:
"""Prefix tree for optimized solution search."""
def __init__(self, value: str = "") -> None:
self.value = value
self.children: dict[str, TrieNode] = {}
self.valid_word = False
self.definitions: list[str] = []
def get_next(self, symbol: str) -> "TrieNode | None":
"""
Get next possible node in from a given symobl.
Simple lookup in children of this prefix.
"""
return self.children.get(symbol)
def add(self, word: str, definitions: list[str] | None = None) -> None:
"""Add a word to the trie."""
word = word.lower().strip().replace("ё", "е") # noqa: RUF001
node = self
for i in range(len(word)):
if word[i] not in node.children:
node.children[word[i]] = TrieNode(word[: i + 1])
node = node.children[word[i]]
node.valid_word = True
if definitions:
node.definitions.extend(definitions)
def find_node(self, word: str) -> "TrieNode | None":
"""
Find a node in the trie.
The node will only be returned if
the word is in the trie, or
is a prefix of a longer word.
"""
word = word.lower().strip().replace("ё", "е") # noqa: RUF001
node = self
for i in range(len(word)):
if word[i] not in node.children:
return None
node = node.children[word[i]]
return node
def __str__(self) -> str:
ret = "Node"
if self.value:
ret += f"(val={self.value})"
ret += "\n"
for prefix in self.children:
ret += f" {prefix}:\n"
child = str(self.children[prefix])
for line in child.splitlines():
ret += f" {line}\n"
return ret