PintRunner
An Android app for planning pub golf: a pub crawl with teams, challenges and a live scorecard. I designed and built the app, the backend and the hosting on my own.
Role
Solo developer: app, backend, hosting and website.
App
Android, written in Kotlin with Jetpack Compose.
Backend
Kotlin and Ktor with MariaDB, self-hosted behind Cloudflare.
Status
Installable Android build, being tested with friends.
Overview
Pub golf works well with a small group. With a large group, though, somebody usually ends up becoming the organiser: keeping the route, checking challenges, recording scores, answering “where are we going next?”, and trying to keep everyone moving.
PintRunner is designed to take that job away.
An organiser creates a route with the pubs, drinks and challenges, then shares the event with the group. Players join from their phones and PintRunner handles the structure of the round.
Players are split into teams, and each team is assigned a rival. The rival team records your scores and checks whether you completed your challenges, while you do the same for them. Because you don't score yourself, there is less opportunity to simply enter a better score.
During the round, the app tells players which pub they're at, what they need to drink, what challenge they're doing and where they go next. Location check-ins make sure teams actually reach the next pub before continuing.
The result is a pub golf event that needs much less organising before and during the night. The organiser sets it up; the players run the game themselves.
Events can also be made public. Instead of being limited to an existing group of friends, a public event can be discovered by people nearby and give them a way to join a local game and meet other players.
Screenshots
Swipe through a round, from finding an event to the leaderboard.
A round, step by step
Find or build a route
Search for pubs with Google Places, drop them on the map and give each stop a drink, an optional challenge and a water hazard if you want one. Routes can be private or public.
Create the event
Pick a date and start time, then share the join code or make the event public so people nearby can find it.
Get everyone in
Players join and can form their own teams. When the host starts the round, the server balances the teams and pairs each one with a rival.
Play the route
The app shows where your team is, what the drink is and what the challenge is. Your rivals log your scores, and you log theirs.
Move on together
The next pub only unlocks when your team is really there, using GPS check-ins from everyone's phone.
See who won
Team and individual leaderboards update as scores come in. If someone is spoiling the night, the group can start a removal poll.
Problems I had to solve
Scores nobody can shortcut
Teams are paired, and rivals record each other's scores, so you can't mark your own. The server, not the app, works out which pub a team is on, so tapping through entries can't skip anyone ahead.
Knowing when a team has arrived
Phones send a location ping every 20 seconds while a round is open. The server finds the nearest pub to each one with the haversine formula. A team moves on when at least half its players are within 150 m of the next pub.
Splitting people fairly
Team formation has rules of its own: at least two teams, pre-made teams kept together when they're roughly even, no lonely one-person teams, and leftovers folded into existing teams. It covers awkward cases like odd numbers and one huge pre-made team.
Release builds that don't crash
My first minified release build crashed on the map screen. R8 had stripped a class the Google Places SDK loads at runtime. I traced the obfuscated stack trace back through the mapping file, added a keep rule, and now test the release build on a real phone before sharing it.
Hosting it without a hosting bill
The API and database run on a Linux machine of my own, reached through a Cloudflare Tunnel with no port forwarding. Services start on boot through systemd, and the database is backed up every night. I've tested restoring a backup.
Looking after users
Sessions use Google sign-in and JWTs. Profile photos are resized and re-encoded on the phone, which also strips location metadata. There's in-app account deletion, plus a privacy policy and terms on the website.
How it's built
The app talks to a Ktor API over HTTPS. Cloudflare handles the certificate and forwards traffic through a tunnel to the server, which runs Ktor and MariaDB. Profile photos are stored in Supabase, and the map, place search and sign-in come from Google.
What's next
An iPhone version, which can reuse the same backend, and a Google Play release once the pilot rounds are done.
Want to talk about how it's built?
I'm happy to walk through the code, the architecture or the decisions behind it.