Skip to content

Commit

Permalink
Add benchmarks.
Browse files Browse the repository at this point in the history
Fix Configuration file.
Improve performances.
  • Loading branch information
FrequentlyMissedDeadlines committed Dec 30, 2020
1 parent 2c2d2da commit b3831d5
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 64 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,21 @@ By default it uses [MinMax with AlphaBeta pruning optimization](https://en.wikip

## In Arena Chess GUI
This engine supports UCI protocol so it can be plugged in any polite compatible GUI. For Arena Chess:
- If **Java 8+** is already installed on your computer go to next step. If not, you can [get it there](https://www.java.com/download/).
- Download the last version of the **JAR file** in [Releases page](https://github.com/FrequentlyMissedDeadlines/Chuck-Norris-Chess/releases).
- Open Arena Chess GUI
- Go to **Engines > Install New Engine** and select the previous **JAR file**
- Select UCI
- You should now be able to play against Chuck, enjoy being kicked!
![](doc/Arena.png)
## In standalone mode
Run from code:
```
mvn spring-boot:run
```
Run jar:
```
java -jar Chuck-Norris-Chess-1.0.jar
java -jar Chuck-Norris-Chess-1.2.jar
```

If you terminal supports Unicode you should see something like this (I run it directly from IntelliJ):

![](doc/Chessboard_1.png)

To play, type your moves with this notation:
Expand All @@ -41,9 +39,19 @@ e7e5
If the display is broken and you want to switch to the degraded mode go to next section (configuration) and uncomment this property: `#UI.UIConsole.enableUTF8=false`

# Configuration
Update `src/main/resources/application.properties` file and pass it at runtime to override default conf:
Update src/main/resources/application.properties file and pass it at runtime to override default conf:
```
java -jar Chuck-Norris-Chess-1.2.jar --config.location=src/main/resources/application.properties
```
java -jar Chuck-Norris-Chess-1.0.jar --spring.config.location=src/main/resources/application.properties

# Benchmarks
We can run the JAR with `benchmark` option to run JMH (figures depend on your hardware and Java version):
```
java -jar Chuck-Norris-Chess-1.2.jar benchmark
Benchmark Mode Cnt Score Error Units
AlphaBetaBenchmark.alphaBeta thrpt 5 48,335 ± 0,720 ops/s
AlphaBetaBenchmark.negaMax thrpt 5 45,252 ± 0,642 ops/s
```

# Dependencies
Expand Down
14 changes: 13 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.contracyclix.chuckchess</groupId>
<artifactId>Chuck-Norris-Chess</artifactId>
<version>1.1</version>
<version>1.2</version>
<properties>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down Expand Up @@ -165,6 +165,18 @@
<version>3.6.28</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.27</version>
</dependency>

<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.27</version>
</dependency>
</dependencies>

</project>
15 changes: 13 additions & 2 deletions src/main/java/com/contracyclix/chuckchess/MainApp.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package com.contracyclix.chuckchess;

import com.contracyclix.chuckchess.ai.NoAIClassException;
import com.contracyclix.chuckchess.benchmark.Benchmarks;
import com.contracyclix.chuckchess.config.Config;
import com.contracyclix.chuckchess.ui.NoUIClassException;
import org.openjdk.jmh.runner.RunnerException;

public class MainApp {
private static final String configOption = "--config.location=";

public static void main(String... args) throws NoUIClassException, NoAIClassException {
new ChessApp().run();
public static void main(String... args) throws NoUIClassException, NoAIClassException, RunnerException {
if (args.length > 0 && "benchmark".equals(args[0])) {
Benchmarks.main(args);
} else {
if (args.length > 0 && args[0].startsWith(configOption)) {
Config.setConfigFileName(args[0].split(configOption)[1]);
}
new ChessApp().run();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.contracyclix.chuckchess.benchmark;

import com.contracyclix.chuckchess.minmax.AlphaBeta;
import com.github.bhlangonijr.chesslib.Board;
import org.openjdk.jmh.annotations.Benchmark;

public class AlphaBetaBenchmark {

@Benchmark
public void alphaBeta() {
AlphaBeta alphaBeta = new AlphaBeta(3, true, 3);

alphaBeta.alphaBeta(new Board(), 0, Long.MIN_VALUE, Long.MAX_VALUE, true);
}

@Benchmark
public void negaMax() {
AlphaBeta alphaBeta = new AlphaBeta(3, true, 3);

alphaBeta.negaMax(new Board(), Long.MIN_VALUE, 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.contracyclix.chuckchess.benchmark;

import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

public class Benchmarks {
public static void main(String[]args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(AlphaBetaBenchmark.class.getSimpleName())
.forks(1)
.build();

new Runner(opt).run();
}
}
8 changes: 6 additions & 2 deletions src/main/java/com/contracyclix/chuckchess/config/Config.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.contracyclix.chuckchess.config;

import lombok.Setter;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;

import java.io.File;

public class Config {
private static Config singleton = new Config();
@Setter
private static String configFileName = "application.properties";

private static final String configFileName = "application.properties";
private static Config singleton = null;

private PropertiesConfiguration config;

Expand All @@ -25,6 +27,8 @@ private Config() {
}

public static PropertiesConfiguration get() {
if (singleton == null)
singleton = new Config();
return singleton.config;
}
}
101 changes: 53 additions & 48 deletions src/main/java/com/contracyclix/chuckchess/minmax/AlphaBeta.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.contracyclix.chuckchess.minmax;

import com.github.bhlangonijr.chesslib.Board;
import com.github.bhlangonijr.chesslib.Piece;
import com.github.bhlangonijr.chesslib.Side;
import com.github.bhlangonijr.chesslib.Square;
import com.github.bhlangonijr.chesslib.move.*;
import com.github.bhlangonijr.chesslib.*;
import com.github.bhlangonijr.chesslib.move.Move;
import com.github.bhlangonijr.chesslib.move.MoveConversionException;
import com.github.bhlangonijr.chesslib.move.MoveGeneratorException;
import lombok.AllArgsConstructor;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@AllArgsConstructor
Expand All @@ -19,33 +21,28 @@ public class AlphaBeta {
private final boolean isWhite;
private final int numThreads;

private static final Map<Piece, Integer> piecesScores = new MoveComparator(null).scores;

private final static List<Square> allSquares = new LinkedList<>();
static {
for (Square s : Square.values()) {
if (s != Square.NONE) {
allSquares.add(s);
}
}
}

private static boolean isTerminal(Board board) {
return board.isDraw() || board.isStaleMate() || board.isInsufficientMaterial() || board.isMated() || board.isRepetition();
return board.isDraw() || board.isMated();
}

private static long getPieceValue(Piece p, Side playerSide) {
long val = 0;
if (p == null || p.value() == "NONE") {
return 0;
}
switch (p.getPieceType()) {
case BISHOP:
val+=3;
break;
case KNIGHT:
val+=3;
break;
case PAWN:
val+=1;
break;
case QUEEN:
val+=9;
break;
case ROOK:
val+=5;
break;

if (piecesScores.containsKey(p)) {
val += piecesScores.get(p);
}

if (p.getPieceSide() == playerSide) {
return val;
} else {
Expand All @@ -57,11 +54,11 @@ private long evaluate(Board board, int currentDepth) {
Piece[] pieces = board.boardToArray();
Side playerSide = isWhite ? Side.WHITE : Side.BLACK;

if (board.isDraw() || board.isInsufficientMaterial() || board.isRepetition()) {
if (board.isDraw()) {
return 0;
}

if (board.isStaleMate() || board.isMated()) {
if (board.isMated()) {
if (playerSide == board.getSideToMove()) {
return currentDepth - 1000000;
} else {
Expand All @@ -76,30 +73,40 @@ private long evaluate(Board board, int currentDepth) {

score *= 100;

for (Square s : Square.values()) {
if (s != Square.NONE) {
if (board.squareAttackedBy(s, Side.BLACK) != 0) {
if (playerSide == Side.BLACK) {
score += 1;
} else {
score -= 1;
}
//List<Move> allMoves = board.pseudoLegalMoves();
//score += 2 * allMoves.stream().filter(m -> board.getPiece(m.getFrom()).getPieceSide() == playerSide).count() - allMoves.size();

for (Square s : allSquares) {
if (squareAttackedBy(s, Side.BLACK, board)) {
if (playerSide == Side.BLACK) {
score += 1;
} else {
score -= 1;
}
if (board.squareAttackedBy(s, Side.WHITE) != 0) {
if (playerSide == Side.WHITE) {
score += 1;
} else {
score -= 1;
}
}
if (squareAttackedBy(s, Side.WHITE, board)) {
if (playerSide == Side.WHITE) {
score += 1;
} else {
score -= 1;
}
}
}

return score;
}

public Move getBestMove(Board initial) throws MoveGeneratorException, MoveConversionException {
List<Move> moves = MoveGenerator.generateLegalMoves(initial);
private boolean squareAttackedBy(Square square, Side side, Board board) {
long occ = board.getBitboard();
return (Bitboard.getPawnAttacks(side.flip(), square) & board.getBitboard(Piece.make(side, PieceType.PAWN)) & occ) != 0 ||
(Bitboard.getKnightAttacks(square, occ) & board.getBitboard(Piece.make(side, PieceType.KNIGHT))) != 0 ||
(Bitboard.getBishopAttacks(occ, square) & (board.getBitboard(Piece.make(side, PieceType.BISHOP)) | board.getBitboard(Piece.make(side, PieceType.QUEEN)))) != 0 ||
(Bitboard.getRookAttacks(occ, square) & (board.getBitboard(Piece.make(side, PieceType.ROOK)) | board.getBitboard(Piece.make(side, PieceType.QUEEN)))) != 0 ||
(Bitboard.getKingAttacks(square, occ) & board.getBitboard(Piece.make(side, PieceType.KING))) != 0;
}

public Move getBestMove(Board initial) throws MoveConversionException {
List<Move> moves = initial.legalMoves();

ExecutorService service = Executors.newFixedThreadPool(numThreads);
long[] scores = new long[moves.size()];
Expand All @@ -122,15 +129,13 @@ public Move getBestMove(Board initial) throws MoveGeneratorException, MoveConver
Move bestMove = null;
service.shutdown();
try {
boolean terminated = service.awaitTermination(60, TimeUnit.SECONDS);
boolean terminated = service.awaitTermination(600, TimeUnit.SECONDS);
for (int i = 0 ; i < moves.size() ; i++) {
if (scores[i] > bestScore) {
bestScore = scores[i];
bestMove = moves.get(i);
}
}
//System.out.println("Current eval: " + evaluate(initial, 0));
//System.out.println("Best move: " + bestMove.toString() + " " + bestScore);
} catch (InterruptedException e) {
e.printStackTrace();
}
Expand All @@ -143,7 +148,7 @@ public long alphaBeta(Board node, int depth, long alpha, long beta, boolean isMa
return evaluate(node, depth);
}

List<Move> moves = MoveGenerator.generateLegalMoves(node);
List<Move> moves = node.legalMoves();

if (isMax) {
long score = Long.MIN_VALUE;
Expand Down Expand Up @@ -177,7 +182,7 @@ public long negaMax(Board node, long minScore, int depth) throws MoveGeneratorEx
return evaluate(node, depth);
}
long score = Long.MIN_VALUE;
List<Move> moves = MoveGenerator.generateLegalMoves(node);
List<Move> moves = node.legalMoves().stream().sorted(new MoveComparator(node)).collect(Collectors.toList());

for (Move move : moves) {
Board child = node.clone();
Expand Down
Loading

0 comments on commit b3831d5

Please sign in to comment.