changes from H.G. Muller; version 4.3.13
[xboard.git] / gamelist.c
1 /*\r
2  * gamelist.c -- Functions to manage a gamelist\r
3  * XBoard $Id: gamelist.c,v 2.1 2003/10/27 19:21:00 mann Exp $\r
4  *\r
5  * Copyright 1995 Free Software Foundation, Inc.\r
6  *\r
7  * ------------------------------------------------------------------------\r
8  * This program is free software; you can redistribute it and/or modify\r
9  * it under the terms of the GNU General Public License as published by\r
10  * the Free Software Foundation; either version 2 of the License, or\r
11  * (at your option) any later version.\r
12  *\r
13  * This program is distributed in the hope that it will be useful,\r
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
16  * GNU General Public License for more details.\r
17  *\r
18  * You should have received a copy of the GNU General Public License\r
19  * along with this program; if not, write to the Free Software\r
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.\r
21  * ------------------------------------------------------------------------\r
22  */\r
23 \r
24 #include "config.h"\r
25 \r
26 #include <stdio.h>\r
27 #include <errno.h>\r
28 #if STDC_HEADERS\r
29 # include <stdlib.h>\r
30 # include <string.h>\r
31 #else /* not STDC_HEADERS */\r
32 # if HAVE_STRING_H\r
33 #  include <string.h>\r
34 # else /* not HAVE_STRING_H */\r
35 #  include <strings.h>\r
36 # endif /* not HAVE_STRING_H */\r
37 #endif /* not STDC_HEADERS */\r
38 \r
39 #include "common.h"\r
40 #include "frontend.h"\r
41 #include "backend.h"\r
42 #include "parser.h"\r
43 \r
44 \r
45 /* Variables\r
46  */\r
47 List gameList;\r
48 \r
49 \r
50 /* Local function prototypes\r
51  */\r
52 static void GameListDeleteGame P((ListGame *));\r
53 static ListGame *GameListCreate P((void));\r
54 static void GameListFree P((List *));\r
55 static int GameListNewGame P((ListGame **));\r
56 \r
57 /* Delete a ListGame; implies removint it from a list.\r
58  */\r
59 static void GameListDeleteGame(listGame)\r
60     ListGame *listGame;\r
61 {\r
62     if (listGame) {\r
63         if (listGame->gameInfo.event) free(listGame->gameInfo.event);\r
64         if (listGame->gameInfo.site) free(listGame->gameInfo.site);\r
65         if (listGame->gameInfo.date) free(listGame->gameInfo.date);\r
66         if (listGame->gameInfo.round) free(listGame->gameInfo.round);\r
67         if (listGame->gameInfo.white) free(listGame->gameInfo.white);\r
68         if (listGame->gameInfo.black) free(listGame->gameInfo.black);\r
69         if (listGame->gameInfo.fen) free(listGame->gameInfo.fen);\r
70         if (listGame->gameInfo.resultDetails) free(listGame->gameInfo.resultDetails);\r
71         if (listGame->gameInfo.timeControl) free(listGame->gameInfo.timeControl);\r
72         if (listGame->gameInfo.extraTags) free(listGame->gameInfo.extraTags);\r
73         if (listGame->gameInfo.outOfBook) free(listGame->gameInfo.outOfBook);\r
74         ListNodeFree((ListNode *) listGame);\r
75     }\r
76 }\r
77 \r
78 \r
79 /* Free the previous list of games.\r
80  */\r
81 static void GameListFree(gameList)\r
82     List *gameList;\r
83 {\r
84     while (!ListEmpty(gameList))\r
85     {\r
86         GameListDeleteGame((ListGame *) gameList->head);\r
87     }\r
88 }\r
89 \r
90 \r
91 \r
92 /* Initialize a new GameInfo structure.\r
93  */\r
94 void GameListInitGameInfo(gameInfo)\r
95     GameInfo *gameInfo;\r
96 {\r
97     gameInfo->event = NULL;\r
98     gameInfo->site = NULL;\r
99     gameInfo->date = NULL;\r
100     gameInfo->round = NULL;\r
101     gameInfo->white = NULL;\r
102     gameInfo->black = NULL;\r
103     gameInfo->result = GameUnfinished;\r
104     gameInfo->fen = NULL;\r
105     gameInfo->resultDetails = NULL;\r
106     gameInfo->timeControl = NULL;\r
107     gameInfo->extraTags = NULL;\r
108     gameInfo->whiteRating = -1; /* unknown */\r
109     gameInfo->blackRating = -1; /* unknown */\r
110     gameInfo->variant = VariantNormal;\r
111     gameInfo->outOfBook = NULL;\r
112 }\r
113 \r
114 \r
115 /* Create empty ListGame; returns ListGame or NULL, if out of memory.\r
116  *\r
117  * Note, that the ListGame is *not* added to any list\r
118  */\r
119 static ListGame *GameListCreate()\r
120 \r
121 {\r
122     ListGame *listGame;\r
123 \r
124     if ((listGame = (ListGame *) ListNodeCreate(sizeof(*listGame)))) {\r
125         GameListInitGameInfo(&listGame->gameInfo);\r
126     }\r
127     return(listGame);\r
128 }\r
129 \r
130 \r
131 /* Creates a new game for the gamelist.\r
132  */\r
133 static int GameListNewGame(listGamePtr)\r
134      ListGame **listGamePtr;\r
135 {\r
136     if (!(*listGamePtr = (ListGame *) GameListCreate())) {\r
137         GameListFree(&gameList);\r
138         return(ENOMEM);\r
139     }\r
140     ListAddTail(&gameList, (ListNode *) *listGamePtr);\r
141     return(0);\r
142 }\r
143 \r
144 \r
145 /* Build the list of games in the open file f.\r
146  * Returns 0 for success or error number.\r
147  */\r
148 int GameListBuild(f)\r
149     FILE *f;\r
150 {\r
151     ChessMove cm, lastStart;\r
152     int gameNumber;\r
153     ListGame *currentListGame = NULL;\r
154     int error;\r
155     int offset;\r
156 \r
157     GameListFree(&gameList);\r
158     yynewfile(f);\r
159     gameNumber = 0;\r
160 \r
161     lastStart = (ChessMove) 0;\r
162     yyskipmoves = FALSE;\r
163     do {\r
164         yyboardindex = 0;\r
165         offset = yyoffset();\r
166         cm = (ChessMove) yylex();\r
167         switch (cm) {\r
168           case GNUChessGame:\r
169             if ((error = GameListNewGame(&currentListGame))) {\r
170                 rewind(f);\r
171                 yyskipmoves = FALSE;\r
172                 return(error);\r
173             }\r
174             currentListGame->number = ++gameNumber;\r
175             currentListGame->offset = offset;\r
176             if (currentListGame->gameInfo.event != NULL) {\r
177                 free(currentListGame->gameInfo.event);\r
178             }\r
179             currentListGame->gameInfo.event = StrSave(yy_text);\r
180             lastStart = cm;\r
181             break;\r
182           case XBoardGame:\r
183             lastStart = cm;\r
184             break;\r
185           case MoveNumberOne:\r
186             switch (lastStart) {\r
187               case GNUChessGame:\r
188                 break;          /*  ignore  */\r
189               case PGNTag:\r
190                 lastStart = cm;\r
191                 break;          /*  Already started */\r
192               case (ChessMove) 0:\r
193               case MoveNumberOne:\r
194               case XBoardGame:\r
195                 if ((error = GameListNewGame(&currentListGame))) {\r
196                     rewind(f);\r
197                     yyskipmoves = FALSE;\r
198                     return(error);\r
199                 }\r
200                 currentListGame->number = ++gameNumber;\r
201                 currentListGame->offset = offset;\r
202                 lastStart = cm;\r
203                 break;\r
204               default:\r
205                 break;          /*  impossible  */\r
206             }\r
207             break;\r
208           case PGNTag:\r
209             lastStart = cm;\r
210             if ((error = GameListNewGame(&currentListGame))) {\r
211                 rewind(f);\r
212                 yyskipmoves = FALSE;\r
213                 return(error);\r
214             }\r
215             currentListGame->number = ++gameNumber;\r
216             currentListGame->offset = offset;\r
217             ParsePGNTag(yy_text, &currentListGame->gameInfo);\r
218             do {\r
219                 yyboardindex = 1;\r
220                 offset = yyoffset();\r
221                 cm = (ChessMove) yylex();\r
222                 if (cm == PGNTag) {\r
223                     ParsePGNTag(yy_text, &currentListGame->gameInfo);\r
224                 }\r
225             } while (cm == PGNTag || cm == Comment);\r
226             break;\r
227           case NormalMove:\r
228             /* Allow the first game to start with an unnumbered move */\r
229             yyskipmoves = TRUE;\r
230             if (lastStart == (ChessMove) 0) {\r
231               if ((error = GameListNewGame(&currentListGame))) {\r
232                 rewind(f);\r
233                 yyskipmoves = FALSE;\r
234                 return(error);\r
235               }\r
236               currentListGame->number = ++gameNumber;\r
237               currentListGame->offset = offset;\r
238               lastStart = MoveNumberOne;\r
239             }\r
240             break;\r
241           default:\r
242             break;\r
243         }\r
244     }\r
245     while (cm != (ChessMove) 0);\r
246 \r
247 \r
248     if (appData.debugMode) {\r
249         for (currentListGame = (ListGame *) gameList.head;\r
250              currentListGame->node.succ;\r
251              currentListGame = (ListGame *) currentListGame->node.succ) {\r
252 \r
253             fprintf(debugFP, "Parsed game number %d, offset %ld:\n",\r
254                     currentListGame->number, currentListGame->offset);\r
255             PrintPGNTags(debugFP, &currentListGame->gameInfo);\r
256         }\r
257     }\r
258 \r
259     rewind(f);\r
260     yyskipmoves = FALSE;\r
261     return 0;\r
262 }\r
263 \r
264 \r
265 /* Clear an existing GameInfo structure.\r
266  */\r
267 void ClearGameInfo(gameInfo)\r
268     GameInfo *gameInfo;\r
269 {\r
270     if (gameInfo->event != NULL) {\r
271         free(gameInfo->event);\r
272     }\r
273     if (gameInfo->site != NULL) {\r
274         free(gameInfo->site);\r
275     }\r
276     if (gameInfo->date != NULL) {\r
277         free(gameInfo->date);\r
278     }\r
279     if (gameInfo->round != NULL) {\r
280         free(gameInfo->round);\r
281     }\r
282     if (gameInfo->white != NULL) {\r
283         free(gameInfo->white);\r
284     }\r
285     if (gameInfo->black != NULL) {\r
286         free(gameInfo->black);\r
287     }\r
288     if (gameInfo->resultDetails != NULL) {\r
289         free(gameInfo->resultDetails);\r
290     }\r
291     if (gameInfo->fen != NULL) {\r
292         free(gameInfo->fen);\r
293     }\r
294     if (gameInfo->timeControl != NULL) {\r
295         free(gameInfo->timeControl);\r
296     }\r
297     if (gameInfo->extraTags != NULL) {\r
298         free(gameInfo->extraTags);\r
299     }\r
300     if (gameInfo->outOfBook != NULL) {\r
301         free(gameInfo->outOfBook);\r
302     }\r
303 \r
304     GameListInitGameInfo(gameInfo);\r
305 }\r
306 \r
307 /* [AS] Replaced by "dynamic" tag selection below */\r
308 char *\r
309 GameListLineOld(number, gameInfo)\r
310      int number;\r
311      GameInfo *gameInfo;\r
312 {\r
313     char *event = (gameInfo->event && strcmp(gameInfo->event, "?") != 0) ?\r
314                      gameInfo->event : gameInfo->site ? gameInfo->site : "?";\r
315     char *white = gameInfo->white ? gameInfo->white : "?";\r
316     char *black = gameInfo->black ? gameInfo->black : "?";\r
317     char *date = gameInfo->date ? gameInfo->date : "?";\r
318     int len = 10 + strlen(event) + 2 + strlen(white) + 1 + \r
319       strlen(black) + 11 + strlen(date) + 1;\r
320     char *ret = (char *) malloc(len);\r
321     sprintf(ret, "%d. %s, %s-%s, %s, %s",\r
322             number, event, white, black, PGNResult(gameInfo->result), date);\r
323     return ret;\r
324 }\r
325 \r
326 #define MAX_FIELD_LEN   64  /* To avoid overflowing the buffer */\r
327 \r
328 char * GameListLine( int number, GameInfo * gameInfo )\r
329 {\r
330     char buffer[1024];\r
331     char * buf = buffer;\r
332     char * glt = appData.gameListTags;\r
333     \r
334     buf += sprintf( buffer, "%d.", number );\r
335 \r
336     while( *glt != '\0' ) {\r
337         *buf++ = ' ';\r
338 \r
339         switch( *glt ) {\r
340         case GLT_EVENT:\r
341             strncpy( buf, gameInfo->event ? gameInfo->event : "?", MAX_FIELD_LEN );\r
342             break;\r
343         case GLT_SITE:\r
344             strncpy( buf, gameInfo->site ? gameInfo->site : "?", MAX_FIELD_LEN );\r
345             break;\r
346         case GLT_DATE:\r
347             strncpy( buf, gameInfo->date ? gameInfo->date : "?", MAX_FIELD_LEN );\r
348             break;\r
349         case GLT_ROUND:\r
350             strncpy( buf, gameInfo->round ? gameInfo->round : "?", MAX_FIELD_LEN );\r
351             break;\r
352         case GLT_PLAYERS:\r
353             strncpy( buf, gameInfo->white ? gameInfo->white : "?", MAX_FIELD_LEN );\r
354             buf[ MAX_FIELD_LEN-1 ] = '\0';\r
355             buf += strlen( buf );\r
356             *buf++ = '-';\r
357             strncpy( buf, gameInfo->black ? gameInfo->black : "?", MAX_FIELD_LEN );\r
358             break;\r
359         case GLT_RESULT:\r
360             strcpy( buf, PGNResult(gameInfo->result) );\r
361             break;\r
362         case GLT_WHITE_ELO:\r
363             if( gameInfo->whiteRating > 0 )\r
364                 sprintf( buf, "%d", gameInfo->whiteRating );\r
365             else\r
366                 strcpy( buf, "?" );\r
367             break;\r
368         case GLT_BLACK_ELO:\r
369             if( gameInfo->blackRating > 0 )\r
370                 sprintf( buf, "%d", gameInfo->blackRating );\r
371             else\r
372                 strcpy( buf, "?" );\r
373             break;\r
374         case GLT_TIME_CONTROL:\r
375             strncpy( buf, gameInfo->timeControl ? gameInfo->timeControl : "?", MAX_FIELD_LEN );\r
376             break;\r
377         case GLT_VARIANT:\r
378             break;\r
379         case GLT_OUT_OF_BOOK:\r
380             strncpy( buf, gameInfo->outOfBook ? gameInfo->outOfBook : "?", MAX_FIELD_LEN );\r
381             break;\r
382         default:\r
383             break;\r
384         }\r
385 \r
386         buf[MAX_FIELD_LEN-1] = '\0';\r
387 \r
388         buf += strlen( buf );\r
389 \r
390         glt++;\r
391 \r
392         if( *glt != '\0' ) {\r
393             *buf++ = ',';\r
394         }\r
395     }\r
396 \r
397     *buf = '\0';\r
398 \r
399     return strdup( buffer );\r
400 }\r
401 \r
402 char * GameListLineFull( int number, GameInfo * gameInfo )\r
403 {\r
404     char * event = gameInfo->event ? gameInfo->event : "?";\r
405     char * site = gameInfo->site ? gameInfo->site : "?";\r
406     char * white = gameInfo->white ? gameInfo->white : "?";\r
407     char * black = gameInfo->black ? gameInfo->black : "?";\r
408     char * round = gameInfo->round ? gameInfo->round : "?";\r
409     char * date = gameInfo->date ? gameInfo->date : "?";\r
410     char * oob = gameInfo->outOfBook ? gameInfo->outOfBook : "";\r
411     \r
412     int len = 64 + strlen(event) + strlen(site) + strlen(white) + strlen(black) + strlen(date) + strlen(oob);\r
413 \r
414     char *ret = (char *) malloc(len);\r
415 \r
416     sprintf(ret, "%d, \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\"", number, event, site, round, white, black, PGNResult(gameInfo->result), date, oob );\r
417 \r
418     return ret;\r
419 }\r