Allow display of 50-move counter in zippy mode
[xboard.git] / engineoutput.c
1 /*
2  * engineoutput.c - split-off backe-end from Engine output (PV) by HGM
3  *
4  * Author: Alessandro Scotti (Dec 2005)
5  *
6  * Copyright 2005 Alessandro Scotti
7  *
8  * Enhancements Copyright 1995, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * ------------------------------------------------------------------------
11  *
12  * GNU XBoard is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation, either version 3 of the License, or (at
15  * your option) any later version.
16  *
17  * GNU XBoard is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20  * General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program. If not, see http://www.gnu.org/licenses/.  *
24  *
25  *------------------------------------------------------------------------
26  ** See the file ChangeLog for a revision history.  */
27
28 #define SHOW_PONDERING
29
30 #include "config.h"
31
32 #include <stdio.h>
33
34 #if STDC_HEADERS
35 # include <stdlib.h>
36 # include <string.h>
37 #else /* not STDC_HEADERS */
38 # if HAVE_STRING_H
39 #  include <string.h>
40 # else /* not HAVE_STRING_H */
41 #  include <strings.h>
42 # endif /* not HAVE_STRING_H */
43 #endif /* not STDC_HEADERS */
44
45 #include "common.h"
46 #include "frontend.h"
47 #include "backend.h"
48 #include "moves.h"
49 #include "engineoutput.h"
50 #include "gettext.h"
51
52 #ifdef ENABLE_NLS
53 # define  _(s) gettext (s)
54 # define N_(s) gettext_noop (s)
55 #else
56 # ifdef WIN32
57 #  define  _(s) T_(s)
58 #  undef  ngettext
59 #  define  ngettext(s,p,n) T_(p)
60 # else
61 #  define  _(s) (s)
62 # endif
63 # define N_(s)  s
64 #endif
65
66 typedef struct {
67     char * name;
68     int which;
69     int depth;
70     u64 nodes;
71     int score;
72     int time;
73     char * pv;
74     char * hint;
75     int an_move_index;
76     int an_move_count;
77     int moveKey;
78 } EngineOutputData;
79
80 // called by other front-end
81 void EngineOutputUpdate( FrontEndProgramStats * stats );
82 void OutputKibitz(int window, char *text);
83
84 // module back-end routines
85 static void VerifyDisplayMode();
86 static void UpdateControls( EngineOutputData * ed );
87
88 static int  lastDepth[2] = { -1, -1 };
89 static int  lastForwardMostMove[2] = { -1, -1 };
90 static int  engineState[2] = { -1, -1 };
91 static char lastLine[2][MSG_SIZ];
92 static char header[2][MSG_SIZ];
93
94 #define MAX_VAR 400
95 static int scores[MAX_VAR], textEnd[MAX_VAR], keys[MAX_VAR], curDepth[2], nrVariations[2];
96
97 extern int initialRulePlies;
98
99 void
100 MakeEngineOutputTitle ()
101 {
102         static char buf[MSG_SIZ];
103         static char oldTitle[MSG_SIZ];
104         char title[MSG_SIZ];
105         int count, rule = 2*appData.ruleMoves;
106
107         snprintf(title, MSG_SIZ, _("Engine Output") );
108
109         if(!EngineOutputIsUp()) return;
110         // figure out value of 50-move counter
111         count = currentMove;
112         while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove ) count--;
113         if( count == backwardMostMove ) count -= initialRulePlies;
114         count = currentMove - count;
115         if(!rule) rule = 100;
116         if(count >= rule - 40 && (!appData.icsActive || gameMode == IcsObserving || appData.zippyPlay)) {
117                 snprintf(buf, MSG_SIZ, ngettext("%s (%d reversible ply)", "%s (%d reversible plies)", count), title, count);
118                 safeStrCpy(title, buf, MSG_SIZ);
119         }
120         if(!strcmp(oldTitle, title)) return;
121         safeStrCpy(oldTitle, title, MSG_SIZ);
122         SetEngineOutputTitle(title);
123 }
124
125 // back end, due to front-end wrapper for SetWindowText, and new SetIcon arguments
126 void
127 SetEngineState (int which, enum ENGINE_STATE state, char * state_data)
128 {
129     int x_which = 1 - which;
130
131     if( engineState[ which ] != state ) {
132         engineState[ which ] = state;
133
134         switch( state ) {
135         case STATE_THINKING:
136             SetIcon( which, nStateIcon, nThinking );
137             if( engineState[ x_which ] == STATE_THINKING ) {
138                 SetEngineState( x_which, STATE_IDLE, "" );
139             }
140             break;
141         case STATE_PONDERING:
142             SetIcon( which, nStateIcon, nPondering );
143             break;
144         case STATE_ANALYZING:
145             SetIcon( which, nStateIcon, nAnalyzing );
146             break;
147         default:
148             SetIcon( which, nStateIcon, nClear );
149             break;
150         }
151     }
152
153     if( state_data != 0 ) {
154         DoSetWindowText( which, nStateData, state_data );
155     }
156 }
157
158 // back end, now the front-end wrapper ClearMemo is used, and ed no longer contains handles.
159 void
160 SetProgramStats (FrontEndProgramStats * stats) // now directly called by back-end
161 {
162     EngineOutputData ed;
163     int clearMemo = FALSE;
164     int which, depth, multi;
165     ChessMove moveType;
166     int ff, ft, rf, rt;
167     char pc;
168
169     if( stats == 0 ) {
170         SetEngineState( 0, STATE_IDLE, "" );
171         SetEngineState( 1, STATE_IDLE, "" );
172         return;
173     }
174
175     if(gameMode == IcsObserving && !appData.icsEngineAnalyze)
176         return; // [HGM] kibitz: shut up engine if we are observing an ICS game
177
178     which = stats->which;
179     depth = stats->depth;
180
181     if( which < 0 || which > 1 || depth < 0 || stats->time < 0 || stats->pv == 0 ) {
182         return;
183     }
184
185     if( !EngineOutputDialogExists() ) {
186         return;
187     }
188
189     VerifyDisplayMode();
190
191     ed.which = which;
192     ed.depth = depth;
193     ed.nodes = stats->nodes;
194     ed.score = stats->score;
195     ed.time = stats->time;
196     ed.pv = stats->pv;
197     ed.hint = stats->hint;
198     ed.an_move_index = stats->an_move_index;
199     ed.an_move_count = stats->an_move_count;
200
201     /* Get target control. [HGM] this is moved to front end, which get them from a table */
202     if( which == 0 ) {
203         ed.name = first.tidy;
204     }
205     else {
206         ed.name = second.tidy;
207     }
208
209     if( ed.pv != 0 && ed.pv[0] == ' ' ) {
210         if( strncmp( ed.pv, " no PV", 6 ) == 0 ) { /* Hack on hack! :-O */
211             ed.pv = "";
212         }
213     }
214
215     /* Clear memo if needed */
216     if( lastDepth[which] > depth || (lastDepth[which] == depth && depth <= 1 && ed.pv[0]) ) { // no reason to clear if we won't add line
217         clearMemo = TRUE;
218     }
219
220     if( lastForwardMostMove[which] != forwardMostMove ) {
221         clearMemo = TRUE;
222     }
223
224     if( clearMemo ) {
225         DoClearMemo(which); nrVariations[which] = 0;
226         header[which][0] = NULLCHAR;
227         if(gameMode == AnalyzeMode) {
228           ChessProgramState *cps = (which ? &second : &first);
229           if((multi = MultiPV(cps)) >= 0) {
230             snprintf(header[which], MSG_SIZ, "\t%s viewpoint\t\tfewer / Multi-PV setting = %d / more\n",
231                                        appData.whitePOV || appData.scoreWhite ? "white" : "mover", cps->option[multi].value);
232           }
233           if(!which) snprintf(header[which]+strlen(header[which]), MSG_SIZ-strlen(header[which]), "%s", exclusionHeader);
234           InsertIntoMemo( which, header[which], 0);
235         } else
236         if(appData.ponderNextMove && lastLine[which][0]) {
237             InsertIntoMemo( which, lastLine[which], 0 );
238             InsertIntoMemo( which, "\n", 0 );
239         }
240     }
241
242     if(ed.pv && ed.pv[0] && ParseOneMove(ed.pv, currentMove, &moveType, &ff, &rf, &ft, &rt, &pc))
243         ed.moveKey = (ff<<24 | rf << 16 | ft << 8 | rt) ^ pc*87161;
244     else ed.moveKey = ed.nodes; // kludge to get unique key unlikely to match any move
245
246     /* Update */
247     lastDepth[which] = depth == 1 && ed.nodes == 0 ? 0 : depth; // [HGM] info-line kudge
248     lastForwardMostMove[which] = forwardMostMove;
249
250     UpdateControls( &ed );
251 }
252
253 #define ENGINE_COLOR_WHITE      'w'
254 #define ENGINE_COLOR_BLACK      'b'
255 #define ENGINE_COLOR_UNKNOWN    ' '
256
257 // pure back end
258 static char
259 GetEngineColor (int which)
260 {
261     char result = ENGINE_COLOR_UNKNOWN;
262
263     if( which == 0 || which == 1 ) {
264         ChessProgramState * cps;
265
266         switch (gameMode) {
267         case MachinePlaysBlack:
268         case IcsPlayingBlack:
269             result = ENGINE_COLOR_BLACK;
270             break;
271         case MachinePlaysWhite:
272         case IcsPlayingWhite:
273             result = ENGINE_COLOR_WHITE;
274             break;
275         case AnalyzeMode:
276         case AnalyzeFile:
277             result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
278             break;
279         case TwoMachinesPlay:
280             cps = (which == 0) ? &first : &second;
281             result = cps->twoMachinesColor[0];
282             result = result == 'w' ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
283             break;
284         default: ; // does not happen, but suppresses pedantic warnings
285         }
286     }
287
288     return result;
289 }
290
291 // pure back end
292 static char
293 GetActiveEngineColor ()
294 {
295     char result = ENGINE_COLOR_UNKNOWN;
296
297     if( gameMode == TwoMachinesPlay ) {
298         result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
299     }
300
301     return result;
302 }
303
304 // pure back end
305 static int
306 IsEnginePondering (int which)
307 {
308     int result = FALSE;
309
310     switch (gameMode) {
311     case MachinePlaysBlack:
312     case IcsPlayingBlack:
313         if( WhiteOnMove(forwardMostMove) ) result = TRUE;
314         break;
315     case MachinePlaysWhite:
316     case IcsPlayingWhite:
317         if( ! WhiteOnMove(forwardMostMove) ) result = TRUE;
318         break;
319     case TwoMachinesPlay:
320         if( GetActiveEngineColor() != ENGINE_COLOR_UNKNOWN ) {
321             if( GetEngineColor( which ) != GetActiveEngineColor() ) result = TRUE;
322         }
323         break;
324     default: ; // does not happen, but suppresses pedantic warnings
325     }
326
327     return result;
328 }
329
330 // back end
331 static void
332 SetDisplayMode (int mode)
333 {
334     if( windowMode != mode ) {
335         windowMode = mode;
336
337         ResizeWindowControls( mode );
338     }
339 }
340
341 // pure back end
342 static void
343 VerifyDisplayMode ()
344 {
345     int mode;
346
347     /* Get proper mode for current game */
348     switch( gameMode ) {
349     case IcsObserving:    // [HGM] ICS analyze
350         if(!appData.icsEngineAnalyze) return;
351     case AnalyzeFile:
352     case MachinePlaysWhite:
353     case MachinePlaysBlack:
354         mode = 0;
355         break;
356     case AnalyzeMode:
357         mode = second.analyzing;
358         break;
359     case IcsPlayingWhite:
360     case IcsPlayingBlack:
361         mode = appData.zippyPlay && opponentKibitzes; // [HGM] kibitz
362         break;
363     case TwoMachinesPlay:
364         mode = 1;
365         break;
366     default:
367         /* Do not change */
368         return;
369     }
370
371     SetDisplayMode( mode );
372 }
373
374 // back end. Determine what icon to set in the color-icon field, and print it
375 void
376 SetEngineColorIcon (int which)
377 {
378     char color = GetEngineColor(which);
379     int nicon = 0;
380
381     if( color == ENGINE_COLOR_BLACK )
382         nicon = nColorBlack;
383     else if( color == ENGINE_COLOR_WHITE )
384         nicon = nColorWhite;
385     else
386         nicon = nColorUnknown;
387
388     SetIcon( which, nColorIcon, nicon );
389 }
390
391 #define MAX_NAME_LENGTH 32
392
393 // [HGM] multivar: sort Thinking Output within one depth on score
394
395 static int
396 InsertionPoint (int len, EngineOutputData *ed)
397 {
398         int i, offs = 0, newScore = ed->score, n = ed->which;
399
400         if(ed->nodes == 0 && ed->score == 0 && ed->time == 0)
401                 newScore = 1e6; // info lines inserted on top
402         if(ed->depth != curDepth[n]) { // depth has changed
403                 curDepth[n] = ed->depth;
404                 nrVariations[n] = 0; // throw away everything we had
405         }
406         // loop through all lines. Note even / odd used for different panes
407         for(i=nrVariations[n]-2; i>=0; i-=2) {
408                 // put new item behind those we haven't looked at
409                 offs = textEnd[i+n];
410                 textEnd[i+n+2] = offs + len;
411                 scores[i+n+2] = newScore;
412                 keys[i+n+2] = ed->moveKey;
413                 if(ed->moveKey != keys[i+n] && // same move always tops previous one (as a higher score must be a fail low)
414                    newScore < scores[i+n]) break;
415                 // if it had higher score as previous, move previous in stead
416                 scores[i+n+2] = scores[i+n];
417                 textEnd[i+n+2] = textEnd[i+n] + len;
418                 keys[i+n+2] = keys[i+n];
419         }
420         if(i<0) {
421                 offs = 0;
422                 textEnd[n] = offs + len;
423                 scores[n] = newScore;
424         }
425         nrVariations[n] += 2;
426       return offs + (gameMode == AnalyzeMode)*strlen(header[ed->which]);
427 }
428
429
430 // pure back end, now SetWindowText is called via wrapper DoSetWindowText
431 static void
432 UpdateControls (EngineOutputData *ed)
433 {
434 //    int isPondering = FALSE;
435
436     char s_label[MAX_NAME_LENGTH + 32];
437     int h;
438     char * name = ed->name;
439
440     /* Label */
441     if( name == 0 || *name == '\0' ) {
442         name = "?";
443     }
444
445     strncpy( s_label, name, MAX_NAME_LENGTH );
446     s_label[ MAX_NAME_LENGTH-1 ] = '\0';
447
448 #ifdef SHOW_PONDERING
449     if( IsEnginePondering( ed->which ) ) {
450         char buf[8];
451
452         buf[0] = '\0';
453
454         if( ed->hint != 0 && *ed->hint != '\0' ) {
455             strncpy( buf, ed->hint, sizeof(buf) );
456             buf[sizeof(buf)-1] = '\0';
457         }
458         else if( ed->pv != 0 && *ed->pv != '\0' ) {
459             char * sep = strchr( ed->pv, ' ' );
460             int buflen = sizeof(buf);
461
462             if( sep != NULL ) {
463                 buflen = sep - ed->pv + 1;
464                 if( buflen > sizeof(buf) ) buflen = sizeof(buf);
465             }
466
467             strncpy( buf, ed->pv, buflen );
468             buf[ buflen-1 ] = '\0';
469         }
470
471         SetEngineState( ed->which, STATE_PONDERING, buf );
472     }
473     else if( gameMode == TwoMachinesPlay ) {
474         SetEngineState( ed->which, STATE_THINKING, "" );
475     }
476     else if( gameMode == AnalyzeMode || gameMode == AnalyzeFile
477           || (gameMode == IcsObserving && appData.icsEngineAnalyze)) { // [HGM] ICS-analyze
478         char buf[64];
479         int time_secs = ed->time / 100;
480         int time_mins = time_secs / 60;
481
482         buf[0] = '\0';
483
484         if( ed->an_move_index != 0 && ed->an_move_count != 0 && *ed->hint != '\0' ) {
485             char mov[16];
486
487             strncpy( mov, ed->hint, sizeof(mov) );
488             mov[ sizeof(mov)-1 ] = '\0';
489
490             snprintf( buf, sizeof(buf)/sizeof(buf[0]), "[%d] %d/%d: %s [%02d:%02d:%02d]", ed->depth, ed->an_move_index,
491                         ed->an_move_count, mov, time_mins / 60, time_mins % 60, time_secs % 60 );
492         }
493
494         SetEngineState( ed->which, STATE_ANALYZING, buf );
495     }
496     else {
497         SetEngineState( ed->which, STATE_IDLE, "" );
498     }
499 #endif
500
501     DoSetWindowText( ed->which, nLabel, s_label );
502
503     s_label[0] = '\0';
504
505     if( ed->time > 0 && ed->nodes > 0 ) {
506         unsigned long nps_100 = ed->nodes / ed->time;
507
508         if( nps_100 < 100000 ) {
509           snprintf( s_label, sizeof(s_label)/sizeof(s_label[0]), "%s: %lu", _("NPS"), nps_100 * 100 );
510         }
511         else {
512           snprintf( s_label, sizeof(s_label)/sizeof(s_label[0]), "%s: %.1fk", _("NPS"), nps_100 / 10.0 );
513         }
514     }
515
516     DoSetWindowText( ed->which, nLabelNPS, s_label );
517
518     /* Memo */
519     if( ed->pv != 0 && *ed->pv != '\0' ) {
520         char s_nodes[24];
521         char s_score[16];
522         char s_time[24];
523         char buf[256];
524         int buflen;
525         int time_secs = ed->time / 100;
526         int time_cent = ed->time % 100;
527
528         /* Nodes */
529         if( ed->nodes < 1000000 ) {
530             snprintf( s_nodes, sizeof(s_nodes)/sizeof(s_nodes[0]), u64Display, ed->nodes );
531         }
532         else {
533             snprintf( s_nodes, sizeof(s_nodes)/sizeof(s_nodes[0]), "%.1fM", u64ToDouble(ed->nodes) / 1000000.0 );
534         }
535
536         /* Score */
537         h = ((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(currentMove) ? -1 : 1) * ed->score;
538         if( h > 0 ) {
539           snprintf( s_score, sizeof(s_score)/sizeof(s_score[0]), "+%.2f", h / 100.0 );
540         }
541         else {
542           snprintf( s_score, sizeof(s_score)/sizeof(s_score[0]), "%.2f", h / 100.0 );
543         }
544
545         /* Time */
546         snprintf( s_time, sizeof(s_time)/sizeof(s_time[0]), "%d:%02d.%02d", time_secs / 60, time_secs % 60, time_cent );
547
548         /* Put all together... */
549         if(ed->nodes == 0 && ed->score == 0 && ed->time == 0)
550           snprintf( buf, sizeof(buf)/sizeof(buf[0]), "%3d\t", ed->depth );
551         else
552           snprintf( buf, sizeof(buf)/sizeof(buf[0]), "%3d\t%s\t%s\t%s\t", ed->depth, s_score, s_nodes, s_time );
553
554         /* Add PV */
555         buflen = strlen(buf);
556
557         strncpy( buf + buflen, ed->pv, sizeof(buf) - buflen );
558
559         buf[ sizeof(buf) - 3 ] = '\0';
560
561         strcat( buf + buflen, "\r\n" );
562
563         /* Update memo */
564         InsertIntoMemo( ed->which, buf, InsertionPoint(strlen(buf), ed) );
565         strncpy(lastLine[ed->which], buf, MSG_SIZ);
566     }
567
568     /* Colors */
569     SetEngineColorIcon( ed->which );
570 }
571
572 // [HGM] kibitz: write kibitz line; split window for it if necessary
573 void
574 OutputKibitz (int window, char *text)
575 {
576         static int currentLineEnd[2];
577         int where = 0;
578         if(!EngineOutputIsUp()) return;
579         if(!opponentKibitzes) { // on first kibitz of game, clear memos
580             DoClearMemo(1); currentLineEnd[1] = 0;
581             if(gameMode == IcsObserving) { DoClearMemo(0); currentLineEnd[0] = 0; }
582         }
583         opponentKibitzes = TRUE; // this causes split window DisplayMode in ICS modes.
584         VerifyDisplayMode();
585         strncpy(text+strlen(text)-1, "\r\n",sizeof(text+strlen(text)-1)); // to not lose line breaks on copying
586         if(gameMode == IcsObserving) {
587             DoSetWindowText(0, nLabel, gameInfo.white);
588             SetIcon( 0, nColorIcon,  nColorWhite);
589             SetIcon( 0, nStateIcon,  nClear);
590         }
591         DoSetWindowText(1, nLabel, gameMode == IcsPlayingBlack ? gameInfo.white : gameInfo.black); // opponent name
592         SetIcon( 1, nColorIcon,  gameMode == IcsPlayingBlack ? nColorWhite : nColorBlack);
593         SetIcon( 1, nStateIcon,  nClear);
594         if(strstr(text, "\\  ") == text) where = currentLineEnd[window-1]; // continuation line
595 //if(appData.debugMode) fprintf(debugFP, "insert '%s' at %d (end = %d,%d)\n", text, where, currentLineEnd[0], currentLineEnd[1]);
596         InsertIntoMemo(window-1, text, where); // [HGM] multivar: always at top
597         currentLineEnd[window-1] = where + strlen(text);
598 }