feat(economy): add /lottery - daily draw, weighted winner takes the pot

Buy tickets (200 coins each, max 100/draw); one winner is drawn daily at 21:00
Tallinn time weighted by ticket count and credited the whole pot. Coin-conserving
by design: each ticket's cost is deducted at purchase and the winner is minted
exactly the sum of all ticket spend - no shared pot record, so no cross-period
race. Ticket state lives per-user keyed by draw period (full scan only at draw
time and for the pot view).

- New lottery.py: TICKET_COST/MAX_TICKETS/DRAW_HOUR, pure period_for, and
  do_buy_ticket / get_lottery_state / do_lottery_draw. New lottery_tickets +
  lottery_period schema fields (period added to _TEXT_FIELDS).
- /lottery [kogus] command (view or buy) with full failure handling.
- Scheduled lottery_draw_daily loop in bot.py (both profiles; each draws its own
  collection), announcing to the optional LOTTERY_CHANNEL_ID (config + .env).

14 tests: period boundary, buy/accumulate/reset/caps/guards, pot state, draw
payout with coin conservation, and the more-tickets-wins-more weighting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
This commit is contained in:
Rene Arumetsa
2026-09-04 03:10:42 +03:00
parent ba044bf16f
commit d1cf6cdbd1
13 changed files with 462 additions and 3 deletions

View File

@@ -80,6 +80,7 @@ CMD: dict[str, str] = {
"bank": "Vaata oma panka - röövikindel hoius",
"deposit": "Pane münte panka (röövikindel, aga ei teeni intressi)",
"withdraw": "Võta münte pangast rahakotti",
"lottery": "Vaata TipiLOTO potti või osta pileteid (tühjaks jättes näeb infot)",
}
# ---------------------------------------------------------------------------
@@ -99,6 +100,7 @@ OPT: dict[str, str] = {
"buy_ese": "Eseme nimi (vaata /shop)",
"deposit_summa": "Kui palju panna panka? ('all' = kogu vaba raha)",
"withdraw_summa": "Kui palju pangast välja võtta? ('all' = kogu pangas)",
"lottery_kogus": "Mitu piletit osta (tühjaks jättes näeb potti)",
"consumable_ese": "Turgutus, mida osta (tühjaks jättes näeb menüüd)",
"vanity_ese": "Tiitel, mida osta või kanda (tühjaks jättes näeb poodi)",
"rps_panus": "Valikuline TipiCOINide panus ('all' = kogu saldo)",
@@ -161,6 +163,7 @@ HELP_CATEGORIES: dict[str, dict] = {
("/withdraw <amount>", "Võta münte pangast rahakotti."),
("/quests", "Vaata oma päeva- ja nädalaülesandeid ning nõua auhinnad (uueneb iga päev/nädal)."),
("/achievements", "Vaata oma saavutusi. Iga lukust lahti saanud märk annab ühekordse müntipreemia."),
("/lottery [kogus]", "Vaata TipiLOTO potti või osta pileteid. Loosimine iga päev - üks võitja saab kogu poti (rohkem pileteid = suurem võiduvõimalus)."),
("/leaderboard", "TipiBOTi edetabel - kes on kõige rikkam?"),
("/shop", "Sirvi TipiBOTi poodi"),
("/buy <item>", "Osta ese TipiBOTi poodist"),