2 * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <glib/gi18n.h>
23 /* universal settings */
24 static boolean gd_no_invisible_outbox = FALSE;
27 void gd_game_free(GdGame *game)
32 if (game->element_buffer)
33 gd_cave_map_free(game->element_buffer);
34 if (game->last_element_buffer)
35 gd_cave_map_free(game->last_element_buffer);
37 gd_cave_map_free(game->dir_buffer);
39 gd_cave_map_free(game->gfx_buffer);
41 game->player_lives = 0;
44 gd_cave_free(game->cave);
46 /* if we recorded some replays during this run, we check them.
47 we remove those which are too short */
48 if (game->replays_recorded)
53 for (citer = gd_caveset; citer != NULL; citer = citer->next)
55 GdCave *cave = (GdCave *)citer->data;
58 /* check replays of all caves */
59 for (riter = cave->replays; riter != NULL; )
61 GdReplay *replay = (GdReplay *)riter->data;
63 /* remember next iter, as we may delete the current */
64 GList *nextrep = riter->next;
66 /* if we recorded this replay now, and it is too short, we delete it */
67 /* but do not delete successful ones! */
68 if (g_list_find(game->replays_recorded, replay) &&
69 (replay->movements->len < 16) &&
72 /* delete from list */
73 cave->replays = g_list_delete_link(cave->replays, riter);
75 /* also free replay */
76 gd_replay_free(replay);
83 /* free the list of newly recorded replays, as we checked them */
84 g_list_free(game->replays_recorded);
85 game->replays_recorded = NULL;
91 /* add bonus life. if sound enabled, play sound, too. */
92 static void add_bonus_life(GdGame *game, boolean inform_user)
94 /* only inform about bonus life when playing a game */
95 /* or when testing the cave (so the user can see that a bonus life can be earned in that cave */
96 if (game->type == GD_GAMETYPE_NORMAL ||
97 game->type == GD_GAMETYPE_TEST)
101 gd_sound_play_bonus_life();
102 game->bonus_life_flash = 100;
106 /* really increment number of lifes? only in a real game, nowhere else. */
107 if (game->player_lives &&
108 game->player_lives < gd_caveset_data->maximum_lives)
110 /* only add a life, if lives is > 0.
111 lives == 0 is a test run or a snapshot, no bonus life then. */
112 /* also, obey max number of bonus lives. */
113 game->player_lives++;
117 /* increment score of player.
118 flash screen if bonus life
120 static void increment_score(GdGame *game, int increment)
124 i = game->player_score / gd_caveset_data->bonus_life_score;
125 game->player_score += increment;
126 game->cave_score += increment;
128 /* also record to replay */
129 if (game->replay_record)
130 game->replay_record->score += increment;
132 /* if score crossed bonus_life_score point boundary, player won a bonus life */
133 if (game->player_score / gd_caveset_data->bonus_life_score > i)
134 add_bonus_life(game, TRUE);
137 /* do the things associated with loading a new cave. function creates gfx buffer and the like. */
138 static void load_cave(GdGame *game)
142 /* delete element buffer */
143 if (game->element_buffer)
144 gd_cave_map_free(game->element_buffer);
145 game->element_buffer = NULL;
147 /* delete last element buffer */
148 if (game->last_element_buffer)
149 gd_cave_map_free(game->last_element_buffer);
150 game->last_element_buffer = NULL;
152 /* delete direction buffer */
153 if (game->dir_buffer)
154 gd_cave_map_free(game->dir_buffer);
155 game->dir_buffer = NULL;
157 /* delete gfx buffer */
158 if (game->gfx_buffer)
159 gd_cave_map_free(game->gfx_buffer);
160 game->gfx_buffer = NULL;
163 game->cave_score = 0;
165 /* delete previous cave */
166 gd_cave_free(game->cave);
168 if (native_bd_level.loaded_from_caveset)
169 game->original_cave = gd_get_original_cave_from_caveset(game->cave_num);
171 game->original_cave = native_bd_level.cave;
173 game->cave = gd_get_prepared_cave(game->original_cave, game->level_num);
175 if (game->cave->intermission && game->cave->intermission_instantlife)
176 add_bonus_life(game, FALSE);
178 game->milliseconds_anim = 0;
179 game->milliseconds_game = 0; /* set game timer to zero, too */
181 /* create new element buffer */
182 game->element_buffer = gd_cave_map_new(game->cave, int);
184 for (y = 0; y < game->cave->h; y++)
185 for (x = 0; x < game->cave->w; x++)
186 game->element_buffer[y][x] = O_NONE;
188 /* create new last element buffer */
189 game->last_element_buffer = gd_cave_map_new(game->cave, int);
191 for (y = 0; y < game->cave->h; y++)
192 for (x = 0; x < game->cave->w; x++)
193 game->last_element_buffer[y][x] = O_NONE;
195 /* create new direction buffer */
196 game->dir_buffer = gd_cave_map_new(game->cave, int);
198 for (y = 0; y < game->cave->h; y++)
199 for (x = 0; x < game->cave->w; x++)
200 game->dir_buffer[y][x] = GD_MV_STILL;
202 /* create new gfx buffer */
203 game->gfx_buffer = gd_cave_map_new(game->cave, int);
205 for (y = 0; y < game->cave->h; y++)
206 for (x = 0; x < game->cave->w; x++)
207 game->gfx_buffer[y][x] = -1; /* fill with "invalid" */
210 GdCave *gd_create_snapshot(GdGame *game)
213 g_return_val_if_fail (game->cave != NULL, NULL);
215 /* make an exact copy */
216 snapshot = gd_cave_new_from_cave(game->cave);
221 /* this starts a new game */
222 GdGame *gd_game_new(const int cave, const int level)
226 game = checked_calloc(sizeof(GdGame));
228 game->cave_num = cave;
229 game->level_num = level;
231 game->player_lives = gd_caveset_data->initial_lives;
232 game->player_score = 0;
234 game->player_move = GD_MV_STILL;
235 game->player_move_stick = FALSE;
236 game->player_fire = FALSE;
238 game->type = GD_GAMETYPE_NORMAL;
239 game->state_counter = GAME_INT_LOAD_CAVE;
241 game->show_story = TRUE;
246 /* starts a new snapshot playing */
247 GdGame *gd_game_new_replay(GdCave *cave, GdReplay *replay)
251 game = checked_calloc(sizeof(GdGame));
253 gd_strcpy(game->player_name, "");
255 game->player_lives = 0;
256 game->player_score = 0;
258 game->player_move = GD_MV_STILL;
259 game->player_move_stick = FALSE;
260 game->player_fire = FALSE;
262 game->original_cave = cave;
263 game->replay_from = replay;
265 game->type = GD_GAMETYPE_REPLAY;
266 game->state_counter = GAME_INT_LOAD_CAVE;
271 static void iterate_cave(GdGame *game, GdDirection player_move, boolean fire)
273 boolean suicide = FALSE;
275 /* if we are playing a replay, but the user intervents, continue as a snapshot. */
276 /* do not trigger this for fire, as it would not be too intuitive. */
277 if (game->type == GD_GAMETYPE_REPLAY)
279 if (player_move != GD_MV_STILL)
281 game->type = GD_GAMETYPE_CONTINUE_REPLAY;
282 game->replay_from = NULL;
286 /* ANYTHING EXCEPT A TIMEOUT, WE ITERATE THE CAVE */
287 if (game->cave->player_state != GD_PL_TIMEOUT)
289 /* IF PLAYING FROM REPLAY, OVERWRITE KEYPRESS VARIABLES FROM REPLAY */
290 if (game->type == GD_GAMETYPE_REPLAY)
294 /* if the user does touch the keyboard, we immediately exit replay,
295 and he can continue playing */
296 result = gd_replay_get_next_movement(game->replay_from, &player_move, &fire, &suicide);
297 /* if could not get move from snapshot, continue from keyboard input. */
299 game->replay_no_more_movements++;
301 /* if no more available movements, and the user does not do anything,
302 we cover cave and stop game. */
303 if (game->replay_no_more_movements > 15)
304 game->state_counter = GAME_INT_COVER_START;
307 if (TapeIsPlaying_ReplayBD())
309 byte *action_rnd = TapePlayAction_BD();
311 if (action_rnd != NULL)
313 int action_bd = map_action_RND_to_BD(action_rnd[0]);
315 player_move = (action_bd & GD_REPLAY_MOVE_MASK);
316 fire = (action_bd & GD_REPLAY_FIRE_MASK) != 0;
321 gd_cave_iterate(game->cave, player_move, fire, suicide);
323 if (game->replay_record)
324 gd_replay_store_movement(game->replay_record, player_move, fire, suicide);
326 if (game->cave->score)
327 increment_score(game, game->cave->score);
329 gd_sound_play_cave(game->cave);
332 if (game->cave->player_state == GD_PL_EXITED)
334 if (game->cave->intermission &&
335 game->cave->intermission_rewardlife &&
336 game->player_lives != 0)
338 /* one life extra for completing intermission */
339 add_bonus_life(game, FALSE);
342 if (game->replay_record)
343 game->replay_record->success = TRUE;
345 /* start adding points for remaining time */
346 game->state_counter = GAME_INT_CHECK_BONUS_TIME;
347 gd_cave_clear_sounds(game->cave);
349 /* play cave finished sound */
350 gd_sound_play(game->cave, GD_S_FINISHED, O_NONE, -1, -1);
351 gd_sound_play_cave(game->cave);
354 if (game->cave->player_state == GD_PL_DIED ||
355 game->cave->player_state == GD_PL_TIMEOUT)
357 game_bd.game_over = TRUE;
358 game_bd.cover_screen = TRUE;
362 static GdGameState gd_game_main_int(GdGame *game, boolean allow_iterate, boolean fast_forward)
364 int millisecs_elapsed = 20;
365 boolean frame; /* set to true, if this will be an animation frame */
366 GdGameState return_state;
370 counter_next = GAME_INT_INVALID;
371 return_state = GD_GAME_INVALID_STATE;
372 game->milliseconds_anim += millisecs_elapsed; /* keep track of time */
373 frame = FALSE; /* set to true, if this will be an animation frame */
375 if (game->milliseconds_anim >= 40)
378 game->milliseconds_anim -= 40;
381 /* cannot be less than uncover start. */
382 if (game->state_counter < GAME_INT_LOAD_CAVE)
386 else if (game->state_counter == GAME_INT_LOAD_CAVE)
388 /* do the things associated with loading a new cave. function creates gfx buffer and the like. */
391 return_state = GD_GAME_NOTHING;
392 counter_next = GAME_INT_SHOW_STORY;
394 else if (game->state_counter == GAME_INT_SHOW_STORY)
396 /* for normal game, every cave can have a long string of description/story. show that. */
398 /* if we have a story... */
400 if (game->show_story && game->original_cave && game->original_cave->story->len != 0)
401 Info("Cave Story: %s", game->original_cave->story->str);
404 counter_next = GAME_INT_START_UNCOVER;
405 return_state = GD_GAME_NOTHING;
407 else if (game->state_counter == GAME_INT_START_UNCOVER)
409 /* the very beginning. */
411 /* cover all cells of cave */
412 for (y = 0; y < game->cave->h; y++)
413 for (x = 0; x < game->cave->w; x++)
414 game->cave->map[y][x] |= COVERED;
416 counter_next = game->state_counter + 1;
418 /* very important: tell the caller that we loaded a new cave. */
419 /* size of the cave might be new, colors might be new, and so on. */
420 return_state = GD_GAME_CAVE_LOADED;
422 else if (game->state_counter < GAME_INT_UNCOVER_ALL)
424 /* uncover animation */
426 /* to play cover sound */
427 gd_sound_play(game->cave, GD_S_COVERING, O_COVERED, -1, -1);
428 gd_sound_play_cave(game->cave);
430 counter_next = game->state_counter;
436 /* original game uncovered one cell per line each frame.
437 * we have different cave sizes, so uncover width * height / 40 random
438 * cells each frame. (original was width = 40).
439 * this way the uncovering is the same speed also for intermissions. */
440 for (j = 0; j < game->cave->w * game->cave->h / 40; j++)
442 y = g_random_int_range(0, game->cave->h);
443 x = g_random_int_range(0, game->cave->w);
445 game->cave->map[y][x] &= ~COVERED;
448 counter_next++; /* as we did something, advance the counter. */
451 return_state = GD_GAME_NOTHING;
453 else if (game->state_counter == GAME_INT_UNCOVER_ALL)
455 /* time to uncover the whole cave. */
456 for (y = 0; y < game->cave->h; y++)
457 for (x = 0; x < game->cave->w; x++)
458 game->cave->map[y][x] &= ~COVERED;
460 /* to stop uncover sound. */
461 gd_cave_clear_sounds(game->cave);
462 gd_sound_play_cave(game->cave);
464 counter_next = GAME_INT_CAVE_RUNNING;
465 return_state = GD_GAME_NOTHING;
467 else if (game->state_counter == GAME_INT_CAVE_RUNNING)
473 cavespeed = game->cave->speed; /* cave speed in ms, like 175ms/frame */
475 cavespeed = 40; /* if fast forward, ignore cave speed, and go as 25 iterations/sec */
477 /* ITERATION - cave is running. */
479 /* normally nothing happes. but if we iterate, this might change. */
480 return_state = GD_GAME_NOTHING;
482 /* if allowing cave movements, add elapsed time to timer. and then we can check what to do. */
484 game->milliseconds_game += millisecs_elapsed;
486 /* increment cycle (frame) counter for the current cave iteration */
489 if (game->milliseconds_game >= cavespeed)
493 game->milliseconds_game -= cavespeed;
494 pl = game->cave->player_state;
496 /* initialize buffers for last cave element and direction for next iteration */
497 for (y = 0; y < game->cave->h; y++)
499 for (x = 0; x < game->cave->w; x++)
501 game->last_element_buffer[y][x] = game->element_buffer[y][x];
502 game->dir_buffer[y][x] = GD_MV_STILL;
506 /* store last maximum number of cycles (to force redraw if changed) */
507 game->itermax_last = game->itermax;
509 /* update maximum number of cycles (frame) per cave iteration */
510 game->itermax = game->itercycle;
512 /* reset cycle (frame) counter for the next cave iteration */
515 iterate_cave(game, game->player_move, game->player_fire);
517 if (game->player_move == GD_MV_STILL)
519 game->player_move_stick = FALSE;
523 game->player_move_stick = TRUE;
524 game->player_move = GD_MV_STILL;
527 /* as we iterated, the score and the like could have been changed. */
528 return_state = GD_GAME_LABELS_CHANGED;
530 /* and if the cave timeouted at this moment, that is a special case. */
531 if (pl != GD_PL_TIMEOUT && game->cave->player_state == GD_PL_TIMEOUT)
532 return_state = GD_GAME_TIMEOUT_NOW;
535 /* do not change counter state, as it is set by iterate_cave() */
536 counter_next = game->state_counter;
538 else if (game->state_counter == GAME_INT_CHECK_BONUS_TIME)
540 /* before covering, we check for time bonus score */
543 /* if time remaining, bonus points are added. do not start animation yet. */
544 if (game->cave->time > 0)
546 /* subtract number of "milliseconds" - nothing to do with gameplay->millisecs! */
547 game->cave->time -= game->cave->timing_factor;
549 /* higher levels get more bonus points per second remained */
550 increment_score(game, game->cave->timevalue);
552 /* if much time (> 60s) remained, fast counter :) */
553 if (game->cave->time > 60 * game->cave->timing_factor)
555 /* decrement by nine each frame, so it also looks like a fast counter. 9 is 8 + 1! */
556 game->cave->time -= 8 * game->cave->timing_factor;
557 increment_score(game, game->cave->timevalue * 8);
560 /* just to be neat */
561 if (game->cave->time < 0)
562 game->cave->time = 0;
564 counter_next = game->state_counter; /* do not change yet */
568 game_bd.level_solved = TRUE;
569 game_bd.cover_screen = TRUE;
571 /* if no more points, start waiting a bit, and later start covering. */
572 counter_next = GAME_INT_WAIT_BEFORE_COVER;
575 if (game->cave->time / game->cave->timing_factor > 8)
576 gd_sound_play(game->cave, GD_S_FINISHED, O_NONE, -1, -1); /* play cave finished sound */
578 /* play bonus sound */
579 gd_cave_set_seconds_sound(game->cave);
580 gd_sound_play_cave(game->cave);
582 return_state = GD_GAME_LABELS_CHANGED;
586 return_state = GD_GAME_NOTHING;
588 /* do not change counter state, as it is set by iterate_cave() */
589 counter_next = game->state_counter;
592 else if (game->state_counter == GAME_INT_WAIT_BEFORE_COVER)
594 /* after adding bonus points, we wait some time before starting to cover.
595 this is the FIRST frame... so we check for game over and maybe jump there */
596 /* if no more lives, game is over. */
597 counter_next = game->state_counter;
599 if (game->type == GD_GAMETYPE_NORMAL && game->player_lives == 0)
600 return_state = GD_GAME_NO_MORE_LIVES;
602 return_state = GD_GAME_NOTHING;
604 else if (game->state_counter > GAME_INT_WAIT_BEFORE_COVER &&
605 game->state_counter < GAME_INT_COVER_START)
607 /* after adding bonus points, we wait some time before starting to cover.
608 ... and the other frames. */
609 counter_next = game->state_counter;
611 counter_next++; /* 40 ms elapsed, advance counter */
613 return_state = GD_GAME_NOTHING;
615 else if (game->state_counter == GAME_INT_COVER_START)
617 /* starting to cover. start cover sound. */
619 gd_cave_clear_sounds(game->cave);
620 gd_sound_play(game->cave, GD_S_COVERING, O_COVERED, -1, -1);
622 /* to play cover sound */
623 gd_sound_play_cave(game->cave);
625 counter_next = game->state_counter + 1;
626 return_state = GD_GAME_NOTHING;
628 else if (game->state_counter > GAME_INT_COVER_START &&
629 game->state_counter < GAME_INT_COVER_ALL)
632 gd_sound_play(game->cave, GD_S_COVERING, O_COVERED, -1, -1);
634 counter_next = game->state_counter;
640 counter_next++; /* 40 ms elapsed, doing cover: advance counter */
642 /* covering eight times faster than uncovering. */
643 for (j = 0; j < game->cave->w * game->cave->h * 8 / 40; j++)
644 game->cave->map[g_random_int_range(0, game->cave->h)][g_random_int_range (0, game->cave->w)] |= COVERED;
647 return_state = GD_GAME_NOTHING;
649 else if (game->state_counter == GAME_INT_COVER_ALL)
652 for (y = 0; y < game->cave->h; y++)
653 for (x = 0; x < game->cave->w; x++)
654 game->cave->map[y][x] |= COVERED;
656 counter_next = game->state_counter + 1;
657 return_state = GD_GAME_NOTHING;
659 /* to stop uncover sound. */
660 gd_cave_clear_sounds(game->cave);
661 gd_sound_play_cave(game->cave);
667 /* if this is a normal game: */
668 if (game->type == GD_GAMETYPE_NORMAL)
670 if (game->player_lives != 0)
671 return_state = GD_GAME_NOTHING; /* and go to next level */
673 return_state = GD_GAME_GAME_OVER;
677 /* for snapshots and replays and the like, this is the end. */
678 return_state = GD_GAME_STOP;
685 if (game->bonus_life_flash) /* bonus life - frames */
686 game->bonus_life_flash--;
688 game->animcycle = (game->animcycle + 1) % 8;
691 /* always render the cave to the gfx buffer;
692 however it may do nothing if animcycle was not changed. */
693 if (game->element_buffer && game->gfx_buffer)
694 gd_drawcave_game(game->cave, game->element_buffer, game->gfx_buffer,
695 game->bonus_life_flash != 0, game->animcycle, gd_no_invisible_outbox);
697 game->state_counter = counter_next;
702 void play_game_func(GdGame *game, int action)
705 boolean move_up = ((action & JOY_UP) != 0);
706 boolean move_down = ((action & JOY_DOWN) != 0);
707 boolean move_left = ((action & JOY_LEFT) != 0);
708 boolean move_right = ((action & JOY_RIGHT) != 0);
709 boolean fire = ((action & (JOY_BUTTON_1 | JOY_BUTTON_2)) != 0);
711 if (game->player_move_stick || move_up || move_down || move_left || move_right) // no "fire"!
714 game->player_move = gd_direction_from_keypress(move_up, move_down, move_left, move_right);
716 /* when storing last action, only update fire action with direction */
717 /* (prevents clearing direction if snapping stopped before action is performed) */
718 game->player_fire = fire;
721 /* if no direction was stored before, allow setting fire to current state */
722 if (game->player_move == GD_MV_STILL)
723 game->player_fire = fire;
725 /* tell the interrupt "20ms has passed" */
726 state = gd_game_main_int(game, !game->out_of_window, gd_keystate[SDL_SCANCODE_F]);
728 /* state of game, returned by gd_game_main_int */
731 case GD_GAME_CAVE_LOADED:
732 /* select colors, prepare drawing etc. */
733 gd_scroll_to_origin();
735 /* fill whole screen with black - cave might be smaller than previous! */
736 FillRectangle(gd_screen_bitmap, 0, 0, SXSIZE, SYSIZE, BLACK_PIXEL);
743 /* for the sdl version, it seems nicer if we first scroll, and then draw. */
744 /* scrolling for the sdl version will merely invalidate the whole gfx buffer. */
745 /* if drawcave was before scrolling, it would draw, scroll would invalidate,
746 and then it should be drawn again */
747 /* only do the drawing if the cave already exists. */
748 if (game->cave && game->element_buffer && game->gfx_buffer)
750 /* if fine scrolling, scroll at 50hz. if not, only scroll at every second call, so 25hz. */
751 /* do the scrolling. scroll exactly, if player is not yet alive */
752 game->out_of_window = gd_scroll(game, game->cave->player_state == GD_PL_NOT_YET, FALSE);