-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSearch.java
333 lines (299 loc) · 13.2 KB
/
Search.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
import java.util.Arrays;
public class Search {
//Initialize the input
public static int horizontalGridSize = setHorizontalGridSize();
public static int verticalGridSize = setVerticalGridSize();
public static char[] input = setInput();
public static Piece[] pieces = setPieces(); // An array to store all the valid pentominos. See setPieces method
//Static UI class to display the board
public static UI ui = new UI(horizontalGridSize, verticalGridSize, 50);
/**
* Read the input from users
*
* @return
*/
public static int setHorizontalGridSize() {
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.println("Please type in the column number of rectangle (integer):");
System.out.print("-->");
return s.nextInt();
}
public static int setVerticalGridSize() {
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.println("Please type in the row number of rectangle (integer):");
System.out.print("-->");
return s.nextInt();
}
/**
* Turn the input from string to char[]
*
* @return pentominos name
*/
public static char[] setInput() {
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.println("Please type in the valid types of Pentominos (characters separated with ','):");
System.out.print("-->");
String chars = s.nextLine();
char[] tempInput = new char[chars.length() / 2 + 1];
for (int i = 0; i < chars.length(); i += 2) {
tempInput[i / 2] = chars.toUpperCase().charAt(i);
}
return tempInput;
}
/**
* Turn every character in input[] into pentID, and create new piece with these pentID, to initialize piece[]
* So that the piece[] is exactly the valid pentominos that user input
*
* @return Piece array stores all usable pentominos
*/
public static Piece[] setPieces() {
Piece[] pieces = new Piece[input.length];
for (int i = 0; i < input.length; i++) {
int pentID = characterToID(input[i]);
pieces[i] = new Piece(pentID);
}
return pieces;
}
/**
* Get as input the character representation of a pentomino and translate it into its corresponding numerical value (ID)
*
* @param character a character representating a pentomino
* @return the corresponding ID (numerical value)
*/
private static int characterToID(char character) {
int pentID = -1;
if (character == 'I') {
pentID = 0;
} else if (character == 'X') {
pentID = 1;
} else if (character == 'Z') {
pentID = 2;
} else if (character == 'V') {
pentID = 3;
} else if (character == 'T') {
pentID = 4;
} else if (character == 'W') {
pentID = 5;
} else if (character == 'U') {
pentID = 6;
} else if (character == 'L') {
pentID = 7;
} else if (character == 'N') {
pentID = 8;
} else if (character == 'Y') {
pentID = 9;
} else if (character == 'F') {
pentID = 10;
} else if (character == 'P') {
pentID = 11;
}
return pentID;
}
/**
* Initialize the field, and call branchSearch method
*/
public static void search() {
//Quick check:
if ((horizontalGridSize * verticalGridSize) % 5 != 0 || (input.length * 5) < (horizontalGridSize * verticalGridSize)) {
System.out.println("No solution!");
System.exit(0);
}
// Initialize an empty board
int[][] field = new int[horizontalGridSize][verticalGridSize];
for (int i = 0; i < field.length; i++) {
// -1 in the state matrix corresponds to empty square
// Any positive number identifies the ID of the pentomino
Arrays.fill(field[i], -1);
}
// Start the DFS search
// branches: we need to cut meaningless branches
branchSearch(field);
System.out.println(Arrays.deepToString(field));
if (isFilled(field)) {
//display the field
ui.setState(field);
System.out.println("Solution found");
} else {
System.out.println("No Solution!");
System.exit(0);
}
}
/**
* Do the whole DFS search
* Piece by piece: pentominos first, then find empty grid
* Grid by grid: find empty grid, then pentomino
*
* @param field the field you want to fill
* @return the field with solution. But if there is no solution, it will return null.
*/
public static int[][] branchSearch(int[][] field) { // try to
if (isFilled(field))
return field;
for (int i = 0; i < field.length; i++) { // 2 for loops to find the empty block in field, to try to put the piece in
for (int j = 0; j < field[i].length; j++) {
if (field[i][j] == -1) {
for (int k = 0; k < pieces.length; k++) { // Pick a pentomino from the valid pentominos which are defined by user
if (pieces[k].usedState) continue; // Only use the pentomino which has not been used
for (int p = 0; p < pieces[k].possiblePent.length; p++) { // try every rotated/flipped piece, see if it can be put in specific position
pieces[k].setCurrentPent(p); // set current pentomino (its exactly the process of rotate or flip)
int[][] newField = putPiece(pieces[k], field, i, j);
// try if the piece can be put in field. if newField is null, means the piece can not be put.
if (newField != null) { // if the piece is put in successfully, then go on
pieces[k].setUsed(true, i, j); // set the used state of the piece to 'true', and record its position
if (obviousBlockExists(field)) { // pruning
removePiece(pieces[k], field);
continue;
}
branchSearch(newField); // recursion
if (isFilled(field))
return field;
else {
removePiece(pieces[k], field);
}
// if it met that all of its branches are failed, it will remove the current piece, and continue to do the loop
}
}
pieces[k].setCurrentPent(0);
}
}
}
}
return field;
}
/**
* It will put the current pentomino of the piece in to specific position.
* It will check if you can put the piece at first. If not, it will immediately return 'null' and will not change the field.
* If the piece pass the checks, it will been put on the field. And what is return is the field with this piece put in
*
* @param p the piece you wan to put
* @param field the board
* @param row the row number of the position you want to put
* @param col the column number of the position you want to put
* @return return the field with the piece put in. But if the piece can not be put in this position, it will return 'null'
*/
public static int[][] putPiece(Piece p, int[][] field, int row, int col) {
if (isFilled(field))
return field;
if (!checkValid(p, field, row, col, -1))
return null;
field[row][col] = p.pentID;
for (int i = 1; i < 9; i += 2) {
field[row + p.currentPent[i]][col + p.currentPent[i + 1]] = p.pentID;
}
ui.setState(field);
return field;
}
/**
* It will remove the current pentomino of the piece from field
*
* @param p the piece you wan to remove
* @param field the board
*/
public static void removePiece(Piece p, int[][] field) {
if (!checkValid(p, field, p.posRow, p.posCol, p.pentID))
return;
field[p.posRow][p.posCol] = -1;
for (int i = 1; i < 9; i += 2) {
field[p.posRow + p.currentPent[i]][p.posCol + p.currentPent[i + 1]] = -1;
}
p.setUsed(false, -1, -1);
}
/**
* Check if the field is filled.
*
* @param field the field
* @return if the field is filled, return true.
*/
public static boolean isFilled(int[][] field) {
for (int i = 0; i < field.length; i++) {
for (int j = 0; j < field[i].length; j++) {
if (field[i][j] == -1) {
return false;
}
}
}
return true;
}
/**
* Check if you put the piece in the field, it will be out of field or not
*
* @param p the piece you want to check
* @param field the field
* @param row the position of where you want to put the piece
* @param col the position of where you want to put the piece
* @return if it is in the range, return true
*/
public static boolean checkSide(Piece p, int[][] field, int row, int col) {
if (row < 0 || col < 0 || row > field.length - 1 || col > field[0].length - 1) // Check if the top-left block is outside
return false;
for (int i = 1; i < 9; i += 2) {
if (row + p.currentPent[i] > field.length - 1 || col + p.currentPent[i + 1] > field[0].length - 1 || row + p.currentPent[i] < 0 || col + p.currentPent[i + 1] < 0) {
return false;
}
}
return true;
}
/**
* Check if the specific area in field is all filled with checkedValue.
* If checkedValue = -1, this method can be used for check if the area is empty. If yes, you can put the piece in.
* If checkedValue = number, this method can be used for check if the area is filled with this number(such as pentID). If yes, you can remove the piece.
*
* @param p the piece which should cover in the field
* @param field current field
* @param row the row number which the piece will be put, which is equal to the row of top-left block of the piece
* @param col the col number which the piece will be put, which is equal to the col of top-left block of the piece
* @param checkedValue If checkedValue = -1, this method can be used for check if the area which will be filled is empty.
* @return if the area is filled with checkedValue
*/
public static boolean checkValid(Piece p, int[][] field, int row, int col, int checkedValue) {
if (!checkSide(p, field, row, col))
return false;
if (field[row][col] != checkedValue)// Check if the top-left block is empty/equal to checkedValue
return false;
for (int i = 1; i < 9; i += 2) {
if (field[row + p.currentPent[i]][col + p.currentPent[i + 1]] != checkedValue)
return false;
}
return true;
}
/**
* Test if there is obvious block exits, which means there are less than 5 grids are surrounded by pentominos.
*
* @param field the current field.
* @return if there is obvious block
*/
public static boolean obviousBlockExists(int[][] field) {
boolean[][] tempTest = new boolean[field.length][field[0].length];
for (int i = 0; i < field.length; i++) {
for (int j = 0; j < field[0].length; j++) {
int count = countEmptyBlock(i, j, field, tempTest);
if (count < 5 && count > 0) {
return true;
}
}
}
return false;
}
/**
* To count how many grids are surrounded by pentominos.
*
* @param row the current grid's row position
* @param col the current grid's column position
* @param field current field
* @param tempTest the boolean array to record if this grid is been test
* @return the number of grids which are surrounded by pentominos
*/
private static int countEmptyBlock(int row, int col, int[][] field, boolean[][] tempTest) {
if (row < 0 || row >= field.length || col < 0 || col >= field[0].length || tempTest[row][col] || field[row][col] != -1) {
return 0;
}
tempTest[row][col] = true;
return countEmptyBlock(row, col + 1, field, tempTest) + countEmptyBlock(row + 1, col, field, tempTest) + 1;
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
search();
long endTime = System.currentTimeMillis();
System.out.println("Running time: " + ((endTime - startTime) / 1000) + "s and " + (endTime - startTime) % 1000 + " ms.");
}
}