Thank you for releasing your code! You have some clever solutions and I would like to add few more points to the previous answers.
Naming
Piece
The class Pawn confuses me, not because it is to complicated, but because of its name. Pawn is used to validate a movement and to do a move of a piece on the board, while the name suggests that it is the piece on the board. So with other words: It is not a piece it is a utility of a piece
The abstract class Piece could be renamed to AbstractMovements and Pawn to PawnMovements.
Note the appended s to the names, which indicates that it is a utility class. You can find some in the Java-World like Collections, Objects and so on.
Move
Additional the class MoveImpl is not responsible for a move. The moves are done by the implementations of Piece (AbstractMovements). Actual MoveImpl response is to extract the movement information from the users input and delegate it to a Piece. I think it would make sense to rename the abstract class Move to Reader and MoveImpl to ComandLineReader.
Primitive Obsession
Primitive Obsession is using primitive data types to represent domain ideas. For example, we use a String to represent a message [...]
The code-base contains a heavy use of int to represent a Position as a source and destination, String gets abused to represent a Piece and String[][] represents the board.
Examples
The method signiture of validateForPiece in Piece could look like
public boolean validateForPiece(Position source, Position destination)
or the class MoveImpl like
public class MoveImpl implements Move {
Piece piece;
Position source;
Position destination;
// ...
}
With the new data-types the if-statements can be clearer. For example
public boolean checkMiddlePieces(int srcX, int srcY, int destX, int destY) { if(srcX==destX && srcY!=destY) { /*..*/ } // .. }
could be look like
public boolean checkMiddlePieces(Position source, Position destination) {
if(source.hasSameRowAs(destination) && source.hasNotSameColoumAs(destination)) { /*.. */ }
// ..
}
The Main Class
The main looks like a Factory with super power. It should be possible to move these if-statements into its own class. The main class could interact with with a class called ChessGame or something like that, that knows all your chess logic.
To check if the player enters the correct task at the correct time you could use the State Pattern. The InputState makes it possible for example that the user can't enter "Display" five times in a row or don't "Move" before "Display" or "Board"
public class Main {
private ChessGame chessGame = new ChessGame();
public static void main(String[] args) throws IOException {
Move move = new MoveImpl();
System.out.println("Input:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String input = br.readLine();
chessGame.play(input);
}
}
public class ChessGame {
private InputState inputState;
public ChessGame() {
// user needs to enter first "Board" or "Display"
this.inputState = new InformationInputState(this);
}
void setState(InputState inputState) {
this.inputState = inputState;
}
public void play(String input) {
inputState.execute(input);
}
}
interface InputState {
void execute(ChessGame, String input);
}
class MoveInputState implements InputState {
private ChessGame chessGame;
// constructor
public void execute(String input) {
if (input.equals("Move")) {
// ..
chessGame.setState(new InformationInputState(chessGame))
}
}
}
class InformationInputState implements InputState {
private ChessGame chessGame;
// constructor
public void execute(String input) {
if (input.equals("Board")) {
// ..
chessGame.setState(new MoveInputState(chessGame))
} else if ("Display") {
// ..
chessGame.setState(new MoveInputState(chessGame))
}
}
}