Skip to content

Commit

Permalink
Merge pull request #289 from lrozenblyum/ThinkFor2Plies-235
Browse files Browse the repository at this point in the history
Think for 2 plies #235
  • Loading branch information
lrozenblyum authored Feb 13, 2019
2 parents 820077e + e27faa9 commit ef14620
Show file tree
Hide file tree
Showing 50 changed files with 1,131 additions and 280 deletions.
1 change: 1 addition & 0 deletions Chess.iml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.9.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.9.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.6" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.github.stefanbirkner:system-rules:1.19.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jooq:jool-java-8:0.9.14" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:26.0-jre" level="project" />
<orderEntry type="library" name="Maven: com.fluxchess:jcpi:1.4.0" level="project" />
Expand Down
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>

<!-- To try improved lambdas in tests, if it succeeds
we may use it in core as well -->
<dependency>
Expand Down
3 changes: 2 additions & 1 deletion src/main/bat/runTwoEngines.bat
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ 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)
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%

Expand Down
5 changes: 3 additions & 2 deletions src/main/bat/variables.bat
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ 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
SET RUN_OPTIONS=
rem you may pass -Dblack.engine=Simple to choose a different engine for blacks
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%
26 changes: 20 additions & 6 deletions src/main/java/com/leokom/chess/MainRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ private MainRunner() {
*
* The parameters are provided via easier-to-use Java system properties way.
* <p>
* Supported parameters:
* General parameters:
* <ul>
* <li>-Dwhite=<code>engineName</code></li>
* <li>-Dblack=<code>engineName</code></li>
* <li>-Dwhite.engine=<code>engineName</code></li>
* <li>-Dblack.engine=<code>engineName</code></li>
* </ul>
*
* <code>engineName</code> could be any of:
Expand All @@ -34,11 +34,25 @@ private MainRunner() {
* <li>Simple</li>
* <li>Legal</li>
* </ul>
*
* Default players:
* <ul>
* <li>-Dwhite.engine=Winboard</li>
* <li>-Dblack.engine=Legal</li>
* </ul>
*
* Default players:
* <p>
*
* Optional parameters for LegalPlayer
* <ul>
* <li>-Dwhite.depth=<code>depth in plies</code></li>
* <li>-Dblack.depth=<code>depth in plies</code></li>
* </ul>
*
* <code>depth in plies</code> can be any of:
* <ul>
* <li>-Dwhite=Winboard</li>
* <li>-Dblack=Legal</li>
* <li>1</li>
* <li>2</li>
* </ul>
*
* For Winboard opponents always specify them as Black
Expand Down
102 changes: 48 additions & 54 deletions src/main/java/com/leokom/chess/PlayerFactory.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,45 @@
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.LegalPlayer;
import com.leokom.chess.player.legal.brain.simple.SimpleBrain;
import com.leokom.chess.player.winboard.WinboardPlayer;
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;

import java.util.Map;
import java.util.function.Supplier;
import java.util.Optional;

/**
* Create players for the chess game
*
* 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 );

//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" );
/**
* 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;
}

Optional<String> getFor( Side side ) {
return Optional.ofNullable(
System.getProperty(
String.format( "%s.%s", side.name().toLowerCase(), propertyName )
)
);
}
}

/**
* Create player for the side
Expand All @@ -40,56 +54,36 @@ private PlayerFactory() {}
* 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
*/
static Player createPlayer( Side side ) {
final String engineName = System.getProperty( SYSTEM_PROPERTIES.get( side ) );

logger.info( "Engine from system properties: " + engineName + ". Side = " + side );

return selectPlayer( side, engineName ).create();
}

private static PlayerSelection selectPlayer( Side side, String engineName ) {
if ( engineName == null ) {
logger.info( "No selection done. Selecting default player" );
return getDefaultPlayer( side );
}

switch ( engineName ) {
case "Legal":
return PlayerSelection.LEGAL;
case "Simple":
return PlayerSelection.SIMPLE;
case "Winboard":
return PlayerSelection.WINBOARD;
default:
logger.warn( "Unsupported option specified. Selecting default player" );
return getDefaultPlayer( side );
}
}

public enum PlayerSelection {
LEGAL( LegalPlayer::new ),
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();
}
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 getLegalPlayerSupplier( side );
case "Simple":
return new SimplePlayerSupplier();
case "Winboard":
return new WinboardPlayerSupplier();
default:
throw new IllegalArgumentException( "The engine is not supported: " + engineName);
}
}).orElseGet(() -> {
logger.info( "Selecting a default engine for Side = " + side );
return side == Side.WHITE ? new WinboardPlayerSupplier() : getLegalPlayerSupplier( side );
}).get();
}

private static PlayerSelection getDefaultPlayer( Side side ) {
logger.info( "Selecting default engine for Side = " + side );
return side == Side.WHITE ? PlayerSelection.WINBOARD : PlayerSelection.LEGAL;
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
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/leokom/chess/engine/GameState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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 <T> type of transitions
* @param <S> current type (state)
*/
/*
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< T extends GameTransition, S extends GameState<T, S> > {
S move(T move);

Set<T> getMoves();
}
9 changes: 9 additions & 0 deletions src/main/java/com/leokom/chess/engine/GameTransition.java
Original file line number Diff line number Diff line change
@@ -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 {
}
2 changes: 1 addition & 1 deletion src/main/java/com/leokom/chess/engine/Move.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")
*/
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/leokom/chess/engine/Position.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
* Author: Leonid
* Date-time: 21.08.12 15:55
*/
public class Position {
public class Position implements GameState< Move, Position > {
/**
* Chess rules mention moves counter must be calculated
* for both players
Expand Down Expand Up @@ -641,6 +641,7 @@ public Position move( String squareFrom, String move ) {
* @param move act of movement
* @return new position, which is received from current by making 1 move
*/
@Override
public Position move( Move move ) {
return new PositionGenerator( this ).generate( move );
}
Expand Down Expand Up @@ -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<>();
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/leokom/chess/engine/PositionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public PositionBuilder setSide( Side side ) {
return this;
}

public Side getSideToMove() {
return position.getSideToMove();
}

public Position build() {
return position;
}
Expand Down
18 changes: 9 additions & 9 deletions src/main/java/com/leokom/chess/player/legal/LegalPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,7 +22,7 @@
public class LegalPlayer implements Player {
private Player opponent;
private Position position = Position.getInitialPosition();
private final Brain brain;
private final GenericBrain< Position, Move > brain;

private boolean recordingMode;
private Side ourSide;
Expand All @@ -34,16 +34,16 @@ public LegalPlayer() {
this( new DenormalizedBrain() );
}

public LegalPlayer( Brain brain ) {
public LegalPlayer( GenericBrain< Position, Move > brain ) {
this.brain = 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
Expand Down Expand Up @@ -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 );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.leokom.chess.player.legal;

import com.leokom.chess.player.Player;
import com.leokom.chess.player.legal.brain.normalized.MasterEvaluator;
import com.leokom.chess.player.legal.brain.normalized.NormalizedBrain;

import java.util.function.Supplier;

public class LegalPlayerSupplier implements Supplier<Player> {
//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 ) );
}
}
Loading

0 comments on commit ef14620

Please sign in to comment.