936fcd86fd9b6f7ce8fafde90061558ad52946e2
[xboard.git] / winboard / wengineo.c
1 /*\r
2  * Engine output (PV)\r
3  *\r
4  * Author: Alessandro Scotti (Dec 2005)\r
5  *\r
6  * Copyright 2005 Alessandro Scotti\r
7  *\r
8  * ------------------------------------------------------------------------\r
9  *\r
10  * GNU XBoard is free software: you can redistribute it and/or modify\r
11  * it under the terms of the GNU General Public License as published by\r
12  * the Free Software Foundation, either version 3 of the License, or (at\r
13  * your option) any later version.\r
14  *\r
15  * GNU XBoard is distributed in the hope that it will be useful, but\r
16  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
18  * General Public License for more details.\r
19  *\r
20  * You should have received a copy of the GNU General Public License\r
21  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
22  *\r
23  *------------------------------------------------------------------------\r
24  ** See the file ChangeLog for a revision history.  */\r
25 \r
26 #include "config.h"\r
27 \r
28 #include <windows.h> /* required for all Windows applications */\r
29 #include <richedit.h>\r
30 #include <stdio.h>\r
31 #include <stdlib.h>\r
32 #include <malloc.h>\r
33 #include <commdlg.h>\r
34 #include <dlgs.h>\r
35 \r
36 #include "common.h"\r
37 #include "frontend.h"\r
38 #include "backend.h"\r
39 #include "winboard.h"\r
40 \r
41 #include "wsnap.h"\r
42 \r
43 // [HGM] define numbers to indicate icons, for referring to them in platform-independent way\r
44 #define nColorBlack   1\r
45 #define nColorWhite   2\r
46 #define nColorUnknown 3\r
47 #define nClear        4\r
48 #define nPondering    5\r
49 #define nThinking     6\r
50 #define nAnalyzing    7\r
51 \r
52 HICON icons[8]; // [HGM] this front-end array translates back-end icon indicator to handle\r
53 \r
54 // [HGM] same for output fields (note that there are two of each type, one per color)\r
55 #define nColorIcon 1\r
56 #define nStateIcon 2\r
57 #define nLabel     3\r
58 #define nStateData 4\r
59 #define nLabelNPS  5\r
60 #define nMemo      6\r
61 \r
62 HWND outputField[2][7]; // [HGM] front-end array to translate output field to window handle\r
63 \r
64 #define SHOW_PONDERING\r
65 \r
66 /* Module variables */\r
67 #define H_MARGIN            2\r
68 #define V_MARGIN            2\r
69 #define LABEL_V_DISTANCE    1   /* Distance between label and memo */\r
70 #define SPLITTER_SIZE       4   /* Distance between first memo and second label */\r
71 \r
72 #define ICON_SIZE           14\r
73 \r
74 #define STATE_UNKNOWN   -1\r
75 #define STATE_THINKING   0\r
76 #define STATE_IDLE       1\r
77 #define STATE_PONDERING  2\r
78 #define STATE_ANALYZING  3\r
79 \r
80 static int  windowMode = 1;\r
81 static int  needInit = TRUE;\r
82 static int  lastDepth[2] = { -1, -1 };\r
83 static int  lastForwardMostMove[2] = { -1, -1 };\r
84 static int  engineState[2] = { -1, -1 };\r
85 static BOOLEAN engineOutputDialogUp = FALSE;\r
86 \r
87 typedef struct {\r
88 //    HWND hColorIcon; // [HGM] the output-control handles are no loger passed,\r
89 //    HWND hLabel;     //       to give better front-end / back-end separation\r
90 //    HWND hStateIcon; //       the front-end routines now get them from a (front-end)\r
91 //    HWND hStateData; //       table, indexed by output-field indicators.\r
92 //    HWND hLabelNPS;\r
93 //    HWND hMemo;\r
94     char * name;\r
95     int which;\r
96     int depth;\r
97     u64 nodes;\r
98     int score;\r
99     int time;\r
100     char * pv;\r
101     char * hint;\r
102     int an_move_index;\r
103     int an_move_count;\r
104 } EngineOutputData;\r
105 \r
106 static void VerifyDisplayMode();\r
107 static void UpdateControls( EngineOutputData * ed );\r
108 static void SetEngineState( int which, int state, char * state_data );\r
109 \r
110 // front end\r
111 static HICON LoadIconEx( int id )\r
112 {\r
113     return LoadImage( hInst, MAKEINTRESOURCE(id), IMAGE_ICON, ICON_SIZE, ICON_SIZE, 0 );\r
114 }\r
115 \r
116 // [HGM] the platform-dependent way of indicating where output should go is now all\r
117 // concentrated here, where a table of platform-dependent handles are initialized.\r
118 // This cleanses most other routines of front-end stuff, so they can go into the back end.\r
119 static void InitializeEngineOutput()\r
120 {\r
121  //   if( needInit ) { // needInit was already tested before call\r
122         // [HGM] made this into a table, rather than separate global variables\r
123         icons[nColorBlack]   = LoadIconEx( IDI_BLACK_14 );\r
124         icons[nColorWhite]   = LoadIconEx( IDI_WHITE_14 );\r
125         icons[nColorUnknown] = LoadIconEx( IDI_UNKNOWN_14 );\r
126         icons[nClear]        = LoadIconEx( IDI_TRANS_14 );\r
127         icons[nPondering]    = LoadIconEx( IDI_PONDER_14 );\r
128         icons[nThinking]     = LoadIconEx( IDI_CLOCK_14 );\r
129         icons[nAnalyzing]    = LoadIconEx( IDI_ANALYZE2_14 );\r
130 \r
131         // [HGM] also make a table of handles to output controls\r
132         // Note that engineOutputDialog must be defined first!\r
133         outputField[0][nColorIcon] = GetDlgItem( engineOutputDialog, IDC_Color1 );\r
134         outputField[0][nLabel]     = GetDlgItem( engineOutputDialog, IDC_EngineLabel1 );\r
135         outputField[0][nStateIcon] = GetDlgItem( engineOutputDialog, IDC_StateIcon1 );\r
136         outputField[0][nStateData] = GetDlgItem( engineOutputDialog, IDC_StateData1 );\r
137         outputField[0][nLabelNPS]  = GetDlgItem( engineOutputDialog, IDC_Engine1_NPS );\r
138         outputField[0][nMemo]      = GetDlgItem( engineOutputDialog, IDC_EngineMemo1 );\r
139 \r
140         outputField[1][nColorIcon] = GetDlgItem( engineOutputDialog, IDC_Color2 );\r
141         outputField[1][nLabel]     = GetDlgItem( engineOutputDialog, IDC_EngineLabel2 );\r
142         outputField[1][nStateIcon] = GetDlgItem( engineOutputDialog, IDC_StateIcon2 );\r
143         outputField[1][nStateData] = GetDlgItem( engineOutputDialog, IDC_StateData2 );\r
144         outputField[1][nLabelNPS]  = GetDlgItem( engineOutputDialog, IDC_Engine2_NPS );\r
145         outputField[1][nMemo]      = GetDlgItem( engineOutputDialog, IDC_EngineMemo2 );\r
146 //        needInit = FALSE;\r
147 //    }\r
148 }\r
149 \r
150 // front end\r
151 static void SetControlPos( HWND hDlg, int id, int x, int y, int width, int height )\r
152 {\r
153     HWND hControl = GetDlgItem( hDlg, id );\r
154 \r
155     SetWindowPos( hControl, HWND_TOP, x, y, width, height, SWP_NOZORDER );\r
156 }\r
157 \r
158 #define HIDDEN_X    20000\r
159 #define HIDDEN_Y    20000\r
160 \r
161 // front end\r
162 static void HideControl( HWND hDlg, int id )\r
163 {\r
164     HWND hControl = GetDlgItem( hDlg, id );\r
165     RECT rc;\r
166 \r
167     GetWindowRect( hControl, &rc );\r
168 \r
169     /* \r
170         Avoid hiding an already hidden control, because that causes many\r
171         unnecessary WM_ERASEBKGND messages!\r
172     */\r
173     if( rc.left != HIDDEN_X || rc.top != HIDDEN_Y ) {\r
174         SetControlPos( hDlg, id, 20000, 20000, 100, 100 );\r
175     }\r
176 }\r
177 \r
178 // front end, although we might make GetWindowRect front end instead\r
179 static int GetControlWidth( HWND hDlg, int id )\r
180 {\r
181     RECT rc;\r
182 \r
183     GetWindowRect( GetDlgItem( hDlg, id ), &rc );\r
184 \r
185     return rc.right - rc.left;\r
186 }\r
187 \r
188 // front end?\r
189 static int GetControlHeight( HWND hDlg, int id )\r
190 {\r
191     RECT rc;\r
192 \r
193     GetWindowRect( GetDlgItem( hDlg, id ), &rc );\r
194 \r
195     return rc.bottom - rc.top;\r
196 }\r
197 \r
198 static int GetHeaderHeight()\r
199 {\r
200     int result = GetControlHeight( engineOutputDialog, IDC_EngineLabel1 );\r
201 \r
202     if( result < ICON_SIZE ) result = ICON_SIZE;\r
203 \r
204     return result;\r
205 }\r
206 \r
207 // The size calculations should be backend? If setControlPos is a platform-dependent way of doing things,\r
208 // a platform-independent wrapper for it should be supplied.\r
209 static void PositionControlSet( HWND hDlg, int x, int y, int clientWidth, int memoHeight, int idColor, int idEngineLabel, int idNPS, int idMemo, int idStateIcon, int idStateData )\r
210 {\r
211     int label_x = x + ICON_SIZE + H_MARGIN;\r
212     int label_h = GetControlHeight( hDlg, IDC_EngineLabel1 );\r
213     int label_y = y + ICON_SIZE - label_h;\r
214     int nps_w = GetControlWidth( hDlg, IDC_Engine1_NPS );\r
215     int nps_x = clientWidth - H_MARGIN - nps_w;\r
216     int state_data_w = GetControlWidth( hDlg, IDC_StateData1 );\r
217     int state_data_x = nps_x - H_MARGIN - state_data_w;\r
218     int state_icon_x = state_data_x - ICON_SIZE - 2;\r
219     int max_w = clientWidth - 2*H_MARGIN;\r
220     int memo_y = y + ICON_SIZE + LABEL_V_DISTANCE;\r
221 \r
222     SetControlPos( hDlg, idColor, x, y, ICON_SIZE, ICON_SIZE );\r
223     SetControlPos( hDlg, idEngineLabel, label_x, label_y, state_icon_x - label_x, label_h );\r
224     SetControlPos( hDlg, idStateIcon, state_icon_x, y, ICON_SIZE, ICON_SIZE );\r
225     SetControlPos( hDlg, idStateData, state_data_x, label_y, state_data_w, label_h );\r
226     SetControlPos( hDlg, idNPS, nps_x, label_y, nps_w, label_h );\r
227     SetControlPos( hDlg, idMemo, x, memo_y, max_w, memoHeight );\r
228 }\r
229 \r
230 // Also here some of the size calculations should go to the back end, and their actual application to a front-end routine\r
231 static void ResizeWindowControls( HWND hDlg, int mode )\r
232 {\r
233     RECT rc;\r
234     int headerHeight = GetHeaderHeight();\r
235 //    int labelHeight = GetControlHeight( hDlg, IDC_EngineLabel1 );\r
236 //    int labelOffset = H_MARGIN + ICON_SIZE + H_MARGIN;\r
237 //    int labelDeltaY = ICON_SIZE - labelHeight;\r
238     int clientWidth;\r
239     int clientHeight;\r
240     int maxControlWidth;\r
241     int npsWidth;\r
242 \r
243     /* Initialize variables */\r
244     GetClientRect( hDlg, &rc );\r
245 \r
246     clientWidth = rc.right - rc.left;\r
247     clientHeight = rc.bottom - rc.top;\r
248 \r
249     maxControlWidth = clientWidth - 2*H_MARGIN;\r
250 \r
251     npsWidth = GetControlWidth( hDlg, IDC_Engine1_NPS );\r
252 \r
253     /* Resize controls */\r
254     if( mode == 0 ) {\r
255         /* One engine */\r
256         PositionControlSet( hDlg, H_MARGIN, V_MARGIN, \r
257             clientWidth, \r
258             clientHeight - V_MARGIN - LABEL_V_DISTANCE - headerHeight- V_MARGIN,\r
259             IDC_Color1, IDC_EngineLabel1, IDC_Engine1_NPS, IDC_EngineMemo1, IDC_StateIcon1, IDC_StateData1 );\r
260 \r
261         /* Hide controls for the second engine */\r
262         HideControl( hDlg, IDC_Color2 );\r
263         HideControl( hDlg, IDC_EngineLabel2 );\r
264         HideControl( hDlg, IDC_StateIcon2 );\r
265         HideControl( hDlg, IDC_StateData2 );\r
266         HideControl( hDlg, IDC_Engine2_NPS );\r
267         HideControl( hDlg, IDC_EngineMemo2 );\r
268         SendDlgItemMessage( hDlg, IDC_EngineMemo2, WM_SETTEXT, 0, (LPARAM) "" );\r
269         /* TODO: we should also hide/disable them!!! what about tab stops?!?! */\r
270     }\r
271     else {\r
272         /* Two engines */\r
273         int memo_h = (clientHeight - headerHeight*2 - V_MARGIN*2 - LABEL_V_DISTANCE*2 - SPLITTER_SIZE) / 2;\r
274         int header1_y = V_MARGIN;\r
275         int header2_y = V_MARGIN + headerHeight + LABEL_V_DISTANCE + memo_h + SPLITTER_SIZE;\r
276 \r
277         PositionControlSet( hDlg, H_MARGIN, header1_y, clientWidth, memo_h,\r
278             IDC_Color1, IDC_EngineLabel1, IDC_Engine1_NPS, IDC_EngineMemo1, IDC_StateIcon1, IDC_StateData1 );\r
279 \r
280         PositionControlSet( hDlg, H_MARGIN, header2_y, clientWidth, memo_h,\r
281             IDC_Color2, IDC_EngineLabel2, IDC_Engine2_NPS, IDC_EngineMemo2, IDC_StateIcon2, IDC_StateData2 );\r
282     }\r
283 \r
284     InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo1), NULL, FALSE );\r
285     InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo2), NULL, FALSE );\r
286 }\r
287 \r
288 // front end. Actual printing of PV lines into the output field\r
289 static void InsertIntoMemo( int which, char * text, int where )\r
290 {\r
291     SendMessage( outputField[which][nMemo], EM_SETSEL, where, where ); // [HGM] multivar: choose insertion point\r
292 \r
293     SendMessage( outputField[which][nMemo], EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );\r
294 }\r
295 \r
296 // front end. Associates an icon with an output field ("control" in Windows jargon).\r
297 // [HGM] let it find out the output field from the 'which' number by itself\r
298 static void SetIcon( int which, int field, int nIcon )\r
299 {\r
300 \r
301     if( nIcon != 0 ) {\r
302         SendMessage( outputField[which][field], STM_SETICON, (WPARAM) icons[nIcon], 0 );\r
303     }\r
304 }\r
305 \r
306 // front end wrapper for SetWindowText, taking control number in stead of handle\r
307 void DoSetWindowText(int which, int field, char *s_label)\r
308 {\r
309     SetWindowText( outputField[which][field], s_label );\r
310 }\r
311 \r
312 // This seems pure front end\r
313 LRESULT CALLBACK EngineOutputProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
314 {\r
315     static SnapData sd;\r
316 \r
317     switch (message) {\r
318     case WM_INITDIALOG:\r
319         if( engineOutputDialog == NULL ) {\r
320             engineOutputDialog = hDlg;\r
321 \r
322             RestoreWindowPlacement( hDlg, &wpEngineOutput ); /* Restore window placement */\r
323 \r
324             ResizeWindowControls( hDlg, windowMode );\r
325 \r
326             /* Set font */\r
327             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo1, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
328             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo2, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
329 \r
330             SetEngineState( 0, STATE_IDLE, "" );\r
331             SetEngineState( 1, STATE_IDLE, "" );\r
332         }\r
333 \r
334         return FALSE;\r
335 \r
336     case WM_COMMAND:\r
337         switch (LOWORD(wParam)) {\r
338         case IDOK:\r
339           EndDialog(hDlg, TRUE);\r
340           return TRUE;\r
341 \r
342         case IDCANCEL:\r
343           EndDialog(hDlg, FALSE);\r
344           return TRUE;\r
345 \r
346         default:\r
347           break;\r
348         }\r
349 \r
350         break;\r
351 \r
352     case WM_GETMINMAXINFO:\r
353         {\r
354             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
355         \r
356             mmi->ptMinTrackSize.x = 100;\r
357             mmi->ptMinTrackSize.y = 160;\r
358         }\r
359         break;\r
360 \r
361     case WM_CLOSE:\r
362         EngineOutputPopDown();\r
363         break;\r
364 \r
365     case WM_SIZE:\r
366         ResizeWindowControls( hDlg, windowMode );\r
367         break;\r
368 \r
369     case WM_ENTERSIZEMOVE:\r
370         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
371 \r
372     case WM_SIZING:\r
373         return OnSizing( &sd, hDlg, wParam, lParam );\r
374 \r
375     case WM_MOVING:\r
376         return OnMoving( &sd, hDlg, wParam, lParam );\r
377 \r
378     case WM_EXITSIZEMOVE:\r
379         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
380     }\r
381 \r
382     return FALSE;\r
383 }\r
384 \r
385 // front end\r
386 void EngineOutputPopUp()\r
387 {\r
388   FARPROC lpProc;\r
389   \r
390   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_CHECKED);\r
391 \r
392   if( engineOutputDialog ) {\r
393     SendMessage( engineOutputDialog, WM_INITDIALOG, 0, 0 );\r
394 \r
395     if( ! engineOutputDialogUp ) {\r
396         ShowWindow(engineOutputDialog, SW_SHOW);\r
397     }\r
398   }\r
399   else {\r
400     lpProc = MakeProcInstance( (FARPROC) EngineOutputProc, hInst );\r
401 \r
402     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
403     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EngineOutput), hwndMain, (DLGPROC)lpProc );\r
404 \r
405     FreeProcInstance(lpProc);\r
406   }\r
407 \r
408   // [HGM] displaced to after creation of dialog, to allow initialization of output fields\r
409   if( needInit ) {\r
410       InitializeEngineOutput();\r
411       needInit = FALSE;\r
412   }\r
413 \r
414   engineOutputDialogUp = TRUE;\r
415 }\r
416 \r
417 // front end\r
418 void EngineOutputPopDown()\r
419 {\r
420   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_UNCHECKED);\r
421 \r
422   if( engineOutputDialog ) {\r
423       ShowWindow(engineOutputDialog, SW_HIDE);\r
424   }\r
425 \r
426   engineOutputDialogUp = FALSE;\r
427 }\r
428 \r
429 // front end. [HGM] Takes handle of output control from table, so only number is passed\r
430 void DoClearMemo(int which)\r
431 {\r
432         SendMessage( outputField[which][nMemo], WM_SETTEXT, 0, (LPARAM) "" );\r
433 }\r
434 \r
435 //------------------------ pure back-end routines -------------------------------\r
436 \r
437 #define MAX_VAR 400\r
438 static int scores[MAX_VAR], textEnd[MAX_VAR], curDepth[2], nrVariations[2];\r
439 \r
440 // back end, due to front-end wrapper for SetWindowText, and new SetIcon arguments\r
441 static void SetEngineState( int which, int state, char * state_data )\r
442 {\r
443     int x_which = 1 - which;\r
444 \r
445     if( engineState[ which ] != state ) {\r
446         engineState[ which ] = state;\r
447 \r
448         switch( state ) {\r
449         case STATE_THINKING:\r
450             SetIcon( which, nStateIcon, nThinking );\r
451             if( engineState[ x_which ] == STATE_THINKING ) {\r
452                 SetEngineState( x_which, STATE_IDLE, "" );\r
453             }\r
454             break;\r
455         case STATE_PONDERING:\r
456             SetIcon( which, nStateIcon, nPondering );\r
457             break;\r
458         case STATE_ANALYZING:\r
459             SetIcon( which, nStateIcon, nAnalyzing );\r
460             break;\r
461         default:\r
462             SetIcon( which, nStateIcon, nClear );\r
463             break;\r
464         }\r
465     }\r
466 \r
467     if( state_data != 0 ) {\r
468         DoSetWindowText( which, nStateData, state_data );\r
469     }\r
470 }\r
471 \r
472 // back end, now the front-end wrapper ClearMemo is used, and ed no longer contains handles.\r
473 void EngineOutputUpdate( FrontEndProgramStats * stats )\r
474 {\r
475     EngineOutputData ed;\r
476     int clearMemo = FALSE;\r
477     int which;\r
478     int depth;\r
479 \r
480     if( stats == 0 ) {\r
481         SetEngineState( 0, STATE_IDLE, "" );\r
482         SetEngineState( 1, STATE_IDLE, "" );\r
483         return;\r
484     }\r
485 \r
486     if(gameMode == IcsObserving && !appData.icsEngineAnalyze)\r
487         return; // [HGM] kibitz: shut up engine if we are observing an ICS game\r
488 \r
489     which = stats->which;\r
490     depth = stats->depth;\r
491 \r
492     if( which < 0 || which > 1 || depth < 0 || stats->time < 0 || stats->pv == 0 ) {\r
493         return;\r
494     }\r
495 \r
496     if( engineOutputDialog == NULL ) {\r
497         return;\r
498     }\r
499 \r
500     VerifyDisplayMode();\r
501 \r
502     ed.which = which;\r
503     ed.depth = depth;\r
504     ed.nodes = stats->nodes;\r
505     ed.score = stats->score;\r
506     ed.time = stats->time;\r
507     ed.pv = stats->pv;\r
508     ed.hint = stats->hint;\r
509     ed.an_move_index = stats->an_move_index;\r
510     ed.an_move_count = stats->an_move_count;\r
511 \r
512     /* Get target control. [HGM] this is moved to front end, which get them from a table */\r
513     if( which == 0 ) {\r
514         ed.name = first.tidy;\r
515     }\r
516     else {\r
517         ed.name = second.tidy;\r
518     }\r
519 \r
520     /* Clear memo if needed */\r
521     if( lastDepth[which] > depth || (lastDepth[which] == depth && depth <= 1) ) {\r
522         clearMemo = TRUE;\r
523     }\r
524 \r
525     if( lastForwardMostMove[which] != forwardMostMove ) {\r
526         clearMemo = TRUE;\r
527     }\r
528 \r
529     if( clearMemo ) { DoClearMemo(which); nrVariations[which] = 0; }\r
530 \r
531     /* Update */\r
532     lastDepth[which] = depth == 1 && ed.nodes == 0 ? 0 : depth; // [HGM] info-line kudge\r
533     lastForwardMostMove[which] = forwardMostMove;\r
534 \r
535     if( ed.pv != 0 && ed.pv[0] == ' ' ) {\r
536         if( strncmp( ed.pv, " no PV", 6 ) == 0 ) { /* Hack on hack! :-O */\r
537             ed.pv = "";\r
538         }\r
539     }\r
540 \r
541     UpdateControls( &ed );\r
542 }\r
543 \r
544 #define ENGINE_COLOR_WHITE      'w'\r
545 #define ENGINE_COLOR_BLACK      'b'\r
546 #define ENGINE_COLOR_UNKNOWN    ' '\r
547 \r
548 // pure back end\r
549 char GetEngineColor( int which )\r
550 {\r
551     char result = ENGINE_COLOR_UNKNOWN;\r
552 \r
553     if( which == 0 || which == 1 ) {\r
554         ChessProgramState * cps;\r
555 \r
556         switch (gameMode) {\r
557         case MachinePlaysBlack:\r
558         case IcsPlayingBlack:\r
559             result = ENGINE_COLOR_BLACK;\r
560             break;\r
561         case MachinePlaysWhite:\r
562         case IcsPlayingWhite:\r
563             result = ENGINE_COLOR_WHITE;\r
564             break;\r
565         case AnalyzeMode:\r
566         case AnalyzeFile:\r
567             result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;\r
568             break;\r
569         case TwoMachinesPlay:\r
570             cps = (which == 0) ? &first : &second;\r
571             result = cps->twoMachinesColor[0];\r
572             result = result == 'w' ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;\r
573             break;\r
574         default: ; // does not happen, but suppresses pedantic warnings\r
575         }\r
576     }\r
577 \r
578     return result;\r
579 }\r
580 \r
581 // pure back end\r
582 char GetActiveEngineColor()\r
583 {\r
584     char result = ENGINE_COLOR_UNKNOWN;\r
585 \r
586     if( gameMode == TwoMachinesPlay ) {\r
587         result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;\r
588     }\r
589 \r
590     return result;\r
591 }\r
592 \r
593 // pure back end\r
594 static int IsEnginePondering( int which )\r
595 {\r
596     int result = FALSE;\r
597 \r
598     switch (gameMode) {\r
599     case MachinePlaysBlack:\r
600     case IcsPlayingBlack:\r
601         if( WhiteOnMove(forwardMostMove) ) result = TRUE;\r
602         break;\r
603     case MachinePlaysWhite:\r
604     case IcsPlayingWhite:\r
605         if( ! WhiteOnMove(forwardMostMove) ) result = TRUE;\r
606         break;\r
607     case TwoMachinesPlay:\r
608         if( GetActiveEngineColor() != ENGINE_COLOR_UNKNOWN ) {\r
609             if( GetEngineColor( which ) != GetActiveEngineColor() ) result = TRUE;\r
610         }\r
611         break;\r
612     default: ; // does not happen, but suppresses pedantic warnings\r
613     }\r
614 \r
615     return result;\r
616 }\r
617 \r
618 // back end\r
619 static void SetDisplayMode( int mode )\r
620 {\r
621     if( windowMode != mode ) {\r
622         windowMode = mode;\r
623 \r
624         ResizeWindowControls( engineOutputDialog, mode );\r
625     }\r
626 }\r
627 \r
628 // pure back end\r
629 static void VerifyDisplayMode()\r
630 {\r
631     int mode;\r
632 \r
633     /* Get proper mode for current game */\r
634     switch( gameMode ) {\r
635     case IcsObserving:    // [HGM] ICS analyze\r
636         if(!appData.icsEngineAnalyze) return;\r
637     case AnalyzeMode:\r
638     case AnalyzeFile:\r
639     case MachinePlaysWhite:\r
640     case MachinePlaysBlack:\r
641         mode = 0;\r
642         break;\r
643     case IcsPlayingWhite:\r
644     case IcsPlayingBlack:\r
645         mode = appData.zippyPlay && opponentKibitzes; // [HGM] kibitz\r
646         break;\r
647     case TwoMachinesPlay:\r
648         mode = 1;\r
649         break;\r
650     default:\r
651         /* Do not change */\r
652         return;\r
653     }\r
654 \r
655     SetDisplayMode( mode );\r
656 }\r
657 \r
658 // back end. Determine what icon to se in the color-icon field, and print it\r
659 static void SetEngineColorIcon( int which )\r
660 {\r
661     char color = GetEngineColor(which);\r
662     int nicon = 0;\r
663 \r
664     if( color == ENGINE_COLOR_BLACK )\r
665         nicon = nColorBlack;\r
666     else if( color == ENGINE_COLOR_WHITE )\r
667         nicon = nColorWhite;\r
668     else\r
669         nicon = nColorUnknown;\r
670 \r
671     SetIcon( which, nColorIcon, nicon );\r
672 }\r
673 \r
674 #define MAX_NAME_LENGTH 32\r
675 \r
676 // [HGM] multivar: sort Thinking Output within one depth on score\r
677 \r
678 static int InsertionPoint( int len, EngineOutputData * ed )\r
679 {\r
680         int i, offs = 0, newScore = ed->score, n = ed->which;\r
681 \r
682         if(ed->nodes == 0 && ed->score == 0 && ed->time == 0)\r
683                 newScore = 1e6; // info lines inserted on top\r
684         if(ed->depth != curDepth[n]) { // depth has changed\r
685                 curDepth[n] = ed->depth;\r
686                 nrVariations[n] = 0; // throw away everything we had\r
687         }\r
688         // loop through all lines. Note even / odd used for different panes\r
689         for(i=nrVariations[n]-2; i>=0; i-=2) {\r
690                 // put new item behind those we haven't looked at\r
691                 offs = textEnd[i+n];\r
692                 textEnd[i+n+2] = offs + len;\r
693                 scores[i+n+2] = newScore;\r
694                 if(newScore < scores[i+n]) break;\r
695                 // if it had higher score as previous, move previous in stead\r
696                 scores[i+n+2] = scores[i+n];\r
697                 textEnd[i+n+2] = textEnd[i+n] + len;\r
698         }\r
699         if(i<0) {\r
700                 offs = 0;\r
701                 textEnd[n] = offs + len;\r
702                 scores[n] = newScore;\r
703         }\r
704         nrVariations[n] += 2;\r
705       return offs;\r
706 }\r
707 \r
708 // pure back end, now SetWindowText is called via wrapper DoSetWindowText\r
709 static void UpdateControls( EngineOutputData * ed )\r
710 {\r
711 //    int isPondering = FALSE;\r
712 \r
713     char s_label[MAX_NAME_LENGTH + 32];\r
714     \r
715     char * name = ed->name;\r
716 \r
717     /* Label */\r
718     if( name == 0 || *name == '\0' ) {\r
719         name = "?";\r
720     }\r
721 \r
722     strncpy( s_label, name, MAX_NAME_LENGTH );\r
723     s_label[ MAX_NAME_LENGTH-1 ] = '\0';\r
724 \r
725 #ifdef SHOW_PONDERING\r
726     if( IsEnginePondering( ed->which ) ) {\r
727         char buf[8];\r
728 \r
729         buf[0] = '\0';\r
730 \r
731         if( ed->hint != 0 && *ed->hint != '\0' ) {\r
732             strncpy( buf, ed->hint, sizeof(buf) );\r
733             buf[sizeof(buf)-1] = '\0';\r
734         }\r
735         else if( ed->pv != 0 && *ed->pv != '\0' ) {\r
736             char * sep = strchr( ed->pv, ' ' );\r
737             int buflen = sizeof(buf);\r
738 \r
739             if( sep != NULL ) {\r
740                 buflen = sep - ed->pv + 1;\r
741                 if( buflen > sizeof(buf) ) buflen = sizeof(buf);\r
742             }\r
743 \r
744             strncpy( buf, ed->pv, buflen );\r
745             buf[ buflen-1 ] = '\0';\r
746         }\r
747 \r
748         SetEngineState( ed->which, STATE_PONDERING, buf );\r
749     }\r
750     else if( gameMode == TwoMachinesPlay ) {\r
751         SetEngineState( ed->which, STATE_THINKING, "" );\r
752     }\r
753     else if( gameMode == AnalyzeMode || gameMode == AnalyzeFile\r
754           || (gameMode == IcsObserving && appData.icsEngineAnalyze)) { // [HGM] ICS-analyze\r
755         char buf[64];\r
756         int time_secs = ed->time / 100;\r
757         int time_mins = time_secs / 60;\r
758 \r
759         buf[0] = '\0';\r
760 \r
761         if( ed->an_move_index != 0 && ed->an_move_count != 0 && *ed->hint != '\0' ) {\r
762             char mov[16];\r
763 \r
764             strncpy( mov, ed->hint, sizeof(mov) );\r
765             mov[ sizeof(mov)-1 ] = '\0';\r
766 \r
767             sprintf( buf, "%d/%d: %s [%02d:%02d:%02d]", ed->an_move_index, ed->an_move_count, mov, time_mins / 60, time_mins % 60, time_secs % 60 );\r
768         }\r
769 \r
770         SetEngineState( ed->which, STATE_ANALYZING, buf );\r
771     }\r
772     else {\r
773         SetEngineState( ed->which, STATE_IDLE, "" );\r
774     }\r
775 #endif\r
776 \r
777     DoSetWindowText( ed->which, nLabel, s_label );\r
778 \r
779     s_label[0] = '\0';\r
780 \r
781     if( ed->time > 0 && ed->nodes > 0 ) {\r
782         unsigned long nps_100 = ed->nodes / ed->time;\r
783 \r
784         if( nps_100 < 100000 ) {\r
785             sprintf( s_label, "NPS: %lu", nps_100 * 100 );\r
786         }\r
787         else {\r
788             sprintf( s_label, "NPS: %.1fk", nps_100 / 10.0 );\r
789         }\r
790     }\r
791 \r
792     DoSetWindowText( ed->which, nLabelNPS, s_label );\r
793 \r
794     /* Memo */\r
795     if( ed->pv != 0 && *ed->pv != '\0' ) {\r
796         char s_nodes[24];\r
797         char s_score[16];\r
798         char s_time[24];\r
799         char buf[256];\r
800         int buflen;\r
801         int time_secs = ed->time / 100;\r
802         int time_cent = ed->time % 100;\r
803 \r
804         /* Nodes */\r
805         if( ed->nodes < 1000000 ) {\r
806             sprintf( s_nodes, u64Display, ed->nodes );\r
807         }\r
808         else {\r
809             sprintf( s_nodes, "%.1fM", u64ToDouble(ed->nodes) / 1000000.0 );\r
810         }\r
811 \r
812         /* Score */\r
813         if( ed->score > 0 ) {\r
814             sprintf( s_score, "+%.2f", ed->score / 100.0 );\r
815         }\r
816         else {\r
817             sprintf( s_score, "%.2f", ed->score / 100.0 );\r
818         }\r
819 \r
820         /* Time */\r
821         sprintf( s_time, "%d:%02d.%02d", time_secs / 60, time_secs % 60, time_cent );\r
822 \r
823         /* Put all together... */\r
824         if(ed->nodes == 0 && ed->score == 0 && ed->time == 0) sprintf( buf, "%3d\t", ed->depth ); else \r
825         sprintf( buf, "%3d\t%s\t%s\t%s\t", ed->depth, s_score, s_nodes, s_time );\r
826 \r
827         /* Add PV */\r
828         buflen = strlen(buf);\r
829 \r
830         strncpy( buf + buflen, ed->pv, sizeof(buf) - buflen );\r
831 \r
832         buf[ sizeof(buf) - 3 ] = '\0';\r
833 \r
834         strcat( buf + buflen, "\r\n" );\r
835 \r
836         /* Update memo */\r
837         InsertIntoMemo( ed->which, buf, InsertionPoint(strlen(buf), ed) );\r
838     }\r
839 \r
840     /* Colors */\r
841     SetEngineColorIcon( ed->which );\r
842 }\r
843 \r
844 // back end\r
845 int EngineOutputIsUp()\r
846 {\r
847     return engineOutputDialogUp;\r
848 }\r
849 \r
850 // [HGM] kibitz: write kibitz line; split window for it if necessary\r
851 void OutputKibitz(int window, char *text)\r
852 {\r
853         if(!EngineOutputIsUp()) return;\r
854         if(!opponentKibitzes) { // on first kibitz of game, clear memos\r
855             DoClearMemo(1);\r
856             if(gameMode == IcsObserving) DoClearMemo(0);\r
857         }\r
858         opponentKibitzes = TRUE; // this causes split window DisplayMode in ICS modes.\r
859         VerifyDisplayMode();\r
860         if(gameMode == IcsObserving) {\r
861             DoSetWindowText(0, nLabel, gameInfo.white);\r
862             SetIcon( 0, nColorIcon,  nColorWhite);\r
863             SetIcon( 0, nStateIcon,  nClear);\r
864         }\r
865         DoSetWindowText(1, nLabel, gameMode == IcsPlayingBlack ? gameInfo.white : gameInfo.black); // opponent name\r
866         SetIcon( 1, nColorIcon,  gameMode == IcsPlayingBlack ? nColorWhite : nColorBlack);\r
867         SetIcon( 1, nStateIcon,  nClear);\r
868         InsertIntoMemo(window-1, text, 0); // [HGM] multivar: always at top\r
869 }\r