/* C PROGRAM TO RATE COLLEGE FOOTBALL TEAMS (c) Copyright 1994-8 David Lee Wilson; 620 Pickford St.; Madison, WI 53711 This program may be freely distributed and used for non-commercial use. */ #include #include #define SCALE 100.0 #define CENTER 500.0 /* average rating */ struct TEAM { char *name; /* team name */ double rating; /* team rating */ double next_rating; /* will be copied to rating for next iteration */ double def_rating; /* default rating */ short int won, lost, tied; /* raw team record */ struct GAME *games; /* link to games played */ struct TEAM *link; /* link to next team */ struct TEAM *link2; /* for "pending" list when breaking into groups */ short int division; /* set if team in listed division */ } *team_list; struct GAME { double result; /* 1 for win, 0 for tie, -1 for loss */ struct TEAM *opponent; struct GAME *link; /* link to next game for same team */ }; struct NAME { char *name; /* possible team name variant */ char *official; /* official team name */ struct NAME *link; /* link to next possible name */ } *name_list; char save_line[80]; /* orginal input line for error message */ long int ngames; /* number of games */ short int nteams; /* number of teams */ int tie_found = 0; /* if no ties, don't print tie column */ int all_found = 1; /* set if all teams found in names.txt */ char* division_name[10]; char division_divider[10]; char* division_label = "-+ *#23\"^&"; void syntax_error() { printf(" Sorry, I don't understand: %s",save_line); printf(" (Input line for game number %ld)\n",ngames+1); exit(1); } char *malloc(); char *mallocck(size) int size; { char *result; if(size <= 0 || size > 1000) { printf("Bad memory allocation size requested: %d\n",size); exit(1); } result = malloc((long) size); if(!result) {printf("***Memory Overflow***\n"); exit(1);} return result; } void read_names() { FILE *namesfp; char line[256]; /* input line */ char *cp, *cp2, *official; struct NAME *namep; namesfp = fopen("names.txt","r"); if(!namesfp) {printf("Can't open names.txt\n"); exit(1);} printf("Reading names.txt...\n"); /* read input file */ fgets(line, sizeof line, namesfp); while(!feof(namesfp)) { official = 0; for(cp = line;cp2 = strchr(cp,'|');cp = cp2+1) { *cp2 = 0; namep = (struct NAME *) mallocck(sizeof(struct NAME)); namep->name = mallocck(strlen(cp)+1); strcpy(namep->name,cp); if(!official) official = namep->name; namep->official = official; namep->link = name_list; name_list = namep; } fgets(line, sizeof line, namesfp); } fclose(namesfp); } void add_game(teamp, score1, opponentp, score2, increment) struct TEAM *teamp, *opponentp; short int score1, score2; int increment; { struct GAME *gamep, *gamep2; gamep = (struct GAME *) mallocck(sizeof(struct GAME)); if(score1 > score2) { gamep->result = 1.0; teamp->won += increment; } else if(score1 == score2) { gamep->result = 0; teamp->tied += increment; tie_found = 1; } else { gamep->result = -1.0; teamp->lost += increment; } gamep->opponent = opponentp; gamep->link = 0; gamep2 = teamp->games; if(gamep2) { while(gamep2->link) gamep2 = gamep2->link; gamep2->link = gamep; } else teamp->games = gamep; } int mystrcmp(s1,s2) register char *s1, *s2; { /* case insensative strcmp */ register char ch1, ch2; do { while((ch1 = toupper(*s1++)) && (ch1<'A' || ch1>'Z')); while((ch2 = toupper(*s2++)) && (ch2<'A' || ch2>'Z')); } while(ch1 == ch2 && ch1); return (ch1 - ch2); } struct TEAM *find_team(name) char *name; { struct TEAM *teamp; struct NAME *namep; /* look up official name */ for(namep=name_list; namep && mystrcmp(name,namep->name); namep=namep->link); if(!namep) { /* new name--not found in names.txt */ printf("Team name not found in names.txt: %s\n",name); all_found = 0; namep = (struct NAME *) mallocck(sizeof(struct NAME)); namep->name = mallocck(strlen(name)+1); strcpy(namep->name,name); namep->official = namep->name; namep->link = name_list; name_list = namep; } name = namep->official; /* look for a team by this name */ for(teamp=team_list; teamp && strcmp(name,teamp->name); teamp=teamp->link); if(!teamp) { /* new team */ teamp = (struct TEAM *) mallocck(sizeof(struct TEAM)); teamp->name = name; teamp->rating = 0; teamp->next_rating = 0; teamp->def_rating = -1; teamp->won = 0; teamp->lost = 0; teamp->tied = 0; teamp->games = 0; teamp->division = 0; teamp->link = team_list; team_list = teamp; nteams++; } return teamp; } void read_games() { short int bowl; /* set for bowl games */ char *name; /* start of name of team on input line */ short int score[2]; /* scores for current game */ char line[80]; /* input line */ char ch, ch2; /* a character from the input line */ short int which; /* 0 or 1 for which team in current game */ short int number; /* used to gather a score */ char *cp, *cp2; /* places in input line */ struct TEAM *teamp, *pair[2]; struct NAME *namep; FILE *gamesfp; gamesfp = fopen("games.txt","r"); if(!gamesfp) {printf("Can't open games.txt\n"); exit(1);} printf("Reading games.txt...\n"); bowl = 0; nteams = 0; ngames = 0; /* read input file */ fgets(line, sizeof line, gamesfp); while(!feof(gamesfp)) { strcpy(save_line,line); if(!strncmp(line,"=====",5)) { bowl = 1; /* bowl games count double */ } else { /* parse input line */ cp = line-1; for(which=0; which<=1; which++) { while((ch = *++cp) && ch <= '*'); /* bypass leading blanks */ /* check for a date line or blank line */ if(!ch) goto skip; if(ch >= '0' && ch <= '9') { /* some names start with things like: 20th */ cp2 = cp; while(*cp2 >= '0' && *cp2 <= '9') cp2++; if(*cp2 < 'A') { /* bypass date */ while(*cp2 > ' ') cp2++; while(*cp2 == ' ') cp2++; if(*cp2 < '1') goto skip; /* date preceded score */ cp = cp2; } } name = cp; /* start of the name */ continue_name: do { while((ch = *++cp) > ' '); /* find ending blank */ if(! *cp) goto skip; /* check for header line */ cp2 = cp; /* possible end-of-name location */ while((ch = *++cp) && ch <= ' '); /* find non-blank */ if(!ch) goto skip; /* if it's not a digit, the name continues */ } while(ch < '0' || ch > '9'); *cp2 = 0; /* terminate the team name */ number = 0; /* gather up the score */ do { number = 10*number + ch - '0'; ch = *++cp; } while(ch >= '0' && ch <= '9'); if(ch>='n' && ch<='t') { /* oops--ordinal like 1st, 2nd, 3rd, 4th. name continues */ *cp2 = ' '; goto continue_name; } if(ch == ',') ch = *++cp; if(ch > ' ') syntax_error(); score[which] = number; /* store the score */ pair[which] = find_team(name); } add_game(pair[0], score[0], pair[1], score[1], 1); add_game(pair[1], score[1], pair[0], score[0], 1); if(bowl) { /* put game in second time for a bowl game */ add_game(pair[0], score[0], pair[1], score[1], 0); add_game(pair[1], score[1], pair[0], score[0], 0); } ngames++; } goto read; skip: if(which) syntax_error(); read: fgets(line, sizeof line, gamesfp); } fclose(gamesfp); printf("%d teams, %ld games\n",nteams,ngames); /* The following code was used to help look for errors in the names.txt file. It listed teams that played no games and thus may have been a misspelling of another entry. Or it may be a school that is no longer playing football but where the entry may still be needed for previous years. To activate this code, comment out the "all_found = 0" statement. */ all_found = 0; if(all_found && nteams > 675) for(namep=name_list; namep; namep=namep->link) if(!strcmp(namep->name,namep->official)) { name = namep->official; /* look for a team by this name */ for(teamp=team_list; teamp && strcmp(name,teamp->name); teamp=teamp->link); if(!teamp) { /* new team */ printf("%s had no games\n",name); } } } void read_divisions() { FILE *fp; char line[256]; /* input line */ int len; char *name; struct TEAM *teamp; int division = 1; double div_def_rate = 0; int team_count = 0; double atof(); fp = fopen("conf.txt","r"); if(!fp) {printf("Can't open conf.txt\n"); exit(1);} printf("Reading conf.txt...\n"); /* read input file */ fgets(line, sizeof line, fp); len = strlen(line); while(!feof(fp) && len) { line[len-1] = 0; /* drop final \n */ if(line[0] == '-' || line[0] == '=') { /* if division name */ division++; division_divider[division] = line[0]; if(division < 10 && len > 11) { division_name[division] = malloc(len-11); strcpy(division_name[division],line+11); } div_def_rate = (atof(line+6)-CENTER)/SCALE; team_count = 0; } else if(line[0] < 'A') { /* if not conference name */ name = line; /* find team name */ while(*name == ' ') name++; teamp = find_team(name); teamp->division = division; /* set default ratings to division default */ teamp->def_rating = div_def_rate; team_count++; } fgets(line, sizeof line, fp); len = strlen(line); } fclose(fp); /* empty final "division" gives default rating for other divisions */ if(!team_count && division < 10) division_name[division] = 0; /* set default rating for remaining teams */ for(teamp=team_list; teamp; teamp=teamp->link) { if(teamp->def_rating == -1) teamp->def_rating = div_def_rate; } } void calculate_ratings() { register double result; /* result of current game */ register struct GAME *gamep; register double game_count; register double opponent_rating; register double new_rating, old_rating; register double game_rating; long int iteration; double sum, last_sum; /* sum of absolute values of new_rating */ struct TEAM *teamp; double diff1, diff2, ratio; /* used for convergence acceleration */ /* set default ratings */ for(teamp=team_list; teamp; teamp=teamp->link) { teamp->next_rating = teamp->def_rating; if(teamp->won + teamp->lost + teamp->tied >= 16) printf("%s played %d games\n",teamp->name, teamp->won + teamp->lost + teamp->tied); } /* time to calculate the ratings... */ iteration = 0; printf("Calculating ratings...\n"); while(1) { /* copy the ratings... */ for(teamp=team_list; teamp; teamp=teamp->link) teamp->rating = teamp->next_rating; /* recalculate each team's rating */ for(teamp=team_list; teamp; teamp=teamp->link) { game_count = 0; new_rating = 0; old_rating = teamp->rating; for(gamep = teamp->games; gamep; gamep=gamep->link) { result = gamep->result; opponent_rating = gamep->opponent->rating; game_rating = opponent_rating + result; if(game_rating > old_rating ? result >= 0 : result <= 0) { game_count += 1; new_rating += game_rating; } else { game_count += 0.05; new_rating += 0.05*game_rating; } } if(game_count) { new_rating = new_rating/game_count; teamp->next_rating = (new_rating + old_rating)/2; /*printf("%s %f %f\n",teamp->name,old_rating,new_rating);*/ } } iteration = iteration + 1; if(!(iteration % 500)) { /* calculate sum of absolute value of shifts */ sum = 0; for(teamp=team_list; teamp; teamp=teamp->link) { new_rating = teamp->next_rating - teamp->rating; sum += new_rating >= 0 ? new_rating : -new_rating; } printf(" Iteration %6ld, Average rating shift =%12.7f\n", iteration, SCALE*sum/nteams); /* check if time to exit */ if(sum/nteams <= 0.0000001) goto out; if(sum >= last_sum && iteration >= 100000) goto out; if(iteration >= 500000) goto out; last_sum = sum; } } out:; /* make the average rating zero */ sum = 0; for(teamp=team_list; teamp; teamp=teamp->link) { sum += teamp->rating; } sum /= nteams; for(teamp=team_list; teamp; teamp=teamp->link) { teamp->rating -= sum; } } void write_ratings() { char name[64]; struct TEAM *teamp; struct GAME *gamep; double game_count, new_rating, old_rating, result; double weight, product, game_rating, opponent_rating; struct GAME *sorted[255]; int ngames, igame, iresult; static char* results[3] = {"LOSS","TIE ","WIN "}; int postseason; while(1) { printf(" School name: "); if(!gets(name)) exit(0); if(strlen(name) == 0) exit(0); teamp = find_team(name); game_count = 0; new_rating = 0; ngames = 0; old_rating = teamp->rating; printf("\n\n"); printf("%-17s Oppon. Game\n",teamp->name); printf( " Opponent Result Rating Rating Weight Product Effect\n"); printf( "-------------- ------ ------ ------ ------ ------- ------\n"); for(gamep = teamp->games; gamep; gamep=gamep->link) { result = gamep->result; opponent_rating = gamep->opponent->rating; game_rating = opponent_rating + result; if(game_rating > old_rating ? result >= 0 : result <= 0) { weight = 1.0; } else { weight = 0.05; } for(igame=ngames; igame>0 && sorted[igame-1]->opponent->rating < opponent_rating; igame--) { sorted[igame] = sorted[igame-1]; } sorted[igame] = gamep; if(ngames < 255) ngames++; if(gamep->link && gamep->link->opponent == gamep->opponent && gamep->link->result == gamep->result) { weight *= 2.0; gamep = gamep->link; } game_count += weight; product = weight*game_rating; new_rating += product; printf("%-17s %s%10.1f%9.1f%8.2f%11.1f%+9.1f\n", gamep->opponent->name, result > 0 ? "WIN " : result < 0 ? "LOSS" : "TIE ", opponent_rating*SCALE+CENTER, game_rating*SCALE+CENTER, weight, product*SCALE+weight*CENTER, weight*(game_rating-teamp->rating)*SCALE); } if(game_count) { printf( " ===== =======\n"); printf(" %8.2f%11.1f\n", game_count, new_rating*SCALE+game_count*CENTER); printf("%36.1f\n",new_rating*SCALE+game_count*CENTER); printf("%17s's rating = ------ =%7.1f\n", teamp->name, teamp->rating*SCALE+CENTER); printf("%35.2f\n\n",game_count); } printf(" Team Rating Effect\n"); printf(" --------------- ------ ------\n"); for(iresult = -1; iresult <= 1; iresult++) { for(igame=0; igameresult; if(result == iresult) { opponent_rating = gamep->opponent->rating; game_rating = opponent_rating + result; if(game_rating > old_rating ? result >= 0 : result <= 0) { weight = 1.0; } else { weight = 0.05; } postseason = gamep->link && gamep->link->opponent == gamep->opponent && gamep->link->result == gamep->result; if(postseason) weight *= 2.0; printf(" %-17s%7.1f %s%+7.1f %s",gamep->opponent->name, opponent_rating*SCALE+CENTER, results[iresult+1],(game_rating-old_rating)*SCALE*weight, division_name[gamep->opponent->division]); if(postseason) printf(" postseason"); printf("\n"); } } if(!iresult) { printf(">> %-17s%7.1f <<\n",teamp->name, teamp->rating*SCALE+CENTER); } } printf("\n\n"); } } int main() { read_names(); read_games(); read_divisions(); calculate_ratings(); write_ratings(); return 0; }