Inspiration

I wanted a game where the tension is in the faces around the table, not in the cards. Bluff & Barrel is a 1930s Western saloon: up to four players sit around a table, play cards face down and claim what they are. Anyone can call a bluff. Whoever loses the call faces the barrel. It's a bluffing game where every lie has a cost, built for phones so friends can play it anywhere, with voice chat so you can hear someone hesitate.

I'm a solo, non-technical developer. I had never shipped a game before, and I built this entirely in Unreal Engine 5 Blueprints, without writing C++ myself.

How I built it

  • Engine: Unreal Engine 5, Blueprint-only, Android first (Steam PC version next).
  • Online: Epic Online Services through the EOS Integration Kit: device and Google sign-in, sessions, lobbies, presence, friends, voice chat, player reports and sanctions.
  • Monetisation: RevenueCat handles the one purchase, Remove Ads, through Google Play Billing, including restore on a new device and entitlement checks at login. Ads come through Yodo1 MAS with Google's UMP consent form for EU players.
  • Workflow: I worked with AI the whole way. Claude was my architect and log reader, and an in-editor agent (NeoStack) made the Blueprint edits. Every change followed the same loop: a read-only discovery of the graph → an edit brief with a file-hash gate → the edit with a full readback → a git commit → a cook → a drill on real phones → reading the device logs. That loop ran more than 450 times.

Peer-to-peer with no servers

There are no dedicated servers. One player's phone is the listen-server host, and the others connect to it peer-to-peer through EOS P2P sockets (with relay fallback when NAT gets in the way). The host advertises its table as an EOS session with attributes (host ID, table code, state, player count and a heartbeat timestamp), and Quick Play, the saloon list and private codes all search those attributes.

That keeps running costs at zero, but it creates the hardest problem in the project: if the host's phone leaves, the table should not die.

Host migration

This took the most time, and it's the part I'm proudest of.

  1. Every phone already has the state. After each turn the host multicasts a full match snapshot: seats, hands, who's alive and whose turn it is. When a crisis comes, nothing needs to be fetched.
  2. Fast detection. The host replicates a heartbeat every second. Each client runs a watchdog: after 2.5 s of silence a "Reconnecting…" veil goes up, and the engine's 8 s connection timeout confirms the loss.
  3. Deterministic election. Every survivor runs the same election on the same snapshot (lowest surviving human seat, excluding the dead host), so all phones agree on the heir without talking to each other.
  4. Rejoin first, elect second. A phone can't tell "the host died" from "my Wi-Fi hiccupped". So before migrating, the dropped player searches once for the original host's table and checks its heartbeat stamp. If the stamp is fresh, the host is alive: the player rejoins and the host gives them back their own seat by player ID. If it's stale or gone, migration runs.
  5. The heir re-hosts. The heir opens a new listen server, restores the match from the snapshot, hands empty seats to AI, and publishes a new session. The other survivors search for the heir's player ID and reclaim their seats on arrival.
  6. Ghost defence. Dead hosts' sessions can stay listed for a while, so searches filter on the heartbeat age, blacklist hosts that failed a join, and abort a travel that resolves to a dead host.

Migration has been drilled in chains (host dies, heir dies, the next heir carries on) on a fleet of four Android phones.

Challenges

  • Bugs that only exist on real phones. Two-phone tests could never tell a link blip from a host death; a three-phone drill exposed it. Several fixes looked right in the editor and failed on devices, so I adopted a rule: nothing is "fixed" until a device log says so.
  • GPU drivers. A Pixel 6 crashed on Vulkan regardless of what I changed in the game; the fix was a device profile that moves that GPU to OpenGL ES.
  • Performance on mobile. A detailed saloon interior on Mali GPUs meant a long list of measured experiments. Most made no difference, and I recorded each one so I'd never retry it.
  • Platform rules. EOS limits accounts to eight devices, Google Play needs a closed test before production, EU players need a consent form, and a voice game needs mute, report and appeal flows. Each was its own small project.
  • Working without code. Blueprint graphs with thousands of nodes are hard to reason about. Discovery-before-edit and hash gates are what kept 450+ changes from breaking each other.

What I learned

  • Design for the failure first. In a peer-to-peer game, "the host left" is a normal event, not an edge case.
  • Logs are the truth. Every important branch prints a short breadcrumb, and those breadcrumbs found most of the bugs.
  • Small steps win. One change, one build, one drill. The one time I batched eight builds without a checkpoint, I had to revert all of them.
  • A non-programmer can ship a real multiplayer game today, if they are disciplined about verification.

What's next

Steam (PC) cross-play, more saloon tables and characters, and ranked Quick Play.

Built With

Share this project:

Updates

Submission history