Add optional auto-save/restore feature using -a <filename> option 288/head
authorRob Swindell (on Debian Linux) <rob@synchro.net>
Fri, 3 Mar 2023 03:44:47 +0000 (19:44 -0800)
committerRob Swindell (on Debian Linux) <rob@synchro.net>
Fri, 3 Mar 2023 03:44:47 +0000 (19:44 -0800)
To enable use with online Bulletin Board Systems (BBSes) where users
may be disconnected unexpectedly, but would naturally want to resume
playing their same game, added support for an optional save game
path/filename to be specified on the command-line (very similar to
"-r <filename>"), except this save/restore file is:
1. automatically loaded/restored if it exists
2. automatically created when starting a new game
3. automatically updated when exiting a game for any reason
4. cannot be changed to a different path/filename by the user

Since a BBS server program can be expected to send a SIGHUP or SIGTERM
to the game process upon user disconnection (or timeout), those
signals are caught and a graceful termination will occur which saves
the current game state.

Build with ADVENT_AUTOSAVE defined to enable this option.

BUG:
The 'info' command still reports the save/suspend/pause commands as
valid, though they are not when this build option is used (same is
true of ADVENT_NOSAVE, and that doesn't apparently bother anyone).

Makefile
advent.h
main.c
saveresume.c
score.c

index 4e5f3316f814f9b2b106cc75054016af3a0477d6..38139dec2f89210d7860dc927ddfa022a7d25eff 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
 # Makefile for the open-source release of adventure 2.5
 
 # To build with save/resume disabled, pass CFLAGS="-DADVENT_NOSAVE"
+# To build with auto-save/resume enabled, pass CFLAGS="-D ADVENT_AUTOSAVE"
 
 VERS=$(shell sed -n <NEWS '/^[0-9]/s/:.*//p' | head -1)
 
index a68613baf46798c955e918a26fae32bbeb2f56cb..bf27d9bd52310c615979e9e82aa0a82ca3368e95 100644 (file)
--- a/advent.h
+++ b/advent.h
@@ -244,6 +244,9 @@ extern int32_t randrange(int32_t);
 extern int score(enum termination);
 extern void terminate(enum termination) __attribute__((noreturn));
 extern int savefile(FILE *, int32_t);
+#if defined ADVENT_AUTOSAVE
+extern void autosave(void);
+#endif
 extern int suspend(void);
 extern int resume(void);
 extern int restore(FILE *);
diff --git a/main.c b/main.c
index 1bdc64c3fbbc3b733283c9504f62efe207c9704f..c53660081b3fa5abfc8444c03ce244cb7e76113c 100644 (file)
--- a/main.c
+++ b/main.c
 
 #define DIM(a) (sizeof(a)/sizeof(a[0]))
 
+#if defined ADVENT_AUTOSAVE
+static FILE* autosave_fp;
+void autosave(void)
+{
+    if (autosave_fp != NULL) {
+        rewind(autosave_fp);
+        savefile(autosave_fp, /* version (auto): */0);
+        fflush(autosave_fp);
+    }
+}
+#endif
+
 // LCOV_EXCL_START
 // exclude from coverage analysis because it requires interactivity to test
 static void sig_handler(int signo)
@@ -26,6 +38,11 @@ static void sig_handler(int signo)
         if (settings.logfp != NULL)
             fflush(settings.logfp);
     }
+
+#if defined ADVENT_AUTOSAVE
+    if (signo == SIGHUP || signo == SIGTERM)
+        autosave();
+#endif
     exit(EXIT_FAILURE);
 }
 // LCOV_EXCL_STOP
@@ -50,7 +67,12 @@ int main(int argc, char *argv[])
 
     /*  Options. */
 
-#ifndef ADVENT_NOSAVE
+#if defined ADVENT_AUTOSAVE
+    const char* opts = "l:oa:";
+    const char* usage = "Usage: %s [-l logfilename] [-o] [-a filename] [script...]\n";
+    FILE *rfp = NULL;
+    const char* autosave_filename = NULL;
+#elif !defined ADVENT_NOSAVE
     const char* opts = "l:or:";
     const char* usage = "Usage: %s [-l logfilename] [-o] [-r restorefilename] [script...]\n";
     FILE *rfp = NULL;
@@ -72,7 +94,14 @@ int main(int argc, char *argv[])
             settings.oldstyle = true;
             settings.prompt = false;
             break;
-#ifndef ADVENT_NOSAVE
+#ifdef ADVENT_AUTOSAVE
+        case 'a':
+            rfp = fopen(optarg, READ_MODE);
+            autosave_filename = optarg;
+            signal(SIGHUP, sig_handler);
+            signal(SIGTERM, sig_handler);
+            break;
+#elif !defined ADVENT_NOSAVE
         case 'r':
             rfp = fopen(optarg, "r");
             if (rfp == NULL)
@@ -88,7 +117,10 @@ int main(int argc, char *argv[])
                     "        -l create a log file of your game named as specified'\n");
             fprintf(stderr,
                     "        -o 'oldstyle' (no prompt, no command editing, displays 'Initialising...')\n");
-#ifndef ADVENT_NOSAVE
+#if defined ADVENT_AUTOSAVE
+            fprintf(stderr,
+                    "        -a automatic save/restore from specified saved game file\n");
+#elif !defined ADVENT_NOSAVE
             fprintf(stderr,
                     "        -r restore from specified saved game file\n");
 #endif
@@ -105,14 +137,26 @@ int main(int argc, char *argv[])
     /*  Initialize game variables */
     int seedval = initialise();
 
-#ifndef ADVENT_NOSAVE
+#if !defined ADVENT_NOSAVE
     if (!rfp) {
         game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]);
         if (game.novice)
             game.limit = NOVICELIMIT;
     } else {
         restore(rfp);
+#if defined ADVENT_AUTOSAVE
+        score(scoregame);
+#endif
     }
+#if defined ADVENT_AUTOSAVE
+    if (autosave_filename != NULL) {
+        if ((autosave_fp = fopen(autosave_filename, WRITE_MODE)) == NULL) {
+            perror(autosave_filename);
+            return EXIT_FAILURE;
+        }
+        autosave();
+    }
+#endif
 #else
     game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]);
     if (game.novice)
index 25757c4f24be83c82d66d9e71d0a70a7ff1b15b2..ad9f844b16b7e289d2924432d43f5913909024ac 100644 (file)
@@ -62,7 +62,7 @@ int suspend(void)
      *  battles or to start over after learning zzword).
      *  If ADVENT_NOSAVE is defined, do nothing instead. */
 
-#ifdef ADVENT_NOSAVE
+#if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE
     return GO_UNKNOWN;
 #endif
     FILE *fp = NULL;
@@ -93,7 +93,7 @@ int resume(void)
     /*  Resume.  Read a suspended game back from a file.
      *  If ADVENT_NOSAVE is defined, do nothing instead. */
 
-#ifdef ADVENT_NOSAVE
+#if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE
     return GO_UNKNOWN;
 #endif
     FILE *fp = NULL;
diff --git a/score.c b/score.c
index 7e6b28c26ae9ee02a96f3d596983f39a6abae393..24dcc0a29d7bcca580f720e052abed574953a58b 100644 (file)
--- a/score.c
+++ b/score.c
@@ -117,6 +117,9 @@ void terminate(enum termination mode)
 /* End of game.  Let's tell him all about it. */
 {
     int points = score(mode);
+#if defined ADVENT_AUTOSAVE
+    autosave();
+#endif
 
     if (points + game.trnluz + 1 >= mxscor && game.trnluz != 0)
         rspeak(TOOK_LONG);