Problem mit Klassenhierarchie.



  • Soweit das ich eine virtuelle Methode "Aktion" mache, welche jede FeldKlasse enthält hab ich noch nicht gedacht *schäm*.
    Hört sich eigentlich Prima an, bin nur grad am überlegen ob ich es bei mir auch einsetzen kann.

    Blos, was ist wenn ich mir aus einer Feldklasse mit getter Methoden versch. Daten holen muss, welche ich in meiner Board Klasse ( da diese eigentlich alle Spielabläufe implementiert) benötige. Bis jetzt hab ich die nötigen Methoden direkt von Board aufgerufen und hatte somit den passenden return wert.
    Läuft jetz jeder Feld-Aufruf in Board über "Aktion" so hab ich ein und den selben return Wert egal was ich brauche. : (

    Bestimmt hast du auch dafür eine Lösung, mir fällt keine ein die nicht ungalublich aufwendig wäre *g*

    Bin dann gleich ab ins WE erst Mo wieder da, werd dann noch Codeauszüge posten, würde sich jetz nimmer ausgehn.
    lg



  • sust schrieb:

    Blos, was ist wenn ich mir aus einer Feldklasse mit getter Methoden versch. Daten holen muss, welche ich in meiner Board Klasse ( da diese eigentlich alle Spielabläufe implementiert) benötige.

    Was für Daten muss denn das Board holen? Und vor allem zu welchem Zweck?

    Wenn das Board mit Daten einer speziellen Feldklasse hantiert, macht es etwas feldpsezifisches - und diese feldspezifischen Aktionen kann man eigentlich immer ins jeweilige Feld auslagern.
    Und wenns mal garnicht anders geht hilft häufig sowas:

    Ansatz mit ekligen casts:

    class Board 
    {
      void doEreignis(EreignisFeld& ef);
      void doBuyable(BuyableFiled& bf);
      void doGefaengnis(GeafengisField& gf);
    
      void allgemein()
      {
      if (isereignisfeld)
        doEreignis(dynamic_cast<EreignisFeld&>(*field)); /iih ein cast...
      else if (isbuyble)
        doBuyable(/*... noch ein cast */)
      else //usw.
      }
    };
    

    besser, ohne casts:

    class Board {
      void doEreignis(EreignisFeld& ef);
      void doBuyable(BuyableFiled& bf);
      void doGefaengnis(GeafengisField& gf);
    
      void allgemein()
      {
        field.callspezifisch(*this); //callspezifisch ist virtuell in Field...
      }
    };
    
    EreignisFeld::callspezifisch(Board& theBoard)
    {
      theBoard.doEreignis(*this);
    }
    
    BuyableField::callspezifisch(Board& theBoard)
    {
      theBoard.doBuyableField(*this);
    }
    
    //usw.
    

    Klar, jetzt muss das Board alle Feldtypen kennen, aber das musste es vorher auch schon.



  • Mal ein kurzer Codeausschnitt:

    #include <string>
    #include <vector>
    
    #ifndef BOARD_H_
    #define BOARD_H_
    
    class Piece;
    class Fields;
    class Player;
    
    class Board
    {
      private:
        Piece *piece_;
        Fields *field_ptr_[40];
        int bank_;      // Bank VORERST als integer deklariert.
        Board(Board &);   //No Shallow Copys.
        std::vector<Piece*> pieces_;  // nötig wegen Rückgabe von Vector
    
      public:
        Board(Player *current_player);    /// übergabe von current_player hier für testzwecke!
        virtual ~Board();
        void setPieceToStart(Piece *piece);
        int movePiece(Player *curren_player, int rolled);
        void build(std::string input, Player *current_player);
        void printField(Player *current_player);
        void print();
        void initFields(char ponf[40], char ooff[40], char honf[40]);
        bool checkHouseRule(std::vector<int>& houses, int index);
        bool checkField(Player *current_player, int rolled);
        bool checkInput(int cases);
        int checkBankrupt(Player *current_player);
        void removePossessions();
    };
    
    #endif  //BOARD_H_
    
    //---------------------------------------------------------------------
    #include "BuyableFields.h"
    
    #ifndef STREETS_H_
    #define STREETS_H_
    
    class Streets: public BuyableFields
    {
      private:
        int house_;
        int house_price_;
        int rent_list_[6];
        Colors group_;
        Streets(Streets &);   //No Shallow Copys.
    
      public:
        Streets(std::string field_name, int price, int site, int hse1,
          int hse2, int hse3, int hse4, int hotel,
          int house_price, Colors group, Player *owner = NULL);
        virtual ~Streets();
    
        Colors getGroup();
        void setHouse();
        int getHouse();
        void delHouse();
        int getHousePrice();
        int getRent(int field_count, int rolled);
    
        std::string getFieldType();
    };
    
    #endif  //STREETS_H_
    

    Als kleine veranschaulichung, zwei Headerfiles meiner Klassen. Eine Klasse GameHandler steuert prinzipiell alle Inputs von den Spielern usw. Die Klasse Board (oben angeführt) regelt den weiteren Spielablauf. In den Klassen der Felder ( wie zb streets ) sind lediglich alle Feldrelevanten Daten mit ihren zugehörigen getter und setter Methoden enthalten.
    Imho ist das eigentlich ein sehr schöner Aufbau, die Felder selbst können bebaut, gekauft, vermietet werden. Gesteuert wird dies jedoch vom Board selbst, da die Felder ja über den eigentlich Spielablauf keine Ahnung haben. Nur das Board hat überblick über den gesamten Spielverlauf und kennt alle Regeln etc.
    Wird nun im GameHandler die Methode Build vom Board aufgerufen. So wird Board seinerseids, in der Methode Build Methoden wie getHousePrice(), getHouse() und setHouse() von den Streets aufrufen.

    Lediglich als Beispiel wieso und wie ich in Board eigentlich alles verwalte und warum eine generelle Methode "Aktion()" welche jedes Feld besitzt wohl schwer zu realisieren wird.

    Denkst du das dieser Designansatz komplett daneben ist? Lediglich das Problem mit der Mutterklasse welche alle Methoden kennen muss (ausser durch casts) bleibt dadurch irgendwie bestehen... 😕

    lg



  • Wo bist du pumuckl...? 😞
    Gib mich net auf 😃

    Na spaß, verstehs gut wenns langsam zu "leseintensiv" wird und die Leute hier besseres zu tun haben als meinen Schrott auszubessern. Bedanke mich dennoch mal ganz herzlich bei allen die ihre Zeit für mich geopfert haben. Vll fällt mir ja noch was ein.
    lg



  • Was für Aktionen kann ein Feld denn auslösen?

    Der Spieler kann Geld ausgeben/einnehmen (um ein haus zu bauen, upzugraden, miete zu zahlen, ereigniskarte schnekt ihm geld,...) und/oder er kann besitz bekommen/verlieren (haus, grundstück,..) und/oder es kann seine position auf dem spielfeld ändern.

    damit ist die schnittstelle zwischen feld und board definiert. jedes feld macht alles selber und das board hat keine ahnung was auf einem feld passiert und was das für ein feld ist. ist ihm auch egal, es sagt nur feld.handle() und schon handelt das feld.



  • Hm wies scheint ist mein Beitrag vorhin net angekommen oO

    Ich hatte erstmal zu folgenden Puntke was gesagt:

    1. sollten includes in headern innerhalb der include-guards stehen, nicht davor. Bei größeren projekten können sonst Ringincludes entstehen und dann öffnet der Präprozessor erst A.hpp, dann B.hpp, dann wieder A.hpp, dann B.hpp... irgendwann streckt er alle Viere von sich und aus die Maus 😉
    2. Du übergibst oft Zeiger auf andere Objekte, meist tuns Referenzen auch.
    3. fields würde ich auch zum vector statt zum C-Array machen, schadet nicht.

    Das interface von Board sieht schon recht vollständig aus, mal abgesehn davon dass ich keine Ahnug hab was du mit initFields als public Methode und vor allem mit den ganzen Parametern dort erreichen willst. Die Initialisierung der Felder sollte eigentlich im Konstruktor geschehen.

    Wie Shade schon schreibt, bracuht die Basisklasse für alle Felder wenig mehr als eine virtuelle Methode handle() oder Aktion() mit einem Spieler als Argument. Wenn du einen Spielstein auf dem Board versetzt, nimmst du das Zielfeld und rufst einfach diese Methode auf, mit der Person als Parameter. Das feld selber weiß dann am besten was zu tun ist, z.B. einen Dialog anzeigen "willst du Häuser bauen" oder was auch immer.



  • Ja ihr habt wohl recht.
    hmm, langsam habe ich das Gefühl mein Design scheint etwas unglückglich gewählt.
    Da das Programm zu 90% fertig ist kommt mir die Erkenntnis leider etwas spät. Anstatt der freude am "Erfolg" bleibt nun nur etwas Demotivation. Mal schaun ob der Aufwand das alles zu ändernd lohnt.
    danke, lg



  • pumuckl schrieb:

    1. sollten includes in headern innerhalb der include-guards stehen, nicht davor. Bei größeren projekten können sonst Ringincludes entstehen und dann öffnet der Präprozessor erst A.hpp, dann B.hpp, dann wieder A.hpp, dann B.hpp... irgendwann streckt er alle Viere von sich und aus die Maus 😉

    Danke, hab ich bis jetzt irgendwie immer so gemacht, wobei es total logisch klingt was du sagst.

    pumuckl schrieb:

    1. Du übergibst oft Zeiger auf andere Objekte, meist tuns Referenzen auch.

    Stimmt, arbeite bei der Parameterübergabe zu wenig mit Referenzen. Halte mich da noch zu sehr an die alte Ptrübergabe.

    pumuckl schrieb:

    1. fields würde ich auch zum vector statt zum C-Array machen, schadet nicht.

    *g* zu dem Zeitpunkt wusste ich noch nicht mit containern umzugehen ;P

    pumuckl schrieb:

    Das interface von Board sieht schon recht vollständig aus, mal abgesehn davon dass ich keine Ahnug hab was du mit initFields als public Methode und vor allem mit den ganzen Parametern dort erreichen willst. Die Initialisierung der Felder sollte eigentlich im Konstruktor geschehen.

    initFields ist für die ausgabe des Spielfeldes gedacht. Dabei habe ich für jeden Typ der sich ändern kann auf der Spielfeldausgabe ein chararray angelegt.
    (ponf - peaces on field, ooff - owner of field, honf - houses on field )
    eigentlich würds dann private gehören, stimmt.
    Wär ich zu beginn so klug gewesen und hätte das gesamte Spielfeld intern als string gespeichert wäre mir wohl einiges erspart geblieben. 😞

    pumuckl schrieb:

    Wie Shade schon schreibt, bracuht die Basisklasse für alle Felder wenig mehr als eine virtuelle Methode handle() oder Aktion() mit einem Spieler als Argument. Wenn du einen Spielstein auf dem Board versetzt, nimmst du das Zielfeld und rufst einfach diese Methode auf, mit der Person als Parameter. Das feld selber weiß dann am besten was zu tun ist, z.B. einen Dialog anzeigen "willst du Häuser bauen" oder was auch immer.

    Genau hier scheint mein Design fehlgeschlagen zu sein. Habe die Funktionen für bestimmte Felder in Board implementiert. Das jetzt zu ändern dauert ewig. Sollte das Programm vielleich neu schreiben, blos fehlt mir dazu gerade die Motivation.



  • Im großen und ganzen ist es irgendwie schwer solch ein Programm als "anfänger" zu schreiben, ohne ständig jemanden zu haben der alles kontrolliert und einen auf jeden Fehler aufmerksam macht. Am Ende steht man dann da und bemerkt was alles anders sein müsste *g*

    Danke trotzdem, echt tolles Forum, mit sehr vielen sehr hilfsbereiten Leuten.
    Werd das für heute erstmal ruhen lassn 😃

    ps. ich weiß, irgendwie fehlts da oben ziemlich an const-deklaration. werd mich noch damit beschäftigen, gefällt mir selbst nicht.



  • Glaub mir, ich kenn das dass man anfängt, mit einer vagen Vorstellung des Programmdesigns im Hinterkopf einfach drauflos tippt und nach einiger Zeit bemerkt, dass man einiges umzustellen hat. Entweder fängt man dann nochmal von vorne an und ärgert sich, dass man so viel zeit verschleudert hat, oder man ist gleich so frustriert dass man alles in die Ecke wirft und wieder einen halbfertigen Haufen Sourcecode auf der Platte hat. Ich hab zwar schon so einige kürzere Programme geschrieben und glaube auch, dass ich schon einiges an Wissen über verschiedene Details und auch über Designpatterns parat hab, aber in Punkto Projektentwurf bzw. Design von größeren Modulen bin ich ein ziemlicher Anfänger. Ich denke, da muss man einfach der Versuchung widerstehen und tatsächlich mit Papier und Bleistift erstmal ein Konzept entwickeln, und vor allem viel Erfahrung sammeln.
    Gibts vllt. irgendwo Tutorials oder Bücher wo beschrieben wird, wie man größere Programmkomponenten/Module entwirft?



  • Sooo, habe mich nun erstmal dazu überwunden alles umzuschreiben, geht ja schließlich darum, dass ichs lern 🙂

    Leider schaff ich es nach wie vor nicht mein Design lückenlos anzupassen und stehe wieder vor einigen Problemen.

    Shade Of Mine schrieb:

    Der Spieler kann Geld ausgeben/einnehmen (um ein haus zu bauen, upzugraden, miete zu zahlen, ereigniskarte schnekt ihm geld,...) und/oder er kann besitz bekommen/verlieren (haus, grundstück,..) und/oder es kann seine position auf dem spielfeld ändern.

    damit ist die schnittstelle zwischen feld und board definiert. jedes feld macht alles selber und das board hat keine ahnung was auf einem feld passiert und was das für ein feld ist. ist ihm auch egal, es sagt nur feld.handle() und schon handelt das feld.

    Genau hier fängts an, du sagst die Schnittstelle zwischen Feld und Board sei durch geld ausgeben/einnehmen, besitz bekommen/verliern definiert.
    In meinem Fall hätte ich gesagt sie ist erstmal durch Feldkaufen, getMiete und Hausbauen definiert. (Tut nichts zur sache, ist ja dann Spielspezifisch)
    Das heißt mein Feld stellt meinem Board diese 3 Methoden zur verfügung. Wiedermals ist es hier ja nicht möglich eine einzige Schnittstelle (feld.handle()) zu definieren über welche alles gemacht werden kann.

    Selbst wenn ich nur diese eine Methode als Schnittstelle bereitstellen würde, ich kann nicht den gesamten Spielablauf wenn ein Spieler auf ein Feld fährt dort regeln. Sobald beispielsweise ein Spieler ein Feld kauft, muss dem Spielerkonto Geld abgezogen, und der Bank Geld gutgeschrieben werden. Die Bank ist jedoch im Board realisiert und mein Feld kennt sie garnicht.

    Ich kann jetz natürlich alles in allem includen damit jede klasse alles kennt, aber des bringt mich wohl in Teufelsküche und hat mit dem eigentlichen Sinn von oop kaum mehr was am hut ?!



  • ein weiteres Beispiel:

    Will ein Spieler Häuser bauen, so muss überprüft werden ob der Spieler alle Felder dieser Gruppe besitzt. Ich müsste also alle Felder der richtigen Gruppe rausfiltern und überprüfen ob der Besitzter der selbe ist.
    Das Feld welches bebaut wird müsste diese Funktion implementieren. Das Feld kennt allerdings die andren Felder nicht, woher auch, die Felder werden ja über einen Field_ptr im board gehalten.
    *hmmmm*



  • Mehrere Möglichkeiten:

    a) Jedes buyable-Field hat Zeiger auf die anderen Felder seiner Gruppe (spric bahnhöfe, Werke, Straßen einer Farbe). Das feld kann dann über diese Pointer direkt schauen ob die anderen Felder der Gruppe den selben Eigentümer haben. Halte ich für das sinnvollste.

    b) Das Board bietet eine Methode an, mit der man die anderen BuyableFields einer Gruppe herausfinden kann (und darüber dann schaut wem die gehören) - halte ich für nicht so prickelnd, damit müsste das Board nämlich wieder zu viel über seine Felder wissen

    c) Das BuyableField fragt seinen Besitzer welche Felder ihm noch gehören (die Felder die ein Player besitzen kann sind schließlich allesamt BuyableFields) und schaut ob die anderen Felder der Gruppe dabei sind.

    Codeausschnitt zu a)

    typedef std::map<std::string, std::vector<BuyableField*> > FieldGroupMap;
    FieldGroupMap fieldGroups; //evtl im Board oder sonstwo
    
    BuyableField::BuyableField(std::string name, std::string group) : Field(name), group(group) //group ist auch ein Attribut jedes Felds
    {
      fieldGroups[group].push_back(this);
    }
    
    Street::buildHouse()
    {
      const std::vector<BuyableField*> groupmembers = fieldGroups[this->group];
      if (alle noetigen strassen in groupmembers) ok
      else geht nicht
    }
    

    zu c)

    std::vector<BuyableField*> Player::getOwnedFields() {/*...*/}
    


  • sust schrieb:

    In meinem Fall hätte ich gesagt sie ist erstmal durch Feldkaufen, getMiete und Hausbauen definiert. (Tut nichts zur sache, ist ja dann Spielspezifisch)
    Das heißt mein Feld stellt meinem Board diese 3 Methoden zur verfügung. Wiedermals ist es hier ja nicht möglich eine einzige Schnittstelle (feld.handle()) zu definieren über welche alles gemacht werden kann.

    Nein. Das Feld hat nur handle() und sonst nichts. Das Board bietet mieteZahlen(), hausBauen(), etc. an.

    Wenn wir nun zum Beispiel auf ein kaufbares Feld kommen:

    class KaufbaresFeld : public Feld {
    public:
      void handle(Board& b, Spieler& s) {
         if(hatBesitzer()) {
           s.mussMieteZahlenAn(besitzer);
         } else {
           if(s.willKaufen("Kärtnerstraße")) {
             s.bezahlen(1000);
             neuerBesitzer(&s);
             b.updateFeld(this);
           }
        } 
      }
    private:
      Spieler* besitzer;
      bool hatBesitzer() { rezurn besitzer!=0; }
      void neuerBesitzer(Spieler* s) { besitzer=s; }
    };
    

    Wobei die Optionen wie willKaufen() auch noch in eigene Optionenklassen gepackt werden können.


Anmelden zum Antworten