#!/usr/bin/env python3
"""Monte Carlo: do betting systems actually work?

Tests the four systems people actually search for, on a real casino game with a
real table limit, and reports what happens over a fixed session.

Game: European roulette even-money bet (red/black).
  win prob = 18/37 = 0.486486...,  house edge = 1/37 = 2.70%

Session rules (stated so they are reproducible):
  bankroll B, base unit u, table max M, session ends when either
    - the player completes N rounds (survived), or
    - the player cannot place the next required bet (busted)
  A "round" is one spin for flat/D'Alembert/Fibonacci, and one full
  progression step for Martingale (each spin counts as a bet either way).

Systems:
  FLAT        always bet u
  MARTINGALE  double after a loss, reset to u after a win
  DALEMBERT   +1 unit after a loss, -1 unit after a win (floor u)
  FIBONACCI   advance the Fibonacci ladder on a loss, back two steps on a win

Seed 20260803.
"""
import random
from statistics import mean

random.seed(20260803)

P_WIN = 18 / 37          # European roulette even-money
EDGE = 1 / 37


def session(system, bankroll, unit, table_max, rounds):
    bal = float(bankroll)
    wagered = 0.0
    spins = 0
    # system state
    mart = unit
    dalem = unit
    fib = [1, 1]
    fib_i = 0

    for _ in range(rounds):
        if system == 'flat':
            bet = unit
        elif system == 'martingale':
            bet = mart
        elif system == 'dalembert':
            bet = dalem
        else:  # fibonacci
            bet = fib[fib_i] * unit

        bet = min(bet, table_max)
        if bet > bal:                     # cannot cover the required bet
            return {'bust': True, 'bal': bal, 'wagered': wagered, 'spins': spins}

        win = random.random() < P_WIN
        bal += bet if win else -bet
        wagered += bet
        spins += 1

        if system == 'martingale':
            mart = unit if win else min(mart * 2, table_max)
        elif system == 'dalembert':
            dalem = max(unit, dalem - unit) if win else dalem + unit
        elif system == 'fibonacci':
            if win:
                fib_i = max(0, fib_i - 2)
            else:
                fib_i += 1
                while fib_i >= len(fib):
                    fib.append(fib[-1] + fib[-2])

    return {'bust': False, 'bal': bal, 'wagered': wagered, 'spins': spins}


def run(system, n, bankroll, unit, table_max, rounds):
    busts = 0
    bals = []
    wag = []
    for _ in range(n):
        r = session(system, bankroll, unit, table_max, rounds)
        busts += r['bust']
        bals.append(r['bal'])
        wag.append(r['wagered'])
    finals = sorted(bals)
    return {
        'bust_rate': busts / n,
        'avg_final': mean(bals),
        'median_final': finals[n // 2],
        'avg_wagered': mean(wag),
        'net': mean(bals) - bankroll,
        'edge_of_wagered': (bankroll - mean(bals)) / mean(wag) if mean(wag) else 0,
        'p_ahead': sum(1 for b in bals if b > bankroll) / n,
    }


N = 100_000
BANKROLL = 200
UNIT = 1
TABLE_MAX = 500
ROUNDS = 250

print(f"European roulette even-money (win {P_WIN:.6f}, house edge {EDGE*100:.2f}%)")
print(f"bankroll ${BANKROLL}, base unit ${UNIT}, table max ${TABLE_MAX}, "
      f"{ROUNDS} rounds/session, {N:,} sessions per system\n")

rows = []
for s in ('flat', 'martingale', 'dalembert', 'fibonacci'):
    r = run(s, N, BANKROLL, UNIT, TABLE_MAX, ROUNDS)
    rows.append((s, r))
    print(f"--- {s.upper()}")
    print(f"    busted before {ROUNDS} rounds : {r['bust_rate']*100:.2f}%")
    print(f"    finished ahead of ${BANKROLL} : {r['p_ahead']*100:.2f}%")
    print(f"    average final bankroll      : ${r['avg_final']:.2f}   (net ${r['net']:+.2f})")
    print(f"    median final bankroll       : ${r['median_final']:.2f}")
    print(f"    average total wagered       : ${r['avg_wagered']:,.0f}")
    print(f"    loss as % of money wagered  : {r['edge_of_wagered']*100:.2f}%  (house edge is {EDGE*100:.2f}%)")
    print()

print("=== the point ===")
for s, r in rows:
    print(f"{s:12} net ${r['net']:+7.2f}   loss/wagered {r['edge_of_wagered']*100:5.2f}%")
