This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Sprint-n-Squint is a multiplayer scavenger hunt that only works if you leave your desk. Everyone in the round gets the same thing to find ("a spiderweb", "a stray pebble or stone", "a piece of litter"), a five-minute clock starts, and you sprint outside to photograph it. An AI judge looks at your photo and decides whether you really found it. The first photo the judge believes wins the round.
The idea comes from YouTuber Dream's block shuffle challenge videos: random Minecraft blocks to hunt with a clock and a rival. I wanted that feeling, but with the phone as the camera instead of the thing that keeps you inside. I first thought about a screen-time lock, then decided a game is a better reason to go outside than a rule.
Demo
The judge is a model running locally on my own laptop, so the live app is only up when my laptop is. The live demo shows the deployed server and the frontend, interacting with the actively running judge in my laptop. The judge is made accessible remotely, by use of tunneling of port.
Code
Sprint-n-Squint
Find it. Photograph it. Convince the judge.
Sprint-n-Squint is a multiplayer scavenger hunt that only works if you leave your desk. Everyone in a round gets the same thing to find ("a spiderweb", "a stray pebble or stone", "a rusty piece of metal or pipe"), a five-minute clock starts, and you sprint outside to photograph it. A vision model running locally through Ollama judges your photo, and the first photo it believes wins the round.
It was built for the Hacktoberfest Open-Source AI Challenge, Week 1: Touch Grass. The idea comes from YouTuber Dream's block shuffle challenge videos.
Features
- Real-time multiplayer rounds over Socket.IO: one shared target, one shared countdown, first verified photo wins
- Late joiners and page refreshes catch up to the round in progress
- A local vision model judges each photo and returns a JSON verdict, including a description of what it saw…
How I Built It
The judge: A vision model running locally through Ollama. I started with llava:7b, then switched to gemma4:e4b and the verdicts got much more accurate. But, I noticed that the judge got considerably slower (it almost took 20-30 seconds to respond for a single image). So, I made a compromise and switched back to llava. Changing the temperature value (which controls the strictness of the judge) to 0.2 from 0.0, made it slightly more generous and work-able. The server sends each photo with this prompt, at temperature 0.2:
You are a strict, objective judge for a photo scavenger hunt. Your task is to determine if the target item: "${target}" is present in the image.
Follow these strict rules:
1. The target must be a real, physical, 3D object.
2. Explicitly check for cheating: If the target is displayed on a digital screen (look for bezels, pixels, or screen glare), printed on a piece of paper, a drawing, or a 2D photograph, it DOES NOT count.
3. The item can be partially obscured or close-up, as long as it is clearly identifiable as "${target}". Other objects in the background do not matter.
Before coming to a conclusion, think step-by-step.
Provide your response strictly in the following JSON format, without any markdown formatting, code blocks, or extra text:
{
"sees": "One short sentence describing exactly what is physically visible in the foreground and background of the photo.",
"reasoning": "Briefly explain if you see the target, and explicitly state whether it is a real 3D object or a 2D fake (screen/paper/drawing) based on depth and context clues.",
"found": true or false
}
Making the model describe the photo first has a nice side effect: the description doubles as the rejection message. Photograph a pile of pebbles for "a spiderweb" and you get: "Judge couldn't spot a spiderweb. It saw: a collection of pebbles with various colors and sizes." It not only fits the game, it also adds a fun mechanic to play around with.
The game: Node, Express and Socket.IO keep everyone in sync. Photos are judged one at a time through a queue so the model never gets flooded, and a round id makes sure a slow verdict from an old round can't win the next one.
The app: React and Vite. On a phone, the photo button opens the camera directly. The photo is shrunk to about 800px in the browser before it's sent, which keeps it under the socket size limit and makes the model answer faster.
Where it runs: On the same wifi, phones just open the dev server. For friends on other networks I use a Cloudflare tunnel, and I tested it from devices far from where I live. The app is also deployed on Render, with the judge reached through a tunnel to Ollama hosted locally on my laptop.
A bug worth telling: My first photo submission failed with just "fetch failed", even though Ollama was clearly running. The cause: Node on Windows resolved localhost to IPv6, and Ollama only listens on IPv4. Changing localhost to 127.0.0.1 fixed it.
AI assistance: I used AI assistants like Gemini and Claude to help write and debug parts of the code.
Testing it on my phone: I ran a test round on my phone against the deployed page with "a rusty piece of metal or pipe" as the target. I photographed a rusty, curved piece of metal, and the judge said it saw "a rusty piece of metal or pipe" and then rejected the photo anyway. It was contradicting itself, which is what sent me back to the prompt. It now spells out the rules: background objects don't matter, close-ups and partly hidden items count, and screens, printouts and drawings don't.
What it can't do yet: On a desktop, the upload button opens a file picker, so you can submit a picture from the internet and the judge will happily accept a rock that was never found outdoors. The prompt tells the judge to reject photos of screens and printouts, but that only helps if you photograph the screen, not if you upload the image file directly. On phones the camera opens directly, but I don't check photo timestamps yet, and that's the first thing I'd add. There's also one global match instead of rooms, and having the judge run locally on my laptop is why the live link isn't always up.
Why Does Open Innovation Matter?
An open model made three things possible that a closed API wouldn't have:
- Photos stay on my machine. People photograph their surroundings, sometimes their homes or faces. With a local model, no photo leaves the laptop for a cloud service.
-
I could swap the judge in one line. Moving from
llavatogemmatook a single string change, and I compared them on the same photos with no new account, key or bill. It also let me trade accuracy for speed on my own hardware:gemmawas more accurate but took 20-30 seconds per photo, whilellavawas fast enough to play. - No per-photo cost. A game where everyone fires photos at a judge would get expensive on a paid API. Locally, it only costs my laptop's time.
The trade-off is: Because the model runs on my laptop, the project isn't always on. A hosted open-weight model would fix that, and the fact that the model is open is exactly what makes that swap possible.
Top comments (0)