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)
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.
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.
18 * David Barnes may be contacted as D.J.Barnes@kent.ac.uk
19 * http://www.cs.kent.ac.uk/people/staff/djb/
39 /* Functions for outputting games in the required format. */
41 /* Define the width in which to print a CM move and move number. */
42 #define MOVE_NUMBER_WIDTH 3
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)
48 /* Define a macro to calculate an array's size. */
49 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(*arr))
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;
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,
65 static void print_epd_game(Game current_game,FILE *outputfile,
66 unsigned move_number,Boolean white_to_move,
68 static void print_epd_move_list(Game current_game,FILE *outputfile,
69 unsigned move_number, Boolean white_to_move,
71 static const char *build_FEN_comment(const Board *board);
72 static void add_total_plycount(const Game *game, Boolean count_variations);
73 static void add_hashcode_tag(const Game *game);
74 static unsigned count_single_move_ply(const Move *move_details, Boolean count_variations);
75 static unsigned count_move_list_ply(Move *move_list, Boolean count_variations);
77 /* List, the order in which the tags should be output.
78 * The first seven should be the Seven Tag Roster that should
79 * be present in every game.
80 * The order of the remainder is, I believe, a matter of taste.
81 * any PSEUDO_*_TAGs should not appear in this list.
84 /* Give the array an int type, because a negative value is
85 * used as a terminator.
87 static int DefaultTagOrder[] = {
88 EVENT_TAG, SITE_TAG, DATE_TAG, ROUND_TAG, WHITE_TAG, BLACK_TAG, RESULT_TAG,
90 /* @@@ Consider omitting some of these from the default ordering,
91 * and allow the output order to be determined from the
94 WHITE_TITLE_TAG, BLACK_TITLE_TAG, WHITE_ELO_TAG, BLACK_ELO_TAG,
95 WHITE_USCF_TAG, BLACK_USCF_TAG,
96 WHITE_TYPE_TAG, BLACK_TYPE_TAG,
97 WHITE_NA_TAG, BLACK_NA_TAG,
98 ECO_TAG, NIC_TAG, OPENING_TAG, VARIATION_TAG, SUB_VARIATION_TAG,
102 EVENT_DATE_TAG, EVENT_SPONSOR_TAG, SECTION_TAG, STAGE_TAG, BOARD_TAG,
103 TIME_TAG, UTC_DATE_TAG, UTC_TIME_TAG,
106 TERMINATION_TAG, MODE_TAG, PLY_COUNT_TAG,
108 /* The final value should be negative. */
112 /* Provision for a user-defined tag ordering.
113 * See add_to_output_tag_order().
114 * Once allocated, the end of the list must be negative.
116 static int *TagOrder = NULL;
117 static int tag_order_space = 0;
120 set_output_line_length(unsigned length)
122 if(output_line != NULL) {
123 (void) free((void *) output_line);
125 output_line = (char *) MallocOrDie(length + 1);
126 GlobalState.max_line_length = length;
129 /* Which output format does the user require, based upon the
130 * given command line argument?
133 which_output_format(const char *arg)
153 /* Add others before the terminating NULL. */
154 { (const char *) NULL, SAN }
157 for(i = 0; formats[i].arg != NULL; i++){
158 const char *format_prefix = formats[i].arg;
159 const size_t format_prefix_len = strlen(format_prefix);
160 if(strncmp(arg,format_prefix,format_prefix_len) == 0){
161 OutputFormat format = formats[i].format;
163 if(*format_prefix == '\0' && *arg != '\0') {
164 fprintf(GlobalState.logfile,
165 "Unknown output format %s.\n", arg);
168 /* If the format is SAN, it is possible to supply
169 * a 6-piece suffix listing language-specific
170 * letters to use in the output.
172 if((format == SAN || format == ELALG) &&
173 (strlen(arg) > format_prefix_len)){
174 set_output_piece_characters(&arg[format_prefix_len]);
179 fprintf(GlobalState.logfile,"Unknown output format %s.\n",arg);
183 /* Which file suffix should be used for this output format. */
185 output_file_suffix(OutputFormat format)
187 /* Define a suffix for the output files. */
188 static const char PGN_suffix[] = ".pgn";
189 static const char EPD_suffix[] = ".epd";
190 static const char CM_suffix[] = ".cm";
209 select_tag_string(TagName tag)
210 { const char *tag_string;
212 if((tag == PSEUDO_PLAYER_TAG) || (tag == PSEUDO_ELO_TAG) || (tag == PSEUDO_FEN_PATTERN_TAG)){
216 tag_string = tag_header_string(tag);
238 /* Output the tags held in the Tags structure.
239 * At least the full Seven Tag Roster is printed.
242 output_tag(TagName tag, char **Tags, FILE *outfp)
243 { const char *tag_string;
245 /* Must print STR elements and other non-NULL tags. */
246 if((is_STR(tag)) || (Tags[tag] != NULL)){
247 tag_string = select_tag_string(tag);
249 if(tag_string != NULL){
250 fprintf(outfp,"[%s \"",tag_string);
251 if(Tags[tag] != NULL){
252 fwrite(Tags[tag],sizeof(char),strlen(Tags[tag]),outfp);
256 fprintf(outfp,"????.??.??");
262 fprintf(outfp,"\"]\n");
267 /* Output the Seven Tag Roster. */
269 output_STR(FILE *outfp,char **Tags)
270 { unsigned tag_index;
272 /* Use the default ordering to ensure that STR is output
273 * in the way it should be.
275 for(tag_index = 0; tag_index < 7; tag_index++){
276 output_tag(DefaultTagOrder[tag_index],Tags,outfp);
280 /* Print out on outfp the current details.
281 * These can be used in the case of an error.
284 show_tags(FILE *outfp,char **Tags,int tags_length)
286 /* Take a copy of the Tags data, so that we can keep
287 * track of what has been printed. This will make
288 * it possible to print tags that were identified
289 * in the source but are not defined with _TAG values.
290 * See lex.c for how these extra tags are handled.
292 char **copy_of_tags =
293 (char **) MallocOrDie(tags_length*sizeof(*copy_of_tags));
295 for(i = 0; i < tags_length; i++){
296 copy_of_tags[i] = Tags[i];
299 /* Ensure that a tag ordering is available. */
300 if(TagOrder == NULL){
301 /* None set by the user - use the default. */
302 /* Handle the standard tags.
303 * The end of the list is marked with a negative value.
305 for(tag_index = 0; DefaultTagOrder[tag_index] >= 0; tag_index++){
306 TagName tag = DefaultTagOrder[tag_index];
307 output_tag(tag,copy_of_tags,outfp);
308 copy_of_tags[tag] = (char *)NULL;
310 /* Handle the extra tags. */
311 for(tag_index = 0; tag_index < tags_length; tag_index++){
312 if(copy_of_tags[tag_index] != NULL){
313 output_tag(tag_index,copy_of_tags,outfp);
318 for(tag_index = 0; TagOrder[tag_index] >= 0; tag_index++){
319 TagName tag = TagOrder[tag_index];
320 output_tag(tag,copy_of_tags,outfp);
321 copy_of_tags[tag] = (char *)NULL;
324 (void) free(copy_of_tags);
328 /* Ensure that there is room for len more characters on the
332 check_line_length(FILE *fp,size_t len)
334 if((line_length + len) > GlobalState.max_line_length){
339 /* Print ch to fp and update how much of the line
340 * has been printed on.
343 single_char(FILE *fp, char ch)
345 check_line_length(fp,1);
346 output_line[line_length] = ch;
350 /* Print a space, unless at the beginning of a line. */
352 print_separator(FILE *fp)
354 /* Lines shouldn't have trailing spaces, so ensure that there
355 * will be room for at least one more character after the space.
357 check_line_length(fp,2);
358 if(line_length != 0){
359 output_line[line_length] = ' ';
364 /* Ensure that what comes next starts on a fresh line. */
366 terminate_line(FILE *fp)
368 /* Delete any trailing space(s). */
369 while(line_length >= 1 && output_line[line_length - 1] == ' ') {
372 if(line_length > 0) {
373 output_line[line_length] = '\0';
374 fprintf(fp, "%s\n", output_line);
379 /* Print str to fp and update how much of the line
380 * has been printed on.
383 print_str(FILE *fp, const char *str)
384 { size_t len = strlen(str);
386 check_line_length(fp,len);
387 if(len > GlobalState.max_line_length) {
388 fprintf(GlobalState.logfile, "String length %lu is too long for the line length of %lu:\n",
390 (unsigned long) GlobalState.max_line_length);
391 fprintf(GlobalState.logfile, "%s\n", str);
392 report_details(GlobalState.logfile);
393 fprintf(fp, "%s\n", str);
396 sprintf(&(output_line[line_length]),"%s",str);
402 print_comment_list(FILE *fp, CommentList *comment_list)
403 { CommentList *next_comment;
405 for(next_comment = comment_list ; next_comment != NULL;
406 next_comment = next_comment->next){
407 StringList *comment = next_comment->Comment;
410 /* We will use strtok to break up the comment string,
411 * with chunk to point to each bit in turn.
416 for( ; comment != NULL; comment = comment->next){
417 /* Make a copy because the string is altered. */
418 char *str = copy_string(comment->str);
419 chunk = strtok(str," ");
420 while(chunk != NULL){
423 chunk = strtok((char *)NULL," ");
425 (void) free((void *) str);
429 if(next_comment->next != NULL){
437 print_move_list(FILE *outputfile, unsigned move_number, Boolean white_to_move,
438 const Move *move_details,const Board *final_board)
439 { unsigned print_move_number = 1;
440 const Move *move = move_details;
441 Boolean keepPrinting;
444 /* Work out the ply depth. */
445 plies = 2*(move_number) - 1;
449 if(GlobalState.output_ply_limit >= 0 &&
450 plies > GlobalState.output_ply_limit) {
451 keepPrinting = FALSE;
457 while(move != NULL && keepPrinting){
458 /* Reset print_move number if a variation was printed. */
459 print_move_number = print_move(outputfile,move_number,
463 /* See if there is a result attached. This may be attached either
464 * to a move or a comment.
466 if(!GlobalState.check_only && (move != NULL) &&
467 (move->terminating_result != NULL)){
468 if(GlobalState.output_FEN_string && final_board != NULL){
469 print_separator(outputfile);
470 print_str(outputfile,build_FEN_comment(final_board));
472 if(GlobalState.keep_results) {
473 print_separator(outputfile);
474 print_str(outputfile,move->terminating_result);
477 if(move->move[0] != '\0'){
478 /* A genuine move was just printed, rather than a comment. */
480 white_to_move = FALSE;
484 white_to_move = TRUE;
487 if(GlobalState.output_ply_limit >= 0 &&
488 plies > GlobalState.output_ply_limit) {
489 keepPrinting = FALSE;
493 /* The following is slightly inaccurate.
494 * If the previous value of move was a comment and
495 * we aren't printing comments, then this results in two
496 * separators being printed after the move preceding the comment.
497 * Not sure how to cleanly fix it, because there might have
498 * been nags attached to the comment that were printed, for instance!
500 if(move != NULL && keepPrinting){
501 print_separator(outputfile);
505 if(move != NULL && !keepPrinting) {
506 /* We ended printing the game prematurely. */
509 * Decide whether to print a result indicator.
511 if(GlobalState.keep_results) {
512 /* Find the final move to see if there was a result there. */
513 while(move->next != NULL) {
516 if(move->terminating_result != NULL) {
517 print_separator(outputfile);
518 print_str(outputfile, "*");
524 /* Output the current move along with associated information.
525 * Return TRUE if either a variation or comment was printed,
527 * This is needed to determine whether a new move number
528 * is to be printed after a variation.
531 print_move(FILE *outputfile, unsigned move_number, unsigned print_move_number,
532 Boolean white_to_move, const Move *move_details)
533 { Boolean something_printed = FALSE;
535 if(move_details == NULL){
536 /* Shouldn't happen. */
537 fprintf(GlobalState.logfile,
538 "Internal error: NULL move in print_move.\n");
539 report_details(GlobalState.logfile);
542 if(GlobalState.check_only){
543 /* Nothing to be output. */
546 StringList *nags = move_details->Nags;
547 Variation *variants = move_details->Variants;
548 const unsigned char *move_text = move_details->move;
550 if(*move_text != '\0'){
551 if(GlobalState.keep_move_numbers &&
552 (white_to_move || print_move_number)){
553 static char small_number[] = "99999...";
555 /* @@@ Should 1... be written as 1. ... ? */
556 sprintf(small_number,"%u.%s",move_number, white_to_move?"":"..");
557 print_str(outputfile,small_number);
558 print_separator(outputfile);
560 switch(GlobalState.output_format){
563 /* @@@ move_text should be handled as unsigned
564 * char text, as the source may be 8-bit rather
567 if(GlobalState.keep_checks) {
568 print_str(outputfile, (const char *) move_text);
571 /* Look for a check or mate symbol. */
572 char *check = strchr((const char *) move_text, '+');
574 check = strchr((const char *) move_text, '#');
577 /* We need to drop it from move_text. */
578 int len = check - ((char *) move_text);
579 char *just_move = (char *) MallocOrDie(len + 1);
580 strncpy(just_move, (const char *) move_text, len);
581 just_move[len] = '\0';
582 print_str(outputfile, just_move);
583 (void) free(just_move);
586 print_str(outputfile, (const char *) move_text);
591 { char algebraic[MAX_MOVE_LEN+1];
594 switch(move_details->class){
596 case ENPASSANT_PAWN_MOVE:
597 case KINGSIDE_CASTLE:
598 case QUEENSIDE_CASTLE:
600 sprintf(algebraic,"%c%c-%c%c",
601 move_details->from_col,
602 move_details->from_rank,
603 move_details->to_col,
604 move_details->to_rank);
606 case PAWN_MOVE_WITH_PROMOTION:
607 sprintf(algebraic,"%c%c-%c%c%c",
608 move_details->from_col,
609 move_details->from_rank,
610 move_details->to_col,
611 move_details->to_rank,
612 promoted_piece_letter(move_details->promoted_piece));
615 strcpy(algebraic, NULL_MOVE_STRING);
618 strcpy(algebraic,"???");
621 if(GlobalState.keep_checks) {
622 switch(move_details->check_status){
626 strcat(algebraic,"+");
629 strcat(algebraic,"#");
633 print_str(outputfile,algebraic);
638 { char algebraic[MAX_MOVE_LEN+1];
641 /* Prefix with a piece name if ELALG. */
642 if(GlobalState.output_format == ELALG &&
643 move_details->class == PIECE_MOVE){
645 piece_str(move_details->piece_to_move));
646 ind = strlen(algebraic);
648 /* Format the basics. */
649 if(move_details->class != NULL_MOVE) {
650 sprintf(&algebraic[ind],"%c%c%c%c",
651 move_details->from_col,
652 move_details->from_rank,
653 move_details->to_col,
654 move_details->to_rank);
658 strcpy(algebraic, NULL_MOVE_STRING);
659 ind += strlen(NULL_MOVE_STRING);
661 switch(move_details->class){
663 case KINGSIDE_CASTLE:
664 case QUEENSIDE_CASTLE:
667 /* Nothing more to do at this stage. */
669 case ENPASSANT_PAWN_MOVE:
670 if(GlobalState.output_format == ELALG){
671 strcat(algebraic,"ep");
675 case PAWN_MOVE_WITH_PROMOTION:
676 sprintf(&algebraic[ind],"%s",
677 piece_str(move_details->promoted_piece));
678 ind = strlen(algebraic);
681 strcpy(algebraic,"???");
685 if(GlobalState.keep_checks) {
686 switch(move_details->check_status){
690 strcat(algebraic,"+");
694 strcat(algebraic,"#");
699 print_str(outputfile,algebraic);
703 fprintf(GlobalState.logfile,
704 "Unknown output format %d in print_move()\n",
705 GlobalState.output_format);
712 fprintf(GlobalState.logfile,
713 "Internal error: Empty move in print_move.\n");
714 report_details(GlobalState.logfile);
716 /* Print further information, that may be attached to moves
719 if(GlobalState.keep_NAGs){
721 print_separator(outputfile);
722 print_str(outputfile,nags->str);
725 /* We don't need to output move numbers after just
726 * NAGs, so don't set something_printed.
729 if(GlobalState.keep_comments){
730 if(move_details->Comment != NULL){
731 print_separator(outputfile);
732 print_comment_list(outputfile,move_details->Comment);
733 something_printed = TRUE;
736 if((GlobalState.keep_variations) && (variants != NULL)){
737 while(variants != NULL){
738 print_separator(outputfile);
739 single_char(outputfile,'(');
740 if(GlobalState.keep_comments &&
741 (variants->prefix_comment != NULL)){
742 print_comment_list(outputfile,variants->prefix_comment);
743 print_separator(outputfile);
745 /* Always start with a move number.
746 * The final board position is not needed.
748 print_move_list(outputfile,move_number,
749 white_to_move,variants->moves,
750 (const Board *)NULL);
751 single_char(outputfile,')');
752 if(GlobalState.keep_comments &&
753 (variants->suffix_comment != NULL)){
754 print_separator(outputfile);
755 print_comment_list(outputfile,variants->suffix_comment);
757 variants = variants->next;
759 something_printed = TRUE;
763 return something_printed;
766 /* Return the letter associated with the given piece. */
768 promoted_piece_letter(Piece piece)
784 /* Output a comment in CM format. */
786 output_cm_comment(CommentList *comment,FILE *outputfile,unsigned indent)
787 { /* Don't indent for the first comment line, because
788 * we should already be positioned at the correct spot.
790 unsigned indent_for_this_line = 0;
792 putc(CM_COMMENT_CHAR,outputfile);
794 while(comment != NULL){
795 /* We will use strtok to break up the comment string,
796 * with chunk to point to each bit in turn.
799 StringList *comment_str = comment->Comment;
801 for( ; comment_str != NULL; comment_str = comment_str->next){
802 char *str = copy_string(comment_str->str);
803 chunk = strtok(str," ");
804 while(chunk != NULL){
805 size_t len = strlen(chunk);
807 if((line_length+1+len) > GlobalState.max_line_length){
808 /* Start a new line. */
809 indent_for_this_line = indent;
810 fprintf(outputfile,"\n%*s%c ",indent_for_this_line,"",
812 line_length = indent_for_this_line+2;
815 putc(' ',outputfile);
818 fprintf(outputfile,"%s",chunk);
820 chunk = strtok((char *)NULL," ");
822 (void) free((void *) str);
824 comment = comment->next;
826 putc('\n',outputfile);
831 output_cm_result(const char *result,FILE *outputfile)
833 fprintf(outputfile,"%c ",CM_COMMENT_CHAR);
834 if(strcmp(result,"1-0") == 0){
835 fprintf(outputfile,"and black resigns");
837 else if(strcmp(result,"0-1") == 0){
838 fprintf(outputfile,"and white resigns");
840 else if(strncmp(result,"1/2",3) == 0){
841 fprintf(outputfile,"draw");
844 fprintf(outputfile,"incomplete result");
849 /* Output the game in Chess Master format. */
851 output_cm_game(FILE *outputfile,unsigned move_number,
852 Boolean white_to_move,const Game game)
853 { const Move *move = game.moves;
855 if((move_number != 1) || (!white_to_move)){
856 fprintf(GlobalState.logfile,
857 "Unable to output CM games other than from the starting position.\n");
858 report_details(GlobalState.logfile);
860 fprintf(outputfile,"WHITE: %s\n",
861 game.tags[WHITE_TAG] != NULL? game.tags[WHITE_TAG] : "");
862 fprintf(outputfile,"BLACK: %s\n",
863 game.tags[BLACK_TAG] != NULL? game.tags[BLACK_TAG] : "");
864 putc('\n',outputfile);
866 if(game.prefix_comment != NULL){
868 output_cm_comment(game.prefix_comment,outputfile,0);
871 if(move->move[0] != '\0'){
872 /* A genuine move. */
874 fprintf(outputfile,"%*u. ",MOVE_NUMBER_WIDTH,move_number);
875 fprintf(outputfile,"%*s",-MOVE_WIDTH,move->move);
876 white_to_move = FALSE;
879 fprintf(outputfile,"%*s",-MOVE_WIDTH,move->move);
881 white_to_move = TRUE;
884 if((move->Comment != NULL) && GlobalState.keep_comments){
885 const char *result = move->terminating_result;
888 fprintf(outputfile,"%*s",-MOVE_WIDTH,"...");
890 line_length = COMMENT_INDENT;
891 output_cm_comment(move->Comment,outputfile,COMMENT_INDENT);
892 if((result != NULL) && (move->check_status != CHECKMATE)){
893 /* Give some information on the nature of the finish. */
895 fprintf(outputfile,"%*s",COMMENT_INDENT,"");
898 /* Print out a string representing the result. */
899 fprintf(outputfile,"%*s %*s%*s",
900 MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...",
903 output_cm_result(result,outputfile);
904 putc('\n',outputfile);
908 /* Indicate that the next move is Black's. */
909 fprintf(outputfile,"%*s %*s",
910 MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...");
915 if((move->terminating_result != NULL) &&
916 (move->check_status != CHECKMATE)){
917 /* Give some information on the nature of the finish. */
918 const char *result = move->terminating_result;
921 fprintf(outputfile,"%*s",-MOVE_WIDTH,"...");
923 output_cm_result(result,outputfile);
925 putc('\n',outputfile);
926 fprintf(outputfile,"%*s %*s",
927 MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...");
929 putc('\n',outputfile);
933 /* Terminate the move pair. */
934 putc('\n',outputfile);
940 putc('\n',outputfile);
943 /* Output the current game according to the required output format. */
945 output_game(Game current_game,FILE *outputfile)
946 { Boolean white_to_move = TRUE;
947 unsigned move_number = 1;
948 /* The final board position, if available. */
949 Board *final_board = NULL;
951 /* If we aren't starting from the initial setup, then we
952 * need to know the current move number and whose
955 if(current_game.tags[FEN_TAG] != NULL){
956 Board *board = new_fen_board(current_game.tags[FEN_TAG]);
959 move_number = board->move_number;
960 white_to_move = board->to_move == WHITE;
961 (void) free((void *)board);
965 /* Start at the beginning of a line. */
967 /* See if the moves should be rewritten into
970 if(GlobalState.output_format != SOURCE){
971 /* Rewrite the moves of the game into
972 * SAN (Standard Algebraic Notation).
974 final_board = rewrite_game(¤t_game);
976 if(final_board != NULL) {
977 if(GlobalState.output_total_plycount) {
978 add_total_plycount(¤t_game, GlobalState.keep_variations);
980 if(GlobalState.add_hashcode_tag || current_game.tags[HASHCODE_TAG] != NULL) {
981 add_hashcode_tag(¤t_game);
983 switch(GlobalState.output_format){
989 print_algebraic_game(current_game,outputfile,move_number,white_to_move,
993 print_epd_game(current_game,outputfile,move_number,white_to_move,
997 output_cm_game(outputfile,move_number,white_to_move,current_game);
1000 fprintf(GlobalState.logfile,
1001 "Internal error: unknown output type %d in output_game().\n",
1002 GlobalState.output_format);
1006 (void) free((void *) final_board);
1010 /* Add the given tag to the output ordering. */
1012 add_to_output_tag_order(TagName tag)
1015 if(TagOrder == NULL){
1016 tag_order_space = ARRAY_SIZE(DefaultTagOrder);
1017 TagOrder = (int *) MallocOrDie(tag_order_space*sizeof(*TagOrder));
1018 /* Always ensure that there is a negative value at the end. */
1021 /* Check to ensure a position has not already been indicated
1024 for(tag_index = 0; (TagOrder[tag_index] != -1) &&
1025 (TagOrder[tag_index] != (int) tag); tag_index++){
1028 if(TagOrder[tag_index] == -1){
1029 /* Make sure there is enough space for another. */
1030 if(tag_index >= tag_order_space){
1031 /* Allocate some more. */
1032 tag_order_space += 10;
1033 TagOrder = (int *) ReallocOrDie((void *)TagOrder,
1034 tag_order_space*sizeof(*TagOrder));
1036 TagOrder[tag_index] = tag;
1037 TagOrder[tag_index+1] = -1;
1040 fprintf(GlobalState.logfile,"Duplicate position for tag: %s\n",
1041 select_tag_string(tag));
1045 static const char *format_epd_game_comment(char **Tags)
1047 static char comment_prefix[] = "c0 ";
1048 static char player_separator[] = "-";
1049 static size_t prefix_and_separator_len =
1050 sizeof(comment_prefix)+sizeof(player_separator);
1051 size_t space_needed = prefix_and_separator_len;
1054 if(Tags[WHITE_TAG] != NULL){
1055 space_needed += strlen(Tags[WHITE_TAG]);
1057 if(Tags[BLACK_TAG] != NULL){
1058 space_needed += strlen(Tags[BLACK_TAG]);
1060 /* Allow a space character before each of the remaining tags. */
1061 if(Tags[EVENT_TAG] != NULL){
1062 space_needed += 1+strlen(Tags[EVENT_TAG]);
1064 if(Tags[SITE_TAG] != NULL){
1065 space_needed += 1+strlen(Tags[SITE_TAG]);
1067 if(Tags[DATE_TAG] != NULL){
1068 space_needed += 1+strlen(Tags[DATE_TAG]);
1070 /* Allow for a terminating semicolon. */
1073 comment = (char *) MallocOrDie(space_needed+1);
1075 strcpy(comment,comment_prefix);
1076 if(Tags[WHITE_TAG] != NULL){
1077 strcat(comment,Tags[WHITE_TAG]);
1079 strcat(comment,player_separator);
1080 if(Tags[BLACK_TAG] != NULL){
1081 strcat(comment,Tags[BLACK_TAG]);
1083 if(Tags[EVENT_TAG] != NULL){
1084 strcat(comment," ");
1085 strcat(comment,Tags[EVENT_TAG]);
1087 if(Tags[SITE_TAG] != NULL){
1088 strcat(comment," ");
1089 strcat(comment,Tags[SITE_TAG]);
1091 if(Tags[DATE_TAG] != NULL){
1092 strcat(comment," ");
1093 strcat(comment,Tags[DATE_TAG]);
1095 strcat(comment,";");
1096 if(strlen(comment) >= space_needed){
1097 fprintf(GlobalState.logfile,
1098 "Internal error: overflow in format_epd_game_comment\n");
1104 print_algebraic_game(Game current_game,FILE *outputfile,
1105 unsigned move_number,Boolean white_to_move,
1108 /* Report details on the output. */
1109 if(GlobalState.tag_output_format == ALL_TAGS) {
1110 show_tags(outputfile,current_game.tags,current_game.tags_length);
1112 else if(GlobalState.tag_output_format == SEVEN_TAG_ROSTER){
1113 output_STR(outputfile,current_game.tags);
1114 if(GlobalState.add_ECO && !GlobalState.parsing_ECO_file){
1115 /* If ECO classification has been requested, then assume
1116 * that ECO tags are also required.
1118 output_tag(ECO_TAG,current_game.tags,outputfile);
1119 output_tag(OPENING_TAG,current_game.tags,outputfile);
1120 output_tag(VARIATION_TAG,current_game.tags,outputfile);
1121 output_tag(SUB_VARIATION_TAG,current_game.tags,outputfile);
1124 /* Output any FEN that there might be. */
1125 output_tag(FEN_TAG,current_game.tags,outputfile);
1126 putc('\n',outputfile);
1128 else if(GlobalState.tag_output_format == NO_TAGS) {
1131 fprintf(GlobalState.logfile,
1132 "Unknown output form for tags: %d\n",
1133 GlobalState.tag_output_format);
1136 if((GlobalState.keep_comments) &&
1137 (current_game.prefix_comment != NULL)){
1138 print_comment_list(outputfile,
1139 current_game.prefix_comment);
1140 terminate_line(outputfile);
1141 putc('\n',outputfile);
1143 print_move_list(outputfile,move_number,white_to_move,
1144 current_game.moves,final_board);
1145 /* Take account of a possible zero move game. */
1146 if(current_game.moves == NULL) {
1147 if(current_game.tags[RESULT_TAG] != NULL) {
1148 print_str(outputfile,current_game.tags[RESULT_TAG]);
1151 fprintf(GlobalState.logfile,
1152 "Internal error: Zero move game with no result\n");
1155 terminate_line(outputfile);
1156 putc('\n',outputfile);
1160 print_epd_move_list(Game current_game,FILE *outputfile,
1161 unsigned move_number, Boolean white_to_move,
1163 { const Move *move = current_game.moves;
1164 const char *game_comment = format_epd_game_comment(current_game.tags);
1165 static char epd[FEN_SPACE];
1167 while(move != NULL){
1168 if(move->epd != NULL){
1169 fprintf(outputfile,"%s",move->epd);
1172 fprintf(GlobalState.logfile,"Internal error: Missing EPD\n");
1174 fprintf(outputfile," %s",game_comment);
1175 putc('\n',outputfile);
1178 if(final_board != NULL) {
1179 build_basic_EPD_string(final_board,epd);
1180 fprintf(outputfile,"%s %s",epd,game_comment);
1181 putc('\n',outputfile);
1183 (void) free((void *)game_comment);
1187 print_epd_game(Game current_game,FILE *outputfile,
1188 unsigned move_number,Boolean white_to_move,
1191 if(!GlobalState.check_only){
1192 print_epd_move_list(current_game,outputfile,move_number,white_to_move,
1194 putc('\n',outputfile);
1199 * Build a comment containing a FEN representation of the board.
1200 * NB: This function re-uses static space each time it is called
1201 * so a later call will overwrite an earlier call.
1204 build_FEN_comment(const Board *board)
1206 static char fen[FEN_SPACE];
1208 build_FEN_string(board,&fen[strlen(fen)]);
1210 /* A belated sanity check. */
1211 if(strlen(fen) >= FEN_SPACE){
1212 fprintf(GlobalState.logfile,
1213 "Internal error: string overflow in build_FEN_comment.\n");
1219 * Count how many ply recorded for the given move.
1220 * Include variations if count_variations.
1222 static unsigned count_single_move_ply(const Move *move_details, Boolean count_variations)
1225 if(count_variations) {
1226 Variation *variant = move_details->Variants;
1227 while(variant != NULL) {
1228 count += count_move_list_ply(variant->moves, count_variations);
1229 variant = variant->next;
1236 * Count how many plies in the game in total.
1237 * Include variations if count_variations.
1239 static unsigned count_move_list_ply(Move *move_list, Boolean count_variations)
1242 while(move_list != NULL) {
1243 count += count_single_move_ply(move_list, count_variations);
1244 move_list = move_list->next;
1250 * Count how many plies in the game in total.
1251 * Include variations if count_variations.
1253 static void add_total_plycount(const Game *game, Boolean count_variations)
1255 unsigned count = count_move_list_ply(game->moves, count_variations);
1256 char formatted_count[20];
1257 sprintf(formatted_count, "%u", count);
1259 if(game->tags[TOTAL_PLY_COUNT_TAG] != NULL) {
1260 (void) free(game->tags[TOTAL_PLY_COUNT_TAG]);
1262 game->tags[TOTAL_PLY_COUNT_TAG] = copy_string(formatted_count);
1266 * Include a tag containing a hashcode for the game.
1267 * Use the cumulative hash value.
1269 static void add_hashcode_tag(const Game *game)
1271 HashCode hashcode = game->cumulative_hash_value;
1272 char formatted_code[20];
1273 sprintf(formatted_code, "%08x", (unsigned) hashcode);
1275 if(game->tags[HASHCODE_TAG] != NULL) {
1276 (void) free(game->tags[HASHCODE_TAG]);
1278 game->tags[HASHCODE_TAG] = copy_string(formatted_code);