15 puzzle game

From Rosetta Code
Revision as of 00:20, 17 February 2016 by Thundergnat (talk | contribs) (→‎{{header|Perl 6}}: Add Perl 6 example)
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.

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>

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   │
	└──────┴──────┴──────┴──────┘

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>