HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Evaluation function for Connect Four

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
evaluationconnectfourfunctionfor

Problem

Here is some code I have written as a connect four negamax evaluation function. The state of the board is stored inside a class state. state.get() is \$O(1)\$, state.getBoard() is \$O(1)\$, and state.getWinnerColor() and state.getDraw() are also constant (although they have a higher constant coefficient). The real meat of the problem is the connectedStrength() function, which is the most accurate component of the heuristic I have. With it commented out of evaluate(), it runs in around 2500 nanoseconds, uncommented, it jumps 430% to around 10800!

```
private int[][] bS = {{3, 4, 5, 7, 5, 4, 3}, // coefficients for board values
{4, 6, 8, 10, 8, 6, 4},
{5, 8, 11, 13, 11, 8, 5},
{5, 8, 11, 13, 11, 8, 5},
{4, 6, 8, 10, 8, 6, 4},
{3, 4, 5, 7, 5, 4, 3}};
private int[] cS = {0, 10, 100}; // coefficients for x in a row

public int evaluate() {
int winner = state.getWinnerColor();
if (winner == 1) {
return Integer.MAX_VALUE;
}
else if (winner == -1) {
return -Integer.MAX_VALUE;
}
else if (state.isDraw()) {
return 0;
}
int total = 0;
total += boardStrength();
total += connectedStrength();
return total;
}

private int boardStrength() {
int total = 0;
for (int row = 0; row = 3) { // if possible to create a four with this chain
total += color * cS[a.first() + b.first()]; // add to heuristic
}
}
}
visited[row][col] = true;
}
}
}
return total;
}

private IntegerPair check(IntegerPair last, int d1, int d2, boolean[][] visited) { // returns the # in a row
//in row direction d1 and col direction d2 and also if the next one is free
int len = 1, player = state.get(last.first(), last.second());
while (last.first() + len * d1 >= 0
&& last.second() + l

Solution

Code duplication and magic numbers

You have some code duplication in check() method which can be extracted to a separate method and be simplified to do less claculations.

Let us first add a isValidColorPosition() method to get this ugly while condition extracted

private final int LEFT_BOTTOM_BORDER= 0;
private final int RIGHT_BORDER = 6;
private final int TOP_BORDER = 5;
private boolean isValidColorPosition(int row, int col, int color) {
    return row >= LEFT_BOTTOM_BORDER
            && col >= LEFT_BOTTOM_BORDER
            && row <= TOP_BORDER
            && col <= RIGHT_BORDER
            && state.get(row, col) == color;
}


great, we also removed the magic numbers.

Now we use this method in the extracted getLength() method

private int getLength(IntegerPair position, IntegerPair directions, int currentColor, boolean[][] visited, int count) {
    int row = position.first() + count * directions.first();
    int col = position.second() + count * directions.second();
    while (isValidColorPosition(row, col, currentColor)) {
        visited[row][col] = true;
        count += 1;
        row += directions.first();
        col += directions.second();
    }
    return count;
}


which is called by the former check() method now renamed to getLengthPair()

private final int EMPTY_COLOR = 0;
private IntegerPair getLengthPair(IntegerPair last, IntegerPair direction, int playerColor, boolean[][] visited) {

    IntegerPair position = new IntegerPair(last.first() + direction.first(), 
            last.second() + direction.second());

    int playerLength = getLength(position, direction, playerColor, visited, 0);

    int possibleLength = getLength(position, direction, EMPTY_COLOR, visited, playerLength);

    return new IntegerPair(playerLength, possibleLength);
}


and again one magic number is gone.

This method will be called by the new getStrength() method

private int getStrength(IntegerPair last, int color, boolean[][] visited, int[][] directions) {

    int strength = 0;

    for (int i = 0; i = 3) { 
            strength += color * cS[a.first() + b.first()];
        }
    }
    return strength;
}


which is called by the connectedStrength() method

public int connectedStrength() {
    int total = 0;
    int[][] s = {{1, 1, 0, -1}, {0, 1, 1, 1}};
    boolean[][] visited = new boolean[6][7];
    for (int row = 0; row < 6; ++row) {
        for (int col = 0; col < 7; ++col) {
            if (visited[row][col]) {
                continue;
            }
            int color = state.get(row, col);
            if (color != 0) {
                total += getStrength(new IntegerPair(row, col), color, visited, s);
            }
            visited[row][col] = true;
        }
    }
    return total;
}


Comments should be used only to describe why something is done in the way it is done. The code itself should describe what is done by using meaningful names for classes, methods and variables.

By using guard clauses you can prevent to code by using the Arrow Anti Pattern.

Code Snippets

private final int LEFT_BOTTOM_BORDER= 0;
private final int RIGHT_BORDER = 6;
private final int TOP_BORDER = 5;
private boolean isValidColorPosition(int row, int col, int color) {
    return row >= LEFT_BOTTOM_BORDER
            && col >= LEFT_BOTTOM_BORDER
            && row <= TOP_BORDER
            && col <= RIGHT_BORDER
            && state.get(row, col) == color;
}
private int getLength(IntegerPair position, IntegerPair directions, int currentColor, boolean[][] visited, int count) {
    int row = position.first() + count * directions.first();
    int col = position.second() + count * directions.second();
    while (isValidColorPosition(row, col, currentColor)) {
        visited[row][col] = true;
        count += 1;
        row += directions.first();
        col += directions.second();
    }
    return count;
}
private final int EMPTY_COLOR = 0;
private IntegerPair getLengthPair(IntegerPair last, IntegerPair direction, int playerColor, boolean[][] visited) {

    IntegerPair position = new IntegerPair(last.first() + direction.first(), 
            last.second() + direction.second());

    int playerLength = getLength(position, direction, playerColor, visited, 0);

    int possibleLength = getLength(position, direction, EMPTY_COLOR, visited, playerLength);

    return new IntegerPair(playerLength, possibleLength);
}
private int getStrength(IntegerPair last, int color, boolean[][] visited, int[][] directions) {

    int strength = 0;

    for (int i = 0; i < 4; ++i) { 
        IntegerPair direction = new IntegerPair(directions[0][i], directions[1][i]);
        IntegerPair a = getLengthPair(last, direction, color, visited);

        direction = new IntegerPair(-directions[0][i], -directions[1][i]);
        IntegerPair b = getLengthPair(last, direction, color, visited);

        if (a.second() + b.second() >= 3) { 
            strength += color * cS[a.first() + b.first()];
        }
    }
    return strength;
}
public int connectedStrength() {
    int total = 0;
    int[][] s = {{1, 1, 0, -1}, {0, 1, 1, 1}};
    boolean[][] visited = new boolean[6][7];
    for (int row = 0; row < 6; ++row) {
        for (int col = 0; col < 7; ++col) {
            if (visited[row][col]) {
                continue;
            }
            int color = state.get(row, col);
            if (color != 0) {
                total += getStrength(new IntegerPair(row, col), color, visited, s);
            }
            visited[row][col] = true;
        }
    }
    return total;
}

Context

StackExchange Code Review Q#82647, answer score: 5

Revisions (0)

No revisions yet.