I have created a Tetris AI which can play Tetris on its own given a set of rules. It places the next piece in every possible way on the board, and calculates which position that gives the fewest "holes" on the playfield.
The playfield is represented by an int[]
array, where every bit represents a position in a row, and every int
represents a row.
A playfield can look like this:
(0000000000000000000000)1111111111
(0000000000000000000000)1111111111
(0000000000000000000000)1111111111
(0000000000000000000000)1111111111
(0000000000000000000000)0111111111
(0000000000000000000000)0111111111
(0000000000000000000000)0111001111
(0000000000000000000000)0111100111
the zeroes in parentheses are not part of the playfield, and are not used. 1 represents an empty square, 0 represents a filled one. So the example above would be a long piece on the left, and a squiggly piece in the middle.
However, to determine if this playfield is "desirable", I calculate the number of holes on the grid. A hole is defined as follows:
- An empty square under the topmost filled square in a column.
- An empty square in a column next to a column where we have found a filled square, given that the empty square in question is under the topmost filled square in the other column.
I guess it's hard to explain, but I've marked all "holes" here:
(0000000000000000000000)1111111111
(0000000000000000000000)1111111111
(0000000000000000000000)1111111111
(0000000000000000000000)1111111111
(0000000000000000000000)0X11111111
(0000000000000000000000)0X11111111
(0000000000000000000000)0X1X00X111
(0000000000000000000000)0X11D00X11
Every hole is marked with an "X", except for one in the bottom row, since that one is counted twice, because it is under a filled square, and next to a square that is under a filled square.
Right now this is the optimal solution that I've found. It is a combination of JS1's and Brythan's solutions:
private static int EMPTY_ROW = (1<<xsize)-1;
public int calcHolesConverted(int[] grid) {
//int gridMask = (1 << xsize) - 1;
int underMask = 0;
int lneighborMask = 0;
int rneighborMask = 0;
int foundHoles = 0;
int minY = 0;
while ( minY < ysize && grid[minY] == EMPTY_ROW ) {
minY++;
}
for (int y = minY; y < ysize; y++) {
int line = grid[y];
int filled = ~line & EMPTY_ROW;
underMask |= filled;
lneighborMask |= (filled << 1);
rneighborMask |= (filled >> 1);
foundHoles += setOnes[underMask & line];
foundHoles += setOnes[lneighborMask & line];
foundHoles += setOnes[rneighborMask & line];
}
return foundHoles;
}
This results in over 10000 Tetris pieces per second, compared to the 4800 that I could accomplish before I asked the question.