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