Monopoly - Aufteilung der Karten

So, nach dem mir meine Schwester mal unser ur-alt-Monopoly (noch von vor der Euro-Umstellung) rumgebracht hat und ich endlich mal die Gelegenheit hatte mich etwas genauer mit den Gemeinschafts- und Ereigniskarten zu befassen - die ich trotz intensiver Suche in mehreren Sprachen ums Verrecken einfach nicht im Netz finden konnte (copyright?) - kam mir nun die Frage nach dem Design des Codes. Dabei war mir klar: Ich muss vorher die Karten halbwegs sinnvoll strukturieren. Dabei ist mir aufgefallen: Es gibt letztlich nur 4 Typen von Karten:

  1. Der Spieler bekommt Geld.
  2. Der Spieler verliert Geld.
  3. Der Spieler bewegt sich.
  4. Spezial: jeweils die beiden Knast-Frei-Karten sowie die eine Ereigniskarte die zur Zahlung auffordert oder sonst auf das Ziehen einer Gemeinschaftskarte verweist.

Weiter gibt es mögliche Unterteilungen:

  1. Der Spieler bekommt Geld …
    a) von der Bank.
    b) von anderen Spielern.

  2. Der Spieler verliert Geld …
    a) an die Bank.
    b) an andere Spieler.

  3. Der Spieler bewegt sich und …
    a) erhält Geld …
    I) von der Bank.
    II) von anderen Spielern.
    b) verliert Geld …
    I) an die Bank.
    II) an andere Spieler.
    c) hat die Möglichkeit ein Grundstück zu kaufen, eine Versteigerung zu starten oder aufzuwerten.

  4. Für die Spezial-Karten fehlt mir aktuell noch die mögliche ausführliche Analyse bzw. ein Konzept der Umsetzung, da die Knast-Frei-Karten ja erst vom Spieler selbst ausgelöst werden bzw. sich bei Ziehen der anderen besagten Karte die Möglichkeit für ein weiteres Ziehen einer weiteren Karte ergibt - was ja so im “normalen” Spiel-Ablauf nicht drin ist - und daher irgendwie einen komplizierten Sonderfall darstellt.

Ergibt sich schon mal ein doch recht verzweigter Baum was bei einem Treffer auf ein Ereignis- bzw. Gemeinschaftsfeld so alles passieren kann.

Für den Code selbst macht es keinen Unterschied ob ich eine Gemeinschafts- oder eine Ereigniskarte ziehe - also brauche ich dafür keine unnötige Unterteilung in zwei Klassen und kann mit einer gemeinsamen Klasse “Card” arbeiten. Auch fällt ein Flag für den Typ weg da die Karten in zwei getrennten Listen (jeweils 16) gehalten werden. Eine Karte muss selbst also auch nicht wissen ob sie eine Gemeinschafts- oder eine Ereigniskarte ist.
Nun frage ich mich aber: Ist es sinnvoll eine Unterteilung nach dem oben erklärten Baum zu machen? Denn ob ein Spieler Geld bekommt oder verliert - und ob diese gegenüber der Bank oder anderen Spielern passiert - ist meiner Ansicht nach auf der gleichen Ebene. Man kann also wie oben nach erhalten/verlieren -> Bank/andere Spieler gehen oder nach Bank/andere Spieler -> erhalten/verlieren. Ich denke hier werde ich mich einfach für eine der beiden Varianten entscheiden müssen - da ich mir zumindest keine kombinierte Unterteilung so richtig vorstellen kann.

Auch wäre dann grundsätzlich noch die Frage: Auf welches Objekt soll die Karte wirken? Wenn ich also Card.doAction(T) habe - von welchem Typ sollte T sein? Mögliche Idee wäre vom Typ Player - da eine Aktion auf einem bestimmten Spieler ausgeführt wird (Erhalt/Verlust von Geld, Bewegung, Knast-Frei/erneutes Ziehen) und die Interaktion mit dem Rest des Spiels (also über die aktuellen Session auch mit den anderen Spielern) bereits abgebildet ist (so in die Richtung currentSession.getPlayer() oder sowas).

Ihr seht: Hier und da die eine oder andere Idee hab ich schon - aber ich stehe dann doch vor der Frage: Wie nun weiter?
Es gibt ja auch noch andere Baustellen wie z.B. dynamische Regeln - also in Richtung Begrenzung von Häusern/Hotels, “Steuern” in Frei Parken oder an die Bank?, doppeltes Income auf Los …

Ich fürchte einfach jetzt schon nur durch die Überlegung während des Schreibens unnötig Spaghetti-Code zu “erdenken” den ich eigentlich durch eine halbwegs saubere Struktur vermeiden will.

Kleine Anmerkung am Rande: Es soll erstmal ohne größere Geschosse wie DI-Frameworks oder modular via OSGI ablaufen. Darüber kann ich mir, wenn Refactoring nötig wird, immer noch Gedanken machen.

Danke erstmal für alle die sich hier mit dran beteiligen - ich geh mich in der Zwischenzeit mal mit den möglichen Copyright-Problemen vertraut machen …

Spontan würde ich nur eine Klasse Card erstellen und den Typ der Karte als Enum (Bewegung / Geld / …) nehmen. Dann noch ein Enum dafür wohin das Geld geht (Bank / anderer Spieler) etc.

ich sehe den Gedankenweg von Übergabe Player-T zur Erkenntnis dass dieser auch über currentSession.getPlayer() abgefragt werden kann, nicht

die Karten brauchen wohl gar keine Typisierung, es ist nichts zu unterscheiden, wie du selber sagst schon nicht zwischen den beiden Kartenstapeln,
aber auch sonst ‚Spieler bewegt‘ usw., nie wird das in einer Anzeige oder Ausführung relevant, jede Karte hat ihren Code hinterlegt,
wohl Enum mit individuellen Untermethoden, und der wird ausgeführt, fertig

und bei der Ausführung auch keine Parameterübergabe:
die ‚Session‘ mit dem aktuellen Spieler, Bank, allen Spielern und allem anderen sollte intern hinterlegt sein


[quote=Sen-Mithrarin;134912]Ich fürchte einfach jetzt schon nur durch die Überlegung während des Schreibens unnötig Spaghetti-Code zu „erdenken“ den ich eigentlich durch eine halbwegs saubere Struktur vermeiden will.

[…] Darüber kann ich mir, wenn Refactoring nötig wird, immer noch Gedanken machen.[/quote]
der letzte Satz passt eigentlich zu allem Code allgemein:
hier ist es ein überschaubares Programm, welches auch nicht später noch zur Atomkraftwerksteuerung wird,
so viel Text und Vorüberlegung wie in diesem Posting hier ist das gar nicht wert, einfach der Reihe nach umsetzen,

wenn erst nach drei Karten als eigene Objekte/ Unterklassen auf die Idee Enum gekommen, dann eben den Code dazu rüberschieben usw.,
dabei kann nicht viel schiefgehen, der ganze Code fertig noch durch drei neue Frameworks geschoben dürfte auch überschaubar bleiben, wenig Nudel-Gefahr

soll nicht heißen dass am Ende die Struktur egal ist, Möglichkeiten zur Verbesserungen wird es dann bestimmt noch geben und auch angebracht sie dann umzusetzen,
aber das sieht man dann fertig viel besser,
vorher nachdenken, hmm, kann auch schrecklich ineffizient sein, wenn ich diese Card-Unterteilung sehe…, und was lernt man schon davon,

im Code mehrere Varianten zu programmieren, lauter kleine Fehler zu erhalten und zu korrigieren, sich über zu doppelnden Code ärgern,
automatisch auf die besseren Strukturen wechseln, das ist meine Empfehlung, davon lernt man so richtig

Hmm, hab mich mit Enums bisher noch nicht beschäftigt. Ich weis zwar schon dass man in der Enum-Deklaration auch Methoden überschreiben kann, aber wenn ich z.B. zwei Enums BANK und GET habe muss ich das Enum ja so designen dass es diese Verkettung ermöglicht. Ich hatte jedoch eher im Sinn eine doAction() Methode zu haben die dann je nach Typ eben entsprechend implementiert ist.
Möglich wird es sicher sein, nur fehlt mir mangels Wissen über Enums und derrn saubere Verwendung ein möglicher Einstiegspunkt um den Stein ins Rollen zu bringen.

Letztlich würde es auch eine Art command-chain machen - nur will ich halt if()-Blöcke vermeiden - bringt ja nichts das Enum nur als Identifier zu nutzen und dann mit if() die richtige Aktion auswählen - dass würde ich dann schon gerne in die Implementierung verlagern.

Kann mir jemand dazu mal ein halbwegs vernünftigrs Tutorial linken (ja, das Sun-Tut werd ich mir noch ansehen)?

falls auf mich bezogen: allzu konkret dachte ich gar nicht,
aber wenn überhaupt, dann hatte ich sicherlich durchaus eine Enum mit

    Karte1 {
        void doAction() {   }
    },
    Karte2  {
        void doAction() {   }
    },
    
    ;

    abstract void doAction();
}

im Blick,

Enum braucht es bei so anonymer Auflistung von Karten gar nicht unbedingt,
sonstige (anonyme innere) Subklassen gehen auch a la ActionListener, aber besser wird andere Definitionen sicherlich auch nicht, Enum-Variante schadet nicht


'Dann noch ein Enum dafür wohin das Geld geht (Bank / anderer Spieler) etc. ’ von mogel ist davon eher unabhängig,
innere Implementierung was die Actions alles so machen, wieviel Code doppelt usw.

edit:

        void doAction() {  payTo(); // Felder nutzen
           }
    },

oder gar

                            // Standardimplementierung der Methode wird meckern falls Lage nur mit Konstruktorparametern unklar

hätte natürlich auch was für sich, gar keine Methode mehr zu überschreiben oder nur bei wenigen Karten wo unnötig extra einen Typ zu hinterlegen,

kann man bauen wie man will,
in der Standardmethode dann freilich ein switch bzw. if/ else über Art der Aktion, Art des Empfängers usw., nix schlimmes,
oder die Enum mit TypePayTo hat wiederum eine Methode implementiert, wo der Code drinsteht…, nur aufzurufen


Karte holen vom Stapel und doAction() aufrufen, da ist doch kein if dabei?

Ich würde dir gerne den Google Suchbegriff „Component Entity System“ ans Herz legen.
Fällt denke ich in die selbe Richtung. Darunter findet man Code-Architektur-Lösungen für PowerUps, Upgrades, Fähigkeiten, etc… wie sie auch in großen Spielen eingesetzt werden.

Wenn ich dich jetzt nicht falsch verstanden habe. Vielleicht hilft es dir. :wink:

Grüße
TMII

@TMII
Auf jeden Fall mal einen Blick wert - werd ich mir mal weiter geben …
@SlaterB
Das mit “if() vermeiden” war in folgende Richtung gemeint: Das Enum quasi nur als Flag und dann in der doAction() halt dann sowas:

{
	if(type==move) moveAction();
	if(type==payment)
	{
		if(paymentType==get) getPaymentAction();
		if(paymentType==pay) payPaymentAction();
	}
	if(type==special)
	{
		if(specialType==jail) jailAction();
		if(specialType==drawCommunity) ...
	}
}```
Das soll halt alles "etwas" verschwinden.
Wenn ich also eine MovementCard habe soll deren doAction() Methode halt lediglich move-Code enthalten.
Habe ich eine PaymentCard könnte ich noch unterschieden zwischen "pay" und "get" und "bank" und "player" - auch wenn beide gleichwertig sind.
Was die "spezial"-Karten angeht - Konzept fehlt noch.

Für das Laden der beiden Stapel sollen normale List genutzt werden - Da mal noch die Zwischenfrage: Bringt die SE-API selbst schon eine Liste mit sowas wie "next()" einfach das nächste Element zurückgibt und am Ende dann von selbst wieder auf 0 setzt oder muss man sich sowas selbst basteln?
Letztlich also nur ein list.add(new Card()) dem dann der Type übergeben wird, ggf sub-type (z.B. PlayerPayment - BankPayment mit dem sub-type get oder pay).

Interessant dürften auch noch die beiden Knast-frei Karten werden. Denn die behält ein Spieler ja so lange bis er sie selbst nutzt oder an andere Spieler vertickt. Nun darf aber, während diese Karten bei Spielern im Umlauf sind, der Stapel diese nicht erneut liefern bis sie wieder in ihm landen. Wäre es hier sinnvoll der Card selbst ein Attribut "ownedByPlayer" oder sowas zu geben um halt anzuzeigen - Karte im Stapel oder auf Spielerhand ?

Werd mal versuchen einen möglichen Anfang zu basteln ... doch irgendwie weit schwieriger als ich dachte.

wie zumindest wiederum auch schon angesprochen wäre

{
    type.doAction(); // evtl. noch Parameter, am kürzesten this
}

vorstellbar


anbetracht Karten beim Spieler scheint es geeignet, beim Karten-Ziehen nahe an der Realität zu bleiben:
wirklich erstes Element aus Liste entfernen, kann dann beim Spieler bleiben, später mal an anderer Stelle (!) wieder einsortiert werden, oder meist gleich zurückgelegt, ans Ende der Liste

kein komplizierter Iterator mit Merken der Position nötig,
Queue evtl. anschauen, sonst selber LinkedList vielleicht geeigneter als ArrayList wegen ständiger Änderungen vorne + hinten

Hmm, das sieht aus als solle es nur eine einzige doAction-Methode geben die alles kann? Gemeint war glaube ich eher eine Methode pro Karte, in etwa so:

enum Ereigniskarte { 
        
        Zahle_100_Euro_an_Bank {
            @Override
            public void doAction(Spieler spieler) {
                spieler.pay(100);
                bank.receive(100);
            }
        },
        
        Ruecke_vor_zum_naechsten_Bahnhof {
            @Override
            public void doAction(Spieler spieler) {
                spieler.moveTo( ... );
            }
        },
        
         ...;

        public abstract void doAction(Spieler spieler);
    };
    
    
    public static void main(String[] args) {
        // Stapel anlegen
        Queue<Ereigniskarte> EreigniskartenStapel = new LinkedList( Arrays.asList(Ereigniskarte.values() ));

        // Karte ziehen
        Ereigniskarte karte = EreigniskartenStapel.poll();
        karte.doAction( aktuellerSpieler );
        EreigniskartenStapel.add(karte);
    }

Eine Unterteilung der Karten in verschiedene Kategorien würde ich dabei allerdings nicht vornehmen wollen. Dürften ja nur ca. 20 verschiedene Karten sein - die sich jedoch in ihren Details soweit unterscheiden das am Ende nicht viele Karten ein gemeinsames Interface nutzen können (iirc gab’s ja nicht nur payToBank und payToPlayer sondern auch payToEachPlayer und so’n Zeug).
Ungeachtet dessen fänd ich es allerdings ärgerlich das die obige Methode Ereigniskarte.doAction weitreichende (zu weitreichend für meinen Geschmack) Befugnisse bräuchte um alle möglichen Aktionen ausführen zu können. -_-
Vielleicht wäre es eine Alternative doAction() jeweils eine Liste von Commands zurückgeben zu lassen welche dann vom Hauptprogramm ausgeführt werden?

Da hätte man auch als Hobby-Java-Bastler auch selbst drauf kommen müssen dass die Kartenstapel FIFO-Stacks sind - und dafür gibts ja z.B. LinkedList mit addLast() und removeFirst(). Wäre zwar schon eher in Richtung LinkedQueue gegangen - aber die kommen nicht von List - und können daher nicht einfach mit Collections.shuffle() “gemischt” werden. Danke für den Wink mit dem Zaunpfahl.
@Dow_Jones
Die Karten selbst definieren grundsätzlich nur PayToEachPlayer. Es gibt zwar die Bahnhofskarte mit dem “spezial” dass der Besitzer die doppelte Miete erhält - aber dass hatte ich geplant nicht über Card.doAction() sondern Field.enter() (im Sinne von Spieler betritt Feld) zu lösen und über die Karte vielleicht nur ein entsprechendes Flag zu setzen.
Auch soll es eben keine einzige doAction() geben die dann mit if-Blöcken rauskramt was zu tun ist - sondern dies soll ja in den einzelnen Implementierungen “verschwinden”.

Habe nicht alles gelesen (ja, schlecht, ich weiß … bin gerade auf der Arbeit, Mittagspause… wie auch immer)

Aber zumindest das hier

Ist mir auch aufgefallen. Also… ich habe gelgentlich die Tendenz, Dinge sehr vordergründig-puristisch zu sehen. In diesem Fall könnte man das lesen als:

Die Karte gibt dem Spieler 100€

Äähwas? Das klingt irgendwie falsch. Man würde eher sagen, dass die Karte besagt, dass eine bestimmte Aktion ausgeführt werden soll (und zwar von einer (ansonsten ja oft fragwürdigen) „Gottgleichen Instanz“ (also „vom Spiel selbst“).

Auch das die Karten „enum“-Objekte sein sollen, erscheint mir nicht sinnvoll. Wieder ähnlich vordergründig:

Eine Karte ist ein Element einer Aufzählung.

Häh? Nee. Eine Karte ist ein Objekt. Ein ganz normales. Man könnte drüber streiten und philosophieren, ob ZWEI Karten, die „gleich“ sind, dieSELBE Karte sein sollen, aber wieder ganz intuitiv: Nein. Es ist zweimal die gleiche Karte, aber eben nicht zweimal dieSELBE. Wenn es eine Karte mit der Bedeutung/dem Text gibt: „Zahle jedem Spieler 100€“, dann kann diese Karte (wenn sie zweimal existiert) auch zweimal im Stapel vorkommen. Es sind aber trotzdem zwei Instanzen der gleichen Karte. Man kann nicht in einem Stapel zweimal dieSELBE Karte an verschiedenen Stellen haben.

Wäre es hier sinnvoll der Card selbst ein Attribut „ownedByPlayer“ oder sowas zu geben um halt anzuzeigen - Karte im Stapel oder auf Spielerhand ?

Dass eine Karte in diesem Sinne einen „inhärenten Zustand“ hat, den sie SELBST kennt, halte ich ebenso für fragwürdig. Die Karte ist nicht veränderbar. Man kann sie zwar knicken oder verbrennen :wink: aber ob sie in einem Stapel liegt, oder ob ein Spieler sie in der Hand hat, das weiß die Karte selbst nicht.


So viel zu verdergründig-gespielt-naiv-intuitiver Modellierung. Spontane Gedanken als Code:

class Card
{
    private final String textAndStuff;
    private final Runnable action;

    Game(String textAndStuff, Runnable action) {
        this.textAndStuff = textAndStuff;
        this.action = action;
    }

    // Hier nochmal überlegen:
    Runnable getAction() { return action; }
    void exectue() { action.run(); }
}




class Game
{
    private Player activePlayer;
    private final Deque<Card> ereigniskarten = ...;

    Game()
    {
         ereigniskarten.addFirst(new Card("Spieler kriegt 100€", createPayAction(100));
         ereigniskarten.addFirst(new Card("Spieler zahlt 100€", createPayAction(-100));
         ...
    }

    private Runnable createPayAction(int amount) 
    {
        return () -> { activePlayer.pay(amount); };
    }

}



class Player
{
    // Ggf. auch ein Optional<...>, aber da kann man streiten...
    private Card duKommstAusDemGefängisFreiKarte = null;

}

Nur kurz (Mittagspause). Interessanter finde ich hier dann die Modellierungsfragen:

  • Woher weiß man, auf wen sich die Karte bezieht? Hier ist das mit dem „activePlayer“ angedeutet. Aber eine Verallgemeinerung, GROB im Sinne von
class TransferAction {
    MoneyOwner source;
    MoneyOwner target;
    int amount;
    void execute() { ... }
}

oder doch

class TransferAction {
    int amount;
    void execute(MoneyOwner source, MoneyOwner target) { ... }
}

oder all den möglichen Optionen dazwischen wäre vielleicht eine Überlegung wert. (Da war vor nicht allzulanger Zeit ein Thread dazu, wo jemand in einem YouTube-Video über „ZU Objektorientierte“ Programmierung gerantet hat, mit Zuständen und Abhängigkeiten - finde den Link gerade nicht, ggf. nochmal heute abend schauen, wenn niemand schon weiß, welchen Thread ich meine und den Link findet ;-))

  • Wie wird der Spielablauf an sich gesteuert? Also, was ist der Punkt bzw. der Verantwortliche für die Ausführung von sowas wie
Card takenCard = ereigniskarten.removeFirst();
takenCard.execute();
takenCards.add(takenCard);
if (ereigniskarten.isEmpty())
{
     Collections.shuffle(takenCards);
     ereigniskarten.addAll(takenCards);
     takenCards.clear();
}
```?
Vielleicht kann der "activePlayer" auch bei "Card#execute" übergeben werden, aber ... reicht das? 
...


Es gibt sicher noch weitere offene Fragen, aber ... Mittagspause fast rum...

[quote=Marco13]Eine Karte ist ein Element einer Aufzählung.

Häh? Nee. Eine Karte ist ein Objekt. Ein ganz normales. Man könnte drüber streiten und philosophieren, ob ZWEI Karten, die „gleich“ sind, dieSELBE Karte sein sollen, aber wieder ganz intuitiv: Nein. Es ist zweimal die gleiche Karte, aber eben nicht zweimal dieSELBE. Wenn es eine Karte mit der Bedeutung/dem Text gibt: „Zahle jedem Spieler 100€“, dann kann diese Karte (wenn sie zweimal existiert) auch zweimal im Stapel vorkommen. Es sind aber trotzdem zwei Instanzen der gleichen Karte. Man kann nicht in einem Stapel zweimal dieSELBE Karte an verschiedenen Stellen haben.[/quote]

zumindest dieser Punkt hier kaum zu befürchten, jede der 2x 16 (nachgeschlagen) Karten wäre gewiss eigener Enum-Wert, nix doppelt

wie ich anfangs mal geschrieben habe wäre

         ereigniskarten.addFirst(new Card("Spieler zahlt 100€", createPayAction(-100));

die alternative Schreibweise anonymer Objekte (oder anonymer innerer Klassen falls noch mit überschriebener Methode ausgestattet),
sie als Enum zu deklarieren

         Karte1("Spieler kriegt 100€", createPayAction(100)),
         Karte2("Spieler zahlt 100€", createPayAction(-100)),

hat keinen großen Vor- oder Nachteil, (geht aber evtl. gar nicht, je nachdem was man sich unter createPayAction vorstellt, was da an Zugriff nötig ist)

Definition ist schnell zu finden, in der Enumklasse vs. wo nochmal die Listen initialisiert? :wink:
kurzer Name mag für Log/Debug nett sein, nicht selbst zu vergeben,

man muss allerdings später noch in Listen einfügen, wenn auch nicht unbedingt mit 32 Aufrufen,
dafür einfacher alle 32 Karten zu initialisieren, etwa mit Game-Context, falls dies nötig sein sollte


soweit ich es kenne kommt die Karte auch wieder nach unten in den Stapel, keine zwei Stapel und Neu-Mischen wenn alle einmal durch :wink:


lustig langer Thread schon jetzt geworden

Die angesprochene Unterscheidung “enum oder nicht” scheint hier im ersten Moment ein Detail zu sein. Es mag sein, dass ich davon beeinflusst bin, dass enums in C einfach nur ints waren :grampa: , aber bin etwas zurückhaltend, wenn es darum geht, Funktionalität im Element einer Aufzählung unterzubringen. (Genau wie ich es nicht schön finde, ein Singleton dadurch zu modellieren, dass man es als “enum” deklariert - irgendwie wirkt es, als würde da ein Sprachkonstrukt, das einen eigentlich sehr spezifischen Zweck erfüllt (hat), sehr weit gedehnt, und bestimmte semantische Artefakte ausgenutzt: “Die SIND Singletons, also kann man sie auch dafür verwenden”).

Bei der Funktionalität, die in so einem Element einer Aufzählung untergebracht wird, gibt es aber IMHO noch zweieinhalb wichtige Unterscheidungen:

  • Enums mit Zustand? NEIN!
  • Funktionalität, die sich NUR auf das enum bezieht? … Jooaaa…
  • Funktionalität, die sich auf die Umgebung bezieht? Neeee

Zweiteres wäre sowas wie eine Methode, die (grob) nur primitive Typen übergeben bekommt, nur auf Fields der Enum operiert, und/oder ggf. was zurückgibt. (Wie etwa (Textsuche) “surfaceWeight” und “surfaceGravity” in den Beispielen auf https://docs.oracle.com/javase/8/docs/technotes/guides/language/enums.html )

Letzteres wäre ja das, was wohl in irgendeiner Form notwendig wäre: Irgendwie muss sich die Aktion, die zu einer Karte gehört, auf den “globalen”, aktuellen (!) Spielzustand beziehen. Offensichtlich: Die Bedeutung von “Du bekommst 100€” hängt davon ab, ob nun Spieler A oder Spieler B die Karte zieht. WO kommt diese Information WIE her? (Das ist IMHO eine der spannenderen Fragen).

EDIT: Im letzeren Sinne, wieder grobe Gedanken: Sowas wie

Runnable generateAction(Card card, Player whoTookThisCard) 
{
    ...
}

wäre zumindest ein Denkansatz, auch wenn er weitere Fragen aufwirft (und ja, bestimmten Dingen, die ich vorher gesagt habe, zu widersprechen scheint ;-))

Wenn ich genauer darüber nachdenke - bei dem gegebenen (überschaubaren) Problem finde ich die Idee ein großes switch in der Hauptschleife zu verwenden gar nicht so blöd. :smiley:

Das hatte ich in meinem EDIT kurz geschrieben/angedeutet, dann aber wieder gelöscht :smiley: Es stimmt schon: Kurz vorher hatte ich noch überlegt, ob man vielleicht eine „ActionFactory“ einführen sollte (und wahrscheinlich kommt jemand dann mit Dependency Injection), aber … es sind 16 Karten. Es werden nie mehr oder weniger. Wenn die implementiert sind, ist es per Definition fertig. Vielleicht muss man seinen Generalisierungs- und Abstraktionsinstinkt auch manchmal einfach unterdrücken :rolleyes:

‚Monopoly - Star Wars Edition‘ :wink:

Gut moeglich, Enums sind in Java eben eine Form von Klassen und Instanzen, da darf man sehr wohl Logik unterbringen, wird u.a. auch von Joshua Bloch in Effective Java empfohlen und man sieht es auch in den Java Quellen, wie bei TimeUnit (Java Platform SE 7 )
Enums sind sehr gut dazu geeignet um das Strategy Muster zu implementieren (Strategie (Entwurfsmuster) – Wikipedia), manchmal kommen die Einschraenkungen im Bezug auf Vererbung einem in die Quere… alles in allem ist die Frage ob Enums oder normale Klassen eben nur ein Implementierungsdetail… switch bzw. if/else waeren IMHO eher als Gegenteil vom Strategy Muster zu sehen (OO vs. prozedural), das ist die wichtigere Designentscheidung fuer mich.

[QUOTE=maki]Gut moeglich, Enums sind in Java eben eine Form von Klassen und Instanzen, da darf man sehr wohl Logik unterbringen, …
Enums sind sehr gut dazu geeignet um das Strategy Muster zu implementieren…[/QUOTE]

Ich hatte ja versucht, das auszudifferenzieren. Die Funktionen z.B. in TimeUnit machen ja meistens nichts anderes, als eine Information über die Enum-Konstante selbst in spezieller (ggf. konvertierter) Form nach draußen zu geben.

Mein Hinterfragen bezog sich wirklich auf die Verantwortlichkeiten, auf - zugegeben - schon fast esoterischer Ebene: Ich finde, dass es in bezug auf die Modellierung schon einen Unterschied macht, ob dieses kleine, am Rand des UML-Diagramms liegende Enum-Konstantchen die Kompetenz hat, einem Spieler Geld aus der Tasche zu ziehen und einem anderen zuzustecken, oder ob es nur eine (ihm selbst nicht näher bekannte, sondern ihm nur von außen (d.h. vom Spiel) zugewiesene “Aktion” (Runnable) verwaltet.

Also, wenn so eine Enum-Konstante eine Referenz auf eine übergeordnete Klasse braucht, klingt das für mich irgendwie falsch. Eine Karte “HAT” kein Spiel. Eine Karte “HAT” keine Spieler. Wie würde man die Aktion “Übertrage 100€ von Spieler A an Spieler B” in so einer Karte denn nun tatsächlich ausimplementieren? Natürlich könnte man sowas machen wie

class/enum Card {
    ...
    @Override
    void executeWhatYouHaveToDoOn(Game game) {
        int money = game.takeMoneyFrom(game.getActivePlayer());
        game.giveMoneyTo(game.getPreviousPlayer(), money);
    }
}

Aber ich fände

class/enum Card {
    ...
    @Override
    void executeWhatYouHaveToDo() {
        runnableThatWasGivenInConstructor.run();
    }

    // Oder
    @Override
    Runnable getAction() {
        retrun runnableThatWasGivenInConstructor;
    }

    // Oder auch
    @Override
    Consumer<Game> getActionToExecuteOnGame() {
        retrun consumerThatWasGivenInConstructor;
    }


}

class Game
{
    void createCards() 
    {
        card = new Card(this::transferMoney);
        
        // oder entsprechend
        card = new Card( game -> game.transferMoney());
        ...
    }
    void transferMoney()
    {
        int money = takeMoneyFrom(getActivePlayer());
        giveMoneyTo(getPreviousPlayer(), money);
    }
}

einfach konzeptuell sauberer getrennt.

(Da sind immernoch viele Fragen offen - wie gesagt, die Diskussion ist sehr high-level…)

Wie immer erstmal Danke an alle die sich hier in ihrer Freizeit so stark engagieren und mir doch sehr eindrucksvoll aufzeigen: Was für einen Mensch mit ein paar simplen Logik-Regeln ein fertiges Spiel macht ist doch sehr schwer einem Rechner beizubringen.
Leider muss ich zugeben wohlmöglich einen schweren Konzept-/Design-Fehler gemacht zu haben in dem ich mich erst wie wild auf diese 32 Karten (Jemand Lust auf Skat?) gestürzt habe und somit quasi von innen heraus das Projekt umzusetzen anstatt wie es vermutlich sinnvoller wäre erstmal von außen wichtige Fragen zu klären wie “Wie läuft eine Runde ab?” und “Wie und woher kennen die einzelnen Instanzen ihre “Mitspieler”?”.
Sind diese Dinge geklärt dürfte sich für die Karten eine sehr einfache Umsetzung ergeben (oder eben nicht - was dann zeigt dass der Rest schon Käse ist).

Naja - ich mach jetzt erstmal entspannt 2 Wochen Urlaub auf Rügen - werde mich mal mit nem Blatt und nem Bleistift ransetzen - dürfte wohl produktiver sein als diese Gedankenspiele.
Werde ich posten? Nein! Werde ich trotzdem weiter mitlesen? Abends dann vielleicht in ner ruhigen Minute wenn ich zu komme.