Konsolen-Simulation des Algorithmus "Türme von Hanoi" unter Verwendung einer Klasse
-
SeppJ schrieb:
Mir war langweilig
. Das Programm erzählt die Geschichte der Türme von Hanoi. Ich habe natürlich peinlich genau darauf geachtet, dass die Scheiben wirklich verschoben werden, keine Tricksereien mit Kopien von Scheiben. Kopien von realen Gegenständen gibt es in einem wohl geformten Universum nicht. Über allem wacht der mächtige Gott Brahma, dass alle Regeln eingehalten werden:
#include <iostream> #include <vector> #include <stdexcept> #include <utility> class EndOfTheWorldAsWeKnowIt { };
P.S.: Da manche Leute immer noch kein C++11 haben:
https://ideone.com/bgcPZlDas liest sich wie ein spannendes Adventure ... mit mystischen Unwirklichkeiten
-
Werner Salomon schrieb:
nicht schlecht SeppJ .. nur bei der Ausgabe der Türme sehe ich noch Verbesserungspotential.
Dazu habe ich mir auch was einfallen lassen. Vom Algorithmus her ist das so eine Art einfacher-2D-Raytracer. Der Kern besteht aus dem Interface
Shape
und der KlasseDisplay
.Display
hat Zugriff auf alleShape
-Objekte, die gezeichnet werden sollen. Wird einDisplay
-Objekt ausgegeben, so berechnet Display für jeden Punkt (Zeichen) in der Ausgabe die jeweilige Position und fordert alle Shapes auf, ggf ihren 'Stempel' dort zu hinterlassen.Jedes Objekt, was 'gezeichnet' werden soll, muss von
Shape
abgeleitet sein, und die Methodevoid draw( const Pos& drawPos, char& out ) const
überladen. Dort muss dann eine Implementierung dafür sorgen, dass das Zeichenout
genau dann überschrieben wird, wenn die PositiondrawPos
auf eine Punkt des Objekts zeigt. Natürlich sollte das Objekt dazu wissen, wo es sich befindet.
Die Klasse Pos enthält schlicht x- und y-Wert einer Position. Man bräuchtePos
in dieser Programm-Skizze nicht zwingend, ich finde das aber durchgängiger und verständlicher. Für Erweiterungen, die Addition- und/oder Subtraktion von Positionen nötig machen istPos
sicher von Vorteil.Die Methode ermöglicht es auch, Objekte übereinander zu zeichnen. Es gewinnt das Objekt, welches 'oben' liegt.
Noch ein Hinweis: die Ausgabe der Skaliereung für x und y ist für die Demo hier. Wer das nicht haben will, lässt die Zeilen 94 und 95 weg und ersetzt die Zeilen 106 und 107 durch ein schlichtesreturn out;
.Es ist übrigens ein Leichtes, SeppJs Programm und dieses zu verbinden. Man nehme die Klassen
Display
undShape
und die MethodeTower::draw
, füge Letztere zu SeppJsTower
hinzu und ersetze die Ausgabe desTemple
durch die Ausgabe vonDisplay
.SeppJ schrieb:
Da manche Leute immer noch kein C++11 haben:
https://ideone.com/bgcPZl.. manchmal schlägt eben der normative Zwang des Faktischen erbarmungslos zu
Gruß
WernerCode lesen ...
Ich sehe's schon ... es wird bei mir noch lange dauern ...
-
Werner Salomon schrieb:
.. nur bei der Ausgabe der Türme sehe ich noch Verbesserungspotential.
Ja, das war auch meine Sorge beim Programmieren. Erst alles schön fertig gemacht, aber dann wusste ich nicht mehr weiter, wie ich das elegant dargestellt bekomme. Derzeit ist es eine Krücke, die Objekte zeichnen sich selbst, das sollte nicht so sein. Logik und Darstellung sollten sauber getrennt sein, aber ich kenne die üblichen Designs dafür nicht und so wirklich zufrieden war ich auch nicht mit dem, was ich mir auf die Schnelle ausgedacht habe. Dein Design ist da ähnlich, wenn auch eleganter. Ist das schon eine der üblichen Vorgehensweisen? Da müsste man jetzt wohl wirklich mal einen Spieleprogrammierer fragen, wie das gut gemacht wird.
-
SeppJ schrieb:
..Derzeit ist es eine Krücke, die Objekte zeichnen sich selbst, das sollte nicht so sein.
Muss ich mein Anfänger-Klassendesign wohl umswitchen. Dass die Methoden den entsprechenden Klassen nicht richtig zugeordnet sind, hatte ich mir schon gedacht. Objekte wie Slice u. Tower sollte man vermutlich so einfach wie möglich halten?
Was bedeuten die Striche hinter den Variablen?
-
Werner Salomon schrieb:
class Pos { public: Pos() : x_(0), y_(0) {} Pos( int x, int y ) : x_(x), y_(y) {} friend int X( const Pos& pos ) { return pos.x_; } friend int Y( const Pos& pos ) { return pos.y_; } private: int x_, y_; };
Bist Du so freundlich uns zu erklären weshalb Du X(...) und Y(...) als globale Funktionen implementierst? Ich hab das hier schonmal irgendwo entdeckt und nur mit dem Kopf geschüttelt
Du weißt schon dass es dafür Klassenmethoden gibt?
Daraus resultiert dann völlig unleserlicher Code wie:
for( int x = X(d.lower_left_); x <= X(d.upper_right_); ++x )
- osdt
-
Cpp_Anfaeger schrieb:
Muss ich mein Anfänger-Klassendesign wohl umswitchen. Dass die Methoden den entsprechenden Klassen nicht richtig zugeordnet sind, hatte ich mir schon gedacht. Objekte wie Slice u. Tower sollte man vermutlich so einfach wie möglich halten?
Das ist nicht unbedingt eine Frage der Einfachheit. Klassen sollen das darstellen, was sie darstellen sollen, egal ob einfach oder kompliziert. Man kann auch sehr gut ein Design verteidigen, bei dem eine Klasse sich selber zeichnet, denn wer wäre besser dafür geeignet als sie selbst?
Die Trennung von Logik und Darstellung ist eher ein technischer Aspekt. Man möchte die Darstellung abkoppeln, damit sie austauschbar wird. So kann die gleiche Programmlogik auf allerlei Art dargestellt werden. Dann wäre es ein leichtes, mein Progrämmchen mit einer anspruchsvollen 3D-Grafik auszurüsten, ohne an der Logik irgendetwas verändern zu müssen.
Was bedeuten die Striche hinter den Variablen?
Bei Werners Code? Die gehören mit zum Namen und haben keine syntaktische Bedeutung. Werner will dadurch unterscheidbar machen, ob eine Variable lokal ist oder ein Klassenmember ist.
-
SeppJ schrieb:
Die Trennung von Logik und Darstellung ist eher ein technischer Aspekt. Man möchte die Darstellung abkoppeln, damit sie austauschbar wird. So kann die gleiche Programmlogik auf allerlei Art dargestellt werden. Dann wäre es ein leichtes, mein Progrämmchen mit einer anspruchsvollen 3D-Grafik auszurüsten, ohne an der Logik irgendetwas verändern zu müssen.
..
Bei Werners Code? Die gehören mit zum Namen und haben keine syntaktische Bedeutung. Werner will dadurch unterscheidbar machen, ob eine Variable lokal ist oder ein Klassenmember ist.Achso, danke. Dann wäre man gut beraten, es so zu designern, dass die Windows-Programmierung später ohne Probleme anknüpfen kann
-
osdt schrieb:
Bist Du so freundlich uns zu erklären weshalb Du X(...) und Y(...) als globale Funktionen implementierst? Ich hab das hier schonmal irgendwo entdeckt und nur mit dem Kopf geschüttelt
Du weißt schon dass es dafür Klassenmethoden gibt?
Nun - sehe als persönlichen Stil meinerseits. Vom Standpunkt des Designs ist es IMHO völlig egal, ob Du in einer Klasse eine Methode oder eine (friend) Funktion, mit der Klasse als einer der Parameter implementierst. Technisch gibt es da Feinheiten - die kannst Du bei Scott Meyers nachlesen.
In diesem speziellen Fall bei Positionen habe ich mir das so angewöhnt, und als ich es oben geschrieben habe, war das reine Gewohnheit ohne konkret darüber nach zu denken. Es hat nämlich den Vorteil, dass man sehr viele Strukturen als Positionen verwenden kann. Teilweise solche, wo man keine Methoden hinzufügen kann, weil es sich um sogenannten Legacy Code oder um eine Klasse aus dem Standard wie std::pair<> oder std::complex<> handelt.
Man kann aber immer diese Funktionen X() und Y() implementieren. Das ist ein großer Vorteil, insbesondere bei der Template-Programmierung.osdt schrieb:
Daraus resultiert dann völlig unleserlicher Code wie:
for( int x = X(d.lower_left_); x <= X(d.upper_right_); ++x )
.. erstaunlich! Denke Dir mal X(egal) heißt X-Wert-von-egal. Dann steht da:
for( int x = X_Wert_von(d.lower_left_); x <= X_Wert_von(d.upper_right_); ++x )
also: Schleife über alle x beginnend vom X-Wert der (unteren) linken Ecke bis zum X-Wert der (oberen) rechten Ecke.
Gruß
Werner
-
Hallo SeppJ,
wenn dieses Forum eine Person wäre, müsste es wegen Schizophrenie sofort zum Psychiater.
SeppJ schrieb:
.. die Objekte zeichnen sich selbst, das sollte nicht so sein. Logik und Darstellung sollten sauber getrennt sein, aber ich kenne die üblichen Designs dafür nicht und so wirklich zufrieden war ich auch nicht mit dem, was ich mir auf die Schnelle ausgedacht habe. Dein Design ist da ähnlich, wenn auch eleganter. Ist das schon eine der üblichen Vorgehensweisen? Da müsste man jetzt wohl wirklich mal einen Spieleprogrammierer fragen, wie das gut gemacht wird.
ich hätte mir diese Diskussion - und ich begrüße sie sehr
- eher in diesem Thread gewünscht, wo ich explizit danach fragte, wie man Code verständlich schreiben kann. Fünf von fünf die geantwortet haben, waren der Meinung, dass der Code des Springerproblems durch meinen (IMHO ojektorientierten) Ansatz nur komplexer, länger und weniger verständlich wird.
Und dann poste ich hier ein Schnipsel im gleichen Stil und Du meinst:SeppJ schrieb:
Logik und Darstellung sollten sauber getrennt sein
- also noch mehr Code und noch mehr Klassen und womöglich noch weniger verständlich?
Ja - natürlich sollte man Logik und Darstellung sauber trennen - was denn sonst. In sogenannten 'professionellen' Programmen kenne ich das gar nicht mehr anders. Und das machen nicht nur Spieleprogrammierer so - Modell-View-Architekturen machen auch wo anders Sinn. Und sie machen auch Sinn, wenn man die View nicht austauschen will. Einfach um die Komplexität runter zu kriegen.
Ob man das bei den Türmen von Hanoi macht oder nicht ist was anderes. Der Turm ist viel zu einfach, als dass das wirklich nenneswerte Verbesserungen des Programms nach sich zieht. Adererseits ist das 'ne nette Übung - und wenn man es mit dem Turm aus Hanoi nicht sauber hinbekommt, braucht man nicht zu hoffen, dass es bei großen/komplexen Objekten einfacher wird.
ich melde mich zu dem Thema später noch mal.
Gruß
Werner
-
Ufff ... Klassen sind schon umfangreicher und brauchen relativ mehr Speicher als normale Programmierung ...
http://s14.directupload.net/images/131208/5afa74r2.jpg
Soweit sind die Klassen fertig designed u. programmiert. Es scheint auch zu funktionieren. Ob ich gravierende Verstöße gegen die objektorientierte Programmierung gemacht habe, weiß ich allerdings nicht:
#include <windows.h> // wegen Windows Handle für Konsolenposition #include <cstdlib> // wegen system("cls") #include <sstream> // wegen stringstream, für int kombiniert mit char* #include <iostream> #include <deque> #include <string> using namespace std; class Slice { public: Slice(); ~Slice(); void setSlice(string slice); string getSlice(); int getSliceCenter(); int getSliceSize(); private: string m_slice_object; int m_slice_length; int m_slice_center; }; Slice::Slice() { } Slice::~Slice() { } //--------------------------------------------------------------------- // gibt der Scheibe ein Aussehen //--------------------------------------------------------------------- void Slice::setSlice(string slice) { m_slice_object = slice; } //--------------------------------------------------------------------- // holt die Scheibe //--------------------------------------------------------------------- string Slice::getSlice() { return m_slice_object; } //--------------------------------------------------------------------- // bestimmt die Mitte der Scheibe //--------------------------------------------------------------------- int Slice::getSliceCenter() { m_slice_center = (m_slice_object.length()+1)/2; return m_slice_center; } //--------------------------------------------------------------------- // bestimmt die Größe der Scheibe //--------------------------------------------------------------------- int Slice::getSliceSize() { m_slice_length = m_slice_object.size(); return m_slice_length; } class Tower { public: Tower(); ~Tower(); Slice getSlice(); //oberste Scheibe zurückgeben Slice getAt(const int &iter); // irgendwo eine Scheibe int getSize(); // Towergröße auslesen void addSlice(Slice slice); // 1x aufnehmen void removeSlice(); // 1x löschen private: deque<Slice> m_tower_content; }; Tower::Tower() { } Tower::~Tower() { } //--------------------------------------------------------------------- // holt oberste Scheibe //--------------------------------------------------------------------- Slice Tower::getSlice() { return m_tower_content.at(0); } //--------------------------------------------------------------------- // holt eine Scheibe an einer best. Stelle im Turm //--------------------------------------------------------------------- Slice Tower::getAt(const int &iter) { return m_tower_content.at(iter); } //--------------------------------------------------------------------- // Scheibenzahl auslesen //--------------------------------------------------------------------- int Tower::getSize() { return m_tower_content.size(); } //--------------------------------------------------------------------- // fügt eine Scheibe oben rein //--------------------------------------------------------------------- void Tower::addSlice(Slice slice) { m_tower_content.push_front(slice); } //--------------------------------------------------------------------- // löscht oberste Scheibe aus Turm //--------------------------------------------------------------------- void Tower::removeSlice() { m_tower_content.pop_front(); } class Display { public: Display(); ~Display(); void clr(); void gotoXY(const int &line, const int &column); void waitKeyboard() {getwchar();} void print(const string &value); void print(const stringstream &value); void print(Tower &tower, int x_start, const int &y_start); }; Display::Display() { } Display::~Display() { } //--------------------------------------------------------------------- // // Bildschirm löschen //--------------------------------------------------------------------- void Display::clr() { system("cls"); } //--------------------------------------------------------------------- // Positioniere Cursor auf Konsole //--------------------------------------------------------------------- void Display::gotoXY(const int &line, const int &column) { COORD p; p.Y = line; // Zeile p.X = column; // Spalte SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), p); } //--------------------------------------------------------------------- // Überladung 1: String auf dem Bildschirm //--------------------------------------------------------------------- void Display::print(const string &value){ cout << value << endl; } //--------------------------------------------------------------------- // Überladung 2: StringStreams auf dem Bildschirm //--------------------------------------------------------------------- void Display::print(const stringstream &value){ cout << value.str() << endl; } //--------------------------------------------------------------------- // Überladung 3: Tower auf Konsole ausgeben //--------------------------------------------------------------------- void Display::print(Tower &tower, int x_start, const int &y_start) { int start_column; // Startspalte for (int i=tower.getSize()-1; i > -1; i--) { start_column = y_start - tower.getAt(i).getSliceCenter(); this->gotoXY(x_start--, start_column); cout << tower.getAt(i).getSlice(); } } class Simulation { public: Simulation(); ~Simulation(); void moveSlice(Tower &start_tower, Tower &destination_tower); void recurFunction(int start, int destination, int slice_count, Tower &tower_1, Tower &tower_2, Tower &tower_3); private: int m_counter; // Schrittzähler }; Simulation::Simulation() { m_counter = 0; } Simulation::~Simulation() { } //--------------------------------------------------------------------- // Scheibe von Turm zu Turm bewegen //--------------------------------------------------------------------- void Simulation::moveSlice(Tower &start_tower, Tower &destination_tower) { destination_tower.addSlice(start_tower.getSlice()); start_tower.removeSlice(); } //------------------------------------------------------------------------- // Rekursiver Methodenaufruf zur Berechnung & Anzeige der Möglichkeiten //------------------------------------------------------------------------- void Simulation::recurFunction(int start, // Startturm int destination, // Zielturm int slice_count, // Anzahl Scheiben Tower &tower_1, Tower &tower_2, Tower &tower_3) { Display console; // Konsole erstellen stringstream step_no; // Schrittnummer int free; // freier Turm if (slice_count != 0) { free = 6-start-destination; // rekursiver Aufruf recurFunction(start, free, slice_count-1, tower_1, tower_2, tower_3); // Schritte anzeigen console.gotoXY(0,0); step_no << ++m_counter << ".) Turm " << start << " nach -> Turm " << destination << endl; console.print(step_no); console.gotoXY(1,26); // Ausgangszustand des Turms 1malig anzeigen if (m_counter == 1) { console.print(tower_1, 9, 15); console.gotoXY(1,26); } console.waitKeyboard(); // Simulationsteil: if (start == 1 && destination == 2) moveSlice(tower_1, tower_2); // Tower1->Tower2 if (start == 1 && destination == 3) moveSlice(tower_1, tower_3); // Tower1->Tower3 if (start == 2 && destination == 1) moveSlice(tower_2, tower_1); // Tower2->Tower1 if (start == 2 && destination == 3) moveSlice(tower_2, tower_3); // Tower2->Tower3 if (start == 3 && destination == 1) moveSlice(tower_3, tower_1); // Tower3->Tower1 if (start == 3 && destination == 2) moveSlice(tower_3, tower_2); // Tower3->Tower2 // neu zeichnen console.clr(); console.print(tower_1, 9, 15); console.print(tower_2, 9, 42); console.print(tower_3, 9, 68); // erneuter rekursiver Aufruf recurFunction(free, destination, slice_count-1, tower_1, tower_2, tower_3); } console.gotoXY(15,0); } //--------------------------------------------------------------------- // Programm-Hauptfunktion //--------------------------------------------------------------------- int main() { Slice slice[6]; // 6 Scheiben mit folgendem Aussehen slice[0].setSlice( "***"); slice[1].setSlice( "*******"); slice[2].setSlice( "***********"); slice[3].setSlice( "***************"); slice[4].setSlice( "*******************"); slice[5].setSlice("***********************"); // 3 Türme erstellen Tower tower_1, tower_2, tower_3; // Turm_1 mit Scheiben auffüllen for (int i = 5; i > -1; --i) { tower_1.addSlice(slice[i]); } // Simulation starten Simulation sim; sim.recurFunction(1, 3, 6, tower_1, tower_2, tower_3); return 0; }
-
-
Klassen sind schon umfangreicher und brauchen relativ mehr Speicher als normale Programmierung ...
Was!?
-
SeppJ schrieb:
Meinst du die syntaktische Deklaration der Methoden ... dass sie so ungefährt deklariert werden sollten?
-
Arcoth schrieb:
Klassen sind schon umfangreicher und brauchen relativ mehr Speicher als normale Programmierung ...
Was!?
Ich kann richtig mit verfolgen, wie die IDE bei der Autovervollständigung nachschlägt bei 1 GB RAM
Muss mir zu Weihnachten mal was Neues göhnen.
-
Ach, du redest von der IDE... ich dachte, du redest von der eigentlichen ausführbaren Datei.
-
Cpp_Anfaeger schrieb:
Ich kann richtig mit verfolgen, wie die IDE bei der Autovervollständigung nachschlägt bei 1 GB RAM
Muss mir zu Weihnachten mal was Neues göhnen.
Ja, z.B. Linux.
-
göhner schrieb:
Cpp_Anfaeger schrieb:
Ich kann richtig mit verfolgen, wie die IDE bei der Autovervollständigung nachschlägt bei 1 GB RAM
Muss mir zu Weihnachten mal was Neues göhnen.
Ja, z.B. Linux.
Ja, wurde mir auch schon empfohlen. Ubuntu glaube ich war das ... soll ziemlich sehr klein sein.
-
Cpp_Anfaeger schrieb:
Ufff ... Klassen sind schon umfangreicher und brauchen relativ mehr Speicher als normale Programmierung ...
http://s14.directupload.net/images/131208/5afa74r2.jpg
Soweit sind die Klassen fertig designed u. programmiert. Es scheint auch zu funktionieren.
Klassen sind schon umfangreicher, aber man gewinnt Übersicht, wenn man es richtig anstellt. Mehr Speicher brauchen sie deshalb nicht.
So umfangreich, wie Du es gemacht hast, brauchen sie auch gar nicht zu sein. Versuche nicht alles zwanghaft in eine Klasse zu packen. SeppJ hat Dir mit dem Link schon den Tipp gegeben, dass Du zumindest den Destruktor überall einsparen kannst. Der wird automatisch richtig erzeugt, solange Du einfache Member hast und nicht etwa Pointer mit allokiertem Memory.
Die Klasse Simulation ist wahrscheinlich völlig überflüssig, da reicht eine Funktion. Bei der Klasse Display sind zumindest die print-Funktionen überflüssig; hier würde gleich eine Ausgabe auf std::cout reichen.
Schau mal wasDisplay
sonst noch macht und was die MethodeSimulation::recurFunction
macht. In beiden stehen Größen und Abläufe, die über das Aussehen der Ausgabe entscheiden. Das ist ungünstig geschnitten, da sind zu viele implizite Schnittstellen zwischen beiden. D.h. wenn Du in der eine was änderst, musst Du in der anderen in der Regel auch Änderungen vornehmen.Die Zeilen 313 bis 328 lassen sich in einer kleinen for-Schleife zusammenfassen, wenn man den String für
Slice
gleich im Konstruktor mitgibt. Tipp: ein Objekt sollte 'gebrauchsfertig' sein, wenn der Konstruktor verlassen wird. Das spart dann auch gleich die MethodeSlice::setSlice
ein. So sähe es dann z.B. aus:// 3 Türme erstellen Tower tower_1, tower_2, tower_3; // Turm_1 mit Scheiben auffüllen string bild = "***********************"; for (int i = 5; i > -1; --i) { tower_1.addSlice(Slice(bild)); if( i > 0 ) bild.erase( bild.end()-4, bild.end() ); }
Wenn man eine Klasse entwirft, sollte man sich zunächst darüber Gedanken machen, was die Klasse repräsentiert, welche Verantwortlichkeit sie hat und wie sie vom Anwender benutzt wird. Die Internas spielen zunächst keine Rolle.
Beispiel Turm: Er repräsentiert den Stapel Scheiben, hält die Scheiben. Man muss eine Scheibe drauf legen und entfernen können - oder eine Scheibe von einem Turm zum anderen verschieben können. Es greift der Verschiebe-Algorithmus darauf zu und die Darstellung des Turms (Turm-View).
Dein Turm:class Tower { public: Tower(); // ~Tower(); // muss nicht // -- Schnittstelle zum Verschiebe-Algorithmus void addSlice(Slice slice); // 1x aufnehmen Slice getSlice(); //oberste Scheibe zurückgeben void removeSlice(); // 1x löschen // -- Schnittstelle zur Darstellung (View) Slice getAt(const int &iter); // irgendwo eine Scheibe int getSize(); // Towergröße auslesen // Member };
das sieht doch schon mal sehr sauber aus. Nur weiter so - Übung macht den Meister
Gruß
Werner
-
Danke dir für die guten Tipps zur Gestaltung der Klassen!
Werner Salomon schrieb:
SeppJ hat Dir mit dem Link schon den Tipp gegeben, dass Du zumindest den Destruktor überall einsparen kannst. Der wird automatisch richtig erzeugt, solange Du einfache Member hast und nicht etwa Pointer mit allokiertem Memory.
Achso, deswegen hat der Designer von Qt Creator standardmäßig keine Destruktoren in die Vorlagen eingeblendet. Ich hatte schon gedacht, dass das ein Bug wäre
Werner Salomon schrieb:
In beiden stehen Größen und Abläufe, die über das Aussehen der Ausgabe entscheiden. Das ist ungünstig geschnitten, da sind zu viele implizite Schnittstellen zwischen beiden. D.h. wenn Du in der eine was änderst, musst Du in der anderen in der Regel auch Änderungen vornehmen.
Ich habe versucht, die Klasse Display so gut wie möglich losgelöst zu erstellen. Inwieweit muss ich sie ändern, wenn ich in der Klasse Simulation etwas ändere? Ich habe gedacht, die Klasse Display gibt die Gebrauchsanweisung vor, wie sie zu benutzen ist, und daran muss jeder sich halten, der sie benutzen möchte.
Werner Salomon schrieb:
Die Zeilen 313 bis 328 lassen sich in einer kleinen for-Schleife zusammenfassen
Ja, ich habe den Hang, alles so zu programmieren, damit ich sofort sehe, was vor sich geht. Dass da wiederum mehr Zeilen herauskommen ... super! Diese Technik ist wirklich neu für mich
:
// 3 Türme erstellen Tower tower_1, tower_2, tower_3; // Turm_1 mit Scheiben auffüllen string bild = "***********************"; for (int i = 5; i > -1; --i) { tower_1.addSlice(Slice(bild)); if( i > 0 ) bild.erase( bild.end()-4, bild.end() ); }
-
Cpp_Anfaeger schrieb:
Werner Salomon schrieb:
In beiden stehen Größen und Abläufe, die über das Aussehen der Ausgabe entscheiden. Das ist ungünstig geschnitten, da sind zu viele implizite Schnittstellen zwischen beiden. D.h. wenn Du in der eine was änderst, musst Du in der anderen in der Regel auch Änderungen vornehmen.
Ich habe versucht, die Klasse Display so gut wie möglich losgelöst zu erstellen. Inwieweit muss ich sie ändern, wenn ich in der Klasse Simulation etwas ändere? Ich habe gedacht, die Klasse Display gibt die Gebrauchsanweisung vor, wie sie zu benutzen ist, und daran muss jeder sich halten, der sie benutzen möchte.
ich meinte gar nicht
Display
sondern das main-Programm und die Simulation. In Der MethodeDisplay::gotoXY()
hast Du X und Y vertauscht - das verwirrt natürlich. X wird zur Zeilennummer (nach unten ansteigend) und Y ist die Spalte.Die Positionen in
Simulation::recurFunction
müssen sich nach der Größe und Anzahl der Scheiben immain
richten. D.h. wenn Du bei dem einen wesentliche Änderungen durchführt, dann muss das andere auch angepasst werden.
Außerdem ist Simulation::recurFunction direkt von Display abhängig, da es dort als lokale Variable benutzt wird. Das ist zumindest aus Sicht des Designs problematisch - natürlich weniger bei so einem kleinen Programm. Bei größeren Applikationen sollte man das besser trennen.Ich könnte mir einen move-Algorithmus vorstellen, der etwa so aussieht:
template< typename View > void move( std::size_t n, Tower& src, Tower& via, Tower& dst, const View& view ) { if( n == 1 ) { move1( src, dst ); view(); } else { move( n-1, src, dst, via, view ); move1( src, dst ); view(); move( n-1, via, src, dst, view ); } }
move1
ist eine (friend-)Funktion zum Verschieben eine Scheibe vom Turmsrc
(Source) nachdst
(Destination).view
ist ein Funktor, in dem der Anwender die Ausgabe der Szene unterbringen kann.Cpp_Anfaeger schrieb:
Werner Salomon schrieb:
Die Zeilen 313 bis 328 lassen sich in einer kleinen for-Schleife zusammenfassen
Ja, ich habe den Hang, alles so zu programmieren, damit ich sofort sehe, was vor sich geht.
Das solltest Du auch so beibehalten. Was mich störte war eigentlich nur das S
lice::setSlice
. Folgendes wäre auch noch eine Alternative:Slice slices[] = { Slice( "***" ), Slice( "*******" ), Slice( "***********" ), Slice( "***************" ), Slice( "*******************" ), Slice( "***********************" ) }; // 3 Türme erstellen + Turm_1 mit Scheiben auffüllen Tower tower_1( begin(slices), end(slices) ), tower_2, tower_3; // Tower tower_1( slices, slices + sizeof(slices)/sizeof(*slices) ), tower_2, tower_3; // bei C++98
Dann brauch es noch einen weiteren Konstruktor:
class Tower { public: Tower(); template< typename InItr > Tower( InItr first, InItr last ) : m_tower_content( first, last ) {}
.. wobei ich auch auch auf die Slice-Klasse ganz verzichten würde. Es reicht, im Turm eine Menge von (Scheiben-)Ids abzulegen - von 1 für die kleinste Scheibe und dann fortlaufend aufsteigend. Und erst bei der Anzeige wird entschieden, wie eine Scheibe genau aussieht.
Gruß
Werner