All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fathzer.chess.utils.notation.AbstractSANConverter Maven / Gradle / Ivy

The newest version!
package com.fathzer.chess.utils.notation;

import static com.fathzer.chess.utils.Pieces.*;

import java.util.List;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;

import com.fathzer.chess.utils.Pieces;
import com.fathzer.chess.utils.adapters.MoveData;
import com.fathzer.games.MoveGenerator;
import com.fathzer.games.MoveGenerator.MoveConfidence;

/**
 * A move to Standard Algebraic Notation (SAN) converter.
 * 
As SAN is not a very strict standard, this class is highly customisable in order to accept usual variations (see the setters). */ public abstract class AbstractSANConverter> implements Supplier> { private static final String[] DEFAULT_PIECE_NOTATIONS = new String[] {"", "", "N", "B", "R", "Q", "K"}; /** The default symbol used to indicate a check ("+") */ public static final String DEFAULT_CHECK_SYMBOL = "+"; /** The default symbol used to indicate a checkmate ("#") */ public static final String DEFAULT_CHECKMATE_SYMBOL = "#"; /** The default symbol used to indicate a capture ("x") */ public static final char DEFAULT_CAPTURE_SYMBOL = 'x'; /** The usual optional symbol used to indicate an en passant capture (default: "") */ public static final String USUAL_EN_PASSANT_SYMBOL = " e.p."; private String checkSymbol = DEFAULT_CHECK_SYMBOL; private String checkmateSymbol = DEFAULT_CHECKMATE_SYMBOL; private char captureSymbol = DEFAULT_CAPTURE_SYMBOL; private String enPassantSymbol = ""; private Function castlingSymbolBuilder = kingSide -> Boolean.TRUE.equals(kingSide) ? "O-O" : "O-O-O"; private IntFunction pieceNotationBuilder = p -> DEFAULT_PIECE_NOTATIONS[p]; private IntFunction promotionSymbolBuilder = p -> "="+pieceNotationBuilder.apply(p); /** * Constructor. *
See the setters for the default values. */ protected AbstractSANConverter() { // Nothing to do } /** * Sets the symbol used to indicate a check. * @param checkSymbol the symbol to use (default: "+") */ public void setCheckSymbol(String checkSymbol) { this.checkSymbol = checkSymbol; } /** * Sets the symbol used to indicate a checkmate. * @param checkmateSymbol the symbol to use (default: "#") */ public void setCheckmateSymbol(String checkmateSymbol) { this.checkmateSymbol = checkmateSymbol; } /** * Sets the symbol used to indicate a capture. * @param captureSymbol the symbol to use (default: "x") */ public void setCaptureSymbol(char captureSymbol) { this.captureSymbol = captureSymbol; } /** * Sets the symbol used to indicate an en passant capture. * @param enPassantSymbol the symbol to use (default: "") */ public void setEnPassantSymbol(String enPassantSymbol) { this.enPassantSymbol = enPassantSymbol; } /** * Sets the symbol used to indicate a castling move. * @param castlingSymbolBuilder the function used to build the castling symbol (default: "O-O" for king side, "O-O-O" for queen side). *
The function takes a boolean parameter indicating if the castling is king side (true) or queen side (false). */ public void setCastlingSymbolBuilder(Function castlingSymbolBuilder) { this.castlingSymbolBuilder = castlingSymbolBuilder; } /** * Sets the symbol used to indicate the moving piece. * @param pieceNotationBuilder the function used to build the piece notation (default: nothing for pawns, piece notation in English for other pieces). *
The function takes an integer parameter indicating the piece type. * @see Pieces */ public void setPieceNotationBuilder(IntFunction pieceNotationBuilder) { this.pieceNotationBuilder = pieceNotationBuilder; } /** * Sets the symbol used to indicate a promotion. * @param promotionSymbolBuilder the function used to build the promotion symbol (default: "=" + piece notation). *
The function takes an integer parameter indicating the piece type to promote to. * @see Pieces */ public void setPromotionSymbolBuilder(IntFunction promotionSymbolBuilder) { this.promotionSymbolBuilder = promotionSymbolBuilder; } /** * Returns the notation for the given move. * @param move the move to convert. * @param board the board on which the move is to be made. * @return the move in SAN notation. * @throws IllegalArgumentException if the move is not legal. */ public String get(M move, B board) { final MoveData moveData = get(); if (!moveData.update(move, board)) { throw new IllegalArgumentException(); } final MoveData mvData = get(); final List candidates = board.getLegalMoves().stream().filter(m -> { mvData.update(m, board); return mvData.getMovingDestination() == moveData.getMovingDestination() && mvData.getMovingPiece() == moveData.getMovingPiece();}). toList(); if (candidates.isEmpty()) { // Move is not legal throw new IllegalArgumentException(); } if (moveData.getCastlingRookIndex()>=0) { // If move is a castling return castlingSymbolBuilder.apply(isKingSideCastling(moveData.getMovingIndex(), moveData.getMovingDestination())); } final StringBuilder notation = new StringBuilder(); final int movingPieceType = Math.abs(moveData.getMovingPiece()); notation.append(pieceNotationBuilder.apply(movingPieceType)); if (movingPieceType != PAWN && movingPieceType != KING && candidates.size() > 1) { addDisambiguation(notation, board, moveData, candidates, mvData); } if (moveData.getCapturedType()>0) { // If move is a capture if (movingPieceType == PAWN) { notation.append(fileNotation(getFile(moveData.getMovingIndex()))); } notation.append(captureSymbol); } // Adds destination square addCellNotation(notation, moveData.getMovingDestination()); // Add promotion piece type if (moveData.getPromotionType() != 0) { notation.append(promotionSymbolBuilder.apply(moveData.getPromotionType())); } // Adds check or checkmate board.makeMove(move, MoveConfidence.LEGAL); try { if (isCheck(board)) { List legalMoves = board.getLegalMoves(); notation.append(legalMoves.isEmpty() ? checkmateSymbol : checkSymbol); } } finally { board.unmakeMove(); } if (moveData.getCapturedType()>0 && moveData.getMovingDestination() != moveData.getCapturedIndex()) { // En passant notation.append(enPassantSymbol); } return notation.toString(); } /** * Checks if any ambiguity exists in notation and adds disambiguation if needed (e.g. if e2 can be reached via Nfe2 and Nbe2) * @param notation the notation to add disambiguation to * @param board the board on which the move is played * @param moveData the move data of the move to add disambiguation for * @param candidates the list of candidate moves * @param mvData a move data instance to use for disambiguation processing */ private void addDisambiguation(StringBuilder notation, B board, MoveData moveData, List candidates, MoveData mvData) { // Disambiguation is required final int pieceFile = getFile(moveData.getMovingIndex()); final boolean fileIsEnough = candidates.stream().filter(m -> { mvData.update(m, board); return pieceFile == getFile(mvData.getMovingIndex());}).count() == 1; if (fileIsEnough) { notation.append(fileNotation(pieceFile)); } else { final int pieceRank = getRank(moveData.getMovingIndex()); final boolean rankIsEnough = candidates.stream().filter(m -> { mvData.update(m, board); return pieceRank == getRank(mvData.getMovingIndex());}).count() == 1; if (rankIsEnough) { notation.append(rankNotation(getRank(moveData.getMovingIndex()))); } else { addCellNotation(notation, moveData.getMovingIndex()); } } } /** * Gets the side of a castling move. * @param kingFrom the index of the king before the castling * @param kingTo the index of the king after the castling * @return true if the castling is king side, false otherwise. The default implementation returns true if kingTo > kingFrom. */ private boolean isKingSideCastling(int kingFrom, int kingTo) { return kingTo > kingFrom; } /** * Gets the file of a cell. * @param cellIndex the index of the cell * @return the file of the cell (0 for a, 1 for b, etc.). The default implementation returns cellIndex % 8. */ private int getFile(int cellIndex) { return cellIndex & 7; } /** * Gets the rank of a cell. * @param cellIndex the index of the cell * @return the rank of the cell (0 for 1, 1 for 2, etc.). The default implementation returns cellIndex / 8. */ private int getRank(int cellIndex) { return 7 - (cellIndex >>> 3); } /** * Checks if the given board is in check. * @param board the board to check * @return true if the board is in check, false otherwise */ protected abstract boolean isCheck(B board); private char fileNotation(int file) { return (char)('a' + file); } private char rankNotation(int rank) { return (char)('1' + rank); } private void addCellNotation(StringBuilder notation, int cellIndex) { notation.append(fileNotation(getFile(cellIndex))); notation.append(rankNotation(getRank(cellIndex))); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy