/* nflrank99.c */
#include <stdio.h>
#include <string.h>
#define NUM_TEAMS 31
#define AFC_EAST 0
#define AFC_WEST 1
#define AFC_CENTRAL 2
#define NFC_EAST 3
#define NFC_WEST 4
#define NFC_CENTRAL 5
#define TOKEN_SEPS " \t\n"
void ParseTeams();
void RateTeams();
void SortTeams();
void PrintTeams();
int GetTeamByName(char *strName);
struct Victory
{
int nTeam;
Victory *next;
};
struct Team
{
int nConf, nWins, nLosses, nWeight;
Victory *victories;
char szName[21];
};
Team aTeams[NUM_TEAMS] =
{
{ NFC_EAST, 0,0,0, NULL, "Arizona Cardinals " },
{ NFC_WEST, 0,0,0, NULL, "Atlanta Falcons " },
{ AFC_CENTRAL, 0,0,0, NULL, "Baltimore Ravens " },
{ AFC_EAST, 0,0,0, NULL, "Buffalo Bills " },
{ NFC_WEST, 0,0,0, NULL, "Carolina Panthers " },
{ NFC_CENTRAL, 0,0,0, NULL, "Chicago Bears " },
{ AFC_CENTRAL, 0,0,0, NULL, "Cincinnati Bengals " },
{ AFC_CENTRAL, 0,0,0, NULL, "Cleveland Browns " },
{ NFC_EAST, 0,0,0, NULL, "Dallas Cowboys " },
{ AFC_WEST, 0,0,0, NULL, "Denver Broncos " },
{ NFC_CENTRAL, 0,0,0, NULL, "Detroit Lions " },
{ NFC_CENTRAL, 0,0,0, NULL, "Green Bay Packers " },
{ AFC_EAST, 0,0,0, NULL, "Indianapolis Colts " },
{ AFC_CENTRAL, 0,0,0, NULL, "Jacksonville Jaguars" },
{ AFC_WEST, 0,0,0, NULL, "Kansas City Chiefs " },
{ AFC_EAST, 0,0,0, NULL, "Miami Dolphins " },
{ NFC_CENTRAL, 0,0,0, NULL, "Minnesota Vikings " },
{ AFC_EAST, 0,0,0, NULL, "New England Patriots" },
{ NFC_WEST, 0,0,0, NULL, "New Orleans Saints " },
{ NFC_EAST, 0,0,0, NULL, "New York Giants " },
{ AFC_EAST, 0,0,0, NULL, "New York Jets " },
{ AFC_WEST, 0,0,0, NULL, "Oakland Raiders " },
{ NFC_EAST, 0,0,0, NULL, "Philadelphia Eagles " },
{ AFC_CENTRAL, 0,0,0, NULL, "Pittsburg Steelers " },
{ NFC_WEST, 0,0,0, NULL, "St. Louis Rams " },
{ AFC_WEST, 0,0,0, NULL, "San Diego Chargers " },
{ NFC_WEST, 0,0,0, NULL, "San Francisco 49ers " },
{ AFC_WEST, 0,0,0, NULL, "Seattle Seahawks " },
{ NFC_CENTRAL, 0,0,0, NULL, "Tampa Bay Buccaneers" },
{ AFC_CENTRAL, 0,0,0, NULL, "Tennessee Titans " },
{ NFC_EAST, 0,0,0, NULL, "Washington Redskins " }
};
int order[NUM_TEAMS];
void main ()
{
ParseTeams();
RateTeams();
SortTeams();
PrintTeams();
}
int GetTeamByName(char *strName)
{
if (!strcmp(strName, "ari"))
return 0;
if (!strcmp(strName, "atl"))
return 1;
if (!strcmp(strName, "bal"))
return 2;
if (!strcmp(strName, "buf"))
return 3;
if (!strcmp(strName, "car"))
return 4;
if (!strcmp(strName, "chi"))
return 5;
if (!strcmp(strName, "cin"))
return 6;
if (!strcmp(strName, "cle"))
return 7;
if (!strcmp(strName, "dal"))
return 8;
if (!strcmp(strName, "den"))
return 9;
if (!strcmp(strName, "det"))
return 10;
if (!strcmp(strName, "gbp"))
return 11;
if (!strcmp(strName, "ind"))
return 12;
if (!strcmp(strName, "jax"))
return 13;
if (!strcmp(strName, "kcc"))
return 14;
if (!strcmp(strName, "mia"))
return 15;
if (!strcmp(strName, "min"))
return 16;
if (!strcmp(strName, "nep"))
return 17;
if (!strcmp(strName, "nos"))
return 18;
if (!strcmp(strName, "nyg"))
return 19;
if (!strcmp(strName, "nyj"))
return 20;
if (!strcmp(strName, "oak"))
return 21;
if (!strcmp(strName, "phi"))
return 22;
if (!strcmp(strName, "pit"))
return 23;
if (!strcmp(strName, "stl"))
return 24;
if (!strcmp(strName, "sdc"))
return 25;
if (!strcmp(strName, "sff"))
return 26;
if (!strcmp(strName, "sea"))
return 27;
if (!strcmp(strName, "tbb"))
return 28;
if (!strcmp(strName, "ten"))
return 29;
if (!strcmp(strName, "was"))
return 30;
return NUM_TEAMS;
}
void ParseTeams()
{
FILE *f;
Victory *last, *temp;
char teamLine[70];
char *teamName;
int i;
f = fopen("nflteams.txt", "r");
for (i=0; i<NUM_TEAMS; i++)
{
last = NULL;
fgets(teamLine,70,f);
teamName = strtok(teamLine, TOKEN_SEPS); /* get team name */
teamName = strtok(NULL, TOKEN_SEPS); /* get first victory */
while (teamName != NULL)
{
temp = new Victory;
temp->nTeam = GetTeamByName(teamName);
temp->next = last;
aTeams[i].nWins++;
aTeams[temp->nTeam].nLosses++;
last=temp;
teamName = strtok(NULL, TOKEN_SEPS); /* get next victory */
}
aTeams[i].victories = last;
}
fclose(f);
}
void RateTeams()
{
Victory *temp;
int i;
for (i=0; i<NUM_TEAMS; i++)
{
temp = aTeams[i].victories;
while (temp != NULL)
{
aTeams[i].nWeight += (1 + aTeams[temp->nTeam].nWins);
temp = temp->next;
}
}
}
void SortTeams()
{
int i, j, last, next;
for (i=1; i<NUM_TEAMS; i++)
{
j=0;
while ((j<i) && (aTeams[i].nWeight <= aTeams[order[j]].nWeight))
{
j++;
}
last = i;
while (j<i)
{
next = order[j];
order[j] = last;
last = next;
j++;
}
order[j] = last;
}
}
void PrintTeams()
{
int i, rank, lastWeight;
printf("Rank Team Record Weight\n");
rank = 0;
lastWeight = 0;
for (i=0;i<NUM_TEAMS;i++)
{
if (aTeams[order[i]].nWeight != lastWeight)
{
rank = i+1;
lastWeight = aTeams[order[i]].nWeight;
}
printf("%2d. %s %2d-%2d %3d\n",
rank, aTeams[order[i]].szName, aTeams[order[i]].nWins,
aTeams[order[i]].nLosses, lastWeight);
}
}
|
|
|
The program took an input file like the one below, which lists all the wins each team gets. The first acronym on each line stands for a team, and the acronyms that follow represent each of the teams it has beaten (which occasionally happens twice for a particular team).
ari dal cle was atl sff car car bal pit jax cin cle jax cin ten dal cle buf ten gbp sdc nyj nep chi kcc car sff sea sff stl gbp chi gbp ind tbb cin den cle cle cin pit nep dal was car ari cin den atl oak sdc cle nyj oak sdc sea det nos was chi gbp tbb atl nyg nep gbp phi ari sff min ind ind kcc jax buf sea nep det nyj jax cle cin dal pit kcc sdc den sea stl sea mia sea bal nep cin buf gbp det sdc ind min chi mia nep det tbb chi buf ari car dal nep den ind cin nos sdc chi car atl ari sff car stl nyg ari phi chi atl dal phi cle ari nyj gbp nep buf tbb nep mia mia chi oak sdc ind cle sff kcc sea sdc kcc nos atl phi dal nos atl ari chi dal pit ari was pit jax nyj cin cle bal cin stl den sea sff atl sdc atl sff nyg sdc kcc sff dal ari kcc atl sea nos sdc sdc jax tbb nep chi det min atl gbp buf ten kcc pit nyg cin jax bal was pit cle jax was car nyg tbb phi bal jax stl |
|
|
This resulted in the following output.
Rank Team Record Weight 1. Tennessee Titans 10- 2 64 2. Minnesota Vikings 10- 2 62 3. Washington Redskins 7- 5 57 4. Miami Dolphins 9- 3 55 5. New York Jets 8- 4 54 6. Detroit Lions 8- 4 52 7. Oakland Raiders 10- 2 51 8. Philadelphia Eagles 9- 4 50 9. Denver Broncos 8- 4 48 10. Baltimore Ravens 9- 4 47 11. Indianapolis Colts 7- 5 46 11. Tampa Bay Buccaneers 7- 5 46 13. New York Giants 8- 4 45 14. St. Louis Rams 8- 4 43 15. Buffalo Bills 7- 5 42 16. New Orleans Saints 8- 4 40 17. Green Bay Packers 5- 7 38 18. Pittsburg Steelers 6- 6 34 19. Carolina Panthers 5- 7 30 19. Kansas City Chiefs 5- 7 30 21. Chicago Bears 3- 9 22 22. Dallas Cowboys 4- 8 21 23. New England Patriots 3- 9 20 24. Jacksonville Jaguars 4- 8 19 24. San Francisco 49ers 4- 8 19 26. Seattle Seahawks 4- 8 18 27. Arizona Cardinals 3- 9 17 27. Atlanta Falcons 3-10 17 29. Cleveland Browns 3-10 14 30. Cincinnati Bengals 2-10 13 31. San Diego Chargers 1-11 6 |
|
|
Unfortunately, this implementation was rather rigid, making it less flexible when the league experienced changes, like teams moving and expansion. Eventually, I came around to Ruby, a new scripting language that made text processing like this much easier and more flexible. My first significant goal in learning this language was to re-write this program with Ruby to use the same input and be able to get the same output on any platform, including a webserver (if only my server allowed Ruby scripts, but that's another issue). Besides, I had to add the Houston Texans to the mix, so the program had to be updated anyway.
The thing with Ruby is, you can't get away with half-assed object orientation like I did in the C program. So, the first step in creating this program was to define a Team class, shown below.
#team.rb - Team class for NFLRank
class Team
attr_reader :wins
attr_accessor :losses
attr_accessor :points
attr_reader :name
attr_reader :winString
def initialize(nm, ws)
@wins = ws.split(" ").length
@losses = 0
@points = 0
@name = nm
@winString = ws
end
end
|
|
|
Simple enough. Each Team object, as with the Team struct in the C program, is responsible for keeping track of its own statistics. However, Ruby is so loosely typed, classes can be defined by whoever creates an object of that class. Team.name and Team.winString end up being strings in my implementation, but it doesn't have to be that way, so the Team class doesn't have to worry about allocation and clean-up of any string buffers.
Moving on, a League class is required to keep track of all the teams, and (in this case) to count up all the wins and losses between the teams in the league. Below is the implementation, which uses the same nflteams.txt input file as shown in Fig. 2.
#league.rb - League class for NFLRank
class League
attr_reader :arTeams
def findTeam(name)
for t in @arTeams
if t.name == name
break
end
end
t
end
def initialize
f = File.open("nflteams.txt")
first = true
while line = f.gets
line.chomp!
if (line.length > 0) && (line =~ /\S/)
name = line[0..2]
wins = line[4..-1]
if wins == nil
wins = " "
end
t = Team.new(name,wins)
if first
@arTeams = [t]
first = false
else
@arTeams += [t]
end
end
end
f.close
for t in @arTeams
arWins = t.winString.split(" ")
for win in arWins
loser = findTeam(win)
t.points += 1 + loser.wins
loser.losses += 1
end
end
end
end
|
|
|
Notice again, because this is an interpreted scripting language, there is no explicit inclusion of team.rb in league.rb. It's up to whoever declares a League object to include the desired Team object as well. Whether that's convenient or lazy programming is up to the reader. In either case, the program works and is flexible -- more on that later.
Now, we will come to the actual script that is executed, which defines what acronyms will be used for what teams, and then sorts them by their point value and prints the results to standard output. That's all the below script does, as it leaves most of the heavy lifting to the instantiation of the League object.
#nflrank.rb - Main executing script for NFLRank 2004
load "team.rb"
load "league.rb"
nameHash = {
"ari" => "Arizona Cardinals ",
"atl" => "Atlanta Falcons ",
"bal" => "Baltimore Ravens ",
"buf" => "Buffalo Bills ",
"car" => "Carolina Panthers ",
"chi" => "Chicago Bears ",
"cin" => "Cincinnati Bengals ",
"cle" => "Cleveland Browns ",
"dal" => "Dallas Cowboys ",
"den" => "Denver Broncos ",
"det" => "Detroit Lions ",
"gbp" => "Green Bay Packers ",
"hou" => "Houston Texans ",
"ind" => "Indianapolis Colts ",
"jax" => "Jacksonville Jaguars ",
"kcc" => "Kansas City Chiefs ",
"mia" => "Miami Dolphins ",
"min" => "Minnesota Vikings ",
"nep" => "New England Patriots ",
"nos" => "New Orleans Saints ",
"nyg" => "New York Giants ",
"nyj" => "New York Jets ",
"oak" => "Oakland Raiders ",
"phi" => "Philadelphia Eagles ",
"pit" => "Pittsburgh Steelers ",
"stl" => "St. Louis Rams ",
"sdc" => "San Diego Chargers ",
"sff" => "San Francisco 49ers ",
"sea" => "Seattle Seahawks ",
"tbb" => "Tampa Bay Buccaneers ",
"ten" => "Tennessee Titans ",
"was" => "Washington Redskins "
}
nfl = League.new
nfl.arTeams.sort! {|a,b| b.points <=> a.points}
print "Rank Team Record Weight\n"
rank = 0
i = 0
lastPoints = 0
for t in nfl.arTeams
i += 1
if t.points != lastPoints
rank = i;
lastPoints = t.points;
end
printf "%3d. %s%2d-%2d %3d\n", rank, nameHash[t.name], t.wins, t.losses, t.points
end
|
|
|
And that's it. Three files, but a lot less code, which is now much more portable and flexible. Adding a new team to the mix is as easy as adding an entry to the nameHash declaration. The only resposibility lies in the creation of a sound nflteams.txt file, in which the correct acronyms are used. As long as the lines in the input are in alpahabetical order by team name, the output will list equally weighted teams alphabetically, just as before. Since I'm the only one creating these input files, I can be confident that nobody sees a bad output. I leave error checking to programs I put in the wild.
So, I accomplished my goal with this program. It's proven flexible enough to use it for ranking college football, after I came up with a new hash table and a slightly modified points system, the latter of which was simply a preference of mine. Indeed, one could use this same ranking system for any team sport. In the case of ties (which actually happened twice in one season since I've been doing this), I just pretend the game never happened. Soccer fans might not be satisfied, but they have their own crazy ranking system to deal with. Anyway, I hope you enjoyed seeing this example of Ruby in action and that you check out my rankings this season.