package dev.kske.chess.board; import java.util.ArrayList; import java.util.List; import dev.kske.chess.board.Piece.Color; /** * Project: Chess
* File: Log.java
* Created: 09.07.2019
* Author: Kai S. K. Engelbart */ public class Log { private MoveNode root, current; private Position enPassant; private Color activeColor; private int fullmoveCounter, halfmoveClock; public Log() { reset(); } /** * Creates a (partially deep) copy of another {@link Log} instance which begins * with the current {@link MoveNode}. * * @param other The {@link Log} instance to copy * @param copyVariations If set to {@code true}, subsequent variations of the * current {@link MoveNode} are copied with the * {@link Log} */ public Log(Log other, boolean copyVariations) { enPassant = other.enPassant; activeColor = other.activeColor; fullmoveCounter = other.fullmoveCounter; halfmoveClock = other.halfmoveClock; // The new root is the current node of the copied instance if (!other.isEmpty()) { root = new MoveNode(other.current, copyVariations); root.parent = null; current = root; } } /** * Adds a move to the move history and adjusts the log to the new position. * * @param move The move to log * @param capturedPiece The piece captured with the move * @param pawnMove {@code true} if the move was made by a pawn */ public void add(Move move, Piece capturedPiece, boolean pawnMove) { enPassant = pawnMove && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null; if (activeColor == Color.BLACK) ++fullmoveCounter; if (pawnMove || capturedPiece != null) halfmoveClock = 0; else++halfmoveClock; activeColor = activeColor.opposite(); final MoveNode leaf = new MoveNode(move, capturedPiece, enPassant, activeColor, fullmoveCounter, halfmoveClock); if (isEmpty()) { root = leaf; current = leaf; } else { current.addVariation(leaf); current = leaf; } } /** * Removed the last move from the log and adjusts its state to the previous * move. */ public void removeLast() { if (!isEmpty() && current.parent != null) { current.parent.variations.remove(current); current = current.parent; activeColor = current.activeColor; enPassant = current.enPassant; fullmoveCounter = current.fullmoveCounter; halfmoveClock = current.halfmoveClock; } else reset(); } public boolean isEmpty() { return root == null; } /** * Reverts the log to its initial state corresponding to the default board * position. */ public void reset() { root = null; current = null; enPassant = null; activeColor = Color.WHITE; fullmoveCounter = 1; halfmoveClock = 0; } /** * @return The first logged move, or {@code null} if there is none */ public MoveNode getRoot() { return root; } /** * @return the last logged move, or {@code null} if there is none */ public MoveNode getLast() { return current; } public Position getEnPassant() { return enPassant; } public void setEnPassant(Position enPassant) { this.enPassant = enPassant; } public Color getActiveColor() { return activeColor; } public void setActiveColor(Color activeColor) { this.activeColor = activeColor; } public int getFullmoveCounter() { return fullmoveCounter; } public void setFullmoveCounter(int fullmoveCounter) { this.fullmoveCounter = fullmoveCounter; } public int getHalfmoveClock() { return halfmoveClock; } public void setHalfmoveClock(int halfmoveClock) { this.halfmoveClock = halfmoveClock; } public static class MoveNode { public final Move move; public final Piece capturedPiece; public final Position enPassant; public final Color activeColor; public final int fullmoveCounter, halfmoveClock; private MoveNode parent; private List variations; /** * Creates a new {@link MoveNode}. * * @param move The logged {@link Move} * @param capturedPiece The {@link Piece} captures by the logged {@link Move} * @param enPassant The en passant {@link Position} valid after the logged * {@link Move}, or {@code null} if there is none * @param activeColor The {@link Color} active after the logged {@link Move} * @param fullmoveCounter * @param halfmoveClock */ public MoveNode(Move move, Piece capturedPiece, Position enPassant, Color activeColor, int fullmoveCounter, int halfmoveClock) { this.move = move; this.capturedPiece = capturedPiece; this.enPassant = enPassant; this.activeColor = activeColor; this.fullmoveCounter = fullmoveCounter; this.halfmoveClock = halfmoveClock; } /** * Creates a (deep) copy of another {@link MoveNode}. * * @param other The {@link MoveNode} to copy * @param copyVariations When this is set to {@code true} a deep copy is * created, which * considers subsequent variations */ public MoveNode(MoveNode other, boolean copyVariations) { this(other.move, other.capturedPiece, other.enPassant, other.activeColor, other.fullmoveCounter, other.halfmoveClock); if (copyVariations && other.variations != null) { if (variations == null) variations = new ArrayList<>(); other.variations.forEach(variation -> { MoveNode copy = new MoveNode(variation, true); copy.parent = this; variations.add(copy); }); } } /** * Adds another {@link MoveNode} as a child node. * * @param variation The {@link MoveNode} to append to this {@link MoveNode} */ public void addVariation(MoveNode variation) { if (variations == null) variations = new ArrayList<>(); if (!variations.contains(variation)) { variations.add(variation); variation.parent = this; } } /** * @return A list of all variations associated with this {@link MoveNode} */ public List getVariations() { return variations; } } }