- Dart 88.6%
- Python 8%
- C++ 1.5%
- CMake 1.2%
- Shell 0.2%
- Other 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitea/workflows | ||
| android | ||
| assets | ||
| deploy | ||
| ios | ||
| lib | ||
| linux | ||
| macos | ||
| test | ||
| tools | ||
| web | ||
| windows | ||
| .dockerignore | ||
| .gitignore | ||
| .metadata | ||
| analysis_options.yaml | ||
| INSTALL.md | ||
| l10n.yaml | ||
| pubspec.lock | ||
| pubspec.yaml | ||
| README.md | ||
| WEB_HOSTING.md | ||
Universal Gaming
Eleven games in one focused, offline-first Flutter app for iOS, Android, macOS, Windows, Linux, and the web. Sudoku and Killer Sudoku puzzles are generated on-device and verified to have exactly one solution, so the supply is effectively endless. The card, Mahjong and Minesweeper games, plus computer and local chess, run entirely locally too; online chess is optional.
Games
| Game | What it is |
|---|---|
| Sudoku | Five difficulties, infinite uniquely solvable puzzles, notes, hints, undo/redo, statistics |
| Killer Sudoku | Dashed cages with sum targets, five difficulties, notes, hints, undo/redo and separate records |
| Mahjong | 144-tile layered board, guaranteed-solvable deals, hints |
| Klondike | Classic draw-three solitaire with foundations, undo and auto-move |
| Classic Solitaire | Draw-one Klondike for a more relaxed game |
| Spider | One-, two- or four-suit Spider with eight runs to complete |
| FreeCell | Eight columns, four free cells, multi-card moves and undo |
| Pyramid | Pair exposed cards that add up to 13; kings clear alone |
| TriPeaks | Clear three peaks by chaining cards one rank above or below until no move remains |
| Chess | Play the computer by default, two players locally, or challenge another account online by username — full rules, move history and undo |
| Minesweeper | Beginner, Intermediate and Expert boards with flood reveal, flags and chording |
Languages
The interface ships in English, German, Spanish, French and Italian. On first start it follows the system language; if that is not one of the five it falls back to English. Pick a language at any time under Settings → Language ("System language" follows the device again).
Translations live in lib/l10n/app_*.arb. They are generated from one table in
tools/build_l10n.py so no language can drift or lose a key:
python3 tools/build_l10n.py # rewrites lib/l10n/app_{en,de,es,fr,it}.arb
flutter gen-l10n # regenerates the AppLocalizations classes
Widgets read strings through context.l10n (lib/l10n/l10n.dart), which falls
back to English when no delegate is installed, so plain widget tests keep
working.
Download & install
Get the latest build from the Releases page.
- Android 7.0+ — download
UniversalGaming-2.12.2-android.apk, allow installation from unknown sources, and open it. The APK is a universal, release-signed build. - Android / Google Play — upload the
.aabwith Play Console. - iOS 15+ — download the unsigned
.ipaand sign it with Sideloadly/AltStore, or build from source with Xcode. - macOS 12+ — download the universal DMG, drag Universal Gaming into Applications, and use Open Anyway in Privacy & Security on first launch.
- Web — play the hosted build at https://games.emilyh.de, or download
UniversalGaming-2.12.2-web.zipand serve the static files yourself. See WEB_HOSTING.md. - Windows / Linux — build from source with the native toolchain. See INSTALL.md.
See INSTALL.md for step-by-step Android, iOS, macOS, Windows, Linux, web, ADB, Xcode, Gatekeeper, checksum and release-signing instructions.
Features
- Eleven complete games — puzzle, solitaire, Mahjong, Minesweeper and chess in one app.
- Endless Sudoku and Killer Sudoku — randomized grid generation plus clue removal with a uniqueness check. Killer puzzles add dashed sum cages and enforce cage uniqueness. No puzzle library and no network required.
- Solitaire depth — Klondike, draw-one Classic Solitaire, Spider (1/2/4 suits), FreeCell, Pyramid and TriPeaks, each with undo and win detection. TriPeaks supports full ascending and descending card series.
- Solvable Mahjong — the layout is filled in a guaranteed solution order, so every deal can be cleared.
- Chess three ways — computer opponent by default, local two-player mode,
or online matches against another account by username. Full rules
(castling, en passant, promotion, checkmate, stalemate and draws), move
history in the header, board flip and undo. Online moves are validated on the
server with
chess.js; the client is never trusted for turn order or legality. - Minesweeper — three classic difficulties, first-click safety, flood reveal, flags, chording and a live timer.
- Keyboard controls — both Sudoku variants support digits, arrows, notes, hint, pause, undo and redo on desktop and web.
- Competitive leaderboard — every solo game records its best time and moves. The default view is the global top five from the account server; viewing is public, submitting needs a free account. If the server is unreachable, the app automatically falls back to the on-device board. A Local switch keeps all results on this device.
- Built-in game explanations — every game has a How to play sheet covering the goal, rules, controls and tips, available from the hub and in-game.
- Resume and statistics — Sudoku and Killer Sudoku save the current game, notes, elapsed time and per-difficulty records locally.
- Light and dark themes — follows the system by default, with manual overrides in Settings.
- Discord Rich Presence (desktop) — optionally show the game you are playing, your move count or progress and elapsed play time on your Discord profile. Off by default unless the build includes a Discord application ID; the switch is in Settings. macOS builds disable the App Sandbox so the app can reach Discord's local IPC socket.
- Responsive layout — phone portrait, phone landscape, tablet, desktop and browser windows. Game boards scale and centre on large screens.
- Update checks and changelog — the app checks the public release feed on startup. The upper-right changelog button shows release notes and a direct download/release-page link.
- Optional accounts — create a username/password and optional e-mail on a self-hosted backend. Sign in with either username or e-mail. Accounts have stable UUIDs, can be renamed, and are never required to play. E-mail confirmation is available when the server has SMTP configured, but it never blocks play. The verification status refreshes automatically when Account settings opens or the app resumes. Accounts with a confirmed e-mail can reset a forgotten password from the sign-in screen; resetting signs out every existing session. Test connection in Account settings checks the server first.
- Save backups — signed-in players can store their saved Sudoku and Killer Sudoku games on the account server and restore them on another device. Saves are written locally first and are uploaded manually from Account → Save backups, or automatically a few seconds after a game is saved. The feature is opt-in per account and needs a backend with save backup support; the server stores opaque JSON per game and never reads gameplay data.
- Live sync — every unfinished game (Mahjong, Minesweeper, all six solitaires, Chess and each Sudoku difficulty slot) follows a signed-in player between devices within a few seconds, with no button to press. A game that is open on one device is never replaced under the player's fingers: the other device shows Updated on another device with a Reload action. Switch it off in Account → Live sync.
- Password-manager friendly — the sign-in fields expose standard username,
e-mail and password autofill hints and trigger the platform save prompt. The
web build also uses the Credential Management API, with a Fill password
action and automatic browser lookup on the first field tap. Android uses
Digital Asset Links plus App Links; iOS carries the
applinksandwebcredentialsassociated-domain entitlements. Android openshttps://games.emilyh.delinks directly. iOS Universal Links and web credentials need the Apple Developer Team ID at build time and a signed app. - Web footer — links to the source repository and warns when the hosted build is older than the latest release.
- iPad ready — universal iPhone/iPad target with all four orientations and layout tests for iPad portrait and landscape.
- Offline and private — all games work offline. Network access is used only
for update checks and, if you opt in, the account server you configure. No
ads or analytics. Competitive leaderboard entries (username, game, mode,
time, moves and optional detail) are uploaded only when you are signed in and
the competitive board is active; Local mode never uploads. Saved games are
uploaded only when you use Back up now in Account settings. Local state
uses
shared_preferences. - Self-hosting ready — prebuilt web and backend container images are
published to the Gitea registry, with Compose and CI workflows in both
repositories. A read-only
/managementpanel gives operators account, leaderboard and chess statistics behind a password. - Security-hardened — external links are restricted to HTTP(S), unencrypted account servers show a warning, and the bundled Nginx/Caddy configs add standard security headers. Built with Flutter 3.47.9 / Dart 3.13.7.
Getting started
Requirements: Flutter 3.47+ (Dart 3.13+), Xcode 27+ for iOS and macOS builds, JDK 17 and Android SDK 36 for Android builds, Visual Studio 2022 for Windows, and GTK 3 development packages for Linux.
flutter pub get
flutter run
Build
# Android APK (universal, installable)
flutter build apk --release
# Android app bundle for Play Store
flutter build appbundle --release
# iOS IPA (requires macOS + Xcode + an Apple signing identity)
flutter build ipa --release
# macOS app (ad-hoc signed without a Developer ID)
flutter build macos --release
# Windows (run on Windows)
flutter build windows --release
# Linux (run on Linux)
flutter build linux --release
tools/package_appimage.sh # single-file AppImage in dist/
# Web static build
flutter build web --release
# Optional: bake in a self-hosted account backend URL
flutter build apk --release \
--dart-define=UNIVERSAL_GAMING_API_URL=https://accounts.example.com
# Optional: enable Discord Rich Presence on desktop builds
flutter build macos --release \
--dart-define=DISCORD_CLIENT_ID=123456789012345678
Discord Rich Presence
Rich Presence is desktop-only (macOS, Windows and Linux). Create an application in the Discord Developer Portal, name it Universal Gaming, and copy its Application ID. Build the desktop targets with:
flutter build macos --release \
--dart-define=DISCORD_CLIENT_ID=your-application-id
The same --dart-define works for flutter run -d macos, Windows and Linux.
Discord uses the application name as the activity title; the app fills in the
current game, difficulty or mode, move count/progress and a live elapsed-time
counter. Users can turn it off under Settings → Integrations → Discord Rich
Presence. If Discord is not running, the app simply retries later and never
blocks gameplay. Web, Android and iOS builds compile without the desktop IPC
dependency.
macOS: the sandbox is disabled in the release and debug entitlements so the app can connect to Discord's Unix-domain IPC socket. This build is not intended for the Mac App Store.
Android release signing is read from android/key.properties (ignored by Git).
If that file is absent, the project falls back to debug signing so local
release builds still work. See INSTALL.md for details.
The account backend is optional. Deploy it from
Emily/UniversalGamingApp-Backend
with Docker Compose, then either build with
--dart-define=UNIVERSAL_GAMING_API_URL=... or set the server URL at runtime
from Account → Account server, then use Test connection to verify the
URL before signing in. See the backend README for environment variables and
API examples, including SMTP TLS mode and the password-reset endpoints.
Competitive leaderboard
The leaderboard opens in Competitive mode by default and shows the global top five per game and mode from the configured account server. Viewing is public; submitting requires a free account and happens automatically when you finish a solo game while signed in. If the server is unreachable, disabled, or you are offline, the screen falls back to the device-local results and shows a notice. Switch to Local at any time to keep all results on this device. Chess is local two-player and is not ranked.
Local results are always saved first, so a network failure never loses a score.
The backend stores only the username, game, mode, time, moves and optional
detail line. See the backend README for the leaderboard API and
LEADERBOARD_ENABLED.
Save backups
Saved Sudoku and Killer Sudoku games can be backed up to the account server and restored on another device. Open Account → Save backups while signed in:
- Back up now uploads every saved game for the account.
- Restore downloads the account's saves and replaces the saved games on the device after a confirmation dialog.
- New saves are uploaded automatically a few seconds after a game is saved, as long as the account server is reachable.
Backups are optional and best-effort. Saves are written locally first, so a
network failure never loses progress. The server stores the save document as
opaque JSON per game (max 32 kB each) and the app ignores unknown game ids, so
older builds can restore from a newer backup. Servers without the feature
report This server does not support save backups and the app keeps using
local saves. On the backend the endpoints are GET /api/v1/saves,
PUT /api/v1/saves, PUT /api/v1/saves/:gameId and
DELETE /api/v1/saves/:gameId, gated by SAVE_BACKUP_ENABLED.
Live sync
Unfinished games are kept in sync between the devices of one account. Open Account → Live sync to see the state, press Sync now, or switch it off.
- A game is uploaded about a second after it changes, and every device polls a cheap cursor endpoint every five seconds, so a move on the phone shows up on the desktop almost immediately.
- A game that is open on this device is never replaced from the server. The screen shows Updated on another device with a Reload action instead, and the player's own progress wins the conflict.
- A game that is not open is adopted silently, so the continue badge on the home screen is already up to date when the player gets there.
- A finished game is reported as finished and cleared on the other devices.
- Devices are identified by a random installation id, so a device ignores the echo of its own writes and only reacts to other devices.
- Nothing is stored in the clear beyond what a save backup already contains: the server keeps one opaque JSON document per game (max 32 kB), the account and its own revision counter.
The backend endpoints are GET /api/v1/sessions?since=<revision>,
GET|PUT|DELETE /api/v1/sessions/:gameId, gated by SESSION_SYNC_ENABLED.
A PUT with a stale revision from another device answers 409 with the stored
session, which the app uses to decide who wins.
Test and analyze
flutter analyze
flutter test
The suite covers Sudoku generation and solving, every new game engine, undo behaviour, win/loss detection, keyboard input, responsive layouts, and the competitive/local leaderboard fallback and upload paths, the chess board grid, the Minesweeper flag-mode control and save backup/restore.
Controls
Sudoku keyboard shortcuts
| Key | Action |
|---|---|
1–9 / numpad |
Enter a digit |
| Arrow keys | Move selection |
Backspace / Delete |
Erase |
N |
Toggle notes |
H |
Hint |
P |
Pause / resume |
Ctrl+Z / Cmd+Z |
Undo |
Ctrl+Shift+Z / Cmd+Shift+Z |
Redo |
Ctrl+Y |
Redo (Windows/Linux) |
Keyboard shortcuts work on macOS, Windows, Linux, and web, and are disabled while a dialog or the settings sheet is open.
Other games
- Cards — tap a card to select it, then tap a destination. Double-tap a face-up card to send it to a foundation/free cell where supported. Use the Undo button to step back.
- Mahjong — tap two matching free tiles. The Hint button highlights a playable pair.
- Chess — tap a piece to see its legal moves, then tap a destination. Promotion opens a piece picker. Use Flip to view from Black's side.
- Minesweeper — tap to reveal, long-press or right-click to flag. Tap a revealed number to chord when the correct number of neighbouring mines are flagged.
Project structure
lib/
core/ Sudoku difficulty, solver and puzzle generator
games/
cards/ cards, rules, and all six solitaire engines
chess/ full chess rules engine
killer/ killer cages, generator and cage-sum solver
mahjong/ tiles, layered layout, solvable deal builder
minesweeper/ board, mines, flood reveal and win/loss state
config/ repository links and build-time app config
models/ account, Sudoku game state, stats, settings
screens/
onboarding_screen.dart first-run offline/account choice
account_screen.dart optional sign-in, profile, rename and delete
game_hub_screen.dart game collection home screen
home_screen.dart Sudoku/Killer menu, continue card, difficulty picker
game_screen.dart Sudoku board, timer, controls, win flow
klondike_screen.dart Klondike and Classic Solitaire
freecell_screen.dart FreeCell
spider_screen.dart Spider
pyramid_screen.dart Pyramid
tripeaks_screen.dart TriPeaks
mahjong_screen.dart Mahjong
minesweeper_screen.dart Minesweeper
chess_screen.dart local two-player chess
services/ puzzle generation, persistence, accounts, updates
widgets/ shared chrome, cards, changelog, web footer, dialogs
theme/ Material 3 light/dark themes
utils/ time formatting helpers
test/ engine, gameplay, keyboard and layout tests
deploy/ Docker, Nginx and Caddy examples
WEB_HOSTING.md self-hosting guide
The optional backend lives in a separate repository: https://git.emilyh.de/Emily/UniversalGamingApp-Backend.
Release
- Latest release: https://git.emilyh.de/Emily/UniversalGamingApp/releases/latest
- Installation guide: INSTALL.md
- Version:
2.12.2+35 - Web self-hosting: WEB_HOSTING.md
License
Private project. All rights reserved unless stated otherwise.