]> git.sesse.net Git - pgn-extract/blob - output.c
Add code to dump out the ECO names by hash.
[pgn-extract] / output.c
1 /*
2  *  Program: pgn-extract: a Portable Game Notation (PGN) extractor.
3  *  Copyright (C) 1994-2014 David Barnes
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 1, or (at your option)
7  *  any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  *  David Barnes may be contacted as D.J.Barnes@kent.ac.uk
19  *  http://www.cs.kent.ac.uk/people/staff/djb/
20  *
21  */
22
23 #include <stdio.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include "bool.h"
28 #include "defs.h"
29 #include "typedef.h"
30 #include "taglist.h"
31 #include "tokens.h"
32 #include "lex.h"
33 #include "grammar.h"
34 #include "apply.h"
35 #include "output.h"
36 #include "mymalloc.h"
37 #include "eco.h"
38
39
40             /* Functions for outputting games in the required format. */
41
42 /* Define the width in which to print a CM move and move number. */
43 #define MOVE_NUMBER_WIDTH 3
44 #define MOVE_WIDTH 15
45 #define CM_COMMENT_CHAR ';'
46 /* Define the width of the moves area before a comment. */
47 #define COMMENT_INDENT (MOVE_NUMBER_WIDTH+2+2*MOVE_WIDTH)
48
49 /* Define a macro to calculate an array's size. */
50 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(*arr))
51
52 /* How much text we have output on the current line. */
53 static size_t line_length = 0;
54 /* The buffer in which each output line of a game is built. */
55 static char *output_line = NULL;
56
57 static Boolean print_move(FILE *outputfile, unsigned move_number,
58                           unsigned print_move_number, Boolean white_to_move,
59                           const Move *move_details);
60 static void output_STR(FILE *outfp,char **Tags);
61 static void show_tags(FILE *outfp,char **Tags,int tags_length);
62 static char promoted_piece_letter(Piece piece);
63 static void print_algebraic_game(Game current_game,FILE *outputfile,
64                            unsigned move_number,Boolean white_to_move,
65                            Board *final_board);
66 static void output_sesse_bin_game(Game current_game,FILE *outputfile,
67                                   unsigned move_number,Boolean white_to_move);
68 static void print_epd_game(Game current_game,FILE *outputfile,
69                            unsigned move_number,Boolean white_to_move,
70                            Board *final_board);
71 static void print_epd_move_list(Game current_game,FILE *outputfile,
72                                 unsigned move_number, Boolean white_to_move,
73                                 Board *final_board);
74 static const char *build_FEN_comment(const Board *board);
75 static void add_total_plycount(const Game *game, Boolean count_variations);
76 static void add_hashcode_tag(const Game *game);
77 static unsigned count_single_move_ply(const Move *move_details, Boolean count_variations);
78 static unsigned count_move_list_ply(Move *move_list, Boolean count_variations);
79
80         /* List, the order in which the tags should be output.
81          * The first seven should be the Seven Tag Roster that should
82          * be present in every game.
83          * The order of the remainder is, I believe, a matter of taste.
84          * any PSEUDO_*_TAGs should not appear in this list.
85          */
86
87     /* Give the array an int type, because a negative value is
88      * used as a terminator.
89      */
90 static int DefaultTagOrder[] = {
91     EVENT_TAG, SITE_TAG, DATE_TAG, ROUND_TAG, WHITE_TAG, BLACK_TAG, RESULT_TAG,
92 #if 1
93     /* @@@ Consider omitting some of these from the default ordering,
94      * and allow the output order to be determined from the
95      * input order.
96      */
97     WHITE_TITLE_TAG, BLACK_TITLE_TAG, WHITE_ELO_TAG, BLACK_ELO_TAG,
98     WHITE_USCF_TAG, BLACK_USCF_TAG,
99     WHITE_TYPE_TAG, BLACK_TYPE_TAG,
100     WHITE_NA_TAG, BLACK_NA_TAG,
101     ECO_TAG, NIC_TAG, OPENING_TAG, VARIATION_TAG, SUB_VARIATION_TAG, 
102     LONG_ECO_TAG,
103     TIME_CONTROL_TAG,
104     ANNOTATOR_TAG,
105     EVENT_DATE_TAG, EVENT_SPONSOR_TAG, SECTION_TAG, STAGE_TAG, BOARD_TAG,
106     TIME_TAG, UTC_DATE_TAG, UTC_TIME_TAG,
107     SETUP_TAG,
108     FEN_TAG,
109     TERMINATION_TAG, MODE_TAG, PLY_COUNT_TAG,
110 #endif
111     /* The final value should be negative. */
112     -1
113 };
114
115         /* Provision for a user-defined tag ordering.
116          * See add_to_output_tag_order().
117          * Once allocated, the end of the list must be negative.
118          */
119 static int *TagOrder = NULL;
120 static int tag_order_space = 0;
121
122 void
123 set_output_line_length(unsigned length)
124 {
125     if(output_line != NULL) {
126         (void) free((void *) output_line);
127     }
128     output_line = (char *) MallocOrDie(length + 1);
129     GlobalState.max_line_length = length;
130 }
131
132     /* Which output format does the user require, based upon the
133      * given command line argument?
134      */
135 OutputFormat
136 which_output_format(const char *arg)
137 {   int i;
138     struct {
139         const char *arg;
140         OutputFormat format;
141     } formats[] = {
142         { "san", SAN },
143         { "SAN", SAN },
144         { "epd", EPD },
145         { "EPD", EPD },
146         { "lalg", LALG },
147         { "halg", HALG },
148         { "CM", CM },
149         { "LALG", LALG },
150         { "HALG", HALG },
151         { "ELALG", ELALG },
152         { "elalg", ELALG },
153         { "uci", UCI },
154         { "cm", CM },
155         { "sessebin", SESSE_BIN },
156         { "", SOURCE },
157         /* Add others before the terminating NULL. */
158         { (const char *) NULL, SAN }
159     };
160
161     for(i = 0; formats[i].arg != NULL; i++){
162       const char *format_prefix = formats[i].arg;
163       const size_t format_prefix_len = strlen(format_prefix);
164       if(strncmp(arg,format_prefix,format_prefix_len) == 0){
165             OutputFormat format = formats[i].format;
166             /* Sanity check. */
167             if(*format_prefix == '\0' && *arg != '\0') {
168                 fprintf(GlobalState.logfile,
169                         "Unknown output format %s.\n", arg);
170                 exit(1);
171             }
172             /* If the format is SAN, it is possible to supply
173              * a 6-piece suffix listing language-specific
174              * letters to use in the output.
175              */
176             if((format == SAN || format == ELALG) &&
177                    (strlen(arg) > format_prefix_len)){
178                 set_output_piece_characters(&arg[format_prefix_len]);
179             }
180             return format;
181       }
182   }
183   fprintf(GlobalState.logfile,"Unknown output format %s.\n",arg);
184   return SAN;
185 }
186
187         /* Which file suffix should be used for this output format. */
188 const char *
189 output_file_suffix(OutputFormat format)
190 {
191     /* Define a suffix for the output files. */
192     static const char PGN_suffix[] = ".pgn";
193     static const char EPD_suffix[] = ".epd";
194     static const char CM_suffix[] = ".cm";
195     static const char BIN_suffix[] = ".bin";
196
197     switch(format){
198         case SOURCE:
199         case SAN:
200         case LALG:
201         case HALG:
202         case ELALG:
203             return PGN_suffix;
204         case EPD:
205             return EPD_suffix;
206         case CM:
207             return CM_suffix;
208         case SESSE_BIN:
209             return BIN_suffix;
210         default:
211             return PGN_suffix;
212     }
213 }
214
215 static const char *
216 select_tag_string(TagName tag)
217 {   const char *tag_string;
218
219     if((tag == PSEUDO_PLAYER_TAG) || (tag == PSEUDO_ELO_TAG) || (tag == PSEUDO_FEN_PATTERN_TAG)){
220         tag_string = NULL;
221     }
222     else{
223         tag_string = tag_header_string(tag);
224     }
225     return tag_string;
226 }
227
228 static Boolean
229 is_STR(TagName tag)
230 {
231     switch(tag){
232         case EVENT_TAG:
233         case SITE_TAG:
234         case DATE_TAG:
235         case ROUND_TAG:
236         case WHITE_TAG:
237         case BLACK_TAG:
238         case RESULT_TAG:
239             return TRUE;
240         default:
241             return FALSE;
242     }
243 }
244
245         /* Output the tags held in the Tags structure.
246          * At least the full Seven Tag Roster is printed.
247          */
248 static void
249 output_tag(TagName tag, char **Tags, FILE *outfp)
250 {   const char *tag_string;
251
252     /* Must print STR elements and other non-NULL tags. */
253     if((is_STR(tag)) || (Tags[tag] != NULL)){
254         tag_string = select_tag_string(tag);
255
256         if(tag_string != NULL){
257             fprintf(outfp,"[%s \"",tag_string);
258             if(Tags[tag] != NULL){
259                 fwrite(Tags[tag],sizeof(char),strlen(Tags[tag]),outfp);
260             }
261             else{
262                 if(tag == DATE_TAG){
263                     fprintf(outfp,"????.??.??");
264                 }
265                 else{
266                     fprintf(outfp,"?");
267                 }
268             }
269             fprintf(outfp,"\"]\n");
270         }
271     }
272 }
273
274         /* Output the Seven Tag Roster. */
275 static void
276 output_STR(FILE *outfp,char **Tags)
277 {   unsigned tag_index;
278
279     /* Use the default ordering to ensure that STR is output
280      * in the way it should be.
281      */
282     for(tag_index = 0; tag_index < 7; tag_index++){
283         output_tag(DefaultTagOrder[tag_index],Tags,outfp);
284     }
285 }
286
287         /* Print out on outfp the current details.
288          * These can be used in the case of an error.
289          */
290 static void
291 show_tags(FILE *outfp,char **Tags,int tags_length)
292 {   int tag_index;
293     /* Take a copy of the Tags data, so that we can keep
294      * track of what has been printed. This will make
295      * it possible to print tags that were identified
296      * in the source but are not defined with _TAG values.
297      * See lex.c for how these extra tags are handled.
298      */
299     char **copy_of_tags =
300             (char **) MallocOrDie(tags_length*sizeof(*copy_of_tags));
301     int i;
302     for(i = 0; i < tags_length; i++){
303         copy_of_tags[i] = Tags[i];
304     }
305
306     /* Ensure that a tag ordering is available. */
307     if(TagOrder == NULL){
308         /* None set by the user - use the default. */
309         /* Handle the standard tags.
310          * The end of the list is marked with a negative value.
311          */
312         for(tag_index = 0; DefaultTagOrder[tag_index] >= 0; tag_index++){
313             TagName tag = DefaultTagOrder[tag_index];
314             output_tag(tag,copy_of_tags,outfp);
315             copy_of_tags[tag] = (char *)NULL;
316         }
317         /* Handle the extra tags. */
318         for(tag_index = 0; tag_index < tags_length; tag_index++){
319             if(copy_of_tags[tag_index] != NULL){
320                 output_tag(tag_index,copy_of_tags,outfp);
321             }
322         }
323     }
324     else{
325         for(tag_index = 0; TagOrder[tag_index] >= 0; tag_index++){
326             TagName tag = TagOrder[tag_index];
327             output_tag(tag,copy_of_tags,outfp);
328             copy_of_tags[tag] = (char *)NULL;
329         }
330     }
331     (void) free(copy_of_tags);
332     putc('\n',outfp);
333 }
334
335         /* Ensure that there is room for len more characters on the
336          * current line.
337          */
338 static void
339 check_line_length(FILE *fp,size_t len)
340 {
341     if((line_length + len) > GlobalState.max_line_length){
342         terminate_line(fp);
343     }
344 }
345
346         /* Print ch to fp and update how much of the line
347          * has been printed on.
348          */
349 static void
350 single_char(FILE *fp, char ch)
351 {
352     check_line_length(fp,1);
353     output_line[line_length] = ch;
354     line_length++;
355 }
356
357         /* Print a space, unless at the beginning of a line. */
358 static void
359 print_separator(FILE *fp)
360 {
361     /* Lines shouldn't have trailing spaces, so ensure that there
362      * will be room for at least one more character after the space.
363      */
364     check_line_length(fp,2);
365     if(line_length != 0){
366         output_line[line_length] = ' ';
367         line_length++;
368     }
369 }
370
371         /* Ensure that what comes next starts on a fresh line. */
372 void
373 terminate_line(FILE *fp)
374 {
375     /* Delete any trailing space(s). */
376     while(line_length >= 1 && output_line[line_length - 1] == ' ') {
377         line_length--;
378     }
379     if(line_length > 0) {
380         output_line[line_length] = '\0';
381         fprintf(fp, "%s\n", output_line);
382         line_length = 0;
383     }
384 }
385
386         /* Print str to fp and update how much of the line
387          * has been printed on.
388          */
389 void
390 print_str(FILE *fp, const char *str)
391 {   size_t len = strlen(str);
392
393     check_line_length(fp,len);
394     if(len > GlobalState.max_line_length) {
395         fprintf(GlobalState.logfile, "String length %lu is too long for the line length of %lu:\n",
396                 (unsigned long) len,
397                 (unsigned long) GlobalState.max_line_length);
398         fprintf(GlobalState.logfile, "%s\n", str);
399         report_details(GlobalState.logfile);
400         fprintf(fp, "%s\n", str);
401     }
402     else {
403         sprintf(&(output_line[line_length]),"%s",str);
404         line_length += len;
405     }
406 }
407
408 static void
409 print_comment_list(FILE *fp, CommentList *comment_list)
410 {   CommentList *next_comment;
411
412     for(next_comment = comment_list ; next_comment != NULL;
413                 next_comment = next_comment->next){
414         StringList *comment = next_comment->Comment;
415
416         if(comment != NULL){
417             /* We will use strtok to break up the comment string,
418              * with chunk to point to each bit in turn.
419              */
420             char *chunk;
421             
422             single_char(fp,'{');
423             for( ; comment != NULL; comment = comment->next){
424                 /* Make a copy because the string is altered. */
425                 char *str = copy_string(comment->str);
426                 chunk = strtok(str," ");
427                 while(chunk != NULL){
428                     print_separator(fp);
429                     print_str(fp,chunk);
430                     chunk = strtok((char *)NULL," ");
431                 }
432                 (void) free((void *) str);
433             }
434             print_separator(fp);
435             single_char(fp,'}');
436             if(next_comment->next != NULL){
437                 print_separator(fp);
438             }
439         }
440     }
441 }
442
443 static void
444 print_move_list(FILE *outputfile, unsigned move_number, Boolean white_to_move,
445                 const Move *move_details,const Board *final_board)
446 {   unsigned print_move_number = 1;
447     const Move *move = move_details;
448     Boolean keepPrinting;
449     int plies;
450
451     /* Work out the ply depth. */
452     plies = 2*(move_number) - 1;
453     if(!white_to_move) {
454         plies++;
455     }
456     if(GlobalState.output_ply_limit >= 0 &&
457             plies > GlobalState.output_ply_limit) {
458         keepPrinting = FALSE;
459     }
460     else {
461         keepPrinting = TRUE;
462     }
463
464     while(move != NULL && keepPrinting){
465         /* Reset print_move number if a variation was printed. */
466         print_move_number = print_move(outputfile,move_number,
467                                        print_move_number,
468                                        white_to_move,move);
469
470         /* See if there is a result attached.  This may be attached either
471          * to a move or a comment.
472          */
473         if(!GlobalState.check_only && (move != NULL) &&
474                                 (move->terminating_result != NULL)){
475             if(GlobalState.output_FEN_string && final_board != NULL){
476                 print_separator(outputfile);
477                 print_str(outputfile,build_FEN_comment(final_board));
478             }
479             if(GlobalState.keep_results) {
480                 print_separator(outputfile);
481                 print_str(outputfile,move->terminating_result);
482             }
483         }
484         if(move->move[0] != '\0'){
485             /* A genuine move was just printed, rather than a comment. */
486             if(white_to_move){
487               white_to_move = FALSE;
488             }
489             else{
490                move_number++;
491                white_to_move = TRUE;
492             }
493             plies++;
494             if(GlobalState.output_ply_limit >= 0 &&
495                     plies > GlobalState.output_ply_limit) {
496                 keepPrinting = FALSE;
497             }
498         }
499         move = move->next;
500         /* The following is slightly inaccurate.
501          * If the previous value of move was a comment and
502          * we aren't printing comments, then this results in two
503          * separators being printed after the move preceding the comment.
504          * Not sure how to cleanly fix it, because there might have
505          * been nags attached to the comment that were printed, for instance!
506          */
507         if(move != NULL && keepPrinting){
508             print_separator(outputfile);
509         }
510     }
511
512     if(move != NULL && !keepPrinting) {
513         /* We ended printing the game prematurely. */
514
515         /*
516          * Decide whether to print a result indicator.
517          */
518         if(GlobalState.keep_results) {
519             /* Find the final move to see if there was a result there. */
520             while(move->next != NULL) {
521                 move = move->next;
522             }
523             if(move->terminating_result != NULL) {
524                 print_separator(outputfile);
525                 print_str(outputfile, "*");
526             }
527         }
528     }
529 }
530
531         /* Output the current move along with associated information.
532          * Return TRUE if either a variation or comment was printed,
533          * FALSE otherwise.
534          * This is needed to determine whether a new move number
535          * is to be printed after a variation.
536          */
537 static Boolean
538 print_move(FILE *outputfile, unsigned move_number, unsigned print_move_number,
539                 Boolean white_to_move, const Move *move_details)
540 {   Boolean something_printed = FALSE;
541
542     if(move_details == NULL){
543         /* Shouldn't happen. */
544         fprintf(GlobalState.logfile,
545                 "Internal error: NULL move in print_move.\n");
546         report_details(GlobalState.logfile);
547     }
548     else{
549         if(GlobalState.check_only){
550             /* Nothing to be output. */
551         }
552         else{
553             StringList *nags = move_details->Nags;
554             Variation *variants = move_details->Variants;
555             const unsigned char *move_text = move_details->move;
556
557             if(*move_text != '\0'){
558                 if(GlobalState.keep_move_numbers &&
559                         (white_to_move || print_move_number)){
560                     static char small_number[] = "99999...";
561
562                     /* @@@ Should 1... be written as 1. ... ? */
563                     sprintf(small_number,"%u.%s",move_number, white_to_move?"":"..");
564                     print_str(outputfile,small_number);
565                     print_separator(outputfile);
566                 }
567                 switch(GlobalState.output_format){
568                     case SAN:
569                     case SOURCE:
570                         /* @@@ move_text should be handled as unsigned
571                          * char text, as the source may be 8-bit rather
572                          * than 7-bit.
573                          */
574                         if(GlobalState.keep_checks) {
575                             print_str(outputfile, (const char *) move_text);
576                         }
577                         else {
578                             /* Look for a check or mate symbol. */
579                             char *check = strchr((const char *) move_text, '+');
580                             if(check == NULL) {
581                                 check = strchr((const char *) move_text, '#');
582                             }
583                             if(check != NULL) {
584                                 /* We need to drop it from move_text. */
585                                 int len = check - ((char *) move_text);
586                                 char *just_move = (char *) MallocOrDie(len + 1);
587                                 strncpy(just_move, (const char *) move_text, len);
588                                 just_move[len] = '\0';
589                                 print_str(outputfile, just_move);
590                                 (void) free(just_move);
591                             }
592                             else {
593                                 print_str(outputfile, (const char *) move_text);
594                             }
595                         }
596                         break;
597                     case HALG:
598                         {  char algebraic[MAX_MOVE_LEN+1];
599
600                            *algebraic = '\0';
601                            switch(move_details->class){
602                                case PAWN_MOVE:
603                                case ENPASSANT_PAWN_MOVE:
604                                case KINGSIDE_CASTLE:
605                                case QUEENSIDE_CASTLE:
606                                case PIECE_MOVE:
607                                    sprintf(algebraic,"%c%c-%c%c",
608                                        move_details->from_col,
609                                        move_details->from_rank,
610                                        move_details->to_col,
611                                        move_details->to_rank);
612                                    break;
613                                case PAWN_MOVE_WITH_PROMOTION:
614                                    sprintf(algebraic,"%c%c-%c%c%c",
615                                        move_details->from_col,
616                                        move_details->from_rank,
617                                        move_details->to_col,
618                                        move_details->to_rank,
619                                        promoted_piece_letter(move_details->promoted_piece));
620                                    break;
621                                case NULL_MOVE:
622                                    strcpy(algebraic, NULL_MOVE_STRING);
623                                    break;
624                                case UNKNOWN_MOVE:
625                                    strcpy(algebraic,"???");
626                                    break;
627                            }
628                            if(GlobalState.keep_checks) {
629                                switch(move_details->check_status){
630                                    case NOCHECK:
631                                      break;
632                                    case CHECK:
633                                      strcat(algebraic,"+");
634                                      break;
635                                    case CHECKMATE:
636                                      strcat(algebraic,"#");
637                                      break;
638                                }
639                            }
640                            print_str(outputfile,algebraic);
641                         }
642                         break;
643                     case LALG:
644                     case ELALG:
645                         {  char algebraic[MAX_MOVE_LEN+1];
646                            size_t ind = 0;
647
648                            /* Prefix with a piece name if ELALG. */
649                            if(GlobalState.output_format == ELALG &&
650                                         move_details->class == PIECE_MOVE){
651                                strcpy(algebraic,
652                                       piece_str(move_details->piece_to_move));
653                                ind = strlen(algebraic);
654                            }
655                            /* Format the basics. */
656                            if(move_details->class != NULL_MOVE) {
657                                sprintf(&algebraic[ind],"%c%c%c%c",
658                                            move_details->from_col,
659                                            move_details->from_rank,
660                                            move_details->to_col,
661                                            move_details->to_rank);
662                                ind += 4;
663                            }
664                            else {
665                                strcpy(algebraic, NULL_MOVE_STRING);
666                                ind += strlen(NULL_MOVE_STRING);
667                            }
668                            switch(move_details->class){
669                                case PAWN_MOVE:
670                                case KINGSIDE_CASTLE:
671                                case QUEENSIDE_CASTLE:
672                                case PIECE_MOVE:
673                                case NULL_MOVE:
674                                    /* Nothing more to do at this stage. */
675                                    break;
676                                case ENPASSANT_PAWN_MOVE:
677                                    if(GlobalState.output_format == ELALG){
678                                        strcat(algebraic,"ep");
679                                        ind += 2;
680                                    }
681                                    break;
682                                case PAWN_MOVE_WITH_PROMOTION:
683                                    sprintf(&algebraic[ind],"%s",
684                                            piece_str(move_details->promoted_piece));
685                                    ind = strlen(algebraic);
686                                    break;
687                                case UNKNOWN_MOVE:
688                                    strcpy(algebraic,"???");
689                                    ind += 3;
690                                    break;
691                            }
692                            if(GlobalState.keep_checks) {
693                                switch(move_details->check_status){
694                                    case NOCHECK:
695                                      break;
696                                    case CHECK:
697                                        strcat(algebraic,"+");
698                                        ind++;
699                                        break;
700                                    case CHECKMATE:
701                                        strcat(algebraic,"#");
702                                        ind++;
703                                        break;
704                                }
705                            }
706                            print_str(outputfile,algebraic);
707                         }
708                         break;
709                     default:
710                         fprintf(GlobalState.logfile,
711                                 "Unknown output format %d in print_move()\n",
712                                 GlobalState.output_format);
713                                 exit(1);
714                         break;
715                 }
716             }
717             else{
718                 /* An empty move. */
719                 fprintf(GlobalState.logfile,
720                                 "Internal error: Empty move in print_move.\n");
721                 report_details(GlobalState.logfile);
722             }
723             /* Print further information, that may be attached to moves
724              * and comments.
725              */
726             if(GlobalState.keep_NAGs){
727                 while(nags != NULL){
728                     print_separator(outputfile);
729                     print_str(outputfile,nags->str);
730                     nags = nags->next;
731                 }
732                 /* We don't need to output move numbers after just
733                  * NAGs, so don't set something_printed.
734                  */
735             }
736             if(GlobalState.keep_comments){
737                 if(move_details->Comment != NULL){
738                     print_separator(outputfile);
739                     print_comment_list(outputfile,move_details->Comment);
740                     something_printed = TRUE;
741                 }
742             }
743             if((GlobalState.keep_variations) && (variants != NULL)){
744                 while(variants != NULL){
745                     print_separator(outputfile);
746                     single_char(outputfile,'(');
747                     if(GlobalState.keep_comments &&
748                             (variants->prefix_comment != NULL)){
749                         print_comment_list(outputfile,variants->prefix_comment);
750                         print_separator(outputfile);
751                     }
752                     /* Always start with a move number.
753                      * The final board position is not needed.
754                      */
755                     print_move_list(outputfile,move_number,
756                                     white_to_move,variants->moves,
757                                     (const Board *)NULL);
758                     single_char(outputfile,')');
759                     if(GlobalState.keep_comments &&
760                             (variants->suffix_comment != NULL)){
761                         print_separator(outputfile);
762                         print_comment_list(outputfile,variants->suffix_comment);
763                     }
764                     variants = variants->next;
765                 }
766                 something_printed = TRUE;
767             }
768         }
769     }
770     return something_printed;
771 }
772
773         /* Return the letter associated with the given piece. */
774 static char
775 promoted_piece_letter(Piece piece)
776 {
777     switch(piece){
778         case QUEEN:
779             return 'Q';
780         case ROOK:
781             return 'R';
782         case BISHOP:
783             return 'B';
784         case KNIGHT:
785             return 'N';
786         default:
787             return '?';
788     }
789 }
790
791         /* Output a comment in CM format. */
792 static void
793 output_cm_comment(CommentList *comment,FILE *outputfile,unsigned indent)
794 {   /* Don't indent for the first comment line, because
795      * we should already be positioned at the correct spot.
796      */
797     unsigned indent_for_this_line = 0;
798
799     putc(CM_COMMENT_CHAR,outputfile);
800     line_length++;
801     while(comment != NULL){
802         /* We will use strtok to break up the comment string,
803          * with chunk to point to each bit in turn.
804          */
805         char *chunk;
806         StringList *comment_str = comment->Comment;
807         
808         for( ; comment_str != NULL; comment_str = comment_str->next){
809             char *str = copy_string(comment_str->str);
810             chunk = strtok(str," ");
811             while(chunk != NULL){
812                 size_t len = strlen(chunk);
813
814                 if((line_length+1+len) > GlobalState.max_line_length){
815                     /* Start a new line. */
816                     indent_for_this_line = indent;
817                     fprintf(outputfile,"\n%*s%c ",indent_for_this_line,"",
818                                         CM_COMMENT_CHAR);
819                     line_length = indent_for_this_line+2;
820                 }
821                 else{
822                     putc(' ',outputfile);
823                     line_length++;
824                 }
825                 fprintf(outputfile,"%s",chunk);
826                 line_length += len;
827                 chunk = strtok((char *)NULL," ");
828             }
829             (void) free((void *) str);
830         }
831         comment = comment->next;
832     }
833     putc('\n',outputfile);
834     line_length = 0;
835 }
836
837 static void
838 output_cm_result(const char *result,FILE *outputfile)
839 {
840     fprintf(outputfile,"%c ",CM_COMMENT_CHAR);
841     if(strcmp(result,"1-0") == 0){
842       fprintf(outputfile,"and black resigns");
843     }
844     else if(strcmp(result,"0-1") == 0){
845       fprintf(outputfile,"and white resigns");
846     }
847     else if(strncmp(result,"1/2",3) == 0){
848       fprintf(outputfile,"draw");
849     }
850     else{
851       fprintf(outputfile,"incomplete result");
852     }
853 }
854
855
856         /* Output the game in Chess Master format. */
857 static void
858 output_cm_game(FILE *outputfile,unsigned move_number,
859                 Boolean white_to_move,const Game game)
860 {   const Move *move = game.moves;
861
862     if((move_number != 1) || (!white_to_move)){
863         fprintf(GlobalState.logfile,
864             "Unable to output CM games other than from the starting position.\n");
865         report_details(GlobalState.logfile);
866     }
867     fprintf(outputfile,"WHITE: %s\n",
868                 game.tags[WHITE_TAG] != NULL? game.tags[WHITE_TAG] : "");
869     fprintf(outputfile,"BLACK: %s\n",
870                 game.tags[BLACK_TAG] != NULL? game.tags[BLACK_TAG] : "");
871     putc('\n',outputfile);
872
873     if(game.prefix_comment != NULL){
874         line_length = 0;
875         output_cm_comment(game.prefix_comment,outputfile,0);
876     }
877     while(move != NULL){
878         if(move->move[0] != '\0'){
879             /* A genuine move. */
880             if(white_to_move){
881                 fprintf(outputfile,"%*u. ",MOVE_NUMBER_WIDTH,move_number);
882                 fprintf(outputfile,"%*s",-MOVE_WIDTH,move->move);
883                 white_to_move = FALSE;
884             }
885             else{
886                 fprintf(outputfile,"%*s",-MOVE_WIDTH,move->move);
887                 move_number++;
888                 white_to_move = TRUE;
889             }
890         }
891         if((move->Comment != NULL) && GlobalState.keep_comments){
892             const char *result = move->terminating_result;
893
894             if(!white_to_move){
895                 fprintf(outputfile,"%*s",-MOVE_WIDTH,"...");
896             }
897             line_length = COMMENT_INDENT;
898             output_cm_comment(move->Comment,outputfile,COMMENT_INDENT);
899             if((result != NULL) && (move->check_status != CHECKMATE)){
900                 /* Give some information on the nature of the finish. */
901                 if(white_to_move){
902                     fprintf(outputfile,"%*s",COMMENT_INDENT,"");
903                 }
904                 else{
905                     /* Print out a string representing the result. */
906                     fprintf(outputfile,"%*s %*s%*s",
907                                     MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...",
908                                     MOVE_WIDTH,"");
909                 }
910                 output_cm_result(result,outputfile);
911                 putc('\n',outputfile);
912             }
913             else{
914                 if(!white_to_move){
915                     /* Indicate that the next move is Black's. */
916                     fprintf(outputfile,"%*s %*s",
917                                 MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...");
918                 }
919             }
920         }
921         else{
922             if((move->terminating_result != NULL) &&
923                         (move->check_status != CHECKMATE)){
924                 /* Give some information on the nature of the finish. */
925                 const char *result = move->terminating_result;
926
927                 if(!white_to_move){
928                     fprintf(outputfile,"%*s",-MOVE_WIDTH,"...");
929                 }
930                 output_cm_result(result,outputfile);
931                 if(!white_to_move){
932                     putc('\n',outputfile);
933                     fprintf(outputfile,"%*s %*s",
934                                     MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...");
935                 }
936                 putc('\n',outputfile);
937             }
938             else{
939                 if(white_to_move){
940                     /* Terminate the move pair. */
941                     putc('\n',outputfile);
942                 }
943             }
944         }
945         move = move->next;
946     }
947     putc('\n',outputfile);
948 }
949
950         /* Output the current game according to the required output format. */
951 void
952 output_game(Game current_game,FILE *outputfile)
953 {   Boolean white_to_move = TRUE;
954     unsigned move_number = 1;
955     /* The final board position, if available. */
956     Board *final_board = NULL;
957
958     /* If we aren't starting from the initial setup, then we
959      * need to know the current move number and whose
960      * move it is.
961      */
962     if(current_game.tags[FEN_TAG] != NULL){
963         Board *board = new_fen_board(current_game.tags[FEN_TAG]);
964
965         if(board != NULL){
966             move_number = board->move_number;
967             white_to_move = board->to_move == WHITE;
968             (void) free((void *)board);
969         }
970     }
971
972     /* Start at the beginning of a line. */
973     line_length = 0;
974     /* See if the moves should be rewritten into
975      * standard form.
976      */
977     if(GlobalState.output_format != SOURCE){
978         /* Rewrite the moves of the game into
979          * SAN (Standard Algebraic Notation).
980          */
981         final_board = rewrite_game(&current_game);
982     }
983     if(final_board != NULL) {
984         if(GlobalState.output_total_plycount) {
985             add_total_plycount(&current_game, GlobalState.keep_variations);
986         }
987         if(GlobalState.add_hashcode_tag || current_game.tags[HASHCODE_TAG] != NULL) {
988             add_hashcode_tag(&current_game);
989         }
990         switch(GlobalState.output_format){
991             case SAN:
992             case SOURCE:
993             case LALG:
994             case HALG:
995             case ELALG:
996                 print_algebraic_game(current_game,outputfile,move_number,white_to_move,
997                                      final_board);
998                 break;
999             case EPD:
1000                 print_epd_game(current_game,outputfile,move_number,white_to_move,
1001                                final_board);
1002                 break;
1003             case CM:
1004                 output_cm_game(outputfile,move_number,white_to_move,current_game);
1005                 break;
1006             case SESSE_BIN:
1007                 output_sesse_bin_game(current_game,outputfile,move_number,white_to_move);
1008                 break;
1009             default:
1010                 fprintf(GlobalState.logfile,
1011                             "Internal error: unknown output type %d in output_game().\n",
1012                             GlobalState.output_format);
1013                 break;
1014         }
1015         fflush(outputfile);
1016         (void) free((void *) final_board);
1017     }
1018 }
1019
1020         /* Add the given tag to the output ordering. */
1021 void
1022 add_to_output_tag_order(TagName tag)
1023 {   int tag_index;
1024
1025     if(TagOrder == NULL){
1026         tag_order_space = ARRAY_SIZE(DefaultTagOrder);
1027         TagOrder = (int *) MallocOrDie(tag_order_space*sizeof(*TagOrder));
1028         /* Always ensure that there is a negative value at the end. */
1029         TagOrder[0] = -1;
1030     }
1031     /* Check to ensure a position has not already been indicated
1032      * for this tag.
1033      */
1034     for(tag_index = 0; (TagOrder[tag_index] != -1) && 
1035                        (TagOrder[tag_index] != (int) tag); tag_index++){
1036     }
1037
1038     if(TagOrder[tag_index] == -1){
1039         /* Make sure there is enough space for another. */
1040         if(tag_index >= tag_order_space){
1041             /* Allocate some more. */
1042             tag_order_space += 10;
1043             TagOrder = (int *) ReallocOrDie((void *)TagOrder,
1044                                             tag_order_space*sizeof(*TagOrder));
1045         }
1046         TagOrder[tag_index] = tag;
1047         TagOrder[tag_index+1] = -1;
1048     }
1049     else{
1050         fprintf(GlobalState.logfile,"Duplicate position for tag: %s\n",
1051                 select_tag_string(tag));
1052     }
1053 }
1054
1055 static const char *format_epd_game_comment(char **Tags)
1056 {   
1057     static char comment_prefix[] = "c0 ";
1058     static char player_separator[] = "-";
1059     static size_t prefix_and_separator_len =
1060                 sizeof(comment_prefix)+sizeof(player_separator);
1061     size_t space_needed = prefix_and_separator_len;
1062     char *comment;
1063
1064     if(Tags[WHITE_TAG] != NULL){
1065       space_needed += strlen(Tags[WHITE_TAG]);
1066     }
1067     if(Tags[BLACK_TAG] != NULL){
1068       space_needed += strlen(Tags[BLACK_TAG]);
1069     }
1070     /* Allow a space character before each of the remaining tags. */
1071     if(Tags[EVENT_TAG] != NULL){
1072       space_needed += 1+strlen(Tags[EVENT_TAG]);
1073     }
1074     if(Tags[SITE_TAG] != NULL){
1075       space_needed += 1+strlen(Tags[SITE_TAG]);
1076     }
1077     if(Tags[DATE_TAG] != NULL){
1078       space_needed += 1+strlen(Tags[DATE_TAG]);
1079     }
1080     /* Allow for a terminating semicolon. */
1081     space_needed++;
1082
1083     comment = (char *) MallocOrDie(space_needed+1);
1084
1085     strcpy(comment,comment_prefix);
1086     if(Tags[WHITE_TAG] != NULL){
1087       strcat(comment,Tags[WHITE_TAG]);
1088     }
1089     strcat(comment,player_separator);
1090     if(Tags[BLACK_TAG] != NULL){
1091       strcat(comment,Tags[BLACK_TAG]);
1092     }
1093     if(Tags[EVENT_TAG] != NULL){
1094       strcat(comment," ");
1095       strcat(comment,Tags[EVENT_TAG]);
1096     }
1097     if(Tags[SITE_TAG] != NULL){
1098       strcat(comment," ");
1099       strcat(comment,Tags[SITE_TAG]);
1100     }
1101     if(Tags[DATE_TAG] != NULL){
1102       strcat(comment," ");
1103       strcat(comment,Tags[DATE_TAG]);
1104     }
1105     strcat(comment,";");
1106     if(strlen(comment) >= space_needed){
1107         fprintf(GlobalState.logfile,
1108                 "Internal error: overflow in format_epd_game_comment\n");
1109     }
1110     return comment;
1111 }
1112
1113 static void
1114 print_algebraic_game(Game current_game,FILE *outputfile,
1115                      unsigned move_number,Boolean white_to_move,
1116                      Board *final_board)
1117 {
1118     /* Report details on the output. */
1119     if(GlobalState.tag_output_format == ALL_TAGS) {
1120       show_tags(outputfile,current_game.tags,current_game.tags_length);
1121     }
1122     else if(GlobalState.tag_output_format == SEVEN_TAG_ROSTER){
1123       output_STR(outputfile,current_game.tags);
1124       if(GlobalState.add_ECO && !GlobalState.parsing_ECO_file){
1125                   /* If ECO classification has been requested, then assume
1126                    * that ECO tags are also required.
1127                    */
1128                   output_tag(ECO_TAG,current_game.tags,outputfile);
1129                   output_tag(OPENING_TAG,current_game.tags,outputfile);
1130                   output_tag(VARIATION_TAG,current_game.tags,outputfile);
1131                   output_tag(SUB_VARIATION_TAG,current_game.tags,outputfile);
1132       }
1133
1134       /* Output any FEN that there might be. */
1135       output_tag(FEN_TAG,current_game.tags,outputfile);
1136       putc('\n',outputfile);
1137     }
1138     else if(GlobalState.tag_output_format == NO_TAGS) {
1139     }
1140     else {
1141         fprintf(GlobalState.logfile,
1142                 "Unknown output form for tags: %d\n",
1143                 GlobalState.tag_output_format);
1144         exit(1);
1145     }
1146     if((GlobalState.keep_comments) &&
1147                     (current_game.prefix_comment != NULL)){
1148       print_comment_list(outputfile,
1149                          current_game.prefix_comment);
1150       terminate_line(outputfile);
1151       putc('\n',outputfile);
1152     }
1153     print_move_list(outputfile,move_number,white_to_move,
1154                     current_game.moves,final_board);
1155     /* Take account of a possible zero move game. */
1156     if(current_game.moves == NULL) {
1157         if(current_game.tags[RESULT_TAG] != NULL) {
1158             print_str(outputfile,current_game.tags[RESULT_TAG]);
1159         }
1160         else{
1161             fprintf(GlobalState.logfile,
1162                     "Internal error: Zero move game with no result\n");
1163         }
1164     }
1165     terminate_line(outputfile);
1166     putc('\n',outputfile);
1167 }
1168
1169 static void
1170 output_sesse_bin_game(Game current_game,FILE *outputfile,
1171                       unsigned move_number,Boolean white_to_move)
1172 {
1173     const char *result = NULL;
1174     Move *move;
1175
1176     // Find the result. Skip games with no result.
1177     for (move = current_game.moves; move != NULL; move = move->next) {
1178         if (move->terminating_result) {
1179             result = move->terminating_result;
1180         }
1181     }
1182     if (result == NULL || strcmp(result, "*") == 0) {
1183         return;
1184     }
1185
1186     int result_int = -1;
1187     if (strcmp(result, "1-0") == 0) {
1188         result_int = 0;
1189     } else if (strcmp(result, "1/2-1/2") == 0) {
1190         result_int = 1;
1191     } else if (strcmp(result, "0-1") == 0) {
1192         result_int = 2;
1193     } else {
1194         fprintf(stderr, "Unknown result '%s'\n", result);
1195         return;
1196     }
1197
1198     // Find Black and White Elos. Skip games with no Elo.
1199     const char *white_elo_tag = current_game.tags[WHITE_ELO_TAG];
1200     const char *black_elo_tag = current_game.tags[BLACK_ELO_TAG];
1201     if (white_elo_tag == NULL || black_elo_tag == NULL) {
1202         return;
1203     }
1204
1205     int white_elo = atoi(white_elo_tag);
1206     int black_elo = atoi(black_elo_tag);
1207
1208     for (move = current_game.moves; move != NULL; move = move->next) {
1209         unsigned int opening = move->eco ? move->eco->cumulative_hash_value : 0;  // Truncate to 32 bits.
1210
1211         // key
1212         putc(move->bpfen_len + strlen((char *)move->move), outputfile);
1213         fwrite(move->bpfen, move->bpfen_len, 1, outputfile);
1214         fwrite(move->move, strlen((char *)move->move), 1, outputfile);
1215
1216         // value
1217         putc(result_int, outputfile);
1218         fwrite(&white_elo, sizeof(white_elo), 1, outputfile);
1219         fwrite(&black_elo, sizeof(black_elo), 1, outputfile);
1220         fwrite(&opening, sizeof(opening), 1, outputfile);
1221     }
1222 }
1223
1224 static void
1225 print_epd_move_list(Game current_game,FILE *outputfile,
1226                     unsigned move_number, Boolean white_to_move,
1227                     Board *final_board)
1228 {   const Move *move = current_game.moves;
1229     const char *game_comment = format_epd_game_comment(current_game.tags);
1230     static char epd[FEN_SPACE];
1231
1232     while(move != NULL){
1233         if(move->epd != NULL){
1234             fprintf(outputfile,"%s",move->epd);
1235         }
1236         else{
1237             fprintf(GlobalState.logfile,"Internal error: Missing EPD\n");
1238         }
1239         fprintf(outputfile," %s",game_comment);
1240         putc('\n',outputfile);
1241         move = move->next;
1242     }
1243     if(final_board != NULL) {
1244         build_basic_EPD_string(final_board,epd);
1245         fprintf(outputfile,"%s %s",epd,game_comment);
1246         putc('\n',outputfile);
1247     }
1248     (void) free((void *)game_comment);
1249 }
1250
1251 static void
1252 print_epd_game(Game current_game,FILE *outputfile,
1253                unsigned move_number,Boolean white_to_move,
1254                Board *final_board)
1255 {
1256     if(!GlobalState.check_only){
1257         print_epd_move_list(current_game,outputfile,move_number,white_to_move,
1258                             final_board);
1259         putc('\n',outputfile);
1260     }
1261 }
1262
1263 /*
1264  * Build a comment containing a FEN representation of the board.
1265  * NB: This function re-uses static space each time it is called
1266  * so a later call will overwrite an earlier call.
1267  */
1268 static const char *
1269 build_FEN_comment(const Board *board)
1270 {
1271     static char fen[FEN_SPACE];
1272     strcpy(fen,"{ \"");
1273     build_FEN_string(board,&fen[strlen(fen)]);
1274     strcat(fen,"\" }");
1275     /* A belated sanity check. */
1276     if(strlen(fen) >= FEN_SPACE){
1277         fprintf(GlobalState.logfile,
1278                 "Internal error: string overflow in build_FEN_comment.\n");
1279     }
1280     return fen;
1281 }
1282
1283 /*
1284  * Count how many ply recorded for the given move.
1285  * Include variations if count_variations.
1286  */
1287 static unsigned count_single_move_ply(const Move *move_details, Boolean count_variations)
1288 {
1289     unsigned count = 1;
1290     if(count_variations) {
1291         Variation *variant = move_details->Variants;
1292         while(variant != NULL) {
1293             count += count_move_list_ply(variant->moves, count_variations);
1294             variant = variant->next;
1295         }
1296     }
1297     return count;
1298 }
1299
1300 /*
1301  * Count how many plies in the game in total.
1302  * Include variations if count_variations.
1303  */
1304 static unsigned count_move_list_ply(Move *move_list, Boolean count_variations)
1305 {
1306     unsigned count = 0;
1307     while(move_list != NULL) {
1308         count += count_single_move_ply(move_list, count_variations);
1309         move_list = move_list->next;
1310     }
1311     return count;
1312 }
1313
1314 /*
1315  * Count how many plies in the game in total.
1316  * Include variations if count_variations.
1317  */
1318 static void add_total_plycount(const Game *game, Boolean count_variations)
1319 {
1320     unsigned count = count_move_list_ply(game->moves, count_variations);
1321     char formatted_count[20];
1322     sprintf(formatted_count, "%u", count);
1323
1324     if(game->tags[TOTAL_PLY_COUNT_TAG] != NULL) {
1325         (void) free(game->tags[TOTAL_PLY_COUNT_TAG]);
1326     }
1327     game->tags[TOTAL_PLY_COUNT_TAG] = copy_string(formatted_count);
1328 }
1329
1330 /*
1331  * Include a tag containing a hashcode for the game.
1332  * Use the cumulative hash value.
1333  */
1334 static void add_hashcode_tag(const Game *game)
1335 {
1336     HashCode hashcode = game->cumulative_hash_value;
1337     char formatted_code[20];
1338     sprintf(formatted_code, "%08x", (unsigned) hashcode);
1339
1340     if(game->tags[HASHCODE_TAG] != NULL) {
1341         (void) free(game->tags[HASHCODE_TAG]);
1342     }
1343     game->tags[HASHCODE_TAG] = copy_string(formatted_code);
1344 }