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