MVC mit Borland C++ (GUI)
-
Hi,
ich programmiere momentan das Spiel Tic Tac Toe mit dem C++-Builder von Borland (Nicht in der Konsole, sondern mit GUI)
Bisher bin ich so weit (es wird eigtl. nur das Spielfeld aufgebaut)
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "tictactoe.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- int cnt = 0; void __fastcall TForm1::onClick(TObject *Sender) { TLabel *label = NULL; label = dynamic_cast<TLabel*>(Sender); label->Color = (cnt++ % 2 == 0) ? clRed : clBlue; } //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { TLabel* label[3][3]; const unsigned int breite_label = 50; const unsigned int hoehe_label = 50; const unsigned int abstand = 5; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { label[i][j] = new TLabel(this); label[i][j]->Color = clBlack; label[i][j]->Font->Color = clWhite; label[i][j]->Parent = Panel1; label[i][j]->AutoSize = false; label[i][j]->Top = i*(hoehe_label+abstand); label[i][j]->Width = breite_label; label[i][j]->Height = hoehe_label; label[i][j]->Left = j*(breite_label+abstand); label[i][j]->Caption = " "; label[i][j]->OnClick = onClick; label[i][j]->Name = "i"+IntToStr(i)+"_j"+IntToStr(j); } } } //---------------------------------------------------------------------------
Da ich das Spiel aber MVC-gerecht programmieren will, frage ich lieber gleich am Anfang, wie ich das in C++ am besten mache. Ich kenne das nur von PHP, wo ich es schon seit Jahren anwende und zufrieden damit bin.
Aber ich wüsste jetzt nicht, wie ich das auf C++ übertragen kann.
Wo muss ich den Controller implementieren? Das Spiel wird ja eigentlich so gestartet, dass gleich die View-Schicht aktiv ist, oder nicht?
Muss ich dann von der View-Schicht den Controller instanziieren und der regelt alles weitere?MfG
Simon
-
Hallo
Der Builder bietet (in meiner alten Version) keine direkte MVC-Implementationen an. Du must also alle entsprechenden Klassen selber designen und implementieren. Dadurch werden deine folgenden Fragen natürlich etwas vage, denn das hängt ja nur davon ab wie du dein MVC aufbaust.
In C++ ist das Observer-Pattern eine Möglichkeit MVC zu erzeugen. Der View ist der Publisher (enthält die Spieldaten), und die Models sind die Subscriber (Eine von TControl abgeleitete GUI-Komponente, das sich bei einem View anmeldet, die Spieldaten anzeigen kann, und vom Publisher benachrichtigt wird wenn sich die Spieldaten ändern). Der Controller kann durch eine einfache Zusammenfassung von Aktionskontrols (Buttons) sowie Logik (Die Spielregeln) erreicht werden.
Durch eine vernünftige Verwendung von polymorpher OOP ist das ganz elegant zu lösen, erfordert aber gerade in Verbindung mit der VCL einiges an Grundkenntniss in C++
bis bald
akari
-
Ohje, das hört sich kompliziert an.
Würdest du mir das für ein einfaches Spiel wie Tic Tac Toe empfehlen?
-
Hallo
Für das Spiel alleine nicht, für die Erweiterung deiner Programmierkenntnisse schon. Vermutlich solltest du dich nicht sofort an das MVC wagen, sondern erstmal noch die Grundkenntnisse sammeln. Es ist dabei hilfreich sich erstmal auf Konsole zu beschränken, um sich nicht von der GUI verwirren zu lassen.
Falls du aber jemals komplexere Software programmieren willst, kommst du um solche Kenntnisse von sprachunabhängigen Lösungstrategien nicht umhin. Dann ist es hilfreich wenn du nicht nur weißt was ein MVC ist, sondern wie es intern arbeitet.
bis bald
akari
-
Hi,
wie MVC funktioniert, weiß ich ja von PHP her. Mir gelingt nur der Transfer auf C++ nicht so ganz, weil ich noch ziemlich verwirrt von der GUI bin.
Ich würde meine Kenntnisse vertiefen, aber ich muss das SPiel bald bei meinem Lehrer abgeben. MVC ist zwar keine Pflicht, aber dann hätte ich die 1 wenigstens sicher....
Ich schreibe gerade eine Klasse, die ein Feld repräsentiert:
class Feld : public TLabel { public: Feld(AnsiString name, TForm1* a, TWinControl* parent, unsigned int width, unsigned int height, unsigned int top, unsigned int left) : TLabel(a) { this->Color = clBlack; this->AutoSize = false; this->Font->Color = clWhite; this->Parent = parent; this->Caption = " "; this->Top = top; this->Left = left; this->Width = width; this->Height = height; this->Name = name; } };
Ist das so üblich in C++?
Es funktioniert zumindest... Und so habe ich die Möglichkeit, weitere Attribute abzuspeichern, z.B. welchem Spieler das Feld "gehört".Simon
-
Hallo
Syntaktisch korrekt ist das schonmal nicht. Und da wir uns an die VCL-Richlinien halten wollen, sollte deine Ableitung von TLabel so aussehen :
class Feld : public TLabel { public: // VCL-konformer Konstruktor __fastcall Feld(TComponent* Owner) : TLabel(Owner) { this->Color = clBlack; this->AutoSize = false; this->Font->Color = clWhite; // this->Parent = parent; wird direkt von Objektinspektor oder über freigegebene Property Parent zugewiesen this->Caption = " "; // this->Top = top; s.o. // this->Left = left; s.o. // this->Width = width; s.o. // this->Height = height; s.o. // this->Name = name; s.o. } };
Alle weitere Eigenschaften sollten nicht im Konstruktor übergeben werden, sondern über VCL-Properties. Zum Beispiel der Verweis auf das Spiel-View.
Es macht keinen Sinn zu versuchen dir innerhalb von 1 oder 2 Tagen
OOP in C++, VCL und MVC-Implementierung zu erklären. Das erfordert wesentlich mehr Einarbeitungszeit.bis bald
akari
-
Das hat doch aber mit MVC nichts zu tun. Das ist doch einfache Vererbung wo nicht klar ist wozu sich der Aufwand lohnen soll.
Dieses Muster bietet sich auch eher im Webbereich an. Die Rich-Client-Entwickler sind da von ihren Frameworks eher verwöhnt, z.B. das Anhängen der Modelle/Daten/Logik an die Forms, das Hangeln zwischen den Forms. Ich könnte mir aber eine MVC-Lösung vorstellen, wenn der Anwender situationsabhängig bedient werden soll: Der Inputcontroller prüft die Anwendereingabe und beauftragt ein Model die entsprechenden Daten zu liefern. Findet er ein Objekt wird dieses vom Controller im Detailview angezeigt, sind es mehrere werden diese in einer Listview gezeigt damit der Benutzer eines wählen kann. Wird nichts gefunden wird zum Eingabeform zurückgesprungen/verblieben.
Wenn Du Deinen Lehrer/Mentor beeinducken willst dann kannst du ja die Spiellogik in extra Klassen auslagern, vllt sogar mit 2-3 Strategieklassen mit unterschiedlicher intelligenter Kompetenz/Stärke, von denen dann der Spieler eine als Gegner wählen kann.
-
Hi,
was meinst du mit "wird direkt von Objektinspektor oder über freigegebene Property Parent zugewiesen"?
Simon
-
witte schrieb:
Das hat doch aber mit MVC nichts zu tun. Das ist doch einfache Vererbung wo nicht klar ist wozu sich der Aufwand lohnen soll.
Das war vermutlich auch nur ein Anfang. Das "Feld" soll die View-Klasse sein.
Wenn Du Deinen Lehrer/Mentor beeinducken willst dann kannst du ja die Spiellogik in extra Klassen auslagern, vllt sogar mit 2-3 Strategieklassen mit unterschiedlicher intelligenter Kompetenz/Stärke, von denen dann der Spieler eine als Gegner wählen kann.
Und wenn man dann noch GUI-Kompoennten und Spieldaten voneinander trennt, dann hat man ein MVC. Ob sich das lohnt, ist eine Frage ob man die Beschäftigung mit dem Thema als Ziel ansieht.
$phpler schrieb:
was meinst du mit "wird direkt von Objektinspektor oder über freigegebene Property Parent zugewiesen"?
Der Builder ist ein sogenanntes RAD-Tool. Er bietet an das die GUI-Komponenten zur Designzeit bequem mit Maus auf dem Form-Prototypen gesetzt werden können und mit dem Objektinspektor, der die Eigenschaften einer Komponente anzeigt, konfiguriert werden können. Damit das auch mit deinen View-Komponenten möglich wäre, müßen sie sich an gewisse Konventionen halten.
bis bald
akari
-
Der Builder ist ein sogenanntes RAD-Tool. Er bietet an das die GUI-Komponenten zur Designzeit bequem mit Maus auf dem Form-Prototypen gesetzt werden können und mit dem Objektinspektor, der die Eigenschaften einer Komponente anzeigt, konfiguriert werden können. Damit das auch mit deinen View-Komponenten möglich wäre, müßen sie sich an gewisse Konventionen halten.
Achso... Ok, aber das wäre jetzt nicht so wichtig.
Ich erzeuge alle Elemente mit Code, ich bin nicht so der Klicki-Fan.Was ich nicht verstehe, ist warum du das Top, Left, Height und Width-Zeugs alles auskommtentiert hast. Diese Werte brauche ich doch.
SImon
-
Hallo
Da du dein Feld von TLabel public ableitest, hat Feld auch alle Eigenschaften von TLabel. Du kannst also die Eigenschaften auch über die Properties setzen :
// Deine Variante : Feld* feld = new Feld(owner, ... , 100, 200, 300, 400); // Meine Variante : Feld* feld = new Feld(owner); feld->Top = 100; feld->Left = 200; ...
Natürlich ist auch deine Variante nicht falsch. Aber warum sollte man so einen umfangreichen Konstruktor definieren, wenn die Eigenschaften sowieso da sind? Ob du dadurch Übersichtlichkeit gewinnst ist Geschmackssache. Die VCL-Konformität verlierst du aber.
bis bald
akari
-
Achso, so meinst du das!
Mir ist das mit dem Konstruktor lieber, wenn ich ehrlich bin.
Dann kann ich nämlich im Konstrutkor noch überprüfen, ob die Werte auch alle passen.Simon
-
Wieso nicht einfach eine uebergeordnete Klasse
TSpielfeld
, die Objekte der KlasseTFeld
als mehrschichtiges Array besitzt? Dann kannst du in deinem Programm einfach ein Objekt vonTSpielfeld
implementieren und entsprechende Funktionen, die das Bedienen des Spielfeldes angehen, in dieser Klasse zusammenfassen.
-
So mach ich das jetzt.
Allerdings habe ich ein Problem.
Wenn ich im Form1 die Klasse Spielfeld instanziieren will, geht das.
Wenn ich sie allerdigns als Attribut "abspeichern" will, komtm die Meldung "Typname erwartet".//--------------------------------------------------------------------------- #ifndef tictactoeH #define tictactoeH //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <ExtCtrls.hpp> #include <Menus.hpp> //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // Von der IDE verwaltete Komponenten TPanel *Panel1; TMainMenu *MainMenu1; TMenuItem *Spiel1; TMenuItem *Optionen1; TMenuItem *Neu1; TMenuItem *Hilfe1; TMenuItem *Info1; TMenuItem *Anleitung1; TMenuItem *Beenden1; void __fastcall FormCreate(TObject *Sender); void __fastcall Optionen1Click(TObject *Sender); void __fastcall Beenden1Click(TObject *Sender); private: // Anwender-Deklarationen void __fastcall TForm1::onClick(TObject *Sender); void __fastcall adjust(TObject *Sender); Spielfeld* spielfeld; public: // Anwender-Deklarationen __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif
(tictactoe.h)
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "tictactoe.h" #include "feld.h" #include "optionen.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { this->spielfeld = new Spielfeld(this); spielfeld->create(); } void __fastcall TForm1::Optionen1Click(TObject *Sender) { Form2->Visible = true; } //--------------------------------------------------------------------------- void __fastcall TForm1::Beenden1Click(TObject *Sender) { Application->Terminate(); } //---------------------------------------------------------------------------
(tictactoe.cpp)
Weiß jemand, was ich falsch mache?
Kann es sein, dass die Klasse noch nicht bekannt ist, wenn ich sie in der Klassensignatur ankündige?Simon
-
Wenn ich class Spielfeld; ganz oben in der tictactoe.h schreibe, geht es.
Es war wohl einfach so, dass die Klasse noch nicht bekannt war, als ich sie hier verwendet habe:private: // Anwender-Deklarationen void __fastcall TForm1::onClick(TObject *Sender); void __fastcall adjust(TObject *Sender); Spielfeld* spielfeld;
Ist das sauber so? (class Spielfeld;)
SImon
-
Es wuerde genuegen, wenn du deine Headerdatei mit der Klasse
Spielfeld
vor oder gar in der Projekt-Headerdatei einbindest.
-
Dazu habe ich noch eine Frage.
Ich komme immer wieder in Probleme, weil ich eine Klasse irgendwo verwende, die erst später deklariert wird. Dann muss ich entweder die Reihenfolge der Header-Dateien ändern oder die Klasse "bekannt machen".
Allerdings gibt es auch Probleme, wenn ich zwar die Klasse kenne, aber ihre genauen Methoden noch nicht.
Ich implementiere die Methoden immer direkt in der Klasse und nicht so:
int Klasse::Methode()
{
return 0;
}Sollte man das so machen?
Wenn ja, wie mache ich das dann mit den Header-Dateien? SOll ich die "Signatur" der Klasse, die alle Attribute und Methoden enthält in einer anderen Datei speichern?
Phpler