From 7a6ed271521cd5645a01174977c32c28870624a4 Mon Sep 17 00:00:00 2001 From: Astesana Date: Wed, 11 Dec 2024 16:02:49 +0100 Subject: [PATCH] Work in progress --- .../evaluators/pesto/PestoEvaluator.java | 89 +++++++++ .../two/AbstractSimplifiedEvaluator2.java | 11 ++ .../simplified/two/PiecesOnlySquareTable.java | 97 ++++++++++ .../utils/AbstractComposedEvaluator.java | 48 +++++ .../AbstractPieceSquareTableEvaluator.java | 131 +++++++++++++ .../evaluators/utils/ComposableEvaluator.java | 14 ++ .../transposition/GenerationDetector.java | 23 +++ ...ctIncrementalSimplifiedEvaluatorTest2.java | 173 ++++++++++++++++++ 8 files changed, 586 insertions(+) create mode 100644 src/main/java/com/fathzer/chess/utils/evaluators/pesto/PestoEvaluator.java create mode 100644 src/main/java/com/fathzer/chess/utils/evaluators/simplified/two/AbstractSimplifiedEvaluator2.java create mode 100644 src/main/java/com/fathzer/chess/utils/evaluators/simplified/two/PiecesOnlySquareTable.java create mode 100644 src/main/java/com/fathzer/chess/utils/evaluators/utils/AbstractComposedEvaluator.java create mode 100644 src/main/java/com/fathzer/chess/utils/evaluators/utils/AbstractPieceSquareTableEvaluator.java create mode 100644 src/main/java/com/fathzer/chess/utils/evaluators/utils/ComposableEvaluator.java create mode 100644 src/main/java/com/fathzer/chess/utils/transposition/GenerationDetector.java create mode 100644 src/test/java/com/fathzer/chess/utils/evaluators/simplified/AbstractIncrementalSimplifiedEvaluatorTest2.java diff --git a/src/main/java/com/fathzer/chess/utils/evaluators/pesto/PestoEvaluator.java b/src/main/java/com/fathzer/chess/utils/evaluators/pesto/PestoEvaluator.java new file mode 100644 index 0000000..41fe638 --- /dev/null +++ b/src/main/java/com/fathzer/chess/utils/evaluators/pesto/PestoEvaluator.java @@ -0,0 +1,89 @@ +package com.fathzer.chess.utils.evaluators.pesto; + +public class PestoEvaluator { + // Piece types + public static final int PAWN = 0; + public static final int KNIGHT = 1; + public static final int BISHOP = 2; + public static final int ROOK = 3; + public static final int QUEEN = 4; + public static final int KING = 5; + + // Board representation + public static final int WHITE = 0; + public static final int BLACK = 1; + + // Piece definitions + public static final int WHITE_PAWN = (2 * PAWN + WHITE); + public static final int BLACK_PAWN = (2 * PAWN + BLACK); + public static final int WHITE_KNIGHT = (2 * KNIGHT + WHITE); + public static final int BLACK_KNIGHT = (2 * KNIGHT + BLACK); + public static final int WHITE_BISHOP = (2 * BISHOP + WHITE); + public static final int BLACK_BISHOP = (2 * BISHOP + BLACK); + public static final int WHITE_ROOK = (2 * ROOK + WHITE); + public static final int BLACK_ROOK = (2 * ROOK + BLACK); + public static final int WHITE_QUEEN = (2 * QUEEN + WHITE); + public static final int BLACK_QUEEN = (2 * QUEEN + BLACK); + public static final int WHITE_KING = (2 * KING + WHITE); + public static final int BLACK_KING = (2 * KING + BLACK); + public static final int EMPTY = BLACK_KING + 1; + + public static int sideToMove; + public static int[] board = new int[64]; + + public static int[] mgValue = {82, 337, 365, 477, 1025, 0}; + public static int[] egValue = {94, 281, 297, 512, 936, 0}; + + public static int[][] mgPestoTable = { /* Add mg tables here */ }; + public static int[][] egPestoTable = { /* Add eg tables here */ }; + + public static int[][] mgTable = new int[12][64]; + public static int[][] egTable = new int[12][64]; + + public static final int[] gamePhaseInc = {0, 0, 1, 1, 1, 1, 2, 2, 4, 4, 0, 0}; + + public static void initTables() { + for (int p = PAWN, pc = WHITE_PAWN; p <= KING; pc += 2, p++) { + for (int sq = 0; sq < 64; sq++) { + mgTable[pc][sq] = mgValue[p] + mgPestoTable[p][sq]; + egTable[pc][sq] = egValue[p] + egPestoTable[p][sq]; + mgTable[pc + 1][sq] = mgValue[p] + mgPestoTable[p][flip(sq)]; + egTable[pc + 1][sq] = egValue[p] + egPestoTable[p][flip(sq)]; + } + } + } + + public static int flip(int square) { + return square ^ 56; + } + + public static int other(int side) { + return side ^ 1; + } + + public static int pcolor(int p) { + return p & 1; + } + + public static int eval() { + int[] mg = {0, 0}; + int[] eg = {0, 0}; + int gamePhase = 0; + + for (int sq = 0; sq < 64; ++sq) { + int pc = board[sq]; + if (pc != EMPTY) { + mg[pcolor(pc)] += mgTable[pc][sq]; + eg[pcolor(pc)] += egTable[pc][sq]; + gamePhase += gamePhaseInc[pc]; + } + } + + int mgScore = mg[sideToMove] - mg[other(sideToMove)]; + int egScore = eg[sideToMove] - eg[other(sideToMove)]; + int mgPhase = Math.min(gamePhase, 24); + int egPhase = 24 - mgPhase; + + return (mgScore * mgPhase + egScore * egPhase) / 24; + } +} diff --git a/src/main/java/com/fathzer/chess/utils/evaluators/simplified/two/AbstractSimplifiedEvaluator2.java b/src/main/java/com/fathzer/chess/utils/evaluators/simplified/two/AbstractSimplifiedEvaluator2.java new file mode 100644 index 0000000..4dd7665 --- /dev/null +++ b/src/main/java/com/fathzer/chess/utils/evaluators/simplified/two/AbstractSimplifiedEvaluator2.java @@ -0,0 +1,11 @@ +package com.fathzer.chess.utils.evaluators.simplified.two; + +import java.util.function.Supplier; + +import com.fathzer.chess.utils.adapters.MoveData; +import com.fathzer.games.MoveGenerator; +import com.fathzer.games.ai.evaluation.ZeroSumEvaluator; + +public abstract class AbstractSimplifiedEvaluator2 > implements ZeroSumEvaluator, Supplier> { + +} diff --git a/src/main/java/com/fathzer/chess/utils/evaluators/simplified/two/PiecesOnlySquareTable.java b/src/main/java/com/fathzer/chess/utils/evaluators/simplified/two/PiecesOnlySquareTable.java new file mode 100644 index 0000000..ebe6893 --- /dev/null +++ b/src/main/java/com/fathzer/chess/utils/evaluators/simplified/two/PiecesOnlySquareTable.java @@ -0,0 +1,97 @@ +package com.fathzer.chess.utils.evaluators.simplified.two; + +import com.fathzer.chess.utils.evaluators.utils.AbstractPieceSquareTableEvaluator; +import com.fathzer.games.MoveGenerator; + +public abstract class PiecesOnlySquareTable> extends AbstractPieceSquareTableEvaluator { + private static final int[] PIECE_VALUES = {0, 100, 320, 330, 500, 900, 20000}; + private static final int [][] PIECE_POSITION_VALUES = new int[][] { + // Just to have index equals to piece type codes + new int[0], + // PAWN + new int[] { + 0, 0, 0, 0, 0, 0, 0, 0, + 50, 50, 50, 50, 50, 50, 50, 50, + 10, 10, 20, 30, 30, 20, 10, 10, + 5, 5, 10, 25, 25, 10, 5, 5, + 0, 0, 0, 20, 20, 0, 0, 0, + 5, -5,-10, 0, 0,-10, -5, 5, + 5, 10, 10,-20,-20, 10, 10, 5, + 0, 0, 0, 0, 0, 0, 0, 0}, + // KNIGHT + new int[] { + -50,-40,-30,-30,-30,-30,-40,-50, + -40,-20, 0, 0, 0, 0,-20,-40, + -30, 0, 10, 15, 15, 10, 0,-30, + -30, 5, 15, 20, 20, 15, 5,-30, + -30, 0, 15, 20, 20, 15, 0,-30, + -30, 5, 10, 15, 15, 10, 5,-30, + -40,-20, 0, 5, 5, 0,-20,-40, + -50,-40,-30,-30,-30,-30,-40,-50}, + // BISHOP + new int[] { + -20,-10,-10,-10,-10,-10,-10,-20, + -10, 0, 0, 0, 0, 0, 0,-10, + -10, 0, 5, 10, 10, 5, 0,-10, + -10, 5, 5, 10, 10, 5, 5,-10, + -10, 0, 10, 10, 10, 10, 0,-10, + -10, 10, 10, 10, 10, 10, 10,-10, + -10, 5, 0, 0, 0, 0, 5,-10, + -20,-10,-10,-10,-10,-10,-10,-20}, + // ROOK + new int[] { + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 10, 10, 10, 10, 10, 10, 5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + 0, 0, 0, 5, 5, 0, 0, 0}, + // QUEEN + new int[] { + -20,-10,-10, -5, -5,-10,-10,-20, + -10, 0, 0, 0, 0, 0, 0,-10, + -10, 0, 5, 5, 5, 5, 0,-10, + -5, 0, 5, 5, 5, 5, 0, -5, + 0, 0, 5, 5, 5, 5, 0, -5, + -10, 5, 5, 5, 5, 5, 0,-10, + -10, 0, 5, 0, 0, 0, 0,-10, + -20,-10,-10, -5, -5,-10,-10,-20}, + // KING + new int[] { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }}; + + static { + for (int i = 0; i < PIECE_VALUES.length; i++) { + add(PIECE_POSITION_VALUES[i], PIECE_VALUES[i]); + } + } + + protected PiecesOnlySquareTable() { + super(); + } + + protected PiecesOnlySquareTable(int eval) { + super(eval); + } + + private static void add(int[] array, int value) { + for (int i = 0; i < array.length; i++) { + array[i] = array[i]+value; + } + } + + @Override + protected int getPositionValue(int piece, int index) { + return PIECE_POSITION_VALUES[piece][index]; + } +} diff --git a/src/main/java/com/fathzer/chess/utils/evaluators/utils/AbstractComposedEvaluator.java b/src/main/java/com/fathzer/chess/utils/evaluators/utils/AbstractComposedEvaluator.java new file mode 100644 index 0000000..738998b --- /dev/null +++ b/src/main/java/com/fathzer/chess/utils/evaluators/utils/AbstractComposedEvaluator.java @@ -0,0 +1,48 @@ +package com.fathzer.chess.utils.evaluators.utils; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Supplier; + +import com.fathzer.chess.utils.adapters.BoardExplorerBuilder; +import com.fathzer.chess.utils.adapters.MoveData; +import com.fathzer.games.MoveGenerator; +import com.fathzer.games.ai.evaluation.ZeroSumEvaluator; + +public abstract class AbstractComposedEvaluator> implements ZeroSumEvaluator, BoardExplorerBuilder, Supplier> { + private MoveData moveData; + private List> evaluators; + + protected AbstractComposedEvaluator() { + moveData = get(); + evaluators = new LinkedList<>(); + } + + @Override + public void prepareMove(B board, M move) { + if (!moveData.update(move, board)) { + return; + } + evaluators.forEach(ev -> ev.prepareMove(moveData)); + } + + @Override + public void init(B board) { + evaluators.forEach(ev -> ev.init(board)); + } + + @Override + public void commitMove() { + evaluators.forEach(ZeroSumEvaluator::commitMove); + } + + @Override + public void unmakeMove() { + evaluators.forEach(ev -> ev.unmakeMove()); + } + + @Override + public int evaluateAsWhite(B board) { + return evaluators.stream().mapToInt(ev -> ev.evaluateAsWhite(board)).sum(); + } +} diff --git a/src/main/java/com/fathzer/chess/utils/evaluators/utils/AbstractPieceSquareTableEvaluator.java b/src/main/java/com/fathzer/chess/utils/evaluators/utils/AbstractPieceSquareTableEvaluator.java new file mode 100644 index 0000000..21539c8 --- /dev/null +++ b/src/main/java/com/fathzer/chess/utils/evaluators/utils/AbstractPieceSquareTableEvaluator.java @@ -0,0 +1,131 @@ +package com.fathzer.chess.utils.evaluators.utils; + +import static com.fathzer.chess.utils.Pieces.KING; +import static com.fathzer.chess.utils.Pieces.ROOK; + +import java.util.concurrent.atomic.AtomicInteger; + +import com.fathzer.chess.utils.Pieces; +import com.fathzer.chess.utils.adapters.BoardExplorer; +import com.fathzer.chess.utils.adapters.BoardExplorerBuilder; +import com.fathzer.chess.utils.adapters.MoveData; +import com.fathzer.games.MoveGenerator; +import com.fathzer.games.ai.evaluation.Evaluator; +import com.fathzer.games.util.Stack; + +/** An incremental evaluator based on a piece square table implementation. + */ +public abstract class AbstractPieceSquareTableEvaluator> implements ComposableEvaluator, BoardExplorerBuilder { + + private final Stack states; //TODO implement something lighter + private int toCommit; + + protected AbstractPieceSquareTableEvaluator() { + this.states = new Stack<>(AtomicInteger::new); + } + + protected AbstractPieceSquareTableEvaluator(int eval) { + this(); + this.states.get().set(eval); + } + + @Override + public void init(B board) { + states.clear(); + states.set(new AtomicInteger(getRawEvaluation(getExplorer(board)))); + } + + private int getRawEvaluation(BoardExplorer explorer) { + int points = 0; + do { + final int p = explorer.getPiece(); + final int piece = Math.abs(p); + final int index = explorer.getIndex(); + final boolean isBlack = p<0; + points += getPositionValue(piece, isBlack, index); + } while (explorer.next()); + return points; + } + + @Override + public void prepareMove(MoveData moveData) { + final boolean isBlack = moveData.getMovingPiece()<0; + int moving = Math.abs(moveData.getMovingPiece()); + final int movingIndex = moveData.getMovingIndex(); + int inc = 0; + if (moving==KING) { + // Be cautious with castling + int rookIndex = moveData.getCastlingRookIndex(); + if (rookIndex>=0) { + // It's a castling move, update rook positions values + inc = getPositionValue(ROOK, isBlack, moveData.getCastlingRookDestinationIndex()) - getPositionValue(ROOK, isBlack, rookIndex); + } + } + // Remove the old position value of the moving piece + inc -= getPositionValue(moving, isBlack, movingIndex); + final int promoType = moveData.getPromotionType(); + if (promoType!=0) { + // If promotion, update the moving piece + moving = promoType; + } + // Adds the new position value of the + inc += getPositionValue(moving, isBlack, moveData.getMovingDestination()); + int captured = moveData.getCapturedType(); + if (captured!=0) { + // If the move is a capture add its position value + inc -= getPositionValue(captured, !isBlack, moveData.getCapturedIndex()); + } + toCommit = states.get().get() + inc; + } + + @Override + public void commitMove() { + states.next(); + states.set(new AtomicInteger(toCommit)); + } + + @Override + public void unmakeMove() { + states.previous(); + } + + @Override + public Evaluator fork() { + return fork(states.get().get()); + } + + @Override + public int evaluateAsWhite(B board) { + return states.get().get(); + } + + /** Gets the position value associated with a white piece at an index. + * @param piece The piece type as define in {@link Pieces} + * @param index The index of the piece on the board as defined in {@link BoardExplorer} + * @return an integer + */ + protected abstract int getPositionValue(int piece, int index); + + /** Gets the value (from the white point of view) of a piece at a position. + * @param piece The piece type as define in {@link Pieces} + * @param black true if piece is black + * @param index The index of the piece on the board as defined in {@link BoardExplorer} + * @return an integer + */ + private int getPositionValue(int piece, boolean black, int index) { + if (black) { + final int row = 7 - index/8; + final int col = index%8; + index = row*8 + col; + return -getPositionValue(piece, index); + } else { + return getPositionValue(piece, index); + } + } + + /** Creates a new instance initialized with current evaluation that will become the initial state of created instance. + * @param state The initial state. + * @return a new evaluator of the same class as this, the same view point, and initialized with the state. + */ + protected abstract AbstractPieceSquareTableEvaluator fork(int evaluation); +} diff --git a/src/main/java/com/fathzer/chess/utils/evaluators/utils/ComposableEvaluator.java b/src/main/java/com/fathzer/chess/utils/evaluators/utils/ComposableEvaluator.java new file mode 100644 index 0000000..c670cec --- /dev/null +++ b/src/main/java/com/fathzer/chess/utils/evaluators/utils/ComposableEvaluator.java @@ -0,0 +1,14 @@ +package com.fathzer.chess.utils.evaluators.utils; + +import com.fathzer.chess.utils.adapters.MoveData; +import com.fathzer.games.MoveGenerator; +import com.fathzer.games.ai.evaluation.ZeroSumEvaluator; + +public interface ComposableEvaluator> extends ZeroSumEvaluator { + void prepareMove(MoveData moveData); + + @Override + default void prepareMove(B board, M move) { + throw new IllegalStateException("Prepare move is not implemented"); + } +} \ No newline at end of file diff --git a/src/main/java/com/fathzer/chess/utils/transposition/GenerationDetector.java b/src/main/java/com/fathzer/chess/utils/transposition/GenerationDetector.java new file mode 100644 index 0000000..32520e0 --- /dev/null +++ b/src/main/java/com/fathzer/chess/utils/transposition/GenerationDetector.java @@ -0,0 +1,23 @@ +package com.fathzer.chess.utils.transposition; + +import java.util.function.Predicate; + +/** A generation detector that triggers a new generation if a pawn is move or a capture occurred. + */ +public abstract class GenerationDetector implements Predicate { + private long lastHash; + + @Override + public boolean test(B t) { + final long hash = getHash(t); + if (hash==lastHash) { + // Seems the position didn't changes + return false; + } + lastHash = hash; + return getHalfMoveCount(t)<2; + } + + protected abstract long getHash(B board); + protected abstract int getHalfMoveCount(B board); +} diff --git a/src/test/java/com/fathzer/chess/utils/evaluators/simplified/AbstractIncrementalSimplifiedEvaluatorTest2.java b/src/test/java/com/fathzer/chess/utils/evaluators/simplified/AbstractIncrementalSimplifiedEvaluatorTest2.java new file mode 100644 index 0000000..cd3d89e --- /dev/null +++ b/src/test/java/com/fathzer/chess/utils/evaluators/simplified/AbstractIncrementalSimplifiedEvaluatorTest2.java @@ -0,0 +1,173 @@ +package com.fathzer.chess.utils.evaluators.simplified; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.function.IntFunction; + +import static com.github.bhlangonijr.chesslib.Square.*; + +import org.junit.jupiter.api.Test; + +import com.fathzer.chess.test.utils.FENUtils; +import com.fathzer.chess.utils.adapters.BoardExplorer; +import com.fathzer.chess.utils.adapters.BoardExplorerBuilder; +import com.fathzer.chess.utils.adapters.MoveData; +import com.fathzer.chess.utils.adapters.chesslib.ChessLibBoardExplorer; +import com.fathzer.chess.utils.adapters.chesslib.ChessLibMoveData; +import com.fathzer.chess.utils.adapters.chesslib.ChessLibMoveGenerator; +import com.fathzer.chess.utils.evaluators.simplified.two.PiecesOnlySquareTable; +import com.fathzer.games.MoveGenerator.MoveConfidence; +import com.github.bhlangonijr.chesslib.Piece; +import com.github.bhlangonijr.chesslib.move.Move; + +class AbstractIncrementalSimplifiedEvaluatorTest2 { + + private final class MyEval extends PiecesOnlySquareTable implements BoardExplorerBuilder { + private MoveData moveData = new ChessLibMoveData(); + + public MyEval() { + super(); + } + + public MyEval(int state) { + super(state); + } + + @Override + public void prepareMove(ChessLibMoveGenerator board, Move move) { + moveData.update(move, board); + prepareMove(moveData); + } + + @Override + public BoardExplorer getExplorer(ChessLibMoveGenerator board) { + return new ChessLibBoardExplorer(board.getBoard()); + } + + @Override + protected PiecesOnlySquareTable fork(int state) { + return new MyEval(state); + } + } + + private int getStaticEval(ChessLibMoveGenerator board) { + MyEval eval = new MyEval(); + eval.init(board); + return eval.evaluateAsWhite(board); + } + + @Test + void testIncrementalThings() { + Move mv; + // Start with black queen and rook vs a pawn, a knight and a bishop for black => Middle game + MyEval ev = new MyEval(); + ChessLibMoveGenerator board = FENUtils.from("3qk3/7P/8/8/8/N7/B4r2/4K3 w - - 0 1"); +/* ev.init(board); + int expected = 150+290+320-895-510; +System.out.println(expected); + assertEquals(expected, ev.evaluateAsWhite(board)); + + MyEval forked = (MyEval) ev.fork(); + ChessLibMoveGenerator forkedMg = (ChessLibMoveGenerator) board.fork(); + assertEquals(expected, forked.evaluateAsWhite(board)); + + // Take black rook => END_GAME + mv = new Move(E1, F2); + forked.prepareMove(forkedMg, mv); + assertTrue(forkedMg.makeMove(mv, MoveConfidence.UNSAFE)); + forked.commitMove(); + int forkedExpected1 = expected+510; + { + ChessLibMoveGenerator b = (ChessLibMoveGenerator) board.fork(); + b.makeMove(mv, MoveConfidence.UNSAFE); + assertEquals(forkedExpected1, getStaticEval(b)); + } + assertEquals(forkedExpected1, forked.evaluateAsWhite(forkedMg)); + assertEquals(expected, ev.evaluateAsWhite(board)); + + // Make a stupid move with the queen :-) + mv = new Move(D8, E7); + forked.prepareMove(forkedMg, mv); + assertTrue(forkedMg.makeMove(mv, MoveConfidence.UNSAFE)); + forked.commitMove(); + int forkedExpected2 = forkedExpected1 - 5; + assertEquals(forkedExpected2, forked.evaluateAsWhite(forkedMg)); + + // A promotion to a queen :-) => come back to Middle game + mv = new Move(H7, H8, Piece.WHITE_QUEEN); + forked.prepareMove(forkedMg, mv); + assertTrue(forkedMg.makeMove(mv, MoveConfidence.UNSAFE)); + forked.commitMove(); + int forkedExpected3 = forkedExpected2 + 880 - 150; + assertEquals(forkedExpected3, forked.evaluateAsWhite(forkedMg)); + + forked.unmakeMove(); + assertEquals(forkedExpected2, forked.evaluateAsWhite(forkedMg)); + forked.unmakeMove(); + assertEquals(forkedExpected1, forked.evaluateAsWhite(forkedMg)); + forked.unmakeMove(); + assertEquals(expected, forked.evaluateAsWhite(forkedMg)); +*/ + // Other tests on another board + board = FENUtils.from("r3k3/8/8/8/8/8/1p6/R3KQ2 b q - 1 1"); + ev.init(board); + assertEquals(740, ev.evaluateAsWhite(board)); + // Test castling + mv = new Move(E8, C8); + { + ChessLibMoveGenerator b = (ChessLibMoveGenerator) board.fork(); + b.makeMove(mv, MoveConfidence.UNSAFE); + assertEquals(740-5, getStaticEval(b)); + } + ev.prepareMove(board, mv); + ev.commitMove(); + assertEquals(740-5, ev.evaluateAsWhite(board)); + + // Test promotion with capture + ev.unmakeMove(); + mv = new Move(B2, A1, Piece.BLACK_QUEEN); + ev.prepareMove(board, mv); + ev.commitMove(); + assertEquals(745+150-880-500, ev.evaluateAsWhite(board)); + + // Test that illegal move does not throw exception + ev.unmakeMove(); + mv = new Move(A7, A6); + ev.prepareMove(board, mv); + mv = new Move(H8, H1, Piece.BLACK_QUEEN); + ev.prepareMove(board, mv); + } + + @Test + void testPositionValuesSymetry() { + testVerticalSymetry(1, "pawn"); + testVerticalSymetry(2, "knight"); + testVerticalSymetry(3, "bishop"); + testVerticalSymetry(4, "rook"); + + // Pawel's suggestion with white queen at b3 + ChessLibMoveGenerator board = FENUtils.from("rnbqkbnr/pp1ppppp/8/8/8/1Q6/PP1PPPPP/RNB1KBNR w KQkq - 0 1"); + MyEval ev = new MyEval(); + ev.init(board); + assertEquals(10, ev.evaluateAsWhite(board)); + // Pawel's suggestion with both queen's at b3 and c7 + board = FENUtils.from("rnb1kbnr/ppqppppp/8/8/8/1Q6/PP1PPPPP/RNB1KBNR w KQkq - 0 1"); + ev.init(board); + assertEquals(0, ev.evaluateAsWhite(board)); + } + + private void testVerticalSymetry(int pieceKind, String name) { + testVerticalSymetry(name, i -> SimplifiedEvaluatorBase.getPositionValue(pieceKind, i)); + } + + private void testVerticalSymetry(String wording, IntFunction valueGetter) { + for (int row=0;row<8;row++) { + final int startOFrowIndex = row*8; + for (int col=0;col<4;col++) { + final int index=startOFrowIndex + col; + final int sym = startOFrowIndex + 7 - col; + assertEquals (valueGetter.apply(index), valueGetter.apply(sym), "No symetry for "+wording+" on indexes "+index+" & "+sym); + } + } + } +}