Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fill 2D grid with single path

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:

  • Routing “paths” through a rectangular array
  • Algorithm to find a random Hamiltonian path in a grid?
like image 779
Bojangles Avatar asked Apr 09 '13 09:04

Bojangles


1 Answers

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);
    }
}
like image 156
David Eisenstat Avatar answered Nov 03 '22 00:11

David Eisenstat