/* * Program: pgn-extract: a Portable Game Notation (PGN) extractor. * Copyright (C) 1994-2001 David Barnes * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * David Barnes may be contacted as D.J.Barnes@ukc.ac.uk * http://www.cs.ukc.ac.uk/people/staff/djb/ * */ #include #include #include #include #include "bool.h" #include "defs.h" #include "typedef.h" #include "taglist.h" #include "tokens.h" #include "lex.h" #include "grammar.h" #include "apply.h" #include "output.h" /* Functions for outputting games in the required format. */ /* Define the width in which to print a CM move and move number. */ #define MOVE_NUMBER_WIDTH 3 #define MOVE_WIDTH 15 #define CM_COMMENT_CHAR ';' /* Define the width of the moves area before a comment. */ #define COMMENT_INDENT (MOVE_NUMBER_WIDTH+2+2*MOVE_WIDTH) /* Define a macro to calculate an array's size. */ #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(*arr)) /* How much text we have output on the current line. */ static unsigned line_length = 0; static Boolean print_move(FILE *outputfile, unsigned move_number, unsigned print_move_number, Boolean white_to_move, const Move *move_details); static void output_STR(FILE *outfp,char **Tags); static void show_tags(FILE *outfp,char **Tags,int tags_length); static char promoted_piece_letter(Piece piece); static void build_FEN_string(const Board *board,char *fen); static const char *build_FEN_comment(const Board *board); static void print_epd_game(Game current_game,FILE *outputfile, unsigned move_number,Boolean white_to_move, Board *final_board); static void print_epd_move_list(Game current_game,FILE *outputfile, unsigned move_number, Boolean white_to_move, Board *final_board); /* List, the order in which the tags should be output. * The first seven should be the Seven Tag Roster that should * be present in every game. * The order of the remainder is, I believe, a matter of taste. * PSEUDO_PLAYER_TAG and PSEUDO_ELO_TAG should not appear * in this list. */ /* Give the array an int type, because a negative value is * used as a terminator. */ static int DefaultTagOrder[] = { EVENT_TAG, SITE_TAG, DATE_TAG, ROUND_TAG, WHITE_TAG, BLACK_TAG, RESULT_TAG, #if 1 /* @@@ Consider omitting some of these from the default ordering, * and allow the output order to be determined from the * input order. */ WHITE_TITLE_TAG, BLACK_TITLE_TAG, WHITE_ELO_TAG, BLACK_ELO_TAG, WHITE_USCF_TAG, BLACK_USCF_TAG, WHITE_TYPE_TAG, BLACK_TYPE_TAG, WHITE_NA_TAG, BLACK_NA_TAG, ECO_TAG, NIC_TAG, OPENING_TAG, VARIATION_TAG, SUB_VARIATION_TAG, LONG_ECO_TAG, TIME_CONTROL_TAG, ANNOTATOR_TAG, EVENT_DATE_TAG, EVENT_SPONSOR_TAG, SECTION_TAG, STAGE_TAG, BOARD_TAG, TIME_TAG, UTC_DATE_TAG, UTC_TIME_TAG, SETUP_TAG, FEN_TAG, TERMINATION_TAG, MODE_TAG, PLY_COUNT_TAG, #endif /* The final value should be negative. */ -1 }; /* Provision for a user-defined tag ordering. * See add_to_output_tag_order(). * Once allocated, the end of the list must be negative. */ static int *TagOrder = NULL; static int tag_order_space = 0; /* Which output format does the user require, based upon the * given command line argument? */ OutputFormat which_output_format(const char *arg) { int i; struct { const char *arg; OutputFormat format; } formats[] = { { "san", SAN }, { "SAN", SAN }, { "epd", EPD }, { "EPD", EPD }, { "lalg", LALG }, { "halg", HALG }, { "CM", CM }, { "LALG", LALG }, { "HALG", HALG }, { "cm", CM }, { "", SOURCE }, /* Add others before the terminating NULL. */ { (const char *) NULL, SAN } }; for(i = 0; formats[i].arg != NULL; i++){ const char *format_prefix = formats[i].arg; const int format_prefix_len = strlen(format_prefix); if(strncmp(arg,format_prefix,format_prefix_len) == 0){ OutputFormat format = formats[i].format; /* If the format is SAN, it is possible to supply * a 6-piece suffix listing language-specific * letters to use in the output. */ if((format == SAN) && (strlen(arg) > format_prefix_len)){ set_output_piece_characters(&arg[format_prefix_len]); } return format; } } fprintf(GlobalState.logfile,"Unknown output format %s.\n",arg); return SAN; } /* Which file suffix should be used for this output format. */ const char * output_file_suffix(OutputFormat format) { /* Define a suffix for the output files. */ static const char PGN_suffix[] = ".pgn"; static const char EPD_suffix[] = ".epd"; static const char CM_suffix[] = ".cm"; switch(format){ case SOURCE: case SAN: case LALG: case HALG: return PGN_suffix; case EPD: return EPD_suffix; case CM: return CM_suffix; default: return PGN_suffix; } } static const char * select_tag_string(TagName tag) { const char *tag_string; if((tag == PSEUDO_PLAYER_TAG) || (tag == PSEUDO_ELO_TAG)){ tag_string = NULL; } else{ tag_string = tag_header_string(tag); } return tag_string; } static Boolean is_STR(TagName tag) { switch(tag){ case EVENT_TAG: case SITE_TAG: case DATE_TAG: case ROUND_TAG: case WHITE_TAG: case BLACK_TAG: case RESULT_TAG: return TRUE; default: return FALSE; } } /* Output the tags held in the Tags structure. * At least the full Seven Tag Roster is printed. */ static void output_tag(TagName tag, char **Tags, FILE *outfp) { const char *tag_string; /* Must print STR elements and other non-NULL tags. */ if((is_STR(tag)) || (Tags[tag] != NULL)){ tag_string = select_tag_string(tag); if(tag_string != NULL){ fprintf(outfp,"[%s \"",tag_string); if(Tags[tag] != NULL){ fwrite(Tags[tag],sizeof(char),strlen(Tags[tag]),outfp); } else{ if(tag == DATE_TAG){ fprintf(outfp,"????.??.??"); } else{ fprintf(outfp,"?"); } } fprintf(outfp,"\"]\n"); } } } /* Output the Seven Tag Roster. */ static void output_STR(FILE *outfp,char **Tags) { unsigned tag_index; /* Use the default ordering to ensure that STR is output * in the way it should be. */ for(tag_index = 0; tag_index < 7; tag_index++){ output_tag(DefaultTagOrder[tag_index],Tags,outfp); } } /* Print out on outfp the current details. * These can be used in the case of an error. */ static void show_tags(FILE *outfp,char **Tags,int tags_length) { int tag_index; /* Take a copy of the Tags data, so that we can keep * track of what has been printed. This will make * it possible to print tags that were identified * in the source but are not defined with _TAG values. * See lex.c for how these extra tags are handled. */ char **copy_of_tags = (char **) MallocOrDie(tags_length*sizeof(*copy_of_tags)); int i; for(i = 0; i < tags_length; i++){ copy_of_tags[i] = Tags[i]; } /* Ensure that a tag ordering is available. */ if(TagOrder == NULL){ /* None set by the user - use the default. */ /* Handle the standard tags. * The end of the list is marked with a negative value. */ for(tag_index = 0; DefaultTagOrder[tag_index] >= 0; tag_index++){ TagName tag = DefaultTagOrder[tag_index]; output_tag(tag,copy_of_tags,outfp); copy_of_tags[tag] = (char *)NULL; } /* Handle the extra tags. */ for(tag_index = 0; tag_index < tags_length; tag_index++){ if(copy_of_tags[tag_index] != NULL){ output_tag(tag_index,copy_of_tags,outfp); } } } else{ for(tag_index = 0; TagOrder[tag_index] >= 0; tag_index++){ TagName tag = TagOrder[tag_index]; output_tag(tag,copy_of_tags,outfp); copy_of_tags[tag] = (char *)NULL; } } (void) free(copy_of_tags); putc('\n',outfp); } /* Ensure that there is room for len more characters on the * current line. */ static void check_line_length(FILE *fp,unsigned len) { if((line_length + len) > GlobalState.max_line_length){ putc('\n',fp); line_length = 0; } } /* Print ch to fp and update how much of the line * has been printed on. */ static void single_char(FILE *fp, char ch) { check_line_length(fp,1); putc(ch,fp); line_length++; } /* Print a space, unless at the beginning of a line. */ static void print_separator(FILE *fp) { /* There is no printing a trailing space so allow for at least * one more character on the line. */ check_line_length(fp,2); if(line_length != 0){ putc(' ',fp); line_length++; } } /* Ensure that what comes next starts on a fresh line. */ void terminate_line(FILE *fp) { if(line_length > 0){ putc('\n',fp); line_length = 0; } } /* Print str to fp and update how much of the line * has been printed on. */ void print_str(FILE *fp, const char *str) { unsigned len = strlen(str); check_line_length(fp,len); fprintf(fp,"%s",str); line_length += len; } static void print_comment_list(FILE *fp, CommentList *comment_list) { CommentList *next_comment; for(next_comment = comment_list ; next_comment != NULL; next_comment = next_comment->next){ StringList *comment = next_comment->Comment; if(comment != NULL){ /* We will use strtok to break up the comment string, * with chunk to point to each bit in turn. */ char *chunk; single_char(fp,'{'); for( ; comment != NULL; comment = comment->next){ chunk = strtok(comment->str," "); while(chunk != NULL){ print_separator(fp); print_str(fp,chunk); chunk = strtok((char *)NULL," "); } } print_separator(fp); single_char(fp,'}'); if(next_comment->next != NULL){ print_separator(fp); } } } } static void print_move_list(FILE *outputfile, unsigned move_number, Boolean white_to_move, const Move *move_details,const Board *final_board) { unsigned print_move_number = 1; const Move *move = move_details; while(move != NULL){ /* Reset print_move number if a variation was printed. */ print_move_number = print_move(outputfile,move_number, print_move_number, white_to_move,move); /* See if there is a result attached. This may be attached either * to a move or a comment. */ if(!GlobalState.check_only && (move != NULL) && (move->terminating_result != NULL)){ if(GlobalState.output_FEN_string && final_board != NULL){ print_separator(outputfile); print_str(outputfile,build_FEN_comment(final_board)); } print_separator(outputfile); print_str(outputfile,move->terminating_result); } if(move->move[0] != '\0'){ /* A genuine move was just printed, rather than a comment. */ if(white_to_move){ white_to_move = FALSE; } else{ move_number++; white_to_move = TRUE; } } move = move->next; /* The following is slightly inaccurate. * If the previous value of move was a comment and * we aren't printing comments, then this results in two * separators being printed after the move preceding the comment. * Not sure how to cleanly fix it, because there might have * been nags attached to the comment that were printed, for instance! */ if(move != NULL){ print_separator(outputfile); } } } /* Output the current move along with associated information. * Return TRUE if either a variation or comment was printed, * FALSE otherwise. * This is needed to determine whether a new move number * is to be printed after a variation. */ static Boolean print_move(FILE *outputfile, unsigned move_number, unsigned print_move_number, Boolean white_to_move, const Move *move_details) { Boolean something_printed = FALSE; if(move_details == NULL){ /* Shouldn't happen. */ fprintf(GlobalState.logfile, "Internal error: NULL move in print_move.\n"); report_details(GlobalState.logfile); } else{ if(GlobalState.check_only){ /* Nothing to be output. */ } else{ StringList *nags = move_details->Nags; Variation *variants = move_details->Variants; const unsigned char *move_text = move_details->move; if(*move_text != '\0'){ if(white_to_move || print_move_number){ static char small_number[] = "99999..."; /* @@@ Should 1... be written as 1. ... ? */ sprintf(small_number,"%u.%s",move_number, white_to_move?"":".."); print_str(outputfile,small_number); print_separator(outputfile); } switch(GlobalState.output_format){ case SAN: case SOURCE: /* @@@ move_text should be handled as unsigned * char text, as the source may be 8-bit rather * than 7-bit. */ print_str(outputfile, (const char *) move_text); break; case LALG: case HALG: { char algebraic[MAX_MOVE_LEN+1]; *algebraic = '\0'; switch(move_details->class){ case PAWN_MOVE: case ENPASSANT_PAWN_MOVE: case PIECE_MOVE: case KINGSIDE_CASTLE: case QUEENSIDE_CASTLE: sprintf(algebraic,"%c%c%s%c%c", move_details->from_col, move_details->from_rank, GlobalState.output_format == HALG?"-":"", move_details->to_col, move_details->to_rank); break; case PAWN_MOVE_WITH_PROMOTION: sprintf(algebraic,"%c%c%s%c%c%c", move_details->from_col, move_details->from_rank, GlobalState.output_format == HALG?"-":"", move_details->to_col, move_details->to_rank, promoted_piece_letter(move_details->promoted_piece)); break; case UNKNOWN_MOVE: strcpy(algebraic,"???"); break; } switch(move_details->check_status){ case NOCHECK: break; case CHECK: strcat(algebraic,"+"); break; case CHECKMATE: strcat(algebraic,"#"); break; } print_str(outputfile,algebraic); } break; default: fprintf(GlobalState.logfile, "Unknown output format %d in print_move()\n", GlobalState.output_format); exit(1); break; } } else{ /* An empty move. */ fprintf(GlobalState.logfile, "Internal error: Empty move in print_move.\n"); report_details(GlobalState.logfile); } /* Print further information, that may be attached to moves * and comments. */ if(GlobalState.keep_NAGs){ while(nags != NULL){ print_separator(outputfile); print_str(outputfile,nags->str); nags = nags->next; } /* We don't need to output move numbers after just * NAGs, so don't set something_printed. */ } if(GlobalState.keep_comments){ if(move_details->Comment != NULL){ print_separator(outputfile); print_comment_list(outputfile,move_details->Comment); something_printed = TRUE; } } if((GlobalState.keep_variations) && (variants != NULL)){ while(variants != NULL){ print_separator(outputfile); single_char(outputfile,'('); if(GlobalState.keep_comments && (variants->prefix_comment != NULL)){ print_comment_list(outputfile,variants->prefix_comment); print_separator(outputfile); } /* Always start with a move number. * The final board position is not needed. */ print_move_list(outputfile,move_number, white_to_move,variants->moves, (const Board *)NULL); single_char(outputfile,')'); if(GlobalState.keep_comments && (variants->suffix_comment != NULL)){ print_separator(outputfile); print_comment_list(outputfile,variants->suffix_comment); } variants = variants->next; } something_printed = TRUE; } } } return something_printed; } /* Return the letter associated with the given piece. */ static char promoted_piece_letter(Piece piece) { switch(piece){ case QUEEN: return 'Q'; case ROOK: return 'R'; case BISHOP: return 'B'; case KNIGHT: return 'N'; default: return '?'; } } /* Build a FEN string from the given board. * Currently, the number of half moves since the * last pawn move or capture is not recorded. */ static void build_FEN_string(const Board *board,char *fen) { int ix; build_basic_EPD_string(board,fen); /* Append the (pseudo) half move count and the full move count. */ ix = strlen(fen); fen[ix] = ' '; ix++; /* Half moves since the last capture or pawn move. * @@@ Currently this is not set correctly. */ fen[ix] = '0'; ix++; fen[ix] = ' '; ix++; /* The move number. */ sprintf(&fen[ix],"%u",board->move_number); } static const char *build_FEN_comment(const Board *board) { static char fen[FEN_SPACE]; strcpy(fen,"{ \""); build_FEN_string(board,&fen[strlen(fen)]); strcat(fen,"\" }"); /* A belated sanity check. */ if(strlen(fen) >= FEN_SPACE){ fprintf(GlobalState.logfile, "Internal error: string overflow in build_FEN_string.\n"); } return fen; } /* Output a comment in CM format. */ static void output_cm_comment(CommentList *comment,FILE *outputfile,unsigned indent) { /* Don't indent for the first comment line, because * we should already be positioned at the correct spot. */ unsigned indent_for_this_line = 0; putc(CM_COMMENT_CHAR,outputfile); line_length++; while(comment != NULL){ /* We will use strtok to break up the comment string, * with chunk to point to each bit in turn. */ char *chunk; StringList *comment_str = comment->Comment; for( ; comment_str != NULL; comment_str = comment_str->next){ chunk = strtok(comment_str->str," "); while(chunk != NULL){ unsigned len = strlen(chunk); if((line_length+1+len) > GlobalState.max_line_length){ /* Start a new line. */ indent_for_this_line = indent; fprintf(outputfile,"\n%*s%c ",indent_for_this_line,"", CM_COMMENT_CHAR); line_length = indent_for_this_line+2; } else{ putc(' ',outputfile); line_length++; } fprintf(outputfile,"%s",chunk); line_length += len; chunk = strtok((char *)NULL," "); } } comment = comment->next; } putc('\n',outputfile); line_length = 0; } static void output_cm_result(const char *result,FILE *outputfile) { fprintf(outputfile,"%c ",CM_COMMENT_CHAR); if(strcmp(result,"1-0") == 0){ fprintf(outputfile,"and black resigns"); } else if(strcmp(result,"0-1") == 0){ fprintf(outputfile,"and white resigns"); } else if(strncmp(result,"1/2",3) == 0){ fprintf(outputfile,"draw"); } else{ fprintf(outputfile,"incomplete result"); } } /* Output the game in Chess Master format. */ static void output_cm_game(FILE *outputfile,unsigned move_number, Boolean white_to_move,const Game game) { const Move *move = game.moves; if((move_number != 1) || (!white_to_move)){ fprintf(GlobalState.logfile, "Unable to output CM games other than from the starting position.\n"); report_details(GlobalState.logfile); } fprintf(outputfile,"WHITE: %s\n", game.tags[WHITE_TAG] != NULL? game.tags[WHITE_TAG] : ""); fprintf(outputfile,"BLACK: %s\n", game.tags[BLACK_TAG] != NULL? game.tags[BLACK_TAG] : ""); putc('\n',outputfile); if(game.prefix_comment != NULL){ line_length = 0; output_cm_comment(game.prefix_comment,outputfile,0); } while(move != NULL){ if(move->move[0] != '\0'){ /* A genuine move. */ if(white_to_move){ fprintf(outputfile,"%*u. ",MOVE_NUMBER_WIDTH,move_number); fprintf(outputfile,"%*s",-MOVE_WIDTH,move->move); white_to_move = FALSE; } else{ fprintf(outputfile,"%*s",-MOVE_WIDTH,move->move); move_number++; white_to_move = TRUE; } } if((move->Comment != NULL) && GlobalState.keep_comments){ const char *result = move->terminating_result; if(!white_to_move){ fprintf(outputfile,"%*s",-MOVE_WIDTH,"..."); } line_length = COMMENT_INDENT; output_cm_comment(move->Comment,outputfile,COMMENT_INDENT); if((result != NULL) && (move->check_status != CHECKMATE)){ /* Give some information on the nature of the finish. */ if(white_to_move){ fprintf(outputfile,"%*s",COMMENT_INDENT,""); } else{ /* Print out a string representing the result. */ fprintf(outputfile,"%*s %*s%*s", MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...", MOVE_WIDTH,""); } output_cm_result(result,outputfile); putc('\n',outputfile); } else{ if(!white_to_move){ /* Indicate that the next move is Black's. */ fprintf(outputfile,"%*s %*s", MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"..."); } } } else{ if((move->terminating_result != NULL) && (move->check_status != CHECKMATE)){ /* Give some information on the nature of the finish. */ const char *result = move->terminating_result; if(!white_to_move){ fprintf(outputfile,"%*s",-MOVE_WIDTH,"..."); } output_cm_result(result,outputfile); if(!white_to_move){ putc('\n',outputfile); fprintf(outputfile,"%*s %*s", MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"..."); } putc('\n',outputfile); } else{ if(white_to_move){ /* Terminate the move pair. */ putc('\n',outputfile); } } } move = move->next; } putc('\n',outputfile); } /* Output the current game according to the required output format. */ void output_game(Game current_game,FILE *outputfile) { Boolean white_to_move = TRUE; unsigned move_number = 1; /* The final board position, if available. */ Board *final_board = NULL; /* If we aren't starting from the initial setup, then we * need to know the current move number and whose * move it is. */ if(current_game.tags[FEN_TAG] != NULL){ Board *board = new_fen_board(current_game.tags[FEN_TAG]); if(board != NULL){ move_number = board->move_number; white_to_move = board->to_move == WHITE; (void) free((void *)board); } } /* Start at the beginning of a line. */ line_length = 0; /* See if the moves should be rewritten into * standard form. */ if(GlobalState.output_format != SOURCE){ /* Rewrite the moves of the game into * SAN (Standard Algebraic Notation). */ final_board = rewrite_game(current_game.moves, current_game.tags[FEN_TAG]); } switch(GlobalState.output_format){ case SAN: case SOURCE: case LALG: case HALG: /* Report details on the output. */ if(GlobalState.seven_tag_roster){ output_STR(outputfile,current_game.tags); if(GlobalState.add_ECO){ /* If ECO classification has been requested, then assume * that ECO tags are also required. */ output_tag(ECO_TAG,current_game.tags,outputfile); output_tag(OPENING_TAG,current_game.tags,outputfile); output_tag(VARIATION_TAG,current_game.tags,outputfile); output_tag(SUB_VARIATION_TAG,current_game.tags,outputfile); } /* Output any FEN that there might be. */ output_tag(FEN_TAG,current_game.tags,outputfile); putc('\n',outputfile); } else{ show_tags(outputfile,current_game.tags,current_game.tags_length); } if((GlobalState.keep_comments) && (current_game.prefix_comment != NULL)){ print_comment_list(outputfile, current_game.prefix_comment); terminate_line(outputfile); putc('\n',outputfile); } print_move_list(outputfile,move_number,white_to_move, current_game.moves,final_board); terminate_line(outputfile); putc('\n',outputfile); break; case EPD: print_epd_game(current_game,outputfile,move_number,white_to_move, final_board); break; case CM: output_cm_game(outputfile,move_number,white_to_move,current_game); break; default: fprintf(GlobalState.logfile, "Internal error: unknown output type %d in output_game().\n", GlobalState.output_format); break; } if(final_board != NULL){ (void) free((void *) final_board); } } /* Add the given tag to the output ordering. */ void add_to_output_tag_order(TagName tag) { int tag_index; if(TagOrder == NULL){ tag_order_space = ARRAY_SIZE(DefaultTagOrder); TagOrder = (int *) MallocOrDie(tag_order_space*sizeof(*TagOrder)); /* Always ensure that there is a negative value at the end. */ TagOrder[0] = -1; } /* Check to ensure a position has not already been indicated * for this tag. */ for(tag_index = 0; (TagOrder[tag_index] != -1) && (TagOrder[tag_index] != tag); tag_index++){ } if(TagOrder[tag_index] == -1){ /* Make sure there is enough space for another. */ if(tag_index >= tag_order_space){ /* Allocate some more. */ tag_order_space += 10; TagOrder = (int *) ReallocOrDie((void *)TagOrder, tag_order_space*sizeof(*TagOrder)); } TagOrder[tag_index] = tag; TagOrder[tag_index+1] = -1; } else{ fprintf(GlobalState.logfile,"Duplicate position for tag: %s\n", select_tag_string(tag)); } } static const char *format_epd_game_comment(char **Tags) { static char comment_prefix[] = "c0 "; static char player_separator[] = "-"; static int prefix_and_separator_len = sizeof(comment_prefix)+ sizeof(player_separator); unsigned space_needed = prefix_and_separator_len; char *comment; if(Tags[WHITE_TAG] != NULL){ space_needed += strlen(Tags[WHITE_TAG]); } if(Tags[BLACK_TAG] != NULL){ space_needed += strlen(Tags[BLACK_TAG]); } /* Allow a space character before each of the remaining tags. */ if(Tags[EVENT_TAG] != NULL){ space_needed += 1+strlen(Tags[EVENT_TAG]); } if(Tags[SITE_TAG] != NULL){ space_needed += 1+strlen(Tags[SITE_TAG]); } if(Tags[DATE_TAG] != NULL){ space_needed += 1+strlen(Tags[DATE_TAG]); } /* Allow for a terminating semicolon. */ space_needed++; comment = (char *) MallocOrDie(space_needed+1); strcpy(comment,comment_prefix); if(Tags[WHITE_TAG] != NULL){ strcat(comment,Tags[WHITE_TAG]); } strcat(comment,player_separator); if(Tags[BLACK_TAG] != NULL){ strcat(comment,Tags[BLACK_TAG]); } if(Tags[EVENT_TAG] != NULL){ strcat(comment," "); strcat(comment,Tags[EVENT_TAG]); } if(Tags[SITE_TAG] != NULL){ strcat(comment," "); strcat(comment,Tags[SITE_TAG]); } if(Tags[DATE_TAG] != NULL){ strcat(comment," "); strcat(comment,Tags[DATE_TAG]); } strcat(comment,";"); if(strlen(comment) >= space_needed){ fprintf(GlobalState.logfile, "Internal error: overflow in format_epd_game_comment\n"); } return comment; } static void print_epd_move_list(Game current_game,FILE *outputfile, unsigned move_number, Boolean white_to_move, Board *final_board) { const Move *move = current_game.moves; const char *game_comment = format_epd_game_comment(current_game.tags); static char epd[FEN_SPACE]; while(move != NULL){ if(move->epd != NULL){ fprintf(outputfile,"%s",move->epd); } else{ fprintf(GlobalState.logfile,"Internal error: Missing EPD\n"); } fprintf(outputfile," %s",game_comment); putc('\n',outputfile); move = move->next; } build_basic_EPD_string(final_board,epd); fprintf(outputfile,"%s %s",epd,game_comment); putc('\n',outputfile); (void) free((void *)game_comment); } static void print_epd_game(Game current_game,FILE *outputfile, unsigned move_number,Boolean white_to_move, Board *final_board) { if(!GlobalState.check_only){ print_epd_move_list(current_game,outputfile,move_number,white_to_move, final_board); putc('\n',outputfile); } }