Added move history and pawn promotion

+ Log class for move history
+ LoggedMove class with piece captured by the logged move
- Made move reversion easier
+ MoveType for recognizing special moves
+ MoveType determination during move generation and validation
This commit is contained in:
Kai S. K. Engelbart 2019-07-10 18:54:53 +02:00
parent 1b7e3b1986
commit 9c19b13785
Signed by: kske
GPG Key ID: 8BEB13EC5DF7EF13
6 changed files with 134 additions and 32 deletions

View File

@ -5,6 +5,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import dev.kske.chess.board.Log.LoggedMove;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Piece.Type;
@ -18,10 +19,12 @@ public class Board implements Cloneable {
private Piece[][] boardArr;
private Map<Color, Position> kingPos;
private Log log;
public Board() {
boardArr = new Piece[8][8];
kingPos = new HashMap<>();
log = new Log();
initializeDefaultPositions();
}
@ -36,11 +39,11 @@ public class Board implements Cloneable {
if (piece == null || !piece.isValidMove(move)) return false;
else {
// Move piece
Piece capturePiece = move(move);
move(move);
// Revert move if it caused a check for its team
if (checkCheck(piece.getColor())) {
revert(move, capturePiece);
revert();
return false;
}
@ -54,31 +57,57 @@ public class Board implements Cloneable {
* @param move The move to execute
* @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 capturePiece = getDest(move);
switch (move.type) {
case PAWN_PROMOTION:
setPos(move, null);
// TODO: Select promotion
setDest(move, new Queen(piece.getColor(), this));
break;
case UNKNOWN:
System.err.printf("Unknown move %s found!%n", move);
case NORMAL:
setDest(move, piece);
setPos(move, null);
}
// Update the king's position if the moved piece is the king
if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
return capturePiece;
// Update log
log.add(move, capturePiece);
}
/**
* Reverts a move.
*
* @param move The move to revert
* @param capturedPiece The piece that has been captured when the move has been
* applied
* Reverts the last move.
*/
public void revert(Move move, Piece capturedPiece) {
public void revert() {
LoggedMove loggedMove = log.getLast();
Move move = loggedMove.move;
Piece capturedPiece = loggedMove.capturedPiece;
switch (move.type) {
case CASTLING:
case EN_PASSANT:
case PAWN_PROMOTION:
setPos(move, new Pawn(getDest(move).getColor(), this));
setDest(move, capturedPiece);
break;
case UNKNOWN:
System.err.printf("Unknown move %s found!%n", move);
case NORMAL:
setPos(move, getDest(move));
setDest(move, capturedPiece);
}
// Update the king's position if the moved piece is the king
if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos);
// Update log
log.removeLast();
}
/**
@ -129,9 +158,9 @@ public class Board implements Cloneable {
if (!getMoves(kingPos.get(color)).isEmpty()) return false;
else {
for (Move move : getMoves(color)) {
Piece capturePiece = move(move);
move(move);
boolean check = checkCheck(color);
revert(move, capturePiece);
revert();
if (!check) return false;
}
return true;
@ -139,8 +168,7 @@ public class Board implements Cloneable {
}
public GameState getGameEventType(Color color) {
return checkCheck(color)
? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
: getMoves(color).isEmpty() ? GameState.STALEMATE : GameState.NORMAL;
}
@ -243,6 +271,8 @@ public class Board implements Cloneable {
board.kingPos = new HashMap<>();
board.kingPos.putAll(kingPos);
board.log = (Log) log.clone();
return board;
}

View File

@ -0,0 +1,55 @@
package dev.kske.chess.board;
import java.util.ArrayList;
import java.util.List;
/**
* 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;
public Log() {
moves = new ArrayList<>();
}
public void add(Move move, Piece capturedPiece) {
moves.add(new LoggedMove(move, capturedPiece));
}
public LoggedMove getLast() {
return moves.get(moves.size() - 1);
}
public void removeLast() {
if (!moves.isEmpty()) moves.remove(moves.size() - 1);
}
@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 static class LoggedMove {
public final Move move;
public final Piece capturedPiece;
public LoggedMove(Move move, Piece capturedPiece) {
this.move = move;
this.capturedPiece = capturedPiece;
}
}
}

View File

@ -10,16 +10,22 @@ public class Move {
public final Position pos, dest;
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.dest = dest;
this.type = type;
xDist = Math.abs(dest.x - pos.x);
yDist = Math.abs(dest.y - pos.y);
xSign = (int) Math.signum(dest.x - pos.x);
ySign = (int) Math.signum(dest.y - pos.y);
}
public Move(Position pos, Position dest) {
this(pos, dest, Type.NORMAL);
}
public Move(int xPos, int yPos, int xDest, int yDest) {
this(new Position(xPos, yPos), new Position(xDest, yDest));
}
@ -34,4 +40,8 @@ public class Move {
public String toString() {
return String.format("%s -> %s", pos, dest);
}
public static enum Type {
NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN
}
}

View File

@ -17,12 +17,17 @@ public class Pawn extends Piece {
@Override
public boolean isValidMove(Move move) {
// TODO: en passant, pawn promotion
// TODO: en passant
boolean step = move.isVertical() && move.yDist == 1;
boolean doubleStep = move.isVertical() && move.yDist == 2;
boolean strafe = move.isDiagonal() && move.xDist == 1;
if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
else doubleStep &= move.pos.y == 1;
// Mark move as pawn promotion if necessary
if (move.ySign == 1 && move.pos.y == 6 || move.ySign == -1 && move.pos.y == 1)
move.type = Move.Type.PAWN_PROMOTION;
return (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move);
}
@ -43,8 +48,6 @@ public class Pawn extends Piece {
int sign = getColor() == Color.WHITE ? -1 : 1;
if (sign == -1 && pos.y == 1 || sign == 1 && pos.y == 7) return moves;
// Strafe left
if (pos.x > 0) {
Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
@ -68,6 +71,11 @@ public class Pawn extends Piece {
Move move = new Move(pos, new Position(pos.x, pos.y + 2 * sign));
if (isFreePath(move)) moves.add(move);
}
// Mark moves as pawn promotions if necessary
if (sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1)
moves.parallelStream().forEach(m -> m.type = Move.Type.PAWN_PROMOTION);
return moves;
}

View File

@ -23,10 +23,10 @@ public abstract class Piece implements Cloneable {
List<Move> moves = getPseudolegalMoves(pos);
for (Iterator<Move> iterator = moves.iterator(); iterator.hasNext();) {
Move move = iterator.next();
Piece capturePiece = board.move(move);
board.move(move);
if (board.checkCheck(getColor()))
iterator.remove();
board.revert(move, capturePiece);
board.revert();
}
return moves;
}
@ -67,7 +67,7 @@ public abstract class Piece implements Cloneable {
public Color getColor() { return color; }
public static enum Type {
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN
}
public static enum Color {

View File

@ -5,7 +5,6 @@ import java.util.concurrent.Callable;
import dev.kske.chess.board.Board;
import dev.kske.chess.board.Move;
import dev.kske.chess.board.Piece;
import dev.kske.chess.board.Piece.Color;
/**
@ -39,7 +38,7 @@ public class MoveProcessor implements Callable<ProcessingResult> {
private int miniMax(Board board, List<Move> moves, Color color, int depth) {
int bestValue = Integer.MIN_VALUE;
for (Move move : moves) {
Piece capturePiece = board.move(move);
board.move(move);
int teamValue = board.evaluate(color);
int enemyValue = board.evaluate(color.opposite());
int valueChange = teamValue - enemyValue;
@ -52,7 +51,7 @@ public class MoveProcessor implements Callable<ProcessingResult> {
if (depth == 0) bestMove = move;
}
board.revert(move, capturePiece);
board.revert();
}
return bestValue;
}