/*
    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'
*/

#include "stdinclude.h"
#include "common.h"

#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"

PUBLIC double Ratings_B_Average;
PUBLIC double Ratings_B_StdDev;

PUBLIC double Ratings_S_Average;
PUBLIC double Ratings_S_StdDev;

PUBLIC double Ratings_L_Average;
PUBLIC double Ratings_L_StdDev;

PUBLIC double Ratings_W_Average;
PUBLIC 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;

/*
PUBLIC rateStruct bestS[MAX_BEST];
PUBLIC int numS = 0;
PUBLIC rateStruct bestB[MAX_BEST];
PUBLIC int numB = 0;
PUBLIC rateStruct bestW[MAX_BEST];
PUBLIC int numW = 0;
*/

#define MAXHIST 30
#define LOWESTHIST 800
PUBLIC int sHist[MAXHIST];
PUBLIC int bHist[MAXHIST];
PUBLIC int wHist[MAXHIST];
PUBLIC int lHist[MAXHIST];

char sdir[] = DEFAULT_STATS;

PUBLIC int is_active(int Games)
{
  return (Games >= PROVISIONAL);
}


PUBLIC void rating_add(int rating, int type)
{
  int which;

  which = (rating - LOWESTHIST) / 100;
  if (which < 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;

  which = (rating - LOWESTHIST) / 100;
  if (which < 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);
       /* added this 3.11.95 foxbat */ 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);
       /* added this 3.10.95 foxbat */ 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);
       /* added this 3.10.95 foxbat */ 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);
       /* added this 3.10.95 foxbat */ 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];
  int i;

  sprintf(fname, "%s/newratingsV%d_data", stats_dir,STATS_VERSION);
  fp = fopen(fname, "r");
  if (!fp) {
    fprintf(stderr, "FICS: Can't read ratings data!\n");
    return;
  }
  fscanf(fp, "%lf %lf %lf %d", &Rb_M, &Rb_S, &Rb_total, &Rb_count);
  fscanf(fp, "%lf %lf %lf %d", &Rs_M, &Rs_S, &Rs_total, &Rs_count);
  fscanf(fp, "%lf %lf %lf %d", &Rw_M, &Rw_S, &Rw_total, &Rw_count);
  fscanf(fp, "%lf %lf %lf %d", &Rl_M, &Rl_S, &Rl_total, &Rl_count);

  for (i = 0; i < MAXHIST; i++) {
    fscanf(fp, "%d %d %d %d", &sHist[i], &bHist[i], &wHist[i], &lHist[i]);
  }
  fclose(fp);
  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 (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;
  }
  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;
  }
}

PUBLIC void save_ratings(void)
{
  FILE *fp;
  char fname[MAX_FILENAME_SIZE];
  int i;

  sprintf(fname, "%s/newratingsV%d_data", stats_dir,STATS_VERSION);
  fp = fopen(fname, "w");
  if (!fp) {
    fprintf(stderr, "FICS: Can't write ratings data!\n");
    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 (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++) {
      strcpy(bestB[i].name, bestB[i + 1].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++) {
      strcpy(bestS[i].name, bestS[i + 1].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++) {
      strcpy(bestW[i].name, bestW[i + 1].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;
	strcpy(bestB[j].name, bestB[j - 1].name);
	bestB[j].rating = bestB[j - 1].rating;
      }
      strcpy(bestB[where].name, parray[p].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;
	strcpy(bestS[j].name, bestS[j - 1].name);
	bestS[j].rating = bestS[j - 1].rating;
      }
      strcpy(bestS[where].name, parray[p].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;
	strcpy(bestW[j].name, bestW[j - 1].name);
	bestW[j].rating = bestW[j - 1].rating;
      }
      strcpy(bestW[where].name, parray[p].name);
      bestW[where].rating = parray[p].w_stats.rating;
      if (numW < MAX_BEST)
	numW++;
    }
  }
}

PUBLIC void BestUpdate(int p)
{
  BestRemove(p);
  BestAdd(p);
}

*/

PUBLIC void zero_stats(void)
{
  int i;
  for (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;
*/
}

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++) {
		sprintf(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));
}

/*  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 */

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)));
}

PUBLIC double current_sterr(double s, int 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, int gtime, int result,
			        int *deltarating, double *newsterr)
{
  statistics *p1_stats;
  statistics *p2_stats;

  double s1, s2;
  int t1, r1, t2, r2;		/* Initial sterrs and ratings */
  double E, fs2, denominator, GK, w;	/* Parts of fancy formulas */
  double delta, sigma;		/* Results to return */

  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 sterrs.  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;
  }

  /* pre-game ratings */
  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;

  /* now crunch */

  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);	/* side effect: calculate 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;
}

/* vek: Next is for when we want just the delta, and not the 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)
{
  int wDelta, bDelta;
  double wSigma, bSigma;	/* vek */

  int wRes, bRes;
  statistics *w_stats;
  statistics *b_stats;

  int gtime;

  int inprogress = (g == parray[garray[g].black].game);
  /* 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);

  /* vek: Update time of last rated game played, for future ratings calcs. */
  /* Kept independently for blitz and standard.                       */
  w_stats->ltime = gtime;
  b_stats->ltime = gtime;
  /* end vek add 4/5/95 */

  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! ");
    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! ");
    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;
  }				/* error messages down to vek */

  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)
{
  int p1 = p, p2, nowtime;
  int p1_connected = 1, p2_connected = 1;
  int win1, draw1, loss1;
  double newsterr1;
  int win2, draw2, loss2;
  double newsterr2;

  nowtime = time(0);

/* Hawk: Now assess can be used with players not  */
/*       logged on -- I wonder if anyone doesn't  */
/*       get just a bit confused here :)          */

  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);
}

#if 0
PUBLIC int com_best(int p, param_list param)
{
  int i;

  pprintf(p, "Standard                Blitz                   Wild\n");
  for (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 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;
}

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 start, end, target, connected;
  int show = SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD;

  if (param[0].type == TYPE_NULL) {
    DisplayTargetRank(p, parray[p].name, show, showComputers);
    return COM_OK;
  } else if (isdigit(param[0].val.word[0])) {
    end = -1;
    sscanf(param[0].val.word, "%d-%d", &start, &end);
    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. */
#if 0
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;
/*  if (s1->sterr < s2->sterr) return 1;
  if (s1->sterr > s2->sterr) return -1;
  if (s1->num > s2->num) return 1;
  if (s1->num < s2->num) return -1;
*/
  fprintf(stderr, "Duplicate entries found: %s.\n", name1);
  return 0;
}
#endif
PRIVATE int GetRankFileName(char *out, int type)
{
  switch (type) {
    case TYPE_BLITZ:
	sprintf(out, "%s/rank.blitz", sdir);
	return type;
    case TYPE_STAND:
	sprintf(out, "%s/rank.std", sdir);
	return type;
    case TYPE_WILD:
	sprintf(out, "%s/rank.wild", sdir);
	return type;
    default:
	return -1;
  }
}

/* loon: Turning this off 28 Oct 1995 (temporary:)) since we're lagged
   into outer space */
PUBLIC void UpdateRank(int type, char *addName,
		        statistics *sNew, char *delName)
{}

#if 0
PUBLIC void UpdateRank(int type, char *addName,
		        statistics *sNew, char *delName)
{
  char RankFile[MAX_FILENAME_SIZE];
  char TmpRankFile[MAX_FILENAME_SIZE];
  char line[MAX_RANK_LINE];
  char login[MAX_LOGIN_NAME];
  char command[MAX_STRING_LENGTH];
  int comp;
  statistics sCur;
  FILE *fp;
  FILE *fptemp;

  if (GetRankFileName(RankFile, type) < 0)
    return;
  fp = fopen(RankFile, "r");
  if (fp == NULL) {
    fprintf(stderr, "Can't open rank file to update.\n");
    return;
  }
  sprintf(TmpRankFile, "%s/tmpRank", sdir);
  fptemp = fopen(TmpRankFile, "w");
  if (fptemp == NULL) {
    fprintf (stderr, "Unable to open rank file for updating.\n");
    return;
  }
  while (fgets(line, MAX_RANK_LINE - 1, fp)) {
    sscanf(line, "%s %d %d %d", login, &sCur.rating,
	   &sCur.num, &comp);
    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);

  sprintf(command, "mv %s %s", TmpRankFile, RankFile);
  system(command);
}
#endif

PRIVATE void DisplayRankHead(int p, int show)
{
  char Line[MAX_STRING_LENGTH];

  Line[0] = '\0';
  if (CheckFlag(show, SHOW_BLITZ))
    strcat(Line, "         Blitz          ");
  if (CheckFlag(show, SHOW_STANDARD))
    strcat(Line, "       Standard          ");
  if (CheckFlag(show, SHOW_WILD))
    strcat(Line, "          Wild");
  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)
{
  int count = 0;
  int nGames, is_computer;
  int playerFound = 0;
  char line[MAX_RANK_LINE];
  char login[MAX_LOGIN_NAME];

  while (fgets(line, MAX_RANK_LINE - 1, fp) && !playerFound) {
    sscanf(line, "%s %*d %d %d", login, &nGames, &is_computer);
    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)
{
  int i, rating, nGames, is_computer;
  char login[MAX_LOGIN_NAME];
  char line[MAX_RANK_LINE];

  if (fp == NULL)
    return;
  rewind(fp);
  for (i = 1; i < count; i++) {
    do {
      fgets(line, MAX_RANK_LINE - 1, fp);
      if (feof(fp))
	break;
      sscanf(line, "%s %d %d %d", login, &rating, &nGames, &is_computer);
    } while (!CountRankLine(showComp, login, nGames, is_computer));
    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 newLine[MAX_RANK_LINE];
  char login[MAX_LOGIN_NAME];
  int rating, findable, nGames, is_comp;

  findable = (count > 0) && !feof(fp);
  if (findable) {
    do {
      fgets(newLine, MAX_RANK_LINE - 1, fp);
      if (feof(fp))
	findable = 0;
      else if (newLine[0] != '\0')
	sscanf(newLine, "%s %d %d %d",
	       login, &rating, &nGames, &is_comp);
      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;
  int i;

  if (n <= 0)
    return 0;
  if (CheckFlag(show, SHOW_BLITZ)) {
    PositionFilePtr(fb, bCount, &lastBlitz, &nTiedBlitz, showComp);
    if (feof(fb))
      ClearFlag(show, SHOW_BLITZ);
  }
  if (CheckFlag(show, SHOW_STANDARD)) {
    PositionFilePtr(fs, sCount, &lastStd, &nTiedStd, showComp);
    if (feof(fs))
      ClearFlag(show, SHOW_STANDARD);
  }
  if (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 (i = 0; i < n && show; i++) {
    if (CheckFlag(show, SHOW_BLITZ))
      bCount += ShowRankEntry(p, fb, bCount, showComp, target,
			      &lastBlitz, &nTiedBlitz);
    if (CheckFlag(show, SHOW_STANDARD))
      sCount += ShowRankEntry(p, fs, sCount, showComp, target,
			      &lastStd, &nTiedStd);
    if (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)
{
  int numToShow = 20;
  int blitzRank = -1, blitzCount;
  int stdRank = -1, stdCount;
  int wildRank = -1, wildCount;
  int numAbove;
  char Path[MAX_FILENAME_SIZE];
  FILE *fb = NULL, *fs = NULL, *fw = NULL;

  if (CheckFlag(show, SHOW_BLITZ)) {
    GetRankFileName(Path, TYPE_BLITZ);
    fb = (FILE *) fopen(Path, "r");
    if (fb != NULL)
      blitzRank = GetRank(fb, target, showComp);
    if (blitzRank < 0)
      ClearFlag(show, SHOW_BLITZ);
  }
  if (CheckFlag(show, SHOW_STANDARD)) {
    GetRankFileName(Path, TYPE_STAND);
    fs = (FILE *) fopen(Path, "r");
    if (fs != NULL)
      stdRank = GetRank(fs, target, showComp);
    if (stdRank < 0)
      ClearFlag(show, SHOW_STANDARD);
  }
  if (CheckFlag(show, SHOW_WILD)) {
    GetRankFileName(Path, TYPE_WILD);
    if (CheckFlag(show, SHOW_WILD))
      fw = (FILE *) 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 = blitzRank - numAbove;
  stdCount = stdRank - numAbove;
  wildCount = 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)
{
  int num = end - start + 1;
  FILE *fb = NULL, *fs = NULL, *fw = NULL;
  char Path[MAX_FILENAME_SIZE];

  if (start <= 0)
    start = 1;
  if (num <= 0)
    return 0;
  if (num > 100)
    num = 100;
  if (CheckFlag(show, SHOW_BLITZ)) {
    GetRankFileName(Path, TYPE_BLITZ);
    fb = (FILE *) fopen(Path, "r");
    if (fb == NULL)
      ClearFlag(show, SHOW_BLITZ);
  }
  if (CheckFlag(show, SHOW_STANDARD)) {
    GetRankFileName(Path, TYPE_STAND);
    fs = (FILE *) fopen(Path, "r");
    if (fs == NULL)
      ClearFlag(show, SHOW_STANDARD);
  }
  if (CheckFlag(show, SHOW_WILD)) {
    GetRankFileName(Path, TYPE_WILD);
    fw = (FILE *) fopen(Path, "r");
    if (fw == 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 i, len = strlen(s);
  int show = 0;

  if (s == NULL || s[0] == '\0')
    return SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD;
  for (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;
}