summary refs log tree commit diff stats
path: root/gamestate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gamestate.cpp')
-rw-r--r--gamestate.cpp953
1 files changed, 464 insertions, 489 deletions
diff --git a/gamestate.cpp b/gamestate.cpp index e77ff76..953d35b 100644 --- a/gamestate.cpp +++ b/gamestate.cpp
@@ -8,6 +8,8 @@
8#include <fstream> 8#include <fstream>
9#include <iostream> 9#include <iostream>
10#include <list> 10#include <list>
11#include <memory>
12#include <random>
11#include <set> 13#include <set>
12#include <sstream> 14#include <sstream>
13#include <tuple> 15#include <tuple>
@@ -16,61 +18,13 @@
16#include <vector> 18#include <vector>
17 19
18#include "highscore.h" 20#include "highscore.h"
21#include "hs_state.h"
19#include "hslist.h" 22#include "hslist.h"
20#include "mazeoflife.h" 23#include "mazeoflife.h"
21#include "titlestate.h" 24#include "titlestate.h"
22#include "util.h" 25#include "util.h"
23 26
24class GameBoard { 27using board_type = std::bitset<WIDTH * HEIGHT>;
25 public:
26 GameBoard(int level, int playerx, int playery);
27 void tick(int playerx, int playery);
28 void render(SDL_Renderer* renderer, int level) const;
29 bool isObstructed(int x, int y) const;
30 bool operator<(const GameBoard& other) const;
31
32 using coord = std::tuple<int, int>;
33
34 private:
35 void initialize(int level);
36 bool solve(int playerx, int playery) const;
37 std::string dump() const;
38
39 using board_type = std::bitset<WIDTH * HEIGHT>;
40
41 void incrementIfNeighbor(int x, int y, const board_type& temp, int playerx,
42 int playery, int& tick) const;
43
44 bool applyNeighbors(int x, int y, const board_type& temp, int playerx,
45 int playery) const;
46
47 board_type blocks;
48 board_type updateable;
49 int oldx;
50 int oldy;
51};
52
53class LoadGameState : public State {
54 public:
55 LoadGameState(int level);
56 State* operator()(SDL_Window* window, SDL_Renderer* renderer);
57
58 private:
59 int level;
60};
61
62class PlayGameState : public State {
63 public:
64 PlayGameState(int level, GameBoard* board, int playerx, int playery);
65 State* operator()(SDL_Window* window, SDL_Renderer* renderer);
66
67 private:
68 bool move(int x, int y);
69 int level;
70 GameBoard* board;
71 int playerx;
72 int playery;
73};
74 28
75void setRendererAliveColor(SDL_Renderer* renderer, int level) { 29void setRendererAliveColor(SDL_Renderer* renderer, int level) {
76 switch ((level / 10) % 5) { 30 switch ((level / 10) % 5) {
@@ -112,8 +66,8 @@ void setRendererDeadColor(SDL_Renderer* renderer, int level) {
112 } 66 }
113} 67}
114 68
115void GameBoard::incrementIfNeighbor(int x, int y, const board_type& temp, 69void incrementIfNeighbor(int x, int y, const board_type& temp, int playerx,
116 int playerx, int playery, int& tick) const { 70 int playery, int& tick) {
117 int nx = x; 71 int nx = x;
118 int ny = y; 72 int ny = y;
119 73
@@ -127,545 +81,566 @@ void GameBoard::incrementIfNeighbor(int x, int y, const board_type& temp,
127 } 81 }
128} 82}
129 83
130State* GameState::operator()(SDL_Window* window, SDL_Renderer* renderer) { 84bool applyNeighbors(int x, int y, const board_type& temp, int playerx,
131 return new LoadGameState(0); 85 int playery) {
132} 86 int neighbors = 0;
133
134LoadGameState::LoadGameState(int m_level) { level = m_level; }
135 87
136State* LoadGameState::operator()(SDL_Window* window, SDL_Renderer* renderer) { 88 incrementIfNeighbor(x - 1, y - 1, temp, playerx, playery, neighbors);
137 char* wintitle = new char[50]; 89 incrementIfNeighbor(x - 1, y, temp, playerx, playery, neighbors);
138 sprintf(wintitle, "Maze Of Life - Level %d", level); 90 incrementIfNeighbor(x - 1, y + 1, temp, playerx, playery, neighbors);
139 SDL_SetWindowTitle(window, wintitle); 91 incrementIfNeighbor(x, y - 1, temp, playerx, playery, neighbors);
92 incrementIfNeighbor(x, y + 1, temp, playerx, playery, neighbors);
93 incrementIfNeighbor(x + 1, y - 1, temp, playerx, playery, neighbors);
94 incrementIfNeighbor(x + 1, y, temp, playerx, playery, neighbors);
95 incrementIfNeighbor(x + 1, y + 1, temp, playerx, playery, neighbors);
140 96
141 // Randomly place the player in a corner 97 if (temp[x + y * WIDTH]) {
142 int playerx, playery; 98 return ((neighbors >= 1) && (neighbors <= 4));
143 switch (rand() % 4) { 99 } else {
144 case 0: 100 return (neighbors == 3);
145 playerx = 1;
146 playery = 1;
147 break;
148 case 1:
149 playerx = 1;
150 playery = HEIGHT - 2;
151 break;
152 case 2:
153 playerx = WIDTH - 2;
154 playery = HEIGHT - 2;
155 break;
156 case 3:
157 playerx = WIDTH - 2;
158 playery = 1;
159 break;
160 } 101 }
102}
161 103
162 // Display the level number 104class GameBoard {
163 setRendererDeadColor(renderer, level); 105 public:
164 SDL_RenderClear(renderer); 106 GameBoard(Game& game, int level, int playerx, int playery) {
165 107 for (;;) {
166 TTF_Font* font = loadFont(100); 108 initialize(game, level);
167 SDL_Color fontColor = {0, 0, 0, 0}; 109 updateable_.set();
168 char levelnum[8]; 110 oldx_ = playerx;
169 sprintf(levelnum, "%d", level); 111 oldy_ = playery;
170 SDL_Surface* dispsurf = TTF_RenderText_Solid(font, levelnum, fontColor); 112
171 SDL_Texture* disptext = SDL_CreateTextureFromSurface(renderer, dispsurf); 113 for (int i = 0; i < 50; i++) {
172 SDL_FreeSurface(dispsurf); 114 tick(playerx, playery);
173 115 }
174 SDL_Rect pos;
175 SDL_QueryTexture(disptext, NULL, NULL, &pos.w, &pos.h);
176 pos.x = 240 - (pos.w / 2);
177 pos.y = 240 - (pos.h / 2);
178
179 SDL_RenderCopy(renderer, disptext, NULL, &pos);
180 SDL_RenderPresent(renderer);
181 116
182 // Do 50 gens of Conway 117 if (solve(playerx, playery)) {
183 GameBoard* board = new GameBoard(level, playerx, playery); 118 break;
119 } else {
120 std::cout << "Impossible board: " << playerx << "," << playery << ","
121 << dump() << std::endl;
122 }
123 }
124 }
184 125
185 // Wait a bit 126 void tick(int playerx, int playery) {
186 SDL_Delay(500); 127 board_type temp{blocks_};
128 board_type tempdateable{updateable_};
129 if ((playerx != oldx_) || (playery != oldy_)) {
130 for (int dy = -1; dy <= 1; dy++) {
131 for (int dx = -1; dx <= 1; dx++) {
132 int tdx = oldx_ + dx;
133 int tdy = oldy_ + dy;
134 wrap(tdx, tdy);
135 tempdateable.set(tdx + tdy * WIDTH);
136
137 tdx = playerx + dx;
138 tdy = playery + dy;
139 wrap(tdx, tdy);
140 tempdateable.set(tdx + tdy * WIDTH);
141 }
142 }
143 }
187 144
188 // Start the level 145 oldx_ = playerx;
189 return new PlayGameState(level, board, playerx, playery); 146 oldy_ = playery;
190}
191 147
192PlayGameState::PlayGameState(int m_level, GameBoard* m_board, int m_playerx, 148 updateable_.reset();
193 int m_playery) {
194 level = m_level;
195 board = m_board;
196 playerx = m_playerx;
197 playery = m_playery;
198}
199 149
200State* PlayGameState::operator()(SDL_Window* window, SDL_Renderer* renderer) { 150 for (int y = 0; y < HEIGHT; y++) {
201 SDL_Event e; 151 for (int x = 0; x < WIDTH; x++) {
152 if (((x == 15) && (y == 15)) || (!tempdateable[x + y * WIDTH])) {
153 continue;
154 }
202 155
203 for (;;) { 156 blocks_[x + y * WIDTH] = applyNeighbors(x, y, temp, playerx, playery);
204 // Tick board
205 board->tick(playerx, playery);
206 157
207 // Paint board 158 if (temp[x + y * WIDTH] != blocks_[x + y * WIDTH]) {
208 board->render(renderer, level); 159 for (int dy = -1; dy <= 1; dy++) {
160 for (int dx = -1; dx <= 1; dx++) {
161 int tdx = x + dx;
162 int tdy = y + dy;
163 wrap(tdx, tdy);
164 updateable_.set(tdx + tdy * WIDTH);
165 }
166 }
167 }
168 }
169 }
170 }
209 171
210 // Paint event 172 void render(SDL_Renderer* renderer, int level) const {
211 SDL_Rect block; 173 SDL_Rect block;
212 block.w = 16; 174 block.w = 16;
213 block.h = 16; 175 block.h = 16;
214 block.x = 15 * 16;
215 block.y = 15 * 16;
216 SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
217 SDL_RenderFillRect(renderer, &block);
218
219 // Paint player
220 block.x = playerx * 16;
221 block.y = playery * 16;
222 SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
223 SDL_RenderFillRect(renderer, &block);
224
225 SDL_RenderPresent(renderer);
226
227 while (SDL_PollEvent(&e)) {
228 if (e.type == SDL_QUIT) {
229 return NULL;
230 } else if (e.type == SDL_KEYDOWN) {
231 switch (e.key.keysym.sym) {
232 case SDLK_LEFT:
233 if (move(playerx - 1, playery)) {
234 return new LoadGameState(level + 1);
235 } else {
236 break;
237 }
238 176
239 case SDLK_RIGHT: 177 for (int y = 0; y < HEIGHT; y++) {
240 if (move(playerx + 1, playery)) { 178 for (int x = 0; x < WIDTH; x++) {
241 return new LoadGameState(level + 1); 179 block.x = x * 16;
242 } else { 180 block.y = y * 16;
243 break;
244 }
245 181
246 case SDLK_UP: 182 if (blocks_[x + y * WIDTH]) {
247 if (move(playerx, playery - 1)) { 183 setRendererAliveColor(renderer, level);
248 return new LoadGameState(level + 1); 184 } else {
249 } else { 185 setRendererDeadColor(renderer, level);
250 break; 186 }
251 }
252
253 case SDLK_DOWN:
254 if (move(playerx, playery + 1)) {
255 return new LoadGameState(level + 1);
256 } else {
257 break;
258 }
259
260 case SDLK_ESCAPE:
261 SDL_SetWindowTitle(window, "");
262
263 std::ifstream exists(getDataFile());
264 if (exists) {
265 FILE* hslist = fopen(getDataFile(), "r");
266 int scores;
267 Highscore* h;
268
269 fscanf(hslist, "%d%*c", &scores);
270 187
271 if (scores < 10) { 188 SDL_RenderFillRect(renderer, &block);
272 fclose(hslist); 189 }
190 }
191 }
273 192
274 return new EnterHighscoreState(level); 193 bool isObstructed(int x, int y) const {
275 } else { 194 return blocks_[x + y * WIDTH] || (x == 15 && y == 15);
276 for (int i = 0; i < scores; i++) { 195 }
277 int namelen;
278 char namelens[4];
279 char* name = (char*)calloc(25, sizeof(char));
280 int score;
281 196
282 fscanf(hslist, "%d", &namelen); 197 bool operator<(const GameBoard& other) const {
283 sprintf(namelens, "%%%dc", namelen); 198 for (int i = WIDTH * HEIGHT - 1; i >= 0; i--) {
284 fscanf(hslist, namelens, name); 199 if (blocks_[i] ^ other.blocks_[i]) {
285 fscanf(hslist, "%d%*c", &score); 200 return other.blocks_[i];
201 }
202 }
286 203
287 h = new Highscore(name, score); 204 return false;
288 } 205 }
289 206
290 fclose(hslist); 207 using coord = std::tuple<int, int>;
291 208
292 if (h->getLevel() < level) { 209 private:
293 return new EnterHighscoreState(level); 210 void initialize(Game& game, int level) {
294 } else { 211 for (int y = 0; y < HEIGHT; y++) {
295 return new DisplayAndReturnLocalHighscoreListState(); 212 for (int x = 0; x < WIDTH; x++) {
296 } 213 blocks_[x + y * WIDTH] = false;
297 } 214
298 } else { 215 switch (level / 10 + 1) {
299 return new EnterHighscoreState(level); 216 case 1:
217 if ((x > 13) && (x < 17) && (y > 13) && (y < 17)) {
218 blocks_[x + y * WIDTH] =
219 std::bernoulli_distribution(0.5)(game.rng);
300 } 220 }
221 break;
222 case 2:
223 case 3:
224 if ((x > 12) && (x < 18) && (y > 12) && (y < 18)) {
225 blocks_[x + y * WIDTH] =
226 std::bernoulli_distribution(0.5)(game.rng);
227 }
228 break;
229 case 4:
230 case 5:
231 if ((x > 11) && (x < 19) && (y > 11) && (y < 19)) {
232 blocks_[x + y * WIDTH] =
233 std::bernoulli_distribution(0.5)(game.rng);
234 }
235 break;
236 default:
237 blocks_[x + y * WIDTH] = std::bernoulli_distribution(0.5)(game.rng);
301 } 238 }
302 } 239 }
303 } 240 }
304 241
305 SDL_Delay(5); 242 blocks_[15 + 15 * WIDTH] = false;
306 } 243 }
307}
308 244
309bool PlayGameState::move(int x, int y) { 245 bool solve(int playerx, int playery) const {
310 wrap(x, y); 246 std::deque<std::tuple<GameBoard, coord, int>> search;
247 std::unordered_map<board_type, board_type> done;
311 248
312 // Are we at the event? 249 // Assume that the player will not move while the board is changing, so tick
313 if ((x == 15) && (y == 15)) { 250 // the board until it either stops changing, or it reaches a state that it
314 return true; 251 // has already been in (in the case of alternating systems).
315 } 252 {
253 GameBoard original = *this;
316 254
317 // Can we even go there? 255 std::unordered_set<board_type> pastStates;
318 if (!board->isObstructed(x, y)) { 256 pastStates.insert(original.blocks_);
319 playerx = x;
320 playery = y;
321 }
322
323 return false;
324}
325 257
326GameBoard::GameBoard(int level, int playerx, int playery) { 258 while (original.updateable_.any()) {
327 for (;;) { 259 original.tick(playerx, playery);
328 initialize(level);
329 updateable.set();
330 oldx = playerx;
331 oldy = playery;
332 260
333 for (int i = 0; i < 50; i++) { 261 if (pastStates.count(original.blocks_)) {
334 tick(playerx, playery); 262 break;
335 } 263 }
336
337 if (solve(playerx, playery)) {
338 break;
339 } else {
340 std::cout << "Impossible board: " << playerx << "," << playery << ","
341 << dump() << std::endl;
342 }
343 }
344}
345 264
346void GameBoard::tick(int playerx, int playery) { 265 pastStates.insert(original.blocks_);
347 board_type temp{blocks};
348 board_type tempdateable{updateable};
349 if ((playerx != oldx) || (playery != oldy)) {
350 for (int dy = -1; dy <= 1; dy++) {
351 for (int dx = -1; dx <= 1; dx++) {
352 int tdx = oldx + dx;
353 int tdy = oldy + dy;
354 wrap(tdx, tdy);
355 tempdateable.set(tdx + tdy * WIDTH);
356
357 tdx = playerx + dx;
358 tdy = playery + dy;
359 wrap(tdx, tdy);
360 tempdateable.set(tdx + tdy * WIDTH);
361 } 266 }
267
268 search.emplace_front(std::move(original), coord{playerx, playery}, 0);
362 } 269 }
363 }
364 270
365 oldx = playerx; 271 // Use breadth first search to find a solution.
366 oldy = playery; 272 bool exists = false;
273 while (!search.empty()) {
274 auto cur = std::move(search.front());
275 search.pop_front();
367 276
368 updateable.reset(); 277 GameBoard& cbr = std::get<0>(cur);
278 coord& cpl = std::get<1>(cur);
279 int cns = std::get<2>(cur);
369 280
370 for (int y = 0; y < HEIGHT; y++) { 281 // If it has been over 100 generations, give up.
371 for (int x = 0; x < WIDTH; x++) { 282 if (cns > 100) {
372 if (((x == 15) && (y == 15)) || (!tempdateable[x + y * WIDTH])) {
373 continue; 283 continue;
374 } 284 }
375 285
376 blocks[x + y * WIDTH] = applyNeighbors(x, y, temp, playerx, playery); 286 int cplx = std::get<0>(cpl);
287 int cply = std::get<1>(cpl);
377 288
378 if (temp[x + y * WIDTH] != blocks[x + y * WIDTH]) { 289 // If this section of this board state has already been checked, skip it.
379 for (int dy = -1; dy <= 1; dy++) { 290 if (done.count(cbr.blocks_) &&
380 for (int dx = -1; dx <= 1; dx++) { 291 done.at(cbr.blocks_)[cplx + cply * WIDTH]) {
381 int tdx = x + dx; 292 continue;
382 int tdy = y + dy;
383 wrap(tdx, tdy);
384 updateable.set(tdx + tdy * WIDTH);
385 }
386 }
387 } 293 }
388 }
389 }
390}
391
392bool GameBoard::applyNeighbors(int x, int y, const board_type& temp,
393 int playerx, int playery) const {
394 int neighbors = 0;
395 294
396 incrementIfNeighbor(x - 1, y - 1, temp, playerx, playery, neighbors); 295 // Use a flood fill to find a set of positions accessible to the player
397 incrementIfNeighbor(x - 1, y, temp, playerx, playery, neighbors); 296 // without modifying the board state, as well as a set of positions
398 incrementIfNeighbor(x - 1, y + 1, temp, playerx, playery, neighbors); 297 // adjacent to the flood that /will/ modify the board state.
399 incrementIfNeighbor(x, y - 1, temp, playerx, playery, neighbors); 298 board_type flood;
400 incrementIfNeighbor(x, y + 1, temp, playerx, playery, neighbors); 299 std::deque<coord> front;
401 incrementIfNeighbor(x + 1, y - 1, temp, playerx, playery, neighbors); 300 front.push_front(cpl);
402 incrementIfNeighbor(x + 1, y, temp, playerx, playery, neighbors); 301 flood[cplx + cply * WIDTH] = true;
403 incrementIfNeighbor(x + 1, y + 1, temp, playerx, playery, neighbors); 302
303 std::set<coord> edges;
304
305 while (!front.empty()) {
306 coord frontLoc = std::move(front.front());
307 front.pop_front();
308
309 // Iterate over the positions 4-adjacent to the current one.
310 for (coord& fc : std::list<coord>{
311 {std::get<0>(frontLoc) - 1, std::get<1>(frontLoc)},
312 {std::get<0>(frontLoc) + 1, std::get<1>(frontLoc)},
313 {std::get<0>(frontLoc), std::get<1>(frontLoc) - 1},
314 {std::get<0>(frontLoc), std::get<1>(frontLoc) + 1},
315 }) {
316 wrap(std::get<0>(fc), std::get<1>(fc));
317 int fcx = std::get<0>(fc);
318 int fcy = std::get<1>(fc);
319
320 // If this position is already in the flood, skip it.
321 if (flood[fcx + fcy * WIDTH]) {
322 continue;
323 }
404 324
405 if (temp[x + y * WIDTH]) { 325 // If the player could not move into this position, skip it.
406 return ((neighbors >= 1) && (neighbors <= 4)); 326 if (cbr.isObstructed(fcx, fcy)) {
407 } else { 327 continue;
408 return (neighbors == 3); 328 }
409 }
410}
411 329
412void GameBoard::render(SDL_Renderer* renderer, int level) const { 330 // If this position is adjacent to the event, then the board is
413 SDL_Rect block; 331 // solvable.
414 block.w = 16; 332 if (((fcx == 15) && ((fcy == 14) || (fcy == 16))) ||
415 block.h = 16; 333 ((fcy == 15) && ((fcx == 14) || (fcx == 16)))) {
334 exists = true;
335 break;
336 }
416 337
417 for (int y = 0; y < HEIGHT; y++) { 338 // Check if the player moving would cause any positions 8-adjacent to
418 for (int x = 0; x < WIDTH; x++) { 339 // the start or end positions to change. This is more efficient than
419 block.x = x * 16; 340 // copying the board state and then running tick.
420 block.y = y * 16; 341 bool changed = false;
342 for (int dy = -1; dy <= 1; dy++) {
343 for (int dx = -1; dx <= 1; dx++) {
344 if (dx == 0 && dy == 0) {
345 continue;
346 }
421 347
422 if (blocks[x + y * WIDTH]) { 348 int cpldx = cplx + dx;
423 setRendererAliveColor(renderer, level); 349 int cpldy = cply + dy;
424 } else { 350 wrap(cpldx, cpldy);
425 setRendererDeadColor(renderer, level);
426 }
427 351
428 SDL_RenderFillRect(renderer, &block); 352 if (cbr.isObstructed(cpldx, cpldy) !=
429 } 353 applyNeighbors(cpldx, cpldy, cbr.blocks_, fcx, fcy)) {
430 } 354 changed = true;
431} 355 break;
356 }
432 357
433bool GameBoard::isObstructed(int x, int y) const { 358 int fcxdx = fcx + dx;
434 return blocks[x + y * WIDTH] || (x == 15 && y == 15); 359 int fcydy = fcy + dy;
435} 360 wrap(fcxdx, fcydy);
436 361
437void GameBoard::initialize(int level) { 362 if (cbr.isObstructed(fcxdx, fcydy) !=
438 for (int y = 0; y < HEIGHT; y++) { 363 applyNeighbors(fcxdx, fcydy, cbr.blocks_, fcx, fcy)) {
439 for (int x = 0; x < WIDTH; x++) { 364 changed = true;
440 blocks[x + y * WIDTH] = false; 365 break;
366 }
367 }
441 368
442 switch (level / 10 + 1) { 369 if (changed) {
443 case 1: 370 break;
444 if ((x > 13) && (x < 17) && (y > 13) && (y < 17)) { 371 }
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 blocks[x + y * WIDTH] = rand() % 2;
452 } 372 }
453 break; 373
454 case 4: 374 // If moving to this position would change the board state, add it to
455 case 5: 375 // the set of edges; otherwise, add it to the flood and the flood
456 if ((x > 11) && (x < 19) && (y > 11) && (y < 19)) { 376 // front.
457 blocks[x + y * WIDTH] = rand() % 2; 377 if (changed) {
378 edges.insert(fc);
379 } else {
380 flood[fcx + fcy * WIDTH] = true;
381 front.push_back(fc);
458 } 382 }
383 }
384
385 if (exists) {
459 break; 386 break;
460 default: 387 }
461 blocks[x + y * WIDTH] = rand() % 2;
462 } 388 }
463 }
464 }
465 389
466 blocks[15 + 15 * WIDTH] = false; 390 if (exists) {
467} 391 break;
392 }
468 393
469bool GameBoard::operator<(const GameBoard& other) const { 394 // Add the flood to the set of checked positions for this board state.
470 for (int i = WIDTH * HEIGHT - 1; i >= 0; i--) { 395 done[cbr.blocks_] |= flood;
471 if (blocks[i] ^ other.blocks[i]) {
472 return other.blocks[i];
473 }
474 }
475 396
476 return false; 397 // Add the edges to the search queue.
477} 398 for (const coord& newLoc : edges) {
399 GameBoard nextState1 = cbr;
400 nextState1.tick(std::get<0>(newLoc), std::get<1>(newLoc));
478 401
479bool GameBoard::solve(int playerx, int playery) const { 402 // Assume that the player will not move while the board is changing, so
480 std::deque<std::tuple<GameBoard, coord, int>> search; 403 // tick the board until it either stops changing, or it reaches a state
481 std::unordered_map<board_type, board_type> done; 404 // that it has already been in (in the case of alternating systems).
405 std::unordered_set<board_type> pastStates;
406 pastStates.insert(nextState1.blocks_);
482 407
483 // Assume that the player will not move while the board is changing, so tick 408 while (nextState1.updateable_.any()) {
484 // the board until it either stops changing, or it reaches a state that it has 409 nextState1.tick(std::get<0>(newLoc), std::get<1>(newLoc));
485 // already been in (in the case of alternating systems).
486 {
487 GameBoard original = *this;
488 410
489 std::unordered_set<board_type> pastStates; 411 if (pastStates.count(nextState1.blocks_)) {
490 pastStates.insert(original.blocks); 412 break;
413 }
491 414
492 while (original.updateable.any()) { 415 pastStates.insert(nextState1.blocks_);
493 original.tick(playerx, playery); 416 }
494 417
495 if (pastStates.count(original.blocks)) { 418 if (!done.count(nextState1.blocks_) ||
496 break; 419 !done.at(nextState1.blocks_)[std::get<0>(newLoc) +
420 std::get<1>(newLoc) * WIDTH]) {
421 search.emplace_back(std::move(nextState1), newLoc, cns + 1);
422 }
497 } 423 }
424 }
498 425
499 pastStates.insert(original.blocks); 426 return exists;
427 }
428
429 std::string dump() const {
430 std::stringstream output;
431 output << std::hex;
432 for (int i = 0; i < WIDTH * HEIGHT / 4; i++) {
433 int chunk = (8 * blocks_[i * 4]) + (4 * blocks_[i * 4 + 1]) +
434 (2 * blocks_[i * 4 + 2]) + blocks_[i * 4 + 3];
435 output << chunk;
500 } 436 }
501 437
502 search.emplace_front(std::move(original), coord{playerx, playery}, 0); 438 return output.str();
503 } 439 }
504 440
505 // Use breadth first search to find a solution. 441 board_type blocks_;
506 bool exists = false; 442 board_type updateable_;
507 while (!search.empty()) { 443 int oldx_;
508 auto cur = std::move(search.front()); 444 int oldy_;
509 search.pop_front(); 445};
510 446
511 GameBoard& cbr = std::get<0>(cur); 447std::unique_ptr<State> startNewLevel(int level,
512 coord& cpl = std::get<1>(cur); 448 std::unique_ptr<GameBoard> board,
513 int cns = std::get<2>(cur); 449 int playerx, int playery);
514 450
515 // If it has been over 100 generations, give up. 451class LoadGameState : public State {
516 if (cns > 100) { 452 public:
517 continue; 453 LoadGameState(int level) : level_(level) {}
454
455 std::unique_ptr<State> operator()(Game& game) {
456 std::ostringstream wintitle;
457 wintitle << "Maze Of Life - Level " << level_;
458 SDL_SetWindowTitle(game.window.get(), wintitle.str().c_str());
459
460 // Randomly place the player in a corner
461 int playerx, playery;
462 switch (std::uniform_int_distribution(0, 3)(game.rng)) {
463 case 0: {
464 playerx = 1;
465 playery = 1;
466 break;
467 }
468 case 1: {
469 playerx = 1;
470 playery = HEIGHT - 2;
471 break;
472 }
473 case 2: {
474 playerx = WIDTH - 2;
475 playery = HEIGHT - 2;
476 break;
477 }
478 case 3: {
479 playerx = WIDTH - 2;
480 playery = 1;
481 break;
482 }
518 } 483 }
519 484
520 int cplx = std::get<0>(cpl); 485 // Display the level number
521 int cply = std::get<1>(cpl); 486 setRendererDeadColor(game.renderer.get(), level_);
487 SDL_RenderClear(game.renderer.get());
522 488
523 // If this section of this board state has already been checked, skip it. 489 font_ptr font = loadFont(100);
524 if (done.count(cbr.blocks) && done.at(cbr.blocks)[cplx + cply * WIDTH]) { 490 SDL_Color fontColor = {0, 0, 0, 0};
525 continue; 491 std::string levelnum = std::to_string(level_);
526 } 492 surface_ptr dispsurf = surface_ptr(
493 TTF_RenderText_Solid(font.get(), levelnum.c_str(), fontColor));
494 texture_ptr disptext = texture_ptr(
495 SDL_CreateTextureFromSurface(game.renderer.get(), dispsurf.get()));
527 496
528 // Use a flood fill to find a set of positions accessible to the player 497 SDL_Rect pos;
529 // without modifying the board state, as well as a set of positions adjacent 498 SDL_QueryTexture(disptext.get(), NULL, NULL, &pos.w, &pos.h);
530 // to the flood that /will/ modify the board state. 499 pos.x = 240 - (pos.w / 2);
531 board_type flood; 500 pos.y = 240 - (pos.h / 2);
532 std::deque<coord> front;
533 front.push_front(cpl);
534 flood[cplx + cply * WIDTH] = true;
535
536 std::set<coord> edges;
537
538 while (!front.empty()) {
539 coord frontLoc = std::move(front.front());
540 front.pop_front();
541
542 // Iterate over the positions 4-adjacent to the current one.
543 for (coord& fc : std::list<coord>{
544 {std::get<0>(frontLoc) - 1, std::get<1>(frontLoc)},
545 {std::get<0>(frontLoc) + 1, std::get<1>(frontLoc)},
546 {std::get<0>(frontLoc), std::get<1>(frontLoc) - 1},
547 {std::get<0>(frontLoc), std::get<1>(frontLoc) + 1},
548 }) {
549 wrap(std::get<0>(fc), std::get<1>(fc));
550 int fcx = std::get<0>(fc);
551 int fcy = std::get<1>(fc);
552
553 // If this position is already in the flood, skip it.
554 if (flood[fcx + fcy * WIDTH]) {
555 continue;
556 }
557 501
558 // If the player could not move into this position, skip it. 502 SDL_RenderCopy(game.renderer.get(), disptext.get(), NULL, &pos);
559 if (cbr.isObstructed(fcx, fcy)) { 503 SDL_RenderPresent(game.renderer.get());
560 continue;
561 }
562 504
563 // If this position is adjacent to the event, then the board is 505 // Do 50 gens of Conway
564 // solvable. 506 std::unique_ptr<GameBoard> board =
565 if (((fcx == 15) && ((fcy == 14) || (fcy == 16))) || 507 std::make_unique<GameBoard>(game, level_, playerx, playery);
566 ((fcy == 15) && ((fcx == 14) || (fcx == 16)))) {
567 exists = true;
568 break;
569 }
570 508
571 // Check if the player moving would cause any positions 8-adjacent to 509 // Wait a bit
572 // the start or end positions to change. This is more efficient than 510 SDL_Delay(500);
573 // copying the board state and then running tick.
574 bool changed = false;
575 for (int dy = -1; dy <= 1; dy++) {
576 for (int dx = -1; dx <= 1; dx++) {
577 if (dx == 0 && dy == 0) {
578 continue;
579 }
580 511
581 int cpldx = cplx + dx; 512 // Start the level
582 int cpldy = cply + dy; 513 return startNewLevel(level_, std::move(board), playerx, playery);
583 wrap(cpldx, cpldy); 514 }
584 515
585 if (cbr.isObstructed(cpldx, cpldy) != 516 private:
586 applyNeighbors(cpldx, cpldy, cbr.blocks, fcx, fcy)) { 517 int level_;
587 changed = true; 518};
588 break;
589 }
590 519
591 int fcxdx = fcx + dx; 520class PlayGameState : public State {
592 int fcydy = fcy + dy; 521 public:
593 wrap(fcxdx, fcydy); 522 PlayGameState(int level, std::unique_ptr<GameBoard> board, int playerx,
523 int playery)
524 : level_(level),
525 board_(std::move(board)),
526 playerx_(playerx),
527 playery_(playery) {}
594 528
595 if (cbr.isObstructed(fcxdx, fcydy) != 529 std::unique_ptr<State> operator()(Game& game) {
596 applyNeighbors(fcxdx, fcydy, cbr.blocks, fcx, fcy)) { 530 SDL_Event e;
597 changed = true; 531
598 break; 532 // Tick board
599 } 533 board_->tick(playerx_, playery_);
534
535 // Paint board
536 board_->render(game.renderer.get(), level_);
537
538 // Paint event
539 SDL_Rect block;
540 block.w = 16;
541 block.h = 16;
542 block.x = 15 * 16;
543 block.y = 15 * 16;
544 SDL_SetRenderDrawColor(game.renderer.get(), 0, 0, 255, 255);
545 SDL_RenderFillRect(game.renderer.get(), &block);
546
547 // Paint player
548 block.x = playerx_ * 16;
549 block.y = playery_ * 16;
550 SDL_SetRenderDrawColor(game.renderer.get(), 255, 255, 0, 255);
551 SDL_RenderFillRect(game.renderer.get(), &block);
552
553 SDL_RenderPresent(game.renderer.get());
554
555 while (SDL_PollEvent(&e)) {
556 if (e.type == SDL_QUIT) {
557 game.should_quit = true;
558
559 return nullptr;
560 } else if (e.type == SDL_KEYDOWN) {
561 bool trymove = false;
562 int to_x = playerx_;
563 int to_y = playery_;
564
565 switch (e.key.keysym.sym) {
566 case SDLK_LEFT: {
567 trymove = true;
568 to_x--;
569 break;
600 } 570 }
571 case SDLK_RIGHT: {
572 trymove = true;
573 to_x++;
574 break;
575 }
576 case SDLK_UP: {
577 trymove = true;
578 to_y--;
579 break;
580 }
581 case SDLK_DOWN: {
582 trymove = true;
583 to_y++;
584 break;
585 }
586 case SDLK_ESCAPE: {
587 SDL_SetWindowTitle(game.window.get(), "");
588
589 std::unique_ptr<HighscoreList> hslist =
590 HighscoreList::GetLocalHighscores();
591 if (hslist->addHighscore(Highscore("", level_)) <= 10) {
592 return std::make_unique<EnterHighscoreState>(game, level_);
593 } else {
594 return std::make_unique<DisplayAndReturnLocalHighscoreListState>(
595 game);
596 }
601 597
602 if (changed) {
603 break; 598 break;
604 } 599 }
605 } 600 }
606 601
607 // If moving to this position would change the board state, add it to 602 if (trymove && move(to_x, to_y)) {
608 // the set of edges; otherwise, add it to the flood and the flood front. 603 return std::make_unique<LoadGameState>(level_ + 1);
609 if (changed) {
610 edges.insert(fc);
611 } else {
612 flood[fcx + fcy * WIDTH] = true;
613 front.push_back(fc);
614 } 604 }
615 } 605 }
616
617 if (exists) {
618 break;
619 }
620 }
621
622 if (exists) {
623 break;
624 } 606 }
625 607
626 // Add the flood to the set of checked positions for this board state. 608 SDL_Delay(5);
627 done[cbr.blocks] |= flood;
628
629 // Add the edges to the search queue.
630 for (const coord& newLoc : edges) {
631 GameBoard nextState1 = cbr;
632 nextState1.tick(std::get<0>(newLoc), std::get<1>(newLoc));
633
634 // Assume that the player will not move while the board is changing, so
635 // tick the board until it either stops changing, or it reaches a state
636 // that it has already been in (in the case of alternating systems).
637 std::unordered_set<board_type> pastStates;
638 pastStates.insert(nextState1.blocks);
639 609
640 while (nextState1.updateable.any()) { 610 return nullptr;
641 nextState1.tick(std::get<0>(newLoc), std::get<1>(newLoc)); 611 }
642 612
643 if (pastStates.count(nextState1.blocks)) { 613 private:
644 break; 614 bool move(int x, int y) {
645 } 615 wrap(x, y);
646 616
647 pastStates.insert(nextState1.blocks); 617 // Are we at the event?
648 } 618 if ((x == 15) && (y == 15)) {
619 return true;
620 }
649 621
650 if (!done.count(nextState1.blocks) || 622 // Can we even go there?
651 !done.at(nextState1.blocks)[std::get<0>(newLoc) + 623 if (!board_->isObstructed(x, y)) {
652 std::get<1>(newLoc) * WIDTH]) { 624 playerx_ = x;
653 search.emplace_back(std::move(nextState1), newLoc, cns + 1); 625 playery_ = y;
654 }
655 } 626 }
627
628 return false;
656 } 629 }
657 630
658 return exists; 631 int level_;
659} 632 std::unique_ptr<GameBoard> board_;
633 int playerx_;
634 int playery_;
635};
660 636
661std::string GameBoard::dump() const { 637std::unique_ptr<State> startNewLevel(int level,
662 std::stringstream output; 638 std::unique_ptr<GameBoard> board,
663 output << std::hex; 639 int playerx, int playery) {
664 for (int i = 0; i < WIDTH * HEIGHT / 4; i++) { 640 return std::make_unique<PlayGameState>(level, std::move(board), playerx,
665 int chunk = (8 * blocks[i * 4]) + (4 * blocks[i * 4 + 1]) + 641 playery);
666 (2 * blocks[i * 4 + 2]) + blocks[i * 4 + 3]; 642}
667 output << chunk;
668 }
669 643
670 return output.str(); 644std::unique_ptr<State> GameState::operator()(Game&) {
645 return std::make_unique<LoadGameState>(0);
671} 646}