Второй проект - многопользовательский онлайн-сапёр на условно-бесконечном поле. Писал в 2016.
Условно-бесконечность означает, что поле генерится блоками 50х50. Блоки генерируются в момент первого обращения к ним. Теоретический максимальный размер поля - 40000х40000 блоков, каждый по 2500 ячеек, 4*10^12 ячеек.
На практике, память сервера кончится быстрее, чем получится сгенерировать даже малую долю максимально возможного поля.
Смысл многопользовательности - все игроки играют на общем поле, действия, сделанные одним игроком, рассылаются остальным, кто видит эту же часть карты.
Стек:
Java 8
Java Servlets, WebSockets, Tomcat, без фреймворков
HTML/CSS, JS
Запустить локально:
docker container run -p 80:8080 zhukovsd/online-minesweeper:latest
Цели, которые я перед собой ставил:
Попрактиковаться с многопоточностью
Реализовать real-time веб-приложение с помощью WebSocket
В этом проекте я набил шишек оверинжиниринга. Составные части:
Онлайн сапёр, как применение самописного фреймворка
Сердце проекта - потокобезопасная коллекция, реализующая доступ к блокам карты. Действия на карте требуют блокировки этих блоков, пока клик одного пользователя обрабатываются, другие ждут. В то же время, действия пользователя на одной части карты не должны блокировать действия пользователей в других областях.
Подход к потокобезопасности основан на lock striping.
Интересные решённые проблемы:
Клик на клетку в сапёре может привести к открытию целой группы клеток. Мы делаем допущение, что этот эффект не может распространиться дальше, чем текущий блоков, и его уже сгенерированные соседи - иначе мы бы не знали, сколько блоков нужно заблокировать для обработки действия пользователя. Чтобы исполнить это требование, расстановка мин внутри блока проходит валидацию. Для расчёта группы клеток, которая откроется при клике, применена библиотека графов, написанная для проекта с лабиринтами
Динамически определяем, каким пользователям нужно доставить сообщения по WebSocket об изменениях на конкретных блоках
В целом, проект сложный и интересный, есть над чем подумать. К сожалению, мотивации полноценно закончить его я не нашёл, поэтому в нём осталось много недоделанных TODO и код не особо отполирован. Но свою основную цель я закрыл - узнал много нового про многопоточность.
Если бы писал этот же проект сейчас с целью его реального применения - для хранения состояния поля использовал бы базу данных, а не коллекцию в памяти.
Написать относительно сложный динамический фронт на JS 2015 (в котором есть нормальная поддержка ООП)
Разобраться в Docker и Docker Swarm (Swarm в настоящее время почти полностью вытеснен Kubernetes)
Реализовать автоматический деплой с помощью CircleCI
Интересные решённые проблемы:
Компонент для отображения списка слов для пользователя. Автоматически скроллится вверх и вниз, в зависимости от позиции каретки, подсвечивает правильный и неправильный ввод цветами
Компонент для отображения текущей скорости ввода текста, стилизованный под спидометр
Бэк простой - сохраняет результат теста в БД (Redis), и вычисляет top-% (например - “вы печатаете быстрее, чем 80% человек) результата теста на основе взятой извне статистики.
Итоговый стек из двух сервисов задеплоен в облако Hetzner на кластер из двух нод, где и работает по сей день.