package dev.kske.chess.board; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import dev.kske.chess.board.Log.MoveNode; import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Type; /** * Project: Chess
* File: Board.java
* Created: 01.07.2019
* Author: Kai S. K. Engelbart */ public class Board { private Piece[][] boardArr = new Piece[8][8]; private Map kingPos = new HashMap<>(); private Map> castlingRights = new HashMap<>(); private Log log = new Log(); private static final Map 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 } }); } /** * Initializes the board with the default chess starting position. */ public Board() { initDefaultPositions(); } /** * Initializes the board with data from a FEN-string. * * @param fen The FEN-string to initialize the board from */ public Board(String fen) { initFromFEN(fen); } /** * Creates a copy of another {@link Board} instance.
* The created object is a deep copy, but does not contain any move history * apart from the current {@link MoveNode}. * * @param other The {@link Board} instance to copy */ public Board(Board other) { boardArr = new Piece[8][8]; for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) { if (other.boardArr[i][j] == null) continue; boardArr[i][j] = (Piece) other.boardArr[i][j].clone(); boardArr[i][j].board = this; } kingPos.putAll(other.kingPos); Map whiteCastling = new HashMap<>(other.castlingRights.get(Color.WHITE)), blackCastling = new HashMap<>(other.castlingRights.get(Color.BLACK)); castlingRights.put(Color.WHITE, whiteCastling); castlingRights.put(Color.BLACK, blackCastling); log = new Log(other.log, false); } /** * Moves a piece across the board if the move is legal. * * @param move The move to execute * @return {@code true}, if the attempted move was legal and thus executed */ public boolean attemptMove(Move move) { Piece piece = getPos(move); if (piece == null || !piece.isValidMove(move)) return false; else { // Set type after validation if (move.type == Move.Type.UNKNOWN) move.type = Move.Type.NORMAL; // Move piece move(move); // Revert move if it caused a check for its team if (checkCheck(piece.getColor())) { revert(); return false; } return true; } } /** * Moves a piece across the board without checking if the move is legal. * * @param move The move to execute */ public void move(Move move) { Piece piece = getPos(move); Piece capturePiece = getDest(move); switch (move.type) { case PAWN_PROMOTION: setPos(move, null); // TODO: Select promotion setDest(move, new Queen(piece.getColor(), this)); break; case EN_PASSANT: setDest(move, piece); setPos(move, null); boardArr[move.dest.x][move.dest.y - move.ySign] = null; break; case CASTLING: // Move the king setDest(move, piece); setPos(move, null); // Move the rook Move rookMove = move.dest.x == 6 ? new Move(7, move.pos.y, 5, move.pos.y) // Kingside : new Move(0, move.pos.y, 3, move.pos.y); // Queenside // Move the rook setDest(rookMove, getPos(rookMove)); setPos(rookMove, null); getDest(rookMove).incMoveCounter(); break; case UNKNOWN: System.err.printf("Move of unknown type %s found!%n", move); case NORMAL: setDest(move, piece); setPos(move, null); break; default: System.err.printf("Move %s of unimplemented type found!%n", move); } // Increment move counter getDest(move).incMoveCounter(); // Update the king's position if the moved piece is the king and castling // availability if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest); // Update log log.add(move, capturePiece, piece.getType() == Type.PAWN); updateCastlingRights(); } /** * Reverts the last move. */ public void revert() { MoveNode moveNode = log.getLast(); Move move = moveNode.move; Piece capturedPiece = moveNode.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 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); } } /** * Generated every legal move for one color * * @param color The color to generate the moves for * @return A list of all legal moves */ public List getMoves(Color color) { List moves = new ArrayList<>(); for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) moves.addAll(boardArr[i][j].getMoves(new Position(i, j))); return moves; } public List getMoves(Position pos) { return get(pos).getMoves(pos); } /** * Checks, if the king is in check. * * @param color The color of the king to check * @return {@code true}, if the king is in check */ public boolean checkCheck(Color color) { for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) { Position pos = new Position(i, j); if (get(pos) != null && get(pos).getColor() != color && get(pos).isValidMove(new Move(pos, kingPos.get(color)))) return true; } return false; } /** * Checks, if the king is in checkmate. * This requires the king to already be in check! * * @param color The color of the king to check * @return {@code true}, if the king is in checkmate */ public boolean checkCheckmate(Color color) { // Return false immediately if the king can move if (!getMoves(kingPos.get(color)).isEmpty()) return false; else { for (Move move : getMoves(color)) { move(move); boolean check = checkCheck(color); revert(); if (!check) return false; } return true; } } public GameState getGameEventType(Color color) { return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE : GameState.NORMAL; } /** * Evaluated the board. * * @param color The color to evaluate for * @return An positive number representing how good the position is */ public int evaluate(Color color) { int score = 0; for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) { switch (boardArr[i][j].getType()) { case QUEEN: score += 90; break; case ROOK: score += 50; break; case KNIGHT: score += 30; break; case BISHOP: score += 30; break; case PAWN: score += 10; break; } if (positionScores.containsKey(boardArr[i][j].getType())) score += positionScores.get(boardArr[i][j].getType())[i][color == Color.WHITE ? j : 7 - j]; } return score; } /** * Initialized the board array with the default chess pieces and positions. */ public void initDefaultPositions() { // Initialize pawns for (int i = 0; i < 8; i++) { boardArr[i][1] = new Pawn(Color.BLACK, this); boardArr[i][6] = new Pawn(Color.WHITE, this); } // Initialize kings boardArr[4][0] = new King(Color.BLACK, this); boardArr[4][7] = new King(Color.WHITE, this); // Initialize king position objects kingPos.put(Color.BLACK, new Position(4, 0)); kingPos.put(Color.WHITE, new Position(4, 7)); // Initialize queens boardArr[3][0] = new Queen(Color.BLACK, this); boardArr[3][7] = new Queen(Color.WHITE, this); // Initialize rooks boardArr[0][0] = new Rook(Color.BLACK, this); boardArr[0][7] = new Rook(Color.WHITE, this); boardArr[7][0] = new Rook(Color.BLACK, this); boardArr[7][7] = new Rook(Color.WHITE, this); // Initialize knights boardArr[1][0] = new Knight(Color.BLACK, this); boardArr[1][7] = new Knight(Color.WHITE, this); boardArr[6][0] = new Knight(Color.BLACK, this); boardArr[6][7] = new Knight(Color.WHITE, this); // Initialize bishops boardArr[2][0] = new Bishop(Color.BLACK, this); boardArr[2][7] = new Bishop(Color.WHITE, this); boardArr[5][0] = new Bishop(Color.BLACK, this); boardArr[5][7] = new Bishop(Color.WHITE, this); // Clear all other tiles for (int i = 0; i < 8; i++) for (int j = 2; j < 6; j++) boardArr[i][j] = null; // Initialize castling rights Map 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(); } /** * Initialized the board with a position specified in a FEN-encoded string. * * @param fen The FEN-encoded string representing target state of the board */ public void initFromFEN(String fen) { String[] parts = fen.split(" "); log.reset(); // Piece placement (from white's perspective) String[] rows = parts[0].split("/"); for (int i = 0; i < 8; i++) { char[] places = rows[i].toCharArray(); for (int j = 0, k = 0; k < places.length; j++, k++) { if (Character.isDigit(places[k])) { for (int l = j; l < Character.digit(places[k], 10); l++, j++) boardArr[j][i] = null; --j; } else { Color color = Character.isUpperCase(places[k]) ? Color.WHITE : Color.BLACK; switch (Character.toLowerCase(places[k])) { case 'k': boardArr[j][i] = new King(color, this); kingPos.put(color, new Position(j, i)); break; case 'q': boardArr[j][i] = new Queen(color, this); break; case 'r': boardArr[j][i] = new Rook(color, this); break; case 'n': boardArr[j][i] = new Knight(color, this); break; case 'b': boardArr[j][i] = new Bishop(color, this); break; case 'p': boardArr[j][i] = new Pawn(color, this); break; default: System.err.printf("Unknown character '%c' in board declaration of FEN string '%s'%n", places[k], fen); } } } } // Active color log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0))); // Castling rights Map whiteCastling = new HashMap<>(), blackCastling = new HashMap<>(); for (char c : parts[2].toCharArray()) switch (c) { case 'K': whiteCastling.put(Type.KING, true); case 'Q': whiteCastling.put(Type.QUEEN, true); case 'k': blackCastling.put(Type.KING, true); case 'q': blackCastling.put(Type.QUEEN, true); case '-': break; default: System.err .printf("Unknown character '%c' in castling rights declaration of FEN string '%s'", c, fen); } castlingRights.put(Color.WHITE, whiteCastling); castlingRights.put(Color.BLACK, blackCastling); // En passant availability if (!parts[3].equals("-")) log.setEnPassant(Position.fromSAN(parts[3])); // Halfmove clock log.setHalfmoveClock(Integer.parseInt(parts[4])); // Fullmove counter log.setFullmoveCounter(Integer.parseInt(parts[5])); } /** * @return a FEN-encoded 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++) { final Piece piece = boardArr[j][i]; if (piece == null) ++emptyCount; else { if (emptyCount != 0) { sb.append(emptyCount); emptyCount = 0; } char p = boardArr[j][i].getType().firstChar(); sb.append(piece.getColor() == Color.WHITE ? Character.toUpperCase(p) : p); } } if (emptyCount != 0) sb.append(emptyCount); if (i < 7) sb.append('/'); } // Active color sb.append(" " + log.getActiveColor().firstChar()); // Castling Rights 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 MoveNode lastMove = log.getLast(); // En passant availability 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(); } /** * @param pos The position from which to return a piece * @return The piece at the position */ public Piece get(Position pos) { return boardArr[pos.x][pos.y]; } /** * Places a piece at a position. * * @param pos The position to place the piece at * @param piece The piece to place */ public void set(Position pos, Piece piece) { boardArr[pos.x][pos.y] = piece; } /** * @param move The move from which position to return a piece * @return The piece at the position of the move */ public Piece getPos(Move move) { return get(move.pos); } /** * @param move The move from which destination to return a piece * @return The piece at the destination of the move */ public Piece getDest(Move move) { return get(move.dest); } /** * Places a piece at the position of a move. * * @param move The move at which position to place the piece * @param piece The piece to place */ public void setPos(Move move, Piece piece) { set(move.pos, piece); } /** * Places a piece at the destination of a move. * * @param move The move at which destination to place the piece * @param piece The piece to place */ public void setDest(Move move, Piece piece) { set(move.dest, piece); } /** * @return The board array */ public Piece[][] getBoardArr() { return boardArr; } /** * @return The move log */ public Log getLog() { return log; } }