http://qs321.pair.com?node_id=11109120


in reply to Re: implementing a scrabble-esque game on Termux III
in thread implementing a scrabble-esque game on Termux III

...again, I will save vertical space for respondents

Did you copy the dictionary file from Win to Linux without removing the \r's ?

No, I did not, tybalt89. After reading your response, I used one of the many ways to convert newlines between dos and unix. This change allowed the one player game to work.

Then I thought, gosh, maybe if I tried to run this script on linux, I might get better behavior out of the multiplayer game, and low and behold, the multiplayer game worked, which is as far as I've been able to replicate. I've been spending time testing, reading, littering the script with say statements.

As script lengths are getting longer and new versions become relevant, I populated a github repo with what I have now. I'm curious whther others can download and get it to work on their *nix systems: github repo.

At some point, I'm going to need to look closer at syntax that I simply don't understand. Let me just pull out some lines of code that continue to baffle me.

We could start here:

    $board =~ /(?<!\w).{2,}(?!\w)(?{ push @pat, [ $-[0], $& ] })(*FAIL)/;

Q1) Can someone talk through what happens on this line? The sorted patterns in @pat look like this:

patterns---------------- ( [0, "..e......."], [11, "..m......."], [22, "..e......."], [33, "..r......."], [44, "..o......."], [55, ".m........"], [55, "..i......."], [55, "...d......"], [55, "....d....."], [55, ".....i...."], [55, "......e..."], [55, ".......s.."], [66, "..d......."],

Another section of code where I lose my grip is here:

my $underpat = qr/[^@under@tiles]/; say "underpat is $underpat"; my @words = grep { length $pat == length $_ && !/$underpat/ && /^$pat$/ && ( ( $old ^ $_ ) !~ /^\0+\]$/ ) # adding just an 's' no +t allowed

Typical output for underpat looks like this

underpat is (?^u:[^mh h i m q r t]) underpat is (?^u:[^mh h i m q r t]) underpat is (?^u:[^e m e r o i dh h i m q r t]) underpat is (?^u:[^e m e r o i dh h i m q r t]) underpat is (?^u:[^e m e r o i dh h i m q r t]) underpat is (?^u:[^e m e r o i dh h i m q r t]) underpat is (?^u:[^e m e r o i dh h i m q r t]) underpat is (?^u:[^e m e r o i dh h i m q r t]) underpat is (?^u:[^e m e r o i dh h i m q r t]) underpat is (?^u:[^dh h i m q r t]) underpat is (?^u:[^dh h i m q r t]) underpat is (?^u:[^dh h i m q r t])

I would think that this $uderpat represents the tiles that are underneath a word. Q2) Why consider a pattern that throws in the tiles from one's hand? Q3) How does the ultimate line prevent a singular s from being appended whilst not mentioning the letter 's'?

say "new mask is $newmask"; substr $heights, $pos, length $highs, ( $highs & $newmask ) =~ tr/0-4/1-5/r | ( $highs & ~$newmask );

The output from this tells me that I'm not intended to view it. Q4) Seek comment unraveling the logic of the mask.

I've got more questions, but I've got to get moving. At the same github repo, I have several different versions of the script. 3.say.pl might be the best way to view these data on STDIN. The way I want to take things in indicate my 2.mp.pl, where functions are herded into a package. So far, I am not calling them correctly, but I keep making inroads.

One feature that this lacks so far is keeping of time. So I've introduced POSIX. Also, I like bliako's idea of determining a state and making the game a series of these snapshots. 3.say.pl creates a path for games to go in. It also has the tiles with their new "correct" frequency. I was horrified to open up the tiles and see a 'Qu' tile. I'm just going to reject the 'u' in that tile as a recent uglification of this game.

I wanted to write this up all perfect including the specification, but I have to take a detour to understand this code on every line yet.

Thanks for your comments

Replies are listed 'Best First'.
Re^3: implementing a scrabble-esque game on Termux III
by tybalt89 (Monsignor) on Nov 23, 2019 at 22:50 UTC
    $board =~ /(?<!\w).{2,}(?!\w)(?{ push @pat, [ $-[0], $& ] })(*FAIL)/ +;

    This is the heart of finding a place to move. For a word to be a legal move, it must match one of the patterns in @pat. The (?<!\w) prevents putting the new word next to a preceding word, and the (?!\w) prevent putting the new word abutting a following word. The(*FAIL) forces the regex engine to try every possible place to find a match.

    Try

    perl -le ' "abcd" =~ /.{2,}(?{print $&})(*FAIL)/ '

    and notice it prints out every substring with two or more characters.

      leaving vertical space for respondents...

      Thanks for your comment,

        Explanation of the masking in "matchrule"

        sub matchrule { my ( $old, $highs, $word ) = @_; $old eq $word and return 0; my $newmask = ( $old ^ $word ) =~ tr/\0/\xff/cr; ( $newmask & $highs ) =~ tr/5// and return 0; my $tiles = "@tiles"; $tiles =~ s/$_// or return 0 for ( $newmask & $word ) =~ /\w/g; return 1; }

        Let's follow a call to matchrule('hello', '14222', 'world')

        First off, if old and word are the same, it's not a valid move.

        Next, get the mask. ( note: strings have been printed by Data::Dumper::Useqq = 1 )

        'hello' ^ 'world' => "\37\n\36\0\13"

        However, I want to change all non nulls ("\0") to "\xff" so that characters can pass through these positions unchanged.

        my $newmask = ('hello' ^ 'world') =~ tr/\0/\xff/cr => "\377\377\377\0\ +377"

        Now we use this mask against the tile heights and look for any 5's, because the "\xff" in $newmask are at the positions where new tiles will be added.

        ("\377\377\377\0\377" & '14222') => "142\0002"

        and since there is no 5, the new word is not invalid (yet).

        "\377\377\377\0\377" & 'world' => "wor\0d"

        This leaves only the new tiles that must be played, ignoring the "\0" because those positions use the old tile. So remove each new letter from a string of the tile rack, and if any are not there, the move is invalid.

        Hope this helps.

        my $newmask = ( $old ^ $word ) =~ tr/\0/\xff/cr; $flip and ( $board, $heights ) = flip $board, $heights; substr $board, $pos, length $word, $word; #say "new mask is $newmask"; substr $heights, $pos, length $highs, ( $highs & $newmask ) =~ tr/0-4/1-5/r | ( $highs & ~$newmask ); $flip and ( $board, $heights ) = flip $board, $heights; my $tiles = join '', @tiles; say "word is $word"; $tiles =~ s/$_// for split //, $word & $newmask;

        let me try...

        my $newmask = ( $old ^ $word ) =~ tr/\0/\xff/cr;

        $newmask is "\xff" where the old and new words differ, that is a new tile must be used. It is "\0" where no tile is to be placed (using existing board state).

        $flip and ( $board, $heights ) = flip $board, $heights; substr $board, $pos, length $word, $word; #say "new mask is $newmask"; # $newmask has unprintable characters +, use something like Data::Dump that will show them. substr $heights, $pos, length $highs, ( $highs & $newmask ) =~ tr/0-4/1-5/r | ( $highs & ~$newmask );

        hehehe! GOAL => increment the stack sizes for only the new tiles.

        $highs & $newmask produces a string that only has non-null characters where new tiles are to be played. In other words, it isolates the stacks for the new tiles, everywhere else is "\0".
        =~ tr/0-4/1-5/r increments the height for each place a new tile is played.
        ~$newmask inverts $newmask to be "\xff" only where old tiles remain visible.
        $highs & ~$newmask produces a string with only the unchanged stack values (and "\0" where the new values are).
        Finally, the | puts the old and new stack counts together so they can be replaced in the heights string.

        $flip and ( $board, $heights ) = flip $board, $heights; my $tiles = join '', @tiles; say "word is $word"; $tiles =~ s/$_// for split //, $word & $newmask;

        This removes the tiles that are used for this word from the string of all tiles, soon to be replaced in @tiles.

        Hope this helps...