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 #include "farmhash-c.h"
42 /* Functions for outputting games in the required format. */
44 /* Define the width in which to print a CM move and move number. */
45 #define MOVE_NUMBER_WIDTH 3
47 #define CM_COMMENT_CHAR ';'
48 /* Define the width of the moves area before a comment. */
49 #define COMMENT_INDENT (MOVE_NUMBER_WIDTH+2+2*MOVE_WIDTH)
51 /* Define a macro to calculate an array's size. */
52 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(*arr))
54 /* How much text we have output on the current line. */
55 static size_t line_length = 0;
56 /* The buffer in which each output line of a game is built. */
57 static char *output_line = NULL;
59 static Boolean print_move(FILE *outputfile, unsigned move_number,
60 unsigned print_move_number, Boolean white_to_move,
61 const Move *move_details);
62 static void output_STR(FILE *outfp,char **Tags);
63 static void show_tags(FILE *outfp,char **Tags,int tags_length);
64 static char promoted_piece_letter(Piece piece);
65 static void print_algebraic_game(Game current_game,FILE *outputfile,
66 unsigned move_number,Boolean white_to_move,
68 static void output_sesse_bin_game(Game current_game,FILE *outputfile,
69 unsigned move_number,Boolean white_to_move,
71 static void print_epd_game(Game current_game,FILE *outputfile,
72 unsigned move_number,Boolean white_to_move,
74 static void print_epd_move_list(Game current_game,FILE *outputfile,
75 unsigned move_number, Boolean white_to_move,
77 static const char *build_FEN_comment(const Board *board);
78 static void add_total_plycount(const Game *game, Boolean count_variations);
79 static void add_hashcode_tag(const Game *game);
80 static unsigned count_single_move_ply(const Move *move_details, Boolean count_variations);
81 static unsigned count_move_list_ply(Move *move_list, Boolean count_variations);
83 /* List, the order in which the tags should be output.
84 * The first seven should be the Seven Tag Roster that should
85 * be present in every game.
86 * The order of the remainder is, I believe, a matter of taste.
87 * any PSEUDO_*_TAGs should not appear in this list.
90 /* Give the array an int type, because a negative value is
91 * used as a terminator.
93 static int DefaultTagOrder[] = {
94 EVENT_TAG, SITE_TAG, DATE_TAG, ROUND_TAG, WHITE_TAG, BLACK_TAG, RESULT_TAG,
96 /* @@@ Consider omitting some of these from the default ordering,
97 * and allow the output order to be determined from the
100 WHITE_TITLE_TAG, BLACK_TITLE_TAG, WHITE_ELO_TAG, BLACK_ELO_TAG,
101 WHITE_USCF_TAG, BLACK_USCF_TAG,
102 WHITE_TYPE_TAG, BLACK_TYPE_TAG,
103 WHITE_NA_TAG, BLACK_NA_TAG,
104 ECO_TAG, NIC_TAG, OPENING_TAG, VARIATION_TAG, SUB_VARIATION_TAG,
108 EVENT_DATE_TAG, EVENT_SPONSOR_TAG, SECTION_TAG, STAGE_TAG, BOARD_TAG,
109 TIME_TAG, UTC_DATE_TAG, UTC_TIME_TAG,
112 TERMINATION_TAG, MODE_TAG, PLY_COUNT_TAG,
114 /* The final value should be negative. */
118 /* Provision for a user-defined tag ordering.
119 * See add_to_output_tag_order().
120 * Once allocated, the end of the list must be negative.
122 static int *TagOrder = NULL;
123 static int tag_order_space = 0;
126 set_output_line_length(unsigned length)
128 if(output_line != NULL) {
129 (void) free((void *) output_line);
131 output_line = (char *) MallocOrDie(length + 1);
132 GlobalState.max_line_length = length;
135 /* Which output format does the user require, based upon the
136 * given command line argument?
139 which_output_format(const char *arg)
158 { "sessebin", SESSE_BIN },
160 /* Add others before the terminating NULL. */
161 { (const char *) NULL, SAN }
164 for(i = 0; formats[i].arg != NULL; i++){
165 const char *format_prefix = formats[i].arg;
166 const size_t format_prefix_len = strlen(format_prefix);
167 if(strncmp(arg,format_prefix,format_prefix_len) == 0){
168 OutputFormat format = formats[i].format;
170 if(*format_prefix == '\0' && *arg != '\0') {
171 fprintf(GlobalState.logfile,
172 "Unknown output format %s.\n", arg);
175 /* If the format is SAN, it is possible to supply
176 * a 6-piece suffix listing language-specific
177 * letters to use in the output.
179 if((format == SAN || format == ELALG) &&
180 (strlen(arg) > format_prefix_len)){
181 set_output_piece_characters(&arg[format_prefix_len]);
186 fprintf(GlobalState.logfile,"Unknown output format %s.\n",arg);
190 /* Which file suffix should be used for this output format. */
192 output_file_suffix(OutputFormat format)
194 /* Define a suffix for the output files. */
195 static const char PGN_suffix[] = ".pgn";
196 static const char EPD_suffix[] = ".epd";
197 static const char CM_suffix[] = ".cm";
198 static const char BIN_suffix[] = ".bin";
219 select_tag_string(TagName tag)
220 { const char *tag_string;
222 if((tag == PSEUDO_PLAYER_TAG) || (tag == PSEUDO_ELO_TAG) || (tag == PSEUDO_FEN_PATTERN_TAG)){
226 tag_string = tag_header_string(tag);
248 /* Output the tags held in the Tags structure.
249 * At least the full Seven Tag Roster is printed.
252 output_tag(TagName tag, char **Tags, FILE *outfp)
253 { const char *tag_string;
255 /* Must print STR elements and other non-NULL tags. */
256 if((is_STR(tag)) || (Tags[tag] != NULL)){
257 tag_string = select_tag_string(tag);
259 if(tag_string != NULL){
260 fprintf(outfp,"[%s \"",tag_string);
261 if(Tags[tag] != NULL){
262 fwrite(Tags[tag],sizeof(char),strlen(Tags[tag]),outfp);
266 fprintf(outfp,"????.??.??");
272 fprintf(outfp,"\"]\n");
277 /* Output the Seven Tag Roster. */
279 output_STR(FILE *outfp,char **Tags)
280 { unsigned tag_index;
282 /* Use the default ordering to ensure that STR is output
283 * in the way it should be.
285 for(tag_index = 0; tag_index < 7; tag_index++){
286 output_tag(DefaultTagOrder[tag_index],Tags,outfp);
290 /* Print out on outfp the current details.
291 * These can be used in the case of an error.
294 show_tags(FILE *outfp,char **Tags,int tags_length)
296 /* Take a copy of the Tags data, so that we can keep
297 * track of what has been printed. This will make
298 * it possible to print tags that were identified
299 * in the source but are not defined with _TAG values.
300 * See lex.c for how these extra tags are handled.
302 char **copy_of_tags =
303 (char **) MallocOrDie(tags_length*sizeof(*copy_of_tags));
305 for(i = 0; i < tags_length; i++){
306 copy_of_tags[i] = Tags[i];
309 /* Ensure that a tag ordering is available. */
310 if(TagOrder == NULL){
311 /* None set by the user - use the default. */
312 /* Handle the standard tags.
313 * The end of the list is marked with a negative value.
315 for(tag_index = 0; DefaultTagOrder[tag_index] >= 0; tag_index++){
316 TagName tag = DefaultTagOrder[tag_index];
317 output_tag(tag,copy_of_tags,outfp);
318 copy_of_tags[tag] = (char *)NULL;
320 /* Handle the extra tags. */
321 for(tag_index = 0; tag_index < tags_length; tag_index++){
322 if(copy_of_tags[tag_index] != NULL){
323 output_tag(tag_index,copy_of_tags,outfp);
328 for(tag_index = 0; TagOrder[tag_index] >= 0; tag_index++){
329 TagName tag = TagOrder[tag_index];
330 output_tag(tag,copy_of_tags,outfp);
331 copy_of_tags[tag] = (char *)NULL;
334 (void) free(copy_of_tags);
338 /* Ensure that there is room for len more characters on the
342 check_line_length(FILE *fp,size_t len)
344 if((line_length + len) > GlobalState.max_line_length){
349 /* Print ch to fp and update how much of the line
350 * has been printed on.
353 single_char(FILE *fp, char ch)
355 check_line_length(fp,1);
356 output_line[line_length] = ch;
360 /* Print a space, unless at the beginning of a line. */
362 print_separator(FILE *fp)
364 /* Lines shouldn't have trailing spaces, so ensure that there
365 * will be room for at least one more character after the space.
367 check_line_length(fp,2);
368 if(line_length != 0){
369 output_line[line_length] = ' ';
374 /* Ensure that what comes next starts on a fresh line. */
376 terminate_line(FILE *fp)
378 /* Delete any trailing space(s). */
379 while(line_length >= 1 && output_line[line_length - 1] == ' ') {
382 if(line_length > 0) {
383 output_line[line_length] = '\0';
384 fprintf(fp, "%s\n", output_line);
389 /* Print str to fp and update how much of the line
390 * has been printed on.
393 print_str(FILE *fp, const char *str)
394 { size_t len = strlen(str);
396 check_line_length(fp,len);
397 if(len > GlobalState.max_line_length) {
398 fprintf(GlobalState.logfile, "String length %lu is too long for the line length of %lu:\n",
400 (unsigned long) GlobalState.max_line_length);
401 fprintf(GlobalState.logfile, "%s\n", str);
402 report_details(GlobalState.logfile);
403 fprintf(fp, "%s\n", str);
406 sprintf(&(output_line[line_length]),"%s",str);
412 print_comment_list(FILE *fp, CommentList *comment_list)
413 { CommentList *next_comment;
415 for(next_comment = comment_list ; next_comment != NULL;
416 next_comment = next_comment->next){
417 StringList *comment = next_comment->Comment;
420 /* We will use strtok to break up the comment string,
421 * with chunk to point to each bit in turn.
426 for( ; comment != NULL; comment = comment->next){
427 /* Make a copy because the string is altered. */
428 char *str = copy_string(comment->str);
429 chunk = strtok(str," ");
430 while(chunk != NULL){
433 chunk = strtok((char *)NULL," ");
435 (void) free((void *) str);
439 if(next_comment->next != NULL){
447 print_move_list(FILE *outputfile, unsigned move_number, Boolean white_to_move,
448 const Move *move_details,const Board *final_board)
449 { unsigned print_move_number = 1;
450 const Move *move = move_details;
451 Boolean keepPrinting;
454 /* Work out the ply depth. */
455 plies = 2*(move_number) - 1;
459 if(GlobalState.output_ply_limit >= 0 &&
460 plies > GlobalState.output_ply_limit) {
461 keepPrinting = FALSE;
467 while(move != NULL && keepPrinting){
468 /* Reset print_move number if a variation was printed. */
469 print_move_number = print_move(outputfile,move_number,
473 /* See if there is a result attached. This may be attached either
474 * to a move or a comment.
476 if(!GlobalState.check_only && (move != NULL) &&
477 (move->terminating_result != NULL)){
478 if(GlobalState.output_FEN_string && final_board != NULL){
479 print_separator(outputfile);
480 print_str(outputfile,build_FEN_comment(final_board));
482 if(GlobalState.keep_results) {
483 print_separator(outputfile);
484 print_str(outputfile,move->terminating_result);
487 if(move->move[0] != '\0'){
488 /* A genuine move was just printed, rather than a comment. */
490 white_to_move = FALSE;
494 white_to_move = TRUE;
497 if(GlobalState.output_ply_limit >= 0 &&
498 plies > GlobalState.output_ply_limit) {
499 keepPrinting = FALSE;
503 /* The following is slightly inaccurate.
504 * If the previous value of move was a comment and
505 * we aren't printing comments, then this results in two
506 * separators being printed after the move preceding the comment.
507 * Not sure how to cleanly fix it, because there might have
508 * been nags attached to the comment that were printed, for instance!
510 if(move != NULL && keepPrinting){
511 print_separator(outputfile);
515 if(move != NULL && !keepPrinting) {
516 /* We ended printing the game prematurely. */
519 * Decide whether to print a result indicator.
521 if(GlobalState.keep_results) {
522 /* Find the final move to see if there was a result there. */
523 while(move->next != NULL) {
526 if(move->terminating_result != NULL) {
527 print_separator(outputfile);
528 print_str(outputfile, "*");
534 /* Output the current move along with associated information.
535 * Return TRUE if either a variation or comment was printed,
537 * This is needed to determine whether a new move number
538 * is to be printed after a variation.
541 print_move(FILE *outputfile, unsigned move_number, unsigned print_move_number,
542 Boolean white_to_move, const Move *move_details)
543 { Boolean something_printed = FALSE;
545 if(move_details == NULL){
546 /* Shouldn't happen. */
547 fprintf(GlobalState.logfile,
548 "Internal error: NULL move in print_move.\n");
549 report_details(GlobalState.logfile);
552 if(GlobalState.check_only){
553 /* Nothing to be output. */
556 StringList *nags = move_details->Nags;
557 Variation *variants = move_details->Variants;
558 const unsigned char *move_text = move_details->move;
560 if(*move_text != '\0'){
561 if(GlobalState.keep_move_numbers &&
562 (white_to_move || print_move_number)){
563 static char small_number[] = "99999...";
565 /* @@@ Should 1... be written as 1. ... ? */
566 sprintf(small_number,"%u.%s",move_number, white_to_move?"":"..");
567 print_str(outputfile,small_number);
568 print_separator(outputfile);
570 switch(GlobalState.output_format){
573 /* @@@ move_text should be handled as unsigned
574 * char text, as the source may be 8-bit rather
577 if(GlobalState.keep_checks) {
578 print_str(outputfile, (const char *) move_text);
581 /* Look for a check or mate symbol. */
582 char *check = strchr((const char *) move_text, '+');
584 check = strchr((const char *) move_text, '#');
587 /* We need to drop it from move_text. */
588 int len = check - ((char *) move_text);
589 char *just_move = (char *) MallocOrDie(len + 1);
590 strncpy(just_move, (const char *) move_text, len);
591 just_move[len] = '\0';
592 print_str(outputfile, just_move);
593 (void) free(just_move);
596 print_str(outputfile, (const char *) move_text);
601 { char algebraic[MAX_MOVE_LEN+1];
604 switch(move_details->class){
606 case ENPASSANT_PAWN_MOVE:
607 case KINGSIDE_CASTLE:
608 case QUEENSIDE_CASTLE:
610 sprintf(algebraic,"%c%c-%c%c",
611 move_details->from_col,
612 move_details->from_rank,
613 move_details->to_col,
614 move_details->to_rank);
616 case PAWN_MOVE_WITH_PROMOTION:
617 sprintf(algebraic,"%c%c-%c%c%c",
618 move_details->from_col,
619 move_details->from_rank,
620 move_details->to_col,
621 move_details->to_rank,
622 promoted_piece_letter(move_details->promoted_piece));
625 strcpy(algebraic, NULL_MOVE_STRING);
628 strcpy(algebraic,"???");
631 if(GlobalState.keep_checks) {
632 switch(move_details->check_status){
636 strcat(algebraic,"+");
639 strcat(algebraic,"#");
643 print_str(outputfile,algebraic);
648 { char algebraic[MAX_MOVE_LEN+1];
651 /* Prefix with a piece name if ELALG. */
652 if(GlobalState.output_format == ELALG &&
653 move_details->class == PIECE_MOVE){
655 piece_str(move_details->piece_to_move));
656 ind = strlen(algebraic);
658 /* Format the basics. */
659 if(move_details->class != NULL_MOVE) {
660 sprintf(&algebraic[ind],"%c%c%c%c",
661 move_details->from_col,
662 move_details->from_rank,
663 move_details->to_col,
664 move_details->to_rank);
668 strcpy(algebraic, NULL_MOVE_STRING);
669 ind += strlen(NULL_MOVE_STRING);
671 switch(move_details->class){
673 case KINGSIDE_CASTLE:
674 case QUEENSIDE_CASTLE:
677 /* Nothing more to do at this stage. */
679 case ENPASSANT_PAWN_MOVE:
680 if(GlobalState.output_format == ELALG){
681 strcat(algebraic,"ep");
685 case PAWN_MOVE_WITH_PROMOTION:
686 sprintf(&algebraic[ind],"%s",
687 piece_str(move_details->promoted_piece));
688 ind = strlen(algebraic);
691 strcpy(algebraic,"???");
695 if(GlobalState.keep_checks) {
696 switch(move_details->check_status){
700 strcat(algebraic,"+");
704 strcat(algebraic,"#");
709 print_str(outputfile,algebraic);
713 fprintf(GlobalState.logfile,
714 "Unknown output format %d in print_move()\n",
715 GlobalState.output_format);
722 fprintf(GlobalState.logfile,
723 "Internal error: Empty move in print_move.\n");
724 report_details(GlobalState.logfile);
726 /* Print further information, that may be attached to moves
729 if(GlobalState.keep_NAGs){
731 print_separator(outputfile);
732 print_str(outputfile,nags->str);
735 /* We don't need to output move numbers after just
736 * NAGs, so don't set something_printed.
739 if(GlobalState.keep_comments){
740 if(move_details->Comment != NULL){
741 print_separator(outputfile);
742 print_comment_list(outputfile,move_details->Comment);
743 something_printed = TRUE;
746 if((GlobalState.keep_variations) && (variants != NULL)){
747 while(variants != NULL){
748 print_separator(outputfile);
749 single_char(outputfile,'(');
750 if(GlobalState.keep_comments &&
751 (variants->prefix_comment != NULL)){
752 print_comment_list(outputfile,variants->prefix_comment);
753 print_separator(outputfile);
755 /* Always start with a move number.
756 * The final board position is not needed.
758 print_move_list(outputfile,move_number,
759 white_to_move,variants->moves,
760 (const Board *)NULL);
761 single_char(outputfile,')');
762 if(GlobalState.keep_comments &&
763 (variants->suffix_comment != NULL)){
764 print_separator(outputfile);
765 print_comment_list(outputfile,variants->suffix_comment);
767 variants = variants->next;
769 something_printed = TRUE;
773 return something_printed;
776 /* Return the letter associated with the given piece. */
778 promoted_piece_letter(Piece piece)
794 /* Output a comment in CM format. */
796 output_cm_comment(CommentList *comment,FILE *outputfile,unsigned indent)
797 { /* Don't indent for the first comment line, because
798 * we should already be positioned at the correct spot.
800 unsigned indent_for_this_line = 0;
802 putc(CM_COMMENT_CHAR,outputfile);
804 while(comment != NULL){
805 /* We will use strtok to break up the comment string,
806 * with chunk to point to each bit in turn.
809 StringList *comment_str = comment->Comment;
811 for( ; comment_str != NULL; comment_str = comment_str->next){
812 char *str = copy_string(comment_str->str);
813 chunk = strtok(str," ");
814 while(chunk != NULL){
815 size_t len = strlen(chunk);
817 if((line_length+1+len) > GlobalState.max_line_length){
818 /* Start a new line. */
819 indent_for_this_line = indent;
820 fprintf(outputfile,"\n%*s%c ",indent_for_this_line,"",
822 line_length = indent_for_this_line+2;
825 putc(' ',outputfile);
828 fprintf(outputfile,"%s",chunk);
830 chunk = strtok((char *)NULL," ");
832 (void) free((void *) str);
834 comment = comment->next;
836 putc('\n',outputfile);
841 output_cm_result(const char *result,FILE *outputfile)
843 fprintf(outputfile,"%c ",CM_COMMENT_CHAR);
844 if(strcmp(result,"1-0") == 0){
845 fprintf(outputfile,"and black resigns");
847 else if(strcmp(result,"0-1") == 0){
848 fprintf(outputfile,"and white resigns");
850 else if(strncmp(result,"1/2",3) == 0){
851 fprintf(outputfile,"draw");
854 fprintf(outputfile,"incomplete result");
859 /* Output the game in Chess Master format. */
861 output_cm_game(FILE *outputfile,unsigned move_number,
862 Boolean white_to_move,const Game game)
863 { const Move *move = game.moves;
865 if((move_number != 1) || (!white_to_move)){
866 fprintf(GlobalState.logfile,
867 "Unable to output CM games other than from the starting position.\n");
868 report_details(GlobalState.logfile);
870 fprintf(outputfile,"WHITE: %s\n",
871 game.tags[WHITE_TAG] != NULL? game.tags[WHITE_TAG] : "");
872 fprintf(outputfile,"BLACK: %s\n",
873 game.tags[BLACK_TAG] != NULL? game.tags[BLACK_TAG] : "");
874 putc('\n',outputfile);
876 if(game.prefix_comment != NULL){
878 output_cm_comment(game.prefix_comment,outputfile,0);
881 if(move->move[0] != '\0'){
882 /* A genuine move. */
884 fprintf(outputfile,"%*u. ",MOVE_NUMBER_WIDTH,move_number);
885 fprintf(outputfile,"%*s",-MOVE_WIDTH,move->move);
886 white_to_move = FALSE;
889 fprintf(outputfile,"%*s",-MOVE_WIDTH,move->move);
891 white_to_move = TRUE;
894 if((move->Comment != NULL) && GlobalState.keep_comments){
895 const char *result = move->terminating_result;
898 fprintf(outputfile,"%*s",-MOVE_WIDTH,"...");
900 line_length = COMMENT_INDENT;
901 output_cm_comment(move->Comment,outputfile,COMMENT_INDENT);
902 if((result != NULL) && (move->check_status != CHECKMATE)){
903 /* Give some information on the nature of the finish. */
905 fprintf(outputfile,"%*s",COMMENT_INDENT,"");
908 /* Print out a string representing the result. */
909 fprintf(outputfile,"%*s %*s%*s",
910 MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...",
913 output_cm_result(result,outputfile);
914 putc('\n',outputfile);
918 /* Indicate that the next move is Black's. */
919 fprintf(outputfile,"%*s %*s",
920 MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...");
925 if((move->terminating_result != NULL) &&
926 (move->check_status != CHECKMATE)){
927 /* Give some information on the nature of the finish. */
928 const char *result = move->terminating_result;
931 fprintf(outputfile,"%*s",-MOVE_WIDTH,"...");
933 output_cm_result(result,outputfile);
935 putc('\n',outputfile);
936 fprintf(outputfile,"%*s %*s",
937 MOVE_NUMBER_WIDTH+1,"",-MOVE_WIDTH,"...");
939 putc('\n',outputfile);
943 /* Terminate the move pair. */
944 putc('\n',outputfile);
950 putc('\n',outputfile);
953 /* Output the current game according to the required output format. */
955 output_game(Game current_game,FILE *outputfile)
956 { Boolean white_to_move = TRUE;
957 unsigned move_number = 1;
958 /* The final board position, if available. */
959 Board *final_board = NULL;
961 /* If we aren't starting from the initial setup, then we
962 * need to know the current move number and whose
965 if(current_game.tags[FEN_TAG] != NULL){
966 Board *board = new_fen_board(current_game.tags[FEN_TAG]);
969 move_number = board->move_number;
970 white_to_move = board->to_move == WHITE;
971 (void) free((void *)board);
975 /* Start at the beginning of a line. */
977 /* See if the moves should be rewritten into
980 if(GlobalState.output_format != SOURCE){
981 /* Rewrite the moves of the game into
982 * SAN (Standard Algebraic Notation).
984 final_board = rewrite_game(¤t_game);
986 if(final_board != NULL) {
987 if(GlobalState.output_total_plycount) {
988 add_total_plycount(¤t_game, GlobalState.keep_variations);
990 if(GlobalState.add_hashcode_tag || current_game.tags[HASHCODE_TAG] != NULL) {
991 add_hashcode_tag(¤t_game);
993 switch(GlobalState.output_format){
999 print_algebraic_game(current_game,outputfile,move_number,white_to_move,
1003 print_epd_game(current_game,outputfile,move_number,white_to_move,
1007 output_cm_game(outputfile,move_number,white_to_move,current_game);
1010 output_sesse_bin_game(current_game,outputfile,move_number,white_to_move,
1014 fprintf(GlobalState.logfile,
1015 "Internal error: unknown output type %d in output_game().\n",
1016 GlobalState.output_format);
1020 (void) free((void *) final_board);
1024 /* Add the given tag to the output ordering. */
1026 add_to_output_tag_order(TagName tag)
1029 if(TagOrder == NULL){
1030 tag_order_space = ARRAY_SIZE(DefaultTagOrder);
1031 TagOrder = (int *) MallocOrDie(tag_order_space*sizeof(*TagOrder));
1032 /* Always ensure that there is a negative value at the end. */
1035 /* Check to ensure a position has not already been indicated
1038 for(tag_index = 0; (TagOrder[tag_index] != -1) &&
1039 (TagOrder[tag_index] != (int) tag); tag_index++){
1042 if(TagOrder[tag_index] == -1){
1043 /* Make sure there is enough space for another. */
1044 if(tag_index >= tag_order_space){
1045 /* Allocate some more. */
1046 tag_order_space += 10;
1047 TagOrder = (int *) ReallocOrDie((void *)TagOrder,
1048 tag_order_space*sizeof(*TagOrder));
1050 TagOrder[tag_index] = tag;
1051 TagOrder[tag_index+1] = -1;
1054 fprintf(GlobalState.logfile,"Duplicate position for tag: %s\n",
1055 select_tag_string(tag));
1059 static const char *format_epd_game_comment(char **Tags)
1061 static char comment_prefix[] = "c0 ";
1062 static char player_separator[] = "-";
1063 static size_t prefix_and_separator_len =
1064 sizeof(comment_prefix)+sizeof(player_separator);
1065 size_t space_needed = prefix_and_separator_len;
1068 if(Tags[WHITE_TAG] != NULL){
1069 space_needed += strlen(Tags[WHITE_TAG]);
1071 if(Tags[BLACK_TAG] != NULL){
1072 space_needed += strlen(Tags[BLACK_TAG]);
1074 /* Allow a space character before each of the remaining tags. */
1075 if(Tags[EVENT_TAG] != NULL){
1076 space_needed += 1+strlen(Tags[EVENT_TAG]);
1078 if(Tags[SITE_TAG] != NULL){
1079 space_needed += 1+strlen(Tags[SITE_TAG]);
1081 if(Tags[DATE_TAG] != NULL){
1082 space_needed += 1+strlen(Tags[DATE_TAG]);
1084 /* Allow for a terminating semicolon. */
1087 comment = (char *) MallocOrDie(space_needed+1);
1089 strcpy(comment,comment_prefix);
1090 if(Tags[WHITE_TAG] != NULL){
1091 strcat(comment,Tags[WHITE_TAG]);
1093 strcat(comment,player_separator);
1094 if(Tags[BLACK_TAG] != NULL){
1095 strcat(comment,Tags[BLACK_TAG]);
1097 if(Tags[EVENT_TAG] != NULL){
1098 strcat(comment," ");
1099 strcat(comment,Tags[EVENT_TAG]);
1101 if(Tags[SITE_TAG] != NULL){
1102 strcat(comment," ");
1103 strcat(comment,Tags[SITE_TAG]);
1105 if(Tags[DATE_TAG] != NULL){
1106 strcat(comment," ");
1107 strcat(comment,Tags[DATE_TAG]);
1109 strcat(comment,";");
1110 if(strlen(comment) >= space_needed){
1111 fprintf(GlobalState.logfile,
1112 "Internal error: overflow in format_epd_game_comment\n");
1118 print_algebraic_game(Game current_game,FILE *outputfile,
1119 unsigned move_number,Boolean white_to_move,
1122 /* Report details on the output. */
1123 if(GlobalState.tag_output_format == ALL_TAGS) {
1124 show_tags(outputfile,current_game.tags,current_game.tags_length);
1126 else if(GlobalState.tag_output_format == SEVEN_TAG_ROSTER){
1127 output_STR(outputfile,current_game.tags);
1128 if(GlobalState.add_ECO && !GlobalState.parsing_ECO_file){
1129 /* If ECO classification has been requested, then assume
1130 * that ECO tags are also required.
1132 output_tag(ECO_TAG,current_game.tags,outputfile);
1133 output_tag(OPENING_TAG,current_game.tags,outputfile);
1134 output_tag(VARIATION_TAG,current_game.tags,outputfile);
1135 output_tag(SUB_VARIATION_TAG,current_game.tags,outputfile);
1138 /* Output any FEN that there might be. */
1139 output_tag(FEN_TAG,current_game.tags,outputfile);
1140 putc('\n',outputfile);
1142 else if(GlobalState.tag_output_format == NO_TAGS) {
1145 fprintf(GlobalState.logfile,
1146 "Unknown output form for tags: %d\n",
1147 GlobalState.tag_output_format);
1150 if((GlobalState.keep_comments) &&
1151 (current_game.prefix_comment != NULL)){
1152 print_comment_list(outputfile,
1153 current_game.prefix_comment);
1154 terminate_line(outputfile);
1155 putc('\n',outputfile);
1157 print_move_list(outputfile,move_number,white_to_move,
1158 current_game.moves,final_board);
1159 /* Take account of a possible zero move game. */
1160 if(current_game.moves == NULL) {
1161 if(current_game.tags[RESULT_TAG] != NULL) {
1162 print_str(outputfile,current_game.tags[RESULT_TAG]);
1165 fprintf(GlobalState.logfile,
1166 "Internal error: Zero move game with no result\n");
1169 terminate_line(outputfile);
1170 putc('\n',outputfile);
1174 output_sesse_bin_game(Game current_game,FILE *outputfile,
1175 unsigned move_number,Boolean white_to_move,
1178 const char *result = NULL;
1181 // Find the result. Skip games with no result.
1182 for (move = current_game.moves; move != NULL; move = move->next) {
1183 if (move->terminating_result) {
1184 result = move->terminating_result;
1187 if (result == NULL || strcmp(result, "*") == 0) {
1191 int result_int = -1;
1192 if (strcmp(result, "1-0") == 0) {
1194 } else if (strcmp(result, "1/2-1/2") == 0) {
1196 } else if (strcmp(result, "0-1") == 0) {
1199 fprintf(stderr, "Unknown result '%s'\n", result);
1203 // Find Black and White Elos.
1204 const char *white_elo_tag = current_game.tags[WHITE_ELO_TAG];
1205 const char *black_elo_tag = current_game.tags[BLACK_ELO_TAG];
1206 int white_elo = 0, black_elo = 0;
1207 if (white_elo_tag) {
1208 white_elo = atoi(white_elo_tag);
1210 if (black_elo_tag) {
1211 black_elo = atoi(black_elo_tag);
1213 int file_num = current_game.file_number;
1214 long start_position = current_game.start_position;
1216 // Parse date and time, if it exists. Set invalid dates to year 3000.
1217 const char *date_tag = current_game.tags[DATE_TAG];
1218 const char *time_tag = current_game.tags[TIME_TAG];
1221 int year, month, day;
1222 if (date_tag && sscanf(date_tag, "%u.%u.%u", &year, &month, &day) == 3) {
1223 int hour, minute, second;
1224 tm.tm_year = year - 1900;
1225 tm.tm_mon = month - 1;
1228 if (time_tag && sscanf(time_tag, "%u:%u:%u", &hour, &minute, &second) == 3) {
1233 timestamp = mktime(&tm);
1235 timestamp = 32503680000;
1238 uint16_t prev_board_hash = 0;
1239 unsigned int opening;
1241 for (move = current_game.moves; move != NULL; move = move->next) {
1242 opening = move->eco ? move->eco->cumulative_hash_value : 0; // Truncate to 32 bits.
1245 putc(move->bpfen_len + sizeof(prev_board_hash), outputfile);
1246 fwrite(move->bpfen, move->bpfen_len, 1, outputfile);
1247 fwrite(&prev_board_hash, sizeof(prev_board_hash), 1, outputfile);
1250 putc(result_int, outputfile);
1251 fwrite(&white_elo, sizeof(white_elo), 1, outputfile);
1252 fwrite(&black_elo, sizeof(black_elo), 1, outputfile);
1253 fwrite(&opening, sizeof(opening), 1, outputfile);
1254 fwrite(×tamp, sizeof(timestamp), 1, outputfile);
1255 fwrite(&file_num, sizeof(file_num), 1, outputfile);
1256 fwrite(&start_position, sizeof(start_position), 1, outputfile);
1257 putc(strlen((char *)move->move), outputfile);
1258 fwrite(move->move, strlen((char *)move->move), 1, outputfile);
1260 prev_board_hash = farmhash_32(move->bpfen, move->bpfen_len);
1266 build_BPFEN_string(final_board, &bpfen, &bpfen_len);
1269 putc(bpfen_len + sizeof(prev_board_hash), outputfile);
1270 fwrite(bpfen, bpfen_len, 1, outputfile);
1271 fwrite(&prev_board_hash, sizeof(prev_board_hash), 1, outputfile);
1274 putc(result_int, outputfile);
1275 fwrite(&white_elo, sizeof(white_elo), 1, outputfile);
1276 fwrite(&black_elo, sizeof(black_elo), 1, outputfile);
1277 fwrite(&opening, sizeof(opening), 1, outputfile); // Not perfect, but should be OK.
1278 fwrite(×tamp, sizeof(timestamp), 1, outputfile);
1279 fwrite(&file_num, sizeof(file_num), 1, outputfile);
1280 fwrite(&start_position, sizeof(start_position), 1, outputfile);
1281 putc(0, outputfile); // No move.
1287 print_epd_move_list(Game current_game,FILE *outputfile,
1288 unsigned move_number, Boolean white_to_move,
1290 { const Move *move = current_game.moves;
1291 const char *game_comment = format_epd_game_comment(current_game.tags);
1292 static char epd[FEN_SPACE];
1294 while(move != NULL){
1295 if(move->epd != NULL){
1296 fprintf(outputfile,"%s",move->epd);
1299 fprintf(GlobalState.logfile,"Internal error: Missing EPD\n");
1301 fprintf(outputfile," %s",game_comment);
1302 putc('\n',outputfile);
1305 if(final_board != NULL) {
1306 build_basic_EPD_string(final_board,epd);
1307 fprintf(outputfile,"%s %s",epd,game_comment);
1308 putc('\n',outputfile);
1310 (void) free((void *)game_comment);
1314 print_epd_game(Game current_game,FILE *outputfile,
1315 unsigned move_number,Boolean white_to_move,
1318 if(!GlobalState.check_only){
1319 print_epd_move_list(current_game,outputfile,move_number,white_to_move,
1321 putc('\n',outputfile);
1326 * Build a comment containing a FEN representation of the board.
1327 * NB: This function re-uses static space each time it is called
1328 * so a later call will overwrite an earlier call.
1331 build_FEN_comment(const Board *board)
1333 static char fen[FEN_SPACE];
1335 build_FEN_string(board,&fen[strlen(fen)]);
1337 /* A belated sanity check. */
1338 if(strlen(fen) >= FEN_SPACE){
1339 fprintf(GlobalState.logfile,
1340 "Internal error: string overflow in build_FEN_comment.\n");
1346 * Count how many ply recorded for the given move.
1347 * Include variations if count_variations.
1349 static unsigned count_single_move_ply(const Move *move_details, Boolean count_variations)
1352 if(count_variations) {
1353 Variation *variant = move_details->Variants;
1354 while(variant != NULL) {
1355 count += count_move_list_ply(variant->moves, count_variations);
1356 variant = variant->next;
1363 * Count how many plies in the game in total.
1364 * Include variations if count_variations.
1366 static unsigned count_move_list_ply(Move *move_list, Boolean count_variations)
1369 while(move_list != NULL) {
1370 count += count_single_move_ply(move_list, count_variations);
1371 move_list = move_list->next;
1377 * Count how many plies in the game in total.
1378 * Include variations if count_variations.
1380 static void add_total_plycount(const Game *game, Boolean count_variations)
1382 unsigned count = count_move_list_ply(game->moves, count_variations);
1383 char formatted_count[20];
1384 sprintf(formatted_count, "%u", count);
1386 if(game->tags[TOTAL_PLY_COUNT_TAG] != NULL) {
1387 (void) free(game->tags[TOTAL_PLY_COUNT_TAG]);
1389 game->tags[TOTAL_PLY_COUNT_TAG] = copy_string(formatted_count);
1393 * Include a tag containing a hashcode for the game.
1394 * Use the cumulative hash value.
1396 static void add_hashcode_tag(const Game *game)
1398 HashCode hashcode = game->cumulative_hash_value;
1399 char formatted_code[20];
1400 sprintf(formatted_code, "%08x", (unsigned) hashcode);
1402 if(game->tags[HASHCODE_TAG] != NULL) {
1403 (void) free(game->tags[HASHCODE_TAG]);
1405 game->tags[HASHCODE_TAG] = copy_string(formatted_code);