/******************************************************************************
 ** $Id: Klassisch.java 1180 2017-04-14 23:53:55Z wmh $
 ** Diese Datei ist Bestandteil der Java-Quelltexte des Würfelspiels JaFuffy.
 ** Lauffähig ab Java 7.
 ******************************************************************************
 ** Copyright (C) Wolfgang Hauck <wolfgang.hauck@3kelvin.de>
 ******************************************************************************
 ** This program is free software: you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
 ** the Free Software Foundation, either version 3 of the License, or
 ** (at your option) any later version.
 **
 ** This program is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ** GNU General Public License for more details.
 **
 ** You should have received a copy of the GNU General Public License
 ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************
 ** Die aktuellste Version von JaFuffy findet sich im Internet unter
 ** <http://jafuffy.3kelvin.de>.
 **
 ** Kommentare, Fehler oder Erweiterungswünsche bitte per E-Mail senden an
 ** <jafuffy@3kelvin.de>.
 ******************************************************************************/
package jafuffy.logik.analyse;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import jafuffy.logik.Analyse;
import jafuffy.logik.Kombinatorik;
import jafuffy.logik.Spieler;
import jafuffy.logik.Tabzeile;
import jafuffy.logik.Turnier;
import jafuffy.logik.Wuerfel;

/** Erstellt Analysen und berechnet Prognosen für die klassische Variante der Spielregeln. */
public class Klassisch extends Analyse {

    /** Kleiner Pasch. */
    class Dreierpasch extends Pasch {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = 1347995442616468479L;
        /** Anzahl der gleichen Würfel für einen kleinen Pasch. */
        private static final int PASCHGROESSE = 3;

        /** Konstruktor. */
        Dreierpasch() {
            super(PASCHGROESSE);
        }

        @Override
        protected double augensumme(int a, int r, int k) {
            if (k != 2) {
                throw new IllegalArgumentException("Falsche Mehrlingslänge");
            }
            if (r > 0) {
                double augensumme;
                augensumme = 4 * (a + 4) * augensumme(a, r - 1, 2) + 66 * Kombinatorik.augenzahl(r)
                        - 2 * Kombinatorik.augenzahl(a, r - 1) + 90 * a + 21;
                for (int alpha = a + 1; alpha <= 6; alpha++) {
                    augensumme += 4 * augensumme(alpha, r - 1, 2);
                }
                augensumme *= 3. / 216.;
                return augensumme;
            } else {
                return 0;
            }
        }

        @Override
        protected boolean pasch() {
            return paschkarte.get(Paschaufzaehlung.DREIERPASCH) || paschkarte.get(Paschaufzaehlung.VIERERPASCH)
                    || paschkarte.get(Paschaufzaehlung.FUENFERPASCH);
        }

    }

    /** Großer Pasch. */
    class Viererpasch extends Pasch {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = -3378721831288162486L;
        /** Anzahl der gleichen Würfel für einen großen Pasch. */
        private static final int PASCHGROESSE = 4;

        /** Konstruktor. */
        Viererpasch() {
            super(PASCHGROESSE);
        }

        @Override
        protected double augensumme(int a, int r, int k) {
            switch (k) {
            case 2:
                return augensumme2(a, r);
            case 3:
                return augensumme3(a, r);
            default:
                throw new IllegalArgumentException("Falsche Mehrlingslänge");
            }
        }

        /**
         * @param a
         *            Fest vorgegebene Augenzahl für einen Zwilling, wobei bei gleich langen Sequenzen diejenige mit der
         *            größeren Augenzahl zu wählen ist.
         * @param r
         *            Anzahl der Restwürfe.
         */
        protected double augensumme2(int a, int r) {
            if (r > 0) {
                double augensumme;
                augensumme = (48 + 12 * a) * augensumme2(a, r - 1) + 75 * augensumme3(a, r - 1)
                        + 18 * Kombinatorik.augenzahl(r) - 2 * Kombinatorik.augenzahl(a, r - 1) + 64 * a;
                for (int alpha = 0; alpha < a; alpha++) {
                    augensumme += augensumme3(alpha, r - 1);
                }
                for (int alpha = a + 1; alpha <= 6; alpha++) {
                    augensumme += 12 * augensumme2(alpha, r - 1);
                    augensumme += augensumme3(alpha, r - 1);
                }
                augensumme /= 216;
                return augensumme;
            } else {
                return 0;
            }
        }

        /**
         * @param a
         *            Fest vorgegebene Augenzahl für einen Drilling, wobei bei gleich langen Sequenzen diejenige mit der
         *            größeren Augenzahl zu wählen ist.
         * @param r
         *            Anzahl der Restwürfe.
         */
        protected double augensumme3(int a, int r) {
            if (r > 0) {
                double augensumme;
                augensumme = 25 * augensumme3(a, r - 1) + 44 * a + 12 * Kombinatorik.augenzahl(r)
                        - Kombinatorik.augenzahl(a, r - 1);
                augensumme /= 36;
                return augensumme;
            } else {
                return 0;
            }
        }

        @Override
        protected boolean pasch() {
            return paschkarte.get(Paschaufzaehlung.VIERERPASCH) || paschkarte.get(Paschaufzaehlung.FUENFERPASCH);
        }

    }

    /** Full House. */
    class FullHouse extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = -1069152277729232203L;
        /** Punktzahlen (Full House). */
        static final int PUNKTE = 25;
        /** Wahrscheinlichkeit für 0, 1, ..., 5 fehlende Würfel auf Full House in einem Wurf mit allen Würfeln. */
        private final double[] restwahrscheinlichkeit = { 17. / 432., 25. / 108., 25. / 144., 25. / 54., 0., 5. / 54. };
        /** Anzahl der Würfel, die zum Full House fehlen. */
        private int fehlend;

        /** Konstruktor. */
        FullHouse() {
            mittel = prognose(Turnier.WUERFEL, Turnier.RUNDEN);
        }

        /** @return Liefert zurück, ob ein Full House vorliegt, wobei ein JaFuffy als Full House betrachtet wird. */
        private boolean istFullHouse() {
            return paschkarte.get(Paschaufzaehlung.ZWEIERPASCH) && paschkarte.get(Paschaufzaehlung.DREIERPASCH)
                    || paschkarte.get(Paschaufzaehlung.FUENFERPASCH);
        }

        /**
         * @param k
         *            Anzahl der zu Full House fehlenden Würfel
         * @param r
         *            Anzahl der verbleibenden Restwürfe (0, ..., 3)
         * @return mittlere erreichbare Punktzahl
         */
        private double prognose(int k, int r) {
            return PUNKTE * restwahrscheinlichkeit(k, r);
        }

        /**
         * @param k
         *            Anzahl der zu Full House fehlenden Würfel
         * @param r
         *            Anzahl der verbleibenden Restwürfe (0, ..., 3)
         * @return Wahrscheinlichkeit der Ergänzung zu Full House
         */
        private double restwahrscheinlichkeit(int k, int r) {
            switch (k) {
            case 0:
                return 1;
            case 1:
                return 1 - Math.pow(2. / 3., r);
            case 2:
                return 1 - Math.pow(Kombinatorik.AUSLASSUNGSWAHRSCHEINLICHKEIT, r);
            case 3:
                switch (r) {
                case 1:
                    return 7. / 72.;
                case 2:
                    return 89. / 324.;
                default:
                    return 0;
                }
            case 5:
                if (r == 0) {
                    return 0;
                }
                double p = 0;
                for (int i = 0; i <= Turnier.WUERFEL; i++) {
                    p += restwahrscheinlichkeit[i] * restwahrscheinlichkeit(i, r - 1);
                }
                return p;
            default:
                return 0;
            }
        }

        @Override
        protected double prognose(Spieler spieler, Zuwachs zuwachs) {
            return prognose(fehlend, spieler.rest());
        }

        @Override
        protected void bewerte(Spieler spieler) {
            wert = istFullHouse() ? PUNKTE : 0;
        }

        @Override
        protected void waehle(Spieler spieler) {
            if (istFullHouse()) {
                // Full House liegt vor
                fehlend = 0;
            } else if (paschkarte.get(Paschaufzaehlung.DREIERPASCH) || paschkarte.get(Paschaufzaehlung.VIERERPASCH)) {
                // mindestens drei gleiche Augenzahlen liegen vor, behalte Drilling
                int zaehler = 0;
                for (Wuerfel w : wuerfel) {
                    neuwurfkarte.put(w, w.vorkommen(augenhaeufigkeiten) < Paschaufzaehlung.DREIERPASCH.groesse()
                            || zaehler == Paschaufzaehlung.DREIERPASCH.groesse());
                    if (w.vorkommen(augenhaeufigkeiten) >= Paschaufzaehlung.DREIERPASCH.groesse()) {
                        zaehler++;
                    }
                }
                fehlend = 2;
            } else if (paschkarte.get(Paschaufzaehlung.ZWEIERPASCH)) {
                // ein Zwilling oder aber zwei Zwillinge mit unterschiedlicher Augenzahl
                fehlend = 0;
                for (int haeufigkeit : augenhaeufigkeiten) {
                    if (haeufigkeit == Paschaufzaehlung.ZWEIERPASCH.groesse()) {
                        if (fehlend == 0) { // erstes Paar gefunden
                            fehlend = 3;
                        } else { // es gibt ein zweites Paar
                            fehlend = 1;
                        }
                    }
                }
                for (Wuerfel w : wuerfel) {
                    neuwurfkarte.put(w, w.vorkommen(augenhaeufigkeiten) < Paschaufzaehlung.ZWEIERPASCH.groesse());
                }
            } else {
                // alle Würfel verschieden
                nominiereNeuwurf(true);
                fehlend = Turnier.WUERFEL;
            }
        }
    }

    /** Aufzählung der Straßenteilpaare {1, 2}, {2, 5} oder {5, 6} sowie {3, 4}. */
    private enum Strassenpaar {
        /** Linkes Straßenpaar {1, 2}. */
        LINKS(Index.EINSER, Index.ZWEIER),
        /** Inneres Straßenpaar {2, 5}. */
        INNEN(Index.ZWEIER, Index.FUENFER),
        /** Rechtes Straßenpaar {5, 6}. */
        RECHTS(Index.FUENFER, Index.SECHSER),
        /** Mittleres Straßenpaar {3, 4}. */
        MITTE(Index.DREIER, Index.VIERER);
        /** Beschreibt das Teilpaar durch seine Augenzahl (weniger eins). */
        private final Index[] feld = new Index[2];

        /**
         * Konstruktor.
         *
         * @param paar0
         *            erste Augenzahl weniger Eins
         * @param paar1
         *            zweite Augenzahl weniger Eins
         */
        Strassenpaar(Index paar0, Index paar1) {
            feld[0] = paar0;
            feld[1] = paar1;
        }

        /** @return Feld, welches das Teilpaar beschreibt. */
        final Index[] feld() {
            return feld;
        }

        /**
         * Ermittle, wie viele Würfel des Straßenteilpaars im Wurf vorkommen.
         *
         * @param augenhaeufigkeiten
         *            Feld mit den Anzahlen der geworfenen Augen
         * @return Anzahl der Würfel vom Straßenteilpaar, die im Wurf vorkommen.
         */
        int vorkommen(int[] augenhaeufigkeiten) {
            int gefunden = 0;
            for (Index element : feld) {
                if (element.vorkommen(augenhaeufigkeiten) > 0) {
                    gefunden++;
                }
            }
            return gefunden;
        }
    }

    /**
     * Stochastische Hilfsklasse, welche bedingte Wahrscheinlichkeiten für die kleine Straße enthält.
     */
    private static class KleineStrasseIndex implements Serializable {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = -6498075827504305923L;

        /**
         * Tabelle für Übergangswahrscheinlichkeiten für die kleine Straße, indiziert durch (aussen, innen, mitte,
         * rest).
         */
        static final Map<KleineStrasseIndex, Double> TABELLE = new HashMap<>();

        /** Belegung der Tabelle mit vorberechneten Werten mit Float-Werten. */
        static {
            tabelliere(0, 0, 1, 1, 2.129630e-01);
            tabelliere(0, 0, 1, 2, 4.891690e-01);
            tabelliere(0, 0, 2, 1, 3.611111e-01);
            tabelliere(0, 0, 2, 2, 6.584362e-01);
            tabelliere(0, 1, 0, 1, 1.481481e-01);
            tabelliere(0, 1, 0, 2, 3.912751e-01);
            tabelliere(0, 1, 1, 1, 2.500000e-01);
            tabelliere(0, 1, 1, 2, 5.150463e-01);
            tabelliere(0, 1, 2, 1, 5.555556e-01);
            tabelliere(0, 1, 2, 2, 8.024691e-01);
            tabelliere(0, 2, 0, 1, 1.388889e-01);
            tabelliere(0, 2, 0, 2, 3.526235e-01);
            tabelliere(0, 2, 1, 1, 3.055556e-01);
            tabelliere(0, 2, 1, 2, 5.177469e-01);
            tabelliere(1, 0, 0, 1, 1.018519e-01);
            tabelliere(1, 0, 0, 2, 3.410851e-01);
            tabelliere(1, 0, 1, 1, 1.666667e-01);
            tabelliere(1, 0, 1, 2, 4.293981e-01);
            tabelliere(1, 0, 2, 1, 3.611111e-01);
            tabelliere(1, 0, 2, 2, 6.296296e-01);
            tabelliere(1, 1, 0, 1, 1.388889e-01);
            tabelliere(1, 1, 0, 2, 3.526235e-01);
            tabelliere(1, 1, 1, 1, 3.055556e-01);
            tabelliere(1, 1, 1, 2, 5.177469e-01);
        }

        /**
         * Trägt Schlüssel und Wert in Tabelle ein.
         *
         * @param aussen
         *            Schlüsselanteil.
         * @param innen
         *            Schlüsselanteil.
         * @param mitte
         *            Schlüsselanteil.
         * @param rest
         *            Schlüsselanteil.
         * @param p
         *            Wert.
         */
        private static void tabelliere(int aussen, int innen, int mitte, int rest, double p) {
            TABELLE.put(new KleineStrasseIndex(aussen, innen, mitte, rest), p);
        }

        /** Anzahl der außen liegenden günstigen Augen (1 oder 6). */
        private final int aussen;
        /** Anzahl der innen liegenden günstigen Augen (2 oder 5). */
        private final int innen;
        /** Anzahl der mittig liegenden günstigen Augen (3 oder 4). */
        private final int mitte;

        /** Anzahl der restlichen noch möglichen Würfe. */
        private final int rest;

        /**
         * Konstruiert einen Index, welcher als Schlüssel für die Tabelle benutzt wird.
         *
         * @param aussen
         *            Anzahl der außen liegenden günstigen Augen (1 oder 6).
         * @param innen
         *            Anzahl der innen liegenden günstigen Augen (2 oder 5).
         * @param mitte
         *            Anzahl der mittig liegenden günstigen Augen (3 oder 4).
         * @param rest
         *            Anzahl der restlichen noch möglichen Würfe.
         */
        KleineStrasseIndex(int aussen, int innen, int mitte, int rest) {
            this.aussen = aussen;
            this.innen = innen;
            this.mitte = mitte;
            this.rest = rest;
        }

        @Override
        public boolean equals(Object obj) {
            KleineStrasseIndex index = (KleineStrasseIndex) obj;
            return aussen == index.aussen && innen == index.innen && mitte == index.mitte && rest == index.rest;
        }

        @Override
        public int hashCode() {
            // [a][b][c][d] ([2][3][3][3]): dcbi+dcj+dk+l=d(cbi+cj+k)+l=d(c(bi+j)+k)+l
            return 3 * (3 * (3 * aussen + innen) + mitte) + rest;
        }
    }

    /** Kleine Straße. */
    public class KleineStrasse extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = 3736058022544823661L;
        /**
         * Die Wahrscheinlichkeit der vorliegenden Strategie in drei Würfen eine kleine Straße zu würfeln.
         */
        private static final double P_TOT = 0.6025279;
        /** Punktzahlen (kleine Straße). */
        public static final int PUNKTE = 30;
        /** Anzahl der außen liegenden günstigen Augen (1 oder 6). */
        private int aussen;
        /** Anzahl der innen liegenden günstigen Augen (2 oder 5). */
        private int innen;
        /** Anzahl der mittig liegenden günstigen Augen (3 oder 4). */
        private int mitte;

        /** Konstruktor. */
        KleineStrasse() {
            mittel = P_TOT * PUNKTE;
        }

        /**
         * Wähle Würfel so aus, dass ein Straßenteilpaar behalten wird (sofern die Paarteile im Wurf enthalten sind).
         *
         * @param paar
         *            Das zu beibehaltende Straßenteilpaar.
         */
        private void behalte(Strassenpaar paar) {
            for (Index element : paar.feld()) {
                if (element.vorkommen(augenhaeufigkeiten) > 0) { // Kommt Augenzahl im Wurf vor?
                    for (Wuerfel w : wuerfel) {
                        if (w.augen() == element.augen()) {
                            neuwurfkarte.put(w, false);
                            break;
                        }
                    }
                }
            }
        }

        @Override
        protected void bewerte(Spieler spieler) {
            wert = liegtKleineStrasseVor() ? PUNKTE : 0;
        }

        /** @return Gibt an, ob der Wurf als kleine Straße vorliegt. */
        protected boolean liegtKleineStrasseVor() {
            return Index.DREIER.vorkommen(augenhaeufigkeiten) > 0 && Index.VIERER.vorkommen(augenhaeufigkeiten) > 0
                    && (Index.EINSER.vorkommen(augenhaeufigkeiten) > 0 && Index.ZWEIER.vorkommen(augenhaeufigkeiten) > 0
                            || Index.ZWEIER.vorkommen(augenhaeufigkeiten) > 0
                                    && Index.FUENFER.vorkommen(augenhaeufigkeiten) > 0
                            || Index.FUENFER.vorkommen(augenhaeufigkeiten) > 0
                                    && Index.SECHSER.vorkommen(augenhaeufigkeiten) > 0);
        }

        @Override
        protected double prognose(Spieler spieler, Zuwachs zuwachs) {
            if (liegtKleineStrasseVor()) {
                return PUNKTE;
            } else {
                return KleineStrasseIndex.TABELLE.get(new KleineStrasseIndex(aussen, innen, mitte, spieler.rest()))
                        * PUNKTE;
            }
        }

        @Override
        protected void waehle(Spieler spieler) {
            waehle(false);
        }

        /**
         * Bereitet die Würfelauswahl vor.
         *
         * @param fertig
         *            Bestimmt, ob bei Erreichen der kleine Straße mit dem Würfeln aufgehört werden soll.
         */
        protected void waehle(boolean fertig) {
            nominiereNeuwurf(!fertig);
            if (!fertig) {
                aussen = 0;
                innen = Strassenpaar.INNEN.vorkommen(augenhaeufigkeiten);
                mitte = Strassenpaar.MITTE.vorkommen(augenhaeufigkeiten);
                int links = Strassenpaar.LINKS.vorkommen(augenhaeufigkeiten);
                int rechts = Strassenpaar.RECHTS.vorkommen(augenhaeufigkeiten);
                if (innen == 2) {
                    behalte(Strassenpaar.INNEN);
                } else if (innen == 1 && links <= 1 && rechts <= 1) {
                    behalte(Strassenpaar.INNEN);
                } else {
                    if (links > rechts) {
                        aussen = links - innen;
                        behalte(Strassenpaar.LINKS);
                    } else if (links < rechts) {
                        aussen = rechts - innen;
                        behalte(Strassenpaar.RECHTS);
                    } else {
                        aussen = links - innen;
                        behalte(ZUFALL.nextBoolean() ? Strassenpaar.LINKS : Strassenpaar.RECHTS);
                    }
                }
                behalte(Strassenpaar.MITTE);
            }
        }
    }

    /** Große Straße. */
    public class GrosseStrasse extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = 8292592862416663732L;
        /** Punktzahlen (große Straße). */
        public static final int PUNKTE = 40;
        /** Anzahl der Würfel je Augenzahl. */
        private final int[] kandidat = new int[Wuerfel.MAXIMALAUGENAUGENZAHL];
        /** Anzahl der fehlenden Würfel zu einer große Straße. */
        private int fehlend;

        /** Konstruktor. */
        GrosseStrasse() {
            mittel = PUNKTE * q(Turnier.WUERFEL, Turnier.RUNDEN);
        }

        /**
         * Wahrscheinlichkeit zur großen Straße, falls 1 oder 6 schon erwürfelt wurde.
         *
         * @param n
         *            Anzahl der fehlenden Würfel zur großen Straße
         * @param r
         *            Anzahl der noch möglichen Restwürfe
         *
         * @return Wahrscheinlichkeit
         */
        private double p(int n, int r) {
            if (r > 1) {
                double summe = 0;
                for (int i = 0; i <= n; i++) {
                    summe += u(n, i) * p(n - i, r - 1);
                }
                return summe;
            } else {
                return Kombinatorik.fakultaet(n) / Math.pow(Wuerfel.MAXIMALAUGENAUGENZAHL, n);
            }
        }

        /**
         * Wahrscheinlichkeit zur großen Straße, falls 1 oder 6 noch nicht erwürfelt wurde.
         *
         * @param n
         *            Anzahl der fehlenden Würfel zur großen Straße
         * @param r
         *            Anzahl der noch möglichen Restwürfe
         *
         * @return Wahrscheinlichkeit
         */
        private double q(int n, int r) {
            if (r > 1) {
                double summe = 0;
                for (int i = 0; i <= n; i++) {
                    summe += w(n, i) * p(n - i, r - 1) + v(n, i) * q(n - i, r - 1);
                }
                return summe;
            } else if (n > 0) {
                return 2 * Kombinatorik.fakultaet(n) / Math.pow(Wuerfel.MAXIMALAUGENAUGENZAHL, n);
            } else {
                return 0;
            }
        }

        /**
         * Wahrscheinlichkeit, genau i unterschiedliche Augen aus n vorgegebenen Augen mit n Würfeln zu erwürfeln. Die
         * nicht vorgegebenen Augen dürfen beliebig oft auftauchen.
         *
         * @param n
         *            Anzahl der Würfel
         * @param i
         *            Anzahl der unterschiedlichen Augen
         *
         * @return Wahrscheinlichkeit
         */
        private double u(int n, int i) {
            return Kombinatorik.strassenteilsequenz(n, 6 - n, i, n) / Math.pow(Wuerfel.MAXIMALAUGENAUGENZAHL, n);
        }

        /**
         * Wahrscheinlichkeit, mit n Würfeln genau i unterschiedliche Augen aus n erlaubten Augen zu erwürfeln, wobei
         * weder 1 noch 6 vorkommen darf. Die restlichen Würfel dürfen alle allen anderen Augen aufweisen, wieder
         * abgesehen von 1 und 6.
         *
         * @param n
         *            Anzahl der Würfel
         * @param i
         *            Anzahl der unterschiedlichen Augen (ohne 1 und 6)
         *
         * @return Wahrscheinlichkeit
         */
        private double v(int n, int i) {
            return Kombinatorik.strassenteilsequenz(n, 5 - n, i, n - 1) / Math.pow(Wuerfel.MAXIMALAUGENAUGENZAHL, n);
        }

        /**
         * Wahrscheinlichkeit, genau i unterschiedliche Augen aus n-1 erlaubten Augen mit n Würfeln zu erwürfeln, wobei
         * zusätzlich 1 oder 6 vorkommen muss. Alle anderen Augen dürfen beliebig oft auftauchen.
         *
         * @param n
         *            Anzahl der Würfel
         * @param i
         *            Anzahl der unterschiedlichen Augen
         *
         * @return Wahrscheinlichkeit
         */
        private double w(int n, int i) {
            return i * Kombinatorik.fallend(n - 1, i - 1)
                    * (2 * Kombinatorik.strassenteilschablone(n, 5 - n, i)
                            + (i + 1) * Kombinatorik.strassenteilschablone(n, 5 - n, i + 1))
                    / Math.pow(Wuerfel.MAXIMALAUGENAUGENZAHL, n);
        }

        @Override
        protected void bewerte(Spieler spieler) {
            if (Index.ZWEIER.vorkommen(augenhaeufigkeiten) == 1 && Index.DREIER.vorkommen(augenhaeufigkeiten) == 1
                    && Index.VIERER.vorkommen(augenhaeufigkeiten) == 1
                    && Index.FUENFER.vorkommen(augenhaeufigkeiten) == 1
                    && (Index.EINSER.vorkommen(augenhaeufigkeiten) == 1
                            || Index.SECHSER.vorkommen(augenhaeufigkeiten) == 1)) {
                wert = PUNKTE;
            } else {
                wert = 0;
            }
        }

        @Override
        protected double prognose(Spieler spieler, Zuwachs zuwachs) {
            if (Index.EINSER.vorkommen(augenhaeufigkeiten) > 0 || Index.SECHSER.vorkommen(augenhaeufigkeiten) > 0) {
                return PUNKTE * p(fehlend, spieler.rest()); // 1 oder 6 wurde schon erwürfelt
            } else {
                return PUNKTE * q(fehlend, spieler.rest()); // 1 oder 6 noch zu erwürfeln
            }
        }

        @Override
        protected void waehle(Spieler spieler) {
            System.arraycopy(augenhaeufigkeiten, 0, kandidat, 0, Wuerfel.MAXIMALAUGENAUGENZAHL);
            if (kandidat[Index.EINSER.ordinal()] > 0 && kandidat[Index.SECHSER.ordinal()] > 0) {
                // 1 und 6 im Wurf, zufällig 1 oder 6 ausschließen
                if (ZUFALL.nextBoolean()) {
                    kandidat[Index.EINSER.ordinal()] = 0; // 1 erneut würfeln (1 virtuell aus
                                                          // Wurf)
                } else {
                    kandidat[Index.SECHSER.ordinal()] = 0; // 6 erneut würfeln (6 virtuell aus
                                                           // Wurf)
                }
            }
            fehlend = 0;
            for (Wuerfel w : wuerfel) { // Würfel auswählen
                int i = w.augen() - 1;
                neuwurfkarte.put(w, kandidat[i] == 0);
                // weitere Würfel mit selber Augenzahl ignorieren
                if (neuwurfkarte.get(w)) {
                    fehlend++;
                } else {
                    kandidat[i] = 0;
                }
            }
        }
    }

    /** JaFuffy. */
    class JaFuffy extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = -6921954073317063844L;
        /** Punktzahlen (JaFuffy). */
        static final int PUNKTE = 50;

        /** Konstruktor. */
        JaFuffy() {
            mittel = prognose(Turnier.RUNDEN, 0);
        }

        /**
         * @param r
         *            Verbleibende Anzahl der Würfe.
         * @param max
         *            Maximale Anzahl gleicher Augen; Null ist erlaubt und meint den ersten von drei Würfen.
         * @return Wahrscheinlichkeit zur Erzielung eines JaFuffy.
         */
        private double p(int r, int max) {
            if (r > 0) {
                double p = 0;
                if (max <= 1) {
                    for (int i = 1; i <= Turnier.WUERFEL; i++) {
                        p += Kombinatorik.gleichenwahrscheinlichkeit(i) * p(r - 1, i);
                    }
                } else if (max == 2) {
                    p = (80. * Kombinatorik.fixgleichenwahrscheinlichkeit(2, r - 1)
                            + 15. * Kombinatorik.fixgleichenwahrscheinlichkeit(1, r - 1) + 1 + 120. * p(r - 1, 2))
                            / 216.;
                } else {
                    p = Kombinatorik.fixgleichenwahrscheinlichkeit(Turnier.WUERFEL - max, r);
                }
                return p;
            } else {
                return max == Turnier.WUERFEL ? 1 : 0;
            }
        }

        /**
         * @param r
         *            Verbleibende Anzahl der Würfe.
         * @param max
         *            Maximale Anzahl gleicher Augen; Null ist erlaubt und meint den ersten von drei Würfen.
         * @return Mittlere erreichbare Punktzahl.
         */
        private double prognose(int r, int max) {
            return p(r, max) * PUNKTE;
        }

        @Override
        protected void bewerte(Spieler spieler) {
            wert = paschkarte.get(Paschaufzaehlung.FUENFERPASCH) ? PUNKTE : 0;
        }

        @Override
        protected double prognose(Spieler spieler, Zuwachs zuwachs) {
            return prognose(spieler.rest(), paschlaenge());
        }

        @Override
        protected void waehle(Spieler spieler) {
            int max = paschlaenge(); // maximale Anzahl gleicher Augen
            int augen; // mit dieser Augenzahl wird JaFuffy versucht
            ArrayList<Integer> augenauswahl = new ArrayList<>();
            // suche Augenzahl mit maximalem Vorkommen heraus
            for (int ind = 0; ind < Wuerfel.MAXIMALAUGENAUGENZAHL; ind++) {
                if (augenhaeufigkeiten[ind] == max) {
                    augenauswahl.add(new Integer(ind + 1));
                }
            }
            // bei Mehrdeutigkeiten zufällige Auswahl der Augenzahl
            augen = augenauswahl.get(ZUFALL.nextInt(augenauswahl.size())).intValue();
            // Würfel auswählen
            for (Wuerfel w : wuerfel) {
                // Falls alle Würfel verschieden alle neu würfeln, ansonsten mit eben ausgewählter Augenzahl.
                neuwurfkarte.put(w, max == 1 || augen != w.augen());
            }
        }
    }

    /**
     * Alle Einträge für die klassische Variante der Spielregeln als Tabelle zusammengefasst.
     */
    protected class Eintragungen extends Analyse.Eintragungen {
        /** Erlaubt Serialisierung. */
        private static final long serialVersionUID = -7416744679857533889L;

        /** Konstruktion der Tabelle. */
        Eintragungen() {
            put(Tabzeile.DREIERPASCH, new Dreierpasch());
            put(Tabzeile.VIERERPASCH, new Viererpasch());
            put(Tabzeile.FULLHOUSE, new FullHouse());
            put(Tabzeile.KLEINESTRASSE, new KleineStrasse());
            put(Tabzeile.GROSSESTRASSE, new GrosseStrasse());
            put(Tabzeile.JAFUFFY, new JaFuffy());
        }
    }

    /** Erlaubt Serialisierung. */
    private static final long serialVersionUID = -3852043350163717864L;

    /**
     * Konstruktor.
     *
     * @param wuerfel
     *            Würfel, mit denen das Turnier durchgeführt wird.
     */
    public Klassisch(Wuerfel[] wuerfel) {
        super(wuerfel);
    }

    @Override
    protected Analyse.Eintragungen eintragungen() {
        return new Eintragungen();
    }

}
