-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPlayerSkeleton.java
394 lines (345 loc) · 10.1 KB
/
PlayerSkeleton.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
public class PlayerSkeleton {
public static final int COLS = 10;
public static final int ROWS = 21;
public static final int N_PIECES = 7;
//indices for legalMoves
public static final int ORIENT = 0;
public static final int SLOT = 1;
//completeLines, aggregateHeight, bumpiness, holes, wellSum
public double[] heuristicWeights;
//possible orientations for a given piece type
protected static int[] pOrients = {1,2,4,4,4,2,2};
//the next several arrays define the piece vocabulary in detail
//width of the pieces [piece ID][orientation]
protected static int[][] pWidth = {
{2},
{1,4},
{2,3,2,3},
{2,3,2,3},
{2,3,2,3},
{3,2},
{3,2}
};
//all legal moves - first index is piece type - then a list of 2-length arrays
protected static int[][][] allLegalMoves = new int[N_PIECES][][];
//initialize legalMoves
{
//for each piece type
for(int i = 0; i < N_PIECES; i++) {
//figure number of legal moves
int n = 0;
for(int j = 0; j < pOrients[i]; j++) {
//number of locations in this orientation
n += COLS+1-pWidth[i][j];
}
//allocate space
allLegalMoves[i] = new int[n][2];
//for each orientation
n = 0;
for(int j = 0; j < pOrients[i]; j++) {
//for each slot
for(int k = 0; k < COLS+1-pWidth[i][j];k++) {
allLegalMoves[i][n][ORIENT] = j;
allLegalMoves[i][n][SLOT] = k;
n++;
}
}
}
}
public PlayerSkeleton(double[] hW){
heuristicWeights = hW;
}
//implement this function to have a working system
public int pickMove(State s, int[][] legalMoves) {
int bestMove;
bestMove = depthTwoSearch(new Node(heuristicWeights, s.getField(), s.getTop(), 0, false), legalMoves, s.getNextPiece());
return bestMove;
}
/**
* Assigns a score to each legal move which is the average of optimal scores for every possible piece playable
* after the legal move is played.
* Optimal score for each piece is obtained by exhaustively searching every orient and position.
* Returns the move corresponding to the best average score among legal moves.
* @author laichengyu
*
* @param s state to apply depth-2 search on
* @param legalMoves of the nextPiece
* @param nextPiece integer representing the next piece
* @return best move to be played at depth-1
*/
public int depthTwoSearch(Node s, int[][] legalMoves, int nextPiece) {
double bestAvg = Integer.MIN_VALUE;
int bestDepthOneMove = 0;
double[] averages = new double[legalMoves.length];
for(int i = 0; i < legalMoves.length; i++) {
Node depthOneNode = s.simulateMove(legalMoves[i], nextPiece);
double[] optimalScores = new double[N_PIECES];
for(int j = 0; j < N_PIECES; j++) {
double bestScore = Integer.MIN_VALUE;
for(int k = 0; k < allLegalMoves[j].length; k++) {
Node depthTwoNode = depthOneNode.simulateMove(allLegalMoves[j][k], j);
double newScore = depthTwoNode.getScore();
if (newScore > bestScore) {
bestScore = newScore;
}
}
optimalScores[j] = bestScore;
}
double avg = 0;
for(int l = 0; l < N_PIECES; l++) {
avg += optimalScores[l];
}
avg /= N_PIECES;
averages[i] = avg;
}
for(int i = 0; i < averages.length; i++) {
if(averages[i] > bestAvg) {
bestAvg = averages[i];
bestDepthOneMove = i;
}
}
return bestDepthOneMove;
}
public static void main(String[] args) {
State s = new State();
// new TFrame(s);
PlayerSkeleton p = new PlayerSkeleton(new double[]{0.1636736030816534, -0.11117594223369093, -0.20390418721234355, -0.9501423421384158, -0.12846584618379997});
while(!s.hasLost()) {
s.makeMove(p.pickMove(s,s.legalMoves()));
// s.draw();
// s.drawNext(0,0);
// try {
// Thread.sleep(300);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
System.out.println("You have completed "+s.getRowsCleared()+" rows.");
}
// Plays the game and returns the number of rows cleared
public int playGame() {
State s = new State();
while(!s.hasLost()) {
s.makeMove(pickMove(s, s.legalMoves()));
}
return s.getRowsCleared();
}
}
class Node {
public static final int COLS = 10;
public static final int ROWS = 21;
//indices for legalMoves
public static final int ORIENT = 0;
public static final int SLOT = 1;
private boolean gameEnded;
private int[][] originalField;
private int[] originalTop;
private double score;
//completeLines, aggregateHeight, bumpiness, holes
public double[] heuristicWeights;
//the next several arrays define the piece vocabulary in detail
//width of the pieces [piece ID][orientation]
protected static int[][] pWidth = {
{2},
{1,4},
{2,3,2,3},
{2,3,2,3},
{2,3,2,3},
{3,2},
{3,2}
};
//height of the pieces [piece ID][orientation]
private static int[][] pHeight = {
{2},
{4,1},
{3,2,3,2},
{3,2,3,2},
{3,2,3,2},
{2,3},
{2,3}
};
private static int[][][] pBottom = {
{{0,0}},
{{0},{0,0,0,0}},
{{0,0},{0,1,1},{2,0},{0,0,0}},
{{0,0},{0,0,0},{0,2},{1,1,0}},
{{0,1},{1,0,1},{1,0},{0,0,0}},
{{0,0,1},{1,0}},
{{1,0,0},{0,1}}
};
private static int[][][] pTop = {
{{2,2}},
{{4},{1,1,1,1}},
{{3,1},{2,2,2},{3,3},{1,1,2}},
{{1,3},{2,1,1},{3,3},{2,2,2}},
{{3,2},{2,2,2},{2,3},{1,2,1}},
{{1,2,2},{3,2}},
{{2,2,1},{2,3}}
};
public Node(double[] hW, int[][] originalField, int[] originalTop, int rowsCleared, boolean gameEnded) {
this.heuristicWeights = hW;
this.originalField = originalField;
this.originalTop = originalTop;
this.gameEnded = gameEnded;
calculateScore(rowsCleared);
}
// Similar logic to makeMove() in State, simulates a single move based on an original state
// @param s - original state
// @param move - move to simulate
// @return field - a duplicated field with the new outcome based on the move
public Node simulateMove(int[] move, int nextPiece) {
int turn = 1;
int orient = move[ORIENT];
int slot = move[SLOT];
boolean hasGameEnded = gameEnded;
// Copy into a new field and top array
// Otherwise it will refer to the same field, top arrays for each simulation
int[][] field = new int[ROWS][COLS];
int[] top = new int[COLS];
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
field[i][j] = originalField[i][j];
}
}
for (int i = 0; i < COLS; i++) {
top[i] = originalTop[i];
}
//height if the first column makes contact
int height = top[slot]-pBottom[nextPiece][orient][0];
//for each column beyond the first in the piece
for(int c = 1; c < pWidth[nextPiece][orient];c++) {
height = Math.max(height,top[slot+c]-pBottom[nextPiece][orient][c]);
}
//check if game ended
if(height+pHeight[nextPiece][orient] >= ROWS) {
hasGameEnded = true;
return new Node(heuristicWeights, field, top, 0, hasGameEnded);
}
//for each column in the piece - fill in the appropriate blocks
for(int i = 0; i < pWidth[nextPiece][orient]; i++) {
//from bottom to top of brick
for(int h = height+pBottom[nextPiece][orient][i]; h < height+pTop[nextPiece][orient][i]; h++) {
field[h][i+slot] = turn;
}
}
//adjust top
for(int c = 0; c < pWidth[nextPiece][orient]; c++) {
top[slot+c]=height+pTop[nextPiece][orient][c];
}
int linesCompleted = 0;
//check for full rows - starting at the top
for(int r = height+pHeight[nextPiece][orient]-1; r >= height; r--) {
//check all columns in the row
boolean full = true;
for(int c = 0; c < COLS; c++) {
if(field[r][c] == 0) {
full = false;
break;
}
}
//if the row was full - remove it and slide above stuff down
if(full) {
linesCompleted++;
//for each column
for(int c = 0; c < COLS; c++) {
//slide down all bricks
for(int i = r; i < top[c]; i++) {
field[i][c] = field[i+1][c];
}
//lower the top
top[c]--;
while(top[c]>=1 && field[top[c]-1][c]==0) top[c]--;
}
}
}
return new Node(heuristicWeights, field, top, linesCompleted, hasGameEnded);
}
//Must call simulateMove before calling this method
public void calculateScore(int rowsCleared) {
int bumpiness = bumpinessHeuristic(originalTop);
int completeLines = rowsCleared;
int aggregateHeight = aggregateHeightHeuristic(originalTop);
int holes = holesHeuristic(originalField);
int wellSum = wellSumHeuristic(originalTop);
if (!gameEnded) {
score = heuristicWeights[0] * completeLines + heuristicWeights[1] * aggregateHeight + heuristicWeights[2] * bumpiness + heuristicWeights[3] * holes + heuristicWeights[4] * wellSum;
} else {
score = Integer.MIN_VALUE;
}
}
public int aggregateHeightHeuristic(int[] top) {
int aggregateHeight = 0;
for(int i=0; i<COLS; i++) {
aggregateHeight += top[i];
}
return aggregateHeight;
}
public int holesHeuristic(int[][] field) {
int numHoles = 0;
for (int j=0; j<field[j].length; j++) {
int startIdx = 0;
int endIdx = 0;
for(int i=0; i<field.length; i++) {
if(field[i][j] != 0) {
numHoles += (endIdx - startIdx);
startIdx = i+1;
endIdx = i+1;
} else {
endIdx++;
}
}
}
return numHoles;
}
public int bumpinessHeuristic(int[] top){
int bumpiness = 0;
for(int i=0; i<COLS - 1; i++){
//System.out.print(depths[i] + " ");
bumpiness += Math.abs(top[i] - top[i+1]);
}
return bumpiness;
}
// sum of all well heights
public int wellSumHeuristic(int[] top) {
int wellSum = 0;
for(int j = 0; j < COLS; j++) {
if (j == 0) {
if (top[j] < top[j+1]) {
int wellHeight = top[j+1] - top[j];
wellSum += wellHeight * (wellHeight+1) / 2;
}
} else if (j == COLS-1) {
if (top[j] < top[j-1]) {
int wellHeight = top[j-1] - top[j];
wellSum += wellHeight * (wellHeight+1) / 2;
}
} else if (top[j] < top[j-1] && top[j] < top[j+1]) {
int wellHeight = Math.min(top[j-1], top[j+1]) - top[j];
wellSum += wellHeight * (wellHeight+1) / 2;
}
}
return wellSum;
}
//unused
public int blockadeHeuristic(int[][] field) {
int numBlockades = 0;
for (int j = 0; j < COLS; j++) {
boolean countingBlockades = false;
for (int i = 0; i < ROWS; i++) {
if (countingBlockades) {
if (field[i][j] != 0) {
numBlockades++;
}
} else {
if (field[i][j] == 0) {
countingBlockades = true;
}
}
}
}
return numBlockades;
}
public double getScore() {
return score;
}
}