A poker engine written from first principles
I started this on a train, when four of us wanted to play poker and there was no signal for most of the route. Tokiyo Cards is a free card-game app built in UIKit with no game engine: a hand evaluator that scores all 21 seven-to-five combinations into one comparable integer, four AI opponents with their own bluffing models, and host-authoritative play between nearby phones over MultipeerConnectivity.
Overview
Tokiyo Cards is a free, entertainment-only card-game app: Texas Hold’em against AI or against people sitting next to you, plus a Jackaroo board game. There is no engine underneath it. Dealing, chip motion and table state are all Core Animation and hand-written game logic, with the shared virtual chip balance persisted in Core Data.
It also deliberately demonstrates both ways of building UIKit. The poker table is 100% programmatic, with every view, constraint and animation in code and no storyboard at all. The surrounding screens are laid out with Storyboards and XIBs instead, including @IBDesignable custom views that render live in Interface Builder. Same framework, two authoring styles, one app.
Tech stack
- Swift
- UIKit
- MultipeerConnectivity
- Core Data
- Keychain
- AVFoundation
- CAShapeLayer
- CAKeyframeAnimation
Inside the app
The poker suite, from the lobby through to the table.
The entry point, with the virtual chip balance up top. Solo against AI, or a nearby table with friends. The multiplayer lobby. The poker table: 100% programmatic UIKit, no storyboard. Creating a table: blinds, buy-in and seat count. Joining an open table.
The product
It started on a train with no signal
Four of us wanted to play poker on a long train journey and every app we found needed an internet connection. Most of that route had none. The ones that worked offline only let you play against bots, which is not really the point when your friends are sitting right there.
So the requirement was fixed before a line of code got written: four people, four phones, no wifi, no mobile data, no router. That constraint is why the whole thing runs peer to peer and why there is no account system anywhere in it. The plan from here is more offline card games in the same app, since the hard part, getting phones talking to each other with nothing in between, is already done.
Why poker was worth writing by hand
Poker is one of the few card games where the rules are the easy part and the interesting problem is incomplete information. Every hand has a provably correct answer at showdown and a genuinely hard question before it, which makes it a good thing to build from scratch. The evaluator has to be exactly right, and the opponents have to be wrong in interesting ways.
Nothing here is gambling, and that is enforced rather than claimed
Chips are virtual and have no cash value. There is no way to buy, sell, withdraw, redeem or transfer them, and no way to turn them into anything outside the app. There is no StoreKit integration, no in-app purchase, no consumable, no subscription and no payment processing anywhere in the binary. Every player gets a one-time grant of 1,000 chips on first launch and that is the only way chips ever enter the app.
There are also no accounts, no leaderboards and no server, so the app never asks who you are and there is nothing to compete for beyond the hand in front of you. On the App Store it sits under Games and Card, and the age rating questionnaire answers Simulated Gambling: None, which is the same classification Solitaire, Hearts and Bridge sit under.
What App Review taught me
An earlier build got rejected. It had shipped with a spin-to-win reward mechanic and a themed wrapper around the card games, and I had answered the age-rating questionnaire accordingly, which put it in a category an individual developer account is not allowed to publish under. The rejection was correct.
Fixing it properly meant changing the app rather than the wording. The reward mechanic became the flat one-time chip grant, the module behind it came out of shipping builds at compile time behind a build flag instead of just being hidden, its entry point was removed, the app was renamed, and the store category and rating answers were redone to match what the binary actually contains. What stuck with me is that on a platform with a review gate, product framing is an engineering constraint, and “disabled in the UI” is a different claim from “not in the build”.
What I built
A hand evaluator that reduces to one integer
In Hold’em a player has seven cards, two in hand and five on the board, and their real hand is the best five of those seven. There are exactly 21 such combinations, so the evaluator generates all 21 by recursive descent, scores each one, and keeps the maximum.
The part worth getting right is the scoring. Rather than returning a category plus a tiebreaker list to sort against, every hand collapses to a single Int that encodes both: a base value per category, then ranks folded in at descending place values, with each kicker position weighted geometrically so a higher card in an earlier position always dominates. Two distinct hands can never collide, so showdown is just max(). The wheel straight, where the ace plays low, is the one case that has to be special-cased on the way in.
Four opponents that are wrong in different ways
A table of bots that all bet on pot odds is one bot copied four times. Each personality runs the same three inputs, hand strength from 0 to 1, pot odds and table position, through its own decision table. The tight player folds anything marginal and raises only premium holdings. The loose one calls far too much and folds only when a bet threatens its stack. The balanced one mixes in occasional position-based bluffs, and the aggressive one bluffs roughly a third of the time with nothing at all.
Raise sizing is a function of each personality’s aggression as a fraction of the pot, jittered so bets aren’t pattern-readable, and decisions are delayed about a second and a half so the table reads as players thinking rather than a loop resolving.
Host-authoritative multiplayer with no server
“Play with friends” runs over MultipeerConnectivity: one device advertises a table, nearby iPhones discover and join it, and AI fills whatever seats are left. There is no internet connection and no router involved, and no backend I operate, which also means there is no lobby service to keep alive.
The host is authoritative over game state, which is what makes hole cards tractable: each peer receives only its own, rather than a full snapshot it’s trusted not to read. Disconnects hand the seat to AI so a hand in progress still resolves, and the message format was kept platform-neutral so the same protocol could carry a non-iOS client later.





