Fix multi-leg promotions
[xboard.git] / evalgraph.c
1 /*
2  * evalgraph.c - Evaluation graph back-end part
3  *
4  * Author: Alessandro Scotti (Dec 2005)
5  *
6  * Copyright 2005 Alessandro Scotti
7  *
8  * Enhancments Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015,
9  * 2016 Free Software Foundation, Inc.
10  *
11  * ------------------------------------------------------------------------
12  *
13  * GNU XBoard is free software: you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation, either version 3 of the License, or (at
16  * your option) any later version.
17  *
18  * GNU XBoard is distributed in the hope that it will be useful, but
19  * WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21  * General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program. If not, see http://www.gnu.org/licenses/.  *
25  *
26  *------------------------------------------------------------------------
27  ** See the file ChangeLog for a revision history.  */
28
29 // code refactored by HGM to obtain front-end / back-end separation
30
31 #include "config.h"
32
33 #include <stdio.h>
34
35 #if STDC_HEADERS
36 # include <stdlib.h>
37 # include <string.h>
38 #else /* not STDC_HEADERS */
39 # if HAVE_STRING_H
40 #  include <string.h>
41 # else /* not HAVE_STRING_H */
42 #  include <strings.h>
43 # endif /* not HAVE_STRING_H */
44 #endif /* not STDC_HEADERS */
45
46 #include "common.h"
47 #include "frontend.h"
48 #include "backend.h"
49 #include "evalgraph.h"
50
51 /* Module globals */
52 ChessProgramStats_Move * currPvInfo;
53 int currFirst = 0;
54 int currLast = 0;
55 int currCurrent = -1;
56 int range = 1;
57 int differentialView;
58
59 int nWidthPB = 0;
60 int nHeightPB = 0;
61
62 int MarginX = 18;
63 int MarginW = 4;
64 int MarginH = 4;
65
66 // back-end
67 static void
68 DrawLine (int x1, int y1, int x2, int y2, int penType)
69 {
70     DrawSegment( x1, y1, NULL, NULL, PEN_NONE );
71     DrawSegment( x2, y2, NULL, NULL, penType );
72 }
73
74 // back-end
75 static void
76 DrawLineEx (int x1, int y1, int x2, int y2, int penType)
77 {
78     int savX, savY;
79     DrawSegment( x1, y1, &savX, &savY, PEN_NONE );
80     DrawSegment( x2, y2, NULL, NULL, penType );
81     DrawSegment( savX, savY, NULL, NULL, PEN_NONE );
82 }
83
84 // back-end
85 static int
86 GetPvScore (int index)
87 {
88     int score = currPvInfo[ index ].score;
89
90     if(differentialView) score = index < currLast-1 ? -currPvInfo[ index+1 ].score - score : 0;
91     if( index & 1 ) score = -score; /* Flip score for black */
92
93     return score;
94 }
95
96 char *
97 MakeEvalTitle (char *title)
98 {
99     int score, depth;
100     static char buf[MSG_SIZ];
101
102     if( currCurrent <0 ) return title; // currCurrent = -1 crashed WB on start without ini file!
103     score = currPvInfo[ currCurrent ].score;
104     depth = currPvInfo[ currCurrent ].depth;
105
106     if( depth <=0 ) return title;
107     if( currCurrent & 1 ) score = -score; /* Flip score for black */
108     snprintf(buf, MSG_SIZ, "%s {%d: %s%.2f/%-2d %d}", title, currCurrent/2+1,
109                                 score>0 ? "+" : " ", score/100., depth, (currPvInfo[currCurrent].time+50)/100);
110
111     return buf;
112 }
113
114 // back-end
115 /*
116     For a centipawn value, this function returns the height of the corresponding
117     histogram, centered on the reference axis.
118
119     Note: height can be negative!
120 */
121 static int
122 GetValueY (int value)
123 {
124     if( value < -range*700 ) value = -range*700;
125     if( value > +range*700 ) value = +range*700;
126     if(value > 100*range)  value += (appData.zoom - 1)*100*range; else
127     if(value < -100*range) value -= (appData.zoom - 1)*100*range; else
128         value *= appData.zoom;
129     return (nHeightPB / 2) - (int)(value * (nHeightPB - 2*MarginH) / ((1200. + 200.*appData.zoom)*range));
130 }
131
132 // the brush selection is made part of the DrawLine, by passing a style argument
133 // the wrapper for doing the text output makes this back-end
134 static void
135 DrawAxisSegmentHoriz (int value, Boolean drawValue)
136 {
137     int y = GetValueY( range*value*100 );
138
139     if( drawValue ) {
140         char buf[MSG_SIZ], *b = buf;
141
142         if( value > 0 ) *b++ = '+';
143         sprintf(b, "%d", range*value);
144
145         DrawEvalText(buf, strlen(buf), y);
146     }
147     // [HGM] counts on DrawEvalText to have select transparent background for dotted line!
148     DrawLine( MarginX, y, MarginX + MarginW, y, PEN_BLACK ); // Y-axis tick marks
149     DrawLine( MarginX + MarginW, y, nWidthPB - MarginW, y, PEN_DOTTED ); // hor grid
150 }
151
152 // The DrawLines again must select their own brush.
153 // the initial brush selection is useless? BkMode needed for dotted line and text
154 static void
155 DrawAxis ()
156 {
157     int cy = nHeightPB / 2, space = nHeightPB/(6 + appData.zoom);
158
159     DrawAxisSegmentHoriz( +5, TRUE );
160     DrawAxisSegmentHoriz( +3, space >= 20 );
161     DrawAxisSegmentHoriz( +1, space >= 20 && space*appData.zoom >= 40 );
162     DrawAxisSegmentHoriz(  0, TRUE );
163     DrawAxisSegmentHoriz( -1, space >= 20 && space*appData.zoom >= 40 );
164     DrawAxisSegmentHoriz( -3, space >= 20 );
165     DrawAxisSegmentHoriz( -5, TRUE );
166
167     DrawLine( MarginX + MarginW, cy, nWidthPB - MarginW, cy, PEN_BLACK ); // x-axis
168     DrawLine( MarginX + MarginW, MarginH, MarginX + MarginW, nHeightPB - MarginH, PEN_BLACK ); // y-axis
169 }
170
171 // back-end
172 static void
173 DrawHistogram (int x, int y, int width, int value, int side)
174 {
175     int left, top, right, bottom;
176
177     if( value > -appData.evalThreshold*range && value < +appData.evalThreshold*range ) return;
178
179     left = x;
180     right = left + width + 1;
181
182     if( value > 0 ) {
183         top = GetValueY( value );
184         bottom = y+1;
185     }
186     else {
187         top = y;
188         bottom = GetValueY( value ) + 1;
189     }
190
191
192     if( width == MIN_HIST_WIDTH ) {
193         right--;
194         DrawRectangle( left, top, right, bottom, side, FILLED );
195     }
196     else {
197         DrawRectangle( left, top, right, bottom, side, OPEN );
198     }
199 }
200
201 // back-end
202 static void
203 DrawSeparator (int index, int x)
204 {
205     if( index > 0 ) {
206         if( index == currCurrent ) {
207             DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_BLUEDOTTED );
208         }
209         else if( (index % 20) == 0 ) {
210             DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_DOTTED );
211         }
212     }
213 }
214
215 // made back-end by replacing MoveToEx and LineTo by DrawSegment
216 /* Actually draw histogram as a diagram, cause there's too much data */
217 static void
218 DrawHistogramAsDiagram (int cy, int paint_width, int hist_count)
219 {
220     double step;
221     int i;
222
223     /* Rescale the graph every few moves (as opposed to every move) */
224     hist_count -= hist_count % 8;
225     hist_count += 8;
226     hist_count /= 2;
227
228     step = (double) paint_width / (hist_count + 1);
229
230     for( i=0; i<2; i++ ) {
231         int index = currFirst;
232         int side = (currCurrent + i + 1) & 1; /* Draw current side last */
233         double x = MarginX + MarginW;
234
235         if( (index & 1) != side ) {
236             x += step / 2;
237             index++;
238         }
239
240         DrawSegment( (int) x, cy, NULL, NULL, PEN_NONE );
241
242         index += 2;
243
244         while( index < currLast ) {
245             x += step;
246
247             DrawSeparator( index, (int) x );
248
249             /* Extend line up to current point */
250             if( currPvInfo[index].depth > 0 ) {
251               DrawSegment((int) x, GetValueY( GetPvScore(index) ), NULL, NULL, (side==0 ? PEN_BOLDWHITE: PEN_BOLDBLACK) );
252             }
253
254             index += 2;
255         }
256     }
257 }
258
259 // back-end, delete pen selection
260 static void
261 DrawHistogramFull (int cy, int hist_width, int hist_count)
262 {
263     int i;
264
265 //    SelectObject( hdcPB, GetStockObject(BLACK_PEN) );
266
267     for( i=0; i<hist_count; i++ ) {
268         int index = currFirst + i;
269         int x = MarginX + MarginW + index * hist_width;
270
271         /* Draw a separator every 10 moves */
272         DrawSeparator( index, x );
273
274         /* Draw histogram */
275         if( currPvInfo[i].depth > 0 ) {
276             DrawHistogram( x, cy, hist_width, GetPvScore(index), index & 1 );
277         }
278     }
279 }
280
281 typedef struct {
282     int cy;
283     int hist_width;
284     int hist_count;
285     int paint_width;
286 } VisualizationData;
287
288 // back-end
289 static Boolean
290 InitVisualization (VisualizationData *vd)
291 {
292     Boolean result = FALSE;
293
294     vd->cy = nHeightPB / 2;
295     vd->hist_width = MIN_HIST_WIDTH;
296     vd->hist_count = currLast - currFirst;
297     vd->paint_width = nWidthPB - MarginX - 2*MarginW;
298
299     if( vd->hist_count > 0 ) {
300         result = TRUE;
301
302         /* Compute width */
303         vd->hist_width = vd->paint_width / vd->hist_count;
304
305         if( vd->hist_width > MAX_HIST_WIDTH ) vd->hist_width = MAX_HIST_WIDTH;
306
307         vd->hist_width -= vd->hist_width % 2;
308     }
309
310     return result;
311 }
312
313 // back-end
314 static void
315 DrawHistograms ()
316 {
317     VisualizationData vd;
318     int i; double step = 1;
319
320     if( InitVisualization( &vd ) ) {
321         if( vd.hist_width < MIN_HIST_WIDTH ) {
322             DrawHistogramAsDiagram( vd.cy, vd.paint_width, vd.hist_count );
323             step = 0.5*vd.paint_width / (((vd.hist_count | 7) + 1)/2 + 1.);
324         }
325         else {
326             DrawHistogramFull( vd.cy, step = vd.hist_width, vd.hist_count );
327         }
328     }
329     if(!differentialView) return;
330     differentialView = 0;
331     DrawSegment( MarginX + MarginW, vd.cy, NULL, NULL, PEN_NONE );
332     for( i=0; i<vd.hist_count; i++ ) {
333         int index = currFirst + i;
334         int x = MarginX + MarginW + index * step + step/2;
335         DrawSegment((int) x, GetValueY( GetPvScore(index) ), NULL, NULL, PEN_ANY );
336     }
337     differentialView = 1;
338 }
339
340 // back-end
341 int
342 GetMoveIndexFromPoint (int x, int y)
343 {
344     int result = -1;
345     int start_x = MarginX + MarginW;
346     VisualizationData vd;
347
348     if( x >= start_x && InitVisualization( &vd ) ) {
349         /* Almost an hack here... we duplicate some of the paint logic */
350         if( vd.hist_width < MIN_HIST_WIDTH ) {
351             double step;
352
353             vd.hist_count -= vd.hist_count % 8;
354             vd.hist_count += 8;
355             vd.hist_count /= 2;
356
357             step = (double) vd.paint_width / (vd.hist_count + 1);
358             step /= 2;
359
360             result = (int) (0.5 + (double) (x - start_x) / step);
361         }
362         else {
363             result = (x - start_x) / vd.hist_width;
364         }
365     }
366
367     if( result >= currLast ) {
368         result = -1;
369     }
370
371     return result;
372 }
373
374 // init and display part split of so they can be moved to front end
375 void
376 PaintEvalGraph (void)
377 {
378     VariantClass v = gameInfo.variant;
379     range = (gameInfo.holdingsWidth && v != VariantSuper && v != VariantGreat && v != VariantSChess) ? 2 : 1; // [HGM] double range in drop games
380     /* Draw */
381     DrawRectangle(0, 0, nWidthPB, nHeightPB, 2, FILLED);
382     DrawAxis();
383     DrawHistograms();
384 }