Create an account


Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Fedora - Demonstrating PERL with Tic-Tac-Toe, Part 2

#1
Demonstrating PERL with Tic-Tac-Toe, Part 2

<div><p>The astute observer may have noticed that PERL is misspelled. In <a rel="noreferrer noopener" aria-label="a March 1, 1999 interview with Linux Journal (opens in a new tab)" href="https://www.linuxjournal.com/article/3394" target="_blank">a March 1, 1999 interview with Linux Journal</a>, Larry Wall explained that he originally intended to include the letter “A” from the word “And” in the title “<span style="text-decoration: underline">P</span>ractical <span style="text-decoration: underline">E</span>xtraction <span style="text-decoration: underline">A</span>nd <span style="text-decoration: underline">R</span>eport <span style="text-decoration: underline">L</span>anguage” such that the acronym would correctly spell the word PEARL. However, before he released PERL, Larry heard that <a rel="noreferrer noopener" aria-label="another programming language (opens in a new tab)" href="https://en.wikipedia.org/wiki/PEARL_(programming_language)" target="_blank">another programming language</a> had already taken that name. To resolve the <a rel="noreferrer noopener" aria-label="name collision (opens in a new tab)" href="https://en.wikipedia.org/wiki/Name_collision" target="_blank">name collision</a>, he dropped the “A”. The acronym is still valid because <a rel="noreferrer noopener" aria-label="title case and acronyms allow articles, short prepositions and conjunctions to be omitted (opens in a new tab)" href="https://en.wikipedia.org/wiki/Letter_case#Title_case" target="_blank">title case and acronyms allow articles, short prepositions and conjunctions to be omitted</a> (compare for example the acronym <a rel="noreferrer noopener" aria-label="LASER (opens in a new tab)" href="https://en.wikipedia.org/wiki/Laser" target="_blank">LASER</a>).</p>
<p>Name collisions happen when distinct commands or variables with the same name are merged into a single <a rel="noreferrer noopener" aria-label="namespace (opens in a new tab)" href="https://en.wikipedia.org/wiki/Namespace" target="_blank">namespace</a>. Because Unix commands share a common namespace, two commands cannot have the same name.</p>
<p>The same problem exists for the names of global variables and subroutines within programs written in languages like PERL. This is an especially significant problem when programmers try to collaborate on large software projects or otherwise incorporate code written by other programmers into their own code base.</p>
<p>Starting with <a rel="noreferrer noopener" aria-label=" (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/perlhist.html#THE-RECORDS:~:text=1994-Oct-17" target="_blank">version 5</a>, PERL supports <a rel="noreferrer noopener" aria-label="packages (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/perlmod.html#Packages" target="_blank">packages</a>. Packages allow PERL code to be modularized with unique namespaces so that the global variables and functions of the modularized code will not collide with the variables and functions of another script or module.</p>
<p>Shortly after its release, PERL5 software developers all over the world began writing software modules to extend PERL’s core functionality. Because many of those developers (currently about 15,000) have made their work freely available on the <a rel="noreferrer noopener" aria-label="Comprehensive Perl Archive Network (CPAN) (opens in a new tab)" href="https://en.wikipedia.org/wiki/CPAN" target="_blank">Comprehensive Perl Archive Network (CPAN)</a>, you can easily extend the functionality of PERL on your PC so that you can perform very advanced and complex tasks with just a few commands.</p>
<p>The remainder of this article builds on <a href="https://fedoramagazine.org/demonstrating-perl-with-tic-tac-toe-part-1/" target="_blank" rel="noreferrer noopener" aria-label="the previous article (opens in a new tab)">the previous article</a> in this series by demonstrating how to install, use and create PERL modules on Fedora Linux.</p>
<h2>An example PERL program</h2>
<p>See the example program from the previous article below, with a few lines of code added to import and use some modules named <em>chip1</em>, <em>chip2</em> and <em>chip3</em>. It is written in such a way that the program should work even if the <em>chip</em> modules cannot be found. Future articles in this series will build on the below script by adding the additional modules named <em>chip2</em> and <em>chip3</em>.</p>
<p>You should be able to copy and paste the below code into a plain text file and use the same <a rel="noreferrer noopener" aria-label="one-liner (opens in a new tab)" href="https://fedoramagazine.org/demonstrating-perl-with-tic-tac-toe-part-1/#:~:text=one-liner" target="_blank">one-liner</a> that was provided in the previous article to strip the leading numbers.</p>
<pre class="wp-block-preformatted">00 #!/usr/bin/perl
01 02 use strict;
03 use warnings;
04 05 use feature 'state';
06 07 use constant MARKS=&gt;[ 'X', 'O' ];
08 use constant HAL9K=&gt;'O';
09 use constant BOARD=&gt;'
10 ┌───┬───┬───┐
11 │ 1 │ 2 │ 3 │
12 ├───┼───┼───┤
13 │ 4 │ 5 │ 6 │
14 ├───┼───┼───┤
15 │ 7 │ 8 │ 9 │
16 └───┴───┴───┘
17 ';
18 19 use lib 'hal';
20 use if -e 'hal/chip1.pm', 'chip1';
21 use if -e 'hal/chip2.pm', 'chip2';
22 use if -e 'hal/chip3.pm', 'chip3';
23 24 sub get_mark {
25 my $game = shift;
26 my @nums = $game =~ /[1-9]/g;
27 my $indx = (@nums+1) % 2;
28 29 return MARKS-&gt;[$indx];
30 }
31 32 sub put_mark {
33 my $game = shift;
34 my $mark = shift;
35 my $move = shift;
36 37 $game =~ s/$move/$mark/;
38 39 return $game;
40 }
41 42 sub get_move {
43 return (&lt;&gt; =~ /^[1-9]$/) ? $&amp; : '0';
44 }
45 46 PROMPT: {
47 no strict;
48 no warnings;
49 50 state $game = BOARD;
51 52 my $mark;
53 my $move;
54 55 print $game;
56 57 if (defined &amp;get_victor) {
58 my $victor = get_victor $game, MARKS;
59 if (defined $victor) {
60 print "$victor wins!\n";
61 complain if ($victor ne HAL9K);
62 last PROMPT;
63 }
64 }
65 66 last PROMPT if ($game !~ /[1-9]/);
67 68 $mark = get_mark $game;
69 print "$mark\'s move?: ";
70 71 if ($mark eq HAL9K and defined &amp;hal_move) {
72 $move = hal_move $game, $mark, MARKS;
73 print "$move\n";
74 } else {
75 $move = get_move;
76 }
77 $game = put_mark $game, $mark, $move;
78 79 redo PROMPT;
80 }</pre>
<p>Once you have the above code downloaded and working, create a subdirectory named <em>hal</em> under the same directory that you put the above program. Then copy and paste the below code into a plain text file and use the same procedure to strip the leading numbers. Name the version without the line numbers <em>chip1.pm</em> and move it into the <em>hal</em> subdirectory.</p>
<pre class="wp-block-preformatted">00 # basic operations chip
01 02 package chip1;
03 04 use strict;
05 use warnings;
06 07 use constant MAGIC=&gt;'
08 ┌───┬───┬───┐
09 │ 2 │ 9 │ 4 │
10 ├───┼───┼───┤
11 │ 7 │ 5 │ 3 │
12 ├───┼───┼───┤
13 │ 6 │ 1 │ 8 │
14 └───┴───┴───┘
15 ';
16 17 use List::Util 'sum';
18 use Algorithm::Combinatorics 'combinations';
19 20 sub get_moves {
21 my $game = shift;
22 my $mark = shift;
23 my @nums;
24 25 while ($game =~ /$mark/g) {
26 push @nums, substr(MAGIC, $-[0], 1);
27 }
28 29 return @nums;
30 }
31 32 sub get_victor {
33 my $game = shift;
34 my $marks = shift;
35 my $victor;
36 37 TEST: for (@$marks) {
38 my $mark = $_;
39 my @nums = get_moves $game, $mark;
40 41 next unless @nums &gt;= 3;
42 for (combinations(\@nums, 3)) {
43 my @comb = @$_;
44 if (sum(@comb) == 15) {
45 $victor = $mark;
46 last TEST;
47 }
48 }
49 }
50 51 return $victor;
52 }
53 54 sub hal_move {
55 my $game = shift;
56 my @nums = $game =~ /[1-9]/g;
57 my $rand = int rand @nums;
58 59 return $nums[$rand];
60 }
61 62 sub complain {
63 print "Daisy, Daisy, give me your answer do.\n";
64 }
65 66 sub import {
67 no strict;
68 no warnings;
69 70 my $p = __PACKAGE__;
71 my $c = caller;
72 73 *{ $c . '::get_victor' } = \&amp;{ $p . '::get_victor' };
74 *{ $c . '::hal_move' } = \&amp;{ $p . '::hal_move' };
75 *{ $c . '::complain' } = \&amp;{ $p . '::complain' };
76 }
77 78 1;</pre>
<p>The first thing that you will probably notice when you try to run the program with chip1.pm in place is an error message like the following (emphasis added):</p>
<pre class="wp-block-preformatted">$ Can't locate <span style="color: #8B0000">Algorithm/Combinatorics.pm</span> in @INC (you may need to install the Algorithm::Combinatorics module) (@INC contains: hal /usr/local/lib64/perl5/5.30 /usr/local/share/perl5/5.30 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5) at hal/chip1.pm line 17.
BEGIN failed--compilation aborted at hal/chip1.pm line 17.
Compilation failed in require at /usr/share/perl5/if.pm line 15.
BEGIN failed--compilation aborted at game line 18.</pre>
<p>When you see an error like the one above, just use the <em>dnf</em> command to search Fedora’s package repository for the name of the system package that <em>provides</em> the needed PERL module as shown below. Note that the module name and path from the above error message have been prefixed with <strong>*/</strong> and then surrounded with single quotes.</p>
<pre class="wp-block-preformatted">$ dnf provides '*/<span style="color: #8B0000">Algorithm/Combinatorics.pm</span>'
...
<span style="color: #8B0000">perl-Algorithm-Combinatorics</span>-0.27-17.fc31.x86_64 : Efficient generation of combinatorial sequences
Repo : fedora
Matched from:
Filename : /usr/lib64/perl5/vendor_perl/Algorithm/Combinatorics.pm</pre>
<p>Hopefully it will find the needed package which you can then install:</p>
<pre class="wp-block-preformatted">$ sudo dnf install <span style="color: #8B0000">perl-Algorithm-Combinatorics</span></pre>
<p>Once you have all the needed modules installed, the program should work.</p>
<h2>How it works</h2>
<p>This example is admittedly quite contrived. Nothing about Tic-Tac-Toe is complex enough to need a CPAN module. To demonstrate installing and using a non-standard module, the above program uses the <em><a rel="noreferrer noopener" aria-label="combinations (opens in a new tab)" href="https://metacpan.org/pod/Algorithm::Combinatorics#combinations(%5C@data,-$k)" target="_blank">combinations</a></em> library routine from the <em><a rel="noreferrer noopener" aria-label="Algorithm::Combinatorics (opens in a new tab)" href="https://metacpan.org/pod/Algorithm::Combinatorics" target="_blank">Algorithm::Combinatorics</a></em> module to generate a list of the possible combinations of three numbers from the provided set. Because the board numbers have been mapped to a 3×3 <a rel="noreferrer noopener" aria-label="magic square (opens in a new tab)" href="https://en.wikipedia.org/wiki/Magic_square" target="_blank">magic square</a>, any set of three numbers that sum to 15 will be aligned on a column, row or diagonal and will therefore be a winning combination.</p>
<p>Modules are imported into a program with the <a rel="noreferrer noopener" aria-label="use (opens in a new tab)" href="https://perldoc.perl.org/5.30.0//functions/use.html" target="_blank"><em>use</em></a> and <a rel="noreferrer noopener" aria-label="require (opens in a new tab)" href="https://perldoc.perl.org/5.30.0//functions/require.html" target="_blank"><em>require</em></a> commands. The only difference between them is that the <em>use</em> command automatically calls the <em>import</em> subroutine (if one exists) in the module being imported. The <em>require</em> command does not automatically call any subroutines.</p>
<p>Modules are just files with a <strong>.pm</strong> extension that contain PERL subroutines and variables. They begin with the <a rel="noreferrer noopener" aria-label="package (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/functions/package.html" target="_blank"><em>package</em></a> command and end with <strong>1;</strong>. But otherwise, they look like any other PERL script. The file name should match the package name. Package and file names are case sensitive.</p>
<p>Beware that when you are reading online documentation about PERL modules, the documentation often veers off into topics about <a rel="noreferrer noopener" aria-label="classes (opens in a new tab)" href="https://en.wikipedia.org/wiki/Class_(computer_programming)" target="_blank">classes</a>. Classes are built on modules, but a simple module does not have to adhere to all the restrictions that apply to classes. When you start seeing words like <em>method</em>, <em>inheritance</em> and <em>polymorphism</em>, you are reading about classes, not modules.</p>
<p>There are two subroutine names that are reserved for special use in modules. They are <em>import</em> and <em>unimport</em> and they are called by the <em>use</em> and <em>no</em> directives respectively.</p>
<p>The purpose of the <em>import</em> and <em>unimport</em> subroutines is typically to alias and unalias the module’s subroutines in and out of the calling namespace respectively. For example, line 17 of <em>chip1.pm</em> shows the <em>sum</em> subroutine being imported from the <em>List::Util</em> module.</p>
<p>The <em>constant</em> module, as used on lines 07 of <em>chip1.pm</em>, is also altering the caller’s namespace (chip1), but rather than importing a predefined subroutine, it is creating a special type of variable.</p>
<p>All the <a rel="noreferrer noopener" aria-label="identifiers (opens in a new tab)" href="https://en.wikipedia.org/wiki/Lexical_analysis#Token" target="_blank">identifiers</a> immediately following the <em>use</em> keywords in the above examples are modules. On my system, many of them can be found under the <em>/usr/share/perl5</em> directory.</p>
<p>Notice that the above error message states “@INC contains:” followed by a list of directories. <em><a rel="noreferrer noopener" aria-label="INC (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/perlvar.html#SPECIAL-VARIABLES:~:text=The%20array%20%40INC" target="_blank">INC</a></em> is a special PERL variable that lists, in order, the directories from which modules should be loaded. The first file found with a matching name will be used.</p>
<p>As demonstrated on line 19 of the Tic-Tac-Toe game, the <em><a rel="noreferrer noopener" aria-label="lib (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/lib.html" target="_blank">lib</a></em> module can be used to update the list of directories in the <em>INC</em> variable.</p>
<p>The <em>chip1</em> module above provides an example of a very simple <em>import</em> subroutine. In most cases you will want to use the import subroutine that is provided by the <a rel="noreferrer noopener" aria-label="Exporter (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/Exporter.html" target="_blank">Exporter</a> module rather than implementing your own. A custom <em>import</em> subroutine is used in the above example to demonstrate the basics of what it does. Also, the custom implementation makes it easy to override the subroutine definitions in later examples.</p>
<p>The <em>import</em> subroutine shown above reveals some of the hidden magic that makes packages work. All variables that are both <em>globally</em> scoped (that is, created outside of any pair of curly brackets) and <em>dynamically</em> scoped (that is, not prefixed with the keywords <em>my</em> or <em>state</em>) and all global subroutines are automatically prefixed with a package name. The default package name if no <em>package</em> command has been issued is <em>main</em>.</p>
<p>By default, the current package is assumed when an unqualified variable or subroutine is used. When <em>get_move</em> is called from the <em>PROMPT</em> block in the above example, <em>main::get_move</em> is assumed because the <em>PROMPT</em> block exists in the <em>main</em> package. Likewise, when <em>get_moves</em> is called from the <em>get_victor</em> subroutine, <em>chip1::get_moves</em> is assumed because <em>get_victor</em> exists in the <em>chip1</em> package.</p>
<p>If you want to access a variable or subroutine that exists in a different package, you either have to use its fully qualified name or create a local alias that refers to the desired subroutine.</p>
<p>The <em>import</em> subroutine shown above demonstrates how to create subroutine aliases that refer to subroutines in other packages. On lines 73-75, the fully qualified names for the subroutines are being constructed and then the <a rel="noreferrer noopener" aria-label="symbol table (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/perlmod.html#Symbol-Tables" target="_blank">symbol table</a> name for the subroutine in the calling namespace (the package in which the <em>use</em> statement is being executed) is being assigned the <a rel="noreferrer noopener" aria-label="reference (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/perlref.html#Making-References" target="_blank">reference</a> of the subroutine in the local package (the package in which the <em>import</em> subroutine is defined).</p>
<p>Notice that subroutines, like variables, have sigils. The sigil for subroutines is the ampersand symbol (<strong>&amp;</strong>). In most contexts, the sigil for subroutines is optional. When working with <a rel="noreferrer noopener" aria-label="references (opens in a new tab)" href="https://en.wikipedia.org/wiki/Reference_(computer_science)" target="_blank">references</a> (as shown on lines 73-75 of the <em>import</em> subroutine) and when checking if a subroutine is defined (as shown on lines 57 and 71 of the <em>PROMPT</em> block), the sigil for subroutines is required.</p>
<p>The <em>import</em> subroutine shown above is just a bare minimum example. There is a lot that it doesn’t do. In particular, <a href="https://perldoc.perl.org/5.30.0/perlmodlib.html#NOTE" target="_blank" rel="noreferrer noopener" aria-label="a proper import subroutine would not automatically import any subroutines or variables (opens in a new tab)">a proper import subroutine would not automatically import any subroutines or variables</a>. Normally, the user would be expected to provide a list of the routines to be imported on the <em>use</em> line and that list is available to the import subroutine in <a href="https://perldoc.perl.org/5.30.0/perlsub.html#DESCRIPTION:~:text=Any%20arguments%20passed%20in%20show%20up%20in%20the%20array%20%40_" target="_blank" rel="noreferrer noopener" aria-label="the @_ array (opens in a new tab)">the @_ array</a>.</p>
<h2>Final notes</h2>
<p>Lines 25-27 of <em>chip1.pm</em> provide a good example of PERL’s dense notation problem. With just a couple of lines code, the board numbers on which a given mark has been placed can be determined. But does the statement within the conditional clause of the <em>while</em> loop perform the search from the beginning of the <em>game</em> variable on each iteration? Or does it continue from where it left off each time? PERL correctly guesses that I want it to provide the position (<a rel="noreferrer noopener" aria-label="$-[0] (opens in a new tab)" href="https://perldoc.perl.org/5.30.0/perlvar.html#Variables-related-to-regular-expressions:~:text=%40LAST_MATCH_START" target="_blank">$<strong>-[0]</strong></a>) of the <span style="text-decoration: underline">next</span> mark, if any exits, on each iteration. But exactly what it will do can be very difficult to determine just by looking at the code.</p>
<p>The last things of note in the above examples are the <em>strict</em> and <em>warnings</em> directives. They enable extra <a rel="noreferrer noopener" aria-label="compile-time (opens in a new tab)" href="https://en.wikipedia.org/wiki/Compile_time" target="_blank">compile-time</a> and <a rel="noreferrer noopener" aria-label="runtime (opens in a new tab)" href="https://en.wikipedia.org/wiki/Runtime_(program_lifecycle_phase)" target="_blank">runtime</a> <a rel="noreferrer noopener" aria-label="debugging (opens in a new tab)" href="https://en.wikipedia.org/wiki/Debugging" target="_blank">debugging</a> messages respectively. Many PERL programmers recommend always including these directives so that programming errors are more likely to be spotted. The downside of having them enabled is that some complex code will sometimes cause the debugger to erroneously generate unwanted output. Consequently, the <em>strict</em> and/or <em>warnings</em> directives may need to be disabled in some code blocks to get your program to run correctly as demonstrated on lines 67 and 68 of the example <em>chip1</em> module. The <em>strict</em> and <em>warnings</em> directives have nothing to do with the program and they can be omitted. Their only purpose is to provide feedback to the program developer.</p>
</div>


https://www.sickgaming.net/blog/2020/03/...oe-part-2/
Reply



Forum Jump:


Users browsing this thread:
2 Guest(s)

Forum software by © MyBB Theme © iAndrew 2016