2016 Update

This commit is contained in:
Tina Müller 2016-12-05 22:15:40 +01:00
parent 948b86eafa
commit dcf5d15da3
7965 changed files with 139854 additions and 31002 deletions

View file

@ -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]]

View 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);
}

View file

@ -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)

View file

@ -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

View 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();
}
}
}

View file

@ -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

View file

@ -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