Solve a Hopido puzzle: Difference between revisions

From Rosetta Code
Content added Content deleted
m (Fixed D entry again)
m (Better D entry)
Line 176: Line 176:


struct HopidoPuzzle {
struct HopidoPuzzle {
alias InputCellBaseType = char;
private alias InputCellBaseType = char;
enum InputCell : InputCellBaseType { available = '#', unavailable = '.' }
private enum InputCell : InputCellBaseType { available = '#', unavailable = '.' }
alias Cell = uint;
private alias Cell = uint;
enum : Cell { unknownCell = 0, unavailableCell = Cell.max } // Special Cell values.
private enum : Cell { unknownCell = 0, unavailableCell = Cell.max } // Special Cell values.


// Neighbors, [shift row, shift column].
// Neighbors, [shift row, shift column].
static immutable int[2][8] shifts = [[-2, -2], [2, -2], [-2, 2], [2, 2],
private static immutable int[2][8] shifts = [[-2, -2], [2, -2], [-2, 2], [2, 2],
[ 0, -3], [0, 3], [-3, 0], [3, 0]];
[ 0, -3], [0, 3], [-3, 0], [3, 0]];


immutable size_t gridWidth, gridHeight;
private immutable size_t gridWidth, gridHeight;
immutable Cell nAvailableCells;
private immutable Cell nAvailableCells;
/*immutable*/ const InputCell[] flatPuzzle;
private /*immutable*/ const InputCell[] flatPuzzle;
Cell[] grid; // Flattened mutable game grid.
private Cell[] grid; // Flattened mutable game grid.


@disable this();
@disable this();
Line 198: Line 198:
assert(!rawPuzzle[0].empty);
assert(!rawPuzzle[0].empty);
assert(rawPuzzle.all!(row => row.length == rawPuzzle[0].length)); // Is rectangular.
assert(rawPuzzle.all!(row => row.length == rawPuzzle[0].length)); // Is rectangular.

// Has at least one start point.
assert(rawPuzzle.join.representation.canFind(InputCell.available));
} body {
} body {
//immutable puzzle = rawPuzzle.to!(InputCell[][]);
//immutable puzzle = rawPuzzle.to!(InputCell[][]);
Line 206: Line 209:
flatPuzzle = puzzle.join;
flatPuzzle = puzzle.join;
nAvailableCells = flatPuzzle.representation.count!(ic => ic == InputCell.available);
nAvailableCells = flatPuzzle.representation.count!(ic => ic == InputCell.available);

assert(nAvailableCells > 0); // Has at least one start point.


grid = flatPuzzle
grid = flatPuzzle
.representation
.map!(ic => ic == InputCell.available ? unknownCell : unavailableCell)
.map!(ic => ic == InputCell.available ? unknownCell : unavailableCell)
.array;
.array;
Line 247: Line 249:




bool search(in size_t r, in size_t c, in Cell cell) pure nothrow @safe @nogc {
private bool search(in size_t r, in size_t c, in Cell cell) pure nothrow @safe @nogc {
if (cell > nAvailableCells)
if (cell > nAvailableCells)
return true; // One solution found.
return true; // One solution found.

Revision as of 13:27, 23 November 2014

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

Hopido puzzles are similar to Hidato. The most important difference is that the only moves allowed are: hop over one tile diagonally; and over two tiles horizontally and vertically. It should be possible to start anywhere in the path, the end point isn't indicated and there are no intermediate clues. Hopido Design Post Mortem contains the following:

"Big puzzles represented another problem. Up until quite late in the project our puzzle solver was painfully slow with most puzzles above 7×7 tiles. Testing the solution from each starting point could take hours. If the tile layout was changed even a little, the whole puzzle had to be tested again. We were just about to give up the biggest puzzles entirely when our programmer suddenly came up with a magical algorithm that cut the testing process down to only minutes. Hooray!"

Knowing the kindness in the heart of every contributor to Rosetta Code I know that we shall feel that as an act of humanity we must solve these puzzles for them in let's say milliseconds.

Example:

. 0 0 . 0 0 .
0 0 0 0 0 0 0
0 0 0 0 0 0 0
. 0 0 0 0 0 .
. . 0 0 0 . .
. . . 0 . . .

Extra credits are available for other interesting designs.

Realated Tasks:

C++

<lang cpp>

  1. include <vector>
  2. include <sstream>
  3. include <iostream>
  4. include <iterator>
  5. include <stdlib.h>
  6. include <string.h>

using namespace std;

struct node {

   int val;
   unsigned char neighbors;

};

class nSolver { public:

   nSolver()
   {

dx[0] = -2; dy[0] = -2; dx[1] = -2; dy[1] = 2; dx[2] = 2; dy[2] = -2; dx[3] = 2; dy[3] = 2; dx[4] = -3; dy[4] = 0; dx[5] = 3; dy[5] = 0; dx[6] = 0; dy[6] = -3; dx[7] = 0; dy[7] = 3;

   }
   void solve( vector<string>& puzz, int max_wid )
   {

if( puzz.size() < 1 ) return; wid = max_wid; hei = static_cast<int>( puzz.size() ) / wid; int len = wid * hei, c = 0; max = len; arr = new node[len]; memset( arr, 0, len * sizeof( node ) );

for( vector<string>::iterator i = puzz.begin(); i != puzz.end(); i++ ) { if( ( *i ) == "*" ) { max--; arr[c++].val = -1; continue; } arr[c].val = atoi( ( *i ).c_str() ); c++; }

solveIt(); c = 0; for( vector<string>::iterator i = puzz.begin(); i != puzz.end(); i++ ) { if( ( *i ) == "." ) { ostringstream o; o << arr[c].val; ( *i ) = o.str(); } c++; } delete [] arr;

   }

private:

   bool search( int x, int y, int w )
   {

if( w > max ) return true;

node* n = &arr[x + y * wid]; n->neighbors = getNeighbors( x, y );

for( int d = 0; d < 8; d++ ) { if( n->neighbors & ( 1 << d ) ) { int a = x + dx[d], b = y + dy[d]; if( arr[a + b * wid].val == 0 ) { arr[a + b * wid].val = w; if( search( a, b, w + 1 ) ) return true; arr[a + b * wid].val = 0; } } } return false;

   }
   unsigned char getNeighbors( int x, int y )
   {

unsigned char c = 0; int a, b; for( int xx = 0; xx < 8; xx++ ) { a = x + dx[xx], b = y + dy[xx]; if( a < 0 || b < 0 || a >= wid || b >= hei ) continue; if( arr[a + b * wid].val > -1 ) c |= ( 1 << xx ); } return c;

   }
   void solveIt()
   {

int x, y, z; findStart( x, y, z ); if( z == 99999 ) { cout << "\nCan't find start point!\n"; return; } search( x, y, z + 1 );

   }
   void findStart( int& x, int& y, int& z )
   {

for( int b = 0; b < hei; b++ ) for( int a = 0; a < wid; a++ ) if( arr[a + wid * b].val == 0 ) { x = a; y = b; z = 1; arr[a + wid * b].val = z; return; }

   }
   int wid, hei, max, dx[8], dy[8];
   node* arr;

};

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

   int wid; string p;
   p = "* . . * . . * . . . . . . . . . . . . . . * . . . . . * * * . . . * * * * * . * * *"; wid = 7;
   istringstream iss( p ); vector<string> puzz;
   copy( istream_iterator<string>( iss ), istream_iterator<string>(), back_inserter<vector<string> >( puzz ) );
   nSolver s; s.solve( puzz, wid );
   int c = 0;
   for( vector<string>::iterator i = puzz.begin(); i != puzz.end(); i++ )
   {

if( ( *i ) != "*" && ( *i ) != "." ) { if( atoi( ( *i ).c_str() ) < 10 ) cout << "0"; cout << ( *i ) << " "; } else cout << " "; if( ++c >= wid ) { cout << endl; c = 0; }

   }
   cout << endl << endl;
   return system( "pause" );

} </lang>

Output:
   01 04    12 03
27 16 19 22 15 18 21
05 08 11 02 07 10 13
   23 26 17 20 25
      06 09 14
         24

D

Translation of: C++

From the refactored C++ version with more precise typing. This tries all possible start positions. The HopidoPuzzle struct is created at compile-time, so its pre-conditions can catch most malformed puzzles at compile-time. <lang d>import std.stdio, std.conv, std.string, std.range, std.algorithm, std.typecons;


struct HopidoPuzzle {

   private alias InputCellBaseType = char;
   private enum InputCell : InputCellBaseType { available = '#', unavailable = '.' }
   private alias Cell = uint;
   private enum : Cell { unknownCell = 0, unavailableCell = Cell.max } // Special Cell values.
   // Neighbors, [shift row, shift column].
   private static immutable int[2][8] shifts = [[-2, -2], [2, -2], [-2, 2], [2, 2],
                                                [ 0, -3], [0,  3], [-3, 0], [3, 0]];
   private immutable size_t gridWidth, gridHeight;
   private immutable Cell nAvailableCells;
   private /*immutable*/ const InputCell[] flatPuzzle;
   private Cell[] grid; // Flattened mutable game grid.
   @disable this();


   this(in string[] rawPuzzle) pure @safe
   in {
       assert(!rawPuzzle.empty);
       assert(!rawPuzzle[0].empty);
       assert(rawPuzzle.all!(row => row.length == rawPuzzle[0].length)); // Is rectangular.
       // Has at least one start point.
       assert(rawPuzzle.join.representation.canFind(InputCell.available));
   } body {
       //immutable puzzle = rawPuzzle.to!(InputCell[][]);
       immutable puzzle = rawPuzzle.map!representation.array.to!(InputCell[][]);
       gridWidth = puzzle[0].length;
       gridHeight = puzzle.length;
       flatPuzzle = puzzle.join;
       nAvailableCells = flatPuzzle.representation.count!(ic => ic == InputCell.available);
       grid = flatPuzzle
              .representation
              .map!(ic => ic == InputCell.available ? unknownCell : unavailableCell)
              .array;
   }


   Nullable!(string[][]) solve() pure /*nothrow*/ @safe
   out(result) {
       if (!result.isNull)
           assert(!grid.canFind(unknownCell));
   } body {
       // Try all possible start positions.
       foreach (immutable r; 0 ..  gridHeight) {
           foreach (immutable c; 0 .. gridWidth) {
               immutable pos = r * gridWidth + c;
               if (grid[pos] == unknownCell) {
                   immutable Cell startCell = 1; // To lay the first cell value.
                   grid[pos] = startCell;        // Try.
                   if (search(r, c, startCell + 1)) {
                       auto result = zip(flatPuzzle, grid)
                                     //.map!({p, c} => ...
                                     .map!(pc => (pc[0] == InputCell.available) ?
                                                 pc[1].text :
                                                 InputCellBaseType(pc[0]).text)
                                     .array
                                     .chunks(gridWidth)
                                     .array;
                       return typeof(return)(result);
                   }
                   grid[pos] = unknownCell; // Restore.
               }
           }
       }
       return typeof(return)();
   }


   private bool search(in size_t r, in size_t c, in Cell cell) pure nothrow @safe @nogc {
       if (cell > nAvailableCells)
           return true; // One solution found.
       foreach (immutable sh; shifts) {
           immutable r2 = r + sh[0],
                     c2 = c + sh[1],
                     pos = r2 * gridWidth + c2;
           // No need to test for >= 0 because uint wraps around.
           if (c2 < gridWidth && r2 < gridHeight && grid[pos] == unknownCell) {
               grid[pos] = cell;        // Try.
               if (search(r2, c2, cell + 1))
                   return true;
               grid[pos] = unknownCell; // Restore.
           }
       }
       return false;
   }

}


void main() @safe {

   // enum HopidoPuzzle to catch malformed puzzles at compile-time.
   enum puzzle = ".##.##.
                  #######
                  #######
                  .#####.
                  ..###..
                  ...#...".split.HopidoPuzzle;
   immutable solution = puzzle.solve; // Solved at run-time.
   if (solution.isNull)
       writeln("No solution found.");
   else
       writefln("One solution:\n%(%-(%2s %)\n%)", solution);

}</lang>

Output:
One solution:
 .  1  4  . 12  3  .
27 16 19 22 15 18 21
 5  8 11  2  7 10 13
 . 23 26 17 20 25  .
 .  .  6  9 14  .  .
 .  .  . 24  .  .  .

Icon and Unicon

Minor variant of Solve_a_Holy_Knight's_tour. Works in Unicon only.

<lang unicon>global nCells, cMap, best record Pos(r,c)

procedure main(A)

   puzzle := showPuzzle("Input",readPuzzle())
   QMouse(puzzle,findStart(puzzle),&null,0)
   showPuzzle("Output", solvePuzzle(puzzle)) | write("No solution!")

end

procedure readPuzzle()

   # Start with a reduced puzzle space
   p := [[-1],[-1]]
   nCells := maxCols := 0
   every line := !&input do {
       put(p,[: -1 | -1 | gencells(line) | -1 | -1 :])
       maxCols <:= *p[-1]
       }
   every put(p, [-1]|[-1])
   # Now normalize all rows to the same length
   every i := 1 to *p do p[i] := [: !p[i] | (|-1\(maxCols - *p[i])) :]
   return p

end

procedure gencells(s)

   static WS, NWS
   initial {
       NWS := ~(WS := " \t")
       cMap := table()     # Map to/from internal model
       cMap["#"] := -1;  cMap["_"] :=  0
       cMap[-1]  := " "; cMap[0]   := "_"
       }
   s ? while not pos(0) do {
           w := (tab(many(WS))|"", tab(many(NWS))) | break
           w := numeric(\cMap[w]|w)
           if -1 ~= w then nCells +:= 1
           suspend w
           }

end

procedure showPuzzle(label, p)

   write(label," with ",nCells," cells:")
   every r := !p do {
       every c := !r do writes(right((\cMap[c]|c),*nCells+1))
       write()
       }
   return p

end

procedure findStart(p)

   if \p[r := !*p][c := !*p[r]] = 1 then return Pos(r,c)

end

procedure solvePuzzle(puzzle)

   if path := \best then {
       repeat {
           loc := path.getLoc()
           puzzle[loc.r][loc.c] := path.getVal()
           path := \path.getParent() | break
           }
       return puzzle
       }

end

class QMouse(puzzle, loc, parent, val)

   method getVal(); return val; end
   method getLoc(); return loc; end
   method getParent(); return parent; end
   method atEnd(); return nCells = val; end
   method visit(r,c)
       if /best & validPos(r,c) then return Pos(r,c)
   end
   method validPos(r,c)
       v := val+1
       xv := (0 <= puzzle[r][c]) | fail
       if xv = (v|0) then {  # make sure this path hasn't already gone there
           ancestor := self
           while xl := (ancestor := \ancestor.getParent()).getLoc() do
               if (xl.r = r) & (xl.c = c) then fail
           return
           }
   end

initially

   val := val+1
   if atEnd() then return best := self
   QMouse(puzzle, visit(loc.r-3,loc.c),   self, val)
   QMouse(puzzle, visit(loc.r-2,loc.c-2), self, val)
   QMouse(puzzle, visit(loc.r,  loc.c-3), self, val)
   QMouse(puzzle, visit(loc.r+2,loc.c-2), self, val)
   QMouse(puzzle, visit(loc.r+3,loc.c),   self, val)
   QMouse(puzzle, visit(loc.r+2,loc.c+2), self, val)
   QMouse(puzzle, visit(loc.r,  loc.c+3), self, val)
   QMouse(puzzle, visit(loc.r-2,loc.c+2), self, val)

end</lang>

Sample run:

->hopido <hopido1.in
Input with 27 cells:
                                 
                                 
           _  _     _  _         
        _  _  _  _  _  _  _      
        _  _  _  _  _  _  _      
           _  _  _  _  _         
              _  _  _            
                 1               
                                 
                                 
Output with 27 cells:
                                 
                                 
           3 21    13 22         
       25  9  6 26 10  7 27      
       20 17 14  2 18 15 12      
           4 24  8  5 23         
             19 16 11            
                 1               
                                 
                                 
->

Perl 6

Using the solver from Solve_a_Hidato_puzzle. <lang perl6>my @adjacent = [3, 0],

     [2, -2],         [2, 2],
  [0, -3],                [0, 3],
     [-2, -2],        [-2, 2],
              [-3, 0];

solveboard q:to/END/;

   . 0 0 . 0 0 .
   0 0 0 0 0 0 0
   0 0 0 0 0 0 0
   . 0 0 0 0 0 .
   . . 0 0 0 . .
   . . . 1 . . .
   END</lang>
Output:
   21  4    20  3   
26 12 15 25 11 14 24
17  6  9 18  5  8 19
   22 27 13 23  2   
      16  7 10      
          1         
59 tries

Racket

This solution uses the module "hidato-family-solver.rkt" from Solve a Numbrix puzzle#Racket. The difference between the two is essentially the neighbourhood function.

<lang racket>#lang racket (require "hidato-family-solver.rkt")

(define hoppy-moore-neighbour-offsets

 '((+3 0) (-3 0) (0 +3) (0 -3) (+2 +2) (-2 -2) (-2 +2) (+2 -2)))

(define solve-hopido (solve-hidato-family hoppy-moore-neighbour-offsets))

(displayln

(puzzle->string
 (solve-hopido
  #(#(_ 0 0 _ 0 0 _)
    #(0 0 0 0 0 0 0)
    #(0 0 0 0 0 0 0)
    #(_ 0 0 0 0 0 _)
    #(_ _ 0 0 0 _ _)
    #(_ _ _ 0 _ _ _)))))

</lang>

Output:
 _  2 20  _  3 19  _
 7 10 13  6  9 12  5
15 22 25 16 21 24 27
 _  1  8 11  4 18  _
 _  _ 14 23 26  _  _
 _  _  _ 17  _  _  _

REXX

This REXX program is a slightly modified version of the REXX Hidato program. <lang rexx>/*REXX program solves a Hopido puzzle, displays puzzle and the solution.*/ call time 'Reset' /*reset the REXX elapsed timer. */ maxr=0; maxc=0; maxx=0; minr=9e9; minc=9e9; minx=9e9; cells=0; @.= parse arg xxx; /*get cell definitions from C.L. */ xxx=translate(xxx, , "/\;:_", ',') /*also allow other chars as comma*/

              do  while xxx\=;  parse var  xxx    r  c  marks  ','  xxx
                  do  while marks\=;                    _=@.r.c
                  parse var  marks  x  marks
                  if datatype(x,'N')   then  x=x/1        /*normalize X*/
                  minr=min(minr,r);    maxr=max(maxr,r)
                  minc=min(minc,c);    maxc=max(maxc,c)
                  if x==1   then do;  !r=r;  !c=c;  end   /*start cell.*/
                  if _\== then call err "cell at" r c 'is already occupied with:' _
                  @.r.c=x;   c=c+1;    cells=cells+1      /*assign mark*/
                  if x==.              then iterate       /*hole? Skip.*/
                  if \datatype(x,'W')  then call err 'illegal marker specified:' x
                  minx=min(minx,x);    maxx=max(maxx,x)   /*min & max X*/
                  end   /*while marks¬= */
              end       /*while xxx  ¬= */

call showGrid /* [↓] used for making fast moves*/ Nr = '0 3 0 -3 -2 2 2 -2' /*possible row for the next move.*/ Nc = '3 0 -3 0 2 -2 2 -2' /* " col " " " " */ pMoves=words(Nr) /*the number of possible moves. */

 do i=1  for pMoves; Nr.i=word(Nr,i); Nc.i=word(Nc,i); end /*fast moves*/

if \next(2,!r,!c) then call err 'No solution possible for this Hopido puzzle.' say 'A solution for the Hopido exists.'; say; call showGrid et=format(time('Elapsed'),,2) /*get REXX elapsed time (in secs)*/ if et<.1 then say 'and took less than 1/10 of a second.'

         else say 'and took'   et   "seconds."

exit /*stick a fork in it, we're done.*/ /*──────────────────────────────────ERR subroutine──────────────────────*/ err: say; say '***error!*** (from Hopido): ' arg(1); say; exit 13 /*──────────────────────────────────NEXT subroutine─────────────────────*/ next: procedure expose @. Nr. Nc. cells pMoves; parse arg #,r,c; ##=#+1

        do t=1  for pMoves                      /* [↓]  try some moves.*/
        parse value  r+Nr.t c+Nc.t  with nr nc  /*next move coördinates*/
        if @.nr.nc==.  then do;                 @.nr.nc=#     /*a move.*/
                            if #==cells         then leave    /*last 1?*/
                            if next(##,nr,nc)   then return 1
                            @.nr.nc=.           /*undo the above move. */
                            iterate             /*go & try another move*/
                            end
        if @.nr.nc==#  then do                  /*is this a fill-in ?  */
                            if #==cells         then return 1 /*last 1.*/
                            if next(##,nr,nc)   then return 1 /*fill-in*/
                            end
        end   /*t*/

return 0 /*This ain't working. */ /*──────────────────────────────────SHOWGRID subroutine─────────────────*/ showGrid: if maxr<1 | maxc<1 then call err 'no legal cell was specified.' if minx<1 then call err 'no 1 was specified for the puzzle start' w=length(cells); do r=maxr to minr by -1; _=

                     do c=minc  to maxc;  _=_ right(@.r.c,w);  end  /*c*/
                 say _
                 end   /*r*/

say; return</lang> output when the input is:
1 4 1 \2 3 . . . \3 2 . . . . . \4 1 . . . . . . . \5 1 . . . . . . . \6 2 . . \6 5 . .

     .  .     .  .
  .  .  .  .  .  .  .
  .  .  .  .  .  .  .
     .  .  .  .  .
        .  .  .
           1

A solution for the Hopido exists.

     5 12     4 11
  8 22 25  7 21 24 27
 13 16 19  2 15 18  3
     6  9 23 26 10
       14 17 20
           1

and took less than  1/10  of a second.

Ruby

This solution uses HLPsolver from here <lang ruby>require 'HLPsolver'

ADJACENT = [[-3, 0], [0, -3], [0, 3], [3, 0], [-2, -2], [-2, 2], [2, -2], [2, 2]]

board1 = <<EOS . 0 0 . 0 0 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 . 0 0 0 0 0 . . . 0 0 0 . . . . . 1 . . . EOS t0 = Time.now HLPsolver.new(board1).solve puts " #{Time.now - t0} sec"</lang> Which produces:

Problem:
     0  0     0  0   
  0  0  0  0  0  0  0
  0  0  0  0  0  0  0
     0  0  0  0  0   
        0  0  0      
           1         

Solution:
     3 12     4 11   
  8 18 21  7 17 20  6
 13 24 27 14 23 26 15
     2  9 19  5 10   
       22 25 16      
           1         

 0.001 sec

Tcl

Works with: Tcl version 8.6

<lang tcl>package require Tcl 8.6

oo::class create HopidoSolver {

   variable grid start limit
   constructor {puzzle} {

set grid $puzzle for {set y 0} {$y < [llength $grid]} {incr y} { for {set x 0} {$x < [llength [lindex $grid $y]]} {incr x} { if {[set cell [lindex $grid $y $x]] == 1} { set start [list $y $x] } incr limit [expr {$cell>=0}] } } if {![info exist start]} { return -code error "no starting position found" }

   }
   method moves {} {

return { 0 -3 -2 -2 -2 2 -3 0 3 0

             -2 2        2 2

0 3 }

   }
   method Moves {g r c} {

set valid {} foreach {dr dc} [my moves] { set R [expr {$r + $dr}] set C [expr {$c + $dc}] if {[lindex $g $R $C] == 0} { lappend valid $R $C } } return $valid

   }
   method Solve {g r c v} {

lset g $r $c [incr v] if {$v >= $limit} {return $g} foreach {r c} [my Moves $g $r $c] { return [my Solve $g $r $c $v] } return -code continue

   }
   method solve {} {

while {[incr i]==1} { set grid [my Solve $grid {*}$start 0] return } return -code error "solution not possible"

   }
   method solution {} {return $grid}

}

proc parsePuzzle {str} {

   foreach line [split $str "\n"] {

if {[string trim $line] eq ""} continue lappend rows [lmap {- c} [regexp -all -inline {(.)\s?} $line] { string map {" " -1 "." -1} $c }]

   }
   set len [tcl::mathfunc::max {*}[lmap r $rows {llength $r}]]
   for {set i 0} {$i < [llength $rows]} {incr i} {

while {[llength [lindex $rows $i]] < $len} { lset rows $i end+1 -1 }

   }
   return $rows

} proc showPuzzle {grid name} {

   foreach row $grid {foreach cell $row {incr c [expr {$cell>=0}]}}
   set len [string length $c]
   set u [string repeat "_" $len]
   puts "$name with $c cells"
   foreach row $grid {

puts [format " %s" [join [lmap c $row { format "%*s" $len [if {$c==-1} list elseif {$c==0} {set u} {set c}] }]]]

   }

} set puzzle [parsePuzzle { . 0 0 . 0 0 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 . 0 0 0 0 0 . . . 0 0 0 . . . . . 1 . . . }] showPuzzle $puzzle "Input" HopidoSolver create hop $puzzle hop solve showPuzzle [hop solution] "Output"</lang>

Output:
Input with 27 cells
     __ __    __ __   
  __ __ __ __ __ __ __
  __ __ __ __ __ __ __
     __ __ __ __ __   
        __ __ __      
            1         
Output with 27 cells
      3  6    23  7   
  27 11 14 26 10 13 25
   5 17 20  4 16 19 22
      2  9 12 24  8   
        15 18 21      
            1