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 < 6; ++row) {
for (int col = 0; col < 7; ++col) {
total += state.getBoard()[row][col] * bS[row][col];
}
}
return total;
}
public int connectedStrength() {
int total = 0, color;
IntegerPair a = null, b = null;
int[][] s = {{1,1,0,-1},{0,1,1,1}}; //up/down, up-right/down-left, right/left, down-right/up-left
IntegerPair last;
boolean[][] visited = new boolean[6][7];
for (int row = 0; row < 6; ++row) {
for (int col = 0; col < 7; ++col) { // loop through each tile position possible
if (!visited[row][col]) {
last = new IntegerPair(row, col);
color = state.get(row, col);
if (color != 0) { // if square isn't empty
for (int i = 0; i < 4; ++ i) { // loop through four directions
a = check(last, s[0][i], s[1][i], visited); // get lengths
b = check(last, -s[0][i], -s[1][i], visited);
if (a.second() + b.second() >= 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() + len * d2 >= 0
&& last.first() + len * d1 <= 5
&& last.second() + len * d2 <= 6 // while inbounds
&& state.get(last.first() + len * d1, last.second() + len * d2) == player) { // and while the tiles are the same color as the player's
visited[last.first() + len * d1][last.second() + len * d2] = true;
len += 1;
}
int same = len;
while (last.first() + len * d1 >= 0
&& last.second() + len * d2 >= 0
&& last.first() + len * d1 <= 5
&& last.second() + len * d2 <= 6 // again, while inbounds
&& state.get(last.first() + len * d1, last.second() + len * d2) == 0) { // while the tiles are not the enemy's
visited[last.first() + len * d1][last.second() + len * d2] = true;
len += 1;
}
return new IntegerPair(same-1, len-1);
}
The evaluate function is quite simple: for each unvisited tile, if the tile has a tile in it (!=0) then it calls check, which returns a pair of ints, the first one being how long the current linear chain is, the second one being how long the chain could potentially extend to. Another way of thinking about it is the first one is when the chain ends at an empty square or enemy tile or the wall, and the second one is when it ends at an enemy tile or the wall. If the second value is less than 3 (thus a chain of four can never be constructed) (3 is not including the starting tile itself) the value is thrown away; otherwise, the value is added to the total (or subtracted depending on the player).
Are there are places where this function could be optimized?
connectedStrength()
does what you think it should do. First, why setvisited
insidecheck()
? If you visit a tile while checking for a horizontal chain, you won't check for a vertical chain starting on that tile because it has already been visited. Second, you don't consider "tile space tile space" as a possible 4 chain becausecheck
stops searching after the first series of spaces. \$\endgroup\$