Compare commits
45 Commits
v0.1-alpha
...
v0.3-alpha
Author | SHA1 | Date | |
---|---|---|---|
1ce8b8355a | |||
36597ac6f1 | |||
e72297bebf | |||
545f946aa0 | |||
984bedfafe | |||
cac235a0db | |||
1f5242935f | |||
51558797cc | |||
ae38e67a90 | |||
5abc51688b | |||
8bcd89d975 | |||
4c0432ca30 | |||
36832733b6 | |||
601104485c | |||
2da185a8fb | |||
0ed80228fe | |||
e353aef867 | |||
b25acff367 | |||
184c96db8c | |||
309495cfae | |||
91962c01e0 | |||
b3710a878f | |||
68d1996bd6 | |||
efe7ab2b60 | |||
a68a87962c | |||
ab54f88a89 | |||
b5b7a749d6 | |||
709383e758 | |||
347eb5d531 | |||
062a5c3075 | |||
29e17d90a5 | |||
d8f5f3bbf4 | |||
4dcc9f7ca0 | |||
cfd71af142 | |||
fcd8bfb26b | |||
cde7f63996 | |||
8ea0c7a603 | |||
8eda941284 | |||
7a986ab9c4 | |||
c245cdb640 | |||
58340ca6ac | |||
199d2f06c6 | |||
d12b06a1ff | |||
6d98d9a963 | |||
c3a787c3a7 |
@ -1,12 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry kind="src" path="src"/>
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="src" path="res"/>
|
||||||
|
<classpathentry kind="src" output="bin_test" path="test">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="module" value="true"/>
|
<attribute name="module" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="lib" path="res"/>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
|
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
|
||||||
<classpathentry kind="output" path="bin"/>
|
<classpathentry kind="output" path="bin"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
.metadata
|
.metadata
|
||||||
bin/
|
bin/
|
||||||
|
/bin_test/
|
||||||
tmp/
|
tmp/
|
||||||
*.tmp
|
*.tmp
|
||||||
*.bak
|
*.bak
|
||||||
@ -20,4 +21,5 @@ local.properties
|
|||||||
.recommenders/
|
.recommenders/
|
||||||
|
|
||||||
# Annotation Processing
|
# Annotation Processing
|
||||||
.apt_generated/
|
.apt_generated/
|
||||||
|
/engine_infos.ser
|
||||||
|
@ -20,14 +20,6 @@ public class Bishop extends Piece {
|
|||||||
return move.isDiagonal() && isFreePath(move);
|
return move.isDiagonal() && isFreePath(move);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isFreePath(Move move) {
|
|
||||||
for (int i = move.pos.x + move.xSign, j = move.pos.y
|
|
||||||
+ move.ySign; i != move.dest.x; i += move.xSign, j += move.ySign)
|
|
||||||
if (board.getBoardArr()[i][j] != null) return false;
|
|
||||||
return checkDestination(move);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Move> getPseudolegalMoves(Position pos) {
|
protected List<Move> getPseudolegalMoves(Position pos) {
|
||||||
List<Move> moves = new ArrayList<>();
|
List<Move> moves = new ArrayList<>();
|
||||||
|
@ -5,6 +5,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import dev.kske.chess.board.Log.LoggedMove;
|
||||||
import dev.kske.chess.board.Piece.Color;
|
import dev.kske.chess.board.Piece.Color;
|
||||||
import dev.kske.chess.board.Piece.Type;
|
import dev.kske.chess.board.Piece.Type;
|
||||||
|
|
||||||
@ -16,12 +17,53 @@ import dev.kske.chess.board.Piece.Type;
|
|||||||
*/
|
*/
|
||||||
public class Board implements Cloneable {
|
public class Board implements Cloneable {
|
||||||
|
|
||||||
private Piece[][] boardArr;
|
private Piece[][] boardArr;
|
||||||
private Map<Color, Position> kingPos;
|
private Map<Color, Position> kingPos;
|
||||||
|
private Map<Color, Map<Type, Boolean>> castlingRights;
|
||||||
|
private Log log;
|
||||||
|
|
||||||
|
private static final Map<Type, int[][]> positionScores;
|
||||||
|
|
||||||
|
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 } });
|
||||||
|
}
|
||||||
|
|
||||||
public Board() {
|
public Board() {
|
||||||
boardArr = new Piece[8][8];
|
boardArr = new Piece[8][8];
|
||||||
kingPos = new HashMap<>();
|
kingPos = new HashMap<>();
|
||||||
|
castlingRights = new HashMap<>();
|
||||||
|
log = new Log();
|
||||||
initializeDefaultPositions();
|
initializeDefaultPositions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,12 +77,15 @@ public class Board implements Cloneable {
|
|||||||
Piece piece = getPos(move);
|
Piece piece = getPos(move);
|
||||||
if (piece == null || !piece.isValidMove(move)) return false;
|
if (piece == null || !piece.isValidMove(move)) return false;
|
||||||
else {
|
else {
|
||||||
|
// Set type after validation
|
||||||
|
if (move.type == Move.Type.UNKNOWN) move.type = Move.Type.NORMAL;
|
||||||
|
|
||||||
// Move piece
|
// Move piece
|
||||||
Piece capturePiece = move(move);
|
move(move);
|
||||||
|
|
||||||
// Revert move if it caused a check for its team
|
// Revert move if it caused a check for its team
|
||||||
if (checkCheck(piece.getColor())) {
|
if (checkCheck(piece.getColor())) {
|
||||||
revert(move, capturePiece);
|
revert();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,31 +99,132 @@ public class Board implements Cloneable {
|
|||||||
* @param move The move to execute
|
* @param move The move to execute
|
||||||
* @return The captures piece, or null if the move's destination was empty
|
* @return The captures piece, or null if the move's destination was empty
|
||||||
*/
|
*/
|
||||||
public Piece move(Move move) {
|
public void move(Move move) {
|
||||||
Piece piece = getPos(move);
|
Piece piece = getPos(move);
|
||||||
Piece capturePiece = getDest(move);
|
Piece capturePiece = getDest(move);
|
||||||
setDest(move, piece);
|
|
||||||
setPos(move, null);
|
|
||||||
|
|
||||||
// Update the king's position if the moved piece is the king
|
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);
|
||||||
|
|
||||||
|
// 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);
|
if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
|
||||||
|
|
||||||
return capturePiece;
|
// Update log
|
||||||
|
log.add(move, capturePiece, piece.getType() == Type.PAWN);
|
||||||
|
|
||||||
|
updateCastlingRights();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverts a move.
|
* Reverts the last move.
|
||||||
*
|
|
||||||
* @param move The move to revert
|
|
||||||
* @param capturedPiece The piece that has been captured when the move has been
|
|
||||||
* applied
|
|
||||||
*/
|
*/
|
||||||
public void revert(Move move, Piece capturedPiece) {
|
public void revert() {
|
||||||
setPos(move, getDest(move));
|
LoggedMove loggedMove = log.getLast();
|
||||||
setDest(move, capturedPiece);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement move counter
|
||||||
|
getPos(move).decMoveCounter();
|
||||||
|
|
||||||
// Update the king's position if the moved piece is the king
|
// 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).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,9 +275,9 @@ public class Board implements Cloneable {
|
|||||||
if (!getMoves(kingPos.get(color)).isEmpty()) return false;
|
if (!getMoves(kingPos.get(color)).isEmpty()) return false;
|
||||||
else {
|
else {
|
||||||
for (Move move : getMoves(color)) {
|
for (Move move : getMoves(color)) {
|
||||||
Piece capturePiece = move(move);
|
move(move);
|
||||||
boolean check = checkCheck(color);
|
boolean check = checkCheck(color);
|
||||||
revert(move, capturePiece);
|
revert();
|
||||||
if (!check) return false;
|
if (!check) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -139,9 +285,9 @@ public class Board implements Cloneable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GameState getGameEventType(Color color) {
|
public GameState getGameEventType(Color color) {
|
||||||
return checkCheck(color)
|
return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
|
||||||
? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
|
: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE
|
||||||
: getMoves(color).isEmpty() ? GameState.STALEMATE : GameState.NORMAL;
|
: GameState.NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,22 +300,26 @@ public class Board implements Cloneable {
|
|||||||
int score = 0;
|
int score = 0;
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
for (int j = 0; j < 8; j++)
|
for (int j = 0; j < 8; j++)
|
||||||
if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) switch (boardArr[i][j].getType()) {
|
if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) {
|
||||||
case QUEEN:
|
switch (boardArr[i][j].getType()) {
|
||||||
score += 8;
|
case QUEEN:
|
||||||
break;
|
score += 90;
|
||||||
case ROOK:
|
break;
|
||||||
score += 5;
|
case ROOK:
|
||||||
break;
|
score += 50;
|
||||||
case KNIGHT:
|
break;
|
||||||
score += 3;
|
case KNIGHT:
|
||||||
break;
|
score += 30;
|
||||||
case BISHOP:
|
break;
|
||||||
score += 3;
|
case BISHOP:
|
||||||
break;
|
score += 30;
|
||||||
case PAWN:
|
break;
|
||||||
score += 1;
|
case PAWN:
|
||||||
break;
|
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;
|
return score;
|
||||||
}
|
}
|
||||||
@ -218,6 +368,18 @@ public class Board implements Cloneable {
|
|||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
for (int j = 2; j < 6; j++)
|
for (int j = 2; j < 6; j++)
|
||||||
boardArr[i][j] = null;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -240,12 +402,85 @@ public class Board implements Cloneable {
|
|||||||
board.boardArr[i][j].board = board;
|
board.boardArr[i][j].board = board;
|
||||||
}
|
}
|
||||||
|
|
||||||
board.kingPos = new HashMap<>();
|
board.kingPos = new HashMap<>();
|
||||||
board.kingPos.putAll(kingPos);
|
board.kingPos.putAll(kingPos);
|
||||||
|
board.log = (Log) log.clone();
|
||||||
|
|
||||||
return board;
|
return board;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A FEN string representing the board
|
||||||
|
*/
|
||||||
|
public String toFEN() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Piece placement (from white's perspective)
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
int emptyCount = 0;
|
||||||
|
for (int j = 0; j < 8; j++)
|
||||||
|
if (boardArr[j][i] == null) ++emptyCount;
|
||||||
|
else {
|
||||||
|
if (emptyCount != 0) {
|
||||||
|
sb.append(emptyCount);
|
||||||
|
emptyCount = 0;
|
||||||
|
}
|
||||||
|
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('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active color
|
||||||
|
sb.append(" " + log.getActiveColor().firstChar());
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
final LoggedMove lastMove = log.getLast();
|
||||||
|
|
||||||
|
// En passant availabillity
|
||||||
|
sb.append(" " + (lastMove == null || lastMove.enPassant == null ? "-" : lastMove.enPassant.toSAN()));
|
||||||
|
|
||||||
|
// 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) {
|
public Piece get(Position pos) {
|
||||||
return boardArr[pos.x][pos.y];
|
return boardArr[pos.x][pos.y];
|
||||||
}
|
}
|
||||||
@ -274,4 +509,14 @@ public class Board implements Cloneable {
|
|||||||
* @return The board array
|
* @return The board array
|
||||||
*/
|
*/
|
||||||
public Piece[][] getBoardArr() { return boardArr; }
|
public Piece[][] getBoardArr() { return boardArr; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The active color for the next move
|
||||||
|
*/
|
||||||
|
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; }
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,39 @@ public class King extends Piece {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValidMove(Move move) {
|
public boolean isValidMove(Move move) {
|
||||||
return move.xDist <= 1 && move.yDist <= 1 && isFreePath(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -29,6 +61,12 @@ public class King extends Piece {
|
|||||||
Move move = new Move(pos, new Position(i, j));
|
Move move = new Move(pos, new Position(i, j));
|
||||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) moves.add(move);
|
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) moves.add(move);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Castling
|
||||||
|
// TODO: Condition: cannot castle out of, through or into check
|
||||||
|
if (canCastleKingside()) moves.add(new Move(pos, new Position(6, pos.y), Move.Type.CASTLING));
|
||||||
|
if (canCastleQueenside()) moves.add(new Move(pos, new Position(2, pos.y), Move.Type.CASTLING));
|
||||||
|
|
||||||
return moves;
|
return moves;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@ public class Knight extends Piece {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValidMove(Move move) {
|
public boolean isValidMove(Move move) {
|
||||||
return Math.abs(move.xDist - move.yDist) == 1 && (move.xDist == 1 || move.yDist == 1) && isFreePath(move);
|
return Math.abs(move.xDist - move.yDist) == 1
|
||||||
|
&& (move.xDist == 1 && move.yDist == 2 || move.xDist == 2 && move.yDist == 1) && checkDestination(move);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
|
private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
|
||||||
|
87
src/dev/kske/chess/board/Log.java
Normal file
87
src/dev/kske/chess/board/Log.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package dev.kske.chess.board;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class Log implements Cloneable {
|
||||||
|
|
||||||
|
private List<LoggedMove> moves;
|
||||||
|
private Color activeColor;
|
||||||
|
|
||||||
|
public Log() {
|
||||||
|
moves = new ArrayList<>();
|
||||||
|
activeColor = Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Fullmove counter and halfmove clock
|
||||||
|
int fullmoveCounter, halfmoveClock;
|
||||||
|
if (moves.isEmpty()) {
|
||||||
|
fullmoveCounter = 1;
|
||||||
|
halfmoveClock = 0;
|
||||||
|
} else {
|
||||||
|
fullmoveCounter = getLast().fullmoveCounter;
|
||||||
|
if (activeColor == Color.BLACK) ++fullmoveCounter;
|
||||||
|
halfmoveClock = capturedPiece != null || pawnMove ? 0 : getLast().halfmoveClock + 1;
|
||||||
|
}
|
||||||
|
activeColor = activeColor.opposite();
|
||||||
|
moves.add(new LoggedMove(move, capturedPiece, enPassant, fullmoveCounter, halfmoveClock));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoggedMove getLast() { return moves.isEmpty() ? null : moves.get(moves.size() - 1); }
|
||||||
|
|
||||||
|
public void removeLast() {
|
||||||
|
if (!moves.isEmpty()) {
|
||||||
|
activeColor = activeColor.opposite();
|
||||||
|
moves.remove(moves.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
moves.clear();
|
||||||
|
activeColor = Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
Log log = null;
|
||||||
|
try {
|
||||||
|
log = (Log) super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
log.moves = new ArrayList<>();
|
||||||
|
log.moves.addAll(this.moves);
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getActiveColor() { return activeColor; }
|
||||||
|
|
||||||
|
public static class LoggedMove {
|
||||||
|
|
||||||
|
public final Move move;
|
||||||
|
public final Piece capturedPiece;
|
||||||
|
public final Position enPassant;
|
||||||
|
public final int fullmoveCounter, halfmoveClock;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,20 +10,31 @@ public class Move {
|
|||||||
|
|
||||||
public final Position pos, dest;
|
public final Position pos, dest;
|
||||||
public final int xDist, yDist, xSign, ySign;
|
public final int xDist, yDist, xSign, ySign;
|
||||||
|
public Type type;
|
||||||
|
|
||||||
public Move(Position pos, Position dest) {
|
public Move(Position pos, Position dest, Type type) {
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
this.dest = dest;
|
this.dest = dest;
|
||||||
|
this.type = type;
|
||||||
xDist = Math.abs(dest.x - pos.x);
|
xDist = Math.abs(dest.x - pos.x);
|
||||||
yDist = Math.abs(dest.y - pos.y);
|
yDist = Math.abs(dest.y - pos.y);
|
||||||
xSign = (int) Math.signum(dest.x - pos.x);
|
xSign = (int) Math.signum(dest.x - pos.x);
|
||||||
ySign = (int) Math.signum(dest.y - pos.y);
|
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) {
|
public Move(int xPos, int yPos, int xDest, int yDest) {
|
||||||
this(new Position(xPos, yPos), new Position(xDest, yDest));
|
this(new Position(xPos, yPos), new Position(xDest, yDest));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Move fromSAN(String move) {
|
||||||
|
return new Move(Position.fromSAN(move.substring(0, 2)),
|
||||||
|
Position.fromSAN(move.substring(2)));
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isHorizontal() { return yDist == 0; }
|
public boolean isHorizontal() { return yDist == 0; }
|
||||||
|
|
||||||
public boolean isVertical() { return xDist == 0; }
|
public boolean isVertical() { return xDist == 0; }
|
||||||
@ -34,4 +45,8 @@ public class Move {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("%s -> %s", pos, dest);
|
return String.format("%s -> %s", pos, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static enum Type {
|
||||||
|
NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,25 @@ public class Pawn extends Piece {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValidMove(Move move) {
|
public boolean isValidMove(Move move) {
|
||||||
// TODO: en passant, pawn promotion
|
|
||||||
boolean step = move.isVertical() && move.yDist == 1;
|
boolean step = move.isVertical() && move.yDist == 1;
|
||||||
boolean doubleStep = move.isVertical() && move.yDist == 2;
|
boolean doubleStep = move.isVertical() && move.yDist == 2;
|
||||||
boolean strafe = move.isDiagonal() && move.xDist == 1;
|
boolean strafe = move.isDiagonal() && move.xDist == 1;
|
||||||
|
boolean enPassant = false;
|
||||||
if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
|
if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
|
||||||
else doubleStep &= move.pos.y == 1;
|
else doubleStep &= move.pos.y == 1;
|
||||||
return (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move);
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -43,8 +55,6 @@ public class Pawn extends Piece {
|
|||||||
|
|
||||||
int sign = getColor() == Color.WHITE ? -1 : 1;
|
int sign = getColor() == Color.WHITE ? -1 : 1;
|
||||||
|
|
||||||
if (sign == -1 && pos.y == 1 || sign == 1 && pos.y == 7) return moves;
|
|
||||||
|
|
||||||
// Strafe left
|
// Strafe left
|
||||||
if (pos.x > 0) {
|
if (pos.x > 0) {
|
||||||
Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
|
Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
|
||||||
@ -68,6 +78,17 @@ public class Pawn extends Piece {
|
|||||||
Move move = new Move(pos, new Position(pos.x, pos.y + 2 * sign));
|
Move move = new Move(pos, new Position(pos.x, pos.y + 2 * sign));
|
||||||
if (isFreePath(move)) moves.add(move);
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
return moves;
|
return moves;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,9 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public abstract class Piece implements Cloneable {
|
public abstract class Piece implements Cloneable {
|
||||||
|
|
||||||
protected Color color;
|
private final Color color;
|
||||||
protected Board board;
|
protected Board board;
|
||||||
|
private int moveCounter;
|
||||||
|
|
||||||
public Piece(Color color, Board board) {
|
public Piece(Color color, Board board) {
|
||||||
this.color = color;
|
this.color = color;
|
||||||
@ -22,11 +23,10 @@ public abstract class Piece implements Cloneable {
|
|||||||
public List<Move> getMoves(Position pos) {
|
public List<Move> getMoves(Position pos) {
|
||||||
List<Move> moves = getPseudolegalMoves(pos);
|
List<Move> moves = getPseudolegalMoves(pos);
|
||||||
for (Iterator<Move> iterator = moves.iterator(); iterator.hasNext();) {
|
for (Iterator<Move> iterator = moves.iterator(); iterator.hasNext();) {
|
||||||
Move move = iterator.next();
|
Move move = iterator.next();
|
||||||
Piece capturePiece = board.move(move);
|
board.move(move);
|
||||||
if (board.checkCheck(getColor()))
|
if (board.checkCheck(getColor())) iterator.remove();
|
||||||
iterator.remove();
|
board.revert();
|
||||||
board.revert(move, capturePiece);
|
|
||||||
}
|
}
|
||||||
return moves;
|
return moves;
|
||||||
}
|
}
|
||||||
@ -35,8 +35,16 @@ public abstract class Piece implements Cloneable {
|
|||||||
|
|
||||||
public abstract boolean isValidMove(Move move);
|
public abstract boolean isValidMove(Move move);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks, if the squares between the position and the destination of a move are
|
||||||
|
* free.
|
||||||
|
*
|
||||||
|
* @param move The move to check
|
||||||
|
*/
|
||||||
protected boolean isFreePath(Move move) {
|
protected boolean isFreePath(Move move) {
|
||||||
// Only check destination by default
|
for (int i = move.pos.x + move.xSign, j = move.pos.y + move.ySign; i != move.dest.x
|
||||||
|
|| j != move.dest.y; i += move.xSign, j += move.ySign)
|
||||||
|
if (board.getBoardArr()[i][j] != null) return false;
|
||||||
return checkDestination(move);
|
return checkDestination(move);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,8 +74,18 @@ public abstract class Piece implements Cloneable {
|
|||||||
|
|
||||||
public Color getColor() { return color; }
|
public Color getColor() { return color; }
|
||||||
|
|
||||||
|
public int getMoveCounter() { return moveCounter; }
|
||||||
|
|
||||||
|
public void incMoveCounter() {
|
||||||
|
++moveCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decMoveCounter() {
|
||||||
|
--moveCounter;
|
||||||
|
}
|
||||||
|
|
||||||
public static enum Type {
|
public static enum Type {
|
||||||
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
|
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN
|
||||||
}
|
}
|
||||||
|
|
||||||
public static enum Color {
|
public static enum Color {
|
||||||
@ -76,5 +94,9 @@ public abstract class Piece implements Cloneable {
|
|||||||
public Color opposite() {
|
public Color opposite() {
|
||||||
return this == WHITE ? BLACK : WHITE;
|
return this == WHITE ? BLACK : WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public char firstChar() {
|
||||||
|
return this == WHITE ? 'w' : 'b';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,36 @@ public class Position {
|
|||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Position fromSAN(String pos) {
|
||||||
|
return new Position(pos.charAt(0) - 97, 8 - Character.getNumericValue(pos.charAt(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toSAN() {
|
||||||
|
return String.valueOf((char) (x + 97)) + String.valueOf(8 - y);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("[%d, %d]", x, y);
|
return String.format("[%d, %d]", x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + x;
|
||||||
|
result = prime * result + y;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null) return false;
|
||||||
|
if (getClass() != obj.getClass()) return false;
|
||||||
|
Position other = (Position) obj;
|
||||||
|
if (x != other.x) return false;
|
||||||
|
if (y != other.y) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,22 +20,6 @@ public class Queen extends Piece {
|
|||||||
return ((move.isHorizontal() || move.isVertical()) || move.isDiagonal()) && isFreePath(move);
|
return ((move.isHorizontal() || move.isVertical()) || move.isDiagonal()) && isFreePath(move);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isFreePath(Move move) {
|
|
||||||
if (move.isHorizontal()) {
|
|
||||||
for (int i = move.pos.x + move.xSign; i != move.dest.x; i += move.xSign)
|
|
||||||
if (board.getBoardArr()[i][move.pos.y] != null) return false;
|
|
||||||
} else if (move.isVertical()) {
|
|
||||||
for (int i = move.pos.y + move.ySign; i != move.dest.y; i += move.ySign)
|
|
||||||
if (board.getBoardArr()[move.pos.x][i] != null) return false;
|
|
||||||
} else {
|
|
||||||
for (int i = move.pos.x + move.xSign, j = move.pos.y
|
|
||||||
+ move.ySign; i != move.dest.x; i += move.xSign, j += move.ySign)
|
|
||||||
if (board.getBoardArr()[i][j] != null) return false;
|
|
||||||
}
|
|
||||||
return checkDestination(move);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Move> getPseudolegalMoves(Position pos) {
|
protected List<Move> getPseudolegalMoves(Position pos) {
|
||||||
List<Move> moves = new ArrayList<>();
|
List<Move> moves = new ArrayList<>();
|
||||||
|
@ -20,18 +20,6 @@ public class Rook extends Piece {
|
|||||||
return (move.isHorizontal() || move.isVertical()) && isFreePath(move);
|
return (move.isHorizontal() || move.isVertical()) && isFreePath(move);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isFreePath(Move move) {
|
|
||||||
if (move.isHorizontal()) {
|
|
||||||
for (int i = move.pos.x + move.xSign; i != move.dest.x; i += move.xSign)
|
|
||||||
if (board.getBoardArr()[i][move.pos.y] != null) return false;
|
|
||||||
} else {
|
|
||||||
for (int i = move.pos.y + move.ySign; i != move.dest.y; i += move.ySign)
|
|
||||||
if (board.getBoardArr()[move.pos.x][i] != null) return false;
|
|
||||||
}
|
|
||||||
return checkDestination(move);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Move> getPseudolegalMoves(Position pos) {
|
protected List<Move> getPseudolegalMoves(Position pos) {
|
||||||
List<Move> moves = new ArrayList<>();
|
List<Move> moves = new ArrayList<>();
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
package dev.kske.chess.game;
|
package dev.kske.chess.game;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
|
||||||
import dev.kske.chess.board.Board;
|
import dev.kske.chess.board.Board;
|
||||||
import dev.kske.chess.board.GameState;
|
import dev.kske.chess.board.GameState;
|
||||||
import dev.kske.chess.board.Move;
|
import dev.kske.chess.board.Move;
|
||||||
import dev.kske.chess.board.Piece.Color;
|
import dev.kske.chess.board.Piece.Color;
|
||||||
|
import dev.kske.chess.game.ai.AIPlayer;
|
||||||
import dev.kske.chess.ui.BoardComponent;
|
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>
|
* Project: <strong>Chess</strong><br>
|
||||||
@ -18,37 +26,92 @@ public class Game {
|
|||||||
|
|
||||||
private Map<Color, Player> players;
|
private Map<Color, Player> players;
|
||||||
private Board board;
|
private Board board;
|
||||||
|
private OverlayComponent overlayComponent;
|
||||||
private BoardComponent boardComponent;
|
private BoardComponent boardComponent;
|
||||||
|
|
||||||
public Game(Map<Color, Player> players, BoardComponent boardComponent) {
|
public Game(BoardPane boardPane, String whiteName, String blackName) {
|
||||||
this.players = players;
|
players = new HashMap<>();
|
||||||
this.boardComponent = boardComponent;
|
board = new Board();
|
||||||
this.board = boardComponent.getBoard();
|
overlayComponent = boardPane.getOverlayComponent();
|
||||||
|
boardComponent = boardPane.getBoardComponent();
|
||||||
|
boardComponent.setBoard(board);
|
||||||
|
|
||||||
|
players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE));
|
||||||
|
players.put(Color.BLACK, getPlayer(blackName, Color.BLACK));
|
||||||
|
|
||||||
// Initialize the game variable in each player
|
// Initialize the game variable in each player
|
||||||
players.values().forEach(player -> player.setGame(this));
|
players.values().forEach(player -> player.setGame(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Player getPlayer(String name, Color color) {
|
||||||
|
switch (name) {
|
||||||
|
case "Natural Player":
|
||||||
|
return new NaturalPlayer(color, overlayComponent);
|
||||||
|
case "AI Player":
|
||||||
|
return new AIPlayer(color, 4, -10);
|
||||||
|
default:
|
||||||
|
for (EngineInfo info : EngineUtil.getEngineInfos())
|
||||||
|
if (info.name.equals(name)) return new UCIPlayer(color, info.path);
|
||||||
|
System.err.println("Invalid player name: " + name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
case CHECKMATE:
|
||||||
|
case STALEMATE:
|
||||||
|
String result = String.format("%s in %s!%n", player.color.opposite(), eventType);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
} else player.requestMove();
|
||||||
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
players.get(Color.WHITE).requestMove();
|
players.get(Color.WHITE).requestMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMove(Player player, Move move) {
|
public void reset() {
|
||||||
if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
|
players.values().forEach(Player::cancelMove);
|
||||||
System.out.printf("%s: %s%n", player.color, move);
|
board.initializeDefaultPositions();
|
||||||
GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite());
|
boardComponent.repaint();
|
||||||
switch (eventType) {
|
overlayComponent.clearDots();
|
||||||
case CHECKMATE:
|
overlayComponent.clearArrow();
|
||||||
case STALEMATE:
|
|
||||||
System.out.printf("%s in %s!%n", player.color.opposite(), eventType);
|
|
||||||
break;
|
|
||||||
case CHECK:
|
|
||||||
System.out.printf("%s in check!%n", player.color.opposite());
|
|
||||||
default:
|
|
||||||
boardComponent.repaint();
|
|
||||||
players.get(player.color.opposite()).requestMove();
|
|
||||||
|
|
||||||
}
|
|
||||||
} else player.requestMove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removed all connections between the game and the UI.
|
||||||
|
*/
|
||||||
|
public void disconnect() {
|
||||||
|
players.values().forEach(Player::disconnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swapColors() {
|
||||||
|
players.values().forEach(Player::cancelMove);
|
||||||
|
Player white = players.get(Color.WHITE);
|
||||||
|
Player black = players.get(Color.BLACK);
|
||||||
|
white.setColor(Color.BLACK);
|
||||||
|
black.setColor(Color.WHITE);
|
||||||
|
players.put(Color.WHITE, black);
|
||||||
|
players.put(Color.BLACK, white);
|
||||||
|
players.get(board.getActiveColor()).requestMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Board getBoard() { return board; }
|
||||||
|
|
||||||
|
public Map<Color, Player> getPlayers() { return players; }
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package dev.kske.chess.game;
|
package dev.kske.chess.game;
|
||||||
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import dev.kske.chess.board.Board;
|
import dev.kske.chess.board.Board;
|
||||||
import dev.kske.chess.board.Move;
|
import dev.kske.chess.board.Move;
|
||||||
|
import dev.kske.chess.board.Move.Type;
|
||||||
import dev.kske.chess.board.Piece.Color;
|
import dev.kske.chess.board.Piece.Color;
|
||||||
import dev.kske.chess.board.Position;
|
import dev.kske.chess.board.Position;
|
||||||
import dev.kske.chess.ui.OverlayComponent;
|
import dev.kske.chess.ui.OverlayComponent;
|
||||||
@ -17,47 +18,72 @@ import dev.kske.chess.ui.OverlayComponent;
|
|||||||
* Created: <strong>06.07.2019</strong><br>
|
* Created: <strong>06.07.2019</strong><br>
|
||||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||||
*/
|
*/
|
||||||
public class NaturalPlayer extends Player {
|
public class NaturalPlayer extends Player implements MouseListener {
|
||||||
|
|
||||||
private boolean moveRequested;
|
private final OverlayComponent overlayComponent;
|
||||||
|
|
||||||
public NaturalPlayer(Board board, Color color, OverlayComponent overlayComponent) {
|
private boolean moveRequested;
|
||||||
super(board, color);
|
private Position pos;
|
||||||
moveRequested = false;
|
|
||||||
overlayComponent.addMouseListener(new MouseAdapter() {
|
|
||||||
|
|
||||||
private Position pos;
|
public NaturalPlayer(Color color, OverlayComponent overlayComponent) {
|
||||||
|
super(color);
|
||||||
|
this.overlayComponent = overlayComponent;
|
||||||
|
name = "Player";
|
||||||
|
moveRequested = false;
|
||||||
|
|
||||||
@Override
|
overlayComponent.addMouseListener(this);
|
||||||
public void mousePressed(MouseEvent evt) {
|
|
||||||
if (!moveRequested) return;
|
|
||||||
if (pos == null) {
|
|
||||||
pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
|
|
||||||
evt.getPoint().y / overlayComponent.getTileSize());
|
|
||||||
|
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
|
|
||||||
evt.getPoint().y / overlayComponent.getTileSize());
|
|
||||||
|
|
||||||
overlayComponent.clearDots();
|
|
||||||
moveRequested = false;
|
|
||||||
game.onMove(NaturalPlayer.this, new Move(pos, dest));
|
|
||||||
pos = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void requestMove() {
|
public void requestMove() {
|
||||||
moveRequested = true;
|
moveRequested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelMove() {
|
||||||
|
moveRequested = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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());
|
||||||
|
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
|
||||||
|
evt.getPoint().y / overlayComponent.getTileSize());
|
||||||
|
|
||||||
|
overlayComponent.clearDots();
|
||||||
|
moveRequested = false;
|
||||||
|
game.onMove(NaturalPlayer.this, new Move(pos, dest, Type.UNKNOWN));
|
||||||
|
pos = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(MouseEvent e) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e) {}
|
||||||
}
|
}
|
||||||
|
@ -11,20 +11,27 @@ import dev.kske.chess.board.Piece.Color;
|
|||||||
*/
|
*/
|
||||||
public abstract class Player {
|
public abstract class Player {
|
||||||
|
|
||||||
protected Game game;
|
protected Game game;
|
||||||
protected Board board;
|
protected Board board;
|
||||||
protected Color color;
|
protected Color color;
|
||||||
|
protected String name;
|
||||||
|
|
||||||
public Player(Board board, Color color) {
|
public Player(Color color) {
|
||||||
this.board = board;
|
this.color = color;
|
||||||
this.color = color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void requestMove();
|
public abstract void requestMove();
|
||||||
|
|
||||||
|
public abstract void cancelMove();
|
||||||
|
|
||||||
|
public abstract void disconnect();
|
||||||
|
|
||||||
public Game getGame() { return game; }
|
public Game getGame() { return game; }
|
||||||
|
|
||||||
public void setGame(Game game) { this.game = game; }
|
public void setGame(Game game) {
|
||||||
|
this.game = game;
|
||||||
|
board = game.getBoard();
|
||||||
|
}
|
||||||
|
|
||||||
public Board getBoard() { return board; }
|
public Board getBoard() { return board; }
|
||||||
|
|
||||||
@ -33,4 +40,8 @@ public abstract class Player {
|
|||||||
public Color getColor() { return color; }
|
public Color getColor() { return color; }
|
||||||
|
|
||||||
public void setColor(Color color) { this.color = color; }
|
public void setColor(Color color) { this.color = color; }
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
|
||||||
|
public void setName(String name) { this.name = name; }
|
||||||
}
|
}
|
||||||
|
92
src/dev/kske/chess/game/UCIPlayer.java
Normal file
92
src/dev/kske/chess/game/UCIPlayer.java
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package dev.kske.chess.game;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import dev.kske.chess.board.Move;
|
||||||
|
import dev.kske.chess.board.Piece.Color;
|
||||||
|
import dev.kske.chess.uci.UCIHandle;
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class UCIPlayer extends Player implements UCIListener {
|
||||||
|
|
||||||
|
private UCIHandle handle;
|
||||||
|
|
||||||
|
public UCIPlayer(Color color, String enginePath) {
|
||||||
|
super(color);
|
||||||
|
try {
|
||||||
|
handle = new UCIHandle(enginePath);
|
||||||
|
handle.setListener(this);
|
||||||
|
handle.start();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestMove() {
|
||||||
|
handle.positionFEN(board.toFEN());
|
||||||
|
handle.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelMove() {
|
||||||
|
handle.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
handle.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onIdName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBestMove(String move) {
|
||||||
|
Move moveObj = Move.fromSAN(move);
|
||||||
|
game.onMove(this, moveObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBestMove(String move, Move ponderMove) {
|
||||||
|
onBestMove(move);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCopyProtectionChecking() {
|
||||||
|
System.out.println("Copy protection checking...");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCopyProtectionOk() {
|
||||||
|
System.out.println("Copy protection ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCopyProtectionError() {
|
||||||
|
System.err.println("Copy protection error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistrationChecking() {
|
||||||
|
System.out.println("Registration checking...");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistrationOk() {
|
||||||
|
System.out.println("Registration ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistrationError() {
|
||||||
|
System.err.println("Registration error!");
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
@ -24,15 +25,23 @@ public class AIPlayer extends Player {
|
|||||||
|
|
||||||
private int availableProcessors;
|
private int availableProcessors;
|
||||||
private int maxDepth;
|
private int maxDepth;
|
||||||
|
private int alphaBetaThreshold;
|
||||||
|
|
||||||
public AIPlayer(Board board, Color color, int maxDepth) {
|
private volatile boolean exitRequested;
|
||||||
super(board, color);
|
private volatile ExecutorService executor;
|
||||||
availableProcessors = Runtime.getRuntime().availableProcessors();
|
|
||||||
this.maxDepth = maxDepth;
|
public AIPlayer(Color color, int maxDepth, int alphaBetaThreshold) {
|
||||||
|
super(color);
|
||||||
|
name = "AIPlayer";
|
||||||
|
availableProcessors = Runtime.getRuntime().availableProcessors();
|
||||||
|
this.maxDepth = maxDepth;
|
||||||
|
this.alphaBetaThreshold = alphaBetaThreshold;
|
||||||
|
exitRequested = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void requestMove() {
|
public void requestMove() {
|
||||||
|
exitRequested = false;
|
||||||
/*
|
/*
|
||||||
* Define some processing threads, split the available moves between them and
|
* Define some processing threads, split the available moves between them and
|
||||||
* retrieve the result after their execution.
|
* retrieve the result after their execution.
|
||||||
@ -41,8 +50,8 @@ public class AIPlayer extends Player {
|
|||||||
/*
|
/*
|
||||||
* Get a copy of the board and the available moves.
|
* Get a copy of the board and the available moves.
|
||||||
*/
|
*/
|
||||||
Board board = (Board) AIPlayer.this.board.clone();
|
Board board = (Board) AIPlayer.this.board.clone();
|
||||||
List<Move> moves = board.getMoves(color);
|
List<Move> moves = board.getMoves(color);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Define move processors and split the available moves between them.
|
* Define move processors and split the available moves between them.
|
||||||
@ -55,16 +64,16 @@ public class AIPlayer extends Player {
|
|||||||
for (int i = 0; i < numThreads; i++) {
|
for (int i = 0; i < numThreads; i++) {
|
||||||
if (rem-- > 0) ++endIndex;
|
if (rem-- > 0) ++endIndex;
|
||||||
endIndex += step;
|
endIndex += step;
|
||||||
processors.add(
|
processors.add(new MoveProcessor((Board) board.clone(), moves.subList(beginIndex, endIndex), color,
|
||||||
new MoveProcessor((Board) board.clone(), moves.subList(beginIndex, endIndex), color, maxDepth));
|
maxDepth, alphaBetaThreshold));
|
||||||
beginIndex = endIndex;
|
beginIndex = endIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Execute processors, get the best result and pass it back to the Game class
|
* Execute processors, get the best result and pass it back to the Game class
|
||||||
*/
|
*/
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
|
executor = Executors.newFixedThreadPool(numThreads);
|
||||||
List<ProcessingResult> results = new ArrayList<>(numThreads);
|
List<ProcessingResult> results = new ArrayList<>(numThreads);
|
||||||
try {
|
try {
|
||||||
List<Future<ProcessingResult>> futures = executor.invokeAll(processors);
|
List<Future<ProcessingResult>> futures = executor.invokeAll(processors);
|
||||||
for (Future<ProcessingResult> f : futures)
|
for (Future<ProcessingResult> f : futures)
|
||||||
@ -74,7 +83,23 @@ public class AIPlayer extends Player {
|
|||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
results.sort((r1, r2) -> Integer.compare(r2.score, r1.score));
|
results.sort((r1, r2) -> Integer.compare(r2.score, r1.score));
|
||||||
SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move));
|
if (!exitRequested) SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move));
|
||||||
}, "AIPlayer calculation setup").start();
|
}, "AIPlayer calculation setup").start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelMove() {
|
||||||
|
exitRequested = true;
|
||||||
|
if (executor != null) {
|
||||||
|
executor.shutdownNow();
|
||||||
|
try {
|
||||||
|
executor.awaitTermination(500, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import java.util.concurrent.Callable;
|
|||||||
|
|
||||||
import dev.kske.chess.board.Board;
|
import dev.kske.chess.board.Board;
|
||||||
import dev.kske.chess.board.Move;
|
import dev.kske.chess.board.Move;
|
||||||
import dev.kske.chess.board.Piece;
|
|
||||||
import dev.kske.chess.board.Piece.Color;
|
import dev.kske.chess.board.Piece.Color;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,17 +16,19 @@ import dev.kske.chess.board.Piece.Color;
|
|||||||
public class MoveProcessor implements Callable<ProcessingResult> {
|
public class MoveProcessor implements Callable<ProcessingResult> {
|
||||||
|
|
||||||
private final Board board;
|
private final Board board;
|
||||||
private final List<Move> rootMoves;;
|
private final List<Move> rootMoves;
|
||||||
private final Color color;
|
private final Color color;
|
||||||
private final int maxDepth;
|
private final int maxDepth;
|
||||||
|
private final int alphaBetaThreshold;
|
||||||
|
|
||||||
private Move bestMove;
|
private Move bestMove;
|
||||||
|
|
||||||
public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth) {
|
public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth, int alphaBetaThreshold) {
|
||||||
this.board = board;
|
this.board = board;
|
||||||
this.rootMoves = rootMoves;
|
this.rootMoves = rootMoves;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.maxDepth = maxDepth;
|
this.maxDepth = maxDepth;
|
||||||
|
this.alphaBetaThreshold = alphaBetaThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -39,12 +40,12 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
|||||||
private int miniMax(Board board, List<Move> moves, Color color, int depth) {
|
private int miniMax(Board board, List<Move> moves, Color color, int depth) {
|
||||||
int bestValue = Integer.MIN_VALUE;
|
int bestValue = Integer.MIN_VALUE;
|
||||||
for (Move move : moves) {
|
for (Move move : moves) {
|
||||||
Piece capturePiece = board.move(move);
|
board.move(move);
|
||||||
int teamValue = board.evaluate(color);
|
int teamValue = board.evaluate(color);
|
||||||
int enemyValue = board.evaluate(color.opposite());
|
int enemyValue = board.evaluate(color.opposite());
|
||||||
int valueChange = teamValue - enemyValue;
|
int valueChange = teamValue - enemyValue;
|
||||||
|
|
||||||
if (depth < maxDepth && valueChange >= 0)
|
if (depth < maxDepth && valueChange >= alphaBetaThreshold)
|
||||||
valueChange -= miniMax(board, board.getMoves(color.opposite()), color.opposite(), depth + 1);
|
valueChange -= miniMax(board, board.getMoves(color.opposite()), color.opposite(), depth + 1);
|
||||||
|
|
||||||
if (valueChange > bestValue) {
|
if (valueChange > bestValue) {
|
||||||
@ -52,7 +53,7 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
|||||||
if (depth == 0) bestMove = move;
|
if (depth == 0) bestMove = move;
|
||||||
}
|
}
|
||||||
|
|
||||||
board.revert(move, capturePiece);
|
board.revert();
|
||||||
}
|
}
|
||||||
return bestValue;
|
return bestValue;
|
||||||
}
|
}
|
||||||
|
143
src/dev/kske/chess/uci/UCIHandle.java
Normal file
143
src/dev/kske/chess/uci/UCIHandle.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package dev.kske.chess.uci;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
*/
|
||||||
|
public class UCIHandle {
|
||||||
|
|
||||||
|
private final Process process;
|
||||||
|
private final PrintWriter out;
|
||||||
|
private final UCIReceiver receiver;
|
||||||
|
|
||||||
|
public UCIHandle(String enginePath) throws IOException {
|
||||||
|
process = new ProcessBuilder(enginePath).start();
|
||||||
|
out = new PrintWriter(process.getOutputStream(), true);
|
||||||
|
receiver = new UCIReceiver(process.getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
new Thread(receiver, "UCI Receiver").start();
|
||||||
|
uci();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the engine to use 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronized the engine with the GUI
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes an internal parameter of the engine.
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the engine
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the engine to postpone the registration.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the position in its initial state.
|
||||||
|
*/
|
||||||
|
public void startPosition() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: go with parameters
|
||||||
|
|
||||||
|
public void go() {
|
||||||
|
out.println("go");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops calculation as soon as possible.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
out.println("stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the engine that the user has played the expected move.
|
||||||
|
*/
|
||||||
|
public void ponderHit() {
|
||||||
|
out.println("ponderhit");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quits the engine process as soon as possible.
|
||||||
|
*/
|
||||||
|
public void quit() {
|
||||||
|
out.println("quit");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(UCIListener listener) {
|
||||||
|
receiver.addListener(listener);
|
||||||
|
}
|
||||||
|
}
|
198
src/dev/kske/chess/uci/UCIInfo.java
Normal file
198
src/dev/kske/chess/uci/UCIInfo.java
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
package dev.kske.chess.uci;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class UCIInfo {
|
||||||
|
|
||||||
|
private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload,
|
||||||
|
cpunr;
|
||||||
|
private List<Move> pv, refutation, currline;
|
||||||
|
private Move currmove;
|
||||||
|
private Score score;
|
||||||
|
private String displayString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains every parameter for the UCI info command. Helpful for parsing
|
||||||
|
* multi-value parameters.
|
||||||
|
*/
|
||||||
|
private static final List<String> params = Arrays.asList("depth",
|
||||||
|
"seldepth",
|
||||||
|
"time",
|
||||||
|
"nodes",
|
||||||
|
"multipv",
|
||||||
|
"currmove",
|
||||||
|
"currmovenumber",
|
||||||
|
"hashfull",
|
||||||
|
"nps",
|
||||||
|
"tbhits",
|
||||||
|
"sbhits",
|
||||||
|
"cpuload",
|
||||||
|
"string",
|
||||||
|
"score",
|
||||||
|
"pv",
|
||||||
|
"refutation",
|
||||||
|
"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++)
|
||||||
|
switch (tokens[i]) {
|
||||||
|
// Single parameter info
|
||||||
|
case "depth":
|
||||||
|
depth = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "seldepth":
|
||||||
|
seldepth = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "time":
|
||||||
|
time = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "nodes":
|
||||||
|
nodes = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "multipv":
|
||||||
|
multipv = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "currmove":
|
||||||
|
currmove = Move.fromSAN(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "currmovenumber":
|
||||||
|
currmovenumber = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "hashfull":
|
||||||
|
hashfull = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "nps":
|
||||||
|
nps = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "tbhits":
|
||||||
|
tbhits = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "sbhits":
|
||||||
|
sbhits = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "cpuload":
|
||||||
|
cpuload = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
displayString = tokens[++i];
|
||||||
|
break;
|
||||||
|
case "score":
|
||||||
|
score = new Score(line.substring(line.indexOf("score") + tokens[i].length() + 1));
|
||||||
|
i += score.getLength() + 1;
|
||||||
|
break;
|
||||||
|
case "pv":
|
||||||
|
while (++i < tokens.length && !params.contains(tokens[i]))
|
||||||
|
pv.add(Move.fromSAN(tokens[i]));
|
||||||
|
break;
|
||||||
|
case "refutation":
|
||||||
|
while (++i < tokens.length && !params.contains(tokens[i]))
|
||||||
|
refutation.add(Move.fromSAN(tokens[i]));
|
||||||
|
break;
|
||||||
|
// TODO: currline
|
||||||
|
case "currline":
|
||||||
|
while (++i < tokens.length && !params.contains(tokens[i]))
|
||||||
|
;
|
||||||
|
System.err.println("The parameter 'currline' for command 'info' is not yet implemented");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.printf("Unknown parameter '%s' for command 'info' found!%n", tokens[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDepth() { return depth; }
|
||||||
|
|
||||||
|
public int getSeldepth() { return seldepth; }
|
||||||
|
|
||||||
|
public int getTime() { return time; }
|
||||||
|
|
||||||
|
public int getNodes() { return nodes; }
|
||||||
|
|
||||||
|
public int getMultipv() { return multipv; }
|
||||||
|
|
||||||
|
public int getCurrmovenumber() { return currmovenumber; }
|
||||||
|
|
||||||
|
public int getHashfull() { return hashfull; }
|
||||||
|
|
||||||
|
public int getNps() { return nps; }
|
||||||
|
|
||||||
|
public int getTbhits() { return tbhits; }
|
||||||
|
|
||||||
|
public int getSbhits() { return sbhits; }
|
||||||
|
|
||||||
|
public int getCpuload() { return cpuload; }
|
||||||
|
|
||||||
|
public int getCpunr() { return cpunr; }
|
||||||
|
|
||||||
|
public List<Move> getPv() { return pv; }
|
||||||
|
|
||||||
|
public List<Move> getRefutation() { return refutation; }
|
||||||
|
|
||||||
|
public List<Move> getCurrline() { return currline; }
|
||||||
|
|
||||||
|
public Move getCurrmove() { return currmove; }
|
||||||
|
|
||||||
|
public Score getScore() { return score; }
|
||||||
|
|
||||||
|
public String getDisplayString() { return displayString; }
|
||||||
|
|
||||||
|
public static class Score {
|
||||||
|
|
||||||
|
private int cp, mate;
|
||||||
|
private boolean lowerbound, upperbound;
|
||||||
|
private int length;
|
||||||
|
|
||||||
|
public Score(String line) {
|
||||||
|
String[] tokens = line.split(" ");
|
||||||
|
int i = 0;
|
||||||
|
for (; i < tokens.length; i++) {
|
||||||
|
if (params.contains(tokens[i])) break;
|
||||||
|
switch (tokens[i]) {
|
||||||
|
case "cp":
|
||||||
|
cp = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "mate":
|
||||||
|
mate = Integer.parseInt(tokens[++i]);
|
||||||
|
break;
|
||||||
|
case "lowerbound":
|
||||||
|
lowerbound = true;
|
||||||
|
break;
|
||||||
|
case "upperbound":
|
||||||
|
upperbound = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.printf("Unknown parameter '%s' for command 'score' found!%n", tokens[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCp() { return cp; }
|
||||||
|
|
||||||
|
public int getMate() { return mate; }
|
||||||
|
|
||||||
|
public boolean isLowerbound() { return lowerbound; }
|
||||||
|
|
||||||
|
public boolean isUpperbound() { return upperbound; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of tokens this 'score' command contains (including
|
||||||
|
* itself).
|
||||||
|
*/
|
||||||
|
public int getLength() { return length; }
|
||||||
|
}
|
||||||
|
}
|
94
src/dev/kske/chess/uci/UCIListener.java
Normal file
94
src/dev/kske/chess/uci/UCIListener.java
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package dev.kske.chess.uci;
|
||||||
|
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public interface UCIListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the name of the engine.
|
||||||
|
*
|
||||||
|
* @param name The name of the engine
|
||||||
|
*/
|
||||||
|
default void onIdName(String name) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the author of the engine.
|
||||||
|
*
|
||||||
|
* @param author The name of the engine's author
|
||||||
|
*/
|
||||||
|
default void onIdAuthor(String author) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine is ready in UCI mode.
|
||||||
|
*/
|
||||||
|
default void onUCIOk() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine has processed all inputs and is ready for new commands.
|
||||||
|
*/
|
||||||
|
default void onReadyOk() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine has stopped searching and has found the best move.
|
||||||
|
*
|
||||||
|
* @param move The best moves the engine has found
|
||||||
|
*/
|
||||||
|
default void onBestMove(String move) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine has stopped searching and has found the best move.
|
||||||
|
*
|
||||||
|
* @param move The best move the engine has found
|
||||||
|
* @param ponderMove The move the engine likes to ponder on
|
||||||
|
*/
|
||||||
|
default void onBestMove(String move, Move ponderMove) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine will check the copy protection now.
|
||||||
|
*/
|
||||||
|
default void onCopyProtectionChecking() {}
|
||||||
|
/**
|
||||||
|
* The engine has successfully checked the copy protection.
|
||||||
|
*/
|
||||||
|
default void onCopyProtectionOk() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine has encountered an error during copy protection checking.
|
||||||
|
*/
|
||||||
|
default void onCopyProtectionError() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine will check the registration now.
|
||||||
|
*/
|
||||||
|
default void onRegistrationChecking() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine has successfully checked the registration.
|
||||||
|
*/
|
||||||
|
default void onRegistrationOk() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine has encountered an error during registration checking.
|
||||||
|
*/
|
||||||
|
default void onRegistrationError() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The engine sends information to the GUI.
|
||||||
|
*
|
||||||
|
* @param additionalInfo Contains all pieces of information to be sent
|
||||||
|
*/
|
||||||
|
default void onInfo(UCIInfo info) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the GUI which parameters can be changed in the engine.
|
||||||
|
*
|
||||||
|
* @param option Option object describing the parameter
|
||||||
|
*/
|
||||||
|
default void onOption(UCIOption option) {}
|
||||||
|
}
|
68
src/dev/kske/chess/uci/UCIOption.java
Normal file
68
src/dev/kske/chess/uci/UCIOption.java
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package dev.kske.chess.uci;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class UCIOption {
|
||||||
|
|
||||||
|
private String name, defaultVal, minVal, maxVal;
|
||||||
|
private GUIType type;
|
||||||
|
private List<String> varList;
|
||||||
|
|
||||||
|
public UCIOption(String line) {
|
||||||
|
varList = new ArrayList<>();
|
||||||
|
String[] tokens = line.split(" ");
|
||||||
|
|
||||||
|
for (int i = 0; i < tokens.length; i++)
|
||||||
|
switch (tokens[i]) {
|
||||||
|
case "name":
|
||||||
|
StringJoiner nameJoiner = new StringJoiner(" ");
|
||||||
|
while (!Arrays.asList("type", "default", "min", "max", "var").contains(tokens[i + 1]))
|
||||||
|
nameJoiner.add(tokens[++i]);
|
||||||
|
name = nameJoiner.toString();
|
||||||
|
break;
|
||||||
|
case "type":
|
||||||
|
type = GUIType.valueOf(tokens[++i].toUpperCase());
|
||||||
|
break;
|
||||||
|
case "default":
|
||||||
|
// Default string may be empty
|
||||||
|
defaultVal = i == tokens.length - 1 ? "" : tokens[++i];
|
||||||
|
break;
|
||||||
|
case "min":
|
||||||
|
minVal = tokens[++i];
|
||||||
|
break;
|
||||||
|
case "max":
|
||||||
|
maxVal = tokens[++i];
|
||||||
|
break;
|
||||||
|
case "var":
|
||||||
|
varList.add(tokens[++i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.printf("Unknown parameter '%s' for command 'option' found!%n", tokens[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
|
||||||
|
public String getDefaultVal() { return defaultVal; }
|
||||||
|
|
||||||
|
public String getMinVal() { return minVal; }
|
||||||
|
|
||||||
|
public String getMaxVal() { return maxVal; }
|
||||||
|
|
||||||
|
public GUIType getType() { return type; }
|
||||||
|
|
||||||
|
public List<String> getVarList() { return varList; }
|
||||||
|
|
||||||
|
public static enum GUIType {
|
||||||
|
CHECK, SPIN, COMBO, BUTTON, STRING
|
||||||
|
}
|
||||||
|
}
|
143
src/dev/kske/chess/uci/UCIReceiver.java
Normal file
143
src/dev/kske/chess/uci/UCIReceiver.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package dev.kske.chess.uci;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class UCIReceiver implements Runnable {
|
||||||
|
|
||||||
|
private final BufferedReader in;
|
||||||
|
|
||||||
|
private List<UCIListener> listeners;
|
||||||
|
|
||||||
|
public UCIReceiver(InputStream in) {
|
||||||
|
this.in = new BufferedReader(new InputStreamReader(in));
|
||||||
|
listeners = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String line;
|
||||||
|
while (!Thread.currentThread().isInterrupted())
|
||||||
|
try {
|
||||||
|
if ((line = in.readLine()) != null && !line.isEmpty()) parse(line);
|
||||||
|
} catch (IndexOutOfBoundsException ex) {
|
||||||
|
System.err.println("Too few arguments were provided!");
|
||||||
|
ex.printStackTrace();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse(String line) {
|
||||||
|
int spaceIndex = line.indexOf(' ');
|
||||||
|
String command = spaceIndex == -1 ? line : line.substring(0, spaceIndex);
|
||||||
|
switch (command) {
|
||||||
|
case "id":
|
||||||
|
parseId(line.substring(command.length() + 1));
|
||||||
|
break;
|
||||||
|
case "uciok":
|
||||||
|
listeners.forEach(UCIListener::onUCIOk);
|
||||||
|
break;
|
||||||
|
case "readyok":
|
||||||
|
listeners.forEach(UCIListener::onReadyOk);
|
||||||
|
break;
|
||||||
|
case "bestmove":
|
||||||
|
parseBestMove(line.substring(command.length() + 1));
|
||||||
|
break;
|
||||||
|
case "copyprotection":
|
||||||
|
parseCopyProtection(line.substring(command.length() + 1));
|
||||||
|
break;
|
||||||
|
case "registration":
|
||||||
|
parseRegistration(line.substring(command.length() + 1));
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
parseInfo(line.substring(command.length() + 1));
|
||||||
|
break;
|
||||||
|
case "option":
|
||||||
|
parseOption(line.substring(command.length() + 1));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.printf("Unknown command '%s' found!%n", command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseId(String line) {
|
||||||
|
String param = line.substring(0, line.indexOf(' '));
|
||||||
|
String arg = line.substring(param.length() + 1);
|
||||||
|
switch (param) {
|
||||||
|
case "name":
|
||||||
|
listeners.forEach(l -> l.onIdName(arg));
|
||||||
|
break;
|
||||||
|
case "author":
|
||||||
|
listeners.forEach(l -> l.onIdAuthor(arg));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.printf("Unknown parameter '%s' for command 'id' found!%n", param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseBestMove(String line) {
|
||||||
|
String[] tokens = line.split(" ");
|
||||||
|
String move = tokens[0];
|
||||||
|
|
||||||
|
// Ponder move
|
||||||
|
if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromSAN(tokens[2])));
|
||||||
|
else listeners.forEach(l -> l.onBestMove(move));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseCopyProtection(String line) {
|
||||||
|
switch (line) {
|
||||||
|
case "checking":
|
||||||
|
listeners.forEach(UCIListener::onCopyProtectionChecking);
|
||||||
|
break;
|
||||||
|
case "ok":
|
||||||
|
listeners.forEach(UCIListener::onCopyProtectionOk);
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
listeners.forEach(UCIListener::onCopyProtectionError);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.printf("Unknown parameter '%s' for command 'copyprotection' found!%n", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseRegistration(String line) {
|
||||||
|
switch (line) {
|
||||||
|
case "checking":
|
||||||
|
listeners.forEach(UCIListener::onRegistrationChecking);
|
||||||
|
break;
|
||||||
|
case "ok":
|
||||||
|
listeners.forEach(UCIListener::onRegistrationOk);
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
listeners.forEach(UCIListener::onRegistrationError);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.printf("Unknown parameter '%s' for command 'registration' found!%n", 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))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(UCIListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
81
src/dev/kske/chess/ui/AIConfigDialog.java
Normal file
81
src/dev/kske/chess/ui/AIConfigDialog.java
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package dev.kske.chess.ui;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JSpinner;
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class AIConfigDialog extends JDialog {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -8047984368152479992L;
|
||||||
|
|
||||||
|
private int maxDepth;
|
||||||
|
private int alphaBetaThreshold;
|
||||||
|
private boolean startGame = false;
|
||||||
|
|
||||||
|
public AIConfigDialog() {
|
||||||
|
setSize(new Dimension(337, 212));
|
||||||
|
setModal(true);
|
||||||
|
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
||||||
|
setTitle("AI Configuration");
|
||||||
|
getContentPane().setLayout(null);
|
||||||
|
|
||||||
|
JSpinner spAlphaBetaThreshold = new JSpinner();
|
||||||
|
spAlphaBetaThreshold.setBounds(222, 68, 95, 28);
|
||||||
|
getContentPane().add(spAlphaBetaThreshold);
|
||||||
|
spAlphaBetaThreshold.setModel(new SpinnerNumberModel(-10, -100, 100, 5));
|
||||||
|
|
||||||
|
JSpinner spMaxDepth = new JSpinner();
|
||||||
|
spMaxDepth.setBounds(222, 6, 95, 28);
|
||||||
|
getContentPane().add(spMaxDepth);
|
||||||
|
spMaxDepth.setModel(new SpinnerNumberModel(4, 1, 10, 1));
|
||||||
|
|
||||||
|
JLabel lblAlphabetaThreshold = new JLabel("Alpha-Beta Threshold:");
|
||||||
|
lblAlphabetaThreshold.setBounds(16, 68, 194, 28);
|
||||||
|
getContentPane().add(lblAlphabetaThreshold);
|
||||||
|
|
||||||
|
JButton btnOk = new JButton("OK");
|
||||||
|
btnOk.setBounds(16, 137, 84, 28);
|
||||||
|
getContentPane().add(btnOk);
|
||||||
|
btnOk.addActionListener((evt) -> {
|
||||||
|
maxDepth = ((Integer) spMaxDepth.getValue()).intValue();
|
||||||
|
alphaBetaThreshold = ((Integer) spAlphaBetaThreshold.getValue()).intValue();
|
||||||
|
startGame = true;
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
btnOk.setToolTipText("Start the game");
|
||||||
|
|
||||||
|
JButton btnCancel = new JButton("Cancel");
|
||||||
|
btnCancel.setBounds(222, 137, 95, 28);
|
||||||
|
getContentPane().add(btnCancel);
|
||||||
|
btnCancel.addActionListener((evt) -> dispose());
|
||||||
|
btnCancel.setToolTipText("Cancel the game start");
|
||||||
|
|
||||||
|
JLabel lblMaximalRecursionDepth = new JLabel("Maximal Recursion Depth:");
|
||||||
|
lblMaximalRecursionDepth.setBounds(16, 12, 194, 16);
|
||||||
|
getContentPane().add(lblMaximalRecursionDepth);
|
||||||
|
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxDepth() { return maxDepth; }
|
||||||
|
|
||||||
|
public void setMaxDepth(int maxDepth) { this.maxDepth = maxDepth; }
|
||||||
|
|
||||||
|
public int getAlphaBetaThreshold() { return alphaBetaThreshold; }
|
||||||
|
|
||||||
|
public void setAlphaBetaThreshold(int alphaBetaThreshold) { this.alphaBetaThreshold = alphaBetaThreshold; }
|
||||||
|
|
||||||
|
public boolean isStartGame() { return startGame; }
|
||||||
|
|
||||||
|
public void setStartGame(boolean startGame) { this.startGame = startGame; }
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package dev.kske.chess.ui;
|
package dev.kske.chess.ui;
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ComponentAdapter;
|
|
||||||
import java.awt.event.ComponentEvent;
|
|
||||||
|
|
||||||
import javax.swing.JLayeredPane;
|
import javax.swing.JLayeredPane;
|
||||||
|
|
||||||
@ -24,23 +22,13 @@ public class BoardPane extends JLayeredPane {
|
|||||||
public BoardPane() {
|
public BoardPane() {
|
||||||
boardComponent = new BoardComponent(this);
|
boardComponent = new BoardComponent(this);
|
||||||
overlayComponent = new OverlayComponent(this);
|
overlayComponent = new OverlayComponent(this);
|
||||||
|
setLayer(overlayComponent, 1);
|
||||||
|
setLayout(null);
|
||||||
|
|
||||||
add(boardComponent, Integer.valueOf(1));
|
add(boardComponent);
|
||||||
add(overlayComponent, Integer.valueOf(2));
|
add(overlayComponent);
|
||||||
|
|
||||||
/*
|
tileSize = 60;
|
||||||
* Add a component listener for adjusting the tile size on resizing.
|
|
||||||
* The size of the board is assumed to be 8x8, as well as the both the board and
|
|
||||||
* the tiles being square.
|
|
||||||
*/
|
|
||||||
addComponentListener(new ComponentAdapter() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void componentResized(ComponentEvent e) {
|
|
||||||
tileSize = getWidth() / 8;
|
|
||||||
TextureUtil.scalePieceTextures(tileSize);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setSize(getPreferredSize());
|
setSize(getPreferredSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
98
src/dev/kske/chess/ui/EngineUtil.java
Normal file
98
src/dev/kske/chess/ui/EngineUtil.java
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package dev.kske.chess.ui;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import dev.kske.chess.uci.UCIHandle;
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class EngineUtil {
|
||||||
|
|
||||||
|
private static volatile List<EngineInfo> engineInfos;
|
||||||
|
|
||||||
|
private static final String engineInfoFile = "engine_infos.ser";
|
||||||
|
|
||||||
|
static {
|
||||||
|
loadEngineInfos();
|
||||||
|
}
|
||||||
|
|
||||||
|
private EngineUtil() {}
|
||||||
|
|
||||||
|
public static void addEngine(String enginePath) {
|
||||||
|
try {
|
||||||
|
EngineInfo info = new EngineInfo(enginePath);
|
||||||
|
UCIHandle handle = new UCIHandle(enginePath);
|
||||||
|
handle.setListener(new UCIListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onIdName(String name) {
|
||||||
|
info.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onIdAuthor(String author) {
|
||||||
|
info.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUCIOk() {
|
||||||
|
engineInfos.add(info);
|
||||||
|
handle.quit();
|
||||||
|
saveEngineInfos();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handle.start();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static void loadEngineInfos() {
|
||||||
|
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(engineInfoFile))) {
|
||||||
|
Object obj = in.readObject();
|
||||||
|
if (obj instanceof ArrayList<?>) engineInfos = (ArrayList<EngineInfo>) obj;
|
||||||
|
else throw new IOException("Serialized object has the wrong class.");
|
||||||
|
} catch (ClassNotFoundException | IOException ex) {
|
||||||
|
engineInfos = new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void saveEngineInfos() {
|
||||||
|
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(engineInfoFile))) {
|
||||||
|
out.writeObject(engineInfos);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EngineInfo implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -474177108900833005L;
|
||||||
|
|
||||||
|
public String path, name, author;
|
||||||
|
|
||||||
|
public EngineInfo(String path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name + " by " + author + " at " + path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<EngineInfo> getEngineInfos() { return engineInfos; }
|
||||||
|
}
|
82
src/dev/kske/chess/ui/GameConfigurationDialog.java
Normal file
82
src/dev/kske/chess/ui/GameConfigurationDialog.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
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; }
|
||||||
|
}
|
@ -1,72 +0,0 @@
|
|||||||
package dev.kske.chess.ui;
|
|
||||||
|
|
||||||
import java.awt.FlowLayout;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JDialog;
|
|
||||||
|
|
||||||
import dev.kske.chess.board.Board;
|
|
||||||
import dev.kske.chess.board.Piece.Color;
|
|
||||||
import dev.kske.chess.game.Game;
|
|
||||||
import dev.kske.chess.game.NaturalPlayer;
|
|
||||||
import dev.kske.chess.game.Player;
|
|
||||||
import dev.kske.chess.game.ai.AIPlayer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Project: <strong>Chess</strong><br>
|
|
||||||
* File: <strong>GameModeDialog.java</strong><br>
|
|
||||||
* Created: <strong>06.07.2019</strong><br>
|
|
||||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
|
||||||
*/
|
|
||||||
public class GameModeDialog extends JDialog {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 5470026233924735607L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the dialog.
|
|
||||||
*/
|
|
||||||
public GameModeDialog(BoardPane boardPane) {
|
|
||||||
super();
|
|
||||||
setModal(true);
|
|
||||||
setTitle("Game Mode Selection");
|
|
||||||
setBounds(100, 100, 231, 133);
|
|
||||||
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
|
||||||
getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
|
|
||||||
|
|
||||||
final BoardComponent boardComponent = boardPane.getBoardComponent();
|
|
||||||
final OverlayComponent overlayComponent = boardPane.getOverlayComponent();
|
|
||||||
final Board board = boardComponent.getBoard();
|
|
||||||
|
|
||||||
JButton btnNatural = new JButton("Game against natural opponent");
|
|
||||||
btnNatural.addActionListener((evt) -> {
|
|
||||||
Map<Color, Player> players = new HashMap<>();
|
|
||||||
players.put(Color.WHITE, new NaturalPlayer(board, Color.WHITE, overlayComponent));
|
|
||||||
players.put(Color.BLACK, new NaturalPlayer(board, Color.BLACK, overlayComponent));
|
|
||||||
new Game(players, boardComponent).start();
|
|
||||||
dispose();
|
|
||||||
});
|
|
||||||
getContentPane().add(btnNatural);
|
|
||||||
|
|
||||||
JButton btnAI = new JButton("Game against AI");
|
|
||||||
btnAI.addActionListener((evt) -> {
|
|
||||||
Map<Color, Player> players = new HashMap<>();
|
|
||||||
players.put(Color.WHITE, new NaturalPlayer(board, Color.WHITE, overlayComponent));
|
|
||||||
players.put(Color.BLACK, new AIPlayer(board, Color.BLACK, 4));
|
|
||||||
new Game(players, boardComponent).start();
|
|
||||||
dispose();
|
|
||||||
});
|
|
||||||
getContentPane().add(btnAI);
|
|
||||||
|
|
||||||
JButton btnAI2 = new JButton("AI against AI");
|
|
||||||
btnAI2.addActionListener((evt) -> {
|
|
||||||
Map<Color, Player> players = new HashMap<>();
|
|
||||||
players.put(Color.WHITE, new AIPlayer(board, Color.WHITE, 4));
|
|
||||||
players.put(Color.BLACK, new AIPlayer(board, Color.BLACK, 3));
|
|
||||||
new Game(players, boardComponent).start();
|
|
||||||
dispose();
|
|
||||||
});
|
|
||||||
getContentPane().add(btnAI2);
|
|
||||||
}
|
|
||||||
}
|
|
47
src/dev/kske/chess/ui/LogFrame.java
Normal file
47
src/dev/kske/chess/ui/LogFrame.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,17 @@ package dev.kske.chess.ui;
|
|||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JFrame;
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
import dev.kske.chess.board.Board;
|
import dev.kske.chess.board.Piece.Color;
|
||||||
|
import dev.kske.chess.game.Game;
|
||||||
|
import dev.kske.chess.game.NaturalPlayer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Project: <strong>Chess</strong><br>
|
* Project: <strong>Chess</strong><br>
|
||||||
@ -18,7 +22,11 @@ import dev.kske.chess.board.Board;
|
|||||||
*/
|
*/
|
||||||
public class MainWindow {
|
public class MainWindow {
|
||||||
|
|
||||||
private JFrame mframe;
|
private JFrame mframe;
|
||||||
|
private JButton btnRestart, btnSwapColors;
|
||||||
|
private BoardPane boardPane;
|
||||||
|
private Game game;
|
||||||
|
private Color activeColor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch the application.
|
* Launch the application.
|
||||||
@ -48,24 +56,58 @@ public class MainWindow {
|
|||||||
* Initialize the contents of the frame.
|
* Initialize the contents of the frame.
|
||||||
*/
|
*/
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
mframe = new JFrame();
|
mframe = new JFrame("Chess by Kai S. K. Engelbart");
|
||||||
mframe.setResizable(false);
|
mframe.setResizable(false);
|
||||||
mframe.setBounds(100, 100, 494, 565);
|
mframe.setBounds(100, 100, 494, 565);
|
||||||
mframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
mframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
mframe.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
|
||||||
|
|
||||||
BoardPane boardPane = new BoardPane();
|
boardPane = new BoardPane();
|
||||||
boardPane.getBoardComponent().setBoard(new Board());
|
|
||||||
mframe.getContentPane().add(boardPane, BorderLayout.CENTER);
|
mframe.getContentPane().add(boardPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
JPanel toolPanel = new JPanel();
|
JPanel toolPanel = new JPanel();
|
||||||
|
btnRestart = new JButton("Restart");
|
||||||
|
btnRestart.addActionListener((evt) -> { if (game != null) game.reset(); game.start(); });
|
||||||
|
|
||||||
|
activeColor = Color.WHITE;
|
||||||
|
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);
|
||||||
mframe.getContentPane().add(toolPanel, BorderLayout.NORTH);
|
mframe.getContentPane().add(toolPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
JButton btnRestart = new JButton("Restart");
|
JPanel letterPanel = new JPanel(new GridLayout(1, 8));
|
||||||
btnRestart.addActionListener((evt) -> System.err.println("Resetting not implemented!"));
|
for (int i = 0; i < 8; i++) {
|
||||||
toolPanel.add(btnRestart);
|
JLabel letterLabel = new JLabel(String.valueOf((char) (65 + i)));
|
||||||
mframe.pack();
|
letterLabel.setHorizontalAlignment(JLabel.CENTER);
|
||||||
|
letterPanel.add(letterLabel);
|
||||||
|
}
|
||||||
|
mframe.add(letterPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
// Display dialog for game mode selection
|
JPanel numberPanel = new JPanel(new GridLayout(8, 1));
|
||||||
new GameModeDialog(boardPane).setVisible(true);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoardPane getBoardPane() { return boardPane; }
|
||||||
|
|
||||||
|
public Game getGame() { return game; }
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
89
src/dev/kske/chess/ui/MenuBar.java
Normal file
89
src/dev/kske/chess/ui/MenuBar.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package dev.kske.chess.ui;
|
||||||
|
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
|
||||||
|
import javax.swing.JMenu;
|
||||||
|
import javax.swing.JMenuBar;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
|
||||||
|
import dev.kske.chess.game.Game;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
initToolsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
});
|
||||||
|
gameMenu.add(newGameMenuItem);
|
||||||
|
|
||||||
|
add(gameMenu);
|
||||||
|
|
||||||
|
// Start a game
|
||||||
|
startGame(new Game(boardPane, "Natural Player", "Natural Player"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initEngineMenu() {
|
||||||
|
JMenu engineMenu = new JMenu("Engine");
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (enginePath != null) EngineUtil.addEngine(enginePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem showInfoMenuItem = new JMenuItem("Show engine info");
|
||||||
|
|
||||||
|
engineMenu.add(addEngineMenuItem);
|
||||||
|
engineMenu.add(showInfoMenuItem);
|
||||||
|
add(engineMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initToolsMenu() {
|
||||||
|
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));
|
||||||
|
toolsMenu.add(exportFENMenuItem);
|
||||||
|
|
||||||
|
add(toolsMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startGame(Game game) {
|
||||||
|
mainWindow.setGame(game);
|
||||||
|
|
||||||
|
// Update board and board component
|
||||||
|
game.reset();
|
||||||
|
game.start();
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,19 @@
|
|||||||
package dev.kske.chess.ui;
|
package dev.kske.chess.ui;
|
||||||
|
|
||||||
|
import java.awt.BasicStroke;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Polygon;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
|
|
||||||
|
import dev.kske.chess.board.Move;
|
||||||
import dev.kske.chess.board.Position;
|
import dev.kske.chess.board.Position;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,11 +29,12 @@ public class OverlayComponent extends JComponent {
|
|||||||
private final BoardPane boardPane;
|
private final BoardPane boardPane;
|
||||||
|
|
||||||
private List<Position> dots;
|
private List<Position> dots;
|
||||||
|
private Move arrow;
|
||||||
|
|
||||||
public OverlayComponent(BoardPane boardPane) {
|
public OverlayComponent(BoardPane boardPane) {
|
||||||
this.boardPane = boardPane;
|
this.boardPane = boardPane;
|
||||||
|
dots = new ArrayList<>();
|
||||||
setSize(boardPane.getPreferredSize());
|
setSize(boardPane.getPreferredSize());
|
||||||
dots = new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -34,7 +42,27 @@ public class OverlayComponent extends JComponent {
|
|||||||
super.paintComponent(g);
|
super.paintComponent(g);
|
||||||
|
|
||||||
final int tileSize = getTileSize();
|
final int tileSize = getTileSize();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Shape arrowShape = createArrowShape(pos, dest);
|
||||||
|
g.setColor(new Color(255, 0, 0, 127));
|
||||||
|
g2d.fill(arrowShape);
|
||||||
|
g2d.setColor(Color.black);
|
||||||
|
g2d.draw(arrowShape);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw possible moves if a piece was selected
|
// Draw possible moves if a piece was selected
|
||||||
if (!dots.isEmpty()) {
|
if (!dots.isEmpty()) {
|
||||||
g.setColor(Color.green);
|
g.setColor(Color.green);
|
||||||
@ -47,6 +75,36 @@ public class OverlayComponent extends JComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Shape createArrowShape(Point pos, Point dest) {
|
||||||
|
Polygon arrowPolygon = new Polygon();
|
||||||
|
arrowPolygon.addPoint(-6, 1);
|
||||||
|
arrowPolygon.addPoint(3, 1);
|
||||||
|
arrowPolygon.addPoint(3, 3);
|
||||||
|
arrowPolygon.addPoint(6, 0);
|
||||||
|
arrowPolygon.addPoint(3, -3);
|
||||||
|
arrowPolygon.addPoint(3, -1);
|
||||||
|
arrowPolygon.addPoint(-6, -1);
|
||||||
|
|
||||||
|
Point midPoint = midpoint(pos, dest);
|
||||||
|
|
||||||
|
double rotate = Math.atan2(dest.y - pos.y, dest.x - pos.x);
|
||||||
|
double ptDistance = pos.distance(dest);
|
||||||
|
double scale = ptDistance / 12.0; // 12 because it's the length of the arrow
|
||||||
|
// polygon.
|
||||||
|
|
||||||
|
AffineTransform transform = new AffineTransform();
|
||||||
|
|
||||||
|
transform.translate(midPoint.x, midPoint.y);
|
||||||
|
transform.rotate(rotate);
|
||||||
|
transform.scale(scale, 5);
|
||||||
|
|
||||||
|
return transform.createTransformedShape(arrowPolygon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point midpoint(Point p1, Point p2) {
|
||||||
|
return new Point((int) ((p1.x + p2.x) / 2.0), (int) ((p1.y + p2.y) / 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
public void displayDots(List<Position> dots) {
|
public void displayDots(List<Position> dots) {
|
||||||
this.dots.clear();
|
this.dots.clear();
|
||||||
this.dots.addAll(dots);
|
this.dots.addAll(dots);
|
||||||
@ -58,5 +116,15 @@ public class OverlayComponent extends JComponent {
|
|||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void displayArrow(Move arrow) {
|
||||||
|
this.arrow = arrow;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearArrow() {
|
||||||
|
arrow = null;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
public int getTileSize() { return boardPane.getTileSize(); }
|
public int getTileSize() { return boardPane.getTileSize(); }
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ package dev.kske.chess.ui;
|
|||||||
|
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -19,11 +19,13 @@ import dev.kske.chess.board.Piece;
|
|||||||
*/
|
*/
|
||||||
public class TextureUtil {
|
public class TextureUtil {
|
||||||
|
|
||||||
private static Map<String, Image> textures;
|
private static Map<String, Image> textures, scaledTextures;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
textures = new HashMap<>();
|
textures = new HashMap<>();
|
||||||
|
scaledTextures = new HashMap<>();
|
||||||
loadPieceTextures();
|
loadPieceTextures();
|
||||||
|
scaledTextures.putAll(textures);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextureUtil() {}
|
private TextureUtil() {}
|
||||||
@ -36,26 +38,28 @@ public class TextureUtil {
|
|||||||
*/
|
*/
|
||||||
public static Image getPieceTexture(Piece piece) {
|
public static Image getPieceTexture(Piece piece) {
|
||||||
String key = piece.getType().toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
|
String key = piece.getType().toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
|
||||||
return textures.get(key);
|
return scaledTextures.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scales all piece textures to fit the current tile size
|
* Scales all piece textures to fit the current tile size
|
||||||
*/
|
*/
|
||||||
public static void scalePieceTextures(int scale) {
|
public static void scalePieceTextures(int scale) {
|
||||||
textures.replaceAll((key, img) -> img.getScaledInstance(scale, scale, Image.SCALE_SMOOTH));
|
scaledTextures.clear();
|
||||||
|
textures
|
||||||
|
.forEach((key, img) -> scaledTextures.put(key, img.getScaledInstance(scale, scale, Image.SCALE_SMOOTH)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an image from a file.
|
* Loads an image from a file in the resource folder.
|
||||||
*
|
*
|
||||||
* @param file The image file
|
* @param fileName The name of the image resource
|
||||||
* @return The loaded image
|
* @return The loaded image
|
||||||
*/
|
*/
|
||||||
private static Image loadImage(File file) {
|
private static Image loadImage(String fileName) {
|
||||||
BufferedImage in = null;
|
BufferedImage in = null;
|
||||||
try {
|
try {
|
||||||
in = ImageIO.read(file);
|
in = ImageIO.read(TextureUtil.class.getResourceAsStream(fileName));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@ -67,10 +71,20 @@ public class TextureUtil {
|
|||||||
* The filenames without extensions are used as keys in the map textures.
|
* The filenames without extensions are used as keys in the map textures.
|
||||||
*/
|
*/
|
||||||
private static void loadPieceTextures() {
|
private static void loadPieceTextures() {
|
||||||
File dir = new File("res/pieces");
|
Arrays
|
||||||
File[] files = dir.listFiles((File parentDir, String name) -> name.toLowerCase().endsWith(".png"));
|
.asList("king_white",
|
||||||
for (File file : files)
|
"king_black",
|
||||||
textures.put(file.getName().replaceFirst("[.][^.]+$", ""), TextureUtil.loadImage(file));
|
"queen_white",
|
||||||
|
"queen_black",
|
||||||
|
"rook_white",
|
||||||
|
"rook_black",
|
||||||
|
"knight_white",
|
||||||
|
"knight_black",
|
||||||
|
"bishop_white",
|
||||||
|
"bishop_black",
|
||||||
|
"pawn_white",
|
||||||
|
"pawn_black")
|
||||||
|
.forEach(name -> textures.put(name, loadImage("/pieces/" + name + ".png")));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,17 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import dev.kske.chess.board.Board;
|
import dev.kske.chess.board.Board;
|
||||||
|
import dev.kske.chess.board.Move;
|
||||||
import dev.kske.chess.board.Piece.Color;
|
import dev.kske.chess.board.Piece.Color;
|
||||||
import dev.kske.chess.board.Queen;
|
import dev.kske.chess.board.Queen;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Project: <strong>Chess</strong><br>
|
* Project: <strong>Chess</strong><br>
|
||||||
* File: <strong>BoardCloneTest.java</strong><br>
|
* File: <strong>BoardTest.java</strong><br>
|
||||||
* Created: <strong>08.07.2019</strong><br>
|
* Created: <strong>08.07.2019</strong><br>
|
||||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||||
*/
|
*/
|
||||||
class BoardCloneTest {
|
class BoardTest {
|
||||||
|
|
||||||
Board board;
|
Board board;
|
||||||
|
|
||||||
@ -38,7 +39,8 @@ class BoardCloneTest {
|
|||||||
assertNotSame(clone.getBoardArr(), board.getBoardArr());
|
assertNotSame(clone.getBoardArr(), board.getBoardArr());
|
||||||
|
|
||||||
clone.getBoardArr()[0][0] = new Queen(Color.BLACK, clone);
|
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.getBoardArr()[0][0], board.getBoardArr()[0][0]);
|
||||||
|
assertNotEquals(clone.getActiveColor(), board.getActiveColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
52
test/dev/kske/chess/test/PositionTest.java
Normal file
52
test/dev/kske/chess/test/PositionTest.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user