Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Evaluators symmetry 293 #316

Merged
merged 95 commits into from
Aug 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
5343bc7
Mobility symmetry red test.
lrozenblyum Feb 25, 2019
aa3bd0e
Mobility evaluator: symmetry introduced.
lrozenblyum Feb 25, 2019
cd30290
Refactoring + doc updated.
lrozenblyum Feb 25, 2019
8104b53
Red test for normalized range keeping.
lrozenblyum Feb 25, 2019
5bd9a26
Mobility evaluator: estimate the advantage in complex way.
lrozenblyum Feb 25, 2019
ea64afd
Refactoring: extracted symmetrical normalized range.
lrozenblyum Feb 25, 2019
3bb6180
Documentation + range reused
lrozenblyum Feb 25, 2019
667d932
Refactoring: constant convention
lrozenblyum Feb 25, 2019
4f0c967
Center control evaluator: symmetry red test
lrozenblyum Feb 26, 2019
5380c38
Center control evaluation: based on index
lrozenblyum Feb 26, 2019
c968b22
Normalized range: red test
lrozenblyum Feb 26, 2019
249cda9
Center control evaluator - normalization enforced
lrozenblyum Feb 26, 2019
6f59063
Clean up
lrozenblyum Feb 26, 2019
200c161
Refactoring: asserts generalized
lrozenblyum Feb 26, 2019
f9ec3c1
Refactoring: normalized assert used more widely
lrozenblyum Feb 26, 2019
78e0619
Test castling symmetry: force opponent blocks
lrozenblyum Apr 1, 2019
b6ba5a4
symmetry introduced, however the test is still red.
lrozenblyum Apr 1, 2019
01fec6d
Castling safety evaluator now takes all in-between pieces into account.
lrozenblyum Apr 1, 2019
9100304
Merge branches 'EvaluatorsSymmetry-293' and 'master' of https://githu…
lrozenblyum Apr 2, 2019
1fefa30
Test that will require normalized range guarantees from
lrozenblyum Apr 2, 2019
696b1e3
Refactoring: moved castling safety evaluator to normalized package.
lrozenblyum Apr 2, 2019
f7fbb83
Guarantee normalized range for castling safety
lrozenblyum Apr 2, 2019
92e32ef
Try to handle Sonar remark
lrozenblyum Apr 2, 2019
28924f2
Refactoring: position enriched by occupied squares method
lrozenblyum Apr 3, 2019
220a34a
Refactoring: reused method inside Position class
lrozenblyum Apr 3, 2019
7401cd7
Castling safety: more symmetry and more correctness
lrozenblyum Apr 8, 2019
db6786a
Test that reveals an old bug: moving one of rooks was encouraged
lrozenblyum Jun 27, 2019
b3dc087
Fixed bug: we were encouraging rook moving to lost castling right.
lrozenblyum Jun 27, 2019
c5829c6
Refactoring: extracted 2-plies evaluator for testability
lrozenblyum Jul 1, 2019
0bc292f
Test that shows illogical behavior of 2-ply thinking of castling safety.
lrozenblyum Jul 1, 2019
47e9f79
Castling safety calculates absolute numbers insetead of relative.
lrozenblyum Jul 1, 2019
3d2b6a8
Test for issue: 2'nd ply evaluator doesn't want castling
lrozenblyum Jul 1, 2019
512f4ef
Fact of castling execution persisted in Position as part of history.
lrozenblyum Jul 2, 2019
28b38cd
Fixed logging that has been incorrect since 2-ply evaluator extraction.
lrozenblyum Jul 2, 2019
1638b91
Prepare Range infrastructure for future reuse
lrozenblyum Jul 2, 2019
f5e2011
Symmetrical normalized range moved to new Range infrastructure
lrozenblyum Jul 2, 2019
bd77b65
Refactoring: Range reused
lrozenblyum Jul 2, 2019
4a74b7b
Javadoc
lrozenblyum Jul 2, 2019
ef44ed7
Cross-reference to the removal ticket
lrozenblyum Jul 2, 2019
940f4c7
Migrated to Parameterized test.
lrozenblyum Jul 2, 2019
b8ebb5c
2-ply evaluator: enforce Normalized constraint.
lrozenblyum Jul 2, 2019
80d937c
Range reused more extensively
lrozenblyum Jul 2, 2019
00a91e7
Refactored length calculation
lrozenblyum Jul 2, 2019
813a9d6
Removed outdated comment: now we can think for > 1 ply
lrozenblyum Jul 3, 2019
9f87e64
Additional center control test coverage
lrozenblyum Jul 3, 2019
42d1998
Test for attack evaluator not taking opponent into account
lrozenblyum Jul 7, 2019
02dc1b1
Attack evaluator respects opponent situation
lrozenblyum Jul 7, 2019
bcbd5b6
Attack evaluator calculates values in normalized range.
lrozenblyum Jul 7, 2019
04ad896
Test to enforce protection evaluator symmetry
lrozenblyum Jul 8, 2019
4a63090
Added test for missing protection evaluator feature:
lrozenblyum Jul 9, 2019
26fc403
Refactoring: introduced separate protection index calculator.
lrozenblyum Jul 9, 2019
df6222d
Draft protection evaluator with symmetry and changed logic.
lrozenblyum Jul 11, 2019
67e4594
Fixed bug in the test
lrozenblyum Jul 11, 2019
ec9229c
Moved test that is now covered by AttackEvaluator to the proper place.
lrozenblyum Jul 17, 2019
bef01de
Moved test that is now covered by AttackEvaluator to the proper place.
lrozenblyum Jul 17, 2019
62ddd1c
Protection evaluator respects fact of multiple pieces protecting one.
lrozenblyum Jul 17, 2019
4df44a6
Protection evaluator: lazy normalization.
lrozenblyum Jul 17, 2019
2ad284e
The more test coverage the better
lrozenblyum Jul 17, 2019
aaa07dc
'Smart' test migrated to 2-ply evaluation.
lrozenblyum Jul 17, 2019
f03235f
Refactoring: comments
lrozenblyum Jul 17, 2019
f498b31
Refactoring: inlined 'index' calculator
lrozenblyum Jul 17, 2019
f244443
Refactoring: inlined 'attack' index calculator
lrozenblyum Jul 17, 2019
fb4cef3
Merge pull request #326 from lrozenblyum/master
lrozenblyum Jul 18, 2019
e471a36
Refactoring
lrozenblyum Jul 22, 2019
10d215e
Refactoring: simplified target result calculation
lrozenblyum Jul 22, 2019
44f5422
Refactoring towards common 'index' concept
lrozenblyum Jul 22, 2019
08c152b
Refactoring: common renaming
lrozenblyum Jul 22, 2019
2b09b2f
Preparing interface for Position+Side analyzing
lrozenblyum Jul 22, 2019
917894e
Refactoring to the new interface
lrozenblyum Jul 22, 2019
f6f01f6
Refactoring: code reuse + preparation for #323
lrozenblyum Jul 22, 2019
d86993a
Refactoring: reused common facility
lrozenblyum Jul 22, 2019
5290a6c
Refactoring: reused common facility
lrozenblyum Jul 22, 2019
4cee654
Refactoring: reused common facility
lrozenblyum Jul 22, 2019
15cfe1d
Refactoring: reused common facility
lrozenblyum Jul 22, 2019
b08b16e
Refactoring: reused common facility
lrozenblyum Jul 22, 2019
efdaf3a
Refactoring: common naming
lrozenblyum Jul 22, 2019
00188fb
Javadoc
lrozenblyum Jul 22, 2019
5e367f0
Refactoring
lrozenblyum Jul 22, 2019
1cc5997
Try to avoid handling terminal positions.
lrozenblyum Jul 24, 2019
e1efa00
Fixed tests that were red due to missing King in the opponent.
lrozenblyum Jul 30, 2019
644d277
Introducing movedSide historical information
lrozenblyum Jul 31, 2019
3e93ae8
Moved side stored correctly for usual moves
lrozenblyum Jul 31, 2019
991131e
Symmetrical test
lrozenblyum Jul 31, 2019
edf5da8
Introduce more historical information about terminal positions
lrozenblyum Jul 31, 2019
e8d01dc
Ensure single-param constructor is not used for terminal positions.
lrozenblyum Jul 31, 2019
c4d3258
Reassurance test for stalemate situation.
lrozenblyum Jul 31, 2019
9a04536
Used historical information about moved side for correct processing.
lrozenblyum Jul 31, 2019
e1c7a5b
Prefer consistency over immutability.
lrozenblyum Jul 31, 2019
18ea1c6
Implemented DenormalizedTest.beSmartALittle.
lrozenblyum Jul 31, 2019
33113de
More precise max estimate for protection evaluator.
lrozenblyum Jul 31, 2019
7b59cfa
Formatting
lrozenblyum Jul 31, 2019
226500c
Symmetry comment
lrozenblyum Jul 31, 2019
1a568a0
Improved Javadoc
lrozenblyum Aug 5, 2019
85c257d
Improved Javadoc, formatting
lrozenblyum Aug 5, 2019
4da86f8
Fixed Javadoc
lrozenblyum Aug 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/com/leokom/chess/engine/InitialPosition.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static int getNotPawnInitialRank( Side side ) {
}

static Position generate( Rules rules ) {
final Position result = new Position( Side.WHITE );
final Position result = new Position( Side.WHITE, null );
result.setRules( rules );

final Set< String > initialRookFiles = new HashSet<>( Arrays.asList( "a", "h" ) );
Expand Down
44 changes: 41 additions & 3 deletions src/main/java/com/leokom/chess/engine/Position.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,16 @@ public class Position implements GameState< Move, Position > {

private Set< Side > hasHRookMoved = new HashSet<>();

//this should be replaced by position history which would be needed for repetition rules
//now it's used for Castling evaluator, it's some extension of historical information
//remove me in https://github.com/lrozenblyum/chess/issues/321
private Set< Side > hasCastlingExecuted = new HashSet<>();

private Side sideToMove;
//technically this historical information should be filled in by .getPrevious().getSideToMove()
//due to mutability of sideToMove AND need to support consistency for test-only method
//setSideToMove, this field is mutable as well
private Side movedSide;

private Result gameResult;
private boolean terminal;
Expand All @@ -92,6 +101,10 @@ void setHasHRookMoved( Side side ) {
this.hasHRookMoved.add( side );
}

void setHasCastlingExecuted( Side side ) {
this.hasCastlingExecuted.add( side );
}

void setEnPassantFile( Character enPassantFile ) { this.enPassantFile = enPassantFile; }

//temporary state in game (which could change)
Expand All @@ -115,11 +128,17 @@ void setHasHRookMoved( Side side ) {
*
* By default en passant file is absent
*
* @param sideToMove side which turn will be now, null for terminal positions
* @param sideToMove side which turn will be now, null is prohibited.
* To init terminal position use another constructor.
*
*/
public Position( Side sideToMove ) {
this( sideToMove, sideToMove.opposite() );
}

public Position( Side sideToMove, Side movedSide ) {
this.sideToMove = sideToMove;
this.movedSide = movedSide;
this.rules = Rules.DEFAULT;
}

Expand Down Expand Up @@ -331,12 +350,17 @@ private Set<String> getSquaresAttackedByRook( String square ) {
return result;
}

public Set<String> getSquaresOccupied() {
//risk for immutability?
return pieces.keySet();
}

public Set<String> getSquaresOccupiedBySide( Side neededSide ) {
return getSquaresOccupiedBySideToStream( neededSide ).collect( toSet() );
}

private Stream<String> getSquaresOccupiedBySideToStream( Side neededSide ) {
return pieces.keySet().stream().filter( square -> this.isOccupiedBy( square, neededSide ) );
return getSquaresOccupied().stream().filter( square -> this.isOccupiedBy( square, neededSide ) );
}

private boolean isKingInCheck( Side side ) {
Expand Down Expand Up @@ -662,14 +686,18 @@ public boolean hasKingMoved( Side side ) {
return hasKingMoved.contains( side );
}

public boolean hasCastlingExecuted( Side side ) {
return hasCastlingExecuted.contains( side );
}

/**
* Copy pieces from current position to the destination
* Copy state (like info if the king has moved)
* @param position destination position
*/
void copyStateTo( Position position ) {
//cloning position
for ( String square : pieces.keySet() ) {
for ( String square : getSquaresOccupied() ) {
//looks safe as both keys and pieces are IMMUTABLE
position.pieces.put( square, pieces.get( square ) );
}
Expand All @@ -679,6 +707,7 @@ void copyStateTo( Position position ) {
position.hasKingMoved = new HashSet<>( this.hasKingMoved );
position.hasARookMoved = new HashSet<>( this.hasARookMoved );
position.hasHRookMoved = new HashSet<>( this.hasHRookMoved );
position.hasCastlingExecuted = new HashSet<>( this.hasCastlingExecuted );

//little overhead but ensuring we really copy the FULL state
position.waitingForAcceptDraw = this.waitingForAcceptDraw;
Expand Down Expand Up @@ -894,8 +923,17 @@ public Side getSideToMove() {
return sideToMove;
}

public Side getMovedSide() {
return movedSide;
}

void setSideToMove( Side sideToMove ) {
setSideToMove( sideToMove, sideToMove.opposite() );
}

void setSideToMove(Side sideToMove, Side lastMovedSide) {
this.sideToMove = sideToMove;
this.movedSide = lastMovedSide;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/leokom/chess/engine/PositionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ public PositionBuilder winningSide( Side side ) {
return this;
}

public PositionBuilder draw() {
public PositionBuilder draw( Side lastMovedSide ) {
this.position.setTerminal( null );
//see the reason of this in PositionBuilder
this.position.setSideToMove( null );
this.position.setSideToMove( null, lastMovedSide );
return this;
}
}
10 changes: 8 additions & 2 deletions src/main/java/com/leokom/chess/engine/PositionGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private void validateStandardMove( Move move ) {
}

private Position createTerminalPosition(Side winningSide, Result gameResult) {
final Position terminalPosition = new Position( null );
final Position terminalPosition = new Position( null, source.getSideToMove() );
source.copyStateTo( terminalPosition );
//TODO: should checkmate move also set this flag?
terminalPosition.setTerminal(winningSide);
Expand Down Expand Up @@ -196,7 +196,13 @@ private Position processKingMove( String squareFrom, String move ) {
newPosition.moveUnconditionally( "a" + rank, "d" + rank );
}

newPosition.setHasKingMoved( this.source.getSide( squareFrom ) );
Side ourSide = this.source.getSide( squareFrom );

if ( isCastlingKingSide || isCastlingQueenSide ) {
newPosition.setHasCastlingExecuted( ourSide );
}

newPosition.setHasKingMoved(ourSide);
return newPosition;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.leokom.chess.player.legal.brain.common;

import com.leokom.chess.engine.Position;
import com.leokom.chess.engine.Side;

@FunctionalInterface
public interface SideEvaluator {
/**
* Evaluate position from point of view of the given side
* @param position position to analyze
* @param side side of interest
* @return a position evaluation
*/
double evaluatePosition( Position position, Side side );
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.leokom.chess.player.legal.brain.denormalized;

import com.google.common.collect.Sets;
import com.leokom.chess.engine.Move;
import com.leokom.chess.engine.Position;
import com.leokom.chess.engine.Side;
import com.leokom.chess.player.legal.brain.common.Evaluator;
import com.leokom.chess.player.legal.brain.internal.common.SymmetricEvaluator;

import java.util.Set;
import java.util.stream.Stream;

/**
* Author: Leonid
Expand All @@ -12,7 +17,41 @@
class AttackEvaluator implements Evaluator {
@Override
public double evaluateMove( Position position, Move move ) {
final Side ourSide = position.getSideToMove();
return AttackIndexCalculator.getAttackIndex( position.move( move ), ourSide );
Position targetPosition = position.move(move);
return new SymmetricEvaluator( new AttackSideEvaluator() ).evaluate( targetPosition );
}

private static class AttackSideEvaluator implements com.leokom.chess.player.legal.brain.common.SideEvaluator {
//TODO: technically, in case of a terminal position, it should return empty result
//REFACTOR: too generic to encapsulate into Position?
private static Set<String> getPiecesAttackedBy( Position position, Side attackerSide ) {
Set<String> defenderSquares = position.getSquaresOccupiedBySide( attackerSide.opposite() );
return Sets.intersection( position.getSquaresAttackedBy( attackerSide ),
defenderSquares );
}

/*
* Backlog for improvements:
* - king has become a main target for attacks
*/
@Override
public double evaluatePosition(Position position, Side side) {
final Set< String > squaresAttacked = getPiecesAttackedBy( position, side );

// sum of piece values
// if a piece is protected - index of piece value is reduced
float result = 0;
for ( String attackedSquare : squaresAttacked ) {
//REFACTOR: probably bad dependency on another brain - extract common utility
int pieceValue = MaterialEvaluator.getValue( position.getPieceType( attackedSquare ) );

final Stream< String > protectors = position.getSquaresAttackingSquare( side.opposite(), attackedSquare );

//+1 to avoid / 0, more protectors is better
result += pieceValue / ( protectors.count() + 1.0 );
}
return result;
}
}

}

This file was deleted.

Loading