Schach Designfehler, Algorithmenproblem

HazelNut

Mitglied
Hey, ich habe vor ein paar Wochen Schach geschrieben, welches auch funktioniert.
Nun wollte ich es etwas erweitern und noch ein paar Regeln implementieren.

Das Problem ist, dass ich wohl meine Klassen nicht sehr gut geplant habe bzw irgendwie meine Methoden zu sehr "verschachtelt" habe.

Mein jetziger Ablauf:

Controller:
erstellt GUi und ChessGame
ChessGame:
erstellt das ChessBoard
setzt die Figuren auf das board

ChessBoard:
ein zweidimensionales Array von ChessFigures
Das board weiß wo sich die Figuren befinden, die Figuren selbst wissen es nicht.

DIe Regeln wohin die FIguren springen dürfen sind in jeder FIgurklasse einzeln implementiert sonst nirgends (das ist mM nach schon ein Fehler).
Okay, nicht in jeder Klasse - Vererbung....
Jedenfalls sobald die Methode getAvailDestinations() aufgerufen wird, wird eben gecheckt wohin die springen dürfen und dann wird ein zweidimensionales bool Array zurückgegeben.

Dieses zurückgegebene Array wird der move() im board übergeben, welche eben dann springt, wenn der Ort zu dem gesprungen werden soll auch im destinations[][] ein true gesetzt ist.
So weit so gut.

Nun, möchte ich für meine Bauern enPassant und Rochade implementieren.
Leider stehe ich da nun an.

Wenn ich meine canDoEnPassant() einfach mit der getavaildest() Methode verbinde, dann würde es bedeuten, dass ich immer zwei true-Einträge zurückgebe welche aber gar nicht korrekt sein müssten.

Sprich, ich könnte zwar dahin springen und enPassant machen, jedoch wird in dieser Methode nur geschaut wo ich alles hingehen darf und nicht ob ich dort hingehe.

Ich habs zwar mal so probiert aber es funktioniert nicht wirklich.
Ich habe nämlich ein int doneDraws welche eben die gemachten Züge angeben soll.
Jedoch ist diese irgendwie nie korrekt, sie erhöht sich immer.
Ich erhöhe sie wenn ein Zug getan wurde, nur ist es so als ob ich sie nicht für die jeweilige Instanz erhöhe sondern eher für die jeweilige Klasse.
Aber auch da gibt es eine inkonsistenz:
Ich mache zB.: einen Zug weis, dann ist die var auf 1 dann mit Schwarz-> var=1
dann ziehe ich wieder mit dem gleichen weisen ist sie auf 2
dann ziehe ich mit einem ANDEREN Schwarzen und die var ist auf 2 obwohl sie für diesen eigentlich auf eins sein sollte. Dann mit nem anderen Weisen ist sie auf 3.....

Ich weis einfach nicht wo ich das überprüfen soll und wo ich dann den gegnerischen Bauern killen soll.
Ich hoffe mal jemand von euch hat eine erlösende Idee für mich.

PS: Code kann ich noch nachreichen:)
 

hasso

Mitglied
Das board weiß wo sich die Figuren befinden, die Figuren selbst wissen es nicht.

DIe Regeln wohin die FIguren springen dürfen sind in jeder FIgurklasse einzeln implementiert sonst nirgends (das ist mM nach schon ein Fehler).

D.h. heißt das Board kennt die Positon und wo liegt jetzt die Methode getAvailDestiantions() genau ? In der Figur kann sie ja nicht liegen, die Figur kann ja keine möglichen Felder zurückgeben wenn sie gar nicht weiß wo sie sitzt und übergeben wird scheinbar nichts.
 

Michael...

Top Contributor
hab nicht alles durchgelesen bzw. verstanden.
DIe Regeln wohin die FIguren springen dürfen sind in jeder FIgurklasse einzeln implementiert sonst nirgends (das ist mM nach schon ein Fehler).
Das sehe ich genauso, die Regeln können meinetwegen in der Figur hinterlegt sein, aber auf jeden Fall muss der Controller diese Regeln kennen, den - wie der Name ja verrät - soll er den Spielverlauf steuern.
Nun, möchte ich für meine Bauern enPassant und Rochade implementieren.
Leider stehe ich da nun an.
Wie Du hier ja selbst erkannt hast, stösst die bisherige Implementierung hier an Ihre Grenzen bzw. lässt sich nur noch recht komplex ausbauen. Der Controller steuert das Spiel, kennt die Regeln, das Model verwaltet den Spielstand (Position ...) und die Figuren
 
P

Pippl

Gast
....
Ich habe nämlich ein int doneDraws welche eben die gemachten Züge angeben soll.
Jedoch ist diese irgendwie nie korrekt, sie erhöht sich immer.
Ich erhöhe sie wenn ein Zug getan wurde, nur ist es so als ob ich sie nicht für die jeweilige Instanz erhöhe sondern eher für die jeweilige Klasse.
Aber auch da gibt es eine inkonsistenz:
Ich mache zB.: einen Zug weis, dann ist die var auf 1 dann mit Schwarz-> var=1
dann ziehe ich wieder mit dem gleichen weisen ist sie auf 2
dann ziehe ich mit einem ANDEREN Schwarzen und die var ist auf 2 obwohl sie für diesen eigentlich auf eins sein sollte. Dann mit nem anderen Weisen ist sie auf 3.....

Hast du für jede Figur eine Klasse (König, Turm, Bauer, Läufer, ...), wenn ja dann hast du dieses Attribut "var" vl statisch in der Klasse. Somit erhöht sich für alle Läufer der Zähler wenn du mit einem weißen oder schwarzen ziehst. Natürlich würde hier reichen das "static" zu entfernen.

Oder aber vl hilft eine Klasse "Move". Diese speichert mit welcher Figur, von wo auf wo gezogen wurde. Alle Moves speicherst du in einer Liste (oder 2 für schwarz und weiss je eine). Nun kannst du überprüfen welche Züge gemacht wurden bzw. mit welcher Figur wie oft gezogen wurde.
 

Ark

Top Contributor
Eine Skizzierung der bisherigen Klassen wäre wohl sinnvoll: Welche Klasse hat welche Felder (und was bedeuten diese, sofern dies nicht intuitiv verständlich ist)?

Ark
 

HazelNut

Mitglied
Okay, ich dachte ich hätte den Ablauf hinreichend erklärt.

Hast du für jede Figur eine Klasse (König, Turm, Bauer, Läufer, ...), wenn ja dann hast du dieses Attribut "var" vl statisch in der Klasse. Somit erhöht sich für alle Läufer der Zähler wenn du mit einem weißen oder schwarzen ziehst. Natürlich würde hier reichen das "static" zu entfernen.

Nein, habe ich (leider) nicht gemacht.
Ich habe in meinem Interface die Methode setDrawCount() und die wird dann eben weitervererbt.
In meiner Abstracten Klasse sage ich dort eben count++ .

Die getAvailDes() liegt in meiner LinearChessMan sowie in Bauer und Springer. Die wie und wohin sie sich bewegen können sind ebenfalls dort gespeichert.

Der getAvailDes() wird eben das board übergeben (also weis diese wo sich alle FIguren befinden) sowie die Position der ausgewählten Figur.
Und daraus wird eben ein 2D bool Array erstellt wohin diese Figur sich bewegen darf.

Was ich mir überlegt habe:
Ich könnte ja, sobald versucht wird sich mit einem bauern zu bewegen wird die move() methode in ChessBoard aufgerufen, diese erhällt die jetzige Pos und wohin es soll, sowie die Figur.
Ich könnte ja mein getAvailDes() ganz einfach so lassen und dann im move() überprüfen ob es sich bei der übergebenen Figur um einen bauern handelt und dann entsprechend prüfen ob ein enpassant möglich ist und dies dann ausführen.
Nur finde ich, dass es generell viel zu verschachtelt schon ist,.....
Mir kommt auch vor das zuviel Logik in meiner View ist.

Vielleicht kann mir ja jemand einen Tipp geben zu meinem Design:

attachment.php


Bitte nicht zu sehr auf die Assoziationen verlassen, bin nicht so ein Freund von den Verknüpfungen.
Habs falsch generiert, Pawn und Knight erben von AbstractChessMan.
Tile wird von den Figuren verwendet um die letze Position zu speichern.

Ich muss zugeben, dass ich das Projekt auch nur als Prototyp gesehen habe, vllt fange ich einfach nochmal an und behalte die Algorithmen....
 

Anhänge

  • Klassendiagramm2.png
    Klassendiagramm2.png
    14 KB · Aufrufe: 159

hasso

Mitglied
Der getAvailDes() wird eben das board übergeben (also weis diese wo sich alle FIguren befinden) sowie die Position der ausgewählten Figur.
Und daraus wird eben ein 2D bool Array erstellt wohin diese Figur sich bewegen darf.

Ich frage nochmal:

getAvailDes() kennt das Board sowie die ausgewählte Figur und prüft doch jetzt schon wohin eine Figur fahren darf. D.h. in Bezug auf den Bauern muss diese Methode doch jetzt schon prüfen:

-> darf ich überhaupt fahren oder steht etwas vor dem Bauern.
-> darf ich ein oder zwei Felder fahren (also sitzt der Bauer noch auf dem Startfeld)

Wieso kann hier jetzt nicht auch eine Überprüfung erfolgen ob enPassant möglich ist ?
Du musst dafür natürlich den letzten Zug kennen.
 

HazelNut

Mitglied
Naja mal die Methode:
public boolean[][] getAvailableDestinations(Chessboard board, int row, int col)

dem, wird eben das board übergeben, sodass man weis was wo steht.
Sowie die Position der Figur die bewegt werden soll.

-> darf ich überhaupt fahren oder steht etwas vor dem Bauern.
-> darf ich ein oder zwei Felder fahren (also sitzt der Bauer noch auf dem Startfeld)
Korrekt.

Jein, es könnte hier erfolgen.
Es geht eher darum das mir vielleicht ein paar Tipps zum Design mehr helfen würden, sprich was wo gemacht wird und ob mein Design so passt.

Im Endeffekt kann ich es bereits dort prüfen und eben gegebenfalls die beiden Felder links und rechts vor mir als true setzen.

Nur, heißt es ja nicht das ich dorthin gehe sondern nur, dass ich dorthin gehen darf.
Wenn sich der User nun Entscheidet nach links oben zu springen, dann müsste ich nochmal überprüfen (am besten in der move()) ob dies per enPassant geschah. Im Endeffekt könnte ich ja einfach schauen ob sich meine neue Spalte nicht mit der alten übereinstimmt und dann eine Reihe nach unten gehen und dort den Bauern löschen.
 

bananajoe

Mitglied
hab nicht alles durchgelesen bzw. verstanden.

Das sehe ich genauso, die Regeln können meinetwegen in der Figur hinterlegt sein, aber auf jeden Fall muss der Controller diese Regeln kennen, den - wie der Name ja verrät - soll er den Spielverlauf steuern.

Sehe ich genauso. Eine Rochade kann nur ausgeführt werden, wenn der König vorher keine Züge ausgeführt hat. Damit der Controller diese Regel anwenden kann, sollte die Königsfigur entweder den letzten Zug speichern oder eine boolsche Variable besitzen, die entsprechend abgefragt werden kann.
 

Ark

Top Contributor
Damit der Controller diese Regel anwenden kann, sollte die Königsfigur entweder den letzten Zug speichern oder eine boolsche Variable besitzen, die entsprechend abgefragt werden kann.
Es gibt noch ein paar mehr Bedingungen für eine Rochade, aber das nur am Rande. Was mir in dem Klassendiagramm fehlt, ist eine Klasse Player für die teilnehmenden Parteien (von denen es pro Spiel nur zwei Instanzen gibt: eine für Weiß und eine für Schwarz). In einem solchen Objekt könnte man auch z.B. speichern (bzw. abfragbar machen), ob ein bestimmter Bauer en passent schlagen könnte oder ob eine Rochade noch möglich ist. D.h., ob der König schon einmal zog, würde ich in so einem Player-Objekt speichern, entsprechendes für en passant. So müssen spezielle Figuren wie König und Bauer nicht gesondert behandelt werden.

Ark
 

AndiE

Top Contributor
Wenn ich das richtig verstehe, wird am Anfang ein Objekt chessboard angelegt, dass mit 32 Objekten Chessmann assoziert ist, und ein Array 8 mal 8 enthält. Chessman ist die Superklasse für AbstractChessman, und diese die Superklasse von Linearchessman. Von diesen beiden Klassen erben die Figuren. Der ChessController zeichnet mit dem Chessboard auf dem Chessdisplay.
Nun gibt Weiß einen Zug ein.

Wie? Was passiert nun bis zum Neuzeichnen des Chessboards?

Meine Idee wäre,
dass die Eingabe durch zwei Mausklicks für Start- und Zielposition erfolgt. Dann würde ich zuerst nachsehen, steht eine Figur auf dem Feld, darf der Spieler ( es werden Spielerobjekte benötigt) sie ziehen (Farbe) und welche Figur ist das. Nun wird abgefragt, wohin die Figur ziehen darf. Ist das eingegebene Zielfeld dabei, wird der Zug ausgeführt. Dann wird das Board neu gezeichnet.

!!!! Bei diesem Vorgehen fehlt aber noch die Fehlerbehandlung, wenn Figuren im Weg stehen!!!

Die Funktionen für das Schlagen der Figuren müssen im ChessBoard sein.

Habe ich das so richtig verstanden?
 
Zuletzt bearbeitet:

HazelNut

Mitglied
Sehe ich genauso. Eine Rochade kann nur ausgeführt werden, wenn der König vorher keine Züge ausgeführt hat. Damit der Controller diese Regel anwenden kann, sollte die Königsfigur entweder den letzten Zug speichern oder eine boolsche Variable besitzen, die entsprechend abgefragt werden kann.

Ja, was eine Rochade ist, weis ich sehr wohl.
Nur geht es mir jetzt hauptsächlich um enPassant und, dass ich ein gutes Design zusammenbekomme.


Es gibt noch ein paar mehr Bedingungen für eine Rochade, aber das nur am Rande. Was mir in dem Klassendiagramm fehlt, ist eine Klasse Player für die teilnehmenden Parteien (von denen es pro Spiel nur zwei Instanzen gibt: eine für Weiß und eine für Schwarz). In einem solchen Objekt könnte man auch z.B. speichern (bzw. abfragbar machen), ob ein bestimmter Bauer en passent schlagen könnte oder ob eine Rochade noch möglich ist. D.h., ob der König schon einmal zog, würde ich in so einem Player-Objekt speichern, entsprechendes für en passant. So müssen spezielle Figuren wie König und Bauer nicht gesondert behandelt werden.

Weshalb ich dort speichern soll, wenn der bauern bzw König bereits gezogen hat leuchtet mir nicht ein.
Zum Player: Weiter unten.

Wenn ich das richtig verstehe, wird am Anfang ein Objekt chessboard angelegt, dass mit 32 Objekten Chessmann assoziert ist, und ein Array 8 mal 8 enthält. Chessman ist die Superklasse für AbstractChessman, und diese die Superklasse von Linearchessman. Von diesen beiden Klassen erben die Figuren. Der ChessController zeichnet mit dem Chessboard auf dem Chessdisplay.
Nun gibt Weiß einen Zug ein.

Wie? Was passiert nun bis zum Neuzeichnen des Chessboards?

Korrekt, nur dass der Controller ChessGame erstellt, welcher das ChessBoard mit seinen Anfangsbedingungen initialisiert. Und dieses ChessGame-Objekt wird dem Display für weiteres übergeben.

Eine Player-Klasse gibt es nicht. Ich wollte zwar eine machen, wusste aber nicht wirklich wie ich diese gestalten sollte.
Bei mir funktioniert das so:
Jede Figur weiß ob sie Schwarz oder Weiß ist.
Im Controller lege ich einfach ein int an, welches sich zwischen 0 und 1 abwechselt und diese Variable übergebe ich per setter in meiner gameLoop der View.
Nachdem ein Zug gemacht wurde, hole mir den Zustand.... in einer while-Schleife....., switcht diese Variable halt.Könnte ich eigentlich auch mit nem boolean machen.

Im View schaut es eben so, aus, dass wenn ein Button gedrückt wurde wird von dort mal die Position ausgelesen.
wenn dann nocheinmal auf einen Btn gedrückt wird, wird zuerst geschaut ob es sich um die korrekte Farbe handelt und die von der vorigen Pos gedrückten Figur ausgelesen.
Dann wird eben mein getavaildes() sowie meine move() ausgelöst, wenn das ein erfolgreicher Zug war wird ein boolean gesetzt, welches alle 10ms per getter von meinem Controller abgefragt wird.
Wenn der Zug erfolgreich war, wird die GUI geupdated, sprich einfach die alte Pos mit "" und die neue mit dem neuen Bild belegt. (Durch move() wird das board ja bereits neu belegt)

Zudem gibt es in meinem ChessGame eine methode die nach jedem Zug eine Liste aller Figuren zurückgibt, und dabei wird halt geschaut ob der König noch lebt.

Nicht vom Namen "Controller" verwirren lassen, der initialisiert nur die beiden Objekte und switcht bzw überprüft den Zustand.
Ich glaube ich muss da wirklich nochmal von null anfangen, könnte mir jemand ein paar Tipps dazu geben?
Ich kann zwar enPassant noch hinklatschen nur spätestens bei Rochade ist schluss. Da benötigt man schießlich zwei Züge, was durch die momentane Implementierung nicht realisierbar ist.
Außer ich würde 300 Zeilen Spaghetticode schreiben.
 

bananajoe

Mitglied
Wenn es Dir um Designfragen geht, dann solltest Du mit einigen Use Cases beginnen wie Spiel laden, Spiel neu usw.

Zum En Passant Problem: Angenommen, die Bauern haben ein Flag wie z.B. enPassant. Zieht nun ein Bauer zwei Felder, muss das Flag enPassant gesetzt sein. Nun, dieser Bauer kann beim nächsten gegnerischen Zug geschlagen werden. Die Figur, welche diesen Bauer schlagen will, muss ebenfalls ein Bauer sein und der Zug muss unmittelbar erfolgen d.h,. die zuletzt abgelegte Figur im Controller sein. Die gegnerische Figur kann Prüfung des enPassant Flag vornehmen, in dem sie entsprechende Methoden der zu schlagenden Figur aufruft. Welche Figur als letzte gezogen ist, kann in eine Variable des Controllers abgelegt werden. Wenn sich nun der Gegner für einen anderen Zug entscheidet, dann kann der Controller, sofern die zuletzt gespeicherte Figur ein Bauer ist, das enPassant Flag wieder auf 0 stellen und die neu gezogene Figur dort speichern. Die Lösung stützt sich hier auf eine Arbeitsteilung zwischen Figur und Controller ab, zumal gewisse Zustände sich auf die Figur beziehen und andere auf das Spiel.

Ich würde das so implementieren, dass die Figuren berechnen, welche Züge möglich sind und welche Figuren geschlagen werden können. Da die Dame die Züge der Türme und der Läufer ausführen kann, bietet es sich an, diese Methoden auszulagern, um Code Duplizierung zu vermeiden. Die Züge werden so ausgeführt, dass eine entsprechende Message zum Controller geschickt wird. So kann der Controller den neuen Spielstand aktualisieren und auch das Spielfeld graphisch aktualisieren.

Was die Regeln verkompliziert sind schachspezifische Regeln. Zwar kann der Springer verschiedene Felder anspringen, aber nur dann, wenn er beim Springen den eigenen König nicht einem Schach aussetzt.
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Zum enPassant braucht man eigentlich keine "besondere" Information - außer den letzten Zug, der gemacht wurde, und der sollte vermutlich sowieso irgendwo gespeichert werden. Dann kann man sowas machen wie (PSEUDOCODE!)
Java:
boolean mayCaptureEnPassant(WhitePawn whitePawn)
{
    return 
        previousMove.piece == BackPawn &&
        previousMove.startRow == 6 &&
        previousMove.endRow == 4 &&
        // EDIT: Column auch noch prüfen
        previousMove.startColumn = whitePawn.column +/- 1;
}
 

Ark

Top Contributor
Okay, mal ne Skizze, wie ich es vielleicht machen würde:

  • Es gibt ein Spielbrett als Instanz einer Klasse Board (zufälligerweise mit 8×8 unveränderliche Instanzen der Klasse Square (prinzipiell aber beliebig groß), Zugriff über entsprechende Methoden). Das Spielbrett speichert Referenzen auf alle beteiligten Parteien (Player, siehe unten).
  • Eine Figur weiß nicht, wo auf dem Brett sie gerade steht. Das weiß nur das Brett. Dafür wäre neben der Abbildung Position→Figur noch eine Abbildung Figur→Position (etwa durch eine HashMap, wichtig für Test auf Bedrohung des Königs, siehe unten) sinnvoll.
  • Es gibt Instanzen einer Klasse Player, diese Instanzen repräsentieren die verschiedenen Parteien (prinzipiell könnte es also beliebig viele Parteien geben). Jede Partei merkt sich, EDIT[STRIKE]ob der König schon einmal zog und[/STRIKE][ob der König schon einmal zog, merkt er sich am besten selbst] welche Figur der jeweiligen Partei als letztes gezogen wurde. Außerdem steht darin die Menge aller von dieser Partei geschlagenen Figuren (Weiß merkt sich also, welche schwarzen Figuren von Weiß geschlagen wurden).
  • Für jede Figur (König, Dame, …, Bauer) und für jede Partei gibt es entsprechend Objekte (d.h., für jeden der 8 Bauern einer Partei gibt es voneinander unterscheidbare Objekte). Die "Farbe" einer Figur ergibt sich aus einem final Player player jeder Figur.
  • Jede Figur hat eine Methode
    Code:
    Set<Square> threatens(Square from, Board board)
    (oder wie auch immer man das auf Englisch sagen würde), die Folgendes ermittelt: Angenommen, this (die jeweilige Figur) stünde an der Position from auf dem Brett board. Welche Felder könnte dann this erreichen, wenn this gezogen würde? (Mit dieser Methode kann z.B. leicht ermittelt werden, ob der König bedroht wird: Um zum Beispiel in Erfahrung zu bringen, ob der schwarze König von einem weißen Turm bedroht wird, fragt man einfach einen schwarzen Dummy-Turm, ob dieser einen weißen Turm schlagen könnte, wenn dieser Dummy-Turm auf der Position des Königs wäre. Um herauszubekommen, welche Figuren noch in Frage kommen, fragt man einfach das Brett.) Für die Rochade und en passant würde dann die jeweilige Figur (König bzw. Bauer) entsprechende Blicke aufs Brett werfen oder fragen, ob der jeweilige gegnerische Bauer der war, der gerade im letzten Zug bewegt wurde. Wichtig: Es wird keine Unterscheidung gemacht, was für eine Figur man gerade fragt, d.h., bei dieser Methode können alle Figuren komplett gleich behandelt werden! Nur Bauern müssen wissen, wie en passant funktioniert, und nur der König muss wissen, wie eine Rochade funktioniert.
  • Jede Figur hat eine Methode
    Code:
    move(Square from, Square to, Board board)
    , die eine entsprechende Bewegung der Figur von from auf to auf dem Brett board durchführt. Bei einer Rochade als Zug des Königs würde da natürlich der entsprechende Turm mitbewegt.
  • Bemerkung: Dass die Dame so zieht, wie Läufer und Turm zusammen, würde ich als dummen Zufall betrachten; der entsprechende Code wäre wahrscheinlich einfach zweimal zu implementieren. Zumindest würde ich auf keinen Fall eine Vererbungsbeziehung (auch nicht über Interfaces!) oder Ähnliches daraus machen.

Was die Sache etwas verkompliziert, ist der Umstand, dass nach einem Zug der eigene König nicht bedroht sein darf. Man müsste also eine Art Dummy-Brett erstellen, das Auskunft darüber gibt, wie die Situation nach dem Zug aussehen würde, und dort untersuchen, ob der König dann bedroht wäre. Entsprechende Züge müssten dann aus der Ergebnismenge von
Code:
threatens()
herausgenommen werden.

Prinzipiell wäre es aber mit diesem Ansatz auch möglich, verschiedene Varianten des Schachspiels (etwa mit mehreren Parteien oder anderen Figuren) zu implementieren.

Hoffentlich war das verständlich. ^^;

Ark
 
Zuletzt bearbeitet:

HazelNut

Mitglied
Hmm
Wozu soll sich weiß merken welche Figuren von Schwarz geschlagen wurden?

Heißt, dass das eine Figur seinen Besitzer, also den Player kennt?

threatens habe ich ja praktisch schon in meiner getDes(), müsste ja nur noch ein weiteres Array erstellen.
Warum sollte die Figur die move() haben? Warum nicht das board?

Ich weiß jetzt nicht wirklich was du mit "dummen Zufall" meinst, dieses Spiel ist schon ein paar Jährchen älter...
Warum Vererbungsbeziehung? So wie ich oben die Chessman Klassen strukturiert habe dürfte doch wohl passen?

Wozu benötige ich genau ein Square Objekt?
Denn einzigen Vorteil denn ich darin sehe ist, dass ich nicht immer die ganzen Zeilen und Spalten ansprechen muss sondern einfach das Square, aber ansonsten...
 

Ark

Top Contributor
Hmm
Wozu soll sich weiß merken welche Figuren von Schwarz geschlagen wurden?
Für die Statistik. ;) Ich meine, vielleicht will man ja eine Übersicht darüber führen, welche Partei welche Figuren welcher anderen Parteien geschlagen hat. (Das ist nicht notwendigerweise die Differenz zur Anfangsverteilung, da ja auch z.B. zwei Damen derselben Partei geschlagen werden könnten.) Wenn man eine solche Übersicht nicht braucht, kann man sie freilich auch weglassen; nur wenn sie benötigt wird, wäre das meines Erachtens ein geeigneter Weg. (Man beachte, dass mein Entwurf auch schon Varianten berücksichtigt, in denen mehr als zwei Parteien gegeneinander spielen.)

Heißt, dass das eine Figur seinen Besitzer, also den Player kennt?
Ja, wie ich oben ja schon schrieb: Die "Farbe" einer Figur ergibt sich aus einem final Player player jeder Figur.

Warum sollte die Figur die move() haben? Warum nicht das board?
Natürlich kann und sollte das Board auch eine
Code:
move()
-Methode haben, allerdings sollte
Code:
Board.move()
praktisch nur den Aufruf an die entsprechende
Code:
Piece.move()
-Methode weiterleiten, da sonst das Board dazu verdonnert würde, die Zugmöglichkeiten der einzelnen Figuren zu kennen. Außerdem sollte
Code:
Piece.move()
selbst nicht "von Außen" aufgerufen werden, sondern immer nur über
Code:
Board.move()
; dies wäre zumindest mein Vorschlag.

Ich weiß jetzt nicht wirklich was du mit "dummen Zufall" meinst, dieses Spiel ist schon ein paar Jährchen älter...
Warum Vererbungsbeziehung? So wie ich oben die Chessman Klassen strukturiert habe dürfte doch wohl passen?
Das stimmt. Ich bezog mich auch eher auf den Beitrag, den Marco13 verlinkt hat; dort sind entsprechende Ansätze zu sehen.

Wozu benötige ich genau ein Square Objekt?
Denn einzigen Vorteil denn ich darin sehe ist, dass ich nicht immer die ganzen Zeilen und Spalten ansprechen muss sondern einfach das Square, aber ansonsten...
Das stimmt ebenfalls. Ich habe auch überlegt, ob man diese Klasse weglassen könnte, und natürlich geht das. Es wäre lediglich etwas schwierig bei der Beschreibung z.B. des Rückgabewertes von
Code:
threatens()
geworden, obwohl natürlich auch
Code:
Set<int[]>
möglich ist. Ergo: Sieh die Klasse
Code:
Position
lediglich als Entwurfshilfe an.

Ark
 

bananajoe

Mitglied
@Hazelnut: Schreib doch ein kleiner Protyp mit den bisher vorhandenen Klassen und ermögliche den Prototypen eine bestimmte Position zu laden. So kannst Du auf die Schnelle die Richtigkeit der Schachregeln testen z.B. En Passant und Rochade sowie die Qualität des Designs überprüfen.
 
Zuletzt bearbeitet:
S

Spacerat

Gast
Hab' mir jetzt auch nicht alles durchgelesen, aber man verzeiht mir hoffentlich, wenn ich eine simple Logik präsentiere, die in 2 Klassen abstrahiert ist.
Java:
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

public final class CheckerBoard {
	public enum Field {
		A1, A2, A3, A4, A5, A6, A7, A8,
		B1, B2, B3, B4, B5, B6, B7, B8,
		C1, C2, C3, C4, C5, C6, C7, C8,
		D1, D2, D3, D4, D5, D6, D7, D8,
		E1, E2, E3, E4, E5, E6, E7, E8,
		F1, F2, F3, F4, F5, F6, F7, F8,
		G1, G2, G3, G4, G5, G6, G7, G8,
		H1, H2, H3, H4, H5, H6, H7, H8,
		;
		public static final int ROW_STRIDE = (int) Math.sqrt(values().length); // bzw. 8
	}

	public enum MoveType {
		STRIKE,
		MOVE,
		INVALID,
	}

	private final Map<Field, Figure> fields2figure = new TreeMap<>();
	private final Map<Figure, Field> figure2fields = new TreeMap<>();

	public MoveType set(Field field, Figure figure) {
		boolean occupied = fields2figure.get(field) != null;
		Field from = figure2fields.get(figure);
		if(figure.isValidMove(field, Collections.unmodifiableMap(fields2figure))) {
			fields2figure.put(from, null);
			fields2figure.put(field, figure);
			return (occupied)? MoveType.STRIKE : MoveType.MOVE;
		}
		return MoveType.INVALID;
	}
}

abstract class Figure {
	public enum Type {
		KING,
		QUEEN,
		BISHOP,
		KNIGHT,
		ROOK,
		PAWN,
	}

	private final Type type;

	protected Figure(Type type) {
		if(type == null) {
			throw new NullPointerException();
		}
		this.type = type;
	}

	public final Type getType() {
		return type;
	}

	@Override
	public String toString() {
		return type.name();
	}

	/**
	 * Testet, ob ein Zug gueltig ist.
	 * @param to
	 * @param board
	 * @param occupied
	 * @return
	 */
	public abstract boolean isValidMove(CheckerBoard.Field to, Map<CheckerBoard.Field, Figure> board);
}
Okay, hier fehlen zwar die konkreten Figuren nebst Regeln und ein Board-Reset, aber ich denke, dass ist durchaus ausbaufähig.
 
Zuletzt bearbeitet von einem Moderator:

HazelNut

Mitglied
enPassant ist schon lange ausprogrammiert und funktioniert :)
Ich bin gerade dabei mir ein hübsches UML-Diagramm zu zeichnen , damit ich dann möglichst nichts durcheinanderwürfle.

chessprogramming kenne ich bereits, aber etwas überfüllt... jedoch auch ganz gut.

Danke, fürs Beispiel, spacerat!

Wie schaut es aus mit meiner getDes() weiterhin das ganze Feld als bools zurückgeben oder was anderes?

Weil, ich es jetzt schon 2x hier gelesen habe, ist es wirklich so, dass sich mein Pferd nicht bewegen darf wenn daraufhin mein König im Matt steht? Ich finde diese Regel nirgends.
Das es logisch ist, dass man das Pferd dann nicht bewegt sollte klar sein, aber eine Regel?
 
S

Spacerat

Gast
Weil, ich es jetzt schon 2x hier gelesen habe, ist es wirklich so, dass sich mein Pferd nicht bewegen darf wenn daraufhin mein König im Matt steht? Ich finde diese Regel nirgends.
Das es logisch ist, dass man das Pferd dann nicht bewegt sollte klar sein, aber eine Regel?
Es gibt eine Regel, die im allgemeinen besagt, dass man keine Züge machen darf, durch welche man sich selbst ins Schach setzt, weil die Folge davon wäre, dass man dadurch im nächsten Zug des Gegners "Matt" ist.
Zu meinem Beispiel... Tja, das ist bei weitem nicht ausgereift (in ca. einer Stunde aus dem Ärmel geschüttelt, ohne auch nur einen von den geposteten Links gelesen zu haben). Inzwischen habe ich mich aber an zwei Schüler aus meiner Schulzeit erinnert, welche sich ständig in einer Art Geheimsprache unterhielten. Ihre Unterhaltungen begannen meist mit "A2-A4" ;).
Mal drüber Nachgedacht und zu dem Schluss gekommen, dass ein imaginäres Schachbrett keine weiteren Infos benötigt, als zwei Felder (Zug von Feld a nach Feld b). Hier ist also das Ergebnis. Nach wie vor müssen den Figuren ihre Regeln beigebracht werden. Um aber herauszufinden, ob ein Zug aus ihrer Sicht gültig ist oder nicht, benötigen sie dazu natürlich eine unveränderliche Instanz der Feld->Figur Karte (Map).
Java:
package chess;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import chess.CheckerBoard.Faction;
import chess.CheckerBoard.Field;
import chess.CheckerBoard.MoveType;

public class Chess {
	public static void main(String[] args) {
		CheckerBoard cb = new CheckerBoard();
		cb.reset();
		switch(cb.set(Field.A2, Field.A4)) {
		case MOVE:
			System.out.println("move");
			break;
		case STRIKE:
			System.out.println("STRIKE!");
			break;
		case INVALID:
		default:
			System.err.println("invalid move");
		}
	}
}

final class CheckerBoard {
	public enum Field {
		A1, A2, A3, A4, A5, A6, A7, A8,
		B1, B2, B3, B4, B5, B6, B7, B8,
		C1, C2, C3, C4, C5, C6, C7, C8,
		D1, D2, D3, D4, D5, D6, D7, D8,
		E1, E2, E3, E4, E5, E6, E7, E8,
		F1, F2, F3, F4, F5, F6, F7, F8,
		G1, G2, G3, G4, G5, G6, G7, G8,
		H1, H2, H3, H4, H5, H6, H7, H8,
		;
		public static final int ROW_STRIDE = (int) Math.sqrt(values().length); // bzw. 8

		public static int indexOf(Field f) {
			return f.ordinal();
		}

		public static int[] coordsOf(Field f, int[] target) {
			if(target == null) {
				target = new int[2];
			}
			int index = indexOf(f);
			target[0] = index / ROW_STRIDE;
			target[1] = index % ROW_STRIDE;
			return target;
		}

		public static Field getByIndex(int index) {
			for(Field f : values()) {
				if(index == f.ordinal()) {
					return f;
				}
			}
			return null;
		}

		public static Field getByCoords(int x, int y) {
			return getByIndex(x * ROW_STRIDE + y);
		}
	}

	public enum MoveType {
		STRIKE,
		MOVE,
		INVALID,
		CHECK,
	}

	public enum Faction {
		BLACK,
		WHITE,
	}

	private static final Map<Faction, Set<Figure>> figureSet;

	static{
		Set<Figure> black = new HashSet<>();
		Set<Figure> white = new HashSet<>();
		int n = 0;
		for(n = 0; n < 8; n++) {
			black.add(new Pawn(Faction.BLACK));
			white.add(new Pawn(Faction.WHITE));
		}
		for(n = 0; n < 2; n++) {
			black.add(new Rook(Faction.BLACK));
			white.add(new Rook(Faction.WHITE));
			black.add(new Knight(Faction.BLACK));
			white.add(new Knight(Faction.WHITE));
			black.add(new Bishop(Faction.BLACK));
			white.add(new Bishop(Faction.WHITE));
		}
		black.add(new King(Faction.BLACK));
		white.add(new King(Faction.WHITE));
		black.add(new Queen(Faction.BLACK));
		white.add(new Queen(Faction.WHITE));
		Map<Faction, Set<Figure>> sets = new HashMap<>();
		sets.put(Faction.BLACK, Collections.unmodifiableSet(black));
		sets.put(Faction.WHITE, Collections.unmodifiableSet(white));
		figureSet = Collections.unmodifiableMap(sets);
	}

	private final Map<Field, Figure> fields2figure = new TreeMap<>();

	public void reset() {
		for(Field fd : Field.values()) {
			fields2figure.put(fd, null);
		}
		for(Entry<Faction, Set<Figure>> e : figureSet.entrySet()) {
			Faction fac = e.getKey();
			int pawnCount = 0;
			int rookCount = 0;
			int knightCount = 0;
			int bishopCount = 0;
			for(Figure fi : e.getValue()) {
				Field fd = null;
				switch(fi.getType()) {
				case BISHOP:
					fd = Field.getByCoords((bishopCount == 0)? 2 : 5, (fac == Faction.BLACK)? 7 : 0);
					bishopCount++;
					break;
				case KING:
					fd = Field.getByCoords(4, (fac == Faction.BLACK)? 7 : 0);
					break;
				case KNIGHT:
					fd = Field.getByCoords((knightCount == 0)? 1 : 6, (fac == Faction.BLACK)? 7 : 0);
					knightCount++;
					break;
				case PAWN:
					fd = Field.getByCoords(pawnCount, (fac == Faction.BLACK)? 6 : 1);
					pawnCount++;
					break;
				case QUEEN:
					fd = Field.getByCoords(3, (fac == Faction.BLACK)? 7 : 0);
					break;
				case ROOK:
					fd = Field.getByCoords((rookCount == 0)? 0 : 7, (fac == Faction.BLACK)? 7 : 0);
					rookCount++;
					break;
				default:
					throw new RuntimeException("no such figure type");
				}
				fields2figure.put(fd, fi);
			}
		}
	}

	public Map<Field, Figure> getPositions() {
		synchronized (fields2figure) {
			return Collections.unmodifiableMap(fields2figure);
		}
	}

	public MoveType set(Field from, Field to) {
		MoveType rc = checkState();
		synchronized (fields2figure) {
			Figure figure = fields2figure.get(from);
			if(figure != null) {
				rc = figure.move(from, to, getPositions());
				if(rc == MoveType.STRIKE || rc == MoveType.MOVE) {
					fields2figure.put(from, null);
					figure = fields2figure.put(to, figure);
				} else {
					rc = MoveType.INVALID;
				}
				if(checkState() == MoveType.CHECK) {
					figure = fields2figure.put(to, figure);
					fields2figure.put(from, figure);
					rc = MoveType.INVALID;
				} 
			}
		}
		return rc;
	}

	private MoveType checkState() {
		MoveType rc = MoveType.INVALID;
		return rc;
	}
}

abstract class Figure implements Comparable<Figure> {
	public enum Type {
		KING,
		QUEEN,
		BISHOP,
		KNIGHT,
		ROOK,
		PAWN,
	}

	private final Type type;
	private final Faction faction;
	private final String name;

	protected Figure(Type type, Faction faction) {
		if(type == null || faction == null) {
			throw new NullPointerException();
		}
		this.type = type;
		this.faction = faction;
		name = faction.name() + " " + type.name();
	}

	public final Type getType() {
		return type;
	}

	public final Faction getFaction() {
		return faction;
	}

	@Override
	public final String toString() {
		return name;
	}

	@Override
	public final int compareTo(Figure o) {
		return name.compareTo(o.name);
	}

	@Override
	public final int hashCode() {
		return System.identityHashCode(this);
	}

	@Override
	public final boolean equals(Object obj) {
		return this == obj;
	}

	/**
	 * Testet, ob ein Zug gueltig ist.
	 * @param from
	 * @param to
	 * @param board
	 * @return
	 */
	public abstract MoveType move(Field from, Field to, Map<Field, Figure> board);
}

class Pawn extends Figure {
	Pawn(Faction faction) {
		super(Type.PAWN, faction);
	}

	@Override
	public MoveType move(Field from, Field to, Map<Field, Figure> board) {
		return MoveType.INVALID;
	}
}

class Rook extends Figure {
	Rook(Faction faction) {
		super(Type.ROOK, faction);
	}

	@Override
	public MoveType move(Field from, Field to, Map<Field, Figure> board) {
		return MoveType.INVALID;
	}
}

class Knight extends Figure {
	Knight(Faction faction) {
		super(Type.KNIGHT, faction);
	}

	@Override
	public MoveType move(Field from, Field to, Map<Field, Figure> board) {
		return MoveType.INVALID;
	}
}

class Bishop extends Figure {
	Bishop(Faction faction) {
		super(Type.BISHOP, faction);
	}

	@Override
	public MoveType move(Field from, Field to, Map<Field, Figure> board) {
		return MoveType.INVALID;
	}
}

class Queen extends Figure {
	Queen(Faction faction) {
		super(Type.QUEEN, faction);
	}

	@Override
	public MoveType move(Field from, Field to, Map<Field, Figure> board) {
		return MoveType.INVALID;
	}
}

class King extends Figure {
	King(Faction faction) {
		super(Type.KING, faction);
	}

	@Override
	public MoveType move(Field from, Field to, Map<Field, Figure> board) {
		return MoveType.INVALID;
	}
}
...immer noch kein Link studiert, aber ich denke mal, so geht's am besten, oder hab' ich was Essentielles (ausser die Regeln) vergessen?
[EDIT]Die Methode "getPositions()" und "set(Field from, Field to)" habe ich auf "field2figure" synchronisiert, damit sich z.B. eine abstrakte Virtualisierung des Schachbretts jederzeit eine aktuelle unveränderliche Instanz (threadsafe) davon holen kann.[/EDIT]
[EDIT]Bitte Beachten: Letzte Änderungen betreffend "checkMateState()" (19:18).[/EDIT]
 
Zuletzt bearbeitet von einem Moderator:

HazelNut

Mitglied
Uhh danke für deine ausführliche Antwort :)
Obwohl ich etwas "seltsam" finde.
z.B.: dein getcoordinates() und dein großes switch-Konstrukt.
Warum übergibst du da ein int und kein Field?
Aber es (reset) schaut generell etwas komisch aus...

Naja hier mal meins:

attachment.php
 

Anhänge

  • Klassendiagramm2.jpg
    Klassendiagramm2.jpg
    60,5 KB · Aufrufe: 47
S

Spacerat

Gast
Ich habe den Feldern halt "Namen" (A1 bis H8) gegeben. Die Felder selbst sind über Index und Koordinaten erreichbar. Über die Koordinaten können einzelne Figuren z.B. auch prüfen, ob die Strecke für einen Zug frei ist.
Natürlich kann man die Ausgangsstellung auch als Map-Konstante festlegen und diese bei einem Reset schlicht kopieren, nur so weit habe ich bei meiner Kreation noch gar nicht gedacht. MAW: Jep, sieht komisch aus, um Designfehler unterhalten wir uns besser nicht mehr :lol:.
Ausserdem fehlt bei mir noch einiges mehr und auch an der "set()"- und der "checkState()"-Methode muss sich auch noch was machen lassen, das Board sollte z.B. auch feststellen, wer am Zuge ist.

Das Wichtige am Design sollte aber klar sein, nämlich die Verwendung von Enums überall dort, wo sie prinzipiell hingehören (Field, Faction, Figure.Type und MoveType) und unveränderliche Sets für die Figuren (soweit ich weiss, gibt es in Turnieren keine Regel, nach welcher man pro durchgebrachten Bauern eine Dame bekommt). Darüber hinaus sinds bei mir auch nur die Figuren, die gültige Züge erkennen können sollen. Leider habe ich im Moment noch keine Idee, wie bei meinem Konzept auf "Schach-Matt" getestet werden soll. Eines ist jedoch sicher, die Klasse "CheckerBoard" ist der Controller.

Zu deinem Design: (Höhö... ich hab' nicht mal eines. XD)
1. Ist es sinnvoll, die GUI (Visualisierung) der "Logik" festzulegen? Wenn du dir das bei mir mal ansiehst, so kann man im Prinzip alles Vorstellbare als Visualisierung verwenden.
2. Muss der Player unbedingt wissen, welche Figuren bereits geschlagen wurden? Mit welchen er noch ziehen kann, zeigt ihm doch das Schachbrett.
3. Stichwort "Geheimsprache" ;). Ich bin mir eigentlich absolut sicher, dass ein schlichtes "move(Field from, Field to)" vollkommen ausreicht, oder haben meine Mitschüler damals in ihrer "Geheimsprache" etwa genau darüber diskutiert? "isAllowed()" und "setPosition()" können also gut zusammengefasst werden, am besten mit einem Ergebnis (MOVE, STRIKE, CHECK, MATE oder INVALID).

Naja, wie gesagt... über Designfehler brauchen wir uns ja nicht mehr unterhalten, aber Gedanken kann man sich ja trotzdem noch witer machen.
 

Ähnliche Java Themen

Neue Themen


Oben