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