1 /*
2 * Copyright (c) 2008,2009 Bertrand Janin <tamentis@neopulsar.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27
28 #include <stdio.h>
29
30 #include "SDL.h"
31
32 #include "rezerwar.h"
33
34
35 extern Configuration *conf;
36 extern Board *board;
37 extern SDL_Surface *screen;
38 extern SDL_Surface *sprites;
39 extern Uint32 key;
40
41 /* Colors of the text when displaying "4x combo!" */
42 int combo_colors[(MAX_COMBO - 1) * 6] = {
43 /* foreground border */
44 255, 255, 0, 120, 120, 0, // 2x
45 255, 120, 0, 120, 60, 0,
46 255, 0, 0, 120, 0, 0,
47 255, 0, 120, 120, 0, 60, // 5x
48 255, 0, 255, 120, 0, 120,
49 255, 120, 255, 120, 60, 120,
50 255, 255, 255, 120, 120, 120,
51 120, 255, 120, 60, 120, 60,
52 0, 255, 0, 0, 120, 0, // 10x
53 };
54
55
56 /**
57 * @constructor
58 */
59 Board *
board_new()60 board_new()
61 {
62 Board *b;
63 size_t size = BOARD_WIDTH * BOARD_HEIGHT;
64 char *path;
65 int i;
66
67 b = r_malloc(sizeof(Board));
68
69 b->width = BOARD_WIDTH;
70 b->height = BOARD_HEIGHT;
71
72 b->offset_x = BOARD_LEFT;
73 b->offset_y = BOARD_TOP;
74
75 b->bg = NULL;
76
77 b->elapsed = 0;
78
79 /* Cube related members initialization. */
80 b->cube_count = 0;
81 b->cubes = r_malloc(size * sizeof(Cube *));
82 for (i = 0; i < size; i++)
83 b->cubes[i] = NULL;
84
85 /* Initialize the Cube Queue */
86 b->cqueue = NULL;
87 b->cqueue_len = 0;
88
89 /* Cube related members. */
90 b->current_cube = NULL;
91 b->hold = NULL;
92 b->next_cube = NULL;
93 b->remains = -1;
94 b->launch_next = false;
95 b->next_line = 1;
96 b->time_limit = -1;
97
98 /* Mole related members */
99 b->last_mole = -1;
100 for (i = 0; i < MAX_MOLES; i++)
101 b->moles[i] = NULL;
102
103 /* Initial flames initialization */
104 board_initialize_flames(b);
105
106 /* Pipe status */
107 for (i = 0; i < BOARD_HEIGHT * 2; i++)
108 b->pipes[i] = pipe_new();
109
110 /* Texts (future OSD) related members */
111 b->texts = NULL;
112 b->text_count = 0;
113
114 /* Movement related (& controls) */
115 b->moving_left = 0;
116 b->moving_right = 0;
117 b->lateral_speed = 100;
118 b->lateral_tick = 0;
119
120 /* Player related */
121 b->score = 0;
122 b->settled = false;
123 b->combo = 0;
124 b->status = MTYPE_NOP;
125 b->paused = false;
126 b->gameover = false;
127 b->success = false;
128 b->silent = false;
129 b->allow_bomb = true;
130 b->allow_medic = true;
131 b->objective_type = OBJTYPE_NONE;
132 board_set_difficulty_from_score(b);
133
134 /* Prompt init. */
135 b->prompt_text = NULL;
136 b->prompt_func = NULL;
137
138 /* Modal and score */
139 b->modal = false;
140 b->score_t = board_add_text(b, "0", 10, 10);
141 b->timeleft_t = board_add_text(b, "", 10, 30);
142
143 /* Status message */
144 b->status_t = board_add_text(b, "", 0, 240);
145 b->status_t->effect = EFFECT_SHAKE;
146 b->status_t->centered = true;
147 text_set_color1(b->status_t, 225, 186, 0);
148 text_set_color2(b->status_t, 127, 55, 0);
149
150 /* (optional) FPS display */
151 b->show_fps = false;
152 b->fps_t = board_add_text(b, "", 550, 10);
153 text_set_color1(b->status_t, 225, 40, 0);
154 text_set_color2(b->status_t, 56, 8, 8);
155
156 /* Load background. */
157 path = dpath("gfx/gameback.bmp");
158 b->bg = SDL_LoadBMP(path);
159 if (b->bg == NULL)
160 fatal("Unable to load game background.");
161 r_free(path);
162 SDL_SetColorKey(b->bg, SDL_SRCCOLORKEY|SDL_RLEACCEL,
163 SDL_MapRGB(b->bg->format, 0, 255, 255));
164
165 /* Level stuff */
166 b->next_level = NULL;
167
168 return b;
169 }
170
171
172 /**
173 * Create a new board and populate the cubes from the given level.
174 */
175 Board *
board_new_from_level(Level * level)176 board_new_from_level(Level *level)
177 {
178 int i;
179 Board *board;
180 Cube *cube = NULL;
181 Text *title, *description, *prompt;
182
183 board = board_new();
184
185 /* Transfer the cubes */
186 for (i = 0; i < (BOARD_WIDTH * BOARD_HEIGHT); i++) {
187 cube = cube_new_from_char(level->cmap[i]);
188 if (cube == NULL)
189 continue;
190 cube->y = i / BOARD_WIDTH;
191 cube->x = i % BOARD_WIDTH;
192 cube_sync_map(board, cube);
193
194 /* No need to count rocks.. their passive. */
195 if (cube->type == CTYPE_ROCK)
196 continue;
197
198 board->cube_count++;
199 }
200
201 /* Transfer the queue */
202 board->cqueue_len = level->queue_len;
203 board->cqueue = r_malloc(sizeof(Cube *) * board->cqueue_len);
204 for (i = 0; i < board->cqueue_len; i++) {
205 cube = cube_new_from_char(level->queue[i]->cmap[0]);
206 cube->speed = board->cube_speed;
207 board->cqueue[i] = cube;
208 }
209
210 /* Prepare the board to welcome the text */
211 board->modal = true;
212 board->silent = true;
213 board->paused = true;
214
215 /* Copy level related stuff */
216 board->objective_type = level->objective_type;
217 board->allow_bomb = level->allow_bomb;
218 board->allow_medic = level->allow_medic;
219 if (level->next)
220 board->next_level = r_strcp(level->next);
221 if (level->max_cubes)
222 board->remains = level->max_cubes;
223 board->rising_speed = level->rising_speed;
224 board->time_limit = level->time_limit;
225 board->max_moles = level->max_moles;
226
227 /* Break a couple pipes at the bottom */
228 for (i = 0; i < level->dead_pipes; i++) {
229 board->pipes[BOARD_HEIGHT - i - 1]->status = 1;
230 board->pipes[BOARD_HEIGHT * 2 - i - 1]->status = 1;
231 }
232
233 /* Draw the title */
234 title = board_add_text(board, level->name, 20, 20);
235 title->centered = true;
236 title->temp = true;
237 text_set_colors(title, 0xFFE64B, 0xB35904);
238
239 /* Draw the description */
240 description = board_add_text(board, level->description, 20, 90);
241 description->temp = true;
242 description->font = 1;
243
244 /* Draw the press p to contine */
245 prompt = board_add_text(board, "press 'enter' to start", 350, 440);
246 prompt->temp = true;
247 text_set_colors(prompt, 0xFFE64B, 0xB35904);
248
249 return board;
250 }
251
252 void
board_kill(Board * board)253 board_kill(Board *board)
254 {
255 int i, size;
256
257 /* Cube cleanup (only if cubes we have) */
258 if (board->cubes != NULL) {
259 size = board->width * board->height;
260 for (i = 0; i < size; i++) {
261 if (board->cubes[i] == NULL)
262 continue;
263
264 cube_kill(board->cubes[i]);
265 }
266 r_free(board->cubes);
267 board->cubes = NULL;
268 }
269
270
271 /* Cube clean up */
272 if (board->next_cube != NULL) {
273 cube_kill(board->next_cube);
274 board->next_cube = NULL;
275 }
276
277 /* Text cleanup */
278 for (i = 0; i < board->text_count; i++) {
279 if (board->texts[i] == NULL)
280 continue;
281 text_kill(board->texts[i]);
282 }
283 free(board->texts);
284
285 /* Cube queue cleanup */
286 r_free(board->cqueue);
287 board->cqueue = NULL;
288 board->cqueue_len = 0;
289
290 /* Mole cleanup */
291 for (i = 0; i < MAX_MOLES; i++) {
292 if (board->moles[i] == NULL)
293 continue;
294
295 mole_kill(board->moles[i]);
296 }
297
298 /* Pipe cleanup */
299 for (i = 0; i < BOARD_HEIGHT * 2; i++) {
300 pipe_kill(board->pipes[i]);
301 }
302
303 /* Level stuff */
304 r_free(board->next_level);
305
306 /* General board clean up */
307 SDL_FreeSurface(board->bg);
308 r_free(board);
309 }
310
311 Text *
board_add_text(Board * board,char * value,int x,int y)312 board_add_text(Board *board, char *value, int x, int y)
313 {
314 Text *t;
315
316 t = text_new(value);
317
318 board->texts = realloc(board->texts, (board->text_count + 1) * sizeof(Text*));
319 board->texts[board->text_count] = t;
320 board->text_count++;
321
322 t->x = x;
323 t->y = y;
324
325 return t;
326 }
327
328 /**
329 * Render the score text on the top left corner of the screen.
330 */
331 #define MAX_SCORESTR_LEN 20
332 void
board_render_texts_score(Board * board)333 board_render_texts_score(Board *board)
334 {
335 char score[MAX_SCORESTR_LEN] = "";
336
337 /*
338 * If the board is not in silent mode and is not in tutorial mode
339 * (i.e. with objective), then draw the score, we are in normal mode.
340 */
341 if (board->silent != true && board->objective_type == OBJTYPE_NONE) {
342 snprintf(score, MAX_SCORESTR_LEN, "score: %d", board->score);
343 }
344
345 text_set_value(board->score_t, score);
346 }
347
348 /**
349 * Render all the texts of the board.
350 */
351 void
board_render_texts(Board * board)352 board_render_texts(Board *board)
353 {
354 int i;
355 Text *t;
356 SDL_Rect r;
357 SDL_Surface *s;
358
359 board_render_texts_score(board);
360
361 /* Update the time Text */
362 if (board->time_limit > -1) {
363 char tl[20];
364 snprintf((char *)tl, 20, "time: %d", board->time_limit);
365 text_set_value(board->timeleft_t, tl);
366 }
367
368 /* Add a modal under all the text. */
369 if (board->modal == true)
370 gfx_modal(160);
371
372 /* Draw all the Texts, cleaning up trashed ones. */
373 for (i = 0; i < board->text_count; i++) {
374 t = board->texts[i];
375 if (t == NULL) continue;
376
377 if (t->trashed == true) {
378 text_kill(t);
379 board->texts[i] = NULL;
380 continue;
381 }
382
383 s = text_get_surface(t);
384
385 /* It's legal for a text to have no surface, just skip it */
386 if (s == NULL)
387 continue;
388
389 text_get_rectangle(t, &r);
390
391 SDL_BlitSurface(s, NULL, screen, &r);
392 SDL_FreeSurface(s);
393 }
394 }
395
396 /**
397 * Remove all the text marked 'temp'.
398 */
399 void
board_trash_temp_texts(Board * board)400 board_trash_temp_texts(Board *board)
401 {
402 Text *t;
403 int i;
404
405 for (i = 0; i < board->text_count; i++) {
406 t = board->texts[i];
407 if (t == NULL)
408 continue;
409
410 if (t->temp == true)
411 t->trashed = true;
412 }
413 }
414
415 /**
416 * Toggle paused state. Ignore the request in GameOver mode.
417 */
418 void
board_toggle_pause(Board * board)419 board_toggle_pause(Board *board)
420 {
421 if (board->gameover)
422 return;
423
424 if (board->paused == true) {
425 board->modal = false;
426 board->paused = false;
427 text_set_value(board->status_t, "");
428 board_trash_temp_texts(board);
429 } else {
430 board->silent = false;
431 board->modal = true;
432 board->paused = true;
433 text_set_value(board->status_t, "paused!");
434 }
435 }
436
437 /**
438 * Apply a mask on top of all the rendered shit
439 */
440 void
board_render_transition(Board * board)441 board_render_transition(Board *board)
442 {
443 switch (board->transition) {
444 case TTYPE_SHUTTER_OPEN:
445 gfx_shutter_open();
446 board->transition = TTYPE_NONE;
447 break;
448 /*
449 case TTYPE_PIXEL_OPEN:
450 gfx_pixel_open();
451 board->transition = TTYPE_NONE;
452 break;
453 case TTYPE_GREY_CURTAIN:
454 gfx_transition_grey_curtain();
455 board->transition = TTYPE_NONE;
456 break;
457 */
458 case TTYPE_NONE:
459 default:
460 break;
461 }
462 }
463
464
465 /**
466 * Render the moles.
467 */
468 void
board_render_moles(Board * board)469 board_render_moles(Board *board)
470 {
471 int i;
472
473 for (i = 0; i < MAX_MOLES; i++) {
474 if (board->moles[i] == NULL)
475 continue;
476
477 mole_render_trail(board->moles[i]);
478 }
479
480 for (i = 0; i < MAX_MOLES; i++) {
481 if (board->moles[i] == NULL)
482 continue;
483
484 mole_render(board->moles[i]);
485 }
486 }
487
488
489 /**
490 * Main render function, actually dump pixels on the screen.
491 */
492 void
board_render(Board * board)493 board_render(Board *board)
494 {
495 /* Redraw the sky. */
496 sky_render(board);
497
498 /* Redraw the background. */
499 SDL_BlitSurface(board->bg, NULL, screen, NULL);
500
501 /* Redraw the next and hold cubes. */
502 board_render_next(board);
503 board_render_hold(board);
504
505 /* Redraw all the cubes. */
506 board_render_cubes(board);
507
508 /* Render all the explosions/flames */
509 board_render_flames(board);
510
511 /* Moles */
512 board_render_moles(board);
513
514 /* Pipe statuses */
515 board_render_pipes(board);
516
517 /* Animations */
518 chimneys_render(board);
519
520 /* Draw texts elements (meant to replace OSD), and modal */
521 board_render_texts(board);
522
523 /* Apply the transition if any */
524 board_render_transition(board);
525
526 /* Dig up the back buffer. */
527 SDL_Flip(screen);
528 }
529
530
531 /**
532 * Called when the game has ended, return the appropriate MTYPE.
533 */
534 enum mtype
board_gameover(Board * board)535 board_gameover(Board *board)
536 {
537 /* Success is only for tutorial mode. */
538 if (board->success) {
539 if (board->next_level) {
540 r_free(conf->next_level);
541 conf->next_level = r_strcp(board->next_level);
542 }
543 board_render(board);
544 return MTYPE_GAMEOVER_WIN;
545 }
546
547 /* The player made it without using hold, extra pts! */
548 if (board->hold == NULL)
549 board->score += POINTS_NO_HOLD;
550
551 /* The player made a hiscore! */
552 if (board->objective_type == OBJTYPE_NONE && hiscore_check(board->score) == true) {
553 conf->last_score = board->score;
554 return MTYPE_GAMEOVER_HISCORE;
555 }
556
557 /* The player lost and didn't make a hiscore */
558 return MTYPE_GAMEOVER_LOSE;
559 }
560
561
562 void
board_update_pipes(Board * board,uint32_t now)563 board_update_pipes(Board *board, uint32_t now)
564 {
565 Pipe *pipe;
566 int i;
567
568 #define PIPE_ANIM_SPEED 100
569
570 for (i = 1; i < BOARD_HEIGHT * 2; i++) {
571 pipe = board->pipes[i];
572
573 if (pipe->tick + PIPE_ANIM_SPEED > now)
574 continue;
575
576 if (pipe->status != -1) {
577 if (pipe->status < 10) { // b0rked pipe
578 if (pipe->status >= 3)
579 pipe->status = 0;
580 else {
581 pipe->status++;
582 }
583 }
584 }
585 pipe->tick = now;
586 }
587 }
588
589
590 /**
591 * This function handles all the elements at ticking point, if anything
592 * pushed the board to a game over, let the caller know with an mtype.
593 */
594 enum mtype
board_update(Board * board,uint32_t now)595 board_update(Board *board, uint32_t now)
596 {
597 /*
598 * The game is in pause, this means no internal logic runs, nothing
599 * is happening, not even teh 'elapsed' time flows.
600 */
601 if (board->paused == true)
602 return MTYPE_NOP;
603
604 /*
605 * Every seconds elapsed, decrement the amount of second left from
606 * the time limit.
607 */
608 board->elapsed += TICK;
609 if (board->elapsed > 1000) {
610 board->elapsed = 0;
611 board->time_limit--;
612 }
613
614 /*
615 * If a time limit is set on this level and we've reached it, return
616 * with an MTYPE stating so.
617 */
618 if (board->time_limit == 0) {
619 board->gameover = true;
620 return MTYPE_GAMEOVER_TIMEOUT;
621 }
622
623 /*
624 * Update all the game logic components
625 */
626 board_update_cubes(board, now);
627 board_update_flames(board, now);
628 board_update_water(board, now);
629 board_update_moles(board, now);
630 board_update_pipes(board, now);
631
632 /* Check the cube count for CLEARALL levels. */
633 if (board->objective_type == OBJTYPE_CLEARALL &&
634 board->cube_count == 0) {
635 board->gameover = true;
636 board->success = true;
637 }
638
639 /* Stop here, something in the 'update' caused a gameover */
640 if (board->gameover == true)
641 return board_gameover(board);
642
643 /* We need a new line! */
644 if (board->rising_speed > -1 && board->next_line <= now) {
645 if (board->next_line != 1) {
646 board_add_line(board);
647 if (board->gameover == true)
648 return board_gameover(board);
649 /* Delay the current cube's tick, looks better */
650 board->current_cube->tick = now;
651 board->current_cube->prev_y--;
652 }
653 board->next_line = now + board->rising_speed * 1000;
654 }
655
656 /* We were requested to launch the next cube */
657 if (board->launch_next == true) {
658 board_launch_next_cube(board);
659 board->launch_next = false;
660 }
661
662 /* Animations */
663 chimneys_update(board, now);
664 sky_update(board, now);
665
666 return MTYPE_NOP;
667 }
668
669
670 /**
671 * Generate a random line of cube at the bottom and move everything up one
672 * cube. Do whatever you can to avoid self-triggering lines.
673 */
674 void
board_add_line(Board * board)675 board_add_line(Board *board)
676 {
677 Cube *cube;
678 int size = board->width * board->height;
679 int i;
680
681 /* Raise all the cubes of the board of one */
682 for (i = 0; i < size; i++) {
683 cube = board->cubes[i];
684
685 if (cube == NULL)
686 continue;
687
688 if (i < board->width) { // cube in the first row
689 board->gameover = true;
690 return;
691 }
692
693 cube->y--;
694
695 if (cube->y < 0) {
696 board->gameover = true;
697 return;
698 }
699
700 cube_sync_map(board, cube);
701 }
702
703 /* Populate this new free line with random cubes */
704 board_prepopulate(board, 1);
705 }
706
707
708 /**
709 * Load a new random cube or follow the queue depending on the type of
710 * game.
711 */
712 void
board_load_next_cube(Board * board)713 board_load_next_cube(Board *board)
714 {
715 if (board->cqueue_len) {
716 board->next_cube = board->cqueue[board->cqueue_len - 1];
717 board->cqueue[board->cqueue_len - 1] = NULL;
718 board->cqueue_len--;
719 } else {
720 board->next_cube = cube_new_one(board->allow_bomb,
721 board->allow_medic);
722 }
723
724 board->next_cube->speed = board->cube_speed;
725 board->next_cube->falling = true;
726 }
727
728
729 /**
730 * Take whatever cube is currently loaded as next_cube, add it to the
731 * list of cubes for this board, place it at the right place and pick a
732 * new next_cube.
733 */
734 void
board_launch_next_cube(Board * board)735 board_launch_next_cube(Board *board)
736 {
737 Cube *cube = board->next_cube;
738 int i;
739
740 /* If we had 0 remaining cubes... you lost. */
741 if (board->remains == 0) {
742 board->gameover = true;
743 return;
744 }
745
746 /* Load the new cube */
747 board->current_cube = cube;
748 cube->x = (board->width - 1) / 2;
749 cube->y = 0;
750 board->cube_count++;
751 i = cube->x + cube->y * board->width;
752
753 /* We already have someone there, you lose. */
754 if (board->cubes[i] != NULL) {
755 board->gameover = true;
756 return;
757 }
758
759 /* Position the cube on the board */
760 cube_sync_map(board, cube);
761
762 /* Decrement the remaining cubes */
763 if (board->remains > 0)
764 board->remains--;
765
766 /* If we are NOW at 0, you are on your last cube */
767 if (board->remains == 0) {
768 board->next_cube = NULL;
769 return;
770 }
771
772 board_load_next_cube(board);
773 }
774
775
776
777 void
board_change_next_cube(Board * board)778 board_change_next_cube(Board *board)
779 {
780 cube_kill(board->next_cube);
781 board_load_next_cube(board);
782 }
783
784
785
786 /**
787 * Handle the rendering of the top "next" cube.
788 */
789 void
board_render_next(Board * board)790 board_render_next(Board *board)
791 {
792 SDL_Surface *surface;
793 Cube *cube = board->next_cube;
794
795 if (cube == NULL)
796 return;
797
798 surface = cube_get_surface(cube);
799
800 gfx_toscreen(surface, cube->x * BSIZE + NEXT_CUBE_LEFT + BSIZE / 2,
801 cube->y * BSIZE + NEXT_CUBE_TOP + BSIZE / 2);
802 gfx_free(surface);
803 }
804
805
806 /**
807 * Handle the rendering of the top "hold" cube.
808 */
809 void
board_render_hold(Board * board)810 board_render_hold(Board *board)
811 {
812 SDL_Surface *surface;
813 Cube *cube = board->hold;
814
815 if (cube == NULL)
816 return;
817
818 surface = cube_get_surface(cube);
819
820 gfx_toscreen(surface, HOLD_LEFT + BSIZE / 2, HOLD_TOP + BSIZE / 2);
821 gfx_free(surface);
822 }
823
824 /**
825 * Transfer all the cubes from a cube to the board. This is anticipating the
826 * death of a cube. Set the cube_count to 0 to avoid duplicate killing. Also
827 * checks for special cubes and execute them.
828 */
829 void
cube_landing(Board * board,Cube * cube)830 cube_landing(Board *board, Cube *cube)
831 {
832 /*
833 * This will let other functions in this loop know that a cube
834 * reach its final destination, it is reset to false at every
835 * new loops.
836 */
837 if (board->current_cube == cube) {
838 board->settled = true;
839 board->launch_next = true;
840 }
841
842 cube->falling = false;
843
844 /* Trigger special cubes */
845 switch (cube->type) {
846 case CTYPE_BOMB:
847 board_cube_bomb(board, cube);
848 break;
849 case CTYPE_MEDIC:
850 board_cube_medic(board, cube);
851 /* !FALLTHROUGH! TODO: find medic sound */
852 default:
853 sfx_play_tack1();
854 break;
855 }
856 }
857
858
859 void
board_kill_row(Board * board,int row)860 board_kill_row(Board *board, int row)
861 {
862 int i;
863
864 for (i = board->width * row; i < board->width * (row + 1); i++) {
865 if (board->cubes[i] == NULL)
866 continue;
867 board_trash_cube(board, board->cubes[i]);
868 }
869 }
870
871
872 void
board_kill_column(Board * board,int col)873 board_kill_column(Board *board, int col)
874 {
875 int i;
876
877 for (i = col; i < board->width * board->height; i += board->width) {
878 if (board->cubes[i] == NULL)
879 continue;
880 board_trash_cube(board, board->cubes[i]);
881 }
882 }
883
884
885 /**
886 * Easy wrapper to pass directly a cube.
887 */
888 void
board_add_points_from_cube(Board * board,int points,Cube * cube)889 board_add_points_from_cube(Board *board, int points, Cube *cube)
890 {
891 board_add_points(board, points, cube_get_abs_x(cube) + 2,
892 cube_get_abs_y(cube) + BSIZE / 2);
893 }
894
895
896 /**
897 * Add new points to the score, displaying that on screen.
898 */
899 void
board_add_points(Board * board,int points,int x,int y)900 board_add_points(Board *board, int points, int x, int y)
901 {
902 Text *text;
903 char value[16];
904 int xpts = points * (board->combo + 1);
905
906 /* Don't bother counting score in tutorial mode */
907 if (board->objective_type != OBJTYPE_NONE)
908 return;
909
910 board->score += xpts;
911
912 snprintf(value, 16, "%d", xpts);
913
914 text = board_add_text(board, value, x, y);
915 text->effect |= EFFECT_FADEOUT | EFFECT_FLOAT;
916 }
917
918
919 /**
920 * Load the combo color according to the array up there.
921 */
922 void
board_get_combo_colors(Board * board,int * fr,int * fg,int * fb,int * br,int * bg,int * bb)923 board_get_combo_colors(Board *board, int *fr, int *fg, int *fb, int *br, int *bg, int *bb)
924 {
925 int m = (board->combo - 2) * 6;
926
927 *fr = combo_colors[m + 0];
928 *fg = combo_colors[m + 1];
929 *fb = combo_colors[m + 2];
930 *br = combo_colors[m + 3];
931 *bg = combo_colors[m + 4];
932 *bb = combo_colors[m + 5];
933 }
934
935 /**
936 * Show combos on screen as they get added.
937 */
938 void
board_show_combo(Board * board)939 board_show_combo(Board *board)
940 {
941 Text *text;
942 char buf[16];
943 int fr, fg, fb, // foreground color
944 br, bg, bb; // border color
945
946 if (board->combo < 2)
947 return;
948
949 board_get_combo_colors(board, &fr, &fg, &fb, &br, &bg, &bb);
950
951 snprintf(buf, 16, "%dx combo!", board->combo);
952 text = board_add_text(board, buf, 0, 200);
953 text->centered = true;
954 text->effect |= EFFECT_FADEOUT | EFFECT_SHAKE;
955 text_set_color1(text, fr, fg, fb);
956 text_set_color2(text, br, bg, bb);
957 }
958
959
960 /**
961 * A Cube Medic just dropped! If a broken pipe is around, fix it. In any case,
962 * it makes sure all the surrounding cubes are opened.
963 */
964 #define MAKE_SIDE_CUBE_ALL(RX, RY) \
965 side_cube = board_get_cube(board, cube->x + RX, cube->y + RY); \
966 if (side_cube && side_cube->type != CTYPE_ROCK) \
967 side_cube->type = CTYPE_ALL;
968 void
board_cube_medic(Board * board,Cube * cube)969 board_cube_medic(Board *board, Cube *cube)
970 {
971 Pipe *left_pipe = board->pipes[cube->y];
972 Pipe *right_pipe = board->pipes[cube->y + BOARD_HEIGHT];
973 Cube *side_cube;
974
975 /* Medics shouldn't stop combos from happening, they are good things */
976 board->settled = false;
977
978 /* In front of a broken pipe on the left */
979 if (cube->x == 0 && left_pipe->status != -1) {
980 pipe_fix(board, left_pipe);
981
982 /* In front of a broken pipe on the right */
983 } else if (cube->x == (BOARD_WIDTH - 1) && right_pipe->status != -1) {
984 pipe_fix(board, right_pipe);
985
986 /*
987 * Find adjacent cubes and convert them so that they all have an
988 * opening toward this new cube. For now, convert all of them into
989 * 'all-way' cubes.
990 */
991 } else {
992 /* Sides */
993 MAKE_SIDE_CUBE_ALL( 0, 1);
994 MAKE_SIDE_CUBE_ALL(-1, 0);
995 MAKE_SIDE_CUBE_ALL( 0, -1);
996 MAKE_SIDE_CUBE_ALL( 1, 0);
997 }
998
999 board_trash_cube(board, cube);
1000 sfx_play_menuselect();
1001 }
1002
1003
1004 /**
1005 * Set the speed of cubes, the number of moles, relative to the score.
1006 */
1007 void
board_set_difficulty_from_score(Board * board)1008 board_set_difficulty_from_score(Board *board)
1009 {
1010 if (board->score < 5000) {
1011 board->cube_speed = 1000;
1012 board->max_moles = 2;
1013 board->rising_speed = 30;
1014 } else if (board->score < 10000) {
1015 board->cube_speed = 850;
1016 board->max_moles = 3;
1017 board->rising_speed = 25;
1018 } else if (board->score < 15000) {
1019 board->cube_speed = 800;
1020 board->max_moles = 4;
1021 board->rising_speed = 25;
1022 } else if (board->score < 20000) {
1023 board->cube_speed = 750;
1024 board->max_moles = 5;
1025 board->rising_speed = 20;
1026 } else if (board->score < 25000) {
1027 board->cube_speed = 700;
1028 board->max_moles = 6;
1029 board->rising_speed = 20;
1030 } else {
1031 board->cube_speed = 650;
1032 board->max_moles = 7;
1033 board->rising_speed = 15;
1034 }
1035 }
1036
1037
1038 /**
1039 * Update an individual cube during the current tick (which time is
1040 * reprensented by 'now'.
1041 */
1042 void
board_update_falling_cube(Board * board,uint32_t now,Cube * cube)1043 board_update_falling_cube(Board *board, uint32_t now, Cube *cube) {
1044 /* This cube's tick has expired, we might need to move it. */
1045 if (now - cube->tick > cube->speed) {
1046 /* Can it fit one unit lower? */
1047 if (board_move_check(board, cube, 0, 1) == 0) {
1048 cube->y++;
1049 cube->tick = now;
1050 cube_sync_map(board, cube);
1051 }
1052
1053 /* If the cube didn't move for a tick, it is landing. */
1054 else if (cube->prev_y == cube->y) {
1055 cube_landing(board, cube);
1056 return;
1057 }
1058
1059 cube->prev_y = cube->y;
1060 }
1061
1062 /* Ticking for the lateral moves. */
1063 if (now - board->lateral_tick > board->lateral_speed) {
1064 if (board->moving_left > 1) {
1065 board->moving_left--;
1066 } else if (board->moving_left == 1) {
1067 board_move_current_cube_left(board);
1068 }
1069
1070 if (board->moving_right > 1) {
1071 board->moving_right--;
1072 } else if (board->moving_right == 1) {
1073 board_move_current_cube_right(board);
1074 }
1075
1076 board->lateral_tick = now;
1077 }
1078 }
1079
1080
1081 /**
1082 * move_check()
1083 * bside is the value telling if the cube is on the left of the whole
1084 * cube (1) or on the right side (2).
1085 *
1086 * Return values:
1087 * 0 when path is clear
1088 * 3 when touching the bottom
1089 * 1 when a cube from the left side blocked
1090 * 2 when a cube from the right side blocked
1091 * 4 when another cube is in the way
1092 */
1093 byte
board_move_check(Board * board,Cube * cube,Sint8 x,Sint8 y)1094 board_move_check(Board *board, Cube *cube, Sint8 x, Sint8 y)
1095 {
1096 int j;
1097
1098 /* Reached the bottom of the board. */
1099 if (cube->y + y >= board->height)
1100 return 3;
1101
1102 /* Reached the left border. */
1103 if (cube->x + x < 0)
1104 return 1;
1105
1106 /* Reach the right border. */
1107 if (cube->x + x >= board->width)
1108 return 2;
1109
1110 /* There is a cube in this direction. */
1111 j = (cube->y + y) * board->width + (cube->x + x);
1112 if (board->cubes[j]) {
1113 return 1;
1114 }
1115
1116 return 0;
1117 }
1118
1119
1120 void
board_move_current_cube_left(Board * board)1121 board_move_current_cube_left(Board *board)
1122 {
1123 Cube *cube = board->current_cube;
1124
1125 if (cube == NULL)
1126 return;
1127
1128 /* Don't rotate cube during pause. */
1129 if (board->paused == true)
1130 return;
1131
1132 if (board_move_check(board, cube, -1, 0) == 0) {
1133 cube->x--;
1134 cube_sync_map(board, cube);
1135 }
1136 }
1137
1138
1139 void
board_move_current_cube_right(Board * board)1140 board_move_current_cube_right(Board *board)
1141 {
1142 Cube *cube = board->current_cube;
1143
1144 if (cube == NULL)
1145 return;
1146
1147 /* Don't rotate cube during pause. */
1148 if (board->paused == true)
1149 return;
1150
1151 if (board_move_check(board, cube, 1, 0) == 0) {
1152 cube->x++;
1153 cube_sync_map(board, cube);
1154 }
1155 }
1156
1157
1158 /**
1159 * In charge of rotating a cube on a board. If there is no space for a
1160 * rotation, ignore, if against a cube, move a bit. If cube is NULL,
1161 * default to current.
1162 */
1163 void
board_rotate_cw(Board * board)1164 board_rotate_cw(Board *board)
1165 {
1166 Cube *cube = board->current_cube;
1167
1168 /* Don't rotate cube during pause. */
1169 if (board->paused == true)
1170 return;
1171
1172 cube_rotate_cw(cube);
1173 sfx_play_tick1();
1174 }
1175
1176
1177 /**
1178 * Update all the cubes logic (non-graphic stuff).
1179 */
1180 void
board_update_cubes(Board * board,uint32_t now)1181 board_update_cubes(Board *board, uint32_t now)
1182 {
1183 int i;
1184 int size = board->width * board->height;
1185 int type;
1186 Cube *cube;
1187
1188 board->settled = false;
1189
1190 /* Only re-adjust the difficulty in normal mode */
1191 if (board->objective_type == OBJTYPE_NONE)
1192 board_set_difficulty_from_score(board);
1193
1194 for (i = 0; i < size; i++) {
1195 cube = board->cubes[i];
1196
1197 if (cube == NULL)
1198 continue;
1199
1200 /* Check if the cube is trashed (if yes fade it to death) */
1201 if (cube->trashed == true) {
1202 cube->fade_status++;
1203 if (cube->fade_status > BSIZE / 2) {
1204 cube_kill(cube);
1205 board->cubes[i] = NULL;
1206 }
1207 continue;
1208 }
1209
1210 /* Falling cube have their own logic */
1211 if (cube->falling == true) {
1212 board_update_falling_cube(board, now, cube);
1213 continue;
1214 }
1215
1216
1217 /* If the cube is a rock, just skip, rocks don't move */
1218 if (cube->type == CTYPE_ROCK)
1219 continue;
1220
1221 /*
1222 * Check if the cube has free space under itself, if yes
1223 * disconnect it as a cube and create a new cube falling
1224 * much faster.
1225 */
1226 type = board_get_area_type(board, cube->x, cube->y + 1);
1227 if (type == ATYPE_FREE) {
1228 cube->falling = true;
1229 cube->speed = 100;
1230 }
1231 }
1232 }
1233
1234
1235 /**
1236 * Rendering the cubes is actually handling the graphic part, i.e. blitting
1237 * the textures at the right place.
1238 */
1239 void
board_render_cubes(Board * board)1240 board_render_cubes(Board *board)
1241 {
1242 int i;
1243 int size = board->width * board->height;
1244 SDL_Surface *surface;
1245 Cube *cube;
1246
1247 for (i = 0; i < size; i++) {
1248 cube = board->cubes[i];
1249
1250 if (cube == NULL)
1251 continue;
1252
1253 surface = cube_get_surface(cube);
1254
1255 gfx_toscreen(surface, cube->x * BSIZE + BOARD_LEFT,
1256 cube->y * BSIZE + BOARD_TOP);
1257 gfx_free(surface);
1258 }
1259 }
1260
1261
1262 /**
1263 * Dump on stdout the map of the cube as it stands right now.
1264 */
1265 void
board_dump_cube_map(Board * board)1266 board_dump_cube_map(Board *board)
1267 {
1268 byte x, y;
1269 int i;
1270
1271 for (y = 0; y < board->height; y++) {
1272 for (x = 0; x < board->width; x++) {
1273 i = y * board->width + x;
1274 if (board->cubes[i]) {
1275 printf("x");
1276 } else {
1277 printf(" ");
1278 }
1279 }
1280 printf("\n");
1281 }
1282 }
1283
1284
1285 /**
1286 * Go through all the cubes and remove the water, untie all the current
1287 * networks.
1288 */
1289 void
board_remove_water(Board * board)1290 board_remove_water(Board *board)
1291 {
1292 int i;
1293 int bs = board->width * board->height;
1294
1295 /* Get rid of all the water. */
1296 for (i = 0; i < bs; i++) {
1297 if (board->cubes[i] == NULL)
1298 continue;
1299
1300 board->cubes[i]->water = 0;
1301 board->cubes[i]->root = NULL;
1302 board->cubes[i]->network_integrity = 1;
1303 cube_network_flush(board->cubes[i]);
1304 }
1305 }
1306
1307
1308 /**
1309 * Check the left and right side for cubes. If any, check if they have opened
1310 * pipes on the same side.
1311 */
1312 void
board_update_water(Board * board,uint32_t now)1313 board_update_water(Board *board, uint32_t now)
1314 {
1315 int i, // loop helper
1316 combo = board->combo; // backup the combo
1317 Cube *cube; // current cube during the loops
1318
1319 board_remove_water(board);
1320
1321 /* Scan the left side... */
1322 for (i = 0; i < board->height; i++) {
1323 cube = board->cubes[i * board->width];
1324 if (cube == NULL || cube->falling == true)
1325 continue;
1326
1327 if (board->pipes[i]->status != -1)
1328 continue;
1329
1330 if (cube_plug_match(cube, PLUG_WEST))
1331 board_spread_water(board, cube, NULL, 1);
1332 }
1333
1334 /*
1335 * Now while scanning the right side, also check if water made it all
1336 * the way through, in this case, taint the network.
1337 */
1338 for (i = 0; i < board->height; i++) {
1339 cube = board->cubes[(i + 1) * board->width - 1];
1340 if (cube == NULL || cube->falling == true)
1341 continue;
1342
1343 if (board->pipes[BOARD_HEIGHT+i]->status != -1)
1344 continue;
1345
1346 if (cube_plug_match(cube, PLUG_EAST)) {
1347 if (cube->water == 1) {
1348 cube_network_taint(cube->root);
1349 /* If we are in link mode, this is a win */
1350 if (board->objective_type == OBJTYPE_LINK) {
1351 board->gameover = true;
1352 board->success = true;
1353 }
1354 } else
1355 board_spread_water(board, cube, NULL, 2);
1356 }
1357 }
1358
1359 /*
1360 * Now we know what networks are tainted, go through the left side
1361 * again and look for a root cube (net length > 1), with red water (3),
1362 * and a network_integrity preserved. Toggle an avalanche if found.
1363 */
1364 for (i = 0; i < board->height; i++) {
1365 cube = board->cubes[i * board->width];
1366 if (cube == NULL || cube->falling == true)
1367 continue;
1368
1369 if (cube->network_size > 1 && cube->water == 3 &&
1370 cube->network_integrity == 1) {
1371 if (cube->fade_status > 0) continue;
1372 board_run_avalanche(board, cube);
1373 }
1374 }
1375
1376 /*
1377 * If we are in a loop with a newly settled cube (reach its final
1378 * destination), check if the user has completed a new network (if
1379 * board->combo has changed). If no, reset it to zero.
1380 */
1381 if (board->settled == true) {
1382 if (combo == board->combo) {
1383 board->combo = 0;
1384 } else {
1385 if (board->combo > MAX_COMBO)
1386 board->combo = MAX_COMBO;
1387 board_show_combo(board);
1388 }
1389 }
1390 }
1391
1392
1393 /**
1394 * Run avalanche on the specific cube. This means the cube was already
1395 * identified as the root of a tainted network.
1396 */
1397 void
board_run_avalanche(Board * board,Cube * cube)1398 board_run_avalanche(Board *board, Cube *cube)
1399 {
1400 int i;
1401 Text *text;
1402
1403 /* Start a fading text... */
1404 text = board_add_text(board, "Excellent!", 240, 200);
1405 text->centered = true;
1406 text->temp = true;
1407 text_set_color1(text, 255, 0, 0);
1408 text_set_color2(text, 80, 0, 0);
1409 text->effect |= EFFECT_SHAKE | EFFECT_FADEOUT;
1410
1411 board_add_points_from_cube(board, POINTS_AVALANCHE, cube);
1412
1413 /* Run each columns individually */
1414 board_run_avalanche_column(board, cube);
1415 for (i = 0; i < cube->network_size; i++) {
1416 board_run_avalanche_column(board, cube->network[i]);
1417 }
1418 }
1419
1420
1421 /**
1422 * Run an avalanche on only one column, starting from the cube
1423 */
1424 void
board_run_avalanche_column(Board * board,Cube * cube)1425 board_run_avalanche_column(Board *board, Cube *cube)
1426 {
1427 int y;
1428 Cube *target;
1429
1430 for (y = cube->y; y < board->height; y++) {
1431 target = board_get_cube(board, cube->x, y);
1432
1433 if (target->type == CTYPE_ROCK)
1434 continue;
1435
1436 if (target != NULL && target->trashed == false) {
1437 board_trash_cube(board, target);
1438 board_add_points_from_cube(board, POINTS_NETWORK_FACTOR,
1439 target);
1440 }
1441 }
1442 }
1443
1444
1445 /**
1446 * Return a cube at the given coordinates, return NULL if not found or if
1447 * border of the board.
1448 */
1449 Cube *
board_get_cube(Board * board,int x,int y)1450 board_get_cube(Board *board, int x, int y)
1451 {
1452 /* Bad x value */
1453 if (x < 0 || x >= board->width)
1454 return NULL;
1455
1456 /* Bad y value */
1457 if (y < 0 || y >= board->height)
1458 return NULL;
1459
1460 return (board->cubes[x + board->width * y]);
1461 }
1462
1463
1464 /**
1465 * Return a cube at the given screen coordinates, return NULL if nothing
1466 * found.
1467 */
1468 Cube *
board_get_cube_absolute(Board * board,int x,int y)1469 board_get_cube_absolute(Board *board, int x, int y)
1470 {
1471 int rx, ry;
1472
1473 rx = (x - BOARD_LEFT) / BSIZE;
1474 ry = (y - BOARD_TOP) / BSIZE;
1475
1476 return board_get_cube(board, rx, ry);
1477 }
1478
1479
1480 /**
1481 * Returns an integer representing the type of area at x,y
1482 */
1483 int
board_get_area_type(Board * board,int x,int y)1484 board_get_area_type(Board *board, int x, int y)
1485 {
1486 if (x < 0)
1487 return ATYPE_BOARD_LEFT;
1488
1489 if (x >= board->width)
1490 return ATYPE_BOARD_RIGHT;
1491
1492 if (y >= board->height)
1493 return ATYPE_BOARD_BOTTOM;
1494
1495 if (board->cubes[x + board->width * y] != NULL)
1496 return ATYPE_CUBE;
1497
1498 return ATYPE_FREE;
1499 }
1500
1501
1502 /**
1503 * Given specific x/y offsets, try to spread the water to a neighboring cube
1504 */
1505 void
board_spread_attempt(Board * board,Cube * cube,Cube * root,Sint8 ox,Sint8 oy,byte src_plug,byte dest_plug)1506 board_spread_attempt(Board *board, Cube *cube, Cube *root, Sint8 ox, Sint8 oy,
1507 byte src_plug, byte dest_plug)
1508 {
1509 Cube *n;
1510 int status;
1511 int type;
1512
1513 type = board_get_area_type(board, cube->x + ox, cube->y + oy);
1514 switch (type) {
1515 case ATYPE_FREE:
1516 if (cube_plug_match(cube, src_plug))
1517 root->network_integrity = 0;
1518 break;
1519 case ATYPE_CUBE:
1520 n = board_get_cube(board, cube->x + ox, cube->y + oy);
1521
1522 /* Falling blocks should not be part of any network */
1523 if (n->falling && cube_plug_match(cube, src_plug)) {
1524 root->network_integrity = 0;
1525 return;
1526 }
1527
1528 status = cube_get_plug_status(cube, src_plug, n,
1529 dest_plug);
1530 if (status == PSTAT_CONNECTED)
1531 board_spread_water(board, n, root, root->water);
1532 break;
1533 default:
1534 break;
1535 }
1536 }
1537
1538
1539 /**
1540 * A network is going to be destroyed.
1541 */
1542 void
board_destroy_network(Board * board,Cube * cube)1543 board_destroy_network(Board *board, Cube *cube)
1544 {
1545 int i;
1546
1547 if (cube->trashed == true)
1548 return;
1549
1550 for (i = 0; i < cube->network_size; i++) {
1551 board_trash_cube(board, cube->network[i]);
1552 }
1553
1554 /* Only count combos for networks with more than one cubes. */
1555 if (cube->network_size > 0)
1556 board->combo++;
1557
1558 board_trash_cube(board, cube);
1559 board_add_points_from_cube(board,
1560 (cube->network_size + 1) * POINTS_NETWORK_FACTOR,
1561 cube);
1562
1563 sfx_play_lazer();
1564 }
1565
1566
1567 /**
1568 * Mark a cube for deletion (trashed), it will fade and ultimately be removed
1569 */
1570 void
board_trash_cube(Board * board,Cube * cube)1571 board_trash_cube(Board *board, Cube *cube)
1572 {
1573 cube->trashed = true;
1574 cube_sync_map(board, cube);
1575 board->cube_count--;
1576 }
1577
1578
1579 /**
1580 * Take a cube and check its neighbors for propagation. 'n' is always the
1581 * neighbor cube.
1582 */
1583 void
board_spread_water(Board * board,Cube * cube,Cube * root,int water_type)1584 board_spread_water(Board *board, Cube *cube, Cube *root, int water_type)
1585 {
1586 /* Don't continue if this cube is already watered. */
1587 if (cube->water >= 1)
1588 return;
1589
1590 /* Add 'cube' to the network, it doesn't seem to be a starting point. */
1591 if (root == NULL) {
1592 root = cube;
1593 } else {
1594 cube_network_add(root, cube);
1595 }
1596
1597 cube->water = water_type;
1598
1599 /* North, East, South, West */
1600 board_spread_attempt(board, cube, root, 0, -1, PLUG_NORTH, PLUG_SOUTH);
1601 board_spread_attempt(board, cube, root, 1, 0, PLUG_EAST, PLUG_WEST);
1602 board_spread_attempt(board, cube, root, 0, 1, PLUG_SOUTH, PLUG_NORTH);
1603 board_spread_attempt(board, cube, root, -1, 0, PLUG_WEST, PLUG_EAST);
1604
1605 if (root == cube && cube->network_integrity == 1) {
1606 board_destroy_network(board, cube);
1607 }
1608 }
1609
1610
1611 /**
1612 * Prepopulate a number of lines at the bottom of the board.
1613 */
1614 void
board_prepopulate(Board * board,int lines)1615 board_prepopulate(Board *board, int lines)
1616 {
1617 int x, y;
1618 Cube *cube;
1619
1620 for (x = 1; x < board->width - 1; x++) {
1621 for (y = board->height - lines; y < board->height; y++) {
1622 cube = cube_new_random();
1623 cube->x = x;
1624 cube->y = y;
1625 cube_sync_map(board, cube);
1626 board->cube_count++;
1627 }
1628 }
1629 }
1630
1631
1632 /**
1633 * Keep the current cube aside. If a cube is currenly held, replace with
1634 * the current one.
1635 */
1636 void
board_hold(Board * board)1637 board_hold(Board *board)
1638 {
1639 Cube *cube;
1640
1641 if (board->hold == NULL) {
1642 board->hold = board->current_cube;
1643 board->hold->falling = false;
1644 board->launch_next = true;
1645 cube_sync_map(board, board->hold);
1646 } else {
1647 cube = board->hold;
1648 cube->x = board->current_cube->x;
1649 cube->y = board->current_cube->y;
1650 cube->prev_y = board->current_cube->prev_y;
1651 cube->tick = board->current_cube->tick;
1652 cube->falling = true;
1653 board->hold = board->current_cube;
1654 cube_sync_map(board, board->hold);
1655 board->current_cube = cube;
1656 cube_sync_map(board, cube);
1657 }
1658 }
1659
1660
1661 /**
1662 * Move the current cube as low as possible and trigger the cube transfer to
1663 * avoid the player to move it.
1664 */
1665 void
board_cube_fall(Board * board)1666 board_cube_fall(Board *board)
1667 {
1668 int offset = 1;
1669 Cube *cube = board->current_cube;
1670
1671 while (board_move_check(board, cube, 0, offset) == 0)
1672 offset++;
1673
1674 cube->y += offset - 1;
1675 cube->prev_y = cube->y;
1676 cube->tick += cube->speed * 2;
1677
1678 cube_sync_map(board, cube);
1679 }
1680
1681
1682 /**
1683 * Generate a new mole. If reaching MAX, just remove the oldest.
1684 */
1685 void
board_spawn_mole(Board * board)1686 board_spawn_mole(Board *board)
1687 {
1688 int idx = board->last_mole;
1689
1690 if (idx >= MAX_MOLES - 1)
1691 idx = 0;
1692 else
1693 idx++;
1694
1695 board->last_mole = idx;
1696
1697 if (board->moles[idx] != NULL)
1698 mole_kill(board->moles[idx]);
1699
1700 board->moles[idx] = mole_new();
1701 board->moles[idx]->board = board;
1702 }
1703
1704 void
board_update_moles(Board * board,uint32_t now)1705 board_update_moles(Board *board, uint32_t now)
1706 {
1707 int i, // loop variable
1708 alive_moles = 0; // number of moles currently active
1709
1710
1711 for (i = 0; i < MAX_MOLES; i++) {
1712 if (board->moles[i] == NULL)
1713 continue;
1714
1715 if (board->moles[i]->trashed == true) {
1716 mole_kill(board->moles[i]);
1717 board->moles[i] = NULL;
1718 continue;
1719 }
1720
1721 alive_moles++;
1722 mole_update(board->moles[i], now);
1723 }
1724
1725 /* Ensure we always have enough moles on screen. */
1726 for (i = alive_moles; i < board->max_moles; i++) {
1727 board_spawn_mole(board);
1728 }
1729 }
1730
1731