Mobile simulation game · React Native/Expo
Império do Café

Problem
Showing mobile and complex-logic mastery takes more than a CRUD. It calls for a product with real depth.
Solution
Full mobile coffee-farm management game (Zona da Mata), 100% offline with local saves: varieties, pests and diseases, agronomic cycle and market, across dozens of interlocking systems.
How it was built
The repo's name (rpg-mobile) is a leftover from the early draft. The finished product isn't an RPG, it's a farm-management sim in the "Brasfoot for coffee" mold: menu-and-screen navigation, no 3D scene or sprites. Built with React Native 0.81 and Expo SDK 54, tested straight on-device through Expo Go via QR code (--lan), and running 100% offline: no network calls, local save in AsyncStorage. The game simulates a coffee grower in Minas Gerais's Zona da Mata, from a first plot to a certified farming empire, respecting the crop's real time scale. A new planting takes roughly 3 years to mature and produce.
The architecture follows one rule spelled out in the README: no component holds simulation logic, and no module in logic/ imports React. src/data/ holds pure catalogs (varieties, pests, market, glossary), src/logic/ concentrates roughly 20 pure, UI-runtime-free, testable function modules (agronomic cycle, harvest, post-harvest processing, market, financing), and only src/hooks/useJogo.jsx bridges into React via useReducer and Context, with no Redux and no Zustand. The central reducer processes dozens of actions and orchestrates interlocking systems: 11 coffee varieties with their own traits, 6 seasonal pests, multi-dimensional lot classification (Brazil Type x Screen Size x SCA cupping score), a market index that fluctuates with macro events, stacking certifications, and subsidized financing (Funcafé).
Two engineering details solve real problems for an offline game. First, all randomness (daily weather, pest spawns, extreme events) runs through a custom seedable generator (mulberry32, in src/logic/rng.js) instead of Math.random(): the save only stores the RNG's integer state, not an event log, so reloading reproduces the exact same "fate" going forward. Second, time advancement is hybrid. The default step is 7 days, but it automatically drops to 1-day steps during sensitive phases like coffee drying, where humidity must be tracked day by day (from 60% down to 12%) and skipping a week would break the simulation.
Persistence evolved from a single save into 3 slots, each with its own AsyncStorage key, with automatic, silent migration from the legacy single-save format into slot 0 and a version field (versao: 1) in the serialized JSON. An incompatible save version is discarded with a warning log instead of crashing the app. The project is deliberately plain JS, with no TypeScript, no chart library and no external UI kit, using only React Native's Pressable, View, Text and StyleSheet, to keep the entry barrier low. It's an explicit choice, documented in the README itself to ease future contributions.
Pure logic, zero React
~20 simulation modules in logic/ import no React, so they're independently testable, with no UI involved.
Seedable RNG (mulberry32)
Weather, pests and events run on a custom generator: the save stores only the RNG state and reproduces the same fate on reload.
Hybrid week/day time step
Default 1-week step auto-drops to 1 day during coffee drying, where humidity tracking is critical.
Versioned multi-slot save
3 AsyncStorage slots with automatic migration from the legacy format and safe discard of incompatible saves.
Stack
- React Native
- Expo
- React 19
- JavaScript
- AsyncStorage