Preparing to build Leypur
Leypur will eventually need a bright game-show interface. The first work is less glamorous: prove the rules, events, Pathways, and read models before the UI becomes the product.
Most small game projects start with the fun part.
You sketch the board. You pick colors. You animate the wheel. You make the buttons feel good. A few hours later there is something on screen, which feels like progress.
For Leypur, I want to do the opposite.
Leypur is a fresh build of a bingo-and-prize-wheel game inspired by a local game-show format. The shape is easy to explain: a player works through a bingo slip, unlocks a wheel, then decides whether to stop or risk another spin. It has probability, suspense, and a little bit of game-show theatre.
That makes the UI tempting. A colorful wheel, big prize numbers, dauber marks on a ticket, celebration states. All the visible stuff.
But the visible stuff is not the hard part.
The hard part is making the game state trustworthy.
Why the prep matters
The first decision was to treat the older prototype as reference material only.
Not a codebase to refactor. Not a library to port. Not something to translate file by file into a new stack.
The prototype is useful because it tells us the rules, the game loop, and some product intent. The new project starts clean. That keeps Leypur from inheriting architecture choices that made sense for an experiment but would get in the way of a proper rebuild.
The second decision matters more: Leypur should get its dataflows right before the UI gets polished.
That means the first milestone is not a pretty board. It is a full game running through commands, events, Pathways, and projections.
If that sounds backwards for a game, good. It is intentionally backwards.
The game as facts, not screen state
A game like this has a lot of little state changes:
- a session starts
- a slip is generated
- a number is called
- one cell is marked
- a row or full card is completed
- the wheel unlocks
- a spin happens
- the player stops or continues
- a final result is recorded
- a recommendation is calculated
It is easy to bury all of that inside React state or a local store and call it done. The problem is that the rules then become tangled with the screen.
I want Leypur to be able to answer questions like:
- What happened in this game?
- Why did this board unlock the wheel?
- Was this spin allowed?
- What recommendation did the system give at that point?
- Can we rebuild the current game state from the history of events?
Those questions need a proper data model. Not just components.
The architecture we are preparing
The plan is to use Flowcore as the event backbone and Flowcore Pathways as the orchestration and projection layer.
The browser will not write arbitrary events directly. It will send commands to an API boundary. The API validates intent, attaches metadata, and emits facts. Pathways consume those facts, enforce transitions where needed, and update read models that the UI can render.
The important bit is the direction of control.
The UI does not decide the game. The UI asks for something to happen. The dataflow decides whether it is valid. Then the UI renders the result.
That gives us a clean split:
- Commands describe intent.
- Events describe facts that happened.
- Pathways handle orchestration and projection.
- Read models give the UI something simple to render.
- Pure rule functions stay testable outside the UI and outside Flowcore.
What has to exist before UI polish
Before spending time on the final visual treatment, the project needs a command-level game that works without a custom UI.
A test client should be able to run the whole loop: create a session, generate a valid slip, call numbers, unlock the wheel, record spins, handle stop-or-continue decisions, finalize the game, and rebuild the visible state from events.
If that works, the UI has a strong foundation.
If it does not work, a beautiful UI will only hide the mess.
The rules we need to lock down
The board model matters.
Leypur uses a six-board slip. Each board follows the 90-ball format: three rows, nine columns, and a fixed number of playable cells per row. Across the full slip, the numbers cover the full range exactly once.
That creates a useful invariant: when a number is called, it should map to exactly one cell on exactly one board.
That invariant belongs in the rules package and in tests. It should not be reimplemented in a component. The same goes for:
- line completion
- full-card completion
- wheel round limits
- multiplier behavior
- stop and continue state
- special wheel outcomes
- expected value recommendations
The rules package should be boring. Pure functions, deterministic tests, no UI imports, no Flowcore SDK dependency.
Boring is good here.
The event model
The first event taxonomy is deliberately small.
The main flows are game session, bingo slip, wheel, strategy, and projection diagnostics.
Some example facts: game session created, game mode selected, bingo slip generated, number called, cell marked, line completed, full card completed, wheel round started, spin recorded, stop chosen, continue chosen, recommendation generated, projection updated, and projection failed.
Every event should carry enough metadata to debug and replay it: event ID, event type, flow type, aggregate ID, timestamp, schema version, source, correlation ID, causation or command ID, and idempotency key.
That sounds like ceremony until the first weird bug.
Then it is the difference between "I think the UI got confused" and "this command was retried, this event was accepted once, this projection lagged, and this read model rebuilt correctly."
The read models the UI will use
The UI should not consume raw events. It should consume read models shaped around screens and product questions.
The first set is current game, bingo slip, called numbers, wheel state, strategy recommendation, game history, and stats.
That lets the UI stay simple. The bingo screen asks for the bingo slip model. The wheel screen asks for the wheel model. The strategy panel asks for the strategy model.
It also means we can rebuild those models from events and compare the rebuilt state to the current state. That is the kind of boring reliability work that saves time later.
What we are not doing yet
We are not starting with a polished visual system.
The final product should absolutely feel like a game show. It should have a bold wheel, clear number tiles, strong prize moments, and a visual language that feels lively without turning into a casino app.
But that comes after the core loop works through dataflows.
The first UI can be plain. Ugly, even. It only needs to prove that user actions dispatch commands and screens render read models.
Once the dataflow is green, the fun part gets safer.
The build order
The preparation work gives us a clean order:
- Verify Flowcore resources and local development setup.
- Lock command, event, and read-model contracts.
- Build the deterministic rules package.
- Create the command API.
- Register Pathways and projection adapters.
- Prove the bingo slice.
- Prove the wheel and strategy slice.
- Build a skeletal UI over read models.
- Add the final visual polish, accessibility work, and release hardening.
That order is slower at the beginning. It should be faster later.
A lot of projects pay their complexity tax after the UI already exists. Leypur should pay it up front, while the system is still small enough to reason about.
The real milestone
The milestone I care about first is simple:
Can we play a full game without the real UI?
If yes, we have something solid. The UI becomes an expression of the system, not the system itself.
That is the preparation: understand the rules, name the facts, prove the dataflow, then make it beautiful.