Entwerfen Sie ein Schachspiel nach objektorientierten Prinzipien

165173
SummerCode

Ich würde gerne wissen, ob mein Ansatz richtig ist und wie er verbessert werden könnte. Gibt es auch eine Möglichkeit, die Beziehung zwischen dem Pieceund dem zu Boardbeseitigen? Momentan speichere ich die Position des Stücks sowohl im Stück als auch auf dem Brett. Gibt es eine Möglichkeit, das zu ändern?

Ich habe überlegt Game, eine Instanz von Boardund die beiden zu enthalten Players(eine Schwarze, eine Weiße). Die Stücke enthalten eine Verbindung zum Board, denn um festzustellen, ob sie gültig sind, müssen wir die Beziehung zu anderen Stücken kennen.

Könnte ich Designmuster dafür verwenden? Soll ich Schnittstellen anstelle der Superklasse verwenden?

Game.java

public class Game {
    private Board board = new Board();
    private Player white;
    private Player black;
    public Game() {
        super();
    }

    public void setColorWhite(Player player) {
        this.white = player;
    }

    public void setColorBlack(Player player) {
        this.black = player;
    }

    public Board getBoard() {
        return board;
    }

    public void setBoard(Board board) {
        this.board = board;
    }

    public Player getWhite() {
        return white;
    }

    public void setWhite(Player white) {
        this.white = white;
    }

    public Player getBlack() {
        return black;
    }

    public void setBlack(Player black) {
        this.black = black;
    }

    public boolean initializeBoardGivenPlayers() {
        if(this.black == null || this.white == null)
            return false;
        this.board = new Board();
        for(int i=0; i<black.getPieces().size(); i++){
            board.getSpot(black.getPieces().get(i).getX(), black.getPieces().get(i).getY()).occupySpot(black.getPieces().get(i));
        }
        return true;
    }

}

Spieler.java

public class Player {
    public final int PAWNS = 8;
    public final int BISHOPS = 2;
    public final int ROOKS = 2;
    public boolean white;

    private List<Piece> pieces = new ArrayList<>();

    public Player(boolean white) {
        super();
        this.white = white;
    }

    public List<Piece> getPieces() {
        return pieces;
    }


    public void initializePieces(){
        if(this.white == true){
            for(int i=0; i<PAWNS; i++){ // draw pawns
                pieces.add(new Pawn(true,i,2));
            }
            pieces.add(new Rook(true, 0, 0));
            pieces.add(new Rook(true, 7, 0));
            pieces.add(new Bishop(true, 2, 0));
            pieces.add(new Bishop(true, 5, 0));
            pieces.add(new Knight(true, 1, 0));
            pieces.add(new Knight(true, 6, 0));
            pieces.add(new Queen(true, 3, 0));
            pieces.add(new King(true, 4, 0));
        }
        else{
            for(int i=0; i<PAWNS; i++){ // draw pawns
                pieces.add(new Pawn(true,i,6));
            }
            pieces.add(new Rook(true, 0, 7));
            pieces.add(new Rook(true, 7, 7));
            pieces.add(new Bishop(true, 2, 7));
            pieces.add(new Bishop(true, 5, 7));
            pieces.add(new Knight(true, 1, 7));
            pieces.add(new Knight(true, 6, 7));
            pieces.add(new Queen(true, 3, 7));
            pieces.add(new King(true, 4, 7));
        }

    }
}

Board.java

public class Board {
    private Spot[][] spots = new Spot[8][8];

    public Board() {
        super();
        for(int i=0; i<spots.length; i++){
            for(int j=0; j<spots.length; j++){
                this.spots[i][j] = new Spot(i, j);
            }
        }
    }

    public Spot getSpot(int x, int y) {
        return spots[x][y];
    }

}

Spot.java

public class Spot {
    int x;
    int y;
    Piece piece;

    public Spot(int x, int y) {
        super();
        this.x = x;
        this.y = y;
        piece = null;
    }

    public void occupySpot(Piece piece){
        //if piece already here, delete it, i. e. set it dead
        if(this.piece != null)
            this.piece.setAvailable(false);
        //place piece here
        this.piece = piece;
    }

    public boolean isOccupied() {
        if(piece != null)
            return true;
        return false;
    }

    public Piece releaseSpot() {
        Piece releasedPiece = this.piece;
        this.piece = null;
        return releasedPiece;
    }

}

Piece.java

public class Piece {
    private boolean available;
    private int x;
    private int y;

    public Piece(boolean available, int x, int y) {
        super();
        this.available = available;
        this.x = x;
        this.y = y;
    }


    public boolean isAvailable() {
        return available;
    }
    public void setAvailable(boolean available) {
        this.available = available;
    }
    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
    public int getY() {
        return y;
    }
    public void setY(int y) {
        this.y = y;
    }

    public boolean isValid(Board board, int fromX, int fromY, int toX, int toY){
        if(toX == fromX && toY == fromY)
            return false; //cannot move nothing
        if(toX < 0 || toX > 7 || fromX < 0 || fromX > 7 || toY < 0 || toY > 7 || fromY <0 || fromY > 7)
            return false;
        return true;
    }

}

King.java

public class King extends Piece{

    public King(boolean available, int x, int y) {
        super(available, x, y);
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
        if(super.isValid(board, fromX, fromY, toX, toY) == false)
            return false;
        if(Math.sqrt(Math.pow(Math.abs((toX - fromX)),2)) + Math.pow(Math.abs((toY - fromY)), 2) != Math.sqrt(2)){
            return false;
        }
        return false;
    }

}

Knight.java

public class Knight extends Piece{

    public Knight(boolean available, int x, int y) {
        super(available, x, y);
    }

    @Override
    public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
        if(super.isValid(board, fromX, fromY, toX, toY) == false)
            return false;

        if(toX != fromX - 1 && toX != fromX + 1 && toX != fromX + 2 && toX != fromX - 2)
            return false;
        if(toY != fromY - 2 && toY != fromY + 2 && toY != fromY - 1 && toY != fromY + 1)
            return false;

        return true;
    }

}

Bishop.java

public class Bishop extends Piece{

    public Bishop(boolean available, int x, int y) {
        super(available, x, y);
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
        if(super.isValid(board, fromX, fromY, toX, toY) == false)
            return false;

        if(toX - fromX == toY - fromY)
            return true;

        return false;
    }

}

Rook.java

public class Rook extends Piece{

    public Rook(boolean available, int x, int y) {
        super(available, x, y);
        // TODO Auto-generated constructor stub
    }


    @Override
    public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
        if(super.isValid(board, fromX, fromY, toX, toY) == false)
            return false;
        if(toX == fromX)
            return true;
        if(toY == fromY)
            return true;
        return false;
    }
}

Queen.java

public class Queen extends Piece{

    public Queen(boolean available, int x, int y) {
        super(available, x, y);
    }

    @Override
    public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
        if(super.isValid(board, fromX, fromY, toX, toY) == false)
            return false;
        //diagonal
        if(toX - fromX == toY - fromY)
            return true;
        if(toX == fromX)
            return true;
        if(toY == fromY)
            return true;

        return false;
    }

}
Antworten
59
Wo ist die Pawn.java-Datei? Das habe ich nicht gefunden. vor 4 Jahren 0
@Nitin Kumar Ich habe es in der Tat vergessen, es anscheinend aufzuschreiben, aber ich habe das Projekt wegen Hardware-Problemen nicht mehr ... SummerCode vor 4 Jahren 1
Bischof bewegt sich nur in diagonaler Form, wenn er an der weißen Stelle ist, bedeutet dies, dass sich seine Bewegungen nur in Weiß bewegen, diagonal ansonsten in Schwarz vor 4 Jahren 1

6 Antworten auf die Frage

28
Ramanathan Ganesan

Meine Kommentare beziehen sich auf das Design des Spiels. Ich sehe, dass die Verantwortlichkeiten von Entitäten an vielen Stellen vermischt sind

  • Der Spieler sollte keine Teile initialisieren. Wir können die Verantwortung an Bord verschieben. Board and pieces muss nicht über Spieler Bescheid wissen.
  • Stücke sollten nicht mit Bewegung umgehen. Stücke können eine Liste möglicher Züge zum Erreichen des Zielpfads bereitstellen, aber das Board sollte einen gültigen Pfad auswählen.
  • Das Board sollte die "Check Mate" -Zustellung prüfen.
  • Das Spiel sollte die Bewegungshistorie der Spieler und die Farbauswahl der Teile verfolgen.
  • Spielerklasse sollte nur Spielerdetails haben.

Ein grobes Klassendiagramm ist unten beigefügt

24
Kyle Hale

Schauen wir uns an, was ein vollständiger "Umzug" im Schachspiel bedeutet, ohne eine gründliche Codeüberprüfung anzubieten (da ich nicht viel Java-Kenntnisse besitzt):

  • Der Spieler wählt ein Stück, um sich zu bewegen.
  • Piece macht legale Schritte nach seinen eigenen Umzugsregeln.
  • Neben rein bewegungsbasierten Regeln gibt es auch eine Einfanglogik. Ein Läufer kann sich also nicht von a1-h8 bewegen, wenn auf c3 ein Teil sitzt.
  • Wenn der Spieler zuvor überprüft wurde und der Zug den Scheck nicht entfernt, muss er rückgängig gemacht werden.
  • Wenn die Bewegung die Prüfung freigibt, muss sie rückgängig gemacht werden.
  • Wenn der Spieler eine Figur erobert, entfernen Sie die Figur (einschließlich en passant!)
  • Wenn das Stück ein Bauer ist, der den hinteren Rang erreicht, fördern Sie es.
  • Wenn die Bewegung eine Rochade ist, stellen Sie die neue Position des Turms entsprechend ein. Aber ein König und ein Turm können nur dann ein Schloss bilden, wenn sie sich nicht bewegt haben. Daher müssen Sie den Überblick behalten. Und wenn der König durch einen Scheck zur Burg zieht, ist dies ebenfalls nicht zulässig.
  • Wenn die Bewegung zu einem Patt oder Schachmatt führt, ist das Spiel vorbei.

Es kann sogar mehr sein (?). Dies ist ein komplizierter Schritt, mehr als nur Leerzeichen zu zählen und anschließend zu belegen.

Meine allgemeine Intuition wäre also einfach zu nennen:

Game.move(currentSpot, NewSpot);

Und die Verschiebungsmethode würde den gesamten Code enthalten, um die obigen Schritte zu überprüfen:

  • Überprüfen Sie Piece.isValidMove(currentSpot, newSpot);- wahrscheinlich benötigen Sie hier eine Rochade, da der König mehr als 1 Feld bewegt und der Turm den König springt.
  • Check Player.isChecked()(was nur Zucker ist Player.Pieces["King"].CanBeCaptured()- hier macht mehr Logik Spaß!)
  • Überprüfen Sie, ob newSpotein Stück enthält und wenn ja, newSpot.Piece.Remove();
  • Baue eine Logik zum Aufrufen Piece.CheckEnPassant()(Stück ist Bauer, erster Zug, 2 Schritte, vorbei an einem gegnerischen Bauern, der sich beim vorherigen Zug in Eroberungsposition befand.
  • Piece.CheckPromote() (Stück ist Bauer, Zug endet im hinteren Rang des Gegners)
  • Überprüfen Sie, ob Game.isOver(), die überprüft, Game.isStaleMate()und Game.isCheckMate().

Ihre Board-Klasse ist sehr anämisch. Sie verwenden sie nur als Proxy-Objekt für das Array von Spots. Sie können auch Board einfach als eine Reihe von Spots im Spiel erstellen. In beiden Fällen können Sie es bereits aus Ihrer gesamten Stücklogik entfernen, da Ihre gesamte Logik von den Xs und Ys abhängt, die Sie übergeben.

AKTUALISIEREN

Ich würde alle Ihre Positionseigenschaften aus dem Stück entfernen. Sie verwenden es nur als Proxy, um herauszufinden, welchen Platz das Stück während der Initialisierung einnimmt. Entfernen Sie stattdessen Player.initializePieces()das Board und initialisieren Sie es einfach mit den Stücken an der richtigen Stelle (Board.Spot.Piece = King usw.), und lassen Sie die Spieler dann eine Farbe auswählen.

Weiterer Zug: Einen Bauern en passent nehmen: Wenn ein Bauer im ersten Zug zwei Positionen bewegt, kann er von einem gegnerischen Bauern im nächsten Zug so genommen werden, als hätte er nur eine Position bewegt. Ein Unentschieden anbieten oder aufgeben: Der Spieler, dessen Zug es ist, kann ein Unentschieden anbieten und der andere Spieler kann es annehmen oder ablehnen. Und der Spieler, dessen Zug es ist, kann das Spiel aufgeben. Nach 50 Zügen, bei denen kein Spielstein gezogen oder bewegt wurde, kann der Spieler ein Unentschieden verlangen. Gleich, wenn die identische Position zum dritten Mal eingegeben wird. gnasher729 vor 5 Jahren 4
@Kyle Hale Ich glaube, zur Validierung von "isValidMove" sollte keine Verantwortung für das Stück, aber es sollte die Verantwortung des Validators sein. Ist es nicht M Sach vor 4 Jahren 2
@Mach Ja, wahrscheinlich hast du recht, ich würde das in die Game-Klasse verschieben und einfach die Quell- und Zielquadrate als Parameter hinzufügen. Kyle Hale vor 4 Jahren 0
Dies ist eigentlich ein hervorragendes, nicht triviales Lernbeispiel für das OO-Design und zur Veranschaulichung von Kompromissen, Einkapselung und Delegierung. Es hängt auch davon ab, was Ihr Ziel ist. Wenn also beide Spieler ein Mensch sind, benötigen Sie nur einfache Methoden, um ihre Züge zu überprüfen, und die Einfachheit sollte die Effizienz übertreffen. Wenn es sich jedoch um eine KI handelt, benötigen Sie Methoden, um Kandidatenbewegungen effizient zu generieren und zu validieren ein deep-n-Baum), bewerten sie und beschneiden sie. smci vor 3 Jahren 0
19
Heslacher

Einige schnelle Aufnahmen

  • Ein ifwie geschriebener Zustand if (booleanVariable==true)kann zu vereinfacht werdenif (booleanVariable)
  • Sie sollten keine öffentlichen Variablen wie haben public boolean white;
  • kein Konstruktor Game, Board, Playerund Piecerufen soll, super()weil sie offensichtlich nicht / Verlängerung jede Klasse erben.

Einige Design-Quickshots

  • Ein Schachspiel benötigt ein Brett, 2 Spieler und 32 Stück.
  • Die Stücke sind Teil des Boards
  • Der Spieler bewegt das Stück nach Regeln
  • Die Regeln sind an die Art des Stücks und die Position der Teile auf der Tafel gebunden
  • Diese Regeln müssen von einem Objekt ausgewertet werden, entweder dem Gameoder einem RuleEvaluator.
10
fluffy

Anstatt einen Boolean-Befehl zu verwenden, wenn etwas markiert wird (und damit impliziert, dass es nicht die andere Sache ist), betrachten Sie ein enum; In diesem Fall kauft Sie dies in Bezug auf Flexibilität nicht (da es nicht so aussieht, als würde es jemals Rot, Lila oder was auch immer geben), aber es wird Ihren Code viel klarer machen.

Im Spiel sollten Sie nicht Schwarz und Weiß separat einstellen, sondern Sie sollten eine einzige haben setPlayers(Player black, Player white), oder besser noch, dass das Brett eine Fabrik ist, die die Schwarzweißs Playerselbst liefert - wenn es überhaupt einen Grund gibt, ein Player-Objekt in der ersten zu haben Platz.

5
smci

Ein paar Ergänzungen für alle anderen:

  • beide Playerund Boardmüssen wissen, wo sich die Figuren befinden, sowohl ihre als auch ihre Gegner. Ich kann nicht ohne weiteres den besten Weg finden, um dies zu zerlegen, aber überlegen Sie, wie sie mit minimalem Duplizieren miteinander reden. Ich denke, es ist sinnvoller, es unterzulegen Board, dann Playereinen Verweis darauf zu speichern Boardund seine Methoden entweder zu erben oder zusammenzusetzen:
  • *.isValid()Überprüfen Sie nur von und zu den Koordinaten, sie prüfen derzeit nicht, ob es sich um Freundschafts- oder Feindstücke handelt oder ob der zu besetzte Ort besetzt ist. Ich denke, Sie sollten das in einer Funktion auf Vorstandsebene tun, es wird sehr hässlich, wenn Sie das für jedes Stück tun. Sie benötigen möglicherweise eine Hilfsfunktion (Iterator?) Für jedes Stück, um interveningSpots(fromX,fromY,toX,toY,pieceType)es zu generieren, damit Sie Board.validMove()testen können, ob sie besetzt sind.

  • Der Name Piece.isValid()ist verwirrend, es könnte entweder validMove()oder bedeuten validPosition(). Ich persönlich würde es umbenennen validMove(). (Wir brauchen auch eine, validPosition()wenn Sie jemals eine Beförderung durchführen, aber dies würde auf Vorstandsebene und nicht auf Stück oder Spieler erfolgen.)

  • King.isValid() scheint momentan immer false zurückzugeben - Bug?

  • Queen/Bishop/Rook.isValid()Erlaube derzeit null Nullbewegungen (toX==fromX && toY==fromY). Das hört sich vielleicht nach einem Pickpicking an, aber a) es kann Ihnen fälschlicherweise erlauben, dem Partner durch "nichts zu tun" auszuweichen.

  • ein Tipp für performanteren und kompakteren Code für King.isValid(): Sie müssen die sqrt nicht nehmen; Testen Sie einfach direkt, dass dist2 = (dx ^ 2 + dy ^ 2) entweder 1 oder 2 ist. Und Sie brauchen keine abs (dx), da das Quadrat einer negativen Zahl positiv ist. So:

    @Override
    public boolean isValid(...) {
        if(!super.isValid(...)) {
            return false;
    
        int dist2 = Math.pow((toX - fromX), 2) + Math.pow((toY - fromY), 2);
        return (dist2 == 1 || dist2 == 2);
    }
    
1
ksnortum

Hier einige Möglichkeiten, wie Sie Ihre Logik vereinfachen können:

  • Wie bereits erwähnt, schreiben Sie statt if (condition == true)schreiben if (condition).
  • Ebenso statt if (condition == false)schreiben if (!condition).
  • Wenn Sie versucht sind, zu schreiben:

    if (condition)
        return true;
    return false;
    

    ... stattdessen schreiben return condition;.

  • Ebenso anstelle von:

    if (condition)
        return false;
    return true;
    

    ... schreiben return (!condition);.

  • Wenn Sie versucht sind zu schreiben:

    if (condition1)
        return true;
    if (condition2)
        return true;
    return false;
    

    ... stattdessen überlegen return condition1 || condition2;.

Im Allgemeinen sollten Sie die Logik vereinfachen, wenn sie klarer wird.