Determining winning line in TicTacToe game I am trying to write code to determine a win in tic-tac-toe game. In this question, I am not focusing on the whole game, I am only interested in the question of how to determine that the game ended with the victory of one of the players. So I made a synthetic example to test different approaches.
My guess is that a winning line on the board can only appear after the current player has made his move. Therefore, it makes no sense to check all the lines on the board. I only consider those that include the last move made. I assume that the board can be of any size (but only square) and the winning line has the same size. Thus, it is one of the rows, or one of the columns, or one of the two diagonals.
After several attempts, I ended up with the code below:
import java.util.List;
import java.util.function.Predicate;

enum Cell {
    X,
    O,
    B // blank cell
}

public class Board {

    private final int size = 3;
    private final Cell[][] board;

    public Board(Cell[][] board) {
        this.board = board;
    }

    private boolean isLineCompleted(Predicate&lt;Integer&gt; predicate) {
        boolean result = true;
        int i = 0;
        while (result &amp;&amp; i &lt; size) {
            result = predicate.test(i);
            i++;
        }
        return result;
    }

    public boolean hasWinningLine(int row, int col) {
        if (board[row][col] == Cell.B) return false;

        Cell base = board[row][col];
        List&lt;Predicate&lt;Integer&gt;&gt; checks = List.of(
                i -&gt; board[row][i] == base, //columns
                i -&gt; board[i][col] == base, //rows
                i -&gt; board[i][i] == base,   //top-down diagonal
                i -&gt; board[size - i - 1][i] == base //down-top diagonal
        );

        boolean lineCompleted = false;
        for (var check : checks) {
            if (!lineCompleted) {
                lineCompleted = isLineCompleted(check);
            }
        }

        return lineCompleted;
    }
}

And I wrote some tests that use this code something like this:
Cell[][] xWins = {
    {Cell.X, Cell.O, Cell.X},
    {Cell.O, Cell.X, Cell.O},
    {Cell.X, Cell.B, Cell.B}};

board = new Board(xWins);
AssertTrue(board.hasWinningLine(0, 2));

I have several questions:

How easy is this code to read and understand? How can you improve it in this sense?

I try not to make unnecessary checks. The algorithm should return false as soon as it finds an unsuitable cell. Should I handle the loop like this


for (var check : checks) {
            if (!lineCompleted) {
                lineCompleted = isLineCompleted(check);
            } else break; // That's better? 
        }

Is it possible to rewrite this loop using a stream API?

When checking each line, this code can compare the cell to itself. How can i avoid this without complicating the code? Should I even think about it?

I am new to programming and would appreciate any advice that would help me code better.
EDITED:
The predicate checking loop can be rewritten as
return checks.stream()
    .map(this::isLineCompleted)
    .filter(b -&gt; b == true)
    .findFirst()
    .orElse(false);

What are the disadvantages of this approach?
First off, considering the small number of lines and items in a line, your (and my following) optimizations are probably not really worth it. That said, as you have noticed, your loops run through all all items, even when not necessary, and that would be bad in more complex situations.
Generally your code tends to be a bit too complex/verbose. For example, isLineCompleted would be much simpler using a regular for loop and early exit:
private boolean isLineCompleted(Predicate&lt;Integer&gt; predicate) {
    for (int i = 0; i &lt; size; i++) {
         if (predicate.test(i)) {
             return true;
         }
    }
    return false;
}

By extracting the checks loop into a separate method, the same pattern could be used there.
Or Java's Stream has the .anyMatch() method, which simplifies it even more:
return checks.stream().anyMatch(this::isLineCompleted);

Using a IntStream allows isLineCompleted to do simular using .allMatch():
private boolean isLineCompleted(Predicate&lt;Integer&gt; predicate) {
    return IntStream.range(0, size).allMatch(predicate);
}

One final point: The value B in the Cell enum is badly named considering you need to add the comment &quot;blank cell&quot;. Call it BLANK instead. Also it would seem more logical to put it first in the enum, where it would have the cardinal value 0. Or you may even want to drop it all together and just use null.