diff options
author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2016-02-21 21:45:17 -0500 |
---|---|---|
committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2016-02-21 21:45:17 -0500 |
commit | f6413b88fba9ac6d61c7ecdd8a1a300b69368c8d (patch) | |
tree | 5aa32bafd5670974e13dd4c7c27ee68bd2030741 | |
parent | a8f3a69fdf0428fc26121bfb9d705fdef904eef4 (diff) | |
download | mazeoflife-f6413b88fba9ac6d61c7ecdd8a1a300b69368c8d.tar.gz mazeoflife-f6413b88fba9ac6d61c7ecdd8a1a300b69368c8d.tar.bz2 mazeoflife-f6413b88fba9ac6d61c7ecdd8a1a300b69368c8d.zip |
Ensured that generated levels are possible
I wrote a pathfinding algorithm that will attempt to solve boards as they are generated. I tried a variety of algorithms, most of which either did not terminate in a timely fashion. Depth-first search, with a tendancy towards moving towards the center, proved to be the best algorithm, and managed to solve boards the other algorithms timed out on. Because the algorithm was mostly tested on levels 0-9, and level diversity increases dramatically past that point (to the point where, past level 50, I am concerned that actual impossible boards may appear), I cannot be completely sure that the algorithm will perform as desired. To help debug, the program will dump a string representing a board state if an impossible board is generated. Also fixed a bug in wrap().
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | gamestate.cpp | 323 | ||||
-rw-r--r-- | util.cpp | 8 |
3 files changed, 252 insertions, 81 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ca45e19..136dadc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -14,4 +14,6 @@ include_directories( | |||
14 | ) | 14 | ) |
15 | 15 | ||
16 | add_executable(mazeoflife highscore.cpp hslist.cpp mazeoflife.cpp util.cpp titlestate.cpp gamestate.cpp) | 16 | add_executable(mazeoflife highscore.cpp hslist.cpp mazeoflife.cpp util.cpp titlestate.cpp gamestate.cpp) |
17 | set_property(TARGET mazeoflife PROPERTY CXX_STANDARD 11) | ||
18 | set_property(TARGET mazeoflife PROPERTY CXX_STANDARD_REQUIRED ON) | ||
17 | target_link_libraries(mazeoflife ${SDL2_LIBRARY} ${SDL2_TTF_LIBRARY} ${SDL2_NET_LIBRARIES}) | 19 | target_link_libraries(mazeoflife ${SDL2_LIBRARY} ${SDL2_TTF_LIBRARY} ${SDL2_NET_LIBRARIES}) |
diff --git a/gamestate.cpp b/gamestate.cpp index f0de67a..3ff08c4 100644 --- a/gamestate.cpp +++ b/gamestate.cpp | |||
@@ -6,17 +6,32 @@ | |||
6 | #include "titlestate.h" | 6 | #include "titlestate.h" |
7 | #include <fstream> | 7 | #include <fstream> |
8 | #include "hslist.h" | 8 | #include "hslist.h" |
9 | #include <set> | ||
10 | #include <bitset> | ||
11 | #include <tuple> | ||
12 | #include <algorithm> | ||
13 | #include <vector> | ||
14 | #include <deque> | ||
15 | #include <iostream> | ||
16 | #include <sstream> | ||
9 | 17 | ||
10 | class GameBoard { | 18 | class GameBoard { |
11 | public: | 19 | public: |
12 | GameBoard(int level); | 20 | GameBoard(int level, int playerx, int playery); |
13 | void tick(int playerx, int playery); | 21 | void tick(int playerx, int playery); |
14 | void render(SDL_Renderer* renderer); | 22 | void render(SDL_Renderer* renderer, int level) const; |
15 | bool isObstructed(int x, int y); | 23 | bool isObstructed(int x, int y) const; |
24 | bool operator<(const GameBoard& other) const; | ||
16 | 25 | ||
17 | private: | 26 | private: |
18 | int level; | 27 | void initialize(int level); |
19 | bool blocks[WIDTH][HEIGHT]; | 28 | int solve(int playerx, int playery) const; |
29 | std::string dump() const; | ||
30 | |||
31 | std::bitset<WIDTH*HEIGHT> blocks; | ||
32 | std::bitset<WIDTH*HEIGHT> updateable; | ||
33 | int oldx; | ||
34 | int oldy; | ||
20 | }; | 35 | }; |
21 | 36 | ||
22 | class LoadGameState : public State { | 37 | class LoadGameState : public State { |
@@ -65,7 +80,7 @@ void setRendererDeadColor(SDL_Renderer* renderer, int level) | |||
65 | } | 80 | } |
66 | } | 81 | } |
67 | 82 | ||
68 | void incrementIfNeighbor(int x, int y, bool temp[WIDTH][HEIGHT], int* tick, int playerx, int playery) | 83 | void incrementIfNeighbor(int x, int y, std::bitset<WIDTH*HEIGHT>& temp, int* tick, int playerx, int playery) |
69 | { | 84 | { |
70 | int nx = x; | 85 | int nx = x; |
71 | int ny = y; | 86 | int ny = y; |
@@ -74,7 +89,7 @@ void incrementIfNeighbor(int x, int y, bool temp[WIDTH][HEIGHT], int* tick, int | |||
74 | 89 | ||
75 | if (!((nx!=x)&&(ny!=y))) | 90 | if (!((nx!=x)&&(ny!=y))) |
76 | { | 91 | { |
77 | if ((temp[x][y])||((playerx==x)&&(playery==y))||((x==15)&&(y==15))) | 92 | if ((temp[x+y*WIDTH])||((playerx==x)&&(playery==y))||((x==15)&&(y==15))) |
78 | { | 93 | { |
79 | ++*tick; | 94 | ++*tick; |
80 | } | 95 | } |
@@ -128,11 +143,7 @@ State* LoadGameState::operator() (SDL_Window* window, SDL_Renderer* renderer) | |||
128 | SDL_RenderPresent(renderer); | 143 | SDL_RenderPresent(renderer); |
129 | 144 | ||
130 | // Do 50 gens of Conway | 145 | // Do 50 gens of Conway |
131 | GameBoard* board = new GameBoard(level); | 146 | GameBoard* board = new GameBoard(level, playerx, playery); |
132 | for (int i=0; i<50; i++) | ||
133 | { | ||
134 | board->tick(playerx, playery); | ||
135 | } | ||
136 | 147 | ||
137 | // Wait a bit | 148 | // Wait a bit |
138 | SDL_Delay(500); | 149 | SDL_Delay(500); |
@@ -159,7 +170,7 @@ State* PlayGameState::operator() (SDL_Window* window, SDL_Renderer* renderer) | |||
159 | board->tick(playerx, playery); | 170 | board->tick(playerx, playery); |
160 | 171 | ||
161 | // Paint board | 172 | // Paint board |
162 | board->render(renderer); | 173 | board->render(renderer, level); |
163 | 174 | ||
164 | // Paint event | 175 | // Paint event |
165 | SDL_Rect block; | 176 | SDL_Rect block; |
@@ -292,90 +303,103 @@ bool PlayGameState::move(int x, int y) | |||
292 | return false; | 303 | return false; |
293 | } | 304 | } |
294 | 305 | ||
295 | GameBoard::GameBoard(int m_level) | 306 | GameBoard::GameBoard(int level, int playerx, int playery) |
296 | { | 307 | { |
297 | level = m_level; | 308 | for (;;) |
298 | 309 | { | |
299 | for (int y=0; y<HEIGHT; y++) | 310 | initialize(level); |
300 | { | 311 | updateable.set(); |
301 | for (int x=0; x<WIDTH; x++) | 312 | oldx = playerx; |
302 | { | 313 | oldy = playery; |
303 | blocks[x][y] = false; | 314 | |
304 | 315 | for (int i=0; i<50; i++) | |
305 | switch (level/10+1) | 316 | { |
306 | { | 317 | tick(playerx, playery); |
307 | case 1: | 318 | } |
308 | if ((x>13)&&(x<17)&&(y>13)&&(y<17)) | 319 | |
309 | { | 320 | int states = solve(playerx, playery); |
310 | blocks[x][y] = rand() % 2; | 321 | if (states != 0) |
311 | } | 322 | { |
312 | break; | 323 | break; |
313 | case 2: | 324 | } else { |
314 | case 3: | 325 | std::cout << "Impossible board: " << playerx << "," << playery << "," << dump() << std::endl; |
315 | if ((x>12)&&(x<18)&&(y>12)&&(y<18)) | 326 | } |
316 | { | 327 | } |
317 | blocks[x][y] = rand() % 2; | ||
318 | } | ||
319 | break; | ||
320 | case 4: | ||
321 | case 5: | ||
322 | if ((x>11)&&(x<19)&&(y>11)&&(y<19)) | ||
323 | { | ||
324 | blocks[x][y] = rand() % 2; | ||
325 | } | ||
326 | break; | ||
327 | default: | ||
328 | blocks[x][y] = rand() % 2; | ||
329 | } | ||
330 | } | ||
331 | } | ||
332 | |||
333 | blocks[15][15] = false; | ||
334 | } | 328 | } |
335 | 329 | ||
336 | void GameBoard::tick(int playerx, int playery) | 330 | void GameBoard::tick(int playerx, int playery) |
337 | { | 331 | { |
338 | bool temp[WIDTH][HEIGHT]; | 332 | std::bitset<WIDTH*HEIGHT> temp {blocks}; |
339 | int x,y; | 333 | std::bitset<WIDTH*HEIGHT> tempdateable {updateable}; |
340 | for (x=0;x<WIDTH;x++) | 334 | if ((playerx != oldx) || (playery != oldy)) |
341 | { | 335 | { |
342 | for (y=0;y<HEIGHT;y++) | 336 | for (int dy = -1; dy <= 1; dy++) |
343 | { | 337 | { |
344 | temp[x][y] = blocks[x][y]; | 338 | for (int dx = -1; dx <=1; dx++) |
345 | } | 339 | { |
346 | } | 340 | int tdx = oldx+dx; |
341 | int tdy = oldy+dy; | ||
342 | wrap(&tdx, &tdy); | ||
343 | tempdateable.set(tdx+tdy*WIDTH); | ||
344 | |||
345 | tdx = playerx+dx; | ||
346 | tdy = playery+dy; | ||
347 | wrap(&tdx, &tdy); | ||
348 | tempdateable.set(tdx+tdy*WIDTH); | ||
349 | } | ||
350 | } | ||
351 | } | ||
352 | |||
353 | oldx = playerx; | ||
354 | oldy = playery; | ||
355 | |||
356 | updateable.reset(); | ||
347 | 357 | ||
348 | for (x=0;x<WIDTH;x++) | 358 | for (int y=0;y<HEIGHT;y++) |
349 | { | 359 | { |
350 | for (y=0;y<HEIGHT;y++) | 360 | for (int x=0;x<WIDTH;x++) |
351 | { | 361 | { |
352 | if ((x==15)&&(y==15)) | 362 | if (((x==15)&&(y==15)) || (!tempdateable[x+y*WIDTH])) |
353 | { | 363 | { |
354 | continue; | 364 | continue; |
355 | } | 365 | } |
356 | 366 | ||
357 | int neighbors = 0; | 367 | int neighbors = 0; |
358 | 368 | ||
359 | incrementIfNeighbor(x-1,y-1,temp,&neighbors, playerx, playery); | 369 | incrementIfNeighbor(x-1, y-1, temp, &neighbors, playerx, playery); |
360 | incrementIfNeighbor(x-1,y,temp,&neighbors, playerx, playery); | 370 | incrementIfNeighbor(x-1, y , temp, &neighbors, playerx, playery); |
361 | incrementIfNeighbor(x-1,y+1,temp,&neighbors, playerx, playery); | 371 | incrementIfNeighbor(x-1, y+1, temp, &neighbors, playerx, playery); |
362 | incrementIfNeighbor(x,y-1,temp,&neighbors, playerx, playery); | 372 | incrementIfNeighbor(x , y-1, temp, &neighbors, playerx, playery); |
363 | incrementIfNeighbor(x,y+1,temp,&neighbors, playerx, playery); | 373 | incrementIfNeighbor(x , y+1, temp, &neighbors, playerx, playery); |
364 | incrementIfNeighbor(x+1,y-1,temp,&neighbors, playerx, playery); | 374 | incrementIfNeighbor(x+1, y-1, temp, &neighbors, playerx, playery); |
365 | incrementIfNeighbor(x+1,y,temp,&neighbors, playerx, playery); | 375 | incrementIfNeighbor(x+1, y , temp, &neighbors, playerx, playery); |
366 | incrementIfNeighbor(x+1,y+1,temp,&neighbors, playerx, playery); | 376 | incrementIfNeighbor(x+1, y+1, temp, &neighbors, playerx, playery); |
367 | 377 | ||
368 | if (temp[x][y]) | 378 | if (temp[x+y*WIDTH]) |
369 | { | 379 | { |
370 | blocks[x][y] = ((neighbors >= 1) && (neighbors <= 4)); | 380 | blocks[x+y*WIDTH] = ((neighbors >= 1) && (neighbors <= 4)); |
371 | } else { | 381 | } else { |
372 | blocks[x][y] = (neighbors == 3); | 382 | blocks[x+y*WIDTH] = (neighbors == 3); |
373 | } | 383 | } |
384 | |||
385 | if (temp[x+y*WIDTH] != blocks[x+y*WIDTH]) | ||
386 | { | ||
387 | for (int dy = -1; dy <= 1; dy++) | ||
388 | { | ||
389 | for (int dx = -1; dx <=1; dx++) | ||
390 | { | ||
391 | int tdx = x+dx; | ||
392 | int tdy = y+dy; | ||
393 | wrap(&tdx, &tdy); | ||
394 | updateable.set(tdx+tdy*WIDTH); | ||
395 | } | ||
396 | } | ||
397 | } | ||
374 | } | 398 | } |
375 | } | 399 | } |
376 | } | 400 | } |
377 | 401 | ||
378 | void GameBoard::render(SDL_Renderer* renderer) | 402 | void GameBoard::render(SDL_Renderer* renderer, int level) const |
379 | { | 403 | { |
380 | SDL_Rect block; | 404 | SDL_Rect block; |
381 | block.w = 16; | 405 | block.w = 16; |
@@ -388,7 +412,7 @@ void GameBoard::render(SDL_Renderer* renderer) | |||
388 | block.x = x*16; | 412 | block.x = x*16; |
389 | block.y = y*16; | 413 | block.y = y*16; |
390 | 414 | ||
391 | if (blocks[x][y]) | 415 | if (blocks[x+y*WIDTH]) |
392 | { | 416 | { |
393 | setRendererAliveColor(renderer, level); | 417 | setRendererAliveColor(renderer, level); |
394 | } else { | 418 | } else { |
@@ -400,7 +424,150 @@ void GameBoard::render(SDL_Renderer* renderer) | |||
400 | } | 424 | } |
401 | } | 425 | } |
402 | 426 | ||
403 | bool GameBoard::isObstructed(int x, int y) | 427 | bool GameBoard::isObstructed(int x, int y) const |
428 | { | ||
429 | return blocks[x+y*WIDTH]; | ||
430 | } | ||
431 | |||
432 | void GameBoard::initialize(int level) | ||
433 | { | ||
434 | for (int y=0; y<HEIGHT; y++) | ||
435 | { | ||
436 | for (int x=0; x<WIDTH; x++) | ||
437 | { | ||
438 | blocks[x+y*WIDTH] = false; | ||
439 | |||
440 | switch (level/10+1) | ||
441 | { | ||
442 | case 1: | ||
443 | if ((x>13)&&(x<17)&&(y>13)&&(y<17)) | ||
444 | { | ||
445 | blocks[x+y*WIDTH] = rand() % 2; | ||
446 | } | ||
447 | break; | ||
448 | case 2: | ||
449 | case 3: | ||
450 | if ((x>12)&&(x<18)&&(y>12)&&(y<18)) | ||
451 | { | ||
452 | blocks[x+y*WIDTH] = rand() % 2; | ||
453 | } | ||
454 | break; | ||
455 | case 4: | ||
456 | case 5: | ||
457 | if ((x>11)&&(x<19)&&(y>11)&&(y<19)) | ||
458 | { | ||
459 | blocks[x+y*WIDTH] = rand() % 2; | ||
460 | } | ||
461 | break; | ||
462 | default: | ||
463 | blocks[x+y*WIDTH] = rand() % 2; | ||
464 | } | ||
465 | } | ||
466 | } | ||
467 | |||
468 | blocks[15+15*WIDTH] = false; | ||
469 | } | ||
470 | |||
471 | bool GameBoard::operator<(const GameBoard& other) const | ||
472 | { | ||
473 | for (int i = WIDTH*HEIGHT-1; i >= 0; i--) | ||
474 | { | ||
475 | if (blocks[i] ^ other.blocks[i]) | ||
476 | { | ||
477 | return other.blocks[i]; | ||
478 | } | ||
479 | } | ||
480 | |||
481 | return false; | ||
482 | } | ||
483 | |||
484 | int GameBoard::solve(int playerx, int playery) const | ||
485 | { | ||
486 | std::deque<std::tuple<int, int, GameBoard, int>> search; | ||
487 | std::set<std::tuple<int, int, GameBoard>> done; | ||
488 | search.push_front(std::make_tuple(playerx, playery, *this, 0)); | ||
489 | |||
490 | bool exists = false; | ||
491 | while (!search.empty()) | ||
492 | { | ||
493 | auto cur = search.front(); | ||
494 | search.pop_front(); | ||
495 | |||
496 | int cpx = std::get<0>(cur); | ||
497 | int cpy = std::get<1>(cur); | ||
498 | GameBoard& cbr = std::get<2>(cur); | ||
499 | int cns = std::get<3>(cur); | ||
500 | |||
501 | auto cdn = std::make_tuple(cpx, cpy, cbr); | ||
502 | done.insert(cdn); | ||
503 | |||
504 | if (((cpx == 15) && ((cpy == 14) || (cpy == 16))) || ((cpy == 15) && ((cpx == 14) || (cpx == 16)))) | ||
505 | { | ||
506 | exists = true; | ||
507 | break; | ||
508 | } | ||
509 | |||
510 | if (cns >= 100) | ||
511 | { | ||
512 | continue; | ||
513 | } | ||
514 | |||
515 | GameBoard immnext {cbr}; | ||
516 | immnext.tick(cpx, cpy); | ||
517 | if (immnext.blocks != cbr.blocks) | ||
518 | { | ||
519 | if (done.count(std::make_tuple(cpx, cpy, immnext)) == 0) | ||
520 | { | ||
521 | search.push_front(std::make_tuple(cpx, cpy, immnext, cns)); | ||
522 | |||
523 | continue; | ||
524 | } | ||
525 | } | ||
526 | |||
527 | std::vector<std::pair<int, int>> dirchanges {{cpx-1,cpy}, {cpx,cpy-1}, {cpx+1,cpy}, {cpx,cpy+1}}; | ||
528 | std::sort(std::begin(dirchanges), std::end(dirchanges), [] (const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) { | ||
529 | int lhd = sqrt(pow(15 - lhs.first, 2.0) + pow(15 - lhs.second, 2.0)); | ||
530 | int rhd = sqrt(pow(15 - rhs.first, 2.0) + pow(15 - rhs.second, 2.0)); | ||
531 | |||
532 | return lhd > rhd; | ||
533 | }); | ||
534 | |||
535 | for (auto& dirchange : dirchanges) | ||
536 | { | ||
537 | GameBoard next {cbr}; | ||
538 | int npx = cpx + dirchange.first; | ||
539 | int npy = cpy + dirchange.second; | ||
540 | wrap(&npx, &npy); | ||
541 | |||
542 | if (!next.isObstructed(npx, npy)) | ||
543 | { | ||
544 | next.tick(npx, npy); | ||
545 | |||
546 | if (done.count(std::make_tuple(npx, npy, next)) == 0) | ||
547 | { | ||
548 | search.push_front(std::make_tuple(npx, npy, next, cns+1)); | ||
549 | } | ||
550 | } | ||
551 | } | ||
552 | } | ||
553 | |||
554 | if (exists) | ||
555 | { | ||
556 | return done.size(); | ||
557 | } else { | ||
558 | return 0; | ||
559 | } | ||
560 | } | ||
561 | |||
562 | std::string GameBoard::dump() const | ||
404 | { | 563 | { |
405 | return blocks[x][y]; | 564 | std::stringstream output; |
565 | output << std::hex; | ||
566 | for (int i=0; i<WIDTH*HEIGHT/4; i++) | ||
567 | { | ||
568 | int chunk = (8 * blocks[i*4]) + (4 * blocks[i*4+1]) + (2 * blocks[i*4+2]) + blocks[i*4+3]; | ||
569 | output << chunk; | ||
570 | } | ||
571 | |||
572 | return output.str(); | ||
406 | } | 573 | } |
diff --git a/util.cpp b/util.cpp index b021146..868161f 100644 --- a/util.cpp +++ b/util.cpp | |||
@@ -7,12 +7,14 @@ void wrap(int* x, int* y) | |||
7 | if (*x < 0) | 7 | if (*x < 0) |
8 | { | 8 | { |
9 | *x = WIDTH-(0-*x); | 9 | *x = WIDTH-(0-*x); |
10 | } else if (*y < 0) | ||
11 | { | ||
12 | *y = HEIGHT-(0-*y); | ||
13 | } else if (*x >= WIDTH) | 10 | } else if (*x >= WIDTH) |
14 | { | 11 | { |
15 | *x = *x-WIDTH; | 12 | *x = *x-WIDTH; |
13 | } | ||
14 | |||
15 | if (*y < 0) | ||
16 | { | ||
17 | *y = HEIGHT-(0-*y); | ||
16 | } else if (*y >= HEIGHT) | 18 | } else if (*y >= HEIGHT) |
17 | { | 19 | { |
18 | *y = *y-HEIGHT; | 20 | *y = *y-HEIGHT; |