summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2022-03-13 13:21:17 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2022-03-13 13:21:17 -0400
commit016d1cdf036039792f50e1ed0431386c7b9e93d7 (patch)
treee5f773f34b05c4fd8c73247eae9d20514fa450df
parentb2dfe21775a55e815b2572d844c749c5108671fe (diff)
downloadether-016d1cdf036039792f50e1ed0431386c7b9e93d7.tar.gz
ether-016d1cdf036039792f50e1ed0431386c7b9e93d7.tar.bz2
ether-016d1cdf036039792f50e1ed0431386c7b9e93d7.zip
refactored game code into the game class (for titles / multiple games)
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/game.cpp885
-rw-r--r--src/game.h56
-rw-r--r--src/main.cpp920
-rw-r--r--src/renderer.cpp2
-rw-r--r--src/renderer.h2
-rw-r--r--src/timer.h2
7 files changed, 942 insertions, 926 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 360d4a2..5f07e97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -33,6 +33,7 @@ add_executable(Ether
33 src/renderer.cpp 33 src/renderer.cpp
34 src/muxer.cpp 34 src/muxer.cpp
35 src/animation.cpp 35 src/animation.cpp
36 src/game.cpp
36 vendor/fov.c 37 vendor/fov.c
37) 38)
38 39
diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..8de9b7b --- /dev/null +++ b/src/game.cpp
@@ -0,0 +1,885 @@
1#include "game.h"
2#include <vector>
3#include <fov.h>
4#include <iostream>
5#include "util.h"
6#include "renderer.h"
7
8Game::Game(std::mt19937& rng, Muxer& muxer) :
9 rng(rng),
10 muxer(muxer),
11 map(
12 -INIT_ZOOM * ZOOM_X_FACTOR / 2,
13 -INIT_ZOOM * ZOOM_Y_FACTOR / 2,
14 INIT_ZOOM * ZOOM_X_FACTOR,
15 INIT_ZOOM * ZOOM_Y_FACTOR)
16{
17 losePopLampTimer.accumulate(losePopLampTimer.getDt());
18
19 for (MapData& md : map.data())
20 {
21 if (std::bernoulli_distribution(0.5)(rng))
22 {
23 md.tile = Tile::Wall;
24 }
25 }
26
27 tick();
28 tick();
29
30 for (int y = -1; y <= 1; y++)
31 {
32 for (int x = -1; x <= 1; x++)
33 {
34 map.at(x,y).tile = Tile::Floor;
35 }
36 }
37
38 tick();
39}
40
41inline bool isTileSetOrNotLit(const Map<MapData>& map, int x, int y)
42{
43 return (map.inBounds(x, y) && (map.at(x,y).tile == Tile::Wall || !map.at(x,y).lit));
44}
45
46inline bool isTileSet(const Map<MapData>& map, int x, int y, Tile val = Tile::Wall)
47{
48 return (map.inBounds(x, y) && map.at(x,y).tile == val);
49}
50
51inline void incrementIfSet(const Game& game, int& count, int x, int y, Tile val = Tile::Wall)
52{
53 if (isTileSet(game.map, x, y, val))
54 {
55 count++;
56 }
57}
58
59inline int getZoomLevel(const Game& game) {
60 return game.curZoom - INIT_ZOOM;
61}
62
63void Game::tick(
64 int x1,
65 int y1,
66 int x2,
67 int y2,
68 bool invert,
69 bool onlyDark)
70{
71 Map<MapData> temp(map);
72
73 for (int y = map.getTop(); y < map.getBottom(); y++)
74 {
75 for (int x = map.getLeft(); x < map.getRight(); x++)
76 {
77 if (invert == (x >= x1 && x < x2 && y >= y1 && y < y2))
78 {
79 continue;
80 }
81
82 if (onlyDark && map.at(x,y).lit)
83 {
84 continue;
85 }
86
87 if (map.at(x,y).tile == Tile::Lamp)
88 {
89 continue;
90 }
91
92 int count = 0;
93
94 incrementIfSet(*this, count, x-1, y-1);
95 incrementIfSet(*this, count, x-1, y );
96 incrementIfSet(*this, count, x-1, y+1);
97 incrementIfSet(*this, count, x , y-1);
98 incrementIfSet(*this, count, x , y );
99 incrementIfSet(*this, count, x , y+1);
100 incrementIfSet(*this, count, x+1, y-1);
101 incrementIfSet(*this, count, x+1, y );
102 incrementIfSet(*this, count, x+1, y+1);
103
104 if (count >= 5)
105 {
106 temp.at(x,y).tile = Tile::Wall;
107 } else {
108 temp.at(x,y).tile = Tile::Floor;
109 }
110
111 if (temp.at(x,y).tile != map.at(x,y).tile) {
112 temp.at(x,y).dirtyRender = true;
113 dirtyRender = true;
114 }
115 }
116 }
117
118 map = std::move(temp);
119}
120
121void Game::tick(bool onlyDark)
122{
123 tick(
124 map.getLeft(),
125 map.getTop(),
126 map.getRight(),
127 map.getBottom(),
128 false,
129 onlyDark);
130}
131
132bool Game::movePlayer(int x, int y)
133{
134 if (x >= curBoundX &&
135 y >= curBoundY &&
136 x < curBoundX + curZoom * ZOOM_X_FACTOR &&
137 y < curBoundY + curZoom * ZOOM_Y_FACTOR &&
138 map.at(x,y).tile == Tile::Floor)
139 {
140 if (map.at(player_x, player_y).tile == Tile::Floor)
141 {
142 map.at(player_x, player_y).tile = Tile::Dust;
143 map.at(player_x, player_y).dustLife = 1;
144 numDust++;
145 }
146
147 player_oldx = player_x;
148 player_oldy = player_y;
149 player_x = x;
150 player_y = y;
151 muxer.setPlayerLoc(x, y);
152 moving = true;
153 moveProgress.start(66);
154 dirtyLighting = true;
155
156 return true;
157 } else {
158 if (!alreadyBumped) {
159 muxer.playSoundAtPosition("bump", x, y);
160 alreadyBumped = true;
161 bumpCooldown.reset();
162 }
163
164 return false;
165 }
166}
167
168void Game::recalculateLighting()
169{
170 litSpots = 0;
171 dirtyRender = true;
172
173 for (MapData& md : map.data())
174 {
175 md.wasLit = md.lit;
176 md.lit = false;
177 md.litTiles.clear();
178
179 if (md.tile == Tile::Wall) {
180 md.dirtyRender = true;
181 }
182 }
183
184 fov_settings_type fov;
185 fov_settings_init(&fov);
186
187 fov_settings_set_opacity_test_function(
188 &fov,
189 [] (void* data, int x, int y) {
190 Game& game = *static_cast<Game*>(data);
191
192 return game.map.inBounds(x,y) && game.map.at(x,y).tile == Tile::Wall;
193 });
194
195 fov_settings_set_apply_lighting_function(
196 &fov,
197 [] (void* data, int x, int y, int dx, int dy, void* source) {
198 Game& game = *static_cast<Game*>(data);
199
200 if (game.map.inBounds(x, y))
201 {
202 MapData& sourceData = *static_cast<MapData*>(source);
203 double lightRadius = static_cast<double>(sourceData.lightRadius);
204
205 if (!game.map.at(x,y).lit)
206 {
207 game.litSpots++;
208 }
209
210 game.map.at(x,y).lit = true;
211
212 sourceData.litTiles.emplace(x,y);
213 }
214 });
215
216 for (int y = map.getTop(); y < map.getBottom(); y++)
217 {
218 for (int x = map.getLeft(); x < map.getRight(); x++)
219 {
220 Source ls = Source::None;
221 int lightRadius;
222
223 if (renderPlayer && player_x == x && player_y == y)
224 {
225 ls = Source::Player;
226 lightRadius = RADIUS;
227 } else if (map.at(x,y).tile == Tile::Dust)
228 {
229 ls = Source::Dust;
230 lightRadius = 2;
231 } else if (map.at(x,y).tile == Tile::Lamp)
232 {
233 ls = Source::Lamp;
234 lightRadius = RADIUS;
235 }
236
237 map.at(x,y).lightType = ls;
238
239 if (ls != Source::None)
240 {
241 map.at(x,y).lightRadius = lightRadius;
242 map.at(x,y).litTiles.emplace(x,y);
243
244 fov_circle(
245 &fov,
246 static_cast<void*>(this),
247 static_cast<void*>(&map.at(x,y)),
248 x,
249 y,
250 lightRadius);
251
252 map.at(x,y).lit = true;
253 }
254 }
255 }
256
257 dirtyLighting = false;
258}
259
260void Game::recalculateRender() {
261 for (int y = map.getTop(); y < map.getBottom(); y++)
262 {
263 for (int x = map.getLeft(); x < map.getRight(); x++)
264 {
265 if (map.at(x,y).dirtyRender) {
266 map.at(x,y).dirtyRender = false;
267
268 if (map.at(x,y).tile == Tile::Floor) {
269 if (std::bernoulli_distribution(0.05)(rng)) {
270 static const std::vector<int> furnishings {
271 TilesetIndex(20, 16),
272 TilesetIndex(21, 2),
273 TilesetIndex(22, 2),
274 TilesetIndex(21, 3),
275 TilesetIndex(22, 3)};
276 map.at(x,y).renderId = furnishings.at(std::uniform_int_distribution<int>(0, furnishings.size()-1)(rng));
277 } else {
278 map.at(x,y).renderId = -1;
279 }
280 } else if (map.at(x,y).tile == Tile::Wall) {
281 static bool initWalls = false;
282 static std::vector<int> wallRenders(256, TilesetIndex(0, 0));
283 if (!initWalls) {
284 initWalls = true;
285
286 // Start in top left and go clockwise.
287 wallRenders[0b00011100] = TilesetIndex(16, 14);
288 wallRenders[0b00111100] = TilesetIndex(16, 14);
289 wallRenders[0b00011110] = TilesetIndex(16, 14);
290 wallRenders[0b00111110] = TilesetIndex(16, 14);
291
292 wallRenders[0b00011111] = TilesetIndex(17, 14);
293 wallRenders[0b10011111] = TilesetIndex(17, 14);
294 wallRenders[0b00111111] = TilesetIndex(17, 14);
295 wallRenders[0b10111111] = TilesetIndex(17, 14);
296
297 wallRenders[0b00000111] = TilesetIndex(18, 14);
298 wallRenders[0b00001111] = TilesetIndex(18, 14);
299 wallRenders[0b10000111] = TilesetIndex(18, 14);
300 wallRenders[0b10001111] = TilesetIndex(18, 14);
301
302 wallRenders[0b01111100] = TilesetIndex(16, 15);
303 wallRenders[0b11111100] = TilesetIndex(16, 15);
304 wallRenders[0b01111110] = TilesetIndex(16, 15);
305 wallRenders[0b11111110] = TilesetIndex(16, 15);
306
307 wallRenders[0b11000111] = TilesetIndex(18, 15);
308 wallRenders[0b11001111] = TilesetIndex(18, 15);
309 wallRenders[0b11100111] = TilesetIndex(18, 15);
310 wallRenders[0b11101111] = TilesetIndex(18, 15);
311
312 wallRenders[0b01110000] = TilesetIndex(16, 16);
313 wallRenders[0b01111000] = TilesetIndex(16, 16);
314 wallRenders[0b11110000] = TilesetIndex(16, 16);
315 wallRenders[0b11111000] = TilesetIndex(16, 16);
316
317 wallRenders[0b11110001] = TilesetIndex(17, 16);
318 wallRenders[0b11110011] = TilesetIndex(17, 16);
319 wallRenders[0b11111001] = TilesetIndex(17, 16);
320 wallRenders[0b11111011] = TilesetIndex(17, 16);
321
322 wallRenders[0b11000001] = TilesetIndex(18, 16);
323 wallRenders[0b11000011] = TilesetIndex(18, 16);
324 wallRenders[0b11100001] = TilesetIndex(18, 16);
325 wallRenders[0b11100011] = TilesetIndex(18, 16);
326
327
328 wallRenders[0b11110111] = TilesetIndex(21, 14);
329 wallRenders[0b11111101] = TilesetIndex(22, 14);
330 wallRenders[0b11011111] = TilesetIndex(21, 15);
331 wallRenders[0b01111111] = TilesetIndex(22, 15);
332 }
333
334 int renderDesc = 0;
335 if (isTileSetOrNotLit(map, x-1, y-1)) renderDesc |= (1 << 7);
336 if (isTileSetOrNotLit(map, x , y-1)) renderDesc |= (1 << 6);
337 if (isTileSetOrNotLit(map, x+1, y-1)) renderDesc |= (1 << 5);
338 if (isTileSetOrNotLit(map, x+1, y )) renderDesc |= (1 << 4);
339 if (isTileSetOrNotLit(map, x+1, y+1)) renderDesc |= (1 << 3);
340 if (isTileSetOrNotLit(map, x , y+1)) renderDesc |= (1 << 2);
341 if (isTileSetOrNotLit(map, x-1, y+1)) renderDesc |= (1 << 1);
342 if (isTileSetOrNotLit(map, x-1, y )) renderDesc |= (1 << 0);
343
344 map.at(x,y).renderId = wallRenders.at(renderDesc);
345
346 if (wallRenders.at(renderDesc) == 0 && renderDesc != 255) {
347 std::cout << renderDesc << std::endl;
348 }
349 }
350 }
351 }
352 }
353
354 dirtyRender = false;
355}
356
357bool Game::processKeys(const Input& keystate)
358{
359 int px = player_x;
360 int py = player_y;
361 Direction dir = Direction::up;
362
363 if (keystate.up)
364 {
365 py--;
366 }
367
368 if (keystate.down)
369 {
370 py++;
371 dir = Direction::down;
372 }
373
374 if (keystate.left)
375 {
376 px--;
377 dir = Direction::left;
378 }
379
380 if (keystate.right)
381 {
382 px++;
383 dir = Direction::right;
384 }
385
386 if (!(player_x == px && player_y == py))
387 {
388 playerAnim.setAnimation("walk");
389 playerAnim.setDirection(dir);
390
391 bool succeeds = movePlayer(px, py);
392 if (!succeeds && px != player_x) {
393 succeeds = movePlayer(px, player_y);
394 }
395 if (!succeeds && py != player_y) {
396 succeeds = movePlayer(player_x, py);
397 }
398 return succeeds;
399 } else {
400 return false;
401 }
402}
403
404void Game::kickUpDust(int x, int y, size_t chain)
405{
406 Kickup dk;
407 dk.x = x;
408 dk.y = y;
409 dk.chain = chain;
410 dk.cur = 0;
411 dk.radius = RADIUS + (chain + 1) * (chain + 1);
412 dk.front.emplace(x, y);
413 dk.done.emplace(x, y);
414
415 kickups.push_back(dk);
416}
417
418void Game::popLamp(int x, int y, size_t chain)
419{
420 muxer.playSoundAtPosition("pop", x, y);
421
422 if (map.at(x,y).tile == Tile::Lamp)
423 {
424 numLamps--;
425 }
426
427 map.at(x,y).tile = Tile::Dust;
428 map.at(x,y).dustLife = 2;
429 numDust++;
430 dirtyLighting = true;
431
432 kickUpDust(x, y, chain);
433}
434
435void Game::processKickup()
436{
437 for (Kickup& kickup : kickups)
438 {
439 kickup.cur++;
440
441 std::set<coord> newFront;
442 for (const coord& xy : kickup.front)
443 {
444 auto processDir = [&] (int x, int y) {
445 coord c {x,y};
446
447 if (map.inBounds(x,y) &&
448 (map.at(x,y).tile == Tile::Floor || map.at(x,y).tile == Tile::Lamp) &&
449 !kickup.done.count(c))
450 {
451 newFront.insert(c);
452 kickup.done.insert(c);
453
454 if (map.at(x,y).tile == Tile::Floor)
455 {
456 map.at(x,y).tile = Tile::Dust;
457 map.at(x,y).dustLife = 2;
458 numDust++;
459 dirtyLighting = true;
460 } else if (map.at(x,y).tile == Tile::Lamp)
461 {
462 popLamp(x, y, kickup.chain + 1);
463 }
464 }
465 };
466
467 processDir(std::get<0>(xy) - 1, std::get<1>(xy) );
468 processDir(std::get<0>(xy) + 1, std::get<1>(xy) );
469 processDir(std::get<0>(xy) , std::get<1>(xy) - 1);
470 processDir(std::get<0>(xy) , std::get<1>(xy) + 1);
471
472 if (std::bernoulli_distribution(0.5)(rng))
473 {
474 processDir(std::get<0>(xy) - 1, std::get<1>(xy) - 1);
475 }
476
477 if (std::bernoulli_distribution(0.5)(rng))
478 {
479 processDir(std::get<0>(xy) - 1, std::get<1>(xy) + 1);
480 }
481
482 if (std::bernoulli_distribution(0.5)(rng))
483 {
484 processDir(std::get<0>(xy) + 1, std::get<1>(xy) - 1);
485 }
486
487 if (std::bernoulli_distribution(0.5)(rng))
488 {
489 processDir(std::get<0>(xy) + 1, std::get<1>(xy) + 1);
490 }
491 }
492
493 kickup.front.swap(newFront);
494 }
495
496 erase_if(
497 kickups,
498 [] (const Kickup& kickup) {
499 return kickup.cur == kickup.radius;
500 });
501}
502
503void Game::growMap(size_t zoom)
504{
505 int ol = map.getLeft();
506 int ot = map.getTop();
507 int ow = map.getWidth();
508 int oh = map.getHeight();
509
510 map.resize(
511 -zoom * ZOOM_X_FACTOR / 2,
512 -zoom * ZOOM_Y_FACTOR / 2,
513 zoom * ZOOM_X_FACTOR,
514 zoom * ZOOM_Y_FACTOR);
515
516 maxZoom = zoom;
517
518 for (int y = map.getTop(); y < map.getBottom(); y++)
519 {
520 for (int x = map.getLeft(); x < map.getRight(); x++)
521 {
522 if (!(x >= ol && x < (ol + ow) && y >= ot && y < (ot + oh)))
523 {
524 if (std::bernoulli_distribution(0.5)(rng))
525 {
526 map.at(x,y).tile = Tile::Wall;
527 }
528 }
529 }
530 }
531
532 for (int i = 0; i < 3; i++)
533 {
534 tick(ol, ot, ol + ow, ot + oh, true);
535 }
536}
537
538void Game::setZoom(size_t zoom)
539{
540 if (zoom == curZoom)
541 {
542 return;
543 }
544
545 if (zoom > maxZoom)
546 {
547 growMap(zoom);
548 }
549
550 std::tie(
551 lastZoomLeft,
552 lastZoomTop,
553 lastZoomWidth,
554 lastZoomHeight) =
555 Renderer::calculateZoomRect(*this);
556
557 zoomProgress = 0;
558 zoomLength = std::abs(static_cast<long>(zoom - curZoom)) * TILE_WIDTH;
559 curZoom = zoom;
560 zooming = true;
561
562 curBoundX = player_x - zoom * ZOOM_X_FACTOR / 2;
563 if (curBoundX < map.getLeft())
564 {
565 curBoundX = map.getLeft();
566 } else if (curBoundX + zoom * ZOOM_X_FACTOR >= map.getRight())
567 {
568 curBoundX = map.getRight() - zoom * ZOOM_X_FACTOR;
569 }
570
571 curBoundY = player_y - zoom * ZOOM_Y_FACTOR / 2;
572 if (curBoundY < map.getTop())
573 {
574 curBoundY = map.getTop();
575 } else if (curBoundY + zoom * ZOOM_Y_FACTOR >= map.getBottom())
576 {
577 curBoundY = map.getBottom() - zoom * ZOOM_Y_FACTOR;
578 }
579
580 int zoomLevel = getZoomLevel(*this);
581 if (zoomLevel == 0) {
582 muxer.setMusicLevel(0);
583 } else if (zoomLevel < 3) {
584 muxer.setMusicLevel(1);
585 } else if (zoomLevel < 5) {
586 muxer.setMusicLevel(2);
587 } else if (zoomLevel < 7) {
588 muxer.setMusicLevel(3);
589 } else {
590 muxer.setMusicLevel(4);
591 }
592}
593
594void Game::performDash() {
595 if (map.at(player_x, player_y).tile == Tile::Floor) {
596 std::vector<coord> freeSpaces;
597
598 auto addIfFree = [&] (int x, int y) {
599 if (map.inBounds(x,y) && map.at(x,y).tile == Tile::Floor)
600 {
601 freeSpaces.emplace_back(x, y);
602 }
603 };
604
605 addIfFree(player_x - 1, player_y - 1);
606 addIfFree(player_x , player_y - 1);
607 addIfFree(player_x + 1, player_y - 1);
608 addIfFree(player_x - 1, player_y );
609 addIfFree(player_x + 1, player_y );
610 addIfFree(player_x - 1, player_y + 1);
611 addIfFree(player_x , player_y + 1);
612 addIfFree(player_x + 1, player_y + 1);
613
614 if (!freeSpaces.empty())
615 {
616 map.at(player_x, player_y).tile = Tile::Lamp;
617 numLamps++;
618 dirtyLighting = true;
619 kickUpDust(player_x, player_y, 0);
620 muxer.playSoundAtPosition("drop", player_x, player_y);
621
622 if (firstInput)
623 {
624 for (int i = 0; i < 5; i++)
625 {
626 if (!processKeys(lastInput))
627 {
628 std::uniform_int_distribution<int> freeDist(0, freeSpaces.size() - 1);
629
630 int freeIndex = freeDist(rng);
631 coord& moveTo = freeSpaces[freeIndex];
632
633 movePlayer(std::get<0>(moveTo), std::get<1>(moveTo));
634 }
635
636 tick(
637 player_x - (RADIUS - 1),
638 player_y - (RADIUS - 1),
639 player_x + RADIUS,
640 player_y + RADIUS);
641 }
642 } else {
643 std::uniform_int_distribution<int> freeDist(0, freeSpaces.size() - 1);
644
645 int freeIndex = freeDist(rng);
646 coord& moveTo = freeSpaces[freeIndex];
647
648 movePlayer(std::get<0>(moveTo), std::get<1>(moveTo));
649 }
650
651 //muxer.playSoundAtPosition("dash", player_x, player_y);
652 }
653 }
654}
655
656void Game::update(size_t frameTime) {
657 SDL_Event e;
658
659 while (SDL_PollEvent(&e))
660 {
661 if (e.type == SDL_QUIT)
662 {
663 if (losing != LoseState::None)
664 {
665 quit = true;
666 } else {
667 losing = LoseState::PoppingLamps;
668 muxer.stopMusic();
669 }
670 } else if (e.type == SDL_KEYDOWN)
671 {
672 switch (e.key.keysym.sym)
673 {
674 case SDLK_ESCAPE:
675 {
676 if (losing != LoseState::None)
677 {
678 quit = true;
679 } else {
680 losing = LoseState::PoppingLamps;
681 muxer.stopMusic();
682 }
683
684 break;
685 }
686
687 case SDLK_SPACE:
688 {
689 if (losing == LoseState::None)
690 {
691 if (moving) {
692 queueDash = true;
693 } else {
694 performDash();
695 }
696 }
697
698 break;
699 }
700 }
701 }
702 }
703
704 const Uint8* state = SDL_GetKeyboardState(NULL);
705 keystate.left = state[SDL_SCANCODE_LEFT];
706 keystate.right = state[SDL_SCANCODE_RIGHT];
707 keystate.up = state[SDL_SCANCODE_UP];
708 keystate.down = state[SDL_SCANCODE_DOWN];
709
710 bumpCooldown.accumulate(frameTime);
711 if (alreadyBumped && keystate != lastInput && bumpCooldown.step()) {
712 alreadyBumped = false;
713 }
714
715 if (queueDash && !moving) {
716 queueDash = false;
717 performDash();
718 }
719
720 if (keystate.left || keystate.right || keystate.up || keystate.down)
721 {
722 firstInput = true;
723 lastInput = keystate;
724 } else if (losing == LoseState::None) {
725 playerAnim.setAnimation("still");
726 }
727
728 dustTimer.accumulate(frameTime);
729 inputTimer.accumulate(frameTime);
730
731 while (dustTimer.step())
732 {
733 numDust = 0;
734
735 for (MapData& md : map.data())
736 {
737 if (md.tile == Tile::Dust)
738 {
739 md.dustLife--;
740
741 if (md.dustLife <= 0)
742 {
743 md.tile = Tile::Floor;
744 dirtyLighting = true;
745 } else {
746 numDust++;
747 }
748 }
749 }
750
751 processKickup();
752 }
753
754 switch (losing)
755 {
756 case LoseState::None:
757 {
758 if (moving) {
759 moveProgress.tick(frameTime);
760 if (moveProgress.isComplete()) {
761 moving = false;
762 }
763 }
764
765 while (inputTimer.step())
766 {
767 if (!moving) {
768 processKeys(keystate);
769 }
770 }
771
772 break;
773 }
774
775 case LoseState::PoppingLamps:
776 {
777 if (numLamps == 0)
778 {
779 if (numDust == 0)
780 {
781 losing = LoseState::PoppingPlayer;
782 }
783 } else {
784 losePopLampTimer.accumulate(frameTime);
785
786 while (losePopLampTimer.step())
787 {
788 std::vector<std::tuple<int, int>> lamps;
789
790 for (int y = map.getTop(); y < map.getBottom(); y++)
791 {
792 for (int x = map.getLeft(); x < map.getRight(); x++)
793 {
794 if (map.at(x,y).tile == Tile::Lamp)
795 {
796 lamps.emplace_back(x, y);
797 }
798 }
799 }
800
801 std::uniform_int_distribution<int> lampDist(0, lamps.size() - 1);
802 std::tuple<int, int> popPos = lamps[lampDist(rng)];
803
804 popLamp(std::get<0>(popPos), std::get<1>(popPos), 1);
805 }
806 }
807
808 break;
809 }
810
811 case LoseState::PoppingPlayer:
812 {
813 losePopPlayerTimer.accumulate(frameTime);
814
815 if (losePopPlayerTimer.step())
816 {
817 popLamp(player_x, player_y, 10);
818 renderPlayer = false;
819
820 losing = LoseState::Outro;
821 }
822
823 break;
824 }
825
826 case LoseState::Outro:
827 {
828 if (numDust == 0)
829 {
830 quit = true;
831 }
832
833 break;
834 }
835 }
836
837 if (dirtyLighting)
838 {
839 recalculateLighting();
840
841 for (int y = map.getTop(); y < map.getBottom(); y++)
842 {
843 for (int x = map.getLeft(); x < map.getRight(); x++)
844 {
845 if (!map.at(x,y).lit && map.at(x,y).wasLit)
846 {
847 if (std::bernoulli_distribution(0.5)(rng))
848 {
849 map.at(x,y).tile = Tile::Wall;
850 } else {
851 map.at(x,y).tile = Tile::Floor;
852 }
853 map.at(x,y).dirtyRender = true;
854 }
855 }
856 }
857
858 tick(true);
859 tick(true);
860 tick(true);
861
862 // TODO: better zoom algorithm
863 setZoom(litSpots / 1500 + INIT_ZOOM);
864 }
865
866 if (dirtyRender) {
867 recalculateRender();
868 }
869
870 zoomTimer.accumulate(frameTime);
871 while (zoomTimer.step())
872 {
873 if (zooming)
874 {
875 zoomProgress++;
876
877 if (zoomProgress == zoomLength)
878 {
879 zooming = false;
880 }
881 }
882 }
883
884 playerAnim.update(frameTime);
885}
diff --git a/src/game.h b/src/game.h index 9feb774..89b9eb2 100644 --- a/src/game.h +++ b/src/game.h
@@ -87,18 +87,15 @@ struct MapData {
87class Game { 87class Game {
88public: 88public:
89 89
90 Game(std::mt19937& rng) : 90 Game(std::mt19937& rng, Muxer& muxer);
91 rng(rng), 91
92 map( 92 void update(size_t dt);
93 -INIT_ZOOM * ZOOM_X_FACTOR / 2,
94 -INIT_ZOOM * ZOOM_Y_FACTOR / 2,
95 INIT_ZOOM * ZOOM_X_FACTOR,
96 INIT_ZOOM * ZOOM_Y_FACTOR)
97 {
98 }
99 93
100 std::mt19937& rng; 94 std::mt19937& rng;
101 Muxer muxer; 95 Muxer& muxer;
96
97 bool quit = false;
98 LoseState losing = LoseState::None;
102 99
103 Map<MapData> map; 100 Map<MapData> map;
104 std::list<Kickup> kickups; 101 std::list<Kickup> kickups;
@@ -129,6 +126,7 @@ public:
129 int lastZoomWidth; 126 int lastZoomWidth;
130 int lastZoomHeight; 127 int lastZoomHeight;
131 128
129 Input keystate;
132 bool firstInput = false; 130 bool firstInput = false;
133 Input lastInput; 131 Input lastInput;
134 bool alreadyBumped = false; 132 bool alreadyBumped = false;
@@ -137,6 +135,44 @@ public:
137 bool moving = false; 135 bool moving = false;
138 bool queueDash = false; 136 bool queueDash = false;
139 137
138 Timer dustTimer = {40};
139 Timer inputTimer = {50};
140 Timer losePopLampTimer = {800};
141 Timer losePopPlayerTimer = {3000};
142 Timer zoomTimer = {62};
143
144private:
145
146 void tick(
147 int x1,
148 int y1,
149 int x2,
150 int y2,
151 bool invert = false,
152 bool onlyDark = false);
153
154 void tick(bool onlyDark = false);
155
156 bool movePlayer(int x, int y);
157
158 void recalculateLighting();
159
160 void recalculateRender();
161
162 bool processKeys(const Input& keystate);
163
164 void kickUpDust(int x, int y, size_t chain);
165
166 void popLamp(int x, int y, size_t chain);
167
168 void processKickup();
169
170 void growMap(size_t zoom);
171
172 void setZoom(size_t zoom);
173
174 void performDash();
175
140}; 176};
141 177
142#endif /* end of include guard: GAME_H_7D2B65AE */ 178#endif /* end of include guard: GAME_H_7D2B65AE */
diff --git a/src/main.cpp b/src/main.cpp index c0d5b14..ec253ee 100644 --- a/src/main.cpp +++ b/src/main.cpp
@@ -5,638 +5,6 @@
5#include "game.h" 5#include "game.h"
6#include "renderer.h" 6#include "renderer.h"
7 7
8inline bool isTileSetOrNotLit(const Map<MapData>& map, int x, int y)
9{
10 return (map.inBounds(x, y) && (map.at(x,y).tile == Tile::Wall || !map.at(x,y).lit));
11}
12
13inline bool isTileSet(const Map<MapData>& map, int x, int y, Tile val = Tile::Wall)
14{
15 return (map.inBounds(x, y) && map.at(x,y).tile == val);
16}
17
18inline void incrementIfSet(const Game& game, int& count, int x, int y, Tile val = Tile::Wall)
19{
20 if (isTileSet(game.map, x, y, val))
21 {
22 count++;
23 }
24}
25
26inline int getZoomLevel(const Game& game) {
27 return game.curZoom - INIT_ZOOM;
28}
29
30void tick(
31 Game& game,
32 int x1,
33 int y1,
34 int x2,
35 int y2,
36 bool invert = false,
37 bool onlyDark = false)
38{
39 Map<MapData> temp(game.map);
40
41 for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
42 {
43 for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
44 {
45 if (invert == (x >= x1 && x < x2 && y >= y1 && y < y2))
46 {
47 continue;
48 }
49
50 if (onlyDark && game.map.at(x,y).lit)
51 {
52 continue;
53 }
54
55 if (game.map.at(x,y).tile == Tile::Lamp)
56 {
57 continue;
58 }
59
60 int count = 0;
61
62 incrementIfSet(game, count, x-1, y-1);
63 incrementIfSet(game, count, x-1, y );
64 incrementIfSet(game, count, x-1, y+1);
65 incrementIfSet(game, count, x , y-1);
66 incrementIfSet(game, count, x , y );
67 incrementIfSet(game, count, x , y+1);
68 incrementIfSet(game, count, x+1, y-1);
69 incrementIfSet(game, count, x+1, y );
70 incrementIfSet(game, count, x+1, y+1);
71
72 if (count >= 5)
73 {
74 temp.at(x,y).tile = Tile::Wall;
75 } else {
76 temp.at(x,y).tile = Tile::Floor;
77 }
78
79 if (temp.at(x,y).tile != game.map.at(x,y).tile) {
80 temp.at(x,y).dirtyRender = true;
81 game.dirtyRender = true;
82 }
83 }
84 }
85
86 game.map = std::move(temp);
87}
88
89void tick(Game& game, bool onlyDark = false)
90{
91 tick(
92 game,
93 game.map.getLeft(),
94 game.map.getTop(),
95 game.map.getRight(),
96 game.map.getBottom(),
97 false,
98 onlyDark);
99}
100
101bool movePlayer(Game& game, int x, int y)
102{
103 if (x >= game.curBoundX &&
104 y >= game.curBoundY &&
105 x < game.curBoundX + game.curZoom * ZOOM_X_FACTOR &&
106 y < game.curBoundY + game.curZoom * ZOOM_Y_FACTOR &&
107 game.map.at(x,y).tile == Tile::Floor)
108 {
109 if (game.map.at(game.player_x, game.player_y).tile == Tile::Floor)
110 {
111 game.map.at(game.player_x, game.player_y).tile = Tile::Dust;
112 game.map.at(game.player_x, game.player_y).dustLife = 1;
113 game.numDust++;
114 }
115
116 game.player_oldx = game.player_x;
117 game.player_oldy = game.player_y;
118 game.player_x = x;
119 game.player_y = y;
120 game.muxer.setPlayerLoc(x, y);
121 game.moving = true;
122 game.moveProgress.start(66);
123
124 game.dirtyLighting = true;
125
126 return true;
127 } else {
128 if (!game.alreadyBumped) {
129 game.muxer.playSoundAtPosition("bump", x, y);
130 game.alreadyBumped = true;
131 game.bumpCooldown.reset();
132 }
133
134 return false;
135 }
136}
137
138void recalculateLighting(Game& game)
139{
140 game.litSpots = 0;
141 game.dirtyRender = true;
142
143 for (MapData& md : game.map.data())
144 {
145 md.wasLit = md.lit;
146 md.lit = false;
147 md.litTiles.clear();
148
149 if (md.tile == Tile::Wall) {
150 md.dirtyRender = true;
151 }
152 }
153
154 fov_settings_type fov;
155 fov_settings_init(&fov);
156
157 fov_settings_set_opacity_test_function(
158 &fov,
159 [] (void* data, int x, int y) {
160 Game& game = *static_cast<Game*>(data);
161
162 return game.map.inBounds(x,y) && game.map.at(x,y).tile == Tile::Wall;
163 });
164
165 fov_settings_set_apply_lighting_function(
166 &fov,
167 [] (void* data, int x, int y, int dx, int dy, void* source) {
168 Game& game = *static_cast<Game*>(data);
169
170 if (game.map.inBounds(x, y))
171 {
172 MapData& sourceData = *static_cast<MapData*>(source);
173 double lightRadius = static_cast<double>(sourceData.lightRadius);
174
175 if (!game.map.at(x,y).lit)
176 {
177 game.litSpots++;
178 }
179
180 game.map.at(x,y).lit = true;
181
182 sourceData.litTiles.emplace(x,y);
183 }
184 });
185
186 for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
187 {
188 for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
189 {
190 Source ls = Source::None;
191 int lightRadius;
192
193 if (game.renderPlayer && game.player_x == x && game.player_y == y)
194 {
195 ls = Source::Player;
196 lightRadius = RADIUS;
197 } else if (game.map.at(x,y).tile == Tile::Dust)
198 {
199 ls = Source::Dust;
200 lightRadius = 2;
201 } else if (game.map.at(x,y).tile == Tile::Lamp)
202 {
203 ls = Source::Lamp;
204 lightRadius = RADIUS;
205 }
206
207 game.map.at(x,y).lightType = ls;
208
209 if (ls != Source::None)
210 {
211 game.map.at(x,y).lightRadius = lightRadius;
212 game.map.at(x,y).litTiles.emplace(x,y);
213
214 fov_circle(
215 &fov,
216 static_cast<void*>(&game),
217 static_cast<void*>(&game.map.at(x,y)),
218 x,
219 y,
220 lightRadius);
221
222 game.map.at(x,y).lit = true;
223 }
224 }
225 }
226
227 game.dirtyLighting = false;
228}
229
230void recalculateRender(Game& game) {
231 for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
232 {
233 for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
234 {
235 if (game.map.at(x,y).dirtyRender) {
236 game.map.at(x,y).dirtyRender = false;
237
238 if (game.map.at(x,y).tile == Tile::Floor) {
239 if (std::bernoulli_distribution(0.05)(game.rng)) {
240 static const std::vector<int> furnishings {
241 TilesetIndex(20, 16),
242 TilesetIndex(21, 2),
243 TilesetIndex(22, 2),
244 TilesetIndex(21, 3),
245 TilesetIndex(22, 3)};
246 game.map.at(x,y).renderId = furnishings.at(std::uniform_int_distribution<int>(0, furnishings.size()-1)(game.rng));
247 } else {
248 game.map.at(x,y).renderId = -1;
249 }
250 } else if (game.map.at(x,y).tile == Tile::Wall) {
251 static bool initWalls = false;
252 static std::vector<int> wallRenders(256, TilesetIndex(0, 0));
253 if (!initWalls) {
254 initWalls = true;
255
256 // Start in top left and go clockwise.
257 wallRenders[0b00011100] = TilesetIndex(16, 14);
258 wallRenders[0b00111100] = TilesetIndex(16, 14);
259 wallRenders[0b00011110] = TilesetIndex(16, 14);
260 wallRenders[0b00111110] = TilesetIndex(16, 14);
261
262 wallRenders[0b00011111] = TilesetIndex(17, 14);
263 wallRenders[0b10011111] = TilesetIndex(17, 14);
264 wallRenders[0b00111111] = TilesetIndex(17, 14);
265 wallRenders[0b10111111] = TilesetIndex(17, 14);
266
267 wallRenders[0b00000111] = TilesetIndex(18, 14);
268 wallRenders[0b00001111] = TilesetIndex(18, 14);
269 wallRenders[0b10000111] = TilesetIndex(18, 14);
270 wallRenders[0b10001111] = TilesetIndex(18, 14);
271
272 wallRenders[0b01111100] = TilesetIndex(16, 15);
273 wallRenders[0b11111100] = TilesetIndex(16, 15);
274 wallRenders[0b01111110] = TilesetIndex(16, 15);
275 wallRenders[0b11111110] = TilesetIndex(16, 15);
276
277 wallRenders[0b11000111] = TilesetIndex(18, 15);
278 wallRenders[0b11001111] = TilesetIndex(18, 15);
279 wallRenders[0b11100111] = TilesetIndex(18, 15);
280 wallRenders[0b11101111] = TilesetIndex(18, 15);
281
282 wallRenders[0b01110000] = TilesetIndex(16, 16);
283 wallRenders[0b01111000] = TilesetIndex(16, 16);
284 wallRenders[0b11110000] = TilesetIndex(16, 16);
285 wallRenders[0b11111000] = TilesetIndex(16, 16);
286
287 wallRenders[0b11110001] = TilesetIndex(17, 16);
288 wallRenders[0b11110011] = TilesetIndex(17, 16);
289 wallRenders[0b11111001] = TilesetIndex(17, 16);
290 wallRenders[0b11111011] = TilesetIndex(17, 16);
291
292 wallRenders[0b11000001] = TilesetIndex(18, 16);
293 wallRenders[0b11000011] = TilesetIndex(18, 16);
294 wallRenders[0b11100001] = TilesetIndex(18, 16);
295 wallRenders[0b11100011] = TilesetIndex(18, 16);
296
297
298 wallRenders[0b11110111] = TilesetIndex(21, 14);
299 wallRenders[0b11111101] = TilesetIndex(22, 14);
300 wallRenders[0b11011111] = TilesetIndex(21, 15);
301 wallRenders[0b01111111] = TilesetIndex(22, 15);
302 }
303
304 int renderDesc = 0;
305 if (isTileSetOrNotLit(game.map, x-1, y-1)) renderDesc |= (1 << 7);
306 if (isTileSetOrNotLit(game.map, x , y-1)) renderDesc |= (1 << 6);
307 if (isTileSetOrNotLit(game.map, x+1, y-1)) renderDesc |= (1 << 5);
308 if (isTileSetOrNotLit(game.map, x+1, y )) renderDesc |= (1 << 4);
309 if (isTileSetOrNotLit(game.map, x+1, y+1)) renderDesc |= (1 << 3);
310 if (isTileSetOrNotLit(game.map, x , y+1)) renderDesc |= (1 << 2);
311 if (isTileSetOrNotLit(game.map, x-1, y+1)) renderDesc |= (1 << 1);
312 if (isTileSetOrNotLit(game.map, x-1, y )) renderDesc |= (1 << 0);
313
314 game.map.at(x,y).renderId = wallRenders.at(renderDesc);
315
316 if (wallRenders.at(renderDesc) == 0 && renderDesc != 255) {
317 std::cout << renderDesc << std::endl;
318 }
319 }
320 }
321 }
322 }
323
324 game.dirtyRender = false;
325}
326
327bool processKeys(Game& game, const Input& keystate)
328{
329 int px = game.player_x;
330 int py = game.player_y;
331 Direction dir = Direction::up;
332
333 if (keystate.up)
334 {
335 py--;
336 }
337
338 if (keystate.down)
339 {
340 py++;
341 dir = Direction::down;
342 }
343
344 if (keystate.left)
345 {
346 px--;
347 dir = Direction::left;
348 }
349
350 if (keystate.right)
351 {
352 px++;
353 dir = Direction::right;
354 }
355
356 if (!(game.player_x == px && game.player_y == py))
357 {
358 game.playerAnim.setAnimation("walk");
359 game.playerAnim.setDirection(dir);
360
361 bool succeeds = movePlayer(game, px, py);
362 if (!succeeds && px != game.player_x) {
363 succeeds = movePlayer(game, px, game.player_y);
364 }
365 if (!succeeds && py != game.player_y) {
366 succeeds = movePlayer(game, game.player_x, py);
367 }
368 return succeeds;
369 } else {
370 return false;
371 }
372}
373
374void kickUpDust(Game& game, int x, int y, size_t chain)
375{
376 Kickup dk;
377 dk.x = x;
378 dk.y = y;
379 dk.chain = chain;
380 dk.cur = 0;
381 dk.radius = RADIUS + (chain + 1) * (chain + 1);
382 dk.front.emplace(x, y);
383 dk.done.emplace(x, y);
384
385 game.kickups.push_back(dk);
386}
387
388void popLamp(Game& game, int x, int y, size_t chain)
389{
390 game.muxer.playSoundAtPosition("pop", x, y);
391
392 if (game.map.at(x,y).tile == Tile::Lamp)
393 {
394 game.numLamps--;
395 }
396
397 game.map.at(x,y).tile = Tile::Dust;
398 game.map.at(x,y).dustLife = 2;
399 game.numDust++;
400 game.dirtyLighting = true;
401
402 kickUpDust(game, x, y, chain);
403}
404
405void processKickup(Game& game)
406{
407 for (Kickup& kickup : game.kickups)
408 {
409 kickup.cur++;
410
411 std::set<coord> newFront;
412 for (const coord& xy : kickup.front)
413 {
414 auto processDir = [&] (int x, int y) {
415 coord c {x,y};
416
417 if (game.map.inBounds(x,y) &&
418 (game.map.at(x,y).tile == Tile::Floor ||
419 game.map.at(x,y).tile == Tile::Lamp) &&
420 !kickup.done.count(c))
421 {
422 newFront.insert(c);
423 kickup.done.insert(c);
424
425 if (game.map.at(x,y).tile == Tile::Floor)
426 {
427 game.map.at(x,y).tile = Tile::Dust;
428 game.map.at(x,y).dustLife = 2;
429 game.numDust++;
430 game.dirtyLighting = true;
431 } else if (game.map.at(x,y).tile == Tile::Lamp)
432 {
433 popLamp(game, x, y, kickup.chain + 1);
434 }
435 }
436 };
437
438 processDir(std::get<0>(xy) - 1, std::get<1>(xy) );
439 processDir(std::get<0>(xy) + 1, std::get<1>(xy) );
440 processDir(std::get<0>(xy) , std::get<1>(xy) - 1);
441 processDir(std::get<0>(xy) , std::get<1>(xy) + 1);
442
443 if (std::bernoulli_distribution(0.5)(game.rng))
444 {
445 processDir(std::get<0>(xy) - 1, std::get<1>(xy) - 1);
446 }
447
448 if (std::bernoulli_distribution(0.5)(game.rng))
449 {
450 processDir(std::get<0>(xy) - 1, std::get<1>(xy) + 1);
451 }
452
453 if (std::bernoulli_distribution(0.5)(game.rng))
454 {
455 processDir(std::get<0>(xy) + 1, std::get<1>(xy) - 1);
456 }
457
458 if (std::bernoulli_distribution(0.5)(game.rng))
459 {
460 processDir(std::get<0>(xy) + 1, std::get<1>(xy) + 1);
461 }
462 }
463
464 kickup.front.swap(newFront);
465 }
466
467 erase_if(
468 game.kickups,
469 [] (const Kickup& kickup) {
470 return kickup.cur == kickup.radius;
471 });
472}
473
474void growMap(Game& game, size_t zoom)
475{
476 int ol = game.map.getLeft();
477 int ot = game.map.getTop();
478 int ow = game.map.getWidth();
479 int oh = game.map.getHeight();
480
481 game.map.resize(
482 -zoom * ZOOM_X_FACTOR / 2,
483 -zoom * ZOOM_Y_FACTOR / 2,
484 zoom * ZOOM_X_FACTOR,
485 zoom * ZOOM_Y_FACTOR);
486
487 game.maxZoom = zoom;
488
489 for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
490 {
491 for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
492 {
493 if (!(x >= ol && x < (ol + ow) && y >= ot && y < (ot + oh)))
494 {
495 if (std::bernoulli_distribution(0.5)(game.rng))
496 {
497 game.map.at(x,y).tile = Tile::Wall;
498 }
499 }
500 }
501 }
502
503 for (int i = 0; i < 3; i++)
504 {
505 tick(game, ol, ot, ol + ow, ot + oh, true);
506 }
507}
508
509void setZoom(Game& game, size_t zoom)
510{
511 if (zoom == game.curZoom)
512 {
513 return;
514 }
515
516 if (zoom > game.maxZoom)
517 {
518 growMap(game, zoom);
519 }
520
521 std::tie(
522 game.lastZoomLeft,
523 game.lastZoomTop,
524 game.lastZoomWidth,
525 game.lastZoomHeight) =
526 Renderer::calculateZoomRect(game);
527
528 game.zoomProgress = 0;
529 game.zoomLength =
530 std::abs(static_cast<long>(zoom - game.curZoom)) * TILE_WIDTH;
531 game.curZoom = zoom;
532 game.zooming = true;
533
534 game.curBoundX = game.player_x - zoom * ZOOM_X_FACTOR / 2;
535 if (game.curBoundX < game.map.getLeft())
536 {
537 game.curBoundX = game.map.getLeft();
538 } else if (game.curBoundX + zoom * ZOOM_X_FACTOR >= game.map.getRight())
539 {
540 game.curBoundX = game.map.getRight() - zoom * ZOOM_X_FACTOR;
541 }
542
543 game.curBoundY = game.player_y - zoom * ZOOM_Y_FACTOR / 2;
544 if (game.curBoundY < game.map.getTop())
545 {
546 game.curBoundY = game.map.getTop();
547 } else if (game.curBoundY + zoom * ZOOM_Y_FACTOR >= game.map.getBottom())
548 {
549 game.curBoundY = game.map.getBottom() - zoom * ZOOM_Y_FACTOR;
550 }
551
552 int zoomLevel = getZoomLevel(game);
553 if (zoomLevel == 0) {
554 game.muxer.setMusicLevel(0);
555 } else if (zoomLevel < 3) {
556 game.muxer.setMusicLevel(1);
557 } else if (zoomLevel < 5) {
558 game.muxer.setMusicLevel(2);
559 } else if (zoomLevel < 7) {
560 game.muxer.setMusicLevel(3);
561 } else {
562 game.muxer.setMusicLevel(4);
563 }
564}
565
566void performDash(Game& game, std::mt19937& rng) {
567 if (game.map.at(game.player_x, game.player_y).tile ==
568 Tile::Floor)
569 {
570 std::vector<coord> freeSpaces;
571
572 auto addIfFree = [&] (int x, int y) {
573 if (game.map.inBounds(x,y) &&
574 game.map.at(x,y).tile == Tile::Floor)
575 {
576 freeSpaces.emplace_back(x, y);
577 }
578 };
579
580 addIfFree(game.player_x - 1, game.player_y - 1);
581 addIfFree(game.player_x , game.player_y - 1);
582 addIfFree(game.player_x + 1, game.player_y - 1);
583 addIfFree(game.player_x - 1, game.player_y );
584 addIfFree(game.player_x + 1, game.player_y );
585 addIfFree(game.player_x - 1, game.player_y + 1);
586 addIfFree(game.player_x , game.player_y + 1);
587 addIfFree(game.player_x + 1, game.player_y + 1);
588
589 if (!freeSpaces.empty())
590 {
591 game.map.at(game.player_x, game.player_y).tile = Tile::Lamp;
592 game.numLamps++;
593 game.dirtyLighting = true;
594 kickUpDust(game, game.player_x, game.player_y, 0);
595 game.muxer.playSoundAtPosition("drop", game.player_x, game.player_y);
596
597 if (game.firstInput)
598 {
599 for (int i = 0; i < 5; i++)
600 {
601 if (!processKeys(game, game.lastInput))
602 {
603 std::uniform_int_distribution<int> freeDist(
604 0, freeSpaces.size() - 1);
605
606 int freeIndex = freeDist(rng);
607 coord& moveTo = freeSpaces[freeIndex];
608
609 movePlayer(
610 game,
611 std::get<0>(moveTo),
612 std::get<1>(moveTo));
613 }
614
615 tick(
616 game,
617 game.player_x - (RADIUS - 1),
618 game.player_y - (RADIUS - 1),
619 game.player_x + RADIUS,
620 game.player_y + RADIUS);
621 }
622 } else {
623 std::uniform_int_distribution<int> freeDist(
624 0, freeSpaces.size() - 1);
625
626 int freeIndex = freeDist(rng);
627 coord& moveTo = freeSpaces[freeIndex];
628
629 movePlayer(
630 game,
631 std::get<0>(moveTo),
632 std::get<1>(moveTo));
633 }
634
635 //game.muxer.playSoundAtPosition("dash", game.player_x, game.player_y);
636 }
637 }
638}
639
640int main(int, char**) 8int main(int, char**)
641{ 9{
642 std::random_device randomEngine; 10 std::random_device randomEngine;
@@ -645,296 +13,20 @@ int main(int, char**)
645 try 13 try
646 { 14 {
647 Renderer renderer; 15 Renderer renderer;
16 Muxer muxer;
648 17
649 Game game(rng); 18 Game game(rng, muxer);
650
651 for (MapData& md : game.map.data())
652 {
653 if (std::bernoulli_distribution(0.5)(rng))
654 {
655 md.tile = Tile::Wall;
656 }
657 }
658
659 tick(game);
660 tick(game);
661
662 for (int y = -1; y <= 1; y++)
663 {
664 for (int x = -1; x <= 1; x++)
665 {
666 game.map.at(x,y).tile = Tile::Floor;
667 }
668 }
669
670 tick(game);
671
672 bool quit = false;
673 LoseState losing = LoseState::None;
674 Input keystate;
675 SDL_Event e;
676
677 size_t dustDt = 40;
678 size_t dustAcc = 0;
679
680 size_t inputDt = 50;
681 size_t inputAcc = 0;
682
683 size_t losePopLampDt = 800;
684 size_t losePopLampAcc = losePopLampDt;
685
686 size_t losePopPlayerDt = 3000;
687 size_t losePopPlayerAcc = 0;
688
689 size_t zoomDt = 62;
690 size_t zoomAcc = 0;
691 19
692 size_t lastTime = SDL_GetTicks(); 20 size_t lastTime = SDL_GetTicks();
693 21 while (!game.quit)
694 while (!quit)
695 { 22 {
696 size_t currentTime = SDL_GetTicks(); 23 size_t currentTime = SDL_GetTicks();
697 size_t frameTime = currentTime - lastTime; 24 size_t frameTime = currentTime - lastTime;
698 lastTime = currentTime; 25 lastTime = currentTime;
699 26
700 while (SDL_PollEvent(&e)) 27 game.update(frameTime);
701 { 28 renderer.renderGame(game, true);
702 if (e.type == SDL_QUIT) 29 muxer.update();
703 {
704 if (losing != LoseState::None)
705 {
706 quit = true;
707 } else {
708 losing = LoseState::PoppingLamps;
709 game.muxer.stopMusic();
710 }
711 } else if (e.type == SDL_KEYDOWN)
712 {
713 switch (e.key.keysym.sym)
714 {
715 case SDLK_ESCAPE:
716 {
717 if (losing != LoseState::None)
718 {
719 quit = true;
720 } else {
721 losing = LoseState::PoppingLamps;
722 game.muxer.stopMusic();
723 }
724
725 break;
726 }
727
728 case SDLK_SPACE:
729 {
730 if (losing == LoseState::None)
731 {
732 if (game.moving) {
733 game.queueDash = true;
734 } else {
735 performDash(game, rng);
736 }
737 }
738
739 break;
740 }
741 }
742 }
743 }
744
745 const Uint8* state = SDL_GetKeyboardState(NULL);
746 keystate.left = state[SDL_SCANCODE_LEFT];
747 keystate.right = state[SDL_SCANCODE_RIGHT];
748 keystate.up = state[SDL_SCANCODE_UP];
749 keystate.down = state[SDL_SCANCODE_DOWN];
750
751 game.bumpCooldown.accumulate(frameTime);
752 if (game.alreadyBumped && keystate != game.lastInput && game.bumpCooldown.step()) {
753 game.alreadyBumped = false;
754 }
755
756 if (game.queueDash && !game.moving) {
757 game.queueDash = false;
758 performDash(game, rng);
759 }
760
761 if (keystate.left || keystate.right || keystate.up || keystate.down)
762 {
763 game.firstInput = true;
764 game.lastInput = keystate;
765 } else if (losing == LoseState::None) {
766 game.playerAnim.setAnimation("still");
767 }
768
769 dustAcc += frameTime;
770 inputAcc += frameTime;
771
772 while (dustAcc >= dustDt)
773 {
774 game.numDust = 0;
775
776 for (MapData& md : game.map.data())
777 {
778 if (md.tile == Tile::Dust)
779 {
780 md.dustLife--;
781
782 if (md.dustLife <= 0)
783 {
784 md.tile = Tile::Floor;
785 game.dirtyLighting = true;
786 } else {
787 game.numDust++;
788 }
789 }
790 }
791
792 processKickup(game);
793
794 dustAcc -= dustDt;
795 }
796
797 switch (losing)
798 {
799 case LoseState::None:
800 {
801 if (game.moving) {
802 game.moveProgress.tick(frameTime);
803 if (game.moveProgress.isComplete()) {
804 game.moving = false;
805 }
806 }
807
808 while (inputAcc >= inputDt)
809 {
810 if (!game.moving) {
811 processKeys(game, keystate);
812 }
813
814 inputAcc -= inputDt;
815 }
816
817 break;
818 }
819
820 case LoseState::PoppingLamps:
821 {
822 if (game.numLamps == 0)
823 {
824 if (game.numDust == 0)
825 {
826 losing = LoseState::PoppingPlayer;
827 }
828 } else {
829 losePopLampAcc += frameTime;
830
831 while (losePopLampAcc >= losePopLampDt)
832 {
833 std::vector<std::tuple<int, int>> lamps;
834
835 for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
836 {
837 for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
838 {
839 if (game.map.at(x,y).tile == Tile::Lamp)
840 {
841 lamps.emplace_back(x, y);
842 }
843 }
844 }
845
846 std::uniform_int_distribution<int> lampDist(0, lamps.size() - 1);
847 std::tuple<int, int> popPos = lamps[lampDist(rng)];
848
849 popLamp(game, std::get<0>(popPos), std::get<1>(popPos), 1);
850
851 losePopLampAcc -= losePopLampDt;
852 }
853 }
854
855 break;
856 }
857
858 case LoseState::PoppingPlayer:
859 {
860 losePopPlayerAcc += frameTime;
861
862 if (losePopPlayerAcc >= losePopPlayerDt)
863 {
864 popLamp(game, game.player_x, game.player_y, 10);
865 game.renderPlayer = false;
866
867 losing = LoseState::Outro;
868 }
869
870 break;
871 }
872
873 case LoseState::Outro:
874 {
875 if (game.numDust == 0)
876 {
877 quit = true;
878 }
879
880 break;
881 }
882 }
883
884 if (game.dirtyLighting)
885 {
886 recalculateLighting(game);
887
888 for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
889 {
890 for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
891 {
892 if (!game.map.at(x,y).lit && game.map.at(x,y).wasLit)
893 {
894 if (std::bernoulli_distribution(0.5)(rng))
895 {
896 game.map.at(x,y).tile = Tile::Wall;
897 } else {
898 game.map.at(x,y).tile = Tile::Floor;
899 }
900 game.map.at(x,y).dirtyRender = true;
901 }
902 }
903 }
904
905 tick(game, true);
906 tick(game, true);
907 tick(game, true);
908
909 // TODO: better zoom algorithm
910 setZoom(game, game.litSpots / 1500 + INIT_ZOOM);
911 }
912
913 if (game.dirtyRender) {
914 recalculateRender(game);
915 }
916
917 zoomAcc += frameTime;
918
919 while (zoomAcc >= zoomDt)
920 {
921 if (game.zooming)
922 {
923 game.zoomProgress++;
924
925 if (game.zoomProgress == game.zoomLength)
926 {
927 game.zooming = false;
928 }
929 }
930
931 zoomAcc -= zoomDt;
932 }
933
934 game.playerAnim.update(frameTime);
935
936 game.muxer.update();
937 renderer.render(game, true);
938 } 30 }
939 } catch (const sdl_error& ex) 31 } catch (const sdl_error& ex)
940 { 32 {
diff --git a/src/renderer.cpp b/src/renderer.cpp index 1858520..90ce03f 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp
@@ -150,7 +150,7 @@ Renderer::Renderer()
150 SDL_SetTextureBlendMode(lamp_.get(), SDL_BLENDMODE_BLEND); 150 SDL_SetTextureBlendMode(lamp_.get(), SDL_BLENDMODE_BLEND);
151} 151}
152 152
153void Renderer::render( 153void Renderer::renderGame(
154 const Game& game, 154 const Game& game,
155 bool drawDark) 155 bool drawDark)
156{ 156{
diff --git a/src/renderer.h b/src/renderer.h index 57d9702..ca4e607 100644 --- a/src/renderer.h +++ b/src/renderer.h
@@ -113,7 +113,7 @@ public:
113 113
114 Renderer(); 114 Renderer();
115 115
116 void render( 116 void renderGame(
117 const Game& game, 117 const Game& game,
118 bool drawDark = true); 118 bool drawDark = true);
119 119
diff --git a/src/timer.h b/src/timer.h index ec34f3e..65d5a50 100644 --- a/src/timer.h +++ b/src/timer.h
@@ -23,6 +23,8 @@ public:
23 acc_ = 0; 23 acc_ = 0;
24 } 24 }
25 25
26 int getDt() const { return dt_; }
27
26private: 28private:
27 29
28 int dt_; 30 int dt_;