"""Exact calculations: baccarat (8-deck full enumeration), keno (hypergeometric), craps (exact + 1M-roll check).""" from math import comb from fractions import Fraction import numpy as np from common import save SEED = 20261004 # ---------- Baccarat: exact by enumerating card ranks with removal ---------- def baccarat(decks=8): counts = [16 * decks] + [4 * decks] * 9 # index 0 = ten-value (10,J,Q,K), 1..9 = A..9 total = 52 * decks res = {"banker": 0, "player": 0, "tie": 0} from functools import lru_cache def draw_iter(cnt, n): for r in range(10): if cnt[r]: p = cnt[r] / n cnt[r] -= 1 yield r, p cnt[r] += 1 cnt = counts[:] n = total P = {"banker": 0.0, "player": 0.0, "tie": 0.0} for p1, a in draw_iter(cnt, n): for b1, b in draw_iter(cnt, n - 1): for p2, c in draw_iter(cnt, n - 2): for b2, d in draw_iter(cnt, n - 3): w = a * b * c * d pt = (p1 + p2) % 10; bt = (b1 + b2) % 10 left = n - 4 if pt >= 8 or bt >= 8: P["player" if pt > bt else "banker" if bt > pt else "tie"] += w; continue if pt <= 5: for p3, e in draw_iter(cnt, left): pt3 = (pt + p3) % 10 draws = (bt <= 2 or (bt == 3 and p3 != 8) or (bt == 4 and 2 <= p3 <= 7) or (bt == 5 and 4 <= p3 <= 7) or (bt == 6 and p3 in (6, 7))) if draws: for b3, f in draw_iter(cnt, left - 1): bt3 = (bt + b3) % 10 P["player" if pt3 > bt3 else "banker" if bt3 > pt3 else "tie"] += w * e * f else: P["player" if pt3 > bt else "banker" if bt > pt3 else "tie"] += w * e else: if bt <= 5: for b3, f in draw_iter(cnt, left): bt3 = (bt + b3) % 10 P["player" if pt > bt3 else "banker" if bt3 > pt else "tie"] += w * f else: P["player" if pt > bt else "banker" if bt > pt else "tie"] += w return P bac = {} for decks in (8, 6, 1): P = baccarat(decks) b, p, t = P["banker"], P["player"], P["tie"] bac[decks] = {"decks": decks, "banker_pct": round(b * 100, 4), "player_pct": round(p * 100, 4), "tie_pct": round(t * 100, 4), "banker_edge_pct": round(-(b * 0.95 - p) * 100, 4), "banker_edge_no_commission_6pays_half_pct": None, "player_edge_pct": round(-(p - b) * 100, 4), "tie_8to1_edge_pct": round(-(t * 8 - (1 - t)) * 100, 4), "tie_9to1_edge_pct": round(-(t * 9 - (1 - t)) * 100, 4)} print(bac[decks]) # ---------- Keno: 80 numbers, 20 drawn ---------- keno = [] for spots in range(1, 11): row = {"spots": spots, "hits": []} for h in range(spots + 1): p = comb(20, h) * comb(60, spots - h) / comb(80, spots) row["hits"].append({"hits": h, "p_pct": round(p * 100, 6), "one_in": round(1 / p, 1) if p else None}) keno.append(row) # ---------- Craps ---------- ways = {s: 6 - abs(7 - s) for s in range(2, 13)} pass_win = Fraction(ways[7] + ways[11], 36) for pt in (4, 5, 6, 8, 9, 10): pass_win += Fraction(ways[pt], 36) * Fraction(ways[pt], ways[pt] + ways[7]) dp_win = Fraction(ways[2] + ways[3], 36) for pt in (4, 5, 6, 8, 9, 10): dp_win += Fraction(ways[pt], 36) * Fraction(ways[7], ways[pt] + ways[7]) dp_push = Fraction(ways[12], 36) dp_lose = 1 - dp_win - dp_push rng = np.random.default_rng(SEED) N = 1_000_000 d = rng.integers(1, 7, (N, 2)).sum(1) craps = {"pass_win_exact": f"{pass_win.numerator}/{pass_win.denominator}", "pass_win_pct": round(float(pass_win) * 100, 4), "pass_edge_pct": round(float(1 - 2 * pass_win) * 100, 4), "dont_pass_edge_pct": round(float(dp_lose - dp_win) * 100, 4), "dice_totals": [{"total": s, "ways": ways[s], "p_pct": round(ways[s] / 36 * 100, 4), "sim_pct": round((d == s).mean() * 100, 4)} for s in range(2, 13)], "rolls_sim": N} save("table_games", {"games": "Baccarat (exact enumeration), Keno (hypergeometric), Craps (exact + 1M-roll check)", "seed": SEED, "script": "table_games.py"}, {"baccarat": list(bac.values()), "keno": keno, "craps": craps}, [[r["decks"], r["banker_pct"], r["player_pct"], r["tie_pct"], r["banker_edge_pct"], r["player_edge_pct"], r["tie_8to1_edge_pct"]] for r in bac.values()], ["decks", "banker_win_pct", "player_win_pct", "tie_pct", "banker_edge_pct", "player_edge_pct", "tie_8to1_edge_pct"]) # keno CSV separately import csv, os from common import CSVS with open(os.path.join(CSVS, "keno.csv"), "w", newline="") as f: w = csv.writer(f); w.writerow(["spots", "hits", "probability_pct", "one_in"]) for r in keno: for h in r["hits"]: w.writerow([r["spots"], h["hits"], h["p_pct"], h["one_in"]])