Solitaire by SiggySoft: fair play
The promise the app keeps, and the exact method every deal is made with, so you can check it yourself.
The promise
- Every shuffle is random and every deal can come up. Each deal starts from 512 random bits, enough for every possible order of one or two decks.
- The game never looks at you to pick a deal. A deal is a function of its code alone: not your statistics, your streak, your play history or the time of day.
- Every deal has a code that you can play again or share. Anyone with the code gets the same cards.
- "Winnable" means proven. A deal is called winnable only when the solver has won it with every card known. With face-down cards you can still lose it: with every card known, 81.942% of Klondike draw-3 deals can be won, while the best published strategy for normal play wins 36.97% (Dang, Gent, Nightingale, Ulrich-Oltean and Waller, CP 2025; Bjarnason, Fern and Tadepalli, ICAPS 2009).
- Statistics count every game. A lost deal the solver proves no play could win is shown as unwinnable, not hidden. The solver says "unwinnable" only after trying every reachable position.
- No ads, no purchases, no coins, no lives, nothing locked, no account.
- Nothing leaves the phone in the background. The app uses the internet only when you send feedback, and shows every word first.
Check a deal yourself
Download deal.py (Python 3, standard library only) and run, for example, python deal.py klondike 4711. It prints the order of the shuffled deck for that numbered deal; the app deals exactly those cards. python deal.py --code <deal code> does the same for a code copied from the app.
Version 1, 2026-10-26. This is the method every SiggySoft Solitaire deal is made with. It is published so that anyone can recompute a deal from its code and confirm the app plays the cards the code says. tools/deal.py (Python, standard library only) implements it; the test values at the end come from the app's own engine and were matched by that script.
1. The seed
Every deal starts from a seed of 64 bytes (512 bits).
- Random deal: 64 bytes from the phone's secure random source (Android
SecureRandom). - Numbered deal (1 to 1,000,000):
SHA-512("SiggySoft Solitaire numbered deal v1" || 0x00 || dealKey || 0x00 || number), wheredealKeyis the game's id in UTF-8 andnumberis 8 bytes, big-endian. Games whose options deal the same cards share a key: Klondike with draw 1 and draw 3 isklondikeeither way, so "Klondike 4711" means the same cards for both.
52 cards can be ordered 52! ways, about 8.07 x 10^67, which is about 2^225.6; two decks have about 2^499.5 distinct orders. A 512-bit seed therefore reaches every possible deal of every game in the app. A 64-bit seed, by comparison, reaches about 1.8 x 10^19 deals, a fraction of about 2.3 x 10^-49 of the one-deck orders.
2. The random stream
Block i (counting from 0) is SHA-512("SiggySoft Solitaire deal stream v1" || 0x00 || seed || i), with i as 8 bytes, big-endian. The stream is block 0, then block 1, and so on, read byte by byte.
3. Whole numbers without bias
below(n) gives a number from 0 to n - 1:
- Read 4 bytes of the stream as an unsigned big-endian number x.
- If x < floor(2^32 / n) x n, the answer is x mod n.
- Otherwise discard x and go back to step 1.
Step 3 removes the small bias that "x mod n" alone would give to the low numbers.
4. The shuffle
Start from the game's fresh deck: spades ace to king, then hearts, diamonds and clubs; a second deck repeats that order. (Spider with one suit is eight runs of spades; with two suits, spades and hearts in turn.) Number the positions 0 to size - 1, then apply Fisher-Yates from the end:
for i = size-1 down to 1:
j = below(i + 1)
swap positions i and j
The result is the order the cards are dealt in: position 0 first.
5. Laying the cards out
Each game lays the shuffled cards out in a fixed way that never depends on the player. The tableau is dealt across the piles a row at a time, as at a table: Klondike puts the first card on pile 1, the second on pile 2, and so on to pile 7, then starts the next row from pile 2. Cards left over form the stock, the first of them at the bottom. Games that need randomness after the deal (La Belle Lucie's and Gaps' redeals) shuffle the gathered cards with a stream made the same way from the same seed, with the domain SiggySoft Solitaire redeal <n> v1.
Nothing about the player goes into a deal: not statistics, streaks, history or the time of day. A deal is a function of its seed alone.
6. Deal codes
A code names the game, its options, the rules version and the seed:
SOL1-klondike-draw=3;passes=unlimited;scoring=standard-N4711 numbered deal 4711 SOL1-klondike-draw=1;passes=unlimited;scoring=standard-S<103 base32 characters> random deal, the whole seed SOL1-freecell-cells=4-C11982 classic FreeCell numbering
The rules version (the 1 after SOL) changes whenever a rule change would alter a deal or a legal move, so an old code always means what it meant.
7. Classic FreeCell numbering
FreeCell also offers the numbering most FreeCell programs share, written from the published description of its generator: the state starts at the deal number; each step sets state = (state x 214013 + 2531011) mod 2^31 and gives state / 2^16 (whole part). Start from the cards AC AD AH AS 2C ... KS; until none are left, take the card at index (next number mod cards left), swap it with the last card, remove it and deal it. Deal 1 begins JD 2D 9H JC 5D 7H 7C 5H. Deal 11982 is the one deal of the first 32,000 that cannot be won.
8. Test values
From the app's engine (gradle :engine:runTool -Pargs="vectors"), matched by tools/deal.py:
Klondike deal 1
seed 63101e8dcee7da1a831e6d7f1732cddf183fb22f6aed7dcfca95cccda9072d1b39ca8b57d089e319359d83b77df8f2955bf4811331d78ae9e9714ec984cc4efc first 8 values of below(52): 51 7 41 3 5 46 47 35 order 7D 10S KH QS 8S KD JC AH 10D 3C 3S 4S 10H 5S 7S JD 2D QD 5C AS 9H JS 8H 6H JH 2C 10C KS 9D AD 3D QC 4C 4H QH 6S 6D 3H 5D 7C 8D 8C 6C 2H 7H AC 5H 2S 4D 9C 9S KC
Klondike deal 4711
seed dc4e02e1719dcfa0783c164b04ff65d80ce6a12d6fb3d21aec825360ff9859aec42e5ac282b0ec79f71a5d66c88f856906ef01e3b63dc5ec2dd094a8050b00e2 order JS 6H KH 5S 10H 5D 10C 8S JC 4H QS 7D 4S 3S 2C KS 6S AC 8C QC 6C 9S 7S JH 8D 2S 10D 8H 2D 4D 7C 5C AS 4C 2H KD QH 3H 9D 9C 7H 10S QD AD 6D 5H KC 9H AH JD 3C 3D
FreeCell deal 1 (SiggySoft numbering)
seed 0ffe30f6c8b093e74cff461b0cce2ad31cd0da0f1004e76d9f64ea8415937e8e6c01ba536925a5221b1c41f1c06a43b42bf2e76341445aa73c108a478be17f2f order 9C QD 6H 3H 7S AH 2C 7H 5S 6S KC 6C 10S 5D AC 6D AS 5C JC 10H 8D 3C 10C AD KH JS 8S 2H 4S 9H QS 9D JD 7C 2D 9S JH KD 4C 4H 10D 2S 3D 7D 3S KS 8H QH 5H 4D QC 8C
Classic FreeCell deal 1
JD 2D 9H JC 5D 7H 7C 5H KD KC 9S 5S AD QC KH 3H 2S KS 9D QD JS AS AH 3C 4C 5C TS QH 4H AC 4D 7S 3S TD 4S TH 8H 2C JH 7D 6D 8S 8D QS 6C 3D 8C TC 6S 9C 2H 6H
SHA-512 itself is checked against the FIPS 180-4 example: SHA-512("abc") = ddaf35a1...a54ca49f.
