Blackjack durchsimulieren?

berndoa

Bekanntes Mitglied
Hallo,
ich würde just for fun gerne die Gewinnwahrscheinlichkeit bei Blackjack ausrechnen.
Ich will mich für den Anfang auf den Fall spezialisieren, bei dem nur eine Person (man selbst) gegen den Dealer spielt.

Ich will Diese Berechnung verwirklichen indem ich zu Beginn eine "Startkonfiguration" vorgebe,
das heißt ich gebe die 2 Karten, die der Spieler zu Beginn hat und die 2 Karten die der Dealer hat, vor
(wie viel Karten wer anfangs kriegt, müsste ich nachgucken. kann auch anders sein)

Und dann gehe ich von einer Konfiguration zur nächsten indem ich gucke:
Ich gucke was bei jeder der möglichen Aktionen passsiert die der Spieler machen kann.
Wobei ich mich hier nur auf die Aktionen halten (also gar nix tun) oder Karte ziehen beschränke.
Ich gucke also bspw. beim Halten, wenn der Spieler eine Karte zieht, wie ist dann die neue Situation.
Der Spieler hat sprichwörtlich nix getan, der Dealer zieht seine 3. karte bzw. deckt Diese auf (ich müsste ehct nochmal die genauen Regeln nachlesen).

Wir befinden uns also in einer neuen Konfiguration (konfiguration gibt an welche (offen liegenden) Karten der Spieler sowie der Dealer zu einem zeitpunkt, nahc einer und vor der nächsten Aktion, haben).

Nun gibt es ja Konfigurationen, bei denen das Spiel direkt rum ist.
bspw. weil man selbst weniger als 21 hat und der Dealer ist über 21 gekommen.
dann hat man gewonnen.
oder umgekehrt, man selbst ist zuerst über 21 gekommen, dann hat man verloren.

Nun will ich gewissermassen rekursiv rükwärts das Ganze anpacken.

sagen wir ich habe eine Konfiguration und entscheide mich für Karte ziehen. Sagen wir, die Situation ist so dass ich je nachzu ziehender Karte entweder verliere oder das Spiel weitergeht.
Dann kann ich ja mit simpler Mathematik sagen:
Mit Wahrshcienlichkeit X wird die Karte Ass gezogen und in dem Fall würde ich das Spiel verlieren/weiter gehen.
Ob Gewinn oder verlust durch die Karte Ass lässt sich ja bestimmen indem ich vond ern Folgekonfiguration aus Weiterspinne wie das Spiel weitergeht, was am Wahrshceinlichsten passiert und was das Endergebnis ist wenn ich stets die beste Aktion wähle.

Um das mit der besten Aktion mal zu verdeutlichen:
Sagen wir ich bin genau 2 Punkte von 21 entfernt.
Heißt, wenn ich eine Karte dazubekäme und Alles ausser eine 1 kommt, verliere ich.
Kommt eine 1, würde ich je nach Dealerlage dann verlieren oder gewinnen.

Heißt, ich kann gucken:
Fall Halten:
heißt, ich mahce gar nix und bin bei 19 punkten.
dealer zieht eine Karte. mit Wahrshceinlichkeitsrechnung, Pfaddiagramm und Co. kann ich easy bestimmen mit welcher wahrshceinlichkeit es eine karte ist
die mir das Genick brehcen würde. bzw. umgekehrt wie wahrshceinlich ein Sieg ist (denn die aktionen des Dealers passieren ja zufällig und ich habe keinen Einfluss darauf. Lediglih wann der Dealer etwas tut oder nicht hängt von meinen Aktionen ab.)
das heißt, ich knnn bei der aktion "halten" bestimmen, wie wahrscheinlich es ist eine folgekonfiguration 8dealer hat eine karte mehr) zu erwischen bei der ich verliere.

diese wahrshceinlichkeit ist dann , ausgehend von meiner aktuellen konfiguration, die wahrshceinlich das Spiel noch zugewinnen, wenn ich in dieser runde halte.

gleichermassen kann ich aber auch hingehen und eine Karte ziehen.
Also der Fall wo der Spieler eine Karte dazu bekommt.
Hier lässt sich mit wahrshceinlichkeit auch bestimmen wie wahrshceinlich ich diese odr jene Karte erhalte.
dann mit Baumdiagramm kann man dann gucken wie wahrshceinlich der delaer diese oder jene karte hat.
und auswerten wie hoch die wahrshceinlichkeit für die gewinnfälle ist.
damit ergibt sich dann eine gewinnwahrshceinlichkeit für den Fall "KArte ziehen".


Nun habe ich also meine aktuelle Konfiguration und weiß, beim Tun welcher meiner beiden mmöglichen Aktionen wie wahrshcienlich ich gewinne.
Die "beste "Aktion in der Aktuellen Situation/Konfiguration ist dann natürlich die, mit der ich eine höhere Gewinnwahrshceinlichkeit habe.

Womit wir wieder zum Grundproblem zurückkommen:
Gegeben eine Startkonfiguration, wenn ich in jeder Runde die optimale Aktion tätige, wie wahrshceinlich ist es dann dass ich das Spiel gewinne?

Gleichermassen ist es natürlich auch zu wissen, falls ich mich inmitten einer fortgescrittenen Konfiguration befinde, wie wahrshceinlich ich das Spiel noch rumreißen kann?


Für das Ganze muss ich sicherlich sehr viel rekursiv programmieren und letztlich Alles Mögliche durchsimulieren und Angaben über Gewinn/erlust und Gewinnwahrshceinlichkeit an die Vorgängerkonfiguration zurückgeben.

Das Ganze ist für michg erade shcon eextrem schwer zu durchschauen was ich da Schritt für Schritt tun muss, geschweigedenn das zu simulieren.


Dass die Anzahl der Karten, die ein Spieler haben kann, nicht fest ist, macht es natürlich auch nicht besser.
Schließlich kann ein Spieler nur 2 Karten haben und wäre schon knapp an den 21 Punkten.
Oder er kann 15 Mal eine "1" ziehen und ist immer noch nicht annähernd an den kritischen 21 Punkten dran.

Insofern kann ich für die Konfiguration nicht einfach ein 2d-Array benutzen, weil ein Array ja feste Länge haben muss.


Habe auch shcon überlegt, irgendwie Objekt der Klasse "Konfiguration" zu benutzen die eben intenr die aktuellen karten als Arrays gespeichert hat und über eine Methode schlicht bestimmt ob man schon verloren hat.

Aber ich weiß nicht ob das zielführend wäre oder nicht.
Ich weiß auch nicht recht wie ich beim Rekursiven Durchgehen die Information über Gewinn/Verlust und deren Eintrittswahrscheinlichkeit am besten weitergebe bis zum Anfang durch :-/
 

M.L.

Top Contributor
Grundsätzlich will die Anbieterseite (von z.B. BlackJack) wohl Geld verdienen, von daher werden die langfristig höheren Gewinnchancen wohl auf ebenderen Seite liegen. Und solange der Dealer nicht mindestens 17 Punkte aufweist, muss er nach Regel noch einmal ziehen, mit As (1 Punkt), 2, 3,4,5 Punkten macht das dann 50% Wahrscheinlichkeit für das Intervall 17 bis 21 (bei den übrigen höheren Werten überzieht der Dealer)
 

Blender3D

Top Contributor
Hallo,
ich würde just for fun gerne die Gewinnwahrscheinlichkeit bei Blackjack ausrechnen.
Ich will mich für den Anfang auf den Fall spezialisieren, bei dem nur eine Person (man selbst) gegen den Dealer spielt.
Die Gewinnwahrscheinlichkeit hängt von den angebotenen Regeln ab. Sie ist in jedem Fall positiv für das Casino.
Bei BJ variieren sie aber auch durch die Art wie der Spieler zieht. Es gibt dafür eine sogenannte Basic Strategie. Hält man sich daran kann man den Bankvorteil auf das mögliche Minimum reduzieren.
https://www.blackjackapprenticeship.com/blackjack-strategy-charts/
Um den Bankvorteil mittels einer Simulation zu berechnen, würde ich eine große Anzahl von Spielen in einer Schleife machen. Der simulierte Spieler zieht nach der Basic Strategie bei konstantem Einsatz. Du berechnest den Umsatz und die Höhe des Verlustes. --> Bankvorteil
 

EinNickname9

Bekanntes Mitglied
Wozu simulieren? Der Erwartungswert für dich ist etwas geringer als für den Dealer und die optimalen Regeln für die beste Wahrscheinlichkeit für dich sind bekannt und fest. Jede andere Spielweise führt für dich zu einem höheren Verlust.

Durchschnittlich verliert man, bei optimaler Spielweise und je nach Regelwerk, 0.5 bis 2 Prozent.

Durch Kartenzählen (Schummeln) lässt sich der Verlust weiter drücken oder sogar zu Gewinn transformieren.

Deshalb verwenden viele Casinos 5 bis 6 Decks.

Ich kenne mich damit aus, weil ich regelmäßig Poker spiele und, wenn an den Tischen nix los ist, zwischendurch Blackjack.
 

EinNickname9

Bekanntes Mitglied
Ja aber der Erwartungswert (ohne Karten zählen) ist ja bekannt. Die Simulation sollte zum gleichen Ergebnis kommen (sofern sie anhält).
 

berndoa

Bekanntes Mitglied
Geht mir ja nicht (nur) durm die Gewinnwahrscheinlichkeit zu ermitteln sondern auch zu bestimmen:
Gegeben diese Konfiguration (die auch mitten aus einem laufenden Spiel sein kann), was wäre der beste nächste Zug?

Sozusagen einen Strategie Ratgeber will ich bauen, in dem Sinne :)
 

berndoa

Bekanntes Mitglied
Ist es so unglaublich dass ich mir nicht einfach das fertige Endprodukt nehmen will sondern einfahc mal das selbst programmiertechnisch nachmachen will?

Mir ist klar dass ich das Alles irgendwo für lau nachlesen könnte.
ABer ich wills halt auch mal programmiert haben, gerade ich die übung gebrauchen könnte und bei etwas größeren programmiersachen ziemlich shcnell auf dem Shclauch stehe wie ich da überhaupt anfangen kann. Einfach weil ich das Gesamtgebilde schon gar nicht mehr überblicken kann.
 

berndoa

Bekanntes Mitglied
Hm, wobei wenn ich es mir so überlege , mache ich es mir womöglich einfahc zu kompliziert:
Ich kann mir ja eine Liste mit allen gültigen Konfigurationen erstellen lassen, also alle Varianten was ein Spieler so haben kann ohne direkt verloren zu haben, was der Dealer so haben kann, was gebenenfalls weitere Spieler so haben können.
Das halt kombiniert zu einer ellenlangen Liste an Konfigurationen.

Dann kann ich ja hingehen, wenn ich bspw. vorgebe "Spieler hat die Karten 1,2,3,4 und Dealer hat 9" und so MySQL mässig alle Konfigurationen raussuchen wo eben di ersten 4 Spieler Karten übereinstimmen sowie die dealerkarte passt und damit rumhantieren.

Wenn ich mich bspw. frage, wie gut kann ich abschneiden wenn ich eine (und damit impliziert "oder mehr") Karten ziehen würde, kann suche ich eben die Konfigurationen raus mit den 4 identischen Spielerkarten UND wo der Spieler mind. 5 Karten hat sowie die passende Dealerkarte.

Und gucke für jede der Konfigurationen:
1. wie wahrshceinlich ist sie? (Also einfach die einzalkartenwahrshceinlichkeiten für spieler und dealer multiplizieren, wobei natürlich die vorgegebenen Karten ausser acht bleiben)
2. gewinnt der Spieler?

Dann eben die eintrittswahrshceinlichkeiten der gewinnfälle addieren und schon weiß ich wie wahrscheinlich ich beim ziehen einer weiteren karte gewinnen kann :)
genauso geht auch halten, eben alle konfigurationen betrachten, wo der spieler genau diese 4 karten hat und keine karte mehr, also genau 4 karten länge beim spieler. auswerten, die wahrscheinlichkeiten der gewinnkonfigurationen addieren, erginbt eine gewinnwahrscheinlichkeit.

vergleichen was von beidem höher ist und dementpsrechend ist eben halten oder ziehen der bessere Zug :)

Nur ein problem was mir gleich mal ins Auge fällt:
Die Unmenge an kombinationen die es zu beachten gibt.
selbst wenn man Bube dame König als eine Karte mit einer 4/12 oder so wharshceinlichkeit ntuzt, sind es je nachdem immer nopch 9^7 oder so kombinationen, jetzt mal ins blaue geraten.

Jedenfalls sehr viel sodass ich da locker über alle Zahlenberiche wie int oder long drüber komme :-/


Kurzum, die Liste würde viel zu lang werden, von Laufzeiten ganz zu schweigen :-/

Wobei man sie ja auch bei lsitenerstellung gleich um die Konfigurationen bereinigen könnte, wo bspw. der spieler zwar unter 21 ist (also Alles offen noch ist) aber der Dealer auf eine größere Zahl über 21 kommt und man daher verliert.
Also letztlich wirklich nur Konfigurationen auflsiten, in denen der Spieler auch gewinnt.
Und mit dieser verbesserten Lsite dann obige Suchoperationen machen :)
 

NullCharm

Aktives Mitglied
Hi @berndoa , ich hab das jetzt mal durchgerechnet:

Java:
public class BlackJack {
    private final KarteWahrscheinlichkeit[] kw = {
            new KarteWahrscheinlichkeit(5, 1.0 / 7.0),
            new KarteWahrscheinlichkeit(6, 1.0 / 7.0),
            new KarteWahrscheinlichkeit(7, 1.0 / 7.0),
            new KarteWahrscheinlichkeit(8, 1.0 / 7.0),
            new KarteWahrscheinlichkeit(9, 1.0 / 7.0),
            new KarteWahrscheinlichkeit(10, 1.0 / 7.0),
            new KarteWahrscheinlichkeit(11, 1.0 / 7.0)
    };

    private int wins = 0;
    private int losses = 0;

    public void calc(int summeBank, int summeSpieler, boolean spielerStop, int spielerZiehtBis) {
        if (summeBank == 0) {
            System.out.println("Bank muss ziehen: " + summeBank);
            for (KarteWahrscheinlichkeit k : kw) {
                calc(summeBank + k.wert, summeSpieler, false, spielerZiehtBis);
            }
        } else if (!spielerStop) {
            if (summeSpieler <= spielerZiehtBis) {
                System.out.println("Spieler zieht: " + summeSpieler);
                for (KarteWahrscheinlichkeit k : kw) {
                    calc(summeBank, summeSpieler + k.wert, false, spielerZiehtBis);
                }
            } else {
                System.out.println("Spieler stop: " + summeSpieler);
                calc(summeBank, summeSpieler, true, spielerZiehtBis);
            }
        } else if (summeBank <= 13) {
            System.out.println("Bank muss ziehen: " + summeBank);
            for (KarteWahrscheinlichkeit k : kw) {
                calc(summeBank + k.wert, summeSpieler, true, spielerZiehtBis);
            }
        } else {
            if (summeSpieler > 21) {
                System.out.println("Bank gewinnt, Spieler hat sich überkauft");
                losses++;
            } else if (summeBank > 21) {
                System.out.println("Spieler gewinnt, Bank hat sich überkauft, Spieler nicht");
                wins++;
            } else if (summeBank < summeSpieler) {
                System.out.println("Spieler gewinnt");
                wins++;
            } else if (summeBank == summeSpieler) {
                System.out.println("Unentschieden!");
                wins++;
                losses++;
            } else {
                System.out.println("Bank gewinnt");
                losses++;
            }
        }
    }

    @Override
    public String toString() {
        return "wins=" + wins + ", losses=" + losses;
    }

    public static void main(String[] args) {
        BlackJack bj = new BlackJack();
        bj.calc(0, 0, false, 12);
        System.out.println("bj = " + bj);
        System.out.println((float) bj.wins / (bj.wins + bj.losses) * 100 + " %");
    }
}

class KarteWahrscheinlichkeit {
    int wert;
    double wahrscheinlichkeit;

    public KarteWahrscheinlichkeit(int wert, double wahrscheinlichkeit) {
        this.wert = wert;
        this.wahrscheinlichkeit = wahrscheinlichkeit;
    }
}

Aber mit ZWEI Vereinfachungen:

- Es gibt nur Karten mit Werten von 5 bis 11.
- Jede Karte hat die gleiche Wahrscheinlichkeit, gezogen zu werden (1/7). Es gibt also unendlich viele Decks.
- Die Bank steht bei >13, der Spieler steht bei >12.

Der Erwartungswert beträgt unter diesen Regeln: 48.41105 %

Die Ausgaben sind für dich als orientierung gedacht.
 

NullCharm

Aktives Mitglied
Hier sieht man auch schön, warum der Erwartungswert unter 50% ist, wenn doch der Spieler den Vorteil hat, und die erste Karte des Dealers sehen kann... Wenn sich der Spieler überkauft, ist er sofort ausgeschieden. Wenn sich beide überkaufen würden, gewinnt also der Dealer...

Das mag nicht oft vorkommen, aber wenn du am Abend 100 Spiele spielst mit je 5€ Einsatz, dann läppert es sich schon...
 

NullCharm

Aktives Mitglied
@berndoa So könnte man es nach den europäischen Regeln durchrechnen:

Java:
import java.util.LinkedList;

public class BlackJack {
    private final int[] karten = {2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 11};

    private long wins = 0;
    private long losses = 0;

    public void calc(Verlauf bank, Verlauf spieler, boolean spielerStop) {
        if (bank.summe(true) == 0) {
            for (int k : karten) {
                calc(bank.newVerlauf(k), spieler, false);
            }
        } else if (!spielerStop) {
            if (spieler.summe(true) >= 19) {
                calc(bank, spieler, true);
            } else if (spieler.count() >= 3) {
                if (spieler.summe(true) == 18 && bank.summe(true) < 9) {
                    calc(bank, spieler, true);
                } else {
                    for (int k : karten) {
                        calc(bank, spieler.newVerlauf(k), false);
                    }
                }
            } else if (bank.summe(true) < 4 && spieler.summe(false) >= 13
                    || bank.summe(true) < 7 && spieler.summe(false) >= 12
                    || bank.summe(true) >= 7 && spieler.summe(false) >= 17) {
                calc(bank, spieler, true);
            } else {
                for (int k : karten) {
                    calc(bank, spieler.newVerlauf(k), false);
                }
            }
        } else if (bank.summe(true) != 21 && bank.summe(true) < 17) {
            for (int k : karten) {
                calc(bank.newVerlauf(k), spieler, true);
            }
        } else {
            // System.out.println("bank = " + bank);
            // System.out.println("spieler = " + spieler);
            if (spieler.summe(false) > 21) {
                // System.out.println("Bank gewinnt, Spieler hat sich überkauft");
                losses++;
            } else if (bank.summe(false) > 21) {
                // System.out.println("Spieler gewinnt, Bank hat sich überkauft, Spieler nicht");
                wins++;
            } else {
                int bankSum;
                if (bank.summe(true) <= 21) {
                    bankSum = bank.summe(true);
                } else {
                    bankSum = bank.summe(false);
                }
                int spielerSum;
                if (spieler.summe(true) <= 21) {
                    spielerSum = spieler.summe(true);
                } else {
                    spielerSum = spieler.summe(false);
                }
                if (bankSum < spielerSum) {
                    // System.out.println("Spieler gewinnt");
                    wins++;
                } else if (bankSum == spielerSum) {
                    // System.out.println("Unentschieden!");
                    wins++;
                    losses++;
                } else {
                    // System.out.println("Bank gewinnt");
                    losses++;
                }
            }
        }
    }

    @Override
    public String toString() {
        return "wins=" + wins + ", losses=" + losses + ", expected=" + (float) ((double) wins / (wins + losses) * 100) + " %";
    }

    public static void main(String[] args) {
        BlackJack bj = new BlackJack();
        bj.calc(new Verlauf(), new Verlauf(), false);
        System.out.println("bj = " + bj);
    }
}

class Verlauf {
    LinkedList<Integer> v;

    public Verlauf() {
        v = new LinkedList<>();
    }

    public Verlauf(Verlauf verlauf, int karte) {
        this.v = new LinkedList<>(verlauf.v);
        this.v.add(karte);
    }

    public Verlauf newVerlauf(int karte) {
        return new Verlauf(this, karte);
    }

    public int count() {
        return v.size();
    }

    public int summe(boolean soft) {
        if (soft) {
            return v.stream().mapToInt(Integer::intValue).sum();
        } else {
            return v.stream().mapToInt(Integer::intValue).map(i -> i == 11 ? 1 : i).sum();
        }
    }

    @Override
    public String toString() {
        return v.toString();
    }
}

Ich hab aber gerade nicht die Rechenpower, um das zu berechnen, und es könnte auch sein, dass es weiter vereinfacht werden könnte...

Ich gehe wieder von unendlich vielen Decks aus... Split, Double Down und Insurance habe ich weggelassen (Insurance macht wie im echten Leben nie Sinn). Und man müsste im } else { Teil noch auf Blackjack prüfen. Hat der Spieler Blackjack, so erhält er ja insgesamt den 2,5-fachen Einsatz zurück... Also Beispiel: bei 10€ wären das 25€

Dann sollte der bei Wikipedia angegebene prozentuale Bankvorteil herauskommen. :)
 

NullCharm

Aktives Mitglied
Hier nochmal überarbeitet und etwas schöner...

Java:
import java.util.LinkedList;

public class BlackJack {
    private final int[] karten = {2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11};

    private long wins = 0;
    private long losses = 0;

    public void calc(Verlauf bank, Verlauf spieler, boolean spielerStop) {
        if (bank.summe() == 0) {
            for (int k : karten) {
                calc(bank.newVerlauf(k), spieler, false);
            }
        } else if (!spielerStop) {
            if (       spieler.summe() >= 19
                    || spieler.summe() == 18 && bank.summe() < 9
                    || bank.summe() < 4 && spieler.summe() >= 13
                    || bank.summe() < 7 && spieler.summe() >= 12
                    || bank.summe() >= 7 && spieler.summe() >= 17) {
                calc(bank, spieler, true);
            } else {
                for (int k : karten) {
                    calc(bank, spieler.newVerlauf(k), false);
                }
            }
        } else if (bank.summe() < 17) {
            for (int k : karten) {
                calc(bank.newVerlauf(k), spieler, true);
            }
        } else {
            // System.out.println("bank = " + bank);
            // System.out.println("spieler = " + spieler);
            if (spieler.summe() > 21) {
                // System.out.println("Bank gewinnt, Spieler hat sich überkauft");
                losses++;
            } else if (bank.summe() > 21) {
                // System.out.println("Spieler gewinnt, Bank hat sich überkauft, Spieler nicht");
                wins++;
            } else if (bank.summe() < spieler.summe()) {
                // System.out.println("Spieler gewinnt");
                wins++;
            } else if (bank.summe() == spieler.summe()) {
                // System.out.println("Unentschieden!");
                wins++;
                losses++;
            } else {
                // System.out.println("Bank gewinnt");
                losses++;
            }
        }
    }

    @Override
    public String toString() {
        return "wins=" + wins + ", losses=" + losses + ", expected=" + (float) ((double) wins / (wins + losses) * 100) + " %";
    }

    public static void main(String[] args) {
        BlackJack bj = new BlackJack();
        bj.calc(new Verlauf(), new Verlauf(), false);
        System.out.println("bj = " + bj);
    }
}

class Verlauf {
    LinkedList<Integer> v;

    public Verlauf() {
        v = new LinkedList<>();
    }

    public Verlauf(Verlauf verlauf, int karte) {
        this.v = new LinkedList<>(verlauf.v);
        this.v.add(karte);
    }

    public Verlauf newVerlauf(int karte) {
        return new Verlauf(this, karte);
    }

    public int count() {
        return v.size();
    }

    public boolean hasAce() {
        return v.contains(11);
    }

    public boolean isHard() {
        return count() >= 3 && hasAce() && v.stream().mapToInt(Integer::intValue).sum() > 21;
    }

    public int summe() {
        if (isHard()) {
            int aces = (int) v.stream().filter(i -> i == 11).count();
            int sum = v.stream().mapToInt(Integer::intValue).sum();
            while (aces > 1 && sum > 21) {
                aces--;
                sum -= 10;
            }
            return sum;
        } else {
            return v.stream().mapToInt(Integer::intValue).sum();
        }
    }

    @Override
    public String toString() {
        return v.toString();
    }
}

Code:
bj = wins=37766829, losses=40791086, expected=48.07514 %

Jetzt fehlt natürlich noch Blackjack (der Erwartungswert müsste noch etwas höher sein)... Dafür müsstest du bei Gewinn 4 hinzufügen und bei Blackjack 5 (bei Unentschieden 2).

Bei Fragen einfach fragen
 

Oben