Fix crash on rated Gothic games
[capablanca.git] / lasker-2.2.3 / src / ratings.c
1 /*
2    Copyright (c) 1993 Richard V. Nash.
3    Copyright (c) 2000 Dan Papasian
4    Copyright (C) Andrew Tridgell 2002
5    
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10    
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15    
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #include "includes.h"
22
23 static double Ratings_B_Average;
24 static double Ratings_B_StdDev;
25
26 static double Ratings_S_Average;
27 static double Ratings_S_StdDev;
28
29 static double Ratings_L_Average;
30 static double Ratings_L_StdDev;
31
32 static double Ratings_Bug_Average;
33 static double Ratings_Bug_StdDev;
34
35 static double Ratings_W_Average;
36 static double Ratings_W_StdDev;
37
38
39 static double Rb_M = 0.0, Rb_S = 0.0, Rb_total = 0.0;
40 static int Rb_count = 0;
41
42 static double Rs_M = 0.0, Rs_S = 0.0, Rs_total = 0.0;
43 static int Rs_count = 0;
44
45 static double Rl_M = 0.0, Rl_S = 0.0, Rl_total = 0.0;
46 static int Rl_count = 0;
47
48 static double Rbug_M = 0.0, Rbug_S = 0.0, Rbug_total = 0.0;
49 static int Rbug_count = 0;
50
51 static double Rw_M = 0.0, Rw_S = 0.0, Rw_total = 0.0;
52 static int Rw_count = 0;
53
54 /*
55 rateStruct bestS[MAX_BEST];
56 int numS = 0;
57 rateStruct bestB[MAX_BEST];
58 int numB = 0;
59 rateStruct bestW[MAX_BEST];
60 int numW = 0;
61 */
62
63 #define MAXHIST 30
64 #define LOWESTHIST 800
65 static int sHist[MAXHIST];
66 static int bHist[MAXHIST];
67 static int wHist[MAXHIST];
68 static int lHist[MAXHIST];
69 static int bugHist[MAXHIST];
70
71 static int DisplayRank(int p, param_list param, int showComputers);
72 static int DisplayTargetRank(int p, char *target, int show, int showComp);
73 static int ShowFromString(char *s);
74 static int DisplayRankedPlayers(int p, int start, int end,
75                                 int show, int showComp);
76 static int Best(int p, param_list param, int ShowComp);
77
78 static int is_active(int Games)
79 {
80         return (Games >= PROVISIONAL);
81 }
82
83
84 static void rating_add(int rating, int type)
85 {
86   int which;
87
88   which = (rating - LOWESTHIST) / 100;
89   if (which < 0)
90     which = 0;
91   if (which >= MAXHIST)
92     which = MAXHIST - 1;
93   if (type == TYPE_BLITZ) {
94     bHist[which] += 1;
95     Rb_count++;
96     Rb_total += rating;
97     if (Rb_count == 1) {
98       Rb_M = rating;
99     } else {
100       Rb_S = Rb_S + (rating - Rb_M) * (rating - Rb_M);
101       Rb_M = Rb_M + (rating - Rb_M) / (Rb_count);
102     }
103     Ratings_B_StdDev = sqrt(Rb_S / Rb_count);
104     Ratings_B_Average = Rb_total / (double) Rb_count;
105   } else if (type == TYPE_WILD || type == TYPE_KNIGHTMATE || type == TYPE_GOTHIC || type == TYPE_CAPABLANCA) {  /* TYPE_WILD */
106     wHist[which] += 1;
107     Rw_count++;
108     Rw_total += rating;
109     if (Rw_count == 1) {
110       Rw_M = rating;
111     } else {
112       Rw_S = Rw_S + (rating - Rw_M) * (rating - Rw_M);
113       Rw_M = Rw_M + (rating - Rw_M) / (Rw_count);
114     }
115     Ratings_W_StdDev = sqrt(Rw_S / Rw_count);
116     Ratings_W_Average = Rw_total / (double) Rw_count;
117   } else if (type == TYPE_LIGHT) {       /* TYPE_LIGHT */
118     lHist[which] += 1;
119     Rl_count++;
120     Rl_total += rating;
121     if (Rl_count == 1) {
122       Rl_M = rating;
123     } else {
124       Rl_S = Rl_S + (rating - Rl_M) * (rating - Rl_M);
125       Rl_M = Rl_M + (rating - Rl_M) / (Rl_count);
126     }
127     Ratings_L_StdDev = sqrt(Rl_S / Rl_count);
128     Ratings_L_Average = Rl_total / (double) Rl_count;
129   } else if (type == TYPE_BUGHOUSE) {       /* TYPE_BUGHOUSE */
130     bugHist[which] += 1;
131     Rbug_count++;
132     Rbug_total += rating;
133     if (Rbug_count == 1) {
134       Rbug_M = rating;
135     } else {
136       Rbug_S = Rbug_S + (rating - Rbug_M) * (rating - Rbug_M);
137       Rbug_M = Rbug_M + (rating - Rbug_M) / (Rbug_count);
138     }
139     Ratings_Bug_StdDev = sqrt(Rbug_S / Rbug_count);
140     Ratings_Bug_Average = Rbug_total / (double) Rbug_count;
141   } else {                      /* TYPE_STAND */
142     sHist[which] += 1;
143     Rs_count++;
144     Rs_total += rating;
145     if (Rs_count == 1) {
146       Rs_M = rating;
147     } else {
148       Rs_S = Rs_S + (rating - Rs_M) * (rating - Rs_M);
149       Rs_M = Rs_M + (rating - Rs_M) / (Rs_count);
150     }
151     Ratings_S_StdDev = sqrt(Rs_S / Rs_count);
152     Ratings_S_Average = Rs_total / (double) Rs_count;
153   }
154 }
155
156 static void rating_remove(int rating, int type)
157 {
158   int which;
159
160   which = (rating - LOWESTHIST) / 100;
161   if (which < 0)
162     which = 0;
163   if (which >= MAXHIST)
164     which = MAXHIST - 1;
165   if (type == TYPE_BLITZ) {
166     bHist[which] = bHist[which] - 1;
167     if (bHist[which] < 0)
168       bHist[which] = 0;
169     if (Rb_count == 0)
170       return;
171     Rb_count--;
172     Rb_total -= rating;
173     if (Rb_count == 0) {
174       Rb_M = 0;
175       Rb_S = 0;
176     } else {
177       Rb_M = Rb_M - (rating - Rb_M) / (Rb_count);
178       Rb_S = Rb_S - (rating - Rb_M) * (rating - Rb_M);
179        /* added this 3.11.95 foxbat */ if (Rb_S < 0)
180         Rb_S = 0;
181     }
182     if (Rb_count) {
183       Ratings_B_StdDev = sqrt(Rb_S / Rb_count);
184       Ratings_B_Average = Rb_total / (double) Rb_count;
185     } else {
186       Ratings_B_StdDev = 0;
187       Ratings_B_Average = 0;
188     }
189   } else if (type == TYPE_WILD || type == TYPE_KNIGHTMATE || type == TYPE_GOTHIC || type == TYPE_CAPABLANCA) {  /* TYPE_WILD */
190     wHist[which] = wHist[which] - 1;
191     if (wHist[which] < 0)
192       wHist[which] = 0;
193     if (Rw_count == 0)
194       return;
195     Rw_count--;
196     Rw_total -= rating;
197     if (Rw_count == 0) {
198       Rw_M = 0;
199       Rw_S = 0;
200     } else {
201       Rw_M = Rw_M - (rating - Rw_M) / (Rw_count);
202       Rw_S = Rw_S - (rating - Rw_M) * (rating - Rw_M);
203        /* added this 3.10.95 foxbat */ if (Rw_S < 0)
204         Rw_S = 0;
205     }
206     if (Rw_count) {
207       Ratings_W_StdDev = sqrt(Rw_S / Rw_count);
208       Ratings_W_Average = Rw_total / (double) Rw_count;
209     } else {
210       Ratings_W_StdDev = 0;
211       Ratings_W_Average = 0;
212     }
213   } else if (type == TYPE_LIGHT) {       /* TYPE_LIGHT */
214     lHist[which] = lHist[which] - 1;
215     if (lHist[which] < 0)
216       lHist[which] = 0;
217     if (Rl_count == 0)
218       return;
219     Rl_count--;
220     Rl_total -= rating;
221     if (Rl_count == 0) {
222       Rl_M = 0;
223       Rl_S = 0;
224     } else {
225       Rl_M = Rl_M - (rating - Rl_M) / (Rl_count);
226       Rl_S = Rl_S - (rating - Rl_M) * (rating - Rl_M);
227       if (Rl_S < 0)
228         Rl_S = 0;
229     }
230     if (Rl_count) {
231       Ratings_L_StdDev = sqrt(Rl_S / Rl_count);
232       Ratings_L_Average = Rl_total / (double) Rl_count;
233     } else {
234       Ratings_L_StdDev = 0;
235       Ratings_L_Average = 0;
236     }
237   } else if (type == TYPE_BUGHOUSE) {       /* TYPE_BUGHOUSE */
238     bugHist[which] = bugHist[which] - 1;
239     if (bugHist[which] < 0)
240       bugHist[which] = 0;
241     if (Rbug_count == 0)
242       return;
243     Rbug_count--;
244     Rbug_total -= rating;
245     if (Rbug_count == 0) {
246       Rbug_M = 0;
247       Rbug_S = 0;
248     } else {
249       Rbug_M = Rbug_M - (rating - Rbug_M) / (Rbug_count);
250       Rbug_S = Rbug_S - (rating - Rbug_M) * (rating - Rbug_M);
251       if (Rbug_S < 0)
252         Rbug_S = 0;
253     }
254     if (Rbug_count) {
255       Ratings_Bug_StdDev = sqrt(Rbug_S / Rbug_count);
256       Ratings_Bug_Average = Rbug_total / (double) Rbug_count;
257     } else {
258       Ratings_Bug_StdDev = 0;
259       Ratings_Bug_Average = 0;
260     }
261   } else {                      /* TYPE_STAND */
262     sHist[which] = sHist[which] - 1;
263     if (sHist[which] < 0)
264       sHist[which] = 0;
265     if (Rs_count == 0)
266       return;
267     Rs_count--;
268     Rs_total -= rating;
269     if (Rs_count == 0) {
270       Rs_M = 0;
271       Rs_S = 0;
272     } else {
273       Rs_M = Rs_M - (rating - Rs_M) / (Rs_count);
274       Rs_S = Rs_S - (rating - Rs_M) * (rating - Rs_M);
275        /* added this 3.10.95 foxbat */ if (Rs_S < 0)
276         Rs_S = 0;
277     }
278     if (Rs_count) {
279       Ratings_S_StdDev = sqrt(Rs_S / Rs_count);
280       Ratings_S_Average = Rs_total / (double) Rs_count;
281     } else {
282       Ratings_S_StdDev = 0;
283       Ratings_S_Average = 0;
284     }
285   }
286 }
287
288 static void load_ratings(void)
289 {
290         FILE *fp;
291         int i;
292
293         fp = fopen_p("%s/newratingsV%d_data", "r", STATS_DIR, STATS_VERSION);
294         if (!fp) {
295                 d_printf( "CHESSD: Can't read ratings data!\n");
296                 return;
297         }
298
299         fscanf(fp, "%lf %lf %lf %d", &Rb_M, &Rb_S, &Rb_total, &Rb_count);
300         fscanf(fp, "%lf %lf %lf %d", &Rs_M, &Rs_S, &Rs_total, &Rs_count);
301         fscanf(fp, "%lf %lf %lf %d", &Rw_M, &Rw_S, &Rw_total, &Rw_count);
302         fscanf(fp, "%lf %lf %lf %d", &Rl_M, &Rl_S, &Rl_total, &Rl_count);
303         
304         for (i = 0; i < MAXHIST; i++) {
305                 fscanf(fp, "%d %d %d %d", &sHist[i], &bHist[i], &wHist[i], &lHist[i]);
306         }
307         fclose(fp);
308         if (Rs_count) {
309                 Ratings_S_StdDev = sqrt(Rs_S / Rs_count);
310                 Ratings_S_Average = Rs_total / (double) Rs_count;
311         } else {
312                 Ratings_S_StdDev = 0;
313                 Ratings_S_Average = 0;
314         }
315         if (Rb_count) {
316                 Ratings_B_StdDev = sqrt(Rb_S / Rb_count);
317                 Ratings_B_Average = Rb_total / (double) Rb_count;
318         } else {
319                 Ratings_B_StdDev = 0;
320                 Ratings_B_Average = 0;
321         }
322         if (Rw_count) {
323                 Ratings_W_StdDev = sqrt(Rw_S / Rw_count);
324                 Ratings_W_Average = Rw_total / (double) Rw_count;
325         } else {
326                 Ratings_W_StdDev = 0;
327                 Ratings_W_Average = 0;
328         }
329         if (Rl_count) {
330                 Ratings_L_StdDev = sqrt(Rl_S / Rl_count);
331                 Ratings_L_Average = Rl_total / (double) Rl_count;
332         } else {
333                 Ratings_L_StdDev = 0;
334                 Ratings_L_Average = 0;
335         }
336 }
337
338 static void save_ratings(void)
339 {
340         FILE *fp;
341         int i;
342         
343         fp = fopen_p("%s/newratingsV%d_data", "w", STATS_DIR,STATS_VERSION);
344         if (!fp) {
345                 d_printf( "CHESSD: Can't write ratings data!\n");
346                 return;
347         }
348         
349         fprintf(fp, "%10f %10f %10f %d\n", Rb_M, Rb_S, Rb_total, Rb_count);
350         fprintf(fp, "%10f %10f %10f %d\n", Rs_M, Rs_S, Rs_total, Rs_count);
351         fprintf(fp, "%10f %10f %10f %d\n", Rw_M, Rw_S, Rw_total, Rw_count);
352         fprintf(fp, "%10f %10f %10f %d\n", Rl_M, Rl_S, Rl_total, Rl_count);
353         
354         for (i = 0; i < MAXHIST; i++) {
355                 fprintf(fp, "%d %d %d %d\n", sHist[i], bHist[i], wHist[i], lHist[i]);
356         }
357         fclose(fp);
358 }
359
360 static void zero_stats(void)
361 {
362         int i;
363         for (i = 0; i < MAXHIST; i++) {
364                 sHist[i] = 0;
365                 bHist[i] = 0;
366                 wHist[i] = 0;
367                 lHist[i] = 0;
368         }
369         Rb_M = 0.0, Rb_S = 0.0, Rb_total = 0.0;
370         Rb_count = 0;
371         
372         Rs_M = 0.0, Rs_S = 0.0, Rs_total = 0.0;
373         Rs_count = 0;
374         
375         Rw_M = 0.0, Rw_S = 0.0, Rw_total = 0.0;
376         Rw_count = 0;
377         
378         Rl_M = 0.0, Rl_S = 0.0, Rl_total = 0.0;
379         Rl_count = 0;
380 }
381
382 void ratings_init(void)
383 {
384         zero_stats();
385         load_ratings();
386 }
387
388 static int Round (double x)
389 {
390   return (x < 0   ?   (int) (x - 0.5)   :   (int) (x + 0.5));
391 }
392
393 /*  Constants for Glicko system */
394 #define Gd 1.483
395 #define Gv 0.0005828166666667  /* (0.187)^2 / 60 */
396 #define Gr0 1720
397 #define Gs0 350
398 #define Gq 0.00575646273249       /* ln(10.0)/400.0 */
399 #define Gp 0.000010072398601964   /* (3 * Gq / PI)^2 */
400 #define GBq 0.00287823136624      /* ln(10.0)/800.0 */
401 #define GBp (9 * GBq * GBq / (3.141592653589 * 3.141592653589))
402 /* End of Glicko system variables */
403
404 static double Gf(double ss)
405 {
406   return (1.0 / sqrt(1.0 + Gp * ss * ss));
407 }
408
409 /* Confusing but economical: calculate error and attenuation function together */
410 static double GE(int r, int rr, double ss, double *fss)
411 {
412   *fss = Gf(ss);
413   return (1.0 / (1.0 + pow(10.0, (rr - r) * (*fss) / 400.0)));
414 }
415
416 static double GBf(double sp, double ss, double ssp)
417 {
418   return (1.0 / sqrt(1.0 + GBp * (ss*ss + sp*sp + ssp*ssp)));
419 }
420
421 /* Confusing but economical: calculate error and attenuation function together */
422 static double GBE(int r, int rp, int rr, int rrp,
423                    double sp, double ss, double ssp, double *fss)
424 {
425   *fss = GBf(sp, ss, ssp);
426   return (1.0 / (1.0 + pow(10.0, (rr + rrp - r - rp) * (*fss) / 800.0)));
427 }
428
429 double current_sterr(double s, long t)
430 {
431   double new;
432
433   if (t < 0)
434     t = 0;                      /* this shouldn't happen */
435   new = sqrt(s * s + Gd * Gd * log(1.0 + t / 60.0));    /* log formula */
436   /*  new = sqrt(s * s + Gv * t);  */                /* linear formula */
437   if (new > Gs0)
438     new = Gs0;
439   return (new);
440 }
441
442 static void UpdateStats(struct statistics *p_stats, int *r, double *s, int now)
443 {
444   /* Calculate effective pre-game sterrs.  ltime==0 implies never had sterr. */
445
446   *s = current_sterr(p_stats->sterr, now - p_stats->ltime);
447
448   /* pre-game ratings */
449   *r = (p_stats->rating == 0 && p_stats->num == 0)  ?  Gr0
450                                                     :  p_stats->rating;
451   return;
452 }
453
454 static void GetCurrentStats (int p, int *r, double *s, int *new,
455                       int *rPart, double *sPart, int type, int now)
456 {
457   struct player *pp = &player_globals.parray[p];
458   struct statistics *p_stats = NULL;
459   struct statistics *part_stats;     /* for bughouse. */
460
461   switch (type) {
462     case TYPE_BLITZ:
463       p_stats = &pp->b_stats;
464       break;
465     case TYPE_STAND:
466       p_stats = &pp->s_stats;
467       break;
468     case TYPE_WILD:
469     case TYPE_KNIGHTMATE:
470     case TYPE_CAPABLANCA:
471     case TYPE_GOTHIC:
472       p_stats = &pp->w_stats;
473       break;
474     case TYPE_LIGHT:
475       p_stats = &pp->l_stats;
476       break;
477     case TYPE_BUGHOUSE:
478       p_stats = &pp->bug_stats;
479       part_stats = &player_globals.parray[pp->partner].bug_stats;
480       UpdateStats(part_stats, rPart, sPart, now);
481       break;
482   }
483   /* Calculate effective pre-game sterrs.  ltime==0 implies never had sterr. */
484   UpdateStats(p_stats, r, s, now);
485   if (p_stats->rating == 0 && p_stats->num == 0)
486     *new = 1;
487   else *new = 0;
488 }
489
490 /* Calculates new rating and standard error.  By vek.  The person       */
491 /*   who invented the ratings system is Mark E. Glickman, Ph.D.         */
492 /*   His e-mail address is glickman@hustat.harvard.edu as of April '95. */
493 /*   Postscript copy of the note I coded this from should be available  */
494 /*   for ftp from ics.onenet.net, if not elsewhere.                     */
495
496 void rating_sterr_delta(int p1, int p2, int type, int gtime, int result,
497                                 int *deltarating, double *newsterr)
498 {
499
500   double s1, s2, s1part, s2part;
501   int r1, r2, r1part, r2part, new;      /* Initial sterrs and ratings */
502   double q, E, fs2, denominator, GK, w; /* Parts of fancy formulas */
503   double delta;                         /* Result to return */
504
505   GetCurrentStats (p2, &r2, &s2, &new, &r2part, &s2part, type, gtime);
506   GetCurrentStats (p1, &r1, &s1, &new, &r1part, &s1part, type, gtime);
507
508   /* now crunch */
509   if (result == RESULT_WIN) {
510     w = 1.0;
511   } else if (result == RESULT_DRAW) {
512     w = 0.5;
513   } else {
514     w = 0.0;
515   }
516   if (type != TYPE_BUGHOUSE) {
517     q = Gq;
518     E = GE(r1, r2, s2, &fs2);   /* side effect: calculate fs2 */
519   } else {
520     q = GBq;
521     E = GBE(r1, r1part, r2, r2part, s1part, s2, s2part, &fs2);
522   }
523   denominator = 1.0 / (s1 * s1) + q * q * fs2 * fs2 * E * (1.0 - E);
524   GK = q * fs2 / denominator;
525
526   delta = GK * (w - E);
527   if (new)
528     *deltarating = Round(Gr0 + delta);
529   else
530     *deltarating = Round(delta);        /* Returned values: deltarating,
531                                            newsterr */
532   *newsterr = 1.0 / sqrt(denominator);
533 }
534
535 int rating_update(int g, int link_game)
536 {
537   int wDelta, bDelta;
538   double wSigma, bSigma;        /* vek */
539
540   int wRes, bRes;
541   struct statistics *w_stats;
542   struct statistics *b_stats;
543
544   int gtime;
545
546   int inprogress = (g == player_globals.parray[game_globals.garray[g].black].game);
547   /* if this is adjudication of stored game, be quiet about ratings change */
548
549   if (game_globals.garray[g].type == TYPE_BLITZ) {
550     w_stats = &player_globals.parray[game_globals.garray[g].white].b_stats;
551     b_stats = &player_globals.parray[game_globals.garray[g].black].b_stats;
552   } else if (game_globals.garray[g].type == TYPE_STAND) {
553     w_stats = &player_globals.parray[game_globals.garray[g].white].s_stats;
554     b_stats = &player_globals.parray[game_globals.garray[g].black].s_stats;
555   } else if (game_globals.garray[g].type == TYPE_WILD
556           || game_globals.garray[g].type == TYPE_KNIGHTMATE
557           || game_globals.garray[g].type == TYPE_GOTHIC
558           || game_globals.garray[g].type == TYPE_CAPABLANCA) {
559     w_stats = &player_globals.parray[game_globals.garray[g].white].w_stats;
560     b_stats = &player_globals.parray[game_globals.garray[g].black].w_stats;
561   } else if (game_globals.garray[g].type == TYPE_LIGHT) {
562     w_stats = &player_globals.parray[game_globals.garray[g].white].l_stats;
563     b_stats = &player_globals.parray[game_globals.garray[g].black].l_stats;
564   } else if (game_globals.garray[g].type == TYPE_BUGHOUSE) {
565     w_stats = &player_globals.parray[game_globals.garray[g].white].bug_stats;
566     b_stats = &player_globals.parray[game_globals.garray[g].black].bug_stats;
567   } else {
568     d_printf( "CHESSD: Can't update untimed ratings!\n");
569     return -1;
570   }
571
572   switch (game_globals.garray[g].result) {
573   case END_CHECKMATE:
574   case END_RESIGN:
575   case END_FLAG:
576   case END_ADJWIN:
577     if (game_globals.garray[g].winner == WHITE) {
578       wRes = RESULT_WIN;
579       bRes = RESULT_LOSS;
580     } else {
581       bRes = RESULT_WIN;
582       wRes = RESULT_LOSS;
583     }
584     break;
585   case END_AGREEDDRAW:
586   case END_REPETITION:
587   case END_50MOVERULE:
588   case END_STALEMATE:
589   case END_NOMATERIAL:
590   case END_BOTHFLAG:
591   case END_ADJDRAW:
592   case END_FLAGNOMATERIAL:
593     wRes = bRes = RESULT_DRAW;
594     break;
595   default:
596     d_printf( "CHESSD: Update undecided game %d?\n", game_globals.garray[g].result);
597     return -1;
598   }
599   /* Use end time, not start time; important for adjourned games. */
600   /*  gtime = untenths(game_globals.garray[g].timeOfStart); */
601   gtime = time(0);
602   rating_sterr_delta(game_globals.garray[g].white, game_globals.garray[g].black,
603                      game_globals.garray[g].type, gtime, wRes,
604                      &wDelta, &wSigma);
605
606   rating_sterr_delta(game_globals.garray[g].black, game_globals.garray[g].white,
607                      game_globals.garray[g].type, gtime, bRes,
608                      &bDelta, &bSigma);
609
610   /* hersco, 1/23/96: We now know rating changes for these players;
611      update partners' game. */
612   if (link_game >= 0) {
613     game_globals.garray[link_game].winner = CToggle(game_globals.garray[g].winner);
614     game_globals.garray[link_game].result = game_globals.garray[g].result;
615     rating_update (link_game, -1);    /* but don't recurse any deeper. */
616   }
617   /* vek: Update time of last rated game played, for future ratings calcs. */
618   /* Kept independently for blitz and standard.                       */
619   w_stats->ltime = gtime;
620   b_stats->ltime = gtime;
621   /* end vek add 4/5/95 */
622
623   if (wRes == RESULT_WIN) {
624     w_stats->win++;
625   } else if (wRes == RESULT_LOSS) {
626     w_stats->los++;
627   } else {
628     w_stats->dra++;
629   }
630   w_stats->num++;
631   if (bRes == RESULT_WIN) {
632     b_stats->win++;
633   } else if (bRes == RESULT_LOSS) {
634     b_stats->los++;
635   } else {
636     b_stats->dra++;
637   }
638   b_stats->num++;
639   rating_remove(w_stats->rating, game_globals.garray[g].type);
640   rating_remove(b_stats->rating, game_globals.garray[g].type);
641
642   if (inprogress) {
643     pprintf(game_globals.garray[g].white, "%s rating adjustment: %d ",
644             TypeStrings[game_globals.garray[g].type], w_stats->rating);
645     pprintf(game_globals.garray[g].black, "%s rating adjustment: %d ",
646             TypeStrings[game_globals.garray[g].type], b_stats->rating);
647   }
648   if (wDelta < -1000) {
649     pprintf(game_globals.garray[g].white, "not changed due to bug (way too small)! sorry!\n");
650     d_printf( "CHESSD: Got too small ratings bug for %s (w) vs. %s\n",
651             player_globals.parray[game_globals.garray[g].white].login, player_globals.parray[game_globals.garray[g].black].login);
652   } else if (wDelta > 3000) {
653     pprintf(game_globals.garray[g].white, "not changed due to bug (way too big)! sorry!\n");
654     d_printf( "CHESSD: Got too big ratings bug for %s (w) vs. %s\n",
655             player_globals.parray[game_globals.garray[g].white].login, player_globals.parray[game_globals.garray[g].black].login);
656   } else {
657     w_stats->rating += wDelta;
658     w_stats->sterr = wSigma;
659   }
660
661   if (bDelta < -1000) {
662     pprintf(game_globals.garray[g].black, "not changed due to bug (way too small)! sorry! ");
663     d_printf( "CHESSD: Got too small ratings bug for %s (b) vs. %s\n",
664             player_globals.parray[game_globals.garray[g].black].login, player_globals.parray[game_globals.garray[g].white].login);
665   } else if (bDelta > 3000) {
666     pprintf(game_globals.garray[g].black, "not changed due to bug (way too big)! sorry! ");
667     d_printf( "CHESSD: Got too big ratings bug for %s (b) vs. %s\n",
668             player_globals.parray[game_globals.garray[g].black].login, player_globals.parray[game_globals.garray[g].white].login);
669   } else {
670     b_stats->rating += bDelta;
671     b_stats->sterr = bSigma;
672   }                             /* error messages down to vek */
673
674   rating_add(w_stats->rating, game_globals.garray[g].type);
675   rating_add(b_stats->rating, game_globals.garray[g].type);
676
677   if ((w_stats->rating > w_stats->best) && (is_active(w_stats->num))) {
678     w_stats->best = w_stats->rating;
679     w_stats->whenbest = time(NULL);
680   }
681   if ((b_stats->rating > b_stats->best) && (is_active(b_stats->num))) {
682     b_stats->best = b_stats->rating;
683     b_stats->whenbest = time(NULL);
684   }
685
686 /* ratings are saved later - DAV */
687
688 /* foxbat 3.11.95 */
689   if (game_globals.garray[g].type == TYPE_BLITZ) {
690     Rb_count++;
691     Rb_total += (w_stats->rating + b_stats->rating) / 2.0;
692   } else if (game_globals.garray[g].type == TYPE_STAND) {
693     Rs_count++;
694     Rs_total += (w_stats->rating + b_stats->rating) / 2.0;
695   } else if (game_globals.garray[g].type == TYPE_LIGHT) {
696     Rl_count++;
697     Rl_total += (w_stats->rating + b_stats->rating) / 2.0;
698   } else if (game_globals.garray[g].type == TYPE_BUGHOUSE) {
699     Rbug_count++;
700     Rbug_total += (w_stats->rating + b_stats->rating) / 2.0;
701   } else if (game_globals.garray[g].type == TYPE_WILD
702           || game_globals.garray[g].type == TYPE_KNIGHTMATE
703           || game_globals.garray[g].type == TYPE_CAPABLANCA
704           || game_globals.garray[g].type == TYPE_GOTHIC) {
705     Rw_count++;
706     Rw_total += (w_stats->rating + b_stats->rating) / 2.0;
707   }
708 /* end add */
709   if (inprogress) {
710     pprintf(game_globals.garray[g].white, "--> %d\n", w_stats->rating);
711     pprintf(game_globals.garray[g].black, "--> %d\n", b_stats->rating);
712   }
713   save_ratings();
714   UpdateRank(game_globals.garray[g].type, player_globals.parray[game_globals.garray[g].white].name,
715              w_stats, player_globals.parray[game_globals.garray[g].white].name);
716   UpdateRank(game_globals.garray[g].type, player_globals.parray[game_globals.garray[g].black].name,
717              b_stats, player_globals.parray[game_globals.garray[g].black].name);
718   return 0;
719 }
720
721 static void ShowAssess (int p, int p1, int p2, int type1, int type2)
722 {
723   int p3, p4;
724   int win1, win2, win3, win4;
725   int draw1, draw2, draw3, draw4;
726   int loss1, loss2, loss3, loss4;
727   double newsterr1, newsterr2, newsterr3, newsterr4;
728   int nowtime = time(0);
729   char line[100];        /* should never need any more than 80. */
730
731   if (type1 == TYPE_BUGHOUSE) {
732     p3 = player_globals.parray[p1].partner;
733     p4 = player_globals.parray[p2].partner;
734     type2 = type1;
735     if (!CheckPFlag(p3, PFLAG_REG) || !CheckPFlag(p4, PFLAG_REG)) {
736       pprintf (p, "Bughouse with unregistered partners cannot be rated.\n");
737       return;
738     }
739   } else {
740     p3 = p1;
741     p4 = p2;
742   }
743   rating_sterr_delta(p1, p2, type1, nowtime, RESULT_WIN, &win1, &newsterr1);
744   rating_sterr_delta(p1, p2, type1, nowtime, RESULT_DRAW, &draw1, &newsterr1);
745   rating_sterr_delta(p1, p2, type1, nowtime, RESULT_LOSS, &loss1, &newsterr1);
746   rating_sterr_delta(p2, p1, type1, nowtime, RESULT_WIN, &win2, &newsterr2);
747   rating_sterr_delta(p2, p1, type1, nowtime, RESULT_DRAW, &draw2, &newsterr2);
748   rating_sterr_delta(p2, p1, type1, nowtime, RESULT_LOSS, &loss2, &newsterr2);
749
750   if (type2 >= 0) {
751     rating_sterr_delta(p3,p4, type2, nowtime,RESULT_WIN, &win3, &newsterr3);
752     rating_sterr_delta(p3,p4, type2, nowtime,RESULT_DRAW, &draw3, &newsterr3);
753     rating_sterr_delta(p3,p4, type2, nowtime,RESULT_LOSS, &loss3, &newsterr3);
754     rating_sterr_delta(p4,p3, type2, nowtime,RESULT_WIN, &win4, &newsterr4);
755     rating_sterr_delta(p4,p3, type2, nowtime,RESULT_DRAW, &draw4, &newsterr4);
756     rating_sterr_delta(p4,p3, type2, nowtime,RESULT_LOSS, &loss4, &newsterr4);
757   }
758   sprintf (line, "%7s", "");
759   if (type1 != TYPE_BUGHOUSE) {
760     CenterText (&line[7], TypeStrings[type1], 35, 1);
761     if (type2 >= 0) {
762       strcat (line, "  ");
763       CenterText (&line[44], TypeStrings[type2], 35, 0);
764     }
765   }
766   else {
767     CenterText (&line[7], "Bughouse", 72, 0);
768   }
769   pprintf (p, "\n%s\n", line);
770
771   sprintf (line, "%7s", "");
772   CenterText (&line[7], player_globals.parray[p1].name, 17, 1);
773   strcat (line, " ");
774   CenterText (&line[25], player_globals.parray[p2].name, 17, 1);
775   if (type2 >= 0) {
776     strcat (line, "  ");
777     CenterText (&line[44], player_globals.parray[p3].name, 17, 1);
778     strcat (line, " ");
779     CenterText (&line[62], player_globals.parray[p4].name, 17, 0);
780   }
781   pprintf (p, "%s\n", line);
782   pprintf (p, "%7s (%4s RD %5.1f) ",
783            "", ratstrii(GetRating(&player_globals.parray[p1], type1), p1),
784                GetRD(&player_globals.parray[p1], type1));
785   pprintf (p, "  (%4s RD %5.1f)",
786               ratstrii(GetRating(&player_globals.parray[p2], type1), p2),
787               GetRD(&player_globals.parray[p2], type1));
788   if (type2 >= 0) {
789     pprintf (p, "    (%4s RD %5.1f)",
790                 ratstrii(GetRating(&player_globals.parray[p3], type2), p3),
791                 GetRD(&player_globals.parray[p3], type2));
792     pprintf (p, "   (%4s RD %5.1f)",
793                 ratstrii(GetRating(&player_globals.parray[p4], type2), p4),
794                 GetRD(&player_globals.parray[p4], type2));
795   }
796   pprintf (p, "\n%7s%9d %17d", "Win:", win1, loss2);
797   if (type2 >= 0)
798     pprintf (p, "  %17d %17d", win3, loss4);
799   pprintf (p, "\n%7s%9d %17d", "Draw:", draw1, draw2);
800   if (type2 >= 0)
801     pprintf (p, "  %17d %17d", draw3, draw4);
802   pprintf (p, "\n%7s%9d %17d", "Loss:", loss1, win2);
803   if (type2 >= 0)
804     pprintf (p, "  %17d %17d", loss3, win4);
805   pprintf (p, "\n%7s%10.1f %17.1f", "New RD:", newsterr1, newsterr2);
806   if (type2 >= 0)
807     pprintf (p, "  %17.1f %17.1f", newsterr3, newsterr4);
808   pprintf(p, "\n");
809 }
810
811 int com_assess(int p, param_list param)
812 {
813   struct player *pp = &player_globals.parray[p];
814   int p1 = p, p2, g, gametype = -1;
815   int p1_connected = 1, p2_connected = 1;
816
817 /* Hawk: Now assess can be used with players not  */
818 /*       logged on -- I wonder if anyone doesn't  */
819 /*       get just a bit confused here :)          */
820
821   if (param[0].type == TYPE_NULL) {
822     g = pp->game;
823     if (g < 0) {
824       pprintf(p, "You are not playing or examining a game.\n");
825       return COM_OK;
826     } else if ((game_globals.garray[g].status == GAME_EXAMINE) || (game_globals.garray[g].status == GAME_SETUP)) {
827       gametype = game_globals.garray[g].type;
828       if (!strcmp(game_globals.garray[g].black_name, pp->name)) {
829         if (game_globals.garray[g].rated) {
830           pcommand(p, "assess %s %s %s\n", game_globals.garray[g].black_name,
831                       game_globals.garray[g].white_name, TypeStrings[gametype]);
832           return COM_OK_NOPROMPT;
833         } else {
834           pprintf(p, "You are not involved in a rated game.\n");
835         }
836       } else {
837         if (game_globals.garray[g].rated) {
838           pcommand(p, "assess %s %s %s\n", game_globals.garray[g].white_name,
839                       game_globals.garray[g].black_name, TypeStrings[gametype]);
840           return COM_OK_NOPROMPT;
841         } else {
842           pprintf(p, "You are not involved in a rated game.\n");
843         }
844       }
845       return COM_OK;
846     } else {
847       p2 = pp->opponent;
848       gametype = game_globals.garray[g].type;
849     }
850   } else if (param[0].type == TYPE_INT) {
851     if (param[1].type != TYPE_NULL) {
852       pprintf(p, "Assess <game number> cannot use other parameters.\n");
853       return COM_OK;
854     }
855     g = param[0].val.integer - 1;
856     if (g < 0 || g >= game_globals.g_num) {
857       pprintf (p, "There is no game %d.\n", g+1);
858       return COM_OK;
859     }
860     gametype = game_globals.garray[g].type;
861     pcommand(p, "assess %s %s %s\n", game_globals.garray[g].white_name,
862                  game_globals.garray[g].black_name, TypeStrings[gametype]);
863     return COM_OK_NOPROMPT;
864   } else {
865     if (!FindPlayer(p, param[0].val.word, &p2, &p2_connected)) {
866       pprintf(p, "No user named \"%s\" was found.\n", param[0].val.word);
867       return COM_OK;
868     }
869     if (param[1].type != TYPE_NULL) {
870       p1 = p2;
871       p1_connected = p2_connected;
872       if (!FindPlayer(p, param[1].val.word, &p2, &p2_connected)) {
873         pprintf(p, "No user named \"%s\" was found.\n", param[1].val.word);
874         if (!p1_connected)
875           player_remove(p1);
876         return COM_OK;
877       }
878       if (param[2].type == TYPE_WORD) {
879         param[2].val.word[0] = toupper(param[2].val.word[0]);
880         for (gametype = 0; gametype < NUM_GAMETYPES; gametype++)
881           if (!strcmp(TypeStrings[gametype], param[2].val.word))
882             break;
883       }
884     }
885   }
886   if (gametype >= NUM_GAMETYPES) {
887     pprintf(p, "There is no game type %s.\n", param[2].val.word);
888   } else if (p1 == p2) {
889     pprintf(p, "You can't assess the same players.\n");
890   } else if (!CheckPFlag(p1, PFLAG_REG) || !CheckPFlag(p2, PFLAG_REG)) {
891     pprintf(p, "You can only assess games between registered players.\n");
892   } else if (gametype >= 0) {
893     if (gametype == TYPE_BUGHOUSE
894         && (player_globals.parray[p1].partner < 0 || player_globals.parray[p2].partner < 0)) {
895       pprintf(p, "You can only assess bughouse games if both players have partners.\n");
896     } else if (gametype == TYPE_UNTIMED || gametype == TYPE_NONSTANDARD) {
897       pprintf (p, "%s games are never rated.\n", TypeStrings[gametype]);
898     } else {
899       ShowAssess (p, p1, p2, gametype, -1);
900     }
901   } else {
902     ShowAssess (p, p1, p2, TYPE_BLITZ, TYPE_STAND);
903     ShowAssess (p, p1, p2, TYPE_LIGHT, TYPE_WILD);
904     if (player_globals.parray[p1].partner >= 0 && player_globals.parray[p2].partner >= 0
905         && player_globals.parray[p1].partner != p2)
906       ShowAssess (p, p1, p2, TYPE_BUGHOUSE, TYPE_BUGHOUSE);
907   }
908   if (!p1_connected)
909       player_remove(p1);
910   if (!p2_connected)
911     player_remove(p2);
912   return COM_OK;
913 }
914
915
916 int com_best(int p, param_list param)
917 {
918   return Best(p, param, 1);
919 }
920
921 int com_hbest(int p, param_list param)
922 {
923   return Best(p, param, 0);
924 }
925
926 #if 0
927 int com_best(int p, param_list param)
928 {
929   int i;
930
931   pprintf(p, "Standard                Blitz                   Wild\n");
932   for (i = 0; i < MAX_BEST; i++) {
933     if ((i >= numS) && (i >= numB))
934       break;
935     if (i < numS) {
936       pprintf(p, "%4d %-17s  ", bestS[i].rating, bestS[i].name);
937     } else {
938       pprintf(p, "                        ");
939     }
940     if (i < numB) {
941       pprintf(p, "%4d %-17s  ", bestB[i].rating, bestB[i].name);
942     } else {
943       pprintf(p, "                        ");
944     }
945     if (i < numW) {
946       pprintf(p, "%4d %-17s\n", bestW[i].rating, bestW[i].name);
947     } else {
948       pprintf(p, "\n");
949     }
950   }
951   return COM_OK;
952 }
953 #endif
954
955 int com_statistics(int p, param_list param)
956 {
957   pprintf(p, "                Standard       Blitz   Lightning        Wild\n");
958   pprintf(p, "average:         %7.2f     %7.2f     %7.2f     %7.2f\n", Ratings_S_Average, Ratings_B_Average, Ratings_L_Average, Ratings_W_Average);
959   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);
960   pprintf(p, "number :      %7d     %7d     %7d     %7d\n", Rs_count, Rb_count,  Rl_count, Rw_count);
961   return COM_OK;
962 }
963
964 int com_fixrank(int p, param_list param)
965 {
966   int p1, connected;
967
968   if (!FindPlayer(p, param[0].val.word, &p1, &connected))
969     return COM_OK;
970   UpdateRank(TYPE_BLITZ, player_globals.parray[p1].name, &player_globals.parray[p1].b_stats,
971              player_globals.parray[p1].name);
972   UpdateRank(TYPE_STAND, player_globals.parray[p1].name, &player_globals.parray[p1].s_stats,
973              player_globals.parray[p1].name);
974   UpdateRank(TYPE_WILD, player_globals.parray[p1].name, &player_globals.parray[p1].w_stats,
975              player_globals.parray[p1].name);
976   if (!connected)
977     player_remove(p1);
978   return COM_OK;
979 }
980
981 int com_rank(int p, param_list param)
982 {
983         return DisplayRank(p, param, 1);
984 }
985
986 int com_hrank(int p, param_list param)
987 {
988   return DisplayRank(p, param, 0);
989 }
990
991 static int DisplayRank(int p, param_list param, int showComputers)
992 {
993   struct player *pp = &player_globals.parray[p];
994   int start, end, target, connected;
995   int show = SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD;
996
997   if (param[0].type == TYPE_NULL) {
998     DisplayTargetRank(p, pp->name, show, showComputers);
999     return COM_OK;
1000   } else if (isdigit(param[0].val.word[0])) {
1001     end = -1;
1002     sscanf(param[0].val.word, "%d-%d", &start, &end);
1003     if (end > 0 && (param[1].type != TYPE_NULL))
1004       show = ShowFromString(param[1].val.word);
1005     DisplayRankedPlayers(p, start, end, show, showComputers);
1006     return COM_OK;
1007   } else {
1008     target = player_search(p, param[0].val.word);
1009     if (target == 0) {
1010       pprintf(p, "Target %s not found.\n", param[0].val.word);
1011       return COM_OK;
1012     }
1013     connected = (target > 0);
1014     if (!connected)
1015       target = -target - 1;
1016     else
1017       target--;
1018
1019     if (param[1].type != TYPE_NULL)
1020       show = ShowFromString(param[1].val.word);
1021     DisplayTargetRank(p, player_globals.parray[target].name, show, showComputers);
1022     if (!connected)
1023       player_remove(target);
1024     return COM_OK;
1025   }
1026 }
1027
1028 /* CompareStats returns 1 if s1 comes first, -1 if s2 comes first, and 0
1029    if neither takes precedence. */
1030 #if 0
1031 static int CompareStats(char *name1, statistics *s1,
1032                           char *name2, statistics *s2)
1033 {
1034   int i, l1;
1035
1036   if (s1 == NULL)
1037     if (s2 == NULL)
1038       return 0;
1039     else
1040       return -1;
1041   else if (s2 == NULL)
1042     return 1;
1043
1044   if (s1->rating > s2->rating)
1045     return 1;
1046   if (s1->rating < s2->rating)
1047     return -1;
1048   l1 = strlen(name1);
1049   for (i = 0; i < l1; i++) {
1050     if (name2[i] == '\0')
1051       return -1;
1052     if (tolower(name1[i]) < tolower(name2[i]))
1053       return 1;
1054     if (tolower(name1[i]) > tolower(name2[i]))
1055       return -1;
1056   }
1057   if (name2[i] != '\0')
1058     return 1;
1059 /*  if (s1->sterr < s2->sterr) return 1;
1060   if (s1->sterr > s2->sterr) return -1;
1061   if (s1->num > s2->num) return 1;
1062   if (s1->num < s2->num) return -1;
1063 */
1064   d_printf( "Duplicate entries found: %s.\n", name1);
1065   return 0;
1066 }
1067 #endif
1068 static int GetRankFileName(char *out, int type)
1069 {
1070   switch (type) {
1071     case TYPE_BLITZ:
1072         sprintf(out, "%s/rank.blitz", STATS_DIR);
1073         return type;
1074     case TYPE_STAND:
1075         sprintf(out, "%s/rank.std", STATS_DIR);
1076         return type;
1077     case TYPE_WILD:
1078         sprintf(out, "%s/rank.wild", STATS_DIR);
1079         return type;
1080     default:
1081         return -1;
1082   }
1083 }
1084
1085 /* loon: Turning this off 28 Oct 1995 (temporary:)) since we're lagged
1086    into outer space */
1087 void UpdateRank(int type, char *addName,
1088                 struct statistics *sNew, char *delName)
1089 {}
1090
1091 #if 0
1092 void UpdateRank(int type, char *addName,
1093                 struct statistics *sNew, char *delName)
1094 {
1095   char RankFile[MAX_FILENAME_SIZE];
1096   char TmpRankFile[MAX_FILENAME_SIZE];
1097   char line[MAX_RANK_LINE];
1098   char login[MAX_LOGIN_NAME];
1099   
1100   int comp;
1101   statistics sCur;
1102   FILE *fp;
1103   FILE *fptemp;
1104
1105   if (GetRankFileName(RankFile, type) < 0)
1106     return;
1107   fp = fopen_s(RankFile, "r");
1108   if (fp == NULL) {
1109     d_printf( "Can't open rank file to update.\n");
1110     return;
1111   }
1112   sprintf(TmpRankFile, "%s/tmpRank", sdir);
1113   fptemp = fopen_s(TmpRankFile, "w");
1114   if (fptemp == NULL) {
1115     d_printf("Unable to open rank file for updating.\n");
1116     fclose (fp);
1117     return;
1118   }
1119   while (fgets(line, MAX_RANK_LINE - 1, fp)) {
1120     sscanf(line, "%s %d %d %d", login, &sCur.rating,
1121            &sCur.num, &comp);
1122     if (delName != NULL && !strcasecmp(delName, login)) {       /* Kill name. */
1123       delName = NULL;
1124       continue;
1125     }
1126     if (addName != NULL && CompareStats(addName, sNew, login, &sCur) > 0) {
1127       int computer = in_list(-1, L_COMPUTER, addName);
1128       fprintf(fptemp, "%s %d %d %d\n", addName, sNew->rating,
1129               sNew->num, computer);
1130       addName = NULL;
1131     }
1132     fprintf(fptemp, "%s %d %d %d\n", login, sCur.rating, sCur.num, comp);
1133   }
1134   fclose(fptemp);
1135   fclose(fp);
1136
1137   rename(TmpRankFile, RankFile);
1138 }
1139 #endif
1140
1141 static void DisplayRankHead(int p, int show)
1142 {
1143   char Line[MAX_STRING_LENGTH];
1144
1145   Line[0] = '\0';
1146   if (CheckFlag(show, SHOW_BLITZ))
1147     strcat(Line, "         Blitz          ");
1148   if (CheckFlag(show, SHOW_STANDARD))
1149     strcat(Line, "       Standard          ");
1150   if (CheckFlag(show, SHOW_WILD))
1151     strcat(Line, "          Wild");
1152   pprintf(p, "%s\n\n", Line);
1153 }
1154
1155 static int CountRankLine(int countComp, char *loginName,
1156                            int num, int is_computer)
1157 {
1158   if (loginName == NULL || loginName[0] == '\0')
1159     return 0;
1160   return (countComp || !is_computer) && (is_active(num));
1161 }
1162
1163 static int GetRank(FILE * fp, char *target, int countComp)
1164 {
1165   int count = 0;
1166   int nGames, is_computer;
1167   int playerFound = 0;
1168   char line[MAX_RANK_LINE];
1169   char login[MAX_LOGIN_NAME];
1170
1171   while (fgets(line, MAX_RANK_LINE - 1, fp) && !playerFound) {
1172     sscanf(line, "%s %*d %d %d", login, &nGames, &is_computer);
1173     if ((playerFound = !strcasecmp(login, target))
1174         || CountRankLine(countComp, login, nGames, is_computer))
1175       count++;
1176   }
1177   return (playerFound ? count : -1);
1178 }
1179
1180 static void PositionFilePtr(FILE * fp, int count, int *last,
1181                               int *nTied, int showComp)
1182 {
1183   int i, rating, nGames, is_computer;
1184   char login[MAX_LOGIN_NAME];
1185   char line[MAX_RANK_LINE];
1186
1187   if (fp == NULL)
1188     return;
1189   rewind(fp);
1190   for (i = 1; i < count; i++) {
1191     do {
1192       fgets(line, MAX_RANK_LINE - 1, fp);
1193       if (feof(fp))
1194         break;
1195       sscanf(line, "%s %d %d %d", login, &rating, &nGames, &is_computer);
1196     } while (!CountRankLine(showComp, login, nGames, is_computer));
1197     if (rating != *last) {
1198       *nTied = 1;
1199       *last = rating;
1200     } else
1201       (*nTied)++;
1202   }
1203 }
1204
1205 static int ShowRankEntry(int p, FILE * fp, int count, int comp,
1206                            char *target, int *lastRating, int *nTied)
1207 {
1208   char newLine[MAX_RANK_LINE];
1209   char login[MAX_LOGIN_NAME];
1210   int rating, findable, nGames, is_comp;
1211
1212   findable = (count > 0) && !feof(fp);
1213   if (findable) {
1214     do {
1215       fgets(newLine, MAX_RANK_LINE - 1, fp);
1216       if (feof(fp))
1217         findable = 0;
1218       else if (newLine[0] != '\0')
1219         sscanf(newLine, "%s %d %d %d",
1220                login, &rating, &nGames, &is_comp);
1221       else
1222         login[0] = '\0';
1223     } while (!CountRankLine(comp, login, nGames, is_comp) && findable
1224              && strcasecmp(login, target));
1225   }
1226   if (findable) {
1227     if (!strcasecmp(login, target)
1228         && !CountRankLine(comp, login, nGames, is_comp)) {
1229       pprintf_highlight(p, "----  %-12.12s %4s", login, ratstr(rating));
1230       pprintf(p, "  ");
1231       return 0;
1232     } else if (*lastRating == rating && *nTied < 1) {
1233       pprintf(p, "      ");
1234       if (!strcasecmp(login, target))
1235         pprintf_highlight(p, "%-12.12s %4s", login, ratstr(rating));
1236       else
1237         pprintf(p, "%-12.12s %4s", login, ratstr(rating));
1238       pprintf(p, "  ");
1239       return 1;
1240     } else {
1241       if (*nTied >= 1) {
1242         if (*lastRating == rating)
1243           count -= *nTied;
1244         *nTied = -1;
1245       }
1246       if (!strcasecmp(login, target))
1247         pprintf_highlight(p, "%4d. %-12.12s %4s",
1248                           count, login, ratstr(rating));
1249       else
1250         pprintf(p, "%4d. %-12.12s %4s",
1251                 count, login, ratstr(rating));
1252       pprintf(p, "  ");
1253       *lastRating = rating;
1254       return 1;
1255     }
1256   } else {
1257     pprintf(p, "%25s", "");
1258     return 1;
1259   }
1260 }
1261
1262 static int CountAbove(int num, int blitz, int std, int wild, int which)
1263 {
1264   int max = blitz;
1265
1266   if (max < std)
1267     max = std;
1268   if (max < wild)
1269     max = wild;
1270   return (max <= (num + 1) / 2 ? max - 1 : (num + 1) / 2);
1271 }
1272
1273 static int ShowRankLines(int p, FILE * fb, FILE * fs, FILE * fw, int bCount,
1274         int sCount, int wCount, int n, int showComp, int show, char *target)
1275 {
1276   int lastBlitz = 9999, nTiedBlitz = 0;
1277   int lastStd = 9999, nTiedStd = 0;
1278   int lastWild = 9999, nTiedWild = 0;
1279   int i;
1280
1281   if (n <= 0)
1282     return 0;
1283   if (CheckFlag(show, SHOW_BLITZ)) {
1284     PositionFilePtr(fb, bCount, &lastBlitz, &nTiedBlitz, showComp);
1285     if (feof(fb))
1286       FlagOFF(show, SHOW_BLITZ);
1287   }
1288   if (CheckFlag(show, SHOW_STANDARD)) {
1289     PositionFilePtr(fs, sCount, &lastStd, &nTiedStd, showComp);
1290     if (feof(fs))
1291       FlagOFF(show, SHOW_STANDARD);
1292   }
1293   if (CheckFlag(show, SHOW_WILD)) {
1294     PositionFilePtr(fw, wCount, &lastWild, &nTiedWild, showComp);
1295     if (feof(fw))
1296       FlagOFF(show, SHOW_WILD);
1297   }
1298   if (!CheckFlag(show, SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD))
1299     return 0;
1300   DisplayRankHead(p, show);
1301
1302   for (i = 0; i < n && show; i++) {
1303     if (CheckFlag(show, SHOW_BLITZ))
1304       bCount += ShowRankEntry(p, fb, bCount, showComp, target,
1305                               &lastBlitz, &nTiedBlitz);
1306     if (CheckFlag(show, SHOW_STANDARD))
1307       sCount += ShowRankEntry(p, fs, sCount, showComp, target,
1308                               &lastStd, &nTiedStd);
1309     if (CheckFlag(show, SHOW_WILD))
1310       wCount += ShowRankEntry(p, fw, wCount, showComp, target,
1311                               &lastWild, &nTiedWild);
1312     pprintf(p, "\n");
1313   }
1314   return 1;
1315 }
1316
1317 static int DisplayTargetRank(int p, char *target, int show, int showComp)
1318 {
1319   int numToShow = 20;
1320   int blitzRank = -1, blitzCount;
1321   int stdRank = -1, stdCount;
1322   int wildRank = -1, wildCount;
1323   int numAbove;
1324   char Path[MAX_FILENAME_SIZE];
1325   FILE *fb = NULL, *fs = NULL, *fw = NULL;
1326
1327   if (CheckFlag(show, SHOW_BLITZ)) {
1328     GetRankFileName(Path, TYPE_BLITZ);
1329     fb = (FILE *) fopen_s(Path, "r");
1330     if (fb != NULL)
1331       blitzRank = GetRank(fb, target, showComp);
1332     if (blitzRank < 0)
1333       FlagOFF(show, SHOW_BLITZ);
1334   }
1335   if (CheckFlag(show, SHOW_STANDARD)) {
1336     GetRankFileName(Path, TYPE_STAND);
1337     fs = (FILE *) fopen_s(Path, "r");
1338     if (fs != NULL)
1339       stdRank = GetRank(fs, target, showComp);
1340     if (stdRank < 0)
1341       FlagOFF(show, SHOW_STANDARD);
1342   }
1343   if (CheckFlag(show, SHOW_WILD)) {
1344     GetRankFileName(Path, TYPE_WILD);
1345     if (CheckFlag(show, SHOW_WILD))
1346       fw = (FILE *) fopen_s(Path, "r");
1347     if (fw != NULL)
1348       wildRank = GetRank(fw, target, showComp);
1349     if (wildRank < 0)
1350       FlagOFF(show, SHOW_WILD);
1351   }
1352   if (!CheckFlag(show, SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD)) {
1353     pprintf(p, "No ratings to show.\n");
1354     if (fb != NULL) fclose(fb);
1355     if (fs != NULL) fclose(fs);
1356     if (fw != NULL) fclose(fw);
1357     return (0);
1358   }
1359   numAbove = CountAbove(numToShow, blitzRank, stdRank, wildRank, show);
1360   blitzCount = blitzRank - numAbove;
1361   stdCount = stdRank - numAbove;
1362   wildCount = wildRank - numAbove;
1363
1364   ShowRankLines(p, fb, fs, fw, blitzCount, stdCount, wildCount,
1365                 numToShow, showComp, show, target);
1366   if (fb != NULL) fclose(fb);
1367   if (fs != NULL) fclose(fs);
1368   if (fw != NULL) fclose(fw);
1369   return (1);
1370 }
1371
1372 static int DisplayRankedPlayers(int p, int start, int end,
1373                                  int show, int showComp)
1374 {
1375   int num = end - start + 1;
1376   FILE *fb = NULL, *fs = NULL, *fw = NULL;
1377   char Path[MAX_FILENAME_SIZE];
1378
1379   if (start <= 0)
1380     start = 1;
1381   if (num <= 0)
1382     return 0;
1383   if (num > 100)
1384     num = 100;
1385   if (CheckFlag(show, SHOW_BLITZ)) {
1386     GetRankFileName(Path, TYPE_BLITZ);
1387     fb = (FILE *) fopen_s(Path, "r");
1388     if (fb == NULL)
1389       FlagOFF(show, SHOW_BLITZ);
1390   }
1391   if (CheckFlag(show, SHOW_STANDARD)) {
1392     GetRankFileName(Path, TYPE_STAND);
1393     fs = (FILE *) fopen_s(Path, "r");
1394     if (fs == NULL)
1395       FlagOFF(show, SHOW_STANDARD);
1396   }
1397   if (CheckFlag(show, SHOW_WILD)) {
1398     GetRankFileName(Path, TYPE_WILD);
1399     fw = (FILE *) fopen_s(Path, "r");
1400     if (fw == NULL)
1401       FlagOFF(show, SHOW_WILD);
1402   }
1403   ShowRankLines(p, fb, fs, fw, start, start, start,
1404                 num, showComp, show, "");
1405   if (fb)
1406     fclose(fb);
1407   if (fs)
1408     fclose(fs);
1409   if (fw)
1410     fclose(fw);
1411   return 1;
1412 }
1413
1414 static int ShowFromString(char *s)
1415 {
1416   int i, len = strlen(s);
1417   int show = 0;
1418
1419   if (s == NULL || s[0] == '\0')
1420     return SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD;
1421   for (i = 0; i < len; i++) {
1422     switch (s[i]) {
1423     case 'b':
1424       FlagON(show, SHOW_BLITZ);
1425       break;
1426     case 's':
1427       FlagON(show, SHOW_STANDARD);
1428       break;
1429     case 'w':
1430       FlagON(show, SHOW_WILD);
1431       break;
1432     }
1433   }
1434   return (show);
1435 }
1436
1437 static int Best(int p, param_list param, int ShowComp)
1438 {
1439   int show = SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD;
1440
1441   if (param[0].type != TYPE_NULL)
1442     show = ShowFromString(param[0].val.word);
1443
1444   DisplayRankedPlayers(p, 1, 20, show, ShowComp);
1445   return COM_OK;
1446 }
1447