From 21434f1913159483172db297db3c4f792ba9274a Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Fri, 13 Feb 2009 18:29:45 +0100 Subject: [PATCH] Save and load the current game on quit/start Save the current gamestate in a file and re-load it on next startup, so users can play complete games. We're missing some input sanitizing here, but the worst case is that Tennix won't start, in which case the user simply has to remove the gamestate file. --- TODO | 2 -- game.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ game.h | 3 ++ makefile | 2 +- tennix.c | 60 ++++++++++++++++++++++++++++------------ tennix.h | 6 ++++ 6 files changed, 149 insertions(+), 20 deletions(-) diff --git a/TODO b/TODO index 51e82eb..b5153a9 100644 --- a/TODO +++ b/TODO @@ -8,8 +8,6 @@ Target release date: 19.02.2009 RELEASE-CRITICAL - 3. Always save running game and allow to restart later - (fwrite GameState, BUT check for input issues and pointers!) 4. Make AI player a bit more intelligent & beatable 10. Clean-up the menu appearance / style to be more consistent (remove buttons as much as possible and replace with gfx) diff --git a/game.c b/game.c index f03b6bd..0e6416a 100644 --- a/game.c +++ b/game.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "tennix.h" #include "game.h" @@ -40,6 +41,7 @@ GameState *gamestate_new() { GameState template = { NULL, + -1, { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6, false, -1, false }, { { NULL, -1, GAME_X_MIN-RACKET_X_MID*2, GAME_Y_MID, 0.0, POWER_UP_FACTOR, POWER_DOWN_FACTOR, true, 0, DESIRE_NORMAL, PLAYER_TYPE_AI, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true }, @@ -97,6 +99,100 @@ GameState *gamestate_new() { } +int gamestate_save(GameState* s, const char* filename) +{ + Location *location; + const char* status; + InputDevice* input_devices[MAXPLAYERS]; + int i, result = 0; + FILE* fp = NULL; +#ifndef WIN32 + char tmp[MAXPATHLEN]; + + assert(getcwd(tmp, MAXPATHLEN) == tmp); + assert(chdir(getenv("HOME")) == 0); +#endif + + /** + * Process-specific data (pointers to data + * structures only of interest to this process) + * has to be "removed" before saving. + * + * It can later be restored (in gamestate_load). + **/ + + fp = fopen(filename, "w"); + if (fp == NULL) { + return -1; + } + + /* Save data for later recovery + clear */ + location = s->location; + s->location = NULL; + status = s->status; + s->status = NULL; + for (i=1; i<=MAXPLAYERS; i++) { + input_devices[i-1] = PLAYER(s, i).input; + PLAYER(s, i).input = NULL; + } + + if (fwrite(s, sizeof(GameState), 1, fp) != 1) { + result = -2; + } + + /* Restore process-specific data */ + s->location = location; + s->status = status; + for (i=1; i<=MAXPLAYERS; i++) { + PLAYER(s, i).input = input_devices[i-1]; + } + + fclose(fp); + +#ifndef WIN32 + assert(chdir(tmp) == 0); +#endif + + return result; +} + + +GameState* gamestate_load(const char* filename) +{ + FILE* fp; + GameState* s = NULL; +#ifndef WIN32 + char tmp[MAXPATHLEN]; + + assert(getcwd(tmp, MAXPATHLEN) == tmp); + assert(chdir(getenv("HOME")) == 0); +#endif + + fp = fopen(filename, "r"); + + if (fp != NULL) { + s = (GameState*)malloc(sizeof(GameState)); + if (s != NULL) { + if (fread(s, sizeof(GameState), 1, fp) == 1) { + /* Restore/create process-specific data */ + s->status = format_status(s); + /* FIXME: s->location, players' "input" */ + } else { + free(s); + s = NULL; + } + } + fclose(fp); + } + +#ifndef WIN32 + assert(chdir(tmp) == 0); +#endif + + return s; +} + + void gameloop(GameState *s) { Uint32 ot = SDL_GetTicks(); Uint32 nt; diff --git a/game.h b/game.h index de6f37f..2b7d8f3 100644 --- a/game.h +++ b/game.h @@ -155,6 +155,7 @@ enum { typedef struct { Location* location; + int current_location; /* index of loc. in global location table */ Ball ball; Player players[MAXPLAYERS]; Uint32 time; @@ -274,6 +275,8 @@ typedef struct { /* GameState handling*/ GameState *gamestate_new(); +int gamestate_save(GameState* s, const char* filename); +GameState* gamestate_load(const char* filename); /* Game module functions */ void gameloop(GameState*); diff --git a/makefile b/makefile index 9e4f0bb..b01379b 100644 --- a/makefile +++ b/makefile @@ -35,7 +35,7 @@ DATAROOTDIR ?= $(PREFIX)/share DATADIR ?= $(DATAROOTDIR)/games LIBS = -CFLAGS += -std=c99 -W -Wall -ansi -pedantic -Wcast-qual -Wwrite-strings -DVERSION=\"$(RELEASE)\" -O2 -DPREFIX=\"$(PREFIX)\" +CFLAGS += -std=c99 -W -Wall -ansi -pedantic -Wcast-qual -Wwrite-strings -DVERSION=\"$(RELEASE)\" -O2 -DPREFIX=\"$(PREFIX)\" -g ifeq ($(UPDRECTANGLE),1) CFLAGS += -DDRAW_UPDATE_RECTANGLE diff --git a/tennix.c b/tennix.c index e5f5e02..fa500af 100644 --- a/tennix.c +++ b/tennix.c @@ -101,7 +101,7 @@ int main( int argc, char** argv) { unsigned int night_start, night_end; GameState *current_game = NULL, *prepared_game = NULL; InputDevice* input_devices; - unsigned int input_device_count; + unsigned int input_device_count, input_device_id; Animation *intro; AnimationState *intro_playback; float wiggle; @@ -135,7 +135,6 @@ int main( int argc, char** argv) { 255, 50, 50 }; - int current_location = -1; int highlight_location = -1; float highlight_location_distance = 0.0; float new_location_distance = 0.0; @@ -230,6 +229,26 @@ int main( int argc, char** argv) { input_devices = find_input_devices(&input_device_count); + current_game = gamestate_load(GAMESTATE_FILE); + if (current_game != NULL) { + /* restore pointer to location */ + current_game->location = &(locations[current_game->current_location]); + /* */ + for (i=1; i<=MAXPLAYERS; i++) { + if (PLAYER(current_game, i).type == PLAYER_TYPE_HUMAN) { + input_device_id = PLAYER(current_game, i).input_device_index; + if (input_device_id < input_device_count) { + /* ok, we still have that device around */ + PLAYER(current_game, i).input = &(input_devices[input_device_id]); + } else { + /* the device vanished - set to AI (FIXME: select new device) */ + PLAYER(current_game, i).type = PLAYER_TYPE_AI; + PLAYER(current_game, i).input = NULL; + } + } + } + } + #ifdef ENABLE_FPS_LIMIT frames = 0; ft = SDL_GetTicks(); @@ -341,8 +360,10 @@ int main( int argc, char** argv) { #else PLAYER(prepared_game, 1).input_device_index = 2; #endif + PLAYER(prepared_game, 1).type = PLAYER_TYPE_HUMAN; + PLAYER(prepared_game, 1).input = &(input_devices[PLAYER(prepared_game, 1).input_device_index]); location_info_visible = false; - current_location = -1; + prepared_game->current_location = -1; highlight_location = -1; } break; @@ -486,18 +507,17 @@ int main( int argc, char** argv) { } } } - if (!location_info_visible) { - for (x=0; xcurrent_location != -1 && x==(unsigned int)(prepared_game->current_location)), 0); + } + } else { rectangle_alpha(location_info_xpos-5, location_info_ypos-5, 10+get_image_width(prepared_game->location->photo), get_image_height(prepared_game->location->photo)+100, 100, 0, 0, 200); show_sprite(prepared_game->location->photo, (i/1000)%(prepared_game->location->photo_frames), prepared_game->location->photo_frames, location_info_xpos, location_info_ypos, 255); font_draw_string(FONT_SMALL, prepared_game->location->name, location_info_xpos+MENU_OPTIONS_BORDER, location_info_ypos+MENU_OPTIONS_BORDER+200); @@ -632,15 +652,15 @@ int main( int argc, char** argv) { break; default: if (!location_info_visible && highlight_location != -1) { - current_location = highlight_location; - prepared_game->location = &(locations[current_location]); + prepared_game->current_location = highlight_location; + prepared_game->location = &(locations[prepared_game->current_location]); location_info_xpos = MAX(0, MIN(WIDTH-320-50, mx-320/2)); location_info_ypos = MAX(0, MIN(HEIGHT-200-160, my-200/2)); location_info_visible = true; } else { location_info_visible = false; - current_location = -1; highlight_location = -1; + prepared_game->current_location = -1; prepared_game->location = NULL; } break; @@ -711,6 +731,12 @@ int main( int argc, char** argv) { #endif } + if (current_game != NULL) { + if (gamestate_save(current_game, GAMESTATE_FILE) != 0) { + fprintf(stderr, "Warning: cannot save gamestate to %s\n", GAMESTATE_FILE); + } + } + uninit_graphics(); uninit_input(); diff --git a/tennix.h b/tennix.h index 43d6e99..7a69fe3 100644 --- a/tennix.h +++ b/tennix.h @@ -37,6 +37,12 @@ #define ARCHIVE_FILE "tennix.tnx" #define ARCHIVE_FILE_INSTALLED "/" PREFIX "/share/games/tennix/" ARCHIVE_FILE +#ifdef WIN32 +# define GAMESTATE_FILE "current_match-" VERSION ".tennix" +#else +# define GAMESTATE_FILE ".tennix-current_match-" VERSION +#endif + #define COPYRIGHT "Copyright 2003, 2007, 2008, 2009 Thomas Perl" #define URL "http://icculus.org/tennix/" -- 2.11.4.GIT