2008 Winter Scripting Games

Solution to Advanced Windows PowerShell Event 7: Play Ball!

Event 7 Solution


Windows PowerShell solution to Event 7 in the 2008 Winter Scripting Games.

Solutions are also available for VBScript and Perl.

*

Play Ball!


Event 7 was a deceptive little event. There’s really not much to it; had we chosen to format the code a little differently we could have carried this out in 10 lines or less. (As a general rule, the Scripting Guys like to use lots of indenting and white space; we find that makes it easier for people to identify the various parts of the script.)

That said, there are definitely some tricky aspects to the script: figuring out who is supposed to play who (and keeping track of that), as well as “shuffling” the resulting set of games to help ensure that one team doesn’t play 5 games in a row. That made this event challenging, yet reasonable: once you figured out the logic, the script itself took only a few minutes to write.

Or so we hope anyway.

Incidentally, the script we came up with looks an awful lot like this one:

$a = New-Object Random

$arrGames = @()
$arrSchedule = @()

$arrTeams = "A", "B", "C", "D", "E", "F"

for ($i = 0; $i -lt $arrTeams.Length - 1; $i++)
    {       
        for ($j = $i; $j -lt $arrTeams.Length - 1; $j++)    
            {
                $arrGames += $arrTeams[$i] + " vs. " + $arrTeams[$j + 1]
            }
    }

$intGamesLeft = $arrGames.Length

do
    {
        $b = $a.next(0,15)
        If ($arrGames[$b] -ne "Done")
            {
                $arrSchedule += $arrGames[$b]
                $arrGames[$b] = "Done"
                $intGamesLeft--
            }
    }
until ($intGamesLeft -eq 0)

$arrSchedule

Event 7 was an event that put your organizational skills to the test. Like we said, it wasn’t all that hard … provided that you kept track of where you were and what you were doing. If you lost your place anywhere along the way, however, well ….

So how did the Scripting Guys (a team not exactly famous for knowing where they are or what they’re doing) tackle this problem? Well, we started out by using the New-Object cmdlet to create an instance of the System.Random class; System.Random is a .NET Framework class that enables us to generate random numbers.

Note. Why do we need random numbers? Well, remember, we need to make sure that Team A doesn’t play the first five games in the tournament. One easy way to do that is to generate a complete list of the games that need to be played, then randomly assign the order of those games.

Next we create a pair of empty arrays, $arrGames (which will hold the collection of the games that need to be played) and $arrSchedule (which will hold the final, official collection of games to be played, in the order in which they are to be played):

$arrGames = @()
$arrSchedule = @()

We then use this line of code to assign each of the teams to an array named $arrTeams:

$arrTeams = "A", "B", "C", "D", "E", "F"

That brings us to this frightening-looking chunk of code:

for ($i = 0; $i -lt $arrTeams.Length - 1; $i++)
    {       
        for ($j = $i; $j -lt $arrTeams.Length - 1; $j++)    
            {
                $arrGames += $arrTeams[$i] + " vs. " + $arrTeams[$j + 1]
            }
    }

As you can see, what we have here is a pair of loops, one nested inside the other. The outside loop starts at 0 and continues to run as long as the variable $i is less than the Length of the array $arrTeams minus 1. What does that mean? That means that the loop will start with the first item in the array – A – and continue on through B, C, D, and E. Why do we leave out F? That’s easy. We’re going to use our inner loop to schedule all the games for the tournament. After we’ve scheduled the games for teams A, B, C, D, and E we won’t have to schedule any games for team F; because each team plays each other team once, all the games for Team F will have already been scheduled.

Speaking of inner loops, notice that the inner loop starts with the counter variable $j equal to $i. Why? Because if we start off with Team A (item 0 in the array) we want to continue using Team A. Inside this inner loop we execute just a single line of code:

$arrGames += $arrTeams[$i] + " vs. " + $arrTeams[$j + 1]

What we’re doing here is adding a single game to the array $arrGames. What game are we adding? Well, the first time we enter the loop $i is equal to 0. That means we’re scheduling $arrTeams[0] (Team A) against $arrTeams[0] + 1. Why the + 1? You got it: we don’t want to schedule Team A to play itself. The +1 causes us to schedule Team A vs. Team B. We then go back to the top of the loop and increment $j by 1. The net effect of that? That will cause us to schedule Team A vs. Team C. This continues until Team A has been scheduled to play all the other 5 teams in the tournament. Once that’s happened, the inner loop ends and we return to the outer loop, where we schedule all the games for Team B.

Well, with one exception. The second time through the loop the variable $i is equal to 1; that means that we’re working with Team B. That also means that, when we enter the inner loop, $i will still be equal to 1, $j will start off equal to 1, and $j + 1 will equal 2. In other words, we’ll never schedule Team B to play Team A (that is, team 0 in the array). But that’s OK; remember, we already scheduled Team A and Team B the first time through the loop.

That’s what we were talking about when we said the tricky part here was keeping track of where you were and what you were doing.

Eventually we’ll get an array that starts off something like this:

A vs. B
A vs. C
A vs. D
A vs. E
A vs. F

That’s good but, as we noted earlier, we don’t want Team A to have to play the first 5 games in the tournament. So how do we mix these games up a little? Well, to begin with, we assign the Length of the array $arrGames to the variable $intGamesLeft; this will help us keep track of the number of games that still have to be scheduled:

$intGamesLeft = $arrGames.Length

We then set up a do loop that’s designed to run until $intGamesLeft is equal to 0:

until ($intGamesLeft -eq 0)

Why 0? Because as soon as $intGamesLeft is equal to 0 then we’re out of games that need to be scheduled.

Inside the loop we use this code to generate a random number between 0 and 15, inclusive:

$b = $a.next(0, 15)

Note. Why 0 and 15 rather than 1 and 16? Well, remember, we’re dealing with a 15-item array here. That means that the first item in the array has an index number equal to 0, and the last item in the array has an index number equal to 14. OK, so then how come we don’t use 0 and 14? Well, the System.Random class is a strange bird. The minimum value passed to the next method (0) is included the random number range; however, the maximum value is not. Thus the syntax (0, 15) really means this: pick a random number between 0 (the minimum value) and 14 (that is, one less than the maximum value). Why does it work this way? Who knows; it just does.

After we have a random number we check the array $arrGames to see if the value of that array item (e.g., item 7) is equal to Done:

If ($arrGames[$b] -ne "Done")

If the value is equal to Done that means we’ve already scheduled that game; in turn, we go back to the top of the loop, generate a new random number, and try again. If the value isn’t equal to Done we then execute these lines of code:

$arrSchedule += $arrGames[$b]
$arrGames[$b] = "Done"
$intGamesLeft--

In line 1 we add the value of the item in $arrGames (for example, Team A vs. Team E) to the array $arrSchedule; we then set the value in $arrGames to Done, and decrement the value of $intGamesLeft (because we now have one less game that needs to be scheduled). If we were to look at the array $arrGames partway through this process it might look something like this:

A vs. B
Done
A vs. D
Done
A vs. F

Once we’ve assigned all the games we’re ready to unveil the tournament schedule, something we do by echoing back the value of $arrSchedule:

C vs. E
B vs. F
C vs. D
A vs. B
B vs. E
C vs. F
A vs. F
B vs. D
B vs. C
A vs. E
E vs. F
A vs. C
D vs. E
A vs. D
D vs. F

The event title says it all: Play ball!


Top of pageTop of page