From a6fcaee70e98ee7bed332ceb33c68932d6d8854e Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 22 Jul 2019 21:40:25 +0200 Subject: [PATCH] Fixed castling, added castling export to FEN + isFreePath implementation in Piece - Removed isFreePath from Bishop, Rook, Queen and King + canCastleKingside and canCastleQueenside methods in King + Castling rights record in Board + FEN export + equals method in Position + UCI 'position startpos' command - Switched to Java 8 compliance for compatibility reasons --- .classpath | 2 +- src/dev/kske/chess/board/Bishop.java | 8 --- src/dev/kske/chess/board/Board.java | 78 ++++++++++++++++++++----- src/dev/kske/chess/board/King.java | 57 +++++++++--------- src/dev/kske/chess/board/Piece.java | 10 +++- src/dev/kske/chess/board/Position.java | 20 +++++++ src/dev/kske/chess/board/Queen.java | 16 ----- src/dev/kske/chess/board/Rook.java | 12 ---- src/dev/kske/chess/uci/UCIHandle.java | 7 +++ src/dev/kske/chess/uci/UCIReceiver.java | 2 +- src/dev/kske/chess/ui/LogFrame.java | 54 +++++++++++++++++ test/dev/kske/chess/test/BoardTest.java | 3 + 12 files changed, 186 insertions(+), 83 deletions(-) create mode 100644 src/dev/kske/chess/ui/LogFrame.java diff --git a/.classpath b/.classpath index 83f2c4a..d0a08f5 100644 --- a/.classpath +++ b/.classpath @@ -7,7 +7,7 @@ - + diff --git a/src/dev/kske/chess/board/Bishop.java b/src/dev/kske/chess/board/Bishop.java index c4dc97d..8f2cb9d 100644 --- a/src/dev/kske/chess/board/Bishop.java +++ b/src/dev/kske/chess/board/Bishop.java @@ -20,14 +20,6 @@ public class Bishop extends Piece { return move.isDiagonal() && isFreePath(move); } - @Override - protected boolean isFreePath(Move move) { - for (int i = move.pos.x + move.xSign, j = move.pos.y - + move.ySign; i != move.dest.x; i += move.xSign, j += move.ySign) - if (board.getBoardArr()[i][j] != null) return false; - return checkDestination(move); - } - @Override protected List getPseudolegalMoves(Position pos) { List moves = new ArrayList<>(); diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 7404fc3..88cb298 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -17,10 +17,11 @@ import dev.kske.chess.board.Piece.Type; */ public class Board implements Cloneable { - private Piece[][] boardArr; - private Map kingPos; - private Color activeColor; - private Log log; + private Piece[][] boardArr; + private Map kingPos; + private Map> castlingRights; + private Color activeColor; + private Log log; private int fullmoveCounter, halfmoveClock; @@ -62,9 +63,10 @@ public class Board implements Cloneable { } public Board() { - boardArr = new Piece[8][8]; - kingPos = new HashMap<>(); - log = new Log(); + boardArr = new Piece[8][8]; + kingPos = new HashMap<>(); + castlingRights = new HashMap<>(); + log = new Log(); initializeDefaultPositions(); } @@ -140,8 +142,13 @@ public class Board implements Cloneable { // Increment move counter getDest(move).incMoveCounter(); - // Update the king's position if the moved piece is the king - if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest); + // Update the king's position if the moved piece is the king and castling + // availability + if (piece.getType() == Type.KING) { + kingPos.put(piece.getColor(), move.dest); + castlingRights.get(piece.getColor()).put(Type.KING, false); + castlingRights.get(piece.getColor()).put(Type.QUEEN, false); + } // Update log log.add(move, capturePiece); @@ -154,6 +161,8 @@ public class Board implements Cloneable { // Increment halfmove clock ++halfmoveClock; + + updateCastlingRights(); } /** @@ -211,6 +220,30 @@ public class Board implements Cloneable { // Decrement halfmove clock --halfmoveClock; + + updateCastlingRights(); + } + + private void updateCastlingRights() { + // White + if (new Position(4, 7).equals(kingPos.get(Color.WHITE))) { + final King king = (King) get(kingPos.get(Color.WHITE)); + castlingRights.get(Color.WHITE).put(Type.KING, king.canCastleKingside()); + castlingRights.get(Color.WHITE).put(Type.QUEEN, king.canCastleQueenside()); + } else { + castlingRights.get(Color.WHITE).put(Type.KING, false); + castlingRights.get(Color.WHITE).put(Type.QUEEN, false); + } + + // Black + if (new Position(4, 0).equals(kingPos.get(Color.BLACK))) { + final King king = (King) get(kingPos.get(Color.BLACK)); + castlingRights.get(Color.BLACK).put(Type.KING, king.canCastleKingside()); + castlingRights.get(Color.BLACK).put(Type.QUEEN, king.canCastleQueenside()); + } else { + castlingRights.get(Color.BLACK).put(Type.KING, false); + castlingRights.get(Color.BLACK).put(Type.QUEEN, false); + } } /** @@ -354,9 +387,19 @@ public class Board implements Cloneable { for (int j = 2; j < 6; j++) boardArr[i][j] = null; + // Initialize castling rights + castlingRights.clear(); + Map whiteCastling = new HashMap<>(), blackCastling = new HashMap<>(); + whiteCastling.put(Type.KING, true); + whiteCastling.put(Type.QUEEN, true); + blackCastling.put(Type.KING, true); + blackCastling.put(Type.QUEEN, true); + castlingRights.put(Color.WHITE, whiteCastling); + castlingRights.put(Color.BLACK, blackCastling); + activeColor = Color.WHITE; - - fullmoveCounter = 1; + + fullmoveCounter = 1; halfmoveClock = 0; } @@ -437,8 +480,14 @@ public class Board implements Cloneable { // Active color sb.append(" " + (activeColor == Color.WHITE ? 'w' : 'b')); - // TODO: castling rights - sb.append(" -"); + sb.append(' '); + StringBuilder castlingSb = new StringBuilder(); + if (castlingRights.get(Color.WHITE).get(Type.KING)) castlingSb.append('K'); + if (castlingRights.get(Color.WHITE).get(Type.QUEEN)) castlingSb.append('Q'); + if (castlingRights.get(Color.BLACK).get(Type.KING)) castlingSb.append('k'); + if (castlingRights.get(Color.BLACK).get(Type.QUEEN)) castlingSb.append('q'); + if (castlingSb.length() == 0) sb.append("-"); + sb.append(castlingSb); // TODO: en passant availability sb.append(" -"); @@ -481,5 +530,8 @@ public class Board implements Cloneable { */ public Piece[][] getBoardArr() { return boardArr; } + /** + * @return The active color for the next move + */ public Color getActiveColor() { return activeColor; } } diff --git a/src/dev/kske/chess/board/King.java b/src/dev/kske/chess/board/King.java index 7c0ad2e..78f672d 100644 --- a/src/dev/kske/chess/board/King.java +++ b/src/dev/kske/chess/board/King.java @@ -18,27 +18,40 @@ public class King extends Piece { @Override public boolean isValidMove(Move move) { // Castling - if (getMoveCounter() == 0 && move.xDist == 2 && move.yDist == 0) { - - // Kingside - if (board.getBoardArr()[7][move.pos.y] != null && board.getBoardArr()[7][move.pos.y].getType() == Type.ROOK - && isFreePath(new Move(new Position(5, move.pos.y), new Position(7, move.pos.y)))) { + if (move.xDist == 2 && move.yDist == 0) { + if (canCastleKingside()) { move.type = Move.Type.CASTLING; return true; } - - // Queenside - if (board.getBoardArr()[0][move.pos.y] != null && board.getBoardArr()[0][move.pos.y].getType() == Type.ROOK - && isFreePath(new Move(new Position(1, move.pos.y), new Position(4, move.pos.y)))) { + if (canCastleQueenside()) { move.type = Move.Type.CASTLING; return true; } - } return move.xDist <= 1 && move.yDist <= 1 && checkDestination(move); } + public boolean canCastleKingside() { + if (getMoveCounter() == 0) { + int y = getColor() == Color.WHITE ? 7 : 0; + Position rookPos = new Position(7, y); + Piece rook = board.get(rookPos); + return rook != null && rook.getType() == Type.ROOK && rook.getMoveCounter() == 0 + && isFreePath(new Move(new Position(4, y), new Position(6, y))); + } else return false; + } + + public boolean canCastleQueenside() { + if (getMoveCounter() == 0) { + int y = getColor() == Color.WHITE ? 7 : 0; + Position rookPos = new Position(0, y); + Piece rook = board.get(rookPos); + return rook != null && rook.getType() == Type.ROOK && rook.getMoveCounter() == 0 + && isFreePath(new Move(new Position(4, y), new Position(1, y))); + } else return false; + } + @Override protected List getPseudolegalMoves(Position pos) { List moves = new ArrayList<>(); @@ -50,31 +63,13 @@ public class King extends Piece { } // Castling - // TODO: Check attacked squares in between - // TODO: Castling out of check? - if (getMoveCounter() == 0) { - - // Kingside - if (board.getBoardArr()[7][pos.y] != null && board.getBoardArr()[7][pos.y].getType() == Type.ROOK - && isFreePath(new Move(new Position(5, pos.y), new Position(7, pos.y)))) - moves.add(new Move(pos, new Position(6, pos.y), Move.Type.CASTLING)); - - // Queenside - if (board.getBoardArr()[0][pos.y] != null && board.getBoardArr()[0][pos.y].getType() == Type.ROOK - && isFreePath(new Move(new Position(1, pos.y), new Position(4, pos.y)))) - moves.add(new Move(pos, new Position(2, pos.y), Move.Type.CASTLING)); - } + // TODO: Condition: cannot castle out of, through or into check + if (canCastleKingside()) moves.add(new Move(pos, new Position(6, pos.y), Move.Type.CASTLING)); + if (canCastleQueenside()) moves.add(new Move(pos, new Position(2, pos.y), Move.Type.CASTLING)); return moves; } - @Override - protected boolean isFreePath(Move move) { - for (int i = move.pos.x, j = move.pos.y; i != move.dest.x || j != move.dest.y; i += move.xSign, j += move.ySign) - if (board.getBoardArr()[i][j] != null) return false; - return true; - } - @Override public Type getType() { return Type.KING; } } diff --git a/src/dev/kske/chess/board/Piece.java b/src/dev/kske/chess/board/Piece.java index 3298a8d..0006e84 100644 --- a/src/dev/kske/chess/board/Piece.java +++ b/src/dev/kske/chess/board/Piece.java @@ -35,8 +35,16 @@ public abstract class Piece implements Cloneable { public abstract boolean isValidMove(Move move); + /** + * Checks, if the squares between the position and the destination of a move are + * free. + * + * @param move The move to check + */ protected boolean isFreePath(Move move) { - // Only check destination by default + for (int i = move.pos.x + move.xSign, j = move.pos.y + move.ySign; i != move.dest.x + || j != move.dest.y; i += move.xSign, j += move.ySign) + if (board.getBoardArr()[i][j] != null) return false; return checkDestination(move); } diff --git a/src/dev/kske/chess/board/Position.java b/src/dev/kske/chess/board/Position.java index a3dcadd..69bb03b 100644 --- a/src/dev/kske/chess/board/Position.java +++ b/src/dev/kske/chess/board/Position.java @@ -23,4 +23,24 @@ public class Position { public String toString() { return String.format("[%d, %d]", x, y); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + y; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Position other = (Position) obj; + if (x != other.x) return false; + if (y != other.y) return false; + return true; + } } diff --git a/src/dev/kske/chess/board/Queen.java b/src/dev/kske/chess/board/Queen.java index 29a2ba6..6fcc401 100644 --- a/src/dev/kske/chess/board/Queen.java +++ b/src/dev/kske/chess/board/Queen.java @@ -20,22 +20,6 @@ public class Queen extends Piece { return ((move.isHorizontal() || move.isVertical()) || move.isDiagonal()) && isFreePath(move); } - @Override - protected boolean isFreePath(Move move) { - if (move.isHorizontal()) { - for (int i = move.pos.x + move.xSign; i != move.dest.x; i += move.xSign) - if (board.getBoardArr()[i][move.pos.y] != null) return false; - } else if (move.isVertical()) { - for (int i = move.pos.y + move.ySign; i != move.dest.y; i += move.ySign) - if (board.getBoardArr()[move.pos.x][i] != null) return false; - } else { - for (int i = move.pos.x + move.xSign, j = move.pos.y - + move.ySign; i != move.dest.x; i += move.xSign, j += move.ySign) - if (board.getBoardArr()[i][j] != null) return false; - } - return checkDestination(move); - } - @Override protected List getPseudolegalMoves(Position pos) { List moves = new ArrayList<>(); diff --git a/src/dev/kske/chess/board/Rook.java b/src/dev/kske/chess/board/Rook.java index 158eefe..affc7b0 100644 --- a/src/dev/kske/chess/board/Rook.java +++ b/src/dev/kske/chess/board/Rook.java @@ -20,18 +20,6 @@ public class Rook extends Piece { return (move.isHorizontal() || move.isVertical()) && isFreePath(move); } - @Override - protected boolean isFreePath(Move move) { - if (move.isHorizontal()) { - for (int i = move.pos.x + move.xSign; i != move.dest.x; i += move.xSign) - if (board.getBoardArr()[i][move.pos.y] != null) return false; - } else { - for (int i = move.pos.y + move.ySign; i != move.dest.y; i += move.ySign) - if (board.getBoardArr()[move.pos.x][i] != null) return false; - } - return checkDestination(move); - } - @Override protected List getPseudolegalMoves(Position pos) { List moves = new ArrayList<>(); diff --git a/src/dev/kske/chess/uci/UCIHandle.java b/src/dev/kske/chess/uci/UCIHandle.java index 8c8be2a..cf13e63 100644 --- a/src/dev/kske/chess/uci/UCIHandle.java +++ b/src/dev/kske/chess/uci/UCIHandle.java @@ -94,6 +94,13 @@ public class UCIHandle { // TODO: position + /** + * Sets up the position in its initial state. + */ + public void startPosition() { + out.println("position startpos"); + } + /** * Sets up the position described in the FEN string. * diff --git a/src/dev/kske/chess/uci/UCIReceiver.java b/src/dev/kske/chess/uci/UCIReceiver.java index 5793356..fbfb686 100644 --- a/src/dev/kske/chess/uci/UCIReceiver.java +++ b/src/dev/kske/chess/uci/UCIReceiver.java @@ -32,7 +32,7 @@ public class UCIReceiver implements Runnable { String line; while (!Thread.currentThread().isInterrupted()) try { - if ((line = in.readLine()) != null && !line.isBlank()) parse(line); + if ((line = in.readLine()) != null && !line.isEmpty()) parse(line); } catch (IndexOutOfBoundsException ex) { System.err.println("Too few arguments were provided!"); ex.printStackTrace(); diff --git a/src/dev/kske/chess/ui/LogFrame.java b/src/dev/kske/chess/ui/LogFrame.java new file mode 100644 index 0000000..cabcabc --- /dev/null +++ b/src/dev/kske/chess/ui/LogFrame.java @@ -0,0 +1,54 @@ +package dev.kske.chess.ui; + +import java.awt.BorderLayout; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.border.EmptyBorder; +import javax.swing.table.DefaultTableModel; + +import dev.kske.chess.board.Log; + +/** + * Project: Chess
+ * File: LogFrame.java
+ * Created: 17.07.2019
+ * Author: Kai S. K. Engelbart + */ +public class LogFrame extends JFrame { + + private static final long serialVersionUID = 1932671698254197119L; + + private JPanel mcontentPane; + private JTable mtable; + + private Log log; + + /** + * Create the frame. + */ + public LogFrame() { + setTitle("Move History"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(100, 100, 450, 300); + mcontentPane = new JPanel(); + mcontentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); + mcontentPane.setLayout(new BorderLayout(0, 0)); + setContentPane(mcontentPane); + + mtable = new JTable(); + mtable.setModel(new DefaultTableModel(new Object[][] {}, new String[] { "Black", "New column" })); + mtable.setEnabled(false); + mcontentPane.add(mtable, BorderLayout.CENTER); + } + + public void update() { + + } + + public Log getLog() { return log; } + + public void setLog(Log log) { this.log = log; } + +} diff --git a/test/dev/kske/chess/test/BoardTest.java b/test/dev/kske/chess/test/BoardTest.java index 415d3a4..44212ee 100644 --- a/test/dev/kske/chess/test/BoardTest.java +++ b/test/dev/kske/chess/test/BoardTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import dev.kske.chess.board.Board; +import dev.kske.chess.board.Move; import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Queen; @@ -38,6 +39,8 @@ class BoardTest { assertNotSame(clone.getBoardArr(), board.getBoardArr()); clone.getBoardArr()[0][0] = new Queen(Color.BLACK, clone); + clone.move(new Move(1, 1, 1, 2)); assertNotEquals(clone.getBoardArr()[0][0], board.getBoardArr()[0][0]); + assertNotEquals(clone.getActiveColor(), board.getActiveColor()); } }