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