2016 Update
This commit is contained in:
parent
948b86eafa
commit
dcf5d15da3
7965 changed files with 139854 additions and 31002 deletions
|
|
@ -2,7 +2,7 @@ Hopido puzzles are similar to [[Solve a Hidato puzzle | Hidato]]. The most impor
|
|||
|
||||
"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.
|
||||
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:
|
||||
|
||||
|
|
@ -15,8 +15,9 @@ Example:
|
|||
|
||||
Extra credits are available for other interesting designs.
|
||||
|
||||
Realated Tasks:
|
||||
Related Tasks:
|
||||
* [[Solve a Hidato puzzle]]
|
||||
* [[Solve a Holy Knight's tour]]
|
||||
* [[Solve a Numbrix puzzle]]
|
||||
* [[Solve the no connection puzzle]]
|
||||
* [[Knight's tour]]
|
||||
|
|
|
|||
114
Task/Solve-a-Hopido-puzzle/D/solve-a-hopido-puzzle.d
Normal file
114
Task/Solve-a-Hopido-puzzle/D/solve-a-hopido-puzzle.d
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# require HLPsolver
|
||||
|
||||
adjacent = [{-3, 0}, {0, -3}, {0, 3}, {3, 0}, {-2, -2}, {-2, 2}, {2, -2}, {2, 2}]
|
||||
|
||||
board = """
|
||||
. 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 . . .
|
||||
"""
|
||||
HLPsolver.solve(board, adjacent)
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
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
|
||||
109
Task/Solve-a-Hopido-puzzle/Java/solve-a-hopido-puzzle.java
Normal file
109
Task/Solve-a-Hopido-puzzle/Java/solve-a-hopido-puzzle.java
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import java.util.*;
|
||||
|
||||
public class Hopido {
|
||||
|
||||
final static String[] board = {
|
||||
".00.00.",
|
||||
"0000000",
|
||||
"0000000",
|
||||
".00000.",
|
||||
"..000..",
|
||||
"...0..."};
|
||||
|
||||
final static int[][] moves = {{-3, 0}, {0, 3}, {3, 0}, {0, -3},
|
||||
{2, 2}, {2, -2}, {-2, 2}, {-2, -2}};
|
||||
static int[][] grid;
|
||||
static int totalToFill;
|
||||
|
||||
public static void main(String[] args) {
|
||||
int nRows = board.length + 6;
|
||||
int nCols = board[0].length() + 6;
|
||||
|
||||
grid = new int[nRows][nCols];
|
||||
|
||||
for (int r = 0; r < nRows; r++) {
|
||||
Arrays.fill(grid[r], -1);
|
||||
for (int c = 3; c < nCols - 3; c++)
|
||||
if (r >= 3 && r < nRows - 3) {
|
||||
if (board[r - 3].charAt(c - 3) == '0') {
|
||||
grid[r][c] = 0;
|
||||
totalToFill++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int pos = -1, r, c;
|
||||
do {
|
||||
do {
|
||||
pos++;
|
||||
r = pos / nCols;
|
||||
c = pos % nCols;
|
||||
} while (grid[r][c] == -1);
|
||||
|
||||
grid[r][c] = 1;
|
||||
if (solve(r, c, 2))
|
||||
break;
|
||||
grid[r][c] = 0;
|
||||
|
||||
} while (pos < nRows * nCols);
|
||||
|
||||
printResult();
|
||||
}
|
||||
|
||||
static boolean solve(int r, int c, int count) {
|
||||
if (count > totalToFill)
|
||||
return true;
|
||||
|
||||
List<int[]> nbrs = neighbors(r, c);
|
||||
|
||||
if (nbrs.isEmpty() && count != totalToFill)
|
||||
return false;
|
||||
|
||||
Collections.sort(nbrs, (a, b) -> a[2] - b[2]);
|
||||
|
||||
for (int[] nb : nbrs) {
|
||||
r = nb[0];
|
||||
c = nb[1];
|
||||
grid[r][c] = count;
|
||||
if (solve(r, c, count + 1))
|
||||
return true;
|
||||
grid[r][c] = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static List<int[]> neighbors(int r, int c) {
|
||||
List<int[]> nbrs = new ArrayList<>();
|
||||
|
||||
for (int[] m : moves) {
|
||||
int x = m[0];
|
||||
int y = m[1];
|
||||
if (grid[r + y][c + x] == 0) {
|
||||
int num = countNeighbors(r + y, c + x) - 1;
|
||||
nbrs.add(new int[]{r + y, c + x, num});
|
||||
}
|
||||
}
|
||||
return nbrs;
|
||||
}
|
||||
|
||||
static int countNeighbors(int r, int c) {
|
||||
int num = 0;
|
||||
for (int[] m : moves)
|
||||
if (grid[r + m[1]][c + m[0]] == 0)
|
||||
num++;
|
||||
return num;
|
||||
}
|
||||
|
||||
static void printResult() {
|
||||
for (int[] row : grid) {
|
||||
for (int i : row) {
|
||||
if (i == -1)
|
||||
System.out.printf("%2s ", ' ');
|
||||
else
|
||||
System.out.printf("%2d ", i);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,10 @@ my @adjacent = [3, 0],
|
|||
[-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
|
||||
|
|
|
|||
|
|
@ -1,57 +1,56 @@
|
|||
/*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*/
|
||||
/*REXX program solves a Hopido puzzle, it also displays the puzzle and the solution. */
|
||||
call time 'Reset' /*reset the REXX elapsed timer to zero.*/
|
||||
maxR=0; maxC=0; maxX=0; minR=9e9; minC=9e9; minX=9e9; cells=0; @.=
|
||||
parse arg xxx /*get the cell definitions from the CL.*/
|
||||
xxx=translate(xxx, , "/\;:_", ',') /*also allow other characters as comma.*/
|
||||
|
||||
do while xxx\=''; parse var xxx r c marks ',' xxx
|
||||
do while marks\=''; _=@.r.c
|
||||
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,'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 /*the START cell. */
|
||||
if _\=='' then call err "cell at" r c 'is already occupied with:' _
|
||||
@.r.c=x; c=c+1; cells=cells+1 /*assign a mark. */
|
||||
if x==. then iterate /*is a hole? Skip*/
|
||||
if \datatype(x,'W') then call err 'illegal marker specified:' x
|
||||
minx=min(minx,x); maxx=max(maxx,x) /*min & max X*/
|
||||
minX=min(minX,x); maxX=max(maxX,x) /*min and 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─────────────────────*/
|
||||
call show /* [↓] is 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' /* " column " " " " */
|
||||
pMoves=words(Nr) /*the number of possible moves. */
|
||||
do i=1 for pMoves; Nr.i=word(Nr, i); Nc.i=word(Nc,i); end /*i*/
|
||||
if \next(2,!r,!c) then call err 'No solution possible for this Hopido puzzle.'
|
||||
say 'A solution for the Hopido exists.'; say; call show
|
||||
etime= format(time('Elapsed'), , 2) /*obtain the elapsed time (in seconds).*/
|
||||
if etime<.1 then say 'and took less than 1/10 of a second.'
|
||||
else say 'and took' etime "seconds."
|
||||
exit /*stick a fork in it, we're all done. */
|
||||
/*──────────────────────────────────────────────────────────────────────────────────────*/
|
||||
err: say; say '***error*** (from Hopido): ' arg(1); say; exit 13
|
||||
/*──────────────────────────────────────────────────────────────────────────────────────*/
|
||||
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
|
||||
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=# /*let's try this move. */
|
||||
if #==cells then leave /*is this the last move?*/
|
||||
if next(##,nr,nc) then return 1
|
||||
@.nr.nc=. /*undo the above move. */
|
||||
iterate /*go & try another move.*/
|
||||
end
|
||||
if @.nr.nc==# then do /*this a fill-in move ? */
|
||||
if #==cells then return 1 /*this is the last move.*/
|
||||
if next(##,nr,nc) then return 1 /*a fill-in move. */
|
||||
end
|
||||
end /*t*/
|
||||
return 0 /*This ain't working. */
|
||||
/*──────────────────────────────────────────────────────────────────────────────────────*/
|
||||
show: 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=max(2,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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue