Complete.Org: Mailing Lists: Archives: freeciv-dev: March 2000:
[Freeciv-Dev] patch for long-term play
Home

[Freeciv-Dev] patch for long-term play

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: freeciv-dev@xxxxxxxxxxx
Subject: [Freeciv-Dev] patch for long-term play
From: jm@xxxxxxxxxx (Justin Mason)
Date: Mon, 27 Mar 2000 14:17:34 +0100

Hi all,

as promised, here's a patch (against the March 26 CVS snapshot) to enable
long-term play. It provides:

  > explain minturn
  Option: minturn  -  Minimum seconds per turn
  Description:
    Each turn will not end until this amount of time has elapsed, even if all
    players have hit "end turn". Zero means turns will end without a minimum
    time.
  Status: changeable
  Value: 0, Minimum: 0, Default: 0, Maximum: 99999

  > explain turntrigger
  Option: turntrigger  -  Trigger file for next turn
  Description:
    This should specify the filename of a file used to trigger turn changes,
    relative to the data directory.  If this isnon-empty, turns will not end
    until the modification dateof this file changes.  This file must exist
    before the gamestarts.  Default is empty (i.e. turns happen normally,under
    game control).
  Status: changeable
  Value: "", Default: ""

The second bit is the most important. Rather than reinventing the wheel
(and it would have been quite a tricky wheel at that!), I decided to let
cron do the hard work; this patch allows you to "touch" a file from cron
at any time you choose, triggering a turn change.  The code only stats
once every 10 calls (roughly 10 secs in this case), so (a) there may be up
to 10 second delay between the "touch" and the turn start, and (b) the
load incurred should be minimal.  I chose this file-based method as I
reckon it'd be more portable than signals.

Since it uses cron, it means a schedule of 11am, 1pm, 3pm, 5pm Monday to
Friday is easy:

  0 11,13,15,17 * * 1,2,3,4,5 /bin/touch /path/to/freeciv/turntrigger

(although it'd obviously be better if a script which mailed the players
saying "turn's ready" was used -- but you get the idea ;)

BTW I've also made timeout go up to 99999 seconds to support long-term
games.

Both turntrigger and minturn are NOT currently sent to clients. Not sure
if they should be, or how this is handled -- so I left them as
server-only.

Note: sys/stat.h is now checked in configure; and I didn't include
"configure" itself as my ver of autoconf makes a mess of it! This means
that you'll need to "autoconf; rm config.cache; ./configure" to use this
patch.

BTW I haven't tested it heavily, just a bit of manual testing.  My set of
players have changed their mind and now want to simply play "blitz-style",
grrr... ;)

(I'm impressed by freeciv's code and development model too, BTW.  It was
nice to find out I could submit a patch, instead of mucking about with cvs
as so many pkgs do nowadays!)

--j.

diff -ru freeciv.old/common/game.h freeciv/common/game.h
--- freeciv.old/common/game.h   Mon Mar 27 13:12:14 2000
+++ freeciv/common/game.h       Mon Mar 27 13:14:29 2000
@@ -46,6 +46,8 @@
   int tech;
   int skill_level;
   int timeout;
+  int minturn;
+  char turntrigger[MAX_LEN_NAME];
   int end_year;
   int year;
   int techlevel;
@@ -265,9 +267,13 @@
 #define GAME_MIN_SPACERACE           0
 #define GAME_MAX_SPACERACE           1
 
-#define GAME_DEFAULT_TIMEOUT          0
-#define GAME_MIN_TIMEOUT              0
-#define GAME_MAX_TIMEOUT            999
+#define GAME_DEFAULT_MIN_TURN_TIME   0
+#define GAME_MIN_MIN_TURN_TIME       0
+#define GAME_MAX_MIN_TURN_TIME       99999
+
+#define GAME_DEFAULT_TIMEOUT         0
+#define GAME_MIN_TIMEOUT             0
+#define GAME_MAX_TIMEOUT             99999
 
 #define GAME_DEFAULT_BARBARIANRATE   2
 #define GAME_MIN_BARBARIANRATE       0
@@ -288,6 +294,8 @@
 #define GAME_OLD_DEFAULT_SKILL_LEVEL 5  /* normal; for old save games */
 
 #define GAME_DEFAULT_DEMOGRAPHY      "NASRLPEMOqrb"
+
+#define GAME_DEFAULT_TURNTRIGGER     ""
 
 #define GAME_START_YEAR -4000
 
diff -ru freeciv.old/config.h.in freeciv/config.h.in
--- freeciv.old/config.h.in     Mon Mar 27 13:12:11 2000
+++ freeciv/config.h.in Mon Mar 27 13:14:29 2000
@@ -238,6 +238,9 @@
 /* Define if you have the <sys/uio.h> header file.  */
 #undef HAVE_SYS_UIO_H
 
+/* Define if you have the <sys/stat.h> header file.  */
+#undef HAVE_SYS_STAT_H
+
 /* Define if you have the <termios.h> header file.  */
 #undef HAVE_TERMIOS_H
 
diff -ru freeciv.old/configure.in freeciv/configure.in
--- freeciv.old/configure.in    Mon Mar 27 13:12:11 2000
+++ freeciv/configure.in        Mon Mar 27 13:14:58 2000
@@ -359,7 +359,8 @@
 AC_HEADER_STDC
 AC_CHECK_HEADERS(arpa/inet.h netdb.h netinet/in.h pwd.h sys/ioctl.h \
                  sys/select.h sys/signal.h sys/socket.h sys/termio.h \
-                sys/time.h sys/types.h sys/uio.h termios.h unistd.h fcntl.h)
+                sys/time.h sys/types.h sys/uio.h sys/stat.h termios.h \
+                unistd.h fcntl.h)
 if test x$client = xxaw; then
   dnl Want to get appropriate -I flags:
   fc_save_CPPFLAGS="$CPPFLAGS"
diff -ru freeciv.old/server/civserver.c freeciv/server/civserver.c
--- freeciv.old/server/civserver.c      Mon Mar 27 13:12:22 2000
+++ freeciv/server/civserver.c  Mon Mar 27 13:14:30 2000
@@ -344,6 +344,7 @@
 
   nations_avail = fc_calloc( game.playable_nation_count, sizeof(int));
   nations_used = fc_calloc( game.playable_nation_count, sizeof(int));
+  setup_turntrigger();
 
 main_start_players:
 
diff -ru freeciv.old/server/gamehand.c freeciv/server/gamehand.c
--- freeciv.old/server/gamehand.c       Mon Mar 27 13:12:23 2000
+++ freeciv/server/gamehand.c   Mon Mar 27 13:19:43 2000
@@ -188,6 +188,12 @@
   ginfo.techlevel=game.techlevel;
   ginfo.skill_level=game.skill_level;
   ginfo.timeout=game.timeout;
+  /* Mar 24 2000 jm: commented to avoid marshalling errors
+   * when talking to a client without this patch; client does not need
+   * to know this, as far as I can see. Ditto for game.turntrigger.
+   * Change this if you like ;)
+   */
+  /* ginfo.minturn=game.minturn; */
   ginfo.end_year=game.end_year;
   ginfo.year=game.year;
   ginfo.min_players=game.min_players;
@@ -296,6 +302,9 @@
   if (game.skill_level==0)
     game.skill_level = GAME_OLD_DEFAULT_SKILL_LEVEL;
   game.timeout       = secfile_lookup_int(file, "game.timeout");
+  game.minturn      = secfile_lookup_int(file, "game.minturn");
+  sz_strlcpy(game.turntrigger,
+          secfile_lookup_str_default(file, "", "game.turntrigger"));
   game.end_year      = secfile_lookup_int(file, "game.end_year");
   game.techlevel     = secfile_lookup_int(file, "game.techlevel");
   game.year          = secfile_lookup_int(file, "game.year");
@@ -551,6 +560,8 @@
   secfile_insert_int(file, game.tech, "game.tech");
   secfile_insert_int(file, game.skill_level, "game.skill_level");
   secfile_insert_int(file, game.timeout, "game.timeout");
+  secfile_insert_int(file, game.minturn, "game.minturn");
+  secfile_insert_str(file, game.turntrigger, "game.turntrigger");
   secfile_insert_int(file, game.end_year, "game.end_year");
   secfile_insert_int(file, game.year, "game.year");
   secfile_insert_int(file, game.techlevel, "game.techlevel");
diff -ru freeciv.old/server/sernet.c freeciv/server/sernet.c
--- freeciv.old/server/sernet.c Mon Mar 27 13:12:23 2000
+++ freeciv/server/sernet.c     Mon Mar 27 13:18:21 2000
@@ -62,11 +62,15 @@
 #ifdef HAVE_ARPA_INET_H
 #include <arpa/inet.h>
 #endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
 
 #include "log.h"
 #include "packets.h"
 #include "shared.h"
 #include "support.h"
+#include "fcintl.h"
 
 #include "civserver.h"
 #include "console.h"
@@ -87,6 +91,9 @@
 #endif
 extern int force_end_of_sniff;
 
+static char turntrigger_buf[512];
+static int turn_triggered;
+
 
 /*****************************************************************************/
 void close_connection(struct connection *pconn)
@@ -109,6 +116,68 @@
   close(sock);
 }
 
+/***************************************************************************** 
+  Allow turn changes to be triggered by an external process, using
+  a file to communicate the changes.  The idea is that an irregular
+  turn schedule can be used (e.g. 11am, 1pm, 3pm, 5pm Mon-Fri)
+  without reinventing cron's wheel.  I chose a file's modification time
+  as the interface as (a) it's fast and (b) portable. <jm@xxxxxxxxxx>
+*****************************************************************************/
+void setup_turntrigger (void)
+{
+  char *dfile;
+
+  turn_triggered = 0; *turntrigger_buf = '\0';
+  if (*game.turntrigger == '\0') { return; }
+
+  dfile = datafilename (game.turntrigger);
+  if (!dfile) {
+    freelog(LOG_FATAL,
+       _("Could not find readable turntrigger file \"%s\""),
+       game.turntrigger);
+    freelog(LOG_FATAL, _("The data path may be set via"
+       " the environment variable FREECIV_PATH."));
+    freelog(LOG_FATAL,
+       _("Current data path is: \"%s\""), datafilename(NULL));
+    exit (1);
+  }
+
+  sz_strlcpy (turntrigger_buf, dfile);
+}
+
+/***************************************************************************** 
+  Check to see if the turntrigger file has been updated.  We only poll
+  it once every 10 calls to avoid wasting resources with needless polling;
+  it's unlikely that the turn will be triggered more frequently than
+  every 10 seconds.
+  Since you could, concievably, set up a game with timeout, minturn,
+  and turntrigger all on, we need to keep track of turn triggers that
+  have occurred, but been ignored because one of the other timeouts
+  were not ready -- hence the turn_triggered variable.
+*****************************************************************************/
+int check_turntrigger(void)
+{
+  static time_t lastmod;
+  struct stat sbuf;
+  static int trig_check_count = 0;
+
+  if (*turntrigger_buf == '\0') { return 0; }
+  if (turn_triggered) { return 1; }
+  if ((trig_check_count++) % 10 == 0) { return 0; }
+
+  stat (turntrigger_buf, &sbuf);
+  if (sbuf.st_mtime == lastmod) { return 0; }
+
+  lastmod = sbuf.st_mtime;
+  turn_triggered = 1;
+  return 1;
+}
+
+void clear_turntrigger (void)
+{
+  turn_triggered = 0;
+}
+
 /*****************************************************************************
 Get and handle:
 - new connections,
@@ -128,6 +197,8 @@
   fd_set readfs;
   struct timeval tv;
   static time_t time_at_turn_end;
+  static time_t min_time_at_turn_end;
+  time_t now;
   static int year;
 #ifdef SOCKET_ZERO_ISNT_STDIN
   char buf[BUF_SIZE+1];
@@ -135,16 +206,21 @@
 #endif
   
   if(year!=game.year) {
-    time_at_turn_end = time(NULL) + game.timeout;
+    time (&now);
+    min_time_at_turn_end = now + game.minturn;
+    time_at_turn_end = now + game.timeout;
     if (server_state == RUN_GAME_STATE) year=game.year;
   }
   
   while(1) {
     con_prompt_on();           /* accepting new input */
     
-    if(force_end_of_sniff) {
+    if((force_end_of_sniff)
+      && (time(NULL)>min_time_at_turn_end)
+      && check_turntrigger()) {
       force_end_of_sniff=0;
       con_prompt_off();
+      clear_turntrigger();
       return 2;
     }
     
@@ -165,10 +241,14 @@
     
     if(select(max_desc+1, &readfs, NULL, NULL, &tv)==0) { /* timeout */
       send_server_info_to_metaserver(0,0);
+      time (&now);
       if((game.timeout) 
-       && (time(NULL)>time_at_turn_end)
-       && (server_state == RUN_GAME_STATE)){
+       && (now>time_at_turn_end)
+       && (now>min_time_at_turn_end)
+       && (server_state == RUN_GAME_STATE)
+       && check_turntrigger()) {
        con_prompt_off();
+       clear_turntrigger();
        return 0;
       }
 #ifdef SOCKET_ZERO_ISNT_STDIN
@@ -230,12 +310,18 @@
   }
   con_prompt_off();
   
+  time (&now);
   if((game.timeout) 
-    && (time(NULL)>time_at_turn_end)
-    && (game.timeout)) return 0;
+    && (now>time_at_turn_end)
+    && (now>min_time_at_turn_end)
+    && check_turntrigger()){
+    clear_turntrigger();
+    return 0;
+  }
+
   return 1;
 }
-  
+
   
 static void nonblock(int sockfd)
 {
diff -ru freeciv.old/server/sernet.h freeciv/server/sernet.h
--- freeciv.old/server/sernet.h Mon Mar 27 13:12:23 2000
+++ freeciv/server/sernet.h     Mon Mar 27 13:18:21 2000
@@ -25,5 +25,6 @@
 void close_connections_and_socket(void);
 void init_connections(void);
 void close_connection(struct connection *pconn);
+void setup_turntrigger (void);
 
 #endif  /* FC__SERNET_H */
diff -ru freeciv.old/server/stdinhand.c freeciv/server/stdinhand.c
--- freeciv.old/server/stdinhand.c      Mon Mar 27 13:12:23 2000
+++ freeciv/server/stdinhand.c  Mon Mar 27 13:18:21 2000
@@ -553,6 +553,14 @@
     GAME_MIN_END_YEAR, GAME_MAX_END_YEAR, GAME_DEFAULT_END_YEAR,
     N_("Year the game ends"), "" },
 
+  { "minturn", &game.minturn,
+    SSET_META, SSET_TO_CLIENT,
+    GAME_MIN_MIN_TURN_TIME, GAME_MAX_MIN_TURN_TIME, GAME_DEFAULT_MIN_TURN_TIME,
+    N_("Minimum seconds per turn"),
+    N_("Each turn will not end until this amount of time has elapsed, "
+       "even if all players have hit \"end turn\". Zero means turns will "
+       "end without a minimum time.") },
+
   { "timeout", &game.timeout,
     SSET_META, SSET_TO_CLIENT,
     GAME_MIN_TIMEOUT, GAME_MAX_TIMEOUT, GAME_DEFAULT_TIMEOUT,
@@ -610,6 +618,19 @@
        "(with the -g command line option).  "
        "Levels: 0=no logging, 20=standard logging, 30=detailed logging, "
        "40=debuging logging.") },
+
+  { "turntrigger", NULL,
+    SSET_RULES, SSET_SERVER_ONLY,
+    0, 0, 0,
+    N_("Trigger file for next turn"),
+    N_("This should specify the filename of a file used to trigger "
+       "turn changes, relative to the data directory.  If this is"
+       "non-empty, turns will not end until the modification date"
+       "of this file changes.  This file must exist before the game"
+       "starts.  Default is empty (i.e. turns happen normally,"
+       "under game control)."),
+    game.turntrigger, GAME_DEFAULT_TURNTRIGGER,
+    sizeof(game.turntrigger) },
 
   { NULL, NULL,
     SSET_LAST, SSET_SERVER_ONLY,

[Prev in Thread] Current Thread [Next in Thread]
  • [Freeciv-Dev] patch for long-term play, Justin Mason <=