Enhance multi-session TC clock handling
authorH.G. Muller <h.g.muller@hccnet.nl>
Mon, 4 Oct 2010 09:50:38 +0000 (11:50 +0200)
committerArun Persaud <arun@nubati.net>
Tue, 5 Oct 2010 02:36:08 +0000 (19:36 -0700)
Some new TC types are introduced: Bronstein and free sessions. The
former is implemented by letting an exclamation point '!' in front of an
increment (in the -tc string) indicate that the increment is limited to
the time actually used on the previous move. The latter is an interval
of given duraton in which you can do any number of moves (even zero),
before the next session starts. This is needed for implementing Shogi
byoyomi-type TC, as a first session, followed by a session of fixed-time
per move. The latter can now be specified with the -tc argument as a
degenerate case of Bronstein, where the time on the clock is <= the
increment (so that it is never possible to think longer than the
increment). A TC of 5 min + 10 sec byoyomi can then be indicated as
300:10+!10.

The sessions are now separated by ':' in stead of '+', and all times in
the TC string are converted to seconds first (in ParseTimeControl), so
the fullTCstring can now be directly used in a PGN tag.

Note that this patch only addresses clock handling; the engine will not
be informed yet if the TC type or parameters change at the start of new
session!

backend.c

index 2a6e2e6..e12be26 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -440,9 +440,10 @@ TimeMark lastNodeCountTime;
 long lastNodeCount=0;
 int have_sent_ICS_logon = 0;
 int movesPerSession;
-long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
+int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
+long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
 long timeControl_2; /* [AS] Allow separate time controls */
-char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
+char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
 long timeRemaining[2][MAX_MOVES];
 int matchGame = 0;
 TimeMark programStartTime;
@@ -970,46 +971,55 @@ int NextTimeControlFromString( char ** str, long * value )
     return result;
 }
 
-int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
-{   /* [HGM] routine added to read '+moves/time' for secondary time control */
-    int result = -1; long temp, temp2;
+int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
+{   /* [HGM] routine added to read '+moves/time' for secondary time control. */
+    int result = -1, type = 0; long temp, temp2;
 
-    if(**str != '+') return -1; // old params remain in force!
+    if(**str != ':') return -1; // old params remain in force!
     (*str)++;
-    if( NextTimeControlFromString( str, &temp ) ) return -1;
+    if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
+    if( NextIntegerFromString( str, &temp ) ) return -1;
+    if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
 
     if(**str != '/') {
         /* time only: incremental or sudden-death time control */
         if(**str == '+') { /* increment follows; read it */
             (*str)++;
+            if(**str == '!') type = *(*str)++; // Bronstein TC
             if(result = NextIntegerFromString( str, &temp2)) return -1;
             *inc = temp2 * 1000;
         } else *inc = 0;
-        *moves = 0; *tc = temp * 1000; 
+        *moves = 0; *tc = temp * 1000; *incType = type;
         return 0;
-    } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
+    }
 
     (*str)++; /* classical time control */
-    result = NextTimeControlFromString( str, &temp2);
+    result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
+
     if(result == 0) {
-        *moves = temp/60;
+        *moves = temp;
         *tc    = temp2 * 1000;
         *inc   = 0;
+        *incType = type;
     }
     return result;
 }
 
-int GetTimeQuota(int movenr)
+int GetTimeQuota(int movenr, int lastUsed, char *tcString)
 {   /* [HGM] get time to add from the multi-session time-control string */
-    int moves=1; /* kludge to force reading of first session */
+    int incType, moves=1; /* kludge to force reading of first session */
     long time, increment;
-    char *s = fullTimeControlString;
+    char *s = tcString;
 
-    if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
+    if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
+    if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
     do {
-        if(moves) NextSessionFromString(&s, &moves, &time, &increment);
+        if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
+        nextSession = s; suddenDeath = moves == 0 && increment == 0;
         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
         if(movenr == -1) return time;    /* last move before new session     */
+        if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
+        if(incType == '!' && lastUsed < increment) increment = lastUsed;
         if(!moves) return increment;     /* current session is incremental   */
         if(movenr >= 0) movenr -= moves; /* we already finished this session */
     } while(movenr >= -1);               /* try again for next session       */
@@ -1025,19 +1035,22 @@ ParseTimeControl(tc, ti, mps)
 {
   long tc1;
   long tc2;
-  char buf[MSG_SIZ];
+  char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
+  int min, sec=0;
   
   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
+  if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
+      sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
   if(ti > 0) {
     if(mps)
-      sprintf(buf, "+%d/%s+%d", mps, tc, ti);
-    else sprintf(buf, "+%s+%d", tc, ti);
+      sprintf(buf, ":%d/%s+%d", mps, mytc, ti);
+    else sprintf(buf, ":%s+%d", mytc, ti);
   } else {
     if(mps)
-             sprintf(buf, "+%d/%s", mps, tc);
-    else sprintf(buf, "+%s", tc);
+             sprintf(buf, ":%d/%s", mps, mytc);
+    else sprintf(buf, ":%s", mytc);
   }
-  fullTimeControlString = StrSave(buf);
+  fullTimeControlString = StrSave(buf); // this should now be in PGN format
   
   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
     return FALSE;
@@ -14058,15 +14071,20 @@ CheckTimeControl()
     /*
      * add time to clocks when time control is achieved ([HGM] now also used for increment)
      */
-    if ( !WhiteOnMove(forwardMostMove) )
+    if ( !WhiteOnMove(forwardMostMove) ) {
        /* White made time control */
-        whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+        lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
+        whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
         /* [HGM] time odds: correct new time quota for time odds! */
                                             / WhitePlayer()->timeOdds;
-      else
+        lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
+    } else {
+        lastBlack -= blackTimeRemaining;
        /* Black made time control */
-        blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+        blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
                                             / WhitePlayer()->other->timeOdds;
+        lastWhite = whiteTimeRemaining;
+    }
 }
 
 void
@@ -14182,13 +14200,15 @@ ResetClocks()
        whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
        blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
     } else { /* [HGM] correct new time quote for time odds */
-        whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
-        blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
+        whiteTC = blackTC = fullTimeControlString;
+        whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
+        blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
     }
     if (whiteFlag || blackFlag) {
        DisplayTitle("");
        whiteFlag = blackFlag = FALSE;
     }
+    lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
     DisplayBothClocks();
 }
 
@@ -14216,15 +14236,28 @@ DecrementClocks()
     if (WhiteOnMove(forwardMostMove)) {
        if(whiteNPS >= 0) lastTickLength = 0;
        timeRemaining = whiteTimeRemaining -= lastTickLength;
+        if(timeRemaining < 0) {
+            GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
+            if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
+                whiteStartMove = forwardMostMove; whiteTC = nextSession;
+                lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
+            }
+        }
        DisplayWhiteClock(whiteTimeRemaining - fudge,
                          WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
     } else {
        if(blackNPS >= 0) lastTickLength = 0;
        timeRemaining = blackTimeRemaining -= lastTickLength;
+        if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
+            GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
+            if(suddenDeath) {
+                blackStartMove = forwardMostMove;
+                lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
+            }
+        }
        DisplayBlackClock(blackTimeRemaining - fudge,
                          !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
     }
-
     if (CheckFlags()) return;
        
     tickStartTM = now;