生成填字游戏的算法 [已关闭]

Algorithm to generate a crossword [closed]

提问人:nickf 提问时间:6/3/2009 最后编辑:nickf 更新时间:4/20/2020 访问量:111098

问:

2年前关闭。
锁定。这个问题及其答案被锁定,因为这个问题偏离了主题,但具有历史意义。它目前不接受新的答案或互动。

给定一个单词列表,您将如何将它们排列成填字游戏网格?

它不必像一个“适当的”填字游戏,它是对称的或类似的东西:基本上只是输出每个单词的起始位置和方向。

算法 填字游戏 语言不可知论

评论


答:

2赞 eipipuz 6/3/2009 #1

我会得到每个单词使用的每个字母的索引,以了解可能的交叉。然后我会选择最大的单词并使用它作为基础。选择下一个大的并越过它。冲洗并重复。这可能是一个NP问题。

另一个想法是创建一种遗传算法,其中强度指标是您可以在网格中输入多少个单词。

我发现最困难的部分是什么时候知道某个列表不可能被跨越。

评论

1赞 Adrian McCarthy 6/4/2009
我也在考虑遗传算法。适应度函数可以是单词在网格中的紧密程度。
20赞 paxdiablo 6/3/2009 #2

实际上,大约十年前,我写了一个填字游戏生成程序(它很神秘,但同样的规则也适用于普通的填字游戏)。

它有一个单词列表(和相关线索),存储在一个文件中,按迄今为止的用法降序排序(因此较少使用的单词位于文件的顶部)。一个模板,基本上是代表黑色和自由方块的位掩码,是从客户端提供的池中随机选择的。

然后,对于拼图中的每个不完整的单词(基本上找到第一个空白方块,看看右边的方块(横字)或下面的方块(下字)是否也是空白的),对文件进行搜索,寻找第一个合适的单词,同时考虑到该单词中已有的字母。如果没有合适的单词,您只需将整个单词标记为不完整并继续前进。

最后是一些未完成的单词,编译器必须填写这些单词(如果需要,还可以将单词和线索添加到文件中)。如果他们想不出任何想法,他们可以手动编辑填字游戏以更改约束,或者只是要求完全重新生成。

一旦单词/线索文件达到一定大小(并且每天为该客户添加 50-100 条线索),很少有必须为每个填字游戏完成两到三个以上的手动修复的情况。

评论

0赞 nickf 6/3/2009
在我的情况下,这实际上对我没有帮助,因为我只有一个大约 6-12 个单词的列表。我的更像是用户的学习练习,而不是单词拼图。无论如何,+1 对于有趣的算法!
1赞 dmckee --- ex-moderator kitten 6/3/2009
不错的描述。我过去想过几次,但从未尝试过。现在来问一个神奇的问题:它的效果如何?只是为了稀疏的谜题,还是为了密集的谜题(如在论文中)?密集的谜题需要多少线索?
1赞 paxdiablo 6/4/2009
@dmckee,那是很久以前的事了,但凭记忆,即使是密集的谜题也相当不错。许多都是在没有干预的情况下完成的,但你仍然会得到五分之一,需要添加一两个额外的单词。我们谈论的是文件中的数千个单词。毫无疑问,回溯可能会有所帮助,但对于客户来说,拒绝(例如)5个未完成的单词比担心试图提出额外的线索要容易得多。五大约是我所看到的未完成的单词的外部限制。
5赞 Eric 6/3/2009 #3

我会生成两个数字:长度和拼字游戏分数。假设拼字游戏分数低意味着它更容易加入(低分数 = 大量常用字母)。按长度降序和拼字游戏分数升序对列表进行排序。

接下来,只需向下看列表。如果单词没有与现有单词交叉(分别按长度和拼字游戏分数检查每个单词),则将其放入队列中,并检查下一个单词。

冲洗并重复,这应该会生成一个填字游戏。

当然,我很确定这是 O(n!),它不能保证为您完成填字游戏,但也许有人可以改进它。

67赞 nickf 6/20/2009 #4

我想出了一个解决方案,它可能不是最有效的,但它足够有效。基本上:

  1. 按长度对所有单词进行降序排序。
  2. 把第一个字放在黑板上。
  3. 拿下一个词。
  4. 搜索板上已有的所有单词,看看是否有任何可能的交叉点(任何常用字母)与该单词。
  5. 如果该单词可能存在位置,请遍历板上的所有单词,并检查新单词是否干扰。
  6. 如果这个词没有破坏板子,那么把它放在那里并转到步骤 3,否则,继续搜索一个地方(步骤 4)。
  7. 继续此循环,直到所有单词都已放置或无法放置。

这使得填字游戏有效,但通常很差。我对上面的基本配方进行了一些更改,以得出更好的结果。

  • 在生成填字游戏结束时,根据放置的单词数量(越多越好)、棋盘的大小(越小越好)以及高度和宽度之间的比率(越接近 1 越好)给它打分。生成一些填字游戏,然后比较它们的分数并选择最好的填字游戏。
    • 我决定在任意的时间内创建尽可能多的填字游戏,而不是运行任意数量的迭代。如果你只有一个小单词列表,那么你会在 5 秒内得到几十个可能的填字游戏。较大的填字游戏可能只能从 5-6 种可能性中选择。
  • 放置一个新单词时,不要在找到可接受的位置后立即放置它,而是根据它增加网格大小的程度和交叉点的数量给该单词位置打分(理想情况下,您希望每个单词被 2-3 个其他单词交叉)。跟踪所有位置及其分数,然后选择最佳位置。

评论

7赞 user31586 7/24/2009
在我们说话的时候,我碰巧正在编写这个程序,这也是我选择的相同的算法。对于少量单词(10 个或更少),程序可以毫不费力地在几毫秒内计算出所有可能的解决方案。不过,算法是指数级的。简单的部分是编写基本算法,该算法通过所有可能的组合进行暴力破解。困难的部分是你需要的十几个“捷径”,以防止程序尝试所有死胡同的解决方案。
2赞 George Armhold 9/22/2009
"5. ...并检查新单词是否干扰“ 您如何解释新单词与现有单词放在一起的情况,这会在它有相邻方格的地方产生乱码?例如:LEMON ERASE 如果“LE”、“ER”和“MA”等不是列表中的单词,则这是错误的。另一方面,完全拒绝这样的邻接关系可能会丢弃非常好的网格,例如:W LEMON ERASE NEXUS T T
4赞 nickf 9/22/2009
@Kaffeine,是的,我知道你的意思 - 我不得不抛弃这些选项,因为即使它们可以创建非常好的网格,也很难检查(阅读:我不会被打扰),而且很可能只是干扰。
0赞 Brainstorming 7/3/2021
在这里实现了基本相同的方法:colab.research.google.com/drive/......
9赞 Yogi 6/20/2009 #5

为什么不直接使用随机概率方法开始呢?从一个单词开始,然后反复随机选择一个单词,并尝试将其融入拼图的当前状态,而不会破坏对大小等的限制。如果你失败了,就重新开始。

您会惊讶于像这样的蒙特卡洛方法的有效频率。

评论

2赞 ChatGPT 4/13/2012
是的,这就是我选择的方法。你不必尝试变得非常聪明。按单词从最长到最短的顺序排列。在一个循环中,选择一个随机单元格(列和行坐标)并将该单词放在板上进行测试,看看它是否从末尾跑出或干扰另一个单词(在将单词写入网格之前,请检查每个单元格是否为空,或者如果它有一个字母,该字母与您尝试编写的字母匹配)。还有一些其他的逻辑可以检查边界和东西。我用蛮力生成一堆越来越小的网格,然后根据相交的单词对最小的网格进行排名。
24赞 Bryan 4/12/2010 #6

我最近刚刚用 Python 编写了自己的内容。你可以在这里找到它:http://bryanhelmig.com/python-crossword-puzzle-generator/。它不会创建密集的纽约时报风格的填字游戏,而是您可能在儿童益智书中找到的填字游戏风格。

与我发现的一些算法不同,这些算法实现了一些随机的蛮力方法来放置单词,就像一些人建议的那样,我试图在单词放置时实现一种稍微聪明的蛮力方法。这是我的过程:

  1. 创建任意大小的网格和单词列表。
  2. 随机排列单词列表,然后按最长到最短的顺序对单词进行排序。
  3. 将第一个也是最长的单词放在左上角最上角的位置 1,1(垂直或水平)。
  4. 移动到下一个单词,循环遍历单词中的每个字母和网格中的每个单元格,寻找字母到字母的匹配。
  5. 找到匹配项后,只需将该位置添加到该单词的建议坐标列表中即可。
  6. 循环使用建议的坐标列表,并根据单词交叉的其他单词数量对单词位置进行“评分”。分数为 0 表示位置不正确(与现有单词相邻)或没有单词交叉。
  7. 返回步骤#4,直到单词列表用尽。可选的第二道关口。
  8. 我们现在应该有一个填字游戏,但由于一些随机放置,质量可能会被击中或错过。因此,我们缓冲此填字游戏并返回步骤#2。如果下一个填字游戏在黑板上放置了更多单词,它将替换缓冲区中的填字游戏。这是有时间限制的(在 x 秒内找到最好的填字游戏)。

到最后,你有一个像样的填字游戏或单词搜索游戏,因为它们差不多。它往往运行得相当好,但如果您有任何改进建议,请告诉我。更大的网格运行速度呈指数级增长;线性更大的单词列表。单词列表越大,获得更好单词位置数字的机会也要高得多。

评论

0赞 Karl Adler 3/27/2016
@Neil N:对于其他单词来说,可能更好的字母匹配可能性。也许也是一种按每个单词包含的不同字母计数进行排序的方法,这大多会导致相同的结果。
0赞 Lynn 4/13/2019
@NeilN Python 是稳定的,这意味着(例如)简单地按长度对字母顺序的单词列表进行排序将使所有 8 个字母的单词按字母顺序排序。array.sort(key=f)
4赞 Michael A 10/11/2019
@Bryan,您的网站链接对我不起作用,主域名只是重定向到 Twitter。您是否有更新的代码链接?
2赞 lvictorino 4/1/2020
这是(显然)布莱恩生成器的克隆:github.com/jeremy886/crossword_helmig
3赞 Jake 9/23/2010 #7

我一直在思考这个问题。我的感觉是,要创建一个真正密集的填字游戏,你不能指望你有限的单词列表就足够了。因此,您可能希望获取字典并将其放入“trie”数据结构中。这将使您能够轻松找到填充剩余空格的单词。在尝试中,实现一个遍历是相当有效的,比如说,给你所有形式的“c?t”的单词。

所以,我的总体想法是:创建某种相对蛮力的方法,就像这里描述的那样,创建一个低密度的十字架,并用字典单词填补空白。

如果其他人采取了这种方法,请告诉我。

17赞 thiagolr 7/22/2013 #8

该算法可在 50 秒内创建 60 个密集的 6x9 箭头填字游戏。它使用一个单词数据库(带有单词+提示)和一个板数据库(带有预配置的板)。

1) Search for all starting cells (the ones with an arrow), store their size and directions
2) Loop through all starting cells
2.1) Search a word
2.1.1) Check if it was not already used
2.1.2) Check if it fits
2.2) Add the word to the board
3) Check if all cells were filled

更大的单词数据库大大缩短了生成时间,并且某些类型的板更难填充!较大的板子需要更多时间才能正确填充!


例:

预配置的 6x9 板:

(# 表示一个单元格中的一个尖端,% 表示一个单元格中有两个尖端,箭头未显示)

# - # # - % # - # 
- - - - - - - - - 
# - - - - - # - - 
% - - # - # - - - 
% - - - - - % - - 
- - - - - - - - - 

生成的 6x9 板:

# C # # P % # O # 
S A T E L L I T E 
# N I N E S # T A 
% A B # A # G A S 
% D E N S E % W E 
C A T H E D R A L 

提示 [行,列]:

[1,0] SATELLITE: Used for weather forecast
[5,0] CATHEDRAL: The principal church of a city
[0,1] CANADA: Country on USA's northern border
[0,4] PLEASE: A polite way to ask things
[0,7] OTTAWA: Canada's capital
[1,2] TIBET: Dalai Lama's region
[1,8] EASEL: A tripod used to put a painting
[2,1] NINES: Dressed up to (?)
[4,1] DENSE: Thick; impenetrable
[3,6] GAS: Type of fuel
[1,5] LS: Lori Singer, american actress
[2,7] TA: Teaching assistant (abbr.)
[3,1] AB: A blood type
[4,3] NH: New Hampshire (abbr.)
[4,5] ED: (?) Harris, american actor
[4,7] WE: The first person of plural (Grammar)
7赞 FascistDonut 3/8/2014 #9

以下是一些基于 nickf 的答案和 Bryan 的 Python 代码的 JavaScript 代码。只是在 js 中发布它以防其他人需要它。

function board(cols, rows) { //instantiator object for making gameboards
this.cols = cols;
this.rows = rows;
var activeWordList = []; //keeps array of words actually placed in board
var acrossCount = 0;
var downCount = 0;

var grid = new Array(cols); //create 2 dimensional array for letter grid
for (var i = 0; i < rows; i++) {
    grid[i] = new Array(rows);
}

for (var x = 0; x < cols; x++) {
    for (var y = 0; y < rows; y++) {
        grid[x][y] = {};
        grid[x][y].targetChar = EMPTYCHAR; //target character, hidden
        grid[x][y].indexDisplay = ''; //used to display index number of word start
        grid[x][y].value = '-'; //actual current letter shown on board
    }
}

function suggestCoords(word) { //search for potential cross placement locations
    var c = '';
    coordCount = [];
    coordCount = 0;
    for (i = 0; i < word.length; i++) { //cycle through each character of the word
        for (x = 0; x < GRID_HEIGHT; x++) {
            for (y = 0; y < GRID_WIDTH; y++) {
                c = word[i];
                if (grid[x][y].targetChar == c) { //check for letter match in cell
                    if (x - i + 1> 0 && x - i + word.length-1 < GRID_HEIGHT) { //would fit vertically?
                        coordList[coordCount] = {};
                        coordList[coordCount].x = x - i;
                        coordList[coordCount].y = y;
                        coordList[coordCount].score = 0;
                        coordList[coordCount].vertical = true;
                        coordCount++;
                    }

                    if (y - i + 1 > 0 && y - i + word.length-1 < GRID_WIDTH) { //would fit horizontally?
                        coordList[coordCount] = {};
                        coordList[coordCount].x = x;
                        coordList[coordCount].y = y - i;
                        coordList[coordCount].score = 0;
                        coordList[coordCount].vertical = false;
                        coordCount++;
                    }
                }
            }
        }
    }
}

function checkFitScore(word, x, y, vertical) {
    var fitScore = 1; //default is 1, 2+ has crosses, 0 is invalid due to collision

    if (vertical) { //vertical checking
        for (i = 0; i < word.length; i++) {
            if (i == 0 && x > 0) { //check for empty space preceeding first character of word if not on edge
                if (grid[x - 1][y].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            } else if (i == word.length && x < GRID_HEIGHT) { //check for empty space after last character of word if not on edge
                 if (grid[x+i+1][y].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            }
            if (x + i < GRID_HEIGHT) {
                if (grid[x + i][y].targetChar == word[i]) { //letter match - aka cross point
                    fitScore += 1;
                } else if (grid[x + i][y].targetChar != EMPTYCHAR) { //letter doesn't match and it isn't empty so there is a collision
                    fitScore = 0;
                    break;
                } else { //verify that there aren't letters on either side of placement if it isn't a crosspoint
                    if (y < GRID_WIDTH - 1) { //check right side if it isn't on the edge
                        if (grid[x + i][y + 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                    if (y > 0) { //check left side if it isn't on the edge
                        if (grid[x + i][y - 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                }
            }

        }

    } else { //horizontal checking
        for (i = 0; i < word.length; i++) {
            if (i == 0 && y > 0) { //check for empty space preceeding first character of word if not on edge
                if (grid[x][y-1].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            } else if (i == word.length - 1 && y + i < GRID_WIDTH -1) { //check for empty space after last character of word if not on edge
                if (grid[x][y + i + 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            }
            if (y + i < GRID_WIDTH) {
                if (grid[x][y + i].targetChar == word[i]) { //letter match - aka cross point
                    fitScore += 1;
                } else if (grid[x][y + i].targetChar != EMPTYCHAR) { //letter doesn't match and it isn't empty so there is a collision
                    fitScore = 0;
                    break;
                } else { //verify that there aren't letters on either side of placement if it isn't a crosspoint
                    if (x < GRID_HEIGHT) { //check top side if it isn't on the edge
                        if (grid[x + 1][y + i].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                    if (x > 0) { //check bottom side if it isn't on the edge
                        if (grid[x - 1][y + i].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                }
            }

        }
    }

    return fitScore;
}

function placeWord(word, clue, x, y, vertical) { //places a new active word on the board

    var wordPlaced = false;

    if (vertical) {
        if (word.length + x < GRID_HEIGHT) {
            for (i = 0; i < word.length; i++) {
                grid[x + i][y].targetChar = word[i];
            }
            wordPlaced = true;
        }
    } else {
        if (word.length + y < GRID_WIDTH) {
            for (i = 0; i < word.length; i++) {
                grid[x][y + i].targetChar = word[i];
            }
            wordPlaced = true;
        }
    }

    if (wordPlaced) {
        var currentIndex = activeWordList.length;
        activeWordList[currentIndex] = {};
        activeWordList[currentIndex].word = word;
        activeWordList[currentIndex].clue = clue;
        activeWordList[currentIndex].x = x;
        activeWordList[currentIndex].y = y;
        activeWordList[currentIndex].vertical = vertical;

        if (activeWordList[currentIndex].vertical) {
            downCount++;
            activeWordList[currentIndex].number = downCount;
        } else {
            acrossCount++;
            activeWordList[currentIndex].number = acrossCount;
        }
    }

}

function isActiveWord(word) {
    if (activeWordList.length > 0) {
        for (var w = 0; w < activeWordList.length; w++) {
            if (word == activeWordList[w].word) {
                //console.log(word + ' in activeWordList');
                return true;
            }
        }
    }
    return false;
}

this.displayGrid = function displayGrid() {

    var rowStr = "";
    for (var x = 0; x < cols; x++) {

        for (var y = 0; y < rows; y++) {
            rowStr += "<td>" + grid[x][y].targetChar + "</td>";
        }
        $('#tempTable').append("<tr>" + rowStr + "</tr>");
        rowStr = "";

    }
    console.log('across ' + acrossCount);
    console.log('down ' + downCount);
}

//for each word in the source array we test where it can fit on the board and then test those locations for validity against other already placed words
this.generateBoard = function generateBoard(seed = 0) {

    var bestScoreIndex = 0;
    var top = 0;
    var fitScore = 0;
    var startTime;

    //manually place the longest word horizontally at 0,0, try others if the generated board is too weak
    placeWord(wordArray[seed].word, wordArray[seed].displayWord, wordArray[seed].clue, 0, 0, false);

    //attempt to fill the rest of the board 
    for (var iy = 0; iy < FIT_ATTEMPTS; iy++) { //usually 2 times is enough for max fill potential
        for (var ix = 1; ix < wordArray.length; ix++) {
            if (!isActiveWord(wordArray[ix].word)) { //only add if not already in the active word list
                topScore = 0;
                bestScoreIndex = 0;

                suggestCoords(wordArray[ix].word); //fills coordList and coordCount
                coordList = shuffleArray(coordList); //adds some randomization

                if (coordList[0]) {
                    for (c = 0; c < coordList.length; c++) { //get the best fit score from the list of possible valid coordinates
                        fitScore = checkFitScore(wordArray[ix].word, coordList[c].x, coordList[c].y, coordList[c].vertical);
                        if (fitScore > topScore) {
                            topScore = fitScore;
                            bestScoreIndex = c;
                        }
                    }
                }

                if (topScore > 1) { //only place a word if it has a fitscore of 2 or higher

                    placeWord(wordArray[ix].word, wordArray[ix].clue, coordList[bestScoreIndex].x, coordList[bestScoreIndex].y, coordList[bestScoreIndex].vertical);
                }
            }

        }
    }
    if(activeWordList.length < wordArray.length/2) { //regenerate board if if less than half the words were placed
        seed++;
        generateBoard(seed);
    }
}
}
function seedBoard() {
    gameboard = new board(GRID_WIDTH, GRID_HEIGHT);
    gameboard.generateBoard();
    gameboard.displayGrid();
}

评论

0赞 whoopdedoo 8/6/2017
word对象架构缺失,请提供wordArray
0赞 FascistDonut 10/19/2017
从字面上看,只是一组单词,例如 ['apple', 'orange', 'pear']
0赞 double-beep 2/15/2020
嗨,仅供参考,我的编辑没有更改很多代码,它只是格式化了它。我知道在“内联”查看它时它看起来很混乱,但如果您想查看代码中的真正更改,请单击“并排降价”。井。。。我应该在编辑描述中写“格式化代码”,但是嗯。
0赞 GKS 7/19/2020
这是如何工作的?你能提供一个包含这个javascript的html文件吗?
11赞 Nikos M. 5/3/2014 #10

虽然这是一个较老的问题,但会根据我所做的类似工作尝试答案。

有许多方法可以解决约束问题(一般属于 NPC 复杂度类)。

这与组合优化和约束规划有关。在这种情况下,约束是网格的几何形状和单词唯一的要求等。

随机化/退火方法也可以工作(尽管在适当的设置范围内)。

高效简单可能就是终极智慧!

要求是或多或少完整的填字游戏编译器和(可视化所见即所得)构建器。

撇开所见即所得的构建器部分不谈,编译器大纲是这样的:

  1. 加载可用的单词列表(按单词长度排序,即 2,3,..,20)

  2. 在用户构造的网格上找到单词(即网格单词)(例如,长度为L的x,y处的单词,水平或垂直)(复杂度O(N) )

  3. 计算网格字的相交点(需要填充)(复杂度 O(N^2) )

  4. 计算单词列表中单词与所用字母表中各种字母的交集(这允许使用模板搜索匹配的单词,例如。cwc 使用的 Sik Cambon 论文 )( 复杂度 O(WL*AL) )

步骤 .3 和 .4 允许执行此任务:

一个。网格词与自身的交集可以创建一个“模板”,用于尝试在该网格词的可用词的关联词列表中查找匹配项(通过使用其他与该词相交的单词的字母,这些字母已经在算法的某个步骤中填充)

b.单词列表中的单词与字母表的交集可以找到与给定“模板”匹配的匹配(候选)单词(例如,第一位的“A”和第三位的“B”等)

因此,在实现这些数据结构后,使用的算法如下所示:

注意:如果网格和单词数据库是常量,则前面的步骤只需执行一次。

  1. 该算法的第一步是随机选择一个空的字槽(网格字),并从其相关的词列表中填充一个候选词(随机化可以在算法的连续执行中产生不同的解子)(复杂度 O(1) 或 O(N) )

  2. 对于每个仍然为空的字槽(与已填充的字槽有交集),计算一个约束比(这可能会有所不同,简单是该步骤中可用解的数量)并按此比率对空字槽进行排序(复杂度 O(NlogN) 或 O(N) ) )

  3. 遍历上一步计算的空字段,并为每个空位尝试一些候选解决方案(确保“弧一致性保留”,即如果使用此词,网格在此步骤之后有一个解决方案)并根据下一步的最大可用性对它们进行排序(即,如果该词在那个时候在那个地方使用,则下一步具有最大可能的解决方案, 等。( 复杂度 O(N*MaxCandidatesUsed) )

  4. 填写该单词(将其标记为已填写并转到步骤 2)

  5. 如果没有找到满足步骤 .3 条件的单词,请尝试回溯到前一步的另一个候选解决方案(此处的标准可能会有所不同)(复杂度 O(N) ) )

  6. 如果找到回溯,请使用替代方法,并选择性地重置任何可能需要重置的已填充单词(再次将它们标记为未填充)(复杂度 O(N) )

  7. 如果未找到回溯,则找不到解决方案(至少使用此配置、初始种子等)

  8. 否则,当所有字数都填满时,您就有一个解决方案

该算法对问题的解决方案树进行随机一致的游走。如果在某个时候有一个死胡同,它会回溯到前一个节点并遵循另一条路线。直到找到解决方案或各个节点的候选节点数量耗尽。

一致性部分确保找到的解决方案确实是解决方案,随机部分能够在不同的执行中产生不同的解决方案,并且平均而言具有更好的性能。

所有这些(以及其他)都是在纯JavaScript(具有并行处理和所见即所得)功能中实现的

PS2的。该算法可以很容易地并行化,以便同时生成多个(不同的)解决方案

希望这会有所帮助

评论

1赞 Jim 7/3/2014
这是为了创建密集布局(如纽约时报)还是稀疏布局?
1赞 Nikos M. 7/3/2014
@Jim,这主要用于密集布局,但也可以针对稀疏进行调整。区别在于在密集布局(例如经典、斯堪的纳维亚等)中,有网格并搜索单词,而对于自由格式布局(稀疏),有单词并搜索网格。
1赞 Jim 7/4/2014
您是否碰巧在某个地方提供了一些示例源来实现上述步骤。例如,大部分时间我都和你在一起(并且已经独立实现了大部分),但是当涉及到“计算约束比......”时,我不得不承认你失去了我。在网上搜索“STH Ratio”之类的东西对我也没有多大帮助。我的实现的问题是试图找到要填写的单词效率非常低,而且花费的时间太长。
1赞 Nikos M. 7/4/2014
@Jim,我已经使用了,但这是专门为我的工作完成的,我可能会在我的开源项目上发布一个轻量级版本,如果您需要更多帮助,请与我联系(PS 确实在某些情况下,我发布的算法可能需要太长时间,但平均而言不会)
1赞 Nikos M. 4/4/2015
@Jim,看看这个填字游戏网站(仍在进行中)istavrolexo.gr(希腊语)以及各种(密集)填字游戏(即斯堪的纳维亚语、经典语、数独语),这些填字游戏是由类似的算法(一个大型斯堪的纳维亚填字游戏)生成的)
3赞 Alex 12/5/2014 #11

我正在玩填字游戏生成器引擎,我发现这是最重要的:

0.!/usr/bin/python

  1. 一个。allwords.sort(key=len, reverse=True)

    b. 制作一些像光标这样的项目/对象,它将在矩阵中移动以便于定位 除非您以后想通过随机选择进行迭代。

  2. 第一个,拿起第一对,把它们放在 0,0 的对面和下面; 将第一个存储为我们当前的填字游戏“领导者”。

  3. 按对角线或随机顺序移动光标,对角线概率更大,移动到下一个空单元格

  4. 遍历单词 like 并使用可用空格长度来定义最大单词长度: temp=[] for w_size in range( len( w_space ), 2, -1 ) : # t for w in [ word for word in allwords if len(word) == w_size ] : # if w not in temp and putTheWord( w, w_space ) : # temp.append( w )

  5. 将单词与我使用的可用空间进行比较,即:

    w_space=['c','.','a','.','.','.'] # whereas dots are blank cells
    
    # CONVERT MULTIPLE '.' INTO '.*' FOR REGEX
    
    pattern = r''.join( [ x.letter for x in w_space ] )
    pattern = pattern.strip('.') +'.*' if pattern[-1] == '.' else pattern
    
    prog = re.compile( pattern, re.U | re.I )
    
    if prog.match( w ) :
        #
        if prog.match( w ).group() == w :
            #
            return True
    
  6. 每个成功使用的单词后,改变方向。 当所有单元格都填满时循环,或者你用完了单词,或者通过迭代的限制,然后:

# CHANGE ALL WORDS LIST inexOf1stWord = allwords.index( leading_w ) allwords = allwords[:inexOf1stWord+1][:] + allwords[inexOf1stWord+1:][:]

...并再次迭代新的填字游戏。

  1. 通过易于填写和一些估计计算来制作评分系统。 为当前填字游戏打分,并通过将其附加到 如果您的评分系统满足分数,则填字游戏列表。

  2. 在第一次迭代会话之后,再次从制作的填字游戏列表中迭代以完成工作。

通过使用更多的参数,速度可以提高一个巨大的因素。

2赞 HoldOffHunger 4/18/2018 #12

jQuery Crossword Puzzle Generator and Game

我已经为这个问题编写了一个JavaScript / jQuery解决方案:

示例演示:http://www.earthfluent.com/crossword-puzzle-demo.html

源代码: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator

我使用的算法的意图:

  1. 尽可能减少网格中不可用的方块的数量。
  2. 尽可能多地混合单词。
  3. 在极快的时间内进行计算。

Demonstration of a generated crossword puzzle.

我将描述我使用的算法:

  1. 根据共享共同字母的单词将单词组合在一起。

  2. 从这些组中,构建新数据结构(“词块”)的集合,该结构是主要单词(贯穿所有其他单词),然后是其他单词(贯穿主要单词)。

  3. 从填字游戏左上角的第一个单词块开始填字游戏。

  4. 对于其余的单词块,从填字游戏的最右下角开始,向上和向左移动,直到没有更多可用插槽可以填充。如果向上的空列多于向左的空列,则向上移动,反之亦然。

评论

0赞 Jon Glazer 6/18/2019
@holdoffhunger 你有显示填字游戏键的方法吗?带有填充字母的盒子?
0赞 HoldOffHunger 6/18/2019
@Jon Glazer:通常,您将填字游戏键发送到函数本身,但您可以将填字游戏记录为字符的二维数组,紧跟在 .只需控制台记录此值即可。别忘了,游戏中有一个“作弊模式”,你只需点击“揭示答案”,就可以立即获得价值。var crosswords = generateCrosswordBlockSources(puzzlewords);
0赞 Beejor 1/14/2020
这会在带有相邻“向下”框的地方生成带有乱码“交叉”单词的谜题,反之亦然。标准的填字游戏不是这样工作的,尽管它确实最大限度地提高了密度。
3赞 Sandipan Dey 4/20/2020 #13

这个项目出现在哈佛大学的 AI CS50 课程中。这个想法是将填字游戏生成问题表述为约束满足问题,并通过使用不同的启发式回溯来解决它,以减少搜索空间。

首先,我们需要几个输入文件:

  1. 填字游戏的结构(如下图所示,例如,“#”表示要填充的字符,“_”表示要填充的字符)

`

###_####_#
____####_#
_##_#_____
_##_#_##_#
______####
#_###_####
#_##______
#_###_##_#
_____###_#
#_######_#
##_______#    

`

  1. 输入词汇表(单词列表/词典),从中选择候选单词(如下所示)。

    a abandon ability able abortion about above abroad absence absolute absolutely ...

现在,CSP 已定义并按如下方式求解:

  1. 变量被定义为具有作为输入提供的单词(词汇表)列表中的值(即它们的域)。
  2. 每个变量由一个 3 元组表示:(grid_coordinate, direction, length),其中坐标表示相应单词的开头,direction 可以是水平或垂直的,长度定义为变量将被分配到的单词的长度。
  3. 约束由提供的结构输入定义:例如,如果水平变量和垂直变量具有共同特征,则它将表示为重叠(圆弧)约束。
  4. 现在,可以使用节点一致性和 AC3 弧一致性算法来减少域。
  5. 然后回溯以获得具有 MRV(最小剩余值)、度等的 CSP 解决方案(如果存在),启发式方法可用于选择下一个未分配的变量,而 LCV(最小约束值)等启发式方法可用于域排序,以使搜索算法更快。

下面显示了使用 CSP 求解算法的实现获得的输出:

`
███S████D█
MUCH████E█
E██A█AGENT
S██R█N██Y█
SUPPLY████
█N███O████
█I██INSIDE
█Q███E██A█
SUGAR███N█
█E██████C█
██OFFENSE█

`

以下动画显示了回溯步骤:

enter image description here

这是另一个带有孟加拉语(孟加拉语)单词列表的单词:

enter image description here

评论

0赞 nickf 4/20/2020
+1 是一个非常有趣的解释。然而,我在这里试图解决的问题的背景是,有一小组单词必须全部使用,我试图为填字游戏找到一个最佳布局,而不是从一个布局开始,然后找到合适的单词。
0赞 Emobe 6/1/2021
链接已经死了,但仍然是一个很好的答案AI CS50 course