Some code refactoring and cleanup; one small bug fix
[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 )\r
290 {\r
291     SendMessage( outputField[which][nMemo], EM_SETSEL, 0, 0 );\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 \r
438 // back end, due to front-end wrapper for SetWindowText, and new SetIcon arguments\r
439 static void SetEngineState( int which, int state, char * state_data )\r
440 {\r
441     int x_which = 1 - which;\r
442 \r
443     if( engineState[ which ] != state ) {\r
444         engineState[ which ] = state;\r
445 \r
446         switch( state ) {\r
447         case STATE_THINKING:\r
448             SetIcon( which, nStateIcon, nThinking );\r
449             if( engineState[ x_which ] == STATE_THINKING ) {\r
450                 SetEngineState( x_which, STATE_IDLE, "" );\r
451             }\r
452             break;\r
453         case STATE_PONDERING:\r
454             SetIcon( which, nStateIcon, nPondering );\r
455             break;\r
456         case STATE_ANALYZING:\r
457             SetIcon( which, nStateIcon, nAnalyzing );\r
458             break;\r
459         default:\r
460             SetIcon( which, nStateIcon, nClear );\r
461             break;\r
462         }\r
463     }\r
464 \r
465     if( state_data != 0 ) {\r
466         DoSetWindowText( which, nStateData, state_data );\r
467     }\r
468 }\r
469 \r
470 // back end, now the front-end wrapper ClearMemo is used, and ed no longer contains handles.\r
471 void EngineOutputUpdate( FrontEndProgramStats * stats )\r
472 {\r
473     EngineOutputData ed;\r
474     int clearMemo = FALSE;\r
475     int which;\r
476     int depth;\r
477 \r
478     if( stats == 0 ) {\r
479         SetEngineState( 0, STATE_IDLE, "" );\r
480         SetEngineState( 1, STATE_IDLE, "" );\r
481         return;\r
482     }\r
483 \r
484     if(gameMode == IcsObserving && !appData.icsEngineAnalyze)\r
485         return; // [HGM] kibitz: shut up engine if we are observing an ICS game\r
486 \r
487     which = stats->which;\r
488     depth = stats->depth;\r
489 \r
490     if( which < 0 || which > 1 || depth < 0 || stats->time < 0 || stats->pv == 0 ) {\r
491         return;\r
492     }\r
493 \r
494     if( engineOutputDialog == NULL ) {\r
495         return;\r
496     }\r
497 \r
498     VerifyDisplayMode();\r
499 \r
500     ed.which = which;\r
501     ed.depth = depth;\r
502     ed.nodes = stats->nodes;\r
503     ed.score = stats->score;\r
504     ed.time = stats->time;\r
505     ed.pv = stats->pv;\r
506     ed.hint = stats->hint;\r
507     ed.an_move_index = stats->an_move_index;\r
508     ed.an_move_count = stats->an_move_count;\r
509 \r
510     /* Get target control. [HGM] this is moved to front end, which get them from a table */\r
511     if( which == 0 ) {\r
512         ed.name = first.tidy;\r
513     }\r
514     else {\r
515         ed.name = second.tidy;\r
516     }\r
517 \r
518     /* Clear memo if needed */\r
519     if( lastDepth[which] > depth || (lastDepth[which] == depth && depth <= 1) ) {\r
520         clearMemo = TRUE;\r
521     }\r
522 \r
523     if( lastForwardMostMove[which] != forwardMostMove ) {\r
524         clearMemo = TRUE;\r
525     }\r
526 \r
527     if( clearMemo ) DoClearMemo(which);\r
528 \r
529     /* Update */\r
530     lastDepth[which] = depth == 1 && ed.nodes == 0 ? 0 : depth; // [HGM] info-line kudge\r
531     lastForwardMostMove[which] = forwardMostMove;\r
532 \r
533     if( ed.pv != 0 && ed.pv[0] == ' ' ) {\r
534         if( strncmp( ed.pv, " no PV", 6 ) == 0 ) { /* Hack on hack! :-O */\r
535             ed.pv = "";\r
536         }\r
537     }\r
538 \r
539     UpdateControls( &ed );\r
540 }\r
541 \r
542 #define ENGINE_COLOR_WHITE      'w'\r
543 #define ENGINE_COLOR_BLACK      'b'\r
544 #define ENGINE_COLOR_UNKNOWN    ' '\r
545 \r
546 // pure back end\r
547 char GetEngineColor( int which )\r
548 {\r
549     char result = ENGINE_COLOR_UNKNOWN;\r
550 \r
551     if( which == 0 || which == 1 ) {\r
552         ChessProgramState * cps;\r
553 \r
554         switch (gameMode) {\r
555         case MachinePlaysBlack:\r
556         case IcsPlayingBlack:\r
557             result = ENGINE_COLOR_BLACK;\r
558             break;\r
559         case MachinePlaysWhite:\r
560         case IcsPlayingWhite:\r
561             result = ENGINE_COLOR_WHITE;\r
562             break;\r
563         case AnalyzeMode:\r
564         case AnalyzeFile:\r
565             result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;\r
566             break;\r
567         case TwoMachinesPlay:\r
568             cps = (which == 0) ? &first : &second;\r
569             result = cps->twoMachinesColor[0];\r
570             result = result == 'w' ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;\r
571             break;\r
572         default: ; // does not happen, but suppresses pedantic warnings\r
573         }\r
574     }\r
575 \r
576     return result;\r
577 }\r
578 \r
579 // pure back end\r
580 char GetActiveEngineColor()\r
581 {\r
582     char result = ENGINE_COLOR_UNKNOWN;\r
583 \r
584     if( gameMode == TwoMachinesPlay ) {\r
585         result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;\r
586     }\r
587 \r
588     return result;\r
589 }\r
590 \r
591 // pure back end\r
592 static int IsEnginePondering( int which )\r
593 {\r
594     int result = FALSE;\r
595 \r
596     switch (gameMode) {\r
597     case MachinePlaysBlack:\r
598     case IcsPlayingBlack:\r
599         if( WhiteOnMove(forwardMostMove) ) result = TRUE;\r
600         break;\r
601     case MachinePlaysWhite:\r
602     case IcsPlayingWhite:\r
603         if( ! WhiteOnMove(forwardMostMove) ) result = TRUE;\r
604         break;\r
605     case TwoMachinesPlay:\r
606         if( GetActiveEngineColor() != ENGINE_COLOR_UNKNOWN ) {\r
607             if( GetEngineColor( which ) != GetActiveEngineColor() ) result = TRUE;\r
608         }\r
609         break;\r
610     default: ; // does not happen, but suppresses pedantic warnings\r
611     }\r
612 \r
613     return result;\r
614 }\r
615 \r
616 // back end\r
617 static void SetDisplayMode( int mode )\r
618 {\r
619     if( windowMode != mode ) {\r
620         windowMode = mode;\r
621 \r
622         ResizeWindowControls( engineOutputDialog, mode );\r
623     }\r
624 }\r
625 \r
626 // pure back end\r
627 static void VerifyDisplayMode()\r
628 {\r
629     int mode;\r
630 \r
631     /* Get proper mode for current game */\r
632     switch( gameMode ) {\r
633     case IcsObserving:    // [HGM] ICS analyze\r
634         if(!appData.icsEngineAnalyze) return;\r
635     case AnalyzeMode:\r
636     case AnalyzeFile:\r
637     case MachinePlaysWhite:\r
638     case MachinePlaysBlack:\r
639         mode = 0;\r
640         break;\r
641     case IcsPlayingWhite:\r
642     case IcsPlayingBlack:\r
643         mode = appData.zippyPlay && opponentKibitzes; // [HGM] kibitz\r
644         break;\r
645     case TwoMachinesPlay:\r
646         mode = 1;\r
647         break;\r
648     default:\r
649         /* Do not change */\r
650         return;\r
651     }\r
652 \r
653     SetDisplayMode( mode );\r
654 }\r
655 \r
656 // back end. Determine what icon to se in the color-icon field, and print it\r
657 static void SetEngineColorIcon( int which )\r
658 {\r
659     char color = GetEngineColor(which);\r
660     int nicon = 0;\r
661 \r
662     if( color == ENGINE_COLOR_BLACK )\r
663         nicon = nColorBlack;\r
664     else if( color == ENGINE_COLOR_WHITE )\r
665         nicon = nColorWhite;\r
666     else\r
667         nicon = nColorUnknown;\r
668 \r
669     SetIcon( which, nColorIcon, nicon );\r
670 }\r
671 \r
672 #define MAX_NAME_LENGTH 32\r
673 \r
674 // pure back end, now SetWindowText is called via wrapper DoSetWindowText\r
675 static void UpdateControls( EngineOutputData * ed )\r
676 {\r
677 //    int isPondering = FALSE;\r
678 \r
679     char s_label[MAX_NAME_LENGTH + 32];\r
680     \r
681     char * name = ed->name;\r
682 \r
683     /* Label */\r
684     if( name == 0 || *name == '\0' ) {\r
685         name = "?";\r
686     }\r
687 \r
688     strncpy( s_label, name, MAX_NAME_LENGTH );\r
689     s_label[ MAX_NAME_LENGTH-1 ] = '\0';\r
690 \r
691 #ifdef SHOW_PONDERING\r
692     if( IsEnginePondering( ed->which ) ) {\r
693         char buf[8];\r
694 \r
695         buf[0] = '\0';\r
696 \r
697         if( ed->hint != 0 && *ed->hint != '\0' ) {\r
698             strncpy( buf, ed->hint, sizeof(buf) );\r
699             buf[sizeof(buf)-1] = '\0';\r
700         }\r
701         else if( ed->pv != 0 && *ed->pv != '\0' ) {\r
702             char * sep = strchr( ed->pv, ' ' );\r
703             int buflen = sizeof(buf);\r
704 \r
705             if( sep != NULL ) {\r
706                 buflen = sep - ed->pv + 1;\r
707                 if( buflen > sizeof(buf) ) buflen = sizeof(buf);\r
708             }\r
709 \r
710             strncpy( buf, ed->pv, buflen );\r
711             buf[ buflen-1 ] = '\0';\r
712         }\r
713 \r
714         SetEngineState( ed->which, STATE_PONDERING, buf );\r
715     }\r
716     else if( gameMode == TwoMachinesPlay ) {\r
717         SetEngineState( ed->which, STATE_THINKING, "" );\r
718     }\r
719     else if( gameMode == AnalyzeMode || gameMode == AnalyzeFile\r
720           || (gameMode == IcsObserving && appData.icsEngineAnalyze)) { // [HGM] ICS-analyze\r
721         char buf[64];\r
722         int time_secs = ed->time / 100;\r
723         int time_mins = time_secs / 60;\r
724 \r
725         buf[0] = '\0';\r
726 \r
727         if( ed->an_move_index != 0 && ed->an_move_count != 0 && *ed->hint != '\0' ) {\r
728             char mov[16];\r
729 \r
730             strncpy( mov, ed->hint, sizeof(mov) );\r
731             mov[ sizeof(mov)-1 ] = '\0';\r
732 \r
733             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
734         }\r
735 \r
736         SetEngineState( ed->which, STATE_ANALYZING, buf );\r
737     }\r
738     else {\r
739         SetEngineState( ed->which, STATE_IDLE, "" );\r
740     }\r
741 #endif\r
742 \r
743     DoSetWindowText( ed->which, nLabel, s_label );\r
744 \r
745     s_label[0] = '\0';\r
746 \r
747     if( ed->time > 0 && ed->nodes > 0 ) {\r
748         unsigned long nps_100 = ed->nodes / ed->time;\r
749 \r
750         if( nps_100 < 100000 ) {\r
751             sprintf( s_label, "NPS: %lu", nps_100 * 100 );\r
752         }\r
753         else {\r
754             sprintf( s_label, "NPS: %.1fk", nps_100 / 10.0 );\r
755         }\r
756     }\r
757 \r
758     DoSetWindowText( ed->which, nLabelNPS, s_label );\r
759 \r
760     /* Memo */\r
761     if( ed->pv != 0 && *ed->pv != '\0' ) {\r
762         char s_nodes[24];\r
763         char s_score[16];\r
764         char s_time[24];\r
765         char buf[256];\r
766         int buflen;\r
767         int time_secs = ed->time / 100;\r
768         int time_cent = ed->time % 100;\r
769 \r
770         /* Nodes */\r
771         if( ed->nodes < 1000000 ) {\r
772             sprintf( s_nodes, u64Display, ed->nodes );\r
773         }\r
774         else {\r
775             sprintf( s_nodes, "%.1fM", u64ToDouble(ed->nodes) / 1000000.0 );\r
776         }\r
777 \r
778         /* Score */\r
779         if( ed->score > 0 ) {\r
780             sprintf( s_score, "+%.2f", ed->score / 100.0 );\r
781         }\r
782         else {\r
783             sprintf( s_score, "%.2f", ed->score / 100.0 );\r
784         }\r
785 \r
786         /* Time */\r
787         sprintf( s_time, "%d:%02d.%02d", time_secs / 60, time_secs % 60, time_cent );\r
788 \r
789         /* Put all together... */\r
790         if(ed->nodes == 0 && ed->score == 0 && ed->time == 0) sprintf( buf, "%3d\t", ed->depth ); else \r
791         sprintf( buf, "%3d\t%s\t%s\t%s\t", ed->depth, s_score, s_nodes, s_time );\r
792 \r
793         /* Add PV */\r
794         buflen = strlen(buf);\r
795 \r
796         strncpy( buf + buflen, ed->pv, sizeof(buf) - buflen );\r
797 \r
798         buf[ sizeof(buf) - 3 ] = '\0';\r
799 \r
800         strcat( buf + buflen, "\r\n" );\r
801 \r
802         /* Update memo */\r
803         InsertIntoMemo( ed->which, buf );\r
804     }\r
805 \r
806     /* Colors */\r
807     SetEngineColorIcon( ed->which );\r
808 }\r
809 \r
810 // back end\r
811 int EngineOutputIsUp()\r
812 {\r
813     return engineOutputDialogUp;\r
814 }\r
815 \r
816 // [HGM] kibitz: write kibitz line; split window for it if necessary\r
817 void OutputKibitz(int window, char *text)\r
818 {\r
819         if(!EngineOutputIsUp()) return;\r
820         if(!opponentKibitzes) { // on first kibitz of game, clear memos\r
821             DoClearMemo(1);\r
822             if(gameMode == IcsObserving) DoClearMemo(0);\r
823         }\r
824         opponentKibitzes = TRUE; // this causes split window DisplayMode in ICS modes.\r
825         VerifyDisplayMode();\r
826         if(gameMode == IcsObserving) {\r
827             DoSetWindowText(0, nLabel, gameInfo.white);\r
828             SetIcon( 0, nColorIcon,  nColorWhite);\r
829             SetIcon( 0, nStateIcon,  nClear);\r
830         }\r
831         DoSetWindowText(1, nLabel, gameMode == IcsPlayingBlack ? gameInfo.white : gameInfo.black); // opponent name\r
832         SetIcon( 1, nColorIcon,  gameMode == IcsPlayingBlack ? nColorWhite : nColorBlack);\r
833         SetIcon( 1, nStateIcon,  nClear);\r
834         InsertIntoMemo(window-1, text);\r
835 }\r