├── .gitignore ├── README.md ├── outputs.txt ├── resource ├── Level1.txt ├── Level2.txt ├── Level3.txt └── Level4.txt └── src ├── AStarSolver.java ├── AbstractSolver.java ├── BFSSolver.java ├── BoardState.java ├── BoxGoalHeuristic.java ├── DFSSolver.java ├── Direction.java ├── GreedyBFSSolver.java ├── Heuristic.java ├── ManhattanHeuristic.java ├── NoSolutionException.java ├── SokobanMain.java ├── SokobanSolver.java └── UniformCostSolver.java /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .settings/ 3 | .classpath 4 | .project 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sokoban Solver 2 | ============== 3 | 4 | Forrest Knight 5 | 6 | CS 480 - Artificial Intelligence - Fall 2017 7 | 8 | Usage 9 | ----- 10 | 11 | Compile files 12 | 13 | cd src 14 | javac *.java 15 | 16 | SokobanMain contains the main method. The program takes command-line arguments with 17 | the following options. 18 | 19 | java SokobanMain [-option] [Sokoban input file] 20 | 21 | Options 22 | -b Breadth-first search 23 | -d Depth-first search 24 | -u Uniform-cost search (move = 1, push = 2) 25 | -gb Greedy best-first search with number of boxes on goal heuristic 26 | -gm Greedy best-first search with Manhattan distances heuristic 27 | -ab AStar with number of boxes on goal heuristic 28 | -am AStar with goals and boxes Manhattan distances heuristic 29 | 30 | NO command-line input validation is done. It's a very simple command-line as I 31 | was focused more on the implementation of the searches rather than the prettiness 32 | of the UI. 33 | 34 | Input 35 | ----- 36 | 37 | The Sokoban files must be in the following format. 38 | 39 | [Number of columns] 40 | [Number of rows] 41 | [Rest of the puzzle] 42 | 43 | With the following state mappings (note that when the player or box is on the 44 | goal, the mapping changes). 45 | 46 | 0 (hash) Wall (Obstacle) 47 | S (period) Empty goal (Storage) 48 | R (at) Player on floor (Robot) 49 | \+ (plus) Player on goal 50 | B (dollar) Box on floor (Block) 51 | \* (asterisk) Box on goal 52 | 53 | Output 54 | ------ 55 | 56 | The output is in the following format. 57 | 58 | 1. String representation of initial state 59 | 2. String representation of the final state 60 | 3. Move solution 61 | 4. Number of nodes explored 62 | 5. Number of previously seen nodes 63 | 6. Number of nodes at the fringe 64 | 7. Number of explored nodes 65 | 8. Time elapsed in milliseconds 66 | -------------------------------------------------------------------------------- /outputs.txt: -------------------------------------------------------------------------------- 1 | Sample Output 2 | ------------- 3 | 4 | 000 Level1 5 | 6 | 0000 7 | 0 S0 8 | 0 000 9 | 0*R 0 10 | 0 B 0 11 | 0 0 12 | 000000 13 | 14 | BFS 15 | 16 | 0000 17 | 0 *0 18 | 0 R000 19 | 0* 0 20 | 0 0 21 | 0 0 22 | 000000 23 | 24 | Solution: r, r, d, l, d, l, u, u, u 25 | Nodes explored: 633 26 | Previously seen: 151 27 | Fringe: 196 28 | Explored set: 286 29 | Milliseconds elapsed: 268 30 | 31 | DFS 32 | 0000 33 | 0 *0 34 | 0 R000 35 | 0* 0 36 | 0 0 37 | 0 0 38 | 000000 39 | 40 | Solution: d, l, d, r, r, r, u, l, d, l, l, u, u, d, d, r, r, r, u, l, u, l, u, u, l, d, d, r, r, d, d, l, u, d, l, u, d, r, r, r, u, l, l, u, u 41 | Nodes explored: 808 42 | Previously seen: 204 43 | Fringe: 34 44 | Explored set: 570 45 | Milliseconds elapsed: 61 46 | 47 | UFS 48 | 49 | 0000 50 | 0 *0 51 | 0 R000 52 | 0* 0 53 | 0 0 54 | 0 0 55 | 000000 56 | 57 | Solution: r, r, d, l, d, l, u, u, u 58 | Nodes explored: 980 59 | Previously seen: 350 60 | Fringe: 219 61 | Explored set: 411 62 | Milliseconds elapsed: 86 63 | 64 | Greedy BFS Box-goal 65 | 66 | 0000 67 | 0 *0 68 | 0 R000 69 | 0* 0 70 | 0 0 71 | 0 0 72 | 000000 73 | 74 | Solution: d, l, d, r, r, u, l, l, d, r, r, r, u, u, l, d, l, u, u 75 | Nodes explored: 135 76 | Previously seen: 6 77 | Fringe: 43 78 | Explored set: 86 79 | Milliseconds elapsed: 14 80 | 81 | Greedy BFS Manhattan 82 | 83 | 0000 84 | 0 *0 85 | 0 R000 86 | 0* 0 87 | 0 0 88 | 0 0 89 | 000000 90 | 91 | Solution: d, l, d, r, r, u, r, u, l, d, l, u, u 92 | Nodes explored: 50 93 | Previously seen: 1 94 | Fringe: 25 95 | Explored set: 24 96 | Milliseconds elapsed: 7 97 | 98 | A\* Box-goal 99 | 100 | 0000 101 | 0 *0 102 | 0 R000 103 | 0* 0 104 | 0 0 105 | 0 0 106 | 000000 107 | 108 | Solution: d, l, d, r, r, u, l, l, d, r, r, r, u, u, l, d, l, u, u 109 | Nodes explored: 136 110 | Previously seen: 6 111 | Fringe: 44 112 | Explored set: 86 113 | Milliseconds elapsed: 20 114 | 115 | A\* Manhattan 116 | 117 | 0000 118 | 0 *0 119 | 0 R000 120 | 0* 0 121 | 0 0 122 | 0 0 123 | 000000 124 | 125 | Solution: d, l, d, r, r, u, r, u, l, d, l, u, u 126 | Nodes explored: 51 127 | Previously seen: 1 128 | Fringe: 26 129 | Explored set: 24 130 | Milliseconds elapsed: 6 131 | 132 | 000 Level2 133 | 134 | 0000000 135 | 0 0 136 | 0RBS0 0 137 | 0*B S0 138 | 0 BB 0 139 | 0 S S 0 140 | 0000000 141 | 142 | BFS 143 | 144 | 0000000 145 | 0 0 146 | 0 *0 0 147 | 0* *0 148 | 0 R 0 149 | 0 * * 0 150 | 0000000 151 | 152 | Solution: d, r, r, r, d, d, l, l, l, u, u, r, d, r, u, u, u, l, l, d, r, d, 153 | r, r, d 154 | Nodes explored: 1018557 155 | Previously seen: 625397 156 | Fringe: 233206 157 | Explored set: 159954 158 | Milliseconds elapsed: 10516 159 | 160 | DFS 161 | 162 | 0000000 163 | 0 0 164 | 0 R*0 0 165 | 0* *0 166 | 0 0 167 | 0 * * 0 168 | 0000000 169 | 170 | Solution: d, r, l, u, r, l, d, r, r, l, l, u, r, u, r, d, l, l, u, r, r, r, r, d, d, d, l, d, l, l, l, u, d, r, r, r, r, u, u, u, u, l, l, l, l, d, r, d, d, l, d, r, l, u, r, r, l, l, d, r, r, l, l, u, r, r, u, l, d, l, u, d, d, r, r, u, l, u, u, u, l, d, d, r, u, l, u, r, r, d, l, l, u, r, r, r, r, d, d, d, l, l, d, l, l, u, d, r, r, u, r, r, u, u, u, l, l, l, l, d, r, d, d, l, u, d, r, r, r, r, u, u, u, l, l, l, l, d, r, r, u, r, r, d, d, d, l, l, u, l, d, l, u, d, r, r, r, r, u, l, l, d, l, l, u, r, l, d, r, r, r, r, u, u, u, l, l, l, l, d, r, d, d, r, r, r, u, l, l, d, l, u, d, l, u, d, r, r, r, r, u, u, u, l, l, l, l, d, u, r, d, l, u, r, r, d, l, l, u, r, r, r, r, d, d, l, d, l, l, l, u, d, r, u, l, d, r, r, r, r, u, u, u, l, l, l, l, d, u, r, r, r, r, d, d, l, d, l, l, u, r, l, d, l, u, d, r, r, r, r, u, u, u, l, l, l, l, d, u, r, d, l, u, r, r, d, d, d, l, l, u, d, r, r, r, r, u, u, u, l, l, l, d, r, d, r, l, d, l, l, u, r, l, d, r, r, r, r, u, l, l, d, l, l, u, r, l, d, r, r, u, l, u, u, l, d, r, u, r, d, l, l, u, r, r, r, r, d, u, l, l, l, l, d, r, d, d, r, r, r, u, l, l, d, l, u, d, l, u, d, r, r, r, r, u, l, l, u, u, l, l, d, u, r, d, l, u, r, r, r, r, d, u, l, l, l, l, d, r, r, d, d, l, u, d, l, u, d, r, r, r, u, l, u, u, l, l, d, r 171 | Nodes explored: 400513 172 | Previously seen: 97934 173 | Fringe: 239 174 | Explored set: 302340 175 | Milliseconds elapsed: 5658 176 | 177 | UFS 178 | 179 | 0000000 180 | 0 0 181 | 0 *0 0 182 | 0* *0 183 | 0 R 0 184 | 0 * * 0 185 | 0000000 186 | 187 | Solution: r, l, d, r, r, r, d, d, l, l, l, u, d, r, r, r, u, u, l, l, d, r, u, r, d 188 | Nodes explored: 798010 189 | Previously seen: 481585 190 | Fringe: 167737 191 | Explored set: 148688 192 | Milliseconds elapsed: 14218 193 | 194 | Greedy BFS Box-goal 195 | 196 | 0000000 197 | 0 0 198 | 0 *0 0 199 | 0* *0 200 | 0 R 0 201 | 0 * * 0 202 | 0000000 203 | 204 | Solution: r, u, r, r, r, d, d, l, l, d, l, u, d, l, l, u, r, u, d, l, d, r, r, r, r, u, u, u, l, d, r, d, l, u, l, l, d, r, r, u, r, r, u, u, u, l, l, l, l, d, d, r, r, r, l, l, l, u, u, r, d, d, r, r, d, l, d, l, l, u, r, u, r, r, d, l, u, l, d 205 | Nodes explored: 80691 206 | Previously seen: 26760 207 | Fringe: 7412 208 | Explored set: 46519 209 | Milliseconds elapsed: 969 210 | 211 | Greedy BFS Manhattan 212 | 213 | 0000000 214 | 0 0 215 | 0 *0 0 216 | 0* *0 217 | 0 R 0 218 | 0 * * 0 219 | 0000000 220 | 221 | Solution: r, l, d, r, r, r, d, d, l, l, l, u, d, r, r, r, u, u, l, l, d, r, u, r, d 222 | Nodes explored: 212834 223 | Previously seen: 68497 224 | Fringe: 21098 225 | Explored set: 123239 226 | Milliseconds elapsed: 3288 227 | 228 | A\* Box-goal 229 | 230 | 0000000 231 | 0 0 232 | 0 *0 0 233 | 0* R*0 234 | 0 0 235 | 0 * * 0 236 | 0000000 237 | 238 | Solution: r, u, r, r, r, d, d, l, l, d, l, u, r, d, l, l, l, u, r, u, r, d, l, l, d, r, r, u, r, r, u, u, u, l, l, l, d, d, d, r, r, r 239 | Nodes explored: 46657 240 | Previously seen: 16714 241 | Fringe: 3595 242 | Explored set: 26348 243 | Milliseconds elapsed: 602 244 | 245 | A\* Manhattan 246 | 247 | 0000000 248 | 0 0 249 | 0 *0 0 250 | 0* *0 251 | 0 R 0 252 | 0 * * 0 253 | 0000000 254 | 255 | Solution: r, l, d, r, r, r, d, d, l, l, l, u, d, r, r, r, u, u, l, l, d, r, u, r, d 256 | Nodes explored: 173403 257 | Previously seen: 55158 258 | Fringe: 15790 259 | Explored set: 102455 260 | Milliseconds elapsed: 2604 261 | 262 | 000 Level3 263 | 264 | 000000 265 | 0 SSR0 266 | 0 BB 0 267 | 00 000 268 | 0 0 269 | 0 0 270 | 0000 0 271 | 0 00 272 | 0 0 0 273 | 0 0 0 274 | 000 0 275 | 00000 276 | 277 | BFS 278 | 279 | 000000 280 | 0 ** 0 281 | 0 R 0 282 | 00 000 283 | 0 0 284 | 0 0 285 | 0000 0 286 | 0 00 287 | 0 0 0 288 | 0 0 0 289 | 000 0 290 | 00000 291 | 292 | Solution: l, l, d, d, d, d, d, d, l, d, d, d, r, r, u, u, l, u, u, u, u, u, u, u, r, r, d, l, u, l, d, d, d, d, d, d, l, l, l, d, d, r, r, u, d, l, l, u, u, r, r, d, d, d, r, r, u, u, l, u, u, u, u, u, u, l, u, r, d, d, d, d, d, d, l, l, l, d, d, r, r, u, d, l, l, u, u, r, r, d, r, u, u, u, u, u, u 293 | Nodes explored: 7569 294 | Previously seen: 3271 295 | Fringe: 143 296 | Explored set: 4155 297 | Milliseconds elapsed: 162 298 | 299 | DFS 300 | 301 | 000000 302 | 0 ** 0 303 | 0 R 0 304 | 00 000 305 | 0 0 306 | 0 0 307 | 0000 0 308 | 0 00 309 | 0 0 0 310 | 0 0 0 311 | 000 0 312 | 00000 313 | 314 | Solution: l, l, d, d, d, d, d, d, l, l, l, d, d, r, r, d, r, r, u, u, l, r, d, d, l, l, u, l, l, u, u, r, r, r, u, u, u, u, u, l, u, r, r, r, d, l, r, u, l, l, d, d, d, d, d, d, l, l, l, d, d, r, r, u, d, l, l, u, u, r, r, l, l, d, d, r, r, d, r, r, u, u, l, r, d, d, l, l, u, l, l, u, u, r, r, d, r, u, l, d, r, r, d, d, l, l, u, l, l, u, u, r, r, r, u, d, l, l, l, d, d, r, r, u, d, d, r, r, u, u, l, u, l, d, d, d, r, r, u, u, l, u, u, u, d, d, l, d, d, l, l, u, u, r, l, d, d, r, r, d, r, r, u, u, l, u, u, u, u, d, d, d, l, d, d, d, r, r, u, u, l, u, u, u, u, u, d, d, d, d, l, d, d, l, l, u, u, r, l, d, d, r, r, d, r, r, u, u, l, u, u, u, u, u, u, l, u, r, l, d, r, d, d, d, d, d, l, d, d, l, l, u, u, r, r, l, l, d, d, r, r, d, r, r, u, u, l, u, u, u, u, u, u 315 | Nodes explored: 2186 316 | Previously seen: 255 317 | Fringe: 66 318 | Explored set: 1865 319 | Milliseconds elapsed: 110 320 | 321 | UFS 322 | 323 | 000000 324 | 0 ** 0 325 | 0 R 0 326 | 00 000 327 | 0 0 328 | 0 0 329 | 0000 0 330 | 0 00 331 | 0 0 0 332 | 0 0 0 333 | 000 0 334 | 00000 335 | 336 | Solution: l, l, d, d, d, d, d, d, l, d, d, d, r, r, u, u, l, u, u, u, u, u, u, u, r, r, d, l, u, l, d, d, d, d, d, d, l, l, l, d, d, r, r, u, d, l, l, u, u, r, r, d, d, d, r, r, u, u, l, u, u, u, u, u, u, l, u, r, d, d, d, d, d, d, d, r, d, d, l, l, u, u, d, l, l, u, u, r, r, d, r, u, u, u, u, u, u 337 | Nodes explored: 7852 338 | Previously seen: 3544 339 | Fringe: 89 340 | Explored set: 4219 341 | Milliseconds elapsed: 202 342 | 343 | Greedy BFS Box-goal 344 | 345 | 000000 346 | 0 ** 0 347 | 0 R 0 348 | 00 000 349 | 0 0 350 | 0 0 351 | 0000 0 352 | 0 00 353 | 0 0 0 354 | 0 0 0 355 | 000 0 356 | 00000 357 | 358 | Solution: l, l, d, d, d, d, d, d, l, l, l, d, d, r, r, d, r, r, u, u, l, r, d, d, l, l, u, l, l, u, u, r, r, r, u, u, u, u, u, l, u, r, r, r, d, l, r, u, l, l, d, d, d, d, d, d, l, l, l, d, d, r, r, u, d, l, l, u, u, r, r, l, l, d, d, r, r, d, r, r, u, u, l, r, d, d, l, l, u, u, d, d, r, r, u, u, l, u, l, d, r, r, u, d, l, d, d, l, l, u, u, r, l, d, d, r, r, d, r, r, u, u, l, u, u, u, d, d, l, d, d, d, r, r, u, u, l, u, u, u, u, d, d, d, l, d, d, l, l, u, u, r, l, d, d, r, r, d, r, r, u, u, l, u, u, u, u, u, u, d, d, d, d, d, l, r, u, u, u, u, u, l, u, r, l, d, r, d, d, d, d, d, l, d, d, l, l, u, u, r, r, d, r, u, u, u, u, u, u 359 | Nodes explored: 2074 360 | Previously seen: 432 361 | Fringe: 134 362 | Explored set: 1508 363 | Milliseconds elapsed: 140 364 | 365 | Greedy BFS Manhattan 366 | 367 | 000000 368 | 0 ** 0 369 | 0 R 0 370 | 00 000 371 | 0 0 372 | 0 0 373 | 0000 0 374 | 0 00 375 | 0 0 0 376 | 0 0 0 377 | 000 0 378 | 00000 379 | 380 | Solution: l, l, d, d, d, d, d, d, l, d, d, d, r, r, u, u, l, r, d, d, l, l, u, l, l, u, u, r, r, r, u, u, u, u, u, l, u, u, r, r, d, l, u, l, d, d, d, d, d, d, l, l, l, d, d, r, r, u, d, l, l, u, u, r, r, d, d, d, r, r, u, u, l, u, u, u, u, u, u, l, u, r, d, d, d, d, d, d, l, l, l, d, d, r, u, u, d, l, l, u, u, r, r, d, r, u, u, u, u, u, u 381 | Nodes explored: 2823 382 | Previously seen: 630 383 | Fringe: 42 384 | Explored set: 2151 385 | Milliseconds elapsed: 214 386 | 387 | A\* Box-goal 388 | 389 | 000000 390 | 0 ** 0 391 | 0 R 0 392 | 00 000 393 | 0 0 394 | 0 0 395 | 0000 0 396 | 0 00 397 | 0 0 0 398 | 0 0 0 399 | 000 0 400 | 00000 401 | 402 | Solution: l, l, d, d, d, d, d, d, l, l, l, d, d, r, r, d, r, r, u, u, l, r, d, d, l, l, u, l, l, u, u, r, r, r, u, u, u, u, u, l, u, r, r, r, d, l, r, u, l, l, d, d, d, d, d, d, l, l, l, d, d, r, r, u, d, l, l, u, u, r, r, l, l, d, d, r, r, d, r, r, u, u, l, u, l, l, l, d, d, r, r, u, d, d, r, r, u, u, l, u, l, d, r, r, u, d, l, d, d, l, l, u, u, r, l, d, d, r, r, d, r, r, u, u, l, u, u, u, d, d, l, d, d, d, r, r, u, u, l, u, u, u, u, d, d, d, l, d, d, l, l, u, u, r, l, d, d, r, r, d, r, r, u, u, l, u, u, u, u, u, u, d, d, d, d, d, l, r, u, u, u, u, u, l, u, r, d, d, d, d, d, d, d, l, d, l, l, u, u, r, r, d, r, u, u, u, u, u, u 403 | Nodes explored: 2770 404 | Previously seen: 560 405 | Fringe: 103 406 | Explored set: 2107 407 | Milliseconds elapsed: 165 408 | 409 | A\* Manhattan 410 | 411 | 000000 412 | 0 ** 0 413 | 0 R 0 414 | 00 000 415 | 0 0 416 | 0 0 417 | 0000 0 418 | 0 00 419 | 0 0 0 420 | 0 0 0 421 | 000 0 422 | 00000 423 | 424 | Solution: l, l, d, d, d, d, d, d, l, d, d, d, r, r, u, u, l, r, d, d, l, l, u, l, l, u, u, r, r, u, u, u, u, u, u, u, r, r, d, l, u, l, d, d, d, d, d, d, l, l, l, d, d, r, r, u, d, l, l, u, u, r, r, d, d, d, r, r, u, u, l, u, u, u, u, u, u, l, u, r, d, d, d, d, d, d, l, l, l, d, d, r, u, u, d, l, l, u, u, r, r, d, r, u, u, u, u, u, u 425 | Nodes explored: 2797 426 | Previously seen: 598 427 | Fringe: 44 428 | Explored set: 2155 429 | Milliseconds elapsed: 177 430 | 431 | 000 Level4 432 | 433 | 0000000 434 | 0 R 0 435 | 0B* **0 436 | 0S 0B 0 437 | 0 S B 0 438 | 0 S 0 439 | 0000000 440 | 441 | BFS 442 | 443 | Out of Memory Exception 444 | 445 | DFS 446 | 447 | 0000000 448 | 0 0 449 | 0 * **0 450 | 0* 0R 0 451 | 0 * 0 452 | 0 * 0 453 | 0000000 454 | 455 | Solution: l, d, d, r, d, d, r, r, r, u, l, d, l, l, u, u, l, u, u, r, r, r, r, d, l, d, u, r, d, l, u, r, u, l, l, l, l, d, d, r, d, d, l, u, d, r, r, r, r, u, d, l, l, l, l, u, u, d, d, r, r, r, r, u, u, l, u, u, l, l, l, d, d, r, d, d, l, u, d, r, r, r, r, u, u, l, u, u, l, l, d, l, d, u, r, d, l, u, r, u, r, r, d, l, r, d, r, d, d, l, l, l, l, u, d, r, r, r, r, u, u, l, u, l, u, l, l, d, r, l, u, r, r, r, d, d, r, d, d, l, l, l, l, u, u, d, d, r, r, r, r, u, u, l, u, u, l, l, d, d, d, l, d, r, l, u, r, u, u, u, l, d, d, r, d, d, l, u, d, r, r, l, l, u, r, u, u, l, u, r, r, r, d, l, r, d, r, d, d, l, u, l, r, u 456 | Nodes explored: 1714975 457 | Previously seen: 353735 458 | Fringe: 101 459 | Explored set: 1361139 460 | Milliseconds elapsed: 45105 461 | 462 | UFS 463 | 464 | Greedy BFS Box-goal 465 | 466 | 0000000 467 | 0 0 468 | 0 * **0 469 | 0* 0 0 470 | 0 *R 0 471 | 0 * 0 472 | 0000000 473 | 474 | Solution: l, d, d, u, u, r, r, r, r, d, l, u, l, l, l, d, d, r, d, d, r, r, r, u, u, d, l, u, d, d, l, l, u, u, l, u, u, r, d, d, u, u, r, r, d, l, r, d, r, d, d, l, l, l, l, u, d, r, r, r, r, u, u, l, u, l, u, l, l, d, r, u, r, r, d, d, r, d, d, l, u, u, d, d, l, l, l, u, u, r, u, u, l, d, r, d, d, l, d, r, u, u, u, u, r, r, d, l, r, d, r, d, d, l, u, u, d, l 475 | Nodes explored: 1684551 476 | Previously seen: 530587 477 | Fringe: 105738 478 | Explored set: 1048226 479 | Milliseconds elapsed: 47175 480 | 481 | Greedy BFS Manhattan 482 | 483 | 0000000 484 | 0 0 485 | 0 * **0 486 | 0* 0 0 487 | 0 *R 0 488 | 0 * 0 489 | 0000000 490 | 491 | Solution: l, d, u, r, r, r, r, d, l, u, l, l, l, d, d, r, d, d, r, r, r, u, u, d, l, d, l, l, u, u, l, u, u, r, d, d, u, u, r, r, d, l, r, d, r, d, d, l, l, l, l, u, d, r, r, r, r, u, u, l, u, l, u, l, l, d, r, u, r, r, d, d, r, d, d, l, u, u, d, d, l, l, l, u, u, r, u, u, l, d, r, d, d, l, d, r, u, u, u, u, r, r, d, l, r, d, r, d, d, l, u, u, d, l 492 | Nodes explored: 530912 493 | Previously seen: 128970 494 | Fringe: 89015 495 | Explored set: 312927 496 | Milliseconds elapsed: 9735 497 | 498 | A\* Box-goal 499 | 500 | 0000000 501 | 0 0 502 | 0 * **0 503 | 0* 0 0 504 | 0 *R 0 505 | 0 * 0 506 | 0000000 507 | 508 | Solution: d, d, u, r, u, r, r, d, l, u, l, l, d, d, l, d, d, r, r, r, r, u, u, d, l, u, d, d, l, l, l, u, u, r, u, u, l, d, r, d, d, l, d, r, u, u, u, u, r, r, d, l, r, d, r, d, d, l, u, u, d, l 509 | Nodes explored: 1483319 510 | Previously seen: 437633 511 | Fringe: 115700 512 | Explored set: 929986 513 | Milliseconds elapsed: 40355 514 | 515 | A\* Manhattan 516 | 517 | 0000000 518 | 0 0 519 | 0 * **0 520 | 0* 0 0 521 | 0 *R 0 522 | 0 * 0 523 | 0000000 524 | 525 | Solution: l, d, d, r, d, r, d, r, r, u, l, d, l, l, u, u, l, u, u, r, r, r, r, d, l, u, l, l, l, d, d, r, d, d, r, r, r, u, u, d, l, d, l, l, u, u, l, u, u, r, d, d, u, u, r, r, d, l, r, d, r, d, d, l, l, l, l, u, d, r, r, r, r, u, u, l, u, u, l, l, l, d, r, u, r, r, d, d, r, d, d, l, u, u, d, d, l, l, l, u, u, r, u, u, l, d, r, d, d, l, d, r, u, u, u, u, r, r, d, l, r, d, r, d, d, l, u, u, d, l 526 | Nodes explored: 632363 527 | Previously seen: 166675 528 | Fringe: 96468 529 | Explored set: 369220 530 | Milliseconds elapsed: 11384 531 | -------------------------------------------------------------------------------- /resource/Level1.txt: -------------------------------------------------------------------------------- 1 | 6 2 | 7 3 | #### 4 | # .# 5 | # ### 6 | #*@ # 7 | # $ # 8 | # # 9 | ###### -------------------------------------------------------------------------------- /resource/Level2.txt: -------------------------------------------------------------------------------- 1 | 7 2 | 7 3 | ####### 4 | # # 5 | #@$.# # 6 | #*$ .# 7 | # $$ # 8 | # . . # 9 | ####### -------------------------------------------------------------------------------- /resource/Level3.txt: -------------------------------------------------------------------------------- 1 | 8 2 | 12 3 | ###### 4 | # ..@# 5 | # $$ # 6 | ## ### 7 | # # 8 | # # 9 | #### # 10 | # ## 11 | # # # 12 | # # # 13 | ### # 14 | ##### -------------------------------------------------------------------------------- /resource/Level4.txt: -------------------------------------------------------------------------------- 1 | 7 2 | 7 3 | ####### 4 | # @ # 5 | #$* **# 6 | #. #$ # 7 | # . $ # 8 | # . # 9 | ####### 10 | -------------------------------------------------------------------------------- /src/AStarSolver.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.PriorityQueue; 3 | 4 | 5 | public class AStarSolver extends AbstractSolver { 6 | private Heuristic heuristic; 7 | 8 | private AStarSolver(BoardState initialBoard) { 9 | super(initialBoard); 10 | queue = new PriorityQueue(); 11 | } 12 | 13 | public AStarSolver(BoardState initialBoard, Heuristic heuristic) { 14 | this(initialBoard); 15 | this.heuristic = heuristic; 16 | } 17 | 18 | @Override 19 | protected void searchFunction(ArrayList validMoves) { 20 | for (BoardState move : validMoves) { 21 | backtrack.put(move, currentState); 22 | heuristic.score(move); 23 | queue.add(move); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/AbstractSolver.java: -------------------------------------------------------------------------------- 1 | import java.awt.Point; 2 | import java.util.ArrayList; 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | import java.util.LinkedList; 6 | import java.util.Queue; 7 | 8 | /** 9 | * Abstract solver class with base search functionality 10 | * 11 | * All subclasses: 12 | * BFSSolver 13 | * DFSSolver 14 | * UniformCostSolver 15 | * GreedyBFSSolver 16 | * AStarSolver 17 | */ 18 | public abstract class AbstractSolver { 19 | protected BoardState currentState; 20 | protected HashSet visited; 21 | protected HashMap backtrack; 22 | protected Queue queue; 23 | 24 | private long startTime; 25 | private long endTime; 26 | private int previouslySeen; 27 | 28 | public AbstractSolver(BoardState initialState) { 29 | currentState = initialState; 30 | visited = new HashSet(); 31 | backtrack = new HashMap(); 32 | startTime = endTime = -1; 33 | previouslySeen = 0; 34 | } 35 | 36 | /** 37 | * Searches the Sokoban puzzle for solution and returns the move sequence 38 | * @return the Sokoban solution move sequence 39 | * @throws NoSolutionException if no solution is found 40 | */ 41 | public String search() throws NoSolutionException { 42 | startTimer(); 43 | searchStart(); 44 | while (!queue.isEmpty()) { 45 | currentState = queue.poll(); 46 | if (visited.contains(currentState)) 47 | previouslySeen++; 48 | visited.add(currentState); 49 | 50 | if (currentState.isSolved()) { 51 | System.out.println(currentState); 52 | String solution = backtrackMoves(currentState); 53 | stopTimer(); 54 | return solution; 55 | } 56 | 57 | ArrayList validMoves = getValidMoves(); 58 | searchFunction(validMoves); 59 | } 60 | throw new NoSolutionException(); 61 | } 62 | 63 | /** 64 | * Initialization before the search 65 | */ 66 | protected void searchStart() { 67 | queue.add(currentState); 68 | } 69 | 70 | /** 71 | * Search function 72 | * @param validMoves list of valid movies 73 | */ 74 | protected abstract void searchFunction(ArrayList validMoves); 75 | 76 | /** 77 | * Returns the valid moves from the current state. 78 | * @return the valid moves from the current state. 79 | */ 80 | protected ArrayList getValidMoves() { 81 | ArrayList validMoves = new ArrayList(4); 82 | addIfValid(validMoves, Direction.UP); 83 | addIfValid(validMoves, Direction.RIGHT); 84 | addIfValid(validMoves, Direction.DOWN); 85 | addIfValid(validMoves, Direction.LEFT); 86 | return validMoves; 87 | } 88 | 89 | /** 90 | * Backtracks through the search to find the move sequence 91 | * @param finalState the final, goal state 92 | * @return the Sokoban solution move sequence 93 | */ 94 | protected String backtrackMoves(BoardState finalState) { 95 | // Backtracking solutions and adding moves to stack 96 | LinkedList moveStack = new LinkedList(); 97 | BoardState current = finalState; 98 | while (current.getDirectionTaken() != null) { 99 | char move = Direction.directionToChar(current.getDirectionTaken()); 100 | moveStack.push(move); 101 | current = backtrack.get(current); 102 | } 103 | 104 | // Comma delimiting solution 105 | StringBuilder solution = new StringBuilder(); 106 | String delim = ""; 107 | for (Character move : moveStack) { 108 | solution.append(delim); 109 | solution.append(move); 110 | delim = ", "; 111 | } 112 | return solution.toString(); 113 | } 114 | 115 | protected void startTimer() { 116 | startTime = System.currentTimeMillis(); 117 | endTime = System.currentTimeMillis(); 118 | } 119 | 120 | protected void stopTimer() { 121 | endTime = System.currentTimeMillis(); 122 | } 123 | 124 | public long getElapsedTimeMillis() { 125 | return endTime - startTime; 126 | } 127 | 128 | public int getVisitedLength() { 129 | return visited.size(); 130 | } 131 | 132 | public int getFringeLength() { 133 | return queue.size(); 134 | } 135 | 136 | public int getNodesExplored() { 137 | return getPreviouslySeen() + getFringeLength() + getVisitedLength(); 138 | } 139 | 140 | public int getPreviouslySeen() { 141 | return previouslySeen; 142 | } 143 | 144 | private void addIfValid(ArrayList moves, Point direction) { 145 | if (currentState.canMove(direction)) { 146 | BoardState newState = currentState.getMove(direction); 147 | if (!visited.contains(newState)) 148 | moves.add(newState); 149 | } 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/BFSSolver.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.LinkedList; 3 | 4 | 5 | /** 6 | * Attempts to solve a Sokoban puzzle with BFS 7 | */ 8 | public class BFSSolver extends AbstractSolver { 9 | 10 | public BFSSolver(BoardState initialState) { 11 | super(initialState); 12 | queue = new LinkedList(); 13 | } 14 | 15 | @Override 16 | protected void searchFunction(ArrayList validMoves) { 17 | for (BoardState move : validMoves) { 18 | backtrack.put(move, currentState); 19 | queue.add(move); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/BoardState.java: -------------------------------------------------------------------------------- 1 | import java.awt.Point; 2 | import java.io.BufferedReader; 3 | import java.io.FileReader; 4 | import java.io.IOException; 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.HashSet; 8 | import java.util.Map.Entry; 9 | import java.util.Set; 10 | 11 | /** 12 | * Represents a single Sokoban BoardState 13 | */ 14 | public class BoardState implements Comparable { 15 | // Board position bitfields 16 | public static final byte PLAYER = 1 << 0; 17 | public static final byte WALL = 1 << 1; 18 | public static final byte BOX = 1 << 2; 19 | public static final byte GOAL = 1 << 3; 20 | // Character to bitfield bimapping 21 | private static HashMap charToField; 22 | private static HashMap fieldToChar; 23 | static { 24 | charToField = new HashMap(); 25 | charToField.put('0', WALL); 26 | charToField.put('S', GOAL); 27 | charToField.put('R', PLAYER); 28 | charToField.put('+', (byte) (PLAYER | GOAL)); 29 | charToField.put('B', BOX); 30 | charToField.put('*', (byte) (BOX | GOAL)); 31 | charToField.put(' ', (byte) 0); 32 | 33 | fieldToChar = new HashMap(); 34 | for (Entry entry : charToField.entrySet()) { 35 | fieldToChar.put(entry.getValue(), entry.getKey()); 36 | } 37 | } 38 | 39 | private byte[][] board; 40 | private Point player; 41 | private Set goals; 42 | private Set boxes; 43 | private Point directionTaken; 44 | private int cost; 45 | 46 | public BoardState(byte[][] board, Point player, Set goals, 47 | Set boxes) { 48 | this(board, player, goals, boxes, null); 49 | } 50 | 51 | public BoardState(byte[][] board, Point player, Set goals, 52 | Set boxes, Point direction) { 53 | this.board = board; 54 | this.player = player; 55 | this.goals = goals; 56 | this.boxes = boxes; 57 | this.directionTaken = direction; 58 | cost = 0; 59 | } 60 | 61 | public boolean isSolved() { 62 | for (Point p : goals) { 63 | if (!(pointHas(p.x, p.y, GOAL) && pointHas(p.x, p.y, BOX))) { 64 | return false; 65 | } 66 | } 67 | return true; 68 | } 69 | 70 | /** 71 | * Returns whether or not the player can move in a certain direction 72 | * @param direction the row/col direction 73 | * @return True if player can move, false otherwise 74 | */ 75 | public boolean canMove(Point direction) { 76 | Point newPos = new Point(player.x + direction.x, player.y + direction.y); 77 | Point oneOutPos = new Point(newPos.x + direction.x, newPos.y + direction.y); 78 | if (pointHas(newPos, BOX)) { 79 | // Box can't be pushed if there's a wall or box 80 | if (pointHas(oneOutPos, WALL) || pointHas(oneOutPos, BOX)) 81 | return false; 82 | // Shouldn't ever happen 83 | // else if (pointHas(oneOutPos, PLAYER)) 84 | // throw new IllegalStateException( 85 | // String.format("Player shouldn't be there row: %d col: %d", 86 | // oneOutPos.x, oneOutPos.y)); 87 | // Goal or empty 88 | else 89 | return true; 90 | } 91 | else if (pointHas(newPos, WALL)) 92 | return false; 93 | // else if (pointHas(newPos, PLAYER)) 94 | // throw new IllegalStateException( 95 | // String.format("Player shouldn't be there row: %d col: %d", 96 | // newPos.x, newPos.y)); 97 | // Goal or empty 98 | else 99 | return true; 100 | } 101 | 102 | /** 103 | * Returns the new BoardState after moving a certain direction 104 | * @param direction the direction to move 105 | * @return the new BoardState 106 | * @pre must be called only if canMove is true 107 | */ 108 | public BoardState getMove(Point direction) { 109 | Point newPos = new Point(player.x + direction.x, player.y + direction.y); 110 | Point oneOutPos = new Point(newPos.x + direction.x, newPos.y + direction.y); 111 | Set newBoxes = boxes; 112 | 113 | // Deep copy board 114 | byte[][] newBoard = new byte[board.length][]; 115 | for (int i = 0; i < newBoard.length; i++) 116 | newBoard[i] = board[i].clone(); 117 | 118 | // Take player off at current position 119 | byte playerBitField = newBoard[player.x][player.y]; 120 | newBoard[player.x][player.y] = toggleField(playerBitField, PLAYER); 121 | 122 | // Turn player on at new position 123 | byte newPlayerBitField = newBoard[newPos.x][newPos.y]; 124 | newBoard[newPos.x][newPos.y] = toggleField(newPlayerBitField, PLAYER); 125 | 126 | // If pushing a box, move box 127 | if (pointHas(newPos, BOX)) { 128 | byte oldBoxBitfield = newBoard[newPos.x][newPos.y]; 129 | byte newBoxBitfield = newBoard[oneOutPos.x][oneOutPos.y]; 130 | newBoard[newPos.x][newPos.y] = toggleField(oldBoxBitfield, BOX); 131 | newBoard[oneOutPos.x][oneOutPos.y] = toggleField(newBoxBitfield, BOX); 132 | // TODO big potential for a copy bug here? 133 | newBoxes = new HashSet(boxes); 134 | newBoxes.remove(newPos); 135 | newBoxes.add(oneOutPos); 136 | } 137 | 138 | // Not copying goals because they SHOULD be the same anyways... 139 | return new BoardState(newBoard, newPos, goals, newBoxes, direction); 140 | } 141 | 142 | /** 143 | * Returns true if the next move has the input bitfield. False otherwise. 144 | * @param field the next move's bitfield check 145 | * @return True if the next move has the input bitfield. False otherwise. 146 | */ 147 | public boolean nextMoveHas(byte field, Point direction) { 148 | Point nextPos = new Point(player.x + direction.x, player.y + direction.y); 149 | return pointHas(nextPos, field); 150 | } 151 | 152 | /** 153 | * Gets the byte board representation used for search hashing 154 | * @return the byte board representation 155 | */ 156 | public byte[][] getBoard() { 157 | return board; 158 | } 159 | 160 | /** 161 | * Gets the direction that the player made to get to the boardstate 162 | * @return the direction that the player made to get to the boardstate 163 | */ 164 | public Point getDirectionTaken() { 165 | return directionTaken; 166 | } 167 | 168 | /** 169 | * Sets the current state's cost 170 | * @param cost the current state's cost 171 | */ 172 | public void setCost(int cost) { 173 | this.cost = cost; 174 | } 175 | 176 | /** 177 | * Gets the current state's cost 178 | * @return the current state's cost 179 | */ 180 | public int getCost() { 181 | return cost; 182 | } 183 | 184 | public Set getGoals() { 185 | return new HashSet(goals); 186 | } 187 | 188 | public Set getBoxes() { 189 | return new HashSet(boxes); 190 | } 191 | 192 | @Override 193 | public int compareTo(BoardState other) { 194 | if (this.getCost() < other.getCost()) 195 | return -1; 196 | else if (this.getCost() > other.getCost()) 197 | return 1; 198 | else 199 | return 0; 200 | } 201 | 202 | @Override 203 | public String toString() { 204 | StringBuilder builder = new StringBuilder(); 205 | for (int row = 0; row < board.length; row++) { 206 | for (int col = 0; col < board[0].length; col++) { 207 | builder.append(fieldToChar.get(board[row][col])); 208 | } 209 | builder.append('\n'); 210 | } 211 | return builder.toString(); 212 | } 213 | 214 | @Override 215 | public int hashCode() { 216 | final int prime = 31; 217 | int result = 1; 218 | result = prime * result + Arrays.deepHashCode(board); 219 | result = prime * result + ((goals == null) ? 0 : goals.hashCode()); 220 | result = prime * result + ((player == null) ? 0 : player.hashCode()); 221 | return result; 222 | } 223 | 224 | @Override 225 | public boolean equals(Object obj) { 226 | if (this == obj) 227 | return true; 228 | if (obj == null) 229 | return false; 230 | if (getClass() != obj.getClass()) 231 | return false; 232 | BoardState other = (BoardState) obj; 233 | if (!Arrays.deepEquals(board, other.board)) 234 | return false; 235 | if (goals == null) { 236 | if (other.goals != null) 237 | return false; 238 | } else if (!goals.equals(other.goals)) 239 | return false; 240 | if (player == null) { 241 | if (other.player != null) 242 | return false; 243 | } else if (!player.equals(other.player)) 244 | return false; 245 | return true; 246 | } 247 | 248 | /** 249 | * Checks if a row/col pair has a certain bitfield 250 | * @param row the board row 251 | * @param col the board column 252 | * @param field the bitfield to check 253 | * @return True if the board row/col has the field. False otherwise. 254 | */ 255 | private boolean pointHas(int row, int col, byte field) { 256 | return (board[row][col] & field) == field; 257 | } 258 | 259 | /** 260 | * Checks if a Point coordinate has a certain field 261 | * @param pos the Point coordinate, where x is row and y is column 262 | * @param field the bitfield to check 263 | * @return True if the Point coordinate has the field. False otherwise. 264 | */ 265 | private boolean pointHas(Point pos, byte field) { 266 | return pointHas(pos.x, pos.y, field); 267 | } 268 | 269 | /** 270 | * Toggles the bitfield with a field. 271 | * @param bitfield the bitfield to toggle 272 | * @param field the field to toggle 273 | * @return the new, toggled bitfield 274 | */ 275 | private byte toggleField(byte bitfield, byte field) { 276 | return (byte) (bitfield ^ field); 277 | } 278 | 279 | /** 280 | * Parses a Sokoban text file into a Board object 281 | * @param boardInput the Sokoban text file 282 | * @return the Board state object 283 | * @throws IOException if the Sokoban file does not exist 284 | */ 285 | public static BoardState parseBoardInput(String boardInput) throws IOException { 286 | BufferedReader reader = new BufferedReader(new FileReader(boardInput)); 287 | int width = Integer.parseInt(reader.readLine()); 288 | int height = Integer.parseInt(reader.readLine()); 289 | byte[][] boardPoints = new byte[height][width]; 290 | Point player = new Point(); 291 | Set goals = new HashSet(); 292 | Set boxes = new HashSet(); 293 | 294 | String line; 295 | for (int row = 0; row < height && (line = reader.readLine()) != null; row++) { 296 | for (int col = 0; col < width && col < line.length(); col++) { 297 | byte field = charToField.get(line.charAt(col)); 298 | boardPoints[row][col] = field; 299 | if ((field & PLAYER) == PLAYER) 300 | player = new Point(row, col); 301 | if ((field & GOAL) == GOAL) 302 | goals.add(new Point(row, col)); 303 | if ((field & BOX) == BOX) 304 | boxes.add(new Point(row, col)); 305 | } 306 | } 307 | 308 | reader.close(); 309 | return new BoardState(boardPoints, player, goals, boxes); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/BoxGoalHeuristic.java: -------------------------------------------------------------------------------- 1 | import java.awt.Point; 2 | import java.util.HashSet; 3 | import java.util.Set; 4 | 5 | public class BoxGoalHeuristic implements Heuristic { 6 | 7 | @Override 8 | public void score(BoardState state) { 9 | Set goals = state.getGoals(); 10 | Set boxes = state.getBoxes(); 11 | 12 | Set intersection = new HashSet(goals); 13 | intersection.retainAll(boxes); 14 | 15 | // Difference because lower costs are better 16 | state.setCost(goals.size() - intersection.size()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/DFSSolver.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.LinkedList; 3 | 4 | /** 5 | * Attempts to solve a Sokoban puzzle with DFS 6 | */ 7 | public class DFSSolver extends AbstractSolver { 8 | 9 | public DFSSolver(BoardState initialState) { 10 | super(initialState); 11 | queue = new LinkedList(); 12 | } 13 | 14 | @Override 15 | protected void searchFunction(ArrayList validMoves) { 16 | for (BoardState move : validMoves) { 17 | backtrack.put(move, currentState); 18 | ((LinkedList) queue).push(move); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Direction.java: -------------------------------------------------------------------------------- 1 | import java.awt.Point; 2 | 3 | /** 4 | * Wrapper class for Sokoban board movements 5 | */ 6 | 7 | public class Direction { 8 | public static final Point UP = new Point(-1, 0); 9 | public static final Point RIGHT = new Point(0, 1); 10 | public static final Point DOWN = new Point(1, 0); 11 | public static final Point LEFT = new Point(0, -1); 12 | 13 | private Direction() {} 14 | 15 | /** 16 | * Point direction to character mapping for search output 17 | * @param direction the direction to translate 18 | * @return the corresponding character mapping 19 | */ 20 | public static char directionToChar(Point direction) { 21 | if (direction.equals(UP)) 22 | return 'u'; 23 | else if (direction.equals(RIGHT)) 24 | return 'r'; 25 | else if (direction.equals(DOWN)) 26 | return 'd'; 27 | else if (direction.equals(LEFT)) 28 | return 'l'; 29 | else 30 | throw new IllegalStateException("Non-existent direction: " 31 | + direction); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/GreedyBFSSolver.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.PriorityQueue; 3 | 4 | /** 5 | * Attempts to solve a Sokoban puzzle with greedy best-first search 6 | */ 7 | public class GreedyBFSSolver extends AbstractSolver { 8 | private Heuristic heuristic; 9 | 10 | private GreedyBFSSolver(BoardState initialState) { 11 | super(initialState); 12 | queue = new PriorityQueue(); 13 | } 14 | 15 | public GreedyBFSSolver(BoardState initialState, Heuristic heuristic) { 16 | this(initialState); 17 | this.heuristic = heuristic; 18 | } 19 | 20 | @Override 21 | protected void searchStart() { 22 | super.searchStart(); 23 | heuristic.score(currentState); 24 | } 25 | 26 | @Override 27 | protected void searchFunction(ArrayList validMoves) { 28 | for (BoardState move : validMoves) { 29 | backtrack.put(move, currentState); 30 | heuristic.score(move); 31 | if (move.getCost() < currentState.getCost()) { 32 | queue.add(currentState); 33 | queue.add(move); 34 | break; 35 | } 36 | queue.add(move); 37 | } 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/Heuristic.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Command interface for varying heuristics in greedy BFS and A* 4 | * 5 | * All implementing classes: 6 | * BoxGoalHeuristic 7 | * ManhattanHeuristic 8 | */ 9 | public interface Heuristic { 10 | 11 | public void score(BoardState state); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/ManhattanHeuristic.java: -------------------------------------------------------------------------------- 1 | import java.awt.Point; 2 | import java.util.HashSet; 3 | import java.util.Set; 4 | 5 | 6 | public class ManhattanHeuristic implements Heuristic { 7 | 8 | @Override 9 | public void score(BoardState state) { 10 | Set goals = state.getGoals(); 11 | Set boxes = state.getBoxes(); 12 | 13 | // Boxes on a goal are cost 0 so don't check them 14 | Set intersection = new HashSet(goals); 15 | intersection.retainAll(boxes); 16 | goals.removeAll(intersection); 17 | boxes.removeAll(intersection); 18 | 19 | // TODO optimize to recalculate only one box's delta score if it has 20 | // been pushed. Although for complicated puzzles (like Kristi), this 21 | // is not a bottleneck 22 | int cost = 0; 23 | for (Point box : boxes) { 24 | int minMarginalCost = Integer.MAX_VALUE; 25 | for (Point goal : goals) { 26 | int dist = getManhattanDistance(box, goal); 27 | if (dist < minMarginalCost) 28 | minMarginalCost = dist; 29 | } 30 | cost += minMarginalCost; 31 | } 32 | state.setCost(cost); 33 | } 34 | 35 | private static int getManhattanDistance(Point p1, Point p2) { 36 | return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/NoSolutionException.java: -------------------------------------------------------------------------------- 1 | public class NoSolutionException extends Exception { 2 | public NoSolutionException() { 3 | super(); 4 | } 5 | 6 | public NoSolutionException(String message) { 7 | super(message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SokobanMain.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Assignment 1 Sokoban Solver 3 | * Forrest Knight - CS480 - Artificial Intelligence 4 | * Fall 2017 5 | */ 6 | public class SokobanMain { 7 | 8 | /** 9 | * @param args 10 | */ 11 | public static void main(String[] args) { 12 | SokobanSolver.parseArguments(args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/SokobanSolver.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | 3 | /** 4 | * Command line interface for solving Sokoban with: 5 | * - BFS 6 | * - DFS 7 | * - Uniform cost search 8 | * - Greedy best first search 9 | * - A* search 10 | */ 11 | 12 | public class SokobanSolver { 13 | 14 | public static void parseArguments(String[] args) { 15 | try { 16 | // TODO some form of input validation 17 | String flag = args[0]; 18 | String puzzlePath = args[1]; 19 | BoardState initialBoard = BoardState.parseBoardInput(puzzlePath); 20 | AbstractSolver solver = null; 21 | System.out.println(initialBoard); 22 | if (flag.equals("-b")) { 23 | solver = new BFSSolver(initialBoard); 24 | } 25 | else if (flag.equals("-d")) { 26 | solver = new DFSSolver(initialBoard); 27 | } 28 | else if (flag.equals("-u")) { 29 | solver = new UniformCostSolver(initialBoard); 30 | } 31 | else if (flag.equals("-ab")) { 32 | solver = new AStarSolver(initialBoard, new BoxGoalHeuristic()); 33 | } 34 | else if (flag.equals("-gb")) { 35 | solver = new GreedyBFSSolver(initialBoard, new BoxGoalHeuristic()); 36 | } 37 | else if (flag.equals("-am")) { 38 | solver = new AStarSolver(initialBoard, new ManhattanHeuristic()); 39 | } 40 | else if (flag.equals("-gm")) { 41 | solver = new GreedyBFSSolver(initialBoard, new ManhattanHeuristic()); 42 | } 43 | else { 44 | System.out.println("Invalid command"); 45 | } 46 | 47 | if (solver != null) { 48 | String solution = solver.search(); 49 | int nodesExplored = solver.getNodesExplored(); 50 | int previouslySeen = solver.getPreviouslySeen(); 51 | int queueLength = solver.getFringeLength(); 52 | int visitedLength = solver.getVisitedLength(); 53 | long timeElapsed = solver.getElapsedTimeMillis(); 54 | System.out.println("Solution: " + solution); 55 | System.out.println("Nodes explored: " + nodesExplored); 56 | System.out.println("Previously seen: " + previouslySeen); 57 | System.out.println("Fringe: " + queueLength); 58 | System.out.println("Explored set: " + visitedLength); 59 | System.out.println("Millis elapsed: " + timeElapsed); 60 | } 61 | } catch (IOException e) { 62 | System.out.println("Puzzle file not found"); 63 | } catch (NoSolutionException e) { 64 | System.out.println("Solution does not exist"); 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/UniformCostSolver.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.PriorityQueue; 3 | 4 | /** 5 | * Attempts to solve a Sokoban puzzle with uniform cost search, having push as cost 2 and move as cost 1 6 | */ 7 | 8 | public class UniformCostSolver extends AbstractSolver { 9 | 10 | public UniformCostSolver(BoardState initialState) { 11 | super(initialState); 12 | queue = new PriorityQueue(); 13 | } 14 | 15 | @Override 16 | protected void searchFunction(ArrayList moves) { 17 | for (BoardState move : moves) { 18 | backtrack.put(move, currentState); 19 | uniformCostFunction(move, currentState.getCost()); 20 | queue.add(move); 21 | } 22 | } 23 | 24 | private void uniformCostFunction(BoardState state, int baseCost) { 25 | if (currentState.nextMoveHas(BoardState.BOX, state.getDirectionTaken())) 26 | state.setCost(baseCost + 2); 27 | else 28 | state.setCost(baseCost + 1); 29 | } 30 | } 31 | --------------------------------------------------------------------------------