Hidden Objects:旁白寧願你留下的密室逃脱遊戲
Hidden Objects: an escape room where the narrator would rather you stayed
Hidden Objects 把場景物件清單變成密室謎題,並以 Gemma 驅動旁白選擇下一個物件及改寫謎語。遊戲線上使用 Gemma 4 31B,亦可透過 Ollama 在本機使用 Gemma 3 4B;遊戲會檢查模型回覆,不合規時改用預先撰寫的台詞。
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
"I locked you in. The key gets you out, but you don't have it yet."
That's the first thing the narrator says. It doesn't shout, and it doesn't rush you. It would just rather you stayed.
Play it at hiddenobjects.tech, with your sound on.
What I Built
If you've used Blender, Maya, or Unity, you know the outliner: the panel that lists every object in a scene. Hidden Objects turns it into a puzzle.
The room is drawn with pieces missing. The narrator gives you eight riddles, each describing an object that isn't in the picture. Click the matching name in the outliner, and the object appears and the next riddle begins. The last name is the key, and the key opens the door.
- A wrong name costs 25 points. Three wrong names and the door stays shut.
- There's no countdown. Riddles are better when you have time to think.
- The eye icons can't cheat. An object you haven't found yet stays hidden.
For Ghost in the Machine, the ghost is the narrator: a voice living inside a scene file that has hidden your things. The objects are ordinary on purpose (a clock, a mirror, a vase, a lamp, a stool, a plant, a frame, and the key), and a pumpkin, a candle, and fairy lights dress the room for Halloween. Every line is written with younger players in mind. Miss a riddle, and you'll hear "Wrong. I'm not in a rush."
Where the open-weight Gemma model comes in
The narrator runs on Gemma, Google's family of open-weight models, so anyone can run it themselves:
-
On the live site: Gemma 4 31B (
gemma-4-31B-it) on DigitalOcean serverless inference. -
On your own machine: Gemma 3 4B (
gemma3:4b) through Ollama.
Both get the same prompts and the same checks. Gemma has two jobs.
1. Choosing what you look for next. Before each riddle, the server tells Gemma which objects are still hidden, which you've found, and how many times you've missed. Gemma replies with the next object and a mood for the room's lighting. A real reply from the live site, in under a second:
{"id": "mirror", "mood": "still", "line": "I see myself."}
The game only accepts an object that is still hidden. A late or invalid reply falls back to my written order, so the game never waits on the model.
2. Making the narrator sound different every play. I wrote every riddle and line. Before one is spoken, Gemma rewrites it with the same meaning in new words, so a second play doesn't sound like a recording. From a local run, my line:
"Mirror's up. Look once, then look away. The way out isn't on the other side."
became:
"It's just the reflection, dear. Watch it carefully, then turn your eyes away – the answer hides somewhere else."
A rewrite only plays if it's finished (two sentences at most), spoils nothing (no object the original didn't name), and passes a filter for kids. Otherwise my original line plays.
Is it an agent? Yes, a small one. An agent is a model inside a loop: it sees the state, picks an action, and is called again to see the result. Gemma does that every turn. It has no tools, no memory of its own, and no final say, though, since the game can refuse any move. It's a ghost that's only allowed to move the furniture.
Demo
hiddenobjects.tech is the full game: room, voice, and Gemma on DigitalOcean. No login is needed. Choose Enter the room, listen, and click the name you think the riddle means. Drag or use WASD to look around. With the voice off, every line still appears on screen.
Code
GitHub repository: AbisoyeOnanuga/hidden-objects (MIT)
To play locally, one PowerShell script installs Ollama and Gemma 3 4B. Then run python server.py and open http://127.0.0.1:8765/.
How I Built It
Three files for the page, one Python file for the server, no framework, and no build step.
- Page: plain HTML, CSS, and JavaScript, with the room drawn as SVG and a draggable camera that keeps every object in frame.
-
Server: Python's standard library only.
/acthandles Gemma's turns, and/speakhandles the voice. API keys never reach the browser. - Voice: ElevenLabs Flash v2.5, with each word appearing on screen as it's spoken.
- Host: a Render web service on hiddenobjects.tech.
flowchart LR
click["You select an object in the outliner"] --> act["/act"]
act --> gemma["Gemma: next object + reworded line"]
gemma --> checks{"Game checks it"}
checks -->|passes| voice["/speak: ElevenLabs"]
checks -->|fails| script["My written line"] --> voiceThe hardest part was turn-taking, not the model. A narrator that talks over itself sounds broken, so every line goes through one queue. A late reply can't interrupt a riddle. A correct early click skips straight to the next riddle. A wrong click only counts once you've heard the whole riddle, and the ending line always plays alone.
Why Does Open Innovation Matter
Because Gemma's weights are open, the narrator isn't locked to one company's servers.
- Same game, any host: Gemma 4 on DigitalOcean online, Gemma 3 on my laptop offline. One environment variable switches between them.
- Free iteration: I tuned the checks against the local model, offline and at no cost, and the hosted model got the same rules.
- It can't be taken away: if the hosted model disappears, anyone can still play against a local Gemma, and with no model at all, my written script still runs the room.
Prize Categories
- Gemma: chooses the next object, sets the mood, and rewords the narrator so each play sounds different, inside rules the game enforces.
- DigitalOcean: serves Gemma 4 for the live site through serverless inference, at a fraction of a cent per game.
- Render: hosts the game as a web service.
- ElevenLabs: gives the narrator its voice.
- .tech domain: the game lives at hiddenobjects.tech.
來源:DEV Community · dev.to


