15 puzzle game: Difference between revisions

From Rosetta Code
Content added Content deleted
m (added whitespace before a link.)
Line 3: Line 3:
[[File:15_puzzle.png|270px|thumb|right]]
[[File:15_puzzle.png|270px|thumb|right]]


Implement the [[wp:15_puzzle|Fifteen Puzzle Game]].
Implement the   [[wp:15_puzzle|Fifteen Puzzle Game]].


<br><br>
<br><br>

Revision as of 22:22, 11 April 2016

15 puzzle game is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Implement the   Fifteen Puzzle Game.



C++

<lang cpp>

  1. include <time.h>
  2. include <vector>
  3. include <string>
  4. include <iostream>

class p15 { public :

   void play() {
       bool p = true;
       std::string a;
       while( p ) {
           createBrd();
           while( !isDone() ) { drawBrd();getMove(); }
           drawBrd();
           std::cout << "\n\nCongratulations!\nPlay again (Y/N)?";
           std::cin >> a; if( a != "Y" && a != "y" ) break;
       }
   }

private:

   void createBrd() {
       int i = 1; std::vector<int> v;
       for( ; i < 16; i++ ) { brd[i - 1] = i; }
       brd[15] = 0; x = y = 3;
       for( i = 0; i < 1000; i++ ) {
           getCandidates( v );
           move( v[rand() % v.size()] );
           v.clear();
       }
   }
   void move( int d ) {
       int t = x + y * 4;
       switch( d ) {
           case 1: y--; break;
           case 2: x++; break;
           case 4: y++; break;
           case 8: x--;
       }
       brd[t] = brd[x + y * 4];
       brd[x + y * 4] = 0;
   }
   void getCandidates( std::vector<int>& v ) {
       if( x < 3 ) v.push_back( 2 ); if( x > 0 ) v.push_back( 8 );
       if( y < 3 ) v.push_back( 4 ); if( y > 0 ) v.push_back( 1 );
   }
   void drawBrd() {
       int r; std::cout << "\n\n";
       for( int y = 0; y < 4; y++ ) {
           std::cout << "+----+----+----+----+\n";
           for( int x = 0; x < 4; x++ ) {
               r = brd[x + y * 4];
               std::cout << "| ";
               if( r < 10 ) std::cout << " ";
               if( !r ) std::cout << "  ";
               else std::cout << r << " ";
           }
           std::cout << "|\n";
       }
       std::cout << "+----+----+----+----+\n";
   }
   void getMove() {
       std::vector<int> v; getCandidates( v );
       std::vector<int> p; getTiles( p, v ); unsigned int i;
       while( true ) {
           std::cout << "\nPossible moves: ";
           for( i = 0; i < p.size(); i++ ) std::cout << p[i] << " ";
           int z; std::cin >> z;
           for( i = 0; i < p.size(); i++ )
               if( z == p[i] ) { move( v[i] ); return; }
       }
   }
   void getTiles( std::vector<int>& p, std::vector<int>& v ) {
       for( unsigned int t = 0; t < v.size(); t++ ) {
           int xx = x, yy = y;
           switch( v[t] ) {
               case 1: yy--; break;
               case 2: xx++; break;
               case 4: yy++; break;
               case 8: xx--;
           }
           p.push_back( brd[xx + yy * 4] );
       }
   }
   bool isDone() {
       for( int i = 0; i < 15; i++ ) {
           if( brd[i] != i + 1 ) return false;
       }
       return true;
   }
   int brd[16], x, y;

}; int main( int argc, char* argv[] ) {

   srand( ( unsigned )time( 0 ) );
   p15 p; p.play(); return 0;

} </lang>

+----+----+----+----+
| 11 |  5 | 12 |  3 |
+----+----+----+----+
| 10 |  7 |  6 |  4 |
+----+----+----+----+
| 13 |    |  2 |  1 |
+----+----+----+----+
| 15 | 14 |  8 |  9 |
+----+----+----+----+

Possible moves: 2 13 14 7

J

Implementation:

<lang J>require'general/misc/prompt'

genboard=:3 :0

 b=. ?~16
 if. 0<C.!.2 b do.
   b=. (<0 _1)C. b
 end.
 a: (b i.0)} <"0 b

)

done=: (<"0]1+i.15),a:

shift=: |.!._"0 2 taxi=: |:,/"2(_1 1 shift i.4 4),_1 1 shift"0 1/ i.4 4

showboard=:3 :0

 echo 'current board:'
 echo 4 4$y

)

help=:0 :0

 Slide a number block into the empty space
 until you get:

┌──┬──┬──┬──┐ │1 │2 │3 │4 │ ├──┼──┼──┼──┤ │5 │6 │7 │8 │ ├──┼──┼──┼──┤ │9 │10│11│12│ ├──┼──┼──┼──┤ │13│14│15│ │ └──┴──┴──┴──┘

 Or type 'q' to quit.

)

getmove=:3 :0

 showboard y
 blank=. y i. a:
 options=. /:~ ;y {~ _ -.~ blank { taxi
 whilst. -. n e. options do.
   echo 'Valid moves: ',":options
   t=. prompt 'move which number? '
   if. 'q' e. t do.
     echo 'giving up'
     throw.
   elseif. 'h' e. t do.
     echo help
     showboard y
   end.
   n=. {._".t
 end.
 (<blank,y i.<n) C. y

)

game=: 3 :0

 echo '15 puzzle'
 echo 'h for help, q to quit'
 board=. genboard
 whilst. -. done-:board do.
   board=. getmove board
 end.
 showboard board
 echo 'You win.'

)</lang>

Most of this is user interface code. We initially shuffle the numbers randomly, then check their parity and swap the first and last squares if needed. Then, for each move, we allow the user to pick one of the taxicab neighbors of the empty square.

A full game would be too big to be worth showing here, so for the purpose of giving a glimpse of what this looks like in action we replace the random number generator with a constant:

<lang J> game 15 puzzle h for help, q to quit current board: ┌──┬──┬──┬──┐ │1 │2 │3 │4 │ ├──┼──┼──┼──┤ │5 │6 │7 │8 │ ├──┼──┼──┼──┤ │9 │10│ │11│ ├──┼──┼──┼──┤ │13│14│15│12│ └──┴──┴──┴──┘ Valid moves: 7 10 11 15 move which number? 11 current board: ┌──┬──┬──┬──┐ │1 │2 │3 │4 │ ├──┼──┼──┼──┤ │5 │6 │7 │8 │ ├──┼──┼──┼──┤ │9 │10│11│ │ ├──┼──┼──┼──┤ │13│14│15│12│ └──┴──┴──┴──┘ Valid moves: 8 11 12 move which number? 12 current board: ┌──┬──┬──┬──┐ │1 │2 │3 │4 │ ├──┼──┼──┼──┤ │5 │6 │7 │8 │ ├──┼──┼──┼──┤ │9 │10│11│12│ ├──┼──┼──┼──┤ │13│14│15│ │ └──┴──┴──┴──┘ You win.</lang>

Java

Works with: Java version 8

<lang java>import java.awt.*; import java.awt.event.*; import java.util.Random; import javax.swing.*;

public class FifteenPuzzle extends JPanel {

   final static int numTiles = 15;
   final static int side = 4;
   Random rand = new Random();
   int[] tiles = new int[numTiles + 1];
   int tileSize, blankPos, margin, gridSize;
   public FifteenPuzzle() {
       final int dim = 640;
       margin = 80;
       tileSize = (dim - 2 * margin) / side;
       gridSize = tileSize * side;
       setPreferredSize(new Dimension(dim, dim));
       setBackground(Color.white);
       setForeground(new Color(0x6495ED)); // cornflowerblue 
       setFont(new Font("SansSerif", Font.BOLD, 60));
       addMouseListener(new MouseAdapter() {
           @Override
           public void mousePressed(MouseEvent e) {
               int ex = e.getX() - margin;
               int ey = e.getY() - margin;
               if (ex < 0 || ex > gridSize || ey < 0 || ey > gridSize)
                   return;
               int c1 = ex / tileSize;
               int r1 = ey / tileSize;
               int c2 = blankPos % side;
               int r2 = blankPos / side;
               if ((c1 == c2 && Math.abs(r1 - r2) == 1)
                       || (r1 == r2 && Math.abs(c1 - c2) == 1)) {
                   int clickPos = r1 * side + c1;
                   tiles[blankPos] = tiles[clickPos];
                   tiles[clickPos] = 0;
                   blankPos = clickPos;
               }
               repaint();
           }
       });
       shuffle();
   }
   final void shuffle() {
       do {
           reset();
           // don't include the blank space in the shuffle, leave it
           // in the home position
           int n = numTiles;
           while (n > 1) {
               int r = rand.nextInt(n--);
               int tmp = tiles[r];
               tiles[r] = tiles[n];
               tiles[n] = tmp;
           }
       } while (!isSolvable());
   }
   void reset() {
       for (int i = 0; i < tiles.length; i++)
           tiles[i] = (i + 1) % tiles.length;
       blankPos = numTiles;
   }
   /*  Only half the permutations of the puzzle are solvable.
       Whenever a tile is preceded by a tile with higher value it counts
       as an inversion. In our case, with the blank space in the home
       position, the number of inversions must be even for the puzzle
       to be solvable.
       See also:
       www.cs.bham.ac.uk/~mdr/teaching/modules04/java2/TilesSolvability.html
   */
   boolean isSolvable() {
       int countInversions = 0;
       for (int i = 0; i < numTiles; i++) {
           for (int j = 0; j < i; j++) {
               if (tiles[j] > tiles[i])
                   countInversions++;
           }
       }
       return countInversions % 2 == 0;
   }
   void drawGrid(Graphics2D g) {
       for (int i = 0; i < tiles.length; i++) {
           if (tiles[i] == 0)
               continue;
           int r = i / side;
           int c = i % side;
           int x = margin + c * tileSize;
           int y = margin + r * tileSize;
           g.setColor(getForeground());
           g.fillRoundRect(x, y, tileSize, tileSize, 25, 25);
           g.setColor(Color.black);
           g.drawRoundRect(x, y, tileSize, tileSize, 25, 25);
           g.setColor(Color.white);
           drawCenteredString(g, String.valueOf(tiles[i]), x, y);
       }
   }
   void drawCenteredString(Graphics2D g, String s, int x, int y) {
       FontMetrics fm = g.getFontMetrics();
       int asc = fm.getAscent();
       int dec = fm.getDescent();
       x = x + (tileSize - fm.stringWidth(s)) / 2;
       y = y + (asc + (tileSize - (asc + dec)) / 2);
       g.drawString(s, x, y);
   }
   @Override
   public void paintComponent(Graphics gg) {
       super.paintComponent(gg);
       Graphics2D g = (Graphics2D) gg;
       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
               RenderingHints.VALUE_ANTIALIAS_ON);
       drawGrid(g);
   }
   public static void main(String[] args) {
       SwingUtilities.invokeLater(() -> {
           JFrame f = new JFrame();
           f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           f.setTitle("Fifteen Puzzle");
           f.setResizable(false);
           f.add(new FifteenPuzzle(), BorderLayout.CENTER);
           f.pack();
           f.setLocationRelativeTo(null);
           f.setVisible(true);
       });
   }

}</lang>

Perl 6

Works with: Rakudo version 2016-01

Most of this is interface code. Reused substantial portions from the 2048 task. Use the arrow keys to slide tiles, press 'q' to quit or 'n' for a new puzzle. Requires a POSIX termios aware terminal. Ensures that the puzzle is solvable by shuffling the board with an even number of swaps, then checking for even taxicab parity for the empty space. <lang perl6>use Term::termios;

constant $saved = Term::termios.new(fd => 1).getattr; constant $termios = Term::termios.new(fd => 1).getattr;

  1. raw mode interferes with carriage returns, so
  2. set flags needed to emulate it manually

$termios.unset_iflags(<BRKINT ICRNL ISTRIP IXON>); $termios.unset_lflags(< ECHO ICANON IEXTEN ISIG>); $termios.setattr(:DRAIN);

  1. reset terminal to original setting on exit

END { $saved.setattr(:NOW) }

constant n = 4; # board size constant cell = 6; # cell width

constant $top = join '─' x cell, '┌', '┬' xx n-1, '┐'; constant $mid = join '─' x cell, '├', '┼' xx n-1, '┤'; constant $bot = join '─' x cell, '└', '┴' xx n-1, '┘';

my %dir = (

  "\e[A" => 'up',
  "\e[B" => 'down',
  "\e[C" => 'right',
  "\e[D" => 'left',

);

my @solved = [1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,' ']; my @board; new();

sub new () {

   loop {
       @board = shuffle();
       last if parity-ok(@board);
   }

}

sub shuffle () {

   my @c = [1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,' '];
   for (^16).pick(*) -> $y, $x {
       my ($yd, $ym, $xd, $xm) = ($y div n, $y mod n, $x div n, $x mod n);
       my $temp    = @c[$ym;$yd];
       @c[$ym;$yd] = @c[$xm;$xd];
       @c[$xm;$xd] = $temp;
   }
   @c;

}

sub parity-ok (@b) {

   so (sum @b».grep(/' '/,:k).grep(/\d/, :kv)) %% 2;

}

sub row (@row) { '│' ~ (join '│', @row».&center) ~ '│' }

sub center ($s){

   my $c   = cell - $s.chars;
   my $pad = ' ' x ceiling($c/2);
   sprintf "%{cell}s", "$s$pad";

}

sub draw-board {

   run('clear');
   print qq:to/END/;


Press direction arrows to move.

Press q to quit. Press n for a new puzzle.

$top { join "\n\t$mid\n\t", map { .&row }, @board } $bot

{ (so @board ~~ @solved) ?? 'Solved!!' !! } END }

sub slide (@c is copy) {

   my $t = (grep { /' '/ }, :k, @c)[0];
   return @c unless $t and $t > 0;
   @c[$t,$t-1] = @c[$t-1,$t];
   @c;

}

multi sub move('up') {

   map { @board[*;$_] = reverse slide reverse @board[*;$_] }, ^n;

}

multi sub move('down') {

   map { @board[*;$_] = slide @board[*;$_] }, ^n;

}

multi sub move('left') {

   map { @board[$_] = reverse slide reverse @board[$_] }, ^n;

}

multi sub move('right') {

   map { @board[$_] = slide @board[$_] }, ^n;

}

loop {

   draw-board;
   # Read up to 4 bytes from keyboard buffer.
   # Page navigation keys are 3-4 bytes each.
   # Specifically, arrow keys are 3.
   my $key = $*IN.read(4).decode;
   move %dir{$key} if so %dir{$key};
   last if $key eq 'q'; # (q)uit
   new() if $key eq 'n';

}</lang> Sample screen shot:

	Press direction arrows to move.

	Press q to quit. Press n for a new puzzle.

	┌──────┬──────┬──────┬──────┐
	│  2   │  1   │  10  │  14  │
	├──────┼──────┼──────┼──────┤
	│  15  │  11  │  12  │      │
	├──────┼──────┼──────┼──────┤
	│  13  │  3   │  6   │  7   │
	├──────┼──────┼──────┼──────┤
	│  9   │  4   │  5   │  8   │
	└──────┴──────┴──────┴──────┘

Racket

This is a GUI game; and there are difficulties getting screen shots onto RC. Use the arrow keys to slide the blank square.

It uses the 2htdp/universe package.

<lang racket>#lang racket/base (require 2htdp/universe 2htdp/image racket/list racket/match)

(define ((fifteen->pict (finished? #f)) fifteen)

 (for/fold ((i (empty-scene 0 0))) ((r 4))
   (define row
     (for/fold ((i (empty-scene 0 0))) ((c 4))
       (define v (list-ref fifteen (+ (* r 4) c)))
       (define cell
         (if v
             (overlay/align
              "center" "center"
              (rectangle 50 50 'outline (if finished? "white" "blue"))
              (text (number->string v) 30 "black"))
             (rectangle 50 50 'solid (if finished? "white" "powderblue"))))
       (beside i cell)))
   (above i row)))

(define (move-space fifteen direction)

 (define idx (for/first ((i (in-naturals)) (x fifteen) #:unless x) i))
 (define-values (row col) (quotient/remainder idx 4))
 (define dest (+ idx (match direction
                       ['l #:when (> col 0) -1]
                       ['r #:when (< col 3)  1]
                       ['u #:when (> row 0) -4]
                       ['d #:when (< row 3)  4]
                       [else 0])))
   (list-set (list-set fifteen idx (list-ref fifteen dest)) dest #f))

(define (key-move-space fifteen a-key)

 (cond [(key=? a-key "left") (move-space fifteen 'l)]
       [(key=? a-key "right") (move-space fifteen 'r)]
       [(key=? a-key "up") (move-space fifteen 'u)]
       [(key=? a-key "down") (move-space fifteen 'd)]
       [else fifteen]))

(define (shuffle-15 fifteen shuffles)

 (for/fold ((rv fifteen)) ((_ shuffles))
   (move-space rv (list-ref '(u d l r) (random 4)))))

(define fifteen0 '(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #f))

(define (solved-world? w) (equal? w fifteen0))

(big-bang (shuffle-15 fifteen0 200)

         (name "Fifteen")
         (to-draw (fifteen->pict))
         (stop-when solved-world? (fifteen->pict #t))
         (on-key key-move-space))</lang>

REXX

This REXX version allows the user to specify the size of the puzzle   (N,   where   NxN   is the size of the puzzle).

With some more complexity, the REXX computer program could be changed to allow multiple-tile moves   (so that, for instance, three tiles could be slide to the right). <lang rexx>/*REXX pgm implements the 15-puzzle (Gem Puzzle, Boss Puzzle, Mystic Square).*/ parse arg N seed . /*obtain optional arguments from the CL*/ if N== | N==',' then N=4 /*Not specified? Then use the default.*/ if datatype(seed,'W') then call random ,,seed /*use a RANDOM seed if given.*/ sep='────────'; prompt=sep 'Please enter a tile number ' sep " (or Quit)." pad=translate(sep,,'─')" "; nn=N*N-1; nh=N*N; w=length(nn); $=; @.=

      do i=1  for nn;  $=$ i;   end   /* [◄]  build a solution for testing.  */

done=$ /* [↓] scramble the tiles in puzzle. */

      do j=1  for nn;  a=random(1,words($));  @.j=word($,a);   $=delword($,a,1)
      end   /*j*/
  do  until puzz=done  &  @.nh==    /*perform moves until puzzle is solved.*/
  x=0;   call showgrid 1;    say;  say prompt;     pull x _ .
  if abbrev('QUIT',x,1)  then do;  say;  say;  say sep 'quitting.';  exit;  end
      select
      when x ==            then em= "nothing entered"
      when _\==            then em= "too many items entered"
      when \datatype(x,'N')  then em= "number isn't numeric"
      when \datatype(x,'W')  then em= "number isn't an integer"
      when x=0               then em= "number can't be zero"
      when x<0               then em= "number can't be negative"
      when x>nn              then em= "number can't be greater then"   nn
      otherwise                   em=
      end   /*select*/                /* [↑]  verify the human entered data. */
  if em\==  then do; say sep em'.'; iterate; end  /*possible error message?*/
  call showgrid 0;   g=                             /*validate if move is OK.*/
  rm=holeRow-1;  rp=holeRow+1;  cm=holeCol-1;  cp=holeCol+1  /*possible move.*/
  g=!.rm.holeCol  !.rp.holeCol  !.holeRow.cm  !.holeRow.cp   /*legal moves.  */
  if wordpos(x,g)==0  then do; say sep 'tile' x "can't be moved."; iterate; end
  @.hole=x; @.tile=; call showgrid 0  /*move the tile to the hole in puzzle. */
  end   /*until*/

call showgrid 1; say; say sep 'Congratulations! The' nn"-puzzle is solved." exit /*stick a fork in it, we're all done. */ /*────────────────────────────────────────────────────────────────────────────*/ showgrid: parse arg show;  !.=; #=0; x=x/1; puzz=

         top='╔'copies(copies("═",w)'╦',N);    top=left(top,length(top)-1)"╗"
         bar='╠'copies(copies("═",w)'╬',N);    bar=left(bar,length(bar)-1)"╣"
         bot='╚'copies(copies("═",w)'╩',N);    bot=left(bot,length(bot)-1)"╝"
         if show  then say pad top
              do   r=1  for N;      z='║'
                do c=1  for N;      #=#+1;     y=@.#;    puzz=puzz y;   !.r.c=y
                _=right(@.#,w)"║";             z=z || _    /* [↓]  find hole.*/
                if @.#==  then do;  hole=#;  holeRow=r;   holeCol=c;    end
                if @.#==x   then do;  tile=#;  tileRow=r;   tileCol=c;    end
                end   /*c*/                                /* [↑]  find  X.  */
              if show  then do;  say pad z;  if r\==N  then say pad bar;  end
              end     /*r*/
         if show  then say pad bot
         return</lang>

output   when using the default input:

           ╔══╦══╦══╦══╗
           ║10║ 7║ 8║11║
           ╠══╬══╬══╬══╣
           ║ 4║ 3║15║ 1║
           ╠══╬══╬══╬══╣
           ║ 9║12║ 2║13║
           ╠══╬══╬══╬══╣
           ║14║ 5║ 6║  ║
           ╚══╩══╩══╩══╝

──────── Please enter a tile number  ────────  (or Quit).
13
           ╔══╦══╦══╦══╗
           ║10║ 7║ 8║11║
           ╠══╬══╬══╬══╣
           ║ 4║ 3║15║ 1║
           ╠══╬══╬══╬══╣
           ║ 9║12║ 2║  ║
           ╠══╬══╬══╬══╣
           ║14║ 5║ 6║13║
           ╚══╩══╩══╩══╝

──────── Please enter a tile number  ────────  (or Quit).
1
           ╔══╦══╦══╦══╗
           ║10║ 7║ 8║11║
           ╠══╬══╬══╬══╣
           ║ 4║ 3║15║  ║
           ╠══╬══╬══╬══╣
           ║ 9║12║ 2║ 1║
           ╠══╬══╬══╬══╣
           ║14║ 5║ 6║13║
           ╚══╩══╩══╩══╝

──────── Please enter a tile number  ────────  (or Quit).
15
           ╔══╦══╦══╦══╗
           ║10║ 7║ 8║11║
           ╠══╬══╬══╬══╣
           ║ 4║ 3║  ║15║
           ╠══╬══╬══╬══╣
           ║ 9║12║ 2║ 1║
           ╠══╬══╬══╬══╣
           ║14║ 5║ 6║13║
           ╚══╩══╩══╩══╝

──────── Please enter a tile number  ────────  (or Quit).
quit


──────── quitting.

Ring

CalmoSoft Fifteen Puzzle Game written in Ring Programming Language (http://ring-lang.net)

Output: [video]

The code: <lang ring>load "guilib.ring"

App1 = new qApp {

       rnd = []  
       empty = 16        
       win1 = new qWidget() {
                  move(0,0)
                  resize(350,400)
                  setWindowTitle("CalmoSoft Fifteen Puzzle Game")
                  
               new qPushButton(win1)
               {
                      setgeometry(100,220,120,30)
                      settext("Scramble")
                      setclickevent("scramble()")                        
               }
                btn1 = new qPushButton(win1)
               {
                           setgeometry(100,100,30,30)
                           setclickevent("moveTile(1)")                   
               }                
               btn2 = new qPushButton(win1)
               {
                          setgeometry(130,100,30,30)
                          setclickevent("moveTile(2)")                      
               } 
               btn3 = new qPushButton(win1)
               {
                          setgeometry(160,100,30,30)
                          setclickevent("moveTile(3)")                        
               }

               btn4 = new qPushButton(win1)
               {
                          setgeometry(190,100,30,30)
                          setclickevent("moveTile(4)")
               }
               btn5 = new qPushButton(win1)
               {
                          setgeometry(100,130,30,30)
                          setclickevent("moveTile(5)")
               }
   
               btn6 = new qPushButton(win1)
               {
                          setgeometry(130,130,30,30)
                          setclickevent("moveTile(6)")
               }
               btn7 = new qPushButton(win1)
               {
                          setgeometry(160,130,30,30)
                          setclickevent("moveTile(7)")
               }
               btn8 = new qPushButton(win1)
               {
                          setgeometry(190,130,30,30)
                          setclickevent("moveTile(8)")
               }
               btn9 = new qPushButton(win1)   
               {
                          setgeometry(100,160,30,30)
                          setclickevent("moveTile(9)")
               }
               btn10 = new qPushButton(win1)   
               {
                            setgeometry(130,160,30,30)
                            setclickevent("moveTile(10)")
               }
               btn11 = new qPushButton(win1)   
               {
                            setgeometry(160,160,30,30)
                            setclickevent("moveTile(11)")
               }
               btn12 = new qPushButton(win1)   
               {
                            setgeometry(190,160,30,30)
                            setclickevent("moveTile(12)")
               }
               btn13 = new qPushButton(win1)   
               {
                            setgeometry(100,190,30,30)
                            setclickevent("moveTile(13)")
               }
               btn14 = new qPushButton(win1)   
               {
                            setgeometry(130,190,30,30)
                            setclickevent("moveTile(14)")
               }
               btn15 = new qPushButton(win1)   
               {
                            setgeometry(160,190,30,30)
                            setclickevent("moveTile(15)")
               }
               btn16 = new qPushButton(win1)   
               {
                            setgeometry(190,190,30,30)
                            settext("")
                            setclickevent("moveTile(16)")
               }
               resetbtn = new qPushButton(win1)   
               {
                               setgeometry(100,250,120,30)
                               settext("Reset")
                               setclickevent("resetTiles()")
               }
               button = [btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn10, btn11, btn12, btn13, btn14, btn15, btn16]
               for i = 1 to 15
                    button[i] {settext(string(i))}
               next
               show()
       }
       exec()

}

func scramble

      for n= 1 to 300   
           nr=random(16)
           up = (empty = (nr - 4))
           down = (empty = (nr + 4))
           left = ((empty = (nr - 1)) and ((nr % 4) != 1))
           right = ((empty = (nr + 1)) and ((nr % 4) != 0))
           move = up or down or left  or right
           if move = 1 and (nr != 0)
              button[nr] { temp = text() }
              button[empty]  {settext(temp)}
              button[nr]  {settext("")}
              empty = nr
           ok
      next

func moveTile nr2

      up = (empty = (nr2 - 4))
      down = (empty = (nr2 + 4))
      left = ((empty = (nr2- 1)) and ((nr2 % 4) != 1))
      right = ((empty = (nr2 + 1)) and ((nr2 % 4) != 0))
      move = up or down or left  or right
      if move = 1    
         button[nr2] { temp2 = text() }
         button[empty]  {settext(temp2)}
         button[nr2]  {settext("")}
         empty = nr2
      ok

func resetTiles

      empty = 16
      for i = 1 to 15
           button[i] {settext(string(i))}
      next
      button[16] {settext("")}</lang>

Tcl

Library: Tk

Works with Tcl/Tk 8.5

This program uses Tk, the graphical counterpart to Tcl. The tiles are made of a grid of buttons, and the text on the buttons is moved around.

The button "New game" selects one of the canned puzzles. The window-title is used to show messages.

<lang tcl> # 15puzzle_21.tcl - HaJo Gurt - 2016-02-16

# http://wiki.tcl.tk/14403
#: 15-Puzzle - with grid, buttons and colors
 package require Tk
 set progVersion "15-Puzzle v0.21";        # 2016-02-20
 global Msg Moves PuzzNr GoalNr
 set Msg    " "
 set Moves  -1
 set PuzzNr  0
 set GoalNr  0
 set Keys   { 11 12 13 14  21 22 23 24  31 32 33 34  41 42 43 44 }
 set Puzz_T {  T  h  e  F   i  f  t  e   e  n  P  u   z  z  l  e }; # Title
 set Goal_T {  x  x  x  F   i  f  t  e   e  n  x  x   x  x  x  x }; # Title-highlight
 set Puzz_0 {  E  G  P  N   C  A  F  B   D  L  H  I   O  K  M  _ }; # -  / 116
 set Puzz_1 {  C  A  F  B   E  G  P  N   D  L  H  I   O  K  M  _ }; # E  / 156 from Tk-demo
 set Puzz_2 {  E  O  N  K   M  I  _  G   B  H  L  P   C  F  A  D }; # L  / 139
 set Puzz_3 {  P  G  M  _   E  L  N  D   O  K  H  I   B  C  F  A }; # EK / 146
 set Goal_0 {  A  B  C  D   E  F  G  H   I  K  L  M   N  O  P  _ }; # Rows LTR   / 1:E : 108
 set Goal_1 {  A  E  I  N   B  F  K  O   C  G  L  P   D  H  M  _ }; # Cols forw. / 1:M : 114
 set Puzz $Puzz_T
 set Goal $Goal_T
  1. ---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+---
 proc Move {k} {
 # find the key with the empty tile:
   set e -1
   foreach p $::Keys  {
     set t [.key$p cget -text]
     if { $t eq "_" } { set e $p }
   }
   if {$e  <  0} {return 0};   # no key with empty tile found
   if {$k == $e} {return 0};   # click was on the empty tile
   set t [.key$k cget -text]
   .key$e config -text $t
   .key$k config -text "_";  
   return 1
 }
 proc Check {} {
   set ok 0
   set i  0
   foreach k $::Keys {
     set t [.key$k cget -text]
     set g [lindex $::Goal $i]
     incr i
     .key$k config -background white
     if { $t eq $g  } { .key$k config -background lightgreen; incr ok }
     if { $t eq "_" } { .key$k config -background gray }
   }
   # Solved:
   update
   if { $ok > 15 && $::Moves > 0} {
     foreach k $::Keys  {
       .key$k flash; bell;
     }
   }
 }
 proc Click {k} {
   set ::Msg ""
   set val [.key$k cget -text]
   set ok [Move $k]
   incr ::Moves $ok
   wm title . "$::Moves moves"
   Check
 }
 proc ShowKeys {} {
   set i 0
   foreach k $::Keys  {
     set t [lindex $::Puzz $i]
     incr i
     .key$k config -text $t -background white;  
   }
   Check
 }
 proc NewGame {N} {
   global Msg Moves PuzzNr GoalNr
   incr  PuzzNr $N
   if { $PuzzNr > 3} { set PuzzNr 0 }
                        set ::Goal $::Goal_0;
   if { $GoalNr == 1} { set ::Goal $::Goal_1; }
   if { $PuzzNr == 0} { set ::Puzz $::Puzz_0; }
   if { $PuzzNr == 1} { set ::Puzz $::Puzz_1; }
   if { $PuzzNr == 2} { set ::Puzz $::Puzz_2; }
   if { $PuzzNr == 3} { set ::Puzz $::Puzz_3; }
                 set Msg "Try again"
   if { $N>0 } { set Msg "New game" }
   set Moves 0
   ShowKeys
   wm title . "$Msg "
 }
  1. ---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+---
 button .reset   -text "Restart"  -fg blue -command {NewGame  0}
 button .newGame -text "New Game" -fg red  -command {NewGame +1}
 foreach k $::Keys {
   button .key$k -text "$k" -width 4 -command "Click $k"
 }
 grid .newGame x .reset x -sticky nsew
 grid .key11 .key12 .key13 .key14  -sticky nsew  -padx 2 -pady 2
 grid .key21 .key22 .key23 .key24  -sticky nsew  -padx 2 -pady 2
 grid .key31 .key32 .key33 .key34  -sticky nsew  -padx 2 -pady 2
 grid .key41 .key42 .key43 .key44  -sticky nsew  -padx 2 -pady 2
 grid configure .newGame .reset  -columnspan 2 -padx 4
 ShowKeys; Check
 wm title . $progVersion
 focus -force .
 wm resizable . 0 0
  1. For some more versions, see: http://wiki.tcl.tk/15067 : Classic 15 Puzzle and http://wiki.tcl.tk/15085 : N-Puzzle

</lang>