随机路径生成算法

9
我想要从矩阵的顶部到底部生成一条随机路径。 FIDDLE 要求:
- 路径可以蜿蜒曲折,但必须连接第1行和最后一行。 - 最终,我希望每个路径片段的颜色都是随机的,但现在可以是统一的(我在下面测试了红色)。 - 从上到下连接的路径是随机生成的。 - 路径片段显然必须连接,并且不应分叉(即,不应该给玩家两个选择的选项,如此处所示)。 - 路径只能从上到下移动(不能向上移动),但可以左右蜿蜒。

enter image description here

我所考虑的:

  • 我不能简单地检查上一行的列是否是路径的一部分,因为这样它会在找到第一个真值时不断生成路径片段。
  • 我不想手动生成路径,因为那需要一个新的矩阵来指定我想要的路径的1和0。而且对于每个“随机”的路径选项,我都必须建立一个新的矩阵。更重要的是,在矩阵中手动生成路径将使矩阵大小的扩展变得更加繁琐......例如,如果我将我的6x6矩阵改为100x100。

所以,是的,简单的方法就是做这个并迭代它:

        var matrixPaths = [
            [0,1,0,0,0,0],
            [0,1,1,1,0,0],
            [0,0,0,1,0,0],
            [0,0,0,1,1,1],
            [0,0,0,0,0,1],
            [0,0,0,0,0,1]
        ];

在左边是空的网格,在右边是它应该生成的内容。

enter image description here

我的想法是先创建网格,然后在每个矩阵条目中添加跨度:
        function createMyGrid() {
            //create 6x6 matrix
            for(var i=1; i<=6; i++) {
                matrix[i] = [];
                for(var j=1; j<=6; j++) {
                    var colorIndex = Math.floor(Math.random() * (color.length - 0) + 0);
                    var $span = $('<span />').attr('class', 'colorSquare').html("[" + i + "][" + j + "]");
                    $("#grid").append($span);
                    matrix[i][j] = $span;
                }
            }
        }

接下来,在第一行随机生成第一个路径片段。然后对于每个后续的行,检查上面是否有路径片段可以连接...然后从那个片段开始生成下一组:

        function createPath() {
            var randomColumn = Math.floor(Math.random() * (matrix[1].length - 0) + 0);
            matrix[1][randomColumn].data('partOfPath', true);
            matrix[1][randomColumn].addClass("red");
            for (var row = 2; row <= 6; row++) {
                for (var col = 1; col <= 6; col++) {
                    if (matrix[row-1][col].data('partOfPath')) { //if block above is partOfPath... add a set of items of random # of columns across
                        addRowPath(row, col);
                    }
                }           
            }
        }

        function addRowPath (row, pathCol) { //need to start offset from that row/col position, 
            var randRowPathLength = Math.floor(Math.random() * (matrix[row].length - 0) + 0);
                for (var col = pathCol; col <= randRowPathLength; col++) {          
                    matrix[row][col].addClass("red");   
                }
        }

到目前为止,它添加了初始步骤,然后是下面的行,但是就停止了。

enter image description here


在这里添加一个 JSFiddle 会非常有用,可以让人们更容易地回答问题。 - Ingo Bürk
@IngoBürk 已添加 Fiddle - user3871
你做了什么调试?几个console.logs应该能显示出它停在哪里以及那时变量的值。添加一个断点并逐步执行代码应该可以确保解决问题。 - user1106925
@Squint 嗯,更多的是我不知道该从哪里继续了。 - user3871
你的意思是什么?很明显你的代码中有一些条件没有达到你的期望,所以要在各个关键点上加入 console.log() 调用来查看值和路径为什么会结束,或者在代码中设置断点并逐行调试,直到找出问题。这就是调试。 - user1106925
@squint 我已经更新了我的问题。我已经意识到了逻辑上的缺陷-我认为我不能简单地检查上一行是否有路径块,然后从那里开始连接,因为一旦它找到任何路径块,它就会开始添加行块,这不会生成我正在寻找的“弯曲”的路径。 - user3871
5个回答

3

需要指出的一件事是,你应该将数组的范围改为从零开始,或者修复生成数字的范围。目前,它产生了一个具有无效索引的范围。由于你的问题不是关注这个问题,所以我把它留下了。

这会产生一条曲折的路径,可以一直向下走,然后再返回,直到它没有有效的移动或到达屏幕底部。这里是一个 JFIDDLE 的链接:http://jsfiddle.net/j6gkzbr5/1/

var colorEn = ["RoyalBlue", "LawnGreen", "red", "orange", "yellow", "black", "white", "MediumOrchid"];
var $color = "null";
var matrix = [];
var list = []

$(document).ready(function () {

    createMyGrid();
    createPath();

});

function createPath() {
    var row = 1;
    var randomColumn = Math.floor(Math.random() * (matrix[1].length - 0) + 0);

    matrix[1][randomColumn].data('partOfPath', true);
    matrix[1][randomColumn].addClass("red");

   //Main loop, runs until we reach the final row.
    do {
        CreateNewFrontier(row, randomColumn);
        //list now contains a list of all legal moves to make

        var randomNumber = Math.floor((Math.random() * (list.length)));
        //Select one at random

        row = list[randomNumber][0];
        randomColumn = list[randomNumber][1];

        //And mark it
        MarkPath(row, randomColumn);
    } while (row < 6)//This should be matrix.length - 1
}

//This function clears out the previous list of valid moves and generates a new one.

function CreateNewFrontier(row, column) {
    list = [];

    //Check if each cardinal direction falls within the bounds of the matrix.
    //If it does pass that node to the addtofrontier function for further consideration.

    //if (row - 1 >= 1) AddToFrontier(row - 1, column);
    //Commented out, as we are no longer considering paths that lead up.
    if (column + 1 < matrix[row].length) AddToFrontier(row, column + 1);
    if (row + 1 < matrix.length) AddToFrontier(row + 1, column);
    if (column - 1 >= 1) AddToFrontier(row, column - 1);
}

//This function checks to make sure nodes to be added to the frontier don't violate any restrictions
//Mainly, per the question description, no node can touch more than 2 nodes on any cardinal direction

function AddToFrontier(row, column) {
    //First we make sure this node is not already on the path. No backtracking, as it would violate the condition that there be only one continuous path.

    if (matrix[row][column].data('partOfPath') != true) {

        //Now we need to make sure that this node currently only has 1 neighbor at the most that
       //is already on a path, otherwise we will violate the single path condition.
       //So count up all marked neighbors...
        var markedNeighbors = 0;
        if (row - 1 >= 1 && !IsNotMarked(row - 1, column)) {
            markedNeighbors++;
        }
        if (column + 1 < matrix[row].length && !IsNotMarked(row, column + 1)) {
            markedNeighbors++;
        }
        if (row + 1 < matrix.length && !IsNotMarked(row + 1, column)) {
            markedNeighbors++;
        }
        if (column - 1 >= 1 && !IsNotMarked(row, column - 1)) {
            markedNeighbors++;
        }

        //...and if there is only 1, we add the node to the list of possible moves.
        if (markedNeighbors < 2) {
            var index = list.length;
            list[index] = [];
            list[index][0] = row;
            list[index][1] = column;
        }
    }
}

//Helper function to mark a node as visited.
function MarkPath(row, column) {
    matrix[row][column].data('partOfPath', true);
    matrix[row][column].addClass("red");
}

//Helper function to check if a path is marked. 
//It looks a little odd because i'm not that familiar with JS and wasn't sure how an uninitialized     //variable would return, so i decided to return the opposite.

function IsNotMarked(row, column) {
    if (row < 1 || row >= matrix.length) return true;
    if (column < 1 || column >= matrix[row].length) return true;
    return matrix[row][column].data('partOfPath') != true;
}

function createMyGrid() {
    //create 6x6 matrix
    for (var i = 1; i <= 6; i++) {
        matrix[i] = [];
        for (var j = 1; j <= 6; j++) {
            var colorIndex = Math.floor(Math.random() * (colorEn.length - 0) + 0);
            var $span = $('<span />').attr('class', 'colorSquare').html("[" + i + "][" + j + "]");
            $("#grid").append($span);
            matrix[i][j] = $span;
        }
    }
}

function log(word) {
    console.log(word);
}

1
完全不生成路径的原因很可能是我在帖子开头提到的那个问题。有时候初始随机数生成的范围不在1到6之间。关于有时不能从顶部到底部,这可能是所有可能合法移动都用完了的一个例子。你链接的那张图片就是一个例子,它没有更多的移动可以做而不违反你的规定。如果想要确保每次运行都产生向下的路径,你需要限制它不向上移动,或者用不同的方式生成路径。我会对代码进行注释。 - Taekahn
好的,我已经添加了一个规则,它不能向上移动...它只能从上到下、左右移动,并且必须从上到下连接。 - user3871
1
在这种情况下,将“if (row - 1 >= 1) AddToFrontier(row - 1, column);”注释掉,它将不再考虑“向上”作为可能的移动。我会在我的答案代码中处理这个问题。 - Taekahn
谢谢。将循环更改为 for (var i = 0; i <6; i++) { 以解决非生成路径问题,现在从第二行开始路径。 - user3871
这是一个更新的 Fiddler,它将矩阵数组的范围更改为 0-5 x 0-5。需要进行几个编辑,您可以执行差异以了解更改情况。http://jsfiddle.net/j6gkzbr5/2/ - Taekahn
显示剩余2条评论

1
这是我解决这个问题的方法。
1)明确定义什么是有效路径的规则,即将矩阵映射到布尔值的函数。从你的问题中,我不清楚什么是有效路径,而且我也不确定你是否清楚。
2)重复生成随机图块的排列,直到生成的排列符合有效路径的规则。在当前处理器上,对于6x6,这将快速工作,但对于更大的正方形(例如100x100),需要更长的时间。
算法解决方案可能是可行的,可以节省处理时间,但这是开发时间和代码简洁性的快速胜利。还具有产生“均匀分布”的优点,即每条路径被生成的概率都相同。
例如规则集可能是:
当且仅当矩阵满足以下所有条件时,路径才有效:
1)除了两个端点之外,矩阵中的每个图块必须与四个可能的方向N、S、E、W中的恰好两个图块相邻。
2)矩阵中的两个图块(端点)必须与四个可能的方向N、S、E、W中的恰好一个图块相邻。

3) 一端瓦片必须位于顶行,另一端瓦片必须位于底行


1
我会使用一种方法生成迷宫,使得从每个点都可以到达其他所有点。递归回溯法足够好且易于实现。
在矩阵上生成迷宫后,您需要找到从起点到终点的路径(可以是任何点,但您必须记住每隔一个点可能是墙)。您可以在帖子底部的链接中找到许多不同的算法。使用递归回溯法生成的迷宫保证了您想要的连接行/列(就像我说的,每个点都与其他每个点相连),但并非每个位置都可以成为路径(因为需要放置墙壁)。
您找到的从起点到终点的路径符合您的要求,因此您只需删除不属于该路径的所有内容。
如果您创建自己的堆栈,可以轻松处理大约2Kx2K大小的矩阵。甚至更大。
有关迷宫相关的更多阅读,我建议访问这个网站http://www.astrolog.org/labyrnth/algrithm.htm
你可以对迷宫生成器进行一些更改,使其可以在任何可能的方块上都有起点/终点,而不仅仅是每两个方块中的一个。我能想到的最简单的解决方案是在50%的情况下将偏移量设置为1来生成迷宫。

0

如果你一步一步地走,而向上的方向被排除在外,那么在我看来,你只需要跟踪最后两个移动(除了确定第一步时)。然后算法可以遵循一组规则,从可用选项中随机选择下一个瓷砖。

例如:

If the last two movements were West then South,
  East would not be available.

If the last two movements were East then East,
  Southeast would not be available.

And so on.

-3

在这里 jsfiddle

创建任意大小的行和列

function yolYap(satir,sutun=0){ //My Create Path Function
if(ilk){ //İlk seçim: First selection
    ilk=false;
    sutun = Math.floor((Math.random()*6)+1);
    matrix[satir][sutun].data('p', true);
    matrix[satir][sutun].addClass("red");
    yolYap(satir,sutun);
}else{
    var xy=[];
    var solust=satir>1&&sutun>1?!matrix[satir-1][sutun-1].data("p"):true
    var sol=sutun>1?!matrix[satir][sutun-1].data("p"):false
    if(sol && solust)//{
        xy.push([satir,sutun-1]);//alert("<-");}

    var sagust=satir>1&&sutun<matrix[1].length-1?!matrix[satir-1][sutun+1].data("p"):true; 
    var sag=sutun<matrix[1].length-1?!matrix[satir][sutun+1].data("p"):false;
    if(sag && sagust)//{
        xy.push([satir,sutun+1]);//alert("->");}
    //satir>-1?alert("büyük"):alert("küçük");

    xy.push([satir+1,sutun]);
    var sec = Math.floor((Math.random()*102)+1)%xy.length;
    matrix[xy[sec][0]][xy[sec][1]].data("p", true);
    matrix[xy[sec][0]][xy[sec][1]].addClass("red");
    //alert(xy.join(" - "));
    if(xy[sec][0]<matrix.length-1)
        yolYap(xy[sec][0],xy[sec][1]);
    else
        alert("Finish");
}

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接