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