# FreeCell deal numbers: reproducing Microsoft's deals, the Internet FreeCell Project and #11982

> Reproduce Microsoft FreeCell deals in Python, check deals 1 and 11982 against published layouts, and see why a deal number alone does not guarantee solvability.

Source: https://sheng.page/en/posts/freecell-deal-numbers/ · Published: 2026-10-31 · Author: Sheng

Microsoft FreeCell uses an integer as a deal number. The original release offered deals 1 to 32,000; compatible implementations reproduce the same layout from a given number. Compatibility depends on the Microsoft C runtime's `rand()`, the numbering of the 52 cards, and the rules for drawing, backfilling and laying them out in columns. Changing those rules can change the deal even when the input number stays the same.

This post re-implements the deal in Python following the compatible code in Freecell Solver, and prints deal 1 and the famous deal 11982; both match Rosetta Code's published layouts card for card. The second half goes through the record of the Internet FreeCell Project and explains why "reproducible", "solvable" and "cheat-resistant" are three separate guarantees. It follows on from [no-guess Minesweeper boards](/en/posts/minesweeper-no-guess-board-generation/) and [uniformly sampled solvable 15-puzzles](/en/posts/fifteen-puzzle-uniform-solvable-board-generation/): those two cover generating and checking a board; this one is about letting someone else generate the same one.

## Four things a compatible dealer has to fix

The header of Freecell Solver's `pi_make_microsoft_freecell_board.c` credits code by Jim Horne, who wrote the original Microsoft FreeCell, and the Microsoft C Run-Time Library. The FreeCell FAQ at Solitaire Laboratory says the same thing in prose: the deals are random deals generated from integer seeds using the random number generator in the Microsoft C compiler. The implementation agrees with the rules on Rosetta Code, and they come down to four:

1. The generator is a linear congruential generator. The state update is `state = state * 214013 + 2531011`, and each call returns `(state >> 16) & 0x7fff`, a 15-bit value between 0 and 32767. The initial state is the deal number.
2. Cards are numbered `rank * 4 + suit` with suits in the order C, D, H, S, so AC is 0, AD is 1, AH is 2, AS is 3, 2C is 4 and KS is 51.
3. Each draw takes `rand % cards_left` as an index, deals that card, moves the last card of the remaining deck into the vacated slot and shortens the deck by one. Its output order differs from the Fisher-Yates implementation compared below, so the two cannot be substituted directly.
4. The 52 dealt cards go into eight columns in order: card `i` goes to column `i % 8`, so the first four columns get seven cards and the last four get six.

In Python that is under forty lines:

```python
import sys

RANKS = "A23456789TJQK"
SUITS = "CDHS"  # card = rank * 4 + suit: AC=0, AD=1, AH=2, AS=3, 2C=4 ...


def ms_rand(state):
    """Microsoft C runtime rand(): LCG; returns new state and a 15-bit output."""
    state = (state * 214013 + 2531011) & 0xFFFFFFFF
    return state, (state >> 16) & 0x7FFF


def deal(game_number):
    deck = list(range(52))
    state = game_number
    dealt = []
    while deck:
        state, r = ms_rand(state)
        j = r % len(deck)
        dealt.append(deck[j])
        deck[j] = deck[-1]  # backfill the hole with the last card, then shrink
        deck.pop()
    return dealt


def card_name(c):
    return RANKS[c >> 2] + SUITS[c & 3]


if __name__ == "__main__":
    n = int(sys.argv[1]) if len(sys.argv) > 1 else 1
    d = deal(n)
    print(f"Game #{n}")
    for row in range(0, 52, 8):  # 8 cards per row; card i goes to column i % 8
        print(" ".join(card_name(c) for c in d[row:row + 8]))
```

Deal 1:

```text
Game #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
```

Each row is one round of dealing, so reading down a column gives that column's contents: the first column, top to bottom, is JD, KD, 2S, 4C, 3S, 6D, 6S. All 52 cards match Rosetta Code's listing for deal 1, and deal 617 matches too. The same program on 11982:

```text
Game #11982
AH AS 4H AC 2D 6S TS JS
3D 3H QS QC 8S 7H AD KS
KD 6H 5S 4D 9H JH 9S 3C
JC 5D 5C 8C 9D TD KH 7C
6C 2C TH QH 6D TC 4S 7S
JD 7D 8H 9C 2H QD 4C 5H
KC 8D 2S 3S
```

This matches, card for card, the output printed by Rosetta Code's Python solution. As a further check, `pysol_cards` 0.24.0, a PyPI package by the Freecell Solver author, agrees with `deal()` above on every deal from 1 to 32,000.

## Same number, different shuffle, different game

"The deal number is the seed" is often read as "same seed, same cards". The shuffle procedure is part of the contract too. The script below compares three routines using deal number 1: Microsoft's draw-and-backfill, a textbook Fisher-Yates driven by the same LCG, and Python's own `random.Random(1).shuffle`, which uses a different generator. Only the first row is printed.

```python
import random
from ms_deal import ms_rand, deal, card_name


def fisher_yates_with_ms_rand(game_number):
    """Same LCG, same deal number, but a textbook Fisher-Yates swap."""
    deck = list(range(52))
    state = game_number
    for i in range(51, 0, -1):
        state, r = ms_rand(state)
        j = r % (i + 1)
        deck[i], deck[j] = deck[j], deck[i]
    return deck


def python_shuffle(game_number):
    deck = list(range(52))
    random.Random(game_number).shuffle(deck)
    return deck


for name, d in [("Microsoft", deal(1)),
                ("same LCG, Fisher-Yates", fisher_yates_with_ms_rand(1)),
                ("random.Random(1).shuffle", python_shuffle(1))]:
    print(f"{name:26s} first row: {' '.join(card_name(c) for c in d[:8])}")
```

```text
Microsoft                  first row: JD 2D 9H JC 5D 7H 7C 5H
same LCG, Fisher-Yates     first row: 6H 2H 9C 6S TC 8C 3D 6C
random.Random(1).shuffle   first row: KD 3D TD 6H AH TH 5S 3S
```

The second line keeps the generator but swaps with the tail and reads the deck after shuffling, and the first card changes from JD to 6H. Compatibility requires the same draw and output order as well as the same generator and card numbering.

## 32,000 and 15 bits are different numbers

Wikipedia describes the original as "32,000 numbered deals, generated by a 15-bit, pseudorandom-number seed". The deal numbers do fit in 15 bits, but the generator's state is wider. In the code above the 15 bits appear in the output mask, `& 0x7fff`. The state itself is a 32-bit multiply-and-add, and 32,000 is simply the range of deal numbers Horne chose. Those quantities describe different parts of the procedure.

The output takes bits 16 to 30 of the state, and the low bits of a product or sum never depend on the high bits, so for this LCG calculation the output is unchanged whether the state is kept in 31, 32 or 64 bits, provided the deal number fits in 32 bits. Rosetta Code's Python solution masks with `(1 << 31) - 1`, which is `& 0x7FFFFFFF`; Freecell Solver keeps the state in an unmasked `unsigned long long`, and the Python above uses `& 0xFFFFFFFF`. That is easy to check directly:

```python
def outputs(game_number, mask):
    state, out = game_number, []
    for _ in range(52):
        state = (state * 214013 + 2531011) & mask
        out.append((state >> 16) & 0x7FFF)
    return out


same = all(
    outputs(n, 0x7FFFFFFF) == outputs(n, 0xFFFFFFFF) == outputs(n, (1 << 64) - 1)
    for n in range(1, 32001)
)
print("1..32000 identical across 31/32/64-bit state:", same)
```

```text
1..32000 identical across 31/32/64-bit state: True
```

At 2^31 the compatibility rules change. Freecell Solver's `gen_ms_boards__rand.h` has special cases for deal numbers at or above 2^31 (the output is OR-ed with `0x8000`) and at or above 2^32 (a 16-bit output plus one). These branches handle the extended range. The FAQ says Ettlinger later extended FCPro's deal numbers to over 8 billion, but does not say when each branch was added. The original 32,000 never touch them. The Windows XP version raised the count to one million deals, continuing the same numbering, still well under 2^31.

## The Internet FreeCell Project and deal 11982

Michael Keller, who maintains the FreeCell FAQ at Solitaire Laboratory, took part in the project and his account is the primary record. In August 1994 Dave Ring recruited volunteers on rec.puzzles, sci.math and elsewhere, eventually more than 100 people, and gave each of them 100 consecutive deals. The project finished in April 1995 with every deal reported solved except 11982. One early report that 11982 had been solved was withdrawn when it turned out the solver had been playing Baker's Game by hand, a related game with different rules. Wikipedia's Microsoft FreeCell article records the same outcome, citing the 1999 book *Windows 98 in a Nutshell*.

Failing to find a solution by hand and proving there is none are different things; one unsolved deal out of 32,000 only meant nobody had found a line yet. Later computer searches supplied further evidence. The FAQ records that Adrian Ettlinger, using Don Woods's solver with extensions of his own and working mainly with the FCPro program he wrote with Wilson Callan, analysed 20 million deals, and reports that the Ring project's result was verified, with only deal 11982 unsolvable among the 32,000 standard deals. Keller adds that 11982 has defeated at least eight independent computer programs he knows of, that he is confident in calling it impossible, and that it becomes solvable with five free cells instead of four. The FAQ also says most of those programs search exhaustively, though it publishes no search logs; this post cites the result and has not rerun a search.

After the XP release raised the count to one million, the FAQ reports that eight of those deals are unsolvable, and separately lists the 130 unsolvable deals among the first ten million. The first eight on that list are 11982, 146692, 186216, 455889, 495505, 512118, 517776 and 781948, all below one million, and there are exactly eight of them.

## Reproducible, solvable and cheat-resistant are three guarantees

A compatible deal number reproduces the cards; it gives no guarantee that they are solvable or that a player can solve them within a time limit. Keller estimates that thousands of people have tried 11982 without success. Establishing that it is unsolvable takes an exhaustive search under the correct rules. [No-guess Minesweeper generation](/en/posts/minesweeper-no-guess-board-generation/) filters candidate boards through a solver and may have to retry, while [solvable 15-puzzle generation](/en/posts/fifteen-puzzle-uniform-solvable-board-generation/) uses a parity test. A specification needs to say how solvability is established rather than relying on the presence of a deal number.

A solver needs to distinguish a found solution, an exhaustive search that found none, and a search that timed out. A claim of unsolvability depends on the search being complete and following the correct rules; a timeout leaves the question open. The generalised game is NP-complete (Wikipedia cites Helmert, 2003), which says nothing about how long a particular deal will take. A product still needs a policy for searches that do not finish: discard the board, mark it unclassified or allow it to be played.

A leaderboard raises a separate issue. Even if the server validates every move and keeps time itself, a public deal number lets players prepare a solution in advance. A legal move sequence alone may not reveal whether it was memorised or produced with a solver. The specification needs to say which forms of cheating it aims to prevent and how those claims will be checked. A daily challenge that publishes its layout early has the same limitation, whether or not the date is used as its seed.

Games that require unpredictable outcomes need to avoid publishing information sufficient to reproduce them in advance, as discussed in [game RNG: unpredictable in production, replayable in tests](/en/posts/rng-unpredictable-in-prod-replayable-in-tests/). FreeCell deal numbers serve a different requirement: letting players share a layout. Publishing the seed and dealing rules suits that purpose, provided it is the guarantee the game needs.

## Notes

- Reproducing a Microsoft FreeCell deal means fixing four things: the LCG (`214013`, `2531011`, a 15-bit output), the card numbering (`rank * 4 + suit`, suits CDHS), draw-and-backfill from the tail, and dealing into eight columns in order.
- With the same LCG and the same deal number, replacing the backfill with Fisher-Yates changes the first card of deal 1 from JD to 6H; compatibility depends on the complete dealing procedure.
- 15 bits is the output width and 32,000 is the deal range; state widths of 31, 32 and 64 bits give identical outputs for all 32,000 deals.
- The Internet FreeCell Project ran from August 1994 to April 1995; 31,999 deals were reported solved and 11982 was not. Ettlinger's FCPro analysis later verified that result, and Keller records at least eight independent solvers failing on it.
- Reproducible, solvable and cheat-resistant are separate guarantees. A solver should tell apart a found solution, an exhaustive search with none, and a timeout; publishing a board lets players prepare.

## Further reading

- Rosetta Code, [Deal cards for FreeCell](https://rosettacode.org/wiki/Deal_cards_for_FreeCell): the LCG, card numbering and dealing steps, layouts for deals 1 and 617, and a Python solution that prints deal 11982; the examples here match all three.
- Freecell Solver, [pi_make_microsoft_freecell_board.c](https://github.com/shlomif/fc-solve/blob/master/fc-solve/source/board_gen/pi_make_microsoft_freecell_board.c): the compatible dealer, with a header crediting Jim Horne's code and the Microsoft C Run-Time Library; the generator and deal loop live in `gen_ms_boards__rand.h` and `range_solvers_gen_ms_boards.h` in the same repository. Read from master; a pinned commit is better for citation.
- Michael Keller, [FreeCell FAQ](https://www.solitairelaboratory.com/fcfaq.html) (Solitaire Laboratory): first-hand record of the Internet FreeCell Project, the history of 11982, the Ettlinger and FCPro verification, the XP million deals and the lists of unsolvable deal numbers; most facts in this post come from here.
- Wikipedia, [Microsoft FreeCell](https://en.wikipedia.org/wiki/Microsoft_FreeCell) and [FreeCell](https://en.wikipedia.org/wiki/FreeCell): deal counts, project dates, Jim Horne's background and the NP-completeness citation (Helmert 2003); neither article describes the generator.

> Ideas and technical judgement by Sheng; drafted with Claude · topic chosen by Sheng; text, case notes and examples written by Claude, run on Linux with Python 3.13.18.