"""Blackjack, eksakt modell med uendelig kortstokk (nye-casino.io, 21.10.2026).

Regler: dealer står på alle 17 (S17), dealer kikker etter blackjack (peek) med ess/10,
blackjack betaler 3:2 (parameter), dobling på to første kort, dobling etter deling (DAS),
én deling (ingen ny deling), delte ess får ett kort hver, ingen overgivelse.
Uendelig kortstokk: hvert kort trekkes med fast sannsynlighet (1/13, tiere 4/13).
Kjør: python3 bj_model.py
"""
from functools import lru_cache

P = {c: 1/13 for c in range(2, 10)}
P[10] = 4/13
P[11] = 1/13  # ess
CARDS = list(P)


def add(total, soft, card):
    if card == 11:
        total += 11; soft += 1
    else:
        total += card
    while total > 21 and soft:
        total -= 10; soft -= 1
    return total, soft


@lru_cache(None)
def dealer_dist(total, soft, s17=True):
    """Fordeling av dealerens slutt (17..21 eller 22=bust) fra en gitt hånd."""
    if total > 21:
        return {22: 1.0}
    if total >= 17 and not (not s17 and total == 17 and soft):
        return {total: 1.0}
    out = {}
    for c in CARDS:
        t, s = add(total, soft, c)
        for k, v in dealer_dist(t, s, s17).items():
            out[k] = out.get(k, 0) + P[c] * v
    return out


def dealer_given_up(up, s17=True):
    """Dealerens sluttfordeling gitt åpent kort, betinget av at dealer IKKE har blackjack (peek)."""
    t0, s0 = add(0, 0, up)
    out = {}
    norm = 0
    for c in CARDS:
        t, s = add(t0, s0, c)
        if t == 21:  # blackjack, utelukket ved peek
            continue
        norm += P[c]
        for k, v in dealer_dist(t, s, s17).items():
            out[k] = out.get(k, 0) + P[c] * v
    return {k: v / norm for k, v in out.items()}, 1 - norm  # (fordeling, P(dealer BJ))


def stand_ev(total, dd):
    if total > 21:
        return -1.0
    ev = 0
    for k, v in dd.items():
        if k == 22 or total > k:
            ev += v
        elif total < k:
            ev -= v
    return ev


def make_solver(dd):
    @lru_cache(None)
    def best_hit_stand(total, soft):
        """Beste EV når bare trekk/stå er lov (etter tredje kort)."""
        if total > 21:
            return -1.0
        s = stand_ev(total, dd)
        h = hit_ev(total, soft)
        return max(s, h)

    @lru_cache(None)
    def hit_ev(total, soft):
        ev = 0
        for c in CARDS:
            t, s = add(total, soft, c)
            ev += P[c] * (best_hit_stand(t, s) if t <= 21 else -1.0)
        return ev

    def double_ev(total, soft):
        ev = 0
        for c in CARDS:
            t, s = add(total, soft, c)
            ev += P[c] * stand_ev(t, dd)
        return 2 * ev

    return best_hit_stand, hit_ev, double_ev


def hand_value(cards):
    t = s = 0
    for c in cards:
        t, s = add(t, s, c)
    return t, s


def analyse(up, bj_pay=1.5, s17=True, policy="basic"):
    """EV per startinnsats for et gitt dealerkort, sammen med spillerens første to kort."""
    dd, p_dbj = dealer_given_up(up, s17)
    best, hit, dbl = make_solver(dd)
    ev = 0
    for c1 in CARDS:
        for c2 in CARDS:
            p = P[c1] * P[c2]
            t, s = hand_value([c1, c2])
            if t == 21:  # spillerens blackjack
                # dealer BJ er allerede utelukket i dd; legges til under
                ev += p * bj_pay
                continue
            if policy == "basic":
                opts = {"stå": stand_ev(t, dd), "trekk": hit(t, s), "doble": dbl(t, s)}
                if c1 == c2:
                    opts["dele"] = split_ev(c1, dd, best, hit, dbl)
                ev += p * max(opts.values())
            elif policy == "mimic":
                ev += p * mimic_ev(t, s, dd)
            elif policy == "never_bust":
                ev += p * never_bust_ev(t, s, dd)
    # vekt mot dealer-BJ: når dealer har BJ taper spilleren 1, unntatt mot egen BJ (push)
    p_pbj = 2 * P[11] * P[10]
    total = (1 - p_dbj) * ev + p_dbj * (-(1 - p_pbj))
    return total


def split_ev(card, dd, best, hit, dbl):
    """Én deling, DAS, delte ess får ett kort. EV for begge hender samlet."""
    one = 0
    for c in CARDS:
        t, s = hand_value([card, c])
        if card == 11:
            v = stand_ev(t, dd)  # ett kort på ess, 21 er ikke blackjack
        else:
            v = max(stand_ev(t, dd), hit(t, s), dbl(t, s))
        one += P[c] * v
    return 2 * one


def mimic_ev(total, soft, dd):
    """Kopier dealeren: trekk til 17, ingen dobling/deling. Spillerens bust taper alltid."""
    if total > 21:
        return -1.0
    if total >= 17:
        return stand_ev(total, dd)
    return sum(P[c] * mimic_ev(*add(total, soft, c), dd) for c in CARDS)


def never_bust_ev(total, soft, dd):
    """Stå på hard 12 eller mer; trekk på hard 11 eller lavere og myke hender under 17."""
    if total > 21:
        return -1.0
    if (not soft and total >= 12) or (soft and total >= 17):
        return stand_ev(total, dd)
    return sum(P[c] * never_bust_ev(*add(total, soft, c), dd) for c in CARDS)


def house_edge(bj_pay=1.5, s17=True, policy="basic"):
    return -sum(P[up] * analyse(up, bj_pay, s17, policy) for up in CARDS)


def basic_table():
    """Grunnstrategi fra modellen: beste valg per hånd og dealerkort."""
    rows = {}
    ups = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    for up in ups:
        dd, _ = dealer_given_up(up)
        best, hit, dbl = make_solver(dd)
        for t in range(5, 21):  # harde hender
            o = {"S": stand_ev(t, dd), "H": hit(t, 0), "D": dbl(t, 0)}
            rows.setdefault(f"hard {t}", []).append(max(o, key=o.get))
        for t in range(13, 21):  # myke hender (A2..A9)
            o = {"S": stand_ev(t, dd), "H": hit(t, 1), "D": dbl(t, 1)}
            rows.setdefault(f"myk {t}", []).append(max(o, key=o.get))
        for c in [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]:
            t, s = hand_value([c, c])
            o = {"S": stand_ev(t, dd), "H": hit(t, s), "D": dbl(t, s), "P": split_ev(c, dd, best, hit, dbl)}
            rows.setdefault(f"par {c}", []).append(max(o, key=o.get))
    return ups, rows


if __name__ == "__main__":
    print("Husfordel per startinnsats (uendelig stokk, S17, peek, DAS, én deling, ingen overgivelse)")
    for name, kw in [("Grunnstrategi 3:2", {}), ("Grunnstrategi 6:5", {"bj_pay": 1.2}),
                     ("Grunnstrategi 3:2, H17", {"s17": False}),
                     ("Kopier dealeren 3:2", {"policy": "mimic"}),
                     ("Stå på hard 12+ 3:2", {"policy": "never_bust"})]:
        print(f"{name:28s} {100*house_edge(**kw):6.3f} %")
    ups, rows = basic_table()
    print("\nGrunnstrategi (S=stå, H=trekk, D=doble, P=dele); kolonner dealerkort", ups)
    for k, v in rows.items():
        print(f"{k:8s} {' '.join(v)}")
