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