From c3eee8e333866d92e5fd94ae83cef618758c11bb Mon Sep 17 00:00:00 2001 From: Markus Uhlin Date: Mon, 15 Sep 2025 18:50:32 +0200 Subject: FICS RPBLC v1.4.6 --- FICS/ratings.c | 1964 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1964 insertions(+) create mode 100644 FICS/ratings.c (limited to 'FICS/ratings.c') diff --git a/FICS/ratings.c b/FICS/ratings.c new file mode 100644 index 0000000..a85776d --- /dev/null +++ b/FICS/ratings.c @@ -0,0 +1,1964 @@ +/* + ratings.c + + fics - An internet chess server. + Copyright (C) 1993 Richard V. Nash + + 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 2 of the License, 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. +*/ + +/* Revision history: + name email yy/mm/dd Change + Richard Nash 93/10/22 Created + vek leeds@math.gatech.edu 95/04/05 Glicko system, with sterr + Markus Uhlin 23/12/17 Fixed the includes + Markus Uhlin 23/12/17 Usage of 'time_t' + Markus Uhlin 24/04/05 Reformatted ALL functions and + reenabled disabled code. + Markus Uhlin 24/04/05 Replaced unbounded string + handling functions. + Markus Uhlin 24/05/20 Fixed clang warnings + Markus Uhlin 24/07/07 Return value checking of the + fscanf() calls. + Markus Uhlin 24/11/27 Added sscanf() width spec and + fixed ignored retvals. + Markus Uhlin 24/11/28 Added null checks + Markus Uhlin 25/03/16 Fixed use of 32-bit 'time_t'. + Markus Uhlin 25/04/06 Fixed Clang Tidy warnings. + Markus Uhlin 25/07/28 Fixed missing return-value check + for a 'scanf'-like function. + Markus Uhlin 25/07/28 Restricted file permissions upon + creation. +*/ + +#include "stdinclude.h" +#include "common.h" + +#include +#include +#include +#include +#include + +#include "command.h" +#include "comproc.h" +#include "config.h" +#include "ficsmain.h" +#include "gamedb.h" +#include "lists.h" +#include "playerdb.h" +#include "ratings.h" +#include "utils.h" + +#if __linux__ +#include +#endif + +// Constants for Glicko system +#define Gd 3.25 +#define Gr0 1720 +#define Gs0 350 +#define Gq 0.00575646273249 +#define Gp 0.000010072398601964 +// End of Glicko system variables + +#define LOWESTHIST 800 +#define MAXHIST 30 + +PRIVATE double Ratings_B_Average; +PRIVATE double Ratings_B_StdDev; +PRIVATE double Ratings_S_Average; +PRIVATE double Ratings_S_StdDev; +PRIVATE double Ratings_L_Average; +PRIVATE double Ratings_L_StdDev; +PRIVATE double Ratings_W_Average; +PRIVATE double Ratings_W_StdDev; + +PRIVATE double Rb_M = 0.0, + Rb_S = 0.0, + Rb_total = 0.0; +PRIVATE int Rb_count = 0; + +PRIVATE double Rs_M = 0.0, + Rs_S = 0.0, + Rs_total = 0.0; +PRIVATE int Rs_count = 0; + +PRIVATE double Rl_M = 0.0, + Rl_S = 0.0, + Rl_total = 0.0; +PRIVATE int Rl_count = 0; + +PRIVATE double Rw_M = 0.0, + Rw_S = 0.0, + Rw_total = 0.0; +PRIVATE int Rw_count = 0; + +PRIVATE rateStruct bestS[MAX_BEST]; +PRIVATE int numS = 0; +PRIVATE rateStruct bestB[MAX_BEST]; +PRIVATE int numB = 0; +PRIVATE rateStruct bestW[MAX_BEST]; +PRIVATE int numW = 0; + +PRIVATE int sHist[MAXHIST] = { 0 }; +PRIVATE int bHist[MAXHIST] = { 0 }; +PRIVATE int wHist[MAXHIST] = { 0 }; +PRIVATE int lHist[MAXHIST] = { 0 }; + +PRIVATE char sdir[] = DEFAULT_STATS; + +PUBLIC int +is_active(int Games) +{ + return (Games >= PROVISIONAL); +} + +PUBLIC void +rating_add(int rating, int type) +{ + int which; + + if ((which = (rating - LOWESTHIST) / 100) < 0) + which = 0; + + if (which >= MAXHIST) + which = MAXHIST - 1; + + if (type == TYPE_BLITZ) { + bHist[which] += 1; + + Rb_count++; + Rb_total += rating; + + if (Rb_count == 1) { + Rb_M = rating; + } else { + Rb_S = Rb_S + (rating - Rb_M) * (rating - Rb_M); + Rb_M = Rb_M + (rating - Rb_M) / (Rb_count); + } + + Ratings_B_StdDev = sqrt(Rb_S / Rb_count); + Ratings_B_Average = (Rb_total / (double)Rb_count); + } else if (type == TYPE_WILD) { // TYPE_WILD + wHist[which] += 1; + + Rw_count++; + Rw_total += rating; + + if (Rw_count == 1) { + Rw_M = rating; + } else { + Rw_S = Rw_S + (rating - Rw_M) * (rating - Rw_M); + Rw_M = Rw_M + (rating - Rw_M) / (Rw_count); + } + + Ratings_W_StdDev = sqrt(Rw_S / Rw_count); + Ratings_W_Average = (Rw_total / (double)Rw_count); + } else if (type == TYPE_LIGHT) { // TYPE_LIGHT + lHist[which] += 1; + + Rl_count++; + Rl_total += rating; + + if (Rl_count == 1) { + Rl_M = rating; + } else { + Rl_S = Rl_S + (rating - Rl_M) * (rating - Rl_M); + Rl_M = Rl_M + (rating - Rl_M) / (Rl_count); + } + + Ratings_L_StdDev = sqrt(Rl_S / Rl_count); + Ratings_L_Average = (Rl_total / (double)Rl_count); + + // Insert bughouse stuff + } else { // TYPE_STAND + sHist[which] += 1; + + Rs_count++; + Rs_total += rating; + + if (Rs_count == 1) { + Rs_M = rating; + } else { + Rs_S = Rs_S + (rating - Rs_M) * (rating - Rs_M); + Rs_M = Rs_M + (rating - Rs_M) / (Rs_count); + } + + Ratings_S_StdDev = sqrt(Rs_S / Rs_count); + Ratings_S_Average = (Rs_total / (double)Rs_count); + } +} + +PUBLIC void +rating_remove(int rating, int type) +{ + int which; + + if ((which = (rating - LOWESTHIST) / 100) < 0) + which = 0; + + if (which >= MAXHIST) + which = MAXHIST - 1; + + if (type == TYPE_BLITZ) { + bHist[which] = bHist[which] - 1; + + if (bHist[which] < 0) + bHist[which] = 0; + if (Rb_count == 0) + return; + + Rb_count--; + Rb_total -= rating; + + if (Rb_count == 0) { + Rb_M = 0; + Rb_S = 0; + } else { + Rb_M = Rb_M - (rating - Rb_M) / (Rb_count); + Rb_S = Rb_S - (rating - Rb_M) * (rating - Rb_M); + + if (Rb_S < 0) + Rb_S = 0; + } + + if (Rb_count) { + Ratings_B_StdDev = sqrt(Rb_S / Rb_count); + Ratings_B_Average = (Rb_total / (double)Rb_count); + } else { + Ratings_B_StdDev = 0; + Ratings_B_Average = 0; + } + } else if (type == TYPE_WILD) { // TYPE_WILD + wHist[which] = wHist[which] - 1; + + if (wHist[which] < 0) + wHist[which] = 0; + if (Rw_count == 0) + return; + + Rw_count--; + Rw_total -= rating; + + if (Rw_count == 0) { + Rw_M = 0; + Rw_S = 0; + } else { + Rw_M = Rw_M - (rating - Rw_M) / (Rw_count); + Rw_S = Rw_S - (rating - Rw_M) * (rating - Rw_M); + + if (Rw_S < 0) + Rw_S = 0; + } + + if (Rw_count) { + Ratings_W_StdDev = sqrt(Rw_S / Rw_count); + Ratings_W_Average = (Rw_total / (double)Rw_count); + } else { + Ratings_W_StdDev = 0; + Ratings_W_Average = 0; + } + } else if (type == TYPE_LIGHT) { // TYPE_LIGHT + lHist[which] = lHist[which] - 1; + + if (lHist[which] < 0) + lHist[which] = 0; + if (Rl_count == 0) + return; + + Rl_count--; + Rl_total -= rating; + + if (Rl_count == 0) { + Rl_M = 0; + Rl_S = 0; + } else { + Rl_M = Rl_M - (rating - Rl_M) / (Rl_count); + Rl_S = Rl_S - (rating - Rl_M) * (rating - Rl_M); + + if (Rl_S < 0) + Rl_S = 0; + } + + if (Rl_count) { + Ratings_L_StdDev = sqrt(Rl_S / Rl_count); + Ratings_L_Average = (Rl_total / (double)Rl_count); + } else { + Ratings_L_StdDev = 0; + Ratings_L_Average = 0; + } + + // insert bughouse stuff here + } else { // TYPE_STAND + sHist[which] = sHist[which] - 1; + + if (sHist[which] < 0) + sHist[which] = 0; + if (Rs_count == 0) + return; + + Rs_count--; + Rs_total -= rating; + + if (Rs_count == 0) { + Rs_M = 0; + Rs_S = 0; + } else { + Rs_M = Rs_M - (rating - Rs_M) / (Rs_count); + Rs_S = Rs_S - (rating - Rs_M) * (rating - Rs_M); + + if (Rs_S < 0) + Rs_S = 0; + } + + if (Rs_count) { + Ratings_S_StdDev = sqrt(Rs_S / Rs_count); + Ratings_S_Average = (Rs_total / (double)Rs_count); + } else { + Ratings_S_StdDev = 0; + Ratings_S_Average = 0; + } + } +} + +PRIVATE void +load_ratings(void) +{ + FILE *fp; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + + snprintf(fname, sizeof fname, "%s/newratingsV%d_data", stats_dir, + STATS_VERSION); + + if ((fp = fopen(fname, "r")) == NULL) { + warn("%s: can't read ratings data", __func__); + return; + } else if (fscanf(fp, "%lf %lf %lf %d", &Rb_M, &Rb_S, &Rb_total, + &Rb_count) != 4 || + fscanf(fp, "%lf %lf %lf %d", &Rs_M, &Rs_S, &Rs_total, + &Rs_count) != 4 || + fscanf(fp, "%lf %lf %lf %d", &Rw_M, &Rw_S, &Rw_total, + &Rw_count) != 4 || + fscanf(fp, "%lf %lf %lf %d", &Rl_M, &Rl_S, &Rl_total, + &Rl_count) != 4) { + warn("%s: fscanf", __func__); + fclose(fp); + return; + } + + for (int i = 0; i < MAXHIST && !feof(fp) && !ferror(fp); i++) { + int ret; + + sHist[i] = bHist[i] = wHist[i] = lHist[i] = 0; + + ret = fscanf(fp, "%d %d %d %d", &sHist[i], &bHist[i], &wHist[i], + &lHist[i]); + if (ret != 4) { + warnx("%s: %s: too few items assigned (iteration: %d)", + __func__, fname, i); + fclose(fp); + return; + } + } + + if (ferror(fp)) { + warnx("%s: %s: the error indicator is set", __func__, fname); + fclose(fp); + return; + } + + fclose(fp); + + if (Rs_count != 0) { + Ratings_S_StdDev = sqrt(Rs_S / Rs_count); // NOLINT + Ratings_S_Average = (Rs_total / (double)Rs_count); + } else { + Ratings_S_StdDev = 0; + Ratings_S_Average = 0; + } + if (Rb_count != 0) { + Ratings_B_StdDev = sqrt(Rb_S / Rb_count); // NOLINT + Ratings_B_Average = (Rb_total / (double)Rb_count); + } else { + Ratings_B_StdDev = 0; + Ratings_B_Average = 0; + } + if (Rw_count != 0) { + Ratings_W_StdDev = sqrt(Rw_S / Rw_count); // NOLINT + Ratings_W_Average = (Rw_total / (double)Rw_count); + } else { + Ratings_W_StdDev = 0; + Ratings_W_Average = 0; + } + if (Rl_count != 0) { + Ratings_L_StdDev = sqrt(Rl_S / Rl_count); // NOLINT + Ratings_L_Average = (Rl_total / (double)Rl_count); + } else { + Ratings_L_StdDev = 0; + Ratings_L_Average = 0; + } +} + +PUBLIC void +save_ratings(void) +{ + FILE *fp; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + int fd; + + snprintf(fname, sizeof fname, "%s/newratingsV%d_data", stats_dir, + STATS_VERSION); + + errno = 0; + fd = open(fname, g_open_flags[1], g_open_modes); + + if (fd < 0) { + warn("%s: can't write ratings data", __func__); + return; + } else if ((fp = fdopen(fd, "w")) == NULL) { + warn("%s: can't write ratings data", __func__); + close(fd); + return; + } + + fprintf(fp, "%10f %10f %10f %d\n", Rb_M, Rb_S, Rb_total, Rb_count); + fprintf(fp, "%10f %10f %10f %d\n", Rs_M, Rs_S, Rs_total, Rs_count); + fprintf(fp, "%10f %10f %10f %d\n", Rw_M, Rw_S, Rw_total, Rw_count); + fprintf(fp, "%10f %10f %10f %d\n", Rl_M, Rl_S, Rl_total, Rl_count); + + for (int i = 0; i < MAXHIST; i++) { + fprintf(fp, "%d %d %d %d\n", sHist[i], bHist[i], wHist[i], + lHist[i]); + } + + fclose(fp); +} + +PRIVATE void +BestRemove(int p) +{ + int i; + + for (i = 0; i < numB; i++) { + if (!strcmp(bestB[i].name, parray[p].name)) + break; + } + if (i < numB) { + for (; i < numB - 1; i++) { + strlcpy(bestB[i].name, bestB[i + 1].name, + sizeof bestB[i].name); + bestB[i].rating = bestB[i + 1].rating; + } + numB--; + } + + for (i = 0; i < numS; i++) { + if (!strcmp(bestS[i].name, parray[p].name)) + break; + } + if (i < numS) { + for (; i < numS - 1; i++) { + strlcpy(bestS[i].name, bestS[i + 1].name, + sizeof bestS[i].name); + bestS[i].rating = bestS[i + 1].rating; + } + numS--; + } + + for (i = 0; i < numW; i++) { + if (!strcmp(bestW[i].name, parray[p].name)) + break; + } + if (i < numW) { + for (; i < numW - 1; i++) { + strlcpy(bestW[i].name, bestW[i + 1].name, + sizeof bestW[i].name); + bestW[i].rating = bestW[i + 1].rating; + } + numW--; + } +} + +PRIVATE void +BestAdd(int p) +{ + int where, j; + + if (parray[p].b_stats.rating > 0 && + parray[p].b_stats.num > 19) { + for (where = 0; where < numB; where++) { + if (parray[p].b_stats.rating > bestB[where].rating) + break; + } + + if (where < MAX_BEST) { + for (j = numB; j > where; j--) { + if (j == MAX_BEST) + continue; + strlcpy(bestB[j].name, bestB[j - 1].name, + sizeof(bestB[j].name)); + bestB[j].rating = bestB[j - 1].rating; + } + + strlcpy(bestB[where].name, parray[p].name, + sizeof(bestB[where].name)); + bestB[where].rating = parray[p].b_stats.rating; + + if (numB < MAX_BEST) + numB++; + } + } + + if (parray[p].s_stats.rating > 0 && + parray[p].s_stats.num > 19) { + for (where = 0; where < numS; where++) { + if (parray[p].s_stats.rating > bestS[where].rating) + break; + } + + if (where < MAX_BEST) { + for (j = numS; j > where; j--) { + if (j == MAX_BEST) + continue; + strlcpy(bestS[j].name, bestS[j - 1].name, + sizeof(bestS[j].name)); + bestS[j].rating = bestS[j - 1].rating; + } + + strlcpy(bestS[where].name, parray[p].name, + sizeof(bestS[where].name)); + bestS[where].rating = parray[p].s_stats.rating; + + if (numS < MAX_BEST) + numS++; + } + } + + if (parray[p].w_stats.rating > 0 && + parray[p].w_stats.num > 19) { + for (where = 0; where < numW; where++) { + if (parray[p].w_stats.rating > bestW[where].rating) + break; + } + + if (where < MAX_BEST) { + for (j = numW; j > where; j--) { + if (j == MAX_BEST) + continue; + strlcpy(bestW[j].name, bestW[j - 1].name, + sizeof(bestW[j].name)); + bestW[j].rating = bestW[j - 1].rating; + } + + strlcpy(bestW[where].name, parray[p].name, + sizeof(bestW[where].name)); + bestW[where].rating = parray[p].w_stats.rating; + + if (numW < MAX_BEST) + numW++; + } + } +} + +PUBLIC void +BestUpdate(int p) +{ + BestRemove(p); + BestAdd(p); +} + +PRIVATE void +zero_stats(void) +{ + for (int i = 0; i < MAXHIST; i++) { + sHist[i] = 0; + bHist[i] = 0; + wHist[i] = 0; + lHist[i] = 0; + } + + Rb_M = 0.0, Rb_S = 0.0, Rb_total = 0.0; + Rb_count = 0; + + Rs_M = 0.0, Rs_S = 0.0, Rs_total = 0.0; + Rs_count = 0; + + Rw_M = 0.0, Rw_S = 0.0, Rw_total = 0.0; + Rw_count = 0; + + Rl_M = 0.0, Rl_S = 0.0, Rl_total = 0.0; + Rl_count = 0; + + numS = 0; + numB = 0; + numW = 0; +} + +#if 0 +PUBLIC int +com_best(int p, param_list param) +{ + pprintf(p, "Standard Blitz Wild\n"); + + for (int i = 0; i < MAX_BEST; i++) { + if (i >= numS && i >= numB) + break; + + if (i < numS) { + pprintf(p, "%4d %-17s ", + bestS[i].rating, + bestS[i].name); + } else { + pprintf(p, " "); + } + + if (i < numB) { + pprintf(p, "%4d %-17s ", + bestB[i].rating, + bestB[i].name); + } else { + pprintf(p, " "); + } + + if (i < numW) { + pprintf(p, "%4d %-17s\n", + bestW[i].rating, + bestW[i].name); + } else { + pprintf(p, "\n"); + } + } + + return COM_OK; +} +#endif + +PUBLIC void +rating_init(void) +{ + zero_stats(); + load_ratings(); +} + +/* + * This recalculates the rating info from the player data. (Which can + * take a long time!) + */ +PUBLIC void +rating_recalc(void) +{ + DIR *dirp; + char dname[MAX_FILENAME_SIZE]; + int c; + int p1; +#if USE_DIRENT + struct dirent *dp; +#else + struct direct *dp; +#endif + time_t t = time(NULL); + + fprintf(stderr, "FICS: Recalculating ratings at %s\n", strltime(&t)); + zero_stats(); + + for (c = 'a'; c <= 'z'; c++) { + snprintf(dname, sizeof dname, "%s/%c", player_dir, c); + + if ((dirp = opendir(dname)) == NULL) + continue; + + for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { + if (dp->d_name[0] != '.') { + p1 = player_new(); + + if (player_read(p1, dp->d_name)) { + player_remove(p1); + + fprintf(stderr, "FICS: Problem reading " + "player %s.\n", dp->d_name); + + continue; + } + + if (parray[p1].b_stats.rating > 0) { + rating_add(parray[p1].b_stats.rating, + TYPE_BLITZ); + } + if (parray[p1].s_stats.rating > 0) { + rating_add(parray[p1].s_stats.rating, + TYPE_STAND); + } + if (parray[p1].l_stats.rating > 0) { + rating_add(parray[p1].l_stats.rating, + TYPE_LIGHT); + } + + // insert bughouse stuff here + + if (parray[p1].w_stats.rating > 0) { + rating_add(parray[p1].w_stats.rating, + TYPE_WILD); + } + + player_remove(p1); + } + } /* for */ + + closedir(dirp); + } /* for */ + + if (Rs_count) { + Ratings_S_StdDev = sqrt(Rs_S / Rs_count); + Ratings_S_Average = (Rs_total / (double)Rs_count); + } else { + Ratings_S_StdDev = 0; + Ratings_S_Average = 0; + } + if (Rb_count) { + Ratings_B_StdDev = sqrt(Rb_S / Rb_count); + Ratings_B_Average = (Rb_total / (double)Rb_count); + } else { + Ratings_B_StdDev = 0; + Ratings_B_Average = 0; + } + if (Rl_count) { + Ratings_L_StdDev = sqrt(Rl_S / Rl_count); + Ratings_L_Average = (Rl_total / (double)Rl_count); + } else { + Ratings_L_StdDev = 0; + Ratings_L_Average = 0; + } + if (Rw_count) { + Ratings_W_StdDev = sqrt(Rw_S / Rw_count); + Ratings_W_Average = (Rw_total / (double)Rw_count); + } else { + Ratings_W_StdDev = 0; + Ratings_W_Average = 0; + } + + save_ratings(); + + t = time(NULL); + fprintf(stderr, "FICS: Finished at %s\n", strltime(&t)); +} + +PRIVATE int +Round(double x) +{ + return (x < 0 ? (int)(x - 0.5) : (int)(x + 0.5)); +} + +PRIVATE double +Gf(double ss) +{ + return (1.0 / sqrt(1.0 + Gp * ss * ss)); +} + +/* + * Confusing but economical: calculate error and attenuation function + * together. + */ +PRIVATE double +GE(int r, int rr, double ss, double *fss) +{ + *fss = Gf(ss); + return (1.0 / (1.0 + pow(10.0, (rr - r) * (*fss) / 400.0))); +} + +PRIVATE double +current_sterr(double s, int64_t t) +{ + if (t < 0) + t = 0; // this shouldn't happen + return sqrt(s * s + Gd * Gd * log(1.0 + t / 60.0)); +} + +/* + * Calculates new rating and standard error. By vek. The person who + * invented the ratings system is Mark E. Glickman, Ph.D. His e-mail + * address is glickman@hustat.harvard.edu as of April '95. Postscript + * copy of the note I coded this from should be available for ftp from + * ics.onenet.net, if not elsewhere. + */ +PUBLIC void +rating_sterr_delta(int p1, int p2, int type, time_t gtime, int result, + int *deltarating, double *newsterr) +{ + double E, fs2, denominator, GK, w; // Parts of fancy formulas + double delta, sigma; // Results to return + double s1, s2; + int r1, r2; // Initial sterrs and ratings + statistics *p1_stats; + statistics *p2_stats; + time_t t1, t2; + + if (type == TYPE_BLITZ) { + p1_stats = &parray[p1].b_stats; + p2_stats = &parray[p2].b_stats; + } else if (type == TYPE_WILD) { + p1_stats = &parray[p1].w_stats; + p2_stats = &parray[p2].w_stats; + } else if (type == TYPE_LIGHT) { + p1_stats = &parray[p1].l_stats; + p2_stats = &parray[p2].l_stats; + + // insert bughouse stuff here + } else { + p1_stats = &parray[p1].s_stats; + p2_stats = &parray[p2].s_stats; + } + + /* + * Calculate effective pre-game sterr's. ltime == 0 implies + * never had sterr. + */ + if (p1_stats->ltime == 0) + s1 = Gs0; + else { + t1 = gtime - p1_stats->ltime; + s1 = current_sterr(p1_stats->sterr, t1); + + if (s1 > Gs0) + s1 = Gs0; + } + + if (p2_stats->ltime == 0) + s2 = Gs0; + else { + t2 = gtime - p2_stats->ltime; + s2 = current_sterr(p2_stats->sterr, t2); + + if (s2 > Gs0) + s2 = Gs0; + } + + if (p1_stats->rating == 0 && p1_stats->num == 0) + r1 = Gr0; + else + r1 = p1_stats->rating; + + if (p2_stats->rating == 0 && p2_stats->num == 0) + r2 = Gr0; + else + r2 = p2_stats->rating; + + if (result == RESULT_WIN) { + w = 1.0; + } else if (result == RESULT_DRAW) { + w = 0.5; + } else { + w = 0.0; + } + + E = GE(r1, r2, s2, &fs2); + denominator = 1.0 / (s1 * s1) + Gq * Gq * fs2 * fs2 * E * (1.0 - E); + GK = Gq * fs2 / denominator; + delta = GK * (w - E); + + if (p1_stats->rating == 0 && p1_stats->num == 0) + *deltarating = Round(Gr0 + delta); + else + *deltarating = Round(delta); // Returned values: deltarating, + // newsterr. + + sigma = 1.0 / sqrt(denominator); + *newsterr = (double) sigma; +} + +PUBLIC int +rating_delta(int p1, int p2, int type, int result, int gtime) +{ + int delta; + double sigma; + + rating_sterr_delta(p1, p2, type, gtime, result, &delta, &sigma); + return delta; +} + +PUBLIC int +rating_update(int g) +{ + double wSigma, bSigma; + int inprogress = (g == parray[garray[g].black].game); + int wDelta, bDelta; + int wRes, bRes; + statistics *b_stats; + statistics *w_stats; + time_t gtime; + + /* + * If this is adjudication of stored game - be quiet about + * ratings change. + */ + if (garray[g].type == TYPE_BLITZ) { + w_stats = &parray[garray[g].white].b_stats; + b_stats = &parray[garray[g].black].b_stats; + } else if (garray[g].type == TYPE_STAND) { + w_stats = &parray[garray[g].white].s_stats; + b_stats = &parray[garray[g].black].s_stats; + } else if (garray[g].type == TYPE_WILD) { + w_stats = &parray[garray[g].white].w_stats; + b_stats = &parray[garray[g].black].w_stats; + } else if (garray[g].type == TYPE_LIGHT) { + w_stats = &parray[garray[g].white].l_stats; + b_stats = &parray[garray[g].black].l_stats; + } else { + fprintf(stderr, "FICS: Can't update untimed ratings!\n"); + return -1; + } + + switch (garray[g].result) { + case END_CHECKMATE: + case END_RESIGN: + case END_FLAG: + case END_ADJWIN: + if (garray[g].winner == WHITE) { + wRes = RESULT_WIN; + bRes = RESULT_LOSS; + } else { + bRes = RESULT_WIN; + wRes = RESULT_LOSS; + } + break; + case END_AGREEDDRAW: + case END_REPETITION: + case END_50MOVERULE: + case END_STALEMATE: + case END_NOMATERIAL: + case END_BOTHFLAG: + case END_ADJDRAW: + case END_FLAGNOMATERIAL: + wRes = bRes = RESULT_DRAW; + break; + default: + fprintf(stderr, "FICS: Update undecided game %d?\n", + garray[g].result); + return -1; + } + + gtime = untenths(garray[g].timeOfStart); + + rating_sterr_delta(garray[g].white, garray[g].black, garray[g].type, + gtime, wRes, &wDelta, &wSigma); + rating_sterr_delta(garray[g].black, garray[g].white, garray[g].type, + gtime, bRes, &bDelta, &bSigma); + + w_stats->ltime = gtime; + b_stats->ltime = gtime; + + if (wRes == RESULT_WIN) { + w_stats->win++; + } else if (wRes == RESULT_LOSS) { + w_stats->los++; + } else { + w_stats->dra++; + } + + w_stats->num++; + + if (bRes == RESULT_WIN) { + b_stats->win++; + } else if (bRes == RESULT_LOSS) { + b_stats->los++; + } else { + b_stats->dra++; + } + + b_stats->num++; + + rating_remove(w_stats->rating, garray[g].type); + rating_remove(b_stats->rating, garray[g].type); + + if (inprogress) { + pprintf(garray[g].white, "%s rating adjustment: %d ", + ((garray[g].type == TYPE_BLITZ) ? "Blitz" : + ((garray[g].type == TYPE_WILD) ? "Wild" : + ((garray[g].type == TYPE_LIGHT) ? "Lightning" : + "Standard"))), + w_stats->rating); + pprintf(garray[g].black, "%s rating adjustment: %d ", + ((garray[g].type == TYPE_BLITZ) ? "Blitz" : + ((garray[g].type == TYPE_WILD) ? "Wild" : + ((garray[g].type == TYPE_LIGHT) ? "Lightning" : + "Standard"))), + b_stats->rating); + } + + if (wDelta < -1000) { + pprintf(garray[g].white, "not changed due to bug " + "(way too small)! sorry!\n"); + fprintf(stderr, "FICS: Got too small ratings bug for %s " + "(w) vs. %s\n", + parray[garray[g].white].login, + parray[garray[g].black].login); + } else if (wDelta > 3000) { + pprintf(garray[g].white, "not changed due to bug " + "(way too big)! sorry!\n"); + fprintf(stderr, "FICS: Got too big ratings bug for %s " + "(w) vs. %s\n", + parray[garray[g].white].login, + parray[garray[g].black].login); + } else { + w_stats->rating += wDelta; + w_stats->sterr = wSigma; + } + + if (bDelta < -1000) { + pprintf(garray[g].black, "not changed due to bug " + "(way too small)! sorry!\n"); + fprintf(stderr, "FICS: Got too small ratings bug for %s " + "(b) vs. %s\n", + parray[garray[g].black].login, + parray[garray[g].white].login); + } else if (bDelta > 3000) { + pprintf(garray[g].black, "not changed due to bug " + "(way too big)! sorry!\n"); + fprintf(stderr, "FICS: Got too big ratings bug for %s " + "(b) vs. %s\n", + parray[garray[g].black].login, + parray[garray[g].white].login); + } else { + b_stats->rating += bDelta; + b_stats->sterr = bSigma; + } + + rating_add(w_stats->rating, garray[g].type); + rating_add(b_stats->rating, garray[g].type); + + if (w_stats->rating > w_stats->best && + is_active(w_stats->num)) { + w_stats->best = w_stats->rating; + w_stats->whenbest = time(NULL); + } + if (b_stats->rating > b_stats->best && + is_active(b_stats->num)) { + b_stats->best = b_stats->rating; + b_stats->whenbest = time(NULL); + } + + // Ratings are now saved to disk after each game + player_save(garray[g].white); + player_save(garray[g].black); + + // foxbat 3.11.95 + if (garray[g].type == TYPE_BLITZ) { + Rb_count++; + Rb_total += (w_stats->rating + b_stats->rating) / 2.0; + } else if (garray[g].type == TYPE_STAND) { + Rs_count++; + Rs_total += (w_stats->rating + b_stats->rating) / 2.0; + } else if (garray[g].type == TYPE_LIGHT) { + Rl_count++; + Rl_total += (w_stats->rating + b_stats->rating) / 2.0; + } else if (garray[g].type == TYPE_WILD) { + Rw_count++; + Rw_total += (w_stats->rating + b_stats->rating) / 2.0; + } + + // end add + if (inprogress) { + pprintf(garray[g].white, "--> %d\n", w_stats->rating); + pprintf(garray[g].black, "--> %d\n", b_stats->rating); + } + + save_ratings(); + + UpdateRank(garray[g].type, parray[garray[g].white].name, w_stats, + parray[garray[g].white].name); + UpdateRank(garray[g].type, parray[garray[g].black].name, b_stats, + parray[garray[g].black].name); + return 0; +} + +PUBLIC int +com_assess(int p, param_list param) +{ + double newsterr1; + double newsterr2; + int p1 = p, p2; + int p1_connected = 1, p2_connected = 1; + int win1, draw1, loss1; + int win2, draw2, loss2; + time_t nowtime; + + nowtime = time(NULL); + + if (param[0].type == TYPE_NULL) { + if (parray[p].game < 0) { + pprintf(p, "You are not playing a game.\n"); + return COM_OK; + } else if (garray[parray[p].game].status == GAME_EXAMINE) { + if (!strcmp(garray[parray[p].game].black_name, + parray[p].name)) { + pcommand(p, "assess %s\n", + garray[parray[p].game].white_name); + } else { + pcommand(p, "assess %s %s\n", + garray[parray[p].game].white_name, + garray[parray[p].game].black_name); + } + + return COM_OK; + } else { + p2 = parray[p].opponent; + } + } else { + if (!FindPlayer(p, param[0].val.word, &p2, &p2_connected)) { + pprintf(p, "No user named \"%s\" was found.\n", + param[0].val.word); + return COM_OK; + } + + if (param[1].type != TYPE_NULL) { + p1 = p2; + p1_connected = p2_connected; + + if (!FindPlayer(p, param[1].val.word, &p2, + &p2_connected)) { + pprintf(p, "No user named \"%s\" was found.\n", + param[1].val.word); + + if (!p1_connected) + player_remove(p1); + return COM_OK; + } + } + } + + if (p1 == p2) { + pprintf(p, "You can't assess the same players.\n"); + + if (!p1_connected) + player_remove(p1); + if (!p2_connected) + player_remove(p2); + return COM_OK; + } + + rating_sterr_delta(p1, p2, TYPE_BLITZ, nowtime, RESULT_WIN, &win1, + &newsterr1); + rating_sterr_delta(p1, p2, TYPE_BLITZ, nowtime, RESULT_DRAW, &draw1, + &newsterr1); + rating_sterr_delta(p1, p2, TYPE_BLITZ, nowtime, RESULT_LOSS, &loss1, + &newsterr1); + rating_sterr_delta(p2, p1, TYPE_BLITZ, nowtime, RESULT_WIN, &win2, + &newsterr2); + rating_sterr_delta(p2, p1, TYPE_BLITZ, nowtime, RESULT_DRAW, &draw2, + &newsterr2); + rating_sterr_delta(p2, p1, TYPE_BLITZ, nowtime, RESULT_LOSS, &loss2, + &newsterr2); + + pprintf(p, "\nBlitz\n %10s (%4s, RD: %5.1f) %10s (%4s, RD: %5.1f)\n", + parray[p1].name, + ratstrii(parray[p1].b_stats.rating, parray[p1].registered), + parray[p1].b_stats.sterr, + parray[p2].name, + ratstrii(parray[p2].b_stats.rating, parray[p2].registered), + parray[p2].b_stats.sterr); + + pprintf(p, "Win : %4d %4d\n", + win1, + loss2); + pprintf(p, "Draw: %4d %4d\n", + draw1, + draw2); + pprintf(p, "Loss: %4d %4d\n", + loss1, + win2); + pprintf(p, "New RD: %5.1f %5.1f\n", + newsterr1, + newsterr2); + + rating_sterr_delta(p1, p2, TYPE_STAND, nowtime, RESULT_WIN, &win1, + &newsterr1); + rating_sterr_delta(p1, p2, TYPE_STAND, nowtime, RESULT_DRAW, &draw1, + &newsterr1); + rating_sterr_delta(p1, p2, TYPE_STAND, nowtime, RESULT_LOSS, &loss1, + &newsterr1); + rating_sterr_delta(p2, p1, TYPE_STAND, nowtime, RESULT_WIN, &win2, + &newsterr2); + rating_sterr_delta(p2, p1, TYPE_STAND, nowtime, RESULT_DRAW, &draw2, + &newsterr2); + rating_sterr_delta(p2, p1, TYPE_STAND, nowtime, RESULT_LOSS, &loss2, + &newsterr2); + + pprintf(p, "\nStandard\n %10s (%4s, RD: %5.1f) %10s " + "(%4s, RD: %5.1f)\n", + parray[p1].name, + ratstrii(parray[p1].s_stats.rating, parray[p1].registered), + parray[p1].s_stats.sterr, + parray[p2].name, + ratstrii(parray[p2].s_stats.rating, parray[p2].registered), + parray[p2].s_stats.sterr); + + pprintf(p, "Win : %4d %4d\n", + win1, + loss2); + pprintf(p, "Draw: %4d %4d\n", + draw1, + draw2); + pprintf(p, "Loss: %4d %4d\n", + loss1, + win2); + pprintf(p, "New RD: %5.1f %5.1f\n", + newsterr1, + newsterr2); + + rating_sterr_delta(p1, p2, TYPE_LIGHT, nowtime, RESULT_WIN, &win1, + &newsterr1); + rating_sterr_delta(p1, p2, TYPE_LIGHT, nowtime, RESULT_DRAW, &draw1, + &newsterr1); + rating_sterr_delta(p1, p2, TYPE_LIGHT, nowtime, RESULT_LOSS, &loss1, + &newsterr1); + rating_sterr_delta(p2, p1, TYPE_LIGHT, nowtime, RESULT_WIN, &win2, + &newsterr2); + rating_sterr_delta(p2, p1, TYPE_LIGHT, nowtime, RESULT_DRAW, &draw2, + &newsterr2); + rating_sterr_delta(p2, p1, TYPE_LIGHT, nowtime, RESULT_LOSS, &loss2, + &newsterr2); + + pprintf(p, "\nLightning\n %10s (%4s, RD: %5.1f) %10s " + "(%4s, RD: %5.1f)\n", + parray[p1].name, + ratstrii(parray[p1].l_stats.rating, parray[p1].registered), + parray[p1].l_stats.sterr, + parray[p2].name, + ratstrii(parray[p2].l_stats.rating, parray[p2].registered), + parray[p2].l_stats.sterr); + + pprintf(p, "Win : %4d %4d\n", + win1, + loss2); + pprintf(p, "Draw: %4d %4d\n", + draw1, + draw2); + pprintf(p, "Loss: %4d %4d\n", + loss1, + win2); + pprintf(p, "New RD: %5.1f %5.1f\n", + newsterr1, + newsterr2); + + rating_sterr_delta(p1, p2, TYPE_WILD, nowtime, RESULT_WIN, &win1, + &newsterr1); + rating_sterr_delta(p1, p2, TYPE_WILD, nowtime, RESULT_DRAW, &draw1, + &newsterr1); + rating_sterr_delta(p1, p2, TYPE_WILD, nowtime, RESULT_LOSS, &loss1, + &newsterr1); + + rating_sterr_delta(p2, p1, TYPE_WILD, nowtime, RESULT_WIN, &win2, + &newsterr2); + rating_sterr_delta(p2, p1, TYPE_WILD, nowtime, RESULT_DRAW, &draw2, + &newsterr2); + rating_sterr_delta(p2, p1, TYPE_WILD, nowtime, RESULT_LOSS, &loss2, + &newsterr2); + + pprintf(p, "\nWild\n %10s (%4s, RD: %5.1f) %10s (%4s, RD: %5.1f)\n", + parray[p1].name, + ratstrii(parray[p1].w_stats.rating, parray[p1].registered), + parray[p1].w_stats.sterr, + parray[p2].name, + ratstrii(parray[p2].w_stats.rating, parray[p2].registered), + parray[p2].w_stats.sterr); + + pprintf(p, "Win : %4d %4d\n", + win1, + loss2); + pprintf(p, "Draw: %4d %4d\n", + draw1, + draw2); + pprintf(p, "Loss: %4d %4d\n", + loss1, + win2); + pprintf(p, "New RD: %5.1f %5.1f\n", + newsterr1, + newsterr2); + + if (!p1_connected) + player_remove(p1); + if (!p2_connected) + player_remove(p2); + return COM_OK; +} + +PUBLIC int +com_best(int p, param_list param) +{ + return Best(p, param, 1); +} + +PUBLIC int +com_hbest(int p, param_list param) +{ + return Best(p, param, 0); +} + +PUBLIC int +com_statistics(int p, param_list param) +{ + pprintf(p, " Standard Blitz Lightning " + "Wild\n"); + pprintf(p, "average: %7.2f %7.2f %7.2f %7.2f\n", + Ratings_S_Average, + Ratings_B_Average, + Ratings_L_Average, + Ratings_W_Average); + pprintf(p, "std dev: %7.2f %7.2f %7.2f %7.2f\n", + Ratings_S_StdDev, + Ratings_B_StdDev, + Ratings_L_StdDev, + Ratings_W_StdDev); + pprintf(p, "number : %7d %7d %7d %7d\n", + Rs_count, Rb_count, Rl_count, Rw_count); + return COM_OK; +} + +/* + * Return the difference of 'a - b' + */ +PUBLIC int +int_diff(const char *fn, const int a, const int b) +{ + if ((b > 0 && a < INT_MIN + b) || + (b < 0 && a > INT_MAX + b)) + errx(1, "%s: integer overflow (%d - %d)", fn, a, b); + return (a - b); +} + +PUBLIC int +com_fixrank(int p, param_list param) +{ + int p1, connected; + + if (!FindPlayer(p, param[0].val.word, &p1, &connected)) + return COM_OK; + + UpdateRank(TYPE_BLITZ, parray[p1].name, &parray[p1].b_stats, + parray[p1].name); + UpdateRank(TYPE_STAND, parray[p1].name, &parray[p1].s_stats, + parray[p1].name); + UpdateRank(TYPE_WILD, parray[p1].name, &parray[p1].w_stats, + parray[p1].name); + + if (!connected) + player_remove(p1); + return COM_OK; +} + +PUBLIC int +com_rank(int p, param_list param) +{ + return DisplayRank(p, param, 1); +} + +PUBLIC int +com_hrank(int p, param_list param) +{ + return DisplayRank(p, param, 0); +} + +PUBLIC int +DisplayRank(int p, param_list param, int showComputers) +{ + int show = (SHOW_BLITZ|SHOW_STANDARD|SHOW_WILD); + int start, end, target, connected; + + if (param[0].type == TYPE_NULL) { + DisplayTargetRank(p, parray[p].name, show, showComputers); + return COM_OK; + } else if (isdigit(param[0].val.word[0])) { + int ret; + + start = 0; + end = -1; + ret = sscanf(param[0].val.word, "%d-%d", &start, &end); + + if (ret != 2) + return COM_FAILED; + + if (end > 0 && (param[1].type != TYPE_NULL)) + show = ShowFromString(param[1].val.word); + + DisplayRankedPlayers(p, start, end, show, showComputers); + return COM_OK; + } else { + target = player_search(p, param[0].val.word); + + if (target == 0) { + pprintf(p, "Target %s not found.\n", param[0].val.word); + return COM_OK; + } + + connected = (target > 0); + + if (!connected) + target = -target - 1; + else + target--; + + if (param[1].type != TYPE_NULL) + show = ShowFromString(param[1].val.word); + + DisplayTargetRank(p, parray[target].name, show, showComputers); + + if (!connected) + player_remove(target); + return COM_OK; + } +} + +/* + * CompareStats() returns: + * - 1 if s1 comes first, + * - -1 if s2 comes first, + * - and 0 if neither takes precedence. + */ +PRIVATE int +CompareStats(char *name1, statistics *s1, + char *name2, statistics *s2) +{ + int i, l1; + + if (s1 == NULL) { + if (s2 == NULL) + return 0; + else + return -1; + } else if (s2 == NULL) + return 1; + + if (s1->rating > s2->rating) + return 1; + if (s1->rating < s2->rating) + return -1; + + l1 = strlen(name1); + + for (i = 0; i < l1; i++) { + if (name2[i] == '\0') + return -1; + if (tolower(name1[i]) < tolower(name2[i])) + return 1; + if (tolower(name1[i]) > tolower(name2[i])) + return -1; + } + + if (name2[i] != '\0') + return 1; + + fprintf(stderr, "Duplicate entries found: %s.\n", name1); + return 0; +} + +PRIVATE int +GetRankFileName(char *out, const size_t size, int type) +{ + switch (type) { + case TYPE_BLITZ: + snprintf(out, size, "%s/rank.blitz", sdir); + return type; + case TYPE_STAND: + snprintf(out, size, "%s/rank.std", sdir); + return type; + case TYPE_WILD: + snprintf(out, size, "%s/rank.wild", sdir); + return type; + default: + return -1; + } +} + +PUBLIC void +UpdateRank(int type, char *addName, statistics *sNew, char *delName) +{ + FILE *fp; + FILE *fptemp; + char RankFile[MAX_FILENAME_SIZE]; + char TmpRankFile[MAX_FILENAME_SIZE]; + char command[MAX_STRING_LENGTH]; + char line[MAX_RANK_LINE] = { '\0' }; + char login[MAX_LOGIN_NAME] = { '\0' }; + int comp = 0; + int fd = -1; + statistics sCur; + + if (GetRankFileName(RankFile, sizeof RankFile, type) < 0) + return; + + if ((fp = fopen(RankFile, "r")) == NULL) { + warn("%s: can't open rank file to update", __func__); + return; + } + + snprintf(TmpRankFile, sizeof TmpRankFile, "%s/tmpRank", sdir); + + errno = 0; + fd = open(TmpRankFile, g_open_flags[1], g_open_modes); + + if (fd < 0) { + warn("%s: open", __func__); + fclose(fp); + return; + } else if ((fptemp = fdopen(fd, "w")) == NULL) { + warn("%s: unable to open rank file for updating", __func__); + fclose(fp); + close(fd); + return; + } + + while (fgets(line, sizeof line, fp) != NULL) { + _Static_assert(ARRAY_SIZE(login) > 19, "'login' too small"); + + if (sscanf(line, "%19s %d %d %d", login, + &sCur.rating, &sCur.num, &comp) != 4) { + warnx("%s: %s: sscanf() error -- too few items", + __func__, RankFile); + continue; + } + + if (delName != NULL && + !strcasecmp(delName, login)) { // Kill name. + delName = NULL; + continue; + } + + if (addName != NULL && + CompareStats(addName, sNew, login, &sCur) > 0) { + int computer = in_list(-1, L_COMPUTER, addName); + + fprintf(fptemp, "%s %d %d %d\n", addName, sNew->rating, + sNew->num, computer); + addName = NULL; + } + + fprintf(fptemp, "%s %d %d %d\n", login, sCur.rating, sCur.num, + comp); + } + + fclose(fptemp); + fclose(fp); + + // XXX +#define NASH_CODE 0 +#if NASH_CODE + snprintf(command, sizeof command, "mv %s %s", TmpRankFile, RankFile); + system(command); +#else + if (rename(TmpRankFile, RankFile) == -1) + warn("%s: rename()", __func__); + UNUSED_VAR(command); +#endif +} + +PRIVATE void +DisplayRankHead(int p, int show) +{ + char Line[MAX_STRING_LENGTH] = { '\0' }; + + if (CheckFlag(show, SHOW_BLITZ)) + strlcat(Line, " Blitz ", sizeof(Line)); + if (CheckFlag(show, SHOW_STANDARD)) + strlcat(Line, " Standard ", sizeof(Line)); + if (CheckFlag(show, SHOW_WILD)) + strlcat(Line, " Wild", sizeof(Line)); + + pprintf(p, "%s\n\n", Line); +} + +PRIVATE int +CountRankLine(int countComp, char *loginName, int num, int is_computer) +{ + if (loginName == NULL || loginName[0] == '\0') + return 0; + return (countComp || !is_computer) && (is_active(num)); +} + +PRIVATE int +GetRank(FILE *fp, char *target, int countComp) +{ + char line[MAX_RANK_LINE] = { '\0' }; + char login[MAX_LOGIN_NAME] = { '\0' }; + int count = 0; + int is_computer = 0; + int nGames = 0; + int playerFound = 0; + + while (fgets(line, sizeof line, fp) != NULL && + !playerFound) { + _Static_assert(ARRAY_SIZE(login) > 19, "'login' too small"); + + if (sscanf(line, "%19s %*d %d %d", login, &nGames, &is_computer) + < 3) { + continue; + } + + if ((playerFound = !strcasecmp(login, target)) || + CountRankLine(countComp, login, nGames, is_computer)) + count++; + } + + return (playerFound ? count : -1); +} + +PRIVATE void +PositionFilePtr(FILE *fp, int count, int *last, int *nTied, int showComp) +{ + char line[MAX_RANK_LINE] = { '\0' }; + char login[MAX_LOGIN_NAME] = { '\0' }; + int rating, nGames, is_computer; + + if (fp == NULL) + return; + + rating = nGames = is_computer = 0; + errno = 0; + rewind(fp); + if (errno) { + warn("%s: rewind", __func__); + return; + } + + for (int i = 1; i < count; i++) { + do { + _Static_assert(ARRAY_SIZE(login) > 19, + "'login' too small"); + + if (feof(fp) || ferror(fp) || + fgets(line, sizeof line, fp) == NULL) + break; + else if (sscanf(line, "%19s %d %d %d", login, &rating, + &nGames, &is_computer) != 4) { + warnx("%s: sscanf() error", __func__); + break; + } + } while (!CountRankLine(showComp, login, nGames, is_computer)); + + if (ferror(fp)) { + warnx("%s: the error indicator is set", __func__); + return; + } + + if (rating != *last) { + *nTied = 1; + *last = rating; + } else + (*nTied)++; + } +} + +PRIVATE int +ShowRankEntry(int p, FILE *fp, int count, int comp, char *target, + int *lastRating, int *nTied) +{ + char login[MAX_LOGIN_NAME] = { '\0' }; + char newLine[MAX_RANK_LINE] = { '\0' }; + int rating, findable, nGames, is_comp; + + // XXX + rating = 0; + findable = (count > 0 && !feof(fp) && !ferror(fp)); + nGames = 0; + is_comp = 0; + + if (findable) { + do { + if (fgets(newLine, sizeof newLine, fp) == NULL || + feof(fp) || + ferror(fp)) { + findable = 0; + } else if (newLine[0] != '\0') { + _Static_assert(ARRAY_SIZE(login) > 19, + "Assertion has failed"); + + if (sscanf(newLine, "%19s %d %d %d", login, + &rating, &nGames, &is_comp) != 4) { + warnx("%s: sscanf() error", __func__); + findable = 0; + break; + } + } else { + login[0] = '\0'; + } + } while (!CountRankLine(comp, login, nGames, is_comp) && + findable && + strcasecmp(login, target)); + } + + if (findable) { + if (!strcasecmp(login, target) && !CountRankLine(comp, login, + nGames, is_comp)) { + pprintf_highlight(p, "---- %-12.12s %4s", login, + ratstr(rating)); + pprintf(p, " "); + return 0; + } else if (*lastRating == rating && *nTied < 1) { + pprintf(p, " "); + + if (!strcasecmp(login, target)) { + pprintf_highlight(p, "%-12.12s %4s", login, + ratstr(rating)); + } else { + pprintf(p, "%-12.12s %4s", login, + ratstr(rating)); + } + + pprintf(p, " "); + return 1; + } else { + if (*nTied >= 1) { + if (*lastRating == rating) + count -= *nTied; + *nTied = -1; + } + + if (!strcasecmp(login, target)) { + pprintf_highlight(p, "%4d. %-12.12s %4s", count, + login, ratstr(rating)); + } else { + pprintf(p, "%4d. %-12.12s %4s", count, login, + ratstr(rating)); + } + + pprintf(p, " "); + *lastRating = rating; + return 1; + } + } else { + pprintf(p, "%25s", ""); + return 1; + } +} + +PRIVATE int +CountAbove(int num, int blitz, int std, int wild, int which) +{ + int max = blitz; + + if (max < std) + max = std; + if (max < wild) + max = wild; + return (max <= (num + 1) / 2 ? max - 1 : (num + 1) / 2); +} + +PRIVATE int +ShowRankLines(int p, FILE *fb, FILE *fs, FILE *fw, int bCount, int sCount, + int wCount, int n, int showComp, int show, char *target) +{ + int lastBlitz = 9999, nTiedBlitz = 0; + int lastStd = 9999, nTiedStd = 0; + int lastWild = 9999, nTiedWild = 0; + + if (n <= 0) + return 0; + + if (fb != NULL && CheckFlag(show, SHOW_BLITZ)) { + PositionFilePtr(fb, bCount, &lastBlitz, &nTiedBlitz, showComp); + + if (feof(fb)) + ClearFlag(show, SHOW_BLITZ); + } + + if (fs != NULL && CheckFlag(show, SHOW_STANDARD)) { + PositionFilePtr(fs, sCount, &lastStd, &nTiedStd, showComp); + + if (feof(fs)) + ClearFlag(show, SHOW_STANDARD); + } + + if (fw != NULL && CheckFlag(show, SHOW_WILD)) { + PositionFilePtr(fw, wCount, &lastWild, &nTiedWild, showComp); + + if (feof(fw)) + ClearFlag(show, SHOW_WILD); + } + + if (!CheckFlag(show, (SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD))) + return 0; + + DisplayRankHead(p, show); + + for (int i = 0; i < n && show; i++) { + if (fb != NULL && CheckFlag(show, SHOW_BLITZ)) { + bCount += ShowRankEntry(p, fb, bCount, showComp, target, + &lastBlitz, &nTiedBlitz); + } + if (fs != NULL && CheckFlag(show, SHOW_STANDARD)) { + sCount += ShowRankEntry(p, fs, sCount, showComp, target, + &lastStd, &nTiedStd); + } + if (fw != NULL && CheckFlag(show, SHOW_WILD)) { + wCount += ShowRankEntry(p, fw, wCount, showComp, target, + &lastWild, &nTiedWild); + } + pprintf(p, "\n"); + } + + return 1; +} + +PUBLIC int +DisplayTargetRank(int p, char *target, int show, int showComp) +{ + FILE *fb = NULL; + FILE *fs = NULL; + FILE *fw = NULL; + char Path[MAX_FILENAME_SIZE] = { '\0' }; + int numAbove; + int numToShow = 20; + int blitzRank = -1, blitzCount; + int stdRank = -1, stdCount; + int wildRank = -1, wildCount; + + if (CheckFlag(show, SHOW_BLITZ)) { + GetRankFileName(Path, sizeof Path, TYPE_BLITZ); + + if ((fb = fopen(Path, "r")) != NULL) + blitzRank = GetRank(fb, target, showComp); + if (blitzRank < 0) + ClearFlag(show, SHOW_BLITZ); + } + + if (CheckFlag(show, SHOW_STANDARD)) { + GetRankFileName(Path, sizeof Path, TYPE_STAND); + + if ((fs = fopen(Path, "r")) != NULL) + stdRank = GetRank(fs, target, showComp); + if (stdRank < 0) + ClearFlag(show, SHOW_STANDARD); + } + + if (CheckFlag(show, SHOW_WILD)) { + GetRankFileName(Path, sizeof Path, TYPE_WILD); + + if (CheckFlag(show, SHOW_WILD)) + fw = fopen(Path, "r"); + if (fw != NULL) + wildRank = GetRank(fw, target, showComp); + if (wildRank < 0) + ClearFlag(show, SHOW_WILD); + } + + if (!CheckFlag(show, (SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD))) { + pprintf(p, "No ratings to show.\n"); + + if (fb != NULL) + fclose(fb); + if (fs != NULL) + fclose(fs); + if (fw != NULL) + fclose(fw); + return 0; + } + + numAbove = CountAbove(numToShow, blitzRank, stdRank, wildRank, show); + + blitzCount = int_diff(__func__, blitzRank, numAbove); + stdCount = int_diff(__func__, stdRank, numAbove); + wildCount = int_diff(__func__, wildRank, numAbove); + + ShowRankLines(p, fb, fs, fw, blitzCount, stdCount, wildCount, numToShow, + showComp, show, target); + + if (fb != NULL) + fclose(fb); + if (fs != NULL) + fclose(fs); + if (fw != NULL) + fclose(fw); + return 1; +} + +PUBLIC int +DisplayRankedPlayers(int p, int start, int end, int show, int showComp) +{ + FILE *fb = NULL; + FILE *fs = NULL; + FILE *fw = NULL; + char Path[MAX_FILENAME_SIZE] = { '\0' }; + int num = (end - start + 1); + + if (start <= 0) + start = 1; + if (num <= 0) + return 0; + if (num > 100) + num = 100; + + if (CheckFlag(show, SHOW_BLITZ)) { + GetRankFileName(Path, sizeof Path, TYPE_BLITZ); + + if ((fb = fopen(Path, "r")) == NULL) + ClearFlag(show, SHOW_BLITZ); + } + if (CheckFlag(show, SHOW_STANDARD)) { + GetRankFileName(Path, sizeof Path, TYPE_STAND); + + if ((fs = fopen(Path, "r")) == NULL) + ClearFlag(show, SHOW_STANDARD); + } + if (CheckFlag(show, SHOW_WILD)) { + GetRankFileName(Path, sizeof Path, TYPE_WILD); + + if ((fw = fopen(Path, "r")) == NULL) + ClearFlag(show, SHOW_WILD); + } + + ShowRankLines(p, fb, fs, fw, start, start, start, num, showComp, show, + ""); + + if (fb) + fclose(fb); + if (fs) + fclose(fs); + if (fw) + fclose(fw); + return 1; +} + +PUBLIC int +ShowFromString(char *s) +{ + int len = strlen(s ? s : ""); + int show = 0; + + if (s == NULL || s[0] == '\0') + return (SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD); + + for (int i = 0; i < len; i++) { + switch (s[i]) { + case 'b': + SetFlag(show, SHOW_BLITZ); + break; + case 's': + SetFlag(show, SHOW_STANDARD); + break; + case 'w': + SetFlag(show, SHOW_WILD); + break; + } + } + + return show; +} + +PUBLIC int +Best(int p, param_list param, int ShowComp) +{ + int show = (SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD); + + if (param[0].type != TYPE_NULL) + show = ShowFromString(param[0].val.word); + + DisplayRankedPlayers(p, 1, 20, show, ShowComp); + return COM_OK; +} -- cgit v1.2.3