Rev 66 | Rev 69 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed
| Rev 66 | Rev 68 | ||
|---|---|---|---|
| Line 12... | Line 12... | ||
| 12 | { \ |
12 | { \ |
| 13 | (dest)[char_index] = 0; \ |
13 | (dest)[char_index] = 0; \ |
| 14 | break; \ |
14 | break; \ |
| 15 | } \ |
15 | } \ |
| 16 | } |
16 | } |
| - | 17 | ||
| - | 18 | ||
| - | 19 | // global variables used in this module only |
|
| - | 20 | static char *pgnfile_data = NULL; // mallocated |
|
| - | 21 | static size_t pgnfile_size = 0; |
|
| 17 | 22 | ||
| 18 | 23 | ||
| 19 | // prototypes of local functions |
24 | // prototypes of local functions |
| 20 | static void PGNFile_GameList_Init (int entry_count); |
25 | static void PGNFile_GameList_Init (int entry_count); |
| 21 | static void PGNFile_GameList_Shutdown (void); |
26 | static void PGNFile_GameList_Shutdown (void); |
| Line 25... | Line 30... | ||
| 25 | bool PGNFile_Load (const wchar_t *pgnfile_pathname) |
30 | bool PGNFile_Load (const wchar_t *pgnfile_pathname) |
| 26 | { |
31 | { |
| 27 | // this function loads a PGN file and builds the game databases of the games described in this file |
32 | // this function loads a PGN file and builds the game databases of the games described in this file |
| 28 | 33 | ||
| 29 | char line_buffer[256]; // PGN files have 256 chars max per line by design |
34 | char line_buffer[256]; // PGN files have 256 chars max per line by design |
| 30 | char *file_data; // mallocated |
- | |
| 31 | char *buffer; |
35 | char *buffer; |
| 32 | int file_size; |
- | |
| 33 | int file_index; |
36 | int file_index; |
| 34 | int char_index; |
37 | int char_index; |
| 35 | int entry_count; |
38 | int entry_count; |
| 36 | FILE *fp; |
39 | FILE *fp; |
| 37 | size_t converted_count; // used by the STRING_TO_CHAR macro |
40 | size_t converted_count; // used by the STRING_TO_CHAR macro |
| Line 41... | Line 44... | ||
| 41 | if (fp == NULL) |
44 | if (fp == NULL) |
| 42 | return (false); // on error, cancel |
45 | return (false); // on error, cancel |
| 43 | 46 | ||
| 44 | // get file length |
47 | // get file length |
| 45 | fseek (fp, 0, SEEK_END); |
48 | fseek (fp, 0, SEEK_END); |
| 46 |
|
49 | pgnfile_size = ftell (fp); |
| 47 | fseek (fp, 0, SEEK_SET); |
50 | fseek (fp, 0, SEEK_SET); |
| 48 | 51 | ||
| 49 | // mallocate space for it and read it all at once |
52 | // mallocate space for it and read it all at once |
| 50 |
|
53 | pgnfile_data = (char *) SAFE_realloc (pgnfile_data, 0, pgnfile_size, sizeof (char), false); |
| 51 | fread ( |
54 | fread (pgnfile_data, pgnfile_size, 1, fp); |
| 52 | fclose (fp); // we no longer need the file, so close it |
55 | fclose (fp); // we no longer need the file, so close it |
| 53 | 56 | ||
| 54 | // now the file is fully loaded in memory |
57 | // now the file is fully loaded in memory |
| 55 | 58 | ||
| 56 | // read line per line and count the number of games |
59 | // read line per line and count the number of games |
| 57 | buffer = |
60 | buffer = pgnfile_data; |
| 58 | entry_count = 0; |
61 | entry_count = 0; |
| 59 | while ((buffer = sgets (line_buffer, sizeof (line_buffer), buffer)) != NULL) |
62 | while ((buffer = sgets (line_buffer, sizeof (line_buffer), buffer)) != NULL) |
| 60 | if (strncmp (line_buffer, "[Event \"", 8) == 0) |
63 | if (strncmp (line_buffer, "[Event \"", 8) == 0) |
| 61 | entry_count++; // we know now one game more |
64 | entry_count++; // we know now one game more |
| 62 | 65 | ||
| 63 | // now prepare the games database for "entry_count" games |
66 | // now prepare the games database for "entry_count" games |
| 64 | PGNFile_GameList_Init (entry_count); |
67 | PGNFile_GameList_Init (entry_count); |
| 65 | 68 | ||
| 66 | // read line per line |
69 | // read line per line |
| 67 | buffer = |
70 | buffer = pgnfile_data; |
| 68 | entry_count = 0; |
71 | entry_count = 0; |
| 69 | file_index = 0; |
72 | file_index = 0; |
| 70 | while ((buffer = sgets (line_buffer, sizeof (line_buffer), buffer)) != NULL) |
73 | while ((buffer = sgets (line_buffer, sizeof (line_buffer), buffer)) != NULL) |
| 71 | { |
74 | { |
| 72 | // is it a new game ? |
75 | // is it a new game ? |
| Line 108... | Line 111... | ||
| 108 | // else is it the beginning of a game ? |
111 | // else is it the beginning of a game ? |
| 109 | else if (strncmp (line_buffer, "1.", 2) == 0) |
112 | else if (strncmp (line_buffer, "1.", 2) == 0) |
| 110 | games[entry_count - 1].gamedata_start = file_index; // remember where this game starts |
113 | games[entry_count - 1].gamedata_start = file_index; // remember where this game starts |
| 111 | } |
114 | } |
| 112 | 115 | ||
| 113 | file_index = buffer - |
116 | file_index = buffer - pgnfile_data; // save current file pointer index |
| 114 | } |
117 | } |
| 115 | - | ||
| 116 | // we no longer need the file data space, so free it |
- | |
| 117 | SAFE_free ((void **) &file_data); |
- | |
| 118 | 118 | ||
| 119 | return (true); // finished, return TRUE |
119 | return (true); // finished, return TRUE |
| 120 | } |
120 | } |
| 121 | 121 | ||
| 122 | 122 | ||
| Line 125... | Line 125... | ||
| 125 | // this function loads and parses a game data in a PGN file. If the selected game is NULL, it means that |
125 | // this function loads and parses a game data in a PGN file. If the selected game is NULL, it means that |
| 126 | // the user didn't want to chose any game at all, so just free the games list and return a success value. |
126 | // the user didn't want to chose any game at all, so just free the games list and return a success value. |
| 127 | 127 | ||
| 128 | static wchar_t pgn_comment[65536]; // declared static so as not to reallocate it |
128 | static wchar_t pgn_comment[65536]; // declared static so as not to reallocate it |
| 129 | 129 | ||
| 130 | char *file_data; // mallocated |
- | |
| 131 | boardmove_t new_move; |
130 | boardmove_t new_move; |
| 132 | int file_size; |
- | |
| 133 | int length; |
131 | int length; |
| 134 | int char_index; |
132 | int char_index; |
| 135 | int fieldstart; |
133 | int fieldstart; |
| 136 | int fieldstop; |
134 | int fieldstop; |
| 137 | int variation_depth; |
135 | int variation_depth; |
| 138 | char movenumber_string[8]; |
136 | char movenumber_string[8]; |
| 139 | FILE *fp; |
- | |
| 140 | 137 | ||
| 141 | // did we chose NO game ? |
138 | // did we chose NO game ? |
| 142 | if (game == NULL) |
139 | if (game == NULL) |
| 143 | { |
140 | { |
| 144 | PGNFile_GameList_Shutdown (); // free the games list |
141 | PGNFile_GameList_Shutdown (); // free the games list |
| - | 142 | SAFE_free ((void **) &pgnfile_data); // free the file data space |
|
| 145 | return (true); // return success as there's nothing to load |
143 | return (true); // return success as there's nothing to load |
| 146 | } |
144 | } |
| 147 | - | ||
| 148 | // try to open file for reading in BINARY mode so as NOT to convert end of lines |
- | |
| 149 | _wfopen_s (&fp, pgnfile_pathname, L"rb"); |
- | |
| 150 | if (fp == NULL) |
- | |
| 151 | { |
- | |
| 152 | PGNFile_GameList_Shutdown (); // free the games list |
- | |
| 153 | return (false); // on error, cancel |
- | |
| 154 | } |
- | |
| 155 | - | ||
| 156 | // get file length |
- | |
| 157 | fseek (fp, 0, SEEK_END); |
- | |
| 158 | file_size = ftell (fp); |
- | |
| 159 | fseek (fp, 0, SEEK_SET); |
- | |
| 160 | - | ||
| 161 | // mallocate space for it and read it all at once |
- | |
| 162 | file_data = (char *) SAFE_malloc (file_size, sizeof (char), false); |
- | |
| 163 | fread (file_data, file_size, 1, fp); |
- | |
| 164 | fclose (fp); // we no longer need the file, so close it |
- | |
| 165 | - | ||
| 166 | // now the file is fully loaded in memory |
- | |
| 167 | 145 | ||
| 168 | // reset the board (but NOT the players, just their view angles) |
146 | // reset the board (but NOT the players, just their view angles) |
| 169 | Board_Reset (board, game->fen_str); |
147 | Board_Reset (board, game->fen_str); |
| 170 | animation_endtime = current_time + 2.0f; // HACK: this sorta prevents the "load file" dialog box trailing clicks to be misinterpreted |
148 | animation_endtime = current_time + 2.0f; // HACK: this sorta prevents the "load file" dialog box trailing clicks to be misinterpreted |
| 171 | 149 | ||
| Line 181... | Line 159... | ||
| 181 | { |
159 | { |
| 182 | // build the move number string |
160 | // build the move number string |
| 183 | sprintf_s (movenumber_string, sizeof (movenumber_string), "%d.", 1 + board->move_count / 2); |
161 | sprintf_s (movenumber_string, sizeof (movenumber_string), "%d.", 1 + board->move_count / 2); |
| 184 | 162 | ||
| 185 | // is it a space ? |
163 | // is it a space ? |
| 186 | if (isspace ( |
164 | if (isspace (pgnfile_data[char_index])) |
| 187 | { |
165 | { |
| 188 | char_index++; // if so, skip it |
166 | char_index++; // if so, skip it |
| 189 | continue; // and proceed to the next data |
167 | continue; // and proceed to the next data |
| 190 | } |
168 | } |
| 191 | 169 | ||
| 192 | // else is what we're reading a move number ? |
170 | // else is what we're reading a move number ? |
| 193 | else if (strncmp (& |
171 | else if (strncmp (&pgnfile_data[char_index], movenumber_string, strlen (movenumber_string)) == 0) |
| 194 | { |
172 | { |
| 195 | char_index += strlen (movenumber_string); // if so, skip it |
173 | char_index += strlen (movenumber_string); // if so, skip it |
| 196 | continue; // and proceed to the next data |
174 | continue; // and proceed to the next data |
| 197 | } |
175 | } |
| 198 | 176 | ||
| 199 | // else is it a dot ? |
177 | // else is it a dot ? |
| 200 | else if ( |
178 | else if (pgnfile_data[char_index] == '.') |
| 201 | { |
179 | { |
| 202 | char_index++; // if so, skip it |
180 | char_index++; // if so, skip it |
| 203 | continue; // and proceed to the next data |
181 | continue; // and proceed to the next data |
| 204 | } |
182 | } |
| 205 | 183 | ||
| 206 | // else is it an en passant notification ? |
184 | // else is it an en passant notification ? |
| 207 | else if (strncmp (& |
185 | else if (strncmp (&pgnfile_data[char_index], "e.p.", 4) == 0) |
| 208 | { |
186 | { |
| 209 | char_index += 4; // this notification is superfluous, skip it |
187 | char_index += 4; // this notification is superfluous, skip it |
| 210 | continue; // and proceed to the next data |
188 | continue; // and proceed to the next data |
| 211 | } |
189 | } |
| 212 | 190 | ||
| 213 | // else is it a comment ? |
191 | // else is it a comment ? |
| 214 | else if ( |
192 | else if (pgnfile_data[char_index] == '{') |
| 215 | { |
193 | { |
| 216 | fieldstart = char_index + 1; // skip the leading brace |
194 | fieldstart = char_index + 1; // skip the leading brace |
| 217 | 195 | ||
| 218 | while ((fieldstart < |
196 | while ((fieldstart < (int) pgnfile_size) && isspace (pgnfile_data[fieldstart])) |
| 219 | fieldstart++; // skip any leading spaces |
197 | fieldstart++; // skip any leading spaces |
| 220 | 198 | ||
| 221 | // move through all the other characters... |
199 | // move through all the other characters... |
| 222 | for (fieldstop = fieldstart; fieldstop < |
200 | for (fieldstop = fieldstart; fieldstop < (int) pgnfile_size; fieldstop++) |
| 223 | if ( |
201 | if (pgnfile_data[fieldstop] == '}') |
| 224 | break; // and stop at the first closing brace we find |
202 | break; // and stop at the first closing brace we find |
| 225 | 203 | ||
| 226 | char_index = fieldstop + 1; // remember where to continue reading (that is, after the closing brace) |
204 | char_index = fieldstop + 1; // remember where to continue reading (that is, after the closing brace) |
| 227 | 205 | ||
| 228 | while ((fieldstop > 0) && isspace ( |
206 | while ((fieldstop > 0) && isspace (pgnfile_data[fieldstop])) |
| 229 | fieldstop--; // chop off any trailing spaces |
207 | fieldstop--; // chop off any trailing spaces |
| 230 | 208 | ||
| 231 |
|
209 | pgnfile_data[fieldstop] = 0; // break the string at this location |
| 232 | 210 | ||
| 233 | // now copy out the commentary by appending it to the one we know already |
211 | // now copy out the commentary by appending it to the one we know already |
| 234 | if (pgn_comment[0] != 0) |
212 | if (pgn_comment[0] != 0) |
| 235 | wcscat_s (pgn_comment, WCHAR_SIZEOF (pgn_comment), L" "); |
213 | wcscat_s (pgn_comment, WCHAR_SIZEOF (pgn_comment), L" "); |
| 236 | length = wcslen (pgn_comment); |
214 | length = wcslen (pgn_comment); |
| 237 | ConvertToWideChar (&pgn_comment[length], WCHAR_SIZEOF (pgn_comment) - length, & |
215 | ConvertToWideChar (&pgn_comment[length], WCHAR_SIZEOF (pgn_comment) - length, &pgnfile_data[fieldstart]); |
| 238 | ConvertCRLFsToSingleSpaces (pgn_comment); // linearize string |
216 | ConvertCRLFsToSingleSpaces (pgn_comment); // linearize string |
| 239 | 217 | ||
| 240 | continue; // and proceed to the next data |
218 | continue; // and proceed to the next data |
| 241 | } |
219 | } |
| 242 | 220 | ||
| 243 | // else is it a numeric annotation glyph ? if so, just ignore it (FIXME: better support this) |
221 | // else is it a numeric annotation glyph ? if so, just ignore it (FIXME: better support this) |
| 244 | else if ( |
222 | else if (pgnfile_data[char_index] == '$') |
| 245 | { |
223 | { |
| 246 | while ((char_index < |
224 | while ((char_index < (int) pgnfile_size) && !isspace (pgnfile_data[char_index])) |
| 247 | char_index++; // figure out where it stops |
225 | char_index++; // figure out where it stops |
| 248 | while ((char_index < |
226 | while ((char_index < (int) pgnfile_size) && isspace (pgnfile_data[char_index])) |
| 249 | char_index++; // figure out where the next word starts |
227 | char_index++; // figure out where the next word starts |
| 250 | 228 | ||
| 251 | continue; // and proceed to the next data |
229 | continue; // and proceed to the next data |
| 252 | } |
230 | } |
| 253 | 231 | ||
| 254 | // else is it a variation ? if so, just ignore it (FIXME: better support this) |
232 | // else is it a variation ? if so, just ignore it (FIXME: better support this) |
| 255 | else if ( |
233 | else if (pgnfile_data[char_index] == '(') |
| 256 | { |
234 | { |
| 257 | variation_depth = 1; |
235 | variation_depth = 1; |
| 258 | while ((char_index < |
236 | while ((char_index < (int) pgnfile_size) && (variation_depth != 0)) |
| 259 | { |
237 | { |
| 260 | char_index++; // move through file data and cope with nested variations |
238 | char_index++; // move through file data and cope with nested variations |
| 261 | if ( |
239 | if (pgnfile_data[char_index] == '(') variation_depth++; |
| 262 | else if ( |
240 | else if (pgnfile_data[char_index] == ')') variation_depth--; |
| 263 | } |
241 | } |
| 264 | char_index++; // skip the closing parenthese |
242 | char_index++; // skip the closing parenthese |
| 265 | while ((char_index < |
243 | while ((char_index < (int) pgnfile_size) && isspace (pgnfile_data[char_index])) |
| 266 | char_index++; // figure out where the next word starts |
244 | char_index++; // figure out where the next word starts |
| 267 | 245 | ||
| 268 | continue; // and proceed to the next data |
246 | continue; // and proceed to the next data |
| 269 | } |
247 | } |
| 270 | 248 | ||
| 271 | // else is it a game result ? |
249 | // else is it a game result ? |
| 272 | else if ((strncmp (& |
250 | else if ((strncmp (&pgnfile_data[char_index], "1/2-1/2", 7) == 0) |
| 273 | || (strncmp (& |
251 | || (strncmp (&pgnfile_data[char_index], "1-0", 3) == 0) |
| 274 | || (strncmp (& |
252 | || (strncmp (&pgnfile_data[char_index], "0-1", 3) == 0) |
| 275 | || ( |
253 | || (pgnfile_data[char_index] == '*')) |
| 276 | { |
254 | { |
| 277 | // if there's a move pending, validate it |
255 | // if there's a move pending, validate it |
| 278 | if ((new_move.source[0] != -1) && (new_move.source[1] != -1) && (new_move.target[0] != -1) && (new_move.target[1] != -1)) |
256 | if ((new_move.source[0] != -1) && (new_move.source[1] != -1) && (new_move.target[0] != -1) && (new_move.target[1] != -1)) |
| 279 | { |
257 | { |
| 280 | Board_AppendMove (board, new_move.source[0], new_move.source[1], new_move.target[0], new_move.target[1], new_move.promotion_type, pgn_comment); // save move |
258 | Board_AppendMove (board, new_move.source[0], new_move.source[1], new_move.target[0], new_move.target[1], new_move.promotion_type, pgn_comment); // save move |
| Line 307... | Line 285... | ||
| 307 | new_move.promotion_type = 0; |
285 | new_move.promotion_type = 0; |
| 308 | pgn_comment[0] = 0; // reset comment |
286 | pgn_comment[0] = 0; // reset comment |
| 309 | } |
287 | } |
| 310 | 288 | ||
| 311 | // convert the move string data to wide char |
289 | // convert the move string data to wide char |
| 312 | ConvertToWideChar (new_move.pgntext, WCHAR_SIZEOF (new_move.pgntext), & |
290 | ConvertToWideChar (new_move.pgntext, WCHAR_SIZEOF (new_move.pgntext), &pgnfile_data[char_index]); |
| 313 | 291 | ||
| 314 | // evaluate the string in Standard Algebraic Notation and find the source, destination, part type and promotion |
292 | // evaluate the string in Standard Algebraic Notation and find the source, destination, part type and promotion |
| 315 | if (!Move_SetupFromSAN (&board->moves[board->move_count - 1], &new_move, Board_ColorToMove (board))) |
293 | if (!Move_SetupFromSAN (&board->moves[board->move_count - 1], &new_move, Board_ColorToMove (board))) |
| 316 | { |
294 | { |
| 317 | PGNFile_GameList_Shutdown (); // free the games list |
295 | PGNFile_GameList_Shutdown (); // free the games list |
| 318 | SAFE_free ((void **) & |
296 | SAFE_free ((void **) &pgnfile_data); // free the file data space |
| 319 | return (false); // on error, cancel |
297 | return (false); // on error, cancel |
| 320 | } |
298 | } |
| 321 | 299 | ||
| 322 | // find where it stops |
300 | // find where it stops |
| 323 | while ((char_index < |
301 | while ((char_index < (int) pgnfile_size) && !isspace (pgnfile_data[char_index])) |
| 324 | char_index++; // reach the next space |
302 | char_index++; // reach the next space |
| 325 | 303 | ||
| 326 | char_index++; // remember where to continue reading (that is, after the next space) |
304 | char_index++; // remember where to continue reading (that is, after the next space) |
| 327 | continue; // and proceed to the next data |
305 | continue; // and proceed to the next data |
| 328 | } |
306 | } |
| Line 334... | Line 312... | ||
| 334 | 312 | ||
| 335 | // we loaded the game we want, we no longer need the games array |
313 | // we loaded the game we want, we no longer need the games array |
| 336 | PGNFile_GameList_Shutdown (); |
314 | PGNFile_GameList_Shutdown (); |
| 337 | 315 | ||
| 338 | // we no longer need the file data space, so free it |
316 | // we no longer need the file data space, so free it |
| 339 | SAFE_free ((void **) & |
317 | SAFE_free ((void **) &pgnfile_data); |
| 340 | 318 | ||
| 341 | return (true); // game loaded successfully, return TRUE |
319 | return (true); // game loaded successfully, return TRUE |
| 342 | } |
320 | } |
| 343 | 321 | ||
| 344 | 322 | ||