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 bde14db4cc
commit 26f7d67461
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.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;
@ -18,10 +19,12 @@ public class Board implements Cloneable {
private Piece[][] boardArr; private Piece[][] boardArr;
private Map<Color, Position> kingPos; private Map<Color, Position> kingPos;
private Log log;
public Board() { public Board() {
boardArr = new Piece[8][8]; boardArr = new Piece[8][8];
kingPos = new HashMap<>(); kingPos = new HashMap<>();
log = new Log();
initializeDefaultPositions(); initializeDefaultPositions();
} }
@ -36,11 +39,11 @@ public class Board implements Cloneable {
if (piece == null || !piece.isValidMove(move)) return false; if (piece == null || !piece.isValidMove(move)) return false;
else { else {
// 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 +57,57 @@ 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);
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); setDest(move, piece);
setPos(move, null); setPos(move, null);
}
// 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 (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);
} }
/** /**
* 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() {
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)); setPos(move, getDest(move));
setDest(move, capturedPiece); setDest(move, capturedPiece);
}
// 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();
} }
/** /**
@ -129,9 +158,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,8 +168,7 @@ 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() ? GameState.STALEMATE : GameState.NORMAL; : getMoves(color).isEmpty() ? GameState.STALEMATE : GameState.NORMAL;
} }
@ -243,6 +271,8 @@ public class Board implements Cloneable {
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;
} }

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 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));
} }
@ -34,4 +40,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
}
} }

View File

@ -17,12 +17,17 @@ public class Pawn extends Piece {
@Override @Override
public boolean isValidMove(Move move) { public boolean isValidMove(Move move) {
// TODO: en passant, pawn promotion // TODO: en passant
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;
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;
// 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); 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; 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 +71,11 @@ 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 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; return moves;
} }

View File

@ -23,10 +23,10 @@ public abstract class Piece implements Cloneable {
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(move, capturePiece); board.revert();
} }
return moves; return moves;
} }
@ -67,7 +67,7 @@ public abstract class Piece implements Cloneable {
public Color getColor() { return color; } public Color getColor() { return color; }
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 {

View File

@ -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;
/** /**
@ -39,7 +38,7 @@ 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;
@ -52,7 +51,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;
} }