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