
Perl solution to Event 10 in the 2008 Winter Scripting Games.
Solutions are also available for VBScript and Windows PowerShell.
To successfully solve this event you needed to be able to work with arrays and – most likely, depending on your particular solution – nested if statements. This was as much an exercise in logic as in scripting. Here’s what we came up with:
@arrFrames = (2,5,7,"/",8,1,"X",9,"/",5,3,7,0,4,5,"X",2,0);
$iPoints = 0;
for ($i = 0; $i < @arrFrames.Length; $i++)
{
$vFrame = @arrFrames[$i];
if (($vFrame ne "X") && ($vFrame ne "/"))
{
$iPoints = $iPoints + (substr $vFrame, 0,1);
}
else
{
if ($vFrame eq "/")
{
$iPoints = $iPoints + (10 - @arrFrames[$i - 1]);
if ((@arrFrames[$i + 1] ne "X") && (@arrFrames[$i + 1] ne "/"))
{
$iPoints = $iPoints + @arrFrames[$i + 1];
}
else
{
$iPoints = $iPoints + 10;
}
}
else
{
$iPoints = $iPoints + 10;
if ((@arrFrames[$i + 1] ne "X") && (@arrFrames[$i + 1] ne "/"))
{
$iPoints = $iPoints + @arrFrames[$i + 1];
if ((@arrFrames[$i + 2] ne "X") && (@arrFrames[$i + 2] ne "/"))
{
$iPoints = $iPoints + @arrFrames[$i + 2];
}
else
{
$iPoints = $iPoints + (10 - @arrFrames[$i + 1]);
}
}
else
{
$iPoints = $iPoints + 10;
if ((@arrFrames[$i + 2] ne "X") && (@arrFrames[$i + 2] ne "/"))
{
$iPoints = $iPoints + @arrFrames[$i + 2];
}
else
{
$iPoints = $iPoints + 10;
}
}
}
}
# print "$vFrame\n";
# print "Total: $iPoints\n\n";
}
print $iPoints
We started the script with this array, which was given to you as part of the event description:
@arrFrames = (2,5,7,"/",8,1,"X",9,"/",5,3,7,0,4,5,"X",2,0);
We’re going to be tallying points as we work our way through the array, so we initialize a variable to store the accumulated points:
$iPoints = 0;
Now it’s time to get down to business. We need to work our way through this array, and typically the best way to work through an array is with a for loop. So that’s exactly what we do:
for ($i = 0; $i < @arrFrames.Length; $i++)
This loop is going to start at item 0 in our array (the first item – remember, arrays begin indexing at 0 rather than 1); that’s what the $i = 0 means. We’ll continue, incrementing $i by 1 each time through the loop ($i++) while $i is less than the length of the array (@arrFrames.Length).
Inside the for loop the first thing we do is read the first item from the array, which represents the score from the first ball that was rolled, and assign it to the variable $vFrame:
$vFrame = @arrFrames[$i];
The first thing we want to do is check to see what happened with this first ball: did the bowling ball knock down just a few pins, in which case this item will be the number of pins; was this a spare, meaning the array will contain a slash (/); or was this a strike, which is represented with an X? The first step towards determining which of these possibilities occurred is to check for a numeric value, meaning the value is not equal to (ne) either X or /:
if (($vFrame ne "X") && ($vFrame ne "/"))
The only time we need to do anything special with the scoring is when we’ve encountered a strike or a spare. If we just knocked down a few pins we simply add this number of pins to our total score:
$iPoints = $iPoints + (substr $vFrame, 0,1);
Okay, maybe it wasn’t that simple. When you read a value from an array, the array attaches a newline character to the end. We want only the number, so we need to strip off that last character. We do that with the substr method. We know that the number must be between 0 and 9, meaning it will never be more than one digit. So we use substr to say we want 1 digit begin at position 0 (the beginning of the string).
If we received a spare or a strike we’ll fall into our else statement, at which point things get a little complicated. We start with another if statement, this one checking to see if the item is a spare (/):
if ($vFrame eq "/")
If we’ve received a spare, that means we receive 10 points for the current frame, plus the points from the first roll in the second frame. For example, suppose on our first roll we knocked down 4 pins. At that point we have 4 points. In the second roll we knock down the remaining pins, giving us a spare. The first thing we need to do is calculate how many pins were remaining. We do this by subtracting the value of the previous roll (@arrFrames[$i - 1], or 4) from the total number of pins (10), like this:
$iPoints = $iPoints + (10 - @arrFrames[$i - 1]);
Now we have the 10 points from the current frame. Next we need to add the number of points from the next ball rolled. There are two possibilities for the next ball: we knock down some of the pins or we knock down all the pins. If we knock down some of them, the next array item (@arrFrames[$i + 1]) will contain the number of pins knocked down. If we knock down all the pins, the next array item will be a strike (X). First we check to see if we knocked down some of the pins, so we didn’t get a strike:
if (@arrFrames[$i + 1] ne "X")
If this is the case, we want to add the number of pins to the total points:
$iPoints = $iPoints + @arrFrames[$i + 1];
In other words, if we knocked down 8 pins, the score for our spare frame would be 18 (4 + 6 + 8). If instead of just some of the pins we knocked down all the pins, our next array item will contain an X and we simply add 10 points to the total:
$iPoints = $iPoints + 10;
That takes care of a spare. Now what about a strike? A strike means we knocked down all 10 pins on one roll, so the first thing we do is add those 10 points to our total:
$iPoints = $iPoints + 10;
In addition to those 10 points we add the number of points from the next two rolls. Now things really get weird. Basically, we need to redo everything we’ve already done so far. Let’s take a look. First we check to see if the next roll knocks down some of the pins (the next array item is not equal to X or /):
if ((@arrFrames[$i + 1] ne "X") && (@arrFrames[$i + 1] ne "/"))
If it did we add that number of pins to the total:
$iPoints = $iPoints + @arrFrames[$i + 1];
Remember, we need the next two rolls after a strike. If the next roll was a number, the one after that has to be either a number or a spare (you can’t get a strike on the second roll; the definition of a strike is knocking down all 10 pins in one roll). So we check the frame at array index $i + 2, first to see if it’s numeric (not a /):
if (@arrFrames[$i + 2] ne "/")
If it is, we simply add that number of pins to the total:
$iPoints = $iPoints + @arrFrames[$i + 2];
If it’s not, that means we have a spare, so we need to figure out how many pins to add to make up the spare. That number would be 10 minus the number of pins knocked down in roll $i + 1:
$iPoints = $iPoints + (10 - @arrFrames[$i + 1]);
Let’s stop for just a moment before we move on to make sure we understood what we just did. Let’s say we rolled a strike, then our next two rolls were a 5 and then a 4. Our array would look like this:
| Index | Value |
$i | X |
$I + 1 | 5 |
$i + 2 | 4 |
We started by adding 10 points to the total for the strike, so $iPoints is equal to 10. Then we check to see if the value at array index $i + 1 is anything but a strike or a spare, meaning it’s a number. It is, so we add the value of @arrFrames[$i + 1] (5) to the total, making the total 15. Then we check to see if the value at array index $i + 2 is numeric. In this example it is, so we once again add that value to the total, bringing our total to 19.
Now let’s say our array looks like this instead:
| Index | Value |
$i | X |
$I + 1 | 5 |
$i + 2 | / |
Once again we add the 10 points for the strike, making $iPoints equal to 10. The value of @arrFrames[$i + 1] – 5 – isn’t an X or a /, so we add that value to our total, making $iPoints equal to 15. The next item, @arrFrames[$i + 2], is a /, which means we rolled a spare. So we subtract the value of @arrFrames[$i + 1] (5) from 10 and add the result (5) to $iPoints, giving us 20 points.
Still with us? Good, because there’s more.
Let’s say the value of the next ball rolled after the strike is either an X or a /, in other words, not a number. In reality the only thing it can be is a strike. (You can’t roll a spare on the first roll of a frame, that would make it a strike.) So we add 10 to the total points:
iPoints = iPoints + 10;
That takes care of the roll following the strike. Now we need to find out the value of the following roll. It can be only a numeric value or a strike. If it’s a numeric value (not a strike), we add that value to the total:
if ((@arrFrames[$i + 2] ne "X") && (@arrFrames[$i + 2] ne "/"))
{
$iPoints = $iPoints + @arrFrames[$i + 2];
}
If it’s a strike, we add 10 points:
else
{
$iPoints = $iPoints + 10;
}
Like we said, the logic gets a little confusing. Not to mention all the nested if statements. Take a look at all the braces we need to close everything out:
}
}
}
}
}
But we’re finally done. All that’s left is to display the number of points:
print $iPoints;
And how many points did we score in this game? This many:
107
Which, by the way, isn’t a very good score. (The maximum possible is 300.) It’s higher than the Scripting Guys normally get, but still not good.