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