A Tux-themed Mahjong solitaire game for Linux, windows & MacOS built with Rust and SDL2.
xMahjong is a classic tile-matching solitaire game featuring Tux penguin-themed graphics. Clear all 144 tiles from the board by matching pairs of free tiles. The game uses the traditional Turtle layout with 5 stacked layers, and every generated board is guaranteed to be solvable.
Example
- Classic Turtle layout with 144 tiles across 5 layers
- Guaranteed solvable boards via reverse-deal generation
- Hint system, undo (up to 10 moves), and progressive shuffle system
- Timer and scoring system with local leaderboard (top 10)
- Keyboard shortcuts for all actions
- Audio feedback with mute support
- Resizable window (min 1920×1080, adapts to screen resolution)
- Native Linux packages (.deb, .rpm, AppImage)
- Daily new free Shuffle +1
You need SDL2 development libraries installed:
Ubuntu / Debian:
sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev pkg-configFedora:
sudo dnf install SDL2-devel SDL2_image-devel SDL2_mixer-devel SDL2_ttf-devel pkg-configArch Linux:
sudo pacman -S sdl2 sdl2_image sdl2_mixer sdl2_ttf pkg-configInstall Rust via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh# Debug build
cargo build
# Release build (optimized)
cargo build --releasemacOS (requires Homebrew SDL2 libraries):
brew install sdl2 sdl2_image sdl2_mixer sdl2_ttf pkg-config
LIBRARY_PATH="/opt/homebrew/lib" cargo build --releaseThe binary is output to target/debug/xmahjong or target/release/xmahjong.
# Run directly
cargo run
# Or run the release binary
cargo run --release# Run all tests (unit + property-based)
cargo test
# Run only unit tests
cargo test --lib
# Run a specific property test file
cargo test --test board_properties
# Run tests with output shown
cargo test -- --nocaptureThe project includes 19 property-based tests using proptest that validate correctness properties like board generation invariants, solvability, matching logic, undo/redo behavior, shuffle guarantees, and layout scaling.
| Shortcut | Action |
|---|---|
| Left click | Select tile |
| Ctrl+S | Save game |
| Ctrl+Q | Save + Quit |
| Ctrl+N | New Game |
| Ctrl+R | Resume |
| Ctrl+P | Pause |
| Ctrl+M | Toggle Mute |
| Shift+S | Shuffle |
| Shift+U | Undo |
| Shift+H | Hint |
| Escape | Pause / Resume (toggle) |
Score starts at 0 and increases with each pair matched:
- Base: +10 points per pair removed
- Streak bonus: +2 per pair (rewards continuous play)
- Penalties: −5 per hint used, −10 per shuffle used
- Time bonus (at game end): max(0, 500 − elapsed_seconds)
Top 10 scores are saved to a local leaderboard.
Shuffles rearrange the remaining tiles on the board when no moves are available (or strategically when desired). The shuffle economy is progressive:
- Starting shuffles: Each new game begins with 1 shuffle
- Level completion bonus: +1 shuffle awarded for each level completed (carried across levels)
- Daily bonus: +1 shuffle granted once per day when the game is launched
- Total potential: By completing all 50 levels, a player accumulates up to 51 shuffles (1 starting + 50 level rewards), plus any daily bonuses earned
- Wait for Shuffle: If you run out of shuffles and moves mid-run, you can choose "Wait for Shuffle" instead of starting over. This saves your game and exits. The next day when you launch the game, the daily bonus gives you a new shuffle so you can continue where you left off.
Shuffle state persists across sessions via shuffles.json in the data storage directory.
The game offers two difficulty levels that affect how shuffles behave:
- Easy (default): Shuffles are guaranteed to produce a playable board state. The game uses smart placement to ensure free tiles always have valid matching pairs after a shuffle. You will never get stuck due to a bad shuffle.
- Normal: Shuffles randomly redistribute face IDs across the remaining tiles without any solvability guarantee. You may end up with no valid moves after a shuffle and need to use another shuffle to continue.
Difficulty can be changed at any time from the pause menu (ESC → navigate to "DIFFICULTY" → press Enter or click). Mid-game you can pause, switch from EASY to NORMAL (or vice versa), resume, and keep playing. The shuffle behavior changes immediately — if you switch to NORMAL and use a shuffle on the current level, it will already be the random (non-guaranteed) shuffle. When you advance to the next level, the game uses whichever difficulty you last set.
Difficulty is saved with the game state and displayed on the leaderboard.
A packaging script is included to create distribution packages:
# Build all three formats
./package.sh all
# Or build individually
./package.sh deb
./package.sh rpm
./package.sh appimageOutput goes to target/package/.
| Format | Tool needed | Install with |
|---|---|---|
| .deb | dpkg-deb | Pre-installed on Debian/Ubuntu |
| .rpm | rpmbuild | sudo dnf install rpm-build |
| AppImage | wget | Pre-installed on most distros (downloads appimagetool automatically) |
.deb (Ubuntu/Debian):
sudo apt install ./target/package/xmahjong_0.1.0_amd64.debThis automatically installs SDL2 runtime dependencies via apt.
.rpm (Fedora/RHEL):
sudo dnf install ./target/package/xmahjong-0.1.0-1.x86_64.rpmThis automatically installs SDL2 runtime dependencies via dnf.
AppImage (any distro):
chmod +x target/package/xmahjong-0.1.0-x86_64.AppImage
./target/package/xmahjong-0.1.0-x86_64.AppImageAppImages bundle SDL2 libraries inside, so no system dependencies are needed.
macOS (.app bundle):
cp -R xMahjong-0.1.0-aarch64.app /Applications/
open /Applications/xMahjong-0.1.0-aarch64.appCopy the .app to your Applications folder and launch it. The app bundle includes all SDL2 libraries — no Homebrew or other dependencies required.
macOS (.dmg disk image):
open xMahjong-0.1.0-arm64.dmgDouble-click the .dmg to mount it, then drag xMahjong.app into the Applications folder. No additional dependencies are needed.
All game data is stored as JSON files in a platform-specific directory:
| Platform | Path |
|---|---|
| Linux | ~/.local/share/xmahjong/ |
| macOS | ~/Library/Application Support/xmahjong/ |
| Windows | %APPDATA%\xmahjong\ (e.g. C:\Users\<user>\AppData\Roaming\xmahjong\) |
| File | Description |
|---|---|
leaderboard.json |
Top 10 scores with player name, score, time, hints used, shuffles used, undos used, and date |
settings.json |
Persistent settings (muted state) |
savegame.json |
In-progress game state for resuming later (deleted after loading) |
shuffles.json |
Shuffle state: tracks last daily bonus date for the +1 daily shuffle reward |
- Pause menu (render_menu) — ✅ Has keyboard nav
- Victory (render_victory) — ✅ Has keyboard nav
- Leaderboard (render_leaderboard) — ✅ Has Enter/Escape support
- Shortcuts (render_shortcuts) — ✅ Has Enter/Escape support
- No Moves (render_no_moves) — ✅ Has Shuffle + New Game buttons
- Game Over (render_game_over) — ✅ Has Save Score + New Game buttons
- Quit Confirmation (render_quit_confirmation) — ✅ Has Yes/No buttons
- Name Entry (render_name_entry) — ✅ Text input
This project is licensed under the GNU General Public License v3.0.
