From 3084f4d8e8ea2dabf83d6a0ad4d39f6716b871cb Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sat, 12 Jan 2019 22:21:15 +0200 Subject: [PATCH 001/110] Refactoring: generification. The goal of this change is to make search by game tree to evaluate moves independent from our actual implementation classes (Position/Move) thus the algorithm should become universal. Evaluator interface is implemented in multiple places so to reduce amount of changes I extracted a generic parent. Brain interface is implemented just in 3 places so I decided to make it generic in-place. --- Chess.iml | 9 +++ .../com/leokom/chess/engine/GameState.java | 11 ++++ .../leokom/chess/engine/GameTransition.java | 9 +++ .../java/com/leokom/chess/engine/Move.java | 2 +- .../com/leokom/chess/engine/Position.java | 2 +- .../chess/player/legal/LegalPlayer.java | 6 +- .../player/legal/brain/common/Brain.java | 10 ++-- .../player/legal/brain/common/Evaluator.java | 2 +- .../legal/brain/common/GenericEvaluator.java | 8 +++ .../brain/denormalized/DenormalizedBrain.java | 4 +- .../brain/normalized/MasterEvaluator.java | 4 +- .../brain/normalized/NormalizedBrain.java | 34 +++++------ .../legal/brain/simple/SimpleBrain.java | 2 +- .../java/com/leokom/chess/SimulatorIT.java | 3 +- .../chess/engine/GameTransitionImpl.java | 14 +++++ .../brain/normalized/NormalizedBrainTest.java | 57 +++++++++++++++++++ 16 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/leokom/chess/engine/GameState.java create mode 100644 src/main/java/com/leokom/chess/engine/GameTransition.java create mode 100644 src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java create mode 100644 src/test/java/com/leokom/chess/engine/GameTransitionImpl.java create mode 100644 src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java diff --git a/Chess.iml b/Chess.iml index a1d78f435..3103b5f28 100644 --- a/Chess.iml +++ b/Chess.iml @@ -13,6 +13,15 @@ + + + + + + + + + diff --git a/src/main/java/com/leokom/chess/engine/GameState.java b/src/main/java/com/leokom/chess/engine/GameState.java new file mode 100644 index 000000000..34dac4a64 --- /dev/null +++ b/src/main/java/com/leokom/chess/engine/GameState.java @@ -0,0 +1,11 @@ +package com.leokom.chess.engine; + +import java.util.Set; + +/** + * The notion of game state is very generic and can be extracted to something chess-independent + * @param type of transitions + */ +public interface GameState< TransitionType extends GameTransition > { + Set< TransitionType > getMoves(); +} diff --git a/src/main/java/com/leokom/chess/engine/GameTransition.java b/src/main/java/com/leokom/chess/engine/GameTransition.java new file mode 100644 index 000000000..968dc89b7 --- /dev/null +++ b/src/main/java/com/leokom/chess/engine/GameTransition.java @@ -0,0 +1,9 @@ +package com.leokom.chess.engine; + +/** + * The notion of game transition is an attempt to represent the game + * as a state automate. + * This notion is so generic that can be extracted to something chess-independent + */ +public interface GameTransition { +} diff --git a/src/main/java/com/leokom/chess/engine/Move.java b/src/main/java/com/leokom/chess/engine/Move.java index a0a1243ab..607ce1730 100644 --- a/src/main/java/com/leokom/chess/engine/Move.java +++ b/src/main/java/com/leokom/chess/engine/Move.java @@ -13,7 +13,7 @@ */ //REFACTOR: use this class on all applicable layers //where pair of 'squareFrom, to' are used -public final class Move { +public final class Move implements GameTransition { /** * Size of promotion move (e.g. "h1Q") */ diff --git a/src/main/java/com/leokom/chess/engine/Position.java b/src/main/java/com/leokom/chess/engine/Position.java index 650ad610f..78d017135 100644 --- a/src/main/java/com/leokom/chess/engine/Position.java +++ b/src/main/java/com/leokom/chess/engine/Position.java @@ -33,7 +33,7 @@ * Author: Leonid * Date-time: 21.08.12 15:55 */ -public class Position { +public class Position implements GameState< Move > { /** * Chess rules mention moves counter must be calculated * for both players diff --git a/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java b/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java index 26e40e8b5..46f5efc70 100644 --- a/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java +++ b/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java @@ -22,7 +22,7 @@ public class LegalPlayer implements Player { private Player opponent; private Position position = Position.getInitialPosition(); - private final Brain brain; + private final Brain< Position, Move > brain; private boolean recordingMode; private Side ourSide; @@ -34,7 +34,7 @@ public LegalPlayer() { this( new DenormalizedBrain() ); } - public LegalPlayer( Brain brain ) { + public LegalPlayer( Brain< Position, Move > brain ) { this.brain = brain; } @@ -43,7 +43,7 @@ public LegalPlayer( Brain brain ) { * @param brains brains to evaluate moves */ public LegalPlayer( Evaluator brains ) { - this.brain = new NormalizedBrain( brains ); + this.brain = new NormalizedBrain<>(brains); } @Override diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java b/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java index 4126e88a3..f246fc80b 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java @@ -1,7 +1,7 @@ package com.leokom.chess.player.legal.brain.common; -import com.leokom.chess.engine.Move; -import com.leokom.chess.engine.Position; +import com.leokom.chess.engine.GameState; +import com.leokom.chess.engine.GameTransition; import java.util.List; /** @@ -16,7 +16,7 @@ * Author: Leonid * Date-time: 23.08.16 22:53 */ -public interface Brain { +public interface Brain < StateType extends GameState< TransitionType >, TransitionType extends GameTransition> { /** * Finds the best move(s) in the current position. @@ -26,7 +26,7 @@ public interface Brain { * @return best move according to current strategy, absence of moves means: * no moves are legal - we reached a terminal position */ - List< Move > findBestMove( Position position ); + List< TransitionType > findBestMove( StateType position ); /** * Get the best move to execute when it's not our @@ -40,7 +40,7 @@ public interface Brain { to allow evolving interface while not forcing existing implementations to increase complexity */ - default Move findBestMoveForOpponent( Position position ) { + default TransitionType findBestMoveForOpponent( StateType position ) { return null; } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/Evaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/common/Evaluator.java index 0da6126ba..5549da884 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/Evaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/Evaluator.java @@ -11,7 +11,7 @@ * Date-time: 14.07.14 22:57 */ @FunctionalInterface -public interface Evaluator { +public interface Evaluator extends GenericEvaluator< Position, Move > { /** * Get 'rating' of a move diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java new file mode 100644 index 000000000..a394d0591 --- /dev/null +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java @@ -0,0 +1,8 @@ +package com.leokom.chess.player.legal.brain.common; + +import com.leokom.chess.engine.GameState; +import com.leokom.chess.engine.GameTransition; + +public interface GenericEvaluator< StateType extends GameState< TransitionType >, TransitionType extends GameTransition > { + double evaluateMove(StateType position, TransitionType move ); +} diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index b3304d4e5..61909ab66 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -21,7 +21,7 @@ * Author: Leonid * Date-time: 27.08.16 21:51 */ -public class DenormalizedBrain implements Brain { +public class DenormalizedBrain implements Brain< Position, Move > { private static final Logger LOG = LogManager.getLogger(); private static final double DEFAULT_FOR_EQUAL_NOT_IN_RANGE = 0.5; @@ -44,7 +44,7 @@ public List< Move > findBestMove( Position position ) { Table weightedTable = generateWithWeights( normalizedTable ); logTable( weightedTable, "WEIGHTED" ); - return new NormalizedBrain( getEvaluator( weightedTable ) ).findBestMove( position ); + return new NormalizedBrain<>( getEvaluator( weightedTable ) ).findBestMove( position ); } private Evaluator getEvaluator(Table weightedTable) { diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 7c5f9a3c5..bf6fbdde5 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -13,7 +13,7 @@ /** * Central brain of a move ('brains') */ -class MasterEvaluator implements Evaluator { +public class MasterEvaluator implements Evaluator { private static final Logger LOG = LogManager.getLogger(); //we don't need to know that we can execute other moves @@ -24,7 +24,7 @@ class MasterEvaluator implements Evaluator { private final Map evaluatorWeights; - MasterEvaluator() { + public MasterEvaluator() { this( EvaluatorWeights.getStandardWeights() ); } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 488a36421..ab43ee36b 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -1,28 +1,30 @@ package com.leokom.chess.player.legal.brain.normalized; +import com.leokom.chess.engine.GameState; +import com.leokom.chess.engine.GameTransition; import com.leokom.chess.engine.Move; -import com.leokom.chess.engine.Position; import com.leokom.chess.player.legal.brain.common.Brain; -import com.leokom.chess.player.legal.brain.common.Evaluator; +import com.leokom.chess.player.legal.brain.common.GenericEvaluator; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** - * Initial decision maker based on MasterEvaluator. - * independent evaluation of each move is delegated to MasterEvaluator. - * You can inject any custom brains instead of MasterEvaluator via constructor. + * Initial decision maker. + * + * Historically it was based on MasterEvaluator. + * Now it has become generic (actually even not depending on chess-related notions)/ + * You can inject any custom brains via constructor. * * Author: Leonid * Date-time: 23.08.16 22:54 */ -public class NormalizedBrain implements Brain { - private Evaluator brains; - - public NormalizedBrain() { - this( new MasterEvaluator() ); - } +public class NormalizedBrain < StateType extends GameState< TransitionType >, TransitionType extends GameTransition> implements Brain< StateType, TransitionType > { + private final GenericEvaluator< StateType, TransitionType > brains; - public NormalizedBrain(Evaluator brains ) { + public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { this.brains = brains; } @@ -45,8 +47,8 @@ public NormalizedBrain(Evaluator brains ) { * */ @Override - public List findBestMove(Position position ) { - Map< Move, Double > moveRatings = new HashMap<>(); + public List findBestMove(StateType position ) { + Map< TransitionType, Double > moveRatings = new HashMap<>(); //filtering Draw offers till #161 is solved //this looks safe since Offer draw cannot be a single legal move in a position. @@ -59,7 +61,7 @@ public List findBestMove(Position position ) { return getMoveWithMaxRating( moveRatings ); } - private List getMoveWithMaxRating( Map< Move, Double > moveValues ) { + private List getMoveWithMaxRating( Map< TransitionType, Double > moveValues ) { return moveValues.entrySet().stream() .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java index 004938dd7..b8a894c06 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java @@ -23,7 +23,7 @@ * Author: Leonid * Date-time: 15.04.13 22:26 */ -public class SimpleBrain implements Brain { +public class SimpleBrain implements Brain< Position, Move > { @Override public List< Move > findBestMove( Position position ) { if ( position.isTerminal() ) { diff --git a/src/test/java/com/leokom/chess/SimulatorIT.java b/src/test/java/com/leokom/chess/SimulatorIT.java index 10729d83f..5923afa2b 100644 --- a/src/test/java/com/leokom/chess/SimulatorIT.java +++ b/src/test/java/com/leokom/chess/SimulatorIT.java @@ -8,6 +8,7 @@ import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.common.EvaluatorType; import com.leokom.chess.player.legal.brain.denormalized.DenormalizedBrain; +import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; import com.leokom.chess.player.legal.brain.normalized.MasterEvaluatorBuilder; import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; import org.junit.Ignore; @@ -180,7 +181,7 @@ public void legalPlayerEqualProbableDraw() { @Test public void newBrainShouldBeBetter() { final LegalPlayer withNewSkills = new LegalPlayer( new DenormalizedBrain() ); - final LegalPlayer classicPlayer = new LegalPlayer( new NormalizedBrain() ); + final LegalPlayer classicPlayer = new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator() ) ); final SimulatorStatistics statistics = new Simulator( withNewSkills, classicPlayer ) .gamePairs( 5 ) .run(); diff --git a/src/test/java/com/leokom/chess/engine/GameTransitionImpl.java b/src/test/java/com/leokom/chess/engine/GameTransitionImpl.java new file mode 100644 index 000000000..e62ee9ab3 --- /dev/null +++ b/src/test/java/com/leokom/chess/engine/GameTransitionImpl.java @@ -0,0 +1,14 @@ +package com.leokom.chess.engine; + +//fake implementation for test purposes +public class GameTransitionImpl implements com.leokom.chess.engine.GameTransition { + private final long id; + + public GameTransitionImpl(long id ) { + this.id = id; + } + + public long getId() { + return this.id; + } +} diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java new file mode 100644 index 000000000..e486b5e30 --- /dev/null +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -0,0 +1,57 @@ +package com.leokom.chess.player.legal.brain.normalized; + +import com.leokom.chess.engine.GameState; +import com.leokom.chess.engine.GameTransition; +import com.leokom.chess.engine.GameTransitionImpl; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +public class NormalizedBrainTest { + //these abstract classes are introduced to provide explicit generics and avoid type cast for Mockito + //as per https://stackoverflow.com/a/23349216/1429367 + abstract class GenericState implements GameState< GameTransition > { + } + + abstract class GenericImplState implements GameState { + } + + @Test + public void noMovesNoBestMove() { + GenericState gameState = Mockito.mock( GenericState.class ); + when( gameState.getMoves() ).thenReturn( new HashSet<>()); + + List result = new NormalizedBrain<>((state, transition) -> 0).findBestMove(gameState); + assertTrue( result.isEmpty() ); + } + + @Test + public void singlePossibleMoveReturned() { + GenericImplState gameState = Mockito.mock( GenericImplState.class ); + int moveId = 12345; + when( gameState.getMoves() ).thenReturn( new HashSet<>(Collections.singletonList(new GameTransitionImpl(moveId)))); + + List result = new NormalizedBrain< GameState< GameTransitionImpl >, GameTransitionImpl >((state, transition) -> 0).findBestMove(gameState); + assertEquals( 1, result.size() ); + assertEquals( moveId, result.get(0).getId() ); + } + + @Test + public void betterMoveFound() { + GenericImplState gameState = Mockito.mock( GenericImplState.class ); + when( gameState.getMoves() ).thenReturn( new HashSet<>(Arrays.asList( + new GameTransitionImpl(12), + new GameTransitionImpl( 20 ) + ))); + + List result = new NormalizedBrain< GameState< GameTransitionImpl >, GameTransitionImpl >( + (state, transition) -> transition.getId() // just a simple evaluation - let's say bigger id is better + ).findBestMove(gameState); + assertEquals( 1, result.size() ); + assertEquals( 20, result.get(0).getId() ); + } +} \ No newline at end of file From b9d681e9e7e09fb628bccf7192c6fd42101f5e71 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sat, 12 Jan 2019 22:49:43 +0200 Subject: [PATCH 002/110] Refactoring: replaced mockito by fake implementation --- .../brain/normalized/NormalizedBrainTest.java | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index e486b5e30..6f297fd0b 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -1,54 +1,39 @@ package com.leokom.chess.player.legal.brain.normalized; -import com.leokom.chess.engine.GameState; +import com.leokom.chess.engine.GameStateImpl; import com.leokom.chess.engine.GameTransition; import com.leokom.chess.engine.GameTransitionImpl; import org.junit.Test; -import org.mockito.Mockito; import java.util.*; import static org.junit.Assert.*; -import static org.mockito.Mockito.when; public class NormalizedBrainTest { - //these abstract classes are introduced to provide explicit generics and avoid type cast for Mockito - //as per https://stackoverflow.com/a/23349216/1429367 - abstract class GenericState implements GameState< GameTransition > { - } - - abstract class GenericImplState implements GameState { - } - @Test public void noMovesNoBestMove() { - GenericState gameState = Mockito.mock( GenericState.class ); - when( gameState.getMoves() ).thenReturn( new HashSet<>()); + GameStateImpl gameState = new GameStateImpl<>(); - List result = new NormalizedBrain<>((state, transition) -> 0).findBestMove(gameState); + List result = new NormalizedBrain< GameStateImpl< GameTransition >, GameTransition >((state, transition) -> 0).findBestMove(gameState); assertTrue( result.isEmpty() ); } @Test public void singlePossibleMoveReturned() { - GenericImplState gameState = Mockito.mock( GenericImplState.class ); int moveId = 12345; - when( gameState.getMoves() ).thenReturn( new HashSet<>(Collections.singletonList(new GameTransitionImpl(moveId)))); + GameStateImpl gameState = new GameStateImpl<>( new GameTransitionImpl(moveId) ); - List result = new NormalizedBrain< GameState< GameTransitionImpl >, GameTransitionImpl >((state, transition) -> 0).findBestMove(gameState); + List result = new NormalizedBrain, GameTransitionImpl>((state, transition) -> 0).findBestMove(gameState); assertEquals( 1, result.size() ); assertEquals( moveId, result.get(0).getId() ); } @Test public void betterMoveFound() { - GenericImplState gameState = Mockito.mock( GenericImplState.class ); - when( gameState.getMoves() ).thenReturn( new HashSet<>(Arrays.asList( - new GameTransitionImpl(12), - new GameTransitionImpl( 20 ) - ))); + GameStateImpl gameState = new GameStateImpl<>( new GameTransitionImpl(12), + new GameTransitionImpl( 20 ) ); - List result = new NormalizedBrain< GameState< GameTransitionImpl >, GameTransitionImpl >( + List result = new NormalizedBrain< GameStateImpl< GameTransitionImpl>, GameTransitionImpl >( (state, transition) -> transition.getId() // just a simple evaluation - let's say bigger id is better ).findBestMove(gameState); assertEquals( 1, result.size() ); From 6e7e5518fb4c88f2a835b12d0732f28bdb9c6e51 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sat, 12 Jan 2019 23:04:58 +0200 Subject: [PATCH 003/110] Game state can produce new game state. Reduced generics amount in favor of readability --- .../com/leokom/chess/engine/GameState.java | 2 ++ .../com/leokom/chess/engine/Position.java | 4 ++- .../leokom/chess/engine/GameStateImpl.java | 27 +++++++++++++++++++ .../brain/normalized/NormalizedBrainTest.java | 13 +++++---- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/leokom/chess/engine/GameStateImpl.java diff --git a/src/main/java/com/leokom/chess/engine/GameState.java b/src/main/java/com/leokom/chess/engine/GameState.java index 34dac4a64..3b4090b99 100644 --- a/src/main/java/com/leokom/chess/engine/GameState.java +++ b/src/main/java/com/leokom/chess/engine/GameState.java @@ -7,5 +7,7 @@ * @param type of transitions */ public interface GameState< TransitionType extends GameTransition > { + GameState< TransitionType > move(TransitionType move); + Set< TransitionType > getMoves(); } diff --git a/src/main/java/com/leokom/chess/engine/Position.java b/src/main/java/com/leokom/chess/engine/Position.java index 78d017135..c52e91fd7 100644 --- a/src/main/java/com/leokom/chess/engine/Position.java +++ b/src/main/java/com/leokom/chess/engine/Position.java @@ -641,7 +641,8 @@ public Position move( String squareFrom, String move ) { * @param move act of movement * @return new position, which is received from current by making 1 move */ - public Position move( Move move ) { + @Override + public Position move(Move move) { return new PositionGenerator( this ).generate( move ); } @@ -803,6 +804,7 @@ void moveUnconditionally( String from, String to ) { * for #getSideToMove() * @return set of possible legal moves */ + @Override public Set< Move > getMoves() { if ( terminal ) { return new HashSet<>(); diff --git a/src/test/java/com/leokom/chess/engine/GameStateImpl.java b/src/test/java/com/leokom/chess/engine/GameStateImpl.java new file mode 100644 index 000000000..77202e806 --- /dev/null +++ b/src/test/java/com/leokom/chess/engine/GameStateImpl.java @@ -0,0 +1,27 @@ +package com.leokom.chess.engine; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +//fake implementation for test purposes +public class GameStateImpl implements GameState< GameTransitionImpl > { + private final Set transitions; + + public GameStateImpl(GameTransitionImpl ... transitions ) { + this( new HashSet<>(Arrays.asList( transitions ))); + } + + private GameStateImpl(Set transitions) { + this.transitions = transitions; + } + + @Override + public GameState move(GameTransitionImpl move) { + return null; + } + + public Set getMoves() { + return transitions; + } +} diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 6f297fd0b..7949881d4 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -1,7 +1,6 @@ package com.leokom.chess.player.legal.brain.normalized; import com.leokom.chess.engine.GameStateImpl; -import com.leokom.chess.engine.GameTransition; import com.leokom.chess.engine.GameTransitionImpl; import org.junit.Test; @@ -12,28 +11,28 @@ public class NormalizedBrainTest { @Test public void noMovesNoBestMove() { - GameStateImpl gameState = new GameStateImpl<>(); + GameStateImpl gameState = new GameStateImpl(); - List result = new NormalizedBrain< GameStateImpl< GameTransition >, GameTransition >((state, transition) -> 0).findBestMove(gameState); + List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >((state, transition) -> 0).findBestMove(gameState); assertTrue( result.isEmpty() ); } @Test public void singlePossibleMoveReturned() { int moveId = 12345; - GameStateImpl gameState = new GameStateImpl<>( new GameTransitionImpl(moveId) ); + GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(moveId) ); - List result = new NormalizedBrain, GameTransitionImpl>((state, transition) -> 0).findBestMove(gameState); + List result = new NormalizedBrain((state, transition) -> 0).findBestMove(gameState); assertEquals( 1, result.size() ); assertEquals( moveId, result.get(0).getId() ); } @Test public void betterMoveFound() { - GameStateImpl gameState = new GameStateImpl<>( new GameTransitionImpl(12), + GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(12), new GameTransitionImpl( 20 ) ); - List result = new NormalizedBrain< GameStateImpl< GameTransitionImpl>, GameTransitionImpl >( + List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( (state, transition) -> transition.getId() // just a simple evaluation - let's say bigger id is better ).findBestMove(gameState); assertEquals( 1, result.size() ); From 4e836a7c9e7f97ac46aeb614883fbe624868bcd3 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 18:08:27 +0200 Subject: [PATCH 004/110] Refactoring: game state in test infrastructure preparation for real work. --- .../leokom/chess/engine/GameStateImpl.java | 28 +++++++++++++------ .../brain/normalized/NormalizedBrainTest.java | 6 ++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/leokom/chess/engine/GameStateImpl.java b/src/test/java/com/leokom/chess/engine/GameStateImpl.java index 77202e806..f0e594f30 100644 --- a/src/test/java/com/leokom/chess/engine/GameStateImpl.java +++ b/src/test/java/com/leokom/chess/engine/GameStateImpl.java @@ -1,27 +1,37 @@ package com.leokom.chess.engine; -import java.util.Arrays; -import java.util.HashSet; +import com.google.common.collect.ImmutableMap; + +import java.util.Map; import java.util.Set; //fake implementation for test purposes public class GameStateImpl implements GameState< GameTransitionImpl > { - private final Set transitions; + private final Map tree; + + //a few constructors for simplicity + public GameStateImpl() { + this(ImmutableMap.of()); + } + + public GameStateImpl(GameTransitionImpl gameTransition, GameStateImpl gameState) { + this( ImmutableMap.of( gameTransition, gameState ) ); + } - public GameStateImpl(GameTransitionImpl ... transitions ) { - this( new HashSet<>(Arrays.asList( transitions ))); + public GameStateImpl(GameTransitionImpl gameTransition, GameStateImpl gameState, GameTransitionImpl gameTransition2, GameStateImpl gameState2) { + this( ImmutableMap.of( gameTransition, gameState, gameTransition2, gameState2 ) ); } - private GameStateImpl(Set transitions) { - this.transitions = transitions; + private GameStateImpl(Map tree ) { + this.tree = tree; } @Override public GameState move(GameTransitionImpl move) { - return null; + return tree.get(move); } public Set getMoves() { - return transitions; + return tree.keySet(); } } diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 7949881d4..71e218786 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -20,7 +20,7 @@ public void noMovesNoBestMove() { @Test public void singlePossibleMoveReturned() { int moveId = 12345; - GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(moveId) ); + GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(moveId), new GameStateImpl() ); List result = new NormalizedBrain((state, transition) -> 0).findBestMove(gameState); assertEquals( 1, result.size() ); @@ -29,8 +29,8 @@ public void singlePossibleMoveReturned() { @Test public void betterMoveFound() { - GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(12), - new GameTransitionImpl( 20 ) ); + GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(12), new GameStateImpl(), + new GameTransitionImpl( 20 ), new GameStateImpl() ); List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( (state, transition) -> transition.getId() // just a simple evaluation - let's say bigger id is better From c51c33f1c89ee63de3de05d325b2e07d02bae051 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 18:11:05 +0200 Subject: [PATCH 005/110] Single ply logic of thinking mustn't check the second ply --- .../brain/normalized/NormalizedBrainTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 71e218786..7ad963662 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -38,4 +38,18 @@ public void betterMoveFound() { assertEquals( 1, result.size() ); assertEquals( 20, result.get(0).getId() ); } + + //we must not look to the 2'nd ply if we are limited by the 1'st one + @Test + public void singlePlyThinkingIsLimited() { + GameStateImpl gameState = new GameStateImpl( + new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 100 ), new GameStateImpl() ), + new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl() ) ); // too low result on the 2'd ply + + List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( + (state, transition) -> transition.getId() // just a simple evaluation - let's say bigger id is better + ).findBestMove(gameState); + assertEquals( 1, result.size() ); + assertEquals( 20, result.get(0).getId() ); + } } \ No newline at end of file From 75c16b60ce1587aa8f716ead38cf13aee0312d28 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 18:13:25 +0200 Subject: [PATCH 006/110] Second ply thinking: red test --- .../legal/brain/normalized/NormalizedBrain.java | 6 ++++++ .../brain/normalized/NormalizedBrainTest.java | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index ab43ee36b..941216958 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -23,9 +23,15 @@ */ public class NormalizedBrain < StateType extends GameState< TransitionType >, TransitionType extends GameTransition> implements Brain< StateType, TransitionType > { private final GenericEvaluator< StateType, TransitionType > brains; + private final int pliesDepth; public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { + this( brains, 1 ); + } + + public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, int pliesDepth ) { this.brains = brains; + this.pliesDepth = pliesDepth; } /** diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 7ad963662..4b0c5f41a 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -52,4 +52,19 @@ public void singlePlyThinkingIsLimited() { assertEquals( 1, result.size() ); assertEquals( 20, result.get(0).getId() ); } + + //we must look to the 2'nd ply and detect a really better move + @Test + public void secondPlyThinkingMustSuggestBetterMove() { + GameStateImpl gameState = new GameStateImpl( + new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 100 ), new GameStateImpl() ), + new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl() ) ); + + List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( + (state, transition) -> transition.getId(), // just a simple evaluation - let's say bigger id is better + 2 + ).findBestMove(gameState); + assertEquals( 1, result.size() ); + assertEquals( 12, result.get(0).getId() ); + } } \ No newline at end of file From bd3a584b8d23e092bd9b5a7dd5f20f5482778dc2 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 19:10:09 +0200 Subject: [PATCH 007/110] Simple logic for the 2'nd ply analyzing. --- .../brain/normalized/NormalizedBrain.java | 38 ++++++++++++++----- .../brain/normalized/NormalizedBrainTest.java | 8 ++-- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 941216958..ca0727d04 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -29,7 +29,16 @@ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { this( brains, 1 ); } - public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, int pliesDepth ) { + /** + * We assume that brains always evaluate the move from the side to move the next ply + * It was a logical assumption when we developed a 1-ply engine. + * It can still be kept. + * The alternative could be: stable evaluator that returns positive/negative result depending on color of the side to move + * + * @param brains evauator + * @param pliesDepth depth to think + */ + NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, int pliesDepth ) { this.brains = brains; this.pliesDepth = pliesDepth; } @@ -54,17 +63,28 @@ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, in */ @Override public List findBestMove(StateType position ) { - Map< TransitionType, Double > moveRatings = new HashMap<>(); + Map moveRatings = new HashMap<>(); - //filtering Draw offers till #161 is solved - //this looks safe since Offer draw cannot be a single legal move in a position. + if ( pliesDepth == 1 ) { + //filtering Draw offers till #161 is solved + //this looks safe since Offer draw cannot be a single legal move in a position. - //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches - position.getMoves().stream().filter( move -> move != Move.OFFER_DRAW ).forEach( move -> - moveRatings.put( move, brains.evaluateMove( position, move ) ) - ); + //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches + position.getMoves().stream().filter(move -> move != Move.OFFER_DRAW).forEach(move -> + moveRatings.put(move, brains.evaluateMove(position, move)) + ); + } + else { //just 2 is supported now + position.getMoves().forEach( move -> { + GameState target = position.move( move ); + TransitionType secondLevelFirstMove = target.getMoves().iterator().next(); + //negating because bigger for the opponents means worse for the current player + //TODO: fix the casting + moveRatings.put( move, - brains.evaluateMove((StateType) target, secondLevelFirstMove ) ); + } ); + } - return getMoveWithMaxRating( moveRatings ); + return getMoveWithMaxRating(moveRatings); } private List getMoveWithMaxRating( Map< TransitionType, Double > moveValues ) { diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 4b0c5f41a..29d67b8d6 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -43,8 +43,8 @@ public void betterMoveFound() { @Test public void singlePlyThinkingIsLimited() { GameStateImpl gameState = new GameStateImpl( - new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 100 ), new GameStateImpl() ), - new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl() ) ); // too low result on the 2'd ply + new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl() ), + new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 100 ), new GameStateImpl() ) ); // bigger means better for the opponent List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( (state, transition) -> transition.getId() // just a simple evaluation - let's say bigger id is better @@ -57,8 +57,8 @@ public void singlePlyThinkingIsLimited() { @Test public void secondPlyThinkingMustSuggestBetterMove() { GameStateImpl gameState = new GameStateImpl( - new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 100 ), new GameStateImpl() ), - new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl() ) ); + new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl() ), + new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 100 ), new GameStateImpl() ) ); List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( (state, transition) -> transition.getId(), // just a simple evaluation - let's say bigger id is better From 04811b22ebd2c97bf3828d9379d10888376e64e9 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 19:13:30 +0200 Subject: [PATCH 008/110] Test that will enforce searching for the best move --- .../brain/normalized/NormalizedBrainTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 29d67b8d6..e74b75f72 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -67,4 +67,20 @@ public void secondPlyThinkingMustSuggestBetterMove() { assertEquals( 1, result.size() ); assertEquals( 12, result.get(0).getId() ); } + + @Test + public void ifOpponentCanSelectCoolMoveDetectThat() { + GameStateImpl gameState = new GameStateImpl( + //here the opponent can execute an average move + new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 50 ), new GameStateImpl() ), + //here he can execute a cool and a bad move. Thinking about him in positive way - that he'll select the best one + new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl(), new GameTransitionImpl( 100 ), new GameStateImpl() ) ); + + List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( + (state, transition) -> transition.getId(), // just a simple evaluation - let's say bigger id is better + 2 + ).findBestMove(gameState); + assertEquals( 1, result.size() ); + assertEquals( 12, result.get(0).getId() ); + } } \ No newline at end of file From 6fb57803f6588d8406e906f6e92d0ac23d122fc9 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 19:16:31 +0200 Subject: [PATCH 009/110] Analyzing the 2'nd level via recursion to the 1'st level --- .../player/legal/brain/normalized/NormalizedBrain.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index ca0727d04..5fa3cd688 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -75,12 +75,16 @@ public List findBestMove(StateType position ) { ); } else { //just 2 is supported now + position.getMoves().forEach( move -> { GameState target = position.move( move ); - TransitionType secondLevelFirstMove = target.getMoves().iterator().next(); + List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove((StateType) target); + //negating because bigger for the opponents means worse for the current player //TODO: fix the casting - moveRatings.put( move, - brains.evaluateMove((StateType) target, secondLevelFirstMove ) ); + //TODO: what if empty + //TODO: what if > 1 + moveRatings.put( move, - brains.evaluateMove((StateType) target, bestMove.get(0) ) ); } ); } From e82c269a11c5c8337fb79e0f61b1af2f9a421ed8 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 22:14:03 +0200 Subject: [PATCH 010/110] Fixed need of casting in NormalizedBrain. It required introducing a complex idea of recursive generics. --- src/main/java/com/leokom/chess/engine/GameState.java | 10 ++++++++-- src/main/java/com/leokom/chess/engine/Position.java | 2 +- .../leokom/chess/player/legal/brain/common/Brain.java | 2 +- .../player/legal/brain/common/GenericEvaluator.java | 2 +- .../player/legal/brain/normalized/NormalizedBrain.java | 9 ++++----- .../java/com/leokom/chess/engine/GameStateImpl.java | 4 ++-- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/leokom/chess/engine/GameState.java b/src/main/java/com/leokom/chess/engine/GameState.java index 3b4090b99..3ade5fb66 100644 --- a/src/main/java/com/leokom/chess/engine/GameState.java +++ b/src/main/java/com/leokom/chess/engine/GameState.java @@ -5,9 +5,15 @@ /** * The notion of game state is very generic and can be extracted to something chess-independent * @param type of transitions + * @param current type */ -public interface GameState< TransitionType extends GameTransition > { - GameState< TransitionType > move(TransitionType move); +/* + Rather complex recursive generic to THIS class is introduced in order to support return of exactly + our class in the move method. + Inspired by https://www.sitepoint.com/self-types-with-javas-generics/ + */ +public interface GameState< TransitionType extends GameTransition, THIS extends GameState< TransitionType, THIS > > { + THIS move(TransitionType move); Set< TransitionType > getMoves(); } diff --git a/src/main/java/com/leokom/chess/engine/Position.java b/src/main/java/com/leokom/chess/engine/Position.java index c52e91fd7..4c286e914 100644 --- a/src/main/java/com/leokom/chess/engine/Position.java +++ b/src/main/java/com/leokom/chess/engine/Position.java @@ -33,7 +33,7 @@ * Author: Leonid * Date-time: 21.08.12 15:55 */ -public class Position implements GameState< Move > { +public class Position implements GameState< Move, Position > { /** * Chess rules mention moves counter must be calculated * for both players diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java b/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java index f246fc80b..fe6d4d52f 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java @@ -16,7 +16,7 @@ * Author: Leonid * Date-time: 23.08.16 22:53 */ -public interface Brain < StateType extends GameState< TransitionType >, TransitionType extends GameTransition> { +public interface Brain < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> { /** * Finds the best move(s) in the current position. diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java index a394d0591..003876d30 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java @@ -3,6 +3,6 @@ import com.leokom.chess.engine.GameState; import com.leokom.chess.engine.GameTransition; -public interface GenericEvaluator< StateType extends GameState< TransitionType >, TransitionType extends GameTransition > { +public interface GenericEvaluator< StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition > { double evaluateMove(StateType position, TransitionType move ); } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 5fa3cd688..d680f70cf 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -21,7 +21,7 @@ * Author: Leonid * Date-time: 23.08.16 22:54 */ -public class NormalizedBrain < StateType extends GameState< TransitionType >, TransitionType extends GameTransition> implements Brain< StateType, TransitionType > { +public class NormalizedBrain < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> implements Brain< StateType, TransitionType > { private final GenericEvaluator< StateType, TransitionType > brains; private final int pliesDepth; @@ -77,14 +77,13 @@ public List findBestMove(StateType position ) { else { //just 2 is supported now position.getMoves().forEach( move -> { - GameState target = position.move( move ); - List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove((StateType) target); + StateType target = position.move( move ); + List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); //negating because bigger for the opponents means worse for the current player - //TODO: fix the casting //TODO: what if empty //TODO: what if > 1 - moveRatings.put( move, - brains.evaluateMove((StateType) target, bestMove.get(0) ) ); + moveRatings.put( move, - brains.evaluateMove(target, bestMove.get(0) ) ); } ); } diff --git a/src/test/java/com/leokom/chess/engine/GameStateImpl.java b/src/test/java/com/leokom/chess/engine/GameStateImpl.java index f0e594f30..4f0bb95a9 100644 --- a/src/test/java/com/leokom/chess/engine/GameStateImpl.java +++ b/src/test/java/com/leokom/chess/engine/GameStateImpl.java @@ -6,7 +6,7 @@ import java.util.Set; //fake implementation for test purposes -public class GameStateImpl implements GameState< GameTransitionImpl > { +public class GameStateImpl implements GameState< GameTransitionImpl, GameStateImpl > { private final Map tree; //a few constructors for simplicity @@ -27,7 +27,7 @@ private GameStateImpl(Map tree ) { } @Override - public GameState move(GameTransitionImpl move) { + public GameStateImpl move(GameTransitionImpl move) { return tree.get(move); } From aa4e912dd94c3cb2f410ac4f9da596f726ec1191 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 22:39:06 +0200 Subject: [PATCH 011/110] IT test about logic expectations for the deeper thinker. Shows crash condition --- src/main/java/com/leokom/chess/PlayerFactory.java | 6 +++++- .../legal/brain/normalized/NormalizedBrain.java | 2 +- src/test/java/com/leokom/chess/SimulatorIT.java | 12 ++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 96b6c6681..b7124b1b2 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -4,6 +4,8 @@ import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; import com.leokom.chess.player.legal.LegalPlayer; +import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; +import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; import com.leokom.chess.player.legal.brain.simple.SimpleBrain; import com.leokom.chess.player.winboard.WinboardPlayer; import org.apache.logging.log4j.LogManager; @@ -73,7 +75,9 @@ private static PlayerSelection selectPlayer( Side side, String engineName ) { } public enum PlayerSelection { - LEGAL( LegalPlayer::new ), + LEGAL(() -> { + return new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), 2)); + }), SIMPLE( () -> new LegalPlayer( new SimpleBrain() ) ), WINBOARD( WinboardPlayer::create ); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index d680f70cf..0f25f13f1 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -38,7 +38,7 @@ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { * @param brains evauator * @param pliesDepth depth to think */ - NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, int pliesDepth ) { + public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, int pliesDepth ) { this.brains = brains; this.pliesDepth = pliesDepth; } diff --git a/src/test/java/com/leokom/chess/SimulatorIT.java b/src/test/java/com/leokom/chess/SimulatorIT.java index 5923afa2b..6740c6800 100644 --- a/src/test/java/com/leokom/chess/SimulatorIT.java +++ b/src/test/java/com/leokom/chess/SimulatorIT.java @@ -189,4 +189,16 @@ public void newBrainShouldBeBetter() { assertTrue( statistics + " should prove advantage of the first player", statistics.getFirstWins() > statistics.getSecondWins() ); } + + @Test + public void normalizedPlayerWithDepth2IsBetterThanDepth1() { + final LegalPlayer deeperThinker = new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), 2 ) ); + final LegalPlayer classicPlayer = new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), 1 ) ); + final SimulatorStatistics statistics = new Simulator( deeperThinker, classicPlayer ) + .gamePairs( 5 ) + .run(); + + assertTrue( statistics + " should prove advantage of the first player", + statistics.getFirstWins() > statistics.getSecondWins() ); + } } \ No newline at end of file From 9e137a10021250e347e1631a62640b372fa7e3c9 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 22:41:47 +0200 Subject: [PATCH 012/110] Old implementation restored --- src/main/java/com/leokom/chess/PlayerFactory.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index b7124b1b2..96b6c6681 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -4,8 +4,6 @@ import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; import com.leokom.chess.player.legal.LegalPlayer; -import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; -import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; import com.leokom.chess.player.legal.brain.simple.SimpleBrain; import com.leokom.chess.player.winboard.WinboardPlayer; import org.apache.logging.log4j.LogManager; @@ -75,9 +73,7 @@ private static PlayerSelection selectPlayer( Side side, String engineName ) { } public enum PlayerSelection { - LEGAL(() -> { - return new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), 2)); - }), + LEGAL( LegalPlayer::new ), SIMPLE( () -> new LegalPlayer( new SimpleBrain() ) ), WINBOARD( WinboardPlayer::create ); From 50b8d521c6cb6b41ab72327441ed4705b10a63d4 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 13 Jan 2019 22:49:28 +0200 Subject: [PATCH 013/110] Red test for handling terminal cases (integrational test) --- .../NormalizedBrainPositionMoveTest.java | 21 +++++++++++++++++++ .../brain/normalized/NormalizedBrainTest.java | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java new file mode 100644 index 000000000..c6b6e7a4c --- /dev/null +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java @@ -0,0 +1,21 @@ +package com.leokom.chess.player.legal.brain.normalized; + +import com.leokom.chess.engine.Move; +import com.leokom.chess.engine.Position; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertFalse; + +//this test is based on Chess notions (in contrary to NormalizedBrainTest) +//so it can be considered being more integrations with Position, Move +public class NormalizedBrainPositionMoveTest { + + @Test + public void integrationWithPosition() { + List bestMove = new NormalizedBrain(((position, move) -> 0), 2) + .findBestMove(Position.getInitialPosition()); + assertFalse( bestMove.isEmpty() ); + } +} diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index e74b75f72..2b516ec5c 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -2,6 +2,8 @@ import com.leokom.chess.engine.GameStateImpl; import com.leokom.chess.engine.GameTransitionImpl; +import com.leokom.chess.engine.Move; +import com.leokom.chess.engine.Position; import org.junit.Test; import java.util.*; From a9cd3ae439da1163688b8f96b0d7cf0b3349a6ca Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 14 Jan 2019 22:01:49 +0200 Subject: [PATCH 014/110] Fixed crash when 1'st level position is terminal and we think for 2 levels --- .../brain/normalized/NormalizedBrain.java | 10 ++++++---- .../brain/normalized/NormalizedBrainTest.java | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 0f25f13f1..4fd94e3e6 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -80,10 +80,12 @@ public List findBestMove(StateType position ) { StateType target = position.move( move ); List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); - //negating because bigger for the opponents means worse for the current player - //TODO: what if empty - //TODO: what if > 1 - moveRatings.put( move, - brains.evaluateMove(target, bestMove.get(0) ) ); + //can be empty in case of terminal position + if ( ! bestMove.isEmpty() ) { + //negating because bigger for the opponents means worse for the current player + //TODO: what if > 1 + moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); + } } ); } diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 2b516ec5c..93f28b66f 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -2,13 +2,12 @@ import com.leokom.chess.engine.GameStateImpl; import com.leokom.chess.engine.GameTransitionImpl; -import com.leokom.chess.engine.Move; -import com.leokom.chess.engine.Position; import org.junit.Test; -import java.util.*; +import java.util.List; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class NormalizedBrainTest { @Test @@ -85,4 +84,16 @@ public void ifOpponentCanSelectCoolMoveDetectThat() { assertEquals( 1, result.size() ); assertEquals( 12, result.get(0).getId() ); } + + @Test + public void secondPlyThinkingNoCrashOnTerminalPosition() { + GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(25 ), new GameStateImpl() //terminal + ); + + new NormalizedBrain< GameStateImpl, GameTransitionImpl >( + (state, transition) -> transition.getId(), + 2 + ).findBestMove(gameState); + } + } \ No newline at end of file From 1859369ce62821e2f86a636ddd40ecc535f65a57 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 14 Jan 2019 22:06:59 +0200 Subject: [PATCH 015/110] Refactoring: generics --- .../player/legal/brain/denormalized/DenormalizedBrainTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java index e872bc042..c94750e4a 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java @@ -15,7 +15,7 @@ * Date-time: 27.08.16 21:58 */ public class DenormalizedBrainTest { - private Brain brain; + private Brain< Position, Move > brain; @Before public void prepare() { From ea26d80962e537a4474b92168367ead4c6c6ec0b Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 14 Jan 2019 22:14:00 +0200 Subject: [PATCH 016/110] We can select a move that leads to a terminal --- .../legal/brain/normalized/NormalizedBrain.java | 2 ++ .../legal/brain/normalized/NormalizedBrainTest.java | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 4fd94e3e6..0f857da31 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -85,6 +85,8 @@ public List findBestMove(StateType position ) { //negating because bigger for the opponents means worse for the current player //TODO: what if > 1 moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); + } else { + moveRatings.put( move, -10000000d ); //TODO: check the value } } ); } diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 93f28b66f..2ca2f207d 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -96,4 +96,17 @@ public void secondPlyThinkingNoCrashOnTerminalPosition() { ).findBestMove(gameState); } + @Test + public void singleMoveMustBeSelectableWhenNextIsTerminal() { + GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(0 ), new GameStateImpl() ); + + List bestMove = new NormalizedBrain( + (state, transition) -> transition.getId(), + 2 + ).findBestMove(gameState); + + assertEquals( 1, bestMove.size() ); + assertEquals( 0, bestMove.get(0).getId() ); + } + } \ No newline at end of file From 9ba3fdd5032b72fe96420d7c58075cb48cbc3f27 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 14 Jan 2019 22:22:34 +0200 Subject: [PATCH 017/110] Logic for balanced terminal positions analyzing works. --- .../legal/brain/normalized/NormalizedBrain.java | 2 +- .../com/leokom/chess/engine/GameStateImpl.java | 5 +++++ .../brain/normalized/NormalizedBrainTest.java | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 0f857da31..9c3f4b12b 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -86,7 +86,7 @@ public List findBestMove(StateType position ) { //TODO: what if > 1 moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); } else { - moveRatings.put( move, -10000000d ); //TODO: check the value + moveRatings.put( move, brains.evaluateMove( position, move ) ); //falling back to 1'st level } } ); } diff --git a/src/test/java/com/leokom/chess/engine/GameStateImpl.java b/src/test/java/com/leokom/chess/engine/GameStateImpl.java index 4f0bb95a9..27bcf895d 100644 --- a/src/test/java/com/leokom/chess/engine/GameStateImpl.java +++ b/src/test/java/com/leokom/chess/engine/GameStateImpl.java @@ -22,6 +22,11 @@ public GameStateImpl(GameTransitionImpl gameTransition, GameStateImpl gameState, this( ImmutableMap.of( gameTransition, gameState, gameTransition2, gameState2 ) ); } + public GameStateImpl(GameTransitionImpl gameTransition, GameStateImpl gameState, GameTransitionImpl gameTransition2, GameStateImpl gameState2, + GameTransitionImpl gameTransition3, GameStateImpl gameState3 ) { + this( ImmutableMap.of( gameTransition, gameState, gameTransition2, gameState2, gameTransition3, gameState3 ) ); + } + private GameStateImpl(Map tree ) { this.tree = tree; } diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 2ca2f207d..221088f4c 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -109,4 +109,20 @@ public void singleMoveMustBeSelectableWhenNextIsTerminal() { assertEquals( 0, bestMove.get(0).getId() ); } + @Test + public void movesLeadingToTerminalBetterToSelect() { + GameStateImpl gameState = new GameStateImpl( + new GameTransitionImpl(100 ), new GameStateImpl(), + new GameTransitionImpl(0 ), new GameStateImpl(), + new GameTransitionImpl(50 ), new GameStateImpl() + ); + + List bestMove = new NormalizedBrain( + (state, transition) -> transition.getId(), + 2 + ).findBestMove(gameState); + + assertEquals( 1, bestMove.size() ); + assertEquals( 100, bestMove.get(0).getId() ); + } } \ No newline at end of file From a14953ffafc9f8a605477289c6fec69ce5abf57d Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 14 Jan 2019 22:24:26 +0200 Subject: [PATCH 018/110] Interface trick to avoid generic warnings win mocks --- .../player/winboard/WinboardLegalIntegrationTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/leokom/chess/player/winboard/WinboardLegalIntegrationTest.java b/src/test/java/com/leokom/chess/player/winboard/WinboardLegalIntegrationTest.java index 481c7bdef..832b6a6de 100644 --- a/src/test/java/com/leokom/chess/player/winboard/WinboardLegalIntegrationTest.java +++ b/src/test/java/com/leokom/chess/player/winboard/WinboardLegalIntegrationTest.java @@ -2,6 +2,7 @@ import com.leokom.chess.engine.Move; import com.leokom.chess.engine.PieceType; +import com.leokom.chess.engine.Position; import com.leokom.chess.player.legal.LegalPlayer; import com.leokom.chess.player.legal.brain.common.Brain; import org.junit.Assert; @@ -26,6 +27,9 @@ public class WinboardLegalIntegrationTest {private WinboardCommunicator communic private WinboardPlayer playerSpy; private LegalPlayer opponent; + interface BrainMock extends Brain { + } + @Before public void prepare() { communicator = mock( WinboardCommunicator.class ); @@ -45,7 +49,7 @@ private void setOpponent( LegalPlayer opponent ) { @Test public void legalPlayerCanResignWhenNotHisMove() { - Brain brain = mock( Brain.class ); + BrainMock brain = mock( BrainMock.class ); when( brain.findBestMoveForOpponent( any() ) ).thenReturn(Move.RESIGN); LegalPlayer legalPlayer = new LegalPlayer(brain); setOpponent( legalPlayer ); @@ -58,7 +62,7 @@ public void legalPlayerCanResignWhenNotHisMove() { @Test public void winboardUnderstandsMultipleMoveWithOfferDraw() { - Brain brain = mock( Brain.class ); + BrainMock brain = mock( BrainMock.class ); when( brain.findBestMove( any() ) ).thenReturn(Arrays.asList( new Move( "d7", "d5" ), Move.OFFER_DRAW ) @@ -74,7 +78,7 @@ public void winboardUnderstandsMultipleMoveWithOfferDraw() { @Test public void winboardUnderstandsMultipleMoveWithResign() { - Brain brain = mock( Brain.class ); + BrainMock brain = mock( BrainMock.class ); when( brain.findBestMove( any() ) ).thenReturn(Arrays.asList( new Move( "e7", "e5" ), Move.RESIGN ) From 0e6f2872dc143ef44732d978a87d48711ffc8190 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 14 Jan 2019 22:48:18 +0200 Subject: [PATCH 019/110] Test covered resign behavior --- .../legal/brain/normalized/MasterEvaluatorTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index 7fe3e1379..bf454fe6a 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -31,4 +31,12 @@ public void beSmartALittle() { new EvaluatorAsserts( evaluator ) .assertFirstBetter( position, simpleMove, captureWithRiskToLoseQueen ); } + + @Test + public void resignIsWeak() { + Position position = Position.getInitialPosition(); + + new EvaluatorAsserts( evaluator ) + .assertFirstBetter( position, new Move( "e2", "e4" ), Move.RESIGN ); + } } \ No newline at end of file From 29cef12571b1bb700c4b9c642cdc04fc56554a40 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 14 Jan 2019 22:51:48 +0200 Subject: [PATCH 020/110] Red test: resign is considered the best move. The reason is unbalanced termination. RESIGN causes terminal position with estimate = 0. Other moves evaluate to some negative values --- .../NormalizedBrainMasterEvaluatorTest.java | 20 +++++++++++++++++++ .../NormalizedBrainPositionMoveTest.java | 1 + 2 files changed, 21 insertions(+) create mode 100644 src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java new file mode 100644 index 000000000..a0a52991a --- /dev/null +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java @@ -0,0 +1,20 @@ +package com.leokom.chess.player.legal.brain.normalized; + + +import com.leokom.chess.engine.Move; +import com.leokom.chess.engine.Position; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertNotEquals; + +//it's move integration than NormalizedBrainPositionMoveTest +public class NormalizedBrainMasterEvaluatorTest { + @Test + public void resignIsNotTheBest() { + List bestMove = new NormalizedBrain<>(new MasterEvaluator(), 2) + .findBestMove(Position.getInitialPosition()); + assertNotEquals( Move.RESIGN, bestMove.get(0) ); + } +} diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java index c6b6e7a4c..d3e40ba46 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java @@ -7,6 +7,7 @@ import java.util.List; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; //this test is based on Chess notions (in contrary to NormalizedBrainTest) //so it can be considered being more integrations with Position, Move From 7c1907abf9eea640d278b0abcba7eecc95b57eac Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 14 Jan 2019 23:09:23 +0200 Subject: [PATCH 021/110] Added more logging. --- .../player/legal/brain/normalized/NormalizedBrain.java | 8 ++++++++ src/main/resources/log4j2.xml | 2 +- .../brain/normalized/NormalizedBrainPositionMoveTest.java | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 9c3f4b12b..7808db3bc 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -5,6 +5,8 @@ import com.leokom.chess.engine.Move; import com.leokom.chess.player.legal.brain.common.Brain; import com.leokom.chess.player.legal.brain.common.GenericEvaluator; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.ThreadContext; import java.util.Collections; import java.util.HashMap; @@ -77,6 +79,8 @@ public List findBestMove(StateType position ) { else { //just 2 is supported now position.getMoves().forEach( move -> { + ThreadContext.put( "moveBeingAnalyzed", move.toString() ); + StateType target = position.move( move ); List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); @@ -86,8 +90,11 @@ public List findBestMove(StateType position ) { //TODO: what if > 1 moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); } else { + LogManager.getLogger().info( "Evaluating just the current level" ); moveRatings.put( move, brains.evaluateMove( position, move ) ); //falling back to 1'st level } + + ThreadContext.clearAll(); } ); } @@ -96,6 +103,7 @@ public List findBestMove(StateType position ) { private List getMoveWithMaxRating( Map< TransitionType, Double > moveValues ) { return moveValues.entrySet().stream() + .peek( System.out::println ) .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) .map( Collections::singletonList ) diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 5ecfb0661..b6e595a1a 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,7 +2,7 @@ - + diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java index d3e40ba46..c6b6e7a4c 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainPositionMoveTest.java @@ -7,7 +7,6 @@ import java.util.List; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; //this test is based on Chess notions (in contrary to NormalizedBrainTest) //so it can be considered being more integrations with Position, Move From 0d15f347a3d86506134c51af206e453c453a7126 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:23:19 +0200 Subject: [PATCH 022/110] Red test: enforce [ 0, 1 ] range for MasterEvaluator --- .../brain/normalized/MasterEvaluatorTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index bf454fe6a..83c90c878 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -6,6 +6,8 @@ import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertTrue; + public class MasterEvaluatorTest { private Evaluator evaluator; @@ -39,4 +41,18 @@ public void resignIsWeak() { new EvaluatorAsserts( evaluator ) .assertFirstBetter( position, new Move( "e2", "e4" ), Move.RESIGN ); } + + //enforce rules that are valid for the whole normalized package, to MasterEvaluator itself + @Test + public void allMovesMustBeEvaluatedFrom0To1() { + Position position = Position.getInitialPosition(); + + position.getMoves().forEach( move -> { + double result = evaluator.evaluateMove(position, move); + assertTrue( + String.format( "The move %s must be evaluated in range [0,1], actually: %s", move, result ) + ,result >= 0.0 && result <= 1.0 ); + } ); + } + } \ No newline at end of file From a7a48fad4513fb70c0485ecc527bcf9009dd15b8 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:25:55 +0200 Subject: [PATCH 023/110] Artificially shrinking the master evaluator output to [ 0, 1 ] --- src/main/java/com/leokom/chess/PlayerFactory.java | 4 +++- .../legal/brain/internal/common/EvaluatorWeights.java | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 96b6c6681..870e97cc2 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -4,6 +4,8 @@ import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; import com.leokom.chess.player.legal.LegalPlayer; +import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; +import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; import com.leokom.chess.player.legal.brain.simple.SimpleBrain; import com.leokom.chess.player.winboard.WinboardPlayer; import org.apache.logging.log4j.LogManager; @@ -73,7 +75,7 @@ private static PlayerSelection selectPlayer( Side side, String engineName ) { } public enum PlayerSelection { - LEGAL( LegalPlayer::new ), + LEGAL( () -> new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), 2) ) ), SIMPLE( () -> new LegalPlayer( new SimpleBrain() ) ), WINBOARD( WinboardPlayer::create ); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java index cbb0dc924..3ba4e921c 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java @@ -14,9 +14,9 @@ public final class EvaluatorWeights { private EvaluatorWeights() {} - private static final double HIGHEST_PRIORITY = 100.0; - private static final double INCREASED_PRIORITY = 3.0; - private static final double NORMAL_PRIORITY = 1.0; + private static final double HIGHEST_PRIORITY = 1.0; + private static final double INCREASED_PRIORITY = 0.03; + private static final double NORMAL_PRIORITY = 0.01; private static final double LOWEST_POSSIBLE = 0.0; public static Map getStandardWeights() { From 177a7fab638fce9213e1d301c5b2bbeb0907768e Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:27:58 +0200 Subject: [PATCH 024/110] Red test: resign is not correctly determined --- .../legal/brain/normalized/MasterEvaluatorTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index 83c90c878..9b15ad043 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -6,6 +6,7 @@ import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class MasterEvaluatorTest { @@ -55,4 +56,11 @@ public void allMovesMustBeEvaluatedFrom0To1() { } ); } + //losing should get the minimal possible value + @Test + public void losingIsEvaluatedTo0() { + Position position = Position.getInitialPosition(); + assertEquals( 0.0, evaluator.evaluateMove(position, Move.RESIGN), 0 ); + } + } \ No newline at end of file From bb40c1b0fd437bb8ab041e2b28c17f501603199c Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:31:47 +0200 Subject: [PATCH 025/110] Winning upper boundary set up --- .../legal/brain/normalized/MasterEvaluatorTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index 9b15ad043..9b07cb386 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -63,4 +63,17 @@ public void losingIsEvaluatedTo0() { assertEquals( 0.0, evaluator.evaluateMove(position, Move.RESIGN), 0 ); } + @Test + public void winningIsEvaluatedTo1() { + Position position = Position.getInitialPosition() + .move( "f2", "f3" ) + .move( "e7", "e5" ) + .move( "g2", "g4" ); + //fools checkmate is prepared + + + Move checkmateMove = new Move( "d8", "h4" ); + assertEquals( 1, evaluator.evaluateMove( position, checkmateMove ), 0 ); + } + } \ No newline at end of file From 0f772c87d67d57e295017e5d5c606943c70255ce Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:35:10 +0200 Subject: [PATCH 026/110] Winning + Losing are bound by [ 0, 1 ] --- .../player/legal/brain/denormalized/CheckmateEvaluator.java | 3 ++- .../chess/player/legal/brain/normalized/MasterEvaluator.java | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java index 5aa67380e..36d5aa977 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java @@ -10,7 +10,8 @@ * * Checkmate is the highest goal of the whole game */ -class CheckmateEvaluator implements Evaluator { +//TODO: move to normalized package +public class CheckmateEvaluator implements Evaluator { private static final int BEST_MOVE = 1; private static final int WORST_MOVE = 0; diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index bf6fbdde5..4a97ceba7 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -4,6 +4,7 @@ import com.leokom.chess.engine.Position; import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.common.EvaluatorType; +import com.leokom.chess.player.legal.brain.denormalized.CheckmateEvaluator; import com.leokom.chess.player.legal.brain.internal.common.EvaluatorWeights; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,6 +35,10 @@ public MasterEvaluator() { @Override public double evaluateMove( Position position, Move move ) { + if ( position.move( move ).isTerminal() ) { + return new CheckmateEvaluator().evaluateMove( position, move ); + } + double result = evaluatorWeights.entrySet().stream().mapToDouble(evaluatorEntry -> { final Evaluator evaluator = new NormalizedEvaluatorFactory().get(evaluatorEntry.getKey()); final double weight = evaluatorEntry.getValue(); From 301c6752673ae9c35dad98fd31537fb8b31ce208 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:38:40 +0200 Subject: [PATCH 027/110] More freedom added into Brain selection for LegalPlayer --- .../java/com/leokom/chess/PlayerFactoryTest.java | 6 +++--- .../chess/player/legal/LegalPlayerNameTest.java | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index 5a6bf96bf..78bd84b61 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -3,10 +3,10 @@ import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; import com.leokom.chess.player.winboard.WinboardPlayer; +import org.hamcrest.CoreMatchers; import org.junit.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class PlayerFactoryTest { private static String whiteProperty; @@ -80,7 +80,7 @@ public void legalSelected() { } private void assertIsLegal( Player player ) { - assertEquals( "LegalPlayer : DenormalizedBrain", player.name() ); + assertThat( player.name(), CoreMatchers.startsWith( "LegalPlayer" ) ); } @Test diff --git a/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java b/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java index 75c455e5a..8aec7ad7c 100644 --- a/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java +++ b/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java @@ -1,19 +1,26 @@ package com.leokom.chess.player.legal; +import com.leokom.chess.player.legal.brain.denormalized.DenormalizedBrain; +import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; +import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; import org.junit.Test; -import static com.leokom.chess.PlayerFactory.PlayerSelection.LEGAL; import static com.leokom.chess.PlayerFactory.PlayerSelection.SIMPLE; import static org.junit.Assert.assertEquals; public class LegalPlayerNameTest { @Test - public void defaultName() { - assertEquals( "LegalPlayer : DenormalizedBrain", LEGAL.create().name() ); + public void denormalizedBrain() { + assertEquals( "LegalPlayer : DenormalizedBrain", new LegalPlayer( new DenormalizedBrain() ).name() ); } @Test - public void customNameWithCustomBrain() { + public void normalizedBrain() { + assertEquals( "LegalPlayer : NormalizedBrain", new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator()) ).name() ); + } + + @Test + public void simpleBrain() { assertEquals( "LegalPlayer : SimpleBrain", SIMPLE.create().name() ); } } From 2801b7956308ea7c5c0691975ebee3c33cdc60c1 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:40:51 +0200 Subject: [PATCH 028/110] TODO added --- .../chess/player/legal/brain/normalized/MasterEvaluator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 4a97ceba7..1b173d938 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -35,6 +35,8 @@ public MasterEvaluator() { @Override public double evaluateMove( Position position, Move move ) { + //TODO: do we really ensure [ 0, 1 ] range here? + if ( position.move( move ).isTerminal() ) { return new CheckmateEvaluator().evaluateMove( position, move ); } From 42e6e711421041d3f7bbadebe34c89e565406410 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:44:56 +0200 Subject: [PATCH 029/110] Taking terminal position results into account for evaluation. --- .../chess/player/legal/brain/normalized/NormalizedBrain.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 7808db3bc..201a3dc42 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -91,7 +91,10 @@ public List findBestMove(StateType position ) { moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); } else { LogManager.getLogger().info( "Evaluating just the current level" ); - moveRatings.put( move, brains.evaluateMove( position, move ) ); //falling back to 1'st level + //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range + //TODO: highly depends on MasterEvaluator [ 0, 1 ]! + //where all the second level moves exist + moveRatings.put( move, brains.evaluateMove( position, move ) - 1 ); //falling back to 1'st level } ThreadContext.clearAll(); From 35c18c68bebe6c0109477f9a90804b624ba4a7d8 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 22:56:37 +0200 Subject: [PATCH 030/110] Draw offer is excluded from evaluation at the moment --- .../legal/brain/normalized/NormalizedBrain.java | 10 +++++++--- .../NormalizedBrainMasterEvaluatorTest.java | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 201a3dc42..ca42d7f4b 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -12,6 +12,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; /** * Initial decision maker. @@ -72,13 +73,12 @@ public List findBestMove(StateType position ) { //this looks safe since Offer draw cannot be a single legal move in a position. //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches - position.getMoves().stream().filter(move -> move != Move.OFFER_DRAW).forEach(move -> + getMovesWithoutDrawOffer(position).forEach(move -> moveRatings.put(move, brains.evaluateMove(position, move)) ); } else { //just 2 is supported now - - position.getMoves().forEach( move -> { + getMovesWithoutDrawOffer( position ).forEach( move -> { ThreadContext.put( "moveBeingAnalyzed", move.toString() ); StateType target = position.move( move ); @@ -104,6 +104,10 @@ public List findBestMove(StateType position ) { return getMoveWithMaxRating(moveRatings); } + private Stream getMovesWithoutDrawOffer(StateType position) { + return position.getMoves().stream().filter(move -> move != Move.OFFER_DRAW); + } + private List getMoveWithMaxRating( Map< TransitionType, Double > moveValues ) { return moveValues.entrySet().stream() .peek( System.out::println ) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java index a0a52991a..7134cdc58 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java @@ -17,4 +17,21 @@ public void resignIsNotTheBest() { .findBestMove(Position.getInitialPosition()); assertNotEquals( Move.RESIGN, bestMove.get(0) ); } + + @Test + public void drawOfferIsNotTheBest() { + List bestMove = new NormalizedBrain<>(new MasterEvaluator(), 2) + .findBestMove(Position.getInitialPosition()); + assertNotEquals( Move.OFFER_DRAW, bestMove.get(0) ); + } + + //it's not a strict requirement. I just want to exclude draw offer from the algorithm + //we're not ready for it yet. + //TODO: extract draw offer 2'nd level evaluation to a separate ticket + @Test + public void drawOfferIsNotTheBestResponse() { + List bestMove = new NormalizedBrain<>(new MasterEvaluator(), 2) + .findBestMove(Position.getInitialPosition().move( "e2", "e4" )); + assertNotEquals( Move.OFFER_DRAW, bestMove.get(0) ); + } } From 2693990f3151316fde8b33047853e9f3085593f1 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 15 Jan 2019 23:04:22 +0200 Subject: [PATCH 031/110] TODO added --- .../player/legal/brain/denormalized/CheckmateEvaluator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java index 36d5aa977..44b274c88 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java @@ -23,6 +23,7 @@ public class CheckmateEvaluator implements Evaluator { */ @Override public double evaluateMove( Position position, Move move ) { + //TODO: what about accepted draw and other terminal conditions? final Position result = position.move( move ); return result.isTerminal() && result.getWinningSide() != null && //this excludes draws From d127168ac06207f5bd7ed5590291ec5e4a35f042 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 16 Jan 2019 22:42:05 +0200 Subject: [PATCH 032/110] Red test: [ 0, 1 ] range ensuring for custom weights --- .../brain/normalized/MasterEvaluatorTest.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index 9b07cb386..0a8dbba4a 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -3,9 +3,14 @@ import com.leokom.chess.engine.*; import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.common.EvaluatorAsserts; +import com.leokom.chess.player.legal.brain.common.EvaluatorType; import org.junit.Before; import org.junit.Test; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -48,14 +53,27 @@ public void resignIsWeak() { public void allMovesMustBeEvaluatedFrom0To1() { Position position = Position.getInitialPosition(); + assertAllMovesEvaluatedIn0To1Range(position, evaluator); + } + + private void assertAllMovesEvaluatedIn0To1Range(Position position, Evaluator evaluatorToValidate) { position.getMoves().forEach( move -> { - double result = evaluator.evaluateMove(position, move); + double result = evaluatorToValidate.evaluateMove(position, move); assertTrue( String.format( "The move %s must be evaluated in range [0,1], actually: %s", move, result ) ,result >= 0.0 && result <= 1.0 ); } ); } + @Test + public void allMovesMustBeEvaluatedFrom0To1EvenWithCustomWeights() { + Map weights = new HashMap<>(); + Arrays.stream( EvaluatorType.values() ).forEach( type -> weights.put( type, 1.0 ) ); + + MasterEvaluator masterEvaluatorWithCustomWeigths = new MasterEvaluator(weights); + assertAllMovesEvaluatedIn0To1Range( Position.getInitialPosition(), masterEvaluatorWithCustomWeigths ); + } + //losing should get the minimal possible value @Test public void losingIsEvaluatedTo0() { From 8e03496871741e081d9ebeebec63ef25a21fa5b1 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 17 Jan 2019 22:20:50 +0200 Subject: [PATCH 033/110] [ 0, 1 ] range ensuring for master evaluator result --- .../legal/brain/normalized/MasterEvaluator.java | 11 +++++++---- .../legal/brain/normalized/NormalizedBrain.java | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 1b173d938..0d0d9c4cf 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -35,8 +35,6 @@ public MasterEvaluator() { @Override public double evaluateMove( Position position, Move move ) { - //TODO: do we really ensure [ 0, 1 ] range here? - if ( position.move( move ).isTerminal() ) { return new CheckmateEvaluator().evaluateMove( position, move ); } @@ -49,7 +47,12 @@ public double evaluateMove( Position position, Move move ) { LOG.debug( "{} [{}] : {} * {} = {}", move, evaluatorEntry.getKey(), weight, evaluatorResponse, moveEstimate ); return moveEstimate; }).sum(); - LOG.info("{} ===> {}", move, result); - return result; + + //result that is in [ 0, 1 ] range + //it should work assuming the weights themselves are in [ 0, 1 ] + double normalizedResult = result / evaluatorWeights.size(); + + LOG.info("{} ===> {} ===> {}", move, result, normalizedResult); + return normalizedResult; } } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index ca42d7f4b..0710389d9 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -110,7 +110,6 @@ private Stream getMovesWithoutDrawOffer(StateType position) { private List getMoveWithMaxRating( Map< TransitionType, Double > moveValues ) { return moveValues.entrySet().stream() - .peek( System.out::println ) .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) .map( Collections::singletonList ) From a4fb4b5a96e31efb1e6f2fa4f2e3bd9069456cfc Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 17 Jan 2019 22:25:51 +0200 Subject: [PATCH 034/110] More TODOs --- .../chess/player/legal/brain/normalized/MasterEvaluator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 0d0d9c4cf..28e0efa37 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -36,6 +36,7 @@ public MasterEvaluator() { @Override public double evaluateMove( Position position, Move move ) { if ( position.move( move ).isTerminal() ) { + //TODO: remove CheckmateEvaluator from further algorithm then return new CheckmateEvaluator().evaluateMove( position, move ); } @@ -49,7 +50,7 @@ public double evaluateMove( Position position, Move move ) { }).sum(); //result that is in [ 0, 1 ] range - //it should work assuming the weights themselves are in [ 0, 1 ] + //TODO: it should work assuming the weights themselves are in [ 0, 1 ] double normalizedResult = result / evaluatorWeights.size(); LOG.info("{} ===> {} ===> {}", move, result, normalizedResult); From 5eb46a3dd60b2fc89b05ec4b4b4722c4bc1a7429 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sat, 19 Jan 2019 21:03:35 +0200 Subject: [PATCH 035/110] new TODO based on a discovered case --- .../chess/player/legal/brain/denormalized/DenormalizedBrain.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index 61909ab66..d0237de48 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -64,6 +64,7 @@ private Table generateWithWeights( Table result = HashBasedTable.create(); + //TODO: if one of normalized evaluators is disabled, here we'll get NPE. normalizedTable.cellSet().forEach( cell -> result.put( cell.getRowKey(), cell.getColumnKey(), cell.getValue() * standardWeights.get( cell.getRowKey() ) ) ); From 94730d9642b5a3c74cdb9aadc3f8834b51c5b18c Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sat, 19 Jan 2019 21:44:39 +0200 Subject: [PATCH 036/110] Refactoring: introduced Brain interface which is chess-specific and GenericBrain which is chess-independent (more or less) --- .../chess/player/legal/LegalPlayer.java | 10 ++-- .../player/legal/brain/common/Brain.java | 50 ++----------------- .../legal/brain/common/GenericBrain.java | 50 +++++++++++++++++++ .../brain/denormalized/DenormalizedBrain.java | 7 +-- .../brain/normalized/NormalizedBrain.java | 4 +- .../legal/brain/normalized/package-info.java | 2 +- .../legal/brain/simple/SimpleBrain.java | 4 +- .../chess/player/legal/package-info.java | 2 +- .../denormalized/DenormalizedBrainTest.java | 2 +- .../WinboardLegalIntegrationTest.java | 10 ++-- 10 files changed, 72 insertions(+), 69 deletions(-) create mode 100644 src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java diff --git a/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java b/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java index 46f5efc70..0ac6343e0 100644 --- a/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java +++ b/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java @@ -4,7 +4,7 @@ import com.leokom.chess.engine.Position; import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; -import com.leokom.chess.player.legal.brain.common.Brain; +import com.leokom.chess.player.legal.brain.common.GenericBrain; import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.denormalized.DenormalizedBrain; import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; @@ -22,7 +22,7 @@ public class LegalPlayer implements Player { private Player opponent; private Position position = Position.getInitialPosition(); - private final Brain< Position, Move > brain; + private final GenericBrain< Position, Move > brain; private boolean recordingMode; private Side ourSide; @@ -34,7 +34,7 @@ public LegalPlayer() { this( new DenormalizedBrain() ); } - public LegalPlayer( Brain< Position, Move > brain ) { + public LegalPlayer( GenericBrain< Position, Move > brain ) { this.brain = brain; } @@ -110,10 +110,10 @@ void executeOurMove() { final List< Move > bestMoves = brain.findBestMove( position ); if ( bestMoves == null ) { - throw new IllegalStateException( "Brain should never return null but it did that" ); + throw new IllegalStateException( "GenericBrain should never return null but it did that" ); } if ( bestMoves.isEmpty() ) { - throw new IllegalStateException( "Brain doesn't want to move while the position is not terminal! It's a bug in the brain" ); + throw new IllegalStateException( "GenericBrain doesn't want to move while the position is not terminal! It's a bug in the brain" ); } executeMoves( bestMoves ); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java b/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java index fe6d4d52f..4ca56602a 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/Brain.java @@ -1,50 +1,10 @@ package com.leokom.chess.player.legal.brain.common; -import com.leokom.chess.engine.GameState; -import com.leokom.chess.engine.GameTransition; +import com.leokom.chess.engine.Move; +import com.leokom.chess.engine.Position; -import java.util.List; /** - * The brain. - * - * Makes decision : - * this is the move(s) to execute in current position. - * - * A valid implementation of the decision maker must be - * stateless. - * - * Author: Leonid - * Date-time: 23.08.16 22:53 + * Chess-specific brain */ -public interface Brain < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> { - - /** - * Finds the best move(s) in the current position. - * 2 moves can be returned if a player is allowed to execute - * something extra after his move, e.g. OFFER DRAW - * @param position position to analyze - * @return best move according to current strategy, absence of moves means: - * no moves are legal - we reached a terminal position - */ - List< TransitionType > findBestMove( StateType position ); - - /** - * Get the best move to execute when it's not our - * turn to move - * @param position position to analyze - * @return best move in not our turn - * null if we don't want to move) - */ - /* - That's exactly why default methods were introduced: - to allow evolving interface while not forcing existing - implementations to increase complexity - */ - default TransitionType findBestMoveForOpponent( StateType position ) { - return null; - } - - default String name() { - return this.getClass().getSimpleName(); - } -} \ No newline at end of file +public interface Brain extends GenericBrain{ +} diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java new file mode 100644 index 000000000..9648fb923 --- /dev/null +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java @@ -0,0 +1,50 @@ +package com.leokom.chess.player.legal.brain.common; + +import com.leokom.chess.engine.GameState; +import com.leokom.chess.engine.GameTransition; + +import java.util.List; +/** + * The brain. + * + * Makes decision : + * this is the move(s) to execute in current position. + * + * A valid implementation of the decision maker must be + * stateless. + * + * Author: Leonid + * Date-time: 23.08.16 22:53 + */ +public interface GenericBrain< StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> { + + /** + * Finds the best move(s) in the current position. + * 2 moves can be returned if a player is allowed to execute + * something extra after his move, e.g. OFFER DRAW + * @param position position to analyze + * @return best move according to current strategy, absence of moves means: + * no moves are legal - we reached a terminal position + */ + List< TransitionType > findBestMove( StateType position ); + + /** + * Get the best move to execute when it's not our + * turn to move + * @param position position to analyze + * @return best move in not our turn + * null if we don't want to move) + */ + /* + That's exactly why default methods were introduced: + to allow evolving interface while not forcing existing + implementations to increase complexity + */ + default TransitionType findBestMoveForOpponent( StateType position ) { + return null; + } + + default String name() { + return this.getClass().getSimpleName(); + } +} \ No newline at end of file diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index d0237de48..0e52ea398 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -4,10 +4,7 @@ import com.google.common.collect.Table; import com.leokom.chess.engine.Move; import com.leokom.chess.engine.Position; -import com.leokom.chess.player.legal.brain.common.Brain; -import com.leokom.chess.player.legal.brain.common.Evaluator; -import com.leokom.chess.player.legal.brain.common.EvaluatorFactory; -import com.leokom.chess.player.legal.brain.common.EvaluatorType; +import com.leokom.chess.player.legal.brain.common.*; import com.leokom.chess.player.legal.brain.internal.common.EvaluatorWeights; import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; import org.apache.logging.log4j.LogManager; @@ -21,7 +18,7 @@ * Author: Leonid * Date-time: 27.08.16 21:51 */ -public class DenormalizedBrain implements Brain< Position, Move > { +public class DenormalizedBrain implements Brain { private static final Logger LOG = LogManager.getLogger(); private static final double DEFAULT_FOR_EQUAL_NOT_IN_RANGE = 0.5; diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 0710389d9..ea3a0d424 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -3,7 +3,7 @@ import com.leokom.chess.engine.GameState; import com.leokom.chess.engine.GameTransition; import com.leokom.chess.engine.Move; -import com.leokom.chess.player.legal.brain.common.Brain; +import com.leokom.chess.player.legal.brain.common.GenericBrain; import com.leokom.chess.player.legal.brain.common.GenericEvaluator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.ThreadContext; @@ -24,7 +24,7 @@ * Author: Leonid * Date-time: 23.08.16 22:54 */ -public class NormalizedBrain < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> implements Brain< StateType, TransitionType > { +public class NormalizedBrain < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> implements GenericBrain< StateType, TransitionType > { private final GenericEvaluator< StateType, TransitionType > brains; private final int pliesDepth; diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/package-info.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/package-info.java index 36487e07c..cbf186830 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/package-info.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/package-info.java @@ -1,5 +1,5 @@ /** - * Brain based on normalized evaluators. + * GenericBrain based on normalized evaluators. * * All evaluators in this package must return value * diff --git a/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java index b8a894c06..e500ce889 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java @@ -10,7 +10,7 @@ import static java.util.Collections.singletonList; /** - * Brain that implements Simple player in Legal player's infrastructure + * GenericBrain that implements Simple player in Legal player's infrastructure * Created by Leonid on 09.02.17. * * Based on previous implementation with comments: @@ -23,7 +23,7 @@ * Author: Leonid * Date-time: 15.04.13 22:26 */ -public class SimpleBrain implements Brain< Position, Move > { +public class SimpleBrain implements Brain { @Override public List< Move > findBestMove( Position position ) { if ( position.isTerminal() ) { diff --git a/src/main/java/com/leokom/chess/player/legal/package-info.java b/src/main/java/com/leokom/chess/player/legal/package-info.java index 690068006..1ae6393ad 100644 --- a/src/main/java/com/leokom/chess/player/legal/package-info.java +++ b/src/main/java/com/leokom/chess/player/legal/package-info.java @@ -2,7 +2,7 @@ * Implementation of a player * who can execute the legal moves according to chess rules. * - * Different strategies can be injected via Evaluator or Brain. + * Different strategies can be injected via Evaluator or GenericBrain. * * @see com.leokom.chess.player.legal.LegalPlayer */ diff --git a/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java index c94750e4a..e872bc042 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java @@ -15,7 +15,7 @@ * Date-time: 27.08.16 21:58 */ public class DenormalizedBrainTest { - private Brain< Position, Move > brain; + private Brain brain; @Before public void prepare() { diff --git a/src/test/java/com/leokom/chess/player/winboard/WinboardLegalIntegrationTest.java b/src/test/java/com/leokom/chess/player/winboard/WinboardLegalIntegrationTest.java index 832b6a6de..481c7bdef 100644 --- a/src/test/java/com/leokom/chess/player/winboard/WinboardLegalIntegrationTest.java +++ b/src/test/java/com/leokom/chess/player/winboard/WinboardLegalIntegrationTest.java @@ -2,7 +2,6 @@ import com.leokom.chess.engine.Move; import com.leokom.chess.engine.PieceType; -import com.leokom.chess.engine.Position; import com.leokom.chess.player.legal.LegalPlayer; import com.leokom.chess.player.legal.brain.common.Brain; import org.junit.Assert; @@ -27,9 +26,6 @@ public class WinboardLegalIntegrationTest {private WinboardCommunicator communic private WinboardPlayer playerSpy; private LegalPlayer opponent; - interface BrainMock extends Brain { - } - @Before public void prepare() { communicator = mock( WinboardCommunicator.class ); @@ -49,7 +45,7 @@ private void setOpponent( LegalPlayer opponent ) { @Test public void legalPlayerCanResignWhenNotHisMove() { - BrainMock brain = mock( BrainMock.class ); + Brain brain = mock( Brain.class ); when( brain.findBestMoveForOpponent( any() ) ).thenReturn(Move.RESIGN); LegalPlayer legalPlayer = new LegalPlayer(brain); setOpponent( legalPlayer ); @@ -62,7 +58,7 @@ public void legalPlayerCanResignWhenNotHisMove() { @Test public void winboardUnderstandsMultipleMoveWithOfferDraw() { - BrainMock brain = mock( BrainMock.class ); + Brain brain = mock( Brain.class ); when( brain.findBestMove( any() ) ).thenReturn(Arrays.asList( new Move( "d7", "d5" ), Move.OFFER_DRAW ) @@ -78,7 +74,7 @@ public void winboardUnderstandsMultipleMoveWithOfferDraw() { @Test public void winboardUnderstandsMultipleMoveWithResign() { - BrainMock brain = mock( BrainMock.class ); + Brain brain = mock( Brain.class ); when( brain.findBestMove( any() ) ).thenReturn(Arrays.asList( new Move( "e7", "e5" ), Move.RESIGN ) From 9633a9225d1a293e26977c4b66a7b1482e14b917 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 20 Jan 2019 16:21:22 +0200 Subject: [PATCH 037/110] Verify range of evaluator weigths --- .../legal/brain/normalized/MasterEvaluator.java | 15 +++++++++++++++ .../brain/normalized/MasterEvaluatorTest.java | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 28e0efa37..01d08dc16 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -29,10 +29,25 @@ public MasterEvaluator() { this( EvaluatorWeights.getStandardWeights() ); } + /** + * create evaluator with custom weights + * @param weights evaluator -> weight + * @throws IllegalArgumentException if any weight is outside [ 0, 1 ] range + */ + /* + Alternative to throwing the exception would be normalizing the weights on-fly. At the moment - not needed + */ MasterEvaluator( Map weights ) { + verifyRange( weights ); this.evaluatorWeights = weights; } + private void verifyRange(Map weights) { + if ( weights.values().stream().anyMatch( weight -> weight < 0.0 || weight > 1.0 ) ) { + throw new IllegalArgumentException(); + } + } + @Override public double evaluateMove( Position position, Move move ) { if ( position.move( move ).isTerminal() ) { diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index 0a8dbba4a..1d46c58c7 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -94,4 +94,18 @@ public void winningIsEvaluatedTo1() { assertEquals( 1, evaluator.evaluateMove( position, checkmateMove ), 0 ); } + @Test( expected = IllegalArgumentException.class ) + public void evaluatorWeightBigger1NotAccepted() { + Map weights = new HashMap<>(); + weights.put( EvaluatorType.PROTECTION, 1.1 ); + new MasterEvaluator( weights ); + } + + + @Test( expected = IllegalArgumentException.class ) + public void evaluatorWeightLess0NotAccepted() { + Map weights = new HashMap<>(); + weights.put( EvaluatorType.PROTECTION, -0.1 ); + new MasterEvaluator( weights ); + } } \ No newline at end of file From 59e4e02b060335fd6812163a3814c3176d69195a Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 20 Jan 2019 16:39:32 +0200 Subject: [PATCH 038/110] Ensured constraint [ 0, 1 ] for evaluator weights --- .../brain/denormalized/DenormalizedBrain.java | 2 +- .../internal/common/EvaluatorWeights.java | 40 +++++++++++++++++-- .../brain/normalized/MasterEvaluator.java | 17 ++++---- .../brain/normalized/NormalizedBrain.java | 2 +- .../leokom/chess/SimulatorMultiDeltaIT.java | 17 ++++---- .../internal/common/EvaluatorWeightsTest.java | 32 +++++++++++++++ .../normalized/MasterEvaluatorBuilder.java | 2 +- .../brain/normalized/MasterEvaluatorTest.java | 15 ------- 8 files changed, 89 insertions(+), 38 deletions(-) create mode 100644 src/test/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeightsTest.java diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index 0e52ea398..1482669c9 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -57,7 +57,7 @@ private void logTable( Table weightedTable, String } private Table generateWithWeights( Table normalizedTable ) { - final Map standardWeights = EvaluatorWeights.getStandardWeights(); + final Map standardWeights = new EvaluatorWeights().asMap(); Table< EvaluatorType, Move, Double > result = HashBasedTable.create(); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java index 3ba4e921c..9f748b5b7 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java @@ -2,8 +2,11 @@ import com.leokom.chess.player.legal.brain.common.EvaluatorType; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Stream; import static com.leokom.chess.player.legal.brain.common.EvaluatorType.*; @@ -12,14 +15,33 @@ * Date-time: 27.08.16 21:54 */ public final class EvaluatorWeights { - private EvaluatorWeights() {} - private static final double HIGHEST_PRIORITY = 1.0; private static final double INCREASED_PRIORITY = 0.03; private static final double NORMAL_PRIORITY = 0.01; private static final double LOWEST_POSSIBLE = 0.0; - public static Map getStandardWeights() { + private final Map weights; + + public EvaluatorWeights() { + this( getStandardWeights() ); + } + + /** + * Create weights object, ensure constraint: every weight is in [ 0,1 ] range + * @param weights weights + */ + public EvaluatorWeights(Map weights) { + verifyRange( weights ); + this.weights = Collections.unmodifiableMap( new HashMap<>( weights ) ); + } + + private void verifyRange(Map weights) { + if ( weights.values().stream().anyMatch( weight -> weight < 0.0 || weight > 1.0 ) ) { + throw new IllegalArgumentException( String.format( "Illegal weight outside of allowed range detected. Map: %s", weights ) ); + } + } + + private static Map getStandardWeights() { //TODO: refactor to constant immutable map Map result = new HashMap<>(); result.put( CHECKMATE, HIGHEST_PRIORITY ); @@ -35,4 +57,16 @@ public static Map getStandardWeights() { result.put( SPECIAL_MOVE, LOWEST_POSSIBLE ); return result; } + + public Stream> stream() { + return this.weights.entrySet().stream(); + } + + public int size() { + return this.weights.size(); + } + + public Map asMap() { + return this.weights; + } } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 01d08dc16..1a2ec76fd 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -23,10 +23,11 @@ public class MasterEvaluator implements Evaluator { //among 2 'equal' moves we would like to select according to some //compare 1-to-another logic - private final Map evaluatorWeights; + private final EvaluatorWeights evaluatorWeights; public MasterEvaluator() { - this( EvaluatorWeights.getStandardWeights() ); + //standard weights + this( new EvaluatorWeights() ); } /** @@ -38,14 +39,12 @@ public MasterEvaluator() { Alternative to throwing the exception would be normalizing the weights on-fly. At the moment - not needed */ MasterEvaluator( Map weights ) { - verifyRange( weights ); - this.evaluatorWeights = weights; + //custom weights + this( new EvaluatorWeights( weights ) ); } - private void verifyRange(Map weights) { - if ( weights.values().stream().anyMatch( weight -> weight < 0.0 || weight > 1.0 ) ) { - throw new IllegalArgumentException(); - } + MasterEvaluator( EvaluatorWeights evaluatorWeights ) { + this.evaluatorWeights = evaluatorWeights; } @Override @@ -55,7 +54,7 @@ public double evaluateMove( Position position, Move move ) { return new CheckmateEvaluator().evaluateMove( position, move ); } - double result = evaluatorWeights.entrySet().stream().mapToDouble(evaluatorEntry -> { + double result = evaluatorWeights.stream().mapToDouble(evaluatorEntry -> { final Evaluator evaluator = new NormalizedEvaluatorFactory().get(evaluatorEntry.getKey()); final double weight = evaluatorEntry.getValue(); final double evaluatorResponse = evaluator.evaluateMove(position, move); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index ea3a0d424..222e4c4a9 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -92,7 +92,7 @@ public List findBestMove(StateType position ) { } else { LogManager.getLogger().info( "Evaluating just the current level" ); //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range - //TODO: highly depends on MasterEvaluator [ 0, 1 ]! + // highly depends on MasterEvaluator [ 0, 1 ]! //where all the second level moves exist moveRatings.put( move, brains.evaluateMove( position, move ) - 1 ); //falling back to 1'st level } diff --git a/src/test/java/com/leokom/chess/SimulatorMultiDeltaIT.java b/src/test/java/com/leokom/chess/SimulatorMultiDeltaIT.java index 50f16a880..2155e464c 100644 --- a/src/test/java/com/leokom/chess/SimulatorMultiDeltaIT.java +++ b/src/test/java/com/leokom/chess/SimulatorMultiDeltaIT.java @@ -24,7 +24,7 @@ public class SimulatorMultiDeltaIT { /* * Protection property. - * Coefficient :: [ 0 .. 90 ] delta 10 ==> 10 coefficients probed + * Coefficient :: [ 0 .. 0.9 ] delta 0.1 ==> 10 coefficients probed * Each probe :: 10 games (5 pairs of games with colours switched) * */ @@ -38,31 +38,32 @@ public void attackingDeltas() { simulateDeltas( this::createAttacker, 1 ); } - private LegalPlayer createAttacker( Integer coefficient ) { + private LegalPlayer createAttacker( double coefficient ) { return new LegalPlayer( new MasterEvaluatorBuilder().weight( EvaluatorType.ATTACK, coefficient ).build() ); } - private void simulateDeltas( Function< Integer, LegalPlayer > whitePlayerGenerator, int gamePairsPerIteration ) { - Map< Integer, SimulatorStatistics > statisticsMap = new TreeMap<>(); + private void simulateDeltas( Function< Double, LegalPlayer > whitePlayerGenerator, int gamePairsPerIteration ) { + Map< Double, SimulatorStatistics > statisticsMap = new TreeMap<>(); for ( int coefficient = 0; coefficient <= 90; coefficient += 10 ) { - final LegalPlayer protectionBasedPlayer = whitePlayerGenerator.apply( coefficient ); + double doubleCoefficient = coefficient / 100.0; //normalizing + final LegalPlayer protectionBasedPlayer = whitePlayerGenerator.apply( doubleCoefficient ); final LegalPlayer classicPlayer = new LegalPlayer(); final SimulatorStatistics stats = new Simulator( protectionBasedPlayer, classicPlayer ) .gamePairs( gamePairsPerIteration ).run(); - statisticsMap.put( coefficient, stats ); + statisticsMap.put( doubleCoefficient, stats ); } printResults( statisticsMap ); } - private LegalPlayer createProtector( int coefficient ) { + private LegalPlayer createProtector( double coefficient ) { return new LegalPlayer( new MasterEvaluatorBuilder().weight( EvaluatorType.PROTECTION, coefficient ).build() ); } - private void printResults( Map statisticsMap ) { + private void printResults( Map statisticsMap ) { final String statsPrettyPrinted = statisticsMap.entrySet().stream() .map( entry -> entry.getKey() + " ==> " + entry.getValue() ) .collect( Collectors.joining( "\n" ) ); diff --git a/src/test/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeightsTest.java b/src/test/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeightsTest.java new file mode 100644 index 000000000..2e1ea6f78 --- /dev/null +++ b/src/test/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeightsTest.java @@ -0,0 +1,32 @@ +package com.leokom.chess.player.legal.brain.internal.common; + +import com.leokom.chess.player.legal.brain.common.EvaluatorType; +import org.junit.Test; +import org.mutabilitydetector.unittesting.MutabilityAssert; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class EvaluatorWeightsTest { + @Test + public void weightsAreImmutable() { + MutabilityAssert.assertImmutable( EvaluatorWeights.class ); + } + + @Test( expected = IllegalArgumentException.class ) + public void evaluatorWeightBigger1NotAccepted() { + Map weights = new HashMap<>(); + weights.put( EvaluatorType.PROTECTION, 1.1 ); + new EvaluatorWeights( weights ); + } + + + @Test( expected = IllegalArgumentException.class ) + public void evaluatorWeightLess0NotAccepted() { + Map weights = new HashMap<>(); + weights.put( EvaluatorType.PROTECTION, -0.1 ); + new EvaluatorWeights( weights ); + } +} \ No newline at end of file diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java index 81ce9068c..71543ba2b 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java @@ -13,7 +13,7 @@ * Date-time: 19.04.16 23:03 */ public class MasterEvaluatorBuilder { - private Map weights = EvaluatorWeights.getStandardWeights(); + private Map weights = new EvaluatorWeights().asMap(); public MasterEvaluatorBuilder weight( EvaluatorType evaluatorType, double weight ) { weights.put( evaluatorType, weight ); diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index 1d46c58c7..030813204 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -93,19 +93,4 @@ public void winningIsEvaluatedTo1() { Move checkmateMove = new Move( "d8", "h4" ); assertEquals( 1, evaluator.evaluateMove( position, checkmateMove ), 0 ); } - - @Test( expected = IllegalArgumentException.class ) - public void evaluatorWeightBigger1NotAccepted() { - Map weights = new HashMap<>(); - weights.put( EvaluatorType.PROTECTION, 1.1 ); - new MasterEvaluator( weights ); - } - - - @Test( expected = IllegalArgumentException.class ) - public void evaluatorWeightLess0NotAccepted() { - Map weights = new HashMap<>(); - weights.put( EvaluatorType.PROTECTION, -0.1 ); - new MasterEvaluator( weights ); - } } \ No newline at end of file From 833e8b47d3210713e24618034133dd5d72997bd8 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 20 Jan 2019 16:53:50 +0200 Subject: [PATCH 039/110] IT tests migrated to use [ 0, 1 ] constraint --- src/test/java/com/leokom/chess/SimulatorMultiRunnerIT.java | 2 +- .../player/legal/brain/normalized/MasterEvaluatorBuilder.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/leokom/chess/SimulatorMultiRunnerIT.java b/src/test/java/com/leokom/chess/SimulatorMultiRunnerIT.java index 9d7bde6b9..43f1471d3 100644 --- a/src/test/java/com/leokom/chess/SimulatorMultiRunnerIT.java +++ b/src/test/java/com/leokom/chess/SimulatorMultiRunnerIT.java @@ -50,7 +50,7 @@ public static Iterable data() { @Test public void runGamePair() { final Evaluator brainLikesToProtectItself = new MasterEvaluatorBuilder() - .weight( EvaluatorType.PROTECTION, 1000.0 ).build(); + .weight( EvaluatorType.PROTECTION, 1.0 ).build(); final SimulatorStatistics statistics = new Simulator( new LegalPlayer(), new LegalPlayer( brainLikesToProtectItself ) ) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java index 71543ba2b..3dd68ad66 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java @@ -3,6 +3,7 @@ import com.leokom.chess.player.legal.brain.common.EvaluatorType; import com.leokom.chess.player.legal.brain.internal.common.EvaluatorWeights; +import java.util.HashMap; import java.util.Map; /** @@ -13,7 +14,8 @@ * Date-time: 19.04.16 23:03 */ public class MasterEvaluatorBuilder { - private Map weights = new EvaluatorWeights().asMap(); + //wrapping to a hash map to allow mutability + private Map weights = new HashMap<>( new EvaluatorWeights().asMap() ); public MasterEvaluatorBuilder weight( EvaluatorType evaluatorType, double weight ) { weights.put( evaluatorType, weight ); From b08d609f93db7abe048014696fdf43d97a75e03e Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 20 Jan 2019 17:12:45 +0200 Subject: [PATCH 040/110] Fixed logging pattern --- src/main/resources/log4j2.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index b6e595a1a..c28fe541c 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,7 +2,7 @@ - + From ff8d7fa288979996c8930e58a7547ccc5a1e2e0d Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 20 Jan 2019 17:24:10 +0200 Subject: [PATCH 041/110] Integrationally verify logical behavior for checkmate --- .../NormalizedBrainMasterEvaluatorTest.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java index 7134cdc58..9a52f2d11 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainMasterEvaluatorTest.java @@ -1,12 +1,12 @@ package com.leokom.chess.player.legal.brain.normalized; -import com.leokom.chess.engine.Move; -import com.leokom.chess.engine.Position; +import com.leokom.chess.engine.*; import org.junit.Test; import java.util.List; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; //it's move integration than NormalizedBrainPositionMoveTest @@ -34,4 +34,18 @@ public void drawOfferIsNotTheBestResponse() { .findBestMove(Position.getInitialPosition().move( "e2", "e4" )); assertNotEquals( Move.OFFER_DRAW, bestMove.get(0) ); } + + @Test + public void winningMaterialNotBestIdeaIfCanCheckmate() { + Position position = new PositionBuilder() + .add(Side.BLACK, "a1", PieceType.KING) + .add(Side.WHITE, "c1", PieceType.KING) + .add(Side.WHITE, "c3", PieceType.ROOK) + .add(Side.BLACK, "h3", PieceType.BISHOP) + .build(); + + List result = new NormalizedBrain<>(new MasterEvaluator(), 2).findBestMove(position); + //we shouldn't try c3: h3 + assertEquals( new Move( "c3", "a3" ), result.get( 0 ) ); + } } From a5f4583012c6c1083d887461a201a1d33ed22c4f Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 20 Jan 2019 17:30:20 +0200 Subject: [PATCH 042/110] Added missing logging --- .../player/legal/brain/normalized/MasterEvaluator.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 1a2ec76fd..568936d77 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -43,7 +43,7 @@ public MasterEvaluator() { this( new EvaluatorWeights( weights ) ); } - MasterEvaluator( EvaluatorWeights evaluatorWeights ) { + private MasterEvaluator( EvaluatorWeights evaluatorWeights ) { this.evaluatorWeights = evaluatorWeights; } @@ -51,7 +51,9 @@ public MasterEvaluator() { public double evaluateMove( Position position, Move move ) { if ( position.move( move ).isTerminal() ) { //TODO: remove CheckmateEvaluator from further algorithm then - return new CheckmateEvaluator().evaluateMove( position, move ); + double result = new CheckmateEvaluator().evaluateMove(position, move); + LOG.info( "{} ===> {}", move, result ); + return result; } double result = evaluatorWeights.stream().mapToDouble(evaluatorEntry -> { @@ -64,7 +66,7 @@ public double evaluateMove( Position position, Move move ) { }).sum(); //result that is in [ 0, 1 ] range - //TODO: it should work assuming the weights themselves are in [ 0, 1 ] + //depends on the fact that the weights themselves are in [ 0, 1 ] double normalizedResult = result / evaluatorWeights.size(); LOG.info("{} ===> {} ===> {}", move, result, normalizedResult); From 47482989a3aff351a955ad5a7afe995f4b59ff5b Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 22 Jan 2019 22:16:10 +0200 Subject: [PATCH 043/110] Better logging: show the intermediate results --- .../chess/player/legal/brain/normalized/NormalizedBrain.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 222e4c4a9..eb1651dba 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -97,6 +97,7 @@ public List findBestMove(StateType position ) { moveRatings.put( move, brains.evaluateMove( position, move ) - 1 ); //falling back to 1'st level } + LogManager.getLogger().info( "result = {}", moveRatings.get( move ) ); ThreadContext.clearAll(); } ); } From b4547a08fcde14f006f18b3904b9911c10531284 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 22 Jan 2019 22:42:29 +0200 Subject: [PATCH 044/110] Checkmate evaluator moved to normalized package. --- .../legal/brain/denormalized/DenormalizedEvaluatorFactory.java | 1 + .../brain/{denormalized => normalized}/CheckmateEvaluator.java | 3 +-- .../chess/player/legal/brain/normalized/MasterEvaluator.java | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) rename src/main/java/com/leokom/chess/player/legal/brain/{denormalized => normalized}/CheckmateEvaluator.java (89%) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedEvaluatorFactory.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedEvaluatorFactory.java index bf5f44b21..0b04599c5 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedEvaluatorFactory.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedEvaluatorFactory.java @@ -4,6 +4,7 @@ import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.common.EvaluatorFactory; import com.leokom.chess.player.legal.brain.common.EvaluatorType; +import com.leokom.chess.player.legal.brain.normalized.CheckmateEvaluator; import java.util.EnumMap; import java.util.Map; diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/CheckmateEvaluator.java similarity index 89% rename from src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java rename to src/main/java/com/leokom/chess/player/legal/brain/normalized/CheckmateEvaluator.java index 44b274c88..a40a6d2e8 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/CheckmateEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/CheckmateEvaluator.java @@ -1,4 +1,4 @@ -package com.leokom.chess.player.legal.brain.denormalized; +package com.leokom.chess.player.legal.brain.normalized; import com.leokom.chess.engine.Move; import com.leokom.chess.engine.Position; @@ -10,7 +10,6 @@ * * Checkmate is the highest goal of the whole game */ -//TODO: move to normalized package public class CheckmateEvaluator implements Evaluator { private static final int BEST_MOVE = 1; diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 568936d77..c0d30390d 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -4,7 +4,6 @@ import com.leokom.chess.engine.Position; import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.common.EvaluatorType; -import com.leokom.chess.player.legal.brain.denormalized.CheckmateEvaluator; import com.leokom.chess.player.legal.brain.internal.common.EvaluatorWeights; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; From 40587ffedd2e1556dc48d4f7d053c96d706a9561 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 24 Jan 2019 22:09:03 +0200 Subject: [PATCH 045/110] Validate legal depth --- .../legal/brain/normalized/NormalizedBrain.java | 12 ++++++++++++ .../legal/brain/normalized/NormalizedBrainTest.java | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index eb1651dba..b425a564c 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -25,6 +25,10 @@ * Date-time: 23.08.16 22:54 */ public class NormalizedBrain < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> implements GenericBrain< StateType, TransitionType > { + //this constant will increase with chess evolution + private static final int MAXIMAL_SUPPORTED_DEPTH = 2; + //this is an absolute constant + private static final int MINIMAL_POSSIBLE_DEPTH = 1; private final GenericEvaluator< StateType, TransitionType > brains; private final int pliesDepth; @@ -42,6 +46,14 @@ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { * @param pliesDepth depth to think */ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, int pliesDepth ) { + if ( pliesDepth < MINIMAL_POSSIBLE_DEPTH) { + throw new IllegalArgumentException( String.format( "This depth is wrong: %s", pliesDepth ) ); + } + + if ( pliesDepth > MAXIMAL_SUPPORTED_DEPTH) { + throw new IllegalArgumentException( String.format( "This depth is not supported yet: %s", pliesDepth ) ); + } + this.brains = brains; this.pliesDepth = pliesDepth; } diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 221088f4c..61d1e13d1 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -125,4 +125,14 @@ public void movesLeadingToTerminalBetterToSelect() { assertEquals( 1, bestMove.size() ); assertEquals( 100, bestMove.get(0).getId() ); } + + @Test( expected = IllegalArgumentException.class) + public void depthMore2NotSupported() { + new NormalizedBrain( (state, transition) -> transition.getId(), 3 ); + } + + @Test( expected = IllegalArgumentException.class) + public void depthLess1NotSupported() { + new NormalizedBrain( (state, transition) -> transition.getId(), 0 ); + } } \ No newline at end of file From 48b911064274f1571e0a9e9b404494c36a9ed27a Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sat, 26 Jan 2019 22:28:38 +0200 Subject: [PATCH 046/110] Basic validation for Master evaluator range. NOTE: it may be a breaking change for DenormalizedBrain --- .../brain/normalized/NormalizedBrain.java | 14 ++++++-- .../brain/normalized/NormalizedBrainTest.java | 32 +++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index b425a564c..84216716b 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -32,6 +32,10 @@ public class NormalizedBrain < StateType extends GameState< TransitionType, Stat private final GenericEvaluator< StateType, TransitionType > brains; private final int pliesDepth; + /** + * Create normalized brain + * @param brains evaluator with results in [ 0, 1 ] range + */ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { this( brains, 1 ); } @@ -42,7 +46,7 @@ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { * It can still be kept. * The alternative could be: stable evaluator that returns positive/negative result depending on color of the side to move * - * @param brains evauator + * @param brains evaluator with results in [ 0, 1 ] range * @param pliesDepth depth to think */ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, int pliesDepth ) { @@ -86,7 +90,13 @@ public List findBestMove(StateType position ) { //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches getMovesWithoutDrawOffer(position).forEach(move -> - moveRatings.put(move, brains.evaluateMove(position, move)) + { + double value = brains.evaluateMove(position, move); + if ( value < 0.0 || value > 1.0 ) { + throw new IllegalArgumentException( String.format( "The value is outside of supported range: %s", value ) ); + } + moveRatings.put(move, value); + } ); } else { //just 2 is supported now diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index 61d1e13d1..cbddb946b 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -2,6 +2,7 @@ import com.leokom.chess.engine.GameStateImpl; import com.leokom.chess.engine.GameTransitionImpl; +import com.leokom.chess.player.legal.brain.common.GenericEvaluator; import org.junit.Test; import java.util.List; @@ -33,9 +34,7 @@ public void betterMoveFound() { GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(12), new GameStateImpl(), new GameTransitionImpl( 20 ), new GameStateImpl() ); - List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( - (state, transition) -> transition.getId() // just a simple evaluation - let's say bigger id is better - ).findBestMove(gameState); + List result = new NormalizedBrain<>(getSimpleIdEvaluator()).findBestMove(gameState); assertEquals( 1, result.size() ); assertEquals( 20, result.get(0).getId() ); } @@ -47,9 +46,7 @@ public void singlePlyThinkingIsLimited() { new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl() ), new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 100 ), new GameStateImpl() ) ); // bigger means better for the opponent - List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( - (state, transition) -> transition.getId() // just a simple evaluation - let's say bigger id is better - ).findBestMove(gameState); + List result = new NormalizedBrain<>( getSimpleIdEvaluator()).findBestMove(gameState); assertEquals( 1, result.size() ); assertEquals( 20, result.get(0).getId() ); } @@ -61,14 +58,16 @@ public void secondPlyThinkingMustSuggestBetterMove() { new GameTransitionImpl(12), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl() ), new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 100 ), new GameStateImpl() ) ); - List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( - (state, transition) -> transition.getId(), // just a simple evaluation - let's say bigger id is better - 2 - ).findBestMove(gameState); + List result = new NormalizedBrain<>( getSimpleIdEvaluator(),2).findBestMove(gameState); assertEquals( 1, result.size() ); assertEquals( 12, result.get(0).getId() ); } + // just a simple evaluation - let's say bigger id is better + private GenericEvaluator getSimpleIdEvaluator() { + return (state, transition) -> transition.getId() / 100.0; + } + @Test public void ifOpponentCanSelectCoolMoveDetectThat() { GameStateImpl gameState = new GameStateImpl( @@ -77,10 +76,7 @@ public void ifOpponentCanSelectCoolMoveDetectThat() { //here he can execute a cool and a bad move. Thinking about him in positive way - that he'll select the best one new GameTransitionImpl( 20 ), new GameStateImpl( new GameTransitionImpl( 0 ), new GameStateImpl(), new GameTransitionImpl( 100 ), new GameStateImpl() ) ); - List result = new NormalizedBrain< GameStateImpl, GameTransitionImpl >( - (state, transition) -> transition.getId(), // just a simple evaluation - let's say bigger id is better - 2 - ).findBestMove(gameState); + List result = new NormalizedBrain<>( getSimpleIdEvaluator(),2 ).findBestMove(gameState); assertEquals( 1, result.size() ); assertEquals( 12, result.get(0).getId() ); } @@ -135,4 +131,12 @@ public void depthMore2NotSupported() { public void depthLess1NotSupported() { new NormalizedBrain( (state, transition) -> transition.getId(), 0 ); } + + //if evaluator is not providing correct range for the move, we should throw an exception + //TODO: it's semantically questionable, the evaluator was passed in constructor and we'll throw this from the method + @Test( expected = IllegalArgumentException.class) + public void evaluatorWithWrongResultMustBeDetected() { + new NormalizedBrain( ( state, transition ) -> 1.1 ) + .findBestMove( new GameStateImpl( new GameTransitionImpl(1), new GameStateImpl() ) ); + } } \ No newline at end of file From 1109ab5e173afcdd3e875622a8db8d99fc952627 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sat, 26 Jan 2019 22:38:41 +0200 Subject: [PATCH 047/110] Delegating validation to a separate class --- .../brain/normalized/NormalizedBrain.java | 12 +++------ .../ValidatingNormalizedEvaluator.java | 27 +++++++++++++++++++ .../brain/normalized/NormalizedBrainTest.java | 10 ++----- 3 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/leokom/chess/player/legal/brain/normalized/ValidatingNormalizedEvaluator.java diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 84216716b..c77900c60 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -58,7 +58,7 @@ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, in throw new IllegalArgumentException( String.format( "This depth is not supported yet: %s", pliesDepth ) ); } - this.brains = brains; + this.brains = new ValidatingNormalizedEvaluator<>( brains ); this.pliesDepth = pliesDepth; } @@ -89,14 +89,8 @@ public List findBestMove(StateType position ) { //this looks safe since Offer draw cannot be a single legal move in a position. //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches - getMovesWithoutDrawOffer(position).forEach(move -> - { - double value = brains.evaluateMove(position, move); - if ( value < 0.0 || value > 1.0 ) { - throw new IllegalArgumentException( String.format( "The value is outside of supported range: %s", value ) ); - } - moveRatings.put(move, value); - } + getMovesWithoutDrawOffer(position).forEach( move -> + moveRatings.put(move, brains.evaluateMove( position, move ) ) ); } else { //just 2 is supported now diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/ValidatingNormalizedEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/ValidatingNormalizedEvaluator.java new file mode 100644 index 000000000..ce0d98520 --- /dev/null +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/ValidatingNormalizedEvaluator.java @@ -0,0 +1,27 @@ +package com.leokom.chess.player.legal.brain.normalized; + +import com.leokom.chess.engine.GameState; +import com.leokom.chess.engine.GameTransition; +import com.leokom.chess.player.legal.brain.common.GenericEvaluator; + +/** + * Evaluator delegate that ensures [ 0, 1 ] constraint for the move + * @param + * @param + */ +class ValidatingNormalizedEvaluator < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> implements GenericEvaluator< StateType, TransitionType > { + private final GenericEvaluator delegate; + + ValidatingNormalizedEvaluator(GenericEvaluator delegate ) { + this.delegate = delegate; + } + + @Override + public double evaluateMove(StateType position, TransitionType move) { + double result = delegate.evaluateMove(position, move); + if ( result < 0.0 || result > 1.0 ) { + throw new IllegalArgumentException( String.format( "The value is outside of supported range: %s", result ) ); + } + return result; + } +} diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index cbddb946b..aabd28ff5 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -86,10 +86,7 @@ public void secondPlyThinkingNoCrashOnTerminalPosition() { GameStateImpl gameState = new GameStateImpl( new GameTransitionImpl(25 ), new GameStateImpl() //terminal ); - new NormalizedBrain< GameStateImpl, GameTransitionImpl >( - (state, transition) -> transition.getId(), - 2 - ).findBestMove(gameState); + new NormalizedBrain<>( getSimpleIdEvaluator(),2).findBestMove(gameState); } @Test @@ -113,10 +110,7 @@ public void movesLeadingToTerminalBetterToSelect() { new GameTransitionImpl(50 ), new GameStateImpl() ); - List bestMove = new NormalizedBrain( - (state, transition) -> transition.getId(), - 2 - ).findBestMove(gameState); + List bestMove = new NormalizedBrain<>( getSimpleIdEvaluator(),2 ).findBestMove(gameState); assertEquals( 1, bestMove.size() ); assertEquals( 100, bestMove.get(0).getId() ); From 6328ed634cac91700aa12dfb9e98dc2f58df66b2 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sat, 26 Jan 2019 23:06:18 +0200 Subject: [PATCH 048/110] Red test: shows violation of NormalizedBrain contract by call from DenormalizedBrain --- .../brain/denormalized/DenormalizedBrain.java | 13 +++++++++++-- .../denormalized/DenormalizedBrainTest.java | 17 +++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index 1482669c9..471a8d365 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -22,7 +22,16 @@ public class DenormalizedBrain implements Brain { private static final Logger LOG = LogManager.getLogger(); private static final double DEFAULT_FOR_EQUAL_NOT_IN_RANGE = 0.5; - private EvaluatorFactory evaluatorFactory = new DenormalizedEvaluatorFactory(); + private final EvaluatorFactory evaluatorFactory = new DenormalizedEvaluatorFactory(); + private final EvaluatorWeights evaluatorWeights; + + public DenormalizedBrain() { + this( new EvaluatorWeights() ); + } + + DenormalizedBrain( EvaluatorWeights evaluatorWeights ) { + this.evaluatorWeights = evaluatorWeights; + } @Override public List< Move > findBestMove( Position position ) { @@ -57,7 +66,7 @@ private void logTable( Table weightedTable, String } private Table generateWithWeights( Table normalizedTable ) { - final Map standardWeights = new EvaluatorWeights().asMap(); + final Map standardWeights = evaluatorWeights.asMap(); Table< EvaluatorType, Move, Double > result = HashBasedTable.create(); diff --git a/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java index e872bc042..9dab75b8c 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrainTest.java @@ -2,13 +2,18 @@ import com.leokom.chess.engine.*; import com.leokom.chess.player.legal.brain.common.Brain; +import com.leokom.chess.player.legal.brain.common.EvaluatorType; +import com.leokom.chess.player.legal.brain.internal.common.EvaluatorWeights; import org.junit.Before; import org.junit.Test; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.*; /** * Author: Leonid @@ -49,4 +54,12 @@ public void beSmartALittle() { assertFalse( movesFound.isEmpty() ); assertEquals( captureIsSmartest, movesFound.get( 0 ) ); } + + @Test + public void nonStandardEvaluatorWeightsMustNotCrash() { + //maximal legal 1.0 weight set up + Map weights = Arrays.stream(EvaluatorType.values()).collect(Collectors.toMap(Function.identity(), type -> 1.0)); + EvaluatorWeights evaluatorWeights = new EvaluatorWeights(weights); + assertNotNull( new DenormalizedBrain( evaluatorWeights ).findBestMove( Position.getInitialPosition() ) ); + } } \ No newline at end of file From 8e1a2d7c7d772bfe155c1d2ff5186424fe63e060 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 28 Jan 2019 21:45:55 +0200 Subject: [PATCH 049/110] Make sure denormalized brain supplies correct input for normalized one. --- .../player/legal/brain/denormalized/DenormalizedBrain.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index 471a8d365..4431061cc 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -54,9 +54,13 @@ public List< Move > findBestMove( Position position ) { } private Evaluator getEvaluator(Table weightedTable) { + //the division by evaluators count is done to ensure that the result will be in [ 0, 1 ] measures which is needed for correct input to NormalizedBrain + //this division is very similar to that one done in MasterEvaluator + //for example if we had 2 evaluators with result eval1: 1, eval2: 0.5 plain sum would be 1.5 but the normalized one is 0.75 return ( position, move ) -> //summing without converting to DoubleStream http://stackoverflow.com/q/24421140 - weightedTable.column( move ).values().stream().reduce( 0.0, Double::sum ); + weightedTable.column( move ).values().stream().reduce( 0.0, Double::sum ) / + evaluatorWeights.size(); } private void logTable( Table weightedTable, String prefix ) { From 42b643ee33f9c5edb350e83a7181aca50fc7239b Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 28 Jan 2019 21:54:44 +0200 Subject: [PATCH 050/110] Refactoring: introduced concept similar to MasterEvaluator. --- .../brain/denormalized/DenormalizedBrain.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index 4431061cc..3306d7eef 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -50,17 +50,7 @@ public List< Move > findBestMove( Position position ) { Table weightedTable = generateWithWeights( normalizedTable ); logTable( weightedTable, "WEIGHTED" ); - return new NormalizedBrain<>( getEvaluator( weightedTable ) ).findBestMove( position ); - } - - private Evaluator getEvaluator(Table weightedTable) { - //the division by evaluators count is done to ensure that the result will be in [ 0, 1 ] measures which is needed for correct input to NormalizedBrain - //this division is very similar to that one done in MasterEvaluator - //for example if we had 2 evaluators with result eval1: 1, eval2: 0.5 plain sum would be 1.5 but the normalized one is 0.75 - return ( position, move ) -> - //summing without converting to DoubleStream http://stackoverflow.com/q/24421140 - weightedTable.column( move ).values().stream().reduce( 0.0, Double::sum ) / - evaluatorWeights.size(); + return new NormalizedBrain<>( new DenormalizedMasterEvaluator( weightedTable ) ).findBestMove( position ); } private void logTable( Table weightedTable, String prefix ) { @@ -147,4 +137,22 @@ private double standardizedValueFormula( double maxValue, double minValue, Doubl private interface Formula { double accept( double maxValue, double minValue, Double value ); } + + private class DenormalizedMasterEvaluator implements Evaluator { + private final Table weightedTable; + + DenormalizedMasterEvaluator(Table weightedTable) { + this.weightedTable = weightedTable; + } + + @Override + public double evaluateMove(Position position, Move move) { + //the division by evaluators count is done to ensure that the result will be in [ 0, 1 ] measures which is needed for correct input to NormalizedBrain + //this division is very similar to that one done in MasterEvaluator + //for example if we had 2 evaluators with result eval1: 1, eval2: 0.5 plain sum would be 1.5 but the normalized one is 0.75 + + return weightedTable.column(move).values().stream().reduce(0.0, Double::sum) / + evaluatorWeights.size(); + } + } } From 7115faaba9c17bf93c9c21576035505276879846 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 28 Jan 2019 22:15:25 +0200 Subject: [PATCH 051/110] Useful logging during simulation --- src/test/java/com/leokom/chess/Simulator.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/leokom/chess/Simulator.java b/src/test/java/com/leokom/chess/Simulator.java index 87970577b..92126fce5 100644 --- a/src/test/java/com/leokom/chess/Simulator.java +++ b/src/test/java/com/leokom/chess/Simulator.java @@ -1,6 +1,7 @@ package com.leokom.chess; import com.leokom.chess.player.Player; +import org.apache.logging.log4j.LogManager; import java.util.ArrayList; import java.util.List; @@ -43,6 +44,7 @@ class Simulator { * @return statistics about game results */ SimulatorStatistics run() { + LogManager.getLogger().info("Starting simulation for {} and {}", first.name(), second.name()); List< Player > winners = new ArrayList<>(); IntStream.rangeClosed( 1, timesToRun ).forEach( iteration -> { winners.add( createGame( first, second ).run() ); @@ -52,7 +54,9 @@ SimulatorStatistics run() { final long firstWins = countWinsOf( winners, first ); final long secondWins = countWinsOf( winners, second ); final long totalGames = timesToRun * GAMES_IN_SINGLE_ITERATION; - return new SimulatorStatistics( totalGames, firstWins, secondWins ); + SimulatorStatistics simulatorStatistics = new SimulatorStatistics(totalGames, firstWins, secondWins); + LogManager.getLogger().info("The simulation has been finished. Stats: {}", simulatorStatistics); + return simulatorStatistics; } private static long countWinsOf( List< Player > winners, Player player ) { From 8faee2bf2c6b1c4f1c8557d95d6afa9f0b806632 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 29 Jan 2019 22:58:20 +0200 Subject: [PATCH 052/110] Normalized brain mentions its depth --- .../legal/brain/normalized/NormalizedBrain.java | 5 +++++ .../chess/player/legal/LegalPlayerNameTest.java | 2 +- .../brain/normalized/NormalizedBrainTest.java | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index c77900c60..8e745f603 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -121,6 +121,11 @@ public List findBestMove(StateType position ) { return getMoveWithMaxRating(moveRatings); } + @Override + public String name() { + return String.format( "NormalizedBrain: %s depth", pliesDepth ); + } + private Stream getMovesWithoutDrawOffer(StateType position) { return position.getMoves().stream().filter(move -> move != Move.OFFER_DRAW); } diff --git a/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java b/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java index 8aec7ad7c..95ba86083 100644 --- a/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java +++ b/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java @@ -16,7 +16,7 @@ public void denormalizedBrain() { @Test public void normalizedBrain() { - assertEquals( "LegalPlayer : NormalizedBrain", new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator()) ).name() ); + assertEquals( "LegalPlayer : NormalizedBrain: 1 depth", new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator()) ).name() ); } @Test diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java index aabd28ff5..a14d7497c 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrainTest.java @@ -3,12 +3,12 @@ import com.leokom.chess.engine.GameStateImpl; import com.leokom.chess.engine.GameTransitionImpl; import com.leokom.chess.player.legal.brain.common.GenericEvaluator; +import org.hamcrest.CoreMatchers; import org.junit.Test; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class NormalizedBrainTest { @Test @@ -133,4 +133,16 @@ public void evaluatorWithWrongResultMustBeDetected() { new NormalizedBrain( ( state, transition ) -> 1.1 ) .findBestMove( new GameStateImpl( new GameTransitionImpl(1), new GameStateImpl() ) ); } + + @Test + public void brainNameRespectsDepthOne() { + assertThat( new NormalizedBrain< GameStateImpl, GameTransitionImpl >( + ( state, transition ) -> 0.0, 1 ).name(), CoreMatchers.containsString( "1" )); + } + + @Test + public void brainNameRespectsDepthTwo() { + assertThat( new NormalizedBrain< GameStateImpl, GameTransitionImpl >( + ( state, transition ) -> 0.5, 2 ).name(), CoreMatchers.containsString( "2" )); + } } \ No newline at end of file From 87612fe9c28f6ca4734f8c22d97333cdec4a0f54 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 30 Jan 2019 22:06:30 +0200 Subject: [PATCH 053/110] CheckmateEvaluator rebranded to TerminalEvaluator. --- .../chess/player/legal/brain/common/EvaluatorType.java | 2 +- .../brain/denormalized/DenormalizedEvaluatorFactory.java | 4 ++-- .../player/legal/brain/internal/common/EvaluatorWeights.java | 2 +- .../chess/player/legal/brain/normalized/MasterEvaluator.java | 4 ++-- .../{CheckmateEvaluator.java => TerminalEvaluator.java} | 5 +++-- ...heckmateEvaluatorTest.java => TerminalEvaluatorTest.java} | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) rename src/main/java/com/leokom/chess/player/legal/brain/normalized/{CheckmateEvaluator.java => TerminalEvaluator.java} (83%) rename src/test/java/com/leokom/chess/player/legal/brain/common/{CheckmateEvaluatorTest.java => TerminalEvaluatorTest.java} (95%) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/EvaluatorType.java b/src/main/java/com/leokom/chess/player/legal/brain/common/EvaluatorType.java index 06e129910..44171ee56 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/EvaluatorType.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/EvaluatorType.java @@ -8,7 +8,7 @@ * Date-time: 19.04.16 22:46 */ public enum EvaluatorType { - CHECKMATE, + TERMINAL, CASTLING_SAFETY, CENTER_CONTROL, MATERIAL, diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedEvaluatorFactory.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedEvaluatorFactory.java index 0b04599c5..9e0f2e4b5 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedEvaluatorFactory.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedEvaluatorFactory.java @@ -4,7 +4,7 @@ import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.common.EvaluatorFactory; import com.leokom.chess.player.legal.brain.common.EvaluatorType; -import com.leokom.chess.player.legal.brain.normalized.CheckmateEvaluator; +import com.leokom.chess.player.legal.brain.normalized.TerminalEvaluator; import java.util.EnumMap; import java.util.Map; @@ -24,7 +24,7 @@ public class DenormalizedEvaluatorFactory implements EvaluatorFactory { evaluatorsMutable.put( EvaluatorType.ATTACK, new AttackEvaluator() ); evaluatorsMutable.put( EvaluatorType.CASTLING_SAFETY, new CastlingSafetyEvaluator() ); evaluatorsMutable.put( EvaluatorType.CENTER_CONTROL, new CenterControlEvaluator() ); - evaluatorsMutable.put( EvaluatorType.CHECKMATE, new CheckmateEvaluator() ); + evaluatorsMutable.put( EvaluatorType.TERMINAL, new TerminalEvaluator() ); evaluatorsMutable.put( EvaluatorType.MATERIAL, new MaterialEvaluator() ); evaluatorsMutable.put( EvaluatorType.MOBILITY, new MobilityEvaluator() ); evaluatorsMutable.put( EvaluatorType.PROTECTION, new ProtectionEvaluator() ); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java index 9f748b5b7..7f7753e65 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java @@ -44,7 +44,7 @@ private void verifyRange(Map weights) { private static Map getStandardWeights() { //TODO: refactor to constant immutable map Map result = new HashMap<>(); - result.put( CHECKMATE, HIGHEST_PRIORITY ); + result.put( TERMINAL, HIGHEST_PRIORITY ); result.put( CASTLING_SAFETY, NORMAL_PRIORITY ); result.put( CENTER_CONTROL, NORMAL_PRIORITY ); result.put( MOBILITY, NORMAL_PRIORITY ); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index c0d30390d..28ee0c782 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -49,8 +49,8 @@ private MasterEvaluator( EvaluatorWeights evaluatorWeights ) { @Override public double evaluateMove( Position position, Move move ) { if ( position.move( move ).isTerminal() ) { - //TODO: remove CheckmateEvaluator from further algorithm then - double result = new CheckmateEvaluator().evaluateMove(position, move); + //TODO: remove TerminalEvaluator from further algorithm then + double result = new TerminalEvaluator().evaluateMove(position, move); LOG.info( "{} ===> {}", move, result ); return result; } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/CheckmateEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java similarity index 83% rename from src/main/java/com/leokom/chess/player/legal/brain/normalized/CheckmateEvaluator.java rename to src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java index a40a6d2e8..59501df66 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/CheckmateEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java @@ -8,9 +8,10 @@ * Author: Leonid * Date-time: 01.03.15 22:32 * - * Checkmate is the highest goal of the whole game + * Evaluate final game state. + * Checkmate is the highest goal of the whole game and will mean win. */ -public class CheckmateEvaluator implements Evaluator { +public class TerminalEvaluator implements Evaluator { private static final int BEST_MOVE = 1; private static final int WORST_MOVE = 0; diff --git a/src/test/java/com/leokom/chess/player/legal/brain/common/CheckmateEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/common/TerminalEvaluatorTest.java similarity index 95% rename from src/test/java/com/leokom/chess/player/legal/brain/common/CheckmateEvaluatorTest.java rename to src/test/java/com/leokom/chess/player/legal/brain/common/TerminalEvaluatorTest.java index 9a62b07ec..6b1c7d469 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/common/CheckmateEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/common/TerminalEvaluatorTest.java @@ -7,7 +7,7 @@ * Author: Leonid * Date-time: 01.03.15 22:31 */ -public class CheckmateEvaluatorTest extends EvaluatorTestCase { +public class TerminalEvaluatorTest extends EvaluatorTestCase { @Test public void checkmateBetterThanNot() { PositionBuilder builder = new PositionBuilder() @@ -68,6 +68,6 @@ public void drawClaimIsWorseThanCheckmate() { @Override EvaluatorType getEvaluatorType() { - return EvaluatorType.CHECKMATE; + return EvaluatorType.TERMINAL; } } From 6db71c35fd9021a317585948bdd078d6daff4b7b Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 30 Jan 2019 22:19:04 +0200 Subject: [PATCH 054/110] Red test for logical assumption about draws handling. Fixed bug in test infrastructure: EvaluatorAsserts processed special moves incorrectly. --- .../java/com/leokom/chess/engine/PositionBuilder.java | 4 ++++ .../player/legal/brain/common/EvaluatorAsserts.java | 8 +++++++- .../legal/brain/common/TerminalEvaluatorTest.java | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/engine/PositionBuilder.java b/src/main/java/com/leokom/chess/engine/PositionBuilder.java index 2e1ee34e8..6452f8de8 100644 --- a/src/main/java/com/leokom/chess/engine/PositionBuilder.java +++ b/src/main/java/com/leokom/chess/engine/PositionBuilder.java @@ -38,6 +38,10 @@ public PositionBuilder setSide( Side side ) { return this; } + public Side getSideToMove() { + return position.getSideToMove(); + } + public Position build() { return position; } diff --git a/src/test/java/com/leokom/chess/player/legal/brain/common/EvaluatorAsserts.java b/src/test/java/com/leokom/chess/player/legal/brain/common/EvaluatorAsserts.java index 2e410bdc1..67537b072 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/common/EvaluatorAsserts.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/common/EvaluatorAsserts.java @@ -17,7 +17,13 @@ public EvaluatorAsserts( Evaluator evaluator ) { } public void assertFirstBetter( PositionBuilder position, Move expectedBetter, Move expectedWorse ) { - assertFirstBetter( position.setSideOf( expectedBetter.getFrom() ).build(), expectedBetter, expectedWorse ); + if ( ! expectedBetter.isSpecial() ) { + position.setSideOf( expectedBetter.getFrom() ); + } + else if ( position.getSideToMove() == null ) { + throw new IllegalArgumentException( "We are unable autodetect the side to move from a special move, you should have set up it in the builder" ); + } + assertFirstBetter( position.build(), expectedBetter, expectedWorse ); } public void assertFirstBetter( Position position, Move expectedBetter, Move expectedWorse ) { diff --git a/src/test/java/com/leokom/chess/player/legal/brain/common/TerminalEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/common/TerminalEvaluatorTest.java index 6b1c7d469..ace28cc5c 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/common/TerminalEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/common/TerminalEvaluatorTest.java @@ -66,6 +66,17 @@ public void drawClaimIsWorseThanCheckmate() { asserts.assertFirstBetter( builder, checkmateMove, claimDrawMove ); } + @Test + public void drawClaimIsBetterThanResign() { + PositionBuilder builder = new PositionBuilder() + .add( Side.WHITE, "c1", PieceType.ROOK ) + .add( Side.WHITE, "b7", PieceType.ROOK ) + .add( Side.BLACK, "h8", PieceType.KING ); + + + asserts.assertFirstBetter( builder, Move.CLAIM_DRAW, Move.RESIGN ); + } + @Override EvaluatorType getEvaluatorType() { return EvaluatorType.TERMINAL; From 05fa6ef1506927f81dd1cf559efbb2ae177c195b Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 30 Jan 2019 22:23:39 +0200 Subject: [PATCH 055/110] Terminal evaluator respects draws. --- .../brain/normalized/TerminalEvaluator.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java index 59501df66..f12941afe 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java @@ -13,8 +13,9 @@ */ public class TerminalEvaluator implements Evaluator { - private static final int BEST_MOVE = 1; - private static final int WORST_MOVE = 0; + private static final double BEST_MOVE = 1; + private static final double AVERAGE_MOVE = 0.5; + private static final double WORST_MOVE = 0; /** * @@ -23,11 +24,18 @@ public class TerminalEvaluator implements Evaluator { */ @Override public double evaluateMove( Position position, Move move ) { - //TODO: what about accepted draw and other terminal conditions? final Position result = position.move( move ); - return result.isTerminal() && - result.getWinningSide() != null && //this excludes draws - position.getSide( move.getFrom() ) == result.getWinningSide() ? - BEST_MOVE : WORST_MOVE; + + if ( !result.isTerminal() ) { + //TODO: maybe throw an exception if we allow calling just in terminal cases + return WORST_MOVE; + } + + //assuming it's draw + if ( result.getWinningSide() == null ) { + return AVERAGE_MOVE; + } + + return position.getSide( move.getFrom() ) == result.getWinningSide() ? BEST_MOVE : WORST_MOVE; } } From 7d4316e7b505e7893a10eb85dd8d5950783ecbe9 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 30 Jan 2019 22:27:09 +0200 Subject: [PATCH 056/110] Refactored to Evaluator Factory --- .../player/legal/brain/normalized/MasterEvaluator.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 28ee0c782..0beae4d42 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -23,8 +23,9 @@ public class MasterEvaluator implements Evaluator { //compare 1-to-another logic private final EvaluatorWeights evaluatorWeights; + private final NormalizedEvaluatorFactory evaluatorFactory; - public MasterEvaluator() { + public MasterEvaluator() { //standard weights this( new EvaluatorWeights() ); } @@ -44,19 +45,20 @@ public MasterEvaluator() { private MasterEvaluator( EvaluatorWeights evaluatorWeights ) { this.evaluatorWeights = evaluatorWeights; + this.evaluatorFactory = new NormalizedEvaluatorFactory(); } @Override public double evaluateMove( Position position, Move move ) { if ( position.move( move ).isTerminal() ) { //TODO: remove TerminalEvaluator from further algorithm then - double result = new TerminalEvaluator().evaluateMove(position, move); + double result = evaluatorFactory.get( EvaluatorType.TERMINAL ).evaluateMove(position, move); LOG.info( "{} ===> {}", move, result ); return result; } double result = evaluatorWeights.stream().mapToDouble(evaluatorEntry -> { - final Evaluator evaluator = new NormalizedEvaluatorFactory().get(evaluatorEntry.getKey()); + final Evaluator evaluator = evaluatorFactory.get(evaluatorEntry.getKey()); final double weight = evaluatorEntry.getValue(); final double evaluatorResponse = evaluator.evaluateMove(position, move); final double moveEstimate = weight * evaluatorResponse; From 882a6791a34cd56b167d4143723aec0d95801587 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 31 Jan 2019 22:06:27 +0200 Subject: [PATCH 057/110] Weird duplicates from iml removed --- Chess.iml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Chess.iml b/Chess.iml index 3103b5f28..a1d78f435 100644 --- a/Chess.iml +++ b/Chess.iml @@ -13,15 +13,6 @@ - - - - - - - - - From ffd8a2dd5503cdf3097438ec04ea968679d44322 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 31 Jan 2019 22:10:28 +0200 Subject: [PATCH 058/110] Explanation why we validate range in Evaluator weights --- .../player/legal/brain/internal/common/EvaluatorWeights.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java index 7f7753e65..849a1b736 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java @@ -35,6 +35,8 @@ public EvaluatorWeights(Map weights) { this.weights = Collections.unmodifiableMap( new HashMap<>( weights ) ); } + //alternative would be: normalize during processing + //decided not to do that for simplicity private void verifyRange(Map weights) { if ( weights.values().stream().anyMatch( weight -> weight < 0.0 || weight > 1.0 ) ) { throw new IllegalArgumentException( String.format( "Illegal weight outside of allowed range detected. Map: %s", weights ) ); From d6ed6ffb8f1e624483ea7ac581e31ab5f26f2832 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 31 Jan 2019 22:20:02 +0200 Subject: [PATCH 059/110] Comments updated --- .../player/legal/brain/normalized/NormalizedBrain.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 8e745f603..c89633428 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -18,8 +18,8 @@ * Initial decision maker. * * Historically it was based on MasterEvaluator. - * Now it has become generic (actually even not depending on chess-related notions)/ - * You can inject any custom brains via constructor. + * Now it has become generic (actually even not depending on chess-related notions). + * You can inject any custom evaluator that acts as a normalized one via constructor. * * Author: Leonid * Date-time: 23.08.16 22:54 @@ -108,8 +108,8 @@ public List findBestMove(StateType position ) { } else { LogManager.getLogger().info( "Evaluating just the current level" ); //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range - // highly depends on MasterEvaluator [ 0, 1 ]! //where all the second level moves exist + // highly depends on evaluator range [ 0, 1 ] which is guaranteed by ValidatingNormalizedEvaluator moveRatings.put( move, brains.evaluateMove( position, move ) - 1 ); //falling back to 1'st level } From 530fd567a1dc17c0595326829cceeabac1853a98 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 31 Jan 2019 22:23:22 +0200 Subject: [PATCH 060/110] Refactoring: symmetry --- .../player/legal/brain/denormalized/DenormalizedBrain.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index 3306d7eef..d1946166b 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -22,15 +22,16 @@ public class DenormalizedBrain implements Brain { private static final Logger LOG = LogManager.getLogger(); private static final double DEFAULT_FOR_EQUAL_NOT_IN_RANGE = 0.5; - private final EvaluatorFactory evaluatorFactory = new DenormalizedEvaluatorFactory(); + private final EvaluatorFactory evaluatorFactory; private final EvaluatorWeights evaluatorWeights; public DenormalizedBrain() { - this( new EvaluatorWeights() ); + this( new EvaluatorWeights(), new DenormalizedEvaluatorFactory() ); } - DenormalizedBrain( EvaluatorWeights evaluatorWeights ) { + private DenormalizedBrain( EvaluatorWeights evaluatorWeights, EvaluatorFactory evaluatorFactory ) { this.evaluatorWeights = evaluatorWeights; + this.evaluatorFactory = evaluatorFactory; } @Override From 504683ebf024cdc9479011c7cae41f1731609321 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 31 Jan 2019 22:36:02 +0200 Subject: [PATCH 061/110] Rollback --- .../player/legal/brain/denormalized/DenormalizedBrain.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index d1946166b..3306d7eef 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -22,16 +22,15 @@ public class DenormalizedBrain implements Brain { private static final Logger LOG = LogManager.getLogger(); private static final double DEFAULT_FOR_EQUAL_NOT_IN_RANGE = 0.5; - private final EvaluatorFactory evaluatorFactory; + private final EvaluatorFactory evaluatorFactory = new DenormalizedEvaluatorFactory(); private final EvaluatorWeights evaluatorWeights; public DenormalizedBrain() { - this( new EvaluatorWeights(), new DenormalizedEvaluatorFactory() ); + this( new EvaluatorWeights() ); } - private DenormalizedBrain( EvaluatorWeights evaluatorWeights, EvaluatorFactory evaluatorFactory ) { + DenormalizedBrain( EvaluatorWeights evaluatorWeights ) { this.evaluatorWeights = evaluatorWeights; - this.evaluatorFactory = evaluatorFactory; } @Override From 9ed32ef0f2428d0561f2330daf3b13dd5e21fc9c Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 31 Jan 2019 22:37:35 +0200 Subject: [PATCH 062/110] Master evaluator doesn't use Terminal evaluator at next stages. --- .../player/legal/brain/normalized/MasterEvaluator.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 0beae4d42..9de5b4cc2 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -51,13 +51,16 @@ private MasterEvaluator( EvaluatorWeights evaluatorWeights ) { @Override public double evaluateMove( Position position, Move move ) { if ( position.move( move ).isTerminal() ) { - //TODO: remove TerminalEvaluator from further algorithm then double result = evaluatorFactory.get( EvaluatorType.TERMINAL ).evaluateMove(position, move); LOG.info( "{} ===> {}", move, result ); return result; } - double result = evaluatorWeights.stream().mapToDouble(evaluatorEntry -> { + // Terminal evaluator excluded because it's used above. + // NOTE: it's still in evaluatorWeights until DenormalizedBrain uses it + double result = evaluatorWeights.stream().filter( evaluatorEntry -> + evaluatorEntry.getKey() != EvaluatorType.TERMINAL + ).mapToDouble(evaluatorEntry -> { final Evaluator evaluator = evaluatorFactory.get(evaluatorEntry.getKey()); final double weight = evaluatorEntry.getValue(); final double evaluatorResponse = evaluator.evaluateMove(position, move); From 07c926b4abf5c526404d71b5a47e94e70e57230e Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 3 Feb 2019 21:03:59 +0200 Subject: [PATCH 063/110] Comments --- .../player/legal/brain/internal/common/EvaluatorWeights.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java index 849a1b736..d6204606b 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java @@ -16,6 +16,7 @@ */ public final class EvaluatorWeights { private static final double HIGHEST_PRIORITY = 1.0; + //historically it was a minimal multiplier that causes GOOD results for DenormalizedBrain, it may be irrelevant now private static final double INCREASED_PRIORITY = 0.03; private static final double NORMAL_PRIORITY = 0.01; private static final double LOWEST_POSSIBLE = 0.0; @@ -50,7 +51,6 @@ private static Map getStandardWeights() { result.put( CASTLING_SAFETY, NORMAL_PRIORITY ); result.put( CENTER_CONTROL, NORMAL_PRIORITY ); result.put( MOBILITY, NORMAL_PRIORITY ); - //empirically found minimal multiplier that causes GOOD results for DenormalizedBrain result.put( MATERIAL, INCREASED_PRIORITY ); result.put( PROTECTION, NORMAL_PRIORITY ); result.put( ATTACK, NORMAL_PRIORITY ); From 27dab51a9574c7ddba4f8124e1a060e2977aa7be Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 3 Feb 2019 21:39:31 +0200 Subject: [PATCH 064/110] [ Sonar ] Type parameter names should comply with a naming convention --- .../com/leokom/chess/engine/GameState.java | 12 +++++----- .../legal/brain/common/GenericBrain.java | 9 +++++--- .../legal/brain/common/GenericEvaluator.java | 9 ++++++-- .../brain/normalized/NormalizedBrain.java | 23 +++++++++++-------- .../ValidatingNormalizedEvaluator.java | 12 +++++----- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/leokom/chess/engine/GameState.java b/src/main/java/com/leokom/chess/engine/GameState.java index 3ade5fb66..75c5cebc1 100644 --- a/src/main/java/com/leokom/chess/engine/GameState.java +++ b/src/main/java/com/leokom/chess/engine/GameState.java @@ -4,16 +4,16 @@ /** * The notion of game state is very generic and can be extracted to something chess-independent - * @param type of transitions - * @param current type + * @param type of transitions + * @param current type (state) */ /* - Rather complex recursive generic to THIS class is introduced in order to support return of exactly + Rather complex recursive generic to S class is introduced in order to support return of exactly our class in the move method. Inspired by https://www.sitepoint.com/self-types-with-javas-generics/ */ -public interface GameState< TransitionType extends GameTransition, THIS extends GameState< TransitionType, THIS > > { - THIS move(TransitionType move); +public interface GameState< T extends GameTransition, S extends GameState > { + S move(T move); - Set< TransitionType > getMoves(); + Set getMoves(); } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java index 9648fb923..b2fe4f30a 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java @@ -13,10 +13,13 @@ * A valid implementation of the decision maker must be * stateless. * + * @param game state + * @param transition type + * * Author: Leonid * Date-time: 23.08.16 22:53 */ -public interface GenericBrain< StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> { +public interface GenericBrain< S extends GameState, T extends GameTransition> { /** * Finds the best move(s) in the current position. @@ -26,7 +29,7 @@ public interface GenericBrain< StateType extends GameState< TransitionType, Stat * @return best move according to current strategy, absence of moves means: * no moves are legal - we reached a terminal position */ - List< TransitionType > findBestMove( StateType position ); + List findBestMove( S position ); /** * Get the best move to execute when it's not our @@ -40,7 +43,7 @@ public interface GenericBrain< StateType extends GameState< TransitionType, Stat to allow evolving interface while not forcing existing implementations to increase complexity */ - default TransitionType findBestMoveForOpponent( StateType position ) { + default T findBestMoveForOpponent(S position ) { return null; } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java index 003876d30..d2ab8b737 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericEvaluator.java @@ -3,6 +3,11 @@ import com.leokom.chess.engine.GameState; import com.leokom.chess.engine.GameTransition; -public interface GenericEvaluator< StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition > { - double evaluateMove(StateType position, TransitionType move ); +/** + * Generic evaluator, actually game-agnostic + * @param game state + * @param transition type + */ +public interface GenericEvaluator< S extends GameState, T extends GameTransition > { + double evaluateMove( S position, T move ); } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index c89633428..a67056e8f 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -21,22 +21,25 @@ * Now it has become generic (actually even not depending on chess-related notions). * You can inject any custom evaluator that acts as a normalized one via constructor. * + * @param game state + * @param transition type + * * Author: Leonid * Date-time: 23.08.16 22:54 */ -public class NormalizedBrain < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> implements GenericBrain< StateType, TransitionType > { +public class NormalizedBrain < S extends GameState, T extends GameTransition> implements GenericBrain { //this constant will increase with chess evolution private static final int MAXIMAL_SUPPORTED_DEPTH = 2; //this is an absolute constant private static final int MINIMAL_POSSIBLE_DEPTH = 1; - private final GenericEvaluator< StateType, TransitionType > brains; + private final GenericEvaluator brains; private final int pliesDepth; /** * Create normalized brain * @param brains evaluator with results in [ 0, 1 ] range */ - public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { + public NormalizedBrain( GenericEvaluator brains ) { this( brains, 1 ); } @@ -49,7 +52,7 @@ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains ) { * @param brains evaluator with results in [ 0, 1 ] range * @param pliesDepth depth to think */ - public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, int pliesDepth ) { + public NormalizedBrain(GenericEvaluator brains, int pliesDepth ) { if ( pliesDepth < MINIMAL_POSSIBLE_DEPTH) { throw new IllegalArgumentException( String.format( "This depth is wrong: %s", pliesDepth ) ); } @@ -81,8 +84,8 @@ public NormalizedBrain( GenericEvaluator< StateType, TransitionType > brains, in * */ @Override - public List findBestMove(StateType position ) { - Map moveRatings = new HashMap<>(); + public List findBestMove(S position ) { + Map moveRatings = new HashMap<>(); if ( pliesDepth == 1 ) { //filtering Draw offers till #161 is solved @@ -97,8 +100,8 @@ public List findBestMove(StateType position ) { getMovesWithoutDrawOffer( position ).forEach( move -> { ThreadContext.put( "moveBeingAnalyzed", move.toString() ); - StateType target = position.move( move ); - List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); + S target = position.move( move ); + List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); //can be empty in case of terminal position if ( ! bestMove.isEmpty() ) { @@ -126,11 +129,11 @@ public String name() { return String.format( "NormalizedBrain: %s depth", pliesDepth ); } - private Stream getMovesWithoutDrawOffer(StateType position) { + private Stream getMovesWithoutDrawOffer(S position) { return position.getMoves().stream().filter(move -> move != Move.OFFER_DRAW); } - private List getMoveWithMaxRating( Map< TransitionType, Double > moveValues ) { + private List getMoveWithMaxRating(Map moveValues ) { return moveValues.entrySet().stream() .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/ValidatingNormalizedEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/ValidatingNormalizedEvaluator.java index ce0d98520..3f20f5181 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/ValidatingNormalizedEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/ValidatingNormalizedEvaluator.java @@ -6,18 +6,18 @@ /** * Evaluator delegate that ensures [ 0, 1 ] constraint for the move - * @param - * @param + * @param game state + * @param transition type */ -class ValidatingNormalizedEvaluator < StateType extends GameState< TransitionType, StateType >, TransitionType extends GameTransition> implements GenericEvaluator< StateType, TransitionType > { - private final GenericEvaluator delegate; +class ValidatingNormalizedEvaluator < S extends GameState, T extends GameTransition> implements GenericEvaluator { + private final GenericEvaluator delegate; - ValidatingNormalizedEvaluator(GenericEvaluator delegate ) { + ValidatingNormalizedEvaluator(GenericEvaluator delegate ) { this.delegate = delegate; } @Override - public double evaluateMove(StateType position, TransitionType move) { + public double evaluateMove(S position, T move) { double result = delegate.evaluateMove(position, move); if ( result < 0.0 || result > 1.0 ) { throw new IllegalArgumentException( String.format( "The value is outside of supported range: %s", result ) ); From 5f9485ac653706b65736fa473015ec38be1631f4 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:14:05 +0200 Subject: [PATCH 065/110] Basic infrastructure for depth passing from command line --- .../com/leokom/chess/PlayerFactoryTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index 78bd84b61..39653cbab 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -11,11 +11,13 @@ public class PlayerFactoryTest { private static String whiteProperty; private static String blackProperty; + private static String whiteDepthProperty; @BeforeClass public static void preserveSystemProperties() { whiteProperty = System.getProperty( "white" ); blackProperty = System.getProperty( "black" ); + whiteDepthProperty = System.getProperty( "whiteDepthProperty" ); } @AfterClass @@ -29,6 +31,10 @@ public static void restoreSystemProperties() { if ( blackProperty != null ) { System.setProperty( "black", blackProperty ); } + + if ( whiteDepthProperty != null ) { + System.setProperty( "\"whiteDepthProperty\"", whiteDepthProperty ); + } } //ensure one test has no influence on another @@ -90,4 +96,34 @@ public void legalSelectedWhite() { final Player player = PlayerFactory.createPlayer( Side.WHITE ); assertIsLegal( player ); } + + @Test + public void depth2FromCommandLineRespected() { + System.setProperty( "white", "Legal" ); + System.setProperty( "whiteDepth", "2" ); + + final Player player = PlayerFactory.createPlayer( Side.WHITE ); + assertDepth( player, 2 ); + } + + @Test + public void depth1FromCommandLineRespected() { + System.setProperty( "white", "Legal" ); + System.setProperty( "whiteDepth", "1" ); + + final Player player = PlayerFactory.createPlayer( Side.WHITE ); + assertDepth( player, 1 ); + } + + /* + More cases: + - default value + - black + Refactor: automate system properties manipulation + */ + + private void assertDepth( Player player, int expectedDepth ) { + //shallow yet good enough check + assertThat( player.name(), CoreMatchers.containsString( String.valueOf( expectedDepth ) ) ); + } } \ No newline at end of file From c7895f470e2f9f82afb0012f9abb97ed687de852 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:16:40 +0200 Subject: [PATCH 066/110] Basic whiteDepth system property handling --- src/main/java/com/leokom/chess/PlayerFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 870e97cc2..b7fe3e4c5 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -75,7 +75,7 @@ private static PlayerSelection selectPlayer( Side side, String engineName ) { } public enum PlayerSelection { - LEGAL( () -> new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), 2) ) ), + LEGAL( () -> new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), Integer.valueOf(System.getProperty( "whiteDepth" ))) ) ), SIMPLE( () -> new LegalPlayer( new SimpleBrain() ) ), WINBOARD( WinboardPlayer::create ); From 3e5d77eb41a071fd484e873d7ebbe45e98421357 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:19:38 +0200 Subject: [PATCH 067/110] Test cases for black handling --- .../com/leokom/chess/PlayerFactoryTest.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index 39653cbab..cfa90ddef 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -12,12 +12,14 @@ public class PlayerFactoryTest { private static String whiteProperty; private static String blackProperty; private static String whiteDepthProperty; + private static String blackDepthProperty; @BeforeClass public static void preserveSystemProperties() { whiteProperty = System.getProperty( "white" ); blackProperty = System.getProperty( "black" ); whiteDepthProperty = System.getProperty( "whiteDepthProperty" ); + blackDepthProperty = System.getProperty( "blackDepthProperty" ); } @AfterClass @@ -33,7 +35,10 @@ public static void restoreSystemProperties() { } if ( whiteDepthProperty != null ) { - System.setProperty( "\"whiteDepthProperty\"", whiteDepthProperty ); + System.setProperty( "whiteDepthProperty", whiteDepthProperty ); + } + if ( blackDepthProperty != null ) { + System.setProperty( "blackDepthProperty", blackDepthProperty ); } } @@ -98,7 +103,7 @@ public void legalSelectedWhite() { } @Test - public void depth2FromCommandLineRespected() { + public void depth2FromCommandLineRespectedForWhite() { System.setProperty( "white", "Legal" ); System.setProperty( "whiteDepth", "2" ); @@ -107,7 +112,7 @@ public void depth2FromCommandLineRespected() { } @Test - public void depth1FromCommandLineRespected() { + public void depth1FromCommandLineRespectedForWhite() { System.setProperty( "white", "Legal" ); System.setProperty( "whiteDepth", "1" ); @@ -115,11 +120,28 @@ public void depth1FromCommandLineRespected() { assertDepth( player, 1 ); } + @Test + public void depth1FromCommandLineRespectedForBlack() { + System.setProperty( "black", "Legal" ); + System.setProperty( "blackDepth", "1" ); + + final Player player = PlayerFactory.createPlayer( Side.BLACK ); + assertDepth( player, 1 ); + } + + @Test + public void depth2FromCommandLineRespectedForBlack() { + System.setProperty( "black", "Legal" ); + System.setProperty( "blackDepth", "2" ); + + final Player player = PlayerFactory.createPlayer( Side.BLACK ); + assertDepth( player, 2 ); + } + /* More cases: - default value - - black - Refactor: automate system properties manipulation + - Refactor: automate system properties manipulation */ private void assertDepth( Player player, int expectedDepth ) { From 2edc09e2b217d3c9131b983599ace746874d073d Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:47:18 +0200 Subject: [PATCH 068/110] Depth property is respected for both sides. It caused refactoring which proved nice idea to get rid of enums --- .../com/leokom/chess/LegalPlayerSupplier.java | 26 ++++++++++++ .../java/com/leokom/chess/PlayerFactory.java | 42 +++++++------------ .../leokom/chess/SimplePlayerSupplier.java | 15 +++++++ .../leokom/chess/WinboardPlayerSupplier.java | 14 +++++++ src/test/java/com/leokom/chess/Simulator.java | 5 ++- .../java/com/leokom/chess/SimulatorIT.java | 10 ++--- .../player/legal/LegalPlayerNameTest.java | 4 +- .../legal/brain/simple/SimpleBrainTest.java | 4 +- 8 files changed, 82 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/leokom/chess/LegalPlayerSupplier.java create mode 100644 src/main/java/com/leokom/chess/SimplePlayerSupplier.java create mode 100644 src/main/java/com/leokom/chess/WinboardPlayerSupplier.java diff --git a/src/main/java/com/leokom/chess/LegalPlayerSupplier.java b/src/main/java/com/leokom/chess/LegalPlayerSupplier.java new file mode 100644 index 000000000..7e49e211b --- /dev/null +++ b/src/main/java/com/leokom/chess/LegalPlayerSupplier.java @@ -0,0 +1,26 @@ +package com.leokom.chess; + +import com.leokom.chess.player.Player; +import com.leokom.chess.player.legal.LegalPlayer; +import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; +import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; + +import java.util.function.Supplier; + +class LegalPlayerSupplier implements Supplier { + //this depth has been used for years + private static final int DEFAULT_DEPTH = 1; + private final int depth; + + public LegalPlayerSupplier() { + this(DEFAULT_DEPTH); + } + + public LegalPlayerSupplier( int depth ) { + this.depth = depth; + } + + public Player get() { + return new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), depth ) ); + } +} diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index b7fe3e4c5..12f0be2cc 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -3,11 +3,6 @@ import com.google.common.collect.ImmutableMap; import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; -import com.leokom.chess.player.legal.LegalPlayer; -import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; -import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; -import com.leokom.chess.player.legal.brain.simple.SimpleBrain; -import com.leokom.chess.player.winboard.WinboardPlayer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -20,7 +15,7 @@ * Author: Leonid * Date-time: 06.05.14 22:45 */ -public final class PlayerFactory { +final class PlayerFactory { private PlayerFactory() {} private static Logger logger = LogManager.getLogger( PlayerFactory.class ); @@ -52,10 +47,10 @@ static Player createPlayer( Side side ) { logger.info( "Engine from system properties: " + engineName + ". Side = " + side ); - return selectPlayer( side, engineName ).create(); + return selectPlayer( side, engineName ).get(); } - private static PlayerSelection selectPlayer( Side side, String engineName ) { + private static Supplier< Player > selectPlayer( Side side, String engineName ) { if ( engineName == null ) { logger.info( "No selection done. Selecting default player" ); return getDefaultPlayer( side ); @@ -63,35 +58,30 @@ private static PlayerSelection selectPlayer( Side side, String engineName ) { switch ( engineName ) { case "Legal": - return PlayerSelection.LEGAL; + return new LegalPlayerSupplier( Integer.valueOf( System.getProperty(getDepthProperty( side )) ) ); case "Simple": - return PlayerSelection.SIMPLE; + return new SimplePlayerSupplier(); case "Winboard": - return PlayerSelection.WINBOARD; + return new WinboardPlayerSupplier(); default: logger.warn( "Unsupported option specified. Selecting default player" ); return getDefaultPlayer( side ); } } - public enum PlayerSelection { - LEGAL( () -> new LegalPlayer( new NormalizedBrain<>( new MasterEvaluator(), Integer.valueOf(System.getProperty( "whiteDepth" ))) ) ), - SIMPLE( () -> new LegalPlayer( new SimpleBrain() ) ), - WINBOARD( WinboardPlayer::create ); - - private final Supplier< Player > playerCreator; - - PlayerSelection( Supplier< Player > playerCreator ) { - this.playerCreator = playerCreator; - } - - public Player create() { - return playerCreator.get(); + private static String getDepthProperty( Side side ) { + switch ( side ) { + case WHITE: + return "whiteDepth"; + case BLACK: + return "blackDepth"; + default: + throw new IllegalArgumentException( "The side is not supported: " + side ); } } - private static PlayerSelection getDefaultPlayer( Side side ) { + private static Supplier< Player > getDefaultPlayer( Side side ) { logger.info( "Selecting default engine for Side = " + side ); - return side == Side.WHITE ? PlayerSelection.WINBOARD : PlayerSelection.LEGAL; + return side == Side.WHITE ? new WinboardPlayerSupplier() : new LegalPlayerSupplier( 1 ); } } diff --git a/src/main/java/com/leokom/chess/SimplePlayerSupplier.java b/src/main/java/com/leokom/chess/SimplePlayerSupplier.java new file mode 100644 index 000000000..d802a1528 --- /dev/null +++ b/src/main/java/com/leokom/chess/SimplePlayerSupplier.java @@ -0,0 +1,15 @@ +package com.leokom.chess; + +import com.leokom.chess.player.Player; +import com.leokom.chess.player.legal.LegalPlayer; +import com.leokom.chess.player.legal.brain.simple.SimpleBrain; + +import java.util.function.Supplier; + +public class SimplePlayerSupplier implements Supplier { + + @Override + public Player get() { + return new LegalPlayer( new SimpleBrain() ); + } +} diff --git a/src/main/java/com/leokom/chess/WinboardPlayerSupplier.java b/src/main/java/com/leokom/chess/WinboardPlayerSupplier.java new file mode 100644 index 000000000..086e75396 --- /dev/null +++ b/src/main/java/com/leokom/chess/WinboardPlayerSupplier.java @@ -0,0 +1,14 @@ +package com.leokom.chess; + +import com.leokom.chess.player.Player; +import com.leokom.chess.player.winboard.WinboardPlayer; + +import java.util.function.Supplier; + +class WinboardPlayerSupplier implements Supplier { + + @Override + public Player get() { + return WinboardPlayer.create(); + } +} diff --git a/src/test/java/com/leokom/chess/Simulator.java b/src/test/java/com/leokom/chess/Simulator.java index 92126fce5..36ab08777 100644 --- a/src/test/java/com/leokom/chess/Simulator.java +++ b/src/test/java/com/leokom/chess/Simulator.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Supplier; import java.util.stream.IntStream; /** @@ -25,8 +26,8 @@ class Simulator { private int timesToRun = 1; - Simulator( PlayerFactory.PlayerSelection first, PlayerFactory.PlayerSelection second ) { - this( first.create(), second.create() ); + Simulator( Supplier< Player > first, Supplier< Player > second ) { + this( first.get(), second.get() ); } Simulator( Player first, Player second ) { diff --git a/src/test/java/com/leokom/chess/SimulatorIT.java b/src/test/java/com/leokom/chess/SimulatorIT.java index 6740c6800..9cbe62705 100644 --- a/src/test/java/com/leokom/chess/SimulatorIT.java +++ b/src/test/java/com/leokom/chess/SimulatorIT.java @@ -14,8 +14,6 @@ import org.junit.Ignore; import org.junit.Test; -import static com.leokom.chess.PlayerFactory.PlayerSelection.LEGAL; -import static com.leokom.chess.PlayerFactory.PlayerSelection.SIMPLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -133,25 +131,25 @@ private void programPlayers( Position position, Position ... positions ) { @Test public void legalVsSimpleNoCrash() { - new Simulator( LEGAL, SIMPLE ).run(); + new Simulator( new LegalPlayerSupplier(), new SimplePlayerSupplier() ).run(); } //we expect the default brain of the legal player is much smarter than the simple one @Test public void legalVsSimpleStatistics() { - final SimulatorStatistics statistics = new Simulator( LEGAL, SIMPLE ).run(); + final SimulatorStatistics statistics = new Simulator( new LegalPlayerSupplier(), new SimplePlayerSupplier() ).run(); assertEquals( new SimulatorStatistics( 2, 2, 0 ), statistics ); } @Test public void simpleVsSimpleNoCrash() { - new Simulator( SIMPLE, SIMPLE ).run(); + new Simulator( new SimplePlayerSupplier(), new SimplePlayerSupplier() ).run(); } @Test public void simpleVsSimpleStatistics() { - final SimulatorStatistics statistics = new Simulator( SIMPLE, SIMPLE ).run(); + final SimulatorStatistics statistics = new Simulator( new SimplePlayerSupplier(), new SimplePlayerSupplier() ).run(); //now simple vs simple correctly draws at the second move assertEquals( new SimulatorStatistics( 2, 0, 0 ), statistics ); diff --git a/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java b/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java index 95ba86083..d68100ed2 100644 --- a/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java +++ b/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java @@ -1,11 +1,11 @@ package com.leokom.chess.player.legal; +import com.leokom.chess.SimplePlayerSupplier; import com.leokom.chess.player.legal.brain.denormalized.DenormalizedBrain; import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; import org.junit.Test; -import static com.leokom.chess.PlayerFactory.PlayerSelection.SIMPLE; import static org.junit.Assert.assertEquals; public class LegalPlayerNameTest { @@ -21,6 +21,6 @@ public void normalizedBrain() { @Test public void simpleBrain() { - assertEquals( "LegalPlayer : SimpleBrain", SIMPLE.create().name() ); + assertEquals( "LegalPlayer : SimpleBrain", new SimplePlayerSupplier().get().name() ); } } diff --git a/src/test/java/com/leokom/chess/player/legal/brain/simple/SimpleBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/simple/SimpleBrainTest.java index d675d2ba5..e4dd5408a 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/simple/SimpleBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/simple/SimpleBrainTest.java @@ -1,13 +1,13 @@ package com.leokom.chess.player.legal.brain.simple; import com.leokom.chess.Game; +import com.leokom.chess.SimplePlayerSupplier; import com.leokom.chess.engine.Move; import com.leokom.chess.player.Player; import com.leokom.chess.player.PlayerBuilder; import org.junit.Before; import org.junit.Test; -import static com.leokom.chess.PlayerFactory.PlayerSelection.SIMPLE; import static org.mockito.Mockito.*; /** @@ -18,7 +18,7 @@ public class SimpleBrainTest { @Before public void prepare() { - simplePlayer = SIMPLE.create(); + simplePlayer = new SimplePlayerSupplier().get(); } @Test From d85f3feea588511dbd616a89c26230479dd3b72a Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:49:43 +0200 Subject: [PATCH 069/110] Refactoring: moved player suppliers closer to their packages --- src/main/java/com/leokom/chess/PlayerFactory.java | 3 +++ .../leokom/chess/{ => player/legal}/LegalPlayerSupplier.java | 4 ++-- .../{ => player/legal/brain/simple}/SimplePlayerSupplier.java | 2 +- .../java/com/leokom/chess/player/winboard/WinboardPlayer.java | 2 +- .../chess/{ => player/winboard}/WinboardPlayerSupplier.java | 4 ++-- src/test/java/com/leokom/chess/SimulatorIT.java | 2 ++ .../com/leokom/chess/player/legal/LegalPlayerNameTest.java | 2 +- .../chess/player/legal/brain/simple/SimpleBrainTest.java | 1 - 8 files changed, 12 insertions(+), 8 deletions(-) rename src/main/java/com/leokom/chess/{ => player/legal}/LegalPlayerSupplier.java (86%) rename src/main/java/com/leokom/chess/{ => player/legal/brain/simple}/SimplePlayerSupplier.java (87%) rename src/main/java/com/leokom/chess/{ => player/winboard}/WinboardPlayerSupplier.java (67%) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 12f0be2cc..dade2c76b 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -3,6 +3,9 @@ import com.google.common.collect.ImmutableMap; import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; +import com.leokom.chess.player.legal.LegalPlayerSupplier; +import com.leokom.chess.player.legal.brain.simple.SimplePlayerSupplier; +import com.leokom.chess.player.winboard.WinboardPlayerSupplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/com/leokom/chess/LegalPlayerSupplier.java b/src/main/java/com/leokom/chess/player/legal/LegalPlayerSupplier.java similarity index 86% rename from src/main/java/com/leokom/chess/LegalPlayerSupplier.java rename to src/main/java/com/leokom/chess/player/legal/LegalPlayerSupplier.java index 7e49e211b..36991501f 100644 --- a/src/main/java/com/leokom/chess/LegalPlayerSupplier.java +++ b/src/main/java/com/leokom/chess/player/legal/LegalPlayerSupplier.java @@ -1,4 +1,4 @@ -package com.leokom.chess; +package com.leokom.chess.player.legal; import com.leokom.chess.player.Player; import com.leokom.chess.player.legal.LegalPlayer; @@ -7,7 +7,7 @@ import java.util.function.Supplier; -class LegalPlayerSupplier implements Supplier { +public class LegalPlayerSupplier implements Supplier { //this depth has been used for years private static final int DEFAULT_DEPTH = 1; private final int depth; diff --git a/src/main/java/com/leokom/chess/SimplePlayerSupplier.java b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimplePlayerSupplier.java similarity index 87% rename from src/main/java/com/leokom/chess/SimplePlayerSupplier.java rename to src/main/java/com/leokom/chess/player/legal/brain/simple/SimplePlayerSupplier.java index d802a1528..390009f58 100644 --- a/src/main/java/com/leokom/chess/SimplePlayerSupplier.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimplePlayerSupplier.java @@ -1,4 +1,4 @@ -package com.leokom.chess; +package com.leokom.chess.player.legal.brain.simple; import com.leokom.chess.player.Player; import com.leokom.chess.player.legal.LegalPlayer; diff --git a/src/main/java/com/leokom/chess/player/winboard/WinboardPlayer.java b/src/main/java/com/leokom/chess/player/winboard/WinboardPlayer.java index e99ae6138..4ed8a4278 100644 --- a/src/main/java/com/leokom/chess/player/winboard/WinboardPlayer.java +++ b/src/main/java/com/leokom/chess/player/winboard/WinboardPlayer.java @@ -162,7 +162,7 @@ private boolean canClaimDrawBeExecutedNow() { * @return instance of properly initialized Player against WinBoard-powered player * */ - public static Player create() { + static Player create() { //TODO: implement some singleton policy? final WinboardCommunicator communicator = new WinboardCommunicator(); return new WinboardPlayer( new WinboardCommanderImpl( communicator ) ); diff --git a/src/main/java/com/leokom/chess/WinboardPlayerSupplier.java b/src/main/java/com/leokom/chess/player/winboard/WinboardPlayerSupplier.java similarity index 67% rename from src/main/java/com/leokom/chess/WinboardPlayerSupplier.java rename to src/main/java/com/leokom/chess/player/winboard/WinboardPlayerSupplier.java index 086e75396..1ab53e236 100644 --- a/src/main/java/com/leokom/chess/WinboardPlayerSupplier.java +++ b/src/main/java/com/leokom/chess/player/winboard/WinboardPlayerSupplier.java @@ -1,11 +1,11 @@ -package com.leokom.chess; +package com.leokom.chess.player.winboard; import com.leokom.chess.player.Player; import com.leokom.chess.player.winboard.WinboardPlayer; import java.util.function.Supplier; -class WinboardPlayerSupplier implements Supplier { +public class WinboardPlayerSupplier implements Supplier { @Override public Player get() { diff --git a/src/test/java/com/leokom/chess/SimulatorIT.java b/src/test/java/com/leokom/chess/SimulatorIT.java index 9cbe62705..e86900812 100644 --- a/src/test/java/com/leokom/chess/SimulatorIT.java +++ b/src/test/java/com/leokom/chess/SimulatorIT.java @@ -5,12 +5,14 @@ import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; import com.leokom.chess.player.legal.LegalPlayer; +import com.leokom.chess.player.legal.LegalPlayerSupplier; import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.common.EvaluatorType; import com.leokom.chess.player.legal.brain.denormalized.DenormalizedBrain; import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; import com.leokom.chess.player.legal.brain.normalized.MasterEvaluatorBuilder; import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; +import com.leokom.chess.player.legal.brain.simple.SimplePlayerSupplier; import org.junit.Ignore; import org.junit.Test; diff --git a/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java b/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java index d68100ed2..749b2c987 100644 --- a/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java +++ b/src/test/java/com/leokom/chess/player/legal/LegalPlayerNameTest.java @@ -1,6 +1,6 @@ package com.leokom.chess.player.legal; -import com.leokom.chess.SimplePlayerSupplier; +import com.leokom.chess.player.legal.brain.simple.SimplePlayerSupplier; import com.leokom.chess.player.legal.brain.denormalized.DenormalizedBrain; import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; diff --git a/src/test/java/com/leokom/chess/player/legal/brain/simple/SimpleBrainTest.java b/src/test/java/com/leokom/chess/player/legal/brain/simple/SimpleBrainTest.java index e4dd5408a..ea44ee660 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/simple/SimpleBrainTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/simple/SimpleBrainTest.java @@ -1,7 +1,6 @@ package com.leokom.chess.player.legal.brain.simple; import com.leokom.chess.Game; -import com.leokom.chess.SimplePlayerSupplier; import com.leokom.chess.engine.Move; import com.leokom.chess.player.Player; import com.leokom.chess.player.PlayerBuilder; From 9753fc9d186ca02cb8219df402cdf2a1e5137cb4 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:54:38 +0200 Subject: [PATCH 070/110] Fixed test --- src/test/java/com/leokom/chess/SimulatorIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/leokom/chess/SimulatorIT.java b/src/test/java/com/leokom/chess/SimulatorIT.java index e86900812..3d3bec05e 100644 --- a/src/test/java/com/leokom/chess/SimulatorIT.java +++ b/src/test/java/com/leokom/chess/SimulatorIT.java @@ -160,7 +160,7 @@ public void simpleVsSimpleStatistics() { //non-deterministic, it's not a business-requirement @Test public void legalVsLegalCustomEvaluator() { - final Evaluator brainLikesToEatPieces = new MasterEvaluatorBuilder().weight( EvaluatorType.MATERIAL, 100.0 ).build(); + final Evaluator brainLikesToEatPieces = new MasterEvaluatorBuilder().weight( EvaluatorType.MATERIAL, 1.0 ).build(); final SimulatorStatistics statistics = new Simulator( new LegalPlayer(), new LegalPlayer( brainLikesToEatPieces ) ).run(); From 0015810a3e3c310eef2598d4a59df6c75a0efa6d Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:55:45 +0200 Subject: [PATCH 071/110] Refactoring: don't hard-code default depth here --- src/main/java/com/leokom/chess/PlayerFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index dade2c76b..2d78950b9 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -85,6 +85,6 @@ private static String getDepthProperty( Side side ) { private static Supplier< Player > getDefaultPlayer( Side side ) { logger.info( "Selecting default engine for Side = " + side ); - return side == Side.WHITE ? new WinboardPlayerSupplier() : new LegalPlayerSupplier( 1 ); + return side == Side.WHITE ? new WinboardPlayerSupplier() : new LegalPlayerSupplier(); } } From e64640cd82eacb2c456686248987603379cd9359 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:58:24 +0200 Subject: [PATCH 072/110] Default value is respected --- src/main/java/com/leokom/chess/PlayerFactory.java | 6 +++++- src/test/java/com/leokom/chess/PlayerFactoryTest.java | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 2d78950b9..1b9d0f9d8 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -61,7 +61,11 @@ private static Supplier< Player > selectPlayer( Side side, String engineName ) { switch ( engineName ) { case "Legal": - return new LegalPlayerSupplier( Integer.valueOf( System.getProperty(getDepthProperty( side )) ) ); + String depthProperty = System.getProperty( getDepthProperty( side ) ); + return depthProperty != null ? + new LegalPlayerSupplier( Integer.valueOf( depthProperty ) ) : + new LegalPlayerSupplier(); + case "Simple": return new SimplePlayerSupplier(); case "Winboard": diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index cfa90ddef..6ff3d3822 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -138,9 +138,16 @@ public void depth2FromCommandLineRespectedForBlack() { assertDepth( player, 2 ); } + @Test + public void defaultDepthIs1() { + System.setProperty( "black", "Legal" ); + + final Player player = PlayerFactory.createPlayer( Side.BLACK ); + assertDepth( player, 1 ); + } + /* More cases: - - default value - Refactor: automate system properties manipulation */ From 189c5d35f5a0510c745ea738599787da9d5ff77d Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Mon, 4 Feb 2019 22:58:59 +0200 Subject: [PATCH 073/110] TODO-list extended --- src/test/java/com/leokom/chess/PlayerFactoryTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index 6ff3d3822..f02c08226 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -149,6 +149,8 @@ public void defaultDepthIs1() { /* More cases: - Refactor: automate system properties manipulation + - extend *.bat file(s) + - extend docs */ private void assertDepth( Player player, int expectedDepth ) { From fa887dd53e236557cbf5cff497ddd5587d1258ff Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 22:18:27 +0200 Subject: [PATCH 074/110] Fixed fragile test which depends on system properties --- src/test/java/com/leokom/chess/PlayerFactoryTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index f02c08226..e3c82f69a 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -18,8 +18,8 @@ public class PlayerFactoryTest { public static void preserveSystemProperties() { whiteProperty = System.getProperty( "white" ); blackProperty = System.getProperty( "black" ); - whiteDepthProperty = System.getProperty( "whiteDepthProperty" ); - blackDepthProperty = System.getProperty( "blackDepthProperty" ); + whiteDepthProperty = System.getProperty( "whiteDepth" ); + blackDepthProperty = System.getProperty( "blackDepth" ); } @AfterClass @@ -35,10 +35,10 @@ public static void restoreSystemProperties() { } if ( whiteDepthProperty != null ) { - System.setProperty( "whiteDepthProperty", whiteDepthProperty ); + System.setProperty( "whiteDepth", whiteDepthProperty ); } if ( blackDepthProperty != null ) { - System.setProperty( "blackDepthProperty", blackDepthProperty ); + System.setProperty( "blackDepth", blackDepthProperty ); } } @@ -48,6 +48,8 @@ public static void restoreSystemProperties() { public void clearSystemProperties() { System.clearProperty( "black" ); System.clearProperty( "white" ); + System.clearProperty( "whiteDepth" ); + System.clearProperty( "blackDepth" ); } @Test From 2cca3eac98aeb3a890bfb3b42eb9e1969ab361a6 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 22:36:27 +0200 Subject: [PATCH 075/110] Refactoring: used system-rules lib to simplify system properties testing. --- Chess.iml | 1 + pom.xml | 7 +++ .../com/leokom/chess/PlayerFactoryTest.java | 47 ++----------------- 3 files changed, 12 insertions(+), 43 deletions(-) diff --git a/Chess.iml b/Chess.iml index a1d78f435..f3c545329 100644 --- a/Chess.iml +++ b/Chess.iml @@ -21,6 +21,7 @@ + diff --git a/pom.xml b/pom.xml index cdad349b3..e4be67de9 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,13 @@ test + + com.github.stefanbirkner + system-rules + 1.19.0 + test + + diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index e3c82f69a..ce2fe331a 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -5,52 +5,14 @@ import com.leokom.chess.player.winboard.WinboardPlayer; import org.hamcrest.CoreMatchers; import org.junit.*; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; import static org.junit.Assert.*; public class PlayerFactoryTest { - private static String whiteProperty; - private static String blackProperty; - private static String whiteDepthProperty; - private static String blackDepthProperty; - - @BeforeClass - public static void preserveSystemProperties() { - whiteProperty = System.getProperty( "white" ); - blackProperty = System.getProperty( "black" ); - whiteDepthProperty = System.getProperty( "whiteDepth" ); - blackDepthProperty = System.getProperty( "blackDepth" ); - } - - @AfterClass - public static void restoreSystemProperties() { - //if any of them is null, @After method already cleared it. - //setting null value of system property causes NPE - if ( whiteProperty != null ) { - System.setProperty( "white", whiteProperty ); - } - - if ( blackProperty != null ) { - System.setProperty( "black", blackProperty ); - } - - if ( whiteDepthProperty != null ) { - System.setProperty( "whiteDepth", whiteDepthProperty ); - } - if ( blackDepthProperty != null ) { - System.setProperty( "blackDepth", blackDepthProperty ); - } - } - - //ensure one test has no influence on another - @Before - @After - public void clearSystemProperties() { - System.clearProperty( "black" ); - System.clearProperty( "white" ); - System.clearProperty( "whiteDepth" ); - System.clearProperty( "blackDepth" ); - } + //snapshots all system properties before a test, restores after it + @Rule + public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); @Test public void noSystemPropertiesDefaultPlayerBlack() { @@ -150,7 +112,6 @@ public void defaultDepthIs1() { /* More cases: - - Refactor: automate system properties manipulation - extend *.bat file(s) - extend docs */ From 65818f1f3858115f9d5f4d668fe08329bb679db7 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 22:43:33 +0200 Subject: [PATCH 076/110] Refactoring: engine selection system properties are improved. --- src/main/bat/runTwoEngines.bat | 2 +- src/main/bat/variables.bat | 2 +- src/main/java/com/leokom/chess/MainRunner.java | 8 ++++---- .../java/com/leokom/chess/PlayerFactory.java | 2 +- .../java/com/leokom/chess/PlayerFactoryTest.java | 16 ++++++++-------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/bat/runTwoEngines.bat b/src/main/bat/runTwoEngines.bat index 5a7995dbd..740da8b68 100644 --- a/src/main/bat/runTwoEngines.bat +++ b/src/main/bat/runTwoEngines.bat @@ -5,7 +5,7 @@ call variables.bat SET RUNNABLE_JAR_DIRECTORY_2=%WINBOARD_INSTALLATION_PATH%\LeokomChessTest SET RUN_JAR_PATH_2=%RUNNABLE_JAR_DIRECTORY_2%\Chess.jar -rem you may specify -Dblack=Simple +rem you may specify -Dblack.engine=Simple (or -Dblack=Simple if the second instance is Chess <= 0.3) SET RUN_OPTIONS_2= SET ENGINE_2=%JAVA_PATH% %RUN_OPTIONS_2% -jar %RUN_JAR_PATH_2% diff --git a/src/main/bat/variables.bat b/src/main/bat/variables.bat index 57bc30d38..034306320 100644 --- a/src/main/bat/variables.bat +++ b/src/main/bat/variables.bat @@ -6,7 +6,7 @@ rem it should be equal to 'project.deployDirectory' property in pom.xml SET RUNNABLE_JAR_DIRECTORY=%WINBOARD_INSTALLATION_PATH%\LeokomChess SET JAVA_PATH=Q:\Program Files\Java\jdk1.8.0_162\bin\java.exe SET RUN_JAR_PATH=%RUNNABLE_JAR_DIRECTORY%\Chess.jar -rem you may pass -Dblack=Simple to choose a different engine for blacks +rem you may pass -Dblack.engine=Simple to choose a different engine for blacks SET RUN_OPTIONS= SET ENGINE=%JAVA_PATH% %RUN_OPTIONS% -jar %RUN_JAR_PATH% \ No newline at end of file diff --git a/src/main/java/com/leokom/chess/MainRunner.java b/src/main/java/com/leokom/chess/MainRunner.java index 52f3c79d3..4d098e762 100644 --- a/src/main/java/com/leokom/chess/MainRunner.java +++ b/src/main/java/com/leokom/chess/MainRunner.java @@ -24,8 +24,8 @@ private MainRunner() { *

* Supported parameters: *

    - *
  • -Dwhite=engineName
  • - *
  • -Dblack=engineName
  • + *
  • -Dwhite.engine=engineName
  • + *
  • -Dblack.engine=engineName
  • *
* * engineName could be any of: @@ -37,8 +37,8 @@ private MainRunner() { * * Default players: *
    - *
  • -Dwhite=Winboard
  • - *
  • -Dblack=Legal
  • + *
  • -Dwhite.engine=Winboard
  • + *
  • -Dblack.engine=Legal
  • *
* * For Winboard opponents always specify them as Black diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 1b9d0f9d8..bb2cc51b5 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -25,7 +25,7 @@ private PlayerFactory() {} //side -> name of system property that specifies player for the side private static final Map< Side, String > SYSTEM_PROPERTIES = - ImmutableMap.of( Side.WHITE, "white", Side.BLACK, "black" ); + ImmutableMap.of( Side.WHITE, "white.engine", Side.BLACK, "black.engine" ); /** * Create player for the side diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index ce2fe331a..1a61bc219 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -22,7 +22,7 @@ public void noSystemPropertiesDefaultPlayerBlack() { @Test public void canSelectSimpleEngineForWhite() { - System.setProperty( "white", "Simple" ); + System.setProperty( "whiteEngine", "Simple" ); final Player player = PlayerFactory.createPlayer( Side.WHITE ); assertIsSimple( player ); @@ -34,7 +34,7 @@ private void assertIsSimple(Player player) { @Test public void canSelectWinboardForBlack() { - System.setProperty( "black", "Winboard" ); + System.setProperty( "blackEngine", "Winboard" ); final Player player = PlayerFactory.createPlayer( Side.BLACK ); assertTrue( player instanceof WinboardPlayer ); @@ -60,7 +60,7 @@ private void assertIsLegal( Player player ) { @Test public void legalSelectedWhite() { - System.setProperty( "white", "Legal" ); + System.setProperty( "whiteEngine", "Legal" ); final Player player = PlayerFactory.createPlayer( Side.WHITE ); assertIsLegal( player ); @@ -68,7 +68,7 @@ public void legalSelectedWhite() { @Test public void depth2FromCommandLineRespectedForWhite() { - System.setProperty( "white", "Legal" ); + System.setProperty( "whiteEngine", "Legal" ); System.setProperty( "whiteDepth", "2" ); final Player player = PlayerFactory.createPlayer( Side.WHITE ); @@ -77,7 +77,7 @@ public void depth2FromCommandLineRespectedForWhite() { @Test public void depth1FromCommandLineRespectedForWhite() { - System.setProperty( "white", "Legal" ); + System.setProperty( "whiteEngine", "Legal" ); System.setProperty( "whiteDepth", "1" ); final Player player = PlayerFactory.createPlayer( Side.WHITE ); @@ -86,7 +86,7 @@ public void depth1FromCommandLineRespectedForWhite() { @Test public void depth1FromCommandLineRespectedForBlack() { - System.setProperty( "black", "Legal" ); + System.setProperty( "blackEngine", "Legal" ); System.setProperty( "blackDepth", "1" ); final Player player = PlayerFactory.createPlayer( Side.BLACK ); @@ -95,7 +95,7 @@ public void depth1FromCommandLineRespectedForBlack() { @Test public void depth2FromCommandLineRespectedForBlack() { - System.setProperty( "black", "Legal" ); + System.setProperty( "blackEngine", "Legal" ); System.setProperty( "blackDepth", "2" ); final Player player = PlayerFactory.createPlayer( Side.BLACK ); @@ -104,7 +104,7 @@ public void depth2FromCommandLineRespectedForBlack() { @Test public void defaultDepthIs1() { - System.setProperty( "black", "Legal" ); + System.setProperty( "blackEngine", "Legal" ); final Player player = PlayerFactory.createPlayer( Side.BLACK ); assertDepth( player, 1 ); From 781e6172d919a17e138c85106fc71c87a84186e3 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 22:56:46 +0200 Subject: [PATCH 077/110] Refactoring: generic common conventions for system properties. Code reused. --- .../java/com/leokom/chess/PlayerFactory.java | 35 +++++++++---------- .../com/leokom/chess/PlayerFactoryTest.java | 24 ++++++------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index bb2cc51b5..8b3a10ee8 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -1,6 +1,5 @@ package com.leokom.chess; -import com.google.common.collect.ImmutableMap; import com.leokom.chess.engine.Side; import com.leokom.chess.player.Player; import com.leokom.chess.player.legal.LegalPlayerSupplier; @@ -9,7 +8,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.Map; import java.util.function.Supplier; /** @@ -23,9 +21,21 @@ private PlayerFactory() {} private static Logger logger = LogManager.getLogger( PlayerFactory.class ); - //side -> name of system property that specifies player for the side - private static final Map< Side, String > SYSTEM_PROPERTIES = - ImmutableMap.of( Side.WHITE, "white.engine", Side.BLACK, "black.engine" ); + /** + * Chess system properties. + * Represent properties in format 'side.property' (like 'white.depth' or 'black.engine') + */ + static class ChessSystemProperty { + private final String propertyName; + + ChessSystemProperty( String propertyName ) { + this.propertyName = propertyName; + } + + String getFor( Side side ) { + return System.getProperty( side.name().toLowerCase() + "." + propertyName ); + } + } /** * Create player for the side @@ -46,7 +56,7 @@ private PlayerFactory() {} * @return new instance of a player */ static Player createPlayer( Side side ) { - final String engineName = System.getProperty( SYSTEM_PROPERTIES.get( side ) ); + final String engineName = new ChessSystemProperty( "engine" ).getFor( side ); logger.info( "Engine from system properties: " + engineName + ". Side = " + side ); @@ -61,7 +71,7 @@ private static Supplier< Player > selectPlayer( Side side, String engineName ) { switch ( engineName ) { case "Legal": - String depthProperty = System.getProperty( getDepthProperty( side ) ); + String depthProperty = new ChessSystemProperty( "depth" ).getFor( side ); return depthProperty != null ? new LegalPlayerSupplier( Integer.valueOf( depthProperty ) ) : new LegalPlayerSupplier(); @@ -76,17 +86,6 @@ private static Supplier< Player > selectPlayer( Side side, String engineName ) { } } - private static String getDepthProperty( Side side ) { - switch ( side ) { - case WHITE: - return "whiteDepth"; - case BLACK: - return "blackDepth"; - default: - throw new IllegalArgumentException( "The side is not supported: " + side ); - } - } - private static Supplier< Player > getDefaultPlayer( Side side ) { logger.info( "Selecting default engine for Side = " + side ); return side == Side.WHITE ? new WinboardPlayerSupplier() : new LegalPlayerSupplier(); diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index 1a61bc219..6853be1e8 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -22,7 +22,7 @@ public void noSystemPropertiesDefaultPlayerBlack() { @Test public void canSelectSimpleEngineForWhite() { - System.setProperty( "whiteEngine", "Simple" ); + System.setProperty( "white.engine", "Simple" ); final Player player = PlayerFactory.createPlayer( Side.WHITE ); assertIsSimple( player ); @@ -34,7 +34,7 @@ private void assertIsSimple(Player player) { @Test public void canSelectWinboardForBlack() { - System.setProperty( "blackEngine", "Winboard" ); + System.setProperty( "black.engine", "Winboard" ); final Player player = PlayerFactory.createPlayer( Side.BLACK ); assertTrue( player instanceof WinboardPlayer ); @@ -60,7 +60,7 @@ private void assertIsLegal( Player player ) { @Test public void legalSelectedWhite() { - System.setProperty( "whiteEngine", "Legal" ); + System.setProperty( "white.engine", "Legal" ); final Player player = PlayerFactory.createPlayer( Side.WHITE ); assertIsLegal( player ); @@ -68,8 +68,8 @@ public void legalSelectedWhite() { @Test public void depth2FromCommandLineRespectedForWhite() { - System.setProperty( "whiteEngine", "Legal" ); - System.setProperty( "whiteDepth", "2" ); + System.setProperty( "white.engine", "Legal" ); + System.setProperty( "white.depth", "2" ); final Player player = PlayerFactory.createPlayer( Side.WHITE ); assertDepth( player, 2 ); @@ -77,8 +77,8 @@ public void depth2FromCommandLineRespectedForWhite() { @Test public void depth1FromCommandLineRespectedForWhite() { - System.setProperty( "whiteEngine", "Legal" ); - System.setProperty( "whiteDepth", "1" ); + System.setProperty( "white.engine", "Legal" ); + System.setProperty( "white.depth", "1" ); final Player player = PlayerFactory.createPlayer( Side.WHITE ); assertDepth( player, 1 ); @@ -86,8 +86,8 @@ public void depth1FromCommandLineRespectedForWhite() { @Test public void depth1FromCommandLineRespectedForBlack() { - System.setProperty( "blackEngine", "Legal" ); - System.setProperty( "blackDepth", "1" ); + System.setProperty( "black.engine", "Legal" ); + System.setProperty( "black.depth", "1" ); final Player player = PlayerFactory.createPlayer( Side.BLACK ); assertDepth( player, 1 ); @@ -95,8 +95,8 @@ public void depth1FromCommandLineRespectedForBlack() { @Test public void depth2FromCommandLineRespectedForBlack() { - System.setProperty( "blackEngine", "Legal" ); - System.setProperty( "blackDepth", "2" ); + System.setProperty( "black.engine", "Legal" ); + System.setProperty( "black.depth", "2" ); final Player player = PlayerFactory.createPlayer( Side.BLACK ); assertDepth( player, 2 ); @@ -104,7 +104,7 @@ public void depth2FromCommandLineRespectedForBlack() { @Test public void defaultDepthIs1() { - System.setProperty( "blackEngine", "Legal" ); + System.setProperty( "black.engine", "Legal" ); final Player player = PlayerFactory.createPlayer( Side.BLACK ); assertDepth( player, 1 ); From 93c36fe548c178a9721c7ce707ca37875bafa903 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 22:58:42 +0200 Subject: [PATCH 078/110] Refactoring: String.format for clarity --- src/main/java/com/leokom/chess/PlayerFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 8b3a10ee8..2e12db19d 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -33,7 +33,7 @@ static class ChessSystemProperty { } String getFor( Side side ) { - return System.getProperty( side.name().toLowerCase() + "." + propertyName ); + return System.getProperty( String.format( "%s.%s", side.name().toLowerCase(), propertyName ) ); } } From 8b4192ee45e3275d5a8debb1b1045f05f123a175 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:07:45 +0200 Subject: [PATCH 079/110] Refactoring to Optional. --- .../java/com/leokom/chess/PlayerFactory.java | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 2e12db19d..c9f739e83 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -8,6 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.Optional; import java.util.function.Supplier; /** @@ -32,8 +33,12 @@ static class ChessSystemProperty { this.propertyName = propertyName; } - String getFor( Side side ) { - return System.getProperty( String.format( "%s.%s", side.name().toLowerCase(), propertyName ) ); + Optional getFor(Side side ) { + return Optional.ofNullable( + System.getProperty( + String.format( "%s.%s", side.name().toLowerCase(), propertyName ) + ) + ); } } @@ -56,34 +61,27 @@ String getFor( Side side ) { * @return new instance of a player */ static Player createPlayer( Side side ) { - final String engineName = new ChessSystemProperty( "engine" ).getFor( side ); - - logger.info( "Engine from system properties: " + engineName + ". Side = " + side ); - - return selectPlayer( side, engineName ).get(); + return selectPlayer( side ).get(); } - private static Supplier< Player > selectPlayer( Side side, String engineName ) { - if ( engineName == null ) { - logger.info( "No selection done. Selecting default player" ); - return getDefaultPlayer( side ); - } - - switch ( engineName ) { - case "Legal": - String depthProperty = new ChessSystemProperty( "depth" ).getFor( side ); - return depthProperty != null ? - new LegalPlayerSupplier( Integer.valueOf( depthProperty ) ) : - new LegalPlayerSupplier(); - - case "Simple": - return new SimplePlayerSupplier(); - case "Winboard": - return new WinboardPlayerSupplier(); - default: - logger.warn( "Unsupported option specified. Selecting default player" ); - return getDefaultPlayer( side ); - } + private static Supplier< Player > selectPlayer( Side side ) { + return new ChessSystemProperty( "engine" ).getFor( side ).map( engineName -> { + switch ( engineName ) { + case "Legal": + return + new ChessSystemProperty( "depth" ).getFor( side ) + .map( Integer::valueOf ) + .map( LegalPlayerSupplier::new ) //takes depth parameter + .orElseGet( LegalPlayerSupplier::new ); //without parameters, default constructor + case "Simple": + return new SimplePlayerSupplier(); + case "Winboard": + return new WinboardPlayerSupplier(); + default: + logger.warn( "Unsupported option specified. Selecting default player" ); + return getDefaultPlayer( side ); + } + } ).orElseGet( () -> getDefaultPlayer( side ) ); } private static Supplier< Player > getDefaultPlayer( Side side ) { From 3249fc5c5b92c405c8b5cf3479a9487f9ccea890 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:09:32 +0200 Subject: [PATCH 080/110] Removed tolerance of unsupported engines --- src/main/java/com/leokom/chess/PlayerFactory.java | 3 +-- src/test/java/com/leokom/chess/PlayerFactoryTest.java | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index c9f739e83..eac6fdd3a 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -78,8 +78,7 @@ private static Supplier< Player > selectPlayer( Side side ) { case "Winboard": return new WinboardPlayerSupplier(); default: - logger.warn( "Unsupported option specified. Selecting default player" ); - return getDefaultPlayer( side ); + throw new IllegalArgumentException( "The engine is not supported: " + engineName ); } } ).orElseGet( () -> getDefaultPlayer( side ) ); } diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index 6853be1e8..3560683cd 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -28,6 +28,13 @@ public void canSelectSimpleEngineForWhite() { assertIsSimple( player ); } + @Test( expected = IllegalArgumentException.class ) + public void failFastOnUnsupportedEngine() { + System.setProperty( "white.engine", "Unsupported" ); + + PlayerFactory.createPlayer( Side.WHITE ); + } + private void assertIsSimple(Player player) { assertEquals( "LegalPlayer : SimpleBrain", player.name() ); } From aa572f9242dd111b0b15855a6280cf7afa06e035 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:14:20 +0200 Subject: [PATCH 081/110] Logging restored at a better place now. --- src/main/java/com/leokom/chess/PlayerFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index eac6fdd3a..68af36921 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -66,6 +66,7 @@ static Player createPlayer( Side side ) { private static Supplier< Player > selectPlayer( Side side ) { return new ChessSystemProperty( "engine" ).getFor( side ).map( engineName -> { + logger.info( "Selecting engine for Side = " + side + " by engine name = " + engineName ); switch ( engineName ) { case "Legal": return From 8aee314641a45eeca9b06ef380aed96d7a111a87 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:17:47 +0200 Subject: [PATCH 082/110] Deeper refactoring --- .../java/com/leokom/chess/PlayerFactory.java | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 68af36921..4205825c7 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -9,7 +9,6 @@ import org.apache.logging.log4j.Logger; import java.util.Optional; -import java.util.function.Supplier; /** * Create players for the chess game @@ -33,7 +32,7 @@ static class ChessSystemProperty { this.propertyName = propertyName; } - Optional getFor(Side side ) { + Optional getFor( Side side ) { return Optional.ofNullable( System.getProperty( String.format( "%s.%s", side.name().toLowerCase(), propertyName ) @@ -61,31 +60,25 @@ Optional getFor(Side side ) { * @return new instance of a player */ static Player createPlayer( Side side ) { - return selectPlayer( side ).get(); - } - - private static Supplier< Player > selectPlayer( Side side ) { - return new ChessSystemProperty( "engine" ).getFor( side ).map( engineName -> { - logger.info( "Selecting engine for Side = " + side + " by engine name = " + engineName ); - switch ( engineName ) { + return new ChessSystemProperty("engine").getFor(side).map(engineName -> { + logger.info("Selecting an engine for Side = " + side + " by engine name = " + engineName); + switch (engineName) { case "Legal": return - new ChessSystemProperty( "depth" ).getFor( side ) - .map( Integer::valueOf ) - .map( LegalPlayerSupplier::new ) //takes depth parameter - .orElseGet( LegalPlayerSupplier::new ); //without parameters, default constructor + new ChessSystemProperty("depth").getFor(side) + .map(Integer::valueOf) + .map(LegalPlayerSupplier::new) //takes depth parameter + .orElseGet(LegalPlayerSupplier::new); //without parameters, default constructor case "Simple": return new SimplePlayerSupplier(); case "Winboard": return new WinboardPlayerSupplier(); default: - throw new IllegalArgumentException( "The engine is not supported: " + engineName ); + throw new IllegalArgumentException( "The engine is not supported: " + engineName); } - } ).orElseGet( () -> getDefaultPlayer( side ) ); - } - - private static Supplier< Player > getDefaultPlayer( Side side ) { - logger.info( "Selecting default engine for Side = " + side ); - return side == Side.WHITE ? new WinboardPlayerSupplier() : new LegalPlayerSupplier(); + }).orElseGet(() -> { + logger.info( "Selecting a default engine for Side = " + side ); + return side == Side.WHITE ? new WinboardPlayerSupplier() : new LegalPlayerSupplier(); + }).get(); } } From eec92f6bc51f15aa069789e9f4ab846347c1fbeb Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:23:46 +0200 Subject: [PATCH 083/110] Comments in bat files --- src/main/bat/runTwoEngines.bat | 1 + src/main/bat/variables.bat | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/bat/runTwoEngines.bat b/src/main/bat/runTwoEngines.bat index 740da8b68..874fa1a9d 100644 --- a/src/main/bat/runTwoEngines.bat +++ b/src/main/bat/runTwoEngines.bat @@ -6,6 +6,7 @@ SET RUNNABLE_JAR_DIRECTORY_2=%WINBOARD_INSTALLATION_PATH%\LeokomChessTest SET RUN_JAR_PATH_2=%RUNNABLE_JAR_DIRECTORY_2%\Chess.jar rem you may specify -Dblack.engine=Simple (or -Dblack=Simple if the second instance is Chess <= 0.3) +rem for LegalPlayer you may specify -Dblack.depth=2 (if the second instance is Chess >= 0.4) SET RUN_OPTIONS_2= SET ENGINE_2=%JAVA_PATH% %RUN_OPTIONS_2% -jar %RUN_JAR_PATH_2% diff --git a/src/main/bat/variables.bat b/src/main/bat/variables.bat index 034306320..13e78a36c 100644 --- a/src/main/bat/variables.bat +++ b/src/main/bat/variables.bat @@ -7,6 +7,7 @@ SET RUNNABLE_JAR_DIRECTORY=%WINBOARD_INSTALLATION_PATH%\LeokomChess SET JAVA_PATH=Q:\Program Files\Java\jdk1.8.0_162\bin\java.exe SET RUN_JAR_PATH=%RUNNABLE_JAR_DIRECTORY%\Chess.jar rem you may pass -Dblack.engine=Simple to choose a different engine for blacks +rem for LegalPlayer you may specify -Dblack.depth SET RUN_OPTIONS= SET ENGINE=%JAVA_PATH% %RUN_OPTIONS% -jar %RUN_JAR_PATH% \ No newline at end of file From 68ccf37abd4c38bbbf7eeb598e3b0deae8b99a5b Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:25:35 +0200 Subject: [PATCH 084/110] Import clean up --- .../java/com/leokom/chess/player/legal/LegalPlayerSupplier.java | 1 - .../chess/player/legal/brain/simple/SimplePlayerSupplier.java | 1 - .../com/leokom/chess/player/winboard/WinboardPlayerSupplier.java | 1 - 3 files changed, 3 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/LegalPlayerSupplier.java b/src/main/java/com/leokom/chess/player/legal/LegalPlayerSupplier.java index 36991501f..5e44dcce3 100644 --- a/src/main/java/com/leokom/chess/player/legal/LegalPlayerSupplier.java +++ b/src/main/java/com/leokom/chess/player/legal/LegalPlayerSupplier.java @@ -1,7 +1,6 @@ package com.leokom.chess.player.legal; import com.leokom.chess.player.Player; -import com.leokom.chess.player.legal.LegalPlayer; import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator; import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain; diff --git a/src/main/java/com/leokom/chess/player/legal/brain/simple/SimplePlayerSupplier.java b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimplePlayerSupplier.java index 390009f58..c537c93f0 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/simple/SimplePlayerSupplier.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimplePlayerSupplier.java @@ -2,7 +2,6 @@ import com.leokom.chess.player.Player; import com.leokom.chess.player.legal.LegalPlayer; -import com.leokom.chess.player.legal.brain.simple.SimpleBrain; import java.util.function.Supplier; diff --git a/src/main/java/com/leokom/chess/player/winboard/WinboardPlayerSupplier.java b/src/main/java/com/leokom/chess/player/winboard/WinboardPlayerSupplier.java index 1ab53e236..49d42c2f9 100644 --- a/src/main/java/com/leokom/chess/player/winboard/WinboardPlayerSupplier.java +++ b/src/main/java/com/leokom/chess/player/winboard/WinboardPlayerSupplier.java @@ -1,7 +1,6 @@ package com.leokom.chess.player.winboard; import com.leokom.chess.player.Player; -import com.leokom.chess.player.winboard.WinboardPlayer; import java.util.function.Supplier; From f95e7a11b3aa05a0cfe448eaa2d77376ed14cabc Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:28:23 +0200 Subject: [PATCH 085/110] Legal player is created with depth even if its engine is not provided. --- src/main/java/com/leokom/chess/PlayerFactory.java | 15 +++++++++------ .../java/com/leokom/chess/PlayerFactoryTest.java | 9 +++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 4205825c7..89a71937f 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -64,11 +64,7 @@ static Player createPlayer( Side side ) { logger.info("Selecting an engine for Side = " + side + " by engine name = " + engineName); switch (engineName) { case "Legal": - return - new ChessSystemProperty("depth").getFor(side) - .map(Integer::valueOf) - .map(LegalPlayerSupplier::new) //takes depth parameter - .orElseGet(LegalPlayerSupplier::new); //without parameters, default constructor + return getLegalPlayerSupplier( side ); case "Simple": return new SimplePlayerSupplier(); case "Winboard": @@ -78,7 +74,14 @@ static Player createPlayer( Side side ) { } }).orElseGet(() -> { logger.info( "Selecting a default engine for Side = " + side ); - return side == Side.WHITE ? new WinboardPlayerSupplier() : new LegalPlayerSupplier(); + return side == Side.WHITE ? new WinboardPlayerSupplier() : getLegalPlayerSupplier( side ); }).get(); } + + private static LegalPlayerSupplier getLegalPlayerSupplier( Side side ) { + return new ChessSystemProperty("depth").getFor(side) + .map(Integer::valueOf) + .map(LegalPlayerSupplier::new) //takes depth parameter + .orElseGet(LegalPlayerSupplier::new); //without parameters, default constructor + } } diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index 3560683cd..7f8fe3fc5 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -109,6 +109,15 @@ public void depth2FromCommandLineRespectedForBlack() { assertDepth( player, 2 ); } + @Test + public void legalPlayerDepthCanBeProvidedEvenIfEngineIsNotProvided() { + //because legal is default one + System.setProperty( "black.depth", "2" ); + + final Player player = PlayerFactory.createPlayer( Side.BLACK ); + assertDepth( player, 2 ); + } + @Test public void defaultDepthIs1() { System.setProperty( "black.engine", "Legal" ); From 73bc356d5af3b4de4282fcda4a8e990f18ad956b Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:30:36 +0200 Subject: [PATCH 086/110] Let command line suggest depth 2 explicitly by default. --- src/main/bat/variables.bat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/bat/variables.bat b/src/main/bat/variables.bat index 13e78a36c..11ec517c5 100644 --- a/src/main/bat/variables.bat +++ b/src/main/bat/variables.bat @@ -7,7 +7,7 @@ SET RUNNABLE_JAR_DIRECTORY=%WINBOARD_INSTALLATION_PATH%\LeokomChess SET JAVA_PATH=Q:\Program Files\Java\jdk1.8.0_162\bin\java.exe SET RUN_JAR_PATH=%RUNNABLE_JAR_DIRECTORY%\Chess.jar rem you may pass -Dblack.engine=Simple to choose a different engine for blacks -rem for LegalPlayer you may specify -Dblack.depth -SET RUN_OPTIONS= +rem for LegalPlayer you may specify -Dblack.depth (1 or 2) +SET RUN_OPTIONS=-Dblack.depth=2 SET ENGINE=%JAVA_PATH% %RUN_OPTIONS% -jar %RUN_JAR_PATH% \ No newline at end of file From 89472a09a8c1cc805765ea2a86ca5d5f8d9f006b Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 5 Feb 2019 23:35:53 +0200 Subject: [PATCH 087/110] Documentation --- .../java/com/leokom/chess/MainRunner.java | 22 +++++++++++++++---- .../java/com/leokom/chess/PlayerFactory.java | 4 +++- .../com/leokom/chess/PlayerFactoryTest.java | 6 ----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/leokom/chess/MainRunner.java b/src/main/java/com/leokom/chess/MainRunner.java index 4d098e762..85bace134 100644 --- a/src/main/java/com/leokom/chess/MainRunner.java +++ b/src/main/java/com/leokom/chess/MainRunner.java @@ -22,7 +22,7 @@ private MainRunner() { * * The parameters are provided via easier-to-use Java system properties way. *

- * Supported parameters: + * General parameters: *

    *
  • -Dwhite.engine=engineName
  • *
  • -Dblack.engine=engineName
  • @@ -34,11 +34,25 @@ private MainRunner() { *
  • Simple
  • *
  • Legal
  • *
+ * + * Default players: + *
    + *
  • -Dwhite.engine=Winboard
  • + *
  • -Dblack.engine=Legal
  • + *
* - * Default players: + *

+ * + * Optional parameters for LegalPlayer + *

    + *
  • -Dwhite.depth=depth in plies
  • + *
  • -Dblack.depth=depth in plies
  • + *
+ * + * depth in plies can be any of: *
    - *
  • -Dwhite.engine=Winboard
  • - *
  • -Dblack.engine=Legal
  • + *
  • 1
  • + *
  • 2
  • *
* * For Winboard opponents always specify them as Black diff --git a/src/main/java/com/leokom/chess/PlayerFactory.java b/src/main/java/com/leokom/chess/PlayerFactory.java index 89a71937f..744547de3 100644 --- a/src/main/java/com/leokom/chess/PlayerFactory.java +++ b/src/main/java/com/leokom/chess/PlayerFactory.java @@ -54,7 +54,9 @@ Optional getFor( Side side ) { * Winboard vs any other engine that uses System.out has no practical use (UCI?) * * LegalPlayer vs LegalPlayer is possible but can lead to StackOverflow due to - * no limits on move amount and single-threaded model of execution + * no limits on move amount and single-threaded model of execution. + * + * LegalPlayer supports optional depth parameter. * * @param side side to create * @return new instance of a player diff --git a/src/test/java/com/leokom/chess/PlayerFactoryTest.java b/src/test/java/com/leokom/chess/PlayerFactoryTest.java index 7f8fe3fc5..a26a5b92a 100644 --- a/src/test/java/com/leokom/chess/PlayerFactoryTest.java +++ b/src/test/java/com/leokom/chess/PlayerFactoryTest.java @@ -126,12 +126,6 @@ public void defaultDepthIs1() { assertDepth( player, 1 ); } - /* - More cases: - - extend *.bat file(s) - - extend docs - */ - private void assertDepth( Player player, int expectedDepth ) { //shallow yet good enough check assertThat( player.name(), CoreMatchers.containsString( String.valueOf( expectedDepth ) ) ); From 37ea5872863bf0db9d4097e4cfd5173fe7fa4aed Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 7 Feb 2019 21:46:17 +0200 Subject: [PATCH 088/110] TODO moved to https://github.com/lrozenblyum/chess/issues/290 --- .../chess/player/legal/brain/denormalized/DenormalizedBrain.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index 3306d7eef..a5a49b078 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -64,7 +64,6 @@ private Table generateWithWeights( Table result = HashBasedTable.create(); - //TODO: if one of normalized evaluators is disabled, here we'll get NPE. normalizedTable.cellSet().forEach( cell -> result.put( cell.getRowKey(), cell.getColumnKey(), cell.getValue() * standardWeights.get( cell.getRowKey() ) ) ); From 1b0aca40c236c6305961353c2ffee02ed878c7e7 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 7 Feb 2019 21:47:35 +0200 Subject: [PATCH 089/110] TODO -> https://github.com/lrozenblyum/chess/issues/290 --- .../chess/player/legal/brain/normalized/TerminalEvaluator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java index f12941afe..65a09d769 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/TerminalEvaluator.java @@ -27,7 +27,6 @@ public double evaluateMove( Position position, Move move ) { final Position result = position.move( move ); if ( !result.isTerminal() ) { - //TODO: maybe throw an exception if we allow calling just in terminal cases return WORST_MOVE; } From 88202087abea8f9e2c09f6f34a87b3b3ca0ffaf4 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 7 Feb 2019 21:53:07 +0200 Subject: [PATCH 090/110] Composite move handling split to #291 --- .../chess/player/legal/brain/normalized/NormalizedBrain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index a67056e8f..15dd77f44 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -106,7 +106,7 @@ public List findBestMove(S position ) { //can be empty in case of terminal position if ( ! bestMove.isEmpty() ) { //negating because bigger for the opponents means worse for the current player - //TODO: what if > 1 + //composite moves handling split to https://github.com/lrozenblyum/chess/issues/291 moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); } else { LogManager.getLogger().info( "Evaluating just the current level" ); From fd7a47db3da6f935ceb717f3bdbcf2480d7aaf7c Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 7 Feb 2019 22:06:45 +0200 Subject: [PATCH 091/110] Code clean up --- src/main/java/com/leokom/chess/engine/Position.java | 2 +- .../leokom/chess/player/legal/brain/common/EvaluatorType.java | 2 +- .../leokom/chess/player/legal/brain/common/GenericBrain.java | 2 +- .../player/legal/brain/denormalized/DenormalizedBrain.java | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/leokom/chess/engine/Position.java b/src/main/java/com/leokom/chess/engine/Position.java index 4c286e914..cca28985b 100644 --- a/src/main/java/com/leokom/chess/engine/Position.java +++ b/src/main/java/com/leokom/chess/engine/Position.java @@ -642,7 +642,7 @@ public Position move( String squareFrom, String move ) { * @return new position, which is received from current by making 1 move */ @Override - public Position move(Move move) { + public Position move( Move move ) { return new PositionGenerator( this ).generate( move ); } diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/EvaluatorType.java b/src/main/java/com/leokom/chess/player/legal/brain/common/EvaluatorType.java index 44171ee56..82afebec8 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/EvaluatorType.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/EvaluatorType.java @@ -8,7 +8,7 @@ * Date-time: 19.04.16 22:46 */ public enum EvaluatorType { - TERMINAL, + TERMINAL, CASTLING_SAFETY, CENTER_CONTROL, MATERIAL, diff --git a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java index b2fe4f30a..90b428b5b 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/common/GenericBrain.java @@ -50,4 +50,4 @@ default T findBestMoveForOpponent(S position ) { default String name() { return this.getClass().getSimpleName(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index a5a49b078..924ef7f51 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -137,6 +137,8 @@ private interface Formula { double accept( double maxValue, double minValue, Double value ); } + //this class is an initial attempt to introduce symmetry between denormalized and normalized brains + //as a matter of fact, the denormalized brain has mixed responsibilities of brain and master evaluator private class DenormalizedMasterEvaluator implements Evaluator { private final Table weightedTable; From 0bdb49e818fb39c43a78e4434efe8ce84fac4fbf Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Thu, 7 Feb 2019 22:17:47 +0200 Subject: [PATCH 092/110] Logging configuration improved. Only Normalized brain uses additional information from MDC. --- src/main/resources/log4j2.xml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index c28fe541c..1530ec093 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -1,13 +1,23 @@ + + + + + - + + + + + + From c287361c1453b207dd4577f6d0d7c8ec8e1d61c7 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Sun, 10 Feb 2019 22:19:30 +0200 Subject: [PATCH 093/110] Improved logging in simulator --- src/test/java/com/leokom/chess/Simulator.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/leokom/chess/Simulator.java b/src/test/java/com/leokom/chess/Simulator.java index 36ab08777..636739826 100644 --- a/src/test/java/com/leokom/chess/Simulator.java +++ b/src/test/java/com/leokom/chess/Simulator.java @@ -2,6 +2,7 @@ import com.leokom.chess.player.Player; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.ArrayList; import java.util.List; @@ -23,6 +24,7 @@ class Simulator { private static final int GAMES_IN_SINGLE_ITERATION = 2; private final Player first; private final Player second; + private final Logger logger; private int timesToRun = 1; @@ -33,6 +35,7 @@ class Simulator { Simulator( Player first, Player second ) { this.first = first; this.second = second; + this.logger = LogManager.getLogger(); } /** @@ -45,18 +48,20 @@ class Simulator { * @return statistics about game results */ SimulatorStatistics run() { - LogManager.getLogger().info("Starting simulation for {} and {}", first.name(), second.name()); + logger.info("Starting simulation for {} and {}", first.name(), second.name()); List< Player > winners = new ArrayList<>(); IntStream.rangeClosed( 1, timesToRun ).forEach( iteration -> { + logger.info( "Simulation # {} of {}: starting...", iteration, timesToRun ); winners.add( createGame( first, second ).run() ); winners.add( createGame( second, first ).run() ); + logger.info( "Simulation # {} of {}: done", iteration, timesToRun ); } ); final long firstWins = countWinsOf( winners, first ); final long secondWins = countWinsOf( winners, second ); final long totalGames = timesToRun * GAMES_IN_SINGLE_ITERATION; SimulatorStatistics simulatorStatistics = new SimulatorStatistics(totalGames, firstWins, secondWins); - LogManager.getLogger().info("The simulation has been finished. Stats: {}", simulatorStatistics); + logger.info("The simulation has been finished. Stats: {}", simulatorStatistics); return simulatorStatistics; } From fd3c0d1ab7d4dbebe697e94dff6a8196d4f57aa1 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 12 Feb 2019 22:34:02 +0200 Subject: [PATCH 094/110] Comment on terminal evaluator presence in weights --- .../player/legal/brain/internal/common/EvaluatorWeights.java | 1 + .../chess/player/legal/brain/normalized/MasterEvaluator.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java index d6204606b..d5b4b62a2 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/internal/common/EvaluatorWeights.java @@ -47,6 +47,7 @@ private void verifyRange(Map weights) { private static Map getStandardWeights() { //TODO: refactor to constant immutable map Map result = new HashMap<>(); + //terminal evaluator is still here till https://github.com/lrozenblyum/chess/issues/290 result.put( TERMINAL, HIGHEST_PRIORITY ); result.put( CASTLING_SAFETY, NORMAL_PRIORITY ); result.put( CENTER_CONTROL, NORMAL_PRIORITY ); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 9de5b4cc2..bb6524da4 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -57,7 +57,7 @@ public double evaluateMove( Position position, Move move ) { } // Terminal evaluator excluded because it's used above. - // NOTE: it's still in evaluatorWeights until DenormalizedBrain uses it + // NOTE: it's still in evaluatorWeights until DenormalizedBrain uses it (till https://github.com/lrozenblyum/chess/issues/290) double result = evaluatorWeights.stream().filter( evaluatorEntry -> evaluatorEntry.getKey() != EvaluatorType.TERMINAL ).mapToDouble(evaluatorEntry -> { From 5e9aa258c10650e165190be213d9711c5ae439c1 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 12 Feb 2019 22:51:30 +0200 Subject: [PATCH 095/110] Fixed bug: we excluded terminal evaluator but still counted it. --- .../brain/normalized/MasterEvaluator.java | 15 +++++++----- .../brain/normalized/MasterEvaluatorTest.java | 24 +++++++++++++++++-- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index bb6524da4..01403270e 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -3,6 +3,7 @@ import com.leokom.chess.engine.Move; import com.leokom.chess.engine.Position; import com.leokom.chess.player.legal.brain.common.Evaluator; +import com.leokom.chess.player.legal.brain.common.EvaluatorFactory; import com.leokom.chess.player.legal.brain.common.EvaluatorType; import com.leokom.chess.player.legal.brain.internal.common.EvaluatorWeights; import org.apache.logging.log4j.LogManager; @@ -23,11 +24,11 @@ public class MasterEvaluator implements Evaluator { //compare 1-to-another logic private final EvaluatorWeights evaluatorWeights; - private final NormalizedEvaluatorFactory evaluatorFactory; + private final EvaluatorFactory evaluatorFactory; public MasterEvaluator() { //standard weights - this( new EvaluatorWeights() ); + this( new EvaluatorWeights(), new NormalizedEvaluatorFactory() ); } /** @@ -40,12 +41,12 @@ public MasterEvaluator() { */ MasterEvaluator( Map weights ) { //custom weights - this( new EvaluatorWeights( weights ) ); + this( new EvaluatorWeights( weights ), new NormalizedEvaluatorFactory() ); } - private MasterEvaluator( EvaluatorWeights evaluatorWeights ) { + MasterEvaluator( EvaluatorWeights evaluatorWeights, EvaluatorFactory evaluatorFactory ) { this.evaluatorWeights = evaluatorWeights; - this.evaluatorFactory = new NormalizedEvaluatorFactory(); + this.evaluatorFactory = evaluatorFactory; } @Override @@ -71,7 +72,9 @@ public double evaluateMove( Position position, Move move ) { //result that is in [ 0, 1 ] range //depends on the fact that the weights themselves are in [ 0, 1 ] - double normalizedResult = result / evaluatorWeights.size(); + + //-1 because we excluded terminal evaluator + double normalizedResult = result / ( evaluatorWeights.size() - 1 ); LOG.info("{} ===> {} ===> {}", move, result, normalizedResult); return normalizedResult; diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index 030813204..d50a70ed6 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -3,9 +3,12 @@ import com.leokom.chess.engine.*; import com.leokom.chess.player.legal.brain.common.Evaluator; import com.leokom.chess.player.legal.brain.common.EvaluatorAsserts; +import com.leokom.chess.player.legal.brain.common.EvaluatorFactory; import com.leokom.chess.player.legal.brain.common.EvaluatorType; +import com.leokom.chess.player.legal.brain.internal.common.EvaluatorWeights; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import java.util.Arrays; import java.util.HashMap; @@ -70,8 +73,25 @@ public void allMovesMustBeEvaluatedFrom0To1EvenWithCustomWeights() { Map weights = new HashMap<>(); Arrays.stream( EvaluatorType.values() ).forEach( type -> weights.put( type, 1.0 ) ); - MasterEvaluator masterEvaluatorWithCustomWeigths = new MasterEvaluator(weights); - assertAllMovesEvaluatedIn0To1Range( Position.getInitialPosition(), masterEvaluatorWithCustomWeigths ); + MasterEvaluator masterEvaluatorWithCustomWeights = new MasterEvaluator(weights); + assertAllMovesEvaluatedIn0To1Range( Position.getInitialPosition(), masterEvaluatorWithCustomWeights ); + } + + @Test + public void singleNonTerminalEvaluatorWeightMaximalResultIs1() { + Map weights = new HashMap<>(); + weights.put( EvaluatorType.TERMINAL, 1.0 ); + weights.put( EvaluatorType.CASTLING_SAFETY, 1.0 ); //max weight + + EvaluatorFactory factory = Mockito.mock(EvaluatorFactory.class); + Evaluator castlingEvaluatorMock = Mockito.mock(Evaluator.class); + //max estimate + Mockito.when( castlingEvaluatorMock.evaluateMove( Mockito.any(), Mockito.any() ) ).thenReturn( 1.0 ); + Mockito.when( factory.get( EvaluatorType.CASTLING_SAFETY )) .thenReturn( castlingEvaluatorMock ); + + MasterEvaluator masterEvaluator = new MasterEvaluator( new EvaluatorWeights( weights ), factory ); + double evaluation = masterEvaluator.evaluateMove(Position.getInitialPosition(), new Move("e2", "e4")); + assertEquals( 1.0, evaluation, 0 ); } //losing should get the minimal possible value From ebe70377dc4804c36e52937fe9ea48e27013c848 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 12 Feb 2019 22:53:18 +0200 Subject: [PATCH 096/110] Refactoring: reduced duplication. Removed magic -1 --- .../legal/brain/normalized/MasterEvaluator.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 01403270e..2e542068a 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger; import java.util.Map; +import java.util.stream.Stream; /** * Central brain of a move ('brains') @@ -59,9 +60,7 @@ public double evaluateMove( Position position, Move move ) { // Terminal evaluator excluded because it's used above. // NOTE: it's still in evaluatorWeights until DenormalizedBrain uses it (till https://github.com/lrozenblyum/chess/issues/290) - double result = evaluatorWeights.stream().filter( evaluatorEntry -> - evaluatorEntry.getKey() != EvaluatorType.TERMINAL - ).mapToDouble(evaluatorEntry -> { + double result = evaluatorsExceptTerminal().mapToDouble(evaluatorEntry -> { final Evaluator evaluator = evaluatorFactory.get(evaluatorEntry.getKey()); final double weight = evaluatorEntry.getValue(); final double evaluatorResponse = evaluator.evaluateMove(position, move); @@ -72,11 +71,15 @@ public double evaluateMove( Position position, Move move ) { //result that is in [ 0, 1 ] range //depends on the fact that the weights themselves are in [ 0, 1 ] - - //-1 because we excluded terminal evaluator - double normalizedResult = result / ( evaluatorWeights.size() - 1 ); + double normalizedResult = result / evaluatorsExceptTerminal().count(); LOG.info("{} ===> {} ===> {}", move, result, normalizedResult); return normalizedResult; } + + private Stream> evaluatorsExceptTerminal() { + return evaluatorWeights.stream().filter( evaluatorEntry -> + evaluatorEntry.getKey() != EvaluatorType.TERMINAL + ); + } } From 11b09c3fa8eca849ce8fbc47a8b1434a92bc41e8 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 12 Feb 2019 22:58:12 +0200 Subject: [PATCH 097/110] Refactoring: symmetry. MasterEvaluator is not responsible now for Evaluator weights building. --- .../legal/brain/normalized/MasterEvaluator.java | 12 ++++-------- .../brain/normalized/MasterEvaluatorBuilder.java | 2 +- .../legal/brain/normalized/MasterEvaluatorTest.java | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index 2e542068a..cc4622534 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -29,20 +29,16 @@ public class MasterEvaluator implements Evaluator { public MasterEvaluator() { //standard weights - this( new EvaluatorWeights(), new NormalizedEvaluatorFactory() ); + this( new EvaluatorWeights() ); } /** * create evaluator with custom weights * @param weights evaluator -> weight - * @throws IllegalArgumentException if any weight is outside [ 0, 1 ] range */ - /* - Alternative to throwing the exception would be normalizing the weights on-fly. At the moment - not needed - */ - MasterEvaluator( Map weights ) { - //custom weights - this( new EvaluatorWeights( weights ), new NormalizedEvaluatorFactory() ); + MasterEvaluator( EvaluatorWeights weights ) { + //standard evaluator factory + this( weights, new NormalizedEvaluatorFactory() ); } MasterEvaluator( EvaluatorWeights evaluatorWeights, EvaluatorFactory evaluatorFactory ) { diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java index 3dd68ad66..f13ea898f 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorBuilder.java @@ -23,6 +23,6 @@ public MasterEvaluatorBuilder weight( EvaluatorType evaluatorType, double weight } public MasterEvaluator build() { - return new MasterEvaluator( weights ); + return new MasterEvaluator( new EvaluatorWeights( weights ) ); } } \ No newline at end of file diff --git a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java index d50a70ed6..25e1313ea 100644 --- a/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java +++ b/src/test/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluatorTest.java @@ -73,7 +73,7 @@ public void allMovesMustBeEvaluatedFrom0To1EvenWithCustomWeights() { Map weights = new HashMap<>(); Arrays.stream( EvaluatorType.values() ).forEach( type -> weights.put( type, 1.0 ) ); - MasterEvaluator masterEvaluatorWithCustomWeights = new MasterEvaluator(weights); + MasterEvaluator masterEvaluatorWithCustomWeights = new MasterEvaluator(new EvaluatorWeights(weights)); assertAllMovesEvaluatedIn0To1Range( Position.getInitialPosition(), masterEvaluatorWithCustomWeights ); } From c70c5eafc773d0fbf76499ebfe8c7cbc4ed92c5f Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Tue, 12 Feb 2019 23:18:26 +0200 Subject: [PATCH 098/110] More logging --- .../chess/player/legal/brain/normalized/NormalizedBrain.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 15dd77f44..793722f03 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -121,7 +121,9 @@ public List findBestMove(S position ) { } ); } - return getMoveWithMaxRating(moveRatings); + List bestMove = getMoveWithMaxRating(moveRatings); + LogManager.getLogger().info( "Best move(s): {}", bestMove ); + return bestMove; } @Override From eaa73b181e4ac54b7ea4f712b820689145c457bd Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 22:46:27 +0200 Subject: [PATCH 099/110] Refactoring --- .../brain/normalized/MasterEvaluator.java | 2 +- .../brain/normalized/NormalizedBrain.java | 85 +++++++++++-------- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java index cc4622534..01f0eac9d 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/MasterEvaluator.java @@ -13,7 +13,7 @@ import java.util.stream.Stream; /** - * Central brain of a move ('brains') + * Main normalized evaluator. Delegates evaluation to other normalized evaluators. */ public class MasterEvaluator implements Evaluator { private static final Logger LOG = LogManager.getLogger(); diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 793722f03..3ebd1c83e 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -84,46 +84,57 @@ public NormalizedBrain(GenericEvaluator brains, int pliesDepth ) { * */ @Override - public List findBestMove(S position ) { + public List findBestMove( S position ) { + //just 1 or 2 is supported now + Map moveRatings = + pliesDepth == 1 ? + getSinglePlyRatings( position ) : + getTwoPliesRatings( position ); + List bestMove = getMoveWithMaxRating(moveRatings); + LogManager.getLogger().info( "Best move(s): {}", bestMove ); + return bestMove; + } + + private Map getTwoPliesRatings(S position) { Map moveRatings = new HashMap<>(); + //filtering out draw offers till #161 + getMovesWithoutDrawOffer( position ).forEach( move -> { + ThreadContext.put( "moveBeingAnalyzed", move.toString() ); + + S target = position.move( move ); + List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); + + //can be empty in case of terminal position + if ( ! bestMove.isEmpty() ) { + //negating because bigger for the opponents means worse for the current player + //composite moves handling split to https://github.com/lrozenblyum/chess/issues/291 + moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); + } else { + LogManager.getLogger().info( "Evaluating just the current level" ); + //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range + //where all the second level moves exist + // highly depends on evaluator range [ 0, 1 ] which is guaranteed by ValidatingNormalizedEvaluator + moveRatings.put( move, brains.evaluateMove( position, move ) - 1 ); //falling back to the 1'st level + } + + LogManager.getLogger().info( "result = {}", moveRatings.get( move ) ); + ThreadContext.clearAll(); + } ); + return moveRatings; + } - if ( pliesDepth == 1 ) { - //filtering Draw offers till #161 is solved - //this looks safe since Offer draw cannot be a single legal move in a position. + //thinking for 1 ply + private Map getSinglePlyRatings( S position ) { + Map moveRatings = new HashMap<>(); + //filtering Draw offers till #161 is solved + //this looks safe since Offer draw cannot be a single legal move in a position. - //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches - getMovesWithoutDrawOffer(position).forEach( move -> - moveRatings.put(move, brains.evaluateMove( position, move ) ) - ); - } - else { //just 2 is supported now - getMovesWithoutDrawOffer( position ).forEach( move -> { - ThreadContext.put( "moveBeingAnalyzed", move.toString() ); - - S target = position.move( move ); - List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); - - //can be empty in case of terminal position - if ( ! bestMove.isEmpty() ) { - //negating because bigger for the opponents means worse for the current player - //composite moves handling split to https://github.com/lrozenblyum/chess/issues/291 - moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); - } else { - LogManager.getLogger().info( "Evaluating just the current level" ); - //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range - //where all the second level moves exist - // highly depends on evaluator range [ 0, 1 ] which is guaranteed by ValidatingNormalizedEvaluator - moveRatings.put( move, brains.evaluateMove( position, move ) - 1 ); //falling back to 1'st level - } - - LogManager.getLogger().info( "result = {}", moveRatings.get( move ) ); - ThreadContext.clearAll(); - } ); - } + //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches + getMovesWithoutDrawOffer(position).forEach( move -> + moveRatings.put(move, brains.evaluateMove( position, move ) ) + ); - List bestMove = getMoveWithMaxRating(moveRatings); - LogManager.getLogger().info( "Best move(s): {}", bestMove ); - return bestMove; + return moveRatings; } @Override @@ -135,7 +146,7 @@ private Stream getMovesWithoutDrawOffer(S position) { return position.getMoves().stream().filter(move -> move != Move.OFFER_DRAW); } - private List getMoveWithMaxRating(Map moveValues ) { + private List getMoveWithMaxRating( Map moveValues ) { return moveValues.entrySet().stream() .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) From 68ec1398d3eefc5bd0673518a381447db90496d9 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 22:49:54 +0200 Subject: [PATCH 100/110] Deeper refactoring --- .../brain/normalized/NormalizedBrain.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 3ebd1c83e..5938c1bd7 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -95,7 +95,7 @@ public List findBestMove( S position ) { return bestMove; } - private Map getTwoPliesRatings(S position) { + private Map getTwoPliesRatings( S position ) { Map moveRatings = new HashMap<>(); //filtering out draw offers till #161 getMovesWithoutDrawOffer( position ).forEach( move -> { @@ -106,24 +106,27 @@ private Map getTwoPliesRatings(S position) { //can be empty in case of terminal position if ( ! bestMove.isEmpty() ) { - //negating because bigger for the opponents means worse for the current player - //composite moves handling split to https://github.com/lrozenblyum/chess/issues/291 - moveRatings.put(move, -brains.evaluateMove(target, bestMove.get(0))); - } else { LogManager.getLogger().info( "Evaluating just the current level" ); - //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range - //where all the second level moves exist - // highly depends on evaluator range [ 0, 1 ] which is guaranteed by ValidatingNormalizedEvaluator - moveRatings.put( move, brains.evaluateMove( position, move ) - 1 ); //falling back to the 1'st level } + double moveRating = bestMove.isEmpty() ? + //falling back to the 1'st level + //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range + //where all the second level moves exist + // highly depends on evaluator range [ 0, 1 ] which is guaranteed by ValidatingNormalizedEvaluator + brains.evaluateMove(position, move) - 1 : + //negating because bigger for the opponents means worse for the current player + //composite moves handling split to https://github.com/lrozenblyum/chess/issues/291 + -brains.evaluateMove(target, bestMove.get(0)); + + moveRatings.put(move, moveRating); + LogManager.getLogger().info( "result = {}", moveRatings.get( move ) ); ThreadContext.clearAll(); } ); return moveRatings; } - //thinking for 1 ply private Map getSinglePlyRatings( S position ) { Map moveRatings = new HashMap<>(); //filtering Draw offers till #161 is solved From 36e9bcb2922cce313ba3d54a6b97ced9096a96fc Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:04:28 +0200 Subject: [PATCH 101/110] Good refactoring. A lot of duplication removed --- .../brain/normalized/NormalizedBrain.java | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 5938c1bd7..08edd9fb4 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -9,11 +9,14 @@ import org.apache.logging.log4j.ThreadContext; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.ToDoubleBiFunction; import java.util.stream.Stream; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + /** * Initial decision maker. * @@ -86,58 +89,55 @@ public NormalizedBrain(GenericEvaluator brains, int pliesDepth ) { @Override public List findBestMove( S position ) { //just 1 or 2 is supported now + ToDoubleBiFunction< S, T > moveEvaluator = pliesDepth == 1 ? + this::evaluateMoveViaSinglePly : + this::evaluateMoveViaTwoPlies; + + //filtering Draw offers till #161 is solved + //this looks safe since Offer draw cannot be a single legal move in a position. + + //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches + //filtering out draw offers till #161 Map moveRatings = - pliesDepth == 1 ? - getSinglePlyRatings( position ) : - getTwoPliesRatings( position ); - List bestMove = getMoveWithMaxRating(moveRatings); + getMovesWithoutDrawOffer( position ).collect( + toMap( + identity(), + move -> moveEvaluator.applyAsDouble( position, move ) + ) + ); + List bestMove = getMoveWithMaxRating( moveRatings ); LogManager.getLogger().info( "Best move(s): {}", bestMove ); return bestMove; } - private Map getTwoPliesRatings( S position ) { - Map moveRatings = new HashMap<>(); - //filtering out draw offers till #161 - getMovesWithoutDrawOffer( position ).forEach( move -> { - ThreadContext.put( "moveBeingAnalyzed", move.toString() ); - - S target = position.move( move ); - List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); - - //can be empty in case of terminal position - if ( ! bestMove.isEmpty() ) { - LogManager.getLogger().info( "Evaluating just the current level" ); - } - - double moveRating = bestMove.isEmpty() ? - //falling back to the 1'st level - //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range - //where all the second level moves exist - // highly depends on evaluator range [ 0, 1 ] which is guaranteed by ValidatingNormalizedEvaluator - brains.evaluateMove(position, move) - 1 : - //negating because bigger for the opponents means worse for the current player - //composite moves handling split to https://github.com/lrozenblyum/chess/issues/291 - -brains.evaluateMove(target, bestMove.get(0)); - - moveRatings.put(move, moveRating); - - LogManager.getLogger().info( "result = {}", moveRatings.get( move ) ); - ThreadContext.clearAll(); - } ); - return moveRatings; - } + private double evaluateMoveViaTwoPlies( S position, T move ) { + ThreadContext.put( "moveBeingAnalyzed", move.toString() ); - private Map getSinglePlyRatings( S position ) { - Map moveRatings = new HashMap<>(); - //filtering Draw offers till #161 is solved - //this looks safe since Offer draw cannot be a single legal move in a position. + S target = position.move( move ); + List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); - //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches - getMovesWithoutDrawOffer(position).forEach( move -> - moveRatings.put(move, brains.evaluateMove( position, move ) ) - ); + //can be empty in case of terminal position + if ( ! bestMove.isEmpty() ) { + LogManager.getLogger().info( "Evaluating just the current level" ); + } + + double moveRating = bestMove.isEmpty() ? + //falling back to the 1'st level + //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range + //where all the second level moves exist + // highly depends on evaluator range [ 0, 1 ] which is guaranteed by ValidatingNormalizedEvaluator + brains.evaluateMove(position, move) - 1 : + //negating because bigger for the opponents means worse for the current player + //composite moves handling split to https://github.com/lrozenblyum/chess/issues/291 + -brains.evaluateMove(target, bestMove.get(0)); + + LogManager.getLogger().info( "result = {}", moveRating ); + ThreadContext.clearAll(); + return moveRating; + } - return moveRatings; + private double evaluateMoveViaSinglePly( S position, T move ) { + return brains.evaluateMove( position, move ); } @Override From 1903e8cc29449f07ab8c3ceaf4759cdddc57adac Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:07:00 +0200 Subject: [PATCH 102/110] Refactoring: fixed old confusion between brain and evaluator. --- .../brain/normalized/NormalizedBrain.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 08edd9fb4..6cea78425 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -35,27 +35,27 @@ public class NormalizedBrain < S extends GameState, T extends GameTransiti private static final int MAXIMAL_SUPPORTED_DEPTH = 2; //this is an absolute constant private static final int MINIMAL_POSSIBLE_DEPTH = 1; - private final GenericEvaluator brains; + private final GenericEvaluator evaluator; private final int pliesDepth; /** * Create normalized brain - * @param brains evaluator with results in [ 0, 1 ] range + * @param evaluator evaluator with results in [ 0, 1 ] range */ - public NormalizedBrain( GenericEvaluator brains ) { - this( brains, 1 ); + public NormalizedBrain( GenericEvaluator evaluator ) { + this( evaluator, 1 ); } /** - * We assume that brains always evaluate the move from the side to move the next ply + * We assume that evaluator always evaluate the move from the side to move the next ply * It was a logical assumption when we developed a 1-ply engine. * It can still be kept. * The alternative could be: stable evaluator that returns positive/negative result depending on color of the side to move * - * @param brains evaluator with results in [ 0, 1 ] range + * @param evaluator evaluator with results in [ 0, 1 ] range * @param pliesDepth depth to think */ - public NormalizedBrain(GenericEvaluator brains, int pliesDepth ) { + public NormalizedBrain(GenericEvaluator evaluator, int pliesDepth ) { if ( pliesDepth < MINIMAL_POSSIBLE_DEPTH) { throw new IllegalArgumentException( String.format( "This depth is wrong: %s", pliesDepth ) ); } @@ -64,7 +64,7 @@ public NormalizedBrain(GenericEvaluator brains, int pliesDepth ) { throw new IllegalArgumentException( String.format( "This depth is not supported yet: %s", pliesDepth ) ); } - this.brains = new ValidatingNormalizedEvaluator<>( brains ); + this.evaluator = new ValidatingNormalizedEvaluator<>(evaluator); this.pliesDepth = pliesDepth; } @@ -100,10 +100,10 @@ public List findBestMove( S position ) { //filtering out draw offers till #161 Map moveRatings = getMovesWithoutDrawOffer( position ).collect( - toMap( - identity(), - move -> moveEvaluator.applyAsDouble( position, move ) - ) + toMap( + identity(), + move -> moveEvaluator.applyAsDouble( position, move ) + ) ); List bestMove = getMoveWithMaxRating( moveRatings ); LogManager.getLogger().info( "Best move(s): {}", bestMove ); @@ -114,7 +114,7 @@ private double evaluateMoveViaTwoPlies( S position, T move ) { ThreadContext.put( "moveBeingAnalyzed", move.toString() ); S target = position.move( move ); - List bestMove = new NormalizedBrain<>(this.brains, 1).findBestMove(target); + List bestMove = new NormalizedBrain<>(this.evaluator, 1).findBestMove(target); //can be empty in case of terminal position if ( ! bestMove.isEmpty() ) { @@ -126,10 +126,10 @@ private double evaluateMoveViaTwoPlies( S position, T move ) { //trick: moving our evaluation results from [ 0, 1 ] to [ -1, 0 ] range //where all the second level moves exist // highly depends on evaluator range [ 0, 1 ] which is guaranteed by ValidatingNormalizedEvaluator - brains.evaluateMove(position, move) - 1 : + evaluator.evaluateMove(position, move) - 1 : //negating because bigger for the opponents means worse for the current player //composite moves handling split to https://github.com/lrozenblyum/chess/issues/291 - -brains.evaluateMove(target, bestMove.get(0)); + -evaluator.evaluateMove(target, bestMove.get(0)); LogManager.getLogger().info( "result = {}", moveRating ); ThreadContext.clearAll(); @@ -137,7 +137,7 @@ private double evaluateMoveViaTwoPlies( S position, T move ) { } private double evaluateMoveViaSinglePly( S position, T move ) { - return brains.evaluateMove( position, move ); + return evaluator.evaluateMove( position, move ); } @Override From d47d55b54bcc399958561520e7b06f9a570e05f7 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:08:29 +0200 Subject: [PATCH 103/110] Fixed wrong logging condition --- .../chess/player/legal/brain/normalized/NormalizedBrain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 6cea78425..c53437d65 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -117,7 +117,7 @@ private double evaluateMoveViaTwoPlies( S position, T move ) { List bestMove = new NormalizedBrain<>(this.evaluator, 1).findBestMove(target); //can be empty in case of terminal position - if ( ! bestMove.isEmpty() ) { + if ( bestMove.isEmpty() ) { LogManager.getLogger().info( "Evaluating just the current level" ); } From f4f80d4be2a24f66adc4545d0e35441e0d388d23 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:10:45 +0200 Subject: [PATCH 104/110] Comment --- .../chess/player/legal/brain/normalized/NormalizedBrain.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index c53437d65..de7b94a94 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -98,6 +98,8 @@ public List findBestMove( S position ) { //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches //filtering out draw offers till #161 + + //NOTE: in future we may even not materialize the map and continue the Stream API chain to find the best move Map moveRatings = getMovesWithoutDrawOffer( position ).collect( toMap( From 1f490ac48ca419e64c2863dc52aa0023febe6fb2 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:12:37 +0200 Subject: [PATCH 105/110] Comments --- .../legal/brain/normalized/NormalizedBrain.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index de7b94a94..0feee1451 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -24,6 +24,11 @@ * Now it has become generic (actually even not depending on chess-related notions). * You can inject any custom evaluator that acts as a normalized one via constructor. * + * We assume that the evaluator always evaluates the move from the side to move the next ply + * It was a logical assumption when we developed a 1-ply engine. + * It can still be kept. + * The alternative could be: stable evaluator that returns positive/negative result depending on color of the side to move + * * @param game state * @param transition type * @@ -47,13 +52,9 @@ public NormalizedBrain( GenericEvaluator evaluator ) { } /** - * We assume that evaluator always evaluate the move from the side to move the next ply - * It was a logical assumption when we developed a 1-ply engine. - * It can still be kept. - * The alternative could be: stable evaluator that returns positive/negative result depending on color of the side to move - * + * Create brain with custom plies depth * @param evaluator evaluator with results in [ 0, 1 ] range - * @param pliesDepth depth to think + * @param pliesDepth depth to think (1 or 2 are supported) */ public NormalizedBrain(GenericEvaluator evaluator, int pliesDepth ) { if ( pliesDepth < MINIMAL_POSSIBLE_DEPTH) { From c450678bc15bd9926e17a4c08f239195647ed13a Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:22:53 +0200 Subject: [PATCH 106/110] Restored lost comment --- .../chess/player/legal/brain/denormalized/DenormalizedBrain.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java index 924ef7f51..9e32b2d41 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/denormalized/DenormalizedBrain.java @@ -152,6 +152,7 @@ public double evaluateMove(Position position, Move move) { //this division is very similar to that one done in MasterEvaluator //for example if we had 2 evaluators with result eval1: 1, eval2: 0.5 plain sum would be 1.5 but the normalized one is 0.75 + //summing without converting to DoubleStream http://stackoverflow.com/q/24421140 return weightedTable.column(move).values().stream().reduce(0.0, Double::sum) / evaluatorWeights.size(); } From ccddbd57fab36634c88c0b3e409b8edfeb7c3461 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:24:46 +0200 Subject: [PATCH 107/110] Cleaned up comments --- .../player/legal/brain/normalized/NormalizedBrain.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java index 0feee1451..a57c25198 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/normalized/NormalizedBrain.java @@ -94,13 +94,11 @@ public List findBestMove( S position ) { this::evaluateMoveViaSinglePly : this::evaluateMoveViaTwoPlies; - //filtering Draw offers till #161 is solved + //1. filtering Draw offers till #161 is solved //this looks safe since Offer draw cannot be a single legal move in a position. - //the best place to filter is this decision maker because it's used both by Normalized and Denormalized branches - //filtering out draw offers till #161 - //NOTE: in future we may even not materialize the map and continue the Stream API chain to find the best move + //2. in future we may even not materialize the map and continue the Stream API chain to find the best move Map moveRatings = getMovesWithoutDrawOffer( position ).collect( toMap( From 9d1d1639b204199e6a0ef5837a9aab78d5411bae Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:26:00 +0200 Subject: [PATCH 108/110] Restored the original comment --- .../com/leokom/chess/player/legal/brain/simple/SimpleBrain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java index e500ce889..004938dd7 100644 --- a/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java +++ b/src/main/java/com/leokom/chess/player/legal/brain/simple/SimpleBrain.java @@ -10,7 +10,7 @@ import static java.util.Collections.singletonList; /** - * GenericBrain that implements Simple player in Legal player's infrastructure + * Brain that implements Simple player in Legal player's infrastructure * Created by Leonid on 09.02.17. * * Based on previous implementation with comments: From e7e7959a4fecceaa7abb7b25072ef860023af916 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:27:37 +0200 Subject: [PATCH 109/110] Fixed naming to evaluator. --- .../java/com/leokom/chess/player/legal/LegalPlayer.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java b/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java index 0ac6343e0..f25a21d93 100644 --- a/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java +++ b/src/main/java/com/leokom/chess/player/legal/LegalPlayer.java @@ -39,11 +39,11 @@ public LegalPlayer( GenericBrain< Position, Move > brain ) { } /** - * Create a player with standard decision maker and injected evaluators - * @param brains brains to evaluate moves + * Create a player with standard decision maker and injected evaluator + * @param evaluator evaluator to evaluate moves */ - public LegalPlayer( Evaluator brains ) { - this.brain = new NormalizedBrain<>(brains); + public LegalPlayer( Evaluator evaluator ) { + this.brain = new NormalizedBrain<>(evaluator); } @Override From e27faa9267447e35ab04436bc8db389009c3e3e7 Mon Sep 17 00:00:00 2001 From: Leonid Rozenblyum Date: Wed, 13 Feb 2019 23:28:46 +0200 Subject: [PATCH 110/110] Restored the original default pattern --- src/main/resources/log4j2.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 1530ec093..71b000ce3 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -4,7 +4,7 @@ as it may damage WinBoard --> - +