Transparenz

Diese Seite ist ohne Anmeldung erreichbar. Sie enthält alles, was nötig ist, um eine beliebige vergangene Runde ohne Mithilfe des Betreibers nachzurechnen.

Verfahren

Wie das Ergebnis zustande kommt

Vor jeder Runde steht das Ergebnis bereits fest – aber niemand kennt es, auch der Betreiber nicht. Das geht so:

  1. Der Server erzeugt einen geheimen Zufallswert, den Server-Seed, und veröffentlicht sofort dessen SHA256-Prüfsumme. Damit ist er festgelegt und kann nachträglich nicht mehr ausgetauscht werden.
  2. Der Spieler wählt einen Client-Seed. Weil er in die Rechnung eingeht, kann der Betreiber das Ergebnis nicht vorher bestimmen – er kennt die Wahl des Spielers nicht, wenn er den Server-Seed festlegt.
  3. Die Nonce zählt die Runden dieses Seed-Paares, der Cursor die Zahlen innerhalb einer Runde.
  4. Aus diesen vier Werten entsteht der Wurf. Dieselben Eingaben ergeben immer dasselbe Ergebnis – jeder kann es nachrechnen.
  5. Beim Seed-Wechsel wird der alte Server-Seed offengelegt. Wer die Prüfsumme von vorher mit dem offengelegten Seed vergleicht, sieht, dass nichts vertauscht wurde.
raw = HMAC_SHA256(key = serverSeed, message = "<clientSeed>:<nonce>:<cursor>")

Der Schlüssel ist der Server-Seed als Zeichenkette, nicht die aus dem Hex dekodierten Bytes. Die Zahl 0 bis 9999 entsteht aus den ersten zwei Bytes; fällt der Rohwert in den Bereich, der sich nicht gleichmäßig aufteilen lässt, wird er verworfen und mit erhöhtem Cursor neu gezogen. Ohne dieses Verwerfen wären manche Ergebnisse häufiger als andere.

Selbst nachrechnen
Hausvorteil
SpielHausvorteilRegelwerk
Dice 2.00 % 10000 gleich wahrscheinliche Ausgänge. Quote = (1 / Gewinnchance) × 0.98. Gewinnchance von 1,00 % bis 95,00 %.
Es wird nicht gerundet. Angeboten werden nur Quoten, die für den gewählten Einsatz ohne Rest aufgehen – dadurch beträgt der Hausvorteil bei jeder einzelnen Wette exakt 2.00 %, auch bei einem Einsatz von 1 eGold. Einen Mindesteinsatz gibt es nicht.
Roulette 2,7027 % Europäisches Rad: 37 Felder, eine Null. Die Auszahlung ist auf 36 gerechnet – eine Wette auf n Felder zahlt das 36/n-fache des Einsatzes zurück.
Der Hausvorteil steckt allein in der Null und ist damit bei jeder Wettart derselbe: Einzelzahl 36×, Split 18×, Querreihe 12×, Karree 9×, Doppelreihe 6×, Dutzend und Kolonne 3×, einfache Chancen 2×.
Rot/Schwarz, Gerade/Ungerade und 1–18/19–36 verlieren bei der Null vollständig – es gibt keine Teilrückgabe und keine Gefangenenregel (kein en prison, kein partage). Das ist die Variante mit dem höheren Hausvorteil auf den einfachen Chancen; sie steht hier, weil alle Wettarten dann denselben Vorteil haben und die Seite keine zwei Zahlen ausweisen muss.
Es wird nicht gerundet. 36 ist durch jede vorkommende Feldzahl teilbar, jede Auszahlung ist damit ein ganzzahliges Vielfaches des Einsatzes. Auch hier gibt es deshalb keinen Mindesteinsatz. Höchstens 20 Einsätze je Wurf.
Blackjack 1,92 % Blackjack zahlt 6:5. Das ist die teuerste Regel dieses Tisches – sie kostet rund 1,2 Punkte gegenüber den sonst üblichen 3:2 und ist der Hauptgrund für den ausgewiesenen Hausvorteil.
Der Dealer zieht auf Soft 17 statt stehen zu bleiben. Weitere rund 0,2 Punkte.
6 Decks, vor jeder Runde genau einmal gemischt. Kein Cut Card, kein fortlaufender Schuh – Kartenzählen bringt hier nichts, weil jede Runde bei derselben Zusammensetzung beginnt.
Dealer prüft bei Ass oder Zehnwert sofort auf Blackjack (Peek). Double auf beliebige zwei Karten und nach dem Teilen. Split auf bis zu 3 Hände; geteilte Asse bekommen genau eine Karte und werden nicht erneut geteilt. 21 aus einer geteilten Hand ist kein Blackjack. Kein Surrender, keine Insurance, kein Even Money.
Einsätze nur in Vielfachen von 5 eGold. Bei 6:5 zahlt ein Blackjack das 11/5-fache des Einsatzes; bei 1 eGold wären das 2,2 eGold. Gerundet wird nicht, also sind nur Einsätze spielbar, bei denen die Rechnung aufgeht.
Worauf sich die Zahl bezieht: Wer verdoppelt oder teilt, legt nach – im Mittel liegen 1,14 Grundeinsätze auf dem Tisch. Die 1,92 % oben sind der Verlust je Grundeinsatz, also bezogen auf den Betrag, den man zu setzen beschließt. Bezogen auf jeden tatsächlich gesetzten eGold sind es 1,69 % – nur diese Zahl ist mit Dice und Roulette direkt vergleichbar, denn dort gibt es kein Nachlegen.
Woher die Zahl kommt: anders als bei Dice und Roulette lässt sie sich nicht ausrechnen, sondern nur messen – sie folgt aus dem Zusammenspiel von Ziehregeln, Double, Split und Auszahlung. Gemessen über 2,5 Millionen simulierte Runden in 10 unabhängigen Läufen, gegen Basisstrategie, 6 Decks, H17, Double nach Split, kein Surrender, mit einer Unsicherheit von ± 0,07 Punkten. Wer schlechter spielt als die Basisstrategie, zahlt mehr.
Slot
mit Jackpot
2,00 %
über einen Jackpot-Zyklus
Drei Walzen mit je 20 Positionen, alle drei gleich belegt, gewertet wird die mittlere Reihe. Das Walzenband, Position 0 bis 19:
K Z K G K 7 Z K G ★ K Z G K 7 Z K G K Z
K = Kirsche, Z = Zitrone, G = Glocke, 7 = Sieben, ★ = Stern. Je Walze eine Zahl mit getInt(20), Cursor ab 0; wird verworfen, rückt der Cursor weiter, und die nächste Walze beginnt dort. Das ergibt genau 8.000 gleich wahrscheinliche Ausgänge – so wenige, dass sich alle abzählen lassen:
KategorieBedingungAusgängeZahlt
Jackpotdrei Sterne1/800015 × Einsatz und der anteilige Jackpot
Größerdrei Glocken oder drei Sieben72/800015 × Einsatz
Mitteldrei Kirschen oder drei Zitronen637/80003 × Einsatz
Kleinstgenau zwei gleiche4470/80001 × Einsatz
–alles andere2820/8000nichts
Die Rechnung:
Festgewinne  = (4470·1 + 637·3 + 72·15 + 1·15) / 8000 = 7476/8000
Jackpot                                      = 364/8000
Auszahlungsquote über den Zyklus             = 7840/8000
Hausvorteil                                  = 160/8000
Der Hausvorteil steckt in der Gewinntabelle, nicht in einer Gebühr. Jede Festquote ist ein ganzzahliges Vielfaches des Einsatzes; gerundet wird dort nie, einen Mindesteinsatz gibt es nicht.
Der Jackpot. Jeder Einsatz führt ihm 364/8000 zu, also 4,55 %. Er liegt auf einem eigenen Konto, zählt zu den Verbindlichkeiten des Hauses und wird aus diesem Konto ausgezahlt, nie aus der Bankroll. Bruchteile unter einem eGold gehen nicht verloren: Sie werden in 1/8000 eGold zum nächsten Spiel mitgenommen, sodass über alle Spiele zusammen exakt 364/8000 des Umsatzes im Topf landen – auch bei Einsätzen von 1 eGold.
Volleinsatz 25 eGold – zugleich der Höchsteinsatz. Drei Sterne zahlen den Jackpot anteilig: Anteil = abrunden(Jackpot × Einsatz / 25). Wer 25 eGold setzt, bekommt den ganzen Stand, der auf der Maschine steht, wer die Hälfte setzt, die Hälfte; der Rest bleibt für den nächsten Treffer stehen. Ohne diese Kopplung hätte ein Einsatz von 1 eGold dieselbe Chance auf einen Topf, den andere mit dem 25-Fachen gefüllt haben. Der Volleinsatz ist eine feste Zahl; geändert wird er, wenn überhaupt, nur nach oben und mit Ankündigung. Nach einem Treffer beginnt der Topf ohne Startbetrag neu; drei Sterne zahlen deshalb immer mindestens 15 × Einsatz.
98 % sind ein Mittel, kein Festwert. Die Auszahlungsquote ist bei jedem Einsatz dieselbe, hängt aber vom Jackpotstand ab:
Auszahlungsquote = 7476/8000 + Jackpot / (8000 × 25)
Bei leerem Topf sind es 93,45 %, bei 9.100 eGold – dem mittleren Stand – 98,00 %, und über 13.100 eGold über 100 %. Das ist kein Fehler: Wer bei hohem Topf spielt, spielt mit Geld, das frühere Spieler eingezahlt haben. Das Haus behält in jedem Fall 160/8000 des Umsatzes.
Der Höchsteinsatz richtet sich außerdem nach §5: Der mögliche Gewinn eines Spiels (14 × Einsatz – der Jackpot zählt nicht, er kommt aus seinem eigenen Topf) darf 1 % der Bankroll nicht übersteigen.
Wird die Maschine je abgeschaltet, wird der Resttopf unter allen Spielern verlost – provably fair, mit vorher veröffentlichter Teilnehmerliste und dem Hash des Server-Seeds. Wer genau teilnimmt, wird vor einer Abschaltung bekanntgegeben. Der Topf ist Spielergeld; er verfällt nicht und geht nicht an das Haus.
Coinflip
Spieler gegen Spieler
1,0 % (Höchstwert) Das Haus spielt hier nicht mit. Zwei Spieler setzen denselben Betrag, eine Münze entscheidet, der Gewinner nimmt den Pot abzüglich Kommission. Die Bank trägt kein Ergebnisrisiko und hat deshalb auch kein Interesse am Ausgang – sie nimmt in jedem Fall dasselbe.
Die Rechnung, mit E als Einsatz je Spieler:
pot           = 2E
rake          = abrunden(pot × 1 %) = abrunden(E / 50)
Auszahlung    = pot − rake
EV je Spieler = 0,5 × (pot − rake) − E = −rake/2
Das sind 1,0 % (Höchstwert) auf den eigenen Einsatz und damit direkt mit den Zahlen oben vergleichbar.
Höchstwert, nicht Festwert. Der Rake wird abgerundet, also zugunsten der Spieler. Exakt 1 % sind es nur bei Einsätzen in Vielfachen von 50 eGold; dazwischen weniger – bei 75 eGold zum Beispiel 1 statt 1,5, also 0,67 %.
Mindesteinsatz 50 eGold, Höchsteinsatz 10.000 eGold. Das ist die einzige Stelle im Haus mit einem Mindesteinsatz, und sie steht nicht wegen eines Risikos da: Unterhalb von 50 eGold wäre der abgerundete Rake schlicht 0.
Der Gegner lässt sich nicht aussuchen. Jede Partie steht offen in der Lobby und wird von dem angenommen, der zuerst zugreift. Betreiber- und Bank-Accounts sind ausgeschlossen – sie können weder eröffnen noch beitreten. Der Betreiber kennt den Server-Seed und könnte sonst als Beitretender seinen eigenen Client-Seed durchprobieren, bis er gewinnt; dagegen schützt kein Prüfverfahren, nur der Ausschluss.
Der Server-Seed einer Partie trägt genau diese eine Runde und wird unmittelbar nach ihr offengelegt – nicht erst bei einem Seed-Wechsel. Jede Partie ist damit Sekunden später nachrechenbar.
Poker
Spieler gegen Spieler
Rake 2 %
höchstens 2 Big Blinds
Das Haus spielt nicht mit. No-Limit Texas Hold'em als Cash Game, zwei bis 6 Plätze je Tisch. Das Haus nimmt nur den Rake:
rake = min(abrunden(pot × 2 %), 2 × Big Blind)
vom ganzen Pot, nachdem nicht gecallte Einsätze zurück sind, abgezogen vom Hauptpot zuerst – und ohne Flop kein Rake. Auf Pots unter 50 eGold ist er abgerundet null.
Stufen 1/2, 5/10, 25/50 eGold (Small/Big Blind). Buy-in 20 bis 100 Big Blinds; nachkaufen zwischen zwei Händen bis zum Höchst-Buy-in. Der Big Blind wandert je Hand einen Platz weiter; wer neu sitzt, spielt ab der Hand, in der er ihn zahlen müsste.
Setzen: Fold, Check, Call, Bet, Raise. Kleinster Einsatz ein Big Blind, ein Raise mindestens so groß wie der letzte derselben Runde; All-in geht immer, ein kurzes All-in öffnet die Runde nicht wieder.
Showdown: die beste Fünf-Karten-Hand aus sieben, A-2-3-4-5 ist die kleinste Straße, Farben haben keinen Rang, Gleichstand teilt. Alle Hände, die noch dabei sind, werden aufgedeckt. Niemand gewinnt mehr, als er gedeckt hat – darüber hinaus entsteht ein Side Pot. Ein ungerader Chip geht an den ersten Gewinner links vom Button; gerundet wird nicht.
Mischen und Austeilen: ein Seed-Paar je Tisch, dessen Prüfsumme feststeht, bevor sich jemand setzt. Die Nachricht sind die Platz-Seeds derer, die die Hand bekommen, aufsteigend nach Spielernummer; die Nonce zählt die Hände. Je Hand wird genau einmal gemischt und an festen Stellen ausgeteilt – welche, steht auf der Seite jeder Hand.
Zeit: 20 Sekunden je Entscheidung, danach Check, wenn es geht, sonst Fold. Wer aufsteht oder die Seite verlässt, für den entscheidet der Tisch bis zum Ende der Hand sofort.
Betreiber- und Bank-Accounts sitzen nicht am Tisch. Der Betreiber kann den Tisch-Seed lesen und kennt damit jede Karte jeder laufenden Hand – dagegen hilft kein Verfahren, nur der Ausschluss.
Offenlegung: Der Tisch-Seed wird nach 100 Händen oder 24 Stunden gewechselt und offengelegt, ebenso, wenn der Tisch schließt. Danach ist jede Hand dieses Seeds nachrechenbar – auch die Karten derer, die gefoldet haben. Man sieht also im Nachhinein, wer geblufft hat; anders lässt sich die Mischung nicht prüfen.
Deckung

Keine Verbindlichkeiten. Es liegt derzeit kein Spielerguthaben im Haus, eine Deckungsquote ist damit nicht definiert.

Die Quote setzt den Bestand des Bank-Accounts ins Verhältnis zur Summe aller Spielerguthaben – dazu zählt auch der Jackpot der Slotmaschine, denn er gehört den Spielern. 100 % bedeutet: Jeder eGold, den Spieler im Haus haben, ist gedeckt. Absolute Beträge werden nicht ausgewiesen – sie sagen über die Sicherheit des Guthabens nichts aus, verraten aber die Geschäftszahlen.

Grenze je Runde. Bei den Spielen gegen die Bank darf der mögliche Gewinn einer Runde 1 % der Bankroll nicht übersteigen – gerechnet mit der Bankroll abgerundet auf volle 5.000 eGold. Wer angemeldet ist, sieht die Grenze beim Spiel, bevor er setzt; geprüft wird genau diese Zahl. Sie ändert sich nur in Stufen und verrät die Bankroll deshalb nicht genau. Bei Coinflip trägt das Haus kein Risiko, dort gilt sie nicht.

Noch ohne Aussagekraft: Es ist noch kein Abgleich gegen den tatsächlichen Kontostand gelaufen, der Bestand stammt aus dem eigenen Buchwerk. Die Quote kann dadurch gar nicht unter 100 % fallen – sie prüft derzeit nichts.

Jackpot der Slotmaschine

Jackpot jetzt: 0 eGold · Auszahlungsquote bei diesem Stand: 93,45 %
Noch nie geknackt.

Diese beiden Beträge sind die einzigen auf dieser Seite: Ein Jackpot, den niemand sieht, ist keiner. Ältere Treffer verschwinden, sobald ein neuer fällt. Die Runde eines Treffers ist wie jede andere nachrechenbar, ohne Einsatz und Auszahlung.

Letzte Runden gegen die Bank

Noch keine Runden gespielt.

Letzte Partien Spieler gegen Spieler

Noch keine PvP-Partie gespielt.

Letzte Runden an Roulette-Tischen

An den Tischen wurde noch nicht gedreht.

Eine Tischrunde ist eine Runde mit mehreren Plätzen. Ihre Nachricht besteht aus den Platz-Seeds aller, die bei „Nichts geht mehr" saßen, aufsteigend nach Spielernummer verkettet – es genügt, dass einer seinen Seed ändert, dann ist es für alle ein anderer Wurf. Der Tisch-Seed wird bei der Rotation (nach 100 Runden oder 24 Stunden) oder wenn der Tisch schließt offengelegt. Einsätze und Auszahlungen stehen auch hier nicht (§7).

Eine Grenze: Wer den Tisch-Seed kennt, könnte als Mitspieler seinen eigenen Platz-Seed so wählen, dass die Zahl gegen die anderen fällt. Deshalb können sich Betreiber- und Bank-Accounts nicht an einen Tisch setzen. Gegen einen Betreiber, der mit einem fremden Konto mitspielt, schützt am gemeinsamen Kessel kein Verfahren; beim Einzelwurf zählt nur der eigene Seed.

Letzte Runden an Blackjack-Tischen

An den Blackjack-Tischen wurde noch nicht gespielt.

Eine Blackjack-Tischrunde ist eine Runde mit mehreren Plätzen und einem Schuh. Die Nachricht besteht aus den Platz-Seeds aller, die beim Austeilen saßen – auch derer, die nicht gesetzt haben –, aufsteigend nach Spielernummer verkettet. Daraus wird genau einmal gemischt. Der Tisch-Seed wird bei der Rotation (nach 100 Runden oder 24 Stunden) oder wenn der Tisch schließt offengelegt. Einsätze und Auszahlungen stehen auch hier nicht (§7).

Letzte Hände an Pokertischen

An den Pokertischen wurde noch keine Hand gespielt.

Jede Hand hat eine eigene Seite: wer saß, mit welchem Platz-Seed, das Board, die Hände vom Showdown und wer welchen Pot gewann – ohne Beträge (§7). Ist der Tisch-Seed offen (nach 100 Händen, 24 Stunden oder wenn der Tisch schließt), stehen dort auch die Karten derer, die gefoldet haben, und ein Python-Stück, das alles nachrechnet.

Offengelegte Server-Seeds

Noch kein Server-Seed offengelegt. Das geschieht, sobald ein Spieler seinen Seed wechselt.

Die Berechnung im Klartext

Diese Datei ist die unabhängige Zweitimplementierung des Verfahrens. Sie ist aus der Spezifikation geschrieben, nicht aus dem Anwendungscode übersetzt, und liefert nachweislich dieselben Ergebnisse. Speichern, ausführen, selbst nachrechnen – es braucht nur Python.

#!/usr/bin/env python3
"""
Unabhaengige Zweitimplementierung des Provably-Fair-Verfahrens.

CLAUDE.md §8 verlangt sie: "Eine unabhaengig geschriebene Zweitimplementierung
muss dieselben Ergebnisse liefern." Sinn der Sache ist, dass ein Denkfehler
nicht in beiden Implementierungen gleich ausfaellt. Deshalb ist diese Datei
aus der Spezifikation in §3 geschrieben, nicht aus dem TypeScript-Code
uebersetzt, und sie teilt mit ihm keine einzige Hilfsfunktion.

Sie ist zugleich das Stueck Code, das auf der Transparenzseite im Klartext
steht: kurz genug, dass ein Aussenstehender es liest, und in einer Sprache,
die er ohne Projekt-Setup ausfuehren kann.

Aufruf:
    python3 tools/referenz.py vektoren      # Testvektoren als JSON
    python3 tools/referenz.py pruefe FILE   # Eingaben pruefen, Ergebnis als JSON
"""
import hashlib
import hmac
import json
import sys


def raw(server_seed: str, client_seed: str, nonce: int, cursor: int) -> bytes:
    """HMAC_SHA256(key = serverSeed, message = "<clientSeed>:<nonce>:<cursor>").

    Der Schluessel ist der Server-Seed **als Zeichenkette**, nicht die aus dem
    Hex dekodierten Bytes. Wer nachrechnet, fuegt den Seed genau so ein, wie
    er offengelegt wurde.
    """
    message = f"{client_seed}:{nonce}:{cursor}"
    return hmac.new(server_seed.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).digest()


def get_float(server_seed: str, client_seed: str, nonce: int, cursor: int) -> float:
    """Gleitkommazahl in [0,1) aus den ersten vier Bytes.

        b0/256 + b1/256^2 + b2/256^3 + b3/256^4

    Vier Bytes, weil das Verfahren von Hand nachrechenbar bleiben soll.
    """
    b = raw(server_seed, client_seed, nonce, cursor)
    return b[0] / 256 + b[1] / 256**2 + b[2] / 256**3 + b[3] / 256**4


def _bytes_needed(spanne: int) -> int:
    return max(1, ((spanne - 1).bit_length() + 7) // 8)


def get_int(spanne: int, server_seed: str, client_seed: str, nonce: int, cursor: int):
    """Ganzzahl in 0..spanne-1 ohne Modulo-Bias.

    Es werden so viele Bytes gelesen, wie die Spanne braucht. Faellt der
    Rohwert in den Rest, der sich nicht gleichmaessig aufteilen laesst, wird
    er **verworfen** und mit erhoehtem Cursor neu gezogen. Genau dieses
    Verwerfen macht das Ergebnis gleichverteilt.

    Rueckgabe: (wert, naechster_cursor). Der Cursor wandert auch bei jedem
    verworfenen Versuch weiter, damit ein Pruefer den Ablauf nachvollziehen
    kann.
    """
    if spanne < 1:
        raise ValueError("Spanne muss mindestens 1 sein")
    if spanne == 1:
        return 0, cursor + 1

    k = _bytes_needed(spanne)
    topf = 256**k
    grenze = topf - (topf % spanne)

    while True:
        wert = int.from_bytes(raw(server_seed, client_seed, nonce, cursor)[:k], "big")
        cursor += 1
        if wert < grenze:
            return wert % spanne, cursor


def shuffle(anzahl: int, server_seed: str, client_seed: str, nonce: int, cursor: int = 0):
    """Fisher-Yates ueber 0..anzahl-1. Genau einmal je Runde (R8).

    Rueckgabe: (reihenfolge, naechster_cursor).
    """
    deck = list(range(anzahl))
    for i in range(anzahl - 1, 0, -1):
        j, cursor = get_int(i + 1, server_seed, client_seed, nonce, cursor)
        deck[i], deck[j] = deck[j], deck[i]
    return deck, cursor


def server_seed_hash(server_seed: str) -> str:
    return hashlib.sha256(server_seed.encode("utf-8")).hexdigest()


# ---------------------------------------------------------------------------

SEED_A = "0000000000000000000000000000000000000000000000000000000000000000"
SEED_B = "a3f1c9d4e5b6072839a0b1c2d3e4f5061728394a5b6c7d8e9f0a1b2c3d4e5f60"


def vektoren():
    out = {"hash": [], "raw": [], "float": [], "int": [], "shuffle": []}

    for seed in (SEED_A, SEED_B):
        out["hash"].append({"serverSeed": seed, "hash": server_seed_hash(seed)})

    faelle = [
        (SEED_A, "spieler-1", 0, 0),
        (SEED_A, "spieler-1", 0, 1),
        (SEED_A, "spieler-1", 1, 0),
        (SEED_A, "", 0, 0),
        (SEED_B, "MarcTestet", 42, 7),
        (SEED_B, "a-b_c", 999999, 123),
    ]
    for seed, client, nonce, cursor in faelle:
        out["raw"].append({
            "serverSeed": seed, "clientSeed": client, "nonce": nonce, "cursor": cursor,
            "raw": raw(seed, client, nonce, cursor).hex(),
        })
        out["float"].append({
            "serverSeed": seed, "clientSeed": client, "nonce": nonce, "cursor": cursor,
            "value": repr(get_float(seed, client, nonce, cursor)),
        })

    for spanne in (2, 6, 37, 52, 200, 312, 10_000):
        wert, folge = get_int(spanne, SEED_B, "MarcTestet", 42, 0)
        out["int"].append({
            "range": spanne, "serverSeed": SEED_B, "clientSeed": "MarcTestet",
            "nonce": 42, "cursor": 0, "value": wert, "cursorNext": folge,
        })

    for anzahl in (5, 52, 312):
        reihenfolge, folge = shuffle(anzahl, SEED_B, "MarcTestet", 42, 0)
        out["shuffle"].append({
            "count": anzahl, "serverSeed": SEED_B, "clientSeed": "MarcTestet", "nonce": 42,
            "order": reihenfolge, "cursorNext": folge,
        })

    return out


def pruefe(pfad):
    with open(pfad, encoding="utf-8") as fh:
        eingaben = json.load(fh)

    ergebnisse = []
    for e in eingaben:
        seed, client, nonce, cursor = e["serverSeed"], e["clientSeed"], e["nonce"], e["cursor"]
        eintrag = {
            "raw": raw(seed, client, nonce, cursor).hex(),
            "float": repr(get_float(seed, client, nonce, cursor)),
        }
        if "range" in e:
            wert, folge = get_int(e["range"], seed, client, nonce, cursor)
            eintrag["int"] = wert
            eintrag["cursorNext"] = folge
        if "shuffleCount" in e:
            reihenfolge, folge = shuffle(e["shuffleCount"], seed, client, nonce, cursor)
            eintrag["shuffle"] = reihenfolge
            eintrag["shuffleCursorNext"] = folge
        ergebnisse.append(eintrag)

    return ergebnisse


if __name__ == "__main__":
    befehl = sys.argv[1] if len(sys.argv) > 1 else "vektoren"
    if befehl == "vektoren":
        print(json.dumps(vektoren(), indent=2))
    elif befehl == "pruefe":
        print(json.dumps(pruefe(sys.argv[2])))
    else:
        print(__doc__)
        sys.exit(1)

Poker: Austeilen und Handbewertung

poker_referenz.py liegt neben der Datei oben und benutzt deren shuffle. Sie liest die Karten an den Austeilpositionen ab und bewertet Hände – absichtlich anders gebaut als das Casino: Sie probiert alle Fünferkombinationen durch.

#!/usr/bin/env python3
"""
Poker – unabhängige Zweitimplementierung (CLAUDE.md §8, Phase 11).

Mit diesem Programm rechnet jeder eine Pokerhand nach, ohne dem Casino zu
glauben: Stapel mischen (mit referenz.py), Karten an den öffentlichen
Positionen ablesen, Hände bewerten. Es ist absichtlich anders gebaut als
src/games/poker.ts: Es probiert alle Fünferkombinationen durch und
vergleicht Tupel, statt mit Bitmasken zu rechnen.

    python3 poker_referenz.py austeilen <server_seed> <client_seed> <nonce> <spieler>
    python3 poker_referenz.py bewerte <datei.json>      # Liste von Kartenlisten

Karten sind Zahlen 0..51: Rang = Karte % 13 (0 = Ass, 9 = Zehn, 10..12 =
Bube, Dame, König), Farbe = Karte // 13 (♣ ♦ ♥ ♠). Beim Poker zählt das Ass
14, in der Straße A-2-3-4-5 als Eins.
"""
import itertools
import json
import os
import sys
from collections import Counter

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from referenz import shuffle  # noqa: E402

KATEGORIEN = [
    "high_card", "paar", "zwei_paare", "drilling", "strasse",
    "flush", "full_house", "vierling", "straight_flush",
]


def rang(karte):
    r = karte % 13
    return 14 if r == 0 else r + 1


def strasse_hoch(raenge):
    """Höchste Karte einer Straße aus fünf verschiedenen Rängen, sonst 0."""
    s = sorted(set(raenge))
    if len(s) != 5:
        return 0
    if s[4] - s[0] == 4:
        return s[4]
    if s == [2, 3, 4, 5, 14]:
        return 5
    return 0


def fuenf(karten):
    """(Kategorie, fünf Ränge) einer Fünf-Karten-Hand."""
    raenge = [rang(k) for k in karten]
    farben = {k // 13 for k in karten}
    flush = len(farben) == 1
    hoch = strasse_hoch(raenge)
    # Gruppen nach (Anzahl, Rang), größte zuerst
    gruppen = sorted(Counter(raenge).items(), key=lambda kv: (kv[1], kv[0]), reverse=True)
    anzahl = [n for _, n in gruppen]
    werte = [r for r, _ in gruppen]

    def auf5(liste):
        return (list(liste) + [0, 0, 0, 0, 0])[:5]

    if flush and hoch:
        return 8, auf5([hoch])
    if anzahl[0] == 4:
        return 7, auf5(werte[:2])
    if anzahl[0] == 3 and anzahl[1] == 2:
        return 6, auf5(werte[:2])
    if flush:
        return 5, sorted(raenge, reverse=True)
    if hoch:
        return 4, auf5([hoch])
    if anzahl[0] == 3:
        return 3, auf5(werte[:3])
    if anzahl[0] == 2 and anzahl[1] == 2:
        return 2, auf5(werte[:3])
    if anzahl[0] == 2:
        return 1, auf5(werte[:4])
    return 0, sorted(raenge, reverse=True)


def bewerte(karten):
    """Beste Hand aus fünf bis sieben Karten: [Kategorie, Ränge]."""
    if not 5 <= len(karten) <= 7 or len(set(karten)) != len(karten):
        raise ValueError("5 bis 7 verschiedene Karten")
    beste = max(fuenf(c) for c in itertools.combinations(karten, 5))
    return [KATEGORIEN[beste[0]], beste[1]]


def positionen(n):
    """Austeilen ab links vom Button, zweimal herum; je eine Karte verbrannt vor Flop, Turn, River."""
    return {
        "loch": [[i, n + i] for i in range(n)],
        "flop": [2 * n + 1, 2 * n + 2, 2 * n + 3],
        "turn": 2 * n + 5,
        "river": 2 * n + 7,
    }


def austeilen(server_seed, client_seed, nonce, n):
    deck, _ = shuffle(52, server_seed, client_seed, nonce, 0)
    p = positionen(n)
    return {
        "loch": [[deck[a], deck[b]] for a, b in p["loch"]],
        "board": [deck[x] for x in p["flop"] + [p["turn"], p["river"]]],
    }


def main(argv):
    if len(argv) >= 2 and argv[1] == "bewerte":
        with open(argv[2], encoding="utf-8") as f:
            haende = json.load(f)
        print(json.dumps([bewerte(h) for h in haende]))
        return 0
    if len(argv) == 6 and argv[1] == "austeilen":
        print(json.dumps(austeilen(argv[2], argv[3], int(argv[4]), int(argv[5]))))
        return 0
    print(__doc__)
    return 2


if __name__ == "__main__":
    sys.exit(main(sys.argv))
Die Basisstrategie, gegen die der Blackjack-Hausvorteil gemessen wurde

Der ausgewiesene Hausvorteil von Blackjack ist kein gerechneter, sondern ein gemessener Wert – und gemessen wurde gegen genau diese Tabelle. Wer schlechter spielt, zahlt mehr; besser geht bei einem Schuh, der vor jeder Runde neu gemischt wird, nicht.

/**
 * Basisstrategie fuer das Regelwerk aus §6: 6 Decks, Dealer zieht Soft 17,
 * Double nach Split erlaubt, kein Surrender.
 *
 * **Wozu das hier steht:** §6 sagt, der verbindliche Hausvorteil sei das
 * Ergebnis der Simulation aus §8. Eine Simulation braucht aber einen Spieler,
 * und wie gut der spielt, entscheidet die Zahl. Gemessen wird deshalb gegen
 * **optimales Spiel ohne Kartenzaehlen** – die Basisstrategie. Wer schlechter
 * spielt, zahlt mehr; besser geht bei einem Schuh, der vor jeder Runde neu
 * gemischt wird, nicht.
 *
 * Die Tabelle gehoert damit zur ausgewiesenen Zahl und steht auf der
 * Transparenzseite. Eine Prozentzahl ohne die Strategie, gegen die sie
 * gemessen wurde, ist keine nachpruefbare Angabe.
 *
 * Die 6:5-Auszahlung kommt in der Tabelle **nicht** vor, und das ist richtig
 * so: Sie aendert die Hoehe des Blackjack-Gewinns, nie die beste Entscheidung
 * an einer Hand. Sie schlaegt voll auf den Hausvorteil durch, ohne das Spiel
 * zu veraendern – deshalb ist sie die teuerste Regel des Tisches.
 */
import {
  handWert,
  kartenwert,
  type Aktion,
  type BlackjackRules,
  BLACKJACK_RULES,
} from './blackjack.ts'

export interface Lage {
  readonly karten: readonly number[]
  /** Zaehlwert der offenen Dealerkarte: 2 bis 11. */
  readonly dealer: number
  readonly darfVerdoppeln: boolean
  readonly darfTeilen: boolean
}

/** Dealerkarten, gegen die gesplittet wird – kompakt als Liste. */
const SPLIT_GEGEN: Record<number, readonly number[]> = {
  11: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // Asse immer
  10: [], // Zehner nie
  9: [2, 3, 4, 5, 6, 8, 9], // nicht gegen 7, 10, Ass
  8: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // Achter immer
  7: [2, 3, 4, 5, 6, 7],
  6: [2, 3, 4, 5, 6],
  5: [], // nie – 5,5 wird als harte 10 gespielt
  4: [5, 6], // nur mit Double nach Split sinnvoll
  3: [2, 3, 4, 5, 6, 7],
  2: [2, 3, 4, 5, 6, 7],
}

/** Weiche Haende: bis zu welcher Dealerkarte verdoppelt wird. */
const SOFT_DOUBLE_GEGEN: Record<number, readonly number[]> = {
  13: [5, 6],
  14: [5, 6],
  15: [4, 5, 6],
  16: [4, 5, 6],
  17: [3, 4, 5, 6],
  18: [2, 3, 4, 5, 6],
  19: [6], // nur weil der Dealer Soft 17 zieht
}

const HART_DOUBLE_GEGEN: Record<number, readonly number[]> = {
  9: [3, 4, 5, 6],
  10: [2, 3, 4, 5, 6, 7, 8, 9],
  11: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // gegen alles, auch das Ass (Soft 17)
}

/**
 * Die Entscheidung.
 *
 * Reihenfolge wie am Tisch: erst Paare, dann weiche, dann harte Haende. Eine
 * andere Reihenfolge waere nicht bloss unuebersichtlich, sondern falsch –
 * A,A ist ein Paar **und** eine weiche Hand, und gespielt wird es als Paar.
 */
export function basisstrategie(
  lage: Lage,
  rules: BlackjackRules = BLACKJACK_RULES,
): Aktion {
  const { karten, dealer } = lage
  const wert = handWert(karten)

  // --- Paare ---------------------------------------------------------------
  if (lage.darfTeilen && karten.length === 2) {
    const a = kartenwert(karten[0]!)
    const b = kartenwert(karten[1]!)
    if (a === b) {
      const gegen = SPLIT_GEGEN[a]
      if (gegen !== undefined && gegen.includes(dealer)) return 'teilen'
      // 4,4 nur mit Double nach Split; ohne DAS wird gezogen.
      if (a === 4 && !rules.doubleNachSplit) return 'ziehen'
    }
  }

  // --- Weiche Haende -------------------------------------------------------
  if (wert.soft && wert.summe >= 13 && wert.summe <= 19) {
    const doppeln = SOFT_DOUBLE_GEGEN[wert.summe]
    if (lage.darfVerdoppeln && doppeln !== undefined && doppeln.includes(dealer)) {
      return 'verdoppeln'
    }
    // Ohne Double: Soft 18 steht gegen 2 bis 8, Soft 19 und 20 immer.
    if (wert.summe === 18) return dealer <= 8 ? 'stehen' : 'ziehen'
    if (wert.summe >= 19) return 'stehen'
    return 'ziehen'
  }
  if (wert.soft && wert.summe >= 20) return 'stehen'

  // --- Harte Haende --------------------------------------------------------
  if (lage.darfVerdoppeln) {
    const doppeln = HART_DOUBLE_GEGEN[wert.summe]
    if (doppeln !== undefined && doppeln.includes(dealer)) return 'verdoppeln'
  }

  if (wert.summe >= 17) return 'stehen'
  if (wert.summe >= 13) return dealer <= 6 ? 'stehen' : 'ziehen'
  if (wert.summe === 12) return dealer >= 4 && dealer <= 6 ? 'stehen' : 'ziehen'
  return 'ziehen'
}

/** Die Dealerkarte, gegen die entschieden wird – die **offene**. */
export function dealerZeigt(dealer: readonly number[]): number {
  const karte = dealer[0]
  if (karte === undefined) throw new Error('Der Dealer hat keine offene Karte.')
  return kartenwert(karte)
}

/** Ob eine Hand als Paar gilt – gleicher Zaehlwert, nicht gleicher Rang. */
export function istPaar(karten: readonly number[]): boolean {
  return karten.length === 2 && kartenwert(karten[0]!) === kartenwert(karten[1]!)
}