[C++] Vier gewinnt

moagnus

Ensign
Registriert
Juli 2007
Beiträge
189
Hallo zusammen,

ich habe kürzlich versucht, das Spiel "Vier gewinnt" für zwei Leute nachzuprogrammieren.
Es funktioniert auch alles soweit, bis auf die Überprüfung, ob diagonal vier gleiche Buchstaben erreicht sind. Vielleicht kann mir hier einer von euch helfen...

Die Überprüfung ist allerdings noch nicht ganz vollständig: es wird nur auf die zwei langen Diagonalen von links unten nach rechts oben geprüft. Wenn mein Problem gelöst ist, schreib ich die andern auch noch.

Zum Code: äh :rolleyes: Ihr dürft das gerne verkürzen/verbessern...

Danke im Voraus! :)
 

Anhänge

  • 4gewinnt.7z
    1,4 KB · Aufrufe: 478
Mich würde mal interessieren welcher Compiler das ohne Fehlermeldungen übersetzt. Sowohl in der Funktion main als auch in der Funktion pruefen wird die Variable i mehrfach definiert. VC++ meldet beim ersten Versuch den Code zu compilieren 6 Fehler.

Desweiteren solltest du (auch wenn es den Compiler nicht stört) keine Funktionen in Header-Files definieren. Header sollten nur Deklarationen enthalten und dienen somit dem Exportieren von Klassen, Variablen und Funktionen.

Im Prinzip ist dein Programm auch kein C++ sondern C, abgesehen von der Verwendung von std::cin, std::cout und den mit // eigeleiteten Kommentaren.

Beim den ersten Blick auf den Code würde ich sagen, da lässt sich mit Sicherheit noch einiges optimieren. So tief bin ich jetzt allerdings bisher nicht eingestiegen. Als spontanen Tipp würde ich dir aber vorschlagen, bei der Überprüfung in der Funktion pruefen von dem zuletzt eingeworfenen Spielstein ausgehend zu prüfen, ob dieser nun eine gültige Viererkombination ergibt.

Stelle dir mal die Frage, wieviel Aufwand es wäre, dein Programm so umzuschreiben, dass das Spielfeld z.B. 10x10 Felder groß ist. Wenn du feststellen solltest, dass du damit das meiste deines Codes neu schreiben musst, dann weißt du, dass dein Programm nicht optimal programmiert ist. ;) Natürlich ist es legitim, die Aufgabenstellung 1:1 zu lösen, aber ein gewisses Maß an Flexibilität ist in der Praxis häufig erforderlich. Ein typisches Erkennungsmerkmal für unflexiblen Code ist beispielsweise die Verwendung von sogenannten "Magic Numbers", also Zahlen im Code wie z.B. feld[ i ][9]. Genau an dieser Stelle bekommst du Probleme, wenn sich die Abmessungen des Spielfeldes ändern. Besser wäre die Verwendung zweier Konstanten wie FELD_MAX_X und FELD_MAX_Y und die Implementierung aller Algorithmen unter Verwendung dieser Konstanten, ohne "Magic Numbers" sondern ausschließlich über Schleifen bzw. Berechnung der Indices des Feldes.

j o e
 
1. MinGW 5.1.3 mit GCC 3.4.2 übersetzt das ohne Warnungen oder Fehler.
2. Die Variable i kann ich ja mehrmals definieren, weil sie ja immer nur lokal für die eine Schleife gültig ist, für die ich sie im Schleifenkopf einführe.
3. Bei den Headern hast du recht... ich war zu faul ;)
4. Naja in C heißts ja glaub ich "#include<stdio.h>" und nicht "#include<iostream>" usw.
5. Das mit dem letzten Stein ist eine gute Idee :)
6. Werd mich mal dranmachen und die Zahlen aus dem Code bannen.

Danke

Würde mich aber trotzdem mal interessieren, was dein Compiler daran auszusetzen hat.
 
Wahrscheinlich verwendet er VC++ 6, und das has so seine Probleme mit dem C++ Standard. Mit VC8 kompilierts bei mir auch problemlos.
 
Was die Compiler-Fehler betrifft habe ich mal folgendes Beispiel aus der Funktion main genommen:

Code:
    // leeres Anfangsfeld erstellen
    for(int i=0;i<6;++i) {
        for(int j=0;j<15;j+=2) {
            feld[i][j]='|';
        }
    }
    for(int i=0;i<6;++i) {
        for(int j=1;j<14;j+=2) {
            feld[i][j]=' ';
        }
    }

In der Tat ist es so, dass verschiedene Compiler darauf unterschiedlich reagieren.

"In C++ implementierten Compilerbauer die Variante einmal so, dass die Variable nur im Block gilt, andere interpretierten die Sprachspezifikation so, dass diese auch außerhalb gültig blieb. Die aktuelle C++-Definition schreibt nun vor, dass die Variable außerhalb des Blocks nicht mehr gültig ist. Da es jedoch noch alten Programmcode gibt, haben viele Compilerbauer eine Option eingebaut, mit der das Verhalten der lokalen Variablen bestimmt werden kann." (Quelle: http://www.galileocomputing.de/openbook/javainsel6/javainsel_02_006.htm)

Insofern ist dein Code OK, wird aber eben nicht immer compiliert. Die Definition von j ist eindeutig, da sie ja innerhalb der geschweiften Klammern der äußeren Schleife definiert wird und somit in jedem Fall nur dort gilt.

Eine Warnung sollte dein Compiler allerdings dennoch ausgeben, denn deine Funktion main gibt keinen Rückgabewert zurück obwohl sie laut Definition einen Wert vom Typ int liefern sollte.

Die Beispiele aus deinem Code, die C++ -typisch sind habe ich natürlich nicht auf Vollständigkeit geprüft, gemeint habe ich damit aber auch was anderes. "Richtiges" C++ wäre dein Programm, wenn du Klassen und Objekte definieren würdest. In deinem Fall würde sich eine Klasse Feld anbieten, die sowohl die Daten (also dein Array) enthält als auch private Methoden zur Umrechnung von Spalten in Array-Indices sowie öffentliche Methoden für den Zugriff auf die Daten und die Ermittlung ob ein Zug zum Sieg führt.

j o e
 
Zuletzt bearbeitet:
Ein "return 0;" in int main() schadet nicht, ist aber überflüssig, da bei fehlerfreiem Durchlauf automatisch 0 zurückgegeben wird.
Man hätte das Ganze natürlich auch objektorientiert realisieren können, aber da liegt doch der Vorteil von C++: es lässt dich zwischen prozeduraler und oo Programmierung frei entscheiden.
 
joe67 schrieb:
Insofern ist dein Code OK, wird aber eben nicht immer compiliert. Die Definition von j ist eindeutig, da sie ja innerhalb der geschweiften Klammern der äußeren Schleife definiert wird und somit in jedem Fall nur dort gilt.
Der Code ist korrekt. Die andere "Variante" sowie der Kommentar von galileocomputing sind etwas ... veraltet (der Text auf galileocomputing in Anbetracht der Aktualität schlichweg falsch). Und zwar 9 Jahre, um genau zu sein. Seit dem gibt es da international standardisiert nix zu interpretieren sondern es ist festgelegt, dass die Variable nur in der Schleife gültig ist. Compiler die diesen Code nicht anstandslos compilieren gehören entweder zum Alteisen gelegt oder durch eine korrekte, neue Version ersetzt.

joe67 schrieb:
"Richtiges" C++ wäre dein Programm, wenn du Klassen und Objekte definieren würdest.
C++ ist eine Multiparadigmensprache. Und afaik hat er keinen Anspruch darauf erhoben objektorientiert zu sein.



Ansonsten ist der Code etwas sehr umständlich. Die Abfragen sollten wie Joe gesagt hat nicht alle händisch ausprogrammiert sein, sondern mit einer Schleife (mit variabler Spalten/Zeilenzahl am besten) ausgeführt werden. Ebenso reicht auch genau die Prüfung ob der letzte Zug zum Sieg geführt hat.

Die statics solltest du rauswerfen, stattdessen ein struct übergeben was sich die Werte merkt, wenn du denn prozedural bleiben willst. Die globale Variable "feld" in main.cpp ist auch sehr unschön und sollte beseitigt werden.


Wenn du Spaß dran hast, poste doch mal eine neue Version :)
 
Zuletzt bearbeitet:
Werd ich auf jeden Fall machen. Danke für alle kompetenten Antworten :)

Um mich rauszureden... hab das Ganze nachts geschrieben und wollte es dann einfach nur noch zum Laufen bringen. Aber da sich hier Leute gefunden haben, mit denen man darüber diskutieren kann, versuch ich nochmal mein Glück.
 
So, habs jetzt nochmal überarbeitet. Jetzt auch objektorientiert, joe ;)
Für Tipps und Anregungen bin ich natürlich offen :)

PS: Ich weiß, dass die diagonale Prüfung immer noch umständlich ist, aber ich habs nicht kapiert, wie ichs machen soll, dass er nur vom letzten gesetzten Buchstaben ausgehend prüft :freaky:
 

Anhänge

  • 4_gewinnt.7z
    1,2 KB · Aufrufe: 293
Als erstes muss ich sagen, dass mit der Ansatz mit Klassen (OK, es ist nur eine :)) besser gefällt als der ursprüngliche.

Mir fällt jedoch noch auf, dass du die Variablen r0 bis r5 ebenfalls durch ein Array ersetzen solltest, um den Code unabhängig von der Spielfeldgröße zu machen. Desweiteren meldet mein Compiler (jaja VC++6) immer noch zwei Fehler. Die Methoden Feld::pruefen und Feld::ziehen sind als bool definiert, liefern aber keinen Rückgabewert (bei pruefen meint der Compiler aufgrund des else if am Ende der Funktion es gäbe noch einen weiteren Zweig, der ja auch Formal da ist, aufgrund der bool-Variable gewonnen aber nie erreicht wird, also der nicht implementierte else zum else if).

Ich habe mir mal erlaubt, deinen Code umzuschreiben, und ihn auch von der Anzahl der Spielsteine die zum Sieg führen unabhängig zu machen. Damit sollte es nun auch möglich sein, 10 gewinnt auf 100x100 Feldern zu spielen. Getestet hab ich das ganze nur soweit wie meine Zeit es zulässt. Fehler könnten also noch drin sein. Probier einfach mal damit rum.

Außerdem habe ich mir mal auf die Schnelle andere Methoden zur Überpfüfung auf Spielende überlegt. Sind bestimmt nicht die elegantesten Lösungen, scheinen aber zu funktionieren und sind ebenfalls unabhängig von allen Spielparametern.

Als letztes noch ein Punkt, den ich allerdings so gelassen habe wie er ist: Die Methode zeichnen würde ich nicht als Member von Feld implementieren. Die Klasse Feld sollte sich nur um die Verwaltung des Spielfeldes in Hinblick auf die Spielregeln kümmern. Grafische Ausgabe hat dort nichts verloren. Auf diese Weise wärst du in der Lage, das Spiel grafisch in verschiedensten Varianten zu gestalten, ohne dass du das Herz des Spiels nämlich die Klasse Feld nochmal anfassen musst. Die Klasse Feld sollte dazu aber noch eine Methode zur Verfügung stellen, mittels derer der Inhalt von Feld (bzw. einzelnen Feldern) abgefragt werden kann. Außerdem habe ich Drei Konstanten in feld.h definiert, die ich eher als Parameter bei der Initialisierung von Feld übergeben würde. Damit wäre die Klasse dann unabhängig und parametriebar.

j o e
 

Anhänge

  • 4win.7z
    1,4 KB · Aufrufe: 277
Danke für die Mühe! :)

Bei der Funktion "ziehen" hast du natürlich recht. Das hab ich einfach übersehen. Komisch nur, dass der Compiler nicht gemotzt hat. Normalerweise ist der ziemlich kleinlich, vor allem mit Warnungen. Bei der Methode "pruefen" schreib ich dann das else if einfach zu nem else um.

Werde mir deinen Code auf jeden Fall mal anschaun ;)
 
Zurück
Oben