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