dimanche 21 novembre 2021

A case of template method pattern: how would you refactor this?

I'm refactoring my Tic Tac Toe java project and I've stumbled upon an issue with (my comprehension of) the form template method pattern. In summary: I have created three player classes that implement variants of the minimax algorithm. I then created an abstract class to collect all the common methods of those classes. Then I realized that two of the three methods that call the recursive step of the variant of minimax algorithm are nearly identical. Talking about the public int sendMove() method.

Here's the call to the simple variant:

public class CpuMiniMax extends CommonTools implements Player  {
...
...
    public int sendMove() {
    int bestScore = Integer.MIN_VALUE;
    int score = 0;
    int position = 0;
    char opponentMark = myMark == 'X' ? 'O' : 'X';
    for (int i = 0; i < board.length; i++) {
        if (board[i] == ' ') {
            board[i] = myMark;
            score = miniMax(3, opponentMark, false); // <- the only difference
            board[i] = ' ';
            if (score > bestScore) {
                position = i;
                bestScore = score;
            }
        }
    }
    board[position] = myMark;
    return position;
    }
...
...
}

Here's the call to the alpha beta pruning variant:

public class CpuMiniMaxAlphaBetaPruning extends CommonTools implements Player  {
...
...
    public int sendMove() {
    int bestScore = Integer.MIN_VALUE;
    int score = 0;
    int position = 0;
    char opponentMark = myMark == 'X' ? 'O' : 'X';
    for (int i = 0; i < board.length; i++) {
        if (board[i] == ' ') {
            board[i] = myMark;
            score = miniMaxAlphaBetaPruning(3, opponentMark, Integer.MIN_VALUE, Integer.MAX_VALUE, false); // <- the only difference
            board[i] = ' ';
            if (score > bestScore) {
                position = i;
                bestScore = score;
            }
        }
    }
    board[position] = myMark;
    return position;
    }
...
...
}

And now for something completely different here's the call to an optimized alpha beta pruning:

public class CpuMiniMaxAlphaBetaPruningOptimized extends CommonTools implements Player {
...
...
public int sendMove() {
    int[] emptySquares = emptySquaresRandomizer();
    char opponentMark = myMark == 'X' ? 'O' : 'X';
    for (int i = 0; i < emptySquares.length; i++) {
        board[emptySquares[i]] = myMark;
        if (tris(myMark)) {
            return emptySquares[i];
        }
        board[emptySquares[i]] = ' ';
    }
    for (int i = 0; i < emptySquares.length; i++) {
        board[emptySquares[i]] = opponentMark;
        if (tris(opponentMark)) {
            board[emptySquares[i]] = myMark;
            return emptySquares[i];
        }
        board[emptySquares[i]] = ' ';
    }
    for (int i = 0; i < emptySquares.length; i++) {
        board[emptySquares[i]] = myMark;
        if (isFork(emptySquares, i)) {
            return emptySquares[i];
        }
        board[emptySquares[i]] = ' ';
    }
    int bestScore = Integer.MIN_VALUE;
    int score = 0;
    int position = 0;
    for (int i = 0; i < emptySquares.length; i++) {
        board[emptySquares[i]] = myMark;
        score = miniMaxAlphaBetaPruningOptimized(3, opponentMark, Integer.MIN_VALUE, Integer.MAX_VALUE, false);
        board[emptySquares[i]] = ' ';
        if (score > bestScore) {
            position = emptySquares[i];
            bestScore = score;
        }
    }
    board[position] = myMark;
    return position;
    }
...
...
}

So I decided to refactor more in my CommonTools abstract class:

public abstract class CommonTools {
...
...
    public int sendMove() {
    int bestScore = Integer.MIN_VALUE;
    int score = 0;
    int position = 0;
    char opponentMark = myMark == 'X' ? 'O' : 'X';
    for (int i = 0; i < board.length; i++) {
        if (board[i] == ' ') {
            board[i] = myMark;
            score = callRecursiveMiniMaxAlgorithm(opponentMark); // <- call to the method subclasses have to implement
            board[i] = ' ';
            if (score > bestScore) {
                position = i;
                bestScore = score;
            }
        }
    }
    board[position] = myMark;
    return position;
    }
...
...
    protected abstract int callRecursiveMiniMaxAlgorithm(char opponentMark); // <- the method subclasses have to implement
...
...
}

In this way I was able to eliminate duplication of code in my two classes CpuMiniMax and CpuMiniMaxAlphaBetaPruning just by deleting the public int sendMove() method and implementing the abstract method above, while I kept its implementation in my CpuMiniMaxAlphaBetaPruningOptimized class. The latter, extending the abstract class, has to implement the abstract method too of course. And this implementation would remain a stub since the class overrides the public int sendMove() defined in the abstract class. And this, for some reasons, quite bothers me. So my questions are: is this a correct use of the pattern (even if produces unused code)? How would you refactor?

Aucun commentaire:

Enregistrer un commentaire