In your code there are four loops with very similar functionality. Each loop checks the board in a different direction. This could be extracted to a method and reuse the method 4 times:
/**
*
* @param theSeed Value expected in each cell
* @param row Row index to check
* @param col Col index to check
* @param count Number of consecutive elements found in the line.
* @param rowIncrement Increment to the row index for the next evaluation
* @param colIncrement Increment to the col index for the next evaluation
* @return true if there is a winner combination in the line, otherwise false.
*/
private boolean checkLine(Seed theSeed, int row, int col, int count, int rowIncrement, int colIncrement) {
if (!areValidIndexes(row, col)) {
return false;
}
if (theSeed == cells[row][col].getContent()) {
count++;
if (count >= WIN_SIZE) {
return true;
} else {
return checkLine(theSeed, row + rowIncrement, col + colIncrement, count, rowIncrement, colIncrement);
}
} else {
return false;
}
}
private boolean areValidIndexes(int row, int col) {
if (row >= 0 && row < ROWS &&
col >= 0 && col < COLS) {
return true;
}
return false;
}
To simplify the usage of this method, we could create some wrapper method for each direction:
private boolean checkRow(Seed theSeed, int row, int col) {
return checkLine(theSeed, row, col, 0, 1, 0);
}
private boolean checkColumn(Seed theSeed, int row, int col) {
return checkLine(theSeed, row, col, 0, 0, 1);
}
private boolean checkForwardDiagonal(Seed theSeed, int row, int col) {
return checkLine(theSeed, row, col, 0, 1, 1);
}
private boolean checkBackwardDiagonal(Seed theSeed, int row, int col) {
return checkLine(theSeed, row, col, 0, -1, 1);
}
At this point, implement the hasWon
method is very straightforward, just iterate over each cell and check each direction:
public boolean hasWon(Seed theSeed) {
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
if (checkRow(theSeed, row, col) ||
checkColumn(theSeed, row, col) ||
checkForwardDiagonal(theSeed, row, col) ||
checkBackwardDiagonal(theSeed, row, col)) {
return true;
}
}
}
return false;
}
UPDATE
As suggested in the comments, the algorithm could be more efficient if only the row, column and diagonals of the last move are checked.
The method checkLine would be more useful to implement this change if the full line is checked for a winner combination:
/**
*
* @param theSeed Value expected in each cell
* @param row Row index to check
* @param col Col index to check
* @param count Number of consecutive elements in the line.
* @param rowIncrement Increment to the row index for the next evaluation
* @param colIncrement Increment to the col index for the next evaluation
* @return true if there is a winner combination in the line, otherwise false.
*/
private boolean checkLine(Seed theSeed, int row, int col, int count, int rowIncrement, int colIncrement) {
if (!areValidIndexes(row, col)) {
return false;
}
if (theSeed == cells[row][col].getContent()) {
count++;
if (count >= WIN_SIZE) {
return true;
} else {
return checkLine(theSeed, row + rowIncrement, col + colIncrement, count, rowIncrement, colIncrement);
}
} else {
// Reset the count and keep processing the line
return checkLine(theSeed, row + rowIncrement, col + colIncrement, 0, rowIncrement, colIncrement);
}
}
Now, the wrapper methods to check each direction have more functionality and they calculate the beginning of the row, column or diagonal. The methods for the row and column are straightforward, just set to 0 the index for the row or column:
private boolean checkRow(Seed theSeed, int row, int col) {
return checkLine(theSeed, 0, col, 0, 1, 0);
}
private boolean checkColumn(Seed theSeed, int row, int col) {
return checkLine(theSeed, row, 0, 0, 0, 1);
}
For the diagonals, we need to calculate the starting indexes. We are at the start of the diagonal when the row index or column index are in the edge, so we calculate the minimum distance to the edge and increase or reduce the indexes that amount:
private boolean checkForwardDiagonal(Seed theSeed, int row, int col) {
int minDistanceToEdge = Math.min(row, col);
return checkLine(theSeed, row - minDistanceToEdge, col - minDistanceToEdge, 0, 1, 1);
}
private boolean checkBackwardDiagonal(Seed theSeed, int row, int col) {
int minDistanceToEdge = Math.min(ROWS - 1 - row, col);
return checkLine(theSeed, row + minDistanceToEdge, col - minDistanceToEdge, 0, -1, 1);
}
The hasWon
method can now use the wrapper functions to check only the row, column and diagonals of the last move:
public boolean hasWon(Seed theSeed, int row, int col) {
return checkRow(theSeed, row, col) ||
checkColumn(theSeed, row, col) ||
checkForwardDiagonal(theSeed, row, col) ||
checkBackwardDiagonal(theSeed, row, col);
}
XXOX
? It doesn't seem like X should win if there is an O blocking like that, but currently your code considers that a win. \$\endgroup\$