15 Puzzle

During my childhood:
I used to play the 15 puzzle sliding game in a toy. The rear-side of the toy shows the jig-saw puzzle of a simple Tajmahal drawing. If we complete the 15 puzzle, the other side reveals the correctly aligned Tajmahal drawing.

In college days:
As a lab exercise, I had written a C program under Turbo C compiler using clrscr() of conio.h and direct video memory access to give colors and the extended character set to draw the borders. Unfortunately, I somehow missed the source code.

Just before two years:
I had written the same puzzle in HTML/JavaScript. JavaScript being an interpreted language that is readily available in any system, I wrote it quickly and/or easily.

Now:
Using Gtk2 Perl bindings, I have written another one.

Please see the codes below.

HTML/JavaScript:

<!DOCTYPE html>
<html>
<head><title>Sliding Puzzle</title>
<style type="text/css">
body{font-family:Arial;margin:7%;padding:0;text-align:center;}
h1{padding:5px; background-color:#aaf;border:dashed 3px #ddf;}
div#hdr{text-align:center}
div#lpane, div#rpane {float:left;width:50%;padding-top:2%;}
table#tbl {
  border:solid 0px maroon;
  background-color:#aaf;
  float:left;
  width:100%;
  text-align:center;
}
table#tbl tr{border:solid 0px maroon;}
table#tbl tr td{
  border:solid 1px maroon;
  background-color: #ddf;
  font-family: Arial;
  padding: 3px;
  margin: 3px;
  width:20.8%;
  text-align:center;
  font-size:2.8em;
}
table#track tr.success {color:#afa;}
</style>
<script type="text/javascript">
//Global Variables
var gamenum = 0;
var movenum = 0;
function handleKey(e){
    var t=document.getElementById("tbl").tBodies[0];
    if(e.keyCode == 0 && (e.charCode == 110 || e.charCode == 78)) {
      //'n' or 'N' is pressed
      newPuzzle();
      return;
    }
    if(e.keyCode >= 37 && e.keyCode <= 40)
    OUTER:
      for(i=0;i<=3;i++)
      for(j=0;j<=3;j++)//{alert(t.rows[i].cells[j].innerHTML);}return;
        if(t.rows[i].cells[j].innerHTML == "&nbsp;")
          switch(e.keyCode){
            case 37: if(j==3) return; //Left Arrow Key
                     var tmp = t.rows[i].cells[j+1].innerHTML;
                     t.rows[i].cells[j+1].innerHTML = t.rows[i].cells[j].innerHTML;
                     t.rows[i].cells[j].innerHTML = tmp;
                     movenum++;
                     break OUTER;
            case 38: if(i==3) return; //Upper Arrow Key
                     var tmp = t.rows[i+1].cells[j].innerHTML;
                     t.rows[i+1].cells[j].innerHTML = t.rows[i].cells[j].innerHTML;
                     t.rows[i].cells[j].innerHTML = tmp;
                     movenum++;
                     break OUTER;
            case 39: if(j==0) return; //Right Arrow Key
                     var tmp = t.rows[i].cells[j-1].innerHTML;
                     t.rows[i].cells[j-1].innerHTML = t.rows[i].cells[j].innerHTML;
                     t.rows[i].cells[j].innerHTML = tmp;
                     movenum++;
                     break OUTER;
            case 40: if(i==0) return; //Down Arrow Key
                     var tmp = t.rows[i-1].cells[j].innerHTML;
                     t.rows[i-1].cells[j].innerHTML = t.rows[i].cells[j].innerHTML;
                     t.rows[i].cells[j].innerHTML = tmp;
                     movenum++;
                     break OUTER;
          }
    document.getElementById("track").tBodies[0].rows[0].cells[3].innerHTML = movenum;
    checkCorrectness();
}
function checkCorrectness(){
  var t = document.getElementById("tbl");
  OUTER:
  for(var i=0;i<=3;i++)
  for(var j=0;j<=3;j++)
    if(t.rows[i].cells[j].innerHTML == i*4+j+1)
      ;
    else break OUTER;
  if(i*4+j+1==16){
    document.getElementById("track").tBodies[0].rows[0].className="success";
    alert("Very Good!\nNew Game Starts ...");
    newPuzzle();
  }
}
function newPuzzle(){
  gamenum++;
  movenum=0;
  var numbag=Array();
  var t = document.getElementById("tbl");
  for(var i=0; i<=3; ++i){
    for(var j=0; j<=3; ++j){
      if((i+1)*(j+1)==16)break;
      while(1){
        var num = Math.round(Math.random()*20);
        if(num >=1 && num <= 15 && numbag[""+num]==null){
          numbag[""+num]=(i+1)*(j+1);
          t.rows[i].cells[j].innerHTML = num;
          break;
        }
      }
      //t.rows[i].cells[j].innerHTML = i*4+j+1;
    }
  }
  t.rows[3].cells[3].innerHTML = "&nbsp;";
  var trk = document.getElementById("track").tBodies[0];
  var tr = trk.insertRow(0);
  var td = tr.insertCell(0); td.innerHTML = "Game";
      td = tr.insertCell(1); td.innerHTML = gamenum;
      td = tr.insertCell(2); td.innerHTML = "Moves";
      td = tr.insertCell(3); td.innerHTML = movenum;
}
</script>
</head>
<body onkeydown="handleKey(event);" onload="newPuzzle();">
<div id="hdr"><h1>15-Puzzle</h1>
Use 'N' to start a new game.<br />
Use arrow keys to move the numbers into the empty box.
</div>
<div id="bdy">
<div id="lpane">
<script type="text/javascript">
document.write('<table id="tbl"><tbody>');
for(var i=0; i<=3; ++i){
  document.write("<tr>");
  for(var j=0; j<=3; ++j){
    document.write("<td>&nbsp;</td>");
  }
  document.write("</tr>");
}
document.write("</tbody></table>");
</script>
</div><div id="rpane">
<table id="track">
<tbody></tbody>
</table>
</div></div>
</body>
</html>

Perl:

#!/usr/bin/env perl
use strict;
use warnings;
use List::Util qw(shuffle);
use List::MoreUtils qw(any all apply);
use Gtk2 -init;
use Glib qw/TRUE FALSE/;

sub cust_window {
	my $window = Gtk2::Window->new ('toplevel');
	$window->set_title('15 Puzzle');
	$window->signal_connect (delete_event => sub { Gtk2->main_quit });
	return $window;
}

sub cust_btn {
	my $btn = Gtk2::Button->new(shift);
	$btn->set_can_focus(FALSE);
	$btn->set_size_request(100, 100);
	return $btn;
}

sub gen_btn_arr {
	map {
		my $r = $_;
		my @btns = map {
			my $c = $_;
			$r * $c == 16 ? undef : cust_btn(($r - 1) * 4 + $c);
		} 1 .. 4;
		\@btns;
	} 1 .. 4
}

sub randomize_btn_label {
	my $btn_arr = shift;
	my @labels  = @_;
	apply {
		my $r = $_;
		apply {
			my $c = $_;
			$btn_arr->[$r - 1][$c - 1]->set_label( $r * $c == 16 ? ' ' : $labels[($r - 1) * 4 + $c - 1]);
		} 1 .. 4;
	} 1 .. 4
}

sub is_complete {
	my $btn_arr = shift;
	all {
		my $r = $_;
		all {
			my $c = $_;
			$btn_arr->[$r - 1][$c - 1]->get_label() eq ($r * $c == 16 ? ' ' : ($r - 1) * 4 + $c);
		} 1 .. 4;
	} 1 .. 4
}

sub cursor_btn {
	my $btn = Gtk2::Button->new(' ');
	return $btn;
}

sub attach_btns {
	my $tbl = shift;
	my $btn_arr = shift;
	apply {
		my $r = $_;
		apply {
			my $c = $_;
			$tbl->attach($btn_arr->[$r - 1][$c - 1], $c - 1, $c, $r - 1, $r, 'fill', 'fill', 0, 0);
		} 1 .. 4
	} 1 .. 4
}

sub swpbtns {
	my ($btn_arr, $oldcurr, $oldcurc, $keypressed) = @_;
	my ($newcurr, $newcurc) = $keypressed eq 'Up'    ? ($oldcurr - 1, $oldcurc) :
							  $keypressed eq 'Down'  ? ($oldcurr + 1, $oldcurc) :
							  $keypressed eq 'Left'  ? ($oldcurr, $oldcurc - 1) :
							  $keypressed eq 'Right' ? ($oldcurr, $oldcurc + 1) : ();
	my ($t1, $t2) = ($btn_arr->[$oldcurr][$oldcurc]->get_label(), $btn_arr->[$newcurr][$newcurc]->get_label());
	$btn_arr->[$oldcurr][$oldcurc]->set_can_focus(FALSE);
	$btn_arr->[$newcurr][$newcurc]->set_can_focus(TRUE);
	$btn_arr->[$newcurr][$newcurc]->grab_focus();
	$btn_arr->[$newcurr][$newcurc]->set_label($t1);
	$btn_arr->[$oldcurr][$oldcurc]->set_label($t2);
	return ($newcurr, $newcurc);
}

sub main {
	my $win = cust_window();
	my $tbl = Gtk2::Table->new(4, 4, TRUE);
	my @btn_arr = gen_btn_arr();
	my $dialog = undef;
	my ($cur_r, $cur_c) = (3, 3);
	my $moves = 0;

	$btn_arr[3][3] = cursor_btn();
	attach_btns($tbl, \@btn_arr);

	$win->add($tbl);
	$win->signal_connect('key-press-event' => sub {
				my ($widget, $event, $parameter) = @_;
				my $key_nr = $event->keyval();
				if($key_nr == 110) {
					randomize_btn_label(\@btn_arr, shuffle 1 .. 15);
				}
				if($key_nr >= 65361 and $key_nr <= 65364) { 					my %key_human = (65361 => 'Left', 65362 => 'Up', 65363 => 'Right', 65364 => 'Down');
					return undef if(($key_nr == 65361 and $cur_c == 0)
								 or ($key_nr == 65362 and $cur_r == 0)
								 or ($key_nr == 65363 and $cur_c == 3)
								 or ($key_nr == 65364 and $cur_r == 3));
					($cur_r, $cur_c) = swpbtns(\@btn_arr, $cur_r, $cur_c, $key_human{$key_nr});
					$moves++;
					if (is_complete(\@btn_arr)) {
						$dialog = Gtk2::MessageDialog->new($win, 'modal', 'info', 'ok',
												"Completed in %s moves.\nNew game starts ...", $moves);
						$dialog->run(); $dialog->destroy();
						$moves = 0 ;
						randomize_btn_label(\@btn_arr, shuffle 1 .. 15);
					}
				}});
	$win->show_all();
	randomize_btn_label(\@btn_arr, 1 .. 15);
	Gtk2->main();
}

main();

– How about writing the same perl program in Prima GUI (or) wxPerl (or) Tcl/Tk (or) Qt libraries?
– How about writing a program that solves the puzzle instead of just simulating the toy game?

Happy Programming!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s