Renamed SAN-like coordinate notation to LAN, added SAN support to Board
This commit is contained in:
		| @@ -4,6 +4,8 @@ import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| 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.Type; | ||||
| @@ -182,6 +184,65 @@ public class Board { | ||||
| 		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. | ||||
| 	 */ | ||||
| @@ -485,7 +546,7 @@ public class Board { | ||||
| 		castlingRights.put(Color.BLACK, blackCastling); | ||||
|  | ||||
| 		// 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 | ||||
| 		log.setHalfmoveClock(Integer.parseInt(parts[4])); | ||||
| @@ -506,7 +567,7 @@ public class Board { | ||||
| 			for (int j = 0; j < 8; j++) { | ||||
| 				final Piece piece = boardArr[j][i]; | ||||
| 				if (piece == null) ++emptyCount; | ||||
| 				else { | ||||
| 				else { // TODO: rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b - - 1 2 error | ||||
| 					if (emptyCount != 0) { | ||||
| 						sb.append(emptyCount); | ||||
| 						emptyCount = 0; | ||||
| @@ -535,7 +596,7 @@ public class Board { | ||||
| 		final MoveNode lastMove = log.getLast(); | ||||
|  | ||||
| 		// 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 | ||||
| 		sb.append(" " + String.valueOf(lastMove == null ? 0 : lastMove.halfmoveClock)); | ||||
| @@ -554,6 +615,58 @@ public class Board { | ||||
| 		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. | ||||
| 	 *  | ||||
|   | ||||
| @@ -30,13 +30,13 @@ public class Move { | ||||
| 		this(new Position(xPos, yPos), new Position(xDest, yDest)); | ||||
| 	} | ||||
|  | ||||
| 	public static Move fromSAN(String move) { | ||||
| 		return new Move(Position.fromSAN(move.substring(0, 2)), | ||||
| 				Position.fromSAN(move.substring(2))); | ||||
| 	public static Move fromLAN(String move) { | ||||
| 		return new Move(Position.fromLAN(move.substring(0, 2)), | ||||
| 				Position.fromLAN(move.substring(2))); | ||||
| 	} | ||||
|  | ||||
| 	public String toSAN() { | ||||
| 		return pos.toSAN() + dest.toSAN(); | ||||
| 	public String toLAN() { | ||||
| 		return pos.toLAN() + dest.toLAN(); | ||||
| 	} | ||||
|  | ||||
| 	public boolean isHorizontal() { return yDist == 0; } | ||||
|   | ||||
| @@ -85,17 +85,39 @@ public abstract class Piece implements Cloneable { | ||||
| 	} | ||||
|  | ||||
| 	public static enum Type { | ||||
|  | ||||
| 		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() { | ||||
| 			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 { | ||||
|  | ||||
| 		WHITE, BLACK; | ||||
|  | ||||
| 		public static Color fromFirstChar(char c) { | ||||
|   | ||||
| @@ -15,11 +15,11 @@ public class Position { | ||||
| 		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))); | ||||
| 	} | ||||
|  | ||||
| 	public String toSAN() { | ||||
| 	public String toLAN() { | ||||
| 		return String.valueOf((char) (x + 97)) + String.valueOf(8 - y); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -51,7 +51,7 @@ public class UCIPlayer extends Player implements UCIListener { | ||||
|  | ||||
| 	@Override | ||||
| 	public void onBestMove(String move) { | ||||
| 		Move moveObj = Move.fromSAN(move); | ||||
| 		Move moveObj = Move.fromLAN(move); | ||||
| 		game.onMove(this, moveObj); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -68,7 +68,7 @@ public class UCIInfo { | ||||
| 					multipv = Integer.parseInt(tokens[++i]); | ||||
| 					break; | ||||
| 				case "currmove": | ||||
| 					currmove = Move.fromSAN(tokens[++i]); | ||||
| 					currmove = Move.fromLAN(tokens[++i]); | ||||
| 					break; | ||||
| 				case "currmovenumber": | ||||
| 					currmovenumber = Integer.parseInt(tokens[++i]); | ||||
| @@ -97,11 +97,11 @@ public class UCIInfo { | ||||
| 					break; | ||||
| 				case "pv": | ||||
| 					while (++i < tokens.length && !params.contains(tokens[i])) | ||||
| 						pv.add(Move.fromSAN(tokens[i])); | ||||
| 						pv.add(Move.fromLAN(tokens[i])); | ||||
| 					break; | ||||
| 				case "refutation": | ||||
| 					while (++i < tokens.length && !params.contains(tokens[i])) | ||||
| 						refutation.add(Move.fromSAN(tokens[i])); | ||||
| 						refutation.add(Move.fromLAN(tokens[i])); | ||||
| 					break; | ||||
| 				// TODO: currline | ||||
| 				case "currline": | ||||
|   | ||||
| @@ -93,7 +93,7 @@ public class UCIReceiver implements Runnable { | ||||
| 		String		move	= tokens[0]; | ||||
|  | ||||
| 		// 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)); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -58,8 +58,8 @@ public class LogPanel extends JPanel implements Subscribable { | ||||
| 		if (log == null || log.isEmpty()) return; | ||||
| 		final DefaultTableModel model = new DefaultTableModel(new String[] { "White", "Black" }, 0); | ||||
| 		for (Iterator<MoveNode> iter = log.iterator(); iter.hasNext();) { | ||||
| 			String[] row = new String[] { iter.next().move.toSAN(), "" }; | ||||
| 			if (iter.hasNext()) row[1] = iter.next().move.toSAN(); | ||||
| 			String[] row = new String[] { iter.next().move.toLAN(), "" }; | ||||
| 			if (iter.hasNext()) row[1] = iter.next().move.toLAN(); | ||||
| 			model.addRow(row); | ||||
| 		} | ||||
| 		mtable.setModel(model); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user