This repository has been archived on 2021-02-18. You can view files and clone it, but cannot push or open issues or pull requests.
chess/src/main/java/dev/kske/chess/board/Log.java

366 lines
9.3 KiB
Java

package dev.kske.chess.board;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.game.Game;
/**
* Manages the move history of a {@link Game}.<br>
* <br>
* Project: <strong>Chess</strong><br>
* File: <strong>Log.java</strong><br>
* Created: <strong>09.07.2019</strong><br>
*
* @since Chess v0.1-alpha
* @author Kai S. K. Engelbart
*/
public class Log implements Iterable<MoveNode> {
private MoveNode root, current;
private Color activeColor;
private boolean[] castlingRights;
private Position enPassant;
private int fullmoveNumber, halfmoveClock;
/**
* Creates an instance of {@link Log} in the default state.
*/
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;
castlingRights = other.castlingRights.clone();
activeColor = other.activeColor;
fullmoveNumber = other.fullmoveNumber;
halfmoveClock = other.halfmoveClock;
// The new root is the current node of the copied instance
if (!other.isEmpty()) {
root = new MoveNode(other.root, copyVariations);
root.setParent(null);
current = root;
}
}
/**
* @return an iterator over all {@link MoveNode} objects that are either the
* root node or a first variation of another node, starting from the
* root node
*/
@Override
public Iterator<MoveNode> iterator() {
return new Iterator<MoveNode>() {
private MoveNode current = root;
private boolean hasNext = !isEmpty();
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public MoveNode next() {
MoveNode result = current;
if (current.hasVariations())
current = current.getVariations().get(0);
else
hasNext = false;
return result;
}
};
}
/**
* Adds a move to the move history and adjusts the log to the new position.
*
* @param move The move to log
* @param piece The piece that performed the move
* @param capturedPiece The piece captured with the move
*/
public void add(Move move, Piece piece, Piece capturedPiece) {
enPassant = piece instanceof Pawn && move.getyDist() == 2
? new Position(move.getPos().x, move.getPos().y + move.getySign())
: null;
if (activeColor == Color.BLACK)
++fullmoveNumber;
if (piece instanceof Pawn || capturedPiece != null)
halfmoveClock = 0;
else
++halfmoveClock;
activeColor = activeColor.opposite();
// Disable castling rights if a king or a rook has been moved
if (piece instanceof King || piece instanceof Rook)
disableCastlingRights(piece, move.getPos());
final MoveNode leaf = new MoveNode(
move,
capturedPiece,
castlingRights.clone(),
enPassant,
activeColor,
fullmoveNumber,
halfmoveClock
);
if (isEmpty()) {
root = leaf;
current = leaf;
} else {
current.addVariation(leaf);
current = leaf;
}
}
/**
* Removes the last move from the log and adjusts its state to the previous
* move.
*/
public void removeLast() {
if (hasParent()) {
current.getParent().getVariations().remove(current);
current = current.getParent();
update();
} else
reset();
}
/**
* @return {@code true} if the root node exists
*/
public boolean isEmpty() { return root == null; }
/**
* @return {@code true} if the current node has a parent node
*/
public boolean hasParent() {
return !isEmpty() && current.hasParent();
}
/**
* Reverts the log to its initial state corresponding to the default board
* position.
*/
public void reset() {
root = null;
current = null;
castlingRights = new boolean[] {
true, true, true, true
};
enPassant = null;
activeColor = Color.WHITE;
fullmoveNumber = 1;
halfmoveClock = 0;
}
/**
* Changes the current node to one of its children (variations).
*
* @param index the index of the variation to select
*/
public void selectNextNode(int index) {
if (
!isEmpty() && current.hasVariations()
&& index < current.getVariations().size()
) {
current = current.getVariations().get(index);
update();
}
}
/**
* Selects the parent of the current {@link MoveNode} as the current node.
*/
public void selectPreviousNode() {
if (hasParent()) {
current = current.getParent();
update();
}
}
/**
* Selects the root {@link MoveNode} as the current node.
*/
public void selectRootNode() {
if (!isEmpty()) {
current = root;
update();
}
}
/**
* Sets the active color, castling rights, en passant target square,
* fullmove
* number and halfmove clock to those of the current {@link MoveNode}.
*/
private void update() {
activeColor = current.activeColor;
castlingRights = current.castlingRights.clone();
enPassant = current.enPassant;
fullmoveNumber = current.fullmoveCounter;
halfmoveClock = current.halfmoveClock;
}
/**
* Removed the castling rights bound to a rook or king for the rest of the
* game.
* This method should be called once the piece has been moved, as a castling
* move involving this piece is forbidden afterwards.
*
* @param piece the rook or king to disable the castling rights
* for
* @param initialPosition the initial position of the piece during the start
* of
* the game
*/
private void disableCastlingRights(Piece piece, Position initialPosition) {
// Kingside
if (
piece instanceof King
|| piece instanceof Rook && initialPosition.x == 7
)
castlingRights[piece.getColor() == Color.WHITE
? MoveNode.WHITE_KINGSIDE
: MoveNode.BLACK_KINGSIDE] = false;
// Queenside
if (
piece instanceof King
|| piece instanceof Rook && initialPosition.x == 0
)
castlingRights[piece.getColor() == Color.WHITE
? MoveNode.WHITE_QUEENSIDE
: MoveNode.BLACK_QUEENSIDE] = false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(castlingRights);
result = prime * result + Objects.hash(
activeColor,
current,
enPassant,
fullmoveNumber,
halfmoveClock
);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Log other = (Log) obj;
return activeColor == other.activeColor && Arrays
.equals(castlingRights, other.castlingRights)
&& Objects.equals(current, other.current)
&& Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber && halfmoveClock == other.halfmoveClock;
}
/**
* @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; }
/**
* @return the castling rights present during the current move
*/
public boolean[] getCastlingRights() { return castlingRights; }
/**
* Sets the castling rights present during the current move.
*
* @param castlingRights the castling rights to set
*/
public void setCastlingRights(boolean[] castlingRights) {
this.castlingRights = castlingRights;
}
/**
* @return the en passant target position of the current move or
* {@code null} if
* the current move is not an en passant move.
*/
public Position getEnPassant() { return enPassant; }
/**
* Sets the en passant target position.
*
* @param enPassant the en passant target position to set
*/
public void setEnPassant(Position enPassant) {
this.enPassant = enPassant;
}
/**
* @return the color active during the current move
*/
public Color getActiveColor() { return activeColor; }
/**
* Sets the color active during the current move.
*
* @param activeColor the active color to set
*/
public void setActiveColor(Color activeColor) {
this.activeColor = activeColor;
}
/**
* @return the number of moves made until the current move
*/
public int getFullmoveNumber() { return fullmoveNumber; }
/**
* Sets the number of moves made until the current move.
*
* @param fullmoveNumber the fullmove number to set
*/
public void setFullmoveNumber(int fullmoveNumber) {
this.fullmoveNumber = fullmoveNumber;
}
/**
* @return the number of halfmoves since the last capture move or pawn move
*/
public int getHalfmoveClock() { return halfmoveClock; }
/**
* Sets then number of halfmoves since the last capture move or pawn move
*
* @param halfmoveClock the halfmove clock to set
*/
public void setHalfmoveClock(int halfmoveClock) {
this.halfmoveClock = halfmoveClock;
}
}