Computing: Website and Database Programming

Simple Nim.


1. The Nim game.
 
Nim is a mathematical game of strategy in which two players take turns removing (or "nimming") objects from distinct heaps or piles. On each turn, a player must remove at least one object, and may remove any number of objects provided they all come from the same heap or pile. Depending on the version being played, the goal of the game is either to avoid taking the last object or to take the last object.
There are lots of different versions of Nim, one of the best known is the Marienbad game, so called because having been played in the French New Wave film Last Year at Marienbad (1961). In this variant, there are 4 heaps (rows) with 1, 3, 5 and 7 objects (actually matches). The players may take any number of matches they want (at least one) but all from the same row. The player, who takes the last match, looses. Game variants, where the last object looses, are sometimes called misere variants If you like this kind of strategy games, you may want to download my Marienbad PC application from the Lazarus/Free Pascal Programming section of my site. In Fort Boyard Nim, there is one single heap with 15 objects (matches or sticks). The players may take 1, 2 or 3 matches at each turn. The player, who takes the last match, looses. The Game of Whytoff consists of several heaps and the players may take matches in one or two of them; if they take in two heaps, they must remove the same number of objects in each of them. The last match wins. In the Nimbi variant, there are 4 rows of 8 matches each. The players take 1, 2 or 3 matches, that are adjacent either in a row or a column. The last match wins or looses, depending on what has been said at the beginning of the game. Other variants, with more specific rules, are for example the Grundy game and Squayles.
2. My Simple Nim online application.
 
My Simple Nim online application is based on Fort Boyard Nim, with the initial number of matches (10 to 30), as well as the winning rule being set at the beginning of the game. The application is for 1 human player, who plays against the computer. There are three strength levels available:
  • Novice: The computer makes all random moves, including at the end of the game (may be seen as if it did not remember if the last match wins or looses).
  • Intermediate: At the first half of the game (i.e. until half of the initial number of matches has been taken), the computer plays randomly or not (1 random move out of 3); in the second half, it plays at full strength.
  • Expert: The computer plays at full strength, i.e. if there is a possibility to win the game, it will always win.
To play the game, choose the number of initial matches, set the wanted game parameters and push the Start button. If the computer has to make the first move, it does it. Now it's your turn. Enter the number of matches, that you want to nim, then push Play. The computer moves automatically after you have done so. If the game is over, push New to prepare for a new one, set the game parameters and push Start to begin.
Use the following link to start the online application.
3. The Simple Nim Perl script.
 
Simple Nim is a Perl application, user input validity check, input field focusing and display of the winner message being done by Javascript. The validity check is done by a function called when the submit button is pushed. This function, displaying a message if input isn't ok, returns a Boolean: If it is true, the Perl script is called, otherwise, nothing happens (concerning the Javascript code of this function, cf. below). The code for the field focusing and the message display is directly included into the website, generated by the Perl script. Use the following links to download the Simple Nim application (HTML, Javascript and Perl sources) resp. to show the Simple Nim Perl source code.
#!C:/Programs/Strawberry/win64/perl/bin/perl.exe -I"."

use strict; use warnings;
use CGI;
use CGI::Carp "fatalsToBrowser";

my $cgi = new CGI;
print "Content-Type: text/html\n\n";
print '<!DOCTYPE html>', "\n\n";
my $app_dir = "C:\\Programs\\Apache24\\htdocs\\computing\\website\\applications"; # Change this to your application directory!
my $initialmatches; my $lastmatch; my $firstmove; my $level;
my $matches; my $remaining; my $matchesplayer; my $matchescomputer;
# Get parameters from 'calling' webpage and determine next button-push action
my %params = $cgi->Vars;
my $action = lc($params{'action'});
$action = 'init' unless ($action);
if ($action eq 'init' or $action eq 'new' or $action eq 'start' or $action eq 'play') {
    my $nextaction;
    if ($action eq 'init' or $action eq 'new') {
        if ($action eq 'init') {
            $initialmatches = 15; $lastmatch = 'winner'; $firstmove = 'player'; $level = 1;
        }
        else {
            $initialmatches = $params{'hinitialmatches'}; $lastmatch = $params{'hlastmatch'};
            $firstmove = $params{'hfirstmove'}; $level = $params{'hlevel'};
        }
        $matches = 0; $remaining = 0;
        $matchesplayer = 0; $matchescomputer = 0;
        $nextaction = 'start';
    }
    else {
        if ($action eq 'start') {
            $initialmatches = $params{'initialmatches'}; $lastmatch = $params{'lastmatch'};
            $firstmove = $params{'firstmove'}; $level = $params{'level'};
            $matches = $initialmatches; $remaining = $matches;
            $matchesplayer = 0; $matchescomputer = 0;
        }
        else {
            $initialmatches = $params{'hinitialmatches'}; $lastmatch = $params{'hlastmatch'};
            $firstmove = $params{'hfirstmove'}; $level = $params{'hlevel'};
            $matches = $params{'hmatches'}; $remaining = $params{'hremaining'};
            $matchesplayer = $params{'player'}; # get number of mateches taken by player
        }
        $nextaction = 'play';
    }
    # XSS protection: Potential risk characters removal and variable check (better being to careful than not enough...)
    $initialmatches =~ s/[^0-9]*//g; $matches =~ s/[^0-9]*//g; $remaining =~ s/[^0-9]*//g;
    $matchesplayer =~ s/[^1-3]//g; $matchescomputer =~ s/[^1-3]//g;
    $initialmatches = 15 unless ($initialmatches);
    $matches = 0 unless ($matches);
    $remaining = 0 unless ($remaining);
    $matchesplayer = 0 unless ($matchesplayer);
    $matchescomputer = 0 unless ($matchescomputer);
    $lastmatch = 'winner' unless ($lastmatch eq 'looser');
    $firstmove = 'player' unless ($firstmove eq 'computer');
    $level = 1 unless ($level == 2 or $level == 3);
    # Do action, depending on the actual button caption ('Start' or 'Play')
    my $winner = '';
    if ($action eq 'start' or $action eq 'play') {
        if ($action eq 'start') {
            # If the computer has to move first, do the move (otherwise just generate the webpage)
            if ($firstmove eq 'computer') {
                $matches = $remaining;
                $matchescomputer = ComputerMove($remaining, $lastmatch, $level);
                $remaining = $matches - $matchescomputer; # matches remaining after computer's move
            }
        }
        elsif ($action eq 'play') {
            # Determine matches remaining after player's move. If all matches have been taken, the game is over. Otherwise, do computer's move
            $matches = $remaining;
            $remaining -= $matchesplayer; # matches remaining after player's move
            if ($remaining == 0) {
                # No match remaining: Player has taken last match
                if ($lastmatch eq 'winner') {
                    $winner = 'The player takes the last match and wins!';
                }
                else {
                    $winner = 'The player takes the last match and looses!';
                }
            }
            else {
                # One or more matches remaining: Do computer's move
                $matchescomputer = ComputerMove($remaining, $lastmatch, $level);
                $remaining -= $matchescomputer; # matches remaining after computer's move
                if ($remaining == 0) {
                    # No match remaining: Computer has taken last match
                    if ($lastmatch eq 'winner') {
                        $winner = 'The computer takes the last match and wins!';
                    }
                    else {
                        $winner = 'The computer takes the last match and looses!';
                    }
                }
            }
        }
        if ($winner) {
            $nextaction = 'new';
        }
    }
    # Read template HTML file
    my $template = "$app_dir\\simplenim.template.html";
    open(my $input, "<", $template)
        or die "Can't open template file $template: $!";
    my @lines = <$input>;
    close($input);
    # Create the webpage, printing the template, replacing all #-tags by the actual values
    my $matchcount = 0;
    foreach my $line (@lines) {
        chomp($line);
        if ($line) {
            if (substr($line, 0, 1) eq '#') { # A '#' at beginning of line indicates that line contains a tag
                $line = substr($line, 1);
                if ($line =~ /#initialmatches#/) {
                    $line =~ s/#initialmatches#/$initialmatches/;
                    if ($action eq 'init' or $action eq 'new') {
                        $line =~ s/#readonly#//;
                    }
                    else {
                        $line =~ s/#readonly#/readonly="readonly"/;
                    }
                }
                elsif ($line =~ /#checked1a#/) {
                    if ($lastmatch eq 'winner') {
                        $line =~ s/#checked1a#/checked="checked"/;
                    }
                    else {
                        $line =~ s/#checked1a#//;
                    }
                }
                elsif ($line =~ /#checked1b#/) {
                    if ($lastmatch eq 'looser') {
                        $line =~ s/#checked1b#/checked="checked"/;
                    }
                    else {
                        $line =~ s/#checked1b#//;
                    }
                }
                elsif ($line =~ /#checked2a#/) {
                    if ($firstmove eq 'player') {
                        $line =~ s/#checked2a#/checked="checked"/;
                    }
                    else {
                        $line =~ s/#checked2a#//;
                    }
                }
                elsif ($line =~ /#checked2b#/) {
                    if ($firstmove eq 'computer') {
                        $line =~ s/#checked2b#/checked="checked"/;
                    }
                    else {
                        $line =~ s/#checked2b#//;
                    }
                }
                elsif ($line =~ /#selected1#/) {
                    if ($level == 1) {
                        $line =~ s/#selected1#/selected="selected"/;
                    }
                    else {
                        $line =~ s/#selected1#//;
                    }
                }
                elsif ($line =~ /#selected2#/) {
                    if ($level == 2) {
                        $line =~ s/#selected2#/selected="selected"/;
                    }
                    else {
                        $line =~ s/#selected2#//;
                    }
                }
                elsif ($line =~ /#selected3#/) {
                    if ($level == 3) {
                        $line =~ s/#selected3#/selected="selected"/;
                    }
                    else {
                        $line =~ s/#selected3#//;
                    }
                }
                elsif ($line =~ /#visible#/) {
                    # Show actual matches (by hiding those already taken)
                    $matchcount++;
                    my $visible = 'style="visibility:';
                    my $matchesdisplayed;
                    if ($action eq 'init' or $action eq 'new') {
                        $matchesdisplayed = $initialmatches;
                    }
                    else {
                        $matchesdisplayed = $remaining;
                    }
                    if ($matchcount <= $matchesdisplayed) {
                        $visible .= 'visible"';
                    }
                    else {
                        $visible .= 'hidden"';
                    }
                    $line =~ s/#visible#/$visible/;
                }
                elsif ($line =~ /#matches#/) {
                    if ($matches == 0) {
                        $line =~ s/#matches#//;
                    }
                    else {
                        $line =~ s/#matches#/$matches/;
                    }
                }
                elsif ($line =~ /#remaining#/) {
                    if ($remaining == 0 and $winner eq '') {
                        $line =~ s/#remaining#//;
                    }
                    else {
                        $line =~ s/#remaining#/$remaining/;
                    }
                }
                elsif ($line =~ /#player#/) {
                    if ($matchesplayer == 0) {
                        $line =~ s/#player#//;
                    }
                    else {
                        $line =~ s/#player#/$matchesplayer/;
                    }
                    if ($nextaction eq 'play') {
                        # During play, the input field must be accessible for user input
                        $line =~ s/#readonly#//; $line =~ s/#bold#/bold/;
                    }
                    else {
                        $line =~ s/#readonly#/readonly="readonly"/; $line =~ s/#bold#//;
                    }
                }
                elsif ($line =~ /#computer#/) {
                    if ($matchescomputer == 0) {
                        $line =~ s/#computer#//;
                    }
                    else {
                        $line =~ s/#computer#/$matchescomputer/;
                    }
                }
                elsif ($line =~ /#button#/) {
                    my $button = ucfirst($nextaction);
                    $line =~ s/#button#/$button/;
                }
                elsif ($line =~ /#hinitialmatches#/) {
                    $line =~ s/#hinitialmatches#/$initialmatches/;
                }
                elsif ($line =~ /#hlastmatch#/) {
                    $line =~ s/#hlastmatch#/$lastmatch/;
                }
                elsif ($line =~ /#hfirstmove#/) {
                    $line =~ s/#hfirstmove#/$firstmove/;
                }
                elsif ($line =~ /#hlevel#/) {
                    $line =~ s/#hlevel#/$level/;
                }
                elsif ($line =~ /#hmatches#/) {
                    $line =~ s/#hmatches#/$matches/;
                }
                elsif ($line =~ /#hremaining#/) {
                    $line =~ s/#hremaining#/$remaining/;
                }
                elsif ($line =~ /#hplayer#/) {
                    $line =~ s/#hplayer#/$matchesplayer/;
                }
                elsif ($line =~ /#js#/) {
                    # Add Javascript code to perform some action when the page is displayed
                    my $js;
                    if ($nextaction eq 'play' and $winner eq '') {
                        # Javascript to focus the 'number of matches taken by player' input field
                        $js = "<script>document.getElementById('player').focus();</script>";
                    }
                    elsif ($winner ne '') {
                        # Javascript to pop-up a message, telling who wins/looses the game
                        $js = "<script>alert('$winner');</script>";
                    }
                    else {
                        $js = '';
                    }
                    $line =~ s/#js#/$js/;
                }
            }
        }
        # Print file-line (with tags replaced) to webpage
        unless ($line =~ /#/) {
            print "$line\n";
        }
    }
}

#
# Do the computer's move (depending on actual last-match-rule and strength level)
#


sub ComputerMove {
    my ($matches, $last, $level) = @_;
    my $computer = int(rand(3) + 1);
    unless ($level == 1) { # level 1: all moves are random
        my $bestmove = 1;
        if ($level == 2) {
            if ($matches >= $initialmatches / 2) {
                # First half of game: Random move or not (1 out of 3); full strength during second half of game
                if (int(rand(3)) == 0) {
                    $bestmove = 0;
                }
            }
        }
        if ($bestmove) {
            # Check if there is a winning move. If yes, do it
            # The winning algorithm is simplistic:
            # If the last match wins, the number of matches left after the move must be a multiple of 4
            # If the last match looses, the number of matches left after the move must be a multiple of 4 plus 1

            my $modulo;
            if ($lastmatch eq 'winner') {
                $modulo = 0;
            }
            else {
                $modulo = 1;
            }
            for (my $i = 1; $i <= 3; $i++) {
                if (($matches - $i) % 4 == $modulo) {
                    # If taking 1, 2 or 3 matches results in a winning position, do this move (otherwise keep the random number of matches)
                    $computer = $i;
                }
            }
        }
    }
    if ($computer > $matches) {
        $computer = $matches; # cannot take more matches than there are left
    }
    return $computer;
}
4. User input validity check with Javascript.
 
The function checks the initial number of matches to be between 10 and 30 and, during the game, the number of matches taken by the player being 1, 2 or 3, without being greater than the number of matches actually available. If user input is valid, the function returns true and the Perl script is called. When writing the function, I encountered a problem, that can't occur in Perl, that has separate operators for numeric and string comparison. Comparing the number of matches taken and the number of matches left (without transformation) with if (taken > remaining) does a comparison, considering the values as strings and if, for example, taken = 3 and remaining = 15, the result of the if is true. My knowledge of Javascript is rudimentary and the only way, I found to make the function work correctly, is to add a leading zero for numbers less than 10 and doing a classic string compare. Help appreciated to change this to code, that a "real Javascript programmer" would use. Use the following link to show the Simple Nim Javascript code.
function checkinput () {
    var ok = false;
    if (document.getElementById) {
        // Check total number of matches (must be between 10 and 30)
        var matches = document.getElementById("initialmatches").value;
        if (matches.length == 1) {
            matches = '0' + matches;
        }
        var pattern = /^([0123456789]*)$/g; var res = pattern.test(matches);
        if (matches == "" || !res) {
            alert('The total number of matches is missing or invalid!');
        }
        else if (matches < '10' || matches > '30') {
            alert('The total number of matches must be between 10 and 30!');
        }
        else {
        ok = true;
        if (document.getElementById("action").value == 'Play') {
            // If the game has started, check the number of matches taken (must be 1, 2 or 3)
                ok = false;
                var taken = document.getElementById("player").value; var remaining = document.getElementById("remaining").value;
                if (taken.length == 1) {
                    taken = '0' + taken;
                }
                if (remaining.length == 1) {
                    remaining = '0' + remaining;
                }
                pattern = /^([0123456789]*)$/g; res = pattern.test(taken);
                if (taken == "" || !res) {
                    alert('The number of matches taken is missing or invalid!');
                }
                else if (taken < '01' || taken > '03') {
                    alert('You have to take 1, 2 or 3 matches!');
                }
                else if (taken > remaining) {
                    alert('You cannot take more matches than there are left!');
                }
                else {
                    ok = true;
                }
            }
        }
    }
    return ok;
}
Note: The call to the Javascript function checkinput() is implemented in the HTML file, calling the Perl script, if the function result is true (i.e. the template used by Perl to generate the game website) as follows: <form id="form1" name="form1" method="post" action="/cgi-bin/simplenim.pl" onSubmit="return(checkinput());">

If you find this page helpful or if you like the Simple Nim web application, please, support me and this website by signing my guestbook.