New
Apps 1,043Makers 1,041This week 1,043
Promote your app−30%
Almanac screenshot 1
0views in
the last 30 days

Almanac

Real-time quiz battles in Bulgarian: duels, 8-player royale and a daily challenge.

Built with Claude Code

by almanac-bg · from IEIreland · added Oct 29, 2026
no ratings0 comments

АЛМАНАХ ⚡ — almanac.bg

Куиз битки в реално време за много играчи: играй на almanac.bg. Тук е пълният изходен код на играта, свободен софтуер под AGPL-3.0.

АЛМАНАХ

Какво има:

  • Режими: соло тренировка, въпросите на деня, класирани дуели 1 на 1 и отбори 2 на 2, мачове всеки срещу всеки за 3 и 5 играчи, Роял за 8 играчи с отпадане, частни стаи за приятели и линкове с предизвикателство, които всеки може да изиграе.
  • Прогрес: профилите качват нива, отключват аватари, рамки и титли, печелят постижения, изпълняват дневни задачи, трупат дневна серия (с щитове), изкачват се в месечни сезони и пазят подробна статистика.
  • Приятели и общност: играчите могат да се добавят за приятели, да се канят и предизвикват, да искат реванш и да предлагат свои въпроси за одобрение. Посетителите могат да играят веднага като гости и да запазят прогреса си по-късно.

Бърз старт

Нужен е Node 24+. Играта ползва вградения node:sqlite, така че няма база данни за инсталиране.

npm install
npm run dev

Отвори http://localhost:5173, регистрирай профил и играй. При разработка първият профил, който регистрираш, става администратор, за да може да одобрява въпроси от общността. В продукция никой не става администратор с регистрация; виж стъпка 7 от Внедряване.

Истинската банка с въпроси на играта (около 2000 въпроса) не е част от публичния код, за да не могат отговорите да се търсят по време на мач. Без нея се играе с примерните 120 въпроса от apps/server/src/data/questions.sample.json — публична извадка от истинската банка, която стига за разработка и за всички тестове. Виж Въпроси.

Когато никой друг не чака за мач, след няколко секунди ботове заемат празните места, така че всеки режим може да се играе и без други хора. За да тестваш истински мултиплейър локално, отвори втори браузър (или частен прозорец) с друг профил.

Скрипт Какво прави
npm run dev API + realtime сървър на :3001 и Vite на :5173 (през proxy)
npm run build Билдва уеб приложението и пакетира сървъра в apps/server/dist
npm start Продукционен сървър на :3001 (чист Node), който сервира и уеб приложението
npm run typecheck Проверява типовете във всички пакети
npm test Всички тестове (около минута и половина), виж по-долу
npm run lint Форматиране, правила и подредба на импортите (Biome), както в CI
npm run format Поправя автоматично каквото може от горното

Тестове

npm test пуска тези тестове с вградения test runner на Node:

  • e2e.test.ts стартира истински сървър с временна база данни и минава през всичко с истински сокети. Двама играчи и ботове покриват: соло, класиран дуел с повторно свързване, отпадания в Роял, частни стаи и отбори, въпросите на деня и линковете към тях, приятели, покани и предизвикателства, всеки коз, въпроси от общността с модерация, сезонни награди, сигнали и скриване на грешни въпроси, гости, линкове с предизвикателство, реванши, отказ от напомнянията за серията, резултати, показани отново след прекъсната връзка, невалидни съобщения по сокета (които никога не бива да сриват сървъра) и кеширането на файловете на сайта.
  • game.test.ts управлява енджина на мача с подменен часовник: напускане и служебни загуби, прекъснати връзки, изоставени мачове, „Двойно или нищо“, равенства и силата на ботовете.
  • rewards.test.ts проверява какво носи класиран мач, върху истинска база: мач, напуснат преди третия въпрос, не се зачита, по-късното напускане носи победа според изиграното, гостите играят срещу хора без рейтинг, а лимитът от три мача на ден между двама казва защо рейтингът не се е променил.
  • restart.test.ts покрива плавните рестарти (текущите мачове първо завършват) и опит в дневното предизвикателство, прекъснат от срив.
  • database.test.ts проверява историята на въпросите за всеки играч и че честите заявки използват индекси.
  • production.test.ts пуска сървъра като в продукция: администратори се добавят само от командния ред, security хедъри за браузъра, health check, отчети за сривове, заглавията и адресите на страниците, „noindex“ и 404 за грешни адреси.
  • maintenance.test.ts покрива почистването на изтекли сесии, дневните копия на базата данни и изтриването на профили, неизползвани три години (с писмо месец по-рано).
  • progress.test.ts покрива дневните задачи, щитовете за серията, вечерното напомняне за серията, почистването на гост-профили и статистиката за старта.
  • monitoring.test.ts проверява отчетите за грешки към Sentry, ограничението на броя им и че тунелът от браузъра препраща само към собствения проект на сайта.
  • questions.test.ts валидира банката с въпроси. Новите въпроси трябва да минават проверката.
  • rules.test.ts проверява правилата за точките и прогреса, включително границите на дните и сезоните по софийско време при смяна на лятното часово време.

npm run test:dist -w @quiz/server пуска същите тестове срещу продукционния bundle. Колко играчи носи един сървър показва npm run build && caffeinate -i npm run load -w @quiz/server (няколко минути; ulimit -n 30000 преди големи стъпки): хиляди симулирани играчи с реалните времена на играта и колко бързо отговаря сървърът междувременно. На MacBook с M4 3000 души наведнъж минаха без нито една грешка, с най-много 35 ms забавяне. GitHub Actions (.github/workflows/ci.yml) при всеки push към main и всеки pull request пуска lint, typecheck, build, двата варианта на тестовете и браузърните тестове, на Node 24 и 26; после билдва Docker образа, проверява, че стартира, и прави пълен бекъп и възстановяване с Litestream. На main, щом всичко това мине, билдва образа за сървъра (ARM) и го публикува в GitHub Container Registry като ghcr.io/almanac-bg/almanac:<commit> — продукцията пуска точно него.

Конфигурация (променливи на средата)

Променлива По подразбиране Значение
PORT 3001 Порт на сървъра
DB_PATH apps/server/data/quiz.db SQLite файл (създава се и се мигрира автоматично)
BOT_FILL_SECONDS 8 Изчакване в опашката, преди ботове да заемат празните места; off изключва ботовете
BOT_FILL_BUSY_SECONDS 20 Същото, докато друг човек е онлайн и свободен (не е в мач, опашка или стая): тогава си струва да го изчакаме
PUBLIC_QUEUES all Класирани опашки, в които играчите могат да влизат (all или списък от duel, teams, squad, royale)
COOKIE_SECURE false Задай true, когато сайтът се сервира през HTTPS
TRUST_PROXY loopback Express trust proxy: на кои reverse proxy сървъри да се вярва
APP_URL http://localhost:5173 Публичният адрес (origin) в линковете в имейлите
SMTP_URL Напр. smtp://user:pass@host:587. Без него → имейлите се печатат в конзолата
EMAIL_EVENTS_SECRET Тайната в адреса за отчети от Scaleway (/api/email-events/<тайна>). Без нея → изключено
MAIL_FROM АЛМАНАХ <no-reply@almanac.bg> Адрес на подателя
GAME_TIME_SCALE 1 Умножава всички таймери в играта. Само за автоматични тестове
BACKUP_DIR backups до базата данни Дневни копия на базата; off ги изключва
BACKUP_KEEP_DAYS 3 Колко дневни копия да се пазят
DRAIN_SECONDS 330 в продукция, иначе 0 При спиране: колко време имат текущите мачове да завършат
SENTRY_DSN Sentry проект за отчети за грешки; без него → грешките отиват само в лога
APP_RELEASE Версия в отчетите за грешки (командата за деплой задава git commit-а)
OPERATOR_NAME, OPERATOR_ADDRESS Кой поддържа сайта; показва се в политиката за поверителност и условията (името е задължително, адресът — не)
CONTACT_EMAIL privacy@<domain> Адрес за контакт в правните страници и имейлите
EMAIL_PROVIDER, HOSTING_PROVIDER Посочени в политиката за поверителност като обработващи данни
BACKUP_PROVIDER, INBOX_PROVIDER Също в политиката, ако са зададени: бекъпите извън сървъра и пренасочването на писмата до CONTACT_EMAIL
SOURCE_URL Публичното хранилище, към което води линкът във футъра („Изходен код“)
QUESTIONS_PATH private/questions.json, иначе примерната Вградената банка с въпроси (виж Добавяне на въпроси)

Името на марката е в packages/shared/src/brand.ts.

Структура на кода

packages/shared   Правила и каталог, общи за клиента и сървъра: режими, крива на нивата, сезони,
                  козметика, козове, постижения, типове за сокетите и REST
apps/server       Express + Socket.IO + SQLite
  src/game/Game.ts        Енджинът на мача, изцяло на сървъра (таймери, точки, отбори, козове, отпадане, ботове)
  src/game/matchmaking.ts Опашки за бърз мач, попълване с ботове и балансиране на отборите
  src/game/rooms.ts       Частни стаи (6-цифрени кодове, линкове /join/123456, отбори, покани към приятели)
  src/game/rewards.ts     Записва мачовете: XP, статистика, сезонно Elo, дневни резултати, постижения
  src/seasons.ts          Месечни сезони: частично нулиране, награди в края на сезона (изчисляват се при нужда)
  src/daily.ts            Въпросите на деня: един набор на ден (от seed), един опит, линкове към деня
  src/skill.ts            Колко труден е всеки въпрос и колко силен всеки играч, мерено по отговорите (като Elo)
  src/runs.ts             Всеки изигран мач въпрос по въпрос (пази се година): за съперници-записи и анализ
  src/friends.ts          Покани за приятелство и кой е онлайн
  src/submissions.ts      Въпроси от общността и модерация
  src/data/questions.sample.json  Публична извадка от банката с въпроси (истинската е частна, в private/)
  test/                   Тестовете на сървъра (виж „Тестове“)
packages/client   Ядрото на клиента: API заявки, състояние на играта в реално време (zustand), форматиране.
                  Сайтът добавя звуците си; отделено е, за да могат и други клиенти да го ползват
apps/web          React 19 + Vite + Tailwind 4 + Motion
  e2e/                    Браузърните тестове (Playwright)
deploy/           Caddyfile (HTTPS и страницата при обновяване) и litestream.yml (бекъпите)
scripts/          github-setup.sh: настройките на хранилището в GitHub

Как се играе

  • Режими:
    • Соло: 10 въпроса от избрана категория.
    • Въпросите на деня: едни и същи 10 въпроса за всички (всеки играч получава свой ред на отговорите), един опит на ден, без козове, собствена класация. Изтеглят се първо от въпросите с „Знаеше ли?“, а от тях — от най-малко играните. Гост или профил, направен днес, играе същите въпроси, но извън класацията, и вижда къде щеше да е в нея („Щеше да си на 7-мо място от 24“): иначе профил за секунди би бил класиран заедно с основния. Гост от предишен ден, който запази профила си, влиза в класацията веднага, с днешния си резултат. Изиграл деня, играчът може да прати свой линк към него (/daily/<код>, важи до полунощ): който го отвори, играе същите въпроси, вижда резултата на пратилия и се сравнява с него, а пратилият получава известие и вижда на /daily всички изиграли по линка му. Опит, прекъснат от срив или обновяване след разкрит отговор, приключва с резултата дотук (иначе същите въпроси щяха да дойдат отново с известни отговори), а преди първия разкрит отговор може да се изиграе отново; напуснат на отброяването (преди първия въпрос) не е опит. Денят се брои за серията, ако е изиграна поне половината.
    • Дуел: 1 на 1, в три рунда по три въпроса, всеки от една категория. Първият рунд е изтеглен; преди втория и третия изоставащият по точки (при равенство — с по-нисък рейтинг) избира категорията от три неизиграни, за 5 секунди (иначе тя се изтегля; ботът избира силна своя). Трудността пак расте: лесни, средни, после трудни. Последният, десети въпрос е финалът с число, от категория извън рундовете.
    • Отбори: двама приятели в един отбор срещу друга двойка (или два бота). Влиза се само по двама: единият кани приятел от картата на режима (duo:invite), другият приема (duo:accept) и двамата чакат заедно; „Реванш“ пази двойките.
    • Арена (3–5): 10 въпроса в рундове като Дуел (категорията избира последният) и финал с число. Започва с петима веднага, а щом времето за чакане изтече — с колкото са дошли; ботове допълват само до трима. Замени Трио (3) и старата Арена (5); мачовете „Трио“ остават в историята, а PUBLIC_QUEUES=trio значи Арена.
    • Роял: 8 играчи. Играчът с най-малко точки отпада след въпроси 4, 6, 8, 9, 11 и 13 (не след 3-ия: един неуспешен първи въпрос не стига, за да изпрати някого у дома); последните двама играят 14-ия и финала с число.
    • Предизвикателство (линк с предизвикателство, /c/<id>): играч създава линк с 10 фиксирани въпроса и го изиграва, преди да го сподели; всеки с линка (и гостите) играе същите въпроси веднъж, със свой ред на отговорите и без козове, и влиза в класацията на самия линк. Създателят получава известие за всеки резултат. Линковете важат 7 дни.
    • Дуел с приятел (същият /c/<id>, но само за двама): играеш сега, приятелят ти има 24 часа за същите въпроси — без да е нужно двамата да са онлайн. Въпросите са в рундове като в Дуел (без избор по време на мача) и с финал с число; без козове и без рейтинг. Приятелят получава известие с резултата за биене, а който е играл пръв — известие как е свършил дуелът (с XP за победата, колкото за победа 1 на 1). Всяка двойка има общ резултат от дуелите си (на профила и в резултатите). „Реванш“: категориите на трите рунда избира загубилият последния дуел. Най-много 3 неизиграни дуела на двойка; изтекъл неизигран дуел носи известие на изиграл своя ход.
    • Частна стая: 2–40 играчи (с елиминации — до 8), с настройки за въпросите, таймера, категориите („Микс“, „Избира последният“ — рундове, или една), финала с число, отпадането и отборите. „Само водя“: домакинът не играе, а показва мача на голям екран (въпросът, колко са отговорили, после колко са избрали всеки отговор, фактът и първите петима; накрая подиумът), докато играчите отговарят от телефоните. Гост, който влиза в стая, си избира име за нея (2–20 знака, уникално в стаята). В голям мач класирането показва първите и теб.
    • Моите въпроси (/sets): собствен набор до 50 въпроса — с избор от четири, вярно/невярно или число (верният отговор, мерна единица и отклонение, при което точките стават нула) — играе се в частните стаи на автора, в реда на набора. При вярно/невярно 50/50 и „Втори шанс“ не важат. Линкът (/sets/shared/<код>) дава на друг домакин да вземе копие; сигналът отива при модераторите като доклад за автора, а модератор може да скрие набора. Въпрос с избор може да се предложи в общата банка. Мачът на собствен набор плаща само класирането в стаята и не мери знание (без сила, статистика, алманах, задачи и постижения за мача).
    • Вечерен Роял: всеки ден в 21:00 софийско време (EVENING_AT, off го спира), вход от 20:55. До 50 души в мач (повече правят няколко мача), ботове само до осем. След 3-ти, 6-и, 9-и и 12-и въпрос отпадат последните 30% (поне един), след 13-и — всички освен двама, които играят 14-и и финала с число. Без рейтинг; XP като в Роял за осем. Победителят носи „Шампион на вечерта“ 24 часа, три победи дават рамка, а първите десет са на началната страница до следващата вечер.
    • Всички класирани опашки са отворени; PUBLIC_QUEUES (списък) може да затвори някои.
    • Поправителен: сгрешените въпроси (от всеки режим) се връщат 3 дни след грешката, после — познати — след 7 и след 21 дни; сгрешен отново, въпросът започва отначало, а познат и на третата стъпка излиза. Играе се, щом поне 3 са се върнали, по до 10 наведнъж (въпросите с число накрая), без козове и без финал. Верните отговори носят XP, но отговорите (вече показани веднъж) не влизат никъде, където се мери знание: в силата и рейтинга на въпросите, в общите числа за тях, в дневника на отговорите, в записите за повторение, в точността и рекордите, в задачите и в постиженията за мача.
  • Моят алманах (/almanac): „Знаеше ли?“ на всеки въпрос, познат в мач (или спрян за четене, когато играеш сам), остава на играча — за всяка категория страница с търсене и брой („48 от 116“). Пазят се всички познати въпроси, затова факт, написан по-късно, също се появява. Постижения при 100, 500 и 1000 факта; броят е в профила.
  • Колко знаят: след 30 отговора на въпрос разкриването показва „62% знаят“ (на въпрос с число — „бяха близо“), а прегледът след мача — колко са избрали всеки отговор. Изборите се броят в question_stats по реда на отговорите в банката и се показват в реда на мача; въпрос, променен оттогава, не показва нищо. Във въпросите на деня, щом са играли поне 20: „По-добър от 73% днес“ и стълбчета с броя верни на всички.
  • Майсторство: за всяка категория степен Познавач, Експерт или Майстор според

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#quiz · #games · #multiplayer