How can I fill in a square 2D array with numbers so that a (random) path of consecutive numbers in ascending order is created from 1 to (edge length)
2
?
I'm trying to write a Hidato (aka Hidoku) generator in JavaScript. It's not necessarily the best language to write it in, but that's what I'm using currently. The game board is initially only partially filled. The only guaranteed numbers shown are the first and last numbers in the path. The idea of the game is to create a single path of numbers through the grid (vertically, horizontally or diagonally) so that there is a consecutive ascending chain of numbers. The chain can overlap due to the diagonals being taken into account.
I'm stuck on the board generation part. A valid grid must have a (single, non-branching) path of consecutive numbers going from 1
to (grid size)
2
. I've looked and looked but found nothing that might've helped. Is there an path tracing algorithm that can fill a 2D array with a single path made up of consecutive numbers?
My initial, naive approach was to fill a 2D array with values and swap values until the grid was a valid Hidato puzzle. This would take forever to compute and would be very inefficient, so I scrapped that idea.
My next thought was to use a backtracking path tracer to fill in the grid with consecutive values, however I'm unsure how to implement such a tracer. Generating a path is easy enough (choose a random adjacent cell and move to it until the 2D array is full), but my issue here is the "backtracking-ness" of the algorithm, or some other way to always ensure there is a random path of consecutive numbers throughout the grid. I thought about a maze tracer but that doesn't deal with single paths with no forking or dead ends.
How can I proceed from here? Should I be considering other options than a path tracer or other similar algorithm?
Related questions:
It turns out that the local search algorithm for Hamilton path due to Angluin and Valiant (1977) is pretty good at this, even though there's no proof for non-random graphs. Here's a sample square
99 98 101 103 105 106 129 132 133 140 135 136
97 100 102 104 107 130 131 128 141 134 139 137
95 96 109 108 112 122 127 126 125 142 143 138
80 94 110 111 121 113 123 124 40 39 36 144
79 81 93 120 116 115 114 48 41 38 37 35
78 82 92 90 119 117 47 46 49 42 33 34
77 83 84 91 89 118 45 58 43 50 32 31
76 1 85 87 88 60 59 44 57 51 30 28
75 2 86 4 6 63 61 54 52 56 29 27
73 74 3 7 5 64 62 53 55 22 24 26
72 69 67 8 65 11 12 14 15 23 21 25
70 71 68 66 9 10 13 16 17 18 19 20
and the (somewhat hastily written) Java code that made it.
import java.util.*;
public class AV {
public static void main(String[] args) {
// construct an n-by-n grid
int n = 12;
Node[][] node = new Node[n][n];
List<Node> nodes = new ArrayList<Node>();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
nodes.add((node[i][j] = new Node()));
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i >= 1) {
if (j >= 1) {
node[i - 1][j - 1].addEdge(node[i][j]);
}
node[i - 1][j].addEdge(node[i][j]);
if (j < n - 1) {
node[i - 1][j + 1].addEdge(node[i][j]);
}
}
if (j >= 1) {
node[i][j - 1].addEdge(node[i][j]);
}
}
}
findPath(nodes);
labelPath(nodes);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.printf("%4d", node[i][j].label);
}
System.out.println();
}
}
private static void findPath(List<Node> nodes) {
for (Node node : nodes) {
node.isOnPath = false;
}
Random random = new Random();
Node sink = nodes.get(random.nextInt(nodes.size()));
sink.isOnPath = true;
int isNotOnPathCount = nodes.size() - 1;
while (isNotOnPathCount > 0) {
sink.pathOut = sink.out.get(random.nextInt(sink.out.size()));
sink = sink.pathOut.head;
if (sink.isOnPath) {
// rotate
sink = sink.pathOut.head;
Arc reverse = null;
Node node = sink;
do {
Arc temp = node.pathOut;
node.pathOut = reverse;
reverse = temp.reverse;
node = temp.head;
} while (node != sink);
} else {
// extend
sink.isOnPath = true;
isNotOnPathCount--;
}
}
}
private static void labelPath(Collection<Node> nodes) {
for (Node node : nodes) {
node.isSource = true;
}
for (Node node : nodes) {
if (node.pathOut != null) {
node.pathOut.head.isSource = false;
}
}
Node source = null;
for (Node node : nodes) {
if (node.isSource) {
source = node;
break;
}
}
int count = 0;
while (true) {
source.label = ++count;
if (source.pathOut == null) {
break;
}
source = source.pathOut.head;
}
}
}
class Node {
public final List<Arc> out = new ArrayList<Arc>();
public boolean isOnPath;
public Arc pathOut;
public boolean isSource;
public int label;
public void addEdge(Node that) {
Arc arc = new Arc(this, that);
this.out.add(arc.reverse);
that.out.add(arc);
}
}
class Arc {
public final Node head;
public final Arc reverse;
private Arc(Node head, Arc reverse) {
this.head = head;
this.reverse = reverse;
}
public Arc(Node head, Node tail) {
this.head = head;
this.reverse = new Arc(tail, this);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With