1 /*
2 
3     eboard - chess client
4     http://www.bergo.eng.br/eboard
5     https://github.com/fbergo/eboard
6     Copyright (C) 2000-2016 Felipe Bergo
7     fbergo/at/gmail/dot/com
8 
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13 
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 
23 */
24 
25 #include <iostream>
26 #include <stdlib.h>
27 #include <sys/time.h>
28 #include <unistd.h>
29 #include "clock.h"
30 #include "global.h"
31 #include "tstring.h"
32 
33 ClockMaster Chronos;
34 
35 int ChessClock::freeid=1;
36 
ClockMaster()37 ClockMaster::ClockMaster() {
38   timeout_on=0;
39   timeout_id=-1;
40 }
41 
append(ChessClock * clockp)42 void ClockMaster::append(ChessClock *clockp) {
43   clocks.push_back(clockp);
44   if (!timeout_on) {
45     timeout_id=gtk_timeout_add(90,clockmaster_timeout,this);
46     timeout_on=1;
47   }
48 }
49 
remove(ChessClock * clockp)50 void ClockMaster::remove(ChessClock *clockp) {
51   list<ChessClock *>::iterator li;
52   for(li=clocks.begin();li!=clocks.end();li++)
53     if ( (*li) == clockp ) {
54       clocks.erase(li);
55       return;
56     }
57 }
58 
update()59 void ClockMaster::update() {
60   list<ChessClock *>::iterator it;
61   for(it=clocks.begin();it!=clocks.end();it++)
62     (*it)->update();
63 }
64 
clockmaster_timeout(gpointer data)65 gint clockmaster_timeout(gpointer data) {
66   ClockMaster *cm;
67   cm=(ClockMaster *)data;
68   cm->update();
69   return 1;
70 }
71 
72 // ==================================================================
73 
ChessClock()74 ChessClock::ChessClock() {
75   active=CLK_STOPPED;
76   host=0;
77   host2=0;
78   mirror=0;
79   value[0]=value[1]=0;
80   t_ref[0]=t_ref[1]=Timestamp::now();
81   val_ref[0]=val_ref[1]=0;
82   id=freeid++;
83   countdownf=1;
84 
85   LastWarning = Timestamp::now();
86 
87   Chronos.append(this);
88 }
89 
~ChessClock()90 ChessClock::~ChessClock() {
91   active=CLK_STOPPED;
92   host=0;
93   Chronos.remove(this);
94 }
95 
setMirror(ChessClock * dest)96 void ChessClock::setMirror(ChessClock *dest) {
97   mirror=dest;
98 }
99 
setClock2(int whitemsec,int blackmsec,int activep,int countdown)100 void ChessClock::setClock2(int whitemsec,int blackmsec,int activep,int countdown)
101 {
102   global.debug("ChessClock","setClock");
103   countdownf=countdown;
104 
105   //  cerr << "setClock " << whitesec << ',' << blacksec << ',' << activep << endl;
106   //  cerr << "oldvals = " << value[0] << ',' << value[1] << endl;
107 
108   if (whitemsec != CLOCK_UNCHANGED)
109     value[0]=whitemsec;
110 
111   if (blackmsec != CLOCK_UNCHANGED)
112     value[1]=blackmsec;
113 
114   t_ref[0]=t_ref[1]=Timestamp::now();
115   val_ref[0]=value[0];
116   val_ref[1]=value[1];
117 
118   //  cerr << "newvals = " << value[0] << ',' << value[1] << endl;
119 
120   active=activep;
121   if (mirror)
122     mirror->setClock2(whitemsec,blackmsec,activep,countdown);
123 }
124 
incrementClock2(int which,int msecs)125 void ChessClock::incrementClock2(int which, int msecs) {
126   if (msecs==0)
127     return;
128   if (which==0)
129     which = active;
130   if (which == -1) {
131     value[0]   += msecs;
132     t_ref[0]   = Timestamp::now();
133     val_ref[0] = value[0];
134   }
135   if (which == 1) {
136     value[1]   += msecs;
137     t_ref[1]   = Timestamp::now();
138     val_ref[1] = value[1];
139   }
140   if (mirror)
141     mirror->incrementClock2(which, msecs);
142 }
143 
setHost(ClockHost * hostp)144 void ChessClock::setHost(ClockHost *hostp) {
145   host=hostp;
146 }
147 
setAnotherHost(ClockHost * hostp)148 void ChessClock::setAnotherHost(ClockHost *hostp) {
149   host2=hostp;
150 }
151 
getActive()152 int  ChessClock::getActive() {
153   return active;
154 }
155 
update()156 void ChessClock::update() {
157   Timestamp now;
158   bool u = false;
159   if (host) {
160     switch(active) {
161     case CLK_WHITE_RUNNING:
162       now=Timestamp::now();
163       value[0]=val_ref[0]+(countdownf?-1:1)*(int)((now-t_ref[0])*1000.0);
164       u = true;
165       break;
166     case CLK_BLACK_RUNNING:
167       now=Timestamp::now();
168       value[1]=val_ref[1]+(countdownf?-1:1)*(int)((now-t_ref[1])*1000.0);
169       u = true;
170       break;
171     }
172     if (u) {
173       host->updateClock();
174       if (host2) host2->updateClock();
175     }
176   }
177 }
178 
getValue2(int black)179 int ChessClock::getValue2(int black) {
180   return(value[black?1:0]);
181 }
182 
lowTimeWarning(piece mycolor)183 bool ChessClock::lowTimeWarning(piece mycolor) {
184   Timestamp now;
185 
186   // only countdown clocks can issue warnings
187   if (!countdownf) return false;
188 
189   now = Timestamp::now();
190   if (now - LastWarning < 1.0) return false;
191 
192   if ( (active==CLK_WHITE_RUNNING) &&
193        (mycolor==WHITE) && (value[0] <= 1000*global.LowTimeWarningLimit) ) {
194     global.timeRunningOut();
195     LastWarning = Timestamp::now();
196     return true;
197   }
198 
199   if ( (active==CLK_BLACK_RUNNING) &&
200        (mycolor==BLACK) && (value[1] <= 1000*global.LowTimeWarningLimit) ) {
201     global.timeRunningOut();
202     LastWarning = Timestamp::now();
203     return true;
204   }
205 
206   return false;
207 }
208 
TimeControl()209 TimeControl::TimeControl() {
210   mode = TC_NONE;
211 }
212 
operator =(TimeControl & src)213 TimeControl & TimeControl::operator=(TimeControl &src) {
214   mode = src.mode;
215   value[0] = src.value[0];
216   value[1] = src.value[1];
217   return(*this);
218 }
219 
operator ==(TimeControl & src)220 int TimeControl::operator==(TimeControl &src) {
221   if (mode != src.mode) return 0;
222   switch(mode) {
223   case TC_SPM:
224     return(value[0] == src.value[0]);
225   case TC_XMOVES:
226   case TC_ICS:
227     return(value[0] == src.value[0] && value[1] == src.value[1]);
228   default:
229     return 1;
230   }
231 }
232 
operator !=(TimeControl & src)233 int TimeControl::operator!=(TimeControl &src) { return(!(src==(*this))); }
234 
setSecondsPerMove(int seconds)235 void TimeControl::setSecondsPerMove(int seconds) {
236   mode = TC_SPM;
237   value[0] = seconds;
238 }
239 
240 // the serialization is "/a/b/c" (no quotes), a=mode, b,c=value
fromSerialization(const char * s)241 void TimeControl::fromSerialization(const char *s) {
242   tstring t;
243   int i;
244 
245   // cope with bookmarks from previous versions which just wrote
246   // the secs per move value
247   if (s[0]>='0' && s[0]<='9') {
248     setSecondsPerMove(atoi(s));
249     return;
250   }
251 
252   t.set(s);
253   t.setFail(-1);
254   i = t.tokenvalue("/");
255   if (i<0 || (i>2 && i!=99) ) {
256     mode = TC_NONE;
257     return;
258   }
259   mode = (TimeControlMode) i;
260   value[0] = t.tokenvalue("/");
261   value[1] = t.tokenvalue("/");
262 }
263 
setIcs(int base,int increment)264 void TimeControl::setIcs(int base /* secs */, int increment /* secs */) {
265   mode = TC_ICS;
266   value[0] = base;
267   value[1] = increment;
268 }
269 
setXMoves(int nmoves,int nsecs)270 void TimeControl::setXMoves(int nmoves, int nsecs) {
271   mode = TC_XMOVES;
272   value[0] = nmoves;
273   value[1] = nsecs;
274 }
275 
276 // make sure dest is not shorter than char[32] to avoid
277 // trouble
toXBoard(char * dest,int maxlen,int flags)278 void TimeControl::toXBoard(char *dest, int maxlen, int flags) {
279   switch(mode) {
280   case TC_NONE:
281     dest[0] = 0;
282     return;
283   case TC_SPM:
284     if (flags & TCF_GNUCHESS4QUIRK) {
285       if (value[0]%60)
286 	snprintf(dest,maxlen,"level 1 %d:%02d",value[0]/60,value[0]%60);
287       else
288 	snprintf(dest,maxlen,"level 1 %d",value[0]/60);
289     } else {
290       snprintf(dest,maxlen,"st %d",value[0]);
291     }
292     break;
293   case TC_ICS:
294     if (value[0]%60)
295       snprintf(dest,maxlen,"level 0 %d:%02d %d",value[0]/60,value[0]%60,value[1]);
296     else
297       snprintf(dest,maxlen,"level 0 %d %d",value[0]/60,value[1]);
298     break;
299   case TC_XMOVES:
300     if (value[1]%60)
301       snprintf(dest,maxlen,"level %d %d:%02d 0",value[0],value[1]/60,value[1]%60);
302     else
303       snprintf(dest,maxlen,"level %d %d 0",value[0],value[1]/60);
304     break;
305   }
306 }
307 
toShortString(char * dest,int maxlen)308 void TimeControl::toShortString(char *dest, int maxlen) {
309   char z[64],y[64];
310   switch(mode) {
311   case TC_NONE:
312     snprintf(dest,maxlen,_("untimed")); // TRANSLATE
313     break;
314   case TC_SPM:
315     TimeControl::secondsToString(z,64,value[0],true);
316     snprintf(dest,maxlen,_("%s/move"),z); // TRANSLATE
317     break;
318   case TC_ICS:
319     snprintf(dest,maxlen,"%d %d",value[0]/60,value[1]);
320     break;
321   case TC_XMOVES:
322     TimeControl::secondsToString(z,64,value[1],true);
323     snprintf(dest,maxlen,_("%d moves in %s"),value[0],z); // OK
324     break;
325   }
326 }
327 
toString(char * dest,int maxlen)328 void TimeControl::toString(char *dest, int maxlen) {
329   char z[64],y[64];
330   switch(mode) {
331   case TC_NONE:
332     snprintf(dest,maxlen,_("no time control set"));
333     break;
334   case TC_SPM:
335     TimeControl::secondsToString(z,64,value[0],true);
336     snprintf(dest,maxlen,_("%s per move"),z);
337     break;
338   case TC_ICS:
339     TimeControl::secondsToString(z,64,value[0],true);
340     TimeControl::secondsToString(y,64,value[1],true);
341     snprintf(dest,maxlen,_("initial time %s, increment %s"),z,y);
342     break;
343   case TC_XMOVES:
344     TimeControl::secondsToString(z,64,value[1],true);
345     snprintf(dest,maxlen,_("%d moves in %s"),value[0],z);
346     break;
347   }
348 }
349 
toPGN(char * dest,int maxlen)350 void TimeControl::toPGN(char *dest, int maxlen) {
351   switch(mode) {
352   case TC_NONE:
353     g_strlcpy(dest,"?",maxlen);
354     break;
355   case TC_SPM:
356     snprintf(dest,maxlen,"0+%d",value[0]);
357     break;
358   case TC_ICS:
359     snprintf(dest,maxlen,"%d+%d",value[0],value[1]);
360     break;
361   case TC_XMOVES:
362     snprintf(dest,maxlen,"%d/%d",value[0],value[1]);
363     break;
364   }
365 }
366 
isRegressive()367 bool TimeControl::isRegressive() {
368   return(mode == TC_ICS || mode == TC_XMOVES);
369 }
370 
startValue()371 int TimeControl::startValue() {
372   if (mode == TC_NONE || mode == TC_SPM)
373     return 0;
374   else
375     return(mode==TC_XMOVES?value[1]:value[0]);
376 }
377 
stringToSeconds(const char * src)378 int TimeControl::stringToSeconds(const char *src) {
379   tstring t;
380   int x, v=0;
381   if (src[0] == '-')
382     src++;
383   t.set(src);
384   t.setFail(-1);
385 
386   x=t.tokenvalue(":hms");
387   if (x>=0) {
388     v = x;
389     x=t.tokenvalue(":hms");
390     if (x>=0) {
391       v = 60*v + x;
392       x=t.tokenvalue(":hms");
393       if (x>=0)
394 	v = 60*v + x;
395     }
396   }
397   return v;
398 }
399 
secondsToString(char * dest,int maxlen,int secs,bool units)400 void TimeControl::secondsToString(char *dest, int maxlen, int secs, bool units) {
401   if (secs < 0) {
402     secs = -secs;
403     dest[0] = '-';
404     ++dest;
405     --maxlen;
406   }
407   if (secs < 60)
408     snprintf(dest,maxlen,"%d%c",secs, units?'s':0);
409   else if (secs < 3600)
410     snprintf(dest,maxlen,"%d%c%02d%c",
411 	     secs/60,units?'m':':',secs%60,units?'s':0);
412   else
413     snprintf(dest,maxlen,"%d%c%02d%c%02d%c",
414 	     secs/3600,     units?'h':':',
415 	     (secs%3600)/60,units?'m':':',
416 	     (secs%3600)%60,units?'s':0);
417 }
418 
TimeControlEditDialog(TimeControl * tc,bool allownone)419 TimeControlEditDialog::TimeControlEditDialog(TimeControl *tc, bool allownone) :
420   ModalDialog(N_("Edit Time Control"))
421 {
422   GtkWidget *v, *om, *hs, *bhb, *ok, *cancel;
423   GtkWidget *h[6], *l[6], *V[3];
424   GtkMenu *ddm;
425   int i;
426   char z[64];
427 
428   listener = 0;
429   src      = tc;
430   local    = (*src);
431 
432   if (local.mode == TC_NONE)
433     allownone = true;
434 
435   gtk_window_set_default_size(GTK_WINDOW(widget),300,150);
436 
437   v=gtk_vbox_new(FALSE,0);
438   gtk_container_add(GTK_CONTAINER(widget),v);
439 
440   om = gtk_option_menu_new();
441 
442   ddm = GTK_MENU(gtk_menu_new());
443   mi[0] = gtk_menu_item_new_with_label(_("Type: Fixed Time per Move"));
444   mi[1] = gtk_menu_item_new_with_label(_("Type: X Moves per Time Period"));
445   mi[2] = gtk_menu_item_new_with_label(_("Type: Fischer Clock (ICS-like)"));
446   mi[3] = gtk_menu_item_new_with_label(_("Type: Use engine's default setting"));
447   for(i=0;i< (allownone ? 4 : 3);i++) {
448     gtk_menu_shell_append(GTK_MENU_SHELL(ddm), mi[i]);
449     gtk_signal_connect(GTK_OBJECT(mi[i]),"activate",
450 		       GTK_SIGNAL_FUNC(tced_dropmenu),
451 		       (gpointer) this);
452     Gtk::show(mi[i],NULL);
453   }
454 
455   gtk_option_menu_set_menu(GTK_OPTION_MENU(om),GTK_WIDGET(ddm));
456 
457   gtk_box_pack_start(GTK_BOX(v),om,FALSE,TRUE,0);
458 
459   for(i=0;i<3;i++) {
460     f[i] = gtk_frame_new(0);
461     V[i] = gtk_vbox_new(FALSE,2);
462     h[i] = gtk_hbox_new(TRUE,2);
463     h[3+i] = gtk_hbox_new(TRUE,2);
464     gtk_container_add(GTK_CONTAINER(f[i]),V[i]);
465     gtk_container_set_border_width(GTK_CONTAINER(V[i]),4);
466     gtk_box_pack_start(GTK_BOX(V[i]),h[i],FALSE,TRUE,2);
467     gtk_box_pack_start(GTK_BOX(V[i]),h[i+3],FALSE,TRUE,2);
468     Gtk::show(h[i],h[i+3],V[i],NULL);
469   }
470 
471   e[0] = gtk_entry_new();
472   l[1] = gtk_label_new(_("per move"));
473   gtk_box_pack_start(GTK_BOX(h[0]),e[0],TRUE,TRUE,2);
474   gtk_box_pack_start(GTK_BOX(h[0]),l[1],FALSE,TRUE,2);
475   gtk_box_pack_start(GTK_BOX(v), f[0], FALSE, TRUE, 4);
476 
477   e[1] = gtk_entry_new();
478   l[2] = gtk_label_new(_("moves in"));
479   e[2] = gtk_entry_new();
480   l[3] = gtk_label_new(_("(time period)"));
481   gtk_box_pack_start(GTK_BOX(h[1]),e[1],TRUE,TRUE,2);
482   gtk_box_pack_start(GTK_BOX(h[1]),l[2],FALSE,TRUE,2);
483   gtk_box_pack_start(GTK_BOX(h[1+3]),e[2],TRUE,TRUE,2);
484   gtk_box_pack_start(GTK_BOX(h[1+3]),l[3],FALSE,TRUE,2);
485   gtk_box_pack_start(GTK_BOX(v), f[1], FALSE, TRUE, 2);
486 
487   l[4] = gtk_label_new(_("Starting Time:"));
488   e[3] = gtk_entry_new();
489   l[5] = gtk_label_new(_("Increment:"));
490   e[4] = gtk_entry_new();
491   gtk_box_pack_start(GTK_BOX(h[2]),l[4],FALSE,TRUE,2);
492   gtk_box_pack_start(GTK_BOX(h[2]),e[3],TRUE,TRUE,2);
493   gtk_box_pack_start(GTK_BOX(h[2+3]),l[5],FALSE,TRUE,2);
494   gtk_box_pack_start(GTK_BOX(h[2+3]),e[4],TRUE,TRUE,2);
495   gtk_box_pack_start(GTK_BOX(v), f[2], FALSE, TRUE, 2);
496 
497   l[0] = gtk_label_new(_("Times can be given as hh:mm:ss , mm:ss or ss"));
498   gtk_box_pack_start(GTK_BOX(v),l[0],FALSE,TRUE,4);
499 
500   bhb=gtk_hbutton_box_new();
501   gtk_button_box_set_layout(GTK_BUTTON_BOX(bhb), GTK_BUTTONBOX_END);
502   gtk_button_box_set_spacing(GTK_BUTTON_BOX(bhb), 5);
503   gtk_box_pack_end(GTK_BOX(v),bhb,FALSE,FALSE,4);
504   ok=gtk_button_new_with_label    (_("OK"));
505   GTK_WIDGET_SET_FLAGS(ok,GTK_CAN_DEFAULT);
506   cancel=gtk_button_new_with_label(_("Cancel"));
507   GTK_WIDGET_SET_FLAGS(cancel,GTK_CAN_DEFAULT);
508   gtk_box_pack_start(GTK_BOX(bhb),ok,TRUE,TRUE,0);
509   gtk_box_pack_start(GTK_BOX(bhb),cancel,TRUE,TRUE,0);
510   gtk_widget_grab_default(ok);
511 
512   hs=gtk_hseparator_new();
513   gtk_box_pack_end(GTK_BOX(v),hs,FALSE,TRUE,4);
514 
515   gtk_signal_connect(GTK_OBJECT(ok), "clicked",
516 		     GTK_SIGNAL_FUNC(tced_ok), (gpointer) this);
517 
518   setDismiss(GTK_OBJECT(cancel),"clicked");
519 
520   Gtk::show(om,GTK_WIDGET(ddm),l[0],l[1],l[2],l[3],l[4],l[5],
521 	    e[0],e[1],e[2],e[3],e[4],
522 	    hs,ok,cancel,bhb,v,NULL);
523 
524   switch(local.mode) {
525   case TC_SPM:
526     gtk_option_menu_set_history(GTK_OPTION_MENU(om), 0);
527     TimeControl::secondsToString(z,64,local.value[0]);
528     gtk_entry_set_text(GTK_ENTRY(e[0]),z);
529     gshow(f[0]);
530     break;
531   case TC_XMOVES:
532     gtk_option_menu_set_history(GTK_OPTION_MENU(om), 1);
533     snprintf(z,64,"%d",local.value[0]);
534     gtk_entry_set_text(GTK_ENTRY(e[1]),z);
535     TimeControl::secondsToString(z,64,local.value[1]);
536     gtk_entry_set_text(GTK_ENTRY(e[2]),z);
537     gshow(f[1]);
538     break;
539   case TC_ICS:
540     gtk_option_menu_set_history(GTK_OPTION_MENU(om), 2);
541     TimeControl::secondsToString(z,64,local.value[0]);
542     gtk_entry_set_text(GTK_ENTRY(e[3]),z);
543     TimeControl::secondsToString(z,64,local.value[1]);
544     gtk_entry_set_text(GTK_ENTRY(e[4]),z);
545     gshow(f[2]);
546     break;
547   case TC_NONE:
548     gtk_option_menu_set_history(GTK_OPTION_MENU(om), 3);
549     break;
550   }
551 }
552 
setUpdateListener(UpdateInterface * ui)553 void TimeControlEditDialog::setUpdateListener(UpdateInterface *ui) {
554   listener = ui;
555 }
556 
tced_dropmenu(GtkMenuItem * w,gpointer data)557 void tced_dropmenu(GtkMenuItem *w, gpointer data) {
558   TimeControlEditDialog *me = (TimeControlEditDialog *) data;
559   GtkWidget *ww = GTK_WIDGET(w);
560 
561   switch(me->local.mode) {
562   case TC_SPM:    gtk_widget_hide(me->f[0]); break;
563   case TC_XMOVES: gtk_widget_hide(me->f[1]); break;
564   case TC_ICS:    gtk_widget_hide(me->f[2]); break;
565   default:
566     break;
567   }
568 
569   if (ww == me->mi[0]) {
570     me->local.mode = TC_SPM;
571     gshow(me->f[0]);
572     gtk_widget_grab_focus(me->e[0]);
573   } else if (ww == me->mi[1]) {
574     me->local.mode = TC_XMOVES;
575     gshow(me->f[1]);
576     gtk_widget_grab_focus(me->e[1]);
577   } else if (ww == me->mi[2]) {
578     me->local.mode = TC_ICS;
579     gshow(me->f[2]);
580     gtk_widget_grab_focus(me->e[3]);
581   } else if (ww == me->mi[3]) {
582     me->local.mode = TC_NONE;
583   }
584 }
585 
tced_ok(GtkWidget * w,gpointer data)586 void tced_ok(GtkWidget *w, gpointer data) {
587   char z[128], y[128];
588 
589   TimeControlEditDialog *me = (TimeControlEditDialog *) data;
590 
591   switch(me->local.mode) {
592   case TC_SPM:
593     g_strlcpy(z, gtk_entry_get_text(GTK_ENTRY(me->e[0])), 128);
594     me->local.setSecondsPerMove( TimeControl::stringToSeconds(z) );
595     break;
596   case TC_XMOVES:
597     g_strlcpy(z, gtk_entry_get_text(GTK_ENTRY(me->e[1])), 128);
598     g_strlcpy(y, gtk_entry_get_text(GTK_ENTRY(me->e[2])), 128);
599     me->local.setXMoves( atoi(z), TimeControl::stringToSeconds(y) );
600     break;
601   case TC_ICS:
602     g_strlcpy(z, gtk_entry_get_text(GTK_ENTRY(me->e[3])), 128);
603     g_strlcpy(y, gtk_entry_get_text(GTK_ENTRY(me->e[4])), 128);
604     me->local.setIcs(TimeControl::stringToSeconds(z),
605 		     TimeControl::stringToSeconds(y) );
606     break;
607   }
608 
609   *(me->src) = me->local;
610   if (me->listener)
611     me->listener->update();
612   me->release();
613 }
614