First working UCI implementation

+ bestmove, position and go command implementations
+ Move initialization from algebraic notation
+ FEN string generation
This commit is contained in:
Kai S. K. Engelbart 2019-07-22 14:51:24 +02:00
parent f92715e42f
commit ff68def767
8 changed files with 221 additions and 109 deletions

View File

@ -19,8 +19,11 @@ public class Board implements Cloneable {
private Piece[][] boardArr; private Piece[][] boardArr;
private Map<Color, Position> kingPos; private Map<Color, Position> kingPos;
private Color activeColor;
private Log log; private Log log;
private int fullmoveCounter, halfmoveClock;
private static final Map<Type, int[][]> positionScores; private static final Map<Type, int[][]> positionScores;
static { static {
@ -101,6 +104,8 @@ public class Board implements Cloneable {
Piece piece = getPos(move); Piece piece = getPos(move);
Piece capturePiece = getDest(move); Piece capturePiece = getDest(move);
// TODO: reset halfmove clock
switch (move.type) { switch (move.type) {
case PAWN_PROMOTION: case PAWN_PROMOTION:
setPos(move, null); setPos(move, null);
@ -140,6 +145,15 @@ public class Board implements Cloneable {
// Update log // Update log
log.add(move, capturePiece); log.add(move, capturePiece);
// Swap active color
activeColor = activeColor.opposite();
// Increment fullmove counter
if (piece.getColor() == Color.BLACK) ++fullmoveCounter;
// Increment halfmove clock
++halfmoveClock;
} }
/** /**
@ -188,6 +202,15 @@ public class Board implements Cloneable {
// Update log // Update log
log.removeLast(); log.removeLast();
// Swap active color
activeColor = activeColor.opposite();
// Decrement fullmove counter
if (getPos(move).getColor() == Color.BLACK) --fullmoveCounter;
// Decrement halfmove clock
--halfmoveClock;
} }
/** /**
@ -330,6 +353,11 @@ public class Board implements Cloneable {
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
for (int j = 2; j < 6; j++) for (int j = 2; j < 6; j++)
boardArr[i][j] = null; boardArr[i][j] = null;
activeColor = Color.WHITE;
fullmoveCounter = 1;
halfmoveClock = 0;
} }
/** /**
@ -354,12 +382,76 @@ public class Board implements Cloneable {
board.kingPos = new HashMap<>(); board.kingPos = new HashMap<>();
board.kingPos.putAll(kingPos); board.kingPos.putAll(kingPos);
// TODO: activeColor clone ?
board.log = (Log) log.clone(); board.log = (Log) log.clone();
return board; return board;
} }
/**
* @return A FEN string representing the board
*/
public String toFEN() {
StringBuilder sb = new StringBuilder();
// Piece placement (from white's perspective)
for (int i = 0; i < 8; i++) {
int emptyCount = 0;
for (int j = 0; j < 8; j++)
if (boardArr[j][i] == null) ++emptyCount;
else {
if (emptyCount != 0) {
sb.append(emptyCount);
emptyCount = 0;
}
char piece;
switch (boardArr[j][i].getType()) {
case KING:
piece = 'K';
break;
case QUEEN:
piece = 'Q';
break;
case ROOK:
piece = 'R';
break;
case KNIGHT:
piece = 'N';
break;
case BISHOP:
piece = 'B';
break;
case PAWN:
piece = 'P';
break;
default:
piece = '-';
}
if (boardArr[j][i].getColor() == Color.BLACK) piece = Character.toLowerCase(piece);
sb.append(piece);
}
if (emptyCount != 0) sb.append(emptyCount);
if (i < 7) sb.append('/');
}
// Active color
sb.append(" " + (activeColor == Color.WHITE ? 'w' : 'b'));
// TODO: castling rights
sb.append(" -");
// TODO: en passant availability
sb.append(" -");
// Halfmove clock
sb.append(" " + halfmoveClock);
// Fullmove counter
sb.append(" " + fullmoveCounter);
return sb.toString();
}
public Piece get(Position pos) { public Piece get(Position pos) {
return boardArr[pos.x][pos.y]; return boardArr[pos.x][pos.y];
} }
@ -388,4 +480,6 @@ public class Board implements Cloneable {
* @return The board array * @return The board array
*/ */
public Piece[][] getBoardArr() { return boardArr; } public Piece[][] getBoardArr() { return boardArr; }
public Color getActiveColor() { return activeColor; }
} }

View File

@ -30,6 +30,11 @@ public class Move {
this(new Position(xPos, yPos), new Position(xDest, yDest)); this(new Position(xPos, yPos), new Position(xDest, yDest));
} }
public static Move fromAlgebraicNotation(String move) {
return new Move(Position.fromAlgebraicNotation(move.substring(0, 2)),
Position.fromAlgebraicNotation(move.substring(2)));
}
public boolean isHorizontal() { return yDist == 0; } public boolean isHorizontal() { return yDist == 0; }
public boolean isVertical() { return xDist == 0; } public boolean isVertical() { return xDist == 0; }

View File

@ -15,6 +15,10 @@ public class Position {
this.y = y; this.y = y;
} }
public static Position fromAlgebraicNotation(String pos) {
return new Position(pos.charAt(0) - 97, 7 - (Character.getNumericValue(pos.charAt(1)) - 1));
}
@Override @Override
public String toString() { public String toString() {
return String.format("[%d, %d]", x, y); return String.format("[%d, %d]", x, y);

View File

@ -73,6 +73,7 @@ public class Game {
public void onMove(Player player, Move move) { public void onMove(Player player, Move move) {
if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) { if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
System.out.printf("%s: %s%n", player.color, move); System.out.printf("%s: %s%n", player.color, move);
System.out.println("FEN: " + board.toFEN());
GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite()); GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite());
switch (eventType) { switch (eventType) {
case CHECKMATE: case CHECKMATE:
@ -83,7 +84,7 @@ public class Game {
System.out.printf("%s in check!%n", player.color.opposite()); System.out.printf("%s in check!%n", player.color.opposite());
default: default:
boardComponent.repaint(); boardComponent.repaint();
players.get(player.color.opposite()).requestMove(); players.get(board.getActiveColor()).requestMove();
} }
overlayComponent.displayArrow(move); overlayComponent.displayArrow(move);
} else player.requestMove(); } else player.requestMove();

View File

@ -1,9 +1,15 @@
package dev.kske.chess.game; package dev.kske.chess.game;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import dev.kske.chess.board.Move;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.uci.UCIHandle; import dev.kske.chess.uci.UCIHandle;
import dev.kske.chess.uci.UCIListener;
import dev.kske.chess.uci.UCIOption;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -11,17 +17,19 @@ import dev.kske.chess.uci.UCIHandle;
* Created: <strong>18.07.2019</strong><br> * Created: <strong>18.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong> * Author: <strong>Kai S. K. Engelbart</strong>
*/ */
public class UCIPlayer extends Player { public class UCIPlayer extends Player implements UCIListener {
private UCIHandle handle; private UCIHandle handle;
private UCIPlayerListener listener;
private String name, author;
private List<UCIOption> options;
public UCIPlayer(Color color, String enginePath) { public UCIPlayer(Color color, String enginePath) {
super(color); super(color);
options = new ArrayList<>();
try { try {
handle = new UCIHandle(enginePath); handle = new UCIHandle(enginePath);
listener = new UCIPlayerListener(); handle.setListener(this);
handle.setListener(listener);
handle.start(); handle.start();
} catch (IOException ex) { } catch (IOException ex) {
ex.printStackTrace(); ex.printStackTrace();
@ -30,7 +38,8 @@ public class UCIPlayer extends Player {
@Override @Override
public void requestMove() { public void requestMove() {
handle.positionFEN(board.toFEN());
handle.go();
} }
@Override @Override
@ -38,7 +47,80 @@ public class UCIPlayer extends Player {
handle.stop(); handle.stop();
} }
@Override
public void disconnect() { public void disconnect() {
handle.quit(); handle.quit();
} }
@Override
public void onIdName(String name) {
this.name = name;
}
@Override
public void onIdAuthor(String author) {
this.author = author;
}
@Override
public void onUCIOk() {
System.out.println("UCI ok");
}
@Override
public void onReadyOk() {
System.out.println("Ready ok");
}
@Override
public void onBestMove(String move) {
Move moveObj = Move.fromAlgebraicNotation(move);
game.onMove(this, moveObj);
}
@Override
public void onBestMove(String move, String ponderMove) {
onBestMove(move);
}
@Override
public void onCopyProtectionChecking() {
System.out.println("Copy protection checking...");
}
@Override
public void onCopyProtectionOk() {
System.out.println("Copy protection ok");
}
@Override
public void onCopyProtectionError() {
System.err.println("Copy protection error!");
}
@Override
public void onRegistrationChecking() {
System.out.println("Registration checking...");
}
@Override
public void onRegistrationOk() {
System.out.println("Registration ok");
}
@Override
public void onRegistrationError() {
System.err.println("Registration error!");
}
@Override
public void onInfo(Map<String, String> additionalInfo) {
System.out.println("Info:");
additionalInfo.forEach((k, v) -> System.out.printf("%s: %s%n", k, v));
}
@Override
public void onOption(UCIOption option) {
options.add(option);
}
} }

View File

@ -1,99 +0,0 @@
package dev.kske.chess.game;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import dev.kske.chess.uci.UCIListener;
import dev.kske.chess.uci.UCIOption;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>UCIPlayerListener.java</strong><br>
* Created: <strong>20.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong>
*/
class UCIPlayerListener implements UCIListener {
private String name, author;
private List<UCIOption> options;
public UCIPlayerListener() {
options = new ArrayList<>();
}
@Override
public void onIdName(String name) {
this.name = name;
}
@Override
public void onIdAuthor(String author) {
this.author = author;
}
@Override
public void onUCIOk() {
System.out.println("UCI ok");
}
@Override
public void onReadyOk() {
System.out.println("Ready ok");
}
@Override
public void onBestMove(String move) {
// TODO Auto-generated method stub
}
@Override
public void onBestMove(String move, String ponderMove) {
// TODO Auto-generated method stub
}
@Override
public void onCopyProtectionChecking() {
System.out.println("Copy protection checking...");
}
@Override
public void onCopyProtectionOk() {
System.out.println("Copy protection ok");
}
@Override
public void onCopyProtectionError() {
System.err.println("Copy protection error!");
}
@Override
public void onRegistrationChecking() {
System.out.println("Registration checking...");
}
@Override
public void onRegistrationOk() {
System.out.println("Registration ok");
}
@Override
public void onRegistrationError() {
System.err.println("Registration error!");
}
@Override
public void onInfo(Map<String, String> additionalInfo) {
System.out.println("Info:");
additionalInfo.forEach((k, v) -> System.out.printf("%s: %s%n", k, v));
}
@Override
public void onOption(UCIOption option) {
options.add(option);
}
public String getName() { return name; };
public String getAuthor() { return author; };
}

View File

@ -93,7 +93,21 @@ public class UCIHandle {
} }
// TODO: position // TODO: position
// TODO: go
/**
* Sets up the position described in the FEN string.
*
* @param fen FEN representation of the current board
*/
public void positionFEN(String fen) {
out.println("position fen " + fen);
}
// TODO: go with parameters
public void go() {
out.println("go");
}
/** /**
* Stops calculation as soon as possible. * Stops calculation as soon as possible.

View File

@ -54,7 +54,9 @@ public class UCIReceiver implements Runnable {
case "readyok": case "readyok":
listener.onReadyOk(); listener.onReadyOk();
break; break;
// TODO: bestmove case "bestmove":
parseBestMove(line.substring(command.length() + 1));
break;
case "copyprotection": case "copyprotection":
parseCopyProtection(line.substring(command.length() + 1)); parseCopyProtection(line.substring(command.length() + 1));
break; break;
@ -87,6 +89,15 @@ public class UCIReceiver implements Runnable {
} }
} }
private void parseBestMove(String line) {
String[] tokens = line.split(" ");
String move = tokens[0];
// Ponder move
if (tokens.length == 3) listener.onBestMove(move, tokens[2]);
else listener.onBestMove(move);
}
private void parseCopyProtection(String line) { private void parseCopyProtection(String line) {
switch (line) { switch (line) {
case "checking": case "checking":