A casino-style spinning wheel game with a fish mascot, streaks, and a full upgrade shop β running on a Python/Flask backend with PostgreSQL persistence and user authentication.
π Patch Notes
Lucky Wheel is a browser-based gambling wheel built with a Python/Flask backend and a React frontend. Spin the wheel, rack up wins, collect fish clicks, and spend them in the shop on cosmetic upgrades and gameplay boosts.
All game state is stored server-side in PostgreSQL β progress persists across devices and sessions, and client-side cheating is prevented.
- Spinning wheel β WIN or LOSE, styled as a neon casino wheel with smooth CSS rotation
- Win/loss counter β persisted in PostgreSQL across sessions and devices
- Win-streak multiplier β 3+ consecutive wins or losses triggers a scaling bonus. Exponential (Γ2 per step) up to streak 15, then buffed cubic and linear growth, with a hard cap at streak 150 (~113,096 raw bonus)
- Streak panel β appears in the left sidebar only when a streak is active (fire emoji for wins, skull for losses)
- Streak persistence β streak is saved server-side (refresh-to-reset exploit patched)
- Stats popup β π button shows total spins, wins, losses, win rate, season fish bucks, fastest catch percentage, and complete Season History
- Community Pot β All players can contribute Fish Bucks to a global pot. When the target is reached, a 30-minute win rate boost activates for all players. Each fill permanently stacks +0.5% onto the boost rate (capped at 75%), so every window is stronger than the last. Between fills the game returns to 50/50. After the window expires, the pot resets with a 25%-higher target (Γ1.25). Target decays 20% every 12 hours if unfilled.
- Dice Roll β A charge-based high-risk mechanic between the wheel and shop. Roll two dice (or three with the Extra Die upgrade) to add the sum (2β18) to your current win streak. Requires a win streak of 3+. Snake eyes halves your streak; a pair of sixes doubles it. With three dice: triple 1s Γ·3, triple 6s Γ3. Charges recharge every 10 minutes (max 1β4, upgradeable in the shop).
- Register with a username (3β32 alphanumeric) and password (6+ chars)
- One account per device (enforced via a long-lived
device_idcookie; multiple users on the same IP are fine) - Strict single-session enforcement β logging in on a new device boots the previous session
- 30-day persistent login sessions (signed HTTP-only cookies)
- Brute-force protection: escalating lockouts after 5/10/20 failed attempts per username (1min/5min/1hr)
- All login and registration attempts are logged with IP, normalised username, User-Agent, and rejection reason
- A fish lives on the left side of the screen, centred vertically (desktop); accessible via the π toolbar button on mobile
- Reacts to spin results (happy on win, sad on loss, idle otherwise)
- Shows a fire aura when wins are ahead, a gloom aura when losses are ahead β aura size and intensity scale with the net gap (tight drop-shadow glow on the fish + large ambient blur halo behind it)
- Trail effects (sparkle/fire/rainbow/frost/thunder/galaxy) and the aura glow coexist independently
- The equipped fish emoji acts as your fisher β holds a rod and stands at the water's edge
Cast & Reel (Season 6) β replaces passive fish clicking with an active timing minigame:
- Click π£ CAST to drop your line. Shadow fish drift near the bobber while you wait.
- When the fish bites, a bite bar begins depleting β click to reel before it empties.
- Click too early (before the bite indicator) and it's an instant miss.
- Catch one of 13 species across Common, Uncommon, Rare, and Legendary tiers. Each awards Fish Bucks scaled by your Lure level.
- Lucky Fish (β) β a rare Legendary catch that doubles the value of your next successful reel.
- Auto-Cast β re-casts automatically; you still handle the bite window.
- Auto-Fish β fully automated; catches Common and Uncommon species (Rare unlocked by Master Auto-Fisher). Never catches Legendary fish.
- Fish Encyclopaedia (π top-left) β tracks all 13 species. Completing it unlocks Master Lure and Master Auto-Fisher.
- All timing is server-authoritative β the bite window and catch validation cannot be spoofed client-side.
- Checkbox to enable automatic spinning on a configurable delay
- While active, manual spinning is locked out to prevent stacking
- The wheel can begin spinning while the previous result banner is still fading out
- Seasons track per-user win/loss history and freeze a top-5 leaderboard snapshot at end-of-season
- Season History β users can view their final wins and finishing positions for all past seasons in the stats popup
- Season info shown in the UI; transitions announced via toast
- The active leaderboard (bottom-left) displays the top 10 players, including their current and all-time best streaks
- Season 7 is open-ended β no automatic reset. The "Season ends in" countdown shows β? and seasons are advanced manually.
- A full-viewport canvas fire effect rises behind all game UI, scaling with win streak intensity
- Mix mode (default) β embers and a cellular automaton inferno layered with additive blending
- Embers appear from streak 3; inferno ignites from streak 10; screen fills around streak 30
- Intensity lerps smoothly β wins cause the fire to grow, a loss makes it fall gradually rather than cutting out
- Suppressed automatically in Low-Spec Mode and when OS
prefers-reduced-motionis set
- Fully playable on phones and tablets (β€ 768 px breakpoint); desktop layout is completely unchanged
- Bottom toolbar β five icon buttons toggle panels: Shop πͺ, Leaderboard π, Fish+Community Pot π, Season Winners π , Stats π
- Slide-in drawers β the shop/sidebar panel slides in from the right; leaderboard, season winners, and fish panels open as overlays
- Tap-to-dismiss backdrop β tapping outside any open panel closes it
- Community Pot moved into the fish panel on mobile to avoid crowding the top bar
- Low-Spec Mode (β‘ button in the top bar) β disables infinite CSS animations, GPU-heavy drop-shadows, confetti, fish aura, and fire effect; respects OS
prefers-reduced-motion - Preference is saved per user in the database and synced across devices
- All game logic runs server-side; clients cannot submit win/loss outcomes
- Fish-click API capped at 10 clicks per request
- Per-user click budget enforced in PostgreSQL: 75 raw clicks per 5-second rolling window, enforced atomically with
FOR UPDATEacross all workers (prevents per-worker rate-limit bypass) - Rate limiter keys on user account rather than IP (prevents shared-network collisions)
The shop is always visible as a two-column panel on the right side of the screen (cosmetics on the left, functional upgrades on the right). Locked tiers are hidden until the prerequisite is owned β items unlock progressively. All purchases persist server-side. Hover over any item description to see the full tooltip.
- Wins: Used for all functional upgrades and gameplay boosts.
- Losses: Used for all cosmetic items (skins, trails, themes, backgrounds).
- Fish Bucks: Used for Lure Mastery (infinite upgrade) and The Singularity. Can be converted to Wins via the Fish Exchange.
Functional upgrades are gated behind total win milestones. Locked items appear greyed out with the required win count shown.
| Tier | Unlocks at | Example items |
|---|---|---|
| Tier 1 | Always available | Speed upgrades, Guard, Click Frenzy IβIV, Win/Bonus/Click Power |
| Tier 2 | 1,000 total wins | Regenerating Shield, Auto-Guard, Final Frenzy, Extra Dice Charge |
| Tier 3 | 5,000 total wins | Fortune Charm, Lucky Seven, Win Echo, Jackpot, Resilience, Max Dice Charge, Overcharge, Extra Die |
| Skin | Cost | Emoji |
|---|---|---|
| Tropical Fish | 25 | π |
| Pufferfish | 50 | π‘ |
| Octopus | 75 | π |
| Shark | 100 | π¦ |
| Dolphin | 150 | π¬ |
| Squid | 200 | π¦ |
| Turtle | 350 | π’ |
| Crab | 600 | π¦ |
| Lobster | 1,000 | π¦ |
| Whale | 2,000 | π³ |
| Seal | 3,500 | π¦ |
| Shrimp | 6,000 | π¦ |
| Coral | 10,000 | πͺΈ |
| Mermaid | 17,500 | π§ |
| Crocodile | 30,000 | π |
Each skin has custom idle/win/loss speech. Buy and equip to change the fish.
| Upgrade | Cost | Spin Duration |
|---|---|---|
| Speed Boost | 100 | 4.5s β 3s |
| Turbo Spin | 1,000 | 3s β 1.5s |
| Hyper Spin | 10,000 | 1.5s β 1s |
| Ultra Spin | 100,000 | 1s β 0.75s |
| Max Spin | 1,000,000 | 0.75s β 0.5s |
| Upgrade | Cost | Auto-Spin Delay |
|---|---|---|
| Quick Auto | 200 | 1500ms β 1000ms |
| Rapid Auto | 10,000 | 1000ms β 500ms |
| Instant Auto | 1,000,000 | 500ms β 0ms |
Multiplies each win's score contribution. Single item purchased repeatedly β no tier cap.
| Level range | Cost per level | Multiplier |
|---|---|---|
| Lv 1β7 | 200 / 600 / 2,000 / 6,400 / 20,000 / 64,000 / 200,000 | Γ2 β Γ128 |
| Lv 8+ | 400,000 Γ 1.18^(levelβ8) | +16 per level (Γ144, Γ160, β¦) |
The shop card shows current level and next multiplier: Lv3 Β· Γ8 β Γ16.
Multiplies streak bonus payouts β for both win streaks and loss streaks.
| Level range | Cost per level | Multiplier |
|---|---|---|
| Lv 1β6 | 300 / 900 / 2,800 / 8,500 / 26,000 / 80,000 | Γ2 β Γ70 |
| Lv 7β30 | 200,000 Γ 1.18^(levelβ7) | +8 per level (Γ78, Γ86, β¦ Γ262) |
| Lv 31+ | continues at same scaling | +5 per level (Γ267, Γ272, β¦) |
Season 7: level 6 cap reduced from Γ100 β Γ70; post-tier scaling reduced from +10/level to +8/level (levels 7β30) then +5/level (levels 31+).
| Tier | Cost | Fish Size |
|---|---|---|
| Big Fish | 50 | 20rem |
| Giant Fish | 200 | 28rem |
| Colossal | 800 | 40rem |
Visual trail effect on the fish. Trail and streak aura effects coexist independently.
| Tier | Cost | Effect |
|---|---|---|
| Sparkle Trail | 125 | β¨ Gold shimmer |
| Fire Trail | 500 | π₯ Flame glow |
| Rainbow Trail | 2,000 | π Rainbow hue |
| Frost Trail | 7,000 | βοΈ Ice crystal aura |
| Thunder Trail | 22,000 | β‘ Electric sparks |
| Galaxy Trail | 70,000 | π Cosmic swirl |
Each fish click counts as more clicks server-side. Also scales all Frenzy passive tick amounts. Single item purchased repeatedly β no tier cap.
| Level range | Cost per level | Multiplier |
|---|---|---|
| Lv 1β5 | 75 / 250 / 600 / 1,400 / 3,000 | Γ1.25 β Γ2.25 |
| Lv 6+ | 10,000 Γ 1.5^(levelβ6) | +0.25 per level (Γ2.5, Γ2.75, β¦) |
Passive income β server ticks fish clicks automatically. All Frenzy amounts are multiplied by your Click Power level.
| Tier | Cost | Base clicks per 5s |
|---|---|---|
| Frenzy I | 300 | +1 |
| Frenzy II | 3,000 | +5 |
| Frenzy III | 30,000 | +20 |
| Frenzy IV | 300,000 | +50 |
| Frenzy V | 3,000,000 | +100 |
| Final Frenzy | 30,000,000 | +500 (requires Frenzy V; toggleable; disables manual clicking while active) |
Lure Upgrades β reduce bite wait time and multiply catch value. Both manual and Auto-Fish benefit.
| Upgrade | Cost | Bite Speed | Value Multiplier |
|---|---|---|---|
| Lure I | 100 | 10% faster | 1.5Γ |
| Lure II | 500 | 20% faster | 2Γ |
| Lure III | 2,500 | 35% faster | 5Γ |
| Lure IV | 15,000 | 50% faster | 10Γ |
| β Master Lure | 500,000 | 65% faster | 20Γ + +1% per legendary |
Master Lure requires completing the Fish Encyclopaedia (all 13 species caught).
Auto-Cast (1,000 wins) β automatically re-casts the line when idle. You still handle the bite window.
Auto-Fisher β unlock and improve the Auto-Fish tickbox. Auto-Fish fires every 6s, is rate-limited server-side, and never catches Legendary fish at any level.
| Upgrade | Cost | Catch Rate | Species Pool |
|---|---|---|---|
| Auto-Fisher I | 300 | 45% | Common + Uncommon |
| Auto-Fisher II | 2,000 | 55% | Common + Uncommon |
| Auto-Fisher III | 12,000 | 65% | Common + Uncommon |
| π€ Master Auto-Fisher | 500,000 | 75% | Common + Uncommon + Rare |
Master Auto-Fisher requires completing the Fish Encyclopaedia.
Precise Angler (Tier 2, 1,000 wins) β rewards fast reflexes. Multipliers are exclusive; highest gate hit wins.
| Upgrade | Cost | Threshold | Multiplier |
|---|---|---|---|
| Precise Angler | 50,000 | β€ 50% through bar | 1.2Γ |
| Precise Angler II | 100,000 | β€ 20% through bar | 1.5Γ |
| π― Master Angler | 500,000 | β€ 15% through bar | 2Γ |
Master Angler requires completing the Fish Encyclopaedia. Precise Angler multipliers stack with Lure and Lucky Fish multipliers independently.
| Item | Cost | Behaviour |
|---|---|---|
| π‘οΈ Guard | 500 | 50% chance to block any loss. Breaks on success, survives on failure. |
| π Auto-Guard | 50,000 | Requires Guard. Toggleable. When enabled and Guard breaks, automatically re-buys Guard for 500 Wins before the next spin. Disables itself if you can't afford the 500 Wins cost. |
| π Regenerating Shield | 1,500 | Blocks any loss when charged. Recharges after 5 wins. Never breaks. |
- Guard β activates on any loss. A mini-wheel spins (50/50). If it lands on the win segment, the loss is fully blocked and the guard is consumed. If it fails, the guard survives and you take the loss as normal.
- Auto-Guard β toggle on/off via the shop like Final Frenzy. When Guard breaks, the replacement is purchased silently before your next spin for a flat 500 Wins (unaffected by Click Power).
- Regenerating Shield β blocks the next loss with 100% certainty while charged. After triggering, it recharges automatically after 5 consecutive wins.
Changes the canvas colour palette of the wheel.
| Theme | Cost | Look |
|---|---|---|
| Fire Theme | 250 | π₯ Red/orange |
| Ice Theme | 1,000 | βοΈ Blue/cyan |
| Neon Theme | 4,000 | π Purple/neon |
| Void Theme | 12,000 | π Deep void |
| Gold Theme | 40,000 | β¨ Pure gold |
| Golden Wheel | 300 | β¨ Radiant glow ring (independent of theme) |
Ocean Casino is the default background for all players in Season 6 (animated seabed scene; static fallback in Low-Spec Mode). Purchasing and equipping a different background overrides it.
| Theme | Cost | Look |
|---|---|---|
| Ocean Casino | 100 | Deep sea blue (Season 5 default β animated seabed) |
| Royal Casino | 400 | Rich purple |
| Inferno Casino | 1,600 | Blazing red |
| Forest | 5,000 | π² Lush green |
| Abyss | 15,000 | π Deep dark ocean |
| Cosmic | 50,000 | π Space nebula |
| Theme | Cost | Look |
|---|---|---|
| Season 1 | 1,000 | Classic gold & orange |
| Season 2 | 1,000 | Green & red |
| Season 3 | 1,000 | Purple & orange |
| Season 4 | 1,000 | Deep violet |
| Season 5 | 1,000 | Bioluminescent cyan & coral |
| Season 6 π | 1,000 | Night ocean β deep indigo & violet (current season default) |
| Tier | Cost | Count |
|---|---|---|
| Confetti+ | 75 | Γ2 |
| Confetti++ | 300 | Γ5 |
| Confetti MAX | 1,200 | Γ15 |
| Party Mode | 150 | Confetti on every result |
| Item | Cost | Effect | Tier |
|---|---|---|---|
| Extra Charge | 2,000 | Max dice charges: 1 β 2 | Tier 2 (1k wins) |
| Max Charge | 15,000 | Max dice charges: 2 β 3 | Tier 3 (5k wins) |
| π² Overcharge | 100,000 | Max dice charges: 3 β 4 | Tier 3 (5k wins) |
| π² Extra Die | 1,000,000 | Roll 3 dice. Triple 6s Γ3, Triple 1s Γ·3 | Tier 3 (5k wins) |
All Special Upgrades require Tier 3 (5,000 total wins) to unlock.
| Item | Cost | Effect |
|---|---|---|
| π Fortune Charm | 1,000,000 | All streak bonuses are increased by 25% |
| 7οΈβ£ Lucky Seven | 7,000,000 | Every 7th spin is guaranteed to win |
| π Win Echo | 1,000,000 | 20% β 40% chance each win is doubled (upgradeable via Echo Amplification) |
| πͺ Resilience | 10,000,000 | When on a win streak, losses reduce streak by 1 instead of resetting it (50% base chance) |
| π° Jackpot | 3,000,000 | 1% β 3% chance each win multiplies all gains by 25Γ. 5% chance for Jackpot Echo (upgradeable via Jackpot Resonance) |
| π‘οΈ Streak Armor | 500,000β2,750,000 | Infinite upgrade (10 levels). Requires Resilience. +1% to Resilience save chance per level (50% β 60% max) |
| π£ Lure Mastery | 5,000β1,500,000+ Fish Bucks | Infinite upgrade (no cap). +10% to all fish catch value per level, stacking on top of Master Lure's 20Γ |
| π° Jackpot Resonance | 5,000,000β40,000,000+ Wins | Infinite upgrade (10 levels max). Requires Jackpot. +0.2% jackpot proc rate per level (1% β 3%) |
| π Echo Amplification | 2,000,000β25,000,000+ Wins | Infinite upgrade (10 levels max). Requires Win Echo. +2% echo proc rate per level (20% β 40%) |
| β‘ Proc Streak | 3,000,000β50,000,000+ Wins | Infinite upgrade (15 levels max). Requires any proc upgrade. Multiplies proc payouts by (1 + streak Γ level Γ 0.005). Counter shown in sidebar. |
Each class costs 10,000,000 Wins. All three can be owned simultaneously; only one can be equipped at a time. Equipping a new class replaces the previous one. Toggle equip by clicking an already-equipped class.
| Class | Effect |
|---|---|
| π Earth | +25% to all fish income (manual reels and Auto-Fish) |
| π Moon | +5% added to every proc rate (Jackpot, Win Echo, Fortune Charm) |
| β Star | +20% applied to all win multiplier payouts |
Converts Fish Bucks into Wins at a diminishing rate. Available in the shop's functional tab when Fish Bucks > 0. Two buttons: 10% (10% of current balance) or ALL (entire balance).
Rate: 1.0 / (1 + total_ever_exchanged / 50,000,000) β starts at 1:1, halves at 50M lifetime exchanged, continues declining. The live rate is shown before each conversion.
| Item | Cost | Effect |
|---|---|---|
| The Singularity | 1,000,000,000 | Transcend reality. Every spin is a win. |
- Python 3.8+
- PostgreSQL 14+
- Node.js (for the one-time JSX build step)
pip install -r requirements.txt# Create DB user and database
sudo -u postgres psql -c "CREATE USER wheelapp WITH PASSWORD '<your-password>';"
sudo -u postgres psql -c "CREATE DATABASE wheeldb OWNER wheelapp;"Then apply the baseline schema and run migrations:
PGPASSWORD='<your-password>' psql -U wheelapp -d wheeldb -h localhost -f schema.sql
DATABASE_URL="postgresql://wheelapp:<your-password>@localhost/wheeldb" python migrate.pyBoth variables are required β the server will refuse to start without them.
export DATABASE_URL="postgresql://wheelapp:<your-password>@localhost/wheeldb"
export WHEEL_SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
export PORT=5000 # optional, defaults to 5000For convenience, copy .env.example to .env β python-dotenv will load it automatically.
The JSX source must be transpiled once (and again after any app.jsx changes):
npx babel static/app.jsx --presets @babel/preset-react,@babel/preset-env -o static/app.jsProduction (recommended):
gunicorn -c gunicorn.conf.py server:appDevelopment:
python server.pyOpen http://localhost:5000 in your browser. You'll be prompted to register or log in.
A separate staging environment runs on port 5001 against a wheeldb_staging database, using a git worktree on the staging branch.
/home/user/wheel-app/ β master (production, port 5000, wheeldb)
/home/user/wheel-app-staging/ β staging (port 5001, wheeldb_staging)
Start staging dev server:
cd /home/user/wheel-app-staging && PORT=5001 python server.pyPromote to production:
cd /home/user/wheel-app && ./deploy.shdeploy.sh merges staging β master, applies pending migrations, rebuilds the frontend, and reloads gunicorn.
Schema changes are managed with numbered SQL files and a lightweight migration runner.
python migrate.py # apply pending migrations
python migrate.py --status # show applied / pending migrations
python migrate.py --dry-run # preview without executingMigration files live in migrations/NNN_description.sql. Applied versions are tracked in the schema_migrations table in each database.
wheel-app/
βββ server.py # Thin entry point: create_app() β gunicorn target
βββ app.py # Flask app factory: config, extensions, blueprints, error handlers
βββ auth.py # Blueprint: /api/me, /api/register, /api/login, /api/logout
βββ game.py # Blueprint: /api/state, /api/spin, /api/buy, /api/equip,
β # /api/equip-cosmetic, /api/equip-class, /api/fish-click,
β # /api/click-frenzy, /api/fish-exchange,
β # /api/stats, /api/leaderboard, /api/health
βββ db.py # psycopg2 ThreadedConnectionPool + db_connection() context manager
βββ models.py # User class, FISH_SKINS, SHOP_ITEMS, INFINITE_UPGRADES, helper functions
βββ security.py # check_lockout(), record_attempt(), clear_attempts(), require_json()
βββ extensions.py # Flask-Limiter and Flask-Login instances
βββ migrate.py # SQL migration runner (apply / status / dry-run)
βββ deploy.sh # Production deploy: merge staging β migrate β build β reload
βββ gunicorn.conf.py # Gunicorn config: 4 gthread workers Γ 4 threads, PORT from env
βββ schema.sql # PostgreSQL baseline schema
βββ migrations/ # Numbered SQL migration files (NNN_description.sql)
βββ requirements.txt # Python dependencies
βββ .env.example # Required environment variable template
βββ static/
βββ index.html # Slim HTML shell
βββ app.jsx # React source (edit this)
βββ app.js # Compiled output (generated by Babel β do not edit directly)
βββ styles.css # All CSS
All game endpoints require authentication (session cookie). POST endpoints require Content-Type: application/json.
| Endpoint | Method | Rate Limit | Description |
|---|---|---|---|
/api/me |
GET | β | Returns {username} or {username: null} |
/api/register |
POST | 5/hr | Create account |
/api/login |
POST | 10/min | Authenticate |
/api/logout |
POST | β | Clear session |
| Endpoint | Method | Rate Limit | Description |
|---|---|---|---|
/api/health |
GET | β | DB connectivity check β {"status":"ok"} or 503 |
/api/state |
GET | β | Full game state (including community pot) |
/api/spin |
POST | 10/sec | Server determines outcome, updates DB |
/api/buy |
POST | β | Purchase shop item |
/api/equip |
POST | β | Equip a fish skin |
/api/equip-cosmetic |
POST | β | Toggle a cosmetic item on/off |
/api/community-pot/state |
GET | β | Current pot progress and target |
/api/community-pot/contribute |
POST | 5/sec | Contribute Fish Bucks to the global pot |
/api/fish-click |
POST | 5/sec | Legacy endpoint β no-op (returns current state) |
/api/click-frenzy |
POST | 1/sec | Legacy endpoint β no-op (returns current state) |
/api/cast |
POST | 5/sec | Start a fishing session β returns {bite_at, expires_at} |
/api/reel |
POST | 5/sec | Attempt a reel β server validates timing, returns catch result |
/api/auto-fish-tick |
POST | 1/5sec | One automated catch cycle (requires Auto-Fisher I+) |
/api/settings |
POST | β | Persist user preferences (e.g. low_spec_mode) |
/api/stats |
GET | β | Personal stats (including Season History and fastest catch %) |
/api/leaderboard |
GET | β | Public β top 10 players |
/api/equip-class |
POST | β | Equip or unequip a class item ({"item_id": "class_earth"}) |
/api/fish-exchange |
POST | β | Convert Fish Bucks β Wins ({"mode": "10pct"} or {"mode": "all"}) |
/api/spin response:
{
"result": "win",
"angle": 2345.6,
"wins_delta": 4,
"losses_delta": 0,
"streak": 4,
"owned_items": ["regen_shield"],
"active_cosmetics": ["auto_guard"],
"shield_charges": 0,
"regen_recharge_wins": 0,
"shield_used": false,
"shield_used_type": null,
"shield_broke": false,
"guard_triggered": false,
"guard_blocked": false,
"bonus_earned": 4,
"echo_triggered": false,
"jackpot_hit": false,
"resilience_triggered": false,
"lucky_seven_triggered": false,
"fortune_charm_triggered": false,
"auto_guard_failed": false,
"proc_streak": 3
}wins_delta and losses_delta represent the change in currency from this spin. The client adds these to its local state to avoid race conditions.
/api/leaderboard (public, no auth required):
[
{ "username": "alice", "wins": 42, "losses": 18, "streak": 5, "best_streak": 12 },
...
]Returns top 10 players by win count. Auto-refreshed client-side every 5 seconds.
The frontend is a pre-compiled React app. Edit static/app.jsx and run the Babel build step to update static/app.js. Key components:
| Component | Purpose |
|---|---|
App |
Root: checks /api/me, renders AuthPage or GameApp |
AuthPage |
Login/register form with error handling |
GameApp |
Main game: wheel, fish, shop, all API calls |
Fish |
Left-side mascot β aura, mood, trail effects |
FishingPanel |
Cast & Reel minigame β bobber, bite bar, shadow fish, Auto-Cast/Auto-Fish toggles |
FishEncyclopedia |
Modal showing all 13 catchable species (silhouettes until discovered) |
GuardWheel |
Mini canvas wheel overlay for guard activation (50/50 animation) |
StreakPanel |
Sidebar streak display (only shown at streak β₯ 2) |
ShopPanel |
Two-column shop (cosmetics left, functional right); collapsible via a pinned βΊ/βΉ toggle button |
ShopItem |
Individual item card (buy / equip / active states; full desc on hover) |
Scoreboard |
Win/loss counter below the wheel |
StatsPanel |
Modal overlay showing personal stats (π button) |
Confetti |
Win confetti overlay |
Leaderboard |
Vertical panel (bottom-left) β top 10 players with wins, best streak, and live streak glow effect; refreshes every 5s |
FireEffect |
Full-viewport canvas fire effect behind all UI β ember particles + cellular automaton inferno, scaled by win streak |
drawWheel |
Canvas rendering with theme support (default / fire / ice / neon / void / gold) |
drawGuardWheel |
Canvas rendering for the guard mini-wheel (50% green / 50% red) |
Mobile layout is handled entirely in CSS (@media (max-width: 768px)) and a small amount of React state (isMobile, mobilePanel) in GameApp. No separate mobile components β the same components are reused, conditionally positioned via CSS class toggles.
No localStorage β all state lives in PostgreSQL. Legacy localStorage keys are cleared on mount.
- Backend: Python, Flask, flask-login, flask-limiter, bcrypt
- Database: PostgreSQL (psycopg2 with
ThreadedConnectionPool) - WSGI: Gunicorn (gthread workers)
- Frontend: React 18 (CDN UMD), pre-compiled JSX via Babel CLI, vanilla CSS
- Auth: Server-side sessions via signed HTTP-only cookies (SameSite=Lax)