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.
|
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";
my $initialmatches; my $lastmatch; my $firstmove; my $level;
my $matches; my $remaining; my $matchesplayer; my $matchescomputer;
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';
}
$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);
my $winner = '';
if ($action eq 'start' or $action eq 'play') {
if ($action eq 'start') {
if ($firstmove eq 'computer') {
$matches = $remaining;
$matchescomputer = ComputerMove($remaining, $lastmatch, $level);
$remaining = $matches - $matchescomputer;
}
}
elsif ($action eq 'play') {
$matches = $remaining;
$remaining -= $matchesplayer;
if ($remaining == 0) {
if ($lastmatch eq 'winner') {
$winner = 'The player takes the last match and wins!';
}
else {
$winner = 'The player takes the last match and looses!';
}
}
else {
$matchescomputer = ComputerMove($remaining, $lastmatch, $level);
$remaining -= $matchescomputer;
if ($remaining == 0) {
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';
}
}
my $template = "$app_dir\\simplenim.template.html";
open(my $input, "<", $template)
or die "Can't open template file $template: $!";
my @lines = <$input>;
close($input);
my $matchcount = 0;
foreach my $line (@lines) {
chomp($line);
if ($line) {
if (substr($line, 0, 1) eq '#') {
$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#/) {
$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') {
$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#/) {
my $js;
if ($nextaction eq 'play' and $winner eq '') {
$js = "<script>document.getElementById('player').focus();</script>";
}
elsif ($winner ne '') {
$js = "<script>alert('$winner');</script>";
}
else {
$js = '';
}
$line =~ s/#js#/$js/;
}
}
}
unless ($line =~ /#/) {
print "$line\n";
}
}
}
sub ComputerMove {
my ($matches, $last, $level) = @_;
my $computer = int(rand(3) + 1);
unless ($level == 1) {
my $bestmove = 1;
if ($level == 2) {
if ($matches >= $initialmatches / 2) {
if (int(rand(3)) == 0) {
$bestmove = 0;
}
}
}
if ($bestmove) {
my $modulo;
if ($lastmatch eq 'winner') {
$modulo = 0;
}
else {
$modulo = 1;
}
for (my $i = 1; $i <= 3; $i++) {
if (($matches - $i) % 4 == $modulo) {
$computer = $i;
}
}
}
}
if ($computer > $matches) {
$computer = $matches;
}
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) {
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') {
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());">
|
|
|