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