Auf Thema antworten

Wir können das Design von [USER=70879]@KonradN[/USER] aus #47 mal aufgreifen, ein wenig ausführen und in Code gießen.


Erstmal die zwei kleinen Hilfstypen:

[code=Java]

public enum Color { GREEN, RED, BLACK }

[/code]

[code=Java]

public record RouletteResult(Color color) {

    public boolean equalsInColor(RouletteResult result) {

        return color == result.color;

    }

}

[/code]

Zu Color gibt es nicht viel zu sagen, RouletteResult ist a) ein Record (gibts seit Java 16, bitte nachschlagen, ich habe keine Lust, das an jeder Ecke zu wiederholen) und besteht b) nur aus der Farbe, weil aktuell nicht mehr interessiert. Später könnte der Wert hinzukommen.


Außerdem wollen wir noch die Wette auf eine Farbe darstellen:

[code=Java]

public record Bet(Color color, int cents) {}

[/code]



Gut, fangen wir an: was ist der Kern der Anwendung? Offensichtlich soll ein Roulette-Roboter entwickelt werden. Was macht der? Im Endeffekt spielt er (bis zu einem gewissen Punkt) Roulette und wendet dabei eine Taktik an. Die Taktik entscheidet, welche "Wetten" in der nächsten Runde abgegeben werden. Wie genau, ist ein Implementierungsdetail, das für den Roulette-Roboter irrelevant ist.


Daher erstmal eine Schnittstelle:

[spoiler=Tactic.java]

[code=Java]

import java.util.Collection;


/**

 * Von jeder Taktik zu implementierende Schnittstelle.

 */

public interface Tactic {


    /**

     * Liefert die nächsten Einsätze.

     *

     * @return  Liste der nächsten Einsätze.

     */

    Collection<Bet> nextBets();

   

    /**

     * Wendet ein Spielergebnis auf die Taktik an.

     *

     * @param  result  das letzte Spielergebnis.

     */

    void apply(RouletteResult result);

}[/code]

[/spoiler]


Das Roulette ist aus Sicht des Roboters ebenfalls ein Implementierungsdetail: es interessiert nicht, was für ein Roulette es ist, ob es sich um ein lokales oder ein Online-Roulette handelt, ob die Kommunikation wia java.awt.Robot, REST-Services, einem Selenium Web-Driver oder wie auch immer abläuft usw. Was der Roboter braucht, sind ein paar Methoden:

  • Läuft das Spiel noch, oder ist es schon vorbei?
  • Einsätze müssen vom Tisch genommen werden können.
  • Einsätze müssen platziert werden können
  • Die Runde muss gestartet werden können (Rouletterad drehen)
  • Das Ergebnis der letzten Runde muss abgerufen werden können.

Das lässt sich wieder mit einer Schnittstelle spezifizieren:

[spoiler=Roulette.java]

[code=Java]

/**

 * Von jedem Roulette zu implementierende Schnittstelle.

 */

public interface Roulette {

    /**

     * Prüft, ob das Spiel beendet ist.

     *

     * Das Spiel ist beendet, wenn keine weiteren Einsätze angenommen

     * und keine weiteren Runden gespielt werden können.

     *

     * @return {@code true}, falls das Spiel beendet ist, {@code false} sonst.

     */

    boolean isOver();

   

    /**

     * Entfernt alle Einsätze vom Tisch.

     *

     * @throws IllegalStateException     falls das Spiel bereits beendet ist.

     */

    void clearBets();

   

    /**

     * Platziert eine Wette auf eine Farbe.

     *

     * @param bet  Wette.

     *

     * @throws IllegalArgumentException  falls die Wette nicht angenommen wird.

     * @throws IllegalStateException     falls das Spiel bereits beendet ist.

     */

    void betOnColor(Bet bet);

   

    /**

     * Spielt eine Runde, dreht also das Rouletterad und

     * lässt dabei die Kugel laufen.

     *

     * @throws IllegalStateException  falls das Spiel bereits beendet ist.

     */

    void spinWheel();

   

    /** Liefert das Ergebnis der letzten gespielten Runde.

      * Mit anderen Worten, wo die Kugel im Roulettekessel der letzten

      * Runde liegen geblieben ist.

      *

      * @return RouletteResult  Ergebnis der letzten Runde.

      * @throws IllegalStateException  falls die Methode aufgerufen wurde,

      *                                bevor das Rad mind. einmal gedreht wurde.

      */ 

    RouletteResult getResult();

}[/code]

[/spoiler]


Mehr braucht es für den Roboter nicht, der muss einfach nur wissen, mit welchem konkreten Roulette und welcher Taktik er spielen soll:

[code=Java]

public class RouletteRobot {

    private final Roulette game;

    private final Tactic tactic;

   

    public RouletteRobot(Roulette game, Tactic tactic) {

        this.game = game;

        this.tactic = tactic;

    }

   

    public void play() {

        while (!game.isOver()) {

            game.clearBets();

            for (Bet bet : tactic.nextBets()) {

                game.betOnColor(bet);

            }

            game.spinWheel();

            tactic.apply(game.getResult());

        }

    }

}

[/code]

Gut, hier sollten Exceptions noch behandelt werden aber im Wesentlichen ist das der Kern der Anwendung. Taktik und das Roulette sind einfach "Add-Ons".


Wo ist die Historie? Nun, die betrachte ich als Teil der Taktik. Eine Zufallstaktik braucht z. B. keine Historie, ebenso wird diese nicht benötigt, wenn nur das letzte Ergebnis interessiert.


Wie könnte nun eine solche Taktik aussehen?

[code=Java]

import java.util.ArrayList;

import java.util.Collection;

import java.util.Collections;

import java.util.List;


public class BerndoaTactic implements Tactic {

    public static record ColorBet(int centsOnColor, int centsOnZero) { }


    private final List<ColorBet> bets;

    private final History history;

    private final int streakLength;


    private int round = 0;

    private boolean initialized;

   

    public BerndoaTactic(List<ColorBet> bets, History history, int streakLength) {

        this.bets = new ArrayList<>(bets);

        this.history = history;

        this.streakLength = streakLength;

    }

   

    @Override

    public Collection<Bet> nextBets() {

        if (!initialized || round >= bets.size()) {

            return Collections.emptyList();

        }


        Color color = determineColor();       

        ColorBet nextBets = bets.get(round);

        round++;

       

        return List.of(

                new Bet(color, nextBets.centsOnColor()),

                new Bet(Color.GREEN, nextBets.centsOnZero()));      

    }

   

    private Color determineColor() {

        RouletteResult lastResult = history.getLastResult();

        return lastResult.color() == Color.RED ? Color.BLACK : Color.RED;

    }

   

    @Override

    public void apply(RouletteResult result) {

        history.add(result);


        if (!initialized) {

            if (history.getStreakLength() >= streakLength) {

                initialized = true;

            }

        }

    }

}[/code]


Hier wird also eine Historie benötigt, die nicht wirklich viel macht:

[spoiler=Historie.java]

[code=Java]

import java.util.Iterator;

import java.util.LinkedList;

import java.util.Deque;


public class History {

    private final Deque<RouletteResult> results;

   

    public History() {

        results = new LinkedList<>();

    }

   

    public History(Deque<RouletteResult> results) {

        this.results = results;

    }

   

    public void add(RouletteResult result) {

        results.addLast(result);

    }

   

    public RouletteResult getLastResult() {

        return results.getLast();

    }

   

    public int getStreakLength() {

        int len = 0;

       

        if (!results.isEmpty()) {

            RouletteResult result = getLastResult();

       

            Iterator<RouletteResult> it = results.descendingIterator();

            while (it.hasNext() && it.next().equalsInColor(result)) {

                len++;

            }

        }

       

        return len;

    }

}[/code]

[/spoiler]

Der zweite Konstruktor kann zum Laden einer zuvor gespeicherten Historie verwendet werden, tatsächlich dient er aber der Testbarkeit der Klasse.


Die Implementierung des Roulette erspare ich mir. Nur so viel: die Methoden können natürlich beim Aufruf ggf. den Browserstart veranlassen. In isGameOver() lässt sich prüfen, ob die Zeit rum ist, der Browser schließen und true zurückgeben. Auch spare ich mir an der Stelle den ExcelReader.


Die Teile werden einfach z. B. beim Start der Anwendung zusammengefügt:

[code=Java]

public class App {

    public static final void main(String[] arg) {

        List<BerndoaTactic.ColorBet> bets = // ... z. B. einlesen aus Datei

        Tactic tactic = new BerndoaTactic(bets, new History(), 7);

        Roulette roulette = new BerndoaRoulette(...);

        new RouletteRobot(roulette, tactic).play();

    }

}

[/code]


Willst Du zu einem Casino mit anderer Bedienung wechseln? Einfach neue Roulette-Implementierung schreiben und die verwenden. Bei gleicher Bedienung und anderen Layouts bietet es sich an, die Roulette-Implementierung derart zu gestalten, dass ein Layout übergeben werden kann. Willst Du eine andere Taktik ausprobieren? Kein Problem, einfach Tactic implementieren und verwenden. Willst Du verschiedene Taktiken abwechseln? Auch kein Problem: einfach eine Tactic implementieren, die zwischen bestehenden Taktiken wechselt.


Ach, damit ich es nicht vergesse: der Code soll den Spaß nur skizzieren und ist dementsprechend unvollständig und auch nicht getestet, die Taktikimplementierung dürfte im Großen und Ganzen Deiner Beschreibung entsprechen, musst Du aber ggf. anpassen usw.



Oben