15 puzzle solver: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|C++}}: Imperative solution < 1 sec)
Line 123: Line 123:
sys 0m0.008s
sys 0m0.008s
</pre>
</pre>
===Time to get Imperetive===
===Time to get Imperative===
====The Solver====
====The Solver====
<lang cpp>
<lang cpp>
Line 129: Line 129:
class fifteenSolver{
class fifteenSolver{
const int Nr[16]{3,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3}, Nc[16]{3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2}, i{1}, g{8}, e{2}, l{4};
const int Nr[16]{3,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3}, Nc[16]{3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2}, i{1}, g{8}, e{2}, l{4};
int n{},_n{}, N0[80]{},N1[80]{},N3[81]{},N4[80];
int n{},_n{}, N0[85]{},N1[85]{},N3[85]{},N4[85];
unsigned long N2[80]{};
unsigned long N2[85]{};
bool fU(){
bool fU(){
if (N2[n]==0x123456789abcdef0) return true;
if (N2[n]==0x123456789abcdef0) return true;
Line 154: Line 154:
case 'r': return fZ(i+e);
case 'r': return fZ(i+e);
case 'u': return fZ(e+l);
case 'u': return fZ(e+l);
default : return fZ(l);}}
default : return fZ(i+e+l);}}
case 3: switch(N1[n]){case 0: switch(N3[n]){case 'l': return fZ(g);
case 3: switch(N1[n]){case 0: switch(N3[n]){case 'l': return fZ(g);
case 'd': return fZ(e);
case 'd': return fZ(e);
Line 203: Line 203:
void Solve(){
void Solve(){
while (not fN()){n=0;++_n;}
while (not fN()){n=0;++_n;}
std::cout<<"Solution found in "<<n<<" moves: "; for (int g{0};g<=n;++g) std::cout<<(char)N3[g]; std::cout<<std::endl;
std::cout<<"Solution found in "<<n<<" moves: "; for (int g{1};g<=n;++g) std::cout<<(char)N3[g]; std::cout<<std::endl;
}
}
};
};

Revision as of 10:05, 19 October 2017

Task
15 puzzle solver
You are encouraged to solve this task according to the task description, using any language you may know.

Your task is to write a program that finds a solution in the fewest moves possible to a random Fifteen Puzzle Game.
For this task you will be using the following puzzle:

15 14  1  6
 9 11  4 12
 0 10  7  3
13  8  5  2


Solution:

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15  0

The output must show the moves' directions, like so: left, left, left, down, right... and so on.
There are 2 solutions with 52 moves:
rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd
rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd
finding either one, or both is an acceptable result.

Extra credit.

Solve the following problems:

 2  1  3  4
 5  6  7  8
 9 10 11 12
13 14 15  0

and

  0 12  9 13
 15 11 10 14
  3  7  2  5
  4  8  6  1

C++

Staying Functional (as possible in C++)

The Solver

Translation of: FSharp

<lang cpp> // Solve Random 15 Puzzles : Nigel Galloway - October 11th., 2017 const int Nr[]{3,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3}, Nc[]{3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2}; using N = std::tuple<int,int,unsigned long,std::string,int>; void fN(const N,const int); const N fI(const N n){

 int           g = (11-std::get<1>(n)-std::get<0>(n)*4)*4;
 unsigned long a = std::get<2>(n)&((unsigned long)15<<g);
 return N (std::get<0>(n)+1,std::get<1>(n),std::get<2>(n)-a+(a<<16),std::get<3>(n)+"d",(Nr[a>>g]<=std::get<0>(n))?std::get<4>(n):std::get<4>(n)+1);

} const N fG(const N n){

 int           g = (19-std::get<1>(n)-std::get<0>(n)*4)*4;
 unsigned long a = std::get<2>(n)&((unsigned long)15<<g);
 return N (std::get<0>(n)-1,std::get<1>(n),std::get<2>(n)-a+(a>>16),std::get<3>(n)+"u",(Nr[a>>g]>=std::get<0>(n))?std::get<4>(n):std::get<4>(n)+1);

} const N fE(const N n){

 int           g = (14-std::get<1>(n)-std::get<0>(n)*4)*4;
 unsigned long a = std::get<2>(n)&((unsigned long)15<<g);
 return N (std::get<0>(n),std::get<1>(n)+1,std::get<2>(n)-a+(a<<4),std::get<3>(n)+"r",(Nc[a>>g]<=std::get<1>(n))?std::get<4>(n):std::get<4>(n)+1);

} const N fL(const N n){

 int           g = (16-std::get<1>(n)-std::get<0>(n)*4)*4;
 unsigned long a = std::get<2>(n)&((unsigned long)15<<g);
 return N (std::get<0>(n),std::get<1>(n)-1,std::get<2>(n)-a+(a>>4),std::get<3>(n)+"l",(Nc[a>>g]>=std::get<1>(n))?std::get<4>(n):std::get<4>(n)+1);

} void fZ(const N n, const int g){

 if (std::get<2>(n)==0x123456789abcdef0){
   int g{};for (char a: std::get<3>(n)) ++g;
   std::cout<<"Solution found with "<<g<<" moves: "<<std::get<3>(n)<<std::endl; exit(0);}
 if (std::get<4>(n) <= g) fN(n,g);

} void fN(const N n, const int g){

 const int i{std::get<0>(n)}, e{std::get<1>(n)}, l{std::get<3>(n).back()};
 switch(i){case  0: switch(e){case  0: switch(l){case 'l': fZ(fI(n),g); return;
                                                 case 'u': fZ(fE(n),g); return;
                                                 default : fZ(fE(n),g); fZ(fI(n),g); return;}
                              case  3: switch(l){case 'r': fZ(fI(n),g); return;
                                                 case 'u': fZ(fL(n),g); return;
                                                 default : fZ(fL(n),g); fZ(fI(n),g); return;}
                              default: switch(l){case 'l': fZ(fI(n),g); fZ(fL(n),g); return;
                                                 case 'r': fZ(fI(n),g); fZ(fE(n),g); return;
                                                 case 'u': fZ(fE(n),g); fZ(fL(n),g); return;
                                                 default : fZ(fL(n),g); fZ(fI(n),g); fZ(fE(n),g); return;}}
           case  3: switch(e){case  0: switch(l){case 'l': fZ(fG(n),g); return;
                                                 case 'd': fZ(fE(n),g); return;
                                                 default : fZ(fE(n),g); fZ(fG(n),g); return;}
                              case  3: switch(l){case 'r': fZ(fG(n),g); return;
                                                 case 'd': fZ(fL(n),g); return;
                                                 default : fZ(fL(n),g); fZ(fG(n),g); return;}
                              default: switch(l){case 'l': fZ(fG(n),g); fZ(fL(n),g); return;
                                                 case 'r': fZ(fG(n),g); fZ(fE(n),g); return;
                                                 case 'd': fZ(fE(n),g); fZ(fL(n),g); return;
                                                 default : fZ(fL(n),g); fZ(fG(n),g); fZ(fE(n),g); return;}}
           default: switch(e){case  0: switch(l){case 'l': fZ(fI(n),g); fZ(fG(n),g); return;
                                                 case 'u': fZ(fE(n),g); fZ(fG(n),g); return;
                                                 case 'd': fZ(fE(n),g); fZ(fI(n),g); return;
                                                 default : fZ(fE(n),g); fZ(fI(n),g); fZ(fG(n),g); return;}
                              case  3: switch(l){case 'd': fZ(fI(n),g); fZ(fL(n),g); return;
                                                 case 'u': fZ(fL(n),g); fZ(fG(n),g); return;
                                                 case 'r': fZ(fI(n),g); fZ(fG(n),g); return;
                                                 default : fZ(fL(n),g); fZ(fI(n),g); fZ(fG(n),g); return;}
                              default: switch(l){case 'd': fZ(fI(n),g); fZ(fL(n),g); fZ(fE(n),g); return;
                                                 case 'l': fZ(fI(n),g); fZ(fL(n),g); fZ(fG(n),g); return;
                                                 case 'r': fZ(fI(n),g); fZ(fE(n),g); fZ(fG(n),g); return;
                                                 case 'u': fZ(fE(n),g); fZ(fL(n),g); fZ(fG(n),g); return;
                                                 default : fZ(fL(n),g); fZ(fI(n),g); fZ(fE(n),g); fZ(fG(n),g); return;}}}

} void Solve(N n){for(int g{};;++g) fN(n,g);} </lang>

The Task

<lang cpp> int main (){

 N start(2,0,0xfe169b4c0a73d852,"",0);
 Solve(start);

} </lang>

Output:
Solution found with 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

real    0m2.897s
user    0m2.887s
sys     0m0.008s

Time to get Imperative

The Solver

<lang cpp> // Solve Random 15 Puzzles : Nigel Galloway - October 18th., 2017 class fifteenSolver{

 const int Nr[16]{3,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3}, Nc[16]{3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2}, i{1}, g{8}, e{2}, l{4};
 int n{},_n{}, N0[85]{},N1[85]{},N3[85]{},N4[85];
 unsigned long N2[85]{};
 bool fU(){
   if (N2[n]==0x123456789abcdef0) return true; 
   if (N4[n]<=_n) return fN();
   return false;
 }
 bool fZ(const int w){
   int a = n;
   if ((w&i)>0){fI(); if (fU()) return true; n=a;}
   if ((w&g)>0){fG(); if (fU()) return true; n=a;}
   if ((w&e)>0){fE(); if (fU()) return true; n=a;}
   if ((w&l)>0){fL(); return fU();}
   return false;
 }
 bool fN(){
   switch(N0[n]){case  0: switch(N1[n]){case  0: switch(N3[n]){case 'l': return fZ(i);
                                                               case 'u': return fZ(e);
                                                               default : return fZ(i+e);}
                                        case  3: switch(N3[n]){case 'r': return fZ(i);
                                                               case 'u': return fZ(l);
                                                               default : return fZ(i+l);}
                                        default: switch(N3[n]){case 'l': return fZ(i+l);
                                                               case 'r': return fZ(i+e);
                                                               case 'u': return fZ(e+l);
                                                               default : return fZ(i+e+l);}}
                 case  3: switch(N1[n]){case  0: switch(N3[n]){case 'l': return fZ(g);
                                                               case 'd': return fZ(e);
                                                               default : return fZ(e+g);}
                                        case  3: switch(N3[n]){case 'r': return fZ(g);
                                                               case 'd': return fZ(l);
                                                               default : return fZ(g+l);}
                                        default: switch(N3[n]){case 'l': return fZ(g+l);
                                                               case 'r': return fZ(e+g);
                                                               case 'd': return fZ(e+l);
                                                               default : return fZ(g+e+l);}}
                 default: switch(N1[n]){case  0: switch(N3[n]){case 'l': return fZ(i+g);
                                                               case 'u': return fZ(g+e);
                                                               case 'd': return fZ(i+e);
                                                               default : return fZ(i+g+e);}
                                        case  3: switch(N3[n]){case 'd': return fZ(i+l);
                                                               case 'u': return fZ(g+l);
                                                               case 'r': return fZ(i+g);
                                                               default : return fZ(i+g+l);}
                                        default: switch(N3[n]){case 'd': return fZ(i+e+l);
                                                               case 'l': return fZ(i+g+l);
                                                               case 'r': return fZ(i+g+e);
                                                               case 'u': return fZ(g+e+l);
                                                               default : return fZ(i+g+e+l);}}}
 }
 void fI(){
   int           g = (11-N1[n]-N0[n]*4)*4;
   unsigned long a = N2[n]&((unsigned long)15<<g);
   N0[n+1]=N0[n]+1; N1[n+1]=N1[n]; N2[n+1]=N2[n]-a+(a<<16); N3[n+1]='d'; N4[n+1]=N4[n]+(Nr[a>>g]<=N0[n++]?0:1);
 } 
 void fG(){
   int           g = (19-N1[n]-N0[n]*4)*4;
   unsigned long a = N2[n]&((unsigned long)15<<g);
   N0[n+1]=N0[n]-1; N1[n+1]=N1[n]; N2[n+1]=N2[n]-a+(a>>16); N3[n+1]='u'; N4[n+1]=N4[n]+(Nr[a>>g]>=N0[n++]?0:1);
 } 
 void fE(){
   int           g = (14-N1[n]-N0[n]*4)*4;
   unsigned long a = N2[n]&((unsigned long)15<<g);
   N0[n+1]=N0[n]; N1[n+1]=N1[n]+1; N2[n+1]=N2[n]-a+(a<<4); N3[n+1]='r'; N4[n+1]=N4[n]+(Nc[a>>g]<=N1[n++]?0:1);
 } 
 void fL(){
   int           g = (16-N1[n]-N0[n]*4)*4;
   unsigned long a = N2[n]&((unsigned long)15<<g);
   N0[n+1]=N0[n]; N1[n+1]=N1[n]-1; N2[n+1]=N2[n]-a+(a>>4); N3[n+1]='l'; N4[n+1]=N4[n]+(Nc[a>>g]>=N1[n++]?0:1);
 }

public:

 fifteenSolver(int n, int i, unsigned long g){N0[0]=n; N1[0]=i; N2[0]=g; N4[0]=0;}
 void Solve(){
   while (not fN()){n=0;++_n;}
   std::cout<<"Solution found in "<<n<<" moves: "; for (int g{1};g<=n;++g) std::cout<<(char)N3[g]; std::cout<<std::endl;
 }

}; </lang>

The Task

<lang cpp> int main (){

 fifteenSolver start(2,0,0xfe169b4c0a73d852);
 start.Solve();

} </lang>

Output:
Solution found in 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

real    0m0.795s
user    0m0.794s
sys     0m0.000s

F#

<lang fsharp> // A Naive 15 puzzle solver using no memory. Nigel Galloway: October 6th., 2017 let Nr = [|3;0;0;0;0;1;1;1;1;2;2;2;2;3;3;3|] let Nc = [|3;0;1;2;3;0;1;2;3;0;1;2;3;0;1;2|] let rec Solve n =

 let rec fN (i,g,e,l,_) = seq {
   let   fI = let n = (11-g-i*4)*4
              let a = (e&&&(15UL<<<n))
              (i+1,g,e-a+(a<<<16),'d'::l,Nr.[(int (a>>>n))] <= i)
   let   fG = let n = (19-g-i*4)*4
              let a = (e&&&(15UL<<<n))
              (i-1,g,e-a+(a>>>16),'u'::l,Nr.[(int (a>>>n))] >= i)
   let   fE = let n = (14-g-i*4)*4
              let a = (e&&&(15UL<<<n))
              (i,g+1,e-a+(a<<<4), 'r'::l,Nc.[(int (a>>>n))] <= g)
   let   fL = let n = (16-g-i*4)*4
              let a = (e&&&(15UL<<<n))
              (i,g-1,e-a+(a>>>4), 'l'::l,Nc.[(int (a>>>n))] >= g)
   let   fZ (n,i,g,e,l) = seq{yield (n,i,g,e,l); if l then yield! fN (n,i,g,e,l)}
   match (i,g,l) with
     |(0,0,'l'::_) -> yield! fZ fI
     |(0,0,'u'::_) -> yield! fZ fE
     |(0,0,_)      -> yield! fZ fI; yield! fZ fE
     |(0,3,'r'::_) -> yield! fZ fI
     |(0,3,'u'::_) -> yield! fZ fL
     |(0,3,_)      -> yield! fZ fI; yield! fZ fL
     |(3,0,'l'::_) -> yield! fZ fG
     |(3,0,'d'::_) -> yield! fZ fE
     |(3,0,_)      -> yield! fZ fE; yield! fZ fG
     |(3,3,'r'::_) -> yield! fZ fG
     |(3,3,'d'::_) -> yield! fZ fL
     |(3,3,_)      -> yield! fZ fG; yield! fZ fL
     |(0,_,'l'::_) -> yield! fZ fI; yield! fZ fL
     |(0,_,'r'::_) -> yield! fZ fI; yield! fZ fE
     |(0,_,'u'::_) -> yield! fZ fE; yield! fZ fL
     |(0,_,_)      -> yield! fZ fI; yield! fZ fE; yield! fZ fL
     |(_,0,'l'::_) -> yield! fZ fI; yield! fZ fG
     |(_,0,'u'::_) -> yield! fZ fE; yield! fZ fG
     |(_,0,'d'::_) -> yield! fZ fI; yield! fZ fE
     |(_,0,_)      -> yield! fZ fI; yield! fZ fE; yield! fZ fG
     |(3,_,'l'::_) -> yield! fZ fG; yield! fZ fL
     |(3,_,'r'::_) -> yield! fZ fE; yield! fZ fG
     |(3,_,'d'::_) -> yield! fZ fE; yield! fZ fL
     |(3,_,_)      -> yield! fZ fE; yield! fZ fG; yield! fZ fL
     |(_,3,'d'::_) -> yield! fZ fI; yield! fZ fL
     |(_,3,'u'::_) -> yield! fZ fL; yield! fZ fG
     |(_,3,'r'::_) -> yield! fZ fI; yield! fZ fG
     |(_,3,_)      -> yield! fZ fI; yield! fZ fL; yield! fZ fG
     |(_,_,'d'::_) -> yield! fZ fI; yield! fZ fE; yield! fZ fL
     |(_,_,'l'::_) -> yield! fZ fI; yield! fZ fG; yield! fZ fL
     |(_,_,'r'::_) -> yield! fZ fI; yield! fZ fE; yield! fZ fG
     |(_,_,'u'::_) -> yield! fZ fE; yield! fZ fG; yield! fZ fL
     |_            -> yield! fZ fI; yield! fZ fE; yield! fZ fG; yield! fZ fL
 }
 let n = Seq.collect fN n
 match (Seq.tryFind(fun(_,_,n,_,_)->n=0x123456789abcdef0UL)) n with
 |Some(_,_,_,n,_) -> printf "Solution found with %d moves: " (List.length n); List.iter (string >> printf "%s") (List.rev n); printfn ""
 |_               -> Solve (Seq.filter(fun (_,_,_,_,n)->not n) n)

Solve [(2,0,0xfe169b4c0a73d852UL,[],false)] </lang>

Output:
Solution found with 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

see: Pretty Print of Optimal Soltion

Phix

This example is incorrect. Please fix the code and remove this message.

Details: The task calls for a solution in the fewest moves which is 52 not 58

Brute force width-first search, probably not quite optimal, since the scoring algorithm may trim some better paths from the search space.
Shows first solution found. No multi-tile moves. <lang Phix>-- -- demo\rosetta\Solve15puzzle.exw -- ============================== -- constant udlr = {"up", "down", "left", "right"} sequence board = tagset(15)&0 integer pos = 16

integer collected = 0 sequence lines = repeat("",5)

procedure print_board(integer last) integer k = 2

   for i=1 to length(board) do
       string this = iff(i=pos?"   ":sprintf("%3d",{board[i]}))
       lines[k] &= this
       if mod(i,4)=0 then k+=1 end if
   end for
   collected += 1
   if collected=6 or last then
       puts(1,join(lines,"\n")&"\n\n")
       lines = repeat("",5)
       collected = 0
   else
       for i=2 to 5 do
           lines[i] &= "      "
       end for
   end if

end procedure

function move(integer d) integer valid = 0 integer stick = 0

   for k=1 to 8 by 2 do
       if board[k]!=k then exit end if
       if board[k+1]!=k+1 then exit end if
       stick = k+1
   end for
   integer new_pos = pos+{+4,-4,+1,-1}[d]
   if new_pos>=1 and new_pos<=16
   and (mod(pos,4)=mod(new_pos,4) -- same col, or row:
    or floor((pos-1)/4)=floor((new_pos-1)/4)) then
       {board[pos],board[new_pos]} = {board[new_pos],0}
       valid = pos>stick and new_pos>stick
       pos = new_pos
   end if
   return {valid,stick}

end function

constant posns = {1,2,3,4,5,6,7,8,9,13,10,14,11,12,15}

function score(sequence board) integer res = 0, pos, act_pos

   for i=1 to 15 do
       pos = posns[i]
       act_pos = find(pos,board)
       res += (abs(mod(pos,4)-mod(act_pos,4))+
               abs(floor((pos-1)/4)-floor((act_pos-1)/4)))*10*pos
   end for
   return res

end function

if 0 then

   for i=1 to 5555555 do {}=move(rand(4)) end for      -- (25% are likely invalid)

else

   board = {15,14, 1, 6,
             9,11, 4,12,
             0,10, 7, 3,
            13, 8, 5, 2}
   pos = find(0,board)

end if atom t0 = time() integer pos0 = pos, s, valid, stick sequence board0 = board, boards = {{0,score(board),{},board,pos}}, new_boards, moves integer visited = new_dict() while 1 do

   new_boards = {}
   for i=1 to length(boards) do
       for c=1 to 4 do
           {?,?,moves,board,pos} = boards[i]
           {valid,stick} = move(c)
           if valid and getd_index(board,visited)=0 then
               moves &= c
               s = score(board)
               if s=0 then exit end if
               new_boards = append(new_boards,{8-stick,s,moves,board,pos})
               setd(board,0,visited)
           end if
       end for
       if s=0 then exit end if
   end for
   if s=0 then exit end if
   if length(new_boards)>16384 then
       boards = sort(new_boards)[1..16384]
       integer dsv = dict_size(visited)
       {?,s,{},board,pos} = boards[1]
       lines[1] = sprintf("thinking... %d boards visited, best score: %d (0=solved):",{dsv,s})
       print_board(1)
   else
       boards = new_boards
   end if

end while

pos = pos0 board = board0 lines[1] = "solved!!: " print_board(0) for i=1 to length(moves) do

   integer mi = moves[i]
   string m = udlr[mi]
   string this = sprintf("move %d, %s:",{i,m})
   lines[1] &= sprintf("%-18s",{this})
   moves[i] = upper(m[1])
   {} = move(mi)
   print_board(i=length(moves))

end for printf(1,"solved in %d moves (%d boards visited, %s)\n",{length(moves),dict_size(visited),elapsed(time()-t0)}) printf(1,"moves: %s\n",{moves}) {} = wait_key()</lang>

Output:
thinking... 39204 boards visited, best score: 1910 (0=solved):
 15     1  6
  8 14 11  4
  9 10  3 12
 13  5  7  2

thinking... 71666 boards visited, best score: 1760 (0=solved):
    15  1  6
  8 14 11  4
  9 10  3 12
 13  5  7  2

thinking... 103082 boards visited, best score: 1840 (0=solved):
 15  1  6
  8 14 11  4
  9 10  3 12
 13  5  7  2

solved in 58 moves (1330046 boards visited, 49.59s)
moves: LDDLUULURRDDLULURRDLLDDRURURDDLURULDLULURDRDLLUURDRRDLLULU