Compare commits
68 Commits
v0.3-alpha
...
v0.5-alpha
Author | SHA1 | Date | |
---|---|---|---|
95773c49ba | |||
3bff5aa56e | |||
0487b47691 | |||
39b7ce20b1 | |||
91512b7774 | |||
44f91591b4 | |||
6af213ed4f | |||
fbf66e6ec1 | |||
6360a8a4ba | |||
78bac2e913 | |||
44f6a4b9b8 | |||
6bbef7deee | |||
a98ca5e350 | |||
15cdf00eb1 | |||
37c5f2bd23 | |||
02c5e33f10 | |||
28939f0471 | |||
b5c30d59af | |||
2e672841cd | |||
1e44234f7e | |||
792f93fc3f | |||
27f37a8cf0 | |||
4d943c1a19 | |||
1d78b8f071 | |||
a41a2819da | |||
40d80fdc12 | |||
cbf6aa2013 | |||
5dbd38d1c0 | |||
4857b48e4e | |||
c438dd00cb | |||
7e8f75a008 | |||
db8fe1c4c0 | |||
acb0e63c82 | |||
2f1ae6e9c8 | |||
1f2cedd455 | |||
1d19f17c56 | |||
86cf2afc8f | |||
8b9793611a | |||
446f895ae1 | |||
9839d5a23e | |||
713c95338e | |||
84b3e1503f | |||
cc0440233b | |||
286ea93ee3 | |||
994cb84729 | |||
54e4a0e2e4 | |||
1ecafa5485 | |||
c987bfcebb | |||
3941a75c91 | |||
249480724a | |||
216877b76b | |||
19712a2bb7 | |||
08ac0ac114 | |||
de9cd05602 | |||
3b73cd1efb | |||
964de02e24 | |||
76fa3859ef | |||
c1a8589a04 | |||
358654b1ed | |||
8e2af63c35 | |||
642a0bf4d1 | |||
3ea48ef21b | |||
d121e85897 | |||
14c7167ce4 | |||
90c100e0e1 | |||
e7af9f40c2 | |||
3d8877ddbd | |||
83c6e48f03 |
@ -7,11 +7,12 @@
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
|
||||
<classpathentry kind="src" output="bin_test" path="test_res">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
@ -7,7 +7,9 @@ import java.util.List;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Bishop.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Bishop extends Piece {
|
||||
|
||||
@ -63,5 +65,5 @@ public class Bishop extends Piece {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() { return Type.BISHOP; }
|
||||
public int getValue() { return 30; }
|
||||
}
|
||||
|
@ -1,70 +1,55 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import dev.kske.chess.board.Log.LoggedMove;
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.board.Piece.Type;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Board.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Board implements Cloneable {
|
||||
public class Board {
|
||||
|
||||
private Piece[][] boardArr;
|
||||
private Map<Color, Position> kingPos;
|
||||
private Map<Color, Map<Type, Boolean>> castlingRights;
|
||||
private Log log;
|
||||
private Piece[][] boardArr = new Piece[8][8];
|
||||
private Map<Color, Position> kingPos = new HashMap<>();
|
||||
private Log log = new Log();
|
||||
|
||||
private static final Map<Type, int[][]> positionScores;
|
||||
/**
|
||||
* Initializes the board with the default chess starting position.
|
||||
*/
|
||||
public Board() { initDefaultPositions(); }
|
||||
|
||||
static {
|
||||
positionScores = new HashMap<>();
|
||||
positionScores.put(Type.KING,
|
||||
new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 },
|
||||
new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
|
||||
new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -2, -3, -3, -2, -2, -2, -2, -1 },
|
||||
new int[] { -1, -2, -2, -2, -2, -2, -2, -1 }, new int[] { 2, 2, 0, 0, 0, 0, 2, 2 },
|
||||
new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
|
||||
positionScores.put(Type.QUEEN,
|
||||
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, -2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
|
||||
new int[] { 0, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 0, -1 },
|
||||
new int[] { -1, 0, 1, 0, 0, 0, 0, -1 }, new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
|
||||
positionScores.put(Type.ROOK,
|
||||
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
|
||||
positionScores.put(Type.KNIGHT,
|
||||
new int[][] { new int[] { -5, -4, -3, -3, -3, -3, -4, -5 }, new int[] { -4, -2, 0, 0, 0, 0, -2, -4 },
|
||||
new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 },
|
||||
new int[] { -3, 0, 2, 2, 2, 2, 0, -1 }, new int[] { -3, 1, 1, 2, 2, 1, 1, -3 },
|
||||
new int[] { -4, -2, 0, 1, 1, 0, -2, -4 }, new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
|
||||
positionScores.put(Type.BISHOP,
|
||||
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, 2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 },
|
||||
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 },
|
||||
new int[] { -1, 1, 0, 0, 0, 0, 1, -1 }, new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
|
||||
positionScores.put(Type.PAWN,
|
||||
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 },
|
||||
new int[] { 1, 1, 2, 3, 3, 2, 1, 1 }, new int[] { 0, 0, 1, 3, 3, 1, 0, 0 },
|
||||
new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
|
||||
new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } });
|
||||
/**
|
||||
* Creates a copy of another {@link Board} instance.<br>
|
||||
* The created object is a deep copy, but does not contain any move history
|
||||
* apart from the current {@link MoveNode}.
|
||||
*
|
||||
* @param other The {@link Board} instance to copy
|
||||
* @param copyVariations TODO
|
||||
*/
|
||||
public Board(Board other, boolean copyVariations) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (other.boardArr[i][j] == null) continue;
|
||||
boardArr[i][j] = (Piece) other.boardArr[i][j].clone();
|
||||
boardArr[i][j].board = this;
|
||||
}
|
||||
|
||||
public Board() {
|
||||
boardArr = new Piece[8][8];
|
||||
kingPos = new HashMap<>();
|
||||
castlingRights = new HashMap<>();
|
||||
log = new Log();
|
||||
initializeDefaultPositions();
|
||||
kingPos.putAll(other.kingPos);
|
||||
log = new Log(other.log, copyVariations);
|
||||
|
||||
// Synchronize the current move node with the board
|
||||
while (log.getLast().hasVariations())
|
||||
log.selectNextNode(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,9 +62,6 @@ public class Board implements Cloneable {
|
||||
Piece piece = getPos(move);
|
||||
if (piece == null || !piece.isValidMove(move)) return false;
|
||||
else {
|
||||
// Set type after validation
|
||||
if (move.type == Move.Type.UNKNOWN) move.type = Move.Type.NORMAL;
|
||||
|
||||
// Move piece
|
||||
move(move);
|
||||
|
||||
@ -97,134 +79,45 @@ public class Board implements Cloneable {
|
||||
* Moves a piece across the board without checking if the move is legal.
|
||||
*
|
||||
* @param move The move to execute
|
||||
* @return The captures piece, or null if the move's destination was empty
|
||||
*/
|
||||
public void move(Move move) {
|
||||
Piece piece = getPos(move);
|
||||
Piece capturePiece = getDest(move);
|
||||
|
||||
switch (move.type) {
|
||||
case PAWN_PROMOTION:
|
||||
setPos(move, null);
|
||||
// TODO: Select promotion
|
||||
setDest(move, new Queen(piece.getColor(), this));
|
||||
break;
|
||||
case EN_PASSANT:
|
||||
setDest(move, piece);
|
||||
setPos(move, null);
|
||||
boardArr[move.dest.x][move.dest.y - move.ySign] = null;
|
||||
break;
|
||||
case CASTLING:
|
||||
// Move the king
|
||||
setDest(move, piece);
|
||||
setPos(move, null);
|
||||
// Execute the move
|
||||
move.execute(this);
|
||||
|
||||
// Move the rook
|
||||
Move rookMove = move.dest.x == 6 ? new Move(7, move.pos.y, 5, move.pos.y) // Kingside
|
||||
: new Move(0, move.pos.y, 3, move.pos.y); // Queenside
|
||||
|
||||
// Move the rook
|
||||
setDest(rookMove, getPos(rookMove));
|
||||
setPos(rookMove, null);
|
||||
|
||||
getDest(rookMove).incMoveCounter();
|
||||
break;
|
||||
case UNKNOWN:
|
||||
System.err.printf("Move of unknown type %s found!%n", move);
|
||||
case NORMAL:
|
||||
setDest(move, piece);
|
||||
setPos(move, null);
|
||||
break;
|
||||
default:
|
||||
System.err.printf("Move %s of unimplemented type found!%n", move);
|
||||
}
|
||||
|
||||
// Increment move counter
|
||||
getDest(move).incMoveCounter();
|
||||
|
||||
// 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);
|
||||
// Update the king's position if the moved piece is the king
|
||||
if (piece instanceof King) kingPos.put(piece.getColor(), move.getDest());
|
||||
|
||||
// Update log
|
||||
log.add(move, capturePiece, piece.getType() == Type.PAWN);
|
||||
|
||||
updateCastlingRights();
|
||||
log.add(move, piece, capturePiece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the last move.
|
||||
* Moves a piece across the board without checking if the move is legal.
|
||||
*
|
||||
* @param sanMove The move to execute in SAN (Standard Algebraic Notation)
|
||||
*/
|
||||
public void revert() {
|
||||
LoggedMove loggedMove = log.getLast();
|
||||
Move move = loggedMove.move;
|
||||
Piece capturedPiece = loggedMove.capturedPiece;
|
||||
|
||||
switch (move.type) {
|
||||
case PAWN_PROMOTION:
|
||||
setPos(move, new Pawn(getDest(move).getColor(), this));
|
||||
setDest(move, capturedPiece);
|
||||
break;
|
||||
case EN_PASSANT:
|
||||
setPos(move, getDest(move));
|
||||
setDest(move, null);
|
||||
boardArr[move.dest.x][move.dest.y - move.ySign] = new Pawn(getPos(move).getColor().opposite(), this);
|
||||
break;
|
||||
case CASTLING:
|
||||
// Move the king
|
||||
setPos(move, getDest(move));
|
||||
setDest(move, null);
|
||||
|
||||
// Move the rook
|
||||
Move rookMove = move.dest.x == 6 ? new Move(5, move.pos.y, 7, move.pos.y) // Kingside
|
||||
: new Move(3, move.pos.y, 0, move.pos.y); // Queenside
|
||||
setDest(rookMove, getPos(rookMove));
|
||||
setPos(rookMove, null);
|
||||
|
||||
getDest(rookMove).decMoveCounter();
|
||||
break;
|
||||
case UNKNOWN:
|
||||
System.err.printf("Move of unknown type %s found!%n", move);
|
||||
case NORMAL:
|
||||
setPos(move, getDest(move));
|
||||
setDest(move, capturedPiece);
|
||||
break;
|
||||
default:
|
||||
System.err.printf("Move %s of unimplemented type found!%n", move);
|
||||
public void move(String sanMove) {
|
||||
move(Move.fromSAN(sanMove, this));
|
||||
}
|
||||
|
||||
// Decrement move counter
|
||||
getPos(move).decMoveCounter();
|
||||
/**
|
||||
* Reverts the last move and removes it from the log.
|
||||
*/
|
||||
public void revert() {
|
||||
MoveNode moveNode = log.getLast();
|
||||
Move move = moveNode.move;
|
||||
|
||||
// Revert the move
|
||||
move.revert(this, moveNode.capturedPiece);
|
||||
|
||||
// Update the king's position if the moved piece is the king
|
||||
if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos);
|
||||
if (getPos(move) instanceof King) kingPos.put(getPos(move).getColor(), move.getPos());
|
||||
|
||||
// Update log
|
||||
log.removeLast();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,14 +130,11 @@ public class Board implements Cloneable {
|
||||
List<Move> moves = new ArrayList<>();
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (boardArr[i][j] != null && boardArr[i][j].getColor() == color)
|
||||
moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
|
||||
if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
|
||||
return moves;
|
||||
}
|
||||
|
||||
public List<Move> getMoves(Position pos) {
|
||||
return get(pos).getMoves(pos);
|
||||
}
|
||||
public List<Move> getMoves(Position pos) { return get(pos).getMoves(pos); }
|
||||
|
||||
/**
|
||||
* Checks, if the king is in check.
|
||||
@ -252,13 +142,20 @@ public class Board implements Cloneable {
|
||||
* @param color The color of the king to check
|
||||
* @return {@code true}, if the king is in check
|
||||
*/
|
||||
public boolean checkCheck(Color color) {
|
||||
public boolean checkCheck(Color color) { return isAttacked(kingPos.get(color), color.opposite()); }
|
||||
|
||||
/**
|
||||
* Checks, if a field can be attacked by pieces of a certain color.
|
||||
*
|
||||
* @param dest the field to check
|
||||
* @param color the color of a potential attacker piece
|
||||
* @return {@code true} if a move with the destination {@code dest}
|
||||
*/
|
||||
public boolean isAttacked(Position dest, Color color) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++) {
|
||||
Position pos = new Position(i, j);
|
||||
if (get(pos) != null && get(pos).getColor() != color
|
||||
&& get(pos).isValidMove(new Move(pos, kingPos.get(color))))
|
||||
return true;
|
||||
if (get(pos) != null && get(pos).getColor() == color && get(pos).isValidMove(new Move(pos, dest))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -284,50 +181,15 @@ public class Board implements Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
public GameState getGameEventType(Color color) {
|
||||
return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
|
||||
: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE
|
||||
: GameState.NORMAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluated the board.
|
||||
*
|
||||
* @param color The color to evaluate for
|
||||
* @return An positive number representing how good the position is
|
||||
*/
|
||||
public int evaluate(Color color) {
|
||||
int score = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) {
|
||||
switch (boardArr[i][j].getType()) {
|
||||
case QUEEN:
|
||||
score += 90;
|
||||
break;
|
||||
case ROOK:
|
||||
score += 50;
|
||||
break;
|
||||
case KNIGHT:
|
||||
score += 30;
|
||||
break;
|
||||
case BISHOP:
|
||||
score += 30;
|
||||
break;
|
||||
case PAWN:
|
||||
score += 10;
|
||||
break;
|
||||
}
|
||||
if (positionScores.containsKey(boardArr[i][j].getType()))
|
||||
score += positionScores.get(boardArr[i][j].getType())[i][color == Color.WHITE ? j : 7 - j];
|
||||
}
|
||||
return score;
|
||||
public BoardState getGameEventType(Color color) {
|
||||
return checkCheck(color) ? checkCheckmate(color) ? BoardState.CHECKMATE : BoardState.CHECK
|
||||
: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? BoardState.STALEMATE : BoardState.NORMAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialized the board array with the default chess pieces and positions.
|
||||
*/
|
||||
public void initializeDefaultPositions() {
|
||||
public void initDefaultPositions() {
|
||||
// Initialize pawns
|
||||
for (int i = 0; i < 8; i++) {
|
||||
boardArr[i][1] = new Pawn(Color.BLACK, this);
|
||||
@ -369,141 +231,116 @@ 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);
|
||||
|
||||
log.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new instance of this class with a shallow copy of both
|
||||
* {@code kingPos} and {code boardArr}
|
||||
*/
|
||||
@Override
|
||||
public Object clone() {
|
||||
Board board = null;
|
||||
try {
|
||||
board = (Board) super.clone();
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
board.boardArr = new Piece[8][8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (boardArr[i][j] == null) continue;
|
||||
board.boardArr[i][j] = (Piece) boardArr[i][j].clone();
|
||||
board.boardArr[i][j].board = board;
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.deepHashCode(boardArr);
|
||||
result = prime * result + Objects.hash(kingPos, log);
|
||||
return result;
|
||||
}
|
||||
|
||||
board.kingPos = new HashMap<>();
|
||||
board.kingPos.putAll(kingPos);
|
||||
board.log = (Log) log.clone();
|
||||
|
||||
return board;
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Board other = (Board) obj;
|
||||
return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A FEN string representing the board
|
||||
* @param pos The position from which to return a piece
|
||||
* @return The piece at the position
|
||||
*/
|
||||
public String toFEN() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
public Piece get(Position pos) { return boardArr[pos.x][pos.y]; }
|
||||
|
||||
// Piece placement (from white's perspective)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int emptyCount = 0;
|
||||
/**
|
||||
* Searches for a {@link Piece} inside a file (A - H).
|
||||
*
|
||||
* @param pieceClass The class of the piece to search for
|
||||
* @param file The file in which to search for the piece
|
||||
* @return The rank (1 - 8) of the first piece with the specified type and
|
||||
* current color in the file, or {@code -1} if there isn't any
|
||||
*/
|
||||
public int get(Class<? extends Piece> pieceClass, char file) {
|
||||
int x = file - 97;
|
||||
for (int i = 0; i < 8; i++)
|
||||
if (boardArr[x][i] != null && boardArr[x][i].getClass() == pieceClass && boardArr[x][i].getColor() == log.getActiveColor()) return 8 - i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a {@link Piece} inside a rank (1 - 8).
|
||||
*
|
||||
* @param pieceClass The class of the piece to search for
|
||||
* @param rank The rank in which to search for the piece
|
||||
* @return The file (A - H) of the first piece with the specified type and
|
||||
* current color in the file, or {@code -} if there isn't any
|
||||
*/
|
||||
public char get(Class<? extends Piece> pieceClass, int rank) {
|
||||
int y = rank - 1;
|
||||
for (int i = 0; i < 8; i++)
|
||||
if (boardArr[i][y] != null && boardArr[i][y].getClass() == pieceClass && boardArr[i][y].getColor() == log.getActiveColor())
|
||||
return (char) (i + 97);
|
||||
return '-';
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a {@link Piece} that can move to a {@link Position}.
|
||||
*
|
||||
* @param pieceClass The class of the piece to search for
|
||||
* @param dest The destination that the piece is required to reach
|
||||
* @return The position of a piece that can move to the specified destination
|
||||
*/
|
||||
public Position get(Class<? extends Piece> pieceClass, Position dest) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (boardArr[j][i] == null) ++emptyCount;
|
||||
else {
|
||||
if (emptyCount != 0) {
|
||||
sb.append(emptyCount);
|
||||
emptyCount = 0;
|
||||
if (boardArr[i][j] != null && boardArr[i][j].getClass() == pieceClass && boardArr[i][j].getColor() == log.getActiveColor()) {
|
||||
Position pos = new Position(i, j);
|
||||
if (boardArr[i][j].isValidMove(new Move(pos, dest))) return pos;
|
||||
}
|
||||
char piece;
|
||||
switch (boardArr[j][i].getType()) {
|
||||
case KING:
|
||||
piece = 'K';
|
||||
break;
|
||||
case QUEEN:
|
||||
piece = 'Q';
|
||||
break;
|
||||
case ROOK:
|
||||
piece = 'R';
|
||||
break;
|
||||
case KNIGHT:
|
||||
piece = 'N';
|
||||
break;
|
||||
case BISHOP:
|
||||
piece = 'B';
|
||||
break;
|
||||
case PAWN:
|
||||
piece = 'P';
|
||||
break;
|
||||
default:
|
||||
piece = '-';
|
||||
}
|
||||
if (boardArr[j][i].getColor() == Color.BLACK) piece = Character.toLowerCase(piece);
|
||||
sb.append(piece);
|
||||
}
|
||||
if (emptyCount != 0) sb.append(emptyCount);
|
||||
if (i < 7) sb.append('/');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Active color
|
||||
sb.append(" " + log.getActiveColor().firstChar());
|
||||
/**
|
||||
* Places a piece at a position.
|
||||
*
|
||||
* @param pos The position to place the piece at
|
||||
* @param piece The piece to place
|
||||
*/
|
||||
public void set(Position pos, Piece piece) { boardArr[pos.x][pos.y] = piece; }
|
||||
|
||||
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);
|
||||
/**
|
||||
* @param move The move from which position to return a piece
|
||||
* @return The piece at the position of the move
|
||||
*/
|
||||
public Piece getPos(Move move) { return get(move.getPos()); }
|
||||
|
||||
final LoggedMove lastMove = log.getLast();
|
||||
/**
|
||||
* @param move The move from which destination to return a piece
|
||||
* @return The piece at the destination of the move
|
||||
*/
|
||||
public Piece getDest(Move move) { return get(move.getDest()); }
|
||||
|
||||
// En passant availabillity
|
||||
sb.append(" " + (lastMove == null || lastMove.enPassant == null ? "-" : lastMove.enPassant.toSAN()));
|
||||
/**
|
||||
* Places a piece at the position of a move.
|
||||
*
|
||||
* @param move The move at which position to place the piece
|
||||
* @param piece The piece to place
|
||||
*/
|
||||
public void setPos(Move move, Piece piece) { set(move.getPos(), piece); }
|
||||
|
||||
// Halfmove clock
|
||||
sb.append(" " + String.valueOf(lastMove == null ? 0 : lastMove.halfmoveClock));
|
||||
|
||||
// Fullmove counter
|
||||
sb.append(" " + String.valueOf(lastMove == null ? 1 : lastMove.fullmoveCounter));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Piece get(Position pos) {
|
||||
return boardArr[pos.x][pos.y];
|
||||
}
|
||||
|
||||
public void set(Position pos, Piece piece) {
|
||||
boardArr[pos.x][pos.y] = piece;
|
||||
}
|
||||
|
||||
public Piece getPos(Move move) {
|
||||
return get(move.pos);
|
||||
}
|
||||
|
||||
public Piece getDest(Move move) {
|
||||
return get(move.dest);
|
||||
}
|
||||
|
||||
public void setPos(Move move, Piece piece) {
|
||||
set(move.pos, piece);
|
||||
}
|
||||
|
||||
public void setDest(Move move, Piece piece) {
|
||||
set(move.dest, piece);
|
||||
}
|
||||
/**
|
||||
* Places a piece at the destination of a move.
|
||||
*
|
||||
* @param move The move at which destination to place the piece
|
||||
* @param piece The piece to place
|
||||
*/
|
||||
public void setDest(Move move, Piece piece) { set(move.getDest(), piece); }
|
||||
|
||||
/**
|
||||
* @return The board array
|
||||
@ -511,12 +348,7 @@ public class Board implements Cloneable {
|
||||
public Piece[][] getBoardArr() { return boardArr; }
|
||||
|
||||
/**
|
||||
* @return The active color for the next move
|
||||
* @return The move log
|
||||
*/
|
||||
public Color getActiveColor() { return log.getActiveColor(); }
|
||||
|
||||
/**
|
||||
* @return The current en passant square, or null if there isn't any
|
||||
*/
|
||||
public Position getEnPassantSquare() { return log.getLast() == null ? null : log.getLast().enPassant; }
|
||||
public Log getLog() { return log; }
|
||||
}
|
||||
|
@ -2,10 +2,12 @@ package dev.kske.chess.board;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>GameState.java</strong><br>
|
||||
* File: <strong>BoardState.java</strong><br>
|
||||
* Created: <strong>07.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public enum GameState {
|
||||
public enum BoardState {
|
||||
CHECK, CHECKMATE, STALEMATE, NORMAL;
|
||||
}
|
44
src/dev/kske/chess/board/Castling.java
Normal file
44
src/dev/kske/chess/board/Castling.java
Normal file
@ -0,0 +1,44 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Castling.java</strong><br>
|
||||
* Created: <strong>2 Nov 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Castling extends Move {
|
||||
|
||||
private final Move rookMove;
|
||||
|
||||
public Castling(Position pos, Position dest) {
|
||||
super(pos, dest);
|
||||
rookMove = dest.x == 6 ? new Move(7, pos.y, 5, pos.y) : new Move(0, pos.y, 3, pos.y);
|
||||
}
|
||||
|
||||
public Castling(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
|
||||
|
||||
@Override
|
||||
public void execute(Board board) {
|
||||
// Move the king and the rook
|
||||
super.execute(board);
|
||||
rookMove.execute(board);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revert(Board board, Piece capturedPiece) {
|
||||
// Move the king and the rook
|
||||
super.revert(board, capturedPiece);
|
||||
rookMove.revert(board, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code O-O-O} for a queenside castling or {@code O-O} for a kingside
|
||||
* castling
|
||||
*/
|
||||
@Override
|
||||
public String toSAN(Board board) {
|
||||
return rookMove.pos.x == 0 ? "O-O-O" : "O-O";
|
||||
}
|
||||
}
|
35
src/dev/kske/chess/board/EnPassant.java
Normal file
35
src/dev/kske/chess/board/EnPassant.java
Normal file
@ -0,0 +1,35 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>EnPassant.java</strong><br>
|
||||
* Created: <strong>2 Nov 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class EnPassant extends Move {
|
||||
|
||||
private final Position capturePos;
|
||||
|
||||
public EnPassant(Position pos, Position dest) {
|
||||
super(pos, dest);
|
||||
capturePos = new Position(dest.x, dest.y - ySign);
|
||||
}
|
||||
|
||||
public EnPassant(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
|
||||
|
||||
@Override
|
||||
public void execute(Board board) {
|
||||
super.execute(board);
|
||||
board.set(capturePos, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revert(Board board, Piece capturedPiece) {
|
||||
super.revert(board, capturedPiece);
|
||||
board.set(capturePos, new Pawn(board.get(pos).getColor().opposite(), board));
|
||||
}
|
||||
|
||||
public Position getCapturePos() { return capturePos; }
|
||||
}
|
212
src/dev/kske/chess/board/FENString.java
Normal file
212
src/dev/kske/chess/board/FENString.java
Normal file
@ -0,0 +1,212 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.exception.ChessException;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>FENString.java</strong><br>
|
||||
* Created: <strong>20 Oct 2019</strong><br>
|
||||
* <br>
|
||||
* Represents a FEN string and enables parsing an existing FEN string or
|
||||
* serializing a {@link Board} to one.
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class FENString {
|
||||
|
||||
private Board board;
|
||||
private String piecePlacement, castlingAvailability;
|
||||
private int halfmoveClock, fullmoveNumber;
|
||||
private Color activeColor;
|
||||
private Position enPassantTargetSquare;
|
||||
|
||||
/**
|
||||
* Constructs a {@link FENString} representing the starting position
|
||||
* {@code rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1}.
|
||||
*/
|
||||
public FENString() {
|
||||
board = new Board();
|
||||
piecePlacement = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
|
||||
activeColor = Color.WHITE;
|
||||
castlingAvailability = "KQkq";
|
||||
halfmoveClock = 0;
|
||||
fullmoveNumber = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link FENString} by parsing an existing string.
|
||||
*
|
||||
* @param fen the FEN string to parse
|
||||
* @throws ChessException
|
||||
*/
|
||||
public FENString(String fen) throws ChessException {
|
||||
// Check fen string against regex
|
||||
Pattern fenPattern = Pattern.compile(
|
||||
"^(?<piecePlacement>(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?<activeColor>[wb]) (?<castlingAvailability>-|[KQkq]{1,4}) (?<enPassantTargetSquare>-|[a-h][1-8]) (?<halfmoveClock>\\d+) (?<fullmoveNumber>\\d+)$");
|
||||
Matcher matcher = fenPattern.matcher(fen);
|
||||
if (!matcher.find()) throw new ChessException("FEN string does not match pattern " + fenPattern.pattern());
|
||||
|
||||
// Initialize data fields
|
||||
piecePlacement = matcher.group("piecePlacement");
|
||||
activeColor = Color.fromFirstChar(matcher.group("activeColor").charAt(0));
|
||||
castlingAvailability = matcher.group("castlingAvailability");
|
||||
if (!matcher.group("enPassantTargetSquare").equals("-")) enPassantTargetSquare = Position.fromLAN(matcher.group("enPassantTargetSquare"));
|
||||
halfmoveClock = Integer.parseInt(matcher.group("halfmoveClock"));
|
||||
fullmoveNumber = Integer.parseInt(matcher.group("fullmoveNumber"));
|
||||
|
||||
// Initialize and clean board
|
||||
board = new Board();
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
board.getBoardArr()[i][j] = null;
|
||||
|
||||
// Parse individual fields
|
||||
|
||||
// Piece placement
|
||||
final String[] rows = piecePlacement.split("/");
|
||||
if (rows.length != 8) throw new ChessException("FEN string contains invalid piece placement");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
final char[] cols = rows[i].toCharArray();
|
||||
int j = 0;
|
||||
for (char c : cols) {
|
||||
|
||||
// Empty space
|
||||
if (Character.isDigit(c)) {
|
||||
j += Character.getNumericValue(c);
|
||||
} else {
|
||||
Color color = Character.isUpperCase(c) ? Color.WHITE : Color.BLACK;
|
||||
try {
|
||||
Constructor<? extends Piece> pieceConstructor = Piece.fromFirstChar(c).getDeclaredConstructor(Color.class, Board.class);
|
||||
pieceConstructor.setAccessible(true);
|
||||
board.getBoardArr()[j][i] = pieceConstructor.newInstance(color, board);
|
||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
|
||||
| NoSuchMethodException | SecurityException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Active color
|
||||
board.getLog().setActiveColor(activeColor);
|
||||
|
||||
// Castling availability
|
||||
boolean castlingRights[] = new boolean[4];
|
||||
for (char c : castlingAvailability.toCharArray())
|
||||
switch (c) {
|
||||
case 'K':
|
||||
castlingRights[MoveNode.WHITE_KINGSIDE] = true;
|
||||
break;
|
||||
case 'Q':
|
||||
castlingRights[MoveNode.WHITE_QUEENSIDE] = true;
|
||||
break;
|
||||
case 'k':
|
||||
castlingRights[MoveNode.BLACK_KINGSIDE] = true;
|
||||
break;
|
||||
case 'q':
|
||||
castlingRights[MoveNode.BLACK_QUEENSIDE] = true;
|
||||
break;
|
||||
}
|
||||
board.getLog().setCastlingRights(castlingRights);
|
||||
|
||||
// En passant square
|
||||
board.getLog().setEnPassant(enPassantTargetSquare);
|
||||
|
||||
// Halfmove clock
|
||||
board.getLog().setHalfmoveClock(halfmoveClock);
|
||||
|
||||
// Fullmove number
|
||||
board.getLog().setFullmoveNumber(fullmoveNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link FENString} form a {@link Board} object.
|
||||
*
|
||||
* @param board the {@link Board} object to encode in this {@link FENString}
|
||||
*/
|
||||
public FENString(Board board) {
|
||||
this.board = board;
|
||||
|
||||
// Serialize individual fields
|
||||
|
||||
// Piece placement
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int empty = 0;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
final Piece piece = board.getBoardArr()[j][i];
|
||||
|
||||
if (piece == null) ++empty;
|
||||
else {
|
||||
|
||||
// Write empty field count
|
||||
if (empty > 0) {
|
||||
sb.append(empty);
|
||||
empty = 0;
|
||||
}
|
||||
|
||||
// Write piece character
|
||||
char p = piece.firstChar();
|
||||
sb.append(piece.getColor() == Color.WHITE ? Character.toUpperCase(p) : p);
|
||||
}
|
||||
}
|
||||
|
||||
// Write empty field count
|
||||
if (empty > 0) {
|
||||
sb.append(empty);
|
||||
empty = 0;
|
||||
}
|
||||
|
||||
if (i < 7) sb.append('/');
|
||||
}
|
||||
piecePlacement = sb.toString();
|
||||
|
||||
// Active color
|
||||
activeColor = board.getLog().getActiveColor();
|
||||
|
||||
// Castling availability
|
||||
castlingAvailability = "";
|
||||
final char castlingRightsChars[] = new char[] { 'K', 'Q', 'k', 'q' };
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (board.getLog().getCastlingRights()[i]) castlingAvailability += castlingRightsChars[i];
|
||||
if (castlingAvailability.isEmpty()) castlingAvailability = "-";
|
||||
|
||||
// En passant availability
|
||||
enPassantTargetSquare = board.getLog().getEnPassant();
|
||||
|
||||
// Halfmove clock
|
||||
halfmoveClock = board.getLog().getHalfmoveClock();
|
||||
|
||||
// Fullmove counter
|
||||
fullmoveNumber = board.getLog().getFullmoveNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports this {@link FENString} object to a FEN string.
|
||||
*
|
||||
* @return a FEN string representing the board
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s %c %s %s %d %d",
|
||||
piecePlacement,
|
||||
activeColor.firstChar(),
|
||||
castlingAvailability,
|
||||
enPassantTargetSquare == null ? "-" : enPassantTargetSquare.toLAN(),
|
||||
halfmoveClock,
|
||||
fullmoveNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@link Board} object corresponding to this {@link FENString}
|
||||
*/
|
||||
public Board getBoard() { return board; }
|
||||
}
|
@ -7,49 +7,19 @@ import java.util.List;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>King.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class King extends Piece {
|
||||
|
||||
public King(Color color, Board board) {
|
||||
super(color, board);
|
||||
}
|
||||
public King(Color color, Board board) { super(color, board); }
|
||||
|
||||
@Override
|
||||
public boolean isValidMove(Move move) {
|
||||
// Castling
|
||||
if (move.xDist == 2 && move.yDist == 0) {
|
||||
if (canCastleKingside()) {
|
||||
move.type = Move.Type.CASTLING;
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
return (move.getxDist() == 2 && move.getyDist() == 0
|
||||
&& (move.getDest().x == 6 && canCastleKingside() || move.getDest().x == 2 && canCastleQueenside()))
|
||||
|| move.getxDist() <= 1 && move.getyDist() <= 1 && checkDestination(move);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -63,13 +33,40 @@ public class King extends Piece {
|
||||
}
|
||||
|
||||
// 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));
|
||||
if (canCastleKingside()) moves.add(new Castling(pos, new Position(6, pos.y)));
|
||||
if (canCastleQueenside()) moves.add(new Castling(pos, new Position(2, pos.y)));
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
private boolean canCastleKingside() {
|
||||
if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE]) {
|
||||
int y = getColor() == Color.WHITE ? 7 : 0;
|
||||
Position kingPos = new Position(4, y);
|
||||
Position jumpPos = new Position(5, y);
|
||||
Position kingDest = new Position(6, y);
|
||||
Position rookPos = new Position(7, y);
|
||||
return canCastle(kingPos, kingDest, rookPos, jumpPos);
|
||||
} else return false;
|
||||
}
|
||||
|
||||
private boolean canCastleQueenside() {
|
||||
if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE]) {
|
||||
int y = getColor() == Color.WHITE ? 7 : 0;
|
||||
Position kingPos = new Position(4, y);
|
||||
Position jumpPos = new Position(3, y);
|
||||
Position freeDest = new Position(1, y);
|
||||
Position rookPos = new Position(0, y);
|
||||
return canCastle(kingPos, freeDest, rookPos, jumpPos);
|
||||
} else return false;
|
||||
}
|
||||
|
||||
private boolean canCastle(Position kingPos, Position freeDest, Position rookPos, Position jumpPos) {
|
||||
Piece rook = board.get(rookPos);
|
||||
return rook != null && rook instanceof Rook && isFreePath(new Move(kingPos, freeDest)) && !board.isAttacked(kingPos, getColor().opposite())
|
||||
&& !board.isAttacked(jumpPos, getColor().opposite());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() { return Type.KING; }
|
||||
public int getValue() { return 0; }
|
||||
}
|
@ -7,7 +7,9 @@ import java.util.List;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Knight.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Knight extends Piece {
|
||||
|
||||
@ -17,8 +19,8 @@ public class Knight extends Piece {
|
||||
|
||||
@Override
|
||||
public boolean isValidMove(Move move) {
|
||||
return Math.abs(move.xDist - move.yDist) == 1
|
||||
&& (move.xDist == 1 && move.yDist == 2 || move.xDist == 2 && move.yDist == 1) && checkDestination(move);
|
||||
return Math.abs(move.getxDist() - move.getyDist()) == 1
|
||||
&& (move.getxDist() == 1 && move.getyDist() == 2 || move.getxDist() == 2 && move.getyDist() == 1) && checkDestination(move);
|
||||
}
|
||||
|
||||
private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
|
||||
@ -43,5 +45,8 @@ public class Knight extends Piece {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() { return Type.KNIGHT; }
|
||||
public int getValue() { return 35; }
|
||||
|
||||
@Override
|
||||
public char firstChar() { return 'n'; }
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
|
||||
@ -9,79 +10,243 @@ import dev.kske.chess.board.Piece.Color;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Log.java</strong><br>
|
||||
* Created: <strong>09.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Log implements Cloneable {
|
||||
public class Log implements Iterable<MoveNode> {
|
||||
|
||||
private MoveNode root, current;
|
||||
|
||||
private List<LoggedMove> moves;
|
||||
private Color activeColor;
|
||||
private boolean[] castlingRights;
|
||||
private Position enPassant;
|
||||
private int fullmoveNumber, halfmoveClock;
|
||||
|
||||
public Log() {
|
||||
moves = new ArrayList<>();
|
||||
activeColor = Color.WHITE;
|
||||
public Log() { reset(); }
|
||||
|
||||
/**
|
||||
* Creates a (partially deep) copy of another {@link Log} instance which begins
|
||||
* with the current {@link MoveNode}.
|
||||
*
|
||||
* @param other The {@link Log} instance to copy
|
||||
* @param copyVariations If set to {@code true}, subsequent variations of the
|
||||
* current {@link MoveNode} are copied with the
|
||||
* {@link Log}
|
||||
*/
|
||||
public Log(Log other, boolean copyVariations) {
|
||||
enPassant = other.enPassant;
|
||||
castlingRights = other.castlingRights.clone();
|
||||
activeColor = other.activeColor;
|
||||
fullmoveNumber = other.fullmoveNumber;
|
||||
halfmoveClock = other.halfmoveClock;
|
||||
|
||||
// The new root is the current node of the copied instance
|
||||
if (!other.isEmpty()) {
|
||||
root = new MoveNode(other.root, copyVariations);
|
||||
root.setParent(null);
|
||||
current = root;
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Move move, Piece capturedPiece, boolean pawnMove) {
|
||||
// En passant availability
|
||||
Position enPassant = null;
|
||||
if (pawnMove && move.yDist == 2) enPassant = new Position(move.pos.x, move.pos.y + move.ySign);
|
||||
/**
|
||||
* @return an iterator over all {@link MoveNode} objects that are either the
|
||||
* root node or a first variation of another node, starting from the
|
||||
* root node
|
||||
*/
|
||||
@Override
|
||||
public Iterator<MoveNode> iterator() {
|
||||
return new Iterator<MoveNode>() {
|
||||
|
||||
// Fullmove counter and halfmove clock
|
||||
int fullmoveCounter, halfmoveClock;
|
||||
if (moves.isEmpty()) {
|
||||
fullmoveCounter = 1;
|
||||
halfmoveClock = 0;
|
||||
private MoveNode current = root;
|
||||
private boolean hasNext = !isEmpty();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() { return hasNext; }
|
||||
|
||||
@Override
|
||||
public MoveNode next() {
|
||||
MoveNode result = current;
|
||||
if (current.hasVariations()) current = current.getVariations().get(0);
|
||||
else hasNext = false;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a move to the move history and adjusts the log to the new position.
|
||||
*
|
||||
* @param move The move to log
|
||||
* @param piece The piece that performed the move
|
||||
* @param capturedPiece The piece captured with the move
|
||||
*/
|
||||
public void add(Move move, Piece piece, Piece capturedPiece) {
|
||||
enPassant = piece instanceof Pawn && move.getyDist() == 2 ? new Position(move.getPos().x, move.getPos().y + move.getySign()) : null;
|
||||
if (activeColor == Color.BLACK) ++fullmoveNumber;
|
||||
if (piece instanceof Pawn || capturedPiece != null) halfmoveClock = 0;
|
||||
else++halfmoveClock;
|
||||
activeColor = activeColor.opposite();
|
||||
|
||||
// Disable castling rights if a king or a rook has been moved
|
||||
if (piece instanceof King || piece instanceof Rook) disableCastlingRights(piece, move.getPos());
|
||||
|
||||
final MoveNode leaf = new MoveNode(move, capturedPiece, castlingRights.clone(), enPassant, activeColor, fullmoveNumber, halfmoveClock);
|
||||
|
||||
if (isEmpty()) {
|
||||
root = leaf;
|
||||
current = leaf;
|
||||
} else {
|
||||
fullmoveCounter = getLast().fullmoveCounter;
|
||||
if (activeColor == Color.BLACK) ++fullmoveCounter;
|
||||
halfmoveClock = capturedPiece != null || pawnMove ? 0 : getLast().halfmoveClock + 1;
|
||||
current.addVariation(leaf);
|
||||
current = leaf;
|
||||
}
|
||||
activeColor = activeColor.opposite();
|
||||
moves.add(new LoggedMove(move, capturedPiece, enPassant, fullmoveCounter, halfmoveClock));
|
||||
}
|
||||
|
||||
public LoggedMove getLast() { return moves.isEmpty() ? null : moves.get(moves.size() - 1); }
|
||||
|
||||
/**
|
||||
* Removes the last move from the log and adjusts its state to the previous
|
||||
* move.
|
||||
*/
|
||||
public void removeLast() {
|
||||
if (!moves.isEmpty()) {
|
||||
activeColor = activeColor.opposite();
|
||||
moves.remove(moves.size() - 1);
|
||||
if (hasParent()) {
|
||||
current.getParent().getVariations().remove(current);
|
||||
current = current.getParent();
|
||||
update();
|
||||
} else reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the root node exists
|
||||
*/
|
||||
public boolean isEmpty() { return root == null; }
|
||||
|
||||
/**
|
||||
* @return {@code true} if the current node has a parent node
|
||||
*/
|
||||
public boolean hasParent() { return !isEmpty() && current.hasParent(); }
|
||||
|
||||
/**
|
||||
* Reverts the log to its initial state corresponding to the default board
|
||||
* position.
|
||||
*/
|
||||
public void reset() {
|
||||
root = null;
|
||||
current = null;
|
||||
castlingRights = new boolean[] { true, true, true, true };
|
||||
enPassant = null;
|
||||
activeColor = Color.WHITE;
|
||||
fullmoveNumber = 1;
|
||||
halfmoveClock = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current node to one of its children (variations).
|
||||
*
|
||||
* @param index the index of the variation to select
|
||||
*/
|
||||
public void selectNextNode(int index) {
|
||||
if (!isEmpty() && current.hasVariations() && index < current.getVariations().size()) {
|
||||
current = current.getVariations().get(index);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
moves.clear();
|
||||
activeColor = Color.WHITE;
|
||||
/**
|
||||
* Selects the parent of the current {@link MoveNode} as the current node.
|
||||
*/
|
||||
public void selectPreviousNode() {
|
||||
if (hasParent()) {
|
||||
current = current.getParent();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the root {@link MoveNode} as the current node.
|
||||
*/
|
||||
public void selectRootNode() {
|
||||
if (!isEmpty()) {
|
||||
current = root;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active color, castling rights, en passant target square, fullmove
|
||||
* number and halfmove clock to those of the current {@link MoveNode}.
|
||||
*/
|
||||
private void update() {
|
||||
activeColor = current.activeColor;
|
||||
castlingRights = current.castlingRights.clone();
|
||||
enPassant = current.enPassant;
|
||||
fullmoveNumber = current.fullmoveCounter;
|
||||
halfmoveClock = current.halfmoveClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed the castling rights bound to a rook or king for the rest of the game.
|
||||
* This method should be called once the piece has been moved, as a castling
|
||||
* move involving this piece is forbidden afterwards.
|
||||
*
|
||||
* @param piece the rook or king to disable the castling rights for
|
||||
* @param initialPosition the initial position of the piece during the start of
|
||||
* the game
|
||||
*/
|
||||
private void disableCastlingRights(Piece piece, Position initialPosition) {
|
||||
// Kingside
|
||||
if (piece instanceof King || piece instanceof Rook && initialPosition.x == 7)
|
||||
castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE] = false;
|
||||
|
||||
// Queenside
|
||||
if (piece instanceof King || piece instanceof Rook && initialPosition.x == 0)
|
||||
castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE] = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
Log log = null;
|
||||
try {
|
||||
log = (Log) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(castlingRights);
|
||||
result = prime * result + Objects.hash(activeColor, current, enPassant, fullmoveNumber, halfmoveClock);
|
||||
return result;
|
||||
}
|
||||
log.moves = new ArrayList<>();
|
||||
log.moves.addAll(this.moves);
|
||||
return log;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Log other = (Log) obj;
|
||||
return activeColor == other.activeColor && Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(current, other.current)
|
||||
&& Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber && halfmoveClock == other.halfmoveClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The first logged move, or {@code null} if there is none
|
||||
*/
|
||||
public MoveNode getRoot() { return root; }
|
||||
|
||||
/**
|
||||
* @return the last logged move, or {@code null} if there is none
|
||||
*/
|
||||
public MoveNode getLast() { return current; }
|
||||
|
||||
public boolean[] getCastlingRights() { return castlingRights; }
|
||||
|
||||
public void setCastlingRights(boolean[] castlingRights) { this.castlingRights = castlingRights; }
|
||||
|
||||
public Position getEnPassant() { return enPassant; }
|
||||
|
||||
public void setEnPassant(Position enPassant) { this.enPassant = enPassant; }
|
||||
|
||||
public Color getActiveColor() { return activeColor; }
|
||||
|
||||
public static class LoggedMove {
|
||||
public void setActiveColor(Color activeColor) { this.activeColor = activeColor; }
|
||||
|
||||
public final Move move;
|
||||
public final Piece capturedPiece;
|
||||
public final Position enPassant;
|
||||
public final int fullmoveCounter, halfmoveClock;
|
||||
public int getFullmoveNumber() { return fullmoveNumber; }
|
||||
|
||||
public LoggedMove(Move move, Piece capturedPiece, Position enPassant, int fullmoveCounter, int halfmoveClock) {
|
||||
this.move = move;
|
||||
this.capturedPiece = capturedPiece;
|
||||
this.enPassant = enPassant;
|
||||
this.fullmoveCounter = fullmoveCounter;
|
||||
this.halfmoveClock = halfmoveClock;
|
||||
}
|
||||
}
|
||||
public void setFullmoveNumber(int fullmoveNumber) { this.fullmoveNumber = fullmoveNumber; }
|
||||
|
||||
public int getHalfmoveClock() { return halfmoveClock; }
|
||||
|
||||
public void setHalfmoveClock(int halfmoveClock) { this.halfmoveClock = halfmoveClock; }
|
||||
}
|
@ -1,52 +1,205 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Move.java</strong><br>
|
||||
* Created: <strong>02.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Move {
|
||||
|
||||
public final Position pos, dest;
|
||||
public final int xDist, yDist, xSign, ySign;
|
||||
public Type type;
|
||||
protected final Position pos, dest;
|
||||
protected final int xDist, yDist, xSign, ySign;
|
||||
|
||||
public Move(Position pos, Position dest, Type type) {
|
||||
public Move(Position pos, Position dest) {
|
||||
this.pos = pos;
|
||||
this.dest = dest;
|
||||
this.type = type;
|
||||
xDist = Math.abs(dest.x - pos.x);
|
||||
yDist = Math.abs(dest.y - pos.y);
|
||||
xSign = (int) Math.signum(dest.x - pos.x);
|
||||
ySign = (int) Math.signum(dest.y - pos.y);
|
||||
}
|
||||
|
||||
public Move(Position pos, Position dest) {
|
||||
this(pos, dest, Type.NORMAL);
|
||||
public Move(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
|
||||
|
||||
public void execute(Board board) {
|
||||
// Move the piece to the move's destination square and clean the old position
|
||||
board.set(dest, board.get(pos));
|
||||
board.set(pos, null);
|
||||
}
|
||||
|
||||
public Move(int xPos, int yPos, int xDest, int yDest) {
|
||||
this(new Position(xPos, yPos), new Position(xDest, yDest));
|
||||
public void revert(Board board, Piece capturedPiece) {
|
||||
// Move the piece to the move's position square and clean the destination
|
||||
board.set(pos, board.get(dest));
|
||||
board.set(dest, capturedPiece);
|
||||
}
|
||||
|
||||
public static Move fromSAN(String move) {
|
||||
return new Move(Position.fromSAN(move.substring(0, 2)),
|
||||
Position.fromSAN(move.substring(2)));
|
||||
public static Move fromLAN(String move) {
|
||||
Position pos = Position.fromLAN(move.substring(0, 2));
|
||||
Position dest = Position.fromLAN(move.substring(2));
|
||||
if (move.length() == 5) {
|
||||
try {
|
||||
return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4)));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
} else return new Move(pos, dest);
|
||||
}
|
||||
|
||||
public boolean isHorizontal() { return yDist == 0; }
|
||||
public String toLAN() { return getPos().toLAN() + getDest().toLAN(); }
|
||||
|
||||
public boolean isVertical() { return xDist == 0; }
|
||||
/**
|
||||
* Converts a move string from standard algebraic notation to a {@link Move}
|
||||
* object.
|
||||
*
|
||||
* @param sanMove the move string to convert from
|
||||
* @param board the board on which the move has to be executed
|
||||
* @return the converted {@link Move} object
|
||||
*/
|
||||
public static Move fromSAN(String sanMove, Board board) {
|
||||
Map<String, Pattern> patterns = new HashMap<>();
|
||||
patterns.put("pieceMove",
|
||||
Pattern.compile(
|
||||
"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$"));
|
||||
patterns.put("pawnCapture",
|
||||
Pattern.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)?$"));
|
||||
patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)$"));
|
||||
patterns.put("castling", Pattern.compile("^(?<queenside>O-O-O)|(?<kingside>O-O)(?:\\+{0,2}|\\#)?$"));
|
||||
|
||||
public boolean isDiagonal() { return xDist == yDist; }
|
||||
for (Map.Entry<String, Pattern> entry : patterns.entrySet()) {
|
||||
Matcher m = entry.getValue().matcher(sanMove);
|
||||
if (m.find()) {
|
||||
Position pos = null, dest = null;
|
||||
Move move = null;
|
||||
switch (entry.getKey()) {
|
||||
case "pieceMove":
|
||||
dest = Position.fromLAN(m.group("toSquare"));
|
||||
if (m.group("fromSquare") != null) pos = Position.fromLAN(m.group("fromSquare"));
|
||||
else {
|
||||
Class<? extends Piece> pieceClass = Piece.fromFirstChar(m.group("pieceType").charAt(0));
|
||||
char file;
|
||||
int rank;
|
||||
if (m.group("fromFile") != null) {
|
||||
file = m.group("fromFile").charAt(0);
|
||||
rank = board.get(pieceClass, file);
|
||||
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||
} else if (m.group("fromRank") != null) {
|
||||
rank = Integer.parseInt(m.group("fromRank").substring(0, 1));
|
||||
file = board.get(pieceClass, rank);
|
||||
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||
} else pos = board.get(pieceClass, dest);
|
||||
}
|
||||
move = new Move(pos, dest);
|
||||
break;
|
||||
case "pawnCapture":
|
||||
char file = m.group("fromFile").charAt(0);
|
||||
int rank = m.group("fromRank") == null ? board.get(Pawn.class, file) : Integer.parseInt(m.group("fromRank"));
|
||||
|
||||
dest = Position.fromLAN(m.group("toSquare"));
|
||||
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||
|
||||
if (m.group("promotedTo") != null) {
|
||||
try {
|
||||
move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else move = new Move(pos, dest);
|
||||
break;
|
||||
case "pawnPush":
|
||||
dest = Position.fromLAN(m.group("toSquare"));
|
||||
int step = board.getLog().getActiveColor() == Color.WHITE ? 1 : -1;
|
||||
|
||||
// One step forward
|
||||
if (board.getBoardArr()[dest.x][dest.y + step] != null) pos = new Position(dest.x, dest.y + step);
|
||||
|
||||
// Double step forward
|
||||
else pos = new Position(dest.x, dest.y + 2 * step);
|
||||
|
||||
if (m.group("promotedTo") != null) {
|
||||
try {
|
||||
move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else move = new Move(pos, dest);
|
||||
break;
|
||||
case "castling":
|
||||
pos = new Position(4, board.getLog().getActiveColor() == Color.WHITE ? 7 : 0);
|
||||
dest = new Position(m.group("kingside") != null ? 6 : 2, pos.y);
|
||||
move = new Castling(pos, dest);
|
||||
break;
|
||||
}
|
||||
return move;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toSAN(Board board) {
|
||||
final Piece piece = board.get(pos);
|
||||
StringBuilder sb = new StringBuilder(8);
|
||||
|
||||
// Piece symbol
|
||||
if(!(piece instanceof Pawn))
|
||||
sb.append(Character.toUpperCase(piece.firstChar()));
|
||||
|
||||
// Position
|
||||
// TODO: Deconstruct position into optional file or rank
|
||||
// Omit position if the move is a pawn push
|
||||
if (!(piece instanceof Pawn && xDist == 0)) sb.append(pos.toLAN());
|
||||
|
||||
// Capture indicator
|
||||
if (board.get(dest) != null) sb.append('x');
|
||||
|
||||
// Destination
|
||||
sb.append(dest.toLAN());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isHorizontal() { return getyDist() == 0; }
|
||||
|
||||
public boolean isVertical() { return getxDist() == 0; }
|
||||
|
||||
public boolean isDiagonal() { return getxDist() == getyDist(); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s -> %s", pos, dest);
|
||||
public String toString() { return toLAN(); }
|
||||
|
||||
@Override
|
||||
public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Move other = (Move) obj;
|
||||
return Objects.equals(getDest(), other.getDest()) && Objects.equals(getPos(), other.getPos()) && getxDist() == other.getxDist()
|
||||
&& getxSign() == other.getxSign() && getyDist() == other.getyDist() && getySign() == other.getySign();
|
||||
}
|
||||
|
||||
public static enum Type {
|
||||
NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN
|
||||
}
|
||||
public Position getPos() { return pos; }
|
||||
|
||||
public Position getDest() { return dest; }
|
||||
|
||||
public int getxDist() { return xDist; }
|
||||
|
||||
public int getyDist() { return yDist; }
|
||||
|
||||
public int getxSign() { return xSign; }
|
||||
|
||||
public int getySign() { return ySign; }
|
||||
}
|
||||
|
126
src/dev/kske/chess/board/MoveNode.java
Normal file
126
src/dev/kske/chess/board/MoveNode.java
Normal file
@ -0,0 +1,126 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>MoveNode.java</strong><br>
|
||||
* Created: <strong>02.10.2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class MoveNode {
|
||||
|
||||
public static final int WHITE_KINGSIDE = 0, WHITE_QUEENSIDE = 1, BLACK_KINGSIDE = 2, BLACK_QUEENSIDE = 3;
|
||||
|
||||
public final Move move;
|
||||
public final Piece capturedPiece;
|
||||
public final boolean[] castlingRights;
|
||||
public final Position enPassant;
|
||||
public final Color activeColor;
|
||||
public final int fullmoveCounter, halfmoveClock;
|
||||
|
||||
private MoveNode parent;
|
||||
private List<MoveNode> variations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MoveNode}.
|
||||
*
|
||||
* @param move The logged {@link Move}
|
||||
* @param capturedPiece The {@link Piece} captures by the logged {@link Move}
|
||||
* @param enPassant The en passant {@link Position} valid after the logged
|
||||
* {@link Move}, or {@code null} if there is none
|
||||
* @param activeColor The {@link Color} active after the logged {@link Move}
|
||||
* @param fullmoveCounter
|
||||
* @param halfmoveClock
|
||||
*/
|
||||
public MoveNode(Move move, Piece capturedPiece, boolean castlingRights[], Position enPassant, Color activeColor,
|
||||
int fullmoveCounter, int halfmoveClock) {
|
||||
this.move = move;
|
||||
this.capturedPiece = capturedPiece;
|
||||
this.castlingRights = castlingRights;
|
||||
this.enPassant = enPassant;
|
||||
this.activeColor = activeColor;
|
||||
this.fullmoveCounter = fullmoveCounter;
|
||||
this.halfmoveClock = halfmoveClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (deep) copy of another {@link MoveNode}.
|
||||
*
|
||||
* @param other The {@link MoveNode} to copy
|
||||
* @param copyVariations When this is set to {@code true} a deep copy is
|
||||
* created, which
|
||||
* considers subsequent variations
|
||||
*/
|
||||
public MoveNode(MoveNode other, boolean copyVariations) {
|
||||
this(other.move, other.capturedPiece, other.castlingRights.clone(), other.enPassant, other.activeColor,
|
||||
other.fullmoveCounter, other.halfmoveClock);
|
||||
if (copyVariations && other.variations != null) {
|
||||
if (variations == null) variations = new ArrayList<>();
|
||||
for (MoveNode variation : other.variations) {
|
||||
MoveNode copy = new MoveNode(variation, true);
|
||||
copy.parent = this;
|
||||
variations.add(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another {@link MoveNode} as a child node.
|
||||
*
|
||||
* @param variation The {@link MoveNode} to append to this {@link MoveNode}
|
||||
*/
|
||||
public void addVariation(MoveNode variation) {
|
||||
if (variations == null) variations = new ArrayList<>();
|
||||
if (!variations.contains(variation)) {
|
||||
variations.add(variation);
|
||||
variation.parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of all variations associated with this {@link MoveNode}
|
||||
*/
|
||||
public List<MoveNode> getVariations() { return variations; }
|
||||
|
||||
public boolean hasVariations() {
|
||||
return variations != null && variations.size() > 0;
|
||||
}
|
||||
|
||||
public MoveNode getParent() { return parent; }
|
||||
|
||||
public void setParent(MoveNode parent) { this.parent = parent; }
|
||||
|
||||
public boolean hasParent() {
|
||||
return parent != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(castlingRights);
|
||||
result = prime * result
|
||||
+ Objects.hash(activeColor, capturedPiece, enPassant, fullmoveCounter, halfmoveClock, move);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
MoveNode other = (MoveNode) obj;
|
||||
return activeColor == other.activeColor && Objects.equals(capturedPiece, other.capturedPiece)
|
||||
&& Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(enPassant, other.enPassant)
|
||||
&& fullmoveCounter == other.fullmoveCounter && halfmoveClock == other.halfmoveClock
|
||||
&& Objects.equals(move, other.move);
|
||||
}
|
||||
}
|
@ -7,44 +7,33 @@ import java.util.List;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Pawn.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Pawn extends Piece {
|
||||
|
||||
public Pawn(Color color, Board board) {
|
||||
super(color, board);
|
||||
}
|
||||
public Pawn(Color color, Board board) { super(color, board); }
|
||||
|
||||
@Override
|
||||
public boolean isValidMove(Move move) {
|
||||
boolean step = move.isVertical() && move.yDist == 1;
|
||||
boolean doubleStep = move.isVertical() && move.yDist == 2;
|
||||
boolean strafe = move.isDiagonal() && move.xDist == 1;
|
||||
boolean enPassant = false;
|
||||
if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
|
||||
else doubleStep &= move.pos.y == 1;
|
||||
boolean step = move.isVertical() && move.getyDist() == 1;
|
||||
boolean doubleStep = move.isVertical() && move.getyDist() == 2;
|
||||
boolean strafe = move.isDiagonal() && move.getxDist() == 1;
|
||||
boolean enPassant = strafe && move.getDest().equals(board.getLog().getEnPassant());
|
||||
if (getColor() == Color.WHITE) doubleStep &= move.getPos().y == 6;
|
||||
else doubleStep &= move.getPos().y == 1;
|
||||
|
||||
// Mark move as pawn promotion if necessary
|
||||
if (move.ySign == 1 && move.pos.y == 6 || move.ySign == -1 && move.pos.y == 1)
|
||||
move.type = Move.Type.PAWN_PROMOTION;
|
||||
|
||||
// Mark the move as en passant if necessary
|
||||
if (strafe && move.dest.equals(board.getEnPassantSquare())) {
|
||||
enPassant = true;
|
||||
move.type = Move.Type.EN_PASSANT;
|
||||
}
|
||||
|
||||
return enPassant || (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1)
|
||||
&& isFreePath(move);
|
||||
return enPassant || (step ^ doubleStep ^ strafe) && move.getySign() == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isFreePath(Move move) {
|
||||
// Two steps forward
|
||||
if (move.yDist == 2)
|
||||
return board.getBoardArr()[move.pos.x][move.dest.y - move.ySign] == null && board.getDest(move) == null;
|
||||
if (move.getyDist() == 2)
|
||||
return board.getBoardArr()[move.getPos().x][move.getDest().y - move.getySign()] == null && board.getDest(move) == null;
|
||||
// One step forward
|
||||
else if (move.xDist == 0) return board.getDest(move) == null;
|
||||
else if (move.getxDist() == 0) return board.getDest(move) == null;
|
||||
// Capture move
|
||||
else return board.getDest(move) != null && board.getDest(move).getColor() != getColor();
|
||||
}
|
||||
@ -52,46 +41,46 @@ public class Pawn extends Piece {
|
||||
@Override
|
||||
protected List<Move> getPseudolegalMoves(Position pos) {
|
||||
List<Move> moves = new ArrayList<>();
|
||||
|
||||
int sign = getColor() == Color.WHITE ? -1 : 1;
|
||||
boolean pawnPromotion = sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1;
|
||||
|
||||
// Strafe left
|
||||
if (pos.x > 0) {
|
||||
Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
|
||||
if (isFreePath(move)) moves.add(move);
|
||||
}
|
||||
if (pos.x > 0) addMoveIfValid(moves, pos, new Position(pos.x - 1, pos.y + sign), pawnPromotion);
|
||||
|
||||
// Strafe right
|
||||
if (pos.x < 7) {
|
||||
Move move = new Move(pos, new Position(pos.x + 1, pos.y + sign));
|
||||
if (isFreePath(move)) moves.add(move);
|
||||
}
|
||||
if (pos.x < 7) addMoveIfValid(moves, pos, new Position(pos.x + 1, pos.y + sign), pawnPromotion);
|
||||
|
||||
// Step forward
|
||||
if (sign == 1 && pos.y < 7 || sign == -1 && pos.y > 0) {
|
||||
Move move = new Move(pos, new Position(pos.x, pos.y + sign));
|
||||
if (isFreePath(move)) moves.add(move);
|
||||
}
|
||||
if (sign == 1 && pos.y < 7 || sign == -1 && pos.y > 0) addMoveIfValid(moves, pos, new Position(pos.x, pos.y + sign), pawnPromotion);
|
||||
|
||||
// Double step forward
|
||||
if (sign == 1 && pos.y == 1 || sign == -1 && pos.y == 6) {
|
||||
Move move = new Move(pos, new Position(pos.x, pos.y + 2 * sign));
|
||||
if (isFreePath(move)) moves.add(move);
|
||||
}
|
||||
|
||||
// Mark moves as pawn promotion if necessary
|
||||
if (sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1)
|
||||
moves.parallelStream().forEach(m -> m.type = Move.Type.PAWN_PROMOTION);
|
||||
if (sign == 1 && pos.y == 1 || sign == -1 && pos.y == 6) addMoveIfValid(moves, pos, new Position(pos.x, pos.y + 2 * sign), pawnPromotion);
|
||||
|
||||
// Add en passant move if necessary
|
||||
if (board.getEnPassantSquare() != null) {
|
||||
Move move = new Move(pos, board.getEnPassantSquare(), Move.Type.EN_PASSANT);
|
||||
if (move.isDiagonal() && move.xDist == 1) moves.add(move);
|
||||
if (board.getLog().getEnPassant() != null) {
|
||||
Move move = new EnPassant(pos, board.getLog().getEnPassant());
|
||||
if (move.isDiagonal() && move.getxDist() == 1) moves.add(move);
|
||||
}
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
private void addMoveIfValid(List<Move> moves, Position pos, Position dest, boolean pawnPromotion) {
|
||||
Move move = new Move(pos, dest);
|
||||
if (isFreePath(move)) {
|
||||
if (pawnPromotion) {
|
||||
try {
|
||||
moves.add(new PawnPromotion(pos, dest, Queen.class));
|
||||
moves.add(new PawnPromotion(pos, dest, Rook.class));
|
||||
moves.add(new PawnPromotion(pos, dest, Knight.class));
|
||||
moves.add(new PawnPromotion(pos, dest, Bishop.class));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else moves.add(move);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() { return Type.PAWN; }
|
||||
public int getValue() { return 10; }
|
||||
}
|
||||
|
80
src/dev/kske/chess/board/PawnPromotion.java
Normal file
80
src/dev/kske/chess/board/PawnPromotion.java
Normal file
@ -0,0 +1,80 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Objects;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>PawnPromotion.java</strong><br>
|
||||
* Created: <strong>2 Nov 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class PawnPromotion extends Move {
|
||||
|
||||
private final Constructor<? extends Piece> promotionPieceConstructor;
|
||||
private final char promotionPieceChar;
|
||||
|
||||
public PawnPromotion(Position pos, Position dest, Class<? extends Piece> promotionPieceClass)
|
||||
throws ReflectiveOperationException, RuntimeException {
|
||||
super(pos, dest);
|
||||
|
||||
// Cache piece constructor
|
||||
promotionPieceConstructor = promotionPieceClass.getDeclaredConstructor(Color.class, Board.class);
|
||||
promotionPieceConstructor.setAccessible(true);
|
||||
|
||||
// Get piece char
|
||||
promotionPieceChar = (char) promotionPieceClass.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null));
|
||||
}
|
||||
|
||||
public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class<? extends Piece> promotionPiece)
|
||||
throws ReflectiveOperationException, RuntimeException {
|
||||
this(new Position(xPos, yPos), new Position(xDest, yDest), promotionPiece);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Board board) {
|
||||
try {
|
||||
board.set(pos, promotionPieceConstructor.newInstance(board.get(pos).getColor(), board));
|
||||
super.execute(board);
|
||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revert(Board board, Piece capturedPiece) {
|
||||
board.set(dest, new Pawn(board.get(dest).getColor(), board));
|
||||
super.revert(board, capturedPiece);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toLAN() { return pos.toLAN() + dest.toLAN() + promotionPieceChar; }
|
||||
|
||||
@Override
|
||||
public String toSAN(Board board) {
|
||||
String san = super.toSAN(board);
|
||||
return san + Character.toUpperCase(promotionPieceChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + Objects.hash(promotionPieceChar, promotionPieceConstructor);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!super.equals(obj)) return false;
|
||||
if (!(obj instanceof PawnPromotion)) return false;
|
||||
PawnPromotion other = (PawnPromotion) obj;
|
||||
return promotionPieceChar == other.promotionPieceChar && Objects.equals(promotionPieceConstructor, other.promotionPieceConstructor);
|
||||
}
|
||||
}
|
@ -2,18 +2,20 @@ package dev.kske.chess.board;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Piece.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public abstract class Piece implements Cloneable {
|
||||
|
||||
private final Color color;
|
||||
protected Board board;
|
||||
private int moveCounter;
|
||||
|
||||
public Piece(Color color, Board board) {
|
||||
this.color = color;
|
||||
@ -42,8 +44,8 @@ public abstract class Piece implements Cloneable {
|
||||
* @param move The move to check
|
||||
*/
|
||||
protected boolean isFreePath(Move move) {
|
||||
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)
|
||||
for (int i = move.getPos().x + move.getxSign(), j = move.getPos().y + move.getySign(); i != move.getDest().x
|
||||
|| j != move.getDest().y; i += move.getxSign(), j += move.getySign())
|
||||
if (board.getBoardArr()[i][j] != null) return false;
|
||||
return checkDestination(move);
|
||||
}
|
||||
@ -55,9 +57,7 @@ public abstract class Piece implements Cloneable {
|
||||
* @param move The move to check
|
||||
* @return {@code false} if the move's destination is from the same team
|
||||
*/
|
||||
protected final boolean checkDestination(Move move) {
|
||||
return board.getDest(move) == null || board.getDest(move).getColor() != getColor();
|
||||
}
|
||||
protected final boolean checkDestination(Move move) { return board.getDest(move) == null || board.getDest(move).getColor() != getColor(); }
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
@ -70,33 +70,65 @@ public abstract class Piece implements Cloneable {
|
||||
return piece;
|
||||
}
|
||||
|
||||
public abstract Type getType();
|
||||
@Override
|
||||
public String toString() { return getClass().getSimpleName(); }
|
||||
|
||||
@Override
|
||||
public int hashCode() { return Objects.hash(color); }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Piece other = (Piece) obj;
|
||||
return color == other.color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the standard value of this {@link Piece} that can be used for board
|
||||
* evaluation
|
||||
*/
|
||||
public abstract int getValue();
|
||||
|
||||
/**
|
||||
* @return The first character of this {@link Piece} in algebraic notation and
|
||||
* lower case
|
||||
*/
|
||||
public char firstChar() { return Character.toLowerCase(toString().charAt(0)); }
|
||||
|
||||
public static Class<? extends Piece> fromFirstChar(char firstChar) {
|
||||
switch (Character.toLowerCase(firstChar)) {
|
||||
case 'k':
|
||||
return King.class;
|
||||
case 'q':
|
||||
return Queen.class;
|
||||
case 'r':
|
||||
return Rook.class;
|
||||
case 'n':
|
||||
return Knight.class;
|
||||
case 'b':
|
||||
return Bishop.class;
|
||||
case 'p':
|
||||
return Pawn.class;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Color} of this {@link Piece}
|
||||
*/
|
||||
public Color getColor() { return color; }
|
||||
|
||||
public int getMoveCounter() { return moveCounter; }
|
||||
|
||||
public void incMoveCounter() {
|
||||
++moveCounter;
|
||||
}
|
||||
|
||||
public void decMoveCounter() {
|
||||
--moveCounter;
|
||||
}
|
||||
|
||||
public static enum Type {
|
||||
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN
|
||||
}
|
||||
|
||||
public static enum Color {
|
||||
|
||||
WHITE, BLACK;
|
||||
|
||||
public Color opposite() {
|
||||
return this == WHITE ? BLACK : WHITE;
|
||||
}
|
||||
public static Color fromFirstChar(char c) { return Character.toLowerCase(c) == 'w' ? WHITE : BLACK; }
|
||||
|
||||
public char firstChar() {
|
||||
return this == WHITE ? 'w' : 'b';
|
||||
}
|
||||
public char firstChar() { return this == WHITE ? 'w' : 'b'; }
|
||||
|
||||
public Color opposite() { return this == WHITE ? BLACK : WHITE; }
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ package dev.kske.chess.board;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Position.java</strong><br>
|
||||
* Created: <strong>02.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Position {
|
||||
|
||||
@ -15,11 +17,11 @@ public class Position {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public static Position fromSAN(String pos) {
|
||||
public static Position fromLAN(String pos) {
|
||||
return new Position(pos.charAt(0) - 97, 8 - Character.getNumericValue(pos.charAt(1)));
|
||||
}
|
||||
|
||||
public String toSAN() {
|
||||
public String toLAN() {
|
||||
return String.valueOf((char) (x + 97)) + String.valueOf(8 - y);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,9 @@ import java.util.List;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Queen.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Queen extends Piece {
|
||||
|
||||
@ -99,5 +101,5 @@ public class Queen extends Piece {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() { return Type.QUEEN; }
|
||||
public int getValue() { return 90; }
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ import java.util.List;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Rook.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Rook extends Piece {
|
||||
|
||||
@ -63,5 +65,5 @@ public class Rook extends Piece {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() { return Type.ROOK; }
|
||||
public int getValue() { return 50; }
|
||||
}
|
||||
|
17
src/dev/kske/chess/event/Event.java
Normal file
17
src/dev/kske/chess/event/Event.java
Normal file
@ -0,0 +1,17 @@
|
||||
package dev.kske.chess.event;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Event.java</strong><br>
|
||||
* Created: <strong>7 Aug 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.4-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public interface Event<T> {
|
||||
|
||||
/**
|
||||
* @return The data associated with the event
|
||||
*/
|
||||
T getData();
|
||||
}
|
38
src/dev/kske/chess/event/EventBus.java
Normal file
38
src/dev/kske/chess/event/EventBus.java
Normal file
@ -0,0 +1,38 @@
|
||||
package dev.kske.chess.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>EventBus.java</strong><br>
|
||||
* Created: <strong>7 Aug 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.4-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class EventBus {
|
||||
|
||||
private List<Subscribable> subscribers;
|
||||
|
||||
private static EventBus instance;
|
||||
|
||||
public static EventBus getInstance() {
|
||||
if (instance == null) instance = new EventBus();
|
||||
return instance;
|
||||
}
|
||||
|
||||
private EventBus() {
|
||||
subscribers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void register(Subscribable subscribable) {
|
||||
subscribers.add(subscribable);
|
||||
}
|
||||
|
||||
public void dispatch(Event<?> event) {
|
||||
subscribers.stream().filter(e -> e.supports().contains(event.getClass())).forEach(e -> e.handle(event));
|
||||
}
|
||||
|
||||
public List<Subscribable> getSubscribers() { return subscribers; }
|
||||
}
|
21
src/dev/kske/chess/event/GameStartEvent.java
Normal file
21
src/dev/kske/chess/event/GameStartEvent.java
Normal file
@ -0,0 +1,21 @@
|
||||
package dev.kske.chess.event;
|
||||
|
||||
import dev.kske.chess.game.Game;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>GameStartEvent.java</strong><br>
|
||||
* Created: <strong>30 Oct 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class GameStartEvent implements Event<Game> {
|
||||
|
||||
private final Game game;
|
||||
|
||||
public GameStartEvent(Game source) { game = source; }
|
||||
|
||||
@Override
|
||||
public Game getData() { return game; }
|
||||
}
|
28
src/dev/kske/chess/event/MoveEvent.java
Normal file
28
src/dev/kske/chess/event/MoveEvent.java
Normal file
@ -0,0 +1,28 @@
|
||||
package dev.kske.chess.event;
|
||||
|
||||
import dev.kske.chess.board.BoardState;
|
||||
import dev.kske.chess.board.Move;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>MoveEvent.java</strong><br>
|
||||
* Created: <strong>7 Aug 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.4-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class MoveEvent implements Event<Move> {
|
||||
|
||||
private final Move move;
|
||||
private final BoardState boardState;
|
||||
|
||||
public MoveEvent(Move move, BoardState boardState) {
|
||||
this.move = move;
|
||||
this.boardState = boardState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Move getData() { return move; }
|
||||
|
||||
public BoardState getBoardState() { return boardState; }
|
||||
}
|
26
src/dev/kske/chess/event/Subscribable.java
Normal file
26
src/dev/kske/chess/event/Subscribable.java
Normal file
@ -0,0 +1,26 @@
|
||||
package dev.kske.chess.event;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Subscribable.java</strong><br>
|
||||
* Created: <strong>7 Aug 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.4-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public interface Subscribable {
|
||||
|
||||
/**
|
||||
* Consumes an event dispatched by an event bus.
|
||||
*
|
||||
* @param event The event dispatched by the event bus, only of supported type
|
||||
*/
|
||||
void handle(Event<?> event);
|
||||
|
||||
/**
|
||||
* @return A set of classes this class is supposed to handle in events
|
||||
*/
|
||||
Set<Class<?>> supports();
|
||||
}
|
26
src/dev/kske/chess/exception/ChessException.java
Normal file
26
src/dev/kske/chess/exception/ChessException.java
Normal file
@ -0,0 +1,26 @@
|
||||
package dev.kske.chess.exception;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>ChessException.java</strong><br>
|
||||
* Created: <strong>22 Sep 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class ChessException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -2208596063548245189L;
|
||||
|
||||
public ChessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ChessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ChessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -6,36 +6,52 @@ import java.util.Map;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import dev.kske.chess.board.Board;
|
||||
import dev.kske.chess.board.GameState;
|
||||
import dev.kske.chess.board.BoardState;
|
||||
import dev.kske.chess.board.Move;
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.event.EventBus;
|
||||
import dev.kske.chess.event.GameStartEvent;
|
||||
import dev.kske.chess.event.MoveEvent;
|
||||
import dev.kske.chess.game.ai.AIPlayer;
|
||||
import dev.kske.chess.io.EngineUtil;
|
||||
import dev.kske.chess.io.EngineUtil.EngineInfo;
|
||||
import dev.kske.chess.ui.BoardComponent;
|
||||
import dev.kske.chess.ui.BoardPane;
|
||||
import dev.kske.chess.ui.EngineUtil;
|
||||
import dev.kske.chess.ui.EngineUtil.EngineInfo;
|
||||
import dev.kske.chess.ui.OverlayComponent;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Game.java</strong><br>
|
||||
* Created: <strong>06.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class Game {
|
||||
|
||||
private Map<Color, Player> players;
|
||||
private Map<Color, Player> players = new HashMap<>();
|
||||
private Board board;
|
||||
private OverlayComponent overlayComponent;
|
||||
private BoardComponent boardComponent;
|
||||
|
||||
public Game(BoardPane boardPane, String whiteName, String blackName) {
|
||||
players = new HashMap<>();
|
||||
board = new Board();
|
||||
init(boardPane, whiteName, blackName);
|
||||
}
|
||||
|
||||
public Game(BoardPane boardPane, String whiteName, String blackName, Board board) {
|
||||
this.board = board;
|
||||
init(boardPane, whiteName, blackName);
|
||||
}
|
||||
|
||||
private void init(BoardPane boardPane, String whiteName, String blackName) {
|
||||
|
||||
// Initialize / synchronize UI
|
||||
overlayComponent = boardPane.getOverlayComponent();
|
||||
boardComponent = boardPane.getBoardComponent();
|
||||
boardComponent.setBoard(board);
|
||||
|
||||
// Initialize players
|
||||
players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE));
|
||||
players.put(Color.BLACK, getPlayer(blackName, Color.BLACK));
|
||||
|
||||
@ -59,47 +75,54 @@ public class Game {
|
||||
|
||||
public void onMove(Player player, Move move) {
|
||||
if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
|
||||
|
||||
// Redraw
|
||||
boardComponent.repaint();
|
||||
overlayComponent.displayArrow(move);
|
||||
|
||||
System.out.printf("%s: %s%n", player.color, move);
|
||||
System.out.println("FEN: " + board.toFEN());
|
||||
GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite());
|
||||
switch (eventType) {
|
||||
// Run garbage collection
|
||||
System.gc();
|
||||
|
||||
BoardState boardState = board.getGameEventType(board.getDest(move).getColor().opposite());
|
||||
EventBus.getInstance().dispatch(new MoveEvent(move, boardState));
|
||||
switch (boardState) {
|
||||
case CHECKMATE:
|
||||
case STALEMATE:
|
||||
String result = String.format("%s in %s!%n", player.color.opposite(), eventType);
|
||||
String result = String.format("%s in %s!%n", player.color.opposite(), boardState);
|
||||
System.out.print(result);
|
||||
JOptionPane.showMessageDialog(boardComponent, result);
|
||||
break;
|
||||
case CHECK:
|
||||
System.out.printf("%s in check!%n", player.color.opposite());
|
||||
default:
|
||||
players.get(board.getActiveColor()).requestMove();
|
||||
players.get(board.getLog().getActiveColor()).requestMove();
|
||||
}
|
||||
} else player.requestMove();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
players.get(Color.WHITE).requestMove();
|
||||
EventBus.getInstance().dispatch(new GameStartEvent(this));
|
||||
players.get(board.getLog().getActiveColor()).requestMove();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
players.values().forEach(Player::cancelMove);
|
||||
board.initializeDefaultPositions();
|
||||
board.initDefaultPositions();
|
||||
boardComponent.repaint();
|
||||
overlayComponent.clearDots();
|
||||
overlayComponent.clearArrow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed all connections between the game and the UI.
|
||||
* Stops the game by disconnecting its players form the UI.
|
||||
*/
|
||||
public void disconnect() {
|
||||
public void stop() {
|
||||
players.values().forEach(Player::disconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the players their opposite colors.
|
||||
*/
|
||||
public void swapColors() {
|
||||
players.values().forEach(Player::cancelMove);
|
||||
Player white = players.get(Color.WHITE);
|
||||
@ -108,10 +131,16 @@ public class Game {
|
||||
black.setColor(Color.WHITE);
|
||||
players.put(Color.WHITE, black);
|
||||
players.put(Color.BLACK, white);
|
||||
players.get(board.getActiveColor()).requestMove();
|
||||
players.get(board.getLog().getActiveColor()).requestMove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The board on which this game's moves are made
|
||||
*/
|
||||
public Board getBoard() { return board; }
|
||||
|
||||
/**
|
||||
* @return The players participating in this game
|
||||
*/
|
||||
public Map<Color, Player> getPlayers() { return players; }
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ import java.awt.event.MouseListener;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import dev.kske.chess.board.Board;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import dev.kske.chess.board.Move;
|
||||
import dev.kske.chess.board.Move.Type;
|
||||
import dev.kske.chess.board.Piece;
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.board.Position;
|
||||
import dev.kske.chess.ui.OverlayComponent;
|
||||
@ -16,14 +18,17 @@ import dev.kske.chess.ui.OverlayComponent;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>NaturalPlayer.java</strong><br>
|
||||
* Created: <strong>06.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class NaturalPlayer extends Player implements MouseListener {
|
||||
|
||||
private final OverlayComponent overlayComponent;
|
||||
|
||||
private boolean moveRequested;
|
||||
private Position pos;
|
||||
private Piece selectedPiece;
|
||||
private List<Move> possibleMoves;
|
||||
|
||||
public NaturalPlayer(Color color, OverlayComponent overlayComponent) {
|
||||
super(color);
|
||||
@ -35,43 +40,63 @@ public class NaturalPlayer extends Player implements MouseListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestMove() {
|
||||
moveRequested = true;
|
||||
}
|
||||
public void requestMove() { moveRequested = true; }
|
||||
|
||||
@Override
|
||||
public void cancelMove() {
|
||||
moveRequested = false;
|
||||
}
|
||||
public void cancelMove() { moveRequested = false; }
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
overlayComponent.removeMouseListener(this);
|
||||
}
|
||||
public void disconnect() { overlayComponent.removeMouseListener(this); }
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent evt) {
|
||||
if (!moveRequested) return;
|
||||
if (pos == null) {
|
||||
pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
|
||||
evt.getPoint().y / overlayComponent.getTileSize());
|
||||
if (selectedPiece == null) {
|
||||
|
||||
Board board = (Board) NaturalPlayer.this.board.clone();
|
||||
if (board.get(pos) != null && board.get(pos).getColor() == color) {
|
||||
List<Position> positions = board.getMoves(pos)
|
||||
.stream()
|
||||
.map(move -> move.dest)
|
||||
.collect(Collectors.toList());
|
||||
overlayComponent.displayDots(positions);
|
||||
} else pos = null;
|
||||
// Get selected Piece
|
||||
final Position pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
|
||||
selectedPiece = board.get(pos);
|
||||
|
||||
// Check if a piece was selected
|
||||
if (selectedPiece != null) {
|
||||
|
||||
// Discard selection if the piece has the wrong color
|
||||
if (selectedPiece.getColor() == color.opposite()) selectedPiece = null;
|
||||
else {
|
||||
|
||||
// Generate all moves possible with the selected piece and display their
|
||||
// destinations
|
||||
possibleMoves = selectedPiece.getMoves(pos);
|
||||
overlayComponent.displayDots(possibleMoves.stream().map(move -> move.getDest()).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
|
||||
evt.getPoint().y / overlayComponent.getTileSize());
|
||||
Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
|
||||
|
||||
overlayComponent.clearDots();
|
||||
// Get all moves leading to the specified destination
|
||||
List<Move> selectedMoves = possibleMoves.stream().filter(m -> m.getDest().equals(dest)).collect(Collectors.toList());
|
||||
if (!selectedMoves.isEmpty()) {
|
||||
Move move;
|
||||
|
||||
// Process pawn promotion if necessary
|
||||
if (selectedMoves.size() > 1) {
|
||||
|
||||
// Let the user select a promotion piece
|
||||
JComboBox<Move> comboBox = new JComboBox<Move>(selectedMoves.toArray(new Move[0]));
|
||||
JOptionPane.showMessageDialog(overlayComponent, comboBox, "Select a promotion", JOptionPane.QUESTION_MESSAGE);
|
||||
|
||||
move = selectedMoves.get(comboBox.getSelectedIndex());
|
||||
} else move = selectedMoves.get(0);
|
||||
|
||||
// Tell the game to execute the move
|
||||
moveRequested = false;
|
||||
game.onMove(NaturalPlayer.this, new Move(pos, dest, Type.UNKNOWN));
|
||||
pos = null;
|
||||
game.onMove(NaturalPlayer.this, move);
|
||||
}
|
||||
|
||||
// Discard the selection
|
||||
overlayComponent.clearDots();
|
||||
selectedPiece = null;
|
||||
possibleMoves = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,9 @@ import dev.kske.chess.board.Piece.Color;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Player.java</strong><br>
|
||||
* Created: <strong>06.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public abstract class Player {
|
||||
|
||||
|
@ -2,6 +2,7 @@ package dev.kske.chess.game;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import dev.kske.chess.board.FENString;
|
||||
import dev.kske.chess.board.Move;
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.uci.UCIHandle;
|
||||
@ -11,7 +12,9 @@ import dev.kske.chess.uci.UCIListener;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>UCIPlayer.java</strong><br>
|
||||
* Created: <strong>18.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.3-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class UCIPlayer extends Player implements UCIListener {
|
||||
|
||||
@ -30,7 +33,7 @@ public class UCIPlayer extends Player implements UCIListener {
|
||||
|
||||
@Override
|
||||
public void requestMove() {
|
||||
handle.positionFEN(board.toFEN());
|
||||
handle.positionFEN(new FENString(board).toString());
|
||||
handle.go();
|
||||
}
|
||||
|
||||
@ -51,7 +54,7 @@ public class UCIPlayer extends Player implements UCIListener {
|
||||
|
||||
@Override
|
||||
public void onBestMove(String move) {
|
||||
Move moveObj = Move.fromSAN(move);
|
||||
Move moveObj = Move.fromLAN(move);
|
||||
game.onMove(this, moveObj);
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,9 @@ import dev.kske.chess.game.Player;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>AIPlayer.java</strong><br>
|
||||
* Created: <strong>06.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class AIPlayer extends Player {
|
||||
|
||||
@ -50,7 +52,7 @@ public class AIPlayer extends Player {
|
||||
/*
|
||||
* Get a copy of the board and the available moves.
|
||||
*/
|
||||
Board board = (Board) AIPlayer.this.board.clone();
|
||||
Board board = new Board(this.board, false);
|
||||
List<Move> moves = board.getMoves(color);
|
||||
|
||||
/*
|
||||
@ -64,7 +66,7 @@ public class AIPlayer extends Player {
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
if (rem-- > 0) ++endIndex;
|
||||
endIndex += step;
|
||||
processors.add(new MoveProcessor((Board) board.clone(), moves.subList(beginIndex, endIndex), color,
|
||||
processors.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color,
|
||||
maxDepth, alphaBetaThreshold));
|
||||
beginIndex = endIndex;
|
||||
}
|
||||
|
@ -1,17 +1,28 @@
|
||||
package dev.kske.chess.game.ai;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import dev.kske.chess.board.Bishop;
|
||||
import dev.kske.chess.board.Board;
|
||||
import dev.kske.chess.board.King;
|
||||
import dev.kske.chess.board.Knight;
|
||||
import dev.kske.chess.board.Move;
|
||||
import dev.kske.chess.board.Pawn;
|
||||
import dev.kske.chess.board.Piece;
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.board.Queen;
|
||||
import dev.kske.chess.board.Rook;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>MoveProcessor.java</strong><br>
|
||||
* Created: <strong>08.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class MoveProcessor implements Callable<ProcessingResult> {
|
||||
|
||||
@ -23,6 +34,40 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
||||
|
||||
private Move bestMove;
|
||||
|
||||
private static final Map<Class<? extends Piece>, int[][]> positionScores;
|
||||
|
||||
static {
|
||||
positionScores = new HashMap<>();
|
||||
positionScores.put(King.class,
|
||||
new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
|
||||
new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
|
||||
new int[] { -2, -3, -3, -2, -2, -2, -2, -1 }, new int[] { -1, -2, -2, -2, -2, -2, -2, -1 },
|
||||
new int[] { 2, 2, 0, 0, 0, 0, 2, 2 }, new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
|
||||
positionScores.put(Queen.class,
|
||||
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, -2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { 0, 0, 1, 1, 1, 1, 0, -1 },
|
||||
new int[] { -1, 1, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 0, 0, 0, 0, -1 },
|
||||
new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
|
||||
positionScores.put(Rook.class,
|
||||
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
|
||||
positionScores.put(Knight.class,
|
||||
new int[][] { new int[] { -5, -4, -3, -3, -3, -3, -4, -5 }, new int[] { -4, -2, 0, 0, 0, 0, -2, -4 },
|
||||
new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 }, new int[] { -3, 0, 2, 2, 2, 2, 0, -1 },
|
||||
new int[] { -3, 1, 1, 2, 2, 1, 1, -3 }, new int[] { -4, -2, 0, 1, 1, 0, -2, -4 },
|
||||
new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
|
||||
positionScores.put(Bishop.class,
|
||||
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, 2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
|
||||
new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 1, 0, 0, 0, 0, 1, -1 },
|
||||
new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
|
||||
positionScores.put(Pawn.class,
|
||||
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 }, new int[] { 1, 1, 2, 3, 3, 2, 1, 1 },
|
||||
new int[] { 0, 0, 1, 3, 3, 1, 0, 0 }, new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
|
||||
new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } });
|
||||
}
|
||||
|
||||
public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth, int alphaBetaThreshold) {
|
||||
this.board = board;
|
||||
this.rootMoves = rootMoves;
|
||||
@ -41,8 +86,8 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
||||
int bestValue = Integer.MIN_VALUE;
|
||||
for (Move move : moves) {
|
||||
board.move(move);
|
||||
int teamValue = board.evaluate(color);
|
||||
int enemyValue = board.evaluate(color.opposite());
|
||||
int teamValue = evaluate(board, color);
|
||||
int enemyValue = evaluate(board, color.opposite());
|
||||
int valueChange = teamValue - enemyValue;
|
||||
|
||||
if (depth < maxDepth && valueChange >= alphaBetaThreshold)
|
||||
@ -57,4 +102,22 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
||||
}
|
||||
return bestValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluated a board.
|
||||
*
|
||||
* @param color The color to evaluate for
|
||||
* @return An positive number representing how good the position is
|
||||
*/
|
||||
private int evaluate(Board board, Color color) {
|
||||
int score = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (board.getBoardArr()[i][j] != null && board.getBoardArr()[i][j].getColor() == color) {
|
||||
score += board.getBoardArr()[i][j].getValue();
|
||||
if (positionScores.containsKey(board.getBoardArr()[i][j].getClass()))
|
||||
score += positionScores.get(board.getBoardArr()[i][j].getClass())[i][color == Color.WHITE ? j : 7 - j];
|
||||
}
|
||||
return score;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import dev.kske.chess.board.Move;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>ProcessingResult.java</strong><br>
|
||||
* Created: <strong>08.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class ProcessingResult {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.kske.chess.ui;
|
||||
package dev.kske.chess.io;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
@ -16,7 +16,10 @@ import dev.kske.chess.uci.UCIListener;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>MenuBar.java</strong><br>
|
||||
* Created: <strong>23.07.2019</strong><br>
|
||||
* Author: <strong>Leon Hofmeister</strong>
|
||||
*
|
||||
* @since Chess v0.2-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Leon Hofmeister
|
||||
*/
|
||||
public class EngineUtil {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.kske.chess.ui;
|
||||
package dev.kske.chess.io;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
@ -15,15 +15,15 @@ import dev.kske.chess.board.Piece;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>TextureUtil.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class TextureUtil {
|
||||
|
||||
private static Map<String, Image> textures, scaledTextures;
|
||||
private static Map<String, Image> textures = new HashMap<>(), scaledTextures = new HashMap<>();
|
||||
|
||||
static {
|
||||
textures = new HashMap<>();
|
||||
scaledTextures = new HashMap<>();
|
||||
loadPieceTextures();
|
||||
scaledTextures.putAll(textures);
|
||||
}
|
||||
@ -31,23 +31,24 @@ public class TextureUtil {
|
||||
private TextureUtil() {}
|
||||
|
||||
/**
|
||||
* Loads a piece texture fitting to a piece object
|
||||
* Loads a piece texture fitting to a piece object.
|
||||
*
|
||||
* @param piece The piece from which the texture properties are taken
|
||||
* @return The fitting texture
|
||||
*/
|
||||
public static Image getPieceTexture(Piece piece) {
|
||||
String key = piece.getType().toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
|
||||
String key = piece.toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
|
||||
return scaledTextures.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales all piece textures to fit the current tile size
|
||||
* Scales all piece textures to fit the current tile size.
|
||||
*
|
||||
* @param tileSize the new width and height of the piece textures
|
||||
*/
|
||||
public static void scalePieceTextures(int scale) {
|
||||
public static void scalePieceTextures(int tileSize) {
|
||||
scaledTextures.clear();
|
||||
textures
|
||||
.forEach((key, img) -> scaledTextures.put(key, img.getScaledInstance(scale, scale, Image.SCALE_SMOOTH)));
|
||||
textures.forEach((key, img) -> scaledTextures.put(key, img.getScaledInstance(tileSize, tileSize, Image.SCALE_SMOOTH)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,5 +87,4 @@ public class TextureUtil {
|
||||
"pawn_black")
|
||||
.forEach(name -> textures.put(name, loadImage("/pieces/" + name + ".png")));
|
||||
}
|
||||
|
||||
}
|
56
src/dev/kske/chess/pgn/PGNDatabase.java
Normal file
56
src/dev/kske/chess/pgn/PGNDatabase.java
Normal file
@ -0,0 +1,56 @@
|
||||
package dev.kske.chess.pgn;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
import dev.kske.chess.exception.ChessException;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>PGNDatabase.java</strong><br>
|
||||
* Created: <strong>4 Oct 2019</strong><br>
|
||||
* <br>
|
||||
* Contains a series of {@link PGNGame} objects that can be stored inside a PGN
|
||||
* file.
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class PGNDatabase {
|
||||
|
||||
private final List<PGNGame> games = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Loads PGN games from a file.
|
||||
*
|
||||
* @param pgnFile the file to load the games from
|
||||
* @throws FileNotFoundException if the specified file is not found
|
||||
* @throws ChessException if an error occurs while parsing the file
|
||||
*/
|
||||
public void load(File pgnFile) throws FileNotFoundException, ChessException {
|
||||
Scanner sc = new Scanner(pgnFile);
|
||||
while (sc.hasNext())
|
||||
games.add(PGNGame.parse(sc));
|
||||
sc.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves PGN games to a file.
|
||||
*
|
||||
* @param pgnFile the file to save the games to.
|
||||
* @throws IOException if the file could not be created
|
||||
*/
|
||||
public void save(File pgnFile) throws IOException {
|
||||
pgnFile.getParentFile().mkdirs();
|
||||
PrintWriter pw = new PrintWriter(pgnFile);
|
||||
games.forEach(g -> g.writePGN(pw));
|
||||
pw.close();
|
||||
}
|
||||
|
||||
public List<PGNGame> getGames() { return games; }
|
||||
}
|
121
src/dev/kske/chess/pgn/PGNGame.java
Normal file
121
src/dev/kske/chess/pgn/PGNGame.java
Normal file
@ -0,0 +1,121 @@
|
||||
package dev.kske.chess.pgn;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import dev.kske.chess.board.Board;
|
||||
import dev.kske.chess.board.FENString;
|
||||
import dev.kske.chess.board.Move;
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.exception.ChessException;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>PGNGame.java</strong><br>
|
||||
* Created: <strong>22 Sep 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class PGNGame {
|
||||
|
||||
private final Map<String, String> tagPairs = new HashMap<>(7);
|
||||
private final Board board;
|
||||
|
||||
public PGNGame() { board = new Board(); }
|
||||
|
||||
public PGNGame(Board board) { this.board = board; }
|
||||
|
||||
public static PGNGame parse(Scanner sc) throws ChessException {
|
||||
PGNGame game = new PGNGame();
|
||||
|
||||
MatchResult matchResult;
|
||||
Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"),
|
||||
movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"),
|
||||
nagPattern = Pattern.compile("(\\$\\d{1,3})*"), terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
|
||||
|
||||
// Parse tag pairs
|
||||
while (sc.findInLine(tagPairPattern) != null) {
|
||||
matchResult = sc.match();
|
||||
if (matchResult.groupCount() == 2) game.setTag(matchResult.group(1), matchResult.group(2));
|
||||
else break;
|
||||
sc.nextLine();
|
||||
}
|
||||
|
||||
// Parse movetext
|
||||
while (true) {
|
||||
// Skip NAG (Numeric Annotation Glyph)
|
||||
sc.skip(nagPattern);
|
||||
|
||||
// TODO: Parse RAV (Recursive Annotation Variation)
|
||||
|
||||
if (sc.findWithinHorizon(movePattern, 20) != null) {
|
||||
matchResult = sc.match();
|
||||
if (matchResult.groupCount() > 0) for (int i = 1; i < matchResult.groupCount() + 1; i++) {
|
||||
game.board.move(matchResult.group(i));
|
||||
System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + new FENString(game.board).toString());
|
||||
}
|
||||
else break;
|
||||
} else break;
|
||||
}
|
||||
|
||||
// Parse game termination marker
|
||||
if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) System.err.println("Termination marker expected");
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
public void writePGN(PrintWriter pw) {
|
||||
// Set the unknown result tag if no result tag is specified
|
||||
tagPairs.putIfAbsent("Result", "*");
|
||||
|
||||
// Write tag pairs
|
||||
tagPairs.forEach((k, v) -> pw.printf("[%s \"%s\"]%n", k, v));
|
||||
|
||||
// Insert newline if tags were printed
|
||||
if (!tagPairs.isEmpty()) pw.println();
|
||||
|
||||
if (!board.getLog().isEmpty()) {
|
||||
// Collect SAN moves
|
||||
Board clone = new Board(board, true);
|
||||
List<String> chunks = new ArrayList<>();
|
||||
boolean flag = true;
|
||||
while (flag) {
|
||||
Move move = clone.getLog().getLast().move;
|
||||
flag = clone.getLog().hasParent();
|
||||
clone.revert();
|
||||
String chunk = clone.getLog().getActiveColor() == Color.WHITE ? String.format(" %d. ", clone.getLog().getFullmoveNumber()) : " ";
|
||||
chunk += move.toSAN(clone);
|
||||
chunks.add(chunk);
|
||||
}
|
||||
Collections.reverse(chunks);
|
||||
|
||||
// Write movetext
|
||||
String line = "";
|
||||
for (String chunk : chunks)
|
||||
if (line.length() + chunk.length() <= 80) line += chunk;
|
||||
else {
|
||||
pw.println(line);
|
||||
line = chunk;
|
||||
}
|
||||
if (!line.isEmpty()) pw.println(line);
|
||||
}
|
||||
// Write game termination marker
|
||||
pw.print(tagPairs.get("Result"));
|
||||
}
|
||||
|
||||
public String getTag(String tagName) { return tagPairs.get(tagName); }
|
||||
|
||||
public boolean hasTag(String tagName) { return tagPairs.containsKey(tagName); }
|
||||
|
||||
public void setTag(String tagName, String tagValue) { tagPairs.put(tagName, tagValue); }
|
||||
|
||||
public Board getBoard() { return board; }
|
||||
}
|
@ -2,12 +2,18 @@ package dev.kske.chess.uci;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import dev.kske.chess.board.Move;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>UCIHandle.java</strong><br>
|
||||
* Created: <strong>18.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.3-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class UCIHandle {
|
||||
|
||||
@ -29,34 +35,26 @@ public class UCIHandle {
|
||||
/**
|
||||
* Tells the engine to use UCI.
|
||||
*/
|
||||
public void uci() {
|
||||
out.println("uci");
|
||||
}
|
||||
public void uci() { out.println("uci"); }
|
||||
|
||||
/**
|
||||
* Switches the debug mode of the engine on or off.
|
||||
*
|
||||
* @param debug Enables debugging if set to {@code true}, disables it otherwise
|
||||
*/
|
||||
public void debug(boolean debug) {
|
||||
out.println("debug " + (debug ? "on" : "off"));
|
||||
}
|
||||
public void debug(boolean debug) { out.println("debug " + (debug ? "on" : "off")); }
|
||||
|
||||
/**
|
||||
* Synchronized the engine with the GUI
|
||||
*/
|
||||
public void isready() {
|
||||
out.println("isready");
|
||||
}
|
||||
public void isready() { out.println("isready"); }
|
||||
|
||||
/**
|
||||
* Signifies a button press to the engine.
|
||||
*
|
||||
* @param name The name of the button
|
||||
*/
|
||||
public void setOption(String name) {
|
||||
out.println("setoption name " + name);
|
||||
}
|
||||
public void setOption(String name) { out.println("setoption name " + name); }
|
||||
|
||||
/**
|
||||
* Changes an internal parameter of the engine.
|
||||
@ -64,9 +62,7 @@ public class UCIHandle {
|
||||
* @param name The name of the parameter
|
||||
* @param value The value of the parameter
|
||||
*/
|
||||
public void setOption(String name, String value) {
|
||||
out.printf("setoption name %s value %s%n", name, value);
|
||||
}
|
||||
public void setOption(String name, String value) { out.printf("setoption name %s value %s%n", name, value); }
|
||||
|
||||
/**
|
||||
* Registers the engine
|
||||
@ -74,70 +70,131 @@ public class UCIHandle {
|
||||
* @param name The name the engine should be registered with
|
||||
* @param code The code the engine should be registered with
|
||||
*/
|
||||
public void register(String name, String code) {
|
||||
out.printf("register %s %s%n", name, code);
|
||||
}
|
||||
public void register(String name, String code) { out.printf("register %s %s%n", name, code); }
|
||||
|
||||
/**
|
||||
* Tells the engine to postpone the registration.
|
||||
*/
|
||||
public void registerLater() {
|
||||
out.println("register later");
|
||||
}
|
||||
public void registerLater() { out.println("register later"); }
|
||||
|
||||
/**
|
||||
* Tells the engine that the next search will be from a different game.
|
||||
*/
|
||||
public void uciNewGame() {
|
||||
out.println("ucinewgame");
|
||||
}
|
||||
|
||||
// TODO: position
|
||||
public void uciNewGame() { out.println("ucinewgame"); }
|
||||
|
||||
/**
|
||||
* Sets up the position in its initial state.
|
||||
*/
|
||||
public void startPosition() {
|
||||
out.println("position startpos");
|
||||
}
|
||||
public void positionStartpos() { out.println("position startpos"); }
|
||||
|
||||
/**
|
||||
* Sets up the position described in the FEN string.
|
||||
*
|
||||
* @param fen FEN representation of the current board
|
||||
*/
|
||||
public void positionFEN(String fen) {
|
||||
out.println("position fen " + fen);
|
||||
public void positionFEN(String fen) { out.println("position fen " + fen); }
|
||||
|
||||
/**
|
||||
* Sets up the position described by a list of moves.
|
||||
*
|
||||
* @param moves the moves to execute from the starting position to reach the
|
||||
* desired position
|
||||
*/
|
||||
public void positionMoves(List<Move> moves) {
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
moves.forEach(m -> joiner.add(m.toLAN()));
|
||||
out.println("position moves " + joiner);
|
||||
}
|
||||
|
||||
// TODO: go with parameters
|
||||
/**
|
||||
* Starts calculating on the current position.
|
||||
*/
|
||||
public void go() { out.println("go"); }
|
||||
|
||||
public void go() {
|
||||
out.println("go");
|
||||
/**
|
||||
* Starts calculating on the current position.
|
||||
* This command has multiple optional parameters which will only be included in
|
||||
* the call if they are not {@code null}, greater than zero or {@code true} for
|
||||
* {@code searchMoves}, all integer parameters and all boolean parameters
|
||||
* respectively.
|
||||
*
|
||||
* @param searchMoves restrict the search to these moves only
|
||||
* @param ponder start the search in ponder mode
|
||||
* @param wTime the amount of milliseconds left on white's clock
|
||||
* @param bTime the amount of milliseconds left on black's clocks
|
||||
* @param wInc white's increment per move in milliseconds
|
||||
* @param bInc black's increment per move in milliseconds
|
||||
* @param movesToGo the number of moves left until the next time control
|
||||
* @param depth the maximal amount of plies to search
|
||||
* @param nodes the maximal amount of nodes to search
|
||||
* @param mate the amount of moves in which to search for a mate
|
||||
* @param moveTime the exact search time
|
||||
* @param infinite search until the {@code stop} command
|
||||
*/
|
||||
public void go(List<Move> searchMoves, boolean ponder, int wTime, int bTime, int wInc, int bInc, int movesToGo, int depth, int nodes, int mate,
|
||||
int moveTime, boolean infinite) {
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
joiner.add("go");
|
||||
|
||||
if (searchMoves != null && !searchMoves.isEmpty()) {
|
||||
joiner.add("searchmoves");
|
||||
searchMoves.forEach(m -> joiner.add(m.toLAN()));
|
||||
}
|
||||
if (ponder) joiner.add("ponder");
|
||||
if (wTime > 0) {
|
||||
joiner.add("wtime");
|
||||
joiner.add(String.valueOf(wTime));
|
||||
}
|
||||
if (bTime > 0) {
|
||||
joiner.add("btime");
|
||||
joiner.add(String.valueOf(bTime));
|
||||
}
|
||||
if (wInc > 0) {
|
||||
joiner.add("winc");
|
||||
joiner.add(String.valueOf(wInc));
|
||||
}
|
||||
if (bInc > 0) {
|
||||
joiner.add("bind");
|
||||
joiner.add(String.valueOf(bInc));
|
||||
}
|
||||
if (movesToGo > 0) {
|
||||
joiner.add("movestogo");
|
||||
joiner.add(String.valueOf(movesToGo));
|
||||
}
|
||||
if (depth > 0) {
|
||||
joiner.add("depth");
|
||||
joiner.add(String.valueOf(depth));
|
||||
}
|
||||
if (nodes > 0) {
|
||||
joiner.add("nodes");
|
||||
joiner.add(String.valueOf(nodes));
|
||||
}
|
||||
if (mate > 0) {
|
||||
joiner.add("mate");
|
||||
joiner.add(String.valueOf(mate));
|
||||
}
|
||||
if (moveTime > 0) {
|
||||
joiner.add("movetime");
|
||||
joiner.add(String.valueOf(moveTime));
|
||||
}
|
||||
if (infinite) joiner.add("infinite");
|
||||
out.println(joiner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops calculation as soon as possible.
|
||||
*/
|
||||
public void stop() {
|
||||
out.println("stop");
|
||||
}
|
||||
public void stop() { out.println("stop"); }
|
||||
|
||||
/**
|
||||
* Tells the engine that the user has played the expected move.
|
||||
*/
|
||||
public void ponderHit() {
|
||||
out.println("ponderhit");
|
||||
}
|
||||
public void ponderHit() { out.println("ponderhit"); }
|
||||
|
||||
/**
|
||||
* Quits the engine process as soon as possible.
|
||||
*/
|
||||
public void quit() {
|
||||
out.println("quit");
|
||||
}
|
||||
public void quit() { out.println("quit"); }
|
||||
|
||||
public void setListener(UCIListener listener) {
|
||||
receiver.addListener(listener);
|
||||
}
|
||||
public void setListener(UCIListener listener) { receiver.addListener(listener); }
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ package dev.kske.chess.uci;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dev.kske.chess.board.Move;
|
||||
|
||||
@ -10,13 +12,15 @@ import dev.kske.chess.board.Move;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>UCIInfo.java</strong><br>
|
||||
* Created: <strong>28.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.3-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class UCIInfo {
|
||||
|
||||
private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload,
|
||||
cpunr;
|
||||
private List<Move> pv, refutation, currline;
|
||||
private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload, cpunr;
|
||||
private List<Move> pv = new ArrayList<>(), refutation = new ArrayList<>();
|
||||
private Map<Integer, List<Move>> currline = new HashMap<>();
|
||||
private Move currmove;
|
||||
private Score score;
|
||||
private String displayString;
|
||||
@ -44,9 +48,6 @@ public class UCIInfo {
|
||||
"currline");
|
||||
|
||||
public UCIInfo(String line) {
|
||||
pv = new ArrayList<>();
|
||||
refutation = new ArrayList<>();
|
||||
currline = new ArrayList<>();
|
||||
String[] tokens = line.split(" ");
|
||||
|
||||
for (int i = 0; i < tokens.length; i++)
|
||||
@ -68,7 +69,7 @@ public class UCIInfo {
|
||||
multipv = Integer.parseInt(tokens[++i]);
|
||||
break;
|
||||
case "currmove":
|
||||
currmove = Move.fromSAN(tokens[++i]);
|
||||
currmove = Move.fromLAN(tokens[++i]);
|
||||
break;
|
||||
case "currmovenumber":
|
||||
currmovenumber = Integer.parseInt(tokens[++i]);
|
||||
@ -97,16 +98,19 @@ public class UCIInfo {
|
||||
break;
|
||||
case "pv":
|
||||
while (++i < tokens.length && !params.contains(tokens[i]))
|
||||
pv.add(Move.fromSAN(tokens[i]));
|
||||
pv.add(Move.fromLAN(tokens[i]));
|
||||
break;
|
||||
case "refutation":
|
||||
while (++i < tokens.length && !params.contains(tokens[i]))
|
||||
refutation.add(Move.fromSAN(tokens[i]));
|
||||
refutation.add(Move.fromLAN(tokens[i]));
|
||||
break;
|
||||
// TODO: currline
|
||||
case "currline":
|
||||
while (++i < tokens.length && !params.contains(tokens[i]))
|
||||
;
|
||||
// A CPU number of 1 can be omitted
|
||||
final Integer cpu = tokens[i].matches("\\d+") ? Integer.valueOf(tokens[i++]) : 1;
|
||||
final ArrayList<Move> moves = new ArrayList<>();
|
||||
while (i < tokens.length && !params.contains(tokens[i]))
|
||||
moves.add(Move.fromLAN(tokens[i++]));
|
||||
currline.put(cpu, moves);
|
||||
System.err.println("The parameter 'currline' for command 'info' is not yet implemented");
|
||||
break;
|
||||
default:
|
||||
@ -142,7 +146,7 @@ public class UCIInfo {
|
||||
|
||||
public List<Move> getRefutation() { return refutation; }
|
||||
|
||||
public List<Move> getCurrline() { return currline; }
|
||||
public Map<Integer, List<Move>> getCurrline() { return currline; }
|
||||
|
||||
public Move getCurrmove() { return currmove; }
|
||||
|
||||
|
@ -6,7 +6,9 @@ import dev.kske.chess.board.Move;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>UCIListener.java</strong><br>
|
||||
* Created: <strong>19.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.3-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public interface UCIListener {
|
||||
|
||||
@ -53,6 +55,7 @@ public interface UCIListener {
|
||||
* The engine will check the copy protection now.
|
||||
*/
|
||||
default void onCopyProtectionChecking() {}
|
||||
|
||||
/**
|
||||
* The engine has successfully checked the copy protection.
|
||||
*/
|
||||
@ -81,7 +84,7 @@ public interface UCIListener {
|
||||
/**
|
||||
* The engine sends information to the GUI.
|
||||
*
|
||||
* @param additionalInfo Contains all pieces of information to be sent
|
||||
* @param info Contains all pieces of information to be sent
|
||||
*/
|
||||
default void onInfo(UCIInfo info) {}
|
||||
|
||||
|
@ -9,7 +9,9 @@ import java.util.StringJoiner;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>UCIOption.java</strong><br>
|
||||
* Created: <strong>22.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.3-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class UCIOption {
|
||||
|
||||
|
@ -13,7 +13,9 @@ import dev.kske.chess.board.Move;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>UCIReceiver.java</strong><br>
|
||||
* Created: <strong>19.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.3-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class UCIReceiver implements Runnable {
|
||||
|
||||
@ -93,7 +95,7 @@ public class UCIReceiver implements Runnable {
|
||||
String move = tokens[0];
|
||||
|
||||
// Ponder move
|
||||
if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromSAN(tokens[2])));
|
||||
if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromLAN(tokens[2])));
|
||||
else listeners.forEach(l -> l.onBestMove(move));
|
||||
}
|
||||
|
||||
@ -129,15 +131,9 @@ public class UCIReceiver implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void parseInfo(String line) {
|
||||
listeners.forEach(l -> l.onInfo(new UCIInfo(line)));
|
||||
}
|
||||
private void parseInfo(String line) { listeners.forEach(l -> l.onInfo(new UCIInfo(line))); }
|
||||
|
||||
private void parseOption(String line) {
|
||||
listeners.forEach(l -> l.onOption(new UCIOption((line))));
|
||||
}
|
||||
private void parseOption(String line) { listeners.forEach(l -> l.onOption(new UCIOption((line)))); }
|
||||
|
||||
public void addListener(UCIListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
public void addListener(UCIListener listener) { listeners.add(listener); }
|
||||
}
|
||||
|
@ -12,8 +12,11 @@ import javax.swing.SpinnerNumberModel;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>AIConfigDialog.java</strong><br>
|
||||
* Created: <strong>16.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
@Deprecated
|
||||
public class AIConfigDialog extends JDialog {
|
||||
|
||||
private static final long serialVersionUID = -8047984368152479992L;
|
||||
|
@ -6,16 +6,19 @@ import java.awt.Graphics;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import dev.kske.chess.board.Board;
|
||||
import dev.kske.chess.io.TextureUtil;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>BoardComponent.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong><br>
|
||||
* <br>
|
||||
* A square panel for rendering the chess board. To work correctly,
|
||||
* this must be added to a parent component that allows the child to decide the
|
||||
* size.
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class BoardComponent extends JComponent {
|
||||
|
||||
@ -47,8 +50,8 @@ public class BoardComponent extends JComponent {
|
||||
// Draw the pieces if a board is present
|
||||
if (board != null) for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (board.getBoardArr()[i][j] != null) g.drawImage(TextureUtil
|
||||
.getPieceTexture(board.getBoardArr()[i][j]), i * tileSize, j * tileSize, this);
|
||||
if (board.getBoardArr()[i][j] != null)
|
||||
g.drawImage(TextureUtil.getPieceTexture(board.getBoardArr()[i][j]), i * tileSize, j * tileSize, this);
|
||||
}
|
||||
|
||||
public int getTileSize() { return boardPane.getTileSize(); }
|
||||
|
@ -8,7 +8,9 @@ import javax.swing.JLayeredPane;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>BoardPane.java</strong><br>
|
||||
* Created: <strong>08.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class BoardPane extends JLayeredPane {
|
||||
|
||||
|
82
src/dev/kske/chess/ui/DialogUtil.java
Normal file
82
src/dev/kske/chess/ui/DialogUtil.java
Normal file
@ -0,0 +1,82 @@
|
||||
package dev.kske.chess.ui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
|
||||
import dev.kske.chess.io.EngineUtil;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>DialogUtil.java</strong><br>
|
||||
* Created: <strong>24.07.2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.3-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class DialogUtil {
|
||||
|
||||
private DialogUtil() {}
|
||||
|
||||
public static void showFileSelectionDialog(Component parent, Consumer<List<File>> action, Collection<FileNameExtensionFilter> filters) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
|
||||
fileChooser.setAcceptAllFileFilterUsed(false);
|
||||
filters.forEach(fileChooser::addChoosableFileFilter);
|
||||
if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) action.accept(Arrays.asList(fileChooser.getSelectedFile()));
|
||||
}
|
||||
|
||||
public static void showFileSaveDialog(Component parent, Consumer<File> action, Collection<FileNameExtensionFilter> filters) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
|
||||
fileChooser.setAcceptAllFileFilterUsed(false);
|
||||
filters.forEach(fileChooser::addChoosableFileFilter);
|
||||
if (fileChooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) action.accept(
|
||||
new File(fileChooser.getSelectedFile().getAbsolutePath() + "."
|
||||
+ ((FileNameExtensionFilter) fileChooser.getFileFilter()).getExtensions()[0]));
|
||||
}
|
||||
|
||||
public static void showGameConfigurationDialog(Component parent, BiConsumer<String, String> action) {
|
||||
JPanel dialogPanel = new JPanel();
|
||||
|
||||
List<String> options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player"));
|
||||
EngineUtil.getEngineInfos().forEach(info -> options.add(info.name));
|
||||
|
||||
JLabel lblWhite = new JLabel("White:");
|
||||
lblWhite.setFont(new Font("Tahoma", Font.PLAIN, 14));
|
||||
lblWhite.setBounds(10, 11, 49, 14);
|
||||
dialogPanel.add(lblWhite);
|
||||
|
||||
JComboBox<Object> cbWhite = new JComboBox<>();
|
||||
cbWhite.setModel(new DefaultComboBoxModel<>(options.toArray()));
|
||||
cbWhite.setBounds(98, 9, 159, 22);
|
||||
dialogPanel.add(cbWhite);
|
||||
|
||||
JLabel lblBlack = new JLabel("Black:");
|
||||
lblBlack.setFont(new Font("Tahoma", Font.PLAIN, 14));
|
||||
lblBlack.setBounds(10, 38, 49, 14);
|
||||
dialogPanel.add(lblBlack);
|
||||
|
||||
JComboBox<Object> cbBlack = new JComboBox<>();
|
||||
cbBlack.setModel(new DefaultComboBoxModel<>(options.toArray()));
|
||||
cbBlack.setBounds(98, 36, 159, 22);
|
||||
dialogPanel.add(cbBlack);
|
||||
|
||||
JOptionPane.showMessageDialog(parent, dialogPanel, "Game configuration", JOptionPane.QUESTION_MESSAGE);
|
||||
action.accept(options.get(cbWhite.getSelectedIndex()), options.get(cbBlack.getSelectedIndex()));
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package dev.kske.chess.ui;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>GameConfigurationDialog.java</strong><br>
|
||||
* Created: <strong>24.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*/
|
||||
public class GameConfigurationDialog extends JDialog {
|
||||
|
||||
private static final long serialVersionUID = 9080577278529876972L;
|
||||
|
||||
private String whiteName, blackName;
|
||||
private boolean startGame;
|
||||
|
||||
/**
|
||||
* Create the dialog.
|
||||
*/
|
||||
public GameConfigurationDialog() {
|
||||
setTitle("Game Configuration");
|
||||
setBounds(100, 100, 281, 142);
|
||||
setModal(true);
|
||||
setLocationRelativeTo(null);
|
||||
getContentPane().setLayout(null);
|
||||
|
||||
startGame = false;
|
||||
List<String> options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player"));
|
||||
EngineUtil.getEngineInfos().forEach(info -> options.add(info.name));
|
||||
|
||||
JLabel lblWhite = new JLabel("White:");
|
||||
lblWhite.setFont(new Font("Tahoma", Font.PLAIN, 14));
|
||||
lblWhite.setBounds(10, 11, 49, 14);
|
||||
getContentPane().add(lblWhite);
|
||||
|
||||
JComboBox<Object> cbWhite = new JComboBox<>();
|
||||
cbWhite.setModel(new DefaultComboBoxModel<Object>(options.toArray()));
|
||||
cbWhite.setBounds(98, 9, 159, 22);
|
||||
getContentPane().add(cbWhite);
|
||||
|
||||
JLabel lblBlack = new JLabel("Black:");
|
||||
lblBlack.setFont(new Font("Tahoma", Font.PLAIN, 14));
|
||||
lblBlack.setBounds(10, 38, 49, 14);
|
||||
getContentPane().add(lblBlack);
|
||||
|
||||
JComboBox<Object> cbBlack = new JComboBox<>();
|
||||
cbBlack.setModel(new DefaultComboBoxModel<Object>(options.toArray()));
|
||||
cbBlack.setBounds(98, 36, 159, 22);
|
||||
getContentPane().add(cbBlack);
|
||||
|
||||
JButton btnStart = new JButton("Start");
|
||||
btnStart.addActionListener((evt) -> {
|
||||
startGame = true;
|
||||
whiteName = options.get(cbWhite.getSelectedIndex());
|
||||
blackName = options.get(cbBlack.getSelectedIndex());
|
||||
dispose();
|
||||
});
|
||||
btnStart.setBounds(20, 73, 89, 23);
|
||||
getContentPane().add(btnStart);
|
||||
|
||||
JButton btnCancel = new JButton("Cancel");
|
||||
btnCancel.addActionListener((evt) -> dispose());
|
||||
btnCancel.setBounds(157, 73, 89, 23);
|
||||
getContentPane().add(btnCancel);
|
||||
}
|
||||
|
||||
public String getWhiteName() { return whiteName; }
|
||||
|
||||
public String getBlackName() { return blackName; }
|
||||
|
||||
public boolean isStartGame() { return startGame; }
|
||||
}
|
37
src/dev/kske/chess/ui/GameDropTarget.java
Normal file
37
src/dev/kske/chess/ui/GameDropTarget.java
Normal file
@ -0,0 +1,37 @@
|
||||
package dev.kske.chess.ui;
|
||||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.dnd.DropTargetAdapter;
|
||||
import java.awt.dnd.DropTargetDropEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>GameDropTarget.java</strong><br>
|
||||
* Created: <strong>13 Aug 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.3-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class GameDropTarget extends DropTargetAdapter {
|
||||
|
||||
private MainWindow mainWindow;
|
||||
|
||||
public GameDropTarget(MainWindow mainWindow) { this.mainWindow = mainWindow; }
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void drop(DropTargetDropEvent evt) {
|
||||
try {
|
||||
evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
|
||||
mainWindow.loadFiles((List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
|
||||
} catch (UnsupportedFlavorException | IOException ex) {
|
||||
ex.printStackTrace();
|
||||
evt.rejectDrop();
|
||||
}
|
||||
}
|
||||
}
|
199
src/dev/kske/chess/ui/GamePane.java
Normal file
199
src/dev/kske/chess/ui/GamePane.java
Normal file
@ -0,0 +1,199 @@
|
||||
package dev.kske.chess.ui;
|
||||
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.GridLayout;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.ListSelectionModel;
|
||||
|
||||
import dev.kske.chess.board.BoardState;
|
||||
import dev.kske.chess.board.MoveNode;
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.event.Event;
|
||||
import dev.kske.chess.event.EventBus;
|
||||
import dev.kske.chess.event.GameStartEvent;
|
||||
import dev.kske.chess.event.MoveEvent;
|
||||
import dev.kske.chess.event.Subscribable;
|
||||
import dev.kske.chess.game.Game;
|
||||
import dev.kske.chess.game.NaturalPlayer;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>GamePane.java</strong><br>
|
||||
* Created: <strong>23.08.2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.4-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class GamePane extends JComponent {
|
||||
|
||||
private static final long serialVersionUID = 4349772338239617477L;
|
||||
|
||||
private JButton btnRestart, btnSwapColors;
|
||||
private BoardPane boardPane;
|
||||
private Game game;
|
||||
private Color activeColor;
|
||||
private JPanel moveSelectionPanel;
|
||||
private JButton btnNext;
|
||||
private JButton btnFirst;
|
||||
private JButton btnLast;
|
||||
|
||||
public GamePane() {
|
||||
activeColor = Color.WHITE;
|
||||
|
||||
GridBagLayout gridBagLayout = new GridBagLayout();
|
||||
gridBagLayout.columnWidths = new int[] { 450, 1, 0 };
|
||||
gridBagLayout.rowHeights = new int[] { 33, 267, 1, 0 };
|
||||
gridBagLayout.columnWeights = new double[] { 0.0, 1.0, 1.0 };
|
||||
gridBagLayout.rowWeights = new double[] { 1.0, 1.0, 1.0, Double.MIN_VALUE };
|
||||
setLayout(gridBagLayout);
|
||||
|
||||
JPanel toolPanel = new JPanel();
|
||||
btnRestart = new JButton("Restart");
|
||||
btnRestart.addActionListener((evt) -> {
|
||||
if (game != null) {
|
||||
game.reset();
|
||||
game.start();
|
||||
}
|
||||
});
|
||||
|
||||
btnSwapColors = new JButton("Play as black");
|
||||
btnSwapColors.addActionListener((evt) -> {
|
||||
game.swapColors();
|
||||
btnSwapColors.setText("Play as " + activeColor.toString().toLowerCase());
|
||||
activeColor = activeColor.opposite();
|
||||
});
|
||||
|
||||
toolPanel.add(btnRestart);
|
||||
toolPanel.add(btnSwapColors);
|
||||
|
||||
GridBagConstraints gbc_toolPanel = new GridBagConstraints();
|
||||
gbc_toolPanel.anchor = GridBagConstraints.NORTH;
|
||||
gbc_toolPanel.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbc_toolPanel.gridx = 0;
|
||||
gbc_toolPanel.gridy = 0;
|
||||
gbc_toolPanel.gridwidth = 2;
|
||||
add(toolPanel, gbc_toolPanel);
|
||||
|
||||
moveSelectionPanel = new JPanel();
|
||||
GridBagConstraints gbc_moveSelectionPanel = new GridBagConstraints();
|
||||
gbc_moveSelectionPanel.fill = GridBagConstraints.BOTH;
|
||||
gbc_moveSelectionPanel.gridx = 2;
|
||||
gbc_moveSelectionPanel.gridy = 0;
|
||||
add(moveSelectionPanel, gbc_moveSelectionPanel);
|
||||
|
||||
btnFirst = new JButton("First");
|
||||
btnFirst.setEnabled(false);
|
||||
moveSelectionPanel.add(btnFirst);
|
||||
|
||||
JButton btnPreviousMove = new JButton("Previous");
|
||||
btnPreviousMove.setEnabled(false);
|
||||
moveSelectionPanel.add(btnPreviousMove);
|
||||
|
||||
btnNext = new JButton("Next");
|
||||
btnNext.setEnabled(false);
|
||||
moveSelectionPanel.add(btnNext);
|
||||
|
||||
btnLast = new JButton("Last");
|
||||
btnLast.setEnabled(false);
|
||||
moveSelectionPanel.add(btnLast);
|
||||
boardPane = new BoardPane();
|
||||
GridBagConstraints gbc_boardPane = new GridBagConstraints();
|
||||
gbc_boardPane.fill = GridBagConstraints.BOTH;
|
||||
gbc_boardPane.gridx = 0;
|
||||
gbc_boardPane.gridy = 1;
|
||||
add(boardPane, gbc_boardPane);
|
||||
|
||||
JPanel numberPanel = new JPanel(new GridLayout(8, 1));
|
||||
GridBagConstraints gbc_numberPanel = new GridBagConstraints();
|
||||
gbc_numberPanel.anchor = GridBagConstraints.WEST;
|
||||
gbc_numberPanel.fill = GridBagConstraints.VERTICAL;
|
||||
gbc_numberPanel.gridx = 1;
|
||||
gbc_numberPanel.gridy = 1;
|
||||
add(numberPanel, gbc_numberPanel);
|
||||
|
||||
JPanel letterPanel = new JPanel(new GridLayout(1, 8));
|
||||
GridBagConstraints gbc_letterPanel = new GridBagConstraints();
|
||||
gbc_letterPanel.anchor = GridBagConstraints.NORTH;
|
||||
gbc_letterPanel.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbc_letterPanel.gridx = 0;
|
||||
gbc_letterPanel.gridy = 2;
|
||||
add(letterPanel, gbc_letterPanel);
|
||||
|
||||
// Initialize board coordinates
|
||||
for (int i = 0; i < 8; i++) {
|
||||
numberPanel.add(new JLabel(String.valueOf(8 - i)));
|
||||
JLabel letterLabel = new JLabel(String.valueOf((char) (65 + i)));
|
||||
letterLabel.setHorizontalAlignment(JLabel.CENTER);
|
||||
letterPanel.add(letterLabel);
|
||||
}
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane();
|
||||
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
|
||||
gbc_scrollPane.fill = GridBagConstraints.BOTH;
|
||||
gbc_scrollPane.gridx = 2;
|
||||
gbc_scrollPane.gridy = 1;
|
||||
add(scrollPane, gbc_scrollPane);
|
||||
|
||||
JList<MoveNode> pgnList = new JList<>();
|
||||
pgnList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
pgnList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
|
||||
pgnList.setVisibleRowCount(0);
|
||||
pgnList.setCellRenderer(new MoveNodeRenderer());
|
||||
scrollPane.setViewportView(pgnList);
|
||||
|
||||
// Listen to moves and game (re-)starts and update the move list or disable the
|
||||
// color switching buttons if necessary
|
||||
EventBus.getInstance().register(new Subscribable() {
|
||||
|
||||
@Override
|
||||
public void handle(Event<?> event) {
|
||||
if (event instanceof MoveEvent && (((MoveEvent) event).getBoardState() == BoardState.CHECKMATE
|
||||
|| ((MoveEvent) event).getBoardState() == BoardState.STALEMATE))
|
||||
btnSwapColors.setEnabled(false);
|
||||
else if (event instanceof GameStartEvent) btnSwapColors.setEnabled(
|
||||
game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer ^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer);
|
||||
|
||||
if (game.getBoard().getLog() == null) return;
|
||||
|
||||
DefaultListModel<MoveNode> model = new DefaultListModel<>();
|
||||
game.getBoard().getLog().forEach(node -> model.addElement(node));
|
||||
pgnList.setModel(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> supports() { return new HashSet<>(Arrays.asList(MoveEvent.class, GameStartEvent.class)); }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link BoardPane} instance associated with this game pane
|
||||
*/
|
||||
public BoardPane getBoardPane() { return boardPane; }
|
||||
|
||||
/**
|
||||
* @return The {@link Game} instance associated with this game pane
|
||||
*/
|
||||
public Game getGame() { return game; }
|
||||
|
||||
/**
|
||||
* Assigns a new {@link Game} instance to this game pane. If exactly one of the
|
||||
* players is natural, color swapping functionality is enabled.
|
||||
*
|
||||
* @param game The {@link Game} to assign to this game pane.
|
||||
*/
|
||||
public void setGame(Game game) {
|
||||
if (this.game != null) this.game.stop();
|
||||
this.game = game;
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
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.Move;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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[] { "White", "Black" }));
|
||||
mtable.setEnabled(false);
|
||||
mcontentPane.add(mtable, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
public void add(Move move) {
|
||||
|
||||
}
|
||||
}
|
@ -1,46 +1,50 @@
|
||||
package dev.kske.chess.ui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.dnd.DropTarget;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.board.Board;
|
||||
import dev.kske.chess.board.FENString;
|
||||
import dev.kske.chess.exception.ChessException;
|
||||
import dev.kske.chess.game.Game;
|
||||
import dev.kske.chess.game.NaturalPlayer;
|
||||
import dev.kske.chess.pgn.PGNDatabase;
|
||||
import dev.kske.chess.pgn.PGNGame;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>MainWindow.java</strong><br>
|
||||
* Created: <strong>01.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class MainWindow {
|
||||
public class MainWindow extends JFrame {
|
||||
|
||||
private JFrame mframe;
|
||||
private JButton btnRestart, btnSwapColors;
|
||||
private BoardPane boardPane;
|
||||
private Game game;
|
||||
private Color activeColor;
|
||||
private static final long serialVersionUID = -3100939302567978977L;
|
||||
|
||||
private JTabbedPane tabbedPane;
|
||||
|
||||
/**
|
||||
* Launch the application.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
|
||||
public void run() {
|
||||
EventQueue.invokeLater(() -> {
|
||||
try {
|
||||
MainWindow window = new MainWindow();
|
||||
window.mframe.setVisible(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
new MainWindow();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -49,6 +53,7 @@ public class MainWindow {
|
||||
* Create the application.
|
||||
*/
|
||||
public MainWindow() {
|
||||
super("Chess by Kai S. K. Engelbart");
|
||||
initialize();
|
||||
}
|
||||
|
||||
@ -56,58 +61,135 @@ public class MainWindow {
|
||||
* Initialize the contents of the frame.
|
||||
*/
|
||||
private void initialize() {
|
||||
mframe = new JFrame("Chess by Kai S. K. Engelbart");
|
||||
mframe.setResizable(false);
|
||||
mframe.setBounds(100, 100, 494, 565);
|
||||
mframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
mframe.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
|
||||
// Configure frame
|
||||
setResizable(false);
|
||||
setBounds(100, 100, 494, 565);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
|
||||
|
||||
boardPane = new BoardPane();
|
||||
mframe.getContentPane().add(boardPane, BorderLayout.CENTER);
|
||||
// Add frame content
|
||||
tabbedPane = new JTabbedPane();
|
||||
getContentPane().add(tabbedPane);
|
||||
|
||||
JPanel toolPanel = new JPanel();
|
||||
btnRestart = new JButton("Restart");
|
||||
btnRestart.addActionListener((evt) -> { if (game != null) game.reset(); game.start(); });
|
||||
setJMenuBar(new MenuBar(this));
|
||||
new DropTarget(this, new GameDropTarget(this));
|
||||
|
||||
activeColor = Color.WHITE;
|
||||
btnSwapColors = new JButton("Play as black");
|
||||
btnSwapColors.addActionListener((evt) -> {
|
||||
game.swapColors();
|
||||
btnSwapColors.setText("Play as " + activeColor.toString().toLowerCase());
|
||||
activeColor = activeColor.opposite();
|
||||
// Update position and dimensions
|
||||
pack();
|
||||
setLocationRelativeTo(null);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The currently selected {@link GamePane} component
|
||||
*/
|
||||
public GamePane getSelectedGamePane() { return (GamePane) tabbedPane.getSelectedComponent(); }
|
||||
|
||||
/**
|
||||
* Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
|
||||
* The new tab has the title {@code Game n} where {@code n} is its number.
|
||||
*
|
||||
* @return The new {@link GamePane}
|
||||
*/
|
||||
public GamePane addGamePane() { return addGamePane("Game " + (tabbedPane.getComponentCount() + 1)); }
|
||||
|
||||
/**
|
||||
* Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
|
||||
*
|
||||
* @param title The title of the {@link GamePane}
|
||||
* @return The new {@link GamePane}
|
||||
*/
|
||||
public GamePane addGamePane(String title) {
|
||||
GamePane gamePane = new GamePane();
|
||||
tabbedPane.add(title, gamePane);
|
||||
tabbedPane.setSelectedIndex(tabbedPane.getComponentCount() - 1);
|
||||
return gamePane;
|
||||
}
|
||||
|
||||
public GamePane addGamePane(String title, Board board) {
|
||||
GamePane gamePane = addGamePane(title);
|
||||
DialogUtil.showGameConfigurationDialog(this,
|
||||
(whiteName, blackName) -> {
|
||||
Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, board);
|
||||
gamePane.setGame(game);
|
||||
game.start();
|
||||
});
|
||||
|
||||
toolPanel.add(btnRestart);
|
||||
toolPanel.add(btnSwapColors);
|
||||
mframe.getContentPane().add(toolPanel, BorderLayout.NORTH);
|
||||
|
||||
JPanel letterPanel = new JPanel(new GridLayout(1, 8));
|
||||
for (int i = 0; i < 8; i++) {
|
||||
JLabel letterLabel = new JLabel(String.valueOf((char) (65 + i)));
|
||||
letterLabel.setHorizontalAlignment(JLabel.CENTER);
|
||||
letterPanel.add(letterLabel);
|
||||
}
|
||||
mframe.add(letterPanel, BorderLayout.SOUTH);
|
||||
|
||||
JPanel numberPanel = new JPanel(new GridLayout(8, 1));
|
||||
for (int i = 0; i < 8; i++)
|
||||
numberPanel.add(new JLabel(String.valueOf(8 - i)));
|
||||
mframe.add(numberPanel, BorderLayout.EAST);
|
||||
|
||||
mframe.setJMenuBar(new MenuBar(this));
|
||||
|
||||
mframe.pack();
|
||||
mframe.setLocationRelativeTo(null);
|
||||
return gamePane;
|
||||
}
|
||||
|
||||
public BoardPane getBoardPane() { return boardPane; }
|
||||
/**
|
||||
* Removes a {@link GamePane} form the tabbed pane.
|
||||
*
|
||||
* @param index The index of the {@link GamePane} to remove
|
||||
*/
|
||||
public void removeGamePane(int index) { tabbedPane.remove(index); }
|
||||
|
||||
public Game getGame() { return game; }
|
||||
/**
|
||||
* Loads a game file (FEN or PGN) and adds it to a new {@link GamePane}.
|
||||
*
|
||||
* @param files the files to load the game from
|
||||
*/
|
||||
public void loadFiles(List<File> files) {
|
||||
files.forEach(file -> {
|
||||
final String name = file.getName().substring(0, file.getName().lastIndexOf('.'));
|
||||
final String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase();
|
||||
try {
|
||||
Board board;
|
||||
switch (extension) {
|
||||
case ".fen":
|
||||
board = new FENString(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)).getBoard();
|
||||
break;
|
||||
case ".pgn":
|
||||
PGNDatabase pgnDB = new PGNDatabase();
|
||||
pgnDB.load(file);
|
||||
if (pgnDB.getGames().size() > 0) {
|
||||
String[] gameNames = new String[pgnDB.getGames().size()];
|
||||
for (int i = 0; i < gameNames.length; i++) {
|
||||
final PGNGame game = pgnDB.getGames().get(i);
|
||||
gameNames[i] = String.format("%s vs %s: %s", game.getTag("White"), game.getTag("Black"), game.getTag("Result"));
|
||||
}
|
||||
JComboBox<String> comboBox = new JComboBox<>(gameNames);
|
||||
JOptionPane.showMessageDialog(this, comboBox, "Select a game", JOptionPane.QUESTION_MESSAGE);
|
||||
board = pgnDB.getGames().get(comboBox.getSelectedIndex()).getBoard();
|
||||
} else throw new ChessException("The PGN database '" + name + "' is empty!");
|
||||
break;
|
||||
default:
|
||||
throw new ChessException("The file extension '" + extension + "' is not supported!");
|
||||
}
|
||||
addGamePane(name, board);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Failed to load the file " + file.getName() + ": " + e.toString(),
|
||||
"File loading error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setGame(Game game) {
|
||||
if (this.game != null) this.game.disconnect();
|
||||
this.game = game;
|
||||
btnSwapColors.setEnabled(game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer
|
||||
^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer);
|
||||
public void saveFile(File file) {
|
||||
final int dotIndex = file.getName().lastIndexOf('.');
|
||||
final String extension = file.getName().substring(dotIndex).toLowerCase();
|
||||
|
||||
if (extension.equals(".pgn")) try {
|
||||
PGNGame pgnGame = new PGNGame(getSelectedGamePane().getGame().getBoard());
|
||||
pgnGame.setTag("Event", tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
pgnGame.setTag("Result", "*");
|
||||
PGNDatabase pgnDB = new PGNDatabase();
|
||||
pgnDB.getGames().add(pgnGame);
|
||||
pgnDB.save(file);
|
||||
|
||||
if (JOptionPane.showConfirmDialog(this,
|
||||
"Game export finished. Do you want to view the created file?",
|
||||
"Game export finished",
|
||||
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
|
||||
Desktop.getDesktop().open(file);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Failed to save the file " + file.getName() + ": " + e.toString(),
|
||||
"File saving error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,30 +2,35 @@ package dev.kske.chess.ui;
|
||||
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
|
||||
import dev.kske.chess.board.FENString;
|
||||
import dev.kske.chess.exception.ChessException;
|
||||
import dev.kske.chess.game.Game;
|
||||
import dev.kske.chess.io.EngineUtil;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>MenuBar.java</strong><br>
|
||||
* Created: <strong>16.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class MenuBar extends JMenuBar {
|
||||
|
||||
private static final long serialVersionUID = -7221583703531248228L;
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
private final BoardPane boardPane;
|
||||
|
||||
public MenuBar(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
boardPane = mainWindow.getBoardPane();
|
||||
|
||||
initGameMenu();
|
||||
initEngineMenu();
|
||||
@ -34,18 +39,31 @@ public class MenuBar extends JMenuBar {
|
||||
|
||||
private void initGameMenu() {
|
||||
JMenu gameMenu = new JMenu("Game");
|
||||
|
||||
JMenuItem newGameMenuItem = new JMenuItem("New Game");
|
||||
newGameMenuItem.addActionListener((evt) -> {
|
||||
GameConfigurationDialog dialog = new GameConfigurationDialog();
|
||||
dialog.setVisible(true);
|
||||
if (dialog.isStartGame()) startGame(new Game(boardPane, dialog.getWhiteName(), dialog.getBlackName()));
|
||||
});
|
||||
newGameMenuItem.addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
|
||||
GamePane gamePane = mainWindow.addGamePane();
|
||||
Game game = new Game(gamePane.getBoardPane(), whiteName, blackName);
|
||||
gamePane.setGame(game);
|
||||
game.start();
|
||||
}));
|
||||
gameMenu.add(newGameMenuItem);
|
||||
|
||||
add(gameMenu);
|
||||
JMenuItem loadFileMenu = new JMenuItem("Load game file");
|
||||
loadFileMenu.addActionListener((evt) -> DialogUtil
|
||||
.showFileSelectionDialog(mainWindow,
|
||||
mainWindow::loadFiles,
|
||||
Arrays.asList(new FileNameExtensionFilter("FEN and PGN files", "fen", "pgn"))));
|
||||
gameMenu.add(loadFileMenu);
|
||||
|
||||
// Start a game
|
||||
startGame(new Game(boardPane, "Natural Player", "Natural Player"));
|
||||
JMenuItem saveFileMenu = new JMenuItem("Save game file");
|
||||
saveFileMenu
|
||||
.addActionListener((evt) -> DialogUtil
|
||||
.showFileSaveDialog(mainWindow, mainWindow::saveFile, Arrays.asList(new FileNameExtensionFilter("PGN file", "pgn"))));
|
||||
gameMenu.add(saveFileMenu);
|
||||
|
||||
add(gameMenu);
|
||||
newGameMenuItem.doClick();
|
||||
}
|
||||
|
||||
private void initEngineMenu() {
|
||||
@ -53,10 +71,8 @@ public class MenuBar extends JMenuBar {
|
||||
|
||||
JMenuItem addEngineMenuItem = new JMenuItem("Add engine");
|
||||
addEngineMenuItem.addActionListener((evt) -> {
|
||||
String enginePath = JOptionPane.showInputDialog(getParent(),
|
||||
"Enter the path to a UCI-compatible chess engine:",
|
||||
"Engine selection",
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
String enginePath = JOptionPane
|
||||
.showInputDialog(getParent(), "Enter the path to a UCI-compatible chess engine:", "Engine selection", JOptionPane.QUESTION_MESSAGE);
|
||||
if (enginePath != null) EngineUtil.addEngine(enginePath);
|
||||
});
|
||||
|
||||
@ -71,19 +87,32 @@ public class MenuBar extends JMenuBar {
|
||||
JMenu toolsMenu = new JMenu("Tools");
|
||||
|
||||
JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN");
|
||||
exportFENMenuItem.addActionListener((evt) -> Toolkit.getDefaultToolkit()
|
||||
.getSystemClipboard()
|
||||
.setContents(new StringSelection(mainWindow.getGame().getBoard().toFEN()), null));
|
||||
exportFENMenuItem.addActionListener((evt) -> {
|
||||
final String fen = new FENString(mainWindow.getSelectedGamePane().getGame().getBoard()).toString();
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null);
|
||||
JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen));
|
||||
});
|
||||
toolsMenu.add(exportFENMenuItem);
|
||||
|
||||
JMenuItem loadFromFENMenuItem = new JMenuItem("Load board from FEN");
|
||||
loadFromFENMenuItem.addActionListener((evt) -> {
|
||||
final GamePane gamePane = mainWindow.addGamePane();
|
||||
final String fen = JOptionPane.showInputDialog("Enter a FEN string: ");
|
||||
DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
|
||||
Game game;
|
||||
try {
|
||||
game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard());
|
||||
gamePane.setGame(game);
|
||||
game.start();
|
||||
} catch (ChessException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane
|
||||
.showMessageDialog(mainWindow, "Failed to load FEN string: " + e.toString(), "FEN loading error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
});
|
||||
toolsMenu.add(loadFromFENMenuItem);
|
||||
|
||||
add(toolsMenu);
|
||||
}
|
||||
|
||||
private void startGame(Game game) {
|
||||
mainWindow.setGame(game);
|
||||
|
||||
// Update board and board component
|
||||
game.reset();
|
||||
game.start();
|
||||
}
|
||||
}
|
||||
|
34
src/dev/kske/chess/ui/MoveNodeRenderer.java
Normal file
34
src/dev/kske/chess/ui/MoveNodeRenderer.java
Normal file
@ -0,0 +1,34 @@
|
||||
package dev.kske.chess.ui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import dev.kske.chess.board.MoveNode;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>MoveNodeRenderer.java</strong><br>
|
||||
* Created: <strong>9 Oct 2019</strong><br>
|
||||
*
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class MoveNodeRenderer extends JLabel implements ListCellRenderer<MoveNode> {
|
||||
|
||||
private static final long serialVersionUID = 5242015788752442446L;
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends MoveNode> list, MoveNode node, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
setText(node.move.toLAN());
|
||||
setBackground(isSelected ? Color.red : Color.white);
|
||||
setOpaque(true);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -20,7 +20,9 @@ import dev.kske.chess.board.Position;
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>OverlayComponent.java</strong><br>
|
||||
* Created: <strong>08.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*
|
||||
* @since Chess v0.1-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class OverlayComponent extends JComponent {
|
||||
|
||||
@ -46,15 +48,15 @@ public class OverlayComponent extends JComponent {
|
||||
// Draw an arrow representing the last move and mark its position and
|
||||
// destination
|
||||
if (arrow != null) {
|
||||
Point pos = new Point(arrow.pos.x * tileSize + tileSize / 2, arrow.pos.y * tileSize + tileSize / 2);
|
||||
Point dest = new Point(arrow.dest.x * tileSize + tileSize / 2, arrow.dest.y * tileSize + tileSize / 2);
|
||||
Point pos = new Point(arrow.getPos().x * tileSize + tileSize / 2, arrow.getPos().y * tileSize + tileSize / 2);
|
||||
Point dest = new Point(arrow.getDest().x * tileSize + tileSize / 2, arrow.getDest().y * tileSize + tileSize / 2);
|
||||
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
g2d.setStroke(new BasicStroke(3));
|
||||
|
||||
g2d.setColor(Color.yellow);
|
||||
g2d.drawRect(arrow.pos.x * tileSize, arrow.pos.y * tileSize, tileSize, tileSize);
|
||||
g2d.drawRect(arrow.dest.x * tileSize, arrow.dest.y * tileSize, tileSize, tileSize);
|
||||
g2d.drawRect(arrow.getPos().x * tileSize, arrow.getPos().y * tileSize, tileSize, tileSize);
|
||||
g2d.drawRect(arrow.getDest().x * tileSize, arrow.getDest().y * tileSize, tileSize, tileSize);
|
||||
|
||||
Shape arrowShape = createArrowShape(pos, dest);
|
||||
g.setColor(new Color(255, 0, 0, 127));
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.kske.chess.test;
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
@ -6,10 +6,7 @@ import static org.junit.Assert.assertNotSame;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
@ -34,13 +31,13 @@ class BoardTest {
|
||||
*/
|
||||
@Test
|
||||
void testClone() {
|
||||
Board clone = (Board) board.clone();
|
||||
Board clone = new Board(board, false);
|
||||
assertNotSame(clone, board);
|
||||
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());
|
||||
assertNotEquals(clone.getLog().getActiveColor(), board.getLog().getActiveColor());
|
||||
}
|
||||
}
|
72
test/dev/kske/chess/board/FENStringTest.java
Normal file
72
test/dev/kske/chess/board/FENStringTest.java
Normal file
@ -0,0 +1,72 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.exception.ChessException;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>FENStringTest.java</strong><br>
|
||||
* Created: <strong>24 Oct 2019</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
class FENStringTest {
|
||||
|
||||
List<String> fenStrings = new ArrayList<>();
|
||||
List<Board> boards = new ArrayList<>();
|
||||
|
||||
void cleanBoard(Board board) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
board.getBoardArr()[i][j] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws java.lang.Exception
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
fenStrings.addAll(Arrays.asList("rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"));
|
||||
Board board = new Board();
|
||||
board.set(Position.fromLAN("c7"), null);
|
||||
board.set(Position.fromLAN("c5"), new Pawn(Color.BLACK, board));
|
||||
board.set(Position.fromLAN("e4"), new Pawn(Color.WHITE, board));
|
||||
board.set(Position.fromLAN("f3"), new Knight(Color.WHITE, board));
|
||||
board.set(Position.fromLAN("e2"), null);
|
||||
board.set(Position.fromLAN("g1"), null);
|
||||
|
||||
board.getLog().setActiveColor(Color.BLACK);
|
||||
board.getLog().setHalfmoveClock(1);
|
||||
board.getLog().setFullmoveNumber(2);
|
||||
boards.add(board);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.FENString#toString()}.
|
||||
*/
|
||||
@Test
|
||||
void testToString() {
|
||||
for (int i = 0; i < fenStrings.size(); i++)
|
||||
assertEquals(fenStrings.get(i), new FENString(boards.get(i)).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.FENString#getBoard()}.
|
||||
*
|
||||
* @throws ChessException
|
||||
*/
|
||||
@Test
|
||||
void testGetBoard() throws ChessException {
|
||||
for (int i = 0; i < boards.size(); i++)
|
||||
assertEquals(boards.get(i), new FENString(fenStrings.get(i)).getBoard());
|
||||
}
|
||||
}
|
165
test/dev/kske/chess/board/LogTest.java
Normal file
165
test/dev/kske/chess/board/LogTest.java
Normal file
@ -0,0 +1,165 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>LogTest.java</strong><br>
|
||||
* Created: <strong>13 Sep 2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*/
|
||||
class LogTest {
|
||||
|
||||
Log log = new Log();
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#Log()}.
|
||||
*/
|
||||
@Test
|
||||
void testLog() {
|
||||
assertTrue(log.isEmpty());
|
||||
assertNull(log.getLast());
|
||||
assertNull(log.getRoot());
|
||||
assertEquals(log.getActiveColor(), Color.WHITE);
|
||||
assertNull(log.getEnPassant());
|
||||
assertEquals(log.getFullmoveNumber(), 1);
|
||||
assertEquals(log.getHalfmoveClock(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#clone()}.
|
||||
*/
|
||||
@Test
|
||||
void testClone() {
|
||||
Log other = new Log(log, false);
|
||||
log.setActiveColor(Color.WHITE);
|
||||
other.setActiveColor(Color.BLACK);
|
||||
assertNotEquals(log.getActiveColor(), other.getActiveColor());
|
||||
log.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
|
||||
log.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
|
||||
other.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
|
||||
other.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
|
||||
assertNotEquals(log.getRoot(), other.getRoot());
|
||||
assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#add(dev.kske.chess.board.Move, dev.kske.chess.board.Piece, boolean)}.
|
||||
*/
|
||||
@Test
|
||||
void testAdd() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#removeLast()}.
|
||||
*/
|
||||
@Test
|
||||
void testRemoveLast() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#isEmpty()}.
|
||||
*/
|
||||
@Test
|
||||
void testIsEmpty() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#reset()}.
|
||||
*/
|
||||
@Test
|
||||
void testReset() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#getRoot()}.
|
||||
*/
|
||||
@Test
|
||||
void testGetRoot() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#getLast()}.
|
||||
*/
|
||||
@Test
|
||||
void testGetLast() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#getEnPassant()}.
|
||||
*/
|
||||
@Test
|
||||
void testGetEnPassant() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#setEnPassant(dev.kske.chess.board.Position)}.
|
||||
*/
|
||||
@Test
|
||||
void testSetEnPassant() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#getActiveColor()}.
|
||||
*/
|
||||
@Test
|
||||
void testGetActiveColor() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#setActiveColor(dev.kske.chess.board.Piece.Color)}.
|
||||
*/
|
||||
@Test
|
||||
void testSetActiveColor() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#getFullmoveNumber()}.
|
||||
*/
|
||||
@Test
|
||||
void testGetFullmoveCounter() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#setFullmoveNumber(int)}.
|
||||
*/
|
||||
@Test
|
||||
void testSetFullmoveCounter() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#getHalfmoveClock()}.
|
||||
*/
|
||||
@Test
|
||||
void testGetHalfmoveClock() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Log#setHalfmoveClock(int)}.
|
||||
*/
|
||||
@Test
|
||||
void testSetHalfmoveClock() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
}
|
47
test/dev/kske/chess/board/PositionTest.java
Normal file
47
test/dev/kske/chess/board/PositionTest.java
Normal file
@ -0,0 +1,47 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>PositionTest.java</strong><br>
|
||||
* Created: <strong>24.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*/
|
||||
class PositionTest {
|
||||
|
||||
final int n = 4;
|
||||
Position[] positions = new Position[] { new Position(0, 0), new Position(7, 7), new Position(0, 7), new Position(7, 0) };
|
||||
String[] sans = new String[] { "a8", "h1", "a1", "h8" };
|
||||
String[] strings = new String[] { "[0, 0]", "[7, 7]", "[0, 7]", "[7, 0]" };
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
* {@link dev.kske.chess.board.Position#fromLAN(java.lang.String)}.
|
||||
*/
|
||||
@Test
|
||||
void testFromSAN() {
|
||||
for (int i = 0; i < n; i++)
|
||||
assertEquals(positions[i], Position.fromLAN(sans[i]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Position#toLAN()}.
|
||||
*/
|
||||
@Test
|
||||
void testToSAN() {
|
||||
for (int i = 0; i < n; i++)
|
||||
assertEquals(sans[i], positions[i].toLAN());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Position#toString()}.
|
||||
*/
|
||||
@Test
|
||||
void testToString() {
|
||||
for (int i = 0; i < n; i++)
|
||||
assertEquals(strings[i], positions[i].toString());
|
||||
}
|
||||
}
|
36
test/dev/kske/chess/pgn/PGNDatabaseTest.java
Normal file
36
test/dev/kske/chess/pgn/PGNDatabaseTest.java
Normal file
@ -0,0 +1,36 @@
|
||||
package dev.kske.chess.pgn;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import dev.kske.chess.exception.ChessException;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>PGNDatabaseTest.java</strong><br>
|
||||
* Created: <strong>4 Oct 2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*/
|
||||
class PGNDatabaseTest {
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.pgn.PGNDatabase#load(java.io.File)}.
|
||||
*
|
||||
* @throws ChessException
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
@Test
|
||||
void testLoad() {
|
||||
PGNDatabase db = new PGNDatabase();
|
||||
try {
|
||||
db.load(new File(getClass().getClassLoader().getResource("test.pgn").getFile()));
|
||||
} catch (FileNotFoundException | ChessException e) {
|
||||
e.printStackTrace();
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package dev.kske.chess.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import dev.kske.chess.board.Position;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>PositionTest.java</strong><br>
|
||||
* Created: <strong>24.07.2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*/
|
||||
class PositionTest {
|
||||
|
||||
List<Position> positions;
|
||||
List<String> sans;
|
||||
|
||||
/**
|
||||
* @throws java.lang.Exception
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
positions = Arrays.asList(new Position(0, 0), new Position(7, 7), new Position(0, 7), new Position(7, 0));
|
||||
sans = Arrays.asList("a8", "h1", "a1", "h8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
* {@link dev.kske.chess.board.Position#fromSAN(java.lang.String)}.
|
||||
*/
|
||||
@Test
|
||||
void testFromSAN() {
|
||||
IntStream.range(0, positions.size())
|
||||
.forEach(i -> assertEquals(positions.get(i), Position.fromSAN(sans.get(i))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.Position#toSAN()}.
|
||||
*/
|
||||
@Test
|
||||
void testToSAN() {
|
||||
IntStream.range(0, positions.size()).forEach(i -> assertEquals(sans.get(i), positions.get(i).toSAN()));
|
||||
}
|
||||
|
||||
}
|
18
test_res/test.pgn
Normal file
18
test_res/test.pgn
Normal file
@ -0,0 +1,18 @@
|
||||
[Event "Test Game"]
|
||||
[Site "Test Environment"]
|
||||
[Date "2019.10.04"]
|
||||
[Round "1"]
|
||||
[Result "1-0"]
|
||||
[White "Kai Engelbart"]
|
||||
[Black "Kai Engelbart 2"]
|
||||
|
||||
1. e4 c5 2. Nf3 e6 3. d4 cxd4 4. Nxd4 a6 5. Bd3 Nf6 6. O-O d6
|
||||
7. c4 Bd7 8. Nc3 Nc6 9. Be3 Be7 10. h3 Ne5 11. Be2 Rc8 12. Qb3
|
||||
Qc7 13. Rac1 O-O 14. f4 Nc6 15. Nf3 Qb8 16. Qd1 Be8 17. Qd2
|
||||
Na5 18. b3 b6 19. Bd3 Nc6 20. Qf2 b5 21. Rfd1 Nb4 22. Bf1 bxc4
|
||||
23. bxc4 a5 24. Nd4 Qa8 25. Qf3 Na6 26. Ndb5 Nc5 27. e5 dxe5
|
||||
28. Qxa8 Rxa8 29. fxe5 Nfe4 30. Nd6 Bc6 31. Ncxe4 Nxe4 32. c5
|
||||
Ng3 33. Bc4 h5 34. Bf2 h4 35. Bxg3 hxg3 36. Bb5 Bxb5 37. Nxb5
|
||||
f6 38. Rd7 Bd8 39. Rc3 fxe5 40. Rxg3 Rf7 41. Rxf7 Kxf7 42. c6
|
||||
Bb6+ 43. Kf1 Kf8 44. c7 Rc8 45. a4 e4 46. Ke2 e5 47. Rg6 Bd4
|
||||
48. h4 Bb2 1-0
|
Reference in New Issue
Block a user