生成填字游戏的算法


122

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

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

Answers:


62

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

  1. 按长度排序所有单词,降序排列。
  2. 选择第一个单词并将其放在板上。
  3. 下一个词。
  4. 搜索板上已经存在的所有单词,查看该单词是否存在任何可能的交集(任何常见字母)。
  5. 如果该单词可能存在,请遍历黑板上所有单词并检查是否有新单词干扰。
  6. 如果该单词没有破坏木板,则将其放在那儿,然后转到步骤3,否则,继续寻找位置(步骤4)。
  7. 继续此循环,直到所有单词都被放置或无法放置。

这使一个工作正常但通常很差的填字游戏。我对上面的基本配方进行了许多更改,以得出更好的结果。

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

7
我碰巧正在写这个程序,这也是我选择的相同算法。对于少量单词(10个或更少),该程序可以毫不费力地以毫秒为单位计算所有可能的解。算法虽然是指数的。最简单的部分是编写通过所有可能的组合进行暴力破解的基本算法。困难的部分是您需要防止十几种“捷径”,以防止程序尝试所有死胡同的解决方案。
user31586

2
“ 5. ...并检查新单词是否造成干扰”您如何解决新单词与现有单词并排放置的情况,该位置会在相邻的正方形处产生乱码?例如:LEMON ERASE如果“ LE”,“ ER”和“ MA”等不是您列表中的单词,这是错误的。另一方面,完全拒绝这种邻接关系可能会丢掉非常好的网格,例如:W LEMON ERASE NEXUS TT
George Armhold 09年

4
@Kaffeine,是的,我知道你的意思-我不得不舍弃这些选项,因为即使它们可以创建非常好的网格,也很难检查(阅读:我不会打扰),而且无论如何它都是干扰。
尼克

4
使用上述建议和我自己的一些建议,使用jQuery / Javascript构建... mlewiscs.com/crossword
MLewisCodeSolutions 2014年

@MLewisCodeSolutions看起来很棒。您是在开源吗?
GKS

23

我最近才用Python编写了自己的代码。您可以在这里找到它:http : //bryanhelmig.com/python-crossword-puzzle-generator/。它不会创建密集的NYT风格填字游戏,但是会在儿童的益智书中找到填字游戏的样式。

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

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

最后,您将获得一个不错的填字游戏或单词搜索游戏,因为它们大致相同。它通常运行得很好,但是如果您有任何改进建议,请告诉我。更大的网格以指数级的速度运行;较大的单词线性排列。更大的单词列表也有更高的机会获得更好的单词放置数。


@Neil N:换句话说,可能是更好的字母匹配可能性。也可能是一种根据每个单词所包含的不同字母的数量进行排序的方法,这将大部分导致相同的结果。
卡尔·阿德勒

@NeilN Python array.sort(key=f)是稳定的,这意味着(例如)简单地按长度对字母单词列表进行排序将使所有8个字母的单词保持字母排序。
林恩

4
@Bryan,您的网站链接对我不起作用,主要域名仅重定向到Twitter。您是否有更新的代码链接?
Michael A

2
(显然)这是Bryan生成器的克隆:github.com/jeremy886/crossword_helmig
lvictorino

20

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

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

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

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

一旦单词/线索文件达到一定大小(每天为该客户增加50-100条线索),很少会为每个填字游戏进行两次或三个以上的手动修正。


这实际上对我的情况没有帮助,因为我只会列出大约6到12个单词。矿山更像是对用户的学习练习,而不是单词拼图。不管怎么说,+ 1是有趣的算法!
尼克

1
不错的描述。我过去曾数次考虑过,但从未尝试过。现在要解决一个神奇的问题:效果如何?只是用于稀疏难题,还是用于密集难题(如本文中所述)?您需要多少线索来解决密集的难题?
dmckee ---前主持人小猫,

1
@dmckee,已经有一段时间了,但是从记忆中,即使是密集的谜题也相当不错。许多内容是在没有干预的情况下完成的,但您可能仍然有五分之一需要添加一两个额外的单词。我们正在谈论文件中的数千个单词。毫无疑问,回溯可能会有所帮助,但是对于客户来说,拒绝(例如)5个未完成的单词会比担心尝试提供额外线索更容易。五是关于我未完成的单词所看到的外部界限。
paxdiablo

16

此算法可在60秒内创建50个密集的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)

11

尽管这是一个较旧的问题,但将根据我所做的类似工作尝试给出答案。

解决约束问题的方法有很多(总的来说属于NPC复杂性类别)。

这与组合优化和约束编程有关。在这种情况下,约束条件是网格的几何形状以及单词唯一性的要求等。

随机/退火方法也可以使用(尽管在适当的设置下)。

高效的简单性可能只是终极智慧!

要求是针对一个或多或少完整的填字游戏编译器和(可视WYSIWYG)构建器。

除了所见即所得的生成器部分,编译器的轮廓是这样的:

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

  2. 在用户构造的网格上找到单词槽(即网格单词)(例如,x,y处的字符,水平或垂直长度为L)(复杂度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. 否则,当所有字槽都填满时,您只有一个解决方案

该算法对问题的解决方案树进行随机一致的遍历。如果在某个时候出现死角,它将回溯到上一个节点并遵循另一条路线。直到找到解决方案或各种节点的候选数量都用完为止。

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

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

PS2。可以轻松并行化该算法,以便同时生成多个(不同)解决方案

希望这可以帮助


1
这是用于创建密集布局(例如《纽约时报》)还是稀疏布局?
2014年

1
@Jim,这主要用于密集布局,但也可以针对稀疏进行调整。区别在于密集布局(例如经典布局,scandinavik等)中的一个具有网格并搜索单词,而自由格式布局(稀疏)则具有单词并搜索网格。
Nikos M.

1
您是否恰好在某个地方可以找到实现上述步骤的示例源。例如,我将为您提供大部分服务(并且已经独立执行了其中的大多数操作),但是当涉及到“计算约束比率...”时,我不得不承认您迷失了我。在网络上搜索“ STH比率”之类的内容对我也没有太大帮助。我的实现的问题是试图查找要填充的单词效率很低,并且花费的时间太长。
2014年

1
@Jim,我已经使用了,因为这已经被使用了,但是这是专门为完成的工作而完成的,如果您需要更多帮助,请与我联系(如果确实有更多帮助,请联系我)。在某些情况下,我发布的算法可能会花费太长的时间,但平均而言并没有)
Nikos M.

1
@Jim,看看这个填字游戏站点(仍在进行中)istavrolexo.gr(希腊语),其中包含由类似算法(大型scandinavik填字游戏)生成的各种(密集)填字游戏(例如scandinavik,classic,sudoku )。
Nikos M.

9

为什么不只是使用随机概率方法开始呢?从一个单词开始,然后反复选择一个随机单词,并尝试使其适应拼图的当前状态,而不会破坏大小等方面的限制。如果失败,则重新开始。

您会惊讶于这样的蒙特卡洛方法的工作频率。


2
是的,这是我选择的方法。您不必尝试变得非常聪明。从最长到最短排序单词。在一个循环中,选择一个随机单元格(列和行坐标),然后将该单词放在电路板上进行测试,以查看它是否跑到尾部或干扰另一个单词(在将单词写入网格之前,请检查每个单元格是否为为空或包含字母,则表示该字母与您尝试写的字母匹配)。还有其他逻辑来检查范围和内容。我蛮力生成一堆越来越小的网格,然后根据相交的单词排列最小的网格。
Max Hodges 2012年

6

这是一些基于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();
}

缺少单词对象架构,请提供wordArray

从字面上看,只是像['apple','orange','pear']之类的
单词

嗨,仅供参考,我的编辑并没有改变很多代码,只是对其进行了格式化。我知道在“内联”查看时看起来很乱,但是如果您想查看代码的真正变化,请单击“ side-by-side-markdown”。好吧...我应该在编辑描述中写了“格式化代码”,但是。
双响

这是如何运作的?您可以提供一个包含此javascript的html文件吗?
GKS

5

我将生成两个数字:长度和拼字游戏得分。假定低的拼字游戏分数意味着更容易加入(低分数=很多常见字母)。按长度降序和拼字游戏分数升序对列表进行排序。

接下来,只需向下列表。如果该单词不与现有单词交叉(分别通过单词的长度和Scrabble分数进行检查),则将其放入队列中,然后检查下一个单词。

冲洗并重复,这将产生一个填字游戏。

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


3

我一直在考虑这个问题。我的感觉是,要创建一个真正密集的填字游戏,您不能指望有限的单词列表就足够了。因此,您可能需要使用字典并将其放入“ trie”数据结构中。这将使您轻松找到填充左侧空格的单词。在特里里,实现遍历(例如,给您所有形式为“ c?t”的单词)的遍历是相当有效的。

因此,我的总体思路是:创建一种相对强力的方法(如此处所述)以创建低密度十字形,并在字典中填入空白。

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


3

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

0。!/usr/bin/python

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

    b。制作一些像光标一样的项目/对象,它将绕着矩阵走动以便于定向,除非您以后想要通过随机选择进行迭代。

  2. 第一个,拿起第一对,并将它们从0,0向下放置;将第一个存储为当前的填字游戏“ leader”。

  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

我将获得每个单词使用的每个字母的索引,以了解可能的十字架。然后,我将选择最大的单词并将其用作基础。选择下一个较大的交叉。冲洗并重复。这可能是一个NP问题。

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

我发现最困难的部分是何时无法知道某个列表。


1
我也在想遗传算法。适应度函数可以是单词被紧密地打包到网格中的程度。
阿德里安·麦卡锡

1

jQuery填字游戏和游戏

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

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

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

我使用的算法的目的是:

  1. 尽可能减少网格中无法使用的正方形的数量。
  2. 尽可能多地混合单词。
  3. 计算速度非常快。

生成的填字游戏的演示。

我将描述我使用的算法:

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

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

  3. 首先在纵横字谜的左上角位置中的第一个单词块开始纵横字谜。

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


@holdoffhunger你有显示填字游戏密钥的方法吗?装满字母的盒子?
乔恩·格雷泽

@Jon Glazer:通常,您将填字游戏键发送到函数本身,但是您可以紧接着将填字游戏记录为chars的二维数组var crosswords = generateCrosswordBlockSources(puzzlewords);。只需控制台记录此值。别忘了,游戏中有一个“作弊模式”,您只需单击“显示答案”即可立即获得该值。
HoldOffHunger19年

这会在带有相邻“向下”框的地方生成带有乱七八糟的“跨字”的拼图,反之亦然。尽管标准填字游戏确实可以使密度最大化,但它却不能像这样工作。
比霍尔

1

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

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

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

`

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

`

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

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

现在,CSP的定义和解决方法如下:

  1. 变量被定义为具有作为输入提供的单词(词汇)列表中的值(即,它们的域)。
  2. 每个变量都由3个元组表示:(grid_coordinate,方向,长度),其中坐标表示相应单词的开头,方向可以是水平或垂直,长度定义为该变量将要单词的长度分配给。
  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█

`

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

在此处输入图片说明

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

在此处输入图片说明


+1是一个非常有趣的解释。但是,我要在此处解决的问题的上下文是,必须使用一小部分单词,并且我试图为填字游戏找到最佳布局,而不是从布局开始并找到单词合适的。
尼克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.