Note that what I told about Coordinate is an observation made base the code you shared, not a strong recomendation. After introducing a series of changes it might still hold true, or might not.
The point is that you need to develop a habit of a making small incremental changes while evolving your disign and constanly evaluating the code elements you're work with at the moment.
The driving motivation behind every design change should be your understanding of the value this change might bring.
It's normal to revert the change if it doesn't improve the design. It's also normal for individual components of the system to be discarded when they no longer serve their purpose or don't play well with each other. In such cases, it may be necessary to introduce different abstractions to facilitate the addition of a new feature.
The abstractions I proposed to add (Cell and Player) are also not something set in stone. Introducing them into the code is a feasible and relatively small change which you can immediately leverage to improve the code quality. After you finish with this change, evaluate your design. You might come to a conclusion that you need to alter them in someway, or you might decide to introduce Board abstraction and incorporate them into it.
Here's how canMove
can be implemented based on the functionality intoduced earlier:
public boolean canMove(int row, int col) {
if (!isOccupiedByCurrentPlayer(row, col)) return false;
if (anyCanJump()) return false;
return canMove(row, col, ColumnDirection.LEFT)
|| canMove(row, col, ColumnDirection.RIGHT);
}
private boolean anyCanJump() {
boolean canJump = false;
for (int row = 0; row < board.length; row++) {
for (int col = 0; col < board[0].length; col++) {
canJump = canJump(row, col);
if (canJump) break;
}
}
return canJump;
}
private boolean canMove(int row, int col, ColumnDirection colDirection) {
int targetRow = row + 2 * rowDirection();
int targetCow = col + 2 * colDirection.toInt();
return Coordinate.isValid(targetRow, targetCow)
&& isEmpty(targetRow, targetCow);
}