/******************************************************************************
 ** $Id: Analyse.java 1186 2017-04-16 00:28:31Z 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;

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

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

    /** Eintrag in Tabelle. */
    protected abstract class Eintrag implements Serializable {

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

        /** Wichtung für freie Felder im Intervall [0,1]. */
        private double wichtung;
        /** Zufallsgenerator wird zur Umsetzung von Spielstärken benötigt zur Einführung einer Unsicherheit. */
        private final Random spielstaerkenunsicherheit = new Random();

        /** Karte der Entscheidungen, welcher Würfel erneut gewürfelt werden soll. */
        protected final IdentityHashMap<Wuerfel, Boolean> neuwurfkarte = new IdentityHashMap<>(Turnier.WUERFEL);
        {
            nominiereNeuwurf(true);
        }

        /** Durch den Wurf tatsächlich erzielte Punktzahl. */
        protected int wert;
        /** Voraussichtlicher Endstand, wenn der Wurf für den aktuellen Eintrag weiter verfolgt wird. */
        protected double endsumme;
        /** Erwarteter mittlerer Punktstand pro Eintrag (Startmittel). */
        protected double mittel;

        /**
         * Bewertet den vorliegenden Wurf durch Ermittlung der tatsächlich erwürfelten Punktzahl und deren Speicherung.
         *
         * @param spieler
         *            Spieler, für welchen der vorliegende Wurf bewertet wird.
         */
        protected abstract void bewerte(Spieler spieler);

        /**
         * Bestimmt über den Neuwurfstatus des gesamten Würfelsatzes.
         *
         * @param istNeuwurf
         *            Entscheidet, ob ein Neuwurf anliegt.
         */
        protected void nominiereNeuwurf(boolean istNeuwurf) {
            for (Wuerfel w : wuerfel) {
                neuwurfkarte.put(w, istNeuwurf);
            }
        }

        /**
         * @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.
         */
        protected abstract double prognose(Spieler spieler, Zuwachs zuwachs);

        /**
         * Vorschlag zur Würfelauswahl ermitteln. Wird vor der Rückgabe des Prognosewertes mittels {@link #prognose}
         * ausgeführt.
         *
         * @param spieler
         *            Der Spieler, der an der Reihe ist.
         */
        protected abstract void waehle(Spieler spieler);

        /**
         * Berechnet den erwarteten, individuellen Zugewinn für die vorliegende objektive Prognose, angepasst an die
         * Spielstärke.
         *
         * @param unsicherheit
         *            Die Unsicherheit des Spielers bzw. Bots, die auf die objektive Prognose angewendet wird.
         * @param prognose
         *            Die objektive Prognose nach bestmöglichem Wissen.
         * @return Der an die Fähigkeiten des Spieler bzw. Bots angepasste individuelle Zugewinn.
         */
        private double individualzugewinn(double unsicherheit, double prognose) {
            return (2 * unsicherheit * spielstaerkenunsicherheit.nextDouble() + 1 - unsicherheit) * prognose;
        }

        /** @return Voraussichtlicher Punktendstand. */
        double 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 double endsumme(Spieler spieler, Zuwachs zuwachs);

        /**
         * @param w
         *            Referenz auf Würfel.
         * @return Soll Würfel erneut gewürfelt werden?
         */
        boolean erneut(Wuerfel w) {
            return neuwurfkarte.get(w);
        }

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

        /**
         * @param spieler
         *            Spielstärke des Spielers beeinflusst mittlere Punktzahl.
         * @return Mittlere Punktzahl des Eintrags bei 3maligem Würfeln.
         */
        double mittel(Spieler spieler) {
            return individualzugewinn(spieler.prognoseunsicherheit(), mittel);
        }

        /**
         * 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) {
                nominiereNeuwurf(false);
                waehle(spieler);
                endsumme += individualzugewinn(spieler.prognoseunsicherheit(), prognose(spieler, zuwachs));
            } else {
                // alle Würfe verbraucht; vorgeben, als ob alle Würfel nochmals zu werfen sind
                nominiereNeuwurf(true);
                endsumme += wert;
            }
        }

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

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

    }

    /** Zählt alle Einträge aus dem oberen Teil der Tabelle auf. */
    protected enum Index {
        /** Einser-Eintrag im oberen Tabellenteil. */
        EINSER,
        /** Zweier-Eintrag im oberen Tabellenteil. */
        ZWEIER,
        /** Dreier-Eintrag im oberen Tabellenteil. */
        DREIER,
        /** Vierer-Eintrag im oberen Tabellenteil. */
        VIERER,
        /** Fünfer-Eintrag im oberen Tabellenteil. */
        FUENFER,
        /** Sechser-Eintrag im oberen Tabellenteil. */
        SECHSER;
        /** @return Liefert die Augenzahl zum Eintrag zurück. */
        public int augen() {
            return ordinal() + 1;
        }

        /**
         * @param haeufigkeiten
         *            Feld mit allen Augenhäufigkeiten.
         * @return Anzahl der Würfel mit dieser Augenzahl.
         */
        public int vorkommen(int[] haeufigkeiten) {
            return haeufigkeiten[ordinal()];
        }
    }

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

        /** Augenzahl. */
        private final int augen;
        /**
         * Index für Eintrag (0, 1, 2, 3, 4 oder 5) im oberen Teil der Tabelle, abgeleitet aus der Augenzahl.
         */
        private final int ind;

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

        @Override
        protected void bewerte(Spieler spieler) {
            wert = augenhaeufigkeiten[ind] * augen;
        }

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

        @Override
        protected void waehle(Spieler spieler) {
            for (Wuerfel w : wuerfel) {
                neuwurfkarte.put(w, w.augen() != augen);
            }
        }

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

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

        /** @return Maximale Anzahl von Würfeln mit gleichen Augenzahlen, von eins bis fünf. */
        protected int paschlaenge() {
            int max = 1;
            for (Map.Entry<Paschaufzaehlung, Boolean> pasch : paschkarte.entrySet()) {
                int groesse = pasch.getKey().groesse();
                boolean istPaschVorhanden = pasch.getValue();
                if (istPaschVorhanden && groesse > max) {
                    max = groesse;
                }
            }
            return max;
        }

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

    /**
     * Zählt alle Pasche von Zwei bis Fünf auf, wobei alle Würfel zu einem festen Augenwert herangezogen werden.
     */
    protected enum Paschaufzaehlung {
        /** Zweierpasch. */
        ZWEIERPASCH,
        /** Dreierpasch und kein Zweierpasch. */
        DREIERPASCH,
        /** Viererpasch und weder Dreipasch noch Zweierpasch. */
        VIERERPASCH,
        /** Fünferpasch und weder Viererpasch noch Dreierpasch noch Zweierpasch. */
        FUENFERPASCH;
        /**
         * @return Anzahl der Würfel im Pasch, wobei der Maximalwert für einen festen Augenwert zurückgegeben wird.
         */
        public int groesse() {
            return ordinal() + 2;
        }
    }

    /** Paschgrundlagen. */
    public abstract class Pasch extends Unten {
        /** Zur Serialisierung. */
        private static final long serialVersionUID = 8666151619656864766L;

        /** Art des Pasches (3=klein: drei Gleiche, 4=groß: vier Gleiche). */
        private final int paschgroesse;
        /** Mit dieser Augenzahl wird der Pasch versucht. */
        private int paschaugenzahl;

        /**
         * Konstruktor.
         *
         * @param paschgroesse
         *            Art des Pasches (drei: klein, vier: groß)
         */
        protected Pasch(int paschgroesse) {
            this.paschgroesse = paschgroesse;
            mittel = augensumme(Turnier.RUNDEN);
        }

        /**
         * @param r
         *            Anzahl der Restwürfe.
         * @return Mittlere erwartete Augensumme.
         */
        private double augensumme(int r) {
            if (r > 0) {
                double augensumme;
                augensumme = Kombinatorik.gleichenwahrscheinlichkeit(1) * augensumme(r - 1);
                for (int k = 2; k < paschgroesse; k++) {
                    for (int a = 1; a <= 6; a++) {
                        augensumme += Kombinatorik.mehrlingswahrscheinlichkeit(a, k) * augensumme(a, r - 1, k);
                    }
                }
                augensumme += (3.5 * paschgroesse + (Turnier.WUERFEL - paschgroesse) * Kombinatorik.augenzahl(r))
                        * Kombinatorik.mindestgleichenwahrscheinlichkeit(paschgroesse);
                return augensumme;
            } else {
                return 0;
            }
        }

        /**
         * @param augen
         *            Mit dieser Augenzahl wird Pasch versucht.
         * @param r
         *            Anzahl der verbleibenden Restwürfe (0...3).
         * @param max
         *            Maximale Anzahl der schon vorliegenden Würfel gleicher Augenzahl.
         * @return Mittlere erreichbare Punktzahl.
         */
        private double prognose(int augen, int r, int max) {
            double punkte = 0;
            if (max < paschgroesse) {
                if (max > 1) {
                    punkte = augensumme(augen, r, max);
                } else {
                    punkte = augensumme(r);
                }
            } else {
                for (Wuerfel w : wuerfel) {
                    if (neuwurfkarte.get(w)) {
                        punkte += Kombinatorik.augenzahl(r);
                    } else {
                        punkte += w.augen();
                    }
                }
            }
            return punkte;
        }

        /**
         * @param a
         *            Fest vorgegebene Augenzahl für einen Mehrling, wobei bei gleich langen Sequenzen diejenige mit der
         *            größeren Augenzahl zu wählen ist.
         * @param r
         *            Anzahl der Restwürfe.
         * @param k
         *            Maximale Anzahl von Würfeln zur vorgegebenen Augenzahl
         */
        protected abstract double augensumme(int a, int r, int k);

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

        /** @return Liegt ein erwürfelter Pasch vor? */
        protected abstract boolean pasch();

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

        @Override
        protected void waehle(Spieler spieler) {
            paschaugenzahl = Wuerfel.MAXIMALAUGENAUGENZAHL;
            while (augenhaeufigkeiten[paschaugenzahl - 1] < paschlaenge()) {
                paschaugenzahl--;
            }
            int haeufigkeit = 0;
            for (Wuerfel w : wuerfel) {
                if (w.augen() == paschaugenzahl) {
                    haeufigkeit++;
                    neuwurfkarte.put(w,
                            haeufigkeit > paschgroesse && Kombinatorik.augenzahl(spieler.rest()) > w.augen());
                } else {
                    neuwurfkarte.put(w, !pasch() || Kombinatorik.augenzahl(spieler.rest()) > w.augen());
                }
            }
        }
    }

    /** Chance. */
    class Chance extends Unten {

        /** Zur Serialisierung. */
        private static final long serialVersionUID = -3411668587180538841L;
        /** Erwartete Punkte (alle Augen aufsummiert). */
        private double punkte;

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

        @Override
        protected void bewerte(Spieler spieler) {
            wert = augensumme;
        }

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

        @Override
        protected void waehle(Spieler spieler) {
            punkte = 0;
            for (Wuerfel w : wuerfel) {
                double m = Kombinatorik.augenzahl(w.augen(), spieler.rest());
                neuwurfkarte.put(w, m > w.augen());
                punkte += m;
            }
        }

    }

    /** Alle Standardeinträge als Tabelle zusammengefasst. */
    protected abstract class Eintragungen extends HashMap<Tabzeile, Eintrag> {
        /** Erlaubt Serialisierung. */
        private static final long serialVersionUID = 8130365096301351531L;

        /** Belegt die Standardeinträge, welche für alle Varianten der Spielregeln gilt. */
        protected Eintragungen() {
            super(Tabzeile.ALLE.length, 1);
            put(Tabzeile.EINS, new Oben(Index.EINSER));
            put(Tabzeile.ZWEI, new Oben(Index.ZWEIER));
            put(Tabzeile.DREI, new Oben(Index.DREIER));
            put(Tabzeile.VIER, new Oben(Index.VIERER));
            put(Tabzeile.FUENF, new Oben(Index.FUENFER));
            put(Tabzeile.SECHS, new Oben(Index.SECHSER));
            put(Tabzeile.CHANCE, new Chance());
        }
    }

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

        /**
         * 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, double mittlererObererZuwachs, int augensummeObereFreieEintraege,
                int anzahlFreieObereEintraege, double mittlererUntererZuwachs) {
            if (gesamt < Spieler.GRENZE && augensummeObereFreieEintraege > 0) {
                teilung = (Spieler.GRENZE - gesamt + augensummeObereFreieEintraege - 1) / augensummeObereFreieEintraege;
                double q = Kombinatorik.mindestfixgleichenwahrscheinlichkeit(teilung);
                mittlererBonusZuwachs = 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. */
        double 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.
         */
        double zuwachs(int gesamt, int guenstige, int r, int augen) {
            double 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.mindestfixgleichenwahrscheinlichkeit(n, min, r);
                }
            }
            return mittlererZuwachs + q / p * mittlererBonusZuwachs;
        }
    }

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

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

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

    /** Voraussichtlicher mittlerer Punktstand pro Feld, wobei alle drei Würfe genutzt werden. */
    private final Eintragungen eintragungen;

    /** Bester Eintrag. */
    private Tabzeile optimalzeile;

    /** Anzahl der Würfel pro Augenzahl. */
    protected final int[] augenhaeufigkeiten = new int[Wuerfel.MAXIMALAUGENAUGENZAHL];
    /** Die Karte ordnet einem Pasch (2, 3, 4, 5 gleiche Würfel) zu, ob genau ein solcher im Wurf vorhanden ist. */
    protected final Map<Paschaufzaehlung, Boolean> paschkarte = new HashMap<>(Paschaufzaehlung.values().length, 1);
    /** Im Turnier benutzte Würfel. */
    protected final Wuerfel[] wuerfel;

    /** Summe aller Augen. */
    protected int augensumme;

    /**
     * Konstruktor.
     *
     * @param wuerfel
     *            Die im Turnier benutzten Würfel.
     */
    protected Analyse(Wuerfel[] wuerfel) {
        this.wuerfel = wuerfel;
        eintragungen = eintragungen();
    }

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

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

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

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

    /**
     * @param eintrag
     *            Die Zeile in der Tabelle, zu der ein Eintrag zurückgegeben werden soll.
     * @return Eintrag zur Tabellenzeile
     */
    private Eintrag eintrag(Tabzeile eintrag) {
        return eintragungen.get(eintrag);
    }

    /**
     * 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.
     * @return Das Maximum aller Endstände.
     */
    private double findeMaximum(Spieler spieler) {
        double max = 0;
        optimalzeile = null;
        for (Tabzeile z : Tabzeile.ALLE) {
            if (spieler.setzbar(z)) {
                double endstand = eintrag(z).endsumme();
                if (endstand > max) {
                    optimalzeile = 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 double findeMinimum(Spieler spieler, double max) {
        double min = max;
        for (Tabzeile z : Tabzeile.ALLE) {
            if (spieler.setzbar(z)) {
                double endstand = eintrag(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, double min, double max) {
        for (Tabzeile z : Tabzeile.ALLE) {
            if (spieler.setzbar(z)) {
                if (max - min > 0) {
                    double endstand = eintrag(z).endsumme();
                    eintrag(z).gewichte((endstand - min) / (max - min));
                } else {
                    eintrag(z).gewichte(NEUTRALWICHTUNG);
                }
            } else {
                eintrag(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) {
        double mittlererObererZuwachs = 0;
        int anzahlObereFreieEintraege = 0;
        int augensummeObereFreieEintraege = 0;
        for (Tabzeile z : Tabzeile.OBEN) {
            if (spieler.setzbar(z)) {
                Oben eintrag = (Oben) eintrag(z);
                mittlererObererZuwachs += eintrag.mittel(spieler);
                anzahlObereFreieEintraege++;
                augensummeObereFreieEintraege += eintrag.augen;
            }
        }
        double mittlererUntererZuwachs = 0;
        for (Tabzeile z : Tabzeile.UNTEN) {
            if (spieler.setzbar(z)) {
                mittlererUntererZuwachs += eintrag(z).mittel(spieler);
            }
        }
        Zuwachs zuwachs = new Zuwachs(spieler.gesamt(), mittlererObererZuwachs, augensummeObereFreieEintraege,
                anzahlObereFreieEintraege, mittlererUntererZuwachs);
        // Tipps berechnen
        for (Tabzeile z : Tabzeile.ALLE) {
            if (spieler.setzbar(z)) {
                eintrag(z).tippe(spieler, zuwachs);
            }
        }
    }

    /** @return Alle Eintragungen in der Tabelle. */
    protected abstract Eintragungen eintragungen();

    /**
     * @param eintrag
     *            Eintrag in der Tabelle.
     * @param w
     *            Referenz auf Würfel.
     * @return Würfel nochmals würfeln für Vorschlag?
     */
    boolean erneut(Tabzeile eintrag, Wuerfel w) {
        return eintrag(eintrag).erneut(w);
    }

    /**
     * Tatsächlich erwürfelte Punkte für alle Einträge errechnen.
     *
     * @param spieler
     *            Für diesen Spieler werden die Berechnungen durchgeführt.
     */
    void errechne(Spieler spieler) {
        // Zähle Würfel pro Augenzahl, Summe bestimmen
        Arrays.fill(augenhaeufigkeiten, 0);
        augensumme = 0;
        for (Wuerfel w : wuerfel) {
            augensumme += w.augen();
            augenhaeufigkeiten[w.augen() - 1]++;
        }
        // Genau 2, 3, 4, 5 gleiche Würfel?
        for (Paschaufzaehlung pasch : Paschaufzaehlung.values()) {
            boolean istPaschVorhanden = false;
            for (int augenhaeufigkeit : augenhaeufigkeiten) {
                istPaschVorhanden = augenhaeufigkeit == pasch.groesse() || istPaschVorhanden;
            }
            paschkarte.put(pasch, istPaschVorhanden);
        }
        // erwürfelte Punktzahl für alle Einträge
        for (Tabzeile z : Tabzeile.ALLE) {
            eintrag(z).bewerte(spieler);
        }
    }

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

}
