#!/usr/bin/env python3
"""Reproduce PlaySolitaire's English editorial calculations (standard library only).

Models are illustrative unless explicitly labeled frozen observed counts.
Run with Python 3: python3 solitaire-editorial-calculations.py
No network access, current player data, or hidden simulation inputs.
"""
from math import comb, isclose
from itertools import product

def opening_aces(k):
    return comb(4, k) * comb(48, 7 - k) / comb(52, 7)

def any_loss_run(games, length, win_probability):
    # State j counts current trailing losses, conditioned on no full run yet.
    states = [1.0] + [0.0] * (length - 1)
    for _ in range(games):
        states = [sum(states) * win_probability] + [
            states[j - 1] * (1 - win_probability) for j in range(1, length)
        ]
    return 1 - sum(states)

def wilson(wins, starts, z=1.96):
    p = wins / starts
    center = (p + z*z/(2*starts))/(1 + z*z/starts)
    half = z * ((p*(1-p)/starts + z*z/(4*starts*starts)) ** .5)/(1 + z*z/starts)
    return (100*(center-half), 100*(center+half))

if __name__ == '__main__':
    print('Uniform shuffled deck: Aces among seven exposed opening cards')
    for k in range(5):
        print(k, f'{100*opening_aces(k):.6f}%')
    assert isclose(sum(opening_aces(k) for k in range(5)), 1)
    print('At least one Ace:', f'{100*(1-opening_aces(0)):.6f}%')
    print('Ace in a specified unseen position after seven non-Aces:', f'{100*4/45:.6f}%')
    print('\nFixed independent win chance: next k games all losses')
    for p in [.3, .5, .7]:
        print(p, {k: f'{100*(1-p)**k:.6f}%' for k in [3,5,10]})
    run = any_loss_run(20, 5, .5)
    # Independent exact enumeration checks the state recurrence for fair games.
    exhaustive = sum('00000' in ''.join(bits) for bits in product('01', repeat=20)) / 2**20
    assert run == exhaustive == .24987030029296875
    print('Any five-loss run within twenty games at p=.5:', f'{100*run:.6f}%')
    print('\nSynthetic selection examples (not player observations)')
    print('Random completion:', 4/10, 'conditioned on solvability:', 4/8)
    print('Two possible selected-pool completions:', 3/4, 1/4)
    print('Synthetic groups:', 18/90, 1/10, 8/10, 63/90)
    print('Synthetic pooled rates:', (18+8)/(90+10), (1+63)/(10+90))
    print('\nFrozen win-rates-v2-2026-08-05 counts; not current measurements')
    for label,wins,starts in [('Draw 1',51334,138759),('Draw 3',4634,12190)]:
        print(label, f'{100*wins/starts:.1f}%', tuple(round(x,1) for x in wilson(wins,starts)))
    print('\nIllustrative scoring arithmetic')
    print('Solitaired moves + seconds:',110+150,100+180)
    print('Vegas 11 foundation cards:',5*11-52)
