Renamed SAN-like coordinate notation to LAN, added SAN support to Board
This commit is contained in:
parent
dfa3874dcf
commit
c16395fe1f
@ -4,6 +4,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import dev.kske.chess.board.Piece.Color;
|
import dev.kske.chess.board.Piece.Color;
|
||||||
import dev.kske.chess.board.Piece.Type;
|
import dev.kske.chess.board.Piece.Type;
|
||||||
@ -182,6 +184,65 @@ public class Board {
|
|||||||
updateCastlingRights();
|
updateCastlingRights();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a piece across the board without checking if the move is legal.
|
||||||
|
*
|
||||||
|
* @param sanMove The move to execute in SAN (Standard Algebraic Notation)
|
||||||
|
*/
|
||||||
|
public void move(String sanMove) {
|
||||||
|
Map<String, Pattern> patterns = new HashMap<>();
|
||||||
|
patterns.put("pieceMove",
|
||||||
|
Pattern.compile(
|
||||||
|
"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])$"));
|
||||||
|
patterns.put("pawnCapture",
|
||||||
|
Pattern
|
||||||
|
.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?$"));
|
||||||
|
patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?$"));
|
||||||
|
|
||||||
|
patterns.forEach((patternName, pattern) -> {
|
||||||
|
Matcher m = pattern.matcher(sanMove);
|
||||||
|
if (m.find()) {
|
||||||
|
Position pos = null, dest = Position.fromLAN(m.group("toSquare"));
|
||||||
|
switch (patternName) {
|
||||||
|
case "pieceMove":
|
||||||
|
if (m.group("fromSquare") != null) pos = Position.fromLAN(m.group("fromSquare"));
|
||||||
|
else {
|
||||||
|
Type type = Type.fromFirstChar(m.group("pieceType").charAt(0));
|
||||||
|
char file;
|
||||||
|
int rank;
|
||||||
|
if (m.group("fromFile") != null) {
|
||||||
|
file = m.group("fromFile").charAt(0);
|
||||||
|
rank = get(type, file);
|
||||||
|
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||||
|
} else if (m.group("fromRank") != null) {
|
||||||
|
rank = Integer.parseInt(m.group("fromRank").substring(0, 1));
|
||||||
|
file = get(type, rank);
|
||||||
|
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||||
|
} else pos = get(type, dest);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "pawnCapture":
|
||||||
|
char file = m.group("fromFile").charAt(0);
|
||||||
|
int rank = m.group("fromRank") == null ? get(Type.PAWN, file)
|
||||||
|
: Integer.parseInt(m.group("fromRank"));
|
||||||
|
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||||
|
break;
|
||||||
|
case "pawnPush":
|
||||||
|
// TODO: Pawn promotion
|
||||||
|
int step = log.getActiveColor() == Color.WHITE ? 1 : -1;
|
||||||
|
|
||||||
|
// One step forward
|
||||||
|
if (boardArr[dest.x][dest.y + step] != null) pos = new Position(dest.x, dest.y + step);
|
||||||
|
// Double step forward
|
||||||
|
else pos = new Position(dest.x, dest.y + 2 * step);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
move(new Move(pos, dest));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverts the last move.
|
* Reverts the last move.
|
||||||
*/
|
*/
|
||||||
@ -485,7 +546,7 @@ public class Board {
|
|||||||
castlingRights.put(Color.BLACK, blackCastling);
|
castlingRights.put(Color.BLACK, blackCastling);
|
||||||
|
|
||||||
// En passant availability
|
// En passant availability
|
||||||
if (!parts[3].equals("-")) log.setEnPassant(Position.fromSAN(parts[3]));
|
if (!parts[3].equals("-")) log.setEnPassant(Position.fromLAN(parts[3]));
|
||||||
|
|
||||||
// Halfmove clock
|
// Halfmove clock
|
||||||
log.setHalfmoveClock(Integer.parseInt(parts[4]));
|
log.setHalfmoveClock(Integer.parseInt(parts[4]));
|
||||||
@ -506,7 +567,7 @@ public class Board {
|
|||||||
for (int j = 0; j < 8; j++) {
|
for (int j = 0; j < 8; j++) {
|
||||||
final Piece piece = boardArr[j][i];
|
final Piece piece = boardArr[j][i];
|
||||||
if (piece == null) ++emptyCount;
|
if (piece == null) ++emptyCount;
|
||||||
else {
|
else { // TODO: rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b - - 1 2 error
|
||||||
if (emptyCount != 0) {
|
if (emptyCount != 0) {
|
||||||
sb.append(emptyCount);
|
sb.append(emptyCount);
|
||||||
emptyCount = 0;
|
emptyCount = 0;
|
||||||
@ -535,7 +596,7 @@ public class Board {
|
|||||||
final MoveNode lastMove = log.getLast();
|
final MoveNode lastMove = log.getLast();
|
||||||
|
|
||||||
// En passant availability
|
// En passant availability
|
||||||
sb.append(" " + (lastMove == null || lastMove.enPassant == null ? "-" : lastMove.enPassant.toSAN()));
|
sb.append(" " + (lastMove == null || lastMove.enPassant == null ? "-" : lastMove.enPassant.toLAN()));
|
||||||
|
|
||||||
// Halfmove clock
|
// Halfmove clock
|
||||||
sb.append(" " + String.valueOf(lastMove == null ? 0 : lastMove.halfmoveClock));
|
sb.append(" " + String.valueOf(lastMove == null ? 0 : lastMove.halfmoveClock));
|
||||||
@ -554,6 +615,58 @@ public class Board {
|
|||||||
return boardArr[pos.x][pos.y];
|
return boardArr[pos.x][pos.y];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a {@link Piece} inside a file (A - H).
|
||||||
|
*
|
||||||
|
* @param type The {@link Type} of the piece to search for
|
||||||
|
* @param file The file in which to search for the piece
|
||||||
|
* @return The rank (1 - 8) of the first piece with the specified type and
|
||||||
|
* current color in the file, or {@code -1} if there isn't any
|
||||||
|
*/
|
||||||
|
public int get(Type type, char file) {
|
||||||
|
int x = file - 97;
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
if (boardArr[x][i] != null && boardArr[x][i].getType() == type
|
||||||
|
&& boardArr[x][i].getColor() == log.getActiveColor())
|
||||||
|
return 8 - i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a {@link Piece} inside a rank (1 - 8).
|
||||||
|
*
|
||||||
|
* @param type The {@link Type} of the piece to search for
|
||||||
|
* @param rank The rank in which to search for the piece
|
||||||
|
* @return The file (A - H) of the first piece with the specified type and
|
||||||
|
* current color in the file, or {@code -} if there isn't any
|
||||||
|
*/
|
||||||
|
public char get(Type type, int rank) {
|
||||||
|
int y = rank - 1;
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
if (boardArr[i][y] != null && boardArr[i][y].getType() == type
|
||||||
|
&& boardArr[i][y].getColor() == log.getActiveColor())
|
||||||
|
return (char) (i + 97);
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a {@link Piece} that can move to a {@link Position}.
|
||||||
|
*
|
||||||
|
* @param type The {@link Type} of the piece to search for
|
||||||
|
* @param dest The destination that the piece is required to reach
|
||||||
|
* @return The position of a piece that can move to the specified destination
|
||||||
|
*/
|
||||||
|
public Position get(Type type, Position dest) {
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
for (int j = 0; j < 8; j++)
|
||||||
|
if (boardArr[i][j] != null && boardArr[i][j].getType() == type
|
||||||
|
&& boardArr[i][j].getColor() == log.getActiveColor()) {
|
||||||
|
Position pos = new Position(i, j);
|
||||||
|
if (boardArr[i][j].isValidMove(new Move(pos, dest))) return pos;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Places a piece at a position.
|
* Places a piece at a position.
|
||||||
*
|
*
|
||||||
|
@ -30,13 +30,13 @@ public class Move {
|
|||||||
this(new Position(xPos, yPos), new Position(xDest, yDest));
|
this(new Position(xPos, yPos), new Position(xDest, yDest));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Move fromSAN(String move) {
|
public static Move fromLAN(String move) {
|
||||||
return new Move(Position.fromSAN(move.substring(0, 2)),
|
return new Move(Position.fromLAN(move.substring(0, 2)),
|
||||||
Position.fromSAN(move.substring(2)));
|
Position.fromLAN(move.substring(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toSAN() {
|
public String toLAN() {
|
||||||
return pos.toSAN() + dest.toSAN();
|
return pos.toLAN() + dest.toLAN();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHorizontal() { return yDist == 0; }
|
public boolean isHorizontal() { return yDist == 0; }
|
||||||
|
@ -85,17 +85,39 @@ public abstract class Piece implements Cloneable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static enum Type {
|
public static enum Type {
|
||||||
|
|
||||||
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
|
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The first character of this {@link Type} in algebraic notation and lower case
|
* @return The first character of this {@link Type} in algebraic notation and
|
||||||
|
* lower case
|
||||||
*/
|
*/
|
||||||
public char firstChar() {
|
public char firstChar() {
|
||||||
return this == KNIGHT ? 'n' : Character.toLowerCase(this.toString().charAt(0));
|
return this == KNIGHT ? 'n' : Character.toLowerCase(this.toString().charAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Type fromFirstChar(char firstChar) {
|
||||||
|
switch (Character.toLowerCase(firstChar)) {
|
||||||
|
case 'k':
|
||||||
|
return KING;
|
||||||
|
case 'q':
|
||||||
|
return QUEEN;
|
||||||
|
case 'r':
|
||||||
|
return ROOK;
|
||||||
|
case 'n':
|
||||||
|
return KNIGHT;
|
||||||
|
case 'b':
|
||||||
|
return BISHOP;
|
||||||
|
case 'p':
|
||||||
|
return PAWN;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static enum Color {
|
public static enum Color {
|
||||||
|
|
||||||
WHITE, BLACK;
|
WHITE, BLACK;
|
||||||
|
|
||||||
public static Color fromFirstChar(char c) {
|
public static Color fromFirstChar(char c) {
|
||||||
|
@ -15,11 +15,11 @@ public class Position {
|
|||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Position fromSAN(String pos) {
|
public static Position fromLAN(String pos) {
|
||||||
return new Position(pos.charAt(0) - 97, 8 - Character.getNumericValue(pos.charAt(1)));
|
return new Position(pos.charAt(0) - 97, 8 - Character.getNumericValue(pos.charAt(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toSAN() {
|
public String toLAN() {
|
||||||
return String.valueOf((char) (x + 97)) + String.valueOf(8 - y);
|
return String.valueOf((char) (x + 97)) + String.valueOf(8 - y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ public class UCIPlayer extends Player implements UCIListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBestMove(String move) {
|
public void onBestMove(String move) {
|
||||||
Move moveObj = Move.fromSAN(move);
|
Move moveObj = Move.fromLAN(move);
|
||||||
game.onMove(this, moveObj);
|
game.onMove(this, moveObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ public class UCIInfo {
|
|||||||
multipv = Integer.parseInt(tokens[++i]);
|
multipv = Integer.parseInt(tokens[++i]);
|
||||||
break;
|
break;
|
||||||
case "currmove":
|
case "currmove":
|
||||||
currmove = Move.fromSAN(tokens[++i]);
|
currmove = Move.fromLAN(tokens[++i]);
|
||||||
break;
|
break;
|
||||||
case "currmovenumber":
|
case "currmovenumber":
|
||||||
currmovenumber = Integer.parseInt(tokens[++i]);
|
currmovenumber = Integer.parseInt(tokens[++i]);
|
||||||
@ -97,11 +97,11 @@ public class UCIInfo {
|
|||||||
break;
|
break;
|
||||||
case "pv":
|
case "pv":
|
||||||
while (++i < tokens.length && !params.contains(tokens[i]))
|
while (++i < tokens.length && !params.contains(tokens[i]))
|
||||||
pv.add(Move.fromSAN(tokens[i]));
|
pv.add(Move.fromLAN(tokens[i]));
|
||||||
break;
|
break;
|
||||||
case "refutation":
|
case "refutation":
|
||||||
while (++i < tokens.length && !params.contains(tokens[i]))
|
while (++i < tokens.length && !params.contains(tokens[i]))
|
||||||
refutation.add(Move.fromSAN(tokens[i]));
|
refutation.add(Move.fromLAN(tokens[i]));
|
||||||
break;
|
break;
|
||||||
// TODO: currline
|
// TODO: currline
|
||||||
case "currline":
|
case "currline":
|
||||||
|
@ -93,7 +93,7 @@ public class UCIReceiver implements Runnable {
|
|||||||
String move = tokens[0];
|
String move = tokens[0];
|
||||||
|
|
||||||
// Ponder move
|
// Ponder move
|
||||||
if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromSAN(tokens[2])));
|
if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromLAN(tokens[2])));
|
||||||
else listeners.forEach(l -> l.onBestMove(move));
|
else listeners.forEach(l -> l.onBestMove(move));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ public class LogPanel extends JPanel implements Subscribable {
|
|||||||
if (log == null || log.isEmpty()) return;
|
if (log == null || log.isEmpty()) return;
|
||||||
final DefaultTableModel model = new DefaultTableModel(new String[] { "White", "Black" }, 0);
|
final DefaultTableModel model = new DefaultTableModel(new String[] { "White", "Black" }, 0);
|
||||||
for (Iterator<MoveNode> iter = log.iterator(); iter.hasNext();) {
|
for (Iterator<MoveNode> iter = log.iterator(); iter.hasNext();) {
|
||||||
String[] row = new String[] { iter.next().move.toSAN(), "" };
|
String[] row = new String[] { iter.next().move.toLAN(), "" };
|
||||||
if (iter.hasNext()) row[1] = iter.next().move.toSAN();
|
if (iter.hasNext()) row[1] = iter.next().move.toLAN();
|
||||||
model.addRow(row);
|
model.addRow(row);
|
||||||
}
|
}
|
||||||
mtable.setModel(model);
|
mtable.setModel(model);
|
||||||
|
@ -43,10 +43,10 @@ class LogTest {
|
|||||||
log.setActiveColor(Color.WHITE);
|
log.setActiveColor(Color.WHITE);
|
||||||
other.setActiveColor(Color.BLACK);
|
other.setActiveColor(Color.BLACK);
|
||||||
assertNotEquals(log.getActiveColor(), other.getActiveColor());
|
assertNotEquals(log.getActiveColor(), other.getActiveColor());
|
||||||
log.add(Move.fromSAN("a2a4"), null, true);
|
log.add(Move.fromLAN("a2a4"), null, true);
|
||||||
log.add(Move.fromSAN("a4a5"), null, true);
|
log.add(Move.fromLAN("a4a5"), null, true);
|
||||||
other.add(Move.fromSAN("a2a4"), null, true);
|
other.add(Move.fromLAN("a2a4"), null, true);
|
||||||
other.add(Move.fromSAN("a4a5"), null, true);
|
other.add(Move.fromLAN("a4a5"), null, true);
|
||||||
assertNotEquals(log.getRoot(), other.getRoot());
|
assertNotEquals(log.getRoot(), other.getRoot());
|
||||||
assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations());
|
assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations());
|
||||||
}
|
}
|
||||||
|
@ -19,21 +19,21 @@ class PositionTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Test method for
|
* Test method for
|
||||||
* {@link dev.kske.chess.board.Position#fromSAN(java.lang.String)}.
|
* {@link dev.kske.chess.board.Position#fromLAN(java.lang.String)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testFromSAN() {
|
void testFromSAN() {
|
||||||
for (int i = 0; i < n; i++)
|
for (int i = 0; i < n; i++)
|
||||||
assertEquals(positions[i], Position.fromSAN(sans[i]));
|
assertEquals(positions[i], Position.fromLAN(sans[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test method for {@link dev.kske.chess.board.Position#toSAN()}.
|
* Test method for {@link dev.kske.chess.board.Position#toLAN()}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testToSAN() {
|
void testToSAN() {
|
||||||
for (int i = 0; i < n; i++)
|
for (int i = 0; i < n; i++)
|
||||||
assertEquals(sans[i], positions[i].toSAN());
|
assertEquals(sans[i], positions[i].toLAN());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user