// pgn.c Freeware program by Theo van der Storm, thstorm@compuserve.com // Derive tournament ranking from PGN file. // Assumption: PGN game headers contain properly quoted "values". // Usage: pgn pgnfile [event] // This version has improvements due to Tim Foden /* Example PGN game: [Event "First Dutch CC Game"] [Site "Telephone NED"] [Date "1978.06.23"] [Round "01"] [White "IGM"] [Black "Schak-123"] [Result "1-0"] 1.Nf3 { surprise } e6 { no hurry } 2.Ng1 e5 3.d4 f6 4.dxe5 fxe5 5.e4 Nc6 6.Be3 Rb8 7.Bb5 Qe7 8.Bxc6 a6 9.Qh5+ Kd8 10.Bd5 Nf6 11.Qf5 d6 12.Qg5 Bd7 13.Ne2 Rc8 14.Bxb7 a5 15.Bxc8 Kxc8 16.Nd2 Rg8 17.O-O-O c6 18.Rde1 Be6 19.c4 Qf7 20.b3 Be7 21.Qg3 Rf8 22.c5 dxc5 23.Qxe5 Rg8 24.Bxc5 Rf8 25.Bxe7 Qxe7 26.Qxa5 Rg8 27.e5 Nh5 28.Rd1 Rf8 29.f3 Rg8 30.Rde1 Rf8 31.Rd1 Rg8 32.Kb2 Rf8 33.Rhe1 Rg8 34.Rc1 Rf8 35.Rxc6+ Kb8 36.Qb6+ 1-0 */ #include #include #include #define LINE_SIZE 150 #define NAME_SIZE 32 #define PLAYERS 5000 #define GAMES 3000000 static char *event = NULL; static int pl_nr = 0; static short pl_index[PLAYERS + 1]; static struct player { short rank; short games; // #Games played short score; // Number of points scored short buchh; // Sum of opponent's points short sonnb; // Weighted sum of opponent's points char name[NAME_SIZE]; } pl[PLAYERS]; static int gam_nr = 0; static struct results { short w, b; // white and black index in pl[] short r; // r = 0, 1 or 2 for 0-1, draw or 1-0 resp. } gam[GAMES]; static struct pgn_head { char event[64]; char site[64]; char date[64]; char round[64]; char white[64]; char black[64]; char result[64]; } cur_head; int add_pl(char *p) // Add player with name p to pl[] table and return the index. { int i; for (i = 0; i < pl_nr; i++) if (!stricmp(pl[i].name, p)) return i; // Player already known i = pl_nr++; if (i >= PLAYERS) { fprintf(stderr, "Too many players\n"); exit(EXIT_FAILURE); } pl[i].rank = pl_nr; strncpy(pl[i].name, p, NAME_SIZE - 1); return i; } int pl_cmp(const void *a, const void *b) // Player result compare function { int ai = *(short *) a, bi = *(short *) b; int v = pl[bi].score - pl[ai].score; if (v) return v; v = pl[bi].games - pl[ai].games; if (v) return -v; v = pl[bi].buchh - pl[ai].buchh; if (v) return v; return pl[bi].sonnb - pl[ai].sonnb; } void proc_pl(bool detailed) // Process complete player result table { int i, g, r; for (i = 0; i < gam_nr; i++) { // Calculate WP and SB points pl[gam[i].w].buchh += pl[gam[i].b].score; pl[gam[i].b].buchh += pl[gam[i].w].score; pl[gam[i].w].sonnb += gam[i].r * pl[gam[i].b].score; pl[gam[i].b].sonnb += (2 - gam[i].r) * pl[gam[i].w].score; } // Sort the players descending on // Score, least #games played, Buchholz and Sonneborn-Berger // using a separate index table. for (i = 0; i < pl_nr; i++) pl_index[i] = i; qsort(pl_index, pl_nr, sizeof pl_index[0], pl_cmp); for (r = 0; r < pl_nr; r++) pl[pl_index[r]].rank = r + 1; // Establish ranking for (r = 0; r < pl_nr; r++) { i = pl_index[r]; printf("%3d %-20.20s ", pl[i].rank, pl[i].name); // Now produce individual game results. // Note that the Round info from the PGN header is unused. if (detailed) { for (g = 0; g < gam_nr; g++) { if (gam[g].w == i) printf("%3dw%c", pl[gam[g].b].rank, "0=1"[gam[g].r]); if (gam[g].b == i) printf("%3db%c", pl[gam[g].w].rank, "1=0"[gam[g].r]); } } else { // summary int r[6]; r[0] = r[1] = r[2] = r[3] = r[4] = r[5] = 0; for (g = 0; g < gam_nr; g++) { if (gam[g].w == i) r[3 + gam[g].r]++; if (gam[g].b == i) r[2 - gam[g].r]++; } printf("(w%d-%d-%d b%d-%d-%d)", r[5], r[3], r[4], r[2], r[0], r[1]); } printf("%5.1f %5.1f %6.2f %2d\n", pl[i].score / 2.0, pl[i].buchh / 2.0, pl[i].sonnb / 4.0, pl[i].games); } } static int games[PLAYERS][PLAYERS]; void proc_cross() // Process complete player result table { int i, g, r; memset(games, 0, sizeof(games)); int maxGames = 0; // figure out max number of games of each player-player pair for (g = 0; g < gam_nr; g++) { int a = gam[g].w; int b = gam[g].b; if (a >= PLAYERS || b >= PLAYERS) continue; if (a < b) { a = gam[g].b; b = gam[g].w; } if (++games[a][b] > maxGames) maxGames = games[a][b]; } for (i = 0; i < gam_nr; i++) { // Calculate WP and SB points pl[gam[i].w].buchh += pl[gam[i].b].score; pl[gam[i].b].buchh += pl[gam[i].w].score; pl[gam[i].w].sonnb += gam[i].r * pl[gam[i].b].score; pl[gam[i].b].sonnb += (2 - gam[i].r) * pl[gam[i].w].score; } // Sort the players descending on // Score, least #games played, Buchholz and Sonneborn-Berger // using a separate index table. for (i = 0; i < pl_nr; i++) pl_index[i] = i; qsort(pl_index, pl_nr, sizeof pl_index[0], pl_cmp); for (r = 0; r < pl_nr; r++) pl[pl_index[r]].rank = r + 1; // Establish ranking printf(" # Name "); for (r = 0; r < pl_nr; r++) printf("%*d", maxGames + 1, r + 1); printf(" Score Buch Sommb\n"); printf("-----------------------------"); for (r = 0; r < pl_nr; r++) for (i = 0; i < maxGames + 1; i++) printf("-"); printf("----------------------\n"); for (r = 0; r < pl_nr; r++) { i = pl_index[r]; printf("%3d %-25.25s ", pl[i].rank, pl[i].name); for (int s = 0; s < pl_nr; s++) { int j = pl_index[s]; if (i == j) { for (int k = 0; k < maxGames; k++) printf("*"); } else { int count = 0; for (g = 0; g < gam_nr; g++) { if (gam[g].w == i && gam[g].b == j) { printf("%c", "0=1"[gam[g].r]); count++; } if (gam[g].b == i && gam[g].w == j) { printf("%c", "1=0"[gam[g].r]); count++; } } for (; count < maxGames; count++) printf(" "); } printf(" "); } printf("%5.1f/%-2d %5.1f %6.2f\n", pl[i].score * 0.5, pl[i].games, pl[i].buchh * 0.5, pl[i].sonnb * 0.25); } } void clr_head(void) { memset(&cur_head, 0, sizeof cur_head); // ZAP! } void print_head(void) { printf("%s;%s;%s;%s;%s;%s;%s\n", cur_head.event, cur_head.site, cur_head.date, cur_head.round, cur_head.white, cur_head.black, cur_head.result); } int proc_head(void) { int b, w; // print_head(); if (event != NULL && strncmp(cur_head.event, event, strlen(event))) return 0; // Skip uninteresting event if (cur_head.white[0] == '\0' || cur_head.black[0] == '\0' || cur_head.result[0] == '\0') return 1; // Essential info missing w = add_pl(cur_head.white); b = add_pl(cur_head.black); gam[gam_nr].w = w; gam[gam_nr].b = b; if (!strncmp(cur_head.result, "0-1", 3)) { pl[b].score += 2; gam[gam_nr].r = 0; } else if (!strncmp(cur_head.result, "1/2", 3)) { /* Usually: "1/2-1/2" */ pl[w].score++; pl[b].score++; gam[gam_nr].r = 1; } else if (!strncmp(cur_head.result, "1-0", 3)) { pl[w].score += 2; gam[gam_nr].r = 2; } else return 2; /* Error in result header field */ pl[w].games++; pl[b].games++; gam_nr++; return 0; } int line_head(char *h) // Interpret header line { char *quote0 = strchr(h, '"'); char *quote1 = NULL; if (quote0 != NULL) quote1 = strchr(++quote0, '"'); if (quote0 == NULL || quote1 == NULL) return 1; // Maybe a tab char iso space after the tag should also be acceptable *quote1 = '\0'; if (!strnicmp(h, "event ", 6)) strcpy(cur_head.event, quote0); if (!strnicmp(h, "site ", 5)) strcpy(cur_head.site, quote0); if (!strnicmp(h, "date ", 5)) strcpy(cur_head.date, quote0); if (!strnicmp(h, "round ", 6)) strcpy(cur_head.round, quote0); if (!strnicmp(h, "white ", 6)) // sp distinguishes from WhiteElo! strcpy(cur_head.white, quote0); if (!strnicmp(h, "black ", 6)) strcpy(cur_head.black, quote0); if (!strnicmp(h, "result ", 7)) strcpy(cur_head.result, quote0); /* Unknown tag name is not an error */ return 0; } void proc_moves(char *m) { // Not yet. } int main(int argc, char *argv[]) { FILE *fp; static char s[LINE_SIZE]; int linenr = 0, head_toggle = 1; char *cp; bool detailed = false; bool crossTab = false; fprintf(stderr, "%s release date: %s %s\n", argv[0], __DATE__, __TIME__); if (argc == 1) { fprintf(stderr, "usage: %s [-D|-X] [ []]\n", argv[0]); fprintf(stderr, "if is not given, or is '-', reads from stdin\n"); fprintf(stderr, "options: -D show detailed results\n"); fprintf(stderr, " -X output results as a cross-table\n"); return 2; } char *inFile = 0; for (int i = 1; i < argc; i++) { if (argv[i][0] == '-' || argv[i][0] == '/') { switch (argv[i][1]) { case 'D': case 'd': detailed = true; break; case 'X': case 'x': crossTab = true; break; case '\0': if (argv[i][0] == '-') inFile = argv[i]; break; } } else if (inFile == 0) inFile = argv[i]; else event = argv[i]; // Process event by beginning of name } // Let's open your precious PGN file Read-only. if (inFile == 0 || strcmp(inFile, "-") == 0) fp = stdin; else if ((fp = fopen(inFile, "r")) == NULL) { fprintf(stderr, "Cannot open %s for reading\n", argv[1]); return 3; } printf("\n%s %s %s\n", argv[0], argv[1], event == NULL ? "" : event); while (fgets(cp = s, LINE_SIZE, fp) != NULL) { linenr++; // Ignore leading blanks and empty lines while (*cp == ' ' || *cp == '\t') cp++; if (*cp == '\n') continue; // Crystal clear code begins here. if (*cp == '[') { if (head_toggle != 1) { // begin of header clr_head(); head_toggle = 1; } if (line_head(cp + 1) != 0) fprintf(stderr, "%5d %sHeader line quoted value missing\n", linenr, s); } else { if (head_toggle != 0) { // end of header if (proc_head() != 0) fprintf(stderr, "%5d %sHeader incomplete\n", linenr, s); head_toggle = 0; } proc_moves(s); } } fclose(fp); // Now process the total player's results and make a report. if (!crossTab) proc_pl(detailed); else proc_cross(); // printf("===%s ends===\n\n", argv[0]); return 0; }