
VBScript solution to Event 7 in the 2008 Winter Scripting Games.
Solutions are also available for Windows PowerShell and Perl.
As it turns out, one of the Scripting Guys has not only played in a large number of round-robin tournaments in his life, but he’s also been involved in organizing, scheduling, and running a bunch of round-robin tournaments. So did that give him an advantage when it came to solving this event? Well, maybe. It didn’t help much with the actual coding, but it did help him visualize what the code needed to do. On top of that, he also knew how to calculate the number of games in a round-robin tournament:
| • | Take the number of teams in the tournament (6) and multiply that by the number of teams in the tournament minus 1 (6 – 1, or 5). |
| • | Take the resulting value (30) and divide it by 2. |
That simple little equation tells you how many games will be played in this tournament: 15 (30 divided by 2).
But that doesn’t really matter right now, does it? Right now, you just want to see a script that can schedule a round-robin tournament, don’t you? Well, you know what they say: give the people what they want. Here’s a script that can schedule a round-robin tournament:
Dim arrGames()
Dim arrSchedule()
intGameNumber = 0
intScheduledGame = 0
arrTeams = Array("A","B","C","D","E","F")
For i = 0 to UBound(arrTeams) - 1
For j = i to UBound(arrTeams) - 1
ReDim Preserve arrGames(intGameNumber)
arrGames(intGameNumber) = arrTeams(i) & " vs. " & arrTeams(j + 1)
intGameNumber = intGameNumber + 1
Next
Next
intGames = Ubound(arrGames) + 1
intGamesLeft = Ubound(arrGames) + 1
Do Until intGamesLeft = 0
Randomize
intPicker = Int((intGames * Rnd) + 1)
intPicker = intPicker - 1
If arrGames(intPicker) <> "" Then
ReDim Preserve arrSchedule(intScheduledGame)
arrSchedule(intScheduledGame) = arrGames(intPicker)
intScheduledGame = intScheduledGame + 1
arrGames(intPicker) = ""
intGamesLeft = intGamesLeft - 1
End If
Loop
For Each strGame in arrSchedule
Wscript.Echo strGame
Next
This event isn’t really isn’t too difficult; however, it can be a bit confusing trying to keep track of where you are and what still needs to be done. We decided to tackle that problem by using a nested For Next loop; that is a loop within another For Next loop. Did that do the trick? We’re about to find out.
We start things out by declaring a pair of dynamic arrays (arrays whose size can change during the course of the script):
Dim arrGames() Dim arrSchedule()
We’re to going to use the array arrGames to keep track of every game that needs to be played. In our approach, at least, that means arrGames will eventually look like this:
A vs. B A vs. C A vs. D A vs. E A vs. F
And so on.
So then what’s the second array (arrSchedule) for? Well, as we noted in the instructions, we don’t want team A to have to play the first five games of the tournament. Therefore, while arrGames contains a list of all the games that need to be played, it doesn’t display those games in an acceptable order. That’s what we’re going to use arrSchedule for: we’re going to randomly select games from arrGames and put them into arrSchedule. When we’re done all the games that need to be played will also be included in this second array. The difference? The games listed in arrSchedule will be listed in a random – and thus acceptable – order.
Next up is more bookkeeping. First, we assign the value 0 to a pair of variables:
intGameNumber = 0 intScheduledGame = 0
intGameNumber is a counter variable which will help us keep track of games when we build the array arrGames; intScheduledGame serves the same function for the array arrSchedule. After assigning values to these two variables we then use this line of code to put the names of all the teams in the tournament (teams A, B, C, D, E, and F) into a third array, this one named arrTeams:
arrTeams = Array("A","B","C","D","E","F")
Now it gets a little tricky. In order to schedule games we need to write some code that loops us through the array arrTeams; in particular, we need to set up a For Next loop that runs from 0 (the first item in the array) to the next-to-last item in the array (UBound(arrTeams) – 1):
For i = 0 to UBound(arrTeams) - 1
That’s a good question: why the next-to-the-last item in the array? Well, consider a smaller tournament, one with just three teams: A, B, and C. Assume we first schedule all the games for team A; that gives us a schedule that looks like this:
A vs. B A vs. C
Next we add the games for team B; that gives us a schedule that looks like this:
A vs. B A vs. C B vs. C
In a three-team tournament each team plays two games. As you can see, all we have to do is schedule the games for teams A and B; by default, that will end up scheduling all the games for the last team in the tournament (team C) as well. It might seem a little goofy (in fact, it might seem really goofy). But it works.
You might have noticed, too that – with our three-team example – we said “Next we add the game [emphasis added] for team B.” So what’s the deal there; why does team B only get to play one game when everyone else gets to play two games?
Well, if you look at the schedule you’ll see that’s not the case; team B does get to play two games:
A vs. B
A vs. C
B vs. C
So then why do we need to add only one game for team B? You’re absolutely right: when we scheduled all the games for team A that included a game for team B (A vs. B). This is the tricky part. When creating a round-robin tournament, you start out by scheduling all the games for one team. When you schedule the games for the next team you’ll schedule one game fewer; when you schedule games for the next team you’ll schedule one game fewer than that. In a six-team tournament, that works out to this:
Team | Games you need to schedule |
A | 5 |
B | 4 |
C | 3 |
D | 2 |
E | 1 |
F | 0 |
If you think about that for a moment it should make sense. And that’s great. But how in the world do you script something like that?
Our solution was to immediately add a nested loop to our For Next loop:
For j = i to UBound(arrTeams) - 1
Why? Well, to try and explain what we’re doing, let’s look at the code that takes place inside this loop:
ReDim Preserve arrGames(intGameNumber) arrGames(intGameNumber) = arrTeams(i) & " vs. " & arrTeams(j + 1) intGameNumber = intGameNumber + 1
As you can see, we start out by redimensioning the array arrGames, setting it to size intGameNumber (which, the first time through the loop, will be size 0, meaning a one-item array). That brings us to this line of code:
arrGames(intGameNumber) = arrTeams(i) & " vs. " & arrTeams(j + 1)
What we’re doing here is adding the first game of the tournament to the array arrGames. How do we determine what the first game is? Well, one of the teams is going to be team arrTeams(i); because i is currently equal to 0, that means Team A. As for the other team, that’s going to be team arrTeams(j + 1). The variable j is currently equal to 0; that makes j + 1 equal to 1, which means the opponent for Team A will be team 1 (that is, Team B). See how that works? We then increment intGameNumber by 1 and repeat the process with j now equal to 2; that’s going to result in a game between Team A and Team C. We’ll then do this all over again, and keep doing it until Team A has been matched up against all the other teams in the tournament (B, C, D, E, and F).
If your head hurts there’s some aspirin in the medicine cabinet.
After we finish scheduling all the games for Team A, we pop back to our original loop, the one that looks like this:
For i = 0 to UBound(arrTeams) - 1
The second time through the loop the counter variable i is equal to 1; that means we’ll now be assigning games for Team B. But won’t we inadvertently assign B vs. A the moment we enter our nested loop? Believe it or not, we won’t. Take another look at the code for the nested loop:
For j = i to UBound(arrTeams) - 1
See? This loop starts at the value of i; because i is equal to 1 we’ll won’t do a thing with Team A (item 0). Make sense? If not, well, like we said, there’s some aspirin in the medicine cabinet.
Wait; we aren’t done just yet. Although we now know all the games that need to be played, we still need to randomly assign the order of games. To do that we first assign values to a pair of variables:
intGames = Ubound(arrGames) + 1 intGamesLeft = Ubound(arrGames) + 1
intGames simply contains the number of games to be played in the tournament; we can calculate that by using the Ubound function to determine the index number for the last item in the array arrGames, then adding 1. (Why do we add 1? That’s easy: because arrays start with 0 they always have one more item than the highest index number.) Meanwhile, intGamesLeft is a counter variable that will help us keep track of the number of games remaining to be scheduled.
With that in mind, we next set up a Do Until loop that continues to run until there are no more games left to be scheduled:
Do Until intGamesLeft = 0
Inside this Do loop, we use these two lines of code to generate a random number between 1 and the number of games to be scheduled:
Randomize intPicker = Int((intGames * Rnd) + 1)
We then execute this curious little line of code:
intPicker = intPicker - 1
Why pick a random number and then immediately subtract 1 from it? Well, we’re going to use this random number to randomly grab games from the array arrGames. That array has items with index numbers running from 0 to 14; thus our random numbers also need to run from 0 to 14.
That brings us to the following block of code:
If arrGames(intPicker) <> "" Then
ReDim Preserve arrSchedule(intScheduledGame)
arrSchedule(intScheduledGame) = arrGames(intPicker)
intScheduledGame = intScheduledGame + 1
arrGames(intPicker) = ""
intGamesLeft = intGamesLeft - 1
End If
What we’re doing here is using our random number to examine an item in the array; for examples, if our random number is 3 then we’d be looking at arrGames(3). If the value of this array item in not an empty string (“”), we redimension the array arrSchedule and then copy the value from arrGames to arrSchedule. That’s what these two lines do:
arrSchedule(intScheduledGame) = arrGames(intPicker) intScheduledGame = intScheduledGame + 1
For example, if arrGames(3) is D vs. E then arrSchedule(0) will also be D vs. E. We then do three things:
| • | We increment the counter variable intScheduledGame by 1. That means the next game will be copied to arrScheduled(1). |
| • | We set the value of arrGames(3) to an empty string. |
| • | We decrement the counter variable intGamesLeft by 1. That means we now have 14 games left to schedule. |
And then we just keep picking random numbers until we’ve hit all the items in the array arrGames. (And yes, there is a lot of hit-and-miss in this approach. But all the hits and misses take place so quickly you’ll never know the difference.) From there all we have to do is echo back the values in arrSchedule, which will correspond quite nicely to the game schedule:
For Each strGame in arrSchedule
Wscript.Echo strGame
Next
Each time you run the script you’re likely to get a different game schedule; that’s the nature of random selection. But you should get back something on the order of this:
B vs. D B vs. C A vs. D B vs. F C vs. D A vs. F A vs. B C vs. F E vs. F A vs. E D vs. F B vs. E D vs. E A vs. C C vs. E
Five pages of explanation later we can finally say this: that’s all you have to do!