The Bowling challenge

dpuu on 2008-12-24T04:13:37

Having played with the "pairs" challenge, I looked through the other beginners events. Most dealt with files and databases: I didn't want to tackle that right now. So I skipped to challenge-10: interpret a bowling game that is input as an array.

For this one, my thought was to exploit "multi" subs: there're only so many patterns to each frame: I can just code them explicitly. Lets start with my solution:

my @scores = 2,5,7,"/",8,1,"X",9,"/",5,3,7,0,4,5,"X",2,0; my $total = 0; multi frame_score ( Str $a, Str $b, Str $c ;; *@next ) { 30 } multi frame_score ( Str $a, Str $b, Int $c ;; *@next ) { 20 + $c } multi frame_score ( Str $a, Int $b, Str $c ;; *@next ) { 20 } multi frame_score ( Str $a, Int $b, Int $c ;; *@next ) { [+] 10, $b, $c } multi frame_score ( Int $a, Str $b, Str $c ;; *@next ) { 20 } multi frame_score ( Int $a, Str $b, Int $c ;; *@next ) { 10 + $c } multi frame_score ( Int $a, Int $b ;; *@next ) { $a + $b }; while @scores { $total += frame_score |@scores; say "score now $total"; @scores.shift unless @scores[0] eq "X"; @scores.shift; }

So what went wrong? Actually, this one went pretty smoothly. I probably should use specific string values, instead of relying on just the fact that "/" and "X" are generic strings (and it doesn't matter which). That's something I need to investigate. The one thing that feels a bit clunky is that logic at the bottom that decides how many bowls were bowled in each frame (and therefore how many to shift off the array. I'd really like to be able to use a "for" loop to iterate the array.

The're two features missing that don't permit that: one is that it's not possible to do multi pointy-blocks. That would need a syntax something like:

for @x: Int $x -> { ... }, Str $x -> { ... } ;

That specific syntax possibly wouldn't work, but you get the idea...

the other thing missing is the ability to pass non-consumed args to the pointy-block. Something like:

for @a -> $x, $y ;; $z { ... }

where "$x" would not be consumed: only two items would be consumed from the list. ";;" wouldn't be the right syntax for the separate though, because there's not necessary relation between the usage of args for selecting between multi-pointies, and the args that are to be consumed.

Overall, this challenge was felt good in perl6!


Suboptimal

Aristotle on 2008-12-26T02:38:04

The code is really hard to follow. It takes a lot of careful scanning and thinking to understand which cases will invoke which variant of the sub. At first I thought you hadn’t handled the case where a strike is followed by a spare.

Also, the logic for how much to advance is confusingly written. A little extra work would make this 10× easier to read:

subset Strike of Any where { $_ eq 'X' };
subset Spare  of Any where { $_ eq '/' };
subset  Some  of Any where { $_ ne 'X' & '/' };

multi frame_score ( Strike $a, Strike $b, Strike $c ;; *@next ) { 30 }
multi frame_score ( Strike $a, Strike $b,   Some $c ;; *@next ) { 20 + $c }
multi frame_score ( Strike $a,   Some $b,  Spare $c ;; *@next ) { 20 }
multi frame_score ( Strike $a,   Some $b,   Some $c ;; *@next ) { [+] 10, $b, $c }
multi frame_score (   Some $a,  Spare $b, Strike $c ;; *@next ) { 20 }
multi frame_score (   Some $a,  Spare $b,   Some $c ;; *@next ) { 10 + $c }
multi frame_score (   Some $a,   Some $b            ;; *@next ) { $a + $b }

my @scores = 2,5,7,"/",8,1,"X",9,"/",5,3,7,0,4,5,"X",2,0;
my $total = 0;

while @scores {
    $total += frame_score |@scores;
    say "score now $total";
    if @scores.shift ne 'X' { @scores.shift }
}

I am not satisfied with the solution as it stands, but various attempts to improve upon it ran into various traps, the biggest one being that Rakudo will extend an array if you take a slice that overshoots the end of the array. I don’t know whether the spec prescribes this, but it sure is damn inconvenient.

Re:Suboptimal

dpuu on 2008-12-27T02:37:07

Thanks for the subset suggestion. I'd tried putting "where" clauses inline, but that was very clunky. Subsets definitely solve that.

I'm not sure, however, I like your version of the consumption logic any better than mine: using a modifier-op ("shift" in both the conditional and consequent confuses me (I like conditionals to be pure). I would tend to agree, though, that my form was equally confusing. It might be best to be explicit:

        my $bowls_this_frame = @scores[0] eq "X" ?? 1 !! 2;
        @scores.shift for ^$bowls_this_frame