A full-stack golf tournament platform built for our annual trip. Live scoring, social feed, casino mini-games, and a PWA that loads cache-first because we have bad service on the course. 20+ active users who actually rely on it. This project has spanned the entire lifetime of AI agentic coding.
My friends and I do an annual golf trip — 2v2 scramble format, multiple rounds, real stakes. Every year we tracked scores on paper scorecards, lost track of pairings, argued about standings, and had no record of past years. The logistics of running a tournament for 20+ people with a spreadsheet were a mess.
So my buddy (another software engineer, same alma mater, a few years older) and I started building this in 2023, right when agentic coding was just becoming a thing. We decided I'd do the frontend, he'd do the backend. He was dumbfounded when I had the whole frontend stood up within a week. I was embarrassed to say at the time for fear of judgment, but I told him — AI wrote 90% of the code. When I saw him working on the backend, he was hand typing it. I said dude, you have to download Cursor right now. I installed it with him and had it add functionality to the backend just by describing it, and I could see the look in his eyes — he was having his “oh shit” moment.
Back then we would decide on an HTTP endpoint schema and share it in chat between us, then I'd implement the frontend that called it and he'd implement the backend that served it. Fast-forward to today and we both have the frontend and backend cloned to directories where our agents can edit both at the same time. We push changes through PRs and assign each other as reviewers. How the times have changed.

The backend manages 2v2 scramble scoring with configurable match pairings. An admin creates each year's tournament, sets up pairings per round, and players submit scores hole-by-hole. The leaderboard updates live as scores come in. Special contests — closest-to-pin and longest drive — have their own tracking. There's a live dashboard that updates as matches occur for our spectators (families and friends who aren't playing), MVP voting, profile pages for each player with historical stats, and a Course Coin leaderboard for in-app currency that wins a coveted IRL prize at the end of the weekend.
A built-in social feed lets players post photos and updates during the trip. Comments, reactions, image uploads. It sounds simple but it's the feature everyone uses most — it turns the app from a scorecard into a trip hub. Opus 4.5 took the feed from basic HTTP endpoint polling to true WebSocket real-time updates. There's also a wheel of punishment that losers of golf matches have to spin — and the outcome of your spin auto-posts to the social feed to keep you accountable. The feed is rate-limited with per-user cooldowns to prevent spam during the inevitably rowdy evenings.
Because golf trips need downtime entertainment. The app includes crash (my personal favorite), multiplayer horse racing, blackjack, plinko, roulette, slots, and coin flip — all real-time multiplayer over WebSocket. Players get tournament currency and the games are legitimately competitive. Trust me, it's still a golf app.
The app is a PWA that loads cache-first because cell service on the golf course is terrible. The service worker caches the shell and static assets aggressively, so the app opens instantly even with a spotty connection. Data syncs when you have signal. It lives on everyone's home screen and feels native enough that most of the guys don't know it's a web app.
This project is a time capsule of AI coding capabilities. The initial blackjack implementation was a prompt to Claude Sonnet 3.5 (new, I think) in 2023, which after some browbeating got me a working game. Now, I am no professional gambler — but some of my friends are pretty close to a level of degeneracy that warrants the title. So naturally when they couldn't split, double down, or buy insurance, they were pissed. I tried to vibe code it with Sonnet at the time, but it was a buggy mess.
Fast-forward to December 2025. In a few prompts within Claude Code, I completely redid blackjack to be multiplayer using WebSockets. Now players can sit at a table together, see each other's bets and cards, and there's even a lifetime P&L chart in the room. It's just crazy to think that in 2023 models struggled to make a basic card game, and now we're one-shotting 1,000+ line diffs across frontend and backend — and it all works, even all the game edge cases.
With Opus 4.5, in a single day I probably shipped more consequential features than the entire app combined up to that point. This is a private app between friends with a hard auth paywall and no real money involved — if it were customer-facing I wouldn't be vibe-merging multi-thousand line diffs. But for this? It works.
┌───────────────────────┐ ┌──────────────┐
│ React PWA │ │ Admin │
│ (Vite, cache-first) │ │ Controls │
└───────────┬───────────┘ └──────┬───────┘
│ │
└──────────┬───────────┘
│ REST + WebSocket
┌─────────▼─────────┐
│ FastAPI │
│ (Python) │
│ │
│ ┌─────────────┐ │
│ │ SQLite │ │
│ │ (scores, │ │
│ │ social, │ │
│ │ casino) │ │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ WebSocket │ │
│ │ (games, │ │
│ │ live feed)│ │
│ └─────────────┘ │
└───────────────────┘The backend is a Python FastAPI server with 40+ endpoints covering tournament management, social feed CRUD, casino game logic, and admin operations. WebSocket handles real-time multiplayer games and live score updates. The React PWA is the primary client, with a service worker for cache-first loading on spotty course WiFi.
The WebSocket games — crash, blackjack, horse racing — each have their own module with isolated state management and server-authoritative game loops. The simpler games run client-side and report results back to the server.
The WebSocket games — crash, blackjack, horse racing — are fully server-authoritative. Clients send actions, the server resolves outcomes and broadcasts state. But the simpler games? Every time a user bets coins, the frontend sends a bet request with an amount. Every time they win, the frontend sends a win request with an amount. The server doesn't validate what the client says it won. None of our friends know this. I'm not going to change it — not only because generational wealth is a curl request away without them knowing it, but because it reminds me of the old times when I didn't know about auth tokens. I'm nostalgic for it.
Tournaments are scoped by year. Historical data persists across years, so we can compare performance, track rivalries, and settle debates about who's actually been the most consistent. The schema was designed for this from day one, not retrofitted.
This is a private app behind a hard auth paywall, used by 20 friends. There's no money on the line and no customers to disappoint. That context lets me merge multi-thousand line AI diffs without the review rigor I'd apply to production software. The velocity tradeoff is worth it when the blast radius is a group chat roasting you for breaking blackjack.
I'd start it as a native iOS app and get it on the App Store, specifically for push notifications. It's technically possible to get push notifications working as a PWA, but it's not easy. My buddy had a great idea to use Twilio for alerts — but then again, we can just send a message to the group chat.
I plan to run a small open-source LLM on my old gaming computer and hook it up to our chatroom. I'm currently developing an AOL-style chatroom using WebSockets that I've mostly stood up already. I wanted to figure out a way to get AI integration into the app, but I think having a bot that 20 guys in their 20s can @ in a chatroom is a great way to speedrun getting banned from the Anthropic API. So I'm going to use Ollama.