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
This commit is contained in:
Kai S. K. Engelbart 2019-07-22 21:40:25 +02:00
parent 46b74a527d
commit a6fcaee70e
Signed by: kske
GPG Key ID: 8BEB13EC5DF7EF13
12 changed files with 186 additions and 83 deletions

View File

@ -7,7 +7,7 @@
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_212">
<attributes>
<attribute name="module" value="true"/>
</attributes>

View File

@ -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<Move> getPseudolegalMoves(Position pos) {
List<Move> moves = new ArrayList<>();

View File

@ -17,10 +17,11 @@ import dev.kske.chess.board.Piece.Type;
*/
public class Board implements Cloneable {
private Piece[][] boardArr;
private Map<Color, Position> kingPos;
private Color activeColor;
private Log log;
private Piece[][] boardArr;
private Map<Color, Position> kingPos;
private Map<Color, Map<Type, Boolean>> 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<Type, Boolean> 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; }
}

View File

@ -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<Move> getPseudolegalMoves(Position pos) {
List<Move> 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; }
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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<Move> getPseudolegalMoves(Position pos) {
List<Move> moves = new ArrayList<>();

View File

@ -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<Move> getPseudolegalMoves(Position pos) {
List<Move> moves = new ArrayList<>();

View File

@ -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.
*

View File

@ -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();

View File

@ -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: <strong>Chess</strong><br>
* File: <strong>LogFrame.java</strong><br>
* Created: <strong>17.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong>
*/
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; }
}

View File

@ -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());
}
}