Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Prolegemona To A Future Scripting Language: Game Of Life In Perl 6

by jaldhar (Vicar)
on Apr 26, 2003 at 14:54 UTC ( [id://253366]=CUFP: print w/replies, xml ) Need Help??

Having read an article about perl 6 I was inspired to write a program in it. Here is an implementation of Conways' (not that Conways') Game of Life (not that game of life or that one.) There is a game of life implementation in the Parrot distribution but this isn't based on that one.

Of course as perl 6 doesn't exist yet I haven't actually tested this. (which also means you can't downvote me for mistakes ha! ha!) but I've tried to write this as if it were a real life program one would write. My original version had comments but I've stripped them out because I thought monks might find it interesting to ty and work out what's going on for themselves. Personally I was impressed by how easily the new features "clicked." Finally I'd like to say I used prolegemona correctly in the title but I kant. Here's the code:

#!/usr/bin/perl use warnings; use strict; my $life = new life(20); while(1) { $life.display(); } class life { has Int $.count; has Int $.dimension; has Array of Int @.grid is dim ($.dimension, $.dimension); method CREATE(Int $dimension) { $.count = 0; $.dimension = $dimension; loop (my $x = 0; $x < $dimenion; $x++) { loop (my $y = 0; $y < $dimension; $y++) { @.grid[$x][$y] = 0; } } @.grid[$dimension / 2 - 1][$dimension / 2] = 1; @.grid[$dimension / 2 - 1][$dimension / 2 + 1] = 1; @.grid[$dimension / 2][$dimension / 2] = 1; @.grid[$dimension / 2][$dimension / 2 - 1] = 1; @.grid[$dimension / 2 + 1][$dimension / 2] = 1; } method calculate() is private { my @newgrid; loop (my $x = 0; $x < .dimension; $x++) { loop (my $y = 0; $y < .dimension; $y++) { my $live = 0; for ($x - 1, $y - 1, $x, $y - 1, $x + 1, $y - 1, $x - 1, $y, $ +x + 1, $y, $x - 1, $y + 1, $x, $y + 1, $x + 1, $y + 1) -> ($nx, $ny +) { next if 0 > $nx > .dimension || 0 > $ny > .dimension; $live++ if @.grid[$nx][$ny] == 1; } $newgrid[$x][$y] = given @.grid[$x][$y] { when 0 { 1 if $live == 3}; when 1 { 1 if 1 < $live < 4 }: } || 0; } } @.grid = @newgrid; } method display { loop (my $x = 0; $x < $.dimension; $x++) { loop (my $y = 0; $y < $.dimension; $y++) { print $.grid[$x][$y] ? '+' : '.'; } print "\n"; } print "Turn $(++$.count), press enter for next turn, ctl-c to quit +'; <STDIN>; .calculate(); } }

--
જલધર

  • Comment on Prolegemona To A Future Scripting Language: Game Of Life In Perl 6
  • Download Code

Replies are listed 'Best First'.
Re: Prolegemona To A Future Scripting Language: Game Of Life In Perl 6
by TheDamian (Vicar) on Apr 27, 2003 at 01:31 UTC
    That's a great effort, especially without a compiler. Here are a few corrections and some more "idiomatic" solutions.

    Comments in bold italics explain corrections on the previous line.
    Comments in normal italics explain stylistic choices on the previous line.


    my $life = new life: 20;
    # Indirect object syntax requires colon after invocant
    loop { $life.display() }
    # Argumentless &loop is cleaner that &while for infinite loops
    class life { has Int $.count; has Int $.max; has Array of Bit @.grid;
    # Bit is potentially much smaller than Int for 1/0 values
    method BUILD(Int $dim)
    # BUILD is the object initializer.
    # CREATE is something else entirely (see Apocalypse 12 ;-)
    { $.count = 0; $.max = $dimension-1; my Array of Bit @grid is dim($dim,$dim) is default(0);
    # Setting default value to 0 reduces initialization costs
    @grid[$dim / 2 - 1][$dim / 2 ] = 1; @grid[$dim / 2 - 1][$dim / 2 + 1] = 1; @grid[$dim / 2 ][$dim / 2 ] = 1; @grid[$dim / 2 ][$dim / 2 - 1] = 1; @grid[$dim / 2 + 1][$dim / 2 ] = 1; @.grid := @grid;
    # Can't declare variably dimensioned array as attribute.
    # Have to declare it when dimension known (i.e. in
    # constructor) and then bind it to attribute
    } sub iterate (&block)
    # Utility sub to factor out repetitive 2D iterations
    { for 0..$.max -> $x
    # Prefer &for over &loop when possible (e.g. here)
    { for 0..$.max -> $y { block($x,$y); } } } method calculate() is private { my @newgrid; iterate { my $live = sum(@.grid[$^x-1..$^x+1][$^y-1..$^y+1]); @newgrid[$^x][$^y] = $live==2 && @.grid[$^x][$^y] || $live==3; }
    # Pass &iterate a block with placeholders for $x and $y
    @.grid = @newgrid; } method display { iterate { print $.grid[$^x][$^y] ?? '+' :: '.'; print "\n" if $^x == $.max; } print "\n"; print "Turn $(++$.count), press enter to continue or ctl-c to quit +; <$*IN>;
    # STDIN is now $*IN
    .calculate(); } }
    Update: Fixed display method (thanks jkahn)
      I should point out that the line:
      my $live = sum(@.grid[$^x-1..$^x+1][$^y-1..$^y+1]);
      is somewhat speculative. It's not clear to me how multidimensional slices will interact with flattening contexts (such as the argument list of the standard &sum function).

      It might be that that line needs to be:

      my $live = sum(map {sum .[$^y-1..$^y+1]} @.grid[$^x-1..$^x+1]);
      Though, given how ugly that is, I'd naturally prefer sum to be smart enough to flatten multidimensional slices.
      Seems like the method display as written would actually print all the cells in one row -- method iterate doesn't have an explicit hook for "reached end of line". That's fine, since we have $.max available within the object.

      I think method display should actually look like this:

      method display { iterate { print $.grid[$^x][$^y] ?? '+' :: '.'; print "\n" if ($^x == $.max); # print each row on own line } print "\n"; # blank line follows grid print "Turn $(++$.count), press enter to continue" print " or ctl-c to quit"; <$*IN>; .calculate(); } # end method display

      A separate style comment: wouldn't it be a bit cleaner to remove the .calculate(); line from method display and change the loop at the top so that it reads:

      loop { $life.display(); $life.calculate(); }
      Then there's no nasty surprise when someone does:
      # somewhere far, far, away $life.display; `shell-script;clear`; # weird stuff on screen tty $life.display; # refresh terminal *only*
      later. I recognize that's personal preference, but Side Effects Are Scary.
        As for changing the control flow in .display, yes, I was tempted to do so too.

        But that went beyond "idiomaticization" and became an issue of software engineering. In the end I deliberately didn't hoist the call to .calculate because I felt it was more important to preserve as much comparability with the original as was consistent with an idiomatic Perl 6 implementation.

        Quite so. Fixed in the original node. Thanks!

        (Hopefully someone will tie this back to the thread on "Damian Isn't Infallible" :-)

      # Setting default value to 0 reduces initialization costs

      How does setting a default value reduce initialization costs? Zero is the default initialization value for PMCs anyways, so wouldn't setting it explicitly just create extra work?

        I meant: reduces initialization costs in comparison to the previous version, which was iteratively setting every element to zero in a pair of nested for loops.
Re: Prolegemona To A Future Scripting Language: Game Of Life In Perl 6
by dpuu (Chaplain) on Apr 26, 2003 at 18:26 UTC
    I like the idea. Here's my attempt: I've tried to use a few more of the advanced features...
    #!/usr/bin/perl6 given new life(dimension => 20) { for 1..Inf -> $count { .display(); print "TURN $count, press enter for next turn, ctl-c to quit"; <$*STDIN>; .calculate(); } } class life { has int $.dimension; has @.grid is dim($.dimension, $.dimension) is base_index($.dimension/2, $.dimension/2) of bit is default(0) is str {<< - + >>[$_]}; method BUILD($.dimension) { for (-1, 0), (-1, 1), (0, 0), (0, -1), (1, 0) -> $x, $y { @.grid[$y][$x] = 1; } } method will_live(int $y, int $x) returns bool is private { my $neighborhood = @.grid[$y+(-1|0|+1)][$x+(-1|0|+1)]; my $alive = sum($neighborhood.elems); @.grid[$y][$x] ?? 1 < $alive-1 < 4 :: $alive == 3; } method calculate { my @new_grid is like @.grid; for @new_grid.kv -> $y, @new_row is rw { for @row.kv -> $x, $new_cell is rw { $new_cell = .will_live($y,$x) ?? 1 :: 0; } } @.grid = @new_grid; } method display { for @.grid -> @row { print @row, "\n"; } } }
    --Dave

    Update: I'm pretty sure the junction I gave for the neighborhood won't work: here's an improved will_live method:

    method will_live(int $y, int $x) returns bool { my $offsets = [-1|0|+1 , -1|0|+1] & none([0,0]); my @neighbors = ($offsets >>+<< [$y,$x]).states; my @cells = @neighbors.map {@.grid[$_[0]][$_[1]]}; my $alive = @cells.sum; @.grid[$y][$x] ?? $alive == 2|3 :: $alive == 3; }
    Still too much line noise though. There must be a simpler way.
Re: Prolegemona To A Future Scripting Language: Game Of Life In Perl 6
by jryan (Vicar) on Apr 26, 2003 at 22:31 UTC

    A few comments:

    $newgrid[$x][$y] = given @.grid[$x][$y] { when 0 { 1 if $live == 3}; when 1 { 1 if 1 < $live < 4 }: } || 0;

    Is probably better written as:

    $newgrid[$x][$y] = given @.grid[$x][$y] { when 0 { 1 if $live == 3}; when 1 { 1 if 1 < $live < 4 }; default { 0 }; };

    However, the exact semantics of the given statement haven't been fully defined yet (as in, if it returns the return value of the last given), so it might not be good to rely on that. Chances are, if it is implemented as a builtin multimethod, it will, though.

    ? ... : is now ?? ... :.

    <STDIN> should be <$*STDIN>, since filehandles are now scalars, and STDIN is a global. (<> might still default to STDIN, though.)

    Pretty good tho.

    Also, there are 2 working versions of life in the Perl6 compiler located in parrot/languages/perl6. (As if anyone cared. :-/)

Re: Prolegemona To A Future Scripting Language: Game Of Life In Perl 6
by pdcawley (Hermit) on Apr 28, 2003 at 21:07 UTC
    Okay, here's my take on this...
    #!/usr/bin/perl package main; # We're Perl 6 we are use warnings; use strict; given Life.new(20) { while 1 { .display_on($*OUT); .calculate; } } class Life { has Int $.generations; has Int $.max; has @.grid; has @.neighbourhoods; method BUILD(Int $dim) { $.generations = 0; my @grid of Bit is dim(2, $dim, $dim) is default(0); @grid[0][$dim / 2 - 1][$dim / 2 ] = 1; @grid[0][$dim / 2 - 1][$dim / 2 + 1] = 1; @grid[0][$dim / 2 ][$dim / 2 ] = 1; @grid[0][$dim / 2 ][$dim / 2 - 1] = 1; @grid[0][$dim / 2 + 1][$dim / 2 ] = 1; @.grid := @grid; # Set up our neighbourhoods and cell count $.max = $dim - 1; for 0..$.max -> $i { for 0..$.max -> $j { my @hood is dim(2,3,3); @hood[0..1][0..2][0..2] >>:=<< @.grid[0..1][map $_%($.max+1), $i-1..$i+1] .[map $_ % ($.max+1),$j-1..$j+1]; push @.neighbourhoods, @hood; } } } method calculate { for @.neighbourhoods -> @hood { my @old := @hood[$.generations % 2]; my $new := @hood[($.generations + 1) % 2]; my $live = sum(*@old) - @old[1][1]; $new = $live == 2 && @old[1][1] || $live == 3; } $.generations++; } method display_on($fh) { for 0 .. $.max -> $i { for 0 .. $.max -> $j { $fh.print($.grid[$generation][$i][$j] ?? '+' :: '.'); LAST { $fh.print("\n") } } } print "Turn $.generation, press enter for next turn, ctl-c to quit +"; <STDIN> } }
    Of course, all this presupposes that I actually understand what I'm doing with :=, which isn't necessarily a good assumption. The trick is that we're precalculating (and binding) all our neighbourhoods, and setting up the old/new grids at initialization time as well, which enables us to simplify the calculation somewhat.
Re: Prolegemona To A Future Scripting Language: Game Of Life In Perl 6
by jaldhar (Vicar) on May 01, 2003 at 05:43 UTC

    Thanks to all that replied. This was very enlightening for me and I hope it was for others too. Here is the script again with suggestions and corrections various monks made and some comments.

    #!/usr/bin/perl use warnings; use strict;

    theDamian calls this indirect object syntax. what's the direct syntax then?

    my $life = new life : 20;

    theDamian points out this is a more idiomatic way of doing an infinite loop.

    loop { $life.display() }

    In perl 6, a class isn't just a package but a new type of block scope indicated by the keyword class. It can have methods indicated by method and attributes indicated by has.

    class life {

    Perl 6 will still have the regular data types but you will have more say over what range of values they can hold or how they should be represented at the machine level. For instance my grid only holds 0 and 1 values. I originally had Int but several monks point out a Bit is potentially more space-efficient.

    has Int $.count; has Int $.dimension; has Array of Bit @.grid;

    There's no need to create a constructor now it is automatically done for you. Instead you should do initialization in a special method. I was led by the Linux Magazine article to believe it should be called CREATE bu theDamian says it ought to be called BUILD.

    You will note functions can have proper prototypes now.

    method BUILD(Int $dimension) { $.count = 0; $.max = $dimension - 1;

    Here we are creating an array of bits. We are saying that it is two-d, size $dimension x $dimension, and the default value of each element is 0.

    my Array of Bit @.grid is dim($dimension,$dimension) is default(0);

    For those who are not up on their game of life theory, what's happening here is an initial grouping of on cells is being created in the center of the grid. The shape is called an R-pentomino and it becomes static after 100 turns.

    @grid[$dimension / 2 - 1][$dimension / 2] = 1; @grid[$dimension / 2 - 1][$dimension / 2 + 1] = 1; @grid[$dimension / 2][$dimension / 2] = 1; @grid[$dimension / 2][$dimension / 2 - 1] = 1; @grid[$dimension / 2 + 1][$dimension / 2] = 1;

    Because the grid is a multidimensional array it cannot be directly declared as an attribute. That's why we made a local variable called @grid. Now that we know its' size we can assign it to the attibute.

    @.grid = @grid; }

    theDamian suggested adding this routine as a utility. Why is it declared as sub and not method? I'll take a guess and say so it can be inherited by subclasses but not used outside the clase sort of like c++ protected functions. Or it's a typo :-)

    sub iterate (&block) {

    This is what foreach loops will look like. No need to put a my on the $x and $y, it's implied.

    Also note parentheses are optional on fors, ifs etc.

    for 0..$.max -> $x { for 0..$.max -> $y { block($x,$y); } } }

    We can declare methods private to our class. In perl5 you could always get to the internals of an object.

    method calculate() is private { my @newgrid; iterate {

    $^x and $^y are placeholders. They are an implicit way of saying my($x, $y) = @_; You should use them as you need them and they are assigned alphabetically (in UTF-8) to the parameters of your function.

    my $live = sum(@.grid[$^x-1..$^x+1][$^y-1..$^y+1]); @newgrid[$^x][$^y] = $live==2 && @.grid[$^x][$^y] || $live==3; } @.grid = @newgrid; } method display { iterate { print $.grid[$^x][$^y] ?? '+' :: '.'; print "\n" if $^x == $.max; } print "\n";

    We can interpolate any arbitrary code or variable into a string with $( ... )

    print "Turn $(++$.count), press enter to continue or ctl-c to quit +;

    File handles are now scalars and STDIN is called IN.

    <$*IN>;

    jkahn wondered why I have calculate here and not in the main loop. That's how I had it originally and how I would do it in a real program but I confess I broke one of my own rules for this exercise and made calculate a private method just to show off that feature.

    .calculate(); } }

    --
    જલધર

Re: Prolegemona To A Future Scripting Language: Game Of Life In Perl 6
by halley (Prior) on Apr 26, 2003 at 15:01 UTC
    So, who's going to be the first to say, "yes, that Conway"?

    --
    [ e d @ h a l l e y . c c ]

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://253366]
Approved by Aristotle
Front-paged by crenz
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (9)
As of 2024-04-18 16:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found