/******************************************************************************
 ** $Id: Analyse.java 667 2015-01-10 19:31:19Z wmh $
 ** Diese Datei ist Bestandteil der Java-Quelltexte des Würfelspiels JaFuffy.
 ** Lauffähig ab Java 6.
 ******************************************************************************
 ** 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;

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

/**
 * Hält Einträge, erstellt Analysen und berechnet Prognose über die erzielbare Punktzahl bei
 * vorgelegtem Wurf.
 */
class Analyse implements Serializable {

    /** Erwarteter Zuwachs für diverse Tabellenteile. */
    class Zuwachs {
        /** Mindestanzahl der Würfel für jeden einzelnen oberen freien Eintrags. */
        private final int teilung;
        /** Beitragswahrscheinlichkeit eines einzelnen oberen freien Eintrags. */
        private final float p;
        /** Erwarteter Bonuszuwachs. */
        private final float mittlererBonusZuwachs;
        /** Erwarteter Zuwachs für obere und untere Tabelle, aber ohne Bonus. */
        private final float mittlererZuwachs;

        /**
         * Berechnung grundlegender Zuwachsgrößen.
         * 
         * @param gesamt
         *            Die Gesamtpunktzahl aus dem oberen Tabellenteil.
         * @param mittlererObererZuwachs
         *            Erwarteter Zuwachs, aufsummiert über alle oberen Einträge.
         * @param augensummeObereFreieEintraege
         *            Die Augensumme der Augen aller freien oberen Einträge.
         * @param anzahlFreieObereEintraege
         *            Die Anzahl der oberen freien Einträge.
         * @param mittlererUntererZuwachs
         *            Erwarteter Zuwachs, aufsummiert über alle unteren Einträge.
         */
        Zuwachs(int gesamt, float mittlererObererZuwachs, int augensummeObereFreieEintraege,
                int anzahlFreieObereEintraege, float mittlererUntererZuwachs) {
            if (gesamt < Spieler.GRENZE && augensummeObereFreieEintraege > 0) {
                teilung =
                        (Spieler.GRENZE - gesamt + augensummeObereFreieEintraege - 1)
                                / augensummeObereFreieEintraege;
                float q = Kombinatorik.mindestgleichenwahrscheinlichkeit(teilung);
                mittlererBonusZuwachs =
                        (float) Math.pow(q, anzahlFreieObereEintraege) * Spieler.BONUS;
                p = q != 0 ? q : 1;
            } else {
                teilung = 0;
                p = 1;
                mittlererBonusZuwachs = 0;
            }
            mittlererZuwachs = mittlererObererZuwachs + mittlererUntererZuwachs;
        }

        /**
         * @return Der erwarte Zuwachs für die gesamte Tabelle.
         */
        float zuwachs() {
            return mittlererZuwachs + mittlererBonusZuwachs;
        }

        /**
         * @param gesamt
         *            Die Gesamtpunktzahl aus dem oberen Tabellenteil.
         * @param guenstige
         *            Die Anzahl der günstigen Würfel für einen Eintrag.
         * @param r
         *            Die Anzahl der verbleibenden Restwürfe.
         * @param augen
         *            Die Augenzahl für den Eintrag, aus dem der Zuwachs resultiert.
         * @return Der erwarte Zuwachs für die gesamte Tabelle.
         */
        float zuwachs(int gesamt, int guenstige, int r, int augen) {
            float q = 0;
            int punkteBisZumBonus = Spieler.GRENZE - gesamt;
            if (punkteBisZumBonus > 0) {
                punkteBisZumBonus -= guenstige * augen;
                q = 1;
                if (punkteBisZumBonus > 0) {
                    int n = Turnier.WUERFEL - guenstige;
                    int min = guenstige <= teilung ? teilung - guenstige : 0;
                    q = Kombinatorik.mindestgleichenwahrscheinlichkeit(n, min, r);
                }
            }
            return mittlererZuwachs + q / p * mittlererBonusZuwachs;
        }
    }

    /** Eintrag in Tabelle. */
    abstract class Eintrag implements Serializable {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = -1491939796847928172L;
        /**
         * Voraussichtlicher Endstand, wenn der Wurf für den aktuellen Eintrag weiter verfolgt
         * wird.
         */
        protected float endsumme;
        /** Wichtung für freie Felder im Intervall [0,1]. */
        private float wichtung;

        /** Erzielte Punktzahl. */
        protected int wert;
        /** Welche Würfel sollen erneut gewürfelt werden? */
        protected boolean[] erneut = new boolean[Turnier.WUERFEL];
        /** Erwarteter mittlerer Punktstand pro Eintrag (Startmittel). */
        protected float mittel;

        /** Tatsächlich erwürfelte Punktzahl ermitteln und merken. */
        void bewerte() {
            wert = punkte();
        }

        /** @return Voraussichtlicher Punktendstand. */
        float endsumme() {
            return endsumme;
        }

        /**
         * Berechnet die erwartete Endsumme, welche sich aus der gegenwärtigen Endsumme und dem
         * erwarteten Zuwachs ergibt; einschließlich Bonus.
         * 
         * @param spieler
         *            Der Spieler, der gerade an der Reihe ist.
         * @param zuwachs
         *            Der erwartete Zuwachs.
         * @return Erwartete Endsumme.
         */
        abstract float endsumme(Spieler spieler, Zuwachs zuwachs);

        /**
         * @param w
         *            Würfelindex.
         * @return Soll Würfel erneut gewürfelt werden?
         */
        boolean erneut(int w) {
            return erneut[w];
        }

        /**
         * Wichtung eintragen.
         *
         * @param wichtung
         *            Setzt die Wichtung eines einzelnen Eintrags.
         */
        void gewichte(float wichtung) {
            this.wichtung = wichtung;
        }

        /** @return Maximale Anzahl gleicher Augen. */
        int maxgleich() {
            int max = Turnier.WUERFEL;
            while (max >= 2 && !gleich[max - 2]) {
                max--;
            }
            return max;
        }

        /** @return Mittlere Punktzahl des Eintrags bei 3maligem Würfeln. */
        float mittel() {
            return mittel;
        }

        /**
         * @param spieler
         *            Der Spieler, der gerade an der Reihe ist.
         * @param zuwachs
         *            Erwarteter Zuwachs für oberen und unteren Teil der Tabelle.
         * @return Prognose für das zu erwartende Resultat für den laufenden Eintrag zur
         *         Ermittlung des Gesamtergebnisses.
         */
        abstract float prognose(Spieler spieler, Zuwachs zuwachs);

        /**
         * Tatsächlich erzielte Punktzahl ermitteln.
         *
         * @return Momentan erreichte Punktzahl für einen Eintrag.
         */
        abstract int punkte();

        /**
         * Prognose ermitteln, und Würfelauswahl für Vorschlag treffen.
         *
         * @param spieler
         *            Spieler, der an der Reihe ist.
         * @param zuwachs
         *            Mittlerer erwarteter Zuwachs (pro Feld 3mal Würfeln angenommen).
         */
        void tippe(Spieler spieler, Zuwachs zuwachs) {
            endsumme = endsumme(spieler, zuwachs);
            if (spieler.rest() > 0) {
                waehle(spieler);
                endsumme += prognose(spieler, zuwachs);
            } else {
                // alle Würfe verbraucht; vorgeben, als ob alle Würfel nochmals zu werfen sind
                Arrays.fill(erneut, true);
                endsumme += punkte();
            }
        }

        /**
         * Vorschlag zur Würfelauswahl ermitteln.
         *
         * @param spieler
         *            Der Spieler, der an der Reihe ist.
         */
        abstract void waehle(Spieler spieler);

        /** @return Erzielte Punktzahl für Wurf. */
        int wert() {
            return wert;
        }

        /** @return Wichtung */
        float wichtung() {
            return wichtung;
        }
    }

    /** Eintrag im oberen Tabellenteil. Möglicher Bonus wird berücksichtigt. */
    class Oben extends Eintrag {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = -5829769579272736329L;

        /** Augenindex für Eintrag (0, 1, 2, 3, 4 oder 5). */
        private final int ind;
        /** Augenzahl. */
        private final int augen;

        /**
         * Konstruktor.
         *
         * @param ind
         *            Der Index des oberen Eintrags (0, 1, 2, 3, 4, 5).
         */
        Oben(int ind) {
            this.ind = ind;
            augen = ind + 1;
            mittel = augen * Kombinatorik.gleichenmittel(Turnier.WUERFEL, Turnier.RUNDEN);
        }

        @Override
        float endsumme(Spieler spieler, Zuwachs zuwachs) {
            return spieler.endsumme()
                    + zuwachs.zuwachs(spieler.gesamt(), anzahl[ind], spieler.rest(), augen)
                    - mittel;
        }

        @Override
        float prognose(Spieler spieler, Zuwachs zuwachs) {
            // freie Würfel
            int n = Turnier.WUERFEL - anzahl[ind];
            // Mindestpunktwert, schon erwürfelt
            float min = augen * anzahl[ind];
            // Prognose für Zuwächse (nochmals k gleiche Augenzahl)
            float punkte = min;
            // ermittle Beiträge für weitere Anzahlen von gleichen Augenzahlen
            for (int k = 1; k <= n; k++) {
                float p = Kombinatorik.gleichenwahrscheinlichkeit(n, k, spieler.rest());
                float neu = k * augen;
                punkte += p * neu;
            }
            return punkte;
        }

        @Override
        int punkte() {
            return anzahl[ind] * augen;
        }

        @Override
        void waehle(Spieler spieler) {
            for (int w = 0; w < Turnier.WUERFEL; w++) {
                erneut[w] = wuerfel[w].augen() != augen;
            }
        }
    }

    /** Eintrag im oberen Tabellenteil. */
    abstract class Unten extends Eintrag {
        /** Zur Serialisierung benötigt. */
        private static final long serialVersionUID = 8894740065051562154L;

        @Override
        float endsumme(Spieler spieler, Zuwachs zuwachs) {
            return spieler.endsumme() + zuwachs.zuwachs() - mittel;
        }
    }

    /** Kleiner und großer Pasch. */
    class Pasch extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = 8666151619656864766L;
        /** Anzahl der gleichen Würfel für einen kleinen Pasch. */
        private static final int KLEIN = 3;
        /** Anzahl der gleichen Würfel für einen großen Pasch. */
        private static final int GROSS = 4;
        /** Art des Pasches (3=klein: drei Gleiche, 4=groß: vier Gleiche). */
        private final int pasch;
        /** Mit dieser Augenzahl wird der Pasch versucht. */
        private int versuch;

        /**
         * Konstruktor.
         *
         * @param pasch
         *            Art des Pasches (drei: klein, vier: groß)
         */
        Pasch(int pasch) {
            this.pasch = pasch;
            mittel = 0;
            for (int augen = 1; augen <= Wuerfel.AUGEN; augen++) {
                for (int max = 1; max <= Turnier.WUERFEL; max++) {
                    mittel += prognose(max, Turnier.RUNDEN - 1, augen) * q(max, augen);
                }
            }
        }

        /**
         * @param m
         *            Maximale Anzahl aller schon vorliegenden Würfel gleicher Augenzahl.
         * @param j
         *            Anzahl der freien Restwürfe.
         * @param augen
         *            Mit dieser Augenzahl wird Pasch versucht.
         * @return Mittlere Summe aller fünf Augenzahlen.
         */
        private float augenzahl(int m, int j, int augen) {
            float grenze = Kombinatorik.augenzahl(j); // mittlere erreichbare Augenzahl
            int n = (int) grenze; // macht Formeln kürzer
            float punkte; // Rückgabewert
            float alpha; // Wahrscheinlichkeitswert
            float s; // Würfel höher als Grenzwert aufsummiert
            punkte = pasch * augen + (m - pasch) * Math.max(augen, grenze);
            s = (n + 7) * (6 - n) / 2F;
            if (augen <= grenze) { // Neuwurf
                alpha = (n - 1) / 5F;
                punkte += (5 - m) * (alpha * grenze + (1F - alpha) * s / (6 - n));
            } else { // Würfel beibehalten
                alpha = n / 5F;
                punkte += (5 - m) * (alpha * grenze + (1F - alpha) * (s - augen) / (5 - n));
            }
            return punkte;
        }

        /** @return Liegt ein erwürfelter Pasch vor? */
        private boolean pasch() {
            return pasch == KLEIN && gleich[DREI_GLEICHE] || gleich[VIER_GLEICHE]
                    || gleich[FUENF_GLEICHE];
        }

        /**
         * @param max
         *            Maximale Anzahl der schon vorliegenden Würfel gleicher Augenzahl.
         * @param r
         *            Anzahl der verbleibenden Restwürfe (0...3).
         * @param augen
         *            Mit dieser Augenzahl wird Pasch versucht.
         * @return Mittlere erreichbare Punktzahl.
         */
        private float prognose(int max, int r, int augen) {
            float punkte = 0;
            if (max < pasch) {
                int n = Turnier.WUERFEL - max;
                for (int k = pasch - max; k <= n; k++) {
                    for (int j = 1; j <= r; j++) {
                        final float gw0 = Kombinatorik.gleichenwahrscheinlichkeit(n, k, j);
                        final float gw1 = Kombinatorik.gleichenwahrscheinlichkeit(n, k, j - 1);
                        punkte += (gw0 - gw1) * augenzahl(max + k, j - 1, augen);
                    }
                }
            } else {
                if (wuerfel != null) { // mindestens schon einmal gewürfelt
                    for (int w = 0; w < Turnier.WUERFEL; w++) {
                        if (erneut[w]) {
                            punkte += Kombinatorik.augenzahl(r);
                        } else {
                            punkte += wuerfel[w].augen();
                        }
                    }
                } else { // erster Wurf steht an
                    punkte = augenzahl(max, Turnier.RUNDEN - 1, augen);
                }
            }
            return punkte;
        }

        /**
         * @param max
         *            Anzahl Würfel mit gleicher Augenzahl.
         * @param augen
         *            Maximale Augenzahl der gleichen Würfel.
         * @return Wahrscheinlichkeit im ersten Wurf.
         */
        private float q(int max, int augen) {
            if (max >= 3) {
                return Kombinatorik.gleichenwahrscheinlichkeit(max) / 6F;
            } else if (max == 2) {
                return ((augen - 1) * 120F + 600F) / 7776F;
            } else { // max==1
                if (augen <= 4) {
                    return 0F;
                } else if (augen == 5) {
                    return 120F / 7776F;
                } else { // augen==6
                    return 600F / 7776F;
                }
            }
        }

        @Override
        float prognose(Spieler spieler, Zuwachs zuwachs) {
            return prognose(maxgleich(), spieler.rest(), versuch);
        }

        @Override
        int punkte() {
            return pasch() ? augensumme : 0;
        }

        @Override
        void waehle(Spieler spieler) {
            // suche höchste Augenzahl mit häufigstem Vorkommen
            versuch = Wuerfel.AUGEN;
            while (anzahl[versuch - 1] < maxgleich()) {
                versuch--;
            }
            // Würfel auswählen
            for (int zaehler = 0, w = 0; w < Turnier.WUERFEL; w++) {
                boolean kandidat; // Kandidat zum erneuten Wurf
                if (wuerfel[w].augen() == versuch) {
                    zaehler++;
                    // Schon genügend Würfel für einen Pasch vorhanden, so dass Würfel eventuell
                    // frei zum erneuten Wurf ist und bessere Punktzahl zu erwarten?
                    kandidat =
                            zaehler > pasch
                                    && Kombinatorik.augenzahl(spieler.rest()) > wuerfel[w]
                                            .augen();
                } else {
                    // Nochmals werfen, falls Würfel für Pasch benötigt oder
                    // aber voraussichtlich eine höhere Punktzahl zu erwarten
                    // ist.
                    kandidat =
                            !pasch()
                                    || Kombinatorik.augenzahl(spieler.rest()) > wuerfel[w]
                                            .augen();
                }
                // Nochmals werfen, falls Würfel als Kandidat in Frage kommt
                // (nicht mehr für Pasch benötigt) und voraussichtlich eine
                // höhere Punktzahl zu erzielen ist.
                erneut[w] = kandidat;
            }
        }
    }

    /** Full House. */
    class FullHouse extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = -1069152277729232203L;
        /** Punktzahlen (Full House). */
        static final int PUNKTE = 25;
        /** Anzahl der Würfel, die zum Full House fehlen. */
        private int fehlend;

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

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

        /**
         * @param k
         *            Anzahl der zu Full House fehlenden Würfel
         * @return Wahrscheinlichkeit der Ergänzung zu Full House in einem Wurf
         */
        private float restwahrscheinlichkeit(int k) {
            return RESTWAHRSCHEINLICHKEIT[k];
        }

        /**
         * @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 float restwahrscheinlichkeit(int k, int r) {
            switch (k) {
            case 0:
                return 1F;
            case 1:
                return 1F - (float) Math.pow(2F / 3F, r);
            case 2:
                return 1F - (float) Math.pow(5F / 6F, r);
            case 3:
                switch (r) {
                case 1:
                    return 21F / 216F;
                case 2:
                    return 89F / 324F;
                default:
                    return 0;
                }
            case 5:
                if (r == 0) {
                    return 0;
                }
                float restwahrscheinlichkeit = 0;
                for (int i = 0; i <= Turnier.WUERFEL; i++) {
                    restwahrscheinlichkeit +=
                            restwahrscheinlichkeit(i) * restwahrscheinlichkeit(i, r - 1);
                }
                return restwahrscheinlichkeit;
            default:
                return 0;
            }
        }

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

        @Override
        int punkte() {
            if (gleich[ZWEI_GLEICHE] && gleich[DREI_GLEICHE] || gleich[FUENF_GLEICHE]) {
                return PUNKTE;
            } else {
                return 0;
            }
        }

        @Override
        void waehle(Spieler spieler) {
            int w;
            if (gleich[ZWEI_GLEICHE] && gleich[DREI_GLEICHE] || gleich[FUENF_GLEICHE]) {
                // Test auf Full House
                Arrays.fill(erneut, false);
                fehlend = 0;
            } else if (gleich[DREI_GLEICHE] || gleich[VIER_GLEICHE]) {
                // mindestens 3 gleiche
                int zaehler = 0;
                for (w = 0; w < Turnier.WUERFEL; w++) {
                    erneut[w] = anzahl[wuerfel[w].augen() - 1] <= 2 || zaehler == 3;
                    if (anzahl[wuerfel[w].augen() - 1] > 2) {
                        zaehler++;
                    }
                }
                fehlend = 2;
            } else if (gleich[ZWEI_GLEICHE]) { // 1 mal 2 gleiche oder 2 mal 2 gleiche
                fehlend = 0;
                for (int ind = 0; ind < Wuerfel.AUGEN; ind++) {
                    if (anzahl[ind] == 2) {
                        if (fehlend == 0) { // erstes Paar gefunden
                            fehlend = 3;
                        } else { // es gibt ein zweites Paar
                            fehlend = 1;
                        }
                    }
                }
                for (w = 0; w < Turnier.WUERFEL; w++) {
                    erneut[w] = anzahl[wuerfel[w].augen() - 1] == 1;
                }
            } else {
                // alle Würfel verschieden
                Arrays.fill(erneut, true);
                fehlend = Turnier.WUERFEL;
            }
        }
    }

    /** Aufzählung der Straßenteilpaare {1, 2}, {2, 5} oder {5, 6} sowie {3, 4}. */
    enum Strassenpaar {
        /** Alle möglichen Teilpaare. */
        LINKS(EINS, ZWEI), INNEN(ZWEI, FUENF), RECHTS(FUENF, SECHS), MITTE(DREI, VIER);
        /** Beschreibt das Teilpaar durch seine Augenzahl (weniger eins). */
        private final int[] feld = new int[2];

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

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

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

    /**
     * Statische Hilfsklasse, welche bedingte Wahrscheinlichkeiten für die kleine Straße
     * enthält.
     */
    static class KleineStrasseIndex implements Serializable {
        /**
         * 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, float p) {
            TABELLE.put(new KleineStrasseIndex(aussen, innen, mitte, rest), p);
        }

        /** 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, Float> TABELLE =
                new HashMap<KleineStrasseIndex, Float>();

        /** Belegung der Tabelle mit vorberechneten Werten. */
        static {
            tabelliere(0, 0, 1, 1, 2.129630e-01F);
            tabelliere(0, 0, 1, 2, 4.891690e-01F);
            tabelliere(0, 0, 2, 1, 3.611111e-01F);
            tabelliere(0, 0, 2, 2, 6.584362e-01F);
            tabelliere(0, 1, 0, 1, 1.481481e-01F);
            tabelliere(0, 1, 0, 2, 3.912751e-01F);
            tabelliere(0, 1, 1, 1, 2.500000e-01F);
            tabelliere(0, 1, 1, 2, 5.150463e-01F);
            tabelliere(0, 1, 2, 1, 5.555556e-01F);
            tabelliere(0, 1, 2, 2, 8.024691e-01F);
            tabelliere(0, 2, 0, 1, 1.388889e-01F);
            tabelliere(0, 2, 0, 2, 3.526235e-01F);
            tabelliere(0, 2, 1, 1, 3.055556e-01F);
            tabelliere(0, 2, 1, 2, 5.177469e-01F);
            tabelliere(1, 0, 0, 1, 1.018519e-01F);
            tabelliere(1, 0, 0, 2, 3.410851e-01F);
            tabelliere(1, 0, 1, 1, 1.666667e-01F);
            tabelliere(1, 0, 1, 2, 4.293981e-01F);
            tabelliere(1, 0, 2, 1, 3.611111e-01F);
            tabelliere(1, 0, 2, 2, 6.296296e-01F);
            tabelliere(1, 1, 0, 1, 1.388889e-01F);
            tabelliere(1, 1, 0, 2, 3.526235e-01F);
            tabelliere(1, 1, 1, 1, 3.055556e-01F);
            tabelliere(1, 1, 1, 2, 5.177469e-01F);
        }
        /** 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. */
    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 float P_TOT = 0.6025279F;
        /** Punktzahlen (kleine Straße). */
        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 (int augen : paar.feld()) {
                if (anzahl[augen] > 0) { // Kommt Augenzahl im Wurf vor?
                    for (int w = 0; w < Turnier.WUERFEL; w++) {
                        if (wuerfel[w].augen() == augen + 1) {
                            erneut[w] = false;
                            break;
                        }
                    }
                }
            }
        }

        @Override
        float prognose(Spieler spieler, Zuwachs zuwachs) {
            if (aussen + innen + mitte < Turnier.WUERFEL - 1) {
                return KleineStrasseIndex.TABELLE.get(new KleineStrasseIndex(aussen, innen,
                        mitte, spieler.rest())) * PUNKTE;
            } else {
                return PUNKTE;
            }
        }

        @Override
        int punkte() {
            if (anzahl[DREI] > 0
                    && anzahl[VIER] > 0
                    && (anzahl[EINS] > 0 && anzahl[ZWEI] > 0 || anzahl[ZWEI] > 0
                            && anzahl[FUENF] > 0 || anzahl[FUENF] > 0 && anzahl[SECHS] > 0)) {
                return PUNKTE;
            } else {
                return 0;
            }
        }

        @Override
        void waehle(Spieler spieler) {
            Arrays.fill(erneut, true);
            aussen = 0;
            innen = Strassenpaar.INNEN.vorkommen(anzahl);
            mitte = Strassenpaar.MITTE.vorkommen(anzahl);
            int links = Strassenpaar.LINKS.vorkommen(anzahl);
            int rechts = Strassenpaar.RECHTS.vorkommen(anzahl);
            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. */
    class GrosseStrasse extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = 8292592862416663732L;
        /** Punktzahlen (große Straße). */
        static final int PUNKTE = 40;
        /** Anzahl der Würfel je Augenzahl. */
        private final int[] kandidat = new int[Wuerfel.AUGEN];
        /** 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 float p(int n, int r) {
            if (r > 1) {
                float 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) / (float) Math.pow(Wuerfel.AUGEN, 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 float q(int n, int r) {
            if (r > 1) {
                float 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) / (float) Math.pow(Wuerfel.AUGEN, 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 float u(int n, int i) {
            return Kombinatorik.strassenteilsequenz(n, 6 - n, i, n)
                    / (float) Math.pow(Wuerfel.AUGEN, 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 float v(int n, int i) {
            return Kombinatorik.strassenteilsequenz(n, 5 - n, i, n - 1)
                    / (float) Math.pow(Wuerfel.AUGEN, 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 float 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))
                    / (float) Math.pow(Wuerfel.AUGEN, n);
        }

        @Override
        float prognose(Spieler spieler, Zuwachs zuwachs) {
            if (anzahl[EINS] > 0 || anzahl[SECHS] > 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
        int punkte() {
            if (anzahl[ZWEI] == 1 && anzahl[DREI] == 1 && anzahl[VIER] == 1
                    && anzahl[FUENF] == 1 && (anzahl[EINS] == 1 || anzahl[SECHS] == 1)) {
                return PUNKTE;
            } else {
                return 0;
            }
        }

        @Override
        void waehle(Spieler spieler) {
            System.arraycopy(anzahl, 0, kandidat, 0, Wuerfel.AUGEN);
            if (kandidat[EINS] > 0 && kandidat[SECHS] > 0) { // 1 und 6 im Wurf
                // zufällig 1 oder 6 ausschließen
                if (ZUFALL.nextBoolean()) {
                    kandidat[EINS] = 0; // 1 erneut würfeln (1 virtuell aus Wurf)
                } else {
                    kandidat[SECHS] = 0; // 6 erneut würfeln (6 virtuell aus Wurf)
                }
            }
            fehlend = 0;
            for (int w = 0; w < Turnier.WUERFEL; w++) { // Würfel auswählen
                int i = wuerfel[w].augen() - 1;
                erneut[w] = kandidat[i] == 0;
                // weitere Würfel mit selber Augenzahl ignorieren
                if (erneut[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 (0 erlaubt, meint 1. von 3 Würfen).
         * @return mittlere erreichbare Punktzahl
         */
        private float prognose(int r, int max) {
            float p; // Wahrscheinlichkeit auf JaFuffy
            if (max <= 1) { // alle Würfel unterschiedlich bzw. erster Wurf
                p = 0;
                for (int j = 0; j < r; j++) {
                    for (int i = 2; i <= 5; i++) {
                        p +=
                                Math.pow(Kombinatorik.gleichenwahrscheinlichkeit(1), r - 1 - j)
                                        * Kombinatorik.gleichenwahrscheinlichkeit(i)
                                        * Kombinatorik.gleichenwahrscheinlichkeit(5 - i, j);
                    }
                }
            } else { // mindestens zwei gleiche Augenzahlen
                p = Kombinatorik.gleichenwahrscheinlichkeit(5 - max, r);
            }
            return p * PUNKTE;
        }

        @Override
        float prognose(Spieler spieler, Zuwachs zuwachs) {
            return prognose(spieler.rest(), maxgleich());
        }

        @Override
        int punkte() {
            return gleich[FUENF_GLEICHE] ? PUNKTE : 0;
        }

        @Override
        void waehle(Spieler spieler) {
            int max = maxgleich(); // maximale Anzahl gleicher Augen
            int augen; // mit dieser Augenzahl wird JaFuffy versucht
            ArrayList<Integer> augenauswahl = new ArrayList<Integer>();
            // suche Augenzahl mit maximalem Vorkommen heraus
            for (int ind = 0; ind < Wuerfel.AUGEN; ind++) {
                if (anzahl[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 (int w = 0; w < Turnier.WUERFEL; w++) {
                // falls alle Würfel verschieden lieber nochmal alle neu
                // würfeln,
                // ansonsten mit eben ausgewählter Augenzahl fortfahren
                erneut[w] = max == 1 || augen != wuerfel[w].augen();
            }
        }
    }

    /** Chance. */
    class Chance extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = -3411668587180538841L;
        /** Erzielte Punkte (alle Augen aufsummiert). */
        private float punkte;

        /** Konstruktor. */
        Chance() {
            mittel = Turnier.WUERFEL * Kombinatorik.augenzahl(Turnier.RUNDEN);
        }

        @Override
        float prognose(Spieler spieler, Zuwachs zuwachs) {
            return punkte;
        }

        @Override
        int punkte() {
            return augensumme;
        }

        @Override
        void waehle(Spieler spieler) {
            punkte = 0;
            for (int w = 0; w < Turnier.WUERFEL; w++) {
                float m = Kombinatorik.augenzahl(spieler.rest());
                int a = wuerfel[w].augen();
                erneut[w] = m > a;
                punkte += erneut[w] ? m : a;
            }
        }
    }

    /** Zur Serialisierung. */
    private static final long serialVersionUID = -6325984568972413728L;

    /** Index, welcher von sichtbarem Wert auf interne Zählung umsetzt. */
    private static final int EINS = 0;
    /** Index, welcher von sichtbarem Wert auf interne Zählung umsetzt. */
    private static final int ZWEI = 1;
    /** Index, welcher von sichtbarem Wert auf interne Zählung umsetzt. */
    private static final int DREI = 2;
    /** Index, welcher von sichtbarem Wert auf interne Zählung umsetzt. */
    private static final int VIER = 3;
    /** Index, welcher von sichtbarem Wert auf interne Zählung umsetzt. */
    private static final int FUENF = 4;
    /** Index, welcher von sichtbarem Wert auf interne Zählung umsetzt. */
    private static final int SECHS = 5;

    /** Zur Indizierung von Gleichheit von zweien. */
    private static final int ZWEI_GLEICHE = 0;
    /** Zur Indizierung von Gleichheit von dreien. */
    private static final int DREI_GLEICHE = 1;
    /** Zur Indizierung von Gleichheit von vieren. */
    private static final int VIER_GLEICHE = 2;
    /** Zur Indizierung von Gleichheit von fünfen. */
    private static final int FUENF_GLEICHE = 3;

    /**
     * Wahrscheinlichkeit für 0, 1, ..., 5 fehlende Würfel auf Full House in einem Wurf mit
     * allen Würfeln.
     */
    private static final float[] RESTWAHRSCHEINLICHKEIT = { 17F / 432F, 25F / 108F, 25F / 144F,
            25F / 54F, 0F, 5F / 54F };

    /** Zufallsgenerator zur Auswahl von Tipp-Strategien bei Mehrdeutigkeiten. */
    private static final Random ZUFALL = new Random();

    /** Neutrale Wichtung, falls alle Wichtungen gleich sind. */
    private static final float NEUTRALWICHTUNG = 1;

    /** Bester Eintrag. */
    private Tabzeile bester;
    /** Summe aller Augen. */
    private int augensumme;
    /** Anzahl der Würfel pro Augenzahl. */
    private final int[] anzahl = new int[Wuerfel.AUGEN];
    /** Genau 2, 3, 4, 5 gleiche Würfel? */
    private final boolean[] gleich = new boolean[FUENF_GLEICHE - ZWEI_GLEICHE + 1];

    /** Im Turnier benutzte Würfel. */
    private final Wuerfel[] wuerfel;
    /**
     * Voraussichtlicher mittlerer Punktstand pro Feld (alle) drei Würfe genutzt.
     */
    private final HashMap<Tabzeile, Eintrag> eintraege = new HashMap<Tabzeile, Eintrag>(
            Tabzeile.ANZAHL);

    /**
     * Konstruktor.
     *
     * @param wuerfel
     *            Die im Turnier benutzten Würfel.
     */
    Analyse(Wuerfel[] wuerfel) {
        this.wuerfel = wuerfel;
        eintraege.put(Tabzeile.EINS, new Oben(EINS));
        eintraege.put(Tabzeile.ZWEI, new Oben(ZWEI));
        eintraege.put(Tabzeile.DREI, new Oben(DREI));
        eintraege.put(Tabzeile.VIER, new Oben(VIER));
        eintraege.put(Tabzeile.FUENF, new Oben(FUENF));
        eintraege.put(Tabzeile.SECHS, new Oben(SECHS));
        eintraege.put(Tabzeile.GESAMT, null);
        eintraege.put(Tabzeile.BONUS, null);
        eintraege.put(Tabzeile.OSUMME, null);
        eintraege.put(Tabzeile.DREIERPASCH, new Pasch(Pasch.KLEIN));
        eintraege.put(Tabzeile.VIERERPASCH, new Pasch(Pasch.GROSS));
        eintraege.put(Tabzeile.FULLHOUSE, new FullHouse());
        eintraege.put(Tabzeile.KLEINESTRASSE, new KleineStrasse());
        eintraege.put(Tabzeile.GROSSESTRASSE, new GrosseStrasse());
        eintraege.put(Tabzeile.JAFUFFY, new JaFuffy());
        eintraege.put(Tabzeile.CHANCE, new Chance());
        eintraege.put(Tabzeile.USUMME, null);
        eintraege.put(Tabzeile.ESUMME, null);
    }

    /**
     * Ermittelt das Maximum über alle Endstände für alle setzbaren Einträge, und merkt sich
     * einen Eintrag mit dem voraussichtlich besten Endstand.
     *
     * @param spieler
     *            Der Spieler, der gerade an der Reihe ist.
     * @param spieler
     * @return Das Maximum aller Endstände.
     */
    private float findeMaximum(Spieler spieler) {
        float max = 0;
        bester = null;
        for (Tabzeile z : Tabzeile.ALLE) {
            if (spieler.setzbar(z)) {
                float endstand = eintraege.get(z).endsumme();
                if (endstand > max) {
                    bester = z;
                    max = endstand;
                }
            }
        }
        return max;
    }

    /**
     * Ermittelt das Minimum über alle Endstände für alle setzbaren Einträge.
     *
     * @param spieler
     *            Der Spieler, der gerade an der Reihe ist.
     * @param max
     *            Der maximale erwartete Endstand für alle setzbaren Einträge.
     * @return Das Minimum aller Endstände.
     */
    private float findeMinimum(Spieler spieler, float max) {
        float min = max;
        for (Tabzeile z : Tabzeile.ALLE) {
            if (spieler.setzbar(z)) {
                float endstand = eintraege.get(z).endsumme();
                if (endstand < min) {
                    min = endstand;
                }
            }
        }
        return min;
    }

    /**
     * Gewichtet die setzbaren Einträge nach erwarteter Höhe des Endstandes. Das Gewicht je
     * Eintrag entspricht der linearen Abbildung des Intervals zwischen dem minimalen und
     * maximalen Endstand, abgebildet auf das Interval [0, 1].
     *
     * @param spieler
     *            Der Spieler, der gerade an der Reihe ist.
     * @param min
     *            Der minimale erwartete Endstand für alle setzbaren Einträge.
     * @param max
     *            Der maximale erwartete Endstand für alle setzbaren Einträge.
     */
    private void gewichte(Spieler spieler, float min, float max) {
        for (Tabzeile z : Tabzeile.ALLE) {
            if (spieler.setzbar(z)) {
                if (max - min > 0) {
                    float endstand = eintraege.get(z).endsumme();
                    eintraege.get(z).gewichte((endstand - min) / (max - min));
                } else {
                    eintraege.get(z).gewichte(NEUTRALWICHTUNG);
                }
            } else {
                eintraege.get(z).gewichte(0);
            }
        }
    }

    /**
     * Prognosen für jeden setzbaren Eintrag berechnen, Zuwächse ermitteln, und Würfelauswahlen
     * für Vorschläge treffen.
     *
     * @param spieler
     *            Der Spieler, der an der Reihe ist.
     */
    private void tippe(Spieler spieler) {
        float mittlererObererZuwachs = 0;
        int anzahlObereFreieEintraege = 0;
        int augensummeObereFreieEintraege = 0;
        for (Tabzeile z : Tabzeile.OBEN) {
            if (spieler.setzbar(z)) {
                Oben eintrag = (Oben) eintraege.get(z);
                mittlererObererZuwachs += eintrag.mittel();
                anzahlObereFreieEintraege++;
                augensummeObereFreieEintraege += eintrag.augen;
            }
        }
        float mittlererUntererZuwachs = 0;
        for (Tabzeile z : Tabzeile.UNTEN) {
            if (spieler.setzbar(z)) {
                mittlererUntererZuwachs += eintraege.get(z).mittel();
            }
        }
        Zuwachs zuwachs =
                new Zuwachs(spieler.gesamt(), mittlererObererZuwachs,
                        augensummeObereFreieEintraege, anzahlObereFreieEintraege,
                        mittlererUntererZuwachs);
        // Tipps berechnen
        for (Tabzeile z : Tabzeile.ALLE) {
            if (spieler.setzbar(z)) {
                eintraege.get(z).tippe(spieler, zuwachs);
            }
        }
    }

    /** @return bester Eintrag (-1 heißt keiner vorhanden) */
    Tabzeile bester() {
        return bester;
    }

    /**
     * @param eintrag
     *            Eintragsnummer.
     * @param w
     *            Würfelindex.
     * @return Würfel nochmal würfeln für Vorschlag?
     */
    boolean erneut(Tabzeile eintrag, int w) {
        return eintraege.get(eintrag).erneut(w);
    }

    /** Tatsächlich erwürfelte Punkte für alle Einträge errechnen. */
    void errechne() {
        int i; // Index Augenzahl
        int j; // Index Gleiche

        // Zähle Würfel pro Augenzahl, Summe bestimmen
        Arrays.fill(anzahl, 0);
        augensumme = 0;
        for (Wuerfel w : wuerfel) {
            augensumme += w.augen();
            anzahl[w.augen() - 1]++;
        }
        // Genau 2, 3, 4, 5 gleiche Würfel?
        for (j = ZWEI_GLEICHE; j <= FUENF_GLEICHE; j++) {
            gleich[j] = false;
            for (i = 0; i < Wuerfel.AUGEN; i++) {
                gleich[j] = anzahl[i] == j + 2 || gleich[j];
            }
        }

        // erwürfelte Punktzahl für alle Einträge
        for (Tabzeile z : Tabzeile.ALLE) {
            eintraege.get(z).bewerte();
        }
    }

    /**
     * Punktprognosen aus Wurf ableiten.
     *
     * @param spieler
     *            Spieler, der an Reihe ist.
     */
    void prognostiziere(Spieler spieler) {
        tippe(spieler);
        float max = findeMaximum(spieler);
        float min = findeMinimum(spieler, max);
        gewichte(spieler, min, max);

    }

    /**
     * @param eintrag
     *            Der zu untersuchende Eintrag.
     * @return Würfelauswahl entspricht Vorschlag für Eintrag.
     */
    boolean vorgeschlagen(Tabzeile eintrag) {
        int[] anzahlenJeAugenAusgewaehlte = new int[Wuerfel.AUGEN];
        int[] anzahlenJeAugenVorgeschlagene = new int[Wuerfel.AUGEN];
        for (int w = 0; w < Turnier.WUERFEL; w++) {
            int a = wuerfel[w].augen() - 1;
            if (wuerfel[w].isSelected()) {
                anzahlenJeAugenAusgewaehlte[a]++;
            }
            if (eintraege.get(eintrag).erneut(w)) {
                anzahlenJeAugenVorgeschlagene[a]++;
            }
        }
        for (int a = 0; a < Wuerfel.AUGEN; a++) {
            if (anzahlenJeAugenAusgewaehlte[a] != anzahlenJeAugenVorgeschlagene[a]) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param eintrag
     *            Eintragsnummer.
     * @return Erwürfelter Punktstand.
     */
    int wert(Tabzeile eintrag) {
        return eintraege.get(eintrag).wert();
    }

    /**
     * @param eintrag
     *            Eintragsnummer.
     * @return Wichtung für erstellte Vorschläge.
     */
    float wichtung(Tabzeile eintrag) {
        return eintraege.get(eintrag).wichtung();
    }

}
