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